diff --git a/docs/design/code.md b/docs/design/code.md index 61bc84851f..690fc68b3d 100644 --- a/docs/design/code.md +++ b/docs/design/code.md @@ -87,3 +87,13 @@ Wait, length of car affects parking pretty critically. A bunch of things plumb around the precomputed front of the spot, used for drawing and for cars to line up their front in the sim. I think we need to plumb the true start of the spot and have a method to interpolate and pick the true front. + +## Logging + +Libraries will do it too -- that's fine + +nice UI features: +- highlight what general area publishes a message +- filter by area +- remember where the log scroller is, even when hidden +- jump to end or beginning quickly diff --git a/editor/src/plugins/logs.rs b/editor/src/plugins/logs.rs new file mode 100644 index 0000000000..a09071700a --- /dev/null +++ b/editor/src/plugins/logs.rs @@ -0,0 +1,55 @@ +use ezgui::{Canvas, GfxCtx, LogScroller, UserInput}; +use objects::ROOT_MENU; +use piston::input::Key; +use plugins::Colorizer; + +// TODO This is all total boilerplate! +pub enum DisplayLogs { + Inactive, + Active(LogScroller), +} + +impl DisplayLogs { + pub fn new() -> DisplayLogs { + DisplayLogs::Inactive + } + + pub fn event(&mut self, input: &mut UserInput) -> bool { + let mut new_state: Option = None; + match self { + DisplayLogs::Inactive => { + if input.unimportant_key_pressed( + Key::Comma, + ROOT_MENU, + "show logs", + ) { + let mut scroller = LogScroller::new_with_capacity(100); + for i in 0..150 { + scroller.add_line(&format!("Sup line {}", i)); + } + new_state = Some(DisplayLogs::Active(scroller)); + } + } + DisplayLogs::Active(ref mut scroller) => { + if scroller.event(input) { + new_state = Some(DisplayLogs::Inactive); + } + } + } + if let Some(s) = new_state { + *self = s; + } + match self { + DisplayLogs::Inactive => false, + _ => true, + } + } + + pub fn draw(&self, g: &mut GfxCtx, canvas: &Canvas) { + if let DisplayLogs::Active(scroller) = self { + scroller.draw(g, canvas); + } + } +} + +impl Colorizer for DisplayLogs {} diff --git a/editor/src/plugins/mod.rs b/editor/src/plugins/mod.rs index 223eaab7ab..e6992b1073 100644 --- a/editor/src/plugins/mod.rs +++ b/editor/src/plugins/mod.rs @@ -6,6 +6,7 @@ pub mod floodfill; pub mod follow; pub mod geom_validation; pub mod hider; +pub mod logs; pub mod road_editor; pub mod search; pub mod show_route; diff --git a/editor/src/ui.rs b/editor/src/ui.rs index c24a07477e..882982d98a 100644 --- a/editor/src/ui.rs +++ b/editor/src/ui.rs @@ -17,6 +17,7 @@ use piston::input::{Key, MouseCursorEvent}; use piston::window::Size; use plugins::classification::OsmClassifier; use plugins::color_picker::ColorPicker; +use plugins::logs::DisplayLogs; use plugins::debug_objects::DebugObjectsState; use plugins::draw_polygon::DrawPolygonState; use plugins::floodfill::Floodfiller; @@ -125,6 +126,7 @@ impl UIWrapper { turn_cycler: TurnCyclerState::new(), draw_polygon: DrawPolygonState::new(), wizard_sample: WizardSample::new(), + logs: DisplayLogs::new(), active_plugin: None, @@ -246,6 +248,7 @@ impl UIWrapper { Box::new(|ui, input, _osd| ui.turn_cycler.event(input, ui.current_selection)), Box::new(|ui, input, osd| ui.draw_polygon.event(input, &ui.canvas, &ui.map, osd)), Box::new(|ui, input, _osd| ui.wizard_sample.event(input, &ui.map)), + Box::new(|ui, input, _osd| ui.logs.event(input)), ], } } @@ -279,6 +282,7 @@ struct UI { turn_cycler: TurnCyclerState, draw_polygon: DrawPolygonState, wizard_sample: WizardSample, + logs: DisplayLogs, // An index into UIWrapper.plugins. active_plugin: Option, @@ -462,6 +466,7 @@ impl UI { self.color_picker.draw(&self.canvas, g); self.draw_polygon.draw(g, &self.canvas); self.wizard_sample.draw(g, &self.canvas); + self.logs.draw(g, &self.canvas); self.search_state.draw(g, &self.canvas); self.warp.draw(g, &self.canvas); @@ -515,6 +520,7 @@ impl UI { 15 => Some(Box::new(&self.turn_cycler)), 16 => Some(Box::new(&self.draw_polygon)), 17 => Some(Box::new(&self.wizard_sample)), + 18 => Some(Box::new(&self.logs)), _ => panic!("Active plugin {} is too high", idx), } } diff --git a/ezgui/src/lib.rs b/ezgui/src/lib.rs index 72d448205c..da282fb11a 100644 --- a/ezgui/src/lib.rs +++ b/ezgui/src/lib.rs @@ -11,6 +11,7 @@ extern crate piston; mod canvas; mod input; mod keys; +mod log_scroller; mod menu; mod runner; mod text; @@ -23,6 +24,7 @@ use graphics::types::Color; pub use input::UserInput; pub use menu::Menu; use opengl_graphics::{GlGraphics, Texture}; +pub use log_scroller::LogScroller; use piston::input::Key; pub use runner::{run, EventLoopMode, GUI}; pub use text::TextOSD; diff --git a/ezgui/src/log_scroller.rs b/ezgui/src/log_scroller.rs new file mode 100644 index 0000000000..14d03f52d2 --- /dev/null +++ b/ezgui/src/log_scroller.rs @@ -0,0 +1,94 @@ +use piston::input::{Button, Key, PressEvent}; +use std::collections::VecDeque; +use {text, Canvas, GfxCtx, TextOSD, UserInput}; + +pub struct LogScroller { + // TODO store SpanText or similar + lines: VecDeque, + capacity: usize, + y_offset: usize, +} + +impl LogScroller { + pub fn new_with_capacity(capacity: usize) -> LogScroller { + LogScroller { + lines: VecDeque::with_capacity(capacity), + // Store separately, since VecDeque might internally choose a bigger capacity + capacity, + y_offset: 0, + } + } + + // TODO take and store styled text + pub fn add_line(&mut self, line: &str) { + if self.lines.len() == self.capacity { + self.lines.pop_front(); + } + self.lines.push_back(line.to_string()); + } + + // True if done + pub fn event(&mut self, input: &mut UserInput) -> bool { + let ev = input.use_event_directly().clone(); + input.consume_event(); + + if let Some(Button::Keyboard(Key::Escape)) = ev.press_args() { + return true; + } + + if let Some(Button::Keyboard(Key::Up)) = ev.press_args() { + if self.y_offset > 0 { + self.y_offset -= 1; + } + } + if let Some(Button::Keyboard(Key::Down)) = ev.press_args() { + self.y_offset += 1; + } + + false + } + + // TODO overlapping logic with Menu + pub fn draw(&self, g: &mut GfxCtx, canvas: &Canvas) { + let mut osd = TextOSD::new(); + // TODO Force padding of everything to a fixed 80% of the screen or so + osd.add_styled_line( + "Logs".to_string(), + text::TEXT_FG_COLOR, + Some(text::TEXT_QUERY_COLOR), + ); + + // How many lines can we fit on the screen? + let can_fit = { + // Subtract 1 for the title, and an additional TODO hacky + // few to avoid the bottom OSD and stuff. + let n = + (f64::from(canvas.window_size.height) / text::LINE_HEIGHT).floor() as isize - 1 - 6; + if n <= 0 { + 0 + } else { + n as usize + } + }; + // TODO argh, we want to do this clamping in event() or something; otherwise we can + // accumulate a bunch of invisible silly y_offsetness + let mut low_idx = self.y_offset; + if low_idx + can_fit > self.lines.len() { + if can_fit <= self.lines.len() { + low_idx = self.lines.len() - can_fit; + } + } + let high_idx = (low_idx + can_fit).min(self.lines.len()); + + // Slice syntax doesn't seem to work for no elements? + if !self.lines.is_empty() { + // TODO VecDeque can't be sliced, argh + let copy: Vec<&String> = self.lines.iter().collect(); + for line in ©[low_idx .. high_idx] { + osd.add_line(line.to_string()); + } + } + + canvas.draw_centered_text(g, osd); + } +}