-
-
Notifications
You must be signed in to change notification settings - Fork 944
core: Implement handling of text control input #11059
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7796588
3f5b2b5
fa41d3a
ec0d9b3
a36aad0
49cf622
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -17,7 +17,7 @@ use crate::display_object::interactive::{ | |||||||||||||||||||
| }; | ||||||||||||||||||||
| use crate::display_object::{DisplayObjectBase, DisplayObjectPtr, TDisplayObject}; | ||||||||||||||||||||
| use crate::drawing::Drawing; | ||||||||||||||||||||
| use crate::events::{ButtonKeyCode, ClipEvent, ClipEventResult, KeyCode}; | ||||||||||||||||||||
| use crate::events::{ClipEvent, ClipEventResult, TextControlCode}; | ||||||||||||||||||||
| use crate::font::{round_down_to_pixel, Glyph, TextRenderSettings}; | ||||||||||||||||||||
| use crate::html::{BoxBounds, FormatSpans, LayoutBox, LayoutContent, LayoutMetrics, TextFormat}; | ||||||||||||||||||||
| use crate::prelude::*; | ||||||||||||||||||||
|
|
@@ -1174,15 +1174,143 @@ impl<'gc> EditText<'gc> { | |||||||||||||||||||
| None | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| pub fn text_input(self, character: char, context: &mut UpdateContext<'_, 'gc>) { | ||||||||||||||||||||
| if self.0.read().flags.contains(EditTextFlag::READ_ONLY) { | ||||||||||||||||||||
| /// The number of characters that currently can be inserted, considering `TextField.maxChars` | ||||||||||||||||||||
| /// constraint, current text length, and current text selection length. | ||||||||||||||||||||
| fn available_chars(self) -> usize { | ||||||||||||||||||||
n0samu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||
| let read = self.0.read(); | ||||||||||||||||||||
| let max_chars = read.max_chars; | ||||||||||||||||||||
| if max_chars == 0 { | ||||||||||||||||||||
| usize::MAX | ||||||||||||||||||||
| } else { | ||||||||||||||||||||
| let text_len = read.text_spans.text().len() as i32; | ||||||||||||||||||||
| let selection_len = if let Some(selection) = self.selection() { | ||||||||||||||||||||
| (selection.end() - selection.start()) as i32 | ||||||||||||||||||||
| } else { | ||||||||||||||||||||
| 0 | ||||||||||||||||||||
| }; | ||||||||||||||||||||
| 0.max(max_chars.max(0) - (text_len - selection_len)) as usize | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| pub fn text_control_input( | ||||||||||||||||||||
|
||||||||||||||||||||
| self, | ||||||||||||||||||||
| control_code: TextControlCode, | ||||||||||||||||||||
| context: &mut UpdateContext<'_, 'gc>, | ||||||||||||||||||||
| ) { | ||||||||||||||||||||
| if !self.is_editable() && control_code.is_edit_input() { | ||||||||||||||||||||
| return; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if let Some(selection) = self.selection() { | ||||||||||||||||||||
| let mut changed = false; | ||||||||||||||||||||
| match character as u8 { | ||||||||||||||||||||
| 8 | 127 if !selection.is_caret() => { | ||||||||||||||||||||
| let is_selectable = self.is_selectable(); | ||||||||||||||||||||
| match control_code { | ||||||||||||||||||||
| TextControlCode::MoveLeft => { | ||||||||||||||||||||
| let new_pos = if selection.is_caret() && selection.to > 0 { | ||||||||||||||||||||
n0samu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||
| string_utils::prev_char_boundary(&self.text(), selection.to) | ||||||||||||||||||||
| } else { | ||||||||||||||||||||
| selection.start() | ||||||||||||||||||||
| }; | ||||||||||||||||||||
| self.set_selection( | ||||||||||||||||||||
| Some(TextSelection::for_position(new_pos)), | ||||||||||||||||||||
| context.gc_context, | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| TextControlCode::MoveRight => { | ||||||||||||||||||||
| let new_pos = if selection.is_caret() && selection.to < self.text().len() { | ||||||||||||||||||||
| string_utils::next_char_boundary(&self.text(), selection.to) | ||||||||||||||||||||
| } else { | ||||||||||||||||||||
| selection.end() | ||||||||||||||||||||
| }; | ||||||||||||||||||||
| self.set_selection( | ||||||||||||||||||||
| Some(TextSelection::for_position(new_pos)), | ||||||||||||||||||||
| context.gc_context, | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| TextControlCode::SelectLeft => { | ||||||||||||||||||||
| if is_selectable && selection.to > 0 { | ||||||||||||||||||||
| let new_pos = string_utils::prev_char_boundary(&self.text(), selection.to); | ||||||||||||||||||||
| self.set_selection( | ||||||||||||||||||||
| Some(TextSelection::for_range(selection.from, new_pos)), | ||||||||||||||||||||
| context.gc_context, | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| TextControlCode::SelectRight => { | ||||||||||||||||||||
| if is_selectable && selection.to < self.text().len() { | ||||||||||||||||||||
| let new_pos = string_utils::next_char_boundary(&self.text(), selection.to); | ||||||||||||||||||||
| self.set_selection( | ||||||||||||||||||||
| Some(TextSelection::for_range(selection.from, new_pos)), | ||||||||||||||||||||
| context.gc_context, | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| TextControlCode::SelectAll => { | ||||||||||||||||||||
| if is_selectable { | ||||||||||||||||||||
| self.set_selection( | ||||||||||||||||||||
| Some(TextSelection::for_range(0, self.text().len())), | ||||||||||||||||||||
| context.gc_context, | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| TextControlCode::Copy => { | ||||||||||||||||||||
| if !selection.is_caret() { | ||||||||||||||||||||
| let text = &self.text()[selection.start()..selection.end()]; | ||||||||||||||||||||
| context.ui.set_clipboard_content(text.to_string()); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| TextControlCode::Paste => { | ||||||||||||||||||||
| let text = &context.ui.clipboard_content(); | ||||||||||||||||||||
| // TODO: To match Flash Player, we should truncate pasted text that is longer than max_chars | ||||||||||||||||||||
| // instead of canceling the paste action entirely | ||||||||||||||||||||
| if text.len() <= self.available_chars() { | ||||||||||||||||||||
| self.replace_text( | ||||||||||||||||||||
| selection.start(), | ||||||||||||||||||||
| selection.end(), | ||||||||||||||||||||
| &WString::from_utf8(text), | ||||||||||||||||||||
| context, | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| let new_pos = selection.start() + text.len(); | ||||||||||||||||||||
| if is_selectable { | ||||||||||||||||||||
| self.set_selection( | ||||||||||||||||||||
| Some(TextSelection::for_position(new_pos)), | ||||||||||||||||||||
| context.gc_context, | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| } else { | ||||||||||||||||||||
| self.set_selection( | ||||||||||||||||||||
| Some(TextSelection::for_position(self.text().len())), | ||||||||||||||||||||
|
||||||||||||||||||||
| context.gc_context, | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| changed = true; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| TextControlCode::Cut => { | ||||||||||||||||||||
| if !selection.is_caret() { | ||||||||||||||||||||
| let text = &self.text()[selection.start()..selection.end()]; | ||||||||||||||||||||
| context.ui.set_clipboard_content(text.to_string()); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| self.replace_text( | ||||||||||||||||||||
| selection.start(), | ||||||||||||||||||||
| selection.end(), | ||||||||||||||||||||
| WStr::empty(), | ||||||||||||||||||||
| context, | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| if is_selectable { | ||||||||||||||||||||
| self.set_selection( | ||||||||||||||||||||
| Some(TextSelection::for_position(selection.start())), | ||||||||||||||||||||
| context.gc_context, | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| } else { | ||||||||||||||||||||
| self.set_selection( | ||||||||||||||||||||
| Some(TextSelection::for_position(self.text().len())), | ||||||||||||||||||||
| context.gc_context, | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| changed = true; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| TextControlCode::Backspace | TextControlCode::Delete if !selection.is_caret() => { | ||||||||||||||||||||
| // Backspace or delete with multiple characters selected | ||||||||||||||||||||
| self.replace_text(selection.start(), selection.end(), WStr::empty(), context); | ||||||||||||||||||||
| self.set_selection( | ||||||||||||||||||||
|
|
@@ -1191,7 +1319,7 @@ impl<'gc> EditText<'gc> { | |||||||||||||||||||
| ); | ||||||||||||||||||||
| changed = true; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| 8 => { | ||||||||||||||||||||
| TextControlCode::Backspace => { | ||||||||||||||||||||
| // Backspace with caret | ||||||||||||||||||||
| if selection.start() > 0 { | ||||||||||||||||||||
| // Delete previous character | ||||||||||||||||||||
|
|
@@ -1205,7 +1333,7 @@ impl<'gc> EditText<'gc> { | |||||||||||||||||||
| changed = true; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| 127 => { | ||||||||||||||||||||
| TextControlCode::Delete => { | ||||||||||||||||||||
| // Delete with caret | ||||||||||||||||||||
| if selection.end() < self.text_length() { | ||||||||||||||||||||
| // Delete next character | ||||||||||||||||||||
|
|
@@ -1216,27 +1344,39 @@ impl<'gc> EditText<'gc> { | |||||||||||||||||||
| changed = true; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| _ => {} | ||||||||||||||||||||
| } | ||||||||||||||||||||
| if changed { | ||||||||||||||||||||
| let mut activation = Avm1Activation::from_nothing( | ||||||||||||||||||||
|
||||||||||||||||||||
| if changed { | |
| let mut activation = Avm1Activation::from_nothing( | |
| context.reborrow(), | |
| ActivationIdentifier::root("[Propagate Text Binding]"), | |
| self.into(), | |
| ); | |
| self.propagate_text_binding(&mut activation); | |
| self.on_changed(&mut activation); | |
| } |
I don't know too much about this but I think if it's fine there it's fine here. And well, implementing whatever AVM2 feature/event may rely on this could be done in a separate PR, if that's indeed something that's missing (I have no idea)
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Nit: Would a (maybe one-line) comment to document this function be helpful?).
Uh oh!
There was an error while loading. Please reload this page.