Prompt the user to download missing cities from the Proposals screen, too.

This commit is contained in:
Dustin Carlino 2021-03-10 12:10:16 -08:00
parent db96b0a7c4
commit fdc6601520
5 changed files with 244 additions and 196 deletions

View File

@ -1,14 +1,10 @@
use std::collections::HashMap;
use instant::Instant;
use rand::Rng;
use rand_xorshift::XorShiftRng;
use abstutil::Timer;
use geom::{Duration, Line, Percent, Pt2D, Speed};
use map_gui::load::MapLoader;
use map_gui::tools::{open_browser, PopupMsg};
use map_model::PermanentMapEdits;
use geom::{Duration, Line, Pt2D, Speed};
use map_gui::tools::open_browser;
use sim::{AlertHandler, ScenarioGenerator, Sim, SimOptions};
use widgetry::{
hotkeys, Color, ContentMode, DrawBaselayer, EdgeInsets, EventCtx, Font, GfxCtx, Image, Key,
@ -18,10 +14,11 @@ use widgetry::{
use crate::app::{App, Transition};
use crate::challenges::ChallengesPicker;
use crate::devtools::DevToolsMode;
use crate::edit::apply_map_edits;
use crate::sandbox::gameplay::Tutorial;
use crate::sandbox::{GameplayMode, SandboxMode};
mod proposals;
pub struct TitleScreen {
panel: Panel,
screensaver: Screensaver,
@ -225,7 +222,7 @@ impl State<App> for MainMenu {
open_browser("https://forms.gle/ocvbek1bTaZUr3k49");
}
"Community Proposals" => {
return Transition::Push(Proposals::new(ctx, app, None));
return Transition::Push(proposals::Proposals::new(ctx, app, None));
}
"Internal Dev Tools" => {
return Transition::Push(DevToolsMode::new(ctx, app));
@ -332,191 +329,6 @@ impl State<App> for About {
}
}
struct Proposals {
panel: Panel,
proposals: HashMap<String, PermanentMapEdits>,
current: Option<String>,
}
impl Proposals {
fn new(ctx: &mut EventCtx, app: &App, current: Option<String>) -> Box<dyn State<App>> {
let mut proposals = HashMap::new();
let mut buttons = Vec::new();
let mut current_tab = Vec::new();
// If a proposal has fallen out of date, it'll be skipped with an error logged. Since these
// are under version control, much more likely to notice when they break (or we could add a
// step to data/regen.sh).
for (name, edits) in
abstio::load_all_objects::<PermanentMapEdits>(abstio::path("system/proposals"))
{
if current == Some(name.clone()) {
let mut txt = Text::new();
txt.add(Line(&edits.proposal_description[0]).small_heading());
for l in edits.proposal_description.iter().skip(1) {
txt.add(Line(l));
}
current_tab.push(
txt.wrap_to_pct(ctx, 70)
.into_widget(ctx)
.margin_below(15)
.margin_above(15),
);
if edits.proposal_link.is_some() {
current_tab.push(
ctx.style()
.btn_plain
.btn()
.label_underlined_text("Read detailed write-up")
.build_def(ctx)
.margin_below(10),
);
}
current_tab.push(
ctx.style()
.btn_solid_primary
.text("Try out this proposal")
.build_def(ctx),
);
buttons.push(
ctx.style()
.btn_tab
.text(&edits.proposal_description[0])
.disabled(true)
.build_def(ctx)
.margin_below(10),
);
} else {
buttons.push(
ctx.style()
.btn_tab
.text(&edits.proposal_description[0])
.no_tooltip()
.build_widget(ctx, &name)
.margin_below(10),
);
}
proposals.insert(name, edits);
}
let mut col = vec![
{
let mut txt = Text::from(Line("A/B STREET").display_title());
txt.add(Line("PROPOSALS").big_heading_styled());
txt.add(Line(""));
txt.add(Line(
"These are proposed changes to Seattle made by community members.",
));
txt.add(Line("Contact dabreegster@gmail.com to add your idea here!"));
txt.into_widget(ctx).centered_horiz().margin_below(20)
},
Widget::custom_row(buttons).flex_wrap(ctx, Percent::int(80)),
];
col.extend(current_tab);
Box::new(Proposals {
proposals,
panel: Panel::new(Widget::custom_col(vec![
ctx.style()
.btn_back("Home")
.hotkey(Key::Escape)
.build_widget(ctx, "back")
.align_left()
.margin_below(20),
Widget::col(col).bg(app.cs.panel_bg).padding(16),
]))
.exact_size_percent(90, 85)
.build_custom(ctx),
current,
})
}
}
impl State<App> for Proposals {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
"back" => {
return Transition::Pop;
}
"Try out this proposal" => {
let edits = self.proposals[self.current.as_ref().unwrap()].clone();
return Transition::Push(MapLoader::new(
ctx,
app,
edits.map_name.clone(),
Box::new(move |ctx, app| {
// Apply edits before setting up the sandbox, for simplicity
let maybe_err = ctx.loading_screen("apply edits", |ctx, mut timer| {
match edits.to_edits(&app.primary.map) {
Ok(edits) => {
apply_map_edits(ctx, app, edits);
app.primary
.map
.recalculate_pathfinding_after_edits(&mut timer);
None
}
Err(err) => Some(err),
}
});
if let Some(err) = maybe_err {
Transition::Replace(PopupMsg::new(
ctx,
"Can't load proposal",
vec![err.to_string()],
))
} else {
app.primary.layer =
Some(Box::new(crate::layer::map::Static::edits(ctx, app)));
let mode = if abstio::file_exists(abstio::path_scenario(
app.primary.map.get_name(),
"weekday",
)) {
GameplayMode::PlayScenario(
app.primary.map.get_name().clone(),
"weekday".to_string(),
Vec::new(),
)
} else {
GameplayMode::Freeform(app.primary.map.get_name().clone())
};
Transition::Replace(SandboxMode::simple_new(app, mode))
}
}),
));
}
"Read detailed write-up" => {
open_browser(
self.proposals[self.current.as_ref().unwrap()]
.proposal_link
.clone()
.unwrap(),
);
}
x => {
return Transition::Replace(Proposals::new(ctx, app, Some(x.to_string())));
}
},
_ => {}
}
Transition::Keep
}
fn draw_baselayer(&self) -> DrawBaselayer {
DrawBaselayer::Custom
}
fn draw(&self, g: &mut GfxCtx, app: &App) {
g.clear(app.cs.dialog_bg);
self.panel.draw(g);
}
}
struct Screensaver {
line: Line,
started: Instant,

View File

@ -0,0 +1,232 @@
use std::collections::HashMap;
use geom::Percent;
use map_gui::load::MapLoader;
use map_gui::tools::{open_browser, sync_missing_files, ChooseSomething, PopupMsg};
use map_model::PermanentMapEdits;
use widgetry::{DrawBaselayer, EventCtx, GfxCtx, Key, Line, Outcome, Panel, State, Text, Widget};
use crate::app::{App, Transition};
use crate::edit::apply_map_edits;
use crate::sandbox::{GameplayMode, SandboxMode};
pub struct Proposals {
panel: Panel,
proposals: HashMap<String, PermanentMapEdits>,
current: Option<String>,
}
impl Proposals {
pub fn new(ctx: &mut EventCtx, app: &App, current: Option<String>) -> Box<dyn State<App>> {
let mut proposals = HashMap::new();
let mut buttons = Vec::new();
let mut current_tab = Vec::new();
// If a proposal has fallen out of date, it'll be skipped with an error logged. Since these
// are under version control, much more likely to notice when they break (or we could add a
// step to data/regen.sh).
for (name, edits) in
abstio::load_all_objects::<PermanentMapEdits>(abstio::path("system/proposals"))
{
if current == Some(name.clone()) {
let mut txt = Text::new();
txt.add(Line(&edits.proposal_description[0]).small_heading());
for l in edits.proposal_description.iter().skip(1) {
txt.add(Line(l));
}
current_tab.push(
txt.wrap_to_pct(ctx, 70)
.into_widget(ctx)
.margin_below(15)
.margin_above(15),
);
if edits.proposal_link.is_some() {
current_tab.push(
ctx.style()
.btn_plain
.btn()
.label_underlined_text("Read detailed write-up")
.build_def(ctx)
.margin_below(10),
);
}
current_tab.push(
ctx.style()
.btn_solid_primary
.text("Try out this proposal")
.build_def(ctx),
);
buttons.push(
ctx.style()
.btn_tab
.text(&edits.proposal_description[0])
.disabled(true)
.build_def(ctx)
.margin_below(10),
);
} else {
buttons.push(
ctx.style()
.btn_tab
.text(&edits.proposal_description[0])
.no_tooltip()
.build_widget(ctx, &name)
.margin_below(10),
);
}
proposals.insert(name, edits);
}
let mut col = vec![
{
let mut txt = Text::from(Line("A/B STREET").display_title());
txt.add(Line("PROPOSALS").big_heading_styled());
txt.add(Line(""));
txt.add(Line(
"These are proposed changes to Seattle made by community members.",
));
txt.add(Line("Contact dabreegster@gmail.com to add your idea here!"));
txt.into_widget(ctx).centered_horiz().margin_below(20)
},
Widget::custom_row(buttons).flex_wrap(ctx, Percent::int(80)),
];
col.extend(current_tab);
Box::new(Proposals {
proposals,
panel: Panel::new(Widget::custom_col(vec![
ctx.style()
.btn_back("Home")
.hotkey(Key::Escape)
.build_widget(ctx, "back")
.align_left()
.margin_below(20),
Widget::col(col).bg(app.cs.panel_bg).padding(16),
]))
.exact_size_percent(90, 85)
.build_custom(ctx),
current,
})
}
}
impl State<App> for Proposals {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
"back" => {
return Transition::Pop;
}
"Try out this proposal" => {
return launch(
ctx,
app,
self.proposals[self.current.as_ref().unwrap()].clone(),
);
}
"Read detailed write-up" => {
open_browser(
self.proposals[self.current.as_ref().unwrap()]
.proposal_link
.clone()
.unwrap(),
);
}
x => {
return Transition::Replace(Proposals::new(ctx, app, Some(x.to_string())));
}
},
_ => {}
}
Transition::Keep
}
fn draw_baselayer(&self) -> DrawBaselayer {
DrawBaselayer::Custom
}
fn draw(&self, g: &mut GfxCtx, app: &App) {
g.clear(app.cs.dialog_bg);
self.panel.draw(g);
}
}
fn launch(ctx: &mut EventCtx, app: &App, edits: PermanentMapEdits) -> Transition {
#[cfg(not(target_arch = "wasm32"))]
{
if !abstio::file_exists(edits.map_name.path()) {
return Transition::Push(ChooseSomething::new(
ctx,
&format!("Missing data. Download {}?", edits.map_name.city.describe()),
vec![
widgetry::Choice::string("Yes, download"),
widgetry::Choice::string("Never mind").key(Key::Escape),
],
Box::new(move |resp, ctx, _| {
if resp == "Never mind" {
return Transition::Pop;
}
let mut data_packs = abstio::DataPacks::load_or_create();
data_packs.runtime.insert(edits.map_name.city.to_path());
abstio::write_json(abstio::path_player("data.json"), &data_packs);
let messages =
ctx.loading_screen("sync files", |_, timer| sync_missing_files(timer));
Transition::Replace(PopupMsg::new(
ctx,
"Download complete. Please try again",
messages,
))
}),
));
}
}
Transition::Push(MapLoader::new(
ctx,
app,
edits.map_name.clone(),
Box::new(move |ctx, app| {
// Apply edits before setting up the sandbox, for simplicity
let maybe_err = ctx.loading_screen("apply edits", |ctx, mut timer| {
match edits.to_edits(&app.primary.map) {
Ok(edits) => {
apply_map_edits(ctx, app, edits);
app.primary
.map
.recalculate_pathfinding_after_edits(&mut timer);
None
}
Err(err) => Some(err),
}
});
if let Some(err) = maybe_err {
Transition::Replace(PopupMsg::new(
ctx,
"Can't load proposal",
vec![err.to_string()],
))
} else {
app.primary.layer = Some(Box::new(crate::layer::map::Static::edits(ctx, app)));
let mode = if abstio::file_exists(abstio::path_scenario(
app.primary.map.get_name(),
"weekday",
)) {
GameplayMode::PlayScenario(
app.primary.map.get_name().clone(),
"weekday".to_string(),
Vec::new(),
)
} else {
GameplayMode::Freeform(app.primary.map.get_name().clone())
};
Transition::Replace(SandboxMode::simple_new(app, mode))
}
}),
))
}

View File

@ -213,7 +213,7 @@ impl<A: AppLike + 'static> CityPicker<A> {
abstio::write_json(abstio::path_player("data.json"), &data_packs);
let messages = ctx.loading_screen("sync files", |_, timer| {
crate::tools::updater::sync(timer)
crate::tools::updater::sync_missing_files(timer)
});
Transition::Replace(crate::tools::PopupMsg::new(
ctx,

View File

@ -15,6 +15,9 @@ pub use self::ui::{ChooseSomething, PopupMsg, PromptInput};
pub use self::url::URLManager;
use crate::AppLike;
#[cfg(not(target_arch = "wasm32"))]
pub use self::updater::sync_missing_files;
mod camera;
mod city_picker;
mod colors;

View File

@ -82,7 +82,8 @@ impl<A: AppLike + 'static> State<A> for Picker<A> {
}
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_missing_files(timer));
return Transition::Multi(vec![
Transition::Replace(crate::tools::CityPicker::new(
ctx,
@ -137,7 +138,7 @@ fn prettyprint_bytes(bytes: usize) -> String {
// TODO This only downloads files that don't exist but should. It doesn't remove or update
// anything. Not sure if everything the updater does should also be done here.
pub fn sync(timer: &mut Timer) -> Vec<String> {
pub fn sync_missing_files(timer: &mut Timer) -> Vec<String> {
let truth = Manifest::load().filter(DataPacks::load_or_create());
let version = if cfg!(feature = "release_s3") {
NEXT_RELEASE