gpui: Add a standard text example (#30747)

This is a dumb first pass at a standard text example. We'll use this to
start digging in to some text/scale rendering issues.

There will be a ton of follow-up features to this, but starting simple.

Release Notes:

- N/A
This commit is contained in:
Nate Butler 2025-05-16 17:35:44 +02:00 committed by GitHub
parent 9dabf491f0
commit e26620d1cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 606 additions and 362 deletions

View file

@ -7,11 +7,11 @@ use crate::notifications::collab_notification::CollabNotification;
pub struct CollabNotificationStory;
impl Render for CollabNotificationStory {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let window_container = |width, height| div().w(px(width)).h(px(height));
Story::container()
.child(Story::title_for::<CollabNotification>())
Story::container(cx)
.child(Story::title_for::<CollabNotification>(cx))
.child(
StorySection::new().child(StoryItem::new(
"Incoming Call Notification",

View file

@ -257,6 +257,10 @@ path = "examples/image/image.rs"
name = "input"
path = "examples/input.rs"
[[example]]
name = "on_window_close_quit"
path = "examples/on_window_close_quit.rs"
[[example]]
name = "opacity"
path = "examples/opacity.rs"
@ -277,6 +281,10 @@ path = "examples/shadow.rs"
name = "svg"
path = "examples/svg/svg.rs"
[[example]]
name = "text"
path = "examples/text.rs"
[[example]]
name = "text_wrapper"
path = "examples/text_wrapper.rs"
@ -288,7 +296,3 @@ path = "examples/uniform_list.rs"
[[example]]
name = "window_shadow"
path = "examples/window_shadow.rs"
[[example]]
name = "on_window_close_quit"
path = "examples/on_window_close_quit.rs"

View file

@ -0,0 +1,333 @@
use std::{
ops::{Deref, DerefMut},
sync::Arc,
};
use gpui::{
AbsoluteLength, App, Application, Context, DefiniteLength, ElementId, Global, Hsla, Menu,
SharedString, TextStyle, TitlebarOptions, Window, WindowBounds, WindowOptions, bounds,
colors::DefaultColors, div, point, prelude::*, px, relative, rgb, size,
};
use std::iter;
#[derive(Clone, Debug)]
pub struct TextContext {
font_size: f32,
line_height: f32,
type_scale: f32,
}
impl Default for TextContext {
fn default() -> Self {
TextContext {
font_size: 16.0,
line_height: 1.3,
type_scale: 1.33,
}
}
}
impl TextContext {
pub fn get_global(cx: &App) -> &Arc<TextContext> {
&cx.global::<GlobalTextContext>().0
}
}
#[derive(Clone, Debug)]
pub struct GlobalTextContext(pub Arc<TextContext>);
impl Deref for GlobalTextContext {
type Target = Arc<TextContext>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for GlobalTextContext {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Global for GlobalTextContext {}
pub trait ActiveTextContext {
fn text_context(&self) -> &Arc<TextContext>;
}
impl ActiveTextContext for App {
fn text_context(&self) -> &Arc<TextContext> {
&self.global::<GlobalTextContext>().0
}
}
#[derive(Clone, PartialEq)]
pub struct SpecimenTheme {
pub bg: Hsla,
pub fg: Hsla,
}
impl Default for SpecimenTheme {
fn default() -> Self {
Self {
bg: gpui::white(),
fg: gpui::black(),
}
}
}
impl SpecimenTheme {
pub fn invert(&self) -> Self {
Self {
bg: self.fg,
fg: self.bg,
}
}
}
#[derive(Debug, Clone, PartialEq, IntoElement)]
struct Specimen {
id: ElementId,
scale: f32,
text_style: Option<TextStyle>,
string: SharedString,
invert: bool,
}
impl Specimen {
pub fn new(id: usize) -> Self {
let string = SharedString::new_static("The quick brown fox jumps over the lazy dog");
let id_string = format!("specimen-{}", id);
let id = ElementId::Name(id_string.into());
Self {
id,
scale: 1.0,
text_style: None,
string,
invert: false,
}
}
pub fn invert(mut self) -> Self {
self.invert = !self.invert;
self
}
pub fn scale(mut self, scale: f32) -> Self {
self.scale = scale;
self
}
}
impl RenderOnce for Specimen {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let rem_size = window.rem_size();
let scale = self.scale;
let global_style = cx.text_context();
let style_override = self.text_style;
let mut font_size = global_style.font_size;
let mut line_height = global_style.line_height;
if let Some(style_override) = style_override {
font_size = style_override.font_size.to_pixels(rem_size).0;
line_height = match style_override.line_height {
DefiniteLength::Absolute(absolute_len) => match absolute_len {
AbsoluteLength::Rems(absolute_len) => absolute_len.to_pixels(rem_size).0,
AbsoluteLength::Pixels(absolute_len) => absolute_len.0,
},
DefiniteLength::Fraction(value) => value,
};
}
let mut theme = SpecimenTheme::default();
if self.invert {
theme = theme.invert();
}
div()
.id(self.id)
.bg(theme.bg)
.text_color(theme.fg)
.text_size(px(font_size * scale))
.line_height(relative(line_height))
.p(px(10.0))
.child(self.string.clone())
}
}
#[derive(Debug, Clone, PartialEq, IntoElement)]
struct CharacterGrid {
scale: f32,
invert: bool,
text_style: Option<TextStyle>,
}
impl CharacterGrid {
pub fn new() -> Self {
Self {
scale: 1.0,
invert: false,
text_style: None,
}
}
pub fn scale(mut self, scale: f32) -> Self {
self.scale = scale;
self
}
}
impl RenderOnce for CharacterGrid {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let mut theme = SpecimenTheme::default();
if self.invert {
theme = theme.invert();
}
let characters = vec![
"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "A", "B", "C", "D", "E", "F", "G",
"H", "I", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y",
"Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "p", "q",
"r", "s", "t", "u", "v", "w", "x", "y", "z", "", "ſ", "ß", "ð", "Þ", "þ", "α", "β",
"Γ", "γ", "Δ", "δ", "η", "θ", "ι", "κ", "Λ", "λ", "μ", "ν", "ξ", "π", "τ", "υ", "φ",
"χ", "ψ", "", "а", "в", "Ж", "ж", "З", "з", "К", "к", "л", "м", "Н", "н", "Р", "р",
"У", "у", "ф", "ч", "ь", "ы", "Э", "э", "Я", "я", "ij", "öẋ", ".,", "⣝⣑", "~", "*",
"_", "^", "`", "'", "(", "{", "«", "#", "&", "@", "$", "¢", "%", "|", "?", "", "µ",
"", "<=", "!=", "==", "--", "++", "=>", "->",
];
let columns = 11;
let rows = characters.len().div_ceil(columns);
let grid_rows = (0..rows).map(|row_idx| {
let start_idx = row_idx * columns;
let end_idx = (start_idx + columns).min(characters.len());
div()
.w_full()
.flex()
.flex_row()
.children((start_idx..end_idx).map(|i| {
div()
.text_center()
.size(px(62.))
.bg(theme.bg)
.text_color(theme.fg)
.text_size(px(24.0))
.line_height(relative(1.0))
.child(characters[i])
}))
.when(end_idx - start_idx < columns, |d| {
d.children(
iter::repeat_with(|| div().flex_1()).take(columns - (end_idx - start_idx)),
)
})
});
div().p_4().gap_2().flex().flex_col().children(grid_rows)
}
}
struct TextExample {
next_id: usize,
}
impl TextExample {
fn next_id(&mut self) -> usize {
self.next_id += 1;
self.next_id
}
}
impl Render for TextExample {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let tcx = cx.text_context();
let colors = cx.default_colors().clone();
let type_scale = tcx.type_scale;
let step_down_2 = 1.0 / (type_scale * type_scale);
let step_down_1 = 1.0 / type_scale;
let base = 1.0;
let step_up_1 = base * type_scale;
let step_up_2 = step_up_1 * type_scale;
let step_up_3 = step_up_2 * type_scale;
let step_up_4 = step_up_3 * type_scale;
let step_up_5 = step_up_4 * type_scale;
let step_up_6 = step_up_5 * type_scale;
div()
.size_full()
.child(
div()
.id("text-example")
.overflow_y_scroll()
.overflow_x_hidden()
.bg(rgb(0xffffff))
.size_full()
.child(div().child(CharacterGrid::new().scale(base)))
.child(
div()
.child(Specimen::new(self.next_id()).scale(step_down_2))
.child(Specimen::new(self.next_id()).scale(step_down_2).invert())
.child(Specimen::new(self.next_id()).scale(step_down_1))
.child(Specimen::new(self.next_id()).scale(step_down_1).invert())
.child(Specimen::new(self.next_id()).scale(base))
.child(Specimen::new(self.next_id()).scale(base).invert())
.child(Specimen::new(self.next_id()).scale(step_up_1))
.child(Specimen::new(self.next_id()).scale(step_up_1).invert())
.child(Specimen::new(self.next_id()).scale(step_up_2))
.child(Specimen::new(self.next_id()).scale(step_up_2).invert())
.child(Specimen::new(self.next_id()).scale(step_up_3))
.child(Specimen::new(self.next_id()).scale(step_up_3).invert())
.child(Specimen::new(self.next_id()).scale(step_up_4))
.child(Specimen::new(self.next_id()).scale(step_up_4).invert())
.child(Specimen::new(self.next_id()).scale(step_up_5))
.child(Specimen::new(self.next_id()).scale(step_up_5).invert())
.child(Specimen::new(self.next_id()).scale(step_up_6))
.child(Specimen::new(self.next_id()).scale(step_up_6).invert()),
),
)
.child(div().w(px(240.)).h_full().bg(colors.container))
}
}
fn main() {
Application::new().run(|cx: &mut App| {
cx.set_menus(vec![Menu {
name: "GPUI Typography".into(),
items: vec![],
}]);
cx.init_colors();
cx.set_global(GlobalTextContext(Arc::new(TextContext::default())));
let window = cx
.open_window(
WindowOptions {
titlebar: Some(TitlebarOptions {
title: Some("GPUI Typography".into()),
..Default::default()
}),
window_bounds: Some(WindowBounds::Windowed(bounds(
point(px(0.0), px(0.0)),
size(px(920.), px(720.)),
))),
..Default::default()
},
|_window, cx| cx.new(|_cx| TextExample { next_id: 0 }),
)
.unwrap();
window
.update(cx, |_view, _window, cx| {
cx.activate(true);
})
.unwrap();
});
}

View file

@ -38,7 +38,9 @@ use crate::{
PlatformDisplay, PlatformKeyboardLayout, Point, PromptBuilder, PromptHandle, PromptLevel,
Render, RenderImage, RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString,
SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance,
WindowHandle, WindowId, WindowInvalidator, current_platform, hash, init_app_menus,
WindowHandle, WindowId, WindowInvalidator,
colors::{Colors, GlobalColors},
current_platform, hash, init_app_menus,
};
mod async_context;
@ -1656,6 +1658,13 @@ impl App {
_ = window.drop_image(image);
}
}
/// Initializes gpui's default colors for the application.
///
/// These colors can be accessed through `cx.default_colors()`.
pub fn init_colors(&mut self) {
self.set_global(GlobalColors(Arc::new(Colors::default())));
}
}
impl AppContext for App {

122
crates/gpui/src/colors.rs Normal file
View file

@ -0,0 +1,122 @@
use crate::{App, Global, Rgba, Window, WindowAppearance, rgb};
use std::ops::Deref;
use std::sync::Arc;
/// The default set of colors for gpui.
///
/// These are used for styling base components, examples and more.
#[derive(Clone, Debug)]
pub struct Colors {
/// Text color
pub text: Rgba,
/// Selected text color
pub selected_text: Rgba,
/// Background color
pub background: Rgba,
/// Disabled color
pub disabled: Rgba,
/// Selected color
pub selected: Rgba,
/// Border color
pub border: Rgba,
/// Separator color
pub separator: Rgba,
/// Container color
pub container: Rgba,
}
impl Default for Colors {
fn default() -> Self {
Self::light()
}
}
impl Colors {
/// Returns the default colors for the given window appearance.
pub fn for_appearance(window: &Window) -> Self {
match window.appearance() {
WindowAppearance::Light | WindowAppearance::VibrantLight => Self::light(),
WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::dark(),
}
}
/// Returns the default dark colors.
pub fn dark() -> Self {
Self {
text: rgb(0xffffff),
selected_text: rgb(0xffffff),
disabled: rgb(0x565656),
selected: rgb(0x2457ca),
background: rgb(0x222222),
border: rgb(0x000000),
separator: rgb(0xd9d9d9),
container: rgb(0x262626),
}
}
/// Returns the default light colors.
pub fn light() -> Self {
Self {
text: rgb(0x252525),
selected_text: rgb(0xffffff),
background: rgb(0xffffff),
disabled: rgb(0xb0b0b0),
selected: rgb(0x2a63d9),
border: rgb(0xd9d9d9),
separator: rgb(0xe6e6e6),
container: rgb(0xf4f5f5),
}
}
/// Get [Colors] from the global state
pub fn get_global(cx: &App) -> &Arc<Colors> {
&cx.global::<GlobalColors>().0
}
}
/// Get [Colors] from the global state
#[derive(Clone, Debug)]
pub struct GlobalColors(pub Arc<Colors>);
impl Deref for GlobalColors {
type Target = Arc<Colors>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Global for GlobalColors {}
/// Implement this trait to allow global [Color] access via `cx.default_colors()`.
pub trait DefaultColors {
/// Returns the default [`gpui::Colors`]
fn default_colors(&self) -> &Arc<Colors>;
}
impl DefaultColors for App {
fn default_colors(&self) -> &Arc<Colors> {
&self.global::<GlobalColors>().0
}
}
/// The appearance of the base GPUI colors, used to style GPUI elements
///
/// Varies based on the system's current [`WindowAppearance`].
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum DefaultAppearance {
/// Use the set of colors for light appearances.
#[default]
Light,
/// Use the set of colors for dark appearances.
Dark,
}
impl From<WindowAppearance> for DefaultAppearance {
fn from(appearance: WindowAppearance) -> Self {
match appearance {
WindowAppearance::Light | WindowAppearance::VibrantLight => Self::Light,
WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::Dark,
}
}
}

View file

@ -1,115 +0,0 @@
use crate::{Hsla, Rgba, WindowAppearance, rgb};
/// The appearance of the base GPUI colors, used to style GPUI elements
///
/// Varies based on the system's current [`WindowAppearance`].
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum DefaultThemeAppearance {
/// Use the set of colors for light appearances.
#[default]
Light,
/// Use the set of colors for dark appearances.
Dark,
}
impl From<WindowAppearance> for DefaultThemeAppearance {
fn from(appearance: WindowAppearance) -> Self {
match appearance {
WindowAppearance::Light | WindowAppearance::VibrantLight => Self::Light,
WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::Dark,
}
}
}
/// Returns the default colors for the given appearance.
pub fn colors(appearance: DefaultThemeAppearance) -> DefaultColors {
match appearance {
DefaultThemeAppearance::Light => DefaultColors::light(),
DefaultThemeAppearance::Dark => DefaultColors::dark(),
}
}
/// A collection of colors.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DefaultColors {
text: Rgba,
selected_text: Rgba,
background: Rgba,
disabled: Rgba,
selected: Rgba,
border: Rgba,
separator: Rgba,
container: Rgba,
}
impl DefaultColors {
/// Returns the default dark colors.
pub fn dark() -> Self {
Self {
text: rgb(0xffffff),
selected_text: rgb(0xffffff),
disabled: rgb(0x565656),
selected: rgb(0x2457ca),
background: rgb(0x222222),
border: rgb(0x000000),
separator: rgb(0xd9d9d9),
container: rgb(0x262626),
}
}
/// Returns the default light colors.
pub fn light() -> Self {
Self {
text: rgb(0x252525),
selected_text: rgb(0xffffff),
background: rgb(0xffffff),
disabled: rgb(0xb0b0b0),
selected: rgb(0x2a63d9),
border: rgb(0xd9d9d9),
separator: rgb(0xe6e6e6),
container: rgb(0xf4f5f5),
}
}
}
/// A default GPUI color.
#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
pub enum DefaultColor {
/// Text color
Text,
/// Selected text color
SelectedText,
/// Background color
Background,
/// Disabled color
Disabled,
/// Selected color
Selected,
/// Border color
Border,
/// Separator color
Separator,
/// Container color
Container,
}
impl DefaultColor {
/// Returns the RGBA color for the given color type.
pub fn color(&self, colors: &DefaultColors) -> Rgba {
match self {
DefaultColor::Text => colors.text,
DefaultColor::SelectedText => colors.selected_text,
DefaultColor::Background => colors.background,
DefaultColor::Disabled => colors.disabled,
DefaultColor::Selected => colors.selected,
DefaultColor::Border => colors.border,
DefaultColor::Separator => colors.separator,
DefaultColor::Container => colors.container,
}
}
/// Returns the HSLA color for the given color type.
pub fn hsla(&self, colors: &DefaultColors) -> Hsla {
self.color(colors).into()
}
}

View file

@ -1,7 +1,6 @@
mod anchored;
mod animation;
mod canvas;
mod common;
mod deferred;
mod div;
mod image_cache;
@ -15,7 +14,6 @@ mod uniform_list;
pub use anchored::*;
pub use animation::*;
pub use canvas::*;
pub use common::*;
pub use deferred::*;
pub use div::*;
pub use image_cache::*;

View file

@ -73,6 +73,8 @@ mod asset_cache;
mod assets;
mod bounds_tree;
mod color;
/// The default colors used by GPUI.
pub mod colors;
mod element;
mod elements;
mod executor;

View file

@ -1,6 +1,5 @@
use gpui::{
AnyElement, App, DefaultColor, DefaultColors, Div, SharedString, Window, div, prelude::*, px,
rems,
AnyElement, App, Div, SharedString, Window, colors::DefaultColors, div, prelude::*, px, rems,
};
use itertools::Itertools;
use smallvec::SmallVec;
@ -8,9 +7,7 @@ use smallvec::SmallVec;
pub struct Story {}
impl Story {
pub fn container() -> gpui::Stateful<Div> {
let colors = DefaultColors::light();
pub fn container(cx: &App) -> gpui::Stateful<Div> {
div()
.id("story_container")
.overflow_y_scroll()
@ -18,84 +15,66 @@ impl Story {
.min_h_full()
.flex()
.flex_col()
.text_color(DefaultColor::Text.hsla(&colors))
.bg(DefaultColor::Background.hsla(&colors))
.text_color(cx.default_colors().text)
.bg(cx.default_colors().background)
}
pub fn title(title: impl Into<SharedString>) -> impl Element {
let colors = DefaultColors::light();
pub fn title(title: impl Into<SharedString>, cx: &App) -> impl Element {
div()
.text_xs()
.text_color(DefaultColor::Text.hsla(&colors))
.text_color(cx.default_colors().text)
.child(title.into())
}
pub fn title_for<T>() -> impl Element {
Self::title(std::any::type_name::<T>())
pub fn title_for<T>(cx: &App) -> impl Element {
Self::title(std::any::type_name::<T>(), cx)
}
pub fn section() -> Div {
let colors = DefaultColors::light();
pub fn section(cx: &App) -> Div {
div()
.p_4()
.m_4()
.border_1()
.border_color(DefaultColor::Separator.hsla(&colors))
.border_color(cx.default_colors().separator)
}
pub fn section_title() -> Div {
let colors = DefaultColors::light();
div().text_lg().text_color(DefaultColor::Text.hsla(&colors))
pub fn section_title(cx: &App) -> Div {
div().text_lg().text_color(cx.default_colors().text)
}
pub fn group() -> Div {
let colors = DefaultColors::light();
div().my_2().bg(DefaultColor::Container.hsla(&colors))
pub fn group(cx: &App) -> Div {
div().my_2().bg(cx.default_colors().container)
}
pub fn code_block(code: impl Into<SharedString>) -> Div {
let colors = DefaultColors::light();
pub fn code_block(code: impl Into<SharedString>, cx: &App) -> Div {
div()
.size_full()
.p_2()
.max_w(rems(36.))
.bg(DefaultColor::Container.hsla(&colors))
.bg(cx.default_colors().container)
.rounded_sm()
.text_sm()
.text_color(DefaultColor::Text.hsla(&colors))
.text_color(cx.default_colors().text)
.overflow_hidden()
.child(code.into())
}
pub fn divider() -> Div {
let colors = DefaultColors::light();
div()
.my_2()
.h(px(1.))
.bg(DefaultColor::Separator.hsla(&colors))
pub fn divider(cx: &App) -> Div {
div().my_2().h(px(1.)).bg(cx.default_colors().separator)
}
pub fn description(description: impl Into<SharedString>) -> impl Element {
let colors = DefaultColors::light();
pub fn description(description: impl Into<SharedString>, cx: &App) -> impl Element {
div()
.text_sm()
.text_color(DefaultColor::Text.hsla(&colors))
.text_color(cx.default_colors().text)
.min_w_96()
.child(description.into())
}
pub fn label(label: impl Into<SharedString>) -> impl Element {
let colors = DefaultColors::light();
pub fn label(label: impl Into<SharedString>, cx: &App) -> impl Element {
div()
.text_xs()
.text_color(DefaultColor::Text.hsla(&colors))
.text_color(cx.default_colors().text)
.child(label.into())
}
@ -135,8 +114,8 @@ impl StoryItem {
}
impl RenderOnce for StoryItem {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let colors = DefaultColors::light();
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let colors = cx.default_colors();
div()
.my_2()
@ -148,20 +127,20 @@ impl RenderOnce for StoryItem {
.px_2()
.w_1_2()
.min_h_px()
.child(Story::label(self.label))
.child(Story::label(self.label, cx))
.child(
div()
.rounded_sm()
.bg(DefaultColor::Background.hsla(&colors))
.bg(colors.background)
.border_1()
.border_color(DefaultColor::Border.hsla(&colors))
.border_color(colors.border)
.py_1()
.px_2()
.overflow_hidden()
.child(self.item),
)
.when_some(self.description, |this, description| {
this.child(Story::description(description))
this.child(Story::description(description, cx))
}),
)
.child(
@ -171,8 +150,8 @@ impl RenderOnce for StoryItem {
.w_1_2()
.min_h_px()
.when_some(self.usage, |this, usage| {
this.child(Story::label("Example Usage"))
.child(Story::code_block(usage))
this.child(Story::label("Example Usage", cx))
.child(Story::code_block(usage, cx))
}),
)
}
@ -205,21 +184,21 @@ impl StorySection {
}
impl RenderOnce for StorySection {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let children: SmallVec<[AnyElement; 2]> = SmallVec::from_iter(Itertools::intersperse_with(
self.children.into_iter(),
|| Story::divider().into_any_element(),
|| Story::divider(cx).into_any_element(),
));
Story::section()
Story::section(cx)
// Section title
.py_2()
// Section description
.when_some(self.description.clone(), |section, description| {
section.child(Story::description(description))
section.child(Story::description(description, cx))
})
.child(div().flex().flex_col().gap_2().children(children))
.child(Story::divider())
.child(Story::divider(cx))
}
}

View file

@ -1,6 +1,5 @@
mod auto_height_editor;
mod cursor;
mod default_colors;
mod focus;
mod kitchen_sink;
mod overflow_scroll;
@ -12,7 +11,6 @@ mod with_rem_size;
pub use auto_height_editor::*;
pub use cursor::*;
pub use default_colors::*;
pub use focus::*;
pub use kitchen_sink::*;
pub use overflow_scroll::*;

View file

@ -5,7 +5,7 @@ use ui::prelude::*;
pub struct CursorStory;
impl Render for CursorStory {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let all_cursors: [(&str, Box<dyn Fn(Stateful<Div>) -> Stateful<Div>>); 19] = [
(
"cursor_default",
@ -85,10 +85,10 @@ impl Render for CursorStory {
),
];
Story::container()
Story::container(cx)
.flex()
.gap_1()
.child(Story::title("cursor"))
.child(Story::title("cursor", cx))
.children(all_cursors.map(|(name, apply_cursor)| {
div().gap_1().flex().text_color(gpui::white()).child(
div()
@ -102,7 +102,7 @@ impl Render for CursorStory {
.bg(gpui::red())
.active(|style| style.bg(gpui::green()))
.text_sm()
.child(Story::label(name)),
.child(Story::label(name, cx)),
)
}))
}

View file

@ -1,89 +0,0 @@
use gpui::{
App, Context, DefaultColor, DefaultThemeAppearance, Entity, Hsla, Render, Window, colors, div,
prelude::*,
};
use story::Story;
use strum::IntoEnumIterator;
use ui::{ActiveTheme, h_flex};
pub struct DefaultColorsStory;
impl DefaultColorsStory {
pub fn model(cx: &mut App) -> Entity<Self> {
cx.new(|_| Self)
}
}
impl Render for DefaultColorsStory {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let appearances = [DefaultThemeAppearance::Light, DefaultThemeAppearance::Dark];
Story::container()
.child(Story::title("Default Colors"))
.children(appearances.iter().map(|&appearance| {
let colors = colors(appearance);
let color_types = DefaultColor::iter()
.map(|color| {
let name = format!("{:?}", color);
let rgba = color.hsla(&colors);
(name, rgba)
})
.collect::<Vec<_>>();
div()
.flex()
.flex_col()
.gap_4()
.p_4()
.child(Story::label(format!("{:?} Appearance", appearance)))
.children(color_types.iter().map(|(name, color)| {
let color: Hsla = *color;
div()
.flex()
.items_center()
.gap_2()
.child(
div()
.w_12()
.h_12()
.bg(color)
.border_1()
.border_color(cx.theme().colors().border),
)
.child(Story::label(format!("{}: {:?}", name, color.clone())))
}))
.child(
h_flex()
.gap_1()
.child(
h_flex()
.bg(DefaultColor::Background.hsla(&colors))
.h_8()
.p_2()
.text_sm()
.text_color(DefaultColor::Text.hsla(&colors))
.child("Default Text"),
)
.child(
h_flex()
.bg(DefaultColor::Container.hsla(&colors))
.h_8()
.p_2()
.text_sm()
.text_color(DefaultColor::Text.hsla(&colors))
.child("Text on Container"),
)
.child(
h_flex()
.bg(DefaultColor::Selected.hsla(&colors))
.h_8()
.p_2()
.text_sm()
.text_color(DefaultColor::SelectedText.hsla(&colors))
.child("Selected Text"),
),
)
}))
}
}

View file

@ -1,12 +1,12 @@
use std::fmt::format;
use gpui::{
colors, div, prelude::*, uniform_list, DefaultColor, DefaultThemeAppearance, Hsla, Render,
DefaultColor, DefaultThemeAppearance, Hsla, Render, colors, div, prelude::*, uniform_list,
};
use story::Story;
use strum::IntoEnumIterator;
use ui::{
h_flex, px, v_flex, AbsoluteLength, ActiveTheme, Color, DefiniteLength, Label, LabelCommon,
AbsoluteLength, ActiveTheme, Color, DefiniteLength, Label, LabelCommon, h_flex, px, v_flex,
};
const LENGTH: usize = 100;
@ -34,7 +34,7 @@ impl IndentGuidesStory {
impl Render for IndentGuidesStory {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
Story::container()
Story::container(cx)
.child(Story::title("Indent guides"))
.child(
v_flex().size_full().child(

View file

@ -19,11 +19,11 @@ impl Render for KitchenSinkStory {
.map(|selector| selector.story(window, cx))
.collect::<Vec<_>>();
Story::container()
Story::container(cx)
.id("kitchen-sink")
.overflow_y_scroll()
.child(Story::title("Kitchen Sink"))
.child(Story::label("Components"))
.child(Story::title("Kitchen Sink", cx))
.child(Story::label("Components", cx))
.child(div().flex().flex_col().children(component_stories))
// Add a bit of space at the bottom of the kitchen sink so elements
// don't end up squished right up against the bottom of the screen.

View file

@ -6,10 +6,10 @@ use ui::prelude::*;
pub struct OverflowScrollStory;
impl Render for OverflowScrollStory {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
Story::container()
.child(Story::title("Overflow Scroll"))
.child(Story::label("`overflow_x_scroll`"))
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
Story::container(cx)
.child(Story::title("Overflow Scroll", cx))
.child(Story::label("`overflow_x_scroll`", cx))
.child(
h_flex()
.id("overflow_x_scroll")
@ -22,7 +22,7 @@ impl Render for OverflowScrollStory {
.child(SharedString::from(format!("Child {}", i + 1)))
})),
)
.child(Story::label("`overflow_y_scroll`"))
.child(Story::label("`overflow_y_scroll`", cx))
.child(
v_flex()
.w_full()

View file

@ -14,9 +14,9 @@ impl TextStory {
}
impl Render for TextStory {
fn render(&mut self, window: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
Story::container()
.child(Story::title("Text"))
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
Story::container(cx)
.child(Story::title("Text", cx))
.children(vec![
StorySection::new()
.child(

View file

@ -6,8 +6,8 @@ use ui::prelude::*;
pub struct ViewportUnitsStory;
impl Render for ViewportUnitsStory {
fn render(&mut self, window: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
Story::container().child(
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
Story::container(cx).child(
div()
.flex()
.flex_row()

View file

@ -6,8 +6,8 @@ use ui::{prelude::*, utils::WithRemSize};
pub struct WithRemSizeStory;
impl Render for WithRemSizeStory {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
Story::container().child(
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
Story::container(cx).child(
Example::new(16., gpui::red())
.child(
Example::new(24., gpui::green())

View file

@ -17,7 +17,6 @@ pub enum ComponentStory {
CollabNotification,
ContextMenu,
Cursor,
DefaultColors,
Focus,
IconButton,
Keybinding,
@ -47,7 +46,6 @@ impl ComponentStory {
.into(),
Self::ContextMenu => cx.new(|_| ui::ContextMenuStory).into(),
Self::Cursor => cx.new(|_| crate::stories::CursorStory).into(),
Self::DefaultColors => DefaultColorsStory::model(cx).into(),
Self::Focus => FocusStory::model(window, cx).into(),
Self::IconButton => cx.new(|_| ui::IconButtonStory).into(),
Self::Keybinding => cx.new(|_| ui::KeybindingStory).into(),

View file

@ -18,9 +18,9 @@ impl ApplicationMenuStory {
}
impl Render for ApplicationMenuStory {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
Story::container()
.child(Story::title_for::<ApplicationMenu>())
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
Story::container(cx)
.child(Story::title_for::<ApplicationMenu>(cx))
.child(StorySection::new().child(StoryItem::new(
"Application Menu",
h_flex().child(self.menu.clone()),

View file

@ -26,8 +26,8 @@ fn build_menu(
pub struct ContextMenuStory;
impl Render for ContextMenuStory {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
Story::container()
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
Story::container(cx)
.on_action(|_: &PrintCurrentDate, _, _| {
println!("printing unix time!");
if let Ok(unix_time) = std::time::UNIX_EPOCH.elapsed() {

View file

@ -7,9 +7,9 @@ use crate::prelude::*;
pub struct DisclosureStory;
impl Render for DisclosureStory {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
Story::container()
.child(Story::title_for::<Disclosure>())
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
Story::container(cx)
.child(Story::title_for::<Disclosure>(cx))
.child(Story::label("Toggled"))
.child(Disclosure::new("toggled", true))
.child(Story::label("Not Toggled"))

View file

@ -7,7 +7,7 @@ use crate::{IconButtonShape, Tooltip, prelude::*};
pub struct IconButtonStory;
impl Render for IconButtonStory {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let default_button = StoryItem::new(
"Default",
IconButton::new("default_icon_button", IconName::Hash),
@ -113,8 +113,8 @@ impl Render for IconButtonStory {
selected_with_tooltip_button,
];
Story::container()
.child(Story::title_for::<IconButton>())
Story::container(cx)
.child(Story::title_for::<IconButton>(cx))
.child(StorySection::new().children(buttons))
.child(
StorySection::new().child(StoryItem::new(

View file

@ -15,11 +15,11 @@ impl Render for KeybindingStory {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let all_modifier_permutations = ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
Story::container()
.child(Story::title_for::<KeyBinding>())
.child(Story::label("Single Key"))
Story::container(cx)
.child(Story::title_for::<KeyBinding>(cx))
.child(Story::label("Single Key", cx))
.child(KeyBinding::new(binding("Z"), cx))
.child(Story::label("Single Key with Modifier"))
.child(Story::label("Single Key with Modifier", cx))
.child(
div()
.flex()
@ -29,7 +29,7 @@ impl Render for KeybindingStory {
.child(KeyBinding::new(binding("cmd-c"), cx))
.child(KeyBinding::new(binding("shift-c"), cx)),
)
.child(Story::label("Single Key with Modifier (Permuted)"))
.child(Story::label("Single Key with Modifier (Permuted)", cx))
.child(
div().flex().flex_col().children(
all_modifier_permutations
@ -46,33 +46,33 @@ impl Render for KeybindingStory {
}),
),
)
.child(Story::label("Single Key with All Modifiers"))
.child(Story::label("Single Key with All Modifiers", cx))
.child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z"), cx))
.child(Story::label("Chord"))
.child(Story::label("Chord", cx))
.child(KeyBinding::new(binding("a z"), cx))
.child(Story::label("Chord with Modifier"))
.child(Story::label("Chord with Modifier", cx))
.child(KeyBinding::new(binding("ctrl-a shift-z"), cx))
.child(KeyBinding::new(binding("fn-s"), cx))
.child(Story::label("Single Key with All Modifiers (Linux)"))
.child(Story::label("Single Key with All Modifiers (Linux)", cx))
.child(
KeyBinding::new(binding("ctrl-alt-cmd-shift-z"), cx)
.platform_style(PlatformStyle::Linux),
)
.child(Story::label("Chord (Linux)"))
.child(Story::label("Chord (Linux)", cx))
.child(KeyBinding::new(binding("a z"), cx).platform_style(PlatformStyle::Linux))
.child(Story::label("Chord with Modifier (Linux)"))
.child(Story::label("Chord with Modifier (Linux)", cx))
.child(
KeyBinding::new(binding("ctrl-a shift-z"), cx).platform_style(PlatformStyle::Linux),
)
.child(KeyBinding::new(binding("fn-s"), cx).platform_style(PlatformStyle::Linux))
.child(Story::label("Single Key with All Modifiers (Windows)"))
.child(Story::label("Single Key with All Modifiers (Windows)", cx))
.child(
KeyBinding::new(binding("ctrl-alt-cmd-shift-z"), cx)
.platform_style(PlatformStyle::Windows),
)
.child(Story::label("Chord (Windows)"))
.child(Story::label("Chord (Windows)", cx))
.child(KeyBinding::new(binding("a z"), cx).platform_style(PlatformStyle::Windows))
.child(Story::label("Chord with Modifier (Windows)"))
.child(Story::label("Chord with Modifier (Windows)", cx))
.child(
KeyBinding::new(binding("ctrl-a shift-z"), cx)
.platform_style(PlatformStyle::Windows),

View file

@ -7,17 +7,17 @@ use crate::{ListHeader, ListSeparator, ListSubHeader, prelude::*};
pub struct ListStory;
impl Render for ListStory {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
Story::container()
.child(Story::title_for::<List>())
.child(Story::label("Default"))
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
Story::container(cx)
.child(Story::title_for::<List>(cx))
.child(Story::label("Default", cx))
.child(
List::new()
.child(ListItem::new("apple").child("Apple"))
.child(ListItem::new("banana").child("Banana"))
.child(ListItem::new("cherry").child("Cherry")),
)
.child(Story::label("With sections"))
.child(Story::label("With sections", cx))
.child(
List::new()
.header(ListHeader::new("Produce"))

View file

@ -7,20 +7,20 @@ use crate::{IconName, ListHeader};
pub struct ListHeaderStory;
impl Render for ListHeaderStory {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
Story::container()
.child(Story::title_for::<ListHeader>())
.child(Story::label("Default"))
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
Story::container(cx)
.child(Story::title_for::<ListHeader>(cx))
.child(Story::label("Default", cx))
.child(ListHeader::new("Section 1"))
.child(Story::label("With left icon"))
.child(Story::label("With left icon", cx))
.child(ListHeader::new("Section 2").start_slot(Icon::new(IconName::Bell)))
.child(Story::label("With left icon and meta"))
.child(Story::label("With left icon and meta", cx))
.child(
ListHeader::new("Section 3")
.start_slot(Icon::new(IconName::BellOff))
.end_slot(IconButton::new("action_1", IconName::Bolt)),
)
.child(Story::label("With multiple meta"))
.child(Story::label("With multiple meta", cx))
.child(
ListHeader::new("Section 4")
.end_slot(IconButton::new("action_1", IconName::Bolt))

View file

@ -10,12 +10,12 @@ pub struct ListItemStory;
impl Render for ListItemStory {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
Story::container()
Story::container(cx)
.bg(cx.theme().colors().background)
.child(Story::title_for::<ListItem>())
.child(Story::label("Default"))
.child(Story::title_for::<ListItem>(cx))
.child(Story::label("Default", cx))
.child(ListItem::new("hello_world").child("Hello, world!"))
.child(Story::label("Inset"))
.child(Story::label("Inset", cx))
.child(
ListItem::new("inset_list_item")
.inset(true)
@ -31,7 +31,7 @@ impl Render for ListItemStory {
.color(Color::Muted),
),
)
.child(Story::label("With start slot icon"))
.child(Story::label("With start slot icon", cx))
.child(
ListItem::new("with start slot_icon")
.child("Hello, world!")
@ -41,7 +41,7 @@ impl Render for ListItemStory {
.color(Color::Muted),
),
)
.child(Story::label("With start slot avatar"))
.child(Story::label("With start slot avatar", cx))
.child(
ListItem::new("with_start slot avatar")
.child("Hello, world!")
@ -49,7 +49,7 @@ impl Render for ListItemStory {
"https://avatars.githubusercontent.com/u/1714999?v=4",
)),
)
.child(Story::label("With end slot"))
.child(Story::label("With end slot", cx))
.child(
ListItem::new("with_left_avatar")
.child("Hello, world!")
@ -57,7 +57,7 @@ impl Render for ListItemStory {
"https://avatars.githubusercontent.com/u/1714999?v=4",
)),
)
.child(Story::label("With end hover slot"))
.child(Story::label("With end hover slot", cx))
.child(
ListItem::new("with_end_hover_slot")
.child("Hello, world!")
@ -84,13 +84,13 @@ impl Render for ListItemStory {
"https://avatars.githubusercontent.com/u/1714999?v=4",
)),
)
.child(Story::label("With `on_click`"))
.child(Story::label("With `on_click`", cx))
.child(ListItem::new("with_on_click").child("Click me").on_click(
|_event, _window, _cx| {
println!("Clicked!");
},
))
.child(Story::label("With `on_secondary_mouse_down`"))
.child(Story::label("With `on_secondary_mouse_down`", cx))
.child(
ListItem::new("with_on_secondary_mouse_down")
.child("Right click me")
@ -98,7 +98,10 @@ impl Render for ListItemStory {
println!("Right mouse down!");
}),
)
.child(Story::label("With overflowing content in the `end_slot`"))
.child(Story::label(
"With overflowing content in the `end_slot`",
cx,
))
.child(
ListItem::new("with_overflowing_content_in_end_slot")
.child("An excerpt")
@ -106,6 +109,7 @@ impl Render for ListItemStory {
)
.child(Story::label(
"`inset` with overflowing content in the `end_slot`",
cx,
))
.child(
ListItem::new("inset_with_overflowing_content_in_end_slot")
@ -115,6 +119,7 @@ impl Render for ListItemStory {
)
.child(Story::label(
"`inset` with overflowing content in `children` and `end_slot`",
cx,
))
.child(
ListItem::new("inset_with_overflowing_content_in_children_and_end_slot")

View file

@ -9,12 +9,12 @@ use crate::{Indicator, Tab};
pub struct TabStory;
impl Render for TabStory {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
Story::container()
.child(Story::title_for::<Tab>())
.child(Story::label("Default"))
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
Story::container(cx)
.child(Story::title_for::<Tab>(cx))
.child(Story::label("Default", cx))
.child(h_flex().child(Tab::new("tab_1").child("Tab 1")))
.child(Story::label("With indicator"))
.child(Story::label("With indicator", cx))
.child(
h_flex().child(
Tab::new("tab_1")
@ -22,7 +22,7 @@ impl Render for TabStory {
.child("Tab 1"),
),
)
.child(Story::label("With close button"))
.child(Story::label("With close button", cx))
.child(
h_flex().child(
Tab::new("tab_1")
@ -37,13 +37,13 @@ impl Render for TabStory {
.child("Tab 1"),
),
)
.child(Story::label("List of tabs"))
.child(Story::label("List of tabs", cx))
.child(
h_flex()
.child(Tab::new("tab_1").child("Tab 1"))
.child(Tab::new("tab_2").child("Tab 2")),
)
.child(Story::label("List of tabs with first tab selected"))
.child(Story::label("List of tabs with first tab selected", cx))
.child(
h_flex()
.child(
@ -64,7 +64,7 @@ impl Render for TabStory {
)
.child(Tab::new("tab_4").position(TabPosition::Last).child("Tab 4")),
)
.child(Story::label("List of tabs with last tab selected"))
.child(Story::label("List of tabs with last tab selected", cx))
.child(
h_flex()
.child(
@ -89,7 +89,7 @@ impl Render for TabStory {
.child("Tab 4"),
),
)
.child(Story::label("List of tabs with second tab selected"))
.child(Story::label("List of tabs with second tab selected", cx))
.child(
h_flex()
.child(

View file

@ -6,7 +6,7 @@ use crate::{Tab, TabBar, TabPosition, prelude::*};
pub struct TabBarStory;
impl Render for TabBarStory {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let tab_count = 20;
let selected_tab_index = 3;
@ -31,9 +31,9 @@ impl Render for TabBarStory {
})
.collect::<Vec<_>>();
Story::container()
.child(Story::title_for::<TabBar>())
.child(Story::label("Default"))
Story::container(cx)
.child(Story::title_for::<TabBar>(cx))
.child(Story::label("Default", cx))
.child(
h_flex().child(
TabBar::new("tab_bar_1")

View file

@ -6,9 +6,9 @@ use crate::{ToggleButton, prelude::*};
pub struct ToggleButtonStory;
impl Render for ToggleButtonStory {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
Story::container()
.child(Story::title_for::<ToggleButton>())
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
Story::container(cx)
.child(Story::title_for::<ToggleButton>(cx))
.child(
StorySection::new().child(
StoryItem::new(