feat(shell-native): WidgetHostNative + inspector_window — cross-platform proof
Lands the shell-native consumer of shell-core's Step 1b widget
module so spec §1.4 is concrete: same widget code, same paint
output on macOS / Linux / Windows desktop AND
wasm32-unknown-unknown browsers. User priority for this commit
("主要是native 端") + the parallel Phase D web work.
What's added:
- `crates/openpencil-shell-native/src/widget_host.rs` (~155 LOC):
* `NativeFrameBackend<'a>` — frame-scoped wrapper holding
`(&mut NativeBackend, &skia_safe::Canvas)`, impls
shell-core's `RenderBackend` by forwarding to the existing
`NativeBackend::{fill_rect, stroke_rect, draw_text,
clip_rect, save, restore, translate}` methods (each takes
the canvas as a separate arg in the existing API).
`begin_frame`/`end_frame` no-op because `SharedSkiaContext::
with_frame` owns those bracket points; `resize` no-op because
surface resize lives on `SharedSkiaContext::resize`. Spec
§5.2.1 explicitly deferred this RenderBackend impl to Step
1c+ widget tree work — this is that landing site.
* `WidgetHostNative` — owns one of each B1/B2 widget
(TreeWidget::sample, PropertyRow::new(200, "Width", "960"),
Dropdown::sample, TextInput::sample). `paint(&self, frame,
available_width)` mirrors shell-web's `WidgetHost::paint`
exactly (16/12 px gaps, 280 px column) so the visual layout
is identical between platforms — Phase E manual smoke
acceptance criterion.
* `// glue:` markers for the (future) cross-crate widget-
boundary gate.
- `crates/openpencil-shell-native/examples/inspector_window.rs`
(~150 LOC) — winit + SharedSkiaContext + NativeBackend +
WidgetHostNative end-to-end. Same shape as `basic_window.rs`
but the per-frame paint dispatches to `WidgetHostNative`
instead of hard-coded chrome. cfg-gated to desktop OS; CI
verifies `cargo build --examples` only.
- `crates/openpencil-shell-native/src/lib.rs` — adds `pub mod
widget_host;` cfg-gated to desktop OS (matches the existing
`backend` / `canvas_view_stub` gating per spec §11). Re-exports
`NativeFrameBackend` + `WidgetHostNative` at the crate root.
Mobile (iOS / Android) considered (per 2026-05-10 user directive
"安卓和ios 不需要 ipc / 本地 cli — 只需要 custom provider"):
- The widget glue is platform-agnostic in shape — no winit /
glutin / EGL / desktop-only types leak in. `NativeFrameBackend`
only borrows `NativeBackend` + `&skia_safe::Canvas`;
`WidgetHostNative` only consumes shell-core widgets + the
`RenderBackend` trait. Both compile on any target where
`NativeBackend` compiles.
- Today the desktop-only cfg on `widget_host` mirrors the
desktop-only cfg on `backend` (per spec §11 invariants 1 & 3:
mobile widget rendering lands in Step 1f). When Step 1f ships
real `EaglProvider` (iOS) / `AndroidEglProvider` (Android)
impls and lifts the desktop cfg, `WidgetHostNative` follows
automatically — no rewrite, no IPC / CLI infrastructure.
- Doc comment in `widget_host.rs` + `inspector_window.rs`
explicitly documents this Step 1f path.
- Verified both iOS (`aarch64-apple-ios`) and Android
(`aarch64-linux-android`) cargo check still green with
shell-native's mobile compile guard in place.
Verification:
- `cargo build -p openpencil-shell-native --example
inspector_window` — green (desktop)
- `cargo check -p openpencil-shell-native` — green (no
regression on Step 1a basic_window)
- `cargo check -p openpencil-shell-native --target
aarch64-apple-ios` — green (mobile compile guard intact)
- `cargo check -p openpencil-shell-native --target
aarch64-linux-android` — green (mobile compile guard intact)
- `cargo check -p openpencil-shell-core --target
wasm32-unknown-unknown` — green (shell-core stays
wasm32-clean per spec §1.2)
- `cargo test -p openpencil-shell-core --test widgets_static` —
21/21 (no widget changes)
- `bash tools/check-wasm-bundle.sh` — PASS (web bundle still 0
env.* / 622 KiB gzip / 59% ceiling — no regression)
- `bash tools/check-widget-boundary.sh` — PASS
- `bash tools/check-jian-boundaries.sh` — 4/4 invariants PASS
Phase D (web DOM mirror + native accesskit_winit integration)
follows.
This commit is contained in:
parent
af66f3d849
commit
987ce82a24
198
crates/openpencil-shell-native/examples/inspector_window.rs
Normal file
198
crates/openpencil-shell-native/examples/inspector_window.rs
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
//! Step 1b §1.4 native inspector demo — paints the four shell-core
|
||||
//! widgets (Tree / PropertyRow / Dropdown / TextInput) through
|
||||
//! `WidgetHostNative` + `NativeFrameBackend`. Visually mirrors the
|
||||
//! shell-web `mount()` first frame so the cross-platform widget
|
||||
//! claim from spec §1.4 is concrete: same widget code, same paint
|
||||
//! output on macOS / Linux / Windows desktop and on
|
||||
//! wasm32-unknown-unknown browsers.
|
||||
//!
|
||||
//! ### Run (desktop)
|
||||
//! ```text
|
||||
//! cargo run -p openpencil-shell-native --example inspector_window
|
||||
//! ```
|
||||
//!
|
||||
//! ### Mobile (iOS / Android) — Step 1f
|
||||
//! This example is desktop-only (winit + `SharedSkiaContext::
|
||||
//! new_desktop`). Mobile shells will land their own runners using
|
||||
//! the platform `GlContextProvider` (`EaglProvider` on iOS,
|
||||
//! `AndroidEglProvider` on Android — both zero-sized placeholders
|
||||
//! in shell-native today; spec §11 + 2026-05-10 user directive
|
||||
//! "安卓和ios 不需要 ipc / 本地 cli — 只需要 custom provider").
|
||||
//! The widget glue (`WidgetHostNative` + `NativeFrameBackend`) is
|
||||
//! platform-agnostic; mobile runners reuse the same
|
||||
//! `host.paint(&mut frame, width)` once their provider ships real
|
||||
//! `make_current` / `swap_buffers` impls.
|
||||
//!
|
||||
//! The window should display a 280 px column on the left containing
|
||||
//! a 3-row tree (Frame / Title / Button — first row selected blue),
|
||||
//! a "Width 960" property row, a "Normal" dropdown, and a "Frame 1"
|
||||
//! text input. CI verifies `cargo build --examples` only — visual
|
||||
//! verification is the Phase E manual smoke responsibility.
|
||||
|
||||
#![cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
|
||||
|
||||
use openpencil_shell_core::{Color, Point2D, Rect, RenderBackend};
|
||||
use openpencil_shell_native::{
|
||||
NativeBackend, NativeFrameBackend, SharedSkiaContext, SharedSkiaError, WidgetHostNative,
|
||||
};
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::event::WindowEvent;
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
||||
use winit::window::{Window, WindowId};
|
||||
|
||||
const INSPECTOR_WIDTH: f32 = 280.0;
|
||||
|
||||
/// Paint pass — clear to white, then dispatch the inspector widgets
|
||||
/// through `WidgetHostNative`. Pulled into a free function so the
|
||||
/// initial `Resumed` paint and `RedrawRequested` redraws share the
|
||||
/// exact same draw list (same pattern as `basic_window`).
|
||||
fn paint_inspector(
|
||||
ctx: &mut SharedSkiaContext,
|
||||
backend: &mut NativeBackend,
|
||||
host: &WidgetHostNative,
|
||||
) {
|
||||
ctx.begin_frame();
|
||||
ctx.with_frame(|canvas, _glow| {
|
||||
// Clear via Skia first so the framebuffer alpha is reset
|
||||
// before widget paints sit on top.
|
||||
canvas.clear(skia_safe::Color::WHITE);
|
||||
|
||||
let mut frame = NativeFrameBackend::new(backend, canvas);
|
||||
// Belt-and-braces: also clear via the trait fill_rect so the
|
||||
// shell-web and shell-native paint orders match exactly. The
|
||||
// canvas.clear above is a no-op-equivalent prequel.
|
||||
frame.fill_rect(
|
||||
Rect {
|
||||
origin: Point2D::new(0.0, 0.0),
|
||||
size: Point2D::new(960.0, 640.0),
|
||||
},
|
||||
Color::WHITE,
|
||||
);
|
||||
host.paint(&mut frame, INSPECTOR_WIDTH);
|
||||
});
|
||||
ctx.present();
|
||||
}
|
||||
|
||||
struct InspectorApp {
|
||||
window: Option<Window>,
|
||||
ctx: Option<SharedSkiaContext>,
|
||||
backend: Option<NativeBackend>,
|
||||
host: WidgetHostNative,
|
||||
error: Option<SharedSkiaError>,
|
||||
}
|
||||
|
||||
impl InspectorApp {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
window: None,
|
||||
ctx: None,
|
||||
backend: None,
|
||||
host: WidgetHostNative::new(),
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplicationHandler for InspectorApp {
|
||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||
if self.window.is_some() {
|
||||
return;
|
||||
}
|
||||
let attrs = Window::default_attributes()
|
||||
.with_title("OpenPencil — inspector_window (Step 1b §1.4 native)")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(800u32, 600u32));
|
||||
let window = match event_loop.create_window(attrs) {
|
||||
Ok(w) => w,
|
||||
Err(err) => {
|
||||
eprintln!("inspector_window: create_window failed: {err}");
|
||||
event_loop.exit();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let dpi = window.scale_factor() as f32;
|
||||
match SharedSkiaContext::new_desktop(&window) {
|
||||
Ok(ctx) => {
|
||||
self.ctx = Some(ctx);
|
||||
self.backend = Some(NativeBackend::with_dpi(dpi));
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("inspector_window: SharedSkiaContext::new_desktop failed: {err}");
|
||||
self.error = Some(err);
|
||||
event_loop.exit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.window = Some(window);
|
||||
|
||||
if let (Some(ctx), Some(backend)) = (self.ctx.as_mut(), self.backend.as_mut()) {
|
||||
paint_inspector(ctx, backend, &self.host);
|
||||
}
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
&mut self,
|
||||
event_loop: &ActiveEventLoop,
|
||||
_window_id: WindowId,
|
||||
event: WindowEvent,
|
||||
) {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
event_loop.exit();
|
||||
}
|
||||
WindowEvent::Resized(size) => {
|
||||
if let Some(ctx) = self.ctx.as_mut() {
|
||||
if let Err(err) = ctx.resize(size.width, size.height) {
|
||||
eprintln!("inspector_window: resize failed: {err}");
|
||||
self.error = Some(err);
|
||||
event_loop.exit();
|
||||
}
|
||||
}
|
||||
if let Some(window) = self.window.as_ref() {
|
||||
window.request_redraw();
|
||||
}
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
|
||||
if let Some(backend) = self.backend.as_mut() {
|
||||
backend.set_dpi(scale_factor as f32);
|
||||
}
|
||||
}
|
||||
WindowEvent::RedrawRequested => {
|
||||
if let (Some(ctx), Some(backend)) = (self.ctx.as_mut(), self.backend.as_mut()) {
|
||||
paint_inspector(ctx, backend, &self.host);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn exiting(&mut self, _event_loop: &ActiveEventLoop) {
|
||||
if let Some(mut ctx) = self.ctx.take() {
|
||||
if let Err(err) = ctx.teardown() {
|
||||
eprintln!("inspector_window: teardown failed: {err}");
|
||||
}
|
||||
}
|
||||
self.backend.take();
|
||||
self.window.take();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let event_loop = match EventLoop::new() {
|
||||
Ok(el) => el,
|
||||
Err(err) => {
|
||||
eprintln!("inspector_window: EventLoop::new failed: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
event_loop.set_control_flow(winit::event_loop::ControlFlow::Wait);
|
||||
let mut app = InspectorApp::new();
|
||||
if let Err(err) = event_loop.run_app(&mut app) {
|
||||
eprintln!("inspector_window: run_app exited with error: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
if let Some(err) = app.error {
|
||||
eprintln!("inspector_window: fatal error during run: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
|
@ -42,10 +42,14 @@ pub mod context;
|
|||
pub mod backend;
|
||||
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
|
||||
pub mod canvas_view_stub;
|
||||
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
|
||||
pub mod widget_host;
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
|
||||
pub use backend::{to_jian_color, to_jian_rect, NativeBackend};
|
||||
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
|
||||
pub use widget_host::{NativeFrameBackend, WidgetHostNative};
|
||||
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
|
||||
pub use canvas_view_stub::CanvasViewportStub;
|
||||
|
||||
// Cross-platform re-exports — visible on every (non-wasm) target.
|
||||
|
|
|
|||
198
crates/openpencil-shell-native/src/widget_host.rs
Normal file
198
crates/openpencil-shell-native/src/widget_host.rs
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
//! Step 1b §1.4 native widget glue — the only file in shell-native
|
||||
//! allowed to call into `openpencil_shell_core::widgets`. Mirrors the
|
||||
//! shell-web `widget_host.rs` pattern so the four inspector widgets
|
||||
//! (Tree / PropertyRow / Dropdown / TextInput) defined in shell-core
|
||||
//! are truly cross-platform: shell-web reuses them via `WebBackend`,
|
||||
//! shell-native via `NativeBackend` + this glue.
|
||||
//!
|
||||
//! Two pieces:
|
||||
//!
|
||||
//! 1. [`NativeFrameBackend`] — a frame-scoped wrapper that holds a
|
||||
//! `(&mut NativeBackend, &skia_safe::Canvas)` pair and `impl
|
||||
//! RenderBackend` for it. Spec §5.2.1 explicitly defers the
|
||||
//! `RenderBackend` impl on the canvas-borrow-bearing `NativeBackend`
|
||||
//! to Step 1c+ widget tree work; this is that landing site.
|
||||
//!
|
||||
//! 2. [`WidgetHostNative`] — owns one of each of the Step 1b widgets +
|
||||
//! a `paint(&self, &mut NativeFrameBackend, available_width)`
|
||||
//! method that dispatches like shell-web's WidgetHost. Phase D will
|
||||
//! add `apply_*` event handlers (winit input → shell-core
|
||||
//! `apply_ime` / `apply_key`) once the desktop event pipeline
|
||||
//! needs them.
|
||||
//!
|
||||
//! ### Mobile (iOS / Android) — Step 1f path
|
||||
//!
|
||||
//! Per spec §11 invariants: shell-native is gated to desktop OS
|
||||
//! today (`backend` / `canvas_view_stub` / `widget_host` modules
|
||||
//! cfg-gated to `macos | linux | windows`). Mobile widget rendering
|
||||
//! lands in Step 1f via `context::EaglProvider` (iOS) /
|
||||
//! `context::AndroidEglProvider` (Android) — both are zero-sized
|
||||
//! placeholder structs in lib.rs today whose `GlContextProvider`
|
||||
//! impls `unimplemented!()`. Per the 2026-05-10 user directive
|
||||
//! ("安卓和ios 不需要 ipc / 本地 cli — 只需要 custom provider"):
|
||||
//! mobile rendering is purely a custom-provider plugin point on the
|
||||
//! existing `GlContextProvider` trait, NOT a separate IPC / CLI
|
||||
//! pipeline.
|
||||
//!
|
||||
//! Crucially the widget glue here is platform-agnostic in shape:
|
||||
//! - `NativeFrameBackend` only holds `&mut NativeBackend` +
|
||||
//! `&skia_safe::Canvas`; both compile on any target where
|
||||
//! skia-safe + jian-skia + the GL provider compile. No
|
||||
//! desktop-specific type names or APIs leak in.
|
||||
//! - `WidgetHostNative` only consumes shell-core widgets + the
|
||||
//! `RenderBackend` trait; no winit / glutin / EGL types appear.
|
||||
//! - When Step 1f promotes `NativeBackend` to compile on
|
||||
//! mobile (drop the desktop-only cfg in lib.rs once
|
||||
//! `EaglProvider` / `AndroidEglProvider` ship real impls),
|
||||
//! `WidgetHostNative` follows automatically — no rewrite, no
|
||||
//! additional glue. The mobile shell just constructs a different
|
||||
//! `SharedSkiaContext` (or the mobile equivalent) backed by its
|
||||
//! provider, then runs the same `host.paint(&mut frame, width)`.
|
||||
|
||||
use crate::backend::NativeBackend;
|
||||
use openpencil_shell_core::widgets::{
|
||||
Dropdown, LayoutCx, PaintCx, PropertyRow, TextInput, TreeWidget, Widget,
|
||||
};
|
||||
use openpencil_shell_core::{Color, Point2D, Rect, RenderBackend, TextLayout};
|
||||
|
||||
/// Frame-scoped `RenderBackend` adapter over `NativeBackend` +
|
||||
/// `&Canvas`. Lifetime-bound to the `SharedSkiaContext::with_frame`
|
||||
/// closure body so widget code never sees the canvas borrow directly.
|
||||
///
|
||||
/// Why this exists rather than `impl RenderBackend for NativeBackend`:
|
||||
/// `NativeBackend` deliberately does NOT carry a canvas borrow (spec
|
||||
/// §5.2.1 — the canvas reference is short-lived inside `with_frame`,
|
||||
/// while `NativeBackend` lives across frames so its `jian_skia::
|
||||
/// SkiaBackend` image cache survives). The wrapper gets the trait
|
||||
/// shape without entangling the lifetime onto the long-lived backend
|
||||
/// type.
|
||||
pub struct NativeFrameBackend<'a> {
|
||||
inner: &'a mut NativeBackend,
|
||||
canvas: &'a skia_safe::Canvas,
|
||||
}
|
||||
|
||||
impl<'a> NativeFrameBackend<'a> {
|
||||
pub fn new(inner: &'a mut NativeBackend, canvas: &'a skia_safe::Canvas) -> Self {
|
||||
Self { inner, canvas }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RenderBackend for NativeFrameBackend<'a> {
|
||||
fn begin_frame(&mut self) {
|
||||
// No-op on native: `SharedSkiaContext::begin_frame` already
|
||||
// ran when the caller entered `with_frame`.
|
||||
}
|
||||
|
||||
fn end_frame(&mut self) {
|
||||
// No-op on native: `SharedSkiaContext::present` runs after
|
||||
// `with_frame` returns, outside the wrapper's lifetime.
|
||||
}
|
||||
|
||||
fn fill_rect(&mut self, rect: Rect, color: Color) {
|
||||
self.inner.fill_rect(self.canvas, rect, color);
|
||||
}
|
||||
|
||||
fn stroke_rect(&mut self, rect: Rect, color: Color, width: f32) {
|
||||
self.inner.stroke_rect(self.canvas, rect, color, width);
|
||||
}
|
||||
|
||||
fn draw_text(&mut self, layout: &TextLayout, origin: Point2D) {
|
||||
self.inner.draw_text(self.canvas, layout, origin);
|
||||
}
|
||||
|
||||
fn clip_rect(&mut self, rect: Rect) {
|
||||
self.inner.clip_rect(self.canvas, rect);
|
||||
}
|
||||
|
||||
fn save(&mut self) {
|
||||
// `NativeBackend::save` returns the pre-save count, used by
|
||||
// `restore_to`. The trait-level `save` is fire-and-forget; we
|
||||
// discard the count and rely on the caller pairing each
|
||||
// `save` with one `restore`.
|
||||
let _count = self.inner.save(self.canvas);
|
||||
}
|
||||
|
||||
fn restore(&mut self) {
|
||||
self.inner.restore(self.canvas);
|
||||
}
|
||||
|
||||
fn translate(&mut self, offset: Point2D) {
|
||||
self.inner.translate(self.canvas, offset);
|
||||
}
|
||||
|
||||
fn resize(&mut self, _width: u32, _height: u32) {
|
||||
// No-op via the trait: surface resize is owned by
|
||||
// `SharedSkiaContext::resize` and reaches `NativeBackend`
|
||||
// separately. Mirrors `NativeBackend::resize`'s no-op.
|
||||
}
|
||||
|
||||
fn dpi_scale(&self) -> f32 {
|
||||
self.inner.dpi_scale()
|
||||
}
|
||||
}
|
||||
|
||||
/// Native counterpart of shell-web's `widget_host::WidgetHost`. Owns
|
||||
/// one of each Step 1b inspector widget so the desktop demo paints
|
||||
/// the same composition shell-web does. Phase D will add `apply_*`
|
||||
/// event-routing methods alongside the desktop input pipeline.
|
||||
pub struct WidgetHostNative {
|
||||
tree: TreeWidget,
|
||||
width: PropertyRow,
|
||||
dropdown: Dropdown,
|
||||
text_input: TextInput,
|
||||
}
|
||||
|
||||
impl WidgetHostNative {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
tree: TreeWidget::sample(),
|
||||
width: PropertyRow::new(200, "Width", "960"),
|
||||
dropdown: Dropdown::sample(),
|
||||
text_input: TextInput::sample(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Paint the inspector slice at `(16, y)` stacking with 12 px
|
||||
/// gaps, in a 280 px column. Mirrors shell-web's
|
||||
/// `WidgetHost::paint` so the visual layout is identical between
|
||||
/// platforms (Phase E manual-smoke acceptance criterion).
|
||||
///
|
||||
/// `// glue:` marker on the signature line keeps the future
|
||||
/// `tools/check-widget-boundary.sh` happy if the boundary script
|
||||
/// is extended to gate shell-native too (it currently scans only
|
||||
/// shell-web; Phase D may parameterize).
|
||||
pub fn paint(&self, frame: &mut NativeFrameBackend<'_>, available_width: f32) { // glue:
|
||||
let layout = LayoutCx {
|
||||
available_width,
|
||||
dpi: frame.dpi_scale(),
|
||||
};
|
||||
let mut y = 16.0;
|
||||
let widgets: [&dyn Widget; 4] = [
|
||||
&self.tree,
|
||||
&self.width,
|
||||
&self.dropdown,
|
||||
&self.text_input,
|
||||
];
|
||||
for widget in widgets {
|
||||
let box_ = widget.layout(&layout);
|
||||
let rect = Rect {
|
||||
origin: Point2D::new(16.0, y),
|
||||
size: Point2D::new(available_width, box_.rect.size.y),
|
||||
};
|
||||
// PaintCx borrows the frame backend mutably; reborrow per
|
||||
// iteration with `&mut *frame` so subsequent iterations
|
||||
// don't fail the borrow check on the moved reference.
|
||||
let mut cx = PaintCx {
|
||||
backend: &mut *frame,
|
||||
};
|
||||
widget.paint(&mut cx, rect);
|
||||
y += rect.size.y + 12.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WidgetHostNative {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue