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