gpui-component/crates/ui/src/window_border.rs
Jason Lee 327d40e0e0
chore: Upgrade GPUI 3 (#587)
The pervious version has keep on
[gpui2](https://github.com/longbridge/gpui-component/tree/gpui2) branch,
if there need to use.

Ref https://github.com/zed-industries/zed/pull/22632

## Prepare

### Generate index.scip

```bash
rust-analyzer scip ./ > index.scip
```

### Get [refactor](https://github.com/zed-industries/refactor)

```bash
https://github.com/zed-industries/refactor.git
```

## Refactor exist code

```bash
cd refactor
cargo run -- ~/work/gpui-component
```

Then will get result if successed:

```
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/refactor /Users/jason/work/gpui-component`
Loaded 115 SCIP documents
Processing files starting with focus path: ""
Changed 90 files
```

-------------

## Changes

```diff
- View<T>
- Model<T>
+ Entity<T>

- WeakView<T>
+ WeakEntity<T>

- FocusableView
+ Focusable

- cx.bounds()
+ window.bounds()
- cx.refresh()
+ window.refresh()
- cx.focused()
+ window.focused(cx)
- cx.with_optional_element_state
- window.with_optional_element_state
```

```diff
- &mut WindowContext
+ &mut Window, &mut App

- cx: &mut WindowContext
+ window: &mut Window, cx: &mut App

- &mut ViewContext<
+ &mut Window, &mut Context<

- cx: &mut ViewContext<
+ window: &mut Window, cx: &mut Context<
```

```diff
- cx.spawn(|_, mut cx| async move { 
+ cx.spawn_in(window, |_, mut cx| async move {
- cx.dispatch_action(Box::new(...))
+ window.dispatch_action(Box::new(...), cx)
```

`cx.spawn`

```diff
- cx.spawn(|view, mut cx| async move {
+ cx.spawn_in(window, |view, mut window| async move {
+     _ = view.update_in(&mut window, move |view, window, cx| {
```
2025-01-27 03:58:48 +08:00

201 lines
8.2 KiB
Rust

// From:
// https://github.com/zed-industries/zed/blob/a8afc63a91f6b75528540dcffe73dc8ce0c92ad8/crates/gpui/examples/window_shadow.rs
use gpui::{
canvas, div, point, prelude::FluentBuilder as _, px, AnyElement, App, Bounds, CursorStyle,
Decorations, Edges, Hsla, InteractiveElement as _, IntoElement, MouseButton, ParentElement,
Pixels, Point, RenderOnce, ResizeEdge, Size, Styled as _, Window,
};
use crate::ActiveTheme;
#[cfg(not(target_os = "linux"))]
const SHADOW_SIZE: Pixels = Pixels(0.0);
#[cfg(target_os = "linux")]
const SHADOW_SIZE: Pixels = Pixels(12.0);
const BORDER_SIZE: Pixels = Pixels(1.0);
pub(crate) const BORDER_RADIUS: Pixels = Pixels(0.0);
/// Create a new window border.
pub fn window_border() -> WindowBorder {
WindowBorder::new()
}
/// Window border use to render a custom window border and shadow for Linux.
#[derive(IntoElement, Default)]
pub struct WindowBorder {
children: Vec<AnyElement>,
}
impl WindowBorder {
pub fn new() -> Self {
Self {
..Default::default()
}
}
}
/// Get the window paddings.
pub fn window_paddings(window: &Window) -> Edges<Pixels> {
match window.window_decorations() {
Decorations::Server => Edges::all(px(0.0)),
Decorations::Client { tiling } => {
let mut paddings = Edges::all(SHADOW_SIZE);
if tiling.top {
paddings.top = px(0.0);
}
if tiling.bottom {
paddings.bottom = px(0.0);
}
if tiling.left {
paddings.left = px(0.0);
}
if tiling.right {
paddings.right = px(0.0);
}
paddings
}
}
}
impl ParentElement for WindowBorder {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements);
}
}
impl RenderOnce for WindowBorder {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let decorations = window.window_decorations();
window.set_client_inset(SHADOW_SIZE);
div()
.id("window-backdrop")
.bg(gpui::transparent_black())
.map(|div| match decorations {
Decorations::Server => div,
Decorations::Client { tiling, .. } => div
.bg(gpui::transparent_black())
.child(
canvas(
|_bounds, window, _| {
window.insert_hitbox(
Bounds::new(
point(px(0.0), px(0.0)),
window.window_bounds().get_bounds().size,
),
false,
)
},
move |_bounds, hitbox, window, _| {
let mouse = window.mouse_position();
let size = window.window_bounds().get_bounds().size;
let Some(edge) = resize_edge(mouse, SHADOW_SIZE, size) else {
return;
};
window.set_cursor_style(
match edge {
ResizeEdge::Top | ResizeEdge::Bottom => {
CursorStyle::ResizeUpDown
}
ResizeEdge::Left | ResizeEdge::Right => {
CursorStyle::ResizeLeftRight
}
ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
CursorStyle::ResizeUpLeftDownRight
}
ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
CursorStyle::ResizeUpRightDownLeft
}
},
&hitbox,
);
},
)
.size_full()
.absolute(),
)
.when(!(tiling.top || tiling.right), |div| {
div.rounded_tr(BORDER_RADIUS)
})
.when(!(tiling.top || tiling.left), |div| {
div.rounded_tl(BORDER_RADIUS)
})
.when(!tiling.top, |div| div.pt(SHADOW_SIZE))
.when(!tiling.bottom, |div| div.pb(SHADOW_SIZE))
.when(!tiling.left, |div| div.pl(SHADOW_SIZE))
.when(!tiling.right, |div| div.pr(SHADOW_SIZE))
.on_mouse_move(|_e, window, _| window.refresh())
.on_mouse_down(MouseButton::Left, move |_, window, _| {
let size = window.window_bounds().get_bounds().size;
let pos = window.mouse_position();
match resize_edge(pos, SHADOW_SIZE, size) {
Some(edge) => window.start_window_resize(edge),
None => {}
};
}),
})
.size_full()
.child(
div()
.map(|div| match decorations {
Decorations::Server => div,
Decorations::Client { tiling } => div
// .border_color(cx.theme().window_border)
.when(!(tiling.top || tiling.right), |div| {
div.rounded_tr(BORDER_RADIUS)
})
.when(!(tiling.top || tiling.left), |div| {
div.rounded_tl(BORDER_RADIUS)
})
.border_color(cx.theme().window_border)
.when(!tiling.top, |div| div.border_t(BORDER_SIZE))
.when(!tiling.bottom, |div| div.border_b(BORDER_SIZE))
.when(!tiling.left, |div| div.border_l(BORDER_SIZE))
.when(!tiling.right, |div| div.border_r(BORDER_SIZE))
.when(!tiling.is_tiled(), |div| {
div.shadow(smallvec::smallvec![gpui::BoxShadow {
color: Hsla {
h: 0.,
s: 0.,
l: 0.,
a: 0.3,
},
blur_radius: SHADOW_SIZE / 2.,
spread_radius: px(0.),
offset: point(px(0.0), px(0.0)),
}])
}),
})
.on_mouse_move(|_e, _, cx| {
cx.stop_propagation();
})
.bg(gpui::transparent_black())
.size_full()
.children(self.children),
)
}
}
fn resize_edge(pos: Point<Pixels>, shadow_size: Pixels, size: Size<Pixels>) -> Option<ResizeEdge> {
let edge = if pos.y < shadow_size && pos.x < shadow_size {
ResizeEdge::TopLeft
} else if pos.y < shadow_size && pos.x > size.width - shadow_size {
ResizeEdge::TopRight
} else if pos.y < shadow_size {
ResizeEdge::Top
} else if pos.y > size.height - shadow_size && pos.x < shadow_size {
ResizeEdge::BottomLeft
} else if pos.y > size.height - shadow_size && pos.x > size.width - shadow_size {
ResizeEdge::BottomRight
} else if pos.y > size.height - shadow_size {
ResizeEdge::Bottom
} else if pos.x < shadow_size {
ResizeEdge::Left
} else if pos.x > size.width - shadow_size {
ResizeEdge::Right
} else {
return None;
};
Some(edge)
}