Redesign for the v1 stable release #3

Merged
reya merged 30 commits from v1-redesign into master 2026-02-04 01:43:24 +00:00
11 changed files with 452 additions and 378 deletions
Showing only changes of commit 8e62f30c05 - Show all commits

View File

@@ -5,12 +5,12 @@ use gpui::{
div, relative, svg, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle,
Focusable, IntoElement, ParentElement, Render, SharedString, Styled, Window,
};
use state::NostrRegistry;
use state::{NostrRegistry, RelayState};
use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants};
use ui::{h_flex, v_flex, Icon, IconName, Sizable, StyledExt};
use crate::panels::{connect, import, profile};
use crate::panels::{connect, import, profile, relay_list};
use crate::workspace::Workspace;
pub fn init(window: &mut Window, cx: &mut App) -> Entity<GreeterPanel> {
@@ -71,6 +71,11 @@ impl Render for GreeterPanel {
let nostr = NostrRegistry::global(cx);
let identity = nostr.read(cx).identity();
let relay_list_state = identity.read(cx).relay_list_state();
let messaging_relay_state = identity.read(cx).messaging_relays_state();
let required_actions =
relay_list_state == RelayState::NotSet || messaging_relay_state == RelayState::NotSet;
h_flex()
.size_full()
.items_center()
@@ -78,15 +83,16 @@ impl Render for GreeterPanel {
.p_2()
.child(
v_flex()
.gap_3()
.h_full()
.w_112()
.gap_6()
.items_center()
.justify_center()
.child(
h_flex()
.mb_7()
.mb_4()
.gap_2()
.w_96()
.w_full()
.child(
svg()
.path("brand/coop.svg")
@@ -110,11 +116,74 @@ impl Render for GreeterPanel {
),
),
)
.when(required_actions, |this| {
this.child(
v_flex()
.gap_2()
.w_full()
.items_start()
.child(
h_flex()
.gap_1()
.w_full()
.text_sm()
.font_semibold()
.text_color(cx.theme().text_muted)
.child(SharedString::from("Required Actions"))
.child(div().flex_1().h_px().bg(cx.theme().border)),
)
.child(
v_flex()
.w_full()
.items_start()
.justify_start()
.gap_2()
.when(relay_list_state == RelayState::NotSet, |this| {
this.child(
Button::new("connect")
.icon(Icon::new(IconName::Door))
.label("Set up relay list")
.ghost()
.small()
.on_click(move |_ev, window, cx| {
Workspace::add_panel(
relay_list::init(window, cx),
DockPlacement::Center,
window,
cx,
);
}),
)
})
.when(
messaging_relay_state == RelayState::NotSet,
|this| {
this.child(
Button::new("import")
.icon(Icon::new(IconName::Usb))
.label("Set up messaging relays")
.ghost()
.small()
.on_click(move |_ev, window, cx| {
Workspace::add_panel(
import::init(window, cx),
DockPlacement::Center,
window,
cx,
);
}),
)
},
),
),
)
})
.when(!identity.read(cx).owned, |this| {
this.child(
v_flex()
.gap_2()
.w_96()
.w_full()
.items_start()
.child(
h_flex()
.gap_1()
@@ -167,7 +236,8 @@ impl Render for GreeterPanel {
.child(
v_flex()
.gap_2()
.w_96()
.w_full()
.items_start()
.child(
h_flex()
.gap_1()

View File

@@ -2,3 +2,4 @@ pub mod connect;
pub mod greeter;
pub mod import;
pub mod profile;
pub mod relay_list;

View File

@@ -401,7 +401,7 @@ impl Render for ProfilePanel {
.child(divider(cx))
.child(
Button::new("submit")
.label("Continue")
.label("Update")
.primary()
.disabled(self.uploading)
.on_click(cx.listener(move |this, _ev, window, cx| {

View File

@@ -0,0 +1,366 @@
use std::collections::HashSet;
use std::time::Duration;
use anyhow::{anyhow, Error};
use common::BOOTSTRAP_RELAYS;
use dock::panel::{Panel, PanelEvent};
use gpui::prelude::FluentBuilder;
use gpui::{
div, relative, uniform_list, AnyElement, App, AppContext, Context, Entity, EventEmitter,
FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
Styled, Subscription, Task, TextAlign, UniformList, Window,
};
use nostr_sdk::prelude::*;
use smallvec::{smallvec, SmallVec};
use state::NostrRegistry;
use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants};
use ui::input::{InputEvent, InputState, TextInput};
use ui::{divider, h_flex, v_flex, IconName, Sizable, StyledExt};
pub fn init(window: &mut Window, cx: &mut App) -> Entity<RelayListPanel> {
cx.new(|cx| RelayListPanel::new(window, cx))
}
#[derive(Debug)]
pub struct RelayListPanel {
name: SharedString,
focus_handle: FocusHandle,
/// Relay URL input
input: Entity<InputState>,
/// Relay metadata input
metadata: Entity<Option<RelayMetadata>>,
/// Error message
error: Option<SharedString>,
// All relays
relays: HashSet<(RelayUrl, Option<RelayMetadata>)>,
// Event subscriptions
_subscriptions: SmallVec<[Subscription; 1]>,
// Background tasks
_tasks: SmallVec<[Task<()>; 1]>,
}
impl RelayListPanel {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let input = cx.new(|cx| InputState::new(window, cx).placeholder("wss://example.com"));
let metadata = cx.new(|_| None);
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let mut subscriptions = smallvec![];
let mut tasks = smallvec![];
tasks.push(
// Load user's relays in the local database
cx.spawn_in(window, async move |this, cx| {
let result = cx
.background_spawn(async move { Self::load(&client).await })
.await;
if let Ok(relays) = result {
this.update(cx, |this, cx| {
this.relays.extend(relays);
cx.notify();
})
.ok();
}
}),
);
subscriptions.push(
// Subscribe to user's input events
cx.subscribe_in(&input, window, move |this, _input, event, window, cx| {
if let InputEvent::PressEnter { .. } = event {
this.add(window, cx);
}
}),
);
Self {
name: "Update Relay List".into(),
focus_handle: cx.focus_handle(),
input,
metadata,
relays: HashSet::new(),
error: None,
_subscriptions: subscriptions,
_tasks: tasks,
}
}
async fn load(client: &Client) -> Result<Vec<(RelayUrl, Option<RelayMetadata>)>, Error> {
let signer = client.signer().await?;
let public_key = signer.get_public_key().await?;
let filter = Filter::new()
.kind(Kind::InboxRelays)
.author(public_key)
.limit(1);
if let Some(event) = client.database().query(filter).await?.first_owned() {
Ok(nip65::extract_owned_relay_list(event).collect())
} else {
Err(anyhow!("Not found."))
}
}
fn add(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let value = self.input.read(cx).value().to_string();
let metadata = self.metadata.read(cx);
if !value.starts_with("ws") {
self.set_error("Relay URl is invalid", window, cx);
return;
}
if let Ok(url) = RelayUrl::parse(&value) {
if !self.relays.insert((url, metadata.to_owned())) {
self.input.update(cx, |this, cx| {
this.set_value("", window, cx);
});
cx.notify();
}
} else {
self.set_error("Relay URl is invalid", window, cx);
}
}
fn remove(&mut self, url: &RelayUrl, cx: &mut Context<Self>) {
self.relays.retain(|(relay, _)| relay != url);
cx.notify();
}
fn set_error<E>(&mut self, error: E, window: &mut Window, cx: &mut Context<Self>)
where
E: Into<SharedString>,
{
self.error = Some(error.into());
cx.notify();
cx.spawn_in(window, async move |this, cx| {
cx.background_executor().timer(Duration::from_secs(2)).await;
// Clear the error message after a delay
this.update(cx, |this, cx| {
this.error = None;
cx.notify();
})
.ok();
})
.detach();
}
pub fn set_relays(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if self.relays.is_empty() {
self.set_error("You need to add at least 1 relay", window, cx);
return;
};
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let relays = self.relays.clone();
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let signer = client.signer().await?;
let event = EventBuilder::relay_list(relays).sign(&signer).await?;
// Set relay list for current user
client.send_event_to(BOOTSTRAP_RELAYS, &event).await?;
Ok(())
});
cx.spawn_in(window, async move |this, cx| {
match task.await {
Ok(_) => {
// TODO
}
Err(e) => {
this.update_in(cx, |this, window, cx| {
this.set_error(e.to_string(), window, cx);
})
.ok();
}
};
})
.detach();
}
fn render_list(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> UniformList {
let relays = self.relays.clone();
let total = relays.len();
uniform_list(
"relays",
total,
cx.processor(move |_v, range, _window, cx| {
let mut items = Vec::new();
for ix in range {
let Some((url, metadata)) = relays.iter().nth(ix) else {
continue;
};
items.push(
div()
.id(SharedString::from(url.to_string()))
.group("")
.w_full()
.h_9()
.py_0p5()
.child(
h_flex()
.px_2()
.flex()
.justify_between()
.rounded(cx.theme().radius)
.bg(cx.theme().elevated_surface_background)
.child(
div().text_sm().child(SharedString::from(url.to_string())),
)
.child(
h_flex()
.gap_1()
.text_xs()
.map(|this| {
if let Some(metadata) = metadata {
this.child(SharedString::from(
metadata.to_string(),
))
} else {
this.child(SharedString::from("Read+Write"))
}
})
.child(
Button::new("remove_{ix}")
.icon(IconName::Close)
.xsmall()
.ghost()
.invisible()
.group_hover("", |this| this.visible())
.on_click({
let url = url.to_owned();
cx.listener(
move |this, _ev, _window, cx| {
this.remove(&url, cx);
},
)
}),
),
),
),
)
}
items
}),
)
.h_full()
}
fn render_empty(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
h_flex()
.mt_2()
.h_20()
.justify_center()
.border_2()
.border_dashed()
.border_color(cx.theme().border)
.rounded(cx.theme().radius_lg)
.text_sm()
.text_align(TextAlign::Center)
.child(SharedString::from("Please add some relays."))
}
}
impl Panel for RelayListPanel {
fn panel_id(&self) -> SharedString {
self.name.clone()
}
fn title(&self, _cx: &App) -> AnyElement {
self.name.clone().into_any_element()
}
}
impl EventEmitter<PanelEvent> for RelayListPanel {}
impl Focusable for RelayListPanel {
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl Render for RelayListPanel {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.size_full()
.items_center()
.justify_center()
.p_2()
.gap_10()
.child(
div()
.text_center()
.font_semibold()
.line_height(relative(1.25))
.child(SharedString::from("Update Relay List")),
)
.child(
v_flex()
.w_112()
.gap_2()
.text_sm()
.child(
v_flex()
.gap_1p5()
.child(
h_flex()
.gap_1()
.w_full()
.child(TextInput::new(&self.input).small())
.child(
Button::new("add")
.icon(IconName::Plus)
.label("Add")
.ghost()
.on_click(cx.listener(move |this, _, window, cx| {
this.add(window, cx);
})),
),
)
.when_some(self.error.as_ref(), |this, error| {
this.child(
div()
.italic()
.text_xs()
.text_color(cx.theme().danger_foreground)
.child(error.clone()),
)
}),
)
.map(|this| {
if !self.relays.is_empty() {
this.child(self.render_list(window, cx))
} else {
this.child(self.render_empty(window, cx))
}
})
.child(divider(cx))
.child(
Button::new("submit")
.label("Update")
.primary()
.on_click(cx.listener(move |this, _ev, window, cx| {
this.set_relays(window, cx);
})),
),
)
}
}

View File

@@ -7,9 +7,9 @@ use common::{DebouncedDelay, RenderedTimestamp, TextUtils, BOOTSTRAP_RELAYS, SEA
use dock::panel::{Panel, PanelEvent};
use gpui::prelude::FluentBuilder;
use gpui::{
deferred, div, rems, uniform_list, App, AppContext, Context, Decorations, Entity, EventEmitter,
FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render,
RetainAllImageCache, SharedString, Styled, Subscription, Task, Window,
deferred, div, rems, uniform_list, App, AppContext, Context, Entity, EventEmitter, FocusHandle,
Focusable, InteractiveElement, IntoElement, ParentElement, Render, RetainAllImageCache,
SharedString, Styled, Subscription, Task, Window,
};
use gpui_tokio::Tokio;
use list_item::RoomListItem;
@@ -17,7 +17,7 @@ use nostr_sdk::prelude::*;
use person::PersonRegistry;
use smallvec::{smallvec, SmallVec};
use state::{NostrRegistry, GIFTWRAP_SUBSCRIPTION};
use theme::{ActiveTheme, CLIENT_SIDE_DECORATION_ROUNDING, TITLEBAR_HEIGHT};
use theme::{ActiveTheme, TITLEBAR_HEIGHT};
use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants};
use ui::indicator::Indicator;
@@ -578,9 +578,7 @@ impl Focusable for Sidebar {
}
impl Render for Sidebar {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let decorations = window.window_decorations();
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let nostr = NostrRegistry::global(cx);
let identity = nostr.read(cx).identity();
@@ -601,12 +599,6 @@ impl Render for Sidebar {
.relative()
.gap_2()
.bg(cx.theme().surface_background)
.map(|this| match decorations {
Decorations::Server => this,
Decorations::Client { .. } => this
.rounded_tl(CLIENT_SIDE_DECORATION_ROUNDING)
.rounded_bl(CLIENT_SIDE_DECORATION_ROUNDING),
})
// Titlebar
.child(
h_flex()

View File

@@ -15,7 +15,6 @@ use crate::tab_panel::TabPanel;
pub mod dock;
pub mod panel;
mod platforms;
pub mod resizable;
pub mod stack_panel;
pub mod tab;

View File

@@ -1,198 +0,0 @@
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::OnceLock;
use gpui::prelude::FluentBuilder;
use gpui::{
svg, App, InteractiveElement, IntoElement, MouseButton, ParentElement, RenderOnce,
StatefulInteractiveElement, Styled, Window,
};
use linicon::{lookup_icon, IconType};
use theme::ActiveTheme;
use ui::{h_flex, Icon, IconName, Sizable};
#[derive(IntoElement)]
pub struct LinuxWindowControls {}
impl LinuxWindowControls {
pub fn new() -> Self {
Self {}
}
}
impl RenderOnce for LinuxWindowControls {
fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
h_flex()
.id("linux-window-controls")
.gap_2()
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
.child(WindowControl::new(
LinuxControl::Minimize,
IconName::WindowMinimize,
))
.child({
if window.is_maximized() {
WindowControl::new(LinuxControl::Restore, IconName::WindowRestore)
} else {
WindowControl::new(LinuxControl::Maximize, IconName::WindowMaximize)
}
})
.child(WindowControl::new(
LinuxControl::Close,
IconName::WindowClose,
))
}
}
#[derive(IntoElement)]
pub struct WindowControl {
kind: LinuxControl,
fallback: IconName,
}
impl WindowControl {
pub fn new(kind: LinuxControl, fallback: IconName) -> Self {
Self { kind, fallback }
}
#[allow(dead_code)]
pub fn is_gnome(&self) -> bool {
matches!(detect_desktop_environment(), DesktopEnvironment::Gnome)
}
}
impl RenderOnce for WindowControl {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
h_flex()
.id(self.kind.as_icon_name())
.group("")
.justify_center()
.items_center()
.rounded_full()
.size_6()
.map(|this| {
if let Some(Some(path)) = linux_controls().get(&self.kind).cloned() {
this.child(
svg()
.external_path(path.into_os_string().into_string().unwrap())
.text_color(cx.theme().text)
.size_4(),
)
} else {
this.child(Icon::new(self.fallback).flex_grow().small())
}
})
.on_mouse_move(|_ev, _window, cx| cx.stop_propagation())
.on_click(move |_ev, window, cx| {
cx.stop_propagation();
match self.kind {
LinuxControl::Minimize => window.minimize_window(),
LinuxControl::Restore => window.zoom_window(),
LinuxControl::Maximize => window.zoom_window(),
LinuxControl::Close => cx.quit(),
}
})
}
}
static DE: OnceLock<DesktopEnvironment> = OnceLock::new();
static LINUX_CONTROLS: OnceLock<HashMap<LinuxControl, Option<PathBuf>>> = OnceLock::new();
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DesktopEnvironment {
Gnome,
Kde,
Unknown,
}
/// Detect the current desktop environment
pub fn detect_desktop_environment() -> &'static DesktopEnvironment {
DE.get_or_init(|| {
// Try to use environment variables first
if let Ok(output) = std::env::var("XDG_CURRENT_DESKTOP") {
let desktop = output.to_lowercase();
if desktop.contains("gnome") {
return DesktopEnvironment::Gnome;
} else if desktop.contains("kde") {
return DesktopEnvironment::Kde;
}
}
// Fallback detection methods
if let Ok(output) = std::env::var("DESKTOP_SESSION") {
let session = output.to_lowercase();
if session.contains("gnome") {
return DesktopEnvironment::Gnome;
} else if session.contains("kde") || session.contains("plasma") {
return DesktopEnvironment::Kde;
}
}
DesktopEnvironment::Unknown
})
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum LinuxControl {
Minimize,
Restore,
Maximize,
Close,
}
impl LinuxControl {
pub fn as_icon_name(&self) -> &'static str {
match self {
LinuxControl::Close => "window-close",
LinuxControl::Minimize => "window-minimize",
LinuxControl::Maximize => "window-maximize",
LinuxControl::Restore => "window-restore",
}
}
}
fn linux_controls() -> &'static HashMap<LinuxControl, Option<PathBuf>> {
LINUX_CONTROLS.get_or_init(|| {
let mut icons = HashMap::new();
icons.insert(LinuxControl::Close, None);
icons.insert(LinuxControl::Minimize, None);
icons.insert(LinuxControl::Maximize, None);
icons.insert(LinuxControl::Restore, None);
let icon_names = [
(LinuxControl::Close, vec!["window-close", "dialog-close"]),
(
LinuxControl::Minimize,
vec!["window-minimize", "window-lower"],
),
(
LinuxControl::Maximize,
vec!["window-maximize", "window-expand"],
),
(
LinuxControl::Restore,
vec!["window-restore", "window-return"],
),
];
for (control, icon_names) in icon_names {
for icon_name in icon_names {
// Try GNOME-style naming first
let mut control_icon = lookup_icon(format!("{icon_name}-symbolic"))
.find(|icon| matches!(icon, Ok(icon) if icon.icon_type == IconType::SVG));
// If not found, try KDE-style naming
if control_icon.is_none() {
control_icon = lookup_icon(icon_name)
.find(|icon| matches!(icon, Ok(icon) if icon.icon_type == IconType::SVG));
}
if let Some(Ok(icon)) = control_icon {
icons.entry(control).and_modify(|v| *v = Some(icon.path));
}
}
}
icons
})
}

View File

@@ -1,3 +0,0 @@
#[cfg(target_os = "linux")]
pub mod linux;
pub mod windows;

View File

@@ -1,147 +0,0 @@
use gpui::prelude::FluentBuilder;
use gpui::{
div, px, App, ElementId, Hsla, InteractiveElement, IntoElement, ParentElement, Pixels,
RenderOnce, Rgba, StatefulInteractiveElement, Styled, Window, WindowControlArea,
};
use theme::ActiveTheme;
use ui::h_flex;
#[derive(IntoElement)]
pub struct WindowsWindowControls {
button_height: Pixels,
}
impl WindowsWindowControls {
pub fn new(button_height: Pixels) -> Self {
Self { button_height }
}
#[cfg(not(target_os = "windows"))]
fn get_font() -> &'static str {
"Segoe Fluent Icons"
}
#[cfg(target_os = "windows")]
fn get_font() -> &'static str {
use windows::Wdk::System::SystemServices::RtlGetVersion;
let mut version = unsafe { std::mem::zeroed() };
let status = unsafe { RtlGetVersion(&mut version) };
if status.is_ok() && version.dwBuildNumber >= 22000 {
"Segoe Fluent Icons"
} else {
"Segoe MDL2 Assets"
}
}
}
impl RenderOnce for WindowsWindowControls {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let close_button_hover_color = Rgba {
r: 232.0 / 255.0,
g: 17.0 / 255.0,
b: 32.0 / 255.0,
a: 1.0,
};
let button_hover_color = cx.theme().ghost_element_hover;
let button_active_color = cx.theme().ghost_element_active;
div()
.id("windows-window-controls")
.font_family(Self::get_font())
.flex()
.flex_row()
.justify_center()
.content_stretch()
.max_h(self.button_height)
.min_h(self.button_height)
.child(WindowsCaptionButton::new(
"minimize",
WindowsCaptionButtonIcon::Minimize,
button_hover_color,
button_active_color,
))
.child(WindowsCaptionButton::new(
"maximize-or-restore",
if window.is_maximized() {
WindowsCaptionButtonIcon::Restore
} else {
WindowsCaptionButtonIcon::Maximize
},
button_hover_color,
button_active_color,
))
.child(WindowsCaptionButton::new(
"close",
WindowsCaptionButtonIcon::Close,
close_button_hover_color,
button_active_color,
))
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
enum WindowsCaptionButtonIcon {
Minimize,
Restore,
Maximize,
Close,
}
#[derive(IntoElement)]
struct WindowsCaptionButton {
id: ElementId,
icon: WindowsCaptionButtonIcon,
hover_background_color: Hsla,
active_background_color: Hsla,
}
impl WindowsCaptionButton {
pub fn new(
id: impl Into<ElementId>,
icon: WindowsCaptionButtonIcon,
hover_background_color: impl Into<Hsla>,
active_background_color: impl Into<Hsla>,
) -> Self {
Self {
id: id.into(),
icon,
hover_background_color: hover_background_color.into(),
active_background_color: active_background_color.into(),
}
}
}
impl RenderOnce for WindowsCaptionButton {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
h_flex()
.id(self.id)
.justify_center()
.content_center()
.occlude()
.w(px(36.))
.h_full()
.text_size(px(10.0))
.hover(|style| style.bg(self.hover_background_color))
.active(|style| style.bg(self.active_background_color))
.map(|this| match self.icon {
WindowsCaptionButtonIcon::Close => {
this.window_control_area(WindowControlArea::Close)
}
WindowsCaptionButtonIcon::Maximize | WindowsCaptionButtonIcon::Restore => {
this.window_control_area(WindowControlArea::Max)
}
WindowsCaptionButtonIcon::Minimize => {
this.window_control_area(WindowControlArea::Min)
}
})
.child(match self.icon {
WindowsCaptionButtonIcon::Minimize => "\u{e921}",
WindowsCaptionButtonIcon::Restore => "\u{e923}",
WindowsCaptionButtonIcon::Maximize => "\u{e922}",
WindowsCaptionButtonIcon::Close => "\u{e8bb}",
})
}
}

View File

@@ -6,7 +6,7 @@ use gpui::{
Window,
};
use smallvec::SmallVec;
use theme::{ActiveTheme, CLIENT_SIDE_DECORATION_ROUNDING};
use theme::ActiveTheme;
use ui::{h_flex, AxisExt as _, Placement};
use super::{DockArea, PanelEvent};
@@ -386,7 +386,6 @@ impl Render for StackPanel {
h_flex()
.size_full()
.overflow_hidden()
.rounded(CLIENT_SIDE_DECORATION_ROUNDING)
.bg(cx.theme().elevated_surface_background)
.child(
ResizablePanelGroup::new("stack-panel-group")

View File

@@ -7,7 +7,7 @@ use gpui::{
MouseButton, ParentElement, Pixels, Render, ScrollHandle, SharedString,
StatefulInteractiveElement, Styled, WeakEntity, Window,
};
use theme::{ActiveTheme, CLIENT_SIDE_DECORATION_ROUNDING, TITLEBAR_HEIGHT};
use theme::{ActiveTheme, TITLEBAR_HEIGHT};
use ui::button::{Button, ButtonVariants as _};
use ui::popup_menu::{PopupMenu, PopupMenuExt};
use ui::{h_flex, v_flex, AxisExt, IconName, Placement, Selectable, Sizable, StyledExt};
@@ -594,7 +594,6 @@ impl TabPanel {
.py_2()
.pl_3()
.pr_2()
.rounded_tr(CLIENT_SIDE_DECORATION_ROUNDING)
.when(left_dock_button.is_some(), |this| this.pl_2())
.when(right_dock_button.is_some(), |this| this.pr_2())
.when(has_extend_dock_button, |this| {
@@ -647,10 +646,6 @@ impl TabPanel {
TabBar::new()
.track_scroll(&self.tab_bar_scroll_handle)
.h(TITLEBAR_HEIGHT)
.rounded_tr(CLIENT_SIDE_DECORATION_ROUNDING)
.when(self.zoomed, |this| {
this.rounded_tl(CLIENT_SIDE_DECORATION_ROUNDING)
})
.when(has_extend_dock_button, |this| {
this.prefix(
h_flex()