1use crate::button::{Button, ButtonMessages, ButtonOptions, ButtonShape, SceneBuilderButtonExt};
6use crate::constants::constants::*;
7use crate::font::get_default_font_face;
8use anyhow::Error;
9use carnelian::drawing::FontFace;
10use carnelian::render::rive::load_rive;
11use carnelian::render::Context as RenderContext;
12use carnelian::scene::facets::{
13 RiveFacet, TextFacetOptions, TextHorizontalAlignment, TextVerticalAlignment,
14};
15use carnelian::scene::layout::{
16 Alignment, CrossAxisAlignment, Flex, FlexOptions, MainAxisAlignment, MainAxisSize, Stack,
17 StackOptions,
18};
19use carnelian::scene::scene::{Scene, SceneBuilder};
20use carnelian::{
21 input, make_message, AppSender, Coord, Message, MessageTarget, Point, Size, ViewAssistant,
22 ViewAssistantContext, ViewKey,
23};
24use euclid::{size2, Size2D, UnknownUnit};
25use recovery_util::ota::state_machine::Event;
26use rive_rs::File;
27
28#[derive(Debug, Clone)]
29pub struct ButtonInfo {
30 pub text: &'static str,
31 pub icon_name: Option<&'static str>,
32 pub border: bool,
33 pub reversed: bool,
34 pub event: Event,
35}
36
37impl ButtonInfo {
38 pub fn new(
39 text: &'static str,
40 icon_name: Option<&'static str>,
41 border: bool,
42 reversed: bool,
43 event: Event,
44 ) -> Self {
45 Self { text, icon_name, border, reversed, event }
46 }
47}
48
49type Buttons = Vec<Button>;
50type ButtonInfos = Vec<ButtonInfo>;
51
52pub struct SceneDetails {
53 pub(crate) scene: Scene,
54 buttons: Buttons,
55}
56
57pub struct GenericSplitViewAssistant {
58 app_sender: AppSender,
59 view_key: ViewKey,
60 split: ScreenSplit,
61 text1: Option<String>,
62 text2: Option<String>,
63 text3: Option<String>,
64 button_infos1: Option<ButtonInfos>,
65 button_infos2: Option<ButtonInfos>,
66 permission_button_infos: Option<ButtonInfos>,
67 icon_name: Option<&'static str>,
68 icon_size: Option<Size2D<f32, UnknownUnit>>,
69 scene_details: Option<SceneDetails>,
70 icon_file: Option<File>,
71 logo_file: Option<File>,
72 qr_file: Option<File>,
73}
74
75impl GenericSplitViewAssistant {
76 pub fn new(
78 app_sender: AppSender,
79 view_key: ViewKey,
80 split: ScreenSplit,
81 text1: Option<String>,
82 text2: Option<String>,
83 text3: Option<String>,
84 button_infos1: Option<ButtonInfos>,
85 button_infos2: Option<ButtonInfos>,
86 permission_button_infos: Option<ButtonInfos>,
87 icon_name: Option<&'static str>,
88 icon_size: Option<Size2D<f32, UnknownUnit>>,
89 ) -> Result<GenericSplitViewAssistant, Error> {
90 #[cfg(feature = "debug_logging")]
91 println!("====== New generic view");
92 let icon_file = load_rive(ICONS_PATH).ok();
93 let logo_file = load_rive(LOGO_PATH).ok();
94 let qr_file = load_rive(QR_PATH).ok();
95
96 Ok(GenericSplitViewAssistant {
97 app_sender: app_sender,
98 view_key,
99 split,
100 text1,
101 text2,
102 text3,
103 button_infos1,
104 button_infos2,
105 permission_button_infos,
106 icon_name,
107 icon_size,
108 scene_details: None,
109 icon_file,
110 logo_file,
111 qr_file,
112 })
113 }
114
115 fn check_if_a_button_in_array_has_been_pressed(
116 &self,
117 text: &String,
118 button_infos: &ButtonInfos,
119 ) -> bool {
120 for button_info in button_infos {
121 if button_info.text == text {
123 #[cfg(feature = "debug_logging")]
124 println!("====== Generating event: {:?}", button_info.event);
125 self.app_sender.queue_message(
126 MessageTarget::View(self.view_key),
127 make_message(button_info.event.clone()),
128 );
129 return true;
130 }
131 }
132 false
133 }
134
135 fn check_if_any_button_has_been_pressed(&self, text: &String) {
136 let mut found = false;
137 if let Some(button_infos) = self.button_infos1.as_ref() {
138 found = self.check_if_a_button_in_array_has_been_pressed(text, button_infos);
139 }
140 if !found {
141 if let Some(button_infos) = self.button_infos2.as_ref() {
142 found = self.check_if_a_button_in_array_has_been_pressed(text, button_infos);
143 }
144 }
145 if !found {
146 if let Some(button_infos) = self.permission_button_infos.as_ref() {
147 self.check_if_a_button_in_array_has_been_pressed(text, button_infos);
148 }
149 }
150 }
151
152 fn generic_split_scene(&mut self, context: &ViewAssistantContext) -> SceneDetails {
153 let target_size = context.size;
154 let min_dimension = target_size.width.min(target_size.height);
155 let _padding = (min_dimension / 20.0).ceil().max(8.0);
156 let mut builder = SceneBuilder::new().background_color(BACKGROUND_WHITE);
157 let mut buttons: Buttons = Vec::new();
158 let split = self.split.as_percent();
159 let face = get_default_font_face();
160 builder.group().column().max_size().main_align(MainAxisAlignment::SpaceEvenly).contents(
161 |builder| {
162 builder.start_group(
163 "main screen",
164 Flex::with_options_ptr(FlexOptions::row(
165 MainAxisSize::Max,
166 MainAxisAlignment::SpaceEvenly,
167 CrossAxisAlignment::Start,
168 )),
169 );
170 builder.start_group(
171 "left column",
172 Flex::with_options_ptr(FlexOptions::column(
173 MainAxisSize::Min,
174 MainAxisAlignment::SpaceEvenly,
175 CrossAxisAlignment::Start,
176 )),
177 );
178 builder.start_group(
179 "left row for margin space",
180 Flex::with_options_ptr(FlexOptions::row(
181 MainAxisSize::Min,
182 MainAxisAlignment::Start,
183 CrossAxisAlignment::Start,
184 )),
185 );
186 builder.space(size2(LEFT_MARGIN_SPACE, 10.0));
187 builder.start_group(
188 "left column contents",
189 Flex::with_options_ptr(FlexOptions::column(
190 MainAxisSize::Min,
191 MainAxisAlignment::SpaceEvenly,
192 CrossAxisAlignment::Start,
193 )),
194 );
195 let width1 = target_size.width * split;
196 let width2 = target_size.width - width1;
197 builder.space(size2(width1, TOP_SPACE));
198 if let Some(text) = self.text1.as_ref() {
199 builder.text(
200 face.clone(),
201 text,
202 TITLE_FONT_SIZE,
203 Point::zero(),
204 TextFacetOptions {
205 horizontal_alignment: TextHorizontalAlignment::Left,
206 vertical_alignment: TextVerticalAlignment::Top,
207 color: TITLE_COLOR,
208 ..TextFacetOptions::default()
209 },
210 );
211 }
212
213 self.add_privacy_switch(&mut buttons, &face, builder, width1);
214
215 if let Some(button_infos) = self.button_infos1.as_ref() {
216 buttons.append(&mut self.add_buttons(
217 builder,
218 button_infos,
219 LARGE_BUTTON_FONT_SIZE,
220 ));
221 }
222 builder.space(size2(10.0, AFTER_MAIN_BUTTON_SPACE));
223 builder.start_group(
224 "bottom button row",
225 Flex::with_options_ptr(FlexOptions::row(
226 MainAxisSize::Min,
227 MainAxisAlignment::Start,
228 CrossAxisAlignment::Center,
229 )),
230 );
231 if let Some(text) = self.text3.as_ref() {
232 builder.text(
233 face.clone(),
234 text,
235 TEXT_FONT_SIZE,
236 Point::zero(),
237 TextFacetOptions {
238 horizontal_alignment: TextHorizontalAlignment::Left,
239 vertical_alignment: TextVerticalAlignment::Bottom,
240 color: TEXT_COLOR,
241 ..TextFacetOptions::default()
242 },
243 );
244 }
245 if let Some(button_infos) = self.button_infos2.as_ref() {
246 builder.space(size2(10.0, 40.0));
247 buttons.append(&mut self.add_buttons(
248 builder,
249 button_infos,
250 SMALL_BUTTON_FONT_SIZE,
251 ));
252 }
253
254 builder.end_group(); builder.end_group(); builder.end_group(); builder.end_group(); let options =
260 StackOptions { alignment: Alignment::center_left(), ..StackOptions::default() };
261 builder.start_group("right screen", Stack::with_options_ptr(options));
262 builder.start_group(
263 "right column",
264 Flex::with_options_ptr(FlexOptions::column(
265 MainAxisSize::Min,
266 MainAxisAlignment::SpaceEvenly,
267 CrossAxisAlignment::Center,
268 )),
269 );
270 if self.split == ScreenSplit::Wide {
271 self.device_panel(builder, face.clone());
272 } else if self.split == ScreenSplit::Even {
273 builder.start_group(
274 "right columm row",
275 Flex::with_options_ptr(FlexOptions::row(
276 MainAxisSize::Min,
277 MainAxisAlignment::Center,
278 CrossAxisAlignment::Center,
279 )),
280 );
281 let mut logo_size = size2(296.0, 170.0);
282 if let Some(icon_size) = self.icon_size {
283 logo_size = icon_size;
284 }
285 builder.space(size2((width2 - logo_size.width) / 2.0, 10.0));
286 self.add_facet(builder, self.icon_name, logo_size);
287 builder.end_group(); }
289 builder.end_group(); builder.rectangle(size2(width2, target_size.height), BACKGROUND_GREY);
291 builder.end_group(); builder.end_group(); },
294 );
295 let mut scene = builder.build();
296 for button in &mut buttons {
297 button.set_focused(&mut scene, true);
298 }
299 scene.layout(target_size);
300 SceneDetails { scene, buttons }
301 }
302
303 fn add_privacy_switch(
304 &mut self,
305 buttons: &mut Buttons,
306 face: &FontFace,
307 builder: &mut SceneBuilder,
308 width1: Coord,
309 ) {
310 let ask_permission = self.permission_button_infos.is_some();
311 if let Some(text) = self.text2.as_ref() {
312 builder.space(size2(width1, AFTER_TITLE_SPACE));
313 builder.text(
314 face.clone(),
315 text,
316 TEXT_FONT_SIZE,
317 Point::zero(),
318 TextFacetOptions {
319 horizontal_alignment: TextHorizontalAlignment::Left,
320 vertical_alignment: TextVerticalAlignment::Top,
321 color: TEXT_COLOR,
322 ..TextFacetOptions::default()
323 },
324 );
325 if ask_permission {
326 builder.space(size2(width1, AFTER_TEXT_SPACE / 2.0));
327 } else {
328 builder.space(size2(width1, AFTER_TEXT_SPACE));
329 }
330 }
331 if ask_permission {
332 buttons.push(self.add_report_permission(
333 builder,
334 self.permission_button_infos.as_ref().unwrap()[0].clone(),
335 &face,
336 ));
337 builder.space(size2(width1, AFTER_TEXT_SPACE / 2.0));
338 }
339 }
340
341 fn add_buttons(
342 &self,
343 builder: &mut SceneBuilder,
344 button_infos: &ButtonInfos,
345 button_font_size: f32,
346 ) -> Buttons {
347 let mut buttons: Buttons = Vec::new();
348 if !button_infos.is_empty() {
349 builder.start_group(
350 "Button Row",
351 Flex::with_options_ptr(FlexOptions::row(
352 MainAxisSize::Min,
353 MainAxisAlignment::SpaceEvenly,
354 CrossAxisAlignment::End,
355 )),
356 );
357 for button_info in button_infos {
358 buttons.push(builder.button(
359 button_info.text,
360 self.get_facet(button_info.icon_name, ICON_ADD_SIZE),
361 ButtonOptions {
362 font_size: button_font_size,
363 shape: ButtonShape::Oval,
364 draw_border: button_info.border,
365 bg_fg_swapped: button_info.reversed,
366 ..ButtonOptions::default()
367 },
368 ));
369 builder.space(size2(BUTTON_SPACE, 10.0));
370 }
371 builder.end_group(); }
373 buttons
374 }
375
376 fn device_panel(&mut self, builder: &mut SceneBuilder, face: FontFace) {
377 builder.start_group(
378 &("G Panel Row"),
379 Flex::with_options_ptr(FlexOptions::row(
380 MainAxisSize::Min,
381 MainAxisAlignment::Start,
382 CrossAxisAlignment::Start,
383 )),
384 );
385 builder.space(size2(20.0, 10.0));
386 builder.start_group(
387 &("G Panel Column"),
388 Flex::with_options_ptr(FlexOptions::column(
389 MainAxisSize::Min,
390 MainAxisAlignment::SpaceEvenly,
391 CrossAxisAlignment::Start,
392 )),
393 );
394 builder.space(size2(10.0, 60.0));
395 builder.start_group(
396 &("QR Code Row"),
397 Flex::with_options_ptr(FlexOptions::row(
398 MainAxisSize::Min,
399 MainAxisAlignment::Start,
400 CrossAxisAlignment::Center,
401 )),
402 );
403 if let Some(qr_file) = &self.qr_file {
404 let logo = RiveFacet::new_from_file(QR_CODE_SIZE, qr_file, Some(QR_CODE))
405 .expect("cannot get QR code from QR file");
406 builder.facet(Box::new(logo));
407 }
408 builder.space(size2(16.0, 1.0));
409 builder.add_text(&face, "Scan QR code\nfor Help article", G_TEXT_FONT_SIZE);
410 builder.end_group(); builder.space(size2(10.0, 208.0));
412 let logo_size = size2(G_LOGO_SIZE, G_LOGO_SIZE);
413 if let Some(logo_file) = &self.logo_file {
414 let logo = RiveFacet::new_from_file(logo_size, logo_file, None)
415 .expect("cannot get logo from logo file");
416 builder.facet(Box::new(logo));
417 }
418 builder.space(size2(10.0, 8.0));
419 builder.add_text(&face, "Device information", G_TITLE_FONT_SIZE);
420 builder.add_text(&face, "(TO BE IMPLEMENTED)", G_TITLE_FONT_SIZE);
421 builder.space(size2(10.0, 8.0));
422 builder.add_text(&face, "Product: Product Name", G_TEXT_FONT_SIZE);
423 builder.add_text(&face, "Serial number: 123-456-7890", G_TEXT_FONT_SIZE);
424 builder.add_text(&face, "Build: 00.00000000.00", G_TEXT_FONT_SIZE);
425 builder.add_text(&face, "DRAM:: XXXXX", G_TEXT_FONT_SIZE);
426 builder.add_text(&face, "UFS: Unknown", G_TEXT_FONT_SIZE);
427 builder.add_text(&face, "Package version: XXXXX", G_TEXT_FONT_SIZE);
428 builder.end_group(); builder.end_group(); }
431
432 fn add_report_permission(
433 &mut self,
434 builder: &mut SceneBuilder,
435 button_info: ButtonInfo,
436 face: &FontFace,
437 ) -> Button {
438 builder.start_group(
439 &("Permissions Row"),
440 Flex::with_options_ptr(FlexOptions::row(
441 MainAxisSize::Min,
442 MainAxisAlignment::Start,
443 CrossAxisAlignment::Center,
444 )),
445 );
446 builder.add_text(&face, OPTIONAL_REPORT_TEXT, PERMISSION_FONT_SIZE);
447 let button_true = button_info.reversed;
448 #[cfg(feature = "debug_logging")]
449 println!("====== Privacy button set to {}", button_true);
450 let icon_name = if button_true { IMAGE_SWITCH_ON } else { IMAGE_SWITCH_OFF };
451 let facet = self.get_facet(Some(icon_name), IMAGE_SWITCH_SIZE);
452 let button_text = self.permission_button_infos.as_ref().unwrap()[0].text;
453 let button = builder.button(
454 button_text,
455 facet,
456 ButtonOptions { hide_text: true, bg_fg_swapped: true, ..ButtonOptions::default() },
457 );
458 builder.end_group(); button
460 }
461
462 fn get_facet(
463 &self,
464 artboard_name: Option<&'static str>,
465 logo_size: Size2D<f32, UnknownUnit>,
466 ) -> Option<RiveFacet> {
467 if artboard_name.is_none() {
468 return None;
469 }
470 if let Some(icon_file) = &self.icon_file {
471 Some(
472 RiveFacet::new_from_file(logo_size, icon_file, artboard_name)
473 .expect("facet_from_file"),
474 )
475 } else {
476 None
477 }
478 }
479
480 fn add_facet(
481 &self,
482 builder: &mut SceneBuilder,
483 artboard_name: Option<&'static str>,
484 logo_size: Size2D<f32, UnknownUnit>,
485 ) {
486 if let Some(facet) = self.get_facet(artboard_name, logo_size) {
487 builder.facet(Box::new(facet));
488 }
489 }
490}
491
492pub trait SceneBuilderTextExt {
493 fn add_text(&mut self, face: &FontFace, text: &'static str, size: f32);
494}
495
496impl SceneBuilderTextExt for SceneBuilder {
497 fn add_text(&mut self, face: &FontFace, text: &'static str, size: f32) {
498 self.text(
499 face.clone(),
500 text,
501 size,
502 Point::zero(),
503 TextFacetOptions {
504 horizontal_alignment: TextHorizontalAlignment::Left,
505 color: TEXT_COLOR,
506 max_width: None,
507 ..TextFacetOptions::default()
508 },
509 );
510 }
511}
512
513impl ViewAssistant for GenericSplitViewAssistant {
514 fn resize(&mut self, _new_size: &Size) -> Result<(), Error> {
515 self.scene_details = None;
516 Ok(())
517 }
518
519 fn render(
520 &mut self,
521 render_context: &mut RenderContext,
522 ready_event: zx::Event,
523 context: &ViewAssistantContext,
524 ) -> Result<(), Error> {
525 let mut scene_details =
526 self.scene_details.take().unwrap_or_else(|| self.generic_split_scene(context));
527
528 scene_details.scene.render(render_context, ready_event, context)?;
529 self.scene_details = Some(scene_details);
530 context.request_render();
531 Ok(())
532 }
533
534 fn handle_pointer_event(
535 &mut self,
536 context: &mut ViewAssistantContext,
537 _event: &input::Event,
538 pointer_event: &input::pointer::Event,
539 ) -> Result<(), Error> {
540 if let Some(scene_details) = self.scene_details.as_mut() {
541 for button in &mut scene_details.buttons {
542 button.handle_pointer_event(&mut scene_details.scene, context, &pointer_event);
543 }
544 }
545 context.request_render();
546 Ok(())
547 }
548
549 fn handle_message(&mut self, message: Message) {
550 if let Some(button_message) = message.downcast_ref::<ButtonMessages>() {
551 match button_message {
552 ButtonMessages::Pressed(_time, button_text) => {
553 #[cfg(feature = "debug_logging")]
554 println!("====== Received button press: {}", button_text);
555 self.check_if_any_button_has_been_pressed(button_text);
556 }
557 }
558 }
559 }
560}