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,
|
&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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue