From ef16ea3b2ac11a1a45ce92497e047b0a81cbd006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 28 Apr 2025 22:31:13 +0200 Subject: [PATCH] Tweak and organize `devtools` crate --- core/src/renderer/null.rs | 1 + core/src/text.rs | 5 + core/src/theme.rs | 14 +- devtools/src/comet.rs | 88 +++++++++---- devtools/src/lib.rs | 260 +++++++++++++++++++++++--------------- renderer/src/fallback.rs | 1 + tiny_skia/src/lib.rs | 1 + wgpu/src/lib.rs | 1 + 8 files changed, 239 insertions(+), 132 deletions(-) diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index bf474b58..92fdd660 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -31,6 +31,7 @@ impl text::Renderer for () { type Paragraph = (); type Editor = (); + const MONOSPACE_FONT: Font = Font::MONOSPACE; const ICON_FONT: Font = Font::DEFAULT; const CHECKMARK_ICON: char = '0'; const ARROW_DOWN_ICON: char = '0'; diff --git a/core/src/text.rs b/core/src/text.rs index 79911b62..e2e75e58 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -232,6 +232,11 @@ pub trait Renderer: crate::Renderer { /// The [`Editor`] of this [`Renderer`]. type Editor: Editor + 'static; + /// A monospace font. + /// + /// It may be used by devtools. + const MONOSPACE_FONT: Self::Font; + /// The icon font of the backend. const ICON_FONT: Self::Font; diff --git a/core/src/theme.rs b/core/src/theme.rs index a0ec538b..0adf9ab2 100644 --- a/core/src/theme.rs +++ b/core/src/theme.rs @@ -5,6 +5,7 @@ pub use palette::Palette; use crate::Color; +use std::borrow::Cow; use std::fmt; use std::sync::Arc; @@ -87,14 +88,17 @@ impl Theme { ]; /// Creates a new custom [`Theme`] from the given [`Palette`]. - pub fn custom(name: String, palette: Palette) -> Self { + pub fn custom( + name: impl Into>, + palette: Palette, + ) -> Self { Self::custom_with_fn(name, palette, palette::Extended::generate) } /// Creates a new custom [`Theme`] from the given [`Palette`], with /// a custom generator of a [`palette::Extended`]. pub fn custom_with_fn( - name: String, + name: impl Into>, palette: Palette, generate: impl FnOnce(Palette) -> palette::Extended, ) -> Self { @@ -220,7 +224,7 @@ impl fmt::Display for Theme { /// A [`Theme`] with a customized [`Palette`]. #[derive(Debug, Clone, PartialEq)] pub struct Custom { - name: String, + name: Cow<'static, str>, palette: Palette, extended: palette::Extended, } @@ -234,12 +238,12 @@ impl Custom { /// Creates a [`Custom`] theme from the given [`Palette`] with /// a custom generator of a [`palette::Extended`]. pub fn with_fn( - name: String, + name: impl Into>, palette: Palette, generate: impl FnOnce(Palette) -> palette::Extended, ) -> Self { Self { - name, + name: name.into(), palette, extended: generate(palette), } diff --git a/devtools/src/comet.rs b/devtools/src/comet.rs index 941e4f61..2372899f 100644 --- a/devtools/src/comet.rs +++ b/devtools/src/comet.rs @@ -1,14 +1,12 @@ use crate::executor; use crate::runtime::Task; -use std::io; use std::process; -use std::sync::Arc; pub const COMPATIBLE_REVISION: &str = "69dd2283886dccdaa1ee6e1c274af62f7250bc38"; -pub fn launch() -> Task> { +pub fn launch() -> Task { executor::try_spawn_blocking(|mut sender| { let cargo_install = process::Command::new("cargo") .args(["install", "--list"]) @@ -22,15 +20,15 @@ pub fn launch() -> Task> { } let Some((_, revision)) = line.rsplit_once("?rev=") else { - return Err(Error::Outdated { revision: None }); + return Err(launch::Error::Outdated { revision: None }); }; let Some((revision, _)) = revision.rsplit_once("#") else { - return Err(Error::Outdated { revision: None }); + return Err(launch::Error::Outdated { revision: None }); }; if revision != COMPATIBLE_REVISION { - return Err(Error::Outdated { + return Err(launch::Error::Outdated { revision: Some(revision.to_owned()), }); } @@ -45,16 +43,16 @@ pub fn launch() -> Task> { return Ok(()); } - Err(Error::NotFound) + Err(launch::Error::NotFound) }) } -pub fn install() -> Task> { +pub fn install() -> Task { executor::try_spawn_blocking(|mut sender| { use std::io::{BufRead, BufReader}; use std::process::{Command, Stdio}; - let install = Command::new("cargo") + let mut install = Command::new("cargo") .args([ "install", "--locked", @@ -68,41 +66,75 @@ pub fn install() -> Task> { .stderr(Stdio::piped()) .spawn()?; - let mut stderr = - BufReader::new(install.stderr.expect("stderr must be piped")); + let mut stderr = BufReader::new( + install.stderr.take().expect("stderr must be piped"), + ); let mut log = String::new(); while let Ok(n) = stderr.read_line(&mut log) { if n == 0 { - break; + let status = install.wait()?; + + if status.success() { + break; + } else { + return Err(install::Error::ProcessFailed(status)); + } } - let _ = sender.try_send(Installation::Logged(log.clone())); + let _ = sender.try_send(install::Event::Logged(log.clone())); log.clear(); } - let _ = sender.try_send(Installation::Finished); + let _ = sender.try_send(install::Event::Finished); Ok(()) }) } -#[derive(Debug, Clone)] -pub enum Installation { - Logged(String), - Finished, -} +pub mod launch { + use std::io; + use std::sync::Arc; -#[derive(Debug, Clone)] -pub enum Error { - NotFound, - Outdated { revision: Option }, - IoFailed(Arc), -} + pub type Result = std::result::Result<(), Error>; -impl From for Error { - fn from(error: io::Error) -> Self { - Self::IoFailed(Arc::new(error)) + #[derive(Debug, Clone)] + pub enum Error { + NotFound, + Outdated { revision: Option }, + IoFailed(Arc), + } + + impl From for Error { + fn from(error: io::Error) -> Self { + Self::IoFailed(Arc::new(error)) + } + } +} + +pub mod install { + use std::io; + use std::process; + use std::sync::Arc; + + pub type Result = std::result::Result; + + #[derive(Debug, Clone)] + pub enum Event { + Logged(String), + Finished, + } + + #[derive(Debug, Clone)] + pub enum Error { + ProcessFailed(process::ExitStatus), + IoFailed(Arc), + } + + impl From for Error { + fn from(error: io::Error) -> Self { + Self::IoFailed(Arc::new(error)) + } } } diff --git a/devtools/src/lib.rs b/devtools/src/lib.rs index 18c118bf..95ac3626 100644 --- a/devtools/src/lib.rs +++ b/devtools/src/lib.rs @@ -10,11 +10,12 @@ mod comet; mod executor; mod time_machine; +use crate::core::border; use crate::core::keyboard; use crate::core::theme::{self, Base, Theme}; use crate::core::time::seconds; use crate::core::window; -use crate::core::{Color, Element, Length::Fill}; +use crate::core::{Alignment::Center, Color, Element, Length::Fill}; use crate::futures::Subscription; use crate::program::Program; use crate::runtime::Task; @@ -124,9 +125,9 @@ where enum Message { HideNotification, ToggleComet, - CometLaunched(Result<(), comet::Error>), + CometLaunched(comet::launch::Result), InstallComet, - InstallationProgressed(Result), + Installing(comet::install::Result), CancelSetup, } @@ -195,17 +196,17 @@ where Message::CometLaunched(Ok(())) => Task::none(), Message::CometLaunched(Err(error)) => { match error { - comet::Error::NotFound => { + comet::launch::Error::NotFound => { self.mode = Mode::Setup(Setup::Idle { goal: Goal::Installation, }); } - comet::Error::Outdated { revision } => { + comet::launch::Error::Outdated { revision } => { self.mode = Mode::Setup(Setup::Idle { goal: Goal::Update { revision }, }); } - comet::Error::IoFailed(error) => { + comet::launch::Error::IoFailed(error) => { log::error!("comet failed to run: {error}"); } } @@ -217,29 +218,42 @@ where Mode::Setup(Setup::Running { logs: Vec::new() }); comet::install() - .map(Message::InstallationProgressed) + .map(Message::Installing) .map(Event::Message) } - Message::InstallationProgressed(Ok(installation)) => { + Message::Installing(Ok(installation)) => { let Mode::Setup(Setup::Running { logs }) = &mut self.mode else { return Task::none(); }; match installation { - comet::Installation::Logged(log) => { + comet::install::Event::Logged(log) => { logs.push(log); Task::none() } - comet::Installation::Finished => { + comet::install::Event::Finished => { self.mode = Mode::None; comet::launch().discard() } } } - Message::InstallationProgressed(_error) => { - // TODO + Message::Installing(Err(error)) => { + let Mode::Setup(Setup::Running { logs }) = &mut self.mode + else { + return Task::none(); + }; + + match error { + comet::install::Error::ProcessFailed(status) => { + logs.push(format!("process failed with {status}")); + } + comet::install::Error::IoFailed(error) => { + logs.push(error.to_string()); + } + } + Task::none() } Message::CancelSetup => { @@ -304,7 +318,7 @@ where let derive_theme = move || { theme .palette() - .map(|palette| Theme::custom("DevTools".to_owned(), palette)) + .map(|palette| Theme::custom("iced devtools", palette)) .unwrap_or_default() }; @@ -312,97 +326,14 @@ where Mode::None => None, Mode::Setup(setup) => { let stage: Element<'_, _, Theme, P::Renderer> = match setup { - Setup::Idle { goal } => { - let controls = row![ - button(text("Cancel").center().width(Fill)) - .width(100) - .on_press(Message::CancelSetup) - .style(button::danger), - horizontal_space(), - button( - text(match goal { - Goal::Installation => "Install", - Goal::Update { .. } => "Update", - }) - .center() - .width(Fill) - ) - .width(100) - .on_press(Message::InstallComet) - .style(button::success), - ]; - - let command = container( - text( - "cargo install --locked \ - --git https://github.com/iced-rs/comet.git", - ) - .size(14), - ) - .width(Fill) - .padding(5) - .style(container::dark); - - match goal { - Goal::Installation => column![ - text("comet is not installed!").size(20), - "In order to display performance \ - metrics, the comet debugger must \ - be installed in your system.", - "The comet debugger is an official \ - companion tool that helps you debug \ - your iced applications.", - "Do you wish to install it with the \ - following command?", - command, - controls, - ] - .spacing(20), - Goal::Update { revision } => column![ - text("comet is out of date!").size(20), - text!( - "The installed revision is \"{current}\", \ - but the latest compatible is \"{compatible}\".", - current = revision - .as_deref() - .unwrap_or("Unknown"), - compatible = comet::COMPATIBLE_REVISION, - ), - "Do you wish to update it with the following \ - command?", - command, - controls, - ] - .spacing(20), - } - .into() - } - Setup::Running { logs } => column![ - text("Installing comet...").size(20), - container( - scrollable( - column( - logs.iter() - .map(|log| text(log).size(12).into()), - ) - .spacing(3), - ) - .spacing(10) - .width(Fill) - .height(300) - .anchor_bottom(), - ) - .padding(10) - .style(container::dark) - ] - .spacing(20) - .into(), + Setup::Idle { goal } => self::setup(goal), + Setup::Running { logs } => installation(logs), }; let setup = center( container(stage) .padding(20) - .width(500) + .max_width(500) .style(container::bordered_box), ) .padding(10) @@ -510,3 +441,134 @@ where } } } + +fn setup(goal: &Goal) -> Element<'_, Message, Theme, Renderer> +where + Renderer: core::text::Renderer + 'static, +{ + let controls = row![ + button(text("Cancel").center().width(Fill)) + .width(100) + .on_press(Message::CancelSetup) + .style(button::danger), + horizontal_space(), + button( + text(match goal { + Goal::Installation => "Install", + Goal::Update { .. } => "Update", + }) + .center() + .width(Fill) + ) + .width(100) + .on_press(Message::InstallComet) + .style(button::success), + ]; + + let command = container( + text!( + "cargo install --locked \\ + --git https://github.com/iced-rs/comet.git \\ + --rev {}", + comet::COMPATIBLE_REVISION + ) + .size(14) + .font(Renderer::MONOSPACE_FONT), + ) + .width(Fill) + .padding(5) + .style(container::dark); + + Element::from(match goal { + Goal::Installation => column![ + text("comet is not installed!").size(20), + "In order to display performance \ + metrics, the comet debugger must \ + be installed in your system.", + "The comet debugger is an official \ + companion tool that helps you debug \ + your iced applications.", + column![ + "Do you wish to install it with the \ + following command?", + command + ] + .spacing(10), + controls, + ] + .spacing(20), + Goal::Update { revision } => { + let comparison = column![ + row![ + "Installed revision:", + horizontal_space(), + inline_code(revision.as_deref().unwrap_or("Unknown")) + ] + .align_y(Center), + row![ + "Compatible revision:", + horizontal_space(), + inline_code(comet::COMPATIBLE_REVISION), + ] + .align_y(Center) + ] + .spacing(5); + + column![ + text("comet is out of date!").size(20), + comparison, + column![ + "Do you wish to update it with the following \ + command?", + command + ] + .spacing(10), + controls, + ] + .spacing(20) + } + }) +} + +fn installation<'a, Renderer>( + logs: &'a [String], +) -> Element<'a, Message, Theme, Renderer> +where + Renderer: core::text::Renderer + 'a, +{ + column![ + text("Installing comet...").size(20), + container( + scrollable( + column(logs.iter().map(|log| { + text(log).size(12).font(Renderer::MONOSPACE_FONT).into() + }),) + .spacing(3), + ) + .spacing(10) + .width(Fill) + .height(300) + .anchor_bottom(), + ) + .padding(10) + .style(container::dark) + ] + .spacing(20) + .into() +} + +fn inline_code<'a, Renderer>( + code: impl text::IntoFragment<'a>, +) -> Element<'a, Message, Theme, Renderer> +where + Renderer: core::text::Renderer + 'a, +{ + container(text(code).font(Renderer::MONOSPACE_FONT).size(12)) + .style(|_theme| { + container::Style::default() + .background(Color::BLACK) + .border(border::rounded(2)) + }) + .padding([2, 4]) + .into() +} diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index 4cea1a15..7223f06f 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -84,6 +84,7 @@ where type Paragraph = A::Paragraph; type Editor = A::Editor; + const MONOSPACE_FONT: Self::Font = A::MONOSPACE_FONT; const ICON_FONT: Self::Font = A::ICON_FONT; const CHECKMARK_ICON: char = A::CHECKMARK_ICON; const ARROW_DOWN_ICON: char = A::ARROW_DOWN_ICON; diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index a222e23c..8f343277 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -235,6 +235,7 @@ impl core::text::Renderer for Renderer { type Paragraph = Paragraph; type Editor = Editor; + const MONOSPACE_FONT: Font = Font::MONOSPACE; const ICON_FONT: Font = Font::with_name("Iced-Icons"); const CHECKMARK_ICON: char = '\u{f00c}'; const ARROW_DOWN_ICON: char = '\u{e800}'; diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 3fe2fbbe..94ab81c4 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -643,6 +643,7 @@ impl core::text::Renderer for Renderer { type Paragraph = Paragraph; type Editor = Editor; + const MONOSPACE_FONT: Font = Font::MONOSPACE; const ICON_FONT: Font = Font::with_name("Iced-Icons"); const CHECKMARK_ICON: char = '\u{f00c}'; const ARROW_DOWN_ICON: char = '\u{e800}';