Skip to main content

ravenrs_perf/scenes/
apps.rs

1//! Screens of real applications: what people actually look at.
2
3use component_gallery::Gallery;
4use family_finance::db::Database;
5use family_finance::seed;
6use family_finance::ui::App;
7use family_finance::update::Updater;
8use std::cell::RefCell;
9use std::rc::Rc;
10
11use feature_center::seed::PASSWORD;
12use feature_center::{memory_database, watched};
13use ravenrs_app_ui::Session;
14use ravenrs_core::{AccessAction, Event, Key, Modifiers, Point, Role};
15use ravenrs_widgets::{AnyView, Date, Ui};
16
17use crate::{Ceiling, Class, PerfScene, Setup, Step, ceiling, hover_buttons, nothing, resize, scroll_at};
18
19const INTERACTIONS: &[&str] = &["hover", "scroll", "idle", "resize"];
20
21fn step(interaction: &str) -> Step {
22    match interaction {
23        "hover" => hover_buttons(),
24        "scroll" => scroll_at(Point::new(800.0, 400.0)),
25        "resize" => resize(),
26        _ => nothing(),
27    }
28}
29
30fn finance(page: usize, interaction: &str) -> Setup {
31    let Some(today) = Date::new(2026, 11, 15) else { panic!("a valid date") };
32    let Ok(database) = Database::in_memory() else { panic!("a database") };
33    if seed::load_demo(&database, today).is_err() {
34        panic!("the demo data");
35    }
36    let app = App::new(database, today, None, Updater::idle());
37    app.open_page(page);
38    Setup { root: Box::new(app), step: step(interaction) }
39}
40
41/// The finance app and what moves it between its screens.
42pub fn finance_tour() -> (AnyView, ravenrs_reactive::State<usize>) {
43    let Some(today) = Date::new(2026, 11, 15) else { panic!("a valid date") };
44    let Ok(database) = Database::in_memory() else { panic!("a database") };
45    if seed::load_demo(&database, today).is_err() {
46        panic!("the demo data");
47    }
48    let app = App::new(database, today, None, Updater::idle());
49    let page = app.page_state();
50    (Box::new(app), page)
51}
52
53/// The Feature Center and its session, once someone signs in.
54pub fn feature_center() -> (AnyView, Rc<RefCell<Option<Session>>>) {
55    let Ok(database) = memory_database() else { panic!("a database") };
56    let session = Rc::new(RefCell::new(None));
57    let keep = session.clone();
58    let app = watched(move || database.space(), move |seen| *keep.borrow_mut() = Some(seen.clone()));
59    let Ok(root) = app.build() else { panic!("the app puts itself together") };
60    (Box::new(root), session)
61}
62
63fn act(ui: &mut Ui, role: Role, name: &str, action: AccessAction) {
64    ui.update();
65    let tree = ui.access_tree();
66    let found = tree
67        .nodes
68        .iter()
69        .find(|node| node.role == role && node.name.as_deref().is_some_and(|text| text.starts_with(name)));
70    let Some(id) = found.map(|node| node.id) else { panic!("nothing called {name} on screen") };
71    ui.perform_action(id, action);
72    ui.update();
73}
74
75/// Signs in through the screen, as a person does.
76pub fn sign_in(ui: &mut Ui) {
77    for (field, text) in [("Nome", "ana"), ("Senha", PASSWORD)] {
78        act(ui, Role::TextInput, field, AccessAction::Focus);
79        for character in text.chars() {
80            ui.handle_event(&Event::KeyDown {
81                key: Key::Character(character.to_string()),
82                modifiers: Modifiers::default(),
83            });
84        }
85        ui.update();
86    }
87    act(ui, Role::Button, "Entrar", AccessAction::Click);
88    ui.update();
89}
90
91/// Lists of the Feature Center that show the most, for the first and later visits.
92pub const CENTER_LISTS: [&str; 8] = [
93    "ManyRows",
94    "EditorsDemo",
95    "ListEditorsDemo",
96    "VariantsDemo",
97    "StateDemo",
98    "RelationOwner",
99    "AnalyticsDashboard",
100    "SecurityAuditEntry",
101];
102
103fn center(list: Option<&'static str>, interaction: &str) -> Setup {
104    let (root, session) = feature_center();
105    let mut then = step(interaction);
106    let mut ready = false;
107    // The screen has to exist to be signed in to, so this waits for the first round.
108    let step: Step = Box::new(move |ui, round| {
109        if !ready {
110            ready = true;
111            sign_in(ui);
112            if let (Some(list), Some(session)) = (list, session.borrow().as_ref()) {
113                session.open_view(&format!("{list}_ListView"));
114            }
115            ui.update();
116            return true;
117        }
118        then(ui, round)
119    });
120    Setup { root, step }
121}
122
123fn gallery(interaction: &str) -> Setup {
124    Setup { root: Box::new(Gallery::new()), step: step(interaction) }
125}
126
127const CEILINGS: &[Ceiling] = &[ceiling("idle", "frames", 0), ceiling("hover", "shapes", 0)];
128
129pub(super) fn scenes() -> Vec<PerfScene> {
130    let scene = |name, make| PerfScene {
131        name,
132        covers: &[],
133        class: Class::App,
134        interactions: INTERACTIONS,
135        make,
136        ceilings: CEILINGS,
137    };
138    vec![
139        scene("app-gallery", gallery),
140        scene("app-center-start", |interaction| center(None, interaction)),
141        scene("app-center-many-rows", |interaction| center(Some("ManyRows"), interaction)),
142        scene("app-center-editors", |interaction| center(Some("EditorsDemo"), interaction)),
143        scene("app-center-list-editors", |interaction| center(Some("ListEditorsDemo"), interaction)),
144        scene("app-finance-dashboard", |interaction| finance(0, interaction)),
145        scene("app-finance-entries", |interaction| finance(1, interaction)),
146        scene("app-finance-reports", |interaction| finance(5, interaction)),
147    ]
148}