Another project
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 20 kB View raw
1use core::num::NonZeroU32; 2use std::collections::BTreeSet; 3 4use serde::{Deserialize, Serialize}; 5 6use crate::input::InputSnapshot; 7use crate::widget_id::WidgetId; 8 9#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] 10#[serde(transparent)] 11pub struct FocusScopeId(NonZeroU32); 12 13impl FocusScopeId { 14 pub const ROOT: Self = match NonZeroU32::new(1) { 15 Some(n) => Self(n), 16 None => panic!("ROOT scope id is 1"), 17 }; 18 19 #[must_use] 20 pub const fn get(self) -> NonZeroU32 { 21 self.0 22 } 23} 24 25#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] 26pub enum FocusScopeKind { 27 Window, 28 Modal, 29 Roving, 30} 31 32#[derive(Copy, Clone, Debug, PartialEq, Eq)] 33struct FocusScope { 34 id: FocusScopeId, 35 kind: FocusScopeKind, 36} 37 38const FIRST_CHILD_SCOPE: NonZeroU32 = match NonZeroU32::new(2) { 39 Some(n) => n, 40 None => panic!("first child scope id is 2"), 41}; 42 43#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] 44pub enum InputModality { 45 Keyboard, 46 Pointer, 47} 48 49#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] 50pub enum RovingDirection { 51 Next, 52 Prev, 53 First, 54 Last, 55} 56 57#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] 58pub enum FocusRequest { 59 Set(WidgetId), 60 Clear, 61 Advance, 62 Retreat, 63 Rove(RovingDirection), 64} 65 66#[derive(Clone, Debug, PartialEq)] 67pub struct FocusManager { 68 focused: Option<WidgetId>, 69 modality: InputModality, 70 scopes: Vec<FocusScope>, 71 next_scope: NonZeroU32, 72 tab_stops: Vec<(WidgetId, FocusScopeId)>, 73 tab_stop_ids: BTreeSet<WidgetId>, 74 focusable: BTreeSet<WidgetId>, 75 text_inputs: BTreeSet<WidgetId>, 76 request: Option<FocusRequest>, 77} 78 79impl Default for FocusManager { 80 fn default() -> Self { 81 Self::new() 82 } 83} 84 85impl FocusManager { 86 #[must_use] 87 pub fn new() -> Self { 88 let root = FocusScope { 89 id: FocusScopeId::ROOT, 90 kind: FocusScopeKind::Window, 91 }; 92 Self { 93 focused: None, 94 modality: InputModality::Pointer, 95 scopes: vec![root], 96 next_scope: FIRST_CHILD_SCOPE, 97 tab_stops: Vec::new(), 98 tab_stop_ids: BTreeSet::new(), 99 focusable: BTreeSet::new(), 100 text_inputs: BTreeSet::new(), 101 request: None, 102 } 103 } 104 105 #[must_use] 106 pub fn focused(&self) -> Option<WidgetId> { 107 self.focused 108 } 109 110 #[must_use] 111 pub fn focus_visible(&self) -> bool { 112 matches!(self.modality, InputModality::Keyboard) 113 } 114 115 pub fn observe_input(&mut self, input: &InputSnapshot) { 116 if !input.keys_pressed.is_empty() { 117 self.modality = InputModality::Keyboard; 118 } else if !input.buttons_pressed.is_empty() || !input.buttons_released.is_empty() { 119 self.modality = InputModality::Pointer; 120 } 121 } 122 123 #[must_use] 124 pub fn current_scope(&self) -> FocusScopeId { 125 self.scopes.last().map_or(FocusScopeId::ROOT, |s| s.id) 126 } 127 128 pub fn push_scope(&mut self, kind: FocusScopeKind) -> FocusScopeId { 129 let id = FocusScopeId(self.next_scope); 130 self.next_scope = match self.next_scope.checked_add(1) { 131 Some(n) => n, 132 None => panic!("focus scope id space exhausted"), 133 }; 134 self.scopes.push(FocusScope { id, kind }); 135 id 136 } 137 138 pub fn pop_scope(&mut self) -> Option<FocusScopeId> { 139 if self.scopes.len() <= 1 { 140 return None; 141 } 142 self.scopes.pop().map(|s| s.id) 143 } 144 145 pub fn register_tab_stop(&mut self, id: WidgetId) { 146 if !self.tab_stop_ids.insert(id) { 147 return; 148 } 149 self.focusable.insert(id); 150 let scope = self.current_scope(); 151 self.tab_stops.push((id, scope)); 152 } 153 154 pub fn register_focusable(&mut self, id: WidgetId) { 155 self.focusable.insert(id); 156 } 157 158 pub fn register_text_input(&mut self, id: WidgetId) { 159 self.text_inputs.insert(id); 160 } 161 162 #[must_use] 163 pub fn is_text_input_focused(&self) -> bool { 164 self.focused 165 .is_some_and(|id| self.text_inputs.contains(&id)) 166 } 167 168 #[must_use] 169 pub fn tab_stops(&self) -> &[(WidgetId, FocusScopeId)] { 170 &self.tab_stops 171 } 172 173 pub fn focusable_ids(&self) -> impl Iterator<Item = WidgetId> + '_ { 174 self.focusable.iter().copied() 175 } 176 177 pub fn request(&mut self, request: FocusRequest) { 178 self.request = Some(request); 179 } 180 181 pub fn request_focus(&mut self, id: WidgetId) { 182 self.request(FocusRequest::Set(id)); 183 } 184 185 pub fn begin_frame(&mut self) { 186 self.tab_stops.clear(); 187 self.tab_stop_ids.clear(); 188 self.focusable.clear(); 189 self.text_inputs.clear(); 190 self.scopes.truncate(1); 191 self.next_scope = FIRST_CHILD_SCOPE; 192 } 193 194 pub fn end_frame(&mut self) { 195 let request = self.request.take(); 196 let next = match request { 197 None => self.focused, 198 Some(FocusRequest::Set(id)) => Some(id), 199 Some(FocusRequest::Clear) => None, 200 Some(FocusRequest::Advance) => self.advance_within_scope(false), 201 Some(FocusRequest::Retreat) => self.advance_within_scope(true), 202 Some(FocusRequest::Rove(direction)) => self.rove_within_roving_scope(direction), 203 }; 204 self.focused = self.validate_focus(next); 205 } 206 207 fn validate_focus(&self, candidate: Option<WidgetId>) -> Option<WidgetId> { 208 candidate.filter(|id| self.focusable.contains(id)) 209 } 210 211 fn nearest_scope(&self, kind: FocusScopeKind) -> Option<FocusScopeId> { 212 self.scopes 213 .iter() 214 .rev() 215 .find(|s| s.kind == kind) 216 .map(|s| s.id) 217 } 218 219 fn stops_for_scope(&self, scope: FocusScopeId) -> Vec<WidgetId> { 220 self.tab_stops 221 .iter() 222 .filter(|(_, s)| *s == scope) 223 .map(|(id, _)| *id) 224 .collect() 225 } 226 227 fn advance_within_scope(&self, reverse: bool) -> Option<WidgetId> { 228 let trap = self 229 .nearest_scope(FocusScopeKind::Modal) 230 .unwrap_or(FocusScopeId::ROOT); 231 let stops = self.stops_for_scope(trap); 232 cycle(&stops, self.focused, reverse) 233 } 234 235 fn rove_within_roving_scope(&self, direction: RovingDirection) -> Option<WidgetId> { 236 let scope = self.nearest_scope(FocusScopeKind::Roving)?; 237 let stops = self.stops_for_scope(scope); 238 match direction { 239 RovingDirection::Next => cycle(&stops, self.focused, false), 240 RovingDirection::Prev => cycle(&stops, self.focused, true), 241 RovingDirection::First => stops.first().copied(), 242 RovingDirection::Last => stops.last().copied(), 243 } 244 } 245} 246 247fn cycle(stops: &[WidgetId], current: Option<WidgetId>, reverse: bool) -> Option<WidgetId> { 248 if stops.is_empty() { 249 return None; 250 } 251 let len = stops.len(); 252 let next_index = match current.and_then(|id| stops.iter().position(|s| *s == id)) { 253 None => { 254 if reverse { 255 len - 1 256 } else { 257 0 258 } 259 } 260 Some(idx) => { 261 if reverse { 262 (idx + len - 1) % len 263 } else { 264 (idx + 1) % len 265 } 266 } 267 }; 268 stops.get(next_index).copied() 269} 270 271#[cfg(test)] 272mod tests { 273 use super::{FocusManager, FocusRequest, FocusScopeKind, RovingDirection}; 274 use crate::input::{ 275 FrameInstant, InputSnapshot, KeyChar, KeyCode, KeyEvent, ModifierMask, PointerButton, 276 PointerButtonMask, 277 }; 278 use crate::widget_id::{WidgetId, WidgetKey}; 279 280 fn id(key: &'static str) -> WidgetId { 281 WidgetId::ROOT.child(WidgetKey::new(key)) 282 } 283 284 fn run_frame<F: FnOnce(&mut FocusManager)>(focus: &mut FocusManager, build: F) { 285 focus.begin_frame(); 286 build(focus); 287 focus.end_frame(); 288 } 289 290 fn key_input() -> InputSnapshot { 291 let mut input = InputSnapshot::idle(FrameInstant::ZERO); 292 input.keys_pressed.push(KeyEvent::new( 293 KeyCode::Char(KeyChar::from_char('a')), 294 ModifierMask::NONE, 295 )); 296 input 297 } 298 299 fn pointer_button_input() -> InputSnapshot { 300 let mut input = InputSnapshot::idle(FrameInstant::ZERO); 301 input.buttons_pressed = PointerButtonMask::just(PointerButton::Primary); 302 input 303 } 304 305 #[test] 306 fn observe_input_flips_to_keyboard_on_keypress() { 307 let mut focus = FocusManager::new(); 308 focus.observe_input(&pointer_button_input()); 309 assert!(!focus.focus_visible()); 310 focus.observe_input(&key_input()); 311 assert!(focus.focus_visible()); 312 } 313 314 #[test] 315 fn observe_input_flips_back_to_pointer_on_button() { 316 let mut focus = FocusManager::new(); 317 focus.observe_input(&key_input()); 318 assert!(focus.focus_visible()); 319 focus.observe_input(&pointer_button_input()); 320 assert!(!focus.focus_visible()); 321 } 322 323 #[test] 324 fn observe_input_idle_keeps_modality() { 325 let mut focus = FocusManager::new(); 326 focus.observe_input(&key_input()); 327 assert!(focus.focus_visible()); 328 focus.observe_input(&InputSnapshot::idle(FrameInstant::ZERO)); 329 assert!(focus.focus_visible(), "idle frames must not flip modality"); 330 } 331 332 #[test] 333 fn advance_walks_tab_stops_in_order() { 334 let mut focus = FocusManager::new(); 335 let a = id("a"); 336 let b = id("b"); 337 let c = id("c"); 338 run_frame(&mut focus, |f| { 339 f.register_tab_stop(a); 340 f.register_tab_stop(b); 341 f.register_tab_stop(c); 342 f.request(FocusRequest::Advance); 343 }); 344 assert_eq!(focus.focused(), Some(a)); 345 run_frame(&mut focus, |f| { 346 f.register_tab_stop(a); 347 f.register_tab_stop(b); 348 f.register_tab_stop(c); 349 f.request(FocusRequest::Advance); 350 }); 351 assert_eq!(focus.focused(), Some(b)); 352 run_frame(&mut focus, |f| { 353 f.register_tab_stop(a); 354 f.register_tab_stop(b); 355 f.register_tab_stop(c); 356 f.request(FocusRequest::Advance); 357 }); 358 assert_eq!(focus.focused(), Some(c)); 359 run_frame(&mut focus, |f| { 360 f.register_tab_stop(a); 361 f.register_tab_stop(b); 362 f.register_tab_stop(c); 363 f.request(FocusRequest::Advance); 364 }); 365 assert_eq!(focus.focused(), Some(a)); 366 } 367 368 #[test] 369 fn retreat_walks_in_reverse() { 370 let mut focus = FocusManager::new(); 371 let a = id("a"); 372 let b = id("b"); 373 run_frame(&mut focus, |f| { 374 f.register_tab_stop(a); 375 f.register_tab_stop(b); 376 f.request_focus(a); 377 }); 378 run_frame(&mut focus, |f| { 379 f.register_tab_stop(a); 380 f.register_tab_stop(b); 381 f.request(FocusRequest::Retreat); 382 }); 383 assert_eq!(focus.focused(), Some(b)); 384 } 385 386 #[test] 387 fn modal_trap_restricts_tab_traversal() { 388 let mut focus = FocusManager::new(); 389 let outer = id("outer"); 390 let modal_a = id("modal_a"); 391 let modal_b = id("modal_b"); 392 run_frame(&mut focus, |f| { 393 f.register_tab_stop(outer); 394 f.push_scope(FocusScopeKind::Modal); 395 f.register_tab_stop(modal_a); 396 f.register_tab_stop(modal_b); 397 f.request_focus(modal_a); 398 }); 399 assert_eq!(focus.focused(), Some(modal_a)); 400 run_frame(&mut focus, |f| { 401 f.register_tab_stop(outer); 402 f.push_scope(FocusScopeKind::Modal); 403 f.register_tab_stop(modal_a); 404 f.register_tab_stop(modal_b); 405 f.request(FocusRequest::Advance); 406 }); 407 assert_eq!(focus.focused(), Some(modal_b)); 408 run_frame(&mut focus, |f| { 409 f.register_tab_stop(outer); 410 f.push_scope(FocusScopeKind::Modal); 411 f.register_tab_stop(modal_a); 412 f.register_tab_stop(modal_b); 413 f.request(FocusRequest::Advance); 414 }); 415 assert_eq!(focus.focused(), Some(modal_a)); 416 } 417 418 #[test] 419 fn roving_within_composite_widget() { 420 let mut focus = FocusManager::new(); 421 let toolbar_a = id("tool_a"); 422 let toolbar_b = id("tool_b"); 423 let toolbar_c = id("tool_c"); 424 run_frame(&mut focus, |f| { 425 f.push_scope(FocusScopeKind::Roving); 426 f.register_tab_stop(toolbar_a); 427 f.register_tab_stop(toolbar_b); 428 f.register_tab_stop(toolbar_c); 429 f.request_focus(toolbar_b); 430 }); 431 run_frame(&mut focus, |f| { 432 f.push_scope(FocusScopeKind::Roving); 433 f.register_tab_stop(toolbar_a); 434 f.register_tab_stop(toolbar_b); 435 f.register_tab_stop(toolbar_c); 436 f.request(FocusRequest::Rove(RovingDirection::Next)); 437 }); 438 assert_eq!(focus.focused(), Some(toolbar_c)); 439 run_frame(&mut focus, |f| { 440 f.push_scope(FocusScopeKind::Roving); 441 f.register_tab_stop(toolbar_a); 442 f.register_tab_stop(toolbar_b); 443 f.register_tab_stop(toolbar_c); 444 f.request(FocusRequest::Rove(RovingDirection::First)); 445 }); 446 assert_eq!(focus.focused(), Some(toolbar_a)); 447 } 448 449 #[test] 450 fn focus_drops_when_widget_disappears() { 451 let mut focus = FocusManager::new(); 452 let a = id("a"); 453 let b = id("b"); 454 run_frame(&mut focus, |f| { 455 f.register_tab_stop(a); 456 f.register_tab_stop(b); 457 f.request_focus(b); 458 }); 459 assert_eq!(focus.focused(), Some(b)); 460 run_frame(&mut focus, |f| { 461 f.register_tab_stop(a); 462 }); 463 assert_eq!(focus.focused(), None); 464 } 465 466 #[test] 467 fn pop_scope_does_not_unseat_root() { 468 let mut focus = FocusManager::new(); 469 assert!(focus.pop_scope().is_none()); 470 let inner = focus.push_scope(FocusScopeKind::Modal); 471 assert_eq!(focus.pop_scope(), Some(inner)); 472 assert!(focus.pop_scope().is_none()); 473 } 474 475 #[test] 476 fn tab_skips_roving_stops_without_modal() { 477 let mut focus = FocusManager::new(); 478 let host = id("toolbar"); 479 let rove_a = id("rove_a"); 480 let rove_b = id("rove_b"); 481 let trailing = id("after"); 482 run_frame(&mut focus, |f| { 483 f.register_tab_stop(host); 484 f.push_scope(FocusScopeKind::Roving); 485 f.register_tab_stop(rove_a); 486 f.register_tab_stop(rove_b); 487 f.pop_scope(); 488 f.register_tab_stop(trailing); 489 f.request(FocusRequest::Advance); 490 }); 491 assert_eq!(focus.focused(), Some(host)); 492 run_frame(&mut focus, |f| { 493 f.register_tab_stop(host); 494 f.push_scope(FocusScopeKind::Roving); 495 f.register_tab_stop(rove_a); 496 f.register_tab_stop(rove_b); 497 f.pop_scope(); 498 f.register_tab_stop(trailing); 499 f.request(FocusRequest::Advance); 500 }); 501 assert_eq!(focus.focused(), Some(trailing)); 502 } 503 504 #[test] 505 fn rove_returns_none_outside_roving_scope() { 506 let mut focus = FocusManager::new(); 507 let a = id("a"); 508 let b = id("b"); 509 run_frame(&mut focus, |f| { 510 f.register_tab_stop(a); 511 f.register_tab_stop(b); 512 f.request_focus(a); 513 }); 514 assert_eq!(focus.focused(), Some(a)); 515 run_frame(&mut focus, |f| { 516 f.register_tab_stop(a); 517 f.register_tab_stop(b); 518 f.request(FocusRequest::Rove(RovingDirection::Next)); 519 }); 520 assert_eq!(focus.focused(), None); 521 } 522 523 #[test] 524 fn begin_frame_resets_scope_ids() { 525 let mut focus = FocusManager::new(); 526 focus.begin_frame(); 527 let first = focus.push_scope(FocusScopeKind::Modal); 528 focus.end_frame(); 529 focus.begin_frame(); 530 let second = focus.push_scope(FocusScopeKind::Modal); 531 focus.end_frame(); 532 assert_eq!(first, second, "scope ids reset each frame"); 533 } 534 535 #[test] 536 fn begin_frame_truncates_leaked_scopes() { 537 let mut focus = FocusManager::new(); 538 focus.push_scope(FocusScopeKind::Modal); 539 focus.push_scope(FocusScopeKind::Roving); 540 focus.begin_frame(); 541 assert_eq!(focus.current_scope(), super::FocusScopeId::ROOT); 542 } 543 544 #[test] 545 fn register_tab_stop_dedupes_per_frame() { 546 let mut focus = FocusManager::new(); 547 let a = id("a"); 548 let b = id("b"); 549 run_frame(&mut focus, |f| { 550 f.register_tab_stop(a); 551 f.register_tab_stop(a); 552 f.register_tab_stop(b); 553 f.request_focus(a); 554 }); 555 assert_eq!(focus.tab_stops().len(), 2); 556 run_frame(&mut focus, |f| { 557 f.register_tab_stop(a); 558 f.register_tab_stop(a); 559 f.register_tab_stop(b); 560 f.request(FocusRequest::Advance); 561 }); 562 assert_eq!(focus.focused(), Some(b)); 563 } 564 565 #[test] 566 fn programmatic_focus_holds_for_focusable_non_tab_stop() { 567 let mut focus = FocusManager::new(); 568 let click_only = id("canvas"); 569 run_frame(&mut focus, |f| { 570 f.register_focusable(click_only); 571 f.request_focus(click_only); 572 }); 573 assert_eq!(focus.focused(), Some(click_only)); 574 } 575 576 #[test] 577 fn programmatic_focus_drops_for_unknown_widget() { 578 let mut focus = FocusManager::new(); 579 let ghost = id("ghost"); 580 run_frame(&mut focus, |f| { 581 f.request_focus(ghost); 582 }); 583 assert_eq!(focus.focused(), None); 584 } 585 586 #[test] 587 fn is_text_input_focused_distinguishes_text_input_from_button() { 588 let mut focus = FocusManager::new(); 589 let button = id("button"); 590 let input = id("input"); 591 run_frame(&mut focus, |f| { 592 f.register_focusable(button); 593 f.register_focusable(input); 594 f.register_text_input(input); 595 f.request_focus(button); 596 }); 597 assert_eq!(focus.focused(), Some(button)); 598 assert!( 599 !focus.is_text_input_focused(), 600 "button focus must not match" 601 ); 602 run_frame(&mut focus, |f| { 603 f.register_focusable(button); 604 f.register_focusable(input); 605 f.register_text_input(input); 606 f.request_focus(input); 607 }); 608 assert_eq!(focus.focused(), Some(input)); 609 assert!(focus.is_text_input_focused()); 610 } 611 612 #[test] 613 fn is_text_input_focused_false_when_nothing_focused() { 614 let focus = FocusManager::new(); 615 assert!(!focus.is_text_input_focused()); 616 } 617 618 #[test] 619 fn modal_traps_tab_with_nested_roving_excluded() { 620 let mut focus = FocusManager::new(); 621 let modal_a = id("modal_a"); 622 let rove_x = id("rove_x"); 623 let modal_b = id("modal_b"); 624 run_frame(&mut focus, |f| { 625 f.push_scope(FocusScopeKind::Modal); 626 f.register_tab_stop(modal_a); 627 f.push_scope(FocusScopeKind::Roving); 628 f.register_tab_stop(rove_x); 629 f.pop_scope(); 630 f.register_tab_stop(modal_b); 631 f.request_focus(modal_a); 632 }); 633 run_frame(&mut focus, |f| { 634 f.push_scope(FocusScopeKind::Modal); 635 f.register_tab_stop(modal_a); 636 f.push_scope(FocusScopeKind::Roving); 637 f.register_tab_stop(rove_x); 638 f.pop_scope(); 639 f.register_tab_stop(modal_b); 640 f.request(FocusRequest::Advance); 641 }); 642 assert_eq!(focus.focused(), Some(modal_b)); 643 } 644}