Skip to content

Commit

Permalink
feat(window): drag resize window (#208)
Browse files Browse the repository at this point in the history
* feat: support drag resize window

* chore: add target_os cfg

* fix: do not resize when window is maximized

* feat(window): double click to toggle window maximize

* fix: resizable condition

* fix(window): new window with no decorations and set transparent
  • Loading branch information
pewsheen authored Oct 24, 2024
1 parent 8c8a794 commit decf3b3
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 23 deletions.
4 changes: 2 additions & 2 deletions resources/panel.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/webview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ impl Window {
"MINIMIZE" => {
self.window.set_minimized(true);
}
"MAXIMIZE" => {
"MAXIMIZE" | "DBCLICK_PANEL" => {
let is_maximized = self.window.is_maximized();
self.window.set_maximized(!is_maximized);
}
Expand Down
161 changes: 141 additions & 20 deletions src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use webrender_api::{
units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePoint, LayoutVector2D},
ScrollLocation,
};
#[cfg(any(linux, target_os = "windows"))]
use winit::window::ResizeDirection;
use winit::{
dpi::PhysicalPosition,
event::{ElementState, TouchPhase, WindowEvent},
Expand Down Expand Up @@ -45,7 +47,7 @@ pub struct Window {
/// The WebView of this window.
pub(crate) webview: Option<WebView>,
/// The mouse physical position in the web view.
mouse_position: Cell<PhysicalPosition<f64>>,
mouse_position: Cell<Option<PhysicalPosition<f64>>>,
/// Modifiers state of the keyboard.
modifiers_state: Cell<ModifiersState>,
}
Expand Down Expand Up @@ -90,7 +92,7 @@ impl Window {
surface,
panel: None,
webview: None,
mouse_position: Cell::new(PhysicalPosition::default()),
mouse_position: Default::default(),
modifiers_state: Cell::new(ModifiersState::default()),
},
rendering_context,
Expand All @@ -99,10 +101,11 @@ impl Window {

/// Create a Verso window with the rendering context.
pub fn new_with_compositor(evl: &ActiveEventLoop, compositor: &mut IOCompositor) -> Self {
let window_attrs = WinitWindow::default_attributes()
.with_decorations(false)
.with_transparent(true);
let window = evl
.create_window(WinitWindow::default_attributes())
// .with_transparent(true)
// .with_decorations(false)
.create_window(window_attrs)
.expect("Failed to create window.");

#[cfg(macos)]
Expand All @@ -125,7 +128,7 @@ impl Window {
surface,
panel: None,
webview: None,
mouse_position: Cell::new(PhysicalPosition::default()),
mouse_position: Default::default(),
modifiers_state: Cell::new(ModifiersState::default()),
};
compositor.swap_current_window(&mut window);
Expand Down Expand Up @@ -196,12 +199,43 @@ impl Window {
WindowEvent::CursorEntered { .. } => {
compositor.swap_current_window(self);
}
WindowEvent::CursorLeft { .. } => {
self.mouse_position.set(None);
}
WindowEvent::CursorMoved { position, .. } => {
let cursor: DevicePoint = DevicePoint::new(position.x as f32, position.y as f32);
self.mouse_position.set(*position);
self.mouse_position.set(Some(*position));
compositor.on_mouse_window_move_event_class(cursor);

// handle Windows and Linux non-decoration window resize cursor
#[cfg(any(linux, target_os = "windows"))]
{
if self.is_resizable() {
let direction = self.get_drag_resize_direction();
self.set_drag_resize_cursor(direction);
}
}
}
WindowEvent::MouseInput { state, button, .. } => {
let position = match self.mouse_position.get() {
Some(position) => Point2D::new(position.x as f32, position.y as f32),
None => {
log::trace!("Mouse position is None, skipping MouseInput event.");
return false;
}
};

// handle Windows and Linux non-decoration window resize
#[cfg(any(linux, target_os = "windows"))]
{
if *state == ElementState::Pressed && *button == winit::event::MouseButton::Left
{
if self.is_resizable() {
self.drag_resize_window();
}
}
}

let button: script_traits::MouseButton = match button {
winit::event::MouseButton::Left => script_traits::MouseButton::Left,
winit::event::MouseButton::Right => script_traits::MouseButton::Right,
Expand All @@ -213,10 +247,6 @@ impl Window {
return false;
}
};
let position = Point2D::new(
self.mouse_position.get().x as f32,
self.mouse_position.get().y as f32,
);

let event: MouseWindowEvent = match state {
ElementState::Pressed => MouseWindowEvent::MouseDown(button, position),
Expand All @@ -234,6 +264,14 @@ impl Window {
compositor.on_zoom_window_event(1.0 + *delta as f32, self);
}
WindowEvent::MouseWheel { delta, phase, .. } => {
let position = match self.mouse_position.get() {
Some(position) => position,
None => {
log::trace!("Mouse position is None, skipping MouseWheel event.");
return false;
}
};

// FIXME: Pixels per line, should be configurable (from browser setting?) and vary by zoom level.
const LINE_HEIGHT: f32 = 38.0;

Expand All @@ -250,10 +288,7 @@ impl Window {
// Wheel Event
compositor.on_wheel_event(
WheelDelta { x, y, z: 0.0, mode },
DevicePoint::new(
self.mouse_position.get().x as f32,
self.mouse_position.get().y as f32,
),
DevicePoint::new(position.x as f32, position.y as f32),
);

// Scroll Event
Expand All @@ -273,10 +308,7 @@ impl Window {

compositor.on_scroll_event(
ScrollLocation::Delta(LayoutVector2D::new(x as f32, y as f32)),
DeviceIntPoint::new(
self.mouse_position.get().x as i32,
self.mouse_position.get().y as i32,
),
DeviceIntPoint::new(position.x as i32, position.y as i32),
phase,
);
}
Expand All @@ -301,7 +333,7 @@ impl Window {
clipboard: Option<&mut Clipboard>,
compositor: &mut IOCompositor,
) -> bool {
// // Handle message in Verso Panel
// Handle message in Verso Panel
if let Some(panel) = &self.panel {
if panel.webview.webview_id == webview_id {
return self.handle_servo_messages_with_panel(
Expand Down Expand Up @@ -430,6 +462,95 @@ impl Window {
}
}

// Non-decorated window resizing for Windows and Linux.
#[cfg(any(linux, target_os = "windows"))]
impl Window {
/// Check current window state is allowed to drag-resize.
fn is_resizable(&self) -> bool {
// TODO: Check if the window is in fullscreen mode.
!self.window.is_maximized() && self.window.is_resizable()
}

/// Drag resize the window.
fn drag_resize_window(&self) {
if let Some(direction) = self.get_drag_resize_direction() {
if let Err(err) = self.window.drag_resize_window(direction) {
log::error!("Failed to drag-resize window: {:?}", err);
}
}
}

/// Get drag-resize direction.
fn get_drag_resize_direction(&self) -> Option<ResizeDirection> {
let mouse_position = match self.mouse_position.get() {
Some(position) => position,
None => {
return None;
}
};

let window_size = self.window.outer_size();
let border_size = 5.0 * self.window.scale_factor();

let x_direction = if mouse_position.x < border_size {
Some(ResizeDirection::West)
} else if mouse_position.x > (window_size.width as f64 - border_size) {
Some(ResizeDirection::East)
} else {
None
};

let y_direction = if mouse_position.y < border_size {
Some(ResizeDirection::North)
} else if mouse_position.y > (window_size.height as f64 - border_size) {
Some(ResizeDirection::South)
} else {
None
};

let direction = match (x_direction, y_direction) {
(Some(ResizeDirection::East), None) => ResizeDirection::East,
(Some(ResizeDirection::West), None) => ResizeDirection::West,
(None, Some(ResizeDirection::South)) => ResizeDirection::South,
(None, Some(ResizeDirection::North)) => ResizeDirection::North,
(Some(ResizeDirection::East), Some(ResizeDirection::North)) => {
ResizeDirection::NorthEast
}
(Some(ResizeDirection::West), Some(ResizeDirection::North)) => {
ResizeDirection::NorthWest
}
(Some(ResizeDirection::East), Some(ResizeDirection::South)) => {
ResizeDirection::SouthEast
}
(Some(ResizeDirection::West), Some(ResizeDirection::South)) => {
ResizeDirection::SouthWest
}
_ => return None,
};

Some(direction)
}

/// Set drag-resize cursor when mouse is hover on the border of the window.
fn set_drag_resize_cursor(&self, direction: Option<ResizeDirection>) {
let cursor = match direction {
Some(direction) => match direction {
ResizeDirection::East => CursorIcon::EResize,
ResizeDirection::West => CursorIcon::WResize,
ResizeDirection::South => CursorIcon::SResize,
ResizeDirection::North => CursorIcon::NResize,
ResizeDirection::NorthEast => CursorIcon::NeResize,
ResizeDirection::NorthWest => CursorIcon::NwResize,
ResizeDirection::SouthEast => CursorIcon::SeResize,
ResizeDirection::SouthWest => CursorIcon::SwResize,
},
None => CursorIcon::Default,
};

self.window.set_cursor(cursor);
}
}

/* window decoration */
#[cfg(macos)]
use objc2::runtime::AnyObject;
Expand Down

0 comments on commit decf3b3

Please sign in to comment.