Compare commits

...

6 Commits

Author SHA1 Message Date
an 0e32e7893a add map opening (sort of) 2019-03-31 14:58:36 -04:00
an 2875da07e1 move text from durandal to marathon 2019-03-30 17:19:10 -04:00
an 392e20ae65 simplify frontend code 2019-03-29 15:27:16 -04:00
an 1a3145b5b6 add null_void 2019-03-29 14:32:17 -04:00
an b8e624fecd use droppable structure for unref things 2019-03-29 13:48:36 -04:00
an 75e354a16b unnecessary complication 2019-03-27 17:02:15 -04:00
17 changed files with 359 additions and 217 deletions

View File

@ -15,6 +15,5 @@ pub mod file;
pub mod fixed;
pub mod image;
pub mod sound;
pub mod text;
// EOF

View File

@ -1,6 +1,6 @@
//! Binary data conversion utilities.
use crate::durandal::{err::*, text::mac_roman_conv};
use crate::durandal::err::*;
use std::{fmt, num::NonZeroU16};
#[doc(hidden)]
@ -489,14 +489,6 @@ impl fmt::Debug for OptU16
}
}
impl fmt::Debug for Ident
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "\"{}\"", mac_roman_conv(&self.0))
}
}
impl PartialEq<[u8; 4]> for Ident
{
#[inline]
@ -528,7 +520,7 @@ impl<'a> PartialEq<&'a [u8; 4]> for Ident
/// assert_eq!(&Ident(*b"POLY"), *b"POLY");
/// assert_eq!(&Ident(*b"POLY"), b"POLY");
/// ```
#[derive(Clone, Copy, Default, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize, serde::Deserialize))]
pub struct Ident(/** The individual bytes of this identifier. */ pub [u8; 4]);

View File

@ -9,15 +9,29 @@ macro_rules! c_str {
($s:expr) => {concat!($s, "\0").as_ptr() as $crate::durandal::ffi::NT};
}
#[inline]
pub const fn null_void() -> *const c_void
{
null()
}
#[inline]
pub const fn null_mut_void() -> *mut c_void
{
null_mut()
}
impl CStringVec
{
/// Creates a new empty CStringVec.
#[inline]
pub fn new() -> Self
{
Self{sv: Vec::new(), cv: vec![null()]}
}
/// Creates a new `CStringVec` from an iterator.
#[inline]
pub fn new_from_iter<'a, I: Iterator<Item = &'a str>>(it: I)
-> ResultS<Self>
{
@ -31,6 +45,7 @@ impl CStringVec
}
/// Pushes a new `CString`.
#[inline]
pub fn push(&mut self, st: CString)
{
self.cv.insert(self.cv.len() - 1, st.as_ptr());
@ -38,12 +53,14 @@ impl CStringVec
}
/// Returns the FFI pointer.
#[inline]
pub fn as_ptr(&self) -> *const NT
{
self.cv.as_ptr()
}
/// Returns the FFI pointer mutably.
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut NT
{
self.cv.as_mut_ptr()
@ -52,6 +69,7 @@ impl CStringVec
impl Default for CStringVec
{
#[inline]
fn default() -> Self {Self::new()}
}

View File

@ -1,6 +1,6 @@
use maraiah::{durandal::{err::*, file::*, image::*, sound::*},
marathon::{machdr, ppm, shp, snd, tga, wad, wav}};
use std::{fs, io};
use std::{fs, io, slice::from_ref};
fn make_tga(_opt: &Options, fname: &str, im: &impl Image) -> ResultS<()>
{
@ -140,7 +140,7 @@ fn main() -> ResultS<()>
macro_rules! arg {
($name:expr, $ref:expr, $type:expr, $desc:expr) => {
ap.refer(&mut $ref).add_option(&[$name], $type, $desc);
ap.refer(&mut $ref).add_option(from_ref(&$name), $type, $desc);
};
}

View File

@ -8,6 +8,7 @@ pub mod pict;
pub mod ppm;
pub mod shp;
pub mod snd;
pub mod text;
pub mod tga;
pub mod trm;
pub mod wad;

View File

@ -1,7 +1,7 @@
//! Structures used by Marathon's Map format.
use crate::{durandal::{bin::*, err::*, fixed::*, text::*},
marathon::xfer::TransferMode};
use crate::{durandal::{bin::*, err::*, fixed::*},
marathon::{text::*, xfer::TransferMode}};
use bitflags::bitflags;
/// Reads a `LightFunc` object.

View File

@ -1,7 +1,7 @@
//! Marathon Shapes format handling.
use crate::{durandal::{bin::*, err::*, fixed::*, image::*, text::*},
marathon::xfer::TransferMode};
use crate::{durandal::{bin::*, err::*, fixed::*, image::*},
marathon::{text::*, xfer::TransferMode}};
use bitflags::bitflags;
/// Reads a color from a color table into `clut`.

View File

@ -5,7 +5,7 @@
/// # Examples
///
/// ```
/// use maraiah::durandal::text::to_binsize;
/// use maraiah::marathon::text::to_binsize;
///
/// assert_eq!(to_binsize(5000), "5kB".to_string());
/// ```
@ -57,7 +57,7 @@ pub fn fuck_string(s: &[u8]) -> Vec<u8>
/// # Examples
///
/// ```
/// use maraiah::durandal::text::pascal_str;
/// use maraiah::marathon::text::pascal_str;
///
/// assert_eq!(pascal_str(b"\x0bhello world"), b"hello world"[..].into());
/// assert_eq!(pascal_str(b"\x0chello world"), None);
@ -74,7 +74,7 @@ pub fn pascal_str(b: &[u8]) -> Option<&[u8]>
/// # Examples
///
/// ```
/// use maraiah::durandal::text::mac_roman_conv;
/// use maraiah::marathon::text::mac_roman_conv;
///
/// assert_eq!(mac_roman_conv(b"p\x8cth"), "påth");
/// assert_eq!(mac_roman_conv(b"I\xd5ve"), "Ive");
@ -99,7 +99,7 @@ pub fn mac_roman_conv(s: &[u8]) -> String
/// # Examples
///
/// ```
/// use maraiah::durandal::text::mac_roman_cstr;
/// use maraiah::marathon::text::mac_roman_cstr;
///
/// assert_eq!(mac_roman_cstr(b"I\xd5ve awaken\0ed"), "Ive awaken");
/// assert_eq!(mac_roman_cstr(b"I\xd5ve awaken\0"), "Ive awaken");

View File

@ -1,6 +1,6 @@
//! Structures used by Marathon's Map format's terminal definitions.
use crate::durandal::{bin::*, err::*, text::*};
use crate::{durandal::{bin::*, err::*}, marathon::text::*};
use bitflags::bitflags;
/// Reads an `InterGroup`.

View File

@ -1,7 +1,7 @@
//! Marathon Wad format handling.
use crate::{durandal::{bin::*, err::*, image, text::mac_roman_cstr},
marathon::{map, phy, pict, trm}};
use crate::{durandal::{bin::*, err::*, image},
marathon::{map, phy, pict, text::mac_roman_cstr, trm}};
use std::collections::BTreeMap;
/// Reads all chunks in an entry.

View File

@ -1,29 +1,28 @@
//! Main map editor module.
//!
//! The entry point is responsible for maintaining the lifetime of the editor
//! state and human interactions with it, but is otherwise not permitted to
//! directly edit it.
//! and human interactions with it, but is otherwise not permitted to directly
//! edit it.
pub mod block;
pub mod state;
mod block;
mod state;
use crate::durandal::image::*;
use super::{color, draw::*};
impl MapEditor
{
/// Creates a closed map editor.
#[inline]
pub const fn new_closed() -> Self
{
MapEditor::Closed
}
/// Opens the editor with a new empty map.
#[inline]
pub fn open_new(&mut self)
{
*self = MapEditor::Opened(state::State::default());
self.map = Some(state::OpenMap::default());
}
/// Opens the editor with an existing map.
pub fn open_buf(&mut self, b: &[u8])
{
self.map = Some(state::OpenMap::open_buf(b));
}
/// Draws the screen for this editor state.
@ -36,8 +35,8 @@ impl MapEditor
let iw = im.w();
let ih = im.h();
match self {
MapEditor::Closed => {
match &self.map {
None => {
let tx_top = "Map Required To Proceed";
let tx_bot = "CAS.qterm//CyberAcme Systems Inc.";
@ -51,9 +50,11 @@ impl MapEditor
d.rect(Rect{x: 0, y: dh - 18, w: dw, h: 18}, color::DARK_RED);
d.text((4, dh - 4), tx_bot, color::RED);
}
MapEditor::Opened(st) => {
Some(st) => {
let text = &format!("tool: {:?}", st.tool());
d.clear(Color16::new(0, 0, 0));
d.text((dw/2, dh/2), &format!("tool: {:?}", st.tool), color::RED);
d.text((dw/2, dh/2), text, color::RED);
}
}
}
@ -62,34 +63,28 @@ impl MapEditor
#[inline]
pub fn is_closed(&self) -> bool
{
match self {
MapEditor::Closed => true,
MapEditor::Opened(_) => false,
}
self.map.is_none()
}
/// Returns `true` if `self` is opened.
#[inline]
pub fn is_opened(&self) -> bool
{
match self {
MapEditor::Closed => false,
MapEditor::Opened(_) => true,
}
self.map.is_some()
}
}
impl Default for MapEditor
{
#[inline]
fn default() -> Self {Self::new_closed()}
fn default() -> Self {Self{map: None}}
}
/// An entire map editor, which may be opened or closed.
pub enum MapEditor
/// An entire map editor, which may be opened or closed. Holds state which
/// outlives the opened map state.
pub struct MapEditor
{
Closed,
Opened(state::State),
map: Option<state::OpenMap>,
}
// EOF

View File

@ -9,11 +9,11 @@ impl Default for Block
}
}
/// Copyable map state.
#[derive(Clone)]
pub struct Block
/// Copyable, versioned map state.
#[derive(Clone, Debug)]
pub(super) struct Block
{
info: map::Minf,
pub(super) info: map::Minf,
}
// EOF

View File

@ -1,43 +1,77 @@
use super::block;
//! Map editor state.
impl State
use super::block;
use crate::marathon::{machdr, wad};
impl OpenMap
{
fn cur_block(&self) -> &block::Block
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()
}
fn cur_block_mut(&mut self) -> &mut block::Block
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 State
impl Default for OpenMap
{
#[inline]
fn default() -> Self
{
Self{blocks: vec![block::Block::default()],
tool: Tool::default()}
tools: Self::default_tools()}
}
}
impl Default for Tool
{
#[inline]
fn default() -> Self {Tool::Points}
}
/// The state of an opened map editor.
pub struct State
pub(super) struct OpenMap
{
pub(super) blocks: Vec<block::Block>,
pub(super) tool: Tool,
blocks: Vec<block::Block>,
tools: (Tool, Tool),
}
/// A tool in the map editor.
#[derive(Debug)]
#[derive(Clone, Debug)]
pub(super) enum Tool
{
Points,

View File

@ -8,8 +8,6 @@ edition = "2018"
build = "build.rs"
[dependencies]
maraiah = {path = "../.."}
atk-sys = "0.8"
cairo-sys-rs = "0.8"
gdk-pixbuf-sys = "0.8"
@ -18,6 +16,8 @@ gio-sys = "0.8"
glib-sys = "0.8"
gobject-sys = "0.8"
gtk-sys = {version = "0.8", features = ["v3_16"]}
maraiah = {path = "../.."}
memmap = "0.7"
pango-sys = "0.8"
[[bin]]

View File

@ -1156,7 +1156,7 @@ express Statement of Purpose.
<object class="GtkToolButton" id="btn-point">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Points</property>
<property name="label" translatable="yes">_Points</property>
<property name="use_underline">True</property>
<property name="icon_widget">img-points</property>
<child internal-child="accessible">
@ -1175,7 +1175,7 @@ express Statement of Purpose.
<object class="GtkToolButton" id="btn-lines">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Lines</property>
<property name="label" translatable="yes">_Lines</property>
<property name="use_underline">True</property>
<property name="icon_widget">img-lines</property>
<child internal-child="accessible">
@ -1195,7 +1195,7 @@ express Statement of Purpose.
<object class="GtkToolButton" id="btn-polys">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Polygons</property>
<property name="label" translatable="yes">P_olygons</property>
<property name="use_underline">True</property>
<property name="icon_widget">img-polys</property>
<child internal-child="accessible">

View File

@ -3,7 +3,11 @@
use cairo_sys::*;
use gdk_pixbuf_sys::*;
use gdk_sys::*;
use maraiah::{c_str, durandal::{ffi::*, image::*}, rozinante::draw::*};
use gobject_sys::*;
use gtk_sys::*;
use maraiah::{c_str,
durandal::{ffi, image::*},
rozinante::{draw::*, editor}};
/// Converts a `Color` to a `f64` triple.
fn flt_color(cr: impl Color) -> (f64, f64, f64)
@ -98,7 +102,7 @@ impl DrawArea for CrDrawArea
let x = f64::from(pos.0);
let y = f64::from(pos.1);
let text = CString::new(text).unwrap();
let text = ffi::CString::new(text).unwrap();
unsafe {
cairo_set_source_rgb(self.ctx, r, g, b);
@ -119,6 +123,53 @@ impl DrawArea for CrDrawArea
}
}
impl MapEditor
{
/// Propagates updated information to widgets.
pub fn cause_update(&mut self)
{
unsafe {gtk_widget_queue_draw(self.draw);}
}
/// Sets the drawing area widget.
pub fn set_draw(&mut self, draw: *mut GtkWidget)
{
self.draw = draw;
unsafe {g_object_ref(self.draw as _);}
}
}
impl Default for MapEditor
{
fn default() -> Self
{
Self{edit: editor::MapEditor::default(),
draw: ffi::null_mut()}
}
}
impl Drop for MapEditor
{
fn drop(&mut self)
{
unsafe {g_object_unref(self.draw as _);}
}
}
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}
}
/// An image for a `CrDrawArea`.
pub struct CrImage(pub *const GdkPixbuf);
@ -130,4 +181,14 @@ pub struct CrDrawArea
h: Coord,
}
/// Specialized map editor which has callbacks for frontend purposes.
pub struct MapEditor
{
edit: editor::MapEditor,
draw: *mut GtkWidget,
}
/// A runtime reference to the map editor.
pub type MapEditorRef = std::cell::RefCell<MapEditor>;
// EOF

View File

@ -7,8 +7,18 @@ use gio_sys::*;
use glib_sys::*;
use gobject_sys::*;
use gtk_sys::*;
use maraiah::{c_str, durandal::ffi, rozinante::editor};
use std::{cell::RefCell, rc::Rc};
use maraiah::{c_str, durandal::ffi};
use std::{cell::RefCell, marker::PhantomData, rc::Rc};
const ACTIVATE: ffi::NT = c_str!("activate");
const APP_ID: ffi::NT = c_str!("net.greyserv.maraiah.tycho");
const DELETE_EVENT: ffi::NT = c_str!("delete-event");
const DESTROY: ffi::NT = c_str!("destroy");
const DRAW: ffi::NT = c_str!("draw");
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");
const PATH_CSS: ffi::NT = c_str!("/net/greyserv/maraiah/tycho/css");
/// Called when the application activates in order to set everything up.
unsafe extern "C" fn app_activate(app: *mut GtkApplication, edit: gpointer)
@ -19,41 +29,32 @@ unsafe extern "C" fn app_activate(app: *mut GtkApplication, edit: gpointer)
setup_css();
let path = c_str!("/net/greyserv/maraiah/tycho/ui");
let b = gtk_builder_new_from_resource(path);
let b = Refc::new(gtk_builder_new_from_resource(PATH_BUILDER));
setup_draw_area(b, edit.clone());
setup_win_map_view(b);
setup_win_map_tools(b);
setup_win_map_prop(b);
setup_about_dlg(b);
setup_win_main(b, app, edit.clone());
g_object_unref(b as _);
setup_draw_area(&b, edit.clone());
setup_win_map_view(&b);
setup_win_map_tools(&b);
setup_win_map_prop(&b);
setup_about_dlg(&b);
setup_win_main(&b, app, edit.clone());
}
/// Sets up the map view window's drawing area.
unsafe fn setup_draw_area(b: *mut GtkBuilder, edit: Rc<MapEditorRef>)
unsafe fn setup_draw_area(b: &Refc<GtkBuilder>, edit: Rc<MapEditorRef>)
{
/// All of the state necessary for the drawing area.
struct RenderState
{
im_nomap: *mut GdkPixbuf,
ax: *mut GtkAdjustment,
ay: *mut GtkAdjustment,
im_nomap: Refc<'static, GdkPixbuf>,
ax: Refc<'static, GtkAdjustment>,
ay: Refc<'static, GtkAdjustment>,
edit: Rc<MapEditorRef>,
}
/// Callback to finalize the drawing area.
unsafe extern "C" fn c_done(_: *mut GtkWidget, rend: gpointer)
{
let rend = Box::from_raw(rend as *mut RenderState);
g_object_unref(rend.im_nomap as gpointer as _);
g_object_unref(rend.ax as _);
g_object_unref(rend.ay as _);
// data is dropped and freed here
Box::from_raw(rend as *mut RenderState);
}
/// Callback to draw on the drawing area.
@ -62,19 +63,18 @@ unsafe fn setup_draw_area(b: *mut GtkBuilder, edit: Rc<MapEditorRef>)
rend: gpointer)
-> gboolean
{
let rend = rend as *mut RenderState;
let rend = &mut *rend;
let rend = &mut *(rend as *mut RenderState);
let w = f64::from(gtk_widget_get_allocated_width(wid));
let h = f64::from(gtk_widget_get_allocated_height(wid));
gtk_adjustment_set_lower(rend.ax, 0.0);
gtk_adjustment_set_upper(rend.ax, w);
gtk_adjustment_set_lower(*rend.ax, 0.0);
gtk_adjustment_set_upper(*rend.ax, w);
gtk_adjustment_set_lower(rend.ay, 0.0);
gtk_adjustment_set_upper(rend.ay, h);
gtk_adjustment_set_lower(*rend.ay, 0.0);
gtk_adjustment_set_upper(*rend.ay, h);
let im = CrImage(rend.im_nomap);
let im = CrImage(*rend.im_nomap);
let mut dr = CrDrawArea::new(ctx, w, h);
rend.edit.borrow().draw(&mut dr, &im);
@ -82,61 +82,58 @@ unsafe fn setup_draw_area(b: *mut GtkBuilder, edit: Rc<MapEditorRef>)
1
}
let wid: *mut GtkDrawingArea = get_obj(b, c_str!("draw-area"));
let wid = get_obj::<GtkDrawingArea>(b, c_str!("draw-area"));
// add a callback to draw the area when updated
edit.borrow_mut().call.push(Box::new(move || {
gtk_widget_queue_draw(wid as _);
}));
edit.borrow_mut().set_draw(wid as _);
// get all of the necessary state and related objects
let ax: *mut GtkAdjustment = get_obj(b, c_str!("adj-map-horz"));
let ay: *mut GtkAdjustment = get_obj(b, c_str!("adj-map-vert"));
let ax = Refc::new(get_obj::<GtkAdjustment>(b, c_str!("adj-map-horz")));
let ay = Refc::new(get_obj::<GtkAdjustment>(b, c_str!("adj-map-vert")));
g_object_ref(ax as _);
g_object_ref(ay as _);
g_object_ref(*ax as _);
g_object_ref(*ay as _);
let im_nomap = load_img(c_str!("/net/greyserv/maraiah/tycho/tycho1.png"));
let im_nomap = Refc::new(load_img(IM_NOMAP));
let rend = RenderState{im_nomap, ax, ay, edit};
let rend = Box::into_raw(Box::new(rend));
connect(wid as _, c_str!("destroy"), c_done as _, rend as _);
connect(wid as _, c_str!("draw"), c_draw as _, rend as _);
connect(wid, DESTROY, c_done as _, rend);
connect(wid, DRAW, c_draw as _, rend);
}
/// Sets up the map view window.
unsafe fn setup_win_map_view(b: *mut GtkBuilder)
unsafe fn setup_win_map_view(b: &Refc<GtkBuilder>)
{
let win: *mut GtkWindow = get_obj(b, c_str!("win-map-view"));
let btn: *mut GtkMenuItem = get_obj(b, c_str!("btn-show-map-view"));
let win = get_obj::<GtkWindow >(b, c_str!("win-map-view"));
let btn = get_obj::<GtkMenuItem>(b, c_str!("btn-show-map-view"));
connect_hide(win as _);
connect_show(btn as _, win as _);
connect_hide(win);
connect_show(btn, win);
}
/// Sets up the map tools window.
unsafe fn setup_win_map_tools(b: *mut GtkBuilder)
unsafe fn setup_win_map_tools(b: &Refc<GtkBuilder>)
{
let win: *mut GtkWindow = get_obj(b, c_str!("win-map-tools"));
let btn: *mut GtkMenuItem = get_obj(b, c_str!("btn-show-map-tools"));
let win = get_obj::<GtkWindow >(b, c_str!("win-map-tools"));
let btn = get_obj::<GtkMenuItem>(b, c_str!("btn-show-map-tools"));
connect_hide(win as _);
connect_show(btn as _, win as _);
connect_hide(win);
connect_show(btn, win);
}
/// Sets up the map properties window.
unsafe fn setup_win_map_prop(b: *mut GtkBuilder)
unsafe fn setup_win_map_prop(b: &Refc<GtkBuilder>)
{
let win: *mut GtkWindow = get_obj(b, c_str!("win-map-prop"));
let btn: *mut GtkMenuItem = get_obj(b, c_str!("btn-show-map-prop"));
let win = get_obj::<GtkWindow >(b, c_str!("win-map-prop"));
let btn = get_obj::<GtkMenuItem>(b, c_str!("btn-show-map-prop"));
connect_hide(win as _);
connect_show(btn as _, win as _);
connect_hide(win);
connect_show(btn, win);
}
/// Sets up the about dialogue.
unsafe fn setup_about_dlg(b: *mut GtkBuilder)
unsafe fn setup_about_dlg(b: &Refc<GtkBuilder>)
{
/// Callback to show the dialogue when the "About" button is pressed, and
/// hide it when the "Close" button is pressed on it.
@ -146,26 +143,24 @@ unsafe fn setup_about_dlg(b: *mut GtkBuilder)
gtk_widget_hide(dlg as _);
}
let dlg: *mut GtkAboutDialog = get_obj(b, c_str!("dlg-about"));
let btn: *mut GtkMenuItem = get_obj(b, c_str!("btn-about"));
let dlg = get_obj::<GtkAboutDialog>(b, c_str!("dlg-about"));
let btn = get_obj::<GtkMenuItem >(b, c_str!("btn-about"));
let it = env!("CARGO_PKG_AUTHORS").split(';');
let mut v = ffi::CStringVec::new_from_iter(it).unwrap();
let img = load_img(c_str!("/net/greyserv/maraiah/tycho/tycho2.png"));
let img = Refc::new(load_img(IM_ABOUT));
gtk_about_dialog_set_authors(dlg, v.as_mut_ptr());
gtk_about_dialog_set_version(dlg, c_str!(env!("CARGO_PKG_VERSION")));
gtk_about_dialog_set_website(dlg, c_str!(env!("CARGO_PKG_HOMEPAGE")));
gtk_about_dialog_set_logo(dlg, img);
gtk_about_dialog_set_logo(dlg, *img);
connect_hide(dlg as _);
connect(btn as _, c_str!("activate"), c_show_act as _, dlg as _);
g_object_unref(img as gpointer as _);
connect_hide(dlg);
connect(btn, ACTIVATE, c_show_act as _, dlg);
}
/// Sets up explicit window finalization for the main window.
unsafe fn setup_explicit_drop(b: *mut GtkBuilder, win: *mut GtkWindow)
unsafe fn setup_explicit_drop(b: &Refc<GtkBuilder>, win: *mut GtkWindow)
{
/// Callback to explicitly finalize all windows on exit.
unsafe extern "C" fn c_done(_: *mut GtkWidget, exp_del: gpointer)
@ -182,7 +177,7 @@ unsafe fn setup_explicit_drop(b: *mut GtkBuilder, 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 head = gtk_builder_get_objects(**b);
let mut lst = &*head;
loop {
@ -213,11 +208,11 @@ unsafe fn setup_explicit_drop(b: *mut GtkBuilder, win: *mut GtkWindow)
let exp_del = Box::into_raw(Box::new(exp_del));
connect(win as _, c_str!("destroy"), c_done as _, exp_del as _);
connect(win, DESTROY, c_done as _, exp_del);
}
/// Sets up the main menu window.
unsafe fn setup_win_main(b: *mut GtkBuilder,
unsafe fn setup_win_main(b: &Refc<GtkBuilder>,
app: *mut GtkApplication,
edit: Rc<MapEditorRef>)
{
@ -230,14 +225,13 @@ unsafe fn setup_win_main(b: *mut GtkBuilder,
/// Callback to create a new map when the "New" button is pressed.
unsafe extern "C" fn c_new_act(_: *mut GtkWidget, edit: gpointer)
{
let edit = edit as *const MapEditorRef;
let edit = &*edit;
let edit = &*(edit as *const MapEditorRef);
let mut edit = edit.borrow_mut();
if edit.is_opened() {
let titl = c_str!("Confirm");
let text = c_str!("Are you sure you want to create a new project? \
Data may be lost.");
Unsaved data may be lost.");
if !run_ok_cancel_dlg(titl, text) {
return;
@ -248,15 +242,33 @@ unsafe fn setup_win_main(b: *mut GtkBuilder,
edit.cause_update();
}
/// Callback to finalize the editor state reference.
unsafe extern "C" fn c_new_done(_: *mut GtkWidget, edit: gpointer)
/// Callback to open an existing map when the "Open" button is pressed.
unsafe extern "C" fn c_open_act(_: *mut GtkWidget, edit: gpointer)
{
let edit = edit as *const MapEditorRef;
Rc::from_raw(edit);
let edit = &*(edit as *const MapEditorRef);
let mut edit = edit.borrow_mut();
if edit.is_opened() {
let titl = c_str!("Confirm");
let text = c_str!("Are you sure you want to open this project? \
Unsaved data may be lost.");
if !run_ok_cancel_dlg(titl, text) {
return;
}
}
if let Some(path) = run_file_chooser_open() {
// TODO: handle errors gracefully
let fp = std::fs::File::open(&path).unwrap();
let mm = memmap::Mmap::map(&fp).unwrap();
edit.open_buf(&mm);
edit.cause_update();
}
}
// set up main window
let win: *mut GtkWindow = get_obj(b, c_str!("win-main"));
let win = get_obj::<GtkWindow>(b, c_str!("win-main"));
setup_explicit_drop(b, win);
@ -264,30 +276,25 @@ unsafe fn setup_win_main(b: *mut GtkBuilder,
gtk_widget_show_all(win as _);
// set up buttons
let btn: *mut GtkMenuItem = get_obj(b, c_str!("btn-quit"));
let btn = get_obj::<GtkMenuItem>(b, c_str!("btn-quit"));
connect(btn, ACTIVATE, c_quit_act as _, win);
connect(btn as _, c_str!("activate"), c_quit_act as _, win as _);
let btn = get_obj::<GtkMenuItem>(b, c_str!("btn-new"));
connect(btn, ACTIVATE, c_new_act as _, connect_ref(btn, edit.clone()));
let btn: *mut GtkMenuItem = get_obj(b, c_str!("btn-new"));
let eptr = Rc::into_raw(edit.clone());
connect(btn as _, c_str!("activate"), c_new_act as _, eptr as _);
connect(btn as _, c_str!("destroy"), c_new_done as _, eptr as _);
let btn = get_obj::<GtkMenuItem>(b, c_str!("btn-open"));
connect(btn, ACTIVATE, c_open_act as _, connect_ref(btn, edit.clone()));
}
/// Sets up the CSS styling providers.
unsafe fn setup_css()
{
let path = c_str!("/net/greyserv/maraiah/tycho/css");
let css = gtk_css_provider_new();
gtk_css_provider_load_from_resource(css, path);
let css = Refc::new(gtk_css_provider_new());
gtk_css_provider_load_from_resource(*css, PATH_CSS);
let scr = gdk_screen_get_default();
let pri = GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32;
gtk_style_context_add_provider_for_screen(scr, css as _, pri);
g_object_unref(css as _);
gtk_style_context_add_provider_for_screen(scr, *css as _, pri);
}
/// Runs a modal OK/Cancel dialogue.
@ -301,7 +308,7 @@ unsafe fn run_ok_cancel_dlg(title: ffi::NT, text: ffi::NT) -> bool
GTK_RESPONSE_ACCEPT,
c_str!("_Cancel"),
GTK_RESPONSE_REJECT,
ffi::null_mut::<gpointer>());
ffi::null_mut_void());
let area = gtk_dialog_get_content_area(dlg as _);
let labl = gtk_label_new(text);
@ -313,14 +320,44 @@ unsafe fn run_ok_cancel_dlg(title: ffi::NT, text: ffi::NT) -> bool
gtk_widget_destroy(dlg);
match res {
GTK_RESPONSE_ACCEPT => true,
_ => false,
}
res == GTK_RESPONSE_ACCEPT
}
/// Runs a modal Open File dialogue.
unsafe fn run_file_chooser_open() -> Option<String>
{
let action = GTK_FILE_CHOOSER_ACTION_OPEN;
let dlg = gtk_file_chooser_dialog_new(c_str!("Open File"),
ffi::null_mut(),
action,
c_str!("_Cancel"),
GTK_RESPONSE_CANCEL,
c_str!("_Open"),
GTK_RESPONSE_ACCEPT,
ffi::null_mut_void());
let res = gtk_dialog_run(dlg as _);
let ret = if res == GTK_RESPONSE_ACCEPT {
let fna = gtk_file_chooser_get_filename(dlg as _);
let own = ffi::CStr::from_ptr(fna);
let own = own.to_str().ok()?.to_owned();
g_free(fna as _);
Some(own)
} else {
None
};
gtk_widget_destroy(dlg);
ret
}
/// Connects a handler that hides a toplevel widget when deleted.
unsafe fn connect_hide(wid: *mut GtkWidget)
unsafe fn connect_hide<T>(wid: *mut T)
{
/// Callback to hide the widget.
unsafe extern "C" fn c_hide_del(wid: *mut GtkWidget,
@ -330,11 +367,11 @@ unsafe fn connect_hide(wid: *mut GtkWidget)
gtk_widget_hide(wid);
}
connect(wid as _, c_str!("delete-event"), c_hide_del as _, ffi::null_mut());
connect(wid, DELETE_EVENT, c_hide_del as _, ffi::null_void());
}
/// Connects a handler that shows a widget when activated.
unsafe fn connect_show(btn: *mut GtkWidget, wid: *mut GtkWidget)
unsafe fn connect_show<T, U>(btn: *mut T, wid: *mut U)
{
/// Callback to show the widget.
unsafe extern "C" fn c_show_act(_: *mut GtkWidget, wid: gpointer)
@ -342,21 +379,36 @@ unsafe fn connect_show(btn: *mut GtkWidget, wid: *mut GtkWidget)
gtk_widget_show_all(wid as _);
}
connect(btn as _, c_str!("activate"), c_show_act as _, wid as _);
connect(btn, ACTIVATE, c_show_act as _, wid);
}
/// Connects a reference-counted object to a widget.
unsafe fn connect_ref<T, U>(obj: *mut T, rc: Rc<U>) -> *const U
{
/// Callback to finalize the reference.
unsafe extern "C" fn c_done(_: *mut GtkWidget, edit: gpointer)
{
Rc::from_raw(edit as *const MapEditorRef);
}
let ptr = Rc::into_raw(rc);
connect(obj, DESTROY, c_done as _, ptr);
ptr
}
/// Gets an object from a `GtkBuilder`.
unsafe fn get_obj<T>(b: *mut GtkBuilder, name: ffi::NT) -> *mut T
unsafe fn get_obj<T>(b: &Refc<GtkBuilder>, name: ffi::NT) -> *mut T
{
let obj = gtk_builder_get_object(b, name);
obj as _
gtk_builder_get_object(**b, name) as _
}
/// Connects a signal handler.
unsafe fn connect(obj: *mut GObject, name: ffi::NT, cb: gpointer, d: gpointer)
unsafe fn connect<T, U>(obj: *mut T, name: ffi::NT, cb: gpointer, d: *const U)
{
let cb = std::mem::transmute(cb);
g_signal_connect_data(obj, name, cb, d, None, 0);
g_signal_connect_data(obj as _, name, cb, d as _, None, 0);
}
/// Loads a `Pixbuf` from a resource.
@ -384,23 +436,20 @@ fn main()
g_static_resource_init(&mut resource);
// create a container for the editor state
let edit = MapEditor{edit: editor::MapEditor::new_closed(),
call: Vec::new(),
done: Vec::new()};
let edit = MapEditor::default();
let edit = RefCell::new(edit);
let edit = Rc::new(edit);
let eptr = Rc::into_raw(edit.clone());
// create and run the app
let name = c_str!("net.greyserv.maraiah.tycho");
let app = gtk_application_new(name, 0);
let app = Refc::new(gtk_application_new(APP_ID, 0));
connect(app as _, c_str!("activate"), app_activate as _, eptr as _);
connect(*app, ACTIVATE, app_activate as _, eptr);
g_application_run(app as _, 0, ffi::null_mut());
g_application_run(*app as _, 0, ffi::null_mut());
// ok, clean up all this crap now
g_object_unref(app as _);
drop(app);
assert_eq!(Rc::strong_count(&edit), 1);
drop(edit);
@ -410,39 +459,32 @@ fn main()
}
}
impl MapEditor
impl<T> std::ops::Deref for Refc<'_, T>
{
/// Causes all update callbacks to be called.
fn cause_update(&mut self) {for cb in &mut self.call {cb();}}
type Target = *mut T;
fn deref(&self) -> &Self::Target {&self.p}
}
impl Drop for MapEditor
impl<T> Drop for Refc<'_, T>
{
/// Calls all finalization callbacks on drop.
fn drop(&mut self) {for cb in &mut self.done {cb();}}
fn drop(&mut self)
{
unsafe {
g_object_unref(self.p as _);
}
}
}
impl std::ops::Deref for MapEditor
impl<T> Refc<'_, T>
{
type Target = editor::MapEditor;
fn deref(&self) -> &editor::MapEditor {&self.edit}
fn new(p: *mut T) -> Self {Self{p, l: PhantomData}}
}
impl std::ops::DerefMut for MapEditor
struct Refc<'a, T>
{
fn deref_mut(&mut self) -> &mut editor::MapEditor {&mut self.edit}
p: *mut T,
l: PhantomData<&'a *mut T>,
}
/// Specialized map editor which has callbacks for frontend purposes.
struct MapEditor
{
edit: editor::MapEditor,
call: Vec<Box<dyn FnMut()>>,
done: Vec<Box<dyn FnMut()>>,
}
/// A runtime reference to the map editor.
type MapEditorRef = RefCell<MapEditor>;
// EOF