mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-29 01:13:53 +03:00
make a way simpler, one-choice version of Wizard. use it in two
different places to flesh out how it works
This commit is contained in:
parent
5eefdaef18
commit
c41c998301
@ -464,4 +464,10 @@ impl Choice<String> {
|
||||
pub fn string(label: &str) -> Choice<String> {
|
||||
Choice::new(label.to_string(), label.to_string())
|
||||
}
|
||||
|
||||
pub fn strings(list: Vec<String>) -> Vec<Choice<String>> {
|
||||
list.into_iter()
|
||||
.map(|x| Choice::new(x.clone(), x))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
@ -128,13 +128,8 @@ impl<T: 'static + Clone> WidgetImpl for Menu<T> {
|
||||
self.state = InputResult::Done(choice.label.clone(), choice.data.clone());
|
||||
return;
|
||||
}
|
||||
// Unconsume the click, it was in screen space, but not on us.
|
||||
ctx.input.unconsume_event();
|
||||
} else {
|
||||
// Clicked on the map? Cancel out
|
||||
self.state = InputResult::Canceled;
|
||||
return;
|
||||
}
|
||||
ctx.input.unconsume_event();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
use crate::app::App;
|
||||
use crate::colors::ColorScheme;
|
||||
use crate::game::{State, Transition, WizardState};
|
||||
use crate::game::{ChooseSomething, State, Transition};
|
||||
use aabb_quadtree::QuadTree;
|
||||
use abstutil::{prettyprint_usize, Parallelism};
|
||||
use ezgui::{
|
||||
hotkey, lctrl, Btn, Choice, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx,
|
||||
HorizontalAlignment, Key, Line, Outcome, Text, TextExt, VerticalAlignment, Widget, Wizard,
|
||||
HorizontalAlignment, Key, Line, Outcome, Text, TextExt, VerticalAlignment, Widget,
|
||||
};
|
||||
use geom::{Circle, Distance, PolyLine, Polygon, Pt2D, Ring};
|
||||
use kml::ExtraShapes;
|
||||
@ -166,7 +166,22 @@ impl State for ViewKML {
|
||||
return Transition::Pop;
|
||||
}
|
||||
"load KML file" => {
|
||||
return Transition::Push(WizardState::new(Box::new(choose_kml)));
|
||||
return Transition::Push(ChooseSomething::new(
|
||||
ctx,
|
||||
"Load file",
|
||||
Choice::strings(
|
||||
abstutil::list_dir(std::path::Path::new(&abstutil::path(format!(
|
||||
"input/{}/",
|
||||
app.primary.map.get_city_name()
|
||||
))))
|
||||
.into_iter()
|
||||
.filter(|x| x.ends_with(".bin") && !x.ends_with("popdat.bin"))
|
||||
.collect(),
|
||||
),
|
||||
Box::new(|path, ctx, app| {
|
||||
Transition::Replace(ViewKML::new(ctx, app, Some(path)))
|
||||
}),
|
||||
));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
@ -332,16 +347,3 @@ fn make_query(app: &App, objects: &Vec<Object>, query: &str) -> (GeomBatch, usiz
|
||||
}
|
||||
(batch, cnt)
|
||||
}
|
||||
|
||||
fn choose_kml(wiz: &mut Wizard, ctx: &mut EventCtx, app: &mut App) -> Option<Transition> {
|
||||
let path = wiz.wrap(ctx).choose_string("View what KML dataset?", || {
|
||||
abstutil::list_dir(std::path::Path::new(&abstutil::path(format!(
|
||||
"input/{}/",
|
||||
app.primary.map.get_city_name()
|
||||
))))
|
||||
.into_iter()
|
||||
.filter(|x| x.ends_with(".bin") && !x.ends_with("popdat.bin"))
|
||||
.collect()
|
||||
})?;
|
||||
Some(Transition::Replace(ViewKML::new(ctx, app, Some(path))))
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::app::{App, ShowEverything};
|
||||
use crate::common::CommonState;
|
||||
use crate::game::{DrawBaselayer, State, Transition, WizardState};
|
||||
use crate::game::{ChooseSomething, DrawBaselayer, State, Transition, WizardState};
|
||||
use crate::render::DrawOptions;
|
||||
use ezgui::{
|
||||
hotkey, lctrl, Btn, Choice, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx,
|
||||
@ -198,52 +198,46 @@ impl State for StoryMapEditor {
|
||||
}
|
||||
"load" => {
|
||||
// TODO autosave
|
||||
let current = self.story.name.clone();
|
||||
let btn = self.composite.rect_of("load").clone();
|
||||
return Transition::Push(WizardState::new(Box::new(move |wiz, ctx, app| {
|
||||
let (_, raw) = wiz.wrap(ctx).choose_exact(
|
||||
(
|
||||
HorizontalAlignment::Centered(btn.center().x),
|
||||
VerticalAlignment::Below(btn.y2 + 15.0),
|
||||
),
|
||||
None,
|
||||
|| {
|
||||
let mut list = Vec::new();
|
||||
for (name, story) in abstutil::load_all_objects::<RecordedStoryMap>(
|
||||
abstutil::path("player/stories"),
|
||||
) {
|
||||
if story.name == current {
|
||||
continue;
|
||||
}
|
||||
// TODO Argh, we can't make StoryMap cloneable, so redo some
|
||||
// work
|
||||
let gps_bounds = app.primary.map.get_gps_bounds();
|
||||
if story
|
||||
.markers
|
||||
.iter()
|
||||
.all(|(pts, _)| gps_bounds.try_convert(pts).is_some())
|
||||
{
|
||||
list.push(Choice::new(name, story));
|
||||
}
|
||||
}
|
||||
list.push(Choice::new(
|
||||
"new story",
|
||||
RecordedStoryMap {
|
||||
name: "new story".to_string(),
|
||||
markers: Vec::new(),
|
||||
},
|
||||
));
|
||||
list
|
||||
},
|
||||
)?;
|
||||
let story = StoryMap::load(ctx, app, raw).unwrap();
|
||||
Some(Transition::PopWithData(Box::new(move |state, ctx, _| {
|
||||
let editor = state.downcast_mut::<StoryMapEditor>().unwrap();
|
||||
editor.story = story;
|
||||
editor.dirty = false;
|
||||
editor.redo_panel(ctx);
|
||||
})))
|
||||
})));
|
||||
let mut choices = Vec::new();
|
||||
for (name, story) in abstutil::load_all_objects::<RecordedStoryMap>(
|
||||
abstutil::path("player/stories"),
|
||||
) {
|
||||
if story.name == self.story.name {
|
||||
continue;
|
||||
}
|
||||
// TODO Argh, we can't make StoryMap cloneable, so redo some
|
||||
// work
|
||||
let gps_bounds = app.primary.map.get_gps_bounds();
|
||||
if story
|
||||
.markers
|
||||
.iter()
|
||||
.all(|(pts, _)| gps_bounds.try_convert(pts).is_some())
|
||||
{
|
||||
choices.push(Choice::new(name, story));
|
||||
}
|
||||
}
|
||||
choices.push(Choice::new(
|
||||
"new story",
|
||||
RecordedStoryMap {
|
||||
name: "new story".to_string(),
|
||||
markers: Vec::new(),
|
||||
},
|
||||
));
|
||||
|
||||
return Transition::Push(ChooseSomething::new_below(
|
||||
ctx,
|
||||
self.composite.rect_of("load"),
|
||||
choices,
|
||||
Box::new(|raw, ctx, app| {
|
||||
let story = StoryMap::load(ctx, app, raw).unwrap();
|
||||
Transition::PopWithData(Box::new(move |state, ctx, _| {
|
||||
let editor = state.downcast_mut::<StoryMapEditor>().unwrap();
|
||||
editor.story = story;
|
||||
editor.dirty = false;
|
||||
editor.redo_panel(ctx);
|
||||
}))
|
||||
}),
|
||||
));
|
||||
}
|
||||
"new marker" => {
|
||||
self.hovering = None;
|
||||
|
@ -3,7 +3,10 @@ use crate::options::Options;
|
||||
use crate::pregame::TitleScreen;
|
||||
use crate::render::DrawOptions;
|
||||
use crate::sandbox::{GameplayMode, SandboxMode};
|
||||
use ezgui::{Canvas, Drawable, EventCtx, GfxCtx, Wizard, GUI};
|
||||
use ezgui::{
|
||||
hotkey, Btn, Canvas, Choice, Composite, Drawable, EventCtx, GfxCtx, HorizontalAlignment, Key,
|
||||
Line, Menu, Outcome, ScreenRectangle, VerticalAlignment, Widget, Wizard, GUI,
|
||||
};
|
||||
use geom::Polygon;
|
||||
use map_model::PermanentMapEdits;
|
||||
|
||||
@ -357,3 +360,82 @@ pub fn msg<S: Into<String>>(title: &'static str, lines: Vec<S>) -> Box<dyn State
|
||||
Some(Transition::Pop)
|
||||
}))
|
||||
}
|
||||
|
||||
pub struct ChooseSomething<T> {
|
||||
composite: Composite,
|
||||
cb: Box<dyn Fn(T, &mut EventCtx, &mut App) -> Transition>,
|
||||
}
|
||||
|
||||
impl<T: 'static + Clone> ChooseSomething<T> {
|
||||
pub fn new(
|
||||
ctx: &mut EventCtx,
|
||||
query: &str,
|
||||
choices: Vec<Choice<T>>,
|
||||
cb: Box<dyn Fn(T, &mut EventCtx, &mut App) -> Transition>,
|
||||
) -> Box<dyn State> {
|
||||
Box::new(ChooseSomething {
|
||||
composite: Composite::new(Widget::col(vec![
|
||||
Widget::row(vec![
|
||||
Line(query).small_heading().draw(ctx),
|
||||
Btn::plaintext("X")
|
||||
.build(ctx, "close", hotkey(Key::Escape))
|
||||
.align_right(),
|
||||
]),
|
||||
Menu::new(ctx, choices).named("menu"),
|
||||
]))
|
||||
.build(ctx),
|
||||
cb,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_below(
|
||||
ctx: &mut EventCtx,
|
||||
rect: &ScreenRectangle,
|
||||
choices: Vec<Choice<T>>,
|
||||
cb: Box<dyn Fn(T, &mut EventCtx, &mut App) -> Transition>,
|
||||
) -> Box<dyn State> {
|
||||
Box::new(ChooseSomething {
|
||||
composite: Composite::new(Menu::new(ctx, choices).named("menu").container())
|
||||
.aligned(
|
||||
HorizontalAlignment::Centered(rect.center().x),
|
||||
VerticalAlignment::Below(rect.y2 + 15.0),
|
||||
)
|
||||
.build(ctx),
|
||||
cb,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static + Clone> State for ChooseSomething<T> {
|
||||
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
match self.composite.event(ctx) {
|
||||
Outcome::Clicked(x) => match x.as_ref() {
|
||||
"close" => Transition::Pop,
|
||||
_ => {
|
||||
// TODO We shouldn't need to clone everywhere
|
||||
let data = self.composite.menu::<T>("menu").current_choice().clone();
|
||||
(self.cb)(data, ctx, app)
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
if ctx.normal_left_click() && ctx.canvas.get_cursor_in_screen_space().is_none() {
|
||||
return Transition::Pop;
|
||||
}
|
||||
// new_below doesn't make an X button
|
||||
if ctx.input.key_pressed(Key::Escape) {
|
||||
return Transition::Pop;
|
||||
}
|
||||
Transition::Keep
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_baselayer(&self) -> DrawBaselayer {
|
||||
DrawBaselayer::PreviousState
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
State::grey_out_map(g, app);
|
||||
self.composite.draw(g);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user