mirror of
https://github.com/danbulant/cushy
synced 2026-06-19 22:41:10 +00:00
Checkpoint
Progress on tab focus
This commit is contained in:
parent
8a2dae3f76
commit
5e5d826267
12 changed files with 383 additions and 105 deletions
213
src/context.rs
213
src/context.rs
|
|
@ -12,11 +12,12 @@ use kludgine::shapes::{Shape, StrokeOptions};
|
|||
use kludgine::Kludgine;
|
||||
|
||||
use crate::graphics::Graphics;
|
||||
use crate::styles::components::HighlightColor;
|
||||
use crate::styles::components::{HighlightColor, VisualOrder};
|
||||
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, Styles};
|
||||
use crate::value::Dynamic;
|
||||
use crate::widget::{EventHandling, ManagedWidget, WidgetId, WidgetInstance};
|
||||
use crate::window::{sealed, RunningWindow};
|
||||
use crate::widget::{EventHandling, ManagedWidget, WidgetId, WidgetInstance, WidgetRef};
|
||||
use crate::window::sealed::WindowCommand;
|
||||
use crate::window::RunningWindow;
|
||||
use crate::ConstraintLimit;
|
||||
|
||||
/// 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
|
||||
/// creating an `EventContext` pointing to the child and calling the
|
||||
/// appropriate function to invoke the event.
|
||||
pub fn for_other<'child>(
|
||||
pub fn for_other<'child, Widget>(
|
||||
&'child mut self,
|
||||
widget: ManagedWidget,
|
||||
) -> EventContext<'child, 'window> {
|
||||
EventContext::new(self.widget.for_other(widget), self.kludgine)
|
||||
widget: &Widget,
|
||||
) -> <Widget::Managed as MapManagedWidget<EventContext<'child, 'window>>>::Result
|
||||
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
|
||||
|
|
@ -145,11 +152,11 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
|||
pub(crate) fn hover(&mut self, location: Point<Px>) {
|
||||
let changes = self.current_node.tree.hover(Some(&self.current_node));
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -159,7 +166,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
|||
assert!(changes.hovered.is_empty());
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -170,7 +177,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
|||
let new = match self.current_node.tree.activate(active.as_ref()) {
|
||||
Ok(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);
|
||||
}
|
||||
true
|
||||
|
|
@ -182,7 +189,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
|||
active
|
||||
.lock()
|
||||
.as_widget()
|
||||
.activate(&mut self.for_other(active.clone()));
|
||||
.activate(&mut self.for_other(active));
|
||||
}
|
||||
self.pending_state.active = active;
|
||||
}
|
||||
|
|
@ -194,19 +201,19 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
|||
if focus
|
||||
.lock()
|
||||
.as_widget()
|
||||
.accept_focus(&mut self.for_other(focus.clone()))
|
||||
.accept_focus(&mut self.for_other(&focus))
|
||||
{
|
||||
break Some(focus);
|
||||
} else if let Some(next_focus) = focus.next_focus() {
|
||||
focus = next_focus;
|
||||
} 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()) {
|
||||
Ok(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);
|
||||
}
|
||||
true
|
||||
|
|
@ -215,26 +222,27 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
|||
};
|
||||
if new {
|
||||
if let Some(focus) = &focus {
|
||||
focus
|
||||
.lock()
|
||||
.as_widget()
|
||||
.focus(&mut self.for_other(focus.clone()));
|
||||
focus.lock().as_widget().focus(&mut self.for_other(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.
|
||||
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);
|
||||
}
|
||||
|
||||
// Now, look for the next widget in each hierarchy
|
||||
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);
|
||||
}
|
||||
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
|
||||
// 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(
|
||||
&mut self,
|
||||
focus: &ManagedWidget,
|
||||
stop_at: WidgetId,
|
||||
order: VisualOrder,
|
||||
) -> 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
|
||||
|
|
@ -264,21 +273,22 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
|||
focus: &ManagedWidget,
|
||||
start_at: Option<WidgetId>,
|
||||
stop_at: WidgetId,
|
||||
order: VisualOrder,
|
||||
) -> Option<ManagedWidget> {
|
||||
let child_layouts = focus.child_layouts();
|
||||
// TODO visually sort the layouts
|
||||
|
||||
let mut child_layouts = child_layouts.into_iter().peekable();
|
||||
let mut children = focus
|
||||
.visually_ordered_children(order)
|
||||
.into_iter()
|
||||
.peekable();
|
||||
if let Some(start_at) = start_at {
|
||||
// Skip all children up to `start_at`
|
||||
while child_layouts.peek()?.0.id() != start_at {
|
||||
child_layouts.next();
|
||||
while children.peek()?.id() != start_at {
|
||||
children.next();
|
||||
}
|
||||
// Skip `start_at`
|
||||
child_layouts.next();
|
||||
children.next();
|
||||
}
|
||||
|
||||
for (child, _layout) in child_layouts {
|
||||
for child in children {
|
||||
// Ensure we haven't cycled completely.
|
||||
if stop_at == child.id() {
|
||||
break;
|
||||
|
|
@ -287,16 +297,20 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
|||
if child
|
||||
.lock()
|
||||
.as_widget()
|
||||
.accept_focus(&mut self.for_other(child.clone()))
|
||||
.accept_focus(&mut self.for_other(&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);
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
|
|
@ -362,19 +376,27 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, '
|
|||
|
||||
/// Returns a new `GraphicsContext` that allows invoking graphics functions
|
||||
/// for `widget`.
|
||||
pub fn for_other<'child>(
|
||||
pub fn for_other<'child, Widget>(
|
||||
&'child mut self,
|
||||
widget: ManagedWidget,
|
||||
) -> GraphicsContext<'child, 'window, 'child, 'gfx, 'pass> {
|
||||
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)),
|
||||
}
|
||||
widget: &Widget,
|
||||
) -> <Widget::Managed as MapManagedWidget<
|
||||
GraphicsContext<'child, 'window, 'child, 'gfx, 'pass>,
|
||||
>>::Result
|
||||
where
|
||||
Widget: ManageWidget,
|
||||
Widget::Managed: MapManagedWidget<GraphicsContext<'child, 'window, 'child, 'gfx, 'pass>>,
|
||||
{
|
||||
widget.manage(self).map(|widget| {
|
||||
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.
|
||||
|
|
@ -468,17 +490,18 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> LayoutContext<'context, 'window, 'cl
|
|||
|
||||
/// Returns a new `LayoutContext` that allows invoking layout functions for
|
||||
/// `widget`.
|
||||
pub fn for_other<'child, 'widget>(
|
||||
pub fn for_other<'child, Widget>(
|
||||
&'child mut self,
|
||||
widget: ManagedWidget,
|
||||
) -> LayoutContext<'child, 'window, 'child, 'gfx, 'pass>
|
||||
widget: &Widget,
|
||||
) -> <Widget::Managed as MapManagedWidget<LayoutContext<'child, 'window, 'child, 'gfx, 'pass>>>::Result
|
||||
where
|
||||
'widget: 'child,
|
||||
Widget: ManageWidget,
|
||||
Widget::Managed: MapManagedWidget<LayoutContext<'child, 'window, 'child, 'gfx, 'pass>>,
|
||||
{
|
||||
LayoutContext {
|
||||
graphics: self.graphics.for_other(widget),
|
||||
widget.manage(self).map(|widget| LayoutContext {
|
||||
graphics: self.graphics.for_other(&widget),
|
||||
persist_layout: self.persist_layout,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Invokes [`Widget::layout()`](crate::widget::Widget::layout) on this
|
||||
|
|
@ -541,21 +564,21 @@ pub trait AsEventContext<'window> {
|
|||
pushed_widget
|
||||
.lock()
|
||||
.as_widget()
|
||||
.mounted(&mut context.for_other(pushed_widget.clone()));
|
||||
.mounted(&mut context.for_other(&pushed_widget));
|
||||
pushed_widget
|
||||
}
|
||||
|
||||
/// Removes a widget from the hierarchy.
|
||||
fn remove_child(&mut self, child: &ManagedWidget) {
|
||||
let mut context = self.as_event_context();
|
||||
child
|
||||
.lock()
|
||||
.as_widget()
|
||||
.unmounted(&mut context.for_other(child));
|
||||
context
|
||||
.current_node
|
||||
.tree
|
||||
.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`.
|
||||
pub fn for_other<'child>(
|
||||
pub fn for_other<'child, Widget>(
|
||||
&'child mut self,
|
||||
widget: ManagedWidget,
|
||||
) -> WidgetContext<'child, 'window> {
|
||||
WidgetContext {
|
||||
current_node: widget,
|
||||
widget: &Widget,
|
||||
) -> <Widget::Managed as MapManagedWidget<WidgetContext<'child, 'window>>>::Result
|
||||
where
|
||||
Widget: ManageWidget,
|
||||
Widget::Managed: MapManagedWidget<WidgetContext<'child, 'window>>,
|
||||
{
|
||||
widget.manage(self).map(|current_node| WidgetContext {
|
||||
current_node,
|
||||
redraw_status: self.redraw_status,
|
||||
window: &mut *self.window,
|
||||
pending_state: self.pending_state.borrowed(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn parent(&self) -> Option<ManagedWidget> {
|
||||
|
|
@ -792,14 +819,14 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
|||
}
|
||||
|
||||
pub(crate) struct WindowHandle {
|
||||
kludgine: kludgine::app::WindowHandle<sealed::WindowCommand>,
|
||||
kludgine: kludgine::app::WindowHandle<WindowCommand>,
|
||||
redraw_status: RedrawStatus,
|
||||
}
|
||||
|
||||
impl WindowHandle {
|
||||
pub fn redraw(&self) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
//! All style components supported by the built-in widgets.
|
||||
use std::borrow::Cow;
|
||||
|
||||
use kludgine::figures::units::Lp;
|
||||
use kludgine::figures::units::{Lp, Px};
|
||||
use kludgine::figures::Rect;
|
||||
use kludgine::Color;
|
||||
|
||||
use crate::animation::easings::{EaseInQuadradic, EaseOutQuadradic};
|
||||
|
|
@ -160,3 +161,94 @@ impl ComponentDefinition for EasingOut {
|
|||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
65
src/tree.rs
65
src/tree.rs
|
|
@ -5,6 +5,7 @@ use std::sync::{Arc, Mutex, PoisonError};
|
|||
use kludgine::figures::units::Px;
|
||||
use kludgine::figures::{Point, Rect};
|
||||
|
||||
use crate::styles::components::VisualOrder;
|
||||
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, ComponentType, Styles};
|
||||
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);
|
||||
data.nodes[&parent]
|
||||
.children
|
||||
.iter()
|
||||
.filter_map(|id| {
|
||||
data.nodes[id]
|
||||
let node = &data.nodes[&parent];
|
||||
let mut unordered = node.children.clone();
|
||||
let mut ordered = Vec::<ManagedWidget>::with_capacity(unordered.len());
|
||||
loop {
|
||||
// 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
|
||||
.map(|layout| (data.widget(*id, self).expect("child still owned"), layout))
|
||||
})
|
||||
.collect()
|
||||
.expect("all have layouts")
|
||||
.origin;
|
||||
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 {
|
||||
|
|
|
|||
10
src/utils.rs
10
src/utils.rs
|
|
@ -57,6 +57,8 @@ impl_all_tuples!(impl_with_clone);
|
|||
pub trait ModifiersExt {
|
||||
fn primary(&self) -> bool;
|
||||
fn word_select(&self) -> bool;
|
||||
|
||||
fn possible_shortcut(&self) -> bool;
|
||||
}
|
||||
|
||||
impl ModifiersExt for ModifiersState {
|
||||
|
|
@ -79,6 +81,10 @@ impl ModifiersExt for ModifiersState {
|
|||
fn word_select(&self) -> bool {
|
||||
self.control_key()
|
||||
}
|
||||
|
||||
fn possible_shortcut(&self) -> bool {
|
||||
self.control_key() || self.alt_key() || self.super_key()
|
||||
}
|
||||
}
|
||||
|
||||
impl ModifiersExt for Modifiers {
|
||||
|
|
@ -89,6 +95,10 @@ impl ModifiersExt for Modifiers {
|
|||
fn word_select(&self) -> bool {
|
||||
self.state().word_select()
|
||||
}
|
||||
|
||||
fn possible_shortcut(&self) -> bool {
|
||||
self.state().word_select()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Lazy<T> {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use kludgine::figures::units::{Px, UPx};
|
|||
use kludgine::figures::{Point, Rect, Size};
|
||||
|
||||
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
|
||||
use crate::styles::components::VisualOrder;
|
||||
use crate::styles::Styles;
|
||||
use crate::tree::Tree;
|
||||
use crate::value::{IntoValue, Value};
|
||||
|
|
@ -194,14 +195,14 @@ pub trait MakeWidget: Sized {
|
|||
/// [`WidgetId`].
|
||||
pub trait MakeWidgetWithId: Sized {
|
||||
/// 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
|
||||
where
|
||||
T: Widget,
|
||||
{
|
||||
fn make_with_id(self, id: PendingWidgetId) -> WidgetInstance {
|
||||
fn make_with_id(self, id: WidgetTag) -> WidgetInstance {
|
||||
WidgetInstance::with_id(self, id)
|
||||
}
|
||||
}
|
||||
|
|
@ -211,7 +212,7 @@ where
|
|||
T: MakeWidgetWithId,
|
||||
{
|
||||
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 {
|
||||
/// Returns a new instance containing `widget` that is assigned the unique
|
||||
/// `id` provided.
|
||||
pub fn with_id<W>(widget: W, id: PendingWidgetId) -> Self
|
||||
pub fn with_id<W>(widget: W, id: WidgetTag) -> Self
|
||||
where
|
||||
W: Widget,
|
||||
{
|
||||
|
|
@ -283,7 +284,7 @@ impl WidgetInstance {
|
|||
where
|
||||
W: Widget,
|
||||
{
|
||||
Self::with_id(widget, PendingWidgetId::unique())
|
||||
Self::with_id(widget, WidgetTag::unique())
|
||||
}
|
||||
|
||||
/// Returns the unique id of this widget instance.
|
||||
|
|
@ -330,6 +331,11 @@ impl WidgetInstance {
|
|||
self.next_focus.get()
|
||||
}
|
||||
}
|
||||
impl AsRef<WidgetId> for WidgetInstance {
|
||||
fn as_ref(&self) -> &WidgetId {
|
||||
&self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for WidgetInstance {}
|
||||
|
||||
|
|
@ -484,8 +490,14 @@ impl ManagedWidget {
|
|||
self.tree.reset_child_layouts(self.id());
|
||||
}
|
||||
|
||||
pub(crate) fn child_layouts(&self) -> Vec<(ManagedWidget, Rect<Px>)> {
|
||||
self.tree.child_layouts(self.id())
|
||||
pub(crate) fn visually_ordered_children(&self, order: VisualOrder) -> Vec<ManagedWidget> {
|
||||
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`].
|
||||
///
|
||||
/// 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
|
||||
/// via [`id()`](Self::id), `Into<WidgetId>`, or `Deref`.
|
||||
#[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
|
||||
/// for the lifetime of the application.
|
||||
#[must_use]
|
||||
|
|
@ -671,13 +700,13 @@ impl PendingWidgetId {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<PendingWidgetId> for WidgetId {
|
||||
fn from(value: PendingWidgetId) -> Self {
|
||||
impl From<WidgetTag> for WidgetId {
|
||||
fn from(value: WidgetTag) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for PendingWidgetId {
|
||||
impl Deref for WidgetTag {
|
||||
type Target = WidgetId;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ impl Align {
|
|||
);
|
||||
|
||||
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 (top, bottom, height) = vertical.measure(available_space.height, content_size.height);
|
||||
|
|
@ -128,7 +128,7 @@ impl FrameInfo {
|
|||
impl Widget for Align {
|
||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||
let child = self.child.mounted(&mut context.as_event_context());
|
||||
context.for_other(child).redraw();
|
||||
context.for_other(&child).redraw();
|
||||
}
|
||||
|
||||
fn layout(
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ impl Expand {
|
|||
impl Widget for Expand {
|
||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||
let child = self.child.mounted(&mut context.as_event_context());
|
||||
context.for_other(child).redraw();
|
||||
context.for_other(&child).redraw();
|
||||
}
|
||||
|
||||
fn layout(
|
||||
|
|
@ -62,7 +62,7 @@ impl Widget for Expand {
|
|||
ConstraintLimit::Known(available_space.height.max()),
|
||||
);
|
||||
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()));
|
||||
size
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ impl Resize {
|
|||
impl Widget for Resize {
|
||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||
let child = self.child.mounted(&mut context.as_event_context());
|
||||
context.for_other(child).redraw();
|
||||
context.for_other(&child).redraw();
|
||||
}
|
||||
|
||||
fn layout(
|
||||
|
|
@ -83,7 +83,8 @@ impl Widget for Resize {
|
|||
),
|
||||
);
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use kludgine::shapes::Shape;
|
|||
use kludgine::Color;
|
||||
|
||||
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::{
|
||||
ComponentDefinition, ComponentGroup, ComponentName, Dimension, NamedComponent,
|
||||
|
|
@ -126,7 +126,7 @@ impl Widget for Scroll {
|
|||
let visible_bottom_right = visible_rect.into_signed().extent();
|
||||
|
||||
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 {
|
||||
context.graphics.draw_shape(
|
||||
|
|
@ -167,8 +167,8 @@ impl Widget for Scroll {
|
|||
|
||||
fn layout(
|
||||
&mut self,
|
||||
available_space: Size<crate::ConstraintLimit>,
|
||||
context: &mut crate::context::LayoutContext<'_, '_, '_, '_, '_>,
|
||||
available_space: Size<ConstraintLimit>,
|
||||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||
) -> Size<UPx> {
|
||||
let styles = context.query_styles(&[&ScrollBarThickness]);
|
||||
self.bar_width = styles
|
||||
|
|
@ -192,7 +192,7 @@ impl Widget for Scroll {
|
|||
);
|
||||
let managed = self.contents.mounted(&mut context.as_event_context());
|
||||
let new_content_size = context
|
||||
.for_other(managed.clone())
|
||||
.for_other(&managed)
|
||||
.layout(max_extents)
|
||||
.into_signed();
|
||||
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ impl Stack {
|
|||
impl Widget for Stack {
|
||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||
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,
|
||||
context.graphics.scale(),
|
||||
|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 {
|
||||
context = context.as_temporary();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ impl Widget for Style {
|
|||
|
||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||
let child = self.child.mounted(&mut context.as_event_context());
|
||||
context.for_other(child).redraw();
|
||||
context.for_other(&child).redraw();
|
||||
}
|
||||
|
||||
fn layout(
|
||||
|
|
@ -40,6 +40,6 @@ impl Widget for Style {
|
|||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||
) -> Size<UPx> {
|
||||
let child = self.child.mounted(&mut context.as_event_context());
|
||||
context.for_other(child).layout(available_space)
|
||||
context.for_other(&child).layout(available_space)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ use crate::context::{
|
|||
WidgetContext,
|
||||
};
|
||||
use crate::graphics::Graphics;
|
||||
use crate::styles::components::VisualOrder;
|
||||
use crate::tree::Tree;
|
||||
use crate::utils::ModifiersExt;
|
||||
use crate::widget::{EventHandling, ManagedWidget, Widget, WidgetInstance, HANDLED, IGNORED};
|
||||
|
|
@ -316,6 +317,23 @@ where
|
|||
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;
|
||||
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
|
||||
- widget_context
|
||||
.last_layout()
|
||||
|
|
@ -527,7 +545,7 @@ fn recursively_handle_event(
|
|||
match each_widget(context) {
|
||||
HANDLED => Some(context.widget().clone()),
|
||||
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)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue