Add program::Preset and emulator::Mode
This commit is contained in:
parent
927d5b7cba
commit
73f5569f28
17 changed files with 305 additions and 39 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2400,6 +2400,7 @@ dependencies = [
|
||||||
"iced_devtools",
|
"iced_devtools",
|
||||||
"iced_futures",
|
"iced_futures",
|
||||||
"iced_highlighter",
|
"iced_highlighter",
|
||||||
|
"iced_program",
|
||||||
"iced_renderer",
|
"iced_renderer",
|
||||||
"iced_runtime",
|
"iced_runtime",
|
||||||
"iced_wgpu",
|
"iced_wgpu",
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,9 @@ debug = ["iced_winit/debug", "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 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 = ["debug", "test", "iced_devtools/tester"]
|
||||||
|
# Enables testing features (e.g. application presets)
|
||||||
|
test = ["iced_program/test"]
|
||||||
# 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
|
||||||
|
|
@ -80,10 +82,10 @@ sipper = ["iced_runtime/sipper"]
|
||||||
iced_debug.workspace = true
|
iced_debug.workspace = true
|
||||||
iced_core.workspace = true
|
iced_core.workspace = true
|
||||||
iced_futures.workspace = true
|
iced_futures.workspace = true
|
||||||
|
iced_program.workspace = true
|
||||||
iced_renderer.workspace = true
|
iced_renderer.workspace = true
|
||||||
iced_runtime.workspace = true
|
iced_runtime.workspace = true
|
||||||
iced_widget.workspace = true
|
iced_widget.workspace = true
|
||||||
iced_winit.features = ["program"]
|
|
||||||
iced_winit.workspace = true
|
iced_winit.workspace = true
|
||||||
|
|
||||||
iced_devtools.workspace = true
|
iced_devtools.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@ where
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
Mode::Hidden => {
|
Mode::Hidden => {
|
||||||
self.mode = Mode::Open {
|
self.mode = Mode::Open {
|
||||||
tester: Tester::new(),
|
tester: Tester::new(program),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Mode::Open { tester } if !tester.is_busy() => {
|
Mode::Open { tester } if !tester.is_busy() => {
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,15 @@ use crate::test::emulator;
|
||||||
use crate::test::instruction;
|
use crate::test::instruction;
|
||||||
use crate::test::{Emulator, Instruction};
|
use crate::test::{Emulator, Instruction};
|
||||||
use crate::widget::{
|
use crate::widget::{
|
||||||
button, center, column, container, monospace, row, scrollable, text,
|
button, center, column, combo_box, container, monospace, pick_list, row,
|
||||||
text_input, themer,
|
scrollable, text, text_input, themer,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Tester<P: Program> {
|
pub struct Tester<P: Program> {
|
||||||
viewport: Size,
|
viewport: Size,
|
||||||
|
mode: emulator::Mode,
|
||||||
|
presets: combo_box::State<String>,
|
||||||
|
preset: Option<String>,
|
||||||
instructions: Vec<Instruction>,
|
instructions: Vec<Instruction>,
|
||||||
state: State<P>,
|
state: State<P>,
|
||||||
}
|
}
|
||||||
|
|
@ -42,6 +45,8 @@ enum State<P: Program> {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
ChangeViewport(Size),
|
ChangeViewport(Size),
|
||||||
|
ModeSelected(emulator::Mode),
|
||||||
|
PresetSelected(String),
|
||||||
Record,
|
Record,
|
||||||
Stop,
|
Stop,
|
||||||
Play,
|
Play,
|
||||||
|
|
@ -55,9 +60,19 @@ pub enum Tick<P: Program> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Program + 'static> Tester<P> {
|
impl<P: Program + 'static> Tester<P> {
|
||||||
pub fn new() -> Self {
|
pub fn new(program: &P) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
mode: emulator::Mode::default(),
|
||||||
viewport: Size::new(512.0, 512.0),
|
viewport: Size::new(512.0, 512.0),
|
||||||
|
presets: combo_box::State::new(
|
||||||
|
program
|
||||||
|
.presets()
|
||||||
|
.iter()
|
||||||
|
.map(program::Preset::name)
|
||||||
|
.map(str::to_owned)
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
preset: None,
|
||||||
instructions: Vec::new(),
|
instructions: Vec::new(),
|
||||||
state: State::Idle,
|
state: State::Idle,
|
||||||
}
|
}
|
||||||
|
|
@ -78,10 +93,25 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
Message::ModeSelected(mode) => {
|
||||||
|
self.mode = mode;
|
||||||
|
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::PresetSelected(preset) => {
|
||||||
|
self.preset = Some(preset);
|
||||||
|
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
Message::Record => {
|
Message::Record => {
|
||||||
self.instructions.clear();
|
self.instructions.clear();
|
||||||
|
|
||||||
let (state, task) = program.boot();
|
let (state, task) = if let Some(preset) = self.preset(program) {
|
||||||
|
preset.boot()
|
||||||
|
} else {
|
||||||
|
program.boot()
|
||||||
|
};
|
||||||
|
|
||||||
self.state = State::Recording { state };
|
self.state = State::Recording { state };
|
||||||
|
|
||||||
task.map(Tick::Program)
|
task.map(Tick::Program)
|
||||||
|
|
@ -93,7 +123,14 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
}
|
}
|
||||||
Message::Play => {
|
Message::Play => {
|
||||||
let (sender, receiver) = mpsc::channel(1);
|
let (sender, receiver) = mpsc::channel(1);
|
||||||
let emulator = Emulator::new(program, self.viewport, sender);
|
|
||||||
|
let emulator = Emulator::with_preset(
|
||||||
|
sender,
|
||||||
|
program,
|
||||||
|
self.mode,
|
||||||
|
self.viewport,
|
||||||
|
self.preset(program),
|
||||||
|
);
|
||||||
|
|
||||||
self.state = State::Playing {
|
self.state = State::Playing {
|
||||||
emulator,
|
emulator,
|
||||||
|
|
@ -105,6 +142,18 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn preset<'a>(
|
||||||
|
&self,
|
||||||
|
program: &'a P,
|
||||||
|
) -> Option<&'a program::Preset<P::State, P::Message>> {
|
||||||
|
self.preset.as_ref().and_then(|preset| {
|
||||||
|
program
|
||||||
|
.presets()
|
||||||
|
.iter()
|
||||||
|
.find(|candidate| candidate.name() == preset)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn tick(&mut self, program: &P, tick: Tick<P>) -> Task<Tick<P>> {
|
pub fn tick(&mut self, program: &P, tick: Tick<P>) -> Task<Tick<P>> {
|
||||||
match tick {
|
match tick {
|
||||||
Tick::Program(message) => {
|
Tick::Program(message) => {
|
||||||
|
|
@ -267,8 +316,25 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.align_y(Center);
|
.align_y(Center);
|
||||||
|
|
||||||
|
let preset = combo_box(
|
||||||
|
&self.presets,
|
||||||
|
"Default Preset",
|
||||||
|
self.preset.as_ref(),
|
||||||
|
Message::PresetSelected,
|
||||||
|
)
|
||||||
|
.size(14)
|
||||||
|
.width(Fill);
|
||||||
|
|
||||||
|
let mode = pick_list(
|
||||||
|
emulator::Mode::ALL,
|
||||||
|
Some(self.mode),
|
||||||
|
Message::ModeSelected,
|
||||||
|
)
|
||||||
|
.text_size(14)
|
||||||
|
.width(Fill);
|
||||||
|
|
||||||
let player = {
|
let player = {
|
||||||
let events = container(if self.instructions.is_empty() {
|
let instructions = container(if self.instructions.is_empty() {
|
||||||
Element::from(center(
|
Element::from(center(
|
||||||
monospace("No instructions recorded yet!")
|
monospace("No instructions recorded yet!")
|
||||||
.size(14)
|
.size(14)
|
||||||
|
|
@ -337,12 +403,17 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
};
|
};
|
||||||
|
|
||||||
column![events, controls].spacing(10).align_x(Center)
|
column![instructions, controls].spacing(10).align_x(Center)
|
||||||
};
|
};
|
||||||
|
|
||||||
column![labeled("Viewport", viewport), labeled("Tester", player)]
|
column![
|
||||||
.spacing(10)
|
labeled("Viewport", viewport),
|
||||||
.into()
|
labeled("Mode", mode),
|
||||||
|
labeled("Preset", preset),
|
||||||
|
labeled("Instructions", player)
|
||||||
|
]
|
||||||
|
.spacing(10)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ pub struct Tick<P: Program> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Program> Tester<P> {
|
impl<P: Program> Tester<P> {
|
||||||
pub fn new() -> Self {
|
pub fn new(_program: &P) -> Self {
|
||||||
Self { _type: PhantomData }
|
Self { _type: PhantomData }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,10 @@ authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
|
[features]
|
||||||
|
test = ["iced/test"]
|
||||||
|
tester = ["test", "iced/tester"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced.workspace = true
|
iced.workspace = true
|
||||||
iced.features = ["tokio", "debug", "time-travel"]
|
iced.features = ["tokio", "debug", "time-travel"]
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,16 @@ pub fn main() -> iced::Result {
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
iced::application(Todos::new, Todos::update, Todos::view)
|
let todos = iced::application(Todos::new, Todos::update, Todos::view)
|
||||||
.subscription(Todos::subscription)
|
.subscription(Todos::subscription)
|
||||||
.title(Todos::title)
|
.title(Todos::title)
|
||||||
.font(Todos::ICON_FONT)
|
.font(Todos::ICON_FONT)
|
||||||
.window_size((500.0, 800.0))
|
.window_size((500.0, 800.0));
|
||||||
.run()
|
|
||||||
|
#[cfg(feature = "test")]
|
||||||
|
let todos = todos.presets(presets());
|
||||||
|
|
||||||
|
todos.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -572,6 +576,39 @@ impl SavedState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "test")]
|
||||||
|
fn presets() -> impl Iterator<Item = iced::application::Preset<Todos, Message>>
|
||||||
|
{
|
||||||
|
use iced::application::Preset;
|
||||||
|
|
||||||
|
[
|
||||||
|
Preset::new("Empty", || {
|
||||||
|
(
|
||||||
|
Todos::Loading,
|
||||||
|
Command::done(Message::Loaded(Err(LoadError::File))),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
Preset::new("Basic", || {
|
||||||
|
(
|
||||||
|
Todos::Loaded(State {
|
||||||
|
input_value: "Bake an apple pie".to_owned(),
|
||||||
|
filter: Filter::All,
|
||||||
|
tasks: vec![Task {
|
||||||
|
id: Uuid::new_v4(),
|
||||||
|
description: "Create the universe".to_owned(),
|
||||||
|
completed: false,
|
||||||
|
state: TaskState::Idle,
|
||||||
|
}],
|
||||||
|
dirty: false,
|
||||||
|
saving: false,
|
||||||
|
}),
|
||||||
|
Command::none(),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
time-travel = []
|
time-travel = []
|
||||||
|
test = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced_graphics.workspace = true
|
iced_graphics.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,12 @@ 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;
|
||||||
|
|
||||||
|
#[cfg(feature = "test")]
|
||||||
|
mod preset;
|
||||||
|
|
||||||
|
#[cfg(feature = "test")]
|
||||||
|
pub use preset::Preset;
|
||||||
|
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::text;
|
use crate::core::text;
|
||||||
use crate::core::theme;
|
use crate::core::theme;
|
||||||
|
|
@ -100,6 +106,11 @@ pub trait Program: Sized {
|
||||||
fn scale_factor(&self, _state: &Self::State, _window: window::Id) -> f64 {
|
fn scale_factor(&self, _state: &Self::State, _window: window::Id) -> f64 {
|
||||||
1.0
|
1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "test")]
|
||||||
|
fn presets(&self) -> &[Preset<Self::State, Self::Message>] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decorates a [`Program`] with the given title function.
|
/// Decorates a [`Program`] with the given title function.
|
||||||
|
|
|
||||||
42
program/src/preset.rs
Normal file
42
program/src/preset.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
use crate::runtime::Task;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// A specific boot strategy for a [`Program`].
|
||||||
|
pub struct Preset<State, Message> {
|
||||||
|
name: Cow<'static, str>,
|
||||||
|
boot: Box<dyn Fn() -> (State, Task<Message>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<State, Message> Preset<State, Message> {
|
||||||
|
/// Creates a new [`Preset`] with the given name and boot strategy.
|
||||||
|
pub fn new(
|
||||||
|
name: impl Into<Cow<'static, str>>,
|
||||||
|
boot: impl Fn() -> (State, Task<Message>) + 'static,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
boot: Box::new(boot),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the name of the [`Preset`].
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Boots the [`Preset`], returning the initial [`Program`] state and
|
||||||
|
/// a [`Task`] for concurrent booting.
|
||||||
|
pub fn boot(&self) -> (State, Task<Message>) {
|
||||||
|
(self.boot)()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<State, Message> fmt::Debug for Preset<State, Message> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Preset")
|
||||||
|
.field("name", &self.name)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -42,6 +42,8 @@ use std::borrow::Cow;
|
||||||
|
|
||||||
pub mod timed;
|
pub mod timed;
|
||||||
|
|
||||||
|
#[cfg(feature = "test")]
|
||||||
|
pub use program::Preset;
|
||||||
pub use timed::timed;
|
pub use timed::timed;
|
||||||
|
|
||||||
/// Creates an iced [`Application`] given its boot, update, and view logic.
|
/// Creates an iced [`Application`] given its boot, update, and view logic.
|
||||||
|
|
@ -154,6 +156,9 @@ where
|
||||||
},
|
},
|
||||||
settings: Settings::default(),
|
settings: Settings::default(),
|
||||||
window: window::Settings::default(),
|
window: window::Settings::default(),
|
||||||
|
|
||||||
|
#[cfg(feature = "test")]
|
||||||
|
presets: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,6 +174,9 @@ pub struct Application<P: Program> {
|
||||||
raw: P,
|
raw: P,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
window: window::Settings,
|
window: window::Settings,
|
||||||
|
|
||||||
|
#[cfg(feature = "test")]
|
||||||
|
presets: Vec<Preset<P::State, P::Message>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Program> Application<P> {
|
impl<P: Program> Application<P> {
|
||||||
|
|
@ -338,6 +346,8 @@ impl<P: Program> Application<P> {
|
||||||
}),
|
}),
|
||||||
settings: self.settings,
|
settings: self.settings,
|
||||||
window: self.window,
|
window: self.window,
|
||||||
|
#[cfg(feature = "test")]
|
||||||
|
presets: self.presets,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -352,6 +362,8 @@ impl<P: Program> Application<P> {
|
||||||
raw: program::with_subscription(self.raw, f),
|
raw: program::with_subscription(self.raw, f),
|
||||||
settings: self.settings,
|
settings: self.settings,
|
||||||
window: self.window,
|
window: self.window,
|
||||||
|
#[cfg(feature = "test")]
|
||||||
|
presets: self.presets,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -366,6 +378,8 @@ impl<P: Program> Application<P> {
|
||||||
raw: program::with_theme(self.raw, move |state, _window| f(state)),
|
raw: program::with_theme(self.raw, move |state, _window| f(state)),
|
||||||
settings: self.settings,
|
settings: self.settings,
|
||||||
window: self.window,
|
window: self.window,
|
||||||
|
#[cfg(feature = "test")]
|
||||||
|
presets: self.presets,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -380,6 +394,8 @@ impl<P: Program> Application<P> {
|
||||||
raw: program::with_style(self.raw, f),
|
raw: program::with_style(self.raw, f),
|
||||||
settings: self.settings,
|
settings: self.settings,
|
||||||
window: self.window,
|
window: self.window,
|
||||||
|
#[cfg(feature = "test")]
|
||||||
|
presets: self.presets,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -396,6 +412,8 @@ impl<P: Program> Application<P> {
|
||||||
}),
|
}),
|
||||||
settings: self.settings,
|
settings: self.settings,
|
||||||
window: self.window,
|
window: self.window,
|
||||||
|
#[cfg(feature = "test")]
|
||||||
|
presets: self.presets,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -412,6 +430,24 @@ impl<P: Program> Application<P> {
|
||||||
raw: program::with_executor::<P, E>(self.raw),
|
raw: program::with_executor::<P, E>(self.raw),
|
||||||
settings: self.settings,
|
settings: self.settings,
|
||||||
window: self.window,
|
window: self.window,
|
||||||
|
#[cfg(feature = "test")]
|
||||||
|
presets: self.presets,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the boot presets of the [`Application`].
|
||||||
|
///
|
||||||
|
/// Presets can be used to override the default booting strategy
|
||||||
|
/// of your application during testing to create reproducible
|
||||||
|
/// environments.
|
||||||
|
#[cfg(feature = "test")]
|
||||||
|
pub fn presets(
|
||||||
|
self,
|
||||||
|
presets: impl IntoIterator<Item = Preset<P::State, P::Message>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
presets: presets.into_iter().collect(),
|
||||||
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -474,6 +510,11 @@ impl<P: Program> Program for Application<P> {
|
||||||
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 {
|
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 {
|
||||||
self.raw.scale_factor(state, window)
|
self.raw.scale_factor(state, window)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "test")]
|
||||||
|
fn presets(&self) -> &[Preset<Self::State, Self::Message>] {
|
||||||
|
&self.presets
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The logic to initialize the `State` of some [`Application`].
|
/// The logic to initialize the `State` of some [`Application`].
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,9 @@ where
|
||||||
},
|
},
|
||||||
settings: Settings::default(),
|
settings: Settings::default(),
|
||||||
window: window::Settings::default(),
|
window: window::Settings::default(),
|
||||||
|
|
||||||
|
#[cfg(feature = "test")]
|
||||||
|
presets: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -475,11 +475,11 @@
|
||||||
)]
|
)]
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
|
use iced_program as program;
|
||||||
use iced_widget::graphics;
|
use iced_widget::graphics;
|
||||||
use iced_widget::renderer;
|
use iced_widget::renderer;
|
||||||
use iced_winit as shell;
|
use iced_winit as shell;
|
||||||
use iced_winit::core;
|
use iced_winit::core;
|
||||||
use iced_winit::program;
|
|
||||||
use iced_winit::runtime;
|
use iced_winit::runtime;
|
||||||
|
|
||||||
pub use iced_futures::futures;
|
pub use iced_futures::futures;
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,9 @@ workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced_runtime.workspace = true
|
iced_runtime.workspace = true
|
||||||
|
|
||||||
iced_program.workspace = true
|
iced_program.workspace = true
|
||||||
|
iced_program.features = ["test"]
|
||||||
|
|
||||||
iced_renderer.workspace = true
|
iced_renderer.workspace = true
|
||||||
iced_renderer.features = ["fira-sans"]
|
iced_renderer.features = ["fira-sans"]
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@ use crate::Instruction;
|
||||||
use crate::core;
|
use crate::core;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::widget::operation;
|
use crate::core::widget;
|
||||||
use crate::core::window;
|
use crate::core::window;
|
||||||
use crate::core::{Element, Size};
|
use crate::core::{Element, Size};
|
||||||
|
use crate::program;
|
||||||
use crate::program::Program;
|
use crate::program::Program;
|
||||||
use crate::runtime::futures::futures::StreamExt;
|
use crate::runtime::futures::futures::StreamExt;
|
||||||
use crate::runtime::futures::futures::channel::mpsc;
|
use crate::runtime::futures::futures::channel::mpsc;
|
||||||
|
|
@ -15,11 +16,14 @@ use crate::runtime::task;
|
||||||
use crate::runtime::user_interface;
|
use crate::runtime::user_interface;
|
||||||
use crate::runtime::{Action, Task, UserInterface};
|
use crate::runtime::{Action, Task, UserInterface};
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Emulator<P: Program> {
|
pub struct Emulator<P: Program> {
|
||||||
state: P::State,
|
state: P::State,
|
||||||
runtime: Runtime<P::Executor, mpsc::Sender<Event<P>>, Event<P>>,
|
runtime: Runtime<P::Executor, mpsc::Sender<Event<P>>, Event<P>>,
|
||||||
renderer: P::Renderer,
|
renderer: P::Renderer,
|
||||||
|
mode: Mode,
|
||||||
size: Size,
|
size: Size,
|
||||||
window: window::Id,
|
window: window::Id,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
|
|
@ -35,9 +39,20 @@ pub enum Event<P: Program> {
|
||||||
|
|
||||||
impl<P: Program + 'static> Emulator<P> {
|
impl<P: Program + 'static> Emulator<P> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
program: &P,
|
|
||||||
size: Size,
|
|
||||||
sender: mpsc::Sender<Event<P>>,
|
sender: mpsc::Sender<Event<P>>,
|
||||||
|
program: &P,
|
||||||
|
mode: Mode,
|
||||||
|
size: Size,
|
||||||
|
) -> Emulator<P> {
|
||||||
|
Self::with_preset(sender, program, mode, size, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_preset(
|
||||||
|
sender: mpsc::Sender<Event<P>>,
|
||||||
|
program: &P,
|
||||||
|
mode: Mode,
|
||||||
|
size: Size,
|
||||||
|
preset: Option<&program::Preset<P::State, P::Message>>,
|
||||||
) -> Emulator<P> {
|
) -> Emulator<P> {
|
||||||
use renderer::Headless;
|
use renderer::Headless;
|
||||||
|
|
||||||
|
|
@ -55,12 +70,18 @@ impl<P: Program + 'static> Emulator<P> {
|
||||||
.expect("Create emulator renderer");
|
.expect("Create emulator renderer");
|
||||||
|
|
||||||
let runtime = Runtime::new(executor, sender);
|
let runtime = Runtime::new(executor, sender);
|
||||||
let (state, task) = program.boot();
|
|
||||||
|
let (state, task) = if let Some(preset) = preset {
|
||||||
|
preset.boot()
|
||||||
|
} else {
|
||||||
|
program.boot()
|
||||||
|
};
|
||||||
|
|
||||||
let mut emulator = Self {
|
let mut emulator = Self {
|
||||||
state,
|
state,
|
||||||
runtime,
|
runtime,
|
||||||
renderer,
|
renderer,
|
||||||
|
mode,
|
||||||
size,
|
size,
|
||||||
clipboard: Clipboard { content: None },
|
clipboard: Clipboard { content: None },
|
||||||
cursor: mouse::Cursor::Unavailable,
|
cursor: mouse::Cursor::Unavailable,
|
||||||
|
|
@ -68,8 +89,8 @@ impl<P: Program + 'static> Emulator<P> {
|
||||||
cache: Some(user_interface::Cache::default()),
|
cache: Some(user_interface::Cache::default()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Configurable
|
|
||||||
emulator.wait_for(task);
|
emulator.wait_for(task);
|
||||||
|
emulator.resubscribe(program);
|
||||||
|
|
||||||
emulator
|
emulator
|
||||||
}
|
}
|
||||||
|
|
@ -106,9 +127,9 @@ impl<P: Program + 'static> Emulator<P> {
|
||||||
user_interface.operate(&self.renderer, &mut current);
|
user_interface.operate(&self.renderer, &mut current);
|
||||||
|
|
||||||
match current.finish() {
|
match current.finish() {
|
||||||
operation::Outcome::None => {}
|
widget::operation::Outcome::None => {}
|
||||||
operation::Outcome::Some(()) => {}
|
widget::operation::Outcome::Some(()) => {}
|
||||||
operation::Outcome::Chain(next) => {
|
widget::operation::Outcome::Chain(next) => {
|
||||||
operation = Some(next);
|
operation = Some(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -174,20 +195,28 @@ impl<P: Program + 'static> Emulator<P> {
|
||||||
.map(|message| program.update(&mut self.state, message)),
|
.map(|message| program.update(&mut self.state, message)),
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Configurable
|
|
||||||
self.wait_for(task);
|
self.wait_for(task);
|
||||||
|
|
||||||
self.resubscribe(program);
|
self.resubscribe(program);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wait_for(&mut self, task: Task<P::Message>) {
|
pub fn wait_for(&mut self, task: Task<P::Message>) {
|
||||||
if let Some(stream) = task::into_stream(task) {
|
if let Some(stream) = task::into_stream(task) {
|
||||||
self.runtime.run(
|
match self.mode {
|
||||||
stream
|
Mode::Patient => {
|
||||||
.map(Event::Action)
|
self.runtime.run(
|
||||||
.chain(stream::once(async { Event::Ready }))
|
stream
|
||||||
.boxed(),
|
.map(Event::Action)
|
||||||
);
|
.chain(stream::once(async { Event::Ready }))
|
||||||
|
.boxed(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Mode::Impatient => {
|
||||||
|
self.runtime.run(stream.map(Event::Action).boxed());
|
||||||
|
self.runtime.send(Event::Ready);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.runtime.send(Event::Ready);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,6 +240,26 @@ impl<P: Program + 'static> Emulator<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
|
pub enum Mode {
|
||||||
|
Patient,
|
||||||
|
#[default]
|
||||||
|
Impatient,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mode {
|
||||||
|
pub const ALL: &[Self] = &[Self::Patient, Self::Impatient];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Mode {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Mode::Patient => f.write_str("Patient"),
|
||||||
|
Mode::Impatient => f.write_str("Impatient"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Clipboard {
|
struct Clipboard {
|
||||||
content: Option<String>,
|
content: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,8 @@ use crate::core::text;
|
||||||
use crate::core::time::Instant;
|
use crate::core::time::Instant;
|
||||||
use crate::core::widget::{self, Widget};
|
use crate::core::widget::{self, Widget};
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Clipboard, Element, Event, Length, Padding, Rectangle, Shell, Size, Theme,
|
Clipboard, Element, Event, Length, Padding, Pixels, Rectangle, Shell, Size,
|
||||||
Vector,
|
Theme, Vector,
|
||||||
};
|
};
|
||||||
use crate::overlay::menu;
|
use crate::overlay::menu;
|
||||||
use crate::text::LineHeight;
|
use crate::text::LineHeight;
|
||||||
|
|
@ -249,9 +249,12 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the text sixe of the [`ComboBox`].
|
/// Sets the text sixe of the [`ComboBox`].
|
||||||
pub fn size(mut self, size: f32) -> Self {
|
pub fn size(mut self, size: impl Into<Pixels>) -> Self {
|
||||||
|
let size = size.into();
|
||||||
|
|
||||||
self.text_input = self.text_input.size(size);
|
self.text_input = self.text_input.size(size);
|
||||||
self.size = Some(size);
|
self.size = Some(size.0);
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ workspace = true
|
||||||
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||||
debug = ["iced_debug/enable"]
|
debug = ["iced_debug/enable"]
|
||||||
system = ["sysinfo"]
|
system = ["sysinfo"]
|
||||||
program = []
|
|
||||||
x11 = ["winit/x11"]
|
x11 = ["winit/x11"]
|
||||||
wayland = ["winit/wayland"]
|
wayland = ["winit/wayland"]
|
||||||
wayland-dlopen = ["winit/wayland-dlopen"]
|
wayland-dlopen = ["winit/wayland-dlopen"]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue