From f878b59977c41269fe437b520d910ab1b2506fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 5 Jun 2025 15:11:25 +0200 Subject: [PATCH] Implement instruction editor for `tester` --- devtools/fonts/iced_devtools-icons.toml | 3 + devtools/fonts/iced_devtools-icons.ttf | Bin 7132 -> 7880 bytes devtools/src/icon.rs | 24 +++++ devtools/src/tester.rs | 117 ++++++++++++++++++++++-- 4 files changed, 134 insertions(+), 10 deletions(-) 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 a9c302594e70ecc73a200689d1148fe3e7e6cc76..301915f493b618ef98f9e45888619871135cb52b 100644 GIT binary patch delta 1480 zcmZ`(T})eb6hG(Q_TIMKe$aAnFHp*bOKC?LU|T2?*otH^l4T;%sPh9$TMAO>4KM<_ z*cTTw5|rq&ePC`exTu+0dOf$@i(hSj=iJ}# z|8dUw|Ih8s`1OIenhGbAy#P1@057CwlA5hWTp@l1fH|33Tu?}-kIO~IF`;E=#yft0 ziMT>k$R_7CV~~Cp@|J9Fc|xkMX$LS7<%cIT$#nR}wK0II6D0IZlEC+~&k=u>c+2F> z!qQVMi~64=tdS7Qg0r=a`-+KmkRv*U!dq~&WQT;_Y#jeA})ps%Q#0DTu2Ur8P z{M1g~MpbW6;%7*gs3sOTHKd*kTncQpUJg2fo}jpY`|fdF2l#J&b&wG|k=FkEOIrg& zJDR`@9PnTPYdIeG{vd+|3Md%az@@S+Gc{y{Ax?>^=PIBrAV6~r&}I;zIt>`7-FmqK z3IIVz1r!Q`o(d=^1jPy{JnY}DfD$Rd-Q!RO<%GXnErU*+{IxPF>CvVXb&pvACwRaM z_0R|^1R)Ha5QRAO!7&(u^~6U$ggV99Smoy%EG$)>ZK-$II5Si05-cW@nK5PMYP;2B zmTDNc$L!7)RByA`WcDC>p1l9)!7)G&KAK4M_QqmeU7?Vo$g<0YkQhn~Jv4ai=s+7*pF(AjagJroXw+YSX=11*ZGsLf4{{sy01FW1-AdL^;O<#Bo3 z)m9Fi=m>C97$cHcU8v#Irsme}>R3po+n0w;CtUzUOOe z^A$fZEbgWw>2&FtEqN03}*irC7L!!=s2zY|}k~wR1>RCWs4ArYFR~tr&=fdcbI45$goyc=) zbI>^BcuzFqWtQAFtNgXb3HFquj+@@7a`3z)@x0T9Lbd2E{bu)>+i0}7QN`N4?8CB^ zt)e_O75)siy!rIb^JC8bU%|=lkqAFzkM7?0-{}W>mi>_(UH#0`X|@~VtNWIkQI=jX z<{kY2zaig8_=0*_A>7hS#!}dZJLtj=9Kkp6Dwdda<_>$5{jmJ+V25X+-2GJOFuVq@ z)BI02hpjKO1v@gxq6y8&(WzU|iZ-l50qy7j%XDUWJfECPo3-TW`HX3PA+MRVTymM4 z%cSyiX-><`rlxY{)MO?#&83pFsZ7qAo61fujGxYp+h_8$=&+WbnqA1uaTB?`rY&<5 S`CK|PSI(GH!%fA$?f47EeKzs{ delta 726 zcmZuuOK4L;6umR^UVcrJlG-YMFk;dOYAqE|T7ey3YxNuPr0)ngR!rDz1E%+fm&*a&iGn~0|&i%~XnfF7VFI0|| zt41#o8z!QgiL4sry zy$8U1vqnj>eSv4-1K{DiM%G;Z;@JWp#vxZ^u25W@S-wu>e*yor%$st)K3FF@bn)?z zaaOfP*ih$!ZHue+Z5k6*dSX3i(Q<_t!ig41w0EaqedxME8cjg&!W2Zu7oscCdf#fS zJJ!?Ow(b8Z?K*o4YiFu#XQCu=RMJQ%7r8-QD%3LhFH3UWniQ_`{eQ_`Ro#KQQ*^}v z@+pdj6x5yiRvn-w#qiP!s7|pS2RMhC+YWFd+j-pvUKsXm8+bic6+ax}>$L~^QC2(R zj`P|l?;ML%Hq&n=gYjJoif=0BYjUE0&)O|}O?(%AtJHC3+^76J1dO7u9a#K1e`>c_ z19lLX4Fg;DAC*l5G(ve=qIXPVFS*9=KvRFksf~ggXIjPBvG3&~kiClil+6eBi?FK&v{sQ4?o)7>4 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() +}