From e20fee79564a977c1d4a1f15073fc00eab31719e Mon Sep 17 00:00:00 2001 From: pml68 Date: Tue, 25 Feb 2025 23:41:27 +0100 Subject: [PATCH 1/3] feat: only rehighlight `TextEditor` when `Theme` changes --- widget/src/text_editor.rs | 47 ++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index e0e5b510..ae3b7087 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -170,7 +170,7 @@ impl<'a, Highlighter, Message, Theme, Renderer> TextEditor<'a, Highlighter, Message, Theme, Renderer> where Highlighter: text::Highlighter, - Theme: Catalog, + Theme: Catalog + 'static + PartialEq, Renderer: text::Renderer, { /// Sets the placeholder of the [`TextEditor`]. @@ -335,7 +335,7 @@ where fn input_method<'b>( &self, - state: &'b State, + state: &'b State, renderer: &Renderer, layout: Layout<'_>, ) -> InputMethod<&'b str> { @@ -508,12 +508,13 @@ where /// The state of a [`TextEditor`]. #[derive(Debug)] -pub struct State { +pub struct State { focus: Option, preedit: Option, last_click: Option, drag_click: Option, partial_scroll: f32, + last_theme: RefCell>, highlighter: RefCell, highlighter_settings: Highlighter::Settings, highlighter_format_address: usize, @@ -547,15 +548,17 @@ impl Focus { } } -impl State { +impl + State +{ /// Returns whether the [`TextEditor`] is currently focused or not. pub fn is_focused(&self) -> bool { self.focus.is_some() } } -impl operation::Focusable - for State +impl + operation::Focusable for State { fn is_focused(&self) -> bool { self.focus.is_some() @@ -574,11 +577,11 @@ impl Widget for TextEditor<'_, Highlighter, Message, Theme, Renderer> where Highlighter: text::Highlighter, - Theme: Catalog, + Theme: Catalog + 'static + PartialEq + Clone, Renderer: text::Renderer, { fn tag(&self) -> widget::tree::Tag { - widget::tree::Tag::of::>() + widget::tree::Tag::of::>() } fn state(&self) -> widget::tree::State { @@ -588,6 +591,7 @@ where last_click: None, drag_click: None, partial_scroll: 0.0, + last_theme: RefCell::>::default(), highlighter: RefCell::new(Highlighter::new( &self.highlighter_settings, )), @@ -610,7 +614,7 @@ where limits: &layout::Limits, ) -> iced_renderer::core::layout::Node { let mut internal = self.content.0.borrow_mut(); - let state = tree.state.downcast_mut::>(); + let state = tree.state.downcast_mut::>(); if state.highlighter_format_address != self.highlighter_format as usize { @@ -675,7 +679,7 @@ where return; }; - let state = tree.state.downcast_mut::>(); + let state = tree.state.downcast_mut::>(); let is_redraw = matches!( event, Event::Window(window::Event::RedrawRequested(_now)), @@ -785,13 +789,14 @@ where }, Update::Binding(binding) => { fn apply_binding< + T: PartialEq + 'static, H: text::Highlighter, R: text::Renderer, Message, >( binding: Binding, content: &Content, - state: &mut State, + state: &mut State, on_edit: &dyn Fn(Action) -> Message, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, @@ -934,10 +939,20 @@ where let bounds = layout.bounds(); let mut internal = self.content.0.borrow_mut(); - let state = tree.state.downcast_ref::>(); + let state = tree.state.downcast_ref::>(); let font = self.font.unwrap_or_else(|| renderer.default_font()); + if state + .last_theme + .borrow() + .as_ref() + .is_none_or(|last_theme| last_theme != theme) + { + state.highlighter.borrow_mut().change_line(0); + let _ = state.last_theme.borrow_mut().replace(theme.clone()); + } + internal.editor.highlight( font, state.highlighter.borrow_mut().deref_mut(), @@ -1064,7 +1079,7 @@ where _renderer: &Renderer, operation: &mut dyn widget::Operation, ) { - let state = tree.state.downcast_mut::>(); + let state = tree.state.downcast_mut::>(); operation.focusable(self.id.as_ref(), layout.bounds(), state); } @@ -1076,7 +1091,7 @@ impl<'a, Highlighter, Message, Theme, Renderer> where Highlighter: text::Highlighter, Message: 'a, - Theme: Catalog + 'a, + Theme: Catalog + 'static + PartialEq + Clone, Renderer: text::Renderer, { fn from( @@ -1232,9 +1247,9 @@ enum Ime { } impl Update { - fn from_event( + fn from_event( event: &Event, - state: &State, + state: &State, bounds: Rectangle, padding: Padding, cursor: mouse::Cursor, From 96be0b54707b83846104f134d46189df95918bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 21 Nov 2025 00:59:52 +0100 Subject: [PATCH 2/3] Remove trait redundancy in `text_editor` --- widget/src/text_editor.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index ae3b7087..a630e561 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -170,7 +170,7 @@ impl<'a, Highlighter, Message, Theme, Renderer> TextEditor<'a, Highlighter, Message, Theme, Renderer> where Highlighter: text::Highlighter, - Theme: Catalog + 'static + PartialEq, + Theme: Catalog + 'static, Renderer: text::Renderer, { /// Sets the placeholder of the [`TextEditor`]. @@ -508,7 +508,7 @@ where /// The state of a [`TextEditor`]. #[derive(Debug)] -pub struct State { +pub struct State { focus: Option, preedit: Option, last_click: Option, @@ -548,17 +548,15 @@ impl Focus { } } -impl - State -{ +impl State { /// Returns whether the [`TextEditor`] is currently focused or not. pub fn is_focused(&self) -> bool { self.focus.is_some() } } -impl - operation::Focusable for State +impl operation::Focusable + for State { fn is_focused(&self) -> bool { self.focus.is_some() @@ -577,7 +575,7 @@ impl Widget for TextEditor<'_, Highlighter, Message, Theme, Renderer> where Highlighter: text::Highlighter, - Theme: Catalog + 'static + PartialEq + Clone, + Theme: Catalog + 'static, Renderer: text::Renderer, { fn tag(&self) -> widget::tree::Tag { @@ -789,9 +787,9 @@ where }, Update::Binding(binding) => { fn apply_binding< - T: PartialEq + 'static, H: text::Highlighter, R: text::Renderer, + T, Message, >( binding: Binding, @@ -1091,7 +1089,7 @@ impl<'a, Highlighter, Message, Theme, Renderer> where Highlighter: text::Highlighter, Message: 'a, - Theme: Catalog + 'static + PartialEq + Clone, + Theme: Catalog + 'static, Renderer: text::Renderer, { fn from( @@ -1247,7 +1245,7 @@ enum Ime { } impl Update { - fn from_event( + fn from_event( event: &Event, state: &State, bounds: Rectangle, @@ -1406,7 +1404,7 @@ pub struct Style { } /// The theme catalog of a [`TextEditor`]. -pub trait Catalog { +pub trait Catalog: PartialEq + Clone { /// The item class of the [`Catalog`]. type Class<'a>; From 0495f32918ba5fed7edba56baff6db1b76415993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 21 Nov 2025 01:08:27 +0100 Subject: [PATCH 3/3] Use new `theme::Base::name` for theme change detection --- core/src/theme.rs | 60 +++++++++++++++++++++++---------------- widget/src/text_editor.rs | 49 +++++++++++++++++--------------- 2 files changed, 61 insertions(+), 48 deletions(-) diff --git a/core/src/theme.rs b/core/src/theme.rs index 1260bca8..22cf632a 100644 --- a/core/src/theme.rs +++ b/core/src/theme.rs @@ -168,31 +168,7 @@ impl Theme { impl fmt::Display for Theme { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Light => write!(f, "Light"), - Self::Dark => write!(f, "Dark"), - Self::Dracula => write!(f, "Dracula"), - Self::Nord => write!(f, "Nord"), - Self::SolarizedLight => write!(f, "Solarized Light"), - Self::SolarizedDark => write!(f, "Solarized Dark"), - Self::GruvboxLight => write!(f, "Gruvbox Light"), - Self::GruvboxDark => write!(f, "Gruvbox Dark"), - Self::CatppuccinLatte => write!(f, "Catppuccin Latte"), - Self::CatppuccinFrappe => write!(f, "Catppuccin Frappé"), - Self::CatppuccinMacchiato => write!(f, "Catppuccin Macchiato"), - Self::CatppuccinMocha => write!(f, "Catppuccin Mocha"), - Self::TokyoNight => write!(f, "Tokyo Night"), - Self::TokyoNightStorm => write!(f, "Tokyo Night Storm"), - Self::TokyoNightLight => write!(f, "Tokyo Night Light"), - Self::KanagawaWave => write!(f, "Kanagawa Wave"), - Self::KanagawaDragon => write!(f, "Kanagawa Dragon"), - Self::KanagawaLotus => write!(f, "Kanagawa Lotus"), - Self::Moonfly => write!(f, "Moonfly"), - Self::Nightfly => write!(f, "Nightfly"), - Self::Oxocarbon => write!(f, "Oxocarbon"), - Self::Ferra => write!(f, "Ferra"), - Self::Custom(custom) => custom.fmt(f), - } + f.write_str(self.name()) } } @@ -270,6 +246,12 @@ pub trait Base { /// debugging purposes; like displaying performance /// metrics or devtools. fn palette(&self) -> Option; + + /// Returns the unique name of the theme. + /// + /// This name may be used to efficiently detect theme + /// changes in some widgets. + fn name(&self) -> &str; } impl Base for Theme { @@ -313,6 +295,34 @@ impl Base for Theme { fn palette(&self) -> Option { Some(self.palette()) } + + fn name(&self) -> &str { + match self { + Self::Light => "Light", + Self::Dark => "Dark", + Self::Dracula => "Dracula", + Self::Nord => "Nord", + Self::SolarizedLight => "Solarized Light", + Self::SolarizedDark => "Solarized Dark", + Self::GruvboxLight => "Gruvbox Light", + Self::GruvboxDark => "Gruvbox Dark", + Self::CatppuccinLatte => "Catppuccin Latte", + Self::CatppuccinFrappe => "Catppuccin Frappé", + Self::CatppuccinMacchiato => "Catppuccin Macchiato", + Self::CatppuccinMocha => "Catppuccin Mocha", + Self::TokyoNight => "Tokyo Night", + Self::TokyoNightStorm => "Tokyo Night Storm", + Self::TokyoNightLight => "Tokyo Night Light", + Self::KanagawaWave => "Kanagawa Wave", + Self::KanagawaDragon => "Kanagawa Dragon", + Self::KanagawaLotus => "Kanagawa Lotus", + Self::Moonfly => "Moonfly", + Self::Nightfly => "Nightfly", + Self::Oxocarbon => "Oxocarbon", + Self::Ferra => "Ferra", + Self::Custom(custom) => &custom.name, + } + } } /// The default [`Style`] of a built-in [`Theme`]. diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index a630e561..6c5efb3f 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -42,6 +42,7 @@ use crate::core::renderer; use crate::core::text::editor::{Cursor, Editor as _}; use crate::core::text::highlighter::{self, Highlighter}; use crate::core::text::{self, LineHeight, Text, Wrapping}; +use crate::core::theme; use crate::core::time::{Duration, Instant}; use crate::core::widget::operation; use crate::core::widget::{self, Widget}; @@ -148,7 +149,7 @@ where max_height: f32::INFINITY, padding: Padding::new(5.0), wrapping: Wrapping::default(), - class: Theme::default(), + class: ::default(), key_binding: None, on_edit: None, highlighter_settings: (), @@ -170,7 +171,7 @@ impl<'a, Highlighter, Message, Theme, Renderer> TextEditor<'a, Highlighter, Message, Theme, Renderer> where Highlighter: text::Highlighter, - Theme: Catalog + 'static, + Theme: Catalog, Renderer: text::Renderer, { /// Sets the placeholder of the [`TextEditor`]. @@ -335,7 +336,7 @@ where fn input_method<'b>( &self, - state: &'b State, + state: &'b State, renderer: &Renderer, layout: Layout<'_>, ) -> InputMethod<&'b str> { @@ -508,13 +509,13 @@ where /// The state of a [`TextEditor`]. #[derive(Debug)] -pub struct State { +pub struct State { focus: Option, preedit: Option, last_click: Option, drag_click: Option, partial_scroll: f32, - last_theme: RefCell>, + last_theme: RefCell>, highlighter: RefCell, highlighter_settings: Highlighter::Settings, highlighter_format_address: usize, @@ -548,15 +549,15 @@ impl Focus { } } -impl State { +impl State { /// Returns whether the [`TextEditor`] is currently focused or not. pub fn is_focused(&self) -> bool { self.focus.is_some() } } -impl operation::Focusable - for State +impl operation::Focusable + for State { fn is_focused(&self) -> bool { self.focus.is_some() @@ -575,11 +576,11 @@ impl Widget for TextEditor<'_, Highlighter, Message, Theme, Renderer> where Highlighter: text::Highlighter, - Theme: Catalog + 'static, + Theme: Catalog, Renderer: text::Renderer, { fn tag(&self) -> widget::tree::Tag { - widget::tree::Tag::of::>() + widget::tree::Tag::of::>() } fn state(&self) -> widget::tree::State { @@ -589,7 +590,7 @@ where last_click: None, drag_click: None, partial_scroll: 0.0, - last_theme: RefCell::>::default(), + last_theme: RefCell::default(), highlighter: RefCell::new(Highlighter::new( &self.highlighter_settings, )), @@ -612,7 +613,7 @@ where limits: &layout::Limits, ) -> iced_renderer::core::layout::Node { let mut internal = self.content.0.borrow_mut(); - let state = tree.state.downcast_mut::>(); + let state = tree.state.downcast_mut::>(); if state.highlighter_format_address != self.highlighter_format as usize { @@ -677,7 +678,7 @@ where return; }; - let state = tree.state.downcast_mut::>(); + let state = tree.state.downcast_mut::>(); let is_redraw = matches!( event, Event::Window(window::Event::RedrawRequested(_now)), @@ -789,12 +790,11 @@ where fn apply_binding< H: text::Highlighter, R: text::Renderer, - T, Message, >( binding: Binding, content: &Content, - state: &mut State, + state: &mut State, on_edit: &dyn Fn(Action) -> Message, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, @@ -937,18 +937,21 @@ where let bounds = layout.bounds(); let mut internal = self.content.0.borrow_mut(); - let state = tree.state.downcast_ref::>(); + let state = tree.state.downcast_ref::>(); let font = self.font.unwrap_or_else(|| renderer.default_font()); + let theme_name = theme.name(); + if state .last_theme .borrow() .as_ref() - .is_none_or(|last_theme| last_theme != theme) + .is_none_or(|last_theme| last_theme != theme_name) { state.highlighter.borrow_mut().change_line(0); - let _ = state.last_theme.borrow_mut().replace(theme.clone()); + let _ = + state.last_theme.borrow_mut().replace(theme_name.to_owned()); } internal.editor.highlight( @@ -1077,7 +1080,7 @@ where _renderer: &Renderer, operation: &mut dyn widget::Operation, ) { - let state = tree.state.downcast_mut::>(); + let state = tree.state.downcast_mut::>(); operation.focusable(self.id.as_ref(), layout.bounds(), state); } @@ -1089,7 +1092,7 @@ impl<'a, Highlighter, Message, Theme, Renderer> where Highlighter: text::Highlighter, Message: 'a, - Theme: Catalog + 'static, + Theme: Catalog + 'a, Renderer: text::Renderer, { fn from( @@ -1245,9 +1248,9 @@ enum Ime { } impl Update { - fn from_event( + fn from_event( event: &Event, - state: &State, + state: &State, bounds: Rectangle, padding: Padding, cursor: mouse::Cursor, @@ -1404,7 +1407,7 @@ pub struct Style { } /// The theme catalog of a [`TextEditor`]. -pub trait Catalog: PartialEq + Clone { +pub trait Catalog: theme::Base { /// The item class of the [`Catalog`]. type Class<'a>;