1use embedded_graphics::primitives::Rectangle;
2
3use crate::{ButtonTouchState, FsTheme, I18n, NavHeaderAction, StackNav, TouchEvent};
4
5use super::{AppNavigation, AppRedraw, AppTouchResult, ViewDelegate, ViewResponse};
6
7pub 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 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 pub const fn delegate(&self) -> &Delegate {
34 &self.delegate
35 }
36
37 pub fn delegate_mut(&mut self) -> &mut Delegate {
39 &mut self.delegate
40 }
41
42 pub const fn theme(&self) -> &FsTheme {
44 &self.theme
45 }
46
47 pub fn theme_mut(&mut self) -> &mut FsTheme {
49 &mut self.theme
50 }
51
52 pub const fn i18n(&self) -> &I18n<'a> {
54 &self.i18n
55 }
56
57 pub fn i18n_mut(&mut self) -> &mut I18n<'a> {
59 &mut self.i18n
60 }
61
62 pub fn active_view(&self) -> ViewId {
64 self.stack.top()
65 }
66
67 pub fn is_animating(&self) -> bool {
69 self.stack.is_animating()
70 }
71
72 pub fn content_frame(&self, bounds: Rectangle) -> Rectangle {
74 self.nav_view(bounds).body
75 }
76
77 pub fn active_view_dirty(&self, bounds: Rectangle) -> Rectangle {
79 self.content_frame(bounds)
80 }
81
82 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 pub fn handle_touch(&mut self, touch: TouchEvent, bounds: Rectangle) -> AppRedraw {
96 self.handle_touch_result(touch, bounds).redraw
97 }
98
99 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 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}