mirror of
https://github.com/danbulant/cushy
synced 2026-06-08 17:11:27 +00:00
commit
9182b04c0d
9 changed files with 195 additions and 205 deletions
|
|
@ -21,7 +21,7 @@ fn main() -> gooey::Result {
|
||||||
.and(Input::new(chat_message.clone()).on_key(move |input| {
|
.and(Input::new(chat_message.clone()).on_key(move |input| {
|
||||||
match (input.state, input.logical_key) {
|
match (input.state, input.logical_key) {
|
||||||
(ElementState::Pressed, Key::Enter) => {
|
(ElementState::Pressed, Key::Enter) => {
|
||||||
let new_message = chat_message.map_mut(|text| std::mem::take(text));
|
let new_message = chat_message.map_mut(std::mem::take);
|
||||||
chat_log.map_mut(|chat_log| {
|
chat_log.map_mut(|chat_log| {
|
||||||
chat_log.push_str(&new_message);
|
chat_log.push_str(&new_message);
|
||||||
chat_log.push('\n');
|
chat_log.push('\n');
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ use kludgine::{Color, Kludgine};
|
||||||
use crate::graphics::Graphics;
|
use crate::graphics::Graphics;
|
||||||
use crate::styles::components::{HighlightColor, VisualOrder, WidgetBackground};
|
use crate::styles::components::{HighlightColor, VisualOrder, WidgetBackground};
|
||||||
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, Styles, Theme, ThemePair};
|
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, Styles, Theme, ThemePair};
|
||||||
use crate::value::{Dynamic, Value};
|
use crate::value::{Dynamic, IntoValue, Value};
|
||||||
use crate::widget::{EventHandling, ManagedWidget, WidgetId, WidgetInstance, WidgetRef};
|
use crate::widget::{EventHandling, ManagedWidget, WidgetId, WidgetInstance, WidgetRef};
|
||||||
use crate::window::sealed::WindowCommand;
|
use crate::window::sealed::WindowCommand;
|
||||||
use crate::window::{RunningWindow, ThemeMode};
|
use crate::window::{RunningWindow, ThemeMode};
|
||||||
|
|
@ -196,7 +196,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
Err(_) => false,
|
Err(()) => false,
|
||||||
};
|
};
|
||||||
if new {
|
if new {
|
||||||
if let Some(active) = self.pending_state.active.clone() {
|
if let Some(active) = self.pending_state.active.clone() {
|
||||||
|
|
@ -250,7 +250,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
Err(_) => false,
|
Err(()) => false,
|
||||||
};
|
};
|
||||||
if new {
|
if new {
|
||||||
if let Some(focus) = self.pending_state.focus.clone() {
|
if let Some(focus) = self.pending_state.focus.clone() {
|
||||||
|
|
@ -890,8 +890,8 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
||||||
///
|
///
|
||||||
/// Style queries for children will return any values matching this
|
/// Style queries for children will return any values matching this
|
||||||
/// collection.
|
/// collection.
|
||||||
pub fn attach_styles(&self, styles: Value<Styles>) {
|
pub fn attach_styles(&self, styles: impl IntoValue<Styles>) {
|
||||||
self.current_node.attach_styles(styles);
|
self.current_node.attach_styles(styles.into_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attaches `theme` to the widget hierarchy for this widget.
|
/// Attaches `theme` to the widget hierarchy for this widget.
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ pub mod animation;
|
||||||
pub mod context;
|
pub mod context;
|
||||||
mod graphics;
|
mod graphics;
|
||||||
mod names;
|
mod names;
|
||||||
|
#[macro_use]
|
||||||
pub mod styles;
|
pub mod styles;
|
||||||
mod tick;
|
mod tick;
|
||||||
mod tree;
|
mod tree;
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ use crate::styles::components::{FocusableWidgets, VisualOrder};
|
||||||
use crate::utils::Lazy;
|
use crate::utils::Lazy;
|
||||||
use crate::value::{Dynamic, IntoValue, Value};
|
use crate::value::{Dynamic, IntoValue, Value};
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
pub mod components;
|
pub mod components;
|
||||||
|
|
||||||
/// A collection of style components organized by their name.
|
/// A collection of style components organized by their name.
|
||||||
|
|
@ -58,7 +59,7 @@ impl Styles {
|
||||||
|
|
||||||
/// Adds a [`Component`] for the name provided and returns self.
|
/// Adds a [`Component`] for the name provided and returns self.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with(mut self, name: &impl NamedComponent, component: impl Into<Component>) -> Self {
|
pub fn with(mut self, name: &impl NamedComponent, component: impl IntoComponentValue) -> Self {
|
||||||
self.insert(name, component);
|
self.insert(name, component);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -1007,7 +1008,7 @@ impl SurfaceTheme {
|
||||||
Self {
|
Self {
|
||||||
color: neutral.color(98),
|
color: neutral.color(98),
|
||||||
dim_color: neutral_variant.color(70),
|
dim_color: neutral_variant.color(70),
|
||||||
bright_color: neutral.color(99),
|
bright_color: neutral.color(100),
|
||||||
lowest_container: neutral.color(100),
|
lowest_container: neutral.color(100),
|
||||||
low_container: neutral.color(96),
|
low_container: neutral.color(96),
|
||||||
container: neutral.color(95),
|
container: neutral.color(95),
|
||||||
|
|
@ -1027,7 +1028,7 @@ impl SurfaceTheme {
|
||||||
Self {
|
Self {
|
||||||
color: neutral.color(10),
|
color: neutral.color(10),
|
||||||
dim_color: neutral_variant.color(2),
|
dim_color: neutral_variant.color(2),
|
||||||
bright_color: neutral.color(10),
|
bright_color: neutral.color(11),
|
||||||
lowest_container: neutral.color(15),
|
lowest_container: neutral.color(15),
|
||||||
low_container: neutral.color(20),
|
low_container: neutral.color(20),
|
||||||
container: neutral.color(25),
|
container: neutral.color(25),
|
||||||
|
|
@ -1158,6 +1159,7 @@ impl ColorSource {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn contrast_between(self, other: Self) -> ZeroToOne {
|
pub fn contrast_between(self, other: Self) -> ZeroToOne {
|
||||||
let saturation_delta = self.saturation.difference_between(other.saturation);
|
let saturation_delta = self.saturation.difference_between(other.saturation);
|
||||||
|
|
||||||
let self_hue = self.hue.into_positive_degrees();
|
let self_hue = self.hue.into_positive_degrees();
|
||||||
let other_hue = other.hue.into_positive_degrees();
|
let other_hue = other.hue.into_positive_degrees();
|
||||||
// Calculate the shortest distance between the hues, taking into account
|
// Calculate the shortest distance between the hues, taking into account
|
||||||
|
|
@ -1277,7 +1279,7 @@ impl ColorExt for Color {
|
||||||
let other_alpha = ZeroToOne::new(self.alpha_f32());
|
let other_alpha = ZeroToOne::new(self.alpha_f32());
|
||||||
let alpha_delta = check_alpha.difference_between(other_alpha);
|
let alpha_delta = check_alpha.difference_between(other_alpha);
|
||||||
|
|
||||||
lightness_delta * source_change * alpha_delta
|
ZeroToOne::new((*lightness_delta + *source_change + *alpha_delta) / 3.)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn most_contrasting(self, others: &[Self]) -> Self
|
fn most_contrasting(self, others: &[Self]) -> Self
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,52 @@ use crate::styles::{
|
||||||
Component, ComponentDefinition, ComponentName, Dimension, Global, NamedComponent,
|
Component, ComponentDefinition, ComponentName, Dimension, Global, NamedComponent,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
macro_rules! define_components {
|
||||||
|
($($widget:ident { $($(#$doc:tt)* $component:ident($type:ty, $name:expr, $($default:tt)*))* })*) => {$($(
|
||||||
|
$(#$doc)*
|
||||||
|
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||||
|
pub struct $component;
|
||||||
|
|
||||||
|
const _: () = {
|
||||||
|
use $crate::styles::{ComponentDefinition, ComponentName, NamedComponent};
|
||||||
|
impl NamedComponent for $component {
|
||||||
|
fn name(&self) -> Cow<'_, ComponentName> {
|
||||||
|
Cow::Owned(ComponentName::named::<Button>($name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentDefinition for $component {
|
||||||
|
type ComponentType = $type;
|
||||||
|
|
||||||
|
define_components!($type, $($default)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
)*)*};
|
||||||
|
($type:ty, . $($path:tt)*) => {
|
||||||
|
define_components!($type, |context| context.theme().$($path)*);
|
||||||
|
};
|
||||||
|
($type:ty, |$context:ident| $($expr:tt)*) => {
|
||||||
|
fn default_value(&self, $context: &WidgetContext<'_, '_>) -> Color {
|
||||||
|
$($expr)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($type:ty, @$path:path) => {
|
||||||
|
define_components!($type, |context| context.query_style(&$path));
|
||||||
|
};
|
||||||
|
($type:ty, contrasting!($bg:ident, $($fg:ident),+ $(,)?)) => {
|
||||||
|
define_components!($type, |context| {
|
||||||
|
let styles = context.query_styles(&[&$bg, $(&$fg),*]);
|
||||||
|
styles.get(&$bg, context).most_contrasting(&[
|
||||||
|
$(styles.get(&$fg, context)),+
|
||||||
|
])
|
||||||
|
});
|
||||||
|
};
|
||||||
|
($type:ty, $($expr:tt)*) => {
|
||||||
|
define_components!($type, |_context| $($expr)*);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// The [`Dimension`] to use as the size to render text.
|
/// The [`Dimension`] to use as the size to render text.
|
||||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||||
pub struct TextSize;
|
pub struct TextSize;
|
||||||
|
|
|
||||||
|
|
@ -572,6 +572,12 @@ pub trait MakeWidget: Sized {
|
||||||
fn horizontal_scroll(self) -> Scroll {
|
fn horizontal_scroll(self) -> Scroll {
|
||||||
Scroll::horizontal(self)
|
Scroll::horizontal(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a [`WidgetRef`] for use as child widget.
|
||||||
|
#[must_use]
|
||||||
|
fn widget_ref(self) -> WidgetRef {
|
||||||
|
WidgetRef::new(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type that can create a [`WidgetInstance`] with a preallocated
|
/// A type that can create a [`WidgetInstance`] with a preallocated
|
||||||
|
|
|
||||||
|
|
@ -4,50 +4,48 @@ use std::panic::UnwindSafe;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use kludgine::app::winit::event::{DeviceId, ElementState, KeyEvent, MouseButton};
|
use kludgine::app::winit::event::{DeviceId, ElementState, KeyEvent, MouseButton};
|
||||||
use kludgine::figures::units::{Lp, Px, UPx};
|
use kludgine::figures::units::{Px, UPx};
|
||||||
use kludgine::figures::{IntoUnsigned, Point, Rect, ScreenScale, Size};
|
use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, ScreenScale, Size};
|
||||||
use kludgine::shapes::StrokeOptions;
|
|
||||||
use kludgine::text::Text;
|
|
||||||
use kludgine::Color;
|
use kludgine::Color;
|
||||||
|
|
||||||
use crate::animation::{AnimationHandle, AnimationTarget, LinearInterpolate, Spawn};
|
use crate::animation::{AnimationHandle, AnimationTarget, Spawn};
|
||||||
use crate::context::{EventContext, GraphicsContext, LayoutContext, WidgetContext};
|
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext};
|
||||||
use crate::names::Name;
|
use crate::names::Name;
|
||||||
use crate::styles::components::{
|
use crate::styles::components::{
|
||||||
AutoFocusableControls, DisabledOutlineColor, Easing, IntrinsicPadding, OutlineColor,
|
AutoFocusableControls, Easing, IntrinsicPadding, SurfaceColor, TextColor,
|
||||||
SurfaceColor, TextColor,
|
|
||||||
};
|
};
|
||||||
use crate::styles::{ColorExt, ComponentDefinition, ComponentGroup, ComponentName, NamedComponent};
|
use crate::styles::{ColorExt, ComponentGroup, Styles};
|
||||||
use crate::utils::ModifiersExt;
|
use crate::utils::ModifiersExt;
|
||||||
use crate::value::{Dynamic, IntoValue, Value};
|
use crate::value::{Dynamic, IntoValue, Value};
|
||||||
use crate::widget::{Callback, EventHandling, Widget, HANDLED, IGNORED};
|
use crate::widget::{Callback, EventHandling, MakeWidget, Widget, WidgetRef, HANDLED, IGNORED};
|
||||||
|
|
||||||
/// A clickable button.
|
/// A clickable button.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Button {
|
pub struct Button {
|
||||||
/// The label to display on the button.
|
/// The label to display on the button.
|
||||||
pub label: Value<String>,
|
pub content: WidgetRef,
|
||||||
/// The callback that is invoked when the button is clicked.
|
/// The callback that is invoked when the button is clicked.
|
||||||
pub on_click: Option<Callback<()>>,
|
pub on_click: Option<Callback<()>>,
|
||||||
/// The enabled state of the button.
|
/// The enabled state of the button.
|
||||||
pub enabled: Value<bool>,
|
pub enabled: Value<bool>,
|
||||||
currently_enabled: bool,
|
currently_enabled: bool,
|
||||||
buttons_pressed: usize,
|
buttons_pressed: usize,
|
||||||
colors: Option<Dynamic<Colors>>,
|
background_color: Option<Dynamic<Color>>,
|
||||||
|
text_color: Option<Dynamic<Color>>,
|
||||||
color_animation: AnimationHandle,
|
color_animation: AnimationHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Button {
|
impl Button {
|
||||||
/// Returns a new button with the provided label.
|
/// Returns a new button with the provided label.
|
||||||
pub fn new(label: impl IntoValue<String>) -> Self {
|
pub fn new(content: impl MakeWidget) -> Self {
|
||||||
Self {
|
Self {
|
||||||
label: label.into_value(),
|
content: content.widget_ref(),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
enabled: Value::Constant(true),
|
enabled: Value::Constant(true),
|
||||||
currently_enabled: true,
|
currently_enabled: true,
|
||||||
buttons_pressed: 0,
|
buttons_pressed: 0,
|
||||||
colors: None,
|
background_color: None,
|
||||||
|
text_color: None,
|
||||||
color_animation: AnimationHandle::default(),
|
color_animation: AnimationHandle::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -86,109 +84,75 @@ impl Button {
|
||||||
&ButtonBackground,
|
&ButtonBackground,
|
||||||
&ButtonHoverBackground,
|
&ButtonHoverBackground,
|
||||||
&ButtonDisabledBackground,
|
&ButtonDisabledBackground,
|
||||||
|
&ButtonActiveForeground,
|
||||||
|
&ButtonForeground,
|
||||||
|
&ButtonHoverForeground,
|
||||||
|
&ButtonDisabledForeground,
|
||||||
&Easing,
|
&Easing,
|
||||||
&TextColor,
|
|
||||||
&SurfaceColor,
|
|
||||||
&OutlineColor,
|
|
||||||
&DisabledOutlineColor,
|
|
||||||
]);
|
]);
|
||||||
let text_color = styles.get(&TextColor, context);
|
|
||||||
let surface_color = styles.get(&SurfaceColor, context);
|
let (background_color, text_color) = match () {
|
||||||
let outline_color = styles.get(&OutlineColor, context);
|
() if !self.enabled.get() => (
|
||||||
let (background, outline, text_color, surface_color) = if !self.enabled.get() {
|
|
||||||
(
|
|
||||||
styles.get(&ButtonDisabledBackground, context),
|
styles.get(&ButtonDisabledBackground, context),
|
||||||
styles.get(&DisabledOutlineColor, context),
|
styles.get(&ButtonDisabledForeground, context),
|
||||||
text_color,
|
),
|
||||||
surface_color,
|
// TODO this probably should use actual style.
|
||||||
)
|
() if context.is_default() => (
|
||||||
} else if context.is_default() {
|
|
||||||
// TODO this probably should be de-prioritized if ButtonBackground is explicitly set.
|
|
||||||
(
|
|
||||||
context.theme().primary.color,
|
|
||||||
context.theme().primary.color,
|
context.theme().primary.color,
|
||||||
context.theme().primary.on_color,
|
context.theme().primary.on_color,
|
||||||
context.theme().primary.color,
|
),
|
||||||
)
|
() if context.active() => (
|
||||||
} else if context.active() {
|
|
||||||
(
|
|
||||||
styles.get(&ButtonActiveBackground, context),
|
styles.get(&ButtonActiveBackground, context),
|
||||||
outline_color,
|
styles.get(&ButtonActiveForeground, context),
|
||||||
text_color,
|
),
|
||||||
surface_color,
|
() if context.hovered() => (
|
||||||
)
|
|
||||||
} else if context.hovered() {
|
|
||||||
(
|
|
||||||
styles.get(&ButtonHoverBackground, context),
|
styles.get(&ButtonHoverBackground, context),
|
||||||
outline_color,
|
styles.get(&ButtonHoverForeground, context),
|
||||||
text_color,
|
),
|
||||||
surface_color,
|
() => (
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
styles.get(&ButtonBackground, context),
|
styles.get(&ButtonBackground, context),
|
||||||
outline_color,
|
styles.get(&ButtonForeground, context),
|
||||||
text_color,
|
),
|
||||||
surface_color,
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let text = background.most_contrasting(&[text_color, surface_color]);
|
match (immediate, &self.background_color, &self.text_color) {
|
||||||
|
(false, Some(bg), Some(text)) => {
|
||||||
let new_colors = Colors {
|
self.color_animation = (
|
||||||
background,
|
bg.transition_to(background_color),
|
||||||
text,
|
text.transition_to(text_color),
|
||||||
outline,
|
)
|
||||||
};
|
|
||||||
|
|
||||||
match (immediate, &self.colors) {
|
|
||||||
(false, Some(colors)) => {
|
|
||||||
self.color_animation = colors
|
|
||||||
.transition_to(new_colors)
|
|
||||||
.over(Duration::from_millis(150))
|
.over(Duration::from_millis(150))
|
||||||
.with_easing(styles.get(&Easing, context))
|
.with_easing(styles.get(&Easing, context))
|
||||||
.spawn();
|
.spawn();
|
||||||
}
|
}
|
||||||
(true, Some(colors)) => {
|
(true, Some(bg), Some(text)) => {
|
||||||
colors.update(new_colors);
|
bg.update(background_color);
|
||||||
|
text.update(text_color);
|
||||||
self.color_animation.clear();
|
self.color_animation.clear();
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
self.colors = Some(Dynamic::new(new_colors));
|
self.background_color = Some(Dynamic::new(background_color));
|
||||||
|
let text_color = Dynamic::new(text_color);
|
||||||
|
self.text_color = Some(text_color.clone());
|
||||||
|
context.attach_styles(Styles::new().with(&TextColor, text_color));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_colors(&mut self, context: &WidgetContext<'_, '_>) -> Colors {
|
fn current_background(&mut self, context: &WidgetContext<'_, '_>) -> Color {
|
||||||
if self.colors.is_none() {
|
if self.background_color.is_none() {
|
||||||
self.update_colors(context, false);
|
self.update_colors(context, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let colors = self.colors.as_ref().expect("always initialized");
|
let background_color = self.background_color.as_ref().expect("always initialized");
|
||||||
context.redraw_when_changed(colors);
|
context.redraw_when_changed(background_color);
|
||||||
colors.get()
|
background_color.get()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
|
||||||
struct Colors {
|
|
||||||
background: Color,
|
|
||||||
text: Color,
|
|
||||||
outline: Color,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LinearInterpolate for Colors {
|
|
||||||
fn lerp(&self, target: &Self, percent: f32) -> Self {
|
|
||||||
Self {
|
|
||||||
background: self.background.lerp(&target.background, percent),
|
|
||||||
text: self.text.lerp(&target.text, percent),
|
|
||||||
outline: self.outline.lerp(&target.outline, percent),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Button {
|
impl Widget for Button {
|
||||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||||
|
#![allow(clippy::similar_names)]
|
||||||
let enabled = self.enabled.get();
|
let enabled = self.enabled.get();
|
||||||
// TODO This seems ugly. It needs context, so it can't be moved into the
|
// TODO This seems ugly. It needs context, so it can't be moved into the
|
||||||
// dynamic system.
|
// dynamic system.
|
||||||
|
|
@ -197,30 +161,17 @@ impl Widget for Button {
|
||||||
self.currently_enabled = enabled;
|
self.currently_enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
let size = context.gfx.region().size;
|
|
||||||
let center = Point::from(size) / 2;
|
|
||||||
self.label.redraw_when_changed(context);
|
|
||||||
self.enabled.redraw_when_changed(context);
|
self.enabled.redraw_when_changed(context);
|
||||||
|
|
||||||
let colors = self.current_colors(context);
|
let background_color = self.current_background(context);
|
||||||
context.gfx.fill(colors.background);
|
context.gfx.fill(background_color);
|
||||||
|
|
||||||
if context.focused() {
|
if context.focused() {
|
||||||
context.draw_focus_ring();
|
context.draw_focus_ring();
|
||||||
} else {
|
|
||||||
context.stroke_outline::<Lp>(colors.outline, StrokeOptions::default());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.label.map(|label| {
|
let content = self.content.mounted(&mut context.as_event_context());
|
||||||
context.gfx.draw_text(
|
context.for_other(&content).redraw();
|
||||||
Text::new(label, colors.text)
|
|
||||||
.origin(kludgine::text::TextOrigin::Center)
|
|
||||||
.wrap_at(size.width),
|
|
||||||
center,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hit_test(&mut self, _location: Point<Px>, _context: &mut EventContext<'_, '_>) -> bool {
|
fn hit_test(&mut self, _location: Point<Px>, _context: &mut EventContext<'_, '_>) -> bool {
|
||||||
|
|
@ -295,17 +246,13 @@ impl Widget for Button {
|
||||||
.query_style(&IntrinsicPadding)
|
.query_style(&IntrinsicPadding)
|
||||||
.into_px(context.gfx.scale())
|
.into_px(context.gfx.scale())
|
||||||
.into_unsigned();
|
.into_unsigned();
|
||||||
let width = available_space.width.max().try_into().unwrap_or(Px::MAX);
|
let mounted = self.content.mounted(&mut context.as_event_context());
|
||||||
self.label.map(|label| {
|
let size = context.for_other(&mounted).layout(available_space);
|
||||||
let measured = context
|
context.set_child_layout(
|
||||||
.gfx
|
&mounted,
|
||||||
.measure_text::<Px>(Text::from(label).wrap_at(width));
|
Rect::new(Point::new(padding, padding), size).into_signed(),
|
||||||
|
);
|
||||||
let mut size = measured.size.into_unsigned();
|
size + padding * 2
|
||||||
size.width += padding * 2;
|
|
||||||
size.height = size.height.max(measured.line_height.into_unsigned()) + padding * 2;
|
|
||||||
size
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keyboard_input(
|
fn keyboard_input(
|
||||||
|
|
@ -373,76 +320,27 @@ impl ComponentGroup for Button {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The background color of the button.
|
define_components! {
|
||||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
Button {
|
||||||
pub struct ButtonBackground;
|
/// The background color of the button.
|
||||||
|
ButtonBackground(Color, "background_color", .surface.highest_container) // TODO highest_container seems wrong, but it's what material uses. Perhaps we should add another color so that buttons don't blend with the highest container level.
|
||||||
impl NamedComponent for ButtonBackground {
|
/// The background color of the button when it is active (depressed).
|
||||||
fn name(&self) -> Cow<'_, ComponentName> {
|
ButtonActiveBackground(Color, "active_background_color", .surface.color)
|
||||||
Cow::Owned(ComponentName::named::<Button>("background_color"))
|
/// The background color of the button when the mouse cursor is hovering over
|
||||||
}
|
/// it.
|
||||||
}
|
ButtonHoverBackground(Color, "hover_background_color", .surface.bright_color)
|
||||||
|
/// The background color of the button when the mouse cursor is hovering over
|
||||||
impl ComponentDefinition for ButtonBackground {
|
/// it.
|
||||||
type ComponentType = Color;
|
ButtonDisabledBackground(Color, "disabled_background_color", .surface.dim_color)
|
||||||
|
/// The foreground color of the button.
|
||||||
fn default_value(&self, context: &WidgetContext<'_, '_>) -> Color {
|
ButtonForeground(Color, "foreground_color", contrasting!(ButtonBackground, TextColor, SurfaceColor))
|
||||||
context.theme().surface.color
|
/// The foreground color of the button when it is active (depressed).
|
||||||
}
|
ButtonActiveForeground(Color, "active_foreground_color", contrasting!(ButtonActiveBackground, ButtonForeground, TextColor, SurfaceColor))
|
||||||
}
|
/// The foreground color of the button when the mouse cursor is hovering over
|
||||||
|
/// it.
|
||||||
/// The background color of the button when it is active (depressed).
|
ButtonHoverForeground(Color, "hover_foreground_color", contrasting!(ButtonHoverBackground, ButtonForeground, TextColor, SurfaceColor))
|
||||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
/// The foreground color of the button when the mouse cursor is hovering over
|
||||||
pub struct ButtonActiveBackground;
|
/// it.
|
||||||
|
ButtonDisabledForeground(Color, "disabled_foreground_color", contrasting!(ButtonDisabledBackground, ButtonForeground, TextColor, SurfaceColor))
|
||||||
impl NamedComponent for ButtonActiveBackground {
|
|
||||||
fn name(&self) -> Cow<'_, ComponentName> {
|
|
||||||
Cow::Owned(ComponentName::named::<Button>("active_background_color"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ComponentDefinition for ButtonActiveBackground {
|
|
||||||
type ComponentType = Color;
|
|
||||||
|
|
||||||
fn default_value(&self, context: &WidgetContext<'_, '_>) -> Color {
|
|
||||||
context.theme().surface.dim_color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The background color of the button when the mouse cursor is hovering over
|
|
||||||
/// it.
|
|
||||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
|
||||||
pub struct ButtonHoverBackground;
|
|
||||||
|
|
||||||
impl NamedComponent for ButtonHoverBackground {
|
|
||||||
fn name(&self) -> Cow<'_, ComponentName> {
|
|
||||||
Cow::Owned(ComponentName::named::<Button>("hover_background_color"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ComponentDefinition for ButtonHoverBackground {
|
|
||||||
type ComponentType = Color;
|
|
||||||
|
|
||||||
fn default_value(&self, context: &WidgetContext<'_, '_>) -> Color {
|
|
||||||
context.theme().surface.bright_color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The background color of the button when the mouse cursor is hovering over
|
|
||||||
/// it.
|
|
||||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
|
||||||
pub struct ButtonDisabledBackground;
|
|
||||||
|
|
||||||
impl NamedComponent for ButtonDisabledBackground {
|
|
||||||
fn name(&self) -> Cow<'_, ComponentName> {
|
|
||||||
Cow::Owned(ComponentName::named::<Button>("disabled_background_color"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ComponentDefinition for ButtonDisabledBackground {
|
|
||||||
type ComponentType = Color;
|
|
||||||
|
|
||||||
fn default_value(&self, context: &WidgetContext<'_, '_>) -> Color {
|
|
||||||
context.theme().surface.dim_color
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
//! A read-only text widget.
|
//! A read-only text widget.
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use kludgine::figures::units::{Px, UPx};
|
use kludgine::figures::units::{Px, UPx};
|
||||||
use kludgine::figures::{IntoUnsigned, Point, ScreenScale, Size};
|
use kludgine::figures::{IntoUnsigned, Point, ScreenScale, Size};
|
||||||
use kludgine::text::{MeasuredText, Text, TextOrigin};
|
use kludgine::text::{MeasuredText, Text, TextOrigin};
|
||||||
|
use kludgine::Color;
|
||||||
|
|
||||||
use crate::context::{GraphicsContext, LayoutContext};
|
use crate::context::{GraphicsContext, LayoutContext, WidgetContext};
|
||||||
use crate::styles::components::{IntrinsicPadding, TextColor};
|
use crate::styles::components::{IntrinsicPadding, TextColor};
|
||||||
use crate::styles::ComponentGroup;
|
use crate::styles::{ComponentDefinition, ComponentGroup, ComponentName, NamedComponent};
|
||||||
use crate::value::{IntoValue, Value};
|
use crate::value::{Dynamic, IntoValue, Value};
|
||||||
use crate::widget::Widget;
|
use crate::widget::{MakeWidget, Widget, WidgetInstance};
|
||||||
use crate::{ConstraintLimit, Name};
|
use crate::{ConstraintLimit, Name};
|
||||||
|
|
||||||
/// A read-only text widget.
|
/// A read-only text widget.
|
||||||
|
|
@ -35,13 +38,17 @@ impl Widget for Label {
|
||||||
|
|
||||||
let size = context.gfx.region().size;
|
let size = context.gfx.region().size;
|
||||||
let center = Point::from(size) / 2;
|
let center = Point::from(size) / 2;
|
||||||
|
let styles = context.query_styles(&[&TextColor, &LabelBackground]);
|
||||||
|
|
||||||
|
let background = styles.get(&LabelBackground, context);
|
||||||
|
context.gfx.fill(background);
|
||||||
|
|
||||||
if let Some(measured) = &self.prepared_text {
|
if let Some(measured) = &self.prepared_text {
|
||||||
context
|
context
|
||||||
.gfx
|
.gfx
|
||||||
.draw_measured_text(measured, TextOrigin::Center, center, None, None);
|
.draw_measured_text(measured, TextOrigin::Center, center, None, None);
|
||||||
} else {
|
} else {
|
||||||
let text_color = context.query_style(&TextColor);
|
let text_color = styles.get(&TextColor, context);
|
||||||
self.text.map(|contents| {
|
self.text.map(|contents| {
|
||||||
context.gfx.draw_text(
|
context.gfx.draw_text(
|
||||||
Text::new(contents, text_color)
|
Text::new(contents, text_color)
|
||||||
|
|
@ -84,3 +91,33 @@ impl ComponentGroup for Label {
|
||||||
Name::new("Label")
|
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 {
|
||||||
|
fn make_widget(self) -> WidgetInstance {
|
||||||
|
Label::new(self).make_widget()
|
||||||
|
}
|
||||||
|
})*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_make_widget!(&'_ str, String, Value<String>, Dynamic<String>);
|
||||||
|
|
|
||||||
|
|
@ -406,7 +406,7 @@ impl ComponentDefinition for InactiveTrackColor {
|
||||||
type ComponentType = Color;
|
type ComponentType = Color;
|
||||||
|
|
||||||
fn default_value(&self, context: &WidgetContext<'_, '_>) -> Self::ComponentType {
|
fn default_value(&self, context: &WidgetContext<'_, '_>) -> Self::ComponentType {
|
||||||
context.theme().surface.outline
|
context.theme().surface.highest_container // TODO this is the same as ButtonBackground. This should be abstracted into its own component both can depend on.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue