1use 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
25pub 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
47pub enum Event<P: Program> {
49 Action(Action<P>),
51 Failed(Instruction),
53 Ready,
55}
56
57pub 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 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 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 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 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 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 }
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 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 }
253 }
254 }
255 runtime::Action::System(action) => {
256 dbg!(action);
258 }
259 iced_runtime::Action::Image(action) => {
260 dbg!(action);
262 }
263 iced_runtime::Action::Tick => {
264 }
266 runtime::Action::Exit => {
267 }
269 runtime::Action::Reload => {
270 }
272 },
273 }
274 }
275
276 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 pub fn view(&self, program: &P) -> Element<'_, P::Message, P::Theme, P::Renderer> {
426 program.view(&self.state, self.window)
427 }
428
429 pub fn theme(&self, program: &P) -> Option<P::Theme> {
431 program.theme(&self.state, self.window)
432 }
433
434 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 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 pub fn into_state(self) -> (P::State, core::window::Id) {
490 (self.state, self.window)
491 }
492}
493
494#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
499pub enum Mode {
500 #[default]
505 Zen,
506 Patient,
508 Immediate,
510}
511
512impl Mode {
513 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}