Convert glade components to Rust and add the palette (#26)
Co-authored-by: Savanni D'Gerinel <savanni@luminescent-dreams.com> Reviewed-on: savanni/tools#26
This commit is contained in:
parent
1016ba756c
commit
226329ab6d
|
@ -1,99 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,7 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<gresources>
|
<gresources>
|
||||||
<gresource prefix="/com/luminescent-dreams/hex-grid/">
|
<gresource prefix="/com/luminescent-dreams/hex-grid/">
|
||||||
<file>main.glade</file>
|
|
||||||
<file>terrain.ppm</file>
|
<file>terrain.ppm</file>
|
||||||
<file>map.txt</file>
|
<file>map.txt</file>
|
||||||
</gresource>
|
</gresource>
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
use glib::Object;
|
||||||
|
use gtk::{prelude::*, subclass::prelude::*};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct LabeledFieldPrivate {
|
||||||
|
value: gtk::Label,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for LabeledFieldPrivate {
|
||||||
|
const NAME: &'static str = "LabeledField";
|
||||||
|
type Type = LabeledField;
|
||||||
|
type ParentType = gtk::Box;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for LabeledFieldPrivate {}
|
||||||
|
impl WidgetImpl for LabeledFieldPrivate {}
|
||||||
|
impl BoxImpl for LabeledFieldPrivate {}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct LabeledField(ObjectSubclass<LabeledFieldPrivate>) @extends gtk::Box, gtk::Widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LabeledField {
|
||||||
|
pub fn new(label: &str, default_value: &str) -> Self {
|
||||||
|
let field: Self = Object::builder().build();
|
||||||
|
field.set_hexpand(true);
|
||||||
|
field.set_spacing(8);
|
||||||
|
field.set_homogeneous(true);
|
||||||
|
field.append(>k::Label::new(Some(label)));
|
||||||
|
field.append(&field.imp().value);
|
||||||
|
field.imp().value.set_label(default_value);
|
||||||
|
field
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_value(&self, value: &str) {
|
||||||
|
self.imp().value.set_label(value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,92 +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 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 {
|
|
||||||
Empty,
|
|
||||||
Mountain,
|
|
||||||
Grasslands,
|
|
||||||
ShallowWater,
|
|
||||||
DeepWater,
|
|
||||||
Badlands,
|
|
||||||
Desert,
|
|
||||||
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");
|
||||||
|
@ -110,15 +42,13 @@ fn main() {
|
||||||
app.run();
|
app.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(CompositeTemplate, Default)]
|
|
||||||
#[template(resource = "/com/luminescent-dreams/hex-grid/main.glade")]
|
|
||||||
pub struct HexGridWindowPrivate {
|
pub struct HexGridWindowPrivate {
|
||||||
#[template_child]
|
layout: gtk::Box,
|
||||||
pub drawing_area: TemplateChild<DrawingArea>,
|
palette: gtk::Box,
|
||||||
#[template_child]
|
|
||||||
pub hex_address: TemplateChild<Label>,
|
drawing_area: DrawingArea,
|
||||||
#[template_child]
|
hex_address: labeled_field::LabeledField,
|
||||||
pub canvas_address: TemplateChild<Label>,
|
canvas_address: labeled_field::LabeledField,
|
||||||
|
|
||||||
current_coordinate: Rc<RefCell<Option<AxialAddr>>>,
|
current_coordinate: Rc<RefCell<Option<AxialAddr>>>,
|
||||||
}
|
}
|
||||||
|
@ -129,17 +59,71 @@ impl ObjectSubclass for HexGridWindowPrivate {
|
||||||
type Type = HexGridWindow;
|
type Type = HexGridWindow;
|
||||||
type ParentType = gtk::ApplicationWindow;
|
type ParentType = gtk::ApplicationWindow;
|
||||||
|
|
||||||
fn class_init(c: &mut Self::Class) {
|
fn new() -> Self {
|
||||||
c.bind_template();
|
println!("hexGridWindowPrivate::new()");
|
||||||
}
|
let current_coordinate = Rc::new(RefCell::new(None));
|
||||||
|
|
||||||
fn instance_init(obj: &InitializingObject<Self>) {
|
let drawing_area = DrawingArea::builder()
|
||||||
obj.init_template();
|
.width_request(1024)
|
||||||
|
.height_request(768)
|
||||||
|
.margin_start(8)
|
||||||
|
.margin_end(8)
|
||||||
|
.margin_top(8)
|
||||||
|
.margin_bottom(8)
|
||||||
|
.hexpand(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let layout = gtk::Box::builder()
|
||||||
|
.homogeneous(false)
|
||||||
|
.spacing(8)
|
||||||
|
.can_focus(false)
|
||||||
|
.visible(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let sidebar = gtk::Box::builder()
|
||||||
|
.orientation(gtk::Orientation::Vertical)
|
||||||
|
.width_request(100)
|
||||||
|
.visible(true)
|
||||||
|
.can_focus(false)
|
||||||
|
.margin_start(4)
|
||||||
|
.margin_end(4)
|
||||||
|
.margin_top(4)
|
||||||
|
.margin_bottom(4)
|
||||||
|
.spacing(8)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let canvas_address = labeled_field::LabeledField::new("Canvas Address", "-----");
|
||||||
|
let hex_address = labeled_field::LabeledField::new("Hex Address", "-----");
|
||||||
|
|
||||||
|
let palette = gtk::Box::builder()
|
||||||
|
.spacing(8)
|
||||||
|
.orientation(gtk::Orientation::Vertical)
|
||||||
|
.hexpand(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
sidebar.append(&canvas_address);
|
||||||
|
sidebar.append(&hex_address);
|
||||||
|
sidebar.append(&palette);
|
||||||
|
|
||||||
|
layout.append(&drawing_area);
|
||||||
|
layout.append(&sidebar);
|
||||||
|
|
||||||
|
layout.show();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
drawing_area,
|
||||||
|
hex_address,
|
||||||
|
canvas_address,
|
||||||
|
current_coordinate,
|
||||||
|
layout,
|
||||||
|
palette,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectImpl for HexGridWindowPrivate {
|
impl ObjectImpl for HexGridWindowPrivate {
|
||||||
fn constructed(&self) {
|
fn constructed(&self) {
|
||||||
|
println!("HexGridWindowPrivate::constructed()");
|
||||||
self.parent_constructed();
|
self.parent_constructed();
|
||||||
|
|
||||||
let map_text_resource = resources_lookup_data(
|
let map_text_resource = resources_lookup_data(
|
||||||
|
@ -149,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",
|
||||||
|
@ -161,13 +146,28 @@ impl ObjectImpl for HexGridWindowPrivate {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let image = reader.decode().unwrap();
|
let image = reader.decode().unwrap();
|
||||||
let deep_water = Tile::new(&image, Terrain::DeepWater);
|
let badlands = tile::Tile::new(&image, tile::Terrain::Badlands);
|
||||||
let shallow_water = Tile::new(&image, Terrain::ShallowWater);
|
let deep_water = tile::Tile::new(&image, tile::Terrain::DeepWater);
|
||||||
let grasslands = Tile::new(&image, Terrain::Grasslands);
|
let desert = tile::Tile::new(&image, tile::Terrain::Desert);
|
||||||
let desert = Tile::new(&image, Terrain::Desert);
|
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 badlands = Tile::new(&image, Terrain::Badlands);
|
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);
|
||||||
|
|
||||||
|
self.palette
|
||||||
|
.append(&palette_entry::PaletteEntry::new(&badlands));
|
||||||
|
self.palette
|
||||||
|
.append(&palette_entry::PaletteEntry::new(&deep_water));
|
||||||
|
self.palette
|
||||||
|
.append(&palette_entry::PaletteEntry::new(&desert));
|
||||||
|
self.palette
|
||||||
|
.append(&palette_entry::PaletteEntry::new(&grasslands));
|
||||||
|
self.palette
|
||||||
|
.append(&palette_entry::PaletteEntry::new(&mountain));
|
||||||
|
self.palette
|
||||||
|
.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();
|
||||||
{
|
{
|
||||||
|
@ -182,13 +182,13 @@ impl ObjectImpl for HexGridWindowPrivate {
|
||||||
|
|
||||||
let (q, r) = axial_round(q, r);
|
let (q, r) = axial_round(q, r);
|
||||||
let coordinate = AxialAddr::new(q, r);
|
let coordinate = AxialAddr::new(q, r);
|
||||||
canvas_address.set_label(&format!("{:.0} {:.0}", x, y));
|
canvas_address.set_value(&format!("{:.0} {:.0}", x, y));
|
||||||
|
|
||||||
if coordinate.distance(&AxialAddr::origin()) > MAP_RADIUS {
|
if coordinate.distance(&AxialAddr::origin()) > MAP_RADIUS {
|
||||||
hex_address.set_label(&format!("-----"));
|
hex_address.set_value(&format!("-----"));
|
||||||
*c.borrow_mut() = None;
|
*c.borrow_mut() = None;
|
||||||
} else {
|
} else {
|
||||||
hex_address.set_label(&format!("{:.0} {:.0}", coordinate.q(), coordinate.r()));
|
hex_address.set_value(&format!("{:.0} {:.0}", coordinate.q(), coordinate.r()));
|
||||||
*c.borrow_mut() = Some(coordinate);
|
*c.borrow_mut() = Some(coordinate);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -201,16 +201,6 @@ impl ObjectImpl for HexGridWindowPrivate {
|
||||||
|
|
||||||
context.set_line_width(2.);
|
context.set_line_width(2.);
|
||||||
|
|
||||||
/*
|
|
||||||
deep_water.render_on_context(&context, 0., 0.);
|
|
||||||
shallow_water.render_on_context(&context, 150., 0.);
|
|
||||||
grasslands.render_on_context(&context, 300., 0.);
|
|
||||||
desert.render_on_context(&context, 450., 0.);
|
|
||||||
mountain.render_on_context(&context, 0., 100.);
|
|
||||||
badlands.render_on_context(&context, 150., 100.);
|
|
||||||
swamp.render_on_context(&context, 0., 200.);
|
|
||||||
*/
|
|
||||||
|
|
||||||
for coordinate in vec![AxialAddr::origin()]
|
for coordinate in vec![AxialAddr::origin()]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(AxialAddr::origin().addresses(MAP_RADIUS))
|
.chain(AxialAddr::origin().addresses(MAP_RADIUS))
|
||||||
|
@ -225,17 +215,16 @@ 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);
|
||||||
// draw_hexagon(context, center_x, center_y, HEX_RADIUS);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -254,14 +243,16 @@ glib::wrapper! {
|
||||||
|
|
||||||
impl HexGridWindow {
|
impl HexGridWindow {
|
||||||
pub fn new(app: &Application) -> Self {
|
pub fn new(app: &Application) -> Self {
|
||||||
Object::builder().property("application", app).build()
|
let window: Self = Object::builder().property("application", app).build();
|
||||||
|
window.set_child(Some(&window.imp().layout));
|
||||||
|
window
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
@ -272,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();
|
||||||
|
@ -334,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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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(>k::Label::new(Some(&String::from(&tile.terrain))));
|
||||||
|
|
||||||
|
row.set_child(Some(&layout));
|
||||||
|
|
||||||
|
row
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
|
@ -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.,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue