Draft Instruction DSL in iced_test
This commit is contained in:
parent
327522eb99
commit
921467b5be
12 changed files with 1246 additions and 631 deletions
|
|
@ -1,7 +1,7 @@
|
|||
#![allow(missing_docs)]
|
||||
use iced_debug as debug;
|
||||
use iced_program as program;
|
||||
use iced_widget as widget;
|
||||
use iced_test as test;
|
||||
use iced_widget::core;
|
||||
use iced_widget::runtime;
|
||||
use iced_widget::runtime::futures;
|
||||
|
|
@ -10,6 +10,7 @@ mod comet;
|
|||
mod executor;
|
||||
mod icon;
|
||||
mod time_machine;
|
||||
mod widget;
|
||||
|
||||
use crate::core::alignment::Horizontal::Right;
|
||||
use crate::core::border;
|
||||
|
|
@ -24,6 +25,7 @@ use crate::futures::Subscription;
|
|||
use crate::program::Program;
|
||||
use crate::runtime::Task;
|
||||
use crate::runtime::font;
|
||||
use crate::test::instruction;
|
||||
use crate::time_machine::TimeMachine;
|
||||
use crate::widget::{
|
||||
Text, bottom_right, button, center, column, container, horizontal_space,
|
||||
|
|
@ -137,6 +139,8 @@ pub enum Message {
|
|||
ChangeWidth(String),
|
||||
ChangeHeight(String),
|
||||
Record,
|
||||
Stop,
|
||||
Recorded(core::Event),
|
||||
}
|
||||
|
||||
enum Mode {
|
||||
|
|
@ -145,9 +149,9 @@ enum Mode {
|
|||
Setup(Setup),
|
||||
}
|
||||
|
||||
enum Recorder {
|
||||
Idle { events: Vec<core::Event> },
|
||||
Recording { events: Vec<core::Event> },
|
||||
struct Recorder {
|
||||
instructions: Vec<test::Instruction>,
|
||||
is_recording: bool,
|
||||
}
|
||||
|
||||
enum Setup {
|
||||
|
|
@ -194,21 +198,19 @@ where
|
|||
Task::none()
|
||||
}
|
||||
Message::Toggle => {
|
||||
match self.mode {
|
||||
match &self.mode {
|
||||
Mode::Hidden => {
|
||||
self.mode = Mode::Open {
|
||||
recorder: Recorder::Idle { events: Vec::new() },
|
||||
recorder: Recorder {
|
||||
instructions: Vec::new(),
|
||||
is_recording: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
Mode::Open {
|
||||
recorder: Recorder::Idle { .. },
|
||||
} => {
|
||||
Mode::Open { recorder } if !recorder.is_recording => {
|
||||
self.mode = Mode::Hidden;
|
||||
}
|
||||
Mode::Setup(_)
|
||||
| Mode::Open {
|
||||
recorder: Recorder::Recording { .. },
|
||||
} => {}
|
||||
Mode::Setup(_) | Mode::Open { .. } => {}
|
||||
}
|
||||
|
||||
Task::none()
|
||||
|
|
@ -256,7 +258,6 @@ where
|
|||
.map(Message::Installing)
|
||||
.map(Event::Message)
|
||||
}
|
||||
|
||||
Message::Installing(Ok(installation)) => {
|
||||
let Mode::Setup(Setup::Running { logs }) = &mut self.mode
|
||||
else {
|
||||
|
|
@ -311,15 +312,61 @@ where
|
|||
Task::none()
|
||||
}
|
||||
Message::Record => {
|
||||
let (state, task) = program.boot();
|
||||
|
||||
self.state = state;
|
||||
self.mode = Mode::Open {
|
||||
recorder: Recorder::Recording { events: Vec::new() },
|
||||
let Mode::Open { recorder } = &mut self.mode else {
|
||||
return Task::none();
|
||||
};
|
||||
|
||||
recorder.instructions.clear();
|
||||
recorder.is_recording = true;
|
||||
|
||||
let (state, task) = program.boot();
|
||||
self.state = state;
|
||||
|
||||
task.map(Event::Program)
|
||||
}
|
||||
Message::Recorded(event) => {
|
||||
let Mode::Open { recorder } = &mut self.mode else {
|
||||
return Task::none();
|
||||
};
|
||||
|
||||
let Some(interaction) =
|
||||
instruction::Interaction::from_event(event)
|
||||
else {
|
||||
return Task::none();
|
||||
};
|
||||
|
||||
if let Some(test::Instruction::Interact(last_interaction)) =
|
||||
recorder.instructions.pop()
|
||||
{
|
||||
let (last_interaction, new_interaction) =
|
||||
last_interaction.merge(interaction);
|
||||
|
||||
recorder.instructions.push(
|
||||
test::Instruction::Interact(last_interaction),
|
||||
);
|
||||
|
||||
if let Some(new_interaction) = new_interaction {
|
||||
recorder.instructions.push(
|
||||
test::Instruction::Interact(new_interaction),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
recorder
|
||||
.instructions
|
||||
.push(test::Instruction::Interact(interaction));
|
||||
}
|
||||
|
||||
Task::none()
|
||||
}
|
||||
Message::Stop => {
|
||||
let Mode::Open { recorder } = &mut self.mode else {
|
||||
return Task::none();
|
||||
};
|
||||
|
||||
recorder.is_recording = false;
|
||||
|
||||
Task::none()
|
||||
}
|
||||
},
|
||||
Event::Program(message) => {
|
||||
self.time_machine.push(&message);
|
||||
|
|
@ -417,41 +464,43 @@ where
|
|||
let title = monospace("Developer Tools");
|
||||
|
||||
let recorder = {
|
||||
let events = center(match recorder {
|
||||
Recorder::Idle { events } if events.is_empty() => {
|
||||
monospace("No events recorded yet!")
|
||||
let events = container(if recorder.instructions.is_empty() {
|
||||
Element::from(center(
|
||||
monospace("No instructions recorded yet!")
|
||||
.size(14)
|
||||
.width(Fill)
|
||||
.center()
|
||||
}
|
||||
Recorder::Idle { events }
|
||||
| Recorder::Recording { events } => {
|
||||
monospace(format!("{} events recorded", events.len()))
|
||||
}
|
||||
.center(),
|
||||
))
|
||||
} else {
|
||||
scrollable(
|
||||
column(recorder.instructions.iter().map(
|
||||
|instruction| {
|
||||
monospace(instruction.to_string())
|
||||
.size(10)
|
||||
.into()
|
||||
},
|
||||
))
|
||||
.spacing(5),
|
||||
)
|
||||
.spacing(5)
|
||||
.into()
|
||||
})
|
||||
.style(container::bordered_box);
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.style(container::rounded_box)
|
||||
.padding(5);
|
||||
|
||||
let controls = {
|
||||
row![
|
||||
button(icon::play().size(14).width(Fill).center()),
|
||||
match recorder {
|
||||
Recorder::Idle { .. } => {
|
||||
button(
|
||||
icon::record()
|
||||
.size(14)
|
||||
.width(Fill)
|
||||
.center(),
|
||||
)
|
||||
if recorder.is_recording {
|
||||
button(icon::stop().size(14).width(Fill).center())
|
||||
.on_press(Message::Stop)
|
||||
.style(button::success)
|
||||
} else {
|
||||
button(icon::record().size(14).width(Fill).center())
|
||||
.on_press(Message::Record)
|
||||
.style(button::danger)
|
||||
}
|
||||
Recorder::Recording { .. } => {
|
||||
button(
|
||||
icon::stop().size(14).width(Fill).center(),
|
||||
)
|
||||
.on_press(Message::Record)
|
||||
.style(button::success)
|
||||
}
|
||||
}
|
||||
]
|
||||
.spacing(10)
|
||||
|
|
@ -491,7 +540,7 @@ where
|
|||
};
|
||||
|
||||
let content = row![if let Mode::Open { recorder } = &self.mode {
|
||||
let is_recording = matches!(recorder, Recorder::Recording { .. });
|
||||
let is_recording = recorder.is_recording;
|
||||
|
||||
let status = if is_recording {
|
||||
monospace("Recording").style(|theme| text::Style {
|
||||
|
|
@ -507,9 +556,17 @@ where
|
|||
|
||||
let viewport = container(
|
||||
scrollable(
|
||||
container(view)
|
||||
.width(self.size.width)
|
||||
.height(self.size.height),
|
||||
container(if recorder.is_recording {
|
||||
widget::recorder(view)
|
||||
.on_event(|event| {
|
||||
Event::Message(Message::Recorded(event))
|
||||
})
|
||||
.into()
|
||||
} else {
|
||||
view
|
||||
})
|
||||
.width(self.size.width)
|
||||
.height(self.size.height),
|
||||
)
|
||||
.direction(scrollable::Direction::Both {
|
||||
vertical: scrollable::Scrollbar::default(),
|
||||
|
|
@ -673,7 +730,7 @@ where
|
|||
your iced applications.",
|
||||
column![
|
||||
"Do you wish to install it with the \
|
||||
following command?",
|
||||
following command?",
|
||||
command
|
||||
]
|
||||
.spacing(10),
|
||||
|
|
|
|||
12
devtools/src/widget.rs
Normal file
12
devtools/src/widget.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
mod recorder;
|
||||
|
||||
pub use iced_widget::*;
|
||||
pub use recorder::Recorder;
|
||||
|
||||
use crate::core::Element;
|
||||
|
||||
pub fn recorder<'a, Message, Theme, Renderer>(
|
||||
content: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||
) -> Recorder<'a, Message, Theme, Renderer> {
|
||||
Recorder::new(content)
|
||||
}
|
||||
178
devtools/src/widget/recorder.rs
Normal file
178
devtools/src/widget/recorder.rs
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
use crate::core::layout;
|
||||
use crate::core::mouse;
|
||||
use crate::core::renderer;
|
||||
use crate::core::widget;
|
||||
use crate::core::widget::tree;
|
||||
use crate::core::{
|
||||
self, Clipboard, Element, Event, Layout, Length, Point, Rectangle, Shell,
|
||||
Size, Widget,
|
||||
};
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Recorder<'a, Message, Theme, Renderer> {
|
||||
content: Element<'a, Message, Theme, Renderer>,
|
||||
on_event: Option<Box<dyn Fn(Event) -> Message + 'a>>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> Recorder<'a, Message, Theme, Renderer> {
|
||||
pub fn new(
|
||||
content: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
content: content.into(),
|
||||
on_event: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_event(
|
||||
mut self,
|
||||
on_event: impl Fn(Event) -> Message + 'a,
|
||||
) -> Self {
|
||||
self.on_event = Some(Box::new(on_event));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||
for Recorder<'_, Message, Theme, Renderer>
|
||||
where
|
||||
Renderer: core::Renderer,
|
||||
{
|
||||
fn update(
|
||||
&mut self,
|
||||
state: &mut widget::Tree,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
if shell.is_event_captured() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.content.as_widget_mut().update(
|
||||
state, event, layout, cursor, renderer, clipboard, shell, viewport,
|
||||
);
|
||||
|
||||
if let Some(on_event) = &self.on_event {
|
||||
match event {
|
||||
Event::Mouse(event) => {
|
||||
if !cursor.is_over(layout.bounds()) {
|
||||
return;
|
||||
}
|
||||
|
||||
match event {
|
||||
mouse::Event::ButtonPressed(_)
|
||||
| mouse::Event::ButtonReleased(_)
|
||||
| mouse::Event::WheelScrolled { .. } => {
|
||||
shell
|
||||
.publish(on_event(Event::Mouse(event.clone())));
|
||||
}
|
||||
mouse::Event::CursorMoved { position } => {
|
||||
shell.publish(on_event(Event::Mouse(
|
||||
mouse::Event::CursorMoved {
|
||||
position: *position
|
||||
- (layout.bounds().position()
|
||||
- Point::ORIGIN),
|
||||
},
|
||||
)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Event::Keyboard(event) => {
|
||||
shell.publish(on_event(Event::Keyboard(event.clone())));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tag(&self) -> tree::Tag {
|
||||
self.content.as_widget().tag()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
self.content.as_widget().state()
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<widget::Tree> {
|
||||
self.content.as_widget().children()
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut tree::Tree) {
|
||||
self.content.as_widget().diff(tree);
|
||||
}
|
||||
|
||||
fn size(&self) -> Size<Length> {
|
||||
self.content.as_widget().size()
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> Size<Length> {
|
||||
self.content.as_widget().size_hint()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &mut widget::Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.content.as_widget().layout(tree, renderer, limits)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &widget::Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
self.content
|
||||
.as_widget()
|
||||
.draw(tree, renderer, theme, style, layout, cursor, viewport);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
state: &widget::Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.content
|
||||
.as_widget()
|
||||
.mouse_interaction(state, layout, cursor, viewport, renderer)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
state: &mut widget::Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn widget::Operation,
|
||||
) {
|
||||
self.content
|
||||
.as_widget()
|
||||
.operate(state, layout, renderer, operation);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> From<Recorder<'a, Message, Theme, Renderer>>
|
||||
for Element<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
Theme: 'a,
|
||||
Renderer: core::Renderer + 'a,
|
||||
{
|
||||
fn from(recorder: Recorder<'a, Message, Theme, Renderer>) -> Self {
|
||||
Element::new(recorder)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue