Merge pull request #2312 from iced-rs/theming-reloaded

Theming reloaded
This commit is contained in:
Héctor Ramón 2024-03-08 14:00:28 +01:00 committed by GitHub
commit edf7d7ca75
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
97 changed files with 5738 additions and 6660 deletions

View file

@ -16,7 +16,6 @@ jobs:
cargo doc --no-deps --all-features \
-p iced_core \
-p iced_highlighter \
-p iced_style \
-p iced_futures \
-p iced_runtime \
-p iced_graphics \

View file

@ -39,8 +39,6 @@ tokio = ["iced_futures/tokio"]
async-std = ["iced_futures/async-std"]
# Enables `smol` as the `executor::Default` on native platforms
smol = ["iced_futures/smol"]
# Enables advanced color conversion via `palette`
palette = ["iced_core/palette"]
# Enables querying system information
system = ["iced_winit/system"]
# Enables broken "sRGB linear" blending to reproduce color management of the Web
@ -57,7 +55,6 @@ advanced = []
fira-sans = ["iced_renderer/fira-sans"]
[dependencies]
iced_core.workspace = true
iced_futures.workspace = true
iced_renderer.workspace = true
iced_widget.workspace = true
@ -90,7 +87,6 @@ members = [
"highlighter",
"renderer",
"runtime",
"style",
"tiny_skia",
"wgpu",
"widget",
@ -116,7 +112,6 @@ iced_graphics = { version = "0.13.0-dev", path = "graphics" }
iced_highlighter = { version = "0.13.0-dev", path = "highlighter" }
iced_renderer = { version = "0.13.0-dev", path = "renderer" }
iced_runtime = { version = "0.13.0-dev", path = "runtime" }
iced_style = { version = "0.13.0-dev", path = "style" }
iced_tiny_skia = { version = "0.13.0-dev", path = "tiny_skia" }
iced_wgpu = { version = "0.13.0-dev", path = "wgpu" }
iced_widget = { version = "0.13.0-dev", path = "widget" }

View file

@ -15,14 +15,13 @@ bitflags.workspace = true
glam.workspace = true
log.workspace = true
num-traits.workspace = true
once_cell.workspace = true
palette.workspace = true
smol_str.workspace = true
thiserror.workspace = true
web-time.workspace = true
xxhash-rust.workspace = true
palette.workspace = true
palette.optional = true
[target.'cfg(windows)'.dependencies]
raw-window-handle.workspace = true

View file

@ -11,6 +11,19 @@ pub enum Background {
// TODO: Add image variant
}
impl Background {
/// Scales the the alpha channel of the [`Background`] by the given
/// factor.
pub fn scale_alpha(self, factor: f32) -> Self {
match self {
Self::Color(color) => Self::Color(color.scale_alpha(factor)),
Self::Gradient(gradient) => {
Self::Gradient(gradient.scale_alpha(factor))
}
}
}
}
impl From<Color> for Background {
fn from(color: Color) -> Self {
Background::Color(color)

View file

@ -1,5 +1,5 @@
//! Draw lines around containers.
use crate::Color;
use crate::{Color, Pixels};
/// A border.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
@ -15,11 +15,38 @@ pub struct Border {
}
impl Border {
/// Creates a new default [`Border`] with the given [`Radius`].
pub fn with_radius(radius: impl Into<Radius>) -> Self {
/// Creates a new default rounded [`Border`] with the given [`Radius`].
///
/// ```
/// # use iced_core::Border;
/// #
/// assert_eq!(Border::rounded(10), Border::default().with_radius(10));
/// ```
pub fn rounded(radius: impl Into<Radius>) -> Self {
Self::default().with_radius(radius)
}
/// Updates the [`Color`] of the [`Border`].
pub fn with_color(self, color: impl Into<Color>) -> Self {
Self {
color: color.into(),
..self
}
}
/// Updates the [`Radius`] of the [`Border`].
pub fn with_radius(self, radius: impl Into<Radius>) -> Self {
Self {
radius: radius.into(),
..Self::default()
..self
}
}
/// Updates the width of the [`Border`].
pub fn with_width(self, width: impl Into<Pixels>) -> Self {
Self {
width: width.into().0,
..self
}
}
}

View file

@ -1,4 +1,3 @@
#[cfg(feature = "palette")]
use palette::rgb::{Srgb, Srgba};
/// A color in the `sRGB` color space.
@ -151,6 +150,14 @@ impl Color {
pub fn inverse(self) -> Color {
Color::new(1.0f32 - self.r, 1.0f32 - self.g, 1.0f32 - self.b, self.a)
}
/// Scales the alpha channel of the [`Color`] by the given factor.
pub fn scale_alpha(self, factor: f32) -> Color {
Self {
a: self.a * factor,
..self
}
}
}
impl From<[f32; 3]> for Color {
@ -202,7 +209,6 @@ macro_rules! color {
}};
}
#[cfg(feature = "palette")]
/// Converts from palette's `Rgba` type to a [`Color`].
impl From<Srgba> for Color {
fn from(rgba: Srgba) -> Self {
@ -210,7 +216,6 @@ impl From<Srgba> for Color {
}
}
#[cfg(feature = "palette")]
/// Converts from [`Color`] to palette's `Rgba` type.
impl From<Color> for Srgba {
fn from(c: Color) -> Self {
@ -218,7 +223,6 @@ impl From<Color> for Srgba {
}
}
#[cfg(feature = "palette")]
/// Converts from palette's `Rgb` type to a [`Color`].
impl From<Srgb> for Color {
fn from(rgb: Srgb) -> Self {
@ -226,7 +230,6 @@ impl From<Srgb> for Color {
}
}
#[cfg(feature = "palette")]
/// Converts from [`Color`] to palette's `Rgb` type.
impl From<Color> for Srgb {
fn from(c: Color) -> Self {
@ -234,7 +237,6 @@ impl From<Color> for Srgb {
}
}
#[cfg(feature = "palette")]
#[cfg(test)]
mod tests {
use super::*;

View file

@ -12,18 +12,14 @@ pub enum Gradient {
}
impl Gradient {
/// Adjust the opacity of the gradient by a multiplier applied to each color stop.
pub fn mul_alpha(mut self, alpha_multiplier: f32) -> Self {
match &mut self {
/// Scales the alpha channel of the [`Gradient`] by the given factor.
pub fn scale_alpha(self, factor: f32) -> Self {
match self {
Gradient::Linear(linear) => {
for stop in linear.stops.iter_mut().flatten() {
stop.color.a *= alpha_multiplier;
Gradient::Linear(linear.scale_alpha(factor))
}
}
}
self
}
}
impl From<Linear> for Gradient {
@ -100,4 +96,14 @@ impl Linear {
self
}
/// Scales the alpha channel of the [`Linear`] gradient by the given
/// factor.
pub fn scale_alpha(mut self, factor: f32) -> Self {
for stop in self.stops.iter_mut().flatten() {
stop.color.a *= factor;
}
self
}
}

View file

@ -30,6 +30,7 @@ pub mod overlay;
pub mod renderer;
pub mod svg;
pub mod text;
pub mod theme;
pub mod time;
pub mod touch;
pub mod widget;
@ -76,6 +77,7 @@ pub use shadow::Shadow;
pub use shell::Shell;
pub use size::Size;
pub use text::Text;
pub use theme::Theme;
pub use transformation::Transformation;
pub use vector::Vector;
pub use widget::Widget;

221
core/src/theme.rs Normal file
View file

@ -0,0 +1,221 @@
//! Use the built-in theme and styles.
pub mod palette;
pub use palette::Palette;
use std::fmt;
use std::sync::Arc;
/// A built-in theme.
#[derive(Debug, Clone, PartialEq, Default)]
pub enum Theme {
/// The built-in light variant.
#[default]
Light,
/// The built-in dark variant.
Dark,
/// The built-in Dracula variant.
Dracula,
/// The built-in Nord variant.
Nord,
/// The built-in Solarized Light variant.
SolarizedLight,
/// The built-in Solarized Dark variant.
SolarizedDark,
/// The built-in Gruvbox Light variant.
GruvboxLight,
/// The built-in Gruvbox Dark variant.
GruvboxDark,
/// The built-in Catppuccin Latte variant.
CatppuccinLatte,
/// The built-in Catppuccin Frappé variant.
CatppuccinFrappe,
/// The built-in Catppuccin Macchiato variant.
CatppuccinMacchiato,
/// The built-in Catppuccin Mocha variant.
CatppuccinMocha,
/// The built-in Tokyo Night variant.
TokyoNight,
/// The built-in Tokyo Night Storm variant.
TokyoNightStorm,
/// The built-in Tokyo Night Light variant.
TokyoNightLight,
/// The built-in Kanagawa Wave variant.
KanagawaWave,
/// The built-in Kanagawa Dragon variant.
KanagawaDragon,
/// The built-in Kanagawa Lotus variant.
KanagawaLotus,
/// The built-in Moonfly variant.
Moonfly,
/// The built-in Nightfly variant.
Nightfly,
/// The built-in Oxocarbon variant.
Oxocarbon,
/// A [`Theme`] that uses a [`Custom`] palette.
Custom(Arc<Custom>),
}
impl Theme {
/// A list with all the defined themes.
pub const ALL: &'static [Self] = &[
Self::Light,
Self::Dark,
Self::Dracula,
Self::Nord,
Self::SolarizedLight,
Self::SolarizedDark,
Self::GruvboxLight,
Self::GruvboxDark,
Self::CatppuccinLatte,
Self::CatppuccinFrappe,
Self::CatppuccinMacchiato,
Self::CatppuccinMocha,
Self::TokyoNight,
Self::TokyoNightStorm,
Self::TokyoNightLight,
Self::KanagawaWave,
Self::KanagawaDragon,
Self::KanagawaLotus,
Self::Moonfly,
Self::Nightfly,
Self::Oxocarbon,
];
/// Creates a new custom [`Theme`] from the given [`Palette`].
pub fn custom(name: String, palette: Palette) -> Self {
Self::custom_with_fn(name, palette, palette::Extended::generate)
}
/// Creates a new custom [`Theme`] from the given [`Palette`], with
/// a custom generator of a [`palette::Extended`].
pub fn custom_with_fn(
name: String,
palette: Palette,
generate: impl FnOnce(Palette) -> palette::Extended,
) -> Self {
Self::Custom(Arc::new(Custom::with_fn(name, palette, generate)))
}
/// Returns the [`Palette`] of the [`Theme`].
pub fn palette(&self) -> Palette {
match self {
Self::Light => Palette::LIGHT,
Self::Dark => Palette::DARK,
Self::Dracula => Palette::DRACULA,
Self::Nord => Palette::NORD,
Self::SolarizedLight => Palette::SOLARIZED_LIGHT,
Self::SolarizedDark => Palette::SOLARIZED_DARK,
Self::GruvboxLight => Palette::GRUVBOX_LIGHT,
Self::GruvboxDark => Palette::GRUVBOX_DARK,
Self::CatppuccinLatte => Palette::CATPPUCCIN_LATTE,
Self::CatppuccinFrappe => Palette::CATPPUCCIN_FRAPPE,
Self::CatppuccinMacchiato => Palette::CATPPUCCIN_MACCHIATO,
Self::CatppuccinMocha => Palette::CATPPUCCIN_MOCHA,
Self::TokyoNight => Palette::TOKYO_NIGHT,
Self::TokyoNightStorm => Palette::TOKYO_NIGHT_STORM,
Self::TokyoNightLight => Palette::TOKYO_NIGHT_LIGHT,
Self::KanagawaWave => Palette::KANAGAWA_WAVE,
Self::KanagawaDragon => Palette::KANAGAWA_DRAGON,
Self::KanagawaLotus => Palette::KANAGAWA_LOTUS,
Self::Moonfly => Palette::MOONFLY,
Self::Nightfly => Palette::NIGHTFLY,
Self::Oxocarbon => Palette::OXOCARBON,
Self::Custom(custom) => custom.palette,
}
}
/// Returns the [`palette::Extended`] of the [`Theme`].
pub fn extended_palette(&self) -> &palette::Extended {
match self {
Self::Light => &palette::EXTENDED_LIGHT,
Self::Dark => &palette::EXTENDED_DARK,
Self::Dracula => &palette::EXTENDED_DRACULA,
Self::Nord => &palette::EXTENDED_NORD,
Self::SolarizedLight => &palette::EXTENDED_SOLARIZED_LIGHT,
Self::SolarizedDark => &palette::EXTENDED_SOLARIZED_DARK,
Self::GruvboxLight => &palette::EXTENDED_GRUVBOX_LIGHT,
Self::GruvboxDark => &palette::EXTENDED_GRUVBOX_DARK,
Self::CatppuccinLatte => &palette::EXTENDED_CATPPUCCIN_LATTE,
Self::CatppuccinFrappe => &palette::EXTENDED_CATPPUCCIN_FRAPPE,
Self::CatppuccinMacchiato => {
&palette::EXTENDED_CATPPUCCIN_MACCHIATO
}
Self::CatppuccinMocha => &palette::EXTENDED_CATPPUCCIN_MOCHA,
Self::TokyoNight => &palette::EXTENDED_TOKYO_NIGHT,
Self::TokyoNightStorm => &palette::EXTENDED_TOKYO_NIGHT_STORM,
Self::TokyoNightLight => &palette::EXTENDED_TOKYO_NIGHT_LIGHT,
Self::KanagawaWave => &palette::EXTENDED_KANAGAWA_WAVE,
Self::KanagawaDragon => &palette::EXTENDED_KANAGAWA_DRAGON,
Self::KanagawaLotus => &palette::EXTENDED_KANAGAWA_LOTUS,
Self::Moonfly => &palette::EXTENDED_MOONFLY,
Self::Nightfly => &palette::EXTENDED_NIGHTFLY,
Self::Oxocarbon => &palette::EXTENDED_OXOCARBON,
Self::Custom(custom) => &custom.extended,
}
}
}
impl fmt::Display for Theme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Light => write!(f, "Light"),
Self::Dark => write!(f, "Dark"),
Self::Dracula => write!(f, "Dracula"),
Self::Nord => write!(f, "Nord"),
Self::SolarizedLight => write!(f, "Solarized Light"),
Self::SolarizedDark => write!(f, "Solarized Dark"),
Self::GruvboxLight => write!(f, "Gruvbox Light"),
Self::GruvboxDark => write!(f, "Gruvbox Dark"),
Self::CatppuccinLatte => write!(f, "Catppuccin Latte"),
Self::CatppuccinFrappe => write!(f, "Catppuccin Frappé"),
Self::CatppuccinMacchiato => write!(f, "Catppuccin Macchiato"),
Self::CatppuccinMocha => write!(f, "Catppuccin Mocha"),
Self::TokyoNight => write!(f, "Tokyo Night"),
Self::TokyoNightStorm => write!(f, "Tokyo Night Storm"),
Self::TokyoNightLight => write!(f, "Tokyo Night Light"),
Self::KanagawaWave => write!(f, "Kanagawa Wave"),
Self::KanagawaDragon => write!(f, "Kanagawa Dragon"),
Self::KanagawaLotus => write!(f, "Kanagawa Lotus"),
Self::Moonfly => write!(f, "Moonfly"),
Self::Nightfly => write!(f, "Nightfly"),
Self::Oxocarbon => write!(f, "Oxocarbon"),
Self::Custom(custom) => custom.fmt(f),
}
}
}
/// A [`Theme`] with a customized [`Palette`].
#[derive(Debug, Clone, PartialEq)]
pub struct Custom {
name: String,
palette: Palette,
extended: palette::Extended,
}
impl Custom {
/// Creates a [`Custom`] theme from the given [`Palette`].
pub fn new(name: String, palette: Palette) -> Self {
Self::with_fn(name, palette, palette::Extended::generate)
}
/// Creates a [`Custom`] theme from the given [`Palette`] with
/// a custom generator of a [`palette::Extended`].
pub fn with_fn(
name: String,
palette: Palette,
generate: impl FnOnce(Palette) -> palette::Extended,
) -> Self {
Self {
name,
palette,
extended: generate(palette),
}
}
}
impl fmt::Display for Custom {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)
}
}

View file

@ -1,5 +1,5 @@
//! Define the colors of a theme.
use crate::core::{color, Color};
use crate::{color, Color};
use once_cell::sync::Lazy;
use palette::color_difference::Wcag21RelativeContrast;

View file

@ -17,7 +17,6 @@ pub use text::{LineHeight, Shaping};
#[allow(missing_debug_implementations)]
pub struct Text<'a, Theme, Renderer>
where
Theme: StyleSheet,
Renderer: text::Renderer,
{
content: Cow<'a, str>,
@ -29,12 +28,11 @@ where
vertical_alignment: alignment::Vertical,
font: Option<Renderer::Font>,
shaping: Shaping,
style: Theme::Style,
style: Style<Theme>,
}
impl<'a, Theme, Renderer> Text<'a, Theme, Renderer>
where
Theme: StyleSheet,
Renderer: text::Renderer,
{
/// Create a new fragment of [`Text`] with the given contents.
@ -49,7 +47,7 @@ where
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: Shaping::Basic,
style: Default::default(),
style: Style::default(),
}
}
@ -74,8 +72,20 @@ where
}
/// Sets the style of the [`Text`].
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
self.style = style.into();
pub fn style(mut self, style: fn(&Theme) -> Appearance) -> Self {
self.style = Style::Themed(style);
self
}
/// Sets the [`Color`] of the [`Text`].
pub fn color(mut self, color: impl Into<Color>) -> Self {
self.style = Style::Colored(Some(color.into()));
self
}
/// Sets the [`Color`] of the [`Text`], if `Some`.
pub fn color_maybe(mut self, color: Option<impl Into<Color>>) -> Self {
self.style = Style::Colored(color.map(Into::into));
self
}
@ -123,7 +133,6 @@ pub struct State<P: Paragraph>(P);
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Text<'a, Theme, Renderer>
where
Theme: StyleSheet,
Renderer: text::Renderer,
{
fn tag(&self) -> tree::Tag {
@ -175,14 +184,12 @@ where
) {
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
draw(
renderer,
style,
layout,
state,
theme.appearance(self.style.clone()),
viewport,
);
let appearance = match self.style {
Style::Themed(f) => f(theme),
Style::Colored(color) => Appearance { color },
};
draw(renderer, style, layout, state, appearance, viewport);
}
}
@ -273,7 +280,7 @@ pub fn draw<Renderer>(
impl<'a, Message, Theme, Renderer> From<Text<'a, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Theme: StyleSheet + 'a,
Theme: 'a,
Renderer: text::Renderer + 'a,
{
fn from(
@ -285,7 +292,6 @@ where
impl<'a, Theme, Renderer> Clone for Text<'a, Theme, Renderer>
where
Theme: StyleSheet,
Renderer: text::Renderer,
{
fn clone(&self) -> Self {
@ -298,7 +304,7 @@ where
horizontal_alignment: self.horizontal_alignment,
vertical_alignment: self.vertical_alignment,
font: self.font,
style: self.style.clone(),
style: self.style,
shaping: self.shaping,
}
}
@ -306,7 +312,6 @@ where
impl<'a, Theme, Renderer> From<&'a str> for Text<'a, Theme, Renderer>
where
Theme: StyleSheet,
Renderer: text::Renderer,
{
fn from(content: &'a str) -> Self {
@ -317,7 +322,7 @@ where
impl<'a, Message, Theme, Renderer> From<&'a str>
for Element<'a, Message, Theme, Renderer>
where
Theme: StyleSheet + 'a,
Theme: 'a,
Renderer: text::Renderer + 'a,
{
fn from(content: &'a str) -> Self {
@ -325,15 +330,6 @@ where
}
}
/// The style sheet of some text.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default + Clone;
/// Produces the [`Appearance`] of some text.
fn appearance(&self, style: Self::Style) -> Appearance;
}
/// The apperance of some text.
#[derive(Debug, Clone, Copy, Default)]
pub struct Appearance {
@ -342,3 +338,29 @@ pub struct Appearance {
/// The default, `None`, means using the inherited color.
pub color: Option<Color>,
}
#[derive(Debug)]
enum Style<Theme> {
Themed(fn(&Theme) -> Appearance),
Colored(Option<Color>),
}
impl<Theme> Clone for Style<Theme> {
fn clone(&self) -> Self {
*self
}
}
impl<Theme> Copy for Style<Theme> {}
impl<Theme> Default for Style<Theme> {
fn default() -> Self {
Style::Colored(None)
}
}
impl<Theme> From<fn(&Theme) -> Appearance> for Style<Theme> {
fn from(f: fn(&Theme) -> Appearance) -> Self {
Style::Themed(f)
}
}

View file

@ -49,7 +49,9 @@ impl Sandbox for Example {
column![
text("Bezier tool example").width(Length::Shrink).size(50),
self.bezier.view(&self.curves).map(Message::AddCurve),
button("Clear").padding(8).on_press(Message::Clear),
button("Clear")
.style(button::danger)
.on_press(Message::Clear),
]
.padding(20)
.spacing(20)

View file

@ -1,6 +1,5 @@
use iced::executor;
use iced::font::{self, Font};
use iced::theme;
use iced::widget::{checkbox, column, container, row, text};
use iced::{Application, Command, Element, Length, Settings, Theme};
@ -71,10 +70,10 @@ impl Application for Example {
};
let checkboxes = row![
styled_checkbox("Primary", theme::Checkbox::Primary),
styled_checkbox("Secondary", theme::Checkbox::Secondary),
styled_checkbox("Success", theme::Checkbox::Success),
styled_checkbox("Danger", theme::Checkbox::Danger),
styled_checkbox("Primary", checkbox::primary),
styled_checkbox("Secondary", checkbox::secondary),
styled_checkbox("Success", checkbox::success),
styled_checkbox("Danger", checkbox::danger),
]
.spacing(20);

View file

@ -3,7 +3,7 @@ use iced::mouse;
use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke};
use iced::widget::{canvas, container};
use iced::{
Application, Color, Command, Element, Length, Point, Rectangle, Renderer,
Application, Command, Element, Length, Point, Rectangle, Renderer,
Settings, Subscription, Theme, Vector,
};
@ -80,6 +80,10 @@ impl Application for Clock {
)
})
}
fn theme(&self) -> Theme {
Theme::TokyoNight
}
}
impl<Message> canvas::Program<Message> for Clock {
@ -89,16 +93,18 @@ impl<Message> canvas::Program<Message> for Clock {
&self,
_state: &Self::State,
renderer: &Renderer,
_theme: &Theme,
theme: &Theme,
bounds: Rectangle,
_cursor: mouse::Cursor,
) -> Vec<Geometry> {
let clock = self.clock.draw(renderer, bounds.size(), |frame| {
let palette = theme.extended_palette();
let center = frame.center();
let radius = frame.width().min(frame.height()) / 2.0;
let background = Path::circle(center, radius);
frame.fill(&background, Color::from_rgb8(0x12, 0x93, 0xD8));
frame.fill(&background, palette.primary.weak.color);
let short_hand =
Path::line(Point::ORIGIN, Point::new(0.0, -0.5 * radius));
@ -111,7 +117,7 @@ impl<Message> canvas::Program<Message> for Clock {
let thin_stroke = || -> Stroke {
Stroke {
width,
style: stroke::Style::Solid(Color::WHITE),
style: stroke::Style::Solid(palette.primary.weak.text),
line_cap: LineCap::Round,
..Stroke::default()
}
@ -120,7 +126,7 @@ impl<Message> canvas::Program<Message> for Clock {
let wide_stroke = || -> Stroke {
Stroke {
width: width * 3.0,
style: stroke::Style::Solid(Color::WHITE),
style: stroke::Style::Solid(palette.primary.weak.text),
line_cap: LineCap::Round,
..Stroke::default()
}

View file

@ -7,6 +7,6 @@ publish = false
[dependencies]
iced.workspace = true
iced.features = ["canvas", "palette"]
iced.features = ["canvas"]
palette.workspace = true

View file

@ -3,7 +3,7 @@ use iced::mouse;
use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path};
use iced::widget::{column, row, text, Slider};
use iced::{
Color, Element, Length, Pixels, Point, Rectangle, Renderer, Sandbox,
Color, Element, Font, Length, Pixels, Point, Rectangle, Renderer, Sandbox,
Settings, Size, Vector,
};
use palette::{
@ -15,6 +15,7 @@ use std::ops::RangeInclusive;
pub fn main() -> iced::Result {
ColorPalette::run(Settings {
antialiasing: true,
default_font: Font::MONOSPACE,
..Settings::default()
})
}
@ -87,6 +88,19 @@ impl Sandbox for ColorPalette {
.spacing(10)
.into()
}
fn theme(&self) -> iced::Theme {
iced::Theme::custom(
String::from("Custom"),
iced::theme::Palette {
background: self.theme.base,
primary: *self.theme.lower.first().unwrap(),
text: *self.theme.higher.last().unwrap(),
success: *self.theme.lower.last().unwrap(),
danger: *self.theme.higher.last().unwrap(),
},
)
}
}
#[derive(Debug)]
@ -150,7 +164,7 @@ impl Theme {
.into()
}
fn draw(&self, frame: &mut Frame) {
fn draw(&self, frame: &mut Frame, text_color: Color) {
let pad = 20.0;
let box_size = Size {
@ -169,6 +183,7 @@ impl Theme {
horizontal_alignment: alignment::Horizontal::Center,
vertical_alignment: alignment::Vertical::Top,
size: Pixels(15.0),
color: text_color,
..canvas::Text::default()
};
@ -246,12 +261,14 @@ impl<Message> canvas::Program<Message> for Theme {
&self,
_state: &Self::State,
renderer: &Renderer,
_theme: &iced::Theme,
theme: &iced::Theme,
bounds: Rectangle,
_cursor: mouse::Cursor,
) -> Vec<Geometry> {
let theme = self.canvas_cache.draw(renderer, bounds.size(), |frame| {
self.draw(frame);
let palette = theme.extended_palette();
self.draw(frame, palette.background.base.text);
});
vec![theme]
@ -308,7 +325,7 @@ impl<C: ColorSpace + Copy> ColorPicker<C> {
slider(cr1, c1, move |v| C::new(v, c2, c3)),
slider(cr2, c2, move |v| C::new(c1, v, c3)),
slider(cr3, c3, move |v| C::new(c1, c2, v)),
text(color.to_string()).width(185).size(14),
text(color.to_string()).width(185).size(12),
]
.spacing(10)
.align_items(Alignment::Center)

View file

@ -62,7 +62,7 @@ mod circle {
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
border: Border::with_radius(self.radius),
border: Border::rounded(self.radius),
..renderer::Quad::default()
},
Color::BLACK,

View file

@ -1,14 +1,13 @@
use iced::executor;
use iced::highlighter::{self, Highlighter};
use iced::keyboard;
use iced::theme::{self, Theme};
use iced::widget::{
button, column, container, horizontal_space, pick_list, row, text,
text_editor, tooltip,
};
use iced::{
Alignment, Application, Command, Element, Font, Length, Settings,
Subscription,
Subscription, Theme,
};
use std::ffi;
@ -287,10 +286,10 @@ fn action<'a, Message: Clone + 'a>(
label,
tooltip::Position::FollowCursor,
)
.style(theme::Container::Box)
.style(container::box_)
.into()
} else {
action.style(theme::Button::Secondary).into()
action.style(button::secondary).into()
}
}

View file

@ -6,7 +6,6 @@ use grid::Grid;
use preset::Preset;
use iced::executor;
use iced::theme::{self, Theme};
use iced::time;
use iced::widget::{
button, checkbox, column, container, pick_list, row, slider, text,
@ -14,6 +13,7 @@ use iced::widget::{
use iced::window;
use iced::{
Alignment, Application, Command, Element, Length, Settings, Subscription,
Theme,
};
use std::time::Duration;
@ -171,7 +171,7 @@ fn view_controls<'a>(
.on_press(Message::TogglePlayback),
button("Next")
.on_press(Message::Next)
.style(theme::Button::Secondary),
.style(button::secondary),
]
.spacing(10);
@ -185,17 +185,14 @@ fn view_controls<'a>(
row![
playback_controls,
speed_controls,
checkbox("Grid", is_grid_enabled)
.on_toggle(Message::ToggleGrid)
.size(16)
.spacing(5)
.text_size(16),
pick_list(preset::ALL, Some(preset), Message::PresetPicked)
.padding(8)
.text_size(16),
checkbox("Grid", is_grid_enabled).on_toggle(Message::ToggleGrid),
row![
pick_list(preset::ALL, Some(preset), Message::PresetPicked),
button("Clear")
.on_press(Message::Clear)
.style(theme::Button::Destructive),
.style(button::danger)
]
.spacing(10)
]
.padding(10)
.spacing(20)

View file

@ -1,11 +1,10 @@
use iced::application;
use iced::theme::{self, Theme};
use iced::widget::{
checkbox, column, container, horizontal_space, row, slider, text,
checkbox, column, container, horizontal_space, row, slider, text, themer,
};
use iced::{gradient, window};
use iced::{
Alignment, Background, Color, Element, Length, Radians, Sandbox, Settings,
Alignment, Color, Element, Length, Radians, Sandbox, Settings, Theme,
};
pub fn main() -> iced::Result {
@ -71,20 +70,16 @@ impl Sandbox for Gradient {
transparent,
} = *self;
let gradient_box = container(horizontal_space())
.width(Length::Fill)
.height(Length::Fill)
.style(move |_: &_| {
let gradient = gradient::Linear::new(angle)
.add_stop(0.0, start)
.add_stop(1.0, end)
.into();
.add_stop(1.0, end);
container::Appearance {
background: Some(Background::Gradient(gradient)),
..Default::default()
}
});
let gradient_box = themer(
gradient,
container(horizontal_space())
.width(Length::Fill)
.height(Length::Fill),
);
let angle_picker = row![
text("Angle").width(64),
@ -111,16 +106,14 @@ impl Sandbox for Gradient {
.into()
}
fn style(&self) -> theme::Application {
fn style(&self, theme: &Theme) -> application::Appearance {
if self.transparent {
theme::Application::custom(|theme: &Theme| {
application::Appearance {
background_color: Color::TRANSPARENT,
text_color: theme.palette().text,
}
})
} else {
theme::Application::Default
application::default(theme)
}
}
}

View file

@ -1,25 +1,25 @@
use iced_wgpu::Renderer;
use iced_widget::{slider, text_input, Column, Row, Text};
use iced_winit::core::{Alignment, Color, Element, Length};
use iced_widget::{column, container, row, slider, text, text_input};
use iced_winit::core::alignment;
use iced_winit::core::{Color, Element, Length, Theme};
use iced_winit::runtime::{Command, Program};
use iced_winit::style::Theme;
pub struct Controls {
background_color: Color,
text: String,
input: String,
}
#[derive(Debug, Clone)]
pub enum Message {
BackgroundColorChanged(Color),
TextChanged(String),
InputChanged(String),
}
impl Controls {
pub fn new() -> Controls {
Controls {
background_color: Color::BLACK,
text: String::default(),
input: String::default(),
}
}
@ -38,8 +38,8 @@ impl Program for Controls {
Message::BackgroundColorChanged(color) => {
self.background_color = color;
}
Message::TextChanged(text) => {
self.text = text;
Message::InputChanged(input) => {
self.input = input;
}
}
@ -48,12 +48,8 @@ impl Program for Controls {
fn view(&self) -> Element<Message, Theme, Renderer> {
let background_color = self.background_color;
let text = &self.text;
let sliders = Row::new()
.width(500)
.spacing(20)
.push(
let sliders = row![
slider(0.0..=1.0, background_color.r, move |r| {
Message::BackgroundColorChanged(Color {
r,
@ -61,8 +57,6 @@ impl Program for Controls {
})
})
.step(0.01),
)
.push(
slider(0.0..=1.0, background_color.g, move |g| {
Message::BackgroundColorChanged(Color {
g,
@ -70,8 +64,6 @@ impl Program for Controls {
})
})
.step(0.01),
)
.push(
slider(0.0..=1.0, background_color.b, move |b| {
Message::BackgroundColorChanged(Color {
b,
@ -79,29 +71,25 @@ impl Program for Controls {
})
})
.step(0.01),
);
]
.width(500)
.spacing(20);
Row::new()
.height(Length::Fill)
.align_items(Alignment::End)
.push(
Column::new().align_items(Alignment::End).push(
Column::new()
.padding(10)
.spacing(10)
.push(Text::new("Background color").style(Color::WHITE))
.push(sliders)
.push(
Text::new(format!("{background_color:?}"))
container(
column![
text("Background color").color(Color::WHITE),
text(format!("{background_color:?}"))
.size(14)
.style(Color::WHITE),
)
.push(
text_input("Placeholder", text)
.on_input(Message::TextChanged),
),
),
.color(Color::WHITE),
text_input("Placeholder", &self.input)
.on_input(Message::InputChanged),
sliders,
]
.spacing(10),
)
.padding(10)
.height(Length::Fill)
.align_y(alignment::Vertical::Bottom)
.into()
}
}

View file

@ -10,11 +10,10 @@ use iced_winit::conversion;
use iced_winit::core::mouse;
use iced_winit::core::renderer;
use iced_winit::core::window;
use iced_winit::core::{Color, Font, Pixels, Size};
use iced_winit::core::{Color, Font, Pixels, Size, Theme};
use iced_winit::futures;
use iced_winit::runtime::program;
use iced_winit::runtime::Debug;
use iced_winit::style::Theme;
use iced_winit::winit;
use iced_winit::Clipboard;

View file

@ -1,7 +1,6 @@
use iced::executor;
use iced::keyboard;
use iced::mouse;
use iced::theme;
use iced::widget::{
button, canvas, checkbox, column, container, horizontal_space, pick_list,
row, scrollable, text,
@ -98,7 +97,7 @@ impl Application for Layout {
} else {
self.example.view()
})
.style(|theme: &Theme| {
.style(|theme, _status| {
let palette = theme.extended_palette();
container::Appearance::default()
@ -262,7 +261,7 @@ fn application<'a>() -> Element<'a, Message> {
.padding(10)
.align_items(Alignment::Center),
)
.style(|theme: &Theme| {
.style(|theme, _status| {
let palette = theme.extended_palette();
container::Appearance::default()
@ -276,7 +275,7 @@ fn application<'a>() -> Element<'a, Message> {
.width(200)
.align_items(Alignment::Center),
)
.style(theme::Container::Box)
.style(container::box_)
.height(Length::Fill)
.center_y();

View file

@ -1,4 +1,3 @@
use iced::theme;
use iced::widget::{
button, column, horizontal_space, lazy, pick_list, row, scrollable, text,
text_input,
@ -181,11 +180,10 @@ impl Sandbox for App {
column(items.into_iter().map(|item| {
let button = button("Delete")
.on_press(Message::DeleteItem(item.clone()))
.style(theme::Button::Destructive);
.style(button::danger);
row![
text(&item.name)
.style(theme::Text::Color(item.color.into())),
text(&item.name).color(item.color),
horizontal_space(),
pick_list(Color::ALL, Some(item.color), move |color| {
Message::ItemColorChanged(item.clone(), color)

View file

@ -2,7 +2,6 @@ use iced::event::{self, Event};
use iced::executor;
use iced::keyboard;
use iced::keyboard::key;
use iced::theme;
use iced::widget::{
self, button, column, container, horizontal_space, pick_list, row, text,
text_input,
@ -175,7 +174,7 @@ impl Application for App {
)
.width(300)
.padding(10)
.style(theme::Container::Box);
.style(container::box_);
Modal::new(content, modal)
.on_blur(Message::HideModal)

View file

@ -1,13 +1,13 @@
use iced::alignment::{self, Alignment};
use iced::executor;
use iced::keyboard;
use iced::theme::{self, Theme};
use iced::widget::pane_grid::{self, PaneGrid};
use iced::widget::{
button, column, container, responsive, row, scrollable, text,
};
use iced::{
Application, Color, Command, Element, Length, Settings, Size, Subscription,
Theme,
};
pub fn main() -> iced::Result {
@ -162,7 +162,7 @@ impl Application for Example {
let title = row![
pin_button,
"Pane",
text(pane.id.to_string()).style(if is_focused {
text(pane.id.to_string()).color(if is_focused {
PANE_ID_COLOR_FOCUSED
} else {
PANE_ID_COLOR_UNFOCUSED
@ -287,10 +287,7 @@ fn view_content<'a>(
)
]
.push_maybe(if total_panes > 1 && !is_pinned {
Some(
button("Close", Message::Close(pane))
.style(theme::Button::Destructive),
)
Some(button("Close", Message::Close(pane)).style(button::danger))
} else {
None
})
@ -327,7 +324,7 @@ fn view_controls<'a>(
Some(
button(text(content).size(14))
.style(theme::Button::Secondary)
.style(button::secondary)
.padding(3)
.on_press(message),
)
@ -336,7 +333,7 @@ fn view_controls<'a>(
});
let close = button(text("Close").size(14))
.style(theme::Button::Destructive)
.style(button::danger)
.padding(3)
.on_press_maybe(if total_panes > 1 && !is_pinned {
Some(Message::Close(pane))
@ -351,7 +348,10 @@ mod style {
use iced::widget::container;
use iced::{Border, Theme};
pub fn title_bar_active(theme: &Theme) -> container::Appearance {
pub fn title_bar_active(
theme: &Theme,
_status: container::Status,
) -> container::Appearance {
let palette = theme.extended_palette();
container::Appearance {
@ -361,7 +361,10 @@ mod style {
}
}
pub fn title_bar_focused(theme: &Theme) -> container::Appearance {
pub fn title_bar_focused(
theme: &Theme,
_status: container::Status,
) -> container::Appearance {
let palette = theme.extended_palette();
container::Appearance {
@ -371,7 +374,10 @@ mod style {
}
}
pub fn pane_active(theme: &Theme) -> container::Appearance {
pub fn pane_active(
theme: &Theme,
_status: container::Status,
) -> container::Appearance {
let palette = theme.extended_palette();
container::Appearance {
@ -385,7 +391,10 @@ mod style {
}
}
pub fn pane_focused(theme: &Theme) -> container::Appearance {
pub fn pane_focused(
theme: &Theme,
_status: container::Status,
) -> container::Appearance {
let palette = theme.extended_palette();
container::Appearance {

View file

@ -1,8 +1,6 @@
use iced::futures;
use iced::widget::{self, column, container, image, row, text};
use iced::{
Alignment, Application, Color, Command, Element, Length, Settings, Theme,
};
use iced::{Alignment, Application, Command, Element, Length, Settings, Theme};
pub fn main() -> iced::Result {
Pokedex::run(Settings::default())
@ -116,7 +114,7 @@ impl Pokemon {
text(&self.name).size(30).width(Length::Fill),
text(format!("#{}", self.number))
.size(20)
.style(Color::from([0.5, 0.5, 0.5])),
.color([0.5, 0.5, 0.5]),
]
.align_items(Alignment::Center)
.spacing(20),

View file

@ -1,7 +1,6 @@
use iced::alignment;
use iced::executor;
use iced::keyboard;
use iced::theme;
use iced::widget::{button, column, container, image, row, text, text_input};
use iced::window;
use iced::window::screenshot::{self, Screenshot};
@ -149,7 +148,7 @@ impl Application for Example {
let image = container(image)
.padding(10)
.style(theme::Container::Box)
.style(container::box_)
.width(Length::FillPortion(2))
.height(Length::Fill)
.center_x()
@ -216,9 +215,9 @@ impl Application for Example {
)
} else {
button(centered_text("Saving..."))
.style(theme::Button::Secondary)
.style(button::secondary)
}
.style(theme::Button::Secondary)
.style(button::secondary)
.padding([10, 20, 10, 20])
.width(Length::Fill)
]
@ -227,7 +226,7 @@ impl Application for Example {
crop_controls,
button(centered_text("Crop"))
.on_press(Message::Crop)
.style(theme::Button::Destructive)
.style(button::danger)
.padding([10, 20, 10, 20])
.width(Length::Fill),
]

View file

@ -5,7 +5,8 @@ use iced::widget::{
scrollable, slider, text, vertical_space, Scrollable,
};
use iced::{
Alignment, Application, Color, Command, Element, Length, Settings, Theme,
Alignment, Application, Border, Color, Command, Element, Length, Settings,
Theme,
};
use once_cell::sync::Lazy;
@ -348,6 +349,6 @@ fn progress_bar_custom_style(theme: &Theme) -> progress_bar::Appearance {
progress_bar::Appearance {
background: theme.extended_palette().background.strong.color.into(),
bar: Color::from_rgb8(250, 85, 134).into(),
border_radius: 0.0.into(),
border: Border::default(),
}
}

View file

@ -9,7 +9,6 @@
use iced::application;
use iced::executor;
use iced::mouse;
use iced::theme::{self, Theme};
use iced::widget::canvas;
use iced::widget::canvas::gradient;
use iced::widget::canvas::stroke::{self, Stroke};
@ -17,7 +16,7 @@ use iced::widget::canvas::Path;
use iced::window;
use iced::{
Application, Color, Command, Element, Length, Point, Rectangle, Renderer,
Settings, Size, Subscription, Vector,
Settings, Size, Subscription, Theme, Vector,
};
use std::time::Instant;
@ -80,17 +79,13 @@ impl Application for SolarSystem {
Theme::Dark
}
fn style(&self) -> theme::Application {
fn dark_background(_theme: &Theme) -> application::Appearance {
fn style(&self, _theme: &Theme) -> application::Appearance {
application::Appearance {
background_color: Color::BLACK,
text_color: Color::WHITE,
}
}
theme::Application::custom(dark_background)
}
fn subscription(&self) -> Subscription<Message> {
window::frames().map(Message::Tick)
}

View file

@ -1,11 +1,11 @@
use iced::alignment;
use iced::executor;
use iced::keyboard;
use iced::theme::{self, Theme};
use iced::time;
use iced::widget::{button, column, container, row, text};
use iced::{
Alignment, Application, Command, Element, Length, Settings, Subscription,
Theme,
};
use std::time::{Duration, Instant};
@ -136,7 +136,7 @@ impl Application for Stopwatch {
};
let reset_button = button("Reset")
.style(theme::Button::Destructive)
.style(button::danger)
.on_press(Message::Reset);
let controls = row![toggle_button, reset_button].spacing(20);

View file

@ -1,4 +1,3 @@
use iced::theme;
use iced::widget::{checkbox, column, container, svg};
use iced::{color, Element, Length, Sandbox, Settings};
@ -43,11 +42,11 @@ impl Sandbox for Tiger {
let svg = svg(handle).width(Length::Fill).height(Length::Fill).style(
if self.apply_color_filter {
theme::Svg::custom_fn(|_theme| svg::Appearance {
|_theme, _status| svg::Appearance {
color: Some(color!(0x0000ff)),
})
}
} else {
theme::Svg::Default
|_theme, _status| svg::Appearance::default()
},
);

View file

@ -209,27 +209,6 @@ mod toast {
&[Self::Primary, Self::Secondary, Self::Success, Self::Danger];
}
impl container::StyleSheet for Status {
type Style = Theme;
fn appearance(&self, theme: &Theme) -> container::Appearance {
let palette = theme.extended_palette();
let pair = match self {
Status::Primary => palette.primary.weak,
Status::Secondary => palette.secondary.weak,
Status::Success => palette.success.weak,
Status::Danger => palette.danger.weak,
};
container::Appearance {
background: Some(pair.color.into()),
text_color: pair.text.into(),
..Default::default()
}
}
}
impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
@ -282,14 +261,17 @@ mod toast {
)
.width(Length::Fill)
.padding(5)
.style(
theme::Container::Custom(Box::new(toast.status))
),
.style(match toast.status {
Status::Primary => primary,
Status::Secondary => secondary,
Status::Success => success,
Status::Danger => danger,
}),
horizontal_rule(1),
container(text(toast.body.as_str()))
.width(Length::Fill)
.padding(5)
.style(theme::Container::Box),
.style(container::box_),
])
.max_width(200)
.into()
@ -676,4 +658,48 @@ mod toast {
Element::new(manager)
}
}
fn styled(pair: theme::palette::Pair) -> container::Appearance {
container::Appearance {
background: Some(pair.color.into()),
text_color: pair.text.into(),
..Default::default()
}
}
fn primary(
theme: &Theme,
_status: container::Status,
) -> container::Appearance {
let palette = theme.extended_palette();
styled(palette.primary.weak)
}
fn secondary(
theme: &Theme,
_status: container::Status,
) -> container::Appearance {
let palette = theme.extended_palette();
styled(palette.secondary.weak)
}
fn success(
theme: &Theme,
_status: container::Status,
) -> container::Appearance {
let palette = theme.extended_palette();
styled(palette.success.weak)
}
fn danger(
theme: &Theme,
_status: container::Status,
) -> container::Appearance {
let palette = theme.extended_palette();
styled(palette.danger.weak)
}
}

View file

@ -1,14 +1,14 @@
use iced::alignment::{self, Alignment};
use iced::font::{self, Font};
use iced::keyboard;
use iced::theme::{self, Theme};
use iced::widget::{
self, button, checkbox, column, container, keyed_column, row, scrollable,
text, text_input, Text,
};
use iced::window;
use iced::{Application, Element};
use iced::{Color, Command, Length, Settings, Size, Subscription};
use iced::{
Application, Command, Element, Length, Settings, Size, Subscription, Theme,
};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
@ -209,7 +209,7 @@ impl Application for Todos {
let title = text("todos")
.width(Length::Fill)
.size(100)
.style(Color::from([0.5, 0.5, 0.5]))
.color([0.5, 0.5, 0.5])
.horizontal_alignment(alignment::Horizontal::Center);
let input = text_input("What needs to be done?", input_value)
@ -355,6 +355,7 @@ impl Task {
let checkbox = checkbox(&self.description, self.completed)
.on_toggle(TaskMessage::Completed)
.width(Length::Fill)
.size(17)
.text_shaping(text::Shaping::Advanced);
row![
@ -362,7 +363,7 @@ impl Task {
button(edit_icon())
.on_press(TaskMessage::Edit)
.padding(10)
.style(theme::Button::Text),
.style(button::text),
]
.spacing(20)
.align_items(Alignment::Center)
@ -385,7 +386,7 @@ impl Task {
)
.on_press(TaskMessage::Delete)
.padding(10)
.style(theme::Button::Destructive)
.style(button::danger)
]
.spacing(20)
.align_items(Alignment::Center)
@ -402,9 +403,9 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
let label = text(label);
let button = button(label).style(if filter == current_filter {
theme::Button::Primary
button::primary
} else {
theme::Button::Text
button::text
});
button.on_press(Message::FilterChanged(filter)).padding(8)
@ -467,7 +468,7 @@ fn empty_message(message: &str) -> Element<'_, Message> {
.width(Length::Fill)
.size(25)
.horizontal_alignment(alignment::Horizontal::Center)
.style(Color::from([0.7, 0.7, 0.7])),
.color([0.7, 0.7, 0.7]),
)
.height(200)
.center_y()

View file

@ -1,4 +1,3 @@
use iced::theme;
use iced::widget::tooltip::Position;
use iced::widget::{button, container, tooltip};
use iced::{Element, Length, Sandbox, Settings};
@ -53,7 +52,7 @@ impl Sandbox for Example {
self.position,
)
.gap(10)
.style(theme::Container::Box);
.style(container::box_);
container(tooltip)
.width(Length::Fill)

View file

@ -1,7 +1,6 @@
use iced::alignment::{self, Alignment};
use iced::theme;
use iced::widget::{
checkbox, column, container, horizontal_space, image, radio, row,
button, checkbox, column, container, horizontal_space, image, radio, row,
scrollable, slider, text, text_input, toggler, vertical_space,
};
use iced::widget::{Button, Column, Container, Slider};
@ -56,18 +55,17 @@ impl Sandbox for Tour {
fn view(&self) -> Element<Message> {
let Tour { steps, .. } = self;
let controls = row![]
let controls =
row![]
.push_maybe(steps.has_previous().then(|| {
button("Back")
padded_button("Back")
.on_press(Message::BackPressed)
.style(theme::Button::Secondary)
.style(button::secondary)
}))
.push(horizontal_space())
.push_maybe(
steps
.can_continue()
.then(|| button("Next").on_press(Message::NextPressed)),
);
.push_maybe(steps.can_continue().then(|| {
padded_button("Next").on_press(Message::NextPressed)
}));
let content: Element<_> = column![
steps.view(self.debug).map(Message::StepMessage),
@ -474,7 +472,7 @@ impl<'a> Step {
let color_section = column![
"And its color:",
text(format!("{color:?}")).style(color),
text(format!("{color:?}")).color(color),
color_sliders,
]
.padding(20)
@ -676,8 +674,8 @@ fn ferris<'a>(
.center_x()
}
fn button<'a, Message: Clone>(label: &str) -> Button<'a, Message> {
iced::widget::button(text(label)).padding([12, 24])
fn padded_button<'a, Message: Clone>(label: &str) -> Button<'a, Message> {
button(text(label)).padding([12, 24])
}
fn color_slider<'a>(

View file

@ -1,14 +1,13 @@
use iced::event::{self, Event};
use iced::executor;
use iced::mouse;
use iced::theme::{self, Theme};
use iced::widget::{
column, container, horizontal_space, row, scrollable, text, vertical_space,
};
use iced::window;
use iced::{
Alignment, Application, Color, Command, Element, Font, Length, Point,
Rectangle, Settings, Subscription,
Rectangle, Settings, Subscription, Theme,
};
pub fn main() -> iced::Result {
@ -82,7 +81,10 @@ impl Application for Example {
row![
text(label),
horizontal_space(),
text(value).font(Font::MONOSPACE).size(14).style(color),
text(value)
.font(Font::MONOSPACE)
.size(14)
.color_maybe(color),
]
.height(40)
.align_items(Alignment::Center)
@ -102,13 +104,12 @@ impl Application for Example {
})
.unwrap_or_default()
{
Color {
Some(Color {
g: 1.0,
..Color::BLACK
}
.into()
})
} else {
theme::Text::Default
None
},
)
};
@ -120,7 +121,7 @@ impl Application for Example {
Some(Point { x, y }) => format!("({x}, {y})"),
None => "unknown".to_string(),
},
theme::Text::Default,
None,
),
view_bounds("Outer container", self.outer_bounds),
view_bounds("Inner container", self.inner_bounds),
@ -131,7 +132,7 @@ impl Application for Example {
container(text("I am the outer container!"))
.id(OUTER_CONTAINER.clone())
.padding(40)
.style(theme::Container::Box),
.style(container::box_),
vertical_space().height(400),
scrollable(
column![
@ -140,7 +141,7 @@ impl Application for Example {
container(text("I am the inner container!"))
.id(INNER_CONTAINER.clone())
.padding(40)
.style(theme::Container::Box),
.style(container::box_),
vertical_space().height(400),
]
.padding(20)

View file

@ -6,7 +6,7 @@ use iced::widget::{
button, column, container, row, scrollable, text, text_input,
};
use iced::{
Application, Color, Command, Element, Length, Settings, Subscription, Theme,
color, Application, Command, Element, Length, Settings, Subscription, Theme,
};
use once_cell::sync::Lazy;
@ -99,7 +99,7 @@ impl Application for WebSocket {
let message_log: Element<_> = if self.messages.is_empty() {
container(
text("Your messages will appear here...")
.style(Color::from_rgb8(0x88, 0x88, 0x88)),
.color(color!(0x888888)),
)
.width(Length::Fill)
.height(Length::Fill)

View file

@ -1,7 +1,9 @@
//! Build interactive cross-platform applications.
use crate::{Command, Element, Executor, Settings, Subscription};
pub use crate::style::application::{Appearance, StyleSheet};
use crate::shell::application;
pub use application::{default, Appearance, DefaultStyle};
/// An interactive cross-platform application.
///
@ -91,7 +93,10 @@ pub use crate::style::application::{Appearance, StyleSheet};
/// }
/// }
/// ```
pub trait Application: Sized {
pub trait Application: Sized
where
Self::Theme: DefaultStyle,
{
/// The [`Executor`] that will run commands and subscriptions.
///
/// The [default executor] can be a good starting point!
@ -104,7 +109,7 @@ pub trait Application: Sized {
type Message: std::fmt::Debug + Send;
/// The theme of your [`Application`].
type Theme: Default + StyleSheet;
type Theme: Default;
/// The data needed to initialize your [`Application`].
type Flags;
@ -148,11 +153,9 @@ pub trait Application: Sized {
Self::Theme::default()
}
/// Returns the current `Style` of the [`Theme`].
///
/// [`Theme`]: Self::Theme
fn style(&self) -> <Self::Theme as StyleSheet>::Style {
<Self::Theme as StyleSheet>::Style::default()
/// Returns the current [`Appearance`] of the [`Application`].
fn style(&self, theme: &Self::Theme) -> Appearance {
theme.default_style()
}
/// Returns the event [`Subscription`] for the current state of the
@ -228,11 +231,15 @@ pub trait Application: Sized {
}
}
struct Instance<A: Application>(A);
struct Instance<A>(A)
where
A: Application,
A::Theme: DefaultStyle;
impl<A> crate::runtime::Program for Instance<A>
where
A: Application,
A::Theme: DefaultStyle,
{
type Message = A::Message;
type Theme = A::Theme;
@ -247,9 +254,10 @@ where
}
}
impl<A> crate::shell::Application for Instance<A>
impl<A> application::Application for Instance<A>
where
A: Application,
A::Theme: DefaultStyle,
{
type Flags = A::Flags;
@ -267,8 +275,8 @@ where
self.0.theme()
}
fn style(&self) -> <A::Theme as StyleSheet>::Style {
self.0.style()
fn style(&self, theme: &A::Theme) -> Appearance {
self.0.style(theme)
}
fn subscription(&self) -> Subscription<Self::Message> {

View file

@ -162,7 +162,6 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
use iced_widget::graphics;
use iced_widget::renderer;
use iced_widget::style;
use iced_winit as shell;
use iced_winit::core;
use iced_winit::runtime;
@ -186,15 +185,14 @@ pub mod advanced;
#[cfg(feature = "multi-window")]
pub mod multi_window;
pub use style::theme;
pub use crate::core::alignment;
pub use crate::core::border;
pub use crate::core::color;
pub use crate::core::gradient;
pub use crate::core::theme;
pub use crate::core::{
Alignment, Background, Border, Color, ContentFit, Degrees, Gradient,
Length, Padding, Pixels, Point, Radians, Rectangle, Shadow, Size,
Length, Padding, Pixels, Point, Radians, Rectangle, Shadow, Size, Theme,
Transformation, Vector,
};
@ -314,7 +312,6 @@ pub use renderer::Renderer;
pub use sandbox::Sandbox;
pub use settings::Settings;
pub use subscription::Subscription;
pub use theme::Theme;
/// A generic widget.
///

View file

@ -1,4 +1,256 @@
//! Leverage multi-window support in your application.
mod application;
use crate::window;
use crate::{Command, Element, Executor, Settings, Subscription};
pub use application::Application;
pub use crate::application::{Appearance, DefaultStyle};
/// An interactive cross-platform multi-window application.
///
/// This trait is the main entrypoint of Iced. Once implemented, you can run
/// your GUI application by simply calling [`run`](#method.run).
///
/// - On native platforms, it will run in its own windows.
/// - On the web, it will take control of the `<title>` and the `<body>` of the
/// document and display only the contents of the `window::Id::MAIN` window.
///
/// An [`Application`] can execute asynchronous actions by returning a
/// [`Command`] in some of its methods. If you do not intend to perform any
/// background work in your program, the [`Sandbox`] trait offers a simplified
/// interface.
///
/// When using an [`Application`] with the `debug` feature enabled, a debug view
/// can be toggled by pressing `F12`.
///
/// # Examples
/// See the `examples/multi-window` example to see this multi-window `Application` trait in action.
///
/// ## A simple "Hello, world!"
///
/// If you just want to get started, here is a simple [`Application`] that
/// says "Hello, world!":
///
/// ```no_run
/// use iced::{executor, window};
/// use iced::{Command, Element, Settings, Theme};
/// use iced::multi_window::{self, Application};
///
/// pub fn main() -> iced::Result {
/// Hello::run(Settings::default())
/// }
///
/// struct Hello;
///
/// impl multi_window::Application for Hello {
/// type Executor = executor::Default;
/// type Flags = ();
/// type Message = ();
/// type Theme = Theme;
///
/// fn new(_flags: ()) -> (Hello, Command<Self::Message>) {
/// (Hello, Command::none())
/// }
///
/// fn title(&self, _window: window::Id) -> String {
/// String::from("A cool application")
/// }
///
/// fn update(&mut self, _message: Self::Message) -> Command<Self::Message> {
/// Command::none()
/// }
///
/// fn view(&self, _window: window::Id) -> Element<Self::Message> {
/// "Hello, world!".into()
/// }
/// }
/// ```
///
/// [`Sandbox`]: crate::Sandbox
pub trait Application: Sized
where
Self::Theme: DefaultStyle,
{
/// The [`Executor`] that will run commands and subscriptions.
///
/// The [default executor] can be a good starting point!
///
/// [`Executor`]: Self::Executor
/// [default executor]: crate::executor::Default
type Executor: Executor;
/// The type of __messages__ your [`Application`] will produce.
type Message: std::fmt::Debug + Send;
/// The theme of your [`Application`].
type Theme: Default;
/// The data needed to initialize your [`Application`].
type Flags;
/// Initializes the [`Application`] with the flags provided to
/// [`run`] as part of the [`Settings`].
///
/// Here is where you should return the initial state of your app.
///
/// Additionally, you can return a [`Command`] if you need to perform some
/// async action in the background on startup. This is useful if you want to
/// load state from a file, perform an initial HTTP request, etc.
///
/// [`run`]: Self::run
fn new(flags: Self::Flags) -> (Self, Command<Self::Message>);
/// Returns the current title of the `window` of the [`Application`].
///
/// This title can be dynamic! The runtime will automatically update the
/// title of your window when necessary.
fn title(&self, window: window::Id) -> String;
/// Handles a __message__ and updates the state of the [`Application`].
///
/// This is where you define your __update logic__. All the __messages__,
/// produced by either user interactions or commands, will be handled by
/// this method.
///
/// Any [`Command`] returned will be executed immediately in the background.
fn update(&mut self, message: Self::Message) -> Command<Self::Message>;
/// Returns the widgets to display in the `window` of the [`Application`].
///
/// These widgets can produce __messages__ based on user interaction.
fn view(
&self,
window: window::Id,
) -> Element<'_, Self::Message, Self::Theme, crate::Renderer>;
/// Returns the current [`Theme`] of the `window` of the [`Application`].
///
/// [`Theme`]: Self::Theme
#[allow(unused_variables)]
fn theme(&self, window: window::Id) -> Self::Theme {
Self::Theme::default()
}
/// Returns the current `Style` of the [`Theme`].
///
/// [`Theme`]: Self::Theme
fn style(&self, theme: &Self::Theme) -> Appearance {
Self::Theme::default_style(theme)
}
/// Returns the event [`Subscription`] for the current state of the
/// application.
///
/// A [`Subscription`] will be kept alive as long as you keep returning it,
/// and the __messages__ produced will be handled by
/// [`update`](#tymethod.update).
///
/// By default, this method returns an empty [`Subscription`].
fn subscription(&self) -> Subscription<Self::Message> {
Subscription::none()
}
/// Returns the scale factor of the `window` of the [`Application`].
///
/// It can be used to dynamically control the size of the UI at runtime
/// (i.e. zooming).
///
/// For instance, a scale factor of `2.0` will make widgets twice as big,
/// while a scale factor of `0.5` will shrink them to half their size.
///
/// By default, it returns `1.0`.
#[allow(unused_variables)]
fn scale_factor(&self, window: window::Id) -> f64 {
1.0
}
/// Runs the multi-window [`Application`].
///
/// On native platforms, this method will take control of the current thread
/// until the [`Application`] exits.
///
/// On the web platform, this method __will NOT return__ unless there is an
/// [`Error`] during startup.
///
/// [`Error`]: crate::Error
fn run(settings: Settings<Self::Flags>) -> crate::Result
where
Self: 'static,
{
#[allow(clippy::needless_update)]
let renderer_settings = crate::renderer::Settings {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
antialiasing: if settings.antialiasing {
Some(crate::graphics::Antialiasing::MSAAx4)
} else {
None
},
..crate::renderer::Settings::default()
};
Ok(crate::shell::multi_window::run::<
Instance<Self>,
Self::Executor,
crate::renderer::Compositor,
>(settings.into(), renderer_settings)?)
}
}
struct Instance<A>(A)
where
A: Application,
A::Theme: DefaultStyle;
impl<A> crate::runtime::multi_window::Program for Instance<A>
where
A: Application,
A::Theme: DefaultStyle,
{
type Message = A::Message;
type Theme = A::Theme;
type Renderer = crate::Renderer;
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
self.0.update(message)
}
fn view(
&self,
window: window::Id,
) -> Element<'_, Self::Message, Self::Theme, Self::Renderer> {
self.0.view(window)
}
}
impl<A> crate::shell::multi_window::Application for Instance<A>
where
A: Application,
A::Theme: DefaultStyle,
{
type Flags = A::Flags;
fn new(flags: Self::Flags) -> (Self, Command<A::Message>) {
let (app, command) = A::new(flags);
(Instance(app), command)
}
fn title(&self, window: window::Id) -> String {
self.0.title(window)
}
fn theme(&self, window: window::Id) -> A::Theme {
self.0.theme(window)
}
fn style(&self, theme: &Self::Theme) -> Appearance {
self.0.style(theme)
}
fn subscription(&self) -> Subscription<Self::Message> {
self.0.subscription()
}
fn scale_factor(&self, window: window::Id) -> f64 {
self.0.scale_factor(window)
}
}

View file

@ -1,246 +0,0 @@
use crate::style::application::StyleSheet;
use crate::window;
use crate::{Command, Element, Executor, Settings, Subscription};
/// An interactive cross-platform multi-window application.
///
/// This trait is the main entrypoint of Iced. Once implemented, you can run
/// your GUI application by simply calling [`run`](#method.run).
///
/// - On native platforms, it will run in its own windows.
/// - On the web, it will take control of the `<title>` and the `<body>` of the
/// document and display only the contents of the `window::Id::MAIN` window.
///
/// An [`Application`] can execute asynchronous actions by returning a
/// [`Command`] in some of its methods. If you do not intend to perform any
/// background work in your program, the [`Sandbox`] trait offers a simplified
/// interface.
///
/// When using an [`Application`] with the `debug` feature enabled, a debug view
/// can be toggled by pressing `F12`.
///
/// # Examples
/// See the `examples/multi-window` example to see this multi-window `Application` trait in action.
///
/// ## A simple "Hello, world!"
///
/// If you just want to get started, here is a simple [`Application`] that
/// says "Hello, world!":
///
/// ```no_run
/// use iced::{executor, window};
/// use iced::{Command, Element, Settings, Theme};
/// use iced::multi_window::{self, Application};
///
/// pub fn main() -> iced::Result {
/// Hello::run(Settings::default())
/// }
///
/// struct Hello;
///
/// impl multi_window::Application for Hello {
/// type Executor = executor::Default;
/// type Flags = ();
/// type Message = ();
/// type Theme = Theme;
///
/// fn new(_flags: ()) -> (Hello, Command<Self::Message>) {
/// (Hello, Command::none())
/// }
///
/// fn title(&self, _window: window::Id) -> String {
/// String::from("A cool application")
/// }
///
/// fn update(&mut self, _message: Self::Message) -> Command<Self::Message> {
/// Command::none()
/// }
///
/// fn view(&self, _window: window::Id) -> Element<Self::Message> {
/// "Hello, world!".into()
/// }
/// }
/// ```
///
/// [`Sandbox`]: crate::Sandbox
pub trait Application: Sized {
/// The [`Executor`] that will run commands and subscriptions.
///
/// The [default executor] can be a good starting point!
///
/// [`Executor`]: Self::Executor
/// [default executor]: crate::executor::Default
type Executor: Executor;
/// The type of __messages__ your [`Application`] will produce.
type Message: std::fmt::Debug + Send;
/// The theme of your [`Application`].
type Theme: Default + StyleSheet;
/// The data needed to initialize your [`Application`].
type Flags;
/// Initializes the [`Application`] with the flags provided to
/// [`run`] as part of the [`Settings`].
///
/// Here is where you should return the initial state of your app.
///
/// Additionally, you can return a [`Command`] if you need to perform some
/// async action in the background on startup. This is useful if you want to
/// load state from a file, perform an initial HTTP request, etc.
///
/// [`run`]: Self::run
fn new(flags: Self::Flags) -> (Self, Command<Self::Message>);
/// Returns the current title of the `window` of the [`Application`].
///
/// This title can be dynamic! The runtime will automatically update the
/// title of your window when necessary.
fn title(&self, window: window::Id) -> String;
/// Handles a __message__ and updates the state of the [`Application`].
///
/// This is where you define your __update logic__. All the __messages__,
/// produced by either user interactions or commands, will be handled by
/// this method.
///
/// Any [`Command`] returned will be executed immediately in the background.
fn update(&mut self, message: Self::Message) -> Command<Self::Message>;
/// Returns the widgets to display in the `window` of the [`Application`].
///
/// These widgets can produce __messages__ based on user interaction.
fn view(
&self,
window: window::Id,
) -> Element<'_, Self::Message, Self::Theme, crate::Renderer>;
/// Returns the current [`Theme`] of the `window` of the [`Application`].
///
/// [`Theme`]: Self::Theme
#[allow(unused_variables)]
fn theme(&self, window: window::Id) -> Self::Theme {
Self::Theme::default()
}
/// Returns the current `Style` of the [`Theme`].
///
/// [`Theme`]: Self::Theme
fn style(&self) -> <Self::Theme as StyleSheet>::Style {
<Self::Theme as StyleSheet>::Style::default()
}
/// Returns the event [`Subscription`] for the current state of the
/// application.
///
/// A [`Subscription`] will be kept alive as long as you keep returning it,
/// and the __messages__ produced will be handled by
/// [`update`](#tymethod.update).
///
/// By default, this method returns an empty [`Subscription`].
fn subscription(&self) -> Subscription<Self::Message> {
Subscription::none()
}
/// Returns the scale factor of the `window` of the [`Application`].
///
/// It can be used to dynamically control the size of the UI at runtime
/// (i.e. zooming).
///
/// For instance, a scale factor of `2.0` will make widgets twice as big,
/// while a scale factor of `0.5` will shrink them to half their size.
///
/// By default, it returns `1.0`.
#[allow(unused_variables)]
fn scale_factor(&self, window: window::Id) -> f64 {
1.0
}
/// Runs the multi-window [`Application`].
///
/// On native platforms, this method will take control of the current thread
/// until the [`Application`] exits.
///
/// On the web platform, this method __will NOT return__ unless there is an
/// [`Error`] during startup.
///
/// [`Error`]: crate::Error
fn run(settings: Settings<Self::Flags>) -> crate::Result
where
Self: 'static,
{
#[allow(clippy::needless_update)]
let renderer_settings = crate::renderer::Settings {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
antialiasing: if settings.antialiasing {
Some(crate::graphics::Antialiasing::MSAAx4)
} else {
None
},
..crate::renderer::Settings::default()
};
Ok(crate::shell::multi_window::run::<
Instance<Self>,
Self::Executor,
crate::renderer::Compositor,
>(settings.into(), renderer_settings)?)
}
}
struct Instance<A: Application>(A);
impl<A> crate::runtime::multi_window::Program for Instance<A>
where
A: Application,
{
type Message = A::Message;
type Theme = A::Theme;
type Renderer = crate::Renderer;
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
self.0.update(message)
}
fn view(
&self,
window: window::Id,
) -> Element<'_, Self::Message, Self::Theme, Self::Renderer> {
self.0.view(window)
}
}
impl<A> crate::shell::multi_window::Application for Instance<A>
where
A: Application,
{
type Flags = A::Flags;
fn new(flags: Self::Flags) -> (Self, Command<A::Message>) {
let (app, command) = A::new(flags);
(Instance(app), command)
}
fn title(&self, window: window::Id) -> String {
self.0.title(window)
}
fn theme(&self, window: window::Id) -> A::Theme {
self.0.theme(window)
}
fn style(&self) -> <A::Theme as StyleSheet>::Style {
self.0.style()
}
fn subscription(&self) -> Subscription<Self::Message> {
self.0.subscription()
}
fn scale_factor(&self, window: window::Id) -> f64 {
self.0.scale_factor(window)
}
}

View file

@ -1,5 +1,5 @@
use crate::theme::{self, Theme};
use crate::{Application, Command, Element, Error, Settings, Subscription};
use crate::application::{self, Application};
use crate::{Command, Element, Error, Settings, Subscription, Theme};
/// A sandboxed [`Application`].
///
@ -120,11 +120,11 @@ pub trait Sandbox {
Theme::default()
}
/// Returns the current style variant of [`theme::Application`].
///
/// By default, it returns [`theme::Application::default`].
fn style(&self) -> theme::Application {
theme::Application::default()
/// Returns the current [`application::Appearance`].
fn style(&self, theme: &Theme) -> application::Appearance {
use application::DefaultStyle;
theme.default_style()
}
/// Returns the scale factor of the [`Sandbox`].
@ -185,8 +185,8 @@ where
T::theme(self)
}
fn style(&self) -> theme::Application {
T::style(self)
fn style(&self, theme: &Theme) -> application::Appearance {
T::style(self, theme)
}
fn subscription(&self) -> Subscription<T::Message> {

View file

@ -1,5 +1,5 @@
//! Listen and react to time.
pub use iced_core::time::{Duration, Instant};
pub use crate::core::time::{Duration, Instant};
#[allow(unused_imports)]
#[cfg_attr(

View file

@ -1,18 +0,0 @@
[package]
name = "iced_style"
description = "The default set of styles of Iced"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
categories.workspace = true
keywords.workspace = true
[dependencies]
iced_core.workspace = true
iced_core.features = ["palette"]
palette.workspace = true
once_cell.workspace = true

View file

@ -1,23 +0,0 @@
//! Change the appearance of an application.
use iced_core::Color;
/// A set of rules that dictate the style of an application.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;
/// Returns the [`Appearance`] of the application for the provided [`Style`].
///
/// [`Style`]: Self::Style
fn appearance(&self, style: &Self::Style) -> Appearance;
}
/// The appearance of an application.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Appearance {
/// The background [`Color`] of the application.
pub background_color: Color,
/// The default text [`Color`] of the application.
pub text_color: Color,
}

View file

@ -1,79 +0,0 @@
//! Change the apperance of a button.
use iced_core::{Background, Border, Color, Shadow, Vector};
/// The appearance of a button.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The amount of offset to apply to the shadow of the button.
pub shadow_offset: Vector,
/// The [`Background`] of the button.
pub background: Option<Background>,
/// The text [`Color`] of the button.
pub text_color: Color,
/// The [`Border`] of the buton.
pub border: Border,
/// The [`Shadow`] of the butoon.
pub shadow: Shadow,
}
impl std::default::Default for Appearance {
fn default() -> Self {
Self {
shadow_offset: Vector::default(),
background: None,
text_color: Color::BLACK,
border: Border::default(),
shadow: Shadow::default(),
}
}
}
/// A set of rules that dictate the style of a button.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;
/// Produces the active [`Appearance`] of a button.
fn active(&self, style: &Self::Style) -> Appearance;
/// Produces the hovered [`Appearance`] of a button.
fn hovered(&self, style: &Self::Style) -> Appearance {
let active = self.active(style);
Appearance {
shadow_offset: active.shadow_offset + Vector::new(0.0, 1.0),
..active
}
}
/// Produces the pressed [`Appearance`] of a button.
fn pressed(&self, style: &Self::Style) -> Appearance {
Appearance {
shadow_offset: Vector::default(),
..self.active(style)
}
}
/// Produces the disabled [`Appearance`] of a button.
fn disabled(&self, style: &Self::Style) -> Appearance {
let active = self.active(style);
Appearance {
shadow_offset: Vector::default(),
background: active.background.map(|background| match background {
Background::Color(color) => Background::Color(Color {
a: color.a * 0.5,
..color
}),
Background::Gradient(gradient) => {
Background::Gradient(gradient.mul_alpha(0.5))
}
}),
text_color: Color {
a: active.text_color.a * 0.5,
..active.text_color
},
..active
}
}
}

View file

@ -1,45 +0,0 @@
//! Change the appearance of a checkbox.
use iced_core::{Background, Border, Color};
/// The appearance of a checkbox.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The [`Background`] of the checkbox.
pub background: Background,
/// The icon [`Color`] of the checkbox.
pub icon_color: Color,
/// The [`Border`] of hte checkbox.
pub border: Border,
/// The text [`Color`] of the checkbox.
pub text_color: Option<Color>,
}
/// A set of rules that dictate the style of a checkbox.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;
/// Produces the active [`Appearance`] of a checkbox.
fn active(&self, style: &Self::Style, is_checked: bool) -> Appearance;
/// Produces the hovered [`Appearance`] of a checkbox.
fn hovered(&self, style: &Self::Style, is_checked: bool) -> Appearance;
/// Produces the disabled [`Appearance`] of a checkbox.
fn disabled(&self, style: &Self::Style, is_checked: bool) -> Appearance {
let active = self.active(style, is_checked);
Appearance {
background: match active.background {
Background::Color(color) => Background::Color(Color {
a: color.a * 0.5,
..color
}),
Background::Gradient(gradient) => {
Background::Gradient(gradient.mul_alpha(0.5))
}
},
..active
}
}
}

View file

@ -1,51 +0,0 @@
//! Change the appearance of a container.
use crate::core::{Background, Border, Color, Pixels, Shadow};
/// The appearance of a container.
#[derive(Debug, Clone, Copy, Default)]
pub struct Appearance {
/// The text [`Color`] of the container.
pub text_color: Option<Color>,
/// The [`Background`] of the container.
pub background: Option<Background>,
/// The [`Border`] of the container.
pub border: Border,
/// The [`Shadow`] of the container.
pub shadow: Shadow,
}
impl Appearance {
/// Derives a new [`Appearance`] with a border of the given [`Color`] and
/// `width`.
pub fn with_border(
self,
color: impl Into<Color>,
width: impl Into<Pixels>,
) -> Self {
Self {
border: Border {
color: color.into(),
width: width.into().0,
..Border::default()
},
..self
}
}
/// Derives a new [`Appearance`] with the given [`Background`].
pub fn with_background(self, background: impl Into<Background>) -> Self {
Self {
background: Some(background.into()),
..self
}
}
}
/// A set of rules that dictate the [`Appearance`] of a container.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;
/// Produces the [`Appearance`] of a container.
fn appearance(&self, style: &Self::Style) -> Appearance;
}

View file

@ -1,38 +0,0 @@
//! The styling library of Iced.
//!
//! It contains a set of styles and stylesheets for most of the built-in
//! widgets.
//!
//! ![The foundations of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/foundations.png?raw=true)
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
)]
#![forbid(unsafe_code, rust_2018_idioms)]
#![deny(
unused_results,
missing_docs,
unused_results,
rustdoc::broken_intra_doc_links
)]
pub use iced_core as core;
pub mod application;
pub mod button;
pub mod checkbox;
pub mod container;
pub mod menu;
pub mod pane_grid;
pub mod pick_list;
pub mod progress_bar;
pub mod qr_code;
pub mod radio;
pub mod rule;
pub mod scrollable;
pub mod slider;
pub mod svg;
pub mod text_editor;
pub mod text_input;
pub mod theme;
pub mod toggler;
pub use theme::Theme;

View file

@ -1,26 +0,0 @@
//! Change the appearance of menus.
use iced_core::{Background, Border, Color};
/// The appearance of a menu.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The text [`Color`] of the menu.
pub text_color: Color,
/// The [`Background`] of the menu.
pub background: Background,
/// The [`Border`] of the menu.
pub border: Border,
/// The text [`Color`] of a selected option in the menu.
pub selected_text_color: Color,
/// The background [`Color`] of a selected option in the menu.
pub selected_background: Background,
}
/// The style sheet of a menu.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default + Clone;
/// Produces the [`Appearance`] of a menu.
fn appearance(&self, style: &Self::Style) -> Appearance;
}

View file

@ -1,38 +0,0 @@
//! Change the appearance of a pane grid.
use iced_core::{Background, Border, Color};
/// The appearance of the hovered region of a pane grid.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The [`Background`] of the pane region.
pub background: Background,
/// The [`Border`] of the pane region.
pub border: Border,
}
/// A line.
///
/// It is normally used to define the highlight of something, like a split.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Line {
/// The [`Color`] of the [`Line`].
pub color: Color,
/// The width of the [`Line`].
pub width: f32,
}
/// A set of rules that dictate the style of a container.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;
/// The [`Appearance`] to draw when a pane is hovered.
fn hovered_region(&self, style: &Self::Style) -> Appearance;
/// The [`Line`] to draw when a split is picked.
fn picked_split(&self, style: &Self::Style) -> Option<Line>;
/// The [`Line`] to draw when a split is hovered.
fn hovered_split(&self, style: &Self::Style) -> Option<Line>;
}

View file

@ -1,29 +0,0 @@
//! Change the appearance of a pick list.
use iced_core::{Background, Border, Color};
/// The appearance of a pick list.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The text [`Color`] of the pick list.
pub text_color: Color,
/// The placeholder [`Color`] of the pick list.
pub placeholder_color: Color,
/// The handle [`Color`] of the pick list.
pub handle_color: Color,
/// The [`Background`] of the pick list.
pub background: Background,
/// The [`Border`] of the pick list.
pub border: Border,
}
/// A set of rules that dictate the style of a container.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default + Clone;
/// Produces the active [`Appearance`] of a pick list.
fn active(&self, style: &<Self as StyleSheet>::Style) -> Appearance;
/// Produces the hovered [`Appearance`] of a pick list.
fn hovered(&self, style: &<Self as StyleSheet>::Style) -> Appearance;
}

View file

@ -1,23 +0,0 @@
//! Change the appearance of a progress bar.
use crate::core::border;
use crate::core::Background;
/// The appearance of a progress bar.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The [`Background`] of the progress bar.
pub background: Background,
/// The [`Background`] of the bar of the progress bar.
pub bar: Background,
/// The border radius of the progress bar.
pub border_radius: border::Radius,
}
/// A set of rules that dictate the style of a progress bar.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;
/// Produces the [`Appearance`] of the progress bar.
fn appearance(&self, style: &Self::Style) -> Appearance;
}

View file

@ -1,20 +0,0 @@
//! Change the appearance of a QR code.
use crate::core::Color;
/// The appearance of a QR code.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Appearance {
/// The color of the QR code data cells
pub cell: Color,
/// The color of the QR code background
pub background: Color,
}
/// A set of rules that dictate the style of a QR code.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;
/// Produces the style of a QR code.
fn appearance(&self, style: &Self::Style) -> Appearance;
}

View file

@ -1,29 +0,0 @@
//! Change the appearance of radio buttons.
use iced_core::{Background, Color};
/// The appearance of a radio button.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The [`Background`] of the radio button.
pub background: Background,
/// The [`Color`] of the dot of the radio button.
pub dot_color: Color,
/// The border width of the radio button.
pub border_width: f32,
/// The border [`Color`] of the radio button.
pub border_color: Color,
/// The text [`Color`] of the radio button.
pub text_color: Option<Color>,
}
/// A set of rules that dictate the style of a radio button.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;
/// Produces the active [`Appearance`] of a radio button.
fn active(&self, style: &Self::Style, is_selected: bool) -> Appearance;
/// Produces the hovered [`Appearance`] of a radio button.
fn hovered(&self, style: &Self::Style, is_selected: bool) -> Appearance;
}

View file

@ -1,89 +0,0 @@
//! Change the appearance of a rule.
use crate::core::border;
use crate::core::Color;
/// The appearance of a rule.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The color of the rule.
pub color: Color,
/// The width (thickness) of the rule line.
pub width: u16,
/// The radius of the line corners.
pub radius: border::Radius,
/// The [`FillMode`] of the rule.
pub fill_mode: FillMode,
}
/// A set of rules that dictate the style of a rule.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;
/// Produces the style of a rule.
fn appearance(&self, style: &Self::Style) -> Appearance;
}
/// The fill mode of a rule.
#[derive(Debug, Clone, Copy)]
pub enum FillMode {
/// Fill the whole length of the container.
Full,
/// Fill a percent of the length of the container. The rule
/// will be centered in that container.
///
/// The range is `[0.0, 100.0]`.
Percent(f32),
/// Uniform offset from each end, length units.
Padded(u16),
/// Different offset on each end of the rule, length units.
/// First = top or left.
AsymmetricPadding(u16, u16),
}
impl FillMode {
/// Return the starting offset and length of the rule.
///
/// * `space` - The space to fill.
///
/// # Returns
///
/// * (`starting_offset`, `length`)
pub fn fill(&self, space: f32) -> (f32, f32) {
match *self {
FillMode::Full => (0.0, space),
FillMode::Percent(percent) => {
if percent >= 100.0 {
(0.0, space)
} else {
let percent_width = (space * percent / 100.0).round();
(((space - percent_width) / 2.0).round(), percent_width)
}
}
FillMode::Padded(padding) => {
if padding == 0 {
(0.0, space)
} else {
let padding = padding as f32;
let mut line_width = space - (padding * 2.0);
if line_width < 0.0 {
line_width = 0.0;
}
(padding, line_width)
}
}
FillMode::AsymmetricPadding(first_pad, second_pad) => {
let first_pad = first_pad as f32;
let second_pad = second_pad as f32;
let mut line_width = space - first_pad - second_pad;
if line_width < 0.0 {
line_width = 0.0;
}
(first_pad, line_width)
}
}
}
}

View file

@ -1,55 +0,0 @@
//! Change the appearance of a scrollable.
use crate::container;
use crate::core::{Background, Border, Color};
/// The appearance of a scrolable.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The [`container::Appearance`] of a scrollable.
pub container: container::Appearance,
/// The [`Scrollbar`] appearance.
pub scrollbar: Scrollbar,
/// The [`Background`] of the gap between a horizontal and vertical scrollbar.
pub gap: Option<Background>,
}
/// The appearance of the scrollbar of a scrollable.
#[derive(Debug, Clone, Copy)]
pub struct Scrollbar {
/// The [`Background`] of a scrollbar.
pub background: Option<Background>,
/// The [`Border`] of a scrollbar.
pub border: Border,
/// The appearance of the [`Scroller`] of a scrollbar.
pub scroller: Scroller,
}
/// The appearance of the scroller of a scrollable.
#[derive(Debug, Clone, Copy)]
pub struct Scroller {
/// The [`Color`] of the scroller.
pub color: Color,
/// The [`Border`] of the scroller.
pub border: Border,
}
/// A set of rules that dictate the style of a scrollable.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;
/// Produces the [`Appearance`] of an active scrollable.
fn active(&self, style: &Self::Style) -> Appearance;
/// Produces the [`Appearance`] of a scrollable when it is being hovered.
fn hovered(
&self,
style: &Self::Style,
is_mouse_over_scrollbar: bool,
) -> Appearance;
/// Produces the [`Appearance`] of a scrollable when it is being dragged.
fn dragging(&self, style: &Self::Style) -> Appearance {
self.hovered(style, true)
}
}

View file

@ -1,68 +0,0 @@
//! Change the apperance of a slider.
use crate::core::border;
use crate::core::Color;
/// The appearance of a slider.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The colors of the rail of the slider.
pub rail: Rail,
/// The appearance of the [`Handle`] of the slider.
pub handle: Handle,
}
/// The appearance of a slider rail
#[derive(Debug, Clone, Copy)]
pub struct Rail {
/// The colors of the rail of the slider.
pub colors: (Color, Color),
/// The width of the stroke of a slider rail.
pub width: f32,
/// The border radius of the corners of the rail.
pub border_radius: border::Radius,
}
/// The appearance of the handle of a slider.
#[derive(Debug, Clone, Copy)]
pub struct Handle {
/// The shape of the handle.
pub shape: HandleShape,
/// The [`Color`] of the handle.
pub color: Color,
/// The border width of the handle.
pub border_width: f32,
/// The border [`Color`] of the handle.
pub border_color: Color,
}
/// The shape of the handle of a slider.
#[derive(Debug, Clone, Copy)]
pub enum HandleShape {
/// A circular handle.
Circle {
/// The radius of the circle.
radius: f32,
},
/// A rectangular shape.
Rectangle {
/// The width of the rectangle.
width: u16,
/// The border radius of the corners of the rectangle.
border_radius: border::Radius,
},
}
/// A set of rules that dictate the style of a slider.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;
/// Produces the style of an active slider.
fn active(&self, style: &Self::Style) -> Appearance;
/// Produces the style of an hovered slider.
fn hovered(&self, style: &Self::Style) -> Appearance;
/// Produces the style of a slider that is being dragged.
fn dragging(&self, style: &Self::Style) -> Appearance;
}

View file

@ -1,28 +0,0 @@
//! Change the appearance of a svg.
use iced_core::Color;
/// The appearance of an SVG.
#[derive(Debug, Default, Clone, Copy)]
pub struct Appearance {
/// The [`Color`] filter of an SVG.
///
/// Useful for coloring a symbolic icon.
///
/// `None` keeps the original color.
pub color: Option<Color>,
}
/// The stylesheet of a svg.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;
/// Produces the [`Appearance`] of the svg.
fn appearance(&self, style: &Self::Style) -> Appearance;
/// Produces the hovered [`Appearance`] of a svg content.
fn hovered(&self, style: &Self::Style) -> Appearance {
self.appearance(style)
}
}

View file

@ -1,43 +0,0 @@
//! Change the appearance of a text editor.
use crate::core::{Background, Border, Color};
/// The appearance of a text input.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The [`Background`] of the text editor.
pub background: Background,
/// The [`Border`] of the text editor.
pub border: Border,
}
/// A set of rules that dictate the style of a text input.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;
/// Produces the style of an active text input.
fn active(&self, style: &Self::Style) -> Appearance;
/// Produces the style of a focused text input.
fn focused(&self, style: &Self::Style) -> Appearance;
/// Produces the [`Color`] of the placeholder of a text input.
fn placeholder_color(&self, style: &Self::Style) -> Color;
/// Produces the [`Color`] of the value of a text input.
fn value_color(&self, style: &Self::Style) -> Color;
/// Produces the [`Color`] of the value of a disabled text input.
fn disabled_color(&self, style: &Self::Style) -> Color;
/// Produces the [`Color`] of the selection of a text input.
fn selection_color(&self, style: &Self::Style) -> Color;
/// Produces the style of an hovered text input.
fn hovered(&self, style: &Self::Style) -> Appearance {
self.focused(style)
}
/// Produces the style of a disabled text input.
fn disabled(&self, style: &Self::Style) -> Appearance;
}

View file

@ -1,45 +0,0 @@
//! Change the appearance of a text input.
use iced_core::{Background, Border, Color};
/// The appearance of a text input.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The [`Background`] of the text input.
pub background: Background,
/// The [`Border`] of the text input.
pub border: Border,
/// The icon [`Color`] of the text input.
pub icon_color: Color,
}
/// A set of rules that dictate the style of a text input.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;
/// Produces the style of an active text input.
fn active(&self, style: &Self::Style) -> Appearance;
/// Produces the style of a focused text input.
fn focused(&self, style: &Self::Style) -> Appearance;
/// Produces the [`Color`] of the placeholder of a text input.
fn placeholder_color(&self, style: &Self::Style) -> Color;
/// Produces the [`Color`] of the value of a text input.
fn value_color(&self, style: &Self::Style) -> Color;
/// Produces the [`Color`] of the value of a disabled text input.
fn disabled_color(&self, style: &Self::Style) -> Color;
/// Produces the [`Color`] of the selection of a text input.
fn selection_color(&self, style: &Self::Style) -> Color;
/// Produces the style of an hovered text input.
fn hovered(&self, style: &Self::Style) -> Appearance {
self.focused(style)
}
/// Produces the style of a disabled text input.
fn disabled(&self, style: &Self::Style) -> Appearance;
}

File diff suppressed because it is too large Load diff

View file

@ -1,35 +0,0 @@
//! Change the appearance of a toggler.
use iced_core::Color;
/// The appearance of a toggler.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The background [`Color`] of the toggler.
pub background: Color,
/// The width of the background border of the toggler.
pub background_border_width: f32,
/// The [`Color`] of the background border of the toggler.
pub background_border_color: Color,
/// The foreground [`Color`] of the toggler.
pub foreground: Color,
/// The width of the foreground border of the toggler.
pub foreground_border_width: f32,
/// The [`Color`] of the foreground border of the toggler.
pub foreground_border_color: Color,
}
/// A set of rules that dictate the style of a toggler.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;
/// Returns the active [`Appearance`] of the toggler for the provided [`Style`].
///
/// [`Style`]: Self::Style
fn active(&self, style: &Self::Style, is_active: bool) -> Appearance;
/// Returns the hovered [`Appearance`] of the toggler for the provided [`Style`].
///
/// [`Style`]: Self::Style
fn hovered(&self, style: &Self::Style, is_active: bool) -> Appearance;
}

View file

@ -158,10 +158,3 @@ pub fn convert(
texture
}
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(C)]
struct Vertex {
ndc: [f32; 2],
uv: [f32; 2],
}

View file

@ -25,7 +25,6 @@ wgpu = ["iced_renderer/wgpu"]
[dependencies]
iced_renderer.workspace = true
iced_runtime.workspace = true
iced_style.workspace = true
num-traits.workspace = true
thiserror.workspace = true

View file

@ -1,26 +1,22 @@
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`].
use crate::core::event::{self, Event};
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;
use crate::core::renderer;
use crate::core::theme::palette;
use crate::core::touch;
use crate::core::widget::tree::{self, Tree};
use crate::core::widget::Operation;
use crate::core::{
Background, Clipboard, Color, Element, Layout, Length, Padding, Rectangle,
Shell, Size, Vector, Widget,
Background, Border, Clipboard, Color, Element, Layout, Length, Padding,
Rectangle, Shadow, Shell, Size, Theme, Vector, Widget,
};
pub use crate::style::button::{Appearance, StyleSheet};
/// A generic widget that produces a message when pressed.
///
/// ```no_run
/// # type Button<'a, Message> =
/// # iced_widget::Button<'a, Message, iced_widget::style::Theme, iced_widget::renderer::Renderer>;
/// # type Button<'a, Message> = iced_widget::Button<'a, Message>;
/// #
/// #[derive(Clone)]
/// enum Message {
@ -34,8 +30,7 @@ pub use crate::style::button::{Appearance, StyleSheet};
/// be disabled:
///
/// ```
/// # type Button<'a, Message> =
/// # iced_widget::Button<'a, Message, iced_widget::style::Theme, iced_widget::renderer::Renderer>;
/// # type Button<'a, Message> = iced_widget::Button<'a, Message>;
/// #
/// #[derive(Clone)]
/// enum Message {
@ -53,7 +48,6 @@ pub use crate::style::button::{Appearance, StyleSheet};
#[allow(missing_debug_implementations)]
pub struct Button<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
where
Theme: StyleSheet,
Renderer: crate::core::Renderer,
{
content: Element<'a, Message, Theme, Renderer>,
@ -62,18 +56,20 @@ where
height: Length,
padding: Padding,
clip: bool,
style: Theme::Style,
style: Style<Theme>,
}
impl<'a, Message, Theme, Renderer> Button<'a, Message, Theme, Renderer>
where
Theme: StyleSheet,
Renderer: crate::core::Renderer,
{
/// Creates a new [`Button`] with the given content.
pub fn new(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Self {
) -> Self
where
Theme: DefaultStyle,
{
let content = content.into();
let size = content.as_widget().size_hint();
@ -82,9 +78,9 @@ where
on_press: None,
width: size.width.fluid(),
height: size.height.fluid(),
padding: Padding::new(5.0),
padding: DEFAULT_PADDING,
clip: false,
style: Theme::Style::default(),
style: Theme::default_style(),
}
}
@ -124,8 +120,8 @@ where
}
/// Sets the style variant of this [`Button`].
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
self.style = style.into();
pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self {
self.style = style;
self
}
@ -137,11 +133,15 @@ where
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
struct State {
is_pressed: bool,
}
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Button<'a, Message, Theme, Renderer>
where
Message: 'a + Clone,
Theme: StyleSheet,
Renderer: 'a + crate::core::Renderer,
{
fn tag(&self) -> tree::Tag {
@ -149,7 +149,7 @@ where
}
fn state(&self) -> tree::State {
tree::State::new(State::new())
tree::State::new(State::default())
}
fn children(&self) -> Vec<Tree> {
@ -173,13 +173,19 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
layout(limits, self.width, self.height, self.padding, |limits| {
layout::padded(
limits,
self.width,
self.height,
self.padding,
|limits| {
self.content.as_widget().layout(
&mut tree.children[0],
renderer,
limits,
)
})
},
)
}
fn operate(
@ -223,9 +229,48 @@ where
return event::Status::Captured;
}
update(event, layout, cursor, shell, &self.on_press, || {
tree.state.downcast_mut::<State>()
})
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
if self.on_press.is_some() {
let bounds = layout.bounds();
if cursor.is_over(bounds) {
let state = tree.state.downcast_mut::<State>();
state.is_pressed = true;
return event::Status::Captured;
}
}
}
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
| Event::Touch(touch::Event::FingerLifted { .. }) => {
if let Some(on_press) = self.on_press.clone() {
let state = tree.state.downcast_mut::<State>();
if state.is_pressed {
state.is_pressed = false;
let bounds = layout.bounds();
if cursor.is_over(bounds) {
shell.publish(on_press);
}
return event::Status::Captured;
}
}
}
Event::Touch(touch::Event::FingerLost { .. }) => {
let state = tree.state.downcast_mut::<State>();
state.is_pressed = false;
}
_ => {}
}
event::Status::Ignored
}
fn draw(
@ -240,16 +285,39 @@ where
) {
let bounds = layout.bounds();
let content_layout = layout.children().next().unwrap();
let is_mouse_over = cursor.is_over(bounds);
let styling = draw(
renderer,
let status = if self.on_press.is_none() {
Status::Disabled
} else if is_mouse_over {
let state = tree.state.downcast_ref::<State>();
if state.is_pressed {
Status::Pressed
} else {
Status::Hovered
}
} else {
Status::Active
};
let styling = (self.style)(theme, status);
if styling.background.is_some()
|| styling.border.width > 0.0
|| styling.shadow.color.a > 0.0
{
renderer.fill_quad(
renderer::Quad {
bounds,
cursor,
self.on_press.is_some(),
theme,
&self.style,
|| tree.state.downcast_ref::<State>(),
border: styling.border,
shadow: styling.shadow,
},
styling
.background
.unwrap_or(Background::Color(Color::TRANSPARENT)),
);
}
let viewport = if self.clip {
bounds.intersection(viewport).unwrap_or(*viewport)
@ -278,7 +346,13 @@ where
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(layout, cursor, self.on_press.is_some())
let is_mouse_over = cursor.is_over(layout.bounds());
if is_mouse_over && self.on_press.is_some() {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
}
}
fn overlay<'b>(
@ -301,7 +375,7 @@ impl<'a, Message, Theme, Renderer> From<Button<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: Clone + 'a,
Theme: StyleSheet + 'a,
Theme: 'a,
Renderer: crate::core::Renderer + 'a,
{
fn from(button: Button<'a, Message, Theme, Renderer>) -> Self {
@ -309,143 +383,182 @@ where
}
}
/// The local state of a [`Button`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct State {
is_pressed: bool,
}
impl State {
/// Creates a new [`State`].
pub fn new() -> State {
State::default()
}
}
/// Processes the given [`Event`] and updates the [`State`] of a [`Button`]
/// accordingly.
pub fn update<'a, Message: Clone>(
event: Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>,
on_press: &Option<Message>,
state: impl FnOnce() -> &'a mut State,
) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
if on_press.is_some() {
let bounds = layout.bounds();
if cursor.is_over(bounds) {
let state = state();
state.is_pressed = true;
return event::Status::Captured;
}
}
}
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
| Event::Touch(touch::Event::FingerLifted { .. }) => {
if let Some(on_press) = on_press.clone() {
let state = state();
if state.is_pressed {
state.is_pressed = false;
let bounds = layout.bounds();
if cursor.is_over(bounds) {
shell.publish(on_press);
}
return event::Status::Captured;
}
}
}
Event::Touch(touch::Event::FingerLost { .. }) => {
let state = state();
state.is_pressed = false;
}
_ => {}
}
event::Status::Ignored
}
/// Draws a [`Button`].
pub fn draw<'a, Theme, Renderer: crate::core::Renderer>(
renderer: &mut Renderer,
bounds: Rectangle,
cursor: mouse::Cursor,
is_enabled: bool,
theme: &Theme,
style: &Theme::Style,
state: impl FnOnce() -> &'a State,
) -> Appearance
where
Theme: StyleSheet,
{
let is_mouse_over = cursor.is_over(bounds);
let styling = if !is_enabled {
theme.disabled(style)
} else if is_mouse_over {
let state = state();
if state.is_pressed {
theme.pressed(style)
} else {
theme.hovered(style)
}
} else {
theme.active(style)
/// The default [`Padding`] of a [`Button`].
pub(crate) const DEFAULT_PADDING: Padding = Padding {
top: 5.0,
bottom: 5.0,
right: 10.0,
left: 10.0,
};
if styling.background.is_some()
|| styling.border.width > 0.0
|| styling.shadow.color.a > 0.0
{
renderer.fill_quad(
renderer::Quad {
bounds,
border: styling.border,
shadow: styling.shadow,
/// The possible status of a [`Button`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Status {
/// The [`Button`] can be pressed.
Active,
/// The [`Button`] can be pressed and it is being hovered.
Hovered,
/// The [`Button`] is being pressed.
Pressed,
/// The [`Button`] cannot be pressed.
Disabled,
}
/// The appearance of a button.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Appearance {
/// The [`Background`] of the button.
pub background: Option<Background>,
/// The text [`Color`] of the button.
pub text_color: Color,
/// The [`Border`] of the buton.
pub border: Border,
/// The [`Shadow`] of the butoon.
pub shadow: Shadow,
}
impl Appearance {
/// Updates the [`Appearance`] with the given [`Background`].
pub fn with_background(self, background: impl Into<Background>) -> Self {
Self {
background: Some(background.into()),
..self
}
}
}
impl std::default::Default for Appearance {
fn default() -> Self {
Self {
background: None,
text_color: Color::BLACK,
border: Border::default(),
shadow: Shadow::default(),
}
}
}
/// The style of a [`Button`].
pub type Style<Theme> = fn(&Theme, Status) -> Appearance;
/// The default style of a [`Button`].
pub trait DefaultStyle {
/// Returns the default style of a [`Button`].
fn default_style() -> Style<Self>;
}
impl DefaultStyle for Theme {
fn default_style() -> Style<Self> {
primary
}
}
impl DefaultStyle for Appearance {
fn default_style() -> Style<Self> {
|appearance, _status| *appearance
}
}
impl DefaultStyle for Color {
fn default_style() -> Style<Self> {
|color, _status| Appearance::default().with_background(*color)
}
}
/// A primary button; denoting a main action.
pub fn primary(theme: &Theme, status: Status) -> Appearance {
let palette = theme.extended_palette();
let base = styled(palette.primary.strong);
match status {
Status::Active | Status::Pressed => base,
Status::Hovered => Appearance {
background: Some(Background::Color(palette.primary.base.color)),
..base
},
styling
Status::Disabled => disabled(base),
}
}
/// A secondary button; denoting a complementary action.
pub fn secondary(theme: &Theme, status: Status) -> Appearance {
let palette = theme.extended_palette();
let base = styled(palette.secondary.base);
match status {
Status::Active | Status::Pressed => base,
Status::Hovered => Appearance {
background: Some(Background::Color(palette.secondary.strong.color)),
..base
},
Status::Disabled => disabled(base),
}
}
/// A success button; denoting a good outcome.
pub fn success(theme: &Theme, status: Status) -> Appearance {
let palette = theme.extended_palette();
let base = styled(palette.success.base);
match status {
Status::Active | Status::Pressed => base,
Status::Hovered => Appearance {
background: Some(Background::Color(palette.success.strong.color)),
..base
},
Status::Disabled => disabled(base),
}
}
/// A danger button; denoting a destructive action.
pub fn danger(theme: &Theme, status: Status) -> Appearance {
let palette = theme.extended_palette();
let base = styled(palette.danger.base);
match status {
Status::Active | Status::Pressed => base,
Status::Hovered => Appearance {
background: Some(Background::Color(palette.danger.strong.color)),
..base
},
Status::Disabled => disabled(base),
}
}
/// A text button; useful for links.
pub fn text(theme: &Theme, status: Status) -> Appearance {
let palette = theme.extended_palette();
let base = Appearance {
text_color: palette.background.base.text,
..Appearance::default()
};
match status {
Status::Active | Status::Pressed => base,
Status::Hovered => Appearance {
text_color: palette.background.base.text.scale_alpha(0.8),
..base
},
Status::Disabled => disabled(base),
}
}
fn styled(pair: palette::Pair) -> Appearance {
Appearance {
background: Some(Background::Color(pair.color)),
text_color: pair.text,
border: Border::rounded(2),
..Appearance::default()
}
}
fn disabled(appearance: Appearance) -> Appearance {
Appearance {
background: appearance
.background
.unwrap_or(Background::Color(Color::TRANSPARENT)),
);
}
styling
}
/// Computes the layout of a [`Button`].
pub fn layout(
limits: &layout::Limits,
width: Length,
height: Length,
padding: Padding,
layout_content: impl FnOnce(&layout::Limits) -> layout::Node,
) -> layout::Node {
layout::padded(limits, width, height, padding, layout_content)
}
/// Returns the [`mouse::Interaction`] of a [`Button`].
pub fn mouse_interaction(
layout: Layout<'_>,
cursor: mouse::Cursor,
is_enabled: bool,
) -> mouse::Interaction {
let is_mouse_over = cursor.is_over(layout.bounds());
if is_mouse_over && is_enabled {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
.map(|background| background.scale_alpha(0.5)),
text_color: appearance.text_color.scale_alpha(0.5),
..appearance
}
}

View file

@ -5,22 +5,21 @@ use crate::core::layout;
use crate::core::mouse;
use crate::core::renderer;
use crate::core::text;
use crate::core::theme::palette;
use crate::core::touch;
use crate::core::widget;
use crate::core::widget::tree::{self, Tree};
use crate::core::{
Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Widget,
Background, Border, Clipboard, Color, Element, Layout, Length, Pixels,
Rectangle, Shell, Size, Theme, Widget,
};
pub use crate::style::checkbox::{Appearance, StyleSheet};
/// A box that can be checked.
///
/// # Example
///
/// ```no_run
/// # type Checkbox<'a, Message> =
/// # iced_widget::Checkbox<'a, Message, iced_widget::style::Theme, iced_widget::renderer::Renderer>;
/// # type Checkbox<'a, Message> = iced_widget::Checkbox<'a, Message>;
/// #
/// pub enum Message {
/// CheckboxToggled(bool),
@ -39,7 +38,6 @@ pub struct Checkbox<
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
Theme: StyleSheet + crate::text::StyleSheet,
Renderer: text::Renderer,
{
is_checked: bool,
@ -53,26 +51,28 @@ pub struct Checkbox<
text_shaping: text::Shaping,
font: Option<Renderer::Font>,
icon: Icon<Renderer::Font>,
style: <Theme as StyleSheet>::Style,
style: Style<Theme>,
}
impl<'a, Message, Theme, Renderer> Checkbox<'a, Message, Theme, Renderer>
where
Renderer: text::Renderer,
Theme: StyleSheet + crate::text::StyleSheet,
{
/// The default size of a [`Checkbox`].
const DEFAULT_SIZE: f32 = 20.0;
const DEFAULT_SIZE: f32 = 16.0;
/// The default spacing of a [`Checkbox`].
const DEFAULT_SPACING: f32 = 10.0;
const DEFAULT_SPACING: f32 = 8.0;
/// Creates a new [`Checkbox`].
///
/// It expects:
/// * the label of the [`Checkbox`]
/// * a boolean describing whether the [`Checkbox`] is checked or not
pub fn new(label: impl Into<String>, is_checked: bool) -> Self {
pub fn new(label: impl Into<String>, is_checked: bool) -> Self
where
Theme: DefaultStyle,
{
Checkbox {
is_checked,
on_toggle: None,
@ -91,7 +91,7 @@ where
line_height: text::LineHeight::default(),
shaping: text::Shaping::Basic,
},
style: Default::default(),
style: Theme::default_style(),
}
}
@ -174,11 +174,8 @@ where
}
/// Sets the style of the [`Checkbox`].
pub fn style(
mut self,
style: impl Into<<Theme as StyleSheet>::Style>,
) -> Self {
self.style = style.into();
pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self {
self.style = style;
self
}
}
@ -186,7 +183,6 @@ where
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Checkbox<'a, Message, Theme, Renderer>
where
Theme: StyleSheet + crate::text::StyleSheet,
Renderer: text::Renderer,
{
fn tag(&self) -> tree::Tag {
@ -293,17 +289,20 @@ where
) {
let is_mouse_over = cursor.is_over(layout.bounds());
let is_disabled = self.on_toggle.is_none();
let is_checked = self.is_checked;
let mut children = layout.children();
let custom_style = if is_disabled {
theme.disabled(&self.style, self.is_checked)
let status = if is_disabled {
Status::Disabled { is_checked }
} else if is_mouse_over {
theme.hovered(&self.style, self.is_checked)
Status::Hovered { is_checked }
} else {
theme.active(&self.style, self.is_checked)
Status::Active { is_checked }
};
let appearance = (self.style)(theme, status);
{
let layout = children.next().unwrap();
let bounds = layout.bounds();
@ -311,10 +310,10 @@ where
renderer.fill_quad(
renderer::Quad {
bounds,
border: custom_style.border,
border: appearance.border,
..renderer::Quad::default()
},
custom_style.background,
appearance.background,
);
let Icon {
@ -339,7 +338,7 @@ where
shaping: *shaping,
},
bounds.center(),
custom_style.icon_color,
appearance.icon_color,
*viewport,
);
}
@ -354,7 +353,7 @@ where
label_layout,
tree.state.downcast_ref(),
crate::text::Appearance {
color: custom_style.text_color,
color: appearance.text_color,
},
viewport,
);
@ -366,7 +365,7 @@ impl<'a, Message, Theme, Renderer> From<Checkbox<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
Theme: 'a + StyleSheet + crate::text::StyleSheet,
Theme: 'a,
Renderer: 'a + text::Renderer,
{
fn from(
@ -390,3 +389,183 @@ pub struct Icon<Font> {
/// The shaping strategy of the icon.
pub shaping: text::Shaping,
}
/// The possible status of a [`Checkbox`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Status {
/// The [`Checkbox`] can be interacted with.
Active {
/// Indicates if the [`Checkbox`] is currently checked.
is_checked: bool,
},
/// The [`Checkbox`] can be interacted with and it is being hovered.
Hovered {
/// Indicates if the [`Checkbox`] is currently checked.
is_checked: bool,
},
/// The [`Checkbox`] cannot be interacted with.
Disabled {
/// Indicates if the [`Checkbox`] is currently checked.
is_checked: bool,
},
}
/// The appearance of a checkbox.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The [`Background`] of the checkbox.
pub background: Background,
/// The icon [`Color`] of the checkbox.
pub icon_color: Color,
/// The [`Border`] of hte checkbox.
pub border: Border,
/// The text [`Color`] of the checkbox.
pub text_color: Option<Color>,
}
/// The style of a [`Checkbox`].
pub type Style<Theme> = fn(&Theme, Status) -> Appearance;
/// The default style of a [`Checkbox`].
pub trait DefaultStyle {
/// Returns the default style of a [`Checkbox`].
fn default_style() -> Style<Self>;
}
impl DefaultStyle for Theme {
fn default_style() -> Style<Self> {
primary
}
}
impl DefaultStyle for Appearance {
fn default_style() -> Style<Self> {
|appearance, _status| *appearance
}
}
/// A primary checkbox; denoting a main toggle.
pub fn primary(theme: &Theme, status: Status) -> Appearance {
let palette = theme.extended_palette();
match status {
Status::Active { is_checked } => styled(
palette.primary.strong.text,
palette.background.base,
palette.primary.strong,
is_checked,
),
Status::Hovered { is_checked } => styled(
palette.primary.strong.text,
palette.background.weak,
palette.primary.base,
is_checked,
),
Status::Disabled { is_checked } => styled(
palette.primary.strong.text,
palette.background.weak,
palette.background.strong,
is_checked,
),
}
}
/// A secondary checkbox; denoting a complementary toggle.
pub fn secondary(theme: &Theme, status: Status) -> Appearance {
let palette = theme.extended_palette();
match status {
Status::Active { is_checked } => styled(
palette.background.base.text,
palette.background.base,
palette.background.strong,
is_checked,
),
Status::Hovered { is_checked } => styled(
palette.background.base.text,
palette.background.weak,
palette.background.strong,
is_checked,
),
Status::Disabled { is_checked } => styled(
palette.background.strong.color,
palette.background.weak,
palette.background.weak,
is_checked,
),
}
}
/// A success checkbox; denoting a positive toggle.
pub fn success(theme: &Theme, status: Status) -> Appearance {
let palette = theme.extended_palette();
match status {
Status::Active { is_checked } => styled(
palette.success.base.text,
palette.background.base,
palette.success.base,
is_checked,
),
Status::Hovered { is_checked } => styled(
palette.success.base.text,
palette.background.weak,
palette.success.base,
is_checked,
),
Status::Disabled { is_checked } => styled(
palette.success.base.text,
palette.background.weak,
palette.success.weak,
is_checked,
),
}
}
/// A danger checkbox; denoting a negaive toggle.
pub fn danger(theme: &Theme, status: Status) -> Appearance {
let palette = theme.extended_palette();
match status {
Status::Active { is_checked } => styled(
palette.danger.base.text,
palette.background.base,
palette.danger.base,
is_checked,
),
Status::Hovered { is_checked } => styled(
palette.danger.base.text,
palette.background.weak,
palette.danger.base,
is_checked,
),
Status::Disabled { is_checked } => styled(
palette.danger.base.text,
palette.background.weak,
palette.danger.weak,
is_checked,
),
}
}
fn styled(
icon_color: Color,
base: palette::Pair,
accent: palette::Pair,
is_checked: bool,
) -> Appearance {
Appearance {
background: Background::Color(if is_checked {
accent.color
} else {
base.color
}),
icon_color,
border: Border {
radius: 2.0.into(),
width: 1.0,
color: accent.color,
},
text_color: None,
}
}

View file

@ -10,11 +10,11 @@ use crate::core::text;
use crate::core::time::Instant;
use crate::core::widget::{self, Widget};
use crate::core::{
Clipboard, Element, Length, Padding, Rectangle, Shell, Size, Vector,
Clipboard, Element, Length, Padding, Rectangle, Shell, Size, Theme, Vector,
};
use crate::overlay::menu;
use crate::text::LineHeight;
use crate::{container, scrollable, text_input, TextInput};
use crate::text_input::{self, TextInput};
use std::cell::RefCell;
use std::fmt::Display;
@ -32,7 +32,6 @@ pub struct ComboBox<
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
Theme: text_input::StyleSheet + menu::StyleSheet,
Renderer: text::Renderer,
{
state: &'a State<T>,
@ -43,7 +42,7 @@ pub struct ComboBox<
on_option_hovered: Option<Box<dyn Fn(T) -> Message>>,
on_close: Option<Message>,
on_input: Option<Box<dyn Fn(String) -> Message>>,
menu_style: <Theme as menu::StyleSheet>::Style,
menu_style: menu::Style<Theme>,
padding: Padding,
size: Option<f32>,
}
@ -51,7 +50,6 @@ pub struct ComboBox<
impl<'a, T, Message, Theme, Renderer> ComboBox<'a, T, Message, Theme, Renderer>
where
T: std::fmt::Display + Clone,
Theme: text_input::StyleSheet + menu::StyleSheet,
Renderer: text::Renderer,
{
/// Creates a new [`ComboBox`] with the given list of options, a placeholder,
@ -62,8 +60,17 @@ where
placeholder: &str,
selection: Option<&T>,
on_selected: impl Fn(T) -> Message + 'static,
) -> Self {
let text_input = TextInput::new(placeholder, &state.value())
) -> Self
where
Theme: DefaultStyle,
{
let style = Theme::default_style();
let text_input = TextInput::with_style(
placeholder,
&state.value(),
style.text_input,
)
.on_input(TextInputEvent::TextChanged);
let selection = selection.map(T::to_string).unwrap_or_default();
@ -77,7 +84,7 @@ where
on_option_hovered: None,
on_input: None,
on_close: None,
menu_style: Default::default(),
menu_style: style.menu,
padding: text_input::DEFAULT_PADDING,
size: None,
}
@ -118,24 +125,11 @@ where
}
/// Sets the style of the [`ComboBox`].
// TODO: Define its own `StyleSheet` trait
pub fn style<S>(mut self, style: S) -> Self
where
S: Into<<Theme as text_input::StyleSheet>::Style>
+ Into<<Theme as menu::StyleSheet>::Style>
+ Clone,
{
self.menu_style = style.clone().into();
self.text_input = self.text_input.style(style);
self
}
pub fn style(mut self, style: impl Into<Style<Theme>>) -> Self {
let style = style.into();
/// Sets the style of the [`TextInput`] of the [`ComboBox`].
pub fn text_input_style<S>(mut self, style: S) -> Self
where
S: Into<<Theme as text_input::StyleSheet>::Style> + Clone,
{
self.text_input = self.text_input.style(style);
self.text_input = self.text_input.style(style.text_input);
self.menu_style = style.menu;
self
}
@ -299,10 +293,6 @@ impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
where
T: Display + Clone + 'static,
Message: Clone,
Theme: container::StyleSheet
+ text_input::StyleSheet
+ scrollable::StyleSheet
+ menu::StyleSheet,
Renderer: text::Renderer,
{
fn size(&self) -> Size<Length> {
@ -679,7 +669,7 @@ where
self.state.sync_filtered_options(filtered_options);
let mut menu = menu::Menu::new(
let mut menu = menu::Menu::with_style(
menu,
&filtered_options.options,
hovered_option,
@ -693,10 +683,10 @@ where
(self.on_selected)(x)
},
self.on_option_hovered.as_deref(),
self.menu_style,
)
.width(bounds.width)
.padding(self.padding)
.style(self.menu_style.clone());
.padding(self.padding);
if let Some(font) = self.font {
menu = menu.font(font);
@ -719,11 +709,7 @@ impl<'a, T, Message, Theme, Renderer>
where
T: Display + Clone + 'static,
Message: Clone + 'a,
Theme: container::StyleSheet
+ text_input::StyleSheet
+ scrollable::StyleSheet
+ menu::StyleSheet
+ 'a,
Theme: 'a,
Renderer: text::Renderer + 'a,
{
fn from(combo_box: ComboBox<'a, T, Message, Theme, Renderer>) -> Self {
@ -731,8 +717,7 @@ where
}
}
/// Search list of options for a given query.
pub fn search<'a, T, A>(
fn search<'a, T, A>(
options: impl IntoIterator<Item = T> + 'a,
option_matchers: impl IntoIterator<Item = &'a A> + 'a,
query: &'a str,
@ -759,8 +744,7 @@ where
})
}
/// Build matchers from given list of options.
pub fn build_matchers<'a, T>(
fn build_matchers<'a, T>(
options: impl IntoIterator<Item = T> + 'a,
) -> Vec<String>
where
@ -775,3 +759,43 @@ where
})
.collect()
}
/// The style of a [`ComboBox`].
#[derive(Debug, PartialEq, Eq)]
pub struct Style<Theme> {
/// The style of the [`TextInput`] of the [`ComboBox`].
pub text_input: fn(&Theme, text_input::Status) -> text_input::Appearance,
/// The style of the [`Menu`] of the [`ComboBox`].
///
/// [`Menu`]: menu::Menu
pub menu: menu::Style<Theme>,
}
impl Style<Theme> {
/// The default style of a [`ComboBox`].
pub const DEFAULT: Self = Self {
text_input: text_input::default,
menu: menu::Style::<Theme>::DEFAULT,
};
}
impl<Theme> Clone for Style<Theme> {
fn clone(&self) -> Self {
*self
}
}
impl<Theme> Copy for Style<Theme> {}
/// The default style of a [`ComboBox`].
pub trait DefaultStyle: Sized {
/// Returns the default style of a [`ComboBox`].
fn default_style() -> Style<Self>;
}
impl DefaultStyle for Theme {
fn default_style() -> Style<Self> {
Style::<Self>::DEFAULT
}
}

View file

@ -1,6 +1,7 @@
//! Decorate content and apply alignment.
use crate::core::alignment::{self, Alignment};
use crate::core::event::{self, Event};
use crate::core::gradient::{self, Gradient};
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;
@ -8,13 +9,11 @@ use crate::core::renderer;
use crate::core::widget::tree::{self, Tree};
use crate::core::widget::{self, Operation};
use crate::core::{
Background, Clipboard, Color, Element, Layout, Length, Padding, Pixels,
Point, Rectangle, Shell, Size, Vector, Widget,
Background, Border, Clipboard, Color, Element, Layout, Length, Padding,
Pixels, Point, Rectangle, Shadow, Shell, Size, Theme, Vector, Widget,
};
use crate::runtime::Command;
pub use iced_style::container::{Appearance, StyleSheet};
/// An element decorating some content.
///
/// It is normally used for alignment purposes.
@ -25,7 +24,6 @@ pub struct Container<
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
Theme: StyleSheet,
Renderer: crate::core::Renderer,
{
id: Option<Id>,
@ -36,21 +34,30 @@ pub struct Container<
max_height: f32,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
style: Theme::Style,
clip: bool,
content: Element<'a, Message, Theme, Renderer>,
style: Style<Theme>,
}
impl<'a, Message, Theme, Renderer> Container<'a, Message, Theme, Renderer>
where
Theme: StyleSheet,
Renderer: crate::core::Renderer,
{
/// Creates an empty [`Container`].
pub fn new<T>(content: T) -> Self
/// Creates a [`Container`] with the given content.
pub fn new(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Self
where
T: Into<Element<'a, Message, Theme, Renderer>>,
Theme: DefaultStyle,
{
Self::with_style(content, Theme::default_style())
}
/// Creates a [`Container`] with the given content and style.
pub fn with_style(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
style: fn(&Theme, Status) -> Appearance,
) -> Self {
let content = content.into();
let size = content.as_widget().size_hint();
@ -63,9 +70,9 @@ where
max_height: f32::INFINITY,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
style: Default::default(),
clip: false,
content,
style,
}
}
@ -130,8 +137,8 @@ where
}
/// Sets the style of the [`Container`].
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
self.style = style.into();
pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self {
self.style = style;
self
}
@ -146,7 +153,6 @@ where
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Container<'a, Message, Theme, Renderer>
where
Theme: StyleSheet,
Renderer: crate::core::Renderer,
{
fn tag(&self) -> tree::Tag {
@ -262,10 +268,18 @@ where
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
let style = theme.appearance(&self.style);
let bounds = layout.bounds();
if let Some(clipped_viewport) = layout.bounds().intersection(viewport) {
draw_background(renderer, &style, layout.bounds());
let status = if cursor.is_over(bounds) {
Status::Hovered
} else {
Status::Idle
};
let style = (self.style)(theme, status);
if let Some(clipped_viewport) = bounds.intersection(viewport) {
draw_background(renderer, &style, bounds);
self.content.as_widget().draw(
tree,
@ -307,7 +321,7 @@ impl<'a, Message, Theme, Renderer> From<Container<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
Theme: 'a + StyleSheet,
Theme: 'a,
Renderer: 'a + crate::core::Renderer,
{
fn from(
@ -482,3 +496,121 @@ pub fn visible_bounds(id: Id) -> Command<Option<Rectangle>> {
bounds: None,
})
}
/// The appearance of a container.
#[derive(Debug, Clone, Copy, Default)]
pub struct Appearance {
/// The text [`Color`] of the container.
pub text_color: Option<Color>,
/// The [`Background`] of the container.
pub background: Option<Background>,
/// The [`Border`] of the container.
pub border: Border,
/// The [`Shadow`] of the container.
pub shadow: Shadow,
}
impl Appearance {
/// Updates the border of the [`Appearance`] with the given [`Color`] and `width`.
pub fn with_border(
self,
color: impl Into<Color>,
width: impl Into<Pixels>,
) -> Self {
Self {
border: Border {
color: color.into(),
width: width.into().0,
..Border::default()
},
..self
}
}
/// Updates the background of the [`Appearance`].
pub fn with_background(self, background: impl Into<Background>) -> Self {
Self {
background: Some(background.into()),
..self
}
}
}
/// The possible status of a [`Container`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Status {
/// The [`Container`] is idle.
Idle,
/// The [`Container`] is being hovered.
Hovered,
}
/// The style of a [`Container`].
pub type Style<Theme> = fn(&Theme, Status) -> Appearance;
/// The default style of a [`Container`].
pub trait DefaultStyle {
/// Returns the default style of a [`Container`].
fn default_style() -> Style<Self>;
}
impl DefaultStyle for Theme {
fn default_style() -> Style<Self> {
transparent
}
}
impl DefaultStyle for Appearance {
fn default_style() -> Style<Self> {
|appearance, _status| *appearance
}
}
impl DefaultStyle for Color {
fn default_style() -> Style<Self> {
|color, _status| Appearance::default().with_background(*color)
}
}
impl DefaultStyle for Gradient {
fn default_style() -> Style<Self> {
|gradient, _status| Appearance::default().with_background(*gradient)
}
}
impl DefaultStyle for gradient::Linear {
fn default_style() -> Style<Self> {
|gradient, _status| Appearance::default().with_background(*gradient)
}
}
/// A transparent [`Container`].
pub fn transparent<Theme>(_theme: &Theme, _status: Status) -> Appearance {
Appearance::default()
}
/// A rounded [`Container`] with a background.
pub fn box_(theme: &Theme, _status: Status) -> Appearance {
let palette = theme.extended_palette();
Appearance {
background: Some(palette.background.weak.color.into()),
border: Border::rounded(2),
..Appearance::default()
}
}
/// A bordered [`Container`] with a background.
pub fn bordered_box(theme: &Theme, _status: Status) -> Appearance {
let palette = theme.extended_palette();
Appearance {
background: Some(palette.background.weak.color.into()),
border: Border {
width: 1.0,
radius: 0.0.into(),
color: palette.background.strong.color,
},
..Appearance::default()
}
}

View file

@ -7,7 +7,6 @@ use crate::core;
use crate::core::widget::operation;
use crate::core::{Element, Length, Pixels};
use crate::keyed;
use crate::overlay;
use crate::pick_list::{self, PickList};
use crate::progress_bar::{self, ProgressBar};
use crate::radio::{self, Radio};
@ -15,13 +14,13 @@ use crate::rule::{self, Rule};
use crate::runtime::Command;
use crate::scrollable::{self, Scrollable};
use crate::slider::{self, Slider};
use crate::style::application;
use crate::text::{self, Text};
use crate::text::Text;
use crate::text_editor::{self, TextEditor};
use crate::text_input::{self, TextInput};
use crate::toggler::{self, Toggler};
use crate::tooltip::{self, Tooltip};
use crate::{Column, MouseArea, Row, Space, Themer, VerticalSlider};
use crate::vertical_slider::{self, VerticalSlider};
use crate::{Column, MouseArea, Row, Space, Themer};
use std::borrow::Borrow;
use std::ops::RangeInclusive;
@ -59,7 +58,7 @@ pub fn container<'a, Message, Theme, Renderer>(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Container<'a, Message, Theme, Renderer>
where
Theme: container::StyleSheet,
Theme: container::DefaultStyle,
Renderer: core::Renderer,
{
Container::new(content)
@ -105,7 +104,7 @@ pub fn scrollable<'a, Message, Theme, Renderer>(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Scrollable<'a, Message, Theme, Renderer>
where
Theme: scrollable::StyleSheet,
Theme: scrollable::DefaultStyle,
Renderer: core::Renderer,
{
Scrollable::new(content)
@ -118,8 +117,8 @@ pub fn button<'a, Message, Theme, Renderer>(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Button<'a, Message, Theme, Renderer>
where
Theme: button::DefaultStyle,
Renderer: core::Renderer,
Theme: button::StyleSheet,
{
Button::new(content)
}
@ -135,7 +134,7 @@ pub fn tooltip<'a, Message, Theme, Renderer>(
position: tooltip::Position,
) -> crate::Tooltip<'a, Message, Theme, Renderer>
where
Theme: container::StyleSheet + text::StyleSheet,
Theme: container::DefaultStyle,
Renderer: core::text::Renderer,
{
Tooltip::new(content, tooltip, position)
@ -148,7 +147,6 @@ pub fn text<'a, Theme, Renderer>(
text: impl ToString,
) -> Text<'a, Theme, Renderer>
where
Theme: text::StyleSheet,
Renderer: core::text::Renderer,
{
Text::new(text.to_string())
@ -162,7 +160,7 @@ pub fn checkbox<'a, Message, Theme, Renderer>(
is_checked: bool,
) -> Checkbox<'a, Message, Theme, Renderer>
where
Theme: checkbox::StyleSheet + text::StyleSheet,
Theme: checkbox::DefaultStyle,
Renderer: core::text::Renderer,
{
Checkbox::new(label, is_checked)
@ -179,7 +177,7 @@ pub fn radio<Message, Theme, Renderer, V>(
) -> Radio<Message, Theme, Renderer>
where
Message: Clone,
Theme: radio::StyleSheet,
Theme: radio::DefaultStyle,
Renderer: core::text::Renderer,
V: Copy + Eq,
{
@ -195,8 +193,8 @@ pub fn toggler<'a, Message, Theme, Renderer>(
f: impl Fn(bool) -> Message + 'a,
) -> Toggler<'a, Message, Theme, Renderer>
where
Theme: toggler::DefaultStyle,
Renderer: core::text::Renderer,
Theme: toggler::StyleSheet,
{
Toggler::new(label, is_checked, f)
}
@ -210,7 +208,7 @@ pub fn text_input<'a, Message, Theme, Renderer>(
) -> TextInput<'a, Message, Theme, Renderer>
where
Message: Clone,
Theme: text_input::StyleSheet,
Theme: text_input::DefaultStyle,
Renderer: core::text::Renderer,
{
TextInput::new(placeholder, value)
@ -224,7 +222,7 @@ pub fn text_editor<Message, Theme, Renderer>(
) -> TextEditor<'_, core::text::highlighter::PlainText, Message, Theme, Renderer>
where
Message: Clone,
Theme: text_editor::StyleSheet,
Theme: text_editor::DefaultStyle,
Renderer: core::text::Renderer,
{
TextEditor::new(content)
@ -241,7 +239,7 @@ pub fn slider<'a, T, Message, Theme>(
where
T: Copy + From<u8> + std::cmp::PartialOrd,
Message: Clone,
Theme: slider::StyleSheet,
Theme: slider::DefaultStyle,
{
Slider::new(range, value, on_change)
}
@ -257,7 +255,7 @@ pub fn vertical_slider<'a, T, Message, Theme>(
where
T: Copy + From<u8> + std::cmp::PartialOrd,
Message: Clone,
Theme: slider::StyleSheet,
Theme: vertical_slider::DefaultStyle,
{
VerticalSlider::new(range, value, on_change)
}
@ -275,13 +273,8 @@ where
L: Borrow<[T]> + 'a,
V: Borrow<T> + 'a,
Message: Clone,
Theme: pick_list::DefaultStyle,
Renderer: core::text::Renderer,
Theme: pick_list::StyleSheet
+ scrollable::StyleSheet
+ overlay::menu::StyleSheet
+ container::StyleSheet,
<Theme as overlay::menu::StyleSheet>::Style:
From<<Theme as pick_list::StyleSheet>::Style>,
{
PickList::new(options, selected, on_selected)
}
@ -297,7 +290,7 @@ pub fn combo_box<'a, T, Message, Theme, Renderer>(
) -> ComboBox<'a, T, Message, Theme, Renderer>
where
T: std::fmt::Display + Clone,
Theme: text_input::StyleSheet + overlay::menu::StyleSheet,
Theme: combo_box::DefaultStyle,
Renderer: core::text::Renderer,
{
ComboBox::new(state, placeholder, selection, on_selected)
@ -324,7 +317,7 @@ pub fn vertical_space() -> Space {
/// [`Rule`]: crate::Rule
pub fn horizontal_rule<Theme>(height: impl Into<Pixels>) -> Rule<Theme>
where
Theme: rule::StyleSheet,
Theme: rule::DefaultStyle,
{
Rule::horizontal(height)
}
@ -334,7 +327,7 @@ where
/// [`Rule`]: crate::Rule
pub fn vertical_rule<Theme>(width: impl Into<Pixels>) -> Rule<Theme>
where
Theme: rule::StyleSheet,
Theme: rule::DefaultStyle,
{
Rule::vertical(width)
}
@ -351,7 +344,7 @@ pub fn progress_bar<Theme>(
value: f32,
) -> ProgressBar<Theme>
where
Theme: progress_bar::StyleSheet,
Theme: progress_bar::DefaultStyle,
{
ProgressBar::new(range, value)
}
@ -371,7 +364,7 @@ pub fn image<Handle>(handle: impl Into<Handle>) -> crate::Image<Handle> {
#[cfg(feature = "svg")]
pub fn svg<Theme>(handle: impl Into<core::svg::Handle>) -> crate::Svg<Theme>
where
Theme: crate::svg::StyleSheet,
Theme: crate::svg::DefaultStyle,
{
crate::Svg::new(handle)
}
@ -397,7 +390,7 @@ where
#[cfg(feature = "qr_code")]
pub fn qr_code<Theme>(data: &crate::qr_code::Data) -> crate::QRCode<'_, Theme>
where
Theme: crate::qr_code::StyleSheet,
Theme: crate::qr_code::DefaultStyle,
{
crate::QRCode::new(data)
}
@ -440,13 +433,20 @@ where
}
/// A widget that applies any `Theme` to its contents.
pub fn themer<'a, Message, Theme, Renderer>(
theme: Theme,
content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Themer<'a, Message, Theme, Renderer>
pub fn themer<'a, Message, OldTheme, NewTheme, Renderer>(
new_theme: NewTheme,
content: impl Into<Element<'a, Message, NewTheme, Renderer>>,
) -> Themer<
'a,
Message,
OldTheme,
NewTheme,
impl Fn(&OldTheme) -> NewTheme,
Renderer,
>
where
Renderer: core::Renderer,
Theme: application::StyleSheet,
NewTheme: Clone,
{
Themer::new(theme, content)
Themer::new(move |_| new_theme.clone(), content)
}

View file

@ -14,11 +14,11 @@ pub use iced_renderer as renderer;
pub use iced_renderer::graphics;
pub use iced_runtime as runtime;
pub use iced_runtime::core;
pub use iced_style as style;
mod column;
mod mouse_area;
mod row;
mod space;
mod themer;
pub mod button;
@ -34,7 +34,6 @@ pub mod radio;
pub mod rule;
pub mod scrollable;
pub mod slider;
pub mod space;
pub mod text;
pub mod text_editor;
pub mod text_input;
@ -135,5 +134,5 @@ pub mod qr_code;
#[doc(no_inline)]
pub use qr_code::QRCode;
pub use crate::core::theme::{self, Theme};
pub use renderer::Renderer;
pub use style::theme::{self, Theme};

View file

@ -10,13 +10,12 @@ use crate::core::text::{self, Text};
use crate::core::touch;
use crate::core::widget::Tree;
use crate::core::{
Border, Clipboard, Length, Padding, Pixels, Point, Rectangle, Size, Vector,
Background, Border, Clipboard, Color, Length, Padding, Pixels, Point,
Rectangle, Size, Theme, Vector,
};
use crate::core::{Element, Shell, Widget};
use crate::scrollable::{self, Scrollable};
pub use iced_style::menu::{Appearance, StyleSheet};
/// A list of selectable options.
#[allow(missing_debug_implementations)]
pub struct Menu<
@ -26,7 +25,6 @@ pub struct Menu<
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
Theme: StyleSheet,
Renderer: text::Renderer,
{
state: &'a mut State,
@ -40,14 +38,14 @@ pub struct Menu<
text_line_height: text::LineHeight,
text_shaping: text::Shaping,
font: Option<Renderer::Font>,
style: Theme::Style,
style: Style<Theme>,
}
impl<'a, T, Message, Theme, Renderer> Menu<'a, T, Message, Theme, Renderer>
where
T: ToString + Clone,
Message: 'a,
Theme: StyleSheet + container::StyleSheet + scrollable::StyleSheet + 'a,
Theme: 'a,
Renderer: text::Renderer + 'a,
{
/// Creates a new [`Menu`] with the given [`State`], a list of options, and
@ -58,6 +56,29 @@ where
hovered_option: &'a mut Option<usize>,
on_selected: impl FnMut(T) -> Message + 'a,
on_option_hovered: Option<&'a dyn Fn(T) -> Message>,
) -> Self
where
Theme: DefaultStyle,
{
Self::with_style(
state,
options,
hovered_option,
on_selected,
on_option_hovered,
Theme::default_style(),
)
}
/// Creates a new [`Menu`] with the given [`State`], a list of options,
/// the message to produced when an option is selected, and its [`Style`].
pub fn with_style(
state: &'a mut State,
options: &'a [T],
hovered_option: &'a mut Option<usize>,
on_selected: impl FnMut(T) -> Message + 'a,
on_option_hovered: Option<&'a dyn Fn(T) -> Message>,
style: Style<Theme>,
) -> Self {
Menu {
state,
@ -71,7 +92,7 @@ where
text_line_height: text::LineHeight::default(),
text_shaping: text::Shaping::Basic,
font: None,
style: Default::default(),
style,
}
}
@ -115,10 +136,7 @@ where
}
/// Sets the style of the [`Menu`].
pub fn style(
mut self,
style: impl Into<<Theme as StyleSheet>::Style>,
) -> Self {
pub fn style(mut self, style: impl Into<Style<Theme>>) -> Self {
self.style = style.into();
self
}
@ -165,7 +183,6 @@ impl Default for State {
struct Overlay<'a, Message, Theme, Renderer>
where
Theme: StyleSheet + container::StyleSheet,
Renderer: crate::core::Renderer,
{
position: Point,
@ -173,13 +190,13 @@ where
container: Container<'a, Message, Theme, Renderer>,
width: f32,
target_height: f32,
style: <Theme as StyleSheet>::Style,
style: Style<Theme>,
}
impl<'a, Message, Theme, Renderer> Overlay<'a, Message, Theme, Renderer>
where
Message: 'a,
Theme: StyleSheet + container::StyleSheet + scrollable::StyleSheet + 'a,
Theme: 'a,
Renderer: text::Renderer + 'a,
{
pub fn new<T>(
@ -205,7 +222,9 @@ where
style,
} = menu;
let container = Container::new(Scrollable::new(List {
let container = Container::with_style(
Scrollable::with_direction_and_style(
List {
options,
hovered_option,
on_selected,
@ -215,8 +234,13 @@ where
text_line_height,
text_shaping,
padding,
style: style.clone(),
}));
style: style.list,
},
scrollable::Direction::default(),
style.scrollable,
),
container::transparent,
);
state.tree.diff(&container as &dyn Widget<_, _, _>);
@ -235,7 +259,6 @@ impl<'a, Message, Theme, Renderer>
crate::core::Overlay<Message, Theme, Renderer>
for Overlay<'a, Message, Theme, Renderer>
where
Theme: StyleSheet + container::StyleSheet,
Renderer: text::Renderer,
{
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
@ -302,9 +325,10 @@ where
layout: Layout<'_>,
cursor: mouse::Cursor,
) {
let appearance = StyleSheet::appearance(theme, &self.style);
let bounds = layout.bounds();
let appearance = (self.style.list)(theme);
renderer.fill_quad(
renderer::Quad {
bounds,
@ -321,7 +345,6 @@ where
struct List<'a, T, Message, Theme, Renderer>
where
Theme: StyleSheet,
Renderer: text::Renderer,
{
options: &'a [T],
@ -333,14 +356,13 @@ where
text_line_height: text::LineHeight,
text_shaping: text::Shaping,
font: Option<Renderer::Font>,
style: Theme::Style,
style: fn(&Theme) -> Appearance,
}
impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for List<'a, T, Message, Theme, Renderer>
where
T: Clone + ToString,
Theme: StyleSheet,
Renderer: text::Renderer,
{
fn size(&self) -> Size<Length> {
@ -483,7 +505,7 @@ where
_cursor: mouse::Cursor,
viewport: &Rectangle,
) {
let appearance = theme.appearance(&self.style);
let appearance = (self.style)(theme);
let bounds = layout.bounds();
let text_size =
@ -517,7 +539,7 @@ where
width: bounds.width - appearance.border.width * 2.0,
..bounds
},
border: Border::with_radius(appearance.border.radius),
border: Border::rounded(appearance.border.radius),
..renderer::Quad::default()
},
appearance.selected_background,
@ -553,10 +575,79 @@ impl<'a, T, Message, Theme, Renderer>
where
T: ToString + Clone,
Message: 'a,
Theme: StyleSheet + 'a,
Theme: 'a,
Renderer: 'a + text::Renderer,
{
fn from(list: List<'a, T, Message, Theme, Renderer>) -> Self {
Element::new(list)
}
}
/// The appearance of a [`Menu`].
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The [`Background`] of the menu.
pub background: Background,
/// The [`Border`] of the menu.
pub border: Border,
/// The text [`Color`] of the menu.
pub text_color: Color,
/// The text [`Color`] of a selected option in the menu.
pub selected_text_color: Color,
/// The background [`Color`] of a selected option in the menu.
pub selected_background: Background,
}
/// The style of the different parts of a [`Menu`].
#[derive(Debug, PartialEq, Eq)]
pub struct Style<Theme> {
/// The style of the list of the [`Menu`].
pub list: fn(&Theme) -> Appearance,
/// The style of the [`Scrollable`] of the [`Menu`].
pub scrollable: fn(&Theme, scrollable::Status) -> scrollable::Appearance,
}
impl Style<Theme> {
/// The default style of a [`Menu`] with the built-in [`Theme`].
pub const DEFAULT: Self = Self {
list: default,
scrollable: scrollable::default,
};
}
impl<Theme> Clone for Style<Theme> {
fn clone(&self) -> Self {
*self
}
}
impl<Theme> Copy for Style<Theme> {}
/// The default style of a [`Menu`].
pub trait DefaultStyle: Sized {
/// Returns the default style of a [`Menu`].
fn default_style() -> Style<Self>;
}
impl DefaultStyle for Theme {
fn default_style() -> Style<Self> {
Style::<Theme>::DEFAULT
}
}
/// The default style of the list of a [`Menu`].
pub fn default(theme: &Theme) -> Appearance {
let palette = theme.extended_palette();
Appearance {
background: palette.background.weak.color.into(),
border: Border {
width: 1.0,
radius: 0.0.into(),
color: palette.background.strong.color,
},
text_color: palette.background.weak.text,
selected_text_color: palette.primary.strong.text,
selected_background: palette.primary.strong.color.into(),
}
}

File diff suppressed because it is too large Load diff

View file

@ -20,25 +20,26 @@ pub struct Content<
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
Theme: container::StyleSheet,
Renderer: crate::core::Renderer,
{
title_bar: Option<TitleBar<'a, Message, Theme, Renderer>>,
body: Element<'a, Message, Theme, Renderer>,
style: Theme::Style,
style: container::Style<Theme>,
}
impl<'a, Message, Theme, Renderer> Content<'a, Message, Theme, Renderer>
where
Theme: container::StyleSheet,
Renderer: crate::core::Renderer,
{
/// Creates a new [`Content`] with the provided body.
pub fn new(body: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self {
pub fn new(body: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self
where
Theme: container::DefaultStyle,
{
Self {
title_bar: None,
body: body.into(),
style: Default::default(),
style: Theme::default_style(),
}
}
@ -52,7 +53,10 @@ where
}
/// Sets the style of the [`Content`].
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
pub fn style(
mut self,
style: fn(&Theme, container::Status) -> container::Appearance,
) -> Self {
self.style = style.into();
self
}
@ -60,7 +64,6 @@ where
impl<'a, Message, Theme, Renderer> Content<'a, Message, Theme, Renderer>
where
Theme: container::StyleSheet,
Renderer: crate::core::Renderer,
{
pub(super) fn state(&self) -> Tree {
@ -104,7 +107,15 @@ where
let bounds = layout.bounds();
{
let style = theme.appearance(&self.style);
let style = {
let status = if cursor.is_over(bounds) {
container::Status::Hovered
} else {
container::Status::Idle
};
(self.style)(theme, status)
};
container::draw_background(renderer, &style, bounds);
}
@ -370,7 +381,6 @@ where
impl<'a, Message, Theme, Renderer> Draggable
for &Content<'a, Message, Theme, Renderer>
where
Theme: container::StyleSheet,
Renderer: crate::core::Renderer,
{
fn can_be_dragged_at(
@ -393,7 +403,7 @@ impl<'a, T, Message, Theme, Renderer> From<T>
for Content<'a, Message, Theme, Renderer>
where
T: Into<Element<'a, Message, Theme, Renderer>>,
Theme: container::StyleSheet,
Theme: container::DefaultStyle,
Renderer: crate::core::Renderer,
{
fn from(element: T) -> Self {

View file

@ -19,32 +19,32 @@ pub struct TitleBar<
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
Theme: container::StyleSheet,
Renderer: crate::core::Renderer,
{
content: Element<'a, Message, Theme, Renderer>,
controls: Option<Element<'a, Message, Theme, Renderer>>,
padding: Padding,
always_show_controls: bool,
style: Theme::Style,
style: container::Style<Theme>,
}
impl<'a, Message, Theme, Renderer> TitleBar<'a, Message, Theme, Renderer>
where
Theme: container::StyleSheet,
Renderer: crate::core::Renderer,
{
/// Creates a new [`TitleBar`] with the given content.
pub fn new<E>(content: E) -> Self
pub fn new(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Self
where
E: Into<Element<'a, Message, Theme, Renderer>>,
Theme: container::DefaultStyle,
{
Self {
content: content.into(),
controls: None,
padding: Padding::ZERO,
always_show_controls: false,
style: Default::default(),
style: Theme::default_style(),
}
}
@ -64,7 +64,10 @@ where
}
/// Sets the style of the [`TitleBar`].
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
pub fn style(
mut self,
style: fn(&Theme, container::Status) -> container::Appearance,
) -> Self {
self.style = style.into();
self
}
@ -85,7 +88,6 @@ where
impl<'a, Message, Theme, Renderer> TitleBar<'a, Message, Theme, Renderer>
where
Theme: container::StyleSheet,
Renderer: crate::core::Renderer,
{
pub(super) fn state(&self) -> Tree {
@ -128,7 +130,17 @@ where
show_controls: bool,
) {
let bounds = layout.bounds();
let style = theme.appearance(&self.style);
let style = {
let status = if cursor.is_over(bounds) {
container::Status::Hovered
} else {
container::Status::Idle
};
(self.style)(theme, status)
};
let inherited_style = renderer::Style {
text_color: style.text_color.unwrap_or(inherited_style.text_color),
};

View file

@ -1,5 +1,4 @@
//! Display a dropdown list of selectable values.
use crate::container;
use crate::core::alignment;
use crate::core::event::{self, Event};
use crate::core::keyboard;
@ -11,15 +10,13 @@ use crate::core::text::{self, Paragraph as _, Text};
use crate::core::touch;
use crate::core::widget::tree::{self, Tree};
use crate::core::{
Clipboard, Element, Layout, Length, Padding, Pixels, Point, Rectangle,
Shell, Size, Vector, Widget,
Background, Border, Clipboard, Color, Element, Layout, Length, Padding,
Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
};
use crate::overlay::menu::{self, Menu};
use crate::scrollable;
use std::borrow::Borrow;
pub use crate::style::pick_list::{Appearance, StyleSheet};
use std::f32;
/// A widget for selecting a single value from a list of options.
#[allow(missing_debug_implementations)]
@ -35,7 +32,6 @@ pub struct PickList<
T: ToString + PartialEq + Clone,
L: Borrow<[T]> + 'a,
V: Borrow<T> + 'a,
Theme: StyleSheet,
Renderer: text::Renderer,
{
on_select: Box<dyn Fn(T) -> Message + 'a>,
@ -51,7 +47,7 @@ pub struct PickList<
text_shaping: text::Shaping,
font: Option<Renderer::Font>,
handle: Handle<Renderer::Font>,
style: Theme::Style,
style: Style<Theme>,
}
impl<'a, T, L, V, Message, Theme, Renderer>
@ -61,23 +57,18 @@ where
L: Borrow<[T]> + 'a,
V: Borrow<T> + 'a,
Message: Clone,
Theme: StyleSheet
+ scrollable::StyleSheet
+ menu::StyleSheet
+ container::StyleSheet,
<Theme as menu::StyleSheet>::Style: From<<Theme as StyleSheet>::Style>,
Renderer: text::Renderer,
{
/// The default padding of a [`PickList`].
pub const DEFAULT_PADDING: Padding = Padding::new(5.0);
/// Creates a new [`PickList`] with the given list of options, the current
/// selected value, and the message to produce when an option is selected.
pub fn new(
options: L,
selected: Option<V>,
on_select: impl Fn(T) -> Message + 'a,
) -> Self {
) -> Self
where
Theme: DefaultStyle,
{
Self {
on_select: Box::new(on_select),
on_open: None,
@ -86,13 +77,13 @@ where
placeholder: None,
selected,
width: Length::Shrink,
padding: Self::DEFAULT_PADDING,
padding: crate::button::DEFAULT_PADDING,
text_size: None,
text_line_height: text::LineHeight::default(),
text_shaping: text::Shaping::Basic,
font: None,
handle: Handle::default(),
style: Default::default(),
style: Theme::default_style(),
}
}
@ -160,10 +151,7 @@ where
}
/// Sets the style of the [`PickList`].
pub fn style(
mut self,
style: impl Into<<Theme as StyleSheet>::Style>,
) -> Self {
pub fn style(mut self, style: impl Into<Style<Theme>>) -> Self {
self.style = style.into();
self
}
@ -176,11 +164,6 @@ where
L: Borrow<[T]>,
V: Borrow<T>,
Message: Clone + 'a,
Theme: StyleSheet
+ scrollable::StyleSheet
+ menu::StyleSheet
+ container::StyleSheet,
<Theme as menu::StyleSheet>::Style: From<<Theme as StyleSheet>::Style>,
Renderer: text::Renderer + 'a,
{
fn tag(&self) -> tree::Tag {
@ -204,20 +187,78 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
layout(
tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
renderer,
limits,
self.width,
self.padding,
self.text_size,
self.text_line_height,
self.text_shaping,
self.font,
self.placeholder.as_deref(),
self.options.borrow(),
let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
let font = self.font.unwrap_or_else(|| renderer.default_font());
let text_size =
self.text_size.unwrap_or_else(|| renderer.default_size());
let options = self.options.borrow();
state.options.resize_with(options.len(), Default::default);
let option_text = Text {
content: "",
bounds: Size::new(
f32::INFINITY,
self.text_line_height.to_absolute(text_size).into(),
),
size: text_size,
line_height: self.text_line_height,
font,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
shaping: self.text_shaping,
};
for (option, paragraph) in options.iter().zip(state.options.iter_mut())
{
let label = option.to_string();
paragraph.update(Text {
content: &label,
..option_text
});
}
if let Some(placeholder) = &self.placeholder {
state.placeholder.update(Text {
content: placeholder,
..option_text
});
}
let max_width = match self.width {
Length::Shrink => {
let labels_width =
state.options.iter().fold(0.0, |width, paragraph| {
f32::max(width, paragraph.min_width())
});
labels_width.max(
self.placeholder
.as_ref()
.map(|_| state.placeholder.min_width())
.unwrap_or(0.0),
)
}
_ => 0.0,
};
let size = {
let intrinsic = Size::new(
max_width + text_size.0 + self.padding.left,
f32::from(self.text_line_height.to_absolute(text_size)),
);
limits
.width(self.width)
.shrink(self.padding)
.resolve(self.width, Length::Shrink, intrinsic)
.expand(self.padding)
};
layout::Node::new(size)
}
fn on_event(
&mut self,
@ -230,18 +271,98 @@ where
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
) -> event::Status {
update(
event,
layout,
cursor,
shell,
self.on_select.as_ref(),
self.on_open.as_ref(),
self.on_close.as_ref(),
self.selected.as_ref().map(Borrow::borrow),
self.options.borrow(),
|| tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
)
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
let state =
tree.state.downcast_mut::<State<Renderer::Paragraph>>();
if state.is_open {
// Event wasn't processed by overlay, so cursor was clicked either outside its
// bounds or on the drop-down, either way we close the overlay.
state.is_open = false;
if let Some(on_close) = &self.on_close {
shell.publish(on_close.clone());
}
event::Status::Captured
} else if cursor.is_over(layout.bounds()) {
let selected = self.selected.as_ref().map(Borrow::borrow);
state.is_open = true;
state.hovered_option = self
.options
.borrow()
.iter()
.position(|option| Some(option) == selected);
if let Some(on_open) = &self.on_open {
shell.publish(on_open.clone());
}
event::Status::Captured
} else {
event::Status::Ignored
}
}
Event::Mouse(mouse::Event::WheelScrolled {
delta: mouse::ScrollDelta::Lines { y, .. },
}) => {
let state =
tree.state.downcast_mut::<State<Renderer::Paragraph>>();
if state.keyboard_modifiers.command()
&& cursor.is_over(layout.bounds())
&& !state.is_open
{
fn find_next<'a, T: PartialEq>(
selected: &'a T,
mut options: impl Iterator<Item = &'a T>,
) -> Option<&'a T> {
let _ = options.find(|&option| option == selected);
options.next()
}
let options = self.options.borrow();
let selected = self.selected.as_ref().map(Borrow::borrow);
let next_option = if y < 0.0 {
if let Some(selected) = selected {
find_next(selected, options.iter())
} else {
options.first()
}
} else if y > 0.0 {
if let Some(selected) = selected {
find_next(selected, options.iter().rev())
} else {
options.last()
}
} else {
None
};
if let Some(next_option) = next_option {
shell.publish((self.on_select)(next_option.clone()));
}
event::Status::Captured
} else {
event::Status::Ignored
}
}
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
let state =
tree.state.downcast_mut::<State<Renderer::Paragraph>>();
state.keyboard_modifiers = modifiers;
event::Status::Ignored
}
_ => event::Status::Ignored,
}
}
fn mouse_interaction(
@ -252,7 +373,14 @@ where
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(layout, cursor)
let bounds = layout.bounds();
let is_mouse_over = cursor.is_over(bounds);
if is_mouse_over {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
}
}
fn draw(
@ -266,23 +394,124 @@ where
viewport: &Rectangle,
) {
let font = self.font.unwrap_or_else(|| renderer.default_font());
draw(
renderer,
theme,
layout,
cursor,
self.padding,
self.text_size,
self.text_line_height,
self.text_shaping,
font,
self.placeholder.as_deref(),
self.selected.as_ref().map(Borrow::borrow),
&self.handle,
&self.style,
|| tree.state.downcast_ref::<State<Renderer::Paragraph>>(),
viewport,
let selected = self.selected.as_ref().map(Borrow::borrow);
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
let bounds = layout.bounds();
let is_mouse_over = cursor.is_over(bounds);
let is_selected = selected.is_some();
let status = if state.is_open {
Status::Opened
} else if is_mouse_over {
Status::Hovered
} else {
Status::Active
};
let appearance = (self.style.field)(theme, status);
renderer.fill_quad(
renderer::Quad {
bounds,
border: appearance.border,
..renderer::Quad::default()
},
appearance.background,
);
let handle = match &self.handle {
Handle::Arrow { size } => Some((
Renderer::ICON_FONT,
Renderer::ARROW_DOWN_ICON,
*size,
text::LineHeight::default(),
text::Shaping::Basic,
)),
Handle::Static(Icon {
font,
code_point,
size,
line_height,
shaping,
}) => Some((*font, *code_point, *size, *line_height, *shaping)),
Handle::Dynamic { open, closed } => {
if state.is_open {
Some((
open.font,
open.code_point,
open.size,
open.line_height,
open.shaping,
))
} else {
Some((
closed.font,
closed.code_point,
closed.size,
closed.line_height,
closed.shaping,
))
}
}
Handle::None => None,
};
if let Some((font, code_point, size, line_height, shaping)) = handle {
let size = size.unwrap_or_else(|| renderer.default_size());
renderer.fill_text(
Text {
content: &code_point.to_string(),
size,
line_height,
font,
bounds: Size::new(
bounds.width,
f32::from(line_height.to_absolute(size)),
),
horizontal_alignment: alignment::Horizontal::Right,
vertical_alignment: alignment::Vertical::Center,
shaping,
},
Point::new(
bounds.x + bounds.width - self.padding.right,
bounds.center_y(),
),
appearance.handle_color,
*viewport,
);
}
let label = selected.map(ToString::to_string);
if let Some(label) = label.as_deref().or(self.placeholder.as_deref()) {
let text_size =
self.text_size.unwrap_or_else(|| renderer.default_size());
renderer.fill_text(
Text {
content: label,
size: text_size,
line_height: self.text_line_height,
font,
bounds: Size::new(
bounds.width - self.padding.horizontal(),
f32::from(self.text_line_height.to_absolute(text_size)),
),
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
shaping: self.text_shaping,
},
Point::new(bounds.x + self.padding.left, bounds.center_y()),
if is_selected {
appearance.text_color
} else {
appearance.placeholder_color
},
*viewport,
);
}
}
fn overlay<'b>(
@ -293,19 +522,38 @@ where
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
let font = self.font.unwrap_or_else(|| renderer.default_font());
overlay(
layout,
translation,
state,
self.padding,
self.text_size,
self.text_shaping,
self.font.unwrap_or_else(|| renderer.default_font()),
if state.is_open {
let bounds = layout.bounds();
let on_select = &self.on_select;
let mut menu = Menu::with_style(
&mut state.menu,
self.options.borrow(),
&self.on_select,
self.style.clone(),
&mut state.hovered_option,
|option| {
state.is_open = false;
(on_select)(option)
},
None,
self.style.menu,
)
.width(bounds.width)
.padding(self.padding)
.font(font)
.text_shaping(self.text_shaping);
if let Some(text_size) = self.text_size {
menu = menu.text_size(text_size);
}
Some(menu.overlay(layout.position() + translation, bounds.height))
} else {
None
}
}
}
@ -317,12 +565,7 @@ where
L: Borrow<[T]> + 'a,
V: Borrow<T> + 'a,
Message: Clone + 'a,
Theme: StyleSheet
+ scrollable::StyleSheet
+ menu::StyleSheet
+ container::StyleSheet
+ 'a,
<Theme as menu::StyleSheet>::Style: From<<Theme as StyleSheet>::Style>,
Theme: 'a,
Renderer: text::Renderer + 'a,
{
fn from(
@ -332,9 +575,8 @@ where
}
}
/// The state of a [`PickList`].
#[derive(Debug)]
pub struct State<P: text::Paragraph> {
struct State<P: text::Paragraph> {
menu: menu::State,
keyboard_modifiers: keyboard::Modifiers,
is_open: bool,
@ -407,394 +649,94 @@ pub struct Icon<Font> {
pub shaping: text::Shaping,
}
/// Computes the layout of a [`PickList`].
pub fn layout<Renderer, T>(
state: &mut State<Renderer::Paragraph>,
renderer: &Renderer,
limits: &layout::Limits,
width: Length,
padding: Padding,
text_size: Option<Pixels>,
text_line_height: text::LineHeight,
text_shaping: text::Shaping,
font: Option<Renderer::Font>,
placeholder: Option<&str>,
options: &[T],
) -> layout::Node
where
Renderer: text::Renderer,
T: ToString,
{
use std::f32;
/// The possible status of a [`PickList`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Status {
/// The [`PickList`] can be interacted with.
Active,
/// The [`PickList`] is being hovered.
Hovered,
/// The [`PickList`] is open.
Opened,
}
let font = font.unwrap_or_else(|| renderer.default_font());
let text_size = text_size.unwrap_or_else(|| renderer.default_size());
/// The appearance of a pick list.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The text [`Color`] of the pick list.
pub text_color: Color,
/// The placeholder [`Color`] of the pick list.
pub placeholder_color: Color,
/// The handle [`Color`] of the pick list.
pub handle_color: Color,
/// The [`Background`] of the pick list.
pub background: Background,
/// The [`Border`] of the pick list.
pub border: Border,
}
state.options.resize_with(options.len(), Default::default);
/// The styles of the different parts of a [`PickList`].
#[derive(Debug, PartialEq, Eq)]
pub struct Style<Theme> {
/// The style of the [`PickList`] itself.
pub field: fn(&Theme, Status) -> Appearance,
let option_text = Text {
content: "",
bounds: Size::new(
f32::INFINITY,
text_line_height.to_absolute(text_size).into(),
),
size: text_size,
line_height: text_line_height,
font,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
shaping: text_shaping,
/// The style of the [`Menu`] of the pick list.
pub menu: menu::Style<Theme>,
}
impl Style<Theme> {
/// The default style of a [`PickList`] with the built-in [`Theme`].
pub const DEFAULT: Self = Self {
field: default,
menu: menu::Style::<Theme>::DEFAULT,
};
for (option, paragraph) in options.iter().zip(state.options.iter_mut()) {
let label = option.to_string();
paragraph.update(Text {
content: &label,
..option_text
});
}
if let Some(placeholder) = placeholder {
state.placeholder.update(Text {
content: placeholder,
..option_text
});
}
let max_width = match width {
Length::Shrink => {
let labels_width =
state.options.iter().fold(0.0, |width, paragraph| {
f32::max(width, paragraph.min_width())
});
labels_width.max(
placeholder
.map(|_| state.placeholder.min_width())
.unwrap_or(0.0),
)
}
_ => 0.0,
};
let size = {
let intrinsic = Size::new(
max_width + text_size.0 + padding.left,
f32::from(text_line_height.to_absolute(text_size)),
);
limits
.width(width)
.shrink(padding)
.resolve(width, Length::Shrink, intrinsic)
.expand(padding)
};
layout::Node::new(size)
}
/// Processes an [`Event`] and updates the [`State`] of a [`PickList`]
/// accordingly.
pub fn update<'a, T, P, Message>(
event: Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>,
on_select: &dyn Fn(T) -> Message,
on_open: Option<&Message>,
on_close: Option<&Message>,
selected: Option<&T>,
options: &[T],
state: impl FnOnce() -> &'a mut State<P>,
) -> event::Status
where
T: PartialEq + Clone + 'a,
P: text::Paragraph + 'a,
Message: Clone,
{
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
let state = state();
if state.is_open {
// Event wasn't processed by overlay, so cursor was clicked either outside it's
// bounds or on the drop-down, either way we close the overlay.
state.is_open = false;
if let Some(on_close) = on_close {
shell.publish(on_close.clone());
}
event::Status::Captured
} else if cursor.is_over(layout.bounds()) {
state.is_open = true;
state.hovered_option =
options.iter().position(|option| Some(option) == selected);
if let Some(on_open) = on_open {
shell.publish(on_open.clone());
}
event::Status::Captured
} else {
event::Status::Ignored
}
}
Event::Mouse(mouse::Event::WheelScrolled {
delta: mouse::ScrollDelta::Lines { y, .. },
}) => {
let state = state();
if state.keyboard_modifiers.command()
&& cursor.is_over(layout.bounds())
&& !state.is_open
{
fn find_next<'a, T: PartialEq>(
selected: &'a T,
mut options: impl Iterator<Item = &'a T>,
) -> Option<&'a T> {
let _ = options.find(|&option| option == selected);
options.next()
}
let next_option = if y < 0.0 {
if let Some(selected) = selected {
find_next(selected, options.iter())
} else {
options.first()
}
} else if y > 0.0 {
if let Some(selected) = selected {
find_next(selected, options.iter().rev())
} else {
options.last()
}
} else {
None
};
if let Some(next_option) = next_option {
shell.publish((on_select)(next_option.clone()));
}
event::Status::Captured
} else {
event::Status::Ignored
}
}
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
let state = state();
state.keyboard_modifiers = modifiers;
event::Status::Ignored
}
_ => event::Status::Ignored,
impl<Theme> Clone for Style<Theme> {
fn clone(&self) -> Self {
*self
}
}
/// Returns the current [`mouse::Interaction`] of a [`PickList`].
pub fn mouse_interaction(
layout: Layout<'_>,
cursor: mouse::Cursor,
) -> mouse::Interaction {
let bounds = layout.bounds();
let is_mouse_over = cursor.is_over(bounds);
impl<Theme> Copy for Style<Theme> {}
if is_mouse_over {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
/// The default style of a [`PickList`].
pub trait DefaultStyle: Sized {
/// Returns the default style of a [`PickList`].
fn default_style() -> Style<Self>;
}
impl DefaultStyle for Theme {
fn default_style() -> Style<Self> {
Style::<Self>::DEFAULT
}
}
/// Returns the current overlay of a [`PickList`].
pub fn overlay<'a, T, Message, Theme, Renderer>(
layout: Layout<'_>,
translation: Vector,
state: &'a mut State<Renderer::Paragraph>,
padding: Padding,
text_size: Option<Pixels>,
text_shaping: text::Shaping,
font: Renderer::Font,
options: &'a [T],
on_selected: &'a dyn Fn(T) -> Message,
style: <Theme as StyleSheet>::Style,
) -> Option<overlay::Element<'a, Message, Theme, Renderer>>
where
T: Clone + ToString,
Message: 'a,
Theme: StyleSheet
+ scrollable::StyleSheet
+ menu::StyleSheet
+ container::StyleSheet
+ 'a,
<Theme as menu::StyleSheet>::Style: From<<Theme as StyleSheet>::Style>,
Renderer: text::Renderer + 'a,
{
if state.is_open {
let bounds = layout.bounds();
/// The default style of the field of a [`PickList`].
pub fn default(theme: &Theme, status: Status) -> Appearance {
let palette = theme.extended_palette();
let mut menu = Menu::new(
&mut state.menu,
options,
&mut state.hovered_option,
|option| {
state.is_open = false;
(on_selected)(option)
let active = Appearance {
text_color: palette.background.weak.text,
background: palette.background.weak.color.into(),
placeholder_color: palette.background.strong.color,
handle_color: palette.background.weak.text,
border: Border {
radius: 2.0.into(),
width: 1.0,
color: palette.background.strong.color,
},
None,
)
.width(bounds.width)
.padding(padding)
.font(font)
.text_shaping(text_shaping)
.style(style);
if let Some(text_size) = text_size {
menu = menu.text_size(text_size);
}
Some(menu.overlay(layout.position() + translation, bounds.height))
} else {
None
}
}
/// Draws a [`PickList`].
pub fn draw<'a, T, Theme, Renderer>(
renderer: &mut Renderer,
theme: &Theme,
layout: Layout<'_>,
cursor: mouse::Cursor,
padding: Padding,
text_size: Option<Pixels>,
text_line_height: text::LineHeight,
text_shaping: text::Shaping,
font: Renderer::Font,
placeholder: Option<&str>,
selected: Option<&T>,
handle: &Handle<Renderer::Font>,
style: &Theme::Style,
state: impl FnOnce() -> &'a State<Renderer::Paragraph>,
viewport: &Rectangle,
) where
Renderer: text::Renderer,
Theme: StyleSheet,
T: ToString + 'a,
{
let bounds = layout.bounds();
let is_mouse_over = cursor.is_over(bounds);
let is_selected = selected.is_some();
let style = if is_mouse_over {
theme.hovered(style)
} else {
theme.active(style)
};
renderer.fill_quad(
renderer::Quad {
bounds,
border: style.border,
..renderer::Quad::default()
match status {
Status::Active => active,
Status::Hovered | Status::Opened => Appearance {
border: Border {
color: palette.primary.strong.color,
..active.border
},
style.background,
);
let handle = match handle {
Handle::Arrow { size } => Some((
Renderer::ICON_FONT,
Renderer::ARROW_DOWN_ICON,
*size,
text::LineHeight::default(),
text::Shaping::Basic,
)),
Handle::Static(Icon {
font,
code_point,
size,
line_height,
shaping,
}) => Some((*font, *code_point, *size, *line_height, *shaping)),
Handle::Dynamic { open, closed } => {
if state().is_open {
Some((
open.font,
open.code_point,
open.size,
open.line_height,
open.shaping,
))
} else {
Some((
closed.font,
closed.code_point,
closed.size,
closed.line_height,
closed.shaping,
))
}
}
Handle::None => None,
};
if let Some((font, code_point, size, line_height, shaping)) = handle {
let size = size.unwrap_or_else(|| renderer.default_size());
renderer.fill_text(
Text {
content: &code_point.to_string(),
size,
line_height,
font,
bounds: Size::new(
bounds.width,
f32::from(line_height.to_absolute(size)),
),
horizontal_alignment: alignment::Horizontal::Right,
vertical_alignment: alignment::Vertical::Center,
shaping,
..active
},
Point::new(
bounds.x + bounds.width - padding.horizontal(),
bounds.center_y(),
),
style.handle_color,
*viewport,
);
}
let label = selected.map(ToString::to_string);
if let Some(label) = label.as_deref().or(placeholder) {
let text_size = text_size.unwrap_or_else(|| renderer.default_size());
renderer.fill_text(
Text {
content: label,
size: text_size,
line_height: text_line_height,
font,
bounds: Size::new(
bounds.width - padding.horizontal(),
f32::from(text_line_height.to_absolute(text_size)),
),
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
shaping: text_shaping,
},
Point::new(bounds.x + padding.left, bounds.center_y()),
if is_selected {
style.text_color
} else {
style.placeholder_color
},
*viewport,
);
}
}

View file

@ -3,17 +3,17 @@ use crate::core::layout;
use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget::Tree;
use crate::core::{Border, Element, Layout, Length, Rectangle, Size, Widget};
use crate::core::{
Background, Border, Element, Layout, Length, Rectangle, Size, Theme, Widget,
};
use std::ops::RangeInclusive;
pub use iced_style::progress_bar::{Appearance, StyleSheet};
/// A bar that displays progress.
///
/// # Example
/// ```no_run
/// # type ProgressBar = iced_widget::ProgressBar<iced_widget::style::Theme>;
/// # type ProgressBar = iced_widget::ProgressBar;
/// #
/// let value = 50.0;
///
@ -22,21 +22,15 @@ pub use iced_style::progress_bar::{Appearance, StyleSheet};
///
/// ![Progress bar drawn with `iced_wgpu`](https://user-images.githubusercontent.com/18618951/71662391-a316c200-2d51-11ea-9cef-52758cab85e3.png)
#[allow(missing_debug_implementations)]
pub struct ProgressBar<Theme = crate::Theme>
where
Theme: StyleSheet,
{
pub struct ProgressBar<Theme = crate::Theme> {
range: RangeInclusive<f32>,
value: f32,
width: Length,
height: Option<Length>,
style: Theme::Style,
style: Style<Theme>,
}
impl<Theme> ProgressBar<Theme>
where
Theme: StyleSheet,
{
impl<Theme> ProgressBar<Theme> {
/// The default height of a [`ProgressBar`].
pub const DEFAULT_HEIGHT: f32 = 30.0;
@ -45,13 +39,16 @@ where
/// It expects:
/// * an inclusive range of possible values
/// * the current value of the [`ProgressBar`]
pub fn new(range: RangeInclusive<f32>, value: f32) -> Self {
pub fn new(range: RangeInclusive<f32>, value: f32) -> Self
where
Theme: DefaultStyle,
{
ProgressBar {
value: value.clamp(*range.start(), *range.end()),
range,
width: Length::Fill,
height: None,
style: Default::default(),
style: Theme::default_style(),
}
}
@ -68,7 +65,7 @@ where
}
/// Sets the style of the [`ProgressBar`].
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
pub fn style(mut self, style: fn(&Theme) -> Appearance) -> Self {
self.style = style.into();
self
}
@ -78,7 +75,6 @@ impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for ProgressBar<Theme>
where
Renderer: crate::core::Renderer,
Theme: StyleSheet,
{
fn size(&self) -> Size<Length> {
Size {
@ -120,15 +116,15 @@ where
/ (range_end - range_start)
};
let style = theme.appearance(&self.style);
let appearance = (self.style)(theme);
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle { ..bounds },
border: Border::with_radius(style.border_radius),
border: appearance.border,
..renderer::Quad::default()
},
style.background,
appearance.background,
);
if active_progress_width > 0.0 {
@ -138,10 +134,10 @@ where
width: active_progress_width,
..bounds
},
border: Border::with_radius(style.border_radius),
border: Border::rounded(appearance.border.radius),
..renderer::Quad::default()
},
style.bar,
appearance.bar,
);
}
}
@ -151,7 +147,7 @@ impl<'a, Message, Theme, Renderer> From<ProgressBar<Theme>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
Theme: StyleSheet + 'a,
Theme: 'a,
Renderer: 'a + crate::core::Renderer,
{
fn from(
@ -160,3 +156,80 @@ where
Element::new(progress_bar)
}
}
/// The appearance of a progress bar.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The [`Background`] of the progress bar.
pub background: Background,
/// The [`Background`] of the bar of the progress bar.
pub bar: Background,
/// The [`Border`] of the progress bar.
pub border: Border,
}
/// The style of a [`ProgressBar`].
pub type Style<Theme> = fn(&Theme) -> Appearance;
/// The default style of a [`ProgressBar`].
pub trait DefaultStyle {
/// Returns the default style of a [`ProgressBar`].
fn default_style() -> Style<Self>;
}
impl DefaultStyle for Theme {
fn default_style() -> Style<Self> {
primary
}
}
impl DefaultStyle for Appearance {
fn default_style() -> Style<Self> {
|appearance| *appearance
}
}
/// The primary style of a [`ProgressBar`].
pub fn primary(theme: &Theme) -> Appearance {
let palette = theme.extended_palette();
styled(
palette.background.strong.color,
palette.primary.strong.color,
)
}
/// The secondary style of a [`ProgressBar`].
pub fn secondary(theme: &Theme) -> Appearance {
let palette = theme.extended_palette();
styled(
palette.background.strong.color,
palette.secondary.base.color,
)
}
/// The success style of a [`ProgressBar`].
pub fn success(theme: &Theme) -> Appearance {
let palette = theme.extended_palette();
styled(palette.background.strong.color, palette.success.base.color)
}
/// The danger style of a [`ProgressBar`].
pub fn danger(theme: &Theme) -> Appearance {
let palette = theme.extended_palette();
styled(palette.background.strong.color, palette.danger.base.color)
}
fn styled(
background: impl Into<Background>,
bar: impl Into<Background>,
) -> Appearance {
Appearance {
background: background.into(),
bar: bar.into(),
border: Border::rounded(2),
}
}

View file

@ -5,7 +5,8 @@ use crate::core::mouse;
use crate::core::renderer::{self, Renderer as _};
use crate::core::widget::tree::{self, Tree};
use crate::core::{
Element, Layout, Length, Point, Rectangle, Size, Vector, Widget,
Color, Element, Layout, Length, Point, Rectangle, Size, Theme, Vector,
Widget,
};
use crate::graphics::geometry::Renderer as _;
use crate::Renderer;
@ -13,33 +14,28 @@ use crate::Renderer;
use std::cell::RefCell;
use thiserror::Error;
pub use crate::style::qr_code::{Appearance, StyleSheet};
const DEFAULT_CELL_SIZE: u16 = 4;
const QUIET_ZONE: usize = 2;
/// A type of matrix barcode consisting of squares arranged in a grid which
/// can be read by an imaging device, such as a camera.
#[derive(Debug)]
pub struct QRCode<'a, Theme = crate::Theme>
where
Theme: StyleSheet,
{
pub struct QRCode<'a, Theme = crate::Theme> {
data: &'a Data,
cell_size: u16,
style: Theme::Style,
style: Style<Theme>,
}
impl<'a, Theme> QRCode<'a, Theme>
where
Theme: StyleSheet,
{
impl<'a, Theme> QRCode<'a, Theme> {
/// Creates a new [`QRCode`] with the provided [`Data`].
pub fn new(data: &'a Data) -> Self {
pub fn new(data: &'a Data) -> Self
where
Theme: DefaultStyle,
{
Self {
data,
cell_size: DEFAULT_CELL_SIZE,
style: Default::default(),
style: Theme::default_style(),
}
}
@ -50,15 +46,14 @@ where
}
/// Sets the style of the [`QRCode`].
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
pub fn style(mut self, style: fn(&Theme) -> Appearance) -> Self {
self.style = style.into();
self
}
}
impl<'a, Message, Theme> Widget<Message, Theme, Renderer> for QRCode<'a, Theme>
where
Theme: StyleSheet,
impl<'a, Message, Theme> Widget<Message, Theme, Renderer>
for QRCode<'a, Theme>
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
@ -102,7 +97,7 @@ where
let bounds = layout.bounds();
let side_length = self.data.width + 2 * QUIET_ZONE;
let appearance = theme.appearance(&self.style);
let appearance = (self.style)(theme);
let mut last_appearance = state.last_appearance.borrow_mut();
if Some(appearance) != *last_appearance {
@ -156,7 +151,7 @@ where
impl<'a, Message, Theme> From<QRCode<'a, Theme>>
for Element<'a, Message, Theme, Renderer>
where
Theme: StyleSheet + 'a,
Theme: 'a,
{
fn from(qr_code: QRCode<'a, Theme>) -> Self {
Self::new(qr_code)
@ -330,3 +325,43 @@ impl From<qrcode::types::QrError> for Error {
struct State {
last_appearance: RefCell<Option<Appearance>>,
}
/// The appearance of a QR code.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Appearance {
/// The color of the QR code data cells
pub cell: Color,
/// The color of the QR code background
pub background: Color,
}
/// The style of a [`QRCode`].
pub type Style<Theme> = fn(&Theme) -> Appearance;
/// The default style of a [`QRCode`].
pub trait DefaultStyle {
/// Returns the default style of a [`QRCode`].
fn default_style() -> Style<Self>;
}
impl DefaultStyle for Theme {
fn default_style() -> Style<Self> {
default
}
}
impl DefaultStyle for Appearance {
fn default_style() -> Style<Self> {
|appearance| *appearance
}
}
/// The default style of a [`QRCode`].
pub fn default(theme: &Theme) -> Appearance {
let palette = theme.palette();
Appearance {
cell: palette.text,
background: palette.background,
}
}

View file

@ -9,18 +9,16 @@ use crate::core::touch;
use crate::core::widget;
use crate::core::widget::tree::{self, Tree};
use crate::core::{
Border, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size,
Widget,
Background, Border, Clipboard, Color, Element, Layout, Length, Pixels,
Rectangle, Shell, Size, Theme, Widget,
};
pub use iced_style::radio::{Appearance, StyleSheet};
/// A circular button representing a choice.
///
/// # Example
/// ```no_run
/// # type Radio<Message> =
/// # iced_widget::Radio<Message, iced_widget::style::Theme, iced_widget::renderer::Renderer>;
/// # iced_widget::Radio<Message, iced_widget::Theme, iced_widget::renderer::Renderer>;
/// #
/// # use iced_widget::column;
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -71,7 +69,6 @@ pub use iced_style::radio::{Appearance, StyleSheet};
#[allow(missing_debug_implementations)]
pub struct Radio<Message, Theme = crate::Theme, Renderer = crate::Renderer>
where
Theme: StyleSheet,
Renderer: text::Renderer,
{
is_selected: bool,
@ -84,20 +81,19 @@ where
text_line_height: text::LineHeight,
text_shaping: text::Shaping,
font: Option<Renderer::Font>,
style: Theme::Style,
style: Style<Theme>,
}
impl<Message, Theme, Renderer> Radio<Message, Theme, Renderer>
where
Message: Clone,
Theme: StyleSheet,
Renderer: text::Renderer,
{
/// The default size of a [`Radio`] button.
pub const DEFAULT_SIZE: f32 = 28.0;
pub const DEFAULT_SIZE: f32 = 16.0;
/// The default spacing of a [`Radio`] button.
pub const DEFAULT_SPACING: f32 = 15.0;
pub const DEFAULT_SPACING: f32 = 8.0;
/// Creates a new [`Radio`] button.
///
@ -114,6 +110,7 @@ where
f: F,
) -> Self
where
Theme: DefaultStyle,
V: Eq + Copy,
F: FnOnce(V) -> Message,
{
@ -128,7 +125,7 @@ where
text_line_height: text::LineHeight::default(),
text_shaping: text::Shaping::Basic,
font: None,
style: Default::default(),
style: Theme::default_style(),
}
}
@ -178,8 +175,8 @@ where
}
/// Sets the style of the [`Radio`] button.
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
self.style = style.into();
pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self {
self.style = style;
self
}
}
@ -188,7 +185,6 @@ impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Radio<Message, Theme, Renderer>
where
Message: Clone,
Theme: StyleSheet + crate::text::StyleSheet,
Renderer: text::Renderer,
{
fn tag(&self) -> tree::Tag {
@ -291,15 +287,18 @@ where
viewport: &Rectangle,
) {
let is_mouse_over = cursor.is_over(layout.bounds());
let is_selected = self.is_selected;
let mut children = layout.children();
let custom_style = if is_mouse_over {
theme.hovered(&self.style, self.is_selected)
let status = if is_mouse_over {
Status::Hovered { is_selected }
} else {
theme.active(&self.style, self.is_selected)
Status::Active { is_selected }
};
let appearance = (self.style)(theme, status);
{
let layout = children.next().unwrap();
let bounds = layout.bounds();
@ -312,12 +311,12 @@ where
bounds,
border: Border {
radius: (size / 2.0).into(),
width: custom_style.border_width,
color: custom_style.border_color,
width: appearance.border_width,
color: appearance.border_color,
},
..renderer::Quad::default()
},
custom_style.background,
appearance.background,
);
if self.is_selected {
@ -329,10 +328,10 @@ where
width: bounds.width - dot_size,
height: bounds.height - dot_size,
},
border: Border::with_radius(dot_size / 2.0),
border: Border::rounded(dot_size / 2.0),
..renderer::Quad::default()
},
custom_style.dot_color,
appearance.dot_color,
);
}
}
@ -346,7 +345,7 @@ where
label_layout,
tree.state.downcast_ref(),
crate::text::Appearance {
color: custom_style.text_color,
color: appearance.text_color,
},
viewport,
);
@ -358,7 +357,7 @@ impl<'a, Message, Theme, Renderer> From<Radio<Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a + Clone,
Theme: StyleSheet + crate::text::StyleSheet + 'a,
Theme: 'a,
Renderer: 'a + text::Renderer,
{
fn from(
@ -367,3 +366,76 @@ where
Element::new(radio)
}
}
/// The possible status of a [`Radio`] button.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Status {
/// The [`Radio`] button can be interacted with.
Active {
/// Indicates whether the [`Radio`] button is currently selected.
is_selected: bool,
},
/// The [`Radio`] button is being hovered.
Hovered {
/// Indicates whether the [`Radio`] button is currently selected.
is_selected: bool,
},
}
/// The appearance of a radio button.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The [`Background`] of the radio button.
pub background: Background,
/// The [`Color`] of the dot of the radio button.
pub dot_color: Color,
/// The border width of the radio button.
pub border_width: f32,
/// The border [`Color`] of the radio button.
pub border_color: Color,
/// The text [`Color`] of the radio button.
pub text_color: Option<Color>,
}
/// The style of a [`Radio`] button.
pub type Style<Theme> = fn(&Theme, Status) -> Appearance;
/// The default style of a [`Radio`] button.
pub trait DefaultStyle {
/// Returns the default style of a [`Radio`] button.
fn default_style() -> Style<Self>;
}
impl DefaultStyle for Theme {
fn default_style() -> Style<Self> {
default
}
}
impl DefaultStyle for Appearance {
fn default_style() -> Style<Self> {
|appearance, _status| *appearance
}
}
/// The default style of a [`Radio`] button.
pub fn default(theme: &Theme, status: Status) -> Appearance {
let palette = theme.extended_palette();
let active = Appearance {
background: Color::TRANSPARENT.into(),
dot_color: palette.primary.strong.color,
border_width: 1.0,
border_color: palette.primary.strong.color,
text_color: None,
};
match status {
Status::Active { .. } => active,
Status::Hovered { .. } => Appearance {
dot_color: palette.primary.strong.color,
background: palette.primary.weak.color.into(),
..active
},
}
}

View file

@ -1,53 +1,52 @@
//! Display a horizontal or vertical rule for dividing content.
use crate::core::border::{self, Border};
use crate::core::layout;
use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget::Tree;
use crate::core::{
Border, Element, Layout, Length, Pixels, Rectangle, Size, Widget,
Color, Element, Layout, Length, Pixels, Rectangle, Size, Theme, Widget,
};
pub use crate::style::rule::{Appearance, FillMode, StyleSheet};
/// Display a horizontal or vertical rule for dividing content.
#[allow(missing_debug_implementations)]
pub struct Rule<Theme = crate::Theme>
where
Theme: StyleSheet,
{
pub struct Rule<Theme = crate::Theme> {
width: Length,
height: Length,
is_horizontal: bool,
style: Theme::Style,
style: Style<Theme>,
}
impl<Theme> Rule<Theme>
where
Theme: StyleSheet,
{
impl<Theme> Rule<Theme> {
/// Creates a horizontal [`Rule`] with the given height.
pub fn horizontal(height: impl Into<Pixels>) -> Self {
pub fn horizontal(height: impl Into<Pixels>) -> Self
where
Theme: DefaultStyle,
{
Rule {
width: Length::Fill,
height: Length::Fixed(height.into().0),
is_horizontal: true,
style: Default::default(),
style: Theme::default_style(),
}
}
/// Creates a vertical [`Rule`] with the given width.
pub fn vertical(width: impl Into<Pixels>) -> Self {
pub fn vertical(width: impl Into<Pixels>) -> Self
where
Theme: DefaultStyle,
{
Rule {
width: Length::Fixed(width.into().0),
height: Length::Fill,
is_horizontal: false,
style: Default::default(),
style: Theme::default_style(),
}
}
/// Sets the style of the [`Rule`].
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
self.style = style.into();
pub fn style(mut self, style: fn(&Theme) -> Appearance) -> Self {
self.style = style;
self
}
}
@ -55,7 +54,6 @@ where
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Rule<Theme>
where
Renderer: crate::core::Renderer,
Theme: StyleSheet,
{
fn size(&self) -> Size<Length> {
Size {
@ -84,34 +82,35 @@ where
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
let style = theme.appearance(&self.style);
let appearance = (self.style)(theme);
let bounds = if self.is_horizontal {
let line_y = (bounds.y + (bounds.height / 2.0)
- (style.width as f32 / 2.0))
- (appearance.width as f32 / 2.0))
.round();
let (offset, line_width) = style.fill_mode.fill(bounds.width);
let (offset, line_width) = appearance.fill_mode.fill(bounds.width);
let line_x = bounds.x + offset;
Rectangle {
x: line_x,
y: line_y,
width: line_width,
height: style.width as f32,
height: appearance.width as f32,
}
} else {
let line_x = (bounds.x + (bounds.width / 2.0)
- (style.width as f32 / 2.0))
- (appearance.width as f32 / 2.0))
.round();
let (offset, line_height) = style.fill_mode.fill(bounds.height);
let (offset, line_height) =
appearance.fill_mode.fill(bounds.height);
let line_y = bounds.y + offset;
Rectangle {
x: line_x,
y: line_y,
width: style.width as f32,
width: appearance.width as f32,
height: line_height,
}
};
@ -119,10 +118,10 @@ where
renderer.fill_quad(
renderer::Quad {
bounds,
border: Border::with_radius(style.radius),
border: Border::rounded(appearance.radius),
..renderer::Quad::default()
},
style.color,
appearance.color,
);
}
}
@ -131,10 +130,120 @@ impl<'a, Message, Theme, Renderer> From<Rule<Theme>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
Theme: StyleSheet + 'a,
Theme: 'a,
Renderer: 'a + crate::core::Renderer,
{
fn from(rule: Rule<Theme>) -> Element<'a, Message, Theme, Renderer> {
Element::new(rule)
}
}
/// The appearance of a rule.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The color of the rule.
pub color: Color,
/// The width (thickness) of the rule line.
pub width: u16,
/// The radius of the line corners.
pub radius: border::Radius,
/// The [`FillMode`] of the rule.
pub fill_mode: FillMode,
}
/// The fill mode of a rule.
#[derive(Debug, Clone, Copy)]
pub enum FillMode {
/// Fill the whole length of the container.
Full,
/// Fill a percent of the length of the container. The rule
/// will be centered in that container.
///
/// The range is `[0.0, 100.0]`.
Percent(f32),
/// Uniform offset from each end, length units.
Padded(u16),
/// Different offset on each end of the rule, length units.
/// First = top or left.
AsymmetricPadding(u16, u16),
}
impl FillMode {
/// Return the starting offset and length of the rule.
///
/// * `space` - The space to fill.
///
/// # Returns
///
/// * (`starting_offset`, `length`)
pub fn fill(&self, space: f32) -> (f32, f32) {
match *self {
FillMode::Full => (0.0, space),
FillMode::Percent(percent) => {
if percent >= 100.0 {
(0.0, space)
} else {
let percent_width = (space * percent / 100.0).round();
(((space - percent_width) / 2.0).round(), percent_width)
}
}
FillMode::Padded(padding) => {
if padding == 0 {
(0.0, space)
} else {
let padding = padding as f32;
let mut line_width = space - (padding * 2.0);
if line_width < 0.0 {
line_width = 0.0;
}
(padding, line_width)
}
}
FillMode::AsymmetricPadding(first_pad, second_pad) => {
let first_pad = first_pad as f32;
let second_pad = second_pad as f32;
let mut line_width = space - first_pad - second_pad;
if line_width < 0.0 {
line_width = 0.0;
}
(first_pad, line_width)
}
}
}
}
/// The style of a [`Rule`].
pub type Style<Theme> = fn(&Theme) -> Appearance;
/// The default style of a [`Rule`].
pub trait DefaultStyle {
/// Returns the default style of a [`Rule`].
fn default_style() -> Style<Self>;
}
impl DefaultStyle for Theme {
fn default_style() -> Style<Self> {
default
}
}
impl DefaultStyle for Appearance {
fn default_style() -> Style<Self> {
|appearance| *appearance
}
}
/// The default styling of a [`Rule`].
pub fn default(theme: &Theme) -> Appearance {
let palette = theme.extended_palette();
Appearance {
color: palette.background.strong.color,
width: 1,
radius: 0.0.into(),
fill_mode: FillMode::Full,
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,5 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`Slider`] has some local [`State`].
use crate::core::border;
use crate::core::event::{self, Event};
use crate::core::keyboard;
use crate::core::keyboard::key::{self, Key};
@ -10,16 +9,12 @@ use crate::core::renderer;
use crate::core::touch;
use crate::core::widget::tree::{self, Tree};
use crate::core::{
Border, Clipboard, Element, Layout, Length, Pixels, Point, Rectangle,
Shell, Size, Widget,
Border, Clipboard, Color, Element, Layout, Length, Pixels, Point,
Rectangle, Shell, Size, Theme, Widget,
};
use std::ops::RangeInclusive;
pub use iced_style::slider::{
Appearance, Handle, HandleShape, Rail, StyleSheet,
};
/// An horizontal bar and a handle that selects a single value from a range of
/// values.
///
@ -30,8 +25,7 @@ pub use iced_style::slider::{
///
/// # Example
/// ```no_run
/// # type Slider<'a, T, Message> =
/// # iced_widget::Slider<'a, Message, T, iced_widget::style::Theme>;
/// # type Slider<'a, T, Message> = iced_widget::Slider<'a, Message, T>;
/// #
/// #[derive(Clone)]
/// pub enum Message {
@ -45,10 +39,7 @@ pub use iced_style::slider::{
///
/// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true)
#[allow(missing_debug_implementations)]
pub struct Slider<'a, T, Message, Theme = crate::Theme>
where
Theme: StyleSheet,
{
pub struct Slider<'a, T, Message, Theme = crate::Theme> {
range: RangeInclusive<T>,
step: T,
shift_step: Option<T>,
@ -58,17 +49,16 @@ where
on_release: Option<Message>,
width: Length,
height: f32,
style: Theme::Style,
style: Style<Theme>,
}
impl<'a, T, Message, Theme> Slider<'a, T, Message, Theme>
where
T: Copy + From<u8> + PartialOrd,
Message: Clone,
Theme: StyleSheet,
{
/// The default height of a [`Slider`].
pub const DEFAULT_HEIGHT: f32 = 22.0;
pub const DEFAULT_HEIGHT: f32 = 16.0;
/// Creates a new [`Slider`].
///
@ -80,6 +70,7 @@ where
/// `Message`.
pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self
where
Theme: DefaultStyle,
F: 'a + Fn(T) -> Message,
{
let value = if value >= *range.start() {
@ -104,7 +95,7 @@ where
on_release: None,
width: Length::Fill,
height: Self::DEFAULT_HEIGHT,
style: Default::default(),
style: Theme::default_style(),
}
}
@ -140,7 +131,7 @@ where
}
/// Sets the style of the [`Slider`].
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self {
self.style = style.into();
self
}
@ -165,7 +156,6 @@ impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone,
Theme: StyleSheet,
Renderer: crate::core::Renderer,
{
fn tag(&self) -> tree::Tag {
@ -173,7 +163,7 @@ where
}
fn state(&self) -> tree::State {
tree::State::new(State::new())
tree::State::new(State::default())
}
fn size(&self) -> Size<Length> {
@ -203,110 +193,27 @@ where
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
) -> event::Status {
update(
event,
layout,
cursor,
shell,
tree.state.downcast_mut::<State>(),
&mut self.value,
self.default,
&self.range,
self.step,
self.shift_step,
self.on_change.as_ref(),
&self.on_release,
)
}
let state = tree.state.downcast_mut::<State>();
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
_style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
draw(
renderer,
layout,
cursor,
tree.state.downcast_ref::<State>(),
self.value,
&self.range,
theme,
&self.style,
);
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(layout, cursor, tree.state.downcast_ref::<State>())
}
}
impl<'a, T, Message, Theme, Renderer> From<Slider<'a, T, Message, Theme>>
for Element<'a, Message, Theme, Renderer>
where
T: Copy + Into<f64> + num_traits::FromPrimitive + 'a,
Message: Clone + 'a,
Theme: StyleSheet + 'a,
Renderer: crate::core::Renderer + 'a,
{
fn from(
slider: Slider<'a, T, Message, Theme>,
) -> Element<'a, Message, Theme, Renderer> {
Element::new(slider)
}
}
/// Processes an [`Event`] and updates the [`State`] of a [`Slider`]
/// accordingly.
pub fn update<Message, T>(
event: Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>,
state: &mut State,
value: &mut T,
default: Option<T>,
range: &RangeInclusive<T>,
step: T,
shift_step: Option<T>,
on_change: &dyn Fn(T) -> Message,
on_release: &Option<Message>,
) -> event::Status
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone,
{
let is_dragging = state.is_dragging;
let current_value = *value;
let current_value = self.value;
let locate = |cursor_position: Point| -> Option<T> {
let bounds = layout.bounds();
let new_value = if cursor_position.x <= bounds.x {
Some(*range.start())
Some(*self.range.start())
} else if cursor_position.x >= bounds.x + bounds.width {
Some(*range.end())
Some(*self.range.end())
} else {
let step = if state.keyboard_modifiers.shift() {
shift_step.unwrap_or(step)
self.shift_step.unwrap_or(self.step)
} else {
step
self.step
}
.into();
let start = (*range.start()).into();
let end = (*range.end()).into();
let start = (*self.range.start()).into();
let end = (*self.range.end()).into();
let percent = f64::from(cursor_position.x - bounds.x)
/ f64::from(bounds.width);
@ -322,17 +229,17 @@ where
let increment = |value: T| -> Option<T> {
let step = if state.keyboard_modifiers.shift() {
shift_step.unwrap_or(step)
self.shift_step.unwrap_or(self.step)
} else {
step
self.step
}
.into();
let steps = (value.into() / step).round();
let new_value = step * (steps + 1.0);
if new_value > (*range.end()).into() {
return Some(*range.end());
if new_value > (*self.range.end()).into() {
return Some(*self.range.end());
}
T::from_f64(new_value)
@ -340,37 +247,38 @@ where
let decrement = |value: T| -> Option<T> {
let step = if state.keyboard_modifiers.shift() {
shift_step.unwrap_or(step)
self.shift_step.unwrap_or(self.step)
} else {
step
self.step
}
.into();
let steps = (value.into() / step).round();
let new_value = step * (steps - 1.0);
if new_value < (*range.start()).into() {
return Some(*range.start());
if new_value < (*self.range.start()).into() {
return Some(*self.range.start());
}
T::from_f64(new_value)
};
let change = |new_value: T| {
if ((*value).into() - new_value.into()).abs() > f64::EPSILON {
shell.publish((on_change)(new_value));
if (self.value.into() - new_value.into()).abs() > f64::EPSILON {
shell.publish((self.on_change)(new_value));
*value = new_value;
self.value = new_value;
}
};
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
if let Some(cursor_position) = cursor.position_over(layout.bounds())
if let Some(cursor_position) =
cursor.position_over(layout.bounds())
{
if state.keyboard_modifiers.command() {
let _ = default.map(change);
let _ = self.default.map(change);
state.is_dragging = false;
} else {
let _ = locate(cursor_position).map(change);
@ -384,7 +292,7 @@ where
| Event::Touch(touch::Event::FingerLifted { .. })
| Event::Touch(touch::Event::FingerLost { .. }) => {
if is_dragging {
if let Some(on_release) = on_release.clone() {
if let Some(on_release) = self.on_release.clone() {
shell.publish(on_release);
}
state.is_dragging = false;
@ -424,31 +332,30 @@ where
event::Status::Ignored
}
/// Draws a [`Slider`].
pub fn draw<T, Theme, Renderer>(
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
_style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
state: &State,
value: T,
range: &RangeInclusive<T>,
theme: &Theme,
style: &Theme::Style,
) where
T: Into<f64> + Copy,
Theme: StyleSheet,
Renderer: crate::core::Renderer,
{
_viewport: &Rectangle,
) {
let state = tree.state.downcast_ref::<State>();
let bounds = layout.bounds();
let is_mouse_over = cursor.is_over(bounds);
let style = if state.is_dragging {
theme.dragging(style)
let style = (self.style)(
theme,
if state.is_dragging {
Status::Dragged
} else if is_mouse_over {
theme.hovered(style)
Status::Hovered
} else {
theme.active(style)
};
Status::Active
},
);
let (handle_width, handle_height, handle_border_radius) =
match style.handle.shape {
@ -461,9 +368,9 @@ pub fn draw<T, Theme, Renderer>(
} => (f32::from(width), bounds.height, border_radius),
};
let value = value.into() as f32;
let value = self.value.into() as f32;
let (range_start, range_end) = {
let (start, end) = range.clone().into_inner();
let (start, end) = self.range.clone().into_inner();
(start.into() as f32, end.into() as f32)
};
@ -485,7 +392,7 @@ pub fn draw<T, Theme, Renderer>(
width: offset + handle_width / 2.0,
height: style.rail.width,
},
border: Border::with_radius(style.rail.border_radius),
border: Border::rounded(style.rail.border_radius),
..renderer::Quad::default()
},
style.rail.colors.0,
@ -499,7 +406,7 @@ pub fn draw<T, Theme, Renderer>(
width: bounds.width - offset - handle_width / 2.0,
height: style.rail.width,
},
border: Border::with_radius(style.rail.border_radius),
border: Border::rounded(style.rail.border_radius),
..renderer::Quad::default()
},
style.rail.colors.1,
@ -524,12 +431,15 @@ pub fn draw<T, Theme, Renderer>(
);
}
/// Computes the current [`mouse::Interaction`] of a [`Slider`].
pub fn mouse_interaction(
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor: mouse::Cursor,
state: &State,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
let state = tree.state.downcast_ref::<State>();
let bounds = layout.bounds();
let is_mouse_over = cursor.is_over(bounds);
@ -541,17 +451,143 @@ pub fn mouse_interaction(
mouse::Interaction::default()
}
}
}
impl<'a, T, Message, Theme, Renderer> From<Slider<'a, T, Message, Theme>>
for Element<'a, Message, Theme, Renderer>
where
T: Copy + Into<f64> + num_traits::FromPrimitive + 'a,
Message: Clone + 'a,
Theme: 'a,
Renderer: crate::core::Renderer + 'a,
{
fn from(
slider: Slider<'a, T, Message, Theme>,
) -> Element<'a, Message, Theme, Renderer> {
Element::new(slider)
}
}
/// The local state of a [`Slider`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct State {
struct State {
is_dragging: bool,
keyboard_modifiers: keyboard::Modifiers,
}
impl State {
/// Creates a new [`State`].
pub fn new() -> State {
State::default()
/// The possible status of a [`Slider`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Status {
/// The [`Slider`] can be interacted with.
Active,
/// The [`Slider`] is being hovered.
Hovered,
/// The [`Slider`] is being dragged.
Dragged,
}
/// The appearance of a slider.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The colors of the rail of the slider.
pub rail: Rail,
/// The appearance of the [`Handle`] of the slider.
pub handle: Handle,
}
impl Appearance {
/// Changes the [`HandleShape`] of the [`Appearance`] to a circle
/// with the given radius.
pub fn with_circular_handle(mut self, radius: impl Into<Pixels>) -> Self {
self.handle.shape = HandleShape::Circle {
radius: radius.into().0,
};
self
}
}
/// The appearance of a slider rail
#[derive(Debug, Clone, Copy)]
pub struct Rail {
/// The colors of the rail of the slider.
pub colors: (Color, Color),
/// The width of the stroke of a slider rail.
pub width: f32,
/// The border radius of the corners of the rail.
pub border_radius: border::Radius,
}
/// The appearance of the handle of a slider.
#[derive(Debug, Clone, Copy)]
pub struct Handle {
/// The shape of the handle.
pub shape: HandleShape,
/// The [`Color`] of the handle.
pub color: Color,
/// The border width of the handle.
pub border_width: f32,
/// The border [`Color`] of the handle.
pub border_color: Color,
}
/// The shape of the handle of a slider.
#[derive(Debug, Clone, Copy)]
pub enum HandleShape {
/// A circular handle.
Circle {
/// The radius of the circle.
radius: f32,
},
/// A rectangular shape.
Rectangle {
/// The width of the rectangle.
width: u16,
/// The border radius of the corners of the rectangle.
border_radius: border::Radius,
},
}
/// The style of a [`Slider`].
pub type Style<Theme> = fn(&Theme, Status) -> Appearance;
/// The default style of a [`Slider`].
pub trait DefaultStyle {
/// Returns the default style of a [`Slider`].
fn default_style() -> Style<Self>;
}
impl DefaultStyle for Theme {
fn default_style() -> Style<Self> {
default
}
}
impl DefaultStyle for Appearance {
fn default_style() -> Style<Self> {
|appearance, _status| *appearance
}
}
/// The default style of a [`Slider`].
pub fn default(theme: &Theme, status: Status) -> Appearance {
let palette = theme.extended_palette();
let color = match status {
Status::Active => palette.primary.strong.color,
Status::Hovered => palette.primary.base.color,
Status::Dragged => palette.primary.strong.color,
};
Appearance {
rail: Rail {
colors: (color, palette.secondary.base.color),
width: 4.0,
border_radius: 2.0.into(),
},
handle: Handle {
shape: HandleShape::Circle { radius: 7.0 },
color,
border_color: Color::TRANSPARENT,
border_width: 0.0,
},
}
}

View file

@ -5,13 +5,13 @@ use crate::core::renderer;
use crate::core::svg;
use crate::core::widget::Tree;
use crate::core::{
ContentFit, Element, Layout, Length, Rectangle, Size, Vector, Widget,
Color, ContentFit, Element, Layout, Length, Rectangle, Size, Theme, Vector,
Widget,
};
use std::path::PathBuf;
pub use crate::style::svg::{Appearance, StyleSheet};
pub use svg::Handle;
pub use crate::core::svg::Handle;
/// A vector graphics image.
///
@ -20,36 +20,36 @@ pub use svg::Handle;
/// [`Svg`] images can have a considerable rendering cost when resized,
/// specially when they are complex.
#[allow(missing_debug_implementations)]
pub struct Svg<Theme = crate::Theme>
where
Theme: StyleSheet,
{
pub struct Svg<Theme = crate::Theme> {
handle: Handle,
width: Length,
height: Length,
content_fit: ContentFit,
style: <Theme as StyleSheet>::Style,
style: Style<Theme>,
}
impl<Theme> Svg<Theme>
where
Theme: StyleSheet,
{
impl<Theme> Svg<Theme> {
/// Creates a new [`Svg`] from the given [`Handle`].
pub fn new(handle: impl Into<Handle>) -> Self {
pub fn new(handle: impl Into<Handle>) -> Self
where
Theme: DefaultStyle,
{
Svg {
handle: handle.into(),
width: Length::Fill,
height: Length::Shrink,
content_fit: ContentFit::Contain,
style: Default::default(),
style: Theme::default_style(),
}
}
/// Creates a new [`Svg`] that will display the contents of the file at the
/// provided path.
#[must_use]
pub fn from_path(path: impl Into<PathBuf>) -> Self {
pub fn from_path(path: impl Into<PathBuf>) -> Self
where
Theme: DefaultStyle,
{
Self::new(Handle::from_path(path))
}
@ -80,15 +80,14 @@ where
/// Sets the style variant of this [`Svg`].
#[must_use]
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
self.style = style.into();
pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self {
self.style = style;
self
}
}
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Svg<Theme>
where
Theme: iced_style::svg::StyleSheet,
Renderer: svg::Renderer,
{
fn size(&self) -> Size<Length> {
@ -158,12 +157,14 @@ where
..bounds
};
let appearance = if is_mouse_over {
theme.hovered(&self.style)
let status = if is_mouse_over {
Status::Hovered
} else {
theme.appearance(&self.style)
Status::Idle
};
let appearance = (self.style)(theme, status);
renderer.draw(
self.handle.clone(),
appearance.color,
@ -184,10 +185,51 @@ where
impl<'a, Message, Theme, Renderer> From<Svg<Theme>>
for Element<'a, Message, Theme, Renderer>
where
Theme: iced_style::svg::StyleSheet + 'a,
Theme: 'a,
Renderer: svg::Renderer + 'a,
{
fn from(icon: Svg<Theme>) -> Element<'a, Message, Theme, Renderer> {
Element::new(icon)
}
}
/// The possible status of an [`Svg`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Status {
/// The [`Svg`] is idle.
Idle,
/// The [`Svg`] is being hovered.
Hovered,
}
/// The appearance of an [`Svg`].
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Appearance {
/// The [`Color`] filter of an [`Svg`].
///
/// Useful for coloring a symbolic icon.
///
/// `None` keeps the original color.
pub color: Option<Color>,
}
/// The style of an [`Svg`].
pub type Style<Theme> = fn(&Theme, Status) -> Appearance;
/// The default style of an [`Svg`].
pub trait DefaultStyle {
/// Returns the default style of an [`Svg`].
fn default_style() -> Style<Self>;
}
impl DefaultStyle for Theme {
fn default_style() -> Style<Self> {
|_theme, _status| Appearance::default()
}
}
impl DefaultStyle for Appearance {
fn default_style() -> Style<Self> {
|appearance, _status| *appearance
}
}

View file

@ -11,7 +11,8 @@ use crate::core::text::highlighter::{self, Highlighter};
use crate::core::text::{self, LineHeight};
use crate::core::widget::{self, Widget};
use crate::core::{
Element, Length, Padding, Pixels, Rectangle, Shell, Size, Vector,
Background, Border, Color, Element, Length, Padding, Pixels, Rectangle,
Shell, Size, Theme, Vector,
};
use std::cell::RefCell;
@ -19,7 +20,6 @@ use std::fmt;
use std::ops::DerefMut;
use std::sync::Arc;
pub use crate::style::text_editor::{Appearance, StyleSheet};
pub use text::editor::{Action, Edit, Motion};
/// A multi-line text input.
@ -32,7 +32,6 @@ pub struct TextEditor<
Renderer = crate::Renderer,
> where
Highlighter: text::Highlighter,
Theme: StyleSheet,
Renderer: text::Renderer,
{
content: &'a Content<Renderer>,
@ -42,7 +41,7 @@ pub struct TextEditor<
width: Length,
height: Length,
padding: Padding,
style: Theme::Style,
style: Style<Theme>,
on_edit: Option<Box<dyn Fn(Action) -> Message + 'a>>,
highlighter_settings: Highlighter::Settings,
highlighter_format: fn(
@ -54,11 +53,13 @@ pub struct TextEditor<
impl<'a, Message, Theme, Renderer>
TextEditor<'a, highlighter::PlainText, Message, Theme, Renderer>
where
Theme: StyleSheet,
Renderer: text::Renderer,
{
/// Creates new [`TextEditor`] with the given [`Content`].
pub fn new(content: &'a Content<Renderer>) -> Self {
pub fn new(content: &'a Content<Renderer>) -> Self
where
Theme: DefaultStyle,
{
Self {
content,
font: None,
@ -67,7 +68,7 @@ where
width: Length::Fill,
height: Length::Shrink,
padding: Padding::new(5.0),
style: Default::default(),
style: Theme::default_style(),
on_edit: None,
highlighter_settings: (),
highlighter_format: |_highlight, _theme| {
@ -81,7 +82,6 @@ impl<'a, Highlighter, Message, Theme, Renderer>
TextEditor<'a, Highlighter, Message, Theme, Renderer>
where
Highlighter: text::Highlighter,
Theme: StyleSheet,
Renderer: text::Renderer,
{
/// Sets the height of the [`TextEditor`].
@ -142,7 +142,7 @@ where
}
/// Sets the style of the [`TextEditor`].
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self {
self.style = style.into();
self
}
@ -306,7 +306,6 @@ impl<'a, Highlighter, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for TextEditor<'a, Highlighter, Message, Theme, Renderer>
where
Highlighter: text::Highlighter,
Theme: StyleSheet,
Renderer: text::Renderer,
{
fn tag(&self) -> widget::tree::Tag {
@ -496,16 +495,18 @@ where
let is_disabled = self.on_edit.is_none();
let is_mouse_over = cursor.is_over(bounds);
let appearance = if is_disabled {
theme.disabled(&self.style)
let status = if is_disabled {
Status::Disabled
} else if state.is_focused {
theme.focused(&self.style)
Status::Focused
} else if is_mouse_over {
theme.hovered(&self.style)
Status::Hovered
} else {
theme.active(&self.style)
Status::Active
};
let appearance = (self.style)(theme, status);
renderer.fill_quad(
renderer::Quad {
bounds,
@ -551,7 +552,7 @@ where
},
..renderer::Quad::default()
},
theme.value_color(&self.style),
appearance.value,
);
}
}
@ -564,7 +565,7 @@ where
bounds: range,
..renderer::Quad::default()
},
theme.selection_color(&self.style),
appearance.selection,
);
}
}
@ -600,7 +601,7 @@ impl<'a, Highlighter, Message, Theme, Renderer>
where
Highlighter: text::Highlighter,
Message: 'a,
Theme: StyleSheet + 'a,
Theme: 'a,
Renderer: text::Renderer,
{
fn from(
@ -776,3 +777,95 @@ mod platform {
}
}
}
/// The possible status of a [`TextEditor`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Status {
/// The [`TextEditor`] can be interacted with.
Active,
/// The [`TextEditor`] is being hovered.
Hovered,
/// The [`TextEditor`] is focused.
Focused,
/// The [`TextEditor`] cannot be interacted with.
Disabled,
}
/// The appearance of a text input.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The [`Background`] of the text input.
pub background: Background,
/// The [`Border`] of the text input.
pub border: Border,
/// The [`Color`] of the icon of the text input.
pub icon: Color,
/// The [`Color`] of the placeholder of the text input.
pub placeholder: Color,
/// The [`Color`] of the value of the text input.
pub value: Color,
/// The [`Color`] of the selection of the text input.
pub selection: Color,
}
/// The style of a [`TextEditor`].
pub type Style<Theme> = fn(&Theme, Status) -> Appearance;
/// The default style of a [`TextEditor`].
pub trait DefaultStyle {
/// Returns the default style of a [`TextEditor`].
fn default_style() -> Style<Self>;
}
impl DefaultStyle for Theme {
fn default_style() -> Style<Self> {
default
}
}
impl DefaultStyle for Appearance {
fn default_style() -> Style<Self> {
|appearance, _status| *appearance
}
}
/// The default style of a [`TextEditor`].
pub fn default(theme: &Theme, status: Status) -> Appearance {
let palette = theme.extended_palette();
let active = Appearance {
background: Background::Color(palette.background.base.color),
border: Border {
radius: 2.0.into(),
width: 1.0,
color: palette.background.strong.color,
},
icon: palette.background.weak.text,
placeholder: palette.background.strong.color,
value: palette.background.base.text,
selection: palette.primary.weak.color,
};
match status {
Status::Active => active,
Status::Hovered => Appearance {
border: Border {
color: palette.background.base.text,
..active.border
},
..active
},
Status::Focused => Appearance {
border: Border {
color: palette.primary.strong.color,
..active.border
},
..active
},
Status::Disabled => Appearance {
background: Background::Color(palette.background.weak.color),
value: active.placeholder,
..active
},
}
}

File diff suppressed because it is too large Load diff

View file

@ -7,58 +7,68 @@ use crate::core::renderer;
use crate::core::widget::tree::{self, Tree};
use crate::core::widget::Operation;
use crate::core::{
Background, Clipboard, Element, Layout, Length, Point, Rectangle, Shell,
Size, Vector, Widget,
Background, Clipboard, Color, Element, Layout, Length, Point, Rectangle,
Shell, Size, Vector, Widget,
};
use crate::style::application;
use std::marker::PhantomData;
/// A widget that applies any `Theme` to its contents.
///
/// This widget can be useful to leverage multiple `Theme`
/// types in an application.
#[allow(missing_debug_implementations)]
pub struct Themer<'a, Message, Theme, Renderer>
pub struct Themer<'a, Message, Theme, NewTheme, F, Renderer = crate::Renderer>
where
F: Fn(&Theme) -> NewTheme,
Renderer: crate::core::Renderer,
Theme: application::StyleSheet,
{
content: Element<'a, Message, Theme, Renderer>,
theme: Theme,
style: Theme::Style,
show_background: bool,
content: Element<'a, Message, NewTheme, Renderer>,
to_theme: F,
text_color: Option<fn(&NewTheme) -> Color>,
background: Option<fn(&NewTheme) -> Background>,
old_theme: PhantomData<Theme>,
}
impl<'a, Message, Theme, Renderer> Themer<'a, Message, Theme, Renderer>
impl<'a, Message, Theme, NewTheme, F, Renderer>
Themer<'a, Message, Theme, NewTheme, F, Renderer>
where
F: Fn(&Theme) -> NewTheme,
Renderer: crate::core::Renderer,
Theme: application::StyleSheet,
{
/// Creates an empty [`Themer`] that applies the given `Theme`
/// to the provided `content`.
pub fn new<T>(theme: Theme, content: T) -> Self
pub fn new<T>(to_theme: F, content: T) -> Self
where
T: Into<Element<'a, Message, Theme, Renderer>>,
T: Into<Element<'a, Message, NewTheme, Renderer>>,
{
Self {
content: content.into(),
theme,
style: Theme::Style::default(),
show_background: false,
to_theme,
text_color: None,
background: None,
old_theme: PhantomData,
}
}
/// Sets whether to draw the background color of the `Theme`.
pub fn background(mut self, background: bool) -> Self {
self.show_background = background;
/// Sets the default text [`Color`] of the [`Themer`].
pub fn text_color(mut self, f: fn(&NewTheme) -> Color) -> Self {
self.text_color = Some(f);
self
}
/// Sets the [`Background`] of the [`Themer`].
pub fn background(mut self, f: fn(&NewTheme) -> Background) -> Self {
self.background = Some(f);
self
}
}
impl<'a, AnyTheme, Message, Theme, Renderer> Widget<Message, AnyTheme, Renderer>
for Themer<'a, Message, Theme, Renderer>
impl<'a, Message, Theme, NewTheme, F, Renderer> Widget<Message, Theme, Renderer>
for Themer<'a, Message, Theme, NewTheme, F, Renderer>
where
F: Fn(&Theme) -> NewTheme,
Renderer: crate::core::Renderer,
Theme: application::StyleSheet,
{
fn tag(&self) -> tree::Tag {
self.content.as_widget().tag()
@ -134,38 +144,36 @@ where
&self,
tree: &Tree,
renderer: &mut Renderer,
_theme: &AnyTheme,
_style: &renderer::Style,
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
let appearance = self.theme.appearance(&self.style);
let theme = (self.to_theme)(theme);
if self.show_background {
if let Some(background) = self.background {
container::draw_background(
renderer,
&container::Appearance {
background: Some(Background::Color(
appearance.background_color,
)),
background: Some(background(&theme)),
..container::Appearance::default()
},
layout.bounds(),
);
}
self.content.as_widget().draw(
tree,
renderer,
&self.theme,
&renderer::Style {
text_color: appearance.text_color,
},
layout,
cursor,
viewport,
);
let style = if let Some(text_color) = self.text_color {
renderer::Style {
text_color: text_color(&theme),
}
} else {
*style
};
self.content
.as_widget()
.draw(tree, renderer, &theme, &style, layout, cursor, viewport);
}
fn overlay<'b>(
@ -174,15 +182,15 @@ where
layout: Layout<'_>,
renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, AnyTheme, Renderer>> {
struct Overlay<'a, Message, Theme, Renderer> {
theme: &'a Theme,
content: overlay::Element<'a, Message, Theme, Renderer>,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
struct Overlay<'a, Message, Theme, NewTheme, Renderer> {
to_theme: &'a dyn Fn(&Theme) -> NewTheme,
content: overlay::Element<'a, Message, NewTheme, Renderer>,
}
impl<'a, AnyTheme, Message, Theme, Renderer>
overlay::Overlay<Message, AnyTheme, Renderer>
for Overlay<'a, Message, Theme, Renderer>
impl<'a, Message, Theme, NewTheme, Renderer>
overlay::Overlay<Message, Theme, Renderer>
for Overlay<'a, Message, Theme, NewTheme, Renderer>
where
Renderer: crate::core::Renderer,
{
@ -197,13 +205,18 @@ where
fn draw(
&self,
renderer: &mut Renderer,
_theme: &AnyTheme,
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
) {
self.content
.draw(renderer, self.theme, style, layout, cursor);
self.content.draw(
renderer,
&(self.to_theme)(theme),
style,
layout,
cursor,
);
}
fn on_event(
@ -252,12 +265,12 @@ where
&'b mut self,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, AnyTheme, Renderer>>
) -> Option<overlay::Element<'b, Message, Theme, Renderer>>
{
self.content
.overlay(layout, renderer)
.map(|content| Overlay {
theme: self.theme,
to_theme: &self.to_theme,
content,
})
.map(|overlay| overlay::Element::new(Box::new(overlay)))
@ -268,24 +281,26 @@ where
.as_widget_mut()
.overlay(tree, layout, renderer, translation)
.map(|content| Overlay {
theme: &self.theme,
to_theme: &self.to_theme,
content,
})
.map(|overlay| overlay::Element::new(Box::new(overlay)))
}
}
impl<'a, AnyTheme, Message, Theme, Renderer>
From<Themer<'a, Message, Theme, Renderer>>
for Element<'a, Message, AnyTheme, Renderer>
impl<'a, Message, Theme, NewTheme, F, Renderer>
From<Themer<'a, Message, Theme, NewTheme, F, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
Theme: 'a + application::StyleSheet,
Theme: 'a,
NewTheme: 'a,
F: Fn(&Theme) -> NewTheme + 'a,
Renderer: 'a + crate::core::Renderer,
{
fn from(
themer: Themer<'a, Message, Theme, Renderer>,
) -> Element<'a, Message, AnyTheme, Renderer> {
themer: Themer<'a, Message, Theme, NewTheme, F, Renderer>,
) -> Element<'a, Message, Theme, Renderer> {
Element::new(themer)
}
}

View file

@ -9,19 +9,16 @@ use crate::core::touch;
use crate::core::widget;
use crate::core::widget::tree::{self, Tree};
use crate::core::{
Border, Clipboard, Element, Event, Layout, Length, Pixels, Rectangle,
Shell, Size, Widget,
Border, Clipboard, Color, Element, Event, Layout, Length, Pixels,
Rectangle, Shell, Size, Theme, Widget,
};
pub use crate::style::toggler::{Appearance, StyleSheet};
/// A toggler widget.
///
/// # Example
///
/// ```no_run
/// # type Toggler<'a, Message> =
/// # iced_widget::Toggler<'a, Message, iced_widget::style::Theme, iced_widget::renderer::Renderer>;
/// # type Toggler<'a, Message> = iced_widget::Toggler<'a, Message>;
/// #
/// pub enum Message {
/// TogglerToggled(bool),
@ -38,7 +35,6 @@ pub struct Toggler<
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
Theme: StyleSheet,
Renderer: text::Renderer,
{
is_toggled: bool,
@ -52,16 +48,15 @@ pub struct Toggler<
text_shaping: text::Shaping,
spacing: f32,
font: Option<Renderer::Font>,
style: Theme::Style,
style: Style<Theme>,
}
impl<'a, Message, Theme, Renderer> Toggler<'a, Message, Theme, Renderer>
where
Theme: StyleSheet,
Renderer: text::Renderer,
{
/// The default size of a [`Toggler`].
pub const DEFAULT_SIZE: f32 = 20.0;
pub const DEFAULT_SIZE: f32 = 16.0;
/// Creates a new [`Toggler`].
///
@ -77,6 +72,7 @@ where
f: F,
) -> Self
where
Theme: DefaultStyle,
F: 'a + Fn(bool) -> Message,
{
Toggler {
@ -91,7 +87,7 @@ where
text_shaping: text::Shaping::Basic,
spacing: Self::DEFAULT_SIZE / 2.0,
font: None,
style: Default::default(),
style: Theme::default_style(),
}
}
@ -149,7 +145,7 @@ where
}
/// Sets the style of the [`Toggler`].
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self {
self.style = style.into();
self
}
@ -158,7 +154,6 @@ where
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Toggler<'a, Message, Theme, Renderer>
where
Theme: StyleSheet + crate::text::StyleSheet,
Renderer: text::Renderer,
{
fn tag(&self) -> tree::Tag {
@ -294,12 +289,18 @@ where
let bounds = toggler_layout.bounds();
let is_mouse_over = cursor.is_over(layout.bounds());
let style = if is_mouse_over {
theme.hovered(&self.style, self.is_toggled)
let status = if is_mouse_over {
Status::Hovered {
is_toggled: self.is_toggled,
}
} else {
theme.active(&self.style, self.is_toggled)
Status::Active {
is_toggled: self.is_toggled,
}
};
let appearance = (self.style)(theme, status);
let border_radius = bounds.height / BORDER_RADIUS_RATIO;
let space = SPACE_RATIO * bounds.height;
@ -315,12 +316,12 @@ where
bounds: toggler_background_bounds,
border: Border {
radius: border_radius.into(),
width: style.background_border_width,
color: style.background_border_color,
width: appearance.background_border_width,
color: appearance.background_border_color,
},
..renderer::Quad::default()
},
style.background,
appearance.background,
);
let toggler_foreground_bounds = Rectangle {
@ -340,12 +341,12 @@ where
bounds: toggler_foreground_bounds,
border: Border {
radius: border_radius.into(),
width: style.foreground_border_width,
color: style.foreground_border_color,
width: appearance.foreground_border_width,
color: appearance.foreground_border_color,
},
..renderer::Quad::default()
},
style.foreground,
appearance.foreground,
);
}
}
@ -354,7 +355,7 @@ impl<'a, Message, Theme, Renderer> From<Toggler<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
Theme: StyleSheet + crate::text::StyleSheet + 'a,
Theme: 'a,
Renderer: text::Renderer + 'a,
{
fn from(
@ -363,3 +364,100 @@ where
Element::new(toggler)
}
}
/// The possible status of a [`Toggler`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Status {
/// The [`Toggler`] can be interacted with.
Active {
/// Indicates whether the [`Toggler`] is toggled.
is_toggled: bool,
},
/// The [`Toggler`] is being hovered.
Hovered {
/// Indicates whether the [`Toggler`] is toggled.
is_toggled: bool,
},
}
/// The appearance of a toggler.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The background [`Color`] of the toggler.
pub background: Color,
/// The width of the background border of the toggler.
pub background_border_width: f32,
/// The [`Color`] of the background border of the toggler.
pub background_border_color: Color,
/// The foreground [`Color`] of the toggler.
pub foreground: Color,
/// The width of the foreground border of the toggler.
pub foreground_border_width: f32,
/// The [`Color`] of the foreground border of the toggler.
pub foreground_border_color: Color,
}
/// The style of a [`Toggler`].
pub type Style<Theme> = fn(&Theme, Status) -> Appearance;
/// The default style of a [`Toggler`].
pub trait DefaultStyle {
/// Returns the default style of a [`Toggler`].
fn default_style() -> Style<Self>;
}
impl DefaultStyle for Theme {
fn default_style() -> Style<Self> {
default
}
}
impl DefaultStyle for Appearance {
fn default_style() -> Style<Self> {
|appearance, _status| *appearance
}
}
/// The default style of a [`Toggler`].
pub fn default(theme: &Theme, status: Status) -> Appearance {
let palette = theme.extended_palette();
let background = match status {
Status::Active { is_toggled } | Status::Hovered { is_toggled } => {
if is_toggled {
palette.primary.strong.color
} else {
palette.background.strong.color
}
}
};
let foreground = match status {
Status::Active { is_toggled } => {
if is_toggled {
palette.primary.strong.text
} else {
palette.background.base.color
}
}
Status::Hovered { is_toggled } => {
if is_toggled {
Color {
a: 0.5,
..palette.primary.strong.text
}
} else {
palette.background.weak.color
}
}
};
Appearance {
background,
foreground,
foreground_border_width: 0.0,
foreground_border_color: Color::TRANSPARENT,
background_border_width: 0.0,
background_border_color: Color::TRANSPARENT,
}
}

View file

@ -20,7 +20,6 @@ pub struct Tooltip<
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
Theme: container::StyleSheet + crate::text::StyleSheet,
Renderer: text::Renderer,
{
content: Element<'a, Message, Theme, Renderer>,
@ -29,12 +28,11 @@ pub struct Tooltip<
gap: f32,
padding: f32,
snap_within_viewport: bool,
style: <Theme as container::StyleSheet>::Style,
style: container::Style<Theme>,
}
impl<'a, Message, Theme, Renderer> Tooltip<'a, Message, Theme, Renderer>
where
Theme: container::StyleSheet + crate::text::StyleSheet,
Renderer: text::Renderer,
{
/// The default padding of a [`Tooltip`] drawn by this renderer.
@ -47,7 +45,10 @@ where
content: impl Into<Element<'a, Message, Theme, Renderer>>,
tooltip: impl Into<Element<'a, Message, Theme, Renderer>>,
position: Position,
) -> Self {
) -> Self
where
Theme: container::DefaultStyle,
{
Tooltip {
content: content.into(),
tooltip: tooltip.into(),
@ -55,7 +56,7 @@ where
gap: 0.0,
padding: Self::DEFAULT_PADDING,
snap_within_viewport: true,
style: Default::default(),
style: Theme::default_style(),
}
}
@ -80,7 +81,7 @@ where
/// Sets the style of the [`Tooltip`].
pub fn style(
mut self,
style: impl Into<<Theme as container::StyleSheet>::Style>,
style: fn(&Theme, container::Status) -> container::Appearance,
) -> Self {
self.style = style.into();
self
@ -90,7 +91,6 @@ where
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Tooltip<'a, Message, Theme, Renderer>
where
Theme: container::StyleSheet + crate::text::StyleSheet,
Renderer: text::Renderer,
{
fn children(&self) -> Vec<widget::Tree> {
@ -239,7 +239,7 @@ where
positioning: self.position,
gap: self.gap,
padding: self.padding,
style: &self.style,
style: self.style,
})))
} else {
None
@ -262,7 +262,7 @@ impl<'a, Message, Theme, Renderer> From<Tooltip<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
Theme: container::StyleSheet + crate::text::StyleSheet + 'a,
Theme: 'a,
Renderer: text::Renderer + 'a,
{
fn from(
@ -298,7 +298,6 @@ enum State {
struct Overlay<'a, 'b, Message, Theme, Renderer>
where
Theme: container::StyleSheet + widget::text::StyleSheet,
Renderer: text::Renderer,
{
position: Point,
@ -310,14 +309,13 @@ where
positioning: Position,
gap: f32,
padding: f32,
style: &'b <Theme as container::StyleSheet>::Style,
style: container::Style<Theme>,
}
impl<'a, 'b, Message, Theme, Renderer>
overlay::Overlay<Message, Theme, Renderer>
for Overlay<'a, 'b, Message, Theme, Renderer>
where
Theme: container::StyleSheet + widget::text::StyleSheet,
Renderer: text::Renderer,
{
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
@ -426,7 +424,7 @@ where
layout: Layout<'_>,
cursor_position: mouse::Cursor,
) {
let style = container::StyleSheet::appearance(theme, self.style);
let style = (self.style)(theme, container::Status::Idle);
container::draw_background(renderer, &style, layout.bounds());

View file

@ -1,9 +1,9 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`VerticalSlider`] has some local [`State`].
use std::ops::RangeInclusive;
pub use crate::style::slider::{Appearance, Handle, HandleShape, StyleSheet};
pub use crate::slider::{
default, Appearance, DefaultStyle, Handle, HandleShape, Status, Style,
};
use crate::core;
use crate::core::event::{self, Event};
@ -29,8 +29,7 @@ use crate::core::{
///
/// # Example
/// ```no_run
/// # type VerticalSlider<'a, T, Message> =
/// # iced_widget::VerticalSlider<'a, T, Message, iced_widget::style::Theme>;
/// # type VerticalSlider<'a, T, Message> = iced_widget::VerticalSlider<'a, T, Message>;
/// #
/// #[derive(Clone)]
/// pub enum Message {
@ -42,10 +41,7 @@ use crate::core::{
/// VerticalSlider::new(0.0..=100.0, value, Message::SliderChanged);
/// ```
#[allow(missing_debug_implementations)]
pub struct VerticalSlider<'a, T, Message, Theme = crate::Theme>
where
Theme: StyleSheet,
{
pub struct VerticalSlider<'a, T, Message, Theme = crate::Theme> {
range: RangeInclusive<T>,
step: T,
shift_step: Option<T>,
@ -55,17 +51,16 @@ where
on_release: Option<Message>,
width: f32,
height: Length,
style: Theme::Style,
style: Style<Theme>,
}
impl<'a, T, Message, Theme> VerticalSlider<'a, T, Message, Theme>
where
T: Copy + From<u8> + std::cmp::PartialOrd,
Message: Clone,
Theme: StyleSheet,
{
/// The default width of a [`VerticalSlider`].
pub const DEFAULT_WIDTH: f32 = 22.0;
pub const DEFAULT_WIDTH: f32 = 16.0;
/// Creates a new [`VerticalSlider`].
///
@ -77,6 +72,7 @@ where
/// `Message`.
pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self
where
Theme: DefaultStyle,
F: 'a + Fn(T) -> Message,
{
let value = if value >= *range.start() {
@ -101,7 +97,7 @@ where
on_release: None,
width: Self::DEFAULT_WIDTH,
height: Length::Fill,
style: Default::default(),
style: Theme::default_style(),
}
}
@ -137,7 +133,7 @@ where
}
/// Sets the style of the [`VerticalSlider`].
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self {
self.style = style.into();
self
}
@ -162,7 +158,6 @@ impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone,
Theme: StyleSheet,
Renderer: core::Renderer,
{
fn tag(&self) -> tree::Tag {
@ -170,7 +165,7 @@ where
}
fn state(&self) -> tree::State {
tree::State::new(State::new())
tree::State::new(State::default())
}
fn size(&self) -> Size<Length> {
@ -200,112 +195,27 @@ where
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
) -> event::Status {
update(
event,
layout,
cursor,
shell,
tree.state.downcast_mut::<State>(),
&mut self.value,
self.default,
&self.range,
self.step,
self.shift_step,
self.on_change.as_ref(),
&self.on_release,
)
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
_style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
draw(
renderer,
layout,
cursor,
tree.state.downcast_ref::<State>(),
self.value,
&self.range,
theme,
&self.style,
);
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(layout, cursor, tree.state.downcast_ref::<State>())
}
}
impl<'a, T, Message, Theme, Renderer>
From<VerticalSlider<'a, T, Message, Theme>>
for Element<'a, Message, Theme, Renderer>
where
T: Copy + Into<f64> + num_traits::FromPrimitive + 'a,
Message: Clone + 'a,
Theme: StyleSheet + 'a,
Renderer: core::Renderer + 'a,
{
fn from(
slider: VerticalSlider<'a, T, Message, Theme>,
) -> Element<'a, Message, Theme, Renderer> {
Element::new(slider)
}
}
/// Processes an [`Event`] and updates the [`State`] of a [`VerticalSlider`]
/// accordingly.
pub fn update<Message, T>(
event: Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>,
state: &mut State,
value: &mut T,
default: Option<T>,
range: &RangeInclusive<T>,
step: T,
shift_step: Option<T>,
on_change: &dyn Fn(T) -> Message,
on_release: &Option<Message>,
) -> event::Status
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone,
{
let state = tree.state.downcast_mut::<State>();
let is_dragging = state.is_dragging;
let current_value = *value;
let current_value = self.value;
let locate = |cursor_position: Point| -> Option<T> {
let bounds = layout.bounds();
let new_value = if cursor_position.y >= bounds.y + bounds.height {
Some(*range.start())
Some(*self.range.start())
} else if cursor_position.y <= bounds.y {
Some(*range.end())
Some(*self.range.end())
} else {
let step = if state.keyboard_modifiers.shift() {
shift_step.unwrap_or(step)
self.shift_step.unwrap_or(self.step)
} else {
step
self.step
}
.into();
let start = (*range.start()).into();
let end = (*range.end()).into();
let start = (*self.range.start()).into();
let end = (*self.range.end()).into();
let percent = 1.0
- f64::from(cursor_position.y - bounds.y)
@ -322,17 +232,17 @@ where
let increment = |value: T| -> Option<T> {
let step = if state.keyboard_modifiers.shift() {
shift_step.unwrap_or(step)
self.shift_step.unwrap_or(self.step)
} else {
step
self.step
}
.into();
let steps = (value.into() / step).round();
let new_value = step * (steps + 1.0);
if new_value > (*range.end()).into() {
return Some(*range.end());
if new_value > (*self.range.end()).into() {
return Some(*self.range.end());
}
T::from_f64(new_value)
@ -340,39 +250,40 @@ where
let decrement = |value: T| -> Option<T> {
let step = if state.keyboard_modifiers.shift() {
shift_step.unwrap_or(step)
self.shift_step.unwrap_or(self.step)
} else {
step
self.step
}
.into();
let steps = (value.into() / step).round();
let new_value = step * (steps - 1.0);
if new_value < (*range.start()).into() {
return Some(*range.start());
if new_value < (*self.range.start()).into() {
return Some(*self.range.start());
}
T::from_f64(new_value)
};
let change = |new_value: T| {
if ((*value).into() - new_value.into()).abs() > f64::EPSILON {
shell.publish((on_change)(new_value));
if (self.value.into() - new_value.into()).abs() > f64::EPSILON {
shell.publish((self.on_change)(new_value));
*value = new_value;
self.value = new_value;
}
};
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
if let Some(cursor_position) = cursor.position_over(layout.bounds())
if let Some(cursor_position) =
cursor.position_over(layout.bounds())
{
if state.keyboard_modifiers.control()
|| state.keyboard_modifiers.command()
{
let _ = default.map(change);
let _ = self.default.map(change);
state.is_dragging = false;
} else {
let _ = locate(cursor_position).map(change);
@ -386,7 +297,7 @@ where
| Event::Touch(touch::Event::FingerLifted { .. })
| Event::Touch(touch::Event::FingerLost { .. }) => {
if is_dragging {
if let Some(on_release) = on_release.clone() {
if let Some(on_release) = self.on_release.clone() {
shell.publish(on_release);
}
state.is_dragging = false;
@ -426,31 +337,30 @@ where
event::Status::Ignored
}
/// Draws a [`VerticalSlider`].
pub fn draw<T, Theme, Renderer>(
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
_style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
state: &State,
value: T,
range: &RangeInclusive<T>,
style_sheet: &Theme,
style: &Theme::Style,
) where
T: Into<f64> + Copy,
Theme: StyleSheet,
Renderer: core::Renderer,
{
_viewport: &Rectangle,
) {
let state = tree.state.downcast_ref::<State>();
let bounds = layout.bounds();
let is_mouse_over = cursor.is_over(bounds);
let style = if state.is_dragging {
style_sheet.dragging(style)
let style = (self.style)(
theme,
if state.is_dragging {
Status::Dragged
} else if is_mouse_over {
style_sheet.hovered(style)
Status::Hovered
} else {
style_sheet.active(style)
};
Status::Active
},
);
let (handle_width, handle_height, handle_border_radius) =
match style.handle.shape {
@ -463,9 +373,9 @@ pub fn draw<T, Theme, Renderer>(
} => (f32::from(width), bounds.width, border_radius),
};
let value = value.into() as f32;
let value = self.value.into() as f32;
let (range_start, range_end) = {
let (start, end) = range.clone().into_inner();
let (start, end) = self.range.clone().into_inner();
(start.into() as f32, end.into() as f32)
};
@ -487,7 +397,7 @@ pub fn draw<T, Theme, Renderer>(
width: style.rail.width,
height: offset + handle_width / 2.0,
},
border: Border::with_radius(style.rail.border_radius),
border: Border::rounded(style.rail.border_radius),
..renderer::Quad::default()
},
style.rail.colors.1,
@ -501,7 +411,7 @@ pub fn draw<T, Theme, Renderer>(
width: style.rail.width,
height: bounds.height - offset - handle_width / 2.0,
},
border: Border::with_radius(style.rail.border_radius),
border: Border::rounded(style.rail.border_radius),
..renderer::Quad::default()
},
style.rail.colors.0,
@ -526,12 +436,15 @@ pub fn draw<T, Theme, Renderer>(
);
}
/// Computes the current [`mouse::Interaction`] of a [`VerticalSlider`].
pub fn mouse_interaction(
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor: mouse::Cursor,
state: &State,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
let state = tree.state.downcast_ref::<State>();
let bounds = layout.bounds();
let is_mouse_over = cursor.is_over(bounds);
@ -543,17 +456,26 @@ pub fn mouse_interaction(
mouse::Interaction::default()
}
}
}
impl<'a, T, Message, Theme, Renderer>
From<VerticalSlider<'a, T, Message, Theme>>
for Element<'a, Message, Theme, Renderer>
where
T: Copy + Into<f64> + num_traits::FromPrimitive + 'a,
Message: Clone + 'a,
Theme: 'a,
Renderer: core::Renderer + 'a,
{
fn from(
slider: VerticalSlider<'a, T, Message, Theme>,
) -> Element<'a, Message, Theme, Renderer> {
Element::new(slider)
}
}
/// The local state of a [`VerticalSlider`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct State {
struct State {
is_dragging: bool,
keyboard_modifiers: keyboard::Modifiers,
}
impl State {
/// Creates a new [`State`].
pub fn new() -> State {
State::default()
}
}

View file

@ -24,7 +24,6 @@ multi-window = ["iced_runtime/multi-window"]
[dependencies]
iced_graphics.workspace = true
iced_runtime.workspace = true
iced_style.workspace = true
log.workspace = true
thiserror.workspace = true

View file

@ -10,7 +10,7 @@ use crate::core::renderer;
use crate::core::time::Instant;
use crate::core::widget::operation;
use crate::core::window;
use crate::core::{Event, Point, Size};
use crate::core::{Color, Event, Point, Size, Theme};
use crate::futures::futures;
use crate::futures::{Executor, Runtime, Subscription};
use crate::graphics::compositor::{self, Compositor};
@ -18,7 +18,6 @@ use crate::runtime::clipboard;
use crate::runtime::program::Program;
use crate::runtime::user_interface::{self, UserInterface};
use crate::runtime::{Command, Debug};
use crate::style::application::{Appearance, StyleSheet};
use crate::{Clipboard, Error, Proxy, Settings};
use futures::channel::mpsc;
@ -39,7 +38,7 @@ use std::sync::Arc;
/// can be toggled by pressing `F12`.
pub trait Application: Program
where
Self::Theme: StyleSheet,
Self::Theme: DefaultStyle,
{
/// The data needed to initialize your [`Application`].
type Flags;
@ -64,8 +63,8 @@ where
fn theme(&self) -> Self::Theme;
/// Returns the `Style` variation of the `Theme`.
fn style(&self) -> <Self::Theme as StyleSheet>::Style {
Default::default()
fn style(&self, theme: &Self::Theme) -> Appearance {
theme.default_style()
}
/// Returns the event `Subscription` for the current state of the
@ -95,6 +94,38 @@ where
}
}
/// The appearance of an application.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Appearance {
/// The background [`Color`] of the application.
pub background_color: Color,
/// The default text [`Color`] of the application.
pub text_color: Color,
}
/// The default style of an [`Application`].
pub trait DefaultStyle {
/// Returns the default style of an [`Application`].
fn default_style(&self) -> Appearance;
}
impl DefaultStyle for Theme {
fn default_style(&self) -> Appearance {
default(self)
}
}
/// The default [`Appearance`] of an [`Application`] with the built-in [`Theme`].
pub fn default(theme: &Theme) -> Appearance {
let palette = theme.extended_palette();
Appearance {
background_color: palette.background.base.color,
text_color: palette.background.base.text,
}
}
/// Runs an [`Application`] with an executor, compositor, and the provided
/// settings.
pub async fn run<A, E, C>(
@ -105,7 +136,7 @@ where
A: Application + 'static,
E: Executor + 'static,
C: Compositor<Renderer = A::Renderer> + 'static,
A::Theme: StyleSheet,
A::Theme: DefaultStyle,
{
use futures::task;
use futures::Future;
@ -289,7 +320,7 @@ async fn run_instance<A, E, C>(
A: Application + 'static,
E: Executor + 'static,
C: Compositor<Renderer = A::Renderer> + 'static,
A::Theme: StyleSheet,
A::Theme: DefaultStyle,
{
use futures::stream::StreamExt;
use winit::event;
@ -612,7 +643,7 @@ pub fn build_user_interface<'a, A: Application>(
debug: &mut Debug,
) -> UserInterface<'a, A::Message, A::Theme, A::Renderer>
where
A::Theme: StyleSheet,
A::Theme: DefaultStyle,
{
debug.view_started();
let view = application.view();
@ -643,7 +674,7 @@ pub fn update<A: Application, C, E: Executor>(
window: &winit::window::Window,
) where
C: Compositor<Renderer = A::Renderer> + 'static,
A::Theme: StyleSheet,
A::Theme: DefaultStyle,
{
for message in messages.drain(..) {
debug.log_message(&message);
@ -694,7 +725,7 @@ pub fn run_command<A, C, E>(
A: Application,
E: Executor,
C: Compositor<Renderer = A::Renderer> + 'static,
A::Theme: StyleSheet,
A::Theme: DefaultStyle,
{
use crate::runtime::command;
use crate::runtime::system;

View file

@ -1,4 +1,4 @@
use crate::application::{self, StyleSheet as _};
use crate::application;
use crate::conversion;
use crate::core::mouse;
use crate::core::{Color, Size};
@ -14,7 +14,7 @@ use winit::window::Window;
#[allow(missing_debug_implementations)]
pub struct State<A: Application>
where
A::Theme: application::StyleSheet,
A::Theme: application::DefaultStyle,
{
title: String,
scale_factor: f64,
@ -29,14 +29,14 @@ where
impl<A: Application> State<A>
where
A::Theme: application::StyleSheet,
A::Theme: application::DefaultStyle,
{
/// Creates a new [`State`] for the provided [`Application`] and window.
pub fn new(application: &A, window: &Window) -> Self {
let title = application.title();
let scale_factor = application.scale_factor();
let theme = application.theme();
let appearance = theme.appearance(&application.style());
let appearance = application.style(&theme);
let viewport = {
let physical_size = window.inner_size();
@ -216,6 +216,6 @@ where
// Update theme and appearance
self.theme = application.theme();
self.appearance = self.theme.appearance(&application.style());
self.appearance = application.style(&self.theme);
}
}

View file

@ -30,7 +30,6 @@ pub use iced_graphics as graphics;
pub use iced_runtime as runtime;
pub use iced_runtime::core;
pub use iced_runtime::futures;
pub use iced_style as style;
pub use winit;
#[cfg(feature = "multi-window")]

View file

@ -22,9 +22,10 @@ use crate::runtime::command::{self, Command};
use crate::runtime::multi_window::Program;
use crate::runtime::user_interface::{self, UserInterface};
use crate::runtime::Debug;
use crate::style::application::StyleSheet;
use crate::{Clipboard, Error, Proxy, Settings};
pub use crate::application::{default, Appearance, DefaultStyle};
use std::collections::HashMap;
use std::mem::ManuallyDrop;
use std::sync::Arc;
@ -43,7 +44,7 @@ use std::time::Instant;
/// can be toggled by pressing `F12`.
pub trait Application: Program
where
Self::Theme: StyleSheet,
Self::Theme: DefaultStyle,
{
/// The data needed to initialize your [`Application`].
type Flags;
@ -68,8 +69,8 @@ where
fn theme(&self, window: window::Id) -> Self::Theme;
/// Returns the `Style` variation of the `Theme`.
fn style(&self) -> <Self::Theme as StyleSheet>::Style {
Default::default()
fn style(&self, theme: &Self::Theme) -> Appearance {
theme.default_style()
}
/// Returns the event `Subscription` for the current state of the
@ -110,7 +111,7 @@ where
A: Application + 'static,
E: Executor + 'static,
C: Compositor<Renderer = A::Renderer> + 'static,
A::Theme: StyleSheet,
A::Theme: DefaultStyle,
{
use winit::event_loop::EventLoopBuilder;
@ -352,7 +353,7 @@ async fn run_instance<A, E, C>(
A: Application + 'static,
E: Executor + 'static,
C: Compositor<Renderer = A::Renderer> + 'static,
A::Theme: StyleSheet,
A::Theme: DefaultStyle,
{
use winit::event;
use winit::event_loop::ControlFlow;
@ -822,7 +823,7 @@ fn build_user_interface<'a, A: Application>(
id: window::Id,
) -> UserInterface<'a, A::Message, A::Theme, A::Renderer>
where
A::Theme: StyleSheet,
A::Theme: DefaultStyle,
{
debug.view_started();
let view = application.view(id);
@ -850,7 +851,7 @@ fn update<A: Application, C, E: Executor>(
ui_caches: &mut HashMap<window::Id, user_interface::Cache>,
) where
C: Compositor<Renderer = A::Renderer> + 'static,
A::Theme: StyleSheet,
A::Theme: DefaultStyle,
{
for message in messages.drain(..) {
debug.log_message(&message);
@ -893,7 +894,7 @@ fn run_command<A, C, E>(
A: Application,
E: Executor,
C: Compositor<Renderer = A::Renderer> + 'static,
A::Theme: StyleSheet,
A::Theme: DefaultStyle,
{
use crate::runtime::clipboard;
use crate::runtime::system;
@ -1219,8 +1220,8 @@ pub fn build_user_interfaces<'a, A: Application, C: Compositor>(
mut cached_user_interfaces: HashMap<window::Id, user_interface::Cache>,
) -> HashMap<window::Id, UserInterface<'a, A::Message, A::Theme, A::Renderer>>
where
A::Theme: StyleSheet,
C: Compositor<Renderer = A::Renderer>,
A::Theme: DefaultStyle,
{
cached_user_interfaces
.drain()

View file

@ -2,18 +2,16 @@ use crate::conversion;
use crate::core::{mouse, window};
use crate::core::{Color, Size};
use crate::graphics::Viewport;
use crate::multi_window::Application;
use crate::style::application;
use crate::multi_window::{self, Application};
use std::fmt::{Debug, Formatter};
use iced_style::application::StyleSheet;
use winit::event::{Touch, WindowEvent};
use winit::window::Window;
/// The state of a multi-windowed [`Application`].
pub struct State<A: Application>
where
A::Theme: application::StyleSheet,
A::Theme: multi_window::DefaultStyle,
{
title: String,
scale_factor: f64,
@ -22,12 +20,12 @@ where
cursor_position: Option<winit::dpi::PhysicalPosition<f64>>,
modifiers: winit::keyboard::ModifiersState,
theme: A::Theme,
appearance: application::Appearance,
appearance: multi_window::Appearance,
}
impl<A: Application> Debug for State<A>
where
A::Theme: application::StyleSheet,
A::Theme: multi_window::DefaultStyle,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("multi_window::State")
@ -43,7 +41,7 @@ where
impl<A: Application> State<A>
where
A::Theme: application::StyleSheet,
A::Theme: multi_window::DefaultStyle,
{
/// Creates a new [`State`] for the provided [`Application`]'s `window`.
pub fn new(
@ -54,7 +52,7 @@ where
let title = application.title(window_id);
let scale_factor = application.scale_factor(window_id);
let theme = application.theme(window_id);
let appearance = theme.appearance(&application.style());
let appearance = application.style(&theme);
let viewport = {
let physical_size = window.inner_size();
@ -236,6 +234,6 @@ where
// Update theme and appearance
self.theme = application.theme(window_id);
self.appearance = self.theme.appearance(&application.style());
self.appearance = application.style(&self.theme);
}
}

View file

@ -2,8 +2,7 @@ use crate::core::mouse;
use crate::core::window::Id;
use crate::core::{Point, Size};
use crate::graphics::Compositor;
use crate::multi_window::{Application, State};
use crate::style::application::StyleSheet;
use crate::multi_window::{Application, DefaultStyle, State};
use std::collections::BTreeMap;
use std::sync::Arc;
@ -12,8 +11,8 @@ use winit::monitor::MonitorHandle;
#[allow(missing_debug_implementations)]
pub struct WindowManager<A: Application, C: Compositor>
where
A::Theme: StyleSheet,
C: Compositor<Renderer = A::Renderer>,
A::Theme: DefaultStyle,
{
aliases: BTreeMap<winit::window::WindowId, Id>,
entries: BTreeMap<Id, Window<A, C>>,
@ -23,7 +22,7 @@ impl<A, C> WindowManager<A, C>
where
A: Application,
C: Compositor<Renderer = A::Renderer>,
A::Theme: StyleSheet,
A::Theme: DefaultStyle,
{
pub fn new() -> Self {
Self {
@ -109,7 +108,7 @@ impl<A, C> Default for WindowManager<A, C>
where
A: Application,
C: Compositor<Renderer = A::Renderer>,
A::Theme: StyleSheet,
A::Theme: DefaultStyle,
{
fn default() -> Self {
Self::new()
@ -121,7 +120,7 @@ pub struct Window<A, C>
where
A: Application,
C: Compositor<Renderer = A::Renderer>,
A::Theme: StyleSheet,
A::Theme: DefaultStyle,
{
pub raw: Arc<winit::window::Window>,
pub state: State<A>,
@ -136,7 +135,7 @@ impl<A, C> Window<A, C>
where
A: Application,
C: Compositor<Renderer = A::Renderer>,
A::Theme: StyleSheet,
A::Theme: DefaultStyle,
{
pub fn position(&self) -> Option<Point> {
self.raw