chore: revamp theme

This commit is contained in:
2025-05-07 14:12:31 +07:00
parent 97e66fbeb7
commit 2f83b5091e
57 changed files with 922 additions and 1494 deletions

View File

@@ -1,35 +1,23 @@
use crate::{
indicator::Indicator,
theme::{scale::ColorScaleStep, ActiveTheme},
tooltip::Tooltip,
Disableable, Icon, Selectable, Sizable, Size, StyledExt,
};
use gpui::{
div, prelude::FluentBuilder as _, px, relative, AnyElement, App, ClickEvent, Corners, Div,
Edges, ElementId, Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels,
RenderOnce, SharedString, StatefulInteractiveElement as _, Styled, Window,
div, prelude::FluentBuilder as _, relative, AnyElement, App, ClickEvent, Div, ElementId, Hsla,
InteractiveElement, IntoElement, MouseButton, ParentElement, RenderOnce, SharedString,
StatefulInteractiveElement as _, Styled, Window,
};
use theme::ActiveTheme;
use crate::{
indicator::Indicator, tooltip::Tooltip, Disableable, Icon, Selectable, Sizable, Size, StyledExt,
};
pub enum ButtonRounded {
None,
Small,
Medium,
Large,
Size(Pixels),
}
impl From<Pixels> for ButtonRounded {
fn from(px: Pixels) -> Self {
ButtonRounded::Size(px)
}
Normal,
Full,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct ButtonCustomVariant {
color: Hsla,
foreground: Hsla,
border: Hsla,
shadow: bool,
hover: Hsla,
active: Hsla,
}
@@ -66,12 +54,10 @@ pub trait ButtonVariants: Sized {
impl ButtonCustomVariant {
pub fn new(_window: &Window, cx: &App) -> Self {
Self {
color: cx.theme().accent.step(cx, ColorScaleStep::NINE),
foreground: cx.theme().accent.step(cx, ColorScaleStep::ONE),
border: cx.theme().accent.step(cx, ColorScaleStep::TEN),
hover: cx.theme().accent.step(cx, ColorScaleStep::TEN),
active: cx.theme().accent.step(cx, ColorScaleStep::ELEVEN),
shadow: true,
color: cx.theme().element_background,
foreground: cx.theme().element_foreground,
hover: cx.theme().element_hover,
active: cx.theme().element_active,
}
}
@@ -85,11 +71,6 @@ impl ButtonCustomVariant {
self
}
pub fn border(mut self, color: Hsla) -> Self {
self.border = color;
self
}
pub fn hover(mut self, color: Hsla) -> Self {
self.hover = color;
self
@@ -99,11 +80,6 @@ impl ButtonCustomVariant {
self.active = color;
self
}
pub fn shadow(mut self, shadow: bool) -> Self {
self.shadow = shadow;
self
}
}
/// The variant of the Button.
@@ -149,12 +125,9 @@ pub struct Button {
disabled: bool,
variant: ButtonVariant,
rounded: ButtonRounded,
border_corners: Corners<bool>,
border_edges: Edges<bool>,
size: Size,
reverse: bool,
bold: bool,
centered: bool,
tooltip: Option<SharedString>,
on_click: OnClick,
loading: bool,
@@ -179,9 +152,7 @@ impl Button {
disabled: false,
selected: false,
variant: ButtonVariant::default(),
rounded: ButtonRounded::Medium,
border_corners: Corners::all(true),
border_edges: Edges::all(true),
rounded: ButtonRounded::Normal,
size: Size::Medium,
tooltip: None,
on_click: None,
@@ -189,7 +160,6 @@ impl Button {
loading: false,
reverse: false,
bold: false,
centered: true,
children: Vec::new(),
loading_icon: None,
}
@@ -201,18 +171,6 @@ impl Button {
self
}
/// Set the border corners side of the Button.
pub(crate) fn border_corners(mut self, corners: impl Into<Corners<bool>>) -> Self {
self.border_corners = corners.into();
self
}
/// Set the border edges of the Button.
pub(crate) fn border_edges(mut self, edges: impl Into<Edges<bool>>) -> Self {
self.border_edges = edges.into();
self
}
/// Set label to the Button, if no label is set, the button will be in Icon Button mode.
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
self.label = Some(label.into());
@@ -243,11 +201,6 @@ impl Button {
self
}
pub fn not_centered(mut self) -> Self {
self.centered = false;
self
}
pub fn bold(mut self) -> Self {
self.bold = true;
self
@@ -335,11 +288,12 @@ impl RenderOnce for Button {
.id(self.id)
.flex()
.items_center()
.when(self.centered, |this| this.justify_center())
.justify_center()
.cursor_pointer()
.overflow_hidden()
.when(cx.theme().shadow && normal_style.shadow, |this| {
this.shadow_sm()
.map(|this| match self.rounded {
ButtonRounded::Normal => this.rounded(cx.theme().radius),
ButtonRounded::Full => this.rounded_full(),
})
.when(!style.no_padding(), |this| {
if self.label.is_none() && self.children.is_empty() {
@@ -361,50 +315,20 @@ impl RenderOnce for Button {
}
}
})
.when(
self.border_corners.top_left && self.border_corners.bottom_left,
|this| match self.rounded {
ButtonRounded::Small => this.rounded_l(px(cx.theme().radius * 0.5)),
ButtonRounded::Medium => this.rounded_l(px(cx.theme().radius)),
ButtonRounded::Large => this.rounded_l(px(cx.theme().radius * 1.6)),
ButtonRounded::Size(px) => this.rounded_l(px),
ButtonRounded::None => this.rounded_none(),
},
)
.when(
self.border_corners.top_right && self.border_corners.bottom_right,
|this| match self.rounded {
ButtonRounded::Small => this.rounded_r(px(cx.theme().radius * 0.5)),
ButtonRounded::Medium => this.rounded_r(px(cx.theme().radius)),
ButtonRounded::Large => this.rounded_r(px(cx.theme().radius * 1.6)),
ButtonRounded::Size(px) => this.rounded_r(px),
ButtonRounded::None => this.rounded_none(),
},
)
.when(self.border_edges.left, |this| this.border_l_1())
.when(self.border_edges.right, |this| this.border_r_1())
.when(self.border_edges.top, |this| this.border_t_1())
.when(self.border_edges.bottom, |this| this.border_b_1())
.text_color(normal_style.fg)
.when(self.selected, |this| {
let selected_style = style.selected(window, cx);
this.bg(selected_style.bg)
.border_color(selected_style.border)
.text_color(selected_style.fg)
this.bg(selected_style.bg).text_color(selected_style.fg)
})
.when(!self.disabled && !self.selected, |this| {
this.border_color(normal_style.border)
.bg(normal_style.bg)
this.bg(normal_style.bg)
.when(normal_style.underline, |this| this.text_decoration_1())
.hover(|this| {
let hover_style = style.hovered(window, cx);
this.bg(hover_style.bg).border_color(hover_style.border)
this.bg(hover_style.bg)
})
.active(|this| {
let active_style = style.active(window, cx);
this.bg(active_style.bg)
.border_color(active_style.border)
.text_color(active_style.fg)
this.bg(active_style.bg).text_color(active_style.fg)
})
})
.when_some(
@@ -427,9 +351,9 @@ impl RenderOnce for Button {
this.cursor_not_allowed()
.bg(disabled_style.bg)
.text_color(disabled_style.fg)
.border_color(disabled_style.border)
.shadow_none()
})
.text_color(normal_style.fg)
.child({
div()
.flex()
@@ -474,45 +398,26 @@ impl RenderOnce for Button {
struct ButtonVariantStyle {
bg: Hsla,
border: Hsla,
fg: Hsla,
underline: bool,
shadow: bool,
}
impl ButtonVariant {
fn bg_color(&self, _window: &Window, cx: &App) -> Hsla {
match self {
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::NINE),
ButtonVariant::Primary => cx.theme().element_background,
ButtonVariant::Custom(colors) => colors.color,
_ => cx.theme().transparent,
_ => cx.theme().ghost_element_background,
}
}
fn text_color(&self, _window: &Window, cx: &App) -> Hsla {
match self {
ButtonVariant::Primary => match cx.theme().accent.name().to_string().as_str() {
"Sky" => cx.theme().base.darken(cx),
"Mint" => cx.theme().base.darken(cx),
"Lime" => cx.theme().base.darken(cx),
"Amber" => cx.theme().base.darken(cx),
"Yellow" => cx.theme().base.darken(cx),
_ => cx.theme().accent.step(cx, ColorScaleStep::ONE),
},
ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::NINE),
ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::TWELVE),
ButtonVariant::Primary => cx.theme().element_foreground,
ButtonVariant::Link => cx.theme().text_accent,
ButtonVariant::Ghost => cx.theme().text_muted,
ButtonVariant::Custom(colors) => colors.foreground,
_ => cx.theme().base.step(cx, ColorScaleStep::TWELVE),
}
}
fn border_color(&self, _window: &Window, cx: &App) -> Hsla {
match self {
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::NINE),
ButtonVariant::Ghost | ButtonVariant::Link | ButtonVariant::Text => {
cx.theme().transparent
}
ButtonVariant::Custom(colors) => colors.border,
_ => cx.theme().text,
}
}
@@ -520,142 +425,79 @@ impl ButtonVariant {
matches!(self, ButtonVariant::Link)
}
fn shadow(&self, _window: &Window, _cx: &App) -> bool {
match self {
ButtonVariant::Primary => true,
ButtonVariant::Custom(c) => c.shadow,
_ => false,
}
}
fn normal(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
let bg = self.bg_color(window, cx);
let border = self.border_color(window, cx);
let fg = self.text_color(window, cx);
let underline = self.underline(window, cx);
let shadow = self.shadow(window, cx);
ButtonVariantStyle {
bg,
border,
fg,
underline,
shadow,
}
ButtonVariantStyle { bg, fg, underline }
}
fn hovered(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
let bg = match self {
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN),
ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::FOUR),
ButtonVariant::Link => cx.theme().transparent,
ButtonVariant::Text => cx.theme().transparent,
ButtonVariant::Primary => cx.theme().element_hover,
ButtonVariant::Ghost => cx.theme().ghost_element_hover,
ButtonVariant::Link => cx.theme().ghost_element_background,
ButtonVariant::Text => cx.theme().ghost_element_background,
ButtonVariant::Custom(colors) => colors.hover,
};
let border = self.border_color(window, cx);
let fg = match self {
ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::TWELVE),
ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::TEN),
ButtonVariant::Ghost => cx.theme().text,
ButtonVariant::Link => cx.theme().text_accent,
_ => self.text_color(window, cx),
};
let underline = self.underline(window, cx);
let shadow = self.shadow(window, cx);
ButtonVariantStyle {
bg,
border,
fg,
underline,
shadow,
}
ButtonVariantStyle { bg, fg, underline }
}
fn active(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
let bg = match self {
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN),
ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::THREE),
ButtonVariant::Link => cx.theme().transparent,
ButtonVariant::Text => cx.theme().transparent,
ButtonVariant::Primary => cx.theme().element_active,
ButtonVariant::Ghost => cx.theme().ghost_element_active,
ButtonVariant::Custom(colors) => colors.active,
_ => cx.theme().ghost_element_background,
};
let fg = match self {
ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::NINE),
ButtonVariant::Text => cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
ButtonVariant::Link => cx.theme().text_accent,
ButtonVariant::Text => cx.theme().text,
_ => self.text_color(window, cx),
};
let border = self.border_color(window, cx);
let underline = self.underline(window, cx);
let shadow = self.shadow(window, cx);
ButtonVariantStyle {
bg,
border,
fg,
underline,
shadow,
}
ButtonVariantStyle { bg, fg, underline }
}
fn selected(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
let bg = match self {
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN),
ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::THREE),
ButtonVariant::Link => cx.theme().transparent,
ButtonVariant::Text => cx.theme().transparent,
ButtonVariant::Primary => cx.theme().element_selected,
ButtonVariant::Ghost => cx.theme().ghost_element_selected,
ButtonVariant::Custom(colors) => colors.active,
_ => cx.theme().ghost_element_background,
};
let fg = match self {
ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::TEN),
ButtonVariant::Text => cx.theme().accent.step(cx, ColorScaleStep::TEN),
ButtonVariant::Link => cx.theme().text_accent,
ButtonVariant::Text => cx.theme().text,
_ => self.text_color(window, cx),
};
let border = self.border_color(window, cx);
let underline = self.underline(window, cx);
let shadow = self.shadow(window, cx);
ButtonVariantStyle {
bg,
border,
fg,
underline,
shadow,
}
ButtonVariantStyle { bg, fg, underline }
}
fn disabled(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
let bg = match self {
ButtonVariant::Link | ButtonVariant::Ghost | ButtonVariant::Text => {
cx.theme().transparent
cx.theme().ghost_element_disabled
}
_ => cx.theme().base.step(cx, ColorScaleStep::THREE),
_ => cx.theme().element_disabled,
};
let fg = match self {
ButtonVariant::Primary => match cx.theme().accent.name().to_string().as_str() {
"Sky" => cx.theme().base.darken(cx),
"Mint" => cx.theme().base.darken(cx),
"Lime" => cx.theme().base.darken(cx),
"Amber" => cx.theme().base.darken(cx),
"Yellow" => cx.theme().base.darken(cx),
_ => cx.theme().accent.step(cx, ColorScaleStep::ONE),
},
_ => cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
ButtonVariant::Primary => cx.theme().text_muted, // TODO: use a different color?
_ => cx.theme().text_muted,
};
let border = bg;
let underline = self.underline(window, cx);
let shadow = false;
ButtonVariantStyle {
bg,
border,
fg,
underline,
shadow,
}
ButtonVariantStyle { bg, fg, underline }
}
}

View File

@@ -1,201 +0,0 @@
use gpui::{
div, prelude::FluentBuilder as _, App, Corners, Div, Edges, ElementId, InteractiveElement,
IntoElement, ParentElement, RenderOnce, StatefulInteractiveElement as _, Styled, Window,
};
use std::{cell::Cell, rc::Rc};
use crate::{
button::{Button, ButtonVariant, ButtonVariants},
Disableable, Sizable, Size,
};
type OnClick = Option<Box<dyn Fn(&Vec<usize>, &mut Window, &mut App) + 'static>>;
/// A ButtonGroup element, to wrap multiple buttons in a group.
#[derive(IntoElement)]
pub struct ButtonGroup {
pub base: Div,
id: ElementId,
children: Vec<Button>,
multiple: bool,
disabled: bool,
// The button props
compact: Option<bool>,
variant: Option<ButtonVariant>,
size: Option<Size>,
on_click: OnClick,
}
impl Disableable for ButtonGroup {
fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
}
impl ButtonGroup {
/// Creates a new ButtonGroup.
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
base: div(),
children: Vec::new(),
id: id.into(),
variant: None,
size: None,
compact: None,
multiple: false,
disabled: false,
on_click: None,
}
}
/// Adds a button as a child to the ButtonGroup.
pub fn child(mut self, child: Button) -> Self {
self.children.push(child.disabled(self.disabled));
self
}
/// With the multiple selection mode.
pub fn multiple(mut self, multiple: bool) -> Self {
self.multiple = multiple;
self
}
/// With the compact mode for the ButtonGroup.
pub fn compact(mut self) -> Self {
self.compact = Some(true);
self
}
/// Sets the on_click handler for the ButtonGroup.
///
/// The handler first argument is a vector of the selected button indices.
pub fn on_click(
mut self,
handler: impl Fn(&Vec<usize>, &mut Window, &mut App) + 'static,
) -> Self {
self.on_click = Some(Box::new(handler));
self
}
}
impl Sizable for ButtonGroup {
fn with_size(mut self, size: impl Into<Size>) -> Self {
self.size = Some(size.into());
self
}
}
impl Styled for ButtonGroup {
fn style(&mut self) -> &mut gpui::StyleRefinement {
self.base.style()
}
}
impl ButtonVariants for ButtonGroup {
fn with_variant(mut self, variant: ButtonVariant) -> Self {
self.variant = Some(variant);
self
}
}
impl RenderOnce for ButtonGroup {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let children_len = self.children.len();
let mut selected_ixs: Vec<usize> = Vec::new();
let state = Rc::new(Cell::new(None));
for (ix, child) in self.children.iter().enumerate() {
if child.selected {
selected_ixs.push(ix);
}
}
self.base
.id(self.id)
.flex()
.items_center()
.children(
self.children
.into_iter()
.enumerate()
.map(|(child_index, child)| {
let state = Rc::clone(&state);
if children_len == 1 {
child
} else if child_index == 0 {
// First
child
.border_corners(Corners {
top_left: true,
top_right: false,
bottom_left: true,
bottom_right: false,
})
.border_edges(Edges {
left: true,
top: true,
right: true,
bottom: true,
})
} else if child_index == children_len - 1 {
// Last
child
.border_edges(Edges {
left: false,
top: true,
right: true,
bottom: true,
})
.border_corners(Corners {
top_left: false,
top_right: true,
bottom_left: false,
bottom_right: true,
})
} else {
// Middle
child
.border_corners(Corners::all(false))
.border_edges(Edges {
left: false,
top: true,
right: true,
bottom: true,
})
}
.stop_propagation(false)
.when_some(self.size, |this, size| this.with_size(size))
.when_some(self.variant, |this, variant| this.with_variant(variant))
.on_click(move |_, _, _| {
state.set(Some(child_index));
})
}),
)
.when_some(
self.on_click.filter(|_| !self.disabled),
move |this, on_click| {
this.on_click(move |_, window, cx| {
let mut selected_ixs = selected_ixs.clone();
if let Some(ix) = state.get() {
if self.multiple {
if let Some(pos) = selected_ixs.iter().position(|&i| i == ix) {
selected_ixs.remove(pos);
} else {
selected_ixs.push(ix);
}
} else {
selected_ixs.clear();
selected_ixs.push(ix);
}
}
on_click(&selected_ixs, window, cx);
})
},
)
}
}

View File

@@ -1,13 +1,11 @@
use crate::{
h_flex,
theme::{scale::ColorScaleStep, ActiveTheme},
v_flex, Disableable, IconName, Selectable,
};
use gpui::{
div, prelude::FluentBuilder as _, relative, svg, App, ElementId, InteractiveElement,
IntoElement, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement as _,
Styled as _, Window,
};
use theme::ActiveTheme;
use crate::{h_flex, v_flex, Disableable, IconName, Selectable};
type OnClick = Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>;
@@ -68,15 +66,9 @@ impl Selectable for Checkbox {
impl RenderOnce for Checkbox {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let (color, icon_color) = if self.disabled {
(
cx.theme().base.step(cx, ColorScaleStep::THREE),
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
)
(cx.theme().ghost_element_disabled, cx.theme().text_muted)
} else {
(
cx.theme().accent.step(cx, ColorScaleStep::NINE),
cx.theme().accent.step(cx, ColorScaleStep::ONE),
)
(cx.theme().text_accent, cx.theme().surface_background)
};
h_flex()
@@ -93,7 +85,7 @@ impl RenderOnce for Checkbox {
.size_4()
.flex_shrink_0()
.map(|this| match self.checked {
false => this.bg(cx.theme().transparent),
false => this.bg(cx.theme().ghost_element_background),
_ => this.bg(color),
})
.child(
@@ -111,22 +103,21 @@ impl RenderOnce for Checkbox {
)
.map(|this| {
if let Some(label) = self.label {
this.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
.child(
div()
.w_full()
.overflow_x_hidden()
.text_ellipsis()
.line_height(relative(1.))
.child(label),
)
this.text_color(cx.theme().text_muted).child(
div()
.w_full()
.overflow_x_hidden()
.text_ellipsis()
.line_height(relative(1.))
.child(label),
)
} else {
this
}
})
.when(self.disabled, |this| {
this.cursor_not_allowed()
.text_color(cx.theme().base.step(cx, ColorScaleStep::TEN))
.text_color(cx.theme().text_placeholder)
})
.when_some(
self.on_click.filter(|_| !self.disabled),

View File

@@ -1,158 +0,0 @@
use gpui::{
prelude::FluentBuilder, AnyElement, App, ClipboardItem, Element, ElementId, GlobalElementId,
IntoElement, LayoutId, ParentElement, SharedString, Styled, Window,
};
use std::{cell::RefCell, rc::Rc, time::Duration};
use crate::{
button::{Button, ButtonVariants as _},
h_flex, IconName, Sizable as _,
};
type ContentBuilder = Option<Box<dyn Fn(&mut Window, &mut App) -> AnyElement>>;
type CopiedCallback = Option<Rc<dyn Fn(SharedString, &mut Window, &mut App)>>;
pub struct Clipboard {
id: ElementId,
value: SharedString,
content_builder: ContentBuilder,
copied_callback: CopiedCallback,
}
impl Clipboard {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
value: "".into(),
content_builder: None,
copied_callback: None,
}
}
pub fn value(mut self, value: impl Into<SharedString>) -> Self {
self.value = value.into();
self
}
pub fn content<E, F>(mut self, builder: F) -> Self
where
E: IntoElement,
F: Fn(&mut Window, &mut App) -> E + 'static,
{
self.content_builder = Some(Box::new(move |window, cx| {
builder(window, cx).into_any_element()
}));
self
}
pub fn on_copied<F>(mut self, handler: F) -> Self
where
F: Fn(SharedString, &mut Window, &mut App) + 'static,
{
self.copied_callback = Some(Rc::new(handler));
self
}
}
impl IntoElement for Clipboard {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
#[derive(Default)]
pub struct ClipboardState {
copied: Rc<RefCell<bool>>,
}
impl Element for Clipboard {
type RequestLayoutState = AnyElement;
type PrepaintState = ();
fn id(&self) -> Option<ElementId> {
Some(self.id.clone())
}
fn request_layout(
&mut self,
global_id: Option<&GlobalElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
window.with_element_state::<ClipboardState, _>(global_id.unwrap(), |state, window| {
let state = state.unwrap_or_default();
let content_element = self
.content_builder
.as_ref()
.map(|builder| builder(window, cx).into_any_element());
let value = self.value.clone();
let clipboard_id = self.id.clone();
let copied_callback = self.copied_callback.as_ref().map(|c| c.clone());
let copied = state.copied.clone();
let copide_value = *copied.borrow();
let mut element = h_flex()
.gap_1()
.items_center()
.when_some(content_element, |this, element| this.child(element))
.child(
Button::new(clipboard_id)
.icon(if copide_value {
IconName::Check
} else {
IconName::Copy
})
.ghost()
.xsmall()
.when(!copide_value, |this| {
this.on_click(move |_, window, cx| {
cx.stop_propagation();
cx.write_to_clipboard(ClipboardItem::new_string(value.to_string()));
*copied.borrow_mut() = true;
let copied = copied.clone();
cx.spawn(async move |cx| {
cx.background_executor().timer(Duration::from_secs(2)).await;
*copied.borrow_mut() = false;
})
.detach();
if let Some(callback) = &copied_callback {
callback(value.clone(), window, cx);
}
})
}),
)
.into_any_element();
((element.request_layout(window, cx), element), state)
})
}
fn prepaint(
&mut self,
_: Option<&gpui::GlobalElementId>,
_: gpui::Bounds<gpui::Pixels>,
element: &mut Self::RequestLayoutState,
window: &mut Window,
cx: &mut App,
) {
element.prepaint(window, cx);
}
fn paint(
&mut self,
_: Option<&gpui::GlobalElementId>,
_: gpui::Bounds<gpui::Pixels>,
element: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
window: &mut Window,
cx: &mut App,
) {
element.paint(window, cx)
}
}

View File

@@ -2,8 +2,7 @@ use gpui::{
div, prelude::FluentBuilder as _, px, Axis, Div, Hsla, IntoElement, ParentElement, RenderOnce,
SharedString, Styled,
};
use crate::theme::{scale::ColorScaleStep, ActiveTheme};
use theme::ActiveTheme;
/// A divider that can be either vertical or horizontal.
#[derive(IntoElement)]
@@ -65,9 +64,7 @@ impl RenderOnce for Divider {
Axis::Vertical => this.w(px(2.)).h_full(),
Axis::Horizontal => this.h(px(2.)).w_full(),
})
.bg(self
.color
.unwrap_or(cx.theme().base.step(cx, ColorScaleStep::FIVE))),
.bg(self.color.unwrap_or(cx.theme().border_variant)),
)
.when_some(self.label, |this, label| {
this.child(

View File

@@ -1,16 +1,17 @@
use std::sync::Arc;
use gpui::{
div, prelude::FluentBuilder as _, px, App, AppContext, Axis, Context, Element, Entity,
InteractiveElement as _, IntoElement, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels,
Point, Render, StatefulInteractiveElement, Style, Styled as _, WeakEntity, Window,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use theme::ActiveTheme;
use super::{DockArea, DockItem};
use crate::{
dock_area::{panel::PanelView, tab_panel::TabPanel},
resizable::{HANDLE_PADDING, HANDLE_SIZE, PANEL_MIN_SIZE},
theme::{scale::ColorScaleStep, ActiveTheme as _},
AxisExt as _, StyledExt,
};
@@ -268,7 +269,7 @@ impl Dock {
.child(
div()
.rounded_full()
.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::SIX)))
.hover(|this| this.bg(cx.theme().border_variant))
.when(axis.is_horizontal(), |this| this.h_full().w(HANDLE_SIZE))
.when(axis.is_vertical(), |this| this.w_full().h(HANDLE_SIZE)),
)

View File

@@ -1,3 +1,12 @@
use gpui::{
div, prelude::FluentBuilder, px, rems, App, AppContext, Context, Corner, DefiniteLength,
DismissEvent, DragMoveEvent, Empty, Entity, EventEmitter, FocusHandle, Focusable,
InteractiveElement as _, IntoElement, ParentElement, Pixels, Render, ScrollHandle,
SharedString, StatefulInteractiveElement, Styled, WeakEntity, Window,
};
use std::sync::Arc;
use theme::ActiveTheme;
use super::{
panel::PanelView, stack_panel::StackPanel, ClosePanel, DockArea, PanelEvent, PanelStyle,
ToggleZoom,
@@ -8,16 +17,8 @@ use crate::{
h_flex,
popup_menu::{PopupMenu, PopupMenuExt},
tab::{tab_bar::TabBar, Tab},
theme::{scale::ColorScaleStep, ActiveTheme},
v_flex, AxisExt, IconName, Placement, Selectable, Sizable, StyledExt,
};
use gpui::{
div, prelude::FluentBuilder, px, rems, App, AppContext, Context, Corner, DefiniteLength,
DismissEvent, DragMoveEvent, Empty, Entity, EventEmitter, FocusHandle, Focusable,
InteractiveElement as _, IntoElement, ParentElement, Pixels, Render, ScrollHandle,
SharedString, StatefulInteractiveElement, Styled, WeakEntity, Window,
};
use std::sync::Arc;
#[derive(Clone)]
struct TabState {
@@ -53,11 +54,11 @@ impl Render for DragPanel {
.justify_center()
.overflow_hidden()
.whitespace_nowrap()
.rounded(px(cx.theme().radius))
.rounded(cx.theme().radius)
.text_xs()
.shadow_lg()
.bg(cx.theme().background)
.text_color(cx.theme().accent.step(cx, ColorScaleStep::TWELVE))
.text_color(cx.theme().text_accent)
.child(self.panel.title(cx))
}
}
@@ -639,9 +640,7 @@ impl TabPanel {
this.rounded_l_none()
.border_l_2()
.border_r_0()
.border_color(
cx.theme().base.step(cx, ColorScaleStep::FIVE),
)
.border_color(cx.theme().border)
})
.on_drop(cx.listener(
move |this, drag: &DragPanel, window, cx| {
@@ -660,10 +659,10 @@ impl TabPanel {
.h_full()
.flex_grow()
.min_w_16()
.rounded(px(cx.theme().radius))
.rounded(cx.theme().radius)
.when(state.droppable, |this| {
this.drag_over::<DragPanel>(|this, _, _, cx| {
this.bg(cx.theme().base.step(cx, ColorScaleStep::TWO))
this.bg(cx.theme().surface_background)
})
.on_drop(cx.listener(
move |this, drag: &DragPanel, window, cx| {
@@ -718,8 +717,8 @@ impl TabPanel {
.size_full()
.rounded_lg()
.shadow_sm()
.when(cx.theme().appearance.is_dark(), |this| this.shadow_lg())
.bg(cx.theme().background)
.when(cx.theme().mode.is_dark(), |this| this.shadow_lg())
.bg(cx.theme().panel_background)
.overflow_hidden()
.child(
active_panel
@@ -738,8 +737,8 @@ impl TabPanel {
div()
.rounded_lg()
.border_1()
.border_color(cx.theme().accent.step(cx, ColorScaleStep::FOUR))
.bg(cx.theme().accent.step_alpha(cx, ColorScaleStep::THREE))
.border_color(cx.theme().element_disabled)
.bg(cx.theme().drop_target_background)
.size_full(),
)
.map(|this| match self.will_split_placement {

View File

@@ -5,11 +5,11 @@ use gpui::{
Pixels, Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task,
WeakEntity, Window,
};
use theme::ActiveTheme;
use crate::{
h_flex,
list::{self, List, ListDelegate, ListItem},
theme::{scale::ColorScaleStep, ActiveTheme},
v_flex, Icon, IconName, Sizable, Size, StyleSized, StyledExt,
};
@@ -216,7 +216,7 @@ where
h_flex()
.justify_center()
.py_6()
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
.text_color(cx.theme().text_muted)
.child(Icon::new(IconName::Inbox).size(px(28.)))
.into_any_element()
}
@@ -545,18 +545,15 @@ where
.when_some(self.title_prefix.clone(), |this, prefix| this.child(prefix))
.child(title.clone())
} else {
div()
.text_color(cx.theme().accent.step(cx, ColorScaleStep::ELEVEN))
.child(
self.placeholder
.clone()
.unwrap_or_else(|| "Please select".into()),
)
div().text_color(cx.theme().text_accent).child(
self.placeholder
.clone()
.unwrap_or_else(|| "Please select".into()),
)
};
title.when(self.disabled, |this| {
this.cursor_not_allowed()
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
this.cursor_not_allowed().text_color(cx.theme().text_muted)
})
}
}
@@ -623,9 +620,9 @@ where
.justify_between()
.bg(cx.theme().background)
.border_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::SIX))
.rounded(px(cx.theme().radius))
.when(cx.theme().shadow, |this| this.shadow_sm())
.border_color(cx.theme().border)
.rounded(cx.theme().radius)
.shadow_sm()
.map(|this| {
if self.disabled {
this.cursor_not_allowed()
@@ -672,10 +669,8 @@ where
Icon::new(icon)
.xsmall()
.text_color(match self.disabled {
true => cx.theme().base.step(cx, ColorScaleStep::TEN),
false => {
cx.theme().base.step(cx, ColorScaleStep::ELEVEN)
}
true => cx.theme().icon_muted,
false => cx.theme().icon,
})
.when(self.disabled, |this| this.cursor_not_allowed()),
)
@@ -706,10 +701,8 @@ where
.mt_1p5()
.bg(cx.theme().background)
.border_1()
.border_color(
cx.theme().base.step(cx, ColorScaleStep::SEVEN),
)
.rounded(px(cx.theme().radius))
.border_color(cx.theme().border_focused)
.rounded(cx.theme().radius)
.shadow_md()
.on_mouse_down_out(|_, _, cx| {
cx.dispatch_action(&Escape);

View File

@@ -6,12 +6,12 @@ use gpui::{
StatefulInteractiveElement, Styled, WeakEntity, Window,
};
use serde::Deserialize;
use theme::ActiveTheme;
use crate::{
button::{Button, ButtonVariants},
input::TextInput,
popover::{Popover, PopoverContent},
theme::{scale::ColorScaleStep, ActiveTheme},
Icon,
};
@@ -99,11 +99,9 @@ impl RenderOnce for EmojiPicker {
.flex()
.items_center()
.justify_center()
.rounded(px(cx.theme().radius))
.rounded(cx.theme().radius)
.child(e.clone())
.hover(|this| {
this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
})
.hover(|this| this.bg(cx.theme().ghost_element_hover))
.on_click({
let item = e.clone();
let input = input.upgrade();

View File

@@ -1,12 +1,11 @@
use crate::{
theme::{scale::ColorScaleStep, ActiveTheme},
Sizable, Size,
};
use gpui::{
prelude::FluentBuilder as _, svg, AnyElement, App, AppContext, Entity, Hsla, IntoElement,
Radians, Render, RenderOnce, SharedString, StyleRefinement, Styled, Svg, Transformation,
Window,
};
use theme::ActiveTheme;
use crate::{Sizable, Size};
#[derive(IntoElement, Clone)]
pub enum IconName {
@@ -295,9 +294,7 @@ impl Render for Icon {
_window: &mut gpui::Window,
cx: &mut gpui::Context<Self>,
) -> impl IntoElement {
let text_color = self
.text_color
.unwrap_or_else(|| cx.theme().base.step(cx, ColorScaleStep::ELEVEN));
let text_color = self.text_color.unwrap_or_else(|| cx.theme().icon);
svg()
.flex_none()

View File

@@ -1,11 +1,12 @@
use super::TextInput;
use crate::theme::{scale::ColorScaleStep, ActiveTheme};
use gpui::{
fill, point, px, relative, size, App, Bounds, Corners, Element, ElementId, ElementInputHandler,
Entity, GlobalElementId, IntoElement, LayoutId, MouseButton, MouseMoveEvent, PaintQuad, Path,
Pixels, Point, Style, TextRun, UnderlineStyle, Window, WrappedLine,
};
use smallvec::SmallVec;
use theme::ActiveTheme;
use super::TextInput;
const RIGHT_MARGIN: Pixels = px(5.);
const BOTTOM_MARGIN: Pixels = px(20.);
@@ -149,7 +150,7 @@ impl TextElement {
),
size(px(1.), cursor_height),
),
cx.theme().accent.step(cx, ColorScaleStep::TEN),
cx.theme().element_active,
))
};
}
@@ -342,17 +343,11 @@ impl Element for TextElement {
let mut bounds = bounds;
let (display_text, text_color) = if text.is_empty() {
(
placeholder,
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
)
(placeholder, cx.theme().text_muted)
} else if input.masked {
(
"*".repeat(text.chars().count()).into(),
cx.theme().base.step(cx, ColorScaleStep::TWELVE),
)
("*".repeat(text.chars().count()).into(), cx.theme().text)
} else {
(text, cx.theme().base.step(cx, ColorScaleStep::TWELVE))
(text, cx.theme().text)
};
let run = TextRun {
@@ -471,7 +466,7 @@ impl Element for TextElement {
// Paint selections
if let Some(path) = prepaint.selection_path.take() {
window.paint_path(path, cx.theme().accent.step(cx, ColorScaleStep::FOUR));
window.paint_path(path, cx.theme().element_disabled);
}
// Paint multi line text

View File

@@ -1,3 +1,5 @@
use std::{cell::Cell, ops::Range, rc::Rc};
use gpui::{
actions, div, point, prelude::FluentBuilder as _, px, AnyElement, App, AppContext, Bounds,
ClipboardItem, Context, Entity, EntityInputHandler, EventEmitter, FocusHandle, Focusable,
@@ -6,7 +8,7 @@ use gpui::{
ScrollWheelEvent, SharedString, Styled as _, Subscription, UTF16Selection, Window, WrappedLine,
};
use smallvec::SmallVec;
use std::{cell::Cell, ops::Range, rc::Rc};
use theme::ActiveTheme;
use unicode_segmentation::*;
use super::{blink_cursor::BlinkCursor, change::Change, element::TextElement};
@@ -14,7 +16,6 @@ use crate::{
history::History,
indicator::Indicator,
scroll::{Scrollbar, ScrollbarAxis, ScrollbarState},
theme::{scale::ColorScaleStep, ActiveTheme},
Sizable, Size, StyleSized, StyledExt,
};
@@ -1624,9 +1625,8 @@ impl Render for TextInput {
.cursor_text()
.when(self.multi_line, |this| this.h_auto())
.when(self.appearance, |this| {
this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
.rounded(px(cx.theme().radius))
.when(cx.theme().shadow, |this| this.shadow_sm())
this.bg(cx.theme().elevated_surface_background)
.rounded(cx.theme().radius)
.when(focused, |this| this.outline(window, cx))
.when(prefix.is_none(), |this| this.input_pl(self.size))
.when(suffix.is_none(), |this| this.input_pr(self.size))
@@ -1642,7 +1642,7 @@ impl Render for TextInput {
.child(TextElement::new(cx.entity().clone())),
)
.when(self.loading, |this| {
this.child(Indicator::new().color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN)))
this.child(Indicator::new().color(cx.theme().text_muted))
})
.children(suffix)
.when(self.is_multi_line(), |this| {

View File

@@ -1,8 +1,24 @@
pub use event::InteractiveElementExt;
pub use focusable::FocusableCycle;
pub use icon::*;
pub use root::{ContextModal, Root};
pub use styled::*;
pub use title_bar::*;
pub use window_border::{window_border, WindowBorder};
pub use crate::Disableable;
mod event;
mod focusable;
mod icon;
mod root;
mod styled;
mod title_bar;
mod window_border;
pub mod animation;
pub mod button;
pub mod button_group;
pub mod checkbox;
pub mod clipboard;
pub mod context_menu;
pub mod divider;
pub mod dock_area;
@@ -16,34 +32,14 @@ pub mod modal;
pub mod notification;
pub mod popover;
pub mod popup_menu;
pub mod progress;
pub mod radio;
pub mod resizable;
pub mod scroll;
pub mod skeleton;
pub mod switch;
pub mod tab;
pub mod text;
pub mod theme;
pub mod tooltip;
pub use crate::Disableable;
pub use event::InteractiveElementExt;
pub use focusable::FocusableCycle;
pub use icon::*;
pub use root::{ContextModal, Root};
pub use styled::*;
pub use title_bar::*;
pub use window_border::{window_border, WindowBorder};
mod event;
mod focusable;
mod icon;
mod root;
mod styled;
mod title_bar;
mod window_border;
/// Initialize the UI module.
///
/// This must be called before using any of the UI components.

View File

@@ -1,9 +1,5 @@
use crate::{
input::{InputEvent, TextInput},
scroll::{Scrollbar, ScrollbarState},
theme::{scale::ColorScaleStep, ActiveTheme},
v_flex, Icon, IconName, Size,
};
use std::{cell::Cell, rc::Rc, time::Duration};
use gpui::{
actions, div, prelude::FluentBuilder, px, uniform_list, AnyElement, App, AppContext, Context,
Entity, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding, Length,
@@ -11,7 +7,13 @@ use gpui::{
Subscription, Task, UniformListScrollHandle, Window,
};
use smol::Timer;
use std::{cell::Cell, rc::Rc, time::Duration};
use theme::ActiveTheme;
use crate::{
input::{InputEvent, TextInput},
scroll::{Scrollbar, ScrollbarState},
v_flex, Icon, IconName, Size,
};
actions!(list, [Cancel, Confirm, SelectPrev, SelectNext]);
@@ -122,10 +124,7 @@ where
let query_input = cx.new(|cx| {
TextInput::new(window, cx)
.appearance(false)
.prefix(|_window, cx| {
Icon::new(IconName::Search)
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
})
.prefix(|_window, cx| Icon::new(IconName::Search).text_color(cx.theme().text_muted))
.placeholder("Search...")
.cleanable()
});
@@ -379,9 +378,9 @@ where
.left(px(0.))
.right(px(0.))
.bottom(px(0.))
.bg(cx.theme().accent.step(cx, ColorScaleStep::SIX))
.bg(cx.theme().element_background)
.border_1()
.border_color(cx.theme().accent.step(cx, ColorScaleStep::NINE)),
.border_color(cx.theme().border_selected),
)
})
})
@@ -394,7 +393,7 @@ where
.right(px(0.))
.bottom(px(0.))
.border_1()
.border_color(cx.theme().accent.step(cx, ColorScaleStep::NINE)),
.border_color(cx.theme().element_active),
)
})
.on_mouse_down(
@@ -471,7 +470,7 @@ where
_ => this.py_1().px_2(),
})
.border_b_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::THREE))
.border_color(cx.theme().border)
.child(input),
)
})

View File

@@ -1,14 +1,12 @@
use crate::{
h_flex,
theme::{scale::ColorScaleStep, ActiveTheme},
Disableable, Icon, IconName, Selectable, Sizable as _,
};
use gpui::{
div, prelude::FluentBuilder as _, AnyElement, App, ClickEvent, Div, ElementId,
InteractiveElement, IntoElement, MouseButton, MouseMoveEvent, ParentElement, RenderOnce,
Stateful, StatefulInteractiveElement as _, Styled, Window,
};
use smallvec::SmallVec;
use theme::ActiveTheme;
use crate::{h_flex, Disableable, Icon, IconName, Selectable, Sizable as _};
type OnClick = Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>;
type OnMouseEnter = Option<Box<dyn Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static>>;
@@ -132,7 +130,7 @@ impl RenderOnce for ListItem {
let is_active = self.selected || self.confirmed;
self.base
.text_color(cx.theme().base.step(cx, ColorScaleStep::TWELVE))
.text_color(cx.theme().text_muted)
.relative()
.items_center()
.justify_between()
@@ -147,11 +145,9 @@ impl RenderOnce for ListItem {
this
}
})
.when(is_active, |this| {
this.bg(cx.theme().accent.step(cx, ColorScaleStep::NINE))
})
.when(is_active, |this| this.bg(cx.theme().element_active))
.when(!is_active && !self.disabled, |this| {
this.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::TWO)))
this.hover(|this| this.bg(cx.theme().surface_background))
})
// Mouse enter
.when_some(self.on_mouse_enter, |this, on_mouse_enter| {
@@ -169,16 +165,15 @@ impl RenderOnce for ListItem {
.gap_x_1()
.child(div().w_full().children(self.children))
.when_some(self.check_icon, |this, icon| {
this.child(div().w_5().items_center().justify_center().when(
self.confirmed,
|this| {
this.child(
icon.small().text_color(
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
),
)
},
))
this.child(
div()
.w_5()
.items_center()
.justify_center()
.when(self.confirmed, |this| {
this.child(icon.small().text_color(cx.theme().text_muted))
}),
)
}),
)
.when_some(self.suffix, |this, suffix| this.child(suffix(window, cx)))

View File

@@ -6,11 +6,11 @@ use gpui::{
IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point, RenderOnce, SharedString,
Styled, Window,
};
use theme::ActiveTheme;
use crate::{
animation::cubic_bezier,
button::{Button, ButtonCustomVariant, ButtonVariants as _},
theme::{scale::ColorScaleStep, ActiveTheme as _},
v_flex, ContextModal, IconName, StyledExt,
};
@@ -47,7 +47,7 @@ impl Modal {
let base = v_flex()
.bg(cx.theme().background)
.border_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::SIX))
.border_color(cx.theme().border)
.rounded_xl()
.shadow_md();
@@ -168,9 +168,7 @@ impl RenderOnce for Modal {
.occlude()
.w(view_size.width)
.h(view_size.height)
.when(self.overlay, |this| {
this.bg(cx.theme().base.step_alpha(cx, ColorScaleStep::TWO))
})
.when(self.overlay, |this| this.bg(cx.theme().overlay))
.when(self.keyboard, |this| {
this.on_mouse_down(MouseButton::Left, {
let on_close = self.on_close.clone();
@@ -201,7 +199,7 @@ impl RenderOnce for Modal {
.items_center()
.font_semibold()
.border_b_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::SIX))
.border_color(cx.theme().border)
.line_height(relative(1.))
.child(title),
)
@@ -217,13 +215,10 @@ impl RenderOnce for Modal {
.right_2()
.custom(
ButtonCustomVariant::new(window, cx)
.foreground(
cx.theme().base.step(cx, ColorScaleStep::NINE),
)
.color(cx.theme().transparent)
.hover(cx.theme().transparent)
.active(cx.theme().transparent)
.border(cx.theme().transparent),
.foreground(cx.theme().icon_muted)
.color(cx.theme().ghost_element_background)
.hover(cx.theme().ghost_element_background)
.active(cx.theme().ghost_element_background),
)
.on_click(
move |_, window, cx| {

View File

@@ -1,21 +1,3 @@
use crate::{
animation::cubic_bezier,
button::{Button, ButtonVariants as _},
h_flex,
theme::{
colors::{blue, green, red, yellow},
scale::ColorScaleStep,
ActiveTheme as _,
},
v_flex, Icon, IconName, Sizable as _, StyledExt,
};
use gpui::{
div, prelude::FluentBuilder, px, Animation, AnimationExt, App, AppContext, ClickEvent, Context,
DismissEvent, ElementId, Entity, EventEmitter, InteractiveElement as _, IntoElement,
ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, Subscription,
Window,
};
use smol::Timer;
use std::{
any::TypeId,
collections::{HashMap, VecDeque},
@@ -23,6 +5,21 @@ use std::{
time::Duration,
};
use gpui::{
blue, div, green, prelude::FluentBuilder, px, red, yellow, Animation, AnimationExt, App,
AppContext, ClickEvent, Context, DismissEvent, ElementId, Entity, EventEmitter,
InteractiveElement as _, IntoElement, ParentElement as _, Render, SharedString,
StatefulInteractiveElement, Styled, Subscription, Window,
};
use smol::Timer;
use theme::ActiveTheme;
use crate::{
animation::cubic_bezier,
button::{Button, ButtonVariants as _},
h_flex, v_flex, Icon, IconName, Sizable as _, StyledExt,
};
pub enum NotificationType {
Info,
Success,
@@ -57,7 +54,7 @@ pub struct Notification {
///
/// None means the notification will be added to the end of the list.
id: NotificationId,
type_: NotificationType,
kind: NotificationType,
title: Option<SharedString>,
message: SharedString,
icon: Option<Icon>,
@@ -110,7 +107,7 @@ impl Notification {
id: id.into(),
title: None,
message: message.into(),
type_: NotificationType::Info,
kind: NotificationType::Info,
icon: None,
autohide: true,
on_click: None,
@@ -169,7 +166,7 @@ impl Notification {
/// Set the type of the notification, default is NotificationType::Info.
pub fn with_type(mut self, type_: NotificationType) -> Self {
self.type_ = type_;
self.kind = type_;
self
}
@@ -217,16 +214,13 @@ impl Render for Notification {
let closing = self.closing;
let icon = match self.icon.clone() {
Some(icon) => icon,
None => match self.type_ {
NotificationType::Info => {
Icon::new(IconName::Info).text_color(blue().step(cx, ColorScaleStep::NINE))
None => match self.kind {
NotificationType::Info => Icon::new(IconName::Info).text_color(blue()),
NotificationType::Error => Icon::new(IconName::CloseCircle).text_color(red()),
NotificationType::Success => Icon::new(IconName::CheckCircle).text_color(green()),
NotificationType::Warning => {
Icon::new(IconName::TriangleAlert).text_color(yellow())
}
NotificationType::Error => Icon::new(IconName::CloseCircle)
.text_color(red().step(cx, ColorScaleStep::NINE)),
NotificationType::Success => Icon::new(IconName::CheckCircle)
.text_color(green().step(cx, ColorScaleStep::NINE)),
NotificationType::Warning => Icon::new(IconName::TriangleAlert)
.text_color(yellow().step(cx, ColorScaleStep::NINE)),
},
};
@@ -237,9 +231,9 @@ impl Render for Notification {
.relative()
.w_72()
.border_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
.bg(cx.theme().background)
.rounded(px(cx.theme().radius))
.border_color(cx.theme().border)
.bg(cx.theme().surface_background)
.rounded(cx.theme().radius)
.shadow_md()
.p_2()
.gap_3()

View File

@@ -1,12 +1,5 @@
use crate::{
button::Button,
h_flex,
list::ListItem,
popover::Popover,
scroll::{Scrollbar, ScrollbarState},
theme::{scale::ColorScaleStep, ActiveTheme},
v_flex, Icon, IconName, Selectable, Sizable as _, StyledExt,
};
use std::{cell::Cell, ops::Deref, rc::Rc};
use gpui::{
actions, anchored, canvas, div, prelude::FluentBuilder, px, rems, Action, AnyElement, App,
AppContext, Bounds, Context, Corner, DismissEvent, Edges, Entity, EventEmitter, FocusHandle,
@@ -14,7 +7,16 @@ use gpui::{
Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled, Subscription,
WeakEntity, Window,
};
use std::{cell::Cell, ops::Deref, rc::Rc};
use theme::ActiveTheme;
use crate::{
button::Button,
h_flex,
list::ListItem,
popover::Popover,
scroll::{Scrollbar, ScrollbarState},
v_flex, Icon, IconName, Selectable, Sizable as _, StyledExt,
};
actions!(menu, [Confirm, Dismiss, SelectNext, SelectPrev]);
@@ -456,14 +458,12 @@ impl PopupMenu {
) -> Option<impl IntoElement> {
if let Some(action) = action {
if let Some(keybinding) = window.bindings_for_action(action.deref()).first() {
let el = div()
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
.children(
keybinding
.keystrokes()
.iter()
.map(|key| key_shortcut(key.clone())),
);
let el = div().text_color(cx.theme().text_muted).children(
keybinding
.keystrokes()
.iter()
.map(|key| key_shortcut(key.clone())),
);
return Some(el);
}
@@ -590,10 +590,7 @@ impl Render for PopupMenu {
.h(px(1.))
.mx_neg_1()
.my_0p5()
.bg(cx
.theme()
.base
.step(cx, ColorScaleStep::TWO)),
.bg(cx.theme().border_variant),
)
}
PopupMenuItem::ElementItem { render, .. } => this

View File

@@ -1,62 +0,0 @@
use crate::theme::{scale::ColorScaleStep, ActiveTheme};
use gpui::{
div, prelude::FluentBuilder, px, relative, App, IntoElement, ParentElement, RenderOnce, Styled,
Window,
};
/// A Progress bar element.
#[derive(IntoElement)]
pub struct Progress {
value: f32,
height: f32,
}
impl Progress {
pub fn new() -> Self {
Progress {
value: Default::default(),
height: 8.,
}
}
pub fn value(mut self, value: f32) -> Self {
self.value = value;
self
}
}
impl Default for Progress {
fn default() -> Self {
Self::new()
}
}
impl RenderOnce for Progress {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let rounded = px(self.height / 2.);
let relative_w = relative(match self.value {
v if v < 0. => 0.,
v if v > 100. => 1.,
v => v / 100.,
});
div()
.relative()
.h(px(self.height))
.rounded(rounded)
.bg(cx.theme().accent.step(cx, ColorScaleStep::THREE))
.child(
div()
.absolute()
.top_0()
.left_0()
.h_full()
.w(relative_w)
.bg(cx.theme().accent.step(cx, ColorScaleStep::NINE))
.map(|this| match self.value {
v if v >= 100. => this.rounded(rounded),
_ => this.rounded_l(rounded),
}),
)
}
}

View File

@@ -1,111 +0,0 @@
use crate::{
h_flex,
theme::{scale::ColorScaleStep, ActiveTheme},
IconName,
};
use gpui::{
div, prelude::FluentBuilder, relative, svg, App, ElementId, InteractiveElement, IntoElement,
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window,
};
type OnClick = Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>;
/// A Radio element.
///
/// This is not included the Radio group implementation, you can manage the group by yourself.
#[derive(IntoElement)]
pub struct Radio {
id: ElementId,
label: Option<SharedString>,
checked: bool,
disabled: bool,
on_click: OnClick,
}
impl Radio {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
label: None,
checked: false,
disabled: false,
on_click: None,
}
}
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
self.label = Some(label.into());
self
}
pub fn checked(mut self, checked: bool) -> Self {
self.checked = checked;
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
pub fn on_click(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
self.on_click = Some(Box::new(handler));
self
}
}
impl RenderOnce for Radio {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let color = if self.disabled {
cx.theme().accent.step(cx, ColorScaleStep::FIVE)
} else {
cx.theme().accent.step(cx, ColorScaleStep::NINE)
};
h_flex()
.id(self.id)
.gap_x_2()
.items_center()
.line_height(relative(1.))
.child(
div()
.relative()
.size_4()
.flex_shrink_0()
.rounded_full()
.border_1()
.border_color(color)
.when(self.checked, |this| this.bg(color))
.child(
svg()
.absolute()
.top_px()
.left_px()
.size_3()
.text_color(color)
.map(|this| match self.checked {
true => this.path(IconName::Check.path()),
false => this,
}),
),
)
.when_some(self.label, |this, label| {
this.child(
div()
.size_full()
.overflow_x_hidden()
.text_ellipsis()
.line_height(relative(1.))
.child(label),
)
})
.when_some(
self.on_click.filter(|_| !self.disabled),
|this, on_click| {
this.on_click(move |_event, window, cx| {
on_click(&!self.checked, window, cx);
})
},
)
}
}

View File

@@ -1,12 +1,11 @@
use crate::{
theme::{scale::ColorScaleStep, ActiveTheme as _},
AxisExt as _,
};
use gpui::{
div, prelude::FluentBuilder as _, px, App, Axis, Div, ElementId, InteractiveElement,
IntoElement, ParentElement as _, Pixels, RenderOnce, Stateful, StatefulInteractiveElement,
Styled as _, Window,
};
use theme::ActiveTheme;
use crate::AxisExt as _;
pub(crate) const HANDLE_PADDING: Pixels = px(8.);
pub(crate) const HANDLE_SIZE: Pixels = px(2.);
@@ -65,7 +64,7 @@ impl RenderOnce for ResizeHandle {
.child(
div()
.rounded_full()
.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::SIX)))
.hover(|this| this.bg(cx.theme().border_variant))
.when(self.axis.is_horizontal(), |this| {
this.h_full().w(HANDLE_SIZE)
})

View File

@@ -1,14 +1,16 @@
use crate::{
modal::Modal,
notification::{Notification, NotificationList},
theme::{scale::ColorScaleStep, ActiveTheme},
window_border,
};
use std::rc::Rc;
use gpui::{
div, AnyView, App, AppContext, Context, Entity, FocusHandle, InteractiveElement, IntoElement,
ParentElement as _, Render, Styled, Window,
};
use std::rc::Rc;
use theme::ActiveTheme;
use crate::{
modal::Modal,
notification::{Notification, NotificationList},
window_border,
};
/// Extension trait for [`WindowContext`] and [`ViewContext`] to add drawer functionality.
pub trait ContextModal: Sized {
@@ -230,8 +232,8 @@ impl Render for Root {
.relative()
.size_full()
.font_family(".SystemUIFont")
.bg(cx.theme().base.step(cx, ColorScaleStep::ONE))
.text_color(cx.theme().base.step(cx, ColorScaleStep::TWELVE))
.bg(cx.theme().background)
.text_color(cx.theme().text)
.child(self.view.clone()),
)
}

View File

@@ -1,38 +1,18 @@
use gpui::{
fill, point, px, relative, App, BorderStyle, Bounds, ContentMask, CursorStyle, Edges, Element,
EntityId, Hitbox, Hsla, IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
Pixels, Point, Position, ScrollHandle, ScrollWheelEvent, UniformListScrollHandle, Window,
};
use serde::{Deserialize, Serialize};
use std::{
cell::Cell,
rc::Rc,
time::{Duration, Instant},
};
use crate::theme::{scale::ColorScaleStep, ActiveTheme};
/// Scrollbar show mode.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
pub enum ScrollbarShow {
#[default]
Scrolling,
Hover,
Always,
}
impl ScrollbarShow {
fn is_hover(&self) -> bool {
matches!(self, Self::Hover)
}
fn is_always(&self) -> bool {
matches!(self, Self::Always)
}
}
use gpui::{
fill, point, px, relative, App, BorderStyle, Bounds, ContentMask, CursorStyle, Edges, Element,
EntityId, Hitbox, Hsla, IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
Pixels, Point, Position, ScrollHandle, ScrollWheelEvent, UniformListScrollHandle, Window,
};
use theme::ActiveTheme;
const WIDTH: Pixels = px(12.);
const BORDER_WIDTH: Pixels = px(0.);
pub(crate) const WIDTH: Pixels = px(12.);
const MIN_THUMB_SIZE: f32 = 80.;
const THUMB_RADIUS: Pixels = Pixels(4.0);
const THUMB_INSET: Pixels = Pixels(3.);
@@ -336,9 +316,9 @@ impl Scrollbar {
fn style_for_active(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
(
cx.theme().scrollbar_thumb_hover,
cx.theme().scrollbar,
cx.theme().base.step(cx, ColorScaleStep::SEVEN),
cx.theme().scrollbar_thumb_hover_background,
cx.theme().scrollbar_thumb_background,
cx.theme().scrollbar_thumb_border,
THUMB_INSET - px(1.),
THUMB_RADIUS,
)
@@ -346,24 +326,20 @@ impl Scrollbar {
fn style_for_hovered_thumb(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
(
cx.theme().scrollbar_thumb_hover,
cx.theme().scrollbar,
cx.theme().base.step(cx, ColorScaleStep::SIX),
cx.theme().scrollbar_thumb_hover_background,
cx.theme().scrollbar_thumb_background,
cx.theme().scrollbar_thumb_border,
THUMB_INSET - px(1.),
THUMB_RADIUS,
)
}
fn style_for_hovered_bar(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
let (inset, radius) = if cx.theme().scrollbar_show.is_hover() {
(THUMB_INSET, THUMB_RADIUS - px(1.))
} else {
(THUMB_INSET - px(1.), THUMB_RADIUS)
};
let (inset, radius) = (THUMB_INSET - px(1.), THUMB_RADIUS);
(
cx.theme().scrollbar_thumb,
cx.theme().scrollbar,
cx.theme().scrollbar_thumb_background,
cx.theme().scrollbar_thumb_border,
gpui::transparent_black(),
inset,
radius,
@@ -514,22 +490,12 @@ impl Element for Scrollbar {
};
let state = self.state.clone();
let is_always_to_show = cx.theme().scrollbar_show.is_always();
let is_hover_to_show = cx.theme().scrollbar_show.is_hover();
let is_hovered_on_bar = state.get().hovered_axis == Some(axis);
let is_hovered_on_thumb = state.get().hovered_on_thumb == Some(axis);
let (thumb_bg, bar_bg, bar_border, inset, radius) =
if state.get().dragged_axis == Some(axis) {
Self::style_for_active(cx)
} else if (is_hover_to_show || is_always_to_show)
&& (is_hovered_on_bar || is_hovered_on_thumb)
{
if is_hovered_on_thumb {
Self::style_for_hovered_thumb(cx)
} else {
Self::style_for_hovered_bar(cx)
}
} else {
let mut idle_state = Self::style_for_idle(cx);
// Delay 2s to fade out the scrollbar thumb (in 1s)
@@ -545,11 +511,12 @@ impl Element for Scrollbar {
};
} else {
if elapsed < FADE_OUT_DELAY {
idle_state.0 = cx.theme().scrollbar_thumb;
idle_state.0 = cx.theme().scrollbar_thumb_background;
} else {
// opacity = 1 - (x - 2)^10
let opacity = 1.0 - (elapsed - FADE_OUT_DELAY).powi(10);
idle_state.0 = cx.theme().scrollbar_thumb.opacity(opacity);
idle_state.0 =
cx.theme().scrollbar_thumb_background.opacity(opacity);
};
window.request_animation_frame();
@@ -626,11 +593,10 @@ impl Element for Scrollbar {
_: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
window: &mut Window,
cx: &mut App,
_cx: &mut App,
) {
let hitbox_bounds = prepaint.hitbox.bounds;
let is_visible = self.state.get().is_scrollbar_visible();
let is_hover_to_show = cx.theme().scrollbar_show.is_hover();
// Update last_scroll_time when offset is changed.
if self.scroll_handle.offset() != self.state.get().last_scroll_offset {
@@ -711,7 +677,7 @@ impl Element for Scrollbar {
let safe_range = (-scroll_area_size + container_size)..px(0.);
if is_hover_to_show || is_visible {
if is_visible {
window.on_mouse_event({
let state = self.state.clone();
let view_id = self.view_id;
@@ -770,7 +736,7 @@ impl Element for Scrollbar {
let mut notify = false;
// When is hover to show mode or it was visible,
// we need to update the hovered state and increase the last_scroll_time.
let need_hover_to_update = is_hover_to_show || is_visible;
let need_hover_to_update = is_visible;
// Update hovered state for scrollbar
if bounds.contains(&event.position) && need_hover_to_update {
state.set(state.get().with_hovered(Some(axis)));

View File

@@ -1,9 +1,10 @@
use crate::theme::{scale::ColorScaleStep, ActiveTheme};
use std::time::Duration;
use gpui::{
bounce, div, ease_in_out, Animation, AnimationExt, Div, IntoElement, ParentElement as _,
RenderOnce, Styled,
};
use std::time::Duration;
use theme::ActiveTheme;
#[derive(IntoElement)]
pub struct Skeleton {
@@ -34,7 +35,7 @@ impl RenderOnce for Skeleton {
fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {
div().child(
self.base
.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
.bg(cx.theme().ghost_element_disabled)
.with_animation(
"skeleton",
Animation::new(Duration::from_secs(2))

View File

@@ -1,10 +1,10 @@
use crate::{
scroll::{Scrollable, ScrollbarAxis},
theme::{scale::ColorScaleStep, ActiveTheme},
};
use std::fmt::{self, Display, Formatter};
use gpui::{div, px, App, Axis, Div, Element, ElementId, EntityId, Pixels, Styled, Window};
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Formatter};
use theme::ActiveTheme;
use crate::scroll::{Scrollable, ScrollbarAxis};
/// Returns a `Div` as horizontal flex layout.
pub fn h_flex() -> Div {
@@ -39,7 +39,7 @@ pub trait StyledExt: Styled + Sized {
/// Render a border with a width of 1px, color ring color
fn outline(self, _window: &Window, cx: &App) -> Self {
self.border_color(cx.theme().accent.step(cx, ColorScaleStep::NINE))
self.border_color(cx.theme().ring)
}
/// Wraps the element in a ScrollView.
@@ -66,7 +66,7 @@ pub trait StyledExt: Styled + Sized {
fn popover_style(self, cx: &mut App) -> Self {
self.bg(cx.theme().background)
.border_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::SIX))
.border_color(cx.theme().border)
.shadow_lg()
.rounded_lg()
}

View File

@@ -1,14 +1,13 @@
use crate::{
h_flex,
theme::{scale::ColorScaleStep, ActiveTheme},
Disableable, Side, Sizable, Size,
};
use std::{cell::RefCell, rc::Rc, time::Duration};
use gpui::{
div, prelude::FluentBuilder as _, px, Animation, AnimationExt as _, AnyElement, App, Element,
ElementId, GlobalElementId, InteractiveElement, IntoElement, LayoutId, ParentElement as _,
SharedString, Styled as _, Window,
};
use std::{cell::RefCell, rc::Rc, time::Duration};
use theme::ActiveTheme;
use crate::{h_flex, Disableable, Side, Sizable, Size};
type OnClick = Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>;
@@ -110,11 +109,8 @@ impl Element for Switch {
let on_click = self.on_click.clone();
let (bg, toggle_bg) = match self.checked {
true => (
theme.accent.step(cx, ColorScaleStep::NINE),
theme.background,
),
false => (theme.base.step(cx, ColorScaleStep::THREE), theme.background),
true => (theme.icon_accent, theme.background),
false => (theme.element_background, theme.background),
};
let (bg, toggle_bg) = match self.disabled {
@@ -126,10 +122,12 @@ impl Element for Switch {
Size::XSmall | Size::Small => (px(28.), px(16.)),
_ => (px(36.), px(20.)),
};
let bar_width = match self.size {
Size::XSmall | Size::Small => px(12.),
_ => px(16.),
};
let inset = px(2.);
let mut element = div()
@@ -150,7 +148,7 @@ impl Element for Switch {
.flex()
.items_center()
.border(inset)
.border_color(theme.transparent)
.border_color(theme.border_transparent)
.bg(bg)
.when(!self.disabled, |this| this.cursor_pointer())
.child(

View File

@@ -1,8 +1,10 @@
use crate::theme::scale::ColorScaleStep;
use crate::theme::ActiveTheme;
use gpui::{
div, prelude::FluentBuilder, px, AnyElement, App, Div, ElementId, InteractiveElement,
IntoElement, ParentElement, RenderOnce, Stateful, StatefulInteractiveElement, Styled, Window,
};
use theme::ActiveTheme;
use crate::Selectable;
use gpui::prelude::FluentBuilder;
use gpui::*;
pub mod tab_bar;
@@ -80,25 +82,24 @@ impl RenderOnce for Tab {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let (text_color, bg_color, hover_bg_color) = match (self.selected, self.disabled) {
(true, false) => (
cx.theme().base.step(cx, ColorScaleStep::TWELVE),
cx.theme().base.step(cx, ColorScaleStep::FIVE),
cx.theme().base.step(cx, ColorScaleStep::FOUR),
cx.theme().text,
cx.theme().tab_active_background,
cx.theme().tab_hover_background,
),
(false, false) => (
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
cx.theme().transparent,
cx.theme().base.step(cx, ColorScaleStep::FOUR),
cx.theme().text_muted,
cx.theme().ghost_element_background,
cx.theme().tab_hover_background,
),
// disabled
(true, true) => (
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
cx.theme().transparent,
cx.theme().base.step(cx, ColorScaleStep::FOUR),
cx.theme().text_muted,
cx.theme().ghost_element_background,
cx.theme().tab_hover_background,
),
(false, true) => (
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
cx.theme().transparent,
cx.theme().base.step(cx, ColorScaleStep::FOUR),
cx.theme().text_muted,
cx.theme().ghost_element_background,
cx.theme().tab_hover_background,
),
};
@@ -115,7 +116,7 @@ impl RenderOnce for Tab {
.text_ellipsis()
.text_color(text_color)
.bg(bg_color)
.rounded(px(cx.theme().radius))
.rounded(cx.theme().radius)
.hover(|this| this.bg(hover_bg_color))
.when_some(self.prefix, |this, prefix| {
this.child(prefix).text_color(text_color)

View File

@@ -8,8 +8,7 @@ use nostr_sdk::prelude::*;
use once_cell::sync::Lazy;
use regex::Regex;
use std::{collections::HashMap, ops::Range, sync::Arc};
use crate::theme::{scale::ColorScaleStep, ActiveTheme};
use theme::ActiveTheme;
static NOSTR_URI_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"nostr:(npub|note|nprofile|nevent|naddr)[a-zA-Z0-9]+").unwrap());
@@ -78,7 +77,7 @@ impl RichText {
}
pub fn element(&self, id: ElementId, window: &mut Window, cx: &App) -> AnyElement {
let link_color = cx.theme().accent.step(cx, ColorScaleStep::ELEVEN);
let link_color = cx.theme().text_accent;
InteractiveText::new(
id,

File diff suppressed because it is too large Load Diff

View File

@@ -1,166 +0,0 @@
use crate::scroll::ScrollbarShow;
use colors::{default_color_scales, hsl};
use gpui::{App, Global, Hsla, SharedString, Window, WindowAppearance};
use scale::ColorScaleSet;
use std::ops::{Deref, DerefMut};
pub mod colors;
pub mod scale;
pub fn init(cx: &mut App) {
Theme::sync_system_appearance(None, cx)
}
pub trait ActiveTheme {
fn theme(&self) -> &Theme;
}
impl ActiveTheme for App {
#[inline]
fn theme(&self) -> &Theme {
Theme::global(self)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct SystemColors {
pub background: Hsla,
pub transparent: Hsla,
pub scrollbar: Hsla,
pub scrollbar_thumb: Hsla,
pub scrollbar_thumb_hover: Hsla,
pub window_border: Hsla,
pub danger: Hsla,
}
impl SystemColors {
pub fn light() -> Self {
Self {
background: hsl(0.0, 0.0, 100.),
transparent: Hsla::transparent_black(),
window_border: hsl(240.0, 5.9, 78.0),
scrollbar: hsl(0., 0., 97.).opacity(0.75),
scrollbar_thumb: hsl(0., 0., 69.).opacity(0.9),
scrollbar_thumb_hover: hsl(0., 0., 59.),
danger: hsl(0.0, 84.2, 60.2),
}
}
pub fn dark() -> Self {
Self {
background: hsl(0.0, 0.0, 8.0),
transparent: Hsla::transparent_black(),
window_border: hsl(240.0, 3.7, 28.0),
scrollbar: hsl(240., 1., 15.).opacity(0.75),
scrollbar_thumb: hsl(0., 0., 48.).opacity(0.9),
scrollbar_thumb_hover: hsl(0., 0., 68.),
danger: hsl(0.0, 62.8, 30.6),
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd, Eq)]
pub enum Appearance {
#[default]
Light,
Dark,
}
impl Appearance {
pub fn is_dark(&self) -> bool {
matches!(self, Self::Dark)
}
}
pub struct Theme {
colors: SystemColors,
/// Base colors.
pub base: ColorScaleSet,
/// Accent colors.
pub accent: ColorScaleSet,
/// Window appearances.
pub appearance: Appearance,
pub font_family: SharedString,
pub font_size: f32,
pub radius: f32,
pub shadow: bool,
/// Show the scrollbar mode, default: Scrolling
pub scrollbar_show: ScrollbarShow,
}
impl Deref for Theme {
type Target = SystemColors;
fn deref(&self) -> &Self::Target {
&self.colors
}
}
impl DerefMut for Theme {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.colors
}
}
impl Global for Theme {}
impl Theme {
/// Returns the global theme reference
pub fn global(cx: &App) -> &Theme {
cx.global::<Theme>()
}
/// Returns the global theme mutable reference
pub fn global_mut(cx: &mut App) -> &mut Theme {
cx.global_mut::<Theme>()
}
/// Sync the theme with the system appearance
pub fn sync_system_appearance(window: Option<&mut Window>, cx: &mut App) {
match cx.window_appearance() {
WindowAppearance::Dark | WindowAppearance::VibrantDark => {
Self::change(Appearance::Dark, window, cx)
}
WindowAppearance::Light | WindowAppearance::VibrantLight => {
Self::change(Appearance::Light, window, cx)
}
}
}
pub fn change(mode: Appearance, window: Option<&mut Window>, cx: &mut App) {
let theme = Theme::new(mode);
cx.set_global(theme);
if let Some(window) = window {
window.refresh();
}
}
}
impl Theme {
fn new(appearance: Appearance) -> Self {
let color_scales = default_color_scales();
let colors = match appearance {
Appearance::Light => SystemColors::light(),
Appearance::Dark => SystemColors::dark(),
};
Theme {
base: color_scales.gray,
accent: color_scales.yellow,
font_size: 15.0,
font_family: if cfg!(target_os = "macos") {
".SystemUIFont".into()
} else if cfg!(target_os = "windows") {
"Segoe UI".into()
} else {
"FreeMono".into()
},
radius: 5.0,
shadow: false,
scrollbar_show: ScrollbarShow::default(),
appearance,
colors,
}
}
}

View File

@@ -1,302 +0,0 @@
use crate::theme::{ActiveTheme, Appearance};
use gpui::{App, Hsla, SharedString};
/// A collection of colors that are used to style the UI.
///
/// Each step has a semantic meaning, and is used to style different parts of the UI.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub struct ColorScaleStep(usize);
impl ColorScaleStep {
pub const ONE: Self = Self(1);
pub const TWO: Self = Self(2);
pub const THREE: Self = Self(3);
pub const FOUR: Self = Self(4);
pub const FIVE: Self = Self(5);
pub const SIX: Self = Self(6);
pub const SEVEN: Self = Self(7);
pub const EIGHT: Self = Self(8);
pub const NINE: Self = Self(9);
pub const TEN: Self = Self(10);
pub const ELEVEN: Self = Self(11);
pub const TWELVE: Self = Self(12);
/// All of the steps in a [`ColorScale`].
pub const ALL: [ColorScaleStep; 12] = [
Self::ONE,
Self::TWO,
Self::THREE,
Self::FOUR,
Self::FIVE,
Self::SIX,
Self::SEVEN,
Self::EIGHT,
Self::NINE,
Self::TEN,
Self::ELEVEN,
Self::TWELVE,
];
}
/// A scale of colors for a given [`ColorScaleSet`].
///
/// Each [`ColorScale`] contains exactly 12 colors. Refer to
/// [`ColorScaleStep`] for a reference of what each step is used for.
pub struct ColorScale(Vec<Hsla>);
impl FromIterator<Hsla> for ColorScale {
fn from_iter<T: IntoIterator<Item = Hsla>>(iter: T) -> Self {
Self(Vec::from_iter(iter))
}
}
impl ColorScale {
/// Returns the specified step in the [`ColorScale`].
#[inline]
pub fn step(&self, step: ColorScaleStep) -> Hsla {
// Steps are one-based, so we need convert to the zero-based vec index.
self.0[step.0 - 1]
}
/// `Step 1` - Used for main application backgrounds.
///
/// This step provides a neutral base for any overlaying components, ideal for applications' main backdrop or empty spaces such as canvas areas.
///
#[inline]
pub fn step_1(&self) -> Hsla {
self.step(ColorScaleStep::ONE)
}
/// `Step 2` - Used for both main application backgrounds and subtle component backgrounds.
///
/// Like `Step 1`, this step allows variations in background styles, from striped tables, sidebar backgrounds, to card backgrounds.
#[inline]
pub fn step_2(&self) -> Hsla {
self.step(ColorScaleStep::TWO)
}
/// `Step 3` - Used for UI component backgrounds in their normal states.
///
/// This step maintains accessibility by guaranteeing a contrast ratio of 4.5:1 with steps 11 and 12 for text. It could also suit hover states for transparent components.
#[inline]
pub fn step_3(&self) -> Hsla {
self.step(ColorScaleStep::THREE)
}
/// `Step 4` - Used for UI component backgrounds in their hover states.
///
/// Also suited for pressed or selected states of components with a transparent background.
#[inline]
pub fn step_4(&self) -> Hsla {
self.step(ColorScaleStep::FOUR)
}
/// `Step 5` - Used for UI component backgrounds in their pressed or selected states.
#[inline]
pub fn step_5(&self) -> Hsla {
self.step(ColorScaleStep::FIVE)
}
/// `Step 6` - Used for subtle borders on non-interactive components.
///
/// Its usage spans from sidebars' borders, headers' dividers, cards' outlines, to alerts' edges and separators.
#[inline]
pub fn step_6(&self) -> Hsla {
self.step(ColorScaleStep::SIX)
}
/// `Step 7` - Used for subtle borders on interactive components.
///
/// This step subtly delineates the boundary of elements users interact with.
#[inline]
pub fn step_7(&self) -> Hsla {
self.step(ColorScaleStep::SEVEN)
}
/// `Step 8` - Used for stronger borders on interactive components and focus rings.
///
/// It strengthens the visibility and accessibility of active elements and their focus states.
#[inline]
pub fn step_8(&self) -> Hsla {
self.step(ColorScaleStep::EIGHT)
}
/// `Step 9` - Used for solid backgrounds.
///
/// `Step 9` is the most saturated step, having the least mix of white or black.
///
/// Due to its high chroma, `Step 9` is versatile and particularly useful for semantic colors such as
/// error, warning, and success indicators.
#[inline]
pub fn step_9(&self) -> Hsla {
self.step(ColorScaleStep::NINE)
}
/// `Step 10` - Used for hovered or active solid backgrounds, particularly when `Step 9` is their normal state.
///
/// May also be used for extremely low contrast text. This should be used sparingly, as it may be difficult to read.
#[inline]
pub fn step_10(&self) -> Hsla {
self.step(ColorScaleStep::TEN)
}
/// `Step 11` - Used for text and icons requiring low contrast or less emphasis.
#[inline]
pub fn step_11(&self) -> Hsla {
self.step(ColorScaleStep::ELEVEN)
}
/// `Step 12` - Used for text and icons requiring high contrast or prominence.
#[inline]
pub fn step_12(&self) -> Hsla {
self.step(ColorScaleStep::TWELVE)
}
}
pub struct ColorScales {
pub gray: ColorScaleSet,
pub mauve: ColorScaleSet,
pub slate: ColorScaleSet,
pub sage: ColorScaleSet,
pub olive: ColorScaleSet,
pub sand: ColorScaleSet,
pub gold: ColorScaleSet,
pub bronze: ColorScaleSet,
pub brown: ColorScaleSet,
pub yellow: ColorScaleSet,
pub amber: ColorScaleSet,
pub orange: ColorScaleSet,
pub tomato: ColorScaleSet,
pub red: ColorScaleSet,
pub ruby: ColorScaleSet,
pub crimson: ColorScaleSet,
pub pink: ColorScaleSet,
pub plum: ColorScaleSet,
pub purple: ColorScaleSet,
pub violet: ColorScaleSet,
pub iris: ColorScaleSet,
pub indigo: ColorScaleSet,
pub blue: ColorScaleSet,
pub cyan: ColorScaleSet,
pub teal: ColorScaleSet,
pub jade: ColorScaleSet,
pub green: ColorScaleSet,
pub grass: ColorScaleSet,
pub lime: ColorScaleSet,
pub mint: ColorScaleSet,
pub sky: ColorScaleSet,
pub black: ColorScaleSet,
pub white: ColorScaleSet,
}
impl IntoIterator for ColorScales {
type Item = ColorScaleSet;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
vec![
self.gray,
self.mauve,
self.slate,
self.sage,
self.olive,
self.sand,
self.gold,
self.bronze,
self.brown,
self.yellow,
self.amber,
self.orange,
self.tomato,
self.red,
self.ruby,
self.crimson,
self.pink,
self.plum,
self.purple,
self.violet,
self.iris,
self.indigo,
self.blue,
self.cyan,
self.teal,
self.jade,
self.green,
self.grass,
self.lime,
self.mint,
self.sky,
self.black,
self.white,
]
.into_iter()
}
}
/// Provides groups of [`ColorScale`]s for light and dark themes, as well as transparent versions of each scale.
pub struct ColorScaleSet {
name: SharedString,
light: ColorScale,
dark: ColorScale,
light_alpha: ColorScale,
dark_alpha: ColorScale,
}
impl ColorScaleSet {
pub fn new(
name: impl Into<SharedString>,
light: ColorScale,
light_alpha: ColorScale,
dark: ColorScale,
dark_alpha: ColorScale,
) -> Self {
Self {
name: name.into(),
light,
light_alpha,
dark,
dark_alpha,
}
}
pub fn name(&self) -> &SharedString {
&self.name
}
pub fn light(&self) -> &ColorScale {
&self.light
}
pub fn light_alpha(&self) -> &ColorScale {
&self.light_alpha
}
pub fn dark(&self) -> &ColorScale {
&self.dark
}
pub fn dark_alpha(&self) -> &ColorScale {
&self.dark_alpha
}
pub fn step(&self, cx: &App, step: ColorScaleStep) -> Hsla {
match cx.theme().appearance {
Appearance::Light => self.light().step(step),
Appearance::Dark => self.dark().step(step),
}
}
pub fn step_alpha(&self, cx: &App, step: ColorScaleStep) -> Hsla {
match cx.theme().appearance {
Appearance::Light => self.light_alpha.step(step),
Appearance::Dark => self.dark_alpha.step(step),
}
}
pub fn darken(&self, cx: &App) -> Hsla {
match cx.theme().appearance {
Appearance::Light => self.light.step_12(),
Appearance::Dark => self.dark.step_1(),
}
}
}

View File

@@ -1,15 +1,20 @@
use crate::{h_flex, theme::ActiveTheme, Icon, IconName, InteractiveElementExt as _, Sizable as _};
use std::rc::Rc;
use gpui::{
black, div, prelude::FluentBuilder as _, px, relative, white, AnyElement, App, ClickEvent, Div,
Element, Hsla, InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels,
RenderOnce, Rgba, Stateful, StatefulInteractiveElement as _, Style, Styled, Window,
};
use std::rc::Rc;
use theme::ActiveTheme;
use crate::{h_flex, Icon, IconName, InteractiveElementExt as _, Sizable as _};
const HEIGHT: Pixels = px(34.);
const TITLE_BAR_HEIGHT: Pixels = px(34.);
#[cfg(target_os = "macos")]
const TITLE_BAR_LEFT_PADDING: Pixels = px(80.);
#[cfg(not(target_os = "macos"))]
const TITLE_BAR_LEFT_PADDING: Pixels = px(12.);
@@ -105,7 +110,7 @@ impl Control {
}
fn fg(&self, _window: &Window, cx: &App) -> Hsla {
if cx.theme().appearance.is_dark() {
if cx.theme().mode.is_dark() {
white()
} else {
black()
@@ -113,7 +118,7 @@ impl Control {
}
fn hover_fg(&self, _window: &Window, cx: &App) -> Hsla {
if self.is_close() || cx.theme().appearance.is_dark() {
if self.is_close() || cx.theme().mode.is_dark() {
white()
} else {
black()
@@ -128,7 +133,7 @@ impl Control {
b: 32.0 / 255.0,
a: 1.0,
}
} else if cx.theme().appearance.is_dark() {
} else if cx.theme().mode.is_dark() {
Rgba {
r: 0.9,
g: 0.9,
@@ -247,7 +252,7 @@ impl RenderOnce for TitleBar {
.items_center()
.justify_between()
.h(HEIGHT)
.bg(cx.theme().transparent)
.bg(cx.theme().title_bar)
.when(window.is_fullscreen(), |this| this.pl(px(12.)))
.on_double_click(|_, window, _cx| window.zoom_window())
.child(

View File

@@ -2,8 +2,7 @@ use gpui::{
div, relative, App, AppContext, Context, Entity, IntoElement, ParentElement, Render,
SharedString, Styled, Window,
};
use crate::theme::{scale::ColorScaleStep, ActiveTheme};
use theme::ActiveTheme;
pub struct Tooltip {
text: SharedString,
@@ -18,18 +17,17 @@ impl Tooltip {
impl Render for Tooltip {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div().child(
// Wrap in a child, to ensure the left margin is applied to the tooltip
div()
.font_family(".SystemUIFont")
.m_3()
.p_2()
.border_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::SIX))
.bg(cx.theme().base.step(cx, ColorScaleStep::TWO))
.border_color(cx.theme().border)
.bg(cx.theme().surface_background)
.shadow_lg()
.rounded_lg()
.text_sm()
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
.text_color(cx.theme().text_muted)
.line_height(relative(1.25))
.child(self.text.clone()),
)

View File

@@ -1,14 +1,16 @@
use crate::theme::ActiveTheme;
use gpui::{
canvas, div, point, prelude::FluentBuilder as _, px, AnyElement, App, Bounds, CursorStyle,
Decorations, Edges, Hsla, InteractiveElement as _, IntoElement, MouseButton, ParentElement,
Pixels, Point, RenderOnce, ResizeEdge, Size, Styled as _, Window,
};
use theme::ActiveTheme;
pub(crate) const BORDER_SIZE: Pixels = Pixels(1.0);
pub(crate) const BORDER_RADIUS: Pixels = Pixels(0.0);
#[cfg(not(target_os = "linux"))]
pub(crate) const SHADOW_SIZE: Pixels = Pixels(0.0);
#[cfg(target_os = "linux")]
pub(crate) const SHADOW_SIZE: Pixels = Pixels(12.0);