Add ResizablePanel (#22)
https://github.com/user-attachments/assets/b4067c1d-0ef0-4de4-bd7a-55f446d35ff0
This commit is contained in:
parent
9e935bed56
commit
2401d5d5a4
15 changed files with 488 additions and 37 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
|
@ -5427,6 +5427,7 @@ dependencies = [
|
|||
"taffy",
|
||||
"unicode-segmentation",
|
||||
"usvg",
|
||||
"uuid",
|
||||
"windows",
|
||||
"wry",
|
||||
]
|
||||
|
|
@ -5577,9 +5578,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.8.0"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
|
||||
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
|
||||
dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
"serde",
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ This is an example of build app by using GPUI.
|
|||
- [x] Floating Popover
|
||||
- [x] Child window Popover
|
||||
- [x] Dockpanel
|
||||
- [ ] Splitter
|
||||
- [x] Resizable
|
||||
- [ ] Progress
|
||||
- [ ] ProgressBar
|
||||
- [ ] Loading
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ use gpui::*;
|
|||
use prelude::FluentBuilder as _;
|
||||
use story::{
|
||||
ButtonStory, CheckboxStory, DropdownStory, ImageStory, InputStory, ListStory, PickerStory,
|
||||
PopoverStory, ProgressStory, StoryContainer, SwitchStory, TableStory, TooltipStory,
|
||||
PopoverStory, ProgressStory, ResizableStory, StoryContainer, SwitchStory, TableStory,
|
||||
TooltipStory,
|
||||
};
|
||||
use workspace::{dock::DockPosition, TitleBar, Workspace};
|
||||
|
||||
|
|
@ -54,7 +55,7 @@ impl StoryWorkspace {
|
|||
InputStory::view(cx).into(),
|
||||
workspace.clone(),
|
||||
DockPosition::Right,
|
||||
px(500.0),
|
||||
px(350.0),
|
||||
cx,
|
||||
);
|
||||
|
||||
|
|
@ -62,7 +63,7 @@ impl StoryWorkspace {
|
|||
CheckboxStory::view(cx).into(),
|
||||
workspace.clone(),
|
||||
DockPosition::Bottom,
|
||||
px(300.),
|
||||
px(200.),
|
||||
cx,
|
||||
);
|
||||
|
||||
|
|
@ -115,7 +116,7 @@ impl StoryWorkspace {
|
|||
ListStory::view(cx).into(),
|
||||
workspace.clone(),
|
||||
DockPosition::Left,
|
||||
px(360.),
|
||||
px(300.),
|
||||
cx,
|
||||
);
|
||||
|
||||
|
|
@ -146,6 +147,15 @@ impl StoryWorkspace {
|
|||
)
|
||||
.detach();
|
||||
|
||||
StoryContainer::add_pane(
|
||||
"Resizable",
|
||||
"Accessible resizable panel groups and layouts with keyboard support.",
|
||||
ResizableStory::view(cx).into(),
|
||||
workspace.clone(),
|
||||
cx,
|
||||
)
|
||||
.detach();
|
||||
|
||||
Self { workspace }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ mod list_story;
|
|||
mod picker_story;
|
||||
mod popover_story;
|
||||
mod progress_story;
|
||||
mod resizable_story;
|
||||
mod switch_story;
|
||||
mod table_story;
|
||||
mod tooltip_story;
|
||||
|
|
@ -20,6 +21,7 @@ pub use list_story::ListStory;
|
|||
pub use picker_story::PickerStory;
|
||||
pub use popover_story::PopoverStory;
|
||||
pub use progress_story::ProgressStory;
|
||||
pub use resizable_story::ResizableStory;
|
||||
pub use switch_story::SwitchStory;
|
||||
pub use table_story::TableStory;
|
||||
pub use tooltip_story::TooltipStory;
|
||||
|
|
@ -206,7 +208,9 @@ impl StoryContainer {
|
|||
impl Render for StoryContainer {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.id("story-container")
|
||||
.size_full()
|
||||
.overflow_scroll()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ impl ListDelegate for ListItemDeletegate {
|
|||
.items
|
||||
.iter()
|
||||
.filter(|item| item.to_lowercase().contains(&query.to_lowercase()))
|
||||
.map(|s| s.clone())
|
||||
.cloned()
|
||||
.collect();
|
||||
cx.notify();
|
||||
}
|
||||
|
|
|
|||
109
crates/story/src/resizable_story.rs
Normal file
109
crates/story/src/resizable_story.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
use gpui::{
|
||||
div, px, AnyElement, IntoElement, ParentElement as _, Render, SharedString, Styled, View,
|
||||
ViewContext, VisualContext, WindowContext,
|
||||
};
|
||||
use ui::theme::ActiveTheme;
|
||||
use ui::{
|
||||
resizable::{h_resizable, resizable_panel, v_resizable, ResizablePanelGroup},
|
||||
v_flex,
|
||||
};
|
||||
|
||||
pub struct ResizableStory {
|
||||
group1: View<ResizablePanelGroup>,
|
||||
group2: View<ResizablePanelGroup>,
|
||||
}
|
||||
|
||||
impl ResizableStory {
|
||||
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||
cx.new_view(|cx| Self::new(cx))
|
||||
}
|
||||
|
||||
fn new(cx: &mut WindowContext) -> Self {
|
||||
fn panel_box(content: impl Into<SharedString>, cx: &WindowContext) -> AnyElement {
|
||||
div()
|
||||
.p_4()
|
||||
.border_1()
|
||||
.border_color(cx.theme().border)
|
||||
.size_full()
|
||||
.child(content.into())
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
let group1 = cx.new_view(|cx| {
|
||||
v_resizable()
|
||||
.group(
|
||||
h_resizable()
|
||||
.size(px(150.))
|
||||
.child(
|
||||
resizable_panel()
|
||||
.size(px(300.))
|
||||
.min_size(px(120.))
|
||||
.content(|cx| panel_box("Left 1 (Min 120px)", cx)),
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
resizable_panel()
|
||||
.size(px(400.))
|
||||
.min_size(px(100.))
|
||||
.content(|cx| panel_box("Center 1", cx)),
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
resizable_panel()
|
||||
.size(px(300.))
|
||||
.min_size(px(100.))
|
||||
.grow()
|
||||
.content(|cx| panel_box("Right (Grow)", cx)),
|
||||
cx,
|
||||
),
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
resizable_panel()
|
||||
.size(px(150.))
|
||||
.max_size(px(550.))
|
||||
.min_size(px(100.))
|
||||
.grow()
|
||||
.content(|cx| panel_box("Center (Grow)", cx)),
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
resizable_panel()
|
||||
.size(px(210.))
|
||||
.min_size(px(100.))
|
||||
.content(|cx| panel_box("Bottom", cx)),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let group2 = cx.new_view(|cx| {
|
||||
h_resizable()
|
||||
.child(
|
||||
resizable_panel()
|
||||
.size(px(300.))
|
||||
.min_size(px(100.))
|
||||
.content(|cx| panel_box("Left 2", cx)),
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
resizable_panel()
|
||||
.size(px(400.))
|
||||
.max_size(px(550.))
|
||||
.min_size(px(100.))
|
||||
.grow()
|
||||
.content(|cx| panel_box("Right (Grow)", cx)),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
Self { group1, group2 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ResizableStory {
|
||||
fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.child(self.group1.clone())
|
||||
.child(self.group2.clone())
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ usvg = { version = "0.41.0", default-features = false }
|
|||
taffy = "0.4.3"
|
||||
paste = "1"
|
||||
once_cell = "1.19.0"
|
||||
uuid = "1.10.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ pub mod popover;
|
|||
pub mod popup_menu;
|
||||
pub mod prelude;
|
||||
pub mod progress;
|
||||
pub mod resizable;
|
||||
pub mod switch;
|
||||
pub mod tab;
|
||||
pub mod table;
|
||||
|
|
|
|||
16
crates/ui/src/resizable/mod.rs
Normal file
16
crates/ui/src/resizable/mod.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
use gpui::Axis;
|
||||
|
||||
mod panel;
|
||||
pub use panel::*;
|
||||
|
||||
pub fn h_resizable() -> ResizablePanelGroup {
|
||||
ResizablePanelGroup::new().axis(Axis::Horizontal)
|
||||
}
|
||||
|
||||
pub fn v_resizable() -> ResizablePanelGroup {
|
||||
ResizablePanelGroup::new().axis(Axis::Vertical)
|
||||
}
|
||||
|
||||
pub fn resizable_panel() -> ResizablePanel {
|
||||
ResizablePanel::new()
|
||||
}
|
||||
278
crates/ui/src/resizable/panel.rs
Normal file
278
crates/ui/src/resizable/panel.rs
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
canvas, deferred, div, prelude::FluentBuilder as _, px, AnyElement, AnyView, Axis, Bounds,
|
||||
DragMoveEvent, EntityId, InteractiveElement as _, IntoElement, ParentElement, Pixels, Render,
|
||||
StatefulInteractiveElement, Styled, View, ViewContext, VisualContext as _, WindowContext,
|
||||
};
|
||||
|
||||
use crate::{h_flex, theme::ActiveTheme, v_flex};
|
||||
|
||||
#[derive(Clone, Render)]
|
||||
pub struct DragPanel(pub (EntityId, usize, Axis));
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ResizablePanelGroup {
|
||||
panels: Vec<View<ResizablePanel>>,
|
||||
sizes: Vec<Pixels>,
|
||||
axis: Axis,
|
||||
handle_size: Pixels,
|
||||
size: Pixels,
|
||||
}
|
||||
|
||||
impl ResizablePanelGroup {
|
||||
pub(super) fn new() -> Self {
|
||||
Self {
|
||||
axis: Axis::Horizontal,
|
||||
sizes: Vec::new(),
|
||||
panels: Vec::new(),
|
||||
handle_size: px(3.),
|
||||
size: px(20.),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the axis of the resizable panel group, default is horizontal.
|
||||
pub fn axis(mut self, axis: Axis) -> Self {
|
||||
self.axis = axis;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the size of the resize handle, default is 3px.
|
||||
///
|
||||
/// The handle size will inherit the parent group handle size, if you insert a group into another group.
|
||||
pub fn handle_size(mut self, size: Pixels) -> Self {
|
||||
self.handle_size = size;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a resizable panel to the group.
|
||||
pub fn child(mut self, panel: ResizablePanel, cx: &mut WindowContext) -> Self {
|
||||
let mut panel = panel;
|
||||
panel.axis = self.axis;
|
||||
self.sizes.push(panel.size);
|
||||
self.panels.push(cx.new_view(|_| panel));
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a ResizablePanelGroup as a child to the group.
|
||||
pub fn group(self, group: ResizablePanelGroup, cx: &mut WindowContext) -> Self {
|
||||
let mut group: ResizablePanelGroup = group;
|
||||
group.handle_size = self.handle_size;
|
||||
let size = group.size;
|
||||
let panel = ResizablePanel::new()
|
||||
.content_view(cx.new_view(|_| group).into())
|
||||
.size(size);
|
||||
self.child(panel, cx)
|
||||
}
|
||||
|
||||
/// Set size of the resizable panel group
|
||||
///
|
||||
/// - When the axis is horizontal, the size is the height of the group.
|
||||
/// - When the axis is vertical, the size is the width of the group.
|
||||
pub fn size(mut self, size: Pixels) -> Self {
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
fn render_resize_handle(&self, ix: usize, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let axis = self.axis;
|
||||
let handle_size = self.handle_size;
|
||||
|
||||
deferred(
|
||||
div()
|
||||
.id(("resizable-handle", ix))
|
||||
.occlude()
|
||||
.hover(|this| this.bg(cx.theme().drag_border))
|
||||
.on_drag_move(cx.listener(move |view, e: &DragMoveEvent<DragPanel>, cx| {
|
||||
match e.drag(cx) {
|
||||
DragPanel((entity_id, ix, axis)) => {
|
||||
let ix = *ix;
|
||||
if cx.entity_id() != *entity_id {
|
||||
return;
|
||||
}
|
||||
|
||||
let panel = view
|
||||
.panels
|
||||
.get(ix)
|
||||
.expect("BUG: invalid panel index")
|
||||
.read(cx);
|
||||
|
||||
match axis {
|
||||
Axis::Horizontal => {
|
||||
let size = e.event.position.x - panel.bounds.left();
|
||||
view.resize_panels(ix, size, cx)
|
||||
}
|
||||
Axis::Vertical => {
|
||||
let size = e.event.position.y - panel.bounds.top();
|
||||
view.resize_panels(ix, size, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
.when(self.axis == Axis::Horizontal, |this| {
|
||||
this.cursor_col_resize().top_0().w(handle_size).h_full()
|
||||
})
|
||||
.when(self.axis == Axis::Vertical, |this| {
|
||||
this.cursor_row_resize().left_0().w_full().h(handle_size)
|
||||
})
|
||||
.on_drag(DragPanel((cx.entity_id(), ix, axis)), |drag_panel, cx| {
|
||||
cx.stop_propagation();
|
||||
cx.new_view(|_| drag_panel.clone())
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/// The `ix`` is the index of the panel to resize,
|
||||
/// and the `size` is the new size for the panel.
|
||||
fn resize_panels(&mut self, ix: usize, size: Pixels, cx: &mut ViewContext<Self>) {
|
||||
// Only resize the middle panels.
|
||||
if ix == self.panels.len() - 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let old_size = self.sizes[ix];
|
||||
let size = self.panels[ix].read(cx).limit_size(size);
|
||||
let changed_size = size - old_size;
|
||||
|
||||
// If change size is less than 1px, do nothing.
|
||||
if changed_size > px(-1.0) && changed_size < px(1.0) {
|
||||
return;
|
||||
}
|
||||
self.sizes[ix] = size;
|
||||
|
||||
let next_size = self.sizes[ix + 1];
|
||||
self.sizes[ix + 1] = self.panels[ix + 1]
|
||||
.read(cx)
|
||||
.limit_size(next_size - changed_size);
|
||||
|
||||
for (i, panel) in self.panels.iter_mut().enumerate() {
|
||||
let size = self.sizes[i];
|
||||
panel.update(cx, |this, _| this.size = size);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ResizablePanelGroup {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let mut children: Vec<AnyElement> = vec![];
|
||||
for (ix, panel) in self.panels.iter().enumerate() {
|
||||
children.push(panel.clone().into_any_element());
|
||||
if ix < self.panels.len() - 1 {
|
||||
children.push(self.render_resize_handle(ix, cx).into_any_element());
|
||||
}
|
||||
}
|
||||
|
||||
let container = if self.axis == Axis::Horizontal {
|
||||
h_flex()
|
||||
} else {
|
||||
v_flex()
|
||||
};
|
||||
|
||||
container.size_full().children(children)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResizablePanel {
|
||||
size: Pixels,
|
||||
max_size: Option<Pixels>,
|
||||
min_size: Option<Pixels>,
|
||||
axis: Axis,
|
||||
content_builder: Option<Rc<dyn Fn(&mut WindowContext) -> AnyElement>>,
|
||||
content_view: Option<AnyView>,
|
||||
/// The bounds of the resizable panel, when render the bounds will be updated.
|
||||
bounds: Bounds<Pixels>,
|
||||
|
||||
grow: bool,
|
||||
}
|
||||
|
||||
impl ResizablePanel {
|
||||
pub(super) fn new() -> Self {
|
||||
Self {
|
||||
size: px(20.),
|
||||
axis: Axis::Horizontal,
|
||||
max_size: None,
|
||||
min_size: None,
|
||||
content_builder: None,
|
||||
content_view: None,
|
||||
bounds: Bounds::default(),
|
||||
grow: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content<F>(mut self, content: F) -> Self
|
||||
where
|
||||
F: Fn(&mut WindowContext) -> AnyElement + 'static,
|
||||
{
|
||||
self.content_builder = Some(Rc::new(content));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn content_view(mut self, content: AnyView) -> Self {
|
||||
self.content_view = Some(content);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn size(mut self, size: Pixels) -> Self {
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max_size(mut self, max_size: Pixels) -> Self {
|
||||
self.max_size = Some(max_size);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn min_size(mut self, min_size: Pixels) -> Self {
|
||||
self.min_size = Some(min_size);
|
||||
self
|
||||
}
|
||||
|
||||
fn limit_size(&self, size: Pixels) -> Pixels {
|
||||
if let Some(max_size) = self.max_size {
|
||||
if size > max_size {
|
||||
return max_size;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(min_size) = self.min_size {
|
||||
if size < min_size {
|
||||
return min_size;
|
||||
}
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
|
||||
/// Set the panel to grow to fill the remaining space.
|
||||
pub fn grow(mut self) -> Self {
|
||||
self.grow = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ResizablePanel {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let size = self.limit_size(self.size);
|
||||
|
||||
div()
|
||||
.size_full()
|
||||
.relative()
|
||||
.when(self.grow, |this| this.flex_grow())
|
||||
.when(self.axis == Axis::Vertical, |this| this.h(size))
|
||||
.when(self.axis == Axis::Horizontal, |this| this.w(size))
|
||||
.overflow_hidden()
|
||||
.child({
|
||||
let view = cx.view().clone();
|
||||
canvas(
|
||||
move |bounds, cx| view.update(cx, |r, _| r.bounds = bounds),
|
||||
|_, _, _| {},
|
||||
)
|
||||
.absolute()
|
||||
.size_full()
|
||||
})
|
||||
.when_some(self.content_builder.clone(), |this, c| this.child(c(cx)))
|
||||
.when_some(self.content_view.clone(), |this, c| this.child(c))
|
||||
}
|
||||
}
|
||||
|
|
@ -127,15 +127,8 @@ pub trait StyledExt: Styled + Sized {
|
|||
}
|
||||
|
||||
/// Render a border with a width of 1px, color ring color
|
||||
///
|
||||
/// Please ensure this after the shadow
|
||||
fn outline(self, cx: &WindowContext) -> Self {
|
||||
self.shadow(smallvec![BoxShadow {
|
||||
color: cx.theme().ring,
|
||||
offset: point(px(0.), px(0.)),
|
||||
blur_radius: px(0.1),
|
||||
spread_radius: px(1.),
|
||||
}])
|
||||
self.border_color(cx.theme().ring)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
use crate::selectable::Selectable;
|
||||
use crate::theme::{ActiveTheme, Colorize};
|
||||
use gpui::prelude::FluentBuilder as _;
|
||||
use gpui::InteractiveElement;
|
||||
use gpui::{
|
||||
div, px, AnyElement, Div, ElementId, IntoElement, ParentElement as _, RenderOnce, Stateful,
|
||||
StatefulInteractiveElement, Styled, WindowContext,
|
||||
div, AnyElement, Div, ElementId, InteractiveElement, IntoElement, ParentElement as _,
|
||||
RenderOnce, Stateful, StatefulInteractiveElement, Styled, WindowContext,
|
||||
};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
|
|
@ -20,7 +19,7 @@ pub struct Tab {
|
|||
impl Tab {
|
||||
pub fn new(id: impl Into<ElementId>, label: impl Into<AnyElement>) -> Self {
|
||||
Self {
|
||||
base: div().id(id.into()).gap_1().py_1().px_3(),
|
||||
base: div().id(id.into()).gap_1().py_1p5().px_3().h_8(),
|
||||
label: label.into(),
|
||||
disabled: false,
|
||||
selected: false,
|
||||
|
|
@ -66,20 +65,23 @@ 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, _) => (cx.theme().foreground, cx.theme().background),
|
||||
(false, true) => (cx.theme().foreground.opacity(0.5), cx.theme().muted),
|
||||
(false, false) => (cx.theme().muted_foreground, cx.theme().muted),
|
||||
(true, _) => (cx.theme().tab_active_foreground, cx.theme().tab_active),
|
||||
(false, true) => (cx.theme().tab_foreground.opacity(0.5), cx.theme().tab),
|
||||
(false, false) => (cx.theme().muted_foreground, cx.theme().tab),
|
||||
};
|
||||
|
||||
self.base
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_full()
|
||||
.flex_shrink_0()
|
||||
.cursor_pointer()
|
||||
.text_color(text_color)
|
||||
.bg(bg_color)
|
||||
.when(self.selected, |this| this.rounded(px(6.)))
|
||||
.border_x_1()
|
||||
.border_color(bg_color)
|
||||
.border_color(cx.theme().transparent)
|
||||
.when(self.selected, |this| this.border_color(cx.theme().border))
|
||||
.text_sm()
|
||||
.when(self.disabled, |this| this)
|
||||
.when_some(self.prefix, |this, prefix| {
|
||||
this.child(prefix).text_color(text_color)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use crate::stack::h_flex;
|
||||
use crate::theme::ActiveTheme;
|
||||
use gpui::InteractiveElement;
|
||||
use gpui::{
|
||||
div, AnyElement, Div, IntoElement, ParentElement, RenderOnce, ScrollHandle, SharedString,
|
||||
StatefulInteractiveElement as _, Styled, WindowContext,
|
||||
};
|
||||
use gpui::{px, InteractiveElement};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
|
|
@ -18,7 +18,7 @@ pub struct TabBar {
|
|||
impl TabBar {
|
||||
pub fn new(id: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
base: div().h_10().p_1(),
|
||||
base: div().h_8().px(px(-1.)),
|
||||
id: id.into(),
|
||||
children: SmallVec::new(),
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
|
|
@ -54,8 +54,10 @@ impl RenderOnce for TabBar {
|
|||
.flex()
|
||||
.flex_none()
|
||||
.items_center()
|
||||
.bg(theme.muted)
|
||||
.text_color(theme.muted_foreground)
|
||||
.bg(theme.tab_bar)
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().border)
|
||||
.text_color(theme.tab_foreground)
|
||||
// The child will append to this level
|
||||
.child(
|
||||
h_flex()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use gpui::{hsla, AppContext, Global, Hsla, WindowAppearance};
|
||||
use gpui::{hsla, point, AppContext, BoxShadow, Global, Hsla, Pixels, WindowAppearance};
|
||||
|
||||
pub trait ActiveTheme {
|
||||
fn theme(&self) -> &Theme;
|
||||
|
|
@ -19,6 +19,27 @@ pub fn hsl(h: f32, s: f32, l: f32) -> Hsla {
|
|||
hsla(h / 360., s / 100.0, l / 100.0, 1.0)
|
||||
}
|
||||
|
||||
/// Make a BoxShadow like CSS
|
||||
///
|
||||
/// e.g:
|
||||
///
|
||||
/// If CSS is `box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);`
|
||||
///
|
||||
/// Then the equivalent in Rust is `box_shadow(0., 0., 10., 0., hsla(0., 0., 0., 0.1))`
|
||||
pub fn box_shadow(
|
||||
x: impl Into<Pixels>,
|
||||
y: impl Into<Pixels>,
|
||||
blur: impl Into<Pixels>,
|
||||
spread: impl Into<Pixels>,
|
||||
color: Hsla,
|
||||
) -> BoxShadow {
|
||||
BoxShadow {
|
||||
offset: point(x.into(), y.into()),
|
||||
blur_radius: blur.into(),
|
||||
spread_radius: spread.into(),
|
||||
color,
|
||||
}
|
||||
}
|
||||
pub trait Colorize {
|
||||
fn opacity(&self, opacity: f32) -> Hsla;
|
||||
fn divide(&self, divisor: f32) -> Hsla;
|
||||
|
|
@ -112,7 +133,7 @@ struct Colors {
|
|||
pub scrollbar: Hsla,
|
||||
pub scrollbar_thumb: Hsla,
|
||||
pub panel: Hsla,
|
||||
pub drop_target: Hsla,
|
||||
pub tab_bar: Hsla,
|
||||
}
|
||||
|
||||
impl Colors {
|
||||
|
|
@ -172,7 +193,7 @@ impl Colors {
|
|||
scrollbar: Hsla::transparent_black(),
|
||||
scrollbar_thumb: hsl(240.0, 5.9, 85.0).opacity(0.7),
|
||||
panel: hsl(0.0, 0.0, 100.0),
|
||||
drop_target: hsl(240.0, 65., 44.0).opacity(0.15),
|
||||
tab_bar: hsl(240.0, 4.8, 95.9),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -231,7 +252,7 @@ impl Colors {
|
|||
scrollbar: Hsla::transparent_black(),
|
||||
scrollbar_thumb: hsl(240.0, 3.7, 15.9).opacity(0.7),
|
||||
panel: hsl(299.0, 2., 9.),
|
||||
drop_target: hsl(240.0, 89., 67.0).opacity(0.8),
|
||||
tab_bar: hsl(299.0, 2., 9.),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -272,8 +293,14 @@ pub struct Theme {
|
|||
pub scrollbar: Hsla,
|
||||
pub scrollbar_thumb: Hsla,
|
||||
pub panel: Hsla,
|
||||
pub drag_border: Hsla,
|
||||
pub drop_target: Hsla,
|
||||
pub radius: f32,
|
||||
pub tab_bar: Hsla,
|
||||
pub tab: Hsla,
|
||||
pub tab_active: Hsla,
|
||||
pub tab_foreground: Hsla,
|
||||
pub tab_active_foreground: Hsla,
|
||||
}
|
||||
|
||||
impl Global for Theme {}
|
||||
|
|
@ -321,7 +348,13 @@ impl From<Colors> for Theme {
|
|||
scrollbar_thumb: colors.scrollbar_thumb,
|
||||
panel: colors.panel,
|
||||
selection: colors.selection,
|
||||
drop_target: colors.drop_target,
|
||||
drag_border: crate::blue_500(),
|
||||
drop_target: hsl(240.0, 65., 44.0).opacity(0.15),
|
||||
tab_bar: colors.tab_bar,
|
||||
tab: gpui::transparent_black(),
|
||||
tab_active: colors.background,
|
||||
tab_foreground: colors.foreground,
|
||||
tab_active_foreground: colors.foreground,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -696,9 +696,10 @@ impl Pane {
|
|||
|tab, cx| cx.new_view(|_| tab.clone()),
|
||||
)
|
||||
.drag_over::<DraggedTab>(|tab, _, cx| {
|
||||
tab.border_l_3()
|
||||
.rounded_l_none()
|
||||
.border_color(cx.theme().drop_target)
|
||||
tab.rounded_l_none()
|
||||
.border_l_2()
|
||||
.border_r_0()
|
||||
.border_color(cx.theme().drag_border)
|
||||
})
|
||||
.drag_over::<DraggedSelection>(|tab, _, cx| tab.bg(cx.theme().drop_target))
|
||||
.when_some(self.can_drop_predicate.clone(), |this, p| {
|
||||
|
|
|
|||
Loading…
Reference in a new issue