Skip to content

Commit

Permalink
Hover on buttons to see matching amenities in the score homes mode too
Browse files Browse the repository at this point in the history
  • Loading branch information
dabreegster committed Mar 2, 2023
1 parent 62c0764 commit 5134630
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 75 deletions.
56 changes: 53 additions & 3 deletions apps/fifteen_min/src/common.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use std::str::FromStr;

use abstutil::MultiMap;
use geom::Distance;
use map_gui::tools::{CityPicker, Navigator};
use map_gui::ID;
use map_model::connectivity::WalkingOptions;
use map_model::BuildingID;
use map_model::{AmenityType, BuildingID};
use widgetry::tools::{ColorLegend, PopupMsg};
use widgetry::{
lctrl, Choice, Color, Drawable, EventCtx, GeomBatch, HorizontalAlignment, Key, Panel, Text,
Toggle, Transition, VerticalAlignment, Widget,
lctrl, Choice, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Panel,
Text, Toggle, Transition, VerticalAlignment, Widget,
};

use crate::isochrone::{Isochrone, MovementOptions, Options};
Expand Down Expand Up @@ -248,3 +251,50 @@ impl HoverOnBuilding {
}
}
}

pub struct HoverOnCategory {
// TODO Try using Cached?
state: Option<(AmenityType, Drawable)>,
color: Color,
}

impl HoverOnCategory {
pub fn new(color: Color) -> Self {
Self { state: None, color }
}

pub fn update_on_mouse_move(
&mut self,
ctx: &EventCtx,
app: &App,
panel: &Panel,
amenities_reachable: &MultiMap<AmenityType, BuildingID>,
) {
let key = panel
.currently_hovering()
.and_then(|x| x.strip_prefix("businesses: "));
if let Some(category) = key {
let category = AmenityType::from_str(category).unwrap();
if self
.state
.as_ref()
.map(|(cat, _)| *cat != category)
.unwrap_or(true)
{
let mut batch = GeomBatch::new();
for b in amenities_reachable.get(category) {
batch.push(self.color, app.map.get_b(*b).polygon.clone());
}
self.state = Some((category, ctx.upload(batch)));
}
} else {
self.state = None;
}
}

pub fn draw(&self, g: &mut GfxCtx) {
if let Some((_, ref draw)) = self.state {
g.redraw(draw);
}
}
}
86 changes: 61 additions & 25 deletions apps/fifteen_min/src/score_homes.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use std::collections::BTreeSet;

use crate::App;
use abstutil::{prettyprint_usize, Counter, Timer};
use abstutil::{prettyprint_usize, Counter, MultiMap, Timer};
use geom::Percent;
use map_gui::tools::grey_out_map;
use map_model::connectivity::Spot;
use map_model::{AmenityType, BuildingID};
use widgetry::tools::{ColorLegend, PopupMsg, URLManager};
use widgetry::{
DrawBaselayer, Drawable, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel, SimpleState,
State, Text, TextExt, Toggle, Transition, Widget,
Color, DrawBaselayer, Drawable, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel,
SimpleState, State, Text, TextExt, Toggle, Transition, Widget,
};

use crate::isochrone::Options;
Expand Down Expand Up @@ -127,27 +129,37 @@ fn score_houses_by_one_match(
app: &App,
amenities: Vec<AmenityType>,
timer: &mut Timer,
) -> Counter<BuildingID> {
) -> (Counter<BuildingID>, MultiMap<AmenityType, BuildingID>) {
let mut satisfied_per_bldg: Counter<BuildingID> = Counter::new();
let mut amenities_reachable = MultiMap::new();

let map = &app.map;
let movement_opts = &app.session.movement;
for times in timer.parallelize("find houses close to amenities", amenities, |category| {
// For each category, find all matching stores
let mut stores = Vec::new();
for b in map.all_buildings() {
if b.has_amenity(category) {
stores.push(Spot::Building(b.id));
for (category, stores, times) in
timer.parallelize("find houses close to amenities", amenities, |category| {
// For each category, find all matching stores
let mut stores = BTreeSet::new();
let mut spots = Vec::new();
for b in map.all_buildings() {
if b.has_amenity(category) {
stores.insert(b.id);
spots.push(Spot::Building(b.id));
}
}
}
movement_opts.clone().times_from(map, stores)
}) {
(
category,
stores,
movement_opts.clone().times_from(map, spots),
)
})
{
amenities_reachable.set(category, stores);
for (b, _) in times {
satisfied_per_bldg.inc(b);
}
}

satisfied_per_bldg
(satisfied_per_bldg, amenities_reachable)
}

// TODO Show the matching amenities.
Expand All @@ -156,7 +168,9 @@ struct Results {
panel: Panel,
draw_houses: Drawable,
amenities: Vec<AmenityType>,
amenities_reachable: MultiMap<AmenityType, BuildingID>,
draw_unwalkable_roads: Drawable,
hovering_on_category: common::HoverOnCategory,
}

impl Results {
Expand All @@ -168,7 +182,7 @@ impl Results {
let draw_unwalkable_roads = render::draw_unwalkable_roads(ctx, app);

assert!(!amenities.is_empty());
let scores = ctx.loading_screen("search for houses", |_, timer| {
let (scores, amenities_reachable) = ctx.loading_screen("search for houses", |_, timer| {
score_houses_by_one_match(app, amenities.clone(), timer)
});

Expand All @@ -186,13 +200,15 @@ impl Results {
batch.push(color, app.map.get_b(b).polygon.clone());
}

let panel = build_panel(ctx, app, &amenities, matches_all);
let panel = build_panel(ctx, app, &amenities, &amenities_reachable, matches_all);

Box::new(Self {
draw_unwalkable_roads,
panel,
draw_houses: ctx.upload(batch),
amenities,
amenities_reachable,
hovering_on_category: common::HoverOnCategory::new(Color::YELLOW),
})
}
}
Expand All @@ -204,6 +220,15 @@ impl State<App> for Results {
URLManager::update_url_cam(ctx, app.map.get_gps_bounds());
}

if ctx.redo_mouseover() {
self.hovering_on_category.update_on_mouse_move(
ctx,
app,
&self.panel,
&self.amenities_reachable,
);
}

match self.panel.event(ctx) {
Outcome::Clicked(x) => {
if x == "change scoring criteria" {
Expand All @@ -212,6 +237,9 @@ impl State<App> for Results {
app,
self.amenities.clone(),
));
} else if x.starts_with("businesses: ") {
// TODO Use ExploreAmenitiesDetails, but omit duration
return Transition::Keep;
}
return common::on_click(ctx, app, &x);
}
Expand All @@ -231,6 +259,7 @@ impl State<App> for Results {
fn draw(&self, g: &mut GfxCtx, _: &App) {
g.redraw(&self.draw_unwalkable_roads);
g.redraw(&self.draw_houses);
self.hovering_on_category.draw(g);
self.panel.draw(g);
}
}
Expand All @@ -239,20 +268,27 @@ fn build_panel(
ctx: &mut EventCtx,
app: &App,
amenities: &Vec<AmenityType>,
amenities_reachable: &MultiMap<AmenityType, BuildingID>,
matches_all: usize,
) -> Panel {
let contents = vec![
"What homes are within 15 minutes away?".text_widget(ctx),
Text::from(format!(
"Containing at least 1 of each: {}",
amenities
"Containing at least 1 of each:".text_widget(ctx),
Widget::custom_row(
amenities_reachable
.borrow()
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ")
))
.wrap_to_pct(ctx, 30)
.into_widget(ctx),
.map(|(amenity, buildings)| {
ctx.style()
.btn_outline
.text(format!("{}: {}", amenity, buildings.len()))
.build_widget(ctx, format!("businesses: {}", amenity))
.margin_right(4)
.margin_below(4)
})
.collect(),
)
.flex_wrap(ctx, Percent::int(30)),
format!(
"{} houses match all categories",
prettyprint_usize(matches_all)
Expand Down
78 changes: 31 additions & 47 deletions apps/fifteen_min/src/single_start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@
//!
//! See https://github.com/a-b-street/abstreet/issues/393 for more context.
use std::str::FromStr;

use abstutil::prettyprint_usize;
use geom::{Distance, FindClosest, Percent};
use map_gui::ID;
use map_model::{AmenityType, Building, BuildingID};
use std::str::FromStr;
use widgetry::tools::{ColorLegend, URLManager};
use widgetry::{
Cached, Color, Drawable, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel, State, Text,
Transition, Widget,
Cached, Color, Drawable, EventCtx, GfxCtx, Key, Line, Outcome, Panel, State, Text, Transition,
Widget,
};

use crate::common::{HoverKey, HoverOnBuilding};
use crate::common::{HoverKey, HoverOnBuilding, HoverOnCategory};
use crate::isochrone::{Isochrone, Options};
use crate::{common, render, App};

Expand All @@ -28,8 +29,7 @@ pub struct SingleStart {
highlight_start: Drawable,
isochrone: Isochrone,
hovering_on_bldg: Cached<HoverKey, HoverOnBuilding>,
// TODO Can't use Cached due to a double borrow
hovering_on_category: Option<(AmenityType, Drawable)>,
hovering_on_category: HoverOnCategory,
}

impl SingleStart {
Expand Down Expand Up @@ -61,7 +61,7 @@ impl SingleStart {
highlight_start: ctx.upload(highlight_start),
isochrone,
hovering_on_bldg: Cached::new(),
hovering_on_category: None,
hovering_on_category: HoverOnCategory::new(Color::RED),
draw_unwalkable_roads,
})
}
Expand Down Expand Up @@ -99,28 +99,12 @@ impl State<App> for SingleStart {
// inside the callback above, because it doesn't run when the key becomes None.
app.current_selection = self.hovering_on_bldg.key().map(|(b, _)| ID::Building(b));

// Update the preview of all businesses belonging to one category
let key = self
.panel
.currently_hovering()
.and_then(|x| x.strip_prefix("businesses: "));
if let Some(category) = key {
let category = AmenityType::from_str(category).unwrap();
if self
.hovering_on_category
.as_ref()
.map(|(cat, _)| *cat != category)
.unwrap_or(true)
{
let mut batch = GeomBatch::new();
for b in self.isochrone.amenities_reachable.get(category) {
batch.push(Color::RED, app.map.get_b(*b).polygon.clone());
}
self.hovering_on_category = Some((category, ctx.upload(batch)));
}
} else {
self.hovering_on_category = None;
}
self.hovering_on_category.update_on_mouse_move(
ctx,
app,
&self.panel,
&self.isochrone.amenities_reachable,
);

if ctx.is_key_down(Key::LeftControl) {
if let Some(cursor) = ctx.canvas.get_cursor_in_map_space() {
Expand Down Expand Up @@ -192,14 +176,12 @@ impl State<App> for SingleStart {
g.draw_mouse_tooltip(hover.tooltip.clone());
g.redraw(&hover.drawn_route);
}
if let Some((_, ref draw)) = self.hovering_on_category {
g.redraw(draw);
}
self.hovering_on_category.draw(g);
}
}

fn build_panel(ctx: &mut EventCtx, app: &App, start: &Building, isochrone: &Isochrone) -> Panel {
let mut contents = vec![
let contents = vec![
Text::from_all(vec![
Line("Click").fg(ctx.style().text_hotkey_color),
Line(" a building or hold ").secondary(),
Expand Down Expand Up @@ -230,20 +212,22 @@ fn build_panel(ctx: &mut EventCtx, app: &App, start: &Building, isochrone: &Isoc
(Color::RED, "15 mins"),
],
),
Widget::custom_row(
isochrone
.amenities_reachable
.borrow()
.iter()
.map(|(amenity, buildings)| {
ctx.style()
.btn_outline
.text(format!("{}: {}", amenity, buildings.len()))
.build_widget(ctx, format!("businesses: {}", amenity))
.margin_right(4)
.margin_below(4)
})
.collect(),
)
.flex_wrap(ctx, Percent::int(30)),
];

let mut amenities = Vec::new();
for (amenity, buildings) in isochrone.amenities_reachable.borrow() {
amenities.push(
ctx.style()
.btn_outline
.text(format!("{}: {}", amenity, buildings.len()))
.build_widget(ctx, format!("businesses: {}", amenity))
.margin_right(4)
.margin_below(4),
);
}
contents.push(Widget::custom_row(amenities).flex_wrap(ctx, Percent::int(30)));

common::build_panel(ctx, app, common::Mode::SingleStart, Widget::col(contents))
}

0 comments on commit 5134630

Please sign in to comment.