427 lines
12 KiB
Rust
427 lines
12 KiB
Rust
mod interfaces;
|
|
|
|
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};
|
|
|
|
/// Called when the application activates in order to set everything up.
|
|
unsafe extern "C" fn app_activate(app: *mut GtkApplication, edit: gpointer)
|
|
{
|
|
// 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 path = c_str!("/net/greyserv/maraiah/tycho/ui");
|
|
let b = Refc::new(gtk_builder_new_from_resource(path));
|
|
|
|
setup_draw_area(&b, edit.clone());
|
|
setup_win_map_view(&b);
|
|
setup_win_map_tools(&b);
|
|
setup_win_map_prop(&b);
|
|
setup_about_dlg(&b);
|
|
setup_win_main(&b, app, edit.clone());
|
|
}
|
|
|
|
/// Sets up the map view window's drawing area.
|
|
unsafe fn setup_draw_area(b: &Refc<GtkBuilder>, edit: Rc<MapEditorRef>)
|
|
{
|
|
/// 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<MapEditorRef>,
|
|
}
|
|
|
|
/// Callback to finalize the drawing area.
|
|
unsafe extern "C" fn c_done(_: *mut GtkWidget, rend: gpointer)
|
|
{
|
|
Box::from_raw(rend as *mut RenderState);
|
|
}
|
|
|
|
/// 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 = rend as *mut RenderState;
|
|
let rend = &mut *rend;
|
|
|
|
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: *mut GtkDrawingArea = 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::<GtkAdjustment>::new(get_obj(b, c_str!("adj-map-horz")));
|
|
let ay = Refc::<GtkAdjustment>::new(get_obj(b, c_str!("adj-map-vert")));
|
|
|
|
g_object_ref(*ax as _);
|
|
g_object_ref(*ay as _);
|
|
|
|
let im_nomap = load_img(c_str!("/net/greyserv/maraiah/tycho/tycho1.png"));
|
|
let im_nomap = Refc::new(im_nomap);
|
|
|
|
let rend = RenderState{im_nomap, ax, ay, edit};
|
|
let rend = Box::into_raw(Box::new(rend));
|
|
|
|
connect(wid as _, c_str!("destroy"), c_done as _, rend as _);
|
|
connect(wid as _, c_str!("draw"), c_draw as _, rend as _);
|
|
}
|
|
|
|
/// Sets up the map view window.
|
|
unsafe fn setup_win_map_view(b: &Refc<GtkBuilder>)
|
|
{
|
|
let win: *mut GtkWindow = get_obj(b, c_str!("win-map-view"));
|
|
let btn: *mut GtkMenuItem = get_obj(b, c_str!("btn-show-map-view"));
|
|
|
|
connect_hide(win as _);
|
|
connect_show(btn as _, win as _);
|
|
}
|
|
|
|
/// Sets up the map tools window.
|
|
unsafe fn setup_win_map_tools(b: &Refc<GtkBuilder>)
|
|
{
|
|
let win: *mut GtkWindow = get_obj(b, c_str!("win-map-tools"));
|
|
let btn: *mut GtkMenuItem = get_obj(b, c_str!("btn-show-map-tools"));
|
|
|
|
connect_hide(win as _);
|
|
connect_show(btn as _, win as _);
|
|
}
|
|
|
|
/// Sets up the map properties window.
|
|
unsafe fn setup_win_map_prop(b: &Refc<GtkBuilder>)
|
|
{
|
|
let win: *mut GtkWindow = get_obj(b, c_str!("win-map-prop"));
|
|
let btn: *mut GtkMenuItem = get_obj(b, c_str!("btn-show-map-prop"));
|
|
|
|
connect_hide(win as _);
|
|
connect_show(btn as _, win as _);
|
|
}
|
|
|
|
/// 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: *mut GtkAboutDialog = get_obj(b, c_str!("dlg-about"));
|
|
let btn: *mut GtkMenuItem = 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 = load_img(c_str!("/net/greyserv/maraiah/tycho/tycho2.png"));
|
|
let img = Refc::new(img);
|
|
|
|
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 as _);
|
|
connect(btn as _, c_str!("activate"), c_show_act as _, dlg as _);
|
|
}
|
|
|
|
/// 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 as _, c_str!("destroy"), c_done as _, exp_del as _);
|
|
}
|
|
|
|
/// 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 edit = &*edit;
|
|
let mut edit = edit.borrow_mut();
|
|
|
|
if edit.is_opened() {
|
|
let titl = c_str!("Confirm");
|
|
let text = c_str!("Are you sure you want to create a new project? \
|
|
Data may be lost.");
|
|
|
|
if !run_ok_cancel_dlg(titl, text) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
edit.open_new();
|
|
edit.cause_update();
|
|
}
|
|
|
|
/// Callback to finalize the editor state reference.
|
|
unsafe extern "C" fn c_new_done(_: *mut GtkWidget, edit: gpointer)
|
|
{
|
|
let edit = edit as *const MapEditorRef;
|
|
Rc::from_raw(edit);
|
|
}
|
|
|
|
// set up main window
|
|
let win: *mut GtkWindow = 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: *mut GtkMenuItem = get_obj(b, c_str!("btn-quit"));
|
|
|
|
connect(btn as _, c_str!("activate"), c_quit_act as _, win as _);
|
|
|
|
let btn: *mut GtkMenuItem = get_obj(b, c_str!("btn-new"));
|
|
|
|
let eptr = Rc::into_raw(edit.clone());
|
|
|
|
connect(btn as _, c_str!("activate"), c_new_act as _, eptr as _);
|
|
connect(btn as _, c_str!("destroy"), c_new_done as _, eptr as _);
|
|
}
|
|
|
|
/// Sets up the CSS styling providers.
|
|
unsafe fn setup_css()
|
|
{
|
|
let path = c_str!("/net/greyserv/maraiah/tycho/css");
|
|
let css = Refc::new(gtk_css_provider_new());
|
|
gtk_css_provider_load_from_resource(*css, path);
|
|
|
|
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::<gpointer>());
|
|
|
|
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);
|
|
|
|
match res {
|
|
GTK_RESPONSE_ACCEPT => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
/// Connects a handler that hides a toplevel widget when deleted.
|
|
unsafe fn connect_hide(wid: *mut GtkWidget)
|
|
{
|
|
/// Callback to hide the widget.
|
|
unsafe extern "C" fn c_hide_del(wid: *mut GtkWidget,
|
|
_: *mut GdkEvent,
|
|
_: gpointer)
|
|
{
|
|
gtk_widget_hide(wid);
|
|
}
|
|
|
|
connect(wid as _, c_str!("delete-event"), c_hide_del as _, ffi::null_mut());
|
|
}
|
|
|
|
/// Connects a handler that shows a widget when activated.
|
|
unsafe fn connect_show(btn: *mut GtkWidget, wid: *mut GtkWidget)
|
|
{
|
|
/// Callback to show the widget.
|
|
unsafe extern "C" fn c_show_act(_: *mut GtkWidget, wid: gpointer)
|
|
{
|
|
gtk_widget_show_all(wid as _);
|
|
}
|
|
|
|
connect(btn as _, c_str!("activate"), c_show_act as _, wid as _);
|
|
}
|
|
|
|
/// Gets an object from a `GtkBuilder`.
|
|
unsafe fn get_obj<T>(b: &Refc<GtkBuilder>, name: ffi::NT) -> *mut T
|
|
{
|
|
let obj = gtk_builder_get_object(**b, name);
|
|
obj as _
|
|
}
|
|
|
|
/// Connects a signal handler.
|
|
unsafe fn connect(obj: *mut GObject, name: ffi::NT, cb: gpointer, d: gpointer)
|
|
{
|
|
let cb = std::mem::transmute(cb);
|
|
g_signal_connect_data(obj, name, cb, d, 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 name = c_str!("net.greyserv.maraiah.tycho");
|
|
let app = Refc::new(gtk_application_new(name, 0));
|
|
|
|
connect(*app as _, c_str!("activate"), app_activate as _, eptr as _);
|
|
|
|
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
|