Skip to content

Commit

Permalink
Merge pull request #37 from hecrj/feature/text-input
Browse files Browse the repository at this point in the history
Text input widget
  • Loading branch information
hecrj authored Nov 5, 2019
2 parents 0ea911a + a216158 commit da2717c
Show file tree
Hide file tree
Showing 21 changed files with 741 additions and 60 deletions.
21 changes: 16 additions & 5 deletions core/src/vector.rs
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)
}
}
8 changes: 4 additions & 4 deletions core/src/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@ pub mod button;
pub mod scrollable;
pub mod slider;
pub mod text;
pub mod text_input;

#[doc(no_inline)]
pub use button::Button;

#[doc(no_inline)]
pub use scrollable::Scrollable;
#[doc(no_inline)]
pub use slider::Slider;

#[doc(no_inline)]
pub use text::Text;

#[doc(no_inline)]
pub use scrollable::Scrollable;
pub use text_input::TextInput;

pub use checkbox::Checkbox;
pub use column::Column;
Expand Down
148 changes: 148 additions & 0 deletions core/src/widget/text_input.rs
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);
}
}
116 changes: 116 additions & 0 deletions examples/todos.rs
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,
};
Loading

0 comments on commit da2717c

Please sign in to comment.