Optimizations

This commit is contained in:
Jonathan Johnson 2023-11-12 19:54:10 -08:00
parent 96d407ddc2
commit 07b93397c5
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
17 changed files with 388 additions and 462 deletions

2
Cargo.lock generated
View file

@ -40,6 +40,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"getrandom",
"once_cell", "once_cell",
"version_check", "version_check",
"zerocopy", "zerocopy",
@ -656,6 +657,7 @@ dependencies = [
name = "gooey" name = "gooey"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ahash",
"alot", "alot",
"intentional", "intentional",
"interner", "interner",

View file

@ -21,6 +21,7 @@ tracing-subscriber = { version = "0.3", optional = true, features = [
"env-filter", "env-filter",
] } ] }
palette = "0.7.3" palette = "0.7.3"
ahash = "0.8.6"
# [patch."https://github.com/khonsulabs/kludgine"] # [patch."https://github.com/khonsulabs/kludgine"]
@ -38,3 +39,6 @@ opt-level = 2
[dev-dependencies] [dev-dependencies]
pollster = "0.3.0" pollster = "0.3.0"
[profile.release]
debug = true

View file

@ -14,9 +14,7 @@ use kludgine::{Color, Kludgine};
use crate::graphics::Graphics; use crate::graphics::Graphics;
use crate::styles::components::{HighlightColor, WidgetBackground}; use crate::styles::components::{HighlightColor, WidgetBackground};
use crate::styles::{ use crate::styles::{ComponentDefinition, Styles, Theme, ThemePair, VisualOrder};
ComponentDefaultvalue, ComponentDefinition, Styles, Theme, ThemePair, VisualOrder,
};
use crate::value::{Dynamic, IntoValue, Value}; use crate::value::{Dynamic, IntoValue, Value};
use crate::widget::{EventHandling, ManagedWidget, WidgetId, WidgetInstance, WidgetRef}; use crate::widget::{EventHandling, ManagedWidget, WidgetId, WidgetInstance, WidgetRef};
use crate::window::sealed::WindowCommand; use crate::window::sealed::WindowCommand;
@ -185,7 +183,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
let mut activation_changes = 0; let mut activation_changes = 0;
while activation_changes < MAX_ITERS { while activation_changes < MAX_ITERS {
let active = self.pending_state.active.clone(); let active = self.pending_state.active.clone();
if self.current_node.tree.active_widget() == active.as_ref().map(ManagedWidget::id) { if self.current_node.tree.active_widget() == active.as_ref().map(|w| w.node_id) {
break; break;
} }
activation_changes += 1; activation_changes += 1;
@ -222,7 +220,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
let mut focus_changes = 0; let mut focus_changes = 0;
while focus_changes < MAX_ITERS { while focus_changes < MAX_ITERS {
let focus = self.pending_state.focus.clone(); let focus = self.pending_state.focus.clone();
if self.current_node.tree.focused_widget() == focus.as_ref().map(ManagedWidget::id) { if self.current_node.tree.focused_widget() == focus.as_ref().map(|w| w.node_id) {
break; break;
} }
focus_changes += 1; focus_changes += 1;
@ -465,24 +463,16 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, '
} }
/// Renders the default focus ring for this widget. /// Renders the default focus ring for this widget.
/// pub fn draw_focus_ring(&mut self) {
/// To ensure the correct color is used, include [`HighlightColor`] in the
/// styles request.
pub fn draw_focus_ring_using(&mut self, styles: &Styles) {
// If this is the root widget, don't draw a focus ring. It's redundant. // If this is the root widget, don't draw a focus ring. It's redundant.
if !self.current_node.has_parent() { if !self.current_node.has_parent() {
return; return;
} }
let color = styles.get(&HighlightColor, self); let color = self.get(&HighlightColor);
self.stroke_outline::<Lp>(color, StrokeOptions::lp_wide(Lp::points(2))); self.stroke_outline::<Lp>(color, StrokeOptions::lp_wide(Lp::points(2)));
} }
/// Renders the default focus ring for this widget.
pub fn draw_focus_ring(&mut self) {
self.draw_focus_ring_using(&self.query_styles(&[&HighlightColor]));
}
/// Invokes [`Widget::redraw()`](crate::widget::Widget::redraw) on this /// Invokes [`Widget::redraw()`](crate::widget::Widget::redraw) on this
/// context's widget. /// context's widget.
/// ///
@ -496,12 +486,12 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, '
"redraw called without set_widget_layout" "redraw called without set_widget_layout"
); );
let background = self.query_style(&WidgetBackground); let background = self.get(&WidgetBackground);
self.gfx.fill(background); self.gfx.fill(background);
self.current_node self.current_node
.tree .tree
.note_widget_rendered(self.current_node.id()); .note_widget_rendered(self.current_node.node_id);
self.current_node.clone().lock().as_widget().redraw(self); self.current_node.clone().lock().as_widget().redraw(self);
} }
} }
@ -679,6 +669,7 @@ pub struct WidgetContext<'context, 'window> {
theme: Cow<'context, ThemePair>, theme: Cow<'context, ThemePair>,
pending_state: PendingState<'context>, pending_state: PendingState<'context>,
theme_mode: ThemeMode, theme_mode: ThemeMode,
effective_styles: Styles,
} }
impl<'context, 'window> WidgetContext<'context, 'window> { impl<'context, 'window> WidgetContext<'context, 'window> {
@ -694,12 +685,13 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
focus: current_node focus: current_node
.tree .tree
.focused_widget() .focused_widget()
.and_then(|id| current_node.tree.widget(id)), .and_then(|id| current_node.tree.widget_from_node(id)),
active: current_node active: current_node
.tree .tree
.active_widget() .active_widget()
.and_then(|id| current_node.tree.widget(id)), .and_then(|id| current_node.tree.widget_from_node(id)),
}), }),
effective_styles: current_node.effective_styles(),
current_node, current_node,
redraw_status, redraw_status,
theme: Cow::Borrowed(theme), theme: Cow::Borrowed(theme),
@ -717,6 +709,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
theme: Cow::Borrowed(self.theme.as_ref()), theme: Cow::Borrowed(self.theme.as_ref()),
pending_state: self.pending_state.borrowed(), pending_state: self.pending_state.borrowed(),
theme_mode: self.theme_mode, theme_mode: self.theme_mode,
effective_styles: self.effective_styles.clone(),
} }
} }
@ -730,7 +723,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
Widget::Managed: MapManagedWidget<WidgetContext<'child, 'window>>, Widget::Managed: MapManagedWidget<WidgetContext<'child, 'window>>,
{ {
widget.manage(self).map(|current_node| { widget.manage(self).map(|current_node| {
let (theme, theme_mode) = current_node.overidden_theme(); let (effective_styles, theme, theme_mode) = current_node.overidden_theme();
let theme = if let Some(theme) = theme { let theme = if let Some(theme) = theme {
Cow::Owned(theme.get_tracked(self)) Cow::Owned(theme.get_tracked(self))
} else { } else {
@ -742,6 +735,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
self.theme_mode self.theme_mode
}; };
WidgetContext { WidgetContext {
effective_styles,
current_node, current_node,
redraw_status: self.redraw_status, redraw_status: self.redraw_status,
window: &mut *self.window, window: &mut *self.window,
@ -868,7 +862,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
/// for more information. /// for more information.
#[must_use] #[must_use]
pub fn is_default(&self) -> bool { pub fn is_default(&self) -> bool {
self.current_node.tree.default_widget() == Some(self.current_node.id()) self.current_node.tree.default_widget() == Some(self.current_node.node_id)
} }
/// Returns true if this widget is the target to activate when the user /// Returns true if this widget is the target to activate when the user
@ -879,7 +873,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
/// for more information. /// for more information.
#[must_use] #[must_use]
pub fn is_escape(&self) -> bool { pub fn is_escape(&self) -> bool {
self.current_node.tree.escape_widget() == Some(self.current_node.id()) self.current_node.tree.escape_widget() == Some(self.current_node.node_id)
} }
/// Returns the widget this context is for. /// Returns the widget this context is for.
@ -910,67 +904,17 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
self.current_node.attach_theme_mode(theme_mode); self.current_node.attach_theme_mode(theme_mode);
} }
/// Queries the widget hierarchy for matching style components.
///
/// 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_styles(&self, query: &[&dyn ComponentDefaultvalue]) -> Styles {
self.current_node
.tree
.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.
/// ///
/// This function traverses up the widget hierarchy looking for the /// This function traverses up the widget hierarchy looking for the
/// component being requested. If a matching component is found, it will be /// component being requested. If a matching component is found, it will be
/// returned. Otherwise, the default value will be returned. /// returned. Otherwise, the default value will be returned.
#[must_use] #[must_use]
pub fn query_style<Component: ComponentDefinition>( pub fn get<Component: ComponentDefinition>(
&self, &self,
query: &Component, query: &Component,
) -> Component::ComponentType { ) -> Component::ComponentType {
self.current_node self.effective_styles.get(query, self)
.tree
.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 {

View file

@ -24,7 +24,7 @@ use kludgine::app::winit::error::EventLoopError;
use kludgine::figures::units::UPx; use kludgine::figures::units::UPx;
use kludgine::figures::{Fraction, IntoUnsigned, ScreenUnit}; use kludgine::figures::{Fraction, IntoUnsigned, ScreenUnit};
pub use names::Name; pub use names::Name;
pub use utils::WithClone; pub use utils::{Lazy, WithClone};
pub use self::graphics::Graphics; pub use self::graphics::Graphics;
pub use self::tick::{InputState, Tick}; pub use self::tick::{InputState, Tick};

View file

@ -3,7 +3,8 @@ use std::ops::Deref;
use interner::global::{GlobalString, StringPool}; use interner::global::{GlobalString, StringPool};
static NAMES: StringPool = StringPool::new(); static NAMES: StringPool<ahash::RandomState> =
StringPool::with_hasher(ahash::RandomState::with_seeds(0, 0, 0, 0));
/// A smart-string type that is used as a "name" in Gooey. /// A smart-string type that is used as a "name" in Gooey.
/// ///
@ -12,7 +13,7 @@ static NAMES: StringPool = StringPool::new();
/// exact underlying instance, optimizations can be made that avoid string /// exact underlying instance, optimizations can be made that avoid string
/// comparisons. /// comparisons.
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Name(GlobalString); pub struct Name(GlobalString<ahash::RandomState>);
impl Name { impl Name {
/// Returns a name for the given string. /// Returns a name for the given string.

View file

@ -2,7 +2,7 @@
use std::any::Any; use std::any::Any;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::{hash_map, HashMap}; use std::collections::hash_map;
use std::fmt::Debug; use std::fmt::Debug;
use std::ops::{ use std::ops::{
Add, Bound, Div, Mul, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive, Add, Bound, Div, Mul, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive,
@ -10,6 +10,7 @@ use std::ops::{
use std::panic::{RefUnwindSafe, UnwindSafe}; use std::panic::{RefUnwindSafe, UnwindSafe};
use std::sync::Arc; use std::sync::Arc;
use ahash::AHashMap;
use kludgine::figures::units::{Lp, Px, UPx}; use kludgine::figures::units::{Lp, Px, UPx};
use kludgine::figures::{Fraction, IntoUnsigned, Rect, ScreenScale, Size}; use kludgine::figures::{Fraction, IntoUnsigned, Rect, ScreenScale, Size};
use kludgine::Color; use kludgine::Color;
@ -26,7 +27,7 @@ pub mod components;
/// A collection of style components organized by their name. /// A collection of style components organized by their name.
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct Styles(Arc<HashMap<Name, HashMap<Name, Value<Component>>>>); pub struct Styles(Arc<AHashMap<ComponentName, Value<Component>>>);
impl Styles { impl Styles {
/// Returns an empty collection. /// Returns an empty collection.
@ -39,15 +40,12 @@ impl Styles {
/// without reallocating. /// without reallocating.
#[must_use] #[must_use]
pub fn with_capacity(capacity: usize) -> Self { pub fn with_capacity(capacity: usize) -> Self {
Self(Arc::new(HashMap::with_capacity(capacity))) Self(Arc::new(AHashMap::with_capacity(capacity)))
} }
/// Inserts a [`Component`] with a given name. /// Inserts a [`Component`] with a given name.
pub fn insert_named(&mut self, name: ComponentName, component: impl IntoComponentValue) { pub fn insert_named(&mut self, name: ComponentName, component: impl IntoComponentValue) {
Arc::make_mut(&mut self.0) Arc::make_mut(&mut self.0).insert(name, component.into_component_value());
.entry(name.group)
.or_default()
.insert(name.name, component.into_component_value());
} }
/// Inserts a [`Component`] using then name provided. /// Inserts a [`Component`] using then name provided.
@ -70,9 +68,7 @@ impl Styles {
Named: NamedComponent + ?Sized, Named: NamedComponent + ?Sized,
{ {
let name = component.name(); let name = component.name();
self.0 self.0.get(&name)
.get(&name.group)
.and_then(|group| group.get(&name.name))
} }
/// Returns the component associated with the given name, or if not found, /// Returns the component associated with the given name, or if not found,
@ -88,14 +84,21 @@ impl Styles {
{ {
let name = component.name(); let name = component.name();
self.0 self.0
.get(&name.group) .get(&name)
.and_then(|group| group.get(&name.name))
.and_then(|component| { .and_then(|component| {
component.redraw_when_changed(context); component.redraw_when_changed(context);
<Named::ComponentType>::try_from_component(component.get()).ok() <Named::ComponentType>::try_from_component(component.get()).ok()
}) })
.unwrap_or_else(|| component.default_value(context)) .unwrap_or_else(|| component.default_value(context))
} }
/// Inserts all components from `other`, overwriting any existing entries
/// with the same [`ComponentName`].
pub fn append(&mut self, other: Styles) {
for (name, value) in other {
self.insert_named(name, value);
}
}
} }
/// A value that can be converted into a `Value<Component>`. /// A value that can be converted into a `Value<Component>`.
@ -141,42 +144,38 @@ impl FromIterator<(ComponentName, Component)> for Styles {
} }
impl IntoIterator for Styles { impl IntoIterator for Styles {
type IntoIter = StylesIntoIter; type IntoIter = hash_map::IntoIter<ComponentName, Value<Component>>;
type Item = (ComponentName, Value<Component>); type Item = (ComponentName, Value<Component>);
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
StylesIntoIter { Arc::try_unwrap(self.0)
main: Arc::try_unwrap(self.0) .unwrap_or_else(|err| err.as_ref().clone())
.unwrap_or_else(|err| err.as_ref().clone()) .into_iter()
.into_iter(),
names: None,
}
} }
} }
/// An iterator over the owned contents of a [`Styles`] instance. // /// An iterator over the owned contents of a [`Styles`] instance.
pub struct StylesIntoIter { // pub struct StylesIntoIter {
main: hash_map::IntoIter<Name, HashMap<Name, Value<Component>>>, // main: hash_map::IntoIter<ComponentName, Value<Component>>,
names: Option<(Name, hash_map::IntoIter<Name, Value<Component>>)>, // }
}
impl Iterator for StylesIntoIter { // impl Iterator for StylesIntoIter {
type Item = (ComponentName, Value<Component>); // type Item = (ComponentName, Value<Component>);
fn next(&mut self) -> Option<Self::Item> { // fn next(&mut self) -> Option<Self::Item> {
loop { // loop {
if let Some((group, names)) = &mut self.names { // if let Some((group, names)) = &mut self.names {
if let Some((name, component)) = names.next() { // if let Some((name, component)) = names.next() {
return Some((ComponentName::new(group.clone(), name), component)); // return Some((ComponentName::new(group.clone(), name), component));
} // }
self.names = None; // self.names = None;
} // }
let (group, names) = self.main.next()?; // let (group, names) = self.main.next()?;
self.names = Some((group, names.into_iter())); // self.names = Some((group, names.into_iter()));
} // }
} // }
} // }
/// A value of a style component. /// A value of a style component.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -189,8 +188,6 @@ pub enum Component {
DimensionRange(DimensionRange), DimensionRange(DimensionRange),
/// A percentage between 0.0 and 1.0. /// A percentage between 0.0 and 1.0.
Percent(ZeroToOne), Percent(ZeroToOne),
/// A custom component type.
Custom(CustomComponent),
/// An easing function for animations. /// An easing function for animations.
Easing(EasingFunction), Easing(EasingFunction),
/// A visual ordering to use for layout. /// A visual ordering to use for layout.
@ -200,6 +197,9 @@ pub enum Component {
/// A description of the depth of a /// A description of the depth of a
/// [`Container`](crate::widgets::Container). /// [`Container`](crate::widgets::Container).
ContainerLevel(ContainerLevel), ContainerLevel(ContainerLevel),
/// A custom component type.
Custom(CustomComponent),
} }
impl From<Color> for Component { impl From<Color> for Component {
@ -614,7 +614,7 @@ where
} }
/// A fully-qualified style component name. /// A fully-qualified style component name.
#[derive(Clone, Eq, PartialEq, Debug)] #[derive(Clone, Eq, PartialEq, Debug, Hash)]
pub struct ComponentName { pub struct ComponentName {
/// The group name. /// The group name.
pub group: Name, pub group: Name,

View file

@ -44,11 +44,13 @@ macro_rules! define_components {
const _: () = { const _: () = {
use $crate::styles::{ComponentDefinition, ComponentName, NamedComponent}; use $crate::styles::{ComponentDefinition, ComponentName, NamedComponent};
use $crate::context::WidgetContext; use $crate::context::WidgetContext;
use $crate::Lazy;
use ::std::borrow::Cow; use ::std::borrow::Cow;
impl NamedComponent for $component { impl NamedComponent for $component {
fn name(&self) -> Cow<'_, ComponentName> { fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::new(stringify!($widget), $name)) static NAME: Lazy<ComponentName> = Lazy::new(|| ComponentName::new(stringify!($widget), $name));
Cow::Borrowed(&*NAME)
} }
} }
@ -69,14 +71,13 @@ macro_rules! define_components {
} }
}; };
($type:ty, @$path:path) => { ($type:ty, @$path:path) => {
define_components!($type, |context| context.query_style(&$path)); define_components!($type, |context| context.get(&$path));
}; };
($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_parent_styles(&[&$bg, $(&$fg),*]); context.get(&$bg).most_contrasting(&[
styles.get(&$bg, context).most_contrasting(&[ $(context.get(&$fg)),+
$(styles.get(&$fg, context)),+
]) ])
}); });
}; };

View file

@ -1,14 +1,12 @@
use std::collections::HashMap;
use std::mem; use std::mem;
use std::sync::{Arc, Mutex, PoisonError}; use std::sync::{Arc, Mutex, PoisonError};
use ahash::AHashMap;
use alot::{LotId, Lots};
use kludgine::figures::units::Px; use kludgine::figures::units::Px;
use kludgine::figures::{Point, Rect}; use kludgine::figures::{Point, Rect};
use crate::context::WidgetContext; use crate::styles::{Styles, ThemePair, VisualOrder};
use crate::styles::{
ComponentDefaultvalue, ComponentDefinition, ComponentType, Styles, ThemePair, VisualOrder,
};
use crate::value::Value; use crate::value::Value;
use crate::widget::{ManagedWidget, WidgetId, WidgetInstance}; use crate::widget::{ManagedWidget, WidgetId, WidgetInstance};
use crate::window::ThemeMode; use crate::window::ThemeMode;
@ -26,32 +24,44 @@ impl Tree {
) -> ManagedWidget { ) -> ManagedWidget {
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
let id = widget.id(); let id = widget.id();
data.nodes.insert( let (effective_styles, parent_id) = if let Some(parent) = parent {
id, (
Node { data.nodes[parent.node_id].child_styles(),
widget: widget.clone(), Some(parent.node_id),
children: Vec::new(), )
parent: parent.map(ManagedWidget::id), } else {
layout: None, (Styles::default(), None)
styles: None, };
theme: None, let node_id = data.nodes.push(Node {
theme_mode: None, widget: widget.clone(),
}, children: Vec::new(),
); parent: parent_id,
layout: None,
associated_styles: None,
effective_styles,
theme: None,
theme_mode: None,
});
data.nodes_by_id.insert(id, node_id);
if widget.is_default() { if widget.is_default() {
data.defaults.push(id); data.defaults.push(node_id);
} }
if widget.is_escape() { if widget.is_escape() {
data.escapes.push(id); data.escapes.push(node_id);
} }
if let Some(parent) = parent { if let Some(parent) = parent_id {
let parent = data.nodes.get_mut(&parent.id()).expect("missing parent"); let parent = &mut data.nodes[parent];
parent.children.push(id); parent.children.push(node_id);
} }
if let Some(next_focus) = widget.next_focus() { if let Some(next_focus) = widget
data.previous_focuses.insert(next_focus, id); .next_focus()
.and_then(|id| data.nodes_by_id.get(&id))
.copied()
{
data.previous_focuses.insert(next_focus, node_id);
} }
ManagedWidget { ManagedWidget {
node_id,
widget, widget,
tree: self.clone(), tree: self.clone(),
} }
@ -59,37 +69,37 @@ impl Tree {
pub fn remove_child(&self, child: &ManagedWidget, parent: &ManagedWidget) { pub fn remove_child(&self, child: &ManagedWidget, parent: &ManagedWidget) {
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
data.remove_child(child.id(), parent.id()); data.remove_child(child.node_id, parent.node_id);
if child.widget.is_default() { if child.widget.is_default() {
data.defaults.retain(|id| *id != child.id()); data.defaults.retain(|id| *id != child.node_id);
} }
if child.widget.is_escape() { if child.widget.is_escape() {
data.escapes.retain(|id| *id != child.id()); data.escapes.retain(|id| *id != child.node_id);
} }
} }
pub(crate) fn set_layout(&self, widget: WidgetId, rect: Rect<Px>) { pub(crate) fn set_layout(&self, widget: LotId, rect: Rect<Px>) {
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
let node = data.nodes.get_mut(&widget).expect("missing widget"); let node = &mut data.nodes[widget];
node.layout = Some(rect); node.layout = Some(rect);
let mut children_to_offset = node.children.clone(); let mut children_to_offset = node.children.clone();
while let Some(child) = children_to_offset.pop() { while let Some(child) = children_to_offset.pop() {
if let Some(layout) = data if let Some(layout) = data
.nodes .nodes
.get_mut(&child) .get_mut(child)
.and_then(|child| child.layout.as_mut()) .and_then(|child| child.layout.as_mut())
{ {
layout.origin += rect.origin; layout.origin += rect.origin;
children_to_offset.extend(data.nodes[&child].children.iter().copied()); children_to_offset.extend(data.nodes[child].children.iter().copied());
} }
} }
} }
pub(crate) fn layout(&self, widget: WidgetId) -> Option<Rect<Px>> { pub(crate) fn layout(&self, widget: LotId) -> Option<Rect<Px>> {
let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
data.nodes.get(&widget).and_then(|widget| widget.layout) data.nodes.get(widget).and_then(|widget| widget.layout)
} }
pub(crate) fn reset_render_order(&self) { pub(crate) fn reset_render_order(&self) {
@ -97,26 +107,26 @@ impl Tree {
data.render_order.clear(); data.render_order.clear();
} }
pub(crate) fn note_widget_rendered(&self, widget: WidgetId) { pub(crate) fn note_widget_rendered(&self, widget: LotId) {
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
data.render_order.push(widget); data.render_order.push(widget);
} }
pub(crate) fn reset_child_layouts(&self, parent: WidgetId) { pub(crate) fn reset_child_layouts(&self, parent: LotId) {
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
let children = data.nodes[&parent].children.clone(); let children = data.nodes[parent].children.clone();
for child in children { for child in children {
data.nodes.get_mut(&child).expect("missing widget").layout = None; data.nodes.get_mut(child).expect("missing widget").layout = None;
} }
} }
pub(crate) fn visually_ordered_children( pub(crate) fn visually_ordered_children(
&self, &self,
parent: WidgetId, parent: LotId,
order: VisualOrder, order: VisualOrder,
) -> Vec<ManagedWidget> { ) -> Vec<ManagedWidget> {
let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
let node = &data.nodes[&parent]; let node = &data.nodes[parent];
let mut unordered = node.children.clone(); let mut unordered = node.children.clone();
let mut ordered = Vec::<ManagedWidget>::with_capacity(unordered.len()); let mut ordered = Vec::<ManagedWidget>::with_capacity(unordered.len());
loop { loop {
@ -126,7 +136,7 @@ impl Tree {
let mut index = 0; let mut index = 0;
while index < unordered.len() { while index < unordered.len() {
let Some(layout) = &data.nodes[&unordered[index]].layout else { let Some(layout) = &data.nodes[unordered[index]].layout else {
unordered.remove(index); unordered.remove(index);
continue; continue;
}; };
@ -146,13 +156,13 @@ impl Tree {
index = 0; index = 0;
let row_base = ordered.len(); let row_base = ordered.len();
while index < unordered.len() { while index < unordered.len() {
let top_left = data.nodes[&unordered[index]] let top_left = data.nodes[unordered[index]]
.layout .layout
.expect("all have layouts") .expect("all have layouts")
.origin; .origin;
if min_vertical <= top_left.y && top_left.y <= max_vertical { if min_vertical <= top_left.y && top_left.y <= max_vertical {
ordered.push( ordered.push(
data.widget(unordered.remove(index), self) data.widget_from_node(unordered.remove(index), self)
.expect("widget is owned"), .expect("widget is owned"),
); );
} else { } else {
@ -161,22 +171,29 @@ impl Tree {
} }
ordered[row_base..].sort_unstable_by_key(|managed| { ordered[row_base..].sort_unstable_by_key(|managed| {
order order.horizontal.sort_key(
.horizontal &data.nodes[managed.node_id]
.sort_key(&data.nodes[&managed.id()].layout.expect("all have layouts")) .layout
.expect("all have layouts"),
)
}); });
} }
ordered ordered
} }
pub(crate) fn effective_styles(&self, id: LotId) -> Styles {
let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
data.nodes[id].effective_styles.clone()
}
pub(crate) fn hover(&self, new_hover: Option<&ManagedWidget>) -> HoverResults { pub(crate) fn hover(&self, new_hover: Option<&ManagedWidget>) -> HoverResults {
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
let hovered = new_hover let hovered = new_hover
.map(|new_hover| data.widget_hierarchy(new_hover.id(), self)) .map(|new_hover| data.widget_hierarchy(new_hover.node_id, self))
.unwrap_or_default(); .unwrap_or_default();
let unhovered = match data.update_tracked_widget(new_hover, self, |data| &mut data.hover) { let unhovered = match data.update_tracked_widget(new_hover, self, |data| &mut data.hover) {
Ok(Some(old_hover)) => { Ok(Some(old_hover)) => {
let mut old_hovered = data.widget_hierarchy(old_hover.id(), self); let mut old_hovered = data.widget_hierarchy(old_hover.node_id, self);
// For any widgets that were shared, remove them, as they don't // For any widgets that were shared, remove them, as they don't
// need to have their events fired again. // need to have their events fired again.
let mut new_index = 0; let mut new_index = 0;
@ -206,24 +223,29 @@ impl Tree {
pub fn widget(&self, id: WidgetId) -> Option<ManagedWidget> { pub fn widget(&self, id: WidgetId) -> Option<ManagedWidget> {
let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
data.widget(id, self) data.widget_from_id(id, self)
} }
pub fn active_widget(&self) -> Option<WidgetId> { pub(crate) fn widget_from_node(&self, id: LotId) -> Option<ManagedWidget> {
let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
data.widget_from_node(id, self)
}
pub(crate) fn active_widget(&self) -> Option<LotId> {
self.data self.data
.lock() .lock()
.map_or_else(PoisonError::into_inner, |g| g) .map_or_else(PoisonError::into_inner, |g| g)
.active .active
} }
pub fn hovered_widget(&self) -> Option<WidgetId> { pub(crate) fn hovered_widget(&self) -> Option<LotId> {
self.data self.data
.lock() .lock()
.map_or_else(PoisonError::into_inner, |g| g) .map_or_else(PoisonError::into_inner, |g| g)
.hover .hover
} }
pub fn default_widget(&self) -> Option<WidgetId> { pub(crate) fn default_widget(&self) -> Option<LotId> {
self.data self.data
.lock() .lock()
.map_or_else(PoisonError::into_inner, |g| g) .map_or_else(PoisonError::into_inner, |g| g)
@ -232,7 +254,7 @@ impl Tree {
.copied() .copied()
} }
pub fn escape_widget(&self) -> Option<WidgetId> { pub(crate) fn escape_widget(&self) -> Option<LotId> {
self.data self.data
.lock() .lock()
.map_or_else(PoisonError::into_inner, |g| g) .map_or_else(PoisonError::into_inner, |g| g)
@ -241,20 +263,20 @@ impl Tree {
.copied() .copied()
} }
pub fn is_hovered(&self, id: WidgetId) -> bool { pub(crate) fn is_hovered(&self, id: LotId) -> bool {
let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
let mut search = data.hover; let mut search = data.hover;
while let Some(hovered) = search { while let Some(hovered) = search {
if hovered == id { if hovered == id {
return true; return true;
} }
search = data.nodes.get(&hovered).and_then(|node| node.parent); search = data.nodes.get(hovered).and_then(|node| node.parent);
} }
false false
} }
pub fn focused_widget(&self) -> Option<WidgetId> { pub(crate) fn focused_widget(&self) -> Option<LotId> {
self.data self.data
.lock() .lock()
.map_or_else(PoisonError::into_inner, |g| g) .map_or_else(PoisonError::into_inner, |g| g)
@ -265,82 +287,47 @@ impl Tree {
let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
let mut hits = Vec::new(); let mut hits = Vec::new();
for id in data.render_order.iter().rev() { for id in data.render_order.iter().rev() {
if let Some(last_rendered) = data.nodes.get(id).and_then(|widget| widget.layout) { if let Some(last_rendered) = data.nodes.get(*id).and_then(|widget| widget.layout) {
if last_rendered.contains(point) { if last_rendered.contains(point) {
hits.push(ManagedWidget { hits.push(data.widget_from_node(*id, self).expect("just accessed"));
widget: data.nodes[id].widget.clone(),
tree: self.clone(),
});
} }
} }
} }
hits hits
} }
pub(crate) fn parent(&self, id: WidgetId) -> Option<WidgetId> { pub(crate) fn parent(&self, id: LotId) -> Option<LotId> {
let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
data.nodes.get(&id).expect("missing widget").parent data.nodes.get(id).expect("missing widget").parent
} }
pub(crate) fn attach_styles(&self, id: WidgetId, styles: Value<Styles>) { pub(crate) fn attach_styles(&self, id: LotId, styles: Value<Styles>) {
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
data.nodes.get_mut(&id).expect("missing widget").styles = Some(styles); data.attach_styles(id, styles);
} }
pub(crate) fn attach_theme(&self, id: WidgetId, theme: Value<ThemePair>) { pub(crate) fn attach_theme(&self, id: LotId, theme: Value<ThemePair>) {
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
data.nodes.get_mut(&id).expect("missing widget").theme = Some(theme); data.nodes.get_mut(id).expect("missing widget").theme = Some(theme);
} }
pub(crate) fn attach_theme_mode(&self, id: WidgetId, theme: Value<ThemeMode>) { pub(crate) fn attach_theme_mode(&self, id: LotId, theme: Value<ThemeMode>) {
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
data.nodes.get_mut(&id).expect("missing widget").theme_mode = Some(theme); data.nodes.get_mut(id).expect("missing widget").theme_mode = Some(theme);
} }
pub(crate) fn overriden_theme( pub(crate) fn overriden_theme(
&self, &self,
id: WidgetId, id: LotId,
) -> (Option<Value<ThemePair>>, Option<Value<ThemeMode>>) { ) -> (Styles, Option<Value<ThemePair>>, Option<Value<ThemeMode>>) {
let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
let node = data.nodes.get(id).expect("missing widget");
( (
data.nodes.get(&id).expect("missing widget").theme.clone(), node.effective_styles.clone(),
data.nodes node.theme.clone(),
.get(&id) node.theme_mode.clone(),
.expect("missing widget")
.theme_mode
.clone(),
) )
} }
pub fn query_styles(
&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, skip_current, context)
}
pub fn query_style<Component: ComponentDefinition>(
&self,
perspective: &ManagedWidget,
component: &Component,
skip_self: bool,
context: &WidgetContext<'_, '_>,
) -> Component::ComponentType {
let result = self
.data
.lock()
.map_or_else(PoisonError::into_inner, |g| g)
.query_style(perspective.id(), component, skip_self, context);
result.unwrap_or_else(|| component.default_value(context))
}
} }
pub(crate) struct HoverResults { pub(crate) struct HoverResults {
@ -350,27 +337,68 @@ pub(crate) struct HoverResults {
#[derive(Default)] #[derive(Default)]
struct TreeData { struct TreeData {
nodes: HashMap<WidgetId, Node>, nodes: Lots<Node>,
active: Option<WidgetId>, nodes_by_id: AHashMap<WidgetId, LotId>,
focus: Option<WidgetId>, active: Option<LotId>,
hover: Option<WidgetId>, focus: Option<LotId>,
defaults: Vec<WidgetId>, hover: Option<LotId>,
escapes: Vec<WidgetId>, defaults: Vec<LotId>,
render_order: Vec<WidgetId>, escapes: Vec<LotId>,
previous_focuses: HashMap<WidgetId, WidgetId>, render_order: Vec<LotId>,
previous_focuses: AHashMap<LotId, LotId>,
} }
impl TreeData { impl TreeData {
fn widget(&self, id: WidgetId, tree: &Tree) -> Option<ManagedWidget> { fn widget_from_id(&self, id: WidgetId, tree: &Tree) -> Option<ManagedWidget> {
let node_id = *self.nodes_by_id.get(&id)?;
Some(ManagedWidget { Some(ManagedWidget {
widget: self.nodes.get(&id)?.widget.clone(), node_id,
widget: self.nodes[node_id].widget.clone(),
tree: tree.clone(), tree: tree.clone(),
}) })
} }
fn remove_child(&mut self, child: WidgetId, parent: WidgetId) { fn widget_from_node(&self, node_id: LotId, tree: &Tree) -> Option<ManagedWidget> {
let removed_node = self.nodes.remove(&child).expect("widget already removed"); Some(ManagedWidget {
let parent = self.nodes.get_mut(&parent).expect("missing widget"); node_id,
widget: self.nodes.get(node_id)?.widget.clone(),
tree: tree.clone(),
})
}
fn attach_styles(&mut self, id: LotId, styles_value: Value<Styles>) {
let node = &mut self.nodes[id];
node.associated_styles = Some(styles_value);
if !node.children.is_empty() {
// We had previously associated styles, we need to rebuild all
// children effective styles
let child_styles = node.child_styles();
let children = node.children.clone();
self.update_effective_styles(&child_styles, children);
}
}
fn update_node_effective_styles(&mut self, id: LotId, effective_styles: &Styles) {
let node = &mut self.nodes[id];
node.effective_styles = effective_styles.clone();
if !node.children.is_empty() {
let child_styles = node.child_styles();
let children = node.children.clone();
self.update_effective_styles(&child_styles, children);
}
}
fn update_effective_styles(&mut self, effective_styles: &Styles, nodes_to_update: Vec<LotId>) {
for node in nodes_to_update {
self.update_node_effective_styles(node, effective_styles);
}
}
fn remove_child(&mut self, child: LotId, parent: LotId) {
let removed_node = self.nodes.remove(child).expect("widget already removed");
self.nodes_by_id.remove(&removed_node.widget.id());
let parent = &mut self.nodes[parent];
let index = parent let index = parent
.children .children
.iter() .iter()
@ -380,21 +408,25 @@ impl TreeData {
parent.children.remove(index); parent.children.remove(index);
let mut detached_nodes = removed_node.children; let mut detached_nodes = removed_node.children;
if let Some(next_focus) = removed_node.widget.next_focus() { if let Some(next_focus) = removed_node
self.previous_focuses.remove(&next_focus); .widget
.next_focus()
.and_then(|id| self.nodes_by_id.get(&id))
{
self.previous_focuses.remove(next_focus);
} }
while let Some(node) = detached_nodes.pop() { while let Some(node) = detached_nodes.pop() {
let mut node = self.nodes.remove(&node).expect("detached node missing"); let mut node = self.nodes.remove(node).expect("detached node missing");
detached_nodes.append(&mut node.children); detached_nodes.append(&mut node.children);
} }
} }
pub(crate) fn widget_hierarchy(&self, mut widget: WidgetId, tree: &Tree) -> Vec<ManagedWidget> { pub(crate) fn widget_hierarchy(&self, mut widget: LotId, tree: &Tree) -> Vec<ManagedWidget> {
let mut hierarchy = Vec::new(); let mut hierarchy = Vec::new();
while let Some(managed) = self.widget(widget, tree) { while let Some(managed) = self.widget_from_node(widget, tree) {
hierarchy.push(managed); hierarchy.push(managed);
let Some(parent) = self.nodes.get(&widget).and_then(|widget| widget.parent) else { let Some(parent) = self.nodes.get(widget).and_then(|widget| widget.parent) else {
break; break;
}; };
widget = parent; widget = parent;
@ -409,98 +441,36 @@ impl TreeData {
&mut self, &mut self,
new_widget: Option<&ManagedWidget>, new_widget: Option<&ManagedWidget>,
tree: &Tree, tree: &Tree,
property: impl FnOnce(&mut Self) -> &mut Option<WidgetId>, property: impl FnOnce(&mut Self) -> &mut Option<LotId>,
) -> Result<Option<ManagedWidget>, ()> { ) -> Result<Option<ManagedWidget>, ()> {
match ( match (
mem::replace(property(self), new_widget.map(ManagedWidget::id)), mem::replace(property(self), new_widget.map(|w| w.node_id)),
new_widget, new_widget,
) { ) {
(Some(old_widget), Some(new_widget)) if old_widget == new_widget.id() => Err(()), (Some(old_widget), Some(new_widget)) if old_widget == new_widget.node_id => Err(()),
(Some(old_widget), _) => Ok(self.nodes.get(&old_widget).map(|node| ManagedWidget { (Some(old_widget), _) => Ok(self.widget_from_node(old_widget, tree)),
widget: node.widget.clone(),
tree: tree.clone(),
})),
(None, _) => Ok(None), (None, _) => Ok(None),
} }
} }
fn query_styles(
&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 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) {
resolved.insert(name, component.clone());
false
} else {
true
}
});
});
}
let Some(parent) = node.parent else { break };
perspective = parent;
}
resolved
}
fn query_style<Component: ComponentDefinition>(
&self,
mut perspective: WidgetId,
query: &Component,
skip_self: bool,
context: &WidgetContext<'_, '_>,
) -> Option<Component::ComponentType> {
let name = query.name();
let mut skip_next = skip_self;
loop {
let node = &self.nodes[&perspective];
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) =
<Component::ComponentType>::try_from_component(component.get())
else {
return Err(());
};
component.redraw_when_changed(context);
return Ok(Some(value));
}
Ok(None)
}) {
Ok(Some(value)) => return Some(value),
Ok(None) => {}
Err(()) => break,
}
}
let Some(parent) = node.parent else { break };
perspective = parent;
}
None
}
} }
pub struct Node { pub struct Node {
pub widget: WidgetInstance, pub widget: WidgetInstance,
pub children: Vec<WidgetId>, pub children: Vec<LotId>,
pub parent: Option<WidgetId>, pub parent: Option<LotId>,
pub layout: Option<Rect<Px>>, pub layout: Option<Rect<Px>>,
pub styles: Option<Value<Styles>>, pub associated_styles: Option<Value<Styles>>,
pub effective_styles: Styles,
pub theme: Option<Value<ThemePair>>, pub theme: Option<Value<ThemePair>>,
pub theme_mode: Option<Value<ThemeMode>>, pub theme_mode: Option<Value<ThemeMode>>,
} }
impl Node {
fn child_styles(&self) -> Styles {
let mut effective_styles = self.effective_styles.clone();
if let Some(associated) = &self.associated_styles {
effective_styles.append(associated.get());
}
effective_styles
}
}

View file

@ -101,12 +101,19 @@ impl ModifiersExt for Modifiers {
} }
} }
/// A [`OnceLock`]-based lazy initializer.
pub struct Lazy<T> { pub struct Lazy<T> {
init: fn() -> T, init: fn() -> T,
once: OnceLock<T>, once: OnceLock<T>,
} }
impl<T> Lazy<T> { impl<T> Lazy<T> {
/// Returns a type that initializes itself once upon being accessed.
///
/// `init` is guaranteed to be called only once, but this type can't accept
/// `FnOnce` generic types due to being unable to allocate a `Box<dyn T>` in
/// `const` or being able to give a name to the type of a function so that
/// users could use this type in static variables.
pub const fn new(init: fn() -> T) -> Self { pub const fn new(init: fn() -> T) -> Self {
Self { Self {
init, init,

View file

@ -8,6 +8,7 @@ use std::panic::UnwindSafe;
use std::sync::atomic::{self, AtomicU64}; use std::sync::atomic::{self, AtomicU64};
use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use alot::LotId;
use kludgine::app::winit::event::{ use kludgine::app::winit::event::{
DeviceId, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase, DeviceId, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase,
}; };
@ -204,6 +205,15 @@ impl From<Size<Px>> for WrappedLayout {
} }
} }
impl From<Size<UPx>> for WrappedLayout {
fn from(size: Size<UPx>) -> Self {
WrappedLayout {
child: size.into_signed().into(),
size,
}
}
}
/// 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.
@ -248,7 +258,15 @@ pub trait WrapperWidget: Debug + Send + UnwindSafe + 'static {
available_space: Size<ConstraintLimit>, available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>, context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> WrappedLayout { ) -> WrappedLayout {
size.into() Size::<UPx>::new(
available_space
.width
.fit_measured(size.width, context.gfx.scale()),
available_space
.height
.fit_measured(size.height, context.gfx.scale()),
)
.into()
} }
/// Returns the background color to render behind the wrapped widget. /// Returns the background color to render behind the wrapped widget.
@ -965,6 +983,7 @@ where
/// A [`Widget`] that has been attached to a widget hierarchy. /// A [`Widget`] that has been attached to a widget hierarchy.
#[derive(Clone)] #[derive(Clone)]
pub struct ManagedWidget { pub struct ManagedWidget {
pub(crate) node_id: LotId,
pub(crate) widget: WidgetInstance, pub(crate) widget: WidgetInstance,
pub(crate) tree: Tree, pub(crate) tree: Tree,
} }
@ -987,7 +1006,7 @@ impl ManagedWidget {
} }
pub(crate) fn set_layout(&self, rect: Rect<Px>) { pub(crate) fn set_layout(&self, rect: Rect<Px>) {
self.tree.set_layout(self.id(), rect); self.tree.set_layout(self.node_id, rect);
} }
/// Returns the unique id of this widget instance. /// Returns the unique id of this widget instance.
@ -1010,69 +1029,77 @@ impl ManagedWidget {
/// Returns the region that the widget was last rendered at. /// Returns the region that the widget was last rendered at.
#[must_use] #[must_use]
pub fn last_layout(&self) -> Option<Rect<Px>> { pub fn last_layout(&self) -> Option<Rect<Px>> {
self.tree.layout(self.id()) self.tree.layout(self.node_id)
}
/// Returns the effective styles for the current tree.
#[must_use]
pub fn effective_styles(&self) -> Styles {
self.tree.effective_styles(self.node_id)
} }
/// Returns true if this widget is the currently active widget. /// Returns true if this widget is the currently active widget.
#[must_use] #[must_use]
pub fn active(&self) -> bool { pub fn active(&self) -> bool {
self.tree.active_widget() == Some(self.id()) self.tree.active_widget() == Some(self.node_id)
} }
/// Returns true if this widget is currently the hovered widget. /// Returns true if this widget is currently the hovered widget.
#[must_use] #[must_use]
pub fn hovered(&self) -> bool { pub fn hovered(&self) -> bool {
self.tree.is_hovered(self.id()) self.tree.is_hovered(self.node_id)
} }
/// Returns true if this widget that is directly beneath the cursor. /// Returns true if this widget that is directly beneath the cursor.
#[must_use] #[must_use]
pub fn primary_hover(&self) -> bool { pub fn primary_hover(&self) -> bool {
self.tree.hovered_widget() == Some(self.id()) self.tree.hovered_widget() == Some(self.node_id)
} }
/// Returns true if this widget is the currently focused widget. /// Returns true if this widget is the currently focused widget.
#[must_use] #[must_use]
pub fn focused(&self) -> bool { pub fn focused(&self) -> bool {
self.tree.focused_widget() == Some(self.id()) self.tree.focused_widget() == Some(self.node_id)
} }
/// Returns the parent of this widget. /// Returns the parent of this widget.
#[must_use] #[must_use]
pub fn parent(&self) -> Option<ManagedWidget> { pub fn parent(&self) -> Option<ManagedWidget> {
self.tree self.tree
.parent(self.id()) .parent(self.node_id)
.and_then(|id| self.tree.widget(id)) .and_then(|id| self.tree.widget_from_node(id))
} }
/// Returns true if this node has a parent. /// Returns true if this node has a parent.
#[must_use] #[must_use]
pub fn has_parent(&self) -> bool { pub fn has_parent(&self) -> bool {
self.tree.parent(self.id()).is_some() self.tree.parent(self.node_id).is_some()
} }
pub(crate) fn attach_styles(&self, styles: Value<Styles>) { pub(crate) fn attach_styles(&self, styles: Value<Styles>) {
self.tree.attach_styles(self.id(), styles); self.tree.attach_styles(self.node_id, styles);
} }
pub(crate) fn attach_theme(&self, theme: Value<ThemePair>) { pub(crate) fn attach_theme(&self, theme: Value<ThemePair>) {
self.tree.attach_theme(self.id(), theme); self.tree.attach_theme(self.node_id, theme);
} }
pub(crate) fn attach_theme_mode(&self, theme: Value<ThemeMode>) { pub(crate) fn attach_theme_mode(&self, theme: Value<ThemeMode>) {
self.tree.attach_theme_mode(self.id(), theme); self.tree.attach_theme_mode(self.node_id, theme);
} }
pub(crate) fn overidden_theme(&self) -> (Option<Value<ThemePair>>, Option<Value<ThemeMode>>) { pub(crate) fn overidden_theme(
self.tree.overriden_theme(self.id()) &self,
) -> (Styles, Option<Value<ThemePair>>, Option<Value<ThemeMode>>) {
self.tree.overriden_theme(self.node_id)
} }
pub(crate) fn reset_child_layouts(&self) { pub(crate) fn reset_child_layouts(&self) {
self.tree.reset_child_layouts(self.id()); self.tree.reset_child_layouts(self.node_id);
} }
pub(crate) fn visually_ordered_children(&self, order: VisualOrder) -> Vec<ManagedWidget> { pub(crate) fn visually_ordered_children(&self, order: VisualOrder) -> Vec<ManagedWidget> {
self.tree.visually_ordered_children(self.id(), order) self.tree.visually_ordered_children(self.node_id, order)
} }
} }

View file

@ -77,22 +77,10 @@ impl Button {
} }
fn update_colors(&mut self, context: &WidgetContext<'_, '_>, immediate: bool) { fn update_colors(&mut self, context: &WidgetContext<'_, '_>, immediate: bool) {
let styles = context.query_styles(&[
&ButtonActiveBackground,
&ButtonBackground,
&ButtonHoverBackground,
&ButtonDisabledBackground,
&ButtonActiveForeground,
&ButtonForeground,
&ButtonHoverForeground,
&ButtonDisabledForeground,
&Easing,
]);
let (background_color, text_color) = match () { let (background_color, text_color) = match () {
() if !self.enabled.get() => ( () if !self.enabled.get() => (
styles.get(&ButtonDisabledBackground, context), context.get(&ButtonDisabledBackground),
styles.get(&ButtonDisabledForeground, context), context.get(&ButtonDisabledForeground),
), ),
// TODO this probably should use actual style. // TODO this probably should use actual style.
() if context.is_default() => ( () if context.is_default() => (
@ -100,16 +88,16 @@ impl Button {
context.theme().primary.on_color, context.theme().primary.on_color,
), ),
() if context.active() => ( () if context.active() => (
styles.get(&ButtonActiveBackground, context), context.get(&ButtonActiveBackground),
styles.get(&ButtonActiveForeground, context), context.get(&ButtonActiveForeground),
), ),
() if context.hovered() => ( () if context.hovered() => (
styles.get(&ButtonHoverBackground, context), context.get(&ButtonHoverBackground),
styles.get(&ButtonHoverForeground, context), context.get(&ButtonHoverForeground),
), ),
() => ( () => (
styles.get(&ButtonBackground, context), context.get(&ButtonBackground),
styles.get(&ButtonForeground, context), context.get(&ButtonForeground),
), ),
}; };
@ -120,7 +108,7 @@ impl Button {
text.transition_to(text_color), text.transition_to(text_color),
) )
.over(Duration::from_millis(150)) .over(Duration::from_millis(150))
.with_easing(styles.get(&Easing, context)) .with_easing(context.get(&Easing))
.spawn(); .spawn();
} }
(true, Some(bg), Some(text)) => { (true, Some(bg), Some(text)) => {
@ -177,7 +165,7 @@ impl Widget for Button {
} }
fn accept_focus(&mut self, context: &mut EventContext<'_, '_>) -> bool { fn accept_focus(&mut self, context: &mut EventContext<'_, '_>) -> bool {
self.enabled.get() && context.query_style(&AutoFocusableControls).is_all() self.enabled.get() && context.get(&AutoFocusableControls).is_all()
} }
fn mouse_down( fn mouse_down(
@ -241,7 +229,7 @@ impl Widget for Button {
context: &mut LayoutContext<'_, '_, '_, '_, '_>, context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> { ) -> Size<UPx> {
let padding = context let padding = context
.query_style(&IntrinsicPadding) .get(&IntrinsicPadding)
.into_px(context.gfx.scale()) .into_px(context.gfx.scale())
.into_unsigned(); .into_unsigned();
let mounted = self.content.mounted(&mut context.as_event_context()); let mounted = self.content.mounted(&mut context.as_event_context());

View file

@ -148,7 +148,7 @@ impl Container {
fn padding(&self, context: &GraphicsContext<'_, '_, '_, '_, '_>) -> Edges<Px> { fn padding(&self, context: &GraphicsContext<'_, '_, '_, '_, '_>) -> Edges<Px> {
match &self.padding { match &self.padding {
Some(padding) => padding.get(), Some(padding) => padding.get(),
None => Edges::from(context.query_style(&IntrinsicPadding)), None => Edges::from(context.get(&IntrinsicPadding)),
} }
.map(|dim| dim.into_px(context.gfx.scale())) .map(|dim| dim.into_px(context.gfx.scale()))
} }
@ -163,12 +163,12 @@ impl WrapperWidget for Container {
let background = match self.background.get() { let background = match self.background.get() {
ContainerBackground::Color(color) => EffectiveBackground::Color(color), ContainerBackground::Color(color) => EffectiveBackground::Color(color),
ContainerBackground::Level(level) => EffectiveBackground::Level(level), ContainerBackground::Level(level) => EffectiveBackground::Level(level),
ContainerBackground::Auto => EffectiveBackground::Level( ContainerBackground::Auto => {
match context.query_parent_style(&CurrentContainerBackground) { EffectiveBackground::Level(match context.get(&CurrentContainerBackground) {
EffectiveBackground::Color(_) => ContainerLevel::default(), EffectiveBackground::Color(_) => ContainerLevel::default(),
EffectiveBackground::Level(level) => level.next().unwrap_or_default(), EffectiveBackground::Level(level) => level.next().unwrap_or_default(),
}, })
), }
}; };
if self.effective_background != Some(background) { if self.effective_background != Some(background) {
@ -253,6 +253,6 @@ impl From<EffectiveBackground> for Component {
define_components! { define_components! {
Container { Container {
/// The container background behind the current widget. /// The container background behind the current widget.
CurrentContainerBackground(EffectiveBackground, "background", |context| EffectiveBackground::Color(context.query_style(&SurfaceColor))) CurrentContainerBackground(EffectiveBackground, "background", |context| EffectiveBackground::Color(context.get(&SurfaceColor)))
} }
} }

View file

@ -18,7 +18,6 @@ use kludgine::{Color, Kludgine};
use crate::context::{EventContext, LayoutContext, WidgetContext}; use crate::context::{EventContext, LayoutContext, WidgetContext};
use crate::styles::components::{HighlightColor, LineHeight, OutlineColor, TextColor, TextSize}; use crate::styles::components::{HighlightColor, LineHeight, OutlineColor, TextColor, TextSize};
use crate::styles::Styles;
use crate::utils::ModifiersExt; use crate::utils::ModifiersExt;
use crate::value::{Generation, IntoValue, Value}; use crate::value::{Generation, IntoValue, Value};
use crate::widget::{Callback, EventHandling, Widget, HANDLED, IGNORED}; use crate::widget::{Callback, EventHandling, Widget, HANDLED, IGNORED};
@ -71,7 +70,6 @@ impl Input {
fn editor_mut( fn editor_mut(
&mut self, &mut self,
kludgine: &mut Kludgine, kludgine: &mut Kludgine,
styles: &Styles,
context: &WidgetContext<'_, '_>, context: &WidgetContext<'_, '_>,
) -> &mut Editor { ) -> &mut Editor {
match (&self.editor, self.text.generation()) { match (&self.editor, self.text.generation()) {
@ -81,8 +79,8 @@ impl Input {
let mut buffer = Buffer::new( let mut buffer = Buffer::new(
kludgine.font_system(), kludgine.font_system(),
Metrics::new( Metrics::new(
styles.get(&TextSize, context).into_px(scale).into_float(), context.get(&TextSize).into_px(scale).into_float(),
styles.get(&LineHeight, context).into_px(scale).into_float(), context.get(&LineHeight).into_px(scale).into_float(),
), ),
); );
self.text.map(|text| { self.text.map(|text| {
@ -103,10 +101,6 @@ impl Input {
&mut self.editor.as_mut().expect("just initialized").editor &mut self.editor.as_mut().expect("just initialized").editor
} }
fn styles(context: &WidgetContext<'_, '_>) -> Styles {
context.query_styles(&[&TextColor, &TextSize, &LineHeight])
}
fn select_all(&mut self) { fn select_all(&mut self) {
let Some(editor) = self.editor.as_mut().map(|editor| &mut editor.editor) else { let Some(editor) = self.editor.as_mut().map(|editor| &mut editor.editor) else {
return; return;
@ -157,15 +151,13 @@ impl Widget for Input {
self.mouse_buttons_down += 1; self.mouse_buttons_down += 1;
context.focus(); context.focus();
self.needs_to_select_all = false; self.needs_to_select_all = false;
let styles = context.query_styles(&[&TextColor]); self.editor_mut(context.kludgine, &context.widget).action(
self.editor_mut(context.kludgine, &styles, &context.widget) context.kludgine.font_system(),
.action( Action::Click {
context.kludgine.font_system(), x: location.x.0,
Action::Click { y: location.y.0,
x: location.x.0, },
y: location.y.0, );
},
);
context.set_needs_redraw(); context.set_needs_redraw();
HANDLED HANDLED
} }
@ -177,15 +169,13 @@ impl Widget for Input {
_button: kludgine::app::winit::event::MouseButton, _button: kludgine::app::winit::event::MouseButton,
context: &mut EventContext<'_, '_>, context: &mut EventContext<'_, '_>,
) { ) {
let styles = context.query_styles(&[&TextColor]); self.editor_mut(context.kludgine, &context.widget).action(
self.editor_mut(context.kludgine, &styles, &context.widget) context.kludgine.font_system(),
.action( Action::Drag {
context.kludgine.font_system(), x: location.x.0,
Action::Drag { y: location.y.0,
x: location.x.0, },
y: location.y.0, );
},
);
self.cursor_state.force_on(); self.cursor_state.force_on();
context.set_needs_redraw(); context.set_needs_redraw();
} }
@ -205,9 +195,8 @@ impl Widget for Input {
self.cursor_state.update(context.elapsed()); self.cursor_state.update(context.elapsed());
let cursor_state = self.cursor_state; let cursor_state = self.cursor_state;
let size = context.gfx.size(); let size = context.gfx.size();
let styles = context.query_styles(&[&TextColor, &HighlightColor, &OutlineColor]); let highlight = context.get(&HighlightColor);
let highlight = styles.get(&HighlightColor, context); let editor = self.editor_mut(&mut context.gfx, &context.widget);
let editor = self.editor_mut(&mut context.gfx, &styles, &context.widget);
let cursor = editor.cursor(); let cursor = editor.cursor();
let selection = editor.select_opt(); let selection = editor.select_opt();
let buffer = editor.buffer_mut(); let buffer = editor.buffer_mut();
@ -219,7 +208,7 @@ impl Widget for Input {
buffer.shape_until_scroll(context.gfx.font_system()); buffer.shape_until_scroll(context.gfx.font_system());
if context.focused() { if context.focused() {
context.draw_focus_ring_using(&styles); context.draw_focus_ring();
context.set_ime_allowed(true); context.set_ime_allowed(true);
let line_height = Px::from_float(buffer.metrics().line_height); let line_height = Px::from_float(buffer.metrics().line_height);
if let Some(selection) = selection { if let Some(selection) = selection {
@ -346,11 +335,11 @@ impl Widget for Input {
} }
} }
} else { } else {
let outline_color = styles.get(&OutlineColor, context); let outline_color = context.get(&OutlineColor);
context.stroke_outline::<Lp>(outline_color, StrokeOptions::default()); context.stroke_outline::<Lp>(outline_color, StrokeOptions::default());
} }
let text_color = styles.get(&TextColor, context); let text_color = context.get(&TextColor);
context.gfx.draw_text_buffer( context.gfx.draw_text_buffer(
buffer, buffer,
text_color, text_color,
@ -366,12 +355,11 @@ impl Widget for Input {
available_space: Size<ConstraintLimit>, available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>, context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> { ) -> Size<UPx> {
let styles = context.query_styles(&[&TextColor]);
if self.needs_to_select_all { if self.needs_to_select_all {
self.needs_to_select_all = false; self.needs_to_select_all = false;
self.select_all(); self.select_all();
} }
let editor = self.editor_mut(&mut context.graphics.gfx, &styles, &context.graphics.widget); let editor = self.editor_mut(&mut context.graphics.gfx, &context.graphics.widget);
let buffer = editor.buffer_mut(); let buffer = editor.buffer_mut();
buffer.set_size( buffer.set_size(
context.gfx.font_system(), context.gfx.font_system(),
@ -396,8 +384,7 @@ impl Widget for Input {
on_key.invoke(input.clone())?; on_key.invoke(input.clone())?;
} }
let styles = context.query_styles(&[&TextColor]); let editor = self.editor_mut(context.kludgine, &context.widget);
let editor = self.editor_mut(context.kludgine, &styles, &context.widget);
// println!( // println!(
// "Keyboard input: {:?}. {:?}, {:?}", // "Keyboard input: {:?}. {:?}, {:?}",
@ -489,12 +476,8 @@ impl Widget for Input {
tracing::warn!("TODO: preview IME input {text}, cursor: {cursor:?}"); tracing::warn!("TODO: preview IME input {text}, cursor: {cursor:?}");
} }
Ime::Commit(text) => { Ime::Commit(text) => {
self.editor_mut( self.editor_mut(context.kludgine, &context.widget)
context.kludgine, .insert_string(&text, None);
&Self::styles(&context.widget),
&context.widget,
)
.insert_string(&text, None);
context.set_needs_redraw(); context.set_needs_redraw();
} }
} }

View file

@ -60,7 +60,7 @@ 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 text_color = context.query_style(&TextColor); let text_color = context.get(&TextColor);
let prepared_text = self.prepared_text(context, text_color, size.width); let prepared_text = self.prepared_text(context, text_color, size.width);
@ -74,12 +74,11 @@ impl Widget for Label {
available_space: Size<ConstraintLimit>, available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>, context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> { ) -> Size<UPx> {
let styles = context.query_styles(&[&TextColor, &IntrinsicPadding]); let padding = context
let padding = styles .get(&IntrinsicPadding)
.get(&IntrinsicPadding, context)
.into_px(context.gfx.scale()) .into_px(context.gfx.scale())
.into_unsigned(); .into_unsigned();
let color = styles.get(&TextColor, context); let color = context.get(&TextColor);
let width = available_space.width.max().try_into().unwrap_or(Px::MAX); let width = available_space.width.max().try_into().unwrap_or(Px::MAX);
let prepared = self.prepared_text(context, color, width); let prepared = self.prepared_text(context, color, width);

View file

@ -86,18 +86,17 @@ impl Scroll {
} }
fn show_scrollbars(&mut self, context: &mut EventContext<'_, '_>) { fn show_scrollbars(&mut self, context: &mut EventContext<'_, '_>) {
let styles = context.query_styles(&[&EasingIn, &EasingOut]);
self.scrollbar_opacity_animation = self self.scrollbar_opacity_animation = self
.scrollbar_opacity .scrollbar_opacity
.transition_to(ZeroToOne::ONE) .transition_to(ZeroToOne::ONE)
.over(Duration::from_millis(300)) .over(Duration::from_millis(300))
.with_easing(styles.get(&EasingIn, context)) .with_easing(context.get(&EasingIn))
.and_then(Duration::from_secs(1)) .and_then(Duration::from_secs(1))
.and_then( .and_then(
self.scrollbar_opacity self.scrollbar_opacity
.transition_to(ZeroToOne::ZERO) .transition_to(ZeroToOne::ZERO)
.over(Duration::from_millis(300)) .over(Duration::from_millis(300))
.with_easing(styles.get(&EasingOut, context)), .with_easing(context.get(&EasingOut)),
) )
.spawn(); .spawn();
} }
@ -117,7 +116,7 @@ impl Widget for Scroll {
.scrollbar_opacity .scrollbar_opacity
.transition_to(ZeroToOne::ZERO) .transition_to(ZeroToOne::ZERO)
.over(Duration::from_millis(300)) .over(Duration::from_millis(300))
.with_easing(context.query_style(&EasingOut)) .with_easing(context.get(&EasingOut))
.spawn(); .spawn();
} }
@ -165,13 +164,10 @@ impl Widget for Scroll {
available_space: Size<ConstraintLimit>, available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>, context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> { ) -> Size<UPx> {
let styles = context.query_styles(&[&ScrollBarThickness, &LineHeight]); self.bar_width = context
self.bar_width = styles .get(&ScrollBarThickness)
.get(&ScrollBarThickness, context)
.into_px(context.gfx.scale());
self.line_height = styles
.get(&LineHeight, context)
.into_px(context.gfx.scale()); .into_px(context.gfx.scale());
self.line_height = context.get(&LineHeight).into_px(context.gfx.scale());
let (mut scroll, current_max_scroll) = self.constrain_scroll(); let (mut scroll, current_max_scroll) = self.constrain_scroll();

View file

@ -165,14 +165,12 @@ where
+ 'static, + 'static,
{ {
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
let styles = let track_color = context.get(&TrackColor);
context.query_styles(&[&TrackColor, &InactiveTrackColor, &KnobColor, &TrackSize]); let inactive_track_color = context.get(&InactiveTrackColor);
let track_color = styles.get(&TrackColor, context); let knob_color = context.get(&KnobColor);
let inactive_track_color = styles.get(&InactiveTrackColor, context);
let knob_color = styles.get(&KnobColor, context);
let knob_size = self.knob_size.into_signed(); let knob_size = self.knob_size.into_signed();
let track_size = styles let track_size = context
.get(&TrackSize, context) .get(&TrackSize)
.into_px(context.gfx.scale()) .into_px(context.gfx.scale())
.min(knob_size); .min(knob_size);
@ -224,13 +222,12 @@ where
available_space: Size<ConstraintLimit>, available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>, context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> { ) -> Size<UPx> {
let styles = context.query_styles(&[&KnobSize, &MinimumSliderSize]); self.knob_size = context
self.knob_size = styles .get(&KnobSize)
.get(&KnobSize, context)
.into_px(context.gfx.scale()) .into_px(context.gfx.scale())
.into_unsigned(); .into_unsigned();
let minimum_size = styles let minimum_size = context
.get(&MinimumSliderSize, context) .get(&MinimumSliderSize)
.into_px(context.gfx.scale()) .into_px(context.gfx.scale())
.into_unsigned(); .into_unsigned();
@ -321,12 +318,12 @@ define_components! {
/// The width and height of the draggable portion of a [`Slider`]. /// The width and height of the draggable portion of a [`Slider`].
KnobSize(Dimension, "knob_size", Dimension::Lp(Lp::points(14))) KnobSize(Dimension, "knob_size", Dimension::Lp(Lp::points(14)))
/// The minimum length of the slidable dimension. /// The minimum length of the slidable dimension.
MinimumSliderSize(Dimension, "minimum_size", |context| context.query_style(&KnobSize) * 2) MinimumSliderSize(Dimension, "minimum_size", |context| context.get(&KnobSize) * 2)
/// The color of the draggable portion of the knob. /// The color of the draggable portion of the knob.
KnobColor(Color, "knob_color", .primary.color) // TODO make this pull from a component multiple widgets can share KnobColor(Color, "knob_color", .primary.color) // TODO make this pull from a component multiple widgets can share
/// The color of the track that the knob rests on. /// The color of the track that the knob rests on.
TrackColor(Color,"track_color", |context| context.query_style(&KnobColor)) TrackColor(Color,"track_color", |context| context.get(&KnobColor))
/// The color of the track that the knob rests on. /// The color of the track that the knob rests on.
InactiveTrackColor(Color, "inactive_track_color", |context| context.query_style(&OpaqueWidgetColor)) InactiveTrackColor(Color, "inactive_track_color", |context| context.get(&OpaqueWidgetColor))
} }
} }

View file

@ -2,7 +2,6 @@
//! window. //! window.
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::ops::{Deref, DerefMut, Not}; use std::ops::{Deref, DerefMut, Not};
use std::panic::{AssertUnwindSafe, UnwindSafe}; use std::panic::{AssertUnwindSafe, UnwindSafe};
@ -10,6 +9,8 @@ use std::path::Path;
use std::string::ToString; use std::string::ToString;
use std::sync::OnceLock; use std::sync::OnceLock;
use ahash::AHashMap;
use alot::LotId;
use kludgine::app::winit::dpi::{PhysicalPosition, PhysicalSize}; use kludgine::app::winit::dpi::{PhysicalPosition, PhysicalSize};
use kludgine::app::winit::event::{ use kludgine::app::winit::event::{
DeviceId, ElementState, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase, DeviceId, ElementState, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase,
@ -35,9 +36,7 @@ use crate::styles::ThemePair;
use crate::tree::Tree; use crate::tree::Tree;
use crate::utils::ModifiersExt; use crate::utils::ModifiersExt;
use crate::value::{Dynamic, DynamicReader, IntoDynamic, IntoValue, Value}; use crate::value::{Dynamic, DynamicReader, IntoDynamic, IntoValue, Value};
use crate::widget::{ use crate::widget::{EventHandling, ManagedWidget, Widget, WidgetInstance, HANDLED, IGNORED};
EventHandling, ManagedWidget, Widget, WidgetId, WidgetInstance, HANDLED, IGNORED,
};
use crate::widgets::{Expand, Resize}; use crate::widgets::{Expand, Resize};
use crate::window::sealed::WindowCommand; use crate::window::sealed::WindowCommand;
use crate::{initialize_tracing, ConstraintLimit, Run}; use crate::{initialize_tracing, ConstraintLimit, Run};
@ -300,12 +299,12 @@ where
fn keyboard_activate_widget( fn keyboard_activate_widget(
&mut self, &mut self,
is_pressed: bool, is_pressed: bool,
widget: Option<WidgetId>, widget: Option<LotId>,
window: &mut RunningWindow<'_>, window: &mut RunningWindow<'_>,
kludgine: &mut Kludgine, kludgine: &mut Kludgine,
) { ) {
if is_pressed { if is_pressed {
if let Some(default) = widget.and_then(|id| self.root.tree.widget(id)) { if let Some(default) = widget.and_then(|id| self.root.tree.widget_from_node(id)) {
if let Some(previously_active) = self.keyboard_activated.take() { if let Some(previously_active) = self.keyboard_activated.take() {
EventContext::new( EventContext::new(
WidgetContext::new( WidgetContext::new(
@ -464,7 +463,7 @@ where
mouse_state: MouseState { mouse_state: MouseState {
location: None, location: None,
widget: None, widget: None,
devices: HashMap::default(), devices: AHashMap::default(),
}, },
redraw_status: RedrawStatus::default(), redraw_status: RedrawStatus::default(),
initial_frame: true, initial_frame: true,
@ -670,8 +669,12 @@ where
input: KeyEvent, input: KeyEvent,
is_synthetic: bool, is_synthetic: bool,
) { ) {
let target = self.root.tree.focused_widget().unwrap_or(self.root.id()); let target = self.root.tree.focused_widget().unwrap_or(self.root.node_id);
let target = self.root.tree.widget(target).expect("missing widget"); let target = self
.root
.tree
.widget_from_node(target)
.expect("missing widget");
let mut window = RunningWindow::new(window, &self.focused, &self.occluded); let mut window = RunningWindow::new(window, &self.focused, &self.occluded);
let mut target = EventContext::new( let mut target = EventContext::new(
WidgetContext::new( WidgetContext::new(
@ -701,8 +704,12 @@ where
if input.state.is_pressed() { if input.state.is_pressed() {
let reverse = window.modifiers().state().shift_key(); let reverse = window.modifiers().state().shift_key();
let target = self.root.tree.focused_widget().unwrap_or(self.root.id()); let target = self.root.tree.focused_widget().unwrap_or(self.root.node_id);
let target = self.root.tree.widget(target).expect("missing widget"); let target = self
.root
.tree
.widget_from_node(target)
.expect("missing widget");
let mut target = EventContext::new( let mut target = EventContext::new(
WidgetContext::new( WidgetContext::new(
target, target,
@ -713,7 +720,7 @@ where
), ),
kludgine, kludgine,
); );
let mut visual_order = target.query_style(&LayoutOrder); let mut visual_order = target.get(&LayoutOrder);
if reverse { if reverse {
visual_order = visual_order.rev(); visual_order = visual_order.rev();
} }
@ -761,7 +768,7 @@ where
.root .root
.tree .tree
.hovered_widget() .hovered_widget()
.and_then(|hovered| self.root.tree.widget(hovered)) .and_then(|hovered| self.root.tree.widget_from_node(hovered))
.unwrap_or_else(|| { .unwrap_or_else(|| {
self.root self.root
.tree .tree
@ -797,7 +804,7 @@ where
.root .root
.tree .tree
.focused_widget() .focused_widget()
.and_then(|hovered| self.root.tree.widget(hovered)) .and_then(|hovered| self.root.tree.widget_from_node(hovered))
.unwrap_or_else(|| { .unwrap_or_else(|| {
self.root self.root
.tree .tree
@ -1030,7 +1037,7 @@ fn recursively_handle_event(
struct MouseState { struct MouseState {
location: Option<Point<Px>>, location: Option<Point<Px>>,
widget: Option<ManagedWidget>, widget: Option<ManagedWidget>,
devices: HashMap<DeviceId, HashMap<MouseButton, ManagedWidget>>, devices: AHashMap<DeviceId, AHashMap<MouseButton, ManagedWidget>>,
} }
pub(crate) mod sealed { pub(crate) mod sealed {