Add a new layer to star buildings. Autosave it. While trying out changes

for #446 in the UI, I kept losing track of the two buildings I'm
focusing on. A player-defined list of shortcuts seems generally helpful.
This commit is contained in:
Dustin Carlino 2021-01-12 19:22:50 -08:00
parent 549a625d57
commit ff598f21a3
6 changed files with 128 additions and 4 deletions

View File

@ -209,7 +209,7 @@ impl State<App> for StoryMapEditor {
// TODO autosave // TODO autosave
let mut choices = Vec::new(); let mut choices = Vec::new();
for (name, story) in for (name, story) in
abstio::load_all_objects::<RecordedStoryMap>(abstio::path("player/stories")) abstio::load_all_objects::<RecordedStoryMap>(abstio::path_player("stories"))
{ {
if story.name == self.story.name { if story.name == self.story.name {
continue; continue;
@ -415,7 +415,7 @@ impl StoryMap {
.collect(), .collect(),
}; };
abstio::write_json( abstio::write_json(
abstio::path(format!("player/stories/{}.json", story.name)), abstio::path_player(format!("stories/{}.json", story.name)),
&story, &story,
); );
} }

101
game/src/layer/favorites.rs Normal file
View File

@ -0,0 +1,101 @@
use std::collections::BTreeSet;
use serde::{Deserialize, Serialize};
use abstutil::Timer;
use map_model::osm::OsmID;
use map_model::BuildingID;
use widgetry::{
Btn, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Panel, RewriteColor,
TextExt, VerticalAlignment, Widget,
};
use crate::app::App;
use crate::layer::{Layer, LayerOutcome};
/// A set of buildings that the player has starred, persisted as player data.
#[derive(Serialize, Deserialize)]
pub struct Favorites {
pub buildings: BTreeSet<OsmID>,
}
impl Favorites {
fn load(app: &App) -> Favorites {
abstio::maybe_read_json::<Favorites>(Favorites::path(app), &mut Timer::throwaway())
.unwrap_or_else(|_| Favorites {
buildings: BTreeSet::new(),
})
}
fn path(app: &App) -> String {
let name = app.primary.map.get_name();
abstio::path_player(format!("favorites/{}/{}.json", name.city, name.map))
}
pub fn contains(app: &App, b: BuildingID) -> bool {
Favorites::load(app)
.buildings
.contains(&app.primary.map.get_b(b).orig_id)
}
pub fn add(app: &App, b: BuildingID) {
let mut faves = Favorites::load(app);
faves.buildings.insert(app.primary.map.get_b(b).orig_id);
abstio::write_json(Favorites::path(app), &faves);
}
pub fn remove(app: &App, b: BuildingID) {
let mut faves = Favorites::load(app);
faves.buildings.remove(&app.primary.map.get_b(b).orig_id);
abstio::write_json(Favorites::path(app), &faves);
}
}
pub struct ShowFavorites {
panel: Panel,
draw: Drawable,
}
impl Layer for ShowFavorites {
fn name(&self) -> Option<&'static str> {
Some("favorites")
}
fn event(&mut self, ctx: &mut EventCtx, _: &mut App, minimap: &Panel) -> Option<LayerOutcome> {
Layer::simple_event(ctx, minimap, &mut self.panel)
}
fn draw(&self, g: &mut GfxCtx, _: &App) {
self.panel.draw(g);
g.redraw(&self.draw);
}
fn draw_minimap(&self, g: &mut GfxCtx) {
g.redraw(&self.draw);
}
}
impl ShowFavorites {
pub fn new(ctx: &mut EventCtx, app: &App) -> ShowFavorites {
let mut batch = GeomBatch::new();
for orig_id in Favorites::load(app).buildings.into_iter() {
if let Some(b) = app.primary.map.find_b_by_osm_id(orig_id) {
batch.append(
GeomBatch::load_svg(ctx, "system/assets/tools/star.svg")
.centered_on(app.primary.map.get_b(b).polygon.center())
.color(RewriteColor::ChangeAll(Color::YELLOW)),
);
}
}
let panel = Panel::new(Widget::row(vec![
Widget::draw_svg(ctx, "system/assets/tools/layers.svg"),
"Your favorite buildings".draw_text(ctx),
Btn::close(ctx),
]))
.aligned(HorizontalAlignment::Right, VerticalAlignment::Center)
.build(ctx);
ShowFavorites {
panel,
draw: ctx.upload(batch),
}
}
}

View File

@ -8,6 +8,7 @@ use crate::common::hotkey_btn;
use crate::sandbox::dashboards; use crate::sandbox::dashboards;
mod elevation; mod elevation;
pub mod favorites;
pub mod map; pub mod map;
mod pandemic; mod pandemic;
mod parking; mod parking;
@ -116,6 +117,7 @@ impl PickLayer {
btn("transit network", Key::U), btn("transit network", Key::U),
btn("population map", Key::X), btn("population map", Key::X),
btn("no sidewalks", Key::S), btn("no sidewalks", Key::S),
btn("favorite buildings", Key::F),
]), ]),
]) ])
.evenly_spaced(), .evenly_spaced(),
@ -183,6 +185,9 @@ impl State<App> for PickLayer {
"no sidewalks" => { "no sidewalks" => {
app.primary.layer = Some(Box::new(map::Static::no_sidewalks(ctx, app))); app.primary.layer = Some(Box::new(map::Static::no_sidewalks(ctx, app)));
} }
"favorite buildings" => {
app.primary.layer = Some(Box::new(favorites::ShowFavorites::new(ctx, app)));
}
"pandemic model" => { "pandemic model" => {
app.primary.layer = Some(Box::new(pandemic::Pandemic::new( app.primary.layer = Some(Box::new(pandemic::Pandemic::new(
ctx, ctx,

View File

@ -26,6 +26,7 @@ use crate::edit::{
can_edit_lane, EditMode, LaneEditor, SaveEdits, StopSignEditor, TrafficSignalEditor, can_edit_lane, EditMode, LaneEditor, SaveEdits, StopSignEditor, TrafficSignalEditor,
}; };
use crate::info::ContextualActions; use crate::info::ContextualActions;
use crate::layer::favorites::{Favorites, ShowFavorites};
use crate::layer::PickLayer; use crate::layer::PickLayer;
use crate::pregame::MainMenu; use crate::pregame::MainMenu;
@ -537,6 +538,13 @@ impl ContextualActions for Actions {
actions.push((Key::E, "edit lane".to_string())); actions.push((Key::E, "edit lane".to_string()));
} }
} }
ID::Building(b) => {
if Favorites::contains(app, b) {
actions.push((Key::F, "remove this building from favorites".to_string()));
} else {
actions.push((Key::F, "add this building to favorites".to_string()));
}
}
_ => {} _ => {}
} }
} }
@ -582,6 +590,16 @@ impl ContextualActions for Actions {
Transition::Push(EditMode::new(ctx, app, self.gameplay.clone())), Transition::Push(EditMode::new(ctx, app, self.gameplay.clone())),
Transition::Push(LaneEditor::new(ctx, app, l, self.gameplay.clone())), Transition::Push(LaneEditor::new(ctx, app, l, self.gameplay.clone())),
]), ]),
(ID::Building(b), "add this building to favorites") => {
Favorites::add(app, b);
app.primary.layer = Some(Box::new(ShowFavorites::new(ctx, app)));
Transition::Keep
}
(ID::Building(b), "remove this building from favorites") => {
Favorites::remove(app, b);
app.primary.layer = Some(Box::new(ShowFavorites::new(ctx, app)));
Transition::Keep
}
(_, "follow (run the simulation)") => { (_, "follow (run the simulation)") => {
*close_panel = false; *close_panel = false;
Transition::ModifyState(Box::new(|state, ctx, app| { Transition::ModifyState(Box::new(|state, ctx, app| {

View File

@ -73,7 +73,7 @@ impl<A: AppLike + 'static> State<A> for Picker<A> {
data_packs.runtime.insert(city); data_packs.runtime.insert(city);
} }
} }
abstio::write_json(abstio::path("player/data.json"), &data_packs); abstio::write_json(abstio::path_player("data.json"), &data_packs);
let messages = ctx.loading_screen("sync files", |_, timer| sync(timer)); let messages = ctx.loading_screen("sync files", |_, timer| sync(timer));
return Transition::Multi(vec![ return Transition::Multi(vec![

View File

@ -61,7 +61,7 @@ impl SimFlags {
let mut opts = self.opts.clone(); let mut opts = self.opts.clone();
if self.load.starts_with(&abstio::path("player/saves/")) { if self.load.starts_with(&abstio::path_player("saves/")) {
timer.note(format!("Resuming from {}", self.load)); timer.note(format!("Resuming from {}", self.load));
let sim: Sim = abstio::must_read_object(self.load.clone(), timer); let sim: Sim = abstio::must_read_object(self.load.clone(), timer);