Add `WidgetType::Tab`
- Dominant language
- Rust
- Stars
- 30.6k
- Forks
- 2.1k
- Avg merge
- 1d 9h
- Merged PRs (30d)
- 72
Description
**Is your feature request related to a problem? Please describe.**
There is no suitable `WidgetType` for tabs while I was implementing AccessKit support for egui_dock. AccessKit provides [`accesskit::Role::Tab`](https://docs.rs/accesskit/latest/accesskit/enum.Role.html#variant.Tab) and other tab-related widgets that will be described by assistive technologies accurately.
**Describe the solution you'd like**
Add a `WidgetType::Tab` variant mapping to `accesskit::Role::Tab`, with `WidgetInfo::selected` triggering set_selected instead of set_toggled for it. Possibly also some way to mark a container `Ui` as `Role::TabList` or `Role::TabPanel`.
**Describe alternatives you've considered**
Currently I had to override the role after `widget_info` using `accesskit_node_builder`:
```rust
response.widget_info(|| WidgetInfo::selected(WidgetType::Button, enabled, selected, title));
ui.ctx().accesskit_node_builder(response.id, |node| {
node.set_role(accesskit::Role::Tab);
node.set_selected(selected);
node.clear_toggled();
});
```
Also tried using other existing `WidgetTypes` like selectable label, but assistive technologies will interpret it as a selectable label instead of a tab, which can be confusing.
**Additional context**
See https://github.com/anhosh/egui_dock/pull/345.
**Edit:** the following has been opened to #8557.
During that PR, I also found out that there is no way to describe the direction (horizontal/vertical) on `WidgetType::ResizeHandle`, and all splitters are interpreted as "horizontal" in VoiceOver, and also window resizing doesn't work using VoiceOver (it always reports the window resizing handle as a horizontal splitter stuck at 0%). The PR works around this by
```rust
let ak_steps = ui.input(|input| {
input.num_accesskit_action_requests(response.id, accesskit::Action::Increment) as f32
- input.num_accesskit_action_requests(response.id, accesskit::Action::Decrement) as f32
});
let ak_set_value = ui.input(|input| {
input
.accesskit_action_requests(response.id, accesskit::Action::SetValue)
.find_map(|request| match request.data {
Some(accesskit::ActionData::NumericValue(value)) => Some(value as f32),
_ => None,
})
});
let mut delta = arrow_key_offset.unwrap_or(response.drag_delta());
delta.dim_point += ak_steps * style.separator.arrow_key_step_distance;
if let Some(value) = ak_set_value {
delta.dim_point += (value.clamp(0.0, 1.0) - split.fraction) * rect.dim_size();
}
apply_separator_delta(split, SeparatorAxis::sep_axis, delta, &style.separator);
if response.double_clicked() {
split.fraction = 0.5;
}
// Allow assistive technologies to adjust the separator fraction.
let fraction = split.fraction;
let step = style.separator.arrow_key_step_distance / rect.dim_size().max(1.0);
ui.ctx().accesskit_node_builder(response.id, |node| {
node.set_orientation(accesskit::Orientation::ak_orientation);
node.set_numeric_value(fraction.into());
node.set_min_numeric_value(0.0);
node.set_max_numeric_value(1.0);
node.set_numeric_value_step(step.into());
node.add_action(accesskit::Action::Increment);
node.add_action(accesskit::Action::Decrement);
node.add_action(accesskit::Action::SetValue);
});
```
Contributor guide
Research direction
Locate the WidgetType definition and the implementation of WidgetInfo::selected, then trace the existing AccessKit role and state mapping. Use the egui_dock pull request context to verify the tab behavior; done means tabs map to accesskit::Role::Tab and selected tabs use set_selected rather than set_toggled, with any container-role support resolved.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- accessibility
- Issue type
- Feature
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100