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