@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
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
use crate::animation::AnimationTiming;
|
|
2
|
+
|
|
3
|
+
#[derive(Clone, Debug, Default, PartialEq)]
|
|
4
|
+
pub struct NodeTransitions {
|
|
5
|
+
opacity_timing: Option<AnimationTiming>,
|
|
6
|
+
background_color_timing: Option<AnimationTiming>,
|
|
7
|
+
scroll_offset_timing: Option<AnimationTiming>,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
impl NodeTransitions {
|
|
11
|
+
pub fn new() -> Self {
|
|
12
|
+
Self::default()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
pub fn opacity(mut self, timing: AnimationTiming) -> Self {
|
|
16
|
+
self.opacity_timing = Some(timing);
|
|
17
|
+
self
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
pub fn bg_color(mut self, timing: AnimationTiming) -> Self {
|
|
21
|
+
self.background_color_timing = Some(timing);
|
|
22
|
+
self
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
pub fn scroll_offset(mut self, timing: AnimationTiming) -> Self {
|
|
26
|
+
self.scroll_offset_timing = Some(timing);
|
|
27
|
+
self
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pub(crate) fn opacity_timing(&self) -> Option<AnimationTiming> {
|
|
31
|
+
self.opacity_timing
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
pub(crate) fn background_color_timing(&self) -> Option<AnimationTiming> {
|
|
35
|
+
self.background_color_timing
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pub(crate) fn scroll_offset_timing(&self) -> Option<AnimationTiming> {
|
|
39
|
+
self.scroll_offset_timing
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
use crate::assets;
|
|
2
|
+
use crate::bindings::ui;
|
|
3
|
+
use crate::logger::warn;
|
|
4
|
+
use std::cell::RefCell;
|
|
5
|
+
use std::rc::Rc;
|
|
6
|
+
|
|
7
|
+
const STYLE_MISMATCH_PENALTY: i32 = 1000;
|
|
8
|
+
const MAX_FONT_SCORE: i32 = i32::MAX;
|
|
9
|
+
const FIRST_DYNAMIC_FONT_ID: u32 = 1024;
|
|
10
|
+
|
|
11
|
+
thread_local! {
|
|
12
|
+
static NEXT_DYNAMIC_FONT_ID: RefCell<u32> = const { RefCell::new(FIRST_DYNAMIC_FONT_ID) };
|
|
13
|
+
static FONT_LOADED_CALLBACKS: RefCell<Vec<FontLoadedRegistration>> = const { RefCell::new(Vec::new()) };
|
|
14
|
+
static FONT_READY_CALLBACKS: RefCell<Vec<FontReadyRegistration>> = const { RefCell::new(Vec::new()) };
|
|
15
|
+
static REGISTERED_FONT_FALLBACKS: RefCell<Vec<(u32, u32)>> = const { RefCell::new(Vec::new()) };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
19
|
+
pub enum FontStyle {
|
|
20
|
+
Normal = 0,
|
|
21
|
+
Italic = 1,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
impl Default for FontStyle {
|
|
25
|
+
fn default() -> Self {
|
|
26
|
+
Self::Normal
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
31
|
+
pub enum FontWeight {
|
|
32
|
+
Regular = 400,
|
|
33
|
+
Medium = 500,
|
|
34
|
+
Semibold = 600,
|
|
35
|
+
Bold = 700,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
impl Default for FontWeight {
|
|
39
|
+
fn default() -> Self {
|
|
40
|
+
Self::Regular
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
45
|
+
pub struct FontFaceLoadedEventArgs {
|
|
46
|
+
pub font: FontFace,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
50
|
+
pub struct FontsLoadedEventArgs;
|
|
51
|
+
|
|
52
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
53
|
+
pub struct FontStackLoadedEventArgs {
|
|
54
|
+
pub stack: FontStack,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
struct FontLoadedRegistration {
|
|
58
|
+
font_id: u32,
|
|
59
|
+
callback: Rc<dyn Fn(FontFaceLoadedEventArgs)>,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
struct FontReadyRegistration {
|
|
63
|
+
font_ids: Vec<u32>,
|
|
64
|
+
callback: Rc<dyn Fn()>,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fn abs_i32(value: i32) -> i32 {
|
|
68
|
+
if value < 0 {
|
|
69
|
+
-value
|
|
70
|
+
} else {
|
|
71
|
+
value
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
fn allocate_dynamic_font_id() -> u32 {
|
|
76
|
+
NEXT_DYNAMIC_FONT_ID.with(|slot| {
|
|
77
|
+
let mut next = slot.borrow_mut();
|
|
78
|
+
let allocated = *next;
|
|
79
|
+
*next += 1;
|
|
80
|
+
allocated
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn push_unique_font_id(font_ids: &mut Vec<u32>, font_id: u32) {
|
|
85
|
+
if font_id == 0 || font_ids.contains(&font_id) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
font_ids.push(font_id);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
fn normalize_font_ids(font_ids: &[u32]) -> Vec<u32> {
|
|
92
|
+
let mut unique = Vec::with_capacity(font_ids.len());
|
|
93
|
+
for font_id in font_ids {
|
|
94
|
+
push_unique_font_id(&mut unique, *font_id);
|
|
95
|
+
}
|
|
96
|
+
unique
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fn register_font_fallback_once(font_id: u32, fallback_font_id: u32) {
|
|
100
|
+
if font_id == 0 || fallback_font_id == 0 {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
let already_registered = REGISTERED_FONT_FALLBACKS.with(|pairs| {
|
|
104
|
+
pairs
|
|
105
|
+
.borrow()
|
|
106
|
+
.iter()
|
|
107
|
+
.any(|(registered_font_id, registered_fallback_id)| {
|
|
108
|
+
*registered_font_id == font_id && *registered_fallback_id == fallback_font_id
|
|
109
|
+
})
|
|
110
|
+
});
|
|
111
|
+
if already_registered {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
ui::register_font_fallback(font_id, fallback_font_id);
|
|
115
|
+
REGISTERED_FONT_FALLBACKS.with(|pairs| {
|
|
116
|
+
pairs.borrow_mut().push((font_id, fallback_font_id));
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fn are_font_ids_loaded(font_ids: &[u32]) -> bool {
|
|
121
|
+
font_ids
|
|
122
|
+
.iter()
|
|
123
|
+
.all(|font_id| FontFace::is_font_loaded(*font_id))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
127
|
+
pub struct FontFace {
|
|
128
|
+
id: u32,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
impl FontFace {
|
|
132
|
+
pub(crate) fn new(id: u32) -> Self {
|
|
133
|
+
Self { id }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
pub fn load(url: &str) -> Self {
|
|
137
|
+
Self::load_with_id(url, allocate_dynamic_font_id())
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
pub(crate) fn load_with_id(url: &str, id: u32) -> Self {
|
|
141
|
+
Self::new(id).load_into(url)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
pub(crate) fn id(&self) -> u32 {
|
|
145
|
+
self.id
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
fn load_into(self, url: &str) -> Self {
|
|
149
|
+
if url.is_empty() {
|
|
150
|
+
warn("Typography", "FontFace.load() received an empty font URL.");
|
|
151
|
+
}
|
|
152
|
+
assets::load_font(self.id, url);
|
|
153
|
+
self
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
pub fn is_loaded(&self) -> bool {
|
|
157
|
+
Self::is_font_loaded(self.id)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
pub(crate) fn is_font_loaded(font_id: u32) -> bool {
|
|
161
|
+
font_id == 0 || assets::is_font_loaded(font_id)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
pub fn on_loaded(&self, callback: impl Fn(FontFaceLoadedEventArgs) + 'static) -> Self {
|
|
165
|
+
Self::when_loaded(self.id, callback);
|
|
166
|
+
self.clone()
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
pub(crate) fn when_loaded(font_id: u32, callback: impl Fn(FontFaceLoadedEventArgs) + 'static) {
|
|
170
|
+
if Self::is_font_loaded(font_id) {
|
|
171
|
+
callback(FontFaceLoadedEventArgs {
|
|
172
|
+
font: FontFace::new(font_id),
|
|
173
|
+
});
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
FONT_LOADED_CALLBACKS.with(|registrations| {
|
|
177
|
+
registrations.borrow_mut().push(FontLoadedRegistration {
|
|
178
|
+
font_id,
|
|
179
|
+
callback: Rc::new(callback),
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
pub(crate) fn when_fonts_loaded(
|
|
185
|
+
font_ids: &[u32],
|
|
186
|
+
callback: impl Fn(FontsLoadedEventArgs) + 'static,
|
|
187
|
+
) {
|
|
188
|
+
let unique_font_ids = normalize_font_ids(font_ids);
|
|
189
|
+
if are_font_ids_loaded(&unique_font_ids) {
|
|
190
|
+
callback(FontsLoadedEventArgs);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
FONT_READY_CALLBACKS.with(|registrations| {
|
|
194
|
+
registrations.borrow_mut().push(FontReadyRegistration {
|
|
195
|
+
font_ids: unique_font_ids,
|
|
196
|
+
callback: Rc::new(move || callback(FontsLoadedEventArgs)),
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
203
|
+
pub struct FontStack {
|
|
204
|
+
id: u32,
|
|
205
|
+
fallback_ids: Vec<u32>,
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
impl FontStack {
|
|
209
|
+
pub fn new(face: FontFace) -> Self {
|
|
210
|
+
Self {
|
|
211
|
+
id: face.id(),
|
|
212
|
+
fallback_ids: Vec::new(),
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
pub(crate) fn from_id(id: u32) -> Self {
|
|
217
|
+
Self::new(FontFace::new(id))
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
pub fn load(url: &str) -> Self {
|
|
221
|
+
Self::new(FontFace::load(url))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
pub(crate) fn id(&self) -> u32 {
|
|
225
|
+
self.id
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
pub fn fallback_face(mut self, face: FontFace) -> Self {
|
|
229
|
+
self = self.fallback_id(face.id());
|
|
230
|
+
self
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
pub fn fallback_stack(mut self, stack: FontStack) -> Self {
|
|
234
|
+
self = self.fallback_id(stack.id());
|
|
235
|
+
self
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
pub fn fallback_loaded(self, url: &str) -> Self {
|
|
239
|
+
self.fallback_loaded_with_id(url, allocate_dynamic_font_id())
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
pub(crate) fn fallback_loaded_with_id(mut self, url: &str, font_id: u32) -> Self {
|
|
243
|
+
if url.is_empty() {
|
|
244
|
+
warn(
|
|
245
|
+
"Typography",
|
|
246
|
+
"FontStack.fallback_loaded() received an empty font URL.",
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
assets::load_font(font_id, url);
|
|
250
|
+
self = self.fallback_id(font_id);
|
|
251
|
+
self
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
pub(crate) fn required_font_ids(&self) -> Vec<u32> {
|
|
255
|
+
let mut font_ids = Vec::with_capacity(1 + self.fallback_ids.len());
|
|
256
|
+
push_unique_font_id(&mut font_ids, self.id);
|
|
257
|
+
for fallback_id in &self.fallback_ids {
|
|
258
|
+
push_unique_font_id(&mut font_ids, *fallback_id);
|
|
259
|
+
}
|
|
260
|
+
font_ids
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
pub fn is_loaded(&self) -> bool {
|
|
264
|
+
are_font_ids_loaded(&self.required_font_ids())
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
pub fn on_loaded(&self, callback: impl Fn(FontStackLoadedEventArgs) + 'static) -> Self {
|
|
268
|
+
let stack = self.clone();
|
|
269
|
+
let required = self.required_font_ids();
|
|
270
|
+
FontFace::when_fonts_loaded(&required, move |_| {
|
|
271
|
+
callback(FontStackLoadedEventArgs {
|
|
272
|
+
stack: stack.clone(),
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
self.clone()
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
fn fallback_id(mut self, font_id: u32) -> Self {
|
|
279
|
+
if font_id == 0 || font_id == self.id {
|
|
280
|
+
warn(
|
|
281
|
+
"Typography",
|
|
282
|
+
&format!(
|
|
283
|
+
"FontStack.fallback() ignored font id {} for stack {}.",
|
|
284
|
+
font_id, self.id
|
|
285
|
+
),
|
|
286
|
+
);
|
|
287
|
+
return self;
|
|
288
|
+
}
|
|
289
|
+
if self.fallback_ids.contains(&font_id) {
|
|
290
|
+
return self;
|
|
291
|
+
}
|
|
292
|
+
register_font_fallback_once(self.id, font_id);
|
|
293
|
+
self.fallback_ids.push(font_id);
|
|
294
|
+
self
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
299
|
+
pub struct FontFamily {
|
|
300
|
+
pub regular_stack: FontStack,
|
|
301
|
+
pub bold_stack: Option<FontStack>,
|
|
302
|
+
pub italic_stack: Option<FontStack>,
|
|
303
|
+
pub bold_italic_stack: Option<FontStack>,
|
|
304
|
+
pub medium_stack: Option<FontStack>,
|
|
305
|
+
pub medium_italic_stack: Option<FontStack>,
|
|
306
|
+
pub semibold_stack: Option<FontStack>,
|
|
307
|
+
pub semibold_italic_stack: Option<FontStack>,
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
impl FontFamily {
|
|
311
|
+
pub fn new(
|
|
312
|
+
regular_stack: FontStack,
|
|
313
|
+
bold_stack: Option<FontStack>,
|
|
314
|
+
italic_stack: Option<FontStack>,
|
|
315
|
+
bold_italic_stack: Option<FontStack>,
|
|
316
|
+
medium_stack: Option<FontStack>,
|
|
317
|
+
medium_italic_stack: Option<FontStack>,
|
|
318
|
+
semibold_stack: Option<FontStack>,
|
|
319
|
+
semibold_italic_stack: Option<FontStack>,
|
|
320
|
+
) -> Self {
|
|
321
|
+
Self {
|
|
322
|
+
regular_stack,
|
|
323
|
+
bold_stack,
|
|
324
|
+
italic_stack,
|
|
325
|
+
bold_italic_stack,
|
|
326
|
+
medium_stack,
|
|
327
|
+
medium_italic_stack,
|
|
328
|
+
semibold_stack,
|
|
329
|
+
semibold_italic_stack,
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
#[cfg(test)]
|
|
334
|
+
pub(crate) fn from_ids(
|
|
335
|
+
regular: u32,
|
|
336
|
+
bold: u32,
|
|
337
|
+
italic: u32,
|
|
338
|
+
bold_italic: u32,
|
|
339
|
+
medium: u32,
|
|
340
|
+
medium_italic: u32,
|
|
341
|
+
semibold: u32,
|
|
342
|
+
semibold_italic: u32,
|
|
343
|
+
) -> Self {
|
|
344
|
+
Self::new(
|
|
345
|
+
FontStack::from_id(regular),
|
|
346
|
+
(bold != 0).then(|| FontStack::from_id(bold)),
|
|
347
|
+
(italic != 0).then(|| FontStack::from_id(italic)),
|
|
348
|
+
(bold_italic != 0).then(|| FontStack::from_id(bold_italic)),
|
|
349
|
+
(medium != 0).then(|| FontStack::from_id(medium)),
|
|
350
|
+
(medium_italic != 0).then(|| FontStack::from_id(medium_italic)),
|
|
351
|
+
(semibold != 0).then(|| FontStack::from_id(semibold)),
|
|
352
|
+
(semibold_italic != 0).then(|| FontStack::from_id(semibold_italic)),
|
|
353
|
+
)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
pub fn with_regular_stack(regular: FontStack) -> Self {
|
|
357
|
+
Self::new(regular, None, None, None, None, None, None, None)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
pub fn with_regular_face(regular: FontFace) -> Self {
|
|
361
|
+
Self::with_regular_stack(FontStack::new(regular))
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
pub fn regular_bold_stacks(regular: FontStack, bold: FontStack) -> Self {
|
|
365
|
+
Self::new(regular, Some(bold), None, None, None, None, None, None)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
pub(crate) fn italic_stack(mut self, stack: FontStack) -> Self {
|
|
369
|
+
self.italic_stack = Some(stack);
|
|
370
|
+
self
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
pub(crate) fn bold_italic_stack(mut self, stack: FontStack) -> Self {
|
|
374
|
+
self.bold_italic_stack = Some(stack);
|
|
375
|
+
self
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
pub(crate) fn resolve(&self, weight: FontWeight, style: FontStyle) -> u32 {
|
|
379
|
+
let target_weight = weight as i32;
|
|
380
|
+
let candidates = [
|
|
381
|
+
Some((&self.regular_stack, FontWeight::Regular, FontStyle::Normal)),
|
|
382
|
+
self.bold_stack
|
|
383
|
+
.as_ref()
|
|
384
|
+
.map(|stack| (stack, FontWeight::Bold, FontStyle::Normal)),
|
|
385
|
+
self.italic_stack
|
|
386
|
+
.as_ref()
|
|
387
|
+
.map(|stack| (stack, FontWeight::Regular, FontStyle::Italic)),
|
|
388
|
+
self.bold_italic_stack
|
|
389
|
+
.as_ref()
|
|
390
|
+
.map(|stack| (stack, FontWeight::Bold, FontStyle::Italic)),
|
|
391
|
+
self.medium_stack
|
|
392
|
+
.as_ref()
|
|
393
|
+
.map(|stack| (stack, FontWeight::Medium, FontStyle::Normal)),
|
|
394
|
+
self.medium_italic_stack
|
|
395
|
+
.as_ref()
|
|
396
|
+
.map(|stack| (stack, FontWeight::Medium, FontStyle::Italic)),
|
|
397
|
+
self.semibold_stack
|
|
398
|
+
.as_ref()
|
|
399
|
+
.map(|stack| (stack, FontWeight::Semibold, FontStyle::Normal)),
|
|
400
|
+
self.semibold_italic_stack
|
|
401
|
+
.as_ref()
|
|
402
|
+
.map(|stack| (stack, FontWeight::Semibold, FontStyle::Italic)),
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
let mut best_id = 0;
|
|
406
|
+
let mut best_score = MAX_FONT_SCORE;
|
|
407
|
+
for (stack, candidate_weight, candidate_style) in candidates.into_iter().flatten() {
|
|
408
|
+
let score = Self::score_candidate(
|
|
409
|
+
stack.id(),
|
|
410
|
+
candidate_weight,
|
|
411
|
+
candidate_style,
|
|
412
|
+
target_weight,
|
|
413
|
+
style,
|
|
414
|
+
);
|
|
415
|
+
if score < best_score {
|
|
416
|
+
best_score = score;
|
|
417
|
+
best_id = stack.id();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if best_id == 0 {
|
|
421
|
+
warn(
|
|
422
|
+
"Typography",
|
|
423
|
+
&format!(
|
|
424
|
+
"FontFamily.resolve() could not resolve a font face for weight {} and style {}; the text will use font id 0.",
|
|
425
|
+
weight as i32,
|
|
426
|
+
style as u32,
|
|
427
|
+
),
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
best_id
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
fn score_candidate(
|
|
434
|
+
font_id: u32,
|
|
435
|
+
weight: FontWeight,
|
|
436
|
+
style: FontStyle,
|
|
437
|
+
target_weight: i32,
|
|
438
|
+
target_style: FontStyle,
|
|
439
|
+
) -> i32 {
|
|
440
|
+
if font_id == 0 {
|
|
441
|
+
return MAX_FONT_SCORE;
|
|
442
|
+
}
|
|
443
|
+
let mut score = abs_i32(weight as i32 - target_weight);
|
|
444
|
+
if style != target_style {
|
|
445
|
+
score += STYLE_MISMATCH_PENALTY;
|
|
446
|
+
}
|
|
447
|
+
score
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
pub(crate) fn notify_font_loaded(font_id: u32) {
|
|
452
|
+
FONT_LOADED_CALLBACKS.with(|registrations| {
|
|
453
|
+
let callbacks: Vec<Rc<dyn Fn(FontFaceLoadedEventArgs)>> = registrations
|
|
454
|
+
.borrow()
|
|
455
|
+
.iter()
|
|
456
|
+
.filter(|registration| registration.font_id == font_id)
|
|
457
|
+
.map(|registration| registration.callback.clone())
|
|
458
|
+
.collect();
|
|
459
|
+
for callback in callbacks {
|
|
460
|
+
callback(FontFaceLoadedEventArgs {
|
|
461
|
+
font: FontFace::new(font_id),
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
FONT_READY_CALLBACKS.with(|registrations| {
|
|
467
|
+
let mut registrations = registrations.borrow_mut();
|
|
468
|
+
let mut ready_callbacks = Vec::new();
|
|
469
|
+
registrations.retain(|registration| {
|
|
470
|
+
if are_font_ids_loaded(®istration.font_ids) {
|
|
471
|
+
ready_callbacks.push(registration.callback.clone());
|
|
472
|
+
false
|
|
473
|
+
} else {
|
|
474
|
+
true
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
drop(registrations);
|
|
478
|
+
for callback in ready_callbacks {
|
|
479
|
+
callback();
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
#[cfg(test)]
|
|
485
|
+
mod tests {
|
|
486
|
+
use super::{notify_font_loaded, FontFace, FontFamily, FontStack, FontStyle, FontWeight};
|
|
487
|
+
use crate::assets;
|
|
488
|
+
use std::cell::Cell;
|
|
489
|
+
use std::rc::Rc;
|
|
490
|
+
|
|
491
|
+
#[test]
|
|
492
|
+
fn font_face_on_loaded_waits_for_bridge_callback() {
|
|
493
|
+
assets::test_reset();
|
|
494
|
+
let fired = Rc::new(Cell::new(0));
|
|
495
|
+
FontFace::new(1024).on_loaded({
|
|
496
|
+
let fired = fired.clone();
|
|
497
|
+
move |_| fired.set(fired.get() + 1)
|
|
498
|
+
});
|
|
499
|
+
assert_eq!(fired.get(), 0);
|
|
500
|
+
assets::on_font_loaded(1024);
|
|
501
|
+
notify_font_loaded(1024);
|
|
502
|
+
assert_eq!(fired.get(), 1);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
#[test]
|
|
506
|
+
fn font_stack_reports_required_font_ids() {
|
|
507
|
+
let stack = FontStack::from_id(1)
|
|
508
|
+
.fallback_face(FontFace::new(3))
|
|
509
|
+
.fallback_stack(FontStack::from_id(4));
|
|
510
|
+
assert_eq!(stack.required_font_ids(), vec![1, 3, 4]);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
#[test]
|
|
514
|
+
fn font_family_resolves_best_matching_face() {
|
|
515
|
+
let family = FontFamily::from_ids(1, 2, 5, 6, 9, 10, 11, 12);
|
|
516
|
+
assert_eq!(family.resolve(FontWeight::Bold, FontStyle::Italic), 6);
|
|
517
|
+
assert_eq!(family.resolve(FontWeight::Semibold, FontStyle::Normal), 11);
|
|
518
|
+
assert_eq!(family.resolve(FontWeight::Medium, FontStyle::Italic), 10);
|
|
519
|
+
}
|
|
520
|
+
}
|
package/src/viewport.rs
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
use crate::bindings::ui;
|
|
2
|
+
use crate::signal::{Callback, Signal, SubscriptionGuard};
|
|
3
|
+
use std::cell::RefCell;
|
|
4
|
+
use std::rc::Rc;
|
|
5
|
+
|
|
6
|
+
#[derive(Clone, Copy)]
|
|
7
|
+
enum ViewportAxis {
|
|
8
|
+
Width,
|
|
9
|
+
Height,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
#[derive(Clone, Copy)]
|
|
13
|
+
pub struct ViewportSignalHandle {
|
|
14
|
+
axis: ViewportAxis,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
thread_local! {
|
|
18
|
+
static VIEWPORT_WIDTH_SIGNAL: RefCell<Signal<f32>> = RefCell::new(Signal::new(ui::get_viewport_width()));
|
|
19
|
+
static VIEWPORT_HEIGHT_SIGNAL: RefCell<Signal<f32>> = RefCell::new(Signal::new(ui::get_viewport_height()));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
fn update_signal(signal: &RefCell<Signal<f32>>, next: f32) {
|
|
23
|
+
let callbacks = signal.borrow_mut().set(next);
|
|
24
|
+
if let Some(callbacks) = callbacks {
|
|
25
|
+
for callback in callbacks {
|
|
26
|
+
callback();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
impl ViewportSignalHandle {
|
|
32
|
+
pub fn value(&self) -> f32 {
|
|
33
|
+
match self.axis {
|
|
34
|
+
ViewportAxis::Width => VIEWPORT_WIDTH_SIGNAL.with(|slot| slot.borrow().get()),
|
|
35
|
+
ViewportAxis::Height => VIEWPORT_HEIGHT_SIGNAL.with(|slot| slot.borrow().get()),
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pub fn subscribe(&self, handler: impl Fn(f32) + 'static) -> SubscriptionGuard {
|
|
40
|
+
handler(self.value());
|
|
41
|
+
match self.axis {
|
|
42
|
+
ViewportAxis::Width => VIEWPORT_WIDTH_SIGNAL.with(|slot| {
|
|
43
|
+
let callback: Callback = Rc::new(move || handler(viewport_width_signal().value()));
|
|
44
|
+
slot.borrow_mut().subscribe(callback)
|
|
45
|
+
}),
|
|
46
|
+
ViewportAxis::Height => VIEWPORT_HEIGHT_SIGNAL.with(|slot| {
|
|
47
|
+
let callback: Callback = Rc::new(move || handler(viewport_height_signal().value()));
|
|
48
|
+
slot.borrow_mut().subscribe(callback)
|
|
49
|
+
}),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
pub fn viewport_width_signal() -> ViewportSignalHandle {
|
|
55
|
+
ViewportSignalHandle {
|
|
56
|
+
axis: ViewportAxis::Width,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
pub fn viewport_height_signal() -> ViewportSignalHandle {
|
|
61
|
+
ViewportSignalHandle {
|
|
62
|
+
axis: ViewportAxis::Height,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pub(crate) fn set_viewport_size(width: f32, height: f32) {
|
|
67
|
+
VIEWPORT_WIDTH_SIGNAL.with(|slot| update_signal(slot, width));
|
|
68
|
+
VIEWPORT_HEIGHT_SIGNAL.with(|slot| update_signal(slot, height));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#[cfg(test)]
|
|
72
|
+
mod tests {
|
|
73
|
+
use super::{set_viewport_size, viewport_height_signal, viewport_width_signal};
|
|
74
|
+
use std::cell::Cell;
|
|
75
|
+
use std::rc::Rc;
|
|
76
|
+
|
|
77
|
+
#[test]
|
|
78
|
+
fn viewport_signals_update_and_notify() {
|
|
79
|
+
let width_values = Rc::new(Cell::new(0));
|
|
80
|
+
let height_values = Rc::new(Cell::new(0));
|
|
81
|
+
|
|
82
|
+
let width_seen = Rc::new(Cell::new(0.0));
|
|
83
|
+
let height_seen = Rc::new(Cell::new(0.0));
|
|
84
|
+
|
|
85
|
+
let width_values_clone = width_values.clone();
|
|
86
|
+
let width_seen_clone = width_seen.clone();
|
|
87
|
+
let _width_guard = viewport_width_signal().subscribe(move |value| {
|
|
88
|
+
width_values_clone.set(width_values_clone.get() + 1);
|
|
89
|
+
width_seen_clone.set(value);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
let height_values_clone = height_values.clone();
|
|
93
|
+
let height_seen_clone = height_seen.clone();
|
|
94
|
+
let _height_guard = viewport_height_signal().subscribe(move |value| {
|
|
95
|
+
height_values_clone.set(height_values_clone.get() + 1);
|
|
96
|
+
height_seen_clone.set(value);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
set_viewport_size(777.0, 555.0);
|
|
100
|
+
|
|
101
|
+
assert_eq!(viewport_width_signal().value(), 777.0);
|
|
102
|
+
assert_eq!(viewport_height_signal().value(), 555.0);
|
|
103
|
+
assert_eq!(width_seen.get(), 777.0);
|
|
104
|
+
assert_eq!(height_seen.get(), 555.0);
|
|
105
|
+
assert!(width_values.get() >= 2);
|
|
106
|
+
assert!(height_values.get() >= 2);
|
|
107
|
+
}
|
|
108
|
+
}
|