Krolyxon
Commit 92a131a

Branches

Refs →

budctlcratesbudctl-guisrcoverview.rs

196 lines · 6.7 KB · Rust

1//! The Overview page: battery, noise control, the switches, and the way to the other pages.
2
3use budctl_core::status::{AncLevel, Charge, NoiseMode, Switch};
4use iced::widget::{column, container, row, text};
5use iced::{Alignment, Element, Length};
6
7use crate::demo::Buds;
8use crate::icons::{self, Icon, Part, RingColors};
9use crate::theme::Look;
10use crate::ui::{self, Segment};
11use crate::{Item, Message, Page};
12
13/// What the keyboard cursor can rest on, top to bottom, as [`view`] shows them.
14pub fn items(buds: &Buds) -> Vec<Item> {
15 let mut items = Vec::new();
16 if let Some(now) = &buds.status.noise_control {
17 items.push(Item::NoiseMode);
18 if now.mode == NoiseMode::Anc(AncLevel::Deep).name() {
19 items.push(Item::AncLevel);
20 }
21 }
22 items.extend(shown(&buds.status.switches).into_iter().map(|s| Item::Switch(s.key)));
23 items.extend(MORE.map(|(page, _, _)| Item::Open(page)));
24 items
25}
26
27/// `focus` is the item under the keyboard cursor, if it shows.
28pub fn view<'a>(look: Look, buds: &'a Buds, anc_level: AncLevel, focus: Option<Item>) -> Element<'a, Message> {
29 column![ui::title(look, "Overview"), battery(look, buds)]
30 .push(
31 buds.status
32 .noise_control
33 .as_ref()
34 .map(|now| noise_control(look, &now.mode, anc_level, focus)),
35 )
36 .push(switches(look, &buds.status.switches, focus))
37 .push(more(look, buds, focus))
38 .spacing(18)
39 .into()
40}
41
42fn battery<'a>(look: Look, buds: &Buds) -> Element<'a, Message> {
43 let p = look.p;
44 let colors = RingColors {
45 track: p.surface_highest,
46 charge: p.accent,
47 part: p.fg,
48 tip: p.dim,
49 cutout: p.surface,
50 };
51 let gauge = |part, label: &'a str, charge: Option<Charge>| -> Element<'a, Message> {
52 let reading = match charge {
53 Some(charge) => format!("{}%", charge.level),
54 None => "—".to_owned(),
55 };
56 let figure = row![text(reading).size(21).font(look.bold()).color(p.fg)]
57 .push(
58 charge
59 .filter(|c| c.charging)
60 .map(|_| icons::icon(Icon::Bolt, 16.0, p.icon)),
61 )
62 .spacing(4)
63 .align_y(Alignment::Center);
64 column![
65 icons::battery_ring(part, charge.map(|c| c.level), 112.0, colors),
66 figure,
67 text(label).size(13).color(p.dim)
68 ]
69 .spacing(6)
70 .align_x(Alignment::Center)
71 .width(Length::Fill)
72 .into()
73 };
74 let battery = buds.status.battery;
75 let gauges = row![
76 gauge(Part::Left, "Left", battery.left),
77 gauge(Part::Case, "Case", battery.case),
78 gauge(Part::Right, "Right", battery.right),
79 ];
80 ui::card(look, vec![container(gauges).padding([22, 8]).into()])
81}
82
83fn noise_control<'a>(look: Look, mode: &str, anc_level: AncLevel, focus: Option<Item>) -> Element<'a, Message> {
84 let modes = [
85 (Icon::NoiseOff, "Off", NoiseMode::Off),
86 (Icon::Anc, "Noise cancelling", NoiseMode::Anc(anc_level)),
87 (Icon::Transparency, "Transparency", NoiseMode::Transparency),
88 ];
89 let segments = modes
90 .into_iter()
91 .map(|(icon, label, set)| Segment {
92 icon: Some(icon),
93 label,
94 selected: set.name() == mode,
95 on_press: Message::Noise(set),
96 })
97 .collect();
98 let levels: Vec<Segment> = [
99 (AncLevel::Light, "Light"),
100 (AncLevel::Medium, "Medium"),
101 (AncLevel::Deep, "Deep"),
102 ]
103 .into_iter()
104 .map(|(level, label)| Segment {
105 icon: None,
106 label,
107 selected: level == anc_level,
108 on_press: Message::Noise(NoiseMode::Anc(level)),
109 })
110 .collect();
111 let strength = row![
112 text("Strength").size(13.5).color(look.p.dim).width(Length::Fill),
113 container(ui::segmented(look, levels, false, focus == Some(Item::AncLevel))).width(300)
114 ]
115 .align_y(Alignment::Center)
116 .padding([0, 8]);
117 column![ui::segmented(look, segments, true, focus == Some(Item::NoiseMode))]
118 .push((mode == NoiseMode::Anc(anc_level).name()).then_some(strength))
119 .spacing(12)
120 .into()
121}
122
123/// How the page shows the switches it knows, in this order. Others still show, last, with a
124/// plain icon and no subtitle.
125const SWITCHES: [(&str, Icon, &str); 5] = [
126 ("game", Icon::Game, "Lower latency, for games and video"),
127 ("spatial", Icon::Spatial, "Wider, surround-like sound"),
128 ("bass-boost", Icon::Bass, "Stronger low end"),
129 ("wind", Icon::Wind, "Less wind noise outdoors"),
130 ("dual", Icon::Dual, "Stay connected to two devices at once"),
131];
132
133/// The switches in the order the page shows them.
134fn shown(switches: &[Switch]) -> Vec<&Switch> {
135 let rank = |s: &&Switch| {
136 SWITCHES
137 .iter()
138 .position(|(k, _, _)| *k == s.key)
139 .unwrap_or(SWITCHES.len())
140 };
141 let mut shown: Vec<&Switch> = switches.iter().collect();
142 shown.sort_by_key(rank);
143 shown
144}
145
146fn switches<'a>(look: Look, switches: &[Switch], focus: Option<Item>) -> Element<'a, Message> {
147 let known = |key: &str| SWITCHES.iter().find(|(k, _, _)| *k == key);
148 let rows = shown(switches)
149 .into_iter()
150 .map(|s| {
151 let (icon, subtitle) = known(s.key).map_or((Icon::Settings, None), |&(_, icon, subtitle)| {
152 (icon, Some(subtitle.to_owned()))
153 });
154 ui::setting(
155 look,
156 icon,
157 s.label,
158 subtitle,
159 ui::switch(look, s.on),
160 Message::Switch(s.key, !s.on),
161 focus == Some(Item::Switch(s.key)),
162 )
163 })
164 .collect();
165 ui::card(look, rows)
166}
167
168/// The rows that open other pages: the page, its icon and title.
169const MORE: [(Page, Icon, &str); 3] = [
170 (Page::Equalizer, Icon::Equalizer, "Equalizer"),
171 (Page::Gestures, Icon::Gestures, "Gestures"),
172 (Page::Earbuds, Icon::Earbud, "Earbud settings"),
173];
174
175fn more<'a>(look: Look, buds: &Buds, focus: Option<Item>) -> Element<'a, Message> {
176 let rows = MORE
177 .into_iter()
178 .map(|(page, icon, title)| {
179 let subtitle = match page {
180 Page::Equalizer => buds.eq_name.clone(),
181 Page::Gestures => "Taps and holds on each bud".to_owned(),
182 _ => "Find my earbuds, firmware".to_owned(),
183 };
184 ui::setting(
185 look,
186 icon,
187 title,
188 Some(subtitle),
189 ui::chevron(look),
190 Message::Open(page),
191 focus == Some(Item::Open(page)),
192 )
193 })
194 .collect();
195 ui::card(look, rows)
196}