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, 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()); g_object_unref(b as _); } /// Sets up the map view window's drawing area. unsafe fn setup_draw_area(b: *mut GtkBuilder, edit: Rc>) { struct RenderState { im_nomap: *mut GdkPixbuf, ax: *mut GtkAdjustment, ay: *mut GtkAdjustment, edit: Rc>, } /// Callback to finalize the drawing area. 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 } /// 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 dr = CrDrawArea::new(ctx, w, h); match &*rend.edit { Some(edit) => edit.draw_some(&dr), None => 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, 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: *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!("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")); 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 explicit window finalization for the main window. unsafe fn setup_explicit_drop(b: *mut 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: *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 _); } /// 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; if let Some(_) = *edit { let titl = c_str!("Confirm"); let text = c_str!("Are you sure you want to create a new project? \ Data may be lost."); match run_ok_cancel_dlg(titl, text) { true => {} false => return, } } *edit = Some(MapEditorState::new()); } /// Callback to finalize the editor state reference. unsafe extern "C" fn c_new_done(_: *mut GtkWidget, edit: gpointer) { 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 = 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 _); } /// 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::()); 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(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_eq!(Rc::strong_count(&edit), 1); drop(edit); // deinit the "static" data, and everything will be done g_static_resource_fini(&mut resource); } } // EOF