1use crate::core;
3use crate::core::font;
4use crate::core::mouse;
5use crate::core::renderer;
6use crate::core::shell;
7use crate::core::time::Instant;
8use crate::core::widget;
9use crate::core::window;
10use crate::core::{Bytes, Element, Point, Size};
11use crate::instruction;
12use crate::program;
13use crate::program::Program;
14use crate::runtime;
15use crate::runtime::futures::futures::StreamExt;
16use crate::runtime::futures::futures::channel::mpsc;
17use crate::runtime::futures::futures::stream;
18use crate::runtime::futures::subscription;
19use crate::runtime::futures::{Executor, Runtime};
20use crate::runtime::task;
21use crate::runtime::user_interface;
22use crate::runtime::{Task, UserInterface};
23use crate::{Instruction, Selector};
24
25use std::borrow::Cow;
26use std::fmt;
27
28pub struct Emulator<P: Program> {
38 state: P::State,
39 runtime: Runtime<P::Executor, mpsc::Sender<Event<P>>, Event<P>>,
40 renderer: P::Renderer,
41 mode: Mode,
42 size: Size,
43 window: core::window::Id,
44 cursor: mouse::Cursor,
45 cache: Option<user_interface::Cache>,
46 pending_tasks: usize,
47}
48
49pub enum Event<P: Program> {
51 Action(Action<P>),
53 Failed(Instruction),
55 Ready,
57}
58
59pub struct Action<P: Program>(Action_<P>);
61
62enum Action_<P: Program> {
63 Runtime(runtime::Action<P::Message>),
64 CountDown,
65}
66
67impl<P: Program + 'static> Emulator<P> {
68 pub fn new(sender: mpsc::Sender<Event<P>>, program: &P, mode: Mode, size: Size) -> Emulator<P> {
74 Self::with_preset(sender, program, mode, size, None)
75 }
76
77 pub fn with_preset(
82 sender: mpsc::Sender<Event<P>>,
83 program: &P,
84 mode: Mode,
85 size: Size,
86 preset: Option<&program::Preset<P::State, P::Message>>,
87 ) -> Emulator<P> {
88 use renderer::Headless;
89
90 let settings = program.settings();
91
92 for font in &settings.fonts {
93 load_font(font.clone()).expect("Font must be valid");
94 }
95
96 let executor = P::Executor::new().expect("Create emulator executor");
98
99 let backend = std::env::var("ICED_TEST_BACKEND").ok();
100
101 let renderer = executor
102 .block_on(P::Renderer::new(
103 renderer::Settings::from(&settings),
104 backend.as_deref(),
105 ))
106 .expect("Create emulator renderer");
107
108 let runtime = Runtime::new(executor, sender);
109
110 let (state, task) = runtime.enter(|| {
111 if let Some(preset) = preset {
112 preset.boot()
113 } else {
114 program.boot()
115 }
116 });
117
118 let mut emulator = Self {
119 state,
120 runtime,
121 renderer,
122 mode,
123 size,
124 cursor: mouse::Cursor::Unavailable,
125 window: core::window::Id::unique(),
126 cache: Some(user_interface::Cache::default()),
127 pending_tasks: 0,
128 };
129
130 emulator.resubscribe(program);
131 emulator.wait_for(task);
132
133 emulator
134 }
135
136 pub fn update(&mut self, program: &P, message: P::Message) {
142 let task = self
143 .runtime
144 .enter(|| program.update(&mut self.state, message));
145
146 self.resubscribe(program);
147
148 match self.mode {
149 Mode::Zen if self.pending_tasks > 0 => self.wait_for(task),
150 _ => {
151 if let Some(stream) = task::into_stream(task) {
152 self.runtime.run(
153 stream
154 .map(Action_::Runtime)
155 .map(Action)
156 .map(Event::Action)
157 .boxed(),
158 );
159 }
160 }
161 }
162 }
163
164 pub fn perform(&mut self, program: &P, action: Action<P>) {
169 match action.0 {
170 Action_::CountDown => {
171 if self.pending_tasks > 0 {
172 self.pending_tasks -= 1;
173
174 if self.pending_tasks == 0 {
175 self.runtime.send(Event::Ready);
176 }
177 }
178 }
179 Action_::Runtime(action) => match action {
180 runtime::Action::Output(message) => {
181 self.update(program, message);
182 }
183 runtime::Action::Widget(operation) => {
184 let mut user_interface = UserInterface::build(
185 program.view(&self.state, self.window),
186 self.size,
187 self.cache.take().unwrap(),
188 &mut self.renderer,
189 );
190
191 let mut operation = Some(operation);
192
193 while let Some(mut current) = operation.take() {
194 user_interface.operate(&self.renderer, &mut current);
195
196 match current.finish() {
197 widget::operation::Outcome::None => {}
198 widget::operation::Outcome::Some(()) => {}
199 widget::operation::Outcome::Chain(next) => {
200 operation = Some(next);
201 }
202 }
203 }
204
205 self.cache = Some(user_interface.into_cache());
206 }
207 runtime::Action::Clipboard(action) => {
208 dbg!(action);
210 }
211 runtime::Action::Window(action) => {
212 use crate::runtime::window;
213
214 match action {
215 window::Action::Open(id, _settings, sender) => {
216 self.window = id;
217
218 let _ = sender.send(self.window);
219 }
220 window::Action::GetOldest(sender) | window::Action::GetLatest(sender) => {
221 let _ = sender.send(Some(self.window));
222 }
223 window::Action::GetSize(id, sender) if id == self.window => {
224 let _ = sender.send(self.size);
225 }
226 window::Action::GetMaximized(id, sender) if id == self.window => {
227 let _ = sender.send(false);
228 }
229 window::Action::GetMinimized(id, sender) if id == self.window => {
230 let _ = sender.send(None);
231 }
232 window::Action::GetPosition(id, sender) if id == self.window => {
233 let _ = sender.send(Some(Point::ORIGIN));
234 }
235 window::Action::GetScaleFactor(id, sender) if id == self.window => {
236 let _ = sender.send(1.0);
237 }
238 window::Action::GetMode(id, sender) if id == self.window => {
239 let _ = sender.send(core::window::Mode::Windowed);
240 }
241 _ => {
242 }
244 }
245 }
246 runtime::Action::System(action) => {
247 dbg!(action);
249 }
250 runtime::Action::Font(action) => {
251 use crate::runtime::font;
252
253 match action {
254 font::Action::Load { bytes, channel } => {
255 let result = load_font(bytes);
256 let _ = channel.send(result);
257 }
258 _ => {
259 dbg!(action);
261 }
262 }
263 }
264 runtime::Action::Image(action) => {
265 dbg!(action);
267 }
268 runtime::Action::Backend(action) => {
269 dbg!(action);
271 }
272 runtime::Action::Event { window, event } => {
273 dbg!(window, event);
275 }
276 runtime::Action::Tick => {
277 }
279 runtime::Action::Exit => {
280 }
282 runtime::Action::Reload => {
283 }
285 },
286 }
287 }
288
289 pub fn run(&mut self, program: &P, instruction: &Instruction) {
296 let mut user_interface = UserInterface::build(
297 program.view(&self.state, self.window),
298 self.size,
299 self.cache.take().unwrap(),
300 &mut self.renderer,
301 );
302
303 let mut messages = shell::Bus::new();
304
305 match instruction {
306 Instruction::Interact(interaction) => {
307 let Some(events) = interaction.events(|target| match target {
308 instruction::Target::Id(id) => {
309 use widget::Operation;
310
311 let mut operation = Selector::find(widget::Id::from(id.to_owned()));
312
313 user_interface.operate(
314 &self.renderer,
315 &mut widget::operation::black_box(&mut operation),
316 );
317
318 match operation.finish() {
319 widget::operation::Outcome::Some(widget) => {
320 Some(widget?.visible_bounds()?.center())
321 }
322 _ => None,
323 }
324 }
325 instruction::Target::Text(text) => {
326 use widget::Operation;
327
328 let mut operation = Selector::find(text.as_str());
329
330 user_interface.operate(
331 &self.renderer,
332 &mut widget::operation::black_box(&mut operation),
333 );
334
335 match operation.finish() {
336 widget::operation::Outcome::Some(text) => {
337 Some(text?.visible_bounds()?.center())
338 }
339 _ => None,
340 }
341 }
342 instruction::Target::Point(position) => Some(*position),
343 }) else {
344 self.runtime.send(Event::Failed(instruction.clone()));
345 self.cache = Some(user_interface.into_cache());
346 return;
347 };
348
349 for event in &events {
350 if let core::Event::Mouse(mouse::Event::CursorMoved { position }) = event {
351 self.cursor = mouse::Cursor::Available(*position);
352 }
353 }
354
355 let (_state, _status) = user_interface.update(
356 &window::Headless,
357 &shell::Waker::noop(),
358 &events,
359 self.cursor,
360 &mut self.renderer,
361 &mut messages,
362 );
363
364 self.cache = Some(user_interface.into_cache());
365
366 let task = self.runtime.enter(|| {
367 Task::batch(
368 messages
369 .into_iter()
370 .map(|message| program.update(&mut self.state, message)),
371 )
372 });
373
374 self.resubscribe(program);
375 self.wait_for(task);
376 }
377 Instruction::Expect(expectation) => match expectation {
378 instruction::Expectation::Text(text) => {
379 use widget::Operation;
380
381 let mut operation = Selector::find(text.as_str());
382
383 user_interface.operate(
384 &self.renderer,
385 &mut widget::operation::black_box(&mut operation),
386 );
387
388 match operation.finish() {
389 widget::operation::Outcome::Some(Some(_text)) => {
390 self.runtime.send(Event::Ready);
391 }
392 _ => {
393 self.runtime.send(Event::Failed(instruction.clone()));
394 }
395 }
396
397 self.cache = Some(user_interface.into_cache());
398 }
399 },
400 }
401 }
402
403 fn wait_for(&mut self, task: Task<P::Message>) {
404 if let Some(stream) = task::into_stream(task) {
405 match self.mode {
406 Mode::Zen => {
407 self.pending_tasks += 1;
408
409 self.runtime.run(
410 stream
411 .map(Action_::Runtime)
412 .map(Action)
413 .map(Event::Action)
414 .chain(stream::once(async {
415 Event::Action(Action(Action_::CountDown))
416 }))
417 .boxed(),
418 );
419 }
420 Mode::Patient => {
421 self.runtime.run(
422 stream
423 .map(Action_::Runtime)
424 .map(Action)
425 .map(Event::Action)
426 .chain(stream::once(async { Event::Ready }))
427 .boxed(),
428 );
429 }
430 Mode::Immediate => {
431 self.runtime.run(
432 stream
433 .map(Action_::Runtime)
434 .map(Action)
435 .map(Event::Action)
436 .boxed(),
437 );
438 self.runtime.send(Event::Ready);
439 }
440 }
441 } else if self.pending_tasks == 0 {
442 self.runtime.send(Event::Ready);
443 }
444 }
445
446 fn resubscribe(&mut self, program: &P) {
447 self.runtime
448 .track(subscription::into_recipes(self.runtime.enter(|| {
449 program.subscription(&self.state).map(|message| {
450 Event::Action(Action(Action_::Runtime(runtime::Action::Output(message))))
451 })
452 })));
453 }
454
455 pub fn view(&self, program: &P) -> Element<'_, P::Message, P::Theme, P::Renderer> {
457 program.view(&self.state, self.window)
458 }
459
460 pub fn theme(&self, program: &P) -> Option<P::Theme> {
462 program.theme(&self.state, self.window)
463 }
464
465 pub fn screenshot(
467 &mut self,
468 program: &P,
469 theme: &P::Theme,
470 scale_factor: f32,
471 ) -> window::Screenshot {
472 use core::renderer::Headless;
473
474 let style = program.style(&self.state, theme);
475
476 let mut user_interface = UserInterface::build(
477 program.view(&self.state, self.window),
478 self.size,
479 self.cache.take().unwrap(),
480 &mut self.renderer,
481 );
482
483 let _ = user_interface.update(
485 &window::Headless,
486 &shell::Waker::noop(),
487 &[core::Event::Window(window::Event::RedrawRequested(
488 Instant::now(),
489 ))],
490 mouse::Cursor::Unavailable,
491 &mut self.renderer,
492 &mut shell::Bus::new(),
493 );
494
495 user_interface.draw(
496 &mut self.renderer,
497 theme,
498 &renderer::Style {
499 text_color: style.text_color,
500 },
501 mouse::Cursor::Unavailable,
502 );
503
504 let physical_size = Size::new(
505 (self.size.width * scale_factor).round() as u32,
506 (self.size.height * scale_factor).round() as u32,
507 );
508
509 let rgba = self
510 .renderer
511 .screenshot(physical_size, scale_factor, style.background_color);
512
513 window::Screenshot {
514 rgba: Bytes::from(rgba),
515 size: physical_size,
516 scale_factor,
517 }
518 }
519
520 pub fn state(&self) -> &P::State {
522 &self.state
523 }
524
525 pub fn into_state(self) -> (P::State, core::window::Id) {
527 (self.state, self.window)
528 }
529}
530
531#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
536pub enum Mode {
537 #[default]
542 Zen,
543 Patient,
545 Immediate,
547}
548
549impl Mode {
550 pub const ALL: &[Self] = &[Self::Zen, Self::Patient, Self::Immediate];
552}
553
554impl fmt::Display for Mode {
555 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
556 f.write_str(match self {
557 Self::Zen => "Zen",
558 Self::Patient => "Patient",
559 Self::Immediate => "Immediate",
560 })
561 }
562}
563
564fn load_font(font: Cow<'static, [u8]>) -> Result<(), font::Error> {
565 crate::renderer::graphics::text::font_system()
566 .write()
567 .expect("Write to font system")
568 .load_font(font);
569
570 Ok(())
571}