From 327522eb998d0a2e1cbf10ed1d5e6f9bc9581db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 28 May 2025 19:58:43 +0200 Subject: [PATCH] Draft test recorder structure in `iced_devtools` --- Cargo.lock | 251 ++++++++++++++++++ core/src/renderer/null.rs | 1 - core/src/text.rs | 5 - devtools/Cargo.toml | 4 + devtools/build.rs | 7 + devtools/fonts/iced_devtools-icons.toml | 8 + devtools/fonts/iced_devtools-icons.ttf | Bin 0 -> 5888 bytes devtools/src/icon.rs | 40 +++ devtools/src/lib.rs | 339 +++++++++++++++++++----- program/src/lib.rs | 7 +- renderer/src/fallback.rs | 1 - tiny_skia/src/lib.rs | 1 - wgpu/src/lib.rs | 1 - widget/src/helpers.rs | 19 +- widget/src/pop.rs | 9 +- widget/src/themer.rs | 94 +++---- 16 files changed, 641 insertions(+), 146 deletions(-) create mode 100644 devtools/build.rs create mode 100644 devtools/fonts/iced_devtools-icons.toml create mode 100644 devtools/fonts/iced_devtools-icons.ttf create mode 100644 devtools/src/icon.rs diff --git a/Cargo.lock b/Cargo.lock index b90d4cec..265b802e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,17 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.12" @@ -141,6 +152,9 @@ name = "arbitrary" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "arc" @@ -666,6 +680,25 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659" +[[package]] +name = "bzip2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" +dependencies = [ + "bzip2-sys", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "cairo-sys-rs" version = "0.18.2" @@ -809,6 +842,16 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.5.39" @@ -952,6 +995,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "core-foundation" version = "0.9.4" @@ -1066,6 +1115,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.4.2" @@ -1227,6 +1291,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" +[[package]] +name = "deflate64" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" + [[package]] name = "deranged" version = "0.4.0" @@ -1236,6 +1306,17 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.7" @@ -1244,6 +1325,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -1881,9 +1963,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -2193,6 +2277,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.11" @@ -2462,11 +2555,27 @@ name = "iced_devtools" version = "0.14.0-dev" dependencies = [ "iced_debug", + "iced_fontello", "iced_program", + "iced_test", "iced_widget", "log", ] +[[package]] +name = "iced_fontello" +version = "0.14.0-dev" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1d2a83ec8063a28ddc818b018c2901a83fa7b773031b691dc80dd32cdb8f32" +dependencies = [ + "reqwest", + "serde", + "serde_json", + "sha2", + "toml", + "zip", +] + [[package]] name = "iced_futures" version = "0.14.0-dev" @@ -2785,6 +2894,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "integration" version = "0.1.0" @@ -3199,6 +3317,27 @@ dependencies = [ "num-traits", ] +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -4093,6 +4232,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -4687,6 +4836,7 @@ dependencies = [ "base64 0.22.1", "bytes", "encoding_rs", + "futures-channel", "futures-core", "futures-util", "h2 0.4.10", @@ -4701,6 +4851,7 @@ dependencies = [ "js-sys", "log", "mime", + "mime_guess", "native-tls", "once_cell", "percent-encoding", @@ -5905,9 +6056,16 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", + "toml_write", "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" + [[package]] name = "tooltip" version = "0.1.0" @@ -7427,6 +7585,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + [[package]] name = "yaml-rust" version = "0.4.5" @@ -7584,6 +7751,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zerotrie" @@ -7618,6 +7799,76 @@ dependencies = [ "syn", ] +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "deflate64", + "displaydoc", + "flate2", + "getrandom 0.3.3", + "hmac", + "indexmap", + "lzma-rs", + "memchr", + "pbkdf2", + "sha1", + "thiserror 2.0.12", + "time", + "xz2", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zopfli" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "zune-core" version = "0.4.12" diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index 2251e527..8105f91a 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -31,7 +31,6 @@ 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 0b51f244..525381ef 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -276,11 +276,6 @@ 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/devtools/Cargo.toml b/devtools/Cargo.toml index 04c3792b..fab9128d 100644 --- a/devtools/Cargo.toml +++ b/devtools/Cargo.toml @@ -19,6 +19,10 @@ time-travel = ["iced_program/time-travel"] [dependencies] iced_debug.workspace = true iced_program.workspace = true +iced_test.workspace = true iced_widget.workspace = true log.workspace = true + +[build-dependencies] +iced_fontello = "0.14.0-dev" diff --git a/devtools/build.rs b/devtools/build.rs new file mode 100644 index 00000000..5bcfd41b --- /dev/null +++ b/devtools/build.rs @@ -0,0 +1,7 @@ +#![allow(missing_docs)] +fn main() { + // println!("cargo::rerun-if-changed=fonts/iced_devtools-icons.toml"); + + // iced_fontello::build("fonts/iced_devtools-icons.toml") + // .expect("Build icons font"); +} diff --git a/devtools/fonts/iced_devtools-icons.toml b/devtools/fonts/iced_devtools-icons.toml new file mode 100644 index 00000000..69c8d86d --- /dev/null +++ b/devtools/fonts/iced_devtools-icons.toml @@ -0,0 +1,8 @@ +module = "icon" + +[glyphs] +play = "entypo-play" +stop = "entypo-stop" +record = "entypo-record" +import = "entypo-folder" +export = "entypo-floppy" diff --git a/devtools/fonts/iced_devtools-icons.ttf b/devtools/fonts/iced_devtools-icons.ttf new file mode 100644 index 0000000000000000000000000000000000000000..179db4f20f63ee12518bfbe6f661c660b1a8fb58 GIT binary patch literal 5888 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 -> Text<'a, Theme, Renderer> +where + Theme: text::Catalog + 'a, + Renderer: program::Renderer, +{ + icon("\u{25B6}") +} + +pub fn record<'a, Theme, Renderer>() -> Text<'a, Theme, Renderer> +where + Theme: text::Catalog + 'a, + Renderer: program::Renderer, +{ + icon("\u{26AB}") +} + +pub fn stop<'a, Theme, Renderer>() -> Text<'a, Theme, Renderer> +where + Theme: text::Catalog + 'a, + Renderer: program::Renderer, +{ + icon("\u{25A0}") +} + +fn icon<'a, Theme, Renderer>(codepoint: &'a str) -> Text<'a, Theme, Renderer> +where + Theme: text::Catalog + 'a, + Renderer: program::Renderer, +{ + text(codepoint).font(Font::with_name("iced_devtools-icons")) +} diff --git a/devtools/src/lib.rs b/devtools/src/lib.rs index e4c2e4d4..44923707 100644 --- a/devtools/src/lib.rs +++ b/devtools/src/lib.rs @@ -8,21 +8,26 @@ use iced_widget::runtime::futures; mod comet; mod executor; +mod icon; mod time_machine; +use crate::core::alignment::Horizontal::Right; 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::{Alignment::Center, Color, Element, Length::Fill}; +use crate::core::{ + Alignment::Center, Color, Element, Font, Length::Fill, Size, +}; use crate::futures::Subscription; use crate::program::Program; use crate::runtime::Task; +use crate::runtime::font; use crate::time_machine::TimeMachine; use crate::widget::{ - bottom_right, button, center, column, container, horizontal_space, opaque, - row, scrollable, stack, text, themer, + Text, bottom_right, button, center, column, container, horizontal_space, + opaque, row, scrollable, stack, text, text_input, themer, }; use std::fmt; @@ -59,7 +64,11 @@ where ( devtools, - Task::batch([boot.map(Event::Program), task.map(Event::Message)]), + Task::batch([ + boot.map(Event::Program), + task.map(Event::Message), + font::load(icon::FONT).discard(), + ]), ) } @@ -110,6 +119,7 @@ where P: Program, { state: P::State, + size: Size, mode: Mode, show_notification: bool, time_machine: TimeMachine

, @@ -118,18 +128,28 @@ where #[derive(Debug, Clone)] pub enum Message { HideNotification, + Toggle, ToggleComet, CometLaunched(comet::launch::Result), InstallComet, Installing(comet::install::Result), CancelSetup, + ChangeWidth(String), + ChangeHeight(String), + Record, } enum Mode { - None, + Hidden, + Open { recorder: Recorder }, Setup(Setup), } +enum Recorder { + Idle { events: Vec }, + Recording { events: Vec }, +} + enum Setup { Idle { goal: Goal }, Running { logs: Vec }, @@ -148,7 +168,8 @@ where ( Self { state, - mode: Mode::None, + size: Size::new(512.0, 512.0), + mode: Mode::Hidden, show_notification: true, time_machine: TimeMachine::new(), }, @@ -172,10 +193,30 @@ where Task::none() } + Message::Toggle => { + match self.mode { + Mode::Hidden => { + self.mode = Mode::Open { + recorder: Recorder::Idle { events: Vec::new() }, + }; + } + Mode::Open { + recorder: Recorder::Idle { .. }, + } => { + self.mode = Mode::Hidden; + } + Mode::Setup(_) + | Mode::Open { + recorder: Recorder::Recording { .. }, + } => {} + } + + Task::none() + } Message::ToggleComet => { if let Mode::Setup(setup) = &self.mode { if matches!(setup, Setup::Idle { .. }) { - self.mode = Mode::None; + self.mode = Mode::Hidden; } Task::none() @@ -228,7 +269,7 @@ where Task::none() } comet::install::Event::Finished => { - self.mode = Mode::None; + self.mode = Mode::Hidden; comet::launch().discard() } } @@ -251,10 +292,34 @@ where Task::none() } Message::CancelSetup => { - self.mode = Mode::None; + self.mode = Mode::Hidden; Task::none() } + Message::ChangeWidth(width) => { + if let Ok(width) = width.parse() { + self.size.width = width; + } + + Task::none() + } + Message::ChangeHeight(height) => { + if let Ok(height) = height.parse() { + self.size.height = height; + } + + Task::none() + } + Message::Record => { + let (state, task) = program.boot(); + + self.state = state; + self.mode = Mode::Open { + recorder: Recorder::Recording { events: Vec::new() }, + }; + + task.map(Event::Program) + } }, Event::Program(message) => { self.time_machine.push(&message); @@ -299,6 +364,9 @@ where let view = { let view = program.view(state, window); + let theme = program.theme(state, window); + + let view: Element<'_, _, Theme, _> = themer(theme, view).into(); if self.time_machine.is_rewinding() { view.map(|_| Event::Discard) @@ -307,58 +375,177 @@ where } }; - let theme = program.theme(state, window); + let theme = program + .theme(state, window) + .palette() + .map(|palette| Theme::custom("iced devtools", palette)) + .unwrap_or_default(); - let derive_theme = move || { - theme - .palette() - .map(|palette| Theme::custom("iced devtools", palette)) - .unwrap_or_default() - }; + let setup = if let Mode::Setup(setup) = &self.mode { + let stage: Element<'_, _, Theme, P::Renderer> = match setup { + Setup::Idle { goal } => self::setup(goal), + Setup::Running { logs } => installation(logs), + }; - let mode = match &self.mode { - Mode::None => None, - Mode::Setup(setup) => { - let stage: Element<'_, _, Theme, P::Renderer> = match setup { - Setup::Idle { goal } => self::setup(goal), - Setup::Running { logs } => installation(logs), - }; + let setup = center( + container(stage) + .padding(20) + .max_width(500) + .style(container::bordered_box), + ) + .padding(10) + .style(|_theme| { + container::Style::default() + .background(Color::BLACK.scale_alpha(0.8)) + }); - let setup = center( - container(stage) - .padding(20) - .max_width(500) - .style(container::bordered_box), - ) - .padding(10) - .style(|_theme| { - container::Style::default() - .background(Color::BLACK.scale_alpha(0.8)) - }); - - Some(setup) - } + Some(setup) + } else { + None } - .map(|mode| { - themer(derive_theme(), Element::from(mode).map(Event::Message)) - }); + .map(|mode| Element::from(mode).map(Event::Message)); let notification = self.show_notification.then(|| { - themer( - derive_theme(), - bottom_right(opaque( - container(text("Press F12 to open debug metrics")) - .padding(10) - .style(container::dark), - )), - ) + bottom_right(opaque( + container(text("Press F12 to open developer tools")) + .padding(10) + .style(container::dark), + )) }); - stack![view] - .height(Fill) - .push_maybe(mode.map(opaque)) - .push_maybe(notification) - .into() + let sidebar = if let Mode::Open { recorder } = &self.mode { + let title = monospace("Developer Tools"); + + let recorder = { + let events = center(match recorder { + Recorder::Idle { events } if events.is_empty() => { + monospace("No events recorded yet!") + .size(14) + .width(Fill) + .center() + } + Recorder::Idle { events } + | Recorder::Recording { events } => { + monospace(format!("{} events recorded", events.len())) + } + }) + .style(container::bordered_box); + + let controls = { + row![ + button(icon::play().size(14).width(Fill).center()), + match recorder { + Recorder::Idle { .. } => { + 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) + }; + + column![events, controls].spacing(10).align_x(Center) + }; + + let viewport = row![ + text_input("Width", &self.size.width.to_string()) + .size(14) + .on_input(Message::ChangeWidth), + monospace("x"), + text_input("Height", &self.size.height.to_string()) + .size(14) + .on_input(Message::ChangeHeight), + ] + .spacing(10) + .align_y(Center); + + let tools = column![ + title, + labeled("Viewport", viewport), + labeled("Tester", recorder) + ] + .spacing(10); + + let sidebar = container(tools) + .padding(10) + .width(250) + .height(Fill) + .style(container::dark); + + Some(Element::from(sidebar).map(Event::Message)) + } else { + None + }; + + let content = row![if let Mode::Open { recorder } = &self.mode { + let is_recording = matches!(recorder, Recorder::Recording { .. }); + + let status = if is_recording { + monospace("Recording").style(|theme| text::Style { + color: Some(theme.palette().danger), + }) + } else { + monospace("Idle").style(|theme| text::Style { + color: Some( + theme.extended_palette().background.strongest.color, + ), + }) + }; + + let viewport = container( + scrollable( + container(view) + .width(self.size.width) + .height(self.size.height), + ) + .direction(scrollable::Direction::Both { + vertical: scrollable::Scrollbar::default(), + horizontal: scrollable::Scrollbar::default(), + }), + ) + .style(move |theme| { + let palette = theme.extended_palette(); + + container::Style { + border: border::width(2.0).color(if is_recording { + palette.danger.base.color + } else { + palette.background.strongest.color + }), + ..container::Style::default() + } + }) + .padding(10); + + center(column![status, viewport].spacing(10).align_x(Right)) + .padding(10) + .into() + } else { + view + }] + .push_maybe(sidebar); + + themer( + theme, + stack![content] + .height(Fill) + .push_maybe(setup.map(opaque)) + .push_maybe(notification), + ) + .into() } fn subscription(&self, program: &P) -> Subscription> { @@ -369,6 +556,9 @@ where let hotkeys = futures::keyboard::on_key_press(|key, _modifiers| match key { keyboard::Key::Named(keyboard::key::Named::F12) => { + Some(Message::Toggle) + } + keyboard::Key::Named(keyboard::key::Named::F11) => { Some(Message::ToggleComet) } _ => None, @@ -438,7 +628,7 @@ where fn setup(goal: &Goal) -> Element<'_, Message, Theme, Renderer> where - Renderer: core::text::Renderer + 'static, + Renderer: program::Renderer + 'static, { let controls = row![ button(text("Cancel").center().width(Fill)) @@ -460,14 +650,13 @@ where ]; let command = container( - text!( + monospace(format!( "cargo install --locked \\ --git https://github.com/iced-rs/comet.git \\ --rev {}", comet::COMPATIBLE_REVISION - ) - .size(14) - .font(Renderer::MONOSPACE_FONT), + )) + .size(14), ) .width(Fill) .padding(5) @@ -528,15 +717,15 @@ fn installation<'a, Renderer>( logs: &'a [String], ) -> Element<'a, Message, Theme, Renderer> where - Renderer: core::text::Renderer + 'a, + Renderer: program::Renderer + 'a, { column![ text("Installing comet...").size(20), container( scrollable( - column(logs.iter().map(|log| { - text(log).size(12).font(Renderer::MONOSPACE_FONT).into() - }),) + column( + logs.iter().map(|log| { monospace(log).size(12).into() }), + ) .spacing(3), ) .spacing(10) @@ -555,9 +744,9 @@ fn inline_code<'a, Renderer>( code: impl text::IntoFragment<'a>, ) -> Element<'a, Message, Theme, Renderer> where - Renderer: core::text::Renderer + 'a, + Renderer: program::Renderer + 'a, { - container(text(code).font(Renderer::MONOSPACE_FONT).size(12)) + container(monospace(code).size(12)) .style(|_theme| { container::Style::default() .background(Color::BLACK) @@ -566,3 +755,25 @@ where .padding([2, 4]) .into() } + +fn monospace<'a, Renderer>( + fragment: impl text::IntoFragment<'a>, +) -> Text<'a, Theme, Renderer> +where + Renderer: program::Renderer + 'a, +{ + text(fragment).font(Font::MONOSPACE) +} + +fn labeled<'a, Message, Renderer>( + fragment: impl text::IntoFragment<'a>, + content: impl Into>, +) -> Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Renderer: program::Renderer + 'a, +{ + column![monospace(fragment).size(14), content.into()] + .spacing(5) + .into() +} diff --git a/program/src/lib.rs b/program/src/lib.rs index e25cdb22..80e59330 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -4,10 +4,10 @@ pub use iced_runtime as runtime; pub use iced_runtime::core; pub use iced_runtime::futures; -use crate::core::Element; use crate::core::text; use crate::core::theme; use crate::core::window; +use crate::core::{Element, Font}; use crate::futures::{Executor, Subscription}; use crate::graphics::compositor; use crate::runtime::Task; @@ -585,9 +585,10 @@ pub fn with_executor( } /// The renderer of some [`Program`]. -pub trait Renderer: text::Renderer + compositor::Default {} +pub trait Renderer: text::Renderer + compositor::Default {} -impl Renderer for T where T: text::Renderer + compositor::Default {} +impl Renderer for T where T: text::Renderer + compositor::Default +{} /// A particular instance of a running [`Program`]. #[allow(missing_debug_implementations)] diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index 79e22c77..7b87e7e1 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -84,7 +84,6 @@ 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 8f343277..a222e23c 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -235,7 +235,6 @@ 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 7f74905b..13ed671d 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -655,7 +655,6 @@ 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/widget/src/helpers.rs b/widget/src/helpers.rs index 5e7b30d7..429a597c 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -997,7 +997,6 @@ pub fn pop<'a, Message, Theme, Renderer>( ) -> Pop<'a, (), Message, Theme, Renderer> where Renderer: core::Renderer, - Message: Clone, { Pop::new(content) } @@ -2054,22 +2053,14 @@ where } /// A widget that applies any `Theme` to its contents. -pub fn themer<'a, Message, OldTheme, NewTheme, Renderer>( - new_theme: NewTheme, - content: impl Into>, -) -> Themer< - 'a, - Message, - OldTheme, - NewTheme, - impl Fn(&OldTheme) -> NewTheme, - Renderer, -> +pub fn themer<'a, Message, Theme, Renderer>( + theme: Theme, + content: impl Into>, +) -> Themer<'a, Message, Theme, Renderer> where Renderer: core::Renderer, - NewTheme: Clone, { - Themer::new(move |_| new_theme.clone(), content) + Themer::new(theme, content) } /// Creates a [`PaneGrid`] with the given [`pane_grid::State`] and view function. diff --git a/widget/src/pop.rs b/widget/src/pop.rs index 44da6a4e..8c6236bc 100644 --- a/widget/src/pop.rs +++ b/widget/src/pop.rs @@ -34,7 +34,6 @@ pub struct Pop< impl<'a, Message, Theme, Renderer> Pop<'a, (), Message, Theme, Renderer> where - Message: Clone, Renderer: core::Renderer, { /// Creates a new [`Pop`] widget with the given content. @@ -55,7 +54,6 @@ where impl<'a, Key, Message, Theme, Renderer> Pop<'a, Key, Message, Theme, Renderer> where - Message: Clone, Key: self::Key, Renderer: core::Renderer, { @@ -163,7 +161,6 @@ impl Widget for Pop<'_, Key, Message, Theme, Renderer> where Key: self::Key, - Message: Clone, Renderer: core::Renderer, { fn tag(&self) -> tree::Tag { @@ -243,8 +240,8 @@ where if let Some(on_show) = &self.on_show { shell.publish(on_show(layout.bounds().size())); } - } else if let Some(on_hide) = &self.on_hide { - shell.publish(on_hide.clone()); + } else if let Some(on_hide) = self.on_hide.take() { + shell.publish(on_hide); } state.should_notify_at = None; @@ -362,7 +359,7 @@ impl<'a, Key, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where - Message: Clone + 'a, + Message: 'a, Key: self::Key + 'a, Renderer: core::Renderer + 'a, Theme: 'a, diff --git a/widget/src/themer.rs b/widget/src/themer.rs index 693b8486..1b9e1c6a 100644 --- a/widget/src/themer.rs +++ b/widget/src/themer.rs @@ -10,63 +10,55 @@ use crate::core::{ Shell, Size, Vector, Widget, }; -use std::marker::PhantomData; - /// A widget that applies any `Theme` to its contents. /// /// This widget can be useful to leverage multiple `Theme` /// types in an application. #[allow(missing_debug_implementations)] -pub struct Themer<'a, Message, Theme, NewTheme, F, Renderer = crate::Renderer> +pub struct Themer<'a, Message, Theme, Renderer = crate::Renderer> where - F: Fn(&Theme) -> NewTheme, Renderer: crate::core::Renderer, { - content: Element<'a, Message, NewTheme, Renderer>, - to_theme: F, - text_color: Option Color>, - background: Option Background>, - old_theme: PhantomData, + content: Element<'a, Message, Theme, Renderer>, + theme: Theme, + text_color: Option Color>, + background: Option Background>, } -impl<'a, Message, Theme, NewTheme, F, Renderer> - Themer<'a, Message, Theme, NewTheme, F, Renderer> +impl<'a, Message, Theme, Renderer> Themer<'a, Message, Theme, Renderer> where - F: Fn(&Theme) -> NewTheme, Renderer: crate::core::Renderer, { /// Creates an empty [`Themer`] that applies the given `Theme` /// to the provided `content`. - pub fn new(to_theme: F, content: T) -> Self - where - T: Into>, - { + pub fn new( + theme: Theme, + content: impl Into>, + ) -> Self { Self { content: content.into(), - to_theme, + theme, text_color: None, background: None, - old_theme: PhantomData, } } /// Sets the default text [`Color`] of the [`Themer`]. - pub fn text_color(mut self, f: fn(&NewTheme) -> Color) -> Self { + pub fn text_color(mut self, f: fn(&Theme) -> Color) -> Self { self.text_color = Some(f); self } /// Sets the [`Background`] of the [`Themer`]. - pub fn background(mut self, f: fn(&NewTheme) -> Background) -> Self { + pub fn background(mut self, f: fn(&Theme) -> Background) -> Self { self.background = Some(f); self } } -impl Widget - for Themer<'_, Message, Theme, NewTheme, F, Renderer> +impl Widget + for Themer<'_, Message, Theme, Renderer> where - F: Fn(&Theme) -> NewTheme, Renderer: crate::core::Renderer, { fn tag(&self) -> tree::Tag { @@ -143,19 +135,17 @@ where &self, tree: &Tree, renderer: &mut Renderer, - theme: &Theme, + _theme: &AnyTheme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, ) { - let theme = (self.to_theme)(theme); - if let Some(background) = self.background { container::draw_background( renderer, &container::Style { - background: Some(background(&theme)), + background: Some(background(&self.theme)), ..container::Style::default() }, layout.bounds(), @@ -164,15 +154,21 @@ where let style = if let Some(text_color) = self.text_color { renderer::Style { - text_color: text_color(&theme), + text_color: text_color(&self.theme), } } else { *style }; - self.content - .as_widget() - .draw(tree, renderer, &theme, &style, layout, cursor, viewport); + self.content.as_widget().draw( + tree, + renderer, + &self.theme, + &style, + layout, + cursor, + viewport, + ); } fn overlay<'b>( @@ -182,15 +178,15 @@ where renderer: &Renderer, viewport: &Rectangle, translation: Vector, - ) -> Option> { - struct Overlay<'a, Message, Theme, NewTheme, Renderer> { - to_theme: &'a dyn Fn(&Theme) -> NewTheme, - content: overlay::Element<'a, Message, NewTheme, Renderer>, + ) -> Option> { + struct Overlay<'a, Message, Theme, Renderer> { + theme: &'a Theme, + content: overlay::Element<'a, Message, Theme, Renderer>, } - impl - overlay::Overlay - for Overlay<'_, Message, Theme, NewTheme, Renderer> + impl + overlay::Overlay + for Overlay<'_, Message, Theme, Renderer> where Renderer: crate::core::Renderer, { @@ -205,14 +201,14 @@ where fn draw( &self, renderer: &mut Renderer, - theme: &Theme, + _theme: &AnyTheme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, ) { self.content.as_overlay().draw( renderer, - &(self.to_theme)(theme), + &self.theme, style, layout, cursor, @@ -259,13 +255,13 @@ where &'b mut self, layout: Layout<'b>, renderer: &Renderer, - ) -> Option> + ) -> Option> { self.content .as_overlay_mut() .overlay(layout, renderer) .map(|content| Overlay { - to_theme: &self.to_theme, + theme: self.theme, content, }) .map(|overlay| overlay::Element::new(Box::new(overlay))) @@ -276,26 +272,24 @@ where .as_widget_mut() .overlay(tree, layout, renderer, viewport, translation) .map(|content| Overlay { - to_theme: &self.to_theme, + theme: &self.theme, content, }) .map(|overlay| overlay::Element::new(Box::new(overlay))) } } -impl<'a, Message, Theme, NewTheme, F, Renderer> - From> - for Element<'a, Message, Theme, Renderer> +impl<'a, Message, Theme, Renderer, AnyTheme> + From> + for Element<'a, Message, AnyTheme, Renderer> where Message: 'a, Theme: 'a, - NewTheme: 'a, - F: Fn(&Theme) -> NewTheme + 'a, Renderer: 'a + crate::core::Renderer, { fn from( - themer: Themer<'a, Message, Theme, NewTheme, F, Renderer>, - ) -> Element<'a, Message, Theme, Renderer> { + themer: Themer<'a, Message, Theme, Renderer>, + ) -> Element<'a, Message, AnyTheme, Renderer> { Element::new(themer) } }