Maraiah/source/tycho/main.rs

343 lines
10 KiB
Rust

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<MapEditorState>);
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!("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 the main menu window.
unsafe fn setup_win_main(b: *mut GtkBuilder,
app: *mut GtkApplication,
edit: Rc<Option<MapEditorState>>)
{
/// 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<MapEditorState>;
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);
}
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 _);
}
/// 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::<gpointer>());
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<T>(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::<MapEditorState>);
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