mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-25 11:44:25 +03:00
adding a scrollable log buffer widget
This commit is contained in:
parent
f6ddd8aeaa
commit
f9e0b6facc
@ -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
|
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
|
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.
|
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
|
||||||
|
55
editor/src/plugins/logs.rs
Normal file
55
editor/src/plugins/logs.rs
Normal file
@ -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<DisplayLogs> = 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 {}
|
@ -6,6 +6,7 @@ pub mod floodfill;
|
|||||||
pub mod follow;
|
pub mod follow;
|
||||||
pub mod geom_validation;
|
pub mod geom_validation;
|
||||||
pub mod hider;
|
pub mod hider;
|
||||||
|
pub mod logs;
|
||||||
pub mod road_editor;
|
pub mod road_editor;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
pub mod show_route;
|
pub mod show_route;
|
||||||
|
@ -17,6 +17,7 @@ use piston::input::{Key, MouseCursorEvent};
|
|||||||
use piston::window::Size;
|
use piston::window::Size;
|
||||||
use plugins::classification::OsmClassifier;
|
use plugins::classification::OsmClassifier;
|
||||||
use plugins::color_picker::ColorPicker;
|
use plugins::color_picker::ColorPicker;
|
||||||
|
use plugins::logs::DisplayLogs;
|
||||||
use plugins::debug_objects::DebugObjectsState;
|
use plugins::debug_objects::DebugObjectsState;
|
||||||
use plugins::draw_polygon::DrawPolygonState;
|
use plugins::draw_polygon::DrawPolygonState;
|
||||||
use plugins::floodfill::Floodfiller;
|
use plugins::floodfill::Floodfiller;
|
||||||
@ -125,6 +126,7 @@ impl UIWrapper {
|
|||||||
turn_cycler: TurnCyclerState::new(),
|
turn_cycler: TurnCyclerState::new(),
|
||||||
draw_polygon: DrawPolygonState::new(),
|
draw_polygon: DrawPolygonState::new(),
|
||||||
wizard_sample: WizardSample::new(),
|
wizard_sample: WizardSample::new(),
|
||||||
|
logs: DisplayLogs::new(),
|
||||||
|
|
||||||
active_plugin: None,
|
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.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.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.wizard_sample.event(input, &ui.map)),
|
||||||
|
Box::new(|ui, input, _osd| ui.logs.event(input)),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -279,6 +282,7 @@ struct UI {
|
|||||||
turn_cycler: TurnCyclerState,
|
turn_cycler: TurnCyclerState,
|
||||||
draw_polygon: DrawPolygonState,
|
draw_polygon: DrawPolygonState,
|
||||||
wizard_sample: WizardSample,
|
wizard_sample: WizardSample,
|
||||||
|
logs: DisplayLogs,
|
||||||
|
|
||||||
// An index into UIWrapper.plugins.
|
// An index into UIWrapper.plugins.
|
||||||
active_plugin: Option<usize>,
|
active_plugin: Option<usize>,
|
||||||
@ -462,6 +466,7 @@ impl UI {
|
|||||||
self.color_picker.draw(&self.canvas, g);
|
self.color_picker.draw(&self.canvas, g);
|
||||||
self.draw_polygon.draw(g, &self.canvas);
|
self.draw_polygon.draw(g, &self.canvas);
|
||||||
self.wizard_sample.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.search_state.draw(g, &self.canvas);
|
||||||
self.warp.draw(g, &self.canvas);
|
self.warp.draw(g, &self.canvas);
|
||||||
|
|
||||||
@ -515,6 +520,7 @@ impl UI {
|
|||||||
15 => Some(Box::new(&self.turn_cycler)),
|
15 => Some(Box::new(&self.turn_cycler)),
|
||||||
16 => Some(Box::new(&self.draw_polygon)),
|
16 => Some(Box::new(&self.draw_polygon)),
|
||||||
17 => Some(Box::new(&self.wizard_sample)),
|
17 => Some(Box::new(&self.wizard_sample)),
|
||||||
|
18 => Some(Box::new(&self.logs)),
|
||||||
_ => panic!("Active plugin {} is too high", idx),
|
_ => panic!("Active plugin {} is too high", idx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ extern crate piston;
|
|||||||
mod canvas;
|
mod canvas;
|
||||||
mod input;
|
mod input;
|
||||||
mod keys;
|
mod keys;
|
||||||
|
mod log_scroller;
|
||||||
mod menu;
|
mod menu;
|
||||||
mod runner;
|
mod runner;
|
||||||
mod text;
|
mod text;
|
||||||
@ -23,6 +24,7 @@ use graphics::types::Color;
|
|||||||
pub use input::UserInput;
|
pub use input::UserInput;
|
||||||
pub use menu::Menu;
|
pub use menu::Menu;
|
||||||
use opengl_graphics::{GlGraphics, Texture};
|
use opengl_graphics::{GlGraphics, Texture};
|
||||||
|
pub use log_scroller::LogScroller;
|
||||||
use piston::input::Key;
|
use piston::input::Key;
|
||||||
pub use runner::{run, EventLoopMode, GUI};
|
pub use runner::{run, EventLoopMode, GUI};
|
||||||
pub use text::TextOSD;
|
pub use text::TextOSD;
|
||||||
|
94
ezgui/src/log_scroller.rs
Normal file
94
ezgui/src/log_scroller.rs
Normal file
@ -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<String>,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user