diff options
72 files changed, 1077 insertions, 432 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..d72651d9 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -25,6 +25,14 @@ 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 RGB8 components. /// /// [`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 cf2f7792..1f51ee54 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 2d595ec4..2a696ffe 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() { @@ -219,8 +219,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..901428de 100644 --- a/examples/progress_bar.rs +++ b/examples/progress_bar.rs @@ -1,16 +1,10 @@ use iced::{ - settings::Window, slider, Background, Color, Column, Element, Length, - ProgressBar, Sandbox, Settings, Slider, + slider, Background, Color, Column, Element, Length, 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)] diff --git a/examples/stopwatch.rs b/examples/stopwatch.rs index 7a7f0793..9b69f7ca 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}; @@ -99,30 +98,29 @@ impl Application for Stopwatch { .width(Length::Shrink) .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() .width(Length::Shrink) @@ -180,3 +178,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/todos.rs b/examples/todos.rs index 42e88f65..1563aad5 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}; @@ -291,12 +291,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() } @@ -320,19 +318,13 @@ impl Task { .push( Button::new( delete_button, - Row::new() - .spacing(10) - .push(delete_icon().color(Color::WHITE)) - .push( - Text::new("Delete") - .width(Length::Shrink) - .color(Color::WHITE), - ), + Row::new().spacing(10).push(delete_icon()).push( + Text::new("Delete").width(Length::Shrink), + ), ) .on_press(TaskMessage::Delete) .padding(10) - .border_radius(5) - .background(Color::from_rgb(0.8, 0.2, 0.2)), + .style(style::Button::Destructive), ) .into() } @@ -359,17 +351,12 @@ impl Controls { let filter_button = |state, label, filter, current_filter| { let label = Text::new(label).size(16).width(Length::Shrink); - 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() @@ -562,3 +549,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 91b75296..84e5d516 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), ); } @@ -692,29 +694,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, @@ -757,6 +742,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/lib.rs b/native/src/lib.rs index 8dcacb2b..9d237196 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -34,7 +34,7 @@ //! [`Windowed`]: renderer/trait.Windowed.html //! [`UserInterface`]: struct.UserInterface.html //! [renderer]: renderer/index.html -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![deny(unsafe_code)] diff --git a/native/src/renderer.rs b/native/src/renderer.rs index 7a68ada4..90cec6c8 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,8 @@ pub trait Renderer: Sized { /// [`Renderer`]: trait.Renderer.html type Output; + 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..96aa132c 100644 --- a/native/src/renderer/null.rs +++ b/native/src/renderer/null.rs @@ -1,20 +1,30 @@ use crate::{ - button, checkbox, column, radio, row, scrollable, text, text_input, - Background, Color, Element, Font, HorizontalAlignment, Layout, Point, - Rectangle, Renderer, Size, VerticalAlignment, + button, checkbox, column, radio, row, scrollable, 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 { + 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 +35,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 +60,7 @@ impl text::Renderer for Null { fn draw( &mut self, + _defaults: &Self::Defaults, _bounds: Rectangle, _content: &str, _size: u16, @@ -85,6 +97,8 @@ impl scrollable::Renderer for Null { } impl text_input::Renderer for Null { + type Style = (); + fn default_size(&self) -> u16 { 20 } @@ -112,19 +126,24 @@ 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 { } } diff --git a/native/src/renderer/windowed.rs b/native/src/renderer/windowed.rs index 813a03f2..c3266e6c 100644 --- a/native/src/renderer/windowed.rs +++ b/native/src/renderer/windowed.rs @@ -4,13 +4,15 @@ use raw_window_handle::HasRawWindowHandle; /// A renderer that can target windows. pub trait Windowed: super::Renderer + Sized { + 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..75ef2693 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,23 +106,6 @@ 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`]. - /// - /// [`Button`]: struct.Button.html - pub fn border_radius(mut self, border_radius: u16) -> Self { - self.border_radius = border_radius; - self - } - /// Sets the message that will be produced when the [`Button`] is pressed. /// /// [`Button`]: struct.Button.html @@ -129,6 +113,11 @@ impl<'a, Message, Renderer> Button<'a, Message, Renderer> { self.on_press = Some(msg); self } + + pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { + self.style = style.into(); + self + } } /// The local state of a [`Button`]. @@ -227,22 +216,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 +246,21 @@ where /// [`Button`]: struct.Button.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer + Sized { + 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 0dcac712..87a7f629 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -134,6 +134,7 @@ where fn draw( &self, renderer: &mut Renderer, + defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { @@ -146,6 +147,7 @@ where let label = text::Renderer::draw( renderer, + defaults, label_layout.bounds(), &self.label, text::Renderer::default_size(renderer), diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 4b5d631c..3418d4b0 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..abe83264 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,27 @@ where } } +pub trait Renderer: crate::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..fe517404 100644 --- a/native/src/widget/progress_bar.rs +++ b/native/src/widget/progress_bar.rs @@ -111,6 +111,7 @@ where fn draw( &self, renderer: &mut Renderer, + _defaults: &Renderer::Defaults, layout: Layout<'_>, _cursor_position: Point, ) -> Renderer::Output { diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index a9995b86..6ac00770 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -131,6 +131,7 @@ where fn draw( &self, renderer: &mut Renderer, + defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { @@ -143,6 +144,7 @@ where let label = text::Renderer::draw( renderer, + defaults, label_layout.bounds(), &self.label, text::Renderer::default_size(renderer), diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 3de65deb..76cca3d0 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..9df09b14 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -255,6 +255,7 @@ where fn draw( &self, renderer: &mut Renderer, + defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { @@ -277,7 +278,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( diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index f446f7e8..ea66a347 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -178,6 +178,7 @@ where fn draw( &self, renderer: &mut Renderer, + _defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { 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 9580f195..f6202f72 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 cf9c9565..3a3db3cd 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 c994b7ba..ee8e5ade 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: @@ -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,8 @@ where /// [`TextInput`]: struct.TextInput.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer + Sized { + type Style: Default; + /// Returns the default size of the text of the [`TextInput`]. /// /// [`TextInput`]: struct.TextInput.html @@ -440,17 +457,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(); @@ -174,7 +174,7 @@ //! [documentation]: https://docs.rs/iced //! [examples]: https://github.com/hecrj/iced/tree/master/examples //! [`Application`]: trait.Application.html -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![deny(unsafe_code)] diff --git a/src/native.rs b/src/native.rs index 022cf337..67f85c20 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,23 +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 use iced_wgpu::widget::*; pub mod scrollable { //! Navigate an endless amount of content with a scrollbar. @@ -54,16 +38,6 @@ pub mod widget { 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. @@ -89,8 +63,9 @@ pub mod widget { #[doc(no_inline)] pub use { - button::Button, image::Image, scrollable::Scrollable, slider::Slider, - svg::Svg, text_input::TextInput, + button::Button, container::Container, image::Image, + scrollable::Scrollable, slider::Slider, svg::Svg, + text_input::TextInput, }; /// A container that distributes its contents vertically. @@ -104,13 +79,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..b725ef9f 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,7 +1,7 @@ //! Configure your application. /// The settings of an application. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Settings { /// The [`Window`] settings. /// @@ -9,6 +9,18 @@ pub struct Settings { /// /// [`Window`]: struct.Window.html pub window: Window, + + // TODO: Add `name` for web compatibility + pub default_font: Option<&'static [u8]>, +} + +impl Default for Settings { + fn default() -> Settings { + Settings { + window: Window::default(), + default_font: None, + } + } } /// 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..03c9ab32 --- /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::BLACK, + } + } +} + +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..1d7e57c4 --- /dev/null +++ b/style/src/lib.rs @@ -0,0 +1,3 @@ +pub mod button; +pub mod container; +pub mod text_input; diff --git a/style/src/text_input.rs b/style/src/text_input.rs new file mode 100644 index 00000000..269b28db --- /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; + + /// Produces the style of an hovered text input. + fn hovered(&self) -> Style { + self.focused() + } + + fn placeholder_color(&self) -> Color; + + fn value_color(&self) -> Color; +} + +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..8de8258b --- /dev/null +++ b/wgpu/src/defaults.rs @@ -0,0 +1,27 @@ +use iced_native::Color; + +#[derive(Debug, Clone, Copy)] +pub struct Defaults { + pub text: Text, +} + +impl Default for Defaults { + fn default() -> Defaults { + Defaults { + text: Text::default(), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct 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..dda4f322 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -19,23 +19,30 @@ //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs //! [WebGPU API]: https://gpuweb.github.io/gpuweb/ //! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![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..eb5d7c09 100644 --- a/wgpu/src/renderer/widget/button.rs +++ b/wgpu/src/renderer/widget/button.rs @@ -1,54 +1,87 @@ -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..1ed27ff7 100644 --- a/wgpu/src/renderer/widget/checkbox.rs +++ b/wgpu/src/renderer/widget/checkbox.rs @@ -1,6 +1,6 @@ use crate::{Primitive, Renderer}; use iced_native::{ - checkbox, Background, HorizontalAlignment, MouseCursor, Rectangle, + checkbox, Background, Color, HorizontalAlignment, MouseCursor, Rectangle, VerticalAlignment, }; @@ -18,30 +18,20 @@ impl checkbox::Renderer for Renderer { is_mouse_over: bool, (label, _): Self::Output, ) -> 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 checkbox = Primitive::Quad { + bounds, + background: Background::Color( + if is_mouse_over { + [0.90, 0.90, 0.90] + } else { + [0.95, 0.95, 0.95] + } + .into(), + ), + border_radius: 5, + border_width: 1, + border_color: Color::from_rgb(0.6, 0.6, 0.6), + }; ( Primitive::Group { @@ -56,9 +46,9 @@ impl checkbox::Renderer for Renderer { 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..e9346fda 100644 --- a/wgpu/src/renderer/widget/progress_bar.rs +++ b/wgpu/src/renderer/widget/progress_bar.rs @@ -23,6 +23,8 @@ impl progress_bar::Renderer for Renderer { .unwrap_or(Background::Color([0.6, 0.6, 0.6].into())) .into(), border_radius: 5, + border_width: 0, + border_color: Color::TRANSPARENT, }], }; @@ -35,6 +37,8 @@ impl progress_bar::Renderer for Renderer { active_color.unwrap_or([0.0, 0.95, 0.0].into()), ), border_radius: 5, + border_width: 0, + border_color: Color::TRANSPARENT, }; ( diff --git a/wgpu/src/renderer/widget/radio.rs b/wgpu/src/renderer/widget/radio.rs index 3c00a4c2..aa1dbadc 100644 --- a/wgpu/src/renderer/widget/radio.rs +++ b/wgpu/src/renderer/widget/radio.rs @@ -1,5 +1,5 @@ use crate::{Primitive, Renderer}; -use iced_native::{radio, Background, MouseCursor, Rectangle}; +use iced_native::{radio, Background, Color, MouseCursor, Rectangle}; const SIZE: f32 = 28.0; const DOT_SIZE: f32 = SIZE / 2.0; @@ -16,30 +16,20 @@ impl radio::Renderer for Renderer { is_mouse_over: bool, (label, _): Self::Output, ) -> 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 radio = Primitive::Quad { + bounds, + 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) as u16, + border_width: 1, + border_color: Color::from_rgb(0.6, 0.6, 0.6), + }; ( Primitive::Group { @@ -53,11 +43,13 @@ impl radio::Renderer for Renderer { }, background: Background::Color([0.3, 0.3, 0.3].into()), 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..42a4a743 100644 --- a/wgpu/src/renderer/widget/scrollable.rs +++ b/wgpu/src/renderer/widget/scrollable.rs @@ -1,5 +1,7 @@ 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; @@ -68,6 +70,8 @@ impl scrollable::Renderer for Renderer { [0.0, 0.0, 0.0, 0.7].into(), ), border_radius: 5, + border_width: 0, + border_color: Color::TRANSPARENT, }; if is_mouse_over_scrollbar || state.is_scroller_grabbed() { @@ -83,6 +87,8 @@ impl scrollable::Renderer for Renderer { [0.0, 0.0, 0.0, 0.3].into(), ), border_radius: 5, + border_width: 0, + border_color: Color::TRANSPARENT, }; Primitive::Group { diff --git a/wgpu/src/renderer/widget/slider.rs b/wgpu/src/renderer/widget/slider.rs index c73a4e56..386decb5 100644 --- a/wgpu/src/renderer/widget/slider.rs +++ b/wgpu/src/renderer/widget/slider.rs @@ -29,8 +29,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([0.6, 0.6, 0.6, 0.5].into()), border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, }, Primitive::Quad { bounds: Rectangle { @@ -41,6 +43,8 @@ impl slider::Renderer for Renderer { }, background: Background::Color(Color::WHITE), border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, }, ); @@ -49,41 +53,31 @@ impl slider::Renderer for Renderer { 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, - }, - 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, + 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, }, - ); + 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, + border_width: 1, + border_color: Color::from_rgb(0.6, 0.6, 0.6), + }; ( 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..c6d8369b --- /dev/null +++ b/wgpu/src/settings.rs @@ -0,0 +1,4 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Settings { + 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..1d7e57c4 --- /dev/null +++ b/wgpu/src/widget.rs @@ -0,0 +1,3 @@ +pub mod button; +pub mod container; +pub mod text_input; 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/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/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 { |