Skip to main content

iced_test/
emulator.rs

1//! Run your application in a headless runtime.
2use crate::core;
3use crate::core::mouse;
4use crate::core::renderer;
5use crate::core::time::Instant;
6use crate::core::widget;
7use crate::core::window;
8use crate::core::{Bytes, Element, Point, Size};
9use crate::instruction;
10use crate::program;
11use crate::program::Program;
12use crate::runtime;
13use crate::runtime::futures::futures::StreamExt;
14use crate::runtime::futures::futures::channel::mpsc;
15use crate::runtime::futures::futures::stream;
16use crate::runtime::futures::subscription;
17use crate::runtime::futures::{Executor, Runtime};
18use crate::runtime::task;
19use crate::runtime::user_interface;
20use crate::runtime::{Task, UserInterface};
21use crate::{Instruction, Selector};
22
23use std::fmt;
24
25/// A headless runtime that can run iced applications and execute
26/// [instructions](crate::Instruction).
27///
28/// An [`Emulator`] runs its program as faithfully as possible to the real thing.
29/// It will run subscriptions and tasks with the [`Executor`](Program::Executor) of
30/// the [`Program`].
31///
32/// If you want to run a simulation without side effects, use a [`Simulator`](crate::Simulator)
33/// instead.
34pub struct Emulator<P: Program> {
35    state: P::State,
36    runtime: Runtime<P::Executor, mpsc::Sender<Event<P>>, Event<P>>,
37    renderer: P::Renderer,
38    mode: Mode,
39    size: Size,
40    window: core::window::Id,
41    cursor: mouse::Cursor,
42    cache: Option<user_interface::Cache>,
43    pending_tasks: usize,
44}
45
46/// An emulation event.
47pub enum Event<P: Program> {
48    /// An action that must be [performed](Emulator::perform) by the [`Emulator`].
49    Action(Action<P>),
50    /// An [`Instruction`] failed to be executed.
51    Failed(Instruction),
52    /// The [`Emulator`] is ready.
53    Ready,
54}
55
56/// An action that must be [performed](Emulator::perform) by the [`Emulator`].
57pub struct Action<P: Program>(Action_<P>);
58
59enum Action_<P: Program> {
60    Runtime(runtime::Action<P::Message>),
61    CountDown,
62}
63
64impl<P: Program + 'static> Emulator<P> {
65    /// Creates a new [`Emulator`] of the [`Program`] with the given [`Mode`] and [`Size`].
66    ///
67    /// The [`Emulator`] will send [`Event`] notifications through the provided [`mpsc::Sender`].
68    ///
69    /// When the [`Emulator`] has finished booting, an [`Event::Ready`] will be produced.
70    pub fn new(sender: mpsc::Sender<Event<P>>, program: &P, mode: Mode, size: Size) -> Emulator<P> {
71        Self::with_preset(sender, program, mode, size, None)
72    }
73
74    /// Creates a new [`Emulator`] analogously to [`new`](Self::new), but it also takes a
75    /// [`program::Preset`] that will be used as the initial state.
76    ///
77    /// When the [`Emulator`] has finished booting, an [`Event::Ready`] will be produced.
78    pub fn with_preset(
79        sender: mpsc::Sender<Event<P>>,
80        program: &P,
81        mode: Mode,
82        size: Size,
83        preset: Option<&program::Preset<P::State, P::Message>>,
84    ) -> Emulator<P> {
85        use renderer::Headless;
86
87        let settings = program.settings();
88
89        // TODO: Error handling
90        let executor = P::Executor::new().expect("Create emulator executor");
91
92        let renderer = executor
93            .block_on(P::Renderer::new(renderer::Settings::from(&settings), None))
94            .expect("Create emulator renderer");
95
96        let runtime = Runtime::new(executor, sender);
97
98        let (state, task) = runtime.enter(|| {
99            if let Some(preset) = preset {
100                preset.boot()
101            } else {
102                program.boot()
103            }
104        });
105
106        let mut emulator = Self {
107            state,
108            runtime,
109            renderer,
110            mode,
111            size,
112            cursor: mouse::Cursor::Unavailable,
113            window: core::window::Id::unique(),
114            cache: Some(user_interface::Cache::default()),
115            pending_tasks: 0,
116        };
117
118        emulator.resubscribe(program);
119        emulator.wait_for(task);
120
121        emulator
122    }
123
124    /// Updates the state of the [`Emulator`] program.
125    ///
126    /// This is equivalent to calling the [`Program::update`] function,
127    /// resubscribing to any subscriptions, and running the resulting tasks
128    /// concurrently.
129    pub fn update(&mut self, program: &P, message: P::Message) {
130        let task = self
131            .runtime
132            .enter(|| program.update(&mut self.state, message));
133
134        self.resubscribe(program);
135
136        match self.mode {
137            Mode::Zen if self.pending_tasks > 0 => self.wait_for(task),
138            _ => {
139                if let Some(stream) = task::into_stream(task) {
140                    self.runtime.run(
141                        stream
142                            .map(Action_::Runtime)
143                            .map(Action)
144                            .map(Event::Action)
145                            .boxed(),
146                    );
147                }
148            }
149        }
150    }
151
152    /// Performs an [`Action`].
153    ///
154    /// Whenever an [`Emulator`] sends an [`Event::Action`], this
155    /// method must be called to proceed with the execution.
156    pub fn perform(&mut self, program: &P, action: Action<P>) {
157        match action.0 {
158            Action_::CountDown => {
159                if self.pending_tasks > 0 {
160                    self.pending_tasks -= 1;
161
162                    if self.pending_tasks == 0 {
163                        self.runtime.send(Event::Ready);
164                    }
165                }
166            }
167            Action_::Runtime(action) => match action {
168                runtime::Action::Output(message) => {
169                    self.update(program, message);
170                }
171                runtime::Action::Widget(operation) => {
172                    let mut user_interface = UserInterface::build(
173                        program.view(&self.state, self.window),
174                        self.size,
175                        self.cache.take().unwrap(),
176                        &mut self.renderer,
177                    );
178
179                    let mut operation = Some(operation);
180
181                    while let Some(mut current) = operation.take() {
182                        user_interface.operate(&self.renderer, &mut current);
183
184                        match current.finish() {
185                            widget::operation::Outcome::None => {}
186                            widget::operation::Outcome::Some(()) => {}
187                            widget::operation::Outcome::Chain(next) => {
188                                operation = Some(next);
189                            }
190                        }
191                    }
192
193                    self.cache = Some(user_interface.into_cache());
194                }
195                runtime::Action::Clipboard(action) => {
196                    // TODO
197                    dbg!(action);
198                }
199                runtime::Action::Window(action) => {
200                    use crate::runtime::window;
201
202                    match action {
203                        window::Action::Open(id, _settings, sender) => {
204                            self.window = id;
205
206                            let _ = sender.send(self.window);
207                        }
208                        window::Action::GetOldest(sender) | window::Action::GetLatest(sender) => {
209                            let _ = sender.send(Some(self.window));
210                        }
211                        window::Action::GetSize(id, sender) => {
212                            if id == self.window {
213                                let _ = sender.send(self.size);
214                            }
215                        }
216                        window::Action::GetMaximized(id, sender) => {
217                            if id == self.window {
218                                let _ = sender.send(false);
219                            }
220                        }
221                        window::Action::GetMinimized(id, sender) => {
222                            if id == self.window {
223                                let _ = sender.send(None);
224                            }
225                        }
226                        window::Action::GetPosition(id, sender) => {
227                            if id == self.window {
228                                let _ = sender.send(Some(Point::ORIGIN));
229                            }
230                        }
231                        window::Action::GetScaleFactor(id, sender) => {
232                            if id == self.window {
233                                let _ = sender.send(1.0);
234                            }
235                        }
236                        window::Action::GetMode(id, sender) => {
237                            if id == self.window {
238                                let _ = sender.send(core::window::Mode::Windowed);
239                            }
240                        }
241                        _ => {
242                            // Ignored
243                        }
244                    }
245                }
246                runtime::Action::System(action) => {
247                    // TODO
248                    dbg!(action);
249                }
250                runtime::Action::Font(action) => {
251                    // TODO
252                    dbg!(action);
253                }
254                runtime::Action::Image(action) => {
255                    // TODO
256                    dbg!(action);
257                }
258                iced_runtime::Action::Event { window, event } => {
259                    // TODO
260                    dbg!(window, event);
261                }
262                runtime::Action::Tick => {
263                    // TODO
264                }
265                runtime::Action::Exit => {
266                    // TODO
267                }
268                runtime::Action::Reload => {
269                    // TODO
270                }
271                runtime::Action::Announce(_) => {}
272            },
273        }
274    }
275
276    /// Runs an [`Instruction`].
277    ///
278    /// If the [`Instruction`] executes successfully, an [`Event::Ready`] will be
279    /// produced by the [`Emulator`].
280    ///
281    /// Otherwise, an [`Event::Failed`] will be triggered.
282    pub fn run(&mut self, program: &P, instruction: &Instruction) {
283        let mut user_interface = UserInterface::build(
284            program.view(&self.state, self.window),
285            self.size,
286            self.cache.take().unwrap(),
287            &mut self.renderer,
288        );
289
290        let mut messages = Vec::new();
291
292        match instruction {
293            Instruction::Interact(interaction) => {
294                let Some(events) = interaction.events(|target| match target {
295                    instruction::Target::Id(id) => {
296                        use widget::Operation;
297
298                        let mut operation = Selector::find(widget::Id::from(id.to_owned()));
299
300                        user_interface.operate(
301                            &self.renderer,
302                            &mut widget::operation::black_box(&mut operation),
303                        );
304
305                        match operation.finish() {
306                            widget::operation::Outcome::Some(widget) => {
307                                Some(widget?.visible_bounds()?.center())
308                            }
309                            _ => None,
310                        }
311                    }
312                    instruction::Target::Text(text) => {
313                        use widget::Operation;
314
315                        let mut operation = Selector::find(text.as_str());
316
317                        user_interface.operate(
318                            &self.renderer,
319                            &mut widget::operation::black_box(&mut operation),
320                        );
321
322                        match operation.finish() {
323                            widget::operation::Outcome::Some(text) => {
324                                Some(text?.visible_bounds()?.center())
325                            }
326                            _ => None,
327                        }
328                    }
329                    instruction::Target::Point(position) => Some(*position),
330                }) else {
331                    self.runtime.send(Event::Failed(instruction.clone()));
332                    self.cache = Some(user_interface.into_cache());
333                    return;
334                };
335
336                for event in &events {
337                    if let core::Event::Mouse(mouse::Event::CursorMoved { position }) = event {
338                        self.cursor = mouse::Cursor::Available(*position);
339                    }
340                }
341
342                let (_state, statuses) =
343                    user_interface.update(&events, self.cursor, &mut self.renderer, &mut messages);
344
345                for (event, status) in events.iter().zip(statuses.iter().copied()) {
346                    let _ = runtime::keyboard::handle_ctrl_tab(
347                        event,
348                        &mut user_interface,
349                        &self.renderer,
350                    ) || runtime::keyboard::handle_tab(
351                        event,
352                        status,
353                        &mut user_interface,
354                        &self.renderer,
355                    );
356                }
357
358                self.cache = Some(user_interface.into_cache());
359
360                let task = self.runtime.enter(|| {
361                    Task::batch(
362                        messages
363                            .into_iter()
364                            .map(|message| program.update(&mut self.state, message)),
365                    )
366                });
367
368                self.resubscribe(program);
369                self.wait_for(task);
370            }
371            Instruction::Expect(expectation) => match expectation {
372                instruction::Expectation::Text(text) => {
373                    use widget::Operation;
374
375                    let mut operation = Selector::find(text.as_str());
376
377                    user_interface.operate(
378                        &self.renderer,
379                        &mut widget::operation::black_box(&mut operation),
380                    );
381
382                    match operation.finish() {
383                        widget::operation::Outcome::Some(Some(_text)) => {
384                            self.runtime.send(Event::Ready);
385                        }
386                        _ => {
387                            self.runtime.send(Event::Failed(instruction.clone()));
388                        }
389                    }
390
391                    self.cache = Some(user_interface.into_cache());
392                }
393                instruction::Expectation::Focused(target) => {
394                    use crate::selector;
395                    use widget::Operation;
396
397                    let mut operation = selector::is_focused().find();
398
399                    user_interface.operate(
400                        &self.renderer,
401                        &mut widget::operation::black_box(&mut operation),
402                    );
403
404                    let focused_id = match operation.finish() {
405                        widget::operation::Outcome::Some(Some(focused)) => match focused {
406                            selector::Target::Focusable { id, .. }
407                            | selector::Target::TextInput { id, .. }
408                            | selector::Target::Container { id, .. }
409                            | selector::Target::Scrollable { id, .. }
410                            | selector::Target::Text { id, .. }
411                            | selector::Target::Custom { id, .. }
412                            | selector::Target::Accessible { id, .. } => id,
413                        },
414                        _ => None,
415                    };
416
417                    let found = match &target {
418                        instruction::Target::Id(id) => focused_id
419                            .as_ref()
420                            .is_some_and(|fid| *fid == widget::Id::from(id.to_owned())),
421                        instruction::Target::Text(_) => {
422                            // Text-based focus matching not supported;
423                            // use widget IDs for focus assertions
424                            false
425                        }
426                        instruction::Target::Point(_) => false,
427                    };
428
429                    if found {
430                        self.runtime.send(Event::Ready);
431                    } else {
432                        self.runtime.send(Event::Failed(instruction.clone()));
433                    }
434
435                    self.cache = Some(user_interface.into_cache());
436                }
437            },
438        }
439    }
440
441    fn wait_for(&mut self, task: Task<P::Message>) {
442        if let Some(stream) = task::into_stream(task) {
443            match self.mode {
444                Mode::Zen => {
445                    self.pending_tasks += 1;
446
447                    self.runtime.run(
448                        stream
449                            .map(Action_::Runtime)
450                            .map(Action)
451                            .map(Event::Action)
452                            .chain(stream::once(async {
453                                Event::Action(Action(Action_::CountDown))
454                            }))
455                            .boxed(),
456                    );
457                }
458                Mode::Patient => {
459                    self.runtime.run(
460                        stream
461                            .map(Action_::Runtime)
462                            .map(Action)
463                            .map(Event::Action)
464                            .chain(stream::once(async { Event::Ready }))
465                            .boxed(),
466                    );
467                }
468                Mode::Immediate => {
469                    self.runtime.run(
470                        stream
471                            .map(Action_::Runtime)
472                            .map(Action)
473                            .map(Event::Action)
474                            .boxed(),
475                    );
476                    self.runtime.send(Event::Ready);
477                }
478            }
479        } else if self.pending_tasks == 0 {
480            self.runtime.send(Event::Ready);
481        }
482    }
483
484    fn resubscribe(&mut self, program: &P) {
485        self.runtime
486            .track(subscription::into_recipes(self.runtime.enter(|| {
487                program.subscription(&self.state).map(|message| {
488                    Event::Action(Action(Action_::Runtime(runtime::Action::Output(message))))
489                })
490            })));
491    }
492
493    /// Returns the current view of the [`Emulator`].
494    pub fn view(&self, program: &P) -> Element<'_, P::Message, P::Theme, P::Renderer> {
495        program.view(&self.state, self.window)
496    }
497
498    /// Returns the current theme of the [`Emulator`].
499    pub fn theme(&self, program: &P) -> Option<P::Theme> {
500        program.theme(&self.state, self.window)
501    }
502
503    /// Takes a [`window::Screenshot`] of the current state of the [`Emulator`].
504    pub fn screenshot(
505        &mut self,
506        program: &P,
507        theme: &P::Theme,
508        scale_factor: f32,
509    ) -> window::Screenshot {
510        use core::renderer::Headless;
511
512        let style = program.style(&self.state, theme);
513
514        let mut user_interface = UserInterface::build(
515            program.view(&self.state, self.window),
516            self.size,
517            self.cache.take().unwrap(),
518            &mut self.renderer,
519        );
520
521        // TODO: Nested redraws!
522        let _ = user_interface.update(
523            &[core::Event::Window(window::Event::RedrawRequested(
524                Instant::now(),
525            ))],
526            mouse::Cursor::Unavailable,
527            &mut self.renderer,
528            &mut Vec::new(),
529        );
530
531        user_interface.draw(
532            &mut self.renderer,
533            theme,
534            &renderer::Style {
535                text_color: style.text_color,
536            },
537            mouse::Cursor::Unavailable,
538        );
539
540        let physical_size = Size::new(
541            (self.size.width * scale_factor).round() as u32,
542            (self.size.height * scale_factor).round() as u32,
543        );
544
545        let rgba = self
546            .renderer
547            .screenshot(physical_size, scale_factor, style.background_color);
548
549        window::Screenshot {
550            rgba: Bytes::from(rgba),
551            size: physical_size,
552            scale_factor,
553        }
554    }
555
556    /// Turns the [`Emulator`] into its internal state.
557    pub fn into_state(self) -> (P::State, core::window::Id) {
558        (self.state, self.window)
559    }
560}
561
562/// The strategy used by an [`Emulator`] when waiting for tasks to finish.
563///
564/// A [`Mode`] can be used to make an [`Emulator`] wait for side effects to finish before
565/// continuing execution.
566#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
567pub enum Mode {
568    /// Waits for all tasks spawned by an [`Instruction`], as well as all tasks indirectly
569    /// spawned by the the results of those tasks.
570    ///
571    /// This is the default.
572    #[default]
573    Zen,
574    /// Waits only for the tasks directly spawned by an [`Instruction`].
575    Patient,
576    /// Never waits for any tasks to finish.
577    Immediate,
578}
579
580impl Mode {
581    /// A list of all the available modes.
582    pub const ALL: &[Self] = &[Self::Zen, Self::Patient, Self::Immediate];
583}
584
585impl fmt::Display for Mode {
586    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
587        f.write_str(match self {
588            Self::Zen => "Zen",
589            Self::Patient => "Patient",
590            Self::Immediate => "Immediate",
591        })
592    }
593}