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