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 runtime::Action::Exit => {
264 }
266 runtime::Action::Reload => {
267 }
269 },
270 }
271 }
272
273 pub fn run(&mut self, program: &P, instruction: Instruction) {
280 let mut user_interface = UserInterface::build(
281 program.view(&self.state, self.window),
282 self.size,
283 self.cache.take().unwrap(),
284 &mut self.renderer,
285 );
286
287 let mut messages = Vec::new();
288
289 match &instruction {
290 Instruction::Interact(interaction) => {
291 let Some(events) = interaction.events(|target| match target {
292 instruction::Target::Point(position) => Some(*position),
293 instruction::Target::Text(text) => {
294 use widget::Operation;
295
296 let mut operation = Selector::find(text.as_str());
297
298 user_interface.operate(
299 &self.renderer,
300 &mut widget::operation::black_box(&mut operation),
301 );
302
303 match operation.finish() {
304 widget::operation::Outcome::Some(text) => {
305 Some(text?.visible_bounds()?.center())
306 }
307 _ => None,
308 }
309 }
310 }) else {
311 self.runtime.send(Event::Failed(instruction));
312 self.cache = Some(user_interface.into_cache());
313 return;
314 };
315
316 for event in &events {
317 if let core::Event::Mouse(mouse::Event::CursorMoved { position }) = event {
318 self.cursor = mouse::Cursor::Available(*position);
319 }
320 }
321
322 let (_state, _status) = user_interface.update(
323 &events,
324 self.cursor,
325 &mut self.renderer,
326 &mut self.clipboard,
327 &mut messages,
328 );
329
330 self.cache = Some(user_interface.into_cache());
331
332 let task = self.runtime.enter(|| {
333 Task::batch(
334 messages
335 .into_iter()
336 .map(|message| program.update(&mut self.state, message)),
337 )
338 });
339
340 self.resubscribe(program);
341 self.wait_for(task);
342 }
343 Instruction::Expect(expectation) => match expectation {
344 instruction::Expectation::Text(text) => {
345 use widget::Operation;
346
347 let mut operation = Selector::find(text.as_str());
348
349 user_interface.operate(
350 &self.renderer,
351 &mut widget::operation::black_box(&mut operation),
352 );
353
354 match operation.finish() {
355 widget::operation::Outcome::Some(Some(_text)) => {
356 self.runtime.send(Event::Ready);
357 }
358 _ => {
359 self.runtime.send(Event::Failed(instruction));
360 }
361 }
362
363 self.cache = Some(user_interface.into_cache());
364 }
365 },
366 }
367 }
368
369 fn wait_for(&mut self, task: Task<P::Message>) {
370 if let Some(stream) = task::into_stream(task) {
371 match self.mode {
372 Mode::Zen => {
373 self.pending_tasks += 1;
374
375 self.runtime.run(
376 stream
377 .map(Action_::Runtime)
378 .map(Action)
379 .map(Event::Action)
380 .chain(stream::once(async {
381 Event::Action(Action(Action_::CountDown))
382 }))
383 .boxed(),
384 );
385 }
386 Mode::Patient => {
387 self.runtime.run(
388 stream
389 .map(Action_::Runtime)
390 .map(Action)
391 .map(Event::Action)
392 .chain(stream::once(async { Event::Ready }))
393 .boxed(),
394 );
395 }
396 Mode::Immediate => {
397 self.runtime.run(
398 stream
399 .map(Action_::Runtime)
400 .map(Action)
401 .map(Event::Action)
402 .boxed(),
403 );
404 self.runtime.send(Event::Ready);
405 }
406 }
407 } else if self.pending_tasks == 0 {
408 self.runtime.send(Event::Ready);
409 }
410 }
411
412 fn resubscribe(&mut self, program: &P) {
413 self.runtime
414 .track(subscription::into_recipes(self.runtime.enter(|| {
415 program.subscription(&self.state).map(|message| {
416 Event::Action(Action(Action_::Runtime(runtime::Action::Output(message))))
417 })
418 })));
419 }
420
421 pub fn view(&self, program: &P) -> Element<'_, P::Message, P::Theme, P::Renderer> {
423 program.view(&self.state, self.window)
424 }
425
426 pub fn theme(&self, program: &P) -> Option<P::Theme> {
428 program.theme(&self.state, self.window)
429 }
430
431 pub fn screenshot(
433 &mut self,
434 program: &P,
435 theme: &P::Theme,
436 scale_factor: f32,
437 ) -> window::Screenshot {
438 use core::renderer::Headless;
439
440 let style = program.style(&self.state, theme);
441
442 let mut user_interface = UserInterface::build(
443 program.view(&self.state, self.window),
444 self.size,
445 self.cache.take().unwrap(),
446 &mut self.renderer,
447 );
448
449 let _ = user_interface.update(
451 &[core::Event::Window(window::Event::RedrawRequested(
452 Instant::now(),
453 ))],
454 mouse::Cursor::Unavailable,
455 &mut self.renderer,
456 &mut self.clipboard,
457 &mut Vec::new(),
458 );
459
460 user_interface.draw(
461 &mut self.renderer,
462 theme,
463 &renderer::Style {
464 text_color: style.text_color,
465 },
466 mouse::Cursor::Unavailable,
467 );
468
469 let physical_size = Size::new(
470 (self.size.width * scale_factor).round() as u32,
471 (self.size.height * scale_factor).round() as u32,
472 );
473
474 let rgba = self
475 .renderer
476 .screenshot(physical_size, scale_factor, style.background_color);
477
478 window::Screenshot {
479 rgba: Bytes::from(rgba),
480 size: physical_size,
481 scale_factor,
482 }
483 }
484
485 pub fn into_state(self) -> (P::State, core::window::Id) {
487 (self.state, self.window)
488 }
489}
490
491#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
496pub enum Mode {
497 #[default]
502 Zen,
503 Patient,
505 Immediate,
507}
508
509impl Mode {
510 pub const ALL: &[Self] = &[Self::Zen, Self::Patient, Self::Immediate];
512}
513
514impl fmt::Display for Mode {
515 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
516 f.write_str(match self {
517 Self::Zen => "Zen",
518 Self::Patient => "Patient",
519 Self::Immediate => "Immediate",
520 })
521 }
522}
523
524struct Clipboard {
525 content: Option<String>,
526}
527
528impl core::Clipboard for Clipboard {
529 fn read(&self, _kind: core::clipboard::Kind) -> Option<String> {
530 self.content.clone()
531 }
532
533 fn write(&mut self, _kind: core::clipboard::Kind, contents: String) {
534 self.content = Some(contents);
535 }
536}