mirror of
https://github.com/danbulant/cushy
synced 2026-07-05 11:10:34 +00:00
Container, query_parent_style
This commit is contained in:
parent
2a50bb32d4
commit
96d407ddc2
17 changed files with 702 additions and 83 deletions
45
examples/containers.rs
Normal file
45
examples/containers.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
use gooey::value::Dynamic;
|
||||||
|
use gooey::widget::{MakeWidget, WidgetInstance};
|
||||||
|
use gooey::widgets::{Button, Label};
|
||||||
|
use gooey::window::ThemeMode;
|
||||||
|
use gooey::Run;
|
||||||
|
|
||||||
|
fn main() -> gooey::Result {
|
||||||
|
let theme_mode = Dynamic::default();
|
||||||
|
set_of_containers(1, theme_mode.clone())
|
||||||
|
.into_window()
|
||||||
|
.with_theme_mode(theme_mode)
|
||||||
|
.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_of_containers(repeat: usize, theme_mode: Dynamic<ThemeMode>) -> WidgetInstance {
|
||||||
|
let inner = if let Some(remaining_iters) = repeat.checked_sub(1) {
|
||||||
|
set_of_containers(remaining_iters, theme_mode)
|
||||||
|
} else {
|
||||||
|
Button::new("Toggle Theme Mode")
|
||||||
|
.on_click(move |_| {
|
||||||
|
theme_mode.map_mut(|mode| mode.toggle());
|
||||||
|
})
|
||||||
|
.make_widget()
|
||||||
|
};
|
||||||
|
Label::new("Lowest")
|
||||||
|
.and(
|
||||||
|
Label::new("Low")
|
||||||
|
.and(
|
||||||
|
Label::new("Mid")
|
||||||
|
.and(
|
||||||
|
Label::new("High")
|
||||||
|
.and(Label::new("Highest").and(inner).into_rows().contain())
|
||||||
|
.into_rows()
|
||||||
|
.contain(),
|
||||||
|
)
|
||||||
|
.into_rows()
|
||||||
|
.contain(),
|
||||||
|
)
|
||||||
|
.into_rows()
|
||||||
|
.contain(),
|
||||||
|
)
|
||||||
|
.into_rows()
|
||||||
|
.contain()
|
||||||
|
.make_widget()
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use gooey::value::Dynamic;
|
use gooey::value::Dynamic;
|
||||||
use gooey::widget::{MakeWidget, HANDLED, IGNORED};
|
use gooey::widget::{MakeWidget, HANDLED, IGNORED};
|
||||||
use gooey::widgets::{Input, Label, Space, Stack};
|
use gooey::widgets::{Input, Label, Space};
|
||||||
use gooey::Run;
|
use gooey::Run;
|
||||||
use kludgine::app::winit::event::ElementState;
|
use kludgine::app::winit::event::ElementState;
|
||||||
use kludgine::app::winit::keyboard::Key;
|
use kludgine::app::winit::keyboard::Key;
|
||||||
|
|
@ -10,13 +10,11 @@ fn main() -> gooey::Result {
|
||||||
let chat_log = Dynamic::new("Chat log goes here.\n".repeat(100));
|
let chat_log = Dynamic::new("Chat log goes here.\n".repeat(100));
|
||||||
let chat_message = Dynamic::new(String::new());
|
let chat_message = Dynamic::new(String::new());
|
||||||
|
|
||||||
Stack::rows(
|
Label::new(chat_log.clone())
|
||||||
Stack::columns(
|
.vertical_scroll()
|
||||||
Label::new(chat_log.clone())
|
.expand()
|
||||||
.vertical_scroll()
|
.and(Space::colored(Color::RED).expand_weighted(2))
|
||||||
.expand()
|
.into_columns()
|
||||||
.and(Space::colored(Color::RED).expand_weighted(2)),
|
|
||||||
)
|
|
||||||
.expand()
|
.expand()
|
||||||
.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) {
|
||||||
|
|
@ -30,8 +28,8 @@ fn main() -> gooey::Result {
|
||||||
}
|
}
|
||||||
_ => IGNORED,
|
_ => IGNORED,
|
||||||
}
|
}
|
||||||
})),
|
}))
|
||||||
)
|
.into_rows()
|
||||||
.expand()
|
.expand()
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use gooey::widget::MakeWidget;
|
||||||
use gooey::widgets::{Input, Label, Scroll, Slider, Stack, Themed};
|
use gooey::widgets::{Input, Label, Scroll, Slider, Stack, Themed};
|
||||||
use gooey::window::ThemeMode;
|
use gooey::window::ThemeMode;
|
||||||
use gooey::Run;
|
use gooey::Run;
|
||||||
|
use kludgine::figures::units::Lp;
|
||||||
use kludgine::Color;
|
use kludgine::Color;
|
||||||
|
|
||||||
const PRIMARY_HUE: f32 = 240.;
|
const PRIMARY_HUE: f32 = 240.;
|
||||||
|
|
@ -67,6 +68,7 @@ fn main() -> gooey::Result {
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.pad_by(Lp::points(16))
|
||||||
.expand()
|
.expand()
|
||||||
.into_window()
|
.into_window()
|
||||||
.with_theme_mode(theme_mode)
|
.with_theme_mode(theme_mode)
|
||||||
|
|
|
||||||
|
|
@ -924,7 +924,25 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
||||||
pub fn query_styles(&self, query: &[&dyn ComponentDefaultvalue]) -> Styles {
|
pub fn query_styles(&self, query: &[&dyn ComponentDefaultvalue]) -> Styles {
|
||||||
self.current_node
|
self.current_node
|
||||||
.tree
|
.tree
|
||||||
.query_styles(&self.current_node, query, self)
|
.query_styles(&self.current_node, query, false, self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Queries the widget hierarchy for matching style components, starting
|
||||||
|
/// with this widget's parent.
|
||||||
|
///
|
||||||
|
/// This function traverses up the widget hierarchy looking for the
|
||||||
|
/// components being requested. The resulting styles will contain the values
|
||||||
|
/// from the closest matches in the widget hierarchy.
|
||||||
|
///
|
||||||
|
/// For style components to be found, they must have previously been
|
||||||
|
/// [attached](Self::attach_styles). The [`Style`](crate::widgets::Style)
|
||||||
|
/// widget is provided as a convenient way to attach styles into the widget
|
||||||
|
/// hierarchy.
|
||||||
|
#[must_use]
|
||||||
|
pub fn query_parent_styles(&self, query: &[&dyn ComponentDefaultvalue]) -> Styles {
|
||||||
|
self.current_node
|
||||||
|
.tree
|
||||||
|
.query_styles(&self.current_node, query, true, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queries the widget hierarchy for a single style component.
|
/// Queries the widget hierarchy for a single style component.
|
||||||
|
|
@ -940,7 +958,19 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
||||||
) -> Component::ComponentType {
|
) -> Component::ComponentType {
|
||||||
self.current_node
|
self.current_node
|
||||||
.tree
|
.tree
|
||||||
.query_style(&self.current_node, query, self)
|
.query_style(&self.current_node, query, false, self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Queries the widget hierarchy for a single style component, starting with
|
||||||
|
/// this widget's parent.
|
||||||
|
#[must_use]
|
||||||
|
pub fn query_parent_style<Component: ComponentDefinition>(
|
||||||
|
&self,
|
||||||
|
query: &Component,
|
||||||
|
) -> Component::ComponentType {
|
||||||
|
self.current_node
|
||||||
|
.tree
|
||||||
|
.query_style(&self.current_node, query, true, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle(&self) -> WindowHandle {
|
pub(crate) fn handle(&self) -> WindowHandle {
|
||||||
|
|
|
||||||
117
src/styles.rs
117
src/styles.rs
|
|
@ -197,6 +197,9 @@ pub enum Component {
|
||||||
VisualOrder(VisualOrder),
|
VisualOrder(VisualOrder),
|
||||||
/// A description of what widgets should be focusable.
|
/// A description of what widgets should be focusable.
|
||||||
FocusableWidgets(FocusableWidgets),
|
FocusableWidgets(FocusableWidgets),
|
||||||
|
/// A description of the depth of a
|
||||||
|
/// [`Container`](crate::widgets::Container).
|
||||||
|
ContainerLevel(ContainerLevel),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Color> for Component {
|
impl From<Color> for Component {
|
||||||
|
|
@ -705,21 +708,48 @@ impl NamedComponent for Cow<'_, ComponentName> {
|
||||||
pub struct Edges<T = FlexibleDimension> {
|
pub struct Edges<T = FlexibleDimension> {
|
||||||
/// The left edge
|
/// The left edge
|
||||||
pub left: T,
|
pub left: T,
|
||||||
/// The right edge
|
|
||||||
pub right: T,
|
|
||||||
/// The top edge
|
/// The top edge
|
||||||
pub top: T,
|
pub top: T,
|
||||||
|
/// The right edge
|
||||||
|
pub right: T,
|
||||||
/// The bottom edge
|
/// The bottom edge
|
||||||
pub bottom: T,
|
pub bottom: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Edges<T> {
|
impl<T> Edges<T> {
|
||||||
/// Returns the sum of the parts as a [`Size`].
|
/// Returns the sum of the parts as a [`Size`].
|
||||||
pub fn size(&self) -> Size<T>
|
pub fn size(self) -> Size<T>
|
||||||
where
|
where
|
||||||
T: Add<Output = T> + Copy,
|
T: Add<Output = T> + Copy,
|
||||||
{
|
{
|
||||||
Size::new(self.left + self.right, self.top + self.bottom)
|
Size::new(self.width(), self.height())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new set of edges produced by calling `map` with each of the
|
||||||
|
/// edges.
|
||||||
|
pub fn map<U>(self, mut map: impl FnMut(T) -> U) -> Edges<U> {
|
||||||
|
Edges {
|
||||||
|
left: map(self.left),
|
||||||
|
top: map(self.top),
|
||||||
|
right: map(self.right),
|
||||||
|
bottom: map(self.bottom),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the sum of the left and right edges.
|
||||||
|
pub fn width(self) -> T
|
||||||
|
where
|
||||||
|
T: Add<Output = T>,
|
||||||
|
{
|
||||||
|
self.left + self.right
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the sum of the top and bottom edges.
|
||||||
|
pub fn height(self) -> T
|
||||||
|
where
|
||||||
|
T: Add<Output = T>,
|
||||||
|
{
|
||||||
|
self.top + self.bottom
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -821,12 +851,42 @@ impl IntoValue<Edges<FlexibleDimension>> for FlexibleDimension {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IntoValue<Edges<FlexibleDimension>> for Dimension {
|
||||||
|
fn into_value(self) -> Value<Edges<FlexibleDimension>> {
|
||||||
|
FlexibleDimension::Dimension(self).into_value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoValue<Edges<FlexibleDimension>> for Px {
|
||||||
|
fn into_value(self) -> Value<Edges<FlexibleDimension>> {
|
||||||
|
Dimension::from(self).into_value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoValue<Edges<FlexibleDimension>> for Lp {
|
||||||
|
fn into_value(self) -> Value<Edges<FlexibleDimension>> {
|
||||||
|
Dimension::from(self).into_value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IntoValue<Edges<Dimension>> for Dimension {
|
impl IntoValue<Edges<Dimension>> for Dimension {
|
||||||
fn into_value(self) -> Value<Edges<Dimension>> {
|
fn into_value(self) -> Value<Edges<Dimension>> {
|
||||||
Value::Constant(Edges::from(self))
|
Value::Constant(Edges::from(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IntoValue<Edges<Dimension>> for Px {
|
||||||
|
fn into_value(self) -> Value<Edges<Dimension>> {
|
||||||
|
Dimension::from(self).into_value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoValue<Edges<Dimension>> for Lp {
|
||||||
|
fn into_value(self) -> Value<Edges<Dimension>> {
|
||||||
|
Dimension::from(self).into_value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A set of light and dark [`Theme`]s.
|
/// A set of light and dark [`Theme`]s.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ThemePair {
|
pub struct ThemePair {
|
||||||
|
|
@ -1465,3 +1525,52 @@ impl TryFrom<Component> for FocusableWidgets {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A description of the level of depth a
|
||||||
|
/// [`Container`](crate::widgets::Container) is nested at.
|
||||||
|
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
|
pub enum ContainerLevel {
|
||||||
|
/// The lowest container level.
|
||||||
|
#[default]
|
||||||
|
Lowest,
|
||||||
|
/// The second lowest container level.
|
||||||
|
Low,
|
||||||
|
/// The mid-level container level.
|
||||||
|
Mid,
|
||||||
|
/// The second-highest container level.
|
||||||
|
High,
|
||||||
|
/// The highest container level.
|
||||||
|
Highest,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContainerLevel {
|
||||||
|
/// Returns the next container level, or None if already at the highet
|
||||||
|
/// level.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn next(self) -> Option<Self> {
|
||||||
|
match self {
|
||||||
|
Self::Lowest => Some(Self::Low),
|
||||||
|
Self::Low => Some(Self::Mid),
|
||||||
|
Self::Mid => Some(Self::High),
|
||||||
|
Self::High => Some(Self::Highest),
|
||||||
|
Self::Highest => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ContainerLevel> for Component {
|
||||||
|
fn from(value: ContainerLevel) -> Self {
|
||||||
|
Self::ContainerLevel(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Component> for ContainerLevel {
|
||||||
|
type Error = Component;
|
||||||
|
|
||||||
|
fn try_from(value: Component) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
Component::ContainerLevel(level) => Ok(level),
|
||||||
|
other => Err(other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,11 @@ use crate::styles::{Dimension, FocusableWidgets, VisualOrder};
|
||||||
/// /// This component whose default value is a color from the current theme.
|
/// /// This component whose default value is a color from the current theme.
|
||||||
/// ThemedComponent(Color, "themed_component", .primary.color)
|
/// ThemedComponent(Color, "themed_component", .primary.color)
|
||||||
/// /// This component is a color whose default value is the currently defined `TextColor`.
|
/// /// This component is a color whose default value is the currently defined `TextColor`.
|
||||||
/// DependentComponent(Color, "dependent_component", |context| context.query_style(&TextColor))
|
/// DependentComponent(Color, "dependent_component", @TextColor)
|
||||||
/// /// This component defaults to picking a contrasting color between `TextColor` and `SurfaceColor`
|
/// /// This component defaults to picking a contrasting color between `TextColor` and `SurfaceColor`
|
||||||
/// ContrastingColor(Color, "contrasting_color", contrasting!(ThemedComponent, TextColor, SurfaceColor))
|
/// ContrastingColor(Color, "contrasting_color", contrasting!(ThemedComponent, TextColor, SurfaceColor))
|
||||||
|
/// /// This component shows how to use a closure for nearly infinite flexibility in computing the default value.
|
||||||
|
/// ClosureDefaultComponent(Color, "closure_component", |context| context.query_style(&TextColor))
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -72,7 +74,7 @@ macro_rules! define_components {
|
||||||
($type:ty, contrasting!($bg:ident, $($fg:ident),+ $(,)?)) => {
|
($type:ty, contrasting!($bg:ident, $($fg:ident),+ $(,)?)) => {
|
||||||
define_components!($type, |context| {
|
define_components!($type, |context| {
|
||||||
use $crate::styles::ColorExt;
|
use $crate::styles::ColorExt;
|
||||||
let styles = context.query_styles(&[&$bg, $(&$fg),*]);
|
let styles = context.query_parent_styles(&[&$bg, $(&$fg),*]);
|
||||||
styles.get(&$bg, context).most_contrasting(&[
|
styles.get(&$bg, context).most_contrasting(&[
|
||||||
$(styles.get(&$fg, context)),+
|
$(styles.get(&$fg, context)),+
|
||||||
])
|
])
|
||||||
|
|
|
||||||
30
src/tree.rs
30
src/tree.rs
|
|
@ -317,24 +317,29 @@ impl Tree {
|
||||||
&self,
|
&self,
|
||||||
perspective: &ManagedWidget,
|
perspective: &ManagedWidget,
|
||||||
query: &[&dyn ComponentDefaultvalue],
|
query: &[&dyn ComponentDefaultvalue],
|
||||||
|
skip_current: bool,
|
||||||
context: &WidgetContext<'_, '_>,
|
context: &WidgetContext<'_, '_>,
|
||||||
) -> Styles {
|
) -> Styles {
|
||||||
self.data
|
self.data
|
||||||
.lock()
|
.lock()
|
||||||
.map_or_else(PoisonError::into_inner, |g| g)
|
.map_or_else(PoisonError::into_inner, |g| g)
|
||||||
.query_styles(perspective.id(), query, context)
|
.query_styles(perspective.id(), query, skip_current, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query_style<Component: ComponentDefinition>(
|
pub fn query_style<Component: ComponentDefinition>(
|
||||||
&self,
|
&self,
|
||||||
perspective: &ManagedWidget,
|
perspective: &ManagedWidget,
|
||||||
component: &Component,
|
component: &Component,
|
||||||
|
skip_self: bool,
|
||||||
context: &WidgetContext<'_, '_>,
|
context: &WidgetContext<'_, '_>,
|
||||||
) -> Component::ComponentType {
|
) -> Component::ComponentType {
|
||||||
self.data
|
let result = self
|
||||||
|
.data
|
||||||
.lock()
|
.lock()
|
||||||
.map_or_else(PoisonError::into_inner, |g| g)
|
.map_or_else(PoisonError::into_inner, |g| g)
|
||||||
.query_style(perspective.id(), component, context)
|
.query_style(perspective.id(), component, skip_self, context);
|
||||||
|
|
||||||
|
result.unwrap_or_else(|| component.default_value(context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -423,13 +428,17 @@ impl TreeData {
|
||||||
&self,
|
&self,
|
||||||
mut perspective: WidgetId,
|
mut perspective: WidgetId,
|
||||||
query: &[&dyn ComponentDefaultvalue],
|
query: &[&dyn ComponentDefaultvalue],
|
||||||
|
skip_self: bool,
|
||||||
context: &WidgetContext<'_, '_>,
|
context: &WidgetContext<'_, '_>,
|
||||||
) -> Styles {
|
) -> Styles {
|
||||||
let mut query = query.iter().map(|n| n.name()).collect::<Vec<_>>();
|
let mut query = query.iter().map(|n| n.name()).collect::<Vec<_>>();
|
||||||
let mut resolved = Styles::new();
|
let mut resolved = Styles::new();
|
||||||
|
let mut skip_next = skip_self;
|
||||||
while !query.is_empty() {
|
while !query.is_empty() {
|
||||||
let node = &self.nodes[&perspective];
|
let node = &self.nodes[&perspective];
|
||||||
if let Some(styles) = &node.styles {
|
if skip_next {
|
||||||
|
skip_next = false;
|
||||||
|
} else if let Some(styles) = &node.styles {
|
||||||
styles.map_tracked(context, |styles| {
|
styles.map_tracked(context, |styles| {
|
||||||
query.retain(|name| {
|
query.retain(|name| {
|
||||||
if let Some(component) = styles.get_named(name) {
|
if let Some(component) = styles.get_named(name) {
|
||||||
|
|
@ -451,12 +460,16 @@ impl TreeData {
|
||||||
&self,
|
&self,
|
||||||
mut perspective: WidgetId,
|
mut perspective: WidgetId,
|
||||||
query: &Component,
|
query: &Component,
|
||||||
|
skip_self: bool,
|
||||||
context: &WidgetContext<'_, '_>,
|
context: &WidgetContext<'_, '_>,
|
||||||
) -> Component::ComponentType {
|
) -> Option<Component::ComponentType> {
|
||||||
let name = query.name();
|
let name = query.name();
|
||||||
|
let mut skip_next = skip_self;
|
||||||
loop {
|
loop {
|
||||||
let node = &self.nodes[&perspective];
|
let node = &self.nodes[&perspective];
|
||||||
if let Some(styles) = &node.styles {
|
if skip_next {
|
||||||
|
skip_next = false;
|
||||||
|
} else if let Some(styles) = &node.styles {
|
||||||
match styles.map_tracked(context, |styles| {
|
match styles.map_tracked(context, |styles| {
|
||||||
if let Some(component) = styles.get_named(&name) {
|
if let Some(component) = styles.get_named(&name) {
|
||||||
let Ok(value) =
|
let Ok(value) =
|
||||||
|
|
@ -469,15 +482,16 @@ impl TreeData {
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}) {
|
}) {
|
||||||
Ok(Some(value)) => return value,
|
Ok(Some(value)) => return Some(value),
|
||||||
Ok(None) => {}
|
Ok(None) => {}
|
||||||
Err(()) => break,
|
Err(()) => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(parent) = node.parent else { break };
|
let Some(parent) = node.parent else { break };
|
||||||
perspective = parent;
|
perspective = parent;
|
||||||
}
|
}
|
||||||
query.default_value(context)
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
14
src/value.rs
14
src/value.rs
|
|
@ -954,6 +954,20 @@ impl<T> Value<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a new value that is updated using `U::from(T.clone())` each time
|
||||||
|
/// `self` is updated.
|
||||||
|
#[must_use]
|
||||||
|
pub fn map_each<R, F>(&self, mut map: F) -> Value<R>
|
||||||
|
where
|
||||||
|
F: for<'a> FnMut(&'a T) -> R + Send + 'static,
|
||||||
|
R: Send + 'static,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Value::Constant(value) => Value::Constant(map(value)),
|
||||||
|
Value::Dynamic(dynamic) => Value::Dynamic(dynamic.map_each(map)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a clone of the currently stored value.
|
/// Returns a clone of the currently stored value.
|
||||||
pub fn get(&self) -> T
|
pub fn get(&self) -> T
|
||||||
where
|
where
|
||||||
|
|
|
||||||
127
src/widget.rs
127
src/widget.rs
|
|
@ -13,12 +13,16 @@ use kludgine::app::winit::event::{
|
||||||
};
|
};
|
||||||
use kludgine::figures::units::{Px, UPx};
|
use kludgine::figures::units::{Px, UPx};
|
||||||
use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size};
|
use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size};
|
||||||
|
use kludgine::Color;
|
||||||
|
|
||||||
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
|
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext};
|
||||||
use crate::styles::{IntoComponentValue, NamedComponent, Styles, ThemePair, VisualOrder};
|
use crate::styles::{
|
||||||
|
ContainerLevel, Dimension, Edges, IntoComponentValue, NamedComponent, Styles, ThemePair,
|
||||||
|
VisualOrder,
|
||||||
|
};
|
||||||
use crate::tree::Tree;
|
use crate::tree::Tree;
|
||||||
use crate::value::{IntoValue, Value};
|
use crate::value::{IntoValue, Value};
|
||||||
use crate::widgets::{Align, Expand, Scroll, Style};
|
use crate::widgets::{Align, Container, Expand, Scroll, Stack, Style};
|
||||||
use crate::window::{RunningWindow, ThemeMode, Window, WindowBehavior};
|
use crate::window::{RunningWindow, ThemeMode, Window, WindowBehavior};
|
||||||
use crate::{ConstraintLimit, Run};
|
use crate::{ConstraintLimit, Run};
|
||||||
|
|
||||||
|
|
@ -173,6 +177,33 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The layout of a [wrapped](WrapperWidget) child widget.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct WrappedLayout {
|
||||||
|
/// The region the child widget occupies within its parent.
|
||||||
|
pub child: Rect<Px>,
|
||||||
|
/// The size the wrapper widget should report as.q
|
||||||
|
pub size: Size<UPx>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Rect<Px>> for WrappedLayout {
|
||||||
|
fn from(child: Rect<Px>) -> Self {
|
||||||
|
WrappedLayout {
|
||||||
|
child,
|
||||||
|
size: child.size.into_unsigned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Size<Px>> for WrappedLayout {
|
||||||
|
fn from(size: Size<Px>) -> Self {
|
||||||
|
WrappedLayout {
|
||||||
|
child: size.into(),
|
||||||
|
size: size.into_unsigned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A [`Widget`] that contains a single child.
|
/// A [`Widget`] that contains a single child.
|
||||||
pub trait WrapperWidget: Debug + Send + UnwindSafe + 'static {
|
pub trait WrapperWidget: Debug + Send + UnwindSafe + 'static {
|
||||||
/// Returns the child widget.
|
/// Returns the child widget.
|
||||||
|
|
@ -185,14 +216,48 @@ pub trait WrapperWidget: Debug + Send + UnwindSafe + 'static {
|
||||||
&mut self,
|
&mut self,
|
||||||
available_space: Size<ConstraintLimit>,
|
available_space: Size<ConstraintLimit>,
|
||||||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||||
) -> Rect<Px> {
|
) -> WrappedLayout {
|
||||||
let child = self.child_mut().mounted(&mut context.as_event_context());
|
let child = self.child_mut().mounted(&mut context.as_event_context());
|
||||||
|
|
||||||
context
|
let adjusted_space = self.adjust_child_constraint(available_space, context);
|
||||||
|
let size = context
|
||||||
.for_other(&child)
|
.for_other(&child)
|
||||||
.layout(available_space)
|
.layout(adjusted_space)
|
||||||
.into_signed()
|
.into_signed();
|
||||||
.into()
|
|
||||||
|
self.position_child(size, available_space, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the adjusted contraints to use when laying out the child.
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[must_use]
|
||||||
|
fn adjust_child_constraint(
|
||||||
|
&mut self,
|
||||||
|
available_space: Size<ConstraintLimit>,
|
||||||
|
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||||
|
) -> Size<ConstraintLimit> {
|
||||||
|
available_space
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the layout after positioning the child that occupies `size`.
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[must_use]
|
||||||
|
fn position_child(
|
||||||
|
&mut self,
|
||||||
|
size: Size<Px>,
|
||||||
|
available_space: Size<ConstraintLimit>,
|
||||||
|
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||||
|
) -> WrappedLayout {
|
||||||
|
size.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the background color to render behind the wrapped widget.
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[must_use]
|
||||||
|
fn background_color(&mut self, context: &WidgetContext<'_, '_>) -> Option<Color> {
|
||||||
|
// WidgetBackground is already filled, so we don't need to do anything
|
||||||
|
// else by default.
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The widget has been mounted into a parent widget.
|
/// The widget has been mounted into a parent widget.
|
||||||
|
|
@ -323,6 +388,11 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||||
|
let background_color = self.background_color(context);
|
||||||
|
if let Some(color) = background_color {
|
||||||
|
context.gfx.fill(color);
|
||||||
|
}
|
||||||
|
|
||||||
let child = self.child_mut().mounted(&mut context.as_event_context());
|
let child = self.child_mut().mounted(&mut context.as_event_context());
|
||||||
context.for_other(&child).redraw();
|
context.for_other(&child).redraw();
|
||||||
}
|
}
|
||||||
|
|
@ -335,8 +405,8 @@ where
|
||||||
let child = self.child_mut().mounted(&mut context.as_event_context());
|
let child = self.child_mut().mounted(&mut context.as_event_context());
|
||||||
|
|
||||||
let layout = self.layout_child(available_space, context);
|
let layout = self.layout_child(available_space, context);
|
||||||
context.set_child_layout(&child, layout);
|
context.set_child_layout(&child, layout.child);
|
||||||
layout.size.into_unsigned()
|
layout.size
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mounted(&mut self, context: &mut EventContext<'_, '_>) {
|
fn mounted(&mut self, context: &mut EventContext<'_, '_>) {
|
||||||
|
|
@ -577,6 +647,31 @@ pub trait MakeWidget: Sized {
|
||||||
fn widget_ref(self) -> WidgetRef {
|
fn widget_ref(self) -> WidgetRef {
|
||||||
WidgetRef::new(self)
|
WidgetRef::new(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wraps `self` in a [`Container`].
|
||||||
|
fn contain(self) -> Container {
|
||||||
|
Container::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wraps `self` in a [`Container`] with the specified level.
|
||||||
|
fn contain_level(self, level: impl IntoValue<ContainerLevel>) -> Container {
|
||||||
|
self.contain().contain_level(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new widget that renders `color` behind `self`.
|
||||||
|
fn background_color(self, color: impl IntoValue<Color>) -> Container {
|
||||||
|
self.contain().pad_by(Px(0)).background_color(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wraps `self` with the default padding.
|
||||||
|
fn pad(self) -> Container {
|
||||||
|
self.contain().transparent()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wraps `self` with the specified padding.
|
||||||
|
fn pad_by(self, padding: impl IntoValue<Edges<Dimension>>) -> Container {
|
||||||
|
self.contain().transparent().pad_by(padding)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type that can create a [`WidgetInstance`] with a preallocated
|
/// A type that can create a [`WidgetInstance`] with a preallocated
|
||||||
|
|
@ -1096,6 +1191,18 @@ impl Children {
|
||||||
pub fn truncate(&mut self, length: usize) {
|
pub fn truncate(&mut self, length: usize) {
|
||||||
self.ordered.truncate(length);
|
self.ordered.truncate(length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `self` as a vertical [`Stack`] of rows.
|
||||||
|
#[must_use]
|
||||||
|
pub fn into_rows(self) -> Stack {
|
||||||
|
Stack::rows(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `self` as a horizontal [`Stack`] of columns.
|
||||||
|
#[must_use]
|
||||||
|
pub fn into_columns(self) -> Stack {
|
||||||
|
Stack::columns(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W> FromIterator<W> for Children
|
impl<W> FromIterator<W> for Children
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
mod align;
|
mod align;
|
||||||
pub mod button;
|
pub mod button;
|
||||||
mod canvas;
|
mod canvas;
|
||||||
|
pub mod container;
|
||||||
mod expand;
|
mod expand;
|
||||||
mod input;
|
mod input;
|
||||||
pub mod label;
|
pub mod label;
|
||||||
|
|
@ -19,6 +20,7 @@ mod tilemap;
|
||||||
pub use align::Align;
|
pub use align::Align;
|
||||||
pub use button::Button;
|
pub use button::Button;
|
||||||
pub use canvas::Canvas;
|
pub use canvas::Canvas;
|
||||||
|
pub use container::Container;
|
||||||
pub use expand::Expand;
|
pub use expand::Expand;
|
||||||
pub use input::Input;
|
pub use input::Input;
|
||||||
pub use label::Label;
|
pub use label::Label;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use kludgine::figures::{Fraction, IntoSigned, IntoUnsigned, Point, Rect, ScreenS
|
||||||
use crate::context::{AsEventContext, LayoutContext};
|
use crate::context::{AsEventContext, LayoutContext};
|
||||||
use crate::styles::{Edges, FlexibleDimension};
|
use crate::styles::{Edges, FlexibleDimension};
|
||||||
use crate::value::{IntoValue, Value};
|
use crate::value::{IntoValue, Value};
|
||||||
use crate::widget::{MakeWidget, WidgetRef, WrapperWidget};
|
use crate::widget::{MakeWidget, WidgetRef, WrappedLayout, WrapperWidget};
|
||||||
use crate::ConstraintLimit;
|
use crate::ConstraintLimit;
|
||||||
|
|
||||||
/// A widget aligns its contents to its container's boundaries.
|
/// A widget aligns its contents to its container's boundaries.
|
||||||
|
|
@ -107,8 +107,8 @@ impl Align {
|
||||||
Layout {
|
Layout {
|
||||||
margin: Edges {
|
margin: Edges {
|
||||||
left,
|
left,
|
||||||
right,
|
|
||||||
top,
|
top,
|
||||||
|
right,
|
||||||
bottom,
|
bottom,
|
||||||
},
|
},
|
||||||
content: Size::new(width, height),
|
content: Size::new(width, height),
|
||||||
|
|
@ -186,7 +186,7 @@ impl WrapperWidget for Align {
|
||||||
&mut self,
|
&mut self,
|
||||||
available_space: Size<ConstraintLimit>,
|
available_space: Size<ConstraintLimit>,
|
||||||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||||
) -> Rect<kludgine::figures::units::Px> {
|
) -> WrappedLayout {
|
||||||
let layout = self.measure(available_space, context);
|
let layout = self.measure(available_space, context);
|
||||||
|
|
||||||
Rect::new(
|
Rect::new(
|
||||||
|
|
@ -196,6 +196,7 @@ impl WrapperWidget for Align {
|
||||||
),
|
),
|
||||||
layout.content.into_signed(),
|
layout.content.into_signed(),
|
||||||
)
|
)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -315,7 +315,7 @@ impl Widget for Button {
|
||||||
define_components! {
|
define_components! {
|
||||||
Button {
|
Button {
|
||||||
/// The background color of the button.
|
/// The background color of the button.
|
||||||
ButtonBackground(Color, "background_color", |context| context.query_style(&OpaqueWidgetColor))
|
ButtonBackground(Color, "background_color", @OpaqueWidgetColor)
|
||||||
/// The background color of the button when it is active (depressed).
|
/// The background color of the button when it is active (depressed).
|
||||||
ButtonActiveBackground(Color, "active_background_color", .surface.color)
|
ButtonActiveBackground(Color, "active_background_color", .surface.color)
|
||||||
/// The background color of the button when the mouse cursor is hovering over
|
/// The background color of the button when the mouse cursor is hovering over
|
||||||
|
|
|
||||||
258
src/widgets/container.rs
Normal file
258
src/widgets/container.rs
Normal file
|
|
@ -0,0 +1,258 @@
|
||||||
|
//! A visual container widget.
|
||||||
|
|
||||||
|
use kludgine::figures::units::Px;
|
||||||
|
use kludgine::figures::{IntoUnsigned, Point, Rect, ScreenScale, Size};
|
||||||
|
use kludgine::Color;
|
||||||
|
|
||||||
|
use crate::context::{GraphicsContext, LayoutContext, WidgetContext};
|
||||||
|
use crate::styles::components::{IntrinsicPadding, SurfaceColor};
|
||||||
|
use crate::styles::{Component, ContainerLevel, Dimension, Edges, Styles};
|
||||||
|
use crate::value::{IntoValue, Value};
|
||||||
|
use crate::widget::{MakeWidget, WidgetRef, WrappedLayout, WrapperWidget};
|
||||||
|
use crate::ConstraintLimit;
|
||||||
|
|
||||||
|
/// A visual container widget, optionally applying padding and a background
|
||||||
|
/// color.
|
||||||
|
///
|
||||||
|
/// # Background Color Selection
|
||||||
|
///
|
||||||
|
/// This widget has three different modes for coloring its background:
|
||||||
|
///
|
||||||
|
/// - [`ContainerBackground::Auto`]: The background color is automatically
|
||||||
|
/// selected by using the [next](ContainerLevel::next) level from the next
|
||||||
|
/// parent container in the hierarchy.
|
||||||
|
///
|
||||||
|
/// If the previous container is [`ContainerLevel::Highest`] or the previous
|
||||||
|
/// parent container uses a color instead of a level,
|
||||||
|
/// [`ContainerLevel::Lowest`] will be used.
|
||||||
|
/// - [`ContainerBackground::Color`]: The specified color will be drawn.
|
||||||
|
/// - [`ContainerBackground::Level`]: The
|
||||||
|
/// [`SurfaceTheme`](crate::styles::SurfaceTheme) container color associated
|
||||||
|
/// with the given level will be used.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Container {
|
||||||
|
/// The configured background selection.
|
||||||
|
pub background: Value<ContainerBackground>,
|
||||||
|
/// Padding to surround the contained widget.
|
||||||
|
///
|
||||||
|
/// If this is None, a uniform surround of [`IntrinsicPadding`] will be
|
||||||
|
/// applied.
|
||||||
|
pub padding: Option<Value<Edges<Dimension>>>,
|
||||||
|
child: WidgetRef,
|
||||||
|
effective_background: Option<EffectiveBackground>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A strategy of applying a background to a [`Container`].
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
|
||||||
|
pub enum ContainerBackground {
|
||||||
|
/// Automatically select a [`ContainerLevel`] by picking the
|
||||||
|
/// [next](ContainerLevel::next) level after the previous parent
|
||||||
|
/// [`Container`].
|
||||||
|
///
|
||||||
|
/// If no parent container is found or a parent container is found with a
|
||||||
|
/// [color](Self::Color) background, [`ContainerLevel::Lowest`] will be
|
||||||
|
/// used. See [`Self::Level`] for more information.
|
||||||
|
#[default]
|
||||||
|
Auto,
|
||||||
|
/// Fills the background with the specified color.
|
||||||
|
Color(Color),
|
||||||
|
/// Applies the [`SurfaceTheme`][st] color
|
||||||
|
/// corresponding with the given level.
|
||||||
|
///
|
||||||
|
/// | [`ContainerLevel`] | [`SurfaceTheme`][st] property |
|
||||||
|
/// |--------------------|-------------------------------|
|
||||||
|
/// | [`Lowest`][ll] | [`lowest_container`][llc] |
|
||||||
|
/// | [`Low`][lo] | [`low_container`][loc] |
|
||||||
|
/// | [`Low`][mi] | [`container`][mic] |
|
||||||
|
/// | [`High`][hi] | [`high_container`][hic] |
|
||||||
|
/// | [`Highest`][hh] | [`highest_container`][hhc] |
|
||||||
|
///
|
||||||
|
/// [st]: crate::styles::SurfaceTheme
|
||||||
|
/// [ll]: ContainerLevel::Lowest
|
||||||
|
/// [llc]: crate::styles::SurfaceTheme::lowest_container
|
||||||
|
/// [lo]: ContainerLevel::Low
|
||||||
|
/// [loc]: crate::styles::SurfaceTheme::low_container
|
||||||
|
/// [mi]: ContainerLevel::Mid
|
||||||
|
/// [mic]: crate::styles::SurfaceTheme::container
|
||||||
|
/// [hi]: ContainerLevel::High
|
||||||
|
/// [hic]: crate::styles::SurfaceTheme::high_container
|
||||||
|
/// [hh]: ContainerLevel::Highest
|
||||||
|
/// [hhc]: crate::styles::SurfaceTheme::highest_container
|
||||||
|
Level(ContainerLevel),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ContainerLevel> for ContainerBackground {
|
||||||
|
fn from(value: ContainerLevel) -> Self {
|
||||||
|
Self::Level(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Color> for ContainerBackground {
|
||||||
|
fn from(value: Color) -> Self {
|
||||||
|
Self::Color(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Container {
|
||||||
|
/// Returns a new container wrapping `child` with default padding and a
|
||||||
|
/// background color automatically selected by the theme.
|
||||||
|
///
|
||||||
|
/// See [`ContainerBackground::Auto`] for more information about automatic
|
||||||
|
/// coloring.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(child: impl MakeWidget) -> Self {
|
||||||
|
Self {
|
||||||
|
padding: None,
|
||||||
|
effective_background: None,
|
||||||
|
background: Value::default(),
|
||||||
|
child: WidgetRef::new(child),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pads the contained widget with `padding`, returning the updated
|
||||||
|
/// container.
|
||||||
|
#[must_use]
|
||||||
|
pub fn pad_by(mut self, padding: impl IntoValue<Edges<Dimension>>) -> Self {
|
||||||
|
self.padding = Some(padding.into_value());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets this container to render no background color, and then returns the
|
||||||
|
/// updated container.
|
||||||
|
#[must_use]
|
||||||
|
pub fn transparent(mut self) -> Self {
|
||||||
|
self.background = Value::Constant(ContainerBackground::Color(Color::CLEAR_WHITE));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets this container to use the specific container level, and then
|
||||||
|
/// returns the updated container.
|
||||||
|
#[must_use]
|
||||||
|
pub fn contain_level(mut self, level: impl IntoValue<ContainerLevel>) -> Container {
|
||||||
|
self.background = level
|
||||||
|
.into_value()
|
||||||
|
.map_each(|level| ContainerBackground::from(*level));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets this container to render the specified `color` background, and then
|
||||||
|
/// returns the updated container.
|
||||||
|
#[must_use]
|
||||||
|
pub fn background_color(mut self, color: impl IntoValue<Color>) -> Self {
|
||||||
|
self.background = color
|
||||||
|
.into_value()
|
||||||
|
.map_each(|color| ContainerBackground::from(*color));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn padding(&self, context: &GraphicsContext<'_, '_, '_, '_, '_>) -> Edges<Px> {
|
||||||
|
match &self.padding {
|
||||||
|
Some(padding) => padding.get(),
|
||||||
|
None => Edges::from(context.query_style(&IntrinsicPadding)),
|
||||||
|
}
|
||||||
|
.map(|dim| dim.into_px(context.gfx.scale()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WrapperWidget for Container {
|
||||||
|
fn child_mut(&mut self) -> &mut WidgetRef {
|
||||||
|
&mut self.child
|
||||||
|
}
|
||||||
|
|
||||||
|
fn background_color(&mut self, context: &WidgetContext<'_, '_>) -> Option<kludgine::Color> {
|
||||||
|
let background = match self.background.get() {
|
||||||
|
ContainerBackground::Color(color) => EffectiveBackground::Color(color),
|
||||||
|
ContainerBackground::Level(level) => EffectiveBackground::Level(level),
|
||||||
|
ContainerBackground::Auto => EffectiveBackground::Level(
|
||||||
|
match context.query_parent_style(&CurrentContainerBackground) {
|
||||||
|
EffectiveBackground::Color(_) => ContainerLevel::default(),
|
||||||
|
EffectiveBackground::Level(level) => level.next().unwrap_or_default(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.effective_background != Some(background) {
|
||||||
|
context.attach_styles(Styles::new().with(&CurrentContainerBackground, background));
|
||||||
|
self.effective_background = Some(background);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(match background {
|
||||||
|
EffectiveBackground::Color(color) => color,
|
||||||
|
EffectiveBackground::Level(level) => match level {
|
||||||
|
ContainerLevel::Lowest => context.theme().surface.lowest_container,
|
||||||
|
ContainerLevel::Low => context.theme().surface.low_container,
|
||||||
|
ContainerLevel::Mid => context.theme().surface.container,
|
||||||
|
ContainerLevel::High => context.theme().surface.high_container,
|
||||||
|
ContainerLevel::Highest => context.theme().surface.highest_container,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn adjust_child_constraint(
|
||||||
|
&mut self,
|
||||||
|
available_space: Size<ConstraintLimit>,
|
||||||
|
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||||
|
) -> Size<ConstraintLimit> {
|
||||||
|
let padding_amount = self
|
||||||
|
.padding(context)
|
||||||
|
.size()
|
||||||
|
.into_px(context.gfx.scale())
|
||||||
|
.into_unsigned();
|
||||||
|
Size::new(
|
||||||
|
available_space.width - padding_amount.width,
|
||||||
|
available_space.height - padding_amount.height,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn position_child(
|
||||||
|
&mut self,
|
||||||
|
size: Size<Px>,
|
||||||
|
_available_space: Size<ConstraintLimit>,
|
||||||
|
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||||
|
) -> WrappedLayout {
|
||||||
|
let padding = self.padding(context);
|
||||||
|
let padded = size + padding.size();
|
||||||
|
|
||||||
|
WrappedLayout {
|
||||||
|
child: Rect::new(Point::new(padding.left, padding.top), size),
|
||||||
|
size: padded.into_unsigned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The selected background configuration of a [`Container`].
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
|
pub enum EffectiveBackground {
|
||||||
|
/// The container rendered using the specified level's theme color.
|
||||||
|
Level(ContainerLevel),
|
||||||
|
/// The container rendered using the specified color.
|
||||||
|
Color(Color),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Component> for EffectiveBackground {
|
||||||
|
type Error = Component;
|
||||||
|
|
||||||
|
fn try_from(value: Component) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
Component::Color(color) => Ok(EffectiveBackground::Color(color)),
|
||||||
|
Component::ContainerLevel(level) => Ok(EffectiveBackground::Level(level)),
|
||||||
|
other => Err(other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EffectiveBackground> for Component {
|
||||||
|
fn from(value: EffectiveBackground) -> Self {
|
||||||
|
match value {
|
||||||
|
EffectiveBackground::Level(level) => Self::ContainerLevel(level),
|
||||||
|
EffectiveBackground::Color(color) => Self::Color(color),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
define_components! {
|
||||||
|
Container {
|
||||||
|
/// The container background behind the current widget.
|
||||||
|
CurrentContainerBackground(EffectiveBackground, "background", |context| EffectiveBackground::Color(context.query_style(&SurfaceColor)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use kludgine::figures::units::{Px, UPx};
|
use kludgine::figures::units::UPx;
|
||||||
use kludgine::figures::{IntoSigned, Rect, Size};
|
use kludgine::figures::{IntoSigned, Size};
|
||||||
|
|
||||||
use crate::context::{AsEventContext, LayoutContext};
|
use crate::context::{AsEventContext, LayoutContext};
|
||||||
use crate::widget::{MakeWidget, WidgetRef, WrapperWidget};
|
use crate::widget::{MakeWidget, WidgetRef, WrappedLayout, WrapperWidget};
|
||||||
use crate::widgets::Space;
|
use crate::widgets::Space;
|
||||||
use crate::ConstraintLimit;
|
use crate::ConstraintLimit;
|
||||||
|
|
||||||
|
|
@ -71,7 +71,7 @@ impl WrapperWidget for Expand {
|
||||||
&mut self,
|
&mut self,
|
||||||
available_space: Size<ConstraintLimit>,
|
available_space: Size<ConstraintLimit>,
|
||||||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||||
) -> Rect<Px> {
|
) -> WrappedLayout {
|
||||||
let available_space = Size::new(
|
let available_space = Size::new(
|
||||||
ConstraintLimit::Known(available_space.width.max()),
|
ConstraintLimit::Known(available_space.width.max()),
|
||||||
ConstraintLimit::Known(available_space.height.max()),
|
ConstraintLimit::Known(available_space.height.max()),
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
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};
|
||||||
use crate::styles::components::{IntrinsicPadding, TextColor};
|
use crate::styles::components::{IntrinsicPadding, TextColor};
|
||||||
|
|
@ -15,7 +16,7 @@ use crate::ConstraintLimit;
|
||||||
pub struct Label {
|
pub struct Label {
|
||||||
/// The contents of the label.
|
/// The contents of the label.
|
||||||
pub text: Value<String>,
|
pub text: Value<String>,
|
||||||
prepared_text: Option<MeasuredText<Px>>,
|
prepared_text: Option<(MeasuredText<Px>, Px, Color)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Label {
|
impl Label {
|
||||||
|
|
@ -26,6 +27,31 @@ impl Label {
|
||||||
prepared_text: None,
|
prepared_text: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prepared_text(
|
||||||
|
&mut self,
|
||||||
|
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
|
||||||
|
color: Color,
|
||||||
|
width: Px,
|
||||||
|
) -> &MeasuredText<Px> {
|
||||||
|
match &self.prepared_text {
|
||||||
|
Some((_, prepared_width, prepared_color))
|
||||||
|
if *prepared_color == color && *prepared_width == width => {}
|
||||||
|
_ => {
|
||||||
|
let measured = self.text.map(|text| {
|
||||||
|
context
|
||||||
|
.gfx
|
||||||
|
.measure_text(Text::new(text, color).wrap_at(width))
|
||||||
|
});
|
||||||
|
self.prepared_text = Some((measured, width, color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.prepared_text
|
||||||
|
.as_ref()
|
||||||
|
.map(|(prepared, _, _)| prepared)
|
||||||
|
.expect("always initialized")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Label {
|
impl Widget for Label {
|
||||||
|
|
@ -34,25 +60,13 @@ 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]);
|
let text_color = context.query_style(&TextColor);
|
||||||
|
|
||||||
if let Some(measured) = &self.prepared_text {
|
let prepared_text = self.prepared_text(context, text_color, size.width);
|
||||||
context
|
|
||||||
.gfx
|
context
|
||||||
.draw_measured_text(measured, TextOrigin::Center, center, None, None);
|
.gfx
|
||||||
} else {
|
.draw_measured_text(prepared_text, TextOrigin::Center, center, None, None);
|
||||||
let text_color = styles.get(&TextColor, context);
|
|
||||||
self.text.map(|contents| {
|
|
||||||
context.gfx.draw_text(
|
|
||||||
Text::new(contents, text_color)
|
|
||||||
.wrap_at(size.width)
|
|
||||||
.origin(TextOrigin::Center),
|
|
||||||
center,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
|
|
@ -67,15 +81,11 @@ impl Widget for Label {
|
||||||
.into_unsigned();
|
.into_unsigned();
|
||||||
let color = styles.get(&TextColor, context);
|
let color = styles.get(&TextColor, context);
|
||||||
let width = available_space.width.max().try_into().unwrap_or(Px::MAX);
|
let width = available_space.width.max().try_into().unwrap_or(Px::MAX);
|
||||||
self.text.map(|contents| {
|
let prepared = self.prepared_text(context, color, width);
|
||||||
let measured = context
|
|
||||||
.gfx
|
let mut size = prepared.size.try_cast().unwrap_or_default();
|
||||||
.measure_text(Text::new(contents, color).wrap_at(width));
|
size += padding * 2;
|
||||||
let mut size = measured.size.try_cast().unwrap_or_default();
|
size
|
||||||
size += padding * 2;
|
|
||||||
self.prepared_text = Some(measured);
|
|
||||||
size
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use kludgine::figures::units::UPx;
|
use kludgine::figures::units::UPx;
|
||||||
use kludgine::figures::{Fraction, IntoSigned, IntoUnsigned, Rect, ScreenScale, Size};
|
use kludgine::figures::{Fraction, IntoSigned, IntoUnsigned, ScreenScale, Size};
|
||||||
|
|
||||||
use crate::context::{AsEventContext, LayoutContext};
|
use crate::context::{AsEventContext, LayoutContext};
|
||||||
use crate::styles::DimensionRange;
|
use crate::styles::DimensionRange;
|
||||||
use crate::widget::{MakeWidget, WidgetRef, WrapperWidget};
|
use crate::widget::{MakeWidget, WidgetRef, WrappedLayout, WrapperWidget};
|
||||||
use crate::ConstraintLimit;
|
use crate::ConstraintLimit;
|
||||||
|
|
||||||
/// A widget that resizes its contained widget to an explicit size.
|
/// A widget that resizes its contained widget to an explicit size.
|
||||||
|
|
@ -66,7 +66,7 @@ impl WrapperWidget for Resize {
|
||||||
&mut self,
|
&mut self,
|
||||||
available_space: Size<ConstraintLimit>,
|
available_space: Size<ConstraintLimit>,
|
||||||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||||
) -> Rect<kludgine::figures::units::Px> {
|
) -> WrappedLayout {
|
||||||
let child = self.child.mounted(&mut context.as_event_context());
|
let child = self.child.mounted(&mut context.as_event_context());
|
||||||
let size = if let (Some(width), Some(height)) =
|
let size = if let (Some(width), Some(height)) =
|
||||||
(self.width.exact_dimension(), self.height.exact_dimension())
|
(self.width.exact_dimension(), self.height.exact_dimension())
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut, Not};
|
||||||
use std::panic::{AssertUnwindSafe, UnwindSafe};
|
use std::panic::{AssertUnwindSafe, UnwindSafe};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::string::ToString;
|
use std::string::ToString;
|
||||||
|
|
@ -283,7 +283,7 @@ struct GooeyWindow<T> {
|
||||||
max_inner_size: Option<Size<UPx>>,
|
max_inner_size: Option<Size<UPx>>,
|
||||||
theme: Option<DynamicReader<ThemePair>>,
|
theme: Option<DynamicReader<ThemePair>>,
|
||||||
current_theme: ThemePair,
|
current_theme: ThemePair,
|
||||||
theme_mode: Dynamic<ThemeMode>,
|
theme_mode: Value<ThemeMode>,
|
||||||
transparent: bool,
|
transparent: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -439,9 +439,10 @@ where
|
||||||
let theme_mode = match context.settings.borrow_mut().theme_mode.take() {
|
let theme_mode = match context.settings.borrow_mut().theme_mode.take() {
|
||||||
Some(Value::Dynamic(dynamic)) => {
|
Some(Value::Dynamic(dynamic)) => {
|
||||||
dynamic.update(window.theme().into());
|
dynamic.update(window.theme().into());
|
||||||
dynamic
|
Value::Dynamic(dynamic)
|
||||||
}
|
}
|
||||||
Some(Value::Constant(_)) | None => Dynamic::new(window.theme().into()),
|
Some(Value::Constant(mode)) => Value::Constant(mode),
|
||||||
|
None => Value::dynamic(window.theme().into()),
|
||||||
};
|
};
|
||||||
let transparent = context.settings.borrow().transparent;
|
let transparent = context.settings.borrow().transparent;
|
||||||
let mut behavior = T::initialize(
|
let mut behavior = T::initialize(
|
||||||
|
|
@ -512,7 +513,7 @@ where
|
||||||
),
|
),
|
||||||
gfx: Exclusive::Owned(Graphics::new(graphics)),
|
gfx: Exclusive::Owned(Graphics::new(graphics)),
|
||||||
};
|
};
|
||||||
context.redraw_when_changed(&self.theme_mode);
|
self.theme_mode.redraw_when_changed(&context);
|
||||||
let mut layout_context = LayoutContext::new(&mut context);
|
let mut layout_context = LayoutContext::new(&mut context);
|
||||||
let window_size = layout_context.gfx.size();
|
let window_size = layout_context.gfx.size();
|
||||||
|
|
||||||
|
|
@ -994,7 +995,9 @@ where
|
||||||
window: kludgine::app::Window<'_, WindowCommand>,
|
window: kludgine::app::Window<'_, WindowCommand>,
|
||||||
_kludgine: &mut Kludgine,
|
_kludgine: &mut Kludgine,
|
||||||
) {
|
) {
|
||||||
self.theme_mode.update(window.theme().into());
|
if let Value::Dynamic(theme_mode) = &self.theme_mode {
|
||||||
|
theme_mode.update(window.theme().into());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn event(
|
fn event(
|
||||||
|
|
@ -1067,6 +1070,30 @@ pub enum ThemeMode {
|
||||||
Dark,
|
Dark,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ThemeMode {
|
||||||
|
/// Returns the opposite mode of `self`.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn inverse(self) -> Self {
|
||||||
|
match self {
|
||||||
|
ThemeMode::Light => Self::Dark,
|
||||||
|
ThemeMode::Dark => Self::Light,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates `self` with its [inverse](Self::inverse).
|
||||||
|
pub fn toggle(&mut self) {
|
||||||
|
*self = !*self;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Not for ThemeMode {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn not(self) -> Self::Output {
|
||||||
|
self.inverse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<window::Theme> for ThemeMode {
|
impl From<window::Theme> for ThemeMode {
|
||||||
fn from(value: window::Theme) -> Self {
|
fn from(value: window::Theme) -> Self {
|
||||||
match value {
|
match value {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue