2019-11-22 22:14:39 +01:00
//! A windowing shell for Iced, on top of [`winit`].
//!
2022-05-02 20:25:47 +02:00
//! 
2019-11-22 22:14:39 +01:00
//!
2023-07-28 19:48:39 +02:00
//! `iced_winit` offers some convenient abstractions on top of [`iced_runtime`]
2019-11-22 22:14:39 +01:00
//! to quickstart development when using [`winit`].
//!
2024-06-19 01:53:40 +02:00
//! It exposes a renderer-agnostic [`Program`] trait that can be implemented
2019-11-22 22:14:39 +01:00
//! and then run with a simple call. The use of this trait is optional.
//!
//! Additionally, a [`conversion`] module is available for users that decide to
//! implement a custom event loop.
//!
2025-12-05 22:52:06 +01:00
//! [`iced_runtime`]: https://github.com/iced-rs/iced/tree/master/runtime
2019-11-22 22:14:39 +01:00
//! [`winit`]: https://github.com/rust-windowing/winit
2020-11-25 07:11:27 +01:00
//! [`conversion`]: crate::conversion
2021-12-08 08:04:46 +01:00
#![ doc(
2021-12-09 15:10:38 +07:00
html_logo_url = " https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg "
2021-12-08 08:04:46 +01:00
) ]
2025-10-08 04:37:13 +02:00
#![ cfg_attr(docsrs, feature(doc_cfg)) ]
2025-11-02 01:36:05 +01:00
pub use iced_debug as debug ;
2025-03-12 02:10:42 +01:00
pub use iced_program as program ;
pub use program ::core ;
pub use program ::graphics ;
pub use program ::runtime ;
pub use runtime ::futures ;
2019-10-03 00:08:16 +02:00
pub use winit ;
2019-10-09 05:36:49 +02:00
2021-09-02 13:46:01 +07:00
pub mod clipboard ;
2019-10-10 05:44:33 +02:00
pub mod conversion ;
2024-06-19 01:53:40 +02:00
2020-09-08 00:35:17 +02:00
mod error ;
2020-01-19 10:17:08 +01:00
mod proxy ;
2025-03-12 02:10:42 +01:00
mod window ;
2019-10-09 05:36:49 +02:00
2020-02-01 22:05:25 -05:00
pub use clipboard ::Clipboard ;
2020-09-08 00:35:17 +02:00
pub use error ::Error ;
2020-05-21 00:37:47 +02:00
pub use proxy ::Proxy ;
2024-06-19 01:53:40 +02:00
2025-03-12 02:10:42 +01:00
use crate ::core ::mouse ;
use crate ::core ::renderer ;
use crate ::core ::theme ;
use crate ::core ::time ::Instant ;
use crate ::core ::widget ::operation ;
2025-08-29 08:39:44 +02:00
use crate ::core ::{ Point , Size } ;
2025-03-12 02:10:42 +01:00
use crate ::futures ::futures ::channel ::mpsc ;
use crate ::futures ::futures ::channel ::oneshot ;
use crate ::futures ::futures ::task ;
use crate ::futures ::futures ::{ Future , StreamExt } ;
use crate ::futures ::subscription ;
use crate ::futures ::{ Executor , Runtime } ;
2025-10-24 17:23:40 +02:00
use crate ::graphics ::{ Compositor , Shell , compositor } ;
2025-10-25 00:07:13 +02:00
use crate ::runtime ::image ;
2025-09-08 14:32:24 +02:00
use crate ::runtime ::system ;
2025-03-12 02:10:42 +01:00
use crate ::runtime ::user_interface ::{ self , UserInterface } ;
use crate ::runtime ::{ Action , Task } ;
use program ::Program ;
use window ::WindowManager ;
use rustc_hash ::FxHashMap ;
use std ::borrow ::Cow ;
use std ::mem ::ManuallyDrop ;
2025-08-07 22:36:02 +02:00
use std ::slice ;
2025-03-12 02:10:42 +01:00
use std ::sync ::Arc ;
/// Runs a [`Program`] with the provided settings.
2025-08-29 08:39:44 +02:00
pub fn run < P > ( program : P ) -> Result < ( ) , Error >
2025-03-12 02:10:42 +01:00
where
P : Program + 'static ,
P ::Theme : theme ::Base ,
{
use winit ::event_loop ::EventLoop ;
let boot_span = debug ::boot ( ) ;
2025-08-29 08:39:44 +02:00
let settings = program . settings ( ) ;
let window_settings = program . window ( ) ;
2025-03-12 02:10:42 +01:00
let event_loop = EventLoop ::with_user_event ( )
. build ( )
. expect ( " Create event loop " ) ;
2025-10-23 15:19:45 +02:00
let graphics_settings = settings . clone ( ) . into ( ) ;
let display_handle = event_loop . owned_display_handle ( ) ;
2025-03-12 02:10:42 +01:00
let ( proxy , worker ) = Proxy ::new ( event_loop . create_proxy ( ) ) ;
2025-06-06 22:58:59 +02:00
#[ cfg(feature = " debug " ) ]
{
let proxy = proxy . clone ( ) ;
debug ::on_hotpatch ( move | | {
proxy . send_action ( Action ::Reload ) ;
} ) ;
}
2025-03-12 02:10:42 +01:00
let mut runtime = {
let executor =
P ::Executor ::new ( ) . map_err ( Error ::ExecutorCreationFailed ) ? ;
executor . spawn ( worker ) ;
Runtime ::new ( executor , proxy . clone ( ) )
} ;
let ( program , task ) = runtime . enter ( | | program ::Instance ::new ( program ) ) ;
let is_daemon = window_settings . is_none ( ) ;
let task = if let Some ( window_settings ) = window_settings {
let mut task = Some ( task ) ;
2025-08-27 10:59:45 +02:00
let ( _id , open ) = runtime ::window ::open ( window_settings ) ;
2025-03-12 02:10:42 +01:00
2025-09-08 09:49:11 +02:00
open . then ( move | _ | task . take ( ) . unwrap_or_else ( Task ::none ) )
2025-03-12 02:10:42 +01:00
} else {
task
} ;
if let Some ( stream ) = runtime ::task ::into_stream ( task ) {
runtime . run ( stream ) ;
}
runtime . track ( subscription ::into_recipes (
runtime . enter ( | | program . subscription ( ) . map ( Action ::Output ) ) ,
) ) ;
let ( event_sender , event_receiver ) = mpsc ::unbounded ( ) ;
let ( control_sender , control_receiver ) = mpsc ::unbounded ( ) ;
2025-09-08 14:32:24 +02:00
let ( system_theme_sender , system_theme_receiver ) = oneshot ::channel ( ) ;
2025-03-12 02:10:42 +01:00
let instance = Box ::pin ( run_instance ::< P > (
program ,
runtime ,
proxy . clone ( ) ,
event_receiver ,
control_sender ,
2025-10-23 15:19:45 +02:00
display_handle ,
2025-03-12 02:10:42 +01:00
is_daemon ,
graphics_settings ,
settings . fonts ,
2025-09-08 14:32:24 +02:00
system_theme_receiver ,
2025-03-12 02:10:42 +01:00
) ) ;
let context = task ::Context ::from_waker ( task ::noop_waker_ref ( ) ) ;
struct Runner < Message : 'static , F > {
instance : std ::pin ::Pin < Box < F > > ,
context : task ::Context < 'static > ,
id : Option < String > ,
sender : mpsc ::UnboundedSender < Event < Action < Message > > > ,
receiver : mpsc ::UnboundedReceiver < Control > ,
error : Option < Error > ,
2025-09-08 14:32:24 +02:00
system_theme : Option < oneshot ::Sender < theme ::Mode > > ,
2025-03-12 02:10:42 +01:00
#[ cfg(target_arch = " wasm32 " ) ]
canvas : Option < web_sys ::HtmlCanvasElement > ,
}
let runner = Runner {
instance ,
context ,
id : settings . id ,
sender : event_sender ,
receiver : control_receiver ,
error : None ,
2025-09-08 14:32:24 +02:00
system_theme : Some ( system_theme_sender ) ,
2025-03-12 02:10:42 +01:00
#[ cfg(target_arch = " wasm32 " ) ]
canvas : None ,
} ;
boot_span . finish ( ) ;
impl < Message , F > winit ::application ::ApplicationHandler < Action < Message > >
for Runner < Message , F >
where
F : Future < Output = ( ) > ,
{
2025-09-08 14:32:24 +02:00
fn resumed ( & mut self , event_loop : & winit ::event_loop ::ActiveEventLoop ) {
if let Some ( sender ) = self . system_theme . take ( ) {
let _ = sender . send (
event_loop
. system_theme ( )
. map ( conversion ::theme_mode )
. unwrap_or_default ( ) ,
) ;
}
2025-03-12 02:10:42 +01:00
}
fn new_events (
& mut self ,
event_loop : & winit ::event_loop ::ActiveEventLoop ,
cause : winit ::event ::StartCause ,
) {
self . process_event (
event_loop ,
Event ::EventLoopAwakened ( winit ::event ::Event ::NewEvents ( cause ) ) ,
) ;
}
fn window_event (
& mut self ,
event_loop : & winit ::event_loop ::ActiveEventLoop ,
window_id : winit ::window ::WindowId ,
event : winit ::event ::WindowEvent ,
) {
#[ cfg(target_os = " windows " ) ]
let is_move_or_resize = matches! (
event ,
winit ::event ::WindowEvent ::Resized ( _ )
| winit ::event ::WindowEvent ::Moved ( _ )
) ;
self . process_event (
event_loop ,
Event ::EventLoopAwakened ( winit ::event ::Event ::WindowEvent {
window_id ,
event ,
} ) ,
) ;
// TODO: Remove when unnecessary
// On Windows, we emulate an `AboutToWait` event after every `Resized` event
// since the event loop does not resume during resize interaction.
// More details: https://github.com/rust-windowing/winit/issues/3272
#[ cfg(target_os = " windows " ) ]
{
if is_move_or_resize {
self . process_event (
event_loop ,
Event ::EventLoopAwakened (
winit ::event ::Event ::AboutToWait ,
) ,
) ;
}
}
}
fn user_event (
& mut self ,
event_loop : & winit ::event_loop ::ActiveEventLoop ,
action : Action < Message > ,
) {
self . process_event (
event_loop ,
Event ::EventLoopAwakened ( winit ::event ::Event ::UserEvent (
action ,
) ) ,
) ;
}
fn about_to_wait (
& mut self ,
event_loop : & winit ::event_loop ::ActiveEventLoop ,
) {
self . process_event (
event_loop ,
Event ::EventLoopAwakened ( winit ::event ::Event ::AboutToWait ) ,
) ;
}
}
impl < Message , F > Runner < Message , F >
where
F : Future < Output = ( ) > ,
{
fn process_event (
& mut self ,
event_loop : & winit ::event_loop ::ActiveEventLoop ,
event : Event < Action < Message > > ,
) {
if event_loop . exiting ( ) {
return ;
}
self . sender . start_send ( event ) . expect ( " Send event " ) ;
loop {
let poll = self . instance . as_mut ( ) . poll ( & mut self . context ) ;
match poll {
task ::Poll ::Pending = > match self . receiver . try_next ( ) {
Ok ( Some ( control ) ) = > match control {
Control ::ChangeFlow ( flow ) = > {
use winit ::event_loop ::ControlFlow ;
match ( event_loop . control_flow ( ) , flow ) {
(
ControlFlow ::WaitUntil ( current ) ,
ControlFlow ::WaitUntil ( new ) ,
) if current < new = > { }
(
ControlFlow ::WaitUntil ( target ) ,
ControlFlow ::Wait ,
) if target > Instant ::now ( ) = > { }
_ = > {
event_loop . set_control_flow ( flow ) ;
}
}
}
Control ::CreateWindow {
id ,
settings ,
title ,
2025-09-02 23:29:22 +02:00
scale_factor ,
2025-03-12 02:10:42 +01:00
monitor ,
on_open ,
} = > {
let exit_on_close_request =
settings . exit_on_close_request ;
let visible = settings . visible ;
#[ cfg(target_arch = " wasm32 " ) ]
let target =
settings . platform_specific . target . clone ( ) ;
let window_attributes =
conversion ::window_attributes (
settings ,
& title ,
2025-09-02 23:29:22 +02:00
scale_factor ,
2025-03-12 02:10:42 +01:00
monitor
. or ( event_loop . primary_monitor ( ) ) ,
self . id . clone ( ) ,
)
. with_visible ( false ) ;
#[ cfg(target_arch = " wasm32 " ) ]
let window_attributes = {
use winit ::platform ::web ::WindowAttributesExtWebSys ;
window_attributes
. with_canvas ( self . canvas . take ( ) )
} ;
log ::info! (
" Window attributes for id `{id:#?}`: {window_attributes:#?} "
) ;
// On macOS, the `position` in `WindowAttributes` represents the "inner"
// position of the window; while on other platforms it's the "outer" position.
// We fix the inconsistency on macOS by positioning the window after creation.
#[ cfg(target_os = " macos " ) ]
let mut window_attributes = window_attributes ;
#[ cfg(target_os = " macos " ) ]
let position =
window_attributes . position . take ( ) ;
let window = event_loop
. create_window ( window_attributes )
. expect ( " Create window " ) ;
#[ cfg(target_os = " macos " ) ]
if let Some ( position ) = position {
window . set_outer_position ( position ) ;
}
#[ cfg(target_arch = " wasm32 " ) ]
{
use winit ::platform ::web ::WindowExtWebSys ;
let canvas = window
. canvas ( )
. expect ( " Get window canvas " ) ;
let _ = canvas . set_attribute (
" style " ,
" display: block; width: 100%; height: 100% " ,
) ;
let window = web_sys ::window ( ) . unwrap ( ) ;
let document = window . document ( ) . unwrap ( ) ;
let body = document . body ( ) . unwrap ( ) ;
let target = target . and_then ( | target | {
body . query_selector ( & format! (
" #{target} "
) )
. ok ( )
. unwrap_or ( None )
} ) ;
match target {
Some ( node ) = > {
let _ = node
. replace_with_with_node_1 (
& canvas ,
)
. expect ( & format! (
" Could not replace #{} " ,
node . id ( )
) ) ;
}
None = > {
let _ = body
. append_child ( & canvas )
. expect (
" Append canvas to HTML body " ,
) ;
}
} ;
}
self . process_event (
event_loop ,
Event ::WindowCreated {
id ,
window : Arc ::new ( window ) ,
exit_on_close_request ,
make_visible : visible ,
on_open ,
} ,
) ;
}
Control ::Exit = > {
2025-09-06 06:02:11 +02:00
self . process_event ( event_loop , Event ::Exit ) ;
2025-03-12 02:10:42 +01:00
event_loop . exit ( ) ;
2025-09-06 06:02:11 +02:00
break ;
2025-03-12 02:10:42 +01:00
}
Control ::Crash ( error ) = > {
self . error = Some ( error ) ;
event_loop . exit ( ) ;
}
2025-11-25 23:13:36 +01:00
Control ::SetAutomaticWindowTabbing ( _enabled ) = > {
#[ cfg(target_os = " macos " ) ]
{
use winit ::platform ::macos ::ActiveEventLoopExtMacOS ;
event_loop
. set_allows_automatic_window_tabbing (
_enabled ,
) ;
}
}
2025-03-12 02:10:42 +01:00
} ,
_ = > {
break ;
}
} ,
task ::Poll ::Ready ( _ ) = > {
event_loop . exit ( ) ;
break ;
}
} ;
}
}
}
#[ cfg(not(target_arch = " wasm32 " )) ]
{
let mut runner = runner ;
let _ = event_loop . run_app ( & mut runner ) ;
runner . error . map ( Err ) . unwrap_or ( Ok ( ( ) ) )
}
#[ cfg(target_arch = " wasm32 " ) ]
{
use winit ::platform ::web ::EventLoopExtWebSys ;
let _ = event_loop . spawn_app ( runner ) ;
Ok ( ( ) )
}
}
#[ derive(Debug) ]
enum Event < Message : 'static > {
WindowCreated {
id : window ::Id ,
window : Arc < winit ::window ::Window > ,
exit_on_close_request : bool ,
make_visible : bool ,
on_open : oneshot ::Sender < window ::Id > ,
} ,
EventLoopAwakened ( winit ::event ::Event < Message > ) ,
2025-09-06 06:02:11 +02:00
Exit ,
2025-03-12 02:10:42 +01:00
}
#[ derive(Debug) ]
enum Control {
ChangeFlow ( winit ::event_loop ::ControlFlow ) ,
Exit ,
Crash ( Error ) ,
CreateWindow {
id : window ::Id ,
settings : window ::Settings ,
title : String ,
monitor : Option < winit ::monitor ::MonitorHandle > ,
on_open : oneshot ::Sender < window ::Id > ,
2025-09-02 23:29:22 +02:00
scale_factor : f32 ,
2025-03-12 02:10:42 +01:00
} ,
2025-11-25 23:13:36 +01:00
SetAutomaticWindowTabbing ( bool ) ,
2025-03-12 02:10:42 +01:00
}
async fn run_instance < P > (
mut program : program ::Instance < P > ,
mut runtime : Runtime < P ::Executor , Proxy < P ::Message > , Action < P ::Message > > ,
mut proxy : Proxy < P ::Message > ,
mut event_receiver : mpsc ::UnboundedReceiver < Event < Action < P ::Message > > > ,
mut control_sender : mpsc ::UnboundedSender < Control > ,
2025-10-23 15:19:45 +02:00
display_handle : winit ::event_loop ::OwnedDisplayHandle ,
2025-03-12 02:10:42 +01:00
is_daemon : bool ,
graphics_settings : graphics ::Settings ,
default_fonts : Vec < Cow < 'static , [ u8 ] > > ,
2025-09-08 14:32:24 +02:00
mut _system_theme : oneshot ::Receiver < theme ::Mode > ,
2025-03-12 02:10:42 +01:00
) where
P : Program + 'static ,
P ::Theme : theme ::Base ,
{
use winit ::event ;
use winit ::event_loop ::ControlFlow ;
let mut window_manager = WindowManager ::new ( ) ;
let mut is_window_opening = ! is_daemon ;
let mut compositor = None ;
let mut events = Vec ::new ( ) ;
let mut messages = Vec ::new ( ) ;
let mut actions = 0 ;
let mut ui_caches = FxHashMap ::default ( ) ;
let mut user_interfaces = ManuallyDrop ::new ( FxHashMap ::default ( ) ) ;
let mut clipboard = Clipboard ::unconnected ( ) ;
2025-09-08 09:49:11 +02:00
#[ cfg(all(feature = " linux-theme-detection " , target_os = " linux " )) ]
2025-09-08 14:32:24 +02:00
let mut system_theme = {
2025-09-08 09:49:11 +02:00
let to_mode = | color_scheme | match color_scheme {
mundy ::ColorScheme ::NoPreference = > theme ::Mode ::None ,
mundy ::ColorScheme ::Light = > theme ::Mode ::Light ,
mundy ::ColorScheme ::Dark = > theme ::Mode ::Dark ,
} ;
runtime . run (
mundy ::Preferences ::stream ( mundy ::Interest ::ColorScheme )
. map ( move | preferences | {
2025-09-08 14:32:24 +02:00
Action ::System ( system ::Action ::NotifyTheme ( to_mode (
preferences . color_scheme ,
) ) )
2025-09-08 09:49:11 +02:00
} )
. boxed ( ) ,
) ;
2025-10-16 02:43:29 +02:00
runtime
. enter ( | | {
mundy ::Preferences ::once_blocking (
mundy ::Interest ::ColorScheme ,
core ::time ::Duration ::from_millis ( 200 ) ,
)
} )
. map ( | preferences | to_mode ( preferences . color_scheme ) )
. unwrap_or_default ( )
2025-09-08 09:49:11 +02:00
} ;
#[ cfg(not(all(feature = " linux-theme-detection " , target_os = " linux " ))) ]
2025-09-08 14:32:24 +02:00
let mut system_theme =
_system_theme . try_recv ( ) . ok ( ) . flatten ( ) . unwrap_or_default ( ) ;
log ::info! ( " System theme: {system_theme:?} " ) ;
2025-09-08 09:49:11 +02:00
2025-10-15 20:04:44 +02:00
' next_event : loop {
2025-03-12 02:10:42 +01:00
// Empty the queue if possible
2025-04-05 18:20:31 +02:00
let event = if let Ok ( event ) = event_receiver . try_next ( ) {
2025-03-12 02:10:42 +01:00
event
} else {
event_receiver . next ( ) . await
} ;
let Some ( event ) = event else {
break ;
} ;
match event {
Event ::WindowCreated {
id ,
window ,
exit_on_close_request ,
make_visible ,
on_open ,
} = > {
if compositor . is_none ( ) {
2025-04-05 18:20:31 +02:00
let ( compositor_sender , compositor_receiver ) =
2025-03-12 02:10:42 +01:00
oneshot ::channel ( ) ;
let create_compositor = {
2025-04-05 18:20:31 +02:00
let window = window . clone ( ) ;
2025-10-23 15:19:45 +02:00
let display_handle = display_handle . clone ( ) ;
2025-06-06 22:58:59 +02:00
let proxy = proxy . clone ( ) ;
2025-03-12 02:10:42 +01:00
let default_fonts = default_fonts . clone ( ) ;
async move {
2025-10-24 17:23:40 +02:00
let shell = Shell ::new ( proxy . clone ( ) ) ;
2025-03-12 02:10:42 +01:00
let mut compositor =
2025-10-23 15:19:45 +02:00
< P ::Renderer as compositor ::Default > ::Compositor ::new (
graphics_settings ,
display_handle ,
window ,
shell ,
) . await ;
2025-03-12 02:10:42 +01:00
if let Ok ( compositor ) = & mut compositor {
for font in default_fonts {
compositor . load_font ( font . clone ( ) ) ;
}
}
compositor_sender
2025-04-05 18:20:31 +02:00
. send ( compositor )
2025-03-12 02:10:42 +01:00
. ok ( )
. expect ( " Send compositor " ) ;
2025-04-05 18:20:31 +02:00
// HACK! Send a proxy event on completion to trigger
// a runtime re-poll
// TODO: Send compositor through proxy (?)
{
let ( sender , _receiver ) = oneshot ::channel ( ) ;
proxy . send_action ( Action ::Window (
runtime ::window ::Action ::GetLatest ( sender ) ,
) ) ;
}
2025-03-12 02:10:42 +01:00
}
} ;
2025-04-05 18:20:31 +02:00
#[ cfg(target_arch = " wasm32 " ) ]
wasm_bindgen_futures ::spawn_local ( create_compositor ) ;
2025-03-12 02:10:42 +01:00
#[ cfg(not(target_arch = " wasm32 " )) ]
2025-04-05 18:20:31 +02:00
runtime . block_on ( create_compositor ) ;
2025-03-12 02:10:42 +01:00
2025-04-05 18:20:31 +02:00
match compositor_receiver
. await
. expect ( " Wait for compositor " )
2025-03-12 02:10:42 +01:00
{
2025-04-05 18:20:31 +02:00
Ok ( new_compositor ) = > {
compositor = Some ( new_compositor ) ;
}
Err ( error ) = > {
let _ = control_sender
. start_send ( Control ::Crash ( error . into ( ) ) ) ;
continue ;
}
2025-03-12 02:10:42 +01:00
}
}
2025-09-10 18:08:01 +02:00
let window_theme = window
. theme ( )
. map ( conversion ::theme_mode )
. unwrap_or_default ( ) ;
if system_theme ! = window_theme {
system_theme = window_theme ;
runtime . broadcast ( subscription ::Event ::SystemThemeChanged (
window_theme ,
) ) ;
}
2025-03-13 00:42:28 +01:00
2025-09-11 02:46:44 +02:00
let is_first = window_manager . is_empty ( ) ;
2025-03-12 02:10:42 +01:00
let window = window_manager . insert (
id ,
window ,
& program ,
compositor
. as_mut ( )
. expect ( " Compositor must be initialized " ) ,
exit_on_close_request ,
2025-09-11 02:46:44 +02:00
system_theme ,
2025-03-12 02:10:42 +01:00
) ;
2025-09-08 10:12:04 +02:00
window . raw . set_theme ( conversion ::window_theme (
window . state . theme_mode ( ) ,
) ) ;
2025-09-08 05:16:20 +02:00
debug ::theme_changed ( | | {
if is_first {
theme ::Base ::palette ( window . state . theme ( ) )
} else {
None
}
} ) ;
2025-03-12 02:10:42 +01:00
let logical_size = window . state . logical_size ( ) ;
let _ = user_interfaces . insert (
id ,
build_user_interface (
& program ,
user_interface ::Cache ::default ( ) ,
& mut window . renderer ,
logical_size ,
id ,
) ,
) ;
let _ = ui_caches . insert ( id , user_interface ::Cache ::default ( ) ) ;
if make_visible {
window . raw . set_visible ( true ) ;
}
events . push ( (
id ,
core ::Event ::Window ( window ::Event ::Opened {
position : window . position ( ) ,
2025-10-29 18:28:18 +01:00
size : window . logical_size ( ) ,
2025-03-12 02:10:42 +01:00
} ) ,
) ) ;
if clipboard . window_id ( ) . is_none ( ) {
clipboard = Clipboard ::connect ( window . raw . clone ( ) ) ;
}
let _ = on_open . send ( id ) ;
is_window_opening = false ;
}
Event ::EventLoopAwakened ( event ) = > {
match event {
event ::Event ::NewEvents ( event ::StartCause ::Init ) = > {
for ( _id , window ) in window_manager . iter_mut ( ) {
window . raw . request_redraw ( ) ;
}
}
event ::Event ::NewEvents (
event ::StartCause ::ResumeTimeReached { .. } ,
) = > {
let now = Instant ::now ( ) ;
for ( _id , window ) in window_manager . iter_mut ( ) {
2025-08-07 22:36:02 +02:00
if let Some ( redraw_at ) = window . redraw_at
& & redraw_at < = now
{
window . raw . request_redraw ( ) ;
window . redraw_at = None ;
2025-03-12 02:10:42 +01:00
}
}
if let Some ( redraw_at ) = window_manager . redraw_at ( ) {
let _ =
control_sender . start_send ( Control ::ChangeFlow (
ControlFlow ::WaitUntil ( redraw_at ) ,
) ) ;
} else {
let _ = control_sender . start_send (
Control ::ChangeFlow ( ControlFlow ::Wait ) ,
) ;
}
}
event ::Event ::UserEvent ( action ) = > {
run_action (
action ,
& program ,
2025-09-08 14:32:24 +02:00
& mut runtime ,
2025-03-12 02:10:42 +01:00
& mut compositor ,
& mut events ,
& mut messages ,
& mut clipboard ,
& mut control_sender ,
& mut user_interfaces ,
& mut window_manager ,
& mut ui_caches ,
& mut is_window_opening ,
2025-09-08 14:32:24 +02:00
& mut system_theme ,
2025-03-12 02:10:42 +01:00
) ;
actions + = 1 ;
}
event ::Event ::WindowEvent {
window_id : id ,
event : event ::WindowEvent ::RedrawRequested ,
..
} = > {
2025-10-15 20:04:44 +02:00
let Some ( mut current_compositor ) = compositor . as_mut ( )
else {
2025-03-12 02:10:42 +01:00
continue ;
} ;
2025-10-15 19:08:39 +02:00
let Some ( ( id , mut window ) ) =
2025-03-12 02:10:42 +01:00
window_manager . get_mut_alias ( id )
else {
continue ;
} ;
let physical_size = window . state . physical_size ( ) ;
2025-10-21 23:56:06 +02:00
let mut logical_size = window . state . logical_size ( ) ;
2025-03-12 02:10:42 +01:00
if physical_size . width = = 0 | | physical_size . height = = 0
{
continue ;
}
2025-10-21 23:56:06 +02:00
// Window was resized between redraws
2025-10-29 18:28:18 +01:00
if window . surface_version
! = window . state . surface_version ( )
{
2025-03-12 02:10:42 +01:00
let ui = user_interfaces
. remove ( & id )
. expect ( " Remove user interface " ) ;
2025-10-21 23:56:06 +02:00
let layout_span = debug ::layout ( id ) ;
2025-03-12 02:10:42 +01:00
let _ = user_interfaces . insert (
id ,
ui . relayout ( logical_size , & mut window . renderer ) ,
) ;
layout_span . finish ( ) ;
2025-10-15 20:04:44 +02:00
current_compositor . configure_surface (
2025-03-12 02:10:42 +01:00
& mut window . surface ,
physical_size . width ,
physical_size . height ,
) ;
2025-10-29 18:28:18 +01:00
window . surface_version =
window . state . surface_version ( ) ;
2025-03-12 02:10:42 +01:00
}
let redraw_event = core ::Event ::Window (
window ::Event ::RedrawRequested ( Instant ::now ( ) ) ,
) ;
let cursor = window . state . cursor ( ) ;
2025-10-15 19:08:39 +02:00
let mut interface = user_interfaces
2025-03-12 02:10:42 +01:00
. get_mut ( & id )
. expect ( " Get user interface " ) ;
2025-10-24 17:23:40 +02:00
let interact_span = debug ::interact ( id ) ;
2025-11-28 22:13:51 +01:00
let mut redraw_count = 0 ;
2025-10-15 19:08:39 +02:00
let state = loop {
2025-10-18 05:30:38 +02:00
let message_count = messages . len ( ) ;
2025-10-15 19:08:39 +02:00
let ( state , _ ) = interface . update (
slice ::from_ref ( & redraw_event ) ,
cursor ,
& mut window . renderer ,
& mut clipboard ,
& mut messages ,
) ;
2025-10-18 05:30:38 +02:00
if message_count = = messages . len ( )
2025-10-15 19:08:39 +02:00
& & ! state . has_layout_changed ( )
{
break state ;
}
2025-11-28 22:13:51 +01:00
if redraw_count > = 2 {
2025-10-15 19:08:39 +02:00
log ::warn! (
2025-11-28 22:13:51 +01:00
" More than 3 consecutive RedrawRequested events \
2025-10-15 19:08:39 +02:00
produced layout invalidation "
) ;
break state ;
}
2025-11-28 22:13:51 +01:00
redraw_count + = 1 ;
2025-10-15 21:59:13 +02:00
if ! messages . is_empty ( ) {
let caches : FxHashMap < _ , _ > =
ManuallyDrop ::into_inner ( user_interfaces )
. into_iter ( )
. map ( | ( id , interface ) | {
( id , interface . into_cache ( ) )
} )
. collect ( ) ;
let actions = update (
& mut program ,
2025-10-15 20:04:44 +02:00
& mut runtime ,
& mut messages ,
) ;
2025-10-15 21:59:13 +02:00
user_interfaces =
ManuallyDrop ::new ( build_user_interfaces (
& program ,
& mut window_manager ,
caches ,
) ) ;
for action in actions {
2025-10-16 18:42:57 +02:00
// Defer all window actions to avoid compositor
// race conditions while redrawing
if let Action ::Window ( _ ) = action {
2025-10-16 18:46:29 +02:00
proxy . send_action ( action ) ;
2025-10-16 18:42:57 +02:00
continue ;
2025-10-16 17:25:04 +02:00
}
2025-10-16 17:02:23 +02:00
2025-10-15 21:59:13 +02:00
run_action (
action ,
& program ,
& mut runtime ,
& mut compositor ,
& mut events ,
& mut messages ,
& mut clipboard ,
& mut control_sender ,
& mut user_interfaces ,
& mut window_manager ,
& mut ui_caches ,
& mut is_window_opening ,
& mut system_theme ,
) ;
}
2025-10-15 20:04:44 +02:00
2025-10-16 17:25:04 +02:00
for ( window_id , window ) in
window_manager . iter_mut ( )
{
// We are already redrawing this window
if window_id = = id {
continue ;
}
window . raw . request_redraw ( ) ;
}
2025-10-15 21:59:13 +02:00
let Some ( next_compositor ) = compositor . as_mut ( )
else {
continue 'next_event ;
} ;
current_compositor = next_compositor ;
window = window_manager . get_mut ( id ) . unwrap ( ) ;
2025-10-21 23:56:06 +02:00
// Window scale factor changed during a redraw request
if logical_size ! = window . state . logical_size ( ) {
logical_size = window . state . logical_size ( ) ;
log ::debug! (
" Window scale factor changed during a redraw request "
) ;
let ui = user_interfaces
. remove ( & id )
. expect ( " Remove user interface " ) ;
let layout_span = debug ::layout ( id ) ;
let _ = user_interfaces . insert (
id ,
ui . relayout (
logical_size ,
& mut window . renderer ,
) ,
) ;
layout_span . finish ( ) ;
}
2025-10-15 21:59:13 +02:00
interface =
user_interfaces . get_mut ( & id ) . unwrap ( ) ;
}
2025-10-15 19:08:39 +02:00
} ;
2025-10-24 17:23:40 +02:00
interact_span . finish ( ) ;
2025-10-15 19:08:39 +02:00
2025-10-24 17:23:40 +02:00
let draw_span = debug ::draw ( id ) ;
2025-10-15 19:08:39 +02:00
interface . draw (
2025-03-12 02:10:42 +01:00
& mut window . renderer ,
window . state . theme ( ) ,
& renderer ::Style {
text_color : window . state . text_color ( ) ,
} ,
cursor ,
) ;
draw_span . finish ( ) ;
if let user_interface ::State ::Updated {
redraw_request ,
input_method ,
2025-05-02 23:11:47 +02:00
mouse_interaction ,
2025-10-15 19:08:39 +02:00
..
} = state
2025-03-12 02:10:42 +01:00
{
window . request_redraw ( redraw_request ) ;
window . request_input_method ( input_method ) ;
2025-05-02 23:11:47 +02:00
window . update_mouse ( mouse_interaction ) ;
2025-03-12 02:10:42 +01:00
}
2025-10-15 19:08:39 +02:00
runtime . broadcast ( subscription ::Event ::Interaction {
window : id ,
event : redraw_event ,
status : core ::event ::Status ::Ignored ,
} ) ;
2025-03-12 02:10:42 +01:00
window . draw_preedit ( ) ;
let present_span = debug ::present ( id ) ;
2025-10-15 20:04:44 +02:00
match current_compositor . present (
2025-03-12 02:10:42 +01:00
& mut window . renderer ,
& mut window . surface ,
window . state . viewport ( ) ,
window . state . background_color ( ) ,
2025-04-01 02:18:20 +02:00
| | window . raw . pre_present_notify ( ) ,
2025-03-12 02:10:42 +01:00
) {
Ok ( ( ) ) = > {
present_span . finish ( ) ;
}
Err ( error ) = > match error {
compositor ::SurfaceError ::OutOfMemory = > {
2025-10-01 23:10:10 +13:00
// This is an unrecoverable error.
2025-06-27 00:00:49 +02:00
panic! ( " {error:?} " ) ;
2025-03-12 02:10:42 +01:00
}
2025-10-01 23:10:10 +13:00
compositor ::SurfaceError ::Outdated
| compositor ::SurfaceError ::Lost = > {
present_span . finish ( ) ;
// Reconfigure surface and try redrawing
let physical_size =
window . state . physical_size ( ) ;
2025-11-29 15:01:16 +01:00
if error = = compositor ::SurfaceError ::Lost {
window . surface = current_compositor
. create_surface (
window . raw . clone ( ) ,
physical_size . width ,
physical_size . height ,
) ;
} else {
current_compositor . configure_surface (
& mut window . surface ,
physical_size . width ,
physical_size . height ,
) ;
}
2025-10-01 23:10:10 +13:00
window . raw . request_redraw ( ) ;
}
2025-03-12 02:10:42 +01:00
_ = > {
present_span . finish ( ) ;
log ::error! (
" Error {error:?} when \
presenting surface . "
) ;
// Try rendering all windows again next frame.
for ( _id , window ) in
window_manager . iter_mut ( )
{
window . raw . request_redraw ( ) ;
}
}
} ,
}
}
event ::Event ::WindowEvent {
event : window_event ,
window_id ,
} = > {
if ! is_daemon
& & matches! (
window_event ,
winit ::event ::WindowEvent ::Destroyed
)
& & ! is_window_opening
& & window_manager . is_empty ( )
{
control_sender
. start_send ( Control ::Exit )
. expect ( " Send control action " ) ;
continue ;
}
let Some ( ( id , window ) ) =
window_manager . get_mut_alias ( window_id )
else {
continue ;
} ;
2025-09-08 14:32:24 +02:00
match window_event {
winit ::event ::WindowEvent ::Resized ( _ ) = > {
window . raw . request_redraw ( ) ;
}
winit ::event ::WindowEvent ::ThemeChanged ( theme ) = > {
let mode = conversion ::theme_mode ( theme ) ;
if mode ! = system_theme {
system_theme = mode ;
runtime . broadcast (
subscription ::Event ::SystemThemeChanged (
mode ,
) ,
) ;
}
}
_ = > { }
2025-03-12 02:10:42 +01:00
}
if matches! (
window_event ,
winit ::event ::WindowEvent ::CloseRequested
) & & window . exit_on_close_request
{
run_action (
Action ::Window ( runtime ::window ::Action ::Close (
id ,
) ) ,
& program ,
2025-09-08 14:32:24 +02:00
& mut runtime ,
2025-03-12 02:10:42 +01:00
& mut compositor ,
& mut events ,
& mut messages ,
& mut clipboard ,
& mut control_sender ,
& mut user_interfaces ,
& mut window_manager ,
& mut ui_caches ,
& mut is_window_opening ,
2025-09-08 14:32:24 +02:00
& mut system_theme ,
2025-03-12 02:10:42 +01:00
) ;
} else {
2025-09-08 09:49:11 +02:00
window . state . update (
& program ,
& window . raw ,
& window_event ,
) ;
2025-03-12 02:10:42 +01:00
if let Some ( event ) = conversion ::window_event (
window_event ,
window . state . scale_factor ( ) ,
window . state . modifiers ( ) ,
) {
events . push ( ( id , event ) ) ;
}
}
}
event ::Event ::AboutToWait = > {
if actions > 0 {
proxy . free_slots ( actions ) ;
actions = 0 ;
}
if events . is_empty ( )
& & messages . is_empty ( )
& & window_manager . is_idle ( )
{
continue ;
}
let mut uis_stale = false ;
for ( id , window ) in window_manager . iter_mut ( ) {
let interact_span = debug ::interact ( id ) ;
let mut window_events = vec! [ ] ;
events . retain ( | ( window_id , event ) | {
if * window_id = = id {
window_events . push ( event . clone ( ) ) ;
false
} else {
true
}
} ) ;
if window_events . is_empty ( ) & & messages . is_empty ( ) {
continue ;
}
let ( ui_state , statuses ) = user_interfaces
. get_mut ( & id )
. expect ( " Get user interface " )
. update (
& window_events ,
window . state . cursor ( ) ,
& mut window . renderer ,
& mut clipboard ,
& mut messages ,
) ;
#[ cfg(feature = " unconditional-rendering " ) ]
window . request_redraw (
window ::RedrawRequest ::NextFrame ,
) ;
match ui_state {
user_interface ::State ::Updated {
redraw_request : _redraw_request ,
2025-05-02 23:11:47 +02:00
mouse_interaction ,
2025-03-12 02:10:42 +01:00
..
} = > {
2025-05-02 23:11:47 +02:00
window . update_mouse ( mouse_interaction ) ;
2025-03-12 02:10:42 +01:00
#[ cfg(not(
feature = " unconditional-rendering "
) ) ]
window . request_redraw ( _redraw_request ) ;
}
user_interface ::State ::Outdated = > {
uis_stale = true ;
}
}
for ( event , status ) in window_events
. into_iter ( )
. zip ( statuses . into_iter ( ) )
{
runtime . broadcast (
subscription ::Event ::Interaction {
window : id ,
event ,
status ,
} ,
) ;
}
interact_span . finish ( ) ;
}
for ( id , event ) in events . drain ( .. ) {
runtime . broadcast (
subscription ::Event ::Interaction {
window : id ,
event ,
status : core ::event ::Status ::Ignored ,
} ,
) ;
}
if ! messages . is_empty ( ) | | uis_stale {
2025-10-15 19:08:39 +02:00
let cached_interfaces : FxHashMap < _ , _ > =
ManuallyDrop ::into_inner ( user_interfaces )
. into_iter ( )
. map ( | ( id , ui ) | ( id , ui . into_cache ( ) ) )
. collect ( ) ;
2025-03-12 02:10:42 +01:00
2025-10-15 20:04:44 +02:00
let actions = update (
& mut program ,
& mut runtime ,
& mut messages ,
) ;
2025-03-12 02:10:42 +01:00
user_interfaces =
ManuallyDrop ::new ( build_user_interfaces (
& program ,
& mut window_manager ,
cached_interfaces ,
) ) ;
2025-10-15 20:04:44 +02:00
for action in actions {
run_action (
action ,
& program ,
& mut runtime ,
& mut compositor ,
& mut events ,
& mut messages ,
& mut clipboard ,
& mut control_sender ,
& mut user_interfaces ,
& mut window_manager ,
& mut ui_caches ,
& mut is_window_opening ,
& mut system_theme ,
) ;
}
2025-10-16 17:25:04 +02:00
for ( _id , window ) in window_manager . iter_mut ( ) {
window . raw . request_redraw ( ) ;
}
2025-03-12 02:10:42 +01:00
}
if let Some ( redraw_at ) = window_manager . redraw_at ( ) {
let _ =
control_sender . start_send ( Control ::ChangeFlow (
ControlFlow ::WaitUntil ( redraw_at ) ,
) ) ;
} else {
let _ = control_sender . start_send (
Control ::ChangeFlow ( ControlFlow ::Wait ) ,
) ;
}
}
_ = > { }
}
}
2025-09-06 06:02:11 +02:00
Event ::Exit = > break ,
2025-03-12 02:10:42 +01:00
}
}
let _ = ManuallyDrop ::into_inner ( user_interfaces ) ;
}
/// Builds a window's [`UserInterface`] for the [`Program`].
fn build_user_interface < ' a , P : Program > (
program : & ' a program ::Instance < P > ,
cache : user_interface ::Cache ,
renderer : & mut P ::Renderer ,
size : Size ,
id : window ::Id ,
) -> UserInterface < ' a , P ::Message , P ::Theme , P ::Renderer >
where
P ::Theme : theme ::Base ,
{
let view_span = debug ::view ( id ) ;
let view = program . view ( id ) ;
view_span . finish ( ) ;
let layout_span = debug ::layout ( id ) ;
let user_interface = UserInterface ::build ( view , size , cache , renderer ) ;
layout_span . finish ( ) ;
user_interface
}
fn update < P : Program , E : Executor > (
program : & mut program ::Instance < P > ,
runtime : & mut Runtime < E , Proxy < P ::Message > , Action < P ::Message > > ,
messages : & mut Vec < P ::Message > ,
2025-10-15 20:04:44 +02:00
) -> Vec < Action < P ::Message > >
where
2025-03-12 02:10:42 +01:00
P ::Theme : theme ::Base ,
{
2025-10-15 20:04:44 +02:00
use futures ::futures ;
let mut actions = Vec ::new ( ) ;
2025-03-12 02:10:42 +01:00
for message in messages . drain ( .. ) {
let task = runtime . enter ( | | program . update ( message ) ) ;
2025-10-15 20:04:44 +02:00
if let Some ( mut stream ) = runtime ::task ::into_stream ( task ) {
let waker = futures ::task ::noop_waker_ref ( ) ;
let mut context = futures ::task ::Context ::from_waker ( waker ) ;
// Run immediately available actions synchronously (e.g. widget operations)
loop {
match runtime . enter ( | | stream . poll_next_unpin ( & mut context ) ) {
futures ::task ::Poll ::Ready ( Some ( action ) ) = > {
actions . push ( action ) ;
}
futures ::task ::Poll ::Ready ( None ) = > {
break ;
}
futures ::task ::Poll ::Pending = > {
runtime . run ( stream ) ;
break ;
}
}
}
2025-03-12 02:10:42 +01:00
}
}
let subscription = runtime . enter ( | | program . subscription ( ) ) ;
2025-03-12 16:29:42 +01:00
let recipes = subscription ::into_recipes ( subscription . map ( Action ::Output ) ) ;
runtime . track ( recipes ) ;
2025-10-15 20:04:44 +02:00
actions
2025-03-12 02:10:42 +01:00
}
2025-06-06 22:58:59 +02:00
fn run_action < ' a , P , C > (
2025-03-12 02:10:42 +01:00
action : Action < P ::Message > ,
2025-06-06 22:58:59 +02:00
program : & ' a program ::Instance < P > ,
2025-09-08 14:32:24 +02:00
runtime : & mut Runtime < P ::Executor , Proxy < P ::Message > , Action < P ::Message > > ,
2025-03-12 02:10:42 +01:00
compositor : & mut Option < C > ,
events : & mut Vec < ( window ::Id , core ::Event ) > ,
messages : & mut Vec < P ::Message > ,
clipboard : & mut Clipboard ,
control_sender : & mut mpsc ::UnboundedSender < Control > ,
interfaces : & mut FxHashMap <
window ::Id ,
2025-06-06 22:58:59 +02:00
UserInterface < ' a , P ::Message , P ::Theme , P ::Renderer > ,
2025-03-12 02:10:42 +01:00
> ,
window_manager : & mut WindowManager < P , C > ,
ui_caches : & mut FxHashMap < window ::Id , user_interface ::Cache > ,
is_window_opening : & mut bool ,
2025-09-08 14:32:24 +02:00
system_theme : & mut theme ::Mode ,
2025-03-12 02:10:42 +01:00
) where
P : Program ,
C : Compositor < Renderer = P ::Renderer > + 'static ,
P ::Theme : theme ::Base ,
{
use crate ::runtime ::clipboard ;
use crate ::runtime ::window ;
match action {
Action ::Output ( message ) = > {
messages . push ( message ) ;
}
Action ::Clipboard ( action ) = > match action {
clipboard ::Action ::Read { target , channel } = > {
let _ = channel . send ( clipboard . read ( target ) ) ;
}
clipboard ::Action ::Write { target , contents } = > {
clipboard . write ( target , contents ) ;
}
} ,
Action ::Window ( action ) = > match action {
2025-08-27 10:59:45 +02:00
window ::Action ::Open ( id , settings , channel ) = > {
2025-03-12 02:10:42 +01:00
let monitor = window_manager . last_monitor ( ) ;
control_sender
. start_send ( Control ::CreateWindow {
id ,
settings ,
title : program . title ( id ) ,
2025-09-02 23:29:22 +02:00
scale_factor : program . scale_factor ( id ) ,
2025-03-12 02:10:42 +01:00
monitor ,
on_open : channel ,
} )
. expect ( " Send control action " ) ;
* is_window_opening = true ;
}
window ::Action ::Close ( id ) = > {
let _ = ui_caches . remove ( & id ) ;
let _ = interfaces . remove ( & id ) ;
if let Some ( window ) = window_manager . remove ( id ) {
if clipboard . window_id ( ) = = Some ( window . raw . id ( ) ) {
* clipboard = window_manager
. first ( )
. map ( | window | window . raw . clone ( ) )
. map ( Clipboard ::connect )
. unwrap_or_else ( Clipboard ::unconnected ) ;
}
events . push ( (
id ,
core ::Event ::Window ( core ::window ::Event ::Closed ) ,
) ) ;
}
if window_manager . is_empty ( ) {
* compositor = None ;
}
}
window ::Action ::GetOldest ( channel ) = > {
let id =
window_manager . iter_mut ( ) . next ( ) . map ( | ( id , _window ) | id ) ;
let _ = channel . send ( id ) ;
}
window ::Action ::GetLatest ( channel ) = > {
let id =
window_manager . iter_mut ( ) . last ( ) . map ( | ( id , _window ) | id ) ;
let _ = channel . send ( id ) ;
}
window ::Action ::Drag ( id ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
let _ = window . raw . drag_window ( ) ;
}
}
window ::Action ::DragResize ( id , direction ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
let _ = window . raw . drag_resize_window (
conversion ::resize_direction ( direction ) ,
) ;
}
}
window ::Action ::Resize ( id , size ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
let _ = window . raw . request_inner_size (
winit ::dpi ::LogicalSize {
width : size . width ,
height : size . height ,
2025-09-02 23:33:42 +02:00
}
. to_physical ::< f32 > ( f64 ::from (
window . state . scale_factor ( ) ,
) ) ,
2025-03-12 02:10:42 +01:00
) ;
}
}
window ::Action ::SetMinSize ( id , size ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
window . raw . set_min_inner_size ( size . map ( | size | {
winit ::dpi ::LogicalSize {
width : size . width ,
height : size . height ,
}
2025-09-02 23:33:42 +02:00
. to_physical ::< f32 > ( f64 ::from (
window . state . scale_factor ( ) ,
) )
2025-03-12 02:10:42 +01:00
} ) ) ;
}
}
window ::Action ::SetMaxSize ( id , size ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
window . raw . set_max_inner_size ( size . map ( | size | {
winit ::dpi ::LogicalSize {
width : size . width ,
height : size . height ,
}
2025-09-02 23:33:42 +02:00
. to_physical ::< f32 > ( f64 ::from (
window . state . scale_factor ( ) ,
) )
2025-03-12 02:10:42 +01:00
} ) ) ;
}
}
window ::Action ::SetResizeIncrements ( id , increments ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
window . raw . set_resize_increments ( increments . map ( | size | {
winit ::dpi ::LogicalSize {
width : size . width ,
height : size . height ,
}
2025-09-02 23:33:42 +02:00
. to_physical ::< f32 > ( f64 ::from (
window . state . scale_factor ( ) ,
) )
2025-03-12 02:10:42 +01:00
} ) ) ;
}
}
window ::Action ::SetResizable ( id , resizable ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
window . raw . set_resizable ( resizable ) ;
}
}
window ::Action ::GetSize ( id , channel ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
2025-10-29 18:28:18 +01:00
let size = window . logical_size ( ) ;
2025-03-12 02:10:42 +01:00
let _ = channel . send ( Size ::new ( size . width , size . height ) ) ;
}
}
window ::Action ::GetMaximized ( id , channel ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
let _ = channel . send ( window . raw . is_maximized ( ) ) ;
}
}
window ::Action ::Maximize ( id , maximized ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
window . raw . set_maximized ( maximized ) ;
}
}
window ::Action ::GetMinimized ( id , channel ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
let _ = channel . send ( window . raw . is_minimized ( ) ) ;
}
}
window ::Action ::Minimize ( id , minimized ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
window . raw . set_minimized ( minimized ) ;
}
}
window ::Action ::GetPosition ( id , channel ) = > {
if let Some ( window ) = window_manager . get ( id ) {
let position = window
. raw
. outer_position ( )
. map ( | position | {
let position = position
. to_logical ::< f32 > ( window . raw . scale_factor ( ) ) ;
Point ::new ( position . x , position . y )
} )
. ok ( ) ;
let _ = channel . send ( position ) ;
}
}
window ::Action ::GetScaleFactor ( id , channel ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
let scale_factor = window . raw . scale_factor ( ) ;
let _ = channel . send ( scale_factor as f32 ) ;
}
}
window ::Action ::Move ( id , position ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
window . raw . set_outer_position (
winit ::dpi ::LogicalPosition {
x : position . x ,
y : position . y ,
} ,
) ;
}
}
window ::Action ::SetMode ( id , mode ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
window . raw . set_visible ( conversion ::visible ( mode ) ) ;
window . raw . set_fullscreen ( conversion ::fullscreen (
window . raw . current_monitor ( ) ,
mode ,
) ) ;
}
}
window ::Action ::SetIcon ( id , icon ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
window . raw . set_window_icon ( conversion ::icon ( icon ) ) ;
}
}
window ::Action ::GetMode ( id , channel ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
let mode = if window . raw . is_visible ( ) . unwrap_or ( true ) {
conversion ::mode ( window . raw . fullscreen ( ) )
} else {
core ::window ::Mode ::Hidden
} ;
let _ = channel . send ( mode ) ;
}
}
window ::Action ::ToggleMaximize ( id ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
window . raw . set_maximized ( ! window . raw . is_maximized ( ) ) ;
}
}
window ::Action ::ToggleDecorations ( id ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
window . raw . set_decorations ( ! window . raw . is_decorated ( ) ) ;
}
}
window ::Action ::RequestUserAttention ( id , attention_type ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
window . raw . request_user_attention (
attention_type . map ( conversion ::user_attention ) ,
) ;
}
}
window ::Action ::GainFocus ( id ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
window . raw . focus_window ( ) ;
}
}
window ::Action ::SetLevel ( id , level ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
window
. raw
. set_window_level ( conversion ::window_level ( level ) ) ;
}
}
window ::Action ::ShowSystemMenu ( id ) = > {
2025-08-07 22:36:02 +02:00
if let Some ( window ) = window_manager . get_mut ( id )
& & let mouse ::Cursor ::Available ( point ) =
2025-03-12 02:10:42 +01:00
window . state . cursor ( )
2025-08-07 22:36:02 +02:00
{
window . raw . show_window_menu ( winit ::dpi ::LogicalPosition {
x : point . x ,
y : point . y ,
} ) ;
2025-03-12 02:10:42 +01:00
}
}
window ::Action ::GetRawId ( id , channel ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
let _ = channel . send ( window . raw . id ( ) . into ( ) ) ;
}
}
2025-11-20 00:20:24 +01:00
window ::Action ::Run ( id , f ) = > {
2025-11-20 00:44:36 +01:00
if let Some ( window ) = window_manager . get_mut ( id ) {
f ( window ) ;
2025-03-12 02:10:42 +01:00
}
}
window ::Action ::Screenshot ( id , channel ) = > {
2025-08-07 22:36:02 +02:00
if let Some ( window ) = window_manager . get_mut ( id )
& & let Some ( compositor ) = compositor
{
let bytes = compositor . screenshot (
& mut window . renderer ,
window . state . viewport ( ) ,
window . state . background_color ( ) ,
) ;
2025-03-12 02:10:42 +01:00
2025-08-07 22:36:02 +02:00
let _ = channel . send ( core ::window ::Screenshot ::new (
bytes ,
window . state . physical_size ( ) ,
2025-01-27 03:33:03 +09:00
window . state . scale_factor ( ) ,
2025-08-07 22:36:02 +02:00
) ) ;
2025-03-12 02:10:42 +01:00
}
}
window ::Action ::EnableMousePassthrough ( id ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
let _ = window . raw . set_cursor_hittest ( false ) ;
}
}
window ::Action ::DisableMousePassthrough ( id ) = > {
if let Some ( window ) = window_manager . get_mut ( id ) {
let _ = window . raw . set_cursor_hittest ( true ) ;
}
}
2025-01-27 03:33:03 +09:00
window ::Action ::GetMonitorSize ( id , channel ) = > {
if let Some ( window ) = window_manager . get ( id ) {
let size = window . raw . current_monitor ( ) . map ( | monitor | {
let scale = window . state . scale_factor ( ) ;
let size = monitor . size ( ) . to_logical ( f64 ::from ( scale ) ) ;
Size ::new ( size . width , size . height )
} ) ;
let _ = channel . send ( size ) ;
}
}
2025-11-25 23:13:36 +01:00
window ::Action ::SetAllowAutomaticTabbing ( enabled ) = > {
control_sender
. start_send ( Control ::SetAutomaticWindowTabbing ( enabled ) )
. expect ( " Send control action " ) ;
}
2025-10-24 17:23:40 +02:00
window ::Action ::RedrawAll = > {
for ( _id , window ) in window_manager . iter_mut ( ) {
window . raw . request_redraw ( ) ;
}
}
window ::Action ::RelayoutAll = > {
for ( id , window ) in window_manager . iter_mut ( ) {
if let Some ( ui ) = interfaces . remove ( & id ) {
let _ = interfaces . insert (
id ,
ui . relayout (
window . state . logical_size ( ) ,
& mut window . renderer ,
) ,
) ;
}
window . raw . request_redraw ( ) ;
}
}
2025-03-12 02:10:42 +01:00
} ,
Action ::System ( action ) = > match action {
2025-09-08 14:32:24 +02:00
system ::Action ::GetInformation ( _channel ) = > {
#[ cfg(feature = " sysinfo " ) ]
2025-03-12 02:10:42 +01:00
{
if let Some ( compositor ) = compositor {
2025-09-08 14:32:24 +02:00
let graphics_info = compositor . information ( ) ;
2025-03-12 02:10:42 +01:00
let _ = std ::thread ::spawn ( move | | {
2025-09-08 14:32:24 +02:00
let information = system_information ( graphics_info ) ;
2025-03-12 02:10:42 +01:00
let _ = _channel . send ( information ) ;
} ) ;
}
}
}
2025-09-08 14:32:24 +02:00
system ::Action ::GetTheme ( channel ) = > {
let _ = channel . send ( * system_theme ) ;
}
system ::Action ::NotifyTheme ( mode ) = > {
if mode ! = * system_theme {
* system_theme = mode ;
runtime . broadcast ( subscription ::Event ::SystemThemeChanged (
mode ,
) ) ;
}
let Some ( theme ) = conversion ::window_theme ( mode ) else {
return ;
} ;
for ( _id , window ) in window_manager . iter_mut ( ) {
window . state . update (
program ,
& window . raw ,
& winit ::event ::WindowEvent ::ThemeChanged ( theme ) ,
) ;
}
}
2025-03-12 02:10:42 +01:00
} ,
Action ::Widget ( operation ) = > {
let mut current_operation = Some ( operation ) ;
while let Some ( mut operation ) = current_operation . take ( ) {
for ( id , ui ) in interfaces . iter_mut ( ) {
if let Some ( window ) = window_manager . get_mut ( * id ) {
ui . operate ( & window . renderer , operation . as_mut ( ) ) ;
}
}
match operation . finish ( ) {
operation ::Outcome ::None = > { }
operation ::Outcome ::Some ( ( ) ) = > { }
operation ::Outcome ::Chain ( next ) = > {
current_operation = Some ( next ) ;
}
}
}
}
2025-10-25 00:07:13 +02:00
Action ::Image ( action ) = > match action {
image ::Action ::Allocate ( handle , sender ) = > {
use core ::Renderer as _ ;
// TODO: Shared image cache in compositor
if let Some ( ( _id , window ) ) = window_manager . iter_mut ( ) . next ( ) {
window . renderer . allocate_image (
& handle ,
move | allocation | {
let _ = sender . send ( allocation ) ;
} ,
) ;
}
}
} ,
2025-03-12 02:10:42 +01:00
Action ::LoadFont { bytes , channel } = > {
if let Some ( compositor ) = compositor {
// TODO: Error handling (?)
compositor . load_font ( bytes . clone ( ) ) ;
let _ = channel . send ( Ok ( ( ) ) ) ;
}
}
2025-06-06 22:58:59 +02:00
Action ::Reload = > {
for ( id , window ) in window_manager . iter_mut ( ) {
let Some ( ui ) = interfaces . remove ( & id ) else {
continue ;
} ;
let cache = ui . into_cache ( ) ;
2025-10-29 18:28:18 +01:00
let size = window . logical_size ( ) ;
2025-06-06 22:58:59 +02:00
let _ = interfaces . insert (
id ,
build_user_interface (
program ,
cache ,
& mut window . renderer ,
size ,
id ,
) ,
) ;
window . raw . request_redraw ( ) ;
}
}
2025-03-12 02:10:42 +01:00
Action ::Exit = > {
control_sender
. start_send ( Control ::Exit )
. expect ( " Send control action " ) ;
}
}
}
/// Build the user interface for every window.
pub fn build_user_interfaces < ' a , P : Program , C > (
program : & ' a program ::Instance < P > ,
window_manager : & mut WindowManager < P , C > ,
mut cached_user_interfaces : FxHashMap < window ::Id , user_interface ::Cache > ,
) -> FxHashMap < window ::Id , UserInterface < ' a , P ::Message , P ::Theme , P ::Renderer > >
where
C : Compositor < Renderer = P ::Renderer > ,
P ::Theme : theme ::Base ,
{
2025-10-15 19:08:39 +02:00
for ( id , window ) in window_manager . iter_mut ( ) {
window . state . synchronize ( program , id , & window . raw ) ;
}
debug ::theme_changed ( | | {
window_manager
. first ( )
. and_then ( | window | theme ::Base ::palette ( window . state . theme ( ) ) )
} ) ;
2025-03-12 02:10:42 +01:00
cached_user_interfaces
. drain ( )
. filter_map ( | ( id , cache ) | {
let window = window_manager . get_mut ( id ) ? ;
Some ( (
id ,
build_user_interface (
program ,
cache ,
& mut window . renderer ,
window . state . logical_size ( ) ,
id ,
) ,
) )
} )
. collect ( )
}
/// Returns true if the provided event should cause a [`Program`] to
/// exit.
pub fn user_force_quit (
event : & winit ::event ::WindowEvent ,
_modifiers : winit ::keyboard ::ModifiersState ,
) -> bool {
match event {
#[ cfg(target_os = " macos " ) ]
winit ::event ::WindowEvent ::KeyboardInput {
event :
winit ::event ::KeyEvent {
logical_key : winit ::keyboard ::Key ::Character ( c ) ,
state : winit ::event ::ElementState ::Pressed ,
..
} ,
..
} if c = = " q " & & _modifiers . super_key ( ) = > true ,
_ = > false ,
}
}
2025-09-08 14:32:24 +02:00
#[ cfg(feature = " sysinfo " ) ]
fn system_information (
graphics : compositor ::Information ,
) -> system ::Information {
use sysinfo ::{ Process , System } ;
let mut system = System ::new_all ( ) ;
system . refresh_all ( ) ;
let cpu_brand = system
. cpus ( )
. first ( )
. map ( | cpu | cpu . brand ( ) . to_string ( ) )
. unwrap_or_default ( ) ;
let memory_used = sysinfo ::get_current_pid ( )
. and_then ( | pid | system . process ( pid ) . ok_or ( " Process not found " ) )
. map ( Process ::memory )
. ok ( ) ;
system ::Information {
system_name : System ::name ( ) ,
system_kernel : System ::kernel_version ( ) ,
system_version : System ::long_os_version ( ) ,
system_short_version : System ::os_version ( ) ,
cpu_brand ,
cpu_cores : system . physical_core_count ( ) ,
memory_total : system . total_memory ( ) ,
memory_used ,
graphics_adapter : graphics . adapter ,
graphics_backend : graphics . backend ,
}
}