@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,467 @@
|
|
|
1
|
+
use crate::drag_drop::{DragDropEffects, DropProposal};
|
|
2
|
+
use crate::file::{register_browser_file, BrowserFile};
|
|
3
|
+
use crate::node::NodeRef;
|
|
4
|
+
use std::cell::RefCell;
|
|
5
|
+
#[cfg(feature = "native-runtime")]
|
|
6
|
+
use std::path::Path;
|
|
7
|
+
|
|
8
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
9
|
+
pub(crate) enum ExternalDragEventType {
|
|
10
|
+
Enter = 1,
|
|
11
|
+
Over = 2,
|
|
12
|
+
Leave = 3,
|
|
13
|
+
Drop = 4,
|
|
14
|
+
Unknown,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
impl ExternalDragEventType {
|
|
18
|
+
pub(crate) fn from_raw(value: u32) -> Self {
|
|
19
|
+
match value {
|
|
20
|
+
1 => Self::Enter,
|
|
21
|
+
2 => Self::Over,
|
|
22
|
+
3 => Self::Leave,
|
|
23
|
+
4 => Self::Drop,
|
|
24
|
+
_ => Self::Unknown,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#[repr(u32)]
|
|
30
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
31
|
+
pub enum ExternalDropItemKind {
|
|
32
|
+
File = 1,
|
|
33
|
+
Text = 2,
|
|
34
|
+
Uri = 3,
|
|
35
|
+
Unknown(u32),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
impl ExternalDropItemKind {
|
|
39
|
+
pub(crate) fn from_raw(value: u32) -> Self {
|
|
40
|
+
match value {
|
|
41
|
+
1 => Self::File,
|
|
42
|
+
2 => Self::Text,
|
|
43
|
+
3 => Self::Uri,
|
|
44
|
+
_ => Self::Unknown(value),
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#[derive(Clone, Debug)]
|
|
50
|
+
pub struct ExternalDropItemInfo {
|
|
51
|
+
pub id: String,
|
|
52
|
+
pub kind: ExternalDropItemKind,
|
|
53
|
+
pub name: String,
|
|
54
|
+
pub mime_type: Option<String>,
|
|
55
|
+
pub size_bytes: f64,
|
|
56
|
+
pub file: Option<BrowserFile>,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
impl ExternalDropItemInfo {
|
|
60
|
+
pub fn new(
|
|
61
|
+
id: impl Into<String>,
|
|
62
|
+
kind: ExternalDropItemKind,
|
|
63
|
+
name: impl Into<String>,
|
|
64
|
+
mime_type: Option<String>,
|
|
65
|
+
size_bytes: f64,
|
|
66
|
+
file: Option<BrowserFile>,
|
|
67
|
+
) -> Self {
|
|
68
|
+
Self {
|
|
69
|
+
id: id.into(),
|
|
70
|
+
kind,
|
|
71
|
+
name: name.into(),
|
|
72
|
+
mime_type,
|
|
73
|
+
size_bytes,
|
|
74
|
+
file,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#[cfg(feature = "native-runtime")]
|
|
79
|
+
pub fn native_path(&self) -> Option<&Path> {
|
|
80
|
+
(self.kind == ExternalDropItemKind::File).then(|| Path::new(&self.id))
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#[derive(Clone, Debug)]
|
|
85
|
+
pub struct ExternalDropEventArgs {
|
|
86
|
+
pub x: f32,
|
|
87
|
+
pub y: f32,
|
|
88
|
+
pub modifiers: u32,
|
|
89
|
+
pub items: Vec<ExternalDropItemInfo>,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
impl ExternalDropEventArgs {
|
|
93
|
+
pub fn new(x: f32, y: f32, modifiers: u32, items: Vec<ExternalDropItemInfo>) -> Self {
|
|
94
|
+
Self {
|
|
95
|
+
x,
|
|
96
|
+
y,
|
|
97
|
+
modifiers,
|
|
98
|
+
items,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
#[derive(Default)]
|
|
104
|
+
struct ExternalDropState {
|
|
105
|
+
active_target: Option<NodeRef>,
|
|
106
|
+
active_effect: DragDropEffects,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
thread_local! {
|
|
110
|
+
static STATE: RefCell<ExternalDropState> = RefCell::new(ExternalDropState::default());
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fn is_default_proposal(proposal: DropProposal) -> bool {
|
|
114
|
+
proposal.effect == DragDropEffects::None && !proposal.show_insertion_marker
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fn normalize_effect(candidate: DragDropEffects) -> DragDropEffects {
|
|
118
|
+
let masked = (candidate as u32)
|
|
119
|
+
& ((DragDropEffects::Copy as u32)
|
|
120
|
+
| (DragDropEffects::Move as u32)
|
|
121
|
+
| (DragDropEffects::Link as u32));
|
|
122
|
+
if masked == DragDropEffects::None as u32 {
|
|
123
|
+
return DragDropEffects::None;
|
|
124
|
+
}
|
|
125
|
+
if (masked & DragDropEffects::Move as u32) != 0 {
|
|
126
|
+
return DragDropEffects::Move;
|
|
127
|
+
}
|
|
128
|
+
if (masked & DragDropEffects::Copy as u32) != 0 {
|
|
129
|
+
return DragDropEffects::Copy;
|
|
130
|
+
}
|
|
131
|
+
if (masked & DragDropEffects::Link as u32) != 0 {
|
|
132
|
+
return DragDropEffects::Link;
|
|
133
|
+
}
|
|
134
|
+
DragDropEffects::None
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
fn resolve_drop_target(pointed_node: Option<NodeRef>) -> Option<NodeRef> {
|
|
138
|
+
let mut current = pointed_node;
|
|
139
|
+
while let Some(node) = current {
|
|
140
|
+
if node.allows_external_drop() {
|
|
141
|
+
return Some(node);
|
|
142
|
+
}
|
|
143
|
+
current = node.parent();
|
|
144
|
+
}
|
|
145
|
+
None
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
fn finish(
|
|
149
|
+
state: &mut ExternalDropState,
|
|
150
|
+
x: f32,
|
|
151
|
+
y: f32,
|
|
152
|
+
modifiers: u32,
|
|
153
|
+
items: &[ExternalDropItemInfo],
|
|
154
|
+
notify_target_leave: bool,
|
|
155
|
+
) {
|
|
156
|
+
let target = state.active_target.take();
|
|
157
|
+
state.active_effect = DragDropEffects::None;
|
|
158
|
+
if notify_target_leave {
|
|
159
|
+
if let Some(target) = target {
|
|
160
|
+
target.handle_external_drag_leave(ExternalDropEventArgs::new(
|
|
161
|
+
x,
|
|
162
|
+
y,
|
|
163
|
+
modifiers,
|
|
164
|
+
items.to_vec(),
|
|
165
|
+
));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
pub(crate) fn handle_event(
|
|
171
|
+
pointed_node: Option<NodeRef>,
|
|
172
|
+
event_type: ExternalDragEventType,
|
|
173
|
+
x: f32,
|
|
174
|
+
y: f32,
|
|
175
|
+
modifiers: u32,
|
|
176
|
+
items: Vec<ExternalDropItemInfo>,
|
|
177
|
+
) -> DragDropEffects {
|
|
178
|
+
STATE.with(|slot| {
|
|
179
|
+
let mut state = slot.borrow_mut();
|
|
180
|
+
if event_type == ExternalDragEventType::Leave {
|
|
181
|
+
finish(&mut state, x, y, modifiers, &items, true);
|
|
182
|
+
return DragDropEffects::None;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let target = resolve_drop_target(pointed_node);
|
|
186
|
+
let args = ExternalDropEventArgs::new(x, y, modifiers, items.clone());
|
|
187
|
+
let mut proposal = DropProposal::none();
|
|
188
|
+
let target_changed = match (&target, &state.active_target) {
|
|
189
|
+
(Some(target), Some(active)) => target.handle() != active.handle(),
|
|
190
|
+
(None, None) => false,
|
|
191
|
+
_ => true,
|
|
192
|
+
};
|
|
193
|
+
if target_changed {
|
|
194
|
+
if let Some(previous_target) = state.active_target.take() {
|
|
195
|
+
previous_target.handle_external_drag_leave(args.clone());
|
|
196
|
+
}
|
|
197
|
+
state.active_target = target.clone();
|
|
198
|
+
state.active_effect = DragDropEffects::None;
|
|
199
|
+
if let Some(target) = target.as_ref() {
|
|
200
|
+
if target.has_external_drag_enter_handler() {
|
|
201
|
+
proposal = target.handle_external_drag_enter(args.clone());
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let Some(target) = target else {
|
|
207
|
+
state.active_effect = DragDropEffects::None;
|
|
208
|
+
return DragDropEffects::None;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
if target.has_external_drag_over_handler() {
|
|
212
|
+
proposal = target.handle_external_drag_over(args.clone());
|
|
213
|
+
} else if is_default_proposal(proposal) {
|
|
214
|
+
proposal = DropProposal::new(state.active_effect, false);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let effect = normalize_effect(proposal.effect);
|
|
218
|
+
state.active_effect = effect;
|
|
219
|
+
if event_type == ExternalDragEventType::Drop {
|
|
220
|
+
if effect != DragDropEffects::None {
|
|
221
|
+
target.handle_external_drop_event(args);
|
|
222
|
+
}
|
|
223
|
+
finish(&mut state, x, y, modifiers, &items, true);
|
|
224
|
+
}
|
|
225
|
+
effect
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
pub(crate) fn handle_node_destroyed(node: NodeRef) {
|
|
230
|
+
STATE.with(|slot| {
|
|
231
|
+
let mut state = slot.borrow_mut();
|
|
232
|
+
let Some(active_target) = state.active_target.as_ref() else {
|
|
233
|
+
return;
|
|
234
|
+
};
|
|
235
|
+
if active_target.handle() == node.handle() {
|
|
236
|
+
state.active_target = None;
|
|
237
|
+
state.active_effect = DragDropEffects::None;
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
pub(crate) fn reset() {
|
|
243
|
+
STATE.with(|slot| {
|
|
244
|
+
let mut state = slot.borrow_mut();
|
|
245
|
+
state.active_target = None;
|
|
246
|
+
state.active_effect = DragDropEffects::None;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
pub(crate) fn decode_payload(
|
|
251
|
+
payload_ptr: *const u8,
|
|
252
|
+
payload_len: u32,
|
|
253
|
+
) -> Vec<ExternalDropItemInfo> {
|
|
254
|
+
let mut items = Vec::new();
|
|
255
|
+
if payload_ptr.is_null() || payload_len == 0 {
|
|
256
|
+
return items;
|
|
257
|
+
}
|
|
258
|
+
let bytes = unsafe { std::slice::from_raw_parts(payload_ptr, payload_len as usize) };
|
|
259
|
+
if bytes.len() < 4 {
|
|
260
|
+
crate::logger::warn("ExternalDrop", "Malformed external drop payload header.");
|
|
261
|
+
return items;
|
|
262
|
+
}
|
|
263
|
+
let mut cursor = 0usize;
|
|
264
|
+
let item_count = u32::from_le_bytes(bytes[cursor..cursor + 4].try_into().unwrap_or([0; 4]));
|
|
265
|
+
cursor += 4;
|
|
266
|
+
for index in 0..item_count {
|
|
267
|
+
if cursor + 12 > bytes.len() {
|
|
268
|
+
crate::logger::warn(
|
|
269
|
+
"ExternalDrop",
|
|
270
|
+
&format!("Truncated external drop item header at index {}.", index),
|
|
271
|
+
);
|
|
272
|
+
return items;
|
|
273
|
+
}
|
|
274
|
+
let kind = ExternalDropItemKind::from_raw(u32::from_le_bytes(
|
|
275
|
+
bytes[cursor..cursor + 4].try_into().unwrap_or([0; 4]),
|
|
276
|
+
));
|
|
277
|
+
cursor += 4;
|
|
278
|
+
let size_bytes = f64::from_le_bytes(bytes[cursor..cursor + 8].try_into().unwrap_or([0; 8]));
|
|
279
|
+
cursor += 8;
|
|
280
|
+
|
|
281
|
+
let Some(id) = decode_string(bytes, &mut cursor, "id", index) else {
|
|
282
|
+
return items;
|
|
283
|
+
};
|
|
284
|
+
let Some(name) = decode_string(bytes, &mut cursor, "name", index) else {
|
|
285
|
+
return items;
|
|
286
|
+
};
|
|
287
|
+
let Some(mime_type) = decode_string(bytes, &mut cursor, "mime", index) else {
|
|
288
|
+
return items;
|
|
289
|
+
};
|
|
290
|
+
let mime_type = if mime_type.is_empty() {
|
|
291
|
+
None
|
|
292
|
+
} else {
|
|
293
|
+
Some(mime_type)
|
|
294
|
+
};
|
|
295
|
+
let file = if kind == ExternalDropItemKind::File && !id.is_empty() {
|
|
296
|
+
Some(register_browser_file(
|
|
297
|
+
id.clone(),
|
|
298
|
+
name.clone(),
|
|
299
|
+
mime_type.clone(),
|
|
300
|
+
size_bytes as u64,
|
|
301
|
+
0,
|
|
302
|
+
))
|
|
303
|
+
} else {
|
|
304
|
+
None
|
|
305
|
+
};
|
|
306
|
+
items.push(ExternalDropItemInfo::new(
|
|
307
|
+
id, kind, name, mime_type, size_bytes, file,
|
|
308
|
+
));
|
|
309
|
+
}
|
|
310
|
+
items
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
fn decode_string(bytes: &[u8], cursor: &mut usize, label: &str, index: u32) -> Option<String> {
|
|
314
|
+
if *cursor + 4 > bytes.len() {
|
|
315
|
+
crate::logger::warn(
|
|
316
|
+
"ExternalDrop",
|
|
317
|
+
&format!(
|
|
318
|
+
"Truncated external drop item {} length at index {}.",
|
|
319
|
+
label, index
|
|
320
|
+
),
|
|
321
|
+
);
|
|
322
|
+
return None;
|
|
323
|
+
}
|
|
324
|
+
let len = u32::from_le_bytes(bytes[*cursor..*cursor + 4].try_into().unwrap_or([0; 4])) as usize;
|
|
325
|
+
*cursor += 4;
|
|
326
|
+
if *cursor + len > bytes.len() {
|
|
327
|
+
crate::logger::warn(
|
|
328
|
+
"ExternalDrop",
|
|
329
|
+
&format!("Truncated external drop item {} at index {}.", label, index),
|
|
330
|
+
);
|
|
331
|
+
return None;
|
|
332
|
+
}
|
|
333
|
+
let value = String::from_utf8_lossy(&bytes[*cursor..*cursor + len]).into_owned();
|
|
334
|
+
*cursor += len;
|
|
335
|
+
Some(value)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
#[cfg(test)]
|
|
339
|
+
mod tests {
|
|
340
|
+
use super::*;
|
|
341
|
+
use crate::drag_drop::DropProposal;
|
|
342
|
+
use crate::event::reset as reset_events;
|
|
343
|
+
use crate::node::{flex_box, Node};
|
|
344
|
+
use std::cell::{Cell, RefCell};
|
|
345
|
+
use std::rc::Rc;
|
|
346
|
+
|
|
347
|
+
#[test]
|
|
348
|
+
fn routes_external_drop_enter_over_leave_and_drop() {
|
|
349
|
+
reset();
|
|
350
|
+
reset_events();
|
|
351
|
+
|
|
352
|
+
let root = flex_box();
|
|
353
|
+
let target = flex_box();
|
|
354
|
+
root.child(&target);
|
|
355
|
+
|
|
356
|
+
let enter_count = Rc::new(Cell::new(0));
|
|
357
|
+
let over_count = Rc::new(Cell::new(0));
|
|
358
|
+
let leave_count = Rc::new(Cell::new(0));
|
|
359
|
+
let drop_count = Rc::new(Cell::new(0));
|
|
360
|
+
let last_name = Rc::new(RefCell::new(String::new()));
|
|
361
|
+
|
|
362
|
+
target
|
|
363
|
+
.allow_external_drop(true)
|
|
364
|
+
.on_external_drag_enter({
|
|
365
|
+
let enter_count = enter_count.clone();
|
|
366
|
+
let last_name = last_name.clone();
|
|
367
|
+
move |args| {
|
|
368
|
+
enter_count.set(enter_count.get() + 1);
|
|
369
|
+
if let Some(item) = args.items.first() {
|
|
370
|
+
last_name.replace(item.name.clone());
|
|
371
|
+
}
|
|
372
|
+
DropProposal::new(DragDropEffects::Copy, false)
|
|
373
|
+
}
|
|
374
|
+
})
|
|
375
|
+
.on_external_drag_over({
|
|
376
|
+
let over_count = over_count.clone();
|
|
377
|
+
move |_args| {
|
|
378
|
+
over_count.set(over_count.get() + 1);
|
|
379
|
+
DropProposal::new(DragDropEffects::Copy, false)
|
|
380
|
+
}
|
|
381
|
+
})
|
|
382
|
+
.on_external_drag_leave({
|
|
383
|
+
let leave_count = leave_count.clone();
|
|
384
|
+
move |_args| {
|
|
385
|
+
leave_count.set(leave_count.get() + 1);
|
|
386
|
+
}
|
|
387
|
+
})
|
|
388
|
+
.on_external_drop({
|
|
389
|
+
let drop_count = drop_count.clone();
|
|
390
|
+
move |_args| {
|
|
391
|
+
drop_count.set(drop_count.get() + 1);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
let items = vec![ExternalDropItemInfo::new(
|
|
396
|
+
"external-drop-1",
|
|
397
|
+
ExternalDropItemKind::File,
|
|
398
|
+
"todo.txt",
|
|
399
|
+
Some("text/plain".to_string()),
|
|
400
|
+
10.0,
|
|
401
|
+
None,
|
|
402
|
+
)];
|
|
403
|
+
|
|
404
|
+
assert_eq!(
|
|
405
|
+
handle_event(
|
|
406
|
+
Some(target.node_ref()),
|
|
407
|
+
ExternalDragEventType::Enter,
|
|
408
|
+
12.0,
|
|
409
|
+
18.0,
|
|
410
|
+
0,
|
|
411
|
+
items.clone(),
|
|
412
|
+
),
|
|
413
|
+
DragDropEffects::Copy
|
|
414
|
+
);
|
|
415
|
+
assert_eq!(
|
|
416
|
+
handle_event(
|
|
417
|
+
Some(target.node_ref()),
|
|
418
|
+
ExternalDragEventType::Over,
|
|
419
|
+
14.0,
|
|
420
|
+
19.0,
|
|
421
|
+
0,
|
|
422
|
+
items.clone(),
|
|
423
|
+
),
|
|
424
|
+
DragDropEffects::Copy
|
|
425
|
+
);
|
|
426
|
+
assert_eq!(
|
|
427
|
+
handle_event(
|
|
428
|
+
Some(target.node_ref()),
|
|
429
|
+
ExternalDragEventType::Drop,
|
|
430
|
+
16.0,
|
|
431
|
+
20.0,
|
|
432
|
+
0,
|
|
433
|
+
items,
|
|
434
|
+
),
|
|
435
|
+
DragDropEffects::Copy
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
assert_eq!(enter_count.get(), 1);
|
|
439
|
+
assert_eq!(over_count.get(), 3);
|
|
440
|
+
assert_eq!(leave_count.get(), 1);
|
|
441
|
+
assert_eq!(drop_count.get(), 1);
|
|
442
|
+
assert_eq!(last_name.borrow().as_str(), "todo.txt");
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
#[test]
|
|
446
|
+
fn decodes_unknown_external_item_kind_without_registering_file() {
|
|
447
|
+
reset();
|
|
448
|
+
reset_events();
|
|
449
|
+
|
|
450
|
+
let mut payload = Vec::new();
|
|
451
|
+
payload.extend_from_slice(&1u32.to_le_bytes());
|
|
452
|
+
payload.extend_from_slice(&99u32.to_le_bytes());
|
|
453
|
+
payload.extend_from_slice(&123.0f64.to_le_bytes());
|
|
454
|
+
for value in ["external-drop-unknown", "note.txt", "text/plain"] {
|
|
455
|
+
payload.extend_from_slice(&(value.len() as u32).to_le_bytes());
|
|
456
|
+
payload.extend_from_slice(value.as_bytes());
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
let items = decode_payload(payload.as_ptr(), payload.len() as u32);
|
|
460
|
+
assert_eq!(items.len(), 1);
|
|
461
|
+
assert_eq!(items[0].kind, ExternalDropItemKind::Unknown(99));
|
|
462
|
+
assert_eq!(items[0].id, "external-drop-unknown");
|
|
463
|
+
assert_eq!(items[0].name, "note.txt");
|
|
464
|
+
assert_eq!(items[0].mime_type.as_deref(), Some("text/plain"));
|
|
465
|
+
assert!(items[0].file.is_none());
|
|
466
|
+
}
|
|
467
|
+
}
|