diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 35b5182c61..11e4828e4e 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -71,7 +71,7 @@ mod bezier { use iced::{ canvas::event::{self, Event}, canvas::{self, Canvas, Cursor, Frame, Geometry, Path, Stroke}, - mouse, Element, Length, Point, Rectangle, + mouse, Element, Length, Point, Rectangle, Theme, }; #[derive(Default)] @@ -158,7 +158,12 @@ mod bezier { } } - fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec { + fn draw( + &self, + _theme: &Theme, + bounds: Rectangle, + cursor: Cursor, + ) -> Vec { let content = self.state.cache.draw(bounds.size(), |frame: &mut Frame| { Curve::draw_all(self.curves, frame); diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 3b8a1d6ad5..48b4cd7bf4 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,7 +1,10 @@ +use iced::canvas::{ + self, Cache, Canvas, Cursor, Geometry, LineCap, Path, Stroke, +}; +use iced::executor; use iced::{ - canvas::{self, Cache, Canvas, Cursor, Geometry, LineCap, Path, Stroke}, - executor, Application, Color, Command, Container, Element, Length, Point, - Rectangle, Settings, Subscription, Vector, + Application, Color, Command, Container, Element, Length, Point, Rectangle, + Settings, Subscription, Theme, Vector, }; pub fn main() -> iced::Result { @@ -22,8 +25,9 @@ enum Message { } impl Application for Clock { - type Executor = executor::Default; type Message = Message; + type Theme = Theme; + type Executor = executor::Default; type Flags = (); fn new(_flags: ()) -> (Self, Command) { @@ -77,7 +81,12 @@ impl Application for Clock { } impl canvas::Program for Clock { - fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec { + fn draw( + &self, + _theme: &Theme, + bounds: Rectangle, + _cursor: Cursor, + ) -> Vec { let clock = self.clock.draw(bounds.size(), |frame| { let center = frame.center(); let radius = frame.width().min(frame.height()) / 2.0; diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index f5fab2516c..16c87a75c7 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -236,7 +236,12 @@ impl Theme { } impl canvas::Program for Theme { - fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec { + fn draw( + &self, + _theme: &iced::Theme, + bounds: Rectangle, + _cursor: Cursor, + ) -> Vec { let theme = self.canvas_cache.draw(bounds.size(), |frame| { self.draw(frame); }); @@ -288,7 +293,7 @@ impl ColorPicker { range: RangeInclusive, component: f32, update: impl Fn(f32) -> C + 'static, - ) -> Slider { + ) -> Slider { Slider::new(state, range, f64::from(component), move |v| { update(v as f32) }) diff --git a/examples/component/src/main.rs b/examples/component/src/main.rs index 39335cf119..b6ff060076 100644 --- a/examples/component/src/main.rs +++ b/examples/component/src/main.rs @@ -54,7 +54,7 @@ mod numeric_input { use iced_native::text; use iced_native::widget::button::{self, Button}; use iced_native::widget::text_input::{self, TextInput}; - use iced_native::widget::{Row, Text}; + use iced_native::widget::{self, Row, Text}; use iced_native::{Element, Length}; pub struct NumericInput<'a, Message> { @@ -95,6 +95,9 @@ mod numeric_input { for NumericInput<'a, Message> where Renderer: 'a + text::Renderer, + Renderer::Theme: button::StyleSheet + + text_input::StyleSheet + + widget::text::StyleSheet, { type Event = Event; @@ -172,6 +175,9 @@ mod numeric_input { where Message: 'a, Renderer: text::Renderer + 'a, + Renderer::Theme: button::StyleSheet + + text_input::StyleSheet + + widget::text::StyleSheet, { fn from(numeric_input: NumericInput<'a, Message>) -> Self { component::view(numeric_input) diff --git a/examples/counter/src/main.rs b/examples/counter/src/main.rs index 931cf5e175..e92f07f25d 100644 --- a/examples/counter/src/main.rs +++ b/examples/counter/src/main.rs @@ -1,6 +1,5 @@ -use iced::{ - button, Alignment, Button, Column, Element, Sandbox, Settings, Text, -}; +use iced::button::{self, Button}; +use iced::{Alignment, Column, Element, Sandbox, Settings, Text}; pub fn main() -> iced::Result { Counter::run(Settings::default()) diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index 28edf2562b..ce5306ba08 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -46,6 +46,7 @@ mod circle { fn draw( &self, renderer: &mut Renderer, + _theme: &Renderer::Theme, _style: &renderer::Style, layout: Layout<'_>, _cursor_position: Point, diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index 21804a0a2f..4a801ba4d6 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -1,6 +1,8 @@ +use iced::button; +use iced::executor; use iced::{ - button, executor, Alignment, Application, Button, Column, Command, - Container, Element, Length, ProgressBar, Settings, Subscription, Text, + Alignment, Application, Button, Column, Command, Container, Element, + Length, ProgressBar, Settings, Subscription, Text, Theme, }; mod download; @@ -24,8 +26,9 @@ pub enum Message { } impl Application for Example { - type Executor = executor::Default; type Message = Message; + type Theme = Theme; + type Executor = executor::Default; type Flags = (); fn new(_flags: ()) -> (Example, Command) { diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 7f024c56df..c87fbc7258 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -1,6 +1,9 @@ +use iced::alignment; +use iced::button; +use iced::executor; use iced::{ - alignment, button, executor, Alignment, Application, Button, Checkbox, - Column, Command, Container, Element, Length, Settings, Subscription, Text, + Alignment, Application, Button, Checkbox, Column, Command, Container, + Element, Length, Settings, Subscription, Text, Theme, }; use iced_native::{window, Event}; @@ -27,8 +30,9 @@ enum Message { } impl Application for Events { - type Executor = executor::Default; type Message = Message; + type Theme = Theme; + type Executor = executor::Default; type Flags = (); fn new(_flags: ()) -> (Events, Command) { diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index ab8b80e4e1..353995849d 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -1,18 +1,18 @@ //! This example showcases an interactive version of the Game of Life, invented //! by John Conway. It leverages a `Canvas` together with other widgets. mod preset; -mod style; use grid::Grid; use iced::button::{self, Button}; use iced::executor; use iced::pick_list::{self, PickList}; use iced::slider::{self, Slider}; +use iced::theme::{self, Theme}; use iced::time; use iced::window; use iced::{ - Alignment, Application, Checkbox, Column, Command, Container, Element, - Length, Row, Settings, Subscription, Text, + Alignment, Application, Checkbox, Column, Command, Element, Length, Row, + Settings, Subscription, Text, }; use preset::Preset; use std::time::{Duration, Instant}; @@ -55,6 +55,7 @@ enum Message { impl Application for GameOfLife { type Message = Message; + type Theme = Theme; type Executor = executor::Default; type Flags = (); @@ -141,20 +142,19 @@ impl Application for GameOfLife { self.grid.preset(), ); - let content = Column::new() + Column::new() .push( self.grid .view() .map(move |message| Message::Grid(message, version)), ) - .push(controls); - - Container::new(content) - .width(Length::Fill) - .height(Length::Fill) - .style(style::Container) + .push(controls) .into() } + + fn theme(&self) -> Theme { + Theme::Dark + } } mod grid { @@ -163,7 +163,7 @@ mod grid { alignment, canvas::event::{self, Event}, canvas::{self, Cache, Canvas, Cursor, Frame, Geometry, Path, Text}, - mouse, Color, Element, Length, Point, Rectangle, Size, Vector, + mouse, Color, Element, Length, Point, Rectangle, Size, Theme, Vector, }; use rustc_hash::{FxHashMap, FxHashSet}; use std::future::Future; @@ -445,7 +445,12 @@ mod grid { } } - fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec { + fn draw( + &self, + _theme: &Theme, + bounds: Rectangle, + cursor: Cursor, + ) -> Vec { let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0); let life = self.life_cache.draw(bounds.size(), |frame| { @@ -836,27 +841,24 @@ impl Controls { Text::new(if is_playing { "Pause" } else { "Play" }), ) .on_press(Message::TogglePlayback) - .style(style::Button), + .style(theme::Button::Primary), ) .push( Button::new(&mut self.next_button, Text::new("Next")) .on_press(Message::Next) - .style(style::Button), + .style(theme::Button::Secondary), ); let speed_controls = Row::new() .width(Length::Fill) .align_items(Alignment::Center) .spacing(10) - .push( - Slider::new( - &mut self.speed_slider, - 1.0..=1000.0, - speed as f32, - Message::SpeedChanged, - ) - .style(style::Slider), - ) + .push(Slider::new( + &mut self.speed_slider, + 1.0..=1000.0, + speed as f32, + Message::SpeedChanged, + )) .push(Text::new(format!("x{}", speed)).size(16)); Row::new() @@ -879,13 +881,12 @@ impl Controls { Message::PresetPicked, ) .padding(8) - .text_size(16) - .style(style::PickList), + .text_size(16), ) .push( Button::new(&mut self.clear_button, Text::new("Clear")) .on_press(Message::Clear) - .style(style::Clear), + .style(theme::Button::Destructive), ) .into() } diff --git a/examples/game_of_life/src/style.rs b/examples/game_of_life/src/style.rs deleted file mode 100644 index be9a0e9632..0000000000 --- a/examples/game_of_life/src/style.rs +++ /dev/null @@ -1,189 +0,0 @@ -use iced::{button, container, pick_list, slider, Background, Color}; - -const ACTIVE: Color = Color::from_rgb( - 0x72 as f32 / 255.0, - 0x89 as f32 / 255.0, - 0xDA as f32 / 255.0, -); - -const DESTRUCTIVE: Color = Color::from_rgb( - 0xC0 as f32 / 255.0, - 0x47 as f32 / 255.0, - 0x47 as f32 / 255.0, -); - -const HOVERED: Color = Color::from_rgb( - 0x67 as f32 / 255.0, - 0x7B as f32 / 255.0, - 0xC4 as f32 / 255.0, -); - -const BACKGROUND: Color = Color::from_rgb( - 0x2F as f32 / 255.0, - 0x31 as f32 / 255.0, - 0x36 as f32 / 255.0, -); - -pub struct Container; - -impl container::StyleSheet for Container { - fn style(&self) -> container::Style { - container::Style { - background: Some(Background::Color(Color::from_rgb8( - 0x36, 0x39, 0x3F, - ))), - text_color: Some(Color::WHITE), - ..container::Style::default() - } - } -} - -pub struct Button; - -impl button::StyleSheet for Button { - fn active(&self) -> button::Style { - button::Style { - background: Some(Background::Color(ACTIVE)), - border_radius: 3.0, - text_color: Color::WHITE, - ..button::Style::default() - } - } - - fn hovered(&self) -> button::Style { - button::Style { - background: Some(Background::Color(HOVERED)), - text_color: Color::WHITE, - ..self.active() - } - } - - fn pressed(&self) -> button::Style { - button::Style { - border_width: 1.0, - border_color: Color::WHITE, - ..self.hovered() - } - } -} - -pub struct Clear; - -impl button::StyleSheet for Clear { - fn active(&self) -> button::Style { - button::Style { - background: Some(Background::Color(DESTRUCTIVE)), - border_radius: 3.0, - text_color: Color::WHITE, - ..button::Style::default() - } - } - - fn hovered(&self) -> button::Style { - button::Style { - background: Some(Background::Color(Color { - a: 0.5, - ..DESTRUCTIVE - })), - text_color: Color::WHITE, - ..self.active() - } - } - - fn pressed(&self) -> button::Style { - button::Style { - border_width: 1.0, - border_color: Color::WHITE, - ..self.hovered() - } - } -} - -pub struct Slider; - -impl slider::StyleSheet for Slider { - fn active(&self) -> slider::Style { - slider::Style { - rail_colors: (ACTIVE, Color { a: 0.1, ..ACTIVE }), - handle: slider::Handle { - shape: slider::HandleShape::Circle { radius: 9.0 }, - color: ACTIVE, - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - } - } - - fn hovered(&self) -> slider::Style { - let active = self.active(); - - slider::Style { - handle: slider::Handle { - color: HOVERED, - ..active.handle - }, - ..active - } - } - - fn dragging(&self) -> slider::Style { - let active = self.active(); - - slider::Style { - handle: slider::Handle { - color: Color::from_rgb(0.85, 0.85, 0.85), - ..active.handle - }, - ..active - } - } -} - -pub struct PickList; - -impl pick_list::StyleSheet for PickList { - fn menu(&self) -> pick_list::Menu { - pick_list::Menu { - text_color: Color::WHITE, - background: BACKGROUND.into(), - border_width: 1.0, - border_color: Color { - a: 0.7, - ..Color::BLACK - }, - selected_background: Color { - a: 0.5, - ..Color::BLACK - } - .into(), - selected_text_color: Color::WHITE, - } - } - - fn active(&self) -> pick_list::Style { - pick_list::Style { - text_color: Color::WHITE, - background: BACKGROUND.into(), - border_width: 1.0, - border_color: Color { - a: 0.6, - ..Color::BLACK - }, - border_radius: 2.0, - icon_size: 0.5, - ..pick_list::Style::default() - } - } - - fn hovered(&self) -> pick_list::Style { - let active = self.active(); - - pick_list::Style { - border_color: Color { - a: 0.9, - ..Color::BLACK - }, - ..active - } - } -} diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 58dfa3ad84..ba4b808e3d 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -25,7 +25,7 @@ mod rainbow { } } - impl Widget> for Rainbow + impl Widget> for Rainbow where B: Backend, { @@ -39,7 +39,7 @@ mod rainbow { fn layout( &self, - _renderer: &Renderer, + _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { let size = limits.width(Length::Fill).resolve(Size::ZERO); @@ -49,7 +49,8 @@ mod rainbow { fn draw( &self, - renderer: &mut Renderer, + renderer: &mut Renderer, + _theme: &T, _style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, @@ -147,11 +148,11 @@ mod rainbow { } } - impl<'a, Message, B> Into>> for Rainbow + impl<'a, Message, B, T> Into>> for Rainbow where B: Backend, { - fn into(self) -> Element<'a, Message, Renderer> { + fn into(self) -> Element<'a, Message, Renderer> { Element::new(self) } } diff --git a/examples/integration_opengl/src/controls.rs b/examples/integration_opengl/src/controls.rs index f387b4e55b..fdaa29d57e 100644 --- a/examples/integration_opengl/src/controls.rs +++ b/examples/integration_opengl/src/controls.rs @@ -89,13 +89,13 @@ impl Program for Controls { .spacing(10) .push( Text::new("Background color") - .color(Color::WHITE), + .style(Color::WHITE), ) .push(sliders) .push( Text::new(format!("{:?}", background_color)) .size(14) - .color(Color::WHITE), + .style(Color::WHITE), ), ), ) diff --git a/examples/integration_opengl/src/main.rs b/examples/integration_opengl/src/main.rs index 1007b90f6b..1a78a493fa 100644 --- a/examples/integration_opengl/src/main.rs +++ b/examples/integration_opengl/src/main.rs @@ -12,7 +12,8 @@ use iced_glow::glow; use iced_glow::{Backend, Renderer, Settings, Viewport}; use iced_glutin::conversion; use iced_glutin::glutin; -use iced_glutin::{program, Clipboard, Debug, Size}; +use iced_glutin::renderer; +use iced_glutin::{program, Clipboard, Color, Debug, Size}; pub fn main() { env_logger::init(); @@ -125,6 +126,10 @@ pub fn main() { viewport.scale_factor(), ), &mut renderer, + &iced_glow::Theme::Dark, + &renderer::Style { + text_color: Color::WHITE, + }, &mut clipboard, &mut debug, ); diff --git a/examples/integration_wgpu/src/controls.rs b/examples/integration_wgpu/src/controls.rs index 9bca40ebf0..cb2c423f3c 100644 --- a/examples/integration_wgpu/src/controls.rs +++ b/examples/integration_wgpu/src/controls.rs @@ -100,13 +100,13 @@ impl Program for Controls { .spacing(10) .push( Text::new("Background color") - .color(Color::WHITE), + .style(Color::WHITE), ) .push(sliders) .push( Text::new(format!("{:?}", background_color)) .size(14) - .color(Color::WHITE), + .style(Color::WHITE), ) .push(TextInput::new( t, diff --git a/examples/integration_wgpu/src/main.rs b/examples/integration_wgpu/src/main.rs index 89ae03c6b7..3d27a0f03a 100644 --- a/examples/integration_wgpu/src/main.rs +++ b/examples/integration_wgpu/src/main.rs @@ -5,7 +5,10 @@ use controls::Controls; use scene::Scene; use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport}; -use iced_winit::{conversion, futures, program, winit, Clipboard, Debug, Size}; +use iced_winit::{ + conversion, futures, program, renderer, winit, Clipboard, Color, Debug, + Size, +}; use winit::{ dpi::PhysicalPosition, @@ -188,6 +191,8 @@ pub fn main() { viewport.scale_factor(), ), &mut renderer, + &iced_wgpu::Theme::Dark, + &renderer::Style { text_color: Color::WHITE }, &mut clipboard, &mut debug, ); diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 2962ca259e..5fbcea2c4e 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -4,6 +4,7 @@ use iced::executor; use iced::keyboard; use iced::pane_grid::{self, PaneGrid}; use iced::scrollable::{self, Scrollable}; +use iced::theme::{self, Theme}; use iced::{ Application, Color, Column, Command, Container, Element, Length, Row, Settings, Size, Subscription, Text, @@ -36,6 +37,7 @@ enum Message { impl Application for Example { type Message = Message; + type Theme = Theme; type Executor = executor::Default; type Flags = (); @@ -171,14 +173,14 @@ impl Application for Example { let text = if *is_pinned { "Unpin" } else { "Pin" }; let pin_button = Button::new(pin_button, Text::new(text).size(14)) .on_press(Message::TogglePin(id)) - .style(style::Button::Pin) + .style(theme::Button::Secondary) .padding(3); let title = Row::with_children(vec![ pin_button.into(), Text::new("Pane").into(), Text::new(content.id.to_string()) - .color(if is_focused { + .style(if is_focused { PANE_ID_COLOR_FOCUSED } else { PANE_ID_COLOR_UNFOCUSED @@ -191,9 +193,9 @@ impl Application for Example { .controls(pane.controls.view(id, total_panes, *is_pinned)) .padding(10) .style(if is_focused { - style::TitleBar::Focused + style::title_bar_focused } else { - style::TitleBar::Active + style::title_bar_active }); pane_grid::Content::new(Responsive::new(responsive, move |size| { @@ -201,9 +203,9 @@ impl Application for Example { })) .title_bar(title_bar) .style(if is_focused { - style::Pane::Focused + style::pane_focused } else { - style::Pane::Active + style::pane_active }) }) .width(Length::Fill) @@ -309,7 +311,7 @@ impl Content { .. } = self; - let button = |state, label, message, style| { + let button = |state, label, message| { Button::new( state, Text::new(label) @@ -320,7 +322,6 @@ impl Content { .width(Length::Fill) .padding(8) .on_press(message) - .style(style) }; let mut controls = Column::new() @@ -330,22 +331,18 @@ impl Content { split_horizontally, "Split horizontally", Message::Split(pane_grid::Axis::Horizontal, pane), - style::Button::Primary, )) .push(button( split_vertically, "Split vertically", Message::Split(pane_grid::Axis::Vertical, pane), - style::Button::Primary, )); if total_panes > 1 && !is_pinned { - controls = controls.push(button( - close, - "Close", - Message::Close(pane), - style::Button::Destructive, - )); + controls = controls.push( + button(close, "Close", Message::Close(pane)) + .style(theme::Button::Destructive), + ); } let content = Scrollable::new(scroll) @@ -379,8 +376,9 @@ impl Controls { ) -> Element { let mut button = Button::new(&mut self.close, Text::new("Close").size(14)) - .style(style::Button::Control) + .style(theme::Button::Destructive) .padding(3); + if total_panes > 1 && !is_pinned { button = button.on_press(Message::Close(pane)); } @@ -389,111 +387,47 @@ impl Controls { } mod style { - use crate::PANE_ID_COLOR_FOCUSED; - use iced::{button, container, Background, Color, Vector}; - - const SURFACE: Color = Color::from_rgb( - 0xF2 as f32 / 255.0, - 0xF3 as f32 / 255.0, - 0xF5 as f32 / 255.0, - ); - - const ACTIVE: Color = Color::from_rgb( - 0x72 as f32 / 255.0, - 0x89 as f32 / 255.0, - 0xDA as f32 / 255.0, - ); - - const HOVERED: Color = Color::from_rgb( - 0x67 as f32 / 255.0, - 0x7B as f32 / 255.0, - 0xC4 as f32 / 255.0, - ); - - pub enum TitleBar { - Active, - Focused, - } + use iced::{container, Theme}; - impl container::StyleSheet for TitleBar { - fn style(&self) -> container::Style { - let pane = match self { - Self::Active => Pane::Active, - Self::Focused => Pane::Focused, - } - .style(); + pub fn title_bar_active(theme: &Theme) -> container::Appearance { + let palette = theme.extended_palette(); - container::Style { - text_color: Some(Color::WHITE), - background: Some(pane.border_color.into()), - ..Default::default() - } + container::Appearance { + text_color: Some(palette.background.strong.text), + background: Some(palette.background.strong.color.into()), + ..Default::default() } } - pub enum Pane { - Active, - Focused, - } + pub fn title_bar_focused(theme: &Theme) -> container::Appearance { + let palette = theme.extended_palette(); - impl container::StyleSheet for Pane { - fn style(&self) -> container::Style { - container::Style { - background: Some(Background::Color(SURFACE)), - border_width: 2.0, - border_color: match self { - Self::Active => Color::from_rgb(0.7, 0.7, 0.7), - Self::Focused => Color::BLACK, - }, - ..Default::default() - } + container::Appearance { + text_color: Some(palette.primary.strong.text), + background: Some(palette.primary.strong.color.into()), + ..Default::default() } } - pub enum Button { - Primary, - Destructive, - Control, - Pin, - } + pub fn pane_active(theme: &Theme) -> container::Appearance { + let palette = theme.extended_palette(); - impl button::StyleSheet for Button { - fn active(&self) -> button::Style { - let (background, text_color) = match self { - Button::Primary => (Some(ACTIVE), Color::WHITE), - Button::Destructive => { - (None, Color::from_rgb8(0xFF, 0x47, 0x47)) - } - Button::Control => (Some(PANE_ID_COLOR_FOCUSED), Color::WHITE), - Button::Pin => (Some(ACTIVE), Color::WHITE), - }; - - button::Style { - text_color, - background: background.map(Background::Color), - border_radius: 5.0, - shadow_offset: Vector::new(0.0, 0.0), - ..button::Style::default() - } + container::Appearance { + background: Some(palette.background.weak.color.into()), + border_width: 2.0, + border_color: palette.background.strong.color, + ..Default::default() } + } - fn hovered(&self) -> button::Style { - let active = self.active(); - - let background = match self { - Button::Primary => Some(HOVERED), - Button::Destructive => Some(Color { - a: 0.2, - ..active.text_color - }), - Button::Control => Some(PANE_ID_COLOR_FOCUSED), - Button::Pin => Some(HOVERED), - }; - - button::Style { - background: background.map(Background::Color), - ..active - } + pub fn pane_focused(theme: &Theme) -> container::Appearance { + let palette = theme.extended_palette(); + + container::Appearance { + background: Some(palette.background.weak.color.into()), + border_width: 2.0, + border_color: palette.primary.strong.color, + ..Default::default() } } } diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs index 85c2698719..89d865e4dc 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -1,6 +1,9 @@ +use iced::button; +use iced::futures; +use iced::image; use iced::{ - button, futures, image, Alignment, Application, Button, Column, Command, - Container, Element, Length, Row, Settings, Text, + Alignment, Application, Button, Color, Column, Command, Container, Element, + Length, Row, Settings, Text, Theme, }; pub fn main() -> iced::Result { @@ -26,8 +29,9 @@ enum Message { } impl Application for Pokedex { - type Executor = iced::executor::Default; type Message = Message; + type Theme = Theme; + type Executor = iced::executor::Default; type Flags = (); fn new(_flags: ()) -> (Pokedex, Command) { @@ -139,7 +143,7 @@ impl Pokemon { .push( Text::new(format!("#{}", self.number)) .size(20) - .color([0.5, 0.5, 0.5]), + .style(Color::from([0.5, 0.5, 0.5])), ), ) .push(Text::new(&self.description)), @@ -238,29 +242,5 @@ impl From for Error { } fn button<'a>(state: &'a mut button::State, text: &str) -> Button<'a, Message> { - Button::new(state, Text::new(text)) - .padding(10) - .style(style::Button::Primary) -} - -mod style { - use iced::{button, Background, Color, Vector}; - - pub enum Button { - Primary, - } - - impl button::StyleSheet for Button { - fn active(&self) -> button::Style { - button::Style { - background: Some(Background::Color(match self { - Button::Primary => Color::from_rgb(0.11, 0.42, 0.87), - })), - border_radius: 12.0, - shadow_offset: Vector::new(1.0, 1.0), - text_color: Color::WHITE, - ..button::Style::default() - } - } - } + Button::new(state, Text::new(text)).padding(10) } diff --git a/examples/pure/component/src/main.rs b/examples/pure/component/src/main.rs index b38d6fca89..64935afd8e 100644 --- a/examples/pure/component/src/main.rs +++ b/examples/pure/component/src/main.rs @@ -47,12 +47,13 @@ impl Sandbox for Component { } mod numeric_input { - use iced::pure::{button, row, text, text_input}; use iced_lazy::pure::{self, Component}; use iced_native::alignment::{self, Alignment}; use iced_native::text; + use iced_native::widget; use iced_native::Length; use iced_pure::Element; + use iced_pure::{button, row, text, text_input}; pub struct NumericInput { value: Option, @@ -88,6 +89,9 @@ mod numeric_input { impl Component for NumericInput where Renderer: text::Renderer + 'static, + Renderer::Theme: widget::button::StyleSheet + + widget::text_input::StyleSheet + + widget::text::StyleSheet, { type State = (); type Event = Event; @@ -158,6 +162,9 @@ mod numeric_input { where Message: 'a, Renderer: 'static + text::Renderer, + Renderer::Theme: widget::button::StyleSheet + + widget::text_input::StyleSheet + + widget::text::StyleSheet, { fn from(numeric_input: NumericInput) -> Self { pure::component(numeric_input) diff --git a/examples/pure/game_of_life/src/main.rs b/examples/pure/game_of_life/src/main.rs index a316470152..851fbd47c7 100644 --- a/examples/pure/game_of_life/src/main.rs +++ b/examples/pure/game_of_life/src/main.rs @@ -1,18 +1,19 @@ //! This example showcases an interactive version of the Game of Life, invented //! by John Conway. It leverages a `Canvas` together with other widgets. mod preset; -mod style; use grid::Grid; +use preset::Preset; + use iced::executor; use iced::pure::{ button, checkbox, column, container, pick_list, row, slider, text, }; use iced::pure::{Application, Element}; +use iced::theme::{self, Theme}; use iced::time; use iced::window; -use iced::{Alignment, Color, Command, Length, Settings, Subscription}; -use preset::Preset; +use iced::{Alignment, Command, Length, Settings, Subscription}; use std::time::{Duration, Instant}; pub fn main() -> iced::Result { @@ -52,6 +53,7 @@ enum Message { impl Application for GameOfLife { type Message = Message; + type Theme = Theme; type Executor = executor::Default; type Flags = (); @@ -69,10 +71,6 @@ impl Application for GameOfLife { String::from("Game of Life - Iced") } - fn background_color(&self) -> Color { - style::BACKGROUND - } - fn update(&mut self, message: Message) -> Command { match message { Message::Grid(message, version) => { @@ -153,9 +151,12 @@ impl Application for GameOfLife { container(content) .width(Length::Fill) .height(Length::Fill) - .style(style::Container) .into() } + + fn theme(&self) -> Theme { + Theme::Dark + } } fn view_controls<'a>( @@ -168,19 +169,19 @@ fn view_controls<'a>( .spacing(10) .push( button(if is_playing { "Pause" } else { "Play" }) - .on_press(Message::TogglePlayback) - .style(style::Button), + .on_press(Message::TogglePlayback), ) - .push(button("Next").on_press(Message::Next).style(style::Button)); + .push( + button("Next") + .on_press(Message::Next) + .style(theme::Button::Secondary), + ); let speed_controls = row() .width(Length::Fill) .align_items(Alignment::Center) .spacing(10) - .push( - slider(1.0..=1000.0, speed as f32, Message::SpeedChanged) - .style(style::Slider), - ) + .push(slider(1.0..=1000.0, speed as f32, Message::SpeedChanged)) .push(text(format!("x{}", speed)).size(16)); row() @@ -198,10 +199,13 @@ fn view_controls<'a>( .push( pick_list(preset::ALL, Some(preset), Message::PresetPicked) .padding(8) - .text_size(16) - .style(style::PickList), + .text_size(16), + ) + .push( + button("Clear") + .on_press(Message::Clear) + .style(theme::Button::Destructive), ) - .push(button("Clear").on_press(Message::Clear).style(style::Clear)) .into() } @@ -213,7 +217,7 @@ mod grid { }; use iced::pure::Element; use iced::{ - alignment, mouse, Color, Length, Point, Rectangle, Size, Vector, + alignment, mouse, Color, Length, Point, Rectangle, Size, Theme, Vector, }; use rustc_hash::{FxHashMap, FxHashSet}; use std::future::Future; @@ -522,6 +526,7 @@ mod grid { fn draw( &self, _interaction: &Interaction, + _theme: &Theme, bounds: Rectangle, cursor: Cursor, ) -> Vec { diff --git a/examples/pure/game_of_life/src/style.rs b/examples/pure/game_of_life/src/style.rs deleted file mode 100644 index 1a64cf4a7c..0000000000 --- a/examples/pure/game_of_life/src/style.rs +++ /dev/null @@ -1,186 +0,0 @@ -use iced::{button, container, pick_list, slider, Background, Color}; - -const ACTIVE: Color = Color::from_rgb( - 0x72 as f32 / 255.0, - 0x89 as f32 / 255.0, - 0xDA as f32 / 255.0, -); - -const DESTRUCTIVE: Color = Color::from_rgb( - 0xC0 as f32 / 255.0, - 0x47 as f32 / 255.0, - 0x47 as f32 / 255.0, -); - -const HOVERED: Color = Color::from_rgb( - 0x67 as f32 / 255.0, - 0x7B as f32 / 255.0, - 0xC4 as f32 / 255.0, -); - -pub const BACKGROUND: Color = Color::from_rgb( - 0x2F as f32 / 255.0, - 0x31 as f32 / 255.0, - 0x36 as f32 / 255.0, -); - -pub struct Container; - -impl container::StyleSheet for Container { - fn style(&self) -> container::Style { - container::Style { - text_color: Some(Color::WHITE), - ..container::Style::default() - } - } -} - -pub struct Button; - -impl button::StyleSheet for Button { - fn active(&self) -> button::Style { - button::Style { - background: Some(Background::Color(ACTIVE)), - border_radius: 3.0, - text_color: Color::WHITE, - ..button::Style::default() - } - } - - fn hovered(&self) -> button::Style { - button::Style { - background: Some(Background::Color(HOVERED)), - text_color: Color::WHITE, - ..self.active() - } - } - - fn pressed(&self) -> button::Style { - button::Style { - border_width: 1.0, - border_color: Color::WHITE, - ..self.hovered() - } - } -} - -pub struct Clear; - -impl button::StyleSheet for Clear { - fn active(&self) -> button::Style { - button::Style { - background: Some(Background::Color(DESTRUCTIVE)), - border_radius: 3.0, - text_color: Color::WHITE, - ..button::Style::default() - } - } - - fn hovered(&self) -> button::Style { - button::Style { - background: Some(Background::Color(Color { - a: 0.5, - ..DESTRUCTIVE - })), - text_color: Color::WHITE, - ..self.active() - } - } - - fn pressed(&self) -> button::Style { - button::Style { - border_width: 1.0, - border_color: Color::WHITE, - ..self.hovered() - } - } -} - -pub struct Slider; - -impl slider::StyleSheet for Slider { - fn active(&self) -> slider::Style { - slider::Style { - rail_colors: (ACTIVE, Color { a: 0.1, ..ACTIVE }), - handle: slider::Handle { - shape: slider::HandleShape::Circle { radius: 9.0 }, - color: ACTIVE, - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - } - } - - fn hovered(&self) -> slider::Style { - let active = self.active(); - - slider::Style { - handle: slider::Handle { - color: HOVERED, - ..active.handle - }, - ..active - } - } - - fn dragging(&self) -> slider::Style { - let active = self.active(); - - slider::Style { - handle: slider::Handle { - color: Color::from_rgb(0.85, 0.85, 0.85), - ..active.handle - }, - ..active - } - } -} - -pub struct PickList; - -impl pick_list::StyleSheet for PickList { - fn menu(&self) -> pick_list::Menu { - pick_list::Menu { - text_color: Color::WHITE, - background: BACKGROUND.into(), - border_width: 1.0, - border_color: Color { - a: 0.7, - ..Color::BLACK - }, - selected_background: Color { - a: 0.5, - ..Color::BLACK - } - .into(), - selected_text_color: Color::WHITE, - } - } - - fn active(&self) -> pick_list::Style { - pick_list::Style { - text_color: Color::WHITE, - background: BACKGROUND.into(), - border_width: 1.0, - border_color: Color { - a: 0.6, - ..Color::BLACK - }, - border_radius: 2.0, - icon_size: 0.5, - ..pick_list::Style::default() - } - } - - fn hovered(&self) -> pick_list::Style { - let active = self.active(); - - pick_list::Style { - border_color: Color { - a: 0.9, - ..Color::BLACK - }, - ..active - } - } -} diff --git a/examples/pure/pane_grid/src/main.rs b/examples/pure/pane_grid/src/main.rs index 6551695668..e85ed78dcf 100644 --- a/examples/pure/pane_grid/src/main.rs +++ b/examples/pure/pane_grid/src/main.rs @@ -4,6 +4,7 @@ use iced::keyboard; use iced::pure::widget::pane_grid::{self, PaneGrid}; use iced::pure::{button, column, container, row, scrollable, text}; use iced::pure::{Application, Element}; +use iced::theme::{self, Theme}; use iced::{Color, Command, Length, Settings, Size, Subscription}; use iced_lazy::pure::responsive; use iced_native::{event, subscription, Event}; @@ -33,6 +34,7 @@ enum Message { impl Application for Example { type Message = Message; + type Theme = Theme; type Executor = executor::Default; type Flags = (); @@ -161,13 +163,12 @@ impl Application for Example { text(if pane.is_pinned { "Unpin" } else { "Pin" }).size(14), ) .on_press(Message::TogglePin(id)) - .style(style::Button::Pin) .padding(3); let title = row() .push(pin_button) .push("Pane") - .push(text(pane.id.to_string()).color(if is_focused { + .push(text(pane.id.to_string()).style(if is_focused { PANE_ID_COLOR_FOCUSED } else { PANE_ID_COLOR_UNFOCUSED @@ -178,9 +179,9 @@ impl Application for Example { .controls(view_controls(id, total_panes, pane.is_pinned)) .padding(10) .style(if is_focused { - style::TitleBar::Focused + style::title_bar_focused } else { - style::TitleBar::Active + style::title_bar_active }); pane_grid::Content::new(responsive(move |size| { @@ -188,9 +189,9 @@ impl Application for Example { })) .title_bar(title_bar) .style(if is_focused { - style::Pane::Focused + style::pane_focused } else { - style::Pane::Active + style::pane_active }) }) .width(Length::Fill) @@ -259,7 +260,7 @@ fn view_content<'a>( is_pinned: bool, size: Size, ) -> Element<'a, Message> { - let button = |label, message, style| { + let button = |label, message| { button( text(label) .width(Length::Fill) @@ -269,7 +270,6 @@ fn view_content<'a>( .width(Length::Fill) .padding(8) .on_press(message) - .style(style) }; let mut controls = column() @@ -278,20 +278,17 @@ fn view_content<'a>( .push(button( "Split horizontally", Message::Split(pane_grid::Axis::Horizontal, pane), - style::Button::Primary, )) .push(button( "Split vertically", Message::Split(pane_grid::Axis::Vertical, pane), - style::Button::Primary, )); if total_panes > 1 && !is_pinned { - controls = controls.push(button( - "Close", - Message::Close(pane), - style::Button::Destructive, - )); + controls = controls.push( + button("Close", Message::Close(pane)) + .style(theme::Button::Destructive), + ); } let content = column() @@ -315,7 +312,7 @@ fn view_controls<'a>( is_pinned: bool, ) -> Element<'a, Message> { let mut button = button(text("Close").size(14)) - .style(style::Button::Control) + .style(theme::Button::Destructive) .padding(3); if total_panes > 1 && !is_pinned { @@ -326,111 +323,47 @@ fn view_controls<'a>( } mod style { - use crate::PANE_ID_COLOR_FOCUSED; - use iced::{button, container, Background, Color, Vector}; - - const SURFACE: Color = Color::from_rgb( - 0xF2 as f32 / 255.0, - 0xF3 as f32 / 255.0, - 0xF5 as f32 / 255.0, - ); - - const ACTIVE: Color = Color::from_rgb( - 0x72 as f32 / 255.0, - 0x89 as f32 / 255.0, - 0xDA as f32 / 255.0, - ); - - const HOVERED: Color = Color::from_rgb( - 0x67 as f32 / 255.0, - 0x7B as f32 / 255.0, - 0xC4 as f32 / 255.0, - ); - - pub enum TitleBar { - Active, - Focused, - } + use iced::{container, Theme}; - impl container::StyleSheet for TitleBar { - fn style(&self) -> container::Style { - let pane = match self { - Self::Active => Pane::Active, - Self::Focused => Pane::Focused, - } - .style(); + pub fn title_bar_active(theme: &Theme) -> container::Appearance { + let palette = theme.extended_palette(); - container::Style { - text_color: Some(Color::WHITE), - background: Some(pane.border_color.into()), - ..Default::default() - } + container::Appearance { + text_color: Some(palette.background.strong.text), + background: Some(palette.background.strong.color.into()), + ..Default::default() } } - pub enum Pane { - Active, - Focused, - } + pub fn title_bar_focused(theme: &Theme) -> container::Appearance { + let palette = theme.extended_palette(); - impl container::StyleSheet for Pane { - fn style(&self) -> container::Style { - container::Style { - background: Some(Background::Color(SURFACE)), - border_width: 2.0, - border_color: match self { - Self::Active => Color::from_rgb(0.7, 0.7, 0.7), - Self::Focused => Color::BLACK, - }, - ..Default::default() - } + container::Appearance { + text_color: Some(palette.primary.strong.text), + background: Some(palette.primary.strong.color.into()), + ..Default::default() } } - pub enum Button { - Primary, - Destructive, - Control, - Pin, - } + pub fn pane_active(theme: &Theme) -> container::Appearance { + let palette = theme.extended_palette(); - impl button::StyleSheet for Button { - fn active(&self) -> button::Style { - let (background, text_color) = match self { - Button::Primary => (Some(ACTIVE), Color::WHITE), - Button::Destructive => { - (None, Color::from_rgb8(0xFF, 0x47, 0x47)) - } - Button::Control => (Some(PANE_ID_COLOR_FOCUSED), Color::WHITE), - Button::Pin => (Some(ACTIVE), Color::WHITE), - }; - - button::Style { - text_color, - background: background.map(Background::Color), - border_radius: 5.0, - shadow_offset: Vector::new(0.0, 0.0), - ..button::Style::default() - } + container::Appearance { + background: Some(palette.background.weak.color.into()), + border_width: 2.0, + border_color: palette.background.strong.color, + ..Default::default() } + } - fn hovered(&self) -> button::Style { - let active = self.active(); - - let background = match self { - Button::Primary => Some(HOVERED), - Button::Destructive => Some(Color { - a: 0.2, - ..active.text_color - }), - Button::Control => Some(PANE_ID_COLOR_FOCUSED), - Button::Pin => Some(HOVERED), - }; - - button::Style { - background: background.map(Background::Color), - ..active - } + pub fn pane_focused(theme: &Theme) -> container::Appearance { + let palette = theme.extended_palette(); + + container::Appearance { + background: Some(palette.background.weak.color.into()), + border_width: 2.0, + border_color: palette.primary.strong.color, + ..Default::default() } } } diff --git a/examples/pure/todos/src/main.rs b/examples/pure/todos/src/main.rs index 6a6c630019..723386ad1b 100644 --- a/examples/pure/todos/src/main.rs +++ b/examples/pure/todos/src/main.rs @@ -4,8 +4,9 @@ use iced::pure::{ button, checkbox, column, container, row, scrollable, text, text_input, Application, Element, }; +use iced::theme::{self, Theme}; use iced::window; -use iced::{Command, Font, Length, Settings}; +use iced::{Color, Command, Font, Length, Settings}; use serde::{Deserialize, Serialize}; pub fn main() -> iced::Result { @@ -44,8 +45,9 @@ enum Message { } impl Application for Todos { - type Executor = iced::executor::Default; type Message = Message; + type Theme = Theme; + type Executor = iced::executor::Default; type Flags = (); fn new(_flags: ()) -> (Todos, Command) { @@ -153,7 +155,7 @@ impl Application for Todos { let title = text("todos") .width(Length::Fill) .size(100) - .color([0.5, 0.5, 0.5]) + .style(Color::from([0.5, 0.5, 0.5])) .horizontal_alignment(alignment::Horizontal::Center); let input = text_input( @@ -287,7 +289,7 @@ impl Task { button(edit_icon()) .on_press(TaskMessage::Edit) .padding(10) - .style(style::Button::Icon), + .style(theme::Button::Text), ) .into() } @@ -313,7 +315,7 @@ impl Task { ) .on_press(TaskMessage::Delete) .padding(10) - .style(style::Button::Destructive), + .style(theme::Button::Destructive), ) .into() } @@ -328,9 +330,9 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element { let label = text(label).size(16); let button = button(label).style(if filter == current_filter { - style::Button::FilterSelected + theme::Button::Primary } else { - style::Button::FilterActive + theme::Button::Text }); button.on_press(Message::FilterChanged(filter)).padding(8) @@ -404,7 +406,7 @@ fn empty_message(message: &str) -> Element<'_, Message> { .width(Length::Fill) .size(25) .horizontal_alignment(alignment::Horizontal::Center) - .color([0.7, 0.7, 0.7]), + .style(Color::from([0.7, 0.7, 0.7])), ) .width(Length::Fill) .height(Length::Units(200)) @@ -552,57 +554,3 @@ impl SavedState { Ok(()) } } - -mod style { - use iced::{button, Background, Color, Vector}; - - pub enum Button { - FilterActive, - FilterSelected, - Icon, - Destructive, - } - - impl button::StyleSheet for Button { - fn active(&self) -> button::Style { - match self { - Button::FilterActive => button::Style::default(), - Button::FilterSelected => button::Style { - background: Some(Background::Color(Color::from_rgb( - 0.2, 0.2, 0.7, - ))), - border_radius: 10.0, - text_color: Color::WHITE, - ..button::Style::default() - }, - Button::Icon => button::Style { - text_color: Color::from_rgb(0.5, 0.5, 0.5), - ..button::Style::default() - }, - Button::Destructive => button::Style { - background: Some(Background::Color(Color::from_rgb( - 0.8, 0.2, 0.2, - ))), - border_radius: 5.0, - text_color: Color::WHITE, - shadow_offset: Vector::new(1.0, 1.0), - ..button::Style::default() - }, - } - } - - fn hovered(&self) -> button::Style { - let active = self.active(); - - button::Style { - text_color: match self { - Button::Icon => Color::from_rgb(0.2, 0.2, 0.7), - Button::FilterActive => Color::from_rgb(0.2, 0.2, 0.7), - _ => active.text_color, - }, - shadow_offset: active.shadow_offset + Vector::new(0.0, 1.0), - ..active - } - } - } -} diff --git a/examples/pure/tooltip/src/main.rs b/examples/pure/tooltip/src/main.rs index dbd83f5f45..e9a6c11146 100644 --- a/examples/pure/tooltip/src/main.rs +++ b/examples/pure/tooltip/src/main.rs @@ -1,6 +1,7 @@ -use iced::pure::{ - button, container, tooltip, widget::tooltip::Position, Element, Sandbox, -}; +use iced::pure::widget::tooltip::Position; +use iced::pure::{button, container, tooltip}; +use iced::pure::{Element, Sandbox}; +use iced::theme; use iced::{Length, Settings}; pub fn main() -> iced::Result { @@ -53,7 +54,7 @@ impl Sandbox for Example { self.position, ) .gap(10) - .style(style::Tooltip); + .style(theme::Container::Box); container(tooltip) .width(Length::Fill) @@ -73,21 +74,3 @@ fn position_to_text<'a>(position: Position) -> &'a str { Position::Right => "Right", } } - -mod style { - use iced::container; - use iced::Color; - - pub struct Tooltip; - - impl container::StyleSheet for Tooltip { - fn style(&self) -> container::Style { - container::Style { - text_color: Some(Color::from_rgb8(0xEE, 0xEE, 0xEE)), - background: Some(Color::from_rgb(0.11, 0.42, 0.87).into()), - border_radius: 12.0, - ..container::Style::default() - } - } - } -} diff --git a/examples/pure/tour/src/main.rs b/examples/pure/tour/src/main.rs index a44d99f366..477a1ec7d3 100644 --- a/examples/pure/tour/src/main.rs +++ b/examples/pure/tour/src/main.rs @@ -5,7 +5,8 @@ use iced::pure::{ scrollable, slider, text, text_input, toggler, vertical_space, }; use iced::pure::{Element, Sandbox}; -use iced::{Color, Length, Settings}; +use iced::theme; +use iced::{Color, Length, Renderer, Settings}; pub fn main() -> iced::Result { env_logger::init(); @@ -55,7 +56,7 @@ impl Sandbox for Tour { controls = controls.push( button("Back") .on_press(Message::BackPressed) - .style(style::Button::Secondary), + .style(theme::Button::Secondary), ); } @@ -65,7 +66,7 @@ impl Sandbox for Tour { controls = controls.push( button("Next") .on_press(Message::NextPressed) - .style(style::Button::Primary), + .style(theme::Button::Primary), ); } @@ -432,7 +433,7 @@ impl<'a> Step { .padding(20) .spacing(20) .push("And its color:") - .push(text(format!("{:?}", color)).color(color)) + .push(text(format!("{:?}", color)).style(color)) .push(color_sliders); Self::container("Text") @@ -575,7 +576,7 @@ impl<'a> Step { .push(if cfg!(target_arch = "wasm32") { Element::new( text("Not available on web yet!") - .color([0.7, 0.7, 0.7]) + .style(Color::from([0.7, 0.7, 0.7])) .horizontal_alignment(alignment::Horizontal::Center), ) } else { @@ -621,7 +622,7 @@ fn button<'a, Message: Clone>(label: &str) -> Button<'a, Message> { fn color_slider<'a>( component: f32, update: impl Fn(f32) -> Color + 'a, -) -> Slider<'a, f64, StepMessage> { +) -> Slider<'a, f64, StepMessage, Renderer> { slider(0.0..=1.0, f64::from(component), move |c| { StepMessage::TextColorChanged(update(c as f32)) }) @@ -669,35 +670,3 @@ pub enum Layout { Row, Column, } - -mod style { - use iced::{button, Background, Color, Vector}; - - pub enum Button { - Primary, - Secondary, - } - - impl button::StyleSheet for Button { - fn active(&self) -> button::Style { - button::Style { - background: Some(Background::Color(match self { - Button::Primary => Color::from_rgb(0.11, 0.42, 0.87), - Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5), - })), - border_radius: 12.0, - shadow_offset: Vector::new(1.0, 1.0), - text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE), - ..button::Style::default() - } - } - - fn hovered(&self) -> button::Style { - button::Style { - text_color: Color::WHITE, - shadow_offset: Vector::new(1.0, 2.0), - ..self.active() - } - } - } -} diff --git a/examples/qr_code/src/main.rs b/examples/qr_code/src/main.rs index 92c82d45e9..3e9ba9213d 100644 --- a/examples/qr_code/src/main.rs +++ b/examples/qr_code/src/main.rs @@ -1,7 +1,8 @@ use iced::qr_code::{self, QRCode}; use iced::text_input::{self, TextInput}; use iced::{ - Alignment, Column, Container, Element, Length, Sandbox, Settings, Text, + Alignment, Color, Column, Container, Element, Length, Sandbox, Settings, + Text, }; pub fn main() -> iced::Result { @@ -48,7 +49,7 @@ impl Sandbox for QRGenerator { fn view(&mut self) -> Element { let title = Text::new("QR Code Generator") .size(70) - .color([0.5, 0.5, 0.5]); + .style(Color::from([0.5, 0.5, 0.5])); let input = TextInput::new( &mut self.input, diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index 8e027504b8..f66d21806f 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -1,8 +1,8 @@ -mod style; - +use iced::button; +use iced::scrollable; use iced::{ - button, scrollable, Button, Column, Container, Element, Length, - ProgressBar, Radio, Row, Rule, Sandbox, Scrollable, Settings, Space, Text, + Button, Column, Container, Element, Length, ProgressBar, Radio, Row, Rule, + Sandbox, Scrollable, Settings, Space, Text, Theme, }; pub fn main() -> iced::Result { @@ -10,13 +10,13 @@ pub fn main() -> iced::Result { } struct ScrollableDemo { - theme: style::Theme, + theme: Theme, variants: Vec, } #[derive(Debug, Clone)] enum Message { - ThemeChanged(style::Theme), + ThemeChanged(Theme), ScrollToTop(usize), ScrollToBottom(usize), Scrolled(usize, f32), @@ -66,18 +66,15 @@ impl Sandbox for ScrollableDemo { theme, variants, .. } = self; - let choose_theme = style::Theme::ALL.iter().fold( + let choose_theme = [Theme::Light, Theme::Dark].iter().fold( Column::new().spacing(10).push(Text::new("Choose a theme:")), |column, option| { - column.push( - Radio::new( - *option, - format!("{:?}", option), - Some(*theme), - Message::ThemeChanged, - ) - .style(*theme), - ) + column.push(Radio::new( + *option, + format!("{:?}", option), + Some(*theme), + Message::ThemeChanged, + )) }, ); @@ -95,7 +92,6 @@ impl Sandbox for ScrollableDemo { .on_scroll(move |offset| { Message::Scrolled(i, offset) }) - .style(*theme) .push(Text::new(variant.title)) .push( Button::new( @@ -160,12 +156,7 @@ impl Sandbox for ScrollableDemo { .width(Length::Fill) .height(Length::Fill) .spacing(10) - .push( - Container::new(scrollable) - .width(Length::Fill) - .height(Length::Fill) - .style(*theme), - ) + .push(scrollable) .push(ProgressBar::new( 0.0..=1.0, variant.latest_offset, @@ -182,7 +173,7 @@ impl Sandbox for ScrollableDemo { .spacing(20) .padding(20) .push(choose_theme) - .push(Rule::horizontal(20).style(self.theme)) + .push(Rule::horizontal(20)) .push(scrollable_row); Container::new(content) @@ -190,9 +181,12 @@ impl Sandbox for ScrollableDemo { .height(Length::Fill) .center_x() .center_y() - .style(self.theme) .into() } + + fn theme(&self) -> Theme { + self.theme + } } /// A version of a scrollable diff --git a/examples/scrollable/src/style.rs b/examples/scrollable/src/style.rs deleted file mode 100644 index 0ed38b0060..0000000000 --- a/examples/scrollable/src/style.rs +++ /dev/null @@ -1,191 +0,0 @@ -use iced::{container, radio, rule, scrollable}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Theme { - Light, - Dark, -} - -impl Theme { - pub const ALL: [Theme; 2] = [Theme::Light, Theme::Dark]; -} - -impl Default for Theme { - fn default() -> Theme { - Theme::Light - } -} - -impl<'a> From for Box { - fn from(theme: Theme) -> Self { - match theme { - Theme::Light => Default::default(), - Theme::Dark => dark::Container.into(), - } - } -} - -impl<'a> From for Box { - fn from(theme: Theme) -> Self { - match theme { - Theme::Light => Default::default(), - Theme::Dark => dark::Radio.into(), - } - } -} - -impl<'a> From for Box { - fn from(theme: Theme) -> Self { - match theme { - Theme::Light => Default::default(), - Theme::Dark => dark::Scrollable.into(), - } - } -} - -impl From for Box { - fn from(theme: Theme) -> Self { - match theme { - Theme::Light => Default::default(), - Theme::Dark => dark::Rule.into(), - } - } -} - -mod dark { - use iced::{container, radio, rule, scrollable, Color}; - - const BACKGROUND: Color = Color::from_rgb( - 0x36 as f32 / 255.0, - 0x39 as f32 / 255.0, - 0x3F as f32 / 255.0, - ); - - const SURFACE: Color = Color::from_rgb( - 0x40 as f32 / 255.0, - 0x44 as f32 / 255.0, - 0x4B as f32 / 255.0, - ); - - const ACCENT: Color = Color::from_rgb( - 0x6F as f32 / 255.0, - 0xFF as f32 / 255.0, - 0xE9 as f32 / 255.0, - ); - - const ACTIVE: Color = Color::from_rgb( - 0x72 as f32 / 255.0, - 0x89 as f32 / 255.0, - 0xDA as f32 / 255.0, - ); - - const SCROLLBAR: Color = Color::from_rgb( - 0x2E as f32 / 255.0, - 0x33 as f32 / 255.0, - 0x38 as f32 / 255.0, - ); - - const SCROLLER: Color = Color::from_rgb( - 0x20 as f32 / 255.0, - 0x22 as f32 / 255.0, - 0x25 as f32 / 255.0, - ); - - pub struct Container; - - impl container::StyleSheet for Container { - fn style(&self) -> container::Style { - container::Style { - background: Color { - a: 0.99, - ..BACKGROUND - } - .into(), - text_color: Color::WHITE.into(), - ..container::Style::default() - } - } - } - - pub struct Radio; - - impl radio::StyleSheet for Radio { - fn active(&self) -> radio::Style { - radio::Style { - background: SURFACE.into(), - dot_color: ACTIVE, - border_width: 1.0, - border_color: ACTIVE, - text_color: None, - } - } - - fn hovered(&self) -> radio::Style { - radio::Style { - background: Color { a: 0.5, ..SURFACE }.into(), - ..self.active() - } - } - } - - pub struct Scrollable; - - impl scrollable::StyleSheet for Scrollable { - fn active(&self) -> scrollable::Scrollbar { - scrollable::Scrollbar { - background: Color { - a: 0.8, - ..SCROLLBAR - } - .into(), - border_radius: 2.0, - border_width: 0.0, - border_color: Color::TRANSPARENT, - scroller: scrollable::Scroller { - color: Color { a: 0.7, ..SCROLLER }, - border_radius: 2.0, - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - } - } - - fn hovered(&self) -> scrollable::Scrollbar { - let active = self.active(); - - scrollable::Scrollbar { - background: SCROLLBAR.into(), - scroller: scrollable::Scroller { - color: SCROLLER, - ..active.scroller - }, - ..active - } - } - - fn dragging(&self) -> scrollable::Scrollbar { - let hovered = self.hovered(); - - scrollable::Scrollbar { - scroller: scrollable::Scroller { - color: ACCENT, - ..hovered.scroller - }, - ..hovered - } - } - } - - pub struct Rule; - - impl rule::StyleSheet for Rule { - fn style(&self) -> rule::Style { - rule::Style { - color: SURFACE, - width: 2, - radius: 1.0, - fill_mode: rule::FillMode::Percent(30.0), - } - } - } -} diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index e96b53ffb4..cee9a02fda 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -6,10 +6,15 @@ //! Inspired by the example found in the MDN docs[1]. //! //! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system +use iced::application; +use iced::canvas::{self, Cursor, Path, Stroke}; +use iced::executor; +use iced::theme::{self, Theme}; +use iced::time; +use iced::window; use iced::{ - canvas::{self, Cursor, Path, Stroke}, - executor, time, window, Application, Canvas, Color, Command, Element, - Length, Point, Rectangle, Settings, Size, Subscription, Vector, + Application, Canvas, Color, Command, Element, Length, Point, Rectangle, + Settings, Size, Subscription, Vector, }; use std::time::Instant; @@ -31,8 +36,9 @@ enum Message { } impl Application for SolarSystem { - type Executor = executor::Default; type Message = Message; + type Theme = Theme; + type Executor = executor::Default; type Flags = (); fn new(_flags: ()) -> (Self, Command) { @@ -48,10 +54,6 @@ impl Application for SolarSystem { String::from("Solar system - Iced") } - fn background_color(&self) -> Color { - Color::BLACK - } - fn update(&mut self, message: Message) -> Command { match message { Message::Tick(instant) => { @@ -73,6 +75,17 @@ impl Application for SolarSystem { .height(Length::Fill) .into() } + + fn theme(&self) -> Theme { + Theme::Dark + } + + fn style(&self) -> theme::Application { + theme::Application::Custom(|_theme| application::Appearance { + background_color: Color::BLACK, + text_color: Color::WHITE, + }) + } } #[derive(Debug)] @@ -135,6 +148,7 @@ impl State { impl canvas::Program for State { fn draw( &self, + _theme: &Theme, bounds: Rectangle, _cursor: Cursor, ) -> Vec { diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index 377d7a2db6..b83b92ecf9 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -1,7 +1,13 @@ +use iced::alignment; +use iced::button; +use iced::executor; +use iced::theme::{self, Theme}; +use iced::time; use iced::{ - alignment, button, executor, time, Alignment, Application, Button, Column, - Command, Container, Element, Length, Row, Settings, Subscription, Text, + Alignment, Application, Button, Column, Command, Container, Element, + Length, Row, Settings, Subscription, Text, }; + use std::time::{Duration, Instant}; pub fn main() -> iced::Result { @@ -28,8 +34,9 @@ enum Message { } impl Application for Stopwatch { - type Executor = executor::Default; type Message = Message; + type Theme = Theme; + type Executor = executor::Default; type Flags = (); fn new(_flags: ()) -> (Stopwatch, Command) { @@ -99,7 +106,7 @@ impl Application for Stopwatch { )) .size(40); - let button = |state, label, style| { + let button = |state, label| { Button::new( state, Text::new(label) @@ -107,21 +114,20 @@ impl Application for Stopwatch { ) .padding(10) .width(Length::Units(80)) - .style(style) }; let toggle_button = { - let (label, color) = match self.state { - State::Idle => ("Start", style::Button::Primary), - State::Ticking { .. } => ("Stop", style::Button::Destructive), + let label = match self.state { + State::Idle => "Start", + State::Ticking { .. } => "Stop", }; - button(&mut self.toggle, label, color).on_press(Message::Toggle) + button(&mut self.toggle, label).on_press(Message::Toggle) }; - let reset_button = - button(&mut self.reset, "Reset", style::Button::Secondary) - .on_press(Message::Reset); + let reset_button = button(&mut self.reset, "Reset") + .style(theme::Button::Destructive) + .on_press(Message::Reset); let controls = Row::new() .spacing(20) @@ -142,29 +148,3 @@ impl Application for Stopwatch { .into() } } - -mod style { - use iced::{button, Background, Color, Vector}; - - pub enum Button { - Primary, - Secondary, - Destructive, - } - - impl button::StyleSheet for Button { - fn active(&self) -> button::Style { - button::Style { - background: Some(Background::Color(match self { - Button::Primary => Color::from_rgb(0.11, 0.42, 0.87), - Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5), - Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2), - })), - border_radius: 12.0, - shadow_offset: Vector::new(1.0, 1.0), - text_color: Color::WHITE, - ..button::Style::default() - } - } - } -} diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs index b4ef3e8778..aa90d17c3a 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -1,7 +1,11 @@ +use iced::button; +use iced::scrollable; +use iced::slider; +use iced::text_input; use iced::{ - button, scrollable, slider, text_input, Alignment, Button, Checkbox, - Column, Container, Element, Length, ProgressBar, Radio, Row, Rule, Sandbox, - Scrollable, Settings, Slider, Space, Text, TextInput, Toggler, + Alignment, Button, Checkbox, Column, Container, Element, Length, + ProgressBar, Radio, Row, Rule, Sandbox, Scrollable, Settings, Slider, + Space, Text, TextInput, Theme, Toggler, }; pub fn main() -> iced::Result { @@ -10,7 +14,7 @@ pub fn main() -> iced::Result { #[derive(Default)] struct Styling { - theme: style::Theme, + theme: Theme, scroll: scrollable::State, input: text_input::State, input_value: String, @@ -23,7 +27,7 @@ struct Styling { #[derive(Debug, Clone)] enum Message { - ThemeChanged(style::Theme), + ThemeChanged(Theme), InputChanged(String), ButtonPressed, SliderChanged(f32), @@ -54,18 +58,15 @@ impl Sandbox for Styling { } fn view(&mut self) -> Element { - let choose_theme = style::Theme::ALL.iter().fold( + let choose_theme = [Theme::Light, Theme::Dark].iter().fold( Column::new().spacing(10).push(Text::new("Choose a theme:")), |column, theme| { - column.push( - Radio::new( - *theme, - format!("{:?}", theme), - Some(self.theme), - Message::ThemeChanged, - ) - .style(self.theme), - ) + column.push(Radio::new( + *theme, + format!("{:?}", theme), + Some(self.theme), + Message::ThemeChanged, + )) }, ); @@ -76,29 +77,24 @@ impl Sandbox for Styling { Message::InputChanged, ) .padding(10) - .size(20) - .style(self.theme); + .size(20); let button = Button::new(&mut self.button, Text::new("Submit")) .padding(10) - .on_press(Message::ButtonPressed) - .style(self.theme); + .on_press(Message::ButtonPressed); let slider = Slider::new( &mut self.slider, 0.0..=100.0, self.slider_value, Message::SliderChanged, - ) - .style(self.theme); + ); - let progress_bar = - ProgressBar::new(0.0..=100.0, self.slider_value).style(self.theme); + let progress_bar = ProgressBar::new(0.0..=100.0, self.slider_value); let scrollable = Scrollable::new(&mut self.scroll) .width(Length::Fill) .height(Length::Units(100)) - .style(self.theme) .push(Text::new("Scroll me!")) .push(Space::with_height(Length::Units(800))) .push(Text::new("You did it!")); @@ -107,8 +103,7 @@ impl Sandbox for Styling { self.checkbox_value, "Check me!", Message::CheckboxToggled, - ) - .style(self.theme); + ); let toggler = Toggler::new( self.toggler_value, @@ -116,15 +111,14 @@ impl Sandbox for Styling { Message::TogglerToggled, ) .width(Length::Shrink) - .spacing(10) - .style(self.theme); + .spacing(10); let content = Column::new() .spacing(20) .padding(20) .max_width(600) .push(choose_theme) - .push(Rule::horizontal(38).style(self.theme)) + .push(Rule::horizontal(38)) .push(Row::new().spacing(10).push(text_input).push(button)) .push(slider) .push(progress_bar) @@ -134,7 +128,7 @@ impl Sandbox for Styling { .height(Length::Units(100)) .align_items(Alignment::Center) .push(scrollable) - .push(Rule::vertical(38).style(self.theme)) + .push(Rule::vertical(38)) .push( Column::new() .width(Length::Shrink) @@ -149,445 +143,10 @@ impl Sandbox for Styling { .height(Length::Fill) .center_x() .center_y() - .style(self.theme) .into() } -} - -mod style { - use iced::{ - button, checkbox, container, progress_bar, radio, rule, scrollable, - slider, text_input, toggler, - }; - - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - pub enum Theme { - Light, - Dark, - } - - impl Theme { - pub const ALL: [Theme; 2] = [Theme::Light, Theme::Dark]; - } - impl Default for Theme { - fn default() -> Theme { - Theme::Light - } - } - - impl<'a> From for Box { - fn from(theme: Theme) -> Self { - match theme { - Theme::Light => Default::default(), - Theme::Dark => dark::Container.into(), - } - } - } - - impl<'a> From for Box { - fn from(theme: Theme) -> Self { - match theme { - Theme::Light => Default::default(), - Theme::Dark => dark::Radio.into(), - } - } - } - - impl<'a> From for Box { - fn from(theme: Theme) -> Self { - match theme { - Theme::Light => Default::default(), - Theme::Dark => dark::TextInput.into(), - } - } - } - - impl<'a> From for Box { - fn from(theme: Theme) -> Self { - match theme { - Theme::Light => light::Button.into(), - Theme::Dark => dark::Button.into(), - } - } - } - - impl<'a> From for Box { - fn from(theme: Theme) -> Self { - match theme { - Theme::Light => Default::default(), - Theme::Dark => dark::Scrollable.into(), - } - } - } - - impl<'a> From for Box { - fn from(theme: Theme) -> Self { - match theme { - Theme::Light => Default::default(), - Theme::Dark => dark::Slider.into(), - } - } - } - - impl From for Box { - fn from(theme: Theme) -> Self { - match theme { - Theme::Light => Default::default(), - Theme::Dark => dark::ProgressBar.into(), - } - } - } - - impl<'a> From for Box { - fn from(theme: Theme) -> Self { - match theme { - Theme::Light => Default::default(), - Theme::Dark => dark::Checkbox.into(), - } - } - } - - impl From for Box { - fn from(theme: Theme) -> Self { - match theme { - Theme::Light => Default::default(), - Theme::Dark => dark::Toggler.into(), - } - } - } - - impl From for Box { - fn from(theme: Theme) -> Self { - match theme { - Theme::Light => Default::default(), - Theme::Dark => dark::Rule.into(), - } - } - } - - mod light { - use iced::{button, Color, Vector}; - - pub struct Button; - - impl button::StyleSheet for Button { - fn active(&self) -> button::Style { - button::Style { - background: Color::from_rgb(0.11, 0.42, 0.87).into(), - border_radius: 12.0, - shadow_offset: Vector::new(1.0, 1.0), - text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE), - ..button::Style::default() - } - } - - fn hovered(&self) -> button::Style { - button::Style { - text_color: Color::WHITE, - shadow_offset: Vector::new(1.0, 2.0), - ..self.active() - } - } - } - } - - mod dark { - use iced::{ - button, checkbox, container, progress_bar, radio, rule, scrollable, - slider, text_input, toggler, Color, - }; - - const SURFACE: Color = Color::from_rgb( - 0x40 as f32 / 255.0, - 0x44 as f32 / 255.0, - 0x4B as f32 / 255.0, - ); - - const ACCENT: Color = Color::from_rgb( - 0x6F as f32 / 255.0, - 0xFF as f32 / 255.0, - 0xE9 as f32 / 255.0, - ); - - const ACTIVE: Color = Color::from_rgb( - 0x72 as f32 / 255.0, - 0x89 as f32 / 255.0, - 0xDA as f32 / 255.0, - ); - - const HOVERED: Color = Color::from_rgb( - 0x67 as f32 / 255.0, - 0x7B as f32 / 255.0, - 0xC4 as f32 / 255.0, - ); - - pub struct Container; - - impl container::StyleSheet for Container { - fn style(&self) -> container::Style { - container::Style { - background: Color::from_rgb8(0x36, 0x39, 0x3F).into(), - text_color: Color::WHITE.into(), - ..container::Style::default() - } - } - } - - pub struct Radio; - - impl radio::StyleSheet for Radio { - fn active(&self) -> radio::Style { - radio::Style { - background: SURFACE.into(), - dot_color: ACTIVE, - border_width: 1.0, - border_color: ACTIVE, - text_color: None, - } - } - - fn hovered(&self) -> radio::Style { - radio::Style { - background: Color { a: 0.5, ..SURFACE }.into(), - ..self.active() - } - } - } - - pub struct TextInput; - - impl text_input::StyleSheet for TextInput { - fn active(&self) -> text_input::Style { - text_input::Style { - background: SURFACE.into(), - border_radius: 2.0, - border_width: 0.0, - border_color: Color::TRANSPARENT, - } - } - - fn focused(&self) -> text_input::Style { - text_input::Style { - border_width: 1.0, - border_color: ACCENT, - ..self.active() - } - } - - fn hovered(&self) -> text_input::Style { - text_input::Style { - border_width: 1.0, - border_color: Color { a: 0.3, ..ACCENT }, - ..self.focused() - } - } - - fn placeholder_color(&self) -> Color { - Color::from_rgb(0.4, 0.4, 0.4) - } - - fn value_color(&self) -> Color { - Color::WHITE - } - - fn selection_color(&self) -> Color { - ACTIVE - } - } - - pub struct Button; - - impl button::StyleSheet for Button { - fn active(&self) -> button::Style { - button::Style { - background: ACTIVE.into(), - border_radius: 3.0, - text_color: Color::WHITE, - ..button::Style::default() - } - } - - fn hovered(&self) -> button::Style { - button::Style { - background: HOVERED.into(), - text_color: Color::WHITE, - ..self.active() - } - } - - fn pressed(&self) -> button::Style { - button::Style { - border_width: 1.0, - border_color: Color::WHITE, - ..self.hovered() - } - } - } - - pub struct Scrollable; - - impl scrollable::StyleSheet for Scrollable { - fn active(&self) -> scrollable::Scrollbar { - scrollable::Scrollbar { - background: SURFACE.into(), - border_radius: 2.0, - border_width: 0.0, - border_color: Color::TRANSPARENT, - scroller: scrollable::Scroller { - color: ACTIVE, - border_radius: 2.0, - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - } - } - - fn hovered(&self) -> scrollable::Scrollbar { - let active = self.active(); - - scrollable::Scrollbar { - background: Color { a: 0.5, ..SURFACE }.into(), - scroller: scrollable::Scroller { - color: HOVERED, - ..active.scroller - }, - ..active - } - } - - fn dragging(&self) -> scrollable::Scrollbar { - let hovered = self.hovered(); - - scrollable::Scrollbar { - scroller: scrollable::Scroller { - color: Color::from_rgb(0.85, 0.85, 0.85), - ..hovered.scroller - }, - ..hovered - } - } - } - - pub struct Slider; - - impl slider::StyleSheet for Slider { - fn active(&self) -> slider::Style { - slider::Style { - rail_colors: (ACTIVE, Color { a: 0.1, ..ACTIVE }), - handle: slider::Handle { - shape: slider::HandleShape::Circle { radius: 9.0 }, - color: ACTIVE, - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - } - } - - fn hovered(&self) -> slider::Style { - let active = self.active(); - - slider::Style { - handle: slider::Handle { - color: HOVERED, - ..active.handle - }, - ..active - } - } - - fn dragging(&self) -> slider::Style { - let active = self.active(); - - slider::Style { - handle: slider::Handle { - color: Color::from_rgb(0.85, 0.85, 0.85), - ..active.handle - }, - ..active - } - } - } - - pub struct ProgressBar; - - impl progress_bar::StyleSheet for ProgressBar { - fn style(&self) -> progress_bar::Style { - progress_bar::Style { - background: SURFACE.into(), - bar: ACTIVE.into(), - border_radius: 10.0, - } - } - } - - pub struct Checkbox; - - impl checkbox::StyleSheet for Checkbox { - fn active(&self, is_checked: bool) -> checkbox::Style { - checkbox::Style { - background: if is_checked { ACTIVE } else { SURFACE } - .into(), - checkmark_color: Color::WHITE, - border_radius: 2.0, - border_width: 1.0, - border_color: ACTIVE, - text_color: None, - } - } - - fn hovered(&self, is_checked: bool) -> checkbox::Style { - checkbox::Style { - background: Color { - a: 0.8, - ..if is_checked { ACTIVE } else { SURFACE } - } - .into(), - ..self.active(is_checked) - } - } - } - - pub struct Toggler; - - impl toggler::StyleSheet for Toggler { - fn active(&self, is_active: bool) -> toggler::Style { - toggler::Style { - background: if is_active { ACTIVE } else { SURFACE }, - background_border: None, - foreground: if is_active { Color::WHITE } else { ACTIVE }, - foreground_border: None, - } - } - - fn hovered(&self, is_active: bool) -> toggler::Style { - toggler::Style { - background: if is_active { ACTIVE } else { SURFACE }, - background_border: None, - foreground: if is_active { - Color { - a: 0.5, - ..Color::WHITE - } - } else { - Color { a: 0.5, ..ACTIVE } - }, - foreground_border: None, - } - } - } - - pub struct Rule; - - impl rule::StyleSheet for Rule { - fn style(&self) -> rule::Style { - rule::Style { - color: SURFACE, - width: 2, - radius: 1.0, - fill_mode: rule::FillMode::Padded(15), - } - } - } + fn theme(&self) -> Theme { + self.theme } } diff --git a/examples/system_information/src/main.rs b/examples/system_information/src/main.rs index 560220b82e..9e6a2f61dc 100644 --- a/examples/system_information/src/main.rs +++ b/examples/system_information/src/main.rs @@ -1,6 +1,6 @@ use iced::{ button, executor, system, Application, Button, Column, Command, Container, - Element, Length, Settings, Text, + Element, Length, Settings, Text, Theme, }; use bytesize::ByteSize; @@ -25,6 +25,7 @@ enum Message { impl Application for Example { type Message = Message; + type Theme = Theme; type Executor = executor::Default; type Flags = (); diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 0b889407b8..dc080ef52a 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -2,9 +2,10 @@ use iced::alignment::{self, Alignment}; use iced::button::{self, Button}; use iced::scrollable::{self, Scrollable}; use iced::text_input::{self, TextInput}; +use iced::theme::{self, Theme}; use iced::{ - Application, Checkbox, Column, Command, Container, Element, Font, Length, - Row, Settings, Text, + Application, Checkbox, Color, Column, Command, Container, Element, Font, + Length, Row, Settings, Text, }; use serde::{Deserialize, Serialize}; @@ -42,6 +43,7 @@ enum Message { impl Application for Todos { type Executor = iced::executor::Default; + type Theme = Theme; type Message = Message; type Flags = (); @@ -153,7 +155,7 @@ impl Application for Todos { let title = Text::new("todos") .width(Length::Fill) .size(100) - .color([0.5, 0.5, 0.5]) + .style(Color::from([0.5, 0.5, 0.5])) .horizontal_alignment(alignment::Horizontal::Center); let input = TextInput::new( @@ -304,7 +306,7 @@ impl Task { Button::new(edit_button, edit_icon()) .on_press(TaskMessage::Edit) .padding(10) - .style(style::Button::Icon), + .style(theme::Button::Text), ) .into() } @@ -335,7 +337,7 @@ impl Task { ) .on_press(TaskMessage::Delete) .padding(10) - .style(style::Button::Destructive), + .style(theme::Button::Destructive), ) .into() } @@ -364,9 +366,9 @@ impl Controls { let label = Text::new(label).size(16); let button = Button::new(state, label).style(if filter == current_filter { - style::Button::FilterSelected + theme::Button::Primary } else { - style::Button::FilterActive + theme::Button::Text }); button.on_press(Message::FilterChanged(filter)).padding(8) @@ -451,7 +453,7 @@ fn empty_message<'a>(message: &str) -> Element<'a, Message> { .width(Length::Fill) .size(25) .horizontal_alignment(alignment::Horizontal::Center) - .color([0.7, 0.7, 0.7]), + .style(Color::from([0.7, 0.7, 0.7])), ) .width(Length::Fill) .height(Length::Units(200)) @@ -599,57 +601,3 @@ impl SavedState { Ok(()) } } - -mod style { - use iced::{button, Background, Color, Vector}; - - pub enum Button { - FilterActive, - FilterSelected, - Icon, - Destructive, - } - - impl button::StyleSheet for Button { - fn active(&self) -> button::Style { - match self { - Button::FilterActive => button::Style::default(), - Button::FilterSelected => button::Style { - background: Some(Background::Color(Color::from_rgb( - 0.2, 0.2, 0.7, - ))), - border_radius: 10.0, - text_color: Color::WHITE, - ..button::Style::default() - }, - Button::Icon => button::Style { - text_color: Color::from_rgb(0.5, 0.5, 0.5), - ..button::Style::default() - }, - Button::Destructive => button::Style { - background: Some(Background::Color(Color::from_rgb( - 0.8, 0.2, 0.2, - ))), - border_radius: 5.0, - text_color: Color::WHITE, - shadow_offset: Vector::new(1.0, 1.0), - ..button::Style::default() - }, - } - } - - fn hovered(&self) -> button::Style { - let active = self.active(); - - button::Style { - text_color: match self { - Button::Icon => Color::from_rgb(0.2, 0.2, 0.7), - Button::FilterActive => Color::from_rgb(0.2, 0.2, 0.7), - _ => active.text_color, - }, - shadow_offset: active.shadow_offset + Vector::new(0.0, 1.0), - ..active - } - } - } -} diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs index cfeaf6a697..1bd1133c8c 100644 --- a/examples/tooltip/src/main.rs +++ b/examples/tooltip/src/main.rs @@ -1,7 +1,9 @@ +use iced::alignment::{self, Alignment}; +use iced::button; +use iced::theme; use iced::tooltip::{self, Tooltip}; use iced::{ - alignment, button, Alignment, Button, Column, Container, Element, Length, - Row, Sandbox, Settings, Text, + Button, Column, Container, Element, Length, Row, Sandbox, Settings, Text, }; pub fn main() { @@ -115,24 +117,6 @@ fn tooltip<'a>( ) .gap(5) .padding(10) - .style(style::Tooltip) + .style(theme::Container::Box) .into() } - -mod style { - use iced::container; - use iced::Color; - - pub struct Tooltip; - - impl container::StyleSheet for Tooltip { - fn style(&self) -> container::Style { - container::Style { - text_color: Some(Color::from_rgb8(0xEE, 0xEE, 0xEE)), - background: Some(Color::from_rgb(0.11, 0.42, 0.87).into()), - border_radius: 12.0, - ..container::Style::default() - } - } - } -} diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 2024d25abf..d85f2916f4 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -1,7 +1,13 @@ +use iced::alignment; +use iced::button; +use iced::scrollable; +use iced::slider; +use iced::text_input; +use iced::theme; use iced::{ - alignment, button, scrollable, slider, text_input, Button, Checkbox, Color, - Column, Container, ContentFit, Element, Image, Length, Radio, Row, Sandbox, - Scrollable, Settings, Slider, Space, Text, TextInput, Toggler, + Button, Checkbox, Color, Column, Container, ContentFit, Element, Image, + Length, Radio, Row, Sandbox, Scrollable, Settings, Slider, Space, Text, + TextInput, Toggler, }; pub fn main() -> iced::Result { @@ -64,7 +70,7 @@ impl Sandbox for Tour { controls = controls.push( button(back_button, "Back") .on_press(Message::BackPressed) - .style(style::Button::Secondary), + .style(theme::Button::Secondary), ); } @@ -74,7 +80,7 @@ impl Sandbox for Tour { controls = controls.push( button(next_button, "Next") .on_press(Message::NextPressed) - .style(style::Button::Primary), + .style(theme::Button::Primary), ); } @@ -132,7 +138,7 @@ impl Steps { size_slider: slider::State::new(), size: 30, color_sliders: [slider::State::new(); 3], - color: Color::BLACK, + color: Color::from_rgb(0.5, 0.5, 0.5), }, Step::Radio { selection: None }, Step::Toggler { @@ -528,7 +534,7 @@ impl<'a> Step { .padding(20) .spacing(20) .push(Text::new("And its color:")) - .push(Text::new(format!("{:?}", color)).color(color)) + .push(Text::new(format!("{:?}", color)).style(color)) .push(color_sliders); Self::container("Text") @@ -710,7 +716,7 @@ impl<'a> Step { .push(if cfg!(target_arch = "wasm32") { Element::new( Text::new("Not available on web yet!") - .color([0.7, 0.7, 0.7]) + .style(Color::from([0.7, 0.7, 0.7])) .horizontal_alignment(alignment::Horizontal::Center), ) } else { @@ -770,7 +776,7 @@ fn color_slider( state: &mut slider::State, component: f32, update: impl Fn(f32) -> Color + 'static, -) -> Slider { +) -> Slider { Slider::new(state, 0.0..=1.0, f64::from(component), move |c| { StepMessage::TextColorChanged(update(c as f32)) }) @@ -818,36 +824,3 @@ pub enum Layout { Row, Column, } - -mod style { - use iced::button; - use iced::{Background, Color, Vector}; - - pub enum Button { - Primary, - Secondary, - } - - impl button::StyleSheet for Button { - fn active(&self) -> button::Style { - button::Style { - background: Some(Background::Color(match self { - Button::Primary => Color::from_rgb(0.11, 0.42, 0.87), - Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5), - })), - border_radius: 12.0, - shadow_offset: Vector::new(1.0, 1.0), - text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE), - ..button::Style::default() - } - } - - fn hovered(&self) -> button::Style { - button::Style { - text_color: Color::WHITE, - shadow_offset: Vector::new(1.0, 2.0), - ..self.active() - } - } - } -} diff --git a/examples/url_handler/src/main.rs b/examples/url_handler/src/main.rs index ee2d249a4a..b544c30db4 100644 --- a/examples/url_handler/src/main.rs +++ b/examples/url_handler/src/main.rs @@ -1,6 +1,7 @@ +use iced::executor; use iced::{ - executor, Application, Command, Container, Element, Length, Settings, - Subscription, Text, + Application, Command, Container, Element, Length, Settings, Subscription, + Text, Theme, }; use iced_native::{ event::{MacOS, PlatformSpecific}, @@ -22,8 +23,9 @@ enum Message { } impl Application for App { - type Executor = executor::Default; type Message = Message; + type Theme = Theme; + type Executor = executor::Default; type Flags = (); fn new(_flags: ()) -> (App, Command) { diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index c03a9f3a94..64addc8f83 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -7,7 +7,7 @@ use iced::scrollable::{self, Scrollable}; use iced::text_input::{self, TextInput}; use iced::{ Application, Color, Column, Command, Container, Element, Length, Row, - Settings, Subscription, Text, + Settings, Subscription, Text, Theme, }; pub fn main() -> iced::Result { @@ -34,6 +34,7 @@ enum Message { impl Application for WebSocket { type Message = Message; + type Theme = Theme; type Flags = (); type Executor = executor::Default; @@ -91,7 +92,7 @@ impl Application for WebSocket { let message_log = if self.messages.is_empty() { Container::new( Text::new("Your messages will appear here...") - .color(Color::from_rgb8(0x88, 0x88, 0x88)), + .style(Color::from_rgb8(0x88, 0x88, 0x88)), ) .width(Length::Fill) .height(Length::Fill) diff --git a/glow/src/lib.rs b/glow/src/lib.rs index d7c0854d15..043c5b1355 100644 --- a/glow/src/lib.rs +++ b/glow/src/lib.rs @@ -30,6 +30,7 @@ pub use settings::Settings; pub(crate) use iced_graphics::Transformation; pub use iced_graphics::{Error, Viewport}; +pub use iced_native::Theme; pub use iced_native::alignment; pub use iced_native::{Alignment, Background, Color, Command, Length, Vector}; @@ -38,4 +39,5 @@ pub use iced_native::{Alignment, Background, Color, Command, Length, Vector}; /// /// [`glow`]: https://github.com/grovesNL/glow /// [`iced`]: https://github.com/iced-rs/iced -pub type Renderer = iced_graphics::Renderer; +pub type Renderer = + iced_graphics::Renderer; diff --git a/glow/src/window/compositor.rs b/glow/src/window/compositor.rs index 622d7fc0bb..f6afaa686e 100644 --- a/glow/src/window/compositor.rs +++ b/glow/src/window/compositor.rs @@ -1,18 +1,21 @@ use crate::{Backend, Color, Error, Renderer, Settings, Viewport}; -use core::ffi::c_void; use glow::HasContext; use iced_graphics::{compositor, Antialiasing, Size}; +use core::ffi::c_void; +use std::marker::PhantomData; + /// A window graphics backend for iced powered by `glow`. #[allow(missing_debug_implementations)] -pub struct Compositor { +pub struct Compositor { gl: glow::Context, + theme: PhantomData, } -impl iced_graphics::window::GLCompositor for Compositor { +impl iced_graphics::window::GLCompositor for Compositor { type Settings = Settings; - type Renderer = Renderer; + type Renderer = Renderer; unsafe fn new( settings: Self::Settings, @@ -46,7 +49,13 @@ impl iced_graphics::window::GLCompositor for Compositor { let renderer = Renderer::new(Backend::new(&gl, settings)); - Ok((Self { gl }, renderer)) + Ok(( + Self { + gl, + theme: PhantomData, + }, + renderer, + )) } fn sample_count(settings: &Settings) -> u32 { diff --git a/glutin/src/application.rs b/glutin/src/application.rs index dbc9b58003..dddf006748 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -2,6 +2,7 @@ use crate::mouse; use crate::{Error, Executor, Runtime}; +pub use iced_winit::application::StyleSheet; pub use iced_winit::Application; use iced_graphics::window; @@ -9,6 +10,7 @@ use iced_winit::application; use iced_winit::conversion; use iced_winit::futures; use iced_winit::futures::channel::mpsc; +use iced_winit::renderer; use iced_winit::user_interface; use iced_winit::{Clipboard, Debug, Proxy, Settings}; @@ -25,6 +27,7 @@ where A: Application + 'static, E: Executor + 'static, C: window::GLCompositor + 'static, + ::Theme: StyleSheet, { use futures::task; use futures::Future; @@ -203,12 +206,14 @@ async fn run_instance( A: Application + 'static, E: Executor + 'static, C: window::GLCompositor + 'static, + ::Theme: StyleSheet, { use glutin::event; use iced_winit::futures::stream::StreamExt; let mut state = application::State::new(&application, context.window()); let mut viewport_version = state.viewport_version(); + let mut user_interface = ManuallyDrop::new(application::build_user_interface( &mut application, @@ -288,8 +293,14 @@ async fn run_instance( } debug.draw_started(); - let new_mouse_interaction = - user_interface.draw(&mut renderer, state.cursor_position()); + let new_mouse_interaction = user_interface.draw( + &mut renderer, + state.theme(), + &renderer::Style { + text_color: state.text_color(), + }, + state.cursor_position(), + ); debug.draw_finished(); if new_mouse_interaction != mouse_interaction { @@ -341,8 +352,14 @@ async fn run_instance( debug.layout_finished(); debug.draw_started(); - let new_mouse_interaction = user_interface - .draw(&mut renderer, state.cursor_position()); + let new_mouse_interaction = user_interface.draw( + &mut renderer, + state.theme(), + &renderer::Style { + text_color: state.text_color(), + }, + state.cursor_position(), + ); debug.draw_finished(); if new_mouse_interaction != mouse_interaction { diff --git a/graphics/src/overlay/menu.rs b/graphics/src/overlay/menu.rs index c5ff093de2..8b489e5efd 100644 --- a/graphics/src/overlay/menu.rs +++ b/graphics/src/overlay/menu.rs @@ -1,3 +1,3 @@ //! Build and show dropdown menus. -pub use iced_style::menu::Style; +pub use iced_style::menu::{Appearance, StyleSheet}; diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index cb31ea5fc0..3c19fbfb3b 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -10,19 +10,23 @@ use iced_native::{Background, Element, Font, Point, Rectangle, Size}; pub use iced_native::renderer::Style; +use std::marker::PhantomData; + /// A backend-agnostic renderer that supports all the built-in widgets. #[derive(Debug)] -pub struct Renderer { +pub struct Renderer { backend: B, primitives: Vec, + theme: PhantomData, } -impl Renderer { +impl Renderer { /// Creates a new [`Renderer`] from the given [`Backend`]. pub fn new(backend: B) -> Self { Self { backend, primitives: Vec::new(), + theme: PhantomData, } } @@ -43,10 +47,12 @@ impl Renderer { } } -impl iced_native::Renderer for Renderer +impl iced_native::Renderer for Renderer where B: Backend, { + type Theme = T; + fn layout<'a, Message>( &mut self, element: &Element<'a, Message, Self>, @@ -114,7 +120,7 @@ where } } -impl text::Renderer for Renderer +impl text::Renderer for Renderer where B: Backend + backend::Text, { @@ -171,7 +177,7 @@ where } } -impl image::Renderer for Renderer +impl image::Renderer for Renderer where B: Backend + backend::Image, { @@ -186,7 +192,7 @@ where } } -impl svg::Renderer for Renderer +impl svg::Renderer for Renderer where B: Backend + backend::Svg, { diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index 23444b2b87..c3e28e8cb1 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -62,10 +62,10 @@ use std::marker::PhantomData; /// ```no_run /// # mod iced { /// # pub use iced_graphics::canvas; -/// # pub use iced_native::{Color, Rectangle}; +/// # pub use iced_native::{Color, Rectangle, Theme}; /// # } /// use iced::canvas::{self, Canvas, Cursor, Fill, Frame, Geometry, Path, Program}; -/// use iced::{Color, Rectangle}; +/// use iced::{Color, Rectangle, Theme}; /// /// // First, we define the data we need for drawing /// #[derive(Debug)] @@ -75,7 +75,7 @@ use std::marker::PhantomData; /// /// // Then, we implement the `Program` trait /// impl Program<()> for Circle { -/// fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec{ +/// fn draw(&self, _theme: &Theme, bounds: Rectangle, _cursor: Cursor) -> Vec{ /// // We prepare a new `Frame` /// let mut frame = Frame::new(bounds.size()); /// @@ -94,14 +94,21 @@ use std::marker::PhantomData; /// let canvas = Canvas::new(Circle { radius: 50.0 }); /// ``` #[derive(Debug)] -pub struct Canvas> { +pub struct Canvas +where + P: Program, +{ width: Length, height: Length, program: P, message_: PhantomData, + theme_: PhantomData, } -impl> Canvas { +impl Canvas +where + P: Program, +{ const DEFAULT_SIZE: u16 = 100; /// Creates a new [`Canvas`]. @@ -111,6 +118,7 @@ impl> Canvas { height: Length::Units(Self::DEFAULT_SIZE), program, message_: PhantomData, + theme_: PhantomData, } } @@ -127,9 +135,9 @@ impl> Canvas { } } -impl Widget> for Canvas +impl Widget> for Canvas where - P: Program, + P: Program, B: Backend, { fn width(&self) -> Length { @@ -142,7 +150,7 @@ where fn layout( &self, - _renderer: &Renderer, + _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { let limits = limits.width(self.width).height(self.height); @@ -156,7 +164,7 @@ where event: iced_native::Event, layout: Layout<'_>, cursor_position: Point, - _renderer: &Renderer, + _renderer: &Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { @@ -193,7 +201,7 @@ where layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, - _renderer: &Renderer, + _renderer: &Renderer, ) -> mouse::Interaction { let bounds = layout.bounds(); let cursor = Cursor::from_window_position(cursor_position); @@ -203,7 +211,8 @@ where fn draw( &self, - renderer: &mut Renderer, + renderer: &mut Renderer, + theme: &T, _style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, @@ -224,7 +233,7 @@ where renderer.draw_primitive(Primitive::Group { primitives: self .program - .draw(bounds, cursor) + .draw(theme, bounds, cursor) .into_iter() .map(Geometry::into_primitive) .collect(), @@ -233,14 +242,17 @@ where } } -impl<'a, Message, P, B> From> - for Element<'a, Message, Renderer> +impl<'a, Message, P, B, T> From> + for Element<'a, Message, Renderer> where Message: 'static, - P: Program + 'a, + P: Program + 'a, B: Backend, + T: 'a, { - fn from(canvas: Canvas) -> Element<'a, Message, Renderer> { + fn from( + canvas: Canvas, + ) -> Element<'a, Message, Renderer> { Element::new(canvas) } } diff --git a/graphics/src/widget/canvas/program.rs b/graphics/src/widget/canvas/program.rs index 85a2f67b30..dddc387d7d 100644 --- a/graphics/src/widget/canvas/program.rs +++ b/graphics/src/widget/canvas/program.rs @@ -1,6 +1,8 @@ use crate::canvas::event::{self, Event}; use crate::canvas::{Cursor, Geometry}; -use iced_native::{mouse, Rectangle}; + +use iced_native::mouse; +use iced_native::Rectangle; /// The state and logic of a [`Canvas`]. /// @@ -8,7 +10,7 @@ use iced_native::{mouse, Rectangle}; /// application. /// /// [`Canvas`]: crate::widget::Canvas -pub trait Program { +pub trait Program { /// Updates the state of the [`Program`]. /// /// When a [`Program`] is used in a [`Canvas`], the runtime will call this @@ -36,7 +38,12 @@ pub trait Program { /// /// [`Frame`]: crate::widget::canvas::Frame /// [`Cache`]: crate::widget::canvas::Cache - fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec; + fn draw( + &self, + theme: &Theme, + bounds: Rectangle, + cursor: Cursor, + ) -> Vec; /// Returns the current mouse interaction of the [`Program`]. /// @@ -53,9 +60,9 @@ pub trait Program { } } -impl Program for &mut T +impl Program for &mut T where - T: Program, + T: Program, { fn update( &mut self, @@ -66,8 +73,13 @@ where T::update(self, event, bounds, cursor) } - fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec { - T::draw(self, bounds, cursor) + fn draw( + &self, + theme: &Theme, + bounds: Rectangle, + cursor: Cursor, + ) -> Vec { + T::draw(self, theme, bounds, cursor) } fn mouse_interaction( diff --git a/graphics/src/widget/pure/canvas.rs b/graphics/src/widget/pure/canvas.rs index b51d5853d8..0eeff3d1c3 100644 --- a/graphics/src/widget/pure/canvas.rs +++ b/graphics/src/widget/pure/canvas.rs @@ -30,10 +30,10 @@ use std::marker::PhantomData; /// # pub mod pure { /// # pub use iced_graphics::pure::canvas; /// # } -/// # pub use iced_native::{Color, Rectangle}; +/// # pub use iced_native::{Color, Rectangle, Theme}; /// # } /// use iced::pure::canvas::{self, Canvas, Cursor, Fill, Frame, Geometry, Path, Program}; -/// use iced::{Color, Rectangle}; +/// use iced::{Color, Rectangle, Theme}; /// /// // First, we define the data we need for drawing /// #[derive(Debug)] @@ -45,7 +45,7 @@ use std::marker::PhantomData; /// impl Program<()> for Circle { /// type State = (); /// -/// fn draw(&self, _state: &(), bounds: Rectangle, _cursor: Cursor) -> Vec{ +/// fn draw(&self, _state: &(), _theme: &Theme, bounds: Rectangle, _cursor: Cursor) -> Vec{ /// // We prepare a new `Frame` /// let mut frame = Frame::new(bounds.size()); /// @@ -64,19 +64,20 @@ use std::marker::PhantomData; /// let canvas = Canvas::new(Circle { radius: 50.0 }); /// ``` #[derive(Debug)] -pub struct Canvas +pub struct Canvas where - P: Program, + P: Program, { width: Length, height: Length, program: P, message_: PhantomData, + theme_: PhantomData, } -impl Canvas +impl Canvas where - P: Program, + P: Program, { const DEFAULT_SIZE: u16 = 100; @@ -87,6 +88,7 @@ where height: Length::Units(Self::DEFAULT_SIZE), program, message_: PhantomData, + theme_: PhantomData, } } @@ -103,9 +105,9 @@ where } } -impl Widget> for Canvas +impl Widget> for Canvas where - P: Program, + P: Program, B: Backend, { fn tag(&self) -> tree::Tag { @@ -127,7 +129,7 @@ where fn layout( &self, - _renderer: &Renderer, + _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { let limits = limits.width(self.width).height(self.height); @@ -142,7 +144,7 @@ where event: iced_native::Event, layout: Layout<'_>, cursor_position: Point, - _renderer: &Renderer, + _renderer: &Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { @@ -182,7 +184,7 @@ where layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, - _renderer: &Renderer, + _renderer: &Renderer, ) -> mouse::Interaction { let bounds = layout.bounds(); let cursor = Cursor::from_window_position(cursor_position); @@ -194,7 +196,8 @@ where fn draw( &self, tree: &Tree, - renderer: &mut Renderer, + renderer: &mut Renderer, + theme: &T, _style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, @@ -216,7 +219,7 @@ where renderer.draw_primitive(Primitive::Group { primitives: self .program - .draw(state, bounds, cursor) + .draw(state, theme, bounds, cursor) .into_iter() .map(Geometry::into_primitive) .collect(), @@ -225,14 +228,17 @@ where } } -impl<'a, Message, P, B> From> - for Element<'a, Message, Renderer> +impl<'a, Message, P, B, T> From> + for Element<'a, Message, Renderer> where Message: 'a, - P: Program + 'a, + P: Program + 'a, B: Backend, + T: 'a, { - fn from(canvas: Canvas) -> Element<'a, Message, Renderer> { + fn from( + canvas: Canvas, + ) -> Element<'a, Message, Renderer> { Element::new(canvas) } } diff --git a/graphics/src/widget/pure/canvas/program.rs b/graphics/src/widget/pure/canvas/program.rs index 058b364b2d..20c6406e96 100644 --- a/graphics/src/widget/pure/canvas/program.rs +++ b/graphics/src/widget/pure/canvas/program.rs @@ -9,7 +9,7 @@ use crate::Rectangle; /// application. /// /// [`Canvas`]: crate::widget::Canvas -pub trait Program { +pub trait Program { /// The internal state mutated by the [`Program`]. type State: Default + 'static; @@ -44,6 +44,7 @@ pub trait Program { fn draw( &self, state: &Self::State, + theme: &Theme, bounds: Rectangle, cursor: Cursor, ) -> Vec; @@ -64,9 +65,9 @@ pub trait Program { } } -impl Program for &T +impl Program for &T where - T: Program, + T: Program, { type State = T::State; @@ -83,10 +84,11 @@ where fn draw( &self, state: &Self::State, + theme: &Theme, bounds: Rectangle, cursor: Cursor, ) -> Vec { - T::draw(self, state, bounds, cursor) + T::draw(self, state, theme, bounds, cursor) } fn mouse_interaction( diff --git a/graphics/src/widget/pure/qr_code.rs b/graphics/src/widget/pure/qr_code.rs index 9d5173749d..23a8ceb631 100644 --- a/graphics/src/widget/pure/qr_code.rs +++ b/graphics/src/widget/pure/qr_code.rs @@ -9,24 +9,24 @@ use iced_native::{Length, Point, Rectangle}; use iced_pure::widget::tree::Tree; use iced_pure::{Element, Widget}; -impl<'a, Message, B> Widget> for QRCode<'a> +impl<'a, Message, B, T> Widget> for QRCode<'a> where B: Backend, { fn width(&self) -> Length { - >>::width(self) + >>::width(self) } fn height(&self) -> Length { - >>::height(self) + >>::height(self) } fn layout( &self, - renderer: &Renderer, + renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - >>::layout( + >>::layout( self, renderer, limits, ) } @@ -34,15 +34,17 @@ where fn draw( &self, _tree: &Tree, - renderer: &mut Renderer, + renderer: &mut Renderer, + theme: &T, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, ) { - >>::draw( + >>::draw( self, renderer, + theme, style, layout, cursor_position, @@ -51,11 +53,12 @@ where } } -impl<'a, Message, B> Into>> for QRCode<'a> +impl<'a, Message, B, T> Into>> + for QRCode<'a> where B: Backend, { - fn into(self) -> Element<'a, Message, Renderer> { + fn into(self) -> Element<'a, Message, Renderer> { Element::new(self) } } diff --git a/graphics/src/widget/qr_code.rs b/graphics/src/widget/qr_code.rs index 907794b725..1eb862ba8b 100644 --- a/graphics/src/widget/qr_code.rs +++ b/graphics/src/widget/qr_code.rs @@ -47,7 +47,7 @@ impl<'a> QRCode<'a> { } } -impl<'a, Message, B> Widget> for QRCode<'a> +impl<'a, Message, B, T> Widget> for QRCode<'a> where B: Backend, { @@ -61,7 +61,7 @@ where fn layout( &self, - _renderer: &Renderer, + _renderer: &Renderer, _limits: &layout::Limits, ) -> layout::Node { let side_length = (self.state.width + 2 * QUIET_ZONE) as f32 @@ -75,7 +75,8 @@ where fn draw( &self, - renderer: &mut Renderer, + renderer: &mut Renderer, + _theme: &T, _style: &renderer::Style, layout: Layout<'_>, _cursor_position: Point, @@ -127,11 +128,12 @@ where } } -impl<'a, Message, B> Into>> for QRCode<'a> +impl<'a, Message, B, T> Into>> + for QRCode<'a> where B: Backend, { - fn into(self) -> Element<'a, Message, Renderer> { + fn into(self) -> Element<'a, Message, Renderer> { Element::new(self) } } diff --git a/lazy/src/component.rs b/lazy/src/component.rs index 2029c2a359..2c6b6ffb3d 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -206,13 +206,21 @@ where fn draw( &self, renderer: &mut Renderer, + theme: &Renderer::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, ) { self.with_element(|element| { - element.draw(renderer, style, layout, cursor_position, viewport); + element.draw( + renderer, + theme, + style, + layout, + cursor_position, + viewport, + ); }); } @@ -337,12 +345,13 @@ where fn draw( &self, renderer: &mut Renderer, + theme: &Renderer::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, ) { self.with_overlay_maybe(|overlay| { - overlay.draw(renderer, style, layout, cursor_position); + overlay.draw(renderer, theme, style, layout, cursor_position); }); } diff --git a/lazy/src/pure/component.rs b/lazy/src/pure/component.rs index 8de40e8cb7..9b29b62872 100644 --- a/lazy/src/pure/component.rs +++ b/lazy/src/pure/component.rs @@ -232,6 +232,7 @@ where &self, tree: &Tree, renderer: &mut Renderer, + theme: &Renderer::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, @@ -241,6 +242,7 @@ where element.as_widget().draw( &tree.children[0], renderer, + theme, style, layout, cursor_position, @@ -375,12 +377,13 @@ where fn draw( &self, renderer: &mut Renderer, + theme: &Renderer::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, ) { self.with_overlay_maybe(|overlay| { - overlay.draw(renderer, style, layout, cursor_position); + overlay.draw(renderer, theme, style, layout, cursor_position); }); } diff --git a/lazy/src/pure/responsive.rs b/lazy/src/pure/responsive.rs index e464d15688..96b89fd6af 100644 --- a/lazy/src/pure/responsive.rs +++ b/lazy/src/pure/responsive.rs @@ -53,7 +53,10 @@ struct Content<'a, Message, Renderer> { element: Element<'a, Message, Renderer>, } -impl<'a, Message, Renderer> Content<'a, Message, Renderer> { +impl<'a, Message, Renderer> Content<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ fn update( &mut self, tree: &mut Tree, @@ -174,6 +177,7 @@ where &self, tree: &Tree, renderer: &mut Renderer, + theme: &Renderer::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, @@ -191,6 +195,7 @@ where element.as_widget().draw( tree, renderer, + theme, style, layout, cursor_position, @@ -331,12 +336,13 @@ where fn draw( &self, renderer: &mut Renderer, + theme: &Renderer::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, ) { self.with_overlay_maybe(|overlay| { - overlay.draw(renderer, style, layout, cursor_position); + overlay.draw(renderer, theme, style, layout, cursor_position); }); } diff --git a/lazy/src/responsive.rs b/lazy/src/responsive.rs index 20a80dacb9..86c8db6b4b 100644 --- a/lazy/src/responsive.rs +++ b/lazy/src/responsive.rs @@ -119,6 +119,7 @@ where fn draw( &self, renderer: &mut Renderer, + theme: &Renderer::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, @@ -129,6 +130,7 @@ where internal.resolve(renderer, |state, renderer, content| { content.draw( renderer, + theme, style, state.layout(layout), cursor_position, @@ -356,12 +358,13 @@ where fn draw( &self, renderer: &mut Renderer, + theme: &Renderer::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, ) { self.with_overlay_maybe(|overlay| { - overlay.draw(renderer, style, layout, cursor_position); + overlay.draw(renderer, theme, style, layout, cursor_position); }); } diff --git a/native/src/element.rs b/native/src/element.rs index 119b789216..425bddc2ef 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -244,13 +244,20 @@ where pub fn draw( &self, renderer: &mut Renderer, + theme: &Renderer::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, ) { - self.widget - .draw(renderer, style, layout, cursor_position, viewport) + self.widget.draw( + renderer, + theme, + style, + layout, + cursor_position, + viewport, + ) } /// Returns the current [`mouse::Interaction`] of the [`Element`]. @@ -350,13 +357,20 @@ where fn draw( &self, renderer: &mut Renderer, + theme: &Renderer::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, ) { - self.widget - .draw(renderer, style, layout, cursor_position, viewport) + self.widget.draw( + renderer, + theme, + style, + layout, + cursor_position, + viewport, + ) } fn mouse_interaction( @@ -444,6 +458,7 @@ where fn draw( &self, renderer: &mut Renderer, + theme: &Renderer::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, @@ -471,6 +486,7 @@ where self.element.widget.draw( renderer, + theme, style, layout, cursor_position, diff --git a/native/src/lib.rs b/native/src/lib.rs index db60976f2e..2d0dd6ecd9 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -76,6 +76,8 @@ pub use iced_core::{ Rectangle, Size, Vector, }; pub use iced_futures::{executor, futures}; +pub use iced_style::application; +pub use iced_style::theme; #[doc(no_inline)] pub use executor::Executor; @@ -93,5 +95,6 @@ pub use renderer::Renderer; pub use runtime::Runtime; pub use shell::Shell; pub use subscription::Subscription; +pub use theme::Theme; pub use user_interface::UserInterface; pub use widget::Widget; diff --git a/native/src/overlay.rs b/native/src/overlay.rs index 86878f6a6a..792d29058a 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -34,6 +34,7 @@ where fn draw( &self, renderer: &mut Renderer, + theme: &Renderer::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index 24c0fe0186..de2e1f3718 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -94,11 +94,13 @@ where pub fn draw( &self, renderer: &mut Renderer, + theme: &Renderer::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, ) { - self.overlay.draw(renderer, style, layout, cursor_position) + self.overlay + .draw(renderer, theme, style, layout, cursor_position) } } @@ -173,10 +175,12 @@ where fn draw( &self, renderer: &mut Renderer, + theme: &Renderer::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, ) { - self.content.draw(renderer, style, layout, cursor_position) + self.content + .draw(renderer, theme, style, layout, cursor_position) } } diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 13fa7beb9b..979a13c39a 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -7,18 +7,22 @@ use crate::overlay; use crate::renderer; use crate::text::{self, Text}; use crate::touch; +use crate::widget::container::{self, Container}; use crate::widget::scrollable::{self, Scrollable}; -use crate::widget::Container; use crate::{ Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, Widget, }; -pub use iced_style::menu::Style; +pub use iced_style::menu::{Appearance, StyleSheet}; /// A list of selectable options. #[allow(missing_debug_implementations)] -pub struct Menu<'a, T, Renderer: text::Renderer> { +pub struct Menu<'a, T, Renderer> +where + Renderer: text::Renderer, + Renderer::Theme: StyleSheet, +{ state: &'a mut State, options: &'a [T], hovered_option: &'a mut Option, @@ -27,13 +31,15 @@ pub struct Menu<'a, T, Renderer: text::Renderer> { padding: Padding, text_size: Option, font: Renderer::Font, - style: Style, + style: ::Style, } impl<'a, T, Renderer> Menu<'a, T, Renderer> where T: ToString + Clone, Renderer: text::Renderer + 'a, + Renderer::Theme: + StyleSheet + container::StyleSheet + scrollable::StyleSheet, { /// Creates a new [`Menu`] with the given [`State`], a list of options, and /// the message to produced when an option is selected. @@ -81,7 +87,10 @@ where } /// Sets the style of the [`Menu`]. - pub fn style(mut self, style: impl Into