Skip to main content

faststep/app/
ui_app.rs

1use embedded_graphics::primitives::Rectangle;
2
3use crate::{ButtonTouchState, FsTheme, I18n, NavHeaderAction, StackNav, TouchEvent};
4
5use super::{AppNavigation, AppRedraw, AppTouchResult, ViewDelegate, ViewResponse};
6
7/// Compatibility app wrapper retained for pre-`UiSystem` consumers.
8pub struct UiApp<'a, ViewId, Delegate, const STACK_DEPTH: usize> {
9    pub(super) theme: FsTheme,
10    pub(super) i18n: I18n<'a>,
11    pub(super) stack: StackNav<ViewId, STACK_DEPTH>,
12    pub(super) nav_touch: ButtonTouchState<NavHeaderAction>,
13    pub(super) delegate: Delegate,
14}
15
16impl<'a, ViewId, Delegate, const STACK_DEPTH: usize> UiApp<'a, ViewId, Delegate, STACK_DEPTH>
17where
18    ViewId: Copy,
19    Delegate: ViewDelegate<'a, ViewId>,
20{
21    /// Creates a compatibility app rooted at `root`.
22    pub fn new(root: ViewId, delegate: Delegate, theme: FsTheme, i18n: I18n<'a>) -> Self {
23        Self {
24            theme,
25            i18n,
26            stack: StackNav::new(root),
27            nav_touch: ButtonTouchState::new(),
28            delegate,
29        }
30    }
31
32    /// Returns the delegate.
33    pub const fn delegate(&self) -> &Delegate {
34        &self.delegate
35    }
36
37    /// Returns the delegate mutably.
38    pub fn delegate_mut(&mut self) -> &mut Delegate {
39        &mut self.delegate
40    }
41
42    /// Returns the active theme.
43    pub const fn theme(&self) -> &FsTheme {
44        &self.theme
45    }
46
47    /// Returns the active theme mutably.
48    pub fn theme_mut(&mut self) -> &mut FsTheme {
49        &mut self.theme
50    }
51
52    /// Returns the active localization tables.
53    pub const fn i18n(&self) -> &I18n<'a> {
54        &self.i18n
55    }
56
57    /// Returns the active localization tables mutably.
58    pub fn i18n_mut(&mut self) -> &mut I18n<'a> {
59        &mut self.i18n
60    }
61
62    /// Returns the currently visible view identifier.
63    pub fn active_view(&self) -> ViewId {
64        self.stack.top()
65    }
66
67    /// Returns whether a stack animation is active.
68    pub fn is_animating(&self) -> bool {
69        self.stack.is_animating()
70    }
71
72    /// Returns the body frame for the active view.
73    pub fn content_frame(&self, bounds: Rectangle) -> Rectangle {
74        self.nav_view(bounds).body
75    }
76
77    /// Returns the dirty region for the active view.
78    pub fn active_view_dirty(&self, bounds: Rectangle) -> Rectangle {
79        self.content_frame(bounds)
80    }
81
82    /// Advances the compatibility app by one tick.
83    pub fn tick(&mut self, dt_ms: u32, bounds: Rectangle) -> AppRedraw {
84        if self.stack.advance(dt_ms) {
85            return AppRedraw::StackMotion;
86        }
87        let body = self.content_frame(bounds);
88        let response = self
89            .delegate
90            .tick(self.stack.top(), dt_ms, body, &self.theme, &self.i18n);
91        self.apply_response(response)
92    }
93
94    /// Handles one touch event.
95    pub fn handle_touch(&mut self, touch: TouchEvent, bounds: Rectangle) -> AppRedraw {
96        self.handle_touch_result(touch, bounds).redraw
97    }
98
99    /// Handles one touch event and returns capture status.
100    pub fn handle_touch_result(&mut self, touch: TouchEvent, bounds: Rectangle) -> AppTouchResult {
101        if self.is_animating() {
102            return AppTouchResult {
103                redraw: AppRedraw::None,
104                captured: false,
105            };
106        }
107
108        let nav = self.nav_view(bounds);
109        let chrome = nav.handle_touch(&mut self.nav_touch, touch);
110        if chrome.action == Some(NavHeaderAction::Back) {
111            self.clear_touch_state();
112            return AppTouchResult {
113                redraw: self
114                    .stack
115                    .pop()
116                    .map(|_| AppRedraw::StackMotion)
117                    .unwrap_or(AppRedraw::None),
118                captured: true,
119            };
120        }
121        if chrome.captured {
122            return AppTouchResult {
123                redraw: if chrome.redraw {
124                    AppRedraw::Interactive
125                } else {
126                    AppRedraw::None
127                },
128                captured: true,
129            };
130        }
131
132        let response = self.delegate.handle_view_touch(
133            self.stack.top(),
134            touch,
135            nav.body,
136            &self.theme,
137            &self.i18n,
138        );
139        AppTouchResult {
140            redraw: self.apply_response(response),
141            captured: response.captured || response.navigation.is_some(),
142        }
143    }
144
145    /// Clears all compatibility touch state.
146    pub fn clear_touch_state(&mut self) {
147        self.nav_touch.clear();
148        self.delegate.clear_touch_state();
149    }
150
151    fn apply_response(&mut self, response: ViewResponse<ViewId>) -> AppRedraw {
152        let mut redraw = response.redraw;
153        match response.navigation {
154            Some(AppNavigation::Push(view)) => {
155                self.clear_touch_state();
156                if self.stack.push(view).is_ok() {
157                    redraw = redraw.merge(AppRedraw::StackMotion);
158                }
159            }
160            Some(AppNavigation::Pop) => {
161                self.clear_touch_state();
162                if self.stack.pop().is_ok() {
163                    redraw = redraw.merge(AppRedraw::StackMotion);
164                }
165            }
166            None => {}
167        }
168        redraw
169    }
170}