mod interfaces; use self::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, rc::Rc}; const APP_ID: ffi::NT = c_str!("net.greyserv.maraiah.tycho"); const B_ADJ_M_HORZ: ffi::NT = c_str!("adj-map-horz"); const B_ADJ_M_VERT: ffi::NT = c_str!("adj-map-vert"); const B_BTN_ABOUT: ffi::NT = c_str!("btn-about"); const B_BTN_M_PROP: ffi::NT = c_str!("btn-show-map-prop"); const B_BTN_M_TOOL: ffi::NT = c_str!("btn-show-map-tools"); const B_BTN_M_VIEW: ffi::NT = c_str!("btn-show-map-view"); const B_BTN_NEW: ffi::NT = c_str!("btn-new"); const B_BTN_OPEN: ffi::NT = c_str!("btn-open"); const B_BTN_QUIT: ffi::NT = c_str!("btn-quit"); const B_CON_F_ENT: ffi::NT = c_str!("con-f-ent"); const B_CON_F_ENV: ffi::NT = c_str!("con-f-env"); const B_CON_F_MSN: ffi::NT = c_str!("con-f-msn"); const B_DLG_ABOUT: ffi::NT = c_str!("dlg-about"); const B_DRAW_AREA: ffi::NT = c_str!("draw-area"); const B_WIN_MAIN: ffi::NT = c_str!("win-main"); const B_WIN_M_PROP: ffi::NT = c_str!("win-map-prop"); const B_WIN_M_TOOL: ffi::NT = c_str!("win-map-tools"); const B_WIN_M_VIEW: ffi::NT = c_str!("win-map-view"); const E_ACTIVATE: ffi::NT = c_str!("activate"); const E_DELETE: ffi::NT = c_str!("delete-event"); const E_DESTROY: ffi::NT = c_str!("destroy"); const E_DRAW: ffi::NT = c_str!("draw"); const E_SHUTDOWN: ffi::NT = c_str!("shutdown"); const E_TOGGLE: ffi::NT = c_str!("toggled"); 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, _: gpointer) { // Callback to finalize the reference. unsafe extern "C" fn c_done(_: *mut GApplication, ptr: gpointer) { let edit = Rc::from_raw(ptr as *const MapEditorRef); assert_eq!(Rc::strong_count(&edit), 1); } let b = Refc::new(gtk_builder_new_from_resource(PATH_BUILDER)); let edit = MapEditor{edit: Default::default(), draw: Refc::own(get_obj(&b, B_DRAW_AREA)), fent: get_flag_fields(&b, B_CON_F_ENT), fenv: get_flag_fields(&b, B_CON_F_ENV), fmsn: get_flag_fields(&b, B_CON_F_MSN)}; let edit = RefCell::new(edit); let edit = Rc::new(edit); setup_css(); setup_toggles(edit.clone()); 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()); connect(app, E_SHUTDOWN, c_done as _, Rc::into_raw(edit)); } // Gets all of the toggle buttons from a container. unsafe fn get_flag_fields(b: &Refc, name: ffi::NT) -> Vec { let mut flags = Vec::new(); let head = get_obj(b, name); let head = ListD::new(gtk_container_get_children(head)); let gtyp = gtk_toggle_button_get_type(); get_typed_from(head, gtyp, |obj| { flags.push(PropFlag{w: Refc::own(obj as _), h: 0}); }); flags } unsafe fn setup_toggles(edit: Rc) { let mut ed = edit.borrow_mut(); connect_toggle(edit.clone(), ed.fent.iter_mut()); connect_toggle(edit.clone(), ed.fenv.iter_mut()); connect_toggle(edit.clone(), ed.fmsn.iter_mut()); } unsafe fn connect_toggle<'a, I>(edit: Rc, it: I) where I: Iterator { unsafe extern "C" fn c_toggled(_: *mut GtkToggleButton, edit: gpointer) { let edit = &*(edit as *const MapEditorRef); edit.borrow_mut().cause_update_props(); } for flg in it { let erf = connect_ref(*flg.w, edit.clone()); flg.h = connect(*flg.w, E_TOGGLE, c_toggled as _, erf); } } // Sets up the map view window's drawing area. unsafe fn setup_draw_area(b: &Refc, edit: Rc) { // 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, } // 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 = &mut *(rend as *mut RenderState); 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, B_DRAW_AREA); // get all of the necessary state and related objects let ax = Refc::own(get_obj(b, B_ADJ_M_HORZ)); let ay = Refc::own(get_obj(b, B_ADJ_M_VERT)); 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, E_DESTROY, c_done as _, rend); connect(wid, E_DRAW, c_draw as _, rend); } // Sets up the map view window. unsafe fn setup_win_map_view(b: &Refc) { let win = get_obj::(b, B_WIN_M_VIEW); let btn = get_obj::(b, B_BTN_M_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, B_WIN_M_TOOL); let btn = get_obj::(b, B_BTN_M_TOOL); 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, B_WIN_M_PROP); let btn = get_obj::(b, B_BTN_M_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, B_DLG_ABOUT); let btn = get_obj::(b, B_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, E_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 = ListS::new(gtk_builder_get_objects(**b)); get_typed_from(head, gtk_window_get_type(), |obj| { let obj = obj as *mut GtkWindow; if obj != win { exp_del.push(obj); } }); let exp_del = Box::into_raw(Box::new(exp_del)); connect(win, E_DESTROY, c_done as _, exp_del); } // Get objects of type `ty` from `it`. unsafe fn get_typed_from(it: I, ty: GType, mut f: F) where I: Iterator, F: FnMut(*mut GObject) { for obj in it { // 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 *mut GTypeInstance, ty) != 0 { f(obj as _); } } } // 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.unclean() { 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_refresh(); } // 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.unclean() { 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_refresh(); } } // set up main window let win = get_obj(b, B_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, B_BTN_QUIT); connect(btn, E_ACTIVATE, c_quit_act as _, win); let btn = get_obj::(b, B_BTN_NEW); connect(btn, E_ACTIVATE, c_new_act as _, connect_ref(btn, edit.clone())); let btn = get_obj::(b, B_BTN_OPEN); connect(btn, E_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 { None }; 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, E_DELETE, 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, E_ACTIVATE, c_show_act as _, wid); } // Connects the map editor reference to a widget. unsafe fn connect_ref(obj: *mut T, rc: Rc) -> *const MapEditorRef { // Callback to finalize the reference. unsafe extern "C" fn c_done(_: *mut GtkWidget, ptr: gpointer) { Rc::from_raw(ptr as *const MapEditorRef); } let ptr = Rc::into_raw(rc); connect(obj, E_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) -> ffi::c_ulong { 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 and run the app let app = Refc::new(gtk_application_new(APP_ID, 0)); connect(*app, E_ACTIVATE, app_activate as _, ffi::null_void()); g_application_run(*app as _, 0, ffi::null_mut()); } // deinit the "static" data, and everything will be done g_static_resource_fini(&mut resource); } } // EOF