-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #37 from hecrj/feature/text-input
Text input widget
- Loading branch information
Showing
21 changed files
with
741 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,26 @@ | ||
/// A 2D vector. | ||
#[derive(Debug, Clone, Copy, PartialEq)] | ||
pub struct Vector { | ||
pub x: f32, | ||
pub y: f32, | ||
pub struct Vector<T = f32> { | ||
pub x: T, | ||
pub y: T, | ||
} | ||
|
||
impl Vector { | ||
impl<T> Vector<T> { | ||
/// Creates a new [`Vector`] with the given components. | ||
/// | ||
/// [`Vector`]: struct.Vector.html | ||
pub fn new(x: f32, y: f32) -> Self { | ||
pub fn new(x: T, y: T) -> Self { | ||
Self { x, y } | ||
} | ||
} | ||
|
||
impl<T> std::ops::Add for Vector<T> | ||
where | ||
T: std::ops::Add<Output = T>, | ||
{ | ||
type Output = Self; | ||
|
||
fn add(self, b: Self) -> Self { | ||
Self::new(self.x + b.x, self.y + b.y) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
use crate::Length; | ||
|
||
pub struct TextInput<'a, Message> { | ||
pub state: &'a mut State, | ||
pub placeholder: String, | ||
pub value: Value, | ||
pub width: Length, | ||
pub max_width: Length, | ||
pub padding: u16, | ||
pub size: Option<u16>, | ||
pub on_change: Box<dyn Fn(String) -> Message>, | ||
pub on_submit: Option<Message>, | ||
} | ||
|
||
impl<'a, Message> TextInput<'a, Message> { | ||
pub fn new<F>( | ||
state: &'a mut State, | ||
placeholder: &str, | ||
value: &str, | ||
on_change: F, | ||
) -> Self | ||
where | ||
F: 'static + Fn(String) -> Message, | ||
{ | ||
Self { | ||
state, | ||
placeholder: String::from(placeholder), | ||
value: Value::new(value), | ||
width: Length::Fill, | ||
max_width: Length::Shrink, | ||
padding: 0, | ||
size: None, | ||
on_change: Box::new(on_change), | ||
on_submit: None, | ||
} | ||
} | ||
|
||
/// Sets the width of the [`TextInput`]. | ||
/// | ||
/// [`TextInput`]: struct.TextInput.html | ||
pub fn width(mut self, width: Length) -> Self { | ||
self.width = width; | ||
self | ||
} | ||
|
||
/// Sets the maximum width of the [`TextInput`]. | ||
/// | ||
/// [`TextInput`]: struct.TextInput.html | ||
pub fn max_width(mut self, max_width: Length) -> Self { | ||
self.max_width = max_width; | ||
self | ||
} | ||
|
||
/// Sets the padding of the [`TextInput`]. | ||
/// | ||
/// [`TextInput`]: struct.TextInput.html | ||
pub fn padding(mut self, units: u16) -> Self { | ||
self.padding = units; | ||
self | ||
} | ||
|
||
pub fn size(mut self, size: u16) -> Self { | ||
self.size = Some(size); | ||
self | ||
} | ||
|
||
pub fn on_submit(mut self, message: Message) -> Self { | ||
self.on_submit = Some(message); | ||
self | ||
} | ||
} | ||
|
||
impl<'a, Message> std::fmt::Debug for TextInput<'a, Message> | ||
where | ||
Message: std::fmt::Debug, | ||
{ | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
// TODO: Complete once stabilized | ||
f.debug_struct("TextInput").finish() | ||
} | ||
} | ||
|
||
#[derive(Debug, Default)] | ||
pub struct State { | ||
pub is_focused: bool, | ||
cursor_position: usize, | ||
} | ||
|
||
impl State { | ||
pub fn new() -> Self { | ||
Self::default() | ||
} | ||
|
||
pub fn move_cursor_right(&mut self, value: &Value) { | ||
let current = self.cursor_position(value); | ||
|
||
if current < value.len() { | ||
self.cursor_position = current + 1; | ||
} | ||
} | ||
|
||
pub fn move_cursor_left(&mut self, value: &Value) { | ||
let current = self.cursor_position(value); | ||
|
||
if current > 0 { | ||
self.cursor_position = current - 1; | ||
} | ||
} | ||
|
||
pub fn cursor_position(&self, value: &Value) -> usize { | ||
self.cursor_position.min(value.len()) | ||
} | ||
} | ||
|
||
// TODO: Use `unicode-segmentation` | ||
pub struct Value(Vec<char>); | ||
|
||
impl Value { | ||
pub fn new(string: &str) -> Self { | ||
Self(string.chars().collect()) | ||
} | ||
|
||
pub fn len(&self) -> usize { | ||
self.0.len() | ||
} | ||
|
||
pub fn until(&self, index: usize) -> Self { | ||
Self(self.0[..index.min(self.len())].iter().cloned().collect()) | ||
} | ||
|
||
pub fn to_string(&self) -> String { | ||
let mut string = String::new(); | ||
|
||
for c in self.0.iter() { | ||
string.push(*c); | ||
} | ||
|
||
string | ||
} | ||
|
||
pub fn insert(&mut self, index: usize, c: char) { | ||
self.0.insert(index, c); | ||
} | ||
|
||
pub fn remove(&mut self, index: usize) { | ||
self.0.remove(index); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
use iced::{ | ||
scrollable, text::HorizontalAlignment, text_input, Align, Application, | ||
Checkbox, Color, Column, Element, Length, Scrollable, Text, TextInput, | ||
}; | ||
|
||
pub fn main() { | ||
Todos::default().run() | ||
} | ||
|
||
#[derive(Debug, Default)] | ||
struct Todos { | ||
scroll: scrollable::State, | ||
input: text_input::State, | ||
input_value: String, | ||
tasks: Vec<Task>, | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
pub enum Message { | ||
InputChanged(String), | ||
CreateTask, | ||
TaskChanged(usize, bool), | ||
} | ||
|
||
impl Application for Todos { | ||
type Message = Message; | ||
|
||
fn update(&mut self, message: Message) { | ||
match message { | ||
Message::InputChanged(value) => { | ||
self.input_value = value; | ||
} | ||
Message::CreateTask => { | ||
if !self.input_value.is_empty() { | ||
self.tasks.push(Task::new(self.input_value.clone())); | ||
self.input_value = String::new(); | ||
} | ||
} | ||
Message::TaskChanged(i, completed) => { | ||
if let Some(task) = self.tasks.get_mut(i) { | ||
task.completed = completed; | ||
} | ||
} | ||
} | ||
|
||
dbg!(self); | ||
} | ||
|
||
fn view(&mut self) -> Element<Message> { | ||
let title = Text::new("todos") | ||
.size(100) | ||
.color(GRAY) | ||
.horizontal_alignment(HorizontalAlignment::Center); | ||
|
||
let input = TextInput::new( | ||
&mut self.input, | ||
"What needs to be done?", | ||
&self.input_value, | ||
Message::InputChanged, | ||
) | ||
.padding(15) | ||
.size(30) | ||
.on_submit(Message::CreateTask); | ||
|
||
let tasks = self.tasks.iter_mut().enumerate().fold( | ||
Column::new().spacing(20), | ||
|column, (i, task)| { | ||
column.push( | ||
task.view() | ||
.map(move |state| Message::TaskChanged(i, state)), | ||
) | ||
}, | ||
); | ||
|
||
let content = Column::new() | ||
.max_width(Length::Units(800)) | ||
.align_self(Align::Center) | ||
.spacing(20) | ||
.push(title) | ||
.push(input) | ||
.push(tasks); | ||
|
||
Scrollable::new(&mut self.scroll) | ||
.padding(40) | ||
.push(content) | ||
.into() | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
struct Task { | ||
description: String, | ||
completed: bool, | ||
} | ||
|
||
impl Task { | ||
fn new(description: String) -> Self { | ||
Task { | ||
description, | ||
completed: false, | ||
} | ||
} | ||
|
||
fn view(&mut self) -> Element<bool> { | ||
Checkbox::new(self.completed, &self.description, |checked| checked) | ||
.into() | ||
} | ||
} | ||
|
||
// Colors | ||
const GRAY: Color = Color { | ||
r: 0.5, | ||
g: 0.5, | ||
b: 0.5, | ||
a: 1.0, | ||
}; |
Oops, something went wrong.