inital patch by Finnerale
This commit is contained in:
parent
d6c2b1121c
commit
b2344a852e
@ -226,7 +226,53 @@ where
|
|||||||
self.font,
|
self.font,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.state.cursor_position = find_cursor_position(
|
self.state.cursor_position =
|
||||||
|
Cursor::Index(find_cursor_position(
|
||||||
|
renderer,
|
||||||
|
target + offset,
|
||||||
|
&value,
|
||||||
|
size,
|
||||||
|
0,
|
||||||
|
self.value.len(),
|
||||||
|
self.font,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
self.state.cursor_position = Cursor::Index(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state.is_pressed = is_clicked;
|
||||||
|
self.state.is_focused = is_clicked;
|
||||||
|
}
|
||||||
|
Event::Mouse(mouse::Event::Input {
|
||||||
|
button: mouse::Button::Left,
|
||||||
|
state: ButtonState::Released,
|
||||||
|
}) => {
|
||||||
|
self.state.is_pressed = false;
|
||||||
|
}
|
||||||
|
Event::Mouse(mouse::Event::CursorMoved { x, .. }) => {
|
||||||
|
if self.state.is_pressed {
|
||||||
|
let text_layout = layout.children().next().unwrap();
|
||||||
|
let target = x - text_layout.bounds().x;
|
||||||
|
|
||||||
|
if target > 0.0 {
|
||||||
|
let value = if self.is_secure {
|
||||||
|
self.value.secure()
|
||||||
|
} else {
|
||||||
|
self.value.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let size = self.size.unwrap_or(renderer.default_size());
|
||||||
|
|
||||||
|
let offset = renderer.offset(
|
||||||
|
text_layout.bounds(),
|
||||||
|
size,
|
||||||
|
&value,
|
||||||
|
&self.state,
|
||||||
|
self.font,
|
||||||
|
);
|
||||||
|
|
||||||
|
let pos = find_cursor_position(
|
||||||
renderer,
|
renderer,
|
||||||
target + offset,
|
target + offset,
|
||||||
&value,
|
&value,
|
||||||
@ -235,21 +281,34 @@ where
|
|||||||
self.value.len(),
|
self.value.len(),
|
||||||
self.font,
|
self.font,
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
self.state.cursor_position = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.state.is_focused = is_clicked;
|
let start = match self.state.cursor_position {
|
||||||
|
Cursor::Index(index) => index,
|
||||||
|
Cursor::Selection { start, .. } => start,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.state.cursor_position =
|
||||||
|
Cursor::Selection { start, end: pos }.simplify();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Event::Keyboard(keyboard::Event::CharacterReceived(c))
|
Event::Keyboard(keyboard::Event::CharacterReceived(c))
|
||||||
if self.state.is_focused
|
if self.state.is_focused
|
||||||
&& self.state.is_pasting.is_none()
|
&& self.state.is_pasting.is_none()
|
||||||
&& !c.is_control() =>
|
&& !c.is_control() =>
|
||||||
{
|
{
|
||||||
let cursor_position = self.state.cursor_position(&self.value);
|
let cursor = self.state.cursor_position(&self.value);
|
||||||
|
|
||||||
self.value.insert(cursor_position, c);
|
match cursor {
|
||||||
|
Cursor::Index(index) => {
|
||||||
|
self.value.insert(index, c);
|
||||||
|
}
|
||||||
|
Cursor::Selection { .. } => {
|
||||||
|
self.state.move_cursor_left(&self.value);
|
||||||
|
self.value.remove_many(cursor.left(), cursor.right());
|
||||||
|
self.value.insert(cursor.left(), c);
|
||||||
|
}
|
||||||
|
}
|
||||||
self.state.move_cursor_right(&self.value);
|
self.state.move_cursor_right(&self.value);
|
||||||
|
|
||||||
let message = (self.on_change)(self.value.to_string());
|
let message = (self.on_change)(self.value.to_string());
|
||||||
@ -266,28 +325,47 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
keyboard::KeyCode::Backspace => {
|
keyboard::KeyCode::Backspace => {
|
||||||
let cursor_position =
|
let cursor = self.state.cursor_position(&self.value);
|
||||||
self.state.cursor_position(&self.value);
|
|
||||||
|
|
||||||
if cursor_position > 0 {
|
match cursor {
|
||||||
|
Cursor::Index(index) if index > 0 => {
|
||||||
self.state.move_cursor_left(&self.value);
|
self.state.move_cursor_left(&self.value);
|
||||||
|
let _ = self.value.remove(index - 1);
|
||||||
let _ = self.value.remove(cursor_position - 1);
|
let message =
|
||||||
|
(self.on_change)(self.value.to_string());
|
||||||
let message = (self.on_change)(self.value.to_string());
|
|
||||||
messages.push(message);
|
messages.push(message);
|
||||||
}
|
}
|
||||||
|
Cursor::Selection { .. } => {
|
||||||
|
self.state.move_cursor_left(&self.value);
|
||||||
|
self.value
|
||||||
|
.remove_many(cursor.left(), cursor.right());
|
||||||
|
let message =
|
||||||
|
(self.on_change)(self.value.to_string());
|
||||||
|
messages.push(message);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
keyboard::KeyCode::Delete => {
|
keyboard::KeyCode::Delete => {
|
||||||
let cursor_position =
|
let cursor = self.state.cursor_position(&self.value);
|
||||||
self.state.cursor_position(&self.value);
|
|
||||||
|
|
||||||
if cursor_position < self.value.len() {
|
match cursor {
|
||||||
let _ = self.value.remove(cursor_position);
|
Cursor::Index(index) if index < self.value.len() => {
|
||||||
|
let _ = self.value.remove(index);
|
||||||
let message = (self.on_change)(self.value.to_string());
|
let message =
|
||||||
|
(self.on_change)(self.value.to_string());
|
||||||
messages.push(message);
|
messages.push(message);
|
||||||
}
|
}
|
||||||
|
Cursor::Selection { .. } => {
|
||||||
|
self.state.move_cursor_left(&self.value);
|
||||||
|
self.value
|
||||||
|
.remove_many(cursor.left(), cursor.right());
|
||||||
|
let message =
|
||||||
|
(self.on_change)(self.value.to_string());
|
||||||
|
messages.push(message);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
keyboard::KeyCode::Left => {
|
keyboard::KeyCode::Left => {
|
||||||
if platform::is_jump_modifier_pressed(modifiers)
|
if platform::is_jump_modifier_pressed(modifiers)
|
||||||
@ -308,7 +386,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
keyboard::KeyCode::Home => {
|
keyboard::KeyCode::Home => {
|
||||||
self.state.cursor_position = 0;
|
self.state.cursor_position = Cursor::Index(0);
|
||||||
}
|
}
|
||||||
keyboard::KeyCode::End => {
|
keyboard::KeyCode::End => {
|
||||||
self.state.move_cursor_to_end(&self.value);
|
self.state.move_cursor_to_end(&self.value);
|
||||||
@ -333,8 +411,19 @@ where
|
|||||||
let cursor_position =
|
let cursor_position =
|
||||||
self.state.cursor_position(&self.value);
|
self.state.cursor_position(&self.value);
|
||||||
|
|
||||||
|
let insert_position = match cursor_position {
|
||||||
|
Cursor::Index(index) => index,
|
||||||
|
Cursor::Selection { .. } => {
|
||||||
|
self.state.move_cursor_left(&self.value);
|
||||||
|
self.value.remove_many(
|
||||||
|
cursor_position.left(),
|
||||||
|
cursor_position.right(),
|
||||||
|
);
|
||||||
|
cursor_position.left()
|
||||||
|
}
|
||||||
|
};
|
||||||
self.value
|
self.value
|
||||||
.insert_many(cursor_position, content.clone());
|
.insert_many(insert_position, content.clone());
|
||||||
|
|
||||||
self.state.move_cursor_right_by_amount(
|
self.state.move_cursor_right_by_amount(
|
||||||
&self.value,
|
&self.value,
|
||||||
@ -499,11 +588,75 @@ where
|
|||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
is_focused: bool,
|
is_focused: bool,
|
||||||
|
is_pressed: bool,
|
||||||
is_pasting: Option<Value>,
|
is_pasting: Option<Value>,
|
||||||
cursor_position: usize,
|
cursor_position: Cursor,
|
||||||
// TODO: Add stateful horizontal scrolling offset
|
// TODO: Add stateful horizontal scrolling offset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The cursor position of a [`TextInput`].
|
||||||
|
///
|
||||||
|
/// [`TextInput`]: struct.TextInput.html
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Cursor {
|
||||||
|
/// The cursor represents a position.
|
||||||
|
Index(usize),
|
||||||
|
/// The cursor represents a range.
|
||||||
|
Selection {
|
||||||
|
/// Where the selection started.
|
||||||
|
start: usize,
|
||||||
|
/// Where the selection was moved to.
|
||||||
|
end: usize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Cursor {
|
||||||
|
fn default() -> Self {
|
||||||
|
Cursor::Index(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cursor {
|
||||||
|
/// Simplify representation to `Cursor::Index`
|
||||||
|
/// if `start` and `end` are the same.
|
||||||
|
pub fn simplify(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Cursor::Index(_) => *self,
|
||||||
|
Cursor::Selection { start, end } => {
|
||||||
|
if start == end {
|
||||||
|
Cursor::Index(*start)
|
||||||
|
} else {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Position at which the cursor should be drawn.
|
||||||
|
pub fn position(&self) -> usize {
|
||||||
|
match *self {
|
||||||
|
Cursor::Index(index) => index,
|
||||||
|
Cursor::Selection { end, .. } => end,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The cursor index or left end of the selection.
|
||||||
|
pub fn left(&self) -> usize {
|
||||||
|
match *self {
|
||||||
|
Cursor::Index(index) => index,
|
||||||
|
Cursor::Selection { start, end } => start.min(end),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The cursor index or right end of the selection.
|
||||||
|
pub fn right(&self) -> usize {
|
||||||
|
match *self {
|
||||||
|
Cursor::Index(index) => index,
|
||||||
|
Cursor::Selection { start, end } => start.max(end),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
/// Creates a new [`State`], representing an unfocused [`TextInput`].
|
/// Creates a new [`State`], representing an unfocused [`TextInput`].
|
||||||
///
|
///
|
||||||
@ -520,8 +673,9 @@ impl State {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
is_focused: true,
|
is_focused: true,
|
||||||
|
is_pressed: false,
|
||||||
is_pasting: None,
|
is_pasting: None,
|
||||||
cursor_position: usize::MAX,
|
cursor_position: Cursor::Index(usize::MAX),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,8 +689,15 @@ impl State {
|
|||||||
/// Returns the cursor position of a [`TextInput`].
|
/// Returns the cursor position of a [`TextInput`].
|
||||||
///
|
///
|
||||||
/// [`TextInput`]: struct.TextInput.html
|
/// [`TextInput`]: struct.TextInput.html
|
||||||
pub fn cursor_position(&self, value: &Value) -> usize {
|
pub fn cursor_position(&self, value: &Value) -> Cursor {
|
||||||
self.cursor_position.min(value.len())
|
match self.cursor_position {
|
||||||
|
Cursor::Index(index) => Cursor::Index(index.min(value.len())),
|
||||||
|
Cursor::Selection { start, end } => Cursor::Selection {
|
||||||
|
start: start.min(value.len()),
|
||||||
|
end: end.min(value.len()),
|
||||||
|
}
|
||||||
|
.simplify(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves the cursor of a [`TextInput`] to the left.
|
/// Moves the cursor of a [`TextInput`] to the left.
|
||||||
@ -545,8 +706,10 @@ impl State {
|
|||||||
pub(crate) fn move_cursor_left(&mut self, value: &Value) {
|
pub(crate) fn move_cursor_left(&mut self, value: &Value) {
|
||||||
let current = self.cursor_position(value);
|
let current = self.cursor_position(value);
|
||||||
|
|
||||||
if current > 0 {
|
self.cursor_position = match current {
|
||||||
self.cursor_position = current - 1;
|
Cursor::Index(index) if index > 0 => Cursor::Index(index - 1),
|
||||||
|
Cursor::Selection { .. } => Cursor::Index(current.left()),
|
||||||
|
_ => Cursor::Index(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -563,11 +726,12 @@ impl State {
|
|||||||
amount: usize,
|
amount: usize,
|
||||||
) {
|
) {
|
||||||
let current = self.cursor_position(value);
|
let current = self.cursor_position(value);
|
||||||
let new_position = current.saturating_add(amount);
|
self.cursor_position = match current {
|
||||||
|
Cursor::Index(index) => {
|
||||||
if new_position < value.len() + 1 {
|
Cursor::Index(index.saturating_add(amount).min(value.len()))
|
||||||
self.cursor_position = new_position;
|
|
||||||
}
|
}
|
||||||
|
Cursor::Selection { .. } => Cursor::Index(current.right()),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves the cursor of a [`TextInput`] to the previous start of a word.
|
/// Moves the cursor of a [`TextInput`] to the previous start of a word.
|
||||||
@ -576,7 +740,8 @@ impl State {
|
|||||||
pub(crate) fn move_cursor_left_by_words(&mut self, value: &Value) {
|
pub(crate) fn move_cursor_left_by_words(&mut self, value: &Value) {
|
||||||
let current = self.cursor_position(value);
|
let current = self.cursor_position(value);
|
||||||
|
|
||||||
self.cursor_position = value.previous_start_of_word(current);
|
self.cursor_position =
|
||||||
|
Cursor::Index(value.previous_start_of_word(current.left()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves the cursor of a [`TextInput`] to the next end of a word.
|
/// Moves the cursor of a [`TextInput`] to the next end of a word.
|
||||||
@ -585,14 +750,15 @@ impl State {
|
|||||||
pub(crate) fn move_cursor_right_by_words(&mut self, value: &Value) {
|
pub(crate) fn move_cursor_right_by_words(&mut self, value: &Value) {
|
||||||
let current = self.cursor_position(value);
|
let current = self.cursor_position(value);
|
||||||
|
|
||||||
self.cursor_position = value.next_end_of_word(current);
|
self.cursor_position =
|
||||||
|
Cursor::Index(value.next_end_of_word(current.right()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves the cursor of a [`TextInput`] to the end.
|
/// Moves the cursor of a [`TextInput`] to the end.
|
||||||
///
|
///
|
||||||
/// [`TextInput`]: struct.TextInput.html
|
/// [`TextInput`]: struct.TextInput.html
|
||||||
pub(crate) fn move_cursor_to_end(&mut self, value: &Value) {
|
pub(crate) fn move_cursor_to_end(&mut self, value: &Value) {
|
||||||
self.cursor_position = value.len();
|
self.cursor_position = Cursor::Index(value.len());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -711,6 +877,11 @@ impl Value {
|
|||||||
let _ = self.graphemes.remove(index);
|
let _ = self.graphemes.remove(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes the graphemes from `start` to `end`.
|
||||||
|
pub fn remove_many(&mut self, start: usize, end: usize) {
|
||||||
|
let _ = self.graphemes.splice(start..end, std::iter::empty());
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a new [`Value`] with all its graphemes replaced with the
|
/// Returns a new [`Value`] with all its graphemes replaced with the
|
||||||
/// dot ('•') character.
|
/// dot ('•') character.
|
||||||
///
|
///
|
||||||
|
@ -33,6 +33,8 @@ pub trait StyleSheet {
|
|||||||
|
|
||||||
fn value_color(&self) -> Color;
|
fn value_color(&self) -> Color;
|
||||||
|
|
||||||
|
fn selection_color(&self) -> Color;
|
||||||
|
|
||||||
/// Produces the style of an hovered text input.
|
/// Produces the style of an hovered text input.
|
||||||
fn hovered(&self) -> Style {
|
fn hovered(&self) -> Style {
|
||||||
self.focused()
|
self.focused()
|
||||||
@ -65,6 +67,10 @@ impl StyleSheet for Default {
|
|||||||
fn value_color(&self) -> Color {
|
fn value_color(&self) -> Color {
|
||||||
Color::from_rgb(0.3, 0.3, 0.3)
|
Color::from_rgb(0.3, 0.3, 0.3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn selection_color(&self) -> Color {
|
||||||
|
Color::from_rgb(0.8, 0.8, 1.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::default::Default for Box<dyn StyleSheet> {
|
impl std::default::Default for Box<dyn StyleSheet> {
|
||||||
|
@ -46,7 +46,7 @@ impl text_input::Renderer for Renderer {
|
|||||||
text_bounds,
|
text_bounds,
|
||||||
value,
|
value,
|
||||||
size,
|
size,
|
||||||
state.cursor_position(value),
|
state.cursor_position(value).position(),
|
||||||
font,
|
font,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -111,15 +111,55 @@ impl text_input::Renderer for Renderer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let (contents_primitive, offset) = if state.is_focused() {
|
let (contents_primitive, offset) = if state.is_focused() {
|
||||||
|
let cursor = state.cursor_position(value);
|
||||||
let (text_value_width, offset) = measure_cursor_and_scroll_offset(
|
let (text_value_width, offset) = measure_cursor_and_scroll_offset(
|
||||||
self,
|
self,
|
||||||
text_bounds,
|
text_bounds,
|
||||||
value,
|
value,
|
||||||
size,
|
size,
|
||||||
state.cursor_position(value),
|
cursor.position(),
|
||||||
font,
|
font,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let selection = match cursor {
|
||||||
|
text_input::Cursor::Index(_) => Primitive::None,
|
||||||
|
text_input::Cursor::Selection { .. } => {
|
||||||
|
let (cursor_left_offset, _) =
|
||||||
|
measure_cursor_and_scroll_offset(
|
||||||
|
self,
|
||||||
|
text_bounds,
|
||||||
|
value,
|
||||||
|
size,
|
||||||
|
cursor.left(),
|
||||||
|
font,
|
||||||
|
);
|
||||||
|
let (cursor_right_offset, _) =
|
||||||
|
measure_cursor_and_scroll_offset(
|
||||||
|
self,
|
||||||
|
text_bounds,
|
||||||
|
value,
|
||||||
|
size,
|
||||||
|
cursor.right(),
|
||||||
|
font,
|
||||||
|
);
|
||||||
|
let width = cursor_right_offset - cursor_left_offset;
|
||||||
|
Primitive::Quad {
|
||||||
|
bounds: Rectangle {
|
||||||
|
x: text_bounds.x + cursor_left_offset,
|
||||||
|
y: text_bounds.y,
|
||||||
|
width,
|
||||||
|
height: text_bounds.height,
|
||||||
|
},
|
||||||
|
background: Background::Color(
|
||||||
|
style_sheet.selection_color(),
|
||||||
|
),
|
||||||
|
border_radius: 0,
|
||||||
|
border_width: 0,
|
||||||
|
border_color: Color::TRANSPARENT,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let cursor = Primitive::Quad {
|
let cursor = Primitive::Quad {
|
||||||
bounds: Rectangle {
|
bounds: Rectangle {
|
||||||
x: text_bounds.x + text_value_width,
|
x: text_bounds.x + text_value_width,
|
||||||
@ -135,7 +175,7 @@ impl text_input::Renderer for Renderer {
|
|||||||
|
|
||||||
(
|
(
|
||||||
Primitive::Group {
|
Primitive::Group {
|
||||||
primitives: vec![text_value, cursor],
|
primitives: vec![selection, text_value, cursor],
|
||||||
},
|
},
|
||||||
Vector::new(offset as u32, 0),
|
Vector::new(offset as u32, 0),
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user