Skip to main content

grid_poc/
main.rs

1//! A proof of concept of the data grid: `cargo run -p grid-poc --release`.
2//!
3//! The rows are made up, and so is the server, which counts what it is asked: the
4//! numbers on the screen are the point of it.
5
6use std::cell::Cell;
7use std::rc::Rc;
8
9use ravenrs::prelude::*;
10
11const LOCAL_ROWS: usize = 100_000;
12const SERVER_ROWS: usize = 5_000_000;
13const REGIONS: [&str; 5] = ["Norte", "Nordeste", "Centro-Oeste", "Sudeste", "Sul"];
14const PLANS: [&str; 4] = ["Grátis", "Básico", "Pro", "Empresa"];
15const OWNERS: [&str; 6] = ["Ana", "Bruno", "Carla", "Diego", "Elisa", "Fábio"];
16
17/// The same made-up row every time, for a row number.
18fn row(n: usize) -> Vec<Value> {
19    vec![
20        Value::Number(n as f64),
21        Value::Text(format!("Cliente {n:07}")),
22        Value::Text(REGIONS[n * 7 % REGIONS.len()].to_owned()),
23        Value::Text(PLANS[n * 13 % PLANS.len()].to_owned()),
24        Value::Text(OWNERS[n * 3 % OWNERS.len()].to_owned()),
25        Value::Number((n * 37 % 500) as f64 + 1.0),
26        Value::Number(((n * 97 % 90_000) as f64 + 1_000.0) / 10.0),
27    ]
28}
29
30fn columns() -> Vec<GridColumn> {
31    vec![
32        GridColumn::number("Id").width(90.0).groupable(false),
33        GridColumn::text("Cliente").width(190.0).fill(),
34        GridColumn::text("Região").width(140.0),
35        GridColumn::text("Plano").width(120.0),
36        GridColumn::text("Responsável").width(140.0),
37        GridColumn::number("Licenças").width(110.0).summary(Summary::Sum),
38        GridColumn::currency("Mensalidade", "R$ ", 2).width(150.0).summary(Summary::Sum),
39    ]
40}
41
42/// What the made-up server has been asked.
43#[derive(Clone, Default)]
44struct Asked {
45    questions: Rc<Cell<usize>>,
46    rows_sent: Rc<Cell<usize>>,
47}
48
49fn server(asked: &Asked, with_total: bool) -> RemoteRows {
50    let asked = asked.clone();
51    RemoteRows::new(move |request, answer| {
52        asked.questions.set(asked.questions.get() + 1);
53        let end = (request.offset + request.limit).min(SERVER_ROWS);
54        asked.rows_sent.set(asked.rows_sent.get() + (end - request.offset));
55        let page = GridPage::new((request.offset..end).map(row).collect(), end < SERVER_ROWS);
56        answer.send(if with_total { page.total(SERVER_ROWS) } else { page });
57    })
58}
59
60struct Poc {
61    data: GridData,
62    view: State<usize>,
63    scenarios: State<Vec<GridScenario>>,
64    asked: Asked,
65    infinite: RemoteRows,
66    paged: RemoteRows,
67}
68
69const CHUNK: usize = 100;
70const KEPT_CHUNKS: usize = 8;
71
72impl Poc {
73    fn new() -> Self {
74        let asked = Asked::default();
75        Self {
76            data: GridData::new((0..LOCAL_ROWS).map(row).collect()),
77            view: State::new(0),
78            scenarios: State::new(Vec::new()),
79            infinite: server(&asked, false).chunk_size(CHUNK).max_chunks(KEPT_CHUNKS),
80            paged: server(&asked, true).chunk_size(CHUNK).max_chunks(KEPT_CHUNKS),
81            asked,
82        }
83    }
84
85    fn counters(&self, held: usize, kind: &str) -> impl View + use<> {
86        let line = format!(
87            "{kind} · o servidor recebeu {} pedidos e mandou {} linhas · no máximo {} de {} linhas ficaram em memória",
88            self.asked.questions.get(),
89            self.asked.rows_sent.get(),
90            held,
91            SERVER_ROWS,
92        );
93        Text::new(line).style(TextStyle::Callout).foreground(Semantic::InkMuted)
94    }
95}
96
97impl View for Poc {
98    fn build(&self, cx: &mut BuildCx<'_>) -> NodeId {
99        let view = self.view.get();
100        let body: AnyView = match view {
101            0 => Box::new(
102                DataGrid::new(self.data.clone(), columns())
103                    .group_panel(true)
104                    .selection_mode(SelectionMode::Multiple)
105                    .scenarios(self.scenarios.binding())
106                    .group_by(2)
107                    .on_export(|_| {}),
108            ),
109            1 => Box::new(
110                DataGrid::new(self.data.clone(), columns())
111                    .group_panel(true)
112                    .scenarios(self.scenarios.binding())
113                    .page_size(50)
114                    .on_export(|_| {}),
115            ),
116            2 => Box::new(DataGrid::remote(self.infinite.clone(), columns())),
117            _ => Box::new(DataGrid::remote(self.paged.clone(), columns()).page_size(50)),
118        };
119        let hint: AnyView = match view {
120            0 => Box::new(
121                Text::new(
122                    "Arraste um cabeçalho para o espaço acima para agrupar; arraste os grupos para trocar a ordem, ou para fora para desagrupar. Botão direito em um cabeçalho abre o menu. Clique em um total no rodapé para trocar a soma ou escrever uma fórmula, como Sum([Mensalidade]) / Sum([Licenças]). Em Cenário, grave o arranjo com as fórmulas.",
123                )
124                .style(TextStyle::Callout)
125                .foreground(Semantic::InkMuted),
126            ),
127            1 => Box::new(
128                Text::new("As mesmas 100 mil linhas em páginas: escolha quantas linhas por página no rodapé.")
129                    .style(TextStyle::Callout)
130                    .foreground(Semantic::InkMuted),
131            ),
132            2 => Box::new(self.counters(self.infinite.peak_rows(), "Rolagem infinita, sem total: nunca há COUNT(*)")),
133            _ => Box::new(self.counters(self.paged.peak_rows(), "Páginas: só a página aberta é pedida")),
134        };
135        cx.child(
136            &VStack::new((
137                Text::new("Grade de dados").style(TextStyle::Title).font_weight(Weight::SEMIBOLD),
138                SegmentedControl::new(
139                    self.view.binding(),
140                    [
141                        "100 mil linhas: agrupar",
142                        "Em páginas",
143                        "Servidor: rolagem infinita",
144                        "Servidor: páginas",
145                    ],
146                )
147                .label("Demonstração"),
148                hint,
149                Keyed::new(view as u64, body).grow(1.0),
150            ))
151            .spacing(14.0)
152            .padding(24.0)
153            .align(Align::Stretch)
154            .grow(1.0),
155        )
156    }
157}
158
159fn main() -> Result<(), ravenrs::native::PlatformError> {
160    tracing_subscriber::fmt()
161        .with_env_filter(
162            tracing_subscriber::EnvFilter::try_from_default_env()
163                .unwrap_or_else(|_| "info,wgpu=warn,naga=warn".into()),
164        )
165        .init();
166    let options = ravenrs::native::WindowOptions::new("RavenRS: grade de dados").size(1360.0, 860.0);
167    let app = Localized::new(Labels::pt_br(), Poc::new());
168    ravenrs::native::run(app, options)
169}