Convert glade components to Rust and add the palette #26

Merged
savanni merged 4 commits from refactoring/rusty-widgets into main 2023-03-01 14:28:38 +00:00
4 changed files with 237 additions and 197 deletions
Showing only changes of commit cd68386df2 - Show all commits

View File

@ -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 <https://www.gnu.org/licenses/>. You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
*/ */
use cairo::{Context, Path}; use cairo::Context;
use coordinates::{hex_map::parse_data, AxialAddr}; use coordinates::{hex_map::parse_data, AxialAddr};
use gio::resources_lookup_data; use gio::resources_lookup_data;
use glib::{subclass::InitializingObject, Object}; use glib::{subclass::InitializingObject, Object};
use gtk::{ use gtk::{gio, prelude::*, subclass::prelude::*, Application, DrawingArea};
gdk_pixbuf::Pixbuf, gio, prelude::*, subclass::prelude::*, Application, CompositeTemplate, use image::io::Reader as ImageReader;
DrawingArea, Label,
};
use image::{io::Reader as ImageReader, DynamicImage};
use std::{cell::RefCell, io::Cursor, rc::Rc}; use std::{cell::RefCell, io::Cursor, rc::Rc};
mod labeled_field; mod labeled_field;
mod palette_entry;
mod tile;
mod utilities;
const APP_ID: &'static str = "com.luminescent-dreams.hex-grid"; const APP_ID: &'static str = "com.luminescent-dreams.hex-grid";
const HEX_RADIUS: f64 = 50.; const HEX_RADIUS: f64 = 50.;
const MAP_RADIUS: usize = 3; const MAP_RADIUS: usize = 3;
const DRAWING_ORIGIN: (f64, f64) = (1024. / 2., 768. / 2.); 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<String> 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() { fn main() {
gio::resources_register_include!("com.luminescent-dreams.hex-grid.gresource") gio::resources_register_include!("com.luminescent-dreams.hex-grid.gresource")
.expect("Failed to register resources"); .expect("Failed to register resources");
@ -162,49 +92,9 @@ impl ObjectSubclass for HexGridWindowPrivate {
.spacing(8) .spacing(8)
.build(); .build();
/*
let canvas_address_row = gtk::Box::builder()
.hexpand(true)
.spacing(8)
.homogeneous(true)
.build();
canvas_address_row.append(&gtk::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 canvas_address = labeled_field::LabeledField::new("Canvas Address", "-----");
let hex_address = labeled_field::LabeledField::new("Hex 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(&gtk::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() let palette = gtk::Box::builder()
.spacing(8) .spacing(8)
.orientation(gtk::Orientation::Vertical) .orientation(gtk::Orientation::Vertical)
@ -243,7 +133,8 @@ impl ObjectImpl for HexGridWindowPrivate {
.expect("map should be in the bundle") .expect("map should be in the bundle")
.to_vec(); .to_vec();
let hex_map = parse_data::<Terrain>(String::from_utf8(map_text_resource).unwrap().lines()); let hex_map =
parse_data::<tile::Terrain>(String::from_utf8(map_text_resource).unwrap().lines());
let terrain_data = resources_lookup_data( let terrain_data = resources_lookup_data(
"/com/luminescent-dreams/hex-grid/terrain.ppm", "/com/luminescent-dreams/hex-grid/terrain.ppm",
@ -255,28 +146,28 @@ impl ObjectImpl for HexGridWindowPrivate {
.unwrap(); .unwrap();
let image = reader.decode().unwrap(); let image = reader.decode().unwrap();
let badlands = Tile::new(&image, Terrain::Badlands); let badlands = tile::Tile::new(&image, tile::Terrain::Badlands);
let deep_water = Tile::new(&image, Terrain::DeepWater); let deep_water = tile::Tile::new(&image, tile::Terrain::DeepWater);
let desert = Tile::new(&image, Terrain::Desert); let desert = tile::Tile::new(&image, tile::Terrain::Desert);
let grasslands = Tile::new(&image, Terrain::Grasslands); let grasslands = tile::Tile::new(&image, tile::Terrain::Grasslands);
let mountain = Tile::new(&image, Terrain::Mountain); let mountain = tile::Tile::new(&image, tile::Terrain::Mountain);
let shallow_water = Tile::new(&image, Terrain::ShallowWater); let shallow_water = tile::Tile::new(&image, tile::Terrain::ShallowWater);
let swamp = Tile::new(&image, Terrain::Swamp); 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 self.palette
.append(&gtk::Image::from_pixbuf(Some(&deep_water.image))); .append(&palette_entry::PaletteEntry::new(&badlands));
self.palette self.palette
.append(&gtk::Image::from_pixbuf(Some(&desert.image))); .append(&palette_entry::PaletteEntry::new(&deep_water));
self.palette self.palette
.append(&gtk::Image::from_pixbuf(Some(&grasslands.image))); .append(&palette_entry::PaletteEntry::new(&desert));
self.palette self.palette
.append(&gtk::Image::from_pixbuf(Some(&mountain.image))); .append(&palette_entry::PaletteEntry::new(&grasslands));
self.palette self.palette
.append(&gtk::Image::from_pixbuf(Some(&shallow_water.image))); .append(&palette_entry::PaletteEntry::new(&mountain));
self.palette self.palette
.append(&gtk::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(); 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 translate_y = center_y - (3. as f64).sqrt() * HEX_RADIUS / 2.;
let tile = match hex_map.get(&coordinate).unwrap() { let tile = match hex_map.get(&coordinate).unwrap() {
Terrain::Mountain => &mountain, tile::Terrain::Mountain => &mountain,
Terrain::Grasslands => &grasslands, tile::Terrain::Grasslands => &grasslands,
Terrain::ShallowWater => &shallow_water, tile::Terrain::ShallowWater => &shallow_water,
Terrain::DeepWater => &deep_water, tile::Terrain::DeepWater => &deep_water,
Terrain::Badlands => &badlands, tile::Terrain::Badlands => &badlands,
Terrain::Desert => &desert, tile::Terrain::Desert => &desert,
Terrain::Swamp => &swamp, tile::Terrain::Swamp => &swamp,
_ => panic!("unhandled terrain type"), _ => panic!("unhandled terrain type"),
}; };
tile.render_on_context(context, translate_x, translate_y); 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) { fn draw_hexagon(context: &Context, center_x: f64, center_y: f64, radius: f64) {
let ul_x = center_x - radius; let ul_x = center_x - radius;
let ul_y = center_y - (3. as f64).sqrt() * radius / 2.; 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.new_path();
context.move_to(ul_x + points[0].0, ul_y + points[0].1); 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[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(); 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) { fn axial_round(q_f64: f64, r_f64: f64) -> (i32, i32) {
let s_f64 = -q_f64 - r_f64; let s_f64 = -q_f64 - r_f64;
let mut q = q_f64.round(); 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::<i32>(), r.to_int_unchecked::<i32>()) } 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,
)
}

View File

@ -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<RefCell<tile::Terrain>>,
}
#[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<PaletteEntryPrivate>) @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(&gtk::Label::new(Some(&String::from(&tile.terrain))));
row.set_child(Some(&layout));
row
}
}

108
hex-grid/src/tile.rs Normal file
View File

@ -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<String> 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,
)
}

46
hex-grid/src/utilities.rs Normal file
View File

@ -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.,
),
]
}