From 671dad6b02a9ce0a924da8f2052dcf010bd3ba5a Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Sun, 16 Dec 2018 14:20:30 -0800 Subject: [PATCH] make a blend between ScrollingMenu and ContextMenu... don't use it yet --- ezgui/src/input.rs | 5 -- ezgui/src/lib.rs | 1 + ezgui/src/menu.rs | 133 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 ezgui/src/menu.rs diff --git a/ezgui/src/input.rs b/ezgui/src/input.rs index ab5fbae90e..cd5d5e21df 100644 --- a/ezgui/src/input.rs +++ b/ezgui/src/input.rs @@ -32,11 +32,6 @@ impl UserInput { reserved_keys: HashMap::new(), }; - // TODO Or left clicking outside of the menu - // TODO If the user left clicks on a menu item, then mark that action as selected, and - // ensure contextual_action is called this round. - // TODO If the user hovers on a menu item, mark it for later highlighting. - // Create the context menu here, even if one already existed. if input.right_mouse_button_pressed() { input.context_menu = Some(ContextMenu { diff --git a/ezgui/src/lib.rs b/ezgui/src/lib.rs index 730f58898d..9435196d54 100644 --- a/ezgui/src/lib.rs +++ b/ezgui/src/lib.rs @@ -5,6 +5,7 @@ mod color; mod event; mod input; mod log_scroller; +mod menu; mod runner; mod scrolling_menu; mod text; diff --git a/ezgui/src/menu.rs b/ezgui/src/menu.rs new file mode 100644 index 0000000000..06199fc420 --- /dev/null +++ b/ezgui/src/menu.rs @@ -0,0 +1,133 @@ +use crate::{text, Canvas, Color, Event, GfxCtx, InputResult, Key, Text, UserInput}; +use geom::{Polygon, Pt2D}; + +// Stores some associated data with each choice +pub struct Menu { + prompt: Option, + choices: Vec<(Key, String, T)>, + current_idx: Option, + + origin: Pt2D, + // The rectangle representing the top row of the menu (not including the optional prompt), then + // the height of one row + // TODO Needing a separate call to initialize geometry sucks. + geometry: Option<(Polygon, f64)>, +} + +impl Menu { + /*pub fn new(prompt: &str, choices: Vec<(String, T)>) -> Menu { + if choices.is_empty() { + panic!("Can't create a menu without choices for \"{}\"", prompt); + } + Menu { + prompt: prompt.to_string(), + choices, + current_idx: 0, + } + }*/ + + pub fn event(&mut self, input: &mut UserInput, canvas: &Canvas) -> InputResult { + // We have to directly look at stuff here; all of input's methods lie and pretend nothing + // is happening. + let maybe_ev = input.use_event_directly(); + if maybe_ev.is_none() { + return InputResult::StillActive; + } + let ev = maybe_ev.unwrap(); + + // Handle the mouse + if let Some((ref row, height)) = self.geometry { + if ev == Event::LeftMouseButtonDown { + if let Some(i) = self.current_idx { + let (_, choice, data) = self.choices[i].clone(); + return InputResult::Done(choice, data); + } else { + return InputResult::Canceled; + } + } else if let Event::MouseMovedTo(x, y) = ev { + let cursor_pt = canvas.screen_to_map((x, y)); + let mut matched = false; + for i in 0..self.choices.len() { + if row + .translate(0.0, (i as f64) * height) + .contains_pt(cursor_pt) + { + self.current_idx = Some(i); + matched = true; + break; + } + } + if !matched { + self.current_idx = None; + } + } + } + + // Handle keys + if ev == Event::KeyPress(Key::Escape) { + return InputResult::Canceled; + } + + // TODO Disable arrow keys in context menus and the top menu? + if let Some(idx) = self.current_idx { + if ev == Event::KeyPress(Key::Enter) { + let (_, name, data) = self.choices[idx].clone(); + return InputResult::Done(name, data); + } else if ev == Event::KeyPress(Key::UpArrow) { + if idx > 0 { + self.current_idx = Some(idx - 1); + } + } else if ev == Event::KeyPress(Key::DownArrow) { + if idx < self.choices.len() - 1 { + self.current_idx = Some(idx + 1); + } + } + } + + InputResult::StillActive + } + + pub(crate) fn calculate_geometry(&mut self, g: &mut GfxCtx, canvas: &Canvas) { + if self.geometry.is_some() { + return; + } + + let mut txt = Text::new(); + // TODO prompt + for (hotkey, choice, _) in &self.choices { + txt.add_line(format!("{} - {}", hotkey.describe(), choice)); + } + let (screen_width, screen_height) = txt.dims(g); + let map_width = screen_width / canvas.cam_zoom; + let map_height = screen_height / canvas.cam_zoom; + let top_left = Pt2D::new( + self.origin.x() - (map_width / 2.0), + self.origin.y() - (map_height / 2.0), + ); + let row_height = map_height / (self.choices.len() as f64); + self.geometry = Some(( + Polygon::rectangle_topleft(top_left, map_width, row_height), + row_height, + )); + } + + pub fn draw(&self, g: &mut GfxCtx, canvas: &Canvas) { + let mut txt = Text::new(); + // TODO prompt using Some(text::TEXT_QUERY_COLOR) + for (idx, (hotkey, choice, _)) in self.choices.iter().enumerate() { + let bg = if Some(idx) == self.current_idx { + Some(Color::WHITE) + } else { + None + }; + txt.add_styled_line(hotkey.describe(), Color::BLUE, bg); + txt.append(format!(" - {}", choice), text::TEXT_FG_COLOR, bg); + } + canvas.draw_text_at(g, txt, self.origin); + } + + pub fn current_choice(&self) -> Option<&T> { + let idx = self.current_idx?; + Some(&self.choices[idx].2) + } +}