define_components embrace, sanitize, docs, export

This commit is contained in:
Jonathan Johnson 2023-11-12 09:21:49 -08:00
parent 849710dbb1
commit 2a50bb32d4
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
9 changed files with 323 additions and 684 deletions

View file

@ -13,8 +13,10 @@ use kludgine::shapes::{Shape, StrokeOptions};
use kludgine::{Color, Kludgine};
use crate::graphics::Graphics;
use crate::styles::components::{HighlightColor, VisualOrder, WidgetBackground};
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, Styles, Theme, ThemePair};
use crate::styles::components::{HighlightColor, WidgetBackground};
use crate::styles::{
ComponentDefaultvalue, ComponentDefinition, Styles, Theme, ThemePair, VisualOrder,
};
use crate::value::{Dynamic, IntoValue, Value};
use crate::widget::{EventHandling, ManagedWidget, WidgetId, WidgetInstance, WidgetRef};
use crate::window::sealed::WindowCommand;

View file

@ -5,20 +5,19 @@ use std::borrow::Cow;
use std::collections::{hash_map, HashMap};
use std::fmt::Debug;
use std::ops::{
Add, Bound, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive,
Add, Bound, Div, Mul, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive,
};
use std::panic::{RefUnwindSafe, UnwindSafe};
use std::sync::Arc;
use kludgine::figures::units::{Lp, Px, UPx};
use kludgine::figures::{Fraction, IntoUnsigned, ScreenScale, Size};
use kludgine::figures::{Fraction, IntoUnsigned, Rect, ScreenScale, Size};
use kludgine::Color;
use palette::{IntoColor, Okhsl, OklabHue, Srgb};
use crate::animation::{EasingFunction, ZeroToOne};
use crate::context::WidgetContext;
use crate::names::Name;
use crate::styles::components::{FocusableWidgets, VisualOrder};
use crate::utils::Lazy;
use crate::value::{Dynamic, IntoValue, Value};
@ -27,7 +26,7 @@ pub mod components;
/// A collection of style components organized by their name.
#[derive(Clone, Debug, Default)]
pub struct Styles(Arc<HashMap<Group, HashMap<Name, Value<Component>>>>);
pub struct Styles(Arc<HashMap<Name, HashMap<Name, Value<Component>>>>);
impl Styles {
/// Returns an empty collection.
@ -157,8 +156,8 @@ impl IntoIterator for Styles {
/// An iterator over the owned contents of a [`Styles`] instance.
pub struct StylesIntoIter {
main: hash_map::IntoIter<Group, HashMap<Name, Value<Component>>>,
names: Option<(Group, hash_map::IntoIter<Name, Value<Component>>)>,
main: hash_map::IntoIter<Name, HashMap<Name, Value<Component>>>,
names: Option<(Name, hash_map::IntoIter<Name, Value<Component>>)>,
}
impl Iterator for StylesIntoIter {
@ -365,6 +364,50 @@ impl ScreenScale for Dimension {
}
}
impl Mul<i32> for Dimension {
type Output = Dimension;
fn mul(self, rhs: i32) -> Self::Output {
match self {
Self::Px(val) => Self::Px(val * rhs),
Self::Lp(val) => Self::Lp(val * rhs),
}
}
}
impl Mul<f32> for Dimension {
type Output = Dimension;
fn mul(self, rhs: f32) -> Self::Output {
match self {
Self::Px(val) => Self::Px(val * rhs),
Self::Lp(val) => Self::Lp(val * rhs),
}
}
}
impl Div<i32> for Dimension {
type Output = Dimension;
fn div(self, rhs: i32) -> Self::Output {
match self {
Self::Px(val) => Self::Px(val / rhs),
Self::Lp(val) => Self::Lp(val / rhs),
}
}
}
impl Div<f32> for Dimension {
type Output = Dimension;
fn div(self, rhs: f32) -> Self::Output {
match self {
Self::Px(val) => Self::Px(val / rhs),
Self::Lp(val) => Self::Lp(val / rhs),
}
}
}
/// A range of [`Dimension`]s.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct DimensionRange {
@ -567,73 +610,23 @@ where
}
}
/// A style component group.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Group(Name);
impl Group {
/// Returns a new group with `name`.
#[must_use]
pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
Self(Name::new(name))
}
/// Returns a new instance using the group name of `T`.
#[must_use]
pub fn from_group<T>() -> Self
where
T: ComponentGroup,
{
Self(T::name())
}
/// Returns true if this instance matches the group name of `T`.
#[must_use]
pub fn matches<T>(&self) -> bool
where
T: ComponentGroup,
{
self.0 == T::name()
}
}
/// A type that represents a group of style components.
pub trait ComponentGroup {
/// Returns the name of the group.
fn name() -> Name;
}
/// The Global style components group.
pub enum Global {}
impl ComponentGroup for Global {
fn name() -> Name {
Name::new("global")
}
}
/// A fully-qualified style component name.
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct ComponentName {
/// The group name.
pub group: Group,
pub group: Name,
/// The name of the component within the group.
pub name: Name,
}
impl ComponentName {
/// Returns a new instance using `group` and `name`.
pub fn new(group: Group, name: impl Into<Name>) -> Self {
pub fn new(group: impl Into<Name>, name: impl Into<Name>) -> Self {
Self {
group,
group: group.into(),
name: name.into(),
}
}
/// Returns a new instance using `G` and `name`.
pub fn named<G: ComponentGroup>(name: impl Into<Name>) -> Self {
Self::new(Group::from_group::<G>(), name)
}
}
impl From<&'static Lazy<ComponentName>> for ComponentName {
@ -1314,3 +1307,161 @@ impl ColorExt for Color {
most_contrasting
}
}
/// A 2d ordering configuration.
#[derive(Copy, Debug, Clone, Eq, PartialEq)]
pub struct VisualOrder {
/// The ordering to apply horizontally.
pub horizontal: HorizontalOrder,
/// The ordering to apply vertically.
pub vertical: VerticalOrder,
}
impl VisualOrder {
/// Returns a right-to-left ordering.
#[must_use]
pub const fn right_to_left() -> Self {
Self {
horizontal: HorizontalOrder::RightToLeft,
vertical: VerticalOrder::TopToBottom,
}
}
/// Returns a left-to-right ordering.
#[must_use]
pub const fn left_to_right() -> Self {
Self {
horizontal: HorizontalOrder::LeftToRight,
vertical: VerticalOrder::TopToBottom,
}
}
/// Returns the reverse ordering of `self`.
#[must_use]
pub fn rev(self) -> Self {
Self {
horizontal: self.horizontal.rev(),
vertical: self.vertical.rev(),
}
}
}
impl From<VisualOrder> for Component {
fn from(value: VisualOrder) -> Self {
Self::VisualOrder(value)
}
}
impl TryFrom<Component> for VisualOrder {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::VisualOrder(order) => Ok(order),
other => Err(other),
}
}
}
/// A horizontal direction.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum HorizontalOrder {
/// Describes an order starting at the left and proceeding to the right.
LeftToRight,
/// Describes an order starting at the right and proceeding to the left.
RightToLeft,
}
impl HorizontalOrder {
/// Returns the reverse order of `self`.
#[must_use]
pub fn rev(self) -> Self {
match self {
Self::LeftToRight => Self::RightToLeft,
Self::RightToLeft => Self::LeftToRight,
}
}
pub(crate) fn sort_key(self, rect: &Rect<Px>) -> Px {
match self {
HorizontalOrder::LeftToRight => rect.origin.x,
HorizontalOrder::RightToLeft => -(rect.origin.x + rect.size.width),
}
}
}
/// A vertical direction.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum VerticalOrder {
/// Describes an order starting at the top and proceeding to the bottom.
TopToBottom,
/// Describes an order starting at the bottom and proceeding to the top.
BottomToTop,
}
impl VerticalOrder {
/// Returns the reverse order of `self`.
#[must_use]
pub fn rev(self) -> Self {
match self {
Self::TopToBottom => VerticalOrder::BottomToTop,
Self::BottomToTop => VerticalOrder::TopToBottom,
}
}
pub(crate) fn max_px(self) -> Px {
match self {
VerticalOrder::TopToBottom => Px::MAX,
VerticalOrder::BottomToTop => Px::MIN,
}
}
pub(crate) fn smallest_px(self, a: Px, b: Px) -> Px {
match self {
VerticalOrder::TopToBottom => a.min(b),
VerticalOrder::BottomToTop => b.max(a),
}
}
}
/// A configuration option to control which controls should be able to receive
/// focus through keyboard focus handling or initial focus handling.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub enum FocusableWidgets {
/// Allow all widgets that can respond to keyboard input to accept focus.
#[default]
All,
/// Only allow widgets that expect textual input to accept focus.
OnlyTextual,
}
impl FocusableWidgets {
/// Returns true if all controls should be focusable.
#[must_use]
pub const fn is_all(self) -> bool {
matches!(self, Self::All)
}
/// Returns true if only textual should be focusable.
#[must_use]
pub const fn is_only_textual(self) -> bool {
matches!(self, Self::OnlyTextual)
}
}
impl From<FocusableWidgets> for Component {
fn from(value: FocusableWidgets) -> Self {
Self::FocusableWidgets(value)
}
}
impl TryFrom<Component> for FocusableWidgets {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::FocusableWidgets(focus) => Ok(focus),
other => Err(other),
}
}
}

View file

@ -1,17 +1,38 @@
//! All style components supported by the built-in widgets.
use std::borrow::Cow;
use kludgine::figures::units::{Lp, Px};
use kludgine::figures::Rect;
use kludgine::figures::units::Lp;
use kludgine::Color;
use crate::animation::easings::{EaseInQuadradic, EaseOutQuadradic};
use crate::animation::easings::{EaseInOutQuadradic, EaseInQuadradic, EaseOutQuadradic};
use crate::animation::EasingFunction;
use crate::context::WidgetContext;
use crate::styles::{
Component, ComponentDefinition, ComponentName, Dimension, Global, NamedComponent,
};
use crate::styles::{Dimension, FocusableWidgets, VisualOrder};
/// Defines a set of style components for Gooey.
///
/// These macros implement [`NamedComponent`](crate::styles::NamedComponent) and
/// [`ComponentDefinition`](crate::styles::ComponentDefinition) for each entry
/// defined. The syntax is:
///
/// ```rust
/// use gooey::define_components;
/// use gooey::styles::Dimension;
/// use gooey::styles::components::{SurfaceColor, TextColor};
/// use gooey::kludgine::Color;
///
/// define_components! {
/// GroupName {
/// /// This is the documentation for example component. It has a default value of `Dimension::ZERO`.
/// ExampleComponent(Dimension, "example_component", Dimension::ZERO)
/// /// This component whose default value is a color from the current theme.
/// ThemedComponent(Color, "themed_component", .primary.color)
/// /// This component is a color whose default value is the currently defined `TextColor`.
/// DependentComponent(Color, "dependent_component", |context| context.query_style(&TextColor))
/// /// This component defaults to picking a contrasting color between `TextColor` and `SurfaceColor`
/// ContrastingColor(Color, "contrasting_color", contrasting!(ThemedComponent, TextColor, SurfaceColor))
/// }
/// }
/// ```
#[macro_export]
macro_rules! define_components {
($($widget:ident { $($(#$doc:tt)* $component:ident($type:ty, $name:expr, $($default:tt)*))* })*) => {$($(
$(#$doc)*
@ -20,9 +41,12 @@ macro_rules! define_components {
const _: () = {
use $crate::styles::{ComponentDefinition, ComponentName, NamedComponent};
use $crate::context::WidgetContext;
use ::std::borrow::Cow;
impl NamedComponent for $component {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Button>($name))
Cow::Owned(ComponentName::new(stringify!($widget), $name))
}
}
@ -38,7 +62,7 @@ macro_rules! define_components {
define_components!($type, |context| context.theme().$($path)*);
};
($type:ty, |$context:ident| $($expr:tt)*) => {
fn default_value(&self, $context: &WidgetContext<'_, '_>) -> Color {
fn default_value(&self, $context: &WidgetContext<'_, '_>) -> $type {
$($expr)*
}
};
@ -47,6 +71,7 @@ macro_rules! define_components {
};
($type:ty, contrasting!($bg:ident, $($fg:ident),+ $(,)?)) => {
define_components!($type, |context| {
use $crate::styles::ColorExt;
let styles = context.query_styles(&[&$bg, $(&$fg),*]);
styles.get(&$bg, context).most_contrasting(&[
$(styles.get(&$fg, context)),+
@ -58,440 +83,48 @@ macro_rules! define_components {
};
}
/// The [`Dimension`] to use as the size to render text.
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct TextSize;
impl NamedComponent for TextSize {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Global>("text_size"))
}
}
impl ComponentDefinition for TextSize {
type ComponentType = Dimension;
fn default_value(&self, _context: &WidgetContext<'_, '_>) -> Dimension {
Dimension::Lp(Lp::points(12))
}
}
/// The [`Dimension`] to use to space multiple lines of text.
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct LineHeight;
impl NamedComponent for LineHeight {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Global>("line_height"))
}
}
impl ComponentDefinition for LineHeight {
type ComponentType = Dimension;
fn default_value(&self, _context: &WidgetContext<'_, '_>) -> Dimension {
Dimension::Lp(Lp::points(14))
}
}
/// The [`Color`] to use when rendering text.
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct SurfaceColor;
impl NamedComponent for SurfaceColor {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Global>("surface_color"))
}
}
impl ComponentDefinition for SurfaceColor {
type ComponentType = Color;
fn default_value(&self, context: &WidgetContext<'_, '_>) -> Color {
context.theme().surface.color
}
}
/// The [`Color`] to use when rendering text.
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct TextColor;
impl NamedComponent for TextColor {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Global>("text_color"))
}
}
impl ComponentDefinition for TextColor {
type ComponentType = Color;
fn default_value(&self, context: &WidgetContext<'_, '_>) -> Color {
context.theme().surface.on_color
}
}
/// A [`Color`] to be used as a highlight color.
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct HighlightColor;
impl NamedComponent for HighlightColor {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Global>("highlight_color"))
}
}
impl ComponentDefinition for HighlightColor {
type ComponentType = Color;
fn default_value(&self, context: &WidgetContext<'_, '_>) -> Color {
context.theme().primary.color.with_alpha(128)
}
}
/// Intrinsic, uniform padding for a widget.
///
/// This component is opt-in and does not automatically work for all widgets.
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct IntrinsicPadding;
impl NamedComponent for IntrinsicPadding {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Global>("padding"))
}
}
impl ComponentDefinition for IntrinsicPadding {
type ComponentType = Dimension;
fn default_value(&self, _context: &WidgetContext<'_, '_>) -> Dimension {
Dimension::Lp(Lp::points(5))
}
}
/// The [`EasingFunction`] to apply to animations that have no inherent
/// directionality.
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct Easing;
impl NamedComponent for Easing {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Global>("easing"))
}
}
impl ComponentDefinition for Easing {
type ComponentType = EasingFunction;
fn default_value(&self, _context: &WidgetContext<'_, '_>) -> Self::ComponentType {
EasingFunction::from(EaseInQuadradic)
}
}
/// The [`EasingFunction`] to apply to animations that transition a value from
/// "nothing" to "something". For example, if an widget is animating a color's
/// alpha channel towards opaqueness, it would query for this style component.
/// Otherwise, it would use [`EasingOut`].
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct EasingIn;
impl NamedComponent for EasingIn {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Global>("easing_in"))
}
}
impl ComponentDefinition for EasingIn {
type ComponentType = EasingFunction;
fn default_value(&self, _context: &WidgetContext<'_, '_>) -> Self::ComponentType {
EasingFunction::from(EaseInQuadradic)
}
}
/// The [`EasingFunction`] to apply to animations that transition a value from
/// "something" to "nothing". For example, if an widget is animating a color's
/// alpha channel towards transparency, it would query for this style component.
/// Otherwise, it would use [`EasingIn`].
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct EasingOut;
impl NamedComponent for EasingOut {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Global>("easing_out"))
}
}
impl ComponentDefinition for EasingOut {
type ComponentType = EasingFunction;
fn default_value(&self, _context: &WidgetContext<'_, '_>) -> Self::ComponentType {
EasingFunction::from(EaseOutQuadradic)
}
}
/// A 2d ordering configuration.
#[derive(Copy, Debug, Clone, Eq, PartialEq)]
pub struct VisualOrder {
/// The ordering to apply horizontally.
pub horizontal: HorizontalOrder,
/// The ordering to apply vertically.
pub vertical: VerticalOrder,
}
impl VisualOrder {
/// Returns a right-to-left ordering.
#[must_use]
pub const fn right_to_left() -> Self {
Self {
horizontal: HorizontalOrder::RightToLeft,
vertical: VerticalOrder::TopToBottom,
}
}
/// Returns a left-to-right ordering.
#[must_use]
pub const fn left_to_right() -> Self {
Self {
horizontal: HorizontalOrder::LeftToRight,
vertical: VerticalOrder::TopToBottom,
}
}
/// Returns the reverse ordering of `self`.
#[must_use]
pub fn rev(self) -> Self {
Self {
horizontal: self.horizontal.rev(),
vertical: self.vertical.rev(),
}
}
}
/// The [`VisualOrder`] strategy to use when laying out content.
#[derive(Debug)]
pub struct LayoutOrder;
impl NamedComponent for LayoutOrder {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Global>("visual_order"))
}
}
impl ComponentDefinition for LayoutOrder {
type ComponentType = VisualOrder;
fn default_value(&self, _context: &WidgetContext<'_, '_>) -> Self::ComponentType {
VisualOrder::left_to_right()
}
}
impl From<VisualOrder> for Component {
fn from(value: VisualOrder) -> Self {
Self::VisualOrder(value)
}
}
impl TryFrom<Component> for VisualOrder {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::VisualOrder(order) => Ok(order),
other => Err(other),
}
}
}
/// A horizontal direction.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum HorizontalOrder {
/// Describes an order starting at the left and proceeding to the right.
LeftToRight,
/// Describes an order starting at the right and proceeding to the left.
RightToLeft,
}
impl HorizontalOrder {
/// Returns the reverse order of `self`.
#[must_use]
pub fn rev(self) -> Self {
match self {
Self::LeftToRight => Self::RightToLeft,
Self::RightToLeft => Self::LeftToRight,
}
}
pub(crate) fn sort_key(self, rect: &Rect<Px>) -> Px {
match self {
HorizontalOrder::LeftToRight => rect.origin.x,
HorizontalOrder::RightToLeft => -(rect.origin.x + rect.size.width),
}
}
}
/// A vertical direction.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum VerticalOrder {
/// Describes an order starting at the top and proceeding to the bottom.
TopToBottom,
/// Describes an order starting at the bottom and proceeding to the top.
BottomToTop,
}
impl VerticalOrder {
/// Returns the reverse order of `self`.
#[must_use]
pub fn rev(self) -> Self {
match self {
Self::TopToBottom => VerticalOrder::BottomToTop,
Self::BottomToTop => VerticalOrder::TopToBottom,
}
}
pub(crate) fn max_px(self) -> Px {
match self {
VerticalOrder::TopToBottom => Px::MAX,
VerticalOrder::BottomToTop => Px::MIN,
}
}
pub(crate) fn smallest_px(self, a: Px, b: Px) -> Px {
match self {
VerticalOrder::TopToBottom => a.min(b),
VerticalOrder::BottomToTop => b.max(a),
}
}
}
/// The set of controls to allow focusing via tab key and initial focus
/// selection.
pub struct AutoFocusableControls;
impl NamedComponent for AutoFocusableControls {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Global>("focus"))
}
}
impl ComponentDefinition for AutoFocusableControls {
type ComponentType = FocusableWidgets;
fn default_value(&self, _context: &WidgetContext<'_, '_>) -> Self::ComponentType {
FocusableWidgets::default()
}
}
impl From<FocusableWidgets> for Component {
fn from(value: FocusableWidgets) -> Self {
Self::FocusableWidgets(value)
}
}
impl TryFrom<Component> for FocusableWidgets {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::FocusableWidgets(focus) => Ok(focus),
other => Err(other),
}
}
}
/// A configuration option to control which controls should be able to receive
/// focus through keyboard focus handling or initial focus handling.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub enum FocusableWidgets {
/// Allow all widgets that can respond to keyboard input to accept focus.
#[default]
All,
/// Only allow widgets that expect textual input to accept focus.
OnlyTextual,
}
impl FocusableWidgets {
/// Returns true if all controls should be focusable.
#[must_use]
pub const fn is_all(self) -> bool {
matches!(self, Self::All)
}
/// Returns true if only textual should be focusable.
#[must_use]
pub const fn is_only_textual(self) -> bool {
matches!(self, Self::OnlyTextual)
}
}
/// A [`Color`] to be used as a highlight color.
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct WidgetBackground;
impl NamedComponent for WidgetBackground {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Global>("widget_background_color"))
}
}
impl ComponentDefinition for WidgetBackground {
type ComponentType = Color;
fn default_value(&self, _context: &WidgetContext<'_, '_>) -> Color {
Color::CLEAR_WHITE
}
}
/// A [`Color`] to be used as an outline color.
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct OutlineColor;
impl NamedComponent for OutlineColor {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Global>("outline_color"))
}
}
impl ComponentDefinition for OutlineColor {
type ComponentType = Color;
fn default_value(&self, context: &WidgetContext<'_, '_>) -> Color {
context.theme().surface.outline
}
}
/// A [`Color`] to be used as an outline color.
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct DisabledOutlineColor;
impl NamedComponent for DisabledOutlineColor {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Global>("disabled_outline_color"))
}
}
impl ComponentDefinition for DisabledOutlineColor {
type ComponentType = Color;
fn default_value(&self, context: &WidgetContext<'_, '_>) -> Color {
context.theme().surface.outline_variant
}
}
/// A [`Color`] to be used as a background color for widgets that render an
/// opaque background.
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct OpaqueWidgetColor;
impl NamedComponent for OpaqueWidgetColor {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Global>("opaque_color"))
}
}
impl ComponentDefinition for OpaqueWidgetColor {
type ComponentType = Color;
fn default_value(&self, context: &WidgetContext<'_, '_>) -> Color {
context.theme().surface.opaque_widget
define_components! {
Global {
/// The [`Dimension`] to use as the size to render text.
TextSize(Dimension, "text_size", Dimension::Lp(Lp::points(12)))
/// The [`Dimension`] to use to space multiple lines of text.
LineHeight(Dimension,"line_height",Dimension::Lp(Lp::points(14)))
/// The [`Color`] of the surface for the user interface to draw upon.
SurfaceColor(Color, "surface_color", .surface.color)
/// The [`Color`] to use when rendering text.
TextColor(Color, "text_color", .surface.on_color)
/// A [`Color`] to be used as a highlight color.
HighlightColor(Color,"highlight_color",.primary.color.with_alpha(128))
/// Intrinsic, uniform padding for a widget.
///
/// This component is opt-in and does not automatically work for all widgets.
IntrinsicPadding(Dimension, "padding", Dimension::Lp(Lp::points(5)))
/// The [`EasingFunction`] to apply to animations that have no inherent
/// directionality.
Easing(EasingFunction, "Easing", EasingFunction::from(EaseInOutQuadradic))
/// The [`EasingFunction`] to apply to animations that transition a value from
/// "nothing" to "something". For example, if an widget is animating a color's
/// alpha channel towards opaqueness, it would query for this style component.
/// Otherwise, it would use [`EasingOut`].
EasingIn(EasingFunction, "easing_out", EasingFunction::from(EaseInQuadradic))
/// The [`EasingFunction`] to apply to animations that transition a value from
/// "something" to "nothing". For example, if an widget is animating a color's
/// alpha channel towards transparency, it would query for this style component.
/// Otherwise, it would use [`EasingIn`].
EasingOut(EasingFunction, "easing_out", EasingFunction::from(EaseOutQuadradic))
/// The [`VisualOrder`] strategy to use when laying out content.
LayoutOrder(VisualOrder, "visual_order", VisualOrder::left_to_right())
/// The set of controls to allow focusing via tab key and initial focus
/// selection.
AutoFocusableControls(FocusableWidgets, "focus", FocusableWidgets::default())
/// A [`Color`] to be used as the background color of a widget.
WidgetBackground(Color, "widget_backgrond_color", Color::CLEAR_WHITE)
/// A [`Color`] to be used as an outline color.
OutlineColor(Color, "outline_color", .surface.outline)
/// A [`Color`] to be used as an outline color.
DisabledOutlineColor(Color, "disabled_outline_color", .surface.outline_variant)
/// A [`Color`] to be used as a background color for widgets that render an
/// opaque background.
OpaqueWidgetColor(Color, "opaque_color", .surface.opaque_widget)
}
}

View file

@ -6,8 +6,9 @@ use kludgine::figures::units::Px;
use kludgine::figures::{Point, Rect};
use crate::context::WidgetContext;
use crate::styles::components::VisualOrder;
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, ComponentType, Styles, ThemePair};
use crate::styles::{
ComponentDefaultvalue, ComponentDefinition, ComponentType, Styles, ThemePair, VisualOrder,
};
use crate::value::Value;
use crate::widget::{ManagedWidget, WidgetId, WidgetInstance};
use crate::window::ThemeMode;

View file

@ -15,8 +15,7 @@ use kludgine::figures::units::{Px, UPx};
use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size};
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
use crate::styles::components::VisualOrder;
use crate::styles::{IntoComponentValue, NamedComponent, Styles, ThemePair};
use crate::styles::{IntoComponentValue, NamedComponent, Styles, ThemePair, VisualOrder};
use crate::tree::Tree;
use crate::value::{IntoValue, Value};
use crate::widgets::{Align, Expand, Scroll, Style};

View file

@ -1,5 +1,4 @@
//! A clickable, labeled button
use std::borrow::Cow;
use std::panic::UnwindSafe;
use std::time::Duration;
@ -10,11 +9,10 @@ use kludgine::Color;
use crate::animation::{AnimationHandle, AnimationTarget, Spawn};
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext};
use crate::names::Name;
use crate::styles::components::{
AutoFocusableControls, Easing, IntrinsicPadding, OpaqueWidgetColor, SurfaceColor, TextColor,
};
use crate::styles::{ColorExt, ComponentGroup, Styles};
use crate::styles::Styles;
use crate::utils::ModifiersExt;
use crate::value::{Dynamic, IntoValue, Value};
use crate::widget::{Callback, EventHandling, MakeWidget, Widget, WidgetRef, HANDLED, IGNORED};
@ -314,12 +312,6 @@ impl Widget for Button {
}
}
impl ComponentGroup for Button {
fn name() -> Name {
Name::new("button")
}
}
define_components! {
Button {
/// The background color of the button.

View file

@ -1,18 +1,14 @@
//! A read-only text widget.
use std::borrow::Cow;
use kludgine::figures::units::{Px, UPx};
use kludgine::figures::{IntoUnsigned, Point, ScreenScale, Size};
use kludgine::text::{MeasuredText, Text, TextOrigin};
use kludgine::Color;
use crate::context::{GraphicsContext, LayoutContext, WidgetContext};
use crate::context::{GraphicsContext, LayoutContext};
use crate::styles::components::{IntrinsicPadding, TextColor};
use crate::styles::{ComponentDefinition, ComponentGroup, ComponentName, NamedComponent};
use crate::value::{Dynamic, IntoValue, Value};
use crate::widget::{MakeWidget, Widget, WidgetInstance};
use crate::{ConstraintLimit, Name};
use crate::ConstraintLimit;
/// A read-only text widget.
#[derive(Debug)]
@ -38,10 +34,7 @@ impl Widget for Label {
let size = context.gfx.region().size;
let center = Point::from(size) / 2;
let styles = context.query_styles(&[&TextColor, &LabelBackground]);
let background = styles.get(&LabelBackground, context);
context.gfx.fill(background);
let styles = context.query_styles(&[&TextColor]);
if let Some(measured) = &self.prepared_text {
context
@ -86,30 +79,6 @@ impl Widget for Label {
}
}
impl ComponentGroup for Label {
fn name() -> Name {
Name::new("Label")
}
}
/// A [`Color`] to be used as a highlight color.
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct LabelBackground;
impl NamedComponent for LabelBackground {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Label>("background_color"))
}
}
impl ComponentDefinition for LabelBackground {
type ComponentType = Color;
fn default_value(&self, _context: &WidgetContext<'_, '_>) -> Color {
Color::CLEAR_WHITE
}
}
macro_rules! impl_make_widget {
($($type:ty),*) => {
$(impl MakeWidget for $type {

View file

@ -1,5 +1,4 @@
//! A container that scrolls its contents on a virtual surface.
use std::borrow::Cow;
use std::time::Duration;
use intentional::Cast;
@ -12,14 +11,12 @@ use kludgine::shapes::Shape;
use kludgine::Color;
use crate::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn, ZeroToOne};
use crate::context::{AsEventContext, EventContext, LayoutContext, WidgetContext};
use crate::context::{AsEventContext, EventContext, LayoutContext};
use crate::styles::components::{EasingIn, EasingOut, LineHeight};
use crate::styles::{
ComponentDefinition, ComponentGroup, ComponentName, Dimension, NamedComponent,
};
use crate::styles::Dimension;
use crate::value::Dynamic;
use crate::widget::{EventHandling, MakeWidget, Widget, WidgetRef, HANDLED, IGNORED};
use crate::{ConstraintLimit, Name};
use crate::ConstraintLimit;
/// A widget that supports scrolling its contents.
#[derive(Debug)]
@ -319,25 +316,9 @@ fn scrollbar_region(scroll: Px, content_size: Px, control_size: Px) -> Scrollbar
}
}
/// The thickness that scrollbars are drawn with.
pub struct ScrollBarThickness;
impl ComponentDefinition for ScrollBarThickness {
type ComponentType = Dimension;
fn default_value(&self, _context: &WidgetContext<'_, '_>) -> Self::ComponentType {
Dimension::Lp(Lp::points(7))
}
}
impl NamedComponent for ScrollBarThickness {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Scroll>("text_size"))
}
}
impl ComponentGroup for Scroll {
fn name() -> Name {
Name::new("Scroll")
define_components! {
Scroll {
/// The thickness that scrollbars are drawn with.
ScrollBarThickness(Dimension, "size", Dimension::Lp(Lp::points(7)))
}
}

View file

@ -1,4 +1,3 @@
use std::borrow::Cow;
use std::fmt::Debug;
use std::panic::UnwindSafe;
@ -12,9 +11,9 @@ use kludgine::shapes::Shape;
use kludgine::{Color, Origin};
use crate::animation::{LinearInterpolate, PercentBetween};
use crate::context::{EventContext, GraphicsContext, LayoutContext, WidgetContext};
use crate::context::{EventContext, GraphicsContext, LayoutContext};
use crate::styles::components::OpaqueWidgetColor;
use crate::styles::{ComponentDefinition, ComponentName, Dimension, Group, NamedComponent};
use crate::styles::Dimension;
use crate::value::{Dynamic, IntoDynamic, IntoValue, Value};
use crate::widget::{EventHandling, Widget, HANDLED};
use crate::ConstraintLimit;
@ -315,107 +314,19 @@ where
}
}
/// The size of the track that the knob of a [`Slider`] traversesq.
pub struct TrackSize;
impl ComponentDefinition for TrackSize {
type ComponentType = Dimension;
fn default_value(&self, _context: &WidgetContext<'_, '_>) -> Self::ComponentType {
Dimension::Lp(Lp::points(5))
}
}
impl NamedComponent for TrackSize {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::new(Group::new("Slider"), "track_size"))
}
}
/// The width and height of the draggable portion of a [`Slider`].
pub struct KnobSize;
impl ComponentDefinition for KnobSize {
type ComponentType = Dimension;
fn default_value(&self, _context: &WidgetContext<'_, '_>) -> Self::ComponentType {
Dimension::Lp(Lp::points(14))
}
}
impl NamedComponent for KnobSize {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::new(Group::new("Slider"), "knob_size"))
}
}
/// The minimum length of the slidable dimension.
pub struct MinimumSliderSize;
impl ComponentDefinition for MinimumSliderSize {
type ComponentType = Dimension;
fn default_value(&self, _context: &WidgetContext<'_, '_>) -> Self::ComponentType {
Dimension::Lp(Lp::points(14))
}
}
impl NamedComponent for MinimumSliderSize {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::new(Group::new("Slider"), "minimum_size"))
}
}
/// The color of the draggable portion of the knob.
pub struct KnobColor;
impl ComponentDefinition for KnobColor {
type ComponentType = Color;
fn default_value(&self, context: &WidgetContext<'_, '_>) -> Self::ComponentType {
context.theme().primary.color
}
}
impl NamedComponent for KnobColor {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::new(Group::new("Slider"), "knob_color"))
}
}
/// The color of the track that the knob rests on.
pub struct TrackColor;
impl ComponentDefinition for TrackColor {
type ComponentType = Color;
fn default_value(&self, context: &WidgetContext<'_, '_>) -> Self::ComponentType {
context.theme().primary.color
}
}
impl NamedComponent for TrackColor {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::new(Group::new("Slider"), "track_color"))
}
}
/// The color of the draggable portion of the knob.
pub struct InactiveTrackColor;
impl ComponentDefinition for InactiveTrackColor {
type ComponentType = Color;
fn default_value(&self, context: &WidgetContext<'_, '_>) -> Self::ComponentType {
context.query_style(&OpaqueWidgetColor)
}
}
impl NamedComponent for InactiveTrackColor {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::new(
Group::new("Slider"),
"inactive_track_color",
))
define_components! {
Slider {
/// The size of the track that the knob of a [`Slider`] traversesq.
TrackSize(Dimension, "track_size", Dimension::Lp(Lp::points(5)))
/// The width and height of the draggable portion of a [`Slider`].
KnobSize(Dimension, "knob_size", Dimension::Lp(Lp::points(14)))
/// The minimum length of the slidable dimension.
MinimumSliderSize(Dimension, "minimum_size", |context| context.query_style(&KnobSize) * 2)
/// The color of the draggable portion of the knob.
KnobColor(Color, "knob_color", .primary.color) // TODO make this pull from a component multiple widgets can share
/// The color of the track that the knob rests on.
TrackColor(Color,"track_color", |context| context.query_style(&KnobColor))
/// The color of the track that the knob rests on.
InactiveTrackColor(Color, "inactive_track_color", |context| context.query_style(&OpaqueWidgetColor))
}
}