elit 3.5.6 → 3.5.7
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 +1 -1
- package/desktop/build.rs +83 -0
- package/desktop/icon.rs +106 -0
- package/desktop/lib.rs +2 -0
- package/desktop/main.rs +235 -0
- package/desktop/native_main.rs +128 -0
- package/desktop/native_renderer/action_widgets.rs +184 -0
- package/desktop/native_renderer/app_models.rs +171 -0
- package/desktop/native_renderer/app_runtime.rs +140 -0
- package/desktop/native_renderer/container_rendering.rs +610 -0
- package/desktop/native_renderer/content_widgets.rs +634 -0
- package/desktop/native_renderer/css_models.rs +371 -0
- package/desktop/native_renderer/embedded_surfaces.rs +414 -0
- package/desktop/native_renderer/form_controls.rs +516 -0
- package/desktop/native_renderer/interaction_dispatch.rs +89 -0
- package/desktop/native_renderer/runtime_support.rs +135 -0
- package/desktop/native_renderer/utilities.rs +495 -0
- package/desktop/native_renderer/vector_drawing.rs +491 -0
- package/desktop/native_renderer.rs +4122 -0
- package/desktop/runtime/external.rs +422 -0
- package/desktop/runtime/mod.rs +67 -0
- package/desktop/runtime/quickjs.rs +106 -0
- package/desktop/window.rs +383 -0
- package/package.json +6 -3
- package/dist/build.d.mts +0 -20
- package/dist/chokidar.d.mts +0 -134
- package/dist/cli.d.mts +0 -81
- package/dist/config.d.mts +0 -254
- package/dist/coverage.d.mts +0 -85
- package/dist/database.d.mts +0 -52
- package/dist/desktop.d.mts +0 -68
- package/dist/dom.d.mts +0 -87
- package/dist/el.d.mts +0 -208
- package/dist/fs.d.mts +0 -255
- package/dist/hmr.d.mts +0 -38
- package/dist/http.d.mts +0 -169
- package/dist/https.d.mts +0 -108
- package/dist/index.d.mts +0 -13
- package/dist/mime-types.d.mts +0 -48
- package/dist/native.d.mts +0 -136
- package/dist/path.d.mts +0 -163
- package/dist/router.d.mts +0 -49
- package/dist/runtime.d.mts +0 -97
- package/dist/server-D0Dp4R5z.d.mts +0 -449
- package/dist/server.d.mts +0 -7
- package/dist/state.d.mts +0 -117
- package/dist/style.d.mts +0 -232
- package/dist/test-reporter.d.mts +0 -77
- package/dist/test-runtime.d.mts +0 -122
- package/dist/test.d.mts +0 -39
- package/dist/types.d.mts +0 -586
- package/dist/universal.d.mts +0 -21
- package/dist/ws.d.mts +0 -200
- package/dist/wss.d.mts +0 -108
- package/src/build.ts +0 -362
- package/src/chokidar.ts +0 -427
- package/src/cli.ts +0 -1162
- package/src/config.ts +0 -509
- package/src/coverage.ts +0 -1479
- package/src/database.ts +0 -1410
- package/src/desktop-auto-render.ts +0 -317
- package/src/desktop-cli.ts +0 -1533
- package/src/desktop.ts +0 -99
- package/src/dev-build.ts +0 -340
- package/src/dom.ts +0 -901
- package/src/el.ts +0 -183
- package/src/fs.ts +0 -609
- package/src/hmr.ts +0 -149
- package/src/http.ts +0 -856
- package/src/https.ts +0 -411
- package/src/index.ts +0 -16
- package/src/mime-types.ts +0 -222
- package/src/mobile-cli.ts +0 -2313
- package/src/native-background.ts +0 -444
- package/src/native-border.ts +0 -343
- package/src/native-canvas.ts +0 -260
- package/src/native-cli.ts +0 -414
- package/src/native-color.ts +0 -904
- package/src/native-estimation.ts +0 -194
- package/src/native-grid.ts +0 -590
- package/src/native-interaction.ts +0 -1289
- package/src/native-layout.ts +0 -568
- package/src/native-link.ts +0 -76
- package/src/native-render-support.ts +0 -361
- package/src/native-spacing.ts +0 -231
- package/src/native-state.ts +0 -318
- package/src/native-strings.ts +0 -46
- package/src/native-transform.ts +0 -120
- package/src/native-types.ts +0 -439
- package/src/native-typography.ts +0 -254
- package/src/native-units.ts +0 -441
- package/src/native-vector.ts +0 -910
- package/src/native.ts +0 -5606
- package/src/path.ts +0 -493
- package/src/pm-cli.ts +0 -2498
- package/src/preview-build.ts +0 -294
- package/src/render-context.ts +0 -138
- package/src/router.ts +0 -260
- package/src/runtime.ts +0 -97
- package/src/server.ts +0 -2294
- package/src/state.ts +0 -556
- package/src/style.ts +0 -1790
- package/src/test-globals.d.ts +0 -184
- package/src/test-reporter.ts +0 -609
- package/src/test-runtime.ts +0 -1359
- package/src/test.ts +0 -368
- package/src/types.ts +0 -381
- package/src/universal.ts +0 -81
- package/src/wapk-cli.ts +0 -3213
- package/src/workspace-package.ts +0 -102
- package/src/ws.ts +0 -648
- package/src/wss.ts +0 -241
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
use eframe::egui::{self, Vec2};
|
|
2
|
+
use serde_json::Value;
|
|
3
|
+
|
|
4
|
+
use super::utilities::{is_external_destination, resolve_interaction};
|
|
5
|
+
use super::{DesktopNativeApp, NativeElementNode, NativeNode};
|
|
6
|
+
|
|
7
|
+
impl DesktopNativeApp {
|
|
8
|
+
pub(super) fn render_button(&mut self, ui: &mut egui::Ui, ctx: &egui::Context, node: &NativeElementNode) {
|
|
9
|
+
let label = self.collect_text_content(&NativeNode::Element(node.clone()));
|
|
10
|
+
let disabled = self.is_disabled(node);
|
|
11
|
+
let base_state = self.base_pseudo_state(node);
|
|
12
|
+
let inactive_style = self.resolve_style_map_with_state(node, base_state);
|
|
13
|
+
|
|
14
|
+
let mut hovered_state = base_state;
|
|
15
|
+
hovered_state.hovered = true;
|
|
16
|
+
let hovered_style = self.resolve_style_map_with_state(node, hovered_state);
|
|
17
|
+
|
|
18
|
+
let mut active_state = hovered_state;
|
|
19
|
+
active_state.active = true;
|
|
20
|
+
let active_style = self.resolve_style_map_with_state(node, active_state);
|
|
21
|
+
|
|
22
|
+
let mut focus_state = base_state;
|
|
23
|
+
focus_state.focused = true;
|
|
24
|
+
focus_state.focus_within = true;
|
|
25
|
+
let focus_style = self.resolve_style_map_with_state(node, focus_state);
|
|
26
|
+
|
|
27
|
+
let mut disabled_state = base_state;
|
|
28
|
+
disabled_state.enabled = false;
|
|
29
|
+
disabled_state.disabled = true;
|
|
30
|
+
let disabled_style = self.resolve_style_map_with_state(node, disabled_state);
|
|
31
|
+
let inactive_gradient = Self::resolve_background_gradient_from_style(inactive_style.as_ref());
|
|
32
|
+
let hovered_gradient = Self::resolve_background_gradient_from_style(hovered_style.as_ref());
|
|
33
|
+
let active_gradient = Self::resolve_background_gradient_from_style(active_style.as_ref());
|
|
34
|
+
let focus_gradient = Self::resolve_background_gradient_from_style(focus_style.as_ref());
|
|
35
|
+
let disabled_gradient = Self::resolve_background_gradient_from_style(disabled_style.as_ref());
|
|
36
|
+
let gradient_shape_idx = if inactive_gradient.is_some() || hovered_gradient.is_some() || active_gradient.is_some() || focus_gradient.is_some() || disabled_gradient.is_some() {
|
|
37
|
+
Some(ui.painter().add(egui::Shape::Noop))
|
|
38
|
+
} else {
|
|
39
|
+
None
|
|
40
|
+
};
|
|
41
|
+
let button_corner_radius = Self::resolve_corner_radius_from_style(inactive_style.as_ref()).unwrap_or(ui.style().visuals.widgets.inactive.corner_radius);
|
|
42
|
+
|
|
43
|
+
let response = ui
|
|
44
|
+
.scope(|ui| {
|
|
45
|
+
self.apply_widget_state_visuals(
|
|
46
|
+
ui,
|
|
47
|
+
inactive_style.as_ref(),
|
|
48
|
+
hovered_style.as_ref(),
|
|
49
|
+
active_style.as_ref(),
|
|
50
|
+
focus_style.as_ref(),
|
|
51
|
+
disabled_style.as_ref(),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if let Some(padding) = Self::resolve_box_edges(inactive_style.as_ref(), "padding") {
|
|
55
|
+
ui.spacing_mut().button_padding = egui::vec2(padding.average_horizontal(), padding.average_vertical());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let min_size = self.resolve_widget_min_size(inactive_style.as_ref());
|
|
59
|
+
let mut button = egui::Button::new(self.resolve_text_style_from_style(ui, inactive_style.as_ref(), label.clone(), false)).frame(true);
|
|
60
|
+
if min_size != Vec2::ZERO {
|
|
61
|
+
button = button.min_size(min_size);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
ui.add_enabled(!disabled, button)
|
|
65
|
+
})
|
|
66
|
+
.inner;
|
|
67
|
+
|
|
68
|
+
if let Some(shape_idx) = gradient_shape_idx {
|
|
69
|
+
let active_gradient = if disabled {
|
|
70
|
+
disabled_gradient.or(inactive_gradient)
|
|
71
|
+
} else if response.is_pointer_button_down_on() {
|
|
72
|
+
active_gradient.or(hovered_gradient).or(focus_gradient).or(inactive_gradient)
|
|
73
|
+
} else if response.hovered() {
|
|
74
|
+
hovered_gradient.or(focus_gradient).or(inactive_gradient)
|
|
75
|
+
} else if response.has_focus() {
|
|
76
|
+
focus_gradient.or(inactive_gradient)
|
|
77
|
+
} else {
|
|
78
|
+
inactive_gradient
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
if let Some(gradient) = active_gradient {
|
|
82
|
+
ui.painter().set(
|
|
83
|
+
shape_idx,
|
|
84
|
+
Self::gradient_shape_for_rect(response.rect, button_corner_radius, &gradient),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if response.clicked() {
|
|
90
|
+
if let Some(interaction) = resolve_interaction(node, None, None) {
|
|
91
|
+
self.dispatch_interaction(ctx, interaction);
|
|
92
|
+
} else if node.events.iter().any(|event| event == "press") {
|
|
93
|
+
self.dispatch_press_event(ctx, node);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
pub(super) fn render_link(&mut self, ui: &mut egui::Ui, ctx: &egui::Context, node: &NativeElementNode) {
|
|
99
|
+
let label = self.collect_text_content(&NativeNode::Element(node.clone()));
|
|
100
|
+
let base_state = self.base_pseudo_state(node);
|
|
101
|
+
let inactive_style = self.resolve_style_map_with_state(node, base_state);
|
|
102
|
+
|
|
103
|
+
let mut hovered_state = base_state;
|
|
104
|
+
hovered_state.hovered = true;
|
|
105
|
+
let hovered_style = self.resolve_style_map_with_state(node, hovered_state);
|
|
106
|
+
|
|
107
|
+
let mut active_state = hovered_state;
|
|
108
|
+
active_state.active = true;
|
|
109
|
+
let active_style = self.resolve_style_map_with_state(node, active_state);
|
|
110
|
+
|
|
111
|
+
let mut focus_state = base_state;
|
|
112
|
+
focus_state.focused = true;
|
|
113
|
+
focus_state.focus_within = true;
|
|
114
|
+
let focus_style = self.resolve_style_map_with_state(node, focus_state);
|
|
115
|
+
|
|
116
|
+
let has_frame = Self::style_has_widget_frame(inactive_style.as_ref());
|
|
117
|
+
let inactive_gradient = Self::resolve_background_gradient_from_style(inactive_style.as_ref());
|
|
118
|
+
let hovered_gradient = Self::resolve_background_gradient_from_style(hovered_style.as_ref());
|
|
119
|
+
let active_gradient = Self::resolve_background_gradient_from_style(active_style.as_ref());
|
|
120
|
+
let focus_gradient = Self::resolve_background_gradient_from_style(focus_style.as_ref());
|
|
121
|
+
let gradient_shape_idx = if inactive_gradient.is_some() || hovered_gradient.is_some() || active_gradient.is_some() || focus_gradient.is_some() {
|
|
122
|
+
Some(ui.painter().add(egui::Shape::Noop))
|
|
123
|
+
} else {
|
|
124
|
+
None
|
|
125
|
+
};
|
|
126
|
+
let link_corner_radius = Self::resolve_corner_radius_from_style(inactive_style.as_ref()).unwrap_or(ui.style().visuals.widgets.inactive.corner_radius);
|
|
127
|
+
let destination = node.props.get("destination").and_then(Value::as_str).map(str::trim).unwrap_or("");
|
|
128
|
+
let response = ui
|
|
129
|
+
.scope(|ui| {
|
|
130
|
+
self.apply_widget_state_visuals(
|
|
131
|
+
ui,
|
|
132
|
+
inactive_style.as_ref(),
|
|
133
|
+
hovered_style.as_ref(),
|
|
134
|
+
active_style.as_ref(),
|
|
135
|
+
focus_style.as_ref(),
|
|
136
|
+
None,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
if has_frame {
|
|
140
|
+
if let Some(padding) = Self::resolve_box_edges(inactive_style.as_ref(), "padding") {
|
|
141
|
+
ui.spacing_mut().button_padding = egui::vec2(padding.average_horizontal(), padding.average_vertical());
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let min_size = self.resolve_widget_min_size(inactive_style.as_ref());
|
|
146
|
+
let mut link = egui::Button::new(self.resolve_text_style_from_style(ui, inactive_style.as_ref(), label.clone(), false)).frame(has_frame);
|
|
147
|
+
if min_size != Vec2::ZERO {
|
|
148
|
+
link = link.min_size(min_size);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
ui.add(link)
|
|
152
|
+
})
|
|
153
|
+
.inner;
|
|
154
|
+
|
|
155
|
+
if let Some(shape_idx) = gradient_shape_idx {
|
|
156
|
+
let active_gradient = if response.is_pointer_button_down_on() {
|
|
157
|
+
active_gradient.or(hovered_gradient).or(focus_gradient).or(inactive_gradient)
|
|
158
|
+
} else if response.hovered() {
|
|
159
|
+
hovered_gradient.or(focus_gradient).or(inactive_gradient)
|
|
160
|
+
} else if response.has_focus() {
|
|
161
|
+
focus_gradient.or(inactive_gradient)
|
|
162
|
+
} else {
|
|
163
|
+
inactive_gradient
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
if let Some(gradient) = active_gradient {
|
|
167
|
+
ui.painter().set(
|
|
168
|
+
shape_idx,
|
|
169
|
+
Self::gradient_shape_for_rect(response.rect, link_corner_radius, &gradient),
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if response.clicked() {
|
|
175
|
+
if !destination.is_empty() && is_external_destination(destination) {
|
|
176
|
+
let _ = webbrowser::open(destination);
|
|
177
|
+
} else if let Some(interaction) = resolve_interaction(node, None, None) {
|
|
178
|
+
self.dispatch_interaction(ctx, interaction);
|
|
179
|
+
} else if node.events.iter().any(|event| event == "press") {
|
|
180
|
+
self.dispatch_press_event(ctx, node);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
use std::path::PathBuf;
|
|
2
|
+
|
|
3
|
+
use eframe::egui::Rect;
|
|
4
|
+
use serde::Serialize;
|
|
5
|
+
use serde_json::Value;
|
|
6
|
+
#[cfg(target_os = "windows")]
|
|
7
|
+
use wry::WebView;
|
|
8
|
+
|
|
9
|
+
#[derive(Debug, Clone, PartialEq, Serialize)]
|
|
10
|
+
pub(crate) struct DesktopInteraction {
|
|
11
|
+
pub(crate) action: Option<String>,
|
|
12
|
+
pub(crate) route: Option<String>,
|
|
13
|
+
pub(crate) payload: Option<Value>,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
impl DesktopInteraction {
|
|
17
|
+
pub(crate) fn is_empty(&self) -> bool {
|
|
18
|
+
self.action.is_none() && self.route.is_none() && self.payload.is_none()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
pub(crate) fn summary(&self) -> String {
|
|
22
|
+
let mut parts = Vec::new();
|
|
23
|
+
if let Some(action) = &self.action {
|
|
24
|
+
parts.push(format!("action={action}"));
|
|
25
|
+
}
|
|
26
|
+
if let Some(route) = &self.route {
|
|
27
|
+
parts.push(format!("route={route}"));
|
|
28
|
+
}
|
|
29
|
+
if let Some(payload) = &self.payload {
|
|
30
|
+
parts.push(format!("payload={payload}"));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if parts.is_empty() {
|
|
34
|
+
String::from("idle")
|
|
35
|
+
} else {
|
|
36
|
+
parts.join(" | ")
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#[derive(Debug, Clone, Default)]
|
|
42
|
+
pub(crate) struct DesktopControlEventData {
|
|
43
|
+
pub(crate) value: Option<String>,
|
|
44
|
+
pub(crate) values: Option<Vec<String>>,
|
|
45
|
+
pub(crate) checked: Option<bool>,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
49
|
+
pub(crate) struct PickerOptionData {
|
|
50
|
+
pub(crate) label: String,
|
|
51
|
+
pub(crate) value: String,
|
|
52
|
+
pub(crate) disabled: bool,
|
|
53
|
+
pub(crate) selected: bool,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#[derive(Debug, Clone, Default)]
|
|
57
|
+
pub(crate) struct ResolvedDesktopInteractionOutput {
|
|
58
|
+
pub(crate) file: Option<PathBuf>,
|
|
59
|
+
pub(crate) stdout: bool,
|
|
60
|
+
pub(crate) emit_ready: bool,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#[derive(Debug, Clone, Default)]
|
|
64
|
+
pub(crate) struct DesktopNavigationState {
|
|
65
|
+
pub(crate) history: Vec<String>,
|
|
66
|
+
pub(crate) index: Option<usize>,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
impl DesktopNavigationState {
|
|
70
|
+
pub(crate) fn current_route(&self) -> Option<&str> {
|
|
71
|
+
self.index.and_then(|index| self.history.get(index)).map(String::as_str)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
pub(crate) fn can_go_back(&self) -> bool {
|
|
75
|
+
self.index.is_some()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
pub(crate) fn can_go_forward(&self) -> bool {
|
|
79
|
+
match self.index {
|
|
80
|
+
Some(index) => index + 1 < self.history.len(),
|
|
81
|
+
None => !self.history.is_empty(),
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
pub(crate) fn navigate_to(&mut self, route: impl Into<String>) -> bool {
|
|
86
|
+
let route = route.into().trim().to_string();
|
|
87
|
+
if route.is_empty() {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if let Some(index) = self.index {
|
|
92
|
+
if index + 1 < self.history.len() {
|
|
93
|
+
self.history.truncate(index + 1);
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
self.history.clear();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
self.history.push(route);
|
|
100
|
+
self.index = Some(self.history.len() - 1);
|
|
101
|
+
true
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
pub(crate) fn go_back(&mut self) -> bool {
|
|
105
|
+
match self.index {
|
|
106
|
+
Some(index) if index > 0 => {
|
|
107
|
+
self.index = Some(index - 1);
|
|
108
|
+
true
|
|
109
|
+
}
|
|
110
|
+
Some(_) => {
|
|
111
|
+
self.index = None;
|
|
112
|
+
true
|
|
113
|
+
}
|
|
114
|
+
None => false,
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
pub(crate) fn go_forward(&mut self) -> bool {
|
|
119
|
+
match self.index {
|
|
120
|
+
Some(index) if index + 1 < self.history.len() => {
|
|
121
|
+
self.index = Some(index + 1);
|
|
122
|
+
true
|
|
123
|
+
}
|
|
124
|
+
None if !self.history.is_empty() => {
|
|
125
|
+
self.index = Some(0);
|
|
126
|
+
true
|
|
127
|
+
}
|
|
128
|
+
_ => false,
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
pub(crate) fn clear(&mut self) {
|
|
133
|
+
self.index = None;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
138
|
+
pub(crate) enum DesktopSurfaceWindowKind {
|
|
139
|
+
WebView,
|
|
140
|
+
Video,
|
|
141
|
+
Audio,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
145
|
+
pub(crate) enum DesktopEmbeddedSurfaceVisibility {
|
|
146
|
+
Hidden,
|
|
147
|
+
Clipped,
|
|
148
|
+
Visible,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
152
|
+
pub(crate) enum DesktopEmbeddedSurfaceContent {
|
|
153
|
+
Url(String),
|
|
154
|
+
Html(String),
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
#[derive(Clone, Debug)]
|
|
158
|
+
pub(crate) struct DesktopEmbeddedSurfaceRequest {
|
|
159
|
+
pub(crate) kind: DesktopSurfaceWindowKind,
|
|
160
|
+
pub(crate) title: String,
|
|
161
|
+
pub(crate) rect: Rect,
|
|
162
|
+
pub(crate) content: DesktopEmbeddedSurfaceContent,
|
|
163
|
+
pub(crate) focus_requested: bool,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#[cfg(target_os = "windows")]
|
|
167
|
+
pub(crate) struct DesktopEmbeddedSurface {
|
|
168
|
+
pub(crate) kind: DesktopSurfaceWindowKind,
|
|
169
|
+
pub(crate) content: DesktopEmbeddedSurfaceContent,
|
|
170
|
+
pub(crate) webview: WebView,
|
|
171
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
use eframe::egui;
|
|
2
|
+
use serde_json::Value;
|
|
3
|
+
|
|
4
|
+
use super::{DesktopNativeApp, NativeElementNode, NativeNode};
|
|
5
|
+
|
|
6
|
+
impl DesktopNativeApp {
|
|
7
|
+
pub(super) fn resolve_prop_number(&self, node: &NativeElementNode, key: &str) -> Option<f32> {
|
|
8
|
+
Self::parse_css_number(node.props.get(key))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
pub(super) fn resolve_prop_string<'a>(&self, node: &'a NativeElementNode, key: &str) -> Option<&'a str> {
|
|
12
|
+
node.props.get(key).and_then(Value::as_str).map(str::trim).filter(|value| !value.is_empty())
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
pub(super) fn resolve_label(&self, node: &NativeElementNode) -> Option<String> {
|
|
16
|
+
["aria-label", "label", "title", "name", "alt"]
|
|
17
|
+
.iter()
|
|
18
|
+
.find_map(|key| self.resolve_prop_string(node, key).map(str::to_string))
|
|
19
|
+
.or_else(|| {
|
|
20
|
+
let text = self.collect_text_content(&NativeNode::Element(node.clone()));
|
|
21
|
+
let trimmed = text.trim();
|
|
22
|
+
(!trimmed.is_empty()).then_some(trimmed.to_string())
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pub(super) fn is_disabled(&self, node: &NativeElementNode) -> bool {
|
|
27
|
+
super::parse_native_bool(node.props.get("disabled")) || super::parse_native_bool(node.props.get("aria-disabled"))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pub(super) fn is_read_only(&self, node: &NativeElementNode) -> bool {
|
|
31
|
+
super::parse_native_bool(node.props.get("readOnly")) || super::parse_native_bool(node.props.get("readonly"))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
pub(super) fn has_focusable_tab_index(&self, node: &NativeElementNode) -> bool {
|
|
35
|
+
node.props
|
|
36
|
+
.get("tabIndex")
|
|
37
|
+
.or_else(|| node.props.get("tabindex"))
|
|
38
|
+
.and_then(|value| match value {
|
|
39
|
+
Value::Number(number) => number.as_i64(),
|
|
40
|
+
Value::String(text) => text.trim().parse::<i64>().ok(),
|
|
41
|
+
_ => None,
|
|
42
|
+
})
|
|
43
|
+
.map(|value| value >= 0)
|
|
44
|
+
.unwrap_or(false)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
pub(super) fn render_node(&mut self, ui: &mut egui::Ui, ctx: &egui::Context, node: &NativeNode, path: &str) {
|
|
48
|
+
match node {
|
|
49
|
+
NativeNode::Text(text_node) => {
|
|
50
|
+
ui.label(self.resolve_text_node_value(text_node));
|
|
51
|
+
}
|
|
52
|
+
NativeNode::Element(element_node) => match element_node.component.as_str() {
|
|
53
|
+
"Text" => self.render_text_element(ui, element_node),
|
|
54
|
+
"Button" => self.render_button(ui, ctx, element_node),
|
|
55
|
+
"Link" => self.render_link(ui, ctx, element_node),
|
|
56
|
+
"TextInput" => self.render_text_input(ui, ctx, element_node, path),
|
|
57
|
+
"Toggle" => self.render_toggle(ui, ctx, element_node, path),
|
|
58
|
+
"Slider" => self.render_slider(ui, ctx, element_node, path),
|
|
59
|
+
"Picker" => self.render_picker(ui, ctx, element_node, path),
|
|
60
|
+
"Option" => self.render_text_element(ui, element_node),
|
|
61
|
+
"Divider" => self.render_divider(ui),
|
|
62
|
+
"Progress" => self.render_progress(ui, element_node),
|
|
63
|
+
"Image" => self.render_image(ui, ctx, element_node),
|
|
64
|
+
"WebView" => self.render_web_view(ui, element_node, path),
|
|
65
|
+
"Media" => self.render_media(ui, ctx, element_node, path),
|
|
66
|
+
"Canvas" => self.render_canvas(ui, element_node),
|
|
67
|
+
"Vector" => self.render_vector(ui, element_node),
|
|
68
|
+
"Math" => self.render_math(ui, element_node),
|
|
69
|
+
"Table" => self.render_table(ui, ctx, element_node, path),
|
|
70
|
+
"Row" => self.render_row(ui, ctx, element_node, path),
|
|
71
|
+
"Cell" => self.render_cell(ui, ctx, element_node, path),
|
|
72
|
+
"ListItem" => self.render_list_item(ui, ctx, element_node, path),
|
|
73
|
+
"List" => self.render_list(ui, ctx, element_node, path),
|
|
74
|
+
"Screen" | "View" => self.render_container(ui, ctx, element_node, path),
|
|
75
|
+
_ => self.render_surface_placeholder(ui, &element_node.component, "Desktop native fallback surface"),
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn render_navigation_bar(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
|
81
|
+
if self.navigation.current_route().is_none() && !self.navigation.can_go_forward() {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
ui.horizontal(|ui| {
|
|
86
|
+
if ui.add_enabled(self.navigation.can_go_back(), egui::Button::new("Back")).clicked() {
|
|
87
|
+
self.navigation.go_back();
|
|
88
|
+
self.apply_navigation_title(ctx);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if ui.add_enabled(self.navigation.can_go_forward(), egui::Button::new("Forward")).clicked() {
|
|
92
|
+
self.navigation.go_forward();
|
|
93
|
+
self.apply_navigation_title(ctx);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if ui.add_enabled(self.navigation.current_route().is_some(), egui::Button::new("Reset")).clicked() {
|
|
97
|
+
self.navigation.clear();
|
|
98
|
+
self.apply_navigation_title(ctx);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
ui.small(self.navigation.current_route().unwrap_or("/"));
|
|
102
|
+
});
|
|
103
|
+
ui.separator();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fn render_last_interaction_summary(&self, ui: &mut egui::Ui) {
|
|
107
|
+
if let Some(interaction) = &self.last_interaction {
|
|
108
|
+
ui.separator();
|
|
109
|
+
ui.small(format!("Last interaction: {}", interaction.summary()));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
impl eframe::App for DesktopNativeApp {
|
|
115
|
+
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
|
116
|
+
let roots = self.payload.tree.roots.clone();
|
|
117
|
+
self.viewport_size = ctx.screen_rect().size();
|
|
118
|
+
self.ensure_fonts_loaded(ctx);
|
|
119
|
+
self.emit_ready_interaction();
|
|
120
|
+
|
|
121
|
+
egui::CentralPanel::default().show(ctx, |ui| {
|
|
122
|
+
egui::ScrollArea::vertical().auto_shrink([false, false]).show(ui, |ui| {
|
|
123
|
+
self.render_navigation_bar(ui, ctx);
|
|
124
|
+
|
|
125
|
+
for (index, root) in roots.iter().enumerate() {
|
|
126
|
+
self.render_node(ui, ctx, root, &format!("root-{index}"));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
self.render_last_interaction_summary(ui);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
self.reconcile_embedded_surfaces(frame);
|
|
134
|
+
|
|
135
|
+
if self.pending_auto_close {
|
|
136
|
+
self.pending_auto_close = false;
|
|
137
|
+
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|