Domain
Feature FocusDomain
& TouchAreaDomain
or combined into Domain
#9274
Unanswered
bryce-happel-walton
asked this question in
Ideas
Replies: 1 comment
-
I wrote something that feels pretty hacky, but works very well. It requires an overlook-able amount of work for me to implement this all over my codebase. If I forget to attach one callback, navigation is no longer predictable and it could create a safety issue for the user. import { Button } from "std-widgets.slint";
import { ApplicationState } from "../application/application_state.slint";
global DomainState {
in-out property <int> num-layers: 1;
in-out property <int> current-layer: 0;
in-out property <int> last-layer: 0;
public function create-domain-layer() -> int {
num-layers += 1;
num-layers - 1
}
public function enter-domain(domain-layer: int) {
DomainState.last-layer = DomainState.current-layer;
DomainState.current-layer = domain-layer;
}
public function domain-exit(domain-layer: int) -> bool {
if domain-layer == self.current-layer {
enter-domain(self.last-layer);
true
}
false
}
}
export component ControlledFocusScope {
in-out property <bool> force-focus: true;
in-out property <bool> enabled <=> i-focus.enabled;
callback key-pressed(KeyEvent) -> EventResult;
callback key-released(KeyEvent) -> EventResult;
callback focus-gained(FocusReason);
callback focus-lost(FocusReason);
callback activated();
forward-focus: i-focus;
i-touch := TouchArea {
enabled: i-focus.enabled;
clicked => {i-focus.focus()};
double-clicked => {root.activated()};
}
i-focus := FocusScope {
width: 0;
init => {
if force-focus {
self.focus();
}
}
key-pressed(event) => {
if (event.text == Key.Return) || (event.text == Key.Space) {
root.activated();
accept
} else {
root.key-pressed(event)
}
}
key-released(event) => {root.key-released(event)};
focus-lost(reason) => {root.focus-lost(reason)};
focus-gained(reason) => {root.focus-gained(reason)};
}
}
export component ControlledFocusDomain {
in-out property <bool> force-focus: true;
in-out property <bool> force-exit-strategy: true;
in property <length> exit-button-width <=> exit.width;
in property <length> exit-button-height <=> exit.height;
out property <int> domain-layer;
out property <bool> domain-active: DomainState.current-layer == self.domain-layer;
private property <int> last-domain: DomainState.last-layer;
private property <bool> was-last: false;
callback nav-front-forward();
callback nav-end-backward();
callback exited();
callback domain-returned();
public function enter-domain() {
DomainState.enter-domain(self.domain-layer);
}
public function exit-domain() {
DomainState.domain-exit(self.domain-layer);
}
forward-focus: pre-nav-lock;
width: 100%;
height: 100%;
changed domain-active => {
if domain-active {
pre-nav-lock.focus();
if was-last {
was-last = false;
domain-returned();
}
} else {
self.exited();
}
}
changed last-domain => {
if last-domain == domain-layer {
was-last = true;
}
}
init => {
self.domain-layer = DomainState.create-domain-layer();
if force-focus {
enter-domain();
}
}
pre-nav-lock := FocusScope {
enabled: root.domain-active;
focus-gained(reason) => {
if reason == FocusReason.tab-navigation {
post-nav-lock.focus();
} else {
nav-front-forward();
}
}
}
if ApplicationState.debug-mode: Rectangle {
width: 100%;
height: 100%;
background: domain-active ? red : transparent;
}
container := Rectangle {
y: 0;
clip: true;
height: (root.domain-active && force-exit-strategy) ? root.height - exit-button-height : root.height;
@children
}
exit := Button {
visible: root.domain-active && root.force-exit-strategy;
y: root.height - self.height;
text: "Back";
clicked => {exit-domain()};
}
post-nav-lock := FocusScope {
enabled: root.domain-active;
focus-gained(reason) => {
if reason == FocusReason.tab-navigation {
pre-nav-lock.focus();
} else if force-exit-strategy {
exit.focus();
} else {
nav-end-backward();
}
}
}
} How I'm using it: center-widget-container-indicator := ScrollView {
property <length> indicator-width: self.height;
i-domain := ControlledFocusDomain {
force-exit-strategy: false;
force-focus: true;
private property <int> return-page;
nav-front-forward => {WidgetContainerState.change-page(0)};
nav-end-backward => {WidgetContainerState.change-page(WidgetContainerState.num-pages - 1)};
domain-returned => {WidgetContainerState.change-page(return-page)};
HorizontalLayout {
alignment: center;
spacing: 10px;
for i in WidgetContainerState.num-pages: Rectangle {
private property <bool> has-view: WidgetContainerState.current-page == i;
private property <bool> domain-active: i-domain.domain-active;
width: indicator-width;
forward-focus: i-focus;
function attempt-focus() {
if domain-active && has-view {
self.focus();
}
}
changed has-view => {
attempt-focus();
}
changed domain-active => {
attempt-focus();
}
init => {
if i == 0 && domain-active {
self.focus();
}
}
Rectangle {
background: darkblue;
visible: has-view;
}
Text {
color: white;
text: i + 1;
}
i-focus := ControlledFocusScope {
force-focus: false;
focus-gained => {
if !has-view { // prevent dirty-ing the number and causing recursion
WidgetContainerState.change-page(i);
}
}
activated => {
return-page = i;
WidgetContainerState.focus-page(i);
}
}
}
}
}
} |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
My application is entirely keyboard controlled, and this has introduced some fun issues with handling focus and where the user is allowed to traverse. I know the idea of tab-navigation is that the user should be able to navigate to every single focusable element freely, but I need something with less freedom.
I would like to lock the focus to a specific domain so that tab-navigation will not try to navigate out of this domain, and we know which domain the user is within.
This would allow capturing user input while the user is focused on a focusable element that will reject the input.
Programmatically, this
FocusDomain
would be aFocusScope
that has@children
and would only allow traversing through the children of itself.Beta Was this translation helpful? Give feedback.
All reactions