mod editor; mod interfaces; use crate::{editor::MapEditorState, 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::rc::Rc; /// Called whne 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 *mut Option); setup_css(); let path = c_str!("/net/greyserv/maraiah/tycho/ui"); let b = gtk_builder_new_from_resource(path); setup_draw_area(b); setup_win_map_view(b); setup_win_map_tools(b); setup_win_map_prop(b); setup_about_dlg(b); setup_win_main(b, app, edit.clone()); g_object_unref(b as _); } /// Sets up the map view window's drawing area. unsafe fn setup_draw_area(b: *mut GtkBuilder) { struct RenderState { im_nomap: *mut GdkPixbuf, ax: *mut GtkAdjustment, ay: *mut GtkAdjustment, } unsafe extern "C" fn c_done(_: *mut GtkWidget, rend: gpointer) { let rend = Box::from_raw(rend as *mut RenderState); g_object_unref(rend.im_nomap as _); g_object_unref(rend.ax as _); g_object_unref(rend.ay as _); // data is dropped and freed here } 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 dr = CrDrawArea::new(ctx, w, h); MapEditorState::draw_none(&dr, &im); 1 } let wid: *mut GtkDrawingArea = get_obj(b, c_str!("draw-area")); let ax: *mut GtkAdjustment = get_obj(b, c_str!("adj-map-horz")); let ay: *mut GtkAdjustment = 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 rend = RenderState{im_nomap, ax, ay}; 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: *mut 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: *mut 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: *mut 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: *mut 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!("win-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")); 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 _); g_object_unref(img as _); } /// Sets up the main menu window. unsafe fn setup_win_main(b: *mut GtkBuilder, 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 _); } let win: *mut GtkWindow = get_obj(b, c_str!("win-main")); let btn: *mut GtkMenuItem = get_obj(b, c_str!("btn-quit")); gtk_window_set_application(win, app); gtk_widget_show_all(win as _); connect(btn as _, c_str!("activate"), c_quit_act as _, win as _); /// Callback to create a new editor state when the "New" button is pressed. unsafe extern "C" fn c_new_act(_: *mut GtkWidget, edit: gpointer) { let edit = edit as *mut Option; let edit = &mut *edit; *edit = Some(MapEditorState::new()); } /// Callback to destroy the editor state reference. unsafe extern "C" fn c_new_done(_: *mut GtkWidget, edit: gpointer) { Rc::from_raw(edit); } let edit = Rc::into_raw(edit); let btn: *mut GtkMenuItem = get_obj(b, c_str!("btn-new")); connect(btn as _, c_str!("activate"), c_new_act as _, edit as _); connect(btn as _, c_str!("destroy"), c_new_done as _, edit as _); } /// Sets up the CSS styling providers. unsafe fn setup_css() { let path = c_str!("/net/greyserv/maraiah/tycho/css"); let css = gtk_css_provider_new(); gtk_css_provider_load_from_resource(css, path); let scr = gdk_screen_get_default(); let pri = GTK_STYLE_PROVIDER_PRIORITY_APPLICATION as u32; gtk_style_context_add_provider_for_screen(scr, css as _, pri); g_object_unref(css as _); } /// 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(b: *mut 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 = Rc::new(None::); let eptr = Rc::into_raw(edit.clone()); // create and run the app let name = c_str!("net.greyserv.maraiah.tycho"); let app = gtk_application_new(name, 0); 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 g_object_unref(app as _); assert!(Rc::strong_count(&edit) == 1); drop(edit); // deinit the "static" data, and everything will be done g_static_resource_fini(&mut resource); } } // EOF