@effindomv2/fui-rs 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COMMERCIAL.md +7 -0
- package/Cargo.toml +34 -0
- package/LICENSE.md +9 -0
- package/README.md +88 -0
- package/package.json +54 -0
- package/scripts/build.sh +227 -0
- package/scripts/check-runtime-dependency.sh +41 -0
- package/scripts/framework-host-services.ts +40 -0
- package/scripts/generate-host-events.ts +17 -0
- package/scripts/generate-host-services.ts +28 -0
- package/scripts/hostgen/common.ts +73 -0
- package/scripts/hostgen/registry.ts +159 -0
- package/scripts/hostgen/rust-host-events.ts +171 -0
- package/scripts/hostgen/rust-host-services.ts +257 -0
- package/src/animation.rs +625 -0
- package/src/app.rs +324 -0
- package/src/assets.rs +572 -0
- package/src/bindings/mod.rs +1 -0
- package/src/bindings/ui.rs +705 -0
- package/src/bitmap.rs +317 -0
- package/src/bridge_callbacks.rs +242 -0
- package/src/context_menu_manager.rs +332 -0
- package/src/controls/anti_selection_area.rs +54 -0
- package/src/controls/button.rs +542 -0
- package/src/controls/checkbox.rs +372 -0
- package/src/controls/combobox.rs +1574 -0
- package/src/controls/context_menu.rs +1367 -0
- package/src/controls/control_template_set.rs +46 -0
- package/src/controls/control_tokens.rs +596 -0
- package/src/controls/dialog.rs +590 -0
- package/src/controls/dropdown.rs +1010 -0
- package/src/controls/form.rs +229 -0
- package/src/controls/internal/button_presenter.rs +142 -0
- package/src/controls/internal/checkbox_indicator_presenter.rs +210 -0
- package/src/controls/internal/dropdown_chevron_presenter.rs +134 -0
- package/src/controls/internal/dropdown_field_presenter.rs +269 -0
- package/src/controls/internal/dropdown_option_row_presenter.rs +184 -0
- package/src/controls/internal/mod.rs +13 -0
- package/src/controls/internal/pressable_indicator_presenter.rs +26 -0
- package/src/controls/internal/pressable_labeled_control.rs +492 -0
- package/src/controls/internal/radio_indicator_presenter.rs +208 -0
- package/src/controls/internal/selectable_popup_list.rs +597 -0
- package/src/controls/internal/slider_presenter.rs +282 -0
- package/src/controls/internal/switch_indicator_presenter.rs +235 -0
- package/src/controls/internal/text_input_core.rs +1074 -0
- package/src/controls/internal/text_input_presenter.rs +108 -0
- package/src/controls/mod.rs +235 -0
- package/src/controls/nav_link.rs +395 -0
- package/src/controls/popup.rs +191 -0
- package/src/controls/progress_bar.rs +287 -0
- package/src/controls/radio_button.rs +348 -0
- package/src/controls/radio_group.rs +283 -0
- package/src/controls/selection_area.rs +82 -0
- package/src/controls/shared.rs +68 -0
- package/src/controls/slider.rs +701 -0
- package/src/controls/switch.rs +293 -0
- package/src/controls/templating.rs +49 -0
- package/src/controls/tests.rs +3905 -0
- package/src/controls/text_area.rs +207 -0
- package/src/controls/text_input.rs +192 -0
- package/src/debug.rs +146 -0
- package/src/drag_drop.rs +424 -0
- package/src/drag_gesture.rs +258 -0
- package/src/drawing.rs +385 -0
- package/src/event.rs +1603 -0
- package/src/external_drop.rs +467 -0
- package/src/fetch.rs +500 -0
- package/src/ffi.rs +3542 -0
- package/src/file.rs +1677 -0
- package/src/focus_adorner.rs +307 -0
- package/src/focus_visibility.rs +121 -0
- package/src/frame_scheduler.rs +159 -0
- package/src/frame_signal.rs +64 -0
- package/src/generated/ffi.rs +798 -0
- package/src/generated/framework_host_services.rs +74 -0
- package/src/generated/mod.rs +2 -0
- package/src/host_services.rs +162 -0
- package/src/image_sampling.rs +78 -0
- package/src/keyboard_scroll.rs +137 -0
- package/src/keyboard_scroll_tracker.rs +385 -0
- package/src/lib.rs +547 -0
- package/src/logger.rs +56 -0
- package/src/mobile_text_selection_toolbar.rs +1034 -0
- package/src/navigation.rs +48 -0
- package/src/node/core.rs +2210 -0
- package/src/node/custom_drawable.rs +149 -0
- package/src/node/flex_box.rs +874 -0
- package/src/node/grid.rs +304 -0
- package/src/node/helpers.rs +442 -0
- package/src/node/image.rs +506 -0
- package/src/node/mod.rs +315 -0
- package/src/node/scroll_bar.rs +845 -0
- package/src/node/scroll_box.rs +514 -0
- package/src/node/scroll_state.rs +121 -0
- package/src/node/scroll_view.rs +557 -0
- package/src/node/svg_node.rs +474 -0
- package/src/node/text_node.rs +633 -0
- package/src/node/virtual_list.rs +678 -0
- package/src/panic_hook.rs +36 -0
- package/src/persisted.rs +274 -0
- package/src/platform.rs +556 -0
- package/src/popup_presenter.rs +344 -0
- package/src/selection_handle_adorner.rs +838 -0
- package/src/signal.rs +134 -0
- package/src/text.rs +1065 -0
- package/src/theme.rs +571 -0
- package/src/timers.rs +106 -0
- package/src/tool_tip.rs +167 -0
- package/src/tool_tip_manager.rs +691 -0
- package/src/transitions.rs +41 -0
- package/src/typography.rs +520 -0
- package/src/viewport.rs +108 -0
- package/src/worker.rs +308 -0
- package/src/worker_job.rs +82 -0
- package/src/worker_runtime.rs +172 -0
package/src/persisted.rs
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
use crate::event;
|
|
2
|
+
use crate::ffi;
|
|
3
|
+
use std::rc::Rc;
|
|
4
|
+
|
|
5
|
+
fn with_utf8(value: &str, callback: impl FnOnce(usize, u32)) {
|
|
6
|
+
let bytes = value.as_bytes();
|
|
7
|
+
callback(
|
|
8
|
+
if bytes.is_empty() {
|
|
9
|
+
0
|
|
10
|
+
} else {
|
|
11
|
+
bytes.as_ptr() as usize
|
|
12
|
+
},
|
|
13
|
+
bytes.len() as u32,
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
18
|
+
pub struct PersistedScrollOffset {
|
|
19
|
+
pub x: f32,
|
|
20
|
+
pub y: f32,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
24
|
+
pub struct PersistedTextState {
|
|
25
|
+
pub version: u32,
|
|
26
|
+
pub payload: String,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
pub trait PersistedStateAdapter {
|
|
30
|
+
fn kind(&self) -> &str;
|
|
31
|
+
fn version(&self) -> u32;
|
|
32
|
+
fn capture(&self) -> Option<String>;
|
|
33
|
+
fn restore(&self, payload: &str, version: u32);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pub trait PersistedStateCodec<T>: 'static {
|
|
37
|
+
fn encode(&self, value: T) -> String;
|
|
38
|
+
fn decode(&self, payload: &str, version: u32) -> T;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
pub struct PersistedStringCodec;
|
|
42
|
+
pub struct PersistedBoolCodec;
|
|
43
|
+
pub struct PersistedInt32Codec;
|
|
44
|
+
pub struct PersistedFloat32Codec;
|
|
45
|
+
|
|
46
|
+
impl PersistedStateCodec<String> for PersistedStringCodec {
|
|
47
|
+
fn encode(&self, value: String) -> String {
|
|
48
|
+
value
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fn decode(&self, payload: &str, _version: u32) -> String {
|
|
52
|
+
payload.to_string()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
impl PersistedStateCodec<bool> for PersistedBoolCodec {
|
|
57
|
+
fn encode(&self, value: bool) -> String {
|
|
58
|
+
value.to_string()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fn decode(&self, payload: &str, _version: u32) -> bool {
|
|
62
|
+
payload.parse::<bool>().unwrap_or(false)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
impl PersistedStateCodec<i32> for PersistedInt32Codec {
|
|
67
|
+
fn encode(&self, value: i32) -> String {
|
|
68
|
+
value.to_string()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fn decode(&self, payload: &str, _version: u32) -> i32 {
|
|
72
|
+
payload.parse::<i32>().unwrap_or(0)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
impl PersistedStateCodec<f32> for PersistedFloat32Codec {
|
|
77
|
+
fn encode(&self, value: f32) -> String {
|
|
78
|
+
value.to_string()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
fn decode(&self, payload: &str, _version: u32) -> f32 {
|
|
82
|
+
payload.parse::<f32>().unwrap_or(0.0)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
struct PersistedValueAdapter<T, TCodec>
|
|
87
|
+
where
|
|
88
|
+
T: 'static,
|
|
89
|
+
TCodec: PersistedStateCodec<T>,
|
|
90
|
+
{
|
|
91
|
+
kind: String,
|
|
92
|
+
version: u32,
|
|
93
|
+
codec: TCodec,
|
|
94
|
+
capture_value: Rc<dyn Fn() -> Option<T>>,
|
|
95
|
+
restore_value: Rc<dyn Fn(T)>,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
impl<T, TCodec> PersistedStateAdapter for PersistedValueAdapter<T, TCodec>
|
|
99
|
+
where
|
|
100
|
+
T: 'static,
|
|
101
|
+
TCodec: PersistedStateCodec<T>,
|
|
102
|
+
{
|
|
103
|
+
fn kind(&self) -> &str {
|
|
104
|
+
&self.kind
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
fn version(&self) -> u32 {
|
|
108
|
+
self.version
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fn capture(&self) -> Option<String> {
|
|
112
|
+
(self.capture_value)().map(|value| self.codec.encode(value))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
fn restore(&self, payload: &str, version: u32) {
|
|
116
|
+
(self.restore_value)(self.codec.decode(payload, version));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
pub fn persisted_value_adapter<T, TCodec>(
|
|
121
|
+
kind: impl Into<String>,
|
|
122
|
+
codec: TCodec,
|
|
123
|
+
version: u32,
|
|
124
|
+
capture_value: impl Fn() -> Option<T> + 'static,
|
|
125
|
+
restore_value: impl Fn(T) + 'static,
|
|
126
|
+
) -> Rc<dyn PersistedStateAdapter>
|
|
127
|
+
where
|
|
128
|
+
T: 'static,
|
|
129
|
+
TCodec: PersistedStateCodec<T>,
|
|
130
|
+
{
|
|
131
|
+
let kind = kind.into();
|
|
132
|
+
assert!(
|
|
133
|
+
!kind.is_empty(),
|
|
134
|
+
"PersistedStateAdapter requires a non-empty kind."
|
|
135
|
+
);
|
|
136
|
+
Rc::new(PersistedValueAdapter {
|
|
137
|
+
kind,
|
|
138
|
+
version,
|
|
139
|
+
codec,
|
|
140
|
+
capture_value: Rc::new(capture_value),
|
|
141
|
+
restore_value: Rc::new(restore_value),
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
pub fn store_scroll_offset(node_id: &str, x: f32, y: f32) {
|
|
146
|
+
with_utf8(node_id, |node_id_ptr, node_id_len| unsafe {
|
|
147
|
+
ffi::fui_set_persisted_scroll_offset(node_id_ptr, node_id_len, x, y);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
pub fn store_text_state(node_id: &str, kind: &str, version: u32, payload: &str) {
|
|
152
|
+
with_utf8(node_id, |node_id_ptr, node_id_len| {
|
|
153
|
+
with_utf8(kind, |kind_ptr, kind_len| {
|
|
154
|
+
with_utf8(payload, |payload_ptr, payload_len| unsafe {
|
|
155
|
+
ffi::fui_set_persisted_state(
|
|
156
|
+
node_id_ptr,
|
|
157
|
+
node_id_len,
|
|
158
|
+
kind_ptr,
|
|
159
|
+
kind_len,
|
|
160
|
+
version,
|
|
161
|
+
payload_ptr,
|
|
162
|
+
payload_len,
|
|
163
|
+
);
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
pub fn try_load_scroll_offset(node_id: &str) -> Option<PersistedScrollOffset> {
|
|
170
|
+
let mut x = 0.0f32;
|
|
171
|
+
let mut y = 0.0f32;
|
|
172
|
+
let found = with_utf8_result(node_id, |node_id_ptr, node_id_len| unsafe {
|
|
173
|
+
ffi::fui_try_get_persisted_scroll_offset(
|
|
174
|
+
node_id_ptr,
|
|
175
|
+
node_id_len,
|
|
176
|
+
(&mut x as *mut f32) as usize,
|
|
177
|
+
(&mut y as *mut f32) as usize,
|
|
178
|
+
)
|
|
179
|
+
});
|
|
180
|
+
if found {
|
|
181
|
+
Some(PersistedScrollOffset { x, y })
|
|
182
|
+
} else {
|
|
183
|
+
None
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
pub fn try_load_text_state(node_id: &str, kind: &str) -> Option<PersistedTextState> {
|
|
188
|
+
let mut version = 0u32;
|
|
189
|
+
let payload_ptr = event::__fui_text_buffer() as usize;
|
|
190
|
+
let payload_capacity = event::__fui_text_buffer_size();
|
|
191
|
+
let copied = with_utf8_result(node_id, |node_id_ptr, node_id_len| {
|
|
192
|
+
with_utf8_result(kind, |kind_ptr, kind_len| unsafe {
|
|
193
|
+
ffi::fui_copy_persisted_state(
|
|
194
|
+
node_id_ptr,
|
|
195
|
+
node_id_len,
|
|
196
|
+
kind_ptr,
|
|
197
|
+
kind_len,
|
|
198
|
+
(&mut version as *mut u32) as usize,
|
|
199
|
+
payload_ptr,
|
|
200
|
+
payload_capacity,
|
|
201
|
+
)
|
|
202
|
+
})
|
|
203
|
+
});
|
|
204
|
+
if copied < 0 {
|
|
205
|
+
return None;
|
|
206
|
+
}
|
|
207
|
+
let copied_len = copied as usize;
|
|
208
|
+
if copied_len > payload_capacity as usize {
|
|
209
|
+
panic!("Persisted state payload exceeded shared Rust text buffer capacity.");
|
|
210
|
+
}
|
|
211
|
+
let payload = if copied_len == 0 {
|
|
212
|
+
String::new()
|
|
213
|
+
} else {
|
|
214
|
+
let bytes = unsafe { std::slice::from_raw_parts(payload_ptr as *const u8, copied_len) };
|
|
215
|
+
String::from_utf8_lossy(bytes).into_owned()
|
|
216
|
+
};
|
|
217
|
+
Some(PersistedTextState { version, payload })
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
fn with_utf8_result<T>(value: &str, callback: impl FnOnce(usize, u32) -> T) -> T {
|
|
221
|
+
let bytes = value.as_bytes();
|
|
222
|
+
callback(
|
|
223
|
+
if bytes.is_empty() {
|
|
224
|
+
0
|
|
225
|
+
} else {
|
|
226
|
+
bytes.as_ptr() as usize
|
|
227
|
+
},
|
|
228
|
+
bytes.len() as u32,
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
#[cfg(test)]
|
|
233
|
+
mod tests {
|
|
234
|
+
use super::{
|
|
235
|
+
persisted_value_adapter, store_scroll_offset, store_text_state, try_load_scroll_offset,
|
|
236
|
+
try_load_text_state, PersistedBoolCodec, PersistedScrollOffset, PersistedTextState,
|
|
237
|
+
};
|
|
238
|
+
use crate::ffi::{self, Call};
|
|
239
|
+
|
|
240
|
+
#[test]
|
|
241
|
+
fn persisted_scroll_round_trips_through_host() {
|
|
242
|
+
ffi::test::reset();
|
|
243
|
+
store_scroll_offset("list", 12.0, 34.0);
|
|
244
|
+
assert_eq!(
|
|
245
|
+
try_load_scroll_offset("list"),
|
|
246
|
+
Some(PersistedScrollOffset { x: 12.0, y: 34.0 }),
|
|
247
|
+
);
|
|
248
|
+
let calls = ffi::test::take_calls();
|
|
249
|
+
assert!(calls.iter().any(|call| matches!(call, Call::SetPersistedScrollOffset { node_id, x, y } if node_id == "list" && *x == 12.0 && *y == 34.0)));
|
|
250
|
+
assert!(calls.iter().any(|call| matches!(call, Call::TryGetPersistedScrollOffset { node_id } if node_id == "list")));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
#[test]
|
|
254
|
+
fn persisted_text_round_trips_through_host() {
|
|
255
|
+
ffi::test::reset();
|
|
256
|
+
store_text_state("input", "text", 2, "hello");
|
|
257
|
+
assert_eq!(
|
|
258
|
+
try_load_text_state("input", "text"),
|
|
259
|
+
Some(PersistedTextState {
|
|
260
|
+
version: 2,
|
|
261
|
+
payload: "hello".to_string(),
|
|
262
|
+
}),
|
|
263
|
+
);
|
|
264
|
+
let calls = ffi::test::take_calls();
|
|
265
|
+
assert!(calls.iter().any(|call| matches!(call, Call::SetPersistedState { node_id, kind, version, payload } if node_id == "input" && kind == "text" && *version == 2 && payload == "hello")));
|
|
266
|
+
assert!(calls.iter().any(|call| matches!(call, Call::CopyPersistedState { node_id, kind } if node_id == "input" && kind == "text")));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
#[test]
|
|
270
|
+
#[should_panic(expected = "PersistedStateAdapter requires a non-empty kind.")]
|
|
271
|
+
fn persisted_value_adapter_rejects_empty_kind() {
|
|
272
|
+
let _ = persisted_value_adapter("", PersistedBoolCodec, 1, || Some(true), |_| {});
|
|
273
|
+
}
|
|
274
|
+
}
|