mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-25 07:25:47 +03:00
hack in a way for ModalMenu to work alongside a slider. use in
SpeedControls.
This commit is contained in:
parent
47ccf2c70b
commit
1df7c580f3
@ -20,8 +20,8 @@ pub use crate::runner::{run, EventLoopMode, GUI};
|
||||
pub use crate::screen_geom::{ScreenDims, ScreenPt, ScreenRectangle};
|
||||
pub use crate::text::{Line, Text, TextSpan, HOTKEY_COLOR};
|
||||
pub use crate::widgets::{
|
||||
Autocomplete, Choice, ItemSlider, ModalMenu, Scroller, Slider, SliderWithTextBox, Warper,
|
||||
WarpingItemSlider, Wizard, WrappedWizard,
|
||||
Autocomplete, Choice, ItemSlider, ModalMenu, Scroller, SidebarPos, Slider, SliderWithTextBox,
|
||||
Warper, WarpingItemSlider, Wizard, WrappedWizard,
|
||||
};
|
||||
|
||||
pub enum InputResult<T: Clone> {
|
||||
|
@ -11,7 +11,7 @@ mod wizard;
|
||||
|
||||
pub use self::autocomplete::Autocomplete;
|
||||
pub use self::menu::{Menu, Position};
|
||||
pub use self::modal_menu::ModalMenu;
|
||||
pub use self::modal_menu::{ModalMenu, SidebarPos};
|
||||
pub(crate) use self::screenshot::{screenshot_current, screenshot_everything};
|
||||
pub use self::scroller::Scroller;
|
||||
pub use self::slider::{ItemSlider, Slider, SliderWithTextBox, WarpingItemSlider};
|
||||
|
@ -1,10 +1,18 @@
|
||||
use crate::widgets::{Menu, Position};
|
||||
use crate::{EventCtx, GfxCtx, InputResult, MultiKey, Text};
|
||||
use crate::{EventCtx, GfxCtx, InputResult, MultiKey, ScreenPt, Slider, Text};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum SidebarPos {
|
||||
Left,
|
||||
Right,
|
||||
At(ScreenPt),
|
||||
}
|
||||
|
||||
pub struct ModalMenu {
|
||||
menu: Menu<()>,
|
||||
chosen_action: Option<String>,
|
||||
choice_groups: Vec<Vec<(Option<MultiKey>, String, ())>>,
|
||||
pos: SidebarPos,
|
||||
}
|
||||
|
||||
impl ModalMenu {
|
||||
@ -22,12 +30,13 @@ impl ModalMenu {
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
let pos = SidebarPos::Right;
|
||||
let mut menu = Menu::new(
|
||||
Text::prompt(prompt_line),
|
||||
choice_groups.clone(),
|
||||
false,
|
||||
true,
|
||||
Position::TopRightOfScreen,
|
||||
pos.pos(),
|
||||
ctx.canvas,
|
||||
);
|
||||
menu.mark_all_inactive();
|
||||
@ -38,9 +47,21 @@ impl ModalMenu {
|
||||
menu,
|
||||
chosen_action: None,
|
||||
choice_groups,
|
||||
pos,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_prompt(mut self, ctx: &mut EventCtx, prompt: Text) -> ModalMenu {
|
||||
self.menu.change_prompt(prompt, ctx.canvas);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_pos(mut self, ctx: &mut EventCtx, pos: SidebarPos) -> ModalMenu {
|
||||
self.pos = pos;
|
||||
self.rebuild_menu(ctx);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn handle_event(&mut self, ctx: &mut EventCtx, new_prompt: Option<Text>) {
|
||||
if let Some(ref action) = self.chosen_action {
|
||||
panic!("Caller didn't consume modal action '{}'", action);
|
||||
@ -63,11 +84,6 @@ impl ModalMenu {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_prompt(mut self, ctx: &mut EventCtx, prompt: Text) -> ModalMenu {
|
||||
self.menu.change_prompt(prompt, ctx.canvas);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_action(&mut self, key: Option<MultiKey>, name: &str, ctx: &mut EventCtx) {
|
||||
self.choice_groups
|
||||
.last_mut()
|
||||
@ -115,7 +131,7 @@ impl ModalMenu {
|
||||
self.choice_groups.clone(),
|
||||
false,
|
||||
true,
|
||||
Position::TopRightOfScreen,
|
||||
self.pos.pos(),
|
||||
ctx.canvas,
|
||||
);
|
||||
menu.mark_all_inactive();
|
||||
@ -127,3 +143,18 @@ impl ModalMenu {
|
||||
self.menu = menu;
|
||||
}
|
||||
}
|
||||
|
||||
impl SidebarPos {
|
||||
fn pos(&self) -> Position {
|
||||
match self {
|
||||
SidebarPos::Left => Position::SomeCornerAt(ScreenPt::new(0.0, 0.0)),
|
||||
SidebarPos::Right => Position::TopRightOfScreen,
|
||||
SidebarPos::At(pt) => Position::SomeCornerAt(*pt),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Assumes the slider never moves
|
||||
pub fn below(slider: &Slider) -> SidebarPos {
|
||||
SidebarPos::At(slider.below_top_left())
|
||||
}
|
||||
}
|
||||
|
@ -15,17 +15,16 @@ const HORIZ_PADDING: f64 = 60.0;
|
||||
const VERT_PADDING: f64 = 20.0;
|
||||
|
||||
pub struct Slider {
|
||||
top_left: ScreenPt,
|
||||
pub top_left: ScreenPt,
|
||||
current_percent: f64,
|
||||
mouse_on_slider: bool,
|
||||
dragging: bool,
|
||||
}
|
||||
|
||||
impl Slider {
|
||||
// TODO Easier placement options.
|
||||
pub fn new(top_left_at: Option<ScreenPt>) -> Slider {
|
||||
pub fn new(top_left: ScreenPt) -> Slider {
|
||||
Slider {
|
||||
top_left: top_left_at.unwrap_or_else(|| ScreenPt::new(0.0, 0.0)),
|
||||
top_left,
|
||||
current_percent: 0.0,
|
||||
mouse_on_slider: false,
|
||||
dragging: false,
|
||||
@ -99,7 +98,7 @@ impl Slider {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx, label: Option<Text>) {
|
||||
pub fn draw(&self, g: &mut GfxCtx) {
|
||||
g.fork_screenspace();
|
||||
|
||||
// A nice background for the entire thing
|
||||
@ -155,16 +154,13 @@ impl Slider {
|
||||
},
|
||||
&self.slider_geom(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(ref txt) = label {
|
||||
g.draw_text_at_screenspace_topleft(
|
||||
txt,
|
||||
ScreenPt::new(
|
||||
self.top_left.x,
|
||||
self.top_left.y + BAR_HEIGHT + 2.0 * VERT_PADDING,
|
||||
),
|
||||
);
|
||||
}
|
||||
pub fn below_top_left(&self) -> ScreenPt {
|
||||
ScreenPt::new(
|
||||
self.top_left.x,
|
||||
self.top_left.y + BAR_HEIGHT + 2.0 * VERT_PADDING,
|
||||
)
|
||||
}
|
||||
|
||||
fn slider_geom(&self) -> Polygon {
|
||||
@ -216,7 +212,7 @@ impl<T> ItemSlider<T> {
|
||||
|
||||
ItemSlider {
|
||||
items,
|
||||
slider: Slider::new(None),
|
||||
slider: Slider::new(ScreenPt::new(0.0, 0.0)),
|
||||
menu: ModalMenu::new(menu_title, choices, ctx),
|
||||
|
||||
noun: noun.to_string(),
|
||||
@ -249,6 +245,7 @@ impl<T> ItemSlider<T> {
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx) {
|
||||
self.menu.draw(g);
|
||||
self.slider.draw(g);
|
||||
|
||||
let idx = self.slider.get_value(self.items.len());
|
||||
let mut txt = Text::from(Line(format!(
|
||||
@ -258,7 +255,7 @@ impl<T> ItemSlider<T> {
|
||||
abstutil::prettyprint_usize(self.items.len())
|
||||
)));
|
||||
txt.extend(&self.items[idx].1);
|
||||
self.slider.draw(g, Some(txt));
|
||||
g.draw_text_at_screenspace_topleft(&txt, self.slider.below_top_left());
|
||||
}
|
||||
|
||||
pub fn get(&self) -> (usize, &T) {
|
||||
@ -358,7 +355,7 @@ impl SliderWithTextBox {
|
||||
top_left.y -= (BAR_HEIGHT + 2.0 * VERT_PADDING + LINE_HEIGHT) / 2.0;
|
||||
|
||||
SliderWithTextBox {
|
||||
slider: Slider::new(Some(top_left)),
|
||||
slider: Slider::new(top_left),
|
||||
tb: TextBox::new(prompt, None),
|
||||
low,
|
||||
high,
|
||||
@ -401,6 +398,7 @@ impl SliderWithTextBox {
|
||||
}
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx) {
|
||||
self.slider.draw(g, Some(self.tb.get_text()));
|
||||
self.slider.draw(g);
|
||||
g.draw_text_at_screenspace_topleft(&self.tb.get_text(), self.slider.below_top_left());
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,8 @@ use crate::game::{State, Transition};
|
||||
use crate::render::MIN_ZOOM_FOR_DETAIL;
|
||||
use crate::ui::{PerMapUI, UI};
|
||||
use ezgui::{
|
||||
hotkey, lctrl, Color, EventCtx, EventLoopMode, GeomBatch, GfxCtx, Key, Line, ModalMenu, Text,
|
||||
hotkey, lctrl, Color, EventCtx, EventLoopMode, GeomBatch, GfxCtx, Key, Line, ModalMenu,
|
||||
ScreenPt, Text,
|
||||
};
|
||||
use geom::{Circle, Distance, Line, PolyLine};
|
||||
use map_model::{Map, LANE_THICKNESS};
|
||||
@ -37,9 +38,6 @@ impl ABTestMode {
|
||||
"A/B Test Mode",
|
||||
vec![
|
||||
vec![
|
||||
(hotkey(Key::LeftBracket), "slow down"),
|
||||
(hotkey(Key::RightBracket), "speed up"),
|
||||
(hotkey(Key::Space), "pause/resume"),
|
||||
(hotkey(Key::M), "step forwards 0.1s"),
|
||||
(hotkey(Key::N), "step forwards 10 mins"),
|
||||
(hotkey(Key::B), "jump to specific time"),
|
||||
@ -64,7 +62,7 @@ impl ABTestMode {
|
||||
],
|
||||
ctx,
|
||||
),
|
||||
speed: SpeedControls::new(ctx, None),
|
||||
speed: SpeedControls::new(ctx, ScreenPt::new(0.0, 0.0)),
|
||||
primary_agent_tools: AgentTools::new(),
|
||||
secondary_agent_tools: AgentTools::new(),
|
||||
diff_trip: None,
|
||||
@ -191,7 +189,7 @@ impl State for ABTestMode {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(dt) = self.speed.event(ctx, &mut self.menu, ui.primary.sim.time()) {
|
||||
if let Some(dt) = self.speed.event(ctx, ui.primary.sim.time()) {
|
||||
ui.primary.sim.step(&ui.primary.map, dt);
|
||||
{
|
||||
let s = ui.secondary.as_mut().unwrap();
|
||||
|
@ -1,5 +1,5 @@
|
||||
use abstutil::elapsed_seconds;
|
||||
use ezgui::{EventCtx, GfxCtx, Line, ModalMenu, ScreenPt, Slider, Text};
|
||||
use ezgui::{hotkey, EventCtx, GfxCtx, Key, Line, ModalMenu, ScreenPt, SidebarPos, Slider, Text};
|
||||
use geom::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
@ -9,6 +9,7 @@ const SPEED_CAP: f64 = 10.0 * 60.0;
|
||||
|
||||
pub struct SpeedControls {
|
||||
slider: Slider,
|
||||
menu: ModalMenu,
|
||||
state: State,
|
||||
}
|
||||
|
||||
@ -23,27 +24,54 @@ enum State {
|
||||
}
|
||||
|
||||
impl SpeedControls {
|
||||
pub fn new(ctx: &mut EventCtx, top_left_at: Option<ScreenPt>) -> SpeedControls {
|
||||
pub fn new(ctx: &mut EventCtx, top_left_at: ScreenPt) -> SpeedControls {
|
||||
let mut slider = Slider::new(top_left_at);
|
||||
slider.set_percent(ctx, 1.0 / SPEED_CAP);
|
||||
|
||||
let menu = ModalMenu::new(
|
||||
"Speed",
|
||||
vec![vec![
|
||||
(hotkey(Key::LeftBracket), "slow down"),
|
||||
(hotkey(Key::RightBracket), "speed up"),
|
||||
(hotkey(Key::Space), "pause/resume"),
|
||||
]],
|
||||
ctx,
|
||||
)
|
||||
.set_pos(ctx, SidebarPos::below(&slider));
|
||||
|
||||
SpeedControls {
|
||||
slider,
|
||||
menu,
|
||||
state: State::Paused,
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the amount of simulation time to step, if running.
|
||||
pub fn event(
|
||||
&mut self,
|
||||
ctx: &mut EventCtx,
|
||||
menu: &mut ModalMenu,
|
||||
current_sim_time: Duration,
|
||||
) -> Option<Duration> {
|
||||
pub fn event(&mut self, ctx: &mut EventCtx, current_sim_time: Duration) -> Option<Duration> {
|
||||
let mut txt = Text::prompt("Speed");
|
||||
if let State::Running {
|
||||
ref speed_description,
|
||||
..
|
||||
} = self.state
|
||||
{
|
||||
txt.add(Line(format!(
|
||||
"{} / desired {:.2}x",
|
||||
speed_description,
|
||||
self.desired_speed()
|
||||
)));
|
||||
} else {
|
||||
txt.add(Line(format!(
|
||||
"paused / desired {:.2}x",
|
||||
self.desired_speed()
|
||||
)));
|
||||
}
|
||||
self.menu.handle_event(ctx, Some(txt));
|
||||
|
||||
let desired_speed = self.desired_speed();
|
||||
if desired_speed != SPEED_CAP && menu.action("speed up") {
|
||||
if desired_speed != SPEED_CAP && self.menu.action("speed up") {
|
||||
self.slider
|
||||
.set_percent(ctx, ((desired_speed + ADJUST_SPEED) / SPEED_CAP).min(1.0));
|
||||
} else if desired_speed != 0.0 && menu.action("slow down") {
|
||||
} else if desired_speed != 0.0 && self.menu.action("slow down") {
|
||||
self.slider
|
||||
.set_percent(ctx, ((desired_speed - ADJUST_SPEED) / SPEED_CAP).max(0.0));
|
||||
} else if self.slider.event(ctx) {
|
||||
@ -52,7 +80,7 @@ impl SpeedControls {
|
||||
|
||||
match self.state {
|
||||
State::Paused => {
|
||||
if menu.action("pause/resume") {
|
||||
if self.menu.action("pause/resume") {
|
||||
let now = Instant::now();
|
||||
self.state = State::Running {
|
||||
last_step: now,
|
||||
@ -70,7 +98,7 @@ impl SpeedControls {
|
||||
ref mut last_measurement,
|
||||
ref mut last_measurement_sim,
|
||||
} => {
|
||||
if menu.action("pause/resume") {
|
||||
if self.menu.action("pause/resume") {
|
||||
self.state = State::Paused;
|
||||
} else if ctx.input.nonblocking_is_update_event() {
|
||||
ctx.input.use_update_event();
|
||||
@ -94,24 +122,8 @@ impl SpeedControls {
|
||||
}
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx) {
|
||||
let mut txt = Text::new();
|
||||
if let State::Running {
|
||||
ref speed_description,
|
||||
..
|
||||
} = self.state
|
||||
{
|
||||
txt.add(Line(format!(
|
||||
"Speed: {} / desired {:.2}x",
|
||||
speed_description,
|
||||
self.desired_speed()
|
||||
)));
|
||||
} else {
|
||||
txt.add(Line(format!(
|
||||
"Speed: paused / desired {:.2}x",
|
||||
self.desired_speed()
|
||||
)));
|
||||
}
|
||||
self.slider.draw(g, Some(txt));
|
||||
self.slider.draw(g);
|
||||
self.menu.draw(g);
|
||||
}
|
||||
|
||||
pub fn pause(&mut self) {
|
||||
|
@ -69,19 +69,14 @@ impl TripsVisualizer {
|
||||
(hotkey(Key::F), "goto start of day"),
|
||||
(hotkey(Key::L), "goto end of day"),
|
||||
],
|
||||
vec![
|
||||
(hotkey(Key::LeftBracket), "slow down"),
|
||||
(hotkey(Key::RightBracket), "speed up"),
|
||||
(hotkey(Key::Space), "pause/resume"),
|
||||
],
|
||||
vec![(hotkey(Key::Escape), "quit")],
|
||||
],
|
||||
ctx,
|
||||
),
|
||||
trips,
|
||||
time_slider: Slider::new(None),
|
||||
time_slider: Slider::new(ScreenPt::new(0.0, 0.0)),
|
||||
// TODO hardcoding placement...
|
||||
speed: SpeedControls::new(ctx, Some(ScreenPt::new(500.0, 0.0))),
|
||||
speed: SpeedControls::new(ctx, ScreenPt::new(500.0, 0.0)),
|
||||
active_trips: Vec::new(),
|
||||
}
|
||||
}
|
||||
@ -132,7 +127,7 @@ impl State for TripsVisualizer {
|
||||
self.time_slider.set_percent(ctx, 1.0);
|
||||
} else if self.time_slider.event(ctx) {
|
||||
// Value changed, fall-through
|
||||
} else if let Some(dt) = self.speed.event(ctx, &mut self.menu, time) {
|
||||
} else if let Some(dt) = self.speed.event(ctx, time) {
|
||||
// TODO Speed description is briefly weird when we jump backwards with the other
|
||||
// control.
|
||||
self.time_slider
|
||||
@ -187,8 +182,11 @@ impl State for TripsVisualizer {
|
||||
batch.draw(g);
|
||||
|
||||
self.menu.draw(g);
|
||||
self.time_slider
|
||||
.draw(g, Some(Text::from(Line(format!("At {}", time)))));
|
||||
self.time_slider.draw(g);
|
||||
g.draw_text_at_screenspace_topleft(
|
||||
&Text::from(Line(format!("At {}", time))),
|
||||
self.time_slider.below_top_left(),
|
||||
);
|
||||
self.speed.draw(g);
|
||||
CommonState::draw_osd(g, ui, &ui.primary.current_selection);
|
||||
}
|
||||
|
@ -13,7 +13,8 @@ use crate::game::{State, Transition, WizardState};
|
||||
use crate::helpers::ID;
|
||||
use crate::ui::{ShowEverything, UI};
|
||||
use ezgui::{
|
||||
hotkey, lctrl, Choice, EventCtx, EventLoopMode, GfxCtx, Key, Line, ModalMenu, Text, Wizard,
|
||||
hotkey, lctrl, Choice, EventCtx, EventLoopMode, GfxCtx, Key, Line, ModalMenu, ScreenPt, Text,
|
||||
Wizard,
|
||||
};
|
||||
use geom::Duration;
|
||||
use sim::Sim;
|
||||
@ -32,7 +33,7 @@ pub struct SandboxMode {
|
||||
impl SandboxMode {
|
||||
pub fn new(ctx: &mut EventCtx, ui: &UI) -> SandboxMode {
|
||||
SandboxMode {
|
||||
speed: SpeedControls::new(ctx, None),
|
||||
speed: SpeedControls::new(ctx, ScreenPt::new(0.0, 0.0)),
|
||||
agent_tools: AgentTools::new(),
|
||||
time_travel: time_travel::InactiveTimeTravel::new(),
|
||||
trip_stats: trip_stats::TripStats::new(
|
||||
@ -45,9 +46,6 @@ impl SandboxMode {
|
||||
"Sandbox Mode",
|
||||
vec![
|
||||
vec![
|
||||
(hotkey(Key::RightBracket), "speed up"),
|
||||
(hotkey(Key::LeftBracket), "slow down"),
|
||||
(hotkey(Key::Space), "pause/resume"),
|
||||
(hotkey(Key::M), "step forwards 0.1s"),
|
||||
(hotkey(Key::N), "step forwards 10 mins"),
|
||||
(hotkey(Key::B), "jump to specific time"),
|
||||
@ -164,7 +162,7 @@ impl State for SandboxMode {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(dt) = self.speed.event(ctx, &mut self.menu, ui.primary.sim.time()) {
|
||||
if let Some(dt) = self.speed.event(ctx, ui.primary.sim.time()) {
|
||||
// If speed is too high, don't be unresponsive for too long.
|
||||
// TODO This should probably match the ezgui framerate.
|
||||
ui.primary
|
||||
|
Loading…
Reference in New Issue
Block a user