Generalize the State/Transition GUI structure, moving it into widgetry.

Nothing about it is specific to A/B Street, and other apps built with
widgetry could organize themselves as a stack of states. This is also a
first step towards sharing more common code between A/B Street and a
future OSM viewer.

Mostly mechanical change. Some more cleanup / documentation coming up
next.
This commit is contained in:
Dustin Carlino 2020-10-22 17:34:59 -07:00
parent 89977b3b31
commit c2b6c917ae
59 changed files with 716 additions and 601 deletions

View File

@ -6,9 +6,9 @@ use rand::seq::SliceRandom;
use abstutil::Timer;
use geom::{Bounds, Circle, Distance, Duration, Pt2D, Time};
use map_model::{IntersectionID, Map, Traversable};
use map_model::{IntersectionID, Map, PermanentMapEdits, Traversable};
use sim::{Analytics, Sim, SimCallback, SimFlags};
use widgetry::{EventCtx, GfxCtx, Prerender};
use widgetry::{Canvas, EventCtx, GfxCtx, Prerender, SharedAppState};
use crate::challenges::HighScore;
use crate::colors::ColorScheme;
@ -671,3 +671,60 @@ impl SimCallback for FindDelayedIntersections {
}
}
}
impl SharedAppState for App {
fn before_event(&mut self) {
self.per_obj.reset();
}
fn draw_default(&self, g: &mut GfxCtx) {
self.draw(g, DrawOptions::new(), &ShowEverything::new());
}
fn dump_before_abort(&self, canvas: &Canvas) {
println!();
println!(
"********************************************************************************"
);
canvas.save_camera_state(self.primary.map.get_name());
println!(
"Crash! Please report to https://github.com/dabreegster/abstreet/issues/ and include \
all output.txt; at least everything starting from the stack trace above!"
);
println!();
self.primary.sim.dump_before_abort();
println!();
println!("Camera:");
println!(
r#"{{ "cam_x": {}, "cam_y": {}, "cam_zoom": {} }}"#,
canvas.cam_x, canvas.cam_y, canvas.cam_zoom
);
println!();
if self.primary.map.get_edits().commands.is_empty() {
println!("No edits");
} else {
abstutil::write_json(
"edits_during_crash.json".to_string(),
&PermanentMapEdits::to_permanent(self.primary.map.get_edits(), &self.primary.map),
);
println!("Please include edits_during_crash.json in your bug report.");
}
// Repeat, because it can be hard to see the top of the report if it's long
println!();
println!(
"Crash! Please report to https://github.com/dabreegster/abstreet/issues/ and include \
all output.txt; at least everything above here until the start of the report!"
);
println!(
"********************************************************************************"
);
}
fn before_quit(&self, canvas: &Canvas) {
canvas.save_camera_state(self.primary.map.get_name());
}
}

View File

@ -4,10 +4,13 @@ use abstutil::{prettyprint_usize, Timer};
use geom::{Duration, Percent, Time};
use map_model::Map;
use sim::{AlertHandler, OrigPersonID, Scenario, Sim, SimFlags, SimOptions};
use widgetry::{Btn, Color, EventCtx, GfxCtx, Key, Line, Outcome, Panel, Text, TextExt, Widget};
use widgetry::{
Btn, Color, DrawBaselayer, EventCtx, GfxCtx, Key, Line, Outcome, Panel, State, Text, TextExt,
Widget,
};
use crate::app::App;
use crate::game::{DrawBaselayer, State, Transition};
use crate::game::Transition;
use crate::sandbox::gameplay::Tutorial;
use crate::sandbox::{GameplayMode, SandboxMode, TutorialState};
@ -17,7 +20,7 @@ pub struct Challenge {
pub description: Vec<String>,
pub alias: String,
pub gameplay: GameplayMode,
pub cutscene: Option<fn(&mut EventCtx, &App, &GameplayMode) -> Box<dyn State>>,
pub cutscene: Option<fn(&mut EventCtx, &App, &GameplayMode) -> Box<dyn State<App>>>,
}
pub struct HighScore {
@ -117,7 +120,7 @@ pub struct ChallengesPicker {
}
impl ChallengesPicker {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
ChallengesPicker::make(ctx, app, None)
}
@ -125,7 +128,7 @@ impl ChallengesPicker {
ctx: &mut EventCtx,
app: &App,
challenge_and_stage: Option<(String, usize)>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
let mut links = BTreeMap::new();
let mut master_col = vec![
Btn::svg_def("system/assets/pregame/back.svg")
@ -244,7 +247,7 @@ impl ChallengesPicker {
}
}
impl State for ChallengesPicker {
impl State<App> for ChallengesPicker {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {

View File

@ -1,11 +1,12 @@
use geom::{Distance, Polygon, Pt2D};
use map_model::City;
use widgetry::{
Btn, Color, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel, ScreenPt, Text, Widget,
Btn, Color, DrawBaselayer, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel, ScreenPt,
State, Text, Widget,
};
use crate::app::App;
use crate::game::{DrawBaselayer, State, Transition};
use crate::game::Transition;
use crate::helpers::{grey_out_map, nice_map_name};
use crate::load::MapLoader;
use crate::render::DrawArea;
@ -24,7 +25,7 @@ impl CityPicker {
ctx: &mut EventCtx,
app: &mut App,
on_load: Box<dyn FnOnce(&mut EventCtx, &mut App) -> Transition>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
app.primary.current_selection = None;
let mut batch = GeomBatch::new();
@ -104,7 +105,7 @@ impl CityPicker {
}
}
impl State for CityPicker {
impl State<App> for CityPicker {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {

View File

@ -2,12 +2,12 @@ use geom::{Distance, Polygon};
use map_model::{connectivity, BuildingID};
use widgetry::{
Btn, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome,
Panel, VerticalAlignment, Widget,
Panel, State, VerticalAlignment, Widget,
};
use crate::app::App;
use crate::common::heatmap::Grid;
use crate::game::{State, Transition};
use crate::game::Transition;
// TODO Move cursor live
pub struct IsochroneViewer {
@ -16,7 +16,7 @@ pub struct IsochroneViewer {
}
impl IsochroneViewer {
pub fn new(ctx: &mut EventCtx, app: &App, start: BuildingID) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &App, start: BuildingID) -> Box<dyn State<App>> {
let draw = make_isochrone(ctx, app, start);
Box::new(IsochroneViewer {
panel: Panel::new(Widget::col(vec![
@ -35,7 +35,7 @@ impl IsochroneViewer {
}
}
impl State for IsochroneViewer {
impl State<App> for IsochroneViewer {
fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
ctx.canvas_movement();

View File

@ -3,12 +3,12 @@ use std::collections::HashSet;
use map_model::RoadID;
use widgetry::{
Autocomplete, Btn, Color, Drawable, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel,
Text, Widget,
State, Text, Widget,
};
use crate::app::App;
use crate::common::Warping;
use crate::game::{State, Transition};
use crate::game::Transition;
use crate::helpers::{grey_out_map, ID};
// TODO Canonicalize names, handling abbreviations like east/e and street/st
@ -17,7 +17,7 @@ pub struct Navigator {
}
impl Navigator {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
Box::new(Navigator {
panel: Panel::new(Widget::col(vec![
Widget::row(vec![
@ -43,7 +43,7 @@ impl Navigator {
}
}
impl State for Navigator {
impl State<App> for Navigator {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
@ -84,7 +84,7 @@ struct CrossStreet {
}
impl CrossStreet {
fn new(ctx: &mut EventCtx, app: &App, first: Vec<RoadID>) -> Box<dyn State> {
fn new(ctx: &mut EventCtx, app: &App, first: Vec<RoadID>) -> Box<dyn State<App>> {
let map = &app.primary.map;
let mut cross_streets = HashSet::new();
let mut batch = GeomBatch::new();
@ -134,7 +134,7 @@ impl CrossStreet {
}
}
impl State for CrossStreet {
impl State<App> for CrossStreet {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
let map = &app.primary.map;
@ -199,7 +199,7 @@ struct SearchBuildings {
}
impl SearchBuildings {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
Box::new(SearchBuildings {
panel: Panel::new(Widget::col(vec![
Widget::row(vec![
@ -249,7 +249,7 @@ impl SearchBuildings {
}
}
impl State for SearchBuildings {
impl State<App> for SearchBuildings {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {

View File

@ -3,11 +3,13 @@ use std::collections::BTreeMap;
use geom::Pt2D;
use map_model::{AreaID, BuildingID, BusRouteID, IntersectionID, LaneID, RoadID};
use sim::{PedestrianID, PersonID, TripID};
use widgetry::{Btn, EventCtx, GfxCtx, Key, Line, Outcome, Panel, Text, TextExt, Warper, Widget};
use widgetry::{
Btn, EventCtx, GfxCtx, Key, Line, Outcome, Panel, State, Text, TextExt, Warper, Widget,
};
use crate::app::{App, PerMap};
use crate::common::Tab;
use crate::game::{PopupMsg, State, Transition};
use crate::game::{PopupMsg, Transition};
use crate::helpers::{grey_out_map, ID};
use crate::info::OpenTrip;
use crate::sandbox::SandboxMode;
@ -26,7 +28,7 @@ impl Warping {
target_cam_zoom: Option<f64>,
id: Option<ID>,
primary: &mut PerMap,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
primary.last_warped_from = Some((ctx.canvas.center_to_map_pt(), ctx.canvas.cam_zoom));
Box::new(Warping {
warper: Warper::new(ctx, pt, target_cam_zoom),
@ -35,7 +37,7 @@ impl Warping {
}
}
impl State for Warping {
impl State<App> for Warping {
fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
if self.warper.event(ctx) {
Transition::Keep
@ -70,7 +72,7 @@ pub struct DebugWarp {
}
impl DebugWarp {
pub fn new(ctx: &mut EventCtx) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx) -> Box<dyn State<App>> {
let c = ctx.style().hotkey_color;
Box::new(DebugWarp {
panel: Panel::new(Widget::col(vec![
@ -124,7 +126,7 @@ impl DebugWarp {
}
}
impl State for DebugWarp {
impl State<App> for DebugWarp {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {

View File

@ -1,10 +1,10 @@
use widgetry::{
hotkeys, Btn, Color, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel, RewriteColor,
Text, Widget,
hotkeys, Btn, Color, DrawBaselayer, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel,
RewriteColor, State, Text, Widget,
};
use crate::app::App;
use crate::game::{DrawBaselayer, State, Transition};
use crate::game::Transition;
use crate::helpers::grey_out_map;
pub struct CutsceneBuilder {
@ -65,7 +65,7 @@ impl CutsceneBuilder {
ctx: &mut EventCtx,
app: &App,
make_task: Box<dyn Fn(&mut EventCtx) -> Widget>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
Box::new(CutscenePlayer {
panel: make_panel(ctx, app, &self.name, &self.scenes, &make_task, 0),
name: self.name,
@ -84,7 +84,7 @@ struct CutscenePlayer {
make_task: Box<dyn Fn(&mut EventCtx) -> Widget>,
}
impl State for CutscenePlayer {
impl State<App> for CutscenePlayer {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
@ -288,7 +288,7 @@ pub struct FYI {
}
impl FYI {
pub fn new(ctx: &mut EventCtx, contents: Widget, bg: Color) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, contents: Widget, bg: Color) -> Box<dyn State<App>> {
Box::new(FYI {
panel: Panel::new(
Widget::custom_col(vec![
@ -307,7 +307,7 @@ impl FYI {
}
}
impl State for FYI {
impl State<App> for FYI {
fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {

View File

@ -3,12 +3,12 @@ use std::collections::HashSet;
use map_model::{connectivity, LaneID, Map, PathConstraints};
use widgetry::{
Btn, Choice, Color, Drawable, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel,
TextExt, VerticalAlignment, Widget,
State, TextExt, VerticalAlignment, Widget,
};
use crate::app::App;
use crate::common::ColorDiscrete;
use crate::game::{State, Transition};
use crate::game::Transition;
pub struct Floodfiller {
panel: Panel,
@ -18,11 +18,11 @@ pub struct Floodfiller {
}
impl Floodfiller {
pub fn floodfill(ctx: &mut EventCtx, app: &App, l: LaneID) -> Box<dyn State> {
pub fn floodfill(ctx: &mut EventCtx, app: &App, l: LaneID) -> Box<dyn State<App>> {
let constraints = PathConstraints::from_lt(app.primary.map.get_l(l).lane_type);
Floodfiller::new(ctx, app, Source::Floodfill(l), constraints)
}
pub fn scc(ctx: &mut EventCtx, app: &App, l: LaneID) -> Box<dyn State> {
pub fn scc(ctx: &mut EventCtx, app: &App, l: LaneID) -> Box<dyn State<App>> {
let constraints = PathConstraints::from_lt(app.primary.map.get_l(l).lane_type);
Floodfiller::new(ctx, app, Source::SCC, constraints)
}
@ -32,7 +32,7 @@ impl Floodfiller {
app: &App,
source: Source,
constraints: PathConstraints,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
let (reachable_lanes, unreachable_lanes, title) =
source.calculate(&app.primary.map, constraints);
let mut colorer = ColorDiscrete::new(
@ -80,7 +80,7 @@ impl Floodfiller {
}
}
impl State for Floodfiller {
impl State<App> for Floodfiller {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
if ctx.redo_mouseover() {
app.recalculate_current_selection(ctx);

View File

@ -5,13 +5,14 @@ use geom::{Distance, Pt2D};
use map_model::{osm, ControlTrafficSignal, NORMAL_LANE_THICKNESS};
use sim::{AgentID, Sim};
use widgetry::{
lctrl, Btn, Checkbox, Choice, Color, Drawable, EventCtx, GeomBatch, GfxCtx,
HorizontalAlignment, Key, Line, Outcome, Panel, Text, UpdateType, VerticalAlignment, Widget,
lctrl, Btn, Checkbox, Choice, Color, DrawBaselayer, Drawable, EventCtx, GeomBatch, GfxCtx,
HorizontalAlignment, Key, Line, Outcome, Panel, State, Text, UpdateType, VerticalAlignment,
Widget,
};
use crate::app::{App, ShowLayers, ShowObject};
use crate::common::{tool_panel, CommonState, ContextualActions};
use crate::game::{ChooseSomething, DrawBaselayer, PopupMsg, PromptInput, State, Transition};
use crate::game::{ChooseSomething, PopupMsg, PromptInput, Transition};
use crate::helpers::ID;
use crate::load::MapLoader;
use crate::options::OptionsPanel;
@ -39,7 +40,7 @@ pub struct DebugMode {
}
impl DebugMode {
pub fn new(ctx: &mut EventCtx) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx) -> Box<dyn State<App>> {
Box::new(DebugMode {
panel: Panel::new(Widget::col(vec![
Widget::row(vec![
@ -111,7 +112,7 @@ impl DebugMode {
}
}
impl State for DebugMode {
impl State<App> for DebugMode {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();
@ -737,7 +738,7 @@ struct ScreenshotTest {
}
impl ScreenshotTest {
fn new(ctx: &mut EventCtx, app: &App, mut todo_maps: Vec<&'static str>) -> Box<dyn State> {
fn new(ctx: &mut EventCtx, app: &App, mut todo_maps: Vec<&'static str>) -> Box<dyn State<App>> {
MapLoader::new(
ctx,
app,
@ -752,7 +753,7 @@ impl ScreenshotTest {
}
}
impl State for ScreenshotTest {
impl State<App> for ScreenshotTest {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
if self.screenshot_done {
if self.todo_maps.is_empty() {

View File

@ -3,13 +3,13 @@
use abstutil::Counter;
use map_model::{IntersectionID, PathStep, RoadID, Traversable};
use widgetry::{
Btn, Color, Drawable, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, Text,
VerticalAlignment, Widget,
Btn, Color, Drawable, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State,
Text, VerticalAlignment, Widget,
};
use crate::app::App;
use crate::common::{ColorLegend, ColorNetwork, CommonState};
use crate::game::{State, Transition};
use crate::game::Transition;
use crate::helpers::ID;
pub struct PathCounter {
@ -25,7 +25,7 @@ impl PathCounter {
ctx: &mut EventCtx,
app: &App,
i: IntersectionID,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
let map = &app.primary.map;
let sim = &app.primary.sim;
let mut cnt = Counter::new();
@ -84,7 +84,7 @@ impl PathCounter {
}
}
impl State for PathCounter {
impl State<App> for PathCounter {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();
if ctx.redo_mouseover() {

View File

@ -1,11 +1,11 @@
use geom::{Polygon, Pt2D, Triangle};
use widgetry::{
Btn, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, Slider, Text,
TextExt, VerticalAlignment, Widget,
Btn, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, Slider,
State, Text, TextExt, VerticalAlignment, Widget,
};
use crate::app::App;
use crate::game::{State, Transition};
use crate::game::Transition;
pub struct PolygonDebugger {
panel: Panel,
@ -26,7 +26,7 @@ impl PolygonDebugger {
noun: &str,
items: Vec<Item>,
center: Option<Pt2D>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
Box::new(PolygonDebugger {
panel: Panel::new(Widget::col(vec![
Widget::row(vec![
@ -55,7 +55,7 @@ impl PolygonDebugger {
}
}
impl State for PolygonDebugger {
impl State<App> for PolygonDebugger {
fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
ctx.canvas_movement();

View File

@ -3,12 +3,12 @@ use map_model::BuildingID;
use sim::{Scenario, TripEndpoint};
use widgetry::{
Btn, Checkbox, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line,
Outcome, Panel, Text, VerticalAlignment, Widget,
Outcome, Panel, State, Text, VerticalAlignment, Widget,
};
use crate::app::App;
use crate::common::{make_heatmap, HeatmapOptions};
use crate::game::{State, Transition};
use crate::game::Transition;
use crate::helpers::{amenity_type, ID};
pub struct PopularDestinations {
@ -18,7 +18,7 @@ pub struct PopularDestinations {
}
impl PopularDestinations {
pub fn new(ctx: &mut EventCtx, app: &App, scenario: &Scenario) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &App, scenario: &Scenario) -> Box<dyn State<App>> {
let mut per_bldg = Counter::new();
for p in &scenario.people {
for trip in &p.trips {
@ -35,7 +35,7 @@ impl PopularDestinations {
app: &App,
per_bldg: Counter<BuildingID>,
opts: Option<HeatmapOptions>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
let map = &app.primary.map;
let mut batch = GeomBatch::new();
let controls = if let Some(ref o) = opts {
@ -108,7 +108,7 @@ impl PopularDestinations {
}
}
impl State for PopularDestinations {
impl State<App> for PopularDestinations {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();
if ctx.redo_mouseover() {

View File

@ -8,12 +8,12 @@ use kml::ExtraShapes;
use map_model::BuildingID;
use widgetry::{
lctrl, Btn, Choice, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key,
Line, Outcome, Panel, Text, TextExt, VerticalAlignment, Widget,
Line, Outcome, Panel, State, Text, TextExt, VerticalAlignment, Widget,
};
use crate::app::App;
use crate::colors::ColorScheme;
use crate::game::{ChooseSomething, State, Transition};
use crate::game::{ChooseSomething, Transition};
pub struct ViewKML {
panel: Panel,
@ -37,7 +37,7 @@ const RADIUS: Distance = Distance::const_meters(5.0);
const THICKNESS: Distance = Distance::const_meters(2.0);
impl ViewKML {
pub fn new(ctx: &mut EventCtx, app: &App, path: Option<String>) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &App, path: Option<String>) -> Box<dyn State<App>> {
ctx.loading_screen("load kml", |ctx, mut timer| {
let raw_shapes = if let Some(ref path) = path {
if path.ends_with(".kml") {
@ -151,7 +151,7 @@ impl ViewKML {
}
}
impl State for ViewKML {
impl State<App> for ViewKML {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();
if ctx.redo_mouseover() {

View File

@ -8,12 +8,12 @@ use geom::{Distance, FindClosest, PolyLine, Polygon};
use map_model::{osm, RoadID};
use widgetry::{
Btn, Checkbox, Choice, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key,
Line, Menu, Outcome, Panel, Text, TextExt, VerticalAlignment, Widget,
Line, Menu, Outcome, Panel, State, Text, TextExt, VerticalAlignment, Widget,
};
use crate::app::App;
use crate::common::{CityPicker, ColorLegend};
use crate::game::{PopupMsg, State, Transition};
use crate::game::{PopupMsg, Transition};
use crate::helpers::{nice_map_name, open_browser, ID};
pub struct ParkingMapper {
@ -43,7 +43,7 @@ pub enum Value {
}
impl ParkingMapper {
pub fn new(ctx: &mut EventCtx, app: &mut App) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &mut App) -> Box<dyn State<App>> {
app.primary.current_selection = None;
ParkingMapper::make(ctx, app, Show::TODO, BTreeMap::new())
}
@ -53,7 +53,7 @@ impl ParkingMapper {
app: &mut App,
show: Show,
data: BTreeMap<WayID, Value>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
app.opts.min_zoom_for_detail = 2.0;
let map = &app.primary.map;
@ -186,7 +186,7 @@ impl ParkingMapper {
}
}
impl State for ParkingMapper {
impl State<App> for ParkingMapper {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
let map = &app.primary.map;
@ -397,7 +397,7 @@ impl ChangeWay {
selected: &HashSet<RoadID>,
show: Show,
data: BTreeMap<WayID, Value>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
let map = &app.primary.map;
let osm_way_id = map
.get_r(*selected.iter().next().unwrap())
@ -457,7 +457,7 @@ impl ChangeWay {
}
}
impl State for ChangeWay {
impl State<App> for ChangeWay {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();
match self.panel.event(ctx) {

View File

@ -1,13 +1,13 @@
use abstutil::Timer;
use geom::{LonLat, Percent};
use widgetry::{
lctrl, Btn, Choice, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, TextExt,
VerticalAlignment, Widget,
lctrl, Btn, Choice, DrawBaselayer, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome,
Panel, State, TextExt, VerticalAlignment, Widget,
};
use crate::app::App;
use crate::common::CityPicker;
use crate::game::{ChooseSomething, DrawBaselayer, State, Transition};
use crate::game::{ChooseSomething, Transition};
use crate::helpers::nice_map_name;
mod destinations;
@ -22,7 +22,7 @@ pub struct DevToolsMode {
}
impl DevToolsMode {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
Box::new(DevToolsMode {
panel: Panel::new(Widget::col(vec![
Widget::row(vec![
@ -54,7 +54,7 @@ impl DevToolsMode {
}
}
impl State for DevToolsMode {
impl State<App> for DevToolsMode {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {

View File

@ -3,13 +3,13 @@ use std::io::{Error, Write};
use geom::{Circle, Distance, LonLat, Pt2D, Ring};
use widgetry::{
Btn, Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, Text,
Btn, Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State, Text,
VerticalAlignment, Widget,
};
use crate::app::App;
use crate::common::CommonState;
use crate::game::{State, Transition};
use crate::game::Transition;
const POINT_RADIUS: Distance = Distance::const_meters(10.0);
// Localized and internal, so don't put in ColorScheme.
@ -27,7 +27,7 @@ pub struct PolygonEditor {
}
impl PolygonEditor {
pub fn new(ctx: &mut EventCtx, name: String, mut points: Vec<LonLat>) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, name: String, mut points: Vec<LonLat>) -> Box<dyn State<App>> {
points.pop();
Box::new(PolygonEditor {
panel: Panel::new(Widget::col(vec![
@ -49,7 +49,7 @@ impl PolygonEditor {
}
}
impl State for PolygonEditor {
impl State<App> for PolygonEditor {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
let gps_bounds = app.primary.map.get_gps_bounds();

View File

@ -1,14 +1,14 @@
use abstutil::prettyprint_usize;
use sim::Scenario;
use widgetry::{
Btn, Color, Drawable, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, Text,
VerticalAlignment, Widget,
Btn, Color, Drawable, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State,
Text, VerticalAlignment, Widget,
};
use crate::app::App;
use crate::common::{ColorDiscrete, CommonState};
use crate::devtools::destinations::PopularDestinations;
use crate::game::{State, Transition};
use crate::game::Transition;
pub struct ScenarioManager {
panel: Panel,
@ -18,7 +18,7 @@ pub struct ScenarioManager {
}
impl ScenarioManager {
pub fn new(scenario: Scenario, ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
pub fn new(scenario: Scenario, ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
let mut colorer = ColorDiscrete::new(
app,
vec![
@ -85,7 +85,7 @@ impl ScenarioManager {
}
}
impl State for ScenarioManager {
impl State<App> for ScenarioManager {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {

View File

@ -2,13 +2,14 @@ use serde::{Deserialize, Serialize};
use geom::{Distance, LonLat, PolyLine, Polygon, Pt2D, Ring};
use widgetry::{
lctrl, Btn, Choice, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key,
Line, Outcome, Panel, RewriteColor, Text, VerticalAlignment, Widget,
lctrl, Btn, Choice, Color, DrawBaselayer, Drawable, EventCtx, GeomBatch, GfxCtx,
HorizontalAlignment, Key, Line, Outcome, Panel, RewriteColor, State, Text, VerticalAlignment,
Widget,
};
use crate::app::{App, ShowEverything};
use crate::common::CommonState;
use crate::game::{ChooseSomething, DrawBaselayer, PromptInput, State, Transition};
use crate::game::{ChooseSomething, PromptInput, Transition};
use crate::render::DrawOptions;
// TODO This is a really great example of things that widgetry ought to make easier. Maybe a radio
@ -37,7 +38,7 @@ enum Mode {
}
impl StoryMapEditor {
pub fn new(ctx: &mut EventCtx) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx) -> Box<dyn State<App>> {
let story = StoryMap::new();
let mode = Mode::View;
let dirty = false;
@ -55,7 +56,7 @@ impl StoryMapEditor {
}
}
impl State for StoryMapEditor {
impl State<App> for StoryMapEditor {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.mode {
Mode::View => {

View File

@ -4,13 +4,13 @@ use geom::Speed;
use map_model::{LaneType, RoadID};
use widgetry::{
hotkeys, Btn, Choice, Color, Drawable, EventCtx, GfxCtx, HorizontalAlignment, Key, Line,
Outcome, Panel, Text, TextExt, VerticalAlignment, Widget,
Outcome, Panel, State, Text, TextExt, VerticalAlignment, Widget,
};
use crate::app::App;
use crate::edit::select::RoadSelector;
use crate::edit::{apply_map_edits, speed_limit_choices, try_change_lt, ConfirmDiscard};
use crate::game::{PopupMsg, State, Transition};
use crate::game::{PopupMsg, Transition};
pub struct BulkSelect {
panel: Panel,
@ -18,7 +18,7 @@ pub struct BulkSelect {
}
impl BulkSelect {
pub fn new(ctx: &mut EventCtx, app: &mut App, start: RoadID) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &mut App, start: RoadID) -> Box<dyn State<App>> {
let selector = RoadSelector::new(ctx, app, btreeset! {start});
let panel = make_select_panel(ctx, &selector);
Box::new(BulkSelect { panel, selector })
@ -53,7 +53,7 @@ fn make_select_panel(ctx: &mut EventCtx, selector: &RoadSelector) -> Panel {
.build(ctx)
}
impl State for BulkSelect {
impl State<App> for BulkSelect {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
@ -122,7 +122,7 @@ struct BulkEdit {
}
impl BulkEdit {
fn new(ctx: &mut EventCtx, roads: Vec<RoadID>, preview: Drawable) -> Box<dyn State> {
fn new(ctx: &mut EventCtx, roads: Vec<RoadID>, preview: Drawable) -> Box<dyn State<App>> {
Box::new(BulkEdit {
panel: Panel::new(Widget::col(vec![
Line(format!("Editing {} roads", roads.len()))
@ -158,7 +158,7 @@ impl BulkEdit {
}
}
impl State for BulkEdit {
impl State<App> for BulkEdit {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();
@ -272,7 +272,7 @@ fn make_bulk_edits(
roads: &Vec<RoadID>,
speed_limit: Option<Speed>,
lt_transformations: Vec<(Option<LaneType>, Option<LaneType>)>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
let mut speed_changes = 0;
let mut lt_changes = 0;
let mut errors = Vec::new();

View File

@ -3,12 +3,12 @@ use std::collections::BTreeSet;
use geom::ArrowCap;
use map_model::{IntersectionCluster, IntersectionID};
use widgetry::{
Btn, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Outcome, Panel, VerticalAlignment,
Widget,
Btn, DrawBaselayer, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Outcome, Panel,
State, VerticalAlignment, Widget,
};
use crate::app::{App, ShowEverything};
use crate::game::{DrawBaselayer, State, Transition};
use crate::game::Transition;
use crate::render::{DrawOptions, DrawUberTurnGroup, BIG_ARROW_THICKNESS};
pub struct ClusterTrafficSignalEditor {
@ -20,7 +20,7 @@ pub struct ClusterTrafficSignalEditor {
}
impl ClusterTrafficSignalEditor {
pub fn new(ctx: &mut EventCtx, app: &mut App, ic: &IntersectionCluster) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &mut App, ic: &IntersectionCluster) -> Box<dyn State<App>> {
app.primary.current_selection = None;
Box::new(ClusterTrafficSignalEditor {
panel: Panel::new(Widget::row(vec![
@ -35,7 +35,7 @@ impl ClusterTrafficSignalEditor {
}
}
impl State for ClusterTrafficSignalEditor {
impl State<App> for ClusterTrafficSignalEditor {
fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {

View File

@ -1,7 +1,7 @@
use map_model::{EditCmd, LaneID, LaneType, Map};
use widgetry::{
Btn, Choice, Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, Text,
TextExt, VerticalAlignment, Widget,
Btn, Choice, Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State,
Text, TextExt, VerticalAlignment, Widget,
};
use crate::app::App;
@ -10,7 +10,7 @@ use crate::edit::zones::ZoneEditor;
use crate::edit::{
apply_map_edits, can_edit_lane, maybe_edit_intersection, speed_limit_choices, try_change_lt,
};
use crate::game::{State, Transition};
use crate::game::Transition;
use crate::helpers::ID;
use crate::render::Renderable;
use crate::sandbox::GameplayMode;
@ -22,7 +22,12 @@ pub struct LaneEditor {
}
impl LaneEditor {
pub fn new(ctx: &mut EventCtx, app: &App, l: LaneID, mode: GameplayMode) -> Box<dyn State> {
pub fn new(
ctx: &mut EventCtx,
app: &App,
l: LaneID,
mode: GameplayMode,
) -> Box<dyn State<App>> {
let mut row = Vec::new();
let lt = app.primary.map.get_l(l).lane_type;
for (icon, label, key, active) in vec![
@ -105,7 +110,7 @@ impl LaneEditor {
}
}
impl State for LaneEditor {
impl State<App> for LaneEditor {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();
// Restrict what can be selected.

View File

@ -7,7 +7,7 @@ use geom::Speed;
use map_model::{EditCmd, IntersectionID, LaneID, LaneType, MapEdits};
use widgetry::{
lctrl, Btn, Choice, Color, Drawable, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Menu,
Outcome, Panel, Text, TextExt, VerticalAlignment, Widget,
Outcome, Panel, State, Text, TextExt, VerticalAlignment, Widget,
};
pub use self::cluster_traffic_signals::ClusterTrafficSignalEditor;
@ -19,7 +19,7 @@ pub use self::validate::{check_blackholes, check_sidewalk_connectivity, try_chan
use crate::app::App;
use crate::common::{tool_panel, ColorLegend, CommonState, Warping};
use crate::debug::DebugMode;
use crate::game::{ChooseSomething, PopupMsg, State, Transition};
use crate::game::{ChooseSomething, PopupMsg, Transition};
use crate::helpers::{grey_out_map, ID};
use crate::options::OptionsPanel;
use crate::render::DrawMap;
@ -53,7 +53,7 @@ pub struct EditMode {
}
impl EditMode {
pub fn new(ctx: &mut EventCtx, app: &mut App, mode: GameplayMode) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &mut App, mode: GameplayMode) -> Box<dyn State<App>> {
let orig_dirty = app.primary.dirty_from_edits;
assert!(app.primary.suspended_sim.is_none());
app.primary.suspended_sim = Some(app.primary.clear_sim());
@ -139,7 +139,7 @@ impl EditMode {
}
}
impl State for EditMode {
impl State<App> for EditMode {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
{
let edits = app.primary.map.get_edits();
@ -389,7 +389,7 @@ impl SaveEdits {
discard: bool,
cancel: Option<Transition>,
on_success: Box<dyn Fn(&mut EventCtx, &mut App)>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
let initial_name = if app.primary.map.unsaved_edits() {
String::new()
} else {
@ -470,7 +470,7 @@ impl SaveEdits {
}
}
impl State for SaveEdits {
impl State<App> for SaveEdits {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
@ -519,7 +519,7 @@ struct LoadEdits {
}
impl LoadEdits {
fn new(ctx: &mut EventCtx, app: &App, mode: GameplayMode) -> Box<dyn State> {
fn new(ctx: &mut EventCtx, app: &App, mode: GameplayMode) -> Box<dyn State<App>> {
let current_edits_name = &app.primary.map.get_edits().edits_name;
let your_edits = vec![
Line("Your proposals").small_heading().draw(ctx),
@ -560,7 +560,7 @@ impl LoadEdits {
}
}
impl State for LoadEdits {
impl State<App> for LoadEdits {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => {
@ -733,7 +733,7 @@ pub fn maybe_edit_intersection(
app: &mut App,
id: IntersectionID,
mode: &GameplayMode,
) -> Option<Box<dyn State>> {
) -> Option<Box<dyn State<App>>> {
if app.primary.map.maybe_get_stop_sign(id).is_some()
&& mode.can_edit_stop_signs()
&& app.per_obj.left_click(ctx, "edit stop signs")
@ -839,7 +839,7 @@ pub struct ConfirmDiscard {
}
impl ConfirmDiscard {
pub fn new(ctx: &mut EventCtx, discard: Box<dyn Fn(&mut App)>) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, discard: Box<dyn Fn(&mut App)>) -> Box<dyn State<App>> {
Box::new(ConfirmDiscard {
discard,
panel: Panel::new(Widget::col(vec![
@ -862,7 +862,7 @@ impl ConfirmDiscard {
}
}
impl State for ConfirmDiscard {
impl State<App> for ConfirmDiscard {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {

View File

@ -1,13 +1,13 @@
use geom::{Duration, Time};
use map_model::{BusRouteID, EditCmd};
use widgetry::{
Btn, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, Spinner, TextExt,
Btn, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, Spinner, State, TextExt,
VerticalAlignment, Widget,
};
use crate::app::App;
use crate::edit::apply_map_edits;
use crate::game::{State, Transition};
use crate::game::Transition;
pub struct RouteEditor {
panel: Panel,
@ -15,7 +15,7 @@ pub struct RouteEditor {
}
impl RouteEditor {
pub fn new(ctx: &mut EventCtx, app: &mut App, id: BusRouteID) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &mut App, id: BusRouteID) -> Box<dyn State<App>> {
app.primary.current_selection = None;
let route = app.primary.map.get_br(id);
@ -42,7 +42,7 @@ impl RouteEditor {
}
}
impl State for RouteEditor {
impl State<App> for RouteEditor {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();

View File

@ -8,14 +8,14 @@ use map_model::{
ControlStopSign, ControlTrafficSignal, EditCmd, EditIntersection, IntersectionID, RoadID,
};
use widgetry::{
Btn, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, Text,
Btn, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State, Text,
VerticalAlignment, Widget,
};
use crate::app::App;
use crate::common::CommonState;
use crate::edit::{apply_map_edits, check_sidewalk_connectivity, TrafficSignalEditor};
use crate::game::{State, Transition};
use crate::game::Transition;
use crate::render::DrawIntersection;
use crate::sandbox::GameplayMode;
@ -36,7 +36,7 @@ impl StopSignEditor {
app: &mut App,
id: IntersectionID,
mode: GameplayMode,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
app.primary.current_selection = None;
let geom = app
.primary
@ -77,7 +77,7 @@ impl StopSignEditor {
}
}
impl State for StopSignEditor {
impl State<App> for StopSignEditor {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();

View File

@ -4,13 +4,14 @@ use map_model::{
ControlStopSign, ControlTrafficSignal, EditCmd, EditIntersection, IntersectionID, PhaseType,
};
use widgetry::{
Btn, Checkbox, Choice, EventCtx, GfxCtx, Key, Line, Outcome, Panel, Spinner, TextExt, Widget,
Btn, Checkbox, Choice, DrawBaselayer, EventCtx, GfxCtx, Key, Line, Outcome, Panel, Spinner,
State, TextExt, Widget,
};
use crate::app::App;
use crate::edit::traffic_signals::{BundleEdits, TrafficSignalEditor};
use crate::edit::{apply_map_edits, check_sidewalk_connectivity, StopSignEditor};
use crate::game::{ChooseSomething, DrawBaselayer, State, Transition};
use crate::game::{ChooseSomething, Transition};
use crate::sandbox::GameplayMode;
pub struct ChangeDuration {
@ -19,7 +20,11 @@ pub struct ChangeDuration {
}
impl ChangeDuration {
pub fn new(ctx: &mut EventCtx, signal: &ControlTrafficSignal, idx: usize) -> Box<dyn State> {
pub fn new(
ctx: &mut EventCtx,
signal: &ControlTrafficSignal,
idx: usize,
) -> Box<dyn State<App>> {
Box::new(ChangeDuration {
panel: Panel::new(Widget::col(vec![
Widget::row(vec![
@ -70,7 +75,7 @@ impl ChangeDuration {
}
}
impl State for ChangeDuration {
impl State<App> for ChangeDuration {
fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
@ -119,7 +124,7 @@ pub fn edit_entire_signal(
i: IntersectionID,
mode: GameplayMode,
original: BundleEdits,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
let has_sidewalks = app
.primary
.map

View File

@ -7,14 +7,15 @@ use map_model::{
TurnPriority,
};
use widgetry::{
lctrl, Btn, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line,
MultiButton, Outcome, Panel, RewriteColor, Text, TextExt, VerticalAlignment, Widget,
lctrl, Btn, Color, DrawBaselayer, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment,
Key, Line, MultiButton, Outcome, Panel, RewriteColor, State, Text, TextExt, VerticalAlignment,
Widget,
};
use crate::app::{App, ShowEverything};
use crate::common::{CommonState, Warping};
use crate::edit::{apply_map_edits, ConfirmDiscard};
use crate::game::{DrawBaselayer, PopupMsg, State, Transition};
use crate::game::{PopupMsg, Transition};
use crate::options::TrafficSignalStyle;
use crate::render::{traffic_signal, DrawMovement, DrawOptions};
use crate::sandbox::GameplayMode;
@ -60,7 +61,7 @@ impl TrafficSignalEditor {
app: &mut App,
members: BTreeSet<IntersectionID>,
mode: GameplayMode,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
app.primary.current_selection = None;
let original = BundleEdits::get_current(app, &members);
@ -176,7 +177,7 @@ impl TrafficSignalEditor {
}
}
impl State for TrafficSignalEditor {
impl State<App> for TrafficSignalEditor {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
if self.warn_changed {
self.warn_changed = false;

View File

@ -7,13 +7,13 @@ use map_model::IntersectionID;
use sim::Scenario;
use widgetry::{
Btn, Color, Drawable, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel,
RewriteColor, Spinner, Text, TextExt, VerticalAlignment, Widget,
RewriteColor, Spinner, State, Text, TextExt, VerticalAlignment, Widget,
};
use crate::app::App;
use crate::common::CommonState;
use crate::edit::traffic_signals::fade_irrelevant;
use crate::game::{State, Transition};
use crate::game::Transition;
use crate::helpers::ID;
pub struct ShowAbsolute {
@ -23,7 +23,11 @@ pub struct ShowAbsolute {
}
impl ShowAbsolute {
pub fn new(ctx: &mut EventCtx, app: &App, members: BTreeSet<IntersectionID>) -> Box<dyn State> {
pub fn new(
ctx: &mut EventCtx,
app: &App,
members: BTreeSet<IntersectionID>,
) -> Box<dyn State<App>> {
let mut batch = fade_irrelevant(app, &members);
for i in &members {
batch.append(
@ -58,7 +62,7 @@ impl ShowAbsolute {
}
}
impl State for ShowAbsolute {
impl State<App> for ShowAbsolute {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();
if ctx.redo_mouseover() {
@ -117,7 +121,7 @@ impl ShowRelative {
app: &App,
base: IntersectionID,
members: BTreeSet<IntersectionID>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
let base_offset = app.primary.map.get_traffic_signal(base).offset;
let mut batch = fade_irrelevant(app, &members);
for i in &members {
@ -160,7 +164,7 @@ impl ShowRelative {
}
}
impl State for ShowRelative {
impl State<App> for ShowRelative {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();
if ctx.redo_mouseover() {
@ -220,7 +224,7 @@ impl TuneRelative {
i1: IntersectionID,
i2: IntersectionID,
members: BTreeSet<IntersectionID>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
let mut batch = fade_irrelevant(app, &btreeset! {i1, i2});
let map = &app.primary.map;
// TODO Colors aren't clear. Show directionality.
@ -281,7 +285,7 @@ impl TuneRelative {
}
}
impl State for TuneRelative {
impl State<App> for TuneRelative {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();
match self.panel.event(ctx) {

View File

@ -3,13 +3,13 @@ use std::collections::BTreeSet;
use map_model::IntersectionID;
use widgetry::{
hotkeys, Btn, Color, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome,
Panel, VerticalAlignment, Widget,
Panel, State, VerticalAlignment, Widget,
};
use crate::app::App;
use crate::common::CommonState;
use crate::edit::TrafficSignalEditor;
use crate::game::{State, Transition};
use crate::game::Transition;
use crate::helpers::ID;
use crate::sandbox::gameplay::GameplayMode;
@ -24,7 +24,7 @@ impl SignalPicker {
ctx: &mut EventCtx,
members: BTreeSet<IntersectionID>,
mode: GameplayMode,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
Box::new(SignalPicker {
panel: Panel::new(Widget::col(vec![
Widget::row(vec![
@ -45,7 +45,7 @@ impl SignalPicker {
}
}
impl State for SignalPicker {
impl State<App> for SignalPicker {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();
if ctx.redo_mouseover() {

View File

@ -4,12 +4,12 @@ use abstutil::Timer;
use geom::Duration;
use map_model::IntersectionID;
use widgetry::{
Btn, Choice, EventCtx, GfxCtx, HorizontalAlignment, Key, Outcome, Panel, TextExt, UpdateType,
VerticalAlignment, Widget,
Btn, Choice, EventCtx, GfxCtx, HorizontalAlignment, Key, Outcome, Panel, State, TextExt,
UpdateType, VerticalAlignment, Widget,
};
use crate::app::App;
use crate::game::{ChooseSomething, State, Transition};
use crate::game::{ChooseSomething, Transition};
use crate::sandbox::{spawn_agents_around, SpeedControls, TimePanel};
// TODO Show diagram, auto-sync the stage.
@ -21,7 +21,7 @@ struct PreviewTrafficSignal {
}
impl PreviewTrafficSignal {
fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
Box::new(PreviewTrafficSignal {
panel: Panel::new(Widget::col(vec![
"Previewing traffic signal".draw_text(ctx),
@ -35,7 +35,7 @@ impl PreviewTrafficSignal {
}
}
impl State for PreviewTrafficSignal {
impl State<App> for PreviewTrafficSignal {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();
@ -76,7 +76,7 @@ pub fn make_previewer(
app: &App,
members: BTreeSet<IntersectionID>,
stage: usize,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
let random = "random agents around these intersections".to_string();
let right_now = format!(
"change the traffic signal live at {}",

View File

@ -2,11 +2,11 @@ use std::collections::BTreeSet;
use abstutil::Timer;
use map_model::{connectivity, EditCmd, LaneID, LaneType, Map, PathConstraints};
use widgetry::{Color, EventCtx};
use widgetry::{Color, EventCtx, State};
use crate::app::App;
use crate::common::ColorDiscrete;
use crate::game::{PopupMsg, State};
use crate::game::PopupMsg;
// All of these take a candidate EditCmd to do, then see if it's valid. If they return None, it's
// fine. They always leave the map in the original state without the new EditCmd.
@ -16,7 +16,7 @@ pub fn check_sidewalk_connectivity(
ctx: &mut EventCtx,
app: &mut App,
cmd: EditCmd,
) -> Option<Box<dyn State>> {
) -> Option<Box<dyn State<App>>> {
let orig_edits = app.primary.map.get_edits().clone();
let (_, disconnected_before) =
connectivity::find_scc(&app.primary.map, PathConstraints::Pedestrian);
@ -61,7 +61,11 @@ pub fn check_sidewalk_connectivity(
#[allow(unused)]
// Could be caused by closing intersections, changing lane types, or reversing lanes
pub fn check_blackholes(ctx: &mut EventCtx, app: &mut App, cmd: EditCmd) -> Option<Box<dyn State>> {
pub fn check_blackholes(
ctx: &mut EventCtx,
app: &mut App,
cmd: EditCmd,
) -> Option<Box<dyn State<App>>> {
let orig_edits = app.primary.map.get_edits().clone();
let mut driving_ok_originally = BTreeSet::new();
let mut biking_ok_originally = BTreeSet::new();
@ -120,7 +124,7 @@ pub fn try_change_lt(
map: &mut Map,
l: LaneID,
new_lt: LaneType,
) -> Result<EditCmd, Box<dyn State>> {
) -> Result<EditCmd, Box<dyn State<App>>> {
let orig_edits = map.get_edits().clone();
let mut edits = orig_edits.clone();

View File

@ -7,7 +7,7 @@ use map_model::{AccessRestrictions, PathConstraints, RoadID};
use sim::TripMode;
use widgetry::{
Btn, Color, Drawable, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel,
Spinner, Text, TextExt, VerticalAlignment, Widget,
Spinner, State, Text, TextExt, VerticalAlignment, Widget,
};
use crate::app::App;
@ -15,7 +15,7 @@ use crate::common::ColorDiscrete;
use crate::common::CommonState;
use crate::edit::apply_map_edits;
use crate::edit::select::RoadSelector;
use crate::game::{State, Transition};
use crate::game::Transition;
use crate::helpers::{checkbox_per_mode, intersections_from_roads};
pub struct ZoneEditor {
@ -29,7 +29,7 @@ pub struct ZoneEditor {
}
impl ZoneEditor {
pub fn new(ctx: &mut EventCtx, app: &mut App, start: RoadID) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &mut App, start: RoadID) -> Box<dyn State<App>> {
let start = app.primary.map.get_r(start);
let members = if let Some(z) = start.get_zone(&app.primary.map) {
z.members.clone()
@ -82,7 +82,7 @@ impl ZoneEditor {
}
// TODO Handle splitting/merging zones.
impl State for ZoneEditor {
impl State<App> for ZoneEditor {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {

View File

@ -1,23 +1,18 @@
use map_model::PermanentMapEdits;
use widgetry::{
hotkeys, Btn, Canvas, Choice, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key,
Line, Menu, Outcome, Panel, ScreenRectangle, Text, VerticalAlignment, Widget, GUI,
hotkeys, Btn, Choice, DrawBaselayer, Drawable, EventCtx, GeomBatch, GfxCtx,
HorizontalAlignment, Key, Line, Menu, Outcome, Panel, ScreenRectangle, State, Text,
VerticalAlignment, Widget,
};
use crate::app::{App, Flags, ShowEverything};
use crate::app::{App, Flags};
use crate::helpers::grey_out_map;
use crate::options::Options;
use crate::pregame::TitleScreen;
use crate::render::DrawOptions;
use crate::sandbox::{GameplayMode, SandboxMode};
// This is the top-level of the GUI logic. This module should just manage interactions between the
// top-level game states.
pub struct Game {
// A stack of states
states: Vec<Box<dyn State>>,
app: App,
}
pub struct Game;
pub type Transition = widgetry::Transition<App>;
impl Game {
pub fn new(
@ -26,7 +21,7 @@ impl Game {
start_with_edits: Option<String>,
maybe_mode: Option<GameplayMode>,
ctx: &mut EventCtx,
) -> Game {
) -> (App, Vec<Box<dyn State<App>>>) {
let title = !opts.dev
&& !flags.sim_flags.load.contains("player/save")
&& !flags.sim_flags.load.contains("system/scenarios")
@ -65,7 +60,7 @@ impl Game {
app.primary.clear_sim();
}
let states: Vec<Box<dyn State>> = if title {
let states: Vec<Box<dyn State<App>>> = if title {
vec![Box::new(TitleScreen::new(ctx, &mut app))]
} else {
let mode = maybe_mode
@ -77,185 +72,9 @@ impl Game {
// PlayScenario without clobbering.
app.primary.sim = ss;
}
Game { states, app }
(app, states)
}
// If true, then the top-most state on the stack needs to be "woken up" with a fake mouseover
// event.
fn execute_transition(&mut self, ctx: &mut EventCtx, transition: Transition) -> bool {
match transition {
Transition::Keep => false,
Transition::KeepWithMouseover => true,
Transition::Pop => {
self.states.pop().unwrap().on_destroy(ctx, &mut self.app);
if self.states.is_empty() {
self.before_quit(ctx.canvas);
std::process::exit(0);
}
true
}
Transition::ModifyState(cb) => {
cb(self.states.last_mut().unwrap(), ctx, &mut self.app);
true
}
Transition::ReplaceWithData(cb) => {
let mut last = self.states.pop().unwrap();
last.on_destroy(ctx, &mut self.app);
let new_states = cb(last, ctx, &mut self.app);
self.states.extend(new_states);
true
}
Transition::Push(state) => {
self.states.push(state);
true
}
Transition::Replace(state) => {
self.states.pop().unwrap().on_destroy(ctx, &mut self.app);
self.states.push(state);
true
}
Transition::Clear(states) => {
while !self.states.is_empty() {
self.states.pop().unwrap().on_destroy(ctx, &mut self.app);
}
self.states.extend(states);
true
}
Transition::Multi(list) => {
// Always wake-up just the last state remaining after the sequence
for t in list {
self.execute_transition(ctx, t);
}
true
}
}
}
}
impl GUI for Game {
fn event(&mut self, ctx: &mut EventCtx) {
self.app.per_obj.reset();
let transition = self.states.last_mut().unwrap().event(ctx, &mut self.app);
if self.execute_transition(ctx, transition) {
// Let the new state initialize with a fake event. Usually these just return
// Transition::Keep, but nothing stops them from doing whatever. (For example, entering
// tutorial mode immediately pushes on a Warper.) So just recurse.
ctx.no_op_event(true, |ctx| self.event(ctx));
}
}
fn draw(&self, g: &mut GfxCtx) {
let state = self.states.last().unwrap();
match state.draw_baselayer() {
DrawBaselayer::DefaultMap => {
self.app.draw(g, DrawOptions::new(), &ShowEverything::new());
}
DrawBaselayer::Custom => {}
DrawBaselayer::PreviousState => {
match self.states[self.states.len() - 2].draw_baselayer() {
DrawBaselayer::DefaultMap => {
self.app.draw(g, DrawOptions::new(), &ShowEverything::new());
}
DrawBaselayer::Custom => {}
// Nope, don't recurse
DrawBaselayer::PreviousState => {}
}
self.states[self.states.len() - 2].draw(g, &self.app);
}
}
state.draw(g, &self.app);
}
fn dump_before_abort(&self, canvas: &Canvas) {
println!();
println!(
"********************************************************************************"
);
canvas.save_camera_state(self.app.primary.map.get_name());
println!(
"Crash! Please report to https://github.com/dabreegster/abstreet/issues/ and include \
all output.txt; at least everything starting from the stack trace above!"
);
println!();
self.app.primary.sim.dump_before_abort();
println!();
println!("Camera:");
println!(
r#"{{ "cam_x": {}, "cam_y": {}, "cam_zoom": {} }}"#,
canvas.cam_x, canvas.cam_y, canvas.cam_zoom
);
println!();
if self.app.primary.map.get_edits().commands.is_empty() {
println!("No edits");
} else {
abstutil::write_json(
"edits_during_crash.json".to_string(),
&PermanentMapEdits::to_permanent(
self.app.primary.map.get_edits(),
&self.app.primary.map,
),
);
println!("Please include edits_during_crash.json in your bug report.");
}
// Repeat, because it can be hard to see the top of the report if it's long
println!();
println!(
"Crash! Please report to https://github.com/dabreegster/abstreet/issues/ and include \
all output.txt; at least everything above here until the start of the report!"
);
println!(
"********************************************************************************"
);
}
fn before_quit(&self, canvas: &Canvas) {
canvas.save_camera_state(self.app.primary.map.get_name());
}
}
pub enum DrawBaselayer {
DefaultMap,
Custom,
PreviousState,
}
pub trait State: downcast_rs::Downcast {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition;
fn draw(&self, g: &mut GfxCtx, app: &App);
fn draw_baselayer(&self) -> DrawBaselayer {
DrawBaselayer::DefaultMap
}
// Before this state is popped or replaced, call this.
fn on_destroy(&mut self, _: &mut EventCtx, _: &mut App) {}
// We don't need an on_enter -- the constructor for the state can just do it.
}
downcast_rs::impl_downcast!(State);
pub enum Transition {
Keep,
KeepWithMouseover,
Pop,
// If a state needs to pass data back to the parent, use this. Sadly, runtime type casting.
ModifyState(Box<dyn FnOnce(&mut Box<dyn State>, &mut EventCtx, &mut App)>),
// TODO This is like Replace + ModifyState, then returning a few Push's from the callback. Not
// sure how to express it in terms of the others without complicating ModifyState everywhere.
ReplaceWithData(
Box<dyn FnOnce(Box<dyn State>, &mut EventCtx, &mut App) -> Vec<Box<dyn State>>>,
),
Push(Box<dyn State>),
Replace(Box<dyn State>),
Clear(Vec<Box<dyn State>>),
Multi(Vec<Transition>),
}
pub struct ChooseSomething<T> {
@ -269,7 +88,7 @@ impl<T: 'static> ChooseSomething<T> {
query: &str,
choices: Vec<Choice<T>>,
cb: Box<dyn Fn(T, &mut EventCtx, &mut App) -> Transition>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
Box::new(ChooseSomething {
panel: Panel::new(Widget::col(vec![
Widget::row(vec![
@ -290,7 +109,7 @@ impl<T: 'static> ChooseSomething<T> {
rect: &ScreenRectangle,
choices: Vec<Choice<T>>,
cb: Box<dyn Fn(T, &mut EventCtx, &mut App) -> Transition>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
Box::new(ChooseSomething {
panel: Panel::new(Menu::new(ctx, choices).named("menu").container())
.aligned(
@ -303,7 +122,7 @@ impl<T: 'static> ChooseSomething<T> {
}
}
impl<T: 'static> State for ChooseSomething<T> {
impl<T: 'static> State<App> for ChooseSomething<T> {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
@ -346,7 +165,7 @@ impl PromptInput {
ctx: &mut EventCtx,
query: &str,
cb: Box<dyn Fn(String, &mut EventCtx, &mut App) -> Transition>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
Box::new(PromptInput {
panel: Panel::new(Widget::col(vec![
Widget::row(vec![
@ -364,7 +183,7 @@ impl PromptInput {
}
}
impl State for PromptInput {
impl State<App> for PromptInput {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
@ -401,7 +220,11 @@ pub struct PopupMsg {
}
impl PopupMsg {
pub fn new<I: Into<String>>(ctx: &mut EventCtx, title: &str, lines: Vec<I>) -> Box<dyn State> {
pub fn new<I: Into<String>>(
ctx: &mut EventCtx,
title: &str,
lines: Vec<I>,
) -> Box<dyn State<App>> {
PopupMsg::also_draw(
ctx,
title,
@ -417,7 +240,7 @@ impl PopupMsg {
lines: Vec<I>,
unzoomed: Drawable,
zoomed: Drawable,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
let mut txt = Text::new();
txt.add(Line(title).small_heading());
for l in lines {
@ -435,7 +258,7 @@ impl PopupMsg {
}
}
impl State for PopupMsg {
impl State<App> for PopupMsg {
fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {

View File

@ -1,8 +1,10 @@
use widgetry::{Btn, EventCtx, GfxCtx, Key, Line, Outcome, Panel, TextExt, Widget};
use widgetry::{
Btn, DrawBaselayer, EventCtx, GfxCtx, Key, Line, Outcome, Panel, State, TextExt, Widget,
};
use crate::app::App;
use crate::common::HeatmapOptions;
use crate::game::{DrawBaselayer, State, Transition};
use crate::game::Transition;
use crate::helpers::{grey_out_map, hotkey_btn};
use crate::sandbox::dashboards;
@ -79,7 +81,7 @@ impl PickLayer {
None
}
pub fn pick(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
pub fn pick(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
let mut col = vec![Widget::custom_row(vec![
Line("Layers").small_heading().draw(ctx),
Btn::plaintext("X")
@ -155,7 +157,7 @@ impl PickLayer {
}
}
impl State for PickLayer {
impl State<App> for PickLayer {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {

View File

@ -5,10 +5,10 @@ use serde::de::DeserializeOwned;
use abstutil::Timer;
use sim::Sim;
use widgetry::{Color, EventCtx, GfxCtx};
use widgetry::{Color, EventCtx, GfxCtx, State};
use crate::app::App;
use crate::game::{State, Transition};
use crate::game::Transition;
#[cfg(not(target_arch = "wasm32"))]
pub use native_loader::FileLoader;
@ -24,7 +24,7 @@ impl MapLoader {
app: &App,
name: String,
on_load: Box<dyn FnOnce(&mut EventCtx, &mut App) -> Transition>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
if app.primary.map.get_name() == &name {
return Box::new(MapAlreadyLoaded {
on_load: Some(on_load),
@ -58,7 +58,7 @@ impl MapLoader {
struct MapAlreadyLoaded {
on_load: Option<Box<dyn FnOnce(&mut EventCtx, &mut App) -> Transition>>,
}
impl State for MapAlreadyLoaded {
impl State<App> for MapAlreadyLoaded {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
(self.on_load.take().unwrap())(ctx, app)
}
@ -82,7 +82,7 @@ mod native_loader {
_: &mut EventCtx,
path: String,
on_load: Box<dyn FnOnce(&mut EventCtx, &mut App, &mut Timer, Option<T>) -> Transition>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
Box::new(FileLoader {
path,
on_load: Some(on_load),
@ -90,7 +90,7 @@ mod native_loader {
}
}
impl<T: 'static + DeserializeOwned> State for FileLoader<T> {
impl<T: 'static + DeserializeOwned> State<App> for FileLoader<T> {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.loading_screen(format!("load {}", self.path), |ctx, timer| {
// Assumes a binary file
@ -114,7 +114,7 @@ mod wasm_loader {
use web_sys::{Request, RequestInit, RequestMode, Response};
use geom::Duration;
use widgetry::{Line, Panel, Text, UpdateType};
use widgetry::{DrawBaselayer, Line, Panel, State, Text, UpdateType};
use super::*;
@ -135,7 +135,7 @@ mod wasm_loader {
ctx: &mut EventCtx,
path: String,
on_load: Box<dyn FnOnce(&mut EventCtx, &mut App, &mut Timer, Option<T>) -> Transition>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
let url = if cfg!(feature = "wasm_s3") {
format!(
"http://abstreet.s3-website.us-east-2.amazonaws.com/{}",

View File

@ -1,12 +1,12 @@
use geom::{Duration, UnitFmt};
use widgetry::{
Btn, Checkbox, Choice, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel, Spinner,
Btn, Checkbox, Choice, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel, Spinner, State,
TextExt, Widget,
};
use crate::app::App;
use crate::colors::{ColorScheme, ColorSchemeChoice};
use crate::game::{State, Transition};
use crate::game::Transition;
use crate::helpers::grey_out_map;
use crate::render::{DrawBuilding, DrawMap};
@ -93,7 +93,7 @@ pub struct OptionsPanel {
}
impl OptionsPanel {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
Box::new(OptionsPanel {
panel: Panel::new(Widget::col(vec![
Widget::custom_row(vec![
@ -239,7 +239,7 @@ impl OptionsPanel {
}
}
impl State for OptionsPanel {
impl State<App> for OptionsPanel {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {

View File

@ -9,15 +9,15 @@ use geom::{Duration, Line, Percent, Pt2D, Speed};
use map_model::PermanentMapEdits;
use sim::ScenarioGenerator;
use widgetry::{
hotkeys, Btn, Color, EventCtx, GfxCtx, Key, Line, Outcome, Panel, RewriteColor, Text,
UpdateType, Widget,
hotkeys, Btn, Color, DrawBaselayer, EventCtx, GfxCtx, Key, Line, Outcome, Panel, RewriteColor,
State, Text, UpdateType, Widget,
};
use crate::app::App;
use crate::challenges::ChallengesPicker;
use crate::devtools::DevToolsMode;
use crate::edit::apply_map_edits;
use crate::game::{DrawBaselayer, PopupMsg, State, Transition};
use crate::game::{PopupMsg, Transition};
use crate::helpers::open_browser;
use crate::load::MapLoader;
use crate::sandbox::gameplay::Tutorial;
@ -61,7 +61,7 @@ impl TitleScreen {
}
}
impl State for TitleScreen {
impl State<App> for TitleScreen {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
@ -89,7 +89,7 @@ pub struct MainMenu {
}
impl MainMenu {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
let col = vec![
Btn::svg_def("system/assets/pregame/quit.svg")
.build(ctx, "quit", Key::Escape)
@ -169,7 +169,7 @@ impl MainMenu {
}
}
impl State for MainMenu {
impl State<App> for MainMenu {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
@ -242,7 +242,7 @@ struct About {
}
impl About {
fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
let col = vec![
Btn::svg_def("system/assets/pregame/back.svg")
.build(ctx, "back", Key::Escape)
@ -289,7 +289,7 @@ impl About {
}
}
impl State for About {
impl State<App> for About {
fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
@ -324,7 +324,7 @@ struct Proposals {
}
impl Proposals {
fn new(ctx: &mut EventCtx, app: &App, current: Option<String>) -> Box<dyn State> {
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();
@ -400,7 +400,7 @@ impl Proposals {
}
}
impl State for Proposals {
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() {
@ -531,7 +531,7 @@ impl Screensaver {
#[cfg(not(target_arch = "wasm32"))]
#[allow(unused)]
mod built_info {
use widgetry::{Color, Line, Text};
use widgetry::{Color, DrawBaselayer, Line, State, Text};
include!(concat!(env!("OUT_DIR"), "/built.rs"));

View File

@ -7,13 +7,14 @@ use geom::{Distance, PolyLine, Polygon, Time};
use map_model::{osm, BuildingID, BuildingType, IntersectionID, LaneID, Map, RoadID, TurnType};
use sim::{TripEndpoint, TripInfo, TripMode};
use widgetry::{
AreaSlider, Btn, Checkbox, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment,
Key, Line, Outcome, Panel, RewriteColor, Text, TextExt, VerticalAlignment, Widget,
AreaSlider, Btn, Checkbox, Color, DrawBaselayer, Drawable, EventCtx, GeomBatch, GfxCtx,
HorizontalAlignment, Key, Line, Outcome, Panel, RewriteColor, State, Text, TextExt,
VerticalAlignment, Widget,
};
use crate::app::{App, ShowEverything};
use crate::common::{ColorLegend, CommonState};
use crate::game::{DrawBaselayer, State, Transition};
use crate::game::Transition;
use crate::helpers::checkbox_per_mode;
use crate::render::DrawOptions;
@ -71,7 +72,7 @@ struct Filter {
type BlockID = usize;
impl CommuterPatterns {
pub fn new(ctx: &mut EventCtx, app: &mut App) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &mut App) -> Box<dyn State<App>> {
assert!(app.primary.suspended_sim.is_none());
app.primary.suspended_sim = Some(app.primary.clear_sim());
@ -341,7 +342,7 @@ impl CommuterPatterns {
}
}
impl State for CommuterPatterns {
impl State<App> for CommuterPatterns {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();

View File

@ -1,9 +1,12 @@
use geom::{Distance, Pt2D};
use sim::{TripEndpoint, TripID};
use widgetry::{Color, EventCtx, GeomBatch, GfxCtx, Outcome, Panel, RewriteColor, ScreenPt};
use widgetry::{
Color, DrawBaselayer, EventCtx, GeomBatch, GfxCtx, Outcome, Panel, RewriteColor, ScreenPt,
State,
};
use crate::app::App;
use crate::game::{DrawBaselayer, State, Transition};
use crate::game::Transition;
use crate::helpers::color_for_trip_phase;
use crate::info::{OpenTrip, Tab};
use crate::sandbox::dashboards::table::Table;
@ -27,7 +30,7 @@ impl<T: 'static, F: 'static, P: 'static + Fn(&mut EventCtx, &App, &Table<T, F>)
tab: DashTab,
table: Table<T, F>,
make_panel: P,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
let panel = (make_panel)(ctx, app, &table);
Box::new(GenericTripTable {
table,
@ -44,7 +47,7 @@ impl<T: 'static, F: 'static, P: 'static + Fn(&mut EventCtx, &App, &Table<T, F>)
}
}
impl<T: 'static, F: 'static, P: 'static + Fn(&mut EventCtx, &App, &Table<T, F>) -> Panel> State
impl<T: 'static, F: 'static, P: 'static + Fn(&mut EventCtx, &App, &Table<T, F>) -> Panel> State<App>
for GenericTripTable<T, F, P>
{
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {

View File

@ -2,13 +2,13 @@ use abstutil::{prettyprint_usize, Counter};
use geom::Time;
use map_model::BusRouteID;
use widgetry::{
Autocomplete, Btn, EventCtx, GfxCtx, Line, LinePlot, Outcome, Panel, PlotOptions, Series,
TextExt, Widget,
Autocomplete, Btn, DrawBaselayer, EventCtx, GfxCtx, Line, LinePlot, Outcome, Panel,
PlotOptions, Series, State, TextExt, Widget,
};
use crate::app::App;
use crate::common::Tab;
use crate::game::{DrawBaselayer, State, Transition};
use crate::game::Transition;
use crate::sandbox::dashboards::DashTab;
use crate::sandbox::SandboxMode;
@ -17,7 +17,7 @@ pub struct ActiveTraffic {
}
impl ActiveTraffic {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
// TODO Downsampling in the middle of the day and comparing to the downsampled entire day
// doesn't work. For the same simulation, by end of day, the plots will be identical, but
// until then, they'll differ. See https://github.com/dabreegster/abstreet/issues/85 for
@ -54,7 +54,7 @@ impl ActiveTraffic {
}
}
impl State for ActiveTraffic {
impl State<App> for ActiveTraffic {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
@ -110,7 +110,7 @@ pub struct TransitRoutes {
}
impl TransitRoutes {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
// Count totals per route
let mut boardings = Counter::new();
for list in app.primary.sim.get_analytics().passengers_boarding.values() {
@ -195,7 +195,7 @@ impl TransitRoutes {
}
}
impl State for TransitRoutes {
impl State<App> for TransitRoutes {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
let route = match self.panel.event(ctx) {
Outcome::Clicked(x) => {

View File

@ -1,9 +1,8 @@
use geom::Duration;
use sim::{TripEndpoint, TripID, TripPhaseType};
use widgetry::{Checkbox, EventCtx, Filler, Line, Panel, Text, Widget};
use widgetry::{Checkbox, EventCtx, Filler, Line, Panel, State, Text, Widget};
use crate::app::App;
use crate::game::State;
use crate::sandbox::dashboards::generic_trip_table::GenericTripTable;
use crate::sandbox::dashboards::table::{Col, Filter, Table};
use crate::sandbox::dashboards::DashTab;
@ -13,7 +12,7 @@ use crate::sandbox::dashboards::DashTab;
pub struct ParkingOverhead;
impl ParkingOverhead {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
let table = make_table(app);
GenericTripTable::new(ctx, app, DashTab::ParkingOverhead, table, make_panel)
}

View File

@ -6,12 +6,12 @@ use abstutil::prettyprint_usize;
use geom::{Distance, Duration, Polygon, Pt2D};
use sim::TripMode;
use widgetry::{
Btn, Checkbox, Choice, Color, CompareTimes, DrawWithTooltips, EventCtx, GeomBatch, GfxCtx,
Line, Outcome, Panel, Text, TextExt, Widget,
Btn, Checkbox, Choice, Color, CompareTimes, DrawBaselayer, DrawWithTooltips, EventCtx,
GeomBatch, GfxCtx, Line, Outcome, Panel, State, Text, TextExt, Widget,
};
use crate::app::App;
use crate::game::{DrawBaselayer, PopupMsg, State, Transition};
use crate::game::{PopupMsg, Transition};
use crate::helpers::color_for_mode;
use crate::sandbox::dashboards::DashTab;
@ -20,7 +20,7 @@ pub struct TripSummaries {
}
impl TripSummaries {
pub fn new(ctx: &mut EventCtx, app: &App, filter: Filter) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &App, filter: Filter) -> Box<dyn State<App>> {
let mut filters = vec!["Filters".draw_text(ctx)];
for mode in TripMode::all() {
filters.push(Checkbox::colored(
@ -68,7 +68,7 @@ impl TripSummaries {
}
}
impl State for TripSummaries {
impl State<App> for TripSummaries {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {

View File

@ -5,13 +5,13 @@ use geom::{ArrowCap, Distance, Duration, Polygon, Time};
use map_model::{ControlTrafficSignal, IntersectionID, MovementID, PathStep, TurnType};
use sim::TripEndpoint;
use widgetry::{
Btn, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome,
Panel, Spinner, Text, TextExt, VerticalAlignment, Widget,
Btn, Color, DrawBaselayer, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key,
Line, Outcome, Panel, Spinner, State, Text, TextExt, VerticalAlignment, Widget,
};
use crate::app::{App, ShowEverything};
use crate::common::CommonState;
use crate::game::{DrawBaselayer, State, Transition};
use crate::game::Transition;
use crate::helpers::ID;
use crate::render::DrawOptions;
@ -24,7 +24,7 @@ pub struct TrafficSignalDemand {
}
impl TrafficSignalDemand {
pub fn new(ctx: &mut EventCtx, app: &mut App) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &mut App) -> Box<dyn State<App>> {
let all_demand = ctx.loading_screen("predict all demand", |_, timer| {
Demand::all_demand(app, timer)
});
@ -68,7 +68,7 @@ impl TrafficSignalDemand {
}
}
impl State for TrafficSignalDemand {
impl State<App> for TrafficSignalDemand {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();
// TODO DrawWithTooltips works great in screenspace; make a similar tool for mapspace?

View File

@ -3,10 +3,9 @@ use std::collections::{BTreeSet, HashMap};
use abstutil::prettyprint_usize;
use geom::{Duration, Time};
use sim::{TripEndpoint, TripID, TripMode};
use widgetry::{Btn, Checkbox, EventCtx, Filler, Line, Panel, Text, Widget};
use widgetry::{Btn, Checkbox, EventCtx, Filler, Line, Panel, State, Text, Widget};
use crate::app::App;
use crate::game::State;
use crate::helpers::{checkbox_per_mode, cmp_duration_shorter, color_for_mode};
use crate::sandbox::dashboards::generic_trip_table::GenericTripTable;
use crate::sandbox::dashboards::table::{Col, Filter, Table};
@ -15,7 +14,7 @@ use crate::sandbox::dashboards::DashTab;
pub struct FinishedTripTable;
impl FinishedTripTable {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
GenericTripTable::new(
ctx,
app,
@ -29,7 +28,7 @@ impl FinishedTripTable {
pub struct CancelledTripTable;
impl CancelledTripTable {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
GenericTripTable::new(
ctx,
app,
@ -43,7 +42,7 @@ impl CancelledTripTable {
pub struct UnfinishedTripTable;
impl UnfinishedTripTable {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
GenericTripTable::new(
ctx,
app,

View File

@ -3,8 +3,8 @@ use std::collections::BTreeMap;
use geom::{Duration, Time};
use sim::{OrigPersonID, PersonID, TripID};
use widgetry::{
Btn, Color, EventCtx, GfxCtx, HorizontalAlignment, Line, Outcome, Panel, RewriteColor, Text,
TextExt, VerticalAlignment, Widget,
Btn, Color, EventCtx, GfxCtx, HorizontalAlignment, Line, Outcome, Panel, RewriteColor, State,
Text, TextExt, VerticalAlignment, Widget,
};
use crate::app::App;
@ -12,7 +12,7 @@ use crate::challenges::{Challenge, HighScore};
use crate::common::Tab;
use crate::cutscene::{CutsceneBuilder, FYI};
use crate::edit::EditMode;
use crate::game::{State, Transition};
use crate::game::Transition;
use crate::helpers::cmp_duration_shorter;
use crate::sandbox::gameplay::{challenge_header, FinalScore, GameplayMode, GameplayState};
use crate::sandbox::{Actions, SandboxControls};
@ -74,7 +74,7 @@ impl OptimizeCommute {
})
}
pub fn cutscene_pt1(ctx: &mut EventCtx, app: &App, mode: &GameplayMode) -> Box<dyn State> {
pub fn cutscene_pt1(ctx: &mut EventCtx, app: &App, mode: &GameplayMode) -> Box<dyn State<App>> {
CutsceneBuilder::new("Optimize one commute: part 1")
.boss("Listen up, I've got a special job for you today.")
.player("What is it? The scooter coalition back with demands for more valet parking?")
@ -96,7 +96,7 @@ impl OptimizeCommute {
.build(ctx, app, cutscene_task(mode))
}
pub fn cutscene_pt2(ctx: &mut EventCtx, app: &App, mode: &GameplayMode) -> Box<dyn State> {
pub fn cutscene_pt2(ctx: &mut EventCtx, app: &App, mode: &GameplayMode) -> Box<dyn State<App>> {
// TODO The person chosen for this currently has more of an issue needing PBLs, actually.
CutsceneBuilder::new("Optimize one commute: part 2")
.boss("I've got another, er, friend who's sick of this parking situation.")
@ -259,7 +259,7 @@ fn final_score(
before: Duration,
after: Duration,
goal: Duration,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
let mut next_mode: Option<GameplayMode> = None;
let msg = if before == after {

View File

@ -2,7 +2,7 @@ use geom::{Duration, Time};
use map_model::IntersectionID;
use widgetry::{
Btn, Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, RewriteColor,
Text, VerticalAlignment, Widget,
State, Text, VerticalAlignment, Widget,
};
use crate::app::{App, FindDelayedIntersections};
@ -10,7 +10,7 @@ use crate::challenges::HighScore;
use crate::common::Warping;
use crate::cutscene::{CutsceneBuilder, FYI};
use crate::edit::EditMode;
use crate::game::{State, Transition};
use crate::game::Transition;
use crate::helpers::ID;
use crate::sandbox::gameplay::{challenge_header, FinalScore, GameplayMode, GameplayState};
use crate::sandbox::{Actions, SandboxControls, SandboxMode};
@ -55,7 +55,7 @@ impl FixTrafficSignals {
})
}
pub fn cutscene_pt1(ctx: &mut EventCtx, app: &App, _: &GameplayMode) -> Box<dyn State> {
pub fn cutscene_pt1(ctx: &mut EventCtx, app: &App, _: &GameplayMode) -> Box<dyn State<App>> {
CutsceneBuilder::new("Traffic signal survivor")
.boss("I hope you've had your coffee. There's a huge mess downtown.")
.player("Did two buses get tangled together again?")
@ -330,7 +330,7 @@ fn final_score(
app: &mut App,
mode: GameplayMode,
failed: bool,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
let score = app.primary.sim.time() - Time::START_OF_DAY;
HighScore {
goal: format!(

View File

@ -10,13 +10,13 @@ use sim::{
};
use widgetry::{
lctrl, Btn, Choice, Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel,
ScreenRectangle, Spinner, Text, TextExt, VerticalAlignment, Widget,
ScreenRectangle, Spinner, State, Text, TextExt, VerticalAlignment, Widget,
};
use crate::app::App;
use crate::common::{CityPicker, CommonState};
use crate::edit::EditMode;
use crate::game::{ChooseSomething, PopupMsg, PromptInput, State, Transition};
use crate::game::{ChooseSomething, PopupMsg, PromptInput, Transition};
use crate::helpers::{nice_map_name, ID};
use crate::sandbox::gameplay::{GameplayMode, GameplayState};
use crate::sandbox::{Actions, SandboxControls, SandboxMode};
@ -146,7 +146,7 @@ pub fn make_change_traffic(
app: &App,
btn: ScreenRectangle,
current: String,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
let mut choices = Vec::new();
for name in abstutil::list_all_objects(abstutil::path_all_scenarios(app.primary.map.get_name()))
{
@ -219,7 +219,7 @@ struct AgentSpawner {
}
impl AgentSpawner {
fn new(ctx: &mut EventCtx, start: Option<BuildingID>) -> Box<dyn State> {
fn new(ctx: &mut EventCtx, start: Option<BuildingID>) -> Box<dyn State<App>> {
let mut spawner = AgentSpawner {
source: None,
goal: None,
@ -269,7 +269,7 @@ impl AgentSpawner {
}
}
impl State for AgentSpawner {
impl State<App> for AgentSpawner {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {

View File

@ -5,7 +5,8 @@ use geom::Duration;
use map_model::{EditCmd, EditIntersection, Map, MapEdits};
use sim::{OrigPersonID, Scenario, ScenarioGenerator, ScenarioModifier};
use widgetry::{
lctrl, Btn, Color, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel, TextExt, Widget,
lctrl, Btn, Color, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel, State, TextExt,
Widget,
};
pub use self::freeform::spawn_agents_around;
@ -13,7 +14,7 @@ pub use self::tutorial::{Tutorial, TutorialPointer, TutorialState};
use crate::app::App;
use crate::challenges::{Challenge, ChallengesPicker};
use crate::edit::{apply_map_edits, SaveEdits};
use crate::game::{State, Transition};
use crate::game::Transition;
use crate::pregame::MainMenu;
use crate::sandbox::{Actions, SandboxControls, SandboxMode};
@ -221,7 +222,7 @@ impl FinalScore {
msg: String,
mode: GameplayMode,
next_mode: Option<GameplayMode>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
Box::new(FinalScore {
panel: Panel::new(
Widget::custom_row(vec![
@ -260,7 +261,7 @@ impl FinalScore {
}
}
impl State for FinalScore {
impl State<App> for FinalScore {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {

View File

@ -5,13 +5,13 @@ use maplit::btreeset;
use sim::{ScenarioModifier, TripMode};
use widgetry::{
lctrl, AreaSlider, Btn, Choice, Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line,
Outcome, Panel, Spinner, Text, TextExt, VerticalAlignment, Widget,
Outcome, Panel, Spinner, State, Text, TextExt, VerticalAlignment, Widget,
};
use crate::app::App;
use crate::common::CityPicker;
use crate::edit::EditMode;
use crate::game::{ChooseSomething, PopupMsg, State, Transition};
use crate::game::{ChooseSomething, PopupMsg, Transition};
use crate::helpers::{checkbox_per_mode, grey_out_map, nice_map_name};
use crate::sandbox::gameplay::freeform::make_change_traffic;
use crate::sandbox::gameplay::{GameplayMode, GameplayState};
@ -165,7 +165,7 @@ impl EditScenarioModifiers {
ctx: &mut EventCtx,
scenario_name: String,
modifiers: Vec<ScenarioModifier>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
let mut rows = vec![
Line("Modify traffic patterns").small_heading().draw(ctx),
Text::from_multiline(vec![
@ -217,7 +217,7 @@ impl EditScenarioModifiers {
}
}
impl State for EditScenarioModifiers {
impl State<App> for EditScenarioModifiers {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
@ -324,7 +324,7 @@ impl ChangeMode {
app: &App,
scenario_name: String,
modifiers: Vec<ScenarioModifier>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
Box::new(ChangeMode {
scenario_name,
modifiers,
@ -369,7 +369,7 @@ impl ChangeMode {
}
}
impl State for ChangeMode {
impl State<App> for ChangeMode {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {

View File

@ -11,14 +11,14 @@ use sim::{
};
use widgetry::{
hotkeys, lctrl, Btn, Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel,
RewriteColor, ScreenPt, Text, TextExt, VerticalAlignment, Widget,
RewriteColor, ScreenPt, State, Text, TextExt, VerticalAlignment, Widget,
};
use crate::app::App;
use crate::common::{tool_panel, Minimap, Warping};
use crate::cutscene::CutsceneBuilder;
use crate::edit::EditMode;
use crate::game::{PopupMsg, State, Transition};
use crate::game::{PopupMsg, Transition};
use crate::helpers::{grey_out_map, ID};
use crate::sandbox::gameplay::{GameplayMode, GameplayState};
use crate::sandbox::{
@ -1408,7 +1408,7 @@ pub fn execute(ctx: &mut EventCtx, app: &mut App, id: ID, action: &str) -> Trans
Transition::Push(response)
}
fn intro_story(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
fn intro_story(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
CutsceneBuilder::new("Introduction")
.boss(
"Argh, the mayor's on my case again about the West Seattle bridge. This day couldn't \

View File

@ -2,13 +2,13 @@ use geom::{ArrowCap, Distance, Time};
use map_model::{LaneID, TurnType};
use sim::AgentID;
use widgetry::{
Btn, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome,
Panel, Text, TextExt, VerticalAlignment, Widget,
Btn, Color, DrawBaselayer, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key,
Line, Outcome, Panel, State, Text, TextExt, VerticalAlignment, Widget,
};
use crate::app::{App, ShowEverything};
use crate::common::ColorLegend;
use crate::game::{DrawBaselayer, State, Transition};
use crate::game::Transition;
use crate::render::{DrawOptions, BIG_ARROW_THICKNESS};
/// Draws a preview of the path for the agent under the mouse cursor.
@ -79,7 +79,7 @@ pub struct TurnExplorer {
}
impl TurnExplorer {
pub fn new(ctx: &mut EventCtx, app: &App, l: LaneID) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &App, l: LaneID) -> Box<dyn State<App>> {
Box::new(TurnExplorer {
l,
idx: 0,
@ -88,7 +88,7 @@ impl TurnExplorer {
}
}
impl State for TurnExplorer {
impl State<App> for TurnExplorer {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();

View File

@ -7,8 +7,8 @@ use abstutil::prettyprint_usize;
use geom::{Circle, Distance, Time};
use sim::{Analytics, Scenario};
use widgetry::{
lctrl, Btn, Choice, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, Text,
TextExt, UpdateType, VerticalAlignment, Widget,
lctrl, Btn, Choice, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State,
Text, TextExt, UpdateType, VerticalAlignment, Widget,
};
use self::misc_tools::{RoutePreview, TurnExplorer};
@ -19,7 +19,7 @@ use crate::edit::{
apply_map_edits, can_edit_lane, EditMode, LaneEditor, SaveEdits, StopSignEditor,
TrafficSignalEditor,
};
use crate::game::{ChooseSomething, PopupMsg, State, Transition};
use crate::game::{ChooseSomething, PopupMsg, Transition};
use crate::helpers::ID;
use crate::layer::PickLayer;
use crate::load::{FileLoader, MapLoader};
@ -56,7 +56,11 @@ pub struct SandboxControls {
impl SandboxMode {
/// If you don't need to chain any transitions after the SandboxMode that rely on its resources
/// being loaded, use this. Otherwise, see `async_new`.
pub fn simple_new(ctx: &mut EventCtx, app: &mut App, mode: GameplayMode) -> Box<dyn State> {
pub fn simple_new(
ctx: &mut EventCtx,
app: &mut App,
mode: GameplayMode,
) -> Box<dyn State<App>> {
SandboxMode::async_new(ctx, app, mode, Box::new(|_, _| Vec::new()))
}
@ -70,7 +74,7 @@ impl SandboxMode {
app: &mut App,
mode: GameplayMode,
finalize: Box<dyn FnOnce(&mut EventCtx, &mut App) -> Vec<Transition>>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
app.primary.clear_sim();
Box::new(SandboxLoader {
stage: Some(LoadStage::LoadingMap),
@ -94,7 +98,7 @@ impl SandboxMode {
}
}
impl State for SandboxMode {
impl State<App> for SandboxMode {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
// Do this before gameplay
if self.gameplay.can_move_canvas() {
@ -273,7 +277,7 @@ pub fn maybe_exit_sandbox(ctx: &mut EventCtx) -> Transition {
struct BackToMainMenu;
impl State for BackToMainMenu {
impl State<App> for BackToMainMenu {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.loading_screen("reset map and sim", |ctx, mut timer| {
// Always safe to do this
@ -614,7 +618,7 @@ struct SandboxLoader {
finalize: Option<Box<dyn FnOnce(&mut EventCtx, &mut App) -> Vec<Transition>>>,
}
impl State for SandboxLoader {
impl State<App> for SandboxLoader {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
loop {
match self.stage.take().unwrap() {

View File

@ -3,13 +3,13 @@ use instant::Instant;
use abstutil::prettyprint_usize;
use geom::{Duration, Polygon, Pt2D, Ring, Time};
use widgetry::{
AreaSlider, Btn, Checkbox, Choice, Color, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome,
Panel, Text, UpdateType, Widget,
AreaSlider, Btn, Checkbox, Choice, Color, DrawBaselayer, EventCtx, GeomBatch, GfxCtx, Key,
Line, Outcome, Panel, State, Text, UpdateType, Widget,
};
use crate::app::{App, FindDelayedIntersections, ShowEverything};
use crate::common::Warping;
use crate::game::{DrawBaselayer, PopupMsg, State, Transition};
use crate::game::{PopupMsg, Transition};
use crate::helpers::{grey_out_map, ID};
use crate::render::DrawOptions;
use crate::sandbox::{GameplayMode, SandboxMode};
@ -22,7 +22,11 @@ pub struct JumpToTime {
}
impl JumpToTime {
pub fn new(ctx: &mut EventCtx, app: &App, maybe_mode: Option<GameplayMode>) -> Box<dyn State> {
pub fn new(
ctx: &mut EventCtx,
app: &App,
maybe_mode: Option<GameplayMode>,
) -> Box<dyn State<App>> {
let target = app.primary.sim.time();
let end_of_day = app.primary.sim.get_end_of_day();
Box::new(JumpToTime {
@ -77,7 +81,7 @@ impl JumpToTime {
}
}
impl State for JumpToTime {
impl State<App> for JumpToTime {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
@ -152,7 +156,11 @@ struct JumpToDelay {
}
impl JumpToDelay {
pub fn new(ctx: &mut EventCtx, app: &App, maybe_mode: Option<GameplayMode>) -> Box<dyn State> {
pub fn new(
ctx: &mut EventCtx,
app: &App,
maybe_mode: Option<GameplayMode>,
) -> Box<dyn State<App>> {
Box::new(JumpToDelay {
maybe_mode,
panel: Panel::new(Widget::col(vec![
@ -195,7 +203,7 @@ impl JumpToDelay {
}
}
impl State for JumpToDelay {
impl State<App> for JumpToDelay {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
@ -256,7 +264,7 @@ impl TimeWarpScreen {
app: &mut App,
target: Time,
mut halt_upon_delay: Option<Duration>,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
if let Some(halt_limit) = halt_upon_delay {
if app.primary.sim_cb.is_none() {
app.primary.sim_cb = Some(Box::new(FindDelayedIntersections {
@ -291,7 +299,7 @@ impl TimeWarpScreen {
}
}
impl State for TimeWarpScreen {
impl State<App> for TimeWarpScreen {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
if ctx.input.nonblocking_is_update_event().is_some() {
ctx.input.use_update_event();

View File

@ -3,14 +3,15 @@ use std::collections::BTreeSet;
use geom::ArrowCap;
use map_model::{IntersectionCluster, IntersectionID, PathConstraints};
use widgetry::{
Btn, Checkbox, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line,
Outcome, Panel, Text, TextExt, VerticalAlignment, Widget,
Btn, Checkbox, Color, DrawBaselayer, Drawable, EventCtx, GeomBatch, GfxCtx,
HorizontalAlignment, Key, Line, Outcome, Panel, State, Text, TextExt, VerticalAlignment,
Widget,
};
use crate::app::{App, ShowEverything};
use crate::common::CommonState;
use crate::edit::ClusterTrafficSignalEditor;
use crate::game::{DrawBaselayer, PopupMsg, State, Transition};
use crate::game::{PopupMsg, Transition};
use crate::helpers::ID;
use crate::render::{DrawOptions, BIG_ARROW_THICKNESS};
@ -20,7 +21,7 @@ pub struct UberTurnPicker {
}
impl UberTurnPicker {
pub fn new(ctx: &mut EventCtx, app: &App, i: IntersectionID) -> Box<dyn State> {
pub fn new(ctx: &mut EventCtx, app: &App, i: IntersectionID) -> Box<dyn State<App>> {
let mut members = BTreeSet::new();
if let Some(list) = IntersectionCluster::autodetect(i, &app.primary.map) {
members.extend(list);
@ -49,7 +50,7 @@ impl UberTurnPicker {
}
}
impl State for UberTurnPicker {
impl State<App> for UberTurnPicker {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();
if ctx.redo_mouseover() {
@ -145,7 +146,7 @@ impl UberTurnViewer {
members: BTreeSet<IntersectionID>,
idx: usize,
legal_turns: bool,
) -> Box<dyn State> {
) -> Box<dyn State<App>> {
app.primary.current_selection = None;
let map = &app.primary.map;
@ -218,7 +219,7 @@ impl UberTurnViewer {
}
}
impl State for UberTurnViewer {
impl State<App> for UberTurnViewer {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();

View File

@ -6,14 +6,31 @@ use map_model::osm;
use map_model::raw::OriginalRoad;
use widgetry::{
Btn, Canvas, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line,
Outcome, Panel, ScreenPt, Text, VerticalAlignment, Widget, GUI,
Outcome, Panel, ScreenPt, SharedAppState, Text, Transition, VerticalAlignment, Widget,
};
mod model;
mod world;
struct UI {
struct App {
model: Model,
}
impl SharedAppState for App {
fn dump_before_abort(&self, canvas: &Canvas) {
if !self.model.map.name.is_empty() {
canvas.save_camera_state(&self.model.map.name);
}
}
fn before_quit(&self, canvas: &Canvas) {
if !self.model.map.name.is_empty() {
canvas.save_camera_state(&self.model.map.name);
}
}
}
struct MainState {
state: State,
panel: Panel,
popup: Option<Drawable>,
@ -32,8 +49,8 @@ enum State {
PreviewIntersection(Drawable, bool),
}
impl UI {
fn new(ctx: &mut EventCtx) -> UI {
impl MainState {
fn new(ctx: &mut EventCtx) -> (App, MainState) {
let mut args = CmdArgs::new();
let load = args.optional_free();
let include_bldgs = args.enabled("--bldgs");
@ -50,30 +67,32 @@ impl UI {
}
let bounds = model.map.gps_bounds.to_bounds();
ctx.canvas.map_dims = (bounds.width(), bounds.height());
UI {
model,
state: State::Viewing,
panel: Panel::new(Widget::col(vec![
Line("Map Editor").small_heading().draw(ctx),
Text::new().draw(ctx).named("current info"),
Widget::col(vec![
Btn::text_fg("quit").build_def(ctx, Key::Escape),
Btn::text_fg("save raw map").build_def(ctx, None),
Btn::text_fg("preview all intersections").build_def(ctx, Key::G),
]),
]))
.aligned(HorizontalAlignment::Right, VerticalAlignment::Top)
.build(ctx),
popup: None,
info_key_held: false,
(
App { model },
MainState {
state: State::Viewing,
panel: Panel::new(Widget::col(vec![
Line("Map Editor").small_heading().draw(ctx),
Text::new().draw(ctx).named("current info"),
Widget::col(vec![
Btn::text_fg("quit").build_def(ctx, Key::Escape),
Btn::text_fg("save raw map").build_def(ctx, None),
Btn::text_fg("preview all intersections").build_def(ctx, Key::G),
]),
]))
.aligned(HorizontalAlignment::Right, VerticalAlignment::Top)
.build(ctx),
popup: None,
info_key_held: false,
last_id: None,
}
last_id: None,
},
)
}
}
impl GUI for UI {
fn event(&mut self, ctx: &mut EventCtx) {
impl widgetry::State<App> for MainState {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition<App> {
if self.info_key_held {
self.info_key_held = !ctx.input.key_released(Key::LeftAlt);
} else {
@ -82,7 +101,7 @@ impl GUI for UI {
ctx.canvas_movement();
if ctx.redo_mouseover() {
self.model.world.handle_mouseover(ctx);
app.model.world.handle_mouseover(ctx);
}
let mut cursor = ctx.canvas.get_cursor_in_map_space();
@ -102,32 +121,32 @@ impl GUI for UI {
Some(ID::Road(r)) | Some(ID::RoadPoint(r, _)) => Some(r),
_ => None,
};
let after = match self.model.world.get_selection() {
let after = match app.model.world.get_selection() {
Some(ID::Road(r)) | Some(ID::RoadPoint(r, _)) => Some(r),
_ => None,
};
if before != after {
if let Some(id) = before {
self.model.stop_showing_pts(id);
app.model.stop_showing_pts(id);
}
if let Some(r) = after {
self.model.show_r_points(r, ctx);
self.model.world.handle_mouseover(ctx);
app.model.show_r_points(r, ctx);
app.model.world.handle_mouseover(ctx);
}
}
}
match self.model.world.get_selection() {
match app.model.world.get_selection() {
Some(ID::Intersection(i)) => {
if ctx.input.pressed(Key::LeftControl) {
self.state = State::MovingIntersection(i);
} else if ctx.input.pressed(Key::R) {
self.state = State::CreatingRoad(i);
} else if ctx.input.pressed(Key::Backspace) {
self.model.delete_i(i);
self.model.world.handle_mouseover(ctx);
} else if !self.model.intersection_geom && ctx.input.pressed(Key::P) {
let draw = preview_intersection(i, &self.model, ctx);
app.model.delete_i(i);
app.model.world.handle_mouseover(ctx);
} else if !app.model.intersection_geom && ctx.input.pressed(Key::P) {
let draw = preview_intersection(i, &app.model, ctx);
self.state = State::PreviewIntersection(draw, false);
}
}
@ -135,44 +154,44 @@ impl GUI for UI {
if ctx.input.pressed(Key::LeftControl) {
self.state = State::MovingBuilding(b);
} else if ctx.input.pressed(Key::Backspace) {
self.model.delete_b(b);
self.model.world.handle_mouseover(ctx);
app.model.delete_b(b);
app.model.world.handle_mouseover(ctx);
}
}
Some(ID::Road(r)) => {
if ctx.input.pressed(Key::Backspace) {
self.model.delete_r(r);
self.model.world.handle_mouseover(ctx);
app.model.delete_r(r);
app.model.world.handle_mouseover(ctx);
} else if cursor.is_some() && ctx.input.pressed(Key::P) {
if let Some(id) = self.model.insert_r_pt(r, cursor.unwrap(), ctx) {
self.model.world.force_set_selection(id);
if let Some(id) = app.model.insert_r_pt(r, cursor.unwrap(), ctx) {
app.model.world.force_set_selection(id);
}
} else if ctx.input.pressed(Key::X) {
self.model.clear_r_pts(r, ctx);
app.model.clear_r_pts(r, ctx);
}
}
Some(ID::RoadPoint(r, idx)) => {
if ctx.input.pressed(Key::LeftControl) {
self.state = State::MovingRoadPoint(r, idx);
} else if ctx.input.pressed(Key::Backspace) {
self.model.delete_r_pt(r, idx, ctx);
self.model.world.handle_mouseover(ctx);
app.model.delete_r_pt(r, idx, ctx);
app.model.world.handle_mouseover(ctx);
}
}
None => {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
"quit" => {
self.before_quit(ctx.canvas);
app.before_quit(ctx.canvas);
std::process::exit(0);
}
"save raw map" => {
// TODO Only do this for synthetic maps
self.model.export();
app.model.export();
}
"preview all intersections" => {
if !self.model.intersection_geom {
let draw = preview_all_intersections(&self.model, ctx);
if !app.model.intersection_geom {
let draw = preview_all_intersections(&app.model, ctx);
self.state = State::PreviewIntersection(draw, false);
}
}
@ -181,16 +200,16 @@ impl GUI for UI {
_ => {
if ctx.input.pressed(Key::I) {
if let Some(pt) = cursor {
self.model.create_i(pt, ctx);
self.model.world.handle_mouseover(ctx);
app.model.create_i(pt, ctx);
app.model.world.handle_mouseover(ctx);
}
// TODO Silly bug: Mouseover doesn't actually work! I think the
// cursor being dead-center messes
// up the precomputed triangles.
} else if ctx.input.pressed(Key::B) {
if let Some(pt) = cursor {
let id = self.model.create_b(pt, ctx);
self.model.world.force_set_selection(id);
let id = app.model.create_b(pt, ctx);
app.model.world.force_set_selection(id);
}
}
}
@ -200,7 +219,7 @@ impl GUI for UI {
}
State::MovingIntersection(id) => {
if let Some(pt) = cursor {
self.model.move_i(id, pt, ctx);
app.model.move_i(id, pt, ctx);
if ctx.input.key_released(Key::LeftControl) {
self.state = State::Viewing;
}
@ -208,7 +227,7 @@ impl GUI for UI {
}
State::MovingBuilding(id) => {
if let Some(pt) = cursor {
self.model.move_b(id, pt, ctx);
app.model.move_b(id, pt, ctx);
if ctx.input.key_released(Key::LeftControl) {
self.state = State::Viewing;
}
@ -216,7 +235,7 @@ impl GUI for UI {
}
State::MovingRoadPoint(r, idx) => {
if let Some(pt) = cursor {
self.model.move_r_pt(r, idx, pt, ctx);
app.model.move_r_pt(r, idx, pt, ctx);
if ctx.input.key_released(Key::LeftControl) {
self.state = State::Viewing;
}
@ -225,12 +244,12 @@ impl GUI for UI {
State::CreatingRoad(i1) => {
if ctx.input.pressed(Key::Escape) {
self.state = State::Viewing;
self.model.world.handle_mouseover(ctx);
} else if let Some(ID::Intersection(i2)) = self.model.world.get_selection() {
app.model.world.handle_mouseover(ctx);
} else if let Some(ID::Intersection(i2)) = app.model.world.get_selection() {
if i1 != i2 && ctx.input.pressed(Key::R) {
self.model.create_r(i1, i2, ctx);
app.model.create_r(i1, i2, ctx);
self.state = State::Viewing;
self.model.world.handle_mouseover(ctx);
app.model.world.handle_mouseover(ctx);
}
}
}
@ -244,24 +263,26 @@ impl GUI for UI {
// TODO Woops, not communicating this kind of thing anymore
if ctx.input.pressed(Key::P) {
self.state = State::Viewing;
self.model.world.handle_mouseover(ctx);
app.model.world.handle_mouseover(ctx);
}
}
}
self.popup = None;
if self.info_key_held {
if let Some(id) = self.model.world.get_selection() {
let txt = self.model.describe_obj(id);
if let Some(id) = app.model.world.get_selection() {
let txt = app.model.describe_obj(id);
// TODO We used to display actions and hotkeys here
self.popup = Some(ctx.upload(txt.render_to_batch(ctx.prerender)));
}
}
self.last_id = self.model.world.get_selection();
self.last_id = app.model.world.get_selection();
Transition::Keep
}
fn draw(&self, g: &mut GfxCtx) {
fn draw(&self, g: &mut GfxCtx, app: &App) {
g.clear(Color::BLACK);
// It's useful to see the origin.
@ -270,20 +291,20 @@ impl GUI for UI {
g.draw_polygon(
Color::rgb(242, 239, 233),
self.model.map.boundary_polygon.clone(),
app.model.map.boundary_polygon.clone(),
);
match self.state {
State::PreviewIntersection(_, _) => self.model.world.draw(g, |id| match id {
State::PreviewIntersection(_, _) => app.model.world.draw(g, |id| match id {
ID::Intersection(_) => false,
_ => true,
}),
_ => self.model.world.draw(g, |_| true),
_ => app.model.world.draw(g, |_| true),
}
match self.state {
State::CreatingRoad(i1) => {
if let Some(cursor) = g.get_cursor_in_map_space() {
if let Some(l) = Line::new(self.model.map.intersections[&i1].point, cursor) {
if let Some(l) = Line::new(app.model.map.intersections[&i1].point, cursor) {
g.draw_polygon(Color::GREEN, l.make_polygons(Distance::meters(5.0)));
}
}
@ -309,18 +330,6 @@ impl GUI for UI {
g.redraw_at(ScreenPt::new(0.0, 0.0), popup);
}
}
fn dump_before_abort(&self, canvas: &Canvas) {
if !self.model.map.name.is_empty() {
canvas.save_camera_state(&self.model.map.name);
}
}
fn before_quit(&self, canvas: &Canvas) {
if !self.model.map.name.is_empty() {
canvas.save_camera_state(&self.model.map.name);
}
}
}
fn preview_intersection(i: osm::NodeID, model: &Model, ctx: &EventCtx) -> Drawable {
@ -362,5 +371,8 @@ fn preview_all_intersections(model: &Model, ctx: &EventCtx) -> Drawable {
}
fn main() {
widgetry::run(widgetry::Settings::new("RawMap editor"), |ctx| UI::new(ctx));
widgetry::run(widgetry::Settings::new("RawMap editor"), |ctx| {
let (app, state) = MainState::new(ctx);
(app, vec![Box::new(state)])
});
}

163
widgetry/src/app_state.rs Normal file
View File

@ -0,0 +1,163 @@
// TODO Lotsa docs needed
use crate::{Canvas, EventCtx, GfxCtx};
pub trait SharedAppState {
fn before_event(&mut self) {}
fn draw_default(&self, _: &mut GfxCtx) {}
// Will be called if event or draw panics.
fn dump_before_abort(&self, _: &Canvas) {}
// Only before a normal exit, like window close
fn before_quit(&self, _: &Canvas) {}
}
pub(crate) struct App<A: SharedAppState> {
/// A stack of states
pub(crate) states: Vec<Box<dyn State<A>>>,
pub(crate) shared_app_state: A,
}
impl<A: SharedAppState> App<A> {
pub(crate) fn event(&mut self, ctx: &mut EventCtx) {
self.shared_app_state.before_event();
let transition = self
.states
.last_mut()
.unwrap()
.event(ctx, &mut self.shared_app_state);
if self.execute_transition(ctx, transition) {
// Let the new state initialize with a fake event. Usually these just return
// Transition::Keep, but nothing stops them from doing whatever. (For example, entering
// tutorial mode immediately pushes on a Warper.) So just recurse.
ctx.no_op_event(true, |ctx| self.event(ctx));
}
}
pub(crate) fn draw(&self, g: &mut GfxCtx) {
let state = self.states.last().unwrap();
match state.draw_baselayer() {
DrawBaselayer::DefaultDraw => {
self.shared_app_state.draw_default(g);
}
DrawBaselayer::Custom => {}
DrawBaselayer::PreviousState => {
match self.states[self.states.len() - 2].draw_baselayer() {
DrawBaselayer::DefaultDraw => {
self.shared_app_state.draw_default(g);
}
DrawBaselayer::Custom => {}
// Nope, don't recurse
DrawBaselayer::PreviousState => {}
}
self.states[self.states.len() - 2].draw(g, &self.shared_app_state);
}
}
state.draw(g, &self.shared_app_state);
}
/// If true, then the top-most state on the stack needs to be "woken up" with a fake mouseover
/// event.
fn execute_transition(&mut self, ctx: &mut EventCtx, transition: Transition<A>) -> bool {
match transition {
Transition::Keep => false,
Transition::KeepWithMouseover => true,
Transition::Pop => {
self.states
.pop()
.unwrap()
.on_destroy(ctx, &mut self.shared_app_state);
if self.states.is_empty() {
self.shared_app_state.before_quit(ctx.canvas);
std::process::exit(0);
}
true
}
Transition::ModifyState(cb) => {
cb(
self.states.last_mut().unwrap(),
ctx,
&mut self.shared_app_state,
);
true
}
Transition::ReplaceWithData(cb) => {
let mut last = self.states.pop().unwrap();
last.on_destroy(ctx, &mut self.shared_app_state);
let new_states = cb(last, ctx, &mut self.shared_app_state);
self.states.extend(new_states);
true
}
Transition::Push(state) => {
self.states.push(state);
true
}
Transition::Replace(state) => {
self.states
.pop()
.unwrap()
.on_destroy(ctx, &mut self.shared_app_state);
self.states.push(state);
true
}
Transition::Clear(states) => {
while !self.states.is_empty() {
self.states
.pop()
.unwrap()
.on_destroy(ctx, &mut self.shared_app_state);
}
self.states.extend(states);
true
}
Transition::Multi(list) => {
// Always wake-up just the last state remaining after the sequence
for t in list {
self.execute_transition(ctx, t);
}
true
}
}
}
}
pub enum DrawBaselayer {
DefaultDraw,
Custom,
PreviousState,
}
pub trait State<A>: downcast_rs::Downcast {
fn event(&mut self, ctx: &mut EventCtx, shared_app_state: &mut A) -> Transition<A>;
fn draw(&self, g: &mut GfxCtx, shared_app_state: &A);
fn draw_baselayer(&self) -> DrawBaselayer {
DrawBaselayer::DefaultDraw
}
/// Before this state is popped or replaced, call this.
fn on_destroy(&mut self, _: &mut EventCtx, _: &mut A) {}
// We don't need an on_enter -- the constructor for the state can just do it.
}
downcast_rs::impl_downcast!(State<A>);
pub enum Transition<A> {
Keep,
KeepWithMouseover,
Pop,
/// If a state needs to pass data back to the parent, use this. Sadly, runtime type casting.
ModifyState(Box<dyn FnOnce(&mut Box<dyn State<A>>, &mut EventCtx, &mut A)>),
// TODO This is like Replace + ModifyState, then returning a few Push's from the callback. Not
// sure how to express it in terms of the others without complicating ModifyState everywhere.
ReplaceWithData(
Box<dyn FnOnce(Box<dyn State<A>>, &mut EventCtx, &mut A) -> Vec<Box<dyn State<A>>>>,
),
Push(Box<dyn State<A>>),
Replace(Box<dyn State<A>>),
Clear(Vec<Box<dyn State<A>>>),
Multi(Vec<Transition<A>>),
}

View File

@ -28,6 +28,7 @@
#[macro_use]
extern crate log;
pub use crate::app_state::{DrawBaselayer, SharedAppState, State, Transition};
pub use crate::backend::Drawable;
pub use crate::canvas::{Canvas, HorizontalAlignment, VerticalAlignment};
pub use crate::color::{Color, Fill, LinearGradient, Texture};
@ -36,7 +37,7 @@ pub use crate::event::{hotkeys, lctrl, Event, Key, MultiKey};
pub use crate::event_ctx::{EventCtx, UpdateType};
pub use crate::geom::{GeomBatch, RewriteColor};
pub use crate::input::UserInput;
pub use crate::runner::{run, Settings, GUI};
pub use crate::runner::{run, Settings};
pub use crate::screen_geom::{ScreenDims, ScreenPt, ScreenRectangle};
pub use crate::style::Style;
pub use crate::text::{Line, Text, TextExt, TextSpan};
@ -60,6 +61,7 @@ pub use crate::widgets::spinner::Spinner;
pub(crate) use crate::widgets::text_box::TextBox;
pub use crate::widgets::{EdgeInsets, Outcome, Panel, Widget, WidgetImpl, WidgetOutput};
mod app_state;
mod assets;
#[cfg(any(feature = "glow-backend", feature = "wasm-backend"))]
mod backend_glow;

View File

@ -7,28 +7,24 @@ use winit::window::Icon;
use geom::Duration;
use crate::app_state::App;
use crate::assets::Assets;
use crate::tools::screenshot::screenshot_everything;
use crate::{Canvas, Event, EventCtx, GfxCtx, Key, Prerender, Style, Text, UpdateType, UserInput};
use crate::{
Canvas, Event, EventCtx, GfxCtx, Key, Prerender, SharedAppState, Style, Text, UpdateType,
UserInput,
};
const UPDATE_FREQUENCY: std::time::Duration = std::time::Duration::from_millis(1000 / 30);
pub trait GUI {
fn event(&mut self, ctx: &mut EventCtx);
fn draw(&self, g: &mut GfxCtx);
// Will be called if event or draw panics.
fn dump_before_abort(&self, _canvas: &Canvas) {}
// Only before a normal exit, like window close
fn before_quit(&self, _canvas: &Canvas) {}
}
pub(crate) struct State<G: GUI> {
pub(crate) gui: G,
// TODO Rename this GUI or something
pub(crate) struct State<A: SharedAppState> {
pub(crate) app: App<A>,
pub(crate) canvas: Canvas,
style: Style,
}
impl<G: GUI> State<G> {
impl<A: SharedAppState> State<A> {
// The bool indicates if the input was actually used.
fn event(&mut self, mut ev: Event, prerender: &Prerender) -> (Vec<UpdateType>, bool) {
if let Event::MouseWheelScroll(dx, dy) = ev {
@ -111,7 +107,7 @@ impl<G: GUI> State<G> {
style: &mut self.style,
updates_requested: vec![],
};
self.gui.event(&mut ctx);
self.app.event(&mut ctx);
// TODO We should always do has_been_consumed, but various hacks prevent this from being
// true. For now, just avoid the specific annoying redraw case when a KeyRelease event
// is unused.
@ -123,7 +119,7 @@ impl<G: GUI> State<G> {
})) {
Ok(pair) => pair,
Err(err) => {
self.gui.dump_before_abort(&self.canvas);
self.app.shared_app_state.dump_before_abort(&self.canvas);
panic::resume_unwind(err);
}
}
@ -136,9 +132,9 @@ impl<G: GUI> State<G> {
self.canvas.start_drawing();
if let Err(err) = panic::catch_unwind(panic::AssertUnwindSafe(|| {
self.gui.draw(&mut g);
self.app.draw(&mut g);
})) {
self.gui.dump_before_abort(&self.canvas);
self.app.shared_app_state.dump_before_abort(&self.canvas);
panic::resume_unwind(err);
}
let naming_hint = g.naming_hint.take();
@ -194,7 +190,13 @@ impl Settings {
}
}
pub fn run<G: 'static + GUI, F: FnOnce(&mut EventCtx) -> G>(settings: Settings, make_gui: F) -> ! {
pub fn run<
A: 'static + SharedAppState,
F: FnOnce(&mut EventCtx) -> (A, Vec<Box<dyn crate::app_state::State<A>>>),
>(
settings: Settings,
make_app: F,
) -> ! {
let (prerender_innards, event_loop) = crate::backend::setup(&settings.window_title);
if let Some(ref path) = settings.window_icon {
@ -224,7 +226,7 @@ pub fn run<G: 'static + GUI, F: FnOnce(&mut EventCtx) -> G>(settings: Settings,
let mut canvas = Canvas::new(initial_size);
prerender.window_resized(initial_size);
let gui = make_gui(&mut EventCtx {
let (shared_app_state, states) = make_app(&mut EventCtx {
fake_mouseover: true,
input: UserInput::new(Event::NoOp, &canvas),
canvas: &mut canvas,
@ -232,8 +234,12 @@ pub fn run<G: 'static + GUI, F: FnOnce(&mut EventCtx) -> G>(settings: Settings,
style: &mut style,
updates_requested: vec![],
});
let app = App {
shared_app_state,
states,
};
let mut state = State { canvas, gui, style };
let mut state = State { canvas, app, style };
let dump_raw_events = settings.dump_raw_events;
@ -251,7 +257,7 @@ pub fn run<G: 'static + GUI, F: FnOnce(&mut EventCtx) -> G>(settings: Settings,
// ControlFlow::Exit cleanly shuts things down, meaning on larger maps, lots of
// GPU stuff is dropped. Better to just abort violently and let the OS clean
// up.
state.gui.before_quit(&state.canvas);
state.app.shared_app_state.before_quit(&state.canvas);
std::process::exit(0);
}
winit::event::Event::WindowEvent { event, .. } => {

View File

@ -3,11 +3,11 @@ use std::{fs, process, thread, time};
use abstutil::Timer;
use crate::runner::{State, GUI};
use crate::Prerender;
use crate::runner::State;
use crate::{Prerender, SharedAppState};
pub(crate) fn screenshot_everything<G: GUI>(
state: &mut State<G>,
pub(crate) fn screenshot_everything<A: SharedAppState>(
state: &mut State<A>,
dir_path: &str,
prerender: &Prerender,
zoom: f64,

View File

@ -7,18 +7,23 @@ use geom::{Angle, Duration, Percent, Polygon, Pt2D, Time};
use widgetry::{
lctrl, Btn, Checkbox, Choice, Color, Drawable, EventCtx, Fill, GeomBatch, GfxCtx,
HorizontalAlignment, Key, Line, LinePlot, Outcome, Panel, PersistentSplit, PlotOptions, Series,
Text, TextExt, Texture, UpdateType, VerticalAlignment, Widget, GUI,
SharedAppState, State, Text, TextExt, Texture, Transition, UpdateType, VerticalAlignment,
Widget,
};
pub fn main() {
// Control flow surrendered here. App implements State, which has an event handler and a draw
// callback.
widgetry::run(widgetry::Settings::new("widgetry demo"), |ctx| {
App::new(ctx)
(App {}, vec![Box::new(Demo::new(ctx))])
});
}
struct App {
struct App {}
impl SharedAppState for App {}
struct Demo {
controls: Panel,
timeseries_panel: Option<(Duration, Panel)>,
scrollable_canvas: Drawable,
@ -26,9 +31,9 @@ struct App {
elapsed: Duration,
}
impl App {
fn new(ctx: &mut EventCtx) -> App {
App {
impl Demo {
fn new(ctx: &mut EventCtx) -> Demo {
Demo {
controls: make_controls(ctx),
timeseries_panel: None,
scrollable_canvas: setup_scrollable_canvas(ctx),
@ -124,8 +129,8 @@ impl App {
}
}
impl GUI for App {
fn event(&mut self, ctx: &mut EventCtx) {
impl State<App> for Demo {
fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition<App> {
// Allow panning and zooming to work.
ctx.canvas_movement();
@ -194,9 +199,11 @@ impl GUI for App {
if !self.controls.is_checked("paused") {
ctx.request_update(UpdateType::Game);
}
Transition::Keep
}
fn draw(&self, g: &mut GfxCtx) {
fn draw(&self, g: &mut GfxCtx, _: &App) {
g.clear(Color::BLACK);
if self.controls.is_checked("Draw scrollable canvas") {