Files
Ely-GPUI-Components/src/shell/frame.rs
T

138 lines
5.4 KiB
Rust

use gpui::{
AnyElement, App, Bounds, CursorStyle, Decorations, HitboxBehavior, IntoElement, MouseButton,
ParentElement, Pixels, Point, RenderOnce, ResizeEdge, Size, Styled, Window, canvas, div, point,
prelude::*,
};
use smallvec::SmallVec;
use crate::theme::{ActiveTheme, Elevation, Radius};
/// Which edge or corner `pos` grabs, within `inset` of the window's rim.
fn resize_edge(pos: Point<Pixels>, inset: Pixels, size: Size<Pixels>) -> Option<ResizeEdge> {
let (top, bottom) = (pos.y < inset, pos.y > size.height - inset);
let (left, right) = (pos.x < inset, pos.x > size.width - inset);
Some(match (top, bottom, left, right) {
(true, _, true, _) => ResizeEdge::TopLeft,
(true, _, _, true) => ResizeEdge::TopRight,
(true, ..) => ResizeEdge::Top,
(_, true, true, _) => ResizeEdge::BottomLeft,
(_, true, _, true) => ResizeEdge::BottomRight,
(_, true, ..) => ResizeEdge::Bottom,
(.., true, _) => ResizeEdge::Left,
(.., true) => ResizeEdge::Right,
_ => return None,
})
}
fn cursor(edge: ResizeEdge) -> CursorStyle {
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,
}
}
/// Resize margin, hairline and shadow for a client-drawn window; else a plain box.
#[derive(IntoElement, Default)]
pub struct ResizeBorder {
children: SmallVec<[AnyElement; 2]>,
}
impl ResizeBorder {
pub fn new() -> Self {
Self::default()
}
}
impl ParentElement for ResizeBorder {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements);
}
}
impl RenderOnce for ResizeBorder {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let Decorations::Client { tiling } = window.window_decorations() else {
return div().size_full().children(self.children).into_any_element();
};
let theme = cx.theme();
let inset = theme.window_inset().to_pixels(window.rem_size());
window.set_client_inset(inset);
let radius = theme.radius(Radius::Lg);
let floating = !tiling.is_tiled();
div()
.id("resize-border")
.size_full()
.child(
canvas(
|_, window, _| {
let size = window.window_bounds().get_bounds().size;
window.insert_hitbox(
Bounds::new(point(Pixels::ZERO, Pixels::ZERO), size),
HitboxBehavior::Normal,
)
},
move |_, hitbox, window, _| {
let size = window.window_bounds().get_bounds().size;
if let Some(edge) = resize_edge(window.mouse_position(), inset, size) {
window.set_cursor_style(cursor(edge), &hitbox);
}
},
)
.absolute()
.size_full(),
)
.when(!tiling.top, |edge| edge.pt(inset))
.when(!tiling.bottom, |edge| edge.pb(inset))
.when(!tiling.left, |edge| edge.pl(inset))
.when(!tiling.right, |edge| edge.pr(inset))
.on_mouse_move(|_, window, _| window.refresh())
.on_mouse_down(MouseButton::Left, move |event, window, _| {
let size = window.window_bounds().get_bounds().size;
if let Some(edge) = resize_edge(event.position, inset, size) {
log::info!("window: resize from {edge:?}");
window.start_window_resize(edge);
}
})
.child(
div()
.size_full()
.overflow_hidden()
.cursor(CursorStyle::Arrow)
.bg(theme.colors.bg)
.when(floating, |pane| {
pane.rounded(radius)
.border_1()
.border_color(theme.colors.border_strong)
.shadow(theme.elevation(Elevation::Modal))
})
.on_mouse_move(|_, _, cx| cx.stop_propagation())
.children(self.children),
)
.into_any_element()
}
}
#[cfg(test)]
mod tests {
use gpui::{ResizeEdge, point, px, size};
use super::resize_edge;
#[test]
fn corners_win_over_edges_and_the_middle_is_free() {
let window = size(px(400.0), px(300.0));
let at = |x: f32, y: f32| resize_edge(point(px(x), px(y)), px(10.0), window);
assert_eq!(at(2.0, 2.0), Some(ResizeEdge::TopLeft));
assert_eq!(at(398.0, 5.0), Some(ResizeEdge::TopRight));
assert_eq!(at(200.0, 3.0), Some(ResizeEdge::Top));
assert_eq!(at(1.0, 299.0), Some(ResizeEdge::BottomLeft));
assert_eq!(at(395.0, 295.0), Some(ResizeEdge::BottomRight));
assert_eq!(at(200.0, 295.0), Some(ResizeEdge::Bottom));
assert_eq!(at(4.0, 150.0), Some(ResizeEdge::Left));
assert_eq!(at(396.0, 150.0), Some(ResizeEdge::Right));
assert_eq!(at(200.0, 150.0), None);
}
}