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,
&Theme,
) -> highlighter::Format<Renderer::Font>,
last_status: Option<Status>,
}
impl<'a, Message, Theme, Renderer>
@ -146,6 +147,7 @@ where
highlighter_format: |_highlight, _theme| {
highlighter::Format::default()
},
last_status: None,
}
}
}
@ -269,6 +271,7 @@ where
on_edit: self.on_edit,
highlighter_settings: settings,
highlighter_format: to_format,
last_status: self.last_status,
}
}
@ -611,6 +614,10 @@ where
};
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) => {
@ -647,157 +654,180 @@ where
_ => {}
}
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;
};
) {
shell.capture_event();
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,
};
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());
state.focus = Some(Focus::now());
state.last_click = Some(click);
state.drag_click = Some(click.kind());
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;
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();
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));
let lines = lines + state.partial_scroll;
state.partial_scroll = lines.fract();
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,
);
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::Cut => {
if let Some(selection) = content.selection() {
clipboard.write(
clipboard::Kind::Standard,
selection,
);
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();
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(
@ -807,7 +837,7 @@ where
theme: &Theme,
_defaults: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
_cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
@ -823,20 +853,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 {
@ -1035,7 +1053,7 @@ impl<Message> Binding<Message> {
status,
} = event;
if status != Status::Focused {
if !matches!(status, Status::Focused { .. }) {
return None;
}
@ -1175,7 +1193,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
};
@ -1221,7 +1241,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,
}
@ -1296,7 +1319,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