diff --git a/Cargo.lock b/Cargo.lock index 7fa337a..536883c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,6 +40,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -656,6 +657,7 @@ dependencies = [ name = "gooey" version = "0.1.0" dependencies = [ + "ahash", "alot", "intentional", "interner", diff --git a/Cargo.toml b/Cargo.toml index 6eff0c1..9792341 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ tracing-subscriber = { version = "0.3", optional = true, features = [ "env-filter", ] } palette = "0.7.3" +ahash = "0.8.6" # [patch."https://github.com/khonsulabs/kludgine"] @@ -38,3 +39,6 @@ opt-level = 2 [dev-dependencies] pollster = "0.3.0" + +[profile.release] +debug = true diff --git a/src/context.rs b/src/context.rs index 297f33f..f0555db 100644 --- a/src/context.rs +++ b/src/context.rs @@ -14,9 +14,7 @@ use kludgine::{Color, Kludgine}; use crate::graphics::Graphics; use crate::styles::components::{HighlightColor, WidgetBackground}; -use crate::styles::{ - ComponentDefaultvalue, ComponentDefinition, Styles, Theme, ThemePair, VisualOrder, -}; +use crate::styles::{ComponentDefinition, Styles, Theme, ThemePair, VisualOrder}; use crate::value::{Dynamic, IntoValue, Value}; use crate::widget::{EventHandling, ManagedWidget, WidgetId, WidgetInstance, WidgetRef}; use crate::window::sealed::WindowCommand; @@ -185,7 +183,7 @@ impl<'context, 'window> EventContext<'context, 'window> { let mut activation_changes = 0; while activation_changes < MAX_ITERS { 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; } activation_changes += 1; @@ -222,7 +220,7 @@ impl<'context, 'window> EventContext<'context, 'window> { let mut focus_changes = 0; while focus_changes < MAX_ITERS { 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; } focus_changes += 1; @@ -465,24 +463,16 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, ' } /// Renders the default focus ring for this widget. - /// - /// To ensure the correct color is used, include [`HighlightColor`] in the - /// styles request. - pub fn draw_focus_ring_using(&mut self, styles: &Styles) { + pub fn draw_focus_ring(&mut self) { // If this is the root widget, don't draw a focus ring. It's redundant. if !self.current_node.has_parent() { return; } - let color = styles.get(&HighlightColor, self); + let color = self.get(&HighlightColor); self.stroke_outline::(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 /// context's widget. /// @@ -496,12 +486,12 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, ' "redraw called without set_widget_layout" ); - let background = self.query_style(&WidgetBackground); + let background = self.get(&WidgetBackground); self.gfx.fill(background); self.current_node .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); } } @@ -679,6 +669,7 @@ pub struct WidgetContext<'context, 'window> { theme: Cow<'context, ThemePair>, pending_state: PendingState<'context>, theme_mode: ThemeMode, + effective_styles: Styles, } impl<'context, 'window> WidgetContext<'context, 'window> { @@ -694,12 +685,13 @@ impl<'context, 'window> WidgetContext<'context, 'window> { focus: current_node .tree .focused_widget() - .and_then(|id| current_node.tree.widget(id)), + .and_then(|id| current_node.tree.widget_from_node(id)), active: current_node .tree .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, redraw_status, theme: Cow::Borrowed(theme), @@ -717,6 +709,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> { theme: Cow::Borrowed(self.theme.as_ref()), pending_state: self.pending_state.borrowed(), theme_mode: self.theme_mode, + effective_styles: self.effective_styles.clone(), } } @@ -730,7 +723,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> { Widget::Managed: MapManagedWidget>, { 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 { Cow::Owned(theme.get_tracked(self)) } else { @@ -742,6 +735,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> { self.theme_mode }; WidgetContext { + effective_styles, current_node, redraw_status: self.redraw_status, window: &mut *self.window, @@ -868,7 +862,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> { /// for more information. #[must_use] 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 @@ -879,7 +873,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> { /// for more information. #[must_use] 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. @@ -910,67 +904,17 @@ impl<'context, 'window> WidgetContext<'context, 'window> { 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. /// /// This function traverses up the widget hierarchy looking for the /// component being requested. If a matching component is found, it will be /// returned. Otherwise, the default value will be returned. - #[must_use] - pub fn query_style( + pub fn get( &self, query: &Component, ) -> Component::ComponentType { - self.current_node - .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( - &self, - query: &Component, - ) -> Component::ComponentType { - self.current_node - .tree - .query_style(&self.current_node, query, true, self) + self.effective_styles.get(query, self) } pub(crate) fn handle(&self) -> WindowHandle { diff --git a/src/lib.rs b/src/lib.rs index 7d0ef5e..5f3ddd0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,7 @@ use kludgine::app::winit::error::EventLoopError; use kludgine::figures::units::UPx; use kludgine::figures::{Fraction, IntoUnsigned, ScreenUnit}; pub use names::Name; -pub use utils::WithClone; +pub use utils::{Lazy, WithClone}; pub use self::graphics::Graphics; pub use self::tick::{InputState, Tick}; diff --git a/src/names.rs b/src/names.rs index cefead6..13487da 100644 --- a/src/names.rs +++ b/src/names.rs @@ -3,7 +3,8 @@ use std::ops::Deref; use interner::global::{GlobalString, StringPool}; -static NAMES: StringPool = StringPool::new(); +static NAMES: StringPool = + StringPool::with_hasher(ahash::RandomState::with_seeds(0, 0, 0, 0)); /// 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 /// comparisons. #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct Name(GlobalString); +pub struct Name(GlobalString); impl Name { /// Returns a name for the given string. diff --git a/src/styles.rs b/src/styles.rs index a377b6e..3e66f32 100644 --- a/src/styles.rs +++ b/src/styles.rs @@ -2,7 +2,7 @@ use std::any::Any; use std::borrow::Cow; -use std::collections::{hash_map, HashMap}; +use std::collections::hash_map; use std::fmt::Debug; use std::ops::{ Add, Bound, Div, Mul, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive, @@ -10,6 +10,7 @@ use std::ops::{ use std::panic::{RefUnwindSafe, UnwindSafe}; use std::sync::Arc; +use ahash::AHashMap; use kludgine::figures::units::{Lp, Px, UPx}; use kludgine::figures::{Fraction, IntoUnsigned, Rect, ScreenScale, Size}; use kludgine::Color; @@ -26,7 +27,7 @@ pub mod components; /// A collection of style components organized by their name. #[derive(Clone, Debug, Default)] -pub struct Styles(Arc>>>); +pub struct Styles(Arc>>); impl Styles { /// Returns an empty collection. @@ -39,15 +40,12 @@ impl Styles { /// without reallocating. #[must_use] 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. pub fn insert_named(&mut self, name: ComponentName, component: impl IntoComponentValue) { - Arc::make_mut(&mut self.0) - .entry(name.group) - .or_default() - .insert(name.name, component.into_component_value()); + Arc::make_mut(&mut self.0).insert(name, component.into_component_value()); } /// Inserts a [`Component`] using then name provided. @@ -70,9 +68,7 @@ impl Styles { Named: NamedComponent + ?Sized, { let name = component.name(); - self.0 - .get(&name.group) - .and_then(|group| group.get(&name.name)) + self.0.get(&name) } /// Returns the component associated with the given name, or if not found, @@ -88,14 +84,21 @@ impl Styles { { let name = component.name(); self.0 - .get(&name.group) - .and_then(|group| group.get(&name.name)) + .get(&name) .and_then(|component| { component.redraw_when_changed(context); ::try_from_component(component.get()).ok() }) .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`. @@ -141,42 +144,38 @@ impl FromIterator<(ComponentName, Component)> for Styles { } impl IntoIterator for Styles { - type IntoIter = StylesIntoIter; + type IntoIter = hash_map::IntoIter>; type Item = (ComponentName, Value); fn into_iter(self) -> Self::IntoIter { - StylesIntoIter { - main: Arc::try_unwrap(self.0) - .unwrap_or_else(|err| err.as_ref().clone()) - .into_iter(), - names: None, - } + Arc::try_unwrap(self.0) + .unwrap_or_else(|err| err.as_ref().clone()) + .into_iter() } } -/// An iterator over the owned contents of a [`Styles`] instance. -pub struct StylesIntoIter { - main: hash_map::IntoIter>>, - names: Option<(Name, hash_map::IntoIter>)>, -} +// /// An iterator over the owned contents of a [`Styles`] instance. +// pub struct StylesIntoIter { +// main: hash_map::IntoIter>, +// } -impl Iterator for StylesIntoIter { - type Item = (ComponentName, Value); +// impl Iterator for StylesIntoIter { +// type Item = (ComponentName, Value); - fn next(&mut self) -> Option { - loop { - if let Some((group, names)) = &mut self.names { - if let Some((name, component)) = names.next() { - return Some((ComponentName::new(group.clone(), name), component)); - } - self.names = None; - } +// fn next(&mut self) -> Option { +// loop { +// if let Some((group, names)) = &mut self.names { +// if let Some((name, component)) = names.next() { +// return Some((ComponentName::new(group.clone(), name), component)); +// } +// self.names = None; +// } - let (group, names) = self.main.next()?; - self.names = Some((group, names.into_iter())); - } - } -} +// let (group, names) = self.main.next()?; +// self.names = Some((group, names.into_iter())); +// } +// } +// } /// A value of a style component. #[derive(Debug, Clone)] @@ -189,8 +188,6 @@ pub enum Component { DimensionRange(DimensionRange), /// A percentage between 0.0 and 1.0. Percent(ZeroToOne), - /// A custom component type. - Custom(CustomComponent), /// An easing function for animations. Easing(EasingFunction), /// A visual ordering to use for layout. @@ -200,6 +197,9 @@ pub enum Component { /// A description of the depth of a /// [`Container`](crate::widgets::Container). ContainerLevel(ContainerLevel), + + /// A custom component type. + Custom(CustomComponent), } impl From for Component { @@ -614,7 +614,7 @@ where } /// A fully-qualified style component name. -#[derive(Clone, Eq, PartialEq, Debug)] +#[derive(Clone, Eq, PartialEq, Debug, Hash)] pub struct ComponentName { /// The group name. pub group: Name, diff --git a/src/styles/components.rs b/src/styles/components.rs index 7e68fda..02d15a2 100644 --- a/src/styles/components.rs +++ b/src/styles/components.rs @@ -44,11 +44,13 @@ macro_rules! define_components { const _: () = { use $crate::styles::{ComponentDefinition, ComponentName, NamedComponent}; use $crate::context::WidgetContext; + use $crate::Lazy; use ::std::borrow::Cow; impl NamedComponent for $component { fn name(&self) -> Cow<'_, ComponentName> { - Cow::Owned(ComponentName::new(stringify!($widget), $name)) + static NAME: Lazy = Lazy::new(|| ComponentName::new(stringify!($widget), $name)); + Cow::Borrowed(&*NAME) } } @@ -69,14 +71,13 @@ macro_rules! define_components { } }; ($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),+ $(,)?)) => { define_components!($type, |context| { use $crate::styles::ColorExt; - let styles = context.query_parent_styles(&[&$bg, $(&$fg),*]); - styles.get(&$bg, context).most_contrasting(&[ - $(styles.get(&$fg, context)),+ + context.get(&$bg).most_contrasting(&[ + $(context.get(&$fg)),+ ]) }); }; diff --git a/src/tree.rs b/src/tree.rs index 69188a1..83c7aeb 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1,14 +1,12 @@ -use std::collections::HashMap; use std::mem; use std::sync::{Arc, Mutex, PoisonError}; +use ahash::AHashMap; +use alot::{LotId, Lots}; use kludgine::figures::units::Px; use kludgine::figures::{Point, Rect}; -use crate::context::WidgetContext; -use crate::styles::{ - ComponentDefaultvalue, ComponentDefinition, ComponentType, Styles, ThemePair, VisualOrder, -}; +use crate::styles::{Styles, ThemePair, VisualOrder}; use crate::value::Value; use crate::widget::{ManagedWidget, WidgetId, WidgetInstance}; use crate::window::ThemeMode; @@ -26,32 +24,44 @@ impl Tree { ) -> ManagedWidget { let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let id = widget.id(); - data.nodes.insert( - id, - Node { - widget: widget.clone(), - children: Vec::new(), - parent: parent.map(ManagedWidget::id), - layout: None, - styles: None, - theme: None, - theme_mode: None, - }, - ); + let (effective_styles, parent_id) = if let Some(parent) = parent { + ( + data.nodes[parent.node_id].child_styles(), + Some(parent.node_id), + ) + } else { + (Styles::default(), None) + }; + let node_id = data.nodes.push(Node { + 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() { - data.defaults.push(id); + data.defaults.push(node_id); } if widget.is_escape() { - data.escapes.push(id); + data.escapes.push(node_id); } - if let Some(parent) = parent { - let parent = data.nodes.get_mut(&parent.id()).expect("missing parent"); - parent.children.push(id); + if let Some(parent) = parent_id { + let parent = &mut data.nodes[parent]; + parent.children.push(node_id); } - if let Some(next_focus) = widget.next_focus() { - data.previous_focuses.insert(next_focus, id); + if let Some(next_focus) = widget + .next_focus() + .and_then(|id| data.nodes_by_id.get(&id)) + .copied() + { + data.previous_focuses.insert(next_focus, node_id); } ManagedWidget { + node_id, widget, tree: self.clone(), } @@ -59,37 +69,37 @@ impl Tree { pub fn remove_child(&self, child: &ManagedWidget, parent: &ManagedWidget) { 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() { - data.defaults.retain(|id| *id != child.id()); + data.defaults.retain(|id| *id != child.node_id); } 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) { + pub(crate) fn set_layout(&self, widget: LotId, rect: Rect) { 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); let mut children_to_offset = node.children.clone(); while let Some(child) = children_to_offset.pop() { if let Some(layout) = data .nodes - .get_mut(&child) + .get_mut(child) .and_then(|child| child.layout.as_mut()) { 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> { + pub(crate) fn layout(&self, widget: LotId) -> Option> { 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) { @@ -97,26 +107,26 @@ impl Tree { 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); 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 children = data.nodes[&parent].children.clone(); + let children = data.nodes[parent].children.clone(); 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( &self, - parent: WidgetId, + parent: LotId, order: VisualOrder, ) -> Vec { 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 ordered = Vec::::with_capacity(unordered.len()); loop { @@ -126,7 +136,7 @@ impl Tree { let mut index = 0; 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); continue; }; @@ -146,13 +156,13 @@ impl Tree { index = 0; let row_base = ordered.len(); while index < unordered.len() { - let top_left = data.nodes[&unordered[index]] + let top_left = data.nodes[unordered[index]] .layout .expect("all have layouts") .origin; if min_vertical <= top_left.y && top_left.y <= max_vertical { ordered.push( - data.widget(unordered.remove(index), self) + data.widget_from_node(unordered.remove(index), self) .expect("widget is owned"), ); } else { @@ -161,22 +171,29 @@ impl Tree { } ordered[row_base..].sort_unstable_by_key(|managed| { - order - .horizontal - .sort_key(&data.nodes[&managed.id()].layout.expect("all have layouts")) + order.horizontal.sort_key( + &data.nodes[managed.node_id] + .layout + .expect("all have layouts"), + ) }); } 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 { let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); 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(); let unhovered = match data.update_tracked_widget(new_hover, self, |data| &mut data.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 // need to have their events fired again. let mut new_index = 0; @@ -206,24 +223,29 @@ impl Tree { pub fn widget(&self, id: WidgetId) -> Option { 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 { + pub(crate) fn widget_from_node(&self, id: LotId) -> Option { + 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 { self.data .lock() .map_or_else(PoisonError::into_inner, |g| g) .active } - pub fn hovered_widget(&self) -> Option { + pub(crate) fn hovered_widget(&self) -> Option { self.data .lock() .map_or_else(PoisonError::into_inner, |g| g) .hover } - pub fn default_widget(&self) -> Option { + pub(crate) fn default_widget(&self) -> Option { self.data .lock() .map_or_else(PoisonError::into_inner, |g| g) @@ -232,7 +254,7 @@ impl Tree { .copied() } - pub fn escape_widget(&self) -> Option { + pub(crate) fn escape_widget(&self) -> Option { self.data .lock() .map_or_else(PoisonError::into_inner, |g| g) @@ -241,20 +263,20 @@ impl Tree { .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 mut search = data.hover; while let Some(hovered) = search { if hovered == id { return true; } - search = data.nodes.get(&hovered).and_then(|node| node.parent); + search = data.nodes.get(hovered).and_then(|node| node.parent); } false } - pub fn focused_widget(&self) -> Option { + pub(crate) fn focused_widget(&self) -> Option { self.data .lock() .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 mut hits = Vec::new(); 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) { - hits.push(ManagedWidget { - widget: data.nodes[id].widget.clone(), - tree: self.clone(), - }); + hits.push(data.widget_from_node(*id, self).expect("just accessed")); } } } hits } - pub(crate) fn parent(&self, id: WidgetId) -> Option { + pub(crate) fn parent(&self, id: LotId) -> Option { 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) { + pub(crate) fn attach_styles(&self, id: LotId, styles: Value) { 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) { + pub(crate) fn attach_theme(&self, id: LotId, theme: Value) { 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) { + pub(crate) fn attach_theme_mode(&self, id: LotId, theme: Value) { 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( &self, - id: WidgetId, - ) -> (Option>, Option>) { + id: LotId, + ) -> (Styles, Option>, Option>) { 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(), - data.nodes - .get(&id) - .expect("missing widget") - .theme_mode - .clone(), + node.effective_styles.clone(), + node.theme.clone(), + node.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( - &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 { @@ -350,27 +337,68 @@ pub(crate) struct HoverResults { #[derive(Default)] struct TreeData { - nodes: HashMap, - active: Option, - focus: Option, - hover: Option, - defaults: Vec, - escapes: Vec, - render_order: Vec, - previous_focuses: HashMap, + nodes: Lots, + nodes_by_id: AHashMap, + active: Option, + focus: Option, + hover: Option, + defaults: Vec, + escapes: Vec, + render_order: Vec, + previous_focuses: AHashMap, } impl TreeData { - fn widget(&self, id: WidgetId, tree: &Tree) -> Option { + fn widget_from_id(&self, id: WidgetId, tree: &Tree) -> Option { + let node_id = *self.nodes_by_id.get(&id)?; Some(ManagedWidget { - widget: self.nodes.get(&id)?.widget.clone(), + node_id, + widget: self.nodes[node_id].widget.clone(), tree: tree.clone(), }) } - fn remove_child(&mut self, child: WidgetId, parent: WidgetId) { - let removed_node = self.nodes.remove(&child).expect("widget already removed"); - let parent = self.nodes.get_mut(&parent).expect("missing widget"); + fn widget_from_node(&self, node_id: LotId, tree: &Tree) -> Option { + Some(ManagedWidget { + node_id, + widget: self.nodes.get(node_id)?.widget.clone(), + tree: tree.clone(), + }) + } + + fn attach_styles(&mut self, id: LotId, styles_value: Value) { + 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) { + 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 .children .iter() @@ -380,21 +408,25 @@ impl TreeData { parent.children.remove(index); let mut detached_nodes = removed_node.children; - if let Some(next_focus) = removed_node.widget.next_focus() { - self.previous_focuses.remove(&next_focus); + if let Some(next_focus) = removed_node + .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() { - 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); } } - pub(crate) fn widget_hierarchy(&self, mut widget: WidgetId, tree: &Tree) -> Vec { + pub(crate) fn widget_hierarchy(&self, mut widget: LotId, tree: &Tree) -> Vec { 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); - 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; }; widget = parent; @@ -409,98 +441,36 @@ impl TreeData { &mut self, new_widget: Option<&ManagedWidget>, tree: &Tree, - property: impl FnOnce(&mut Self) -> &mut Option, + property: impl FnOnce(&mut Self) -> &mut Option, ) -> Result, ()> { match ( - mem::replace(property(self), new_widget.map(ManagedWidget::id)), + mem::replace(property(self), new_widget.map(|w| w.node_id)), new_widget, ) { - (Some(old_widget), Some(new_widget)) if old_widget == new_widget.id() => Err(()), - (Some(old_widget), _) => Ok(self.nodes.get(&old_widget).map(|node| ManagedWidget { - widget: node.widget.clone(), - tree: tree.clone(), - })), + (Some(old_widget), Some(new_widget)) if old_widget == new_widget.node_id => Err(()), + (Some(old_widget), _) => Ok(self.widget_from_node(old_widget, tree)), (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::>(); - 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( - &self, - mut perspective: WidgetId, - query: &Component, - skip_self: bool, - context: &WidgetContext<'_, '_>, - ) -> Option { - 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) = - ::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 widget: WidgetInstance, - pub children: Vec, - pub parent: Option, + pub children: Vec, + pub parent: Option, pub layout: Option>, - pub styles: Option>, + pub associated_styles: Option>, + pub effective_styles: Styles, pub theme: Option>, pub theme_mode: Option>, } + +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 + } +} diff --git a/src/utils.rs b/src/utils.rs index ec57975..8811dcc 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -101,12 +101,19 @@ impl ModifiersExt for Modifiers { } } +/// A [`OnceLock`]-based lazy initializer. pub struct Lazy { init: fn() -> T, once: OnceLock, } impl Lazy { + /// 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` 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 { Self { init, diff --git a/src/widget.rs b/src/widget.rs index c570711..0ead588 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -8,6 +8,7 @@ use std::panic::UnwindSafe; use std::sync::atomic::{self, AtomicU64}; use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; +use alot::LotId; use kludgine::app::winit::event::{ DeviceId, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase, }; @@ -204,6 +205,15 @@ impl From> for WrappedLayout { } } +impl From> for WrappedLayout { + fn from(size: Size) -> Self { + WrappedLayout { + child: size.into_signed().into(), + size, + } + } +} + /// A [`Widget`] that contains a single child. pub trait WrapperWidget: Debug + Send + UnwindSafe + 'static { /// Returns the child widget. @@ -248,7 +258,15 @@ pub trait WrapperWidget: Debug + Send + UnwindSafe + 'static { available_space: Size, context: &mut LayoutContext<'_, '_, '_, '_, '_>, ) -> WrappedLayout { - size.into() + Size::::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. @@ -965,6 +983,7 @@ where /// A [`Widget`] that has been attached to a widget hierarchy. #[derive(Clone)] pub struct ManagedWidget { + pub(crate) node_id: LotId, pub(crate) widget: WidgetInstance, pub(crate) tree: Tree, } @@ -987,7 +1006,7 @@ impl ManagedWidget { } pub(crate) fn set_layout(&self, rect: Rect) { - self.tree.set_layout(self.id(), rect); + self.tree.set_layout(self.node_id, rect); } /// Returns the unique id of this widget instance. @@ -1010,69 +1029,77 @@ impl ManagedWidget { /// Returns the region that the widget was last rendered at. #[must_use] pub fn last_layout(&self) -> Option> { - 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. #[must_use] 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. #[must_use] 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. #[must_use] 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. #[must_use] 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. #[must_use] pub fn parent(&self) -> Option { self.tree - .parent(self.id()) - .and_then(|id| self.tree.widget(id)) + .parent(self.node_id) + .and_then(|id| self.tree.widget_from_node(id)) } /// Returns true if this node has a parent. #[must_use] 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) { - self.tree.attach_styles(self.id(), styles); + self.tree.attach_styles(self.node_id, styles); } pub(crate) fn attach_theme(&self, theme: Value) { - self.tree.attach_theme(self.id(), theme); + self.tree.attach_theme(self.node_id, theme); } pub(crate) fn attach_theme_mode(&self, theme: Value) { - self.tree.attach_theme_mode(self.id(), theme); + self.tree.attach_theme_mode(self.node_id, theme); } - pub(crate) fn overidden_theme(&self) -> (Option>, Option>) { - self.tree.overriden_theme(self.id()) + pub(crate) fn overidden_theme( + &self, + ) -> (Styles, Option>, Option>) { + self.tree.overriden_theme(self.node_id) } 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 { - self.tree.visually_ordered_children(self.id(), order) + self.tree.visually_ordered_children(self.node_id, order) } } diff --git a/src/widgets/button.rs b/src/widgets/button.rs index 5451cdc..5d69f06 100644 --- a/src/widgets/button.rs +++ b/src/widgets/button.rs @@ -77,22 +77,10 @@ impl Button { } 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 () { () if !self.enabled.get() => ( - styles.get(&ButtonDisabledBackground, context), - styles.get(&ButtonDisabledForeground, context), + context.get(&ButtonDisabledBackground), + context.get(&ButtonDisabledForeground), ), // TODO this probably should use actual style. () if context.is_default() => ( @@ -100,16 +88,16 @@ impl Button { context.theme().primary.on_color, ), () if context.active() => ( - styles.get(&ButtonActiveBackground, context), - styles.get(&ButtonActiveForeground, context), + context.get(&ButtonActiveBackground), + context.get(&ButtonActiveForeground), ), () if context.hovered() => ( - styles.get(&ButtonHoverBackground, context), - styles.get(&ButtonHoverForeground, context), + context.get(&ButtonHoverBackground), + context.get(&ButtonHoverForeground), ), () => ( - styles.get(&ButtonBackground, context), - styles.get(&ButtonForeground, context), + context.get(&ButtonBackground), + context.get(&ButtonForeground), ), }; @@ -120,7 +108,7 @@ impl Button { text.transition_to(text_color), ) .over(Duration::from_millis(150)) - .with_easing(styles.get(&Easing, context)) + .with_easing(context.get(&Easing)) .spawn(); } (true, Some(bg), Some(text)) => { @@ -177,7 +165,7 @@ impl Widget for Button { } 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( @@ -241,7 +229,7 @@ impl Widget for Button { context: &mut LayoutContext<'_, '_, '_, '_, '_>, ) -> Size { let padding = context - .query_style(&IntrinsicPadding) + .get(&IntrinsicPadding) .into_px(context.gfx.scale()) .into_unsigned(); let mounted = self.content.mounted(&mut context.as_event_context()); diff --git a/src/widgets/container.rs b/src/widgets/container.rs index 9e452ba..34eb3ce 100644 --- a/src/widgets/container.rs +++ b/src/widgets/container.rs @@ -148,7 +148,7 @@ impl Container { fn padding(&self, context: &GraphicsContext<'_, '_, '_, '_, '_>) -> Edges { match &self.padding { 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())) } @@ -163,12 +163,12 @@ impl WrapperWidget for Container { 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) { + ContainerBackground::Auto => { + EffectiveBackground::Level(match context.get(&CurrentContainerBackground) { EffectiveBackground::Color(_) => ContainerLevel::default(), EffectiveBackground::Level(level) => level.next().unwrap_or_default(), - }, - ), + }) + } }; if self.effective_background != Some(background) { @@ -253,6 +253,6 @@ impl From for Component { define_components! { Container { /// 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))) } } diff --git a/src/widgets/input.rs b/src/widgets/input.rs index 44482fd..367c7ed 100644 --- a/src/widgets/input.rs +++ b/src/widgets/input.rs @@ -18,7 +18,6 @@ use kludgine::{Color, Kludgine}; use crate::context::{EventContext, LayoutContext, WidgetContext}; use crate::styles::components::{HighlightColor, LineHeight, OutlineColor, TextColor, TextSize}; -use crate::styles::Styles; use crate::utils::ModifiersExt; use crate::value::{Generation, IntoValue, Value}; use crate::widget::{Callback, EventHandling, Widget, HANDLED, IGNORED}; @@ -71,7 +70,6 @@ impl Input { fn editor_mut( &mut self, kludgine: &mut Kludgine, - styles: &Styles, context: &WidgetContext<'_, '_>, ) -> &mut Editor { match (&self.editor, self.text.generation()) { @@ -81,8 +79,8 @@ impl Input { let mut buffer = Buffer::new( kludgine.font_system(), Metrics::new( - styles.get(&TextSize, context).into_px(scale).into_float(), - styles.get(&LineHeight, context).into_px(scale).into_float(), + context.get(&TextSize).into_px(scale).into_float(), + context.get(&LineHeight).into_px(scale).into_float(), ), ); self.text.map(|text| { @@ -103,10 +101,6 @@ impl Input { &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) { let Some(editor) = self.editor.as_mut().map(|editor| &mut editor.editor) else { return; @@ -157,15 +151,13 @@ impl Widget for Input { self.mouse_buttons_down += 1; context.focus(); self.needs_to_select_all = false; - let styles = context.query_styles(&[&TextColor]); - self.editor_mut(context.kludgine, &styles, &context.widget) - .action( - context.kludgine.font_system(), - Action::Click { - x: location.x.0, - y: location.y.0, - }, - ); + self.editor_mut(context.kludgine, &context.widget).action( + context.kludgine.font_system(), + Action::Click { + x: location.x.0, + y: location.y.0, + }, + ); context.set_needs_redraw(); HANDLED } @@ -177,15 +169,13 @@ impl Widget for Input { _button: kludgine::app::winit::event::MouseButton, context: &mut EventContext<'_, '_>, ) { - let styles = context.query_styles(&[&TextColor]); - self.editor_mut(context.kludgine, &styles, &context.widget) - .action( - context.kludgine.font_system(), - Action::Drag { - x: location.x.0, - y: location.y.0, - }, - ); + self.editor_mut(context.kludgine, &context.widget).action( + context.kludgine.font_system(), + Action::Drag { + x: location.x.0, + y: location.y.0, + }, + ); self.cursor_state.force_on(); context.set_needs_redraw(); } @@ -205,9 +195,8 @@ impl Widget for Input { self.cursor_state.update(context.elapsed()); let cursor_state = self.cursor_state; let size = context.gfx.size(); - let styles = context.query_styles(&[&TextColor, &HighlightColor, &OutlineColor]); - let highlight = styles.get(&HighlightColor, context); - let editor = self.editor_mut(&mut context.gfx, &styles, &context.widget); + let highlight = context.get(&HighlightColor); + let editor = self.editor_mut(&mut context.gfx, &context.widget); let cursor = editor.cursor(); let selection = editor.select_opt(); let buffer = editor.buffer_mut(); @@ -219,7 +208,7 @@ impl Widget for Input { buffer.shape_until_scroll(context.gfx.font_system()); if context.focused() { - context.draw_focus_ring_using(&styles); + context.draw_focus_ring(); context.set_ime_allowed(true); let line_height = Px::from_float(buffer.metrics().line_height); if let Some(selection) = selection { @@ -346,11 +335,11 @@ impl Widget for Input { } } } else { - let outline_color = styles.get(&OutlineColor, context); + let outline_color = context.get(&OutlineColor); context.stroke_outline::(outline_color, StrokeOptions::default()); } - let text_color = styles.get(&TextColor, context); + let text_color = context.get(&TextColor); context.gfx.draw_text_buffer( buffer, text_color, @@ -366,12 +355,11 @@ impl Widget for Input { available_space: Size, context: &mut LayoutContext<'_, '_, '_, '_, '_>, ) -> Size { - let styles = context.query_styles(&[&TextColor]); if self.needs_to_select_all { self.needs_to_select_all = false; 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(); buffer.set_size( context.gfx.font_system(), @@ -396,8 +384,7 @@ impl Widget for Input { on_key.invoke(input.clone())?; } - let styles = context.query_styles(&[&TextColor]); - let editor = self.editor_mut(context.kludgine, &styles, &context.widget); + let editor = self.editor_mut(context.kludgine, &context.widget); // println!( // "Keyboard input: {:?}. {:?}, {:?}", @@ -489,12 +476,8 @@ impl Widget for Input { tracing::warn!("TODO: preview IME input {text}, cursor: {cursor:?}"); } Ime::Commit(text) => { - self.editor_mut( - context.kludgine, - &Self::styles(&context.widget), - &context.widget, - ) - .insert_string(&text, None); + self.editor_mut(context.kludgine, &context.widget) + .insert_string(&text, None); context.set_needs_redraw(); } } diff --git a/src/widgets/label.rs b/src/widgets/label.rs index 332e96b..266c9cd 100644 --- a/src/widgets/label.rs +++ b/src/widgets/label.rs @@ -60,7 +60,7 @@ impl Widget for Label { let size = context.gfx.region().size; 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); @@ -74,12 +74,11 @@ impl Widget for Label { available_space: Size, context: &mut LayoutContext<'_, '_, '_, '_, '_>, ) -> Size { - let styles = context.query_styles(&[&TextColor, &IntrinsicPadding]); - let padding = styles - .get(&IntrinsicPadding, context) + let padding = context + .get(&IntrinsicPadding) .into_px(context.gfx.scale()) .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 prepared = self.prepared_text(context, color, width); diff --git a/src/widgets/scroll.rs b/src/widgets/scroll.rs index ffd458c..9728d30 100644 --- a/src/widgets/scroll.rs +++ b/src/widgets/scroll.rs @@ -86,18 +86,17 @@ impl Scroll { } fn show_scrollbars(&mut self, context: &mut EventContext<'_, '_>) { - let styles = context.query_styles(&[&EasingIn, &EasingOut]); self.scrollbar_opacity_animation = self .scrollbar_opacity .transition_to(ZeroToOne::ONE) .over(Duration::from_millis(300)) - .with_easing(styles.get(&EasingIn, context)) + .with_easing(context.get(&EasingIn)) .and_then(Duration::from_secs(1)) .and_then( self.scrollbar_opacity .transition_to(ZeroToOne::ZERO) .over(Duration::from_millis(300)) - .with_easing(styles.get(&EasingOut, context)), + .with_easing(context.get(&EasingOut)), ) .spawn(); } @@ -117,7 +116,7 @@ impl Widget for Scroll { .scrollbar_opacity .transition_to(ZeroToOne::ZERO) .over(Duration::from_millis(300)) - .with_easing(context.query_style(&EasingOut)) + .with_easing(context.get(&EasingOut)) .spawn(); } @@ -165,13 +164,10 @@ impl Widget for Scroll { available_space: Size, context: &mut LayoutContext<'_, '_, '_, '_, '_>, ) -> Size { - let styles = context.query_styles(&[&ScrollBarThickness, &LineHeight]); - self.bar_width = styles - .get(&ScrollBarThickness, context) - .into_px(context.gfx.scale()); - self.line_height = styles - .get(&LineHeight, context) + self.bar_width = context + .get(&ScrollBarThickness) .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(); diff --git a/src/widgets/slider.rs b/src/widgets/slider.rs index 64b1216..bbc076d 100644 --- a/src/widgets/slider.rs +++ b/src/widgets/slider.rs @@ -165,14 +165,12 @@ where + 'static, { fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { - let styles = - context.query_styles(&[&TrackColor, &InactiveTrackColor, &KnobColor, &TrackSize]); - let track_color = styles.get(&TrackColor, context); - let inactive_track_color = styles.get(&InactiveTrackColor, context); - let knob_color = styles.get(&KnobColor, context); + let track_color = context.get(&TrackColor); + let inactive_track_color = context.get(&InactiveTrackColor); + let knob_color = context.get(&KnobColor); let knob_size = self.knob_size.into_signed(); - let track_size = styles - .get(&TrackSize, context) + let track_size = context + .get(&TrackSize) .into_px(context.gfx.scale()) .min(knob_size); @@ -224,13 +222,12 @@ where available_space: Size, context: &mut LayoutContext<'_, '_, '_, '_, '_>, ) -> Size { - let styles = context.query_styles(&[&KnobSize, &MinimumSliderSize]); - self.knob_size = styles - .get(&KnobSize, context) + self.knob_size = context + .get(&KnobSize) .into_px(context.gfx.scale()) .into_unsigned(); - let minimum_size = styles - .get(&MinimumSliderSize, context) + let minimum_size = context + .get(&MinimumSliderSize) .into_px(context.gfx.scale()) .into_unsigned(); @@ -321,12 +318,12 @@ define_components! { /// The width and height of the draggable portion of a [`Slider`]. KnobSize(Dimension, "knob_size", Dimension::Lp(Lp::points(14))) /// The minimum length of the slidable dimension. - MinimumSliderSize(Dimension, "minimum_size", |context| context.query_style(&KnobSize) * 2) + MinimumSliderSize(Dimension, "minimum_size", |context| context.get(&KnobSize) * 2) /// The color of the draggable portion of the knob. KnobColor(Color, "knob_color", .primary.color) // TODO make this pull from a component multiple widgets can share /// The color of the track that the knob rests on. - TrackColor(Color,"track_color", |context| context.query_style(&KnobColor)) + TrackColor(Color,"track_color", |context| context.get(&KnobColor)) /// 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)) } } diff --git a/src/window.rs b/src/window.rs index 0dbbf13..87ac3f9 100644 --- a/src/window.rs +++ b/src/window.rs @@ -2,7 +2,6 @@ //! window. use std::cell::RefCell; -use std::collections::HashMap; use std::ffi::OsStr; use std::ops::{Deref, DerefMut, Not}; use std::panic::{AssertUnwindSafe, UnwindSafe}; @@ -10,6 +9,8 @@ use std::path::Path; use std::string::ToString; use std::sync::OnceLock; +use ahash::AHashMap; +use alot::LotId; use kludgine::app::winit::dpi::{PhysicalPosition, PhysicalSize}; use kludgine::app::winit::event::{ DeviceId, ElementState, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase, @@ -35,9 +36,7 @@ use crate::styles::ThemePair; use crate::tree::Tree; use crate::utils::ModifiersExt; use crate::value::{Dynamic, DynamicReader, IntoDynamic, IntoValue, Value}; -use crate::widget::{ - EventHandling, ManagedWidget, Widget, WidgetId, WidgetInstance, HANDLED, IGNORED, -}; +use crate::widget::{EventHandling, ManagedWidget, Widget, WidgetInstance, HANDLED, IGNORED}; use crate::widgets::{Expand, Resize}; use crate::window::sealed::WindowCommand; use crate::{initialize_tracing, ConstraintLimit, Run}; @@ -300,12 +299,12 @@ where fn keyboard_activate_widget( &mut self, is_pressed: bool, - widget: Option, + widget: Option, window: &mut RunningWindow<'_>, kludgine: &mut Kludgine, ) { 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() { EventContext::new( WidgetContext::new( @@ -464,7 +463,7 @@ where mouse_state: MouseState { location: None, widget: None, - devices: HashMap::default(), + devices: AHashMap::default(), }, redraw_status: RedrawStatus::default(), initial_frame: true, @@ -670,8 +669,12 @@ where input: KeyEvent, is_synthetic: bool, ) { - let target = self.root.tree.focused_widget().unwrap_or(self.root.id()); - let target = self.root.tree.widget(target).expect("missing widget"); + let target = self.root.tree.focused_widget().unwrap_or(self.root.node_id); + let target = self + .root + .tree + .widget_from_node(target) + .expect("missing widget"); let mut window = RunningWindow::new(window, &self.focused, &self.occluded); let mut target = EventContext::new( WidgetContext::new( @@ -701,8 +704,12 @@ where if input.state.is_pressed() { let reverse = window.modifiers().state().shift_key(); - let target = self.root.tree.focused_widget().unwrap_or(self.root.id()); - let target = self.root.tree.widget(target).expect("missing widget"); + let target = self.root.tree.focused_widget().unwrap_or(self.root.node_id); + let target = self + .root + .tree + .widget_from_node(target) + .expect("missing widget"); let mut target = EventContext::new( WidgetContext::new( target, @@ -713,7 +720,7 @@ where ), kludgine, ); - let mut visual_order = target.query_style(&LayoutOrder); + let mut visual_order = target.get(&LayoutOrder); if reverse { visual_order = visual_order.rev(); } @@ -761,7 +768,7 @@ where .root .tree .hovered_widget() - .and_then(|hovered| self.root.tree.widget(hovered)) + .and_then(|hovered| self.root.tree.widget_from_node(hovered)) .unwrap_or_else(|| { self.root .tree @@ -797,7 +804,7 @@ where .root .tree .focused_widget() - .and_then(|hovered| self.root.tree.widget(hovered)) + .and_then(|hovered| self.root.tree.widget_from_node(hovered)) .unwrap_or_else(|| { self.root .tree @@ -1030,7 +1037,7 @@ fn recursively_handle_event( struct MouseState { location: Option>, widget: Option, - devices: HashMap>, + devices: AHashMap>, } pub(crate) mod sealed {