1#![doc(
18 html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
19)]
20#![cfg_attr(docsrs, feature(doc_cfg))]
21pub use iced_debug as debug;
22pub use iced_program as program;
23pub use iced_runtime as runtime;
24pub use program::core;
25pub use program::graphics;
26pub use runtime::futures;
27pub use winit;
28
29pub mod clipboard;
30pub mod conversion;
31
32mod error;
33mod proxy;
34mod window;
35
36pub use clipboard::Clipboard;
37pub use error::Error;
38pub use proxy::Proxy;
39
40use crate::core::backend;
41use crate::core::mouse;
42use crate::core::renderer;
43use crate::core::shell;
44use crate::core::theme;
45use crate::core::time::{Duration, Instant};
46use crate::core::widget::operation::{self, Operation};
47use crate::core::{Point, Renderer, Size};
48use crate::futures::futures::channel::mpsc;
49use crate::futures::futures::channel::oneshot;
50use crate::futures::futures::task;
51use crate::futures::futures::{Future, StreamExt};
52use crate::futures::subscription;
53use crate::futures::{Executor, Runtime};
54use crate::graphics::{Compositor, Shell, compositor};
55use crate::runtime::font;
56use crate::runtime::image;
57use crate::runtime::system;
58use crate::runtime::user_interface::{self, UserInterface};
59use crate::runtime::{Action, Task};
60
61use program::Program;
62
63use rustc_hash::FxHashMap;
64use std::borrow::Cow;
65use std::mem::ManuallyDrop;
66use std::slice;
67use std::sync::Arc;
68
69pub fn run<P>(program: P) -> Result<(), Error>
71where
72 P: Program + 'static,
73 P::Theme: theme::Base,
74{
75 use winit::event_loop::EventLoop;
76
77 let boot_span = debug::boot();
78 let settings = program.settings();
79 let window_settings = program.window();
80
81 let event_loop = EventLoop::with_user_event()
82 .build()
83 .expect("Create event loop");
84
85 let backend_settings = backend::Settings::from(&settings);
86 let renderer_settings = renderer::Settings::from(&settings);
87 let display_handle = event_loop.owned_display_handle();
88
89 let (proxy, worker) = Proxy::new(event_loop.create_proxy());
90
91 #[cfg(feature = "debug")]
92 {
93 let proxy = proxy.clone();
94
95 debug::on_hotpatch(move || {
96 proxy.send_action(Action::Reload);
97 });
98 }
99
100 let mut runtime = {
101 let executor = P::Executor::new().map_err(Error::ExecutorCreationFailed)?;
102 executor.spawn(worker);
103
104 Runtime::new(executor, proxy.clone())
105 };
106
107 let (program, task) = runtime.enter(|| program::Instance::new(program));
108 let is_daemon = window_settings.is_none();
109
110 let task = if let Some(window_settings) = window_settings {
111 let mut task = Some(task);
112
113 let (_id, open) = runtime::window::open(window_settings);
114
115 open.then(move |_| task.take().unwrap_or_else(Task::none))
116 } else {
117 task
118 };
119
120 if let Some(stream) = runtime::task::into_stream(task) {
121 runtime.run(stream);
122 }
123
124 runtime.track(subscription::into_recipes(
125 runtime.enter(|| program.subscription().map(Action::Output)),
126 ));
127
128 let (event_sender, event_receiver) = mpsc::unbounded();
129 let (control_sender, control_receiver) = mpsc::unbounded();
130 let (system_theme_sender, system_theme_receiver) = oneshot::channel();
131
132 let instance: std::pin::Pin<Box<dyn Future<Output = ()>>> = Box::pin(run_instance::<P>(
133 program,
134 runtime,
135 proxy.clone(),
136 event_receiver,
137 control_sender,
138 display_handle,
139 is_daemon,
140 backend_settings,
141 renderer_settings,
142 settings.fonts,
143 system_theme_receiver,
144 ));
145
146 let context = task::Context::from_waker(task::noop_waker_ref());
147
148 struct Runner<Message: 'static> {
149 instance: std::pin::Pin<Box<dyn Future<Output = ()>>>,
150 context: task::Context<'static>,
151 id: Option<String>,
152 sender: mpsc::UnboundedSender<Event<Action<Message>>>,
153 receiver: mpsc::UnboundedReceiver<Control>,
154 error: Option<Error>,
155 system_theme: Option<oneshot::Sender<theme::Mode>>,
156
157 #[cfg(target_arch = "wasm32")]
158 canvas: Option<web_sys::HtmlCanvasElement>,
159 }
160
161 let runner = Runner {
162 instance,
163 context,
164 id: settings.id,
165 sender: event_sender,
166 receiver: control_receiver,
167 error: None,
168 system_theme: Some(system_theme_sender),
169
170 #[cfg(target_arch = "wasm32")]
171 canvas: None,
172 };
173
174 boot_span.finish();
175
176 impl<Message> winit::application::ApplicationHandler<Action<Message>> for Runner<Message> {
177 fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
178 if let Some(sender) = self.system_theme.take() {
179 let _ = sender.send(
180 event_loop
181 .system_theme()
182 .map(conversion::theme_mode)
183 .unwrap_or_default(),
184 );
185 }
186 }
187
188 fn new_events(
189 &mut self,
190 event_loop: &winit::event_loop::ActiveEventLoop,
191 cause: winit::event::StartCause,
192 ) {
193 self.process_event(
194 event_loop,
195 Event::EventLoopAwakened(winit::event::Event::NewEvents(cause)),
196 );
197 }
198
199 fn window_event(
200 &mut self,
201 event_loop: &winit::event_loop::ActiveEventLoop,
202 window_id: winit::window::WindowId,
203 event: winit::event::WindowEvent,
204 ) {
205 #[cfg(target_os = "windows")]
206 let is_move_or_resize = matches!(
207 event,
208 winit::event::WindowEvent::Resized(_) | winit::event::WindowEvent::Moved(_)
209 );
210
211 self.process_event(
212 event_loop,
213 Event::EventLoopAwakened(winit::event::Event::WindowEvent { window_id, event }),
214 );
215
216 #[cfg(target_os = "windows")]
221 {
222 if is_move_or_resize {
223 self.process_event(
224 event_loop,
225 Event::EventLoopAwakened(winit::event::Event::AboutToWait),
226 );
227 }
228 }
229 }
230
231 fn user_event(
232 &mut self,
233 event_loop: &winit::event_loop::ActiveEventLoop,
234 action: Action<Message>,
235 ) {
236 self.process_event(
237 event_loop,
238 Event::EventLoopAwakened(winit::event::Event::UserEvent(action)),
239 );
240 }
241
242 fn received_url(&mut self, event_loop: &winit::event_loop::ActiveEventLoop, url: String) {
243 self.process_event(
244 event_loop,
245 Event::EventLoopAwakened(winit::event::Event::PlatformSpecific(
246 winit::event::PlatformSpecific::MacOS(winit::event::MacOS::ReceivedUrl(url)),
247 )),
248 );
249 }
250
251 fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
252 self.process_event(
253 event_loop,
254 Event::EventLoopAwakened(winit::event::Event::AboutToWait),
255 );
256 }
257 }
258
259 impl<Message> Runner<Message> {
260 fn process_event(
261 &mut self,
262 event_loop: &winit::event_loop::ActiveEventLoop,
263 event: Event<Action<Message>>,
264 ) {
265 if event_loop.exiting() {
266 return;
267 }
268
269 self.sender.start_send(event).expect("Send event");
270
271 loop {
272 let poll = self.instance.as_mut().poll(&mut self.context);
273
274 match poll {
275 task::Poll::Pending => match self.receiver.try_recv() {
276 Ok(control) => match control {
277 Control::ChangeFlow(flow) => {
278 use winit::event_loop::ControlFlow;
279
280 match (event_loop.control_flow(), flow) {
281 (
282 ControlFlow::WaitUntil(current),
283 ControlFlow::WaitUntil(new),
284 ) if current > Instant::now() && current < new => {}
285 (ControlFlow::WaitUntil(current), ControlFlow::Wait)
286 if current > Instant::now() => {}
287 _ => {
288 event_loop.set_control_flow(flow);
289 }
290 }
291 }
292 Control::CreateWindow {
293 id,
294 settings,
295 title,
296 scale_factor,
297 monitor,
298 on_open,
299 } => {
300 let exit_on_close_request = settings.exit_on_close_request;
301
302 let visible = settings.visible;
303
304 #[cfg(target_arch = "wasm32")]
305 let target = settings.platform_specific.target.clone();
306
307 let window_attributes = conversion::window_attributes(
308 settings,
309 &title,
310 scale_factor,
311 monitor.or(event_loop.primary_monitor()),
312 self.id.clone(),
313 )
314 .with_visible(false);
315
316 #[cfg(target_arch = "wasm32")]
317 let window_attributes = {
318 use winit::platform::web::WindowAttributesExtWebSys;
319 window_attributes.with_canvas(self.canvas.take())
320 };
321
322 log::info!(
323 "Window attributes for id `{id:#?}`: {window_attributes:#?}"
324 );
325
326 #[cfg(target_os = "macos")]
330 let mut window_attributes = window_attributes;
331
332 #[cfg(target_os = "macos")]
333 let position = window_attributes.position.take();
334
335 let window = event_loop
336 .create_window(window_attributes)
337 .expect("Create window");
338
339 #[cfg(target_os = "macos")]
340 if let Some(position) = position {
341 window.set_outer_position(position);
342 }
343
344 #[cfg(target_arch = "wasm32")]
345 {
346 use winit::platform::web::WindowExtWebSys;
347
348 let canvas = window.canvas().expect("Get window canvas");
349
350 let _ = canvas.set_attribute(
351 "style",
352 "display: block; width: 100%; height: 100%",
353 );
354
355 let window = web_sys::window().unwrap();
356 let document = window.document().unwrap();
357 let body = document.body().unwrap();
358
359 let target = target.and_then(|target| {
360 body.query_selector(&format!("#{target}"))
361 .ok()
362 .unwrap_or(None)
363 });
364
365 match target {
366 Some(node) => {
367 let _ = node.replace_with_with_node_1(&canvas).expect(
368 &format!("Could not replace #{}", node.id()),
369 );
370 }
371 None => {
372 let _ = body
373 .append_child(&canvas)
374 .expect("Append canvas to HTML body");
375 }
376 };
377 }
378
379 self.process_event(
380 event_loop,
381 Event::WindowCreated {
382 id,
383 window: Arc::new(window),
384 exit_on_close_request,
385 make_visible: visible,
386 on_open,
387 },
388 );
389 }
390 Control::Exit => {
391 self.process_event(event_loop, Event::Exit);
392 event_loop.exit();
393 break;
394 }
395 Control::Crash(error) => {
396 self.error = Some(error);
397 event_loop.exit();
398 }
399 Control::SetAutomaticWindowTabbing(_enabled) => {
400 #[cfg(target_os = "macos")]
401 {
402 use winit::platform::macos::ActiveEventLoopExtMacOS;
403 event_loop.set_allows_automatic_window_tabbing(_enabled);
404 }
405 }
406 },
407 _ => {
408 break;
409 }
410 },
411 task::Poll::Ready(_) => {
412 event_loop.exit();
413 break;
414 }
415 };
416 }
417 }
418 }
419
420 #[cfg(not(target_arch = "wasm32"))]
421 {
422 let mut runner = runner;
423 let _ = event_loop.run_app(&mut runner);
424
425 runner.error.map(Err).unwrap_or(Ok(()))
426 }
427
428 #[cfg(target_arch = "wasm32")]
429 {
430 use winit::platform::web::EventLoopExtWebSys;
431 let _ = event_loop.spawn_app(runner);
432
433 Ok(())
434 }
435}
436
437#[derive(Debug)]
438enum Event<Message: 'static> {
439 WindowCreated {
440 id: window::Id,
441 window: Arc<winit::window::Window>,
442 exit_on_close_request: bool,
443 make_visible: bool,
444 on_open: oneshot::Sender<window::Id>,
445 },
446 EventLoopAwakened(winit::event::Event<Message>),
447 Exit,
448}
449
450#[derive(Debug)]
451enum Control {
452 ChangeFlow(winit::event_loop::ControlFlow),
453 Exit,
454 Crash(Error),
455 CreateWindow {
456 id: window::Id,
457 settings: window::Settings,
458 title: String,
459 monitor: Option<winit::monitor::MonitorHandle>,
460 on_open: oneshot::Sender<window::Id>,
461 scale_factor: f32,
462 },
463 SetAutomaticWindowTabbing(bool),
464}
465
466async fn run_instance<P>(
467 mut program: program::Instance<P>,
468 mut runtime: Runtime<P::Executor, Proxy<P::Message>, Action<P::Message>>,
469 mut proxy: Proxy<P::Message>,
470 mut event_receiver: mpsc::UnboundedReceiver<Event<Action<P::Message>>>,
471 mut control_sender: mpsc::UnboundedSender<Control>,
472 display_handle: winit::event_loop::OwnedDisplayHandle,
473 is_daemon: bool,
474 backend_settings: backend::Settings,
475 mut renderer_settings: renderer::Settings,
476 default_fonts: Vec<Cow<'static, [u8]>>,
477 mut _system_theme: oneshot::Receiver<theme::Mode>,
478) where
479 P: Program + 'static,
480 P::Theme: theme::Base,
481{
482 use winit::event;
483 use winit::event_loop::ControlFlow;
484
485 let mut window_manager = window::Manager::new();
486 let mut is_window_opening = !is_daemon;
487
488 let mut compositor = None;
489 let mut events = Vec::new();
490 let mut messages = shell::Bus::new();
491 let mut actions = 0;
492
493 let mut ui_caches = FxHashMap::default();
494 let mut user_interfaces = ManuallyDrop::new(FxHashMap::default());
495 let mut clipboard = Clipboard::new();
496
497 #[cfg(all(feature = "linux-theme-detection", target_os = "linux"))]
498 let mut system_theme = {
499 let to_mode = |color_scheme| match color_scheme {
500 mundy::ColorScheme::NoPreference => theme::Mode::None,
501 mundy::ColorScheme::Light => theme::Mode::Light,
502 mundy::ColorScheme::Dark => theme::Mode::Dark,
503 };
504
505 runtime.run(
506 mundy::Preferences::stream(mundy::Interest::ColorScheme)
507 .map(move |preferences| {
508 Action::System(system::Action::NotifyTheme(to_mode(
509 preferences.color_scheme,
510 )))
511 })
512 .boxed(),
513 );
514
515 runtime
516 .enter(|| {
517 mundy::Preferences::once_blocking(
518 mundy::Interest::ColorScheme,
519 core::time::Duration::from_millis(200),
520 )
521 })
522 .map(|preferences| to_mode(preferences.color_scheme))
523 .unwrap_or_default()
524 };
525
526 #[cfg(not(all(feature = "linux-theme-detection", target_os = "linux")))]
527 let mut system_theme = _system_theme.try_recv().ok().flatten().unwrap_or_default();
528
529 log::info!("System theme: {system_theme:?}");
530
531 'next_event: loop {
532 let event = if let Ok(event) = event_receiver.try_recv() {
534 Some(event)
535 } else {
536 event_receiver.next().await
537 };
538
539 let Some(event) = event else {
540 break;
541 };
542
543 match event {
544 Event::WindowCreated {
545 id,
546 window,
547 exit_on_close_request,
548 make_visible,
549 on_open,
550 } => {
551 if compositor.is_none() {
552 let (compositor_sender, compositor_receiver) = oneshot::channel();
553
554 let create_compositor = {
555 let window = window.clone();
556 let backend_settings = backend_settings.clone();
557 let display_handle = display_handle.clone();
558 let proxy = proxy.clone();
559 let default_fonts = default_fonts.clone();
560
561 async move {
562 let shell = Shell::new(proxy.clone());
563
564 let mut compositor =
565 <P::Renderer as compositor::Default>::Compositor::new(
566 backend_settings,
567 display_handle,
568 window,
569 shell,
570 )
571 .await;
572
573 if let Ok(compositor) = &mut compositor {
574 for font in default_fonts {
575 compositor.load_font(font.clone());
576 }
577 }
578
579 compositor_sender
580 .send(compositor)
581 .ok()
582 .expect("Send compositor");
583
584 {
588 let (sender, _receiver) = oneshot::channel();
589
590 proxy.send_action(Action::Window(
591 runtime::window::Action::GetLatest(sender),
592 ));
593 }
594 }
595 };
596
597 #[cfg(target_arch = "wasm32")]
598 wasm_bindgen_futures::spawn_local(create_compositor);
599
600 #[cfg(not(target_arch = "wasm32"))]
601 runtime.block_on(create_compositor);
602
603 match compositor_receiver.await.expect("Wait for compositor") {
604 Ok(new_compositor) => {
605 compositor = Some(new_compositor);
606 }
607 Err(error) => {
608 let _ = control_sender.start_send(Control::Crash(error.into()));
609 continue;
610 }
611 }
612 }
613
614 let window_theme = window
615 .theme()
616 .map(conversion::theme_mode)
617 .unwrap_or_default();
618
619 if system_theme != window_theme {
620 system_theme = window_theme;
621
622 runtime.broadcast(subscription::Event::SystemThemeChanged(window_theme));
623 }
624
625 let is_first = window_manager.is_empty();
626 let window = window_manager.insert(
627 id,
628 window,
629 &program,
630 compositor.as_mut().expect("Compositor must be initialized"),
631 proxy.clone(),
632 renderer_settings,
633 exit_on_close_request,
634 system_theme,
635 );
636
637 window
638 .raw
639 .set_theme(conversion::window_theme(window.state.theme_mode()));
640
641 debug::theme_changed(|| {
642 if is_first {
643 theme::Base::seed(window.state.theme())
644 } else {
645 None
646 }
647 });
648
649 let logical_size = window.state.logical_size();
650
651 window.renderer.hint(window.state.scale());
652
653 let _ = user_interfaces.insert(
654 id,
655 build_user_interface(
656 &program,
657 user_interface::Cache::default(),
658 &mut window.renderer,
659 logical_size,
660 id,
661 ),
662 );
663 let _ = ui_caches.insert(id, user_interface::Cache::default());
664
665 if make_visible {
666 window.raw.set_visible(true);
667 }
668
669 events.push((
670 id,
671 core::Event::Window(window::Event::Opened {
672 position: window.position(),
673 size: window.state.logical_size(),
674 scale_factor: window.raw.scale_factor() as f32,
675 }),
676 ));
677
678 let _ = on_open.send(id);
679 is_window_opening = false;
680 }
681 Event::EventLoopAwakened(event) => {
682 match event {
683 event::Event::NewEvents(event::StartCause::Init) => {
684 for (_id, window) in window_manager.iter_mut() {
685 window.raw.request_redraw();
686 }
687 }
688 event::Event::NewEvents(event::StartCause::ResumeTimeReached { .. }) => {
689 let now = Instant::now();
690
691 for (_id, window) in window_manager.iter_mut() {
692 if let Some(redraw_at) = window.redraw_at
693 && redraw_at <= now
694 {
695 window.raw.request_redraw();
696 window.redraw_at = None;
697 }
698 }
699
700 if let Some(redraw_at) = window_manager.redraw_at() {
701 let _ = control_sender
702 .start_send(Control::ChangeFlow(ControlFlow::WaitUntil(redraw_at)));
703 } else {
704 let _ =
705 control_sender.start_send(Control::ChangeFlow(ControlFlow::Wait));
706 }
707 }
708 event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(
709 event::MacOS::ReceivedUrl(url),
710 )) => {
711 runtime.broadcast(subscription::Event::PlatformSpecific(
712 subscription::PlatformSpecific::MacOS(
713 subscription::MacOS::ReceivedUrl(url),
714 ),
715 ));
716 }
717 event::Event::UserEvent(action) => {
718 run_action(
719 action,
720 &program,
721 &proxy,
722 &mut runtime,
723 &mut compositor,
724 &mut events,
725 &mut messages,
726 &mut clipboard,
727 &mut control_sender,
728 &mut user_interfaces,
729 &mut window_manager,
730 &mut ui_caches,
731 &mut is_window_opening,
732 &mut system_theme,
733 &mut renderer_settings,
734 );
735 actions += 1;
736 }
737 event::Event::WindowEvent {
738 window_id: id,
739 event: event::WindowEvent::RedrawRequested,
740 ..
741 } => {
742 let Some(mut current_compositor) = compositor.as_mut() else {
743 continue;
744 };
745
746 let Some((id, mut window)) = window_manager.get_mut_alias(id) else {
747 continue;
748 };
749
750 let physical_size = window.state.physical_size();
751 let mut logical_size = window.state.logical_size();
752
753 if physical_size.width == 0 || physical_size.height == 0 {
754 continue;
755 }
756
757 if window.surface_version != window.state.surface_version() {
759 window.renderer.hint(window.state.scale());
760
761 let ui = user_interfaces.remove(&id).expect("Remove user interface");
762
763 let layout_span = debug::layout(id);
764 let _ = user_interfaces
765 .insert(id, ui.relayout(logical_size, &mut window.renderer));
766 layout_span.finish();
767
768 current_compositor.configure_surface(
769 &mut window.surface,
770 physical_size.width,
771 physical_size.height,
772 );
773
774 window.surface_version = window.state.surface_version();
775 }
776
777 let redraw_event =
778 core::Event::Window(window::Event::RedrawRequested(Instant::now()));
779
780 let cursor = window.state.cursor();
781
782 let mut interface =
783 user_interfaces.get_mut(&id).expect("Get user interface");
784
785 let interact_span = debug::interact(id);
786 let mut redraw_count = 0;
787
788 let state = loop {
789 let message_count = messages.len();
790 let (state, _) = interface.update(
791 &window.raw,
792 &window.waker,
793 slice::from_ref(&redraw_event),
794 cursor,
795 &mut window.renderer,
796 &mut messages,
797 );
798
799 if redraw_count >= 2 {
800 log::warn!(
801 "3 consecutive RedrawRequested events produced invalidation"
802 );
803
804 break state;
805 }
806
807 if message_count == messages.len() {
808 match state {
809 user_interface::State::Outdated => {}
810 user_interface::State::Updated { change, .. } => match change {
811 user_interface::Change::None => break state,
812 user_interface::Change::Overlay => {
813 redraw_count += 1;
814 continue;
815 }
816 user_interface::Change::Layout => {}
817 },
818 }
819 }
820
821 redraw_count += 1;
822
823 if !messages.is_empty()
824 || matches!(state, user_interface::State::Outdated)
825 {
826 let caches: FxHashMap<_, _> =
827 ManuallyDrop::into_inner(user_interfaces)
828 .into_iter()
829 .map(|(id, interface)| (id, interface.into_cache()))
830 .collect();
831
832 let actions = update(&mut program, &mut runtime, &mut messages);
833
834 user_interfaces = ManuallyDrop::new(build_user_interfaces(
835 &program,
836 &mut window_manager,
837 caches,
838 &mut proxy,
839 ));
840
841 for action in actions {
842 if let Action::Window(_) = action {
845 proxy.send_action(action);
846 continue;
847 }
848
849 run_action(
850 action,
851 &program,
852 &proxy,
853 &mut runtime,
854 &mut compositor,
855 &mut events,
856 &mut messages,
857 &mut clipboard,
858 &mut control_sender,
859 &mut user_interfaces,
860 &mut window_manager,
861 &mut ui_caches,
862 &mut is_window_opening,
863 &mut system_theme,
864 &mut renderer_settings,
865 );
866 }
867
868 for (window_id, window) in window_manager.iter_mut() {
869 if window_id == id {
871 continue;
872 }
873
874 window.raw.request_redraw();
875 }
876
877 let Some(next_compositor) = compositor.as_mut() else {
878 continue 'next_event;
879 };
880
881 current_compositor = next_compositor;
882 window = window_manager.get_mut(id).unwrap();
883
884 if logical_size != window.state.logical_size() {
886 logical_size = window.state.logical_size();
887
888 log::debug!(
889 "Window scale factor changed during a redraw request"
890 );
891
892 let ui =
893 user_interfaces.remove(&id).expect("Remove user interface");
894
895 let layout_span = debug::layout(id);
896 let _ = user_interfaces.insert(
897 id,
898 ui.relayout(logical_size, &mut window.renderer),
899 );
900 layout_span.finish();
901 }
902
903 interface = user_interfaces.get_mut(&id).unwrap();
904 }
905 };
906 interact_span.finish();
907
908 let draw_span = debug::draw(id);
909 interface.draw(
910 &mut window.renderer,
911 window.state.theme(),
912 &renderer::Style {
913 text_color: window.state.text_color(),
914 },
915 cursor,
916 );
917 draw_span.finish();
918
919 if let user_interface::State::Updated {
920 redraw_request,
921 input_method,
922 mouse_interaction,
923 clipboard: clipboard_requests,
924 ..
925 } = state
926 {
927 window.request_redraw(redraw_request);
928 window.request_input_method(input_method);
929 window.update_mouse(mouse_interaction);
930
931 run_clipboard(&mut proxy, &mut clipboard, clipboard_requests, id);
932 }
933
934 runtime.broadcast(subscription::Event::Interaction {
935 window: id,
936 event: redraw_event,
937 status: core::event::Status::Ignored,
938 });
939
940 window.draw_preedit();
941
942 let present_span = debug::present(id);
943 match current_compositor.present(
944 &mut window.renderer,
945 &mut window.surface,
946 window.state.viewport(),
947 window.state.background_color(),
948 || window.raw.pre_present_notify(),
949 ) {
950 Ok(()) => {
951 present_span.finish();
952 }
953 Err(error) => match error {
954 compositor::SurfaceError::OutOfMemory => {
955 panic!("{error:?}");
957 }
958 compositor::SurfaceError::Occluded => {
959 present_span.finish();
960
961 }
963 compositor::SurfaceError::Timeout => {
964 present_span.finish();
965
966 window.raw.request_redraw();
967 }
968 compositor::SurfaceError::Lost
969 | compositor::SurfaceError::Outdated
970 | compositor::SurfaceError::Other => {
971 present_span.finish();
972
973 let due = window
977 .surface_error_at
978 .is_none_or(|at| at.elapsed() > Duration::from_secs(1));
979
980 if due {
981 window.surface_error_at = Some(Instant::now());
982
983 log::warn!(
984 "Error {error:?} when presenting surface. Recovering it."
985 );
986
987 let physical_size = window.state.physical_size();
988
989 if matches!(error, compositor::SurfaceError::Outdated) {
990 current_compositor.configure_surface(
991 &mut window.surface,
992 physical_size.width,
993 physical_size.height,
994 );
995 } else {
996 window.surface = current_compositor.create_surface(
997 window.raw.clone(),
998 physical_size.width,
999 physical_size.height,
1000 );
1001 }
1002
1003 window.raw.request_redraw();
1004 }
1005 }
1006 },
1007 }
1008 }
1009 event::Event::WindowEvent {
1010 event: window_event,
1011 window_id,
1012 } => {
1013 if !is_daemon
1014 && matches!(window_event, winit::event::WindowEvent::Destroyed)
1015 && !is_window_opening
1016 && window_manager.is_empty()
1017 {
1018 control_sender
1019 .start_send(Control::Exit)
1020 .expect("Send control action");
1021
1022 continue;
1023 }
1024
1025 let Some((id, window)) = window_manager.get_mut_alias(window_id) else {
1026 continue;
1027 };
1028
1029 match window_event {
1030 winit::event::WindowEvent::Resized(_)
1031 | winit::event::WindowEvent::Occluded(false) => {
1032 window.raw.request_redraw();
1033 }
1034 winit::event::WindowEvent::ThemeChanged(theme) => {
1035 let mode = conversion::theme_mode(theme);
1036
1037 if mode != system_theme {
1038 system_theme = mode;
1039
1040 runtime
1041 .broadcast(subscription::Event::SystemThemeChanged(mode));
1042 }
1043 }
1044 _ => {}
1045 }
1046
1047 if matches!(window_event, winit::event::WindowEvent::CloseRequested)
1048 && window.exit_on_close_request
1049 {
1050 run_action(
1051 Action::Window(runtime::window::Action::Close(id)),
1052 &program,
1053 &proxy,
1054 &mut runtime,
1055 &mut compositor,
1056 &mut events,
1057 &mut messages,
1058 &mut clipboard,
1059 &mut control_sender,
1060 &mut user_interfaces,
1061 &mut window_manager,
1062 &mut ui_caches,
1063 &mut is_window_opening,
1064 &mut system_theme,
1065 &mut renderer_settings,
1066 );
1067 } else {
1068 window.state.update(&program, &window.raw, &window_event);
1069
1070 if let Some(event) = conversion::window_event(
1071 window_event,
1072 window.state.scale_factor(),
1073 window.state.modifiers(),
1074 ) {
1075 events.push((id, event));
1076 }
1077 }
1078 }
1079 event::Event::AboutToWait => {
1080 if actions > 0 {
1081 proxy.free_slots(actions);
1082 actions = 0;
1083 }
1084
1085 if events.is_empty() && messages.is_empty() && window_manager.is_idle() {
1086 continue;
1087 }
1088
1089 let mut uis_stale = false;
1090
1091 for (id, window) in window_manager.iter_mut() {
1092 let interact_span = debug::interact(id);
1093 let mut window_events = vec![];
1094
1095 events.retain(|(window_id, event)| {
1096 if *window_id == id {
1097 window_events.push(event.clone());
1098 false
1099 } else {
1100 true
1101 }
1102 });
1103
1104 if window_events.is_empty() {
1105 continue;
1106 }
1107
1108 let (ui_state, statuses) = user_interfaces
1109 .get_mut(&id)
1110 .expect("Get user interface")
1111 .update(
1112 &window.raw,
1113 &window.waker,
1114 &window_events,
1115 window.state.cursor(),
1116 &mut window.renderer,
1117 &mut messages,
1118 );
1119
1120 #[cfg(feature = "unconditional-rendering")]
1121 window.request_redraw(window::RedrawRequest::NextFrame);
1122
1123 match ui_state {
1124 user_interface::State::Updated {
1125 redraw_request: _redraw_request,
1126 mouse_interaction,
1127 clipboard: clipboard_requests,
1128 ..
1129 } => {
1130 window.update_mouse(mouse_interaction);
1131
1132 #[cfg(not(feature = "unconditional-rendering"))]
1133 window.request_redraw(_redraw_request);
1134
1135 run_clipboard(
1136 &mut proxy,
1137 &mut clipboard,
1138 clipboard_requests,
1139 id,
1140 );
1141 }
1142 user_interface::State::Outdated => {
1143 uis_stale = true;
1144 }
1145 }
1146
1147 for (event, status) in window_events.into_iter().zip(statuses) {
1148 runtime.broadcast(subscription::Event::Interaction {
1149 window: id,
1150 event,
1151 status,
1152 });
1153 }
1154
1155 interact_span.finish();
1156 }
1157
1158 for (id, event) in events.drain(..) {
1159 runtime.broadcast(subscription::Event::Interaction {
1160 window: id,
1161 event,
1162 status: core::event::Status::Ignored,
1163 });
1164 }
1165
1166 if !messages.is_empty() || uis_stale {
1167 let cached_interfaces: FxHashMap<_, _> =
1168 ManuallyDrop::into_inner(user_interfaces)
1169 .into_iter()
1170 .map(|(id, ui)| (id, ui.into_cache()))
1171 .collect();
1172
1173 let actions = update(&mut program, &mut runtime, &mut messages);
1174
1175 user_interfaces = ManuallyDrop::new(build_user_interfaces(
1176 &program,
1177 &mut window_manager,
1178 cached_interfaces,
1179 &mut proxy,
1180 ));
1181
1182 for action in actions {
1183 run_action(
1184 action,
1185 &program,
1186 &proxy,
1187 &mut runtime,
1188 &mut compositor,
1189 &mut events,
1190 &mut messages,
1191 &mut clipboard,
1192 &mut control_sender,
1193 &mut user_interfaces,
1194 &mut window_manager,
1195 &mut ui_caches,
1196 &mut is_window_opening,
1197 &mut system_theme,
1198 &mut renderer_settings,
1199 );
1200 }
1201
1202 for (_id, window) in window_manager.iter_mut() {
1203 window.raw.request_redraw();
1204 }
1205 }
1206
1207 if let Some(redraw_at) = window_manager.redraw_at() {
1208 let _ = control_sender
1209 .start_send(Control::ChangeFlow(ControlFlow::WaitUntil(redraw_at)));
1210 } else {
1211 let _ =
1212 control_sender.start_send(Control::ChangeFlow(ControlFlow::Wait));
1213 }
1214 }
1215 _ => {}
1216 }
1217 }
1218 Event::Exit => break,
1219 }
1220 }
1221
1222 let _ = ManuallyDrop::into_inner(user_interfaces);
1223}
1224
1225fn build_user_interface<'a, P: Program>(
1227 program: &'a program::Instance<P>,
1228 cache: user_interface::Cache,
1229 renderer: &mut P::Renderer,
1230 size: Size,
1231 id: window::Id,
1232) -> UserInterface<'a, P::Message, P::Theme, P::Renderer>
1233where
1234 P::Theme: theme::Base,
1235{
1236 let view_span = debug::view(id);
1237 let view = program.view(id);
1238 view_span.finish();
1239
1240 let layout_span = debug::layout(id);
1241 let user_interface = UserInterface::build(view, size, cache, renderer);
1242 layout_span.finish();
1243
1244 user_interface
1245}
1246
1247fn update<P: Program, E: Executor>(
1248 program: &mut program::Instance<P>,
1249 runtime: &mut Runtime<E, Proxy<P::Message>, Action<P::Message>>,
1250 messages: &mut shell::Bus<P::Message>,
1251) -> Vec<Action<P::Message>>
1252where
1253 P::Theme: theme::Base,
1254{
1255 use futures::futures;
1256
1257 let mut actions = Vec::new();
1258 let mut outputs = Vec::new();
1259
1260 while !messages.is_empty() {
1261 for (message, _receipt) in messages.drain() {
1262 let task = runtime.enter(|| program.update(message));
1263
1264 if let Some(mut stream) = runtime::task::into_stream(task) {
1265 let waker = futures::task::noop_waker_ref();
1266 let mut context = futures::task::Context::from_waker(waker);
1267
1268 loop {
1270 match runtime.enter(|| stream.poll_next_unpin(&mut context)) {
1271 futures::task::Poll::Ready(Some(Action::Output(output))) => {
1272 outputs.push(output);
1273 }
1274 futures::task::Poll::Ready(Some(action)) => {
1275 actions.push(action);
1276 }
1277 futures::task::Poll::Ready(None) => {
1278 break;
1279 }
1280 futures::task::Poll::Pending => {
1281 runtime.run(stream);
1282 break;
1283 }
1284 }
1285 }
1286 }
1287 }
1288
1289 for output in outputs.drain(..) {
1290 let _ = messages.push(output);
1291 }
1292 }
1293
1294 let subscription = runtime.enter(|| program.subscription());
1295 let recipes = subscription::into_recipes(subscription.map(Action::Output));
1296
1297 runtime.track(recipes);
1298
1299 actions
1300}
1301
1302fn run_action<'a, P, C>(
1303 action: Action<P::Message>,
1304 program: &'a program::Instance<P>,
1305 _proxy: &Proxy<P::Message>,
1306 runtime: &mut Runtime<P::Executor, Proxy<P::Message>, Action<P::Message>>,
1307 compositor: &mut Option<C>,
1308 events: &mut Vec<(window::Id, core::Event)>,
1309 messages: &mut shell::Bus<P::Message>,
1310 clipboard: &mut Clipboard,
1311 control_sender: &mut mpsc::UnboundedSender<Control>,
1312 interfaces: &mut FxHashMap<window::Id, UserInterface<'a, P::Message, P::Theme, P::Renderer>>,
1313 window_manager: &mut window::Manager<P, C>,
1314 ui_caches: &mut FxHashMap<window::Id, user_interface::Cache>,
1315 is_window_opening: &mut bool,
1316 system_theme: &mut theme::Mode,
1317 renderer_settings: &mut renderer::Settings,
1318) where
1319 P: Program,
1320 C: Compositor<Renderer = P::Renderer> + 'static,
1321 P::Theme: theme::Base,
1322{
1323 use crate::core::Renderer as _;
1324 use crate::runtime::backend;
1325 use crate::runtime::clipboard;
1326 use crate::runtime::window;
1327
1328 match action {
1329 Action::Output(message) => {
1330 let _ = messages.push(message);
1331 }
1332 Action::Clipboard(action) => match action {
1333 clipboard::Action::Read { kind, channel } => {
1334 clipboard.read(kind, move |result| {
1335 let _ = channel.send(result);
1336 });
1337 }
1338 clipboard::Action::Write { content, channel } => {
1339 clipboard.write(content, move |result| {
1340 let _ = channel.send(result);
1341 });
1342 }
1343 },
1344 Action::Window(action) => match action {
1345 window::Action::Open(id, settings, channel) => {
1346 let monitor = window_manager.last_monitor();
1347
1348 control_sender
1349 .start_send(Control::CreateWindow {
1350 id,
1351 settings,
1352 title: program.title(id),
1353 scale_factor: program.scale_factor(id),
1354 monitor,
1355 on_open: channel,
1356 })
1357 .expect("Send control action");
1358
1359 *is_window_opening = true;
1360 }
1361 window::Action::Close(id) => {
1362 let _ = ui_caches.remove(&id);
1363 let _ = interfaces.remove(&id);
1364
1365 if window_manager.remove(id).is_some() {
1366 events.push((id, core::Event::Window(core::window::Event::Closed)));
1367 }
1368
1369 if window_manager.is_empty() {
1370 *compositor = None;
1371 }
1372 }
1373 window::Action::GetOldest(channel) => {
1374 let id = window_manager.iter_mut().next().map(|(id, _window)| id);
1375
1376 let _ = channel.send(id);
1377 }
1378 window::Action::GetLatest(channel) => {
1379 let id = window_manager.iter_mut().last().map(|(id, _window)| id);
1380
1381 let _ = channel.send(id);
1382 }
1383 window::Action::Drag(id) => {
1384 if let Some(window) = window_manager.get_mut(id) {
1385 let _ = window.raw.drag_window();
1386 }
1387 }
1388 window::Action::DragResize(id, direction) => {
1389 if let Some(window) = window_manager.get_mut(id) {
1390 let _ = window
1391 .raw
1392 .drag_resize_window(conversion::resize_direction(direction));
1393 }
1394 }
1395 window::Action::Resize(id, size) => {
1396 if let Some(window) = window_manager.get_mut(id) {
1397 let _ = window.raw.request_inner_size(
1398 winit::dpi::LogicalSize {
1399 width: size.width,
1400 height: size.height,
1401 }
1402 .to_physical::<f32>(f64::from(window.state.scale_factor())),
1403 );
1404 }
1405 }
1406 window::Action::SetMinSize(id, size) => {
1407 if let Some(window) = window_manager.get_mut(id) {
1408 window.raw.set_min_inner_size(size.map(|size| {
1409 winit::dpi::LogicalSize {
1410 width: size.width,
1411 height: size.height,
1412 }
1413 .to_physical::<f32>(f64::from(window.state.scale_factor()))
1414 }));
1415 }
1416 }
1417 window::Action::SetMaxSize(id, size) => {
1418 if let Some(window) = window_manager.get_mut(id) {
1419 window.raw.set_max_inner_size(size.map(|size| {
1420 winit::dpi::LogicalSize {
1421 width: size.width,
1422 height: size.height,
1423 }
1424 .to_physical::<f32>(f64::from(window.state.scale_factor()))
1425 }));
1426 }
1427 }
1428 window::Action::SetResizeIncrements(id, increments) => {
1429 if let Some(window) = window_manager.get_mut(id) {
1430 window.raw.set_resize_increments(increments.map(|size| {
1431 winit::dpi::LogicalSize {
1432 width: size.width,
1433 height: size.height,
1434 }
1435 .to_physical::<f32>(f64::from(window.state.scale_factor()))
1436 }));
1437 }
1438 }
1439 window::Action::SetResizable(id, resizable) => {
1440 if let Some(window) = window_manager.get_mut(id) {
1441 window.raw.set_resizable(resizable);
1442 }
1443 }
1444 window::Action::GetSize(id, channel) => {
1445 if let Some(window) = window_manager.get_mut(id) {
1446 let size = window.state.logical_size();
1447 let _ = channel.send(Size::new(size.width, size.height));
1448 }
1449 }
1450 window::Action::GetMaximized(id, channel) => {
1451 if let Some(window) = window_manager.get_mut(id) {
1452 let _ = channel.send(window.raw.is_maximized());
1453 }
1454 }
1455 window::Action::Maximize(id, maximized) => {
1456 if let Some(window) = window_manager.get_mut(id) {
1457 window.raw.set_maximized(maximized);
1458 }
1459 }
1460 window::Action::GetMinimized(id, channel) => {
1461 if let Some(window) = window_manager.get_mut(id) {
1462 let _ = channel.send(window.raw.is_minimized());
1463 }
1464 }
1465 window::Action::Minimize(id, minimized) => {
1466 if let Some(window) = window_manager.get_mut(id) {
1467 window.raw.set_minimized(minimized);
1468 }
1469 }
1470 window::Action::GetPosition(id, channel) => {
1471 if let Some(window) = window_manager.get(id) {
1472 let position = window
1473 .raw
1474 .outer_position()
1475 .map(|position| {
1476 let position = position.to_logical::<f32>(window.raw.scale_factor());
1477
1478 Point::new(position.x, position.y)
1479 })
1480 .ok();
1481
1482 let _ = channel.send(position);
1483 }
1484 }
1485 window::Action::GetScaleFactor(id, channel) => {
1486 if let Some(window) = window_manager.get_mut(id) {
1487 let scale_factor = window.raw.scale_factor();
1488
1489 let _ = channel.send(scale_factor as f32);
1490 }
1491 }
1492 window::Action::Move(id, position) => {
1493 if let Some(window) = window_manager.get_mut(id) {
1494 window.raw.set_outer_position(winit::dpi::LogicalPosition {
1495 x: position.x,
1496 y: position.y,
1497 });
1498 }
1499 }
1500 window::Action::SetMode(id, mode) => {
1501 if let Some(window) = window_manager.get_mut(id) {
1502 window.raw.set_visible(conversion::visible(mode));
1503 window
1504 .raw
1505 .set_fullscreen(conversion::fullscreen(window.raw.current_monitor(), mode));
1506 }
1507 }
1508 window::Action::SetIcon(id, icon) => {
1509 if let Some(window) = window_manager.get_mut(id) {
1510 window.raw.set_window_icon(conversion::icon(icon));
1511 }
1512 }
1513 window::Action::GetMode(id, channel) => {
1514 if let Some(window) = window_manager.get_mut(id) {
1515 let mode = if window.raw.is_visible().unwrap_or(true) {
1516 conversion::mode(window.raw.fullscreen())
1517 } else {
1518 core::window::Mode::Hidden
1519 };
1520
1521 let _ = channel.send(mode);
1522 }
1523 }
1524 window::Action::ToggleMaximize(id) => {
1525 if let Some(window) = window_manager.get_mut(id) {
1526 window.raw.set_maximized(!window.raw.is_maximized());
1527 }
1528 }
1529 window::Action::ToggleDecorations(id) => {
1530 if let Some(window) = window_manager.get_mut(id) {
1531 window.raw.set_decorations(!window.raw.is_decorated());
1532 }
1533 }
1534 window::Action::RequestUserAttention(id, attention_type) => {
1535 if let Some(window) = window_manager.get_mut(id) {
1536 window
1537 .raw
1538 .request_user_attention(attention_type.map(conversion::user_attention));
1539 }
1540 }
1541 window::Action::GainFocus(id) => {
1542 if let Some(window) = window_manager.get_mut(id) {
1543 window.raw.focus_window();
1544 }
1545 }
1546 window::Action::SetLevel(id, level) => {
1547 if let Some(window) = window_manager.get_mut(id) {
1548 window.raw.set_window_level(conversion::window_level(level));
1549 }
1550 }
1551 window::Action::ShowSystemMenu(id) => {
1552 if let Some(window) = window_manager.get_mut(id)
1553 && let mouse::Cursor::Available(point) = window.state.cursor()
1554 {
1555 window.raw.show_window_menu(winit::dpi::LogicalPosition {
1556 x: point.x,
1557 y: point.y,
1558 });
1559 }
1560 }
1561 window::Action::GetRawId(id, channel) => {
1562 if let Some(window) = window_manager.get_mut(id) {
1563 let _ = channel.send(window.raw.id().into());
1564 }
1565 }
1566 window::Action::Run(id, f) => {
1567 if let Some(window) = window_manager.get_mut(id) {
1568 f(&window.raw);
1569 }
1570 }
1571 window::Action::Screenshot(id, channel) => {
1572 if let Some(window) = window_manager.get_mut(id)
1573 && let Some(compositor) = compositor
1574 {
1575 let bytes = compositor.screenshot(
1576 &mut window.renderer,
1577 window.state.viewport(),
1578 window.state.background_color(),
1579 );
1580
1581 let _ = channel.send(core::window::Screenshot::new(
1582 bytes,
1583 window.state.physical_size(),
1584 window.state.scale_factor(),
1585 ));
1586 }
1587 }
1588 window::Action::EnableMousePassthrough(id) => {
1589 if let Some(window) = window_manager.get_mut(id) {
1590 let _ = window.raw.set_cursor_hittest(false);
1591 }
1592 }
1593 window::Action::DisableMousePassthrough(id) => {
1594 if let Some(window) = window_manager.get_mut(id) {
1595 let _ = window.raw.set_cursor_hittest(true);
1596 }
1597 }
1598 window::Action::GetMonitorSize(id, channel) => {
1599 if let Some(window) = window_manager.get(id) {
1600 let size = window.raw.current_monitor().map(|monitor| {
1601 let scale = window.state.scale_factor();
1602 let size = monitor.size().to_logical(f64::from(scale));
1603
1604 Size::new(size.width, size.height)
1605 });
1606
1607 let _ = channel.send(size);
1608 }
1609 }
1610 window::Action::SetAllowAutomaticTabbing(enabled) => {
1611 control_sender
1612 .start_send(Control::SetAutomaticWindowTabbing(enabled))
1613 .expect("Send control action");
1614 }
1615 window::Action::RedrawAll => {
1616 for (_id, window) in window_manager.iter_mut() {
1617 window.raw.request_redraw();
1618 }
1619 }
1620 window::Action::RelayoutAll => {
1621 for (id, window) in window_manager.iter_mut() {
1622 if let Some(ui) = interfaces.remove(&id) {
1623 let _ = interfaces.insert(
1624 id,
1625 ui.relayout(window.state.logical_size(), &mut window.renderer),
1626 );
1627 }
1628
1629 window.raw.request_redraw();
1630 }
1631 }
1632 },
1633 Action::System(action) => match action {
1634 system::Action::GetInformation(_channel) => {
1635 #[cfg(feature = "sysinfo")]
1636 {
1637 if let Some(compositor) = compositor {
1638 let graphics_info = compositor.information();
1639
1640 let _ = std::thread::spawn(move || {
1641 let information = system_information(graphics_info);
1642
1643 let _ = _channel.send(information);
1644 });
1645 }
1646 }
1647 }
1648 system::Action::GetTheme(channel) => {
1649 let _ = channel.send(*system_theme);
1650 }
1651 system::Action::NotifyTheme(mode) => {
1652 if mode != *system_theme {
1653 *system_theme = mode;
1654
1655 runtime.broadcast(subscription::Event::SystemThemeChanged(mode));
1656 }
1657
1658 let Some(theme) = conversion::window_theme(mode) else {
1659 return;
1660 };
1661
1662 for (_id, window) in window_manager.iter_mut() {
1663 window.state.update(
1664 program,
1665 &window.raw,
1666 &winit::event::WindowEvent::ThemeChanged(theme),
1667 );
1668 }
1669 }
1670 },
1671 Action::Font(action) => match action {
1672 font::Action::Load { bytes, channel } => {
1673 if let Some(compositor) = compositor {
1674 let result = compositor.load_font(bytes.clone());
1675 let _ = channel.send(result);
1676 }
1677 }
1678 font::Action::List { channel } => {
1679 if let Some(compositor) = compositor {
1680 let fonts = compositor.list_fonts();
1681 let _ = channel.send(fonts);
1682 }
1683 }
1684 font::Action::SetDefaults { font, text_size } => {
1685 renderer_settings.font = font;
1686 renderer_settings.text_size = text_size;
1687
1688 let Some(compositor) = compositor else {
1689 return;
1690 };
1691
1692 for (id, window) in window_manager.iter_mut() {
1694 window.renderer = compositor.create_renderer(*renderer_settings);
1695
1696 let Some(ui) = interfaces.remove(&id) else {
1697 continue;
1698 };
1699
1700 let size = window.state.logical_size();
1701 let ui = ui.relayout(size, &mut window.renderer);
1702 let _ = interfaces.insert(id, ui);
1703
1704 window.raw.request_redraw();
1705 }
1706 }
1707 },
1708 Action::Widget(operation) => {
1709 let mut current_operation = Some(operation);
1710
1711 while let Some(mut operation) = current_operation.take() {
1712 for (id, ui) in interfaces.iter_mut() {
1713 if let Some(window) = window_manager.get_mut(*id) {
1714 ui.operate(&window.renderer, operation.as_mut());
1715 }
1716 }
1717
1718 match operation.finish() {
1719 operation::Outcome::None => {}
1720 operation::Outcome::Some(()) => {}
1721 operation::Outcome::Chain(next) => {
1722 current_operation = Some(next);
1723 }
1724 }
1725 }
1726
1727 for (_, window) in window_manager.iter_mut() {
1729 window.raw.request_redraw();
1730 }
1731 }
1732 Action::Image(action) => match action {
1733 image::Action::Allocate(handle, sender) => {
1734 if let Some((_id, window)) = window_manager.iter_mut().next() {
1736 window.renderer.allocate_image(&handle, move |allocation| {
1737 let _ = sender.send(allocation);
1738 });
1739 }
1740 }
1741 },
1742 Action::Backend(action) => match action {
1743 #[cfg(not(target_arch = "wasm32"))]
1744 backend::Action::Configure(settings, sender) => {
1745 let shell = Shell::new(_proxy.clone());
1746
1747 let mut new_compositor = if let Some(window) = window_manager.first() {
1748 match runtime.block_on(C::new(
1749 settings,
1750 window.raw.clone(),
1751 window.raw.clone(),
1752 shell,
1753 )) {
1754 Ok(compositor) => compositor,
1755 Err(error) => {
1756 let _ = sender.send(Err(error));
1757 return;
1758 }
1759 }
1760 } else {
1761 return;
1762 };
1763
1764 graphics::cache::invalidate_all();
1765
1766 window_manager.replace_with(|mut window| {
1767 let size = window.state.physical_size();
1768
1769 drop(window.renderer);
1770 drop(window.surface);
1771
1772 window.renderer = new_compositor.create_renderer(*renderer_settings);
1773 window.surface =
1774 new_compositor.create_surface(window.raw.clone(), size.width, size.height);
1775
1776 window
1777 });
1778
1779 *compositor = Some(new_compositor);
1780
1781 let _ = sender.send(Ok(()));
1782 }
1783 #[cfg(target_arch = "wasm32")]
1784 backend::Action::Configure(_, _) => {}
1785 },
1786 Action::Event { window, event } => {
1787 events.push((window, event));
1788 }
1789 Action::Tick => {
1790 for (_id, window) in window_manager.iter_mut() {
1791 window.renderer.tick();
1792 }
1793 }
1794 Action::Reload => {
1795 for (id, window) in window_manager.iter_mut() {
1796 let Some(ui) = interfaces.remove(&id) else {
1797 continue;
1798 };
1799
1800 let cache = ui.into_cache();
1801 let size = window.state.logical_size();
1802
1803 let _ = interfaces.insert(
1804 id,
1805 build_user_interface(program, cache, &mut window.renderer, size, id),
1806 );
1807
1808 window.raw.request_redraw();
1809 }
1810 }
1811 Action::Exit => {
1812 control_sender
1813 .start_send(Control::Exit)
1814 .expect("Send control action");
1815 }
1816 }
1817}
1818
1819pub fn build_user_interfaces<'a, P: Program, C>(
1821 program: &'a program::Instance<P>,
1822 window_manager: &mut window::Manager<P, C>,
1823 mut cached_user_interfaces: FxHashMap<window::Id, user_interface::Cache>,
1824 proxy: &mut Proxy<P::Message>,
1825) -> FxHashMap<window::Id, UserInterface<'a, P::Message, P::Theme, P::Renderer>>
1826where
1827 C: Compositor<Renderer = P::Renderer>,
1828 P::Theme: theme::Base,
1829{
1830 for (id, window) in window_manager.iter_mut() {
1831 let old_size = window.state.logical_size();
1832
1833 window.state.synchronize(program, id, &window.raw);
1834
1835 let new_size = window.state.logical_size();
1836
1837 if old_size != new_size {
1838 proxy.send_action(Action::Event {
1839 window: id,
1840 event: core::Event::Window(window::Event::Resized(new_size)),
1841 });
1842 }
1843
1844 window.renderer.hint(window.state.scale());
1845 }
1846
1847 debug::theme_changed(|| {
1848 window_manager
1849 .first()
1850 .and_then(|window| theme::Base::seed(window.state.theme()))
1851 });
1852
1853 cached_user_interfaces
1854 .drain()
1855 .filter_map(|(id, cache)| {
1856 let window = window_manager.get_mut(id)?;
1857
1858 Some((
1859 id,
1860 build_user_interface(
1861 program,
1862 cache,
1863 &mut window.renderer,
1864 window.state.logical_size(),
1865 id,
1866 ),
1867 ))
1868 })
1869 .collect()
1870}
1871
1872pub fn user_force_quit(
1875 event: &winit::event::WindowEvent,
1876 _modifiers: winit::keyboard::ModifiersState,
1877) -> bool {
1878 match event {
1879 #[cfg(target_os = "macos")]
1880 winit::event::WindowEvent::KeyboardInput {
1881 event:
1882 winit::event::KeyEvent {
1883 logical_key: winit::keyboard::Key::Character(c),
1884 state: winit::event::ElementState::Pressed,
1885 ..
1886 },
1887 ..
1888 } if c == "q" && _modifiers.super_key() => true,
1889 _ => false,
1890 }
1891}
1892
1893#[cfg(feature = "sysinfo")]
1894fn system_information(graphics: compositor::Information) -> system::Information {
1895 use sysinfo::{Process, System};
1896
1897 let mut system = System::new_all();
1898 system.refresh_all();
1899
1900 let cpu_brand = system
1901 .cpus()
1902 .first()
1903 .map(|cpu| cpu.brand().to_string())
1904 .unwrap_or_default();
1905
1906 let memory_used = sysinfo::get_current_pid()
1907 .and_then(|pid| system.process(pid).ok_or("Process not found"))
1908 .map(Process::memory)
1909 .ok();
1910
1911 system::Information {
1912 system_name: System::name(),
1913 system_kernel: System::kernel_version(),
1914 system_version: System::long_os_version(),
1915 system_short_version: System::os_version(),
1916 cpu_brand,
1917 cpu_cores: system.physical_core_count(),
1918 memory_total: system.total_memory(),
1919 memory_used,
1920 graphics_adapter: graphics.adapter,
1921 graphics_backend: graphics.backend,
1922 }
1923}
1924
1925fn run_clipboard<Message: Send>(
1926 proxy: &mut Proxy<Message>,
1927 clipboard: &mut Clipboard,
1928 requests: core::Clipboard,
1929 window: window::Id,
1930) {
1931 for kind in requests.reads {
1932 let proxy = proxy.clone();
1933
1934 clipboard.read(kind, move |result| {
1935 proxy.send_action(Action::Event {
1936 window,
1937 event: core::Event::Clipboard(core::clipboard::Event::Read(result.map(Arc::new))),
1938 });
1939 });
1940 }
1941
1942 if let Some(content) = requests.write {
1943 let proxy = proxy.clone();
1944
1945 clipboard.write(content, move |result| {
1946 proxy.send_action(Action::Event {
1947 window,
1948 event: core::Event::Clipboard(core::clipboard::Event::Written(result)),
1949 });
1950 });
1951 }
1952}