Explorar el Código

First part of UI refactoring (with proper quick refresh).

Mathias Gottschlag hace 5 años
padre
commit
815f41c7d9

+ 39
- 324
display/firmware/src/display.rs Ver fichero

@@ -1,34 +1,23 @@
1
-use core::fmt::Write;
2
-
3 1
 use embedded_hal::blocking::delay::DelayUs;
4 2
 use embedded_hal::digital::v2::OutputPin;
5 3
 use epd_waveshare::epd4in2::EPD4in2;
6
-use epd_waveshare::prelude::{Color, DisplayStream, RefreshLUT, WaveshareDisplay};
4
+use epd_waveshare::prelude::{Color, DisplayStream, QuickRefresh, RefreshLUT, WaveshareDisplay};
7 5
 use mkl25z4_hal::time::CopyableMonoTimer;
8
-use tinygfx::color::BlackWhite::{self, Black, White};
9
-use tinygfx::image::{MonoBitmapImage, MonoImageData};
10
-use tinygfx::{Clip, Frame, MonoImage, Rectangle, Renderer, Text, TextAlignment};
6
+use tinygfx::color::BlackWhite;
7
+use tinygfx::{Frame, Renderer};
11 8
 
12
-use super::assets::*;
13
-use super::information::{Information, TemperatureHumidity};
14 9
 use super::pins::{
15 10
     DisplayBmeSpi, DisplayBusy, DisplayCs, DisplayDc, DisplayPins, DisplayPwr, DisplayRst,
16 11
 };
12
+use super::ui::Ui;
17 13
 
18 14
 const FULL_REFRESH_INTERVAL: usize = 5;
19 15
 
20
-pub struct DisplayState {}
21
-
22
-impl DisplayState {
23
-    pub fn new() -> DisplayState {
24
-        DisplayState {}
25
-    }
26
-}
27
-
28 16
 pub struct Display {
29 17
     pwr: DisplayPwr,
30 18
     epd: EPD4in2<DisplayBmeSpi, DisplayCs, DisplayBusy, DisplayDc, DisplayRst>,
31 19
     refresh_counter: usize,
20
+    current_ui: Option<Ui>,
32 21
 }
33 22
 
34 23
 impl Display {
@@ -51,16 +40,11 @@ impl Display {
51 40
             pwr: pins.pwr,
52 41
             epd,
53 42
             refresh_counter: 0,
43
+            current_ui: None,
54 44
         }
55 45
     }
56 46
 
57
-    pub fn update(
58
-        &mut self,
59
-        _display_state: &DisplayState,
60
-        info: &Information,
61
-        spi: &mut DisplayBmeSpi,
62
-        mut time: CopyableMonoTimer,
63
-    ) {
47
+    pub fn update(&mut self, ui: Ui, spi: &mut DisplayBmeSpi, mut time: CopyableMonoTimer) {
64 48
         // Enable power.
65 49
         self.pwr.set_low().ok();
66 50
         // Wait a bit for power to stabilize.
@@ -69,87 +53,45 @@ impl Display {
69 53
         // TODO: Error handling.
70 54
         self.epd.wake_up(spi, &mut time).ok();
71 55
 
72
-        // TODO: Insert values.
73
-        let date = Text::new(8, 8, "Samstag, 25.04.2020", &ROBOTO_22, Black);
74
-        let mut time = Text::new(392, 8, "12:34", &ROBOTO_22, Black);
75
-        time.align(TextAlignment::Right);
76
-        let top_bar = Rectangle::new(0, 34, 400, 1, Black);
77
-
78
-        let tile_width = 400 / 2 - 1;
79
-        let tile_height = (300 - 35) / 2 - 1;
80
-
81
-        let outside_strings = info.outside.map(|x| TempHumStrings::new(x));
82
-        let inside_strings = info.inside.map(|x| TempHumStrings::new(x));
83
-        let bathroom_strings = info.bathroom.map(|x| TempHumStrings::new(x));
84
-        let pressure_string = info.pressure.map(|x| PressureString::new(x));
85
-
86
-        let inside_tile = TempHumTile::new(
87
-            "Innen",
88
-            inside_strings.as_ref(),
89
-            0,
90
-            35,
91
-            tile_width,
92
-            tile_height,
93
-        );
94
-        let outside_tile = TempHumTile::new(
95
-            "Außen",
96
-            outside_strings.as_ref(),
97
-            200,
98
-            35,
99
-            tile_width,
100
-            tile_height,
101
-        );
102
-        let bathroom_tile = TempHumTile::new(
103
-            "Badezimmer",
104
-            bathroom_strings.as_ref(),
105
-            0,
106
-            35 + tile_height + 1,
107
-            tile_width,
108
-            tile_height,
109
-        );
110
-        let pressure_tile = PressureTile::new(
111
-            pressure_string.as_ref(),
112
-            200,
113
-            35 + tile_height + 1,
114
-            tile_width,
115
-            tile_height,
116
-        );
117
-
118
-        let vert_separator = Rectangle::new(8, 35 + tile_height, 392, 1, Black);
119
-        let hor_separator = Rectangle::new(tile_width, 35 + 8, 1, 300 - 35 - 16, Black);
120
-
121
-        let mut frame = Frame::new(400, 300, |mut renderer| {
122
-            let clip = renderer.full_frame();
123
-            renderer.clear(White);
124
-
125
-            top_bar.draw(clip, &mut renderer);
126
-            date.draw(clip, &mut renderer);
127
-            time.draw(clip, &mut renderer);
128
-
129
-            vert_separator.draw(clip, &mut renderer);
130
-            hor_separator.draw(clip, &mut renderer);
131
-
132
-            inside_tile.draw(clip, &mut renderer);
133
-            outside_tile.draw(clip, &mut renderer);
134
-            bathroom_tile.draw(clip, &mut renderer);
135
-            pressure_tile.draw(clip, &mut renderer);
136
-        });
137
-        frame.mirror_x(true);
138
-        frame.mirror_y(true);
139
-        let checkerboard = TinyGfxStream::new(frame);
140
-
141
-        // We perform a full update every FULL_REFRESH_INTERVAL frames.
142
-        // TODO: Proper background.
143
-        self.epd.set_background_color(Color::White);
144
-        if self.refresh_counter == 0 {
56
+        // We perform a full refresh every X images or if we do not know the state of the display.
57
+        let current_ui = self.current_ui.as_ref();
58
+        if self.refresh_counter == 0 || current_ui.is_none() {
145 59
             self.refresh_counter = FULL_REFRESH_INTERVAL - 1;
60
+
61
+            self.epd.set_background_color(Color::White);
146 62
             self.epd.set_lut(spi, Some(RefreshLUT::FULL)).unwrap();
63
+
64
+            // Just draw the new UI.
65
+            let mut frame = Frame::new(400, 300, |mut renderer| {
66
+                ui.draw(&mut renderer);
67
+            });
68
+            frame.mirror_x(true);
69
+            frame.mirror_y(true);
70
+            let stream = TinyGfxStream::new(frame);
71
+            self.epd.update_frame_stream(spi, stream).unwrap();
147 72
         } else {
148 73
             self.refresh_counter -= 1;
149 74
             self.epd.set_lut(spi, Some(RefreshLUT::QUICK)).unwrap();
75
+
76
+            // Draw both old and new frames.
77
+            let mut frame = Frame::new(400, 300, |mut renderer| {
78
+                current_ui.unwrap().draw(&mut renderer);
79
+            });
80
+            frame.mirror_x(true);
81
+            frame.mirror_y(true);
82
+            let stream = TinyGfxStream::new(frame);
83
+            self.epd.stream_old_frame(spi, stream).unwrap();
84
+
85
+            let mut frame = Frame::new(400, 300, |mut renderer| {
86
+                ui.draw(&mut renderer);
87
+            });
88
+            frame.mirror_x(true);
89
+            frame.mirror_y(true);
90
+            let stream = TinyGfxStream::new(frame);
91
+            self.epd.stream_new_frame(spi, stream).unwrap();
150 92
         }
93
+        self.current_ui = Some(ui);
151 94
 
152
-        self.epd.update_frame_stream(spi, checkerboard).unwrap();
153 95
         self.epd.display_frame(spi).unwrap();
154 96
 
155 97
         // Disable power again.
@@ -157,233 +99,6 @@ impl Display {
157 99
     }
158 100
 }
159 101
 
160
-struct WriteBuffer<T> {
161
-    buffer: T,
162
-    length: usize,
163
-}
164
-
165
-impl<T> Write for WriteBuffer<T>
166
-where
167
-    T: AsMut<[u8]>,
168
-{
169
-    fn write_str(&mut self, s: &str) -> core::fmt::Result {
170
-        let bytes = s.as_bytes();
171
-        if bytes.len() <= self.buffer.as_mut().len() - self.length {
172
-            self.buffer.as_mut()[self.length..bytes.len() + self.length].copy_from_slice(bytes);
173
-            self.length += bytes.len();
174
-            Ok(())
175
-        } else {
176
-            Err(core::fmt::Error)
177
-        }
178
-    }
179
-}
180
-
181
-impl<T> WriteBuffer<T>
182
-where
183
-    T: AsRef<[u8]>,
184
-{
185
-    fn to_str<'a>(&'a self) -> &'a str {
186
-        unsafe {
187
-            // Safe, we did not use any non-UTF characters above.
188
-            core::str::from_utf8_unchecked(&self.buffer.as_ref()[0..self.length])
189
-        }
190
-    }
191
-}
192
-
193
-struct TempHumStrings {
194
-    temp: WriteBuffer<[u8; 12]>,
195
-    hum: WriteBuffer<[u8; 12]>,
196
-}
197
-
198
-impl TempHumStrings {
199
-    fn new(data: TemperatureHumidity) -> TempHumStrings {
200
-        let mut strings = TempHumStrings {
201
-            temp: WriteBuffer {
202
-                buffer: [0u8; 12],
203
-                length: 0,
204
-            },
205
-            hum: WriteBuffer {
206
-                buffer: [0u8; 12],
207
-                length: 0,
208
-            },
209
-        };
210
-        write!(
211
-            strings.temp,
212
-            "{},{}°C",
213
-            data.temperature / 10,
214
-            (data.temperature % 10).abs()
215
-        )
216
-        .ok();
217
-        let humidity = (data.humidity + 5) / 10;
218
-        write!(strings.hum, "{},{}%", humidity / 10, humidity % 10).ok();
219
-        strings
220
-    }
221
-
222
-    fn temperature<'a>(&'a self) -> &'a str {
223
-        self.temp.to_str()
224
-    }
225
-
226
-    fn humidity<'a>(&'a self) -> &'a str {
227
-        self.hum.to_str()
228
-    }
229
-}
230
-
231
-struct TempHumTile<'a> {
232
-    title: Text<'a, BlackWhite>,
233
-    data: Option<(
234
-        MonoImage<'static, MonoBitmapImage, BlackWhite>,
235
-        Text<'a, BlackWhite>,
236
-        MonoImage<'static, MonoBitmapImage, BlackWhite>,
237
-        Text<'a, BlackWhite>,
238
-    )>,
239
-    error: Option<MonoImage<'static, MonoBitmapImage, BlackWhite>>,
240
-}
241
-
242
-impl<'a> TempHumTile<'a> {
243
-    fn new(
244
-        title: &'a str,
245
-        data: Option<&'a TempHumStrings>,
246
-        left: i32,
247
-        top: i32,
248
-        width: i32,
249
-        height: i32,
250
-    ) -> Self {
251
-        let center = left + width / 2;
252
-
253
-        let mut title = Text::new(center, top + 10, title, &ROBOTO_30, Black);
254
-        title.align(TextAlignment::Center);
255
-
256
-        let mut tile = Self {
257
-            title,
258
-            data: None,
259
-            error: None,
260
-        };
261
-
262
-        if let Some(data) = data {
263
-            let temp_width =
264
-                ROBOTO_30.get_text_size(data.temperature()).0 + 1 + THERMOMETER_36.width();
265
-            let temp_icon = MonoImage::new(
266
-                center - (temp_width / 2) as i32,
267
-                top + 48,
268
-                &THERMOMETER_36,
269
-                Black,
270
-            );
271
-            let mut temperature = Text::new(
272
-                center + (temp_width - temp_width / 2) as i32,
273
-                top + 48,
274
-                data.temperature(),
275
-                &ROBOTO_30,
276
-                Black,
277
-            );
278
-            temperature.align(TextAlignment::Right);
279
-            let hum_width = ROBOTO_30.get_text_size(data.humidity()).0 + 1 + THERMOMETER_36.width();
280
-            let hum_icon = MonoImage::new(
281
-                center - (hum_width / 2) as i32,
282
-                top + 82,
283
-                &WATER_PERCENT_36,
284
-                Black,
285
-            );
286
-            let mut humidity = Text::new(
287
-                center + (hum_width - hum_width / 2) as i32,
288
-                top + 82,
289
-                data.humidity(),
290
-                &ROBOTO_30,
291
-                Black,
292
-            );
293
-            humidity.align(TextAlignment::Right);
294
-
295
-            tile.data = Some((temp_icon, temperature, hum_icon, humidity));
296
-        } else {
297
-            tile.error = Some(MonoImage::new(
298
-                center - (WIFI_STRENGTH_ALERT_36.width() / 2) as i32,
299
-                top + height / 2 - (WIFI_STRENGTH_ALERT_36.height() / 2) as i32,
300
-                &WIFI_STRENGTH_ALERT_36,
301
-                Black,
302
-            ));
303
-        }
304
-
305
-        tile
306
-    }
307
-
308
-    fn draw(&self, clip: Clip, renderer: &mut Renderer<BlackWhite>) {
309
-        self.title.draw(clip, renderer);
310
-        if let Some(data) = self.data.as_ref() {
311
-            data.0.draw(clip, renderer);
312
-            data.1.draw(clip, renderer);
313
-            data.2.draw(clip, renderer);
314
-            data.3.draw(clip, renderer);
315
-        }
316
-        if let Some(error) = self.error.as_ref() {
317
-            error.draw(clip, renderer);
318
-        }
319
-    }
320
-}
321
-
322
-struct PressureString(WriteBuffer<[u8; 12]>);
323
-
324
-impl PressureString {
325
-    fn new(pressure: u32) -> PressureString {
326
-        let pressure = (pressure + 5) / 10;
327
-        let mut buffer = WriteBuffer {
328
-            buffer: [0u8; 12],
329
-            length: 0,
330
-        };
331
-        write!(buffer, "{},{}hPa", pressure / 10, pressure % 10).ok();
332
-        PressureString(buffer)
333
-    }
334
-
335
-    fn to_str<'a>(&'a self) -> &'a str {
336
-        self.0.to_str()
337
-    }
338
-}
339
-
340
-struct PressureTile<'a> {
341
-    title: Text<'a, BlackWhite>,
342
-    data: Option<Text<'a, BlackWhite>>,
343
-    error: Option<MonoImage<'static, MonoBitmapImage, BlackWhite>>,
344
-}
345
-
346
-impl<'a> PressureTile<'a> {
347
-    fn new(data: Option<&'a PressureString>, left: i32, top: i32, width: i32, height: i32) -> Self {
348
-        let center = left + width / 2;
349
-
350
-        let mut title = Text::new(center, top + 10, "Luftdruck", &ROBOTO_30, Black);
351
-        title.align(TextAlignment::Center);
352
-
353
-        let mut tile = Self {
354
-            title,
355
-            data: None,
356
-            error: None,
357
-        };
358
-
359
-        if let Some(data) = data {
360
-            let mut pressure = Text::new(center, top + 48, data.to_str(), &ROBOTO_30, Black);
361
-            pressure.align(TextAlignment::Center);
362
-
363
-            tile.data = Some(pressure);
364
-        } else {
365
-            tile.error = Some(MonoImage::new(
366
-                center - (WIFI_STRENGTH_ALERT_36.width() / 2) as i32,
367
-                top + height / 2 - (WIFI_STRENGTH_ALERT_36.height() / 2) as i32,
368
-                &WIFI_STRENGTH_ALERT_36,
369
-                Black,
370
-            ));
371
-        }
372
-
373
-        tile
374
-    }
375
-
376
-    fn draw(&self, clip: Clip, renderer: &mut Renderer<BlackWhite>) {
377
-        self.title.draw(clip, renderer);
378
-        if let Some(data) = self.data.as_ref() {
379
-            data.draw(clip, renderer);
380
-        }
381
-        if let Some(error) = self.error.as_ref() {
382
-            error.draw(clip, renderer);
383
-        }
384
-    }
385
-}
386
-
387 102
 struct TinyGfxStream<Draw> {
388 103
     frame: Frame<Draw, BlackWhite>,
389 104
     buffer: [u8; 400 / 8],

+ 1
- 0
display/firmware/src/information.rs Ver fichero

@@ -2,6 +2,7 @@
2 2
 
3 3
 use super::sensors::BME280Data;
4 4
 
5
+#[derive(Clone)]
5 6
 pub struct Information {
6 7
     pub time: u32,
7 8
     pub valid: bool,

+ 7
- 6
display/firmware/src/main.rs Ver fichero

@@ -6,6 +6,7 @@ mod information;
6 6
 mod pins;
7 7
 mod radio;
8 8
 mod sensors;
9
+mod ui;
9 10
 
10 11
 // TODO: Enable warnings again.
11 12
 #[allow(unused)]
@@ -23,16 +24,17 @@ use mkl25z4_hal::timer::TimerInterrupt;
23 24
 use panic_semihosting as _;
24 25
 use protocol::{Location, ValueType};
25 26
 
26
-use display::{Display, DisplayState};
27
+use display::Display;
27 28
 use information::{Information, TemperatureHumidity};
28 29
 use pins::{BME280Pins, DisplayBmeSpi};
29 30
 use radio::Radio;
31
+use ui::tiles::TileView;
32
+use ui::{Ui, View};
30 33
 
31 34
 #[rtfm::app(device = mkl25z4_hal::mkl25z4, peripherals = true)]
32 35
 const APP: () = {
33 36
     struct Resources {
34 37
         info: Information,
35
-        display_state: DisplayState,
36 38
         display: Display,
37 39
         display_bme_spi: Option<DisplayBmeSpi>,
38 40
         bme_pins: Option<BME280Pins>,
@@ -87,7 +89,6 @@ const APP: () = {
87 89
                 pressure: None,
88 90
                 local: None,
89 91
             },
90
-            display_state: DisplayState::new(),
91 92
             display,
92 93
             display_bme_spi: Some(pins.display_bme_spi),
93 94
             bme_pins: Some(pins.bme),
@@ -162,11 +163,11 @@ const APP: () = {
162 163
         ctx.spawn.update_display().ok();
163 164
     }
164 165
 
165
-    #[task(priority = 2, resources = [info, display_state, display, display_bme_spi, &time])]
166
+    #[task(priority = 2, resources = [info, display, display_bme_spi, &time])]
166 167
     fn update_display(ctx: update_display::Context) {
168
+        let ui = Ui::view(View::Tiles(TileView::new(ctx.resources.info.clone())));
167 169
         ctx.resources.display.update(
168
-            &ctx.resources.display_state,
169
-            &ctx.resources.info,
170
+            ui,
170 171
             ctx.resources.display_bme_spi.as_mut().unwrap(),
171 172
             ctx.resources.time.clone(),
172 173
         );

+ 17
- 0
display/firmware/src/ui/clock.rs Ver fichero

@@ -0,0 +1,17 @@
1
+use tinygfx::color::BlackWhite;
2
+use tinygfx::Renderer;
3
+
4
+pub struct ClockView {
5
+    // TODO
6
+}
7
+
8
+impl ClockView {
9
+    pub fn new() -> ClockView {
10
+        // TODO
11
+        ClockView {}
12
+    }
13
+
14
+    pub fn draw(&self, _renderer: &mut Renderer<BlackWhite>) {
15
+        // TODO
16
+    }
17
+}

+ 37
- 0
display/firmware/src/ui/mod.rs Ver fichero

@@ -0,0 +1,37 @@
1
+use tinygfx::color::BlackWhite;
2
+use tinygfx::Renderer;
3
+
4
+pub mod clock;
5
+pub mod tiles;
6
+
7
+use clock::ClockView;
8
+use tiles::TileView;
9
+
10
+pub struct Ui {
11
+    view: View,
12
+}
13
+
14
+impl Ui {
15
+    pub fn view(view: View) -> Ui {
16
+        Ui { view }
17
+    }
18
+
19
+    // TODO: Black/white/red frames or greyscale frames?
20
+    pub fn draw(&self, renderer: &mut Renderer<BlackWhite>) {
21
+        self.view.draw(renderer);
22
+    }
23
+}
24
+
25
+pub enum View {
26
+    Tiles(TileView),
27
+    Clock(ClockView),
28
+}
29
+
30
+impl View {
31
+    fn draw(&self, renderer: &mut Renderer<BlackWhite>) {
32
+        match self {
33
+            View::Tiles(view) => view.draw(renderer),
34
+            View::Clock(view) => view.draw(renderer),
35
+        }
36
+    }
37
+}

+ 313
- 0
display/firmware/src/ui/tiles.rs Ver fichero

@@ -0,0 +1,313 @@
1
+use core::fmt::Write;
2
+
3
+use tinygfx::color::BlackWhite::{self, Black, White};
4
+use tinygfx::image::{MonoBitmapImage, MonoImageData};
5
+use tinygfx::{Clip, MonoImage, Rectangle, Renderer, Text, TextAlignment};
6
+
7
+use super::super::assets::*;
8
+use super::super::information::{Information, TemperatureHumidity};
9
+
10
+pub struct TileView {
11
+    info: Information,
12
+}
13
+
14
+impl TileView {
15
+    pub fn new(info: Information) -> TileView {
16
+        // TODO: Optimization, prepare more values here.
17
+        TileView { info }
18
+    }
19
+
20
+    pub fn draw(&self, renderer: &mut Renderer<BlackWhite>) {
21
+        // TODO: Insert values.
22
+        let date = Text::new(8, 8, "Samstag, 25.04.2020", &ROBOTO_22, Black);
23
+        let mut time = Text::new(392, 8, "12:34", &ROBOTO_22, Black);
24
+        time.align(TextAlignment::Right);
25
+        let top_bar = Rectangle::new(0, 34, 400, 1, Black);
26
+
27
+        let tile_width = 400 / 2 - 1;
28
+        let tile_height = (300 - 35) / 2 - 1;
29
+
30
+        let outside_strings = self.info.outside.map(|x| TempHumStrings::new(x));
31
+        let inside_strings = self.info.inside.map(|x| TempHumStrings::new(x));
32
+        let bathroom_strings = self.info.bathroom.map(|x| TempHumStrings::new(x));
33
+        let pressure_string = self.info.pressure.map(|x| PressureString::new(x));
34
+
35
+        let inside_tile = TempHumTile::new(
36
+            "Innen",
37
+            inside_strings.as_ref(),
38
+            0,
39
+            35,
40
+            tile_width,
41
+            tile_height,
42
+        );
43
+        let outside_tile = TempHumTile::new(
44
+            "Außen",
45
+            outside_strings.as_ref(),
46
+            200,
47
+            35,
48
+            tile_width,
49
+            tile_height,
50
+        );
51
+        let bathroom_tile = TempHumTile::new(
52
+            "Badezimmer",
53
+            bathroom_strings.as_ref(),
54
+            0,
55
+            35 + tile_height + 1,
56
+            tile_width,
57
+            tile_height,
58
+        );
59
+        let pressure_tile = PressureTile::new(
60
+            pressure_string.as_ref(),
61
+            200,
62
+            35 + tile_height + 1,
63
+            tile_width,
64
+            tile_height,
65
+        );
66
+
67
+        let vert_separator = Rectangle::new(8, 35 + tile_height, 392, 1, Black);
68
+        let hor_separator = Rectangle::new(tile_width, 35 + 8, 1, 300 - 35 - 16, Black);
69
+
70
+        // Draw everything.
71
+        let clip = renderer.full_frame();
72
+        renderer.clear(White);
73
+
74
+        top_bar.draw(clip, renderer);
75
+        date.draw(clip, renderer);
76
+        time.draw(clip, renderer);
77
+
78
+        vert_separator.draw(clip, renderer);
79
+        hor_separator.draw(clip, renderer);
80
+
81
+        inside_tile.draw(clip, renderer);
82
+        outside_tile.draw(clip, renderer);
83
+        bathroom_tile.draw(clip, renderer);
84
+        pressure_tile.draw(clip, renderer);
85
+    }
86
+}
87
+
88
+struct WriteBuffer<T> {
89
+    buffer: T,
90
+    length: usize,
91
+}
92
+
93
+impl<T> Write for WriteBuffer<T>
94
+where
95
+    T: AsMut<[u8]>,
96
+{
97
+    fn write_str(&mut self, s: &str) -> core::fmt::Result {
98
+        let bytes = s.as_bytes();
99
+        if bytes.len() <= self.buffer.as_mut().len() - self.length {
100
+            self.buffer.as_mut()[self.length..bytes.len() + self.length].copy_from_slice(bytes);
101
+            self.length += bytes.len();
102
+            Ok(())
103
+        } else {
104
+            Err(core::fmt::Error)
105
+        }
106
+    }
107
+}
108
+
109
+impl<T> WriteBuffer<T>
110
+where
111
+    T: AsRef<[u8]>,
112
+{
113
+    fn to_str<'a>(&'a self) -> &'a str {
114
+        unsafe {
115
+            // Safe, we did not use any non-UTF characters above.
116
+            core::str::from_utf8_unchecked(&self.buffer.as_ref()[0..self.length])
117
+        }
118
+    }
119
+}
120
+
121
+struct TempHumStrings {
122
+    temp: WriteBuffer<[u8; 12]>,
123
+    hum: WriteBuffer<[u8; 12]>,
124
+}
125
+
126
+impl TempHumStrings {
127
+    fn new(data: TemperatureHumidity) -> TempHumStrings {
128
+        let mut strings = TempHumStrings {
129
+            temp: WriteBuffer {
130
+                buffer: [0u8; 12],
131
+                length: 0,
132
+            },
133
+            hum: WriteBuffer {
134
+                buffer: [0u8; 12],
135
+                length: 0,
136
+            },
137
+        };
138
+        write!(
139
+            strings.temp,
140
+            "{},{}°C",
141
+            data.temperature / 10,
142
+            (data.temperature % 10).abs()
143
+        )
144
+        .ok();
145
+        let humidity = (data.humidity + 5) / 10;
146
+        write!(strings.hum, "{},{}%", humidity / 10, humidity % 10).ok();
147
+        strings
148
+    }
149
+
150
+    fn temperature<'a>(&'a self) -> &'a str {
151
+        self.temp.to_str()
152
+    }
153
+
154
+    fn humidity<'a>(&'a self) -> &'a str {
155
+        self.hum.to_str()
156
+    }
157
+}
158
+
159
+struct TempHumTile<'a> {
160
+    title: Text<'a, BlackWhite>,
161
+    data: Option<(
162
+        MonoImage<'static, MonoBitmapImage, BlackWhite>,
163
+        Text<'a, BlackWhite>,
164
+        MonoImage<'static, MonoBitmapImage, BlackWhite>,
165
+        Text<'a, BlackWhite>,
166
+    )>,
167
+    error: Option<MonoImage<'static, MonoBitmapImage, BlackWhite>>,
168
+}
169
+
170
+impl<'a> TempHumTile<'a> {
171
+    fn new(
172
+        title: &'a str,
173
+        data: Option<&'a TempHumStrings>,
174
+        left: i32,
175
+        top: i32,
176
+        width: i32,
177
+        height: i32,
178
+    ) -> Self {
179
+        let center = left + width / 2;
180
+
181
+        let mut title = Text::new(center, top + 10, title, &ROBOTO_30, Black);
182
+        title.align(TextAlignment::Center);
183
+
184
+        let mut tile = Self {
185
+            title,
186
+            data: None,
187
+            error: None,
188
+        };
189
+
190
+        if let Some(data) = data {
191
+            let temp_width =
192
+                ROBOTO_30.get_text_size(data.temperature()).0 + 1 + THERMOMETER_36.width();
193
+            let temp_icon = MonoImage::new(
194
+                center - (temp_width / 2) as i32,
195
+                top + 48,
196
+                &THERMOMETER_36,
197
+                Black,
198
+            );
199
+            let mut temperature = Text::new(
200
+                center + (temp_width - temp_width / 2) as i32,
201
+                top + 48,
202
+                data.temperature(),
203
+                &ROBOTO_30,
204
+                Black,
205
+            );
206
+            temperature.align(TextAlignment::Right);
207
+            let hum_width = ROBOTO_30.get_text_size(data.humidity()).0 + 1 + THERMOMETER_36.width();
208
+            let hum_icon = MonoImage::new(
209
+                center - (hum_width / 2) as i32,
210
+                top + 82,
211
+                &WATER_PERCENT_36,
212
+                Black,
213
+            );
214
+            let mut humidity = Text::new(
215
+                center + (hum_width - hum_width / 2) as i32,
216
+                top + 82,
217
+                data.humidity(),
218
+                &ROBOTO_30,
219
+                Black,
220
+            );
221
+            humidity.align(TextAlignment::Right);
222
+
223
+            tile.data = Some((temp_icon, temperature, hum_icon, humidity));
224
+        } else {
225
+            tile.error = Some(MonoImage::new(
226
+                center - (WIFI_STRENGTH_ALERT_36.width() / 2) as i32,
227
+                top + height / 2 - (WIFI_STRENGTH_ALERT_36.height() / 2) as i32,
228
+                &WIFI_STRENGTH_ALERT_36,
229
+                Black,
230
+            ));
231
+        }
232
+
233
+        tile
234
+    }
235
+
236
+    fn draw(&self, clip: Clip, renderer: &mut Renderer<BlackWhite>) {
237
+        self.title.draw(clip, renderer);
238
+        if let Some(data) = self.data.as_ref() {
239
+            data.0.draw(clip, renderer);
240
+            data.1.draw(clip, renderer);
241
+            data.2.draw(clip, renderer);
242
+            data.3.draw(clip, renderer);
243
+        }
244
+        if let Some(error) = self.error.as_ref() {
245
+            error.draw(clip, renderer);
246
+        }
247
+    }
248
+}
249
+
250
+struct PressureString(WriteBuffer<[u8; 12]>);
251
+
252
+impl PressureString {
253
+    fn new(pressure: u32) -> PressureString {
254
+        let pressure = (pressure + 5) / 10;
255
+        let mut buffer = WriteBuffer {
256
+            buffer: [0u8; 12],
257
+            length: 0,
258
+        };
259
+        write!(buffer, "{},{}hPa", pressure / 10, pressure % 10).ok();
260
+        PressureString(buffer)
261
+    }
262
+
263
+    fn to_str<'a>(&'a self) -> &'a str {
264
+        self.0.to_str()
265
+    }
266
+}
267
+
268
+struct PressureTile<'a> {
269
+    title: Text<'a, BlackWhite>,
270
+    data: Option<Text<'a, BlackWhite>>,
271
+    error: Option<MonoImage<'static, MonoBitmapImage, BlackWhite>>,
272
+}
273
+
274
+impl<'a> PressureTile<'a> {
275
+    fn new(data: Option<&'a PressureString>, left: i32, top: i32, width: i32, height: i32) -> Self {
276
+        let center = left + width / 2;
277
+
278
+        let mut title = Text::new(center, top + 10, "Luftdruck", &ROBOTO_30, Black);
279
+        title.align(TextAlignment::Center);
280
+
281
+        let mut tile = Self {
282
+            title,
283
+            data: None,
284
+            error: None,
285
+        };
286
+
287
+        if let Some(data) = data {
288
+            let mut pressure = Text::new(center, top + 48, data.to_str(), &ROBOTO_30, Black);
289
+            pressure.align(TextAlignment::Center);
290
+
291
+            tile.data = Some(pressure);
292
+        } else {
293
+            tile.error = Some(MonoImage::new(
294
+                center - (WIFI_STRENGTH_ALERT_36.width() / 2) as i32,
295
+                top + height / 2 - (WIFI_STRENGTH_ALERT_36.height() / 2) as i32,
296
+                &WIFI_STRENGTH_ALERT_36,
297
+                Black,
298
+            ));
299
+        }
300
+
301
+        tile
302
+    }
303
+
304
+    fn draw(&self, clip: Clip, renderer: &mut Renderer<BlackWhite>) {
305
+        self.title.draw(clip, renderer);
306
+        if let Some(data) = self.data.as_ref() {
307
+            data.draw(clip, renderer);
308
+        }
309
+        if let Some(error) = self.error.as_ref() {
310
+            error.draw(clip, renderer);
311
+        }
312
+    }
313
+}

Loading…
Cancelar
Guardar