Implement reactive-rendering for text_editor

This commit is contained in:
Héctor Ramón Jiménez 2024-11-04 23:21:06 +01:00
parent d5a886dbcb
commit 3482ffecdc
No known key found for this signature in database
GPG key ID: 4C07CEC81AFA161F

View file

@ -119,6 +119,7 @@ pub struct TextEditor<
&Highlighter::Highlight, &Highlighter::Highlight,
&Theme, &Theme,
) -> highlighter::Format<Renderer::Font>, ) -> highlighter::Format<Renderer::Font>,
last_status: Option<Status>,
} }
impl<'a, Message, Theme, Renderer> impl<'a, Message, Theme, Renderer>
@ -146,6 +147,7 @@ where
highlighter_format: |_highlight, _theme| { highlighter_format: |_highlight, _theme| {
highlighter::Format::default() highlighter::Format::default()
}, },
last_status: None,
} }
} }
} }
@ -269,6 +271,7 @@ where
on_edit: self.on_edit, on_edit: self.on_edit,
highlighter_settings: settings, highlighter_settings: settings,
highlighter_format: to_format, highlighter_format: to_format,
last_status: self.last_status,
} }
} }
@ -611,6 +614,10 @@ where
}; };
let state = tree.state.downcast_mut::<State<Highlighter>>(); let state = tree.state.downcast_mut::<State<Highlighter>>();
let is_redraw = matches!(
event,
Event::Window(window::Event::RedrawRequested(_now)),
);
match event { match event {
Event::Window(window::Event::Unfocused) => { Event::Window(window::Event::Unfocused) => {
@ -647,157 +654,180 @@ where
_ => {} _ => {}
} }
let Some(update) = Update::from_event( if let Some(update) = Update::from_event(
event, event,
state, state,
layout.bounds(), layout.bounds(),
self.padding, self.padding,
cursor, cursor,
self.key_binding.as_deref(), self.key_binding.as_deref(),
) else { ) {
return; shell.capture_event();
};
match update { match update {
Update::Click(click) => { Update::Click(click) => {
let action = match click.kind() { let action = match click.kind() {
mouse::click::Kind::Single => { mouse::click::Kind::Single => {
Action::Click(click.position()) Action::Click(click.position())
} }
mouse::click::Kind::Double => Action::SelectWord, mouse::click::Kind::Double => Action::SelectWord,
mouse::click::Kind::Triple => Action::SelectLine, mouse::click::Kind::Triple => Action::SelectLine,
}; };
state.focus = Some(Focus::now()); state.focus = Some(Focus::now());
state.last_click = Some(click); state.last_click = Some(click);
state.drag_click = Some(click.kind()); state.drag_click = Some(click.kind());
shell.publish(on_edit(action)); 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();
if bounds.height >= i32::MAX as f32 {
return;
} }
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; if bounds.height >= i32::MAX as f32 {
state.partial_scroll = lines.fract(); return;
}
shell.publish(on_edit(Action::Scroll { let lines = lines + state.partial_scroll;
lines: lines as i32, state.partial_scroll = lines.fract();
}));
}
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 { shell.publish(on_edit(Action::Scroll {
Binding::Unfocus => { lines: lines as i32,
state.focus = None; }));
state.drag_click = None; }
} Update::Binding(binding) => {
Binding::Copy => { fn apply_binding<
if let Some(selection) = content.selection() { H: text::Highlighter,
clipboard.write( R: text::Renderer,
clipboard::Kind::Standard, Message,
selection, >(
); 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 => {
Binding::Cut => { if let Some(selection) = content.selection() {
if let Some(selection) = content.selection() { clipboard.write(
clipboard.write( clipboard::Kind::Standard,
clipboard::Kind::Standard, selection,
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)); publish(Action::Edit(Edit::Delete));
} }
} Binding::Sequence(sequence) => {
Binding::Paste => { for binding in sequence {
if let Some(contents) = apply_binding(
clipboard.read(clipboard::Kind::Standard) binding, content, state, on_edit,
{ clipboard, shell,
publish(Action::Edit(Edit::Paste(Arc::new( );
contents, }
))));
} }
} Binding::Custom(message) => {
Binding::Move(motion) => { shell.publish(message);
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);
}
} }
}
apply_binding( apply_binding(
binding, binding,
self.content, self.content,
state, state,
on_edit, on_edit,
clipboard, clipboard,
shell, shell,
); );
if let Some(focus) = &mut state.focus { if let Some(focus) = &mut state.focus {
focus.updated_at = Instant::now(); focus.updated_at = Instant::now();
}
} }
} }
} }
shell.capture_event(); 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( fn draw(
@ -807,7 +837,7 @@ where
theme: &Theme, theme: &Theme,
_defaults: &renderer::Style, _defaults: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, _cursor: mouse::Cursor,
_viewport: &Rectangle, _viewport: &Rectangle,
) { ) {
let bounds = layout.bounds(); let bounds = layout.bounds();
@ -823,20 +853,8 @@ where
|highlight| (self.highlighter_format)(highlight, theme), |highlight| (self.highlighter_format)(highlight, theme),
); );
let is_disabled = self.on_edit.is_none(); let style = theme
let is_mouse_over = cursor.is_over(bounds); .style(&self.class, self.last_status.unwrap_or(Status::Active));
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);
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
@ -1035,7 +1053,7 @@ impl<Message> Binding<Message> {
status, status,
} = event; } = event;
if status != Status::Focused { if !matches!(status, Status::Focused { .. }) {
return None; return None;
} }
@ -1175,7 +1193,9 @@ impl<Message> Update<Message> {
.. ..
}) => { }) => {
let status = if state.focus.is_some() { let status = if state.focus.is_some() {
Status::Focused Status::Focused {
is_hovered: cursor.is_over(bounds),
}
} else { } else {
Status::Active Status::Active
}; };
@ -1221,7 +1241,10 @@ pub enum Status {
/// The [`TextEditor`] is being hovered. /// The [`TextEditor`] is being hovered.
Hovered, Hovered,
/// The [`TextEditor`] is focused. /// The [`TextEditor`] is focused.
Focused, Focused {
/// Whether the [`TextEditor`] is hovered, while focused.
is_hovered: bool,
},
/// The [`TextEditor`] cannot be interacted with. /// The [`TextEditor`] cannot be interacted with.
Disabled, Disabled,
} }
@ -1296,7 +1319,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
}, },
..active ..active
}, },
Status::Focused => Style { Status::Focused { .. } => Style {
border: Border { border: Border {
color: palette.primary.strong.color, color: palette.primary.strong.color,
..active.border ..active.border