NAME
Clay::UI::Role::Interaction::HasFocusOrder - role for containers that choose the focus order in their subtree
SYNOPSIS
use v5.22;
use warnings;
use feature 'signatures';
no warnings 'experimental::signatures';
use Object::Pad;
use Clay::UI;
use Clay::UI::Box;
use Clay::UI::Role::Interaction::Focusable;
use Clay::UI::Role::Interaction::HasFocusOrder;
class My::Input :strict(params)
:does(Clay::UI::Box)
:does(Clay::UI::Role::Interaction::Focusable)
{}
# A form whose first Tab goes to the toolbar, although it comes last.
class My::Form :strict(params)
:does(Clay::UI::Box)
:does(Clay::UI::Role::Interaction::HasFocusOrder)
{
field $toolbar :param :reader;
method get_next_focus () {
return $toolbar unless defined $self->ui->interaction->get_focused_widget;
return $self->default_next_focus;
}
method get_previous_focus () {
return $self->default_previous_focus;
}
}
my $toolbar = My::Input->new(id => 'toolbar');
my $form = My::Form->new(id => 'form', toolbar => $toolbar);
$form->add_child(My::Input->new(id => 'name'), My::Input->new(id => 'email'), $toolbar);
my $ui = Clay::UI->new(width => 400, height => 300, root => $form);
for (1 .. 4) {
$ui->interaction->focus_next;
say $ui->interaction->get_focused_widget->id; # toolbar, name, email, toolbar
}
DESCRIPTION
By default, focus_next and focus_previous (Clay::UI::Interaction) move the focus in the depth-first pre-order of the widget tree. A widget that composes this role decides the order inside its subtree instead. It does not need to take the focus itself: HasFocusOrder does not imply Clay::UI::Role::Interaction::Focusable.
Which widget decides:
With a focused widget: the nearest widget composing HasFocusOrder among the focused widget and its ancestors. When there is none, the default order applies.
With nothing focused: the root widget of the Clay::UI, if it composes HasFocusOrder; otherwise the default order applies.
The deciding widget's "get_next_focus" (for focus_next) or "get_previous_focus" (for focus_previous) is called without arguments and must return one of:
undef-
The focus stays where it is.
- a Focusable widget of the same Clay::UI
-
It gets the focus, unless its
can_focusis 0 at that moment; then the focus stays where it is.
Anything else is a bug in the composing class: a non-widget, a widget that does not compose Clay::UI::Role::Interaction::Focusable, or a widget that is not part of this Clay::UI. focus_next / focus_previous then die with Clay::UI::Interaction: <class> returned ..., naming the class and the problem.
The order decides what happens while the focus is inside the subtree. Two optional methods say how the focus gets in and out:
"get_entry_focus" is asked when the default order, coming from outside, would land at or below this widget: for example a view that wants Tab to stop on itself before it steps through its content.
focus_after hands the focus on after the subtree, inside the scope of the enclosing order ("focus_scope"): for example that view, once Tab passed its last widget.
The role composes Clay::UI::Role::Layout::HasParent (for ui).
REQUIRED METHODS
The composing class must implement both.
get_next_focus
method get_next_focus () { ... }
Returns the widget that should get the focus on focus_next, or undef to keep it. $self->ui->interaction->get_focused_widget tells where the focus is now (undef when nothing is focused).
get_previous_focus
method get_previous_focus () { ... }
Returns the widget that should get the focus on focus_previous, or undef; the mirror of "get_next_focus".
OPTIONAL METHODS
The composing class may implement these; the tracker checks for them with can. The role does not provide them, so a class is free to define them.
get_entry_focus
method get_entry_focus ($direction) { return $self }
Called when the default order ("focus_next" in Clay::UI::Interaction, "default_next_focus", "focus_after" in Clay::UI::Interaction), coming from outside this widget's subtree, would focus a widget at or below it. $direction is 'next' or 'previous'. Returns the widget that should get the focus instead, validated like the result of "get_next_focus", or undef to let the default order continue into the subtree. It is not called while the focus is inside the subtree, nor for a walk whose scope is this widget or below it, so a class may use "default_next_focus" with within => $self freely. With such widgets nested, the outermost one that does not hold the focus is asked.
focus_scope
method focus_scope () { return $dialog }
The subtree this widget's order covers, when it is not the widget's own subtree: a widget of the same Clay::UI. "focus_after" in Clay::UI::Interaction steps inside it when it hands the focus on after a subtree below this widget. A modal backdrop whose order keeps Tab inside its dialog returns the dialog here, so a view at the end of the dialog hands the focus to the dialog's first widget and never to the backdrop or the window behind it. Without this method the scope is the widget itself.
PROVIDED METHODS
default_next_focus
return $self->default_next_focus;
return $self->default_next_focus(within => $self);
Returns the widget the default order would focus next from the currently focused widget, ignoring the orders of other HasFocusOrder widgets but honouring their entry hooks (see "default_next_focus" in Clay::UI::Interaction), or undef when no widget can take the focus or this widget does not belong to a Clay::UI. Use it for partial overrides: handle the special case, and return $self->default_next_focus for everything else.
The arguments go to the tracker as they are. With within => $widget (a focus scope, see "Focus scopes" in Clay::UI::Interaction) the order is limited to that subtree and wraps around inside it, which is how a modal dialog keeps Tab inside itself:
method get_next_focus () { return $self->default_next_focus(within => $self) }
method get_previous_focus () { return $self->default_previous_focus(within => $self) }
default_previous_focus
return $self->default_previous_focus;
return $self->default_previous_focus(within => $self);
The mirror of "default_next_focus".
SEE ALSO
"FOCUS" in Clay::UI::Interaction, "focus_next" in Clay::UI::Interaction, Clay::UI::Role::Interaction::Focusable.