Import my hex-grid project

This commit is contained in:
Savanni D'Gerinel 2023-02-06 09:36:02 -05:00
parent 411e0261fc
commit 214463f6b4
9 changed files with 1886 additions and 1 deletions

View File

@ -23,8 +23,10 @@
pkgs.mkShell { pkgs.mkShell {
name = "ld-tools-devshell"; name = "ld-tools-devshell";
buildInputs = [ buildInputs = [
pkgs.pkg-config pkgs.glade
pkgs.gtk4
pkgs.openssl pkgs.openssl
pkgs.pkg-config
rust rust
]; ];
}; };

1382
hex-grid/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

17
hex-grid/Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "hex-grid"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cairo-rs = "0.16"
gio = "0.16"
glib = "0.16"
gtk = { version = "0.5", package = "gtk4" }
coordinates = { path = "../coordinates" }
image = { version = "0.24" }
[build-dependencies]
glib-build-tools = "0.16"

7
hex-grid/build.rs Normal file
View File

@ -0,0 +1,7 @@
fn main() {
glib_build_tools::compile_resources(
"resources",
"resources/resources.gresources.xml",
"com.luminescent-dreams.hex-grid.gresource",
);
}

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="HexGridWindow" parent="GtkApplicationWindow">
<property name="can-focus">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">8</property>
<child>
<object class="GtkDrawingArea" id="drawing_area">
<property name="width-request">1024</property>
<property name="height-request">768</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">8</property>
<property name="margin-end">8</property>
<property name="margin-top">8</property>
<property name="margin-bottom">8</property>
<property name="hexpand">True</property>
</object>
</child>
<child>
<object class="GtkBox">
<property name="width-request">100</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">4</property>
<property name="margin-end">4</property>
<property name="margin-top">4</property>
<property name="margin-bottom">4</property>
<property name="orientation">vertical</property>
<property name="spacing">8</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="spacing">8</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Canvas Address</property>
</object>
</child>
<child>
<object class="GtkLabel" id="canvas_address">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">4</property>
<property name="margin-end">4</property>
<property name="margin-top">4</property>
<property name="margin-bottom">4</property>
<property name="label" translatable="yes">-----</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="spacing">8</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Hexagon Address</property>
</object>
</child>
<child>
<object class="GtkLabel" id="hex_address">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">4</property>
<property name="margin-end">4</property>
<property name="margin-top">4</property>
<property name="margin-bottom">4</property>
<property name="label" translatable="yes">-----</property>
</object>
</child>
</object>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

View File

@ -0,0 +1,37 @@
[0, 0] m
[-3, 2] sw
[1, -2] g
[0, 3] g
[2, 1] g
[0, -2] g
[-1, 0] g
[-2, 2] g
[3, -3] sw
[1, 2] g
[1, 1] d
[2, -2] b
[-3, 0] sw
[-2, 0] g
[2, -3] sw
[-1, -1] g
[-2, 3] sw
[1, -3] sw
[-3, 3] sw
[-1, 1] g
[1, 0] g
[0, 1] g
[-1, 2] s
[1, -1] g
[2, -1] g
[0, -3] sw
[0, 2] g
[3, 0] sw
[-1, 3] sw
[0, -1] g
[3, -1] sw
[-3, 1] sw
[2, 0] g
[-2, 1] g
[3, -2] sw
[-1, -2] sw
[-2, -1] dw

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/luminescent-dreams/hex-grid/">
<file>main.glade</file>
<file>terrain.ppm</file>
<file>map.txt</file>
</gresource>
</gresources>

Binary file not shown.

333
hex-grid/src/main.rs Normal file
View File

@ -0,0 +1,333 @@
use cairo::{Context, Path};
use coordinates::{hex_map::parse_data, AxialAddr};
use gio::resources_lookup_data;
use glib::{subclass::InitializingObject, Object};
use gtk::{
gdk_pixbuf::Pixbuf, gio, prelude::*, subclass::prelude::*, Application, CompositeTemplate,
DrawingArea, Label,
};
use image::io::Reader as ImageReader;
use std::{cell::RefCell, io::Cursor, rc::Rc};
const APP_ID: &'static str = "com.luminescent-dreams.hex-grid";
const HEX_RADIUS: f64 = 50.;
const MAP_RADIUS: usize = 3;
const DRAWING_ORIGIN: (f64, f64) = (1024. / 2., 768. / 2.);
#[derive(Clone, Debug)]
enum Tile {
Empty,
Mountain,
Grasslands,
ShallowWater,
DeepWater,
Badlands,
Desert,
Swamp,
}
impl Tile {
fn color(&self) -> (f64, f64, f64) {
match self {
Self::Empty => (0., 0., 0.),
Self::Mountain => (0.5, 0.5, 0.5),
Self::Grasslands => (0., 1., 0.),
Self::ShallowWater => (0.5, 0.5, 1.),
Self::DeepWater => (0., 0., 1.),
Self::Badlands => (0.5, 0.25, 0.),
Self::Desert => (1., 1., 0.),
Self::Swamp => (0., 0.25, 0.),
}
}
}
impl Default for Tile {
fn default() -> Self {
Self::Empty
}
}
impl From<&str> for Tile {
fn from(s: &str) -> Self {
match s {
"m" => Self::Mountain,
"g" => Self::Grasslands,
"sw" => Self::ShallowWater,
"dw" => Self::DeepWater,
"b" => Self::Badlands,
"d" => Self::Desert,
"s" => Self::Swamp,
_ => Self::Empty,
}
}
}
impl From<String> for Tile {
fn from(s: String) -> Self {
Self::from(s.as_ref())
}
}
fn main() {
gio::resources_register_include!("com.luminescent-dreams.hex-grid.gresource")
.expect("Failed to register resources");
let app = Application::builder().application_id(APP_ID).build();
app.connect_activate(|app| {
let window = HexGridWindow::new(app);
window.present();
});
app.run();
}
#[derive(CompositeTemplate, Default)]
#[template(resource = "/com/luminescent-dreams/hex-grid/main.glade")]
pub struct HexGridWindowPrivate {
#[template_child]
pub drawing_area: TemplateChild<DrawingArea>,
#[template_child]
pub hex_address: TemplateChild<Label>,
#[template_child]
pub canvas_address: TemplateChild<Label>,
current_coordinate: Rc<RefCell<Option<AxialAddr>>>,
}
#[glib::object_subclass]
impl ObjectSubclass for HexGridWindowPrivate {
const NAME: &'static str = "HexGridWindow";
type Type = HexGridWindow;
type ParentType = gtk::ApplicationWindow;
fn class_init(c: &mut Self::Class) {
c.bind_template();
}
fn instance_init(obj: &InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for HexGridWindowPrivate {
fn constructed(&self) {
self.parent_constructed();
let map_text_resource = resources_lookup_data(
"/com/luminescent-dreams/hex-grid/map.txt",
gio::ResourceLookupFlags::NONE,
)
.expect("map should be in the bundle")
.to_vec();
let hex_map = parse_data::<Tile>(String::from_utf8(map_text_resource).unwrap().lines());
let terrain_data = resources_lookup_data(
"/com/luminescent-dreams/hex-grid/terrain.ppm",
gio::ResourceLookupFlags::NONE,
)
.unwrap();
let reader = ImageReader::new(Cursor::new(terrain_data))
.with_guessed_format()
.unwrap();
let image = reader.decode().unwrap();
let image_deep_water = pixbuf_from_image_tile(image.clone().crop(0, 0, 100, 88));
let image_shallow_water = pixbuf_from_image_tile(image.clone().crop(100, 0, 100, 88));
let image_grasslands = pixbuf_from_image_tile(image.clone().crop(200, 0, 100, 88));
let image_desert = pixbuf_from_image_tile(image.clone().crop(300, 0, 100, 88));
let image_mountain = pixbuf_from_image_tile(image.clone().crop(0, 88, 100, 88));
let image_badlands = pixbuf_from_image_tile(image.clone().crop(100, 88, 100, 88));
let image_swamp = pixbuf_from_image_tile(image.clone().crop(0, 176, 100, 88));
/*
let image_pb = Pixbuf::from_bytes(
&glib::Bytes::from(image.as_bytes()),
gtk::gdk_pixbuf::Colorspace::Rgb,
false,
8,
image.width() as i32,
image.height() as i32,
image.to_rgb8().sample_layout().height_stride as i32,
);
*/
let motion_controller = gtk::EventControllerMotion::new();
{
let canvas_address = self.canvas_address.clone();
let hex_address = self.hex_address.clone();
let c = self.current_coordinate.clone();
motion_controller.connect_motion(move |_, x, y| {
let norm_x = x - DRAWING_ORIGIN.0;
let norm_y = y - DRAWING_ORIGIN.1;
let q = (2. / 3. * norm_x) / HEX_RADIUS;
let r = (-1. / 3. * norm_x + (3. as f64).sqrt() / 3. * norm_y) / HEX_RADIUS;
let (q, r) = axial_round(q, r);
let coordinate = AxialAddr::new(q, r);
canvas_address.set_label(&format!("{:.0} {:.0}", x, y));
if coordinate.distance(&AxialAddr::origin()) > MAP_RADIUS {
hex_address.set_label(&format!("-----"));
*c.borrow_mut() = None;
} else {
hex_address.set_label(&format!("{:.0} {:.0}", coordinate.q(), coordinate.r()));
*c.borrow_mut() = Some(coordinate);
}
});
}
{
self.drawing_area.set_draw_func(move |_, context, _, _| {
context.set_source_rgb(0., 0., 0.);
let _ = context.paint();
context.set_line_width(2.);
let clip_path = hexagon_path(context, 100., 88.);
context.set_source_pixbuf(&image_deep_water, 0., 0.);
context.append_path(&clip_path);
context.clip();
context.paint().expect("paint should succeed");
context.set_source_pixbuf(&image_shallow_water, 150., 0.);
context.paint().expect("paint should succeed");
context.set_source_pixbuf(&image_grasslands, 300., 0.);
context.paint().expect("paint should succeed");
context.set_source_pixbuf(&image_desert, 450., 0.);
context.paint().expect("paint should succeed");
context.set_source_pixbuf(&image_mountain, 0., 100.);
context.paint().expect("paint should succeed");
context.set_source_pixbuf(&image_badlands, 150., 100.);
context.paint().expect("paint should succeed");
context.set_source_pixbuf(&image_swamp, 0., 200.);
context.paint().expect("paint should succeed");
/*
for coordinate in vec![AxialAddr::origin()]
.into_iter()
.chain(AxialAddr::origin().addresses(MAP_RADIUS))
{
let center_x =
DRAWING_ORIGIN.0 + HEX_RADIUS * (3. / 2. * (coordinate.q() as f64));
let center_y = DRAWING_ORIGIN.1
+ HEX_RADIUS
* ((3. as f64).sqrt() / 2. * (coordinate.q() as f64)
+ (3. as f64).sqrt() * (coordinate.r() as f64));
let tile = hex_map.get(&coordinate).unwrap();
let color = tile.color();
context.set_source_rgb(color.0, color.1, color.2);
draw_hexagon(context, center_x, center_y, HEX_RADIUS);
let _ = context.fill();
}
*/
});
}
self.drawing_area.add_controller(&motion_controller);
}
}
impl WidgetImpl for HexGridWindowPrivate {}
impl WindowImpl for HexGridWindowPrivate {}
impl ApplicationWindowImpl for HexGridWindowPrivate {}
glib::wrapper! {
pub struct HexGridWindow(ObjectSubclass<HexGridWindowPrivate>) @extends gtk::ApplicationWindow, gtk::Window, gtk::Widget,
@implements gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget;
}
impl HexGridWindow {
pub fn new(app: &Application) -> Self {
Object::builder().property("application", app).build()
}
}
fn draw_hexagon(context: &Context, center_x: f64, center_y: f64, radius: f64) {
let ul_x = center_x - radius;
let ul_y = center_y - (3. as f64).sqrt() * radius / 2.;
let points: Vec<(f64, f64)> = hexagon(radius * 2., (3. as f64).sqrt() * radius);
context.new_path();
context.move_to(ul_x + points[0].0, ul_y + points[0].1);
context.line_to(ul_x + points[1].0, ul_y + points[1].1);
context.line_to(ul_x + points[2].0, ul_y + points[2].1);
context.line_to(ul_x + points[3].0, ul_y + points[3].1);
context.line_to(ul_x + points[4].0, ul_y + points[4].1);
context.line_to(ul_x + points[5].0, ul_y + points[5].1);
context.close_path();
}
fn hexagon_path(context: &Context, width: f64, height: f64) -> Path {
context.new_path();
let points = hexagon(width, height);
context.move_to(points[0].0, points[0].1);
context.line_to(points[1].0, points[1].1);
context.line_to(points[2].0, points[2].1);
context.line_to(points[3].0, points[3].1);
context.line_to(points[4].0, points[4].1);
context.line_to(points[5].0, points[5].1);
context.copy_path().expect("to successfully copy a path")
}
fn hexagon(width: f64, height: f64) -> Vec<(f64, f64)> {
let center_x = width / 2.;
let center_y = height / 2.;
let radius = width / 2.;
vec![
(center_x + radius, center_y),
(
center_x + radius / 2.,
center_y + (3. as f64).sqrt() * radius / 2.,
),
(
center_x - radius / 2.,
center_y + (3. as f64).sqrt() * radius / 2.,
),
(center_x - radius, center_y),
(
center_x - radius / 2.,
center_y - (3. as f64).sqrt() * radius / 2.,
),
(
center_x + radius / 2.,
center_y - (3. as f64).sqrt() * radius / 2.,
),
]
}
fn axial_round(q_f64: f64, r_f64: f64) -> (i32, i32) {
let s_f64 = -q_f64 - r_f64;
let mut q = q_f64.round();
let mut r = r_f64.round();
let s = s_f64.round();
let q_diff = (q - q_f64).abs();
let r_diff = (r - r_f64).abs();
let s_diff = (s - s_f64).abs();
if q_diff > r_diff && q_diff > s_diff {
q = -r - s;
} else if r_diff > s_diff {
r = -q - s;
}
unsafe { (q.to_int_unchecked::<i32>(), r.to_int_unchecked::<i32>()) }
}
fn pixbuf_from_image_tile(image: image::DynamicImage) -> Pixbuf {
Pixbuf::from_bytes(
&glib::Bytes::from(image.as_bytes()),
gtk::gdk_pixbuf::Colorspace::Rgb,
false,
8,
image.width() as i32,
image.height() as i32,
image.to_rgb8().sample_layout().height_stride as i32,
)
}