mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-28 03:35:51 +03:00
Simplify the parking table using the new API. Closes #53
This commit is contained in:
parent
6a13ec0841
commit
023bee5954
@ -1,73 +1,30 @@
|
||||
use crate::app::App;
|
||||
use crate::game::{DrawBaselayer, State, Transition};
|
||||
use crate::info::{OpenTrip, Tab};
|
||||
use crate::sandbox::dashboards::table::make_table;
|
||||
use crate::sandbox::dashboards::table::{Col, Filter, Table};
|
||||
use crate::sandbox::dashboards::trip_table::preview_trip;
|
||||
use crate::sandbox::dashboards::DashTab;
|
||||
use crate::sandbox::SandboxMode;
|
||||
use abstutil::prettyprint_usize;
|
||||
use geom::Duration;
|
||||
use sim::{TripEndpoint, TripID, TripPhaseType};
|
||||
use widgetry::{
|
||||
Btn, Checkbox, EventCtx, Filler, GfxCtx, Line, Outcome, Panel, Text, TextExt, Widget,
|
||||
};
|
||||
use widgetry::{Checkbox, EventCtx, Filler, GfxCtx, Line, Outcome, Panel, Text, Widget};
|
||||
|
||||
const ROWS: usize = 20;
|
||||
|
||||
// TODO Mostly dupliclated code with trip_table. Find the right generalization.
|
||||
// TODO Compare all of these things before/after
|
||||
|
||||
pub struct ParkingOverhead {
|
||||
table: Table<Entry, Filters>,
|
||||
panel: Panel,
|
||||
opts: Options,
|
||||
}
|
||||
|
||||
struct Options {
|
||||
sort_by: SortBy,
|
||||
descending: bool,
|
||||
off_map_starts: bool,
|
||||
off_map_ends: bool,
|
||||
skip: usize,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
fn change(&mut self, value: SortBy) {
|
||||
self.skip = 0;
|
||||
if self.sort_by == value {
|
||||
self.descending = !self.descending;
|
||||
} else {
|
||||
self.sort_by = value;
|
||||
self.descending = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum SortBy {
|
||||
TotalDuration,
|
||||
DrivingDuration,
|
||||
ParkingDuration,
|
||||
WalkingDuration,
|
||||
PercentOverhead,
|
||||
}
|
||||
|
||||
impl ParkingOverhead {
|
||||
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
|
||||
let opts = Options {
|
||||
sort_by: SortBy::PercentOverhead,
|
||||
descending: true,
|
||||
off_map_starts: true,
|
||||
off_map_ends: true,
|
||||
skip: 0,
|
||||
};
|
||||
Box::new(ParkingOverhead {
|
||||
panel: make(ctx, app, &opts),
|
||||
opts,
|
||||
})
|
||||
let table = make_table(app);
|
||||
let panel = make_panel(ctx, app, &table);
|
||||
Box::new(ParkingOverhead { table, panel })
|
||||
}
|
||||
|
||||
fn recalc(&mut self, ctx: &mut EventCtx, app: &App) {
|
||||
let mut new = make(ctx, app, &self.opts);
|
||||
let mut new = make_panel(ctx, app, &self.table);
|
||||
new.restore(ctx, &self.panel);
|
||||
self.panel = new;
|
||||
}
|
||||
@ -76,60 +33,31 @@ impl ParkingOverhead {
|
||||
impl State for ParkingOverhead {
|
||||
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
match self.panel.event(ctx) {
|
||||
Outcome::Clicked(x) => match x.as_ref() {
|
||||
"Total duration" => {
|
||||
self.opts.change(SortBy::TotalDuration);
|
||||
Outcome::Clicked(x) => {
|
||||
if self.table.clicked(&x) {
|
||||
self.recalc(ctx, app);
|
||||
} else if let Ok(idx) = x.parse::<usize>() {
|
||||
let trip = TripID(idx);
|
||||
let person = app.primary.sim.trip_to_person(trip);
|
||||
return Transition::Multi(vec![
|
||||
Transition::Pop,
|
||||
Transition::ModifyState(Box::new(move |state, ctx, app| {
|
||||
let sandbox = state.downcast_mut::<SandboxMode>().unwrap();
|
||||
let mut actions = sandbox.contextual_actions();
|
||||
sandbox.controls.common.as_mut().unwrap().launch_info_panel(
|
||||
ctx,
|
||||
app,
|
||||
Tab::PersonTrips(person, OpenTrip::single(trip)),
|
||||
&mut actions,
|
||||
);
|
||||
})),
|
||||
]);
|
||||
} else {
|
||||
return DashTab::ParkingOverhead.transition(ctx, app, &x);
|
||||
}
|
||||
"Driving duration" => {
|
||||
self.opts.change(SortBy::DrivingDuration);
|
||||
self.recalc(ctx, app);
|
||||
}
|
||||
"Parking duration" => {
|
||||
self.opts.change(SortBy::ParkingDuration);
|
||||
self.recalc(ctx, app);
|
||||
}
|
||||
"Walking duration" => {
|
||||
self.opts.change(SortBy::WalkingDuration);
|
||||
self.recalc(ctx, app);
|
||||
}
|
||||
"Percent overhead" => {
|
||||
self.opts.change(SortBy::PercentOverhead);
|
||||
self.recalc(ctx, app);
|
||||
}
|
||||
"previous trips" => {
|
||||
self.opts.skip -= ROWS;
|
||||
self.recalc(ctx, app);
|
||||
}
|
||||
"next trips" => {
|
||||
self.opts.skip += ROWS;
|
||||
self.recalc(ctx, app);
|
||||
}
|
||||
x => {
|
||||
if let Ok(idx) = x.parse::<usize>() {
|
||||
let trip = TripID(idx);
|
||||
let person = app.primary.sim.trip_to_person(trip);
|
||||
return Transition::Multi(vec![
|
||||
Transition::Pop,
|
||||
Transition::ModifyState(Box::new(move |state, ctx, app| {
|
||||
let sandbox = state.downcast_mut::<SandboxMode>().unwrap();
|
||||
let mut actions = sandbox.contextual_actions();
|
||||
sandbox.controls.common.as_mut().unwrap().launch_info_panel(
|
||||
ctx,
|
||||
app,
|
||||
Tab::PersonTrips(person, OpenTrip::single(trip)),
|
||||
&mut actions,
|
||||
);
|
||||
})),
|
||||
]);
|
||||
}
|
||||
return DashTab::ParkingOverhead.transition(ctx, app, x);
|
||||
}
|
||||
},
|
||||
}
|
||||
Outcome::Changed => {
|
||||
self.opts.off_map_starts = self.panel.is_checked("starting off-map");
|
||||
self.opts.off_map_ends = self.panel.is_checked("ending off-map");
|
||||
self.opts.skip = 0;
|
||||
self.table.panel_changed(&self.panel);
|
||||
self.recalc(ctx, app);
|
||||
}
|
||||
_ => {}
|
||||
@ -156,23 +84,28 @@ struct Entry {
|
||||
parking_duration: Duration,
|
||||
walking_duration: Duration,
|
||||
percent_overhead: usize,
|
||||
starts_off_map: bool,
|
||||
ends_off_map: bool,
|
||||
}
|
||||
|
||||
fn make(ctx: &mut EventCtx, app: &App, opts: &Options) -> Panel {
|
||||
struct Filters {
|
||||
starts_off_map: bool,
|
||||
ends_off_map: bool,
|
||||
}
|
||||
|
||||
fn produce_raw_data(app: &App) -> Vec<Entry> {
|
||||
// Gather raw data
|
||||
let mut data = Vec::new();
|
||||
for (id, phases) in app.primary.sim.get_analytics().get_all_trip_phases() {
|
||||
let trip = app.primary.sim.trip_info(id);
|
||||
if !opts.off_map_starts {
|
||||
if let TripEndpoint::Border(_, _) = trip.start {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if !opts.off_map_ends {
|
||||
if let TripEndpoint::Border(_, _) = trip.end {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let starts_off_map = match trip.start {
|
||||
TripEndpoint::Border(_, _) => true,
|
||||
_ => false,
|
||||
};
|
||||
let ends_off_map = match trip.end {
|
||||
TripEndpoint::Border(_, _) => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let mut total_duration = Duration::ZERO;
|
||||
let mut driving_duration = Duration::ZERO;
|
||||
@ -211,59 +144,77 @@ fn make(ctx: &mut EventCtx, app: &App, opts: &Options) -> Panel {
|
||||
parking_duration,
|
||||
walking_duration,
|
||||
percent_overhead: (100.0 * (1.0 - (driving_duration / total_duration))) as usize,
|
||||
starts_off_map,
|
||||
ends_off_map,
|
||||
});
|
||||
}
|
||||
data
|
||||
}
|
||||
|
||||
// Sort
|
||||
match opts.sort_by {
|
||||
SortBy::TotalDuration => data.sort_by_key(|x| x.total_duration),
|
||||
SortBy::DrivingDuration => data.sort_by_key(|x| x.driving_duration),
|
||||
SortBy::ParkingDuration => data.sort_by_key(|x| x.parking_duration),
|
||||
SortBy::WalkingDuration => data.sort_by_key(|x| x.walking_duration),
|
||||
SortBy::PercentOverhead => data.sort_by_key(|x| x.percent_overhead),
|
||||
}
|
||||
if opts.descending {
|
||||
data.reverse();
|
||||
}
|
||||
let total_rows = data.len();
|
||||
|
||||
// Render data
|
||||
let mut rows = Vec::new();
|
||||
for x in data.into_iter().skip(opts.skip).take(ROWS) {
|
||||
rows.push((
|
||||
x.trip.0.to_string(),
|
||||
vec![
|
||||
Text::from(Line(x.trip.0.to_string())).render_ctx(ctx),
|
||||
Text::from(Line(x.total_duration.to_string())).render_ctx(ctx),
|
||||
Text::from(Line(x.driving_duration.to_string())).render_ctx(ctx),
|
||||
Text::from(Line(x.parking_duration.to_string())).render_ctx(ctx),
|
||||
Text::from(Line(x.walking_duration.to_string())).render_ctx(ctx),
|
||||
Text::from(Line(format!("{}%", x.percent_overhead))).render_ctx(ctx),
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
let btn = |value, name| {
|
||||
if opts.sort_by == value {
|
||||
Btn::text_bg2(format!(
|
||||
"{} {}",
|
||||
name,
|
||||
if opts.descending { "↓" } else { "↑" }
|
||||
))
|
||||
.build(ctx, name, None)
|
||||
} else {
|
||||
Btn::text_bg2(name).build_def(ctx, None)
|
||||
}
|
||||
fn make_table(app: &App) -> Table<Entry, Filters> {
|
||||
let filter: Filter<Entry, Filters> = Filter {
|
||||
state: Filters {
|
||||
starts_off_map: true,
|
||||
ends_off_map: true,
|
||||
},
|
||||
to_controls: Box::new(move |ctx, _, state| {
|
||||
Widget::row(vec![
|
||||
Checkbox::switch(ctx, "starting off-map", None, state.starts_off_map),
|
||||
Checkbox::switch(ctx, "ending off-map", None, state.ends_off_map),
|
||||
])
|
||||
}),
|
||||
from_controls: Box::new(|panel| Filters {
|
||||
starts_off_map: panel.is_checked("starting off-map"),
|
||||
ends_off_map: panel.is_checked("ending off-map"),
|
||||
}),
|
||||
apply: Box::new(|state, x| {
|
||||
if !state.starts_off_map && x.starts_off_map {
|
||||
return false;
|
||||
}
|
||||
if !state.ends_off_map && x.ends_off_map {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}),
|
||||
};
|
||||
let headers = vec![
|
||||
Line("Trip ID").draw(ctx),
|
||||
btn(SortBy::TotalDuration, "Total duration"),
|
||||
btn(SortBy::DrivingDuration, "Driving duration"),
|
||||
btn(SortBy::ParkingDuration, "Parking duration"),
|
||||
btn(SortBy::WalkingDuration, "Walking duration"),
|
||||
btn(SortBy::PercentOverhead, "Percent overhead"),
|
||||
];
|
||||
|
||||
let mut table = Table::new(
|
||||
produce_raw_data(app),
|
||||
Box::new(|x| x.trip.0.to_string()),
|
||||
"Percent overhead",
|
||||
filter,
|
||||
);
|
||||
table.static_col("Trip ID", Box::new(|x| x.trip.0.to_string()));
|
||||
table.column(
|
||||
"Total duration",
|
||||
Box::new(|ctx, _, x| Text::from(Line(x.total_duration.to_string())).render_ctx(ctx)),
|
||||
Col::Sortable(Box::new(|rows| rows.sort_by_key(|x| x.total_duration))),
|
||||
);
|
||||
table.column(
|
||||
"Driving duration",
|
||||
Box::new(|ctx, _, x| Text::from(Line(x.driving_duration.to_string())).render_ctx(ctx)),
|
||||
Col::Sortable(Box::new(|rows| rows.sort_by_key(|x| x.driving_duration))),
|
||||
);
|
||||
table.column(
|
||||
"Parking duration",
|
||||
Box::new(|ctx, _, x| Text::from(Line(x.parking_duration.to_string())).render_ctx(ctx)),
|
||||
Col::Sortable(Box::new(|rows| rows.sort_by_key(|x| x.parking_duration))),
|
||||
);
|
||||
table.column(
|
||||
"Walking duration",
|
||||
Box::new(|ctx, _, x| Text::from(Line(x.walking_duration.to_string())).render_ctx(ctx)),
|
||||
Col::Sortable(Box::new(|rows| rows.sort_by_key(|x| x.walking_duration))),
|
||||
);
|
||||
table.column(
|
||||
"Percent overhead",
|
||||
Box::new(|ctx, _, x| Text::from(Line(format!("{}%", x.percent_overhead))).render_ctx(ctx)),
|
||||
Col::Sortable(Box::new(|rows| rows.sort_by_key(|x| x.percent_overhead))),
|
||||
);
|
||||
|
||||
table
|
||||
}
|
||||
|
||||
fn make_panel(ctx: &mut EventCtx, app: &App, table: &Table<Entry, Filters>) -> Panel {
|
||||
let mut col = vec![DashTab::ParkingOverhead.picker(ctx, app)];
|
||||
col.push(
|
||||
Widget::row(vec![
|
||||
@ -291,41 +242,7 @@ fn make(ctx: &mut EventCtx, app: &App, opts: &Options) -> Panel {
|
||||
])
|
||||
.evenly_spaced(),
|
||||
);
|
||||
col.push(Widget::row(vec![
|
||||
Checkbox::switch(ctx, "starting off-map", None, opts.off_map_starts),
|
||||
Checkbox::switch(ctx, "ending off-map", None, opts.off_map_ends),
|
||||
]));
|
||||
col.push(Widget::row(vec![
|
||||
if opts.skip > 0 {
|
||||
Btn::text_fg("<").build(ctx, "previous trips", None)
|
||||
} else {
|
||||
Btn::text_fg("<").inactive(ctx)
|
||||
},
|
||||
format!(
|
||||
"{}-{} of {}",
|
||||
if total_rows > 0 {
|
||||
prettyprint_usize(opts.skip + 1)
|
||||
} else {
|
||||
"0".to_string()
|
||||
},
|
||||
prettyprint_usize((opts.skip + 1 + ROWS).min(total_rows)),
|
||||
prettyprint_usize(total_rows)
|
||||
)
|
||||
.draw_text(ctx),
|
||||
if opts.skip + 1 + ROWS < total_rows {
|
||||
Btn::text_fg(">").build(ctx, "next trips", None)
|
||||
} else {
|
||||
Btn::text_fg(">").inactive(ctx)
|
||||
},
|
||||
]));
|
||||
|
||||
col.push(make_table(
|
||||
ctx,
|
||||
app,
|
||||
headers,
|
||||
rows,
|
||||
0.88 * ctx.canvas.window_width,
|
||||
));
|
||||
col.push(table.render(ctx, app));
|
||||
|
||||
Panel::new(Widget::col(col))
|
||||
.exact_size_percent(90, 90)
|
||||
|
@ -201,7 +201,7 @@ impl<T: 'static, F> Table<T, F> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_table(
|
||||
fn make_table(
|
||||
ctx: &mut EventCtx,
|
||||
app: &App,
|
||||
headers: Vec<Widget>,
|
||||
|
@ -286,30 +286,28 @@ fn make_table_finished_trips(app: &App) -> Table<FinishedTrip, Filters> {
|
||||
}
|
||||
}),
|
||||
apply: Box::new(|state, x| {
|
||||
// TODO One big boolean expression?
|
||||
let mut ok = true;
|
||||
if !state.modes.contains(&x.mode) {
|
||||
ok = false;
|
||||
return false;
|
||||
}
|
||||
if !state.off_map_starts && x.starts_off_map {
|
||||
ok = false;
|
||||
return false;
|
||||
}
|
||||
if !state.off_map_ends && x.ends_off_map {
|
||||
ok = false;
|
||||
return false;
|
||||
}
|
||||
if !state.unmodified_trips && !x.modified {
|
||||
ok = false;
|
||||
return false;
|
||||
}
|
||||
if !state.modified_trips && x.modified {
|
||||
ok = false;
|
||||
return false;
|
||||
}
|
||||
if !state.uncapped_trips && !x.capped {
|
||||
ok = false;
|
||||
return false;
|
||||
}
|
||||
if !state.capped_trips && x.capped {
|
||||
ok = false;
|
||||
return false;
|
||||
}
|
||||
ok
|
||||
true
|
||||
}),
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user