Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions crates/ui/src/floating_sidebar/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ impl FloatingSidebarState {

/// A floating sidebar that composes SidebarShell and Sidebar with internal resize handling.
///
/// The default elevation is [`ElevationToken::Sm`]. Large elevations may require
/// a larger inset near window edges to avoid native-window clipping.
/// By default, the sidebar uses the theme's panel elevation. Explicit elevation overrides may
/// require a larger inset near window edges to avoid native-window clipping.
#[derive(IntoElement)]
pub struct FloatingSidebar<E: SidebarItem + 'static> {
id: ElementId,
Expand All @@ -57,7 +57,7 @@ pub struct FloatingSidebar<E: SidebarItem + 'static> {
inset: Option<Pixels>,
top_inset: Pixels,
blur_enabled: Option<bool>,
elevation: ElevationToken,
elevation: Option<ElevationToken>,
on_resize_end: Option<Rc<dyn Fn(Pixels, &mut Window, &mut App)>>,
}

Expand All @@ -79,7 +79,7 @@ impl<E: SidebarItem> FloatingSidebar<E> {
inset: Some(DEFAULT_INSET),
top_inset: px(0.0),
blur_enabled: None,
elevation: ElevationToken::Sm,
elevation: None,
on_resize_end: None,
}
}
Expand Down Expand Up @@ -166,10 +166,10 @@ impl<E: SidebarItem> FloatingSidebar<E> {

/// Set the shadow elevation level for the complete sidebar panel.
///
/// Default is [`ElevationToken::Sm`]. Large elevations may require a larger
/// When not set, the theme's panel elevation is used. Large elevations may require a larger
/// inset near window edges to avoid native-window clipping.
pub fn elevation(mut self, elevation: ElevationToken) -> Self {
self.elevation = elevation;
self.elevation = Some(elevation);
self
}

Expand Down Expand Up @@ -334,12 +334,14 @@ impl<E: SidebarItem> RenderOnce for FloatingSidebar<E> {
.max_width(max_width)
.resizer_width(resizer_width)
.top_inset(top_inset)
.elevation(elevation)
.on_resize_start(on_resize_start)
.on_resize_end(move |window, cx| {
end_resize(window, cx);
});

if let Some(elevation) = elevation {
shell = shell.elevation(elevation);
}
if let Some(color) = resizer_hover_bg {
shell = shell.resizer_hover_bg(color);
}
Expand Down Expand Up @@ -487,7 +489,7 @@ mod tests {
#[gpui::test]
fn test_floating_sidebar_builder(_cx: &mut gpui::TestAppContext) {
let default_sidebar = FloatingSidebar::<SidebarMenu>::new("default-floating-sidebar");
assert_eq!(default_sidebar.elevation, ElevationToken::Sm);
assert_eq!(default_sidebar.elevation, None);
assert_eq!(default_sidebar.width, DEFAULT_WIDTH);
assert_eq!(default_sidebar.inset, Some(DEFAULT_INSET));
assert_eq!(default_sidebar.resizer_width, DEFAULT_RESIZER_WIDTH);
Expand Down Expand Up @@ -516,16 +518,16 @@ mod tests {
assert_eq!(sidebar.inset, Some(px(6.0)));
assert_eq!(sidebar.top_inset, px(12.0));
assert_eq!(sidebar.blur_enabled, Some(false));
assert_eq!(sidebar.elevation, ElevationToken::Md);
assert_eq!(sidebar.elevation, Some(ElevationToken::Md));
assert!(sidebar.on_resize_end.is_some());

let no_shadow =
FloatingSidebar::<SidebarMenu>::new("no-shadow").elevation(ElevationToken::None);
assert_eq!(no_shadow.elevation, ElevationToken::None);
assert_eq!(no_shadow.elevation, Some(ElevationToken::None));

let large_shadow =
FloatingSidebar::<SidebarMenu>::new("large-shadow").elevation(ElevationToken::Lg);
assert_eq!(large_shadow.elevation, ElevationToken::Lg);
assert_eq!(large_shadow.elevation, Some(ElevationToken::Lg));
}

#[gpui::test]
Expand Down
53 changes: 40 additions & 13 deletions crates/ui/src/sidebar_shell/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ pub fn sidebar_shadow() -> Vec<BoxShadow> {
/// - Glass blur and noise effects via SurfacePreset::panel()
/// - Draggable resize handle with hover feedback
///
/// The default elevation is [`ElevationToken::Sm`]. Large elevations may require
/// a larger inset near window edges to avoid native-window clipping.
/// By default, the shell uses the theme's panel elevation. Explicit elevation overrides may
/// require a larger inset near window edges to avoid native-window clipping.
///
/// The component uses a builder pattern for configuration and implements
/// `ParentElement` for adding child content, `Styled` for style refinement,
Expand Down Expand Up @@ -138,9 +138,9 @@ pub struct SidebarShell {
on_resize_start: Option<Rc<dyn Fn(Pixels, Pixels, &mut Window, &mut App)>>,
/// Callback invoked when resize ends (mouse up).
on_resize_end: Option<Rc<dyn Fn(&mut Window, &mut App)>>,
/// Shadow elevation level for the complete sidebar panel.
/// Default: `ElevationToken::Sm`.
elevation: ElevationToken,
/// Optional shadow elevation override for the complete sidebar panel.
/// If `None`, the theme's panel elevation is used.
elevation: Option<ElevationToken>,
/// Placement side (left or right).
side: Side,
/// Inset from window edges in pixels. If `None`, inherits from context.
Expand Down Expand Up @@ -197,7 +197,7 @@ impl SidebarShell {
resizer_hover_bg: None,
on_resize_start: None,
on_resize_end: None,
elevation: ElevationToken::Sm,
elevation: None,
side,
inset: None,
top_inset: px(0.0),
Expand Down Expand Up @@ -326,7 +326,7 @@ impl SidebarShell {
/// Sets the shadow elevation level for the complete sidebar panel.
///
/// Controls the single panel shadow using the theme's elevation system.
/// Default: `ElevationToken::Sm`. Large elevations may require a larger
/// When not set, the theme's panel elevation is used. Large elevations may require a larger
/// inset near window edges to avoid native-window clipping.
///
/// # Example
Expand All @@ -337,9 +337,17 @@ impl SidebarShell {
/// .child(content)
/// ```
pub fn elevation(mut self, elevation: ElevationToken) -> Self {
self.elevation = elevation;
self.elevation = Some(elevation);
self
}

fn surface_preset(&self) -> SurfacePreset {
let mut surface_preset = SurfacePreset::panel();
if let Some(elevation) = self.elevation {
surface_preset = surface_preset.with_elevation(elevation);
}
surface_preset
}
}

impl ParentElement for SidebarShell {
Expand Down Expand Up @@ -375,8 +383,8 @@ impl RenderOnce for SidebarShell {
.blur_enabled
.unwrap_or_else(|| GlobalState::global(cx).blur_enabled());

let sidebar_surface = SurfacePreset::panel()
.with_elevation(self.elevation)
let sidebar_surface = self
.surface_preset()
.wrap_with_bounds(
div(),
sidebar_width,
Expand Down Expand Up @@ -458,23 +466,42 @@ mod tests {
#[test]
fn test_sidebar_shell_defaults_and_elevation_builder() {
let default_shell = SidebarShell::left(px(260.0));
assert_eq!(default_shell.elevation, ElevationToken::Sm);
assert_eq!(default_shell.elevation, None);
assert_eq!(default_shell.min_width, px(DEFAULT_MIN_WIDTH));
assert_eq!(default_shell.max_width, px(DEFAULT_MAX_WIDTH));
assert_eq!(default_shell.resizer_width, px(DEFAULT_RESIZER_WIDTH));
assert_eq!(default_shell.inset, None);
assert_eq!(default_shell.side, Side::Left);

let no_shadow = SidebarShell::left(px(260.0)).elevation(ElevationToken::None);
assert_eq!(no_shadow.elevation, ElevationToken::None);
assert_eq!(no_shadow.elevation, Some(ElevationToken::None));

let large_shadow = SidebarShell::right(px(300.0))
.inset(px(12.0))
.resizer_width(px(8.0))
.elevation(ElevationToken::Lg);
assert_eq!(large_shadow.elevation, ElevationToken::Lg);
assert_eq!(large_shadow.elevation, Some(ElevationToken::Lg));
assert_eq!(large_shadow.inset, Some(px(12.0)));
assert_eq!(large_shadow.resizer_width, px(8.0));
assert_eq!(large_shadow.side, Side::Right);
}

#[test]
fn test_sidebar_shell_surface_preset_elevation() {
let theme_default = SidebarShell::left(px(260.0)).surface_preset();
assert_eq!(theme_default.elevation, ElevationToken::Lg);
assert!(theme_default.use_theme_elevation_defaults);

let no_shadow = SidebarShell::left(px(260.0))
.elevation(ElevationToken::None)
.surface_preset();
assert_eq!(no_shadow.elevation, ElevationToken::None);
assert!(!no_shadow.use_theme_elevation_defaults);

let large_shadow = SidebarShell::left(px(260.0))
.elevation(ElevationToken::Lg)
.surface_preset();
assert_eq!(large_shadow.elevation, ElevationToken::Lg);
assert!(!large_shadow.use_theme_elevation_defaults);
}
}
15 changes: 14 additions & 1 deletion crates/ui/src/surface/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,9 @@ struct NoiseTileLayout {
}

fn noise_tile_layout(width: Pixels, height: Pixels, scale_factor: f32) -> NoiseTileLayout {
let tile_size_value = (GLASS_NOISE_TILE_SIZE_BASE / scale_factor.max(1.0)).round();
let tile_size_value = (GLASS_NOISE_TILE_SIZE_BASE / scale_factor.max(1.0))
.round()
.max(1.0);
let tile_size = px(tile_size_value);

if width <= px(0.0) || height <= px(0.0) {
Expand Down Expand Up @@ -631,6 +633,17 @@ mod tests {
assert_eq!(layout.last_tile_height, px(60.0));
}

#[test]
fn noise_tile_layout_clamps_tile_size_at_extreme_scale_factors() {
let layout = noise_tile_layout(px(3.0), px(2.0), 512.0);

assert_eq!(layout.tile_size, px(1.0));
assert_eq!(layout.cols, 3);
assert_eq!(layout.rows, 2);
assert_eq!(layout.last_tile_width, px(1.0));
assert_eq!(layout.last_tile_height, px(1.0));
}

#[test]
fn noise_tile_layout_handles_zero_dimensions() {
let layout = noise_tile_layout(px(0.0), px(0.0), 1.0);
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/components/sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ SidebarToggleButton::new()
### Floating Sidebar (SidebarShell + Sidebar)

`FloatingSidebar` composes `SidebarShell` with `Sidebar`, handling resize internally.
It defaults to an 8px inset, `ElevationToken::Sm`, and can be resized when expanded.
The shell elevation controls the complete panel shadow. Larger elevations may need a larger
It defaults to an 8px inset, uses the theme's panel elevation, and can be resized when expanded.
Call `.elevation(...)` to override the complete panel shadow. Larger elevations may need a larger
inset near window edges to avoid native-window clipping; the inset is never adjusted automatically.

```rust
Expand Down