Implemented PASTE_CMD for TextInput.

This commit is contained in:
Samuel Guerra 2023-05-31 16:15:37 -03:00
parent 453c0ed9ed
commit 2101834526
5 changed files with 101 additions and 23 deletions

View File

@ -1,3 +1,8 @@
* Test emoji, looks like webrender supports then.
- We need more than one "fallback" font?
- Right now we use "Segoe UI Symbol" in Windows.
- We need to fallback to "Segoe UI Emoji" instead, or have both?
# TextInput
* Implement cursor position.
@ -6,6 +11,9 @@
- Review using `TextPoint` for this?
- Remove `TextPoint`?
- Refactor `TextPointDisplay` into `CaretPosition` in the main crate.
- Implement `get_caret_position` getter property.
- Use case is display in a status bar.
- Need to find closest insert point from mouse cursor point.
- Support ligatures (click in middle works).
@ -18,12 +26,13 @@
- Observed this in Chrome, Firefox, VS and Word, use "ö̲" to test.
* Support replace (Insert mode in command line).
* Implement selection.
- Input replaces selection.
- Char input, paste, IME
- Impl cut & copy.
* Implement custom node access to text.
# Clipboard
* Implement clipboard commands.
- Already declared in the main crate (move to core).
* Implement image copy&paste in image example.
# Config

View File

@ -684,6 +684,12 @@ impl fmt::Debug for EventHandle {
f.debug_tuple("EventHandle").field(&i).finish()
}
}
/// Dummy
impl Default for EventHandle {
fn default() -> Self {
Self::dummy()
}
}
impl EventHandle {
fn new(hook: Box<dyn Fn(&mut EventUpdate) -> bool + Send + Sync>) -> (Self, EventHook) {
let rc = Arc::new(EventHandleData {

View File

@ -628,6 +628,11 @@ impl Drop for CommandHandle {
}
}
}
impl Default for CommandHandle {
fn default() -> Self {
Self::dummy()
}
}
/// Represents a reference counted `dyn Any` object.
#[derive(Clone)]

View File

@ -347,6 +347,11 @@ impl fmt::Debug for VarHandle {
f.debug_tuple("VarHandle").field(&i).finish()
}
}
impl Default for VarHandle {
fn default() -> Self {
Self::dummy()
}
}
/// Represents a collection of var handles.
#[derive(Clone, Default)]

View File

@ -3,8 +3,9 @@
use std::{fmt, sync::Arc};
use atomic::{Atomic, Ordering};
use commands::{COPY_CMD, CUT_CMD, PASTE_CMD};
use font_features::FontVariations;
use zero_ui_core::{gesture::CLICK_EVENT, keyboard::Key};
use zero_ui_core::{clipboard::CLIPBOARD, gesture::CLICK_EVENT, keyboard::Key};
use super::text_properties::*;
use crate::core::{
@ -181,11 +182,27 @@ pub fn resolve_text(child: impl UiNode, text: impl IntoVar<Txt>) -> impl UiNode
}
}
/// Data allocated only when `editable`.
#[derive(Default)]
struct EditData {
events: [EventHandle; 4],
caret_animation: VarHandle,
cut: CommandHandle,
copy: CommandHandle,
paste: CommandHandle,
}
impl EditData {
fn get(edit_data: &mut Option<Box<Self>>) -> &mut Self {
&mut *edit_data.get_or_insert_with(Default::default)
}
}
let text = text.into_var();
let mut loading_faces = None;
let mut resolved = None;
let mut event_handles = EventHandles::default();
let mut _caret_opacity_handle = None;
// Use `EditData::get` to access.
let mut edit_data = None;
match_node(child, move |child, op| match op {
UiNodeOp::Init => {
@ -226,7 +243,7 @@ pub fn resolve_text(child: impl UiNode, text: impl IntoVar<Txt>) -> impl UiNode
let editable = TEXT_EDITABLE_VAR.get();
let caret_opacity = if editable && FOCUS.focused().get().map(|p| p.widget_id()) == Some(WIDGET.id()) {
let v = KEYBOARD.caret_animation();
_caret_opacity_handle = Some(v.subscribe(UpdateOp::Update, WIDGET.id()));
EditData::get(&mut edit_data).caret_animation = v.subscribe(UpdateOp::Update, WIDGET.id());
v
} else {
var(0.fct()).read_only()
@ -243,18 +260,24 @@ pub fn resolve_text(child: impl UiNode, text: impl IntoVar<Txt>) -> impl UiNode
});
if editable {
event_handles.push(CHAR_INPUT_EVENT.subscribe(WIDGET.id()));
event_handles.push(KEY_INPUT_EVENT.subscribe(WIDGET.id()));
event_handles.push(FOCUS_CHANGED_EVENT.subscribe(WIDGET.id()));
event_handles.push(CLICK_EVENT.subscribe(WIDGET.id()));
let id = WIDGET.id();
let d = EditData::get(&mut edit_data);
d.events[0] = CHAR_INPUT_EVENT.subscribe(id);
d.events[1] = KEY_INPUT_EVENT.subscribe(id);
d.events[2] = FOCUS_CHANGED_EVENT.subscribe(id);
d.events[3] = CLICK_EVENT.subscribe(id);
d.cut = CUT_CMD.scoped(id).subscribe(true);
d.copy = COPY_CMD.scoped(id).subscribe(true);
d.paste = PASTE_CMD.scoped(id).subscribe(true);
}
RESOLVED_TEXT.with_context_opt(&mut resolved, || child.init());
}
UiNodeOp::Deinit => {
RESOLVED_TEXT.with_context_opt(&mut resolved, || child.deinit());
event_handles.clear();
_caret_opacity_handle = None;
edit_data = None;
loading_faces = None;
resolved = None;
}
@ -279,7 +302,7 @@ pub fn resolve_text(child: impl UiNode, text: impl IntoVar<Txt>) -> impl UiNode
args.propagation().stop();
let new_animation = KEYBOARD.caret_animation();
_caret_opacity_handle = Some(new_animation.subscribe(UpdateOp::Update, WIDGET.id()));
EditData::get(&mut edit_data).caret_animation = new_animation.subscribe(UpdateOp::Update, WIDGET.id());
t.caret_opacity = new_animation;
if args.is_backspace() {
@ -363,7 +386,7 @@ pub fn resolve_text(child: impl UiNode, text: impl IntoVar<Txt>) -> impl UiNode
if TEXT_EDITABLE_VAR.get() {
if args.is_focused(WIDGET.id()) {
let new_animation = KEYBOARD.caret_animation();
_caret_opacity_handle = Some(new_animation.subscribe(UpdateOp::RenderUpdate, WIDGET.id()));
EditData::get(&mut edit_data).caret_animation = new_animation.subscribe(UpdateOp::RenderUpdate, WIDGET.id());
let t = resolved.as_mut().unwrap();
t.caret_opacity = new_animation;
if t.caret_index.is_none() {
@ -372,7 +395,7 @@ pub fn resolve_text(child: impl UiNode, text: impl IntoVar<Txt>) -> impl UiNode
WIDGET.layout(); // update offset
}
} else {
_caret_opacity_handle = None;
EditData::get(&mut edit_data).caret_animation = VarHandle::dummy();
resolved.as_mut().unwrap().caret_opacity = var(0.fct()).read_only();
}
}
@ -412,6 +435,31 @@ pub fn resolve_text(child: impl UiNode, text: impl IntoVar<Txt>) -> impl UiNode
} else {
loading_faces = Some(LoadingFontFaceList::new(faces));
}
} else if let Some(args) = CUT_CMD.scoped(WIDGET.id()).on(update) {
args.propagation().stop();
tracing::error!("TODO cut");
} else if let Some(args) = COPY_CMD.scoped(WIDGET.id()).on(update) {
args.propagation().stop();
tracing::error!("TODO copy");
} else if let Some(args) = PASTE_CMD.scoped(WIDGET.id()).on(update) {
args.propagation().stop();
match CLIPBOARD.text() {
Ok(paste) => {
// insert
let t = resolved.as_mut().unwrap();
let i = t.caret_index.unwrap();
t.caret_index = Some(i + paste.len());
t.pending_layout |= PendingLayout::CARET;
let _ = text.modify(move |t| {
t.to_mut().to_mut().insert_str(i, paste.as_str());
});
}
Err(e) => {
tracing::error!("error pasting, {e}");
}
}
}
RESOLVED_TEXT.with_context_opt(&mut resolved, || child.event(update));
}
@ -472,23 +520,28 @@ pub fn resolve_text(child: impl UiNode, text: impl IntoVar<Txt>) -> impl UiNode
}
if let Some(enabled) = TEXT_EDITABLE_VAR.get_new() {
if enabled && event_handles.0.is_empty() {
if enabled && edit_data.is_none() {
// actually enabled.
let d = EditData::get(&mut edit_data);
let id = WIDGET.id();
event_handles.push(CHAR_INPUT_EVENT.subscribe(id));
event_handles.push(KEY_INPUT_EVENT.subscribe(id));
event_handles.push(FOCUS_CHANGED_EVENT.subscribe(id));
event_handles.push(CLICK_EVENT.subscribe(id));
d.events[0] = CHAR_INPUT_EVENT.subscribe(id);
d.events[1] = KEY_INPUT_EVENT.subscribe(id);
d.events[2] = FOCUS_CHANGED_EVENT.subscribe(id);
d.events[3] = CLICK_EVENT.subscribe(id);
d.cut = CUT_CMD.scoped(id).subscribe(true);
d.copy = COPY_CMD.scoped(id).subscribe(true);
d.paste = PASTE_CMD.scoped(id).subscribe(true);
if FOCUS.focused().get().map(|p| p.widget_id()) == Some(id) {
let new_animation = KEYBOARD.caret_animation();
_caret_opacity_handle = Some(new_animation.subscribe(UpdateOp::RenderUpdate, id));
d.caret_animation = new_animation.subscribe(UpdateOp::RenderUpdate, id);
r.caret_opacity = new_animation;
}
} else {
event_handles.clear();
_caret_opacity_handle = None;
edit_data = None;
r.caret_opacity = var(0.fct()).read_only();
}
}