santa/
title.rs

1use geom::Percent;
2use widgetry::tools::open_browser;
3use widgetry::{
4    ButtonBuilder, Color, ControlState, EdgeInsets, EventCtx, GeomBatch, GfxCtx, Key, Line, Panel,
5    RewriteColor, SimpleState, State, Text, TextExt, Widget,
6};
7
8use crate::levels::Level;
9use crate::{App, Transition};
10
11pub struct TitleScreen;
12
13impl TitleScreen {
14    pub fn new_state(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
15        let mut level_buttons = Vec::new();
16        for (idx, level) in app.session.levels.iter().enumerate() {
17            if idx < app.session.levels_unlocked {
18                level_buttons.push(unlocked_level(ctx, app, level, idx).margin_below(16));
19            } else {
20                level_buttons.push(locked_level(ctx, app, level, idx).margin_below(16));
21            }
22        }
23
24        <dyn SimpleState<_>>::new_state(
25            Panel::new_builder(Widget::col(vec![
26                Line("15-minute Santa")
27                    .display_title()
28                    .into_widget(ctx)
29                    .container()
30                    .padding(16)
31                    .bg(Color::BLACK.alpha(0.8))
32                    .centered_horiz(),
33                Text::from(
34                    Line(
35                        "Time for Santa to deliver presents in Seattle! But... COVID means no \
36                         stopping in houses to munch on cookies (gluten-free and paleo, \
37                         obviously). When your blood sugar gets low, you'll have to stop and \
38                         refill your supply from a store. Those are close to where people live... \
39                         right?",
40                    )
41                    .small_heading(),
42                )
43                .wrap_to_pct(ctx, 50)
44                .into_widget(ctx)
45                .container()
46                .padding(16)
47                .bg(Color::BLACK.alpha(0.8))
48                .centered_horiz(),
49                Widget::custom_row(level_buttons).flex_wrap(ctx, Percent::int(80)),
50                Widget::row(vec![
51                    map_gui::tools::home_btn(ctx),
52                    ctx.style()
53                        .btn_outline
54                        .text("Credits")
55                        .build_def(ctx)
56                        .centered_vert(),
57                    "Created by Dustin Carlino, Yuwen Li, & Michael Kirk"
58                        .text_widget(ctx)
59                        .centered_vert(),
60                ])
61                .centered_horiz()
62                .align_bottom()
63                .bg(Color::BLACK.alpha(0.8)),
64            ]))
65            .build_custom(ctx),
66            Box::new(TitleScreen),
67        )
68    }
69}
70
71impl SimpleState<App> for TitleScreen {
72    fn on_click(
73        &mut self,
74        ctx: &mut EventCtx,
75        app: &mut App,
76        x: &str,
77        _: &mut Panel,
78    ) -> Transition {
79        match x {
80            "Home" => Transition::Clear(vec![map_gui::tools::TitleScreen::new_state(
81                ctx,
82                app,
83                map_gui::tools::Executable::Santa,
84                Box::new(|ctx, app, _| Self::new_state(ctx, app)),
85            )]),
86            "Credits" => Transition::Push(Credits::new_state(ctx)),
87            x => {
88                for level in &app.session.levels {
89                    if x == level.title {
90                        #[cfg(not(target_arch = "wasm32"))]
91                        {
92                            let map_name = level.map.clone();
93                            if !abstio::file_exists(map_name.path()) {
94                                let level = level.clone();
95                                return map_gui::tools::prompt_to_download_missing_data(
96                                    ctx,
97                                    map_name,
98                                    Box::new(move |ctx, app| {
99                                        Transition::Replace(crate::before_level::Picker::new_state(
100                                            ctx, app, level,
101                                        ))
102                                    }),
103                                );
104                            }
105                        }
106
107                        return Transition::Push(crate::before_level::Picker::new_state(
108                            ctx,
109                            app,
110                            level.clone(),
111                        ));
112                    }
113                }
114                panic!("Unknown action {}", x);
115            }
116        }
117    }
118
119    fn other_event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
120        app.session.update_music(ctx);
121        Transition::Keep
122    }
123
124    fn draw(&self, g: &mut GfxCtx, app: &App) {
125        app.session.music.draw(g);
126    }
127}
128
129fn level_btn(ctx: &mut EventCtx, app: &App, level: &Level, idx: usize) -> GeomBatch {
130    let mut txt = Text::new();
131    txt.add_line(Line(format!("LEVEL {}", idx + 1)).small_heading());
132    txt.add_line(Line(&level.title).small_heading());
133    txt.add_line(&level.description);
134    let batch = txt.wrap_to_pct(ctx, 15).render_autocropped(ctx);
135
136    // Add padding
137    let (mut batch, hitbox) = batch
138        .batch()
139        .container()
140        .padding(EdgeInsets {
141            top: 20.0,
142            bottom: 20.0,
143            left: 10.0,
144            right: 10.0,
145        })
146        .into_geom(ctx, None);
147    batch.unshift(app.cs.unzoomed_bike, hitbox);
148    batch
149}
150
151// TODO Preview the map, add padding, add the linear gradient...
152fn locked_level(ctx: &mut EventCtx, app: &App, level: &Level, idx: usize) -> Widget {
153    let mut batch = level_btn(ctx, app, level, idx);
154    let hitbox = batch.get_bounds().get_rectangle();
155    let center = hitbox.center();
156    batch.push(app.cs.fade_map_dark, hitbox);
157    batch.append(GeomBatch::load_svg(ctx, "system/assets/tools/locked.svg").centered_on(center));
158    batch.into_widget(ctx)
159}
160
161fn unlocked_level(ctx: &mut EventCtx, app: &App, level: &Level, idx: usize) -> Widget {
162    let normal = level_btn(ctx, app, level, idx);
163    let hovered = normal
164        .clone()
165        .color(RewriteColor::Change(Color::WHITE, Color::WHITE.alpha(0.6)));
166
167    ButtonBuilder::new()
168        .custom_batch(normal, ControlState::Default)
169        .custom_batch(hovered, ControlState::Hovered)
170        .build_widget(ctx, &level.title)
171}
172
173struct Credits;
174
175impl Credits {
176    fn new_state(ctx: &mut EventCtx) -> Box<dyn State<App>> {
177        <dyn SimpleState<_>>::new_state(
178            Panel::new_builder(Widget::col(vec![
179                Widget::row(vec![
180                    Line("15-minute Santa").big_heading_plain().into_widget(ctx),
181                    ctx.style().btn_close_widget(ctx),
182                ]),
183                link(
184                    ctx,
185                    "Created by the A/B Street team",
186                    "https://abstreet.org"
187                ),
188                Text::from_multiline(vec![
189                    Line("Lead: Dustin Carlino"),
190                    Line("Programming & game design: Michael Kirk"),
191                    Line("UI/UX: Yuwen Li"),
192                ]).into_widget(ctx),
193                link(
194                    ctx,
195                    "Santa made by @parallaxcreativedesign",
196                    "https://www.instagram.com/parallaxcreativedesign/",
197                ),
198                link(
199                    ctx,
200                    "Map data thanks to OpenStreetMap contributors",
201                    "https://www.openstreetmap.org/about"),
202                link(ctx, "Land use data from Seattle GeoData", "https://data-seattlecitygis.opendata.arcgis.com/datasets/current-land-use-zoning-detail"),
203                link(ctx, "Music from various sources", "https://github.com/a-b-street/abstreet/tree/master/data/system/assets/music/sources.md"),
204                link(ctx, "Fonts and icons by various sources", "https://a-b-street.github.io/docs/howto/#data-source-licensing"),
205                "Playtesting by Fridgehaus".text_widget(ctx),
206                ctx.style().btn_outline.text("Back").hotkey(Key::Enter).build_def(ctx).centered_horiz(),
207            ]))
208            .build(ctx), Box::new(Credits))
209    }
210}
211
212fn link(ctx: &mut EventCtx, label: &str, url: &str) -> Widget {
213    ctx.style()
214        .btn_plain
215        .text(label)
216        .build_widget(ctx, format!("open {}", url))
217}
218
219impl SimpleState<App> for Credits {
220    fn on_click(&mut self, _: &mut EventCtx, _: &mut App, x: &str, _: &mut Panel) -> Transition {
221        match x {
222            "close" | "Back" => Transition::Pop,
223            x => {
224                if let Some(url) = x.strip_prefix("open ") {
225                    open_browser(url);
226                    return Transition::Keep;
227                }
228
229                unreachable!()
230            }
231        }
232    }
233
234    fn other_event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
235        app.session.update_music(ctx);
236        Transition::Keep
237    }
238
239    fn draw(&self, g: &mut GfxCtx, app: &App) {
240        app.session.music.draw(g);
241    }
242}