Compare commits

...

11 Commits

Author SHA1 Message Date
Mikayla
fcf9213b0a fix macos 2024-06-27 11:32:16 -07:00
Mikayla Maki
ded63f70ba clippy 2024-06-26 15:43:17 -07:00
Mikayla Maki
e2c9d622cc Finish implementing the window decorations example
co-authored-by: conrad <conrad@zed.dev>
2024-06-26 15:34:38 -07:00
Mikayla Maki
de18e950ed fix compiling 2024-06-26 10:22:54 -07:00
Mikayla Maki
7e29131253 Implement the window border 2024-06-26 10:04:03 -07:00
Mikayla Maki
4420d8c45d WIP 2024-06-26 10:04:02 -07:00
Mikayla Maki
f8e37b922a fix 2024-06-26 10:03:34 -07:00
Mikayla Maki
e0fd8fc32a WIP 2024-06-26 10:03:34 -07:00
Mikayla Maki
2b1896c0c4 Implement a first pass at client side decorations in GPUI 2024-06-26 10:03:34 -07:00
Mikayla Maki
502f652ddb Implement window decoration and tilings 2024-06-26 10:03:32 -07:00
Mikayla Maki
262657eb59 Default platform-specific GPUI methods
Adjust hello world example for wayland
Add GPUI methods for window resizing
2024-06-26 10:01:32 -07:00
19 changed files with 586 additions and 121 deletions

View File

@@ -14,7 +14,7 @@ pub use collab_panel::CollabPanel;
pub use collab_titlebar_item::CollabTitlebarItem;
use gpui::{
actions, point, AppContext, Pixels, PlatformDisplay, Size, Task, WindowBackgroundAppearance,
WindowBounds, WindowContext, WindowKind, WindowOptions,
WindowBounds, WindowContext, WindowDecorations, WindowKind, WindowOptions,
};
use panel_settings::MessageEditorSettings;
pub use panel_settings::{
@@ -122,8 +122,9 @@ fn notification_window_options(
kind: WindowKind::PopUp,
is_movable: false,
display_id: Some(screen.id()),
window_background: WindowBackgroundAppearance::default(),
window_background: WindowBackgroundAppearance::Transparent,
app_id: Some(app_id.to_owned()),
window_min_size: None,
window_decorations: Some(WindowDecorations::Client),
}
}

View File

@@ -159,6 +159,10 @@ path = "examples/image/image.rs"
name = "set_menus"
path = "examples/set_menus.rs"
[[example]]
name = "window_shadow"
path = "examples/window_shadow.rs"
[[example]]
name = "input"
path = "examples/input.rs"

View File

@@ -23,7 +23,7 @@ impl Render for HelloWorld {
fn main() {
App::new().run(|cx: &mut AppContext| {
let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx);
let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),

View File

@@ -52,6 +52,7 @@ fn main() {
is_movable: false,
app_id: None,
window_min_size: None,
window_decorations: None,
}
};

View File

@@ -0,0 +1,203 @@
use gpui::*;
use prelude::FluentBuilder;
struct WindowShadow {}
/*
Things to do:
1. We need a way of calculating which edge or corner the mouse is on,
and then dispatch on that
2. We need to improve the shadow rendering significantly
3. We need to implement the techniques in here in Zed
*/
impl Render for WindowShadow {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let decorations = cx.window_decorations();
let tiling = cx.window_tiling();
let rounding = px(10.0);
let shadow_size = px(10.0);
let border_size = px(1.0);
let grey = rgb(0x808080);
div()
.id("window-backdrop")
.when(decorations == WindowDecorations::Client, |div| {
div.bg(gpui::transparent_black())
.child(
canvas(
|_bounds, cx| {
cx.insert_hitbox(
Bounds::new(
point(px(0.0), px(0.0)),
cx.window_bounds().get_bounds().size,
),
false,
)
},
move |_bounds, hitbox, cx| {
let mouse = cx.mouse_position();
let size = cx.window_bounds().get_bounds().size;
let Some(edge) = resize_edge(mouse, shadow_size, size) else {
return;
};
cx.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(rounding)
})
.when(!(tiling.top || tiling.left), |div| div.rounded_tl(rounding))
.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, cx| cx.refresh())
.on_mouse_down(MouseButton::Left, move |e, cx| {
let size = cx.window_bounds().get_bounds().size;
let pos = e.position;
let edge = match resize_edge(pos, shadow_size, size) {
Some(value) => value,
None => return,
};
cx.start_window_resize(edge);
})
})
.size_full()
.child(
div()
.cursor(CursorStyle::Arrow)
.when(decorations == WindowDecorations::Client, |div| {
div.border_color(grey)
.when(!(tiling.top || tiling.right), |div| {
div.rounded_tr(rounding)
})
.when(!(tiling.top || tiling.left), |div| div.rounded_tl(rounding))
.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))
})
.on_mouse_move(|_e, cx| {
cx.stop_propagation();
})
.bg(gpui::rgb(0xCCCCFF))
.shadow(smallvec::smallvec![gpui::BoxShadow {
color: Hsla {
h: 0.,
s: 0.,
l: 0.,
a: 0.4,
},
blur_radius: shadow_size / 2.,
spread_radius: px(0.),
offset: point(px(0.0), px(0.0)),
}])
.size_full()
.flex()
.flex_col()
.justify_around()
.child(
div().w_full().flex().flex_row().justify_around().child(
div()
.id("hello")
.flex()
.bg(white())
.size(Length::Definite(Pixels(300.0).into()))
.justify_center()
.items_center()
.shadow_lg()
.border_1()
.border_color(rgb(0x0000ff))
.text_xl()
.text_color(rgb(0xffffff))
.child(div().w(px(100.0)).h(px(50.0)).bg(green()).shadow(
smallvec::smallvec![gpui::BoxShadow {
color: Hsla {
h: 0.,
s: 0.,
l: 0.,
a: 1.0,
},
blur_radius: px(20.0),
spread_radius: px(0.0),
offset: point(px(0.0), px(0.0)),
}],
))
.on_mouse_move(|e, cx| {
if e.dragging() {
cx.start_window_move();
}
}),
),
),
)
}
}
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)
}
fn main() {
App::new().run(|cx: &mut AppContext| {
let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx);
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
window_background: WindowBackgroundAppearance::Transparent,
..Default::default()
},
|cx| {
cx.new_view(|cx| {
cx.observe_window_appearance(|_, cx| {
cx.notify();
})
.detach();
WindowShadow {}
})
},
)
.unwrap();
});
}

View File

@@ -309,6 +309,16 @@ pub fn transparent_black() -> Hsla {
}
}
/// Transparent black in [`Hsla`]
pub fn transparent_white() -> Hsla {
Hsla {
h: 0.,
s: 0.,
l: 1.,
a: 0.,
}
}
/// Opaque grey in [`Hsla`], values will be clamped to the range [0, 1]
pub fn opaque_grey(lightness: f32, opacity: f32) -> Hsla {
Hsla {

View File

@@ -210,6 +210,70 @@ impl Debug for DisplayId {
unsafe impl Send for DisplayId {}
/// Which part of the window to resize
#[derive(Debug)]
pub enum ResizeEdge {
/// The top edge
Top,
/// The top right corner
TopRight,
/// The right edge
Right,
/// The bottom right corner
BottomRight,
/// The bottom edge
Bottom,
/// The bottom left corner
BottomLeft,
/// The left edge
Left,
/// The top left corner
TopLeft,
}
/// A type to describe the appearance of a window
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
pub enum WindowDecorations {
/// Client side decorations
Client,
#[default]
/// Server side decorations
Server,
}
/// What window controls this platform supports
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
pub struct WindowControls {
/// Whether this platform supports fullscreen
pub fullscreen: bool,
/// Whether this platform supports maximize
pub maximize: bool,
/// Whether this platform supports minimize
pub minimize: bool,
/// Whether this platform supports a window menu
pub window_menu: bool,
}
/// A type to describe which sides of the window are currently tiled in some way
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
pub struct Tiling {
/// Whether the top edge is tiled
pub top: bool,
/// Whether the left edge is tiled
pub left: bool,
/// Whether the right edge is tiled
pub right: bool,
/// Whether the bottom edge is tiled
pub bottom: bool,
}
impl Tiling {
/// Whether any edge is tiled
pub fn is_tiled(&self) -> bool {
self.top || self.left || self.right || self.bottom
}
}
pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn bounds(&self) -> Bounds<Pixels>;
fn is_maximized(&self) -> bool;
@@ -232,10 +296,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn activate(&self);
fn is_active(&self) -> bool;
fn set_title(&mut self, title: &str);
fn set_app_id(&mut self, app_id: &str);
fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance);
fn set_edited(&mut self, edited: bool);
fn show_character_palette(&self);
fn minimize(&self);
fn zoom(&self);
fn toggle_fullscreen(&self);
@@ -252,12 +313,38 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn completed_frame(&self) {}
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
// macOS specific methods
fn set_edited(&mut self, _edited: bool) {}
fn show_character_palette(&self) {}
#[cfg(target_os = "windows")]
fn get_raw_handle(&self) -> windows::HWND;
fn show_window_menu(&self, position: Point<Pixels>);
fn start_system_move(&self);
fn should_render_window_controls(&self) -> bool;
// Linux specific methods
fn request_decorations(&self, _decorations: WindowDecorations) {}
fn show_window_menu(&self, _position: Point<Pixels>) {}
fn start_window_move(&self) {}
fn start_window_resize(&self, _edge: ResizeEdge) {}
fn window_decorations(&self) -> WindowDecorations {
WindowDecorations::Client
}
fn set_app_id(&mut self, _app_id: &str) {}
fn tiling(&self) -> Tiling {
Tiling {
top: false,
left: false,
right: false,
bottom: false,
}
}
fn window_controls(&self) -> WindowControls {
WindowControls {
fullscreen: true,
maximize: true,
minimize: true,
window_menu: false,
}
}
#[cfg(any(test, feature = "test-support"))]
fn as_test(&mut self) -> Option<&mut TestWindow> {
@@ -570,6 +657,10 @@ pub struct WindowOptions {
/// Window minimum size
pub window_min_size: Option<Size<Pixels>>,
/// Whether to use client or server side decorations. Wayland only
/// Note that this may be ignored.
pub window_decorations: Option<WindowDecorations>,
}
/// The variables that can be configured when creating a new window
@@ -649,6 +740,7 @@ impl Default for WindowOptions {
window_background: WindowBackgroundAppearance::default(),
app_id: None,
window_min_size: None,
window_decorations: None,
}
}
}
@@ -805,6 +897,14 @@ pub enum CursorStyle {
/// corresponds to the CSS cursor value `ns-resize`
ResizeUpDown,
/// A resize cursor directing up-left and down-right
/// corresponds to the CSS cursor value `nesw-resize`
ResizeUpLeftDownRight,
/// A resize cursor directing up-right and down-left
/// corresponds to the CSS cursor value `nwse-resize`
ResizeUpRightDownLeft,
/// A cursor indicating that the item/column can be resized horizontally.
/// corresponds to the CSS curosr value `col-resize`
ResizeColumn,

View File

@@ -558,6 +558,8 @@ impl CursorStyle {
CursorStyle::ResizeUp => Shape::NResize,
CursorStyle::ResizeDown => Shape::SResize,
CursorStyle::ResizeUpDown => Shape::NsResize,
CursorStyle::ResizeUpLeftDownRight => Shape::NwseResize,
CursorStyle::ResizeUpRightDownLeft => Shape::NeswResize,
CursorStyle::ResizeColumn => Shape::ColResize,
CursorStyle::ResizeRow => Shape::RowResize,
CursorStyle::IBeamCursorForVerticalLayout => Shape::VerticalText,
@@ -585,6 +587,8 @@ impl CursorStyle {
CursorStyle::ResizeUp => "n-resize",
CursorStyle::ResizeDown => "s-resize",
CursorStyle::ResizeUpDown => "ns-resize",
CursorStyle::ResizeUpLeftDownRight => "nwse-resize",
CursorStyle::ResizeUpRightDownLeft => "nesw-resize",
CursorStyle::ResizeColumn => "col-resize",
CursorStyle::ResizeRow => "row-resize",
CursorStyle::IBeamCursorForVerticalLayout => "vertical-text",

View File

@@ -138,7 +138,7 @@ impl Globals {
primary_selection_manager: globals.bind(&qh, 1..=1, ()).ok(),
shm: globals.bind(&qh, 1..=1, ()).unwrap(),
seat,
wm_base: globals.bind(&qh, 1..=1, ()).unwrap(),
wm_base: globals.bind(&qh, 2..=2, ()).unwrap(),
viewporter: globals.bind(&qh, 1..=1, ()).ok(),
fractional_scale_manager: globals.bind(&qh, 1..=1, ()).ok(),
decoration_manager: globals.bind(&qh, 1..=1, ()).ok(),

View File

@@ -26,8 +26,9 @@ use crate::platform::{PlatformAtlas, PlatformInputHandler, PlatformWindow};
use crate::scene::Scene;
use crate::{
px, size, AnyWindowHandle, Bounds, Globals, Modifiers, Output, Pixels, PlatformDisplay,
PlatformInput, Point, PromptLevel, Size, WaylandClientStatePtr, WindowAppearance,
WindowBackgroundAppearance, WindowBounds, WindowParams,
PlatformInput, Point, PromptLevel, ResizeEdge, Size, Tiling, WaylandClientStatePtr,
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations,
WindowParams,
};
#[derive(Default)]
@@ -66,6 +67,7 @@ struct InProgressConfigure {
size: Option<Size<Pixels>>,
fullscreen: bool,
maximized: bool,
tiling: Tiling,
}
pub struct WaylandWindowState {
@@ -84,14 +86,17 @@ pub struct WaylandWindowState {
bounds: Bounds<Pixels>,
scale: f32,
input_handler: Option<PlatformInputHandler>,
decoration_state: WaylandDecorationState,
decoration_state: WindowDecorations,
fullscreen: bool,
maximized: bool,
tiling: Tiling,
windowed_bounds: Bounds<Pixels>,
client: WaylandClientStatePtr,
handle: AnyWindowHandle,
active: bool,
in_progress_configure: Option<InProgressConfigure>,
in_progress_window_controls: Option<WindowControls>,
window_controls: WindowControls,
}
#[derive(Clone)]
@@ -160,15 +165,23 @@ impl WaylandWindowState {
bounds: options.bounds,
scale: 1.0,
input_handler: None,
decoration_state: WaylandDecorationState::Client,
decoration_state: WindowDecorations::Client,
fullscreen: false,
maximized: false,
tiling: Tiling::default(),
windowed_bounds: options.bounds,
in_progress_configure: None,
client,
appearance,
handle,
active: false,
in_progress_window_controls: None,
window_controls: WindowControls {
fullscreen: false,
maximize: false,
minimize: false,
window_menu: false,
},
})
}
}
@@ -246,13 +259,7 @@ impl WaylandWindow {
.decoration_manager
.as_ref()
.map(|decoration_manager| {
let decoration = decoration_manager.get_toplevel_decoration(
&toplevel,
&globals.qh,
surface.id(),
);
decoration.set_mode(zxdg_toplevel_decoration_v1::Mode::ClientSide);
decoration
decoration_manager.get_toplevel_decoration(&toplevel, &globals.qh, surface.id())
});
let viewport = globals
@@ -311,6 +318,18 @@ impl WaylandWindowStatePtr {
pub fn handle_xdg_surface_event(&self, event: xdg_surface::Event) {
match event {
xdg_surface::Event::Configure { serial } => {
{
let mut state = self.state.borrow_mut();
if let Some(window_controls) = state.in_progress_window_controls.take() {
state.window_controls = window_controls;
drop(state);
let mut callbacks = self.callbacks.borrow_mut();
if let Some(appearance_changed) = callbacks.appearance_changed.as_mut() {
appearance_changed();
}
}
}
{
let mut state = self.state.borrow_mut();
@@ -318,6 +337,7 @@ impl WaylandWindowStatePtr {
let got_unmaximized = state.maximized && !configure.maximized;
state.fullscreen = configure.fullscreen;
state.maximized = configure.maximized;
state.tiling = configure.tiling;
if got_unmaximized {
configure.size = Some(state.windowed_bounds.size);
@@ -351,10 +371,20 @@ impl WaylandWindowStatePtr {
match event {
zxdg_toplevel_decoration_v1::Event::Configure { mode } => match mode {
WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ServerSide) => {
self.set_decoration_state(WaylandDecorationState::Server)
self.state.borrow_mut().decoration_state = WindowDecorations::Server;
if let Some(mut appearance_changed) =
self.callbacks.borrow_mut().appearance_changed.as_mut()
{
appearance_changed();
}
}
WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ClientSide) => {
self.set_decoration_state(WaylandDecorationState::Client)
self.state.borrow_mut().decoration_state = WindowDecorations::Client;
if let Some(mut appearance_changed) =
self.callbacks.borrow_mut().appearance_changed.as_mut()
{
appearance_changed();
}
}
WEnum::Value(_) => {
log::warn!("Unknown decoration mode");
@@ -389,14 +419,44 @@ impl WaylandWindowStatePtr {
Some(size(px(width as f32), px(height as f32)))
};
let fullscreen = states.contains(&(xdg_toplevel::State::Fullscreen as u8));
let maximized = states.contains(&(xdg_toplevel::State::Maximized as u8));
let states = extract_states::<xdg_toplevel::State>(&states);
let mut tiling = Tiling::default();
let mut fullscreen = false;
let mut maximized = false;
for state in states {
match state {
xdg_toplevel::State::Maximized => {
maximized = true;
}
xdg_toplevel::State::Fullscreen => {
fullscreen = true;
}
xdg_toplevel::State::TiledTop => {
tiling.top = true;
}
xdg_toplevel::State::TiledLeft => {
tiling.left = true;
}
xdg_toplevel::State::TiledRight => {
tiling.right = true;
}
xdg_toplevel::State::TiledBottom => {
tiling.bottom = true;
}
_ => {
// noop
}
}
}
let mut state = self.state.borrow_mut();
state.in_progress_configure = Some(InProgressConfigure {
size,
fullscreen,
maximized,
tiling,
});
false
@@ -415,6 +475,33 @@ impl WaylandWindowStatePtr {
true
}
}
xdg_toplevel::Event::WmCapabilities { capabilities } => {
let mut window_controls = WindowControls::default();
let states = extract_states::<xdg_toplevel::WmCapabilities>(&capabilities);
for state in states {
match state {
xdg_toplevel::WmCapabilities::Maximize => {
window_controls.maximize = true;
}
xdg_toplevel::WmCapabilities::Minimize => {
window_controls.minimize = true;
}
xdg_toplevel::WmCapabilities::Fullscreen => {
window_controls.fullscreen = true;
}
xdg_toplevel::WmCapabilities::WindowMenu => {
window_controls.window_menu = true;
}
_ => {}
}
}
let mut state = self.state.borrow_mut();
state.in_progress_window_controls = Some(window_controls);
false
}
_ => false,
}
}
@@ -545,18 +632,6 @@ impl WaylandWindowStatePtr {
self.set_size_and_scale(None, Some(scale));
}
/// Notifies the window of the state of the decorations.
///
/// # Note
///
/// This API is indirectly called by the wayland compositor and
/// not meant to be called by a user who wishes to change the state
/// of the decorations. This is because the state of the decorations
/// is managed by the compositor and not the client.
pub fn set_decoration_state(&self, state: WaylandDecorationState) {
self.state.borrow_mut().decoration_state = state;
}
pub fn close(&self) {
let mut callbacks = self.callbacks.borrow_mut();
if let Some(fun) = callbacks.close.take() {
@@ -599,6 +674,17 @@ impl WaylandWindowStatePtr {
}
}
fn extract_states<'a, S: TryFrom<u32> + 'a>(states: &'a [u8]) -> impl Iterator<Item = S> + 'a
where
<S as TryFrom<u32>>::Error: 'a,
{
states
.chunks_exact(4)
.flat_map(TryInto::<[u8; 4]>::try_into)
.map(u32::from_ne_bytes)
.flat_map(S::try_from)
}
fn primary_output_scale(state: &mut RefMut<WaylandWindowState>) -> i32 {
let mut scale = 1;
let mut current_output = state.display.take();
@@ -758,14 +844,6 @@ impl PlatformWindow for WaylandWindow {
region.destroy();
}
fn set_edited(&mut self, _edited: bool) {
log::info!("ignoring macOS specific set_edited");
}
fn show_character_palette(&self) {
log::info!("ignoring macOS specific show_character_palette");
}
fn minimize(&self) {
self.borrow().toplevel.set_minimized();
}
@@ -850,22 +928,61 @@ impl PlatformWindow for WaylandWindow {
);
}
fn start_system_move(&self) {
fn start_window_move(&self) {
let state = self.borrow();
let serial = state.client.get_serial(SerialKind::MousePress);
state.toplevel._move(&state.globals.seat, serial);
}
fn should_render_window_controls(&self) -> bool {
self.borrow().decoration_state == WaylandDecorationState::Client
fn start_window_resize(&self, edge: crate::ResizeEdge) {
let state = self.borrow();
state.toplevel.resize(
&state.globals.seat,
state.client.get_serial(SerialKind::MousePress),
edge.to_xdg(),
)
}
fn window_decorations(&self) -> WindowDecorations {
self.borrow().decoration_state
}
fn tiling(&self) -> Tiling {
self.borrow().tiling
}
fn request_decorations(&self, decorations: WindowDecorations) {
let mut state = self.borrow_mut();
if let Some(decoration) = state.decoration.as_ref() {
decoration.set_mode(decorations.to_xdg())
}
}
fn window_controls(&self) -> WindowControls {
self.borrow().window_controls
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum WaylandDecorationState {
/// Decorations are to be provided by the client
Client,
/// Decorations are provided by the server
Server,
impl WindowDecorations {
fn to_xdg(&self) -> zxdg_toplevel_decoration_v1::Mode {
match self {
WindowDecorations::Client => zxdg_toplevel_decoration_v1::Mode::ClientSide,
WindowDecorations::Server => zxdg_toplevel_decoration_v1::Mode::ServerSide,
}
}
}
impl ResizeEdge {
fn to_xdg(&self) -> xdg_toplevel::ResizeEdge {
match self {
ResizeEdge::Top => xdg_toplevel::ResizeEdge::Top,
ResizeEdge::TopRight => xdg_toplevel::ResizeEdge::TopRight,
ResizeEdge::Right => xdg_toplevel::ResizeEdge::Right,
ResizeEdge::BottomRight => xdg_toplevel::ResizeEdge::BottomRight,
ResizeEdge::Bottom => xdg_toplevel::ResizeEdge::Bottom,
ResizeEdge::BottomLeft => xdg_toplevel::ResizeEdge::BottomLeft,
ResizeEdge::Left => xdg_toplevel::ResizeEdge::Left,
ResizeEdge::TopLeft => xdg_toplevel::ResizeEdge::TopLeft,
}
}
}

View File

@@ -4,7 +4,7 @@ use crate::{
platform::blade::{BladeRenderer, BladeSurfaceConfig},
px, size, AnyWindowHandle, Bounds, DevicePixels, ForegroundExecutor, Modifiers, Pixels,
PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
PromptLevel, Scene, Size, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
PromptLevel, Scene, Size, Tiling, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
WindowKind, WindowParams, X11ClientStatePtr,
};
@@ -990,7 +990,7 @@ impl PlatformWindow for X11Window {
.unwrap();
}
fn start_system_move(&self) {
fn start_window_move(&self) {
let state = self.0.state.borrow();
let pointer = self
.0
@@ -1023,7 +1023,18 @@ impl PlatformWindow for X11Window {
.unwrap();
}
fn should_render_window_controls(&self) -> bool {
false
// TODO: implement X11 decoration management
fn window_decorations(&self) -> crate::WindowDecorations {
crate::WindowDecorations::Server
}
// TODO: implement X11 decoration management
fn tiling(&self) -> Tiling {
Tiling {
top: false,
left: false,
right: false,
bottom: false,
}
}
}

View File

@@ -796,14 +796,24 @@ impl Platform for MacPlatform {
CursorStyle::ClosedHand => msg_send![class!(NSCursor), closedHandCursor],
CursorStyle::OpenHand => msg_send![class!(NSCursor), openHandCursor],
CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
CursorStyle::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor],
CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), verticalResizeCursor],
CursorStyle::ResizeLeft => msg_send![class!(NSCursor), resizeLeftCursor],
CursorStyle::ResizeRight => msg_send![class!(NSCursor), resizeRightCursor],
CursorStyle::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor],
CursorStyle::ResizeColumn => msg_send![class!(NSCursor), resizeLeftRightCursor],
CursorStyle::ResizeRow => msg_send![class!(NSCursor), resizeUpDownCursor],
CursorStyle::ResizeUp => msg_send![class!(NSCursor), resizeUpCursor],
CursorStyle::ResizeDown => msg_send![class!(NSCursor), resizeDownCursor],
CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
CursorStyle::ResizeRow => msg_send![class!(NSCursor), resizeUpDownCursor],
// Undocumented, private class methods:
// https://stackoverflow.com/questions/27242353/cocoa-predefined-resize-mouse-cursor
CursorStyle::ResizeUpLeftDownRight => {
msg_send![class!(NSCursor), _windowResizeNorthWestSouthEastCursor]
}
CursorStyle::ResizeUpRightDownLeft => {
msg_send![class!(NSCursor), _windowResizeNorthEastSouthWestCursor]
}
CursorStyle::IBeamCursorForVerticalLayout => {
msg_send![class!(NSCursor), IBeamCursorForVerticalLayout]
}

View File

@@ -1092,14 +1092,6 @@ impl PlatformWindow for MacWindow {
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
self.0.lock().renderer.sprite_atlas().clone()
}
fn show_window_menu(&self, _position: Point<Pixels>) {}
fn start_system_move(&self) {}
fn should_render_window_controls(&self) -> bool {
false
}
}
impl rwh::HasWindowHandle for MacWindow {

View File

@@ -262,13 +262,9 @@ impl PlatformWindow for TestWindow {
unimplemented!()
}
fn start_system_move(&self) {
fn start_window_move(&self) {
unimplemented!()
}
fn should_render_window_controls(&self) -> bool {
false
}
}
pub(crate) struct TestAtlasState {

View File

@@ -511,8 +511,6 @@ impl PlatformWindow for WindowsWindow {
.ok();
}
fn set_app_id(&mut self, _app_id: &str) {}
fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
self.0
.state
@@ -521,12 +519,6 @@ impl PlatformWindow for WindowsWindow {
.update_transparency(background_appearance != WindowBackgroundAppearance::Opaque);
}
// todo(windows)
fn set_edited(&mut self, _edited: bool) {}
// todo(windows)
fn show_character_palette(&self) {}
fn minimize(&self) {
unsafe { ShowWindowAsync(self.0.hwnd, SW_MINIMIZE).ok().log_err() };
}
@@ -645,14 +637,6 @@ impl PlatformWindow for WindowsWindow {
fn get_raw_handle(&self) -> HWND {
self.0.hwnd
}
fn show_window_menu(&self, _position: Point<Pixels>) {}
fn start_system_move(&self) {}
fn should_render_window_controls(&self) -> bool {
false
}
}
#[implement(IDropTarget)]

View File

@@ -9,11 +9,11 @@ use crate::{
MonochromeSprite, MouseButton, MouseEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels,
PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
RenderSvgParams, ScaledPixels, Scene, Shadow, SharedString, Size, StrikethroughStyle, Style,
SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement,
TransformationMatrix, Underline, UnderlineStyle, View, VisualContext, WeakView,
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowOptions, WindowParams,
WindowTextSystem, SUBPIXEL_VARIANTS,
RenderSvgParams, ResizeEdge, ScaledPixels, Scene, Shadow, SharedString, Size,
StrikethroughStyle, Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle,
TextStyleRefinement, TransformationMatrix, Underline, UnderlineStyle, View, VisualContext,
WeakView, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls,
WindowDecorations, WindowOptions, WindowParams, WindowTextSystem, SUBPIXEL_VARIANTS,
};
use anyhow::{anyhow, Context as _, Result};
use collections::{FxHashMap, FxHashSet};
@@ -633,6 +633,7 @@ impl Window {
window_background,
app_id,
window_min_size,
window_decorations,
} = options;
let bounds = window_bounds
@@ -666,6 +667,9 @@ impl Window {
let next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>> = Default::default();
let last_input_timestamp = Rc::new(Cell::new(Instant::now()));
platform_window
.request_decorations(window_decorations.unwrap_or(WindowDecorations::Server));
if let Some(ref window_open_state) = window_bounds {
match window_open_state {
WindowBounds::Fullscreen(_) => platform_window.toggle_fullscreen(),
@@ -984,6 +988,21 @@ impl<'a> WindowContext<'a> {
self.window.platform_window.is_maximized()
}
/// Check if the platform window is current tiled
pub fn window_tiling(&self) -> crate::Tiling {
self.window.platform_window.tiling()
}
/// request a certain window decoration (Wayland)
pub fn request_decorations(&self, decorations: WindowDecorations) {
self.window.platform_window.request_decorations(decorations);
}
/// Start a window resize operation (Wayland)
pub fn start_window_resize(&self, edge: ResizeEdge) {
self.window.platform_window.start_window_resize(edge);
}
/// Return the `WindowBounds` to indicate that how a window should be opened
/// after it has been closed
pub fn window_bounds(&self) -> WindowBounds {
@@ -1211,13 +1230,18 @@ impl<'a> WindowContext<'a> {
/// Tells the compositor to take control of window movement (Wayland and X11)
///
/// Events may not be received during a move operation.
pub fn start_system_move(&self) {
self.window.platform_window.start_system_move()
pub fn start_window_move(&self) {
self.window.platform_window.start_window_move()
}
/// Returns whether the title bar window controls need to be rendered by the application (Wayland and X11)
pub fn should_render_window_controls(&self) -> bool {
self.window.platform_window.should_render_window_controls()
pub fn window_decorations(&self) -> WindowDecorations {
self.window.platform_window.window_decorations()
}
/// Returns which window controls are currently visible (Wayland)
pub fn window_controls(&self) -> WindowControls {
self.window.platform_window.window_controls()
}
/// Updates the window's title at the platform level.

View File

@@ -19,6 +19,7 @@ impl LinuxWindowControls {
impl RenderOnce for LinuxWindowControls {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let controls = cx.window_controls();
let close_button_hover_color = Rgba {
r: 232.0 / 255.0,
g: 17.0 / 255.0,
@@ -49,22 +50,26 @@ impl RenderOnce for LinuxWindowControls {
.content_stretch()
.max_h(self.button_height)
.min_h(self.button_height)
.child(TitlebarButton::new(
"minimize",
TitlebarButtonType::Minimize,
button_hover_color,
self.close_window_action.boxed_clone(),
))
.child(TitlebarButton::new(
"maximize-or-restore",
if cx.is_maximized() {
TitlebarButtonType::Restore
} else {
TitlebarButtonType::Maximize
},
button_hover_color,
self.close_window_action.boxed_clone(),
))
.children(controls.minimize.then(|| {
TitlebarButton::new(
"minimize",
TitlebarButtonType::Minimize,
button_hover_color,
self.close_window_action.boxed_clone(),
)
}))
.children(controls.maximize.then(|| {
TitlebarButton::new(
"maximize-or-restore",
if cx.is_maximized() {
TitlebarButtonType::Restore
} else {
TitlebarButtonType::Maximize
},
button_hover_color,
self.close_window_action.boxed_clone(),
)
}))
.child(TitlebarButton::new(
"close",
TitlebarButtonType::Close,

View File

@@ -1,4 +1,4 @@
use gpui::{Action, AnyElement, Interactivity, Stateful};
use gpui::{Action, AnyElement, Interactivity, Stateful, WindowDecorations};
use smallvec::SmallVec;
use crate::components::title_bar::linux_window_controls::LinuxWindowControls;
@@ -117,16 +117,18 @@ impl RenderOnce for TitleBar {
.when(
self.platform_style == PlatformStyle::Linux
&& !cx.is_fullscreen()
&& cx.should_render_window_controls(),
&& cx.window_decorations() == WindowDecorations::Client,
|title_bar| {
title_bar
.child(LinuxWindowControls::new(height, self.close_window_action))
.on_mouse_down(gpui::MouseButton::Right, move |ev, cx| {
cx.show_window_menu(ev.position)
.when(cx.window_controls().window_menu, |title_bar| {
title_bar.on_mouse_down(gpui::MouseButton::Right, move |ev, cx| {
cx.show_window_menu(ev.position)
})
})
.on_mouse_move(move |ev, cx| {
if ev.dragging() {
cx.start_system_move();
cx.start_window_move();
}
})
},

View File

@@ -105,6 +105,7 @@ pub fn build_window_options(display_uuid: Option<Uuid>, cx: &mut AppContext) ->
display_id: display.map(|display| display.id()),
window_background: cx.theme().window_background_appearance(),
app_id: Some(app_id.to_owned()),
window_decorations: Some(gpui::WindowDecorations::Client),
window_min_size: Some(gpui::Size {
width: px(360.0),
height: px(240.0),