use gtk::prelude::WidgetExtManual; use gtk::{ BoxExt, ButtonExt, ContainerExt, DialogExt, EditableSignals, EntryExt, FileChooserExt, GtkWindowExt, LabelExt, WidgetExt, }; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; use crate::{app, dmc}; // The set of possible symbols for chart icons const SYMBOLS: &[char] = &['A', 'B', 'C', 'D']; #[derive(Clone)] struct App { data: Rc>, } impl App { fn new() -> App { let app_data = app::Data { symbol: 'X', dmc: None, }; let data = Rc::new(RefCell::new(app_data)); App { data } } } /// The name is a bit of a misnomer here, since it also contains the /// "model" (i.e. the `app` field) but this contains all the /// information necessary to refer to parts of the UI. Note that /// everything here is refcounted internally (the `App` via an `Rc` /// and everything else via the GTK machinery) so calling `.clone()` /// on this struct will just produce new copies that point at the same /// underlying data. #[derive(Clone)] pub struct View { // the app data app: App, // the overall window window: gtk::Window, // a map from character to button that changes to that charater icon_buttons: HashMap, // the text entry for the DMC color color_input: gtk::Entry, // the textual name of that DMC color color_name: gtk::Label, // the create-it button submit: gtk::Button, // the drawing area canvas: gtk::DrawingArea, } impl View { /// Setup and run the program pub fn run() { View::create().setup().show() } /// Create and arrange the widgets that will be used for drawing /// the UI fn create() -> View { let window = gtk::Window::new(gtk::WindowType::Toplevel); let app = App::new(); // left pane: the icon choices let left_pane = gtk::Box::new(gtk::Orientation::Vertical, 4); // HKHK let mut icon_buttons = HashMap::new(); for ch in SYMBOLS { let button = gtk::Button::with_label(&ch.to_string()); left_pane.pack_start(&button, false, true, 0); icon_buttons.insert(*ch, button); } // center pane let center_pane = gtk::Box::new(gtk::Orientation::Vertical, 2); let color_input = gtk::Entry::new(); let color_name = gtk::Label::new(None); let submit = gtk::Button::with_label("EXPORT IT"); center_pane.pack_start(&color_input, false, true, 0); center_pane.pack_start(&color_name, false, true, 0); center_pane.pack_start(&submit, false, true, 0); let canvas = gtk::DrawingArea::new(); // the overall container let flow = gtk::Box::new(gtk::Orientation::Horizontal, 2); flow.pack_start(&left_pane, false, true, 0); flow.pack_start(¢er_pane, false, true, 0); flow.pack_start(&canvas, true, true, 0); window.add(&flow); View { app, window, icon_buttons, color_input, color_name, submit, canvas, } } /// Bind callbacks and set up default configuration values for the /// provided widgets fn setup(&self) -> &Self { // setup window properties and callbacks self.window.connect_delete_event(move |_, _| { gtk::main_quit(); gtk::Inhibit(false) }); self.window.set_title("I Am Legend"); self.window.set_default_size(500, 200); self.window.set_resizable(false); // setup all the icon button callbacks for (ch, button) in self.icon_buttons.iter() { self.clone().icon_button_clicked(*ch, button); } self.clone().color_entry_changed(&self.color_input); self.clone().submit_clicked(&self.submit); self.clone().canvas_draw(&self.canvas); self } /// Setup the callback for an icon-chooser button fn icon_button_clicked(self, ch: char, button: >k::Button) { button.connect_clicked(move |_| { self.app.data.borrow_mut().symbol = ch; self.window.queue_draw_area(0, 0, 500, 200); }); } /// Setup the callback that fires when the text changes for the /// DMC thread input box fn color_entry_changed(self, entry: >k::Entry) { entry.connect_changed(move |s| { let str = s.get_text(); if let Some(color) = dmc::LOOKUP.get(str.as_str()) { self.app.data.borrow_mut().dmc = Some(*color.clone()); self.color_name.set_text(color.name); self.submit.set_sensitive(true); } else { self.app.data.borrow_mut().dmc = None; self.color_name.set_text(""); self.submit.set_sensitive(false); } self.window.queue_draw_area(0, 0, 500, 200); }); } /// Setup the callback that fires when the 'EXPORT IT' button is /// clicked fn submit_clicked(self, button: >k::Button) { button.connect_clicked(move |_| { let dialog = gtk::FileChooserDialog::with_buttons( Some("Select filename"), Some(&self.window), gtk::FileChooserAction::Save, &[ ("_Cancel", gtk::ResponseType::Cancel), ("_Open", gtk::ResponseType::Ok), ], ); let filter = gtk::FileFilter::new(); filter.add_mime_type("image/svg+xml"); filter.add_pattern("*.svg"); filter.set_name(Some("SVG image")); dialog.add_filter(&filter); if dialog.run() == gtk::ResponseType::Ok { if let Some(tgt) = dialog.get_filename() { let mut tgt = tgt.to_owned(); if !tgt.ends_with(".svg") { tgt.set_extension("svg"); } if let Err(err) = self.app.data.borrow_mut().draw_to_file(tgt) { eprintln!("Error in rendering: {}", err); } } } unsafe { dialog.destroy(); } }); } /// Setup the callback that handles drawing the canvas fn canvas_draw(self, canvas: >k::DrawingArea) { canvas.connect_draw(move |cv, ctx| { let w = cv.get_allocated_width(); let h = cv.get_allocated_height(); if let Err(err) = self.app.data.borrow_mut().render(ctx, w as f64, h as f64) { eprintln!("Error in rendering: {}", err); } gtk::Inhibit(false) }); } /// Show the whole thing fn show(&self) { self.window.show_all(); } }