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