make flags display on properties pane

gui-branch
an 2019-04-03 21:30:22 -04:00
parent 3b8790b442
commit f895dac2b8
11 changed files with 712 additions and 571 deletions

View File

@ -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;
}
}

View File

@ -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<NativeImage = I>,
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<state::OpenMap>,
blocks: Vec<Block>,
tools: (Tool, Tool),
}
/// A tool in the map editor.
#[derive(Clone, Debug)]
pub enum Tool
{
Points,
Lines,
Polygons,
}
// EOF

View File

@ -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

View File

@ -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<block::Block>,
tools: (Tool, Tool),
}
/// A tool in the map editor.
#[derive(Clone, Debug)]
pub(super) enum Tool
{
Points,
Lines,
Polygons,
}
// EOF

View File

@ -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"

View File

@ -1,15 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1
Author: Alison Sanderson
-->
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.16"/>
<!-- interface-license-type mit -->
<!-- interface-name Maraiah Tycho -->
<!-- interface-description Tycho map editor for Maraiah. -->
<!-- interface-authors Alison Sanderson -->
<object class="GtkAdjustment" id="adj-map-horz">
<property name="upper">100</property>
<property name="step_increment">1</property>
@ -226,151 +218,135 @@ Author: Alison Sanderson
<property name="can_focus">False</property>
<property name="left_padding">12</property>
<child>
<object class="GtkBox">
<object class="GtkGrid" id="con-f-ent">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox">
<object class="GtkCheckButton">
<property name="label" translatable="yes">Solo</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkCheckButton">
<property name="label" translatable="yes">Solo</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">The map can be played in single-player.</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton">
<property name="label" translatable="yes">Co-op</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">The map can be played in multi-player co-operative.</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton">
<property name="label" translatable="yes">Carnage</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">The map can be played in multi-player Carnage.</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton">
<property name="label" translatable="yes">KTMWTB</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">The map can be played in multi-player Kill The Man With The Ball</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">The map can be played in single-player.</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<object class="GtkCheckButton">
<property name="label" translatable="yes">Co-op</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkCheckButton">
<property name="label" translatable="yes">King Of The Hill</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">The map can be played in multi-player King of the Hill.</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton">
<property name="label" translatable="yes">Defense</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">The map can be played in multi-player Defense.</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton">
<property name="label" translatable="yes">Rugby</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">The map can be played in multi-player Rugby.</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton">
<property name="label" translatable="yes">Capture The Flag</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">The map can be played in multi-player Capture The Flag.</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">The map can be played in multi-player co-operative.</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton">
<property name="label" translatable="yes">Carnage</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">The map can be played in multi-player Carnage.</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton">
<property name="label" translatable="yes">KTMWTB</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">The map can be played in multi-player Kill The Man With The Ball</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkCheckButton">
<property name="label" translatable="yes">King Of The Hill</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">The map can be played in multi-player King of the Hill.</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton">
<property name="label" translatable="yes">Defense</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">The map can be played in multi-player Defense.</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton">
<property name="label" translatable="yes">Rugby</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">The map can be played in multi-player Rugby.</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton">
<property name="label" translatable="yes">Capture The Flag</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">The map can be played in multi-player Capture The Flag.</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
</packing>
</child>
</object>
@ -411,7 +387,7 @@ Author: Alison Sanderson
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkBox">
<object class="GtkBox" id="con-f-msn">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
@ -583,7 +559,7 @@ Author: Alison Sanderson
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkBox">
<object class="GtkBox" id="con-f-env">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>

View File

@ -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<T> std::ops::Deref for Refc<'_, T>
{
type Target = *mut T;
fn deref(&self) -> &Self::Target {&self.p}
}
impl<T> Drop for Refc<'_, T>
{
fn drop(&mut self)
{
unsafe {
g_object_unref(self.p as _);
}
}
}
impl<T> 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<gpointer> {
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<gpointer> {
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<MapEditor>;
/// 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

View File

@ -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

View File

@ -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<T, F>(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<T, F>(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<T, F>(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<PropFlag>,
pub fenv: Vec<PropFlag>,
pub fmsn: Vec<PropFlag>,
}
/// A runtime reference to the map editor.
pub type MapEditorRef = std::cell::RefCell<MapEditor>;
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

View File

@ -0,0 +1,113 @@
//! GLib interfaces.
use glib_sys::*;
use gobject_sys::*;
use maraiah::durandal::ffi;
use std::marker::PhantomData;
impl<T> std::ops::Deref for Refc<'_, T>
{
type Target = *mut T;
fn deref(&self) -> &Self::Target {&self.p}
}
impl<T> Drop for Refc<'_, T>
{
fn drop(&mut self)
{
unsafe {
g_object_unref(self.p as _);
}
}
}
impl<T> 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<gpointer> {
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<gpointer> {
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

View File

@ -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<GtkBuilder>, name: ffi::NT) -> Vec<PropFlag>
{
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<MapEditorRef>)
{
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<MapEditorRef>, it: I)
where I: Iterator<Item = &'a mut PropFlag>
{
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<GtkBuilder>, edit: Rc<MapEditorRef>)
{
@ -197,40 +245,37 @@ unsafe fn setup_explicit_drop(b: &Refc<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 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<I, F>(it: I, ty: GType, mut f: F)
where I: Iterator<Item = gpointer>,
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<GtkBuilder>,
app: *mut GtkApplication,
@ -248,7 +293,7 @@ unsafe fn setup_win_main(b: &Refc<GtkBuilder>,
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<GtkBuilder>,
}
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<GtkBuilder>,
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<GtkBuilder>,
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<T>(b: &Refc<GtkBuilder>, name: ffi::NT) -> *mut T
// Connects a signal handler.
unsafe fn connect<T, U>(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.