///|
pub(all) enum SemanticsRole {
None
Text
Button
Checkbox
TextField
List
ListItem
Grid
ScrollView
Switch
Radio
Slider
Progress
Tab
Dialog
Tooltip
Menu
Group
} derive(Eq, Debug, ToJson)
///|
pub(all) enum SemanticsAction {
Activate
Focus
SetText
Submit
Scroll
} derive(Eq, Debug, ToJson)
///|
pub(all) struct SemanticsState {
focused : Bool
disabled : Bool
selected : Bool
checked : Bool
expanded : Bool
} derive(Eq, Debug, ToJson)
///|
pub(all) struct SemanticsNode {
element_id : ElementId
role : SemanticsRole
label : String
value : String
state : SemanticsState
actions : Array[SemanticsAction]
focus_order : Int?
frame : Rect
children : Array[SemanticsNode]
} derive(Eq, Debug, ToJson)
///|
pub fn SemanticsState::default() -> SemanticsState {
{
focused: false,
disabled: false,
selected: false,
checked: false,
expanded: false,
}
}
///|
fn ElementNode::semantics(
self : ElementNode,
render : RenderNode,
focused? : ElementId? = None,
) -> SemanticsNode {
let next_order = Ref(0)
self.build_semantics(render, focused, next_order, list_item=false)
}
///|
fn ElementNode::build_semantics(
self : ElementNode,
render : RenderNode,
focused : ElementId?,
next_order : Ref[Int],
list_item~ : Bool,
) -> SemanticsNode {
let info = self.semantics_info(list_item~)
let focusable = info.actions.any(action => action == SemanticsAction::Focus)
let focus_order = if focusable {
let order = next_order.val
next_order.val = next_order.val + 1
Some(order)
} else {
None
}
let state = {
focused: focused == Some(self.id),
disabled: self.is_semantics_disabled(),
selected: false,
checked: self.is_semantics_checked(),
expanded: false,
}
let children : Array[SemanticsNode] = []
let child_is_list_item = self.spec is ListSpec(_)
for index, child in self.children {
if index < render.children.length() {
children.push(
child.build_semantics(
render.children[index],
focused,
next_order,
list_item=child_is_list_item,
),
)
}
}
{
element_id: self.id,
role: info.role,
label: info.label,
value: info.value,
state,
actions: info.actions,
focus_order,
frame: render.frame,
children,
}
}
///|
priv struct SemanticsInfo {
role : SemanticsRole
label : String
value : String
actions : Array[SemanticsAction]
}
///|
fn ElementNode::semantics_info(
self : ElementNode,
list_item~ : Bool,
) -> SemanticsInfo {
match self.spec {
TextSpec(data) =>
{
role: if list_item {
ListItem
} else {
Text
},
label: data.text,
value: data.text,
actions: [],
}
ButtonSpec(data) =>
{ role: Button, label: data.text, value: "", actions: [Focus, Activate] }
CheckboxSpec(data) =>
{
role: Checkbox,
label: data.label,
value: if data.checked.get() {
"true"
} else {
"false"
},
actions: [Focus, Activate],
}
PickerSpec(data) =>
{
role: Menu,
label: data.placeholder,
value: data.selected.get(),
actions: [Focus, Activate],
}
DatePickerSpec(data) =>
{
role: Menu,
label: data.placeholder,
value: data.selected.get().format(data.mode),
actions: [Focus, Activate],
}
SearchBarSpec(data) =>
{
role: TextField,
label: data.placeholder,
value: data.query.get(),
actions: [Focus, SetText, Submit],
}
SheetSpec(_data) => { role: Dialog, label: "", value: "", actions: [] }
TextFieldSpec(data) =>
{
role: TextField,
label: data.placeholder,
value: data.value.get(),
actions: [Focus, SetText, Submit],
}
RichTextEditorSpec(data) =>
{
role: TextField,
label: data.placeholder,
value: data.value.get(),
actions: [Focus, SetText],
}
ListSpec(_) => { role: List, label: "", value: "", actions: [] }
GridSpec(_) => { role: Grid, label: "", value: "", actions: [] }
ScrollSpec(_) =>
{ role: ScrollView, label: "", value: "", actions: [Scroll] }
ImageSpec(data) =>
{ role: None, label: data.source, value: "", actions: [] }
CustomSpec(data) =>
{
role: data.semantics_role,
label: data.semantics_label,
value: data.semantics_label,
actions: [],
}
CustomLayoutSpec(data) =>
{
role: data.semantics_role,
label: data.semantics_label,
value: data.semantics_label,
actions: [],
}
EmptySpec | SpacerSpec(_) =>
{ role: None, label: "", value: "", actions: [] }
FlexSpec(_)
| PaddingSpec(_)
| ContainerSpec(_)
| FrameSpec(_)
| StackSpec(_)
| ComponentSpec(_) => { role: Group, label: "", value: "", actions: [] }
ModifiedSpec(data) => modified_semantics_info(data.modifier)
}
}
///|
fn modified_semantics_info(modifier : ViewModifier) -> SemanticsInfo {
match modifier {
AccessibilityLabelModifier(label) =>
{ role: Group, label, value: label, actions: [] }
SemanticsRoleModifier(role) => { role, label: "", value: "", actions: [] }
FocusableModifier(focusable) =>
{
role: Group,
label: "",
value: "",
actions: if focusable {
[Focus]
} else {
[]
},
}
OnTapModifier(_) | KeyboardShortcutModifier(_) =>
{ role: Button, label: "", value: "", actions: [Focus, Activate] }
OnFileDropModifier(_) => { role: Group, label: "", value: "", actions: [] }
DisabledModifier(_)
| PaddingModifier(_)
| FrameModifier(_)
| BackgroundModifier(_)
| ForegroundModifier(_)
| FontModifier(_)
| CornerRadiusModifier(_)
| OpacityModifier(_)
| ShadowModifier(_)
| BorderModifier(_)
| OffsetModifier(_, _)
| ClipModifier
| ScaleModifier(_, _)
| FlexibleModifier(_)
| LayoutPriorityModifier(_)
| AlignModifier(_)
| AspectRatioModifier(_)
| IntrinsicWidthModifier
| IntrinsicHeightModifier =>
{ role: Group, label: "", value: "", actions: [] }
}
}
///|
fn ElementNode::is_semantics_checked(self : ElementNode) -> Bool {
match self.spec {
CheckboxSpec(data) => data.checked.get()
ModifiedSpec(_) =>
if self.children.length() > 0 {
self.children[0].is_semantics_checked()
} else {
false
}
_ => false
}
}
///|
fn ElementNode::is_semantics_disabled(self : ElementNode) -> Bool {
match self.spec {
ModifiedSpec(data) => data.modifier is DisabledModifier(true)
_ => false
}
}