mirror of
https://github.com/danbulant/cushy
synced 2026-06-19 06:21:15 +00:00
AutoFocusableControls, LayoutOrder
This commit is contained in:
parent
e4092532d3
commit
85928675ab
4 changed files with 112 additions and 16 deletions
|
|
@ -7,6 +7,7 @@ use std::sync::Arc;
|
|||
|
||||
use crate::animation::{EasingFunction, ZeroToOne};
|
||||
use crate::names::Name;
|
||||
use crate::styles::components::{FocusableWidgets, VisualOrder};
|
||||
use crate::utils::Lazy;
|
||||
use crate::value::{IntoValue, Value};
|
||||
|
||||
|
|
@ -151,6 +152,10 @@ pub enum Component {
|
|||
Custom(CustomComponent),
|
||||
/// An easing function for animations.
|
||||
Easing(EasingFunction),
|
||||
/// A visual ordering to use for layout.
|
||||
VisualOrder(VisualOrder),
|
||||
/// A description of what widgets should be focusable.
|
||||
FocusableWidgets(FocusableWidgets),
|
||||
}
|
||||
|
||||
impl From<Color> for Component {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ use kludgine::Color;
|
|||
|
||||
use crate::animation::easings::{EaseInQuadradic, EaseOutQuadradic};
|
||||
use crate::animation::EasingFunction;
|
||||
use crate::styles::{ComponentDefinition, ComponentName, Dimension, Global, NamedComponent};
|
||||
use crate::styles::{
|
||||
Component, ComponentDefinition, ComponentName, Dimension, Global, NamedComponent,
|
||||
};
|
||||
|
||||
/// The [`Dimension`] to use as the size to render text.
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
|
|
@ -181,7 +183,7 @@ impl ComponentDefinition for EasingOut {
|
|||
}
|
||||
|
||||
/// A 2d ordering configuration.
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
#[derive(Copy, Debug, Clone, Eq, PartialEq)]
|
||||
pub struct VisualOrder {
|
||||
/// The ordering to apply horizontally.
|
||||
pub horizontal: HorizontalOrder,
|
||||
|
|
@ -218,14 +220,43 @@ impl VisualOrder {
|
|||
}
|
||||
}
|
||||
|
||||
impl NamedComponent for VisualOrder {
|
||||
/// The [`VisualOrder`] strategy to use when laying out content.
|
||||
#[derive(Debug)]
|
||||
pub struct LayoutOrder;
|
||||
|
||||
impl NamedComponent for LayoutOrder {
|
||||
fn name(&self) -> Cow<'_, ComponentName> {
|
||||
Cow::Owned(ComponentName::named::<Global>("visual_order"))
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentDefinition for LayoutOrder {
|
||||
type ComponentType = VisualOrder;
|
||||
|
||||
fn default_value(&self) -> Self::ComponentType {
|
||||
VisualOrder::left_to_right()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VisualOrder> for Component {
|
||||
fn from(value: VisualOrder) -> Self {
|
||||
Self::VisualOrder(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Component> for VisualOrder {
|
||||
type Error = Component;
|
||||
|
||||
fn try_from(value: Component) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Component::VisualOrder(order) => Ok(order),
|
||||
other => Err(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A horizontal direction.
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum HorizontalOrder {
|
||||
/// Describes an order starting at the left and proceeding to the right.
|
||||
LeftToRight,
|
||||
|
|
@ -252,7 +283,7 @@ impl HorizontalOrder {
|
|||
}
|
||||
|
||||
/// A vertical direction.
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum VerticalOrder {
|
||||
/// Describes an order starting at the top and proceeding to the bottom.
|
||||
TopToBottom,
|
||||
|
|
@ -284,3 +315,63 @@ impl VerticalOrder {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The set of controls to allow focusing via tab key and initial focus
|
||||
/// selection.
|
||||
pub struct AutoFocusableControls;
|
||||
|
||||
impl NamedComponent for AutoFocusableControls {
|
||||
fn name(&self) -> Cow<'_, ComponentName> {
|
||||
Cow::Owned(ComponentName::named::<Global>("focus"))
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentDefinition for AutoFocusableControls {
|
||||
type ComponentType = FocusableWidgets;
|
||||
|
||||
fn default_value(&self) -> Self::ComponentType {
|
||||
FocusableWidgets::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FocusableWidgets> for Component {
|
||||
fn from(value: FocusableWidgets) -> Self {
|
||||
Self::FocusableWidgets(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Component> for FocusableWidgets {
|
||||
type Error = Component;
|
||||
|
||||
fn try_from(value: Component) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Component::FocusableWidgets(focus) => Ok(focus),
|
||||
other => Err(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A configuration option to control which controls should be able to receive
|
||||
/// focus through keyboard focus handling or initial focus handling.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum FocusableWidgets {
|
||||
/// Allow all widgets that can respond to keyboard input to accept focus.
|
||||
#[default]
|
||||
All,
|
||||
/// Only allow widgets that expect textual input to accept focus.
|
||||
OnlyTextual,
|
||||
}
|
||||
|
||||
impl FocusableWidgets {
|
||||
/// Returns true if all controls should be focusable.
|
||||
#[must_use]
|
||||
pub const fn is_all(self) -> bool {
|
||||
matches!(self, Self::All)
|
||||
}
|
||||
|
||||
/// Returns true if only textual should be focusable.
|
||||
#[must_use]
|
||||
pub const fn is_only_textual(self) -> bool {
|
||||
matches!(self, Self::OnlyTextual)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use crate::animation::{AnimationHandle, AnimationTarget, Spawn};
|
|||
use crate::context::{EventContext, GraphicsContext, LayoutContext, WidgetContext};
|
||||
use crate::names::Name;
|
||||
use crate::styles::components::{
|
||||
Easing, HighlightColor, IntrinsicPadding, PrimaryColor, TextColor,
|
||||
AutoFocusableControls, Easing, HighlightColor, IntrinsicPadding, PrimaryColor, TextColor,
|
||||
};
|
||||
use crate::styles::{ComponentDefinition, ComponentGroup, ComponentName, NamedComponent};
|
||||
use crate::utils::ModifiersExt;
|
||||
|
|
@ -180,9 +180,8 @@ impl Widget for Button {
|
|||
true
|
||||
}
|
||||
|
||||
fn accept_focus(&mut self, _context: &mut EventContext<'_, '_>) -> bool {
|
||||
// TODO this should be driven by a "focus_all_widgets" setting that hopefully can be queried from the OS.
|
||||
self.enabled.get()
|
||||
fn accept_focus(&mut self, context: &mut EventContext<'_, '_>) -> bool {
|
||||
self.enabled.get() && context.query_style(&AutoFocusableControls).is_all()
|
||||
}
|
||||
|
||||
fn mouse_down(
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ use crate::context::{
|
|||
WidgetContext,
|
||||
};
|
||||
use crate::graphics::Graphics;
|
||||
use crate::styles::components::VisualOrder;
|
||||
use crate::styles::components::LayoutOrder;
|
||||
use crate::tree::Tree;
|
||||
use crate::utils::ModifiersExt;
|
||||
use crate::value::{Dynamic, IntoDynamic};
|
||||
|
|
@ -492,18 +492,19 @@ where
|
|||
}
|
||||
Key::Tab if !window.modifiers().possible_shortcut() => {
|
||||
if input.state.is_pressed() {
|
||||
let direction = if window.modifiers().state().shift_key() {
|
||||
VisualOrder::left_to_right().rev()
|
||||
} else {
|
||||
VisualOrder::left_to_right()
|
||||
};
|
||||
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 mut target = EventContext::new(
|
||||
WidgetContext::new(target, &self.redraw_status, &mut window),
|
||||
kludgine,
|
||||
);
|
||||
target.advance_focus(direction);
|
||||
let mut visual_order = target.query_style(&LayoutOrder);
|
||||
if reverse {
|
||||
visual_order = visual_order.rev();
|
||||
}
|
||||
target.advance_focus(visual_order);
|
||||
}
|
||||
}
|
||||
Key::Enter => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue