Maraiah/source/tycho/main.rs

375 lines
12 KiB
Rust

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::{err::*, ffi}};
use std::{cell::RefCell, rc::Rc};
// 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 EditorRef);
assert_eq!(Rc::strong_count(&edit), 1);
}
let b = Refc::new(gtk_builder_new_from_resource(PATH_BUILDER));
let prop = PropertiesWindow{flg_ent: get_flag_fields(&b, B_CON_F_ENT),
flg_env: get_flag_fields(&b, B_CON_F_ENV),
flg_msn: get_flag_fields(&b, B_CON_F_MSN)};
let edit = EditorModel::new(EditorView{prop});
let edit = RefCell::new(edit);
let edit = Rc::new(edit);
setup_css();
//setup_draw_area(&b, edit.clone());
let wv = setup_win(&b, B_WIN_M_VIEW, B_BTN_M_VIEW);
let wt = setup_win(&b, B_WIN_M_TOOL, B_BTN_M_TOOL);
let _ = setup_win(&b, B_WIN_M_PROP, B_BTN_M_PROP);
gtk_widget_show_all(wv);
gtk_widget_show_all(wt);
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<GtkBuilder>, name: ffi::NT)
-> Vec<Refc<'static, GtkToggleButton>>
{
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(Refc::own(obj as _)));
flags
}
// Sets up the map view window.
unsafe fn setup_win(b: &Refc<GtkBuilder>, win: ffi::NT, btn: ffi::NT)
-> *mut GtkWidget
{
// Callback to show the widget.
unsafe extern "C" fn c_show_act(_: *mut GtkWidget, wid: gpointer)
{
gtk_widget_show_all(wid as _);
}
let win = get_obj::<GtkWindow >(b, win);
let btn = get_obj::<GtkMenuItem>(b, btn);
connect_hide(win);
connect(btn, E_ACTIVATE, c_show_act as _, win);
win as *mut GtkWidget
}
// 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 = get_obj::<GtkAboutDialog>(b, B_DLG_ABOUT);
let btn = get_obj::<GtkMenuItem >(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<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 = 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<I, F>(it: I, ty: GType, mut f: F)
where I: Iterator<Item = gpointer>,
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<GtkBuilder>,
app: *mut GtkApplication,
edit: Rc<EditorRef>)
{
// 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 EditorRef);
edit.borrow_mut().open_new();
}
// Opens the map editor with a buffer.
unsafe fn open_buf(path: &str, edit: &mut EditorModel) -> ResultS<()>
{
let fp = std::fs::File::open(&path)?;
let mm = memmap::Mmap::map(&fp)?;
edit.open_buf(&mm);
Ok(())
}
// 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 EditorRef);
if let Some(path) = run_file_chooser_open() {
// TODO: handle errors gracefully
open_buf(&path, &mut edit.borrow_mut()).unwrap();
}
}
// 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::<GtkMenuItem>(b, B_BTN_QUIT);
connect(btn, E_ACTIVATE, c_quit_act as _, win);
let btn = get_obj::<GtkMenuItem>(b, B_BTN_NEW);
connect(btn, E_ACTIVATE, c_new_act as _, connect_ref(btn, edit.clone()));
let btn = get_obj::<GtkMenuItem>(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 Open File dialogue.
unsafe fn run_file_chooser_open() -> Option<String>
{
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<T>(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 the map editor reference to a widget.
unsafe fn connect_ref<T>(obj: *mut T, rc: Rc<EditorRef>) -> *const EditorRef
{
// Callback to finalize the reference.
unsafe extern "C" fn c_done(_: *mut GtkWidget, ptr: gpointer)
{
Rc::from_raw(ptr as *const EditorRef);
}
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<T>(b: &Refc<GtkBuilder>, name: ffi::NT) -> *mut T
{
gtk_builder_get_object(**b, name) as _
}
// Connects a signal handler.
unsafe fn connect<T, U>(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);
}
}
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");
// EOF