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