From 683135c35c05cd547f8a77b3602c2ddc78c1e887 Mon Sep 17 00:00:00 2001 From: Tilman Sauerbeck Date: Sun, 22 Mar 2020 17:08:55 +0100 Subject: [PATCH] application: Introduce data model struct and view structs. This improves the structure of our UI code. --- SConscript.target | 2 + src/application/main.rs | 61 ++++++++++++++----------- src/application/model.rs | 87 +++++++++++++++++++++++++++++++++++ src/application/views.rs | 97 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 221 insertions(+), 26 deletions(-) create mode 100644 src/application/model.rs create mode 100644 src/application/views.rs diff --git a/SConscript.target b/SConscript.target index 539db88..2da14a4 100644 --- a/SConscript.target +++ b/SConscript.target @@ -85,6 +85,8 @@ for s in ['intermediate', 'final']: application_source_files = [ 'src/application/main.rs', # Must be listed first (see below). 'src/application/button.rs', + 'src/application/model.rs', + 'src/application/views.rs', 'src/application/uart0.rs', ] diff --git a/src/application/main.rs b/src/application/main.rs index dc955ce..2b2f8f6 100644 --- a/src/application/main.rs +++ b/src/application/main.rs @@ -29,6 +29,8 @@ extern crate common; mod uart0; mod button; +mod model; +mod views; use common::buffer::Buffer; use common::ringbuf::Ringbuf; @@ -44,7 +46,6 @@ use common::usb_serial; use common::display; use common::gps; use common::screen; -use common::time::Time; use common::mx25l::Mx25l; use common::shell::Shell; use common::logger::Logger; @@ -55,6 +56,12 @@ extern { static mut cdc_tx_buf: Buffer; } +#[derive(Clone, Copy, PartialEq)] +enum View { + Time, + Distance, +} + struct Timer { state: u32, delay_ms: u32, @@ -225,22 +232,27 @@ pub unsafe extern "C" fn _start() -> ! { let mut prev_tap = gps::TimeAndPos::new(); + let mut view = View::Time; + + let mut time_view = views::TimeView::new(); + let mut distance_view = views::DistanceView::new(); + + let mut model = model::Model::new(); + loop { let mut tap = gps::TimeAndPos::new(); - let mut show_time = false; - let mut show_distance = false; let old_gps_has_fix = gps_has_fix; while gps.update(&mut tap, uart0_try_read) { if is_recording { logger.log(&prev_tap, &tap); - show_distance = true; + model.update(model::Field::Distance(logger.total_distance_cm)); } - prev_tap = tap; + model.update(model::Field::UnixTime(tap.unix_time)); - show_time = true; + prev_tap = tap; gps_has_fix = true; gps_has_fix_ticks = systick::now(); @@ -275,26 +287,17 @@ pub unsafe extern "C" fn _start() -> ! { }); } - if show_distance { - let mut distance_m_s = [b' '; 8]; - - common::fmt::fmt_u32(&mut distance_m_s, - logger.total_distance_cm / 100); - - screen.clear(); - screen.draw_text(&distance_m_s); - - display.draw(&screen); - } else if show_time { - if let Some(tm) = Time::from_unix_time(prev_tap.unix_time) { - let mut time_s = [b' '; 8]; - tm.fmt_time(&mut time_s); - - screen.clear(); - screen.draw_text(&time_s); - - display.draw(&screen); - } + match view { + View::Time => { + if time_view.draw(&mut screen, &mut model) { + display.draw(&screen); + } + }, + View::Distance => { + if distance_view.draw(&mut screen, &mut model) { + display.draw(&screen); + } + }, } heart_icon_timer.update(|state| { @@ -314,9 +317,15 @@ pub unsafe extern "C" fn _start() -> ! { if is_recording { logger.start_recording(&prev_tap); + + view = View::Distance; } else { logger.stop_recording(&prev_tap); + + view = View::Time; } + + model.reset(); } if reset_requested() { diff --git a/src/application/model.rs b/src/application/model.rs new file mode 100644 index 0000000..a056af5 --- /dev/null +++ b/src/application/model.rs @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020 Tilman Sauerbeck (tilman at code-monkey de) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +pub enum Field { + UnixTime(u32), + Distance(u32), +} + +pub struct Model { + pub unix_time: u32, + pub distance_cm: u32, + + is_dirty: u32, +} + +impl Model { + pub fn new() -> Model { + Model { + unix_time: 0, + distance_cm: 0, + is_dirty: 0, + } + } + + pub fn reset(&mut self) { + if self.distance_cm != 0 { + self.distance_cm = 0; + self.is_dirty |= Model::dirty_mask(Field::Distance(0)); + } + } + + pub fn update(&mut self, data: Field) { + match data { + Field::UnixTime(unix_time) => { + if self.unix_time != unix_time { + self.unix_time = unix_time; + self.is_dirty |= Model::dirty_mask(data); + } + }, + Field::Distance(distance_cm) => { + if self.distance_cm != distance_cm { + self.distance_cm = distance_cm; + self.is_dirty |= Model::dirty_mask(data); + } + }, + } + } + + pub fn check_and_reset_is_dirty(&mut self, data: Field) -> bool { + let mask = Model::dirty_mask(data); + + if (self.is_dirty & mask) == 0 { + false + } else { + self.is_dirty &= !mask; + + true + } + } + + fn dirty_mask(data: Field) -> u32 { + match data { + Field::UnixTime(_) => 1, + Field::Distance(_) => 2, + } + } +} diff --git a/src/application/views.rs b/src/application/views.rs new file mode 100644 index 0000000..1218532 --- /dev/null +++ b/src/application/views.rs @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2020 Tilman Sauerbeck (tilman at code-monkey de) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +use common::time::Time; +use common::screen; +use model; + +pub struct TimeView { + is_valid: bool, +} + +impl TimeView { + pub fn new() -> TimeView { + TimeView { + is_valid: false, + } + } + + pub fn invalidate(&mut self) { + self.is_valid = false; + } + + pub fn draw(&mut self, screen: &mut screen::Screen, + model: &mut model::Model) -> bool { + if self.is_valid && + !model.check_and_reset_is_dirty(model::Field::UnixTime(0)) { + return false; + } + + if let Some(tm) = Time::from_unix_time(model.unix_time) { + let mut time_s = [b' '; 8]; + tm.fmt_time(&mut time_s); + + screen.clear(); + screen.draw_text(&time_s); + } + + self.is_valid = true; + + true + } +} + +pub struct DistanceView { + is_valid: bool, +} + +impl DistanceView { + pub fn new() -> DistanceView { + DistanceView { + is_valid: false, + } + } + + pub fn invalidate(&mut self) { + self.is_valid = false; + } + + pub fn draw(&mut self, screen: &mut screen::Screen, + model: &mut model::Model) -> bool { + if self.is_valid && + !model.check_and_reset_is_dirty(model::Field::Distance(0)) { + return false; + } + + let mut distance_m_s = [b' '; 8]; + + common::fmt::fmt_u32(&mut distance_m_s, model.distance_cm / 100); + + screen.clear(); + screen.draw_text(&distance_m_s); + + self.is_valid = true; + + true + } +} -- 2.30.2