Move the LTN app header to a separate top panel.

This commit is contained in:
Dustin Carlino 2022-03-08 08:35:38 +00:00
parent 353a05625c
commit 04f00b4c8a
11 changed files with 249 additions and 206 deletions

View File

@ -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);
}

View File

@ -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);

View 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)
}

View File

@ -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

View File

@ -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(),

View File

@ -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);
}

View File

@ -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,

View File

@ -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);

View File

@ -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);

View File

@ -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)
}

View File

@ -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
}