Skip to main content

gpui_cn/
lib.rs

1//! shadcn-style components for GPUI, built on `gpui-base`.
2//!
3//! gpui-cn depends on `gpui-kit` with the component layer off, so it adds
4//! GPUI and `gpui-base` and nothing else.
5//!
6//! Initialize `gpui-base` first (through `gpui_kit::init`), then call
7//! [`init`] once before opening windows. gpui-cn adds itself to every
8//! window whose first level is gpui-base's `Root`, which
9//! `gpui_kit::open_window` puts there:
10//!
11//! ```no_run
12//! use gpui_kit::*;
13//!
14//! struct Hello;
15//!
16//! impl Render for Hello {
17//!     fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
18//!         div().p_4().child(
19//!             gpui_cn::Button::new("save")
20//!                 .primary()
21//!                 .label("Save")
22//!                 .on_click(|_, window, cx| {
23//!                     let _ = window.prompt(PromptLevel::Info, "Saved", None, &["OK"], cx);
24//!                 }),
25//!         )
26//!     }
27//! }
28//!
29//! fn main() {
30//!     gpui_kit::application().run(|cx| {
31//!         gpui_kit::init(cx);
32//!         gpui_cn::init(cx);
33//!         gpui_kit::open_window(WindowOptions::default(), cx, |_, cx| cx.new(|_| Hello))
34//!             .expect("failed to open window");
35//!     });
36//! }
37//! ```
38
39mod avatar;
40mod badge;
41mod button;
42mod collapse;
43mod command;
44mod command_dialog;
45mod composer;
46mod dialog;
47pub mod dock;
48mod file_picker;
49mod folder_picker;
50pub mod fuzzy;
51mod glide;
52mod icon;
53mod input;
54mod label;
55mod looping;
56mod menu;
57mod middle_text;
58mod nav;
59mod path_browser;
60mod plural;
61mod popover;
62mod progress;
63mod radio;
64pub mod remote;
65mod root;
66mod scroll_area;
67mod select;
68pub mod shell;
69mod sidebar;
70mod skeleton;
71mod slider;
72mod spinner;
73mod switch;
74mod tabs;
75mod tag;
76#[cfg(feature = "ghostty")]
77pub mod terminal;
78pub mod theme;
79mod theme_mode_picker;
80mod title_bar;
81mod tooltip;
82mod tooltip_host;
83mod touch_selection;
84#[cfg(feature = "webview")]
85pub mod webview;
86
87pub use avatar::{Avatar, AvatarGroup, AvatarSize};
88pub use badge::Badge;
89pub use button::{Button, ButtonSize, ButtonVariant};
90pub use command::{
91    Command, CommandEntry, CommandEvent, CommandGroup, CommandItem, CommandRow,
92    CommandSearchHandler, CommandState,
93};
94pub use command_dialog::CommandDialog;
95pub use composer::{
96    Attachment, AttachmentKind, AttachmentStatus, AttachmentStrip, AttachmentTile, Composer,
97    ComposerAssets, ComposerEvent, ComposerState, ComposerStatusTab, ContextMeter, EFFORT_LABELS,
98    EffortMenu, ModelEntry, ModelPicker, ModelPickerEvent, ModelPickerState, ModelProvider,
99    ModelView, PermissionEvent, PermissionMenu, PermissionMode, PermissionState, PermissionTone,
100    SEARCH_MIN_OPTIONS, StatusOption, StatusSelect, StatusSelectEvent, StatusSelectState,
101};
102pub use dialog::Dialog;
103pub use dock::DockSkin;
104pub use file_picker::{
105    FileEntry, FileFilter, FileKind, FileListing, FileLoaded, FilePage, FilePicker,
106    FilePickerEvent, FilePickerState, FileSource, LocalFiles, MemoryFiles,
107};
108pub use folder_picker::{
109    FolderEntry, FolderPage, FolderPicker, FolderPickerEvent, FolderPickerState, FolderSource,
110    Listing, Loaded, LocalFolders, MemoryFolders, MoreState, PageToken,
111};
112pub use glide::{Glide, GlideState};
113pub use gpui_kit;
114pub use gpui_kit::base::input::{InputEvent, InputState, TextareaState};
115pub use gpui_kit::base::slider::{SliderEvent, SliderState, SliderValue};
116pub use icon::{Icon, IconSource};
117pub use input::{Field, Input, Textarea};
118pub use label::Label;
119pub use menu::{
120    ContextMenu, DropdownMenu, MenuAnchor, MenuBar, MenuBarMenu, MenuBarState, MenuEntry,
121    MenuEvent, MenuItem, MenuRowState, MenuState, MenuSubmenu,
122};
123pub use nav::{
124    NavButtons, NavMotion, NavOperation, NavPage, NavStack, NavStackEvent, NavStackExt,
125    NavStackState,
126};
127pub use path_browser::{CreateFolderError, ListError, PathStyle, SourcePath};
128pub use popover::Popover;
129pub use progress::Progress;
130pub use radio::{Radio, RadioGroup, RadioMark};
131pub use scroll_area::ScrollArea;
132pub use select::{
133    SearchHandler, Select, SelectEntry, SelectEvent, SelectItem, SelectRow, SelectState,
134    SelectValue,
135};
136pub use sidebar::{
137    Sidebar, SidebarCollapsible, SidebarEvent, SidebarGroup, SidebarLayout, SidebarMenuButton,
138    SidebarMenuSize, SidebarMenuSkeleton, SidebarMenuSub, SidebarSeparator, SidebarSide,
139    SidebarState, SidebarTrigger,
140};
141pub use skeleton::Skeleton;
142pub use slider::Slider;
143pub use spinner::Spinner;
144pub use switch::Switch;
145pub use tabs::{Tab, Tabs, TabsEvent, TabsState};
146pub use tag::{Tag, TagVariant};
147pub use theme::{
148    ActiveTheme, MetricTokens, ReduceMotion, Theme, ThemeConfig, ThemeMode, ThemeScope, ThemeTokens,
149};
150pub use theme_mode_picker::ThemeModePicker;
151pub use title_bar::TitleBar;
152pub use tooltip::{Tooltip, TooltipExt, TooltipTrigger};
153pub use tooltip_host::TooltipHost;
154#[cfg(feature = "webview")]
155pub use webview::{BrowserEvent, BrowserState, WebFrame};
156
157use gpui_kit::{App, Global};
158
159/// Everything an application normally imports from gpui-cn.
160pub mod prelude {
161    #[cfg(all(
162        feature = "ghostty",
163        not(any(target_os = "ios", target_os = "android"))
164    ))]
165    pub use crate::terminal::LocalTerminalOptions;
166    #[cfg(feature = "ghostty")]
167    pub use crate::terminal::{
168        Terminal, TerminalAppearance, TerminalColors, TerminalConfig, TerminalEvent, TerminalState,
169    };
170    #[cfg(feature = "webview")]
171    pub use crate::webview::{BrowserState, WebFrame, WebView};
172    pub use crate::{
173        ActiveTheme, Avatar, AvatarGroup, AvatarSize, Badge, Button, ButtonSize, ButtonVariant,
174        Command, CommandDialog, CommandEntry, CommandGroup, CommandItem, CommandState, ContextMenu,
175        Dialog, DockSkin, DropdownMenu, Field, FilePicker, FilePickerState, FolderPicker,
176        FolderPickerState, Icon, Input, InputState, Label, MenuBar, MenuBarMenu, MenuBarState,
177        MenuEntry, MenuItem, MenuState, MenuSubmenu, NavButtons, NavMotion, NavStack, NavStackExt,
178        NavStackState, Popover, Progress, Radio, RadioGroup, RadioMark, ReduceMotion, ScrollArea,
179        Select, SelectEntry, SelectItem, SelectState, Sidebar, SidebarCollapsible, SidebarGroup,
180        SidebarLayout, SidebarMenuButton, SidebarMenuSub, SidebarSeparator, SidebarSide,
181        SidebarState, SidebarTrigger, Slider, SliderEvent, SliderState, SliderValue, Spinner,
182        Switch, Tab, Tabs, TabsEvent, TabsState, Tag, TagVariant, Textarea, TextareaState, Theme,
183        ThemeMode, ThemeModePicker, ThemeScope, TitleBar, TooltipExt,
184    };
185    pub use gpui_kit::base::{Disableable, Placement, Selectable, StyledExt};
186    pub use gpui_kit::prelude::FluentBuilder;
187}
188
189/// Marks that [`init`] ran, so a second call is a no-op.
190struct Initialized;
191
192impl Global for Initialized {}
193
194/// Initializes gpui-cn. Call it once, after `gpui_base::init` (normally
195/// through `gpui_kit::init`) and before opening windows.
196///
197/// Installs the built-in light and dark themes and projects them onto
198/// `gpui_base::Theme`, as `gpui_component::init` does for its own theme.
199/// With the `jetbrains-mono` feature it also registers JetBrains Mono
200/// with the text system and makes it the code font. See
201/// [`theme::fonts`].
202/// Calling it twice changes nothing. In an application that also runs
203/// `gpui_component::init`, the later of the two owns the base theme and
204/// the other layer follows its colors.
205///
206/// # Panics
207///
208/// If `gpui_base::init` has not run. gpui-cn never initializes base itself:
209/// the host owns that, as it does for every other layer on base.
210pub fn init(cx: &mut App) {
211    if cx.has_global::<Initialized>() {
212        return;
213    }
214    assert!(
215        base_initialized(cx),
216        "gpui_cn::init needs gpui_base::init first; call gpui_kit::init(cx) before it"
217    );
218    theme::fonts::register(cx);
219    Theme::init(cx);
220    root::init(cx);
221    select::init(cx);
222    menu::init(cx);
223    command::init(cx);
224    composer::init_model_picker(cx);
225    path_browser::init(cx);
226    folder_picker::init(cx);
227    file_picker::init(cx);
228    cx.set_global(Initialized);
229}
230
231/// Whether `gpui_base::init` has run in this application.
232///
233/// `gpui_base::init` installs `GlobalState`, which nothing else creates, so
234/// its presence is the signal.
235fn base_initialized(cx: &App) -> bool {
236    cx.has_global::<gpui_kit::base::GlobalState>()
237}
238
239#[cfg(test)]
240mod tests {
241    use gpui_kit::TestAppContext;
242
243    #[gpui_kit::test]
244    fn init_is_idempotent_and_skips_base_when_already_initialized(cx: &mut TestAppContext) {
245        cx.update(|cx| {
246            assert!(!super::base_initialized(cx));
247            gpui_kit::init(cx);
248            assert!(super::base_initialized(cx));
249            super::init(cx);
250            super::init(cx);
251            assert!(cx.has_global::<super::Initialized>());
252        });
253    }
254
255    #[gpui_kit::test]
256    #[should_panic(expected = "needs gpui_base::init first")]
257    fn init_refuses_to_run_before_base(cx: &mut TestAppContext) {
258        cx.update(super::init);
259    }
260}