1use super::icon::ICONS_PIXMAPS;
2use super::{Bus, Event, Hub, Id, RenderData, RenderQueue, View, ViewId, ID_FEEDER};
3use super::{BORDER_RADIUS_SMALL, THICKNESS_LARGE, THICKNESS_MEDIUM};
4use crate::battery::Status;
5use crate::color::{BATTERY_FILL, BLACK, WHITE};
6use crate::context::Context;
7use crate::device::CURRENT_DEVICE;
8use crate::font::Fonts;
9use crate::framebuffer::{Framebuffer, UpdateMode};
10use crate::geom::{BorderSpec, CornerSpec, Rectangle};
11use crate::gesture::GestureEvent;
12use crate::unit::scale_by_dpi;
13
14const BUMP_HEIGHT: f32 = 5.0 * THICKNESS_LARGE;
15const BUMP_WIDTH: f32 = 4.0 * THICKNESS_LARGE;
16const BATTERY_HEIGHT: f32 = 11.0 * THICKNESS_LARGE;
17const BATTERY_WIDTH: f32 = 2.0 * BATTERY_HEIGHT;
18
19pub struct Battery {
20 id: Id,
21 rect: Rectangle,
22 children: Vec<Box<dyn View>>,
23 status: Status,
24 capacity: f32,
25}
26
27impl Battery {
28 pub fn new(rect: Rectangle, capacity: f32, status: Status) -> Battery {
29 Battery {
30 id: ID_FEEDER.next(),
31 rect,
32 children: Vec::new(),
33 capacity,
34 status,
35 }
36 }
37
38 pub fn update(&mut self, rq: &mut RenderQueue, context: &mut Context) {
39 self.capacity = context.battery.capacity().map_or(self.capacity, |v| v[0]);
40 self.status = context.battery.status().map_or(self.status, |v| v[0]);
41 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
42 }
43}
44
45impl View for Battery {
46 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _hub, bus, rq, context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
47 fn handle_event(
48 &mut self,
49 evt: &Event,
50 _hub: &Hub,
51 bus: &mut Bus,
52 rq: &mut RenderQueue,
53 context: &mut Context,
54 ) -> bool {
55 match *evt {
56 Event::BatteryTick => {
57 self.update(rq, context);
58 true
59 }
60 Event::Gesture(GestureEvent::Tap(center)) if self.rect.includes(center) => {
61 bus.push_back(Event::ToggleNear(ViewId::BatteryMenu, self.rect));
62 true
63 }
64 _ => false,
65 }
66 }
67
68 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, fb, _fonts, _rect), fields(rect = ?_rect)))]
69 fn render(&self, fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {
70 let dpi = CURRENT_DEVICE.dpi;
71
72 let border_radius = scale_by_dpi(BORDER_RADIUS_SMALL, dpi) as i32;
73 let border_thickness = scale_by_dpi(THICKNESS_LARGE, dpi) as i32;
74
75 let batt_width = scale_by_dpi(BATTERY_WIDTH, dpi) as i32;
76 let batt_height = scale_by_dpi(BATTERY_HEIGHT, dpi) as i32;
77
78 let bump_width = scale_by_dpi(BUMP_WIDTH, dpi) as i32;
79 let bump_height = scale_by_dpi(BUMP_HEIGHT, dpi) as i32;
80
81 let edge_width = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
82
83 let dx = (self.rect.width() as i32 - (batt_width + bump_width - border_thickness)) / 2;
84 let dy = (self.rect.height() as i32 - batt_height) / 2;
85
86 let mut pt = self.rect.min + pt!(dx, dy);
87 let batt_rect = rect![pt, pt + pt!(batt_width, batt_height)];
88
89 fb.draw_rectangle(&self.rect, WHITE);
90
91 let max_fill_width = batt_width - 2 * border_thickness;
92 let fill_width = (self.capacity.clamp(0.0, 100.0) / 100.0 * max_fill_width as f32) as i32;
93 let fill_height = batt_height - 2 * border_thickness;
94 let x_offset_edge = pt.x + border_thickness + fill_width;
95 let x_offset_fill = x_offset_edge.saturating_sub(edge_width);
96
97 fb.draw_rounded_rectangle_with_border(
98 &batt_rect,
99 &CornerSpec::Uniform(border_radius),
100 &BorderSpec {
101 thickness: border_thickness as u16,
102 color: BLACK,
103 },
104 &|x, _| {
105 if x <= x_offset_fill {
106 BATTERY_FILL
107 } else if x <= x_offset_edge {
108 BLACK
109 } else {
110 WHITE
111 }
112 },
113 );
114
115 pt += pt!(
116 batt_width - border_thickness as i32,
117 (batt_height - bump_height) / 2
118 );
119 let bump_rect = rect![pt, pt + pt!(bump_width, bump_height)];
120
121 fb.draw_rounded_rectangle_with_border(
122 &bump_rect,
123 &CornerSpec::East(border_radius / 2),
124 &BorderSpec {
125 thickness: border_thickness as u16,
126 color: BLACK,
127 },
128 &WHITE,
129 );
130
131 pt = self.rect.min + pt!(dx, dy) + pt!(border_thickness);
132
133 if self.status.is_wired() {
134 let name = if self.status == Status::Charging {
135 "plug"
136 } else {
137 "check_mark-small"
138 };
139 let pixmap = ICONS_PIXMAPS.get(name).unwrap();
140 pt += pt!(
141 (max_fill_width - pixmap.width as i32) / 2,
142 (fill_height - pixmap.height as i32) / 2
143 );
144 fb.draw_blended_pixmap(pixmap, pt, BLACK);
145 }
146 }
147
148 fn rect(&self) -> &Rectangle {
149 &self.rect
150 }
151
152 fn rect_mut(&mut self) -> &mut Rectangle {
153 &mut self.rect
154 }
155
156 fn children(&self) -> &Vec<Box<dyn View>> {
157 &self.children
158 }
159
160 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
161 &mut self.children
162 }
163
164 fn id(&self) -> Id {
165 self.id
166 }
167}