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"
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",

View file

@ -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

View file

@ -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::<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
/// 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<WidgetContext<'child, 'window>>,
{
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<Component: ComponentDefinition>(
pub fn get<Component: ComponentDefinition>(
&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<Component: ComponentDefinition>(
&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 {

View file

@ -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};

View file

@ -3,7 +3,8 @@ use std::ops::Deref;
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.
///
@ -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<ahash::RandomState>);
impl Name {
/// Returns a name for the given string.

View file

@ -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<HashMap<Name, HashMap<Name, Value<Component>>>>);
pub struct Styles(Arc<AHashMap<ComponentName, Value<Component>>>);
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);
<Named::ComponentType>::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<Component>`.
@ -141,42 +144,38 @@ impl FromIterator<(ComponentName, Component)> for Styles {
}
impl IntoIterator for Styles {
type IntoIter = StylesIntoIter;
type IntoIter = hash_map::IntoIter<ComponentName, Value<Component>>;
type Item = (ComponentName, Value<Component>);
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<Name, HashMap<Name, Value<Component>>>,
names: Option<(Name, hash_map::IntoIter<Name, Value<Component>>)>,
}
// /// An iterator over the owned contents of a [`Styles`] instance.
// pub struct StylesIntoIter {
// main: hash_map::IntoIter<ComponentName, Value<Component>>,
// }
impl Iterator for StylesIntoIter {
type Item = (ComponentName, Value<Component>);
// impl Iterator for StylesIntoIter {
// type Item = (ComponentName, Value<Component>);
fn next(&mut self) -> Option<Self::Item> {
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<Self::Item> {
// 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<Color> 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,

View file

@ -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<ComponentName> = 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)),+
])
});
};

View file

@ -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<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 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<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);
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<ManagedWidget> {
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::<ManagedWidget>::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<ManagedWidget> {
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
.lock()
.map_or_else(PoisonError::into_inner, |g| g)
.active
}
pub fn hovered_widget(&self) -> Option<WidgetId> {
pub(crate) fn hovered_widget(&self) -> Option<LotId> {
self.data
.lock()
.map_or_else(PoisonError::into_inner, |g| g)
.hover
}
pub fn default_widget(&self) -> Option<WidgetId> {
pub(crate) fn default_widget(&self) -> Option<LotId> {
self.data
.lock()
.map_or_else(PoisonError::into_inner, |g| g)
@ -232,7 +254,7 @@ impl Tree {
.copied()
}
pub fn escape_widget(&self) -> Option<WidgetId> {
pub(crate) fn escape_widget(&self) -> Option<LotId> {
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<WidgetId> {
pub(crate) fn focused_widget(&self) -> Option<LotId> {
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<WidgetId> {
pub(crate) fn parent(&self, id: LotId) -> Option<LotId> {
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);
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);
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);
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<Value<ThemePair>>, Option<Value<ThemeMode>>) {
id: LotId,
) -> (Styles, Option<Value<ThemePair>>, Option<Value<ThemeMode>>) {
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<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 {
@ -350,27 +337,68 @@ pub(crate) struct HoverResults {
#[derive(Default)]
struct TreeData {
nodes: HashMap<WidgetId, Node>,
active: Option<WidgetId>,
focus: Option<WidgetId>,
hover: Option<WidgetId>,
defaults: Vec<WidgetId>,
escapes: Vec<WidgetId>,
render_order: Vec<WidgetId>,
previous_focuses: HashMap<WidgetId, WidgetId>,
nodes: Lots<Node>,
nodes_by_id: AHashMap<WidgetId, LotId>,
active: Option<LotId>,
focus: Option<LotId>,
hover: Option<LotId>,
defaults: Vec<LotId>,
escapes: Vec<LotId>,
render_order: Vec<LotId>,
previous_focuses: AHashMap<LotId, LotId>,
}
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 {
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<ManagedWidget> {
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<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
.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<ManagedWidget> {
pub(crate) fn widget_hierarchy(&self, mut widget: LotId, tree: &Tree) -> Vec<ManagedWidget> {
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<WidgetId>,
property: impl FnOnce(&mut Self) -> &mut Option<LotId>,
) -> Result<Option<ManagedWidget>, ()> {
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::<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 widget: WidgetInstance,
pub children: Vec<WidgetId>,
pub parent: Option<WidgetId>,
pub children: Vec<LotId>,
pub parent: Option<LotId>,
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_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> {
init: fn() -> T,
once: OnceLock<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 {
Self {
init,

View file

@ -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<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.
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<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> 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.
@ -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<Px>) {
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<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.
#[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<ManagedWidget> {
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<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>) {
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>) {
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>>) {
self.tree.overriden_theme(self.id())
pub(crate) fn overidden_theme(
&self,
) -> (Styles, Option<Value<ThemePair>>, Option<Value<ThemeMode>>) {
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<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) {
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<UPx> {
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());

View file

@ -148,7 +148,7 @@ impl Container {
fn padding(&self, context: &GraphicsContext<'_, '_, '_, '_, '_>) -> Edges<Px> {
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<EffectiveBackground> 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)))
}
}

View file

@ -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::<Lp>(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<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
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();
}
}

View file

@ -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<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
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);

View file

@ -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<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
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();

View file

@ -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<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
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))
}
}

View file

@ -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<WidgetId>,
widget: Option<LotId>,
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<Point<Px>>,
widget: Option<ManagedWidget>,
devices: HashMap<DeviceId, HashMap<MouseButton, ManagedWidget>>,
devices: AHashMap<DeviceId, AHashMap<MouseButton, ManagedWidget>>,
}
pub(crate) mod sealed {