Draft basic styling for TextInput

This commit is contained in:
Héctor Ramón Jiménez 2020-01-01 18:26:49 +01:00
parent d96ced8e2d
commit 5af4159848
10 changed files with 143 additions and 36 deletions

View File

@ -97,6 +97,8 @@ impl scrollable::Renderer for Null {
} }
impl text_input::Renderer for Null { impl text_input::Renderer for Null {
type Style = ();
fn default_size(&self) -> u16 { fn default_size(&self) -> u16 {
20 20
} }
@ -124,6 +126,7 @@ impl text_input::Renderer for Null {
_placeholder: &str, _placeholder: &str,
_value: &text_input::Value, _value: &text_input::Value,
_state: &text_input::State, _state: &text_input::State,
_style: &Self::Style,
) -> Self::Output { ) -> Self::Output {
} }
} }

View File

@ -94,7 +94,7 @@ where
self self
} }
/// Sets the style the [`Container`]. /// Sets the style of the [`Container`].
/// ///
/// [`Container`]: struct.Container.html /// [`Container`]: struct.Container.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {

View File

@ -15,8 +15,9 @@ use unicode_segmentation::UnicodeSegmentation;
/// ///
/// # Example /// # Example
/// ``` /// ```
/// # use iced_native::{text_input, TextInput}; /// # use iced_native::{text_input, renderer::Null};
/// # /// #
/// # pub type TextInput<'a, Message> = iced_native::TextInput<'a, Message, Null>;
/// #[derive(Debug, Clone)] /// #[derive(Debug, Clone)]
/// enum Message { /// enum Message {
/// TextInputChanged(String), /// TextInputChanged(String),
@ -35,7 +36,7 @@ use unicode_segmentation::UnicodeSegmentation;
/// ``` /// ```
/// ![Text input drawn by `iced_wgpu`](https://github.com/hecrj/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text_input.png?raw=true) /// ![Text input drawn by `iced_wgpu`](https://github.com/hecrj/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text_input.png?raw=true)
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct TextInput<'a, Message> { pub struct TextInput<'a, Message, Renderer: self::Renderer> {
state: &'a mut State, state: &'a mut State,
placeholder: String, placeholder: String,
value: Value, value: Value,
@ -46,9 +47,10 @@ pub struct TextInput<'a, Message> {
size: Option<u16>, size: Option<u16>,
on_change: Box<dyn Fn(String) -> Message>, on_change: Box<dyn Fn(String) -> Message>,
on_submit: Option<Message>, on_submit: Option<Message>,
style: Renderer::Style,
} }
impl<'a, Message> TextInput<'a, Message> { impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
/// Creates a new [`TextInput`]. /// Creates a new [`TextInput`].
/// ///
/// It expects: /// It expects:
@ -79,6 +81,7 @@ impl<'a, Message> TextInput<'a, Message> {
size: None, size: None,
on_change: Box::new(on_change), on_change: Box::new(on_change),
on_submit: None, on_submit: None,
style: Renderer::Style::default(),
} }
} }
@ -130,11 +133,20 @@ impl<'a, Message> TextInput<'a, Message> {
self.on_submit = Some(message); self.on_submit = Some(message);
self self
} }
/// Sets the style of the [`TextInput`].
///
/// [`TextInput`]: struct.TextInput.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
}
} }
impl<'a, Message, Renderer> Widget<Message, Renderer> for TextInput<'a, Message> impl<'a, Message, Renderer> Widget<Message, Renderer>
for TextInput<'a, Message, Renderer>
where where
Renderer: self::Renderer, Renderer: 'static + self::Renderer,
Message: Clone + std::fmt::Debug, Message: Clone + std::fmt::Debug,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
@ -359,6 +371,7 @@ where
&self.placeholder, &self.placeholder,
&self.value.secure(), &self.value.secure(),
&self.state, &self.state,
&self.style,
) )
} else { } else {
renderer.draw( renderer.draw(
@ -369,6 +382,7 @@ where
&self.placeholder, &self.placeholder,
&self.value, &self.value,
&self.state, &self.state,
&self.style,
) )
} }
} }
@ -376,7 +390,7 @@ where
fn hash_layout(&self, state: &mut Hasher) { fn hash_layout(&self, state: &mut Hasher) {
use std::{any::TypeId, hash::Hash}; use std::{any::TypeId, hash::Hash};
TypeId::of::<TextInput<'static, ()>>().hash(state); TypeId::of::<TextInput<'static, (), Renderer>>().hash(state);
self.width.hash(state); self.width.hash(state);
self.max_width.hash(state); self.max_width.hash(state);
@ -393,6 +407,8 @@ where
/// [`TextInput`]: struct.TextInput.html /// [`TextInput`]: struct.TextInput.html
/// [renderer]: ../../renderer/index.html /// [renderer]: ../../renderer/index.html
pub trait Renderer: crate::Renderer + Sized { pub trait Renderer: crate::Renderer + Sized {
type Style: Default;
/// Returns the default size of the text of the [`TextInput`]. /// Returns the default size of the text of the [`TextInput`].
/// ///
/// [`TextInput`]: struct.TextInput.html /// [`TextInput`]: struct.TextInput.html
@ -441,17 +457,18 @@ pub trait Renderer: crate::Renderer + Sized {
placeholder: &str, placeholder: &str,
value: &Value, value: &Value,
state: &State, state: &State,
style: &Self::Style,
) -> Self::Output; ) -> Self::Output;
} }
impl<'a, Message, Renderer> From<TextInput<'a, Message>> impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>>
for Element<'a, Message, Renderer> for Element<'a, Message, Renderer>
where where
Renderer: 'static + self::Renderer, Renderer: 'static + self::Renderer,
Message: 'static + Clone + std::fmt::Debug, Message: 'static + Clone + std::fmt::Debug,
{ {
fn from( fn from(
text_input: TextInput<'a, Message>, text_input: TextInput<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> { ) -> Element<'a, Message, Renderer> {
Element::new(text_input) Element::new(text_input)
} }

View File

@ -38,16 +38,6 @@ pub mod widget {
pub use iced_winit::scrollable::State; pub use iced_winit::scrollable::State;
} }
pub mod text_input {
//! Ask for information using text fields.
//!
//! A [`TextInput`] has some local [`State`].
//!
//! [`TextInput`]: struct.TextInput.html
//! [`State`]: struct.State.html
pub use iced_winit::text_input::{State, TextInput};
}
pub mod slider { pub mod slider {
//! Display an interactive selector of a single value from a range of //! Display an interactive selector of a single value from a range of
//! values. //! values.

View File

@ -12,6 +12,12 @@ pub struct Style {
/// A set of rules that dictate the style of a container. /// A set of rules that dictate the style of a container.
pub trait StyleSheet { pub trait StyleSheet {
/// Produces the style of a container. /// Produces the style of a container.
fn style(&self) -> Style;
}
struct Default;
impl StyleSheet for Default {
fn style(&self) -> Style { fn style(&self) -> Style {
Style { Style {
text_color: None, text_color: None,
@ -21,10 +27,6 @@ pub trait StyleSheet {
} }
} }
struct Default;
impl StyleSheet for Default {}
impl std::default::Default for Box<dyn StyleSheet> { impl std::default::Default for Box<dyn StyleSheet> {
fn default() -> Self { fn default() -> Self {
Box::new(Default) Box::new(Default)

View File

@ -1,2 +1,3 @@
pub mod button; pub mod button;
pub mod container; pub mod container;
pub mod text_input;

72
style/src/text_input.rs Normal file
View File

@ -0,0 +1,72 @@
//! Display fields that can be filled with text.
use iced_core::{Background, Color};
/// The appearance of a text input.
#[derive(Debug, Clone, Copy)]
pub struct Style {
pub background: Background,
pub border_radius: u16,
pub border_width: u16,
pub border_color: Color,
}
/// A set of rules that dictate the style of a text input.
pub trait StyleSheet {
/// Produces the style of an active text input.
fn active(&self) -> Style;
/// Produces the style of a focused text input.
fn focused(&self) -> Style;
/// Produces the style of an hovered text input.
fn hovered(&self) -> Style {
self.focused()
}
fn placeholder_color(&self) -> Color;
fn value_color(&self) -> Color;
}
struct Default;
impl StyleSheet for Default {
fn active(&self) -> Style {
Style {
background: Background::Color(Color::WHITE),
border_radius: 5,
border_width: 1,
border_color: Color::from_rgb(0.7, 0.7, 0.7),
}
}
fn focused(&self) -> Style {
Style {
border_color: Color::from_rgb(0.5, 0.5, 0.5),
..self.active()
}
}
fn placeholder_color(&self) -> Color {
Color::from_rgb(0.7, 0.7, 0.7)
}
fn value_color(&self) -> Color {
Color::from_rgb(0.3, 0.3, 0.3)
}
}
impl std::default::Default for Box<dyn StyleSheet> {
fn default() -> Self {
Box::new(Default)
}
}
impl<T> From<T> for Box<dyn StyleSheet>
where
T: 'static + StyleSheet,
{
fn from(style: T) -> Self {
Box::new(style)
}
}

View File

@ -1,4 +1,4 @@
use crate::{Primitive, Renderer}; use crate::{text_input::StyleSheet, Primitive, Renderer};
use iced_native::{ use iced_native::{
text_input, Background, Color, Font, HorizontalAlignment, MouseCursor, text_input, Background, Color, Font, HorizontalAlignment, MouseCursor,
@ -7,6 +7,8 @@ use iced_native::{
use std::f32; use std::f32;
impl text_input::Renderer for Renderer { impl text_input::Renderer for Renderer {
type Style = Box<dyn StyleSheet>;
fn default_size(&self) -> u16 { fn default_size(&self) -> u16 {
// TODO: Make this configurable // TODO: Make this configurable
20 20
@ -61,20 +63,24 @@ impl text_input::Renderer for Renderer {
placeholder: &str, placeholder: &str,
value: &text_input::Value, value: &text_input::Value,
state: &text_input::State, state: &text_input::State,
style_sheet: &Self::Style,
) -> Self::Output { ) -> Self::Output {
let is_mouse_over = bounds.contains(cursor_position); let is_mouse_over = bounds.contains(cursor_position);
let style = if state.is_focused() {
style_sheet.focused()
} else if is_mouse_over {
style_sheet.hovered()
} else {
style_sheet.active()
};
let input = Primitive::Quad { let input = Primitive::Quad {
bounds, bounds,
background: Background::Color(Color::WHITE), background: style.background,
border_radius: 5, border_radius: style.border_radius,
border_width: 1, border_width: style.border_width,
border_color: if is_mouse_over || state.is_focused() { border_color: style.border_color,
[0.5, 0.5, 0.5]
} else {
[0.7, 0.7, 0.7]
}
.into(),
}; };
let text = value.to_string(); let text = value.to_string();
@ -86,9 +92,9 @@ impl text_input::Renderer for Renderer {
text.clone() text.clone()
}, },
color: if text.is_empty() { color: if text.is_empty() {
[0.7, 0.7, 0.7] style_sheet.placeholder_color()
} else { } else {
[0.3, 0.3, 0.3] style_sheet.value_color()
} }
.into(), .into(),
font: Font::Default, font: Font::Default,
@ -117,7 +123,7 @@ impl text_input::Renderer for Renderer {
width: 1.0, width: 1.0,
height: text_bounds.height, height: text_bounds.height,
}, },
background: Background::Color(Color::BLACK), background: Background::Color(style_sheet.value_color()),
border_radius: 0, border_radius: 0,
border_width: 0, border_width: 0,
border_color: Color::TRANSPARENT, border_color: Color::TRANSPARENT,

View File

@ -1,2 +1,3 @@
pub mod button; pub mod button;
pub mod container; pub mod container;
pub mod text_input;

View File

@ -0,0 +1,15 @@
//! Display fields that can be filled with text.
//!
//! A [`TextInput`] has some local [`State`].
//!
//! [`TextInput`]: struct.TextInput.html
//! [`State`]: struct.State.html
use crate::Renderer;
pub use iced_native::text_input::State;
pub use iced_style::text_input::{Style, StyleSheet};
/// A field that can be filled with text.
///
/// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`.
pub type TextInput<'a, Message> = iced_native::TextInput<'a, Message, Renderer>;