diff options
author | 2024-11-13 17:08:26 +0100 | |
---|---|---|
committer | 2024-11-13 17:08:26 +0100 | |
commit | a11fcf8f2dde551335c6f34788393fa2e8f8a362 (patch) | |
tree | 1e423c847a6505a2582bec19908866dba94c436d /widget/src/text_editor.rs | |
parent | 42a2cb6d4f78343f43d6a68a28e5502d9426ed2c (diff) | |
parent | 28ec6df8f0ebf96966bee61caf5a325695314b7a (diff) | |
download | iced-a11fcf8f2dde551335c6f34788393fa2e8f8a362.tar.gz iced-a11fcf8f2dde551335c6f34788393fa2e8f8a362.tar.bz2 iced-a11fcf8f2dde551335c6f34788393fa2e8f8a362.zip |
Merge pull request #2662 from iced-rs/reactive-rendering
Reactive Rendering
Diffstat (limited to 'widget/src/text_editor.rs')
-rw-r--r-- | widget/src/text_editor.rs | 332 |
1 files changed, 178 insertions, 154 deletions
diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index a298252a..b6e291e4 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -33,7 +33,6 @@ //! ``` use crate::core::alignment; use crate::core::clipboard::{self, Clipboard}; -use crate::core::event::{self, Event}; use crate::core::keyboard; use crate::core::keyboard::key; use crate::core::layout::{self, Layout}; @@ -47,7 +46,7 @@ use crate::core::widget::operation; use crate::core::widget::{self, Widget}; use crate::core::window; use crate::core::{ - Background, Border, Color, Element, Length, Padding, Pixels, Point, + Background, Border, Color, Element, Event, Length, Padding, Pixels, Point, Rectangle, Shell, Size, SmolStr, Theme, Vector, }; @@ -120,6 +119,7 @@ pub struct TextEditor< &Highlighter::Highlight, &Theme, ) -> highlighter::Format<Renderer::Font>, + last_status: Option<Status>, } impl<'a, Message, Theme, Renderer> @@ -147,6 +147,7 @@ where highlighter_format: |_highlight, _theme| { highlighter::Format::default() }, + last_status: None, } } } @@ -270,6 +271,7 @@ where on_edit: self.on_edit, highlighter_settings: settings, highlighter_format: to_format, + last_status: self.last_status, } } @@ -596,7 +598,7 @@ where } } - fn on_event( + fn update( &mut self, tree: &mut widget::Tree, event: Event, @@ -606,12 +608,16 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let Some(on_edit) = self.on_edit.as_ref() else { - return event::Status::Ignored; + return; }; let state = tree.state.downcast_mut::<State<Highlighter>>(); + let is_redraw = matches!( + event, + Event::Window(window::Event::RedrawRequested(_now)), + ); match event { Event::Window(window::Event::Unfocused) => { @@ -624,7 +630,7 @@ where focus.is_window_focused = true; focus.updated_at = Instant::now(); - shell.request_redraw(window::RedrawRequest::NextFrame); + shell.request_redraw(); } } Event::Window(window::Event::RedrawRequested(now)) => { @@ -637,168 +643,193 @@ where - (now - focus.updated_at).as_millis() % Focus::CURSOR_BLINK_INTERVAL_MILLIS; - shell.request_redraw(window::RedrawRequest::At( + shell.request_redraw_at( now + Duration::from_millis( millis_until_redraw as u64, ), - )); + ); } } } _ => {} } - let Some(update) = Update::from_event( + if let Some(update) = Update::from_event( event, state, layout.bounds(), self.padding, cursor, self.key_binding.as_deref(), - ) else { - return event::Status::Ignored; - }; - - match update { - Update::Click(click) => { - let action = match click.kind() { - mouse::click::Kind::Single => { - Action::Click(click.position()) - } - mouse::click::Kind::Double => Action::SelectWord, - mouse::click::Kind::Triple => Action::SelectLine, - }; - - state.focus = Some(Focus::now()); - state.last_click = Some(click); - state.drag_click = Some(click.kind()); + ) { + match update { + Update::Click(click) => { + let action = match click.kind() { + mouse::click::Kind::Single => { + Action::Click(click.position()) + } + mouse::click::Kind::Double => Action::SelectWord, + mouse::click::Kind::Triple => Action::SelectLine, + }; - shell.publish(on_edit(action)); - } - Update::Drag(position) => { - shell.publish(on_edit(Action::Drag(position))); - } - Update::Release => { - state.drag_click = None; - } - Update::Scroll(lines) => { - let bounds = self.content.0.borrow().editor.bounds(); + state.focus = Some(Focus::now()); + state.last_click = Some(click); + state.drag_click = Some(click.kind()); - if bounds.height >= i32::MAX as f32 { - return event::Status::Ignored; + shell.publish(on_edit(action)); + shell.capture_event(); + } + Update::Drag(position) => { + shell.publish(on_edit(Action::Drag(position))); } + Update::Release => { + state.drag_click = None; + } + Update::Scroll(lines) => { + let bounds = self.content.0.borrow().editor.bounds(); - let lines = lines + state.partial_scroll; - state.partial_scroll = lines.fract(); + if bounds.height >= i32::MAX as f32 { + return; + } - shell.publish(on_edit(Action::Scroll { - lines: lines as i32, - })); - } - Update::Binding(binding) => { - fn apply_binding< - H: text::Highlighter, - R: text::Renderer, - Message, - >( - binding: Binding<Message>, - content: &Content<R>, - state: &mut State<H>, - on_edit: &dyn Fn(Action) -> Message, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) { - let mut publish = |action| shell.publish(on_edit(action)); - - match binding { - Binding::Unfocus => { - state.focus = None; - state.drag_click = None; - } - Binding::Copy => { - if let Some(selection) = content.selection() { - clipboard.write( - clipboard::Kind::Standard, - selection, - ); - } - } - Binding::Cut => { - if let Some(selection) = content.selection() { - clipboard.write( - clipboard::Kind::Standard, - selection, - ); + let lines = lines + state.partial_scroll; + state.partial_scroll = lines.fract(); + shell.publish(on_edit(Action::Scroll { + lines: lines as i32, + })); + shell.capture_event(); + } + Update::Binding(binding) => { + fn apply_binding< + H: text::Highlighter, + R: text::Renderer, + Message, + >( + binding: Binding<Message>, + content: &Content<R>, + state: &mut State<H>, + on_edit: &dyn Fn(Action) -> Message, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) { + let mut publish = + |action| shell.publish(on_edit(action)); + + match binding { + Binding::Unfocus => { + state.focus = None; + state.drag_click = None; + } + Binding::Copy => { + if let Some(selection) = content.selection() { + clipboard.write( + clipboard::Kind::Standard, + selection, + ); + } + } + Binding::Cut => { + if let Some(selection) = content.selection() { + clipboard.write( + clipboard::Kind::Standard, + selection, + ); + + publish(Action::Edit(Edit::Delete)); + } + } + Binding::Paste => { + if let Some(contents) = + clipboard.read(clipboard::Kind::Standard) + { + publish(Action::Edit(Edit::Paste( + Arc::new(contents), + ))); + } + } + Binding::Move(motion) => { + publish(Action::Move(motion)); + } + Binding::Select(motion) => { + publish(Action::Select(motion)); + } + Binding::SelectWord => { + publish(Action::SelectWord); + } + Binding::SelectLine => { + publish(Action::SelectLine); + } + Binding::SelectAll => { + publish(Action::SelectAll); + } + Binding::Insert(c) => { + publish(Action::Edit(Edit::Insert(c))); + } + Binding::Enter => { + publish(Action::Edit(Edit::Enter)); + } + Binding::Backspace => { + publish(Action::Edit(Edit::Backspace)); + } + Binding::Delete => { publish(Action::Edit(Edit::Delete)); } - } - Binding::Paste => { - if let Some(contents) = - clipboard.read(clipboard::Kind::Standard) - { - publish(Action::Edit(Edit::Paste(Arc::new( - contents, - )))); + Binding::Sequence(sequence) => { + for binding in sequence { + apply_binding( + binding, content, state, on_edit, + clipboard, shell, + ); + } } - } - Binding::Move(motion) => { - publish(Action::Move(motion)); - } - Binding::Select(motion) => { - publish(Action::Select(motion)); - } - Binding::SelectWord => { - publish(Action::SelectWord); - } - Binding::SelectLine => { - publish(Action::SelectLine); - } - Binding::SelectAll => { - publish(Action::SelectAll); - } - Binding::Insert(c) => { - publish(Action::Edit(Edit::Insert(c))); - } - Binding::Enter => { - publish(Action::Edit(Edit::Enter)); - } - Binding::Backspace => { - publish(Action::Edit(Edit::Backspace)); - } - Binding::Delete => { - publish(Action::Edit(Edit::Delete)); - } - Binding::Sequence(sequence) => { - for binding in sequence { - apply_binding( - binding, content, state, on_edit, - clipboard, shell, - ); + Binding::Custom(message) => { + shell.publish(message); } } - Binding::Custom(message) => { - shell.publish(message); - } } - } - apply_binding( - binding, - self.content, - state, - on_edit, - clipboard, - shell, - ); + apply_binding( + binding, + self.content, + state, + on_edit, + clipboard, + shell, + ); + + if let Some(focus) = &mut state.focus { + focus.updated_at = Instant::now(); + } - if let Some(focus) = &mut state.focus { - focus.updated_at = Instant::now(); + shell.capture_event(); } } } - event::Status::Captured + let status = { + let is_disabled = self.on_edit.is_none(); + let is_hovered = cursor.is_over(layout.bounds()); + + if is_disabled { + Status::Disabled + } else if state.focus.is_some() { + Status::Focused { is_hovered } + } else if is_hovered { + Status::Hovered + } else { + Status::Active + } + }; + + if is_redraw { + self.last_status = Some(status); + } else if self + .last_status + .is_some_and(|last_status| status != last_status) + { + shell.request_redraw(); + } } fn draw( @@ -808,7 +839,7 @@ where theme: &Theme, _defaults: &renderer::Style, layout: Layout<'_>, - cursor: mouse::Cursor, + _cursor: mouse::Cursor, _viewport: &Rectangle, ) { let bounds = layout.bounds(); @@ -824,20 +855,8 @@ where |highlight| (self.highlighter_format)(highlight, theme), ); - let is_disabled = self.on_edit.is_none(); - let is_mouse_over = cursor.is_over(bounds); - - let status = if is_disabled { - Status::Disabled - } else if state.focus.is_some() { - Status::Focused - } else if is_mouse_over { - Status::Hovered - } else { - Status::Active - }; - - let style = theme.style(&self.class, status); + let style = theme + .style(&self.class, self.last_status.unwrap_or(Status::Active)); renderer.fill_quad( renderer::Quad { @@ -1036,7 +1055,7 @@ impl<Message> Binding<Message> { status, } = event; - if status != Status::Focused { + if !matches!(status, Status::Focused { .. }) { return None; } @@ -1176,7 +1195,9 @@ impl<Message> Update<Message> { .. }) => { let status = if state.focus.is_some() { - Status::Focused + Status::Focused { + is_hovered: cursor.is_over(bounds), + } } else { Status::Active }; @@ -1222,7 +1243,10 @@ pub enum Status { /// The [`TextEditor`] is being hovered. Hovered, /// The [`TextEditor`] is focused. - Focused, + Focused { + /// Whether the [`TextEditor`] is hovered, while focused. + is_hovered: bool, + }, /// The [`TextEditor`] cannot be interacted with. Disabled, } @@ -1297,7 +1321,7 @@ pub fn default(theme: &Theme, status: Status) -> Style { }, ..active }, - Status::Focused => Style { + Status::Focused { .. } => Style { border: Border { color: palette.primary.strong.color, ..active.border |