@effindomv2/fui-rs 0.1.16 → 0.1.18
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/Cargo.toml +1 -1
- package/README.md +69 -0
- package/package.json +3 -2
- package/scripts/build.sh +6 -1
- package/scripts/hostgen/rust-host-events.ts +43 -13
- package/src/app.rs +7 -0
- package/src/assets.rs +2 -2
- package/src/bindings/ui.rs +2 -0
- package/src/bitmap.rs +39 -3
- package/src/bridge_callbacks.rs +27 -17
- package/src/controls/button.rs +1 -0
- package/src/controls/checkbox.rs +1 -0
- package/src/controls/combobox.rs +5 -3
- package/src/controls/dropdown.rs +3 -1
- package/src/controls/internal/pressable_labeled_control.rs +1 -0
- package/src/controls/internal/text_input_core.rs +7 -3
- package/src/controls/radio_button.rs +1 -0
- package/src/controls/slider.rs +1 -0
- package/src/controls/switch.rs +1 -0
- package/src/controls/tests.rs +19 -7
- package/src/debug.rs +8 -5
- package/src/drag_drop.rs +1 -3
- package/src/drawing.rs +55 -1
- package/src/event.rs +39 -27
- package/src/fetch.rs +21 -7
- package/src/ffi.rs +3 -1
- package/src/file.rs +88 -59
- package/src/host_events.rs +41 -0
- package/src/lib.rs +52 -9
- package/src/node/core.rs +7 -21
- package/src/node/helpers.rs +1 -1
- package/src/node/mod.rs +1 -1
- package/src/node/scroll_bar.rs +101 -0
- package/src/node/scroll_box.rs +7 -0
- package/src/node/scroll_view.rs +20 -14
- package/src/node/text_node.rs +4 -0
- package/src/node/virtual_list.rs +120 -33
- package/src/platform.rs +3 -0
- package/src/popup_presenter.rs +1 -0
- package/src/text.rs +41 -4
- package/src/theme.rs +2 -2
- package/src/timers.rs +1 -1
- package/src/typography.rs +15 -15
- package/src/worker.rs +25 -9
- package/src/worker_runtime.rs +61 -17
package/src/node/virtual_list.rs
CHANGED
|
@@ -4,6 +4,7 @@ use crate::controls::selection_area;
|
|
|
4
4
|
use crate::controls::SelectionArea;
|
|
5
5
|
use crate::frame_scheduler::mark_needs_commit;
|
|
6
6
|
use crate::signal::Subscription;
|
|
7
|
+
use std::any::Any;
|
|
7
8
|
use std::cell::{Cell, RefCell};
|
|
8
9
|
use std::rc::Rc;
|
|
9
10
|
|
|
@@ -11,9 +12,9 @@ const FULL_SIZE: f32 = 100.0;
|
|
|
11
12
|
const DEFAULT_MAX_VISIBLE_ITEMS: i32 = 20;
|
|
12
13
|
const POOL_OVERSCAN_ITEMS: i32 = 2;
|
|
13
14
|
const MISSING_BIND_ITEM_MESSAGE: &str =
|
|
14
|
-
"VirtualList: item renderer not configured. Call .
|
|
15
|
+
"VirtualList: item renderer not configured. Call .on_bind_item() after construction.";
|
|
15
16
|
|
|
16
|
-
type VirtualListBinder = Rc<dyn Fn(
|
|
17
|
+
type VirtualListBinder = Rc<dyn Fn(usize, i32)>;
|
|
17
18
|
|
|
18
19
|
struct VirtualListInner {
|
|
19
20
|
root: FlexBox,
|
|
@@ -27,18 +28,28 @@ struct VirtualListInner {
|
|
|
27
28
|
pool_size: i32,
|
|
28
29
|
pool_rows: Vec<SelectionArea>,
|
|
29
30
|
pool_containers: Vec<FlexBox>,
|
|
31
|
+
row_state_owner: RefCell<Option<Rc<dyn Any>>>,
|
|
30
32
|
pool_item_index_by_row: RefCell<Vec<i32>>,
|
|
31
33
|
subscriptions: RefCell<Vec<Subscription>>,
|
|
32
34
|
current_first_visible_index: Cell<i32>,
|
|
33
35
|
current_last_visible_index: Cell<i32>,
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
pub struct VirtualList {
|
|
38
|
+
pub struct VirtualList<T = FlexBox> {
|
|
38
39
|
inner: Rc<VirtualListInner>,
|
|
40
|
+
row_states: Rc<Vec<T>>,
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
impl VirtualList {
|
|
43
|
+
impl<T> Clone for VirtualList<T> {
|
|
44
|
+
fn clone(&self) -> Self {
|
|
45
|
+
Self {
|
|
46
|
+
inner: self.inner.clone(),
|
|
47
|
+
row_states: self.row_states.clone(),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
impl VirtualList<FlexBox> {
|
|
42
53
|
pub fn new(total_items: i32, item_height: f32) -> Self {
|
|
43
54
|
Self::with_max_visible(total_items, item_height, DEFAULT_MAX_VISIBLE_ITEMS)
|
|
44
55
|
}
|
|
@@ -105,13 +116,43 @@ impl VirtualList {
|
|
|
105
116
|
bottom_spacer,
|
|
106
117
|
pool_size,
|
|
107
118
|
pool_rows,
|
|
108
|
-
pool_containers,
|
|
119
|
+
pool_containers: pool_containers.clone(),
|
|
120
|
+
row_state_owner: RefCell::new(None),
|
|
109
121
|
pool_item_index_by_row: RefCell::new(pool_item_index_by_row),
|
|
110
122
|
subscriptions: RefCell::new(Vec::new()),
|
|
111
123
|
current_first_visible_index: Cell::new(-1),
|
|
112
124
|
current_last_visible_index: Cell::new(-1),
|
|
113
125
|
});
|
|
114
|
-
let
|
|
126
|
+
let row_states = Rc::new(pool_containers);
|
|
127
|
+
inner
|
|
128
|
+
.row_state_owner
|
|
129
|
+
.replace(Some(row_states.clone() as Rc<dyn Any>));
|
|
130
|
+
let list = Self { inner, row_states };
|
|
131
|
+
list.attach_listeners();
|
|
132
|
+
list
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
impl<T: 'static> VirtualList<T> {
|
|
137
|
+
pub fn item_template<U: 'static>(self, template: impl Fn(&FlexBox) -> U) -> VirtualList<U> {
|
|
138
|
+
let row_states = Rc::new(
|
|
139
|
+
self.inner
|
|
140
|
+
.pool_containers
|
|
141
|
+
.iter()
|
|
142
|
+
.map(template)
|
|
143
|
+
.collect::<Vec<_>>(),
|
|
144
|
+
);
|
|
145
|
+
self.inner
|
|
146
|
+
.row_state_owner
|
|
147
|
+
.replace(Some(row_states.clone() as Rc<dyn Any>));
|
|
148
|
+
*self.inner.bind_item.borrow_mut() = Rc::new(|_, _| panic!("{MISSING_BIND_ITEM_MESSAGE}"));
|
|
149
|
+
self.inner.current_first_visible_index.set(-1);
|
|
150
|
+
self.inner.current_last_visible_index.set(-1);
|
|
151
|
+
self.inner.subscriptions.borrow_mut().clear();
|
|
152
|
+
let list = VirtualList {
|
|
153
|
+
inner: self.inner,
|
|
154
|
+
row_states,
|
|
155
|
+
};
|
|
115
156
|
list.attach_listeners();
|
|
116
157
|
list
|
|
117
158
|
}
|
|
@@ -181,19 +222,17 @@ impl VirtualList {
|
|
|
181
222
|
self
|
|
182
223
|
}
|
|
183
224
|
|
|
184
|
-
pub fn on_bind_item(&self, renderer: impl Fn(&
|
|
185
|
-
|
|
225
|
+
pub fn on_bind_item(&self, renderer: impl Fn(&T, i32) + 'static) -> &Self {
|
|
226
|
+
let row_states = self.row_states.clone();
|
|
227
|
+
*self.inner.bind_item.borrow_mut() = Rc::new(move |pool_index, item_index| {
|
|
228
|
+
renderer(&row_states[pool_index], item_index);
|
|
229
|
+
});
|
|
186
230
|
self.inner.current_first_visible_index.set(-1);
|
|
187
231
|
self.inner.current_last_visible_index.set(-1);
|
|
188
232
|
self.rebuild_visible_range(true);
|
|
189
233
|
self
|
|
190
234
|
}
|
|
191
235
|
|
|
192
|
-
#[allow(non_snake_case)]
|
|
193
|
-
pub fn onBindItem(&self, renderer: impl Fn(&FlexBox, i32) + 'static) -> &Self {
|
|
194
|
-
self.on_bind_item(renderer)
|
|
195
|
-
}
|
|
196
|
-
|
|
197
236
|
pub fn update_item_count(&self, next: i32) {
|
|
198
237
|
self.inner.total_items.set(next.max(0));
|
|
199
238
|
self.inner
|
|
@@ -217,27 +256,33 @@ impl VirtualList {
|
|
|
217
256
|
|
|
218
257
|
fn attach_listeners(&self) {
|
|
219
258
|
let weak = Rc::downgrade(&self.inner);
|
|
259
|
+
let weak_row_states = Rc::downgrade(&self.row_states);
|
|
220
260
|
self.inner
|
|
221
261
|
.subscriptions
|
|
222
262
|
.borrow_mut()
|
|
223
263
|
.push(self.inner.scroll_state.subscribe_offset_y(move || {
|
|
224
|
-
if let Some(inner) = weak.upgrade()
|
|
225
|
-
|
|
264
|
+
if let (Some(inner), Some(row_states)) = (weak.upgrade(), weak_row_states.upgrade())
|
|
265
|
+
{
|
|
266
|
+
VirtualList { inner, row_states }.handle_scroll_offset_changed();
|
|
226
267
|
}
|
|
227
268
|
}));
|
|
228
269
|
let weak = Rc::downgrade(&self.inner);
|
|
270
|
+
let weak_row_states = Rc::downgrade(&self.row_states);
|
|
229
271
|
self.inner.subscriptions.borrow_mut().push(
|
|
230
272
|
self.inner.scroll_state.subscribe_viewport_height(move || {
|
|
231
|
-
if let Some(inner) = weak.upgrade()
|
|
232
|
-
|
|
273
|
+
if let (Some(inner), Some(row_states)) = (weak.upgrade(), weak_row_states.upgrade())
|
|
274
|
+
{
|
|
275
|
+
VirtualList { inner, row_states }.handle_metrics_changed();
|
|
233
276
|
}
|
|
234
277
|
}),
|
|
235
278
|
);
|
|
236
279
|
let weak = Rc::downgrade(&self.inner);
|
|
280
|
+
let weak_row_states = Rc::downgrade(&self.row_states);
|
|
237
281
|
self.inner.subscriptions.borrow_mut().push(
|
|
238
282
|
self.inner.scroll_state.subscribe_content_height(move || {
|
|
239
|
-
if let Some(inner) = weak.upgrade()
|
|
240
|
-
|
|
283
|
+
if let (Some(inner), Some(row_states)) = (weak.upgrade(), weak_row_states.upgrade())
|
|
284
|
+
{
|
|
285
|
+
VirtualList { inner, row_states }.handle_metrics_changed();
|
|
241
286
|
}
|
|
242
287
|
}),
|
|
243
288
|
);
|
|
@@ -325,8 +370,12 @@ impl VirtualList {
|
|
|
325
370
|
let previous_item_index_by_row = self.inner.pool_item_index_by_row.borrow().clone();
|
|
326
371
|
let visible_items = (last_visible_index - first_visible_index) as usize;
|
|
327
372
|
|
|
328
|
-
for pool_index in
|
|
329
|
-
|
|
373
|
+
for (pool_index, previous_item_index) in previous_item_index_by_row
|
|
374
|
+
.iter()
|
|
375
|
+
.copied()
|
|
376
|
+
.enumerate()
|
|
377
|
+
.take(self.inner.pool_size as usize)
|
|
378
|
+
{
|
|
330
379
|
if previous_item_index != -1
|
|
331
380
|
&& (previous_item_index < first_visible_index
|
|
332
381
|
|| previous_item_index >= last_visible_index)
|
|
@@ -340,8 +389,7 @@ impl VirtualList {
|
|
|
340
389
|
if pool_index < visible_items {
|
|
341
390
|
let next_item_index = first_visible_index + pool_index as i32;
|
|
342
391
|
row_area.height(self.inner.item_height, Unit::Pixel);
|
|
343
|
-
|
|
344
|
-
self.render_item(container, next_item_index);
|
|
392
|
+
self.render_item(pool_index, next_item_index);
|
|
345
393
|
self.inner.pool_item_index_by_row.borrow_mut()[pool_index] = next_item_index;
|
|
346
394
|
} else {
|
|
347
395
|
self.hide_pool_item(pool_index);
|
|
@@ -450,9 +498,9 @@ impl VirtualList {
|
|
|
450
498
|
mark_needs_commit();
|
|
451
499
|
}
|
|
452
500
|
|
|
453
|
-
fn render_item(&self,
|
|
501
|
+
fn render_item(&self, pool_index: usize, index: i32) {
|
|
454
502
|
let binder = self.inner.bind_item.borrow().clone();
|
|
455
|
-
binder(
|
|
503
|
+
binder(pool_index, index);
|
|
456
504
|
}
|
|
457
505
|
|
|
458
506
|
fn max_offset_for_current_viewport(&self) -> f32 {
|
|
@@ -478,7 +526,7 @@ impl VirtualList {
|
|
|
478
526
|
}
|
|
479
527
|
}
|
|
480
528
|
|
|
481
|
-
impl Node for VirtualList {
|
|
529
|
+
impl<T: 'static> Node for VirtualList<T> {
|
|
482
530
|
fn retained_node_ref(&self) -> NodeRef {
|
|
483
531
|
let list = self.clone();
|
|
484
532
|
self.inner
|
|
@@ -507,22 +555,24 @@ impl Node for VirtualList {
|
|
|
507
555
|
}
|
|
508
556
|
}
|
|
509
557
|
|
|
510
|
-
impl HasFlexBoxRoot for VirtualList {
|
|
558
|
+
impl<T: 'static> HasFlexBoxRoot for VirtualList<T> {
|
|
511
559
|
fn flex_box_root(&self) -> &FlexBox {
|
|
512
560
|
&self.inner.root
|
|
513
561
|
}
|
|
514
562
|
}
|
|
515
563
|
|
|
516
|
-
impl ThemeBindable for VirtualList {
|
|
564
|
+
impl<T: 'static> ThemeBindable for VirtualList<T> {
|
|
517
565
|
fn theme_binding_node(&self) -> NodeRef {
|
|
518
566
|
self.inner.root.retained_node_ref()
|
|
519
567
|
}
|
|
520
568
|
|
|
521
569
|
fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
|
|
522
570
|
let weak = Rc::downgrade(&self.inner);
|
|
571
|
+
let weak_row_states = Rc::downgrade(&self.row_states);
|
|
523
572
|
Box::new(move || {
|
|
524
573
|
Some(VirtualList {
|
|
525
574
|
inner: weak.upgrade()?,
|
|
575
|
+
row_states: weak_row_states.upgrade()?,
|
|
526
576
|
})
|
|
527
577
|
})
|
|
528
578
|
}
|
|
@@ -583,7 +633,7 @@ mod tests {
|
|
|
583
633
|
let list = VirtualList::new(10_000, 20.0);
|
|
584
634
|
let captured_indices = rendered_indices.clone();
|
|
585
635
|
let captured_labels = labels.clone();
|
|
586
|
-
list.
|
|
636
|
+
list.on_bind_item(move |container, index| {
|
|
587
637
|
tracked_bind_virtual_list_item(&captured_indices, &captured_labels, container, index);
|
|
588
638
|
});
|
|
589
639
|
list.width(180.0, Unit::Pixel);
|
|
@@ -608,7 +658,7 @@ mod tests {
|
|
|
608
658
|
let list = VirtualList::new(10_000, 20.0);
|
|
609
659
|
let captured_indices = rendered_indices.clone();
|
|
610
660
|
let captured_labels = labels.clone();
|
|
611
|
-
list.
|
|
661
|
+
list.on_bind_item(move |container, index| {
|
|
612
662
|
tracked_bind_virtual_list_item(&captured_indices, &captured_labels, container, index);
|
|
613
663
|
});
|
|
614
664
|
list.width(180.0, Unit::Pixel);
|
|
@@ -653,7 +703,7 @@ mod tests {
|
|
|
653
703
|
let labels = Rc::new(RefCell::new(HashMap::new()));
|
|
654
704
|
let list = VirtualList::new(10_000, 24.0);
|
|
655
705
|
let captured_labels = labels.clone();
|
|
656
|
-
list.
|
|
706
|
+
list.on_bind_item(move |container, index| {
|
|
657
707
|
static_bind_virtual_list_item(&captured_labels, container, index);
|
|
658
708
|
});
|
|
659
709
|
list.height(120.0, Unit::Pixel);
|
|
@@ -676,7 +726,7 @@ mod tests {
|
|
|
676
726
|
let list = VirtualList::new(10_000, 20.0);
|
|
677
727
|
let captured_indices = rendered_indices.clone();
|
|
678
728
|
let captured_labels = labels.clone();
|
|
679
|
-
list.
|
|
729
|
+
list.on_bind_item(move |container, index| {
|
|
680
730
|
tracked_bind_virtual_list_item(&captured_indices, &captured_labels, container, index);
|
|
681
731
|
});
|
|
682
732
|
list.width(180.0, Unit::Pixel);
|
|
@@ -690,4 +740,41 @@ mod tests {
|
|
|
690
740
|
assert_eq!(rendered[rendered.len() - 1], 5);
|
|
691
741
|
list.dispose();
|
|
692
742
|
}
|
|
743
|
+
|
|
744
|
+
#[test]
|
|
745
|
+
fn typed_item_template_creates_one_retained_state_per_pool_row_and_rebinds_it() {
|
|
746
|
+
#[derive(Clone)]
|
|
747
|
+
struct Row {
|
|
748
|
+
label: TextNode,
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
ffi::test::reset();
|
|
752
|
+
let template_count = Rc::new(Cell::new(0));
|
|
753
|
+
let captured_template_count = template_count.clone();
|
|
754
|
+
let list = VirtualList::with_max_visible(10_000, 20.0, 4).item_template(move |container| {
|
|
755
|
+
captured_template_count.set(captured_template_count.get() + 1);
|
|
756
|
+
let label = text("");
|
|
757
|
+
container.child(&label);
|
|
758
|
+
Row { label }
|
|
759
|
+
});
|
|
760
|
+
let bound_indices = Rc::new(RefCell::new(Vec::new()));
|
|
761
|
+
let captured_indices = bound_indices.clone();
|
|
762
|
+
list.on_bind_item(move |row, index| {
|
|
763
|
+
captured_indices.borrow_mut().push(index);
|
|
764
|
+
row.label.text(format!("typed item {index}"));
|
|
765
|
+
});
|
|
766
|
+
list.height(60.0, Unit::Pixel);
|
|
767
|
+
Application::mount(list.clone());
|
|
768
|
+
|
|
769
|
+
assert_eq!(template_count.get(), 6);
|
|
770
|
+
bound_indices.borrow_mut().clear();
|
|
771
|
+
ffi::test::reset();
|
|
772
|
+
list.scroll_state().set_offset_y(40.0);
|
|
773
|
+
|
|
774
|
+
assert_eq!(&*bound_indices.borrow(), &[2, 3, 4, 5]);
|
|
775
|
+
assert!(!ffi::test::take_calls()
|
|
776
|
+
.iter()
|
|
777
|
+
.any(|call| matches!(call, Call::CreateNode { .. } | Call::DeleteNode { .. })));
|
|
778
|
+
Application::unmount();
|
|
779
|
+
}
|
|
693
780
|
}
|
package/src/platform.rs
CHANGED
|
@@ -260,6 +260,9 @@ pub fn show_open_folder_dialog(
|
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
#[cfg(feature = "native-runtime")]
|
|
263
|
+
/// # Safety
|
|
264
|
+
/// `payload` must reference at least `payload_length` readable bytes when
|
|
265
|
+
/// `payload_length` is non-zero.
|
|
263
266
|
#[no_mangle]
|
|
264
267
|
pub unsafe extern "C" fn __fui_complete_native_file_dialog(
|
|
265
268
|
request_id: u64,
|
package/src/popup_presenter.rs
CHANGED
package/src/text.rs
CHANGED
|
@@ -255,6 +255,10 @@ impl Node for RichText {
|
|
|
255
255
|
fn build_self(&self) {
|
|
256
256
|
self.node.build_self();
|
|
257
257
|
}
|
|
258
|
+
|
|
259
|
+
fn required_font_ids_for_preparation(&self) -> Vec<u32> {
|
|
260
|
+
self.node.required_font_ids()
|
|
261
|
+
}
|
|
258
262
|
}
|
|
259
263
|
|
|
260
264
|
fn compile_rich_text_state(state: &RichTextState) -> CompiledRichText {
|
|
@@ -781,6 +785,8 @@ impl DynamicTextLayout {
|
|
|
781
785
|
layout.set_text(value);
|
|
782
786
|
if was_ready {
|
|
783
787
|
layout.prepare_or_wait();
|
|
788
|
+
} else {
|
|
789
|
+
layout.schedule_ready();
|
|
784
790
|
}
|
|
785
791
|
true
|
|
786
792
|
}
|
|
@@ -853,12 +859,15 @@ impl DynamicTextLayout {
|
|
|
853
859
|
value.chars().all(|ch| charset.contains(ch))
|
|
854
860
|
}
|
|
855
861
|
|
|
856
|
-
fn include_in_charset(state: &mut DynamicTextLayoutState, value: &str) {
|
|
862
|
+
fn include_in_charset(state: &mut DynamicTextLayoutState, value: &str) -> bool {
|
|
863
|
+
let mut changed = false;
|
|
857
864
|
for ch in value.chars() {
|
|
858
865
|
if !state.charset.contains(ch) {
|
|
859
866
|
state.charset.push(ch);
|
|
867
|
+
changed = true;
|
|
860
868
|
}
|
|
861
869
|
}
|
|
870
|
+
changed
|
|
862
871
|
}
|
|
863
872
|
|
|
864
873
|
fn refresh_numeric_text(&self) {
|
|
@@ -880,12 +889,14 @@ impl DynamicTextLayout {
|
|
|
880
889
|
let mut state = self.inner.borrow_mut();
|
|
881
890
|
let prefix = state.numeric_prefix.clone();
|
|
882
891
|
let suffix = state.numeric_suffix.clone();
|
|
883
|
-
Self::include_in_charset(&mut state, &prefix)
|
|
884
|
-
|
|
892
|
+
let charset_changed = Self::include_in_charset(&mut state, &prefix)
|
|
893
|
+
| Self::include_in_charset(&mut state, &suffix);
|
|
885
894
|
let charset = state.charset.clone();
|
|
886
895
|
let layout = state.layout.clone();
|
|
887
896
|
drop(state);
|
|
888
|
-
|
|
897
|
+
if charset_changed {
|
|
898
|
+
layout.set_dynamic_charset_internal(charset);
|
|
899
|
+
}
|
|
889
900
|
format!("{prefix}{}{suffix}", self.format_numeric_value(value))
|
|
890
901
|
}
|
|
891
902
|
|
|
@@ -1077,4 +1088,30 @@ mod tests {
|
|
|
1077
1088
|
.iter()
|
|
1078
1089
|
.any(|call| matches!(call, Call::SetDynamicTextCharset { charset, .. } if charset == "0123456789")));
|
|
1079
1090
|
}
|
|
1091
|
+
|
|
1092
|
+
#[test]
|
|
1093
|
+
fn numeric_dynamic_text_layout_stays_ready_across_value_updates() {
|
|
1094
|
+
ffi::test::reset();
|
|
1095
|
+
frame_scheduler::reset_commit_state();
|
|
1096
|
+
assets::test_reset();
|
|
1097
|
+
|
|
1098
|
+
let layout = DynamicTextLayout::numeric();
|
|
1099
|
+
layout
|
|
1100
|
+
.precision(0)
|
|
1101
|
+
.font_stack(crate::typography::FontStack::from_id(1), 16.0)
|
|
1102
|
+
.width(120.0, Unit::Pixel)
|
|
1103
|
+
.height(24.0, Unit::Pixel)
|
|
1104
|
+
.on_ready(|_| {});
|
|
1105
|
+
frame_scheduler::fire_loaded_callbacks();
|
|
1106
|
+
assert!(layout.is_ready());
|
|
1107
|
+
ffi::test::take_calls();
|
|
1108
|
+
|
|
1109
|
+
assert!(layout.set_value(42.0));
|
|
1110
|
+
assert!(layout.is_ready());
|
|
1111
|
+
assert_eq!(layout.current_text(), "42");
|
|
1112
|
+
let calls = ffi::test::take_calls();
|
|
1113
|
+
assert!(calls
|
|
1114
|
+
.iter()
|
|
1115
|
+
.any(|call| matches!(call, Call::PrepareNode { .. })));
|
|
1116
|
+
}
|
|
1080
1117
|
}
|
package/src/theme.rs
CHANGED
|
@@ -517,12 +517,12 @@ trait Pipe: Sized {
|
|
|
517
517
|
|
|
518
518
|
impl<T> Pipe for T {}
|
|
519
519
|
|
|
520
|
-
#[no_mangle]
|
|
520
|
+
#[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
|
|
521
521
|
pub extern "C" fn __fui_on_system_dark_mode_changed(is_dark: bool) {
|
|
522
522
|
handle_system_dark_mode_changed(is_dark);
|
|
523
523
|
}
|
|
524
524
|
|
|
525
|
-
#[no_mangle]
|
|
525
|
+
#[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
|
|
526
526
|
pub extern "C" fn __fui_on_system_accent_color_changed(color: u32) {
|
|
527
527
|
handle_system_accent_color_changed(color);
|
|
528
528
|
}
|
package/src/timers.rs
CHANGED
|
@@ -58,7 +58,7 @@ pub fn cancel_all_timers() {
|
|
|
58
58
|
ACTIVE_TIMERS.with(|timers| timers.borrow_mut().clear());
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
#[no_mangle]
|
|
61
|
+
#[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
|
|
62
62
|
pub extern "C" fn __fui_on_timer(timer_id: u32) {
|
|
63
63
|
let callback = ACTIVE_TIMERS.with(|timers| timers.borrow_mut().remove(&timer_id));
|
|
64
64
|
if let Some(callback) = callback {
|
package/src/typography.rs
CHANGED
|
@@ -15,32 +15,22 @@ thread_local! {
|
|
|
15
15
|
static REGISTERED_FONT_FALLBACKS: RefCell<Vec<(u32, u32)>> = const { RefCell::new(Vec::new()) };
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
18
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
|
19
19
|
pub enum FontStyle {
|
|
20
|
+
#[default]
|
|
20
21
|
Normal = 0,
|
|
21
22
|
Italic = 1,
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
fn default() -> Self {
|
|
26
|
-
Self::Normal
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
25
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
|
31
26
|
pub enum FontWeight {
|
|
27
|
+
#[default]
|
|
32
28
|
Regular = 400,
|
|
33
29
|
Medium = 500,
|
|
34
30
|
Semibold = 600,
|
|
35
31
|
Bold = 700,
|
|
36
32
|
}
|
|
37
33
|
|
|
38
|
-
impl Default for FontWeight {
|
|
39
|
-
fn default() -> Self {
|
|
40
|
-
Self::Regular
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
34
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
45
35
|
pub struct FontFaceLoadedEventArgs {
|
|
46
36
|
pub font: FontFace,
|
|
@@ -158,7 +148,7 @@ impl FontFace {
|
|
|
158
148
|
}
|
|
159
149
|
|
|
160
150
|
pub(crate) fn is_font_loaded(font_id: u32) -> bool {
|
|
161
|
-
font_id == 0 || assets::is_font_loaded(font_id)
|
|
151
|
+
font_id == 0 || (1..=6).contains(&font_id) || assets::is_font_loaded(font_id)
|
|
162
152
|
}
|
|
163
153
|
|
|
164
154
|
pub fn on_loaded(&self, callback: impl Fn(FontFaceLoadedEventArgs) + 'static) -> Self {
|
|
@@ -308,6 +298,7 @@ pub struct FontFamily {
|
|
|
308
298
|
}
|
|
309
299
|
|
|
310
300
|
impl FontFamily {
|
|
301
|
+
#[allow(clippy::too_many_arguments)]
|
|
311
302
|
pub fn new(
|
|
312
303
|
regular_stack: FontStack,
|
|
313
304
|
bold_stack: Option<FontStack>,
|
|
@@ -331,6 +322,7 @@ impl FontFamily {
|
|
|
331
322
|
}
|
|
332
323
|
|
|
333
324
|
#[cfg(test)]
|
|
325
|
+
#[allow(clippy::too_many_arguments)]
|
|
334
326
|
pub(crate) fn from_ids(
|
|
335
327
|
regular: u32,
|
|
336
328
|
bold: u32,
|
|
@@ -502,6 +494,14 @@ mod tests {
|
|
|
502
494
|
assert_eq!(fired.get(), 1);
|
|
503
495
|
}
|
|
504
496
|
|
|
497
|
+
#[test]
|
|
498
|
+
fn built_in_font_faces_are_preloaded_like_fui_as() {
|
|
499
|
+
assets::test_reset();
|
|
500
|
+
for font_id in 1..=6 {
|
|
501
|
+
assert!(FontFace::new(font_id).is_loaded());
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
505
|
#[test]
|
|
506
506
|
fn font_stack_reports_required_font_ids() {
|
|
507
507
|
let stack = FontStack::from_id(1)
|
package/src/worker.rs
CHANGED
|
@@ -108,7 +108,7 @@ impl Worker {
|
|
|
108
108
|
if already_started {
|
|
109
109
|
return self;
|
|
110
110
|
}
|
|
111
|
-
if input.
|
|
111
|
+
if input.len() > MAX_WORKER_START_INPUT_BYTES {
|
|
112
112
|
let (worker_id, callback) = {
|
|
113
113
|
let mut inner = self.inner.borrow_mut();
|
|
114
114
|
inner.started = true;
|
|
@@ -186,8 +186,14 @@ fn with_active_worker(worker_id: u32, callback: impl FnOnce(&mut WorkerInner)) {
|
|
|
186
186
|
callback(&mut inner);
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
#[no_mangle]
|
|
190
|
-
|
|
189
|
+
#[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
|
|
190
|
+
/// # Safety
|
|
191
|
+
/// `text_ptr` must be null for an empty message or point to `text_len` readable bytes.
|
|
192
|
+
pub unsafe extern "C" fn __fui_on_worker_progress(
|
|
193
|
+
worker_id: u32,
|
|
194
|
+
text_ptr: *const u8,
|
|
195
|
+
text_len: u32,
|
|
196
|
+
) {
|
|
191
197
|
let message = if text_ptr.is_null() || text_len == 0 {
|
|
192
198
|
String::new()
|
|
193
199
|
} else {
|
|
@@ -204,8 +210,14 @@ pub extern "C" fn __fui_on_worker_progress(worker_id: u32, text_ptr: *const u8,
|
|
|
204
210
|
});
|
|
205
211
|
}
|
|
206
212
|
|
|
207
|
-
#[no_mangle]
|
|
208
|
-
|
|
213
|
+
#[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
|
|
214
|
+
/// # Safety
|
|
215
|
+
/// `text_ptr` must be null for an empty result or point to `text_len` readable bytes.
|
|
216
|
+
pub unsafe extern "C" fn __fui_on_worker_complete(
|
|
217
|
+
worker_id: u32,
|
|
218
|
+
text_ptr: *const u8,
|
|
219
|
+
text_len: u32,
|
|
220
|
+
) {
|
|
209
221
|
let result = if text_ptr.is_null() || text_len == 0 {
|
|
210
222
|
String::new()
|
|
211
223
|
} else {
|
|
@@ -228,8 +240,10 @@ pub extern "C" fn __fui_on_worker_complete(worker_id: u32, text_ptr: *const u8,
|
|
|
228
240
|
}
|
|
229
241
|
}
|
|
230
242
|
|
|
231
|
-
#[no_mangle]
|
|
232
|
-
|
|
243
|
+
#[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
|
|
244
|
+
/// # Safety
|
|
245
|
+
/// `text_ptr` must be null for an empty message or point to `text_len` readable bytes.
|
|
246
|
+
pub unsafe extern "C" fn __fui_on_worker_error(worker_id: u32, text_ptr: *const u8, text_len: u32) {
|
|
233
247
|
let message = if text_ptr.is_null() || text_len == 0 {
|
|
234
248
|
String::new()
|
|
235
249
|
} else {
|
|
@@ -300,8 +314,10 @@ mod tests {
|
|
|
300
314
|
result_clone.replace(event.result);
|
|
301
315
|
})
|
|
302
316
|
.start("hello");
|
|
303
|
-
|
|
304
|
-
|
|
317
|
+
unsafe {
|
|
318
|
+
super::__fui_on_worker_progress(1, b"25%".as_ptr(), 3);
|
|
319
|
+
super::__fui_on_worker_complete(1, b"done".as_ptr(), 4);
|
|
320
|
+
}
|
|
305
321
|
assert_eq!(&*progress.borrow(), "25%");
|
|
306
322
|
assert_eq!(&*result.borrow(), "done");
|
|
307
323
|
}
|