Skip to content

Commit

Permalink
fork widget text_input
Browse files Browse the repository at this point in the history
  • Loading branch information
edouardparis committed Feb 11, 2025
1 parent b1354be commit 11dcced
Show file tree
Hide file tree
Showing 8 changed files with 1,639 additions and 22 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions liana-ui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@ edition = "2021"

[dependencies]
iced = { version = "0.13.1", default-features = false, features = ["svg", "image", "lazy", "qr_code", "advanced"] }
iced_core = { version = "0.13.2" }
iced_runtime = { version = "0.13.2" }
unicode-segmentation = "1.0"
bitcoin = "0.32"
chrono = "0.4"
3 changes: 2 additions & 1 deletion liana-ui/src/component/form.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::widget::text_input;
use bitcoin::Denomination;
use iced::{widget::text_input, Length};
use iced::Length;

use crate::{color, component::text, theme, widget::*};

Expand Down
22 changes: 1 addition & 21 deletions liana-ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,4 @@ pub mod font;
pub mod icon;
pub mod image;
pub mod theme;

pub mod widget {
#![allow(dead_code)]
use crate::theme::Theme;

pub type Renderer = iced::Renderer;
pub type Element<'a, Message> = iced::Element<'a, Message, Theme, Renderer>;
pub type Container<'a, Message> = iced::widget::Container<'a, Message, Theme, Renderer>;
pub type Column<'a, Message> = iced::widget::Column<'a, Message, Theme, Renderer>;
pub type Row<'a, Message> = iced::widget::Row<'a, Message, Theme, Renderer>;
pub type Button<'a, Message> = iced::widget::Button<'a, Message, Theme, Renderer>;
pub type CheckBox<'a, Message> = iced::widget::Checkbox<'a, Message, Theme, Renderer>;
pub type Text<'a> = iced::widget::Text<'a, Theme, Renderer>;
pub type TextInput<'a, Message> = iced::widget::TextInput<'a, Message, Theme, Renderer>;
pub type Tooltip<'a> = iced::widget::Tooltip<'a, Theme, Renderer>;
pub type ProgressBar<'a> = iced::widget::ProgressBar<'a, Theme>;
pub type PickList<'a, T, L, V, Message> =
iced::widget::PickList<'a, T, L, V, Message, Theme, Renderer>;
pub type Scrollable<'a, Message> = iced::widget::Scrollable<'a, Message, Theme, Renderer>;
pub type Svg<'a> = iced::widget::Svg<'a, Theme>;
}
pub mod widget;
183 changes: 183 additions & 0 deletions liana-ui/src/widget/cursor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
//! Track the cursor of a text input.
use iced::widget::text_input::Value;

/// The cursor of a text input.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Cursor {
state: State,
}

/// The state of a [`Cursor`].
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum State {
/// Cursor without a selection
Index(usize),

/// Cursor selecting a range of text
Selection {
/// The start of the selection
start: usize,
/// The end of the selection
end: usize,
},
}

impl Default for Cursor {
fn default() -> Self {
Cursor {
state: State::Index(0),
}
}
}

impl Cursor {
/// Returns the [`State`] of the [`Cursor`].
pub fn state(&self, value: &Value) -> State {
match self.state {
State::Index(index) => State::Index(index.min(value.len())),
State::Selection { start, end } => {
let start = start.min(value.len());
let end = end.min(value.len());

if start == end {
State::Index(start)
} else {
State::Selection { start, end }
}
}
}
}

/// Returns the current selection of the [`Cursor`] for the given [`Value`].
///
/// `start` is guaranteed to be <= than `end`.
pub fn selection(&self, value: &Value) -> Option<(usize, usize)> {
match self.state(value) {
State::Selection { start, end } => Some((start.min(end), start.max(end))),
State::Index(_) => None,
}
}

pub(crate) fn move_to(&mut self, position: usize) {
self.state = State::Index(position);
}

pub(crate) fn move_right(&mut self, value: &Value) {
self.move_right_by_amount(value, 1);
}

pub(crate) fn move_right_by_words(&mut self, value: &Value) {
self.move_to(value.next_end_of_word(self.right(value)));
}

pub(crate) fn move_right_by_amount(&mut self, value: &Value, amount: usize) {
match self.state(value) {
State::Index(index) => {
self.move_to(index.saturating_add(amount).min(value.len()));
}
State::Selection { start, end } => self.move_to(end.max(start)),
}
}

pub(crate) fn move_left(&mut self, value: &Value) {
match self.state(value) {
State::Index(index) if index > 0 => self.move_to(index - 1),
State::Selection { start, end } => self.move_to(start.min(end)),
State::Index(_) => self.move_to(0),
}
}

pub(crate) fn move_left_by_words(&mut self, value: &Value) {
self.move_to(value.previous_start_of_word(self.left(value)));
}

pub(crate) fn select_range(&mut self, start: usize, end: usize) {
if start == end {
self.state = State::Index(start);
} else {
self.state = State::Selection { start, end };
}
}

pub(crate) fn select_left(&mut self, value: &Value) {
match self.state(value) {
State::Index(index) if index > 0 => {
self.select_range(index, index - 1);
}
State::Selection { start, end } if end > 0 => {
self.select_range(start, end - 1);
}
_ => {}
}
}

pub(crate) fn select_right(&mut self, value: &Value) {
match self.state(value) {
State::Index(index) if index < value.len() => {
self.select_range(index, index + 1);
}
State::Selection { start, end } if end < value.len() => {
self.select_range(start, end + 1);
}
_ => {}
}
}

pub(crate) fn select_left_by_words(&mut self, value: &Value) {
match self.state(value) {
State::Index(index) => {
self.select_range(index, value.previous_start_of_word(index));
}
State::Selection { start, end } => {
self.select_range(start, value.previous_start_of_word(end));
}
}
}

pub(crate) fn select_right_by_words(&mut self, value: &Value) {
match self.state(value) {
State::Index(index) => {
self.select_range(index, value.next_end_of_word(index));
}
State::Selection { start, end } => {
self.select_range(start, value.next_end_of_word(end));
}
}
}

pub(crate) fn select_all(&mut self, value: &Value) {
self.select_range(0, value.len());
}

pub(crate) fn start(&self, value: &Value) -> usize {
let start = match self.state {
State::Index(index) => index,
State::Selection { start, .. } => start,
};

start.min(value.len())
}

pub(crate) fn end(&self, value: &Value) -> usize {
let end = match self.state {
State::Index(index) => index,
State::Selection { end, .. } => end,
};

end.min(value.len())
}

fn left(&self, value: &Value) -> usize {
match self.state(value) {
State::Index(index) => index,
State::Selection { start, end } => start.min(end),
}
}

fn right(&self, value: &Value) -> usize {
match self.state(value) {
State::Index(index) => index,
State::Selection { start, end } => start.max(end),
}
}
}
71 changes: 71 additions & 0 deletions liana-ui/src/widget/editor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use super::cursor::Cursor;
use iced::widget::text_input::Value;

pub struct Editor<'a> {
value: &'a mut Value,
cursor: &'a mut Cursor,
}

impl<'a> Editor<'a> {
pub fn new(value: &'a mut Value, cursor: &'a mut Cursor) -> Editor<'a> {
Editor { value, cursor }
}

pub fn contents(&self) -> String {
self.value.to_string()
}

pub fn insert(&mut self, character: char) {
if let Some((left, right)) = self.cursor.selection(self.value) {
self.cursor.move_left(self.value);
self.value.remove_many(left, right);
}

self.value.insert(self.cursor.end(self.value), character);
self.cursor.move_right(self.value);
}

pub fn paste(&mut self, content: Value) {
let length = content.len();
if let Some((left, right)) = self.cursor.selection(self.value) {
self.cursor.move_left(self.value);
self.value.remove_many(left, right);
}

self.value.insert_many(self.cursor.end(self.value), content);

self.cursor.move_right_by_amount(self.value, length);
}

pub fn backspace(&mut self) {
match self.cursor.selection(self.value) {
Some((start, end)) => {
self.cursor.move_left(self.value);
self.value.remove_many(start, end);
}
None => {
let start = self.cursor.start(self.value);

if start > 0 {
self.cursor.move_left(self.value);
self.value.remove(start - 1);
}
}
}
}

pub fn delete(&mut self) {
match self.cursor.selection(self.value) {
Some(_) => {
self.backspace();
}
None => {
let end = self.cursor.end(self.value);

if end < self.value.len() {
self.value.remove(end);
}
}
}
}
}
21 changes: 21 additions & 0 deletions liana-ui/src/widget/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
mod cursor;
mod editor;
pub mod text_input;

use crate::theme::Theme;

pub type Renderer = iced::Renderer;
pub type Element<'a, Message> = iced::Element<'a, Message, Theme, Renderer>;
pub type Container<'a, Message> = iced::widget::Container<'a, Message, Theme, Renderer>;
pub type Column<'a, Message> = iced::widget::Column<'a, Message, Theme, Renderer>;
pub type Row<'a, Message> = iced::widget::Row<'a, Message, Theme, Renderer>;
pub type Button<'a, Message> = iced::widget::Button<'a, Message, Theme, Renderer>;
pub type CheckBox<'a, Message> = iced::widget::Checkbox<'a, Message, Theme, Renderer>;
pub type Text<'a> = iced::widget::Text<'a, Theme, Renderer>;
pub type TextInput<'a, Message> = text_input::TextInput<'a, Message, Theme, Renderer>;
pub type Tooltip<'a> = iced::widget::Tooltip<'a, Theme, Renderer>;
pub type ProgressBar<'a> = iced::widget::ProgressBar<'a, Theme>;
pub type PickList<'a, T, L, V, Message> =
iced::widget::PickList<'a, T, L, V, Message, Theme, Renderer>;
pub type Scrollable<'a, Message> = iced::widget::Scrollable<'a, Message, Theme, Renderer>;
pub type Svg<'a> = iced::widget::Svg<'a, Theme>;
Loading

0 comments on commit 11dcced

Please sign in to comment.