1use crate::{action::AppState, registry::AnimationPropertyId};
2use fission_i18n::{I18nRegistry, Locale};
3use fission_ir::{NodeId, WidgetNodeId};
4use fission_layout::{LayoutPoint, LayoutSize};
5use fission_theme::Theme;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::sync::Arc;
9
10#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
11pub struct WindowInsets {
12 pub top: f32,
13 pub bottom: f32,
14 pub left: f32,
15 pub right: f32,
16}
17
18#[derive(Clone)]
20pub struct Env {
21 pub theme: Theme,
22 pub i18n: I18nRegistry,
23 pub locale: Locale,
24 pub window_insets: WindowInsets,
25 pub viewport_size: LayoutSize,
26 pub measurer: Option<Arc<dyn fission_layout::TextMeasurer>>,
27}
28
29impl Default for Env {
30 fn default() -> Self {
31 Self {
32 theme: Theme::default(),
33 i18n: I18nRegistry::new(),
34 locale: Locale::default(),
35 window_insets: WindowInsets::default(),
36 viewport_size: LayoutSize::default(),
37 measurer: None,
38 }
39 }
40}
41
42impl std::fmt::Debug for Env {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 f.debug_struct("Env")
45 .field("theme", &self.theme)
46 .field("locale", &self.locale)
47 .field("window_insets", &self.window_insets)
48 .field("viewport_size", &self.viewport_size)
49 .finish()
50 }
51}
52
53impl Env {
54 pub fn new(measurer: Arc<dyn fission_layout::TextMeasurer>) -> Self {
55 Self {
56 theme: Theme::default(),
57 i18n: I18nRegistry::new(),
58 locale: Locale::default(),
59 window_insets: WindowInsets::default(),
60 viewport_size: LayoutSize::default(),
61 measurer: Some(measurer),
62 }
63 }
64}
65
66pub trait Clipboard: Send + Sync {
67 fn get_text(&self) -> Option<String>;
68 fn set_text(&self, text: &str);
69}
70
71pub trait ImeHandler: Send + Sync {
72 fn set_ime_allowed(&self, allowed: bool);
73 fn set_ime_cursor_area(&self, rect: fission_layout::LayoutRect);
74}
75
76#[derive(Clone, Debug, Default)]
78pub struct RuntimeState {
79 pub scroll: ScrollStateMap,
80 pub video: VideoStateMap,
81 pub web: WebStateMap,
82 pub animation: AnimationStateMap,
83 pub interaction: InteractionStateMap,
84 pub ime_preedit: Option<(NodeId, String)>,
85 pub text_edit: TextEditStateMap,
86 pub clipboard: String,
87 pub caret_visible: HashMap<NodeId, bool>,
88 pub gesture: GestureState,
89 pub hero: HeroState,
90}
91
92#[derive(Clone, Debug, Default)]
93pub struct HeroState {
94 pub positions: HashMap<String, (NodeId, fission_layout::LayoutRect)>,
96}
97
98#[derive(Clone, Debug, Default)]
99pub struct GestureState {
100 pub start_point: Option<LayoutPoint>,
101 pub last_point: Option<LayoutPoint>,
102 pub is_panning: bool,
103 pub target_node: Option<NodeId>,
104 pub dragging_payload: Option<Vec<u8>>,
105 pub pressed_button: Option<crate::event::PointerButton>,
106}
107
108#[derive(Clone, Debug, Default)]
109pub struct AnimationStateMap {
110 pub values: HashMap<(WidgetNodeId, AnimationPropertyId), f32>,
111 pub active: HashMap<(WidgetNodeId, AnimationPropertyId), ActiveAnimation>,
112}
113
114#[derive(Clone, Debug)]
115pub struct ActiveAnimation {
116 pub target: WidgetNodeId,
117 pub property: AnimationPropertyId,
118 pub start_value: f32,
119 pub end_value: f32,
120 pub start_time: u64,
121 pub duration: u64,
122 pub repeat: bool,
123}
124
125#[derive(Clone, Debug, Default)]
126pub struct ScrollStateMap {
127 pub offsets: HashMap<NodeId, f32>,
128}
129
130impl ScrollStateMap {
131 pub fn get_offset(&self, id: NodeId) -> f32 {
132 *self.offsets.get(&id).unwrap_or(&0.0)
133 }
134
135 pub fn set_offset(&mut self, id: NodeId, offset: f32) {
136 self.offsets.insert(id, offset);
137 }
138}
139
140#[derive(Clone, Debug, Default)]
141pub struct TextEditStateMap {
142 pub states: HashMap<NodeId, TextEditState>,
143}
144
145#[derive(Clone, Debug)]
146pub struct TextEditState {
147 pub caret: usize, pub anchor: usize, pub history: TextEditHistory, pub last_value: String, pub pending_model_sync: bool, pub last_dispatched_cursor: Option<(usize, usize)>,
156}
157
158impl Default for TextEditState {
159 fn default() -> Self {
160 Self {
161 caret: 0,
162 anchor: 0,
163 history: TextEditHistory::default(),
164 last_value: String::new(),
165 pending_model_sync: false,
166 last_dispatched_cursor: None,
167 }
168 }
169}
170
171#[derive(Clone, Debug)]
172pub struct TextEditHistory {
173 pub stack: Vec<(String, usize, usize)>,
174 pub index: usize,
175 pub capacity: usize, }
177
178impl Default for TextEditHistory {
179 fn default() -> Self {
180 Self {
181 stack: vec![("".to_string(), 0, 0)],
182 index: 0,
183 capacity: 100,
184 }
185 }
186}
187
188impl TextEditHistory {
189 pub fn push(&mut self, value: String, caret: usize, anchor: usize) {
190 if let Some((last_val, last_caret, last_anchor)) = self.stack.get(self.index) {
192 if last_val == &value && last_caret == &caret && last_anchor == &anchor {
193 return;
194 }
195 }
196
197 self.stack.truncate(self.index + 1);
199
200 self.stack.push((value, caret, anchor));
202 self.index = self.stack.len() - 1;
203
204 if self.stack.len() > self.capacity {
206 let overflow = self.stack.len() - self.capacity;
207 self.stack.drain(0..overflow);
208 self.index = self.stack.len() - 1;
209 }
210 }
211
212 pub fn undo(&mut self) -> Option<&(String, usize, usize)> {
213 if self.index > 0 {
214 self.index -= 1;
215 Some(&self.stack[self.index])
216 } else {
217 None
218 }
219 }
220
221 pub fn redo(&mut self) -> Option<&(String, usize, usize)> {
222 if self.index < self.stack.len() - 1 {
223 self.index += 1;
224 Some(&self.stack[self.index])
225 } else {
226 None
227 }
228 }
229}
230
231impl TextEditStateMap {
232 pub fn get_mut_or_default(&mut self, id: NodeId) -> &mut TextEditState {
233 self.states.entry(id).or_default()
234 }
235 pub fn get(&self, id: NodeId) -> Option<&TextEditState> {
236 self.states.get(&id)
237 }
238 pub fn set_caret(&mut self, id: NodeId, caret: usize, anchor: Option<usize>) {
239 let st = self.states.entry(id).or_default();
240 st.caret = caret;
241 st.anchor = anchor.unwrap_or(caret);
242 st.pending_model_sync = false;
243 }
244}
245
246#[derive(Clone, Debug, Default)]
247pub struct InteractionStateMap {
248 pub hovered: HashMap<NodeId, bool>,
249 pub pressed: HashMap<NodeId, bool>,
250 pub focused: Option<NodeId>,
251 pub last_down_point: Option<LayoutPoint>,
252}
253
254impl InteractionStateMap {
255 pub fn is_hovered(&self, id: NodeId) -> bool {
256 self.hovered.get(&id).copied().unwrap_or(false)
257 }
258 pub fn is_pressed(&self, id: NodeId) -> bool {
259 self.pressed.get(&id).copied().unwrap_or(false)
260 }
261 pub fn is_focused(&self, id: NodeId) -> bool {
262 self.focused == Some(id)
263 }
264
265 pub fn set_hovered(&mut self, id: NodeId, value: bool) {
266 if value {
267 self.hovered.insert(id, true);
268 } else {
269 self.hovered.remove(&id);
270 }
271 }
272
273 pub fn set_pressed(&mut self, id: NodeId, value: bool) {
274 if value {
275 self.pressed.insert(id, true);
276 } else {
277 self.pressed.remove(&id);
278 }
279 }
280
281 pub fn set_focused(&mut self, id: Option<NodeId>) {
282 self.focused = id;
283 }
284}
285
286#[derive(Clone, Debug, Default)]
287pub struct VideoStateMap {
288 pub states: HashMap<WidgetNodeId, VideoState>,
289}
290
291#[derive(Clone, Debug, Default)]
292pub struct WebState {
293 pub url: String,
294 pub user_agent: Option<String>,
295 pub loading: bool,
296 pub can_go_back: bool,
297 pub can_go_forward: bool,
298 pub title: Option<String>,
299}
300
301#[derive(Clone, Debug, Default)]
302pub struct WebStateMap {
303 pub states: HashMap<WidgetNodeId, WebState>,
304}
305
306impl AppState for VideoStateMap {}
309
310#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
311pub struct VideoState {
312 pub status: VideoStatus,
313 pub position_ms: u64,
314 pub duration_ms: Option<u64>,
315 pub rate: f32,
316 pub volume: f32,
317 pub muted: bool,
318 pub looped: bool,
319 pub asset_source: String,
320 pub surface_id: Option<u64>,
321 pub pending_seek: Option<u64>,
322}
323
324impl Default for VideoState {
325 fn default() -> Self {
326 Self {
327 status: VideoStatus::Stopped,
328 position_ms: 0,
329 duration_ms: None,
330 rate: 1.0,
331 volume: 1.0,
332 muted: false,
333 looped: false,
334 asset_source: String::new(),
335 surface_id: None,
336 pending_seek: None,
337 }
338 }
339}
340
341#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
342pub enum VideoStatus {
343 Stopped,
344 Playing,
345 Paused,
346 Buffering,
347 Ended,
348 Error,
349}