This commit is contained in:
2026-10-30 19:36:39 +07:00
parent b4a996e44c
commit 1711ce0e9d
22 changed files with 300 additions and 485 deletions
+9
View File
@@ -0,0 +1,9 @@
[package]
name = "gpui-native-utils"
version = "0.1.0"
edition = "2024"
description = "Small helpers shared by the component crates: layout shorthands, focus plumbing, list navigation, selectable-row paint"
[dependencies]
gpui = { workspace = true }
gpui-native-theme = { workspace = true }
+98
View File
@@ -0,0 +1,98 @@
use gpui::prelude::FluentBuilder as _;
use gpui::{
App, BoxShadow, Div, ElementId, FocusHandle, Hsla, IntoElement, StatefulInteractiveElement,
Styled, Window, div, px,
};
use gpui_native_theme::Theme;
/// A row whose children center on the cross axis.
pub fn h_flex() -> Div {
div().flex().flex_row().items_center()
}
/// A column whose children stack on the main axis.
pub fn v_flex() -> Div {
div().flex().flex_col()
}
/// Returns a focus handle for the given element ID.
pub fn focus_handle(id: &ElementId, window: &mut Window, cx: &mut App) -> FocusHandle {
window
.with_element_namespace(id.clone(), |window| {
window.use_keyed_state("focus-handle", cx, |_, cx| cx.focus_handle())
})
.read(cx)
.clone()
}
/// Returns a focus ring shadow with the given color.
pub fn focus_ring(color: Hsla) -> Vec<BoxShadow> {
vec![BoxShadow::new(px(0.), px(0.), color).spread_radius(px(3.))]
}
/// The next selectable row in the direction, or `None` at the ends.
pub fn adjacent_selectable(
selectable: &[usize],
selected: Option<usize>,
forward: bool,
) -> Option<usize> {
if forward {
selectable
.iter()
.copied()
.find(|index| Some(*index) > selected)
} else {
selectable
.iter()
.copied()
.rev()
.find(|index| Some(*index) < selected)
}
}
/// Whether the given key is a vertical navigation key.
pub fn nav_key<S>(selectable: &[usize], selected: Option<usize>, key: &str, select: S) -> bool
where
S: FnOnce(Option<usize>),
{
let target = match key {
"down" => adjacent_selectable(selectable, selected, true),
"up" => adjacent_selectable(selectable, selected, false),
"home" => selectable.first().copied(),
"end" => selectable.last().copied(),
_ => return false,
};
if let Some(next) = target
&& Some(next) != selected
{
select(Some(next));
}
true
}
/// A selectable row's label and icon color.
pub fn selectable_label_color(theme: &Theme, selected: bool, disabled: bool) -> Hsla {
if disabled {
theme.colors.muted_foreground
} else if selected {
theme.colors.primary_foreground
} else {
theme.colors.foreground
}
}
/// A selectable row's background.
pub fn paint_selectable_row<R>(theme: &Theme, row: R, selected: bool, disabled: bool) -> R
where
R: IntoElement + Styled + StatefulInteractiveElement,
{
row.map(|row| {
if disabled {
row
} else if selected {
row.bg(theme.ramps.primary.base)
} else {
row.hover(|row| row.bg(theme.colors.muted))
}
})
}