T01 primitives: image, divider, backdrop, focus, pressable, measure, tooltip
Ely-built primitives plus gallery demos for every chapter-one entry, including gpui natives. The gallery now scripts real input by posting NSEvents to its own queue and shoots each interaction in both modes. Codex gpt-6-astra (max): three rounds; the last finding, a two-line doc comment, was fixed after the three-review cap.
This commit is contained in:
38 files changed
+2165
-157
No files matched your search
@@ -0,0 +1,96 @@
|
||||
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<E: InteractiveElement> 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<Item = AnyElement>) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user