Maraiah/source/tycho/main.rs

491 lines
14 KiB
Rust
Raw Normal View History

2019-03-24 17:04:49 -07:00
mod interfaces;
2019-03-02 15:26:55 -08:00
use crate::interfaces::*;
2019-03-23 05:33:04 -07:00
use gdk_pixbuf_sys::*;
use gdk_sys::*;
use gio_sys::*;
use glib_sys::*;
use gobject_sys::*;
use gtk_sys::*;
2019-03-27 14:02:15 -07:00
use maraiah::{c_str, durandal::ffi};
use std::{cell::RefCell, marker::PhantomData, rc::Rc};
2019-03-23 05:33:04 -07:00
2019-03-29 12:27:16 -07:00
const ACTIVATE: ffi::NT = c_str!("activate");
const APP_ID: ffi::NT = c_str!("net.greyserv.maraiah.tycho");
const DELETE_EVENT: ffi::NT = c_str!("delete-event");
const DESTROY: ffi::NT = c_str!("destroy");
const DRAW: ffi::NT = c_str!("draw");
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");
2019-03-27 11:07:33 -07:00
/// Called when the application activates in order to set everything up.
unsafe extern "C" fn app_activate(app: *mut GtkApplication, edit: gpointer)
2019-03-23 05:33:04 -07:00
{
// 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);
2019-03-18 05:21:30 -07:00
2019-03-23 05:33:04 -07:00
setup_css();
2019-03-02 15:26:55 -08:00
2019-03-29 12:27:16 -07:00
let b = Refc::new(gtk_builder_new_from_resource(PATH_BUILDER));
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());
2019-03-02 15:26:55 -08:00
}
2019-03-24 17:04:49 -07:00
/// Sets up the map view window's drawing area.
unsafe fn setup_draw_area(b: &Refc<GtkBuilder>, edit: Rc<MapEditorRef>)
2019-03-02 15:26:55 -08:00
{
2019-03-27 04:56:30 -07:00
/// All of the state necessary for the drawing area.
2019-03-23 05:33:04 -07:00
struct RenderState
{
im_nomap: Refc<'static, GdkPixbuf>,
ax: Refc<'static, GtkAdjustment>,
ay: Refc<'static, GtkAdjustment>,
edit: Rc<MapEditorRef>,
2019-03-23 05:33:04 -07:00
}
2019-03-27 02:32:25 -07:00
/// Callback to finalize the drawing area.
unsafe extern "C" fn c_done(_: *mut GtkWidget, rend: gpointer)
2019-03-23 05:33:04 -07:00
{
Box::from_raw(rend as *mut RenderState);
2019-03-23 05:33:04 -07:00
}
2019-03-27 02:32:25 -07:00
/// Callback to draw on the drawing area.
2019-03-23 05:33:04 -07:00
unsafe extern "C" fn c_draw(wid: *mut GtkWidget,
2019-03-24 17:04:49 -07:00
ctx: *mut cairo_sys::cairo_t,
rend: gpointer)
2019-03-23 05:33:04 -07:00
-> gboolean
{
2019-03-29 12:27:16 -07:00
let rend = &mut *(rend as *mut RenderState);
2019-03-23 05:33:04 -07:00
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);
2019-03-23 05:33:04 -07:00
gtk_adjustment_set_lower(*rend.ay, 0.0);
gtk_adjustment_set_upper(*rend.ay, h);
2019-03-23 05:33:04 -07:00
2019-03-29 12:27:16 -07:00
let im = CrImage(*rend.im_nomap);
2019-03-27 04:55:58 -07:00
let mut dr = CrDrawArea::new(ctx, w, h);
2019-03-02 15:26:55 -08:00
2019-03-27 04:55:58 -07:00
rend.edit.borrow().draw(&mut dr, &im);
2019-03-02 15:26:55 -08:00
2019-03-23 05:33:04 -07:00
1
}
2019-03-02 17:49:35 -08:00
2019-03-29 12:27:16 -07:00
let wid = get_obj::<GtkDrawingArea>(b, c_str!("draw-area"));
2019-03-02 15:26:55 -08:00
2019-03-27 14:02:15 -07:00
edit.borrow_mut().set_draw(wid as _);
2019-03-27 04:56:30 -07:00
// get all of the necessary state and related objects
2019-03-29 12:27:16 -07:00
let ax = Refc::new(get_obj::<GtkAdjustment>(b, c_str!("adj-map-horz")));
let ay = Refc::new(get_obj::<GtkAdjustment>(b, c_str!("adj-map-vert")));
2019-03-02 15:26:55 -08:00
g_object_ref(*ax as _);
g_object_ref(*ay as _);
2019-03-02 15:26:55 -08:00
2019-03-29 12:27:16 -07:00
let im_nomap = Refc::new(load_img(IM_NOMAP));
2019-03-02 15:26:55 -08:00
2019-03-27 02:32:25 -07:00
let rend = RenderState{im_nomap, ax, ay, edit};
let rend = Box::into_raw(Box::new(rend));
2019-03-02 15:26:55 -08:00
2019-03-29 12:27:16 -07:00
connect(wid, DESTROY, c_done as _, rend);
connect(wid, DRAW, c_draw as _, rend);
2019-03-02 15:26:55 -08:00
}
2019-03-24 17:04:49 -07:00
/// Sets up the map view window.
unsafe fn setup_win_map_view(b: &Refc<GtkBuilder>)
2019-03-02 15:26:55 -08:00
{
2019-03-29 12:27:16 -07:00
let win = get_obj::<GtkWindow >(b, c_str!("win-map-view"));
let btn = get_obj::<GtkMenuItem>(b, c_str!("btn-show-map-view"));
2019-03-23 05:33:04 -07:00
2019-03-29 12:27:16 -07:00
connect_hide(win);
connect_show(btn, win);
2019-03-02 15:26:55 -08:00
}
2019-03-24 17:04:49 -07:00
/// Sets up the map tools window.
unsafe fn setup_win_map_tools(b: &Refc<GtkBuilder>)
2019-03-02 15:26:55 -08:00
{
2019-03-29 12:27:16 -07:00
let win = get_obj::<GtkWindow >(b, c_str!("win-map-tools"));
let btn = get_obj::<GtkMenuItem>(b, c_str!("btn-show-map-tools"));
2019-03-23 05:33:04 -07:00
2019-03-29 12:27:16 -07:00
connect_hide(win);
connect_show(btn, win);
2019-03-02 15:26:55 -08:00
}
2019-03-24 17:04:49 -07:00
/// Sets up the map properties window.
unsafe fn setup_win_map_prop(b: &Refc<GtkBuilder>)
2019-03-02 15:26:55 -08:00
{
2019-03-29 12:27:16 -07:00
let win = get_obj::<GtkWindow >(b, c_str!("win-map-prop"));
let btn = get_obj::<GtkMenuItem>(b, c_str!("btn-show-map-prop"));
2019-03-23 05:33:04 -07:00
2019-03-29 12:27:16 -07:00
connect_hide(win);
connect_show(btn, win);
2019-03-02 15:26:55 -08:00
}
2019-03-24 17:04:49 -07:00
/// Sets up the about dialogue.
unsafe fn setup_about_dlg(b: &Refc<GtkBuilder>)
2019-03-02 15:26:55 -08:00
{
2019-03-24 17:04:49 -07:00
/// 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)
2019-03-23 05:33:04 -07:00
{
gtk_dialog_run(dlg as _);
gtk_widget_hide(dlg as _);
2019-03-02 15:26:55 -08:00
}
2019-03-29 12:27:16 -07:00
let dlg = get_obj::<GtkAboutDialog>(b, c_str!("dlg-about"));
let btn = get_obj::<GtkMenuItem >(b, c_str!("btn-about"));
2019-03-23 05:33:04 -07:00
let it = env!("CARGO_PKG_AUTHORS").split(';');
let mut v = ffi::CStringVec::new_from_iter(it).unwrap();
2019-03-29 12:27:16 -07:00
let img = Refc::new(load_img(IM_ABOUT));
2019-03-18 05:21:30 -07:00
2019-03-23 05:33:04 -07:00
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);
2019-03-18 05:21:30 -07:00
2019-03-29 12:27:16 -07:00
connect_hide(dlg);
connect(btn, ACTIVATE, c_show_act as _, dlg);
2019-03-23 05:33:04 -07:00
}
2019-03-18 05:21:30 -07:00
2019-03-27 02:32:25 -07:00
/// Sets up explicit window finalization for the main window.
unsafe fn setup_explicit_drop(b: &Refc<GtkBuilder>, win: *mut GtkWindow)
2019-03-27 02:32:25 -07:00
{
/// 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);
2019-03-27 02:32:25 -07:00
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));
2019-03-29 12:27:16 -07:00
connect(win, DESTROY, c_done as _, exp_del);
2019-03-27 02:32:25 -07:00
}
2019-03-24 17:04:49 -07:00
/// Sets up the main menu window.
unsafe fn setup_win_main(b: &Refc<GtkBuilder>,
app: *mut GtkApplication,
edit: Rc<MapEditorRef>)
2019-03-23 05:33:04 -07:00
{
2019-03-24 17:04:49 -07:00
/// Callback to close the window when the "Quit" button is pressed.
unsafe extern "C" fn c_quit_act(_: *mut GtkWidget, win: gpointer)
2019-03-23 05:33:04 -07:00
{
gtk_window_close(win as _);
}
2019-03-18 05:21:30 -07:00
2019-03-27 04:56:30 -07:00
/// Callback to create a new map when the "New" button is pressed.
unsafe extern "C" fn c_new_act(_: *mut GtkWidget, edit: gpointer)
{
2019-03-29 12:27:16 -07:00
let edit = &*(edit as *const MapEditorRef);
let mut edit = edit.borrow_mut();
if edit.is_opened() {
2019-03-25 19:15:50 -07:00
let titl = c_str!("Confirm");
let text = c_str!("Are you sure you want to create a new project? \
2019-03-31 11:58:36 -07:00
Unsaved data may be lost.");
2019-03-25 19:15:50 -07:00
2019-03-27 02:42:22 -07:00
if !run_ok_cancel_dlg(titl, text) {
return;
2019-03-25 19:15:50 -07:00
}
}
edit.open_new();
edit.cause_update();
}
2019-03-31 11:58:36 -07:00
/// 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.is_opened() {
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_update();
}
}
2019-03-27 02:32:25 -07:00
// set up main window
2019-03-29 12:27:16 -07:00
let win = get_obj::<GtkWindow>(b, c_str!("win-main"));
2019-03-27 02:32:25 -07:00
setup_explicit_drop(b, win);
gtk_window_set_application(win, app);
gtk_widget_show_all(win as _);
// set up buttons
2019-03-29 12:27:16 -07:00
let btn = get_obj::<GtkMenuItem>(b, c_str!("btn-quit"));
connect(btn, ACTIVATE, c_quit_act as _, win);
2019-03-27 02:32:25 -07:00
2019-03-29 12:27:16 -07:00
let btn = get_obj::<GtkMenuItem>(b, c_str!("btn-new"));
connect(btn, ACTIVATE, c_new_act as _, connect_ref(btn, edit.clone()));
2019-03-31 11:58:36 -07:00
let btn = get_obj::<GtkMenuItem>(b, c_str!("btn-open"));
connect(btn, ACTIVATE, c_open_act as _, connect_ref(btn, edit.clone()));
2019-03-23 05:33:04 -07:00
}
2019-03-18 05:21:30 -07:00
2019-03-24 17:04:49 -07:00
/// Sets up the CSS styling providers.
2019-03-23 05:33:04 -07:00
unsafe fn setup_css()
{
let css = Refc::new(gtk_css_provider_new());
2019-03-29 12:27:16 -07:00
gtk_css_provider_load_from_resource(*css, PATH_CSS);
2019-03-02 15:26:55 -08:00
2019-03-23 05:33:04 -07:00
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);
2019-03-23 05:33:04 -07:00
}
2019-03-02 15:26:55 -08:00
2019-03-25 19:15:50 -07:00
/// 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;
2019-03-27 02:32:25 -07:00
let dlg = gtk_dialog_new_with_buttons(title,
ffi::null_mut(),
flags,
c_str!("_OK"),
GTK_RESPONSE_ACCEPT,
c_str!("_Cancel"),
GTK_RESPONSE_REJECT,
2019-03-29 12:27:16 -07:00
ffi::null_mut_void());
2019-03-25 19:15:50 -07:00
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);
2019-03-31 11:58:36 -07:00
res == GTK_RESPONSE_ACCEPT
}
/// 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
2019-03-25 19:15:50 -07:00
}
/// Connects a handler that hides a toplevel widget when deleted.
2019-03-29 12:27:16 -07:00
unsafe fn connect_hide<T>(wid: *mut T)
2019-03-23 05:33:04 -07:00
{
/// Callback to hide the widget.
unsafe extern "C" fn c_hide_del(wid: *mut GtkWidget,
_: *mut GdkEvent,
_: gpointer)
2019-03-23 05:33:04 -07:00
{
gtk_widget_hide(wid);
2019-03-02 15:26:55 -08:00
}
2019-03-29 12:27:16 -07:00
connect(wid, DELETE_EVENT, c_hide_del as _, ffi::null_void());
2019-03-23 05:33:04 -07:00
}
2019-03-24 17:04:49 -07:00
/// Connects a handler that shows a widget when activated.
2019-03-29 12:27:16 -07:00
unsafe fn connect_show<T, U>(btn: *mut T, wid: *mut U)
2019-03-23 05:33:04 -07:00
{
/// Callback to show the widget.
unsafe extern "C" fn c_show_act(_: *mut GtkWidget, wid: gpointer)
2019-03-23 05:33:04 -07:00
{
gtk_widget_show_all(wid as _);
}
2019-03-29 12:27:16 -07:00
connect(btn, ACTIVATE, c_show_act as _, wid);
}
/// Connects a reference-counted object to a widget.
unsafe fn connect_ref<T, U>(obj: *mut T, rc: Rc<U>) -> *const U
{
/// Callback to finalize the reference.
unsafe extern "C" fn c_done(_: *mut GtkWidget, edit: gpointer)
{
Rc::from_raw(edit as *const MapEditorRef);
}
let ptr = Rc::into_raw(rc);
connect(obj, DESTROY, c_done as _, ptr);
ptr
2019-03-23 05:33:04 -07:00
}
/// Gets an object from a `GtkBuilder`.
unsafe fn get_obj<T>(b: &Refc<GtkBuilder>, name: ffi::NT) -> *mut T
2019-03-23 05:33:04 -07:00
{
2019-03-29 12:27:16 -07:00
gtk_builder_get_object(**b, name) as _
2019-03-23 05:33:04 -07:00
}
/// Connects a signal handler.
2019-03-29 12:27:16 -07:00
unsafe fn connect<T, U>(obj: *mut T, name: ffi::NT, cb: gpointer, d: *const U)
2019-03-23 05:33:04 -07:00
{
let cb = std::mem::transmute(cb);
2019-03-29 12:27:16 -07:00
g_signal_connect_data(obj as _, name, cb, d as _, None, 0);
2019-03-23 05:33:04 -07:00
}
2019-03-24 17:04:49 -07:00
/// Loads a `Pixbuf` from a resource.
2019-03-23 05:33:04 -07:00
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
2019-03-27 14:02:15 -07:00
let edit = MapEditor::default();
2019-03-27 04:56:30 -07:00
let edit = RefCell::new(edit);
let edit = Rc::new(edit);
let eptr = Rc::into_raw(edit.clone());
2019-03-23 05:33:04 -07:00
// create and run the app
2019-03-29 12:27:16 -07:00
let app = Refc::new(gtk_application_new(APP_ID, 0));
2019-03-23 05:33:04 -07:00
2019-03-29 12:27:16 -07:00
connect(*app, ACTIVATE, app_activate as _, eptr);
2019-03-23 05:33:04 -07:00
g_application_run(*app as _, 0, ffi::null_mut());
2019-03-23 05:33:04 -07:00
// ok, clean up all this crap now
drop(app);
2019-03-23 05:33:04 -07:00
2019-03-27 02:32:25 -07:00
assert_eq!(Rc::strong_count(&edit), 1);
drop(edit);
2019-03-23 05:33:04 -07:00
// deinit the "static" data, and everything will be done
g_static_resource_fini(&mut resource);
}
2019-03-02 15:26:55 -08:00
}
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>,
}
2019-03-02 15:26:55 -08:00
// EOF