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
|
||||
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
|
||||
|
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 geom_validation;
|
||||
pub mod hider;
|
||||
pub mod logs;
|
||||
pub mod road_editor;
|
||||
pub mod search;
|
||||
pub mod show_route;
|
||||
|
@ -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<usize>,
|
||||
@ -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),
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
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