//! Create interactive, native cross-platform applications for WGPU.
mod state;
pub use state::State;
use crate::clipboard::{self, Clipboard};
use crate::conversion;
use crate::mouse;
use crate::renderer;
use crate::settings;
use crate::widget::operation;
use crate::window;
use crate::{
Command, Debug, Element, Error, Executor, Proxy, Renderer, Runtime,
Settings, Size, Subscription,
};
use iced_futures::futures::channel::mpsc;
use iced_futures::futures::{self, FutureExt};
use iced_graphics::compositor;
use iced_native::user_interface::{self, UserInterface};
pub use iced_native::application::{Appearance, StyleSheet};
use std::collections::HashMap;
use std::mem::ManuallyDrop;
use std::time::Instant;
#[cfg(feature = "trace")]
pub use crate::Profiler;
#[cfg(feature = "trace")]
use tracing::{info_span, instrument::Instrument};
/// This is a wrapper around the `Application::Message` associate type
/// to allows the `shell` to create internal messages, while still having
/// the current user-specified custom messages.
#[derive(Debug)]
pub enum Event<Message> {
/// An [`Application`] generated message
Application(Message),
/// A message which spawns a new window.
NewWindow {
/// The [window::Id] of the newly spawned [`Window`].
id: window::Id,
/// The [settings::Window] of the newly spawned [`Window`].
settings: settings::Window,
/// The title of the newly spawned [`Window`].
title: String,
},
/// Close a window.
CloseWindow(window::Id),
/// A message for when the window has finished being created.
WindowCreated(window::Id, winit::window::Window),
}
/// An interactive, native, cross-platform, multi-windowed application.
///
/// This trait is the main entrypoint of multi-window Iced. Once implemented, you can run
/// your GUI application by simply calling [`run`]. It will run in
/// its own window.
///
/// An [`Application`] can execute asynchronous actions by returning a
/// [`Command`] in some of its methods.
///
/// When using an [`Application`] with the `debug` feature enabled, a debug view
/// can be toggled by pressing `F12`.
pub trait Application: Sized
where
<Self::Renderer as crate::Renderer>::Theme: StyleSheet,
{
/// The data needed to initialize your [`Application`].
type Flags;
/// The graphics backend to use to draw the [`Program`].
type Renderer: Renderer;
/// The type of __messages__ your [`Program`] will produce.
type Message: std::fmt::Debug + Send;
/// Handles a __message__ and updates the state of the [`Program`].
///
/// This is where you define your __update logic__. All the __messages__,
/// produced by either user interactions or commands, will be handled by
/// this method.
///
/// Any [`Command`] returned will be executed immediately in the
/// background by shells.
fn update(&mut self, message: Self::Message) -> Command<Self::Message>;
/// Returns the widgets to display for the `window` in the [`Program`].
///
/// These widgets can produce __messages__ based on user interaction.
fn view(
&self,
window: window::Id,
) -> Element<'_, Self::Message, Self::Renderer>;
/// Initializes the [`Application`] with the flags provided to
/// [`run`] as part of the [`Settings`].
///
/// Here is where you should return the initial state of your app.
///
/// Additionally, you can return a [`Command`] if you need to perform some
/// async action in the background on startup. This is useful if you want to
/// load state from a file, perform an initial HTTP request, etc.
fn new(flags: Self::Flags) -> (Self, Command<Self::Message>);
/// Returns the current title of each window of the [`Application`].
///
/// This title can be dynamic! The runtime will automatically update the
/// title of your application when necessary.
fn title(&self, window: window::Id) -> String;
/// Returns the current [`Theme`] of the [`Application`].
fn theme(&self, window: window::Id) -> <Self::Renderer as crate::Renderer>::Theme;
/// Returns the [`Style`] variation of the [`Theme`].
fn style(
&self,
) -> <<Self::Renderer as crate::Renderer>::Theme as StyleSheet>::Style {
Default::default()
}
/// Returns the event `Subscription` for the current state of the
/// application.
///
/// The messages produced by the `Subscription` will be handled by
/// [`update`](#tymethod.update).
///
/// A `Subscription` will be kept alive as long as you keep returning it!
///
/// By default, it returns an empty subscription.
fn subscription(&self) -> Subscription<Self::Message> {
Subscription::none()
}
/// Returns the scale factor of the window of the [`Application`].
///
/// It can be used to dynamically control the size of the UI at runtime
/// (i.e. zooming).
///
/// For instance, a scale factor of `2.0` will make widgets twice as big,
/// while a scale factor of `0.5` will shrink them to half their size.
///
/// By default, it returns `1.0`.
#[allow(unused_variables)]
fn scale_factor(&self, window: window::Id) -> f64 {
1.0
}
/// Returns whether the [`Application`] should be terminated.
///
/// By default, it returns `false`.
fn should_exit(&self) -> bool {
false
}
/// Returns the `Self::Message` that should be processed when a `window` is requested to
/// be closed.
fn close_requested(&self, window: window::Id) -> Self::Message;
}
/// Runs an [`Application`] with an executor, compositor, and the provided
/// settings.
pub fn run<A, E, C>(
settings: Settings<A::Flags>,
compositor_settings: C::Settings,
) -> Result<(), Error>
where
A: Application + 'static,
E: Executor + 'static,
C: iced_graphics::window::Compositor<Renderer = A::Renderer> + 'static,
<A::Renderer as crate::Renderer>::Theme: StyleSheet,
{
use futures::task;
use futures::Future;
use winit::event_loop::EventLoopBuilder;
#[cfg(feature = "trace")]
let _guard = Profiler::init();
let mut debug = Debug::new();
debug.startup_started();
#[cfg(feature = "trace")]
let _ = info_span!("Application", "RUN").entered();
let event_loop = EventLoopBuilder::with_user_event().build();
let proxy = event_loop.create_proxy();
let runtime = {
let proxy = Proxy::new(event_loop.create_proxy());
let executor = E::new().map_err(Error::ExecutorCreationFailed)?;
Runtime::new(executor, proxy)
};
let (application, init_command) = {
let flags = settings.flags;
runtime.enter(|| A::new(flags))
};
let builder = settings.window.into_builder(
&application.title(window::Id::MAIN),
event_loop.primary_monitor(),
settings.id,
);
log::info!("Window builder: {:#?}", builder);
let window = builder
.build(&event_loop)
.map_err(Error::WindowCreationFailed)?;
let windows: HashMap<window::Id, winit::window::Window> =
HashMap::from([(window::Id::MAIN, window)]);
let window = windows.values().next().expect("No window found");
#[cfg(target_arch = "wasm32")]
{
use winit::platform::web::WindowExtWebSys;
let canvas = window.canvas();
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
let _ = body
.append_child(&canvas)
.expect("Append canvas to HTML body");
}
let (compositor, renderer) = C::new(compositor_settings, Some(&window))?;
let (mut event_sender, event_receiver) = mpsc::unbounded();
let (control_sender, mut control_receiver) = mpsc::unbounded();
let mut instance = Box::pin({
let run_instance = run_instance::<A, E, C>(
application,
compositor,
renderer,
runtime,
proxy,
debug,
event_receiver,
control_sender,
init_command,
windows,
settings.exit_on_close_request,
);
#[cfg(feature = "trace")]
let run_instance =
run_instance.instrument(info_span!("Application", "LOOP"));
run_instance
});
let mut context = task::Context::from_waker(task::noop_waker_ref());
platform::run(event_loop, move |event, event_loop, control_flow| {
use winit::event_loop::ControlFlow;
if let ControlFlow::ExitWithCode(_) = control_flow {
return;
}
let event = match event {
winit::event::Event::WindowEvent {
event:
winit::event::WindowEvent::ScaleFactorChanged {
new_inner_size,
..
},
window_id,
} => Some(winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::Resized(*new_inner_size),
window_id,
}),
winit::event::Event::UserEvent(Event::NewWindow {
id,
settings,
title,
}) => {
let window = settings
.into_builder(&title, event_loop.primary_monitor(), None)
.build(event_loop)
.expect("Failed to build window");
Some(winit::event::Event::UserEvent(Event::WindowCreated(
id, window,
)))
}
_ => event.to_static(),
};
if let Some(event) = event {
event_sender.start_send(event).expect("Send event");
let poll = instance.as_mut().poll(&mut context);
match poll {
task::Poll::Pending => {
if let Ok(Some(flow)) = control_receiver.try_next() {
*control_flow = flow;
}
}
task::Poll::Ready(_) => {
*control_flow = ControlFlow::Exit;
}
};
}
})
}
async fn run_instance<A, E, C>(
mut application: A,
mut compositor: C,
mut renderer: A::Renderer,
mut runtime: Runtime<E, Proxy<Event<A::Message>>, Event<A::Message>>,
mut proxy: winit::event_loop::EventLoopProxy<Event<A::Message>>,
mut debug: Debug,
mut event_receiver: mpsc::UnboundedReceiver<
winit::event::Event<'_, Event<A::Message>>,
>,
mut control_sender: mpsc::UnboundedSender<winit::event_loop::ControlFlow>,
init_command: Command<A::Message>,
mut windows: HashMap<window::Id, winit::window::Window>,
_exit_on_close_request: bool,
) where
A: Application + 'static,
E: Executor + 'static,
C: iced_graphics::window::Compositor<Renderer = A::Renderer> + 'static,
<A::Renderer as crate::Renderer>::Theme: StyleSheet,
{
use iced_futures::futures::stream::StreamExt;
use winit::event;
use winit::event_loop::ControlFlow;
let mut clipboard =
Clipboard::connect(windows.values().next().expect("No window found"));
let mut caches = HashMap::new();
let mut window_ids: HashMap<_, _> = windows
.iter()
.map(|(&id, window)| (window.id(), id))
.collect();
let mut states = HashMap::new();
let mut surfaces = HashMap::new();
let mut interfaces = ManuallyDrop::new(HashMap::new());
for (&id, window) in windows.keys().zip(windows.values()) {
let mut surface = compositor.create_surface(window);
let state = State::new(&application, id, window);
let physical_size = state.physical_size();
compositor.configure_surface(
&mut surface,
physical_size.width,
physical_size.height,
);
let user_interface = build_user_interface(
&application,
user_interface::Cache::default(),
&mut renderer,
state.logical_size(),
&mut debug,
id,
);
let _ = states.insert(id, state);
let _ = surfaces.insert(id, surface);
let _ = interfaces.insert(id, user_interface);
let _ = caches.insert(id, user_interface::Cache::default());
}
run_command(
&application,
&mut caches,
&states,
&mut renderer,
init_command,
&mut runtime,
&mut clipboard,
&mut proxy,
&mut debug,
&windows,
|| compositor.fetch_information(),
);
runtime.track(application.subscription().map(Event::Application));
let mut mouse_interaction = mouse::Interaction::default();
let mut events = Vec::new();
let mut messages = Vec::new();
let mut redraw_pending = false;
debug.startup_finished();
'main: while let Some(event) = event_receiver.next().await {
match event {
event::Event::NewEvents(start_cause) => {
redraw_pending = matches!(
start_cause,
event::StartCause::Init
| event::StartCause::Poll
| event::StartCause::ResumeTimeReached { .. }
);
}
event::Event::MainEventsCleared => {
for id in states.keys().copied().collect::<Vec<_>>() {
// Partition events into only events for this window
let (filtered, remaining): (Vec<_>, Vec<_>) =
events.iter().cloned().partition(
|(window_id, _event): &(
Option<window::Id>,
iced_native::event::Event,
)| {
*window_id == Some(id) || *window_id == None
},
);
// Only retain events which have not been processed for next iteration
events.retain(|el| remaining.contains(el));
let window_events: Vec<_> = filtered
.into_iter()
.map(|(_id, event)| event)
.collect();
if !redraw_pending
&& window_events.is_empty()
&& messages.is_empty()
{
continue;
}
// Process winit events for window
debug.event_processing_started();
let cursor_position =
states.get(&id).unwrap().cursor_position();
let (interface_state, statuses) = {
let user_interface = interfaces.get_mut(&id).unwrap();
user_interface.update(
&window_events,
cursor_position,
&mut renderer,
&mut clipboard,
&mut messages,
)
};
for event in
window_events.into_iter().zip(statuses.into_iter())
{
runtime.broadcast(event);
}
debug.event_processing_finished();
// Update application with app messages
// Unless we implement some kind of diffing, we must redraw all windows as we
// cannot know what changed.
if !messages.is_empty()
|| matches!(
interface_state,
user_interface::State::Outdated,
)
{
let mut cached_interfaces: HashMap<_, _> =
ManuallyDrop::into_inner(interfaces)
.drain()
.map(
|(id, interface): (
window::Id,
UserInterface<'_, _, _>,
)| {
(id, interface.into_cache())
},
)
.collect();
// Update application
update(
&mut application,
&mut cached_interfaces,
&states,
&mut renderer,
&mut runtime,
&mut clipboard,
&mut proxy,
&mut debug,
&mut messages,
&windows,
|| compositor.fetch_information(),
);
// synchronize window states with application states.
for (id, state) in states.iter_mut() {
state.synchronize(
&application,
*id,
windows
.get(id)
.expect("No window found with ID."),
);
}
interfaces = ManuallyDrop::new(build_user_interfaces(
&application,
&mut renderer,
&mut debug,
&states,
cached_interfaces,
));
if application.should_exit() {
break 'main;
}
}
// TODO: Avoid redrawing all the time by forcing widgets to
// request redraws on state changes
//
// Then, we can use the `interface_state` here to decide if a redraw
// is needed right away, or simply wait until a specific time.
let redraw_event = iced_native::Event::Window(
id,
window::Event::RedrawRequested(Instant::now()),
);
let (interface_state, _) =
interfaces.get_mut(&id).unwrap().update(
&[redraw_event.clone()],
cursor_position,
&mut renderer,
&mut clipboard,
&mut messages,
);
debug.draw_started();
let new_mouse_interaction = {
let state = states.get(&id).unwrap();
interfaces.get_mut(&id).unwrap().draw(
&mut renderer,
state.theme(),
&renderer::Style {
text_color: state.text_color(),
},
state.cursor_position(),
)
};
debug.draw_finished();
let window = windows.get(&id).unwrap();
if new_mouse_interaction != mouse_interaction {
window.set_cursor_icon(conversion::mouse_interaction(
new_mouse_interaction,
));
mouse_interaction = new_mouse_interaction;
}
for window in windows.values() {
window.request_redraw();
}
runtime.broadcast((
redraw_event.clone(),
crate::event::Status::Ignored,
));
let _ = control_sender.start_send(match interface_state {
user_interface::State::Updated {
redraw_request: Some(redraw_request),
} => match redraw_request {
window::RedrawRequest::NextFrame => {
ControlFlow::Poll
}
window::RedrawRequest::At(at) => {
ControlFlow::WaitUntil(at)
}
},
_ => ControlFlow::Wait,
});
redraw_pending = false;
}
}
event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(
event::MacOS::ReceivedUrl(url),
)) => {
use iced_native::event;
events.push((
None,
iced_native::Event::PlatformSpecific(
event::PlatformSpecific::MacOS(
event::MacOS::ReceivedUrl(url),
),
),
));
}
event::Event::UserEvent(event) => match event {
Event::Application(message) => {
messages.push(message);
}
Event::WindowCreated(id, window) => {
let mut surface = compositor.create_surface(&window);
let state = State::new(&application, id, &window);
let physical_size = state.physical_size();
compositor.configure_surface(
&mut surface,
physical_size.width,
physical_size.height,
);
let user_interface = build_user_interface(
&application,
user_interface::Cache::default(),
&mut renderer,
state.logical_size(),
&mut debug,
id,
);
let _ = states.insert(id, state);
let _ = surfaces.insert(id, surface);
let _ = interfaces.insert(id, user_interface);
let _ = window_ids.insert(window.id(), id);
let _ = windows.insert(id, window);
let _ = caches.insert(id, user_interface::Cache::default());
}
Event::CloseWindow(id) => {
if let Some(window) = windows.get(&id) {
if window_ids.remove(&window.id()).is_none() {
log::error!("Failed to remove window with id {:?} from window_ids.", window.id());
}
} else {
log::error!(
"Could not find window with id {:?} in windows.",
id
);
}
if states.remove(&id).is_none() {
log::error!(
"Failed to remove window {:?} from states.",
id
);
}
if interfaces.remove(&id).is_none() {
log::error!(
"Failed to remove window {:?} from interfaces.",
id
);
}
if windows.remove(&id).is_none() {
log::error!(
"Failed to remove window {:?} from windows.",
id
);
}
if surfaces.remove(&id).is_none() {
log::error!(
"Failed to remove window {:?} from surfaces.",
id
);
}
if windows.is_empty() {
log::info!(
"All windows are closed. Terminating program."
);
break 'main;
} else {
log::info!("Remaining windows: {:?}", windows.len());
}
}
Event::NewWindow { .. } => unreachable!(),
},
event::Event::RedrawRequested(id) => {
#[cfg(feature = "trace")]
let _ = info_span!("Application", "FRAME").entered();
let state = window_ids
.get(&id)
.and_then(|id| states.get_mut(id))
.unwrap();
let surface = window_ids
.get(&id)
.and_then(|id| surfaces.get_mut(id))
.unwrap();
let physical_size = state.physical_size();
if physical_size.width == 0 || physical_size.height == 0 {
continue;
}
debug.render_started();
if state.viewport_changed() {
let mut user_interface = window_ids
.get(&id)
.and_then(|id| interfaces.remove(id))
.unwrap();
let logical_size = state.logical_size();
debug.layout_started();
user_interface =
user_interface.relayout(logical_size, &mut renderer);
debug.layout_finished();
debug.draw_started();
let new_mouse_interaction = {
let state = &state;
user_interface.draw(
&mut renderer,
state.theme(),
&renderer::Style {
text_color: state.text_color(),
},
state.cursor_position(),
)
};
let window = window_ids
.get(&id)
.and_then(|id| windows.get(id))
.unwrap();
if new_mouse_interaction != mouse_interaction {
window.set_cursor_icon(conversion::mouse_interaction(
new_mouse_interaction,
));
mouse_interaction = new_mouse_interaction;
}
debug.draw_finished();
let _ = interfaces
.insert(*window_ids.get(&id).unwrap(), user_interface);
compositor.configure_surface(
surface,
physical_size.width,
physical_size.height,
);
}
match compositor.present(
&mut renderer,
surface,
state.viewport(),
state.background_color(),
&debug.overlay(),
) {
Ok(()) => {
debug.render_finished();
// TODO: Handle animations!
// Maybe we can use `ControlFlow::WaitUntil` for this.
}
Err(error) => match error {
// This is an unrecoverable error.
compositor::SurfaceError::OutOfMemory => {
panic!("{:?}", error);
}
_ => {
debug.render_finished();
log::error!("Error {error:?} when presenting surface.");
// Try rendering windows again next frame.
for window in windows.values() {
window.request_redraw();
}
}
},
}
}
event::Event::WindowEvent {
event: window_event,
window_id,
} => {
if let (Some(window), Some(state)) = (
window_ids.get(&window_id).and_then(|id| windows.get(id)),
window_ids
.get(&window_id)
.and_then(|id| states.get_mut(id)),
) {
if crate::application::requests_exit(&window_event, state.modifiers()) {
if let Some(id) = window_ids.get(&window_id).cloned() {
let message = application.close_requested(id);
messages.push(message);
}
}
state.update(window, &window_event, &mut debug);
if let Some(event) = conversion::window_event(
*window_ids.get(&window_id).unwrap(),
&window_event,
state.scale_factor(),
state.modifiers(),
) {
events
.push((window_ids.get(&window_id).cloned(), event));
}
} else {
log::error!(
"Could not find window or state for id: {window_id:?}"
);
}
}
_ => {}
}
}
// Manually drop the user interfaces
drop(ManuallyDrop::into_inner(interfaces));
}
/// Builds a window's [`UserInterface`] for the [`Application`].
pub fn build_user_interface<'a, A: Application>(
application: &'a A,
cache: user_interface::Cache,
renderer: &mut A::Renderer,
size: Size,
debug: &mut Debug,
id: window::Id,
) -> UserInterface<'a, A::Message, A::Renderer>
where
<A::Renderer as crate::Renderer>::Theme: StyleSheet,
{
#[cfg(feature = "trace")]
let view_span = info_span!("Application", "VIEW").entered();
debug.view_started();
let view = application.view(id);
#[cfg(feature = "trace")]
let _ = view_span.exit();
debug.view_finished();
#[cfg(feature = "trace")]
let layout_span = info_span!("Application", "LAYOUT").entered();
debug.layout_started();
let user_interface = UserInterface::build(view, size, cache, renderer);
#[cfg(feature = "trace")]
let _ = layout_span.exit();
debug.layout_finished();
user_interface
}
/// Updates an [`Application`] by feeding it messages, spawning any
/// resulting [`Command`], and tracking its [`Subscription`].
pub fn update<A: Application, E: Executor>(
application: &mut A,
caches: &mut HashMap<window::Id, user_interface::Cache>,
states: &HashMap<window::Id, State<A>>,
renderer: &mut A::Renderer,
runtime: &mut Runtime<E, Proxy<Event<A::Message>>, Event<A::Message>>,
clipboard: &mut Clipboard,
proxy: &mut winit::event_loop::EventLoopProxy<Event<A::Message>>,
debug: &mut Debug,
messages: &mut Vec<A::Message>,
windows: &HashMap<window::Id, winit::window::Window>,
graphics_info: impl FnOnce() -> compositor::Information + Copy,
) where
A: Application + 'static,
<A::Renderer as crate::Renderer>::Theme: StyleSheet,
{
for message in messages.drain(..) {
#[cfg(feature = "trace")]
let update_span = info_span!("Application", "UPDATE").entered();
debug.log_message(&message);
debug.update_started();
let command = runtime.enter(|| application.update(message));
#[cfg(feature = "trace")]
let _ = update_span.exit();
debug.update_finished();
run_command(
application,
caches,
states,
renderer,
command,
runtime,
clipboard,
proxy,
debug,
windows,
graphics_info,
);
}
let subscription = application.subscription().map(Event::Application);
runtime.track(subscription);
}
/// Runs the actions of a [`Command`].
pub fn run_command<A, E>(
application: &A,
caches: &mut HashMap<window::Id, user_interface::Cache>,
states: &HashMap<window::Id, State<A>>,
renderer: &mut A::Renderer,
command: Command<A::Message>,
runtime: &mut Runtime<E, Proxy<Event<A::Message>>, Event<A::Message>>,
clipboard: &mut Clipboard,
proxy: &mut winit::event_loop::EventLoopProxy<Event<A::Message>>,
debug: &mut Debug,
windows: &HashMap<window::Id, winit::window::Window>,
_graphics_info: impl FnOnce() -> compositor::Information + Copy,
) where
A: Application + 'static,
E: Executor,
<A::Renderer as crate::Renderer>::Theme: StyleSheet,
{
use iced_native::command;
use iced_native::system;
use iced_native::window;
for action in command.actions() {
match action {
command::Action::Future(future) => {
runtime.spawn(Box::pin(future.map(Event::Application)));
}
command::Action::Clipboard(action) => match action {
clipboard::Action::Read(tag) => {
let message = tag(clipboard.read());
proxy
.send_event(Event::Application(message))
.expect("Send message to event loop");
}
clipboard::Action::Write(contents) => {
clipboard.write(contents);
}
},
command::Action::Window(id, action) => match action {
window::Action::Spawn { settings } => {
proxy
.send_event(Event::NewWindow {
id,
settings: settings.into(),
title: application.title(id),
})
.expect("Send message to event loop");
}
window::Action::Close => {
proxy
.send_event(Event::CloseWindow(id))
.expect("Send message to event loop");
}
window::Action::Drag => {
let window = windows.get(&id).expect("No window found");
let _res = window.drag_window();
}
window::Action::Resize { width, height } => {
let window = windows.get(&id).expect("No window found");
window.set_inner_size(winit::dpi::LogicalSize {
width,
height,
});
}
window::Action::Move { x, y } => {
let window = windows.get(&id).expect("No window found");
window.set_outer_position(winit::dpi::LogicalPosition {
x,
y,
});
}
window::Action::ChangeMode(mode) => {
let window = windows.get(&id).expect("No window found");
window.set_visible(conversion::visible(mode));
window.set_fullscreen(conversion::fullscreen(
window.current_monitor(),
mode,
));
}
window::Action::FetchMode(tag) => {
let window = windows.get(&id).expect("No window found");
let mode = if window.is_visible().unwrap_or(true) {
conversion::mode(window.fullscreen())
} else {
window::Mode::Hidden
};
proxy
.send_event(Event::Application(tag(mode)))
.expect("Send message to event loop");
}
window::Action::Maximize(value) => {
let window = windows.get(&id).expect("No window found!");
window.set_maximized(value);
}
window::Action::Minimize(value) => {
let window = windows.get(&id).expect("No window found!");
window.set_minimized(value);
}
window::Action::ToggleMaximize => {
let window = windows.get(&id).expect("No window found!");
window.set_maximized(!window.is_maximized());
}
window::Action::ToggleDecorations => {
let window = windows.get(&id).expect("No window found!");
window.set_decorations(!window.is_decorated());
}
window::Action::RequestUserAttention(attention_type) => {
let window = windows.get(&id).expect("No window found!");
window.request_user_attention(
attention_type.map(conversion::user_attention),
);
}
window::Action::GainFocus => {
let window = windows.get(&id).expect("No window found!");
window.focus_window();
}
window::Action::ChangeAlwaysOnTop(on_top) => {
let window = windows.get(&id).expect("No window found!");
window.set_always_on_top(on_top);
}
window::Action::FetchId(tag) => {
let window = windows.get(&id).expect("No window found!");
proxy
.send_event(Event::Application(tag(window.id().into())))
.expect("Send message to event loop.")
}
},
command::Action::System(action) => match action {
system::Action::QueryInformation(_tag) => {
#[cfg(feature = "system")]
{
let graphics_info = _graphics_info();
let proxy = proxy.clone();
let _ = std::thread::spawn(move || {
let information =
crate::system::information(graphics_info);
let message = _tag(information);
proxy
.send_event(Event::Application(message))
.expect("Send message to event loop")
});
}
}
},
command::Action::Widget(action) => {
let mut current_caches = std::mem::take(caches);
let mut current_operation = Some(action.into_operation());
let mut user_interfaces = build_user_interfaces(
application,
renderer,
debug,
states,
current_caches,
);
while let Some(mut operation) = current_operation.take() {
for user_interface in user_interfaces.values_mut() {
user_interface.operate(renderer, operation.as_mut());
match operation.finish() {
operation::Outcome::None => {}
operation::Outcome::Some(message) => {
proxy
.send_event(Event::Application(message))
.expect("Send message to event loop");
}
operation::Outcome::Chain(next) => {
current_operation = Some(next);
}
}
}
}
let user_interfaces: HashMap<_, _> = user_interfaces
.drain()
.map(|(id, interface)| (id, interface.into_cache()))
.collect();
current_caches = user_interfaces;
*caches = current_caches;
}
}
}
}
/// Build the user interfaces for every window.
pub fn build_user_interfaces<'a, A>(
application: &'a A,
renderer: &mut A::Renderer,
debug: &mut Debug,
states: &HashMap<window::Id, State<A>>,
mut cached_user_interfaces: HashMap<window::Id, user_interface::Cache>,
) -> HashMap<
window::Id,
UserInterface<
'a,
<A as Application>::Message,
<A as Application>::Renderer,
>,
>
where
A: Application + 'static,
<A::Renderer as crate::Renderer>::Theme: StyleSheet,
{
let mut interfaces = HashMap::new();
for (id, cache) in cached_user_interfaces.drain() {
let state = &states.get(&id).unwrap();
let user_interface = build_user_interface(
application,
cache,
renderer,
state.logical_size(),
debug,
id,
);
let _ = interfaces.insert(id, user_interface);
}
interfaces
}
#[cfg(not(target_arch = "wasm32"))]
mod platform {
pub fn run<T, F>(
mut event_loop: winit::event_loop::EventLoop<T>,
event_handler: F,
) -> Result<(), super::Error>
where
F: 'static
+ FnMut(
winit::event::Event<'_, T>,
&winit::event_loop::EventLoopWindowTarget<T>,
&mut winit::event_loop::ControlFlow,
),
{
use winit::platform::run_return::EventLoopExtRunReturn;
let _ = event_loop.run_return(event_handler);
Ok(())
}
}
#[cfg(target_arch = "wasm32")]
mod platform {
pub fn run<T, F>(
event_loop: winit::event_loop::EventLoop<T>,
event_handler: F,
) -> !
where
F: 'static
+ FnMut(
winit::event::Event<'_, T>,
&winit::event_loop::EventLoopWindowTarget<T>,
&mut winit::event_loop::ControlFlow,
),
{
event_loop.run(event_handler)
}
}