Make tester work with daemon (only 1 window for now!)

This commit is contained in:
Héctor Ramón Jiménez 2025-07-08 01:19:37 +02:00
parent 1deb87694d
commit f3a4e44314
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
3 changed files with 113 additions and 84 deletions

View file

@ -361,7 +361,7 @@ where
match &self.mode {
Mode::Open { tester } => {
tester.view(program, window, view, Event::Tester)
tester.view(program, view, Event::Tester)
}
_ => view(),
}
@ -444,19 +444,9 @@ where
}
pub fn subscription(&self, program: &P) -> Subscription<Event<P>> {
let subscription = match &self.mode {
Mode::Open { tester } if !tester.is_idle() => {
tester.subscription(program).map(Event::Tester)
}
_ => {
let subscription =
program.subscription(&self.state).map(Event::Program);
debug::subscriptions_tracked(subscription.units());
subscription
}
};
let subscription =
program.subscription(&self.state).map(Event::Program);
debug::subscriptions_tracked(subscription.units());
let hotkeys =
futures::keyboard::on_key_press(|key, _modifiers| match key {

View file

@ -10,7 +10,6 @@ use crate::core::border;
use crate::core::window;
use crate::core::{Element, Event, Font, Size, Theme};
use crate::executor;
use crate::futures::Subscription;
use crate::futures::futures::channel::mpsc;
use crate::icon;
use crate::program;
@ -36,10 +35,11 @@ pub struct Tester<P: Program> {
enum State<P: Program> {
Idle,
Recording {
state: P::State,
emulator: Emulator<P>,
},
Ready {
state: P::State,
window: window::Id,
},
Playing {
emulator: Emulator<P>,
@ -98,10 +98,6 @@ impl<P: Program + 'static> Tester<P> {
}
}
pub fn is_idle(&self) -> bool {
matches!(self.state, State::Idle)
}
pub fn is_busy(&self) -> bool {
matches!(
self.state,
@ -134,24 +130,30 @@ impl<P: Program + 'static> Tester<P> {
self.edit = None;
self.instructions.clear();
let (state, task) = if let Some(preset) = self.preset(program) {
preset.boot()
} else {
program.boot()
};
let (sender, receiver) = mpsc::channel(1);
self.state = State::Recording { state };
let emulator = Emulator::with_preset(
sender,
program,
self.mode,
self.viewport,
self.preset(program),
);
task.map(Tick::Program)
self.state = State::Recording { emulator };
Task::run(receiver, Tick::Emulator)
}
Message::Stop => {
let State::Recording { state } =
let State::Recording { emulator } =
std::mem::replace(&mut self.state, State::Idle)
else {
return Task::none();
};
self.state = State::Ready { state };
let (state, window) = emulator.into_state();
self.state = State::Ready { state, window };
Task::none()
}
@ -301,11 +303,13 @@ impl<P: Program + 'static> Tester<P> {
match tick {
Tick::Tester(message) => self.update(program, message),
Tick::Program(message) => {
let State::Recording { state } = &mut self.state else {
let State::Recording { emulator } = &mut self.state else {
return Task::none();
};
program.update(state, message).map(Tick::Program)
emulator.update(program, message);
Task::none()
}
Tick::Recorder(event) => {
let mut interaction =
@ -337,35 +341,38 @@ impl<P: Program + 'static> Tester<P> {
Task::none()
}
Tick::Emulator(event) => {
let State::Playing {
emulator,
current,
outcome,
} = &mut self.state
else {
return Task::none();
};
match event {
emulator::Event::Action(action) => {
emulator.perform(program, action);
}
emulator::Event::Failed => {
*outcome = Outcome::Failed;
}
emulator::Event::Ready => {
*current += 1;
if let Some(instruction) =
self.instructions.get(*current - 1).cloned()
{
emulator.run(program, instruction);
}
if *current >= self.instructions.len() {
*outcome = Outcome::Success;
match &mut self.state {
State::Recording { emulator } => {
if let emulator::Event::Action(action) = event {
emulator.perform(program, action);
}
}
State::Playing {
emulator,
current,
outcome,
} => match event {
emulator::Event::Action(action) => {
emulator.perform(program, action);
}
emulator::Event::Failed => {
*outcome = Outcome::Failed;
}
emulator::Event::Ready => {
*current += 1;
if let Some(instruction) =
self.instructions.get(*current - 1).cloned()
{
emulator.run(program, instruction);
}
if *current >= self.instructions.len() {
*outcome = Outcome::Success;
}
}
},
State::Idle | State::Ready { .. } => {}
}
Task::none()
@ -373,21 +380,9 @@ impl<P: Program + 'static> Tester<P> {
}
}
pub fn subscription(&self, program: &P) -> Subscription<Tick<P>> {
match &self.state {
State::Idle | State::Playing { .. } | State::Ready { .. } => {
Subscription::none()
}
State::Recording { state } => {
program.subscription(state).map(Tick::Program)
}
}
}
pub fn view<'a, T: 'static>(
&'a self,
program: &P,
window: window::Id,
current: impl FnOnce() -> Element<'a, T, Theme, P::Renderer>,
emulate: impl Fn(Tick<P>) -> T + 'a,
) -> Element<'a, T, Theme, P::Renderer> {
@ -435,10 +430,9 @@ impl<P: Program + 'static> Tester<P> {
scrollable(
container(match &self.state {
State::Idle => current(),
State::Recording { state } => {
let theme = program.theme(state, window);
let view =
program.view(state, window).map(Tick::Program);
State::Recording { emulator } => {
let theme = emulator.theme(program);
let view = emulator.view(program).map(Tick::Program);
Element::from(
recorder(themer(theme, view))
@ -446,10 +440,10 @@ impl<P: Program + 'static> Tester<P> {
)
.map(emulate)
}
State::Ready { state } => {
let theme = program.theme(state, window);
State::Ready { state, window } => {
let theme = program.theme(state, *window);
let view =
program.view(state, window).map(Tick::Program);
program.view(state, *window).map(Tick::Program);
Element::from(themer(theme, view)).map(emulate)
}

View file

@ -3,8 +3,7 @@ use crate::core;
use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget;
use crate::core::window;
use crate::core::{Element, Size};
use crate::core::{Element, Point, Size};
use crate::instruction;
use crate::program;
use crate::program::Program;
@ -15,6 +14,7 @@ use crate::runtime::futures::subscription;
use crate::runtime::futures::{Executor, Runtime};
use crate::runtime::task;
use crate::runtime::user_interface;
use crate::runtime::window;
use crate::runtime::{Action, Task, UserInterface};
use std::fmt;
@ -26,7 +26,7 @@ pub struct Emulator<P: Program> {
renderer: P::Renderer,
mode: Mode,
size: Size,
window: window::Id,
window: core::window::Id,
cursor: mouse::Cursor,
clipboard: Clipboard,
cache: Option<user_interface::Cache>,
@ -87,7 +87,7 @@ impl<P: Program + 'static> Emulator<P> {
size,
clipboard: Clipboard { content: None },
cursor: mouse::Cursor::Unavailable,
window: window::Id::unique(),
window: core::window::Id::unique(),
cache: Some(user_interface::Cache::default()),
};
@ -143,9 +143,50 @@ impl<P: Program + 'static> Emulator<P> {
// TODO
dbg!(action);
}
Action::Window(_action) => {
// TODO
}
Action::Window(action) => match action {
window::Action::Open(_settings, sender) => {
self.window = core::window::Id::unique();
let _ = sender.send(self.window);
}
window::Action::GetOldest(sender)
| window::Action::GetLatest(sender) => {
let _ = sender.send(Some(self.window));
}
window::Action::GetSize(id, sender) => {
if id == self.window {
let _ = sender.send(self.size);
}
}
window::Action::GetMaximized(id, sender) => {
if id == self.window {
let _ = sender.send(false);
}
}
window::Action::GetMinimized(id, sender) => {
if id == self.window {
let _ = sender.send(None);
}
}
window::Action::GetPosition(id, sender) => {
if id == self.window {
let _ = sender.send(Some(Point::ORIGIN));
}
}
window::Action::GetScaleFactor(id, sender) => {
if id == self.window {
let _ = sender.send(1.0);
}
}
window::Action::GetMode(id, sender) => {
if id == self.window {
let _ = sender.send(core::window::Mode::Windowed);
}
}
_ => {
// Ignored
}
},
Action::System(action) => {
// TODO
dbg!(action);
@ -265,6 +306,10 @@ impl<P: Program + 'static> Emulator<P> {
pub fn theme(&self, program: &P) -> P::Theme {
program.theme(&self.state, self.window)
}
pub fn into_state(self) -> (P::State, core::window::Id) {
(self.state, self.window)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]