Checkpoint

Progress on tab focus
This commit is contained in:
Jonathan Johnson 2023-11-07 15:16:29 -08:00
parent 8a2dae3f76
commit 5e5d826267
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
12 changed files with 383 additions and 105 deletions

View file

@ -12,11 +12,12 @@ use kludgine::shapes::{Shape, StrokeOptions};
use kludgine::Kludgine; use kludgine::Kludgine;
use crate::graphics::Graphics; use crate::graphics::Graphics;
use crate::styles::components::HighlightColor; use crate::styles::components::{HighlightColor, VisualOrder};
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, Styles}; use crate::styles::{ComponentDefaultvalue, ComponentDefinition, Styles};
use crate::value::Dynamic; use crate::value::Dynamic;
use crate::widget::{EventHandling, ManagedWidget, WidgetId, WidgetInstance}; use crate::widget::{EventHandling, ManagedWidget, WidgetId, WidgetInstance, WidgetRef};
use crate::window::{sealed, RunningWindow}; use crate::window::sealed::WindowCommand;
use crate::window::RunningWindow;
use crate::ConstraintLimit; use crate::ConstraintLimit;
/// A context to an event function. /// A context to an event function.
@ -48,11 +49,17 @@ impl<'context, 'window> EventContext<'context, 'window> {
/// parent widget needs to invoke events on a child widget. This is done by /// parent widget needs to invoke events on a child widget. This is done by
/// creating an `EventContext` pointing to the child and calling the /// creating an `EventContext` pointing to the child and calling the
/// appropriate function to invoke the event. /// appropriate function to invoke the event.
pub fn for_other<'child>( pub fn for_other<'child, Widget>(
&'child mut self, &'child mut self,
widget: ManagedWidget, widget: &Widget,
) -> EventContext<'child, 'window> { ) -> <Widget::Managed as MapManagedWidget<EventContext<'child, 'window>>>::Result
EventContext::new(self.widget.for_other(widget), self.kludgine) where
Widget: ManageWidget,
Widget::Managed: MapManagedWidget<EventContext<'child, 'window>>,
{
widget
.manage(self)
.map(|managed| EventContext::new(self.widget.for_other(&managed), self.kludgine))
} }
/// Invokes [`Widget::hit_test()`](crate::widget::Widget::hit_test) on this /// Invokes [`Widget::hit_test()`](crate::widget::Widget::hit_test) on this
@ -145,11 +152,11 @@ impl<'context, 'window> EventContext<'context, 'window> {
pub(crate) fn hover(&mut self, location: Point<Px>) { pub(crate) fn hover(&mut self, location: Point<Px>) {
let changes = self.current_node.tree.hover(Some(&self.current_node)); let changes = self.current_node.tree.hover(Some(&self.current_node));
for unhovered in changes.unhovered { for unhovered in changes.unhovered {
let mut context = self.for_other(unhovered.clone()); let mut context = self.for_other(&unhovered);
unhovered.lock().as_widget().unhover(&mut context); unhovered.lock().as_widget().unhover(&mut context);
} }
for hover in changes.hovered { for hover in changes.hovered {
let mut context = self.for_other(hover.clone()); let mut context = self.for_other(&hover);
hover.lock().as_widget().hover(location, &mut context); hover.lock().as_widget().hover(location, &mut context);
} }
} }
@ -159,7 +166,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
assert!(changes.hovered.is_empty()); assert!(changes.hovered.is_empty());
for old_hover in changes.unhovered { for old_hover in changes.unhovered {
let mut old_hover_context = self.for_other(old_hover.clone()); let mut old_hover_context = self.for_other(&old_hover);
old_hover.lock().as_widget().unhover(&mut old_hover_context); old_hover.lock().as_widget().unhover(&mut old_hover_context);
} }
} }
@ -170,7 +177,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
let new = match self.current_node.tree.activate(active.as_ref()) { let new = match self.current_node.tree.activate(active.as_ref()) {
Ok(old) => { Ok(old) => {
if let Some(old) = old { if let Some(old) = old {
let mut old_context = self.for_other(old.clone()); let mut old_context = self.for_other(&old);
old.lock().as_widget().deactivate(&mut old_context); old.lock().as_widget().deactivate(&mut old_context);
} }
true true
@ -182,7 +189,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
active active
.lock() .lock()
.as_widget() .as_widget()
.activate(&mut self.for_other(active.clone())); .activate(&mut self.for_other(active));
} }
self.pending_state.active = active; self.pending_state.active = active;
} }
@ -194,19 +201,19 @@ impl<'context, 'window> EventContext<'context, 'window> {
if focus if focus
.lock() .lock()
.as_widget() .as_widget()
.accept_focus(&mut self.for_other(focus.clone())) .accept_focus(&mut self.for_other(&focus))
{ {
break Some(focus); break Some(focus);
} else if let Some(next_focus) = focus.next_focus() { } else if let Some(next_focus) = focus.next_focus() {
focus = next_focus; focus = next_focus;
} else { } else {
break self.next_focus_after(focus); break self.next_focus_after(focus, VisualOrder::left_to_right());
} }
}); });
let new = match self.current_node.tree.focus(focus.as_ref()) { let new = match self.current_node.tree.focus(focus.as_ref()) {
Ok(old) => { Ok(old) => {
if let Some(old) = old { if let Some(old) = old {
let mut old_context = self.for_other(old.clone()); let mut old_context = self.for_other(&old);
old.lock().as_widget().blur(&mut old_context); old.lock().as_widget().blur(&mut old_context);
} }
true true
@ -215,26 +222,27 @@ impl<'context, 'window> EventContext<'context, 'window> {
}; };
if new { if new {
if let Some(focus) = &focus { if let Some(focus) = &focus {
focus focus.lock().as_widget().focus(&mut self.for_other(focus));
.lock()
.as_widget()
.focus(&mut self.for_other(focus.clone()));
} }
self.pending_state.focus = focus; self.pending_state.focus = focus;
} }
} }
} }
fn next_focus_after(&mut self, mut focus: ManagedWidget) -> Option<ManagedWidget> { fn next_focus_after(
&mut self,
mut focus: ManagedWidget,
order: VisualOrder,
) -> Option<ManagedWidget> {
// First, look within the current focus for any focusable children. // First, look within the current focus for any focusable children.
let stop_at = focus.id(); let stop_at = focus.id();
if let Some(focus) = self.next_focus_within(&focus, None, stop_at) { if let Some(focus) = self.next_focus_within(&focus, None, stop_at, order) {
return Some(focus); return Some(focus);
} }
// Now, look for the next widget in each hierarchy // Now, look for the next widget in each hierarchy
let root = loop { let root = loop {
if let Some(focus) = self.next_focus_sibling(&focus, stop_at) { if let Some(focus) = self.next_focus_sibling(&focus, stop_at, order) {
return Some(focus); return Some(focus);
} }
let Some(parent) = focus.parent() else { let Some(parent) = focus.parent() else {
@ -245,15 +253,16 @@ impl<'context, 'window> EventContext<'context, 'window> {
// We've exhausted a forward scan, we can now start searching the final // We've exhausted a forward scan, we can now start searching the final
// parent, which is the root. // parent, which is the root.
self.next_focus_within(&root, None, stop_at) self.next_focus_within(&root, None, stop_at, order)
} }
fn next_focus_sibling( fn next_focus_sibling(
&mut self, &mut self,
focus: &ManagedWidget, focus: &ManagedWidget,
stop_at: WidgetId, stop_at: WidgetId,
order: VisualOrder,
) -> Option<ManagedWidget> { ) -> Option<ManagedWidget> {
self.next_focus_within(&focus.parent()?, Some(focus.id()), stop_at) self.next_focus_within(&focus.parent()?, Some(focus.id()), stop_at, order)
} }
/// Searches for the next focus inside of `focus`, returning `None` if /// Searches for the next focus inside of `focus`, returning `None` if
@ -264,21 +273,22 @@ impl<'context, 'window> EventContext<'context, 'window> {
focus: &ManagedWidget, focus: &ManagedWidget,
start_at: Option<WidgetId>, start_at: Option<WidgetId>,
stop_at: WidgetId, stop_at: WidgetId,
order: VisualOrder,
) -> Option<ManagedWidget> { ) -> Option<ManagedWidget> {
let child_layouts = focus.child_layouts(); let mut children = focus
// TODO visually sort the layouts .visually_ordered_children(order)
.into_iter()
let mut child_layouts = child_layouts.into_iter().peekable(); .peekable();
if let Some(start_at) = start_at { if let Some(start_at) = start_at {
// Skip all children up to `start_at` // Skip all children up to `start_at`
while child_layouts.peek()?.0.id() != start_at { while children.peek()?.id() != start_at {
child_layouts.next(); children.next();
} }
// Skip `start_at` // Skip `start_at`
child_layouts.next(); children.next();
} }
for (child, _layout) in child_layouts { for child in children {
// Ensure we haven't cycled completely. // Ensure we haven't cycled completely.
if stop_at == child.id() { if stop_at == child.id() {
break; break;
@ -287,16 +297,20 @@ impl<'context, 'window> EventContext<'context, 'window> {
if child if child
.lock() .lock()
.as_widget() .as_widget()
.accept_focus(&mut self.for_other(child.clone())) .accept_focus(&mut self.for_other(&child))
{ {
return Some(child); return Some(child);
} else if let Some(focus) = self.next_focus_within(&child, None, stop_at) { } else if let Some(focus) = self.next_focus_within(&child, None, stop_at, order) {
return Some(focus); return Some(focus);
} }
} }
None None
} }
pub fn advance_focus(&mut self, direction: VisualOrder) {
self.pending_state.focus = self.next_focus_after(self.current_node.clone(), direction);
}
} }
impl<'context, 'window> Deref for EventContext<'context, 'window> { impl<'context, 'window> Deref for EventContext<'context, 'window> {
@ -362,19 +376,27 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, '
/// Returns a new `GraphicsContext` that allows invoking graphics functions /// Returns a new `GraphicsContext` that allows invoking graphics functions
/// for `widget`. /// for `widget`.
pub fn for_other<'child>( pub fn for_other<'child, Widget>(
&'child mut self, &'child mut self,
widget: ManagedWidget, widget: &Widget,
) -> GraphicsContext<'child, 'window, 'child, 'gfx, 'pass> { ) -> <Widget::Managed as MapManagedWidget<
let widget = self.widget.for_other(widget); GraphicsContext<'child, 'window, 'child, 'gfx, 'pass>,
let layout = widget.last_layout().map_or_else( >>::Result
|| Rect::from(self.graphics.clip_rect().size).into_signed(), where
|rect| rect - self.graphics.region().origin, Widget: ManageWidget,
); Widget::Managed: MapManagedWidget<GraphicsContext<'child, 'window, 'child, 'gfx, 'pass>>,
GraphicsContext { {
widget, widget.manage(self).map(|widget| {
graphics: Exclusive::Owned(self.graphics.clipped_to(layout)), let widget = self.widget.for_other(&widget);
} let layout = widget.last_layout().map_or_else(
|| Rect::from(self.graphics.clip_rect().size).into_signed(),
|rect| rect - self.graphics.region().origin,
);
GraphicsContext {
widget,
graphics: Exclusive::Owned(self.graphics.clipped_to(layout)),
}
})
} }
/// Returns a new graphics context that renders to the `clip` rectangle. /// Returns a new graphics context that renders to the `clip` rectangle.
@ -468,17 +490,18 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> LayoutContext<'context, 'window, 'cl
/// Returns a new `LayoutContext` that allows invoking layout functions for /// Returns a new `LayoutContext` that allows invoking layout functions for
/// `widget`. /// `widget`.
pub fn for_other<'child, 'widget>( pub fn for_other<'child, Widget>(
&'child mut self, &'child mut self,
widget: ManagedWidget, widget: &Widget,
) -> LayoutContext<'child, 'window, 'child, 'gfx, 'pass> ) -> <Widget::Managed as MapManagedWidget<LayoutContext<'child, 'window, 'child, 'gfx, 'pass>>>::Result
where where
'widget: 'child, Widget: ManageWidget,
Widget::Managed: MapManagedWidget<LayoutContext<'child, 'window, 'child, 'gfx, 'pass>>,
{ {
LayoutContext { widget.manage(self).map(|widget| LayoutContext {
graphics: self.graphics.for_other(widget), graphics: self.graphics.for_other(&widget),
persist_layout: self.persist_layout, persist_layout: self.persist_layout,
} })
} }
/// Invokes [`Widget::layout()`](crate::widget::Widget::layout) on this /// Invokes [`Widget::layout()`](crate::widget::Widget::layout) on this
@ -541,21 +564,21 @@ pub trait AsEventContext<'window> {
pushed_widget pushed_widget
.lock() .lock()
.as_widget() .as_widget()
.mounted(&mut context.for_other(pushed_widget.clone())); .mounted(&mut context.for_other(&pushed_widget));
pushed_widget pushed_widget
} }
/// Removes a widget from the hierarchy. /// Removes a widget from the hierarchy.
fn remove_child(&mut self, child: &ManagedWidget) { fn remove_child(&mut self, child: &ManagedWidget) {
let mut context = self.as_event_context(); let mut context = self.as_event_context();
child
.lock()
.as_widget()
.unmounted(&mut context.for_other(child));
context context
.current_node .current_node
.tree .tree
.remove_child(child, &context.current_node); .remove_child(child, &context.current_node);
child
.lock()
.as_widget()
.unmounted(&mut context.for_other(child.clone()));
} }
} }
@ -616,16 +639,20 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
} }
/// Returns a new context representing `widget`. /// Returns a new context representing `widget`.
pub fn for_other<'child>( pub fn for_other<'child, Widget>(
&'child mut self, &'child mut self,
widget: ManagedWidget, widget: &Widget,
) -> WidgetContext<'child, 'window> { ) -> <Widget::Managed as MapManagedWidget<WidgetContext<'child, 'window>>>::Result
WidgetContext { where
current_node: widget, Widget: ManageWidget,
Widget::Managed: MapManagedWidget<WidgetContext<'child, 'window>>,
{
widget.manage(self).map(|current_node| WidgetContext {
current_node,
redraw_status: self.redraw_status, redraw_status: self.redraw_status,
window: &mut *self.window, window: &mut *self.window,
pending_state: self.pending_state.borrowed(), pending_state: self.pending_state.borrowed(),
} })
} }
pub(crate) fn parent(&self) -> Option<ManagedWidget> { pub(crate) fn parent(&self) -> Option<ManagedWidget> {
@ -792,14 +819,14 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
} }
pub(crate) struct WindowHandle { pub(crate) struct WindowHandle {
kludgine: kludgine::app::WindowHandle<sealed::WindowCommand>, kludgine: kludgine::app::WindowHandle<WindowCommand>,
redraw_status: RedrawStatus, redraw_status: RedrawStatus,
} }
impl WindowHandle { impl WindowHandle {
pub fn redraw(&self) { pub fn redraw(&self) {
if self.redraw_status.should_send_refresh() { if self.redraw_status.should_send_refresh() {
let _result = self.kludgine.send(sealed::WindowCommand::Redraw); let _result = self.kludgine.send(WindowCommand::Redraw);
} }
} }
} }
@ -880,3 +907,57 @@ impl RedrawStatus {
self.refresh_sent.store(false, Ordering::Release); self.refresh_sent.store(false, Ordering::Release);
} }
} }
pub trait ManageWidget {
type Managed: MapManagedWidget<ManagedWidget>;
fn manage(&self, context: &WidgetContext<'_, '_>) -> Self::Managed;
}
impl ManageWidget for WidgetInstance {
type Managed = Option<ManagedWidget>;
fn manage(&self, context: &WidgetContext<'_, '_>) -> Self::Managed {
context.current_node.tree.widget(self.id())
}
}
impl ManageWidget for WidgetRef {
type Managed = Option<ManagedWidget>;
fn manage(&self, context: &WidgetContext<'_, '_>) -> Self::Managed {
match self {
WidgetRef::Unmounted(instance) => context.current_node.tree.widget(instance.id()),
WidgetRef::Mounted(instance) => Some(instance.clone()),
}
}
}
impl ManageWidget for ManagedWidget {
type Managed = Self;
fn manage(&self, _context: &WidgetContext<'_, '_>) -> Self::Managed {
self.clone()
}
}
pub trait MapManagedWidget<T> {
type Result;
fn map(self, map: impl FnOnce(ManagedWidget) -> T) -> Self::Result;
}
impl<T> MapManagedWidget<T> for Option<ManagedWidget> {
type Result = Option<T>;
fn map(self, map: impl FnOnce(ManagedWidget) -> T) -> Self::Result {
self.map(map)
}
}
impl<T> MapManagedWidget<T> for ManagedWidget {
type Result = T;
fn map(self, map: impl FnOnce(ManagedWidget) -> T) -> Self::Result {
map(self)
}
}

View file

@ -1,7 +1,8 @@
//! All style components supported by the built-in widgets. //! All style components supported by the built-in widgets.
use std::borrow::Cow; use std::borrow::Cow;
use kludgine::figures::units::Lp; use kludgine::figures::units::{Lp, Px};
use kludgine::figures::Rect;
use kludgine::Color; use kludgine::Color;
use crate::animation::easings::{EaseInQuadradic, EaseOutQuadradic}; use crate::animation::easings::{EaseInQuadradic, EaseOutQuadradic};
@ -160,3 +161,94 @@ impl ComponentDefinition for EasingOut {
EasingFunction::from(EaseOutQuadradic) EasingFunction::from(EaseOutQuadradic)
} }
} }
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct VisualOrder {
pub horizontal: HorizontalOrder,
pub vertical: VerticalOrder,
}
impl VisualOrder {
#[must_use]
pub const fn right_to_left() -> Self {
Self {
horizontal: HorizontalOrder::RightToLeft,
vertical: VerticalOrder::TopToBottom,
}
}
#[must_use]
pub const fn left_to_right() -> Self {
Self {
horizontal: HorizontalOrder::LeftToRight,
vertical: VerticalOrder::TopToBottom,
}
}
#[must_use]
pub fn rev(self) -> Self {
Self {
horizontal: self.horizontal.rev(),
vertical: self.vertical.rev(),
}
}
}
impl NamedComponent for VisualOrder {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Global>("visual_order"))
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum HorizontalOrder {
LeftToRight,
RightToLeft,
}
impl HorizontalOrder {
#[must_use]
pub fn rev(self) -> Self {
match self {
Self::LeftToRight => Self::RightToLeft,
Self::RightToLeft => Self::LeftToRight,
}
}
pub fn sort_key(self, rect: &Rect<Px>) -> Px {
match self {
HorizontalOrder::LeftToRight => rect.origin.x,
HorizontalOrder::RightToLeft => -(rect.origin.x + rect.size.width),
}
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum VerticalOrder {
TopToBottom,
BottomToTop,
}
impl VerticalOrder {
#[must_use]
pub fn rev(self) -> Self {
match self {
Self::TopToBottom => VerticalOrder::BottomToTop,
Self::BottomToTop => VerticalOrder::TopToBottom,
}
}
pub fn max_px(self) -> Px {
match self {
VerticalOrder::TopToBottom => Px::MAX,
VerticalOrder::BottomToTop => Px::MIN,
}
}
pub fn smallest_px(self, a: Px, b: Px) -> Px {
match self {
VerticalOrder::TopToBottom => a.min(b),
VerticalOrder::BottomToTop => b.max(a),
}
}
}

View file

@ -5,6 +5,7 @@ use std::sync::{Arc, Mutex, PoisonError};
use kludgine::figures::units::Px; use kludgine::figures::units::Px;
use kludgine::figures::{Point, Rect}; use kludgine::figures::{Point, Rect};
use crate::styles::components::VisualOrder;
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, ComponentType, Styles}; use crate::styles::{ComponentDefaultvalue, ComponentDefinition, ComponentType, Styles};
use crate::widget::{ManagedWidget, WidgetId, WidgetInstance}; use crate::widget::{ManagedWidget, WidgetId, WidgetInstance};
@ -86,17 +87,63 @@ impl Tree {
} }
} }
pub(crate) fn child_layouts(&self, parent: WidgetId) -> Vec<(ManagedWidget, Rect<Px>)> { pub(crate) fn visually_ordered_children(
&self,
parent: WidgetId,
order: VisualOrder,
) -> Vec<ManagedWidget> {
let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
data.nodes[&parent] let node = &data.nodes[&parent];
.children let mut unordered = node.children.clone();
.iter() let mut ordered = Vec::<ManagedWidget>::with_capacity(unordered.len());
.filter_map(|id| { loop {
data.nodes[id] // Identify the next "row" of widgets by finding the top of a widget that is the closest to the origin of
let mut min_vertical = order.vertical.max_px();
let mut max_vertical = order.vertical.max_px();
let mut index = 0;
while index < unordered.len() {
let Some(layout) = &data.nodes[&unordered[index]].layout else {
unordered.remove(index);
continue;
};
let top = layout.origin.y;
let bottom = top + layout.size.height;
min_vertical = order.vertical.smallest_px(min_vertical, top);
max_vertical = order.vertical.smallest_px(min_vertical, bottom);
index += 1;
}
if unordered.is_empty() {
break;
}
// Find all widgets whose top is within the range found.
index = 0;
let row_base = ordered.len();
while index < unordered.len() {
let top_left = data.nodes[&unordered[index]]
.layout .layout
.map(|layout| (data.widget(*id, self).expect("child still owned"), layout)) .expect("all have layouts")
}) .origin;
.collect() if min_vertical <= top_left.y && top_left.y <= max_vertical {
ordered.push(
data.widget(unordered.remove(index), self)
.expect("widget is owned"),
);
} else {
index += 1;
}
}
ordered[row_base..].sort_unstable_by_key(|managed| {
order
.horizontal
.sort_key(&data.nodes[&managed.id()].layout.expect("all have layouts"))
});
}
ordered
} }
pub(crate) fn hover(&self, new_hover: Option<&ManagedWidget>) -> HoverResults { pub(crate) fn hover(&self, new_hover: Option<&ManagedWidget>) -> HoverResults {

View file

@ -57,6 +57,8 @@ impl_all_tuples!(impl_with_clone);
pub trait ModifiersExt { pub trait ModifiersExt {
fn primary(&self) -> bool; fn primary(&self) -> bool;
fn word_select(&self) -> bool; fn word_select(&self) -> bool;
fn possible_shortcut(&self) -> bool;
} }
impl ModifiersExt for ModifiersState { impl ModifiersExt for ModifiersState {
@ -79,6 +81,10 @@ impl ModifiersExt for ModifiersState {
fn word_select(&self) -> bool { fn word_select(&self) -> bool {
self.control_key() self.control_key()
} }
fn possible_shortcut(&self) -> bool {
self.control_key() || self.alt_key() || self.super_key()
}
} }
impl ModifiersExt for Modifiers { impl ModifiersExt for Modifiers {
@ -89,6 +95,10 @@ impl ModifiersExt for Modifiers {
fn word_select(&self) -> bool { fn word_select(&self) -> bool {
self.state().word_select() self.state().word_select()
} }
fn possible_shortcut(&self) -> bool {
self.state().word_select()
}
} }
pub struct Lazy<T> { pub struct Lazy<T> {

View file

@ -15,6 +15,7 @@ use kludgine::figures::units::{Px, UPx};
use kludgine::figures::{Point, Rect, Size}; use kludgine::figures::{Point, Rect, Size};
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext}; use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
use crate::styles::components::VisualOrder;
use crate::styles::Styles; use crate::styles::Styles;
use crate::tree::Tree; use crate::tree::Tree;
use crate::value::{IntoValue, Value}; use crate::value::{IntoValue, Value};
@ -194,14 +195,14 @@ pub trait MakeWidget: Sized {
/// [`WidgetId`]. /// [`WidgetId`].
pub trait MakeWidgetWithId: Sized { pub trait MakeWidgetWithId: Sized {
/// Returns a new [`WidgetInstance`] whose [`WidgetId`] is `id`. /// Returns a new [`WidgetInstance`] whose [`WidgetId`] is `id`.
fn make_with_id(self, id: PendingWidgetId) -> WidgetInstance; fn make_with_id(self, id: WidgetTag) -> WidgetInstance;
} }
impl<T> MakeWidgetWithId for T impl<T> MakeWidgetWithId for T
where where
T: Widget, T: Widget,
{ {
fn make_with_id(self, id: PendingWidgetId) -> WidgetInstance { fn make_with_id(self, id: WidgetTag) -> WidgetInstance {
WidgetInstance::with_id(self, id) WidgetInstance::with_id(self, id)
} }
} }
@ -211,7 +212,7 @@ where
T: MakeWidgetWithId, T: MakeWidgetWithId,
{ {
fn make_widget(self) -> WidgetInstance { fn make_widget(self) -> WidgetInstance {
self.make_with_id(PendingWidgetId::unique()) self.make_with_id(WidgetTag::unique())
} }
} }
@ -267,7 +268,7 @@ pub struct WidgetInstance {
impl WidgetInstance { impl WidgetInstance {
/// Returns a new instance containing `widget` that is assigned the unique /// Returns a new instance containing `widget` that is assigned the unique
/// `id` provided. /// `id` provided.
pub fn with_id<W>(widget: W, id: PendingWidgetId) -> Self pub fn with_id<W>(widget: W, id: WidgetTag) -> Self
where where
W: Widget, W: Widget,
{ {
@ -283,7 +284,7 @@ impl WidgetInstance {
where where
W: Widget, W: Widget,
{ {
Self::with_id(widget, PendingWidgetId::unique()) Self::with_id(widget, WidgetTag::unique())
} }
/// Returns the unique id of this widget instance. /// Returns the unique id of this widget instance.
@ -330,6 +331,11 @@ impl WidgetInstance {
self.next_focus.get() self.next_focus.get()
} }
} }
impl AsRef<WidgetId> for WidgetInstance {
fn as_ref(&self) -> &WidgetId {
&self.id
}
}
impl Eq for WidgetInstance {} impl Eq for WidgetInstance {}
@ -484,8 +490,14 @@ impl ManagedWidget {
self.tree.reset_child_layouts(self.id()); self.tree.reset_child_layouts(self.id());
} }
pub(crate) fn child_layouts(&self) -> Vec<(ManagedWidget, Rect<Px>)> { pub(crate) fn visually_ordered_children(&self, order: VisualOrder) -> Vec<ManagedWidget> {
self.tree.child_layouts(self.id()) self.tree.visually_ordered_children(self.id(), order)
}
}
impl AsRef<WidgetId> for ManagedWidget {
fn as_ref(&self) -> &WidgetId {
self.widget.as_ref()
} }
} }
@ -631,6 +643,15 @@ impl WidgetRef {
} }
} }
impl AsRef<WidgetId> for WidgetRef {
fn as_ref(&self) -> &WidgetId {
match self {
WidgetRef::Unmounted(widget) => widget.as_ref(),
WidgetRef::Mounted(widget) => widget.as_ref(),
}
}
}
/// The unique id of a [`WidgetInstance`]. /// The unique id of a [`WidgetInstance`].
/// ///
/// Each [`WidgetInstance`] is guaranteed to have a unique [`WidgetId`] across /// Each [`WidgetInstance`] is guaranteed to have a unique [`WidgetId`] across
@ -654,9 +675,17 @@ impl WidgetId {
/// assigned a given [`WidgetId`]. The contained [`WidgetId`] can be accessed /// assigned a given [`WidgetId`]. The contained [`WidgetId`] can be accessed
/// via [`id()`](Self::id), `Into<WidgetId>`, or `Deref`. /// via [`id()`](Self::id), `Into<WidgetId>`, or `Deref`.
#[derive(Eq, PartialEq, Debug)] #[derive(Eq, PartialEq, Debug)]
pub struct PendingWidgetId(WidgetId); pub struct WidgetTag(WidgetId);
impl WidgetTag {
/// Returns a unique tag and its contained id.
#[must_use]
pub fn new() -> (Self, WidgetId) {
let tag = Self::unique();
let id = *tag;
(tag, id)
}
impl PendingWidgetId {
/// Returns a newly allocated [`WidgetId`] that is guaranteed to be unique /// Returns a newly allocated [`WidgetId`] that is guaranteed to be unique
/// for the lifetime of the application. /// for the lifetime of the application.
#[must_use] #[must_use]
@ -671,13 +700,13 @@ impl PendingWidgetId {
} }
} }
impl From<PendingWidgetId> for WidgetId { impl From<WidgetTag> for WidgetId {
fn from(value: PendingWidgetId) -> Self { fn from(value: WidgetTag) -> Self {
value.0 value.0
} }
} }
impl Deref for PendingWidgetId { impl Deref for WidgetTag {
type Target = WidgetId; type Target = WidgetId;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {

View file

@ -47,7 +47,7 @@ impl Align {
); );
let child = self.child.mounted(&mut context.as_event_context()); let child = self.child.mounted(&mut context.as_event_context());
let content_size = context.for_other(child).layout(content_available); let content_size = context.for_other(&child).layout(content_available);
let (left, right, width) = horizontal.measure(available_space.width, content_size.width); let (left, right, width) = horizontal.measure(available_space.width, content_size.width);
let (top, bottom, height) = vertical.measure(available_space.height, content_size.height); let (top, bottom, height) = vertical.measure(available_space.height, content_size.height);
@ -128,7 +128,7 @@ impl FrameInfo {
impl Widget for Align { impl Widget for Align {
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
let child = self.child.mounted(&mut context.as_event_context()); let child = self.child.mounted(&mut context.as_event_context());
context.for_other(child).redraw(); context.for_other(&child).redraw();
} }
fn layout( fn layout(

View file

@ -49,7 +49,7 @@ impl Expand {
impl Widget for Expand { impl Widget for Expand {
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
let child = self.child.mounted(&mut context.as_event_context()); let child = self.child.mounted(&mut context.as_event_context());
context.for_other(child).redraw(); context.for_other(&child).redraw();
} }
fn layout( fn layout(
@ -62,7 +62,7 @@ impl Widget for Expand {
ConstraintLimit::Known(available_space.height.max()), ConstraintLimit::Known(available_space.height.max()),
); );
let child = self.child.mounted(&mut context.as_event_context()); let child = self.child.mounted(&mut context.as_event_context());
let size = context.for_other(child.clone()).layout(available_space); let size = context.for_other(&child).layout(available_space);
context.set_child_layout(&child, Rect::from(size.into_signed())); context.set_child_layout(&child, Rect::from(size.into_signed()));
size size
} }

View file

@ -60,7 +60,7 @@ impl Resize {
impl Widget for Resize { impl Widget for Resize {
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
let child = self.child.mounted(&mut context.as_event_context()); let child = self.child.mounted(&mut context.as_event_context());
context.for_other(child).redraw(); context.for_other(&child).redraw();
} }
fn layout( fn layout(
@ -83,7 +83,8 @@ impl Widget for Resize {
), ),
); );
let child = self.child.mounted(&mut context.as_event_context()); let child = self.child.mounted(&mut context.as_event_context());
context.for_other(child).layout(available_space) // TODO set_child_layout
context.for_other(&child).layout(available_space)
} }
} }
} }

View file

@ -12,7 +12,7 @@ use kludgine::shapes::Shape;
use kludgine::Color; use kludgine::Color;
use crate::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn, ZeroToOne}; use crate::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn, ZeroToOne};
use crate::context::{AsEventContext, EventContext}; use crate::context::{AsEventContext, EventContext, LayoutContext};
use crate::styles::components::{EasingIn, EasingOut}; use crate::styles::components::{EasingIn, EasingOut};
use crate::styles::{ use crate::styles::{
ComponentDefinition, ComponentGroup, ComponentName, Dimension, NamedComponent, ComponentDefinition, ComponentGroup, ComponentName, Dimension, NamedComponent,
@ -126,7 +126,7 @@ impl Widget for Scroll {
let visible_bottom_right = visible_rect.into_signed().extent(); let visible_bottom_right = visible_rect.into_signed().extent();
let managed = self.contents.mounted(&mut context.as_event_context()); let managed = self.contents.mounted(&mut context.as_event_context());
context.for_other(managed).redraw(); context.for_other(&managed).redraw();
if self.horizontal_bar.amount_hidden > 0 { if self.horizontal_bar.amount_hidden > 0 {
context.graphics.draw_shape( context.graphics.draw_shape(
@ -167,8 +167,8 @@ impl Widget for Scroll {
fn layout( fn layout(
&mut self, &mut self,
available_space: Size<crate::ConstraintLimit>, available_space: Size<ConstraintLimit>,
context: &mut crate::context::LayoutContext<'_, '_, '_, '_, '_>, context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> { ) -> Size<UPx> {
let styles = context.query_styles(&[&ScrollBarThickness]); let styles = context.query_styles(&[&ScrollBarThickness]);
self.bar_width = styles self.bar_width = styles
@ -192,7 +192,7 @@ impl Widget for Scroll {
); );
let managed = self.contents.mounted(&mut context.as_event_context()); let managed = self.contents.mounted(&mut context.as_event_context());
let new_content_size = context let new_content_size = context
.for_other(managed.clone()) .for_other(&managed)
.layout(max_extents) .layout(max_extents)
.into_signed(); .into_signed();

View file

@ -132,7 +132,7 @@ impl Stack {
impl Widget for Stack { impl Widget for Stack {
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
for child in &self.synced_children { for child in &self.synced_children {
context.for_other(child.clone()).redraw(); context.for_other(child).redraw();
} }
} }
@ -147,7 +147,7 @@ impl Widget for Stack {
available_space, available_space,
context.graphics.scale(), context.graphics.scale(),
|child_index, constraints, persist| { |child_index, constraints, persist| {
let mut context = context.for_other(self.synced_children[child_index].clone()); let mut context = context.for_other(&self.synced_children[child_index]);
if !persist { if !persist {
context = context.as_temporary(); context = context.as_temporary();
} }

View file

@ -31,7 +31,7 @@ impl Widget for Style {
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
let child = self.child.mounted(&mut context.as_event_context()); let child = self.child.mounted(&mut context.as_event_context());
context.for_other(child).redraw(); context.for_other(&child).redraw();
} }
fn layout( fn layout(
@ -40,6 +40,6 @@ impl Widget for Style {
context: &mut LayoutContext<'_, '_, '_, '_, '_>, context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> { ) -> Size<UPx> {
let child = self.child.mounted(&mut context.as_event_context()); let child = self.child.mounted(&mut context.as_event_context());
context.for_other(child).layout(available_space) context.for_other(&child).layout(available_space)
} }
} }

View file

@ -25,6 +25,7 @@ use crate::context::{
WidgetContext, WidgetContext,
}; };
use crate::graphics::Graphics; use crate::graphics::Graphics;
use crate::styles::components::VisualOrder;
use crate::tree::Tree; use crate::tree::Tree;
use crate::utils::ModifiersExt; use crate::utils::ModifiersExt;
use crate::widget::{EventHandling, ManagedWidget, Widget, WidgetInstance, HANDLED, IGNORED}; use crate::widget::{EventHandling, ManagedWidget, Widget, WidgetInstance, HANDLED, IGNORED};
@ -316,6 +317,23 @@ where
window.set_needs_redraw(); window.set_needs_redraw();
} }
} }
KeyCode::Tab
if !window.modifiers().state().possible_shortcut()
&& input.state.is_pressed() =>
{
let direction = if window.modifiers().state().shift_key() {
VisualOrder::left_to_right().rev()
} else {
VisualOrder::left_to_right()
};
let target = self.root.tree.focused_widget().unwrap_or(self.root.id());
let target = self.root.tree.widget(target).expect("missing widget");
let mut target = EventContext::new(
WidgetContext::new(target, &self.redraw_status, &mut window),
kludgine,
);
target.advance_focus(direction);
}
_ => {} _ => {}
} }
} }
@ -401,7 +419,7 @@ where
); );
self.mouse_state.widget = None; self.mouse_state.widget = None;
for widget in self.root.tree.widgets_at_point(location) { for widget in self.root.tree.widgets_at_point(location) {
let mut widget_context = context.for_other(widget.clone()); let mut widget_context = context.for_other(&widget);
let relative = location let relative = location
- widget_context - widget_context
.last_layout() .last_layout()
@ -527,7 +545,7 @@ fn recursively_handle_event(
match each_widget(context) { match each_widget(context) {
HANDLED => Some(context.widget().clone()), HANDLED => Some(context.widget().clone()),
IGNORED => context.parent().and_then(|parent| { IGNORED => context.parent().and_then(|parent| {
recursively_handle_event(&mut context.for_other(parent), each_widget) recursively_handle_event(&mut context.for_other(&parent), each_widget)
}), }),
} }
} }