mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
Update accesskit to 0.24.0 (and related deps) (#7850)
this patch updates our deps to [accesskit 0.24.0](https://docs.rs/accesskit/0.24.0/accesskit/) (was [0.21.1](https://docs.rs/accesskit/0.21.1/accesskit/)), [accesskit_consumer 0.35.0](https://docs.rs/accesskit_consumer/0.35.0/accesskit_consumer/) (was [0.30.1](https://docs.rs/accesskit_consumer/0.30.1/accesskit_consumer/)), and [accesskit_winit 0.32.0](https://docs.rs/accesskit_winit/0.32.0/accesskit_winit/) (was [0.29.1](https://docs.rs/accesskit_winit/0.29.1/accesskit_winit/)), allowing egui to be used in apps that use accessibility subtrees (AccessKit/accesskit#655). for now, we handle the subtree-related breaking changes by assuming that egui will use [the root tree](https://docs.rs/accesskit/0.24.0/accesskit/struct.TreeId.html#associatedconstant.ROOT), which is good enough for [servoshell](https://github.com/servo/servo) and does not require any API changes. * [x] I have followed the instructions in the PR template <img width="1185" height="954" alt="image" src="https://github.com/user-attachments/assets/6acfb85a-096d-4a7b-963b-d8549bfc749f" /> --------- Co-authored-by: Luke Warlow <lwarlow@igalia.com> Co-authored-by: lucasmerlin <hi@lucasmerlin.me> Co-authored-by: Arnold Loubriat <datatriny@gmail.com>
This commit is contained in:
@@ -102,7 +102,7 @@ pub struct State {
|
||||
has_sent_ime_enabled: bool,
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
accesskit: Option<accesskit_winit::Adapter>,
|
||||
pub accesskit: Option<accesskit_winit::Adapter>,
|
||||
|
||||
allow_ime: bool,
|
||||
ime_rect_px: Option<egui::Rect>,
|
||||
|
||||
@@ -2616,6 +2616,7 @@ impl ContextImpl {
|
||||
platform_output.accesskit_update = Some(accesskit::TreeUpdate {
|
||||
nodes,
|
||||
tree: Some(accesskit::Tree::new(root_id)),
|
||||
tree_id: accesskit::TreeId::ROOT,
|
||||
focus: focus_id,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ impl Id {
|
||||
self.0.get()
|
||||
}
|
||||
|
||||
pub(crate) fn accesskit_id(&self) -> accesskit::NodeId {
|
||||
pub fn accesskit_id(&self) -> accesskit::NodeId {
|
||||
self.value().into()
|
||||
}
|
||||
|
||||
|
||||
@@ -871,7 +871,8 @@ impl InputState {
|
||||
let accesskit_id = id.accesskit_id();
|
||||
self.events.iter().filter_map(move |event| {
|
||||
if let Event::AccessKitActionRequest(request) = event
|
||||
&& request.target == accesskit_id
|
||||
&& request.target_node == accesskit_id
|
||||
&& request.target_tree == accesskit::TreeId::ROOT
|
||||
&& request.action == action
|
||||
{
|
||||
return Some(request);
|
||||
@@ -888,7 +889,8 @@ impl InputState {
|
||||
let accesskit_id = id.accesskit_id();
|
||||
self.events.retain(|event| {
|
||||
if let Event::AccessKitActionRequest(request) = event
|
||||
&& request.target == accesskit_id
|
||||
&& request.target_node == accesskit_id
|
||||
&& request.target_tree == accesskit::TreeId::ROOT
|
||||
{
|
||||
return !consume(request);
|
||||
}
|
||||
|
||||
@@ -564,11 +564,13 @@ impl Focus {
|
||||
|
||||
if let crate::Event::AccessKitActionRequest(accesskit::ActionRequest {
|
||||
action: accesskit::Action::Focus,
|
||||
target,
|
||||
target_node,
|
||||
target_tree,
|
||||
data: None,
|
||||
}) = event
|
||||
&& *target_tree == accesskit::TreeId::ROOT
|
||||
{
|
||||
self.id_requested_by_accesskit = Some(*target);
|
||||
self.id_requested_by_accesskit = Some(*target_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,26 @@ use crate::{Context, Galley, Id};
|
||||
|
||||
use super::{CCursorRange, text_cursor_state::is_word_char};
|
||||
|
||||
/// AccessKit's `word_starts` uses `u8` indices, so text runs cannot exceed this length.
|
||||
pub(crate) const MAX_CHARS_PER_TEXT_RUN: usize = 255;
|
||||
|
||||
/// Convert a (row, column) layout cursor position to a text run node ID and character index,
|
||||
/// accounting for rows that are split into multiple text runs.
|
||||
fn text_run_position(parent_id: Id, row: usize, column: usize) -> accesskit::TextPosition {
|
||||
// When column lands exactly on a chunk boundary (e.g., 255), it refers to
|
||||
// the end of the previous chunk, not the start of a new one.
|
||||
let chunk_index = if column > 0 && column.is_multiple_of(MAX_CHARS_PER_TEXT_RUN) {
|
||||
column / MAX_CHARS_PER_TEXT_RUN - 1
|
||||
} else {
|
||||
column / MAX_CHARS_PER_TEXT_RUN
|
||||
};
|
||||
let character_index = column - chunk_index * MAX_CHARS_PER_TEXT_RUN;
|
||||
accesskit::TextPosition {
|
||||
node: parent_id.with(row).with(chunk_index).accesskit_id(),
|
||||
character_index,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update accesskit with the current text state.
|
||||
pub fn update_accesskit_for_text_widget(
|
||||
ctx: &Context,
|
||||
@@ -20,14 +40,8 @@ pub fn update_accesskit_for_text_widget(
|
||||
let anchor = galley.layout_from_cursor(cursor_range.secondary);
|
||||
let focus = galley.layout_from_cursor(cursor_range.primary);
|
||||
builder.set_text_selection(accesskit::TextSelection {
|
||||
anchor: accesskit::TextPosition {
|
||||
node: parent_id.with(anchor.row).accesskit_id(),
|
||||
character_index: anchor.column,
|
||||
},
|
||||
focus: accesskit::TextPosition {
|
||||
node: parent_id.with(focus.row).accesskit_id(),
|
||||
character_index: focus.column,
|
||||
},
|
||||
anchor: text_run_position(parent_id, anchor.row, anchor.column),
|
||||
focus: text_run_position(parent_id, focus.row, focus.column),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -40,61 +54,144 @@ pub fn update_accesskit_for_text_widget(
|
||||
return;
|
||||
};
|
||||
|
||||
let mut prev_row_ended_with_newline = true;
|
||||
|
||||
for (row_index, row) in galley.rows.iter().enumerate() {
|
||||
let row_id = parent_id.with(row_index);
|
||||
let glyph_count = row.glyphs.len();
|
||||
let mut value = String::with_capacity(glyph_count);
|
||||
let mut character_lengths = Vec::<u8>::with_capacity(glyph_count);
|
||||
let mut character_positions = Vec::<f32>::with_capacity(glyph_count);
|
||||
let mut character_widths = Vec::<f32>::with_capacity(glyph_count);
|
||||
let mut word_starts = Vec::<usize>::new();
|
||||
// For soft-wrapped continuation rows, treat the start as a word
|
||||
// boundary so the first word character gets a `word_starts` entry.
|
||||
// Paragraph-starting runs (first row or after a newline) get an
|
||||
// implicit word start from AccessKit, so they don't need this.
|
||||
let mut was_at_word_end = !prev_row_ended_with_newline;
|
||||
|
||||
ctx.register_accesskit_parent(row_id, parent_id);
|
||||
for glyph in &row.glyphs {
|
||||
let is_word_char = is_word_char(glyph.chr);
|
||||
if is_word_char && was_at_word_end {
|
||||
word_starts.push(character_lengths.len());
|
||||
}
|
||||
was_at_word_end = !is_word_char;
|
||||
let old_len = value.len();
|
||||
value.push(glyph.chr);
|
||||
character_lengths.push((value.len() - old_len) as _);
|
||||
character_positions.push(glyph.pos.x - row.pos.x);
|
||||
character_widths.push(glyph.advance_width);
|
||||
}
|
||||
|
||||
ctx.accesskit_node_builder(row_id, |builder| {
|
||||
builder.set_role(accesskit::Role::TextRun);
|
||||
let rect = global_from_galley * row.rect_without_leading_space();
|
||||
builder.set_bounds(accesskit::Rect {
|
||||
x0: rect.min.x.into(),
|
||||
y0: rect.min.y.into(),
|
||||
x1: rect.max.x.into(),
|
||||
y1: rect.max.y.into(),
|
||||
});
|
||||
builder.set_text_direction(accesskit::TextDirection::LeftToRight);
|
||||
// TODO(mwcampbell): Set more node fields for the row
|
||||
// once AccessKit adapters expose text formatting info.
|
||||
if row.ends_with_newline {
|
||||
value.push('\n');
|
||||
character_lengths.push(1);
|
||||
character_positions.push(row.size.x);
|
||||
character_widths.push(0.0);
|
||||
}
|
||||
|
||||
let glyph_count = row.glyphs.len();
|
||||
let mut value = String::new();
|
||||
value.reserve(glyph_count);
|
||||
let mut character_lengths = Vec::<u8>::with_capacity(glyph_count);
|
||||
let mut character_positions = Vec::<f32>::with_capacity(glyph_count);
|
||||
let mut character_widths = Vec::<f32>::with_capacity(glyph_count);
|
||||
let mut word_lengths = Vec::<u8>::new();
|
||||
let mut was_at_word_end = false;
|
||||
let mut last_word_start = 0usize;
|
||||
let total_chars = character_lengths.len();
|
||||
|
||||
for glyph in &row.glyphs {
|
||||
let is_word_char = is_word_char(glyph.chr);
|
||||
if is_word_char && was_at_word_end {
|
||||
word_lengths.push((character_lengths.len() - last_word_start) as _);
|
||||
last_word_start = character_lengths.len();
|
||||
if total_chars <= MAX_CHARS_PER_TEXT_RUN {
|
||||
let run_id = parent_id.with(row_index).with(0usize);
|
||||
ctx.register_accesskit_parent(run_id, parent_id);
|
||||
|
||||
ctx.accesskit_node_builder(run_id, |builder| {
|
||||
builder.set_role(accesskit::Role::TextRun);
|
||||
builder.set_text_direction(accesskit::TextDirection::LeftToRight);
|
||||
// TODO(mwcampbell): Set more node fields for the row
|
||||
// once AccessKit adapters expose text formatting info.
|
||||
|
||||
let rect = global_from_galley * row.rect_without_leading_space();
|
||||
builder.set_bounds(accesskit::Rect {
|
||||
x0: rect.min.x.into(),
|
||||
y0: rect.min.y.into(),
|
||||
x1: rect.max.x.into(),
|
||||
y1: rect.max.y.into(),
|
||||
});
|
||||
builder.set_value(value);
|
||||
builder.set_character_lengths(character_lengths);
|
||||
|
||||
let pos_offset = character_positions.first().copied().unwrap_or(0.0);
|
||||
for p in &mut character_positions {
|
||||
*p -= pos_offset;
|
||||
}
|
||||
was_at_word_end = !is_word_char;
|
||||
let old_len = value.len();
|
||||
value.push(glyph.chr);
|
||||
character_lengths.push((value.len() - old_len) as _);
|
||||
character_positions.push(glyph.pos.x - row.pos.x);
|
||||
character_widths.push(glyph.advance_width);
|
||||
}
|
||||
builder.set_character_positions(character_positions);
|
||||
builder.set_character_widths(character_widths);
|
||||
|
||||
if row.ends_with_newline {
|
||||
value.push('\n');
|
||||
character_lengths.push(1);
|
||||
character_positions.push(row.size.x);
|
||||
character_widths.push(0.0);
|
||||
}
|
||||
word_lengths.push((character_lengths.len() - last_word_start) as _);
|
||||
let chunk_word_starts: Vec<u8> = word_starts.iter().map(|&ws| ws as u8).collect();
|
||||
builder.set_word_starts(chunk_word_starts);
|
||||
});
|
||||
} else {
|
||||
let num_chunks = total_chars.div_ceil(MAX_CHARS_PER_TEXT_RUN);
|
||||
let mut byte_offset = 0usize;
|
||||
|
||||
builder.set_value(value);
|
||||
builder.set_character_lengths(character_lengths);
|
||||
builder.set_character_positions(character_positions);
|
||||
builder.set_character_widths(character_widths);
|
||||
builder.set_word_lengths(word_lengths);
|
||||
});
|
||||
for chunk_idx in 0..num_chunks {
|
||||
let char_start = chunk_idx * MAX_CHARS_PER_TEXT_RUN;
|
||||
let char_end = (char_start + MAX_CHARS_PER_TEXT_RUN).min(total_chars);
|
||||
|
||||
let byte_start = byte_offset;
|
||||
let chunk_byte_len: usize = character_lengths[char_start..char_end]
|
||||
.iter()
|
||||
.map(|&l| l as usize)
|
||||
.sum();
|
||||
let byte_end = byte_start + chunk_byte_len;
|
||||
byte_offset = byte_end;
|
||||
|
||||
let run_id = parent_id.with(row_index).with(chunk_idx);
|
||||
ctx.register_accesskit_parent(run_id, parent_id);
|
||||
|
||||
ctx.accesskit_node_builder(run_id, |builder| {
|
||||
builder.set_role(accesskit::Role::TextRun);
|
||||
builder.set_text_direction(accesskit::TextDirection::LeftToRight);
|
||||
// TODO(mwcampbell): Set more node fields for the row
|
||||
// once AccessKit adapters expose text formatting info.
|
||||
|
||||
if chunk_idx > 0 {
|
||||
let prev_id = parent_id.with(row_index).with(chunk_idx - 1);
|
||||
builder.set_previous_on_line(prev_id.accesskit_id());
|
||||
}
|
||||
if chunk_idx + 1 < num_chunks {
|
||||
let next_id = parent_id.with(row_index).with(chunk_idx + 1);
|
||||
builder.set_next_on_line(next_id.accesskit_id());
|
||||
}
|
||||
|
||||
let row_rect = row.rect_without_leading_space();
|
||||
let chunk_x0 = row.pos.x + character_positions[char_start];
|
||||
let chunk_x1 = row.pos.x
|
||||
+ character_positions[char_end - 1]
|
||||
+ character_widths[char_end - 1];
|
||||
let chunk_rect = emath::Rect::from_min_max(
|
||||
emath::pos2(chunk_x0, row_rect.min.y),
|
||||
emath::pos2(chunk_x1, row_rect.max.y),
|
||||
);
|
||||
let rect = global_from_galley * chunk_rect;
|
||||
builder.set_bounds(accesskit::Rect {
|
||||
x0: rect.min.x.into(),
|
||||
y0: rect.min.y.into(),
|
||||
x1: rect.max.x.into(),
|
||||
y1: rect.max.y.into(),
|
||||
});
|
||||
builder.set_value(value[byte_start..byte_end].to_owned());
|
||||
builder.set_character_lengths(character_lengths[char_start..char_end].to_vec());
|
||||
|
||||
let pos_offset = character_positions[char_start];
|
||||
let chunk_positions: Vec<f32> = character_positions[char_start..char_end]
|
||||
.iter()
|
||||
.map(|&p| p - pos_offset)
|
||||
.collect();
|
||||
builder.set_character_positions(chunk_positions);
|
||||
builder.set_character_widths(character_widths[char_start..char_end].to_vec());
|
||||
|
||||
let chunk_word_starts: Vec<u8> = word_starts
|
||||
.iter()
|
||||
.filter(|&&ws| ws >= char_start && ws < char_end)
|
||||
.map(|&ws| (ws - char_start) as u8)
|
||||
.collect();
|
||||
builder.set_word_starts(chunk_word_starts);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
prev_row_ended_with_newline = row.ends_with_newline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,10 +192,13 @@ impl CCursorRange {
|
||||
|
||||
Event::AccessKitActionRequest(accesskit::ActionRequest {
|
||||
action: accesskit::Action::SetTextSelection,
|
||||
target,
|
||||
target_node,
|
||||
target_tree,
|
||||
data: Some(accesskit::ActionData::SetTextSelection(selection)),
|
||||
}) => {
|
||||
if _widget_id.accesskit_id() == *target {
|
||||
if _widget_id.accesskit_id() == *target_node
|
||||
&& *target_tree == accesskit::TreeId::ROOT
|
||||
{
|
||||
let primary =
|
||||
ccursor_from_accesskit_text_position(_widget_id, galley, &selection.focus);
|
||||
let secondary =
|
||||
@@ -224,18 +227,31 @@ fn ccursor_from_accesskit_text_position(
|
||||
galley: &Galley,
|
||||
position: &accesskit::TextPosition,
|
||||
) -> Option<CCursor> {
|
||||
use super::accesskit_text::MAX_CHARS_PER_TEXT_RUN;
|
||||
|
||||
let mut total_length = 0usize;
|
||||
for (i, row) in galley.rows.iter().enumerate() {
|
||||
let row_id = id.with(i);
|
||||
if row_id.accesskit_id() == position.node {
|
||||
return Some(CCursor {
|
||||
index: total_length + position.character_index,
|
||||
prefer_next_row: !(position.character_index == row.glyphs.len()
|
||||
&& !row.ends_with_newline
|
||||
&& (i + 1) < galley.rows.len()),
|
||||
});
|
||||
let row_chars = row.glyphs.len() + (row.ends_with_newline as usize);
|
||||
let num_chunks = if row_chars == 0 {
|
||||
1
|
||||
} else {
|
||||
row_chars.div_ceil(MAX_CHARS_PER_TEXT_RUN)
|
||||
};
|
||||
|
||||
for chunk_idx in 0..num_chunks {
|
||||
let run_id = id.with(i).with(chunk_idx);
|
||||
if run_id.accesskit_id() == position.node {
|
||||
let column = chunk_idx * MAX_CHARS_PER_TEXT_RUN + position.character_index;
|
||||
return Some(CCursor {
|
||||
index: total_length + column,
|
||||
prefer_next_row: !(column == row.glyphs.len()
|
||||
&& !row.ends_with_newline
|
||||
&& (i + 1) < galley.rows.len()),
|
||||
});
|
||||
}
|
||||
}
|
||||
total_length += row.glyphs.len() + (row.ends_with_newline as usize);
|
||||
|
||||
total_length += row_chars;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::mem;
|
||||
|
||||
use accesskit::{Action, ActionRequest, NodeId};
|
||||
use accesskit_consumer::{FilterResult, Node, Tree, TreeChangeHandler};
|
||||
use accesskit::{Action, ActionRequest};
|
||||
use accesskit_consumer::{FilterResult, Node, NodeId, Tree, TreeChangeHandler};
|
||||
|
||||
use eframe::epaint::text::TextWrapMode;
|
||||
use egui::{
|
||||
@@ -25,7 +25,7 @@ use egui::{
|
||||
pub struct AccessibilityInspectorPlugin {
|
||||
pub open: bool,
|
||||
tree: Option<accesskit_consumer::Tree>,
|
||||
selected_node: Option<Id>,
|
||||
selected_node: Option<NodeId>,
|
||||
queued_action: Option<ActionRequest>,
|
||||
}
|
||||
|
||||
@@ -113,13 +113,17 @@ impl AccessibilityInspectorPlugin {
|
||||
Id::new("Accessibility Inspector")
|
||||
}
|
||||
|
||||
fn selection_ui(&mut self, ui: &mut Ui, selected_node: Id) {
|
||||
fn selection_ui(&mut self, ui: &mut Ui, selected_node: NodeId) {
|
||||
ui.separator();
|
||||
|
||||
if let Some(tree) = &self.tree
|
||||
&& let Some(node) = tree.state().node_by_id(NodeId::from(selected_node.value()))
|
||||
&& let Some(node) = tree.state().node_by_id(selected_node)
|
||||
{
|
||||
let node_response = ui.ctx().read_response(selected_node);
|
||||
// Safety: This is safe since the `accesskit::NodeId` was created from an `egui::Id`.
|
||||
#[expect(unsafe_code)]
|
||||
let egui_node_id = unsafe { Id::from_high_entropy_bits(node.locate().0.0) };
|
||||
|
||||
let node_response = ui.ctx().read_response(egui_node_id);
|
||||
|
||||
if let Some(widget_response) = node_response {
|
||||
ui.debug_painter().debug_rect(
|
||||
@@ -174,8 +178,10 @@ impl AccessibilityInspectorPlugin {
|
||||
if node.supports_action(action, &|_node| FilterResult::Include)
|
||||
&& ui.button(format!("{action:?}")).clicked()
|
||||
{
|
||||
let (target_node, target_tree) = node.locate();
|
||||
let action_request = ActionRequest {
|
||||
target: node.id(),
|
||||
target_node,
|
||||
target_tree,
|
||||
action,
|
||||
data: None,
|
||||
};
|
||||
@@ -188,8 +194,8 @@ impl AccessibilityInspectorPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
fn node_ui(ui: &mut Ui, node: &Node<'_>, selected_node: &mut Option<Id>) {
|
||||
if node.id() == Self::id().value().into()
|
||||
fn node_ui(ui: &mut Ui, node: &Node<'_>, selected_node: &mut Option<NodeId>) {
|
||||
if node.locate() == (Self::id().value().into(), accesskit::TreeId::ROOT)
|
||||
|| node
|
||||
.value()
|
||||
.as_deref()
|
||||
@@ -200,12 +206,12 @@ impl AccessibilityInspectorPlugin {
|
||||
let label = node
|
||||
.label()
|
||||
.or_else(|| node.value())
|
||||
.unwrap_or_else(|| node.id().0.to_string());
|
||||
.unwrap_or_else(|| node.locate().0.0.to_string());
|
||||
let label = format!("({:?}) {}", node.role(), label);
|
||||
|
||||
// Safety: This is safe since the `accesskit::NodeId` was created from an `egui::Id`.
|
||||
#[expect(unsafe_code)]
|
||||
let egui_node_id = unsafe { Id::from_high_entropy_bits(node.id().0) };
|
||||
let egui_node_id = unsafe { Id::from_high_entropy_bits(node.locate().0.0) };
|
||||
|
||||
ui.push_id(node.id(), |ui| {
|
||||
let child_count = node.children().len();
|
||||
@@ -228,7 +234,7 @@ impl AccessibilityInspectorPlugin {
|
||||
collapsing.set_open(!collapsing.is_open());
|
||||
}
|
||||
let label_response =
|
||||
ui.selectable_value(selected_node, Some(egui_node_id), label.clone());
|
||||
ui.selectable_value(selected_node, Some(node.id()), label.clone());
|
||||
if label_response.hovered() {
|
||||
let widget_response = ui.ctx().read_response(egui_node_id);
|
||||
|
||||
|
||||
@@ -98,9 +98,11 @@ impl Node<'_> {
|
||||
/// This will trigger a [`accesskit::Action::Click`] action.
|
||||
/// In contrast to `click()`, this can also click widgets that are not currently visible.
|
||||
pub fn click_accesskit(&self) {
|
||||
let (target_node, target_tree) = self.accesskit_node.locate();
|
||||
self.event(egui::Event::AccessKitActionRequest(
|
||||
accesskit::ActionRequest {
|
||||
target: self.accesskit_node.id(),
|
||||
target_node,
|
||||
target_tree,
|
||||
action: accesskit::Action::Click,
|
||||
data: None,
|
||||
},
|
||||
@@ -119,9 +121,11 @@ impl Node<'_> {
|
||||
}
|
||||
|
||||
pub fn focus(&self) {
|
||||
let (target_node, target_tree) = self.accesskit_node.locate();
|
||||
self.event(egui::Event::AccessKitActionRequest(ActionRequest {
|
||||
action: accesskit::Action::Focus,
|
||||
target: self.accesskit_node.id(),
|
||||
target_node,
|
||||
target_tree,
|
||||
data: None,
|
||||
}));
|
||||
}
|
||||
@@ -162,45 +166,55 @@ impl Node<'_> {
|
||||
|
||||
/// Scroll the node into view.
|
||||
pub fn scroll_to_me(&self) {
|
||||
let (target_node, target_tree) = self.accesskit_node.locate();
|
||||
self.event(egui::Event::AccessKitActionRequest(ActionRequest {
|
||||
action: accesskit::Action::ScrollIntoView,
|
||||
target: self.accesskit_node.id(),
|
||||
target_node,
|
||||
target_tree,
|
||||
data: None,
|
||||
}));
|
||||
}
|
||||
|
||||
/// Scroll the [`egui::ScrollArea`] containing this node down (100px).
|
||||
pub fn scroll_down(&self) {
|
||||
let (target_node, target_tree) = self.accesskit_node.locate();
|
||||
self.event(egui::Event::AccessKitActionRequest(ActionRequest {
|
||||
action: accesskit::Action::ScrollDown,
|
||||
target: self.accesskit_node.id(),
|
||||
target_node,
|
||||
target_tree,
|
||||
data: None,
|
||||
}));
|
||||
}
|
||||
|
||||
/// Scroll the [`egui::ScrollArea`] containing this node up (100px).
|
||||
pub fn scroll_up(&self) {
|
||||
let (target_node, target_tree) = self.accesskit_node.locate();
|
||||
self.event(egui::Event::AccessKitActionRequest(ActionRequest {
|
||||
action: accesskit::Action::ScrollUp,
|
||||
target: self.accesskit_node.id(),
|
||||
target_node,
|
||||
target_tree,
|
||||
data: None,
|
||||
}));
|
||||
}
|
||||
|
||||
/// Scroll the [`egui::ScrollArea`] containing this node left (100px).
|
||||
pub fn scroll_left(&self) {
|
||||
let (target_node, target_tree) = self.accesskit_node.locate();
|
||||
self.event(egui::Event::AccessKitActionRequest(ActionRequest {
|
||||
action: accesskit::Action::ScrollLeft,
|
||||
target: self.accesskit_node.id(),
|
||||
target_node,
|
||||
target_tree,
|
||||
data: None,
|
||||
}));
|
||||
}
|
||||
|
||||
/// Scroll the [`egui::ScrollArea`] containing this node right (100px).
|
||||
pub fn scroll_right(&self) {
|
||||
let (target_node, target_tree) = self.accesskit_node.locate();
|
||||
self.event(egui::Event::AccessKitActionRequest(ActionRequest {
|
||||
action: accesskit::Action::ScrollRight,
|
||||
target: self.accesskit_node.id(),
|
||||
target_node,
|
||||
target_tree,
|
||||
data: None,
|
||||
}));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user