mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 21:30:03 -04:00
Output events when widgets gain keyboard focus
Part of https://github.com/emilk/egui/issues/167
This commit is contained in:
@@ -207,6 +207,10 @@ impl CollapsingHeader {
|
||||
let (_, rect) = ui.allocate_space(desired_size);
|
||||
|
||||
let header_response = ui.interact(rect, id, Sense::click());
|
||||
if header_response.gained_kb_focus() {
|
||||
ui.output()
|
||||
.push_gained_focus_event(WidgetType::CollapsingHeader, &galley.text);
|
||||
}
|
||||
let text_pos = pos2(
|
||||
text_pos.x,
|
||||
header_response.rect.center().y - galley.size.y / 2.0,
|
||||
|
||||
@@ -27,6 +27,10 @@ pub fn combo_box_with_label(
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let mut response = combo_box(ui, button_id, selected, menu_contents);
|
||||
if response.gained_kb_focus() {
|
||||
ui.output()
|
||||
.push_gained_focus_event(WidgetType::ComboBox, label.text());
|
||||
}
|
||||
response |= ui.add(label);
|
||||
response
|
||||
})
|
||||
|
||||
@@ -20,6 +20,9 @@ pub struct Output {
|
||||
/// As an egui user: don't set this value directly.
|
||||
/// Call `Context::request_repaint()` instead and it will do so for you.
|
||||
pub needs_repaint: bool,
|
||||
|
||||
/// Events that may be useful to e.g. a screen reader.
|
||||
pub events: Vec<OutputEvent>,
|
||||
}
|
||||
|
||||
/// A mouse cursor icon.
|
||||
@@ -45,3 +48,42 @@ impl Default for CursorIcon {
|
||||
Self::Default
|
||||
}
|
||||
}
|
||||
|
||||
/// Things that happened during this frame that the integration may be interested in.
|
||||
///
|
||||
/// In particular, these events may be useful for accessability, i.e. for screen readers.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum OutputEvent {
|
||||
/// A widget gained keyboard focus (by tab key).
|
||||
///
|
||||
/// An integration can for instance read the newly selected widget out loud for the visually impaired.
|
||||
//
|
||||
// TODO: we should output state too, e.g. if a checkbox is selected, or current slider value.
|
||||
Focused(WidgetType, String),
|
||||
}
|
||||
|
||||
/// The different types of built-in widgets in egui
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum WidgetType {
|
||||
Label,
|
||||
Hyperlink,
|
||||
TextEdit,
|
||||
Button,
|
||||
Checkbox,
|
||||
RadioButton,
|
||||
SelectableLabel,
|
||||
ComboBox,
|
||||
Slider,
|
||||
DragValue,
|
||||
ColorButton,
|
||||
ImageButton,
|
||||
CollapsingHeader,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
/// Inform the backend integration that a widget gained focus
|
||||
pub fn push_gained_focus_event(&mut self, widget_type: WidgetType, text: impl Into<String>) {
|
||||
self.events
|
||||
.push(OutputEvent::Focused(widget_type, text.into()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +119,10 @@ impl Button {
|
||||
}
|
||||
|
||||
let (rect, response) = ui.allocate_at_least(desired_size, sense);
|
||||
if response.gained_kb_focus() {
|
||||
ui.output()
|
||||
.push_gained_focus_event(WidgetType::TextEdit, &galley.text);
|
||||
}
|
||||
|
||||
if ui.clip_rect().intersects(rect) {
|
||||
let visuals = ui.style().interact(&response);
|
||||
@@ -228,6 +232,10 @@ impl<'a> Widget for Checkbox<'a> {
|
||||
desired_size = desired_size.at_least(spacing.interact_size);
|
||||
desired_size.y = desired_size.y.max(icon_width);
|
||||
let (rect, mut response) = ui.allocate_exact_size(desired_size, Sense::click());
|
||||
if response.gained_kb_focus() {
|
||||
ui.output()
|
||||
.push_gained_focus_event(WidgetType::Checkbox, &galley.text);
|
||||
}
|
||||
|
||||
if response.clicked() {
|
||||
*checked = !*checked;
|
||||
@@ -338,6 +346,10 @@ impl Widget for RadioButton {
|
||||
desired_size = desired_size.at_least(ui.spacing().interact_size);
|
||||
desired_size.y = desired_size.y.max(icon_width);
|
||||
let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click());
|
||||
if response.gained_kb_focus() {
|
||||
ui.output()
|
||||
.push_gained_focus_event(WidgetType::RadioButton, &galley.text);
|
||||
}
|
||||
|
||||
let text_cursor = pos2(
|
||||
rect.min.x + button_padding.x + icon_width + icon_spacing,
|
||||
@@ -442,6 +454,10 @@ impl Widget for ImageButton {
|
||||
let button_padding = ui.spacing().button_padding;
|
||||
let size = image.size() + 2.0 * button_padding;
|
||||
let (rect, response) = ui.allocate_exact_size(size, sense);
|
||||
if response.gained_kb_focus() {
|
||||
ui.output()
|
||||
.push_gained_focus_event(WidgetType::ImageButton, "");
|
||||
}
|
||||
|
||||
if ui.clip_rect().intersects(rect) {
|
||||
let visuals = ui.style().interact(&response);
|
||||
|
||||
@@ -66,6 +66,10 @@ fn show_hsva(ui: &mut Ui, color: Hsva, desired_size: Vec2) -> Response {
|
||||
fn color_button(ui: &mut Ui, color: Color32) -> Response {
|
||||
let size = ui.spacing().interact_size;
|
||||
let (rect, response) = ui.allocate_exact_size(size, Sense::click());
|
||||
if response.gained_kb_focus() {
|
||||
ui.output()
|
||||
.push_gained_focus_event(WidgetType::ColorButton, "");
|
||||
}
|
||||
let visuals = ui.style().interact(&response);
|
||||
let rect = rect.expand(visuals.expansion);
|
||||
|
||||
|
||||
@@ -298,6 +298,11 @@ impl<'a> Widget for DragValue<'a> {
|
||||
response
|
||||
};
|
||||
|
||||
if response.gained_kb_focus() {
|
||||
ui.output()
|
||||
.push_gained_focus_event(WidgetType::DragValue, "");
|
||||
}
|
||||
|
||||
#[allow(clippy::float_cmp)]
|
||||
{
|
||||
response.changed = get(&mut get_set_value) != value;
|
||||
|
||||
@@ -53,6 +53,10 @@ impl Widget for Hyperlink {
|
||||
let Hyperlink { url, label } = self;
|
||||
let galley = label.layout(ui);
|
||||
let (rect, response) = ui.allocate_exact_size(galley.size, Sense::click());
|
||||
if response.gained_kb_focus() {
|
||||
ui.output()
|
||||
.push_gained_focus_event(WidgetType::Hyperlink, &galley.text);
|
||||
}
|
||||
|
||||
if response.hovered() {
|
||||
ui.ctx().output().cursor_icon = CursorIcon::PointingHand;
|
||||
|
||||
@@ -55,6 +55,10 @@ impl Widget for SelectableLabel {
|
||||
let mut desired_size = total_extra + galley.size;
|
||||
desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y);
|
||||
let (rect, response) = ui.allocate_at_least(desired_size, Sense::click());
|
||||
if response.gained_kb_focus() {
|
||||
ui.output()
|
||||
.push_gained_focus_event(WidgetType::SelectableLabel, &galley.text);
|
||||
}
|
||||
|
||||
let text_cursor = ui
|
||||
.layout()
|
||||
|
||||
@@ -322,6 +322,11 @@ impl<'a> Slider<'a> {
|
||||
self.set_value(new_value);
|
||||
}
|
||||
|
||||
if response.gained_kb_focus() {
|
||||
ui.output()
|
||||
.push_gained_focus_event(WidgetType::Slider, &self.text);
|
||||
}
|
||||
|
||||
if response.has_kb_focus() {
|
||||
let kb_step = ui.input().num_presses(Key::ArrowRight) as f32
|
||||
- ui.input().num_presses(Key::ArrowLeft) as f32;
|
||||
|
||||
@@ -321,6 +321,10 @@ impl<'t> TextEdit<'t> {
|
||||
Sense::hover()
|
||||
};
|
||||
let mut response = ui.interact(rect, id, sense);
|
||||
if response.gained_kb_focus() {
|
||||
ui.output()
|
||||
.push_gained_focus_event(WidgetType::TextEdit, &*text);
|
||||
}
|
||||
|
||||
if enabled {
|
||||
if let Some(pointer_pos) = ui.input().pointer.interact_pos() {
|
||||
|
||||
Reference in New Issue
Block a user