Implement reactive-rendering for text_editor
This commit is contained in:
parent
d5a886dbcb
commit
3482ffecdc
1 changed files with 166 additions and 143 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue