Skip to main content

ui/
settings_page.rs

1// settings_page -- the settings page of a typed schema (base::settings)
2//
3//   settings_page(id, &mut settings) -> true when a value changed in this pass
4//
5// The keys of the schema without KF_HIDDEN, grouped by section: the sections of
6// Schema::sections first (with their titles), then the others in the order of their first
7// key (titled by their name; keys without a section: "Allgemein").  Each section is a title
8// and a card; each key a row: its label on the left, the control on the right -- bool a
9// switch, int a number box (min..max, step), num a number box with its decimals, text a text
10// box (at most max bytes), choice a combo box, colour a swatch and a button with the hex code
11// that opens a picker -- and a reset button (disabled at the default); its help text and
12// (KF_RESTART) a note below.  The label is the text right before the control, so screen
13// readers name text boxes, number boxes and combo boxes by it (their own text is their
14// value); the switch and the colour button carry it as their label (scripts: click "Dunkles
15// Design").  The reset buttons are "Zurücksetzen: <label>".  Labels, help texts, section
16// titles and choice labels go through i18n::t (the app's catalogue).  Ids: the scope of `id`,
17// then the key's name ("value", "reset", "swatch", "pick" inside it; ids.rs).
18
19use alloc::format;
20use alloc::string::String;
21use alloc::vec::Vec;
22
23use base::settings::{Kind, Settings, Val, KF_HIDDEN, KF_RESTART};
24
25use super::consts::*;
26use super::i18n::{t, tr};
27use super::keys::IC_UNDO;
28use super::value::RangeF;
29use super::Ui;
30
31const CONTROL_W: i32 = 200; // text boxes (DIP)
32
33impl Ui {
34    /// the settings page of the schema of `s` (see the module header) -> a value changed
35    #[cfg_attr(jfgui_dev, track_caller)]
36    pub fn settings_page(&mut self, id: u32, s: &mut Settings) -> bool {
37        let _src = self.src_scope();
38        let schema = s.schema();
39        let shown = |i: usize| schema.keys[i].flags & KF_HIDDEN == 0;
40        let mut secs: Vec<&'static str> = schema.sections.iter().map(|x| x.0).collect();
41        for (i, d) in schema.keys.iter().enumerate() {
42            let sec = d.split().0;
43            if shown(i) && !secs.contains(&sec) {
44                secs.push(sec);
45            }
46        }
47        let mut changed = false;
48        self.id_push(id);
49        self.box_begin(0, BOX_V, 12);
50        for sec in secs {
51            let keys: Vec<usize> = (0..schema.keys.len()).filter(|&i| shown(i) && schema.keys[i].split().0 == sec).collect();
52            if keys.is_empty() {
53                continue;
54            }
55            let title = match schema.sections.iter().find(|x| x.0 == sec) {
56                Some(x) => t(x.1),
57                None if sec.is_empty() => tr("Allgemein"),
58                None => sec,
59            };
60            self.text(TS_BODY_STRONG, title);
61            self.box_begin(0, BOX_V | BOX_CARD | pad(16), 12);
62            for i in keys {
63                changed |= self.settings_row(s, i);
64            }
65            self.box_end();
66        }
67        self.box_end();
68        self.id_pop();
69        changed
70    }
71
72    // one key: label and texts, the control, the reset button -> the value changed
73    fn settings_row(&mut self, s: &mut Settings, i: usize) -> bool {
74        let d = s.schema().keys[i];
75        let label = t(d.label);
76        self.id_push_str(d.name);
77        let cid = self.id("value");
78        self.box_begin(0, BOX_V, 2);
79        self.box_begin(0, BOX_H | BOX_XCENTER, 12);
80        self.next_size(-1, 0);
81        self.text(TS_BODY, label);
82        let cur = s.at(i).clone();
83        let mut new: Option<Val> = None;
84        match (d.kind, &cur) {
85            (Kind::Bool(_), Val::Bool(b)) => {
86                let mut v = *b as i32;
87                self.switch(cid, "", &mut v);
88                self.sem_name(cid, label);
89                if (v != 0) != *b {
90                    new = Some(Val::Bool(v != 0));
91                }
92            }
93            (Kind::Int { min, max, step, .. }, Val::Int(x)) => {
94                let fit = |v: i64| v.clamp(i32::MIN as i64, i32::MAX as i64) as i32;
95                let rg = Range::new(fit(min), fit(max), fit(step).max(1), 0);
96                let mut v = fit(*x);
97                if self.number_box(cid, &mut v, &rg) & EV_ACT != 0 && v != fit(*x) {
98                    new = Some(Val::Int(v as i64));
99                }
100            }
101            (Kind::Num { min, max, dec, .. }, Val::Num(x)) => {
102                let dec = dec.min(9);
103                let step = 1.0 / (0..dec).fold(1.0, |a, _| a * 10.0);
104                let mut v = *x;
105                if self.number_box_f(cid, &mut v, &RangeF::new(min, max, step, dec, 0)) & EV_ACT != 0 && v != *x {
106                    new = Some(Val::Num(v));
107                }
108            }
109            (Kind::Text { max, .. }, Val::Text(txt)) => {
110                let mut b = txt.clone();
111                self.next_size(CONTROL_W, 0);
112                self.textbox(cid, None, &mut b, (max as u32).saturating_add(1).min(0xFF_FFFF));
113                if b != *txt {
114                    new = Some(Val::Text(b));
115                }
116            }
117            (Kind::Choice { items, .. }, Val::Choice(k)) => {
118                let list: Vec<&str> = items.iter().map(|it| t(it.1)).collect();
119                let mut sel = *k as i32;
120                self.combobox(cid, &mut sel, &list);
121                if sel >= 0 && sel as usize != *k {
122                    new = Some(Val::Choice(sel as usize));
123                }
124            }
125            (Kind::Color(_), Val::Color(c)) => {
126                let mut argb = 0xFF00_0000 | c;
127                let sw = self.swatch(self.id("swatch"), argb, false);
128                self.dropdown_button(cid, &format!("#{:06X}", c));
129                if sw & EV_ACT != 0 {
130                    self.popup_open(cid);
131                }
132                self.sem_name(cid, label);
133                if self.flyout_begin(cid, FL_BELOW) {
134                    self.color_picker_ex(self.id("pick"), &mut argb, CP_FIELDS);
135                }
136                self.flyout_end();
137                if argb & 0xFF_FFFF != *c {
138                    new = Some(Val::Color(argb & 0xFF_FFFF));
139                }
140            }
141            _ => {}
142        }
143        if cur == s.default_at(i) {
144            self.next_flags(ST_DISABLED);
145        }
146        let reset: String = format!("{}: {}", tr("Zurücksetzen"), label);
147        if self.icon_button(self.id("reset"), IC_UNDO, &reset) & EV_ACT != 0 {
148            new = Some(s.default_at(i));
149        }
150        self.box_end();
151        if !d.help.is_empty() {
152            self.text(TS_CAPTION, t(d.help));
153        }
154        if d.flags & KF_RESTART != 0 {
155            self.text(TS_CAPTION, tr("Wird nach einem Neustart wirksam."));
156        }
157        self.box_end();
158        self.id_pop();
159        match new {
160            Some(v) => s.set_at(i, v),
161            None => false,
162        }
163    }
164
165    // the newest semantic record of widget id gets the row's label (a switch without text,
166    // the colour button showing its hex code: screen readers and scripts find them by it)
167    fn sem_name(&mut self, id: u32, label: &str) {
168        if let Some(r) = self.sems.iter_mut().rev().find(|r| r.id == id) {
169            r.label = Some(String::from(label));
170        }
171    }
172}