1use std::sync::Arc;
30
31use cosmic_text::Buffer;
32use unicode_segmentation::UnicodeSegmentation;
33
34use crate::display::{Clip, NO_CLIP};
35use crate::edit::EditStore;
36use crate::geom::{Rect, Size, Vec2};
37use crate::key::Key;
38use crate::scroll::ScrollStore;
39use crate::spec::NodeSpec;
40use crate::text::TextSystem;
41use crate::tree::{NIL, NodeContent, OriginId, Tree};
42use crate::value::{Handles, Value};
43use crate::window::{WindowButton, WindowRole};
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
53pub enum Role {
54 None,
56 Button,
57 Checkbox,
58 Radio,
59 Switch,
60 Slider,
61 Tab,
62 TabList,
63 Link,
64 Heading,
65 List,
66 ListItem,
67 Image,
68 Dialog,
69 Group,
70 Window,
73 TitleBar,
75 StaticText,
77 TextInput,
80 MultilineTextInput,
82 ScrollView,
84 Line,
89 RadioGroup,
97 Menu,
99 MenuItem,
102 Terminal,
106}
107
108impl Role {
109 pub fn name(self) -> &'static str {
111 match self {
112 Role::None => "none",
113 Role::Button => "button",
114 Role::Checkbox => "checkbox",
115 Role::Radio => "radio",
116 Role::Switch => "switch",
117 Role::Slider => "slider",
118 Role::Tab => "tab",
119 Role::TabList => "tabList",
120 Role::Link => "link",
121 Role::Heading => "heading",
122 Role::List => "list",
123 Role::ListItem => "listItem",
124 Role::Image => "image",
125 Role::Dialog => "dialog",
126 Role::Group => "group",
127 Role::RadioGroup => "radioGroup",
128 Role::Menu => "menu",
129 Role::MenuItem => "menuItem",
130 Role::Terminal => "terminal",
131 Role::Window => "window",
132 Role::TitleBar => "titleBar",
133 Role::StaticText => "staticText",
134 Role::TextInput => "textInput",
135 Role::MultilineTextInput => "multilineTextInput",
136 Role::ScrollView => "scrollView",
137 Role::Line => "line",
138 }
139 }
140
141 pub fn parse(name: &str) -> Option<Role> {
142 Role::ALL.iter().copied().find(|r| r.name() == name)
143 }
144
145 pub const ALL: [Role; 26] = [
146 Role::None,
147 Role::Button,
148 Role::Checkbox,
149 Role::Radio,
150 Role::Switch,
151 Role::Slider,
152 Role::Tab,
153 Role::TabList,
154 Role::Link,
155 Role::Heading,
156 Role::List,
157 Role::ListItem,
158 Role::Image,
159 Role::Dialog,
160 Role::Group,
161 Role::Window,
162 Role::TitleBar,
163 Role::StaticText,
164 Role::TextInput,
165 Role::MultilineTextInput,
166 Role::ScrollView,
167 Role::Line,
168 Role::RadioGroup,
169 Role::Menu,
170 Role::MenuItem,
171 Role::Terminal,
172 ];
173
174 pub fn is_control(self) -> bool {
176 matches!(
177 self,
178 Role::Button
179 | Role::Checkbox
180 | Role::Radio
181 | Role::Switch
182 | Role::Slider
183 | Role::Tab
184 | Role::MenuItem
185 | Role::Link
186 | Role::TextInput
187 | Role::MultilineTextInput
188 )
189 }
190
191 pub fn is_editor(self) -> bool {
193 matches!(self, Role::TextInput | Role::MultilineTextInput)
194 }
195
196 pub(crate) fn presentational(self) -> bool {
200 matches!(
201 self,
202 Role::Button
203 | Role::Checkbox
204 | Role::Radio
205 | Role::Switch
206 | Role::Slider
207 | Role::Tab
208 | Role::MenuItem
209 | Role::Link
210 | Role::Heading
211 | Role::Image
212 )
213 }
214}
215
216#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
222pub enum Live {
223 #[default]
225 Off,
226 Polite,
228 Assertive,
230}
231
232impl Live {
233 pub const ALL: &'static [Live] = &[Live::Off, Live::Polite, Live::Assertive];
236
237 pub fn name(self) -> &'static str {
239 match self {
240 Live::Off => "off",
241 Live::Polite => "polite",
242 Live::Assertive => "assertive",
243 }
244 }
245
246 pub fn from_index(i: usize) -> Live {
249 Self::ALL.get(i).copied().unwrap_or_default()
250 }
251}
252
253#[derive(Clone, Debug, PartialEq, Eq)]
260pub struct Announcement {
261 pub text: String,
262 pub live: Live,
265}
266
267#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
276pub enum Orientation {
277 Horizontal,
278 Vertical,
279}
280
281impl Orientation {
282 pub fn name(self) -> &'static str {
283 match self {
284 Orientation::Horizontal => "horizontal",
285 Orientation::Vertical => "vertical",
286 }
287 }
288
289 pub fn parse(name: &str) -> Option<Orientation> {
290 Orientation::ALL.iter().copied().find(|o| o.name() == name)
291 }
292
293 pub const ALL: [Orientation; 2] = [Orientation::Horizontal, Orientation::Vertical];
294}
295
296#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
300pub enum AccessAction {
301 Click,
306 Focus,
309 Blur,
310 SetValue,
314 Increment,
317 Decrement,
318 ScrollIntoView,
320 ScrollUp,
322 ScrollDown,
323 ScrollLeft,
324 ScrollRight,
325 SetTextSelection,
330 ReplaceSelectedText,
334}
335
336impl AccessAction {
337 pub const ALL: [AccessAction; 13] = [
338 AccessAction::Click,
339 AccessAction::Focus,
340 AccessAction::Blur,
341 AccessAction::SetValue,
342 AccessAction::Increment,
343 AccessAction::Decrement,
344 AccessAction::ScrollIntoView,
345 AccessAction::ScrollUp,
346 AccessAction::ScrollDown,
347 AccessAction::ScrollLeft,
348 AccessAction::ScrollRight,
349 AccessAction::SetTextSelection,
350 AccessAction::ReplaceSelectedText,
351 ];
352
353 pub fn bit(self) -> u32 {
355 1 << (self as u32)
356 }
357
358 pub fn name(self) -> &'static str {
359 match self {
360 AccessAction::Click => "click",
361 AccessAction::Focus => "focus",
362 AccessAction::Blur => "blur",
363 AccessAction::SetValue => "setValue",
364 AccessAction::Increment => "increment",
365 AccessAction::Decrement => "decrement",
366 AccessAction::ScrollIntoView => "scrollIntoView",
367 AccessAction::ScrollUp => "scrollUp",
368 AccessAction::ScrollDown => "scrollDown",
369 AccessAction::ScrollLeft => "scrollLeft",
370 AccessAction::ScrollRight => "scrollRight",
371 AccessAction::SetTextSelection => "setTextSelection",
372 AccessAction::ReplaceSelectedText => "replaceSelectedText",
373 }
374 }
375
376 pub fn parse(name: &str) -> Option<AccessAction> {
377 AccessAction::ALL.iter().copied().find(|a| a.name() == name)
378 }
379}
380
381#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
384pub struct TextPos {
385 pub run: Key,
386 pub character: usize,
387}
388
389impl TextPos {
390 pub fn to_value(self, h: Handles) -> Value {
392 Value::map([
393 ("run", (h.key)(self.run)),
394 ("character", Value::Int(self.character as i64)),
395 ])
396 }
397}
398
399#[derive(Clone, Debug, PartialEq)]
402pub struct AccessRequest {
403 pub key: Key,
404 pub action: AccessAction,
405 pub value: Option<String>,
408 pub anchor: Option<TextPos>,
411 pub focus: Option<TextPos>,
412}
413
414impl AccessRequest {
415 pub fn new(key: Key, action: AccessAction) -> Self {
416 AccessRequest {
417 key,
418 action,
419 value: None,
420 anchor: None,
421 focus: None,
422 }
423 }
424
425 pub fn with_value(mut self, value: impl Into<String>) -> Self {
426 self.value = Some(value.into());
427 self
428 }
429
430 pub fn with_selection(mut self, anchor: TextPos, focus: TextPos) -> Self {
431 self.anchor = Some(anchor);
432 self.focus = Some(focus);
433 self
434 }
435}
436
437#[derive(Clone, Copy, Debug, Default, PartialEq)]
439pub struct ScrollState {
440 pub x: f32,
441 pub y: f32,
442 pub max_x: f32,
443 pub max_y: f32,
444}
445
446impl ScrollState {
447 pub fn to_value(self) -> Value {
449 Value::map([
450 ("x", Value::float(self.x)),
451 ("y", Value::float(self.y)),
452 ("max_x", Value::float(self.max_x)),
453 ("max_y", Value::float(self.max_y)),
454 ])
455 }
456}
457
458#[derive(Clone, Debug, PartialEq)]
465pub struct AccessRun {
466 pub key: Key,
468 pub line: usize,
471 pub start: usize,
473 pub end: usize,
474 pub text: String,
475 pub rect: Rect,
479 pub char_lengths: Vec<u8>,
480 pub char_positions: Vec<f32>,
482 pub char_widths: Vec<f32>,
483 pub word_starts: Vec<u8>,
485 pub rtl: bool,
486}
487
488impl AccessRun {
489 pub fn to_value(&self, h: Handles) -> Value {
491 let bytes = |v: &[u8]| Value::list(v.iter().map(|b| Value::Int(*b as i64)));
492 Value::map([
493 ("key", (h.key)(self.key)),
494 ("line", Value::Int(self.line as i64)),
495 ("start", Value::Int(self.start as i64)),
496 ("end", Value::Int(self.end as i64)),
497 ("text", Value::Str(self.text.clone())),
498 ("rect", self.rect.to_value()),
499 ("char_lengths", bytes(&self.char_lengths)),
500 ("char_positions", Value::floats(&self.char_positions)),
501 ("char_widths", Value::floats(&self.char_widths)),
502 ("word_starts", bytes(&self.word_starts)),
503 ("rtl", Value::Bool(self.rtl)),
504 ])
505 }
506}
507
508pub const RUN_CHARS: usize = 200;
510
511#[derive(Clone, Debug, PartialEq)]
513pub struct AccessNode {
514 pub key: Key,
515 pub parent: Option<Key>,
517 pub origin: OriginId,
518 pub role: Role,
519 pub name: Option<String>,
523 pub description: Option<String>,
525 pub rect: Rect,
531 pub value: Option<String>,
540 pub caret: Option<usize>,
542 pub selection: Option<(usize, usize)>,
544 pub runs: Vec<AccessRun>,
546 pub anchor: Option<TextPos>,
549 pub focus: Option<TextPos>,
550 pub checked: Option<bool>,
552 pub mixed: bool,
555 pub selected: Option<bool>,
560 pub expanded: Option<bool>,
563 pub pos_in_set: Option<usize>,
568 pub orientation: Option<Orientation>,
572 pub set_size: Option<usize>,
576 pub number: Option<f32>,
580 pub min: Option<f32>,
581 pub max: Option<f32>,
582 pub step: Option<f32>,
584 pub focused: bool,
587 pub disabled: bool,
589 pub modal: bool,
595 pub scroll: Option<ScrollState>,
596 pub actions: u32,
598 pub live: Live,
605}
606
607impl AccessNode {
608 pub fn to_value(&self, h: Handles) -> Value {
615 Value::map([
616 ("key", (h.key)(self.key)),
617 ("parent", h.opt_key(self.parent)),
618 ("origin", Value::Int(self.origin.0 as i64)),
619 ("role", Value::str(self.role.name())),
620 ("name", Value::opt_str(&self.name)),
621 ("description", Value::opt_str(&self.description)),
622 ("rect", self.rect.to_value()),
623 ("value", Value::opt_str(&self.value)),
624 ("caret", Value::opt_usize(self.caret)),
625 (
626 "selection",
627 Value::opt(self.selection, |(a, b)| {
628 Value::list([Value::Int(a as i64), Value::Int(b as i64)])
629 }),
630 ),
631 ("anchor", Value::opt(self.anchor, |p| p.to_value(h))),
632 ("focus", Value::opt(self.focus, |p| p.to_value(h))),
633 ("runs", Value::list(self.runs.iter().map(|r| r.to_value(h)))),
634 ("checked", Value::opt_bool(self.checked)),
635 ("mixed", Value::Bool(self.mixed)),
636 ("selected", Value::opt_bool(self.selected)),
637 ("expanded", Value::opt_bool(self.expanded)),
638 ("pos_in_set", Value::opt_usize(self.pos_in_set)),
639 ("set_size", Value::opt_usize(self.set_size)),
640 (
641 "orientation",
642 Value::opt(self.orientation, |o| Value::str(o.name())),
643 ),
644 ("live", Value::str(self.live.name())),
645 ("value_now", Value::opt_float(self.number)),
646 ("value_min", Value::opt_float(self.min)),
647 ("value_max", Value::opt_float(self.max)),
648 ("value_step", Value::opt_float(self.step)),
649 ("focused", Value::Bool(self.focused)),
650 ("disabled", Value::Bool(self.disabled)),
651 ("modal", Value::Bool(self.modal)),
652 ("scroll", Value::opt(self.scroll, ScrollState::to_value)),
653 (
654 "actions",
655 Value::list(self.action_list().into_iter().map(|a| Value::str(a.name()))),
656 ),
657 ])
658 }
659
660 pub fn supports(&self, action: AccessAction) -> bool {
661 self.actions & action.bit() != 0
662 }
663
664 pub fn action_list(&self) -> Vec<AccessAction> {
666 AccessAction::ALL
667 .iter()
668 .copied()
669 .filter(|a| self.supports(*a))
670 .collect()
671 }
672
673 pub fn line_offset(&self, pos: TextPos) -> Option<(usize, usize)> {
676 let run = self.runs.iter().find(|r| r.key == pos.run)?;
677 let within = run
678 .text
679 .char_indices()
680 .nth(pos.character)
681 .map_or(run.text.len(), |(b, _)| b);
682 Some((run.line, run.start + within.min(run.end - run.start)))
683 }
684
685 pub fn text_pos(&self, line: usize, offset: usize) -> Option<TextPos> {
688 let mut last = None;
689 for r in self.runs.iter().filter(|r| r.line == line) {
690 if offset >= r.start && offset < r.end {
691 return Some(TextPos {
692 run: r.key,
693 character: r.text[..offset - r.start].chars().count(),
694 });
695 }
696 last = Some(r);
697 }
698 let r = last?;
699 Some(TextPos {
700 run: r.key,
701 character: r.text[..(offset.max(r.start) - r.start).min(r.end - r.start)]
702 .chars()
703 .count(),
704 })
705 }
706}
707
708#[derive(Clone, Debug, Default, PartialEq)]
711pub struct AccessTree {
712 pub nodes: Vec<AccessNode>,
713 pub focus: Option<Key>,
715 pub hash: u64,
718}
719
720impl AccessTree {
721 pub fn to_value(&self, h: Handles) -> Value {
724 Value::map([
725 (
726 "nodes",
727 Value::list(self.nodes.iter().map(|n| n.to_value(h))),
728 ),
729 ("focus", h.opt_key(self.focus)),
730 ("hash", Value::Str(format!("{:016x}", self.hash))),
731 ])
732 }
733
734 pub fn get(&self, key: Key) -> Option<&AccessNode> {
735 self.nodes.iter().find(|n| n.key == key)
736 }
737
738 pub fn children(&self, key: Key) -> impl Iterator<Item = &AccessNode> {
740 self.nodes.iter().filter(move |n| n.parent == Some(key))
741 }
742
743 pub fn root(&self) -> Option<&AccessNode> {
745 self.nodes.first()
746 }
747}
748
749pub(crate) struct Semantic {
751 pub role: Role,
752 pub name: Option<String>,
753 pub presentational: bool,
755}
756
757pub(crate) fn derived_role(tree: &Tree, i: usize) -> Option<Role> {
760 let spec = &tree.specs[i];
761 if let Some(r) = spec.access().role {
762 return (r != Role::Line).then_some(r);
765 }
766 if i == 0 {
767 return Some(Role::Window);
768 }
769 match tree.content[i] {
770 NodeContent::Text(_) => return Some(Role::StaticText),
771 NodeContent::Edit(_) => return Some(Role::TextInput),
772 NodeContent::Image(..) => return Some(Role::Image),
773 NodeContent::Line(_) | NodeContent::Polygon(_) => {}
778 NodeContent::Cells(_) => return Some(Role::Terminal),
779 NodeContent::Fragment(_) => {}
785 NodeContent::Container => {}
786 }
787 match spec.window {
788 Some(WindowRole::Drag) => return Some(Role::TitleBar),
789 Some(WindowRole::Button(_)) => return Some(Role::Button),
790 None => {}
791 }
792 if spec.events().modal.is_some() {
793 return Some(Role::Dialog);
796 }
797 if spec.events().on_click.is_some() {
798 return Some(Role::Button);
799 }
800 if spec.layout.scroll_x || spec.layout.scroll_y {
801 return Some(Role::ScrollView);
802 }
803 if spec.events().on_key.is_some() || spec.focusable {
804 return Some(Role::Group);
807 }
808 if spec.access().live != Live::Off {
809 return Some(Role::Group);
813 }
814 None
815}
816
817pub(crate) fn is_custom_editor(tree: &Tree, i: usize) -> bool {
820 tree.specs[i].access().role.is_some_and(Role::is_editor)
821 && !matches!(tree.content[i], NodeContent::Edit(_))
822}
823
824pub(crate) fn focusable(tree: &Tree, i: usize) -> bool {
831 let spec = &tree.specs[i];
832 if spec.disabled || spec.access().role == Some(Role::None) || spec.window.is_some() {
833 return false;
834 }
835 if spec.focusable
836 || spec.events().on_key.is_some()
837 || matches!(tree.content[i], NodeContent::Edit(_))
838 {
839 return true;
840 }
841 match spec.access().role {
842 Some(r) => r.is_control(),
843 None => spec.events().on_click.is_some(),
844 }
845}
846
847pub(crate) fn semantic(
851 tree: &Tree,
852 text: &TextSystem,
853 edit: &EditStore,
854 title: Option<&str>,
855 i: usize,
856) -> Option<Semantic> {
857 let mut role = derived_role(tree, i)?;
858 let spec = &tree.specs[i];
859 if role == Role::TextInput
860 && let NodeContent::Edit(key) = tree.content[i]
861 && edit.is_multiline(key)
862 {
863 role = Role::MultilineTextInput;
864 }
865 let presentational = role.presentational()
879 || spec.access().live != Live::Off
880 || matches!(spec.window, Some(WindowRole::Button(_)))
881 || is_custom_editor(tree, i);
882 let name = match (&spec.access().label, spec.window) {
883 (Some(label), _) => Some(label.to_string()),
884 (None, Some(WindowRole::Button(b))) => Some(
885 match b {
886 WindowButton::Close => "Close",
887 WindowButton::Minimize => "Minimize",
888 WindowButton::Maximize => "Maximize",
889 }
890 .to_string(),
891 ),
892 (None, _) => match role {
893 Role::StaticText => match tree.content[i] {
894 NodeContent::Text(id) => Some(text.content(id).to_string()),
895 _ => None,
896 },
897 Role::Window => title.map(str::to_string),
901 _ if role.presentational() || spec.access().live != Live::Off => {
902 content_name(tree, text, i)
903 }
904 _ => None,
905 },
906 };
907 Some(Semantic {
908 role,
909 name,
910 presentational,
911 })
912}
913
914pub(crate) fn live_region_speaks(tree: &Tree, text: &TextSystem, i: usize) -> bool {
920 tree.specs[i].access().label.is_some() || content_name(tree, text, i).is_some()
921}
922
923fn content_name(tree: &Tree, text: &TextSystem, i: usize) -> Option<String> {
933 let end = tree.subtree_end(i);
934 let mut out = String::new();
935 let mut j = i;
936 while j < end {
937 if j > i && tree.specs[j].access().role == Some(Role::None) {
938 j = tree.subtree_end(j);
939 continue;
940 }
941 let at = j;
942 j += 1;
943 if let NodeContent::Text(id) = tree.content[at] {
944 let s = text.content(id).trim();
945 if s.is_empty() {
946 continue;
947 }
948 if !out.is_empty() {
949 out.push(' ');
950 }
951 out.push_str(s);
952 }
953 }
954 (!out.is_empty()).then_some(out)
955}
956
957pub(crate) struct Sources<'a> {
959 pub text: &'a TextSystem,
960 pub cells: &'a crate::cells::CellStore,
961 pub edit: &'a EditStore,
962 pub scroll: &'a ScrollStore,
963 pub title: Option<&'a str>,
964 pub focus: Option<Key>,
966 pub modal: Option<Key>,
968 pub viewport: Size,
969 pub scale: f32,
970 pub clips: &'a [Clip],
975}
976
977fn clip_of(src: &Sources<'_>, i: usize) -> Rect {
979 src.clips.get(i).map_or(NO_CLIP, |c| c.rect)
980}
981
982fn clipped(rect: Rect, clip: Rect) -> Rect {
989 let x0 = rect.x.max(clip.x);
990 let y0 = rect.y.max(clip.y);
991 let x1 = (rect.x + rect.w).min(clip.x + clip.w);
992 let y1 = (rect.y + rect.h).min(clip.y + clip.h);
993 let gone = |lo: f32, hi: f32, extent: f32| hi < lo || (hi == lo && extent > 0.0);
997 if gone(x0, x1, rect.w) || gone(y0, y1, rect.h) {
998 let x = rect.x.max(clip.x).min(clip.x + clip.w);
1001 let y = rect.y.max(clip.y).min(clip.y + clip.h);
1002 return Rect::new(x, y, 0.0, 0.0);
1003 }
1004 Rect::new(x0, y0, x1 - x0, y1 - y0)
1005}
1006
1007fn node_rect(tree: &Tree, src: &Sources<'_>, i: usize) -> Rect {
1010 if i == 0 {
1011 Rect::new(0.0, 0.0, src.viewport.w, src.viewport.h)
1012 } else {
1013 clipped(
1014 Rect::from_pos_size(tree.pos[i], tree.size[i]),
1015 clip_of(src, i),
1016 )
1017 }
1018}
1019
1020fn edit_run_clip(tree: &Tree, src: &Sources<'_>, i: usize, edit_key: Key) -> Rect {
1025 let clip = clip_of(src, i);
1026 if src.edit.folds(edit_key) {
1027 return clip;
1028 }
1029 let pad = tree.specs[i].layout.padding;
1030 let across = Rect::new(
1031 tree.pos[i].x + pad.l,
1032 clip.y,
1033 (tree.size[i].w - pad.x()).max(0.0),
1034 clip.h,
1035 );
1036 clip.intersect(&across)
1037}
1038
1039fn clip_runs(runs: &mut [AccessRun], clip: Rect) {
1043 if clip == NO_CLIP {
1044 return;
1045 }
1046 for run in runs {
1047 let cut = clipped(run.rect, clip);
1048 let dx = run.rect.x - cut.x;
1049 if dx != 0.0 {
1050 for p in &mut run.char_positions {
1051 *p += dx;
1052 }
1053 }
1054 run.rect = cut;
1055 }
1056}
1057
1058pub(crate) fn inputs_hash(tree: &Tree, src: &Sources<'_>) -> Option<u64> {
1085 use std::hash::{Hash, Hasher};
1086 let mut h = rustc_hash::FxHasher::default();
1087 let f = |h: &mut rustc_hash::FxHasher, v: f32| v.to_bits().hash(h);
1088
1089 tree.len().hash(&mut h);
1090 src.focus.map(|k| k.0).hash(&mut h);
1091 src.modal.map(|k| k.0).hash(&mut h);
1092 src.title.hash(&mut h);
1093 f(&mut h, src.viewport.w);
1094 f(&mut h, src.viewport.h);
1095 f(&mut h, src.scale);
1096
1097 let mut skip_until = 0usize;
1098 let mut i = 0usize;
1099 while i < tree.len() {
1100 if i < skip_until {
1101 i += 1;
1102 continue;
1103 }
1104 let Some(sem) = semantic(tree, src.text, src.edit, src.title, i) else {
1105 i += 1;
1106 continue;
1107 };
1108 if sem.role == Role::None {
1109 skip_until = tree.subtree_end(i);
1110 i += 1;
1111 continue;
1112 }
1113 if is_custom_editor(tree, i) {
1114 return None;
1115 }
1116 let key = tree.keys[i];
1117 let spec = &tree.specs[i];
1118 let ax = spec.access();
1119
1120 i.hash(&mut h);
1122 key.0.hash(&mut h);
1123 tree.parent[i].hash(&mut h);
1124 tree.origins[i].0.hash(&mut h);
1125
1126 sem.role.hash(&mut h);
1128 sem.name.hash(&mut h);
1129 sem.presentational.hash(&mut h);
1130 ax.description.as_deref().hash(&mut h);
1131
1132 let rect = node_rect(tree, src, i);
1135 for v in [rect.x, rect.y, rect.w, rect.h] {
1136 f(&mut h, v);
1137 }
1138
1139 (src.focus == Some(key)).hash(&mut h);
1141 (src.modal == Some(key)).hash(&mut h);
1142 spec.disabled.hash(&mut h);
1143 ax.live.hash(&mut h);
1144 spec.window.hash(&mut h);
1145 spec.events().on_click.is_some().hash(&mut h);
1146 focusable(tree, i).hash(&mut h);
1147 crate::composite::orientation(tree, i).hash(&mut h);
1148 ax.expanded.hash(&mut h);
1149 ax.checked.hash(&mut h);
1150 ax.mixed.hash(&mut h);
1151 ax.selected.hash(&mut h);
1152 ax.value_text.as_deref().hash(&mut h);
1153 for v in [ax.value_now, ax.value_min, ax.value_max, ax.value_step] {
1154 v.is_some().hash(&mut h);
1155 f(&mut h, v.unwrap_or(0.0));
1156 }
1157
1158 match tree.content[i] {
1160 NodeContent::Edit(edit_key) => {
1161 let pad = spec.layout.padding;
1162 f(&mut h, tree.pos[i].x + pad.l);
1163 f(&mut h, tree.pos[i].y + pad.t);
1164 src.edit.text(edit_key).hash(&mut h);
1165 src.edit.caret_and_selection(edit_key).hash(&mut h);
1166 src.edit.selection_cursors(edit_key).hash(&mut h);
1167 src.edit.version(edit_key).hash(&mut h);
1173 let clip = edit_run_clip(tree, src, i, edit_key);
1174 for v in [clip.x, clip.y, clip.w, clip.h] {
1175 f(&mut h, v);
1176 }
1177 }
1178 NodeContent::Cells(id) => src.cells.value(id).hash(&mut h),
1179 _ => {}
1180 }
1181
1182 if spec.layout.scroll_x || spec.layout.scroll_y {
1184 spec.layout.scroll_x.hash(&mut h);
1185 spec.layout.scroll_y.hash(&mut h);
1186 let off = src.scroll.drawn(key);
1187 let max = tree.scroll_max[i];
1188 for v in [off.x, off.y, max.x, max.y] {
1189 f(&mut h, v);
1190 }
1191 }
1192
1193 if sem.presentational {
1194 skip_until = tree.subtree_end(i);
1195 }
1196 i += 1;
1197 }
1198 Some(h.finish())
1199}
1200
1201pub(crate) fn build(tree: &Tree, src: &Sources<'_>) -> AccessTree {
1203 let mut out = AccessTree::default();
1204 if tree.is_empty() {
1205 return out;
1206 }
1207 let mut parents: Vec<Option<Key>> = vec![None; tree.len()];
1210 let mut skip_until = 0usize;
1211 let mut i = 0usize;
1212 while i < tree.len() {
1213 if i < skip_until {
1214 i += 1;
1215 continue;
1216 }
1217 let parent = tree.parent[i];
1218 let inherited = if parent == NIL {
1219 None
1220 } else {
1221 parents[parent as usize]
1222 };
1223 let Some(sem) = semantic(tree, src.text, src.edit, src.title, i) else {
1224 parents[i] = inherited;
1225 i += 1;
1226 continue;
1227 };
1228 if sem.role == Role::None {
1229 skip_until = tree.subtree_end(i);
1230 i += 1;
1231 continue;
1232 }
1233 let key = tree.keys[i];
1234 parents[i] = Some(key);
1235 let spec: &NodeSpec = &tree.specs[i];
1236 let rect = node_rect(tree, src, i);
1237 let mut node = AccessNode {
1238 key,
1239 parent: inherited,
1240 origin: tree.origins[i],
1241 role: sem.role,
1242 name: sem.name,
1243 description: spec.access().description.as_ref().map(|d| d.to_string()),
1244 rect,
1245 value: None,
1246 caret: None,
1247 selection: None,
1248 runs: Vec::new(),
1249 anchor: None,
1250 focus: None,
1251 checked: None,
1252 mixed: false,
1253 selected: None,
1254 expanded: None,
1255 orientation: crate::composite::orientation(tree, i),
1256 pos_in_set: None,
1257 set_size: None,
1258 number: None,
1259 min: None,
1260 max: None,
1261 step: None,
1262 focused: src.focus == Some(key),
1263 disabled: spec.disabled,
1264 modal: src.modal == Some(key),
1265 scroll: None,
1266 actions: 0,
1267 live: spec.access().live,
1268 };
1269 let mut actions = 0u32;
1270 match spec.window {
1271 Some(WindowRole::Button(_)) => actions |= AccessAction::Click.bit(),
1272 Some(WindowRole::Drag) => {}
1273 None => {
1274 if spec.events().on_click.is_some() && !spec.disabled {
1275 actions |= AccessAction::Click.bit();
1276 }
1277 }
1278 }
1279 if focusable(tree, i) {
1282 actions |= AccessAction::Focus.bit() | AccessAction::Blur.bit();
1283 }
1284 if let NodeContent::Edit(edit_key) = tree.content[i] {
1285 let pad = spec.layout.padding;
1286 let origin = Vec2::new(tree.pos[i].x + pad.l, tree.pos[i].y + pad.t);
1287 node.value = src.edit.text(edit_key);
1288 node.runs = src.edit.runs(edit_key, key, origin, src.scale);
1289 clip_runs(&mut node.runs, edit_run_clip(tree, src, i, edit_key));
1290 if let Some((caret, selection)) = src.edit.caret_and_selection(edit_key) {
1291 node.caret = Some(caret);
1292 node.selection = selection;
1293 }
1294 if let Some((anchor, focus)) = src.edit.selection_cursors(edit_key) {
1295 node.anchor = node.text_pos(anchor.0, anchor.1);
1296 node.focus = node.text_pos(focus.0, focus.1);
1297 }
1298 if !spec.disabled {
1299 actions |= AccessAction::Click.bit()
1300 | AccessAction::SetValue.bit()
1301 | AccessAction::SetTextSelection.bit()
1302 | AccessAction::ReplaceSelectedText.bit();
1303 }
1304 } else if is_custom_editor(tree, i) {
1305 custom_editor(tree, src, i, &mut node);
1306 if !spec.disabled {
1307 actions |= AccessAction::SetValue.bit()
1308 | AccessAction::SetTextSelection.bit()
1309 | AccessAction::ReplaceSelectedText.bit();
1310 }
1311 } else if let NodeContent::Cells(id) = tree.content[i] {
1312 node.value = Some(src.cells.value(id));
1314 }
1315 let ax = spec.access();
1318 node.expanded = ax.expanded;
1319 match sem.role {
1320 Role::Checkbox | Role::Radio | Role::Switch => {
1321 node.checked = Some(ax.checked);
1322 node.mixed = ax.mixed && sem.role == Role::Checkbox;
1323 }
1324 Role::Tab => node.selected = Some(ax.selected),
1330 Role::ListItem | Role::Link if ax.selected => node.selected = Some(true),
1331 Role::MenuItem if ax.checked => node.checked = Some(true),
1338 Role::Slider => {
1339 node.number = ax.value_now;
1340 node.min = ax.value_min;
1341 node.max = ax.value_max;
1342 node.step = ax.value_step;
1343 node.value = ax.value_text.as_deref().map(str::to_owned);
1349 if !spec.disabled {
1352 actions |= AccessAction::Increment.bit()
1353 | AccessAction::Decrement.bit()
1354 | AccessAction::SetValue.bit();
1355 }
1356 }
1357 _ => {}
1358 }
1359 if spec.layout.scroll_x || spec.layout.scroll_y {
1360 let off = src.scroll.drawn(key);
1361 let max = tree.scroll_max[i];
1362 node.scroll = Some(ScrollState {
1363 x: off.x,
1364 y: off.y,
1365 max_x: max.x,
1366 max_y: max.y,
1367 });
1368 if spec.layout.scroll_y {
1369 actions |= AccessAction::ScrollUp.bit() | AccessAction::ScrollDown.bit();
1370 }
1371 if spec.layout.scroll_x {
1372 actions |= AccessAction::ScrollLeft.bit() | AccessAction::ScrollRight.bit();
1373 }
1374 }
1375 if i != 0 {
1376 actions |= AccessAction::ScrollIntoView.bit();
1377 }
1378 node.actions = actions;
1379 if node.focused {
1380 out.focus = Some(key);
1381 }
1382 out.nodes.push(node);
1383 if sem.presentational {
1384 skip_until = tree.subtree_end(i);
1385 }
1386 i += 1;
1387 }
1388 set_positions(tree, &mut out);
1389 out.hash = hash_of(&out);
1390 out
1391}
1392
1393fn set_positions(tree: &Tree, out: &mut AccessTree) {
1407 let containers: Vec<(usize, Role)> = (0..tree.len())
1408 .filter_map(|i| {
1409 let item = crate::composite::item_role(tree.specs[i].access().role?)?;
1410 Some((i, item))
1411 })
1412 .collect();
1413 let mut items: Vec<usize> = Vec::new();
1414 for (container, item) in containers {
1415 crate::composite::items(tree, container, item, &mut items);
1416 if items.is_empty() {
1417 continue;
1418 }
1419 let size = items.len();
1420 let container = tree.keys[container];
1421 let keys: Vec<Key> = items.iter().map(|&i| tree.keys[i]).collect();
1422 for n in &mut out.nodes {
1423 if n.key == container {
1424 n.set_size = Some(size);
1425 } else if let Some(pos) = keys.iter().position(|k| *k == n.key) {
1426 n.pos_in_set = Some(pos);
1427 }
1428 }
1429 }
1430}
1431
1432pub(crate) fn lines_under(tree: &Tree, i: usize) -> Vec<usize> {
1438 let end = tree.subtree_end(i);
1439 let mut lines: Vec<usize> = Vec::new();
1440 let mut j = i + 1;
1441 while j < end {
1442 let spec = &tree.specs[j];
1443 if spec.access().role == Some(Role::None) {
1444 j = tree.subtree_end(j);
1445 continue;
1446 }
1447 if spec.access().role == Some(Role::Line) {
1448 lines.push(j);
1449 j = tree.subtree_end(j);
1450 continue;
1451 }
1452 j += 1;
1453 }
1454 lines
1455}
1456
1457fn custom_editor(tree: &Tree, src: &Sources<'_>, i: usize, node: &mut AccessNode) {
1462 let lines = lines_under(tree, i);
1463 let mut value = String::new();
1464 let mut run_no = 0usize;
1465 let mut caret: Option<(usize, usize)> = None;
1466 let mut anchor: Option<(usize, usize)> = None;
1467 for (ln, &l) in lines.iter().enumerate() {
1468 let last = ln + 1 == lines.len();
1469 let mut line_text = String::new();
1470 let mut texts: Vec<usize> = Vec::new();
1471 let lend = tree.subtree_end(l);
1472 let mut t = l;
1473 while t < lend {
1474 if tree.specs[t].access().role == Some(Role::None) {
1475 t = tree.subtree_end(t);
1476 continue;
1477 }
1478 if let NodeContent::Text(_) = tree.content[t] {
1479 texts.push(t);
1480 }
1481 t += 1;
1482 }
1483 for (k, &t) in texts.iter().enumerate() {
1484 let NodeContent::Text(id) = tree.content[t] else {
1485 continue;
1486 };
1487 let base = line_text.len();
1488 line_text.push_str(src.text.content(id));
1489 let newline = !last && k + 1 == texts.len();
1490 let first = node.runs.len();
1491 src.text.access_runs(
1492 id,
1493 RunSource {
1494 key: node.key,
1495 line: ln,
1496 byte_base: base,
1497 origin: tree.pos[t],
1498 scale: src.scale,
1499 newline_after_last: newline,
1500 },
1501 &mut run_no,
1502 &mut node.runs,
1503 );
1504 clip_runs(&mut node.runs[first..], clip_of(src, t));
1505 }
1506 if texts.is_empty() {
1507 node.runs.push(empty_run(
1509 node.key,
1510 run_no,
1511 ln,
1512 Rect::from_pos_size(tree.pos[l], tree.size[l]),
1513 !last,
1514 ));
1515 let at = node.runs.len() - 1;
1516 clip_runs(&mut node.runs[at..], clip_of(src, l));
1517 run_no += 1;
1518 }
1519 if let Some(c) = tree.specs[l].access().caret {
1520 caret = Some((ln, (c as usize).min(line_text.len())));
1521 }
1522 if let Some(a) = tree.specs[l].access().selection_anchor {
1523 anchor = Some((ln, (a as usize).min(line_text.len())));
1524 }
1525 if ln > 0 {
1526 value.push('\n');
1527 }
1528 value.push_str(&line_text);
1529 }
1530 node.value = Some(value);
1531 if let Some((line, offset)) = caret {
1532 node.focus = node.text_pos(line, offset);
1533 let anchor = anchor.unwrap_or((line, offset));
1534 node.anchor = node.text_pos(anchor.0, anchor.1);
1535 let flat = |(line, offset): (usize, usize)| {
1537 node.value
1538 .as_ref()
1539 .map(|v| v.split('\n').take(line).map(|l| l.len() + 1).sum::<usize>() + offset)
1540 };
1541 node.caret = flat((line, offset));
1542 if anchor != (line, offset)
1543 && let (Some(a), Some(c)) = (flat(anchor), node.caret)
1544 {
1545 node.selection = Some((a.min(c), a.max(c)));
1546 }
1547 }
1548}
1549
1550pub(crate) struct RunSource {
1553 pub key: Key,
1554 pub line: usize,
1556 pub byte_base: usize,
1559 pub origin: Vec2,
1561 pub scale: f32,
1562 pub newline_after_last: bool,
1565}
1566
1567fn run_key(key: Key, n: usize) -> Key {
1570 key.str("\u{1}run").index(n as u64)
1571}
1572
1573fn empty_run(key: Key, n: usize, line: usize, rect: Rect, newline: bool) -> AccessRun {
1574 AccessRun {
1575 key: run_key(key, n),
1576 line,
1577 start: 0,
1578 end: 0,
1579 text: if newline { "\n".into() } else { String::new() },
1580 rect: Rect::new(rect.x, rect.y, 0.0, rect.h),
1581 char_lengths: if newline { vec![1] } else { Vec::new() },
1582 char_positions: if newline { vec![0.0] } else { Vec::new() },
1583 char_widths: if newline { vec![0.0] } else { Vec::new() },
1584 word_starts: Vec::new(),
1585 rtl: false,
1586 }
1587}
1588
1589pub(crate) fn runs_of_buffer(
1594 buffer: &Buffer,
1595 src: RunSource,
1596 run_no: &mut usize,
1597 out: &mut Vec<AccessRun>,
1598) {
1599 let line_count = buffer.lines.len();
1600 let runs: Vec<_> = buffer.layout_runs().collect();
1601 for (r, run) in runs.iter().enumerate() {
1602 let last_of_line = runs.get(r + 1).is_none_or(|next| next.line_i != run.line_i);
1603 let newline = last_of_line && (run.line_i + 1 < line_count || src.newline_after_last);
1604 push_row_runs(
1605 &RowGlyphs {
1606 glyphs: run.glyphs,
1607 text: run.text,
1608 line: run.line_i,
1609 top: run.line_top,
1610 height: run.line_height,
1611 rtl: run.rtl,
1612 dx: 0.0,
1613 base: 0,
1614 newline,
1615 },
1616 &src,
1617 run_no,
1618 out,
1619 );
1620 }
1621}
1622
1623pub(crate) struct RowGlyphs<'a> {
1628 pub glyphs: &'a [cosmic_text::LayoutGlyph],
1629 pub text: &'a str,
1631 pub line: usize,
1633 pub top: f32,
1634 pub height: f32,
1635 pub rtl: bool,
1636 pub dx: f32,
1637 pub base: usize,
1638 pub newline: bool,
1640}
1641
1642pub(crate) fn push_row_runs(
1645 row: &RowGlyphs<'_>,
1646 src: &RunSource,
1647 run_no: &mut usize,
1648 out: &mut Vec<AccessRun>,
1649) {
1650 let scale = src.scale.max(f32::EPSILON);
1651 let newline = row.newline;
1652 {
1653 let (start, end) = row.glyphs.iter().fold((usize::MAX, 0usize), |(s, e), g| {
1655 (s.min(g.start), e.max(g.end))
1656 });
1657 let (start, end) = if row.glyphs.is_empty() {
1658 (0, 0)
1659 } else {
1660 (start, end)
1661 };
1662 let slice = &row.text[start..end];
1663 let x0 = row
1665 .glyphs
1666 .iter()
1667 .map(|g| g.x + row.dx)
1668 .fold(f32::INFINITY, f32::min);
1669 let x0 = if x0.is_finite() { x0 } else { 0.0 };
1670 let mut chars: Vec<(usize, u8, f32, f32)> = Vec::new(); for (rel, c) in slice.char_indices() {
1674 let idx = start + rel;
1675 let len = c.len_utf8();
1676 let (x, w) = match row.glyphs.iter().find(|g| g.start <= idx && idx < g.end) {
1677 Some(g) => {
1678 let span = (g.end - g.start).max(1) as f32;
1679 (
1680 g.x + row.dx + g.w * (idx - g.start) as f32 / span,
1681 g.w * len as f32 / span,
1682 )
1683 }
1684 None => chars.last().map_or((x0, 0.0), |&(_, _, x, w)| (x + w, 0.0)),
1685 };
1686 chars.push((idx, len as u8, x, w));
1687 }
1688 let line_end_x = chars.last().map_or(x0, |&(_, _, x, w)| x + w);
1689 if newline {
1690 chars.push((end, 1, line_end_x, 0.0));
1691 }
1692 let starts: Vec<usize> = slice
1694 .split_word_bound_indices()
1695 .filter(|(_, w)| !w.chars().all(char::is_whitespace))
1696 .map(|(b, _)| slice[..b].chars().count())
1697 .collect();
1698 let mut at = 0usize;
1700 loop {
1701 let chunk_end = (at + RUN_CHARS).min(chars.len());
1702 let chunk = &chars[at..chunk_end];
1703 let chunk_x = chunk.first().map_or(x0, |c| c.2);
1704 let chunk_end_x = chunk.last().map_or(chunk_x, |c| c.2 + c.3);
1705 let byte_start = chunk.first().map_or(end, |c| c.0);
1706 let byte_end = chunk
1707 .last()
1708 .map_or(end, |c| {
1709 if newline && c.0 == end {
1710 end
1711 } else {
1712 c.0 + c.1 as usize
1713 }
1714 })
1715 .min(end);
1716 let mut text = row.text[byte_start.min(end)..byte_end].to_string();
1717 if newline && chunk_end == chars.len() {
1718 text.push('\n');
1719 }
1720 out.push(AccessRun {
1721 key: run_key(src.key, *run_no),
1722 line: src.line + row.line,
1723 start: src.byte_base + row.base + byte_start.min(end),
1724 end: src.byte_base + row.base + byte_end,
1725 text,
1726 rect: Rect::new(
1727 src.origin.x + chunk_x / scale,
1728 src.origin.y + row.top / scale,
1729 (chunk_end_x - chunk_x) / scale,
1730 row.height / scale,
1731 ),
1732 char_lengths: chunk.iter().map(|c| c.1).collect(),
1733 char_positions: chunk.iter().map(|c| (c.2 - chunk_x) / scale).collect(),
1734 char_widths: chunk.iter().map(|c| c.3 / scale).collect(),
1735 word_starts: starts
1736 .iter()
1737 .filter(|&&s| s >= at && s < chunk_end)
1738 .map(|&s| (s - at) as u8)
1739 .collect(),
1740 rtl: row.rtl,
1741 });
1742 *run_no += 1;
1743 at = chunk_end;
1744 if at >= chars.len() {
1745 break;
1746 }
1747 }
1748 }
1749}
1750
1751fn hash_of(tree: &AccessTree) -> u64 {
1752 let mut h = crate::key::FNV_OFFSET;
1753 let mut mix = |bytes: &[u8]| h = crate::key::fnv(h, bytes);
1754 let mix_str = |mix: &mut dyn FnMut(&[u8]), s: &Option<String>| match s {
1755 Some(s) => {
1756 mix(&[1]);
1757 mix(s.as_bytes());
1758 mix(&[0]);
1759 }
1760 None => mix(&[0]),
1761 };
1762 let mix_f32 = |mix: &mut dyn FnMut(&[u8]), v: Option<f32>| match v {
1763 Some(v) => {
1764 mix(&[1]);
1765 mix(&v.to_bits().to_le_bytes());
1766 }
1767 None => mix(&[0]),
1768 };
1769 let mix_pos = |mix: &mut dyn FnMut(&[u8]), p: Option<TextPos>| match p {
1770 Some(p) => {
1771 mix(&p.run.0.to_le_bytes());
1772 mix(&p.character.to_le_bytes());
1773 }
1774 None => mix(&[0]),
1775 };
1776 for n in &tree.nodes {
1777 mix(&n.key.0.to_le_bytes());
1778 mix(&n.parent.map_or(0, |k| k.0).to_le_bytes());
1779 mix(&[n.role as u8]);
1780 mix_str(&mut mix, &n.name);
1781 mix_str(&mut mix, &n.description);
1782 for v in [n.rect.x, n.rect.y, n.rect.w, n.rect.h] {
1783 mix(&v.to_bits().to_le_bytes());
1784 }
1785 mix_str(&mut mix, &n.value);
1786 mix(&n.caret.unwrap_or(usize::MAX).to_le_bytes());
1787 let (a, b) = n.selection.unwrap_or((usize::MAX, usize::MAX));
1788 mix(&a.to_le_bytes());
1789 mix(&b.to_le_bytes());
1790 for r in &n.runs {
1791 mix(&r.key.0.to_le_bytes());
1792 mix(r.text.as_bytes());
1793 mix(&[0]);
1794 for v in [r.rect.x, r.rect.y, r.rect.w, r.rect.h] {
1795 mix(&v.to_bits().to_le_bytes());
1796 }
1797 for v in &r.char_positions {
1798 mix(&v.to_bits().to_le_bytes());
1799 }
1800 }
1801 mix_pos(&mut mix, n.anchor);
1802 mix_pos(&mut mix, n.focus);
1803 mix(&[n.checked.map_or(2, |c| c as u8), n.mixed as u8]);
1804 mix(&n.step.map_or(u32::MAX, f32::to_bits).to_le_bytes());
1805 mix(&[n.selected.map_or(2, |c| c as u8)]);
1806 mix(&[n.expanded.map_or(2, |c| c as u8)]);
1807 mix(&n.pos_in_set.unwrap_or(usize::MAX).to_le_bytes());
1808 mix(&n.set_size.unwrap_or(usize::MAX).to_le_bytes());
1809 mix_f32(&mut mix, n.number);
1810 mix_f32(&mut mix, n.min);
1811 mix_f32(&mut mix, n.max);
1812 mix(&[n.focused as u8, n.disabled as u8, n.modal as u8]);
1813 if let Some(s) = n.scroll {
1814 for v in [s.x, s.y, s.max_x, s.max_y] {
1815 mix(&v.to_bits().to_le_bytes());
1816 }
1817 }
1818 mix(&n.actions.to_le_bytes());
1819 mix(&[n.live as u8]);
1820 }
1821 mix(&tree.focus.map_or(0, |k| k.0).to_le_bytes());
1822 h
1823}
1824
1825pub type Label = Arc<str>;
1828
1829#[cfg(test)]
1830mod tests {
1831 use super::*;
1832
1833 #[test]
1834 fn role_names_round_trip() {
1835 for r in Role::ALL {
1836 assert_eq!(Role::parse(r.name()), Some(r));
1837 }
1838 assert_eq!(Role::parse("nope"), None);
1839 }
1840
1841 #[test]
1842 fn action_bits_are_distinct_and_named() {
1843 let mut seen = 0u32;
1844 for a in AccessAction::ALL {
1845 assert_eq!(seen & a.bit(), 0);
1846 seen |= a.bit();
1847 assert_eq!(AccessAction::parse(a.name()), Some(a));
1848 }
1849 }
1850
1851 #[test]
1852 fn run_keys_never_collide_with_node_keys() {
1853 let k = Key::ROOT.str("editor");
1854 assert_ne!(run_key(k, 0), k.index(0));
1855 assert_ne!(run_key(k, 0), k.str("run"));
1856 assert_ne!(run_key(k, 0), run_key(k, 1));
1857 }
1858}