Move tester to a new iced_tester subcrate
This commit is contained in:
parent
9e81c2b9e8
commit
4f7444bddf
28 changed files with 392 additions and 355 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
|
@ -2492,6 +2492,7 @@ dependencies = [
|
||||||
"iced_highlighter",
|
"iced_highlighter",
|
||||||
"iced_renderer",
|
"iced_renderer",
|
||||||
"iced_runtime",
|
"iced_runtime",
|
||||||
|
"iced_tester",
|
||||||
"iced_wgpu",
|
"iced_wgpu",
|
||||||
"iced_widget",
|
"iced_widget",
|
||||||
"iced_winit",
|
"iced_winit",
|
||||||
|
|
@ -2548,10 +2549,8 @@ version = "0.14.0-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"iced_debug",
|
"iced_debug",
|
||||||
"iced_program",
|
"iced_program",
|
||||||
"iced_test",
|
|
||||||
"iced_widget",
|
"iced_widget",
|
||||||
"log",
|
"log",
|
||||||
"rfd 0.15.4",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2650,6 +2649,16 @@ dependencies = [
|
||||||
"thiserror 2.0.16",
|
"thiserror 2.0.16",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iced_tester"
|
||||||
|
version = "0.14.0-dev"
|
||||||
|
dependencies = [
|
||||||
|
"iced_test",
|
||||||
|
"iced_widget",
|
||||||
|
"log",
|
||||||
|
"rfd 0.15.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_tiny_skia"
|
name = "iced_tiny_skia"
|
||||||
version = "0.14.0-dev"
|
version = "0.14.0-dev"
|
||||||
|
|
|
||||||
|
|
@ -42,13 +42,13 @@ markdown = ["iced_widget/markdown"]
|
||||||
# Enables lazy widgets
|
# Enables lazy widgets
|
||||||
lazy = ["iced_widget/lazy"]
|
lazy = ["iced_widget/lazy"]
|
||||||
# Enables debug metrics in native platforms (press F12)
|
# Enables debug metrics in native platforms (press F12)
|
||||||
debug = ["iced_winit/debug", "iced_devtools"]
|
debug = ["iced_winit/debug", "dep:iced_devtools"]
|
||||||
# Enables time-travel debugging (very experimental!)
|
# Enables time-travel debugging (very experimental!)
|
||||||
time-travel = ["debug", "iced_devtools/time-travel"]
|
time-travel = ["debug", "iced_devtools/time-travel"]
|
||||||
# Enables hot reloading (very experimental!)
|
# Enables hot reloading (very experimental!)
|
||||||
hot = ["debug", "iced_debug/hot"]
|
hot = ["debug", "iced_debug/hot"]
|
||||||
# Enables the tester developer tool for recording and playing tests (press F12)
|
# Enables the tester developer tool for recording and playing tests (press F12)
|
||||||
tester = ["debug", "iced_devtools/tester"]
|
tester = ["dep:iced_tester"]
|
||||||
# Enables the `thread-pool` futures executor as the `executor::Default` on native platforms
|
# Enables the `thread-pool` futures executor as the `executor::Default` on native platforms
|
||||||
thread-pool = ["iced_futures/thread-pool"]
|
thread-pool = ["iced_futures/thread-pool"]
|
||||||
# Enables `tokio` as the `executor::Default` on native platforms
|
# Enables `tokio` as the `executor::Default` on native platforms
|
||||||
|
|
@ -92,6 +92,9 @@ iced_winit.workspace = true
|
||||||
iced_devtools.workspace = true
|
iced_devtools.workspace = true
|
||||||
iced_devtools.optional = true
|
iced_devtools.optional = true
|
||||||
|
|
||||||
|
iced_tester.workspace = true
|
||||||
|
iced_tester.optional = true
|
||||||
|
|
||||||
iced_highlighter.workspace = true
|
iced_highlighter.workspace = true
|
||||||
iced_highlighter.optional = true
|
iced_highlighter.optional = true
|
||||||
|
|
||||||
|
|
@ -133,6 +136,7 @@ members = [
|
||||||
"runtime",
|
"runtime",
|
||||||
"selector",
|
"selector",
|
||||||
"test",
|
"test",
|
||||||
|
"tester",
|
||||||
"tiny_skia",
|
"tiny_skia",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"widget",
|
"widget",
|
||||||
|
|
@ -165,6 +169,7 @@ iced_renderer = { version = "0.14.0-dev", path = "renderer" }
|
||||||
iced_runtime = { version = "0.14.0-dev", path = "runtime" }
|
iced_runtime = { version = "0.14.0-dev", path = "runtime" }
|
||||||
iced_selector = { version = "0.14.0-dev", path = "selector" }
|
iced_selector = { version = "0.14.0-dev", path = "selector" }
|
||||||
iced_test = { version = "0.14.0-dev", path = "test" }
|
iced_test = { version = "0.14.0-dev", path = "test" }
|
||||||
|
iced_tester = { version = "0.14.0-dev", path = "tester" }
|
||||||
iced_tiny_skia = { version = "0.14.0-dev", path = "tiny_skia" }
|
iced_tiny_skia = { version = "0.14.0-dev", path = "tiny_skia" }
|
||||||
iced_wgpu = { version = "0.14.0-dev", path = "wgpu" }
|
iced_wgpu = { version = "0.14.0-dev", path = "wgpu" }
|
||||||
iced_widget = { version = "0.14.0-dev", path = "widget" }
|
iced_widget = { version = "0.14.0-dev", path = "widget" }
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,11 @@ workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
time-travel = ["iced_program/time-travel"]
|
time-travel = ["iced_program/time-travel"]
|
||||||
tester = ["dep:iced_test", "dep:rfd"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced_debug.workspace = true
|
iced_debug.workspace = true
|
||||||
iced_program.workspace = true
|
|
||||||
iced_widget.workspace = true
|
iced_widget.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|
||||||
iced_test.workspace = true
|
iced_program.workspace = true
|
||||||
iced_test.optional = true
|
iced_program.features = ["debug"]
|
||||||
|
|
||||||
rfd.workspace = true
|
|
||||||
rfd.optional = true
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::executor;
|
use crate::runtime::task::{self, Task};
|
||||||
use crate::runtime::Task;
|
|
||||||
|
|
||||||
use std::process;
|
use std::process;
|
||||||
|
|
||||||
|
|
@ -7,7 +6,7 @@ pub const COMPATIBLE_REVISION: &str =
|
||||||
"20f9c9a897fecac5dce0977bbb5639fdce1f54b9";
|
"20f9c9a897fecac5dce0977bbb5639fdce1f54b9";
|
||||||
|
|
||||||
pub fn launch() -> Task<launch::Result> {
|
pub fn launch() -> Task<launch::Result> {
|
||||||
executor::try_spawn_blocking(|mut sender| {
|
task::try_blocking(|mut sender| {
|
||||||
let cargo_install = process::Command::new("cargo")
|
let cargo_install = process::Command::new("cargo")
|
||||||
.args(["install", "--list"])
|
.args(["install", "--list"])
|
||||||
.output()?;
|
.output()?;
|
||||||
|
|
@ -48,7 +47,7 @@ pub fn launch() -> Task<launch::Result> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn install() -> Task<install::Result> {
|
pub fn install() -> Task<install::Result> {
|
||||||
executor::try_spawn_blocking(|mut sender| {
|
task::try_blocking(|mut sender| {
|
||||||
use std::io::{BufRead, BufReader};
|
use std::io::{BufRead, BufReader};
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
use crate::futures::futures::channel::mpsc;
|
|
||||||
use crate::futures::futures::channel::oneshot;
|
|
||||||
use crate::futures::futures::stream::{self, StreamExt};
|
|
||||||
use crate::runtime::Task;
|
|
||||||
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
pub fn spawn_blocking<T>(
|
|
||||||
f: impl FnOnce(mpsc::Sender<T>) + Send + 'static,
|
|
||||||
) -> Task<T>
|
|
||||||
where
|
|
||||||
T: Send + 'static,
|
|
||||||
{
|
|
||||||
let (sender, receiver) = mpsc::channel(1);
|
|
||||||
|
|
||||||
let _ = thread::spawn(move || {
|
|
||||||
f(sender);
|
|
||||||
});
|
|
||||||
|
|
||||||
Task::stream(receiver)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_spawn_blocking<T, E>(
|
|
||||||
f: impl FnOnce(mpsc::Sender<T>) -> Result<(), E> + Send + 'static,
|
|
||||||
) -> Task<Result<T, E>>
|
|
||||||
where
|
|
||||||
T: Send + 'static,
|
|
||||||
E: Send + 'static,
|
|
||||||
{
|
|
||||||
let (sender, receiver) = mpsc::channel(1);
|
|
||||||
let (error_sender, error_receiver) = oneshot::channel();
|
|
||||||
|
|
||||||
let _ = thread::spawn(move || {
|
|
||||||
if let Err(error) = f(sender) {
|
|
||||||
let _ = error_sender.send(Err(error));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Task::stream(stream::select(
|
|
||||||
receiver.map(Ok),
|
|
||||||
stream::once(error_receiver).filter_map(async |result| result.ok()),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
@ -3,39 +3,28 @@ use iced_debug as debug;
|
||||||
use iced_program as program;
|
use iced_program as program;
|
||||||
use iced_program::runtime;
|
use iced_program::runtime;
|
||||||
use iced_program::runtime::futures;
|
use iced_program::runtime::futures;
|
||||||
#[cfg(feature = "tester")]
|
use iced_widget as widget;
|
||||||
use iced_test as test;
|
|
||||||
use iced_widget::core;
|
use iced_widget::core;
|
||||||
|
|
||||||
mod comet;
|
mod comet;
|
||||||
mod executor;
|
|
||||||
mod icon;
|
|
||||||
mod time_machine;
|
mod time_machine;
|
||||||
mod widget;
|
|
||||||
|
|
||||||
#[cfg(feature = "tester")]
|
|
||||||
mod tester;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "tester"))]
|
|
||||||
#[path = "tester/null.rs"]
|
|
||||||
mod tester;
|
|
||||||
|
|
||||||
use crate::tester::Tester;
|
|
||||||
|
|
||||||
use crate::core::border;
|
use crate::core::border;
|
||||||
use crate::core::keyboard;
|
use crate::core::keyboard;
|
||||||
use crate::core::theme::{self, Base, Theme};
|
use crate::core::theme::{self, Base, Theme};
|
||||||
use crate::core::time::seconds;
|
use crate::core::time::seconds;
|
||||||
use crate::core::window;
|
use crate::core::window;
|
||||||
use crate::core::{Alignment::Center, Color, Element, Length::Fill};
|
use crate::core::{
|
||||||
|
Alignment::Center, Color, Element, Font, Length::Fill, Settings,
|
||||||
|
};
|
||||||
use crate::futures::Subscription;
|
use crate::futures::Subscription;
|
||||||
use crate::program::Program;
|
use crate::program::Program;
|
||||||
use crate::runtime::Task;
|
use crate::program::message;
|
||||||
use crate::runtime::font;
|
use crate::runtime::task::{self, Task};
|
||||||
use crate::time_machine::TimeMachine;
|
use crate::time_machine::TimeMachine;
|
||||||
use crate::widget::{
|
use crate::widget::{
|
||||||
bottom_right, button, center, column, container, horizontal_space,
|
bottom_right, button, center, column, container, horizontal_space, opaque,
|
||||||
monospace, opaque, row, scrollable, stack, text, themer,
|
row, scrollable, stack, text, themer,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
@ -55,6 +44,7 @@ pub struct Attach<P> {
|
||||||
impl<P> Program for Attach<P>
|
impl<P> Program for Attach<P>
|
||||||
where
|
where
|
||||||
P: Program + 'static,
|
P: Program + 'static,
|
||||||
|
P::Message: std::fmt::Debug + message::MaybeClone,
|
||||||
{
|
{
|
||||||
type State = DevTools<P>;
|
type State = DevTools<P>;
|
||||||
type Message = Event<P>;
|
type Message = Event<P>;
|
||||||
|
|
@ -66,21 +56,21 @@ where
|
||||||
P::name()
|
P::name()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn settings(&self) -> core::Settings {
|
fn settings(&self) -> Settings {
|
||||||
self.program.settings()
|
self.program.settings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn window(&self) -> Option<window::Settings> {
|
||||||
|
self.program.window()
|
||||||
|
}
|
||||||
|
|
||||||
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||||
let (state, boot) = self.program.boot();
|
let (state, boot) = self.program.boot();
|
||||||
let (devtools, task) = DevTools::new(state);
|
let (devtools, task) = DevTools::new(state);
|
||||||
|
|
||||||
(
|
(
|
||||||
devtools,
|
devtools,
|
||||||
Task::batch([
|
Task::batch([boot.map(Event::Program), task.map(Event::Message)]),
|
||||||
boot.map(Event::Program),
|
|
||||||
task.map(Event::Message),
|
|
||||||
font::load(icon::FONT).discard(),
|
|
||||||
]),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,7 +120,7 @@ where
|
||||||
state: P::State,
|
state: P::State,
|
||||||
show_notification: bool,
|
show_notification: bool,
|
||||||
time_machine: TimeMachine<P>,
|
time_machine: TimeMachine<P>,
|
||||||
mode: Mode<P>,
|
mode: Mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -141,13 +131,10 @@ pub enum Message {
|
||||||
InstallComet,
|
InstallComet,
|
||||||
Installing(comet::install::Result),
|
Installing(comet::install::Result),
|
||||||
CancelSetup,
|
CancelSetup,
|
||||||
Toggle,
|
|
||||||
Tester(tester::Message),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Mode<P: Program> {
|
enum Mode {
|
||||||
Hidden,
|
Hidden,
|
||||||
Open { tester: Tester<P> },
|
|
||||||
Setup(Setup),
|
Setup(Setup),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,6 +151,7 @@ enum Goal {
|
||||||
impl<P> DevTools<P>
|
impl<P> DevTools<P>
|
||||||
where
|
where
|
||||||
P: Program + 'static,
|
P: Program + 'static,
|
||||||
|
P::Message: std::fmt::Debug + message::MaybeClone,
|
||||||
{
|
{
|
||||||
pub fn new(state: P::State) -> (Self, Task<Message>) {
|
pub fn new(state: P::State) -> (Self, Task<Message>) {
|
||||||
(
|
(
|
||||||
|
|
@ -173,7 +161,7 @@ where
|
||||||
show_notification: true,
|
show_notification: true,
|
||||||
time_machine: TimeMachine::new(),
|
time_machine: TimeMachine::new(),
|
||||||
},
|
},
|
||||||
executor::spawn_blocking(|mut sender| {
|
task::blocking(|mut sender| {
|
||||||
thread::sleep(seconds(2));
|
thread::sleep(seconds(2));
|
||||||
let _ = sender.try_send(());
|
let _ = sender.try_send(());
|
||||||
})
|
})
|
||||||
|
|
@ -193,21 +181,6 @@ where
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::Toggle => {
|
|
||||||
match &self.mode {
|
|
||||||
Mode::Hidden => {
|
|
||||||
self.mode = Mode::Open {
|
|
||||||
tester: Tester::new(program),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Mode::Open { tester } if !tester.is_busy() => {
|
|
||||||
self.mode = Mode::Hidden;
|
|
||||||
}
|
|
||||||
Mode::Setup(_) | Mode::Open { .. } => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
Message::ToggleComet => {
|
Message::ToggleComet => {
|
||||||
if let Mode::Setup(setup) = &self.mode {
|
if let Mode::Setup(setup) = &self.mode {
|
||||||
if matches!(setup, Setup::Idle { .. }) {
|
if matches!(setup, Setup::Idle { .. }) {
|
||||||
|
|
@ -290,13 +263,6 @@ where
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::Tester(message) => {
|
|
||||||
let Mode::Open { tester } = &mut self.mode else {
|
|
||||||
return Task::none();
|
|
||||||
};
|
|
||||||
|
|
||||||
tester.update(program, message).map(Event::Tester)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Event::Program(message) => {
|
Event::Program(message) => {
|
||||||
self.time_machine.push(&message);
|
self.time_machine.push(&message);
|
||||||
|
|
@ -328,13 +294,6 @@ where
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Event::Tester(tick) => {
|
|
||||||
let Mode::Open { tester } = &mut self.mode else {
|
|
||||||
return Task::none();
|
|
||||||
};
|
|
||||||
|
|
||||||
tester.tick(program, tick).map(Event::Tester)
|
|
||||||
}
|
|
||||||
Event::Discard => Task::none(),
|
Event::Discard => Task::none(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -347,23 +306,15 @@ where
|
||||||
let state = self.state();
|
let state = self.state();
|
||||||
|
|
||||||
let view = {
|
let view = {
|
||||||
let view = || {
|
let theme = program.theme(state, window);
|
||||||
let theme = program.theme(state, window);
|
|
||||||
let view: Element<'_, _, Theme, _> =
|
|
||||||
themer(theme, program.view(&self.state, window)).into();
|
|
||||||
|
|
||||||
if self.time_machine.is_rewinding() {
|
let view: Element<'_, _, Theme, _> =
|
||||||
view.map(|_| Event::Discard)
|
themer(theme, program.view(state, window)).into();
|
||||||
} else {
|
|
||||||
view.map(Event::Program)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match &self.mode {
|
if self.time_machine.is_rewinding() {
|
||||||
Mode::Open { tester } => {
|
view.map(|_| Event::Discard)
|
||||||
tester.view(program, view, Event::Tester)
|
} else {
|
||||||
}
|
view.map(Event::Program)
|
||||||
_ => view(),
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -408,28 +359,9 @@ where
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let sidebar = if let Mode::Open { tester } = &self.mode {
|
|
||||||
let title = monospace("Developer Tools");
|
|
||||||
let tester = tester.controls().map(Message::Tester);
|
|
||||||
|
|
||||||
let tools = column![title, tester].spacing(10);
|
|
||||||
|
|
||||||
let sidebar = container(tools)
|
|
||||||
.padding(10)
|
|
||||||
.width(250)
|
|
||||||
.height(Fill)
|
|
||||||
.style(container::dark);
|
|
||||||
|
|
||||||
Some(Element::from(sidebar).map(Event::Message))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let content = row![view, sidebar];
|
|
||||||
|
|
||||||
themer(
|
themer(
|
||||||
theme,
|
theme,
|
||||||
stack![content]
|
stack![view]
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.push_maybe(setup.map(opaque))
|
.push_maybe(setup.map(opaque))
|
||||||
.push_maybe(notification.map(|notification| {
|
.push_maybe(notification.map(|notification| {
|
||||||
|
|
@ -451,14 +383,6 @@ where
|
||||||
let hotkeys =
|
let hotkeys =
|
||||||
futures::keyboard::on_key_press(|key, _modifiers| match key {
|
futures::keyboard::on_key_press(|key, _modifiers| match key {
|
||||||
keyboard::Key::Named(keyboard::key::Named::F12) => {
|
keyboard::Key::Named(keyboard::key::Named::F12) => {
|
||||||
Some(if cfg!(feature = "tester") {
|
|
||||||
Message::Toggle
|
|
||||||
} else {
|
|
||||||
Message::ToggleComet
|
|
||||||
})
|
|
||||||
}
|
|
||||||
#[cfg(feature = "tester")]
|
|
||||||
keyboard::Key::Named(keyboard::key::Named::F11) => {
|
|
||||||
Some(Message::ToggleComet)
|
Some(Message::ToggleComet)
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
@ -479,11 +403,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scale_factor(&self, program: &P, window: window::Id) -> f64 {
|
pub fn scale_factor(&self, program: &P, window: window::Id) -> f64 {
|
||||||
if let Mode::Open { .. } = &self.mode {
|
program.scale_factor(self.state(), window)
|
||||||
1.0
|
|
||||||
} else {
|
|
||||||
program.scale_factor(self.state(), window)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn state(&self) -> &P::State {
|
pub fn state(&self) -> &P::State {
|
||||||
|
|
@ -497,7 +417,6 @@ where
|
||||||
{
|
{
|
||||||
Message(Message),
|
Message(Message),
|
||||||
Program(P::Message),
|
Program(P::Message),
|
||||||
Tester(tester::Tick<P>),
|
|
||||||
Command(debug::Command),
|
Command(debug::Command),
|
||||||
Discard,
|
Discard,
|
||||||
}
|
}
|
||||||
|
|
@ -505,34 +424,18 @@ where
|
||||||
impl<P> fmt::Debug for Event<P>
|
impl<P> fmt::Debug for Event<P>
|
||||||
where
|
where
|
||||||
P: Program,
|
P: Program,
|
||||||
|
P::Message: std::fmt::Debug,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Message(message) => message.fmt(f),
|
Self::Message(message) => message.fmt(f),
|
||||||
Self::Program(message) => message.fmt(f),
|
Self::Program(message) => message.fmt(f),
|
||||||
Self::Tester(_) => f.write_str("Tester"),
|
|
||||||
Self::Command(command) => command.fmt(f),
|
Self::Command(command) => command.fmt(f),
|
||||||
Self::Discard => f.write_str("Discard"),
|
Self::Discard => f.write_str("Discard"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "time-travel")]
|
|
||||||
impl<P> Clone for Event<P>
|
|
||||||
where
|
|
||||||
P: Program,
|
|
||||||
{
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Message(message) => Self::Message(message.clone()),
|
|
||||||
Self::Program(message) => Self::Program(message.clone()),
|
|
||||||
Self::Command(command) => Self::Command(*command),
|
|
||||||
Self::Tester(_) => Self::Discard, // Time traveling an emulator?!
|
|
||||||
Self::Discard => Self::Discard,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup<Renderer>(goal: &Goal) -> Element<'_, Message, Theme, Renderer>
|
fn setup<Renderer>(goal: &Goal) -> Element<'_, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: program::Renderer + 'static,
|
Renderer: program::Renderer + 'static,
|
||||||
|
|
@ -557,13 +460,14 @@ where
|
||||||
];
|
];
|
||||||
|
|
||||||
let command = container(
|
let command = container(
|
||||||
monospace(format!(
|
text!(
|
||||||
"cargo install --locked \\
|
"cargo install --locked \\
|
||||||
--git https://github.com/iced-rs/comet.git \\
|
--git https://github.com/iced-rs/comet.git \\
|
||||||
--rev {}",
|
--rev {}",
|
||||||
comet::COMPATIBLE_REVISION
|
comet::COMPATIBLE_REVISION
|
||||||
))
|
)
|
||||||
.size(14),
|
.size(14)
|
||||||
|
.font(Font::MONOSPACE),
|
||||||
)
|
)
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding(5)
|
.padding(5)
|
||||||
|
|
@ -630,9 +534,9 @@ where
|
||||||
text("Installing comet...").size(20),
|
text("Installing comet...").size(20),
|
||||||
container(
|
container(
|
||||||
scrollable(
|
scrollable(
|
||||||
column(
|
column(logs.iter().map(|log| {
|
||||||
logs.iter().map(|log| { monospace(log).size(12).into() }),
|
text(log).size(12).font(Font::MONOSPACE).into()
|
||||||
)
|
}))
|
||||||
.spacing(3),
|
.spacing(3),
|
||||||
)
|
)
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
|
|
@ -653,7 +557,7 @@ fn inline_code<'a, Renderer>(
|
||||||
where
|
where
|
||||||
Renderer: program::Renderer + 'a,
|
Renderer: program::Renderer + 'a,
|
||||||
{
|
{
|
||||||
container(monospace(code).size(12))
|
container(text(code).size(12).font(Font::MONOSPACE))
|
||||||
.style(|_theme| {
|
.style(|_theme| {
|
||||||
container::Style::default()
|
container::Style::default()
|
||||||
.background(Color::BLACK)
|
.background(Color::BLACK)
|
||||||
|
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
use crate::Program;
|
|
||||||
use crate::core::{Element, Theme};
|
|
||||||
use crate::runtime::Task;
|
|
||||||
use crate::widget::horizontal_space;
|
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
pub struct Tester<P: Program> {
|
|
||||||
_type: PhantomData<P::Message>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Message {}
|
|
||||||
|
|
||||||
#[allow(missing_debug_implementations)]
|
|
||||||
pub struct Tick<P: Program> {
|
|
||||||
_type: PhantomData<P::Message>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Program> Tester<P> {
|
|
||||||
pub fn new(_program: &P) -> Self {
|
|
||||||
Self { _type: PhantomData }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_busy(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&mut self, _program: &P, _message: Message) -> Task<Tick<P>> {
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tick(&mut self, _program: &P, _tick: Tick<P>) -> Task<Tick<P>> {
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view<'a, T: 'static>(
|
|
||||||
&'a self,
|
|
||||||
_program: &P,
|
|
||||||
_current: impl FnOnce() -> Element<'a, T, Theme, P::Renderer>,
|
|
||||||
_emulate: impl Fn(Tick<P>) -> T + 'a,
|
|
||||||
) -> Element<'a, T, Theme, P::Renderer> {
|
|
||||||
horizontal_space().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn controls(&self) -> Element<'_, Message, Theme, P::Renderer> {
|
|
||||||
horizontal_space().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -13,6 +13,7 @@ where
|
||||||
impl<P> TimeMachine<P>
|
impl<P> TimeMachine<P>
|
||||||
where
|
where
|
||||||
P: Program,
|
P: Program,
|
||||||
|
P::Message: std::fmt::Debug + Clone,
|
||||||
{
|
{
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
pub use iced_widget::*;
|
|
||||||
|
|
||||||
use crate::core::Font;
|
|
||||||
use crate::program;
|
|
||||||
|
|
||||||
pub fn monospace<'a, Renderer>(
|
|
||||||
fragment: impl text::IntoFragment<'a>,
|
|
||||||
) -> Text<'a, Theme, Renderer>
|
|
||||||
where
|
|
||||||
Renderer: program::Renderer + 'a,
|
|
||||||
{
|
|
||||||
text(fragment).font(Font::MONOSPACE)
|
|
||||||
}
|
|
||||||
|
|
@ -19,7 +19,7 @@ pub fn main() -> iced::Result {
|
||||||
application().run()
|
application().run()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn application() -> Application<impl Program> {
|
fn application() -> Application<impl Program<Message = Message>> {
|
||||||
iced::application(Todos::new, Todos::update, Todos::view)
|
iced::application(Todos::new, Todos::update, Todos::view)
|
||||||
.subscription(Todos::subscription)
|
.subscription(Todos::subscription)
|
||||||
.title(Todos::title)
|
.title(Todos::title)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
viewport: 512x768
|
viewport: 500x800
|
||||||
mode: Impatient
|
mode: Impatient
|
||||||
preset: Empty
|
preset: Empty
|
||||||
-----
|
-----
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ rust-version.workspace = true
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
debug = []
|
||||||
time-travel = []
|
time-travel = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ pub use iced_runtime as runtime;
|
||||||
pub use iced_runtime::core;
|
pub use iced_runtime::core;
|
||||||
pub use iced_runtime::futures;
|
pub use iced_runtime::futures;
|
||||||
|
|
||||||
|
pub mod message;
|
||||||
|
|
||||||
mod preset;
|
mod preset;
|
||||||
|
|
||||||
pub use preset::Preset;
|
pub use preset::Preset;
|
||||||
|
|
@ -13,7 +15,7 @@ use crate::core::text;
|
||||||
use crate::core::theme;
|
use crate::core::theme;
|
||||||
use crate::core::window;
|
use crate::core::window;
|
||||||
use crate::core::{Element, Font, Settings};
|
use crate::core::{Element, Font, Settings};
|
||||||
use crate::futures::{Executor, Subscription};
|
use crate::futures::{Executor, MaybeSend, Subscription};
|
||||||
use crate::graphics::compositor;
|
use crate::graphics::compositor;
|
||||||
use crate::runtime::Task;
|
use crate::runtime::Task;
|
||||||
|
|
||||||
|
|
@ -27,7 +29,7 @@ pub trait Program: Sized {
|
||||||
type State;
|
type State;
|
||||||
|
|
||||||
/// The message of the program.
|
/// The message of the program.
|
||||||
type Message: Message + 'static;
|
type Message: MaybeSend + 'static;
|
||||||
|
|
||||||
/// The theme of the program.
|
/// The theme of the program.
|
||||||
type Theme: Default + theme::Base;
|
type Theme: Default + theme::Base;
|
||||||
|
|
@ -43,6 +45,8 @@ pub trait Program: Sized {
|
||||||
|
|
||||||
fn settings(&self) -> Settings;
|
fn settings(&self) -> Settings;
|
||||||
|
|
||||||
|
fn window(&self) -> Option<window::Settings>;
|
||||||
|
|
||||||
fn boot(&self) -> (Self::State, Task<Self::Message>);
|
fn boot(&self) -> (Self::State, Task<Self::Message>);
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
|
|
@ -143,6 +147,10 @@ pub fn with_title<P: Program>(
|
||||||
self.program.settings()
|
self.program.settings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn window(&self) -> Option<window::Settings> {
|
||||||
|
self.program.window()
|
||||||
|
}
|
||||||
|
|
||||||
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||||
self.program.boot()
|
self.program.boot()
|
||||||
}
|
}
|
||||||
|
|
@ -229,6 +237,10 @@ pub fn with_subscription<P: Program>(
|
||||||
self.program.settings()
|
self.program.settings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn window(&self) -> Option<window::Settings> {
|
||||||
|
self.program.window()
|
||||||
|
}
|
||||||
|
|
||||||
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||||
self.program.boot()
|
self.program.boot()
|
||||||
}
|
}
|
||||||
|
|
@ -316,6 +328,10 @@ pub fn with_theme<P: Program>(
|
||||||
self.program.settings()
|
self.program.settings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn window(&self) -> Option<window::Settings> {
|
||||||
|
self.program.window()
|
||||||
|
}
|
||||||
|
|
||||||
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||||
self.program.boot()
|
self.program.boot()
|
||||||
}
|
}
|
||||||
|
|
@ -399,6 +415,10 @@ pub fn with_style<P: Program>(
|
||||||
self.program.settings()
|
self.program.settings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn window(&self) -> Option<window::Settings> {
|
||||||
|
self.program.window()
|
||||||
|
}
|
||||||
|
|
||||||
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||||
self.program.boot()
|
self.program.boot()
|
||||||
}
|
}
|
||||||
|
|
@ -478,6 +498,10 @@ pub fn with_scale_factor<P: Program>(
|
||||||
self.program.settings()
|
self.program.settings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn window(&self) -> Option<window::Settings> {
|
||||||
|
self.program.window()
|
||||||
|
}
|
||||||
|
|
||||||
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||||
self.program.boot()
|
self.program.boot()
|
||||||
}
|
}
|
||||||
|
|
@ -565,6 +589,10 @@ pub fn with_executor<P: Program, E: Executor>(
|
||||||
self.program.settings()
|
self.program.settings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn window(&self) -> Option<window::Settings> {
|
||||||
|
self.program.window()
|
||||||
|
}
|
||||||
|
|
||||||
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||||
self.program.boot()
|
self.program.boot()
|
||||||
}
|
}
|
||||||
|
|
@ -683,17 +711,3 @@ impl<P: Program> Instance<P> {
|
||||||
self.program.scale_factor(&self.state, window)
|
self.program.scale_factor(&self.state, window)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait alias for the [`Message`](Program::Message) of a [`Program`].
|
|
||||||
#[cfg(feature = "time-travel")]
|
|
||||||
pub trait Message: Send + std::fmt::Debug + Clone {}
|
|
||||||
|
|
||||||
#[cfg(feature = "time-travel")]
|
|
||||||
impl<T: Send + std::fmt::Debug + Clone> Message for T {}
|
|
||||||
|
|
||||||
/// A trait alias for the [`Message`](Program::Message) of a [`Program`].
|
|
||||||
#[cfg(not(feature = "time-travel"))]
|
|
||||||
pub trait Message: Send + std::fmt::Debug {}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "time-travel"))]
|
|
||||||
impl<T: Send + std::fmt::Debug> Message for T {}
|
|
||||||
|
|
|
||||||
33
program/src/message.rs
Normal file
33
program/src/message.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
//! Traits for the message type of a [`Program`](crate::Program).
|
||||||
|
|
||||||
|
/// A trait alias for [`Clone`], but only when the `time-travel`
|
||||||
|
/// feature is enabled.
|
||||||
|
#[cfg(feature = "time-travel")]
|
||||||
|
pub trait MaybeClone: Clone {}
|
||||||
|
|
||||||
|
#[cfg(feature = "time-travel")]
|
||||||
|
impl<T> MaybeClone for T where T: Clone {}
|
||||||
|
|
||||||
|
/// A trait alias for [`Clone`], but only when the `time-travel`
|
||||||
|
/// feature is enabled.
|
||||||
|
#[cfg(not(feature = "time-travel"))]
|
||||||
|
pub trait MaybeClone {}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "time-travel"))]
|
||||||
|
impl<T> MaybeClone for T {}
|
||||||
|
|
||||||
|
/// A trait alias for [`Debug`](std::fmt::Debug), but only when the
|
||||||
|
/// `debug` feature is enabled.
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
pub trait MaybeDebug: std::fmt::Debug {}
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
impl<T> MaybeDebug for T where T: std::fmt::Debug {}
|
||||||
|
|
||||||
|
/// A trait alias for [`Debug`](std::fmt::Debug), but only when the
|
||||||
|
/// `debug` feature is enabled.
|
||||||
|
#[cfg(not(feature = "debug"))]
|
||||||
|
pub trait MaybeDebug {}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "debug"))]
|
||||||
|
impl<T> MaybeDebug for T {}
|
||||||
|
|
@ -9,6 +9,7 @@ use crate::futures::{BoxStream, MaybeSend, boxed_stream};
|
||||||
|
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
#[cfg(feature = "sipper")]
|
#[cfg(feature = "sipper")]
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
|
|
@ -466,3 +467,47 @@ pub fn effect<T>(action: impl Into<Action<Infallible>>) -> Task<T> {
|
||||||
pub fn into_stream<T>(task: Task<T>) -> Option<BoxStream<Action<T>>> {
|
pub fn into_stream<T>(task: Task<T>) -> Option<BoxStream<Action<T>>> {
|
||||||
task.stream
|
task.stream
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new [`Task`] that will run the given closure in a new thread.
|
||||||
|
///
|
||||||
|
/// Any data sent by the closure through the [`mpsc::Sender`] will be produced
|
||||||
|
/// by the [`Task`].
|
||||||
|
pub fn blocking<T>(f: impl FnOnce(mpsc::Sender<T>) + Send + 'static) -> Task<T>
|
||||||
|
where
|
||||||
|
T: Send + 'static,
|
||||||
|
{
|
||||||
|
let (sender, receiver) = mpsc::channel(1);
|
||||||
|
|
||||||
|
let _ = thread::spawn(move || {
|
||||||
|
f(sender);
|
||||||
|
});
|
||||||
|
|
||||||
|
Task::stream(receiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new [`Task`] that will run the given closure that can fail in a new
|
||||||
|
/// thread.
|
||||||
|
///
|
||||||
|
/// Any data sent by the closure through the [`mpsc::Sender`] will be produced
|
||||||
|
/// by the [`Task`].
|
||||||
|
pub fn try_blocking<T, E>(
|
||||||
|
f: impl FnOnce(mpsc::Sender<T>) -> Result<(), E> + Send + 'static,
|
||||||
|
) -> Task<Result<T, E>>
|
||||||
|
where
|
||||||
|
T: Send + 'static,
|
||||||
|
E: Send + 'static,
|
||||||
|
{
|
||||||
|
let (sender, receiver) = mpsc::channel(1);
|
||||||
|
let (error_sender, error_receiver) = oneshot::channel();
|
||||||
|
|
||||||
|
let _ = thread::spawn(move || {
|
||||||
|
if let Err(error) = f(sender) {
|
||||||
|
let _ = error_sender.send(Err(error));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Task::stream(stream::select(
|
||||||
|
receiver.map(Ok),
|
||||||
|
stream::once(error_receiver).filter_map(async |result| result.ok()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,12 +30,14 @@
|
||||||
//! ]
|
//! ]
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
|
use crate::message;
|
||||||
use crate::program::{self, Program};
|
use crate::program::{self, Program};
|
||||||
use crate::shell;
|
use crate::shell;
|
||||||
use crate::theme;
|
use crate::theme;
|
||||||
use crate::window;
|
use crate::window;
|
||||||
use crate::{
|
use crate::{
|
||||||
Element, Executor, Font, Preset, Result, Settings, Size, Subscription, Task,
|
Element, Executor, Font, MaybeSend, Preset, Result, Settings, Size,
|
||||||
|
Subscription, Task,
|
||||||
};
|
};
|
||||||
|
|
||||||
use iced_debug as debug;
|
use iced_debug as debug;
|
||||||
|
|
@ -81,7 +83,7 @@ pub fn application<State, Message, Theme, Renderer>(
|
||||||
) -> Application<impl Program<State = State, Message = Message, Theme = Theme>>
|
) -> Application<impl Program<State = State, Message = Message, Theme = Theme>>
|
||||||
where
|
where
|
||||||
State: 'static,
|
State: 'static,
|
||||||
Message: program::Message + 'static,
|
Message: MaybeSend + 'static,
|
||||||
Theme: Default + theme::Base,
|
Theme: Default + theme::Base,
|
||||||
Renderer: program::Renderer,
|
Renderer: program::Renderer,
|
||||||
{
|
{
|
||||||
|
|
@ -100,7 +102,7 @@ where
|
||||||
impl<State, Message, Theme, Renderer, Boot, Update, View> Program
|
impl<State, Message, Theme, Renderer, Boot, Update, View> Program
|
||||||
for Instance<State, Message, Theme, Renderer, Boot, Update, View>
|
for Instance<State, Message, Theme, Renderer, Boot, Update, View>
|
||||||
where
|
where
|
||||||
Message: program::Message + 'static,
|
Message: MaybeSend + 'static,
|
||||||
Theme: Default + theme::Base,
|
Theme: Default + theme::Base,
|
||||||
Renderer: program::Renderer,
|
Renderer: program::Renderer,
|
||||||
Boot: self::Boot<State, Message>,
|
Boot: self::Boot<State, Message>,
|
||||||
|
|
@ -142,6 +144,10 @@ where
|
||||||
fn settings(&self) -> Settings {
|
fn settings(&self) -> Settings {
|
||||||
Settings::default()
|
Settings::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn window(&self) -> Option<iced_core::window::Settings> {
|
||||||
|
Some(window::Settings::default())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Application {
|
Application {
|
||||||
|
|
@ -180,25 +186,25 @@ impl<P: Program> Application<P> {
|
||||||
pub fn run(self) -> Result
|
pub fn run(self) -> Result
|
||||||
where
|
where
|
||||||
Self: 'static,
|
Self: 'static,
|
||||||
|
P::Message: message::MaybeDebug + message::MaybeClone,
|
||||||
{
|
{
|
||||||
let settings = self.settings.clone();
|
#[cfg(feature = "debug")]
|
||||||
let window = self.window.clone();
|
iced_debug::init(iced_debug::Metadata {
|
||||||
|
name: P::name(),
|
||||||
|
theme: None,
|
||||||
|
can_time_travel: cfg!(feature = "time-travel"),
|
||||||
|
});
|
||||||
|
|
||||||
#[cfg(all(feature = "debug", not(target_arch = "wasm32")))]
|
#[cfg(feature = "tester")]
|
||||||
let program = {
|
let program = iced_tester::attach(self);
|
||||||
iced_debug::init(iced_debug::Metadata {
|
|
||||||
name: P::name(),
|
|
||||||
theme: None,
|
|
||||||
can_time_travel: cfg!(feature = "time-travel"),
|
|
||||||
});
|
|
||||||
|
|
||||||
iced_devtools::attach(self)
|
#[cfg(all(feature = "debug", not(feature = "tester")))]
|
||||||
};
|
let program = iced_devtools::attach(self);
|
||||||
|
|
||||||
#[cfg(any(not(feature = "debug"), target_arch = "wasm32"))]
|
#[cfg(any(not(feature = "debug"), target_arch = "wasm32"))]
|
||||||
let program = self;
|
let program = self;
|
||||||
|
|
||||||
Ok(shell::run(program, settings, Some(window))?)
|
Ok(shell::run(program)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the [`Settings`] that will be used to run the [`Application`].
|
/// Sets the [`Settings`] that will be used to run the [`Application`].
|
||||||
|
|
@ -456,6 +462,10 @@ impl<P: Program> Program for Application<P> {
|
||||||
self.settings.clone()
|
self.settings.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn window(&self) -> Option<window::Settings> {
|
||||||
|
Some(self.window.clone())
|
||||||
|
}
|
||||||
|
|
||||||
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||||
self.raw.boot()
|
self.raw.boot()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ pub fn timed<State, Message, Theme, Renderer>(
|
||||||
>
|
>
|
||||||
where
|
where
|
||||||
State: 'static,
|
State: 'static,
|
||||||
Message: program::Message + 'static,
|
Message: Send + 'static,
|
||||||
Theme: Default + theme::Base + 'static,
|
Theme: Default + theme::Base + 'static,
|
||||||
Renderer: program::Renderer + 'static,
|
Renderer: program::Renderer + 'static,
|
||||||
{
|
{
|
||||||
|
|
@ -68,7 +68,7 @@ where
|
||||||
View,
|
View,
|
||||||
>
|
>
|
||||||
where
|
where
|
||||||
Message: program::Message + 'static,
|
Message: Send + 'static,
|
||||||
Theme: Default + theme::Base + 'static,
|
Theme: Default + theme::Base + 'static,
|
||||||
Renderer: program::Renderer + 'static,
|
Renderer: program::Renderer + 'static,
|
||||||
Boot: self::Boot<State, Message>,
|
Boot: self::Boot<State, Message>,
|
||||||
|
|
@ -92,6 +92,10 @@ where
|
||||||
Settings::default()
|
Settings::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn window(&self) -> Option<iced_core::window::Settings> {
|
||||||
|
Some(window::Settings::default())
|
||||||
|
}
|
||||||
|
|
||||||
fn boot(&self) -> (State, Task<Self::Message>) {
|
fn boot(&self) -> (State, Task<Self::Message>) {
|
||||||
let (state, task) = self.boot.boot();
|
let (state, task) = self.boot.boot();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
//! Create and run daemons that run in the background.
|
//! Create and run daemons that run in the background.
|
||||||
use crate::application;
|
use crate::application;
|
||||||
|
use crate::message;
|
||||||
use crate::program::{self, Program};
|
use crate::program::{self, Program};
|
||||||
use crate::shell;
|
use crate::shell;
|
||||||
use crate::theme;
|
use crate::theme;
|
||||||
use crate::window;
|
use crate::window;
|
||||||
use crate::{
|
use crate::{
|
||||||
Element, Executor, Font, Preset, Result, Settings, Subscription, Task,
|
Element, Executor, Font, MaybeSend, Preset, Result, Settings, Subscription,
|
||||||
|
Task,
|
||||||
};
|
};
|
||||||
|
|
||||||
use iced_debug as debug;
|
use iced_debug as debug;
|
||||||
|
|
@ -29,7 +31,7 @@ pub fn daemon<State, Message, Theme, Renderer>(
|
||||||
) -> Daemon<impl Program<State = State, Message = Message, Theme = Theme>>
|
) -> Daemon<impl Program<State = State, Message = Message, Theme = Theme>>
|
||||||
where
|
where
|
||||||
State: 'static,
|
State: 'static,
|
||||||
Message: program::Message + 'static,
|
Message: MaybeSend + 'static,
|
||||||
Theme: Default + theme::Base,
|
Theme: Default + theme::Base,
|
||||||
Renderer: program::Renderer,
|
Renderer: program::Renderer,
|
||||||
{
|
{
|
||||||
|
|
@ -48,7 +50,7 @@ where
|
||||||
impl<State, Message, Theme, Renderer, Boot, Update, View> Program
|
impl<State, Message, Theme, Renderer, Boot, Update, View> Program
|
||||||
for Instance<State, Message, Theme, Renderer, Boot, Update, View>
|
for Instance<State, Message, Theme, Renderer, Boot, Update, View>
|
||||||
where
|
where
|
||||||
Message: program::Message + 'static,
|
Message: MaybeSend + 'static,
|
||||||
Theme: Default + theme::Base,
|
Theme: Default + theme::Base,
|
||||||
Renderer: program::Renderer,
|
Renderer: program::Renderer,
|
||||||
Boot: application::Boot<State, Message>,
|
Boot: application::Boot<State, Message>,
|
||||||
|
|
@ -71,6 +73,10 @@ where
|
||||||
Settings::default()
|
Settings::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn window(&self) -> Option<iced_core::window::Settings> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||||
self.boot.boot()
|
self.boot.boot()
|
||||||
}
|
}
|
||||||
|
|
@ -126,9 +132,8 @@ impl<P: Program> Daemon<P> {
|
||||||
pub fn run(self) -> Result
|
pub fn run(self) -> Result
|
||||||
where
|
where
|
||||||
Self: 'static,
|
Self: 'static,
|
||||||
|
P::Message: message::MaybeDebug + message::MaybeClone,
|
||||||
{
|
{
|
||||||
let settings = self.settings.clone();
|
|
||||||
|
|
||||||
#[cfg(all(feature = "debug", not(target_arch = "wasm32")))]
|
#[cfg(all(feature = "debug", not(target_arch = "wasm32")))]
|
||||||
let program = {
|
let program = {
|
||||||
iced_debug::init(iced_debug::Metadata {
|
iced_debug::init(iced_debug::Metadata {
|
||||||
|
|
@ -143,7 +148,7 @@ impl<P: Program> Daemon<P> {
|
||||||
#[cfg(any(not(feature = "debug"), target_arch = "wasm32"))]
|
#[cfg(any(not(feature = "debug"), target_arch = "wasm32"))]
|
||||||
let program = self;
|
let program = self;
|
||||||
|
|
||||||
Ok(shell::run(program, settings, None)?)
|
Ok(shell::run(program)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the [`Settings`] that will be used to run the [`Daemon`].
|
/// Sets the [`Settings`] that will be used to run the [`Daemon`].
|
||||||
|
|
@ -298,6 +303,10 @@ impl<P: Program> Program for Daemon<P> {
|
||||||
self.settings.clone()
|
self.settings.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn window(&self) -> Option<window::Settings> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||||
self.raw.boot()
|
self.raw.boot()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -526,7 +526,9 @@ pub use crate::core::{
|
||||||
Rotation, Settings, Shadow, Size, Theme, Transformation, Vector, never,
|
Rotation, Settings, Shadow, Size, Theme, Transformation, Vector, never,
|
||||||
};
|
};
|
||||||
pub use crate::program::Preset;
|
pub use crate::program::Preset;
|
||||||
|
pub use crate::program::message;
|
||||||
pub use crate::runtime::exit;
|
pub use crate::runtime::exit;
|
||||||
|
pub use crate::runtime::futures::MaybeSend;
|
||||||
pub use iced_futures::Subscription;
|
pub use iced_futures::Subscription;
|
||||||
|
|
||||||
pub use Alignment::Center;
|
pub use Alignment::Center;
|
||||||
|
|
@ -696,7 +698,7 @@ pub fn run<State, Message, Theme, Renderer>(
|
||||||
) -> Result
|
) -> Result
|
||||||
where
|
where
|
||||||
State: Default + 'static,
|
State: Default + 'static,
|
||||||
Message: program::Message + 'static,
|
Message: MaybeSend + message::MaybeDebug + message::MaybeClone + 'static,
|
||||||
Theme: Default + theme::Base + 'static,
|
Theme: Default + theme::Base + 'static,
|
||||||
Renderer: program::Renderer + 'static,
|
Renderer: program::Renderer + 'static,
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -84,10 +84,10 @@
|
||||||
//!
|
//!
|
||||||
//! [the classical counter interface]: https://book.iced.rs/architecture.html#dissecting-an-interface
|
//! [the classical counter interface]: https://book.iced.rs/architecture.html#dissecting-an-interface
|
||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
use iced_program as program;
|
pub use iced_program as program;
|
||||||
use iced_renderer as renderer;
|
pub use iced_renderer as renderer;
|
||||||
use iced_runtime as runtime;
|
pub use iced_runtime as runtime;
|
||||||
use iced_runtime::core;
|
pub use iced_runtime::core;
|
||||||
|
|
||||||
pub use iced_selector as selector;
|
pub use iced_selector as selector;
|
||||||
|
|
||||||
|
|
|
||||||
20
tester/Cargo.toml
Normal file
20
tester/Cargo.toml
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
[package]
|
||||||
|
name = "iced_tester"
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
categories.workspace = true
|
||||||
|
keywords.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced_test.workspace = true
|
||||||
|
iced_widget.workspace = true
|
||||||
|
log.workspace = true
|
||||||
|
rfd.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
@ -3,7 +3,7 @@ use crate::core::Font;
|
||||||
use crate::program;
|
use crate::program;
|
||||||
use crate::widget::{Text, text};
|
use crate::widget::{Text, text};
|
||||||
|
|
||||||
pub const FONT: &[u8] = include_bytes!("../fonts/iced_devtools-icons.ttf");
|
pub const FONT: &[u8] = include_bytes!("../fonts/iced_tester-icons.ttf");
|
||||||
|
|
||||||
pub fn cancel<'a, Theme, Renderer>() -> Text<'a, Theme, Renderer>
|
pub fn cancel<'a, Theme, Renderer>() -> Text<'a, Theme, Renderer>
|
||||||
where
|
where
|
||||||
|
|
@ -1,29 +1,97 @@
|
||||||
|
//! Record, edit, and run end-to-end tests for your iced applications.
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
pub use iced_test as test;
|
||||||
|
pub use iced_test::core;
|
||||||
|
pub use iced_test::program;
|
||||||
|
pub use iced_test::runtime;
|
||||||
|
pub use iced_test::runtime::futures;
|
||||||
|
pub use iced_widget as widget;
|
||||||
|
|
||||||
|
mod icon;
|
||||||
mod recorder;
|
mod recorder;
|
||||||
|
|
||||||
use recorder::recorder;
|
use recorder::recorder;
|
||||||
|
|
||||||
use crate::Program;
|
|
||||||
use crate::core::Alignment::Center;
|
use crate::core::Alignment::Center;
|
||||||
use crate::core::Length::Fill;
|
use crate::core::Length::Fill;
|
||||||
use crate::core::alignment::Horizontal::Right;
|
use crate::core::alignment::Horizontal::Right;
|
||||||
use crate::core::border;
|
use crate::core::border;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::window;
|
use crate::core::window;
|
||||||
use crate::core::{Element, Font, Size, Theme};
|
use crate::core::{Element, Font, Settings, Size, Theme};
|
||||||
use crate::executor;
|
|
||||||
use crate::futures::futures::channel::mpsc;
|
use crate::futures::futures::channel::mpsc;
|
||||||
use crate::icon;
|
use crate::program::Program;
|
||||||
use crate::program;
|
use crate::runtime::task::{self, Task};
|
||||||
use crate::runtime::Task;
|
|
||||||
use crate::test::emulator;
|
use crate::test::emulator;
|
||||||
use crate::test::ice;
|
use crate::test::ice;
|
||||||
use crate::test::instruction;
|
use crate::test::instruction;
|
||||||
use crate::test::{Emulator, Ice, Instruction};
|
use crate::test::{Emulator, Ice, Instruction};
|
||||||
use crate::widget::{
|
use crate::widget::{
|
||||||
button, center, column, combo_box, container, horizontal_space, monospace,
|
button, center, column, combo_box, container, horizontal_space, pick_list,
|
||||||
pick_list, row, scrollable, text, text_editor, text_input, themer,
|
row, scrollable, text, text_editor, text_input, themer,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Attaches a [`Tester`] to the given [`Program`].
|
||||||
|
pub fn attach<P: Program + 'static>(program: P) -> Attach<P> {
|
||||||
|
Attach { program }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [`Program`] with a [`Tester`] attached to it.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Attach<P> {
|
||||||
|
/// The original [`Program`] attatched to the [`Tester`].
|
||||||
|
pub program: P,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> Program for Attach<P>
|
||||||
|
where
|
||||||
|
P: Program + 'static,
|
||||||
|
{
|
||||||
|
type State = Tester<P>;
|
||||||
|
type Message = Tick<P>;
|
||||||
|
type Theme = Theme;
|
||||||
|
type Renderer = P::Renderer;
|
||||||
|
type Executor = P::Executor;
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
P::name()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn settings(&self) -> Settings {
|
||||||
|
let mut settings = self.program.settings();
|
||||||
|
settings.fonts.push(icon::FONT.into());
|
||||||
|
settings
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window(&self) -> Option<window::Settings> {
|
||||||
|
self.program.window().map(|window| window::Settings {
|
||||||
|
size: window.size + Size::new(300.0, 80.0),
|
||||||
|
..window
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boot(&self) -> (Self::State, Task<Self::Message>) {
|
||||||
|
(Tester::new(&self.program), Task::none())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&self,
|
||||||
|
state: &mut Self::State,
|
||||||
|
message: Self::Message,
|
||||||
|
) -> Task<Self::Message> {
|
||||||
|
state.tick(&self.program, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view<'a>(
|
||||||
|
&self,
|
||||||
|
state: &'a Self::State,
|
||||||
|
window: window::Id,
|
||||||
|
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
|
||||||
|
state.view(&self.program, window)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Tester<P: Program> {
|
pub struct Tester<P: Program> {
|
||||||
viewport: Size,
|
viewport: Size,
|
||||||
mode: emulator::Mode,
|
mode: emulator::Mode,
|
||||||
|
|
@ -35,7 +103,10 @@ pub struct Tester<P: Program> {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum State<P: Program> {
|
enum State<P: Program> {
|
||||||
Idle,
|
Empty,
|
||||||
|
Idle {
|
||||||
|
state: P::State,
|
||||||
|
},
|
||||||
Recording {
|
Recording {
|
||||||
emulator: Emulator<P>,
|
emulator: Emulator<P>,
|
||||||
},
|
},
|
||||||
|
|
@ -84,9 +155,12 @@ pub enum Tick<P: Program> {
|
||||||
|
|
||||||
impl<P: Program + 'static> Tester<P> {
|
impl<P: Program + 'static> Tester<P> {
|
||||||
pub fn new(program: &P) -> Self {
|
pub fn new(program: &P) -> Self {
|
||||||
|
let (state, _) = program.boot();
|
||||||
|
let window = program.window().unwrap_or_default();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
mode: emulator::Mode::default(),
|
mode: emulator::Mode::default(),
|
||||||
viewport: window::Settings::default().size,
|
viewport: window.size,
|
||||||
presets: combo_box::State::new(
|
presets: combo_box::State::new(
|
||||||
program
|
program
|
||||||
.presets()
|
.presets()
|
||||||
|
|
@ -97,7 +171,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
),
|
),
|
||||||
preset: None,
|
preset: None,
|
||||||
instructions: Vec::new(),
|
instructions: Vec::new(),
|
||||||
state: State::Idle,
|
state: State::Idle { state },
|
||||||
edit: None,
|
edit: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -150,7 +224,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
}
|
}
|
||||||
Message::Stop => {
|
Message::Stop => {
|
||||||
let State::Recording { emulator } =
|
let State::Recording { emulator } =
|
||||||
std::mem::replace(&mut self.state, State::Idle)
|
std::mem::replace(&mut self.state, State::Empty)
|
||||||
else {
|
else {
|
||||||
return Task::none();
|
return Task::none();
|
||||||
};
|
};
|
||||||
|
|
@ -204,7 +278,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
|
|
||||||
Task::future(import)
|
Task::future(import)
|
||||||
.and_then(|file| {
|
.and_then(|file| {
|
||||||
executor::spawn_blocking(move |mut sender| {
|
task::blocking(move |mut sender| {
|
||||||
let _ = sender.try_send(Ice::parse(
|
let _ = sender.try_send(Ice::parse(
|
||||||
&fs::read_to_string(file.path())
|
&fs::read_to_string(file.path())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
|
|
@ -248,7 +322,13 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
self.preset = ice.preset;
|
self.preset = ice.preset;
|
||||||
self.instructions = ice.instructions;
|
self.instructions = ice.instructions;
|
||||||
self.edit = None;
|
self.edit = None;
|
||||||
self.state = State::Idle;
|
|
||||||
|
let (state, _) = self
|
||||||
|
.preset(program)
|
||||||
|
.map(program::Preset::boot)
|
||||||
|
.unwrap_or_else(|| program.boot());
|
||||||
|
|
||||||
|
self.state = State::Idle { state };
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
@ -358,7 +438,9 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
State::Idle | State::Asserting { .. } => {}
|
State::Empty
|
||||||
|
| State::Idle { .. }
|
||||||
|
| State::Asserting { .. } => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
|
|
@ -432,15 +514,14 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view<'a, T: 'static>(
|
pub fn view<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
program: &P,
|
program: &P,
|
||||||
current: impl FnOnce() -> Element<'a, T, Theme, P::Renderer>,
|
window: window::Id,
|
||||||
emulate: impl Fn(Tick<P>) -> T + 'a,
|
) -> Element<'a, Tick<P>, Theme, P::Renderer> {
|
||||||
) -> Element<'a, T, Theme, P::Renderer> {
|
|
||||||
let status = {
|
let status = {
|
||||||
let (icon, label) = match &self.state {
|
let (icon, label) = match &self.state {
|
||||||
State::Idle => (text(""), "Idle"),
|
State::Empty | State::Idle { .. } => (text(""), "Idle"),
|
||||||
State::Recording { .. } => (icon::record(), "Recording"),
|
State::Recording { .. } => (icon::record(), "Recording"),
|
||||||
State::Asserting { .. } => (icon::lightbulb(), "Asserting"),
|
State::Asserting { .. } => (icon::lightbulb(), "Asserting"),
|
||||||
State::Playing { outcome, .. } => match outcome {
|
State::Playing { outcome, .. } => match outcome {
|
||||||
|
|
@ -456,7 +537,9 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
|
|
||||||
container::Style {
|
container::Style {
|
||||||
text_color: Some(match &self.state {
|
text_color: Some(match &self.state {
|
||||||
State::Idle => palette.background.strongest.color,
|
State::Empty | State::Idle { .. } => {
|
||||||
|
palette.background.strongest.color
|
||||||
|
}
|
||||||
State::Recording { .. } => {
|
State::Recording { .. } => {
|
||||||
palette.danger.base.color
|
palette.danger.base.color
|
||||||
}
|
}
|
||||||
|
|
@ -483,33 +566,34 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
let viewport = container(
|
let viewport = container(
|
||||||
scrollable(
|
scrollable(
|
||||||
container(match &self.state {
|
container(match &self.state {
|
||||||
State::Idle => current(),
|
State::Empty => horizontal_space().into(),
|
||||||
|
State::Idle { state } => Element::from(themer(
|
||||||
|
program.theme(state, window),
|
||||||
|
program.view(state, window),
|
||||||
|
))
|
||||||
|
.map(Tick::Program),
|
||||||
State::Recording { emulator } => {
|
State::Recording { emulator } => {
|
||||||
let theme = emulator.theme(program);
|
let theme = emulator.theme(program);
|
||||||
let view = emulator.view(program).map(Tick::Program);
|
let view = emulator.view(program).map(Tick::Program);
|
||||||
|
|
||||||
Element::from(
|
recorder(themer(theme, view))
|
||||||
recorder(themer(theme, view))
|
.on_record(Tick::Record)
|
||||||
.on_record(Tick::Record),
|
.into()
|
||||||
)
|
|
||||||
.map(emulate)
|
|
||||||
}
|
}
|
||||||
State::Asserting { state, window, .. } => {
|
State::Asserting { state, window, .. } => {
|
||||||
let theme = program.theme(state, *window);
|
let theme = program.theme(state, *window);
|
||||||
let view =
|
let view =
|
||||||
program.view(state, *window).map(Tick::Program);
|
program.view(state, *window).map(Tick::Program);
|
||||||
|
|
||||||
Element::from(
|
recorder(themer(theme, view))
|
||||||
recorder(themer(theme, view))
|
.on_record(Tick::Assert)
|
||||||
.on_record(Tick::Assert),
|
.into()
|
||||||
)
|
|
||||||
.map(emulate)
|
|
||||||
}
|
}
|
||||||
State::Playing { emulator, .. } => {
|
State::Playing { emulator, .. } => {
|
||||||
let theme = emulator.theme(program);
|
let theme = emulator.theme(program);
|
||||||
let view = emulator.view(program).map(Tick::Program);
|
let view = emulator.view(program).map(Tick::Program);
|
||||||
|
|
||||||
Element::from(themer(theme, view)).map(emulate)
|
themer(theme, view).into()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.width(self.viewport.width)
|
.width(self.viewport.width)
|
||||||
|
|
@ -525,7 +609,9 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
|
|
||||||
container::Style {
|
container::Style {
|
||||||
border: border::width(2.0).color(match &self.state {
|
border: border::width(2.0).color(match &self.state {
|
||||||
State::Idle => palette.background.strongest.color,
|
State::Empty | State::Idle { .. } => {
|
||||||
|
palette.background.strongest.color
|
||||||
|
}
|
||||||
State::Recording { .. } => palette.danger.base.color,
|
State::Recording { .. } => palette.danger.base.color,
|
||||||
State::Asserting { .. } => palette.warning.weak.color,
|
State::Asserting { .. } => palette.warning.weak.color,
|
||||||
State::Playing { outcome, .. } => match outcome {
|
State::Playing { outcome, .. } => match outcome {
|
||||||
|
|
@ -539,9 +625,15 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
})
|
})
|
||||||
.padding(10);
|
.padding(10);
|
||||||
|
|
||||||
center(column![status, viewport].spacing(10).align_x(Right))
|
row![
|
||||||
.padding(10)
|
center(column![status, viewport].spacing(10).align_x(Right))
|
||||||
.into()
|
.padding(10),
|
||||||
|
container(self.controls().map(Tick::Tester))
|
||||||
|
.width(250)
|
||||||
|
.padding(10)
|
||||||
|
.style(container::dark)
|
||||||
|
]
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn controls(&self) -> Element<'_, Message, Theme, P::Renderer> {
|
pub fn controls(&self) -> Element<'_, Message, Theme, P::Renderer> {
|
||||||
|
|
@ -552,7 +644,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
width: width.parse().unwrap_or(self.viewport.width),
|
width: width.parse().unwrap_or(self.viewport.width),
|
||||||
..self.viewport
|
..self.viewport
|
||||||
})),
|
})),
|
||||||
monospace("x").size(14),
|
text("x").size(14).font(Font::MONOSPACE),
|
||||||
text_input("Height", &self.viewport.height.to_string())
|
text_input("Height", &self.viewport.height.to_string())
|
||||||
.size(14)
|
.size(14)
|
||||||
.on_input(|height| Message::ChangeViewport(Size {
|
.on_input(|height| Message::ChangeViewport(Size {
|
||||||
|
|
@ -590,8 +682,9 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
.into()
|
.into()
|
||||||
} else if self.instructions.is_empty() {
|
} else if self.instructions.is_empty() {
|
||||||
Element::from(center(
|
Element::from(center(
|
||||||
monospace("No instructions recorded yet!")
|
text("No instructions recorded yet!")
|
||||||
.size(14)
|
.size(14)
|
||||||
|
.font(Font::MONOSPACE)
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.center(),
|
.center(),
|
||||||
))
|
))
|
||||||
|
|
@ -599,9 +692,10 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
scrollable(
|
scrollable(
|
||||||
column(self.instructions.iter().enumerate().map(
|
column(self.instructions.iter().enumerate().map(
|
||||||
|(i, instruction)| {
|
|(i, instruction)| {
|
||||||
monospace(instruction.to_string())
|
text(instruction.to_string())
|
||||||
.wrapping(text::Wrapping::None) // TODO: Ellipsize?
|
.wrapping(text::Wrapping::None) // TODO: Ellipsize?
|
||||||
.size(10)
|
.size(10)
|
||||||
|
.font(Font::MONOSPACE)
|
||||||
.style(move |theme: &Theme| text::Style {
|
.style(move |theme: &Theme| text::Style {
|
||||||
color: match &self.state {
|
color: match &self.state {
|
||||||
State::Playing {
|
State::Playing {
|
||||||
|
|
@ -730,9 +824,12 @@ where
|
||||||
Message: 'a,
|
Message: 'a,
|
||||||
Renderer: program::Renderer + 'a,
|
Renderer: program::Renderer + 'a,
|
||||||
{
|
{
|
||||||
column![monospace(fragment).size(14), content.into()]
|
column![
|
||||||
.spacing(5)
|
text(fragment).size(14).font(Font::MONOSPACE),
|
||||||
.into()
|
content.into()
|
||||||
|
]
|
||||||
|
.spacing(5)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn labeled_with<'a, Message, Renderer>(
|
fn labeled_with<'a, Message, Renderer>(
|
||||||
|
|
@ -746,7 +843,7 @@ where
|
||||||
{
|
{
|
||||||
column![
|
column![
|
||||||
row![
|
row![
|
||||||
monospace(fragment).size(14),
|
text(fragment).size(14).font(Font::MONOSPACE),
|
||||||
horizontal_space(),
|
horizontal_space(),
|
||||||
control.into()
|
control.into()
|
||||||
]
|
]
|
||||||
|
|
@ -45,7 +45,7 @@ use crate::core::renderer;
|
||||||
use crate::core::theme;
|
use crate::core::theme;
|
||||||
use crate::core::time::Instant;
|
use crate::core::time::Instant;
|
||||||
use crate::core::widget::operation;
|
use crate::core::widget::operation;
|
||||||
use crate::core::{Point, Settings, Size};
|
use crate::core::{Point, Size};
|
||||||
use crate::futures::futures::channel::mpsc;
|
use crate::futures::futures::channel::mpsc;
|
||||||
use crate::futures::futures::channel::oneshot;
|
use crate::futures::futures::channel::oneshot;
|
||||||
use crate::futures::futures::task;
|
use crate::futures::futures::task;
|
||||||
|
|
@ -66,11 +66,7 @@ use std::slice;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Runs a [`Program`] with the provided settings.
|
/// Runs a [`Program`] with the provided settings.
|
||||||
pub fn run<P>(
|
pub fn run<P>(program: P) -> Result<(), Error>
|
||||||
program: P,
|
|
||||||
settings: Settings,
|
|
||||||
window_settings: Option<window::Settings>,
|
|
||||||
) -> Result<(), Error>
|
|
||||||
where
|
where
|
||||||
P: Program + 'static,
|
P: Program + 'static,
|
||||||
P::Theme: theme::Base,
|
P::Theme: theme::Base,
|
||||||
|
|
@ -78,6 +74,8 @@ where
|
||||||
use winit::event_loop::EventLoop;
|
use winit::event_loop::EventLoop;
|
||||||
|
|
||||||
let boot_span = debug::boot();
|
let boot_span = debug::boot();
|
||||||
|
let settings = program.settings();
|
||||||
|
let window_settings = program.window();
|
||||||
|
|
||||||
let graphics_settings = settings.clone().into();
|
let graphics_settings = settings.clone().into();
|
||||||
let event_loop = EventLoop::with_user_event()
|
let event_loop = EventLoop::with_user_event()
|
||||||
|
|
@ -169,7 +167,6 @@ where
|
||||||
impl<Message, F> winit::application::ApplicationHandler<Action<Message>>
|
impl<Message, F> winit::application::ApplicationHandler<Action<Message>>
|
||||||
for Runner<Message, F>
|
for Runner<Message, F>
|
||||||
where
|
where
|
||||||
Message: std::fmt::Debug,
|
|
||||||
F: Future<Output = ()>,
|
F: Future<Output = ()>,
|
||||||
{
|
{
|
||||||
fn resumed(
|
fn resumed(
|
||||||
|
|
|
||||||
|
|
@ -88,10 +88,7 @@ impl<T: 'static> Proxy<T> {
|
||||||
///
|
///
|
||||||
/// Note: This skips the backpressure mechanism with an unbounded
|
/// Note: This skips the backpressure mechanism with an unbounded
|
||||||
/// channel. Use sparingly!
|
/// channel. Use sparingly!
|
||||||
pub fn send_action(&self, action: Action<T>)
|
pub fn send_action(&self, action: Action<T>) {
|
||||||
where
|
|
||||||
T: std::fmt::Debug,
|
|
||||||
{
|
|
||||||
let _ = self.raw.send_event(action);
|
let _ = self.raw.send_event(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue