mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-27 15:03:20 +03:00
Move the LTN app header to a separate top panel.
This commit is contained in:
parent
353a05625c
commit
04f00b4c8a
@ -155,7 +155,7 @@ impl TimePanel {
|
||||
]))
|
||||
.aligned(HorizontalAlignment::Left, VerticalAlignment::Top);
|
||||
if let Some(h) = self.override_height {
|
||||
panel = panel.exact_height(h);
|
||||
panel = panel.exact_height_pixels(h);
|
||||
}
|
||||
self.panel = panel.build(ctx);
|
||||
}
|
||||
|
@ -7,15 +7,16 @@ use synthpop::Scenario;
|
||||
use widgetry::mapspace::{ToggleZoomed, World, WorldOutcome};
|
||||
use widgetry::tools::PopupMsg;
|
||||
use widgetry::{
|
||||
Choice, Color, DrawBaselayer, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel,
|
||||
State, Text, TextExt, Toggle, VerticalAlignment, Widget,
|
||||
Choice, Color, DrawBaselayer, EventCtx, GfxCtx, Key, Line, Outcome, Panel, State, Text,
|
||||
TextExt, Toggle, Widget,
|
||||
};
|
||||
|
||||
use crate::filters::auto::Heuristic;
|
||||
use crate::{App, Neighborhood, NeighborhoodID, Transition};
|
||||
|
||||
pub struct BrowseNeighborhoods {
|
||||
panel: Panel,
|
||||
top_panel: Panel,
|
||||
left_panel: Panel,
|
||||
world: World<NeighborhoodID>,
|
||||
draw_over_roads: ToggleZoomed,
|
||||
labels: DrawRoadLabels,
|
||||
@ -38,8 +39,7 @@ impl BrowseNeighborhoods {
|
||||
)
|
||||
});
|
||||
|
||||
let panel = Panel::new_builder(Widget::col(vec![
|
||||
crate::app_header(ctx, app),
|
||||
let left_panel = crate::common::left_panel_builder(Widget::col(vec![
|
||||
app.session.alt_proposals.to_widget(ctx, app),
|
||||
"Click a neighborhood to edit filters".text_widget(ctx),
|
||||
Widget::row(vec![
|
||||
@ -97,10 +97,10 @@ impl BrowseNeighborhoods {
|
||||
])])
|
||||
.section(ctx),
|
||||
]))
|
||||
.aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
|
||||
.build(ctx);
|
||||
Box::new(BrowseNeighborhoods {
|
||||
panel,
|
||||
top_panel: crate::common::app_top_panel(ctx, app),
|
||||
left_panel,
|
||||
world,
|
||||
draw_over_roads,
|
||||
labels: DrawRoadLabels::only_major_roads().light_background(),
|
||||
@ -111,7 +111,10 @@ impl BrowseNeighborhoods {
|
||||
|
||||
impl State<App> for BrowseNeighborhoods {
|
||||
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
match self.panel.event(ctx) {
|
||||
if let Some(t) = crate::common::handle_top_panel(ctx, app, &mut self.top_panel) {
|
||||
return t;
|
||||
}
|
||||
match self.left_panel.event(ctx) {
|
||||
Outcome::Clicked(x) => match x.as_ref() {
|
||||
"Export to GeoJSON" => {
|
||||
let result = crate::export::write_geojson_file(ctx, app);
|
||||
@ -151,9 +154,6 @@ impl State<App> for BrowseNeighborhoods {
|
||||
return Transition::Replace(BrowseNeighborhoods::new_state(ctx, app));
|
||||
}
|
||||
x => {
|
||||
if let Some(t) = crate::handle_app_header_click(ctx, app, x) {
|
||||
return t;
|
||||
}
|
||||
return crate::save::AltProposals::handle_action(
|
||||
ctx,
|
||||
app,
|
||||
@ -165,8 +165,8 @@ impl State<App> for BrowseNeighborhoods {
|
||||
},
|
||||
Outcome::Changed(_) => {
|
||||
app.session.highlight_boundary_roads =
|
||||
self.panel.is_checked("highlight boundary roads");
|
||||
app.session.draw_neighborhood_style = self.panel.dropdown_value("style");
|
||||
self.left_panel.is_checked("highlight boundary roads");
|
||||
app.session.draw_neighborhood_style = self.left_panel.dropdown_value("style");
|
||||
|
||||
ctx.loading_screen("change style", |ctx, timer| {
|
||||
self.world = make_world(ctx, app, timer);
|
||||
@ -191,8 +191,9 @@ impl State<App> for BrowseNeighborhoods {
|
||||
crate::draw_with_layering(g, app, |g| self.world.draw(g));
|
||||
self.draw_over_roads.draw(g);
|
||||
|
||||
self.panel.draw(g);
|
||||
if self.panel.is_checked("highlight boundary roads") {
|
||||
self.top_panel.draw(g);
|
||||
self.left_panel.draw(g);
|
||||
if self.left_panel.is_checked("highlight boundary roads") {
|
||||
self.draw_boundary_roads.draw(g);
|
||||
}
|
||||
app.session.draw_all_filters.draw(g);
|
||||
|
62
apps/ltn/src/common/mod.rs
Normal file
62
apps/ltn/src/common/mod.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use widgetry::{
|
||||
lctrl, EventCtx, HorizontalAlignment, Key, Line, Outcome, Panel, PanelBuilder,
|
||||
VerticalAlignment, Widget,
|
||||
};
|
||||
|
||||
use crate::{App, BrowseNeighborhoods, Transition};
|
||||
|
||||
pub fn app_top_panel(ctx: &mut EventCtx, app: &App) -> Panel {
|
||||
Panel::new_builder(Widget::row(vec![
|
||||
map_gui::tools::home_btn(ctx),
|
||||
Line("Low traffic neighborhoods")
|
||||
.small_heading()
|
||||
.into_widget(ctx)
|
||||
.centered_vert(),
|
||||
map_gui::tools::change_map_btn(ctx, app),
|
||||
ctx.style()
|
||||
.btn_plain
|
||||
.icon("system/assets/tools/search.svg")
|
||||
.hotkey(lctrl(Key::F))
|
||||
.build_widget(ctx, "search")
|
||||
.align_right(),
|
||||
]))
|
||||
.aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
|
||||
.exact_width_percent(1.0)
|
||||
.build(ctx)
|
||||
}
|
||||
|
||||
pub fn handle_top_panel(ctx: &mut EventCtx, app: &App, panel: &mut Panel) -> Option<Transition> {
|
||||
if let Outcome::Clicked(x) = panel.event(ctx) {
|
||||
match x.as_ref() {
|
||||
"Home" => Some(Transition::Clear(vec![
|
||||
map_gui::tools::TitleScreen::new_state(
|
||||
ctx,
|
||||
app,
|
||||
map_gui::tools::Executable::LTN,
|
||||
Box::new(|ctx, app, _| BrowseNeighborhoods::new_state(ctx, app)),
|
||||
),
|
||||
])),
|
||||
"change map" => Some(Transition::Push(map_gui::tools::CityPicker::new_state(
|
||||
ctx,
|
||||
app,
|
||||
Box::new(|ctx, app| Transition::Replace(BrowseNeighborhoods::new_state(ctx, app))),
|
||||
))),
|
||||
"search" => Some(Transition::Push(map_gui::tools::Navigator::new_state(
|
||||
ctx, app,
|
||||
))),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn left_panel_builder(contents: Widget) -> PanelBuilder {
|
||||
Panel::new_builder(contents)
|
||||
// TODO Vertical alignment below top panel is brittle
|
||||
.aligned(
|
||||
HorizontalAlignment::Percent(0.0),
|
||||
VerticalAlignment::Percent(0.1),
|
||||
)
|
||||
.exact_height_percent(0.9)
|
||||
}
|
@ -12,7 +12,8 @@ use crate::per_neighborhood::{FilterableObj, Tab};
|
||||
use crate::{after_edit, App, Neighborhood, NeighborhoodID, Transition};
|
||||
|
||||
pub struct Viewer {
|
||||
panel: Panel,
|
||||
top_panel: Panel,
|
||||
left_panel: Panel,
|
||||
neighborhood: Neighborhood,
|
||||
world: World<FilterableObj>,
|
||||
draw_top_layer: ToggleZoomed,
|
||||
@ -23,7 +24,8 @@ impl Viewer {
|
||||
let neighborhood = Neighborhood::new(ctx, app, id);
|
||||
|
||||
let mut viewer = Viewer {
|
||||
panel: Panel::empty(ctx),
|
||||
top_panel: crate::common::app_top_panel(ctx, app),
|
||||
left_panel: Panel::empty(ctx),
|
||||
neighborhood,
|
||||
world: World::unbounded(),
|
||||
draw_top_layer: ToggleZoomed::empty(ctx),
|
||||
@ -45,7 +47,7 @@ impl Viewer {
|
||||
format!("{} cells are totally disconnected", disconnected_cells)
|
||||
};
|
||||
|
||||
self.panel = Tab::Connectivity
|
||||
self.left_panel = Tab::Connectivity
|
||||
.panel_builder(
|
||||
ctx,
|
||||
app,
|
||||
@ -95,7 +97,10 @@ impl Viewer {
|
||||
|
||||
impl State<App> for Viewer {
|
||||
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
match self.panel.event(ctx) {
|
||||
if let Some(t) = crate::common::handle_top_panel(ctx, app, &mut self.top_panel) {
|
||||
return t;
|
||||
}
|
||||
match self.left_panel.event(ctx) {
|
||||
Outcome::Clicked(x) => {
|
||||
if x == "Automatically stop rat-runs" {
|
||||
ctx.loading_screen("automatically filter a neighborhood", |ctx, timer| {
|
||||
@ -110,7 +115,7 @@ impl State<App> for Viewer {
|
||||
return Transition::Push(FreehandFilters::new_state(
|
||||
ctx,
|
||||
&self.neighborhood,
|
||||
self.panel.center_of("Create filters along a shape"),
|
||||
self.left_panel.center_of("Create filters along a shape"),
|
||||
));
|
||||
}
|
||||
|
||||
@ -119,8 +124,8 @@ impl State<App> for Viewer {
|
||||
.unwrap();
|
||||
}
|
||||
Outcome::Changed(x) => {
|
||||
app.session.draw_cells_as_areas = self.panel.is_checked("draw cells");
|
||||
app.session.heuristic = self.panel.dropdown_value("heuristic");
|
||||
app.session.draw_cells_as_areas = self.left_panel.is_checked("draw cells");
|
||||
app.session.heuristic = self.left_panel.dropdown_value("heuristic");
|
||||
|
||||
if x != "heuristic" {
|
||||
let (world, draw_top_layer) = make_world(ctx, app, &self.neighborhood);
|
||||
@ -149,7 +154,8 @@ impl State<App> for Viewer {
|
||||
g.redraw(&self.neighborhood.fade_irrelevant);
|
||||
self.draw_top_layer.draw(g);
|
||||
|
||||
self.panel.draw(g);
|
||||
self.top_panel.draw(g);
|
||||
self.left_panel.draw(g);
|
||||
app.session.draw_all_filters.draw(g);
|
||||
// TODO Since we cover such a small area, treating multiple segments of one road as the
|
||||
// same might be nice. And we should seed the quadtree with the locations of filters and
|
||||
|
@ -4,8 +4,8 @@ use map_gui::tools::checkbox_per_mode;
|
||||
use synthpop::{Scenario, TripMode};
|
||||
use widgetry::tools::{FileLoader, PopupMsg};
|
||||
use widgetry::{
|
||||
Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Panel, SimpleState,
|
||||
Slider, State, Text, TextExt, Toggle, VerticalAlignment, Widget,
|
||||
Drawable, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel, Slider, State, Text, TextExt,
|
||||
Toggle, Widget,
|
||||
};
|
||||
|
||||
use crate::impact::{end_of_day, Filters, Impact};
|
||||
@ -15,6 +15,8 @@ use crate::{App, BrowseNeighborhoods, Transition};
|
||||
// ... can't we just produce data of a certain shape, and have a UI pretty tuned for that?
|
||||
|
||||
pub struct ShowResults {
|
||||
top_panel: Panel,
|
||||
left_panel: Panel,
|
||||
draw_all_neighborhoods: Drawable,
|
||||
}
|
||||
|
||||
@ -44,8 +46,7 @@ impl ShowResults {
|
||||
});
|
||||
}
|
||||
|
||||
let panel = Panel::new_builder(Widget::col(vec![
|
||||
crate::app_header(ctx, app),
|
||||
let left_panel = crate::common::left_panel_builder(Widget::col(vec![
|
||||
Widget::row(vec![
|
||||
Line("Impact prediction").small_heading().into_widget(ctx),
|
||||
ctx.style()
|
||||
@ -60,7 +61,6 @@ impl ShowResults {
|
||||
app.session.impact.compare_counts.get_panel_widget(ctx).named("compare counts"),
|
||||
ctx.style().btn_outline.text("Save before/after counts to files").build_def(ctx),
|
||||
]))
|
||||
.aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
|
||||
.build(ctx);
|
||||
|
||||
let mut batch = GeomBatch::new();
|
||||
@ -68,93 +68,81 @@ impl ShowResults {
|
||||
batch.push(color.alpha(0.2), block.polygon.clone());
|
||||
}
|
||||
let draw_all_neighborhoods = batch.upload(ctx);
|
||||
<dyn SimpleState<_>>::new_state(
|
||||
panel,
|
||||
Box::new(ShowResults {
|
||||
draw_all_neighborhoods,
|
||||
}),
|
||||
)
|
||||
Box::new(Self {
|
||||
top_panel: crate::common::app_top_panel(ctx, app),
|
||||
left_panel,
|
||||
draw_all_neighborhoods,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SimpleState<App> for ShowResults {
|
||||
fn on_click(
|
||||
&mut self,
|
||||
ctx: &mut EventCtx,
|
||||
app: &mut App,
|
||||
x: &str,
|
||||
panel: &mut Panel,
|
||||
) -> Transition {
|
||||
match x {
|
||||
"Browse neighborhoods" => {
|
||||
// Don't just Pop; if we updated the results, the UI won't warn the user about a slow
|
||||
// loading
|
||||
Transition::Replace(BrowseNeighborhoods::new_state(ctx, app))
|
||||
}
|
||||
"Save before/after counts to files" => {
|
||||
let path1 = "counts_a.json";
|
||||
let path2 = "counts_b.json";
|
||||
abstio::write_json(
|
||||
path1.to_string(),
|
||||
&app.session.impact.compare_counts.counts_a,
|
||||
);
|
||||
abstio::write_json(
|
||||
path2.to_string(),
|
||||
&app.session.impact.compare_counts.counts_b,
|
||||
);
|
||||
Transition::Push(PopupMsg::new_state(
|
||||
ctx,
|
||||
"Saved",
|
||||
vec![format!("Saved {} and {}", path1, path2)],
|
||||
))
|
||||
}
|
||||
x => {
|
||||
if let Some(t) = crate::handle_app_header_click(ctx, app, x) {
|
||||
return t;
|
||||
impl State<App> for ShowResults {
|
||||
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
if let Some(t) = crate::common::handle_top_panel(ctx, app, &mut self.top_panel) {
|
||||
return t;
|
||||
}
|
||||
match self.left_panel.event(ctx) {
|
||||
Outcome::Clicked(x) => match x.as_ref() {
|
||||
"Browse neighborhoods" => {
|
||||
// Don't just Pop; if we updated the results, the UI won't warn the user about a slow
|
||||
// loading
|
||||
return Transition::Replace(BrowseNeighborhoods::new_state(ctx, app));
|
||||
}
|
||||
"Save before/after counts to files" => {
|
||||
let path1 = "counts_a.json";
|
||||
let path2 = "counts_b.json";
|
||||
abstio::write_json(
|
||||
path1.to_string(),
|
||||
&app.session.impact.compare_counts.counts_a,
|
||||
);
|
||||
abstio::write_json(
|
||||
path2.to_string(),
|
||||
&app.session.impact.compare_counts.counts_b,
|
||||
);
|
||||
return Transition::Push(PopupMsg::new_state(
|
||||
ctx,
|
||||
"Saved",
|
||||
vec![format!("Saved {} and {}", path1, path2)],
|
||||
));
|
||||
}
|
||||
x => {
|
||||
// Avoid a double borrow
|
||||
let mut impact = std::mem::replace(&mut app.session.impact, Impact::empty(ctx));
|
||||
let widget = impact
|
||||
.compare_counts
|
||||
.on_click(ctx, app, x)
|
||||
.expect("button click didn't belong to CompareCounts");
|
||||
app.session.impact = impact;
|
||||
self.left_panel.replace(ctx, "compare counts", widget);
|
||||
return Transition::Keep;
|
||||
}
|
||||
},
|
||||
Outcome::Changed(_) => {
|
||||
// TODO The sliders should only trigger updates when the user lets go; way too slow
|
||||
// otherwise
|
||||
let filters = Filters::from_panel(&self.left_panel);
|
||||
if filters == app.session.impact.filters {
|
||||
return Transition::Keep;
|
||||
}
|
||||
|
||||
// Avoid a double borrow
|
||||
let mut impact = std::mem::replace(&mut app.session.impact, Impact::empty(ctx));
|
||||
let widget = impact
|
||||
.compare_counts
|
||||
.on_click(ctx, app, x)
|
||||
.expect("button click didn't belong to CompareCounts");
|
||||
impact.filters = Filters::from_panel(&self.left_panel);
|
||||
ctx.loading_screen("update filters", |ctx, timer| {
|
||||
impact.trips_changed(ctx, app, timer);
|
||||
});
|
||||
app.session.impact = impact;
|
||||
panel.replace(ctx, "compare counts", widget);
|
||||
Transition::Keep
|
||||
return Transition::Keep;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn other_event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
app.session.impact.compare_counts.other_event(ctx);
|
||||
Transition::Keep
|
||||
}
|
||||
|
||||
// TODO The sliders should only trigger updates when the user lets go; way too slow otherwise
|
||||
fn panel_changed(
|
||||
&mut self,
|
||||
ctx: &mut EventCtx,
|
||||
app: &mut App,
|
||||
panel: &mut Panel,
|
||||
) -> Option<Transition> {
|
||||
let filters = Filters::from_panel(panel);
|
||||
if filters == app.session.impact.filters {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Avoid a double borrow
|
||||
let mut impact = std::mem::replace(&mut app.session.impact, Impact::empty(ctx));
|
||||
impact.filters = Filters::from_panel(panel);
|
||||
ctx.loading_screen("update filters", |ctx, timer| {
|
||||
impact.trips_changed(ctx, app, timer);
|
||||
});
|
||||
app.session.impact = impact;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
self.top_panel.draw(g);
|
||||
self.left_panel.draw(g);
|
||||
g.redraw(&self.draw_all_neighborhoods);
|
||||
app.session.impact.compare_counts.draw(g);
|
||||
app.session.draw_all_filters.draw(g);
|
||||
@ -162,7 +150,7 @@ impl SimpleState<App> for ShowResults {
|
||||
}
|
||||
|
||||
impl Filters {
|
||||
fn from_panel(panel: &mut Panel) -> Filters {
|
||||
fn from_panel(panel: &Panel) -> Filters {
|
||||
let (p1, p2) = (
|
||||
panel.slider("depart from").get_percent(),
|
||||
panel.slider("depart until").get_percent(),
|
||||
|
@ -3,7 +3,7 @@
|
||||
use structopt::StructOpt;
|
||||
|
||||
use abstutil::Timer;
|
||||
use widgetry::{lctrl, EventCtx, GfxCtx, Key, Line, Settings, Widget};
|
||||
use widgetry::{EventCtx, GfxCtx, Settings};
|
||||
|
||||
pub use browse::BrowseNeighborhoods;
|
||||
use filters::Toggle3Zoomed;
|
||||
@ -17,6 +17,7 @@ extern crate anyhow;
|
||||
extern crate log;
|
||||
|
||||
mod browse;
|
||||
mod common;
|
||||
mod connectivity;
|
||||
mod draw_cells;
|
||||
mod export;
|
||||
@ -179,50 +180,6 @@ fn draw_with_layering<F: Fn(&mut GfxCtx)>(g: &mut GfxCtx, app: &App, custom: F)
|
||||
}
|
||||
}
|
||||
|
||||
// Like map_gui::tools::app_header, but squeezing in a search button
|
||||
fn app_header(ctx: &EventCtx, app: &App) -> Widget {
|
||||
Widget::col(vec![
|
||||
Widget::row(vec![
|
||||
map_gui::tools::home_btn(ctx),
|
||||
Line("Low traffic neighborhoods")
|
||||
.small_heading()
|
||||
.into_widget(ctx)
|
||||
.centered_vert(),
|
||||
]),
|
||||
Widget::row(vec![
|
||||
map_gui::tools::change_map_btn(ctx, app),
|
||||
ctx.style()
|
||||
.btn_plain
|
||||
.icon("system/assets/tools/search.svg")
|
||||
.hotkey(lctrl(Key::F))
|
||||
.build_widget(ctx, "search")
|
||||
.align_right(),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
fn handle_app_header_click(ctx: &mut EventCtx, app: &App, x: &str) -> Option<Transition> {
|
||||
match x {
|
||||
"Home" => Some(Transition::Clear(vec![
|
||||
map_gui::tools::TitleScreen::new_state(
|
||||
ctx,
|
||||
app,
|
||||
map_gui::tools::Executable::LTN,
|
||||
Box::new(|ctx, app, _| BrowseNeighborhoods::new_state(ctx, app)),
|
||||
),
|
||||
])),
|
||||
"change map" => Some(Transition::Push(map_gui::tools::CityPicker::new_state(
|
||||
ctx,
|
||||
app,
|
||||
Box::new(|ctx, app| Transition::Replace(BrowseNeighborhoods::new_state(ctx, app))),
|
||||
))),
|
||||
"search" => Some(Transition::Push(map_gui::tools::Navigator::new_state(
|
||||
ctx, app,
|
||||
))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn after_edit(ctx: &EventCtx, app: &mut App) {
|
||||
app.session.draw_all_filters = app.session.modal_filters.draw(ctx, &app.map);
|
||||
}
|
||||
|
@ -3,8 +3,7 @@ use map_model::{IntersectionID, PathConstraints, RoadID};
|
||||
use widgetry::mapspace::{ObjectID, World, WorldOutcome};
|
||||
use widgetry::tools::open_browser;
|
||||
use widgetry::{
|
||||
lctrl, Color, EventCtx, HorizontalAlignment, Image, Key, Line, Panel, PanelBuilder, TextExt,
|
||||
VerticalAlignment, Widget, DEFAULT_CORNER_RADIUS,
|
||||
lctrl, Color, EventCtx, Image, Key, Line, PanelBuilder, TextExt, Widget, DEFAULT_CORNER_RADIUS,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@ -24,8 +23,7 @@ impl Tab {
|
||||
app: &App,
|
||||
per_tab_contents: Widget,
|
||||
) -> PanelBuilder {
|
||||
Panel::new_builder(Widget::col(vec![
|
||||
crate::app_header(ctx, app),
|
||||
crate::common::left_panel_builder(Widget::col(vec![
|
||||
app.session.alt_proposals.to_widget(ctx, app),
|
||||
Widget::row(vec![
|
||||
Line("Editing neighborhood")
|
||||
@ -64,7 +62,6 @@ impl Tab {
|
||||
self.make_buttons(ctx),
|
||||
per_tab_contents.section(ctx),
|
||||
]))
|
||||
.aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
|
||||
}
|
||||
|
||||
pub fn handle_action(
|
||||
@ -93,9 +90,6 @@ impl Tab {
|
||||
self.switch_to_state(ctx, app, id)
|
||||
}
|
||||
x => {
|
||||
if let Some(t) = crate::handle_app_header_click(ctx, app, x) {
|
||||
return Some(t);
|
||||
}
|
||||
return crate::save::AltProposals::handle_action(
|
||||
ctx,
|
||||
app,
|
||||
|
@ -10,7 +10,8 @@ use crate::rat_runs::{find_rat_runs, RatRuns};
|
||||
use crate::{App, Neighborhood, NeighborhoodID, Transition};
|
||||
|
||||
pub struct BrowseRatRuns {
|
||||
panel: Panel,
|
||||
top_panel: Panel,
|
||||
left_panel: Panel,
|
||||
rat_runs: RatRuns,
|
||||
// When None, show the heatmap of all rat runs
|
||||
current_idx: Option<usize>,
|
||||
@ -43,7 +44,8 @@ impl BrowseRatRuns {
|
||||
let world = make_world(ctx, app, &neighborhood, &rat_runs);
|
||||
|
||||
let mut state = BrowseRatRuns {
|
||||
panel: Panel::empty(ctx),
|
||||
top_panel: crate::common::app_top_panel(ctx, app),
|
||||
left_panel: Panel::empty(ctx),
|
||||
rat_runs,
|
||||
current_idx: None,
|
||||
draw_path: ToggleZoomed::empty(ctx),
|
||||
@ -73,7 +75,7 @@ impl BrowseRatRuns {
|
||||
self.rat_runs.quiet_and_total_streets(&self.neighborhood);
|
||||
|
||||
if self.rat_runs.paths.is_empty() {
|
||||
self.panel = Tab::RatRuns
|
||||
self.left_panel = Tab::RatRuns
|
||||
.panel_builder(
|
||||
ctx,
|
||||
app,
|
||||
@ -91,11 +93,11 @@ impl BrowseRatRuns {
|
||||
}
|
||||
|
||||
// Optimization to avoid recalculating the whole panel
|
||||
if self.panel.has_widget("prev/next controls") && self.current_idx.is_some() {
|
||||
if self.left_panel.has_widget("prev/next controls") && self.current_idx.is_some() {
|
||||
let controls = self.prev_next_controls(ctx);
|
||||
self.panel.replace(ctx, "prev/next controls", controls);
|
||||
self.left_panel.replace(ctx, "prev/next controls", controls);
|
||||
} else {
|
||||
self.panel = Tab::RatRuns
|
||||
self.left_panel = Tab::RatRuns
|
||||
.panel_builder(
|
||||
ctx,
|
||||
app,
|
||||
@ -178,7 +180,10 @@ impl BrowseRatRuns {
|
||||
|
||||
impl State<App> for BrowseRatRuns {
|
||||
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
match self.panel.event(ctx) {
|
||||
if let Some(t) = crate::common::handle_top_panel(ctx, app, &mut self.top_panel) {
|
||||
return t;
|
||||
}
|
||||
match self.left_panel.event(ctx) {
|
||||
Outcome::Clicked(x) => match x.as_ref() {
|
||||
"previous rat run" => {
|
||||
for idx in &mut self.current_idx {
|
||||
@ -199,7 +204,7 @@ impl State<App> for BrowseRatRuns {
|
||||
}
|
||||
},
|
||||
Outcome::Changed(_) => {
|
||||
if self.panel.is_checked("show rat-runs") {
|
||||
if self.left_panel.is_checked("show rat-runs") {
|
||||
self.current_idx = None;
|
||||
} else {
|
||||
self.current_idx = Some(0);
|
||||
@ -229,7 +234,8 @@ impl State<App> for BrowseRatRuns {
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
self.panel.draw(g);
|
||||
self.top_panel.draw(g);
|
||||
self.left_panel.draw(g);
|
||||
|
||||
if self.current_idx.is_some() {
|
||||
self.draw_path.draw(g);
|
||||
|
@ -7,14 +7,15 @@ use map_model::{PathfinderCaching, NORMAL_LANE_THICKNESS};
|
||||
use synthpop::{TripEndpoint, TripMode};
|
||||
use widgetry::mapspace::{ToggleZoomed, World};
|
||||
use widgetry::{
|
||||
Color, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, RoundedF64,
|
||||
Spinner, State, Text, VerticalAlignment, Widget,
|
||||
Color, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel, RoundedF64, Spinner, State,
|
||||
Text, Widget,
|
||||
};
|
||||
|
||||
use crate::{handle_app_header_click, App, BrowseNeighborhoods, Transition};
|
||||
use crate::{App, BrowseNeighborhoods, Transition};
|
||||
|
||||
pub struct RoutePlanner {
|
||||
panel: Panel,
|
||||
top_panel: Panel,
|
||||
left_panel: Panel,
|
||||
waypoints: InputWaypoints,
|
||||
files: TripManagement<App, RoutePlanner>,
|
||||
world: World<WaypointID>,
|
||||
@ -41,7 +42,8 @@ impl TripManagementState<App> for RoutePlanner {
|
||||
impl RoutePlanner {
|
||||
pub fn new_state(ctx: &mut EventCtx, app: &mut App) -> Box<dyn State<App>> {
|
||||
let mut rp = RoutePlanner {
|
||||
panel: Panel::empty(ctx),
|
||||
top_panel: crate::common::app_top_panel(ctx, app),
|
||||
left_panel: Panel::empty(ctx),
|
||||
waypoints: InputWaypoints::new(app),
|
||||
files: TripManagement::new(app),
|
||||
world: World::unbounded(),
|
||||
@ -62,8 +64,7 @@ impl RoutePlanner {
|
||||
self.files.autosave(app);
|
||||
let results_widget = self.recalculate_paths(ctx, app);
|
||||
|
||||
let mut panel = Panel::new_builder(Widget::col(vec![
|
||||
crate::app_header(ctx, app),
|
||||
let mut panel = crate::common::left_panel_builder(Widget::col(vec![
|
||||
app.session.alt_proposals.to_widget(ctx, app),
|
||||
Widget::row(vec![
|
||||
Line("Plan a route").small_heading().into_widget(ctx),
|
||||
@ -100,10 +101,9 @@ impl RoutePlanner {
|
||||
]))
|
||||
// Hovering on waypoint cards
|
||||
.ignore_initial_events()
|
||||
.aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
|
||||
.build(ctx);
|
||||
panel.restore(ctx, &self.panel);
|
||||
self.panel = panel;
|
||||
panel.restore(ctx, &self.left_panel);
|
||||
self.left_panel = panel;
|
||||
|
||||
// Fade all neighborhood interiors, so it's very clear when a route cuts through
|
||||
let mut batch = GeomBatch::new();
|
||||
@ -241,7 +241,11 @@ impl RoutePlanner {
|
||||
|
||||
impl State<App> for RoutePlanner {
|
||||
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
let panel_outcome = self.panel.event(ctx);
|
||||
if let Some(t) = crate::common::handle_top_panel(ctx, app, &mut self.top_panel) {
|
||||
return t;
|
||||
}
|
||||
|
||||
let panel_outcome = self.left_panel.event(ctx);
|
||||
if let Outcome::Clicked(ref x) = panel_outcome {
|
||||
if x == "Browse neighborhoods" {
|
||||
return Transition::Replace(BrowseNeighborhoods::new_state(ctx, app));
|
||||
@ -253,9 +257,6 @@ impl State<App> for RoutePlanner {
|
||||
}
|
||||
return t;
|
||||
}
|
||||
if let Some(t) = handle_app_header_click(ctx, app, x) {
|
||||
return t;
|
||||
}
|
||||
if let Some(t) = crate::save::AltProposals::handle_action(
|
||||
ctx,
|
||||
app,
|
||||
@ -269,7 +270,7 @@ impl State<App> for RoutePlanner {
|
||||
if let Outcome::Changed(ref x) = panel_outcome {
|
||||
if x == "main road penalty" {
|
||||
app.session.main_road_penalty =
|
||||
self.panel.spinner::<RoundedF64>("main road penalty").0;
|
||||
self.left_panel.spinner::<RoundedF64>("main road penalty").0;
|
||||
self.update_everything(ctx, app);
|
||||
}
|
||||
}
|
||||
@ -288,7 +289,8 @@ impl State<App> for RoutePlanner {
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
self.panel.draw(g);
|
||||
self.top_panel.draw(g);
|
||||
self.left_panel.draw(g);
|
||||
|
||||
self.world.draw(g);
|
||||
|
||||
|
@ -8,8 +8,7 @@ use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::mapspace::{World, WorldOutcome};
|
||||
use widgetry::tools::Lasso;
|
||||
use widgetry::{
|
||||
Color, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State,
|
||||
Text, TextExt, VerticalAlignment, Widget,
|
||||
Color, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel, State, Text, TextExt, Widget,
|
||||
};
|
||||
|
||||
use crate::browse::draw_boundary_roads;
|
||||
@ -17,7 +16,8 @@ use crate::partition::BlockID;
|
||||
use crate::{App, NeighborhoodID, Partitioning, Transition};
|
||||
|
||||
pub struct SelectBoundary {
|
||||
panel: Panel,
|
||||
top_panel: Panel,
|
||||
left_panel: Panel,
|
||||
id: NeighborhoodID,
|
||||
world: World<BlockID>,
|
||||
draw_boundary_roads: ToggleZoomed,
|
||||
@ -37,7 +37,8 @@ pub struct SelectBoundary {
|
||||
impl SelectBoundary {
|
||||
pub fn new_state(ctx: &mut EventCtx, app: &App, id: NeighborhoodID) -> Box<dyn State<App>> {
|
||||
let mut state = SelectBoundary {
|
||||
panel: make_panel(ctx, app),
|
||||
top_panel: crate::common::app_top_panel(ctx, app),
|
||||
left_panel: make_panel(ctx),
|
||||
id,
|
||||
world: World::bounded(app.map.get_bounds()),
|
||||
draw_boundary_roads: draw_boundary_roads(ctx, app),
|
||||
@ -144,12 +145,12 @@ impl SelectBoundary {
|
||||
}
|
||||
|
||||
self.draw_boundary_roads = draw_boundary_roads(ctx, app);
|
||||
self.panel = make_panel(ctx, app);
|
||||
self.left_panel = make_panel(ctx);
|
||||
}
|
||||
Err(err) => {
|
||||
self.last_failed_change = Some((id, self.currently_have_block(app, id)));
|
||||
let label = err.to_string().text_widget(ctx);
|
||||
self.panel.replace(ctx, "warning", label);
|
||||
self.left_panel.replace(ctx, "warning", label);
|
||||
}
|
||||
}
|
||||
|
||||
@ -248,12 +249,15 @@ impl State<App> for SelectBoundary {
|
||||
if let Some(polygon) = lasso.event(ctx) {
|
||||
self.lasso = None;
|
||||
self.add_blocks_freehand(ctx, app, polygon);
|
||||
self.panel = make_panel(ctx, app);
|
||||
self.left_panel = make_panel(ctx);
|
||||
}
|
||||
return Transition::Keep;
|
||||
}
|
||||
|
||||
if let Outcome::Clicked(x) = self.panel.event(ctx) {
|
||||
if let Some(t) = crate::common::handle_top_panel(ctx, app, &mut self.top_panel) {
|
||||
return t;
|
||||
}
|
||||
if let Outcome::Clicked(x) = self.left_panel.event(ctx) {
|
||||
match x.as_ref() {
|
||||
"Cancel" => {
|
||||
// TODO If we destroyed the current neighborhood, then we cancel, we'll pop
|
||||
@ -271,11 +275,9 @@ impl State<App> for SelectBoundary {
|
||||
}
|
||||
"Select freehand" => {
|
||||
self.lasso = Some(Lasso::new());
|
||||
self.panel = make_panel_for_lasso(ctx, app);
|
||||
}
|
||||
x => {
|
||||
return crate::handle_app_header_click(ctx, app, x).unwrap();
|
||||
self.left_panel = make_panel_for_lasso(ctx);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -306,7 +308,8 @@ impl State<App> for SelectBoundary {
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
self.world.draw(g);
|
||||
self.draw_boundary_roads.draw(g);
|
||||
self.panel.draw(g);
|
||||
self.top_panel.draw(g);
|
||||
self.left_panel.draw(g);
|
||||
if g.canvas.is_unzoomed() {
|
||||
self.labels.draw(g, app);
|
||||
}
|
||||
@ -316,9 +319,8 @@ impl State<App> for SelectBoundary {
|
||||
}
|
||||
}
|
||||
|
||||
fn make_panel(ctx: &mut EventCtx, app: &App) -> Panel {
|
||||
Panel::new_builder(Widget::col(vec![
|
||||
crate::app_header(ctx, app),
|
||||
fn make_panel(ctx: &mut EventCtx) -> Panel {
|
||||
crate::common::left_panel_builder(Widget::col(vec![
|
||||
Line("Adjusting neighborhood boundary")
|
||||
.small_heading()
|
||||
.into_widget(ctx),
|
||||
@ -358,13 +360,11 @@ fn make_panel(ctx: &mut EventCtx, app: &App) -> Panel {
|
||||
]),
|
||||
Text::new().into_widget(ctx).named("warning"),
|
||||
]))
|
||||
.aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
|
||||
.build(ctx)
|
||||
}
|
||||
|
||||
fn make_panel_for_lasso(ctx: &mut EventCtx, app: &App) -> Panel {
|
||||
Panel::new_builder(Widget::col(vec![
|
||||
crate::app_header(ctx, app),
|
||||
fn make_panel_for_lasso(ctx: &mut EventCtx) -> Panel {
|
||||
crate::common::left_panel_builder(Widget::col(vec![
|
||||
"Draw a custom boundary for a neighborhood"
|
||||
.text_widget(ctx)
|
||||
.centered_vert(),
|
||||
@ -374,6 +374,5 @@ fn make_panel_for_lasso(ctx: &mut EventCtx, app: &App) -> Panel {
|
||||
])
|
||||
.into_widget(ctx),
|
||||
]))
|
||||
.aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
|
||||
.build(ctx)
|
||||
}
|
||||
|
@ -61,9 +61,17 @@ impl Panel {
|
||||
ScreenDims::new(w * canvas_dims.width, h * canvas_dims.height)
|
||||
}
|
||||
Dims::ExactSize(dims) => dims,
|
||||
Dims::ExactHeight(h) => {
|
||||
Dims::ExactHeightPixels(h) => {
|
||||
ScreenDims::new(self.contents_dims.width.min(canvas_dims.width), h)
|
||||
}
|
||||
Dims::ExactHeightPercent(pct) => ScreenDims::new(
|
||||
self.contents_dims.width.min(canvas_dims.width),
|
||||
pct * canvas_dims.height,
|
||||
),
|
||||
Dims::ExactWidthPercent(pct) => ScreenDims::new(
|
||||
pct * canvas_dims.width,
|
||||
self.contents_dims.height.min(canvas_dims.height),
|
||||
),
|
||||
};
|
||||
self.container_dims = new_container_dims;
|
||||
}
|
||||
@ -588,7 +596,9 @@ pub struct PanelBuilder {
|
||||
enum Dims {
|
||||
MaxPercent(Percent, Percent),
|
||||
ExactPercent(f64, f64),
|
||||
ExactHeight(f64),
|
||||
ExactHeightPixels(f64),
|
||||
ExactHeightPercent(f64),
|
||||
ExactWidthPercent(f64),
|
||||
ExactSize(ScreenDims),
|
||||
}
|
||||
|
||||
@ -623,9 +633,17 @@ impl PanelBuilder {
|
||||
height: Dimension::Points((h * ctx.canvas.window_height) as f32),
|
||||
};
|
||||
}
|
||||
Dims::ExactHeight(h) => {
|
||||
Dims::ExactHeightPixels(h) => {
|
||||
panel.top_level.layout.style.min_size.height = Dimension::Points(h as f32);
|
||||
}
|
||||
Dims::ExactHeightPercent(pct) => {
|
||||
panel.top_level.layout.style.min_size.height =
|
||||
Dimension::Points((pct * ctx.canvas.window_height) as f32);
|
||||
}
|
||||
Dims::ExactWidthPercent(pct) => {
|
||||
panel.top_level.layout.style.min_size.width =
|
||||
Dimension::Points((pct * ctx.canvas.window_width) as f32);
|
||||
}
|
||||
Dims::ExactSize(dims) => {
|
||||
panel.top_level.layout.style.min_size = dims.into();
|
||||
}
|
||||
@ -687,8 +705,18 @@ impl PanelBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn exact_height(mut self, height: f64) -> PanelBuilder {
|
||||
self.dims = Dims::ExactHeight(height);
|
||||
pub fn exact_height_pixels(mut self, height: f64) -> PanelBuilder {
|
||||
self.dims = Dims::ExactHeightPixels(height);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn exact_height_percent(mut self, pct: f64) -> PanelBuilder {
|
||||
self.dims = Dims::ExactHeightPercent(pct);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn exact_width_percent(mut self, pct: f64) -> PanelBuilder {
|
||||
self.dims = Dims::ExactWidthPercent(pct);
|
||||
self
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user