view.rs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. use gtk::prelude::WidgetExtManual;
  2. use gtk::{
  3. BoxExt, ButtonExt, ContainerExt, DialogExt, EditableSignals, EntryExt, FileChooserExt,
  4. GtkWindowExt, LabelExt, WidgetExt,
  5. };
  6. use std::cell::RefCell;
  7. use std::collections::HashMap;
  8. use std::rc::Rc;
  9. use crate::{app, dmc};
  10. // The set of possible symbols for chart icons
  11. const SYMBOLS: &[char] = &['A', 'B', 'C', 'D'];
  12. #[derive(Clone)]
  13. struct App {
  14. data: Rc<RefCell<app::Data>>,
  15. }
  16. impl App {
  17. fn new() -> App {
  18. let app_data = app::Data {
  19. symbol: 'X',
  20. dmc: None,
  21. };
  22. let data = Rc::new(RefCell::new(app_data));
  23. App { data }
  24. }
  25. }
  26. /// The name is a bit of a misnomer here, since it also contains the
  27. /// "model" (i.e. the `app` field) but this contains all the
  28. /// information necessary to refer to parts of the UI. Note that
  29. /// everything here is refcounted internally (the `App` via an `Rc`
  30. /// and everything else via the GTK machinery) so calling `.clone()`
  31. /// on this struct will just produce new copies that point at the same
  32. /// underlying data.
  33. #[derive(Clone)]
  34. pub struct View {
  35. // the app data
  36. app: App,
  37. // the overall window
  38. window: gtk::Window,
  39. // a map from character to button that changes to that charater
  40. icon_buttons: HashMap<char, gtk::Button>,
  41. // the text entry for the DMC color
  42. color_input: gtk::Entry,
  43. // the textual name of that DMC color
  44. color_name: gtk::Label,
  45. // the create-it button
  46. submit: gtk::Button,
  47. // the drawing area
  48. canvas: gtk::DrawingArea,
  49. }
  50. impl View {
  51. /// Setup and run the program
  52. pub fn run() {
  53. View::create().setup().show()
  54. }
  55. /// Create and arrange the widgets that will be used for drawing
  56. /// the UI
  57. fn create() -> View {
  58. let window = gtk::Window::new(gtk::WindowType::Toplevel);
  59. let app = App::new();
  60. // left pane: the icon choices
  61. let left_pane = gtk::Box::new(gtk::Orientation::Vertical, 4);
  62. // HKHK
  63. let mut icon_buttons = HashMap::new();
  64. for ch in SYMBOLS {
  65. let button = gtk::Button::with_label(&ch.to_string());
  66. left_pane.pack_start(&button, false, true, 0);
  67. icon_buttons.insert(*ch, button);
  68. }
  69. // center pane
  70. let center_pane = gtk::Box::new(gtk::Orientation::Vertical, 2);
  71. let color_input = gtk::Entry::new();
  72. let color_name = gtk::Label::new(None);
  73. let submit = gtk::Button::with_label("EXPORT IT");
  74. center_pane.pack_start(&color_input, false, true, 0);
  75. center_pane.pack_start(&color_name, false, true, 0);
  76. center_pane.pack_start(&submit, false, true, 0);
  77. let canvas = gtk::DrawingArea::new();
  78. // the overall container
  79. let flow = gtk::Box::new(gtk::Orientation::Horizontal, 2);
  80. flow.pack_start(&left_pane, false, true, 0);
  81. flow.pack_start(&center_pane, false, true, 0);
  82. flow.pack_start(&canvas, true, true, 0);
  83. window.add(&flow);
  84. View {
  85. app,
  86. window,
  87. icon_buttons,
  88. color_input,
  89. color_name,
  90. submit,
  91. canvas,
  92. }
  93. }
  94. /// Bind callbacks and set up default configuration values for the
  95. /// provided widgets
  96. fn setup(&self) -> &Self {
  97. // setup window properties and callbacks
  98. self.window.connect_delete_event(move |_, _| {
  99. gtk::main_quit();
  100. gtk::Inhibit(false)
  101. });
  102. self.window.set_title("I Am Legend");
  103. self.window.set_default_size(500, 200);
  104. self.window.set_resizable(false);
  105. // setup all the icon button callbacks
  106. for (ch, button) in self.icon_buttons.iter() {
  107. self.clone().icon_button_clicked(*ch, button);
  108. }
  109. self.clone().color_entry_changed(&self.color_input);
  110. self.clone().submit_clicked(&self.submit);
  111. self.clone().canvas_draw(&self.canvas);
  112. self
  113. }
  114. /// Setup the callback for an icon-chooser button
  115. fn icon_button_clicked(self, ch: char, button: &gtk::Button) {
  116. button.connect_clicked(move |_| {
  117. self.app.data.borrow_mut().symbol = ch;
  118. self.window.queue_draw_area(0, 0, 500, 200);
  119. });
  120. }
  121. /// Setup the callback that fires when the text changes for the
  122. /// DMC thread input box
  123. fn color_entry_changed(self, entry: &gtk::Entry) {
  124. entry.connect_changed(move |s| {
  125. let str = s.get_text();
  126. if let Some(color) = dmc::LOOKUP.get(str.as_str()) {
  127. self.app.data.borrow_mut().dmc = Some(*color.clone());
  128. self.color_name.set_text(color.name);
  129. self.submit.set_sensitive(true);
  130. } else {
  131. self.app.data.borrow_mut().dmc = None;
  132. self.color_name.set_text("");
  133. self.submit.set_sensitive(false);
  134. }
  135. self.window.queue_draw_area(0, 0, 500, 200);
  136. });
  137. }
  138. /// Setup the callback that fires when the 'EXPORT IT' button is
  139. /// clicked
  140. fn submit_clicked(self, button: &gtk::Button) {
  141. button.connect_clicked(move |_| {
  142. let dialog = gtk::FileChooserDialog::with_buttons(
  143. Some("Select filename"),
  144. Some(&self.window),
  145. gtk::FileChooserAction::Save,
  146. &[
  147. ("_Cancel", gtk::ResponseType::Cancel),
  148. ("_Open", gtk::ResponseType::Ok),
  149. ],
  150. );
  151. let filter = gtk::FileFilter::new();
  152. filter.add_mime_type("image/svg+xml");
  153. filter.add_pattern("*.svg");
  154. filter.set_name(Some("SVG image"));
  155. dialog.add_filter(&filter);
  156. if dialog.run() == gtk::ResponseType::Ok {
  157. if let Some(tgt) = dialog.get_filename() {
  158. let mut tgt = tgt.to_owned();
  159. if !tgt.ends_with(".svg") {
  160. tgt.set_extension("svg");
  161. }
  162. if let Err(err) = self.app.data.borrow_mut().draw_to_file(tgt) {
  163. eprintln!("Error in rendering: {}", err);
  164. }
  165. }
  166. }
  167. unsafe {
  168. dialog.destroy();
  169. }
  170. });
  171. }
  172. /// Setup the callback that handles drawing the canvas
  173. fn canvas_draw(self, canvas: &gtk::DrawingArea) {
  174. canvas.connect_draw(move |cv, ctx| {
  175. let w = cv.get_allocated_width();
  176. let h = cv.get_allocated_height();
  177. if let Err(err) = self.app.data.borrow_mut().render(ctx, w as f64, h as f64) {
  178. eprintln!("Error in rendering: {}", err);
  179. }
  180. gtk::Inhibit(false)
  181. });
  182. }
  183. /// Show the whole thing
  184. fn show(&self) {
  185. self.window.show_all();
  186. }
  187. }