From aea9def07da51d9d54d015b8310f5762fc86cf2f Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Sat, 18 Nov 2023 14:45:02 -0800 Subject: [PATCH] Rounded rect drawing --- Cargo.lock | 4 +-- src/context.rs | 48 +++++++++++++++++++++++++++----- src/styles.rs | 60 +++++++++++++++++++++++++++++++++++++++- src/styles/components.rs | 4 +++ src/widgets/button.rs | 2 +- 5 files changed, 107 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c71d68f..a9661c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -623,7 +623,7 @@ dependencies = [ [[package]] name = "figures" version = "0.1.0" -source = "git+https://github.com/khonsulabs/figures#52d06f3623cdb47128f1537fdadfe190f7afa88e" +source = "git+https://github.com/khonsulabs/figures#4712514fbed861ad530bd48d4e62f8efcaa4df1f" dependencies = [ "bytemuck", "euclid", @@ -1089,7 +1089,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kludgine" version = "0.1.0" -source = "git+https://github.com/khonsulabs/kludgine#74db2e6be9c79f384eafbe57af8fa92de8b8d03c" +source = "git+https://github.com/khonsulabs/kludgine#ca138e79a4b8ebc3bdd463006b1dadd3269183f3" dependencies = [ "ahash", "alot", diff --git a/src/context.rs b/src/context.rs index 077d9ed..f2b6ab9 100644 --- a/src/context.rs +++ b/src/context.rs @@ -10,12 +10,12 @@ use kludgine::app::winit::event::{ DeviceId, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase, }; use kludgine::figures::units::{Lp, Px, UPx}; -use kludgine::figures::{IntoSigned, Point, Rect, ScreenScale, Size}; +use kludgine::figures::{IntoSigned, IsZero, Point, Rect, ScreenScale, Size}; use kludgine::shapes::{Shape, StrokeOptions}; use kludgine::{Color, Kludgine}; use crate::graphics::Graphics; -use crate::styles::components::{HighlightColor, LayoutOrder, WidgetBackground}; +use crate::styles::components::{CornerRadius, HighlightColor, LayoutOrder, WidgetBackground}; use crate::styles::{ComponentDefinition, Styles, Theme, ThemePair}; use crate::utils::IgnorePoison; use crate::value::{Dynamic, IntoValue, Value}; @@ -519,15 +519,49 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, ' } } + /// Fills the background of this widget with `color`, honoring the current + /// [`CornerRadius`] setting. + /// + /// If the alpha channel of `color` is 0, this function does nothing. + pub fn fill(&mut self, color: Color) { + if color.alpha() > 0 { + let visible_rect = Rect::from(self.gfx.region().size - (Px(1), Px(1))); + + let radii = self.get(&CornerRadius); + let radii = radii.map(|r| r.into_px(self.gfx.scale())); + + let focus_ring = if radii.is_zero() { + Shape::filled_rect(visible_rect, color) + } else { + Shape::filled_round_rect(visible_rect, radii, color) + }; + self.gfx.draw_shape(&focus_ring); + } + } + /// Strokes an outline around this widget's contents. pub fn stroke_outline(&mut self, color: Color, options: StrokeOptions) where - Unit: ScreenScale, + Unit: ScreenScale + IsZero, { - let visible_rect = Rect::from(self.gfx.region().size - (Px(1), Px(1))); - let focus_ring = - Shape::stroked_rect(visible_rect, color, options.into_px(self.gfx.scale())); - self.gfx.draw_shape(&focus_ring); + if color.alpha() > 0 { + let visible_rect = Rect::from(self.gfx.region().size - (Px(1), Px(1))); + + let radii = self.get(&CornerRadius); + let radii = radii.map(|r| r.into_px(self.gfx.scale())); + + let focus_ring = if radii.is_zero() { + Shape::stroked_rect(visible_rect, color, options.into_px(self.gfx.scale())) + } else { + Shape::stroked_round_rect( + visible_rect, + radii, + color, + options.into_px(self.gfx.scale()), + ) + }; + self.gfx.draw_shape(&focus_ring); + } } /// Renders the default focus ring for this widget. diff --git a/src/styles.rs b/src/styles.rs index ddc560e..5c07b56 100644 --- a/src/styles.rs +++ b/src/styles.rs @@ -12,7 +12,8 @@ use std::sync::Arc; use ahash::AHashMap; use kludgine::figures::units::{Lp, Px, UPx}; -use kludgine::figures::{Fraction, IntoSigned, IntoUnsigned, Rect, ScreenScale, Size}; +use kludgine::figures::{Fraction, IntoSigned, IntoUnsigned, IsZero, Rect, ScreenScale, Size}; +use kludgine::shapes::CornerRadii; use kludgine::Color; use palette::{IntoColor, Okhsl, OklabHue, Srgb}; @@ -211,6 +212,18 @@ pub enum Component { Custom(CustomComponent), } +impl Component { + /// Returns a [`CustomComponent`] created from `component`. + /// + /// Custom components allow storing nearly any type in the style system. + pub fn custom(component: T) -> Self + where + T: RequireInvalidation + RefUnwindSafe + UnwindSafe + Debug + Send + Sync + 'static, + { + Self::Custom(CustomComponent::new(component)) + } +} + impl From for Component { fn from(value: Color) -> Self { Self::Color(value) @@ -303,6 +316,42 @@ impl RequireInvalidation for Lp { } } +impl From> for Component +where + Dimension: From, + Unit: Debug + UnwindSafe + RefUnwindSafe + Send + Sync + 'static, +{ + fn from(radii: CornerRadii) -> Self { + let radii = CornerRadii { + top_left: Dimension::from(radii.top_left), + top_right: Dimension::from(radii.top_right), + bottom_right: Dimension::from(radii.bottom_right), + bottom_left: Dimension::from(radii.bottom_left), + }; + Component::custom(radii) + } +} + +impl RequireInvalidation for CornerRadii { + fn requires_invalidation(&self) -> bool { + true + } +} + +impl TryFrom for CornerRadii { + type Error = Component; + + fn try_from(value: Component) -> Result { + match value { + Component::Custom(custom) => custom + .downcast() + .copied() + .ok_or_else(|| Component::Custom(custom)), + other => Err(other), + } + } +} + /// A 1-dimensional measurement that may be automatically calculated. #[derive(Debug, Clone, Copy)] pub enum FlexibleDimension { @@ -373,6 +422,15 @@ impl From for Dimension { } } +impl IsZero for Dimension { + fn is_zero(&self) -> bool { + match self { + Dimension::Px(x) => x.is_zero(), + Dimension::Lp(x) => x.is_zero(), + } + } +} + impl ScreenScale for Dimension { type Lp = Lp; type Px = Px; diff --git a/src/styles/components.rs b/src/styles/components.rs index ae50004..c5da2da 100644 --- a/src/styles/components.rs +++ b/src/styles/components.rs @@ -1,6 +1,7 @@ //! All style components supported by the built-in widgets. use kludgine::figures::units::Lp; +use kludgine::shapes::CornerRadii; use kludgine::Color; use crate::animation::easings::{EaseInOutQuadradic, EaseInQuadradic, EaseOutQuadradic}; @@ -133,5 +134,8 @@ define_components! { /// A [`Color`] to be used as a background color for widgets that render an /// opaque background. OpaqueWidgetColor(Color, "opaque_color", .surface.opaque_widget) + /// A set of radius descriptions for how much roundness to apply to the + /// shapes of widgets. + CornerRadius(CornerRadii, "corner_radius", CornerRadii::from(Dimension::Lp(Lp::points(100)))) } } diff --git a/src/widgets/button.rs b/src/widgets/button.rs index 5ee81d0..f22bf4e 100644 --- a/src/widgets/button.rs +++ b/src/widgets/button.rs @@ -336,7 +336,7 @@ impl Widget for Button { } let style = self.current_style(context); - context.gfx.fill(style.background); + context.fill(style.background); let two_lp_stroke = StrokeOptions::lp_wide(Lp::points(2)); context.stroke_outline(style.outline, two_lp_stroke);