Krolyxon
Commit 6d1e074

Branches

Refs →

budctlcratesbudctl-guisrcoverview.rs

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