Merge pull request #2312 from iced-rs/theming-reloaded
Theming reloaded
This commit is contained in:
commit
edf7d7ca75
97 changed files with 5738 additions and 6660 deletions
1
.github/workflows/document.yml
vendored
1
.github/workflows/document.yml
vendored
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
221
core/src/theme.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,6 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
iced.workspace = true
|
||||
iced.features = ["canvas", "palette"]
|
||||
iced.features = ["canvas"]
|
||||
|
||||
palette.workspace = true
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
//! The styling library of Iced.
|
||||
//!
|
||||
//! It contains a set of styles and stylesheets for most of the built-in
|
||||
//! widgets.
|
||||
//!
|
||||
//! 
|
||||
#![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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
1523
style/src/theme.rs
1523
style/src/theme.rs
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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};
|
|||
///
|
||||
/// 
|
||||
#[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),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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::{
|
|||
///
|
||||
/// 
|
||||
#[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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue