use gpui::prelude::*;
use gpui::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FocusDirection {
#[default]
Vertical,
Horizontal,
Grid {
columns: usize,
},
}
pub struct FocusGroup {
id: ElementId,
children: Vec<AnyElement>,
direction: FocusDirection,
wraparound: bool,
focus_ring: bool,
gap: Pixels,
focus_handle: Option<FocusHandle>,
}
impl FocusGroup {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
children: Vec::new(),
direction: FocusDirection::default(),
wraparound: false,
focus_ring: true,
gap: px(8.0),
focus_handle: None,
}
}
pub fn direction(mut self, direction: FocusDirection) -> Self {
self.direction = direction;
self
}
pub fn wraparound(mut self, wrap: bool) -> Self {
self.wraparound = wrap;
self
}
pub fn focus_ring(mut self, show: bool) -> Self {
self.focus_ring = show;
self
}
pub fn gap(mut self, gap: impl Into<Pixels>) -> Self {
self.gap = gap.into();
self
}
pub fn focus_handle(mut self, handle: FocusHandle) -> Self {
self.focus_handle = Some(handle);
self
}
pub fn child(mut self, child: impl IntoElement) -> Self {
self.children.push(child.into_any_element());
self
}
pub fn children(mut self, children: impl IntoIterator<Item = impl IntoElement>) -> Self {
self.children
.extend(children.into_iter().map(|c| c.into_any_element()));
self
}
}
impl RenderOnce for FocusGroup {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let child_count = self.children.len();
let direction = self.direction;
let wraparound = self.wraparound;
let gap = self.gap;
let focus_handle = self.focus_handle.unwrap_or_else(|| cx.focus_handle());
let mut container = div()
.id(self.id)
.track_focus(&focus_handle)
.flex()
.gap(gap)
.focusable();
container = match direction {
FocusDirection::Vertical => container.flex_col(),
FocusDirection::Horizontal => container.flex_row(),
FocusDirection::Grid { columns: _ } => {
container.flex_row().flex_wrap()
}
};
let focus_handle_for_key = focus_handle.clone();
container = container.on_key_down(move |event, window, cx| {
if !focus_handle_for_key.is_focused(window) {
return;
}
let key = event.keystroke.key.as_str();
match direction {
FocusDirection::Vertical => match key {
"up" => {
cx.stop_propagation();
}
"down" => {
cx.stop_propagation();
}
"home" => {
cx.stop_propagation();
}
"end" => {
cx.stop_propagation();
}
_ => {}
},
FocusDirection::Horizontal => match key {
"left" => {
cx.stop_propagation();
}
"right" => {
cx.stop_propagation();
}
"home" => {
cx.stop_propagation();
}
"end" => {
cx.stop_propagation();
}
_ => {}
},
FocusDirection::Grid { columns } => {
let _ = columns; let _ = wraparound;
let _ = child_count;
match key {
"up" | "down" | "left" | "right" | "home" | "end" => {
cx.stop_propagation();
}
_ => {}
}
}
}
});
for child in self.children {
container = container.child(child);
}
container
}
}
impl IntoElement for FocusGroup {
type Element = gpui::Component<Self>;
fn into_element(self) -> Self::Element {
gpui::Component::new(self)
}
}
pub trait FocusGroupExt {
fn with_focus_navigation(self, id: impl Into<ElementId>) -> FocusGroup;
}