Skip to main content

iced_baseview/shell/
mod.rs

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/// An atomic flag used to notify the program when it should poll for new updates
47/// and redraw (i.e. as a result of the host updating parameters or the audio thread
48/// updating the state of meters). This flag is polled every frame right before
49/// drawing. If the flag is set then the [`poll_events`] subscription will be called.
50#[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
77// This is a bit hacky, but Iced doesn't let you define custom subscription
78// events. So instead, we use an event that is never used in baseview.
79//
80// TODO: Ask Iced team to add custom subscription events.
81const POLL_EVENT: iced_core::Event =
82    iced_core::Event::Window(iced_core::window::Event::Moved(Point::ORIGIN));
83
84/// A subscription which notifies the program when it should poll for new updates
85/// and redraw (i.e. as a result of the host updating parameters or the audio thread
86/// updating the state of meters).
87pub fn poll_events() -> Subscription<()> {
88    iced_futures::event::listen_raw(|event, _status, _window| match event {
89        POLL_EVENT => Some(()),
90        _ => None,
91    })
92}
93
94/// Open a new window that blocks the current thread until the window is destroyed.
95///
96/// * `settings` - The settings of the window.
97/// * `notifier` - An atomic flag used to notify the program when it should
98///   poll for new updates and redraw (i.e. as a result of the host updating parameters
99///   or the audio thread updating the state of meters). This flag is polled every frame
100///   right before drawing. If the flag is set then the [`poll_events`] subscription
101///   will be called.
102/// * `build_program` - The function which builds the Iced program.
103pub 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
122/// Open a new child window.
123///
124/// * `parent` - The parent window.
125/// * `settings` - The settings of the window.
126/// * `notifier` - An atomic flag used to notify the program when it should
127///   poll for new updates and redraw (i.e. as a result of the host updating parameters
128///   or the audio thread updating the state of meters). This flag is polled every frame
129///   right before drawing. If the flag is set then the [`poll_events`] subscription
130///   will be called.
131/// * `build_program` - The functio which builds the Iced program.
132pub 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
159/// Runs a [`Program`] with the provided settings.
160fn 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    // Assume scale for now until there is an event with a new one.
179    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        //preedit: None,
266        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    // Triggered whenever a baseview event gets sent
335    window.redraw_requested = true;
336    window.redraw_at = None;
337    // May be triggered when processing baseview events, will cause the UI to be updated in the next
338    // frame
339    let mut did_process_event = false;
340
341    'next_event: loop {
342        // Empty the queue if possible
343        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                // -- Draw --------------------------------------------------------------------
488
489                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                // Window was resized between redraws
512                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                            // Defer all window actions to avoid compositor
577                            // race conditions while redrawing
578                            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                        // Window scale factor changed during a redraw request
596                        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                /*
652                if let Some(preedit) = &window.preedit {
653                    preedit.draw(
654                        &mut window.renderer,
655                        window.state.text_color(),
656                        window.state.background_color(),
657                        &Rectangle::new(Point::ORIGIN, window.state.viewport().logical_size()),
658                    );
659                }
660                */
661
662                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                            // This is an unrecoverable error.
676                            panic!("{error:?}");
677                        }
678                        compositor::SurfaceError::Outdated | compositor::SurfaceError::Lost => {
679                            present_span.finish();
680
681                            // Reconfigure surface and try redrawing
682                            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                            // Try rendering all windows again next frame.
706                            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    // Manually drop the user interface
769    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
793/// Builds a window's [`UserInterface`] for the [`Program`].
794fn 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            // Run immediately available actions synchronously (e.g. widget operations)
835            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                // TODO: Shared image cache in compositor
1062                window.renderer.allocate_image(&handle, move |allocation| {
1063                    let _ = sender.send(allocation);
1064                });
1065            }
1066        },
1067        Action::LoadFont { bytes, channel } => {
1068            // TODO: Error handling (?)
1069            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}