mirror of
https://github.com/danbulant/cushy
synced 2026-06-17 13:31:07 +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::widget::{MakeWidget, HANDLED, IGNORED};
|
||||
use gooey::widgets::{Input, Label, Space, Stack};
|
||||
use gooey::widgets::{Input, Label, Space};
|
||||
use gooey::Run;
|
||||
use kludgine::app::winit::event::ElementState;
|
||||
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_message = Dynamic::new(String::new());
|
||||
|
||||
Stack::rows(
|
||||
Stack::columns(
|
||||
Label::new(chat_log.clone())
|
||||
.vertical_scroll()
|
||||
.expand()
|
||||
.and(Space::colored(Color::RED).expand_weighted(2)),
|
||||
)
|
||||
Label::new(chat_log.clone())
|
||||
.vertical_scroll()
|
||||
.expand()
|
||||
.and(Space::colored(Color::RED).expand_weighted(2))
|
||||
.into_columns()
|
||||
.expand()
|
||||
.and(Input::new(chat_message.clone()).on_key(move |input| {
|
||||
match (input.state, input.logical_key) {
|
||||
|
|
@ -30,8 +28,8 @@ fn main() -> gooey::Result {
|
|||
}
|
||||
_ => IGNORED,
|
||||
}
|
||||
})),
|
||||
)
|
||||
.expand()
|
||||
.run()
|
||||
}))
|
||||
.into_rows()
|
||||
.expand()
|
||||
.run()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use gooey::widget::MakeWidget;
|
|||
use gooey::widgets::{Input, Label, Scroll, Slider, Stack, Themed};
|
||||
use gooey::window::ThemeMode;
|
||||
use gooey::Run;
|
||||
use kludgine::figures::units::Lp;
|
||||
use kludgine::Color;
|
||||
|
||||
const PRIMARY_HUE: f32 = 240.;
|
||||
|
|
@ -67,6 +68,7 @@ fn main() -> gooey::Result {
|
|||
)),
|
||||
),
|
||||
)
|
||||
.pad_by(Lp::points(16))
|
||||
.expand()
|
||||
.into_window()
|
||||
.with_theme_mode(theme_mode)
|
||||
|
|
|
|||
|
|
@ -924,7 +924,25 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
|||
pub fn query_styles(&self, query: &[&dyn ComponentDefaultvalue]) -> Styles {
|
||||
self.current_node
|
||||
.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.
|
||||
|
|
@ -940,7 +958,19 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
|||
) -> Component::ComponentType {
|
||||
self.current_node
|
||||
.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 {
|
||||
|
|
|
|||
117
src/styles.rs
117
src/styles.rs
|
|
@ -197,6 +197,9 @@ pub enum Component {
|
|||
VisualOrder(VisualOrder),
|
||||
/// A description of what widgets should be focusable.
|
||||
FocusableWidgets(FocusableWidgets),
|
||||
/// A description of the depth of a
|
||||
/// [`Container`](crate::widgets::Container).
|
||||
ContainerLevel(ContainerLevel),
|
||||
}
|
||||
|
||||
impl From<Color> for Component {
|
||||
|
|
@ -705,21 +708,48 @@ impl NamedComponent for Cow<'_, ComponentName> {
|
|||
pub struct Edges<T = FlexibleDimension> {
|
||||
/// The left edge
|
||||
pub left: T,
|
||||
/// The right edge
|
||||
pub right: T,
|
||||
/// The top edge
|
||||
pub top: T,
|
||||
/// The right edge
|
||||
pub right: T,
|
||||
/// The bottom edge
|
||||
pub bottom: T,
|
||||
}
|
||||
|
||||
impl<T> Edges<T> {
|
||||
/// Returns the sum of the parts as a [`Size`].
|
||||
pub fn size(&self) -> Size<T>
|
||||
pub fn size(self) -> Size<T>
|
||||
where
|
||||
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 {
|
||||
fn into_value(self) -> Value<Edges<Dimension>> {
|
||||
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.
|
||||
#[derive(Clone, Debug)]
|
||||
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.
|
||||
/// 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))
|
||||
/// DependentComponent(Color, "dependent_component", @TextColor)
|
||||
/// /// This component defaults to picking a contrasting color between `TextColor` and `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),+ $(,)?)) => {
|
||||
define_components!($type, |context| {
|
||||
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(&$fg, context)),+
|
||||
])
|
||||
|
|
|
|||
30
src/tree.rs
30
src/tree.rs
|
|
@ -317,24 +317,29 @@ impl Tree {
|
|||
&self,
|
||||
perspective: &ManagedWidget,
|
||||
query: &[&dyn ComponentDefaultvalue],
|
||||
skip_current: bool,
|
||||
context: &WidgetContext<'_, '_>,
|
||||
) -> Styles {
|
||||
self.data
|
||||
.lock()
|
||||
.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>(
|
||||
&self,
|
||||
perspective: &ManagedWidget,
|
||||
component: &Component,
|
||||
skip_self: bool,
|
||||
context: &WidgetContext<'_, '_>,
|
||||
) -> Component::ComponentType {
|
||||
self.data
|
||||
let result = self
|
||||
.data
|
||||
.lock()
|
||||
.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,
|
||||
mut perspective: WidgetId,
|
||||
query: &[&dyn ComponentDefaultvalue],
|
||||
skip_self: bool,
|
||||
context: &WidgetContext<'_, '_>,
|
||||
) -> Styles {
|
||||
let mut query = query.iter().map(|n| n.name()).collect::<Vec<_>>();
|
||||
let mut resolved = Styles::new();
|
||||
let mut skip_next = skip_self;
|
||||
while !query.is_empty() {
|
||||
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| {
|
||||
query.retain(|name| {
|
||||
if let Some(component) = styles.get_named(name) {
|
||||
|
|
@ -451,12 +460,16 @@ impl TreeData {
|
|||
&self,
|
||||
mut perspective: WidgetId,
|
||||
query: &Component,
|
||||
skip_self: bool,
|
||||
context: &WidgetContext<'_, '_>,
|
||||
) -> Component::ComponentType {
|
||||
) -> Option<Component::ComponentType> {
|
||||
let name = query.name();
|
||||
let mut skip_next = skip_self;
|
||||
loop {
|
||||
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| {
|
||||
if let Some(component) = styles.get_named(&name) {
|
||||
let Ok(value) =
|
||||
|
|
@ -469,15 +482,16 @@ impl TreeData {
|
|||
}
|
||||
Ok(None)
|
||||
}) {
|
||||
Ok(Some(value)) => return value,
|
||||
Ok(Some(value)) => return Some(value),
|
||||
Ok(None) => {}
|
||||
Err(()) => break,
|
||||
}
|
||||
}
|
||||
|
||||
let Some(parent) = node.parent else { break };
|
||||
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.
|
||||
pub fn get(&self) -> T
|
||||
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::{IntoSigned, IntoUnsigned, Point, Rect, Size};
|
||||
use kludgine::Color;
|
||||
|
||||
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
|
||||
use crate::styles::{IntoComponentValue, NamedComponent, Styles, ThemePair, VisualOrder};
|
||||
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext};
|
||||
use crate::styles::{
|
||||
ContainerLevel, Dimension, Edges, IntoComponentValue, NamedComponent, Styles, ThemePair,
|
||||
VisualOrder,
|
||||
};
|
||||
use crate::tree::Tree;
|
||||
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::{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.
|
||||
pub trait WrapperWidget: Debug + Send + UnwindSafe + 'static {
|
||||
/// Returns the child widget.
|
||||
|
|
@ -185,14 +216,48 @@ pub trait WrapperWidget: Debug + Send + UnwindSafe + 'static {
|
|||
&mut self,
|
||||
available_space: Size<ConstraintLimit>,
|
||||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||
) -> Rect<Px> {
|
||||
) -> WrappedLayout {
|
||||
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)
|
||||
.layout(available_space)
|
||||
.into_signed()
|
||||
.into()
|
||||
.layout(adjusted_space)
|
||||
.into_signed();
|
||||
|
||||
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.
|
||||
|
|
@ -323,6 +388,11 @@ where
|
|||
}
|
||||
|
||||
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());
|
||||
context.for_other(&child).redraw();
|
||||
}
|
||||
|
|
@ -335,8 +405,8 @@ where
|
|||
let child = self.child_mut().mounted(&mut context.as_event_context());
|
||||
|
||||
let layout = self.layout_child(available_space, context);
|
||||
context.set_child_layout(&child, layout);
|
||||
layout.size.into_unsigned()
|
||||
context.set_child_layout(&child, layout.child);
|
||||
layout.size
|
||||
}
|
||||
|
||||
fn mounted(&mut self, context: &mut EventContext<'_, '_>) {
|
||||
|
|
@ -577,6 +647,31 @@ pub trait MakeWidget: Sized {
|
|||
fn widget_ref(self) -> WidgetRef {
|
||||
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
|
||||
|
|
@ -1096,6 +1191,18 @@ impl Children {
|
|||
pub fn truncate(&mut self, length: usize) {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
mod align;
|
||||
pub mod button;
|
||||
mod canvas;
|
||||
pub mod container;
|
||||
mod expand;
|
||||
mod input;
|
||||
pub mod label;
|
||||
|
|
@ -19,6 +20,7 @@ mod tilemap;
|
|||
pub use align::Align;
|
||||
pub use button::Button;
|
||||
pub use canvas::Canvas;
|
||||
pub use container::Container;
|
||||
pub use expand::Expand;
|
||||
pub use input::Input;
|
||||
pub use label::Label;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use kludgine::figures::{Fraction, IntoSigned, IntoUnsigned, Point, Rect, ScreenS
|
|||
use crate::context::{AsEventContext, LayoutContext};
|
||||
use crate::styles::{Edges, FlexibleDimension};
|
||||
use crate::value::{IntoValue, Value};
|
||||
use crate::widget::{MakeWidget, WidgetRef, WrapperWidget};
|
||||
use crate::widget::{MakeWidget, WidgetRef, WrappedLayout, WrapperWidget};
|
||||
use crate::ConstraintLimit;
|
||||
|
||||
/// A widget aligns its contents to its container's boundaries.
|
||||
|
|
@ -107,8 +107,8 @@ impl Align {
|
|||
Layout {
|
||||
margin: Edges {
|
||||
left,
|
||||
right,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
},
|
||||
content: Size::new(width, height),
|
||||
|
|
@ -186,7 +186,7 @@ impl WrapperWidget for Align {
|
|||
&mut self,
|
||||
available_space: Size<ConstraintLimit>,
|
||||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||
) -> Rect<kludgine::figures::units::Px> {
|
||||
) -> WrappedLayout {
|
||||
let layout = self.measure(available_space, context);
|
||||
|
||||
Rect::new(
|
||||
|
|
@ -196,6 +196,7 @@ impl WrapperWidget for Align {
|
|||
),
|
||||
layout.content.into_signed(),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -315,7 +315,7 @@ impl Widget for Button {
|
|||
define_components! {
|
||||
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).
|
||||
ButtonActiveBackground(Color, "active_background_color", .surface.color)
|
||||
/// 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::{IntoSigned, Rect, Size};
|
||||
use kludgine::figures::units::UPx;
|
||||
use kludgine::figures::{IntoSigned, Size};
|
||||
|
||||
use crate::context::{AsEventContext, LayoutContext};
|
||||
use crate::widget::{MakeWidget, WidgetRef, WrapperWidget};
|
||||
use crate::widget::{MakeWidget, WidgetRef, WrappedLayout, WrapperWidget};
|
||||
use crate::widgets::Space;
|
||||
use crate::ConstraintLimit;
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ impl WrapperWidget for Expand {
|
|||
&mut self,
|
||||
available_space: Size<ConstraintLimit>,
|
||||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||
) -> Rect<Px> {
|
||||
) -> WrappedLayout {
|
||||
let available_space = Size::new(
|
||||
ConstraintLimit::Known(available_space.width.max()),
|
||||
ConstraintLimit::Known(available_space.height.max()),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
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};
|
||||
use crate::styles::components::{IntrinsicPadding, TextColor};
|
||||
|
|
@ -15,7 +16,7 @@ use crate::ConstraintLimit;
|
|||
pub struct Label {
|
||||
/// The contents of the label.
|
||||
pub text: Value<String>,
|
||||
prepared_text: Option<MeasuredText<Px>>,
|
||||
prepared_text: Option<(MeasuredText<Px>, Px, Color)>,
|
||||
}
|
||||
|
||||
impl Label {
|
||||
|
|
@ -26,6 +27,31 @@ impl Label {
|
|||
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 {
|
||||
|
|
@ -34,25 +60,13 @@ impl Widget for Label {
|
|||
|
||||
let size = context.gfx.region().size;
|
||||
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 {
|
||||
context
|
||||
.gfx
|
||||
.draw_measured_text(measured, TextOrigin::Center, center, None, None);
|
||||
} else {
|
||||
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,
|
||||
);
|
||||
});
|
||||
}
|
||||
let prepared_text = self.prepared_text(context, text_color, size.width);
|
||||
|
||||
context
|
||||
.gfx
|
||||
.draw_measured_text(prepared_text, TextOrigin::Center, center, None, None);
|
||||
}
|
||||
|
||||
fn layout(
|
||||
|
|
@ -67,15 +81,11 @@ impl Widget for Label {
|
|||
.into_unsigned();
|
||||
let color = styles.get(&TextColor, context);
|
||||
let width = available_space.width.max().try_into().unwrap_or(Px::MAX);
|
||||
self.text.map(|contents| {
|
||||
let measured = context
|
||||
.gfx
|
||||
.measure_text(Text::new(contents, color).wrap_at(width));
|
||||
let mut size = measured.size.try_cast().unwrap_or_default();
|
||||
size += padding * 2;
|
||||
self.prepared_text = Some(measured);
|
||||
size
|
||||
})
|
||||
let prepared = self.prepared_text(context, color, width);
|
||||
|
||||
let mut size = prepared.size.try_cast().unwrap_or_default();
|
||||
size += padding * 2;
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
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::styles::DimensionRange;
|
||||
use crate::widget::{MakeWidget, WidgetRef, WrapperWidget};
|
||||
use crate::widget::{MakeWidget, WidgetRef, WrappedLayout, WrapperWidget};
|
||||
use crate::ConstraintLimit;
|
||||
|
||||
/// A widget that resizes its contained widget to an explicit size.
|
||||
|
|
@ -66,7 +66,7 @@ impl WrapperWidget for Resize {
|
|||
&mut self,
|
||||
available_space: Size<ConstraintLimit>,
|
||||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||
) -> Rect<kludgine::figures::units::Px> {
|
||||
) -> WrappedLayout {
|
||||
let child = self.child.mounted(&mut context.as_event_context());
|
||||
let size = if let (Some(width), Some(height)) =
|
||||
(self.width.exact_dimension(), self.height.exact_dimension())
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::ops::{Deref, DerefMut, Not};
|
||||
use std::panic::{AssertUnwindSafe, UnwindSafe};
|
||||
use std::path::Path;
|
||||
use std::string::ToString;
|
||||
|
|
@ -283,7 +283,7 @@ struct GooeyWindow<T> {
|
|||
max_inner_size: Option<Size<UPx>>,
|
||||
theme: Option<DynamicReader<ThemePair>>,
|
||||
current_theme: ThemePair,
|
||||
theme_mode: Dynamic<ThemeMode>,
|
||||
theme_mode: Value<ThemeMode>,
|
||||
transparent: bool,
|
||||
}
|
||||
|
||||
|
|
@ -439,9 +439,10 @@ where
|
|||
let theme_mode = match context.settings.borrow_mut().theme_mode.take() {
|
||||
Some(Value::Dynamic(dynamic)) => {
|
||||
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 mut behavior = T::initialize(
|
||||
|
|
@ -512,7 +513,7 @@ where
|
|||
),
|
||||
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 window_size = layout_context.gfx.size();
|
||||
|
||||
|
|
@ -994,7 +995,9 @@ where
|
|||
window: kludgine::app::Window<'_, WindowCommand>,
|
||||
_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(
|
||||
|
|
@ -1067,6 +1070,30 @@ pub enum ThemeMode {
|
|||
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 {
|
||||
fn from(value: window::Theme) -> Self {
|
||||
match value {
|
||||
|
|
|
|||
Loading…
Reference in a new issue