diff --git a/source/marathon/map/minf.rs b/source/marathon/map/minf.rs index 364f091..21a03d7 100644 --- a/source/marathon/map/minf.rs +++ b/source/marathon/map/minf.rs @@ -122,11 +122,14 @@ bitflags! { #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] pub struct MsnFlags: u16 { - const EXTERMINATION = 1; - const EXPLORATION = 1 << 1; - const RETRIEVAL = 1 << 2; - const REPAIR = 1 << 3; - const RESCUE = 1 << 4; + const EXTERMINATION = 1; + const EXPLORATION = 1 << 1; + const RETRIEVAL = 1 << 2; + const REPAIR = 1 << 3; + const RESCUE = 1 << 4; + const M1_EXPLORATION = 1 << 5; + const M1_RESCUE = 1 << 6; + const M1_REPAIR = 1 << 7; } } diff --git a/source/rozinante/editor.rs b/source/rozinante/editor.rs index 6572db1..eaf3cda 100644 --- a/source/rozinante/editor.rs +++ b/source/rozinante/editor.rs @@ -5,21 +5,41 @@ //! edit it. mod block; -mod state; + +pub use block::*; use super::{color, draw::*}; -use crate::durandal::image::*; +use crate::{durandal::image::*, marathon::{machdr, wad}}; impl MapEditor { - /// Opens the editor with a new empty map. - #[inline] - pub fn open_new(&mut self) {self.map = Some(state::OpenMap::default());} + /// Opens the editor with a new map. + pub fn open_new(&mut self) + { + *self = Self::default(); + } /// Opens the editor with an existing map. pub fn open_buf(&mut self, b: &[u8]) { - self.map = Some(state::OpenMap::open_buf(b)); + // TODO: handle errors gracefully + let b = &b[machdr::try_mac_header(b)..]; + let wad = wad::read_wad(b).unwrap(); + + let ent = wad.entries.iter().nth(0).unwrap().1; + let info = ent.chunks.iter().find_map(|cnk| { + match cnk { + wad::Chunk::Minf(info) => Some(info), + _ => None, + } + }).unwrap().clone(); + + let block = Block{info}; + + dbg!(&block); + + *self = Self{blocks: vec![block], + tools: Self::default_tools()}; } /// Draws the screen for this editor state. @@ -27,55 +47,83 @@ impl MapEditor where D: DrawArea, I: CacheImage { + /* let dw = d.w(); let dh = d.h(); let iw = im.w(); let ih = im.h(); - match &self.map { - None => { - let tx_top = "Map Required To Proceed"; - let tx_bot = "CAS.qterm//CyberAcme Systems Inc."; + let tx_top = "Map Required To Proceed"; + let tx_bot = "CAS.qterm//CyberAcme Systems Inc."; - d.clear(Color16::new(0, 0, 0)); + d.clear(Color16::new(0, 0, 0)); - d.image((dw / 2 - iw / 2, dh / 2 - ih / 2), im); + d.image((dw / 2 - iw / 2, dh / 2 - ih / 2), im); - d.rect(Rect{x: 0, y: 0, w: dw, h: 18}, color::DARK_RED); - d.text((4, 14), tx_top, color::RED); + d.rect(Rect{x: 0, y: 0, w: dw, h: 18}, color::DARK_RED); + d.text((4, 0), tx_top, color::RED); - d.rect(Rect{x: 0, y: dh - 18, w: dw, h: 18}, color::DARK_RED); - d.text((4, dh - 4), tx_bot, color::RED); - } - Some(st) => { - let text = &format!("tool: {:?}", st.tool()); + d.rect(Rect{x: 0, y: dh - 18, w: dw, h: 18}, color::DARK_RED); + d.text((4, dh - 16), tx_bot, color::RED); + */ - d.clear(Color16::new(0, 0, 0)); - d.text((dw / 2, dh / 2), text, color::RED); - } - } + let _ = im; + + let text = &format!("{:#?}", &self); + + d.clear(Color16::new(0, 0, 0)); + d.text((0, 0), text, color::RED); } - /// Returns `true` if `self` is closed. - #[inline] - pub fn is_closed(&self) -> bool {self.map.is_none()} + // Returns the default tools. + const fn default_tools() -> (Tool, Tool) {(Tool::Points, Tool::Lines)} - /// Returns `true` if `self` is opened. - #[inline] - pub fn is_opened(&self) -> bool {self.map.is_some()} + /// Returns a reference to the current block. + pub fn cur_block(&self) -> &Block {self.blocks.last().unwrap()} + + /// Pushes a new block. + pub fn push_block(&mut self, blk: Block) {self.blocks.push(blk);} + + /// Returns the current tool. + pub fn tool(&self) -> &Tool {&self.tools.0} + + /// Sets the current tool, and returns the previous one. + pub fn set_tool(&mut self, t: Tool) -> &Tool + { + self.tools.1 = self.tools.0.clone(); + self.tools.0 = t; + + &self.tools.1 + } + + /// Returns true if the current map is unclean and needs to be saved. + pub fn unclean(&self) -> bool {true} } impl Default for MapEditor { #[inline] - fn default() -> Self {Self{map: None}} + fn default() -> Self + { + Self{blocks: vec![Block::default()], tools: Self::default_tools()} + } } -/// An entire map editor, which may be opened or closed. Holds state which -/// outlives the opened map state. +/// The state of an opened map editor. +#[derive(Debug)] pub struct MapEditor { - map: Option, + blocks: Vec, + tools: (Tool, Tool), +} + +/// A tool in the map editor. +#[derive(Clone, Debug)] +pub enum Tool +{ + Points, + Lines, + Polygons, } // EOF diff --git a/source/rozinante/editor/block.rs b/source/rozinante/editor/block.rs index 383f969..0b98323 100644 --- a/source/rozinante/editor/block.rs +++ b/source/rozinante/editor/block.rs @@ -8,9 +8,9 @@ impl Default for Block /// Copyable, versioned map state. #[derive(Clone, Debug)] -pub(super) struct Block +pub struct Block { - pub(super) info: map::minf::Minf, + pub info: map::minf::Minf, } // EOF diff --git a/source/rozinante/editor/state.rs b/source/rozinante/editor/state.rs deleted file mode 100644 index 7e680af..0000000 --- a/source/rozinante/editor/state.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! Map editor state. - -use super::block; -use crate::marathon::{machdr, wad}; - -impl OpenMap -{ - pub(super) fn open_buf(b: &[u8]) -> Self - { - // TODO: handle errors gracefully - let b = &b[machdr::try_mac_header(b)..]; - let wad = wad::read_wad(b).unwrap(); - - let ent = wad.entries.iter().nth(0).unwrap().1; - let info = ent.chunks.iter().find_map(|cnk| { - match cnk { - wad::Chunk::Minf(info) => Some(info), - _ => None, - } - }).unwrap().clone(); - - let block = block::Block{info}; - - dbg!(&block); - - Self{blocks: vec![block], - tools: Self::default_tools()} - } - - pub(super) fn cur_block(&self) -> &block::Block - { - self.blocks.last().unwrap() - } - - pub(super) fn cur_block_mut(&mut self) -> &mut block::Block - { - self.blocks.last_mut().unwrap() - } - - pub(super) fn tool(&self) -> &Tool {&self.tools.0} - - pub(super) fn set_tool(&mut self, t: Tool) -> &Tool - { - self.tools.1 = self.tools.0.clone(); - self.tools.0 = t; - - &self.tools.1 - } - - const fn default_tools() -> (Tool, Tool) {(Tool::Points, Tool::Lines)} -} - -impl Default for OpenMap -{ - #[inline] - fn default() -> Self - { - Self{blocks: vec![block::Block::default()], - tools: Self::default_tools()} - } -} - -/// The state of an opened map editor. -pub(super) struct OpenMap -{ - blocks: Vec, - tools: (Tool, Tool), -} - -/// A tool in the map editor. -#[derive(Clone, Debug)] -pub(super) enum Tool -{ - Points, - Lines, - Polygons, -} - -// EOF diff --git a/source/tycho/Cargo.toml b/source/tycho/Cargo.toml index 5359d4b..b25f718 100644 --- a/source/tycho/Cargo.toml +++ b/source/tycho/Cargo.toml @@ -19,6 +19,7 @@ gtk-sys = {version = "0.8", features = ["v3_16"]} maraiah = {path = "../.."} memmap = "0.7" pango-sys = "0.8" +pangocairo-sys = "0.9" [[bin]] name = "tycho" diff --git a/source/tycho/data/ui.xml b/source/tycho/data/ui.xml index b25fa04..ce97ccc 100644 --- a/source/tycho/data/ui.xml +++ b/source/tycho/data/ui.xml @@ -1,15 +1,7 @@ - + - - - - 100 1 @@ -226,151 +218,135 @@ Author: Alison Sanderson False 12 - + True False - + + Solo True - False - vertical - - - Solo - True - True - False - The map can be played in single-player. - True - - - False - True - 0 - - - - - Co-op - True - True - False - The map can be played in multi-player co-operative. - True - - - False - True - 1 - - - - - Carnage - True - True - False - The map can be played in multi-player Carnage. - True - - - False - True - 2 - - - - - KTMWTB - True - True - False - The map can be played in multi-player Kill The Man With The Ball - True - - - False - True - 3 - - + True + False + The map can be played in single-player. + True + True + True - True - True - 0 + 0 + 0 - + + Co-op True - False - vertical - - - King Of The Hill - True - True - False - The map can be played in multi-player King of the Hill. - True - - - False - True - 0 - - - - - Defense - True - True - False - The map can be played in multi-player Defense. - True - - - False - True - 1 - - - - - Rugby - True - True - False - The map can be played in multi-player Rugby. - True - - - False - True - 2 - - - - - Capture The Flag - True - True - False - The map can be played in multi-player Capture The Flag. - True - - - False - True - 3 - - + True + False + The map can be played in multi-player co-operative. + True + True + True - True - True - 1 + 0 + 1 + + + + + Carnage + True + True + False + The map can be played in multi-player Carnage. + True + True + True + + + 0 + 2 + + + + + KTMWTB + True + True + False + The map can be played in multi-player Kill The Man With The Ball + True + True + True + + + 0 + 3 + + + + + King Of The Hill + True + True + False + The map can be played in multi-player King of the Hill. + True + True + True + + + 1 + 0 + + + + + Defense + True + True + False + The map can be played in multi-player Defense. + True + True + True + + + 1 + 1 + + + + + Rugby + True + True + False + The map can be played in multi-player Rugby. + True + True + True + + + 1 + 2 + + + + + Capture The Flag + True + True + False + The map can be played in multi-player Capture The Flag. + True + True + True + + + 1 + 3 @@ -411,7 +387,7 @@ Author: Alison Sanderson 0 in - + True False vertical @@ -583,7 +559,7 @@ Author: Alison Sanderson 0 in - + True False vertical diff --git a/source/tycho/interfaces.rs b/source/tycho/interfaces.rs index ed0dc3e..668be10 100644 --- a/source/tycho/interfaces.rs +++ b/source/tycho/interfaces.rs @@ -1,279 +1,9 @@ -//! Implemented interfaces for Rozinante. +//! Interfaces to the system. -use cairo_sys::*; -use gdk_pixbuf_sys::*; -use gdk_sys::*; -use glib_sys::*; -use gobject_sys::*; -use gtk_sys::*; -use maraiah::{c_str, - durandal::{ffi, image::*}, - rozinante::{draw::*, editor}}; -use std::marker::PhantomData; +pub mod cairo; +pub mod editor; +pub mod glib; -/// Converts a `Color` to a `f64` triple. -fn flt_color(cr: impl Color) -> (f64, f64, f64) -{ - fn flt_color(n: u16) -> f64 {f64::from(n) / f64::from(u16::max_value())} - - (flt_color(cr.r()), flt_color(cr.g()), flt_color(cr.b())) -} - -impl CacheImage for CrImage -{ - fn w(&self) -> Coord {unsafe {gdk_pixbuf_get_width(self.0) as Coord}} - fn h(&self) -> Coord {unsafe {gdk_pixbuf_get_height(self.0) as Coord}} -} - -impl CrDrawArea -{ - pub const fn new(ctx: *mut cairo_t, w: f64, h: f64) -> Self - { - CrDrawArea{ctx, w: w as Coord, h: h as Coord} - } -} - -impl DrawArea for CrDrawArea -{ - type NativeImage = CrImage; - - fn w(&self) -> Coord {self.w} - fn h(&self) -> Coord {self.h} - - fn clear(&mut self, cr: impl Color) - { - self.rect(Rect{x: 0, y: 0, w: self.w(), h: self.h()}, cr); - - let sl = FONT_SLANT_NORMAL; - let wt = FONT_WEIGHT_NORMAL; - - unsafe { - cairo_select_font_face(self.ctx, c_str!("Monospace"), sl, wt); - cairo_set_font_size(self.ctx, 14.0); - cairo_set_line_width(self.ctx, 1.0); - } - } - - fn line_width(&mut self, width: u8) - { - let width = f64::from(width); - - unsafe { - cairo_set_line_width(self.ctx, width); - } - } - - fn line(&mut self, p1: Point, p2: Point, cr: impl Color) - { - let (r, g, b) = flt_color(cr); - - let x1 = f64::from(p1.0); - let y1 = f64::from(p1.1); - - let x2 = f64::from(p2.0); - let y2 = f64::from(p2.1); - - unsafe { - cairo_set_source_rgb(self.ctx, r, g, b); - cairo_move_to(self.ctx, x1, y1); - cairo_line_to(self.ctx, x2, y2); - cairo_stroke(self.ctx); - } - } - - fn rect(&mut self, rect: Rect, cr: impl Color) - { - let px = f64::from(rect.x); - let py = f64::from(rect.y); - let sx = f64::from(rect.w); - let sy = f64::from(rect.h); - - let (r, g, b) = flt_color(cr); - - unsafe { - cairo_set_source_rgb(self.ctx, r, g, b); - cairo_rectangle(self.ctx, px, py, sx, sy); - cairo_fill(self.ctx); - } - } - - fn text(&mut self, pos: Point, text: &str, cr: impl Color) - { - let (r, g, b) = flt_color(cr); - - let x = f64::from(pos.0); - let y = f64::from(pos.1); - - let text = ffi::CString::new(text).unwrap(); - - unsafe { - cairo_set_source_rgb(self.ctx, r, g, b); - cairo_move_to(self.ctx, x, y); - cairo_show_text(self.ctx, text.as_ptr()); - } - } - - fn image(&mut self, pos: Point, im: &Self::NativeImage) - { - let x = f64::from(pos.0); - let y = f64::from(pos.1); - - unsafe { - gdk_cairo_set_source_pixbuf(self.ctx, im.0, x, y); - cairo_paint(self.ctx); - } - } -} - -impl MapEditor -{ - /// Propagates updated information to widgets. - pub fn cause_update(&mut self) - { - unsafe { - gtk_widget_queue_draw(*self.draw); - } - } -} - -impl std::ops::Deref for MapEditor -{ - type Target = editor::MapEditor; - - #[inline] - fn deref(&self) -> &Self::Target {&self.edit} -} - -impl std::ops::DerefMut for MapEditor -{ - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target {&mut self.edit} -} - -impl std::ops::Deref for Refc<'_, T> -{ - type Target = *mut T; - - fn deref(&self) -> &Self::Target {&self.p} -} - -impl Drop for Refc<'_, T> -{ - fn drop(&mut self) - { - unsafe { - g_object_unref(self.p as _); - } - } -} - -impl Refc<'_, T> -{ - /// Creates a new `Refc` which will own an already referenced object. - pub const fn new(p: *mut T) -> Self {Self{p, l: PhantomData}} - - /// Creates a new `Refc` which will hold a reference to the object. - pub fn own(p: *mut T) -> Self {unsafe {g_object_ref(p as _);} Self::new(p)} -} - -impl ListD -{ - pub fn new(head: *mut GList) -> Self {Self{head, iter: head}} -} - -impl Iterator for ListD -{ - type Item = gpointer; - - fn next(&mut self) -> Option { - if self.iter != ffi::null_mut() { - let obj = unsafe { - let obj = (*self.iter).data; - self.iter = (*self.iter).next; - obj - }; - - Some(obj) - } else { - None - } - } -} - -impl Drop for ListD -{ - fn drop(&mut self) {unsafe {g_list_free(self.head);}} -} - -impl ListS -{ - pub fn new(head: *mut GSList) -> Self {Self{head, iter: head}} -} - -impl Iterator for ListS -{ - type Item = gpointer; - - fn next(&mut self) -> Option { - if self.iter != ffi::null_mut() { - let obj = unsafe { - let obj = (*self.iter).data; - self.iter = (*self.iter).next; - obj - }; - - Some(obj) - } else { - None - } - } -} - -impl Drop for ListS -{ - fn drop(&mut self) {unsafe {g_slist_free(self.head);}} -} - -/// An image for a `CrDrawArea`. -pub struct CrImage(pub *const GdkPixbuf); - -/// A `DrawArea` for a Cairo surface. -pub struct CrDrawArea -{ - ctx: *mut cairo_t, - w: Coord, - h: Coord, -} - -/// Specialized map editor which has callbacks for frontend purposes. -pub struct MapEditor -{ - pub edit: editor::MapEditor, - pub draw: Refc<'static, GtkWidget>, -} - -/// A runtime reference to the map editor. -pub type MapEditorRef = std::cell::RefCell; - -/// A GObject owned pointer. -pub struct Refc<'a, T> -{ - p: *mut T, - l: PhantomData<&'a *mut T>, -} - -/// A GList wrapper. -pub struct ListD -{ - head: *mut GList, - iter: *mut GList, -} - -/// A GSList wrapper. -pub struct ListS -{ - head: *mut GSList, - iter: *mut GSList, -} +pub use self::{cairo::*, editor::*, glib::*}; // EOF diff --git a/source/tycho/interfaces/cairo.rs b/source/tycho/interfaces/cairo.rs new file mode 100644 index 0000000..0b806b5 --- /dev/null +++ b/source/tycho/interfaces/cairo.rs @@ -0,0 +1,155 @@ +//! Implemented drawing area for Cairo. + +use super::glib::*; +use cairo_sys::*; +use gdk_pixbuf_sys::*; +use gdk_sys::*; +use pango_sys::*; +use pango_cairo_sys::*; +use maraiah::{c_str, + durandal::{ffi, image::*}, + rozinante::draw}; + +/// Converts a `Color` to a `f64` triple. +fn flt_color(cr: impl Color) -> (f64, f64, f64) +{ + fn flt_color(n: u16) -> f64 {f64::from(n) / f64::from(u16::max_value())} + + (flt_color(cr.r()), flt_color(cr.g()), flt_color(cr.b())) +} + +impl draw::CacheImage for CrImage +{ + fn w(&self) -> draw::Coord + { + unsafe {gdk_pixbuf_get_width(self.0) as draw::Coord} + } + + fn h(&self) -> draw::Coord + { + unsafe {gdk_pixbuf_get_height(self.0) as draw::Coord} + } +} + +impl CrDrawArea +{ + /// Creates a new `CrDrawArea`. + pub fn new(ctx: *mut cairo_t, w: f64, h: f64) -> Self + { + let pan = unsafe { + let pan = pango_cairo_create_layout(ctx); + let dsc = pango_font_description_from_string(c_str!("Monospace 12")); + pango_layout_set_font_description(pan, dsc); + pango_font_description_free(dsc); + pan + }; + let pan = Refc::new(pan); + + CrDrawArea{ctx, pan, w: w as draw::Coord, h: h as draw::Coord} + } +} + +impl draw::DrawArea for CrDrawArea +{ + type NativeImage = CrImage; + + fn w(&self) -> draw::Coord {self.w} + fn h(&self) -> draw::Coord {self.h} + + fn clear(&mut self, cr: impl Color) + { + self.rect(draw::Rect{x: 0, y: 0, w: self.w(), h: self.h()}, cr); + + unsafe { + cairo_set_line_width(self.ctx, 1.0); + } + } + + fn line_width(&mut self, width: u8) + { + let width = f64::from(width); + + unsafe { + cairo_set_line_width(self.ctx, width); + } + } + + fn line(&mut self, p1: draw::Point, p2: draw::Point, cr: impl Color) + { + let (r, g, b) = flt_color(cr); + + let x1 = f64::from(p1.0); + let y1 = f64::from(p1.1); + + let x2 = f64::from(p2.0); + let y2 = f64::from(p2.1); + + unsafe { + cairo_set_source_rgb(self.ctx, r, g, b); + cairo_move_to(self.ctx, x1, y1); + cairo_line_to(self.ctx, x2, y2); + cairo_stroke(self.ctx); + } + } + + fn rect(&mut self, rect: draw::Rect, cr: impl Color) + { + let px = f64::from(rect.x); + let py = f64::from(rect.y); + let sx = f64::from(rect.w); + let sy = f64::from(rect.h); + + let (r, g, b) = flt_color(cr); + + unsafe { + cairo_set_source_rgb(self.ctx, r, g, b); + cairo_rectangle(self.ctx, px, py, sx, sy); + cairo_fill(self.ctx); + } + } + + fn text(&mut self, pos: draw::Point, text: &str, cr: impl Color) + { + let (r, g, b) = flt_color(cr); + + let x = f64::from(pos.0); + let y = f64::from(pos.1); + + let tlen = text.len() as ffi::c_int; + let text = text.as_ptr() as ffi::NT; + + unsafe { + cairo_set_source_rgb(self.ctx, r, g, b); + cairo_move_to(self.ctx, x, y); + + pango_layout_set_markup(*self.pan, text, tlen); + pango_cairo_update_layout(self.ctx, *self.pan); + pango_cairo_show_layout(self.ctx, *self.pan); + } + } + + fn image(&mut self, pos: draw::Point, im: &Self::NativeImage) + { + let x = f64::from(pos.0); + let y = f64::from(pos.1); + + unsafe { + gdk_cairo_set_source_pixbuf(self.ctx, im.0, x, y); + cairo_paint(self.ctx); + } + } +} + +/// An image for a `CrDrawArea`. +pub struct CrImage(pub *const GdkPixbuf); + +/// A `DrawArea` for a Cairo surface. +pub struct CrDrawArea +{ + ctx: *mut cairo_t, + pan: Refc<'static, PangoLayout>, + w: draw::Coord, + h: draw::Coord, +} + +// EOF diff --git a/source/tycho/interfaces/editor.rs b/source/tycho/interfaces/editor.rs new file mode 100644 index 0000000..73863c8 --- /dev/null +++ b/source/tycho/interfaces/editor.rs @@ -0,0 +1,148 @@ +//! Map editor interface. + +use super::glib::*; +use gobject_sys::*; +use gtk_sys::*; +use maraiah::{durandal::ffi, marathon::map, rozinante::editor}; + +fn each_flag(buttons: &[PropFlag], ordering: &[T], mut f: F) + where F: FnMut(&PropFlag, &T) +{ + for (flg, ord) in buttons.iter().zip(ordering) { + f(flg, ord); + } +} + +fn refresh_flags(mut f: F) -> impl FnMut(&PropFlag, &T) + where F: FnMut(&T) -> bool +{ + move |flg, ord| { + unsafe { + g_signal_handler_block(*flg.w as _, flg.h); + + gtk_toggle_button_set_active(*flg.w, f(ord).into()); + + g_signal_handler_unblock(*flg.w as _, flg.h); + } + } +} + +fn set_flags(mut f: F) -> impl FnMut(&PropFlag, &T) + where F: FnMut(&T, bool) +{ + move |flg, ord| f(ord, unsafe {gtk_toggle_button_get_active(*flg.w)} != 0) +} + +impl MapEditor +{ + /// Propagates all updated map information to widgets. + pub fn cause_refresh(&self) + { + self.cause_refresh_view(); + self.cause_refresh_props(); + } + + /// Propagates updated map view information to widgets. + pub fn cause_refresh_view(&self) + { + unsafe { + gtk_widget_queue_draw(*self.draw); + } + } + + /// Propagates updated map property information to widgets. + pub fn cause_refresh_props(&self) + { + let inf = &self.cur_block().info; + + each_flag(&self.fent, &O_ENT, refresh_flags(|&f| inf.entr_flags.contains(f))); + each_flag(&self.fenv, &O_ENV, refresh_flags(|&f| inf.envi_flags.contains(f))); + each_flag(&self.fmsn, &O_MSN, refresh_flags(|&f| inf.miss_flags.contains(f))); + } + + /// Propagates updated map property information to the editor state. + pub fn cause_update_props(&mut self) + { + let mut blk = self.cur_block().clone(); + let inf = &mut blk.info; + + each_flag(&self.fent, &O_ENT, set_flags(|&f, s| inf.entr_flags.set(f, s))); + each_flag(&self.fenv, &O_ENV, set_flags(|&f, s| inf.envi_flags.set(f, s))); + each_flag(&self.fmsn, &O_MSN, set_flags(|&f, s| inf.miss_flags.set(f, s))); + + self.push_block(blk); + self.cause_refresh_view(); + } +} + +impl std::ops::Deref for MapEditor +{ + type Target = editor::MapEditor; + + #[inline] + fn deref(&self) -> &Self::Target {&self.edit} +} + +impl std::ops::DerefMut for MapEditor +{ + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target {&mut self.edit} +} + +/// Specialized map editor which has callbacks for frontend purposes. +pub struct MapEditor +{ + pub edit: editor::MapEditor, + pub draw: Refc<'static, GtkWidget>, + pub fent: Vec, + pub fenv: Vec, + pub fmsn: Vec, +} + +/// A runtime reference to the map editor. +pub type MapEditorRef = std::cell::RefCell; + +pub struct PropFlag +{ + pub w: Refc<'static, GtkToggleButton>, + pub h: ffi::c_ulong, +} + +// NOTE: this is flipped because of GTK weirdness. don't touch +const O_ENT: [map::minf::EntFlags; 8] = [ + map::minf::EntFlags::CTF, + map::minf::EntFlags::RUGBY, + map::minf::EntFlags::DEFENSE, + map::minf::EntFlags::KOTH, + map::minf::EntFlags::KTMWTB, + map::minf::EntFlags::CARNAGE, + map::minf::EntFlags::CO_OP, + map::minf::EntFlags::SOLO, +]; + +const O_ENV: [map::minf::EnvFlags; 11] = [ + map::minf::EnvFlags::VACUUM, + map::minf::EnvFlags::MAGNETIC, + map::minf::EnvFlags::REBELLION, + map::minf::EnvFlags::LOW_GRAV, + map::minf::EnvFlags::M1_GLUE, + map::minf::EnvFlags::LAVA_FLOOR, + map::minf::EnvFlags::REBELLION2, + map::minf::EnvFlags::MUSIC, + map::minf::EnvFlags::TERM_PAUSE, + map::minf::EnvFlags::M1_MONSTER, + map::minf::EnvFlags::M1_WEPS, +]; + +const O_MSN: [map::minf::MsnFlags; 8] = [ + map::minf::MsnFlags::EXTERMINATION, + map::minf::MsnFlags::EXPLORATION, + map::minf::MsnFlags::RETRIEVAL, + map::minf::MsnFlags::REPAIR, + map::minf::MsnFlags::RESCUE, + map::minf::MsnFlags::M1_EXPLORATION, + map::minf::MsnFlags::M1_RESCUE, + map::minf::MsnFlags::M1_REPAIR, +]; + +// EOF diff --git a/source/tycho/interfaces/glib.rs b/source/tycho/interfaces/glib.rs new file mode 100644 index 0000000..087673e --- /dev/null +++ b/source/tycho/interfaces/glib.rs @@ -0,0 +1,113 @@ +//! GLib interfaces. + +use glib_sys::*; +use gobject_sys::*; +use maraiah::durandal::ffi; +use std::marker::PhantomData; + +impl std::ops::Deref for Refc<'_, T> +{ + type Target = *mut T; + + fn deref(&self) -> &Self::Target {&self.p} +} + +impl Drop for Refc<'_, T> +{ + fn drop(&mut self) + { + unsafe { + g_object_unref(self.p as _); + } + } +} + +impl Refc<'_, T> +{ + /// Creates a new `Refc` which will own an already referenced object. + pub const fn new(p: *mut T) -> Self {Self{p, l: PhantomData}} + + /// Creates a new `Refc` which will hold a reference to the object. + pub fn own(p: *mut T) -> Self {unsafe {g_object_ref(p as _);} Self::new(p)} +} + +impl ListD +{ + pub fn new(head: *mut GList) -> Self {Self{head, iter: head}} +} + +impl Iterator for ListD +{ + type Item = gpointer; + + fn next(&mut self) -> Option { + if self.iter != ffi::null_mut() { + let obj = unsafe { + let obj = (*self.iter).data; + self.iter = (*self.iter).next; + obj + }; + + Some(obj) + } else { + None + } + } +} + +impl Drop for ListD +{ + fn drop(&mut self) {unsafe {g_list_free(self.head);}} +} + +impl ListS +{ + pub fn new(head: *mut GSList) -> Self {Self{head, iter: head}} +} + +impl Iterator for ListS +{ + type Item = gpointer; + + fn next(&mut self) -> Option { + if self.iter != ffi::null_mut() { + let obj = unsafe { + let obj = (*self.iter).data; + self.iter = (*self.iter).next; + obj + }; + + Some(obj) + } else { + None + } + } +} + +impl Drop for ListS +{ + fn drop(&mut self) {unsafe {g_slist_free(self.head);}} +} + +/// A GObject owned pointer. +pub struct Refc<'a, T> +{ + p: *mut T, + l: PhantomData<&'a *mut T>, +} + +/// A GList wrapper. +pub struct ListD +{ + head: *mut GList, + iter: *mut GList, +} + +/// A GSList wrapper. +pub struct ListS +{ + head: *mut GSList, + iter: *mut GSList, +} + +// EOF diff --git a/source/tycho/main.rs b/source/tycho/main.rs index 7bc76f9..6ab3dfa 100644 --- a/source/tycho/main.rs +++ b/source/tycho/main.rs @@ -1,6 +1,6 @@ mod interfaces; -use crate::interfaces::*; +use self::interfaces::*; use gdk_pixbuf_sys::*; use gdk_sys::*; use gio_sys::*; @@ -20,6 +20,9 @@ const B_BTN_M_VIEW: ffi::NT = c_str!("btn-show-map-view"); const B_BTN_NEW: ffi::NT = c_str!("btn-new"); const B_BTN_OPEN: ffi::NT = c_str!("btn-open"); const B_BTN_QUIT: ffi::NT = c_str!("btn-quit"); +const B_CON_F_ENT: ffi::NT = c_str!("con-f-ent"); +const B_CON_F_ENV: ffi::NT = c_str!("con-f-env"); +const B_CON_F_MSN: ffi::NT = c_str!("con-f-msn"); const B_DLG_ABOUT: ffi::NT = c_str!("dlg-about"); const B_DRAW_AREA: ffi::NT = c_str!("draw-area"); const B_WIN_MAIN: ffi::NT = c_str!("win-main"); @@ -31,6 +34,7 @@ const E_DELETE: ffi::NT = c_str!("delete-event"); const E_DESTROY: ffi::NT = c_str!("destroy"); const E_DRAW: ffi::NT = c_str!("draw"); const E_SHUTDOWN: ffi::NT = c_str!("shutdown"); +const E_TOGGLE: ffi::NT = c_str!("toggled"); const IM_ABOUT: ffi::NT = c_str!("/net/greyserv/maraiah/tycho/tycho2.png"); const IM_NOMAP: ffi::NT = c_str!("/net/greyserv/maraiah/tycho/tycho1.png"); const PATH_BUILDER: ffi::NT = c_str!("/net/greyserv/maraiah/tycho/ui"); @@ -40,7 +44,7 @@ const PATH_CSS: ffi::NT = c_str!("/net/greyserv/maraiah/tycho/css"); unsafe extern "C" fn app_activate(app: *mut GtkApplication, _: gpointer) { // Callback to finalize the reference. - unsafe extern "C" fn c_done(_: *mut GtkWidget, ptr: gpointer) + unsafe extern "C" fn c_done(_: *mut GApplication, ptr: gpointer) { let edit = Rc::from_raw(ptr as *const MapEditorRef); assert_eq!(Rc::strong_count(&edit), 1); @@ -49,11 +53,16 @@ unsafe extern "C" fn app_activate(app: *mut GtkApplication, _: gpointer) let b = Refc::new(gtk_builder_new_from_resource(PATH_BUILDER)); let edit = MapEditor{edit: Default::default(), - draw: Refc::own(get_obj(&b, B_DRAW_AREA))}; + draw: Refc::own(get_obj(&b, B_DRAW_AREA)), + fent: get_flag_fields(&b, B_CON_F_ENT), + fenv: get_flag_fields(&b, B_CON_F_ENV), + fmsn: get_flag_fields(&b, B_CON_F_MSN)}; + let edit = RefCell::new(edit); let edit = Rc::new(edit); setup_css(); + setup_toggles(edit.clone()); setup_draw_area(&b, edit.clone()); setup_win_map_view(&b); setup_win_map_tools(&b); @@ -64,6 +73,45 @@ unsafe extern "C" fn app_activate(app: *mut GtkApplication, _: gpointer) connect(app, E_SHUTDOWN, c_done as _, Rc::into_raw(edit)); } +// Gets all of the toggle buttons from a container. +unsafe fn get_flag_fields(b: &Refc, name: ffi::NT) -> Vec +{ + let mut flags = Vec::new(); + + let head = get_obj(b, name); + let head = ListD::new(gtk_container_get_children(head)); + let gtyp = gtk_toggle_button_get_type(); + + get_typed_from(head, gtyp, |obj| { + flags.push(PropFlag{w: Refc::own(obj as _), h: 0}); + }); + + flags +} + +unsafe fn setup_toggles(edit: Rc) +{ + let mut ed = edit.borrow_mut(); + connect_toggle(edit.clone(), ed.fent.iter_mut()); + connect_toggle(edit.clone(), ed.fenv.iter_mut()); + connect_toggle(edit.clone(), ed.fmsn.iter_mut()); +} + +unsafe fn connect_toggle<'a, I>(edit: Rc, it: I) + where I: Iterator +{ + unsafe extern "C" fn c_toggled(_: *mut GtkToggleButton, edit: gpointer) + { + let edit = &*(edit as *const MapEditorRef); + edit.borrow_mut().cause_update_props(); + } + + for flg in it { + let erf = connect_ref(*flg.w, edit.clone()); + flg.h = connect(*flg.w, E_TOGGLE, c_toggled as _, erf); + } +} + // Sets up the map view window's drawing area. unsafe fn setup_draw_area(b: &Refc, edit: Rc) { @@ -197,40 +245,37 @@ unsafe fn setup_explicit_drop(b: &Refc, win: *mut GtkWindow) let mut exp_del = Vec::new(); // so, we get all of the objects from the builder, and iterate through them - let head = gtk_builder_get_objects(**b); - let mut lst = &*head; + let head = ListS::new(gtk_builder_get_objects(**b)); - loop { - let obj = lst.data as *mut GObject; + get_typed_from(head, gtk_window_get_type(), |obj| { + let obj = obj as *mut GtkWindow; - // while this is well-defined, it is a weird way of doing it, because - // this exact method of checking types isn't fully documented. we can't - // use the macros for this functionality because we're not using C, so we - // use the underlying function calls. again, get jacked, punk. - if g_type_check_instance_is_a(obj as _, gtk_window_get_type()) != 0 { - let owin = obj as *mut GtkWindow; - - if owin != win { - exp_del.push(owin); - } + if obj != win { + exp_del.push(obj); } - - let nx = lst.next; - - if nx != ffi::null_mut() { - lst = &*nx; - } else { - break; - } - } - - g_slist_free(head); + }); let exp_del = Box::into_raw(Box::new(exp_del)); connect(win, E_DESTROY, c_done as _, exp_del); } +// Get objects of type `ty` from `it`. +unsafe fn get_typed_from(it: I, ty: GType, mut f: F) + where I: Iterator, + F: FnMut(*mut GObject) +{ + for obj in it { + // while this is well-defined, it is a weird way of doing it, because + // this exact method of checking types isn't fully documented. we can't + // use the macros for this functionality because we're not using C, so we + // use the underlying function calls. again, get jacked, punk. + if g_type_check_instance_is_a(obj as *mut GTypeInstance, ty) != 0 { + f(obj as _); + } + } +} + // Sets up the main menu window. unsafe fn setup_win_main(b: &Refc, app: *mut GtkApplication, @@ -248,7 +293,7 @@ unsafe fn setup_win_main(b: &Refc, let edit = &*(edit as *const MapEditorRef); let mut edit = edit.borrow_mut(); - if edit.is_opened() { + if edit.unclean() { let titl = c_str!("Confirm"); let text = c_str!("Are you sure you want to create a new project? \ Unsaved data may be lost."); @@ -259,7 +304,7 @@ unsafe fn setup_win_main(b: &Refc, } edit.open_new(); - edit.cause_update(); + edit.cause_refresh(); } // Callback to open an existing map when the "Open" button is pressed. @@ -268,7 +313,7 @@ unsafe fn setup_win_main(b: &Refc, let edit = &*(edit as *const MapEditorRef); let mut edit = edit.borrow_mut(); - if edit.is_opened() { + if edit.unclean() { let titl = c_str!("Confirm"); let text = c_str!("Are you sure you want to open this project? \ Unsaved data may be lost."); @@ -283,7 +328,7 @@ unsafe fn setup_win_main(b: &Refc, let fp = std::fs::File::open(&path).unwrap(); let mm = memmap::Mmap::map(&fp).unwrap(); edit.open_buf(&mm); - edit.cause_update(); + edit.cause_refresh(); } } @@ -427,9 +472,10 @@ unsafe fn get_obj(b: &Refc, name: ffi::NT) -> *mut T // Connects a signal handler. unsafe fn connect(obj: *mut T, name: ffi::NT, cb: gpointer, d: *const U) + -> ffi::c_ulong { let cb = std::mem::transmute(cb); - g_signal_connect_data(obj as _, name, cb, d as _, None, 0); + g_signal_connect_data(obj as _, name, cb, d as _, None, 0) } // Loads a `Pixbuf` from a resource.