elit 3.5.6 → 3.5.8

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.
Files changed (128) hide show
  1. package/Cargo.toml +1 -1
  2. package/README.md +1 -1
  3. package/desktop/build.rs +83 -0
  4. package/desktop/icon.rs +106 -0
  5. package/desktop/lib.rs +2 -0
  6. package/desktop/main.rs +235 -0
  7. package/desktop/native_main.rs +128 -0
  8. package/desktop/native_renderer/action_widgets.rs +184 -0
  9. package/desktop/native_renderer/app_models.rs +171 -0
  10. package/desktop/native_renderer/app_runtime.rs +140 -0
  11. package/desktop/native_renderer/container_rendering.rs +610 -0
  12. package/desktop/native_renderer/content_widgets.rs +634 -0
  13. package/desktop/native_renderer/css_models.rs +371 -0
  14. package/desktop/native_renderer/embedded_surfaces.rs +414 -0
  15. package/desktop/native_renderer/form_controls.rs +516 -0
  16. package/desktop/native_renderer/interaction_dispatch.rs +89 -0
  17. package/desktop/native_renderer/runtime_support.rs +135 -0
  18. package/desktop/native_renderer/utilities.rs +495 -0
  19. package/desktop/native_renderer/vector_drawing.rs +491 -0
  20. package/desktop/native_renderer.rs +4122 -0
  21. package/desktop/runtime/external.rs +422 -0
  22. package/desktop/runtime/mod.rs +67 -0
  23. package/desktop/runtime/quickjs.rs +106 -0
  24. package/desktop/window.rs +383 -0
  25. package/dist/build.d.ts +1 -1
  26. package/dist/cli.cjs +16 -2
  27. package/dist/cli.mjs +16 -2
  28. package/dist/config.d.ts +1 -1
  29. package/dist/coverage.d.ts +1 -1
  30. package/dist/desktop-auto-render.cjs +2370 -0
  31. package/dist/desktop-auto-render.d.ts +13 -0
  32. package/dist/desktop-auto-render.js +2341 -0
  33. package/dist/desktop-auto-render.mjs +2344 -0
  34. package/dist/render-context.cjs +118 -0
  35. package/dist/render-context.d.ts +39 -0
  36. package/dist/render-context.js +77 -0
  37. package/dist/render-context.mjs +87 -0
  38. package/dist/{server-CNgDUgSZ.d.ts → server-FCdUqabc.d.ts} +1 -1
  39. package/dist/server.d.ts +1 -1
  40. package/package.json +26 -3
  41. package/dist/build.d.mts +0 -20
  42. package/dist/chokidar.d.mts +0 -134
  43. package/dist/cli.d.mts +0 -81
  44. package/dist/config.d.mts +0 -254
  45. package/dist/coverage.d.mts +0 -85
  46. package/dist/database.d.mts +0 -52
  47. package/dist/desktop.d.mts +0 -68
  48. package/dist/dom.d.mts +0 -87
  49. package/dist/el.d.mts +0 -208
  50. package/dist/fs.d.mts +0 -255
  51. package/dist/hmr.d.mts +0 -38
  52. package/dist/http.d.mts +0 -169
  53. package/dist/https.d.mts +0 -108
  54. package/dist/index.d.mts +0 -13
  55. package/dist/mime-types.d.mts +0 -48
  56. package/dist/native.d.mts +0 -136
  57. package/dist/path.d.mts +0 -163
  58. package/dist/router.d.mts +0 -49
  59. package/dist/runtime.d.mts +0 -97
  60. package/dist/server-D0Dp4R5z.d.mts +0 -449
  61. package/dist/server.d.mts +0 -7
  62. package/dist/state.d.mts +0 -117
  63. package/dist/style.d.mts +0 -232
  64. package/dist/test-reporter.d.mts +0 -77
  65. package/dist/test-runtime.d.mts +0 -122
  66. package/dist/test.d.mts +0 -39
  67. package/dist/types.d.mts +0 -586
  68. package/dist/universal.d.mts +0 -21
  69. package/dist/ws.d.mts +0 -200
  70. package/dist/wss.d.mts +0 -108
  71. package/src/build.ts +0 -362
  72. package/src/chokidar.ts +0 -427
  73. package/src/cli.ts +0 -1162
  74. package/src/config.ts +0 -509
  75. package/src/coverage.ts +0 -1479
  76. package/src/database.ts +0 -1410
  77. package/src/desktop-auto-render.ts +0 -317
  78. package/src/desktop-cli.ts +0 -1533
  79. package/src/desktop.ts +0 -99
  80. package/src/dev-build.ts +0 -340
  81. package/src/dom.ts +0 -901
  82. package/src/el.ts +0 -183
  83. package/src/fs.ts +0 -609
  84. package/src/hmr.ts +0 -149
  85. package/src/http.ts +0 -856
  86. package/src/https.ts +0 -411
  87. package/src/index.ts +0 -16
  88. package/src/mime-types.ts +0 -222
  89. package/src/mobile-cli.ts +0 -2313
  90. package/src/native-background.ts +0 -444
  91. package/src/native-border.ts +0 -343
  92. package/src/native-canvas.ts +0 -260
  93. package/src/native-cli.ts +0 -414
  94. package/src/native-color.ts +0 -904
  95. package/src/native-estimation.ts +0 -194
  96. package/src/native-grid.ts +0 -590
  97. package/src/native-interaction.ts +0 -1289
  98. package/src/native-layout.ts +0 -568
  99. package/src/native-link.ts +0 -76
  100. package/src/native-render-support.ts +0 -361
  101. package/src/native-spacing.ts +0 -231
  102. package/src/native-state.ts +0 -318
  103. package/src/native-strings.ts +0 -46
  104. package/src/native-transform.ts +0 -120
  105. package/src/native-types.ts +0 -439
  106. package/src/native-typography.ts +0 -254
  107. package/src/native-units.ts +0 -441
  108. package/src/native-vector.ts +0 -910
  109. package/src/native.ts +0 -5606
  110. package/src/path.ts +0 -493
  111. package/src/pm-cli.ts +0 -2498
  112. package/src/preview-build.ts +0 -294
  113. package/src/render-context.ts +0 -138
  114. package/src/router.ts +0 -260
  115. package/src/runtime.ts +0 -97
  116. package/src/server.ts +0 -2294
  117. package/src/state.ts +0 -556
  118. package/src/style.ts +0 -1790
  119. package/src/test-globals.d.ts +0 -184
  120. package/src/test-reporter.ts +0 -609
  121. package/src/test-runtime.ts +0 -1359
  122. package/src/test.ts +0 -368
  123. package/src/types.ts +0 -381
  124. package/src/universal.ts +0 -81
  125. package/src/wapk-cli.ts +0 -3213
  126. package/src/workspace-package.ts +0 -102
  127. package/src/ws.ts +0 -648
  128. package/src/wss.ts +0 -241
@@ -0,0 +1,516 @@
1
+ use eframe::egui::{self, Ui};
2
+ use serde_json::Value;
3
+
4
+ use super::app_models::{DesktopControlEventData, PickerOptionData};
5
+ use super::utilities::{
6
+ build_event_payload, control_event_action, format_number, parse_native_bool,
7
+ resolve_control_event_input_type, resolve_interaction, should_dispatch_control_event,
8
+ value_as_string,
9
+ };
10
+ use super::{DesktopNativeApp, DesktopStateValue, NativeElementNode, NativeNode};
11
+
12
+ impl DesktopNativeApp {
13
+ fn dispatch_control_events(
14
+ &mut self,
15
+ ctx: &egui::Context,
16
+ node: &NativeElementNode,
17
+ event_data: DesktopControlEventData,
18
+ ) {
19
+ let input_type = resolve_control_event_input_type(node);
20
+
21
+ for event_name in ["input", "change"] {
22
+ if !should_dispatch_control_event(node, event_name) {
23
+ continue;
24
+ }
25
+
26
+ let payload = build_event_payload(node, event_name, input_type.clone(), &event_data);
27
+ if let Some(interaction) = resolve_interaction(node, Some(control_event_action(node, event_name)), Some(payload)) {
28
+ self.dispatch_interaction(ctx, interaction);
29
+ }
30
+ }
31
+ }
32
+
33
+ pub(super) fn render_text_input(&mut self, ui: &mut Ui, ctx: &egui::Context, node: &NativeElementNode, path: &str) {
34
+ let binding = self.get_binding_reference(node);
35
+ let disabled = self.is_disabled(node);
36
+ let read_only = self.is_read_only(node);
37
+ let input_type = resolve_control_event_input_type(node).unwrap_or_else(|| String::from("text"));
38
+ let placeholder = self.resolve_prop_string(node, "placeholder").unwrap_or_default();
39
+ let mut value = if let Some(binding) = &binding {
40
+ self.state_values
41
+ .get(&binding.id)
42
+ .map(DesktopStateValue::as_text_input_value)
43
+ .unwrap_or_else(|| node.props.get("value").and_then(value_as_string).unwrap_or_default())
44
+ } else {
45
+ self.local_text_inputs
46
+ .get(path)
47
+ .cloned()
48
+ .or_else(|| node.props.get("value").and_then(value_as_string))
49
+ .unwrap_or_default()
50
+ };
51
+
52
+ let available_width = ui.available_width().max(1.0);
53
+ let mut base_state = self.base_pseudo_state(node);
54
+ base_state.placeholder_shown = !placeholder.is_empty() && value.trim().is_empty();
55
+ let state_styles = self.resolve_widget_state_styles(node, base_state);
56
+ let inactive_style = state_styles.inactive.as_ref();
57
+ let padding = Self::resolve_box_edges(inactive_style, "padding").filter(|padding| !padding.is_zero());
58
+ let desired_width = self.resolve_widget_width_from_style(inactive_style, available_width);
59
+ let (font_id, _, _) = self.resolve_text_font_id(ui, inactive_style);
60
+ let resolved_font_size = font_id
61
+ .as_ref()
62
+ .map(|font_id| font_id.size)
63
+ .unwrap_or_else(|| egui::TextStyle::Body.resolve(ui.style().as_ref()).size);
64
+ let desired_rows = if node.source_tag == "textarea" {
65
+ inactive_style
66
+ .and_then(|style| {
67
+ let line_height = Self::resolve_text_line_height(
68
+ Some(style),
69
+ resolved_font_size,
70
+ self.css_measure_context(Some(resolved_font_size)),
71
+ )
72
+ .unwrap_or(resolved_font_size * 1.35);
73
+ let target_height = self
74
+ .parse_css_number_with_viewport(style.get("height"))
75
+ .or_else(|| self.parse_css_number_with_viewport(style.get("minHeight")))?;
76
+ let inner_vertical = padding.map(|padding| padding.top + padding.bottom).unwrap_or(0.0);
77
+ Some(((target_height - inner_vertical).max(line_height) / line_height).round().max(1.0) as usize)
78
+ })
79
+ .unwrap_or(4)
80
+ } else {
81
+ 1
82
+ };
83
+ let text_color = Self::resolve_text_color_from_style(if disabled {
84
+ state_styles.disabled.as_ref().or(inactive_style)
85
+ } else {
86
+ inactive_style
87
+ });
88
+
89
+ let response = ui
90
+ .scope(|ui| {
91
+ self.apply_widget_state_visuals(
92
+ ui,
93
+ inactive_style,
94
+ state_styles.hovered.as_ref(),
95
+ state_styles.active.as_ref(),
96
+ state_styles.focus.as_ref(),
97
+ state_styles.disabled.as_ref(),
98
+ );
99
+
100
+ let mut text_edit = if node.source_tag == "textarea" {
101
+ egui::TextEdit::multiline(&mut value).desired_rows(desired_rows)
102
+ } else {
103
+ egui::TextEdit::singleline(&mut value)
104
+ };
105
+
106
+ if let Some(padding) = padding {
107
+ text_edit = text_edit.margin(padding.to_margin());
108
+ }
109
+ if let Some(font_id) = font_id.clone() {
110
+ text_edit = text_edit.font(font_id);
111
+ }
112
+ if let Some(text_color) = text_color {
113
+ text_edit = text_edit.text_color_opt(Some(text_color));
114
+ }
115
+ if let Some(desired_width) = desired_width {
116
+ text_edit = text_edit.desired_width(desired_width);
117
+ }
118
+ if !placeholder.is_empty() {
119
+ text_edit = text_edit.hint_text(placeholder);
120
+ }
121
+ if input_type == "password" {
122
+ text_edit = text_edit.password(true);
123
+ }
124
+ if read_only {
125
+ text_edit = text_edit.interactive(false);
126
+ }
127
+
128
+ if disabled {
129
+ ui.add_enabled(false, text_edit)
130
+ } else {
131
+ ui.add(text_edit)
132
+ }
133
+ })
134
+ .inner;
135
+
136
+ if response.changed() {
137
+ if let Some(binding) = binding {
138
+ let next_value = match binding.value_type.as_str() {
139
+ "number" => value
140
+ .parse::<f64>()
141
+ .map(DesktopStateValue::Number)
142
+ .unwrap_or_else(|_| DesktopStateValue::Number(0.0)),
143
+ "boolean" => DesktopStateValue::Boolean(parse_native_bool(Some(&Value::String(value.clone())))),
144
+ "string-array" => DesktopStateValue::StringArray(
145
+ value
146
+ .split(',')
147
+ .map(str::trim)
148
+ .filter(|entry| !entry.is_empty())
149
+ .map(str::to_string)
150
+ .collect(),
151
+ ),
152
+ _ => DesktopStateValue::String(value.clone()),
153
+ };
154
+ self.state_values.insert(binding.id, next_value);
155
+ } else {
156
+ self.local_text_inputs.insert(path.to_string(), value.clone());
157
+ }
158
+
159
+ self.dispatch_control_events(
160
+ ctx,
161
+ node,
162
+ DesktopControlEventData {
163
+ value: Some(value),
164
+ ..Default::default()
165
+ },
166
+ );
167
+ }
168
+ }
169
+
170
+ pub(super) fn render_toggle(&mut self, ui: &mut Ui, ctx: &egui::Context, node: &NativeElementNode, path: &str) {
171
+ let binding = self.get_binding_reference(node);
172
+ let disabled = self.is_disabled(node);
173
+ let mut checked = if let Some(binding) = &binding {
174
+ self.state_values
175
+ .get(&binding.id)
176
+ .map(DesktopStateValue::as_bool)
177
+ .unwrap_or_else(|| parse_native_bool(node.props.get("checked")))
178
+ } else {
179
+ self.local_toggles
180
+ .get(path)
181
+ .copied()
182
+ .unwrap_or_else(|| parse_native_bool(node.props.get("checked")))
183
+ };
184
+ let label = self.resolve_label(node).unwrap_or_default();
185
+ let mut base_state = self.base_pseudo_state(node);
186
+ base_state.checked = checked;
187
+ let state_styles = self.resolve_widget_state_styles(node, base_state);
188
+ let inactive_style = state_styles.inactive.as_ref();
189
+ let min_size = self.resolve_widget_min_size(inactive_style);
190
+ let response = ui
191
+ .scope(|ui| {
192
+ self.apply_widget_state_visuals(
193
+ ui,
194
+ inactive_style,
195
+ state_styles.hovered.as_ref(),
196
+ state_styles.active.as_ref(),
197
+ state_styles.focus.as_ref(),
198
+ state_styles.disabled.as_ref(),
199
+ );
200
+
201
+ if min_size.x > 0.0 {
202
+ ui.spacing_mut().icon_width = min_size.x;
203
+ ui.spacing_mut().icon_width_inner = (min_size.x * (8.0 / 14.0)).clamp(4.0, min_size.x.max(4.0));
204
+ ui.spacing_mut().interact_size.x = ui.spacing().interact_size.x.max(min_size.x);
205
+ }
206
+ if min_size.y > 0.0 {
207
+ ui.spacing_mut().interact_size.y = ui.spacing().interact_size.y.max(min_size.y);
208
+ }
209
+
210
+ ui.add_enabled(!disabled, egui::Checkbox::new(&mut checked, label.clone()))
211
+ })
212
+ .inner;
213
+
214
+ if response.changed() {
215
+ if let Some(binding) = binding {
216
+ self.state_values.insert(binding.id, DesktopStateValue::Boolean(checked));
217
+ } else {
218
+ self.local_toggles.insert(path.to_string(), checked);
219
+ }
220
+
221
+ self.dispatch_control_events(
222
+ ctx,
223
+ node,
224
+ DesktopControlEventData {
225
+ checked: Some(checked),
226
+ ..Default::default()
227
+ },
228
+ );
229
+ }
230
+ }
231
+
232
+ pub(super) fn render_slider(&mut self, ui: &mut Ui, ctx: &egui::Context, node: &NativeElementNode, path: &str) {
233
+ let binding = self.get_binding_reference(node);
234
+ let disabled = self.is_disabled(node);
235
+ let min = node.props.get("min").and_then(Value::as_f64).unwrap_or(0.0);
236
+ let max = node.props.get("max").and_then(Value::as_f64).unwrap_or(100.0);
237
+ let step = node.props.get("step").and_then(Value::as_f64).filter(|value| *value > 0.0);
238
+ let mut value = if let Some(binding) = &binding {
239
+ match self.state_values.get(&binding.id) {
240
+ Some(DesktopStateValue::Number(number)) => *number,
241
+ Some(DesktopStateValue::String(text)) => text.parse::<f64>().unwrap_or(min),
242
+ _ => node.props.get("value").and_then(Value::as_f64).unwrap_or(min),
243
+ }
244
+ } else {
245
+ self.local_slider_values
246
+ .get(path)
247
+ .copied()
248
+ .or_else(|| node.props.get("value").and_then(Value::as_f64))
249
+ .unwrap_or(min)
250
+ };
251
+ let state_styles = self.resolve_widget_state_styles(node, self.base_pseudo_state(node));
252
+ let inactive_style = state_styles.inactive.as_ref();
253
+ let desired_width = self.resolve_widget_width_from_style(inactive_style, ui.available_width().max(1.0));
254
+ let min_size = self.resolve_widget_min_size(inactive_style);
255
+ let response = ui
256
+ .scope(|ui| {
257
+ self.apply_widget_state_visuals(
258
+ ui,
259
+ inactive_style,
260
+ state_styles.hovered.as_ref(),
261
+ state_styles.active.as_ref(),
262
+ state_styles.focus.as_ref(),
263
+ state_styles.disabled.as_ref(),
264
+ );
265
+
266
+ if let Some(desired_width) = desired_width {
267
+ ui.spacing_mut().slider_width = desired_width;
268
+ }
269
+ if min_size.y > 0.0 {
270
+ ui.spacing_mut().interact_size.y = ui.spacing().interact_size.y.max(min_size.y);
271
+ }
272
+
273
+ let mut slider = egui::Slider::new(&mut value, min..=max);
274
+ if let Some(step) = step {
275
+ slider = slider.step_by(step);
276
+ }
277
+ if let Some(label) = self.resolve_label(node) {
278
+ slider = slider.text(label);
279
+ }
280
+
281
+ ui.add_enabled(!disabled, slider)
282
+ })
283
+ .inner;
284
+
285
+ if response.changed() {
286
+ if let Some(binding) = binding {
287
+ let next_value = match binding.value_type.as_str() {
288
+ "string" => DesktopStateValue::String(format_number(value)),
289
+ _ => DesktopStateValue::Number(value),
290
+ };
291
+ self.state_values.insert(binding.id, next_value);
292
+ } else {
293
+ self.local_slider_values.insert(path.to_string(), value);
294
+ }
295
+
296
+ self.dispatch_control_events(
297
+ ctx,
298
+ node,
299
+ DesktopControlEventData {
300
+ value: Some(format_number(value)),
301
+ ..Default::default()
302
+ },
303
+ );
304
+ }
305
+ }
306
+
307
+ pub(super) fn collect_picker_options(&self, node: &NativeElementNode) -> Vec<PickerOptionData> {
308
+ node.children
309
+ .iter()
310
+ .filter_map(|child| match child {
311
+ NativeNode::Element(element_node) if element_node.component == "Option" => Some(PickerOptionData {
312
+ label: self.collect_text_content(child).trim().to_string(),
313
+ value: element_node
314
+ .props
315
+ .get("value")
316
+ .and_then(value_as_string)
317
+ .unwrap_or_else(|| self.collect_text_content(child).trim().to_string()),
318
+ disabled: parse_native_bool(element_node.props.get("disabled")),
319
+ selected: parse_native_bool(element_node.props.get("selected")),
320
+ }),
321
+ _ => None,
322
+ })
323
+ .collect()
324
+ }
325
+
326
+ pub(super) fn render_picker(&mut self, ui: &mut Ui, ctx: &egui::Context, node: &NativeElementNode, path: &str) {
327
+ let binding = self.get_binding_reference(node);
328
+ let disabled = self.is_disabled(node);
329
+ let options = self.collect_picker_options(node);
330
+ let multiple = parse_native_bool(node.props.get("multiple"));
331
+
332
+ if multiple {
333
+ let mut values = if let Some(binding) = &binding {
334
+ match self.state_values.get(&binding.id) {
335
+ Some(DesktopStateValue::StringArray(values)) => values.clone(),
336
+ Some(DesktopStateValue::String(value)) => vec![value.clone()],
337
+ _ => Vec::new(),
338
+ }
339
+ } else {
340
+ self.local_multi_picker_values
341
+ .get(path)
342
+ .cloned()
343
+ .unwrap_or_else(|| {
344
+ options
345
+ .iter()
346
+ .filter(|option| option.selected)
347
+ .map(|option| option.value.clone())
348
+ .collect()
349
+ })
350
+ };
351
+
352
+ let mut changed = false;
353
+ let mut base_state = self.base_pseudo_state(node);
354
+ base_state.selected = !values.is_empty();
355
+ let state_styles = self.resolve_widget_state_styles(node, base_state);
356
+ let inactive_style = state_styles.inactive.as_ref();
357
+ let min_size = self.resolve_widget_min_size(inactive_style);
358
+ ui.scope(|ui| {
359
+ self.apply_widget_state_visuals(
360
+ ui,
361
+ inactive_style,
362
+ state_styles.hovered.as_ref(),
363
+ state_styles.active.as_ref(),
364
+ state_styles.focus.as_ref(),
365
+ state_styles.disabled.as_ref(),
366
+ );
367
+
368
+ if min_size.x > 0.0 {
369
+ ui.spacing_mut().icon_width = min_size.x;
370
+ ui.spacing_mut().icon_width_inner = (min_size.x * (8.0 / 14.0)).clamp(4.0, min_size.x.max(4.0));
371
+ ui.spacing_mut().interact_size.x = ui.spacing().interact_size.x.max(min_size.x);
372
+ }
373
+ if min_size.y > 0.0 {
374
+ ui.spacing_mut().interact_size.y = ui.spacing().interact_size.y.max(min_size.y);
375
+ }
376
+
377
+ ui.vertical(|ui| {
378
+ for option in &options {
379
+ let mut checked = values.iter().any(|value| value == &option.value);
380
+ let response = ui.add_enabled(!disabled && !option.disabled, egui::Checkbox::new(&mut checked, &option.label));
381
+ if response.changed() {
382
+ changed = true;
383
+ if checked {
384
+ if !values.iter().any(|value| value == &option.value) {
385
+ values.push(option.value.clone());
386
+ }
387
+ } else {
388
+ values.retain(|value| value != &option.value);
389
+ }
390
+ }
391
+ }
392
+ });
393
+ });
394
+
395
+ if changed {
396
+ values.sort();
397
+ if let Some(binding) = binding {
398
+ self.state_values.insert(binding.id, DesktopStateValue::StringArray(values.clone()));
399
+ } else {
400
+ self.local_multi_picker_values.insert(path.to_string(), values.clone());
401
+ }
402
+
403
+ self.dispatch_control_events(
404
+ ctx,
405
+ node,
406
+ DesktopControlEventData {
407
+ values: Some(values),
408
+ ..Default::default()
409
+ },
410
+ );
411
+ }
412
+
413
+ return;
414
+ }
415
+
416
+ let mut selected_value = if let Some(binding) = &binding {
417
+ match self.state_values.get(&binding.id) {
418
+ Some(DesktopStateValue::String(value)) => value.clone(),
419
+ Some(DesktopStateValue::Number(value)) => format_number(*value),
420
+ _ => String::new(),
421
+ }
422
+ } else {
423
+ self.local_picker_values
424
+ .get(path)
425
+ .cloned()
426
+ .or_else(|| node.props.get("value").and_then(value_as_string))
427
+ .or_else(|| options.iter().find(|option| option.selected).map(|option| option.value.clone()))
428
+ .unwrap_or_default()
429
+ };
430
+
431
+ let selected_label = options
432
+ .iter()
433
+ .find(|option| option.value == selected_value)
434
+ .map(|option| option.label.clone())
435
+ .unwrap_or_else(|| {
436
+ if selected_value.is_empty() {
437
+ String::from("Select")
438
+ } else {
439
+ selected_value.clone()
440
+ }
441
+ });
442
+ let previous_value = selected_value.clone();
443
+ let mut base_state = self.base_pseudo_state(node);
444
+ base_state.selected = !selected_value.is_empty();
445
+ let state_styles = self.resolve_widget_state_styles(node, base_state);
446
+ let inactive_style = state_styles.inactive.as_ref();
447
+ let desired_width = self.resolve_widget_width_from_style(inactive_style, ui.available_width().max(1.0));
448
+ let min_size = self.resolve_widget_min_size(inactive_style);
449
+
450
+ ui.scope(|ui| {
451
+ self.apply_widget_state_visuals(
452
+ ui,
453
+ inactive_style,
454
+ state_styles.hovered.as_ref(),
455
+ state_styles.active.as_ref(),
456
+ state_styles.focus.as_ref(),
457
+ state_styles.disabled.as_ref(),
458
+ );
459
+
460
+ if let Some(desired_width) = desired_width {
461
+ ui.spacing_mut().interact_size.x = ui.spacing().interact_size.x.max(desired_width);
462
+ }
463
+ if min_size.y > 0.0 {
464
+ ui.spacing_mut().interact_size.y = ui.spacing().interact_size.y.max(min_size.y);
465
+ }
466
+
467
+ ui.add_enabled_ui(!disabled, |ui| {
468
+ let mut combo_box = egui::ComboBox::from_id_salt(path).selected_text(
469
+ self.resolve_text_style_from_style(
470
+ ui,
471
+ if disabled {
472
+ state_styles.disabled.as_ref().or(inactive_style)
473
+ } else {
474
+ inactive_style
475
+ },
476
+ selected_label.clone(),
477
+ false,
478
+ ),
479
+ );
480
+
481
+ if let Some(desired_width) = desired_width {
482
+ combo_box = combo_box.width(desired_width);
483
+ }
484
+
485
+ combo_box.show_ui(ui, |ui| {
486
+ for option in &options {
487
+ ui.add_enabled_ui(!option.disabled, |ui| {
488
+ ui.selectable_value(&mut selected_value, option.value.clone(), &option.label);
489
+ });
490
+ }
491
+ });
492
+ });
493
+ });
494
+
495
+ if selected_value != previous_value {
496
+ if let Some(binding) = binding {
497
+ let next_value = match binding.value_type.as_str() {
498
+ "number" => DesktopStateValue::Number(selected_value.parse::<f64>().unwrap_or_default()),
499
+ _ => DesktopStateValue::String(selected_value.clone()),
500
+ };
501
+ self.state_values.insert(binding.id, next_value);
502
+ } else {
503
+ self.local_picker_values.insert(path.to_string(), selected_value.clone());
504
+ }
505
+
506
+ self.dispatch_control_events(
507
+ ctx,
508
+ node,
509
+ DesktopControlEventData {
510
+ value: Some(selected_value),
511
+ ..Default::default()
512
+ },
513
+ );
514
+ }
515
+ }
516
+ }
@@ -0,0 +1,89 @@
1
+ use eframe::egui;
2
+ use serde_json::{Map, Value};
3
+
4
+ use super::utilities::{is_external_destination, normalize_jsonish_value, resolve_interaction, resolve_route_from_payload, write_interaction_output};
5
+ use super::{DesktopInteraction, DesktopNativeApp, NativeElementNode};
6
+
7
+ impl DesktopNativeApp {
8
+ pub(super) fn record_interaction(&mut self, interaction: DesktopInteraction) {
9
+ let interaction_json = serde_json::to_string(&interaction).unwrap_or_else(|_| String::from("{}"));
10
+
11
+ if self.interaction_output.stdout {
12
+ println!("ELIT_NATIVE_INTERACTION {interaction_json}");
13
+ }
14
+
15
+ if let Err(error) = write_interaction_output(self.interaction_output.file.as_deref(), &interaction_json) {
16
+ eprintln!("failed to write desktop native interaction output: {error}");
17
+ }
18
+
19
+ self.last_interaction = Some(interaction);
20
+ }
21
+
22
+ pub(super) fn dispatch_interaction(&mut self, ctx: &egui::Context, interaction: DesktopInteraction) {
23
+ self.record_interaction(interaction.clone());
24
+
25
+ match interaction.action.as_deref() {
26
+ Some("desktop:quit") => {
27
+ ctx.send_viewport_cmd(egui::ViewportCommand::Close);
28
+ return;
29
+ }
30
+ Some("desktop:ping") => {
31
+ ctx.send_viewport_cmd(egui::ViewportCommand::Title(format!("{} - IPC OK", self.base_title)));
32
+ return;
33
+ }
34
+ Some("desktop:back") => {
35
+ self.navigation.go_back();
36
+ self.apply_navigation_title(ctx);
37
+ return;
38
+ }
39
+ Some("desktop:forward") => {
40
+ self.navigation.go_forward();
41
+ self.apply_navigation_title(ctx);
42
+ return;
43
+ }
44
+ Some("desktop:clear-route") => {
45
+ self.navigation.clear();
46
+ self.apply_navigation_title(ctx);
47
+ return;
48
+ }
49
+ _ => {}
50
+ }
51
+
52
+ let internal_route = interaction
53
+ .route
54
+ .as_deref()
55
+ .filter(|route| !is_external_destination(route))
56
+ .map(str::to_string)
57
+ .or_else(|| {
58
+ (interaction.action.as_deref() == Some("desktop:navigate"))
59
+ .then(|| resolve_route_from_payload(interaction.payload.as_ref()))
60
+ .flatten()
61
+ });
62
+
63
+ if let Some(route) = internal_route {
64
+ if self.navigation.navigate_to(route) {
65
+ self.apply_navigation_title(ctx);
66
+ }
67
+ return;
68
+ }
69
+
70
+ if let Some(route) = interaction.route.as_deref() {
71
+ if is_external_destination(route) {
72
+ let _ = webbrowser::open(route);
73
+ }
74
+ }
75
+ }
76
+
77
+ pub(super) fn dispatch_press_event(&mut self, ctx: &egui::Context, node: &NativeElementNode) {
78
+ let mut payload = Map::new();
79
+ payload.insert(String::from("event"), Value::String(String::from("press")));
80
+ payload.insert(String::from("sourceTag"), Value::String(node.source_tag.clone()));
81
+ if let Some(detail) = node.props.get("nativePayload") {
82
+ payload.insert(String::from("detail"), normalize_jsonish_value(detail.clone()));
83
+ }
84
+
85
+ if let Some(interaction) = resolve_interaction(node, Some(String::from("elit.event.press")), Some(Value::Object(payload))) {
86
+ self.dispatch_interaction(ctx, interaction);
87
+ }
88
+ }
89
+ }