mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-24 01:15:12 +03:00
Prompt the user to download missing cities from the Proposals screen, too.
This commit is contained in:
parent
db96b0a7c4
commit
fdc6601520
@ -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,
|
232
game/src/pregame/proposals.rs
Normal file
232
game/src/pregame/proposals.rs
Normal 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))
|
||||
}
|
||||
}),
|
||||
))
|
||||
}
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user