1use baseview::{EventStatus, WindowOpenOptions};
2use iced_core::widget::operation;
3use iced_core::window::RedrawRequest;
4use iced_core::{Point, Size, mouse, renderer, theme};
5use iced_futures::futures::{StreamExt, task};
6use iced_futures::{Executor, Subscription};
7use iced_futures::{Runtime, futures::channel::mpsc, subscription};
8use iced_program::Program;
9use iced_runtime::{Action, UserInterface, user_interface};
10use iced_widget::graphics::Viewport;
11use raw_window_handle::HasRawWindowHandle;
12use std::mem::ManuallyDrop;
13use std::sync::Arc;
14use std::sync::atomic::{AtomicBool, Ordering};
15use std::time::Instant;
16use std::{cell::RefCell, rc::Rc};
17
18#[cfg(feature = "nice-log")]
19use nice_plug_core::{nice_dbg as debug, nice_error as error, nice_warn as warn};
20
21#[cfg(all(feature = "tracing", not(feature = "nice-log")))]
22use tracing::{debug, error, warn};
23
24use crate::Error;
25use crate::graphics::{Compositor, compositor};
26
27mod proxy;
28
29#[cfg(feature = "sysinfo")]
30mod system;
31
32pub mod clipboard;
33pub mod conversion;
34pub mod settings;
35pub mod window;
36
37use clipboard::Clipboard;
38use settings::IcedBaseviewSettings;
39use window::{
40 IcedWindowHandler, InstanceWindow, WindowCommand, WindowHandle, WindowQueue,
41 state::State as WindowState,
42};
43
44pub use proxy::Proxy;
45
46#[derive(Debug, Clone)]
51pub struct PollSubNotifier {
52 notify: Arc<AtomicBool>,
53}
54
55impl PollSubNotifier {
56 pub fn new() -> Self {
57 Self {
58 notify: Arc::new(AtomicBool::new(true)),
59 }
60 }
61
62 pub fn notify(&self) {
63 self.notify.store(true, Ordering::Relaxed);
64 }
65
66 pub(crate) fn notify_flag_set(&self) -> bool {
67 self.notify.swap(false, Ordering::Relaxed)
68 }
69}
70
71impl Default for PollSubNotifier {
72 fn default() -> Self {
73 Self::new()
74 }
75}
76
77const POLL_EVENT: iced_core::Event =
82 iced_core::Event::Window(iced_core::window::Event::Moved(Point::ORIGIN));
83
84pub fn poll_events() -> Subscription<()> {
88 iced_futures::event::listen_raw(|event, _status, _window| match event {
89 POLL_EVENT => Some(()),
90 _ => None,
91 })
92}
93
94pub fn open_blocking<P, B>(
104 settings: IcedBaseviewSettings,
105 notifier: PollSubNotifier,
106 build_program: B,
107) where
108 P: Program + 'static,
109 B: Send + 'static + FnOnce() -> P,
110{
111 let (sender, receiver) = mpsc::unbounded();
112
113 baseview::Window::open_blocking(
114 clone_window_options(&settings.window),
115 move |window: &mut baseview::Window<'_>| -> IcedWindowHandler<P> {
116 let program = (build_program)();
117 run_inner(window, settings, program, sender, receiver, notifier).expect("Launch window")
118 },
119 );
120}
121
122pub fn open_parented<W, P, B>(
133 parent: &W,
134 settings: IcedBaseviewSettings,
135 notifier: PollSubNotifier,
136 build_program: B,
137) -> WindowHandle<P::Message>
138where
139 W: HasRawWindowHandle,
140 P: Program + 'static,
141 B: Send + 'static + FnOnce() -> P,
142{
143 let (sender, receiver) = mpsc::unbounded();
144 let sender_clone = sender.clone();
145
146 let bv_handle = baseview::Window::open_parented(
147 parent,
148 clone_window_options(&settings.window),
149 move |window: &mut baseview::Window<'_>| -> IcedWindowHandler<P> {
150 let program = (build_program)();
151 run_inner(window, settings, program, sender_clone, receiver, notifier)
152 .expect("Launch window")
153 },
154 );
155
156 WindowHandle::new(bv_handle, sender)
157}
158
159fn run_inner<P>(
161 window: &mut baseview::Window<'_>,
162 settings: IcedBaseviewSettings,
163 program: P,
164 event_sender: mpsc::UnboundedSender<RuntimeEvent<P::Message>>,
165 event_receiver: mpsc::UnboundedReceiver<RuntimeEvent<P::Message>>,
166 notifier: PollSubNotifier,
167) -> Result<IcedWindowHandler<P>, Error>
168where
169 P: Program + 'static,
170 P::Theme: theme::Base,
171{
172 let boot_span = iced_debug::boot();
173 let program_settings = program.settings();
174 let graphics_settings: iced_widget::graphics::Settings = program_settings.clone().into();
175
176 let (runtime_tx, runtime_rx) = mpsc::unbounded::<Action<P::Message>>();
177
178 let window_scale_factor = 1.0;
180
181 let viewport = {
182 let scale = match settings.window.scale {
183 baseview::WindowScalePolicy::ScaleFactor(scale) => scale,
184 baseview::WindowScalePolicy::SystemScaleFactor => window_scale_factor,
185 };
186
187 let physical_size = Size::new(
188 (settings.window.size.width * scale) as u32,
189 (settings.window.size.height * scale) as u32,
190 );
191
192 Viewport::with_physical_size(physical_size, scale as f32)
193 };
194
195 let proxy = Proxy::new(runtime_tx);
196
197 #[cfg(feature = "debug")]
198 {
199 let proxy = proxy.clone();
200
201 iced_debug::on_hotpatch(move || {
202 let _ = proxy.send_action(Action::Reload);
203 });
204 }
205
206 let mut runtime = {
207 let executor = P::Executor::new().map_err(Error::ExecutorCreationFailed)?;
208
209 Runtime::new(executor, proxy.clone())
210 };
211
212 let (program, init_task) = runtime.enter(|| iced_program::Instance::new(program));
213
214 if let Some(stream) = iced_runtime::task::into_stream(init_task) {
215 runtime.run(stream);
216 }
217
218 runtime.track(iced_futures::subscription::into_recipes(
219 runtime.enter(|| program.subscription().map(Action::Output)),
220 ));
221
222 let window06 = crate::shell::conversion::convert_window(window);
223 let mut compositor =
224 crate::futures::executor::block_on(<P::Renderer as compositor::Default>::Compositor::new(
225 graphics_settings,
226 window06.clone(),
227 window06.clone(),
228 crate::graphics::Shell::new(proxy.clone()),
229 ))?;
230 let surface = compositor.create_surface(
231 window06.clone(),
232 viewport.physical_size().width,
233 viewport.physical_size().height,
234 );
235 let renderer = compositor.create_renderer();
236
237 for font in program_settings.fonts {
238 compositor.load_font(font);
239 }
240
241 let (window_queue, window_queue_rx) = WindowQueue::new();
242 let event_status = Rc::new(RefCell::new(baseview::EventStatus::Ignored));
243
244 let window_id = iced_core::window::Id::unique();
245
246 let clipboard = unsafe { Clipboard::connect(&window06) };
247
248 let state = WindowState::new(
249 &program,
250 window_id,
251 viewport.physical_size(),
252 window_scale_factor as f32,
253 settings.window.scale,
254 iced_core::theme::Mode::None,
255 );
256 let surface_version = state.surface_version();
257
258 let instance_window = InstanceWindow {
259 state,
260 mouse_interaction: mouse::Interaction::default(),
261 surface,
262 surface_version,
263 compositor,
264 renderer,
265 queue: window_queue,
267 window06,
268 id: window_id,
269 always_redraw: settings.always_redraw,
270 ignore_non_modifier_keys: settings.ignore_non_modifier_keys,
271 redraw_requested: true,
272 redraw_at: None,
273 };
274
275 let instance = Box::pin({
276 run_instance(
277 program,
278 runtime,
279 proxy,
280 instance_window,
281 event_receiver,
282 clipboard,
283 Rc::clone(&event_status),
284 notifier,
285 )
286 });
287
288 let runtime_context = task::Context::from_waker(task::noop_waker_ref());
289
290 boot_span.finish();
291
292 Ok(IcedWindowHandler {
293 sender: event_sender,
294 instance,
295 runtime_context,
296 runtime_rx,
297 window_queue_rx,
298 event_status,
299 processed_close_signal: false,
300 })
301}
302
303#[allow(clippy::too_many_arguments)]
304async fn run_instance<P>(
305 mut program: iced_program::Instance<P>,
306 mut runtime: Runtime<P::Executor, Proxy<P::Message>, Action<P::Message>>,
307 proxy: Proxy<P::Message>,
308 mut window: InstanceWindow<P, <P::Renderer as compositor::Default>::Compositor>,
309 mut event_receiver: mpsc::UnboundedReceiver<RuntimeEvent<P::Message>>,
310 mut clipboard: Clipboard,
311 event_status: Rc<RefCell<baseview::EventStatus>>,
312 notifier: PollSubNotifier,
313) where
314 P: Program + 'static,
315 P::Theme: theme::Base,
316{
317 window.surface_version = window.state.surface_version();
318
319 let cache = iced_runtime::user_interface::Cache::default();
320 let mut events: Vec<(iced_core::window::Id, iced_core::Event)> = Vec::new();
321 let mut messages = Vec::new();
322 let mut system_theme = theme::Mode::None;
323
324 let mut interface = ManuallyDrop::new(Some(build_user_interface(
325 &program,
326 cache,
327 &mut window.renderer,
328 window.state.logical_size(),
329 window.id,
330 )));
331
332 window.mouse_interaction = mouse::Interaction::default();
333
334 window.redraw_requested = true;
336 window.redraw_at = None;
337 let mut did_process_event = false;
340
341 'next_event: loop {
342 let event = if let Ok(event) = event_receiver.try_recv() {
344 Some(event)
345 } else {
346 event_receiver.next().await
347 };
348
349 let Some(event) = event else {
350 break;
351 };
352
353 match event {
354 RuntimeEvent::Poll => {
355 if notifier.notify_flag_set() {
356 runtime.broadcast(iced_futures::subscription::Event::Interaction {
357 window: window.id,
358 event: POLL_EVENT,
359 status: iced_core::event::Status::Ignored,
360 });
361 }
362 }
363 RuntimeEvent::OnFrame => {
364 #[cfg(feature = "unconditional-rendering")]
365 {
366 window.redraw_requested = true;
367 }
368
369 #[cfg(not(feature = "unconditional-rendering"))]
370 if window.always_redraw {
371 window.redraw_requested = true;
372 }
373
374 if !window.redraw_requested
375 && !did_process_event
376 && events.is_empty()
377 && messages.is_empty()
378 {
379 continue 'next_event;
380 }
381 did_process_event = false;
382
383 let mut uis_stale = false;
384
385 let interact_span = iced_debug::interact(window.id);
386 let mut window_events = vec![];
387
388 events.retain(|(event_window_id, event)| {
389 if *event_window_id == window.id {
390 window_events.push(event.clone());
391 false
392 } else {
393 true
394 }
395 });
396
397 if window_events.is_empty() && messages.is_empty() && !window.redraw_requested {
398 continue 'next_event;
399 }
400
401 let Some(ui) = interface.as_mut() else {
402 continue 'next_event;
403 };
404
405 let (ui_state, statuses) = ui.update(
406 &window_events,
407 window.state.cursor(),
408 &mut window.renderer,
409 &mut clipboard,
410 &mut messages,
411 );
412
413 match ui_state {
414 user_interface::State::Updated {
415 redraw_request: _redraw_request,
416 mouse_interaction,
417 ..
418 } => {
419 window.queue.send(WindowCommand::SetCursorIcon(
420 crate::shell::conversion::convert_mouse_interaction(mouse_interaction),
421 ));
422
423 #[cfg(not(feature = "unconditional-rendering"))]
424 if !window.always_redraw {
425 match _redraw_request {
426 RedrawRequest::NextFrame => {
427 window.redraw_requested = true;
428 }
429 RedrawRequest::At(at) => {
430 window.redraw_at = Some(at);
431 }
432 RedrawRequest::Wait => {}
433 }
434 }
435 }
436 user_interface::State::Outdated => {
437 uis_stale = true;
438 }
439 }
440
441 for (event, status) in window_events.into_iter().zip(statuses) {
442 runtime.broadcast(subscription::Event::Interaction {
443 window: window.id,
444 event,
445 status,
446 });
447 }
448
449 interact_span.finish();
450
451 for (id, event) in events.drain(..) {
452 runtime.broadcast(subscription::Event::Interaction {
453 window: id,
454 event,
455 status: iced_core::event::Status::Ignored,
456 });
457 }
458
459 if !messages.is_empty() || uis_stale {
460 let cached_interface =
461 ManuallyDrop::into_inner(interface).unwrap().into_cache();
462
463 let actions = update(&mut program, &mut runtime, &mut messages);
464
465 interface = ManuallyDrop::new(Some(rebuild_user_interface(
466 &program,
467 &mut window,
468 cached_interface,
469 )));
470
471 for action in actions {
472 run_action(
473 action,
474 &program,
475 &mut runtime,
476 &mut window,
477 &mut messages,
478 &mut clipboard,
479 &mut interface,
480 &mut system_theme,
481 );
482 }
483
484 window.redraw_requested = true;
485 }
486
487 if let Some(redraw_at) = window.redraw_at
490 && redraw_at <= Instant::now()
491 {
492 window.redraw_requested = true;
493 window.redraw_at = None;
494 }
495
496 if window.surface_version != window.state.surface_version() {
497 window.redraw_requested = true;
498 }
499
500 if !window.redraw_requested {
501 continue 'next_event;
502 }
503
504 let physical_size = window.state.physical_size();
505 let mut logical_size = window.state.logical_size();
506
507 if physical_size.width == 0 || physical_size.height == 0 {
508 continue 'next_event;
509 }
510
511 if window.surface_version != window.state.surface_version() {
513 let ui = interface.take().expect("Remove user interface");
514
515 let layout_span = iced_debug::layout(window.id);
516 *interface = Some(ui.relayout(logical_size, &mut window.renderer));
517 layout_span.finish();
518
519 window.compositor.configure_surface(
520 &mut window.surface,
521 physical_size.width,
522 physical_size.height,
523 );
524
525 window.surface_version = window.state.surface_version();
526 }
527
528 let redraw_event = iced_core::Event::Window(
529 iced_core::window::Event::RedrawRequested(Instant::now()),
530 );
531
532 let cursor = window.state.cursor();
533
534 assert!(interface.is_some(), "Get user interface");
535
536 let interact_span = iced_debug::interact(window.id);
537 let mut redraw_count = 0;
538
539 let state = loop {
540 let message_count = messages.len();
541 let (state, _) = interface.as_mut().unwrap().update(
542 core::slice::from_ref(&redraw_event),
543 cursor,
544 &mut window.renderer,
545 &mut clipboard,
546 &mut messages,
547 );
548
549 if message_count == messages.len() && !state.has_layout_changed() {
550 break state;
551 }
552
553 if redraw_count >= 2 {
554 warn!(
555 "More than 3 consecutive RedrawRequested events produced layout \
556 invalidation"
557 );
558
559 break state;
560 }
561
562 redraw_count += 1;
563
564 if !messages.is_empty() {
565 let cache = ManuallyDrop::into_inner(interface).unwrap().into_cache();
566
567 let actions = update(&mut program, &mut runtime, &mut messages);
568
569 interface = ManuallyDrop::new(Some(rebuild_user_interface(
570 &program,
571 &mut window,
572 cache,
573 )));
574
575 for action in actions {
576 if let Action::Window(_) = action {
579 proxy.send_action(action);
580 continue;
581 }
582
583 run_action(
584 action,
585 &program,
586 &mut runtime,
587 &mut window,
588 &mut messages,
589 &mut clipboard,
590 &mut interface,
591 &mut system_theme,
592 );
593 }
594
595 if logical_size != window.state.logical_size() {
597 logical_size = window.state.logical_size();
598
599 debug!("Window scale factor changed during a redraw request");
600
601 let ui = interface.take().expect("Remove user interface");
602
603 let layout_span = iced_debug::layout(window.id);
604 *interface = Some(ui.relayout(logical_size, &mut window.renderer));
605 layout_span.finish();
606 }
607 }
608 };
609 interact_span.finish();
610
611 let draw_span = iced_debug::draw(window.id);
612 interface.as_mut().unwrap().draw(
613 &mut window.renderer,
614 window.state.theme(),
615 &renderer::Style {
616 text_color: window.state.text_color(),
617 },
618 cursor,
619 );
620 window.redraw_requested = false;
621 draw_span.finish();
622
623 if let user_interface::State::Updated {
624 redraw_request,
625 mouse_interaction: new_mouse_interaction,
626 ..
627 } = state
628 {
629 match redraw_request {
630 RedrawRequest::NextFrame => window.redraw_requested = true,
631 RedrawRequest::At(instant) => window.redraw_at = Some(instant),
632 _ => {}
633 }
634
635 if window.mouse_interaction != new_mouse_interaction {
636 window.mouse_interaction = new_mouse_interaction;
637 window.queue.send(WindowCommand::SetCursorIcon(
638 crate::shell::conversion::convert_mouse_interaction(
639 window.mouse_interaction,
640 ),
641 ));
642 }
643 }
644
645 runtime.broadcast(subscription::Event::Interaction {
646 window: window.id,
647 event: redraw_event,
648 status: iced_core::event::Status::Ignored,
649 });
650
651 let present_span = iced_debug::present(window.id);
663 match window.compositor.present(
664 &mut window.renderer,
665 &mut window.surface,
666 window.state.viewport(),
667 window.state.background_color(),
668 || {},
669 ) {
670 Ok(()) => {
671 present_span.finish();
672 }
673 Err(error) => match error {
674 compositor::SurfaceError::OutOfMemory => {
675 panic!("{error:?}");
677 }
678 compositor::SurfaceError::Outdated | compositor::SurfaceError::Lost => {
679 present_span.finish();
680
681 let physical_size = window.state.physical_size();
683
684 if error == compositor::SurfaceError::Lost {
685 window.surface = window.compositor.create_surface(
686 window.window06.clone(),
687 physical_size.width,
688 physical_size.height,
689 );
690 } else {
691 window.compositor.configure_surface(
692 &mut window.surface,
693 physical_size.width,
694 physical_size.height,
695 );
696 }
697
698 window.redraw_requested = true;
699 }
700 _ => {
701 present_span.finish();
702
703 error!("Error {error:?} when presenting surface.");
704
705 window.redraw_requested = true;
707 }
708 },
709 }
710 }
711 RuntimeEvent::UserEvent(message) => {
712 run_action(
713 message,
714 &program,
715 &mut runtime,
716 &mut window,
717 &mut messages,
718 &mut clipboard,
719 &mut interface,
720 &mut system_theme,
721 );
722 }
723 RuntimeEvent::Baseview((event, do_send_status)) => {
724 window.state.update(&event);
725
726 match &event {
727 baseview::Event::Window(baseview::WindowEvent::Focused)
728 | baseview::Event::Window(baseview::WindowEvent::Unfocused) => {
729 window.redraw_requested = true;
730 }
731 _ => {}
732 }
733
734 crate::shell::conversion::baseview_to_iced_events(
735 event,
736 &mut events,
737 &mut window.state.modifiers,
738 window.ignore_non_modifier_keys,
739 window.id,
740 );
741
742 if events.is_empty() {
743 if do_send_status {
744 *event_status.borrow_mut() = EventStatus::Ignored;
745 }
746 continue;
747 }
748
749 did_process_event = true;
750 }
751 RuntimeEvent::WillClose => {
752 run_action(
753 Action::Window(iced_runtime::window::Action::Close(window.id)),
754 &program,
755 &mut runtime,
756 &mut window,
757 &mut messages,
758 &mut clipboard,
759 &mut interface,
760 &mut system_theme,
761 );
762
763 break 'next_event;
764 }
765 }
766 }
767
768 let _ = ManuallyDrop::into_inner(interface);
770}
771
772fn rebuild_user_interface<'a, P: Program>(
773 program: &'a iced_program::Instance<P>,
774 window: &mut InstanceWindow<P, <P::Renderer as compositor::Default>::Compositor>,
775 cache: user_interface::Cache,
776) -> UserInterface<'a, P::Message, P::Theme, P::Renderer>
777where
778 P::Theme: theme::Base,
779{
780 window.state.synchronize(program, window.id);
781
782 iced_debug::theme_changed(|| theme::Base::palette(window.state.theme()));
783
784 build_user_interface(
785 program,
786 cache,
787 &mut window.renderer,
788 window.state.logical_size(),
789 window.id,
790 )
791}
792
793fn build_user_interface<'a, P: Program>(
795 program: &'a iced_program::Instance<P>,
796 cache: user_interface::Cache,
797 renderer: &mut P::Renderer,
798 size: Size,
799 id: iced_core::window::Id,
800) -> UserInterface<'a, P::Message, P::Theme, P::Renderer>
801where
802 P::Theme: theme::Base,
803{
804 let view_span = iced_debug::view(id);
805 let view = program.view(id);
806 view_span.finish();
807
808 let layout_span = iced_debug::layout(id);
809 let user_interface = UserInterface::build(view, size, cache, renderer);
810 layout_span.finish();
811
812 user_interface
813}
814
815fn update<P: Program, E: Executor>(
816 program: &mut iced_program::Instance<P>,
817 runtime: &mut Runtime<E, Proxy<P::Message>, Action<P::Message>>,
818 messages: &mut Vec<P::Message>,
819) -> Vec<Action<P::Message>>
820where
821 P::Theme: theme::Base,
822{
823 use iced_futures::futures::{self, StreamExt};
824
825 let mut actions = Vec::new();
826
827 for message in messages.drain(..) {
828 let task = runtime.enter(|| program.update(message));
829
830 if let Some(mut stream) = iced_runtime::task::into_stream(task) {
831 let waker = futures::task::noop_waker_ref();
832 let mut context = futures::task::Context::from_waker(waker);
833
834 loop {
836 match runtime.enter(|| stream.poll_next_unpin(&mut context)) {
837 futures::task::Poll::Ready(Some(action)) => {
838 actions.push(action);
839 }
840 futures::task::Poll::Ready(None) => {
841 break;
842 }
843 futures::task::Poll::Pending => {
844 runtime.run(stream);
845 break;
846 }
847 }
848 }
849 }
850 }
851
852 let subscription = runtime.enter(|| program.subscription());
853 let recipes = subscription::into_recipes(subscription.map(Action::Output));
854
855 runtime.track(recipes);
856
857 actions
858}
859
860fn log_unsupported_window_action(command: &str) {
861 warn!(
862 "window::Action::{} is not supported in the baseview backend",
863 command
864 );
865}
866
867#[allow(clippy::too_many_arguments)]
868fn run_action<'a, P>(
869 action: Action<P::Message>,
870 program: &'a iced_program::Instance<P>,
871 runtime: &mut Runtime<P::Executor, Proxy<P::Message>, Action<P::Message>>,
872 window: &mut InstanceWindow<P, <P::Renderer as compositor::Default>::Compositor>,
873 messages: &mut Vec<P::Message>,
874 clipboard: &mut Clipboard,
875 user_interface: &mut Option<UserInterface<'a, P::Message, P::Theme, P::Renderer>>,
876 system_theme: &mut theme::Mode,
877) where
878 P: Program,
879 P::Theme: theme::Base,
880{
881 use crate::runtime::clipboard;
882 use crate::runtime::window;
883
884 match action {
885 Action::Output(message) => {
886 messages.push(message);
887 }
888 Action::Clipboard(action) => match action {
889 clipboard::Action::Read { target, channel } => {
890 let _ = channel.send(clipboard.read(target));
891 }
892 clipboard::Action::Write { target, contents } => {
893 clipboard.write(target, contents);
894 }
895 },
896 Action::Window(action) => match action {
897 window::Action::Open(..) => {
898 log_unsupported_window_action("Open");
899 }
900 window::Action::Close(..) => {
901 window.queue.send(WindowCommand::CloseWindow);
902 }
903 window::Action::GetOldest(channel) => {
904 let _ = channel.send(Some(window.id));
905 }
906 window::Action::GetLatest(channel) => {
907 let _ = channel.send(Some(window.id));
908 }
909 window::Action::Drag(..) => {
910 log_unsupported_window_action("Drag");
911 }
912 window::Action::DragResize(..) => {
913 log_unsupported_window_action("DragResize");
914 }
915 window::Action::Resize(_, size) => {
916 window.queue.send(WindowCommand::ResizeWindow(size));
917 }
918 window::Action::SetMinSize(..) => {
919 log_unsupported_window_action("SetMinSize");
920 }
921 window::Action::SetMaxSize(..) => {
922 log_unsupported_window_action("SetMaxSize");
923 }
924 window::Action::SetResizeIncrements(..) => {
925 log_unsupported_window_action("SetResizeIncrements");
926 }
927 window::Action::SetResizable(..) => {
928 log_unsupported_window_action("SetResizable");
929 }
930 window::Action::GetSize(_, channel) => {
931 let _ = channel.send(window.state.logical_size());
932 }
933 window::Action::GetMaximized(..) => {
934 log_unsupported_window_action("GetMaximized");
935 }
936 window::Action::Maximize(..) => {
937 log_unsupported_window_action("Maximize");
938 }
939 window::Action::GetMinimized(..) => {
940 log_unsupported_window_action("GetMinimized");
941 }
942 window::Action::Minimize(..) => {
943 log_unsupported_window_action("Minimize");
944 }
945 window::Action::GetPosition(..) => {
946 log_unsupported_window_action("GetPosition");
947 }
948 window::Action::GetScaleFactor(_, channel) => {
949 let _ = channel.send(window.state.window_scale_factor());
950 }
951 window::Action::Move(..) => {
952 log_unsupported_window_action("Move");
953 }
954 window::Action::SetMode(..) => {
955 log_unsupported_window_action("SetMode");
956 }
957 window::Action::SetIcon(..) => {
958 log_unsupported_window_action("SetIcon");
959 }
960 window::Action::GetMode(..) => {
961 log_unsupported_window_action("GetMode");
962 }
963 window::Action::ToggleMaximize(..) => {
964 log_unsupported_window_action("ToggleMaximize");
965 }
966 window::Action::ToggleDecorations(..) => {
967 log_unsupported_window_action("ToggleDecorations");
968 }
969 window::Action::RequestUserAttention(..) => {
970 log_unsupported_window_action("RequestUserAttention");
971 }
972 window::Action::GainFocus(..) => {
973 window.queue.send(WindowCommand::Focus);
974 }
975 window::Action::SetLevel(..) => {
976 log_unsupported_window_action("SetLevel");
977 }
978 window::Action::ShowSystemMenu(..) => {
979 log_unsupported_window_action("ShowSystemMenu");
980 }
981 window::Action::GetRawId(..) => {
982 log_unsupported_window_action("GetRawId");
983 }
984 window::Action::Run(_, f) => {
985 (f)(&window.window06);
986 }
987 window::Action::Screenshot(..) => {
988 log_unsupported_window_action("Screenshot");
989 }
990 window::Action::EnableMousePassthrough(..) => {
991 log_unsupported_window_action("EnableMousePassthrough");
992 }
993 window::Action::DisableMousePassthrough(..) => {
994 log_unsupported_window_action("DisableMousePassthrough");
995 }
996 window::Action::GetMonitorSize(..) => {
997 log_unsupported_window_action("GetMonitorSize");
998 }
999 window::Action::SetAllowAutomaticTabbing(..) => {
1000 log_unsupported_window_action("SetAllowAutomaticTabbing");
1001 }
1002 window::Action::RedrawAll => {
1003 window.redraw_requested = true;
1004 }
1005 window::Action::RelayoutAll => {
1006 if let Some(ui) = user_interface.take() {
1007 *user_interface =
1008 Some(ui.relayout(window.state.logical_size(), &mut window.renderer));
1009 }
1010
1011 window.redraw_requested = true;
1012 }
1013 },
1014 Action::System(action) => match action {
1015 iced_runtime::system::Action::GetInformation(_channel) => {
1016 #[cfg(feature = "sysinfo")]
1017 {
1018 let graphics_info = window.compositor.information();
1019
1020 let _ = std::thread::spawn(move || {
1021 let information = crate::shell::system::system_information(graphics_info);
1022
1023 let _ = _channel.send(information);
1024 });
1025 }
1026 }
1027 iced_runtime::system::Action::GetTheme(channel) => {
1028 let _ = channel.send(*system_theme);
1029 }
1030 iced_runtime::system::Action::NotifyTheme(mode) => {
1031 if mode != *system_theme {
1032 *system_theme = mode;
1033
1034 runtime.broadcast(subscription::Event::SystemThemeChanged(mode));
1035 }
1036
1037 window.state.set_system_theme(window.id, mode, program);
1038 }
1039 },
1040 Action::Widget(operation) => {
1041 let mut current_operation = Some(operation);
1042
1043 while let Some(mut operation) = current_operation.take() {
1044 if let Some(ui) = user_interface.as_mut() {
1045 ui.operate(&window.renderer, operation.as_mut());
1046 }
1047
1048 match operation.finish() {
1049 operation::Outcome::None => {}
1050 operation::Outcome::Some(()) => {}
1051 operation::Outcome::Chain(next) => {
1052 current_operation = Some(next);
1053 }
1054 }
1055 }
1056 }
1057 Action::Image(action) => match action {
1058 iced_runtime::image::Action::Allocate(handle, sender) => {
1059 use iced_core::Renderer as _;
1060
1061 window.renderer.allocate_image(&handle, move |allocation| {
1063 let _ = sender.send(allocation);
1064 });
1065 }
1066 },
1067 Action::LoadFont { bytes, channel } => {
1068 window.compositor.load_font(bytes.clone());
1070
1071 let _ = channel.send(Ok(()));
1072 }
1073 Action::Reload => {
1074 let Some(cached_interface) = user_interface.take().map(|ui| ui.into_cache()) else {
1075 return;
1076 };
1077
1078 *user_interface = Some(build_user_interface(
1079 program,
1080 cached_interface,
1081 &mut window.renderer,
1082 window.state.logical_size(),
1083 window.id,
1084 ));
1085
1086 window.redraw_requested = true;
1087 }
1088 Action::Exit => {
1089 window.queue.send(WindowCommand::CloseWindow);
1090 }
1091 }
1092}
1093
1094pub(crate) enum RuntimeEvent<Message: 'static + Send> {
1095 Baseview((baseview::Event, bool)),
1096 UserEvent(iced_runtime::Action<Message>),
1097 Poll,
1098 OnFrame,
1099 WillClose,
1100}
1101
1102fn clone_window_options(window: &WindowOpenOptions) -> WindowOpenOptions {
1103 WindowOpenOptions {
1104 title: window.title.clone(),
1105 size: window.size,
1106 scale: window.scale,
1107 ..Default::default()
1108 }
1109}