use anyhow::{anyhow, bail, Result}; use gtk::prelude::WidgetExtManual; use gtk::{ BoxExt, ButtonExt, ContainerExt, DialogExt, EditableSignals, EntryExt, FileChooserExt, GtkWindowExt, LabelExt, WidgetExt, }; use std::cell::RefCell; use std::rc::Rc; mod dmc; struct AppData { symbol: char, dmc: Option, } impl AppData { fn draw_to_file>(&self, path: P) -> Result<()> { let f = std::fs::File::create(path)?; let surf = cairo::SvgSurface::for_stream(100.0, 100.0, f)?; self.render(&cairo::Context::new(&surf), 100.0, 100.0)?; if let Err(err) = surf.finish_output_stream() { bail!(err.error); }; Ok(()) } fn render(&self, ctx: &cairo::Context, w: f64, h: f64) -> Result<()> { let off_x = (w - 100.) / 2.0; let off_y = (h - 100.) / 2.0; let color = self.color(); ctx.set_source_rgb(color.0, color.1, color.2); ctx.paint(); ctx.set_source_rgb(0., 0., 0.); ctx.rectangle(off_x, off_y, 100., 100.); ctx.stroke(); ctx.move_to(off_x, off_y - 23.0); let layout = pangocairo::functions::create_layout(&ctx) .ok_or_else(|| anyhow!("Could not create layout"))?; layout.set_width(100 * 1024); layout.set_alignment(pango::Alignment::Center); let font = pango::FontDescription::from_string("Fira Sans 92"); layout.set_font_description(Some(&font)); layout.set_text(&self.symbol.to_string()); pangocairo::functions::show_layout(&ctx, &layout); Ok(()) } fn color(&self) -> (f64, f64, f64) { self.dmc .map(|dmc| dmc.color) .unwrap_or_else(|| (0.5, 0.5, 0.5)) } } #[derive(Clone)] struct App { data: Rc>, window: gtk::Window, } impl App { fn new(window: gtk::Window) -> App { let app_data = AppData { symbol: 'X', dmc: None, }; let data = Rc::new(RefCell::new(app_data)); App { data, window } } } fn mk_app() { let window = gtk::Window::new(gtk::WindowType::Toplevel); let app = App::new(window.clone()); window.connect_delete_event(move |_, _| { gtk::main_quit(); gtk::Inhibit(false) }); window.set_title("I Am Legend"); window.set_default_size(500, 200); window.set_resizable(false); let container = gtk::Box::new(gtk::Orientation::Vertical, 4); container.pack_start(&mk_icon_choice('A', app.clone()), false, true, 0); container.pack_start(&mk_icon_choice('B', app.clone()), false, true, 0); container.pack_start(&mk_icon_choice('C', app.clone()), false, true, 0); container.pack_start(&mk_icon_choice('D', app.clone()), false, true, 0); let flow = gtk::Box::new(gtk::Orientation::Horizontal, 2); flow.pack_start(&container, false, true, 0); let container = gtk::Box::new(gtk::Orientation::Vertical, 2); let label = gtk::Label::new(None); let button = gtk::Button::with_label("EXPORT IT"); { let color = gtk::Entry::new(); let app = app.clone(); let window = window.clone(); let label = label.clone(); let button = button.clone(); color.connect_changed(move |s| { let str = s.get_text(); if let Some(color) = dmc::LOOKUP.get(str.as_str()) { app.data.borrow_mut().dmc = Some(*color.clone()); label.set_text(color.name); button.set_sensitive(true); } else { app.data.borrow_mut().dmc = None; label.set_text(""); button.set_sensitive(false); } window.queue_draw_area(0, 0, 500, 200); }); container.pack_start(&color, false, true, 0); } { button.set_sensitive(false); let app = app.clone(); let window = window.clone(); button.connect_clicked(move |_| { let dialog = gtk::FileChooserDialog::with_buttons( Some("Select filename"), Some(&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"); } println!("saving to {:?}", tgt); if let Err(err) = app.data.borrow_mut().draw_to_file(tgt) { eprintln!("Error in rendering: {}", err); } } } unsafe { dialog.destroy(); } }); container.pack_start(&button, false, true, 0); } container.pack_start(&label, false, true, 0); flow.pack_start(&container, false, true, 0); { let canvas = gtk::DrawingArea::new(); canvas.connect_draw(move |cv, ctx| { let w = cv.get_allocated_width(); let h = cv.get_allocated_height(); if let Err(err) = app.data.borrow_mut().render(ctx, w as f64, h as f64) { eprintln!("Error in rendering: {}", err); } gtk::Inhibit(false) }); flow.pack_start(&canvas, true, true, 0); } window.add(&flow); window.show_all(); } fn mk_icon_choice(choice: char, cell: App) -> gtk::Button { let button = gtk::Button::with_label(&choice.to_string()); button.connect_clicked(move |_| { cell.data.borrow_mut().symbol = choice; cell.window.queue_draw_area(0, 0, 500, 200); }); button } fn main() { if gtk::init().is_err() { eprintln!("Failed to initialize GTK application"); } else { mk_app(); gtk::main(); } }