Implement import/export for tester devtool

This commit is contained in:
Héctor Ramón Jiménez 2025-06-05 00:23:36 +02:00
parent e548372fe0
commit 1fd6980f91
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
6 changed files with 287 additions and 45 deletions

View file

@ -15,7 +15,7 @@ workspace = true
[features]
time-travel = ["iced_program/time-travel"]
tester = ["dep:iced_test"]
tester = ["dep:iced_test", "dep:rfd"]
[dependencies]
iced_debug.workspace = true
@ -25,3 +25,6 @@ log.workspace = true
iced_test.workspace = true
iced_test.optional = true
rfd.workspace = true
rfd.optional = true

View file

@ -9,6 +9,7 @@ use crate::core::alignment::Horizontal::Right;
use crate::core::border;
use crate::core::window;
use crate::core::{Element, Event, Size, Theme};
use crate::executor;
use crate::futures::Subscription;
use crate::futures::futures::channel::mpsc;
use crate::icon;
@ -60,10 +61,14 @@ pub enum Message {
Record,
Stop,
Play,
Import,
Export,
Imported(Option<String>),
}
#[allow(missing_debug_implementations)]
pub enum Tick<P: Program> {
Tester(Message),
Program(P::Message),
Recorder(Event),
Emulator(emulator::Event<P>),
@ -163,6 +168,67 @@ impl<P: Program + 'static> Tester<P> {
Task::run(receiver, Tick::Emulator)
}
Message::Import => {
use std::fs;
let import = rfd::AsyncFileDialog::new()
.add_filter("ice", &["ice"])
.pick_file();
Task::future(import)
.and_then(|file| {
executor::spawn_blocking(move |mut sender| {
let _ = sender
.try_send(fs::read_to_string(file.path()).ok());
})
})
.map(Message::Imported)
.map(Tick::Tester)
}
Message::Export => {
use std::fs;
use std::thread;
let test: Vec<_> = self
.instructions
.iter()
.map(Instruction::to_string)
.collect();
let export = rfd::AsyncFileDialog::new()
.add_filter("ice", &["ice"])
.save_file();
Task::future(async move {
let Some(file) = export.await else {
return;
};
let _ = thread::spawn(move || {
fs::write(file.path(), test.join("\n"))
});
})
.discard()
}
Message::Imported(instructions) => {
let Some(instructions) = instructions else {
return Task::none();
};
let instructions: Result<Vec<_>, _> =
instructions.lines().map(Instruction::parse).collect();
match instructions {
Ok(instructions) => {
self.instructions = instructions;
}
Err(error) => {
log::error!("{error}");
}
}
Task::none()
}
}
}
@ -180,6 +246,7 @@ impl<P: Program + 'static> Tester<P> {
pub fn tick(&mut self, program: &P, tick: Tick<P>) -> Task<Tick<P>> {
match tick {
Tick::Tester(message) => self.update(program, message),
Tick::Program(message) => {
let State::Recording { state } = &mut self.state else {
return Task::none();
@ -473,29 +540,43 @@ impl<P: Program + 'static> Tester<P> {
.height(Fill)
.padding(5);
let controls = {
row![
button(icon::play().size(14).width(Fill).center())
.on_press_maybe(
(!matches!(self.state, State::Recording { .. })
&& !self.instructions.is_empty())
.then_some(Message::Play),
),
if let State::Recording { .. } = &self.state {
button(icon::stop().size(14).width(Fill).center())
.on_press(Message::Stop)
.style(button::success)
} else {
button(icon::record().size(14).width(Fill).center())
.on_press_maybe(
(!self.is_busy()).then_some(Message::Record),
)
.style(button::danger)
}
]
.spacing(10)
let control = |icon: text::Text<'static, _, _>| {
button(icon.size(14).width(Fill).height(Fill).center())
};
let play = control(icon::play()).on_press_maybe(
(!matches!(self.state, State::Recording { .. })
&& !self.instructions.is_empty())
.then_some(Message::Play),
);
let record = if let State::Recording { .. } = &self.state {
control(icon::stop())
.on_press(Message::Stop)
.style(button::success)
} else {
control(icon::record())
.on_press_maybe(
(!self.is_busy()).then_some(Message::Record),
)
.style(button::danger)
};
let import = control(icon::folder())
.on_press_maybe((!self.is_busy()).then_some(Message::Import))
.style(button::secondary);
let export = control(icon::floppy())
.on_press_maybe(
(!matches!(self.state, State::Recording { .. })
&& !self.instructions.is_empty())
.then_some(Message::Export),
)
.style(button::success);
let controls =
row![import, export, play, record].height(30).spacing(10);
column![instructions, controls].spacing(10).align_x(Center)
};