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