use gpui::{ AnyElement, App, Div, FocusHandle, InteractiveElement, IntoElement, ParentElement, RenderOnce, StyleRefinement, Styled, Window, actions, div, }; use crate::theme::ActiveTheme; actions!(ely, [FocusNext, FocusPrev]); /// Focus-colored border while focused. Give the element a 1px border. pub trait FocusRing: InteractiveElement + Sized { fn focus_ring(self, cx: &App) -> Self { let ring = cx.theme().colors.focus; self.focus(move |style| style.border_color(ring)) } } impl FocusRing for E {} /// Owns Tab and Shift-Tab for its subtree. `trap` keeps focus inside. #[derive(IntoElement)] pub struct FocusScope { base: Div, handle: FocusHandle, trap: bool, } impl FocusScope { pub fn new(handle: &FocusHandle) -> Self { Self { base: div().track_focus(handle), handle: handle.clone(), trap: false, } } pub fn trap(mut self) -> Self { self.trap = true; self } } impl Styled for FocusScope { fn style(&mut self) -> &mut StyleRefinement { self.base.style() } } impl ParentElement for FocusScope { fn extend(&mut self, elements: impl IntoIterator) { self.base.extend(elements); } } impl RenderOnce for FocusScope { fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement { let (next, prev) = (self.handle.clone(), self.handle); let trap = self.trap; self.base .on_action(move |_: &FocusNext, window, cx| step(&next, trap, true, window, cx)) .on_action(move |_: &FocusPrev, window, cx| step(&prev, trap, false, window, cx)) } } fn step(scope: &FocusHandle, trap: bool, forward: bool, window: &mut Window, cx: &mut App) { let advance = |window: &mut Window| { if forward { window.focus_next() } else { window.focus_prev() } }; if !trap { advance(window); return; } let origin = window.focused(cx); let mut first = None; loop { advance(window); let focused = window.focused(cx); if scope.contains_focused(window, cx) && focused.as_ref() != Some(scope) { return; } if focused.is_none() || focused == origin || (first.is_some() && focused == first) { break; } if first.is_none() { first = focused; } } log::warn!("focus scope: trap holds no tab stop; focus stays"); if let Some(origin) = origin { window.focus(&origin); } }