prototype new speed panel. adapting changes from

https://github.com/jinzhong2/abstreet/tree/new_speed_panel
This commit is contained in:
Dustin Carlino 2019-11-27 15:42:19 -08:00
parent e1156a6384
commit d553530ee7
15 changed files with 393 additions and 25 deletions

View File

@ -103,14 +103,14 @@ impl Slider {
let mut batch = GeomBatch::new();
// A nice background for the entire thing
batch.push(
/*batch.push(
Color::grey(0.3),
Polygon::rectangle_topleft(
self.top_left.to_pt(),
Distance::meters(self.dims.total_width),
Distance::meters(self.dims.bar_height + 2.0 * self.dims.vert_padding),
),
);
);*/
g.canvas.mark_covered_area(ScreenRectangle {
x1: self.top_left.x,
y1: self.top_left.y,
@ -206,9 +206,9 @@ impl Dims {
fn fit_total_width(total_width: f64) -> Dims {
let horiz_padding = total_width / 7.0;
let bar_width = total_width - 2.0 * horiz_padding;
let slider_width = bar_width / 6.0;
let slider_width = 15.0;
let bar_height = bar_width / 3.0;
let bar_height = 25.0;
let slider_height = bar_height * 1.2;
let vert_padding = bar_height / 5.0;

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 754 B

After

Width:  |  Height:  |  Size: 705 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 713 B

After

Width:  |  Height:  |  Size: 808 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 781 B

After

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 240 B

BIN
game/assets/ui/sunrise.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

BIN
game/assets/ui/sunset.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 B

View File

@ -51,7 +51,7 @@ impl CommonState {
(hotkey(Key::K), "navigate"),
(hotkey(Key::SingleQuote), "shortcuts"),
],
0.25,
0.35,
ctx,
),
}

View File

@ -2,9 +2,10 @@ mod bus_explorer;
mod gameplay;
mod overlays;
mod score;
mod speed;
use self::overlays::Overlays;
use crate::common::{time_controls, AgentTools, CommonState, Minimap, SpeedControls};
use crate::common::{AgentTools, CommonState, Minimap};
use crate::debug::DebugMode;
use crate::edit::EditMode;
use crate::edit::{apply_map_edits, save_edits};
@ -24,7 +25,7 @@ use map_model::MapEdits;
use sim::Sim;
pub struct SandboxMode {
speed: SpeedControls,
speed: speed::SpeedControls,
info_tools: MenuUnderButton,
general_tools: MenuUnderButton,
save_tools: MenuUnderButton,
@ -39,7 +40,7 @@ pub struct SandboxMode {
impl SandboxMode {
pub fn new(ctx: &mut EventCtx, ui: &mut UI, mode: GameplayMode) -> SandboxMode {
SandboxMode {
speed: SpeedControls::new(ctx, ui.primary.current_flags.dev, true),
speed: speed::SpeedControls::new(ctx, ui.primary.current_flags.dev),
general_tools: MenuUnderButton::new(
"assets/ui/hamburger.png",
"General",
@ -48,7 +49,7 @@ impl SandboxMode {
(lctrl(Key::D), "debug mode"),
(hotkey(Key::F1), "take a screenshot"),
],
0.2,
0.3,
ctx,
),
info_tools: MenuUnderButton::new(
@ -60,7 +61,7 @@ impl SandboxMode {
(hotkey(Key::Semicolon), "change agent colorscheme"),
(None, "explore a bus route"),
],
0.3,
0.4,
ctx,
),
save_tools: MenuUnderButton::new(
@ -72,7 +73,7 @@ impl SandboxMode {
(hotkey(Key::U), "load next sim state"),
(None, "pick a savestate to load"),
],
0.35,
0.45,
ctx,
),
agent_tools: AgentTools::new(),
@ -94,10 +95,6 @@ impl State for SandboxMode {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition {
{
let mut txt = Text::new();
txt.add(Line(format!(
"Time: {}",
ui.primary.sim.time().ampm_tostring()
)));
let (active, unfinished, buses) = ui.primary.sim.num_trips();
txt.add(Line(format!("{} active (+{} buses)", active, buses)));
txt.add(Line(format!("{} unfinished", unfinished)));
@ -311,15 +308,7 @@ impl State for SandboxMode {
return Transition::Push(WizardState::new(Box::new(load_savestate)));
}
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
.sim
.time_limited_step(&ui.primary.map, dt, Duration::seconds(0.1));
ui.recalculate_current_selection(ctx);
}
if let Some(t) = time_controls(ctx, ui, &mut self.speed) {
if let Some(t) = self.speed.event(ctx, ui) {
return t;
}
@ -361,7 +350,7 @@ impl State for SandboxMode {
}
self.common.draw(g, ui);
self.menu.draw(g);
self.speed.draw(g);
self.speed.draw(g, ui);
self.info_tools.draw(g);
self.general_tools.draw(g);
self.save_tools.draw(g);

377
game/src/sandbox/speed.rs Normal file
View File

@ -0,0 +1,377 @@
use crate::game::{Transition, WizardState};
use crate::ui::UI;
use ezgui::layout::Widget;
use ezgui::{
hotkey, Button, Color, Drawable, EventCtx, GeomBatch, GfxCtx, JustDraw, Key, Line, ScreenPt,
ScreenRectangle, Slider, Text, Wizard,
};
use geom::{Distance, Duration, Line, Polygon, Pt2D, Time};
use std::time::Instant;
// TODO Everything in here is terrible. Layouting sucks.
const PANEL_RECT: ScreenRectangle = ScreenRectangle {
x1: 0.0,
y1: 0.0,
x2: 460.0,
y2: 200.0,
};
const ADJUST_SPEED_PERCENT: f64 = 0.01;
pub struct SpeedControls {
slider: Slider,
state: State,
speed_cap: f64,
panel_bg: Drawable,
resume_btn: Button,
pause_btn: Button,
slow_down_btn: Button,
speed_up_btn: Button,
small_step_btn: Button,
large_step_btn: Button,
jump_to_time_btn: Button,
sunrise: JustDraw,
sunset: JustDraw,
}
enum State {
Paused,
Running {
last_step: Instant,
speed_description: String,
last_measurement: Instant,
last_measurement_sim: Time,
},
}
impl SpeedControls {
pub fn new(ctx: &mut EventCtx, dev_mode: bool) -> SpeedControls {
let mut panel_bg = GeomBatch::new();
panel_bg.push(
Color::grey(0.3),
Polygon::rectangle_topleft(
Pt2D::new(PANEL_RECT.x1, PANEL_RECT.y1),
Distance::meters(PANEL_RECT.x2 - PANEL_RECT.x1),
Distance::meters(PANEL_RECT.y2 - PANEL_RECT.y1),
),
);
let resume_btn = Button::rectangle_img_no_bg(
"assets/ui/resume.png",
//"resume",
hotkey(Key::Space),
ctx,
)
.at(ScreenPt::new(10.0, 10.0));
let pause_btn = Button::rectangle_img_no_bg(
"assets/ui/pause.png",
//"pause",
hotkey(Key::Space),
ctx,
)
.at(ScreenPt::new(10.0, 10.0));
let jump_to_time_btn = Button::rectangle_img_no_bg(
"assets/ui/jump_to_time.png",
//"jump to specific time",
hotkey(Key::B),
ctx,
)
.at(ScreenPt::new(405.0, 10.0));
let small_step_btn = Button::rectangle_img_no_bg(
"assets/ui/small_step.png",
//"step forwards 0.1s",
hotkey(Key::M),
ctx,
)
.at(ScreenPt::new(380.0, 70.0));
let large_step_btn = Button::rectangle_img_no_bg(
"assets/ui/large_step.png",
//"step forwards 10 mins",
hotkey(Key::N),
ctx,
)
.at(ScreenPt::new(380.0, 100.0));
let mut sunrise = JustDraw::image("assets/ui/sunrise.png", ctx);
sunrise.set_pos(ScreenPt::new(92.0, 120.0));
let mut sunset = JustDraw::image("assets/ui/sunset.png", ctx);
sunset.set_pos(ScreenPt::new(254.0, 120.0));
// 10 sim minutes / real second normally, or 1 sim hour / real second for dev mode
let speed_cap: f64 = if dev_mode { 3600.0 } else { 600.0 };
let mut slider = Slider::new(270.0);
// Start with speed=1.0
slider.set_percent(ctx, (speed_cap / 1.0).powf(-1.0 / std::f64::consts::E));
slider.set_pos(ScreenPt::new(50.0, 150.0));
let slow_down_btn = Button::rectangle_img_no_bg(
"assets/ui/slow_down.png",
//"slow down",
hotkey(Key::LeftBracket),
ctx,
)
.at(ScreenPt::new(290.0, 170.0));
let speed_up_btn = Button::rectangle_img_no_bg(
"assets/ui/speed_up.png",
//"speed up",
hotkey(Key::RightBracket),
ctx,
)
.at(ScreenPt::new(425.0, 160.0));
SpeedControls {
slider,
state: State::Paused,
speed_cap,
panel_bg: panel_bg.upload(ctx),
resume_btn,
pause_btn,
slow_down_btn,
speed_up_btn,
small_step_btn,
large_step_btn,
jump_to_time_btn,
sunrise,
sunset,
}
}
pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Option<Transition> {
self.slow_down_btn.event(ctx);
self.speed_up_btn.event(ctx);
let desired_speed = self.desired_speed();
if self.speed_up_btn.clicked() && desired_speed != self.speed_cap {
self.slider.set_percent(
ctx,
(self.slider.get_percent() + ADJUST_SPEED_PERCENT).min(1.0),
);
} else if self.slow_down_btn.clicked() && desired_speed != 0.0 {
self.slider.set_percent(
ctx,
(self.slider.get_percent() - ADJUST_SPEED_PERCENT).max(0.0),
);
} else if self.slider.event(ctx) {
// Keep going
}
match self.state {
State::Paused => {
self.resume_btn.event(ctx);
if self.resume_btn.clicked() {
let now = Instant::now();
self.state = State::Running {
last_step: now,
speed_description: "...".to_string(),
last_measurement: now,
last_measurement_sim: ui.primary.sim.time(),
};
return None;
}
}
State::Running {
ref mut last_step,
ref mut speed_description,
ref mut last_measurement,
ref mut last_measurement_sim,
} => {
self.pause_btn.event(ctx);
if self.pause_btn.clicked() {
self.pause();
} else if ctx.input.nonblocking_is_update_event() {
ctx.input.use_update_event();
let dt = Duration::realtime_elapsed(*last_step) * desired_speed;
*last_step = Instant::now();
let dt_descr = Duration::realtime_elapsed(*last_measurement);
if dt_descr >= Duration::seconds(1.0) {
*speed_description = format!(
"{:.2}x",
(ui.primary.sim.time() - *last_measurement_sim) / dt_descr
);
*last_measurement = *last_step;
*last_measurement_sim = 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
.sim
.time_limited_step(&ui.primary.map, dt, Duration::seconds(0.1));
ui.recalculate_current_selection(ctx);
}
}
}
self.small_step_btn.event(ctx);
if self.small_step_btn.clicked() {
ui.primary.sim.step(&ui.primary.map, Duration::seconds(0.1));
if let Some(ref mut s) = ui.secondary {
s.sim.step(&s.map, Duration::seconds(0.1));
}
ui.recalculate_current_selection(ctx);
}
self.large_step_btn.event(ctx);
if self.large_step_btn.clicked() {
ctx.loading_screen("step forwards 10 minutes", |_, mut timer| {
ui.primary
.sim
.timed_step(&ui.primary.map, Duration::minutes(10), &mut timer);
if let Some(ref mut s) = ui.secondary {
s.sim.timed_step(&s.map, Duration::minutes(10), &mut timer);
}
});
ui.recalculate_current_selection(ctx);
}
self.jump_to_time_btn.event(ctx);
if self.jump_to_time_btn.clicked() {
return Some(Transition::Push(WizardState::new(Box::new(jump_to_time))));
}
None
}
pub fn draw(&self, g: &mut GfxCtx, ui: &UI) {
g.fork_screenspace();
g.redraw(&self.panel_bg);
g.canvas.mark_covered_area(PANEL_RECT);
g.unfork();
// Row 1
if self.is_paused() {
self.resume_btn.draw(g);
} else {
self.pause_btn.draw(g);
}
g.draw_text_at_screenspace_topleft(
&Text::from(Line(ui.primary.sim.time().ampm_tostring()).size(40)).no_bg(),
ScreenPt::new(90.0, 15.0),
);
self.jump_to_time_btn.draw(g);
// Row 2
// TODO Actual slider
{
let y1 = 90.0;
let percent = ui.primary.sim.time().to_percent(Time::END_OF_DAY);
let width = 350.0;
let height = Distance::meters(30.0);
g.fork_screenspace();
// TODO rounded
g.draw_polygon(
Color::WHITE,
&Line::new(Pt2D::new(10.0, y1), Pt2D::new(10.0 + width, y1)).make_polygons(height),
);
if let Some(l) =
Line::maybe_new(Pt2D::new(10.0, y1), Pt2D::new(10.0 + percent * width, y1))
{
g.draw_polygon(Color::grey(0.5), &l.make_polygons(height));
}
g.unfork();
}
self.small_step_btn.draw(g);
self.large_step_btn.draw(g);
// Row 3
{
// time slider is x = 10 to 360
let y1 = 120.0;
g.draw_text_at_screenspace_topleft(
&Text::from(Line("00:00").size(18)).no_bg(),
ScreenPt::new(10.0, y1),
);
self.sunrise.draw(g);
g.draw_text_at_screenspace_topleft(
&Text::from(Line("12:00").size(18)).no_bg(),
ScreenPt::new(175.0, y1),
);
self.sunset.draw(g);
g.draw_text_at_screenspace_topleft(
&Text::from(Line("24:00").size(18)).no_bg(),
ScreenPt::new(360.0, y1),
);
}
// Row 4
{
let y1 = 150.0;
g.fork_screenspace();
g.draw_polygon(
Color::grey(0.5),
&Polygon::rounded_rectangle(
Distance::meters(0.95 * (PANEL_RECT.x2 - PANEL_RECT.x1)),
Distance::meters(40.0),
Distance::meters(5.0),
)
.translate(10.0, y1),
);
g.unfork();
g.draw_text_at_screenspace_topleft(
&Text::from(Line("speed").size(25)).no_bg(),
ScreenPt::new(10.0, y1),
);
self.slider.draw(g);
self.slow_down_btn.draw(g);
// TODO Center this text
g.draw_text_at_screenspace_topleft(
&Text::from(Line(format!("{:.2}x", self.desired_speed())).size(25)).no_bg(),
ScreenPt::new(320.0, y1 + 5.0),
);
self.speed_up_btn.draw(g);
}
}
pub fn pause(&mut self) {
if !self.is_paused() {
self.state = State::Paused;
}
}
pub fn is_paused(&self) -> bool {
match self.state {
State::Paused => true,
State::Running { .. } => false,
}
}
fn desired_speed(&self) -> f64 {
self.speed_cap * self.slider.get_percent().powf(std::f64::consts::E)
}
}
fn jump_to_time(wiz: &mut Wizard, ctx: &mut EventCtx, ui: &mut UI) -> Option<Transition> {
let t = wiz.wrap(ctx).input_time_slider(
"Jump to what time?",
ui.primary.sim.time(),
Time::END_OF_DAY,
)?;
let dt = t - ui.primary.sim.time();
ctx.loading_screen(&format!("step forwards {}", dt), |_, mut timer| {
ui.primary.sim.timed_step(&ui.primary.map, dt, &mut timer);
if let Some(ref mut s) = ui.secondary {
s.sim.timed_step(&s.map, dt, &mut timer);
}
});
Some(Transition::Pop)
}

View File

@ -50,6 +50,8 @@ impl UI {
("assets/ui/slow_down.png", TextureType::Stretch),
("assets/ui/small_step.png", TextureType::Stretch),
("assets/ui/speed_up.png", TextureType::Stretch),
("assets/ui/sunrise.png", TextureType::Stretch),
("assets/ui/sunset.png", TextureType::Stretch),
];
let skip_textures = if flags.textures {
textures.extend(vec![