diff options
Diffstat (limited to 'style/src/theme.rs')
-rw-r--r-- | style/src/theme.rs | 718 |
1 files changed, 718 insertions, 0 deletions
diff --git a/style/src/theme.rs b/style/src/theme.rs new file mode 100644 index 00000000..d2de8a5d --- /dev/null +++ b/style/src/theme.rs @@ -0,0 +1,718 @@ +pub mod palette; + +pub use self::palette::Palette; + +use crate::application; +use crate::button; +use crate::checkbox; +use crate::container; +use crate::menu; +use crate::pane_grid; +use crate::pick_list; +use crate::progress_bar; +use crate::radio; +use crate::rule; +use crate::scrollable; +use crate::slider; +use crate::text; +use crate::text_input; +use crate::toggler; + +use iced_core::{Background, Color}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Theme { + Light, + Dark, +} + +impl Theme { + pub fn palette(self) -> Palette { + match self { + Self::Light => Palette::LIGHT, + Self::Dark => Palette::DARK, + } + } + + pub fn extended_palette(&self) -> &palette::Extended { + match self { + Self::Light => &palette::EXTENDED_LIGHT, + Self::Dark => &palette::EXTENDED_DARK, + } + } +} + +impl Default for Theme { + fn default() -> Self { + Self::Light + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Application { + Default, + Custom(fn(Theme) -> application::Appearance), +} + +impl Default for Application { + fn default() -> Self { + Self::Default + } +} + +impl application::StyleSheet for Theme { + type Style = Application; + + fn appearance(&self, style: Self::Style) -> application::Appearance { + let palette = self.extended_palette(); + + match style { + Application::Default => application::Appearance { + background_color: palette.background.base.color, + text_color: palette.background.base.text, + }, + Application::Custom(f) => f(*self), + } + } +} + +/* + * Button + */ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Button { + Primary, + Secondary, + Positive, + Destructive, + Text, +} + +impl Default for Button { + fn default() -> Self { + Self::Primary + } +} + +impl button::StyleSheet for Theme { + type Style = Button; + + fn active(&self, style: Self::Style) -> button::Appearance { + let palette = self.extended_palette(); + + let appearance = button::Appearance { + border_radius: 2.0, + ..button::Appearance::default() + }; + + let from_pair = |pair: palette::Pair| button::Appearance { + background: Some(pair.color.into()), + text_color: pair.text, + ..appearance + }; + + match style { + Button::Primary => from_pair(palette.primary.strong), + Button::Secondary => from_pair(palette.secondary.base), + Button::Positive => from_pair(palette.success.base), + Button::Destructive => from_pair(palette.danger.base), + Button::Text => button::Appearance { + text_color: palette.background.base.text, + ..appearance + }, + } + } + + fn hovered(&self, style: Self::Style) -> button::Appearance { + let active = self.active(style); + let palette = self.extended_palette(); + + let background = match style { + Button::Primary => Some(palette.primary.base.color), + Button::Secondary => Some(palette.background.strong.color), + Button::Positive => Some(palette.success.strong.color), + Button::Destructive => Some(palette.danger.strong.color), + Button::Text => None, + }; + + button::Appearance { + background: background.map(Background::from), + ..active + } + } +} + +/* + * Checkbox + */ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Checkbox { + Primary, + Secondary, + Success, + Danger, +} + +impl Default for Checkbox { + fn default() -> Self { + Self::Primary + } +} + +impl checkbox::StyleSheet for Theme { + type Style = Checkbox; + + fn active( + &self, + style: Self::Style, + is_checked: bool, + ) -> checkbox::Appearance { + let palette = self.extended_palette(); + + match style { + Checkbox::Primary => checkbox_appearance( + palette.primary.strong.text, + palette.background.base, + palette.primary.strong, + is_checked, + ), + Checkbox::Secondary => checkbox_appearance( + palette.background.base.text, + palette.background.base, + palette.background.base, + is_checked, + ), + Checkbox::Success => checkbox_appearance( + palette.success.base.text, + palette.background.base, + palette.success.base, + is_checked, + ), + Checkbox::Danger => checkbox_appearance( + palette.danger.base.text, + palette.background.base, + palette.danger.base, + is_checked, + ), + } + } + + fn hovered( + &self, + style: Self::Style, + is_checked: bool, + ) -> checkbox::Appearance { + let palette = self.extended_palette(); + + match style { + Checkbox::Primary => checkbox_appearance( + palette.primary.strong.text, + palette.background.weak, + palette.primary.base, + is_checked, + ), + Checkbox::Secondary => checkbox_appearance( + palette.background.base.text, + palette.background.weak, + palette.background.base, + is_checked, + ), + Checkbox::Success => checkbox_appearance( + palette.success.base.text, + palette.background.weak, + palette.success.base, + is_checked, + ), + Checkbox::Danger => checkbox_appearance( + palette.danger.base.text, + palette.background.weak, + palette.danger.base, + is_checked, + ), + } + } +} + +fn checkbox_appearance( + checkmark_color: Color, + base: palette::Pair, + accent: palette::Pair, + is_checked: bool, +) -> checkbox::Appearance { + checkbox::Appearance { + background: Background::Color(if is_checked { + accent.color + } else { + base.color + }), + checkmark_color, + border_radius: 2.0, + border_width: 1.0, + border_color: accent.color, + text_color: None, + } +} + +/* + * Container + */ +#[derive(Clone, Copy)] +pub enum Container { + Transparent, + Box, + Custom(fn(&Theme) -> container::Appearance), +} + +impl Default for Container { + fn default() -> Self { + Self::Transparent + } +} + +impl From<fn(&Theme) -> container::Appearance> for Container { + fn from(f: fn(&Theme) -> container::Appearance) -> Self { + Self::Custom(f) + } +} + +impl container::StyleSheet for Theme { + type Style = Container; + + fn appearance(&self, style: Self::Style) -> container::Appearance { + match style { + Container::Transparent => Default::default(), + Container::Box => { + let palette = self.extended_palette(); + + container::Appearance { + text_color: None, + background: palette.background.weak.color.into(), + border_radius: 2.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + } + } + Container::Custom(f) => f(self), + } + } +} + +/* + * Slider + */ +impl slider::StyleSheet for Theme { + type Style = (); + + fn active(&self, _style: Self::Style) -> slider::Appearance { + let palette = self.extended_palette(); + + let handle = slider::Handle { + shape: slider::HandleShape::Rectangle { + width: 8, + border_radius: 4.0, + }, + color: Color::WHITE, + border_color: Color::WHITE, + border_width: 1.0, + }; + + slider::Appearance { + rail_colors: (palette.primary.base.color, Color::TRANSPARENT), + handle: slider::Handle { + color: palette.background.base.color, + border_color: palette.primary.base.color, + ..handle + }, + } + } + + fn hovered(&self, style: Self::Style) -> slider::Appearance { + let active = self.active(style); + let palette = self.extended_palette(); + + slider::Appearance { + handle: slider::Handle { + color: palette.primary.weak.color, + ..active.handle + }, + ..active + } + } + + fn dragging(&self, style: Self::Style) -> slider::Appearance { + let active = self.active(style); + let palette = self.extended_palette(); + + slider::Appearance { + handle: slider::Handle { + color: palette.primary.base.color, + ..active.handle + }, + ..active + } + } +} + +/* + * Menu + */ +impl menu::StyleSheet for Theme { + type Style = (); + + fn appearance(&self, _style: Self::Style) -> menu::Appearance { + let palette = self.extended_palette(); + + menu::Appearance { + text_color: palette.background.weak.text, + background: palette.background.weak.color.into(), + border_width: 1.0, + border_color: palette.background.strong.color, + selected_text_color: palette.primary.strong.text, + selected_background: palette.primary.strong.color.into(), + } + } +} + +/* + * Pick List + */ +impl pick_list::StyleSheet for Theme { + type Style = (); + + fn active(&self, _style: ()) -> pick_list::Appearance { + let palette = self.extended_palette(); + + pick_list::Appearance { + text_color: palette.background.weak.text, + background: palette.background.weak.color.into(), + placeholder_color: palette.background.strong.color, + border_radius: 2.0, + border_width: 1.0, + border_color: palette.background.strong.color, + icon_size: 0.7, + } + } + + fn hovered(&self, _style: ()) -> pick_list::Appearance { + let palette = self.extended_palette(); + + pick_list::Appearance { + text_color: palette.background.weak.text, + background: palette.background.weak.color.into(), + placeholder_color: palette.background.strong.color, + border_radius: 2.0, + border_width: 1.0, + border_color: palette.primary.strong.color, + icon_size: 0.7, + } + } +} + +/* + * Radio + */ +impl radio::StyleSheet for Theme { + type Style = (); + + fn active(&self, _style: Self::Style) -> radio::Appearance { + let palette = self.extended_palette(); + + radio::Appearance { + background: Color::TRANSPARENT.into(), + dot_color: palette.primary.strong.color.into(), + border_width: 1.0, + border_color: palette.primary.strong.color, + text_color: None, + } + } + + fn hovered(&self, style: Self::Style) -> radio::Appearance { + let active = self.active(style); + let palette = self.extended_palette(); + + radio::Appearance { + dot_color: palette.primary.strong.color.into(), + background: palette.primary.weak.color.into(), + ..active + } + } +} + +/* + * Toggler + */ +impl toggler::StyleSheet for Theme { + type Style = (); + + fn active( + &self, + _style: Self::Style, + is_active: bool, + ) -> toggler::Appearance { + let palette = self.extended_palette(); + + toggler::Appearance { + background: if is_active { + palette.primary.strong.color + } else { + palette.background.strong.color + }, + background_border: None, + foreground: if is_active { + palette.primary.strong.text + } else { + palette.background.base.color + }, + foreground_border: None, + } + } + + fn hovered( + &self, + style: Self::Style, + is_active: bool, + ) -> toggler::Appearance { + let palette = self.extended_palette(); + + toggler::Appearance { + foreground: if is_active { + Color { + a: 0.5, + ..palette.primary.strong.text + } + } else { + palette.background.weak.color + }, + ..self.active(style, is_active) + } + } +} + +/* + * Pane Grid + */ +impl pane_grid::StyleSheet for Theme { + type Style = (); + + fn picked_split(&self, _style: Self::Style) -> Option<pane_grid::Line> { + let palette = self.extended_palette(); + + Some(pane_grid::Line { + color: palette.primary.strong.color, + width: 2.0, + }) + } + + fn hovered_split(&self, _style: Self::Style) -> Option<pane_grid::Line> { + let palette = self.extended_palette(); + + Some(pane_grid::Line { + color: palette.primary.base.color, + width: 2.0, + }) + } +} + +/* + * Progress Bar + */ +#[derive(Clone, Copy)] +pub enum ProgressBar { + Primary, + Success, + Danger, + Custom(fn(&Theme) -> progress_bar::Appearance), +} + +impl Default for ProgressBar { + fn default() -> Self { + Self::Primary + } +} + +impl progress_bar::StyleSheet for Theme { + type Style = ProgressBar; + + fn appearance(&self, style: Self::Style) -> progress_bar::Appearance { + let palette = self.extended_palette(); + + let from_palette = |bar: Color| progress_bar::Appearance { + background: palette.background.strong.color.into(), + bar: bar.into(), + border_radius: 2.0, + }; + + match style { + ProgressBar::Primary => from_palette(palette.primary.base.color), + ProgressBar::Success => from_palette(palette.success.base.color), + ProgressBar::Danger => from_palette(palette.danger.base.color), + ProgressBar::Custom(f) => f(self), + } + } +} + +/* + * Rule + */ +#[derive(Clone, Copy)] +pub enum Rule { + Default, + Custom(fn(&Theme) -> rule::Appearance), +} + +impl Default for Rule { + fn default() -> Self { + Self::Default + } +} + +impl rule::StyleSheet for Theme { + type Style = Rule; + + fn style(&self, style: Self::Style) -> rule::Appearance { + let palette = self.extended_palette(); + + match style { + Rule::Default => rule::Appearance { + color: palette.background.strong.color, + width: 1, + radius: 0.0, + fill_mode: rule::FillMode::Full, + }, + Rule::Custom(f) => f(self), + } + } +} + +/* + * Scrollable + */ +impl scrollable::StyleSheet for Theme { + type Style = (); + + fn active(&self, _style: Self::Style) -> scrollable::Scrollbar { + let palette = self.extended_palette(); + + scrollable::Scrollbar { + background: palette.background.weak.color.into(), + border_radius: 2.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + scroller: scrollable::Scroller { + color: palette.background.strong.color.into(), + border_radius: 2.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + } + } + + fn hovered(&self, _style: Self::Style) -> scrollable::Scrollbar { + let palette = self.extended_palette(); + + scrollable::Scrollbar { + background: palette.background.weak.color.into(), + border_radius: 2.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + scroller: scrollable::Scroller { + color: palette.primary.strong.color.into(), + border_radius: 2.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + } + } +} + +/* + * Text + */ +#[derive(Clone, Copy)] +pub enum Text { + Default, + Color(Color), + Custom(fn(&Theme) -> text::Appearance), +} + +impl Default for Text { + fn default() -> Self { + Self::Default + } +} + +impl From<Color> for Text { + fn from(color: Color) -> Self { + Text::Color(color) + } +} + +impl text::StyleSheet for Theme { + type Style = Text; + + fn appearance(&self, style: Self::Style) -> text::Appearance { + match style { + Text::Default => Default::default(), + Text::Color(c) => text::Appearance { color: Some(c) }, + Text::Custom(f) => f(self), + } + } +} + +/* + * Text Input + */ +impl text_input::StyleSheet for Theme { + type Style = (); + + fn active(&self, _style: Self::Style) -> text_input::Appearance { + let palette = self.extended_palette(); + + text_input::Appearance { + background: palette.background.base.color.into(), + border_radius: 2.0, + border_width: 1.0, + border_color: palette.background.strong.color, + } + } + + fn hovered(&self, _style: Self::Style) -> text_input::Appearance { + let palette = self.extended_palette(); + + text_input::Appearance { + background: palette.background.base.color.into(), + border_radius: 2.0, + border_width: 1.0, + border_color: palette.background.base.text, + } + } + + fn focused(&self, _style: Self::Style) -> text_input::Appearance { + let palette = self.extended_palette(); + + text_input::Appearance { + background: palette.background.base.color.into(), + border_radius: 2.0, + border_width: 1.0, + border_color: palette.primary.strong.color, + } + } + + fn placeholder_color(&self, _style: Self::Style) -> Color { + let palette = self.extended_palette(); + + palette.background.strong.color + } + + fn value_color(&self, _style: Self::Style) -> Color { + let palette = self.extended_palette(); + + palette.background.base.text + } + + fn selection_color(&self, _style: Self::Style) -> Color { + let palette = self.extended_palette(); + + palette.primary.weak.color + } +} |