mirror of https://github.com/linebender/xilem
Allow external event loop to drive masonry and xilem (#417)
These changes allow you to create a masonry or xilem app that is driven by an external event loop. ## Masonry Existing method for creating masonry app: ``` masonry::event_loop_runner::run( masonry::event_loop_runner::EventLoop::with_user_event(), window_attributes, root_widget, app_driver, ) .unwrap(); ``` Instead you can now do this: ``` let masonry_state = MasonryState::new(window_attributes, &event_loop, root_widget); let mut app = AppInterface { masonry_state, app_driver: Box::new(driver), }; event_loop.run_app(&mut app) ``` Where AppInterface implements the winit ApplicationHandler<accesskit_winit::Event> trait. ## Xilem Existing method: ``` let app = Xilem::new(state, app_logic); app.run_windowed(EventLoop::with_user_event(), title)?; ``` Now: ``` let xilem = Xilem::new(0, app_logic); let (root_widget, app_driver) = xilem.split(); let parts = xilem.split(); let (root_widget, app_driver) = (parts.root_widget, parts.driver) // and then create masonry app just like above using root_widget and app_driver ``` Also adds example/external_event_loop.rs which duplicates example/flex.rs but with an external event loop. --------- Co-authored-by: Daniel McNab <36049421+DJMcNab@users.noreply.github.com>
This commit is contained in:
parent
e76cf31258
commit
cf3530097b
|
@ -12,7 +12,10 @@ use vello::{peniko::Color, AaSupport, RenderParams, Renderer, RendererOptions, S
|
|||
use wgpu::PresentMode;
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::error::EventLoopError;
|
||||
use winit::event::{MouseButton as WinitMouseButton, WindowEvent as WinitWindowEvent};
|
||||
use winit::event::{
|
||||
DeviceEvent as WinitDeviceEvent, DeviceId, MouseButton as WinitMouseButton,
|
||||
WindowEvent as WinitWindowEvent,
|
||||
};
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoopProxy};
|
||||
use winit::window::{Window, WindowAttributes, WindowId};
|
||||
|
||||
|
@ -51,11 +54,13 @@ pub enum WindowState<'a> {
|
|||
},
|
||||
}
|
||||
|
||||
struct MainState<'a> {
|
||||
/// The state of the Masonry application. If you run Masonry from an external Winit event loop, create a
|
||||
/// `MasonryState` via [`MasonryState::new`] and forward events to it via the appropriate method (e.g.,
|
||||
/// calling [`handle_window_event`](MasonryState::handle_window_event) in [`window_event`](ApplicationHandler::window_event)).
|
||||
pub struct MasonryState<'a> {
|
||||
render_cx: RenderContext,
|
||||
render_root: RenderRoot,
|
||||
pointer_state: PointerState,
|
||||
app_driver: Box<dyn AppDriver>,
|
||||
renderer: Option<Renderer>,
|
||||
// TODO: Winit doesn't seem to let us create these proxies from within the loop
|
||||
// The reasons for this are unclear
|
||||
|
@ -66,6 +71,11 @@ struct MainState<'a> {
|
|||
window: WindowState<'a>,
|
||||
}
|
||||
|
||||
struct MainState<'a> {
|
||||
masonry_state: MasonryState<'a>,
|
||||
app_driver: Box<dyn AppDriver>,
|
||||
}
|
||||
|
||||
/// The type of the event loop used by Masonry.
|
||||
///
|
||||
/// This *will* be changed to allow custom event types, but is implemented this way for expedience
|
||||
|
@ -96,18 +106,9 @@ pub fn run_with(
|
|||
root_widget: impl Widget,
|
||||
app_driver: impl AppDriver + 'static,
|
||||
) -> Result<(), EventLoopError> {
|
||||
let render_cx = RenderContext::new();
|
||||
// TODO: We can't know this scale factor until later?
|
||||
let scale_factor = 1.0;
|
||||
let mut main_state = MainState {
|
||||
render_cx,
|
||||
render_root: RenderRoot::new(root_widget, WindowSizePolicy::User, scale_factor),
|
||||
renderer: None,
|
||||
pointer_state: PointerState::empty(),
|
||||
masonry_state: MasonryState::new(window, &event_loop, root_widget),
|
||||
app_driver: Box::new(app_driver),
|
||||
proxy: event_loop.create_proxy(),
|
||||
|
||||
window: WindowState::Uninitialized(window),
|
||||
};
|
||||
|
||||
// If there is no default tracing subscriber, we set our own. If one has
|
||||
|
@ -121,6 +122,90 @@ pub fn run_with(
|
|||
|
||||
impl ApplicationHandler<accesskit_winit::Event> for MainState<'_> {
|
||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||
self.masonry_state.handle_resumed(event_loop);
|
||||
}
|
||||
|
||||
fn suspended(&mut self, event_loop: &ActiveEventLoop) {
|
||||
self.masonry_state.handle_suspended(event_loop);
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
&mut self,
|
||||
event_loop: &ActiveEventLoop,
|
||||
window_id: WindowId,
|
||||
event: WinitWindowEvent,
|
||||
) {
|
||||
self.masonry_state.handle_window_event(
|
||||
event_loop,
|
||||
window_id,
|
||||
event,
|
||||
self.app_driver.as_mut(),
|
||||
);
|
||||
}
|
||||
|
||||
fn device_event(
|
||||
&mut self,
|
||||
event_loop: &ActiveEventLoop,
|
||||
device_id: DeviceId,
|
||||
event: WinitDeviceEvent,
|
||||
) {
|
||||
self.masonry_state.handle_device_event(
|
||||
event_loop,
|
||||
device_id,
|
||||
event,
|
||||
self.app_driver.as_mut(),
|
||||
);
|
||||
}
|
||||
|
||||
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: accesskit_winit::Event) {
|
||||
self.masonry_state
|
||||
.handle_user_event(event_loop, event, self.app_driver.as_mut());
|
||||
}
|
||||
|
||||
// The following have empty handlers, but adding this here for future proofing. E.g., memory
|
||||
// warning is very likely to be handled for mobile and we in particular want to make sure
|
||||
// external event loops can let masonry handle these callbacks.
|
||||
|
||||
fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
|
||||
self.masonry_state.handle_about_to_wait(event_loop);
|
||||
}
|
||||
|
||||
fn new_events(
|
||||
&mut self,
|
||||
event_loop: &winit::event_loop::ActiveEventLoop,
|
||||
cause: winit::event::StartCause,
|
||||
) {
|
||||
self.masonry_state.handle_new_events(event_loop, cause);
|
||||
}
|
||||
|
||||
fn exiting(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
|
||||
self.masonry_state.handle_exiting(event_loop);
|
||||
}
|
||||
|
||||
fn memory_warning(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
|
||||
self.masonry_state.handle_memory_warning(event_loop);
|
||||
}
|
||||
}
|
||||
|
||||
impl MasonryState<'_> {
|
||||
pub fn new(window: WindowAttributes, event_loop: &EventLoop, root_widget: impl Widget) -> Self {
|
||||
let render_cx = RenderContext::new();
|
||||
// TODO: We can't know this scale factor until later?
|
||||
let scale_factor = 1.0;
|
||||
|
||||
MasonryState {
|
||||
render_cx,
|
||||
render_root: RenderRoot::new(root_widget, WindowSizePolicy::User, scale_factor),
|
||||
renderer: None,
|
||||
pointer_state: PointerState::empty(),
|
||||
proxy: event_loop.create_proxy(),
|
||||
|
||||
window: WindowState::Uninitialized(window),
|
||||
}
|
||||
}
|
||||
|
||||
// --- MARK: RESUMED ---
|
||||
pub fn handle_resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||
match std::mem::replace(
|
||||
&mut self.window,
|
||||
// TODO: Is there a better default value which could be used?
|
||||
|
@ -183,7 +268,9 @@ impl ApplicationHandler<accesskit_winit::Event> for MainState<'_> {
|
|||
}
|
||||
}
|
||||
}
|
||||
fn suspended(&mut self, _event_loop: &ActiveEventLoop) {
|
||||
|
||||
// --- MARK: SUSPENDED ---
|
||||
pub fn handle_suspended(&mut self, _event_loop: &ActiveEventLoop) {
|
||||
match std::mem::replace(
|
||||
&mut self.window,
|
||||
// TODO: Is there a better default value which could be used?
|
||||
|
@ -206,8 +293,76 @@ impl ApplicationHandler<accesskit_winit::Event> for MainState<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
// --- MARK: RENDER ---
|
||||
fn render(&mut self, scene: Scene) {
|
||||
let WindowState::Rendering {
|
||||
window, surface, ..
|
||||
} = &mut self.window
|
||||
else {
|
||||
tracing::warn!("Tried to render whilst suspended or before window created");
|
||||
return;
|
||||
};
|
||||
let scale_factor = window.scale_factor();
|
||||
// https://github.com/rust-windowing/winit/issues/2308
|
||||
#[cfg(target_os = "ios")]
|
||||
let size = window.outer_size();
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
let size = window.inner_size();
|
||||
let width = size.width;
|
||||
let height = size.height;
|
||||
|
||||
if surface.config.width != width || surface.config.height != height {
|
||||
self.render_cx.resize_surface(surface, width, height);
|
||||
}
|
||||
|
||||
let transformed_scene = if scale_factor == 1.0 {
|
||||
None
|
||||
} else {
|
||||
let mut new_scene = Scene::new();
|
||||
new_scene.append(&scene, Some(Affine::scale(scale_factor)));
|
||||
Some(new_scene)
|
||||
};
|
||||
let scene_ref = transformed_scene.as_ref().unwrap_or(&scene);
|
||||
|
||||
let Ok(surface_texture) = surface.surface.get_current_texture() else {
|
||||
warn!("failed to acquire next swapchain texture");
|
||||
return;
|
||||
};
|
||||
let dev_id = surface.dev_id;
|
||||
let device = &self.render_cx.devices[dev_id].device;
|
||||
let queue = &self.render_cx.devices[dev_id].queue;
|
||||
let renderer_options = RendererOptions {
|
||||
surface_format: Some(surface.format),
|
||||
use_cpu: false,
|
||||
antialiasing_support: AaSupport {
|
||||
area: true,
|
||||
msaa8: false,
|
||||
msaa16: false,
|
||||
},
|
||||
num_init_threads: NonZeroUsize::new(1),
|
||||
};
|
||||
let render_params = RenderParams {
|
||||
base_color: Color::BLACK,
|
||||
width,
|
||||
height,
|
||||
antialiasing_method: vello::AaConfig::Area,
|
||||
};
|
||||
self.renderer
|
||||
.get_or_insert_with(|| Renderer::new(device, renderer_options).unwrap())
|
||||
.render_to_surface(device, queue, scene_ref, &surface_texture, &render_params)
|
||||
.expect("failed to render to surface");
|
||||
surface_texture.present();
|
||||
device.poll(wgpu::Maintain::Wait);
|
||||
}
|
||||
|
||||
// --- MARK: WINDOW_EVENT ---
|
||||
fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WinitWindowEvent) {
|
||||
pub fn handle_window_event(
|
||||
&mut self,
|
||||
event_loop: &ActiveEventLoop,
|
||||
_: WindowId,
|
||||
event: WinitWindowEvent,
|
||||
app_driver: &mut dyn AppDriver,
|
||||
) {
|
||||
let WindowState::Rendering {
|
||||
window,
|
||||
accesskit_adapter,
|
||||
|
@ -354,11 +509,26 @@ impl ApplicationHandler<accesskit_winit::Event> for MainState<'_> {
|
|||
_ => (),
|
||||
}
|
||||
|
||||
self.handle_signals(event_loop);
|
||||
self.handle_signals(event_loop, app_driver);
|
||||
}
|
||||
|
||||
// --- MARK: DEVICE_EVENT ---
|
||||
pub fn handle_device_event(
|
||||
&mut self,
|
||||
_: &ActiveEventLoop,
|
||||
_: DeviceId,
|
||||
_: WinitDeviceEvent,
|
||||
_: &mut dyn AppDriver,
|
||||
) {
|
||||
}
|
||||
|
||||
// --- MARK: USER_EVENT ---
|
||||
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: accesskit_winit::Event) {
|
||||
pub fn handle_user_event(
|
||||
&mut self,
|
||||
event_loop: &ActiveEventLoop,
|
||||
event: accesskit_winit::Event,
|
||||
app_driver: &mut dyn AppDriver,
|
||||
) {
|
||||
match event.window_event {
|
||||
// Note that this event can be called at any time, even multiple times if
|
||||
// the user restarts their screen reader.
|
||||
|
@ -372,75 +542,20 @@ impl ApplicationHandler<accesskit_winit::Event> for MainState<'_> {
|
|||
accesskit_winit::WindowEvent::AccessibilityDeactivated => {}
|
||||
}
|
||||
|
||||
self.handle_signals(event_loop);
|
||||
self.handle_signals(event_loop, app_driver);
|
||||
}
|
||||
}
|
||||
|
||||
impl MainState<'_> {
|
||||
// --- MARK: RENDER ---
|
||||
fn render(&mut self, scene: Scene) {
|
||||
let WindowState::Rendering {
|
||||
window, surface, ..
|
||||
} = &mut self.window
|
||||
else {
|
||||
tracing::warn!("Tried to render whilst suspended or before window created");
|
||||
return;
|
||||
};
|
||||
let scale_factor = window.scale_factor();
|
||||
// https://github.com/rust-windowing/winit/issues/2308
|
||||
#[cfg(target_os = "ios")]
|
||||
let size = window.outer_size();
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
let size = window.inner_size();
|
||||
let width = size.width;
|
||||
let height = size.height;
|
||||
// --- MARK: EMPTY WINIT HANDLERS ---
|
||||
pub fn handle_about_to_wait(&mut self, _: &ActiveEventLoop) {}
|
||||
|
||||
if surface.config.width != width || surface.config.height != height {
|
||||
self.render_cx.resize_surface(surface, width, height);
|
||||
}
|
||||
pub fn handle_new_events(&mut self, _: &ActiveEventLoop, _: winit::event::StartCause) {}
|
||||
|
||||
let transformed_scene = if scale_factor == 1.0 {
|
||||
None
|
||||
} else {
|
||||
let mut new_scene = Scene::new();
|
||||
new_scene.append(&scene, Some(Affine::scale(scale_factor)));
|
||||
Some(new_scene)
|
||||
};
|
||||
let scene_ref = transformed_scene.as_ref().unwrap_or(&scene);
|
||||
pub fn handle_exiting(&mut self, _: &ActiveEventLoop) {}
|
||||
|
||||
let Ok(surface_texture) = surface.surface.get_current_texture() else {
|
||||
warn!("failed to acquire next swapchain texture");
|
||||
return;
|
||||
};
|
||||
let dev_id = surface.dev_id;
|
||||
let device = &self.render_cx.devices[dev_id].device;
|
||||
let queue = &self.render_cx.devices[dev_id].queue;
|
||||
let renderer_options = RendererOptions {
|
||||
surface_format: Some(surface.format),
|
||||
use_cpu: false,
|
||||
antialiasing_support: AaSupport {
|
||||
area: true,
|
||||
msaa8: false,
|
||||
msaa16: false,
|
||||
},
|
||||
num_init_threads: NonZeroUsize::new(1),
|
||||
};
|
||||
let render_params = RenderParams {
|
||||
base_color: Color::BLACK,
|
||||
width,
|
||||
height,
|
||||
antialiasing_method: vello::AaConfig::Area,
|
||||
};
|
||||
self.renderer
|
||||
.get_or_insert_with(|| Renderer::new(device, renderer_options).unwrap())
|
||||
.render_to_surface(device, queue, scene_ref, &surface_texture, &render_params)
|
||||
.expect("failed to render to surface");
|
||||
surface_texture.present();
|
||||
device.poll(wgpu::Maintain::Wait);
|
||||
}
|
||||
pub fn handle_memory_warning(&mut self, _: &ActiveEventLoop) {}
|
||||
|
||||
// --- MARK: SIGNALS ---
|
||||
fn handle_signals(&mut self, _event_loop: &ActiveEventLoop) {
|
||||
fn handle_signals(&mut self, _event_loop: &ActiveEventLoop, app_driver: &mut dyn AppDriver) {
|
||||
let WindowState::Rendering { window, .. } = &mut self.window else {
|
||||
tracing::warn!("Tried to handle a signal whilst suspended or before window created");
|
||||
return;
|
||||
|
@ -453,8 +568,7 @@ impl MainState<'_> {
|
|||
let mut driver_ctx = DriverCtx {
|
||||
main_root_widget: root,
|
||||
};
|
||||
self.app_driver
|
||||
.on_action(&mut driver_ctx, widget_id, action);
|
||||
app_driver.on_action(&mut driver_ctx, widget_id, action);
|
||||
});
|
||||
}
|
||||
render_root::RenderRootSignal::StartIme => {
|
||||
|
@ -492,4 +606,18 @@ impl MainState<'_> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_window_state(&self) -> &WindowState {
|
||||
&self.window
|
||||
}
|
||||
|
||||
pub fn get_root(&mut self) -> &mut RenderRoot {
|
||||
&mut self.render_root
|
||||
}
|
||||
|
||||
pub fn set_present_mode(&mut self, present_mode: wgpu::PresentMode) {
|
||||
if let WindowState::Rendering { surface, .. } = &mut self.window {
|
||||
self.render_cx.set_present_mode(surface, present_mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
// Copyright 2024 the Xilem Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Shows driving a Xilem application from a pre-existing Winit event loop.
|
||||
//! Currently, this supports running as its own window alongside an existing application, or
|
||||
//! accessing raw events from winit.
|
||||
//! Support for more custom embeddings would be welcome, but needs more design work
|
||||
use masonry::{
|
||||
app_driver::AppDriver,
|
||||
widget::{CrossAxisAlignment, MainAxisAlignment},
|
||||
ArcStr,
|
||||
};
|
||||
use winit::{
|
||||
application::ApplicationHandler,
|
||||
error::EventLoopError,
|
||||
event::ElementState,
|
||||
keyboard::{KeyCode, PhysicalKey},
|
||||
};
|
||||
use xilem::{
|
||||
view::{button, flex, label, sized_box},
|
||||
EventLoop, WidgetView, Xilem,
|
||||
};
|
||||
|
||||
/// A component to make a bigger than usual button
|
||||
fn big_button(
|
||||
label: impl Into<ArcStr>,
|
||||
callback: impl Fn(&mut i32) + Send + Sync + 'static,
|
||||
) -> impl WidgetView<i32> {
|
||||
sized_box(button(label, callback)).width(40.).height(40.)
|
||||
}
|
||||
|
||||
fn app_logic(data: &mut i32) -> impl WidgetView<i32> {
|
||||
flex((
|
||||
big_button("-", |data| {
|
||||
*data -= 1;
|
||||
}),
|
||||
label(format!("count: {}", data)).text_size(32.),
|
||||
big_button("+", |data| {
|
||||
*data += 1;
|
||||
}),
|
||||
))
|
||||
.direction(xilem::Axis::Horizontal)
|
||||
.cross_axis_alignment(CrossAxisAlignment::Center)
|
||||
.main_axis_alignment(MainAxisAlignment::Center)
|
||||
}
|
||||
|
||||
/// An application not managed by Xilem, but which wishes to embed Xilem.
|
||||
struct ExternalApp {
|
||||
masonry_state: masonry::event_loop_runner::MasonryState<'static>,
|
||||
app_driver: Box<dyn AppDriver>,
|
||||
}
|
||||
|
||||
impl ApplicationHandler<accesskit_winit::Event> for ExternalApp {
|
||||
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
|
||||
self.masonry_state.handle_resumed(event_loop);
|
||||
}
|
||||
|
||||
fn suspended(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
|
||||
self.masonry_state.handle_suspended(event_loop);
|
||||
}
|
||||
|
||||
fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
|
||||
self.masonry_state.handle_about_to_wait(event_loop);
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
&mut self,
|
||||
event_loop: &winit::event_loop::ActiveEventLoop,
|
||||
window_id: winit::window::WindowId,
|
||||
event: winit::event::WindowEvent,
|
||||
) {
|
||||
self.masonry_state.handle_window_event(
|
||||
event_loop,
|
||||
window_id,
|
||||
event,
|
||||
self.app_driver.as_mut(),
|
||||
);
|
||||
}
|
||||
|
||||
fn user_event(
|
||||
&mut self,
|
||||
event_loop: &winit::event_loop::ActiveEventLoop,
|
||||
event: accesskit_winit::Event,
|
||||
) {
|
||||
self.masonry_state
|
||||
.handle_user_event(event_loop, event, self.app_driver.as_mut());
|
||||
}
|
||||
|
||||
fn device_event(
|
||||
&mut self,
|
||||
event_loop: &winit::event_loop::ActiveEventLoop,
|
||||
device_id: winit::event::DeviceId,
|
||||
event: winit::event::DeviceEvent,
|
||||
) {
|
||||
// Handle the escape key to exit the app outside of masonry/xilem
|
||||
if let winit::event::DeviceEvent::Key(key) = &event {
|
||||
if key.state == ElementState::Pressed
|
||||
&& key.physical_key == PhysicalKey::Code(KeyCode::Escape)
|
||||
{
|
||||
event_loop.exit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.masonry_state.handle_device_event(
|
||||
event_loop,
|
||||
device_id,
|
||||
event,
|
||||
self.app_driver.as_mut(),
|
||||
);
|
||||
}
|
||||
|
||||
fn new_events(
|
||||
&mut self,
|
||||
event_loop: &winit::event_loop::ActiveEventLoop,
|
||||
cause: winit::event::StartCause,
|
||||
) {
|
||||
self.masonry_state.handle_new_events(event_loop, cause);
|
||||
}
|
||||
|
||||
fn exiting(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
|
||||
self.masonry_state.handle_exiting(event_loop);
|
||||
}
|
||||
|
||||
fn memory_warning(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
|
||||
self.masonry_state.handle_memory_warning(event_loop);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), EventLoopError> {
|
||||
let window_size = winit::dpi::LogicalSize::new(800.0, 800.0);
|
||||
let window_attributes = winit::window::Window::default_attributes()
|
||||
.with_title("External event loop".to_string())
|
||||
.with_resizable(true)
|
||||
.with_min_inner_size(window_size);
|
||||
|
||||
let xilem = Xilem::new(0, app_logic);
|
||||
|
||||
let event_loop = EventLoop::with_user_event().build().unwrap();
|
||||
let masonry_state = masonry::event_loop_runner::MasonryState::new(
|
||||
window_attributes,
|
||||
&event_loop,
|
||||
xilem.root_widget,
|
||||
);
|
||||
|
||||
let mut app = ExternalApp {
|
||||
masonry_state,
|
||||
app_driver: Box::new(xilem.driver),
|
||||
};
|
||||
event_loop.run_app(&mut app)
|
||||
}
|
|
@ -34,8 +34,8 @@ pub struct Xilem<State, Logic, View>
|
|||
where
|
||||
View: WidgetView<State>,
|
||||
{
|
||||
root_widget: RootWidget<View::Widget>,
|
||||
driver: MasonryDriver<State, Logic, View, View::ViewState>,
|
||||
pub root_widget: RootWidget<View::Widget>,
|
||||
pub driver: MasonryDriver<State, Logic, View, View::ViewState>,
|
||||
}
|
||||
|
||||
impl<State, Logic, View> Xilem<State, Logic, View>
|
||||
|
|
Loading…
Reference in New Issue