try out a managed GUI state with, gasp, callbacks

This commit is contained in:
Dustin Carlino 2019-11-24 07:21:23 -08:00
parent 3511669c4e
commit 042db55009
4 changed files with 167 additions and 181 deletions

View File

@ -1,4 +1,4 @@
use crate::pregame::{MainMenu, TitleScreen};
use crate::pregame::{main_menu, TitleScreen};
use crate::render::DrawOptions;
use crate::sandbox::{GameplayMode, SandboxMode};
use crate::ui::{Flags, ShowEverything, UI};
@ -20,7 +20,7 @@ impl Game {
vec![Box::new(TitleScreen::new(ctx, &ui))]
} else {
vec![
Box::new(MainMenu::new(ctx)),
main_menu(ctx, &ui),
Box::new(SandboxMode::new(ctx, &mut ui, GameplayMode::Freeform)),
]
};

View File

@ -5,6 +5,7 @@ mod debug;
mod edit;
mod game;
mod helpers;
mod managed;
mod mission;
mod pregame;
mod render;

91
game/src/managed.rs Normal file
View File

@ -0,0 +1,91 @@
use crate::game::{State, Transition};
use crate::ui::UI;
use ezgui::{layout, Color, EventCtx, GfxCtx, Line, MultiText, ScreenPt, Text, TextButton};
type Callback = Box<dyn Fn(&mut EventCtx, &mut UI) -> Option<Transition>>;
pub struct ManagedGUIStateBuilder {
multi_txt: MultiText,
buttons: Vec<(String, Callback)>,
}
impl ManagedGUIStateBuilder {
pub fn draw_text(&mut self, txt: Text, pt: ScreenPt) {
self.multi_txt.add(txt, pt);
}
pub fn text_button(&mut self, label: &str, onclick: Callback) {
self.buttons.push((label.to_string(), onclick));
}
pub fn build(self, ctx: &EventCtx) -> Box<dyn State> {
Box::new(ManagedGUIState {
multi_txt: self.multi_txt,
// TODO Default style. Lots of variations.
buttons: self
.buttons
.into_iter()
.map(|(label, onclick)| {
(
TextButton::new(
Text::from(Line(label).fg(Color::BLACK)),
Color::WHITE,
Color::ORANGE,
ctx,
),
onclick,
)
})
.collect(),
})
}
}
pub struct ManagedGUIState {
multi_txt: MultiText,
buttons: Vec<(TextButton, Callback)>,
}
impl ManagedGUIState {
pub fn builder() -> ManagedGUIStateBuilder {
ManagedGUIStateBuilder {
multi_txt: MultiText::new(),
buttons: Vec::new(),
}
}
}
impl State for ManagedGUIState {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition {
layout::stack_vertically(
layout::ContainerOrientation::Centered,
ctx,
self.buttons
.iter_mut()
.map(|(btn, _)| btn as &mut dyn layout::Widget)
.collect(),
);
for (btn, onclick) in self.buttons.iter_mut() {
btn.event(ctx);
if btn.clicked() {
if let Some(t) = (onclick)(ctx, ui) {
return t;
}
}
}
Transition::Keep
}
fn draw_default_ui(&self) -> bool {
false
}
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
// Happens to be a nice background color too ;)
g.clear(ui.cs.get("grass"));
self.multi_txt.draw(g);
for (btn, _) in &self.buttons {
btn.draw(g);
}
}
}

View File

@ -1,15 +1,13 @@
use crate::abtest::setup::PickABTest;
use crate::challenges::challenges_picker;
use crate::game::{State, Transition};
use crate::managed::ManagedGUIState;
use crate::mission::MissionEditMode;
use crate::sandbox::{GameplayMode, SandboxMode};
use crate::tutorial::TutorialMode;
use crate::ui::UI;
use abstutil::elapsed_seconds;
use ezgui::{
layout, Color, EventCtx, EventLoopMode, GfxCtx, HorizontalAlignment, Line, MultiText, ScreenPt,
Text, TextButton, VerticalAlignment,
};
use ezgui::{layout, Color, EventCtx, EventLoopMode, GfxCtx, Line, ScreenPt, Text, TextButton};
use geom::{Duration, Line, Pt2D, Speed};
use map_model::Map;
use rand::Rng;
@ -46,7 +44,7 @@ impl State for TitleScreen {
// TODO or any keypress
self.play_btn.event(ctx);
if self.play_btn.clicked() {
return Transition::Replace(Box::new(MainMenu::new(ctx)));
return Transition::Replace(main_menu(ctx, ui));
}
self.screensaver.update(&mut self.rng, ctx, &ui.primary.map);
@ -59,185 +57,81 @@ impl State for TitleScreen {
}
}
pub struct MainMenu {
txts: MultiText,
// TODO Icon with text? Nah, just an image button on a rounded rectangle
tutorial_btn: TextButton,
sandbox_btn: TextButton,
challenges_btn: TextButton,
dev_tools_btn: TextButton,
abtest_btn: TextButton,
about_btn: TextButton,
pub fn main_menu(ctx: &EventCtx, ui: &UI) -> Box<dyn State> {
let mut state = ManagedGUIState::builder();
state.draw_text(
Text::from(Line("A/B STREET").size(50)).no_bg(),
ScreenPt::new(200.0, 100.0),
);
state.draw_text(
Text::from(Line("Created by Dustin Carlino")).no_bg(),
ScreenPt::new(250.0, 300.0),
);
state.draw_text(
Text::from(Line("Choose your game")).no_bg(),
ScreenPt::new(250.0, 500.0),
);
state.text_button(
"TUTORIAL",
Box::new(|ctx, _| Some(Transition::Push(Box::new(TutorialMode::new(ctx))))),
);
state.text_button(
"SANDBOX",
Box::new(|ctx, ui| {
Some(Transition::Push(Box::new(SandboxMode::new(
ctx,
ui,
GameplayMode::Freeform,
))))
}),
);
state.text_button(
"CHALLENGES",
Box::new(|_, _| Some(Transition::Push(challenges_picker()))),
);
if ui.primary.current_flags.dev {
state.text_button(
"INTERNAL DEV TOOLS",
Box::new(|ctx, _| Some(Transition::Push(Box::new(MissionEditMode::new(ctx))))),
);
state.text_button(
"INTERNAL A/B TEST MODE",
Box::new(|_, _| Some(Transition::Push(PickABTest::new()))),
);
}
state.text_button(
"About A/B Street",
Box::new(|ctx, _| Some(Transition::Push(about(ctx)))),
);
state.text_button(
"QUIT",
Box::new(|_, _| {
// TODO before_quit?
std::process::exit(0);
}),
);
state.build(ctx)
}
impl MainMenu {
pub fn new(ctx: &EventCtx) -> MainMenu {
let mut txts = MultiText::new();
// TODO Lots wrong here.
txts.add(
Text::from(Line("A/B STREET").size(50)).no_bg(),
ScreenPt::new(200.0, 100.0),
);
txts.add(
Text::from(Line("Created by Dustin Carlino")).no_bg(),
ScreenPt::new(250.0, 300.0),
);
txts.add(
Text::from(Line("Choose your game")).no_bg(),
ScreenPt::new(250.0, 500.0),
);
fn about(ctx: &EventCtx) -> Box<dyn State> {
let mut state = ManagedGUIState::builder();
MainMenu {
txts,
tutorial_btn: TextButton::new(
Text::from(Line("TUTORIAL").fg(Color::BLACK)),
Color::WHITE,
Color::ORANGE,
ctx,
),
sandbox_btn: TextButton::new(
Text::from(Line("SANDBOX").fg(Color::BLACK)),
Color::WHITE,
Color::ORANGE,
ctx,
),
challenges_btn: TextButton::new(
Text::from(Line("CHALLENGES").fg(Color::BLACK)),
Color::WHITE,
Color::ORANGE,
ctx,
),
// TODO hide unless ui.primary.current_flags.dev
dev_tools_btn: TextButton::new(
Text::from(Line("INTERNAL DEV TOOLS").fg(Color::BLACK)),
Color::WHITE,
Color::ORANGE,
ctx,
),
abtest_btn: TextButton::new(
Text::from(Line("INTERNAL A/B TEST MODE").fg(Color::BLACK)),
Color::WHITE,
Color::ORANGE,
ctx,
),
// TODO No background at all...
about_btn: TextButton::new(
Text::from(Line("About A/B Street").fg(Color::WHITE)),
Color::BLACK,
Color::ORANGE,
ctx,
),
}
}
}
let mut txt = Text::new().no_bg();
txt.add(Line("A/B STREET").size(50));
txt.add(Line("Created by Dustin Carlino"));
txt.add(Line(""));
txt.add(Line("Contact: dabreegster@gmail.com"));
txt.add(Line("Project: http://github.com/dabreegster/abstreet"));
txt.add(Line("Map data from OpenStreetMap and King County GIS"));
// TODO Lots more credits
// TODO centered
state.draw_text(txt, ScreenPt::new(100.0, 100.0));
impl State for MainMenu {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition {
// TODO How do we want to express layouting?
layout::stack_vertically(
layout::ContainerOrientation::Centered,
ctx,
vec![
&mut self.tutorial_btn,
&mut self.sandbox_btn,
&mut self.challenges_btn,
&mut self.dev_tools_btn,
&mut self.abtest_btn,
&mut self.about_btn,
],
);
state.text_button("BACK", Box::new(|_, _| Some(Transition::Pop)));
self.tutorial_btn.event(ctx);
self.sandbox_btn.event(ctx);
self.challenges_btn.event(ctx);
self.dev_tools_btn.event(ctx);
self.abtest_btn.event(ctx);
self.about_btn.event(ctx);
if self.tutorial_btn.clicked() {
return Transition::Push(Box::new(TutorialMode::new(ctx)));
}
if self.sandbox_btn.clicked() {
return Transition::Push(Box::new(SandboxMode::new(ctx, ui, GameplayMode::Freeform)));
}
if self.challenges_btn.clicked() {
return Transition::Push(challenges_picker());
}
if self.dev_tools_btn.clicked() {
return Transition::Push(Box::new(MissionEditMode::new(ctx)));
}
if self.abtest_btn.clicked() {
return Transition::Push(PickABTest::new());
}
if self.about_btn.clicked() {
return Transition::Push(Box::new(About::new(ctx)));
}
// TODO Hotkeys? How to communicate?
Transition::Keep
}
fn draw_default_ui(&self) -> bool {
false
}
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
g.clear(ui.cs.get("grass"));
self.txts.draw(g);
self.tutorial_btn.draw(g);
self.sandbox_btn.draw(g);
self.challenges_btn.draw(g);
self.dev_tools_btn.draw(g);
self.abtest_btn.draw(g);
self.about_btn.draw(g);
}
}
struct About {
txt: Text,
back_btn: TextButton,
}
impl About {
fn new(ctx: &EventCtx) -> About {
let mut txt = Text::new().no_bg();
txt.add(Line("A/B STREET").size(50));
txt.add(Line("Created by Dustin Carlino"));
txt.add(Line(""));
txt.add(Line("Contact: dabreegster@gmail.com"));
txt.add(Line("Project: http://github.com/dabreegster/abstreet"));
txt.add(Line("Map data from OpenStreetMap and King County GIS"));
// TODO Lots more credits
About {
txt,
// TODO Arrow and no background
back_btn: TextButton::new(Text::from(Line("BACK")), Color::BLACK, Color::ORANGE, ctx),
}
}
}
impl State for About {
fn event(&mut self, ctx: &mut EventCtx, _: &mut UI) -> Transition {
self.back_btn.event(ctx);
if self.back_btn.clicked() {
return Transition::Pop;
}
Transition::Keep
}
fn draw_default_ui(&self) -> bool {
false
}
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
g.clear(ui.cs.get("grass"));
g.draw_blocking_text(
&self.txt,
(HorizontalAlignment::Center, VerticalAlignment::Center),
);
self.back_btn.draw(g);
}
state.build(ctx)
}
const SPEED: Speed = Speed::const_meters_per_second(20.0);