mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-28 12:12:00 +03:00
preserve plot settings over time. also dropdowns
This commit is contained in:
parent
5868fe4736
commit
ebf31c33e6
@ -77,8 +77,9 @@ impl App {
|
||||
Widget::col(col2).outline(3.0, Color::BLACK).margin(5),
|
||||
Widget::col(col3).outline(3.0, Color::BLACK).margin(5),
|
||||
]),
|
||||
Plot::new_usize(
|
||||
Plot::new(
|
||||
ctx,
|
||||
"timeseries",
|
||||
vec![
|
||||
Series {
|
||||
label: "Linear".to_string(),
|
||||
@ -114,11 +115,10 @@ impl App {
|
||||
.aligned(HorizontalAlignment::Percent(0.6), VerticalAlignment::Center)
|
||||
.build(ctx);
|
||||
|
||||
// Since we're creating an entirely new panel when the time changes, it's nice to keep the
|
||||
// scroll the same. If the new panel got shorter and the old scroll was too long, this
|
||||
// clamps reasonably.
|
||||
// Since we're creating an entirely new panel when the time changes, we need to preserve
|
||||
// some internal state, like scroll and whether plot checkboxes were enabled.
|
||||
if let Some((_, ref old)) = self.timeseries_panel {
|
||||
c.restore_scroll(ctx, old.preserve_scroll());
|
||||
c.restore(ctx, old);
|
||||
}
|
||||
c
|
||||
}
|
||||
@ -269,8 +269,8 @@ fn make_controls(ctx: &mut EventCtx) -> Composite {
|
||||
Btn::text_fg("Reset")
|
||||
.build(ctx, "reset the stopwatch", None)
|
||||
.margin(5),
|
||||
Checkbox::new(ctx, "Draw scrollable canvas", None, true).margin(5),
|
||||
Checkbox::new(ctx, "Show timeseries", lctrl(Key::T), false).margin(5),
|
||||
Checkbox::text(ctx, "Draw scrollable canvas", None, true).margin(5),
|
||||
Checkbox::text(ctx, "Show timeseries", lctrl(Key::T), false).margin(5),
|
||||
])
|
||||
.evenly_spaced(),
|
||||
"Stopwatch: ...".draw_text(ctx).named("stopwatch"),
|
||||
|
@ -379,6 +379,23 @@ impl Widget {
|
||||
}
|
||||
}
|
||||
|
||||
fn restore(&mut self, ctx: &mut EventCtx, prev: &Composite) {
|
||||
if let Some(container) = self.widget.downcast_mut::<Container>() {
|
||||
for w in &mut container.members {
|
||||
w.restore(ctx, prev);
|
||||
}
|
||||
} else if self.widget.can_restore() {
|
||||
self.widget.restore(
|
||||
ctx,
|
||||
&prev
|
||||
.top_level
|
||||
.find(self.id.as_ref().unwrap())
|
||||
.unwrap()
|
||||
.widget,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_btn(&self, name: &str) -> bool {
|
||||
self.widget
|
||||
.downcast_ref::<Button>()
|
||||
@ -658,12 +675,10 @@ impl Composite {
|
||||
actions
|
||||
}
|
||||
|
||||
pub fn preserve_scroll(&self) -> (f64, f64) {
|
||||
self.scroll_offset()
|
||||
}
|
||||
pub fn restore(&mut self, ctx: &mut EventCtx, prev: &Composite) {
|
||||
self.set_scroll_offset(ctx, prev.scroll_offset());
|
||||
|
||||
pub fn restore_scroll(&mut self, ctx: &mut EventCtx, offset: (f64, f64)) {
|
||||
self.set_scroll_offset(ctx, offset);
|
||||
self.top_level.restore(ctx, &prev);
|
||||
|
||||
// Since we just moved things around, let all widgets respond to the mouse being somewhere
|
||||
ctx.no_op_event(true, |ctx| assert!(self.event(ctx).is_none()));
|
||||
|
@ -12,7 +12,7 @@ pub struct Checkbox {
|
||||
}
|
||||
|
||||
impl Checkbox {
|
||||
// TODO Not typesafe! Gotta pass a button.
|
||||
// TODO Not typesafe! Gotta pass a button. Also, make sure to give an ID.
|
||||
pub fn new(enabled: bool, false_btn: Widget, true_btn: Widget) -> Widget {
|
||||
if enabled {
|
||||
Widget::new(Box::new(Checkbox {
|
||||
@ -72,4 +72,17 @@ impl WidgetImpl for Checkbox {
|
||||
fn draw(&self, g: &mut GfxCtx) {
|
||||
self.btn.draw(g);
|
||||
}
|
||||
|
||||
fn can_restore(&self) -> bool {
|
||||
// TODO I'm nervous about doing this one in general, so just do it for plot checkboxes.
|
||||
self.cb_to_plot.is_some()
|
||||
}
|
||||
fn restore(&mut self, _: &mut EventCtx, prev: &Box<dyn WidgetImpl>) {
|
||||
let prev = prev.downcast_ref::<Checkbox>().unwrap();
|
||||
if self.enabled != prev.enabled {
|
||||
std::mem::swap(&mut self.btn, &mut self.other_btn);
|
||||
self.btn.set_pos(self.other_btn.top_left);
|
||||
self.enabled = !self.enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,33 @@ impl<T: 'static + PartialEq + Clone> Dropdown<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static + Clone> Dropdown<T> {
|
||||
fn open_menu(&mut self, ctx: &mut EventCtx) {
|
||||
// TODO set current idx in menu
|
||||
let mut menu = Menu::new(
|
||||
ctx,
|
||||
self.choices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, c)| c.with_value(idx))
|
||||
.collect(),
|
||||
)
|
||||
.take_menu();
|
||||
let y1_below = self.btn.top_left.y + self.btn.dims.height + 15.0;
|
||||
|
||||
menu.set_pos(ScreenPt::new(
|
||||
self.btn.top_left.x,
|
||||
// top_left_for_corner doesn't quite work
|
||||
if y1_below + menu.get_dims().height < ctx.canvas.window_height {
|
||||
y1_below
|
||||
} else {
|
||||
self.btn.top_left.y - 15.0 - menu.get_dims().height
|
||||
},
|
||||
));
|
||||
self.menu = Some(menu);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static + Clone> WidgetImpl for Dropdown<T> {
|
||||
fn get_dims(&self) -> ScreenDims {
|
||||
self.btn.get_dims()
|
||||
@ -81,28 +108,7 @@ impl<T: 'static + Clone> WidgetImpl for Dropdown<T> {
|
||||
} else {
|
||||
self.btn.event(ctx, output);
|
||||
if output.outcome.take().is_some() {
|
||||
// TODO set current idx in menu
|
||||
let mut menu = Menu::new(
|
||||
ctx,
|
||||
self.choices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, c)| c.with_value(idx))
|
||||
.collect(),
|
||||
)
|
||||
.take_menu();
|
||||
let y1_below = self.btn.top_left.y + self.btn.dims.height + 15.0;
|
||||
|
||||
menu.set_pos(ScreenPt::new(
|
||||
self.btn.top_left.x,
|
||||
// top_left_for_corner doesn't quite work
|
||||
if y1_below + menu.get_dims().height < ctx.canvas.window_height {
|
||||
y1_below
|
||||
} else {
|
||||
self.btn.top_left.y - 15.0 - menu.get_dims().height
|
||||
},
|
||||
));
|
||||
self.menu = Some(menu);
|
||||
self.open_menu(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -137,6 +143,18 @@ impl<T: 'static + Clone> WidgetImpl for Dropdown<T> {
|
||||
.mark_covered_area(ScreenRectangle::top_left(m.top_left, m.get_dims()));
|
||||
}
|
||||
}
|
||||
|
||||
fn can_restore(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn restore(&mut self, ctx: &mut EventCtx, prev: &Box<dyn WidgetImpl>) {
|
||||
let prev = prev.downcast_ref::<Dropdown<T>>().unwrap();
|
||||
if prev.menu.is_some() {
|
||||
self.open_menu(ctx);
|
||||
// TODO Preserve menu hovered item. Only matters if we've moved the cursor off the
|
||||
// menu.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_btn(ctx: &EventCtx, name: &str, label: &str, blank_btn_label: bool) -> Button {
|
||||
|
@ -28,6 +28,16 @@ pub trait WidgetImpl: downcast_rs::Downcast {
|
||||
fn event(&mut self, ctx: &mut EventCtx, output: &mut WidgetOutput);
|
||||
/// Draw the widget. Be sure to draw relative to the top-left specified by `set_pos`.
|
||||
fn draw(&self, g: &mut GfxCtx);
|
||||
/// If a new Composite is being created to replace an older one, all widgets have the chance to
|
||||
/// preserve state from the previous version.
|
||||
fn can_restore(&self) -> bool {
|
||||
false
|
||||
}
|
||||
/// Restore state from the previous version of this widget, with the same ID. Implementors must
|
||||
/// downcast.
|
||||
fn restore(&mut self, _ctx: &mut EventCtx, _prev: &Box<dyn WidgetImpl>) {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
/// Internal hack. Don't override.
|
||||
fn update_series(&mut self, _label: String, _enabled: bool) {
|
||||
|
@ -64,6 +64,10 @@ impl<T: Yvalue<T>> Plot<T> {
|
||||
.take_checkbox()
|
||||
.callback_to_plot(id, &s.label),
|
||||
))
|
||||
// TODO Messy! We have to remember to repeat what Checkbox::text does,
|
||||
// because we used take_checkbox
|
||||
.named(&s.label)
|
||||
.outline(ctx.style().outline_thickness, ctx.style().outline_color)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
@ -294,6 +298,16 @@ impl<T: Yvalue<T>> WidgetImpl for Plot<T> {
|
||||
}
|
||||
panic!("Plot doesn't have a series {}", label);
|
||||
}
|
||||
|
||||
fn can_restore(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn restore(&mut self, _: &mut EventCtx, prev: &Box<dyn WidgetImpl>) {
|
||||
let prev = prev.downcast_ref::<Plot<T>>().unwrap();
|
||||
for (s1, s2) in self.series.iter_mut().zip(prev.series.iter()) {
|
||||
s1.enabled = s2.enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Yvalue<T>: 'static + Copy + std::cmp::Ord {
|
||||
|
@ -59,9 +59,9 @@ impl TrafficSignalEditor {
|
||||
|
||||
fn change_phase(&mut self, idx: usize, app: &App, ctx: &mut EventCtx) {
|
||||
if self.current_phase == idx {
|
||||
let preserve_scroll = self.composite.preserve_scroll();
|
||||
self.composite = make_signal_diagram(ctx, app, self.i, self.current_phase, true);
|
||||
self.composite.restore_scroll(ctx, preserve_scroll);
|
||||
let mut new = make_signal_diagram(ctx, app, self.i, self.current_phase, true);
|
||||
new.restore(ctx, &self.composite);
|
||||
self.composite = new;
|
||||
} else {
|
||||
self.current_phase = idx;
|
||||
self.composite = make_signal_diagram(ctx, app, self.i, self.current_phase, true);
|
||||
|
@ -342,9 +342,9 @@ impl InfoPanel {
|
||||
|
||||
// Live update?
|
||||
if app.primary.sim.time() != self.time || ctx_actions.is_paused() != self.is_paused {
|
||||
let preserve_scroll = self.composite.preserve_scroll();
|
||||
*self = InfoPanel::new(ctx, app, self.tab.clone(), ctx_actions);
|
||||
self.composite.restore_scroll(ctx, preserve_scroll);
|
||||
let mut new = InfoPanel::new(ctx, app, self.tab.clone(), ctx_actions);
|
||||
new.composite.restore(ctx, &self.composite);
|
||||
*self = new;
|
||||
return (false, None);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user