use gpui::prelude::FluentBuilder as _; use gpui::{ App, Bounds, BoxShadow, Div, ElementId, FocusHandle, Global, Hsla, IntoElement, Pixels, Refineable as _, StatefulInteractiveElement, StyleRefinement, Styled, Window, WindowId, div, px, }; use gpui_native_theme::ActiveTheme as _; use std::collections::HashMap; /// 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() } /// Extensions over [`Styled`], shared by the components. pub trait StyledExt: Styled + Sized { fn refine_style(mut self, style: &StyleRefinement) -> Self { self.style().refine(style); self } } impl StyledExt for E {} /// 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() } /// The painted bounds of elements that tooltips anchor to, per window and element id. #[derive(Default)] struct AnchorBoundsRegistry(HashMap<(WindowId, ElementId), Bounds>); impl Global for AnchorBoundsRegistry {} impl AnchorBoundsRegistry { fn record(&mut self, window: WindowId, id: ElementId, bounds: Bounds) { self.0.insert((window, id), bounds); } fn get(&self, window: WindowId, id: &ElementId) -> Option> { self.0.get(&(window, id.clone())).copied() } } /// The last bounds an element painted at, in the given window. pub fn anchor_bounds(window: WindowId, id: &ElementId, cx: &App) -> Option> { cx.try_global::() .and_then(|registry| registry.get(window, id)) } /// A canvas prepaint closure recording the element's painted bounds. pub fn record_anchor_bounds( id: ElementId, ) -> impl Fn(Bounds, &mut Window, &mut App) + 'static { move |bounds, window, cx| { let window_id = window.window_handle().window_id(); cx.default_global::() .record(window_id, id.clone(), bounds); } } /// Returns a focus ring shadow with the given color at half ink. pub fn focus_ring(color: Hsla) -> Vec { vec![BoxShadow::new(px(0.), px(0.), color.alpha(0.5)).spread_radius(px(3.))] } /// The next selectable row in the direction, or `None` at the ends. pub fn adjacent_selectable( selectable: &[usize], selected: Option, forward: bool, ) -> Option { 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(selectable: &[usize], selected: Option, key: &str, select: S) -> bool where S: FnOnce(Option), { 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(cx: &App, selected: bool, disabled: bool) -> Hsla { let theme = cx.theme(); 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(cx: &App, row: R, selected: bool, disabled: bool) -> R where R: IntoElement + Styled + StatefulInteractiveElement, { let theme = cx.theme(); row.map(|row| { if disabled { row } else if selected { row.bg(theme.ramps.primary.base) } else { row.hover(|row| row.bg(theme.colors.muted)) } }) }