diff --git a/.gitignore b/.gitignore index c445795..79ee3fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ /target /out -*.res **/*.rs.bk Cargo.lock perf.data* +*.bat diff --git a/LICENSE b/LICENSE index b60e7d7..e0d67b9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ Some of the data contained in tests/data and benches/data is 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 copyright and related or neighboring rights to this Document as described by diff --git a/source/durandal/mod.rs b/source/durandal.rs similarity index 88% rename from source/durandal/mod.rs rename to source/durandal.rs index 8a3b530..c7fc605 100644 --- a/source/durandal/mod.rs +++ b/source/durandal.rs @@ -1,5 +1,7 @@ //! Library for utilities. +#[macro_use] +pub mod ffi; #[macro_use] pub mod err; #[macro_use] @@ -13,6 +15,5 @@ pub mod file; pub mod fixed; pub mod image; pub mod sound; -pub mod text; // EOF diff --git a/source/durandal/bin.rs b/source/durandal/bin.rs index b2dc0aa..f12e567 100644 --- a/source/durandal/bin.rs +++ b/source/durandal/bin.rs @@ -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)] @@ -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 { #[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"); /// ``` -#[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]); diff --git a/source/durandal/cksum.rs b/source/durandal/cksum.rs index 08a359a..3a8ab38 100644 --- a/source/durandal/cksum.rs +++ b/source/durandal/cksum.rs @@ -58,6 +58,6 @@ pub fn crc32(b: &[u8], s: u32) -> u32 } const ISO_3309_POLYNOMIAL: u32 = 0xEDB8_8320; -const ADLER32_MODULO: u32 = 65521; +const ADLER32_MODULO: u32 = 0xFFF1; // EOF diff --git a/source/durandal/ffi.rs b/source/durandal/ffi.rs new file mode 100644 index 0000000..0bf316c --- /dev/null +++ b/source/durandal/ffi.rs @@ -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>(it: I) + -> ResultS + { + 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, + cv: Vec, +} + +/// A null-terminated byte string pointer. +pub type NT = *const c_char; + +// EOF diff --git a/source/leela/main.rs b/source/leela/main.rs index c4a5711..f41cd52 100644 --- a/source/leela/main.rs +++ b/source/leela/main.rs @@ -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<()> { @@ -133,13 +133,13 @@ fn main() -> ResultS<()> { use argparse::*; - let mut opt: Options = Default::default(); + let mut opt = Options::default(); { let mut ap = ArgumentParser::new(); 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); }; } diff --git a/source/marathon/mod.rs b/source/marathon.rs similarity index 94% rename from source/marathon/mod.rs rename to source/marathon.rs index dde7ee7..d01f3c6 100644 --- a/source/marathon/mod.rs +++ b/source/marathon.rs @@ -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; diff --git a/source/marathon/defl.rs b/source/marathon/defl.rs index 0693647..50904fe 100644 --- a/source/marathon/defl.rs +++ b/source/marathon/defl.rs @@ -389,7 +389,7 @@ impl HuffmanTable p += 1; // 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) { return Ok((i, self.syms[usize::from(index + code - first)])); diff --git a/source/marathon/map.rs b/source/marathon/map.rs index c02babe..997e99b 100644 --- a/source/marathon/map.rs +++ b/source/marathon/map.rs @@ -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. @@ -222,7 +222,7 @@ fn read_poly_inter(b: &[u8]) -> ResultS } 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. @@ -751,7 +751,7 @@ pub struct Note /// Static map information. #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))] -#[derive(Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Minf { pub texture_id: u16, diff --git a/source/marathon/pict.rs b/source/marathon/pict.rs index 4dc88e3..6092be9 100644 --- a/source/marathon/pict.rs +++ b/source/marathon/pict.rs @@ -47,8 +47,8 @@ fn read_pm_header<'a>(b: &'a [u8], } let rle = pack_t == PackType::Default || - pack_t == PackType::Rle16 && depth == Depth::Bits16 || - pack_t == PackType::Rle32 && depth == Depth::Bits32; + pack_t == PackType::Rle16 && depth == Depth::_16 || + pack_t == PackType::Rle32 && depth == Depth::_32; let pitch = usize::from(pt_fl & 0x3FFF); @@ -61,7 +61,7 @@ fn read_pm_ind(mut im: Image8, b: &[u8], hdr: Header) -> ResultS let clut = ok!(hdr.clut, "no CLUT in indexed mode")?; 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 for _ in 0..im.h() { for _ in 0..im.w() { @@ -79,7 +79,7 @@ fn read_pm_ind(mut im: Image8, b: &[u8], hdr: Header) -> ResultS for _ in 0..im.h() { let (d, pp) = read_rle::(&b[p..], hdr.pitch)?; - let d = if hdr.depth < Depth::Bits8 { + let d = if hdr.depth < Depth::_8 { expand_data(d, hdr.depth)? } else { 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)?; match hdr.depth { - Depth::Bits1 | - Depth::Bits2 | - Depth::Bits4 | - Depth::Bits8 => read_pm_ind(im, b, hdr), - Depth::Bits16 => read_pm_16(im, b, hdr), - Depth::Bits32 => read_pm_32(im, b, hdr), + Depth::_1 | + Depth::_2 | + Depth::_4 | + Depth::_8 => read_pm_ind(im, b, hdr), + Depth::_16 => read_pm_16(im, b, hdr), + Depth::_32 => read_pm_32(im, b, hdr), } } @@ -442,25 +442,25 @@ impl ReadRleData for u8 fn expand_data(b: Vec, depth: Depth) -> ResultS> { let mut o = Vec::with_capacity(match depth { - Depth::Bits4 => b.len() * 2, - Depth::Bits2 => b.len() * 4, - Depth::Bits1 => b.len() * 8, + Depth::_4 => b.len() * 2, + Depth::_2 => b.len() * 4, + Depth::_1 => b.len() * 8, _ => bail!("invalid bit depth"), }); for ch in b { match depth { - Depth::Bits4 => { + Depth::_4 => { for i in (0..=1).rev() { o.push(ch >> (i * 4) & 0xF_u8); } } - Depth::Bits2 => { + Depth::_2 => { for i in (0..=3).rev() { o.push(ch >> (i * 2) & 0x3_u8); } } - Depth::Bits1 => { + Depth::_1 => { for i in (0..=7).rev() { o.push(ch >> i & 0x1_u8); } @@ -484,12 +484,12 @@ struct Header c_enum! { enum Depth: u16 { - Bits1 = 1, - Bits2 = 2, - Bits4 = 4, - Bits8 = 8, - Bits16 = 16, - Bits32 = 32, + _1 = 1, + _2 = 2, + _4 = 4, + _8 = 8, + _16 = 16, + _32 = 32, } } diff --git a/source/marathon/ppm.rs b/source/marathon/ppm.rs index 48448f3..ca7458a 100644 --- a/source/marathon/ppm.rs +++ b/source/marathon/ppm.rs @@ -1,4 +1,4 @@ -//! Portable PixMap format images. +//! Portable Pixel Map format images. use crate::durandal::{err::*, fixed::FixedLong, image::*}; use std::io; @@ -40,7 +40,7 @@ pub fn read_ppm(inp: &[u8]) -> ResultS 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)?; Ok(nu) }; @@ -56,9 +56,9 @@ pub fn read_ppm(inp: &[u8]) -> ResultS let g = FixedLong::from_int(get_num()?.into()); let b = FixedLong::from_int(get_num()?.into()); - let r = (r / depth * 65535).integ() as u16; - let g = (g / depth * 65535).integ() as u16; - let b = (b / depth * 65535).integ() as u16; + let r = (r / depth * 0xFFFF).integ() as u16; + let g = (g / depth * 0xFFFF).integ() as u16; + let b = (b / depth * 0xFFFF).integ() as u16; im.cr.push(Color16::new(r, g, b)); } diff --git a/source/marathon/shp.rs b/source/marathon/shp.rs index d310497..93aa1ae 100644 --- a/source/marathon/shp.rs +++ b/source/marathon/shp.rs @@ -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`. diff --git a/source/durandal/text.rs b/source/marathon/text.rs similarity index 96% rename from source/durandal/text.rs rename to source/marathon/text.rs index 375b193..fc41163 100644 --- a/source/durandal/text.rs +++ b/source/marathon/text.rs @@ -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 /// # 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"), "I’ve"); @@ -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"), "I’ve awaken"); /// assert_eq!(mac_roman_cstr(b"I\xd5ve awaken\0"), "I’ve awaken"); diff --git a/source/marathon/trm.rs b/source/marathon/trm.rs index 5e7edc4..3181dec 100644 --- a/source/marathon/trm.rs +++ b/source/marathon/trm.rs @@ -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`. diff --git a/source/marathon/wad.rs b/source/marathon/wad.rs index b5569d2..c45f436 100644 --- a/source/marathon/wad.rs +++ b/source/marathon/wad.rs @@ -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. diff --git a/source/rozinante/mod.rs b/source/rozinante.rs similarity index 80% rename from source/rozinante/mod.rs rename to source/rozinante.rs index 96e17cb..260c5eb 100644 --- a/source/rozinante/mod.rs +++ b/source/rozinante.rs @@ -2,5 +2,6 @@ pub mod color; pub mod draw; +pub mod editor; // EOF diff --git a/source/rozinante/color.rs b/source/rozinante/color.rs index 939f7d8..790c9ee 100644 --- a/source/rozinante/color.rs +++ b/source/rozinante/color.rs @@ -2,7 +2,7 @@ use crate::durandal::image::Color16; -pub const CR_RED: Color16 = Color16::new(0xFFFF, 0, 0); -pub const CR_DARK_RED: Color16 = Color16::new(0x4700, 0, 0); +pub const RED: Color16 = Color16::new(0xFFFF, 0, 0); +pub const DARK_RED: Color16 = Color16::new(0x4700, 0, 0); // EOF diff --git a/source/rozinante/draw.rs b/source/rozinante/draw.rs index 0ff3ab6..54292a5 100644 --- a/source/rozinante/draw.rs +++ b/source/rozinante/draw.rs @@ -24,17 +24,23 @@ pub trait DrawArea /// The height of the entire area. fn h(&self) -> Coord; - /// Fills the entire screen with `cr`. - fn clear(&self, cr: impl Color); + /// Fills the entire screen with `cr`. Will also default all settings. + 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`. - 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`. - 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. - 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. diff --git a/source/rozinante/editor.rs b/source/rozinante/editor.rs new file mode 100644 index 0000000..54e6162 --- /dev/null +++ b/source/rozinante/editor.rs @@ -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(&self, d: &mut D, im: &I) + where D: DrawArea, + I: CacheImage + { + let dw = d.w(); + let dh = d.h(); + let iw = im.w(); + let ih = im.h(); + + match &self.map { + None => { + let tx_top = "Map Required To Proceed"; + let tx_bot = "CAS.qterm//CyberAcme Systems Inc."; + + 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, +} + +// EOF diff --git a/source/rozinante/editor/block.rs b/source/rozinante/editor/block.rs new file mode 100644 index 0000000..54b3532 --- /dev/null +++ b/source/rozinante/editor/block.rs @@ -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 diff --git a/source/rozinante/editor/state.rs b/source/rozinante/editor/state.rs new file mode 100644 index 0000000..eaa6c69 --- /dev/null +++ b/source/rozinante/editor/state.rs @@ -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, + tools: (Tool, Tool), +} + +/// A tool in the map editor. +#[derive(Clone, Debug)] +pub(super) enum Tool +{ + Points, + Lines, + Polygons, +} + +// EOF diff --git a/source/tycho/Cargo.toml b/source/tycho/Cargo.toml index 3903586..5359d4b 100644 --- a/source/tycho/Cargo.toml +++ b/source/tycho/Cargo.toml @@ -8,23 +8,17 @@ edition = "2018" build = "build.rs" [dependencies] -maraiah = {path = "../.."} - -# note: these have to be updated all at once, check the gtk crate for versions -atk = "0.6" -cairo-rs = "0.6" -cairo-sys-rs = "0.8" -gdk = "0.10" -gdk-pixbuf = "0.6" -gio = "0.6" -gio-sys = "0.8" -glib = "0.7" -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" +atk-sys = "0.8" +cairo-sys-rs = "0.8" +gdk-pixbuf-sys = "0.8" +gdk-sys = "0.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]] name = "tycho" diff --git a/source/tycho/build.rs b/source/tycho/build.rs index e0c9e2f..58aab15 100644 --- a/source/tycho/build.rs +++ b/source/tycho/build.rs @@ -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") - .arg("--target=data/tycho.res") - .status() - .unwrap(); +fn traverse_dir(path: &Path) -> io::Result<()> +{ + for ent in fs::read_dir(path)? { + 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 diff --git a/source/tycho/data/color/lines.png b/source/tycho/data/color/lines.png new file mode 100644 index 0000000..23fca96 Binary files /dev/null and b/source/tycho/data/color/lines.png differ diff --git a/source/tycho/data/color/map.png b/source/tycho/data/color/map.png new file mode 100644 index 0000000..56ccc3a Binary files /dev/null and b/source/tycho/data/color/map.png differ diff --git a/source/tycho/data/color/points.png b/source/tycho/data/color/points.png new file mode 100644 index 0000000..bab249f Binary files /dev/null and b/source/tycho/data/color/points.png differ diff --git a/source/tycho/data/color/polygons.png b/source/tycho/data/color/polygons.png new file mode 100644 index 0000000..ce49766 Binary files /dev/null and b/source/tycho/data/color/polygons.png differ diff --git a/source/tycho/data/hc/lines.png b/source/tycho/data/hc/lines.png new file mode 100644 index 0000000..769a58c Binary files /dev/null and b/source/tycho/data/hc/lines.png differ diff --git a/source/tycho/data/hc/map.png b/source/tycho/data/hc/map.png new file mode 100644 index 0000000..54ae882 Binary files /dev/null and b/source/tycho/data/hc/map.png differ diff --git a/source/tycho/data/hc/points.png b/source/tycho/data/hc/points.png new file mode 100644 index 0000000..ce116a0 Binary files /dev/null and b/source/tycho/data/hc/points.png differ diff --git a/source/tycho/data/hc/polygons.png b/source/tycho/data/hc/polygons.png new file mode 100644 index 0000000..62f1d27 Binary files /dev/null and b/source/tycho/data/hc/polygons.png differ diff --git a/source/tycho/data/icons.psd b/source/tycho/data/icons.psd deleted file mode 100644 index cb007c9..0000000 Binary files a/source/tycho/data/icons.psd and /dev/null differ diff --git a/source/tycho/data/lines.png b/source/tycho/data/lines.png deleted file mode 100644 index a611345..0000000 Binary files a/source/tycho/data/lines.png and /dev/null differ diff --git a/source/tycho/data/misc/tycho1.png b/source/tycho/data/misc/tycho1.png new file mode 100644 index 0000000..dba3f61 Binary files /dev/null and b/source/tycho/data/misc/tycho1.png differ diff --git a/source/tycho/data/misc/tycho2.png b/source/tycho/data/misc/tycho2.png new file mode 100644 index 0000000..850eb69 Binary files /dev/null and b/source/tycho/data/misc/tycho2.png differ diff --git a/source/tycho/data/points.png b/source/tycho/data/points.png deleted file mode 100644 index 43d8652..0000000 Binary files a/source/tycho/data/points.png and /dev/null differ diff --git a/source/tycho/data/polys.png b/source/tycho/data/polys.png deleted file mode 100644 index 285871e..0000000 Binary files a/source/tycho/data/polys.png and /dev/null differ diff --git a/source/tycho/data/resources.xml b/source/tycho/data/resources.xml new file mode 100644 index 0000000..1e4c785 --- /dev/null +++ b/source/tycho/data/resources.xml @@ -0,0 +1,19 @@ + + + + + data/misc/tycho1.png + data/misc/tycho2.png + data/styles.css + data/ui.xml + + + + data/color/polygons.png + data/color/lines.png + data/color/points.png + + + data/color/map.png + + diff --git a/source/tycho/data/sources/icons.psd b/source/tycho/data/sources/icons.psd new file mode 100644 index 0000000..65188fc Binary files /dev/null and b/source/tycho/data/sources/icons.psd differ diff --git a/source/tycho/data/sources/tycho1.psd b/source/tycho/data/sources/tycho1.psd new file mode 100644 index 0000000..2f48325 Binary files /dev/null and b/source/tycho/data/sources/tycho1.psd differ diff --git a/source/tycho/data/sources/tycho2.psd b/source/tycho/data/sources/tycho2.psd new file mode 100644 index 0000000..cf7f68d Binary files /dev/null and b/source/tycho/data/sources/tycho2.psd differ diff --git a/source/tycho/data/styles.css b/source/tycho/data/styles.css new file mode 100644 index 0000000..6a974a7 --- /dev/null +++ b/source/tycho/data/styles.css @@ -0,0 +1 @@ +/* EOF */ diff --git a/source/tycho/data/tycho1.png b/source/tycho/data/tycho1.png deleted file mode 100644 index 19db8cf..0000000 Binary files a/source/tycho/data/tycho1.png and /dev/null differ diff --git a/source/tycho/data/tycho2.png b/source/tycho/data/tycho2.png deleted file mode 100644 index fb6f43e..0000000 Binary files a/source/tycho/data/tycho2.png and /dev/null differ diff --git a/source/tycho/data/tycho_res.xml b/source/tycho/data/tycho_res.xml deleted file mode 100644 index b0892a8..0000000 --- a/source/tycho/data/tycho_res.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - data/tycho1.png - data/tycho2.png - data/polys.png - data/lines.png - data/points.png - data/tycho.xml - - diff --git a/source/tycho/data/tycho.xml b/source/tycho/data/ui.xml similarity index 51% rename from source/tycho/data/tycho.xml rename to source/tycho/data/ui.xml index 0a0a75c..70b0c46 100644 --- a/source/tycho/data/tycho.xml +++ b/source/tycho/data/ui.xml @@ -231,7 +231,7 @@ Author: Alison Sanderson - False + True True 0 @@ -249,119 +249,151 @@ Author: Alison Sanderson False 12 - + True False - - Co-op + True - True - False - The map can be played in multi-player co-operative. - True + False + vertical + + + Solo + True + True + False + The map can be played in single-player. + True + + + False + True + 0 + + + + + Co-op + True + True + False + The map can be played in multi-player co-operative. + True + + + False + True + 1 + + + + + Carnage + True + True + False + The map can be played in multi-player Carnage. + True + + + False + True + 2 + + + + + KTMWTB + True + True + False + The map can be played in multi-player Kill The Man With The Ball + True + + + False + True + 3 + + - 0 - 1 + True + True + 0 - - Solo + True - True - False - The map can be played in single-player. - True + False + vertical + + + King Of The Hill + True + True + False + The map can be played in multi-player King of the Hill. + True + + + False + True + 0 + + + + + Defense + True + True + False + The map can be played in multi-player Defense. + True + + + False + True + 1 + + + + + Rugby + True + True + False + The map can be played in multi-player Rugby. + True + + + False + True + 2 + + + + + Capture The Flag + True + True + False + The map can be played in multi-player Capture The Flag. + True + + + False + True + 3 + + - 0 - 0 - - - - - Carnage - True - True - False - The map can be played in multi-player Carnage. - True - - - 0 - 2 - - - - - KTMWTB - True - True - False - The map can be played in multi-player Kill The Man With The Ball - True - - - 0 - 3 - - - - - King Of The Hill - True - True - False - The map can be played in multi-player King of the Hill. - True - - - 1 - 0 - - - - - Defense - True - True - False - The map can be played in multi-player Defense. - True - - - 1 - 1 - - - - - Rugby - True - True - False - The map can be played in multi-player Rugby. - True - - - 1 - 2 - - - - - Capture The Flag - True - True - False - The map can be played in multi-player Capture The Flag. - True - - - 1 - 3 + True + True + 1 @@ -402,92 +434,153 @@ Author: Alison Sanderson 0 in - + True False - 12 + vertical - + True False - vertical - True - - - Extermination - True - True - False - Player must kill all monsters on the map, with an error threshold of 8 aliens maximum. - True - - - False - True - 0 - - - - - Exploration - True - True - False - Player must explore all marked polygons. - True - - - False - True - 1 - - - - - Retrieval - True - True - False - Player must grab all items marked items. - True - - - False - True - 2 - - - - - Repair - True - True - False - Player must flip all marked switches. - True - - - False - True - 3 - - - - - Rescue - True - True - False - Player must keep 50% or more civilians alive. - True - - - False - True - 4 - - + Vanilla + + False + True + 0 + + + + + Extermination + True + True + False + Player must kill all monsters on the map, with an error threshold of 8 aliens maximum. + True + + + False + True + 1 + + + + + Exploration + True + True + False + Player must explore all marked polygons. + True + + + False + True + 2 + + + + + Retrieval + True + True + False + Player must grab all items marked items. + True + + + False + True + 3 + + + + + Repair + True + True + False + Player must flip all marked switches. + True + + + False + True + 4 + + + + + Rescue + True + True + False + Player must keep 50% or more civilians alive. + True + + + False + True + 5 + + + + + True + False + Aleph One + + + False + True + 6 + + + + + M1 Exploration + True + True + False + The same as Exploration, but you only need to look at each marked polygon, not actually walk in them. + True + + + False + True + 7 + + + + + M1 Rescue + True + True + False + The same as Rescue, but uses the Marathon 1 class numbers. + True + + + False + True + 8 + + + + + M1 Repair + True + True + False + The same as Repair, except it only requires that the last switch (by side index) be switched to succeed. + True + + + False + True + 9 + @@ -513,76 +606,198 @@ Author: Alison Sanderson 0 in - + True False - 12 + vertical - + True False - vertical - - - Vacuum - True - True - False - Most weapons will not work, and the oxygen bar will deplete. - True - - - False - True - 0 - - - - - Magnetic - True - True - False - The motion sensor will behave erratically. - True - - - False - True - 1 - - - - - Rebellion - True - True - False - Items and health will be stripped, and S'pht enemies will become friendly. - True - - - False - True - 2 - - - - - Low Gravity - True - True - False - Gravity will be halved. - True - - - False - True - 3 - - + Vanilla + + False + True + 0 + + + + + Vacuum + True + True + False + Most weapons will not work, and the oxygen bar will deplete. + True + + + False + True + 1 + + + + + Magnetic + True + True + False + The motion sensor will behave erratically. + True + + + False + True + 2 + + + + + Rebellion + True + True + False + Items and health will be stripped, and S'pht enemies will become friendly. + True + + + False + True + 3 + + + + + Low Gravity + True + True + False + Gravity will be halved. + True + + + False + True + 4 + + + + + True + False + Aleph One + + + False + True + 5 + + + + + Marathon 1 Glue + True + True + False + Glue handles like Marathon 1. + True + + + False + True + 6 + + + + + Lava Floor + True + True + False + The floor damages you. + True + + + False + True + 7 + + + + + Rebellion (No strip) + True + True + False + The same as Rebellion, but your items and health aren't stripped. + True + + + False + True + 8 + + + + + Music + True + True + False + The map has Marathon 1-style music. + True + + + False + True + 9 + + + + + Terminals Stop Time + True + True + False + Terminals will pause the game in Solo. + True + + + False + True + 10 + + + + + M1 Monster Limits + True + True + False + Sets the monster activation limits to Marathon 1's. + True + + + False + True + 11 + + + + + M1 Weapon Differences + True + True + False + Doubles weapon pickups on Total Carnage and makes grenades low-gravity. + True + + + False + True + 12 + @@ -602,7 +817,7 @@ Author: Alison Sanderson - False + True True 2 @@ -617,19 +832,61 @@ Author: Alison Sanderson True False - 5 - 5 - 5 + 8 True False - vertical - 5 + True True False + This field is unused and must be either 0 or 1. It used to be used to give different physics to the map editor and low gravity before it was made into an environment flag. + 0 + none + + + True + False + 0 + 0 + 0 + 0 + 12 + + + True + True + 0 + True + adj-phys-id + True + True + True + + + + + + + True + False + Physics ID + + + + + False + False + 0 + + + + + True + False + This field overrides the Landscape field, and is used for Marathon 1 maps which have music. 0 none @@ -660,245 +917,13 @@ Author: Alison Sanderson True False - Physics ID + Song ID False - True - 0 - - - - - True - False - 10 - - - True - False - These flags were added by Aleph One but are hidden and probably not meant to be used. - 0 - in - - - True - False - 12 - - - True - False - vertical - - - M1 Exploration - True - True - False - The same as Exploration, but you only need to look at each marked polygon, not actually walk in them. - True - - - False - True - 0 - - - - - M1 Rescue - True - True - False - The same as Rescue, but uses the Marathon 1 class numbers. - True - - - False - True - 1 - - - - - M1 Repair - True - True - False - The same as Repair, except it only requires that the last switch (by side index) be switched to succeed. - True - - - False - True - 2 - - - - - - - - - True - False - Hidden Mission Flags - - - - - True - True - 0 - - - - - True - False - These flags were added by Aleph One but are hidden and probably not meant to be used. - 0 - in - - - True - False - 12 - - - True - False - vertical - - - Marathon 1 Glue - True - True - False - Glue handles like Marathon 1. - True - - - False - True - 0 - - - - - Lava Floor - True - True - False - The floor damages you. - True - - - False - True - 1 - - - - - Rebellion (No strip) - True - True - False - The same as Rebellion, but your items and health aren't stripped. - True - - - False - True - 2 - - - - - Music - True - True - False - The map has Marathon 1-style music. - True - - - False - True - 3 - - - - - Terminals Stop Time - True - True - False - Terminals will pause the game in Solo. - True - - - False - True - 4 - - - - - M1 Monster Limits - True - True - False - Sets the monster activation limits to Marathon 1's. - True - - - False - True - 5 - - - - - M1 Weapon Differences - True - True - False - Doubles weapon pickups on Total Carnage and makes grenades low-gravity. - True - - - False - True - 6 - - - - - - - - - True - False - Hidden Environment Flags - - - - - True - True - 1 - - - - - False - True + False 1 @@ -915,7 +940,7 @@ Author: Alison Sanderson - False + True True 3 @@ -923,20 +948,191 @@ Author: Alison Sanderson + + False + True + dialog + Tycho Map Editor + By Alison Sanderson. Public domain. + greetigs i am tico the of superior ai to durdumbal go shoot my soldiers because its funny or ill put you in space + Home Page + To the extent possible under law, I, Alison Sanderson, have waived all +copyright and related or neighboring rights to this Document as described by +the Creative Commons Zero license as follows: + +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. + + image-loading + custom + + + + + + False + vertical + 2 + + + False + end + + + False + False + 0 + + + + + + + + + + About Screen + The about screen for Tycho. + dialog + + + True False - /net/greyserv/maraiah/tycho/lines + 24 + tycho-lines True False - /net/greyserv/maraiah/tycho/points + 24 + tycho-points True False - /net/greyserv/maraiah/tycho/polys + 24 + tycho-polys False @@ -960,7 +1156,7 @@ Author: Alison Sanderson True False - Points + _Points True img-points @@ -979,7 +1175,7 @@ Author: Alison Sanderson True False - Lines + _Lines True img-lines @@ -999,7 +1195,7 @@ Author: Alison Sanderson True False - Polygons + P_olygons True img-polys @@ -1037,48 +1233,6 @@ Author: Alison Sanderson - - False - True - dialog - Tycho Map Editor - Copyright © 2018-2019 Alison Sanderson - greetigs i am tico the of superior ai to durdumbal go shoot my soldiers because its funny or ill put you in space - Home Page - image-loading - mit-x11 - - - - - - False - vertical - 2 - - - False - end - - - False - False - 0 - - - - - - - - - - About Screen - The about screen for Tycho. - dialog - - - False Tycho diff --git a/source/tycho/hiddenprotocol.rs b/source/tycho/hiddenprotocol.rs deleted file mode 100644 index 85b9bf7..0000000 --- a/source/tycho/hiddenprotocol.rs +++ /dev/null @@ -1,40 +0,0 @@ -use maraiah::{durandal::image::*, - marathon::*, - rozinante::{color::*, draw::*}}; - -pub fn draw_map_none(d: &D, im: &I) - where D: DrawArea, - 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 diff --git a/source/tycho/interfaces.rs b/source/tycho/interfaces.rs new file mode 100644 index 0000000..a245b3f --- /dev/null +++ b/source/tycho/interfaces.rs @@ -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; + +// EOF diff --git a/source/tycho/main.rs b/source/tycho/main.rs index efaea64..b14d499 100644 --- a/source/tycho/main.rs +++ b/source/tycho/main.rs @@ -1,127 +1,490 @@ -mod hiddenprotocol; -mod noroom; +mod interfaces; -use crate::{hiddenprotocol::*, noroom::*}; -use gio::prelude::*; -use gtk::prelude::*; -use maraiah::durandal::err::*; +use crate::interfaces::*; +use gdk_pixbuf_sys::*; +use gdk_sys::*; +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: >k::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(); - Inhibit(true) + // this ref will be cloned around a bit, but will ultimately be dropped at + // 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: >k::Builder) +/// Sets up the map view window's drawing area. +unsafe fn setup_draw_area(b: &Refc, edit: Rc) { - let area: gtk::DrawingArea = get_obj(b, "draw-area"); - - let ax: gtk::Adjustment = get_obj(b, "adj-map-horz"); - let ay: gtk::Adjustment = get_obj(b, "adj-map-vert"); - - let im = CairoPixbuf(load_img("/net/greyserv/maraiah/tycho/tycho1")); - - 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: >k::Application) -{ - let b = >k::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::>()); - 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(b: >k::Builder, name: &str) -> T - where T: glib::object::IsA -{ - 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); + /// All of the state necessary for the drawing area. + struct RenderState + { + im_nomap: Refc<'static, GdkPixbuf>, + ax: Refc<'static, GtkAdjustment>, + ay: Refc<'static, GtkAdjustment>, + edit: Rc, } - let app = gtk::Application::new("net.greyserv.maraiah.tycho", - gio::ApplicationFlags::empty())?; + /// Callback to finalize the drawing area. + 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 { - Ok(()) + 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.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::(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::(b, c_str!("adj-map-horz"))); + let ay = Refc::new(get_obj::(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) +{ + let win = get_obj::(b, c_str!("win-map-view")); + let btn = get_obj::(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) +{ + let win = get_obj::(b, c_str!("win-map-tools")); + let btn = get_obj::(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) +{ + let win = get_obj::(b, c_str!("win-map-prop")); + let btn = get_obj::(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) +{ + /// 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::(b, c_str!("dlg-about")); + let btn = get_obj::(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, 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, + app: *mut GtkApplication, + edit: Rc) +{ + /// 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::(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::(b, c_str!("btn-quit")); + connect(btn, ACTIVATE, c_quit_act as _, win); + + let btn = get_obj::(b, c_str!("btn-new")); + connect(btn, ACTIVATE, c_new_act as _, connect_ref(btn, edit.clone())); + + let btn = get_obj::(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 +{ + 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 { - Err(err_msg("bad return")) + None }; - unsafe { - gio_sys::g_static_resource_fini(&mut static_resource); - } + gtk_widget_destroy(dlg); ret } +/// Connects a handler that hides a toplevel widget when deleted. +unsafe fn connect_hide(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(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(obj: *mut T, rc: Rc) -> *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(b: &Refc, name: ffi::NT) -> *mut T +{ + gtk_builder_get_object(**b, name) as _ +} + +/// Connects a signal handler. +unsafe fn connect(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 std::ops::Deref for Refc<'_, T> +{ + type Target = *mut T; + + fn deref(&self) -> &Self::Target {&self.p} +} + +impl Drop for Refc<'_, T> +{ + fn drop(&mut self) + { + unsafe { + g_object_unref(self.p as _); + } + } +} + +impl Refc<'_, T> +{ + fn new(p: *mut T) -> Self {Self{p, l: PhantomData}} +} + +struct Refc<'a, T> +{ + p: *mut T, + l: PhantomData<&'a *mut T>, +} + // EOF diff --git a/source/tycho/noroom.rs b/source/tycho/noroom.rs deleted file mode 100644 index a2390d5..0000000 --- a/source/tycho/noroom.rs +++ /dev/null @@ -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