ui/widgets/mod.rs
1//! Widgets, grouped as catalog traits on `Theme` — import the group, reach
2//! the component: `use ui::widgets::{Content, Controls, Layout,
3//! Scaffolding, Status};` → `theme.group_box()`, `theme.tab(..)`.
4//!
5//! What stays here is deliberately trait-shaped: state flags, pure math, and
6//! the hover refinements, none of which reads the theme as a receiver.
7
8use gpui::{div, prelude::*, px};
9
10use theme::ink;
11
12mod buttons;
13mod content;
14mod controls;
15mod layout;
16mod scaffolding;
17mod status;
18
19pub use buttons::{ButtonStyle, Buttons};
20pub use content::Content;
21pub use controls::{Controls, SliderDrag, slider_fraction};
22pub use layout::{Layout, SplitDrag};
23pub use scaffolding::{OPTION_CARD_HEIGHT, OPTION_CARD_RADIUS, Scaffolding};
24pub use status::Status;
25
26/// What a control paints in the 1px border it keeps for
27/// [`crate::focus::focusable`]'s ring: nothing, until focus fills it.
28///
29/// Always present, never conditional. gpui sizes border-box, so a border that
30/// appeared only on focus would shift the content under it by a pixel — a
31/// checkbox whose tick jumps as you tab onto it.
32pub(crate) const RING_SLOT: gpui::Hsla = gpui::transparent_black();
33
34/// A flag that follows something else until the user takes it over.
35///
36/// The rule behind a section that opens itself while work streams in and
37/// collapses when it stops: auto-follow is right until the first press, and
38/// wrong immediately after — whatever the flag does next, the person who
39/// clicked has to win. Nothing agent-shaped about it; a build log that unfolds
40/// while it runs and a detail pane that follows the selection both want this.
41///
42/// It is an `Option<bool>` rather than the two flags it reads as (*touched*,
43/// plus the value): "untouched, and here is the manual value" is a state that
44/// cannot mean anything, and this way it cannot be written.
45///
46/// ```ignore
47/// let open = self.details.get(self.running); // paint this
48/// // …on the header's click:
49/// self.details.toggle(self.running);
50/// ```
51#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
52pub struct Takeover(Option<bool>);
53
54impl Takeover {
55 /// What to show: `auto` until the first [`Self::toggle`], the user's own
56 /// choice from then on.
57 pub fn get(self, auto: bool) -> bool {
58 self.0.unwrap_or(auto)
59 }
60
61 /// Flip what is currently on screen — which while nobody has touched it is
62 /// `auto`, *not* the stored value — and take over from here.
63 pub fn toggle(&mut self, auto: bool) {
64 self.0 = Some(!self.get(auto));
65 }
66}
67
68/// Where `pointer` falls along `axis` as a fraction of `bounds` — what a
69/// divider dragged there makes the split, and what a slider dragged there makes
70/// the value. `Axis::Horizontal` travels in x.
71///
72/// Clamped to `min..=1-min` — the dead zone a split passes so neither pane can
73/// be squeezed away, and the `0.0` a slider passes because it has none. On a
74/// zero-extent container the answer is `min`: the frame before layout has run
75/// would otherwise divide by zero.
76pub fn axis_fraction(
77 pointer: gpui::Point<gpui::Pixels>,
78 bounds: gpui::Bounds<gpui::Pixels>,
79 axis: gpui::Axis,
80 min: f32,
81) -> f32 {
82 let min = min.clamp(0.0, 0.5);
83 let (offset, extent) = match axis {
84 gpui::Axis::Horizontal => (pointer.x - bounds.left(), bounds.size.width),
85 gpui::Axis::Vertical => (pointer.y - bounds.top(), bounds.size.height),
86 };
87 if extent <= px(0.0) {
88 return min;
89 }
90 (offset / extent).clamp(min, 1.0 - min)
91}
92
93/// A small state dot — the "working / idle / failed" bead on a row. Takes the
94/// tone from the caller so the meaning stays with the caller's domain.
95pub fn status_dot(tone: gpui::Hsla) -> gpui::Div {
96 div().flex_none().size(px(6.0)).rounded_full().bg(tone)
97}
98
99/// The default card-row hover wash (`hover:bg-white/[0.015]`).
100pub fn card_row_hover(s: gpui::StyleRefinement) -> gpui::StyleRefinement {
101 s.bg(ink(0.015))
102}
103
104/// The default step-row hover wash (`hover:bg-white/[0.03]`).
105pub fn step_row_hover(s: gpui::StyleRefinement) -> gpui::StyleRefinement {
106 s.bg(ink(0.03))
107}
108
109/// The default collapsible-header hover wash (`hover:bg-white/[0.03]`).
110pub fn collapsible_header_hover(s: gpui::StyleRefinement) -> gpui::StyleRefinement {
111 s.bg(ink(0.03))
112}