diff --git a/devtools/fonts/iced_devtools-icons.toml b/devtools/fonts/iced_devtools-icons.toml index 9444ede4..44613c4e 100644 --- a/devtools/fonts/iced_devtools-icons.toml +++ b/devtools/fonts/iced_devtools-icons.toml @@ -10,3 +10,6 @@ check = "entypo-check" cancel = "entypo-cancel" folder = "entypo-folder" floppy = "entypo-floppy" +pencil = "entypo-pencil" +mouse_pointer = "fontawesome-mouse-pointer" +keyboard = "entypo-keyboard" diff --git a/devtools/fonts/iced_devtools-icons.ttf b/devtools/fonts/iced_devtools-icons.ttf index a9c30259..301915f4 100644 Binary files a/devtools/fonts/iced_devtools-icons.ttf and b/devtools/fonts/iced_devtools-icons.ttf differ diff --git a/devtools/src/icon.rs b/devtools/src/icon.rs index 63d32ecc..811b1d23 100644 --- a/devtools/src/icon.rs +++ b/devtools/src/icon.rs @@ -37,6 +37,14 @@ where icon("\u{1F4C1}") } +pub fn keyboard<'a, Theme, Renderer>() -> Text<'a, Theme, Renderer> +where + Theme: text::Catalog + 'a, + Renderer: program::Renderer, +{ + icon("\u{2328}") +} + pub fn lightbulb<'a, Theme, Renderer>() -> Text<'a, Theme, Renderer> where Theme: text::Catalog + 'a, @@ -45,6 +53,14 @@ where icon("\u{F0EB}") } +pub fn mouse_pointer<'a, Theme, Renderer>() -> Text<'a, Theme, Renderer> +where + Theme: text::Catalog + 'a, + Renderer: program::Renderer, +{ + icon("\u{F245}") +} + pub fn pause<'a, Theme, Renderer>() -> Text<'a, Theme, Renderer> where Theme: text::Catalog + 'a, @@ -53,6 +69,14 @@ where icon("\u{2389}") } +pub fn pencil<'a, Theme, Renderer>() -> Text<'a, Theme, Renderer> +where + Theme: text::Catalog + 'a, + Renderer: program::Renderer, +{ + icon("\u{270E}") +} + pub fn play<'a, Theme, Renderer>() -> Text<'a, Theme, Renderer> where Theme: text::Catalog + 'a, diff --git a/devtools/src/tester.rs b/devtools/src/tester.rs index ac52b5c4..b7824dbc 100644 --- a/devtools/src/tester.rs +++ b/devtools/src/tester.rs @@ -8,7 +8,7 @@ use crate::core::Length::Fill; use crate::core::alignment::Horizontal::Right; use crate::core::border; use crate::core::window; -use crate::core::{Element, Event, Size, Theme}; +use crate::core::{Element, Event, Font, Size, Theme}; use crate::executor; use crate::futures::Subscription; use crate::futures::futures::channel::mpsc; @@ -19,8 +19,8 @@ use crate::test::emulator; use crate::test::instruction; use crate::test::{Emulator, Instruction}; use crate::widget::{ - button, center, column, combo_box, container, monospace, pick_list, row, - scrollable, text, text_input, themer, + button, center, column, combo_box, container, horizontal_space, monospace, + pick_list, row, scrollable, text, text_editor, text_input, themer, }; pub struct Tester { @@ -30,6 +30,7 @@ pub struct Tester { preset: Option, instructions: Vec, state: State

, + edit: Option>, } enum State { @@ -64,6 +65,9 @@ pub enum Message { Import, Export, Imported(Option), + Edit, + Edited(text_editor::Action), + Confirm, } #[allow(missing_debug_implementations)] @@ -90,6 +94,7 @@ impl Tester

{ preset: None, instructions: Vec::new(), state: State::Idle, + edit: None, } } @@ -126,6 +131,7 @@ impl Tester

{ Task::none() } Message::Record => { + self.edit = None; self.instructions.clear(); let (state, task) = if let Some(preset) = self.preset(program) { @@ -150,6 +156,8 @@ impl Tester

{ Task::none() } Message::Play => { + self.confirm(); + let (sender, receiver) = mpsc::channel(1); let emulator = Emulator::with_preset( @@ -189,6 +197,8 @@ impl Tester

{ use std::fs; use std::thread; + self.confirm(); + let test: Vec<_> = self .instructions .iter() @@ -221,17 +231,60 @@ impl Tester

{ match instructions { Ok(instructions) => { self.instructions = instructions; + self.edit = None; } Err(error) => { log::error!("{error}"); } } + Task::none() + } + Message::Edit => { + if self.is_busy() { + return Task::none(); + } + + self.edit = Some(text_editor::Content::with_text( + &self + .instructions + .iter() + .map(Instruction::to_string) + .collect::>() + .join("\n"), + )); + + Task::none() + } + Message::Edited(action) => { + if let Some(edit) = &mut self.edit { + edit.perform(action); + } + + Task::none() + } + Message::Confirm => { + self.confirm(); + Task::none() } } } + fn confirm(&mut self) { + let Some(edit) = &mut self.edit else { + return; + }; + + self.instructions = edit + .lines() + .filter(|line| !line.text.trim().is_empty()) + .filter_map(|line| Instruction::parse(&line.text).ok()) + .collect(); + + self.edit = None; + } + fn preset<'a>( &self, program: &'a P, @@ -472,7 +525,14 @@ impl Tester

{ .width(Fill); let player = { - let instructions = container(if self.instructions.is_empty() { + let instructions = if let Some(edit) = &self.edit { + text_editor(edit) + .size(12) + .height(Fill) + .font(Font::MONOSPACE) + .on_action(Message::Edited) + .into() + } else if self.instructions.is_empty() { Element::from(center( monospace("No instructions recorded yet!") .size(14) @@ -497,7 +557,6 @@ impl Tester

{ Outcome::Running => { theme.palette().primary } - Outcome::Failed => { theme .extended_palette() @@ -533,12 +592,11 @@ impl Tester

{ )) .spacing(5), ) + .width(Fill) + .height(Fill) .spacing(5) .into() - }) - .width(Fill) - .height(Fill) - .padding(5); + }; let control = |icon: text::Text<'static, _, _>| { button(icon.size(14).width(Fill).height(Fill).center()) @@ -580,11 +638,27 @@ impl Tester

{ column![instructions, controls].spacing(10).align_x(Center) }; + let edit = if self.is_busy() { + Element::from(horizontal_space()) + } else if self.edit.is_none() { + button(icon::pencil().size(14)) + .padding(0) + .on_press(Message::Edit) + .style(button::text) + .into() + } else { + button(icon::check().size(14)) + .padding(0) + .on_press(Message::Confirm) + .style(button::text) + .into() + }; + column![ labeled("Viewport", viewport), labeled("Mode", mode), labeled("Preset", preset), - labeled("Instructions", player) + labeled_with("Instructions", edit, player) ] .spacing(10) .into() @@ -603,3 +677,26 @@ where .spacing(5) .into() } + +fn labeled_with<'a, Message, Renderer>( + fragment: impl text::IntoFragment<'a>, + control: impl Into>, + content: impl Into>, +) -> Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Renderer: program::Renderer + 'a, +{ + column![ + row![ + monospace(fragment).size(14), + horizontal_space(), + control.into() + ] + .spacing(5) + .align_y(Center), + content.into() + ] + .spacing(5) + .into() +}