Column shell: a resizable column drags by its border toward the filling column, whose handle draws that border and occludes the header under it, a drag setting off from the width drawn; a set width is a flex basis, so a column holds its header's own parts, where Windows' caption buttons ran out of the window
This commit is contained in:
4 files changed
+339
-28
No files matched your search
@@ -221,7 +221,7 @@ Ely GPUI Component. A component library for GPUI, in light and dark.
|
|||||||
- `typography::Ellipsis` shapes its line with the text style's own run, so a strike or an underline set on its box shows (`lists::ListItem::struck`, a closed task's title).
|
- `typography::Ellipsis` shapes its line with the text style's own run, so a strike or an underline set on its box shows (`lists::ListItem::struck`, a closed task's title).
|
||||||
- Focus that must follow an item to another parent is taken by the owner and keyed by the item: a handle gpui makes for an element dies with its path (`project::KanbanBoard`, whose cards keep focus across columns).
|
- Focus that must follow an item to another parent is taken by the owner and keyed by the item: a handle gpui makes for an element dies with its path (`project::KanbanBoard`, whose cards keep focus across columns).
|
||||||
- The theme's `platform`, the system's to start, decides the window buttons Ely draws, the title bar's layout and key caps, `secondary` included (`typography::keys::keystroke`). A title bar whose buttons the OS draws follows the OS.
|
- The theme's `platform`, the system's to start, decides the window buttons Ely draws, the title bar's layout and key caps, `secondary` included (`typography::keys::keystroke`). A title bar whose buttons the OS draws follows the OS.
|
||||||
- A window whose sidebar runs to its top is `shell::ColumnShell`: each column's header drags the window; the first leaves the traffic lights room, and drawn controls lead the first header in the macOS manner or end the last. A set width yields before a header's own parts; the host's header items keep one control's width and scroll when short.
|
- A window whose sidebar runs to its top is `shell::ColumnShell`: each column's header drags the window; the first leaves the traffic lights room, and drawn controls lead the first header in the macOS manner or end the last. A set width is a flex basis, not a width, so it holds its header's own parts, which a width let run past it, and yields when the shell runs short; the host's header items keep one control's width and scroll when short. A resizable column drags by its border toward the filling column, its right before it and its left after it; the handle draws that border and occludes the header under it, and a drag sets off from the width drawn, not the owner's.
|
||||||
- Taffy counts a flex item's text in its least width whatever width the item sets. A box that must add none lays its content absolutely inside a filling box (`shell::ColumnShell`'s title and body).
|
- Taffy counts a flex item's text in its least width whatever width the item sets. A box that must add none lays its content absolutely inside a filling box (`shell::ColumnShell`'s title and body).
|
||||||
- A centered title sits between a start and an end that grow alike from a zero basis, their padding on a box inside them, since taffy counts padding in the flex base; their content is their least width, so the title gives way before either end (`shell::TitleBar`).
|
- A centered title sits between a start and an end that grow alike from a zero basis, their padding on a box inside them, since taffy counts padding in the flex base; their content is their least width, so the title gives way before either end (`shell::TitleBar`).
|
||||||
- A pane the window's blur shows through is `shell::Vibrancy`, filled with the palette's `glass`, in a window opened `WindowBackgroundAppearance::Blurred`: vibrancy on macOS, acrylic on Windows. gpui's `MicaBackdrop` and `MicaAltBackdrop` are DWM backdrops from Windows 11 build 22621; older builds get none, in silence, and macOS plain transparency.
|
- A pane the window's blur shows through is `shell::Vibrancy`, filled with the palette's `glass`, in a window opened `WindowBackgroundAppearance::Blurred`: vibrancy on macOS, acrylic on Windows. gpui's `MicaBackdrop` and `MicaAltBackdrop` are DWM backdrops from Windows 11 build 22621; older builds get none, in silence, and macOS plain transparency.
|
||||||
|
|||||||
+168
-27
@@ -1,22 +1,48 @@
|
|||||||
use std::rc::Rc;
|
use std::{cell::Cell, rc::Rc};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, DefiniteLength, ElementId, FontWeight, IntoElement, ParentElement, RenderOnce,
|
AnyElement, App, Axis, DefiniteLength, Div, DragMoveEvent, ElementId, EmptyView, EntityId,
|
||||||
|
FontWeight, IntoElement, MouseButton, MouseDownEvent, ParentElement, Pixels, RenderOnce,
|
||||||
SharedString, Styled, Window, div, prelude::*,
|
SharedString, Styled, Window, div, prelude::*,
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use super::chrome::{OnClose, WindowControls, controls_style, drag_region, route_close};
|
use super::chrome::{OnClose, WindowControls, controls_style, drag_region, route_close};
|
||||||
use crate::{
|
use crate::{
|
||||||
layout::on_axis,
|
layout::{on_axis, resize_handle},
|
||||||
theme::{ActiveTheme, ControlSize, Platform, TextSize},
|
theme::{ActiveTheme, ControlSize, Platform, TextSize},
|
||||||
typography::Ellipsis,
|
typography::Ellipsis,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type OnResize = Rc<dyn Fn(Pixels, &mut Window, &mut App)>;
|
||||||
|
|
||||||
|
/// A width the owner keeps, which a drag on one border moves.
|
||||||
|
struct Resize {
|
||||||
|
width: Pixels,
|
||||||
|
min: Pixels,
|
||||||
|
max: Pixels,
|
||||||
|
on_resize: OnResize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The border a column's width is dragged by.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
enum Side {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BorderDrag {
|
||||||
|
owner: EntityId,
|
||||||
|
side: Side,
|
||||||
|
/// The width drawn when the drag set off.
|
||||||
|
start: Rc<Cell<Option<Pixels>>>,
|
||||||
|
}
|
||||||
|
|
||||||
/// One column of a `ColumnShell`: a header that drags the window, then its body.
|
/// One column of a `ColumnShell`: a header that drags the window, then its body.
|
||||||
pub struct ShellColumn {
|
pub struct ShellColumn {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
width: Option<DefiniteLength>,
|
width: Option<DefiniteLength>,
|
||||||
|
resize: Option<Resize>,
|
||||||
leading: SmallVec<[AnyElement; 2]>,
|
leading: SmallVec<[AnyElement; 2]>,
|
||||||
title: Option<SharedString>,
|
title: Option<SharedString>,
|
||||||
actions: SmallVec<[AnyElement; 2]>,
|
actions: SmallVec<[AnyElement; 2]>,
|
||||||
@@ -29,6 +55,7 @@ impl ShellColumn {
|
|||||||
Self {
|
Self {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
width: None,
|
width: None,
|
||||||
|
resize: None,
|
||||||
leading: SmallVec::new(),
|
leading: SmallVec::new(),
|
||||||
title: None,
|
title: None,
|
||||||
actions: SmallVec::new(),
|
actions: SmallVec::new(),
|
||||||
@@ -36,12 +63,35 @@ impl ShellColumn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A set width instead of the rest; it yields before a header's fixed parts.
|
/// A set width instead of the rest; it holds its header's fixed parts and yields when the shell runs short.
|
||||||
pub fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
|
pub fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
|
||||||
self.width = Some(width.into());
|
self.width = Some(width.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A set width the owner keeps: a drag on the border toward the filling column moves it within `min` and `max`, and the owner hears each new width.
|
||||||
|
pub fn resizable(
|
||||||
|
mut self,
|
||||||
|
width: Pixels,
|
||||||
|
min: Pixels,
|
||||||
|
max: Pixels,
|
||||||
|
on_resize: impl Fn(Pixels, &mut Window, &mut App) + 'static,
|
||||||
|
) -> Self {
|
||||||
|
assert!(
|
||||||
|
min <= max,
|
||||||
|
"shell column {:?}: limits {min:?} to {max:?} run backwards",
|
||||||
|
self.id
|
||||||
|
);
|
||||||
|
self.width = Some(width.into());
|
||||||
|
self.resize = Some(Resize {
|
||||||
|
width,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
on_resize: Rc::new(on_resize),
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Header content before the title.
|
/// Header content before the title.
|
||||||
pub fn leading(mut self, item: impl IntoElement) -> Self {
|
pub fn leading(mut self, item: impl IntoElement) -> Self {
|
||||||
self.leading.push(item.into_any_element());
|
self.leading.push(item.into_any_element());
|
||||||
@@ -132,9 +182,19 @@ impl RenderOnce for ColumnShell {
|
|||||||
.child(controls)
|
.child(controls)
|
||||||
};
|
};
|
||||||
let room = system && !window.is_fullscreen();
|
let room = system && !window.is_fullscreen();
|
||||||
|
// Each border lies toward the column that fills, so a drag on it moves one width.
|
||||||
|
let fill = self
|
||||||
|
.columns
|
||||||
|
.iter()
|
||||||
|
.position(|column| column.width.is_none());
|
||||||
let mut columns = Vec::with_capacity(count);
|
let mut columns = Vec::with_capacity(count);
|
||||||
for (ix, column) in self.columns.into_iter().enumerate() {
|
for (ix, column) in self.columns.into_iter().enumerate() {
|
||||||
let (first, last) = (ix == 0, ix + 1 == count);
|
let (first, last) = (ix == 0, ix + 1 == count);
|
||||||
|
let side = match fill {
|
||||||
|
Some(fill) if ix > fill => Some(Side::Left),
|
||||||
|
Some(fill) if ix == fill => None,
|
||||||
|
_ => (!last).then_some(Side::Right),
|
||||||
|
};
|
||||||
let lead = (!system && first && style == Platform::Mac).then(controls);
|
let lead = (!system && first && style == Platform::Mac).then(controls);
|
||||||
let trail = (!system && last && style != Platform::Mac).then(controls);
|
let trail = (!system && last && style != Platform::Mac).then(controls);
|
||||||
let flush = trail.is_some() && style == Platform::Windows;
|
let flush = trail.is_some() && style == Platform::Windows;
|
||||||
@@ -157,30 +217,48 @@ impl RenderOnce for ColumnShell {
|
|||||||
)
|
)
|
||||||
.children(trail);
|
.children(trail);
|
||||||
let colors = &cx.theme().colors;
|
let colors = &cx.theme().colors;
|
||||||
columns.push(
|
let box_ = div()
|
||||||
div()
|
.relative()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.h_full()
|
.h_full()
|
||||||
.map(|box_| match column.width {
|
// A basis, not a width, so the header's fixed parts set the least width.
|
||||||
Some(width) => box_.w(width),
|
.map(|box_| match column.width {
|
||||||
None => box_.flex_1(),
|
Some(width) => box_.flex_basis(width),
|
||||||
})
|
None => box_.flex_1(),
|
||||||
.when(!last, |box_| box_.border_r_1().border_color(colors.border))
|
})
|
||||||
.debug_selector(move || format!("column-shell-{ix}"))
|
// A resizing column's handle draws its border.
|
||||||
.child(header)
|
.map(|box_| match (side, column.resize.is_some()) {
|
||||||
.child(
|
(Some(Side::Left), false) => box_.border_l_1(),
|
||||||
// Absolute, so only the header sets the least width.
|
(Some(Side::Right), false) => box_.border_r_1(),
|
||||||
div().flex_1().min_h_0().relative().child(
|
_ => box_,
|
||||||
div()
|
})
|
||||||
.absolute()
|
.border_color(colors.border)
|
||||||
.inset_0()
|
.debug_selector(move || format!("column-shell-{ix}"))
|
||||||
.flex()
|
.child(header)
|
||||||
.flex_col()
|
.child(
|
||||||
.children(column.body),
|
// Absolute, so only the header sets the least width.
|
||||||
),
|
div().flex_1().min_h_0().relative().child(
|
||||||
|
div()
|
||||||
|
.absolute()
|
||||||
|
.inset_0()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.children(column.body),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
columns.push(match column.resize {
|
||||||
|
Some(resize) => {
|
||||||
|
let side = side.unwrap_or_else(|| {
|
||||||
|
panic!(
|
||||||
|
"column shell {:?}: column {ix} resizes, but no border lies toward a filling column",
|
||||||
|
self.id
|
||||||
|
)
|
||||||
|
});
|
||||||
|
resizing(box_, column.id, ix, side, resize, window, cx)
|
||||||
|
}
|
||||||
|
None => box_,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let ground = if self.glass {
|
let ground = if self.glass {
|
||||||
@@ -198,6 +276,69 @@ impl RenderOnce for ColumnShell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `box_` with a handle on its `side` border, whose drag moves its width.
|
||||||
|
fn resizing(
|
||||||
|
box_: Div,
|
||||||
|
id: ElementId,
|
||||||
|
ix: usize,
|
||||||
|
side: Side,
|
||||||
|
resize: Resize,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Div {
|
||||||
|
let Resize {
|
||||||
|
width,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
on_resize,
|
||||||
|
} = resize;
|
||||||
|
let pressed = window.use_keyed_state((id.clone(), "press"), cx, |_, _| None::<Pixels>);
|
||||||
|
let owner = pressed.entity_id();
|
||||||
|
let drag = BorderDrag {
|
||||||
|
owner,
|
||||||
|
side,
|
||||||
|
start: Rc::default(),
|
||||||
|
};
|
||||||
|
let at = pressed.clone();
|
||||||
|
let handle = resize_handle((id, "border"), Axis::Horizontal, cx)
|
||||||
|
.absolute()
|
||||||
|
.top_0()
|
||||||
|
.map(|handle| match side {
|
||||||
|
Side::Left => handle.left_0().justify_start(),
|
||||||
|
Side::Right => handle.right_0().justify_end(),
|
||||||
|
})
|
||||||
|
// Over a header, so a press drags the border, not the window.
|
||||||
|
.occlude()
|
||||||
|
.debug_selector(move || format!("column-shell-border-{ix}"))
|
||||||
|
.on_mouse_down(MouseButton::Left, move |event: &MouseDownEvent, _, cx| {
|
||||||
|
at.update(cx, |at, _| *at = Some(event.position.x))
|
||||||
|
})
|
||||||
|
.on_drag(drag, |_, _, _, cx| cx.new(|_| EmptyView));
|
||||||
|
box_.on_drag_move(move |event: &DragMoveEvent<BorderDrag>, window, cx| {
|
||||||
|
let drag = event.drag(cx);
|
||||||
|
if drag.owner != owner {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(anchor) = *pressed.read(cx) else {
|
||||||
|
log::error!("column shell: a drag on column {ix} with no press behind it");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// The drawn width, which a header's own parts may hold past the owner's.
|
||||||
|
let start = drag.start.get().unwrap_or(event.bounds.size.width);
|
||||||
|
drag.start.set(Some(start));
|
||||||
|
let moved = event.event.position.x - anchor;
|
||||||
|
let next = match drag.side {
|
||||||
|
Side::Left => start - moved,
|
||||||
|
Side::Right => start + moved,
|
||||||
|
}
|
||||||
|
.clamp(min, max);
|
||||||
|
if next != width {
|
||||||
|
on_resize(next, window, cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.child(handle)
|
||||||
|
}
|
||||||
|
|
||||||
/// A header strip at the title bar's height that drags the window.
|
/// A header strip at the title bar's height that drags the window.
|
||||||
fn header(id: ElementId, window: &mut Window, cx: &mut App) -> gpui::Stateful<gpui::Div> {
|
fn header(id: ElementId, window: &mut Window, cx: &mut App) -> gpui::Stateful<gpui::Div> {
|
||||||
drag_region((id, "header"), window, cx)
|
drag_region((id, "header"), window, cx)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
mod border;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AppContext as _, Context, FocusHandle, InteractiveElement, IntoElement, KeyUpEvent, Keystroke,
|
AppContext as _, Context, FocusHandle, InteractiveElement, IntoElement, KeyUpEvent, Keystroke,
|
||||||
ParentElement, Render, TestAppContext, VisualTestContext, Window, div, prelude::*, px, size,
|
ParentElement, Render, TestAppContext, VisualTestContext, Window, div, prelude::*, px, size,
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
use gpui::{
|
||||||
|
Context, Entity, IntoElement, Modifiers, MouseButton, ParentElement, Pixels, Point, Render,
|
||||||
|
TestAppContext, VisualTestContext, Window, div, point, prelude::*, px,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::shell::{ColumnShell, ShellColumn};
|
||||||
|
use crate::theme::{Platform, Theme};
|
||||||
|
|
||||||
|
/// Two set columns round a filling one; this view keeps their widths.
|
||||||
|
struct Sided {
|
||||||
|
platform: Platform,
|
||||||
|
widths: [Pixels; 2],
|
||||||
|
heard: Vec<Pixels>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for Sided {
|
||||||
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let view = cx.entity();
|
||||||
|
let side = |id: &'static str, ix: usize| {
|
||||||
|
let view = view.clone();
|
||||||
|
ShellColumn::new(id).title(id).resizable(
|
||||||
|
self.widths[ix],
|
||||||
|
px(100.0),
|
||||||
|
px(400.0),
|
||||||
|
move |width, _, cx| {
|
||||||
|
view.update(cx, |view, cx| {
|
||||||
|
view.widths[ix] = width;
|
||||||
|
view.heard.push(width);
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
div().w(px(1000.0)).h(px(400.0)).flex().flex_col().child(
|
||||||
|
ColumnShell::new("shell")
|
||||||
|
.platform(self.platform)
|
||||||
|
.column(side("left", 0))
|
||||||
|
.column(ShellColumn::new("main").title("main"))
|
||||||
|
.column(side("right", 1)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sided(
|
||||||
|
platform: Platform,
|
||||||
|
widths: [Pixels; 2],
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
) -> (Entity<Sided>, &mut VisualTestContext) {
|
||||||
|
let (view, cx) = cx.add_window_view(|_, _| Sided {
|
||||||
|
platform,
|
||||||
|
widths,
|
||||||
|
heard: Vec::new(),
|
||||||
|
});
|
||||||
|
cx.run_until_parked();
|
||||||
|
(view, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column(ix: usize, cx: &mut VisualTestContext) -> gpui::Bounds<Pixels> {
|
||||||
|
let name: &'static str = ["column-shell-0", "column-shell-1", "column-shell-2"][ix];
|
||||||
|
cx.debug_bounds(name).expect("a column")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Presses at `from`, moves past gpui's drag threshold, then by `by`, and lets go.
|
||||||
|
fn drag(from: Point<Pixels>, by: Pixels, cx: &mut VisualTestContext) {
|
||||||
|
let none = Modifiers::none();
|
||||||
|
cx.simulate_mouse_down(from, MouseButton::Left, none);
|
||||||
|
let set_off = from + point(px(0.0), px(4.0));
|
||||||
|
cx.simulate_mouse_move(set_off, Some(MouseButton::Left), none);
|
||||||
|
cx.run_until_parked();
|
||||||
|
let to = set_off + point(by, px(0.0));
|
||||||
|
cx.simulate_mouse_move(to, Some(MouseButton::Left), none);
|
||||||
|
cx.run_until_parked();
|
||||||
|
cx.simulate_mouse_up(to, MouseButton::Left, none);
|
||||||
|
cx.run_until_parked();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn heard(view: &Entity<Sided>, cx: &mut VisualTestContext) -> Option<Pixels> {
|
||||||
|
view.read_with(cx, |view, _| view.heard.last().copied())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A column before the filling one drags by its right border, one after it by its left.
|
||||||
|
#[gpui::test]
|
||||||
|
fn a_border_toward_the_filling_column_drags_its_width(cx: &mut TestAppContext) {
|
||||||
|
cx.update(Theme::init);
|
||||||
|
let (view, cx) = sided(Platform::Linux, [px(200.0), px(200.0)], cx);
|
||||||
|
let left = cx.debug_bounds("column-shell-border-0").expect("a border");
|
||||||
|
let right = cx.debug_bounds("column-shell-border-2").expect("a border");
|
||||||
|
assert_eq!(left.right(), column(0, cx).right());
|
||||||
|
assert_eq!(right.left(), column(2, cx).left());
|
||||||
|
drag(left.center(), px(50.0), cx);
|
||||||
|
assert_eq!(heard(&view, cx), Some(px(250.0)));
|
||||||
|
assert_eq!(column(0, cx).size.width, px(250.0));
|
||||||
|
drag(right.center(), px(-50.0), cx);
|
||||||
|
assert_eq!(heard(&view, cx), Some(px(250.0)));
|
||||||
|
let (main, panel) = (column(1, cx), column(2, cx));
|
||||||
|
assert_eq!((panel.size.width, panel.right()), (px(250.0), px(1000.0)));
|
||||||
|
assert_eq!((main.left(), main.right()), (px(250.0), px(750.0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A drag stops at the column's least and greatest width.
|
||||||
|
#[gpui::test]
|
||||||
|
fn a_border_drag_keeps_within_the_limits(cx: &mut TestAppContext) {
|
||||||
|
cx.update(Theme::init);
|
||||||
|
let (view, cx) = sided(Platform::Linux, [px(200.0), px(200.0)], cx);
|
||||||
|
let border = |cx: &mut VisualTestContext| {
|
||||||
|
cx.debug_bounds("column-shell-border-0")
|
||||||
|
.expect("a border")
|
||||||
|
.center()
|
||||||
|
};
|
||||||
|
drag(border(cx), px(900.0), cx);
|
||||||
|
assert_eq!(heard(&view, cx), Some(px(400.0)));
|
||||||
|
drag(border(cx), px(-900.0), cx);
|
||||||
|
assert_eq!(heard(&view, cx), Some(px(100.0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// In a header the border takes the press, so no system's drag moves the window, which panics here.
|
||||||
|
#[gpui::test]
|
||||||
|
fn a_border_drag_in_a_header_moves_no_window(cx: &mut TestAppContext) {
|
||||||
|
cx.update(Theme::init);
|
||||||
|
for platform in [Platform::Windows, Platform::Mac, Platform::Linux] {
|
||||||
|
let (view, cx) = sided(platform, [px(200.0), px(200.0)], cx);
|
||||||
|
for (ix, by) in [(0, px(30.0)), (2, px(-30.0))] {
|
||||||
|
let border = cx
|
||||||
|
.debug_bounds(["column-shell-border-0", "", "column-shell-border-2"][ix])
|
||||||
|
.expect("a border");
|
||||||
|
let header = column(ix, cx).top() + px(4.0);
|
||||||
|
drag(point(border.center().x, header), by, cx);
|
||||||
|
assert_eq!(heard(&view, cx), Some(px(230.0)), "{platform:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A header's own parts hold a column past the owner's width; a drag sets off from the drawn one.
|
||||||
|
#[gpui::test]
|
||||||
|
fn a_border_drag_sets_off_from_the_drawn_width(cx: &mut TestAppContext) {
|
||||||
|
cx.update(Theme::init);
|
||||||
|
let (view, cx) = sided(Platform::Windows, [px(200.0), px(100.0)], cx);
|
||||||
|
let (panel, close) = (column(2, cx), cx.debug_bounds("caption-Close"));
|
||||||
|
let close = close.expect("a close button");
|
||||||
|
assert!(close.right() <= panel.right(), "{close:?} in {panel:?}");
|
||||||
|
let drawn = panel.size.width;
|
||||||
|
assert!(drawn > px(100.0), "{drawn:?} holds the caption buttons");
|
||||||
|
let border = cx.debug_bounds("column-shell-border-2").expect("a border");
|
||||||
|
drag(border.center(), px(-10.0), cx);
|
||||||
|
assert_eq!(heard(&view, cx), Some(drawn + px(10.0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Two set columns and nothing that fills.
|
||||||
|
struct Unfilled;
|
||||||
|
|
||||||
|
impl Render for Unfilled {
|
||||||
|
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let column = |id: &'static str| ShellColumn::new(id).width(px(200.0));
|
||||||
|
ColumnShell::new("shell")
|
||||||
|
.platform(Platform::Linux)
|
||||||
|
.column(column("first"))
|
||||||
|
.column(column("last").resizable(px(200.0), px(100.0), px(400.0), |_, _, _| {}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The last column of a shell that nothing fills has no border to drag.
|
||||||
|
#[gpui::test]
|
||||||
|
#[should_panic(expected = "no border lies toward a filling column")]
|
||||||
|
fn a_resizing_column_needs_a_border_toward_a_filling_one(cx: &mut TestAppContext) {
|
||||||
|
cx.update(Theme::init);
|
||||||
|
let (_, cx) = cx.add_window_view(|_, _| Unfilled);
|
||||||
|
cx.run_until_parked();
|
||||||
|
}
|
||||||
Reference in new issue
Block a user