wip: design
This commit is contained in:
@@ -1,123 +0,0 @@
|
||||
use crate::{theme::ActiveTheme as _, Sizable, Size};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, relative, Div, Hsla, InteractiveElement as _, IntoElement,
|
||||
ParentElement, RenderOnce, Styled,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum BadgeVariant {
|
||||
#[default]
|
||||
Primary,
|
||||
Secondary,
|
||||
Outline,
|
||||
Destructive,
|
||||
Custom {
|
||||
color: Hsla,
|
||||
foreground: Hsla,
|
||||
border: Hsla,
|
||||
},
|
||||
}
|
||||
impl BadgeVariant {
|
||||
fn bg(&self, cx: &gpui::WindowContext) -> Hsla {
|
||||
match self {
|
||||
Self::Primary => cx.theme().colors.primary,
|
||||
Self::Secondary => cx.theme().secondary,
|
||||
Self::Outline => gpui::transparent_black(),
|
||||
Self::Destructive => cx.theme().danger,
|
||||
Self::Custom { color, .. } => *color,
|
||||
}
|
||||
}
|
||||
|
||||
fn border(&self, cx: &gpui::WindowContext) -> Hsla {
|
||||
match self {
|
||||
Self::Primary => cx.theme().colors.primary,
|
||||
Self::Secondary => cx.theme().secondary,
|
||||
Self::Outline => cx.theme().border,
|
||||
Self::Destructive => cx.theme().danger,
|
||||
Self::Custom { border, .. } => *border,
|
||||
}
|
||||
}
|
||||
|
||||
fn fg(&self, cx: &gpui::WindowContext) -> Hsla {
|
||||
match self {
|
||||
Self::Primary => cx.theme().primary_foreground,
|
||||
Self::Secondary => cx.theme().secondary_foreground,
|
||||
Self::Outline => cx.theme().foreground,
|
||||
Self::Destructive => cx.theme().danger_foreground,
|
||||
Self::Custom { foreground, .. } => *foreground,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Badge is a small status indicator for UI elements.
|
||||
///
|
||||
/// Only support: Medium, Small
|
||||
#[derive(IntoElement)]
|
||||
pub struct Badge {
|
||||
base: Div,
|
||||
veriant: BadgeVariant,
|
||||
size: Size,
|
||||
}
|
||||
impl Badge {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
base: div().flex().items_center().rounded_md().border_1(),
|
||||
veriant: BadgeVariant::default(),
|
||||
size: Size::Medium,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_variant(mut self, variant: BadgeVariant) -> Self {
|
||||
self.veriant = variant;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn primary() -> Self {
|
||||
Self::new().with_variant(BadgeVariant::Primary)
|
||||
}
|
||||
|
||||
pub fn secondary() -> Self {
|
||||
Self::new().with_variant(BadgeVariant::Secondary)
|
||||
}
|
||||
|
||||
pub fn outline() -> Self {
|
||||
Self::new().with_variant(BadgeVariant::Outline)
|
||||
}
|
||||
|
||||
pub fn destructive() -> Self {
|
||||
Self::new().with_variant(BadgeVariant::Destructive)
|
||||
}
|
||||
|
||||
pub fn custom(color: Hsla, foreground: Hsla, border: Hsla) -> Self {
|
||||
Self::new().with_variant(BadgeVariant::Custom {
|
||||
color,
|
||||
foreground,
|
||||
border,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Sizable for Badge {
|
||||
fn with_size(mut self, size: impl Into<Size>) -> Self {
|
||||
self.size = size.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
impl ParentElement for Badge {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {
|
||||
self.base.extend(elements);
|
||||
}
|
||||
}
|
||||
impl RenderOnce for Badge {
|
||||
fn render(self, cx: &mut gpui::WindowContext) -> impl IntoElement {
|
||||
self.base
|
||||
.line_height(relative(1.3))
|
||||
.map(|this| match self.size {
|
||||
Size::XSmall | Size::Small => this.text_xs().px_1p5().py_0(),
|
||||
_ => this.text_xs().px_2p5().py_0p5(),
|
||||
})
|
||||
.bg(self.veriant.bg(cx))
|
||||
.text_color(self.veriant.fg(cx))
|
||||
.border_color(self.veriant.border(cx))
|
||||
.hover(|this| this.opacity(0.9))
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
indicator::Indicator,
|
||||
theme::{scale::ColorScaleStep, ActiveTheme, Colorize as _},
|
||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||
tooltip::Tooltip,
|
||||
Disableable, Icon, Selectable, Sizable, Size, StyledExt,
|
||||
};
|
||||
@@ -42,16 +42,6 @@ pub trait ButtonVariants: Sized {
|
||||
self.with_variant(ButtonVariant::Primary)
|
||||
}
|
||||
|
||||
/// With the danger style for the Button.
|
||||
fn danger(self) -> Self {
|
||||
self.with_variant(ButtonVariant::Danger)
|
||||
}
|
||||
|
||||
/// With the outline style for the Button.
|
||||
fn outline(self) -> Self {
|
||||
self.with_variant(ButtonVariant::Outline)
|
||||
}
|
||||
|
||||
/// With the ghost style for the Button.
|
||||
fn ghost(self) -> Self {
|
||||
self.with_variant(ButtonVariant::Ghost)
|
||||
@@ -116,12 +106,10 @@ impl ButtonCustomVariant {
|
||||
}
|
||||
}
|
||||
|
||||
/// The veriant of the Button.
|
||||
/// The variant of the Button.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ButtonVariant {
|
||||
Primary,
|
||||
Danger,
|
||||
Outline,
|
||||
Ghost,
|
||||
Link,
|
||||
Text,
|
||||
@@ -406,9 +394,7 @@ impl RenderOnce for Button {
|
||||
.when(normal_style.underline, |this| this.text_decoration_1())
|
||||
.hover(|this| {
|
||||
let hover_style = style.hovered(cx);
|
||||
this.bg(hover_style.bg)
|
||||
.border_color(hover_style.border)
|
||||
.text_color(cx.theme().danger)
|
||||
this.bg(hover_style.bg).border_color(hover_style.border)
|
||||
})
|
||||
.active(|this| {
|
||||
let active_style = style.active(cx);
|
||||
@@ -494,7 +480,6 @@ impl ButtonVariant {
|
||||
fn bg_color(&self, cx: &WindowContext) -> Hsla {
|
||||
match self {
|
||||
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::NINE),
|
||||
ButtonVariant::Danger => cx.theme().danger,
|
||||
ButtonVariant::Custom(colors) => colors.color,
|
||||
_ => cx.theme().transparent,
|
||||
}
|
||||
@@ -511,9 +496,7 @@ impl ButtonVariant {
|
||||
|
||||
fn border_color(&self, cx: &WindowContext) -> Hsla {
|
||||
match self {
|
||||
ButtonVariant::Primary => cx.theme().colors.primary,
|
||||
ButtonVariant::Danger => cx.theme().danger,
|
||||
ButtonVariant::Outline => cx.theme().border,
|
||||
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::NINE),
|
||||
ButtonVariant::Ghost | ButtonVariant::Link | ButtonVariant::Text => {
|
||||
cx.theme().transparent
|
||||
}
|
||||
@@ -527,7 +510,7 @@ impl ButtonVariant {
|
||||
|
||||
fn shadow(&self, _: &WindowContext) -> bool {
|
||||
match self {
|
||||
ButtonVariant::Primary | ButtonVariant::Danger => true,
|
||||
ButtonVariant::Primary => true,
|
||||
ButtonVariant::Custom(c) => c.shadow,
|
||||
_ => false,
|
||||
}
|
||||
@@ -552,8 +535,6 @@ impl ButtonVariant {
|
||||
fn hovered(&self, cx: &WindowContext) -> ButtonVariantStyle {
|
||||
let bg = match self {
|
||||
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN),
|
||||
ButtonVariant::Outline => cx.theme().secondary_hover,
|
||||
ButtonVariant::Danger => cx.theme().danger_hover,
|
||||
ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::FOUR),
|
||||
ButtonVariant::Link => cx.theme().transparent,
|
||||
ButtonVariant::Text => cx.theme().transparent,
|
||||
@@ -561,7 +542,7 @@ impl ButtonVariant {
|
||||
};
|
||||
let border = self.border_color(cx);
|
||||
let fg = match self {
|
||||
ButtonVariant::Link => cx.theme().link_hover,
|
||||
ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::TEN),
|
||||
_ => self.text_color(cx),
|
||||
};
|
||||
let underline = self.underline(cx);
|
||||
@@ -578,26 +559,20 @@ impl ButtonVariant {
|
||||
|
||||
fn active(&self, cx: &WindowContext) -> ButtonVariantStyle {
|
||||
let bg = match self {
|
||||
ButtonVariant::Primary => cx.theme().primary_active,
|
||||
ButtonVariant::Outline => cx.theme().secondary_active,
|
||||
ButtonVariant::Ghost => {
|
||||
if cx.theme().appearance.is_dark() {
|
||||
cx.theme().secondary.lighten(0.2).opacity(0.8)
|
||||
} else {
|
||||
cx.theme().secondary.darken(0.2).opacity(0.8)
|
||||
}
|
||||
}
|
||||
ButtonVariant::Danger => cx.theme().danger_active,
|
||||
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::Custom(colors) => colors.active,
|
||||
};
|
||||
let border = self.border_color(cx);
|
||||
|
||||
let fg = match self {
|
||||
ButtonVariant::Link => cx.theme().link_active,
|
||||
ButtonVariant::Text => cx.theme().foreground.opacity(0.7),
|
||||
ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::NINE),
|
||||
ButtonVariant::Text => cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
||||
_ => self.text_color(cx),
|
||||
};
|
||||
|
||||
let border = self.border_color(cx);
|
||||
let underline = self.underline(cx);
|
||||
let shadow = self.shadow(cx);
|
||||
|
||||
@@ -612,26 +587,20 @@ impl ButtonVariant {
|
||||
|
||||
fn selected(&self, cx: &WindowContext) -> ButtonVariantStyle {
|
||||
let bg = match self {
|
||||
ButtonVariant::Primary => cx.theme().primary_active,
|
||||
ButtonVariant::Outline => cx.theme().secondary_active,
|
||||
ButtonVariant::Ghost => {
|
||||
if cx.theme().appearance.is_dark() {
|
||||
cx.theme().secondary.lighten(0.2).opacity(0.8)
|
||||
} else {
|
||||
cx.theme().secondary.darken(0.2).opacity(0.8)
|
||||
}
|
||||
}
|
||||
ButtonVariant::Danger => cx.theme().danger_active,
|
||||
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::Custom(colors) => colors.active,
|
||||
};
|
||||
let border = self.border_color(cx);
|
||||
|
||||
let fg = match self {
|
||||
ButtonVariant::Link => cx.theme().link_active,
|
||||
ButtonVariant::Text => cx.theme().foreground.opacity(0.7),
|
||||
ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::TEN),
|
||||
ButtonVariant::Text => cx.theme().accent.step(cx, ColorScaleStep::TEN),
|
||||
_ => self.text_color(cx),
|
||||
};
|
||||
|
||||
let border = self.border_color(cx);
|
||||
let underline = self.underline(cx);
|
||||
let shadow = self.shadow(cx);
|
||||
|
||||
@@ -646,26 +615,14 @@ impl ButtonVariant {
|
||||
|
||||
fn disabled(&self, cx: &WindowContext) -> ButtonVariantStyle {
|
||||
let bg = match self {
|
||||
ButtonVariant::Link
|
||||
| ButtonVariant::Ghost
|
||||
| ButtonVariant::Outline
|
||||
| ButtonVariant::Text => cx.theme().transparent,
|
||||
ButtonVariant::Primary => cx.theme().colors.primary.opacity(0.15),
|
||||
ButtonVariant::Danger => cx.theme().danger.opacity(0.15),
|
||||
ButtonVariant::Custom(style) => style.color.opacity(0.15),
|
||||
};
|
||||
let fg = match self {
|
||||
ButtonVariant::Link | ButtonVariant::Text | ButtonVariant::Ghost => {
|
||||
cx.theme().link.grayscale()
|
||||
ButtonVariant::Link | ButtonVariant::Ghost | ButtonVariant::Text => {
|
||||
cx.theme().transparent
|
||||
}
|
||||
_ => cx.theme().secondary_foreground.opacity(0.5).grayscale(),
|
||||
};
|
||||
|
||||
let border = match self {
|
||||
ButtonVariant::Outline => cx.theme().border.opacity(0.5),
|
||||
_ => bg,
|
||||
_ => cx.theme().base.step(cx, ColorScaleStep::FOUR),
|
||||
};
|
||||
|
||||
let fg = cx.theme().base.step(cx, ColorScaleStep::ELEVEN);
|
||||
let border = bg;
|
||||
let underline = self.underline(cx);
|
||||
let shadow = false;
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use crate::{h_flex, theme::ActiveTheme, v_flex, Disableable, IconName, Selectable};
|
||||
use crate::{
|
||||
h_flex,
|
||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||
v_flex, Disableable, IconName, Selectable,
|
||||
};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, relative, svg, ElementId, InteractiveElement, IntoElement,
|
||||
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement as _, Styled as _,
|
||||
@@ -65,11 +69,14 @@ impl RenderOnce for Checkbox {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let (color, icon_color) = if self.disabled {
|
||||
(
|
||||
cx.theme().colors.primary.opacity(0.5),
|
||||
cx.theme().primary_foreground.opacity(0.5),
|
||||
cx.theme().base.step(cx, ColorScaleStep::THREE),
|
||||
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
||||
)
|
||||
} else {
|
||||
(cx.theme().colors.primary, cx.theme().primary_foreground)
|
||||
(
|
||||
cx.theme().accent.step(cx, ColorScaleStep::NINE),
|
||||
cx.theme().accent.step(cx, ColorScaleStep::ONE),
|
||||
)
|
||||
};
|
||||
|
||||
h_flex()
|
||||
@@ -104,21 +111,22 @@ impl RenderOnce for Checkbox {
|
||||
)
|
||||
.map(|this| {
|
||||
if let Some(label) = self.label {
|
||||
this.text_color(cx.theme().foreground).child(
|
||||
div()
|
||||
.w_full()
|
||||
.overflow_x_hidden()
|
||||
.text_ellipsis()
|
||||
.line_height(relative(1.))
|
||||
.child(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),
|
||||
)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
})
|
||||
.when(self.disabled, |this| {
|
||||
this.cursor_not_allowed()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::TEN))
|
||||
})
|
||||
.when_some(
|
||||
self.on_click.filter(|_| !self.disabled),
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use crate::theme::{scale::ColorScaleStep, ActiveTheme};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, px, Axis, Div, Hsla, IntoElement, ParentElement, RenderOnce,
|
||||
SharedString, Styled,
|
||||
};
|
||||
|
||||
use crate::theme::ActiveTheme;
|
||||
|
||||
/// A divider that can be either vertical or horizontal.
|
||||
#[derive(IntoElement)]
|
||||
pub struct Divider {
|
||||
@@ -52,8 +51,6 @@ impl Styled for Divider {
|
||||
|
||||
impl RenderOnce for Divider {
|
||||
fn render(self, cx: &mut gpui::WindowContext) -> impl IntoElement {
|
||||
let theme = cx.theme();
|
||||
|
||||
self.base
|
||||
.flex()
|
||||
.flex_shrink_0()
|
||||
@@ -66,7 +63,9 @@ impl RenderOnce for Divider {
|
||||
Axis::Vertical => this.w(px(1.)).h_full(),
|
||||
Axis::Horizontal => this.h(px(1.)).w_full(),
|
||||
})
|
||||
.bg(self.color.unwrap_or(cx.theme().border)),
|
||||
.bg(self
|
||||
.color
|
||||
.unwrap_or(cx.theme().base.step(cx, ColorScaleStep::THREE))),
|
||||
)
|
||||
.when_some(self.label, |this, label| {
|
||||
this.child(
|
||||
@@ -76,7 +75,6 @@ impl RenderOnce for Divider {
|
||||
.mx_auto()
|
||||
.text_xs()
|
||||
.bg(cx.theme().background)
|
||||
.text_color(theme.muted_foreground)
|
||||
.child(label),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,702 +0,0 @@
|
||||
use gpui::*;
|
||||
use std::{
|
||||
cell::Cell,
|
||||
fmt::{Debug, Formatter},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use super::{DockArea, Panel, PanelEvent, PanelInfo, PanelState, PanelView, TabPanel, TileMeta};
|
||||
use crate::{
|
||||
h_flex,
|
||||
scroll::{Scrollbar, ScrollbarState},
|
||||
theme::ActiveTheme,
|
||||
v_flex, Icon, IconName,
|
||||
};
|
||||
|
||||
const MINIMUM_SIZE: Size<Pixels> = size(px(100.), px(100.));
|
||||
const DRAG_BAR_HEIGHT: Pixels = px(30.);
|
||||
const HANDLE_SIZE: Pixels = px(20.0);
|
||||
|
||||
#[derive(Clone, Render)]
|
||||
pub struct DragMoving(EntityId);
|
||||
|
||||
#[derive(Clone, Render)]
|
||||
pub struct DragResizing(EntityId);
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ResizeDrag {
|
||||
axis: ResizeAxis,
|
||||
last_position: Point<Pixels>,
|
||||
last_bounds: Bounds<Pixels>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
enum ResizeAxis {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
Both,
|
||||
}
|
||||
|
||||
/// TileItem is a moveable and resizable panel that can be added to a Tiles view.
|
||||
#[derive(Clone)]
|
||||
pub struct TileItem {
|
||||
pub(crate) panel: Arc<dyn PanelView>,
|
||||
bounds: Bounds<Pixels>,
|
||||
z_index: usize,
|
||||
}
|
||||
|
||||
impl Debug for TileItem {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("TileItem")
|
||||
.field("bounds", &self.bounds)
|
||||
.field("z_index", &self.z_index)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl TileItem {
|
||||
pub fn new(panel: Arc<dyn PanelView>, bounds: Bounds<Pixels>) -> Self {
|
||||
Self {
|
||||
panel,
|
||||
bounds,
|
||||
z_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn z_index(mut self, z_index: usize) -> Self {
|
||||
self.z_index = z_index;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Tiles is a canvas that can contain multiple panels, each of which can be dragged and resized.
|
||||
pub struct Tiles {
|
||||
focus_handle: FocusHandle,
|
||||
pub(crate) panels: Vec<TileItem>,
|
||||
dragging_index: Option<usize>,
|
||||
dragging_initial_mouse: Point<Pixels>,
|
||||
dragging_initial_bounds: Bounds<Pixels>,
|
||||
resizing_index: Option<usize>,
|
||||
resizing_drag_data: Option<ResizeDrag>,
|
||||
bounds: Bounds<Pixels>,
|
||||
|
||||
scroll_state: Rc<Cell<ScrollbarState>>,
|
||||
scroll_handle: ScrollHandle,
|
||||
}
|
||||
|
||||
impl Panel for Tiles {
|
||||
fn panel_id(&self) -> SharedString {
|
||||
"Tiles".into()
|
||||
}
|
||||
|
||||
fn title(&self, _cx: &WindowContext) -> AnyElement {
|
||||
"Tiles".into_any_element()
|
||||
}
|
||||
|
||||
fn dump(&self, cx: &AppContext) -> PanelState {
|
||||
let panels = self
|
||||
.panels
|
||||
.iter()
|
||||
.map(|item: &TileItem| item.panel.dump(cx))
|
||||
.collect();
|
||||
|
||||
let metas = self
|
||||
.panels
|
||||
.iter()
|
||||
.map(|item: &TileItem| TileMeta {
|
||||
bounds: item.bounds,
|
||||
z_index: item.z_index,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut state = PanelState::new(self);
|
||||
state.panel_name = self.panel_id().to_string();
|
||||
state.children = panels;
|
||||
state.info = PanelInfo::Tiles { metas };
|
||||
state
|
||||
}
|
||||
}
|
||||
|
||||
impl Tiles {
|
||||
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
panels: vec![],
|
||||
dragging_index: None,
|
||||
dragging_initial_mouse: Point::default(),
|
||||
dragging_initial_bounds: Bounds::default(),
|
||||
resizing_index: None,
|
||||
resizing_drag_data: None,
|
||||
bounds: Bounds::default(),
|
||||
scroll_state: Rc::new(Cell::new(ScrollbarState::default())),
|
||||
scroll_handle: ScrollHandle::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn sorted_panels(&self) -> Vec<TileItem> {
|
||||
let mut items: Vec<(usize, TileItem)> = self.panels.iter().cloned().enumerate().collect();
|
||||
items.sort_by(|a, b| a.1.z_index.cmp(&b.1.z_index).then_with(|| a.0.cmp(&b.0)));
|
||||
items.into_iter().map(|(_, item)| item).collect()
|
||||
}
|
||||
|
||||
/// Return the index of the panel.
|
||||
#[inline]
|
||||
pub(crate) fn index_of(&self, panel: Arc<dyn PanelView>) -> Option<usize> {
|
||||
#[allow(clippy::op_ref)]
|
||||
self.panels.iter().position(|p| &p.panel == &panel)
|
||||
}
|
||||
|
||||
/// Remove panel from the children.
|
||||
pub fn remove(&mut self, panel: Arc<dyn PanelView>, cx: &mut ViewContext<Self>) {
|
||||
if let Some(ix) = self.index_of(panel.clone()) {
|
||||
self.panels.remove(ix);
|
||||
|
||||
cx.emit(PanelEvent::LayoutChanged);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_initial_position(&mut self, position: Point<Pixels>, cx: &mut ViewContext<'_, Self>) {
|
||||
let Some((index, item)) = self.find_at_position(position) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let inner_pos = position - self.bounds.origin;
|
||||
let bounds = item.bounds;
|
||||
self.dragging_index = Some(index);
|
||||
self.dragging_initial_mouse = inner_pos;
|
||||
self.dragging_initial_bounds = bounds;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn update_position(&mut self, pos: Point<Pixels>, cx: &mut ViewContext<'_, Self>) {
|
||||
let Some(index) = self.dragging_index else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(item) = self.panels.get_mut(index) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let adjusted_position = pos - self.bounds.origin;
|
||||
let delta = adjusted_position - self.dragging_initial_mouse;
|
||||
let mut new_origin = self.dragging_initial_bounds.origin + delta;
|
||||
|
||||
new_origin.x = new_origin.x.max(px(0.0));
|
||||
new_origin.y = new_origin.y.max(px(0.0));
|
||||
|
||||
item.bounds.origin = round_point_to_nearest_ten(new_origin);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn update_resizing_drag(&mut self, drag_data: ResizeDrag, cx: &mut ViewContext<'_, Self>) {
|
||||
if let Some((index, _item)) = self.find_at_position(drag_data.last_position) {
|
||||
self.resizing_index = Some(index);
|
||||
self.resizing_drag_data = Some(drag_data);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn resize_width(&mut self, new_width: Pixels, cx: &mut ViewContext<'_, Self>) {
|
||||
if let Some(index) = self.resizing_index {
|
||||
if let Some(item) = self.panels.get_mut(index) {
|
||||
item.bounds.size.width = round_to_nearest_ten(new_width);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resize_height(&mut self, new_height: Pixels, cx: &mut ViewContext<'_, Self>) {
|
||||
if let Some(index) = self.resizing_index {
|
||||
if let Some(item) = self.panels.get_mut(index) {
|
||||
item.bounds.size.height = round_to_nearest_ten(new_height);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_item(
|
||||
&mut self,
|
||||
item: TileItem,
|
||||
dock_area: &WeakView<DockArea>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.panels.push(item.clone());
|
||||
|
||||
cx.window_context().defer({
|
||||
let panel = item.panel.clone();
|
||||
let dock_area = dock_area.clone();
|
||||
|
||||
move |cx| {
|
||||
// Subscribe to the panel's layout change event.
|
||||
_ = dock_area.update(cx, |this, cx| {
|
||||
if let Ok(tab_panel) = panel.view().downcast::<TabPanel>() {
|
||||
this.subscribe_panel(&tab_panel, cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
cx.emit(PanelEvent::LayoutChanged);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Find the panel at a given position, considering z-index
|
||||
fn find_at_position(&self, position: Point<Pixels>) -> Option<(usize, &TileItem)> {
|
||||
let inner_pos = position - self.bounds.origin;
|
||||
let mut panels_with_indices: Vec<(usize, &TileItem)> =
|
||||
self.panels.iter().enumerate().collect();
|
||||
|
||||
panels_with_indices
|
||||
.sort_by(|a, b| b.1.z_index.cmp(&a.1.z_index).then_with(|| b.0.cmp(&a.0)));
|
||||
|
||||
for (index, item) in panels_with_indices {
|
||||
let extended_bounds = Bounds::new(
|
||||
item.bounds.origin,
|
||||
item.bounds.size + size(HANDLE_SIZE, HANDLE_SIZE) / 2.0,
|
||||
);
|
||||
if extended_bounds.contains(&inner_pos) {
|
||||
return Some((index, item));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn reset_current_index(&mut self) {
|
||||
self.dragging_index = None;
|
||||
self.resizing_index = None;
|
||||
}
|
||||
|
||||
/// Bring the panel of target_index to front, returns (old_index, new_index) if successful
|
||||
fn bring_to_front(&mut self, target_index: Option<usize>) -> Option<(usize, usize)> {
|
||||
if let Some(old_index) = target_index {
|
||||
if old_index < self.panels.len() {
|
||||
let item = self.panels.remove(old_index);
|
||||
self.panels.push(item);
|
||||
let new_index = self.panels.len() - 1;
|
||||
self.reset_current_index();
|
||||
return Some((old_index, new_index));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Produce a vector of AnyElement representing the three possible resize handles
|
||||
fn render_resize_handles(
|
||||
&mut self,
|
||||
cx: &mut ViewContext<Self>,
|
||||
entity_id: EntityId,
|
||||
item: &TileItem,
|
||||
is_occluded: impl Fn(&Bounds<Pixels>) -> bool,
|
||||
) -> Vec<AnyElement> {
|
||||
let panel_bounds = item.bounds;
|
||||
let right_handle_bounds = Bounds::new(
|
||||
panel_bounds.origin + point(panel_bounds.size.width - HANDLE_SIZE.half(), px(0.0)),
|
||||
size(HANDLE_SIZE.half(), panel_bounds.size.height),
|
||||
);
|
||||
|
||||
let bottom_handle_bounds = Bounds::new(
|
||||
panel_bounds.origin + point(px(0.0), panel_bounds.size.height - HANDLE_SIZE.half()),
|
||||
size(panel_bounds.size.width, HANDLE_SIZE.half()),
|
||||
);
|
||||
|
||||
let corner_handle_bounds = Bounds::new(
|
||||
panel_bounds.origin
|
||||
+ point(
|
||||
panel_bounds.size.width - HANDLE_SIZE.half(),
|
||||
panel_bounds.size.height - HANDLE_SIZE.half(),
|
||||
),
|
||||
size(HANDLE_SIZE.half(), HANDLE_SIZE.half()),
|
||||
);
|
||||
|
||||
let mut elements = Vec::new();
|
||||
|
||||
// Right resize handle
|
||||
elements.push(if !is_occluded(&right_handle_bounds) {
|
||||
div()
|
||||
.id("right-resize-handle")
|
||||
.cursor_col_resize()
|
||||
.absolute()
|
||||
.top(px(0.0))
|
||||
.right(-HANDLE_SIZE.half())
|
||||
.w(HANDLE_SIZE)
|
||||
.h(panel_bounds.size.height)
|
||||
.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener({
|
||||
move |this, event: &MouseDownEvent, cx| {
|
||||
let last_position = event.position;
|
||||
let drag_data = ResizeDrag {
|
||||
axis: ResizeAxis::Horizontal,
|
||||
last_position,
|
||||
last_bounds: panel_bounds,
|
||||
};
|
||||
this.update_resizing_drag(drag_data, cx);
|
||||
if let Some((_, new_ix)) = this.bring_to_front(this.resizing_index) {
|
||||
this.resizing_index = Some(new_ix);
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.on_drag(DragResizing(entity_id), |drag, _, cx| {
|
||||
cx.stop_propagation();
|
||||
cx.new_view(|_| drag.clone())
|
||||
})
|
||||
.on_drag_move(
|
||||
cx.listener(move |this, e: &DragMoveEvent<DragResizing>, cx| {
|
||||
match e.drag(cx) {
|
||||
DragResizing(id) => {
|
||||
if *id != entity_id {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(ref drag_data) = this.resizing_drag_data {
|
||||
if drag_data.axis != ResizeAxis::Horizontal {
|
||||
return;
|
||||
}
|
||||
let pos = e.event.position;
|
||||
let delta = pos.x - drag_data.last_position.x;
|
||||
let new_width = (drag_data.last_bounds.size.width + delta)
|
||||
.max(MINIMUM_SIZE.width);
|
||||
this.resize_width(new_width, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
div().into_any_element()
|
||||
});
|
||||
|
||||
// Bottom resize handle
|
||||
elements.push(if !is_occluded(&bottom_handle_bounds) {
|
||||
div()
|
||||
.id("bottom-resize-handle")
|
||||
.cursor_row_resize()
|
||||
.absolute()
|
||||
.left(px(0.0))
|
||||
.bottom(-HANDLE_SIZE.half())
|
||||
.w(panel_bounds.size.width)
|
||||
.h(HANDLE_SIZE)
|
||||
.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener({
|
||||
move |this, event: &MouseDownEvent, cx| {
|
||||
let last_position = event.position;
|
||||
let drag_data = ResizeDrag {
|
||||
axis: ResizeAxis::Vertical,
|
||||
last_position,
|
||||
last_bounds: panel_bounds,
|
||||
};
|
||||
this.update_resizing_drag(drag_data, cx);
|
||||
if let Some((_, new_ix)) = this.bring_to_front(this.resizing_index) {
|
||||
this.resizing_index = Some(new_ix);
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.on_drag(DragResizing(entity_id), |drag, _, cx| {
|
||||
cx.stop_propagation();
|
||||
cx.new_view(|_| drag.clone())
|
||||
})
|
||||
.on_drag_move(
|
||||
cx.listener(move |this, e: &DragMoveEvent<DragResizing>, cx| {
|
||||
match e.drag(cx) {
|
||||
DragResizing(id) => {
|
||||
if *id != entity_id {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(ref drag_data) = this.resizing_drag_data {
|
||||
let pos = e.event.position;
|
||||
let delta = pos.y - drag_data.last_position.y;
|
||||
let new_height = (drag_data.last_bounds.size.height + delta)
|
||||
.max(MINIMUM_SIZE.width);
|
||||
this.resize_height(new_height, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
div().into_any_element()
|
||||
});
|
||||
|
||||
// Corner resize handle
|
||||
elements.push(if !is_occluded(&corner_handle_bounds) {
|
||||
div()
|
||||
.id("corner-resize-handle")
|
||||
.cursor_nwse_resize()
|
||||
.absolute()
|
||||
.right(-HANDLE_SIZE.half())
|
||||
.bottom(-HANDLE_SIZE.half())
|
||||
.w(HANDLE_SIZE)
|
||||
.h(HANDLE_SIZE)
|
||||
.child(
|
||||
Icon::new(IconName::ResizeCorner)
|
||||
.size(HANDLE_SIZE.half())
|
||||
.text_color(cx.theme().foreground.opacity(0.3)),
|
||||
)
|
||||
.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener({
|
||||
move |this, event: &MouseDownEvent, cx| {
|
||||
let last_position = event.position;
|
||||
let drag_data = ResizeDrag {
|
||||
axis: ResizeAxis::Both,
|
||||
last_position,
|
||||
last_bounds: panel_bounds,
|
||||
};
|
||||
this.update_resizing_drag(drag_data, cx);
|
||||
if let Some((_, new_ix)) = this.bring_to_front(this.resizing_index) {
|
||||
this.resizing_index = Some(new_ix);
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.on_drag(DragResizing(entity_id), |drag, _, cx| {
|
||||
cx.stop_propagation();
|
||||
cx.new_view(|_| drag.clone())
|
||||
})
|
||||
.on_drag_move(
|
||||
cx.listener(move |this, e: &DragMoveEvent<DragResizing>, cx| {
|
||||
match e.drag(cx) {
|
||||
DragResizing(id) => {
|
||||
if *id != entity_id {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(ref drag_data) = this.resizing_drag_data {
|
||||
if drag_data.axis != ResizeAxis::Both {
|
||||
return;
|
||||
}
|
||||
let pos = e.event.position;
|
||||
let delta_x = pos.x - drag_data.last_position.x;
|
||||
let delta_y = pos.y - drag_data.last_position.y;
|
||||
let new_width = (drag_data.last_bounds.size.width + delta_x)
|
||||
.max(MINIMUM_SIZE.width);
|
||||
let new_height = (drag_data.last_bounds.size.height + delta_y)
|
||||
.max(MINIMUM_SIZE.height);
|
||||
this.resize_height(new_height, cx);
|
||||
this.resize_width(new_width, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
div().into_any_element()
|
||||
});
|
||||
|
||||
elements
|
||||
}
|
||||
|
||||
/// Produce the drag-bar element for the given panel item
|
||||
fn render_drag_bar(
|
||||
&mut self,
|
||||
cx: &mut ViewContext<Self>,
|
||||
entity_id: EntityId,
|
||||
item: &TileItem,
|
||||
is_occluded: &impl Fn(&Bounds<Pixels>) -> bool,
|
||||
) -> AnyElement {
|
||||
let drag_bar_bounds = Bounds::new(
|
||||
item.bounds.origin,
|
||||
Size {
|
||||
width: item.bounds.size.width,
|
||||
height: DRAG_BAR_HEIGHT,
|
||||
},
|
||||
);
|
||||
|
||||
if !is_occluded(&drag_bar_bounds) {
|
||||
h_flex()
|
||||
.id("drag-bar")
|
||||
.cursor_grab()
|
||||
.absolute()
|
||||
.w_full()
|
||||
.h(DRAG_BAR_HEIGHT)
|
||||
.bg(cx.theme().transparent)
|
||||
.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener(move |this, event: &MouseDownEvent, cx| {
|
||||
let last_position = event.position;
|
||||
this.update_initial_position(last_position, cx);
|
||||
if let Some((_, new_ix)) = this.bring_to_front(this.dragging_index) {
|
||||
this.dragging_index = Some(new_ix);
|
||||
}
|
||||
}),
|
||||
)
|
||||
.on_drag(DragMoving(entity_id), |drag, _, cx| {
|
||||
cx.stop_propagation();
|
||||
cx.new_view(|_| drag.clone())
|
||||
})
|
||||
.on_drag_move(cx.listener(move |this, e: &DragMoveEvent<DragMoving>, cx| {
|
||||
match e.drag(cx) {
|
||||
DragMoving(id) => {
|
||||
if *id != entity_id {
|
||||
return;
|
||||
}
|
||||
this.update_position(e.event.position, cx);
|
||||
}
|
||||
}
|
||||
}))
|
||||
.into_any_element()
|
||||
} else {
|
||||
div().into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
fn render_panel(
|
||||
&mut self,
|
||||
item: &TileItem,
|
||||
ix: usize,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> impl IntoElement {
|
||||
let entity_id = cx.entity_id();
|
||||
let panel_view = item.panel.view();
|
||||
let is_occluded = {
|
||||
let panels = self.panels.clone();
|
||||
move |bounds: &Bounds<Pixels>| {
|
||||
let this_z = panels[ix].z_index;
|
||||
let this_ix = ix;
|
||||
panels.iter().enumerate().any(|(sub_ix, other_item)| {
|
||||
if sub_ix == this_ix {
|
||||
return false;
|
||||
}
|
||||
let other_is_above = (other_item.z_index > this_z)
|
||||
|| (other_item.z_index == this_z && sub_ix > this_ix);
|
||||
|
||||
other_is_above && other_item.bounds.intersects(bounds)
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.bg(cx.theme().background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().border)
|
||||
.absolute()
|
||||
.left(item.bounds.origin.x - px(1.))
|
||||
.top(item.bounds.origin.y - px(1.))
|
||||
.w(item.bounds.size.width + px(1.))
|
||||
.h(item.bounds.size.height + px(1.))
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.h_full()
|
||||
.overflow_hidden()
|
||||
.child(panel_view),
|
||||
)
|
||||
.children(self.render_resize_handles(cx, entity_id, item, &is_occluded))
|
||||
.child(self.render_drag_bar(cx, entity_id, item, &is_occluded))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn round_to_nearest_ten(value: Pixels) -> Pixels {
|
||||
px((value.0 / 10.0).round() * 10.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn round_point_to_nearest_ten(point: Point<Pixels>) -> Point<Pixels> {
|
||||
Point::new(round_to_nearest_ten(point.x), round_to_nearest_ten(point.y))
|
||||
}
|
||||
|
||||
impl FocusableView for Tiles {
|
||||
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
impl EventEmitter<PanelEvent> for Tiles {}
|
||||
impl EventEmitter<DismissEvent> for Tiles {}
|
||||
impl Render for Tiles {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let view = cx.view().clone();
|
||||
let view_id = view.entity_id();
|
||||
let panels = self.sorted_panels();
|
||||
let scroll_bounds =
|
||||
self.panels
|
||||
.iter()
|
||||
.fold(Bounds::default(), |acc: Bounds<Pixels>, item| Bounds {
|
||||
origin: Point {
|
||||
x: acc.origin.x.min(item.bounds.origin.x),
|
||||
y: acc.origin.y.min(item.bounds.origin.y),
|
||||
},
|
||||
size: Size {
|
||||
width: acc.size.width.max(item.bounds.right()),
|
||||
height: acc.size.height.max(item.bounds.bottom()),
|
||||
},
|
||||
});
|
||||
let scroll_size = scroll_bounds.size - size(scroll_bounds.origin.x, scroll_bounds.origin.y);
|
||||
|
||||
div()
|
||||
.relative()
|
||||
.bg(cx.theme().background)
|
||||
.child(
|
||||
div()
|
||||
.id("tiles")
|
||||
.track_scroll(&self.scroll_handle)
|
||||
.size_full()
|
||||
.overflow_scroll()
|
||||
.children(
|
||||
panels
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(ix, item)| self.render_panel(&item, ix, cx)),
|
||||
)
|
||||
.child({
|
||||
canvas(
|
||||
move |bounds, cx| view.update(cx, |r, _| r.bounds = bounds),
|
||||
|_, _, _| {},
|
||||
)
|
||||
.absolute()
|
||||
.size_full()
|
||||
}),
|
||||
)
|
||||
.on_mouse_up(
|
||||
MouseButton::Left,
|
||||
cx.listener(move |this, _event: &MouseUpEvent, cx| {
|
||||
if this.dragging_index.is_some()
|
||||
|| this.resizing_index.is_some()
|
||||
|| this.resizing_drag_data.is_some()
|
||||
{
|
||||
this.reset_current_index();
|
||||
this.resizing_drag_data = None;
|
||||
cx.emit(PanelEvent::LayoutChanged);
|
||||
cx.notify();
|
||||
}
|
||||
}),
|
||||
)
|
||||
.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener(move |this, event: &MouseDownEvent, cx| {
|
||||
if this.resizing_index.is_none() && this.dragging_index.is_none() {
|
||||
let position = event.position;
|
||||
if let Some((index, _)) = this.find_at_position(position) {
|
||||
this.bring_to_front(Some(index));
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.right_0()
|
||||
.bottom_0()
|
||||
.child(Scrollbar::both(
|
||||
view_id,
|
||||
self.scroll_state.clone(),
|
||||
self.scroll_handle.clone(),
|
||||
scroll_size,
|
||||
)),
|
||||
)
|
||||
.size_full()
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,10 @@
|
||||
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,
|
||||
};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, px, Axis, Element, Entity, InteractiveElement as _,
|
||||
IntoElement, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render,
|
||||
@@ -7,14 +14,6 @@ use gpui::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
resizable::{HANDLE_PADDING, HANDLE_SIZE, PANEL_MIN_SIZE},
|
||||
theme::ActiveTheme as _,
|
||||
AxisExt as _, StyledExt,
|
||||
};
|
||||
|
||||
use super::{DockArea, DockItem, PanelView, TabPanel};
|
||||
|
||||
#[derive(Clone, Render)]
|
||||
struct ResizePanel;
|
||||
|
||||
@@ -190,16 +189,6 @@ impl Dock {
|
||||
}
|
||||
});
|
||||
}
|
||||
DockItem::Tiles { view, .. } => {
|
||||
cx.defer({
|
||||
let view = view.clone();
|
||||
move |cx| {
|
||||
_ = dock_area.update(cx, |this, cx| {
|
||||
this.subscribe_panel(&view, cx);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
DockItem::Panel { .. } => {
|
||||
// Not supported
|
||||
}
|
||||
@@ -285,7 +274,7 @@ impl Dock {
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.bg(cx.theme().border)
|
||||
.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
|
||||
.when(axis.is_horizontal(), |this| this.h_full().w(HANDLE_SIZE))
|
||||
.when(axis.is_vertical(), |this| this.w_full().h(HANDLE_SIZE)),
|
||||
)
|
||||
@@ -383,8 +372,6 @@ impl Render for Dock {
|
||||
DockItem::Split { view, .. } => this.child(view.clone()),
|
||||
DockItem::Tabs { view, .. } => this.child(view.clone()),
|
||||
DockItem::Panel { view, .. } => this.child(view.clone().view()),
|
||||
// Not support to render Tiles and Tile into Dock
|
||||
DockItem::Tiles { .. } => this,
|
||||
})
|
||||
.child(self.render_resize_handle(cx))
|
||||
.child(DockElement {
|
||||
@@ -416,7 +403,7 @@ impl Element for DockElement {
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
cx: &mut gpui::WindowContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
(cx.request_layout(Style::default(), None), ())
|
||||
}
|
||||
@@ -426,7 +413,7 @@ impl Element for DockElement {
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
_: gpui::Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
_: &mut gpui::WindowContext,
|
||||
_: &mut WindowContext,
|
||||
) -> Self::PrepaintState {
|
||||
}
|
||||
|
||||
@@ -436,7 +423,7 @@ impl Element for DockElement {
|
||||
_: gpui::Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
cx: &mut gpui::WindowContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
cx.on_mouse_event({
|
||||
let view = self.view.clone();
|
||||
@@ -1,11 +1,14 @@
|
||||
use super::PanelEvent;
|
||||
use crate::{
|
||||
dock_area::panel::Panel,
|
||||
dock_area::state::PanelState,
|
||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||
};
|
||||
use gpui::{
|
||||
AppContext, EventEmitter, FocusHandle, FocusableView, ParentElement as _, Render, SharedString,
|
||||
Styled as _, WindowContext,
|
||||
};
|
||||
|
||||
use super::{Panel, PanelEvent, PanelState};
|
||||
use crate::theme::ActiveTheme;
|
||||
|
||||
pub(crate) struct InvalidPanel {
|
||||
name: SharedString,
|
||||
focus_handle: FocusHandle,
|
||||
@@ -27,7 +30,7 @@ impl Panel for InvalidPanel {
|
||||
"InvalidPanel".into()
|
||||
}
|
||||
|
||||
fn dump(&self, _cx: &AppContext) -> super::PanelState {
|
||||
fn dump(&self, _cx: &AppContext) -> PanelState {
|
||||
self.old_state.clone()
|
||||
}
|
||||
}
|
||||
@@ -49,7 +52,7 @@ impl Render for InvalidPanel {
|
||||
.flex_col()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
||||
.child(format!(
|
||||
"The `{}` panel type is not registered in PanelRegistry.",
|
||||
self.name.clone()
|
||||
@@ -1,12 +1,13 @@
|
||||
#[allow(clippy::module_inception)]
|
||||
mod dock;
|
||||
mod invalid_panel;
|
||||
mod panel;
|
||||
mod stack_panel;
|
||||
mod state;
|
||||
mod tab_panel;
|
||||
mod tiles;
|
||||
|
||||
use crate::{
|
||||
dock_area::{
|
||||
dock::{Dock, DockPlacement},
|
||||
panel::{Panel, PanelEvent, PanelStyle, PanelView},
|
||||
stack_panel::StackPanel,
|
||||
state::{DockAreaState, DockState},
|
||||
tab_panel::TabPanel,
|
||||
},
|
||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use gpui::{
|
||||
actions, canvas, div, prelude::FluentBuilder, AnyElement, AnyView, AppContext, Axis, Bounds,
|
||||
@@ -14,16 +15,15 @@ use gpui::{
|
||||
ParentElement as _, Pixels, Render, SharedString, Styled, Subscription, View, ViewContext,
|
||||
VisualContext, WeakView, WindowContext,
|
||||
};
|
||||
use panel::PanelRegistry;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use dock::*;
|
||||
pub use panel::*;
|
||||
pub use stack_panel::*;
|
||||
pub use state::*;
|
||||
pub use tab_panel::*;
|
||||
pub use tiles::*;
|
||||
|
||||
use crate::theme::{scale::ColorScaleStep, ActiveTheme};
|
||||
pub mod dock;
|
||||
pub mod invalid_panel;
|
||||
pub mod panel;
|
||||
pub mod stack_panel;
|
||||
pub mod state;
|
||||
pub mod tab_panel;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.set_global(PanelRegistry::new());
|
||||
@@ -88,11 +88,6 @@ pub enum DockItem {
|
||||
},
|
||||
/// Panel layout
|
||||
Panel { view: Arc<dyn PanelView> },
|
||||
/// Tiles layout
|
||||
Tiles {
|
||||
items: Vec<TileItem>,
|
||||
view: View<Tiles>,
|
||||
},
|
||||
}
|
||||
|
||||
impl DockItem {
|
||||
@@ -159,51 +154,6 @@ impl DockItem {
|
||||
Self::Panel { view: panel }
|
||||
}
|
||||
|
||||
/// Create DockItem with tiles layout
|
||||
///
|
||||
/// This items and metas should have the same length.
|
||||
pub fn tiles(
|
||||
items: Vec<DockItem>,
|
||||
metas: Vec<impl Into<TileMeta> + Copy>,
|
||||
dock_area: &WeakView<DockArea>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Self {
|
||||
assert!(items.len() == metas.len());
|
||||
|
||||
let tile_panel = cx.new_view(|cx| {
|
||||
let mut tiles = Tiles::new(cx);
|
||||
for (ix, item) in items.clone().into_iter().enumerate() {
|
||||
match item {
|
||||
DockItem::Tabs { view, .. } => {
|
||||
let meta: TileMeta = metas[ix].into();
|
||||
let tile_item =
|
||||
TileItem::new(Arc::new(view), meta.bounds).z_index(meta.z_index);
|
||||
tiles.add_item(tile_item, dock_area, cx);
|
||||
}
|
||||
_ => {
|
||||
// Ignore non-tabs items
|
||||
}
|
||||
}
|
||||
}
|
||||
tiles
|
||||
});
|
||||
|
||||
cx.defer({
|
||||
let tile_panel = tile_panel.clone();
|
||||
let dock_area = dock_area.clone();
|
||||
move |cx| {
|
||||
_ = dock_area.update(cx, |this, cx| {
|
||||
this.subscribe_panel(&tile_panel, cx);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Self::Tiles {
|
||||
items: tile_panel.read(cx).panels.clone(),
|
||||
view: tile_panel,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create DockItem with tabs layout, items are displayed as tabs.
|
||||
///
|
||||
/// The `active_ix` is the index of the active tab, if `None` the first tab is active.
|
||||
@@ -256,7 +206,6 @@ impl DockItem {
|
||||
match self {
|
||||
Self::Split { view, .. } => Arc::new(view.clone()),
|
||||
Self::Tabs { view, .. } => Arc::new(view.clone()),
|
||||
Self::Tiles { view, .. } => Arc::new(view.clone()),
|
||||
Self::Panel { view, .. } => view.clone(),
|
||||
}
|
||||
}
|
||||
@@ -269,14 +218,6 @@ impl DockItem {
|
||||
}
|
||||
Self::Tabs { items, .. } => items.iter().find(|item| *item == &panel).cloned(),
|
||||
Self::Panel { view } => Some(view.clone()),
|
||||
Self::Tiles { items, .. } => items.iter().find_map(|item| {
|
||||
#[allow(clippy::op_ref)]
|
||||
if &item.panel == &panel {
|
||||
Some(item.panel.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,7 +253,6 @@ impl DockItem {
|
||||
stack_panel.add_panel(new_item.view(), None, dock_area.clone(), cx);
|
||||
});
|
||||
}
|
||||
Self::Tiles { .. } => {}
|
||||
Self::Panel { .. } => {}
|
||||
}
|
||||
}
|
||||
@@ -330,7 +270,6 @@ impl DockItem {
|
||||
item.set_collapsed(collapsed, cx);
|
||||
}
|
||||
}
|
||||
DockItem::Tiles { .. } => {}
|
||||
DockItem::Panel { .. } => {}
|
||||
}
|
||||
}
|
||||
@@ -340,7 +279,6 @@ impl DockItem {
|
||||
match self {
|
||||
DockItem::Tabs { view, .. } => Some(view.clone()),
|
||||
DockItem::Split { view, .. } => view.read(cx).left_top_tab_panel(true, cx),
|
||||
DockItem::Tiles { .. } => None,
|
||||
DockItem::Panel { .. } => None,
|
||||
}
|
||||
}
|
||||
@@ -350,7 +288,6 @@ impl DockItem {
|
||||
match self {
|
||||
DockItem::Tabs { view, .. } => Some(view.clone()),
|
||||
DockItem::Split { view, .. } => view.read(cx).right_top_tab_panel(true, cx),
|
||||
DockItem::Tiles { .. } => None,
|
||||
DockItem::Panel { .. } => None,
|
||||
}
|
||||
}
|
||||
@@ -403,13 +340,7 @@ impl DockArea {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
// FIXME: Remove this method after 2025-01-01
|
||||
#[deprecated(note = "Use `set_center` instead")]
|
||||
pub fn set_root(&mut self, item: DockItem, cx: &mut ViewContext<Self>) {
|
||||
self.set_center(item, cx);
|
||||
}
|
||||
|
||||
/// The the DockItem as the center of the dock area.
|
||||
/// The DockItem as the center of the dock area.
|
||||
///
|
||||
/// This is used to render at the Center of the DockArea.
|
||||
pub fn set_center(&mut self, item: DockItem, cx: &mut ViewContext<Self>) {
|
||||
@@ -722,9 +653,6 @@ impl DockArea {
|
||||
DockItem::Tabs { .. } => {
|
||||
// We subscribe to the tab panel event in StackPanel's insert_panel
|
||||
}
|
||||
DockItem::Tiles { .. } => {
|
||||
// We subscribe to the tab panel event in Tiles's [`add_item`](Tiles::add_item)
|
||||
}
|
||||
DockItem::Panel { .. } => {
|
||||
// Not supported
|
||||
}
|
||||
@@ -794,7 +722,6 @@ impl DockArea {
|
||||
match &self.items {
|
||||
DockItem::Split { view, .. } => view.clone().into_any_element(),
|
||||
DockItem::Tabs { view, .. } => view.clone().into_any_element(),
|
||||
DockItem::Tiles { view, .. } => view.clone().into_any_element(),
|
||||
DockItem::Panel { view, .. } => view.clone().view().into_any_element(),
|
||||
}
|
||||
}
|
||||
@@ -842,49 +769,41 @@ impl Render for DockArea {
|
||||
if let Some(zoom_view) = self.zoom_view.clone() {
|
||||
this.child(zoom_view)
|
||||
} else {
|
||||
match &self.items {
|
||||
DockItem::Tiles { view, .. } => {
|
||||
// render tiles
|
||||
this.child(view.clone())
|
||||
}
|
||||
_ => {
|
||||
// render dock
|
||||
this.child(
|
||||
// render dock
|
||||
this.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.h_full()
|
||||
// Left dock
|
||||
.when_some(self.left_dock.clone(), |this, dock| {
|
||||
this.bg(cx.theme().base.step(cx, ColorScaleStep::ONE))
|
||||
.child(div().flex().flex_none().child(dock))
|
||||
})
|
||||
// Center
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.h_full()
|
||||
// Left dock
|
||||
.when_some(self.left_dock.clone(), |this, dock| {
|
||||
this.bg(cx.theme().base.step(cx, ColorScaleStep::ONE))
|
||||
.child(div().flex().flex_none().child(dock))
|
||||
})
|
||||
// Center
|
||||
.flex_1()
|
||||
.flex_col()
|
||||
.overflow_hidden()
|
||||
// Top center
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_1()
|
||||
.flex_col()
|
||||
.overflow_hidden()
|
||||
// Top center
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.overflow_hidden()
|
||||
.child(self.render_items(cx)),
|
||||
)
|
||||
// Bottom Dock
|
||||
.when_some(self.bottom_dock.clone(), |this, dock| {
|
||||
this.child(dock)
|
||||
}),
|
||||
.child(self.render_items(cx)),
|
||||
)
|
||||
// Right Dock
|
||||
.when_some(self.right_dock.clone(), |this, dock| {
|
||||
this.child(div().flex().flex_none().child(dock))
|
||||
// Bottom Dock
|
||||
.when_some(self.bottom_dock.clone(), |this, dock| {
|
||||
this.child(dock)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
// Right Dock
|
||||
.when_some(self.right_dock.clone(), |this, dock| {
|
||||
this.child(div().flex().flex_none().child(dock))
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
use super::DockArea;
|
||||
use crate::{
|
||||
button::Button,
|
||||
dock_area::state::{PanelInfo, PanelState},
|
||||
popup_menu::PopupMenu,
|
||||
};
|
||||
use gpui::{
|
||||
AnyElement, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView, Global, Hsla,
|
||||
IntoElement, SharedString, View, WeakView, WindowContext,
|
||||
@@ -5,9 +11,6 @@ use gpui::{
|
||||
use nostr_sdk::prelude::Metadata;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use super::{DockArea, PanelInfo, PanelState};
|
||||
use crate::{button::Button, popup_menu::PopupMenu};
|
||||
|
||||
pub enum PanelEvent {
|
||||
ZoomIn,
|
||||
ZoomOut,
|
||||
@@ -1,19 +1,25 @@
|
||||
use gpui::*;
|
||||
use prelude::FluentBuilder;
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{DockArea, Panel, PanelEvent, PanelState, PanelView, TabPanel};
|
||||
use super::{DockArea, PanelEvent};
|
||||
use crate::{
|
||||
dock::PanelInfo,
|
||||
dock_area::{
|
||||
panel::{Panel, PanelView},
|
||||
state::{PanelInfo, PanelState},
|
||||
tab_panel::TabPanel,
|
||||
},
|
||||
h_flex,
|
||||
resizable::{
|
||||
h_resizable, resizable_panel, v_resizable, ResizablePanel, ResizablePanelEvent,
|
||||
ResizablePanelGroup,
|
||||
},
|
||||
theme::ActiveTheme,
|
||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||
AxisExt as _, Placement,
|
||||
};
|
||||
use gpui::{
|
||||
prelude::FluentBuilder, AppContext, Axis, DismissEvent, EventEmitter, FocusHandle,
|
||||
FocusableView, IntoElement, ParentElement, Pixels, Render, SharedString, Styled, Subscription,
|
||||
View, ViewContext, VisualContext as _, WeakView,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct StackPanel {
|
||||
pub(super) parent: Option<WeakView<StackPanel>>,
|
||||
@@ -362,14 +368,17 @@ impl FocusableView for StackPanel {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<PanelEvent> for StackPanel {}
|
||||
|
||||
impl EventEmitter<DismissEvent> for StackPanel {}
|
||||
|
||||
impl Render for StackPanel {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
h_flex()
|
||||
.size_full()
|
||||
.overflow_hidden()
|
||||
.bg(cx.theme().tab_bar)
|
||||
.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
|
||||
.child(self.panel_group.clone())
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
use super::{invalid_panel::InvalidPanel, Dock, DockArea, DockItem, PanelRegistry};
|
||||
use crate::dock_area::{dock::DockPlacement, panel::Panel};
|
||||
use gpui::{
|
||||
point, px, size, AppContext, Axis, Bounds, Pixels, View, VisualContext as _, WeakView,
|
||||
WindowContext,
|
||||
@@ -5,15 +7,11 @@ use gpui::{
|
||||
use itertools::Itertools as _;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{
|
||||
invalid_panel::InvalidPanel, Dock, DockArea, DockItem, DockPlacement, Panel, PanelRegistry,
|
||||
};
|
||||
|
||||
/// Used to serialize and deserialize the DockArea
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DockAreaState {
|
||||
/// The version is used to mark this persisted state is compatible with the current version
|
||||
/// For example, some times we many totally changed the structure of the Panel,
|
||||
/// For example, sometimes we many totally changed the structure of the Panel,
|
||||
/// then we can compare the version to decide whether we can use the state or ignore.
|
||||
#[serde(default)]
|
||||
pub version: Option<usize>,
|
||||
@@ -106,8 +104,6 @@ pub enum PanelInfo {
|
||||
Tabs { active_index: usize },
|
||||
#[serde(rename = "panel")]
|
||||
Panel(serde_json::Value),
|
||||
#[serde(rename = "tiles")]
|
||||
Tiles { metas: Vec<TileMeta> },
|
||||
}
|
||||
|
||||
impl PanelInfo {
|
||||
@@ -126,10 +122,6 @@ impl PanelInfo {
|
||||
Self::Panel(info)
|
||||
}
|
||||
|
||||
pub fn tiles(metas: Vec<TileMeta>) -> Self {
|
||||
Self::Tiles { metas }
|
||||
}
|
||||
|
||||
pub fn axis(&self) -> Option<Axis> {
|
||||
match self {
|
||||
Self::Stack { axis, .. } => Some(if *axis == 0 {
|
||||
@@ -232,7 +224,6 @@ impl PanelState {
|
||||
|
||||
DockItem::tabs(vec![view.into()], None, &dock_area, cx)
|
||||
}
|
||||
PanelInfo::Tiles { metas } => DockItem::tiles(items, metas, &dock_area, cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
use super::{
|
||||
ClosePanel, DockArea, DockPlacement, Panel, PanelEvent, PanelState, PanelStyle, PanelView,
|
||||
StackPanel, ToggleZoom,
|
||||
panel::PanelView, stack_panel::StackPanel, ClosePanel, DockArea, PanelEvent, PanelStyle,
|
||||
ToggleZoom,
|
||||
};
|
||||
use crate::{
|
||||
button::{Button, ButtonVariants as _},
|
||||
dock::PanelInfo,
|
||||
dock_area::{
|
||||
dock::DockPlacement,
|
||||
panel::Panel,
|
||||
state::{PanelInfo, PanelState},
|
||||
},
|
||||
h_flex,
|
||||
popup_menu::{PopupMenu, PopupMenuExt},
|
||||
tab::{tab_bar::TabBar, Tab},
|
||||
@@ -613,7 +617,7 @@ impl TabPanel {
|
||||
this.rounded_l_none()
|
||||
.border_l_2()
|
||||
.border_r_0()
|
||||
.border_color(cx.theme().base.step(cx, ColorScaleStep::TWO))
|
||||
.border_color(cx.theme().base.step(cx, ColorScaleStep::THREE))
|
||||
})
|
||||
.on_drop(cx.listener(
|
||||
move |this, drag: &DragPanel, cx| {
|
||||
@@ -688,7 +692,7 @@ impl TabPanel {
|
||||
div()
|
||||
.invisible()
|
||||
.absolute()
|
||||
.bg(cx.theme().drop_target)
|
||||
.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
|
||||
.map(|this| match self.will_split_placement {
|
||||
Some(placement) => {
|
||||
let size = DefiniteLength::Fraction(0.35);
|
||||
@@ -2,7 +2,7 @@ use crate::{
|
||||
h_flex,
|
||||
input::ClearButton,
|
||||
list::{self, List, ListDelegate, ListItem},
|
||||
theme::ActiveTheme,
|
||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||
v_flex, Disableable, Icon, IconName, Sizable, Size, StyleSized, StyledExt,
|
||||
};
|
||||
use gpui::{
|
||||
@@ -197,7 +197,7 @@ where
|
||||
h_flex()
|
||||
.justify_center()
|
||||
.py_6()
|
||||
.text_color(cx.theme().muted_foreground.opacity(0.6))
|
||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
||||
.child(Icon::new(IconName::Inbox).size(px(28.)))
|
||||
.into_any_element()
|
||||
}
|
||||
@@ -525,16 +525,18 @@ where
|
||||
.when_some(self.title_prefix.clone(), |this, prefix| this.child(prefix))
|
||||
.child(title.clone())
|
||||
} else {
|
||||
div().text_color(cx.theme().accent_foreground).child(
|
||||
self.placeholder
|
||||
.clone()
|
||||
.unwrap_or_else(|| "Please select".into()),
|
||||
)
|
||||
div()
|
||||
.text_color(cx.theme().accent.step(cx, ColorScaleStep::ELEVEN))
|
||||
.child(
|
||||
self.placeholder
|
||||
.clone()
|
||||
.unwrap_or_else(|| "Please select".into()),
|
||||
)
|
||||
};
|
||||
|
||||
title.when(self.disabled, |this| {
|
||||
this.cursor_not_allowed()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -602,7 +604,7 @@ where
|
||||
.justify_between()
|
||||
.bg(cx.theme().background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().input)
|
||||
.border_color(cx.theme().base.step(cx, ColorScaleStep::FOUR))
|
||||
.rounded(px(cx.theme().radius))
|
||||
.when(cx.theme().shadow, |this| this.shadow_sm())
|
||||
.map(|this| {
|
||||
@@ -660,8 +662,10 @@ where
|
||||
Icon::new(icon)
|
||||
.xsmall()
|
||||
.text_color(match self.disabled {
|
||||
true => cx.theme().muted_foreground.opacity(0.5),
|
||||
false => cx.theme().muted_foreground,
|
||||
true => cx.theme().base.step(cx, ColorScaleStep::TEN),
|
||||
false => {
|
||||
cx.theme().base.step(cx, ColorScaleStep::ELEVEN)
|
||||
}
|
||||
})
|
||||
.when(self.disabled, |this| this.cursor_not_allowed()),
|
||||
)
|
||||
@@ -692,7 +696,9 @@ where
|
||||
.mt_1p5()
|
||||
.bg(cx.theme().background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().border)
|
||||
.border_color(
|
||||
cx.theme().base.step(cx, ColorScaleStep::FOUR),
|
||||
)
|
||||
.rounded(px(cx.theme().radius))
|
||||
.shadow_md()
|
||||
.on_mouse_down_out(|_, cx| {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::{theme::ActiveTheme, Sizable, Size};
|
||||
use crate::{
|
||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||
Sizable, Size,
|
||||
};
|
||||
use gpui::{
|
||||
prelude::FluentBuilder as _, svg, AnyElement, Hsla, IntoElement, Radians, Render, RenderOnce,
|
||||
SharedString, StyleRefinement, Styled, Svg, Transformation, View, VisualContext, WindowContext,
|
||||
@@ -313,7 +316,9 @@ impl From<Icon> for AnyElement {
|
||||
|
||||
impl Render for Icon {
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
|
||||
let text_color = self.text_color.unwrap_or_else(|| cx.theme().foreground);
|
||||
let text_color = self
|
||||
.text_color
|
||||
.unwrap_or_else(|| cx.theme().base.step(cx, ColorScaleStep::ELEVEN));
|
||||
|
||||
svg()
|
||||
.flex_none()
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use gpui::{Styled, WindowContext};
|
||||
|
||||
use crate::{
|
||||
button::{Button, ButtonVariants as _},
|
||||
theme::ActiveTheme as _,
|
||||
theme::{scale::ColorScaleStep, ActiveTheme as _},
|
||||
Icon, IconName, Sizable as _,
|
||||
};
|
||||
use gpui::{Styled, WindowContext};
|
||||
|
||||
pub(crate) struct ClearButton {}
|
||||
|
||||
@@ -12,7 +11,10 @@ impl ClearButton {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(cx: &mut WindowContext) -> Button {
|
||||
Button::new("clean")
|
||||
.icon(Icon::new(IconName::CircleX).text_color(cx.theme().muted_foreground))
|
||||
.icon(
|
||||
Icon::new(IconName::CircleX)
|
||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN)),
|
||||
)
|
||||
.ghost()
|
||||
.xsmall()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::TextInput;
|
||||
use crate::theme::ActiveTheme as _;
|
||||
use crate::theme::{scale::ColorScaleStep, ActiveTheme as _};
|
||||
use gpui::{
|
||||
fill, point, px, relative, size, Bounds, Corners, Element, ElementId, ElementInputHandler,
|
||||
GlobalElementId, IntoElement, LayoutId, MouseButton, MouseMoveEvent, PaintQuad, Path, Pixels,
|
||||
@@ -144,7 +144,7 @@ impl TextElement {
|
||||
),
|
||||
size(px(1.5), line_height),
|
||||
),
|
||||
cx.theme().colors.primary,
|
||||
cx.theme().accent.step(cx, ColorScaleStep::NINE),
|
||||
))
|
||||
};
|
||||
}
|
||||
@@ -357,14 +357,17 @@ impl Element for TextElement {
|
||||
let mut bounds = bounds;
|
||||
|
||||
let (display_text, text_color) = if text.is_empty() {
|
||||
(placeholder, cx.theme().muted_foreground)
|
||||
(
|
||||
placeholder,
|
||||
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
||||
)
|
||||
} else if input.masked {
|
||||
(
|
||||
"*".repeat(text.chars().count()).into(),
|
||||
cx.theme().foreground,
|
||||
cx.theme().base.step(cx, ColorScaleStep::TWELVE),
|
||||
)
|
||||
} else {
|
||||
(text, cx.theme().foreground)
|
||||
(text, cx.theme().base.step(cx, ColorScaleStep::TWELVE))
|
||||
};
|
||||
|
||||
let run = TextRun {
|
||||
@@ -480,7 +483,7 @@ impl Element for TextElement {
|
||||
|
||||
// Paint selections
|
||||
if let Some(path) = prepaint.selection_path.take() {
|
||||
cx.paint_path(path, cx.theme().selection);
|
||||
cx.paint_path(path, cx.theme().accent.step(cx, ColorScaleStep::FIVE));
|
||||
}
|
||||
|
||||
// Paint multi line text
|
||||
|
||||
@@ -23,18 +23,15 @@ use gpui::{
|
||||
// - Press Up,Down to move cursor up, down line if multi-line
|
||||
// - Move cursor to skip line eof empty chars.
|
||||
|
||||
use super::blink_cursor::BlinkCursor;
|
||||
use super::change::Change;
|
||||
use super::element::TextElement;
|
||||
use super::ClearButton;
|
||||
use super::{blink_cursor::BlinkCursor, change::Change, element::TextElement, ClearButton};
|
||||
|
||||
use crate::history::History;
|
||||
use crate::indicator::Indicator;
|
||||
use crate::scroll::{Scrollbar, ScrollbarAxis, ScrollbarState};
|
||||
use crate::theme::ActiveTheme;
|
||||
use crate::Size;
|
||||
use crate::StyledExt;
|
||||
use crate::{Sizable, StyleSized};
|
||||
use crate::{
|
||||
history::History,
|
||||
indicator::Indicator,
|
||||
scroll::{Scrollbar, ScrollbarAxis, ScrollbarState},
|
||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||
Sizable, Size, StyleSized, StyledExt,
|
||||
};
|
||||
|
||||
actions!(
|
||||
input,
|
||||
@@ -55,7 +52,9 @@ actions!(
|
||||
SelectAll,
|
||||
Home,
|
||||
End,
|
||||
SelectToHome,
|
||||
SelectToStartOfLine,
|
||||
SelectToEndOfLine,
|
||||
SelectToStart,
|
||||
SelectToEnd,
|
||||
ShowCharacterPalette,
|
||||
Copy,
|
||||
@@ -65,6 +64,8 @@ actions!(
|
||||
Redo,
|
||||
MoveToStartOfLine,
|
||||
MoveToEndOfLine,
|
||||
MoveToStart,
|
||||
MoveToEnd,
|
||||
TextChanged,
|
||||
]
|
||||
);
|
||||
@@ -98,16 +99,16 @@ pub fn init(cx: &mut AppContext) {
|
||||
KeyBinding::new("shift-down", SelectDown, Some(CONTEXT)),
|
||||
KeyBinding::new("home", Home, Some(CONTEXT)),
|
||||
KeyBinding::new("end", End, Some(CONTEXT)),
|
||||
KeyBinding::new("shift-home", SelectToHome, Some(CONTEXT)),
|
||||
KeyBinding::new("shift-end", SelectToEnd, Some(CONTEXT)),
|
||||
KeyBinding::new("shift-home", SelectToStartOfLine, Some(CONTEXT)),
|
||||
KeyBinding::new("shift-end", SelectToEndOfLine, Some(CONTEXT)),
|
||||
#[cfg(target_os = "macos")]
|
||||
KeyBinding::new("ctrl-shift-a", SelectToHome, Some(CONTEXT)),
|
||||
KeyBinding::new("ctrl-shift-a", SelectToStartOfLine, Some(CONTEXT)),
|
||||
#[cfg(target_os = "macos")]
|
||||
KeyBinding::new("ctrl-shift-e", SelectToEnd, Some(CONTEXT)),
|
||||
KeyBinding::new("ctrl-shift-e", SelectToEndOfLine, Some(CONTEXT)),
|
||||
#[cfg(target_os = "macos")]
|
||||
KeyBinding::new("shift-cmd-left", SelectToHome, Some(CONTEXT)),
|
||||
KeyBinding::new("shift-cmd-left", SelectToStartOfLine, Some(CONTEXT)),
|
||||
#[cfg(target_os = "macos")]
|
||||
KeyBinding::new("shift-cmd-right", SelectToEnd, Some(CONTEXT)),
|
||||
KeyBinding::new("shift-cmd-right", SelectToEndOfLine, Some(CONTEXT)),
|
||||
#[cfg(target_os = "macos")]
|
||||
KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, Some(CONTEXT)),
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -138,6 +139,14 @@ pub fn init(cx: &mut AppContext) {
|
||||
KeyBinding::new("cmd-z", Undo, Some(CONTEXT)),
|
||||
#[cfg(target_os = "macos")]
|
||||
KeyBinding::new("cmd-shift-z", Redo, Some(CONTEXT)),
|
||||
#[cfg(target_os = "macos")]
|
||||
KeyBinding::new("cmd-up", MoveToStart, Some(CONTEXT)),
|
||||
#[cfg(target_os = "macos")]
|
||||
KeyBinding::new("cmd-down", MoveToEnd, Some(CONTEXT)),
|
||||
#[cfg(target_os = "macos")]
|
||||
KeyBinding::new("cmd-shift-up", SelectToStart, Some(CONTEXT)),
|
||||
#[cfg(target_os = "macos")]
|
||||
KeyBinding::new("cmd-shift-down", SelectToEnd, Some(CONTEXT)),
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
KeyBinding::new("ctrl-z", Undo, Some(CONTEXT)),
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
@@ -145,7 +154,8 @@ pub fn init(cx: &mut AppContext) {
|
||||
]);
|
||||
}
|
||||
|
||||
type Affixes<T> = Option<Box<dyn Fn(&mut ViewContext<T>) -> AnyElement + 'static>>;
|
||||
type TextInputPrefix<T> = Option<Box<dyn Fn(&mut ViewContext<T>) -> AnyElement + 'static>>;
|
||||
type TextInputSuffix<T> = Option<Box<dyn Fn(&mut ViewContext<T>) -> AnyElement + 'static>>;
|
||||
type Validate = Option<Box<dyn Fn(&str) -> bool + 'static>>;
|
||||
|
||||
pub struct TextInput {
|
||||
@@ -154,8 +164,8 @@ pub struct TextInput {
|
||||
multi_line: bool,
|
||||
pub(super) history: History<Change>,
|
||||
pub(super) blink_cursor: Model<BlinkCursor>,
|
||||
pub(super) prefix: Affixes<Self>,
|
||||
pub(super) suffix: Affixes<Self>,
|
||||
pub(super) prefix: TextInputPrefix<Self>,
|
||||
pub(super) suffix: TextInputSuffix<Self>,
|
||||
pub(super) loading: bool,
|
||||
pub(super) placeholder: SharedString,
|
||||
pub(super) selected_range: Range<usize>,
|
||||
@@ -186,6 +196,8 @@ pub struct TextInput {
|
||||
scrollbar_state: Rc<Cell<ScrollbarState>>,
|
||||
/// The size of the scrollable content.
|
||||
pub(crate) scroll_size: gpui::Size<Pixels>,
|
||||
/// To remember the horizontal column (x-coordinate) of the cursor position.
|
||||
preferred_x_offset: Option<Pixels>,
|
||||
}
|
||||
|
||||
impl EventEmitter<InputEvent> for TextInput {}
|
||||
@@ -228,6 +240,7 @@ impl TextInput {
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
scrollbar_state: Rc::new(Cell::new(ScrollbarState::default())),
|
||||
scroll_size: gpui::size(px(0.), px(0.)),
|
||||
preferred_x_offset: None,
|
||||
};
|
||||
|
||||
// Observe the blink cursor to repaint the view when it changes.
|
||||
@@ -258,6 +271,133 @@ impl TextInput {
|
||||
self
|
||||
}
|
||||
|
||||
/// Called after moving the cursor. Updates preferred_x_offset if we know where the cursor now is.
|
||||
fn update_preferred_x_offset(&mut self, _cx: &mut ViewContext<Self>) {
|
||||
if let (Some(lines), Some(bounds)) = (&self.last_layout, &self.last_bounds) {
|
||||
let offset = self.cursor_offset();
|
||||
let line_height = self.last_line_height;
|
||||
|
||||
// Find which line and sub-line the cursor is on and its position
|
||||
let (_line_index, _sub_line_index, cursor_pos) =
|
||||
self.line_and_position_for_offset(offset, lines, line_height);
|
||||
|
||||
if let Some(pos) = cursor_pos {
|
||||
// Adjust by scroll offset
|
||||
let scroll_offset = bounds.origin;
|
||||
self.preferred_x_offset = Some(pos.x + scroll_offset.x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Find which line and sub-line the given offset belongs to, along with the position within that sub-line.
|
||||
fn line_and_position_for_offset(
|
||||
&self,
|
||||
offset: usize,
|
||||
lines: &[WrappedLine],
|
||||
line_height: Pixels,
|
||||
) -> (usize, usize, Option<Point<Pixels>>) {
|
||||
let mut prev_lines_offset = 0;
|
||||
let mut y_offset = px(0.);
|
||||
for (line_index, line) in lines.iter().enumerate() {
|
||||
let local_offset = offset.saturating_sub(prev_lines_offset);
|
||||
if let Some(pos) = line.position_for_index(local_offset, line_height) {
|
||||
let sub_line_index = (pos.y.0 / line_height.0) as usize;
|
||||
let adjusted_pos = point(pos.x, pos.y + y_offset);
|
||||
return (line_index, sub_line_index, Some(adjusted_pos));
|
||||
}
|
||||
|
||||
y_offset += line.size(line_height).height;
|
||||
prev_lines_offset += line.len() + 1;
|
||||
}
|
||||
(0, 0, None)
|
||||
}
|
||||
|
||||
/// Move the cursor vertically by one line (up or down) while preserving the column if possible.
|
||||
/// direction: -1 for up, +1 for down
|
||||
fn move_vertical(&mut self, direction: i32, cx: &mut ViewContext<Self>) {
|
||||
if self.is_single_line() {
|
||||
return;
|
||||
}
|
||||
|
||||
let (Some(lines), Some(bounds)) = (&self.last_layout, &self.last_bounds) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let offset = self.cursor_offset();
|
||||
let line_height = self.last_line_height;
|
||||
let (current_line_index, current_sub_line, current_pos) =
|
||||
self.line_and_position_for_offset(offset, lines, line_height);
|
||||
|
||||
let Some(current_pos) = current_pos else {
|
||||
return;
|
||||
};
|
||||
|
||||
let current_x = self
|
||||
.preferred_x_offset
|
||||
.unwrap_or_else(|| current_pos.x + bounds.origin.x);
|
||||
|
||||
let mut new_line_index = current_line_index;
|
||||
let mut new_sub_line = current_sub_line as i32;
|
||||
|
||||
new_sub_line += direction;
|
||||
|
||||
// Handle moving above the first line
|
||||
if direction == -1 && new_line_index == 0 && new_sub_line < 0 {
|
||||
// Move cursor to the beginning of the text
|
||||
self.move_to(0, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
if new_sub_line < 0 {
|
||||
if new_line_index > 0 {
|
||||
new_line_index -= 1;
|
||||
new_sub_line = lines[new_line_index].wrap_boundaries.len() as i32;
|
||||
} else {
|
||||
new_sub_line = 0;
|
||||
}
|
||||
} else {
|
||||
let max_sub_line = lines[new_line_index].wrap_boundaries.len() as i32;
|
||||
if new_sub_line > max_sub_line {
|
||||
if new_line_index < lines.len() - 1 {
|
||||
new_line_index += 1;
|
||||
new_sub_line = 0;
|
||||
} else {
|
||||
new_sub_line = max_sub_line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If after adjustment, still at the same position, do not proceed
|
||||
if new_line_index == current_line_index && new_sub_line == current_sub_line as i32 {
|
||||
return;
|
||||
}
|
||||
|
||||
let target_line = &lines[new_line_index];
|
||||
let line_x = current_x - bounds.origin.x;
|
||||
let target_sub_line = new_sub_line as usize;
|
||||
|
||||
let approx_pos = point(line_x, px(target_sub_line as f32 * line_height.0));
|
||||
let index_res = target_line.index_for_position(approx_pos, line_height);
|
||||
|
||||
let new_local_index = match index_res {
|
||||
Ok(i) => i + 1,
|
||||
Err(i) => i,
|
||||
};
|
||||
|
||||
let mut prev_lines_offset = 0;
|
||||
for (i, l) in lines.iter().enumerate() {
|
||||
if i == new_line_index {
|
||||
break;
|
||||
}
|
||||
prev_lines_offset += l.len() + 1;
|
||||
}
|
||||
|
||||
let new_offset = (prev_lines_offset + new_local_index).min(self.text.len());
|
||||
self.selected_range = new_offset..new_offset;
|
||||
self.pause_blink_cursor(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn is_multi_line(&self) -> bool {
|
||||
self.multi_line
|
||||
@@ -444,9 +584,7 @@ impl TextInput {
|
||||
return;
|
||||
}
|
||||
self.pause_blink_cursor(cx);
|
||||
|
||||
let offset = self.start_of_line(cx).saturating_sub(1);
|
||||
self.move_to(offset, cx);
|
||||
self.move_vertical(-1, cx);
|
||||
}
|
||||
|
||||
fn down(&mut self, _: &Down, cx: &mut ViewContext<Self>) {
|
||||
@@ -454,9 +592,7 @@ impl TextInput {
|
||||
return;
|
||||
}
|
||||
self.pause_blink_cursor(cx);
|
||||
|
||||
let offset = (self.end_of_line(cx) + 1).min(self.text.len());
|
||||
self.move_to(offset, cx);
|
||||
self.move_vertical(1, cx);
|
||||
}
|
||||
|
||||
fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
|
||||
@@ -500,12 +636,30 @@ impl TextInput {
|
||||
self.move_to(offset, cx);
|
||||
}
|
||||
|
||||
fn select_to_home(&mut self, _: &SelectToHome, cx: &mut ViewContext<Self>) {
|
||||
fn move_to_start(&mut self, _: &MoveToStart, cx: &mut ViewContext<Self>) {
|
||||
self.move_to(0, cx);
|
||||
}
|
||||
|
||||
fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext<Self>) {
|
||||
let end = self.text.len();
|
||||
self.move_to(end, cx);
|
||||
}
|
||||
|
||||
fn select_to_start(&mut self, _: &SelectToStart, cx: &mut ViewContext<Self>) {
|
||||
self.select_to(0, cx);
|
||||
}
|
||||
|
||||
fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext<Self>) {
|
||||
let end = self.text.len();
|
||||
self.select_to(end, cx);
|
||||
}
|
||||
|
||||
fn select_to_start_of_line(&mut self, _: &SelectToStartOfLine, cx: &mut ViewContext<Self>) {
|
||||
let offset = self.start_of_line(cx);
|
||||
self.select_to(offset, cx);
|
||||
}
|
||||
|
||||
fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext<Self>) {
|
||||
fn select_to_end_of_line(&mut self, _: &SelectToEndOfLine, cx: &mut ViewContext<Self>) {
|
||||
let offset = self.end_of_line(cx);
|
||||
self.select_to(offset, cx);
|
||||
}
|
||||
@@ -594,10 +748,15 @@ impl TextInput {
|
||||
|
||||
fn enter(&mut self, _: &Enter, cx: &mut ViewContext<Self>) {
|
||||
if self.is_multi_line() {
|
||||
let is_eof = self.selected_range.end == self.text.len();
|
||||
self.replace_text_in_range(None, "\n", cx);
|
||||
|
||||
// Move cursor to the start of the next line
|
||||
// TODO: To be test this line is valid
|
||||
self.move_to(self.next_boundary(self.cursor_offset()) - 1, cx);
|
||||
let mut new_offset = self.next_boundary(self.cursor_offset()) - 1;
|
||||
if is_eof {
|
||||
new_offset += 1;
|
||||
}
|
||||
self.move_to(new_offset, cx);
|
||||
}
|
||||
|
||||
cx.emit(InputEvent::PressEnter);
|
||||
@@ -721,6 +880,7 @@ impl TextInput {
|
||||
fn move_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
|
||||
self.selected_range = offset..offset;
|
||||
self.pause_blink_cursor(cx);
|
||||
self.update_preferred_x_offset(cx);
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
@@ -848,7 +1008,9 @@ impl TextInput {
|
||||
self.selected_range.end = word_range.end;
|
||||
}
|
||||
}
|
||||
|
||||
if self.selected_range.is_empty() {
|
||||
self.update_preferred_x_offset(cx);
|
||||
}
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
@@ -1084,6 +1246,7 @@ impl ViewInputHandler for TextInput {
|
||||
self.text = pending_text;
|
||||
self.selected_range = range.start + new_text.len()..range.start + new_text.len();
|
||||
self.marked_range.take();
|
||||
self.update_preferred_x_offset(cx);
|
||||
cx.emit(InputEvent::Change(self.text.clone()));
|
||||
cx.notify();
|
||||
}
|
||||
@@ -1192,11 +1355,21 @@ impl Render for TextInput {
|
||||
.on_action(cx.listener(Self::right))
|
||||
.on_action(cx.listener(Self::select_left))
|
||||
.on_action(cx.listener(Self::select_right))
|
||||
.when(self.multi_line, |this| {
|
||||
this.on_action(cx.listener(Self::up))
|
||||
.on_action(cx.listener(Self::down))
|
||||
.on_action(cx.listener(Self::select_up))
|
||||
.on_action(cx.listener(Self::select_down))
|
||||
})
|
||||
.on_action(cx.listener(Self::select_all))
|
||||
.on_action(cx.listener(Self::select_to_home))
|
||||
.on_action(cx.listener(Self::select_to_end))
|
||||
.on_action(cx.listener(Self::select_to_start_of_line))
|
||||
.on_action(cx.listener(Self::select_to_end_of_line))
|
||||
.on_action(cx.listener(Self::home))
|
||||
.on_action(cx.listener(Self::end))
|
||||
.on_action(cx.listener(Self::move_to_start))
|
||||
.on_action(cx.listener(Self::move_to_end))
|
||||
.on_action(cx.listener(Self::select_to_start))
|
||||
.on_action(cx.listener(Self::select_to_end))
|
||||
.on_action(cx.listener(Self::show_character_palette))
|
||||
.on_action(cx.listener(Self::copy))
|
||||
.on_action(cx.listener(Self::paste))
|
||||
@@ -1214,21 +1387,13 @@ impl Render for TextInput {
|
||||
.input_h(self.size)
|
||||
.input_text_size(self.text_size)
|
||||
.cursor_text()
|
||||
.when(self.multi_line, |this| {
|
||||
this.on_action(cx.listener(Self::up))
|
||||
.on_action(cx.listener(Self::down))
|
||||
.on_action(cx.listener(Self::select_up))
|
||||
.on_action(cx.listener(Self::select_down))
|
||||
.h_auto()
|
||||
})
|
||||
.when(self.multi_line, |this| this.h_auto())
|
||||
.when(self.appearance, |this| {
|
||||
this.bg(if self.disabled {
|
||||
cx.theme().muted
|
||||
cx.theme().base.step(cx, ColorScaleStep::FOUR)
|
||||
} else {
|
||||
cx.theme().background
|
||||
})
|
||||
.border_color(cx.theme().input)
|
||||
.border_1()
|
||||
.rounded(px(cx.theme().radius))
|
||||
.when(cx.theme().shadow, |this| this.shadow_sm())
|
||||
.when(focused, |this| this.outline(cx))
|
||||
@@ -1246,7 +1411,7 @@ impl Render for TextInput {
|
||||
.child(TextElement::new(cx.view().clone())),
|
||||
)
|
||||
.when(self.loading, |this| {
|
||||
this.child(Indicator::new().color(cx.theme().muted_foreground))
|
||||
this.child(Indicator::new().color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN)))
|
||||
})
|
||||
.when(
|
||||
self.cleanable && !self.loading && !self.text.is_empty() && self.is_single_line(),
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
use crate::{h_flex, theme::ActiveTheme};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder, rems, Div, IntoElement, ParentElement, RenderOnce, SharedString,
|
||||
Styled, WindowContext,
|
||||
};
|
||||
|
||||
const MASKED: &str = "•";
|
||||
|
||||
#[derive(Default, PartialEq, Eq)]
|
||||
pub enum TextAlign {
|
||||
#[default]
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct Label {
|
||||
base: Div,
|
||||
label: SharedString,
|
||||
align: TextAlign,
|
||||
marked: bool,
|
||||
}
|
||||
|
||||
impl Label {
|
||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
base: h_flex().line_height(rems(1.25)),
|
||||
label: label.into(),
|
||||
align: TextAlign::default(),
|
||||
marked: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_align(mut self, align: TextAlign) -> Self {
|
||||
self.align = align;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn text_left(mut self) -> Self {
|
||||
self.align = TextAlign::Left;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn text_center(mut self) -> Self {
|
||||
self.align = TextAlign::Center;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn text_right(mut self) -> Self {
|
||||
self.align = TextAlign::Right;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn masked(mut self, masked: bool) -> Self {
|
||||
self.marked = masked;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled for Label {
|
||||
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
||||
self.base.style()
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Label {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let text = self.label;
|
||||
|
||||
let text_display = if self.marked {
|
||||
MASKED.repeat(text.chars().count())
|
||||
} else {
|
||||
text.to_string()
|
||||
};
|
||||
|
||||
div().text_color(cx.theme().foreground).child(
|
||||
self.base
|
||||
.map(|this| match self.align {
|
||||
TextAlign::Left => this.justify_start(),
|
||||
TextAlign::Center => this.justify_center(),
|
||||
TextAlign::Right => this.justify_end(),
|
||||
})
|
||||
.map(|this| {
|
||||
if self.align == TextAlign::Left {
|
||||
this.child(div().size_full().child(text_display))
|
||||
} else {
|
||||
this.child(text_display)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
pub mod animation;
|
||||
pub mod badge;
|
||||
pub mod button;
|
||||
pub mod button_group;
|
||||
pub mod checkbox;
|
||||
pub mod clipboard;
|
||||
pub mod context_menu;
|
||||
pub mod divider;
|
||||
pub mod dock;
|
||||
pub mod dock_area;
|
||||
pub mod dropdown;
|
||||
pub mod history;
|
||||
pub mod indicator;
|
||||
pub mod input;
|
||||
pub mod label;
|
||||
pub mod list;
|
||||
pub mod modal;
|
||||
pub mod notification;
|
||||
@@ -51,7 +49,7 @@ mod window_border;
|
||||
/// You can initialize the UI module at your application's entry point.
|
||||
pub fn init(cx: &mut gpui::AppContext) {
|
||||
theme::init(cx);
|
||||
dock::init(cx);
|
||||
dock_area::init(cx);
|
||||
dropdown::init(cx);
|
||||
input::init(cx);
|
||||
list::init(cx);
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
use std::time::Duration;
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
use crate::Icon;
|
||||
use crate::{
|
||||
input::{InputEvent, TextInput},
|
||||
scroll::{Scrollbar, ScrollbarState},
|
||||
theme::ActiveTheme,
|
||||
v_flex, IconName, Size,
|
||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||
v_flex, Icon, IconName, Size,
|
||||
};
|
||||
use gpui::{
|
||||
actions, div, prelude::FluentBuilder, uniform_list, AnyElement, AppContext, Entity,
|
||||
actions, div, prelude::FluentBuilder, px, uniform_list, AnyElement, AppContext, Entity,
|
||||
FocusHandle, FocusableView, InteractiveElement, IntoElement, KeyBinding, Length,
|
||||
ListSizingBehavior, MouseButton, ParentElement, Render, SharedString, Styled, Task,
|
||||
UniformListScrollHandle, View, ViewContext, VisualContext, WindowContext,
|
||||
ListSizingBehavior, MouseButton, ParentElement, Render, ScrollStrategy, SharedString, Styled,
|
||||
Task, UniformListScrollHandle, View, ViewContext, VisualContext, WindowContext,
|
||||
};
|
||||
use gpui::{px, ScrollStrategy};
|
||||
use smol::Timer;
|
||||
use std::{cell::Cell, rc::Rc, time::Duration};
|
||||
|
||||
actions!(list, [Cancel, Confirm, SelectPrev, SelectNext]);
|
||||
|
||||
@@ -105,7 +101,10 @@ where
|
||||
let query_input = cx.new_view(|cx| {
|
||||
TextInput::new(cx)
|
||||
.appearance(false)
|
||||
.prefix(|cx| Icon::new(IconName::Search).text_color(cx.theme().muted_foreground))
|
||||
.prefix(|cx| {
|
||||
Icon::new(IconName::Search)
|
||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
|
||||
})
|
||||
.placeholder("Search...")
|
||||
.cleanable()
|
||||
});
|
||||
@@ -326,9 +325,9 @@ where
|
||||
.left(px(0.))
|
||||
.right(px(0.))
|
||||
.bottom(px(0.))
|
||||
.bg(cx.theme().list_active)
|
||||
.bg(cx.theme().accent.step(cx, ColorScaleStep::SIX))
|
||||
.border_1()
|
||||
.border_color(cx.theme().list_active_border),
|
||||
.border_color(cx.theme().accent.step(cx, ColorScaleStep::NINE)),
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -341,7 +340,7 @@ where
|
||||
.right(px(0.))
|
||||
.bottom(px(0.))
|
||||
.border_1()
|
||||
.border_color(cx.theme().list_active_border),
|
||||
.border_color(cx.theme().accent.step(cx, ColorScaleStep::NINE)),
|
||||
)
|
||||
})
|
||||
.on_mouse_down(
|
||||
@@ -418,7 +417,7 @@ where
|
||||
_ => this.py_1().px_2(),
|
||||
})
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().border)
|
||||
.border_color(cx.theme().base.step(cx, ColorScaleStep::THREE))
|
||||
.child(input),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use crate::{h_flex, theme::ActiveTheme, Disableable, Icon, IconName, Selectable, Sizable as _};
|
||||
use crate::{
|
||||
h_flex,
|
||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||
Disableable, Icon, IconName, Selectable, Sizable as _,
|
||||
};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, AnyElement, ClickEvent, Div, ElementId, InteractiveElement,
|
||||
IntoElement, MouseButton, MouseMoveEvent, ParentElement, RenderOnce, Stateful,
|
||||
@@ -123,7 +127,7 @@ impl RenderOnce for ListItem {
|
||||
let is_active = self.selected || self.confirmed;
|
||||
|
||||
self.base
|
||||
.text_color(cx.theme().foreground)
|
||||
.text_color(cx.theme().base.step(cx, ColorScaleStep::TWELVE))
|
||||
.relative()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
@@ -138,9 +142,11 @@ impl RenderOnce for ListItem {
|
||||
this
|
||||
}
|
||||
})
|
||||
.when(is_active, |this| this.bg(cx.theme().list_active))
|
||||
.when(is_active, |this| {
|
||||
this.bg(cx.theme().accent.step(cx, ColorScaleStep::NINE))
|
||||
})
|
||||
.when(!is_active && !self.disabled, |this| {
|
||||
this.hover(|this| this.bg(cx.theme().list_hover))
|
||||
this.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::TWO)))
|
||||
})
|
||||
// Mouse enter
|
||||
.when_some(self.on_mouse_enter, |this, on_mouse_enter| {
|
||||
@@ -158,14 +164,16 @@ 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().muted_foreground))
|
||||
},
|
||||
),
|
||||
)
|
||||
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),
|
||||
),
|
||||
)
|
||||
},
|
||||
))
|
||||
}),
|
||||
)
|
||||
.when_some(self.suffix, |this, suffix| this.child(suffix(cx)))
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
use crate::{
|
||||
animation::cubic_bezier,
|
||||
button::{Button, ButtonVariants as _},
|
||||
theme::{scale::ColorScaleStep, ActiveTheme as _},
|
||||
v_flex, ContextModal, IconName, Sizable as _,
|
||||
};
|
||||
use gpui::{
|
||||
actions, anchored, div, hsla, point, prelude::FluentBuilder, px, relative, Animation,
|
||||
AnimationExt as _, AnyElement, AppContext, Bounds, ClickEvent, Div, FocusHandle, Hsla,
|
||||
@@ -6,13 +12,6 @@ use gpui::{
|
||||
};
|
||||
use std::{rc::Rc, time::Duration};
|
||||
|
||||
use crate::{
|
||||
animation::cubic_bezier,
|
||||
button::{Button, ButtonVariants as _},
|
||||
theme::ActiveTheme as _,
|
||||
v_flex, ContextModal, IconName, Sizable as _,
|
||||
};
|
||||
|
||||
actions!(modal, [Escape]);
|
||||
|
||||
const CONTEXT: &str = "Modal";
|
||||
@@ -59,7 +58,7 @@ impl Modal {
|
||||
let base = v_flex()
|
||||
.bg(cx.theme().background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().border)
|
||||
.border_color(cx.theme().base.step(cx, ColorScaleStep::THREE))
|
||||
.rounded_lg()
|
||||
.shadow_xl()
|
||||
.min_h_48()
|
||||
|
||||
@@ -2,7 +2,7 @@ use gpui::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{cell::Cell, rc::Rc, time::Instant};
|
||||
|
||||
use crate::theme::ActiveTheme;
|
||||
use crate::theme::{scale::ColorScaleStep, ActiveTheme};
|
||||
|
||||
/// Scrollbar show mode.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
|
||||
@@ -294,7 +294,7 @@ impl Scrollbar {
|
||||
(
|
||||
cx.theme().scrollbar_thumb_hover,
|
||||
cx.theme().scrollbar,
|
||||
cx.theme().border,
|
||||
cx.theme().base.step(cx, ColorScaleStep::THREE),
|
||||
THUMB_INSET - px(1.),
|
||||
THUMB_RADIUS,
|
||||
)
|
||||
@@ -304,7 +304,7 @@ impl Scrollbar {
|
||||
(
|
||||
cx.theme().scrollbar_thumb_hover,
|
||||
cx.theme().scrollbar,
|
||||
cx.theme().border,
|
||||
cx.theme().base.step(cx, ColorScaleStep::THREE),
|
||||
THUMB_INSET - px(1.),
|
||||
THUMB_RADIUS,
|
||||
)
|
||||
|
||||
@@ -39,7 +39,7 @@ pub trait StyledExt: Styled + Sized {
|
||||
|
||||
/// Render a border with a width of 1px, color ring color
|
||||
fn outline(self, cx: &WindowContext) -> Self {
|
||||
self.border_color(cx.theme().ring)
|
||||
self.border_color(cx.theme().accent.step(cx, ColorScaleStep::NINE))
|
||||
}
|
||||
|
||||
/// Wraps the element in a ScrollView.
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use crate::{h_flex, theme::ActiveTheme, Disableable, Side, Sizable, Size};
|
||||
use crate::{
|
||||
h_flex,
|
||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||
Disableable, Side, Sizable, Size,
|
||||
};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, px, Animation, AnimationExt as _, AnyElement, Element,
|
||||
ElementId, GlobalElementId, InteractiveElement, IntoElement, LayoutId, ParentElement as _,
|
||||
@@ -105,8 +109,11 @@ impl Element for Switch {
|
||||
let on_click = self.on_click.clone();
|
||||
|
||||
let (bg, toggle_bg) = match self.checked {
|
||||
true => (theme.colors.primary, theme.background),
|
||||
false => (theme.input, theme.background),
|
||||
true => (
|
||||
theme.accent.step(cx, ColorScaleStep::NINE),
|
||||
theme.background,
|
||||
),
|
||||
false => (theme.base.step(cx, ColorScaleStep::FOUR), theme.background),
|
||||
};
|
||||
|
||||
let (bg, toggle_bg) = match self.disabled {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::theme::scale::ColorScaleStep;
|
||||
use crate::theme::ActiveTheme;
|
||||
use crate::Selectable;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
@@ -28,7 +29,7 @@ impl Tab {
|
||||
|
||||
Self {
|
||||
id: id.clone(),
|
||||
base: div().id(id).gap_1().py_1p5().px_3().h(px(30.)),
|
||||
base: div().id(id),
|
||||
label: label.into_any_element(),
|
||||
metadata,
|
||||
disabled: false,
|
||||
@@ -85,30 +86,58 @@ impl Styled for Tab {
|
||||
impl RenderOnce for Tab {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let (text_color, bg_color) = match (self.selected, self.disabled) {
|
||||
(true, false) => (cx.theme().tab_active_foreground, cx.theme().tab_active),
|
||||
(false, false) => (cx.theme().muted_foreground, cx.theme().tab),
|
||||
(true, false) => (
|
||||
cx.theme().base.step(cx, ColorScaleStep::TWELVE),
|
||||
cx.theme().background,
|
||||
),
|
||||
(false, false) => (
|
||||
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
||||
cx.theme().base.step(cx, ColorScaleStep::TWO),
|
||||
),
|
||||
// disabled
|
||||
(true, true) => (cx.theme().muted_foreground, cx.theme().tab_active),
|
||||
(false, true) => (cx.theme().muted_foreground, cx.theme().tab),
|
||||
(true, true) => (
|
||||
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
||||
cx.theme().base.step(cx, ColorScaleStep::TWO),
|
||||
),
|
||||
(false, true) => (
|
||||
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
||||
cx.theme().base.step(cx, ColorScaleStep::TWO),
|
||||
),
|
||||
};
|
||||
|
||||
self.base
|
||||
.h(px(30.))
|
||||
.relative()
|
||||
.flex()
|
||||
.items_center()
|
||||
.flex_shrink_0()
|
||||
.cursor_pointer()
|
||||
.overflow_hidden()
|
||||
.text_sm()
|
||||
.text_color(text_color)
|
||||
.bg(bg_color)
|
||||
.border_x_1()
|
||||
.border_color(cx.theme().transparent)
|
||||
.when(self.selected, |this| this.border_color(cx.theme().border))
|
||||
.text_sm()
|
||||
.when(self.selected, |this| {
|
||||
this.border_color(cx.theme().base.step(cx, ColorScaleStep::THREE))
|
||||
})
|
||||
.when(!self.selected, |this| {
|
||||
this.child(
|
||||
div()
|
||||
.absolute()
|
||||
.left_0()
|
||||
.bottom_0()
|
||||
.size_full()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().base.step(cx, ColorScaleStep::THREE)),
|
||||
)
|
||||
})
|
||||
.when_some(self.prefix, |this, prefix| {
|
||||
this.child(prefix).text_color(text_color)
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.px_3()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::scroll::ScrollbarShow;
|
||||
use colors::{default_color_scales, hsl};
|
||||
use gpui::{
|
||||
blue, hsla, transparent_black, AppContext, Global, Hsla, ModelContext, SharedString,
|
||||
ViewContext, WindowAppearance, WindowContext,
|
||||
AppContext, Global, Hsla, ModelContext, SharedString, ViewContext, WindowAppearance,
|
||||
WindowContext,
|
||||
};
|
||||
use scale::ColorScaleSet;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
@@ -13,249 +13,33 @@ pub mod scale;
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct ThemeColors {
|
||||
pub background: Hsla,
|
||||
pub border: Hsla,
|
||||
pub window_border: Hsla,
|
||||
pub accent: Hsla,
|
||||
pub accent_foreground: Hsla,
|
||||
pub card: Hsla,
|
||||
pub card_foreground: Hsla,
|
||||
pub danger: Hsla,
|
||||
pub danger_active: Hsla,
|
||||
pub danger_foreground: Hsla,
|
||||
pub danger_hover: Hsla,
|
||||
pub drag_border: Hsla,
|
||||
pub drop_target: Hsla,
|
||||
pub foreground: Hsla,
|
||||
pub input: Hsla,
|
||||
pub link: Hsla,
|
||||
pub link_active: Hsla,
|
||||
pub link_hover: Hsla,
|
||||
pub list: Hsla,
|
||||
pub list_active: Hsla,
|
||||
pub list_active_border: Hsla,
|
||||
pub list_even: Hsla,
|
||||
pub list_head: Hsla,
|
||||
pub list_hover: Hsla,
|
||||
pub muted: Hsla,
|
||||
pub muted_foreground: Hsla,
|
||||
pub popover: Hsla,
|
||||
pub popover_foreground: Hsla,
|
||||
pub primary: Hsla,
|
||||
pub primary_active: Hsla,
|
||||
pub primary_foreground: Hsla,
|
||||
pub primary_hover: Hsla,
|
||||
pub progress_bar: Hsla,
|
||||
pub ring: Hsla,
|
||||
pub transparent: Hsla,
|
||||
pub scrollbar: Hsla,
|
||||
pub scrollbar_thumb: Hsla,
|
||||
pub scrollbar_thumb_hover: Hsla,
|
||||
pub secondary: Hsla,
|
||||
pub secondary_active: Hsla,
|
||||
pub secondary_foreground: Hsla,
|
||||
pub secondary_hover: Hsla,
|
||||
pub selection: Hsla,
|
||||
pub skeleton: Hsla,
|
||||
pub slider_bar: Hsla,
|
||||
pub slider_thumb: Hsla,
|
||||
pub tab: Hsla,
|
||||
pub tab_active: Hsla,
|
||||
pub tab_active_foreground: Hsla,
|
||||
pub tab_bar: Hsla,
|
||||
pub tab_foreground: Hsla,
|
||||
pub title_bar: Hsla,
|
||||
pub title_bar_border: Hsla,
|
||||
pub window_border: Hsla,
|
||||
}
|
||||
|
||||
impl ThemeColors {
|
||||
pub fn light() -> Self {
|
||||
Self {
|
||||
background: hsl(0.0, 0.0, 100.),
|
||||
accent: hsl(240.0, 5.0, 96.0),
|
||||
accent_foreground: hsl(240.0, 5.9, 10.0),
|
||||
border: hsl(240.0, 5.9, 90.0),
|
||||
transparent: Hsla::transparent_black(),
|
||||
window_border: hsl(240.0, 5.9, 78.0),
|
||||
card: hsl(0.0, 0.0, 100.0),
|
||||
card_foreground: hsl(240.0, 10.0, 3.9),
|
||||
danger: hsl(0.0, 84.2, 60.2),
|
||||
danger_active: hsl(0.0, 84.2, 47.0),
|
||||
danger_foreground: hsl(0.0, 0.0, 98.0),
|
||||
danger_hover: hsl(0.0, 84.2, 65.0),
|
||||
drag_border: blue(),
|
||||
drop_target: hsl(235.0, 30., 44.0).opacity(0.25),
|
||||
foreground: hsl(240.0, 10., 3.9),
|
||||
input: hsl(240.0, 5.9, 90.0),
|
||||
link: hsl(221.0, 83.0, 53.0),
|
||||
link_active: hsl(221.0, 83.0, 53.0).darken(0.2),
|
||||
link_hover: hsl(221.0, 83.0, 53.0).lighten(0.2),
|
||||
list: hsl(0.0, 0.0, 100.),
|
||||
list_active: hsl(211.0, 97.0, 85.0).opacity(0.2),
|
||||
list_active_border: hsl(211.0, 97.0, 85.0),
|
||||
list_even: hsl(240.0, 5.0, 96.0),
|
||||
list_head: hsl(0.0, 0.0, 100.),
|
||||
list_hover: hsl(240.0, 4.8, 95.0),
|
||||
muted: hsl(240.0, 4.8, 95.9),
|
||||
muted_foreground: hsl(240.0, 3.8, 46.1),
|
||||
popover: hsl(0.0, 0.0, 100.0),
|
||||
popover_foreground: hsl(240.0, 10.0, 3.9),
|
||||
primary: hsl(223.0, 5.9, 10.0),
|
||||
primary_active: hsl(223.0, 1.9, 25.0),
|
||||
primary_foreground: hsl(223.0, 0.0, 98.0),
|
||||
primary_hover: hsl(223.0, 5.9, 15.0),
|
||||
progress_bar: hsl(223.0, 5.9, 10.0),
|
||||
ring: hsl(240.0, 5.9, 65.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.),
|
||||
secondary: hsl(240.0, 5.9, 96.9),
|
||||
secondary_active: hsl(240.0, 5.9, 90.),
|
||||
secondary_foreground: hsl(240.0, 59.0, 10.),
|
||||
secondary_hover: hsl(240.0, 5.9, 98.),
|
||||
selection: hsl(211.0, 97.0, 85.0),
|
||||
skeleton: hsl(223.0, 5.9, 10.0).opacity(0.1),
|
||||
slider_bar: hsl(223.0, 5.9, 10.0),
|
||||
slider_thumb: hsl(0.0, 0.0, 100.0),
|
||||
tab: transparent_black(),
|
||||
tab_active: hsl(0.0, 0.0, 100.0),
|
||||
tab_active_foreground: hsl(240.0, 10., 3.9),
|
||||
tab_bar: hsl(240.0, 4.8, 95.9),
|
||||
tab_foreground: hsl(240.0, 10., 3.9),
|
||||
title_bar: hsl(0.0, 0.0, 98.0),
|
||||
title_bar_border: hsl(220.0, 13.0, 91.0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dark() -> Self {
|
||||
Self {
|
||||
background: hsl(0.0, 0.0, 8.0),
|
||||
accent: hsl(240.0, 3.7, 15.9),
|
||||
accent_foreground: hsl(0.0, 0.0, 78.0),
|
||||
border: hsl(240.0, 3.7, 16.9),
|
||||
transparent: Hsla::transparent_black(),
|
||||
window_border: hsl(240.0, 3.7, 28.0),
|
||||
card: hsl(0.0, 0.0, 8.0),
|
||||
card_foreground: hsl(0.0, 0.0, 78.0),
|
||||
danger: hsl(0.0, 62.8, 30.6),
|
||||
danger_active: hsl(0.0, 62.8, 20.6),
|
||||
danger_foreground: hsl(0.0, 0.0, 78.0),
|
||||
danger_hover: hsl(0.0, 62.8, 35.6),
|
||||
drag_border: blue(),
|
||||
drop_target: hsl(235.0, 30., 44.0).opacity(0.1),
|
||||
foreground: hsl(0., 0., 78.),
|
||||
input: hsl(240.0, 3.7, 15.9),
|
||||
link: hsl(221.0, 83.0, 53.0),
|
||||
link_active: hsl(221.0, 83.0, 53.0).darken(0.2),
|
||||
link_hover: hsl(221.0, 83.0, 53.0).lighten(0.2),
|
||||
list: hsl(0.0, 0.0, 8.0),
|
||||
list_active: hsl(240.0, 3.7, 15.0).opacity(0.2),
|
||||
list_active_border: hsl(240.0, 5.9, 35.5),
|
||||
list_even: hsl(240.0, 3.7, 10.0),
|
||||
list_head: hsl(0.0, 0.0, 8.0),
|
||||
list_hover: hsl(240.0, 3.7, 15.9),
|
||||
muted: hsl(240.0, 3.7, 15.9),
|
||||
muted_foreground: hsl(240.0, 5.0, 64.9),
|
||||
popover: hsl(0.0, 0.0, 10.),
|
||||
popover_foreground: hsl(0.0, 0.0, 78.0),
|
||||
primary: hsl(223.0, 0.0, 98.0),
|
||||
primary_active: hsl(223.0, 0.0, 80.0),
|
||||
primary_foreground: hsl(223.0, 5.9, 10.0),
|
||||
primary_hover: hsl(223.0, 0.0, 90.0),
|
||||
progress_bar: hsl(223.0, 0.0, 98.0),
|
||||
ring: hsl(240.0, 4.9, 83.9),
|
||||
scrollbar: hsl(240., 1., 15.).opacity(0.75),
|
||||
scrollbar_thumb: hsl(0., 0., 48.).opacity(0.9),
|
||||
scrollbar_thumb_hover: hsl(0., 0., 68.),
|
||||
secondary: hsl(240.0, 0., 13.0),
|
||||
secondary_active: hsl(240.0, 0., 10.),
|
||||
secondary_foreground: hsl(0.0, 0.0, 78.0),
|
||||
secondary_hover: hsl(240.0, 0., 15.),
|
||||
selection: hsl(211.0, 97.0, 22.0),
|
||||
skeleton: hsla(223.0, 0.0, 98.0, 0.1),
|
||||
slider_bar: hsl(223.0, 0.0, 98.0),
|
||||
slider_thumb: hsl(0.0, 0.0, 8.0),
|
||||
tab: transparent_black(),
|
||||
tab_active: hsl(0.0, 0.0, 8.0),
|
||||
tab_active_foreground: hsl(0., 0., 78.),
|
||||
tab_bar: hsl(299.0, 0., 5.5),
|
||||
tab_foreground: hsl(0., 0., 78.),
|
||||
title_bar: hsl(240.0, 0.0, 10.0),
|
||||
title_bar_border: hsl(240.0, 3.7, 15.9),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Colorize {
|
||||
fn opacity(&self, opacity: f32) -> Hsla;
|
||||
fn divide(&self, divisor: f32) -> Hsla;
|
||||
fn invert(&self) -> Hsla;
|
||||
fn invert_l(&self) -> Hsla;
|
||||
fn lighten(&self, amount: f32) -> Hsla;
|
||||
fn darken(&self, amount: f32) -> Hsla;
|
||||
fn apply(&self, base_color: Hsla) -> Hsla;
|
||||
}
|
||||
|
||||
impl Colorize for Hsla {
|
||||
/// Returns a new color with the given opacity.
|
||||
///
|
||||
/// The opacity is a value between 0.0 and 1.0, where 0.0 is fully transparent and 1.0 is fully opaque.
|
||||
fn opacity(&self, factor: f32) -> Hsla {
|
||||
Hsla {
|
||||
a: self.a * factor.clamp(0.0, 1.0),
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new color with each channel divided by the given divisor.
|
||||
///
|
||||
/// The divisor in range of 0.0 .. 1.0
|
||||
fn divide(&self, divisor: f32) -> Hsla {
|
||||
Hsla {
|
||||
a: divisor,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Return inverted color
|
||||
fn invert(&self) -> Hsla {
|
||||
Hsla {
|
||||
h: (self.h + 1.8) % 3.6,
|
||||
s: 1.0 - self.s,
|
||||
l: 1.0 - self.l,
|
||||
a: self.a,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return inverted lightness
|
||||
fn invert_l(&self) -> Hsla {
|
||||
Hsla {
|
||||
l: 1.0 - self.l,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a new color with the lightness increased by the given factor.
|
||||
///
|
||||
/// factor range: 0.0 .. 1.0
|
||||
fn lighten(&self, factor: f32) -> Hsla {
|
||||
let l = self.l * (1.0 + factor.clamp(0.0, 1.0));
|
||||
|
||||
Hsla { l, ..*self }
|
||||
}
|
||||
|
||||
/// Return a new color with the darkness increased by the given factor.
|
||||
///
|
||||
/// factor range: 0.0 .. 1.0
|
||||
fn darken(&self, factor: f32) -> Hsla {
|
||||
let l = self.l * (1.0 - factor.clamp(0.0, 1.0));
|
||||
|
||||
Hsla { l, ..*self }
|
||||
}
|
||||
|
||||
/// Return a new color with the same lightness and alpha but different hue and saturation.
|
||||
fn apply(&self, new_color: Hsla) -> Hsla {
|
||||
Hsla {
|
||||
h: new_color.h,
|
||||
s: new_color.s,
|
||||
l: self.l,
|
||||
a: self.a,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,7 +77,7 @@ pub fn init(cx: &mut AppContext) {
|
||||
}
|
||||
|
||||
pub struct Theme {
|
||||
pub colors: ThemeColors,
|
||||
colors: ThemeColors,
|
||||
/// Base colors.
|
||||
pub base: ColorScaleSet,
|
||||
/// Accent colors.
|
||||
@@ -304,7 +88,6 @@ pub struct Theme {
|
||||
pub font_size: f32,
|
||||
pub radius: f32,
|
||||
pub shadow: bool,
|
||||
pub transparent: Hsla,
|
||||
/// Show the scrollbar mode, default: Scrolling
|
||||
pub scrollbar_show: ScrollbarShow,
|
||||
}
|
||||
@@ -370,7 +153,6 @@ impl From<ThemeColors> for Theme {
|
||||
base: color_scales.gray,
|
||||
accent: color_scales.yellow,
|
||||
appearance: Appearance::default(),
|
||||
transparent: Hsla::transparent_black(),
|
||||
font_size: 16.0,
|
||||
font_family: if cfg!(target_os = "macos") {
|
||||
".SystemUIFont".into()
|
||||
|
||||
Reference in New Issue
Block a user