1 use crate::app::App;
2 use crate::game::{DrawBaselayer, State, Transition};
3 use widgetry::{
4 hotkey, hotkeys, Btn, Color, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel,
5 RewriteColor, Text, Widget,
6 };
7
8 pub struct CutsceneBuilder {
9 name: String,
10 scenes: Vec<Scene>,
11 }
12
13 enum Layout {
14 PlayerSpeaking,
15 BossSpeaking,
16 Extra(&'static str, f64),
17 }
18
19 struct Scene {
20 layout: Layout,
21 msg: Text,
22 }
23
24 impl CutsceneBuilder {
new(name: &str) -> CutsceneBuilder25 pub fn new(name: &str) -> CutsceneBuilder {
26 CutsceneBuilder {
27 name: name.to_string(),
28 scenes: Vec::new(),
29 }
30 }
31
player<I: Into<String>>(mut self, msg: I) -> CutsceneBuilder32 pub fn player<I: Into<String>>(mut self, msg: I) -> CutsceneBuilder {
33 self.scenes.push(Scene {
34 layout: Layout::PlayerSpeaking,
35 msg: Text::from(Line(msg).fg(Color::BLACK)),
36 });
37 self
38 }
39
boss<I: Into<String>>(mut self, msg: I) -> CutsceneBuilder40 pub fn boss<I: Into<String>>(mut self, msg: I) -> CutsceneBuilder {
41 self.scenes.push(Scene {
42 layout: Layout::BossSpeaking,
43 msg: Text::from(Line(msg).fg(Color::BLACK)),
44 });
45 self
46 }
47
extra<I: Into<String>>( mut self, character: &'static str, scale: f64, msg: I, ) -> CutsceneBuilder48 pub fn extra<I: Into<String>>(
49 mut self,
50 character: &'static str,
51 scale: f64,
52 msg: I,
53 ) -> CutsceneBuilder {
54 self.scenes.push(Scene {
55 layout: Layout::Extra(character, scale),
56 msg: Text::from(Line(msg).fg(Color::BLACK)),
57 });
58 self
59 }
60
build( self, ctx: &mut EventCtx, app: &App, make_task: Box<dyn Fn(&mut EventCtx) -> Widget>, ) -> Box<dyn State>61 pub fn build(
62 self,
63 ctx: &mut EventCtx,
64 app: &App,
65 make_task: Box<dyn Fn(&mut EventCtx) -> Widget>,
66 ) -> Box<dyn State> {
67 Box::new(CutscenePlayer {
68 panel: make_panel(ctx, app, &self.name, &self.scenes, &make_task, 0),
69 name: self.name,
70 scenes: self.scenes,
71 idx: 0,
72 make_task,
73 })
74 }
75 }
76
77 struct CutscenePlayer {
78 name: String,
79 scenes: Vec<Scene>,
80 idx: usize,
81 panel: Panel,
82 make_task: Box<dyn Fn(&mut EventCtx) -> Widget>,
83 }
84
85 impl State for CutscenePlayer {
event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition86 fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
87 match self.panel.event(ctx) {
88 Outcome::Clicked(x) => match x.as_ref() {
89 "quit" => {
90 // TODO Should SandboxMode use on_destroy for this?
91 app.primary.clear_sim();
92 app.set_prebaked(None);
93 return Transition::Multi(vec![Transition::Pop, Transition::Pop]);
94 }
95 "back" => {
96 self.idx -= 1;
97 self.panel = make_panel(
98 ctx,
99 app,
100 &self.name,
101 &self.scenes,
102 &self.make_task,
103 self.idx,
104 );
105 }
106 "next" => {
107 self.idx += 1;
108 self.panel = make_panel(
109 ctx,
110 app,
111 &self.name,
112 &self.scenes,
113 &self.make_task,
114 self.idx,
115 );
116 }
117 "Skip cutscene" => {
118 self.idx = self.scenes.len();
119 self.panel = make_panel(
120 ctx,
121 app,
122 &self.name,
123 &self.scenes,
124 &self.make_task,
125 self.idx,
126 );
127 }
128 "Start" => {
129 return Transition::Pop;
130 }
131 _ => unreachable!(),
132 },
133 _ => {}
134 }
135 // TODO Should the Panel for text widgets with wrapping do this instead?
136 if ctx.input.is_window_resized() {
137 self.panel = make_panel(
138 ctx,
139 app,
140 &self.name,
141 &self.scenes,
142 &self.make_task,
143 self.idx,
144 );
145 }
146
147 Transition::Keep
148 }
149
draw_baselayer(&self) -> DrawBaselayer150 fn draw_baselayer(&self) -> DrawBaselayer {
151 DrawBaselayer::Custom
152 }
153
draw(&self, g: &mut GfxCtx, app: &App)154 fn draw(&self, g: &mut GfxCtx, app: &App) {
155 // Happens to be a nice background color too ;)
156 g.clear(app.cs.grass);
157 self.panel.draw(g);
158 }
159 }
160
make_panel( ctx: &mut EventCtx, app: &App, name: &str, scenes: &Vec<Scene>, make_task: &Box<dyn Fn(&mut EventCtx) -> Widget>, idx: usize, ) -> Panel161 fn make_panel(
162 ctx: &mut EventCtx,
163 app: &App,
164 name: &str,
165 scenes: &Vec<Scene>,
166 make_task: &Box<dyn Fn(&mut EventCtx) -> Widget>,
167 idx: usize,
168 ) -> Panel {
169 let prev = if idx > 0 {
170 Btn::svg(
171 "system/assets/tools/prev.svg",
172 RewriteColor::Change(Color::WHITE, app.cs.hovering),
173 )
174 .build(ctx, "back", hotkey(Key::LeftArrow))
175 } else {
176 Widget::draw_svg_transform(
177 ctx,
178 "system/assets/tools/prev.svg",
179 RewriteColor::ChangeAlpha(0.3),
180 )
181 };
182 let next = Btn::svg(
183 "system/assets/tools/next.svg",
184 RewriteColor::Change(Color::WHITE, app.cs.hovering),
185 )
186 .build(
187 ctx,
188 "next",
189 hotkeys(vec![Key::RightArrow, Key::Space, Key::Enter]),
190 );
191
192 let inner = if idx == scenes.len() {
193 Widget::custom_col(vec![
194 (make_task)(ctx),
195 Btn::txt("Start", Text::from(Line("Start").fg(Color::BLACK)))
196 .build_def(ctx, hotkey(Key::Enter))
197 .centered_horiz()
198 .align_bottom(),
199 ])
200 } else {
201 Widget::custom_col(vec![
202 match scenes[idx].layout {
203 Layout::PlayerSpeaking => Widget::custom_row(vec![
204 Widget::draw_batch(
205 ctx,
206 GeomBatch::load_svg(ctx.prerender, "system/assets/characters/boss.svg")
207 .scale(0.75)
208 .autocrop(),
209 ),
210 Widget::custom_row(vec![
211 scenes[idx].msg.clone().wrap_to_pct(ctx, 30).draw(ctx),
212 Widget::draw_svg(ctx, "system/assets/characters/player.svg"),
213 ])
214 .align_right(),
215 ]),
216 Layout::BossSpeaking => Widget::custom_row(vec![
217 Widget::draw_batch(
218 ctx,
219 GeomBatch::load_svg(ctx.prerender, "system/assets/characters/boss.svg")
220 .scale(0.75)
221 .autocrop(),
222 ),
223 scenes[idx].msg.clone().wrap_to_pct(ctx, 30).draw(ctx),
224 Widget::draw_svg(ctx, "system/assets/characters/player.svg").align_right(),
225 ]),
226 Layout::Extra(name, scale) => Widget::custom_row(vec![
227 Widget::draw_batch(
228 ctx,
229 GeomBatch::load_svg(ctx.prerender, "system/assets/characters/boss.svg")
230 .scale(0.75)
231 .autocrop(),
232 ),
233 Widget::col(vec![
234 Widget::draw_batch(
235 ctx,
236 GeomBatch::load_svg(
237 ctx.prerender,
238 &format!("system/assets/characters/{}.svg", name),
239 )
240 .scale(scale)
241 .autocrop(),
242 ),
243 scenes[idx].msg.clone().wrap_to_pct(ctx, 30).draw(ctx),
244 ]),
245 Widget::draw_svg(ctx, "system/assets/characters/player.svg"),
246 ])
247 .evenly_spaced(),
248 }
249 .margin_above(100),
250 Widget::col(vec![
251 Widget::row(vec![prev, next]).centered_horiz(),
252 Btn::txt(
253 "Skip cutscene",
254 Text::from(Line("Skip cutscene").fg(Color::BLACK)),
255 )
256 .build_def(ctx, None)
257 .centered_horiz(),
258 ])
259 .align_bottom(),
260 ])
261 };
262
263 let col = vec![
264 // TODO Can't get this to alignment to work
265 Widget::custom_row(vec![
266 Btn::svg_def("system/assets/pregame/back.svg")
267 .build(ctx, "quit", None)
268 .margin_right(100),
269 Line(name).big_heading_styled().draw(ctx),
270 ])
271 .margin_below(40),
272 inner
273 .fill_height()
274 .padding(42)
275 .bg(Color::WHITE)
276 .outline(2.0, Color::BLACK),
277 ];
278
279 Panel::new(Widget::custom_col(col))
280 .exact_size_percent(80, 80)
281 .build_custom(ctx)
282 }
283
284 pub struct FYI {
285 panel: Panel,
286 }
287
288 impl FYI {
new(ctx: &mut EventCtx, contents: Widget, bg: Color) -> Box<dyn State>289 pub fn new(ctx: &mut EventCtx, contents: Widget, bg: Color) -> Box<dyn State> {
290 Box::new(FYI {
291 panel: Panel::new(
292 Widget::custom_col(vec![
293 contents,
294 Btn::txt("Okay", Text::from(Line("Okay").fg(Color::BLACK)))
295 .build_def(ctx, hotkeys(vec![Key::Escape, Key::Space, Key::Enter]))
296 .centered_horiz()
297 .align_bottom(),
298 ])
299 .padding(16)
300 .bg(bg),
301 )
302 .exact_size_percent(50, 50)
303 .build_custom(ctx),
304 })
305 }
306 }
307
308 impl State for FYI {
event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition309 fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
310 match self.panel.event(ctx) {
311 Outcome::Clicked(x) => match x.as_ref() {
312 "Okay" => Transition::Pop,
313 _ => unreachable!(),
314 },
315 _ => Transition::Keep,
316 }
317 }
318
draw(&self, g: &mut GfxCtx, app: &App)319 fn draw(&self, g: &mut GfxCtx, app: &App) {
320 State::grey_out_map(g, app);
321 self.panel.draw(g);
322 }
323 }
324