diff options
author | 2020-01-09 18:46:06 +0100 | |
---|---|---|
committer | 2020-01-09 18:46:06 +0100 | |
commit | 0a8302450557877cb667b51fc84383aaf0a11b02 (patch) | |
tree | fe3a8a6b0ae82f7fd1fa0c0de34b4b09d0b9edda | |
parent | 6699329d3f91c5b9d8e8e55ad88de24bd3894955 (diff) | |
parent | 7b278755fc7929633b5771824beac4d39b16e82e (diff) | |
download | iced-0a8302450557877cb667b51fc84383aaf0a11b02.tar.gz iced-0a8302450557877cb667b51fc84383aaf0a11b02.tar.bz2 iced-0a8302450557877cb667b51fc84383aaf0a11b02.zip |
Merge pull request #146 from hecrj/feature/custom-styling
Custom styling
81 files changed, 2288 insertions, 595 deletions
@@ -24,6 +24,7 @@ maintenance = { status = "actively-developed" } members = [ "core", "native", + "style", "web", "wgpu", "winit", diff --git a/core/src/color.rs b/core/src/color.rs index c28e784f..d6bdd365 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -25,6 +25,21 @@ impl Color { a: 1.0, }; + /// A color with no opacity. + pub const TRANSPARENT: Color = Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.0, + }; + + /// Creates a [`Color`] from its RGB components. + /// + /// [`Color`]: struct.Color.html + pub const fn from_rgb(r: f32, g: f32, b: f32) -> Color { + Color { r, g, b, a: 1.0 } + } + /// Creates a [`Color`] from its RGB8 components. /// /// [`Color`]: struct.Color.html @@ -37,13 +52,6 @@ impl Color { } } - /// Creates a [`Color`] from its RGB components. - /// - /// [`Color`]: struct.Color.html - pub fn from_rgb(r: f32, g: f32, b: f32) -> Color { - Color { r, g, b, a: 1.0 } - } - /// Converts the [`Color`] into its linear values. /// /// [`Color`]: struct.Color.html diff --git a/core/src/vector.rs b/core/src/vector.rs index 7d87343a..1c09ee3e 100644 --- a/core/src/vector.rs +++ b/core/src/vector.rs @@ -31,3 +31,15 @@ where Self::new(self.x + b.x, self.y + b.y) } } + +impl<T> Default for Vector<T> +where + T: Default, +{ + fn default() -> Self { + Self { + x: T::default(), + y: T::default(), + } + } +} diff --git a/examples/custom_widget.rs b/examples/custom_widget.rs index c0038cd9..0a570745 100644 --- a/examples/custom_widget.rs +++ b/examples/custom_widget.rs @@ -13,7 +13,7 @@ mod circle { layout, Background, Color, Element, Hasher, Layout, Length, MouseCursor, Point, Size, Widget, }; - use iced_wgpu::{Primitive, Renderer}; + use iced_wgpu::{Defaults, Primitive, Renderer}; pub struct Circle { radius: u16, @@ -54,6 +54,7 @@ mod circle { fn draw( &self, _renderer: &mut Renderer, + _defaults: &Defaults, layout: Layout<'_>, _cursor_position: Point, ) -> (Primitive, MouseCursor) { @@ -62,6 +63,8 @@ mod circle { bounds: layout.bounds(), background: Background::Color(Color::BLACK), border_radius: self.radius, + border_width: 0, + border_color: Color::TRANSPARENT, }, MouseCursor::OutOfBounds, ) diff --git a/examples/geometry.rs b/examples/geometry.rs index ae6c9ca0..9d5fd611 100644 --- a/examples/geometry.rs +++ b/examples/geometry.rs @@ -16,7 +16,7 @@ mod rainbow { }; use iced_wgpu::{ triangle::{Mesh2D, Vertex2D}, - Primitive, Renderer, + Defaults, Primitive, Renderer, }; pub struct Rainbow; @@ -51,6 +51,7 @@ mod rainbow { fn draw( &self, _renderer: &mut Renderer, + _defaults: &Defaults, layout: Layout<'_>, cursor_position: Point, ) -> (Primitive, MouseCursor) { diff --git a/examples/pokedex.rs b/examples/pokedex.rs index f44b6163..7326f94f 100644 --- a/examples/pokedex.rs +++ b/examples/pokedex.rs @@ -1,6 +1,6 @@ use iced::{ - button, image, Align, Application, Button, Color, Column, Command, - Container, Element, Image, Length, Row, Settings, Text, + button, image, Align, Application, Button, Column, Command, Container, + Element, Image, Length, Row, Settings, Text, }; pub fn main() { @@ -214,8 +214,29 @@ impl From<surf::Exception> for Error { } fn button<'a>(state: &'a mut button::State, text: &str) -> Button<'a, Message> { - Button::new(state, Text::new(text).color(Color::WHITE)) - .background(Color::from_rgb(0.11, 0.42, 0.87)) - .border_radius(10) + 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, + shadow_offset: Vector::new(1.0, 1.0), + text_color: Color::WHITE, + ..button::Style::default() + } + } + } } diff --git a/examples/progress_bar.rs b/examples/progress_bar.rs index 525019b4..43b09928 100644 --- a/examples/progress_bar.rs +++ b/examples/progress_bar.rs @@ -1,16 +1,7 @@ -use iced::{ - settings::Window, slider, Background, Color, Column, Element, Length, - ProgressBar, Sandbox, Settings, Slider, -}; +use iced::{slider, Column, Element, ProgressBar, Sandbox, Settings, Slider}; pub fn main() { - Progress::run(Settings { - window: Window { - size: (700, 300), - resizable: true, - decorations: true, - }, - }) + Progress::run(Settings::default()) } #[derive(Default)] @@ -44,14 +35,7 @@ impl Sandbox for Progress { fn view(&mut self) -> Element<Message> { Column::new() .padding(20) - .push( - ProgressBar::new(0.0..=100.0, self.value) - .background(Background::Color(Color::from_rgb( - 0.6, 0.6, 0.6, - ))) - .active_color(Color::from_rgb(0.0, 0.95, 0.0)) - .height(Length::Units(30)), - ) + .push(ProgressBar::new(0.0..=100.0, self.value)) .push(Slider::new( &mut self.progress_bar_slider, 0.0..=100.0, diff --git a/examples/stopwatch.rs b/examples/stopwatch.rs index f4d485e2..c9a61ee9 100644 --- a/examples/stopwatch.rs +++ b/examples/stopwatch.rs @@ -1,7 +1,6 @@ use iced::{ - button, Align, Application, Background, Button, Color, Column, Command, - Container, Element, HorizontalAlignment, Length, Row, Settings, - Subscription, Text, + button, Align, Application, Button, Column, Command, Container, Element, + HorizontalAlignment, Length, Row, Settings, Subscription, Text, }; use std::time::{Duration, Instant}; @@ -98,30 +97,29 @@ impl Application for Stopwatch { )) .size(40); - let button = |state, label, color: [f32; 3]| { + let button = |state, label, style| { Button::new( state, Text::new(label) - .color(Color::WHITE) .horizontal_alignment(HorizontalAlignment::Center), ) .min_width(80) - .background(Background::Color(color.into())) - .border_radius(10) .padding(10) + .style(style) }; let toggle_button = { let (label, color) = match self.state { - State::Idle => ("Start", [0.11, 0.42, 0.87]), - State::Ticking { .. } => ("Stop", [0.9, 0.4, 0.4]), + State::Idle => ("Start", style::Button::Primary), + State::Ticking { .. } => ("Stop", style::Button::Destructive), }; button(&mut self.toggle, label, color).on_press(Message::Toggle) }; - let reset_button = button(&mut self.reset, "Reset", [0.7, 0.7, 0.7]) - .on_press(Message::Reset); + let reset_button = + button(&mut self.reset, "Reset", style::Button::Secondary) + .on_press(Message::Reset); let controls = Row::new() .spacing(20) @@ -177,3 +175,29 @@ mod time { } } } + +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, + shadow_offset: Vector::new(1.0, 1.0), + text_color: Color::WHITE, + ..button::Style::default() + } + } + } +} diff --git a/examples/styling.rs b/examples/styling.rs new file mode 100644 index 00000000..50095ec7 --- /dev/null +++ b/examples/styling.rs @@ -0,0 +1,514 @@ +use iced::{ + button, scrollable, slider, text_input, Align, Button, Checkbox, Column, + Container, Element, Length, ProgressBar, Radio, Row, Sandbox, Scrollable, + Settings, Slider, Space, Text, TextInput, +}; + +pub fn main() { + Styling::run(Settings::default()) +} + +#[derive(Default)] +struct Styling { + theme: style::Theme, + scroll: scrollable::State, + input: text_input::State, + input_value: String, + button: button::State, + slider: slider::State, + slider_value: f32, + toggle_value: bool, +} + +#[derive(Debug, Clone)] +enum Message { + ThemeChanged(style::Theme), + InputChanged(String), + ButtonPressed, + SliderChanged(f32), + CheckboxToggled(bool), +} + +impl Sandbox for Styling { + type Message = Message; + + fn new() -> Self { + Styling::default() + } + + fn title(&self) -> String { + String::from("Styling - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::ThemeChanged(theme) => self.theme = theme, + Message::InputChanged(value) => self.input_value = value, + Message::ButtonPressed => (), + Message::SliderChanged(value) => self.slider_value = value, + Message::CheckboxToggled(value) => self.toggle_value = value, + } + } + + fn view(&mut self) -> Element<Message> { + let choose_theme = style::Theme::ALL.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), + ) + }, + ); + + let text_input = TextInput::new( + &mut self.input, + "Type something...", + &self.input_value, + Message::InputChanged, + ) + .padding(10) + .size(20) + .style(self.theme); + + let button = Button::new(&mut self.button, Text::new("Submit")) + .padding(10) + .on_press(Message::ButtonPressed) + .style(self.theme); + + 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 scrollable = Scrollable::new(&mut self.scroll) + .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!")); + + let checkbox = Checkbox::new( + self.toggle_value, + "Toggle me!", + Message::CheckboxToggled, + ) + .style(self.theme); + + let content = Column::new() + .spacing(20) + .padding(20) + .max_width(600) + .push(choose_theme) + .push(Row::new().spacing(10).push(text_input).push(button)) + .push(slider) + .push(progress_bar) + .push( + Row::new() + .spacing(10) + .align_items(Align::Center) + .push(scrollable) + .push(checkbox), + ); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .style(self.theme) + .into() + } +} + +mod style { + use iced::{ + button, checkbox, container, progress_bar, radio, scrollable, slider, + text_input, + }; + + #[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 From<Theme> for Box<dyn container::StyleSheet> { + fn from(theme: Theme) -> Self { + match theme { + Theme::Light => Default::default(), + Theme::Dark => dark::Container.into(), + } + } + } + + impl From<Theme> for Box<dyn radio::StyleSheet> { + fn from(theme: Theme) -> Self { + match theme { + Theme::Light => Default::default(), + Theme::Dark => dark::Radio.into(), + } + } + } + + impl From<Theme> for Box<dyn text_input::StyleSheet> { + fn from(theme: Theme) -> Self { + match theme { + Theme::Light => Default::default(), + Theme::Dark => dark::TextInput.into(), + } + } + } + + impl From<Theme> for Box<dyn button::StyleSheet> { + fn from(theme: Theme) -> Self { + match theme { + Theme::Light => light::Button.into(), + Theme::Dark => dark::Button.into(), + } + } + } + + impl From<Theme> for Box<dyn scrollable::StyleSheet> { + fn from(theme: Theme) -> Self { + match theme { + Theme::Light => Default::default(), + Theme::Dark => dark::Scrollable.into(), + } + } + } + + impl From<Theme> for Box<dyn slider::StyleSheet> { + fn from(theme: Theme) -> Self { + match theme { + Theme::Light => Default::default(), + Theme::Dark => dark::Slider.into(), + } + } + } + + impl From<Theme> for Box<dyn progress_bar::StyleSheet> { + fn from(theme: Theme) -> Self { + match theme { + Theme::Light => Default::default(), + Theme::Dark => dark::ProgressBar.into(), + } + } + } + + impl From<Theme> for Box<dyn checkbox::StyleSheet> { + fn from(theme: Theme) -> Self { + match theme { + Theme::Light => Default::default(), + Theme::Dark => dark::Checkbox.into(), + } + } + } + + mod light { + use iced::{button, Background, Color, Vector}; + + pub struct Button; + + impl button::StyleSheet for Button { + fn active(&self) -> button::Style { + button::Style { + background: Some(Background::Color(Color::from_rgb( + 0.11, 0.42, 0.87, + ))), + border_radius: 12, + 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, scrollable, + slider, text_input, Background, 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: Some(Background::Color(Color::from_rgb8( + 0x36, 0x39, 0x3F, + ))), + text_color: Some(Color::WHITE), + ..container::Style::default() + } + } + } + + pub struct Radio; + + impl radio::StyleSheet for Radio { + fn active(&self) -> radio::Style { + radio::Style { + background: Background::Color(SURFACE), + dot_color: ACTIVE, + border_width: 1, + border_color: ACTIVE, + } + } + + fn hovered(&self) -> radio::Style { + radio::Style { + background: Background::Color(Color { a: 0.5, ..SURFACE }), + ..self.active() + } + } + } + + pub struct TextInput; + + impl text_input::StyleSheet for TextInput { + fn active(&self) -> text_input::Style { + text_input::Style { + background: Background::Color(SURFACE), + border_radius: 2, + border_width: 0, + border_color: Color::TRANSPARENT, + } + } + + fn focused(&self) -> text_input::Style { + text_input::Style { + border_width: 1, + border_color: ACCENT, + ..self.active() + } + } + + fn hovered(&self) -> text_input::Style { + text_input::Style { + border_width: 1, + 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 + } + } + + pub struct Button; + + impl button::StyleSheet for Button { + fn active(&self) -> button::Style { + button::Style { + background: Some(Background::Color(ACTIVE)), + border_radius: 3, + 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, + border_color: Color::WHITE, + ..self.hovered() + } + } + } + + pub struct Scrollable; + + impl scrollable::StyleSheet for Scrollable { + fn active(&self) -> scrollable::Scrollbar { + scrollable::Scrollbar { + background: Some(Background::Color(SURFACE)), + border_radius: 2, + border_width: 0, + border_color: Color::TRANSPARENT, + scroller: scrollable::Scroller { + color: ACTIVE, + border_radius: 2, + border_width: 0, + border_color: Color::TRANSPARENT, + }, + } + } + + fn hovered(&self) -> scrollable::Scrollbar { + let active = self.active(); + + scrollable::Scrollbar { + background: Some(Background::Color(Color { + a: 0.5, + ..SURFACE + })), + 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 }, + color: ACTIVE, + border_width: 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: Background::Color(SURFACE), + bar: Background::Color(ACTIVE), + border_radius: 10, + } + } + } + + pub struct Checkbox; + + impl checkbox::StyleSheet for Checkbox { + fn active(&self, is_checked: bool) -> checkbox::Style { + checkbox::Style { + background: Background::Color(if is_checked { + ACTIVE + } else { + SURFACE + }), + checkmark_color: Color::WHITE, + border_radius: 2, + border_width: 1, + border_color: ACTIVE, + } + } + + fn hovered(&self, is_checked: bool) -> checkbox::Style { + checkbox::Style { + background: Background::Color(Color { + a: 0.8, + ..if is_checked { ACTIVE } else { SURFACE } + }), + ..self.active(is_checked) + } + } + } + } +} diff --git a/examples/todos.rs b/examples/todos.rs index f5f2f459..4166f75a 100644 --- a/examples/todos.rs +++ b/examples/todos.rs @@ -1,7 +1,7 @@ use iced::{ button, scrollable, text_input, Align, Application, Button, Checkbox, - Color, Column, Command, Container, Element, Font, HorizontalAlignment, - Length, Row, Scrollable, Settings, Text, TextInput, + Column, Command, Container, Element, Font, HorizontalAlignment, Length, + Row, Scrollable, Settings, Text, TextInput, }; use serde::{Deserialize, Serialize}; @@ -293,12 +293,10 @@ impl Task { .align_items(Align::Center) .push(checkbox) .push( - Button::new( - edit_button, - edit_icon().color([0.5, 0.5, 0.5]), - ) - .on_press(TaskMessage::Edit) - .padding(10), + Button::new(edit_button, edit_icon()) + .on_press(TaskMessage::Edit) + .padding(10) + .style(style::Button::Icon), ) .into() } @@ -324,13 +322,12 @@ impl Task { delete_button, Row::new() .spacing(10) - .push(delete_icon().color(Color::WHITE)) - .push(Text::new("Delete").color(Color::WHITE)), + .push(delete_icon()) + .push(Text::new("Delete")), ) .on_press(TaskMessage::Delete) .padding(10) - .border_radius(5) - .background(Color::from_rgb(0.8, 0.2, 0.2)), + .style(style::Button::Destructive), ) .into() } @@ -357,17 +354,12 @@ impl Controls { let filter_button = |state, label, filter, current_filter| { let label = Text::new(label).size(16); - let button = if filter == current_filter { - Button::new(state, label.color(Color::WHITE)) - .background(Color::from_rgb(0.2, 0.2, 0.7)) - } else { - Button::new(state, label) - }; - - button - .on_press(Message::FilterChanged(filter)) - .padding(8) - .border_radius(10) + let button = + Button::new(state, label).style(style::Button::Filter { + selected: filter == current_filter, + }); + + button.on_press(Message::FilterChanged(filter)).padding(8) }; Row::new() @@ -560,3 +552,63 @@ impl SavedState { Ok(()) } } + +mod style { + use iced::{button, Background, Color, Vector}; + + pub enum Button { + Filter { selected: bool }, + Icon, + Destructive, + } + + impl button::StyleSheet for Button { + fn active(&self) -> button::Style { + match self { + Button::Filter { selected } => { + if *selected { + button::Style { + background: Some(Background::Color( + Color::from_rgb(0.2, 0.2, 0.7), + )), + border_radius: 10, + text_color: Color::WHITE, + ..button::Style::default() + } + } else { + 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, + 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::Filter { selected } if !selected => { + 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/tour.rs b/examples/tour.rs index 7fe8f4da..b0ee4d96 100644 --- a/examples/tour.rs +++ b/examples/tour.rs @@ -62,8 +62,9 @@ impl Sandbox for Tour { if steps.has_previous() { controls = controls.push( - secondary_button(back_button, "Back") - .on_press(Message::BackPressed), + button(back_button, "Back") + .on_press(Message::BackPressed) + .style(style::Button::Secondary), ); } @@ -71,8 +72,9 @@ impl Sandbox for Tour { if steps.can_continue() { controls = controls.push( - primary_button(next_button, "Next") - .on_press(Message::NextPressed), + button(next_button, "Next") + .on_press(Message::NextPressed) + .style(style::Button::Primary), ); } @@ -698,29 +700,12 @@ fn button<'a, Message>( ) -> Button<'a, Message> { Button::new( state, - Text::new(label) - .color(Color::WHITE) - .horizontal_alignment(HorizontalAlignment::Center), + Text::new(label).horizontal_alignment(HorizontalAlignment::Center), ) .padding(12) - .border_radius(12) .min_width(100) } -fn primary_button<'a, Message>( - state: &'a mut button::State, - label: &str, -) -> Button<'a, Message> { - button(state, label).background(Color::from_rgb(0.11, 0.42, 0.87)) -} - -fn secondary_button<'a, Message>( - state: &'a mut button::State, - label: &str, -) -> Button<'a, Message> { - button(state, label).background(Color::from_rgb(0.4, 0.4, 0.4)) -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Language { Rust, @@ -763,6 +748,38 @@ pub enum Layout { 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, + 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() + } + } + } +} + // This should be gracefully handled by Iced in the future. Probably using our // own proc macro, or maybe the whole process is streamlined by `wasm-pack` at // some point. diff --git a/native/src/element.rs b/native/src/element.rs index 63d2de0c..9b5adb9c 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -235,10 +235,12 @@ where pub fn draw( &self, renderer: &mut Renderer, + defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - self.widget.draw(renderer, layout, cursor_position) + self.widget + .draw(renderer, defaults, layout, cursor_position) } pub(crate) fn hash_layout(&self, state: &mut Hasher) { @@ -316,10 +318,12 @@ where fn draw( &self, renderer: &mut Renderer, + defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - self.widget.draw(renderer, layout, cursor_position) + self.widget + .draw(renderer, defaults, layout, cursor_position) } fn hash_layout(&self, state: &mut Hasher) { @@ -384,10 +388,12 @@ where fn draw( &self, renderer: &mut Renderer, + defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { renderer.explain( + defaults, self.element.widget.as_ref(), layout, cursor_position, diff --git a/native/src/renderer.rs b/native/src/renderer.rs index 7a68ada4..284c95f6 100644 --- a/native/src/renderer.rs +++ b/native/src/renderer.rs @@ -21,14 +21,15 @@ //! [`checkbox::Renderer`]: ../widget/checkbox/trait.Renderer.html mod debugger; -#[cfg(debug_assertions)] -mod null; mod windowed; pub use debugger::Debugger; +pub use windowed::{Target, Windowed}; + +#[cfg(debug_assertions)] +mod null; #[cfg(debug_assertions)] pub use null::Null; -pub use windowed::{Target, Windowed}; use crate::{layout, Element}; @@ -43,6 +44,13 @@ pub trait Renderer: Sized { /// [`Renderer`]: trait.Renderer.html type Output; + /// The default styling attributes of the [`Renderer`]. + /// + /// This type can be leveraged to implement style inheritance. + /// + /// [`Renderer`]: trait.Renderer.html + type Defaults: Default; + /// Lays out the elements of a user interface. /// /// You should override this if you need to perform any operations before or diff --git a/native/src/renderer/debugger.rs b/native/src/renderer/debugger.rs index 4cc50661..30f3d9a0 100644 --- a/native/src/renderer/debugger.rs +++ b/native/src/renderer/debugger.rs @@ -17,6 +17,7 @@ pub trait Debugger: super::Renderer { /// [`Element::explain`]: struct.Element.html#method.explain fn explain<Message>( &mut self, + defaults: &Self::Defaults, widget: &dyn Widget<Message, Self>, layout: Layout<'_>, cursor_position: Point, diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs index 43076d61..df261cdc 100644 --- a/native/src/renderer/null.rs +++ b/native/src/renderer/null.rs @@ -1,20 +1,33 @@ use crate::{ - button, checkbox, column, radio, row, scrollable, text, text_input, - Background, Color, Element, Font, HorizontalAlignment, Layout, Point, + button, checkbox, column, progress_bar, radio, row, scrollable, slider, + text, text_input, Color, Element, Font, HorizontalAlignment, Layout, Point, Rectangle, Renderer, Size, VerticalAlignment, }; /// A renderer that does nothing. +/// +/// It can be useful if you are writing tests! #[derive(Debug, Clone, Copy)] pub struct Null; +impl Null { + /// Creates a new [`Null`] renderer. + /// + /// [`Null`]: struct.Null.html + pub fn new() -> Null { + Null + } +} + impl Renderer for Null { type Output = (); + type Defaults = (); } impl column::Renderer for Null { fn draw<Message>( &mut self, + _defaults: &Self::Defaults, _content: &[Element<'_, Message, Self>], _layout: Layout<'_>, _cursor_position: Point, @@ -25,6 +38,7 @@ impl column::Renderer for Null { impl row::Renderer for Null { fn draw<Message>( &mut self, + _defaults: &Self::Defaults, _content: &[Element<'_, Message, Self>], _layout: Layout<'_>, _cursor_position: Point, @@ -49,6 +63,7 @@ impl text::Renderer for Null { fn draw( &mut self, + _defaults: &Self::Defaults, _bounds: Rectangle, _content: &str, _size: u16, @@ -61,6 +76,8 @@ impl text::Renderer for Null { } impl scrollable::Renderer for Null { + type Style = (); + fn scrollbar( &self, _bounds: Rectangle, @@ -79,12 +96,15 @@ impl scrollable::Renderer for Null { _is_mouse_over_scrollbar: bool, _scrollbar: Option<scrollable::Scrollbar>, _offset: u32, + _style: &Self::Style, _content: Self::Output, ) { } } impl text_input::Renderer for Null { + type Style = (); + fn default_size(&self) -> u16 { 20 } @@ -112,24 +132,31 @@ impl text_input::Renderer for Null { _placeholder: &str, _value: &text_input::Value, _state: &text_input::State, + _style: &Self::Style, ) -> Self::Output { } } impl button::Renderer for Null { - fn draw( + type Style = (); + + fn draw<Message>( &mut self, + _defaults: &Self::Defaults, _bounds: Rectangle, _cursor_position: Point, + _is_disabled: bool, _is_pressed: bool, - _background: Option<Background>, - _border_radius: u16, - _content: Self::Output, + _style: &Self::Style, + _content: &Element<'_, Message, Self>, + _content_layout: Layout<'_>, ) -> Self::Output { } } impl radio::Renderer for Null { + type Style = (); + fn default_size(&self) -> u32 { 20 } @@ -140,11 +167,14 @@ impl radio::Renderer for Null { _is_selected: bool, _is_mouse_over: bool, _label: Self::Output, + _style: &Self::Style, ) { } } impl checkbox::Renderer for Null { + type Style = (); + fn default_size(&self) -> u32 { 20 } @@ -155,6 +185,41 @@ impl checkbox::Renderer for Null { _is_checked: bool, _is_mouse_over: bool, _label: Self::Output, + _style: &Self::Style, + ) { + } +} + +impl slider::Renderer for Null { + type Style = (); + + fn height(&self) -> u32 { + 30 + } + + fn draw( + &mut self, + _bounds: Rectangle, + _cursor_position: Point, + _range: std::ops::RangeInclusive<f32>, + _value: f32, + _is_dragging: bool, + _style_sheet: &Self::Style, + ) { + } +} + +impl progress_bar::Renderer for Null { + type Style = (); + + const DEFAULT_HEIGHT: u16 = 30; + + fn draw( + &self, + _bounds: Rectangle, + _range: std::ops::RangeInclusive<f32>, + _value: f32, + _style: &Self::Style, ) { } } diff --git a/native/src/renderer/windowed.rs b/native/src/renderer/windowed.rs index 813a03f2..ee020ab1 100644 --- a/native/src/renderer/windowed.rs +++ b/native/src/renderer/windowed.rs @@ -4,13 +4,16 @@ use raw_window_handle::HasRawWindowHandle; /// A renderer that can target windows. pub trait Windowed: super::Renderer + Sized { + /// The settings of the renderer. + type Settings: Default; + /// The type of target. type Target: Target<Renderer = Self>; /// Creates a new [`Windowed`] renderer. /// /// [`Windowed`]: trait.Windowed.html - fn new() -> Self; + fn new(settings: Self::Settings) -> Self; /// Performs the drawing operations described in the output on the given /// target. diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 07b936a9..970bf0c1 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -43,24 +43,7 @@ where /// use iced_wgpu::Renderer; /// /// # mod iced_wgpu { - /// # pub struct Renderer; - /// # - /// # impl Renderer { - /// # pub fn new() -> Self { Renderer } - /// # } - /// # - /// # impl iced_native::Renderer for Renderer { type Output = (); } - /// # - /// # impl iced_native::column::Renderer for Renderer { - /// # fn draw<Message>( - /// # &mut self, - /// # _children: &[iced_native::Element<'_, Message, Self>], - /// # _layout: iced_native::Layout<'_>, - /// # _cursor_position: iced_native::Point, - /// # ) -> Self::Output { - /// # () - /// # } - /// # } + /// # pub use iced_native::renderer::Null as Renderer; /// # } /// # /// # use iced_native::Column; @@ -139,24 +122,7 @@ where /// use iced_wgpu::Renderer; /// /// # mod iced_wgpu { - /// # pub struct Renderer; - /// # - /// # impl Renderer { - /// # pub fn new() -> Self { Renderer } - /// # } - /// # - /// # impl iced_native::Renderer for Renderer { type Output = (); } - /// # - /// # impl iced_native::column::Renderer for Renderer { - /// # fn draw<Message>( - /// # &mut self, - /// # _children: &[iced_native::Element<'_, Message, Self>], - /// # _layout: iced_native::Layout<'_>, - /// # _cursor_position: iced_native::Point, - /// # ) -> Self::Output { - /// # () - /// # } - /// # } + /// # pub use iced_native::renderer::Null as Renderer; /// # } /// # /// # use iced_native::Column; @@ -241,24 +207,7 @@ where /// use iced_wgpu::Renderer; /// /// # mod iced_wgpu { - /// # pub struct Renderer; - /// # - /// # impl Renderer { - /// # pub fn new() -> Self { Renderer } - /// # } - /// # - /// # impl iced_native::Renderer for Renderer { type Output = (); } - /// # - /// # impl iced_native::column::Renderer for Renderer { - /// # fn draw<Message>( - /// # &mut self, - /// # _children: &[iced_native::Element<'_, Message, Self>], - /// # _layout: iced_native::Layout<'_>, - /// # _cursor_position: iced_native::Point, - /// # ) -> Self::Output { - /// # () - /// # } - /// # } + /// # pub use iced_native::renderer::Null as Renderer; /// # } /// # /// # use iced_native::Column; @@ -304,6 +253,7 @@ where pub fn draw(&self, renderer: &mut Renderer) -> Renderer::Output { self.root.widget.draw( renderer, + &Renderer::Defaults::default(), Layout::new(&self.layout), self.cursor_position, ) diff --git a/native/src/widget.rs b/native/src/widget.rs index ccc9b47e..f9424b02 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -107,6 +107,7 @@ where fn draw( &self, renderer: &mut Renderer, + defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output; diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 2881105f..51b02172 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -6,8 +6,8 @@ //! [`State`]: struct.State.html use crate::{ input::{mouse, ButtonState}, - layout, Background, Clipboard, Element, Event, Hasher, Layout, Length, - Point, Rectangle, Widget, + layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, + Rectangle, Widget, }; use std::hash::Hash; @@ -28,7 +28,7 @@ use std::hash::Hash; /// .on_press(Message::ButtonPressed); /// ``` #[allow(missing_debug_implementations)] -pub struct Button<'a, Message, Renderer> { +pub struct Button<'a, Message, Renderer: self::Renderer> { state: &'a mut State, content: Element<'a, Message, Renderer>, on_press: Option<Message>, @@ -37,11 +37,13 @@ pub struct Button<'a, Message, Renderer> { min_width: u32, min_height: u32, padding: u16, - background: Option<Background>, - border_radius: u16, + style: Renderer::Style, } -impl<'a, Message, Renderer> Button<'a, Message, Renderer> { +impl<'a, Message, Renderer> Button<'a, Message, Renderer> +where + Renderer: self::Renderer, +{ /// Creates a new [`Button`] with some local [`State`] and the given /// content. /// @@ -60,8 +62,7 @@ impl<'a, Message, Renderer> Button<'a, Message, Renderer> { min_width: 0, min_height: 0, padding: 0, - background: None, - border_radius: 0, + style: Renderer::Style::default(), } } @@ -105,28 +106,19 @@ impl<'a, Message, Renderer> Button<'a, Message, Renderer> { self } - /// Sets the [`Background`] of the [`Button`]. - /// - /// [`Button`]: struct.Button.html - /// [`Background`]: ../../struct.Background.html - pub fn background<T: Into<Background>>(mut self, background: T) -> Self { - self.background = Some(background.into()); - self - } - - /// Sets the border radius of the [`Button`]. + /// Sets the message that will be produced when the [`Button`] is pressed. /// /// [`Button`]: struct.Button.html - pub fn border_radius(mut self, border_radius: u16) -> Self { - self.border_radius = border_radius; + pub fn on_press(mut self, msg: Message) -> Self { + self.on_press = Some(msg); self } - /// Sets the message that will be produced when the [`Button`] is pressed. + /// Sets the style of the [`Button`]. /// /// [`Button`]: struct.Button.html - pub fn on_press(mut self, msg: Message) -> Self { - self.on_press = Some(msg); + pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { + self.style = style.into(); self } } @@ -227,22 +219,19 @@ where fn draw( &self, renderer: &mut Renderer, + defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - let content = self.content.draw( - renderer, - layout.children().next().unwrap(), - cursor_position, - ); - renderer.draw( + defaults, layout.bounds(), cursor_position, + self.on_press.is_none(), self.state.is_pressed, - self.background, - self.border_radius, - content, + &self.style, + &self.content, + layout.children().next().unwrap(), ) } @@ -260,17 +249,22 @@ where /// [`Button`]: struct.Button.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer + Sized { + /// The style supported by this renderer. + type Style: Default; + /// Draws a [`Button`]. /// /// [`Button`]: struct.Button.html - fn draw( + fn draw<Message>( &mut self, + defaults: &Self::Defaults, bounds: Rectangle, cursor_position: Point, + is_disabled: bool, is_pressed: bool, - background: Option<Background>, - border_radius: u16, - content: Self::Output, + style: &Self::Style, + content: &Element<'_, Message, Self>, + content_layout: Layout<'_>, ) -> Self::Output; } diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 06a484c7..95165997 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -3,7 +3,7 @@ use std::hash::Hash; use crate::{ input::{mouse, ButtonState}, - layout, row, text, Align, Clipboard, Color, Element, Event, Font, Hasher, + layout, row, text, Align, Clipboard, Element, Event, Font, Hasher, HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, VerticalAlignment, Widget, }; @@ -13,7 +13,7 @@ use crate::{ /// # Example /// /// ``` -/// # use iced_native::Checkbox; +/// # type Checkbox<Message> = iced_native::Checkbox<Message, iced_native::renderer::Null>; /// # /// pub enum Message { /// CheckboxToggled(bool), @@ -26,15 +26,15 @@ use crate::{ /// ///  #[allow(missing_debug_implementations)] -pub struct Checkbox<Message> { +pub struct Checkbox<Message, Renderer: self::Renderer> { is_checked: bool, on_toggle: Box<dyn Fn(bool) -> Message>, label: String, - label_color: Option<Color>, width: Length, + style: Renderer::Style, } -impl<Message> Checkbox<Message> { +impl<Message, Renderer: self::Renderer> Checkbox<Message, Renderer> { /// Creates a new [`Checkbox`]. /// /// It expects: @@ -53,29 +53,30 @@ impl<Message> Checkbox<Message> { is_checked, on_toggle: Box::new(f), label: String::from(label), - label_color: None, width: Length::Shrink, + style: Renderer::Style::default(), } } - /// Sets the color of the label of the [`Checkbox`]. + /// Sets the width of the [`Checkbox`]. /// /// [`Checkbox`]: struct.Checkbox.html - pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self { - self.label_color = Some(color.into()); + pub fn width(mut self, width: Length) -> Self { + self.width = width; self } - /// Sets the width of the [`Checkbox`]. + /// Sets the style of the [`Checkbox`]. /// /// [`Checkbox`]: struct.Checkbox.html - pub fn width(mut self, width: Length) -> Self { - self.width = width; + pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { + self.style = style.into(); self } } -impl<Message, Renderer> Widget<Message, Renderer> for Checkbox<Message> +impl<Message, Renderer> Widget<Message, Renderer> + for Checkbox<Message, Renderer> where Renderer: self::Renderer + text::Renderer + row::Renderer, { @@ -134,6 +135,7 @@ where fn draw( &self, renderer: &mut Renderer, + defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { @@ -146,11 +148,12 @@ where let label = text::Renderer::draw( renderer, + defaults, label_layout.bounds(), &self.label, text::Renderer::default_size(renderer), Font::Default, - self.label_color, + None, HorizontalAlignment::Left, VerticalAlignment::Center, ); @@ -163,6 +166,7 @@ where self.is_checked, is_mouse_over, label, + &self.style, ) } @@ -179,6 +183,9 @@ where /// [`Checkbox`]: struct.Checkbox.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer { + /// The style supported by this renderer. + type Style: Default; + /// Returns the default size of a [`Checkbox`]. /// /// [`Checkbox`]: struct.Checkbox.html @@ -199,16 +206,19 @@ pub trait Renderer: crate::Renderer { is_checked: bool, is_mouse_over: bool, label: Self::Output, + style: &Self::Style, ) -> Self::Output; } -impl<'a, Message, Renderer> From<Checkbox<Message>> +impl<'a, Message, Renderer> From<Checkbox<Message, Renderer>> for Element<'a, Message, Renderer> where - Renderer: self::Renderer + text::Renderer + row::Renderer, + Renderer: 'static + self::Renderer + text::Renderer + row::Renderer, Message: 'static, { - fn from(checkbox: Checkbox<Message>) -> Element<'a, Message, Renderer> { + fn from( + checkbox: Checkbox<Message, Renderer>, + ) -> Element<'a, Message, Renderer> { Element::new(checkbox) } } diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 0901bff4..79ec5ab4 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -173,10 +173,11 @@ where fn draw( &self, renderer: &mut Renderer, + defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - renderer.draw(&self.children, layout, cursor_position) + renderer.draw(defaults, &self.children, layout, cursor_position) } fn hash_layout(&self, state: &mut Hasher) { @@ -213,6 +214,7 @@ pub trait Renderer: crate::Renderer + Sized { /// [`Layout`]: ../layout/struct.Layout.html fn draw<Message>( &mut self, + defaults: &Self::Defaults, content: &[Element<'_, Message, Self>], layout: Layout<'_>, cursor_position: Point, diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 74f0e0ef..5682fc87 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -3,7 +3,7 @@ use std::hash::Hash; use crate::{ layout, Align, Clipboard, Element, Event, Hasher, Layout, Length, Point, - Widget, + Rectangle, Widget, }; use std::u32; @@ -12,17 +12,21 @@ use std::u32; /// /// It is normally used for alignment purposes. #[allow(missing_debug_implementations)] -pub struct Container<'a, Message, Renderer> { +pub struct Container<'a, Message, Renderer: self::Renderer> { width: Length, height: Length, max_width: u32, max_height: u32, horizontal_alignment: Align, vertical_alignment: Align, + style: Renderer::Style, content: Element<'a, Message, Renderer>, } -impl<'a, Message, Renderer> Container<'a, Message, Renderer> { +impl<'a, Message, Renderer> Container<'a, Message, Renderer> +where + Renderer: self::Renderer, +{ /// Creates an empty [`Container`]. /// /// [`Container`]: struct.Container.html @@ -37,6 +41,7 @@ impl<'a, Message, Renderer> Container<'a, Message, Renderer> { max_height: u32::MAX, horizontal_alignment: Align::Start, vertical_alignment: Align::Start, + style: Renderer::Style::default(), content: content.into(), } } @@ -78,7 +83,6 @@ impl<'a, Message, Renderer> Container<'a, Message, Renderer> { /// [`Container`]: struct.Container.html pub fn center_x(mut self) -> Self { self.horizontal_alignment = Align::Center; - self } @@ -87,7 +91,14 @@ impl<'a, Message, Renderer> Container<'a, Message, Renderer> { /// [`Container`]: struct.Container.html pub fn center_y(mut self) -> Self { self.vertical_alignment = Align::Center; + self + } + /// Sets the style of the [`Container`]. + /// + /// [`Container`]: struct.Container.html + pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { + self.style = style.into(); self } } @@ -95,7 +106,7 @@ impl<'a, Message, Renderer> Container<'a, Message, Renderer> { impl<'a, Message, Renderer> Widget<Message, Renderer> for Container<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: self::Renderer, { fn width(&self) -> Length { self.width @@ -147,13 +158,17 @@ where fn draw( &self, renderer: &mut Renderer, + defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - self.content.draw( - renderer, - layout.children().next().unwrap(), + renderer.draw( + defaults, + layout.bounds(), cursor_position, + &self.style, + &self.content, + layout.children().next().unwrap(), ) } @@ -168,10 +183,35 @@ where } } +/// The renderer of a [`Container`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use a [`Container`] in your user interface. +/// +/// [`Container`]: struct.Container.html +/// [renderer]: ../../renderer/index.html +pub trait Renderer: crate::Renderer { + /// The style supported by this renderer. + type Style: Default; + + /// Draws a [`Container`]. + /// + /// [`Container`]: struct.Container.html + fn draw<Message>( + &mut self, + defaults: &Self::Defaults, + bounds: Rectangle, + cursor_position: Point, + style: &Self::Style, + content: &Element<'_, Message, Self>, + content_layout: Layout<'_>, + ) -> Self::Output; +} + impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>> for Element<'a, Message, Renderer> where - Renderer: 'a + crate::Renderer, + Renderer: 'a + self::Renderer, Message: 'static, { fn from( diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 20375822..1efe4570 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -95,6 +95,7 @@ where fn draw( &self, renderer: &mut Renderer, + _defaults: &Renderer::Defaults, layout: Layout<'_>, _cursor_position: Point, ) -> Renderer::Output { diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs index b1d4fd92..67d1ab83 100644 --- a/native/src/widget/progress_bar.rs +++ b/native/src/widget/progress_bar.rs @@ -1,7 +1,6 @@ //! Provide progress feedback to your users. use crate::{ - layout, Background, Color, Element, Hasher, Layout, Length, Point, - Rectangle, Size, Widget, + layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; use std::{hash::Hash, ops::RangeInclusive}; @@ -10,8 +9,9 @@ use std::{hash::Hash, ops::RangeInclusive}; /// /// # Example /// ``` -/// # use iced_native::ProgressBar; +/// # use iced_native::renderer::Null; /// # +/// # pub type ProgressBar = iced_native::ProgressBar<Null>; /// let value = 50.0; /// /// ProgressBar::new(0.0..=100.0, value); @@ -19,16 +19,15 @@ use std::{hash::Hash, ops::RangeInclusive}; /// ///  #[allow(missing_debug_implementations)] -pub struct ProgressBar { +pub struct ProgressBar<Renderer: self::Renderer> { range: RangeInclusive<f32>, value: f32, width: Length, height: Option<Length>, - background: Option<Background>, - active_color: Option<Color>, + style: Renderer::Style, } -impl ProgressBar { +impl<Renderer: self::Renderer> ProgressBar<Renderer> { /// Creates a new [`ProgressBar`]. /// /// It expects: @@ -42,8 +41,7 @@ impl ProgressBar { range, width: Length::Fill, height: None, - background: None, - active_color: None, + style: Renderer::Style::default(), } } @@ -63,24 +61,16 @@ impl ProgressBar { self } - /// Sets the background of the [`ProgressBar`]. + /// Sets the style of the [`ProgressBar`]. /// /// [`ProgressBar`]: struct.ProgressBar.html - pub fn background(mut self, background: Background) -> Self { - self.background = Some(background); - self - } - - /// Sets the active color of the [`ProgressBar`]. - /// - /// [`ProgressBar`]: struct.ProgressBar.html - pub fn active_color(mut self, active_color: Color) -> Self { - self.active_color = Some(active_color); + pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { + self.style = style.into(); self } } -impl<Message, Renderer> Widget<Message, Renderer> for ProgressBar +impl<Message, Renderer> Widget<Message, Renderer> for ProgressBar<Renderer> where Renderer: self::Renderer, { @@ -111,6 +101,7 @@ where fn draw( &self, renderer: &mut Renderer, + _defaults: &Renderer::Defaults, layout: Layout<'_>, _cursor_position: Point, ) -> Renderer::Output { @@ -118,8 +109,7 @@ where layout.bounds(), self.range.clone(), self.value, - self.background, - self.active_color, + &self.style, ) } @@ -137,6 +127,9 @@ where /// [`ProgressBar`]: struct.ProgressBar.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer { + /// The style supported by this renderer. + type Style: Default; + /// The default height of a [`ProgressBar`]. /// /// [`ProgressBar`]: struct.ProgressBar.html @@ -157,17 +150,19 @@ pub trait Renderer: crate::Renderer { bounds: Rectangle, range: RangeInclusive<f32>, value: f32, - background: Option<Background>, - active_color: Option<Color>, + style: &Self::Style, ) -> Self::Output; } -impl<'a, Message, Renderer> From<ProgressBar> for Element<'a, Message, Renderer> +impl<'a, Message, Renderer> From<ProgressBar<Renderer>> + for Element<'a, Message, Renderer> where - Renderer: self::Renderer, + Renderer: 'static + self::Renderer, Message: 'static, { - fn from(progress_bar: ProgressBar) -> Element<'a, Message, Renderer> { + fn from( + progress_bar: ProgressBar<Renderer>, + ) -> Element<'a, Message, Renderer> { Element::new(progress_bar) } } diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 876f4f48..99743ec3 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -1,7 +1,7 @@ //! Create choices using radio buttons. use crate::{ input::{mouse, ButtonState}, - layout, row, text, Align, Clipboard, Color, Element, Event, Font, Hasher, + layout, row, text, Align, Clipboard, Element, Event, Font, Hasher, HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, VerticalAlignment, Widget, }; @@ -12,7 +12,8 @@ use std::hash::Hash; /// /// # Example /// ``` -/// # use iced_native::Radio; +/// # type Radio<Message> = +/// # iced_native::Radio<Message, iced_native::renderer::Null>; /// # /// #[derive(Debug, Clone, Copy, PartialEq, Eq)] /// pub enum Choice { @@ -34,14 +35,14 @@ use std::hash::Hash; /// ///  #[allow(missing_debug_implementations)] -pub struct Radio<Message> { +pub struct Radio<Message, Renderer: self::Renderer> { is_selected: bool, on_click: Message, label: String, - label_color: Option<Color>, + style: Renderer::Style, } -impl<Message> Radio<Message> { +impl<Message, Renderer: self::Renderer> Radio<Message, Renderer> { /// Creates a new [`Radio`] button. /// /// It expects: @@ -61,20 +62,20 @@ impl<Message> Radio<Message> { is_selected: Some(value) == selected, on_click: f(value), label: String::from(label), - label_color: None, + style: Renderer::Style::default(), } } - /// Sets the `Color` of the label of the [`Radio`]. + /// Sets the style of the [`Radio`] button. /// /// [`Radio`]: struct.Radio.html - pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self { - self.label_color = Some(color.into()); + pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { + self.style = style.into(); self } } -impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message> +impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message, Renderer> where Renderer: self::Renderer + text::Renderer + row::Renderer, Message: Clone, @@ -132,6 +133,7 @@ where fn draw( &self, renderer: &mut Renderer, + defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { @@ -144,11 +146,12 @@ where let label = text::Renderer::draw( renderer, + defaults, label_layout.bounds(), &self.label, text::Renderer::default_size(renderer), Font::Default, - self.label_color, + None, HorizontalAlignment::Left, VerticalAlignment::Center, ); @@ -161,6 +164,7 @@ where self.is_selected, is_mouse_over, label, + &self.style, ) } @@ -177,6 +181,9 @@ where /// [`Radio`]: struct.Radio.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer { + /// The style supported by this renderer. + type Style: Default; + /// Returns the default size of a [`Radio`] button. /// /// [`Radio`]: struct.Radio.html @@ -197,16 +204,17 @@ pub trait Renderer: crate::Renderer { is_selected: bool, is_mouse_over: bool, label: Self::Output, + style: &Self::Style, ) -> Self::Output; } -impl<'a, Message, Renderer> From<Radio<Message>> +impl<'a, Message, Renderer> From<Radio<Message, Renderer>> for Element<'a, Message, Renderer> where - Renderer: self::Renderer + row::Renderer + text::Renderer, + Renderer: 'static + self::Renderer + row::Renderer + text::Renderer, Message: 'static + Clone, { - fn from(radio: Radio<Message>) -> Element<'a, Message, Renderer> { + fn from(radio: Radio<Message, Renderer>) -> Element<'a, Message, Renderer> { Element::new(radio) } } diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 7d951968..b3dc90ba 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -174,10 +174,11 @@ where fn draw( &self, renderer: &mut Renderer, + defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - renderer.draw(&self.children, layout, cursor_position) + renderer.draw(defaults, &self.children, layout, cursor_position) } fn hash_layout(&self, state: &mut Hasher) { @@ -215,6 +216,7 @@ pub trait Renderer: crate::Renderer + Sized { /// [`Layout`]: ../layout/struct.Layout.html fn draw<Message>( &mut self, + defaults: &Self::Defaults, children: &[Element<'_, Message, Self>], layout: Layout<'_>, cursor_position: Point, diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 9fa602d5..e83f25af 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -11,14 +11,15 @@ use std::{f32, hash::Hash, u32}; /// A widget that can vertically display an infinite amount of content with a /// scrollbar. #[allow(missing_debug_implementations)] -pub struct Scrollable<'a, Message, Renderer> { +pub struct Scrollable<'a, Message, Renderer: self::Renderer> { state: &'a mut State, height: Length, max_height: u32, content: Column<'a, Message, Renderer>, + style: Renderer::Style, } -impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> { +impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> { /// Creates a new [`Scrollable`] with the given [`State`]. /// /// [`Scrollable`]: struct.Scrollable.html @@ -29,6 +30,7 @@ impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> { height: Length::Shrink, max_height: u32::MAX, content: Column::new(), + style: Renderer::Style::default(), } } @@ -90,6 +92,14 @@ impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> { self } + /// Sets the style of the [`Scrollable`] . + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { + self.style = style.into(); + self + } + /// Adds an element to the [`Scrollable`]. /// /// [`Scrollable`]: struct.Scrollable.html @@ -105,7 +115,7 @@ impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> { impl<'a, Message, Renderer> Widget<Message, Renderer> for Scrollable<'a, Message, Renderer> where - Renderer: self::Renderer + column::Renderer, + Renderer: 'static + self::Renderer + column::Renderer, { fn width(&self) -> Length { Length::Fill @@ -255,6 +265,7 @@ where fn draw( &self, renderer: &mut Renderer, + defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { @@ -277,7 +288,12 @@ where Point::new(cursor_position.x, -1.0) }; - self.content.draw(renderer, content_layout, cursor_position) + self.content.draw( + renderer, + defaults, + content_layout, + cursor_position, + ) }; self::Renderer::draw( @@ -289,12 +305,13 @@ where is_mouse_over_scrollbar, scrollbar, offset, + &self.style, content, ) } fn hash_layout(&self, state: &mut Hasher) { - std::any::TypeId::of::<Scrollable<'static, (), ()>>().hash(state); + std::any::TypeId::of::<Scrollable<'static, (), Renderer>>().hash(state); self.height.hash(state); self.max_height.hash(state); @@ -441,6 +458,9 @@ pub struct Scroller { /// [`Scrollable`]: struct.Scrollable.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer + Sized { + /// The style supported by this renderer. + type Style: Default; + /// Returns the [`Scrollbar`] given the bounds and content bounds of a /// [`Scrollable`]. /// @@ -477,6 +497,7 @@ pub trait Renderer: crate::Renderer + Sized { is_mouse_over_scrollbar: bool, scrollbar: Option<Scrollbar>, offset: u32, + style: &Self::Style, content: Self::Output, ) -> Self::Output; } @@ -484,7 +505,7 @@ pub trait Renderer: crate::Renderer + Sized { impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>> for Element<'a, Message, Renderer> where - Renderer: 'a + self::Renderer + column::Renderer, + Renderer: 'static + self::Renderer + column::Renderer, Message: 'static, { fn from( diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index f446f7e8..008203fe 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -21,8 +21,9 @@ use std::{hash::Hash, ops::RangeInclusive}; /// /// # Example /// ``` -/// # use iced_native::{slider, Slider}; +/// # use iced_native::{slider, renderer::Null}; /// # +/// # pub type Slider<'a, Message> = iced_native::Slider<'a, Message, Null>; /// pub enum Message { /// SliderChanged(f32), /// } @@ -35,15 +36,16 @@ use std::{hash::Hash, ops::RangeInclusive}; /// ///  #[allow(missing_debug_implementations)] -pub struct Slider<'a, Message> { +pub struct Slider<'a, Message, Renderer: self::Renderer> { state: &'a mut State, range: RangeInclusive<f32>, value: f32, on_change: Box<dyn Fn(f32) -> Message>, width: Length, + style: Renderer::Style, } -impl<'a, Message> Slider<'a, Message> { +impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> { /// Creates a new [`Slider`]. /// /// It expects: @@ -71,6 +73,7 @@ impl<'a, Message> Slider<'a, Message> { range, on_change: Box::new(on_change), width: Length::Fill, + style: Renderer::Style::default(), } } @@ -81,6 +84,14 @@ impl<'a, Message> Slider<'a, Message> { self.width = width; self } + + /// Sets the style of the [`Slider`]. + /// + /// [`Slider`]: struct.Slider.html + pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { + self.style = style.into(); + self + } } /// The local state of a [`Slider`]. @@ -100,7 +111,8 @@ impl State { } } -impl<'a, Message, Renderer> Widget<Message, Renderer> for Slider<'a, Message> +impl<'a, Message, Renderer> Widget<Message, Renderer> + for Slider<'a, Message, Renderer> where Renderer: self::Renderer, { @@ -178,6 +190,7 @@ where fn draw( &self, renderer: &mut Renderer, + _defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { @@ -187,6 +200,7 @@ where self.range.clone(), self.value, self.state.is_dragging, + &self.style, ) } @@ -203,6 +217,9 @@ where /// [`Slider`]: struct.Slider.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer { + /// The style supported by this renderer. + type Style: Default; + /// Returns the height of the [`Slider`]. /// /// [`Slider`]: struct.Slider.html @@ -227,16 +244,19 @@ pub trait Renderer: crate::Renderer { range: RangeInclusive<f32>, value: f32, is_dragging: bool, + style: &Self::Style, ) -> Self::Output; } -impl<'a, Message, Renderer> From<Slider<'a, Message>> +impl<'a, Message, Renderer> From<Slider<'a, Message, Renderer>> for Element<'a, Message, Renderer> where - Renderer: self::Renderer, + Renderer: 'static + self::Renderer, Message: 'static, { - fn from(slider: Slider<'a, Message>) -> Element<'a, Message, Renderer> { + fn from( + slider: Slider<'a, Message, Renderer>, + ) -> Element<'a, Message, Renderer> { Element::new(slider) } } diff --git a/native/src/widget/space.rs b/native/src/widget/space.rs index 2029c52f..24c94bf6 100644 --- a/native/src/widget/space.rs +++ b/native/src/widget/space.rs @@ -68,6 +68,7 @@ where fn draw( &self, renderer: &mut Renderer, + _defaults: &Renderer::Defaults, layout: Layout<'_>, _cursor_position: Point, ) -> Renderer::Output { diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs index 25587ffa..063730bb 100644 --- a/native/src/widget/svg.rs +++ b/native/src/widget/svg.rs @@ -91,6 +91,7 @@ where fn draw( &self, renderer: &mut Renderer, + _defaults: &Renderer::Defaults, layout: Layout<'_>, _cursor_position: Point, ) -> Renderer::Output { diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs index caa81db0..e4490fb6 100644 --- a/native/src/widget/text.rs +++ b/native/src/widget/text.rs @@ -146,10 +146,12 @@ where fn draw( &self, renderer: &mut Renderer, + defaults: &Renderer::Defaults, layout: Layout<'_>, _cursor_position: Point, ) -> Renderer::Output { renderer.draw( + defaults, layout.bounds(), &self.content, self.size.unwrap_or(renderer.default_size()), @@ -209,6 +211,7 @@ pub trait Renderer: crate::Renderer { /// [`VerticalAlignment`]: enum.VerticalAlignment.html fn draw( &mut self, + defaults: &Self::Defaults, bounds: Rectangle, content: &str, size: u16, diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index f744da27..25032559 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -15,8 +15,9 @@ use unicode_segmentation::UnicodeSegmentation; /// /// # Example /// ``` -/// # use iced_native::{text_input, TextInput}; +/// # use iced_native::{text_input, renderer::Null}; /// # +/// # pub type TextInput<'a, Message> = iced_native::TextInput<'a, Message, Null>; /// #[derive(Debug, Clone)] /// enum Message { /// TextInputChanged(String), @@ -35,7 +36,7 @@ use unicode_segmentation::UnicodeSegmentation; /// ``` ///  #[allow(missing_debug_implementations)] -pub struct TextInput<'a, Message> { +pub struct TextInput<'a, Message, Renderer: self::Renderer> { state: &'a mut State, placeholder: String, value: Value, @@ -46,9 +47,10 @@ pub struct TextInput<'a, Message> { size: Option<u16>, on_change: Box<dyn Fn(String) -> Message>, on_submit: Option<Message>, + style: Renderer::Style, } -impl<'a, Message> TextInput<'a, Message> { +impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> { /// Creates a new [`TextInput`]. /// /// It expects: @@ -64,7 +66,7 @@ impl<'a, Message> TextInput<'a, Message> { placeholder: &str, value: &str, on_change: F, - ) -> TextInput<'a, Message> + ) -> Self where F: 'static + Fn(String) -> Message, { @@ -79,6 +81,7 @@ impl<'a, Message> TextInput<'a, Message> { size: None, on_change: Box::new(on_change), on_submit: None, + style: Renderer::Style::default(), } } @@ -130,11 +133,20 @@ impl<'a, Message> TextInput<'a, Message> { self.on_submit = Some(message); self } + + /// Sets the style of the [`TextInput`]. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { + self.style = style.into(); + self + } } -impl<'a, Message, Renderer> Widget<Message, Renderer> for TextInput<'a, Message> +impl<'a, Message, Renderer> Widget<Message, Renderer> + for TextInput<'a, Message, Renderer> where - Renderer: self::Renderer, + Renderer: 'static + self::Renderer, Message: Clone + std::fmt::Debug, { fn width(&self) -> Length { @@ -343,6 +355,7 @@ where fn draw( &self, renderer: &mut Renderer, + _defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { @@ -358,6 +371,7 @@ where &self.placeholder, &self.value.secure(), &self.state, + &self.style, ) } else { renderer.draw( @@ -368,6 +382,7 @@ where &self.placeholder, &self.value, &self.state, + &self.style, ) } } @@ -375,7 +390,7 @@ where fn hash_layout(&self, state: &mut Hasher) { use std::{any::TypeId, hash::Hash}; - TypeId::of::<TextInput<'static, ()>>().hash(state); + TypeId::of::<TextInput<'static, (), Renderer>>().hash(state); self.width.hash(state); self.max_width.hash(state); @@ -392,6 +407,9 @@ where /// [`TextInput`]: struct.TextInput.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer + Sized { + /// The style supported by this renderer. + type Style: Default; + /// Returns the default size of the text of the [`TextInput`]. /// /// [`TextInput`]: struct.TextInput.html @@ -440,17 +458,18 @@ pub trait Renderer: crate::Renderer + Sized { placeholder: &str, value: &Value, state: &State, + style: &Self::Style, ) -> Self::Output; } -impl<'a, Message, Renderer> From<TextInput<'a, Message>> +impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>> for Element<'a, Message, Renderer> where Renderer: 'static + self::Renderer, Message: 'static + Clone + std::fmt::Debug, { fn from( - text_input: TextInput<'a, Message>, + text_input: TextInput<'a, Message, Renderer>, ) -> Element<'a, Message, Renderer> { Element::new(text_input) } diff --git a/src/application.rs b/src/application.rs index a7e826fb..7dd76774 100644 --- a/src/application.rs +++ b/src/application.rs @@ -151,7 +151,12 @@ pub trait Application: Sized { Self: 'static, { #[cfg(not(target_arch = "wasm32"))] - <Instance<Self> as iced_winit::Application>::run(_settings.into()); + <Instance<Self> as iced_winit::Application>::run( + _settings.into(), + iced_wgpu::Settings { + default_font: _settings.default_font, + }, + ); #[cfg(target_arch = "wasm32")] <Instance<Self> as iced_web::Application>::run(); diff --git a/src/native.rs b/src/native.rs index 022cf337..35441a3e 100644 --- a/src/native.rs +++ b/src/native.rs @@ -1,6 +1,6 @@ pub use iced_winit::{ Align, Background, Color, Command, Font, HorizontalAlignment, Length, - Space, Subscription, VerticalAlignment, + Space, Subscription, Vector, VerticalAlignment, }; pub mod widget { @@ -22,58 +22,7 @@ pub mod widget { //! //! [`TextInput`]: text_input/struct.TextInput.html //! [`text_input::State`]: text_input/struct.State.html - pub mod button { - //! Allow your users to perform actions by pressing a button. - //! - //! A [`Button`] has some local [`State`]. - //! - //! [`Button`]: type.Button.html - //! [`State`]: struct.State.html - - /// A widget that produces a message when clicked. - /// - /// This is an alias of an `iced_native` button with a default - /// `Renderer`. - pub type Button<'a, Message> = - iced_winit::Button<'a, Message, iced_wgpu::Renderer>; - - pub use iced_winit::button::State; - } - - pub mod scrollable { - //! Navigate an endless amount of content with a scrollbar. - - /// A widget that can vertically display an infinite amount of content - /// with a scrollbar. - /// - /// This is an alias of an `iced_native` scrollable with a default - /// `Renderer`. - pub type Scrollable<'a, Message> = - iced_winit::Scrollable<'a, Message, iced_wgpu::Renderer>; - - pub use iced_winit::scrollable::State; - } - - pub mod text_input { - //! Ask for information using text fields. - //! - //! A [`TextInput`] has some local [`State`]. - //! - //! [`TextInput`]: struct.TextInput.html - //! [`State`]: struct.State.html - pub use iced_winit::text_input::{State, TextInput}; - } - - pub mod slider { - //! Display an interactive selector of a single value from a range of - //! values. - //! - //! A [`Slider`] has some local [`State`]. - //! - //! [`Slider`]: struct.Slider.html - //! [`State`]: struct.State.html - pub use iced_winit::slider::{Slider, State}; - } + pub use iced_wgpu::widget::*; pub mod image { //! Display images in your user interface. @@ -85,12 +34,13 @@ pub mod widget { pub use iced_winit::svg::{Handle, Svg}; } - pub use iced_winit::{Checkbox, ProgressBar, Radio, Text}; + pub use iced_winit::Text; #[doc(no_inline)] pub use { - button::Button, image::Image, scrollable::Scrollable, slider::Slider, - svg::Svg, text_input::TextInput, + button::Button, checkbox::Checkbox, container::Container, image::Image, + progress_bar::ProgressBar, radio::Radio, scrollable::Scrollable, + slider::Slider, svg::Svg, text_input::TextInput, }; /// A container that distributes its contents vertically. @@ -104,13 +54,6 @@ pub mod widget { /// This is an alias of an `iced_native` row with a default `Renderer`. pub type Row<'a, Message> = iced_winit::Row<'a, Message, iced_wgpu::Renderer>; - - /// An element decorating some content. - /// - /// This is an alias of an `iced_native` container with a default - /// `Renderer`. - pub type Container<'a, Message> = - iced_winit::Container<'a, Message, iced_wgpu::Renderer>; } #[doc(no_inline)] diff --git a/src/settings.rs b/src/settings.rs index 62a1a614..e20edc97 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -9,6 +9,12 @@ pub struct Settings { /// /// [`Window`]: struct.Window.html pub window: Window, + + /// The bytes of the font that will be used by default. + /// + /// If `None` is provided, a default system font will be chosen. + // TODO: Add `name` for web compatibility + pub default_font: Option<&'static [u8]>, } /// The window settings of an application. diff --git a/style/Cargo.toml b/style/Cargo.toml new file mode 100644 index 00000000..5928c60d --- /dev/null +++ b/style/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "iced_style" +version = "0.1.0-alpha" +authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] +edition = "2018" +description = "The default set of styles of Iced" +license = "MIT" +repository = "https://github.com/hecrj/iced" +documentation = "https://docs.rs/iced_style" +keywords = ["gui", "ui", "graphics", "interface", "widgets"] +categories = ["gui"] + +[dependencies] +iced_core = { version = "0.1.0", path = "../core" } diff --git a/style/src/button.rs b/style/src/button.rs new file mode 100644 index 00000000..93c27860 --- /dev/null +++ b/style/src/button.rs @@ -0,0 +1,96 @@ +//! Allow your users to perform actions by pressing a button. +use iced_core::{Background, Color, Vector}; + +/// The appearance of a button. +#[derive(Debug)] +pub struct Style { + pub shadow_offset: Vector, + pub background: Option<Background>, + pub border_radius: u16, + pub border_width: u16, + pub border_color: Color, + pub text_color: Color, +} + +impl std::default::Default for Style { + fn default() -> Self { + Self { + shadow_offset: Vector::default(), + background: None, + border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, + text_color: Color::BLACK, + } + } +} + +/// A set of rules that dictate the style of a button. +pub trait StyleSheet { + fn active(&self) -> Style; + + fn hovered(&self) -> Style { + let active = self.active(); + + Style { + shadow_offset: active.shadow_offset + Vector::new(0.0, 1.0), + ..active + } + } + + fn pressed(&self) -> Style { + Style { + shadow_offset: Vector::default(), + ..self.active() + } + } + + fn disabled(&self) -> Style { + let active = self.active(); + + Style { + shadow_offset: Vector::default(), + background: active.background.map(|background| match background { + Background::Color(color) => Background::Color(Color { + a: color.a * 0.5, + ..color + }), + }), + text_color: Color { + a: active.text_color.a * 0.5, + ..active.text_color + }, + ..active + } + } +} + +struct Default; + +impl StyleSheet for Default { + fn active(&self) -> Style { + Style { + shadow_offset: Vector::new(0.0, 1.0), + background: Some(Background::Color([0.5, 0.5, 0.5].into())), + border_radius: 5, + border_width: 0, + border_color: Color::TRANSPARENT, + text_color: Color::WHITE, + } + } +} + +impl std::default::Default for Box<dyn StyleSheet> { + fn default() -> Self { + Box::new(Default) + } +} + +impl<T> From<T> for Box<dyn StyleSheet> +where + T: 'static + StyleSheet, +{ + fn from(style: T) -> Self { + Box::new(style) + } +} diff --git a/style/src/checkbox.rs b/style/src/checkbox.rs new file mode 100644 index 00000000..3c645f15 --- /dev/null +++ b/style/src/checkbox.rs @@ -0,0 +1,55 @@ +//! Show toggle controls using checkboxes. +use iced_core::{Background, Color}; + +/// The appearance of a checkbox. +#[derive(Debug)] +pub struct Style { + pub background: Background, + pub checkmark_color: Color, + pub border_radius: u16, + pub border_width: u16, + pub border_color: Color, +} + +/// A set of rules that dictate the style of a checkbox. +pub trait StyleSheet { + fn active(&self, is_checked: bool) -> Style; + + fn hovered(&self, is_checked: bool) -> Style; +} + +struct Default; + +impl StyleSheet for Default { + fn active(&self, _is_checked: bool) -> Style { + Style { + background: Background::Color(Color::from_rgb(0.95, 0.95, 0.95)), + checkmark_color: Color::from_rgb(0.3, 0.3, 0.3), + border_radius: 5, + border_width: 1, + border_color: Color::from_rgb(0.6, 0.6, 0.6), + } + } + + fn hovered(&self, is_checked: bool) -> Style { + Style { + background: Background::Color(Color::from_rgb(0.90, 0.90, 0.90)), + ..self.active(is_checked) + } + } +} + +impl std::default::Default for Box<dyn StyleSheet> { + fn default() -> Self { + Box::new(Default) + } +} + +impl<T> From<T> for Box<dyn StyleSheet> +where + T: 'static + StyleSheet, +{ + fn from(style: T) -> Self { + Box::new(style) + } +} diff --git a/style/src/container.rs b/style/src/container.rs new file mode 100644 index 00000000..d2247342 --- /dev/null +++ b/style/src/container.rs @@ -0,0 +1,59 @@ +//! Decorate content and apply alignment. +use iced_core::{Background, Color}; + +/// The appearance of a container. +#[derive(Debug, Clone, Copy)] +pub struct Style { + pub text_color: Option<Color>, + pub background: Option<Background>, + pub border_radius: u16, + pub border_width: u16, + pub border_color: Color, +} + +impl std::default::Default for Style { + fn default() -> Self { + Self { + text_color: None, + background: None, + border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, + } + } +} + +/// A set of rules that dictate the style of a container. +pub trait StyleSheet { + /// Produces the style of a container. + fn style(&self) -> Style; +} + +struct Default; + +impl StyleSheet for Default { + fn style(&self) -> Style { + Style { + text_color: None, + background: None, + border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, + } + } +} + +impl std::default::Default for Box<dyn StyleSheet> { + fn default() -> Self { + Box::new(Default) + } +} + +impl<T> From<T> for Box<dyn StyleSheet> +where + T: 'static + StyleSheet, +{ + fn from(style: T) -> Self { + Box::new(style) + } +} diff --git a/style/src/lib.rs b/style/src/lib.rs new file mode 100644 index 00000000..e0f56594 --- /dev/null +++ b/style/src/lib.rs @@ -0,0 +1,8 @@ +pub mod button; +pub mod checkbox; +pub mod container; +pub mod progress_bar; +pub mod radio; +pub mod scrollable; +pub mod slider; +pub mod text_input; diff --git a/style/src/progress_bar.rs b/style/src/progress_bar.rs new file mode 100644 index 00000000..73503fa8 --- /dev/null +++ b/style/src/progress_bar.rs @@ -0,0 +1,42 @@ +//! Provide progress feedback to your users. +use iced_core::{Background, Color}; + +/// The appearance of a progress bar. +#[derive(Debug)] +pub struct Style { + pub background: Background, + pub bar: Background, + pub border_radius: u16, +} + +/// A set of rules that dictate the style of a progress bar. +pub trait StyleSheet { + fn style(&self) -> Style; +} + +struct Default; + +impl StyleSheet for Default { + fn style(&self) -> Style { + Style { + background: Background::Color(Color::from_rgb(0.6, 0.6, 0.6)), + bar: Background::Color(Color::from_rgb(0.3, 0.9, 0.3)), + border_radius: 5, + } + } +} + +impl std::default::Default for Box<dyn StyleSheet> { + fn default() -> Self { + Box::new(Default) + } +} + +impl<T> From<T> for Box<dyn StyleSheet> +where + T: 'static + StyleSheet, +{ + fn from(style: T) -> Self { + Box::new(style) + } +} diff --git a/style/src/radio.rs b/style/src/radio.rs new file mode 100644 index 00000000..1f0689b9 --- /dev/null +++ b/style/src/radio.rs @@ -0,0 +1,53 @@ +//! Create choices using radio buttons. +use iced_core::{Background, Color}; + +/// The appearance of a radio button. +#[derive(Debug)] +pub struct Style { + pub background: Background, + pub dot_color: Color, + pub border_width: u16, + pub border_color: Color, +} + +/// A set of rules that dictate the style of a radio button. +pub trait StyleSheet { + fn active(&self) -> Style; + + fn hovered(&self) -> Style; +} + +struct Default; + +impl StyleSheet for Default { + fn active(&self) -> Style { + Style { + background: Background::Color(Color::from_rgb(0.95, 0.95, 0.95)), + dot_color: Color::from_rgb(0.3, 0.3, 0.3), + border_width: 1, + border_color: Color::from_rgb(0.6, 0.6, 0.6), + } + } + + fn hovered(&self) -> Style { + Style { + background: Background::Color(Color::from_rgb(0.90, 0.90, 0.90)), + ..self.active() + } + } +} + +impl std::default::Default for Box<dyn StyleSheet> { + fn default() -> Self { + Box::new(Default) + } +} + +impl<T> From<T> for Box<dyn StyleSheet> +where + T: 'static + StyleSheet, +{ + fn from(style: T) -> Self { + Box::new(style) + } +} diff --git a/style/src/scrollable.rs b/style/src/scrollable.rs new file mode 100644 index 00000000..690c14a2 --- /dev/null +++ b/style/src/scrollable.rs @@ -0,0 +1,76 @@ +//! Navigate an endless amount of content with a scrollbar. +use iced_core::{Background, Color}; + +/// The appearance of a scrollable. +#[derive(Debug, Clone, Copy)] +pub struct Scrollbar { + pub background: Option<Background>, + pub border_radius: u16, + pub border_width: u16, + pub border_color: Color, + pub scroller: Scroller, +} + +/// The appearance of the scroller of a scrollable. +#[derive(Debug, Clone, Copy)] +pub struct Scroller { + pub color: Color, + pub border_radius: u16, + pub border_width: u16, + pub border_color: Color, +} + +/// A set of rules that dictate the style of a scrollable. +pub trait StyleSheet { + /// Produces the style of an active scrollbar. + fn active(&self) -> Scrollbar; + + /// Produces the style of an hovered scrollbar. + fn hovered(&self) -> Scrollbar; + + /// Produces the style of a scrollbar that is being dragged. + fn dragging(&self) -> Scrollbar { + self.hovered() + } +} + +struct Default; + +impl StyleSheet for Default { + fn active(&self) -> Scrollbar { + Scrollbar { + background: None, + border_radius: 5, + border_width: 0, + border_color: Color::TRANSPARENT, + scroller: Scroller { + color: [0.0, 0.0, 0.0, 0.7].into(), + border_radius: 5, + border_width: 0, + border_color: Color::TRANSPARENT, + }, + } + } + + fn hovered(&self) -> Scrollbar { + Scrollbar { + background: Some(Background::Color([0.0, 0.0, 0.0, 0.3].into())), + ..self.active() + } + } +} + +impl std::default::Default for Box<dyn StyleSheet> { + fn default() -> Self { + Box::new(Default) + } +} + +impl<T> From<T> for Box<dyn StyleSheet> +where + T: 'static + StyleSheet, +{ + fn from(style: T) -> Self { + Box::new(style) + } +} diff --git a/style/src/slider.rs b/style/src/slider.rs new file mode 100644 index 00000000..776e180c --- /dev/null +++ b/style/src/slider.rs @@ -0,0 +1,95 @@ +//! Display an interactive selector of a single value from a range of values. +use iced_core::Color; + +/// The appearance of a slider. +#[derive(Debug, Clone, Copy)] +pub struct Style { + pub rail_colors: (Color, Color), + pub handle: Handle, +} + +/// The appearance of the handle of a slider. +#[derive(Debug, Clone, Copy)] +pub struct Handle { + pub shape: HandleShape, + pub color: Color, + pub border_width: u16, + pub border_color: Color, +} + +/// The shape of the handle of a slider. +#[derive(Debug, Clone, Copy)] +pub enum HandleShape { + Circle { radius: u16 }, + Rectangle { width: u16, border_radius: u16 }, +} + +/// A set of rules that dictate the style of a slider. +pub trait StyleSheet { + /// Produces the style of an active slider. + fn active(&self) -> Style; + + /// Produces the style of an hovered slider. + fn hovered(&self) -> Style; + + /// Produces the style of a slider that is being dragged. + fn dragging(&self) -> Style; +} + +struct Default; + +impl StyleSheet for Default { + fn active(&self) -> Style { + Style { + rail_colors: ([0.6, 0.6, 0.6, 0.5].into(), Color::WHITE), + handle: Handle { + shape: HandleShape::Rectangle { + width: 8, + border_radius: 4, + }, + color: Color::from_rgb(0.95, 0.95, 0.95), + border_color: Color::from_rgb(0.6, 0.6, 0.6), + border_width: 1, + }, + } + } + + fn hovered(&self) -> Style { + let active = self.active(); + + Style { + handle: Handle { + color: Color::from_rgb(0.90, 0.90, 0.90), + ..active.handle + }, + ..active + } + } + + fn dragging(&self) -> Style { + let active = self.active(); + + Style { + handle: Handle { + color: Color::from_rgb(0.85, 0.85, 0.85), + ..active.handle + }, + ..active + } + } +} + +impl std::default::Default for Box<dyn StyleSheet> { + fn default() -> Self { + Box::new(Default) + } +} + +impl<T> From<T> for Box<dyn StyleSheet> +where + T: 'static + StyleSheet, +{ + fn from(style: T) -> Self { + Box::new(style) + } +} diff --git a/style/src/text_input.rs b/style/src/text_input.rs new file mode 100644 index 00000000..c5123b20 --- /dev/null +++ b/style/src/text_input.rs @@ -0,0 +1,83 @@ +//! Display fields that can be filled with text. +use iced_core::{Background, Color}; + +/// The appearance of a text input. +#[derive(Debug, Clone, Copy)] +pub struct Style { + pub background: Background, + pub border_radius: u16, + pub border_width: u16, + pub border_color: Color, +} + +impl std::default::Default for Style { + fn default() -> Self { + Self { + background: Background::Color(Color::WHITE), + border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, + } + } +} + +/// A set of rules that dictate the style of a text input. +pub trait StyleSheet { + /// Produces the style of an active text input. + fn active(&self) -> Style; + + /// Produces the style of a focused text input. + fn focused(&self) -> Style; + + fn placeholder_color(&self) -> Color; + + fn value_color(&self) -> Color; + + /// Produces the style of an hovered text input. + fn hovered(&self) -> Style { + self.focused() + } +} + +struct Default; + +impl StyleSheet for Default { + fn active(&self) -> Style { + Style { + background: Background::Color(Color::WHITE), + border_radius: 5, + border_width: 1, + border_color: Color::from_rgb(0.7, 0.7, 0.7), + } + } + + fn focused(&self) -> Style { + Style { + border_color: Color::from_rgb(0.5, 0.5, 0.5), + ..self.active() + } + } + + fn placeholder_color(&self) -> Color { + Color::from_rgb(0.7, 0.7, 0.7) + } + + fn value_color(&self) -> Color { + Color::from_rgb(0.3, 0.3, 0.3) + } +} + +impl std::default::Default for Box<dyn StyleSheet> { + fn default() -> Self { + Box::new(Default) + } +} + +impl<T> From<T> for Box<dyn StyleSheet> +where + T: 'static + StyleSheet, +{ + fn from(style: T) -> Self { + Box::new(style) + } +} diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index bb241914..19d41bba 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -12,6 +12,7 @@ svg = ["resvg"] [dependencies] iced_native = { version = "0.1.0", path = "../native" } +iced_style = { version = "0.1.0-alpha", path = "../style" } wgpu = "0.4" glyph_brush = "0.6" wgpu_glyph = { version = "0.7", git = "https://github.com/hecrj/wgpu_glyph", branch = "fix/font-load-panic" } diff --git a/wgpu/src/defaults.rs b/wgpu/src/defaults.rs new file mode 100644 index 00000000..11718a87 --- /dev/null +++ b/wgpu/src/defaults.rs @@ -0,0 +1,32 @@ +//! Use default styling attributes to inherit styles. +use iced_native::Color; + +/// Some default styling attributes. +#[derive(Debug, Clone, Copy)] +pub struct Defaults { + /// Text styling + pub text: Text, +} + +impl Default for Defaults { + fn default() -> Defaults { + Defaults { + text: Text::default(), + } + } +} + +/// Some default text styling attributes. +#[derive(Debug, Clone, Copy)] +pub struct Text { + /// The default color of text + pub color: Color, +} + +impl Default for Text { + fn default() -> Text { + Text { + color: Color::BLACK, + } + } +} diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 972f56af..ab14987c 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -24,18 +24,25 @@ #![deny(unused_results)] #![deny(unsafe_code)] #![deny(rust_2018_idioms)] +pub mod defaults; pub mod triangle; +pub mod widget; mod image; mod primitive; mod quad; mod renderer; +mod settings; mod text; mod transformation; -pub(crate) use crate::image::Image; -pub(crate) use quad::Quad; -pub(crate) use transformation::Transformation; - +pub use defaults::Defaults; pub use primitive::Primitive; pub use renderer::{Renderer, Target}; +pub use settings::Settings; +#[doc(no_inline)] +pub use widget::*; + +pub(crate) use self::image::Image; +pub(crate) use quad::Quad; +pub(crate) use transformation::Transformation; diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 815ba3b0..481252ef 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -41,6 +41,10 @@ pub enum Primitive { background: Background, /// The border radius of the quad border_radius: u16, + /// The border width of the quad + border_width: u16, + /// The border color of the quad + border_color: Color, }, /// An image primitive Image { diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index c292dec3..fe3276a3 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -125,9 +125,19 @@ impl Pipeline { }, wgpu::VertexAttributeDescriptor { shader_location: 4, - format: wgpu::VertexFormat::Float, + format: wgpu::VertexFormat::Float4, offset: 4 * (2 + 2 + 4), }, + wgpu::VertexAttributeDescriptor { + shader_location: 5, + format: wgpu::VertexFormat::Float, + offset: 4 * (2 + 2 + 4 + 4), + }, + wgpu::VertexAttributeDescriptor { + shader_location: 6, + format: wgpu::VertexFormat::Float, + offset: 4 * (2 + 2 + 4 + 4 + 1), + }, ], }, ], @@ -233,7 +243,8 @@ impl Pipeline { bounds.x, bounds.y, bounds.width, - bounds.height, + // TODO: Address anti-aliasing adjustments properly + bounds.height + 1, ); render_pass.draw_indexed( @@ -277,7 +288,9 @@ pub struct Quad { pub position: [f32; 2], pub scale: [f32; 2], pub color: [f32; 4], + pub border_color: [f32; 4], pub border_radius: f32, + pub border_width: f32, } impl Quad { diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 050daca9..9757904c 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -1,5 +1,6 @@ use crate::{ - image, quad, text, triangle, Image, Primitive, Quad, Transformation, + image, quad, text, triangle, Defaults, Image, Primitive, Quad, Settings, + Transformation, }; use iced_native::{ renderer::{Debugger, Windowed}, @@ -24,7 +25,7 @@ pub struct Renderer { device: Device, queue: Queue, quad_pipeline: quad::Pipeline, - image_pipeline: crate::image::Pipeline, + image_pipeline: image::Pipeline, text_pipeline: text::Pipeline, triangle_pipeline: crate::triangle::Pipeline, } @@ -52,7 +53,7 @@ impl<'a> Layer<'a> { } impl Renderer { - fn new() -> Self { + fn new(settings: Settings) -> Self { let adapter = Adapter::request(&RequestAdapterOptions { power_preference: PowerPreference::Default, backends: BackendBit::all(), @@ -66,7 +67,8 @@ impl Renderer { limits: Limits { max_bind_groups: 2 }, }); - let text_pipeline = text::Pipeline::new(&mut device); + let text_pipeline = + text::Pipeline::new(&mut device, settings.default_font); let quad_pipeline = quad::Pipeline::new(&mut device); let image_pipeline = crate::image::Pipeline::new(&mut device); let triangle_pipeline = triangle::Pipeline::new(&mut device); @@ -223,6 +225,8 @@ impl Renderer { bounds, background, border_radius, + border_width, + border_color, } => { // TODO: Move some of this computations to the GPU (?) layer.quads.push(Quad { @@ -235,6 +239,8 @@ impl Renderer { Background::Color(color) => color.into_linear(), }, border_radius: *border_radius as f32, + border_width: *border_width as f32, + border_color: border_color.into_linear(), }); } Primitive::Image { handle, bounds } => { @@ -434,6 +440,7 @@ impl Renderer { impl iced_native::Renderer for Renderer { type Output = (Primitive, MouseCursor); + type Defaults = Defaults; fn layout<'a, Message>( &mut self, @@ -448,10 +455,11 @@ impl iced_native::Renderer for Renderer { } impl Windowed for Renderer { + type Settings = Settings; type Target = Target; - fn new() -> Self { - Self::new() + fn new(settings: Settings) -> Self { + Self::new(settings) } fn draw<T: AsRef<str>>( @@ -467,13 +475,15 @@ impl Windowed for Renderer { impl Debugger for Renderer { fn explain<Message>( &mut self, + defaults: &Defaults, widget: &dyn Widget<Message, Self>, layout: Layout<'_>, cursor_position: Point, color: Color, ) -> Self::Output { let mut primitives = Vec::new(); - let (primitive, cursor) = widget.draw(self, layout, cursor_position); + let (primitive, cursor) = + widget.draw(self, defaults, layout, cursor_position); explain_layout(layout, color, &mut primitives); primitives.push(primitive); @@ -487,11 +497,12 @@ fn explain_layout( color: Color, primitives: &mut Vec<Primitive>, ) { - // TODO: Draw borders instead primitives.push(Primitive::Quad { bounds: layout.bounds(), - background: Background::Color([0.0, 0.0, 0.0, 0.05].into()), + background: Background::Color(Color::TRANSPARENT), border_radius: 0, + border_width: 1, + border_color: [0.6, 0.6, 0.6, 0.5].into(), }); for child in layout.children() { diff --git a/wgpu/src/renderer/widget.rs b/wgpu/src/renderer/widget.rs index 32187c10..2c75413f 100644 --- a/wgpu/src/renderer/widget.rs +++ b/wgpu/src/renderer/widget.rs @@ -1,6 +1,7 @@ mod button; mod checkbox; mod column; +mod container; mod image; mod progress_bar; mod radio; diff --git a/wgpu/src/renderer/widget/button.rs b/wgpu/src/renderer/widget/button.rs index 86963053..a9209f64 100644 --- a/wgpu/src/renderer/widget/button.rs +++ b/wgpu/src/renderer/widget/button.rs @@ -1,54 +1,86 @@ -use crate::{Primitive, Renderer}; -use iced_native::{button, Background, MouseCursor, Point, Rectangle}; +use crate::{button::StyleSheet, defaults, Defaults, Primitive, Renderer}; +use iced_native::{ + Background, Color, Element, Layout, MouseCursor, Point, Rectangle, Vector, +}; -impl button::Renderer for Renderer { - fn draw( +impl iced_native::button::Renderer for Renderer { + type Style = Box<dyn StyleSheet>; + + fn draw<Message>( &mut self, + defaults: &Defaults, bounds: Rectangle, cursor_position: Point, + is_disabled: bool, is_pressed: bool, - background: Option<Background>, - border_radius: u16, - (content, _): Self::Output, + style: &Box<dyn StyleSheet>, + content: &Element<'_, Message, Self>, + content_layout: Layout<'_>, ) -> Self::Output { let is_mouse_over = bounds.contains(cursor_position); - // TODO: Render proper shadows - // TODO: Make hovering and pressed styles configurable - let shadow_offset = if is_mouse_over { + let styling = if is_disabled { + style.disabled() + } else if is_mouse_over { if is_pressed { - 0.0 + style.pressed() } else { - 2.0 + style.hovered() } } else { - 1.0 + style.active() }; + let (content, _) = content.draw( + self, + &Defaults { + text: defaults::Text { + color: styling.text_color, + }, + ..*defaults + }, + content_layout, + cursor_position, + ); + ( - match background { - None => content, - Some(background) => Primitive::Group { - primitives: vec![ - Primitive::Quad { - bounds: Rectangle { - x: bounds.x + 1.0, - y: bounds.y + shadow_offset, - ..bounds - }, - background: Background::Color( - [0.0, 0.0, 0.0, 0.5].into(), - ), - border_radius, - }, - Primitive::Quad { - bounds, - background, - border_radius, + if styling.background.is_some() || styling.border_width > 0 { + let background = Primitive::Quad { + bounds, + background: styling + .background + .unwrap_or(Background::Color(Color::TRANSPARENT)), + border_radius: styling.border_radius, + border_width: styling.border_width, + border_color: styling.border_color, + }; + + if styling.shadow_offset == Vector::default() { + Primitive::Group { + primitives: vec![background, content], + } + } else { + // TODO: Implement proper shadow support + let shadow = Primitive::Quad { + bounds: Rectangle { + x: bounds.x + styling.shadow_offset.x, + y: bounds.y + styling.shadow_offset.y, + ..bounds }, - content, - ], - }, + background: Background::Color( + [0.0, 0.0, 0.0, 0.5].into(), + ), + border_radius: styling.border_radius, + border_width: 0, + border_color: Color::TRANSPARENT, + }; + + Primitive::Group { + primitives: vec![shadow, background, content], + } + } + } else { + content }, if is_mouse_over { MouseCursor::Pointer diff --git a/wgpu/src/renderer/widget/checkbox.rs b/wgpu/src/renderer/widget/checkbox.rs index 54b4b1cc..17121eea 100644 --- a/wgpu/src/renderer/widget/checkbox.rs +++ b/wgpu/src/renderer/widget/checkbox.rs @@ -1,12 +1,13 @@ -use crate::{Primitive, Renderer}; +use crate::{checkbox::StyleSheet, Primitive, Renderer}; use iced_native::{ - checkbox, Background, HorizontalAlignment, MouseCursor, Rectangle, - VerticalAlignment, + checkbox, HorizontalAlignment, MouseCursor, Rectangle, VerticalAlignment, }; const SIZE: f32 = 28.0; impl checkbox::Renderer for Renderer { + type Style = Box<dyn StyleSheet>; + fn default_size(&self) -> u32 { SIZE as u32 } @@ -17,31 +18,21 @@ impl checkbox::Renderer for Renderer { is_checked: bool, is_mouse_over: bool, (label, _): Self::Output, + style_sheet: &Self::Style, ) -> Self::Output { - let (checkbox_border, checkbox_box) = ( - Primitive::Quad { - bounds, - background: Background::Color([0.6, 0.6, 0.6].into()), - border_radius: 6, - }, - Primitive::Quad { - bounds: Rectangle { - x: bounds.x + 1.0, - y: bounds.y + 1.0, - width: bounds.width - 2.0, - height: bounds.height - 2.0, - }, - background: Background::Color( - if is_mouse_over { - [0.90, 0.90, 0.90] - } else { - [0.95, 0.95, 0.95] - } - .into(), - ), - border_radius: 5, - }, - ); + let style = if is_mouse_over { + style_sheet.hovered(is_checked) + } else { + style_sheet.active(is_checked) + }; + + let checkbox = Primitive::Quad { + bounds, + background: style.background, + border_radius: style.border_radius, + border_width: style.border_width, + border_color: style.border_color, + }; ( Primitive::Group { @@ -51,14 +42,14 @@ impl checkbox::Renderer for Renderer { font: crate::text::BUILTIN_ICONS, size: bounds.height * 0.7, bounds: bounds, - color: [0.3, 0.3, 0.3].into(), + color: style.checkmark_color, horizontal_alignment: HorizontalAlignment::Center, vertical_alignment: VerticalAlignment::Center, }; - vec![checkbox_border, checkbox_box, check, label] + vec![checkbox, check, label] } else { - vec![checkbox_border, checkbox_box, label] + vec![checkbox, label] }, }, if is_mouse_over { diff --git a/wgpu/src/renderer/widget/column.rs b/wgpu/src/renderer/widget/column.rs index 6c31af90..95a7463a 100644 --- a/wgpu/src/renderer/widget/column.rs +++ b/wgpu/src/renderer/widget/column.rs @@ -4,6 +4,7 @@ use iced_native::{column, Element, Layout, MouseCursor, Point}; impl column::Renderer for Renderer { fn draw<Message>( &mut self, + defaults: &Self::Defaults, content: &[Element<'_, Message, Self>], layout: Layout<'_>, cursor_position: Point, @@ -17,7 +18,7 @@ impl column::Renderer for Renderer { .zip(layout.children()) .map(|(child, layout)| { let (primitive, new_mouse_cursor) = - child.draw(self, layout, cursor_position); + child.draw(self, defaults, layout, cursor_position); if new_mouse_cursor > mouse_cursor { mouse_cursor = new_mouse_cursor; diff --git a/wgpu/src/renderer/widget/container.rs b/wgpu/src/renderer/widget/container.rs new file mode 100644 index 00000000..2d4d1db8 --- /dev/null +++ b/wgpu/src/renderer/widget/container.rs @@ -0,0 +1,49 @@ +use crate::{container, defaults, Defaults, Primitive, Renderer}; +use iced_native::{Background, Color, Element, Layout, Point, Rectangle}; + +impl iced_native::container::Renderer for Renderer { + type Style = Box<dyn container::StyleSheet>; + + fn draw<Message>( + &mut self, + defaults: &Defaults, + bounds: Rectangle, + cursor_position: Point, + style_sheet: &Self::Style, + content: &Element<'_, Message, Self>, + content_layout: Layout<'_>, + ) -> Self::Output { + let style = style_sheet.style(); + + let defaults = Defaults { + text: defaults::Text { + color: style.text_color.unwrap_or(defaults.text.color), + }, + ..*defaults + }; + + let (content, mouse_cursor) = + content.draw(self, &defaults, content_layout, cursor_position); + + if style.background.is_some() || style.border_width > 0 { + let quad = Primitive::Quad { + bounds, + background: style + .background + .unwrap_or(Background::Color(Color::TRANSPARENT)), + border_radius: style.border_radius, + border_width: style.border_width, + border_color: style.border_color, + }; + + ( + Primitive::Group { + primitives: vec![quad, content], + }, + mouse_cursor, + ) + } else { + (content, mouse_cursor) + } + } +} diff --git a/wgpu/src/renderer/widget/progress_bar.rs b/wgpu/src/renderer/widget/progress_bar.rs index 8ed4bab7..34e33276 100644 --- a/wgpu/src/renderer/widget/progress_bar.rs +++ b/wgpu/src/renderer/widget/progress_bar.rs @@ -1,7 +1,9 @@ -use crate::{Primitive, Renderer}; -use iced_native::{progress_bar, Background, Color, MouseCursor, Rectangle}; +use crate::{progress_bar::StyleSheet, Primitive, Renderer}; +use iced_native::{progress_bar, Color, MouseCursor, Rectangle}; impl progress_bar::Renderer for Renderer { + type Style = Box<dyn StyleSheet>; + const DEFAULT_HEIGHT: u16 = 30; fn draw( @@ -9,9 +11,10 @@ impl progress_bar::Renderer for Renderer { bounds: Rectangle, range: std::ops::RangeInclusive<f32>, value: f32, - background: Option<Background>, - active_color: Option<Color>, + style_sheet: &Self::Style, ) -> Self::Output { + let style = style_sheet.style(); + let (range_start, range_end) = range.into_inner(); let active_progress_width = bounds.width * ((value - range_start) / (range_end - range_start).max(1.0)); @@ -19,27 +22,31 @@ impl progress_bar::Renderer for Renderer { let background = Primitive::Group { primitives: vec![Primitive::Quad { bounds: Rectangle { ..bounds }, - background: background - .unwrap_or(Background::Color([0.6, 0.6, 0.6].into())) - .into(), - border_radius: 5, + background: style.background, + border_radius: style.border_radius, + border_width: 0, + border_color: Color::TRANSPARENT, }], }; - let active_progress = Primitive::Quad { - bounds: Rectangle { - width: active_progress_width, - ..bounds - }, - background: Background::Color( - active_color.unwrap_or([0.0, 0.95, 0.0].into()), - ), - border_radius: 5, - }; - ( - Primitive::Group { - primitives: vec![background, active_progress], + if active_progress_width > 0.0 { + let bar = Primitive::Quad { + bounds: Rectangle { + width: active_progress_width, + ..bounds + }, + background: style.bar, + border_radius: style.border_radius, + border_width: 0, + border_color: Color::TRANSPARENT, + }; + + Primitive::Group { + primitives: vec![background, bar], + } + } else { + background }, MouseCursor::OutOfBounds, ) diff --git a/wgpu/src/renderer/widget/radio.rs b/wgpu/src/renderer/widget/radio.rs index 3c00a4c2..564f066b 100644 --- a/wgpu/src/renderer/widget/radio.rs +++ b/wgpu/src/renderer/widget/radio.rs @@ -1,10 +1,12 @@ -use crate::{Primitive, Renderer}; -use iced_native::{radio, Background, MouseCursor, Rectangle}; +use crate::{radio::StyleSheet, Primitive, Renderer}; +use iced_native::{radio, Background, Color, MouseCursor, Rectangle}; const SIZE: f32 = 28.0; const DOT_SIZE: f32 = SIZE / 2.0; impl radio::Renderer for Renderer { + type Style = Box<dyn StyleSheet>; + fn default_size(&self) -> u32 { SIZE as u32 } @@ -15,31 +17,21 @@ impl radio::Renderer for Renderer { is_selected: bool, is_mouse_over: bool, (label, _): Self::Output, + style_sheet: &Self::Style, ) -> Self::Output { - let (radio_border, radio_box) = ( - Primitive::Quad { - bounds, - background: Background::Color([0.6, 0.6, 0.6].into()), - border_radius: (SIZE / 2.0) as u16, - }, - Primitive::Quad { - bounds: Rectangle { - x: bounds.x + 1.0, - y: bounds.y + 1.0, - width: bounds.width - 2.0, - height: bounds.height - 2.0, - }, - background: Background::Color( - if is_mouse_over { - [0.90, 0.90, 0.90] - } else { - [0.95, 0.95, 0.95] - } - .into(), - ), - border_radius: (SIZE / 2.0 - 1.0) as u16, - }, - ); + let style = if is_mouse_over { + style_sheet.hovered() + } else { + style_sheet.active() + }; + + let radio = Primitive::Quad { + bounds, + background: style.background, + border_radius: (SIZE / 2.0) as u16, + border_width: style.border_width, + border_color: style.border_color, + }; ( Primitive::Group { @@ -51,13 +43,15 @@ impl radio::Renderer for Renderer { width: bounds.width - DOT_SIZE, height: bounds.height - DOT_SIZE, }, - background: Background::Color([0.3, 0.3, 0.3].into()), + background: Background::Color(style.dot_color), border_radius: (DOT_SIZE / 2.0) as u16, + border_width: 0, + border_color: Color::TRANSPARENT, }; - vec![radio_border, radio_box, radio_circle, label] + vec![radio, radio_circle, label] } else { - vec![radio_border, radio_box, label] + vec![radio, label] }, }, if is_mouse_over { diff --git a/wgpu/src/renderer/widget/row.rs b/wgpu/src/renderer/widget/row.rs index f082dc61..bd9f1a04 100644 --- a/wgpu/src/renderer/widget/row.rs +++ b/wgpu/src/renderer/widget/row.rs @@ -4,6 +4,7 @@ use iced_native::{row, Element, Layout, MouseCursor, Point}; impl row::Renderer for Renderer { fn draw<Message>( &mut self, + defaults: &Self::Defaults, children: &[Element<'_, Message, Self>], layout: Layout<'_>, cursor_position: Point, @@ -17,7 +18,7 @@ impl row::Renderer for Renderer { .zip(layout.children()) .map(|(child, layout)| { let (primitive, new_mouse_cursor) = - child.draw(self, layout, cursor_position); + child.draw(self, defaults, layout, cursor_position); if new_mouse_cursor > mouse_cursor { mouse_cursor = new_mouse_cursor; diff --git a/wgpu/src/renderer/widget/scrollable.rs b/wgpu/src/renderer/widget/scrollable.rs index 6ef57185..bfee7411 100644 --- a/wgpu/src/renderer/widget/scrollable.rs +++ b/wgpu/src/renderer/widget/scrollable.rs @@ -1,10 +1,14 @@ use crate::{Primitive, Renderer}; -use iced_native::{scrollable, Background, MouseCursor, Rectangle, Vector}; +use iced_native::{ + scrollable, Background, Color, MouseCursor, Rectangle, Vector, +}; const SCROLLBAR_WIDTH: u16 = 10; const SCROLLBAR_MARGIN: u16 = 2; impl scrollable::Renderer for Renderer { + type Style = Box<dyn iced_style::scrollable::StyleSheet>; + fn scrollbar( &self, bounds: Rectangle, @@ -51,6 +55,7 @@ impl scrollable::Renderer for Renderer { is_mouse_over_scrollbar: bool, scrollbar: Option<scrollable::Scrollbar>, offset: u32, + style_sheet: &Self::Style, (content, mouse_cursor): Self::Output, ) -> Self::Output { let clip = Primitive::Clip { @@ -61,40 +66,53 @@ impl scrollable::Renderer for Renderer { ( if let Some(scrollbar) = scrollbar { - if is_mouse_over || state.is_scroller_grabbed() { - let scroller = Primitive::Quad { - bounds: scrollbar.scroller.bounds, - background: Background::Color( - [0.0, 0.0, 0.0, 0.7].into(), - ), - border_radius: 5, - }; + let style = if state.is_scroller_grabbed() { + style_sheet.dragging() + } else if is_mouse_over_scrollbar { + style_sheet.hovered() + } else { + style_sheet.active() + }; + + let is_scrollbar_visible = + style.background.is_some() || style.border_width > 0; - if is_mouse_over_scrollbar || state.is_scroller_grabbed() { - let scrollbar = Primitive::Quad { - bounds: Rectangle { - x: scrollbar.bounds.x - + f32::from(SCROLLBAR_MARGIN), - width: scrollbar.bounds.width - - f32::from(2 * SCROLLBAR_MARGIN), - ..scrollbar.bounds - }, - background: Background::Color( - [0.0, 0.0, 0.0, 0.3].into(), - ), - border_radius: 5, - }; + let scroller = if is_mouse_over + || state.is_scroller_grabbed() + || is_scrollbar_visible + { + Primitive::Quad { + bounds: scrollbar.scroller.bounds, + background: Background::Color(style.scroller.color), + border_radius: style.scroller.border_radius, + border_width: style.scroller.border_width, + border_color: style.scroller.border_color, + } + } else { + Primitive::None + }; - Primitive::Group { - primitives: vec![clip, scrollbar, scroller], - } - } else { - Primitive::Group { - primitives: vec![clip, scroller], - } + let scrollbar = if is_scrollbar_visible { + Primitive::Quad { + bounds: Rectangle { + x: scrollbar.bounds.x + f32::from(SCROLLBAR_MARGIN), + width: scrollbar.bounds.width + - f32::from(2 * SCROLLBAR_MARGIN), + ..scrollbar.bounds + }, + background: style + .background + .unwrap_or(Background::Color(Color::TRANSPARENT)), + border_radius: style.border_radius, + border_width: style.border_width, + border_color: style.border_color, } } else { - clip + Primitive::None + }; + + Primitive::Group { + primitives: vec![clip, scrollbar, scroller], } } else { clip diff --git a/wgpu/src/renderer/widget/slider.rs b/wgpu/src/renderer/widget/slider.rs index c73a4e56..c8ebd0da 100644 --- a/wgpu/src/renderer/widget/slider.rs +++ b/wgpu/src/renderer/widget/slider.rs @@ -1,10 +1,14 @@ -use crate::{Primitive, Renderer}; +use crate::{ + slider::{HandleShape, StyleSheet}, + Primitive, Renderer, +}; use iced_native::{slider, Background, Color, MouseCursor, Point, Rectangle}; -const HANDLE_WIDTH: f32 = 8.0; const HANDLE_HEIGHT: f32 = 22.0; impl slider::Renderer for Renderer { + type Style = Box<dyn StyleSheet>; + fn height(&self) -> u32 { 30 } @@ -16,9 +20,18 @@ impl slider::Renderer for Renderer { range: std::ops::RangeInclusive<f32>, value: f32, is_dragging: bool, + style_sheet: &Self::Style, ) -> Self::Output { let is_mouse_over = bounds.contains(cursor_position); + let style = if is_dragging { + style_sheet.dragging() + } else if is_mouse_over { + style_sheet.hovered() + } else { + style_sheet.active() + }; + let rail_y = bounds.y + (bounds.height / 2.0).round(); let (rail_top, rail_bottom) = ( @@ -29,8 +42,10 @@ impl slider::Renderer for Renderer { width: bounds.width, height: 2.0, }, - background: Color::from_rgb(0.6, 0.6, 0.6).into(), + background: Background::Color(style.rail_colors.0), border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, }, Primitive::Quad { bounds: Rectangle { @@ -39,51 +54,45 @@ impl slider::Renderer for Renderer { width: bounds.width, height: 2.0, }, - background: Background::Color(Color::WHITE), + background: Background::Color(style.rail_colors.1), border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, }, ); let (range_start, range_end) = range.into_inner(); - let handle_offset = (bounds.width - HANDLE_WIDTH) + let (handle_width, handle_height, handle_border_radius) = + match style.handle.shape { + HandleShape::Circle { radius } => { + (f32::from(radius * 2), f32::from(radius * 2), radius) + } + HandleShape::Rectangle { + width, + border_radius, + } => (f32::from(width), HANDLE_HEIGHT, border_radius), + }; + + let handle_offset = (bounds.width - handle_width) * ((value - range_start) / (range_end - range_start).max(1.0)); - let (handle_border, handle) = ( - Primitive::Quad { - bounds: Rectangle { - x: bounds.x + handle_offset.round() - 1.0, - y: rail_y - HANDLE_HEIGHT / 2.0 - 1.0, - width: HANDLE_WIDTH + 2.0, - height: HANDLE_HEIGHT + 2.0, - }, - background: Color::from_rgb(0.6, 0.6, 0.6).into(), - border_radius: 5, + let handle = Primitive::Quad { + bounds: Rectangle { + x: bounds.x + handle_offset.round(), + y: rail_y - handle_height / 2.0, + width: handle_width, + height: handle_height, }, - Primitive::Quad { - bounds: Rectangle { - x: bounds.x + handle_offset.round(), - y: rail_y - HANDLE_HEIGHT / 2.0, - width: HANDLE_WIDTH, - height: HANDLE_HEIGHT, - }, - background: Background::Color( - if is_dragging { - [0.85, 0.85, 0.85] - } else if is_mouse_over { - [0.90, 0.90, 0.90] - } else { - [0.95, 0.95, 0.95] - } - .into(), - ), - border_radius: 4, - }, - ); + background: Background::Color(style.handle.color), + border_radius: handle_border_radius, + border_width: style.handle.border_width, + border_color: style.handle.border_color, + }; ( Primitive::Group { - primitives: vec![rail_top, rail_bottom, handle_border, handle], + primitives: vec![rail_top, rail_bottom, handle], }, if is_dragging { MouseCursor::Grabbing diff --git a/wgpu/src/renderer/widget/text.rs b/wgpu/src/renderer/widget/text.rs index 08a162ba..d61c5523 100644 --- a/wgpu/src/renderer/widget/text.rs +++ b/wgpu/src/renderer/widget/text.rs @@ -27,6 +27,7 @@ impl text::Renderer for Renderer { fn draw( &mut self, + defaults: &Self::Defaults, bounds: Rectangle, content: &str, size: u16, @@ -40,7 +41,7 @@ impl text::Renderer for Renderer { content: content.to_string(), size: f32::from(size), bounds, - color: color.unwrap_or(Color::BLACK), + color: color.unwrap_or(defaults.text.color), font, horizontal_alignment, vertical_alignment, diff --git a/wgpu/src/renderer/widget/text_input.rs b/wgpu/src/renderer/widget/text_input.rs index 929f94db..8b774a48 100644 --- a/wgpu/src/renderer/widget/text_input.rs +++ b/wgpu/src/renderer/widget/text_input.rs @@ -1,4 +1,4 @@ -use crate::{Primitive, Renderer}; +use crate::{text_input::StyleSheet, Primitive, Renderer}; use iced_native::{ text_input, Background, Color, Font, HorizontalAlignment, MouseCursor, @@ -7,6 +7,8 @@ use iced_native::{ use std::f32; impl text_input::Renderer for Renderer { + type Style = Box<dyn StyleSheet>; + fn default_size(&self) -> u16 { // TODO: Make this configurable 20 @@ -61,31 +63,24 @@ impl text_input::Renderer for Renderer { placeholder: &str, value: &text_input::Value, state: &text_input::State, + style_sheet: &Self::Style, ) -> Self::Output { let is_mouse_over = bounds.contains(cursor_position); - let border = Primitive::Quad { - bounds, - background: Background::Color( - if is_mouse_over || state.is_focused() { - [0.5, 0.5, 0.5] - } else { - [0.7, 0.7, 0.7] - } - .into(), - ), - border_radius: 5, + let style = if state.is_focused() { + style_sheet.focused() + } else if is_mouse_over { + style_sheet.hovered() + } else { + style_sheet.active() }; let input = Primitive::Quad { - bounds: Rectangle { - x: bounds.x + 1.0, - y: bounds.y + 1.0, - width: bounds.width - 2.0, - height: bounds.height - 2.0, - }, - background: Background::Color(Color::WHITE), - border_radius: 4, + bounds, + background: style.background, + border_radius: style.border_radius, + border_width: style.border_width, + border_color: style.border_color, }; let text = value.to_string(); @@ -97,9 +92,9 @@ impl text_input::Renderer for Renderer { text.clone() }, color: if text.is_empty() { - [0.7, 0.7, 0.7] + style_sheet.placeholder_color() } else { - [0.3, 0.3, 0.3] + style_sheet.value_color() } .into(), font: Font::Default, @@ -128,8 +123,10 @@ impl text_input::Renderer for Renderer { width: 1.0, height: text_bounds.height, }, - background: Background::Color(Color::BLACK), + background: Background::Color(style_sheet.value_color()), border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, }; ( @@ -150,7 +147,7 @@ impl text_input::Renderer for Renderer { ( Primitive::Group { - primitives: vec![border, input, contents], + primitives: vec![input, contents], }, if is_mouse_over { MouseCursor::Text diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs new file mode 100644 index 00000000..dbe81830 --- /dev/null +++ b/wgpu/src/settings.rs @@ -0,0 +1,10 @@ +/// The settings of a [`Renderer`]. +/// +/// [`Renderer`]: struct.Renderer.html +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Settings { + /// The bytes of the font that will be used by default. + /// + /// If `None` is provided, a default system font will be chosen. + pub default_font: Option<&'static [u8]>, +} diff --git a/wgpu/src/shader/quad.frag b/wgpu/src/shader/quad.frag index 2ee77e71..ad1af1ad 100644 --- a/wgpu/src/shader/quad.frag +++ b/wgpu/src/shader/quad.frag @@ -1,14 +1,17 @@ #version 450 layout(location = 0) in vec4 v_Color; -layout(location = 1) in vec2 v_Pos; -layout(location = 2) in vec2 v_Scale; -layout(location = 3) in float v_BorderRadius; +layout(location = 1) in vec4 v_BorderColor; +layout(location = 2) in vec2 v_Pos; +layout(location = 3) in vec2 v_Scale; +layout(location = 4) in float v_BorderRadius; +layout(location = 5) in float v_BorderWidth; layout(location = 0) out vec4 o_Color; -float rounded(in vec2 frag_coord, in vec2 position, in vec2 size, float radius, float s) +float distance(in vec2 frag_coord, in vec2 position, in vec2 size, float radius) { + // TODO: Try SDF approach: https://www.shadertoy.com/view/wd3XRN vec2 inner_size = size - vec2(radius, radius) * 2.0; vec2 top_left = position + vec2(radius, radius); vec2 bottom_right = top_left + inner_size; @@ -21,13 +24,43 @@ float rounded(in vec2 frag_coord, in vec2 position, in vec2 size, float radius, max(max(top_left_distance.y, bottom_right_distance.y), 0) ); - float d = sqrt(distance.x * distance.x + distance.y * distance.y); - - return 1.0 - smoothstep(radius - s, radius + s, d); + return sqrt(distance.x * distance.x + distance.y * distance.y); } void main() { - float radius_alpha = rounded(gl_FragCoord.xy, v_Pos, v_Scale, v_BorderRadius, 0.5); + vec4 mixed_color; + + // TODO: Remove branching (?) + if(v_BorderWidth > 0) { + float internal_border = max(v_BorderRadius - v_BorderWidth, 0); + + float internal_distance = distance( + gl_FragCoord.xy, + v_Pos + vec2(v_BorderWidth), + v_Scale - vec2(v_BorderWidth * 2.0), + internal_border + ); + + float border_mix = smoothstep( + max(internal_border - 0.5, 0.0), + internal_border + 0.5, + internal_distance + ); + + mixed_color = mix(v_Color, v_BorderColor, border_mix); + } else { + mixed_color = v_Color; + } + + float d = distance( + gl_FragCoord.xy, + v_Pos, + v_Scale, + v_BorderRadius + ); + + float radius_alpha = + 1.0 - smoothstep(max(v_BorderRadius - 0.5, 0), v_BorderRadius + 0.5, d); - o_Color = vec4(v_Color.xyz, v_Color.w * radius_alpha); + o_Color = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); } diff --git a/wgpu/src/shader/quad.frag.spv b/wgpu/src/shader/quad.frag.spv Binary files differindex 17bd8f46..519f5f01 100644 --- a/wgpu/src/shader/quad.frag.spv +++ b/wgpu/src/shader/quad.frag.spv diff --git a/wgpu/src/shader/quad.vert b/wgpu/src/shader/quad.vert index 539755cb..1d9a4fd2 100644 --- a/wgpu/src/shader/quad.vert +++ b/wgpu/src/shader/quad.vert @@ -4,7 +4,9 @@ layout(location = 0) in vec2 v_Pos; layout(location = 1) in vec2 i_Pos; layout(location = 2) in vec2 i_Scale; layout(location = 3) in vec4 i_Color; -layout(location = 4) in float i_BorderRadius; +layout(location = 4) in vec4 i_BorderColor; +layout(location = 5) in float i_BorderRadius; +layout(location = 6) in float i_BorderWidth; layout (set = 0, binding = 0) uniform Globals { mat4 u_Transform; @@ -12,9 +14,11 @@ layout (set = 0, binding = 0) uniform Globals { }; layout(location = 0) out vec4 o_Color; -layout(location = 1) out vec2 o_Pos; -layout(location = 2) out vec2 o_Scale; -layout(location = 3) out float o_BorderRadius; +layout(location = 1) out vec4 o_BorderColor; +layout(location = 2) out vec2 o_Pos; +layout(location = 3) out vec2 o_Scale; +layout(location = 4) out float o_BorderRadius; +layout(location = 5) out float o_BorderWidth; void main() { vec2 p_Pos = i_Pos * u_Scale; @@ -28,9 +32,11 @@ void main() { ); o_Color = i_Color; + o_BorderColor = i_BorderColor; o_Pos = p_Pos; o_Scale = p_Scale; o_BorderRadius = i_BorderRadius * u_Scale; + o_BorderWidth = i_BorderWidth * u_Scale; gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0); } diff --git a/wgpu/src/shader/quad.vert.spv b/wgpu/src/shader/quad.vert.spv Binary files differindex 9050adfb..7059b51b 100644 --- a/wgpu/src/shader/quad.vert.spv +++ b/wgpu/src/shader/quad.vert.spv diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 880ad1a6..ab9a2f71 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -22,13 +22,16 @@ pub struct Pipeline { } impl Pipeline { - pub fn new(device: &mut wgpu::Device) -> Self { + pub fn new(device: &mut wgpu::Device, default_font: Option<&[u8]>) -> Self { // TODO: Font customization let font_source = font::Source::new(); - let default_font = font_source - .load(&[font::Family::SansSerif, font::Family::Serif]) - .unwrap_or_else(|_| FALLBACK_FONT.to_vec()); + let default_font = + default_font.map(|slice| slice.to_vec()).unwrap_or_else(|| { + font_source + .load(&[font::Family::SansSerif, font::Family::Serif]) + .unwrap_or_else(|_| FALLBACK_FONT.to_vec()) + }); let load_glyph_brush = |font: Vec<u8>| { let builder = diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs new file mode 100644 index 00000000..e3edda0b --- /dev/null +++ b/wgpu/src/widget.rs @@ -0,0 +1,34 @@ +//! Use the widgets supported out-of-the-box. +//! +//! # Re-exports +//! For convenience, the contents of this module are available at the root +//! module. Therefore, you can directly type: +//! +//! ``` +//! use iced_wgpu::{button, Button}; +//! ``` +pub mod button; +pub mod checkbox; +pub mod container; +pub mod progress_bar; +pub mod radio; +pub mod scrollable; +pub mod slider; +pub mod text_input; + +#[doc(no_inline)] +pub use button::Button; +#[doc(no_inline)] +pub use checkbox::Checkbox; +#[doc(no_inline)] +pub use container::Container; +#[doc(no_inline)] +pub use progress_bar::ProgressBar; +#[doc(no_inline)] +pub use radio::Radio; +#[doc(no_inline)] +pub use scrollable::Scrollable; +#[doc(no_inline)] +pub use slider::Slider; +#[doc(no_inline)] +pub use text_input::TextInput; diff --git a/wgpu/src/widget/button.rs b/wgpu/src/widget/button.rs new file mode 100644 index 00000000..b738c55e --- /dev/null +++ b/wgpu/src/widget/button.rs @@ -0,0 +1,15 @@ +//! Allow your users to perform actions by pressing a button. +//! +//! A [`Button`] has some local [`State`]. +//! +//! [`Button`]: type.Button.html +//! [`State`]: struct.State.html +use crate::Renderer; + +pub use iced_native::button::State; +pub use iced_style::button::{Style, StyleSheet}; + +/// A widget that produces a message when clicked. +/// +/// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`. +pub type Button<'a, Message> = iced_native::Button<'a, Message, Renderer>; diff --git a/wgpu/src/widget/checkbox.rs b/wgpu/src/widget/checkbox.rs new file mode 100644 index 00000000..da0d7a84 --- /dev/null +++ b/wgpu/src/widget/checkbox.rs @@ -0,0 +1,9 @@ +//! Show toggle controls using checkboxes. +use crate::Renderer; + +pub use iced_style::checkbox::{Style, StyleSheet}; + +/// A box that can be checked. +/// +/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`. +pub type Checkbox<Message> = iced_native::Checkbox<Message, Renderer>; diff --git a/wgpu/src/widget/container.rs b/wgpu/src/widget/container.rs new file mode 100644 index 00000000..9a93a246 --- /dev/null +++ b/wgpu/src/widget/container.rs @@ -0,0 +1,10 @@ +//! Decorate content and apply alignment. +use crate::Renderer; + +pub use iced_style::container::{Style, StyleSheet}; + +/// An element decorating some content. +/// +/// This is an alias of an `iced_native` container with a default +/// `Renderer`. +pub type Container<'a, Message> = iced_native::Container<'a, Message, Renderer>; diff --git a/wgpu/src/widget/progress_bar.rs b/wgpu/src/widget/progress_bar.rs new file mode 100644 index 00000000..34450b5e --- /dev/null +++ b/wgpu/src/widget/progress_bar.rs @@ -0,0 +1,15 @@ +//! Allow your users to perform actions by pressing a button. +//! +//! A [`Button`] has some local [`State`]. +//! +//! [`Button`]: type.Button.html +//! [`State`]: struct.State.html +use crate::Renderer; + +pub use iced_style::progress_bar::{Style, StyleSheet}; + +/// A bar that displays progress. +/// +/// This is an alias of an `iced_native` progress bar with an +/// `iced_wgpu::Renderer`. +pub type ProgressBar = iced_native::ProgressBar<Renderer>; diff --git a/wgpu/src/widget/radio.rs b/wgpu/src/widget/radio.rs new file mode 100644 index 00000000..6e5cf042 --- /dev/null +++ b/wgpu/src/widget/radio.rs @@ -0,0 +1,10 @@ +//! Create choices using radio buttons. +use crate::Renderer; + +pub use iced_style::radio::{Style, StyleSheet}; + +/// A circular button representing a choice. +/// +/// This is an alias of an `iced_native` radio button with an +/// `iced_wgpu::Renderer`. +pub type Radio<Message> = iced_native::Radio<Message, Renderer>; diff --git a/wgpu/src/widget/scrollable.rs b/wgpu/src/widget/scrollable.rs new file mode 100644 index 00000000..1d236105 --- /dev/null +++ b/wgpu/src/widget/scrollable.rs @@ -0,0 +1,13 @@ +//! Navigate an endless amount of content with a scrollbar. +use crate::Renderer; + +pub use iced_native::scrollable::State; +pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; + +/// A widget that can vertically display an infinite amount of content +/// with a scrollbar. +/// +/// This is an alias of an `iced_native` scrollable with a default +/// `Renderer`. +pub type Scrollable<'a, Message> = + iced_native::Scrollable<'a, Message, Renderer>; diff --git a/wgpu/src/widget/slider.rs b/wgpu/src/widget/slider.rs new file mode 100644 index 00000000..4e47978f --- /dev/null +++ b/wgpu/src/widget/slider.rs @@ -0,0 +1,16 @@ +//! Display an interactive selector of a single value from a range of values. +//! +//! A [`Slider`] has some local [`State`]. +//! +//! [`Slider`]: struct.Slider.html +//! [`State`]: struct.State.html +use crate::Renderer; + +pub use iced_native::slider::State; +pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet}; + +/// An horizontal bar and a handle that selects a single value from a range of +/// values. +/// +/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`. +pub type Slider<'a, Message> = iced_native::Slider<'a, Message, Renderer>; diff --git a/wgpu/src/widget/text_input.rs b/wgpu/src/widget/text_input.rs new file mode 100644 index 00000000..260fe3a6 --- /dev/null +++ b/wgpu/src/widget/text_input.rs @@ -0,0 +1,15 @@ +//! Display fields that can be filled with text. +//! +//! A [`TextInput`] has some local [`State`]. +//! +//! [`TextInput`]: struct.TextInput.html +//! [`State`]: struct.State.html +use crate::Renderer; + +pub use iced_native::text_input::State; +pub use iced_style::text_input::{Style, StyleSheet}; + +/// A field that can be filled with text. +/// +/// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`. +pub type TextInput<'a, Message> = iced_native::TextInput<'a, Message, Renderer>; diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 60e3f2d0..5727f8cf 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -6,6 +6,9 @@ edition = "2018" description = "A winit runtime for Iced" license = "MIT" repository = "https://github.com/hecrj/iced" +documentation = "https://docs.rs/iced_winit" +keywords = ["gui", "ui", "graphics", "interface", "widgets"] +categories = ["gui"] [features] debug = [] diff --git a/winit/src/application.rs b/winit/src/application.rs index a8612b1a..da943660 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,5 +1,5 @@ use crate::{ - conversion, + container, conversion, input::{keyboard, mouse}, renderer::{Target, Windowed}, subscription, Cache, Clipboard, Command, Container, Debug, Element, Event, @@ -18,7 +18,7 @@ pub trait Application: Sized { /// The renderer to use to draw the [`Application`]. /// /// [`Application`]: trait.Application.html - type Renderer: Windowed; + type Renderer: Windowed + container::Renderer; /// The type of __messages__ your [`Application`] will produce. /// @@ -81,8 +81,10 @@ pub trait Application: Sized { /// It should probably be that last thing you call in your `main` function. /// /// [`Application`]: trait.Application.html - fn run(settings: Settings) - where + fn run( + settings: Settings, + renderer_settings: <Self::Renderer as Windowed>::Settings, + ) where Self: 'static, { use winit::{ @@ -140,7 +142,7 @@ pub trait Application: Sized { let mut resized = false; let clipboard = Clipboard::new(&window); - let mut renderer = Self::Renderer::new(); + let mut renderer = Self::Renderer::new(renderer_settings); let mut target = { let (width, height) = to_physical(size, dpi); diff --git a/winit/src/settings/mod.rs b/winit/src/settings/mod.rs index 58e3d879..b2290b46 100644 --- a/winit/src/settings/mod.rs +++ b/winit/src/settings/mod.rs @@ -1,5 +1,4 @@ //! Configure your application. - #[cfg(target_os = "windows")] #[path = "windows.rs"] mod platform; @@ -10,7 +9,7 @@ mod platform; pub use platform::PlatformSpecific; /// The settings of an application. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Settings { /// The [`Window`] settings /// @@ -18,6 +17,14 @@ pub struct Settings { pub window: Window, } +impl Default for Settings { + fn default() -> Settings { + Settings { + window: Window::default(), + } + } +} + /// The window settings of an application. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Window { |