Merge branch 'gui-branch'

gui-branch
an 2019-03-31 20:28:13 -04:00
commit a523d6f8d4
51 changed files with 1772 additions and 867 deletions

2
.gitignore vendored
View File

@ -1,6 +1,6 @@
/target /target
/out /out
*.res
**/*.rs.bk **/*.rs.bk
Cargo.lock Cargo.lock
perf.data* perf.data*
*.bat

View File

@ -1,6 +1,6 @@
Some of the data contained in tests/data and benches/data is Some of the data contained in tests/data and benches/data is
Copyright © Bungie Software. I do not own them, but am permitted to Copyright © Bungie Software. I do not own them, but am permitted to
redistribute them. Everything else is: redistribute them. Everything else is public domain, as stated:
To the extent possible under law, I, Alison Sanderson, have waived all To the extent possible under law, I, Alison Sanderson, have waived all
copyright and related or neighboring rights to this Document as described by copyright and related or neighboring rights to this Document as described by

View File

@ -1,5 +1,7 @@
//! Library for utilities. //! Library for utilities.
#[macro_use]
pub mod ffi;
#[macro_use] #[macro_use]
pub mod err; pub mod err;
#[macro_use] #[macro_use]
@ -13,6 +15,5 @@ pub mod file;
pub mod fixed; pub mod fixed;
pub mod image; pub mod image;
pub mod sound; pub mod sound;
pub mod text;
// EOF // EOF

View File

@ -1,6 +1,6 @@
//! Binary data conversion utilities. //! Binary data conversion utilities.
use crate::durandal::{err::*, text::mac_roman_conv}; use crate::durandal::err::*;
use std::{fmt, num::NonZeroU16}; use std::{fmt, num::NonZeroU16};
#[doc(hidden)] #[doc(hidden)]
@ -505,14 +505,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 impl PartialEq<[u8; 4]> for Ident
{ {
#[inline] #[inline]
@ -544,7 +536,7 @@ impl<'a> PartialEq<&'a [u8; 4]> for Ident
/// assert_eq!(&Ident(*b"POLY"), *b"POLY"); /// assert_eq!(&Ident(*b"POLY"), *b"POLY");
/// 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))] #[cfg_attr(feature = "serde_obj", derive(serde::Serialize, serde::Deserialize))]
pub struct Ident(/** The individual bytes of this identifier. */ pub [u8; 4]); pub struct Ident(/** The individual bytes of this identifier. */ pub [u8; 4]);

View File

@ -58,6 +58,6 @@ pub fn crc32(b: &[u8], s: u32) -> u32
} }
const ISO_3309_POLYNOMIAL: u32 = 0xEDB8_8320; const ISO_3309_POLYNOMIAL: u32 = 0xEDB8_8320;
const ADLER32_MODULO: u32 = 65521; const ADLER32_MODULO: u32 = 0xFFF1;
// EOF // EOF

87
source/durandal/ffi.rs Normal file
View File

@ -0,0 +1,87 @@
//! Foreign function interface utilities.
use crate::durandal::err::*;
pub use std::{ffi::*, os::raw::*, ptr::{null, null_mut}};
/// Creates a C string from a literal.
#[macro_export]
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>
{
let mut v = Self::new();
for st in it {
v.push(CString::new(st)?);
}
Ok(v)
}
/// Pushes a new `CString`.
#[inline]
pub fn push(&mut self, st: CString)
{
self.cv.insert(self.cv.len() - 1, st.as_ptr());
self.sv.push(st);
}
/// 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()
}
}
impl Default for CStringVec
{
#[inline]
fn default() -> Self {Self::new()}
}
/// An owned null-terminated string vector.
#[derive(Debug)]
pub struct CStringVec
{
sv: Vec<CString>,
cv: Vec<NT>,
}
/// A null-terminated byte string pointer.
pub type NT = *const c_char;
// EOF

View File

@ -1,6 +1,6 @@
use maraiah::{durandal::{err::*, file::*, image::*, sound::*}, use maraiah::{durandal::{err::*, file::*, image::*, sound::*},
marathon::{machdr, ppm, shp, snd, tga, wad, wav}}; 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<()> fn make_tga(_opt: &Options, fname: &str, im: &impl Image) -> ResultS<()>
{ {
@ -133,13 +133,13 @@ fn main() -> ResultS<()>
{ {
use argparse::*; use argparse::*;
let mut opt: Options = Default::default(); let mut opt = Options::default();
{ {
let mut ap = ArgumentParser::new(); let mut ap = ArgumentParser::new();
macro_rules! arg { macro_rules! arg {
($name:expr, $ref:expr, $type:expr, $desc:expr) => { ($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 ppm;
pub mod shp; pub mod shp;
pub mod snd; pub mod snd;
pub mod text;
pub mod tga; pub mod tga;
pub mod trm; pub mod trm;
pub mod wad; pub mod wad;

View File

@ -389,7 +389,7 @@ impl HuffmanTable
p += 1; p += 1;
// check our symbol table for this one (quick tree check) // check our symbol table for this one (quick tree check)
let count = u16::from(self.nums[i]); let count = self.nums[i];
if i32::from(code) - i32::from(count) < i32::from(first) { if i32::from(code) - i32::from(count) < i32::from(first) {
return Ok((i, self.syms[usize::from(index + code - first)])); return Ok((i, self.syms[usize::from(index + code - first)]));

View File

@ -1,7 +1,7 @@
//! Structures used by Marathon's Map format. //! Structures used by Marathon's Map format.
use crate::{durandal::{bin::*, err::*, fixed::*, text::*}, use crate::{durandal::{bin::*, err::*, fixed::*},
marathon::xfer::TransferMode}; marathon::{text::*, xfer::TransferMode}};
use bitflags::bitflags; use bitflags::bitflags;
/// Reads a `LightFunc` object. /// Reads a `LightFunc` object.
@ -222,7 +222,7 @@ fn read_poly_inter(b: &[u8]) -> ResultS<Polygon>
} }
Ok(Polygon{tex_flr, tex_cei, hei_flr, hei_cei, lit_flr, lit_cei, xfr_flr, Ok(Polygon{tex_flr, tex_cei, hei_flr, hei_cei, lit_flr, lit_cei, xfr_flr,
xfr_cei, ..Default::default()}) xfr_cei, ..Polygon::default()})
} }
/// Reads a `POLY` chunk. /// Reads a `POLY` chunk.
@ -751,7 +751,7 @@ pub struct Note
/// Static map information. /// Static map information.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct Minf pub struct Minf
{ {
pub texture_id: u16, pub texture_id: u16,

View File

@ -47,8 +47,8 @@ fn read_pm_header<'a>(b: &'a [u8],
} }
let rle = pack_t == PackType::Default || let rle = pack_t == PackType::Default ||
pack_t == PackType::Rle16 && depth == Depth::Bits16 || pack_t == PackType::Rle16 && depth == Depth::_16 ||
pack_t == PackType::Rle32 && depth == Depth::Bits32; pack_t == PackType::Rle32 && depth == Depth::_32;
let pitch = usize::from(pt_fl & 0x3FFF); let pitch = usize::from(pt_fl & 0x3FFF);
@ -61,7 +61,7 @@ fn read_pm_ind(mut im: Image8, b: &[u8], hdr: Header) -> ResultS<Image8>
let clut = ok!(hdr.clut, "no CLUT in indexed mode")?; let clut = ok!(hdr.clut, "no CLUT in indexed mode")?;
let mut p = 0; let mut p = 0;
if hdr.pitch < 8 && hdr.depth == Depth::Bits8 { if hdr.pitch < 8 && hdr.depth == Depth::_8 {
// uncompressed 8-bit colormap indices // uncompressed 8-bit colormap indices
for _ in 0..im.h() { for _ in 0..im.h() {
for _ in 0..im.w() { for _ in 0..im.w() {
@ -79,7 +79,7 @@ fn read_pm_ind(mut im: Image8, b: &[u8], hdr: Header) -> ResultS<Image8>
for _ in 0..im.h() { for _ in 0..im.h() {
let (d, pp) = read_rle::<u8>(&b[p..], hdr.pitch)?; let (d, pp) = read_rle::<u8>(&b[p..], hdr.pitch)?;
let d = if hdr.depth < Depth::Bits8 { let d = if hdr.depth < Depth::_8 {
expand_data(d, hdr.depth)? expand_data(d, hdr.depth)?
} else { } else {
d d
@ -195,12 +195,12 @@ fn read_pm_area(im: Image8, b: &[u8], pack: bool, clip: bool)
let (b, hdr) = read_pm_header(&b[p..], pack, clip, &im)?; let (b, hdr) = read_pm_header(&b[p..], pack, clip, &im)?;
match hdr.depth { match hdr.depth {
Depth::Bits1 | Depth::_1 |
Depth::Bits2 | Depth::_2 |
Depth::Bits4 | Depth::_4 |
Depth::Bits8 => read_pm_ind(im, b, hdr), Depth::_8 => read_pm_ind(im, b, hdr),
Depth::Bits16 => read_pm_16(im, b, hdr), Depth::_16 => read_pm_16(im, b, hdr),
Depth::Bits32 => read_pm_32(im, b, hdr), Depth::_32 => read_pm_32(im, b, hdr),
} }
} }
@ -442,25 +442,25 @@ impl ReadRleData for u8
fn expand_data(b: Vec<u8>, depth: Depth) -> ResultS<Vec<u8>> fn expand_data(b: Vec<u8>, depth: Depth) -> ResultS<Vec<u8>>
{ {
let mut o = Vec::with_capacity(match depth { let mut o = Vec::with_capacity(match depth {
Depth::Bits4 => b.len() * 2, Depth::_4 => b.len() * 2,
Depth::Bits2 => b.len() * 4, Depth::_2 => b.len() * 4,
Depth::Bits1 => b.len() * 8, Depth::_1 => b.len() * 8,
_ => bail!("invalid bit depth"), _ => bail!("invalid bit depth"),
}); });
for ch in b { for ch in b {
match depth { match depth {
Depth::Bits4 => { Depth::_4 => {
for i in (0..=1).rev() { for i in (0..=1).rev() {
o.push(ch >> (i * 4) & 0xF_u8); o.push(ch >> (i * 4) & 0xF_u8);
} }
} }
Depth::Bits2 => { Depth::_2 => {
for i in (0..=3).rev() { for i in (0..=3).rev() {
o.push(ch >> (i * 2) & 0x3_u8); o.push(ch >> (i * 2) & 0x3_u8);
} }
} }
Depth::Bits1 => { Depth::_1 => {
for i in (0..=7).rev() { for i in (0..=7).rev() {
o.push(ch >> i & 0x1_u8); o.push(ch >> i & 0x1_u8);
} }
@ -484,12 +484,12 @@ struct Header
c_enum! { c_enum! {
enum Depth: u16 enum Depth: u16
{ {
Bits1 = 1, _1 = 1,
Bits2 = 2, _2 = 2,
Bits4 = 4, _4 = 4,
Bits8 = 8, _8 = 8,
Bits16 = 16, _16 = 16,
Bits32 = 32, _32 = 32,
} }
} }

View File

@ -1,4 +1,4 @@
//! Portable PixMap format images. //! Portable Pixel Map format images.
use crate::durandal::{err::*, fixed::FixedLong, image::*}; use crate::durandal::{err::*, fixed::FixedLong, image::*};
use std::io; use std::io;
@ -40,7 +40,7 @@ pub fn read_ppm(inp: &[u8]) -> ResultS<Image16>
st => break st st => break st
} }
}; };
let st = unsafe {std::str::from_utf8_unchecked(&st)}; let st = unsafe {std::str::from_utf8_unchecked(st)};
let nu = u16::from_str_radix(st, 10)?; let nu = u16::from_str_radix(st, 10)?;
Ok(nu) Ok(nu)
}; };
@ -56,9 +56,9 @@ pub fn read_ppm(inp: &[u8]) -> ResultS<Image16>
let g = FixedLong::from_int(get_num()?.into()); let g = FixedLong::from_int(get_num()?.into());
let b = FixedLong::from_int(get_num()?.into()); let b = FixedLong::from_int(get_num()?.into());
let r = (r / depth * 65535).integ() as u16; let r = (r / depth * 0xFFFF).integ() as u16;
let g = (g / depth * 65535).integ() as u16; let g = (g / depth * 0xFFFF).integ() as u16;
let b = (b / depth * 65535).integ() as u16; let b = (b / depth * 0xFFFF).integ() as u16;
im.cr.push(Color16::new(r, g, b)); im.cr.push(Color16::new(r, g, b));
} }

View File

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

View File

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

View File

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

View File

@ -2,5 +2,6 @@
pub mod color; pub mod color;
pub mod draw; pub mod draw;
pub mod editor;
// EOF // EOF

View File

@ -2,7 +2,7 @@
use crate::durandal::image::Color16; use crate::durandal::image::Color16;
pub const CR_RED: Color16 = Color16::new(0xFFFF, 0, 0); pub const RED: Color16 = Color16::new(0xFFFF, 0, 0);
pub const CR_DARK_RED: Color16 = Color16::new(0x4700, 0, 0); pub const DARK_RED: Color16 = Color16::new(0x4700, 0, 0);
// EOF // EOF

View File

@ -24,17 +24,23 @@ pub trait DrawArea
/// The height of the entire area. /// The height of the entire area.
fn h(&self) -> Coord; fn h(&self) -> Coord;
/// Fills the entire screen with `cr`. /// Fills the entire screen with `cr`. Will also default all settings.
fn clear(&self, cr: impl Color); fn clear(&mut self, cr: impl Color);
/// Changes the width for lines. The default is `1`.
fn line_width(&mut self, width: u8);
/// Draws a line from `p1` to `p2` with color `cr`.
fn line(&mut self, p1: Point, p2: Point, cr: impl Color);
/// Draws a rectangle `rect` of color `cr`. /// Draws a rectangle `rect` of color `cr`.
fn rect(&self, rect: Rect, cr: impl Color); fn rect(&mut self, rect: Rect, cr: impl Color);
/// Draws the Unicode `text` at `pos` stroked with color `cr`. /// Draws the Unicode `text` at `pos` stroked with color `cr`.
fn text(&self, pos: Point, text: &str, cr: impl Color); fn text(&mut self, pos: Point, text: &str, cr: impl Color);
/// Draws `im` at `pos`, starting from the top left column. /// Draws `im` at `pos`, starting from the top left column.
fn image(&self, pos: Point, im: &Self::NativeImage); fn image(&mut self, pos: Point, im: &Self::NativeImage);
} }
/// A type capable of representing any coordinate on any axis. /// A type capable of representing any coordinate on any axis.

View File

@ -0,0 +1,90 @@
//! Main map editor module.
//!
//! The entry point is responsible for maintaining the lifetime of the editor
//! and human interactions with it, but is otherwise not permitted to directly
//! edit it.
mod block;
mod state;
use crate::durandal::image::*;
use super::{color, draw::*};
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 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.
pub fn draw<D, I>(&self, d: &mut D, im: &I)
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.";
d.clear(Color16::new(0, 0, 0));
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: 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.clear(Color16::new(0, 0, 0));
d.text((dw/2, dh/2), text, color::RED);
}
}
}
/// Returns `true` if `self` is closed.
#[inline]
pub fn is_closed(&self) -> bool
{
self.map.is_none()
}
/// Returns `true` if `self` is opened.
#[inline]
pub fn is_opened(&self) -> bool
{
self.map.is_some()
}
}
impl Default for MapEditor
{
#[inline]
fn default() -> Self {Self{map: None}}
}
/// An entire map editor, which may be opened or closed. Holds state which
/// outlives the opened map state.
pub struct MapEditor
{
map: Option<state::OpenMap>,
}
// EOF

View File

@ -0,0 +1,19 @@
use crate::marathon::map;
impl Default for Block
{
#[inline]
fn default() -> Self
{
Self{info: map::Minf::default()}
}
}
/// Copyable, versioned map state.
#[derive(Clone, Debug)]
pub(super) struct Block
{
pub(super) info: map::Minf,
}
// EOF

View File

@ -0,0 +1,82 @@
//! 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

@ -8,23 +8,17 @@ edition = "2018"
build = "build.rs" build = "build.rs"
[dependencies] [dependencies]
maraiah = {path = "../.."} atk-sys = "0.8"
cairo-sys-rs = "0.8"
# note: these have to be updated all at once, check the gtk crate for versions gdk-pixbuf-sys = "0.8"
atk = "0.6" gdk-sys = "0.8"
cairo-rs = "0.6" gio-sys = "0.8"
cairo-sys-rs = "0.8" glib-sys = "0.8"
gdk = "0.10" gobject-sys = "0.8"
gdk-pixbuf = "0.6" gtk-sys = {version = "0.8", features = ["v3_16"]}
gio = "0.6" maraiah = {path = "../.."}
gio-sys = "0.8" memmap = "0.7"
glib = "0.7" pango-sys = "0.8"
glib-sys = "0.8"
gobject-sys = "0.8"
gtk = {version = "0.6", features = ["v3_16"]}
gtk-sys = "0.8"
pango = "0.6"
pango-sys = "0.8"
[[bin]] [[bin]]
name = "tycho" name = "tycho"

View File

@ -1,13 +1,48 @@
use std::process::Command; use std::{env, fs, io, path::Path, process::Command};
fn main() fn io_err(st: &'static str) -> io::Error
{ {
println!("cargo:rerun-if-changed=data"); io::Error::new(io::ErrorKind::Other, st)
}
Command::new("glib-compile-resources").arg("data/tycho_res.xml") fn traverse_dir(path: &Path) -> io::Result<()>
.arg("--target=data/tycho.res") {
.status() for ent in fs::read_dir(path)? {
.unwrap(); let path = ent?.path();
if path.is_dir() {
traverse_dir(&path)?;
} else {
if let Some(path) = path.to_str() {
println!("cargo:rerun-if-changed={}", path);
} else {
return Err(io_err("failed to convert path"));
}
}
}
Ok(())
}
fn main() -> io::Result<()>
{
let out_file = env::var("OUT_DIR").unwrap();
let out_file = format!("--target={}/resources", out_file);
// traverse each file in the data directory, because cargo won't do this
traverse_dir(Path::new("data"))?;
let o = Command::new("glib-compile-resources").arg("data/resources.xml")
.arg(out_file)
.output()
.unwrap();
if !o.status.success() {
dbg!(o);
Err(io_err("failed to compile resources"))
} else {
Ok(())
}
} }
// EOF // EOF

Binary file not shown.

After

Width:  |  Height:  |  Size: 805 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 972 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 538 B

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<!-- Toplevel namespace. Contains UI and helpers. -->
<gresource prefix="/net/greyserv/maraiah/tycho">
<file compressed="true" alias="tycho1.png">data/misc/tycho1.png</file>
<file compressed="true" alias="tycho2.png">data/misc/tycho2.png</file>
<file compressed="true" alias="css">data/styles.css</file>
<file compressed="true" preprocess="xml-stripblanks" alias="ui">data/ui.xml</file>
</gresource>
<!-- Icons, color. We can't make highcolor yet. -->
<gresource prefix="/net/greyserv/maraiah/tycho/icons/48x48/actions">
<file compressed="true" alias="tycho-polys.png">data/color/polygons.png</file>
<file compressed="true" alias="tycho-lines.png">data/color/lines.png</file>
<file compressed="true" alias="tycho-points.png">data/color/points.png</file>
</gresource>
<gresource prefix="/net/greyserv/maraiah/tycho/icons/48x48/apps">
<file compressed="true" alias="tycho-map.png">data/color/map.png</file>
</gresource>
</gresources>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
/* EOF */

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/net/greyserv/maraiah/tycho">
<file compressed="true" alias="tycho1">data/tycho1.png</file>
<file compressed="true" alias="tycho2">data/tycho2.png</file>
<file compressed="true" alias="polys">data/polys.png</file>
<file compressed="true" alias="lines">data/lines.png</file>
<file compressed="true" alias="points">data/points.png</file>
<file compressed="true" preprocess="xml-stripblanks" alias="ui">data/tycho.xml</file>
</gresource>
</gresources>

File diff suppressed because it is too large Load Diff

View File

@ -1,40 +0,0 @@
use maraiah::{durandal::image::*,
marathon::*,
rozinante::{color::*, draw::*}};
pub fn draw_map_none<D, I>(d: &D, im: &I)
where D: DrawArea<NativeImage = I>,
I: CacheImage
{
d.clear(Color16::new(0, 0, 0));
d.image((d.w() / 2 - im.w() / 2, d.h() / 2 - im.h() / 2), im);
d.rect(Rect{x: 0, y: 0, w: d.w(), h: 18}, CR_DARK_RED);
d.text((4, 14), "Map Required To Proceed", CR_RED);
d.rect(Rect{x: 0, y: d.h() - 18, w: d.w(), h: 18}, CR_DARK_RED);
d.text((4, d.h() - 4), "CAS.qterm//CyberAcme Systems Inc.", CR_RED);
}
pub fn new_map() -> MapState
{
let info = Default::default();
let ed = EditorState{};
let ma = MapState{ed, info};
ma
}
pub struct EditorState
{
}
pub struct MapState
{
ed: EditorState,
info: map::Minf,
}
// EOF

194
source/tycho/interfaces.rs Normal file
View File

@ -0,0 +1,194 @@
//! Implemented interfaces for Rozinante.
use cairo_sys::*;
use gdk_pixbuf_sys::*;
use gdk_sys::*;
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)
{
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);}
}
/// 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);
/// 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
{
edit: editor::MapEditor,
draw: *mut GtkWidget,
}
/// A runtime reference to the map editor.
pub type MapEditorRef = std::cell::RefCell<MapEditor>;
// EOF

View File

@ -1,127 +1,490 @@
mod hiddenprotocol; mod interfaces;
mod noroom;
use crate::{hiddenprotocol::*, noroom::*}; use crate::interfaces::*;
use gio::prelude::*; use gdk_pixbuf_sys::*;
use gtk::prelude::*; use gdk_sys::*;
use maraiah::durandal::err::*; use gio_sys::*;
use glib_sys::*;
use gobject_sys::*;
use gtk_sys::*;
use maraiah::{c_str, durandal::ffi};
use std::{cell::RefCell, marker::PhantomData, rc::Rc};
fn hide_on_delete(win: &gtk::Window, _: &gdk::Event) -> Inhibit 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)
{ {
win.hide(); // this ref will be cloned around a bit, but will ultimately be dropped at
Inhibit(true) // the end of this function.
let edit = Rc::from_raw(edit as *const MapEditorRef);
setup_css();
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());
} }
fn mk_draw_area(b: &gtk::Builder) /// Sets up the map view window's drawing area.
unsafe fn setup_draw_area(b: &Refc<GtkBuilder>, edit: Rc<MapEditorRef>)
{ {
let area: gtk::DrawingArea = get_obj(b, "draw-area"); /// All of the state necessary for the drawing area.
struct RenderState
let ax: gtk::Adjustment = get_obj(b, "adj-map-horz"); {
let ay: gtk::Adjustment = get_obj(b, "adj-map-vert"); im_nomap: Refc<'static, GdkPixbuf>,
ax: Refc<'static, GtkAdjustment>,
let im = CairoPixbuf(load_img("/net/greyserv/maraiah/tycho/tycho1")); ay: Refc<'static, GtkAdjustment>,
edit: Rc<MapEditorRef>,
area.connect_draw(move |area, cr| {
let w = f64::from(area.get_allocated_width());
let h = f64::from(area.get_allocated_height());
ax.set_lower(0.0);
ax.set_upper(w);
ay.set_lower(0.0);
ay.set_upper(h);
let d = CairoDrawArea::new(cr.clone(), w, h);
draw_map_none(&d, &im);
Inhibit(true)
});
}
fn run_app(app: &gtk::Application)
{
let b = &gtk::Builder::new_from_resource("/net/greyserv/maraiah/tycho/ui");
let app_ = app.clone();
let btn: gtk::MenuItem = get_obj(b, "btn-quit");
btn.connect_activate(move |_| app_.quit());
let btn: gtk::MenuItem = get_obj(b, "btn-about");
let win: gtk::AboutDialog = get_obj(b, "win-about");
btn.connect_activate(move |_| {win.run(); win.hide();});
let btn: gtk::MenuItem = get_obj(b, "btn-show-map-view");
let win: gtk::Window = get_obj(b, "win-map-view");
win.connect_delete_event(hide_on_delete);
btn.connect_activate(move |_| win.show_all());
let btn: gtk::MenuItem = get_obj(b, "btn-show-map-tools");
let win: gtk::Window = get_obj(b, "win-map-tools");
win.connect_delete_event(hide_on_delete);
btn.connect_activate(move |_| win.show_all());
let btn: gtk::MenuItem = get_obj(b, "btn-show-map-prop");
let win: gtk::Window = get_obj(b, "win-map-prop");
win.connect_delete_event(hide_on_delete);
btn.connect_activate(move |_| win.show_all());
mk_draw_area(b);
let win: gtk::AboutDialog = get_obj(b, "win-about");
win.set_authors(&env!("CARGO_PKG_AUTHORS").split(';').collect::<Vec<_>>());
win.set_version(env!("CARGO_PKG_VERSION"));
win.set_website(env!("CARGO_PKG_HOMEPAGE"));
win.set_logo(&load_img("/net/greyserv/maraiah/tycho/tycho2"));
let win: gtk::Window = get_obj(b, "win-main");
win.set_application(app);
win.show_all();
}
fn load_img(path: &'static str) -> gdk_pixbuf::Pixbuf
{
gdk_pixbuf::Pixbuf::new_from_resource(path).unwrap()
}
fn get_obj<T>(b: &gtk::Builder, name: &str) -> T
where T: glib::object::IsA<glib::object::Object>
{
b.get_object(name).unwrap()
}
fn main() -> ResultS<()>
{
// get jacked, punk. opaque data structures are for nerds.
const RESOURCE_DATA: &[u8] = include_bytes!("data/tycho.res");
let mut static_resource =
gio_sys::GStaticResource{data: RESOURCE_DATA.as_ptr(),
data_len: RESOURCE_DATA.len(),
resource: std::ptr::null_mut(),
next: std::ptr::null_mut(),
padding: std::ptr::null_mut()};
unsafe {
gio_sys::g_static_resource_init(&mut static_resource);
} }
let app = gtk::Application::new("net.greyserv.maraiah.tycho", /// Callback to finalize the drawing area.
gio::ApplicationFlags::empty())?; unsafe extern "C" fn c_done(_: *mut GtkWidget, rend: gpointer)
{
Box::from_raw(rend as *mut RenderState);
}
app.connect_activate(run_app); /// Callback to draw on the drawing area.
unsafe extern "C" fn c_draw(wid: *mut GtkWidget,
ctx: *mut cairo_sys::cairo_t,
rend: gpointer)
-> gboolean
{
let rend = &mut *(rend as *mut RenderState);
let ret = if app.run(&[]) == 0 { let w = f64::from(gtk_widget_get_allocated_width(wid));
Ok(()) 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.ay, 0.0);
gtk_adjustment_set_upper(*rend.ay, h);
let im = CrImage(*rend.im_nomap);
let mut dr = CrDrawArea::new(ctx, w, h);
rend.edit.borrow().draw(&mut dr, &im);
1
}
let wid = get_obj::<GtkDrawingArea>(b, c_str!("draw-area"));
edit.borrow_mut().set_draw(wid as _);
// get all of the necessary state and related objects
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 _);
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, 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: &Refc<GtkBuilder>)
{
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);
connect_show(btn, win);
}
/// Sets up the map tools window.
unsafe fn setup_win_map_tools(b: &Refc<GtkBuilder>)
{
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);
connect_show(btn, win);
}
/// Sets up the map properties window.
unsafe fn setup_win_map_prop(b: &Refc<GtkBuilder>)
{
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);
connect_show(btn, win);
}
/// Sets up the about dialogue.
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.
unsafe extern "C" fn c_show_act(_: *mut GtkWidget, dlg: gpointer)
{
gtk_dialog_run(dlg as _);
gtk_widget_hide(dlg as _);
}
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 = 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);
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: &Refc<GtkBuilder>, win: *mut GtkWindow)
{
/// Callback to explicitly finalize all windows on exit.
unsafe extern "C" fn c_done(_: *mut GtkWidget, exp_del: gpointer)
{
let exp_del = Box::from_raw(exp_del as *mut Vec<*mut GtkWindow>);
for win in *exp_del {
gtk_widget_destroy(win as _);
}
}
// we need to explicitly drop other windows on exit, which means we need to
// create a list of them to send to the callback
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;
loop {
let obj = lst.data as *mut GObject;
// 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);
}
}
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, DESTROY, c_done as _, exp_del);
}
/// Sets up the main menu window.
unsafe fn setup_win_main(b: &Refc<GtkBuilder>,
app: *mut GtkApplication,
edit: Rc<MapEditorRef>)
{
/// Callback to close the window when the "Quit" button is pressed.
unsafe extern "C" fn c_quit_act(_: *mut GtkWidget, win: gpointer)
{
gtk_window_close(win as _);
}
/// 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 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? \
Unsaved data may be lost.");
if !run_ok_cancel_dlg(titl, text) {
return;
}
}
edit.open_new();
edit.cause_update();
}
/// 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);
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 = get_obj::<GtkWindow>(b, c_str!("win-main"));
setup_explicit_drop(b, win);
gtk_window_set_application(win, app);
gtk_widget_show_all(win as _);
// set up buttons
let btn = get_obj::<GtkMenuItem>(b, c_str!("btn-quit"));
connect(btn, ACTIVATE, c_quit_act as _, win);
let btn = get_obj::<GtkMenuItem>(b, c_str!("btn-new"));
connect(btn, ACTIVATE, c_new_act as _, connect_ref(btn, edit.clone()));
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 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);
}
/// Runs a modal OK/Cancel dialogue.
unsafe fn run_ok_cancel_dlg(title: ffi::NT, text: ffi::NT) -> bool
{
let flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
let dlg = gtk_dialog_new_with_buttons(title,
ffi::null_mut(),
flags,
c_str!("_OK"),
GTK_RESPONSE_ACCEPT,
c_str!("_Cancel"),
GTK_RESPONSE_REJECT,
ffi::null_mut_void());
let area = gtk_dialog_get_content_area(dlg as _);
let labl = gtk_label_new(text);
gtk_container_add(area as _, labl);
gtk_widget_show_all(area as _);
let res = gtk_dialog_run(dlg as _);
gtk_widget_destroy(dlg);
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 { } else {
Err(err_msg("bad return")) None
}; };
unsafe { gtk_widget_destroy(dlg);
gio_sys::g_static_resource_fini(&mut static_resource);
}
ret ret
} }
/// Connects a handler that hides a toplevel widget when deleted.
unsafe fn connect_hide<T>(wid: *mut T)
{
/// Callback to hide the widget.
unsafe extern "C" fn c_hide_del(wid: *mut GtkWidget,
_: *mut GdkEvent,
_: gpointer)
{
gtk_widget_hide(wid);
}
connect(wid, DELETE_EVENT, c_hide_del as _, ffi::null_void());
}
/// Connects a handler that shows a widget when activated.
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)
{
gtk_widget_show_all(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: &Refc<GtkBuilder>, name: ffi::NT) -> *mut T
{
gtk_builder_get_object(**b, name) as _
}
/// Connects a signal handler.
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 as _, name, cb, d as _, None, 0);
}
/// Loads a `Pixbuf` from a resource.
unsafe fn load_img(path: ffi::NT) -> *mut GdkPixbuf
{
gdk_pixbuf_new_from_resource(path, ffi::null_mut())
}
/// Entry point.
fn main()
{
unsafe {
// get jacked, punk. opaque data structures are for nerds.
const RESOURCE_DATA: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/resources"));
// first we create the static resource header, which is really simple
let mut resource = GStaticResource{data: RESOURCE_DATA.as_ptr(),
data_len: RESOURCE_DATA.len(),
resource: ffi::null_mut(),
next: ffi::null_mut(),
padding: ffi::null_mut()};
// init it, now we can use it throughout the entire app without copying!
g_static_resource_init(&mut resource);
// create a container for the editor state
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 app = Refc::new(gtk_application_new(APP_ID, 0));
connect(*app, ACTIVATE, app_activate as _, eptr);
g_application_run(*app as _, 0, ffi::null_mut());
// ok, clean up all this crap now
drop(app);
assert_eq!(Rc::strong_count(&edit), 1);
drop(edit);
// deinit the "static" data, and everything will be done
g_static_resource_fini(&mut resource);
}
}
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>
{
fn new(p: *mut T) -> Self {Self{p, l: PhantomData}}
}
struct Refc<'a, T>
{
p: *mut T,
l: PhantomData<&'a *mut T>,
}
// EOF // EOF

View File

@ -1,83 +0,0 @@
use maraiah::{durandal::image::*, rozinante::draw::*};
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 CairoPixbuf
{
fn w(&self) -> Coord {self.0.get_width() as Coord}
fn h(&self) -> Coord {self.0.get_height() as Coord}
}
impl CairoDrawArea
{
pub const fn new(ctx: cairo::Context, w: f64, h: f64) -> Self
{
CairoDrawArea{ctx, w: w as Coord, h: h as Coord}
}
}
impl DrawArea for CairoDrawArea
{
type NativeImage = CairoPixbuf;
fn w(&self) -> Coord {self.w}
fn h(&self) -> Coord {self.h}
fn clear(&self, cr: impl Color)
{
use cairo::{FontSlant, FontWeight};
self.rect(Rect{x: 0, y: 0, w: self.w(), h: self.h()}, cr);
self.ctx
.select_font_face("Sans", FontSlant::Normal, FontWeight::Normal);
self.ctx.set_font_size(14.0);
}
fn rect(&self, rect: Rect, cr: impl Color)
{
let x1 = f64::from(rect.x);
let y1 = f64::from(rect.y);
let x2 = f64::from(rect.w) + x1;
let y2 = f64::from(rect.h) + y1;
let (r, g, b) = flt_color(cr);
self.ctx.set_source_rgb(r, g, b);
self.ctx.rectangle(x1, y1, x2, y2);
self.ctx.fill();
}
fn text(&self, pos: Point, text: &str, cr: impl Color)
{
let (r, g, b) = flt_color(cr);
self.ctx.set_source_rgb(r, g, b);
self.ctx.move_to(f64::from(pos.0), f64::from(pos.1));
self.ctx.show_text(text);
}
fn image(&self, pos: Point, im: &Self::NativeImage)
{
use gdk::prelude::*;
self.ctx
.set_source_pixbuf(&im.0, f64::from(pos.0), f64::from(pos.1));
self.ctx.paint();
}
}
pub struct CairoPixbuf(pub gdk_pixbuf::Pixbuf);
pub struct CairoDrawArea
{
ctx: cairo::Context,
w: Coord,
h: Coord,
}
// EOF