diff --git a/hex-grid/src/main.rs b/hex-grid/src/main.rs index b3b88b6..61f0b54 100644 --- a/hex-grid/src/main.rs +++ b/hex-grid/src/main.rs @@ -10,94 +10,24 @@ Luminescent Dreams Tools is distributed in the hope that it will be useful, but You should have received a copy of the GNU General Public License along with Lumeto. If not, see . */ -use cairo::{Context, Path}; +use cairo::Context; 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, DynamicImage}; +use gtk::{gio, prelude::*, subclass::prelude::*, Application, DrawingArea}; +use image::io::Reader as ImageReader; use std::{cell::RefCell, io::Cursor, rc::Rc}; mod labeled_field; +mod palette_entry; +mod tile; +mod utilities; 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 Terrain { - Badlands, - DeepWater, - Desert, - Empty, - Grasslands, - Mountain, - ShallowWater, - Swamp, -} - -impl Default for Terrain { - fn default() -> Self { - Self::Empty - } -} - -impl From<&str> for Terrain { - 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 for Terrain { - fn from(s: String) -> Self { - Self::from(s.as_ref()) - } -} - -struct Tile { - terrain: Terrain, - image: Pixbuf, -} - -impl Tile { - fn new(source: &DynamicImage, terrain: Terrain) -> Tile { - let image = match terrain { - Terrain::DeepWater => pixbuf_from_image_tile(source.clone().crop(0, 0, 100, 88)), - Terrain::ShallowWater => pixbuf_from_image_tile(source.clone().crop(100, 0, 100, 88)), - Terrain::Grasslands => pixbuf_from_image_tile(source.clone().crop(200, 0, 100, 88)), - Terrain::Desert => pixbuf_from_image_tile(source.clone().crop(300, 0, 100, 88)), - Terrain::Mountain => pixbuf_from_image_tile(source.clone().crop(0, 88, 100, 88)), - Terrain::Badlands => pixbuf_from_image_tile(source.clone().crop(100, 88, 100, 88)), - Terrain::Swamp => pixbuf_from_image_tile(source.clone().crop(0, 176, 100, 88)), - Terrain::Empty => pixbuf_from_image_tile(source.clone().crop(300, 176, 100, 88)), - }; - - Tile { terrain, image } - } - - fn render_on_context(&self, context: &Context, translate_x: f64, translate_y: f64) { - context.save().unwrap(); - context.append_path(&hexagon_path(context, translate_x, translate_y, 100., 88.)); - context.clip(); - context.set_source_pixbuf(&self.image, translate_x, translate_y); - context.paint().expect("paint should succeed"); - context.restore().unwrap(); - } -} - fn main() { gio::resources_register_include!("com.luminescent-dreams.hex-grid.gresource") .expect("Failed to register resources"); @@ -162,49 +92,9 @@ impl ObjectSubclass for HexGridWindowPrivate { .spacing(8) .build(); - /* - let canvas_address_row = gtk::Box::builder() - .hexpand(true) - .spacing(8) - .homogeneous(true) - .build(); - - canvas_address_row.append(>k::Label::builder().label("Canvas Address").build()); - - let canvas_address = gtk::Label::builder() - .label("-----") - .margin_start(4) - .margin_end(4) - .margin_top(4) - .margin_bottom(4) - .build(); - - canvas_address_row.append(&canvas_address); - */ - let canvas_address = labeled_field::LabeledField::new("Canvas Address", "-----"); let hex_address = labeled_field::LabeledField::new("Hex Address", "-----"); - /* - let hex_address_row = gtk::Box::builder() - .hexpand(true) - .spacing(8) - .homogeneous(true) - .build(); - - hex_address_row.append(>k::Label::builder().label("Hex Address").build()); - - let hex_address = gtk::Label::builder() - .label("-----") - .margin_start(4) - .margin_end(4) - .margin_top(4) - .margin_bottom(4) - .build(); - - hex_address_row.append(&hex_address); - */ - let palette = gtk::Box::builder() .spacing(8) .orientation(gtk::Orientation::Vertical) @@ -243,7 +133,8 @@ impl ObjectImpl for HexGridWindowPrivate { .expect("map should be in the bundle") .to_vec(); - let hex_map = parse_data::(String::from_utf8(map_text_resource).unwrap().lines()); + let hex_map = + parse_data::(String::from_utf8(map_text_resource).unwrap().lines()); let terrain_data = resources_lookup_data( "/com/luminescent-dreams/hex-grid/terrain.ppm", @@ -255,28 +146,28 @@ impl ObjectImpl for HexGridWindowPrivate { .unwrap(); let image = reader.decode().unwrap(); - let badlands = Tile::new(&image, Terrain::Badlands); - let deep_water = Tile::new(&image, Terrain::DeepWater); - let desert = Tile::new(&image, Terrain::Desert); - let grasslands = Tile::new(&image, Terrain::Grasslands); - let mountain = Tile::new(&image, Terrain::Mountain); - let shallow_water = Tile::new(&image, Terrain::ShallowWater); - let swamp = Tile::new(&image, Terrain::Swamp); + let badlands = tile::Tile::new(&image, tile::Terrain::Badlands); + let deep_water = tile::Tile::new(&image, tile::Terrain::DeepWater); + let desert = tile::Tile::new(&image, tile::Terrain::Desert); + let grasslands = tile::Tile::new(&image, tile::Terrain::Grasslands); + let mountain = tile::Tile::new(&image, tile::Terrain::Mountain); + let shallow_water = tile::Tile::new(&image, tile::Terrain::ShallowWater); + let swamp = tile::Tile::new(&image, tile::Terrain::Swamp); - let badlands_image = gtk::Image::from_pixbuf(Some(&badlands.image)); - self.palette.append(&badlands_image); self.palette - .append(>k::Image::from_pixbuf(Some(&deep_water.image))); + .append(&palette_entry::PaletteEntry::new(&badlands)); self.palette - .append(>k::Image::from_pixbuf(Some(&desert.image))); + .append(&palette_entry::PaletteEntry::new(&deep_water)); self.palette - .append(>k::Image::from_pixbuf(Some(&grasslands.image))); + .append(&palette_entry::PaletteEntry::new(&desert)); self.palette - .append(>k::Image::from_pixbuf(Some(&mountain.image))); + .append(&palette_entry::PaletteEntry::new(&grasslands)); self.palette - .append(>k::Image::from_pixbuf(Some(&shallow_water.image))); + .append(&palette_entry::PaletteEntry::new(&mountain)); self.palette - .append(>k::Image::from_pixbuf(Some(&swamp.image))); + .append(&palette_entry::PaletteEntry::new(&shallow_water)); + self.palette + .append(&palette_entry::PaletteEntry::new(&swamp)); let motion_controller = gtk::EventControllerMotion::new(); { @@ -324,13 +215,13 @@ impl ObjectImpl for HexGridWindowPrivate { let translate_y = center_y - (3. as f64).sqrt() * HEX_RADIUS / 2.; let tile = match hex_map.get(&coordinate).unwrap() { - Terrain::Mountain => &mountain, - Terrain::Grasslands => &grasslands, - Terrain::ShallowWater => &shallow_water, - Terrain::DeepWater => &deep_water, - Terrain::Badlands => &badlands, - Terrain::Desert => &desert, - Terrain::Swamp => &swamp, + tile::Terrain::Mountain => &mountain, + tile::Terrain::Grasslands => &grasslands, + tile::Terrain::ShallowWater => &shallow_water, + tile::Terrain::DeepWater => &deep_water, + tile::Terrain::Badlands => &badlands, + tile::Terrain::Desert => &desert, + tile::Terrain::Swamp => &swamp, _ => panic!("unhandled terrain type"), }; tile.render_on_context(context, translate_x, translate_y); @@ -361,7 +252,7 @@ impl HexGridWindow { 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); + let points: Vec<(f64, f64)> = utilities::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); @@ -372,51 +263,6 @@ fn draw_hexagon(context: &Context, center_x: f64, center_y: f64, radius: f64) { context.close_path(); } -fn hexagon_path( - context: &Context, - translate_x: f64, - translate_y: f64, - width: f64, - height: f64, -) -> Path { - context.new_path(); - let points = hexagon(width, height); - context.move_to(translate_x + points[0].0, translate_y + points[0].1); - context.line_to(translate_x + points[1].0, translate_y + points[1].1); - context.line_to(translate_x + points[2].0, translate_y + points[2].1); - context.line_to(translate_x + points[3].0, translate_y + points[3].1); - context.line_to(translate_x + points[4].0, translate_y + points[4].1); - context.line_to(translate_x + points[5].0, translate_y + 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(); @@ -434,15 +280,3 @@ fn axial_round(q_f64: f64, r_f64: f64) -> (i32, i32) { } unsafe { (q.to_int_unchecked::(), r.to_int_unchecked::()) } } - -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, - ) -} diff --git a/hex-grid/src/palette_entry.rs b/hex-grid/src/palette_entry.rs new file mode 100644 index 0000000..6942202 --- /dev/null +++ b/hex-grid/src/palette_entry.rs @@ -0,0 +1,52 @@ +use crate::tile; +use glib::Object; +use gtk::{prelude::*, subclass::prelude::*}; +use std::{cell::RefCell, rc::Rc}; + +pub struct PaletteEntryPrivate { + terrain: Rc>, +} + +#[glib::object_subclass] +impl ObjectSubclass for PaletteEntryPrivate { + const NAME: &'static str = "PaletteEntry"; + type Type = PaletteEntry; + type ParentType = gtk::ListBoxRow; + + fn new() -> Self { + Self { + terrain: Rc::new(RefCell::new(tile::Terrain::Empty)), + } + } +} + +impl ObjectImpl for PaletteEntryPrivate {} +impl WidgetImpl for PaletteEntryPrivate {} +impl ListBoxRowImpl for PaletteEntryPrivate { + fn activate(&self) { + println!("row activated: {:?}", self.terrain); + } +} + +glib::wrapper! { + pub struct PaletteEntry(ObjectSubclass) @extends gtk::ListBoxRow, gtk::Widget; +} + +impl PaletteEntry { + pub fn new(tile: &tile::Tile) -> Self { + let row: Self = Object::builder().build(); + *row.imp().terrain.borrow_mut() = tile.terrain.clone(); + let layout = gtk::Box::builder().spacing(8).build(); + + let image = gtk::Image::from_pixbuf(Some(&tile.image)); + image.set_width_request(100); + image.set_height_request(88); + + layout.append(&image); + layout.append(>k::Label::new(Some(&String::from(&tile.terrain)))); + + row.set_child(Some(&layout)); + + row + } +} diff --git a/hex-grid/src/tile.rs b/hex-grid/src/tile.rs new file mode 100644 index 0000000..ced3870 --- /dev/null +++ b/hex-grid/src/tile.rs @@ -0,0 +1,108 @@ +use crate::utilities; +use cairo::Context; +use gtk::{gdk_pixbuf::Pixbuf, prelude::*}; +use image::DynamicImage; + +#[derive(Clone, Debug)] +pub enum Terrain { + Badlands, + DeepWater, + Desert, + Empty, + Grasslands, + Mountain, + ShallowWater, + Swamp, +} + +impl Default for Terrain { + fn default() -> Self { + Self::Empty + } +} + +impl From<&str> for Terrain { + 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 for Terrain { + fn from(s: String) -> Self { + Self::from(s.as_ref()) + } +} + +impl From<&Terrain> for String { + fn from(t: &Terrain) -> Self { + match t { + Terrain::Badlands => "Badlands", + Terrain::DeepWater => "Deep Water", + Terrain::Desert => "Desert", + Terrain::Empty => "Empty", + Terrain::Grasslands => "Grasslands", + Terrain::Mountain => "Mountain", + Terrain::ShallowWater => "Shallow Water", + Terrain::Swamp => "Swamp", + } + .to_owned() + } +} + +pub struct Tile { + pub terrain: Terrain, + pub image: Pixbuf, +} + +impl Tile { + pub fn new(source: &DynamicImage, terrain: Terrain) -> Tile { + let image = match terrain { + Terrain::DeepWater => pixbuf_from_image_tile(source.clone().crop(0, 0, 100, 88)), + Terrain::ShallowWater => pixbuf_from_image_tile(source.clone().crop(100, 0, 100, 88)), + Terrain::Grasslands => pixbuf_from_image_tile(source.clone().crop(200, 0, 100, 88)), + Terrain::Desert => pixbuf_from_image_tile(source.clone().crop(300, 0, 100, 88)), + Terrain::Mountain => pixbuf_from_image_tile(source.clone().crop(0, 88, 100, 88)), + Terrain::Badlands => pixbuf_from_image_tile(source.clone().crop(100, 88, 100, 88)), + Terrain::Swamp => pixbuf_from_image_tile(source.clone().crop(0, 176, 100, 88)), + Terrain::Empty => pixbuf_from_image_tile(source.clone().crop(300, 176, 100, 88)), + }; + + Tile { terrain, image } + } + + pub fn render_on_context(&self, context: &Context, translate_x: f64, translate_y: f64) { + context.save().unwrap(); + context.append_path(&utilities::hexagon_path( + context, + translate_x, + translate_y, + 100., + 88., + )); + context.clip(); + context.set_source_pixbuf(&self.image, translate_x, translate_y); + context.paint().expect("paint should succeed"); + context.restore().unwrap(); + } +} + +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, + ) +} diff --git a/hex-grid/src/utilities.rs b/hex-grid/src/utilities.rs new file mode 100644 index 0000000..613dce6 --- /dev/null +++ b/hex-grid/src/utilities.rs @@ -0,0 +1,46 @@ +use cairo::{Context, Path}; + +pub fn hexagon_path( + context: &Context, + translate_x: f64, + translate_y: f64, + width: f64, + height: f64, +) -> Path { + context.new_path(); + let points = hexagon(width, height); + context.move_to(translate_x + points[0].0, translate_y + points[0].1); + context.line_to(translate_x + points[1].0, translate_y + points[1].1); + context.line_to(translate_x + points[2].0, translate_y + points[2].1); + context.line_to(translate_x + points[3].0, translate_y + points[3].1); + context.line_to(translate_x + points[4].0, translate_y + points[4].1); + context.line_to(translate_x + points[5].0, translate_y + points[5].1); + context.copy_path().expect("to successfully copy a path") +} + +pub 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., + ), + ] +}