Move widgets from core to native and web
Also made fields private and improved `Renderer` traits.
This commit is contained in:
parent
d3553adf27
commit
65eb218d3d
59 changed files with 2455 additions and 1942 deletions
|
|
@ -1,32 +1,144 @@
|
|||
//! Allow your users to perform actions by pressing a button.
|
||||
//!
|
||||
//! A [`Button`] has some local [`State`] and a [`Class`].
|
||||
//! A [`Button`] has some local [`State`].
|
||||
//!
|
||||
//! [`Button`]: struct.Button.html
|
||||
//! [`State`]: struct.State.html
|
||||
//! [`Class`]: enum.Class.html
|
||||
|
||||
use crate::input::{mouse, ButtonState};
|
||||
use crate::{layout, Element, Event, Hasher, Layout, Point, Widget};
|
||||
use crate::{
|
||||
input::{mouse, ButtonState},
|
||||
layout, Background, Element, Event, Hasher, Layout, Length, Point,
|
||||
Rectangle, Widget,
|
||||
};
|
||||
use std::hash::Hash;
|
||||
|
||||
pub use iced_core::button::State;
|
||||
/// A generic widget that produces a message when clicked.
|
||||
pub struct Button<'a, Message, Renderer> {
|
||||
state: &'a mut State,
|
||||
content: Element<'a, Message, Renderer>,
|
||||
on_press: Option<Message>,
|
||||
width: Length,
|
||||
min_width: u32,
|
||||
padding: u16,
|
||||
background: Option<Background>,
|
||||
border_radius: u16,
|
||||
}
|
||||
|
||||
pub type Button<'a, Message, Renderer> =
|
||||
iced_core::Button<'a, Message, Element<'a, Message, Renderer>>;
|
||||
impl<'a, Message, Renderer> Button<'a, Message, Renderer> {
|
||||
/// Creates a new [`Button`] with some local [`State`] and the given
|
||||
/// content.
|
||||
///
|
||||
/// [`Button`]: struct.Button.html
|
||||
/// [`State`]: struct.State.html
|
||||
pub fn new<E>(state: &'a mut State, content: E) -> Self
|
||||
where
|
||||
E: Into<Element<'a, Message, Renderer>>,
|
||||
{
|
||||
Button {
|
||||
state,
|
||||
content: content.into(),
|
||||
on_press: None,
|
||||
width: Length::Shrink,
|
||||
min_width: 0,
|
||||
padding: 0,
|
||||
background: None,
|
||||
border_radius: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Button`].
|
||||
///
|
||||
/// [`Button`]: struct.Button.html
|
||||
pub fn width(mut self, width: Length) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the minimum width of the [`Button`].
|
||||
///
|
||||
/// [`Button`]: struct.Button.html
|
||||
pub fn min_width(mut self, min_width: u32) -> Self {
|
||||
self.min_width = min_width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the padding of the [`Button`].
|
||||
///
|
||||
/// [`Button`]: struct.Button.html
|
||||
pub fn padding(mut self, padding: u16) -> Self {
|
||||
self.padding = padding;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`Background`] of the [`Button`].
|
||||
///
|
||||
/// [`Button`]: struct.Button.html
|
||||
/// [`Background`]: ../../struct.Background.html
|
||||
pub fn background(mut self, background: Background) -> Self {
|
||||
self.background = Some(background);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the border radius of the [`Button`].
|
||||
///
|
||||
/// [`Button`]: struct.Button.html
|
||||
pub fn border_radius(mut self, border_radius: u16) -> Self {
|
||||
self.border_radius = border_radius;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the message that will be produced when the [`Button`] is pressed.
|
||||
///
|
||||
/// [`Button`]: struct.Button.html
|
||||
pub fn on_press(mut self, msg: Message) -> Self {
|
||||
self.on_press = Some(msg);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The local state of a [`Button`].
|
||||
///
|
||||
/// [`Button`]: struct.Button.html
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct State {
|
||||
is_pressed: bool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Creates a new [`State`].
|
||||
///
|
||||
/// [`State`]: struct.State.html
|
||||
pub fn new() -> State {
|
||||
State::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Button<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Message: Clone + std::fmt::Debug,
|
||||
Message: Clone,
|
||||
{
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
renderer.layout(&self, limits)
|
||||
let padding = f32::from(self.padding);
|
||||
let limits = limits
|
||||
.min_width(self.min_width)
|
||||
.width(self.width)
|
||||
.height(Length::Shrink)
|
||||
.pad(padding);
|
||||
|
||||
let mut content = self.content.layout(renderer, &limits);
|
||||
|
||||
content.bounds.x = padding;
|
||||
content.bounds.y = padding;
|
||||
|
||||
let size = limits.resolve(content.size()).pad(padding);
|
||||
|
||||
layout::Node::with_children(size, vec![content])
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
|
|
@ -73,7 +185,20 @@ where
|
|||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Renderer::Output {
|
||||
renderer.draw(&self, layout, cursor_position)
|
||||
let content = self.content.draw(
|
||||
renderer,
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
);
|
||||
|
||||
renderer.draw(
|
||||
layout.bounds(),
|
||||
cursor_position,
|
||||
self.state.is_pressed,
|
||||
self.background,
|
||||
self.border_radius,
|
||||
content,
|
||||
)
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -90,24 +215,17 @@ where
|
|||
/// [`Button`]: struct.Button.html
|
||||
/// [renderer]: ../../renderer/index.html
|
||||
pub trait Renderer: crate::Renderer + Sized {
|
||||
/// Creates a [`Node`] for the provided [`Button`].
|
||||
///
|
||||
/// [`Node`]: ../../struct.Node.html
|
||||
/// [`Button`]: struct.Button.html
|
||||
fn layout<Message>(
|
||||
&self,
|
||||
button: &Button<'_, Message, Self>,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node;
|
||||
|
||||
/// Draws a [`Button`].
|
||||
///
|
||||
/// [`Button`]: struct.Button.html
|
||||
fn draw<Message>(
|
||||
fn draw(
|
||||
&mut self,
|
||||
button: &Button<'_, Message, Self>,
|
||||
layout: Layout<'_>,
|
||||
bounds: Rectangle,
|
||||
cursor_position: Point,
|
||||
is_pressed: bool,
|
||||
background: Option<Background>,
|
||||
border_radius: u16,
|
||||
content: Self::Output,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +233,7 @@ impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
|
|||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: 'static + self::Renderer,
|
||||
Message: 'static + Clone + std::fmt::Debug,
|
||||
Message: 'static + Clone,
|
||||
{
|
||||
fn from(
|
||||
button: Button<'a, Message, Renderer>,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,72 @@
|
|||
//! Show toggle controls using checkboxes.
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::input::{mouse, ButtonState};
|
||||
use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget};
|
||||
use crate::{
|
||||
input::{mouse, ButtonState},
|
||||
layout, row, text, Align, Color, Element, Event, Font, Hasher,
|
||||
HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,
|
||||
VerticalAlignment, Widget,
|
||||
};
|
||||
|
||||
pub use iced_core::Checkbox;
|
||||
/// A box that can be checked.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use iced_native::Checkbox;
|
||||
///
|
||||
/// pub enum Message {
|
||||
/// CheckboxToggled(bool),
|
||||
/// }
|
||||
///
|
||||
/// let is_checked = true;
|
||||
///
|
||||
/// Checkbox::new(is_checked, "Toggle me!", Message::CheckboxToggled);
|
||||
/// ```
|
||||
///
|
||||
/// 
|
||||
pub struct Checkbox<Message> {
|
||||
is_checked: bool,
|
||||
on_toggle: Box<dyn Fn(bool) -> Message>,
|
||||
label: String,
|
||||
label_color: Option<Color>,
|
||||
}
|
||||
|
||||
impl<Message> Checkbox<Message> {
|
||||
/// Creates a new [`Checkbox`].
|
||||
///
|
||||
/// It expects:
|
||||
/// * a boolean describing whether the [`Checkbox`] is checked or not
|
||||
/// * the label of the [`Checkbox`]
|
||||
/// * a function that will be called when the [`Checkbox`] is toggled. It
|
||||
/// will receive the new state of the [`Checkbox`] and must produce a
|
||||
/// `Message`.
|
||||
///
|
||||
/// [`Checkbox`]: struct.Checkbox.html
|
||||
pub fn new<F>(is_checked: bool, label: &str, f: F) -> Self
|
||||
where
|
||||
F: 'static + Fn(bool) -> Message,
|
||||
{
|
||||
Checkbox {
|
||||
is_checked,
|
||||
on_toggle: Box::new(f),
|
||||
label: String::from(label),
|
||||
label_color: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the color of the label of the [`Checkbox`].
|
||||
///
|
||||
/// [`Checkbox`]: struct.Checkbox.html
|
||||
pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self {
|
||||
self.label_color = Some(color.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Renderer> Widget<Message, Renderer> for Checkbox<Message>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: self::Renderer + text::Renderer + row::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
Length::Fill
|
||||
|
|
@ -19,7 +77,18 @@ where
|
|||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
renderer.layout(&self, limits)
|
||||
let size = self::Renderer::default_size(renderer);
|
||||
|
||||
Row::<(), Renderer>::new()
|
||||
.spacing(15)
|
||||
.align_items(Align::Center)
|
||||
.push(
|
||||
Row::new()
|
||||
.width(Length::Units(size as u16))
|
||||
.height(Length::Units(size as u16)),
|
||||
)
|
||||
.push(Text::new(&self.label))
|
||||
.layout(renderer, limits)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
|
|
@ -51,7 +120,33 @@ where
|
|||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Renderer::Output {
|
||||
renderer.draw(&self, layout, cursor_position)
|
||||
let bounds = layout.bounds();
|
||||
let mut children = layout.children();
|
||||
|
||||
let checkbox_layout = children.next().unwrap();
|
||||
let label_layout = children.next().unwrap();
|
||||
let checkbox_bounds = checkbox_layout.bounds();
|
||||
|
||||
let label = text::Renderer::draw(
|
||||
renderer,
|
||||
label_layout.bounds(),
|
||||
&self.label,
|
||||
text::Renderer::default_size(renderer),
|
||||
Font::Default,
|
||||
self.label_color,
|
||||
HorizontalAlignment::Left,
|
||||
VerticalAlignment::Center,
|
||||
);
|
||||
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
self::Renderer::draw(
|
||||
renderer,
|
||||
checkbox_bounds,
|
||||
self.is_checked,
|
||||
is_mouse_over,
|
||||
label,
|
||||
)
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -67,15 +162,7 @@ where
|
|||
/// [`Checkbox`]: struct.Checkbox.html
|
||||
/// [renderer]: ../../renderer/index.html
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// Creates a [`Node`] for the provided [`Checkbox`].
|
||||
///
|
||||
/// [`Node`]: ../../struct.Node.html
|
||||
/// [`Checkbox`]: struct.Checkbox.html
|
||||
fn layout<Message>(
|
||||
&self,
|
||||
checkbox: &Checkbox<Message>,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node;
|
||||
fn default_size(&self) -> u32;
|
||||
|
||||
/// Draws a [`Checkbox`].
|
||||
///
|
||||
|
|
@ -86,18 +173,19 @@ pub trait Renderer: crate::Renderer {
|
|||
/// * whether the [`Checkbox`] is checked or not
|
||||
///
|
||||
/// [`Checkbox`]: struct.Checkbox.html
|
||||
fn draw<Message>(
|
||||
fn draw(
|
||||
&mut self,
|
||||
checkbox: &Checkbox<Message>,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
bounds: Rectangle,
|
||||
is_checked: bool,
|
||||
is_mouse_over: bool,
|
||||
label: Self::Output,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Checkbox<Message>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: self::Renderer + text::Renderer + row::Renderer,
|
||||
Message: 'static,
|
||||
{
|
||||
fn from(checkbox: Checkbox<Message>) -> Element<'a, Message, Renderer> {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,113 @@
|
|||
use std::hash::Hash;
|
||||
|
||||
use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget};
|
||||
use crate::{
|
||||
layout, Align, Element, Event, Hasher, Layout, Length, Point, Widget,
|
||||
};
|
||||
|
||||
use std::u32;
|
||||
|
||||
/// A container that distributes its contents vertically.
|
||||
pub type Column<'a, Message, Renderer> =
|
||||
iced_core::Column<Element<'a, Message, Renderer>>;
|
||||
///
|
||||
/// A [`Column`] will try to fill the horizontal space of its container.
|
||||
///
|
||||
/// [`Column`]: struct.Column.html
|
||||
pub struct Column<'a, Message, Renderer> {
|
||||
spacing: u16,
|
||||
padding: u16,
|
||||
width: Length,
|
||||
height: Length,
|
||||
max_width: u32,
|
||||
max_height: u32,
|
||||
align_items: Align,
|
||||
children: Vec<Element<'a, Message, Renderer>>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
|
||||
/// Creates an empty [`Column`].
|
||||
///
|
||||
/// [`Column`]: struct.Column.html
|
||||
pub fn new() -> Self {
|
||||
Column {
|
||||
spacing: 0,
|
||||
padding: 0,
|
||||
width: Length::Fill,
|
||||
height: Length::Shrink,
|
||||
max_width: u32::MAX,
|
||||
max_height: u32::MAX,
|
||||
align_items: Align::Start,
|
||||
children: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the vertical spacing _between_ elements.
|
||||
///
|
||||
/// Custom margins per element do not exist in Iced. You should use this
|
||||
/// method instead! While less flexible, it helps you keep spacing between
|
||||
/// elements consistent.
|
||||
pub fn spacing(mut self, units: u16) -> Self {
|
||||
self.spacing = units;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the padding of the [`Column`].
|
||||
///
|
||||
/// [`Column`]: struct.Column.html
|
||||
pub fn padding(mut self, units: u16) -> Self {
|
||||
self.padding = units;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Column`].
|
||||
///
|
||||
/// [`Column`]: struct.Column.html
|
||||
pub fn width(mut self, width: Length) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the height of the [`Column`].
|
||||
///
|
||||
/// [`Column`]: struct.Column.html
|
||||
pub fn height(mut self, height: Length) -> Self {
|
||||
self.height = height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum width of the [`Column`].
|
||||
///
|
||||
/// [`Column`]: struct.Column.html
|
||||
pub fn max_width(mut self, max_width: u32) -> Self {
|
||||
self.max_width = max_width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum height of the [`Column`] in pixels.
|
||||
///
|
||||
/// [`Column`]: struct.Column.html
|
||||
pub fn max_height(mut self, max_height: u32) -> Self {
|
||||
self.max_height = max_height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the horizontal alignment of the contents of the [`Column`] .
|
||||
///
|
||||
/// [`Column`]: struct.Column.html
|
||||
pub fn align_items(mut self, align: Align) -> Self {
|
||||
self.align_items = align;
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds an element to the [`Column`].
|
||||
///
|
||||
/// [`Column`]: struct.Column.html
|
||||
pub fn push<E>(mut self, child: E) -> Self
|
||||
where
|
||||
E: Into<Element<'a, Message, Renderer>>,
|
||||
{
|
||||
self.children.push(child.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Column<'a, Message, Renderer>
|
||||
|
|
@ -64,7 +167,7 @@ where
|
|||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Renderer::Output {
|
||||
renderer.draw(&self, layout, cursor_position)
|
||||
renderer.draw(&self.children, layout, cursor_position)
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -85,7 +188,7 @@ where
|
|||
pub trait Renderer: crate::Renderer + Sized {
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
row: &Column<'_, Message, Self>,
|
||||
content: &[Element<'_, Message, Self>],
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Self::Output;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,94 @@
|
|||
use std::hash::Hash;
|
||||
|
||||
use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget};
|
||||
use crate::{
|
||||
layout, Align, Element, Event, Hasher, Layout, Length, Point, Widget,
|
||||
};
|
||||
|
||||
/// A container that distributes its contents vertically.
|
||||
pub type Container<'a, Message, Renderer> =
|
||||
iced_core::Container<Element<'a, Message, Renderer>>;
|
||||
use std::u32;
|
||||
|
||||
/// An element decorating some content.
|
||||
///
|
||||
/// It is normally used for alignment purposes.
|
||||
#[allow(missing_docs)]
|
||||
pub struct Container<'a, Message, Renderer> {
|
||||
width: Length,
|
||||
height: Length,
|
||||
max_width: u32,
|
||||
max_height: u32,
|
||||
horizontal_alignment: Align,
|
||||
vertical_alignment: Align,
|
||||
content: Element<'a, Message, Renderer>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Container<'a, Message, Renderer> {
|
||||
/// Creates an empty [`Container`].
|
||||
///
|
||||
/// [`Container`]: struct.Container.html
|
||||
pub fn new<T>(content: T) -> Self
|
||||
where
|
||||
T: Into<Element<'a, Message, Renderer>>,
|
||||
{
|
||||
Container {
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
max_width: u32::MAX,
|
||||
max_height: u32::MAX,
|
||||
horizontal_alignment: Align::Start,
|
||||
vertical_alignment: Align::Start,
|
||||
content: content.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Container`].
|
||||
///
|
||||
/// [`Container`]: struct.Container.html
|
||||
pub fn width(mut self, width: Length) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the height of the [`Container`].
|
||||
///
|
||||
/// [`Container`]: struct.Container.html
|
||||
pub fn height(mut self, height: Length) -> Self {
|
||||
self.height = height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum width of the [`Container`].
|
||||
///
|
||||
/// [`Container`]: struct.Container.html
|
||||
pub fn max_width(mut self, max_width: u32) -> Self {
|
||||
self.max_width = max_width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum height of the [`Container`] in pixels.
|
||||
///
|
||||
/// [`Container`]: struct.Container.html
|
||||
pub fn max_height(mut self, max_height: u32) -> Self {
|
||||
self.max_height = max_height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Centers the contents in the horizontal axis of the [`Container`].
|
||||
///
|
||||
/// [`Container`]: struct.Container.html
|
||||
pub fn center_x(mut self) -> Self {
|
||||
self.horizontal_alignment = Align::Center;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Centers the contents in the vertical axis of the [`Container`].
|
||||
///
|
||||
/// [`Container`]: struct.Container.html
|
||||
pub fn center_y(mut self) -> Self {
|
||||
self.vertical_alignment = Align::Center;
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Container<'a, Message, Renderer>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,58 @@
|
|||
//! Display images in your user interface.
|
||||
|
||||
use crate::{layout, Element, Hasher, Layout, Point, Widget};
|
||||
use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget};
|
||||
|
||||
use std::hash::Hash;
|
||||
|
||||
pub use iced_core::Image;
|
||||
/// A frame that displays an image while keeping aspect ratio.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use iced_native::Image;
|
||||
///
|
||||
/// let image = Image::new("resources/ferris.png");
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct Image {
|
||||
/// The image path
|
||||
pub path: String,
|
||||
|
||||
/// The width of the image
|
||||
pub width: Length,
|
||||
|
||||
/// The height of the image
|
||||
pub height: Length,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
/// Creates a new [`Image`] with the given path.
|
||||
///
|
||||
/// [`Image`]: struct.Image.html
|
||||
pub fn new<T: Into<String>>(path: T) -> Self {
|
||||
Image {
|
||||
path: path.into(),
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Image`] boundaries.
|
||||
///
|
||||
/// [`Image`]: struct.Image.html
|
||||
pub fn width(mut self, width: Length) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the height of the [`Image`] boundaries.
|
||||
///
|
||||
/// [`Image`]: struct.Image.html
|
||||
pub fn height(mut self, height: Length) -> Self {
|
||||
self.height = height;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Renderer> Widget<Message, Renderer> for Image
|
||||
where
|
||||
|
|
@ -15,7 +63,26 @@ where
|
|||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
renderer.layout(&self, limits)
|
||||
let (width, height) = renderer.dimensions(&self.path);
|
||||
|
||||
let aspect_ratio = width as f32 / height as f32;
|
||||
|
||||
// TODO: Deal with additional cases
|
||||
let (width, height) = match (self.width, self.height) {
|
||||
(Length::Units(width), _) => (
|
||||
self.width,
|
||||
Length::Units((width as f32 / aspect_ratio).round() as u16),
|
||||
),
|
||||
(_, _) => {
|
||||
(Length::Units(width as u16), Length::Units(height as u16))
|
||||
}
|
||||
};
|
||||
|
||||
let mut size = limits.width(width).height(height).resolve(Size::ZERO);
|
||||
|
||||
size.height = size.width / aspect_ratio;
|
||||
|
||||
layout::Node::new(size)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
|
|
@ -41,13 +108,7 @@ where
|
|||
/// [`Image`]: struct.Image.html
|
||||
/// [renderer]: ../../renderer/index.html
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// Creates a [`Node`] for the provided [`Image`].
|
||||
///
|
||||
/// You should probably keep the original aspect ratio, if possible.
|
||||
///
|
||||
/// [`Node`]: ../../struct.Node.html
|
||||
/// [`Image`]: struct.Image.html
|
||||
fn layout(&self, image: &Image, limits: &layout::Limits) -> layout::Node;
|
||||
fn dimensions(&self, path: &str) -> (u32, u32);
|
||||
|
||||
/// Draws an [`Image`].
|
||||
///
|
||||
|
|
|
|||
|
|
@ -1,15 +1,82 @@
|
|||
//! Create choices using radio buttons.
|
||||
use crate::input::{mouse, ButtonState};
|
||||
use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget};
|
||||
use crate::{
|
||||
input::{mouse, ButtonState},
|
||||
layout, row, text, Align, Color, Element, Event, Font, Hasher,
|
||||
HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,
|
||||
VerticalAlignment, Widget,
|
||||
};
|
||||
|
||||
use std::hash::Hash;
|
||||
|
||||
pub use iced_core::Radio;
|
||||
/// A circular button representing a choice.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use iced_native::Radio;
|
||||
///
|
||||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// pub enum Choice {
|
||||
/// A,
|
||||
/// B,
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Debug, Clone, Copy)]
|
||||
/// pub enum Message {
|
||||
/// RadioSelected(Choice),
|
||||
/// }
|
||||
///
|
||||
/// let selected_choice = Some(Choice::A);
|
||||
///
|
||||
/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected);
|
||||
///
|
||||
/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected);
|
||||
/// ```
|
||||
///
|
||||
/// 
|
||||
pub struct Radio<Message> {
|
||||
is_selected: bool,
|
||||
on_click: Message,
|
||||
label: String,
|
||||
label_color: Option<Color>,
|
||||
}
|
||||
|
||||
impl<Message> Radio<Message> {
|
||||
/// Creates a new [`Radio`] button.
|
||||
///
|
||||
/// It expects:
|
||||
/// * the value related to the [`Radio`] button
|
||||
/// * the label of the [`Radio`] button
|
||||
/// * the current selected value
|
||||
/// * a function that will be called when the [`Radio`] is selected. It
|
||||
/// receives the value of the radio and must produce a `Message`.
|
||||
///
|
||||
/// [`Radio`]: struct.Radio.html
|
||||
pub fn new<F, V>(value: V, label: &str, selected: Option<V>, f: F) -> Self
|
||||
where
|
||||
V: Eq + Copy,
|
||||
F: 'static + Fn(V) -> Message,
|
||||
{
|
||||
Radio {
|
||||
is_selected: Some(value) == selected,
|
||||
on_click: f(value),
|
||||
label: String::from(label),
|
||||
label_color: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the `Color` of the label of the [`Radio`].
|
||||
///
|
||||
/// [`Radio`]: struct.Radio.html
|
||||
pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self {
|
||||
self.label_color = Some(color.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Message: Clone + std::fmt::Debug,
|
||||
Renderer: self::Renderer + text::Renderer + row::Renderer,
|
||||
Message: Clone,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
Length::Fill
|
||||
|
|
@ -20,7 +87,18 @@ where
|
|||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
renderer.layout(&self, limits)
|
||||
let size = self::Renderer::default_size(renderer);
|
||||
|
||||
Row::<(), Renderer>::new()
|
||||
.spacing(15)
|
||||
.align_items(Align::Center)
|
||||
.push(
|
||||
Row::new()
|
||||
.width(Length::Units(size as u16))
|
||||
.height(Length::Units(size as u16)),
|
||||
)
|
||||
.push(Text::new(&self.label))
|
||||
.layout(renderer, limits)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
|
|
@ -50,7 +128,33 @@ where
|
|||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Renderer::Output {
|
||||
renderer.draw(&self, layout, cursor_position)
|
||||
let bounds = layout.bounds();
|
||||
let mut children = layout.children();
|
||||
|
||||
let radio_layout = children.next().unwrap();
|
||||
let label_layout = children.next().unwrap();
|
||||
let radio_bounds = radio_layout.bounds();
|
||||
|
||||
let label = text::Renderer::draw(
|
||||
renderer,
|
||||
label_layout.bounds(),
|
||||
&self.label,
|
||||
text::Renderer::default_size(renderer),
|
||||
Font::Default,
|
||||
self.label_color,
|
||||
HorizontalAlignment::Left,
|
||||
VerticalAlignment::Center,
|
||||
);
|
||||
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
self::Renderer::draw(
|
||||
renderer,
|
||||
radio_bounds,
|
||||
self.is_selected,
|
||||
is_mouse_over,
|
||||
label,
|
||||
)
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -66,15 +170,7 @@ where
|
|||
/// [`Radio`]: struct.Radio.html
|
||||
/// [renderer]: ../../renderer/index.html
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// Creates a [`Node`] for the provided [`Radio`].
|
||||
///
|
||||
/// [`Node`]: ../../struct.Node.html
|
||||
/// [`Radio`]: struct.Radio.html
|
||||
fn layout<Message>(
|
||||
&self,
|
||||
radio: &Radio<Message>,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node;
|
||||
fn default_size(&self) -> u32;
|
||||
|
||||
/// Draws a [`Radio`] button.
|
||||
///
|
||||
|
|
@ -85,21 +181,22 @@ pub trait Renderer: crate::Renderer {
|
|||
/// * whether the [`Radio`] is selected or not
|
||||
///
|
||||
/// [`Radio`]: struct.Radio.html
|
||||
fn draw<Message>(
|
||||
fn draw(
|
||||
&mut self,
|
||||
radio: &Radio<Message>,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
bounds: Rectangle,
|
||||
is_selected: bool,
|
||||
is_mouse_over: bool,
|
||||
label: Self::Output,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Radio<Message>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Message: 'static + Clone + std::fmt::Debug,
|
||||
Renderer: self::Renderer + row::Renderer + text::Renderer,
|
||||
Message: 'static + Clone,
|
||||
{
|
||||
fn from(checkbox: Radio<Message>) -> Element<'a, Message, Renderer> {
|
||||
Element::new(checkbox)
|
||||
fn from(radio: Radio<Message>) -> Element<'a, Message, Renderer> {
|
||||
Element::new(radio)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,115 @@
|
|||
use std::hash::Hash;
|
||||
|
||||
use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget};
|
||||
use crate::{
|
||||
layout, Align, Element, Event, Hasher, Layout, Length, Point, Widget,
|
||||
};
|
||||
|
||||
use std::u32;
|
||||
|
||||
/// A container that distributes its contents horizontally.
|
||||
pub type Row<'a, Message, Renderer> =
|
||||
iced_core::Row<Element<'a, Message, Renderer>>;
|
||||
///
|
||||
/// A [`Row`] will try to fill the horizontal space of its container.
|
||||
///
|
||||
/// [`Row`]: struct.Row.html
|
||||
#[allow(missing_docs)]
|
||||
pub struct Row<'a, Message, Renderer> {
|
||||
spacing: u16,
|
||||
padding: u16,
|
||||
width: Length,
|
||||
height: Length,
|
||||
max_width: u32,
|
||||
max_height: u32,
|
||||
align_items: Align,
|
||||
children: Vec<Element<'a, Message, Renderer>>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
|
||||
/// Creates an empty [`Row`].
|
||||
///
|
||||
/// [`Row`]: struct.Row.html
|
||||
pub fn new() -> Self {
|
||||
Row {
|
||||
spacing: 0,
|
||||
padding: 0,
|
||||
width: Length::Fill,
|
||||
height: Length::Shrink,
|
||||
max_width: u32::MAX,
|
||||
max_height: u32::MAX,
|
||||
align_items: Align::Start,
|
||||
children: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the horizontal spacing _between_ elements.
|
||||
///
|
||||
/// Custom margins per element do not exist in Iced. You should use this
|
||||
/// method instead! While less flexible, it helps you keep spacing between
|
||||
/// elements consistent.
|
||||
pub fn spacing(mut self, units: u16) -> Self {
|
||||
self.spacing = units;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the padding of the [`Row`].
|
||||
///
|
||||
/// [`Row`]: struct.Row.html
|
||||
pub fn padding(mut self, units: u16) -> Self {
|
||||
self.padding = units;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Row`].
|
||||
///
|
||||
/// [`Row`]: struct.Row.html
|
||||
pub fn width(mut self, width: Length) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the height of the [`Row`].
|
||||
///
|
||||
/// [`Row`]: struct.Row.html
|
||||
pub fn height(mut self, height: Length) -> Self {
|
||||
self.height = height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum width of the [`Row`].
|
||||
///
|
||||
/// [`Row`]: struct.Row.html
|
||||
pub fn max_width(mut self, max_width: u32) -> Self {
|
||||
self.max_width = max_width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum height of the [`Row`].
|
||||
///
|
||||
/// [`Row`]: struct.Row.html
|
||||
pub fn max_height(mut self, max_height: u32) -> Self {
|
||||
self.max_height = max_height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the vertical alignment of the contents of the [`Row`] .
|
||||
///
|
||||
/// [`Row`]: struct.Row.html
|
||||
pub fn align_items(mut self, align: Align) -> Self {
|
||||
self.align_items = align;
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds an [`Element`] to the [`Row`].
|
||||
///
|
||||
/// [`Element`]: ../struct.Element.html
|
||||
/// [`Row`]: struct.Row.html
|
||||
pub fn push<E>(mut self, child: E) -> Self
|
||||
where
|
||||
E: Into<Element<'a, Message, Renderer>>,
|
||||
{
|
||||
self.children.push(child.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Row<'a, Message, Renderer>
|
||||
|
|
@ -64,7 +169,7 @@ where
|
|||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Renderer::Output {
|
||||
renderer.draw(&self, layout, cursor_position)
|
||||
renderer.draw(&self.children, layout, cursor_position)
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -86,7 +191,7 @@ where
|
|||
pub trait Renderer: crate::Renderer + Sized {
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
row: &Row<'_, Message, Self>,
|
||||
children: &[Element<'_, Message, Self>],
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Self::Output;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,104 @@
|
|||
use crate::{
|
||||
column,
|
||||
input::{mouse, ButtonState},
|
||||
layout, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size,
|
||||
Widget,
|
||||
layout, Align, Column, Element, Event, Hasher, Layout, Length, Point,
|
||||
Rectangle, Size, Widget,
|
||||
};
|
||||
|
||||
pub use iced_core::scrollable::State;
|
||||
use std::{f32, hash::Hash, u32};
|
||||
|
||||
use std::f32;
|
||||
use std::hash::Hash;
|
||||
/// A widget that can vertically display an infinite amount of content with a
|
||||
/// scrollbar.
|
||||
pub struct Scrollable<'a, Message, Renderer> {
|
||||
state: &'a mut State,
|
||||
height: Length,
|
||||
max_height: u32,
|
||||
content: Column<'a, Message, Renderer>,
|
||||
}
|
||||
|
||||
/// A scrollable [`Column`].
|
||||
///
|
||||
/// [`Column`]: ../column/struct.Column.html
|
||||
pub type Scrollable<'a, Message, Renderer> =
|
||||
iced_core::Scrollable<'a, Element<'a, Message, Renderer>>;
|
||||
impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> {
|
||||
/// Creates a new [`Scrollable`] with the given [`State`].
|
||||
///
|
||||
/// [`Scrollable`]: struct.Scrollable.html
|
||||
/// [`State`]: struct.State.html
|
||||
pub fn new(state: &'a mut State) -> Self {
|
||||
Scrollable {
|
||||
state,
|
||||
height: Length::Shrink,
|
||||
max_height: u32::MAX,
|
||||
content: Column::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the vertical spacing _between_ elements.
|
||||
///
|
||||
/// Custom margins per element do not exist in Iced. You should use this
|
||||
/// method instead! While less flexible, it helps you keep spacing between
|
||||
/// elements consistent.
|
||||
pub fn spacing(mut self, units: u16) -> Self {
|
||||
self.content = self.content.spacing(units);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the padding of the [`Scrollable`].
|
||||
///
|
||||
/// [`Scrollable`]: struct.Scrollable.html
|
||||
pub fn padding(mut self, units: u16) -> Self {
|
||||
self.content = self.content.padding(units);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Scrollable`].
|
||||
///
|
||||
/// [`Scrollable`]: struct.Scrollable.html
|
||||
pub fn width(mut self, width: Length) -> Self {
|
||||
self.content = self.content.width(width);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the height of the [`Scrollable`].
|
||||
///
|
||||
/// [`Scrollable`]: struct.Scrollable.html
|
||||
pub fn height(mut self, height: Length) -> Self {
|
||||
self.height = height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum width of the [`Scrollable`].
|
||||
///
|
||||
/// [`Scrollable`]: struct.Scrollable.html
|
||||
pub fn max_width(mut self, max_width: u32) -> Self {
|
||||
self.content = self.content.max_width(max_width);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum height of the [`Scrollable`] in pixels.
|
||||
///
|
||||
/// [`Scrollable`]: struct.Scrollable.html
|
||||
pub fn max_height(mut self, max_height: u32) -> Self {
|
||||
self.max_height = max_height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the horizontal alignment of the contents of the [`Scrollable`] .
|
||||
///
|
||||
/// [`Scrollable`]: struct.Scrollable.html
|
||||
pub fn align_items(mut self, align_items: Align) -> Self {
|
||||
self.content = self.content.align_items(align_items);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds an element to the [`Scrollable`].
|
||||
///
|
||||
/// [`Scrollable`]: struct.Scrollable.html
|
||||
pub fn push<E>(mut self, child: E) -> Self
|
||||
where
|
||||
E: Into<Element<'a, Message, Renderer>>,
|
||||
{
|
||||
self.content = self.content.push(child);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Scrollable<'a, Message, Renderer>
|
||||
|
|
@ -153,13 +237,35 @@ where
|
|||
) -> Renderer::Output {
|
||||
let bounds = layout.bounds();
|
||||
let content_layout = layout.children().next().unwrap();
|
||||
let content_bounds = content_layout.bounds();
|
||||
let offset = self.state.offset(bounds, content_bounds);
|
||||
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
let is_mouse_over_scrollbar = renderer.is_mouse_over_scrollbar(
|
||||
bounds,
|
||||
content_bounds,
|
||||
cursor_position,
|
||||
);
|
||||
|
||||
let content = {
|
||||
let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
|
||||
Point::new(cursor_position.x, cursor_position.y + offset as f32)
|
||||
} else {
|
||||
Point::new(cursor_position.x, -1.0)
|
||||
};
|
||||
|
||||
self.content.draw(renderer, content_layout, cursor_position)
|
||||
};
|
||||
|
||||
self::Renderer::draw(
|
||||
renderer,
|
||||
&self,
|
||||
&self.state,
|
||||
bounds,
|
||||
content_layout,
|
||||
cursor_position,
|
||||
content_layout.bounds(),
|
||||
is_mouse_over,
|
||||
is_mouse_over_scrollbar,
|
||||
offset,
|
||||
content,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -173,6 +279,80 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The local state of a [`Scrollable`].
|
||||
///
|
||||
/// [`Scrollable`]: struct.Scrollable.html
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct State {
|
||||
scrollbar_grabbed_at: Option<Point>,
|
||||
offset: u32,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Creates a new [`State`] with the scrollbar located at the top.
|
||||
///
|
||||
/// [`State`]: struct.State.html
|
||||
pub fn new() -> Self {
|
||||
State::default()
|
||||
}
|
||||
|
||||
/// Apply a scrolling offset to the current [`State`], given the bounds of
|
||||
/// the [`Scrollable`] and its contents.
|
||||
///
|
||||
/// [`Scrollable`]: struct.Scrollable.html
|
||||
/// [`State`]: struct.State.html
|
||||
pub fn scroll(
|
||||
&mut self,
|
||||
delta_y: f32,
|
||||
bounds: Rectangle,
|
||||
content_bounds: Rectangle,
|
||||
) {
|
||||
if bounds.height >= content_bounds.height {
|
||||
return;
|
||||
}
|
||||
|
||||
self.offset = (self.offset as i32 - delta_y.round() as i32)
|
||||
.max(0)
|
||||
.min((content_bounds.height - bounds.height) as i32)
|
||||
as u32;
|
||||
}
|
||||
|
||||
/// Moves the scroll position to a relative amount, given the bounds of
|
||||
/// the [`Scrollable`] and its contents.
|
||||
///
|
||||
/// `0` represents scrollbar at the top, while `1` represents scrollbar at
|
||||
/// the bottom.
|
||||
///
|
||||
/// [`Scrollable`]: struct.Scrollable.html
|
||||
/// [`State`]: struct.State.html
|
||||
pub fn scroll_to(
|
||||
&mut self,
|
||||
percentage: f32,
|
||||
bounds: Rectangle,
|
||||
content_bounds: Rectangle,
|
||||
) {
|
||||
self.offset = ((content_bounds.height - bounds.height) * percentage)
|
||||
.max(0.0) as u32;
|
||||
}
|
||||
|
||||
/// Returns the current scrolling offset of the [`State`], given the bounds
|
||||
/// of the [`Scrollable`] and its contents.
|
||||
///
|
||||
/// [`Scrollable`]: struct.Scrollable.html
|
||||
/// [`State`]: struct.State.html
|
||||
pub fn offset(&self, bounds: Rectangle, content_bounds: Rectangle) -> u32 {
|
||||
let hidden_content =
|
||||
(content_bounds.height - bounds.height).max(0.0).round() as u32;
|
||||
|
||||
self.offset.min(hidden_content)
|
||||
}
|
||||
|
||||
/// Returns whether the scrollbar is currently grabbed or not.
|
||||
pub fn is_scrollbar_grabbed(&self) -> bool {
|
||||
self.scrollbar_grabbed_at.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Renderer: crate::Renderer + Sized {
|
||||
fn is_mouse_over_scrollbar(
|
||||
&self,
|
||||
|
|
@ -181,12 +361,15 @@ pub trait Renderer: crate::Renderer + Sized {
|
|||
cursor_position: Point,
|
||||
) -> bool;
|
||||
|
||||
fn draw<Message>(
|
||||
fn draw(
|
||||
&mut self,
|
||||
scrollable: &Scrollable<'_, Message, Self>,
|
||||
scrollable: &State,
|
||||
bounds: Rectangle,
|
||||
content_layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
content_bounds: Rectangle,
|
||||
is_mouse_over: bool,
|
||||
is_mouse_over_scrollbar: bool,
|
||||
offset: u32,
|
||||
content: Self::Output,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,79 @@
|
|||
//!
|
||||
//! [`Slider`]: struct.Slider.html
|
||||
//! [`State`]: struct.State.html
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::input::{mouse, ButtonState};
|
||||
use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget};
|
||||
use crate::{
|
||||
input::{mouse, ButtonState},
|
||||
layout, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size,
|
||||
Widget,
|
||||
};
|
||||
|
||||
pub use iced_core::slider::*;
|
||||
use std::{hash::Hash, ops::RangeInclusive};
|
||||
|
||||
pub struct Slider<'a, Message> {
|
||||
state: &'a mut State,
|
||||
range: RangeInclusive<f32>,
|
||||
value: f32,
|
||||
on_change: Box<dyn Fn(f32) -> Message>,
|
||||
width: Length,
|
||||
}
|
||||
|
||||
impl<'a, Message> Slider<'a, Message> {
|
||||
/// Creates a new [`Slider`].
|
||||
///
|
||||
/// It expects:
|
||||
/// * the local [`State`] of the [`Slider`]
|
||||
/// * an inclusive range of possible values
|
||||
/// * the current value of the [`Slider`]
|
||||
/// * a function that will be called when the [`Slider`] is dragged.
|
||||
/// It receives the new value of the [`Slider`] and must produce a
|
||||
/// `Message`.
|
||||
///
|
||||
/// [`Slider`]: struct.Slider.html
|
||||
/// [`State`]: struct.State.html
|
||||
pub fn new<F>(
|
||||
state: &'a mut State,
|
||||
range: RangeInclusive<f32>,
|
||||
value: f32,
|
||||
on_change: F,
|
||||
) -> Self
|
||||
where
|
||||
F: 'static + Fn(f32) -> Message,
|
||||
{
|
||||
Slider {
|
||||
state,
|
||||
value: value.max(*range.start()).min(*range.end()),
|
||||
range,
|
||||
on_change: Box::new(on_change),
|
||||
width: Length::Fill,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Slider`].
|
||||
///
|
||||
/// [`Slider`]: struct.Slider.html
|
||||
pub fn width(mut self, width: Length) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The local state of a [`Slider`].
|
||||
///
|
||||
/// [`Slider`]: struct.Slider.html
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct State {
|
||||
is_dragging: bool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Creates a new [`State`].
|
||||
///
|
||||
/// [`State`]: struct.State.html
|
||||
pub fn new() -> State {
|
||||
State::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer> for Slider<'a, Message>
|
||||
where
|
||||
|
|
@ -24,7 +91,13 @@ where
|
|||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
renderer.layout(&self, limits)
|
||||
let limits = limits
|
||||
.width(self.width)
|
||||
.height(Length::Units(renderer.height() as u16));
|
||||
|
||||
let size = limits.resolve(Size::ZERO);
|
||||
|
||||
layout::Node::new(size)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
|
|
@ -81,7 +154,13 @@ where
|
|||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Renderer::Output {
|
||||
renderer.draw(&self, layout, cursor_position)
|
||||
renderer.draw(
|
||||
layout.bounds(),
|
||||
cursor_position,
|
||||
self.range.clone(),
|
||||
self.value,
|
||||
self.state.is_dragging,
|
||||
)
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -97,15 +176,7 @@ where
|
|||
/// [`Slider`]: struct.Slider.html
|
||||
/// [renderer]: ../../renderer/index.html
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// Creates a [`Node`] for the provided [`Radio`].
|
||||
///
|
||||
/// [`Node`]: ../../struct.Node.html
|
||||
/// [`Radio`]: struct.Radio.html
|
||||
fn layout<Message>(
|
||||
&self,
|
||||
slider: &Slider<'_, Message>,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node;
|
||||
fn height(&self) -> u32;
|
||||
|
||||
/// Draws a [`Slider`].
|
||||
///
|
||||
|
|
@ -119,11 +190,13 @@ pub trait Renderer: crate::Renderer {
|
|||
/// [`Slider`]: struct.Slider.html
|
||||
/// [`State`]: struct.State.html
|
||||
/// [`Class`]: enum.Class.html
|
||||
fn draw<Message>(
|
||||
fn draw(
|
||||
&mut self,
|
||||
slider: &Slider<'_, Message>,
|
||||
layout: Layout<'_>,
|
||||
bounds: Rectangle,
|
||||
cursor_position: Point,
|
||||
range: RangeInclusive<f32>,
|
||||
value: f32,
|
||||
is_dragging: bool,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,113 @@
|
|||
//! Write some text for your users to read.
|
||||
use crate::{layout, Element, Hasher, Layout, Length, Point, Widget};
|
||||
use crate::{
|
||||
layout, Color, Element, Font, Hasher, HorizontalAlignment, Layout, Length,
|
||||
Point, Rectangle, Size, VerticalAlignment, Widget,
|
||||
};
|
||||
|
||||
use std::hash::Hash;
|
||||
|
||||
pub use iced_core::text::*;
|
||||
/// A paragraph of text.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use iced_native::Text;
|
||||
///
|
||||
/// Text::new("I <3 iced!")
|
||||
/// .size(40);
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Text {
|
||||
content: String,
|
||||
size: Option<u16>,
|
||||
color: Option<Color>,
|
||||
font: Font,
|
||||
width: Length,
|
||||
height: Length,
|
||||
horizontal_alignment: HorizontalAlignment,
|
||||
vertical_alignment: VerticalAlignment,
|
||||
}
|
||||
|
||||
impl Text {
|
||||
/// Create a new fragment of [`Text`] with the given contents.
|
||||
///
|
||||
/// [`Text`]: struct.Text.html
|
||||
pub fn new(label: &str) -> Self {
|
||||
Text {
|
||||
content: String::from(label),
|
||||
size: None,
|
||||
color: None,
|
||||
font: Font::Default,
|
||||
width: Length::Fill,
|
||||
height: Length::Shrink,
|
||||
horizontal_alignment: HorizontalAlignment::Left,
|
||||
vertical_alignment: VerticalAlignment::Top,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the size of the [`Text`].
|
||||
///
|
||||
/// [`Text`]: struct.Text.html
|
||||
pub fn size(mut self, size: u16) -> Self {
|
||||
self.size = Some(size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`Color`] of the [`Text`].
|
||||
///
|
||||
/// [`Text`]: struct.Text.html
|
||||
/// [`Color`]: ../../struct.Color.html
|
||||
pub fn color<C: Into<Color>>(mut self, color: C) -> Self {
|
||||
self.color = Some(color.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`Font`] of the [`Text`].
|
||||
///
|
||||
/// [`Text`]: struct.Text.html
|
||||
/// [`Font`]: ../../struct.Font.html
|
||||
pub fn font(mut self, font: Font) -> Self {
|
||||
self.font = font;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Text`] boundaries.
|
||||
///
|
||||
/// [`Text`]: struct.Text.html
|
||||
pub fn width(mut self, width: Length) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the height of the [`Text`] boundaries.
|
||||
///
|
||||
/// [`Text`]: struct.Text.html
|
||||
pub fn height(mut self, height: Length) -> Self {
|
||||
self.height = height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`HorizontalAlignment`] of the [`Text`].
|
||||
///
|
||||
/// [`Text`]: struct.Text.html
|
||||
/// [`HorizontalAlignment`]: enum.HorizontalAlignment.html
|
||||
pub fn horizontal_alignment(
|
||||
mut self,
|
||||
alignment: HorizontalAlignment,
|
||||
) -> Self {
|
||||
self.horizontal_alignment = alignment;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`VerticalAlignment`] of the [`Text`].
|
||||
///
|
||||
/// [`Text`]: struct.Text.html
|
||||
/// [`VerticalAlignment`]: enum.VerticalAlignment.html
|
||||
pub fn vertical_alignment(mut self, alignment: VerticalAlignment) -> Self {
|
||||
self.vertical_alignment = alignment;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Renderer> Widget<Message, Renderer> for Text
|
||||
where
|
||||
|
|
@ -18,7 +122,18 @@ where
|
|||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
renderer.layout(&self, limits)
|
||||
let limits = limits.width(self.width).height(self.height);
|
||||
|
||||
let size = self.size.unwrap_or(renderer.default_size());
|
||||
|
||||
let bounds = limits.max();
|
||||
|
||||
let (width, height) =
|
||||
renderer.measure(&self.content, size, self.font, bounds);
|
||||
|
||||
let size = limits.resolve(Size::new(width, height));
|
||||
|
||||
layout::Node::new(size)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
|
|
@ -27,7 +142,15 @@ where
|
|||
layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
) -> Renderer::Output {
|
||||
renderer.draw(&self, layout)
|
||||
renderer.draw(
|
||||
layout.bounds(),
|
||||
&self.content,
|
||||
self.size.unwrap_or(renderer.default_size()),
|
||||
self.font,
|
||||
self.color,
|
||||
self.horizontal_alignment,
|
||||
self.vertical_alignment,
|
||||
)
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -47,17 +170,15 @@ where
|
|||
/// [renderer]: ../../renderer/index.html
|
||||
/// [`UserInterface`]: ../../struct.UserInterface.html
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// Creates a [`Node`] with the given [`Style`] for the provided [`Text`]
|
||||
/// contents and size.
|
||||
///
|
||||
/// You should probably use [`Node::with_measure`] to allow [`Text`] to
|
||||
/// adapt to the dimensions of its container.
|
||||
///
|
||||
/// [`Node`]: ../../struct.Node.html
|
||||
/// [`Style`]: ../../struct.Style.html
|
||||
/// [`Text`]: struct.Text.html
|
||||
/// [`Node::with_measure`]: ../../struct.Node.html#method.with_measure
|
||||
fn layout(&self, text: &Text, limits: &layout::Limits) -> layout::Node;
|
||||
fn default_size(&self) -> u16;
|
||||
|
||||
fn measure(
|
||||
&self,
|
||||
content: &str,
|
||||
size: u16,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
) -> (f32, f32);
|
||||
|
||||
/// Draws a [`Text`] fragment.
|
||||
///
|
||||
|
|
@ -72,7 +193,16 @@ pub trait Renderer: crate::Renderer {
|
|||
/// [`Text`]: struct.Text.html
|
||||
/// [`HorizontalAlignment`]: enum.HorizontalAlignment.html
|
||||
/// [`VerticalAlignment`]: enum.VerticalAlignment.html
|
||||
fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Output;
|
||||
fn draw(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
content: &str,
|
||||
size: u16,
|
||||
font: Font,
|
||||
color: Option<Color>,
|
||||
horizontal_alignment: HorizontalAlignment,
|
||||
vertical_alignment: VerticalAlignment,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Text> for Element<'a, Message, Renderer>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,95 @@
|
|||
//! Display fields that can be filled with text.
|
||||
//!
|
||||
//! A [`TextInput`] has some local [`State`].
|
||||
//!
|
||||
//! [`TextInput`]: struct.TextInput.html
|
||||
//! [`State`]: struct.State.html
|
||||
use crate::{
|
||||
input::{keyboard, mouse, ButtonState},
|
||||
layout, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size,
|
||||
Widget,
|
||||
};
|
||||
|
||||
pub use iced_core::{text_input::State, TextInput};
|
||||
/// A widget that can be filled with text by using a keyboard.
|
||||
pub struct TextInput<'a, Message> {
|
||||
state: &'a mut State,
|
||||
placeholder: String,
|
||||
value: Value,
|
||||
width: Length,
|
||||
max_width: Length,
|
||||
padding: u16,
|
||||
size: Option<u16>,
|
||||
on_change: Box<dyn Fn(String) -> Message>,
|
||||
on_submit: Option<Message>,
|
||||
}
|
||||
|
||||
impl<'a, Message> TextInput<'a, Message> {
|
||||
/// Creates a new [`TextInput`].
|
||||
///
|
||||
/// [`TextInput`]: struct.TextInput.html
|
||||
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
|
||||
}
|
||||
|
||||
/// Sets the text size of the [`TextInput`].
|
||||
///
|
||||
/// [`TextInput`]: struct.TextInput.html
|
||||
pub fn size(mut self, size: u16) -> Self {
|
||||
self.size = Some(size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the message that should be produced when the [`TextInput`] is
|
||||
/// focused and the enter key is pressed.
|
||||
///
|
||||
/// [`TextInput`]: struct.TextInput.html
|
||||
pub fn on_submit(mut self, message: Message) -> Self {
|
||||
self.on_submit = Some(message);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer> for TextInput<'a, Message>
|
||||
where
|
||||
|
|
@ -120,12 +205,19 @@ where
|
|||
let bounds = layout.bounds();
|
||||
let text_bounds = layout.children().next().unwrap().bounds();
|
||||
|
||||
renderer.draw(&self, bounds, text_bounds, cursor_position)
|
||||
renderer.draw(
|
||||
bounds,
|
||||
text_bounds,
|
||||
cursor_position,
|
||||
self.size.unwrap_or(renderer.default_size()),
|
||||
&self.placeholder,
|
||||
&self.value,
|
||||
&self.state,
|
||||
)
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
use std::any::TypeId;
|
||||
use std::hash::Hash;
|
||||
use std::{any::TypeId, hash::Hash};
|
||||
|
||||
TypeId::of::<TextInput<'static, ()>>().hash(state);
|
||||
|
||||
|
|
@ -139,12 +231,15 @@ where
|
|||
pub trait Renderer: crate::Renderer + Sized {
|
||||
fn default_size(&self) -> u16;
|
||||
|
||||
fn draw<Message>(
|
||||
fn draw(
|
||||
&mut self,
|
||||
text_input: &TextInput<'_, Message>,
|
||||
bounds: Rectangle,
|
||||
text_bounds: Rectangle,
|
||||
cursor_position: Point,
|
||||
size: u16,
|
||||
placeholder: &str,
|
||||
value: &Value,
|
||||
state: &State,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
|
|
@ -160,3 +255,125 @@ where
|
|||
Element::new(text_input)
|
||||
}
|
||||
}
|
||||
|
||||
/// The state of a [`TextInput`].
|
||||
///
|
||||
/// [`TextInput`]: struct.TextInput.html
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct State {
|
||||
is_focused: bool,
|
||||
cursor_position: usize,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Creates a new [`State`], representing an unfocused [`TextInput`].
|
||||
///
|
||||
/// [`State`]: struct.State.html
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Creates a new [`State`], representing a focused [`TextInput`].
|
||||
///
|
||||
/// [`State`]: struct.State.html
|
||||
pub fn focused() -> Self {
|
||||
use std::usize;
|
||||
|
||||
Self {
|
||||
is_focused: true,
|
||||
cursor_position: usize::MAX,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_focused(&self) -> bool {
|
||||
self.is_focused
|
||||
}
|
||||
|
||||
/// Returns the cursor position of a [`TextInput`].
|
||||
///
|
||||
/// [`TextInput`]: struct.TextInput.html
|
||||
pub fn cursor_position(&self, value: &Value) -> usize {
|
||||
self.cursor_position.min(value.len())
|
||||
}
|
||||
|
||||
/// Moves the cursor of a [`TextInput`] to the right.
|
||||
///
|
||||
/// [`TextInput`]: struct.TextInput.html
|
||||
pub fn move_cursor_right(&mut self, value: &Value) {
|
||||
let current = self.cursor_position(value);
|
||||
|
||||
if current < value.len() {
|
||||
self.cursor_position = current + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves the cursor of a [`TextInput`] to the left.
|
||||
///
|
||||
/// [`TextInput`]: struct.TextInput.html
|
||||
pub fn move_cursor_left(&mut self, value: &Value) {
|
||||
let current = self.cursor_position(value);
|
||||
|
||||
if current > 0 {
|
||||
self.cursor_position = current - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves the cursor of a [`TextInput`] to the end.
|
||||
///
|
||||
/// [`TextInput`]: struct.TextInput.html
|
||||
pub fn move_cursor_to_end(&mut self, value: &Value) {
|
||||
self.cursor_position = value.len();
|
||||
}
|
||||
}
|
||||
|
||||
/// The value of a [`TextInput`].
|
||||
///
|
||||
/// [`TextInput`]: struct.TextInput.html
|
||||
// TODO: Use `unicode-segmentation`
|
||||
#[derive(Debug)]
|
||||
pub struct Value(Vec<char>);
|
||||
|
||||
impl Value {
|
||||
/// Creates a new [`Value`] from a string slice.
|
||||
///
|
||||
/// [`Value`]: struct.Value.html
|
||||
pub fn new(string: &str) -> Self {
|
||||
Self(string.chars().collect())
|
||||
}
|
||||
|
||||
/// Returns the total amount of `char` in the [`Value`].
|
||||
///
|
||||
/// [`Value`]: struct.Value.html
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
/// Returns a new [`Value`] containing the `char` until the given `index`.
|
||||
///
|
||||
/// [`Value`]: struct.Value.html
|
||||
pub fn until(&self, index: usize) -> Self {
|
||||
Self(self.0[..index.min(self.len())].to_vec())
|
||||
}
|
||||
|
||||
/// Converts the [`Value`] into a `String`.
|
||||
///
|
||||
/// [`Value`]: struct.Value.html
|
||||
pub fn to_string(&self) -> String {
|
||||
use std::iter::FromIterator;
|
||||
String::from_iter(self.0.iter())
|
||||
}
|
||||
|
||||
/// Inserts a new `char` at the given `index`.
|
||||
///
|
||||
/// [`Value`]: struct.Value.html
|
||||
pub fn insert(&mut self, index: usize, c: char) {
|
||||
self.0.insert(index, c);
|
||||
}
|
||||
|
||||
/// Removes the `char` at the given `index`.
|
||||
///
|
||||
/// [`Value`]: struct.Value.html
|
||||
pub fn remove(&mut self, index: usize) {
|
||||
let _ = self.0.remove(index);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue