- Add optional modifier-only Super binding whose tap is cancelled by other input, focus changes, grabs, locks, and session teardown. - Ship the native ferese-rime user service, Rime defaults, installer inventory, package dependencies, session environment, and docs. - Vendor Smithay 8a27023 with the SHM texture-cache format fix and add GPU regressions for format changes and resize snapshots. - Release reused CRTC owners before staging output modes, then restore changed outputs before former owners when a transaction fails. - Wait for resize-capture GPU completion, expand filtered surface damage, and preserve IME popup origin under fractional scaling. - Mark weekends like holidays in both calendar layouts.
642 lines
24 KiB
Rust
642 lines
24 KiB
Rust
use std::cell::RefCell;
|
|
|
|
use smithay::{
|
|
desktop::{
|
|
PopupKeyboardGrab, PopupKind, PopupPointerGrab, PopupUngrabStrategy, Space, Window,
|
|
WindowSurfaceType, find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output,
|
|
space::SpaceElement,
|
|
},
|
|
input::{Seat, pointer::Focus},
|
|
output::Output,
|
|
reexports::{
|
|
wayland_protocols::xdg::{decoration as xdg_decoration, shell::server::xdg_toplevel},
|
|
wayland_server::{
|
|
Resource,
|
|
protocol::{wl_output, wl_seat, wl_surface::WlSurface},
|
|
},
|
|
},
|
|
utils::{Logical, Point, Serial},
|
|
wayland::{
|
|
compositor::{self, with_states},
|
|
seat::WaylandFocus,
|
|
shell::xdg::{
|
|
Configure, PopupSurface, PositionerState, ToplevelCachedState, ToplevelSurface, XdgShellHandler,
|
|
XdgShellState,
|
|
},
|
|
},
|
|
};
|
|
use tracing::{trace, warn};
|
|
|
|
use crate::{
|
|
focus::KeyboardFocusTarget,
|
|
shell::{TouchMoveSurfaceGrab, TouchResizeSurfaceGrab},
|
|
state::{AnvilState, Backend},
|
|
};
|
|
|
|
use super::{
|
|
FullscreenSurface, PointerMoveSurfaceGrab, PointerResizeSurfaceGrab, ResizeData, ResizeEdge, ResizeState,
|
|
SurfaceData, WindowElement, fullscreen_output_geometry, place_new_window,
|
|
};
|
|
|
|
impl<BackendData: Backend> XdgShellHandler for AnvilState<BackendData> {
|
|
fn xdg_shell_state(&mut self) -> &mut XdgShellState {
|
|
&mut self.xdg_shell_state
|
|
}
|
|
|
|
fn new_toplevel(&mut self, surface: ToplevelSurface) {
|
|
// Do not send a configure here, the initial configure
|
|
// of a xdg_surface has to be sent during the commit if
|
|
// the surface is not already configured
|
|
let window = WindowElement(Window::new_wayland_window(surface.clone()));
|
|
place_new_window(&mut self.space, self.pointer.current_location(), &window, true);
|
|
|
|
compositor::add_post_commit_hook(surface.wl_surface(), |state: &mut Self, _, surface| {
|
|
handle_toplevel_commit(&mut state.space, surface);
|
|
});
|
|
}
|
|
|
|
fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) {
|
|
// Do not send a configure here, the initial configure
|
|
// of a xdg_surface has to be sent during the commit if
|
|
// the surface is not already configured
|
|
|
|
self.unconstrain_popup(&surface);
|
|
|
|
if let Err(err) = self.popups.track_popup(PopupKind::from(surface)) {
|
|
warn!("Failed to track popup: {}", err);
|
|
}
|
|
}
|
|
|
|
fn reposition_request(&mut self, surface: PopupSurface, positioner: PositionerState, token: u32) {
|
|
surface.with_pending_state(|state| {
|
|
let geometry = positioner.get_geometry();
|
|
state.geometry = geometry;
|
|
state.positioner = positioner;
|
|
});
|
|
self.unconstrain_popup(&surface);
|
|
surface.send_repositioned(token);
|
|
}
|
|
|
|
fn move_request(&mut self, surface: ToplevelSurface, seat: wl_seat::WlSeat, serial: Serial) {
|
|
let seat: Seat<AnvilState<BackendData>> = Seat::from_resource(&seat).unwrap();
|
|
self.move_request_xdg(&surface, &seat, serial)
|
|
}
|
|
|
|
fn resize_request(
|
|
&mut self,
|
|
surface: ToplevelSurface,
|
|
seat: wl_seat::WlSeat,
|
|
serial: Serial,
|
|
edges: xdg_toplevel::ResizeEdge,
|
|
) {
|
|
let seat: Seat<AnvilState<BackendData>> = Seat::from_resource(&seat).unwrap();
|
|
|
|
if let Some(touch) = seat.get_touch() {
|
|
if touch.has_grab(serial) {
|
|
let start_data = touch.grab_start_data().unwrap();
|
|
tracing::info!(?start_data);
|
|
|
|
// If the client disconnects after requesting a move
|
|
// we can just ignore the request
|
|
let Some(window) = self.window_for_surface(surface.wl_surface()) else {
|
|
tracing::info!("no window");
|
|
return;
|
|
};
|
|
|
|
// If the focus was for a different surface, ignore the request.
|
|
if start_data.focus.is_none()
|
|
|| !start_data
|
|
.focus
|
|
.as_ref()
|
|
.unwrap()
|
|
.0
|
|
.same_client_as(&surface.wl_surface().id())
|
|
{
|
|
tracing::info!("different surface");
|
|
return;
|
|
}
|
|
let geometry = window.geometry();
|
|
let loc = self.space.element_location(&window).unwrap();
|
|
let (initial_window_location, initial_window_size) = (loc, geometry.size);
|
|
|
|
with_states(surface.wl_surface(), move |states| {
|
|
states
|
|
.data_map
|
|
.get::<RefCell<SurfaceData>>()
|
|
.unwrap()
|
|
.borrow_mut()
|
|
.resize_state = ResizeState::Resizing(ResizeData {
|
|
edges: edges.into(),
|
|
initial_window_location,
|
|
initial_window_size,
|
|
});
|
|
});
|
|
|
|
let grab = TouchResizeSurfaceGrab {
|
|
start_data,
|
|
window,
|
|
edges: edges.into(),
|
|
initial_window_location,
|
|
initial_window_size,
|
|
last_window_size: initial_window_size,
|
|
};
|
|
|
|
touch.set_grab(self, grab, serial);
|
|
return;
|
|
}
|
|
}
|
|
|
|
let pointer = seat.get_pointer().unwrap();
|
|
|
|
// Check that this surface has a click grab.
|
|
if !pointer.has_grab(serial) {
|
|
return;
|
|
}
|
|
|
|
let start_data = pointer.grab_start_data().unwrap();
|
|
|
|
let window = self.window_for_surface(surface.wl_surface()).unwrap();
|
|
|
|
// If the focus was for a different surface, ignore the request.
|
|
if start_data.focus.is_none()
|
|
|| !start_data
|
|
.focus
|
|
.as_ref()
|
|
.unwrap()
|
|
.0
|
|
.same_client_as(&surface.wl_surface().id())
|
|
{
|
|
return;
|
|
}
|
|
|
|
let geometry = window.geometry();
|
|
let loc = self.space.element_location(&window).unwrap();
|
|
let (initial_window_location, initial_window_size) = (loc, geometry.size);
|
|
|
|
with_states(surface.wl_surface(), move |states| {
|
|
states
|
|
.data_map
|
|
.get::<RefCell<SurfaceData>>()
|
|
.unwrap()
|
|
.borrow_mut()
|
|
.resize_state = ResizeState::Resizing(ResizeData {
|
|
edges: edges.into(),
|
|
initial_window_location,
|
|
initial_window_size,
|
|
});
|
|
});
|
|
|
|
let grab = PointerResizeSurfaceGrab {
|
|
start_data,
|
|
window,
|
|
edges: edges.into(),
|
|
initial_window_location,
|
|
initial_window_size,
|
|
last_window_size: initial_window_size,
|
|
};
|
|
|
|
pointer.set_grab(self, grab, serial, Focus::Clear);
|
|
}
|
|
|
|
fn ack_configure(&mut self, surface: WlSurface, configure: Configure) {
|
|
if let Configure::Toplevel(configure) = configure {
|
|
if let Some(serial) = with_states(&surface, |states| {
|
|
if let Some(data) = states.data_map.get::<RefCell<SurfaceData>>() {
|
|
if let ResizeState::WaitingForFinalAck(_, serial) = data.borrow().resize_state {
|
|
return Some(serial);
|
|
}
|
|
}
|
|
|
|
None
|
|
}) {
|
|
// When the resize grab is released the surface
|
|
// resize state will be set to WaitingForFinalAck
|
|
// and the client will receive a configure request
|
|
// without the resize state to inform the client
|
|
// resizing has finished. Here we will wait for
|
|
// the client to acknowledge the end of the
|
|
// resizing. To check if the surface was resizing
|
|
// before sending the configure we need to use
|
|
// the current state as the received acknowledge
|
|
// will no longer have the resize state set
|
|
let is_resizing = with_states(&surface, |states| {
|
|
states
|
|
.cached_state
|
|
.get::<ToplevelCachedState>()
|
|
.current()
|
|
.last_acked
|
|
.as_ref()
|
|
.is_some_and(|c| c.state.states.contains(xdg_toplevel::State::Resizing))
|
|
});
|
|
|
|
if configure.serial >= serial && is_resizing {
|
|
with_states(&surface, |states| {
|
|
let mut data = states
|
|
.data_map
|
|
.get::<RefCell<SurfaceData>>()
|
|
.unwrap()
|
|
.borrow_mut();
|
|
if let ResizeState::WaitingForFinalAck(resize_data, _) = data.resize_state {
|
|
data.resize_state = ResizeState::WaitingForCommit(resize_data);
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
let window = self
|
|
.space
|
|
.elements()
|
|
.find(|element| element.wl_surface().as_deref() == Some(&surface));
|
|
if let Some(window) = window {
|
|
use xdg_decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode;
|
|
let is_ssd = configure
|
|
.state
|
|
.decoration_mode
|
|
.map(|mode| mode == Mode::ServerSide)
|
|
.unwrap_or(false);
|
|
window.set_ssd(is_ssd);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn fullscreen_request(&mut self, surface: ToplevelSurface, mut wl_output: Option<wl_output::WlOutput>) {
|
|
// NOTE: This is only one part of the solution. We can set the
|
|
// location and configure size here, but the surface should be rendered fullscreen
|
|
// independently from its buffer size
|
|
let wl_surface = surface.wl_surface();
|
|
|
|
let output_geometry = fullscreen_output_geometry(wl_surface, wl_output.as_ref(), &mut self.space);
|
|
|
|
if let Some(geometry) = output_geometry {
|
|
let output = wl_output
|
|
.as_ref()
|
|
.and_then(Output::from_resource)
|
|
.unwrap_or_else(|| self.space.outputs().next().unwrap().clone());
|
|
let client = match self.display_handle.get_client(wl_surface.id()) {
|
|
Ok(client) => client,
|
|
Err(_) => return,
|
|
};
|
|
for output in output.client_outputs(&client) {
|
|
wl_output = Some(output);
|
|
}
|
|
let window = self
|
|
.space
|
|
.elements()
|
|
.find(|window| window.wl_surface().map(|s| &*s == wl_surface).unwrap_or(false))
|
|
.unwrap();
|
|
|
|
surface.with_pending_state(|state| {
|
|
state.states.set(xdg_toplevel::State::Fullscreen);
|
|
state.size = Some(geometry.size);
|
|
state.fullscreen_output = wl_output;
|
|
});
|
|
output.user_data().insert_if_missing(FullscreenSurface::default);
|
|
output
|
|
.user_data()
|
|
.get::<FullscreenSurface>()
|
|
.unwrap()
|
|
.set(window.clone());
|
|
trace!("Fullscreening: {:?}", window);
|
|
}
|
|
|
|
// The protocol demands us to always reply with a configure,
|
|
// regardless of we fulfilled the request or not
|
|
if surface.is_initial_configure_sent() {
|
|
surface.send_configure();
|
|
} else {
|
|
// Will be sent during initial configure
|
|
}
|
|
}
|
|
|
|
fn unfullscreen_request(&mut self, surface: ToplevelSurface) {
|
|
let ret = surface.with_pending_state(|state| {
|
|
state.states.unset(xdg_toplevel::State::Fullscreen);
|
|
state.size = None;
|
|
state.fullscreen_output.take()
|
|
});
|
|
if let Some(output) = ret {
|
|
let output = Output::from_resource(&output).unwrap();
|
|
if let Some(fullscreen) = output.user_data().get::<FullscreenSurface>() {
|
|
trace!("Unfullscreening: {:?}", fullscreen.get());
|
|
fullscreen.clear();
|
|
self.backend_data.reset_buffers(&output);
|
|
}
|
|
}
|
|
|
|
// The protocol demands us to always reply with a configure,
|
|
// regardless of we fulfilled the request or not
|
|
if surface.is_initial_configure_sent() {
|
|
surface.send_configure();
|
|
} else {
|
|
// Will be sent during initial configure
|
|
}
|
|
}
|
|
|
|
fn maximize_request(&mut self, surface: ToplevelSurface) {
|
|
// NOTE: This should use layer-shell when it is implemented to
|
|
// get the correct maximum size
|
|
let window = self.window_for_surface(surface.wl_surface()).unwrap();
|
|
let outputs_for_window = self.space.outputs_for_element(&window);
|
|
let output = outputs_for_window
|
|
.first()
|
|
// The window hasn't been mapped yet, use the primary output instead
|
|
.or_else(|| self.space.outputs().next())
|
|
// Assumes that at least one output exists
|
|
.expect("No outputs found");
|
|
let geometry = self.space.output_geometry(output).unwrap();
|
|
|
|
surface.with_pending_state(|state| {
|
|
state.states.set(xdg_toplevel::State::Maximized);
|
|
state.size = Some(geometry.size);
|
|
});
|
|
self.space.map_element(window, geometry.loc, true);
|
|
|
|
// The protocol demands us to always reply with a configure,
|
|
// regardless of we fulfilled the request or not
|
|
if surface.is_initial_configure_sent() {
|
|
surface.send_configure();
|
|
} else {
|
|
// Will be sent during initial configure
|
|
}
|
|
}
|
|
|
|
fn unmaximize_request(&mut self, surface: ToplevelSurface) {
|
|
surface.with_pending_state(|state| {
|
|
state.states.unset(xdg_toplevel::State::Maximized);
|
|
state.size = None;
|
|
});
|
|
|
|
// The protocol demands us to always reply with a configure,
|
|
// regardless of we fulfilled the request or not
|
|
if surface.is_initial_configure_sent() {
|
|
surface.send_configure();
|
|
} else {
|
|
// Will be sent during initial configure
|
|
}
|
|
}
|
|
|
|
fn grab(&mut self, surface: PopupSurface, seat: wl_seat::WlSeat, serial: Serial) {
|
|
let seat: Seat<AnvilState<BackendData>> = Seat::from_resource(&seat).unwrap();
|
|
let kind = PopupKind::Xdg(surface);
|
|
if let Some(root) = find_popup_root_surface(&kind).ok().and_then(|root| {
|
|
self.space
|
|
.elements()
|
|
.find(|w| w.wl_surface().map(|s| *s == root).unwrap_or(false))
|
|
.cloned()
|
|
.map(KeyboardFocusTarget::from)
|
|
.or_else(|| {
|
|
self.space
|
|
.outputs()
|
|
.find_map(|o| {
|
|
let map = layer_map_for_output(o);
|
|
map.layer_for_surface(&root, WindowSurfaceType::TOPLEVEL).cloned()
|
|
})
|
|
.map(KeyboardFocusTarget::LayerSurface)
|
|
})
|
|
}) {
|
|
let ret = self.popups.grab_popup(root, kind, &seat, serial);
|
|
|
|
if let Ok(mut grab) = ret {
|
|
if let Some(keyboard) = seat.get_keyboard() {
|
|
if keyboard.is_grabbed()
|
|
&& !(keyboard.has_grab(serial)
|
|
|| keyboard.has_grab(grab.previous_serial().unwrap_or(serial)))
|
|
{
|
|
grab.ungrab(PopupUngrabStrategy::All);
|
|
return;
|
|
}
|
|
keyboard.set_focus(self, grab.current_grab(), serial);
|
|
keyboard.set_grab(self, PopupKeyboardGrab::new(&grab), serial);
|
|
}
|
|
if let Some(pointer) = seat.get_pointer() {
|
|
if pointer.is_grabbed()
|
|
&& !(pointer.has_grab(serial)
|
|
|| pointer.has_grab(grab.previous_serial().unwrap_or_else(|| grab.serial())))
|
|
{
|
|
grab.ungrab(PopupUngrabStrategy::All);
|
|
return;
|
|
}
|
|
pointer.set_grab(self, PopupPointerGrab::new(&grab), serial, Focus::Keep);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<BackendData: Backend> AnvilState<BackendData> {
|
|
pub fn move_request_xdg(&mut self, surface: &ToplevelSurface, seat: &Seat<Self>, serial: Serial) {
|
|
if let Some(touch) = seat.get_touch() {
|
|
if touch.has_grab(serial) {
|
|
let start_data = touch.grab_start_data().unwrap();
|
|
|
|
// If the client disconnects after requesting a move
|
|
// we can just ignore the request
|
|
let Some(window) = self.window_for_surface(surface.wl_surface()) else {
|
|
return;
|
|
};
|
|
|
|
// If the focus was for a different surface, ignore the request.
|
|
if start_data.focus.is_none()
|
|
|| !start_data
|
|
.focus
|
|
.as_ref()
|
|
.unwrap()
|
|
.0
|
|
.same_client_as(&surface.wl_surface().id())
|
|
{
|
|
return;
|
|
}
|
|
|
|
let mut initial_window_location = self.space.element_location(&window).unwrap();
|
|
|
|
// If surface is maximized then unmaximize it
|
|
let changed = surface.with_pending_state(|state| {
|
|
if state.states.unset(xdg_toplevel::State::Maximized) {
|
|
state.size = None;
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
});
|
|
if changed {
|
|
surface.send_configure();
|
|
|
|
// NOTE: In real compositor mouse location should be mapped to a new window size
|
|
// For example, you could:
|
|
// 1) transform mouse pointer position from compositor space to window space (location relative)
|
|
// 2) divide the x coordinate by width of the window to get the percentage
|
|
// - 0.0 would be on the far left of the window
|
|
// - 0.5 would be in middle of the window
|
|
// - 1.0 would be on the far right of the window
|
|
// 3) multiply the percentage by new window width
|
|
// 4) by doing that, drag will look a lot more natural
|
|
//
|
|
// but for anvil needs setting location to pointer location is fine
|
|
initial_window_location = start_data.location.to_i32_round();
|
|
}
|
|
|
|
let grab = TouchMoveSurfaceGrab {
|
|
start_data,
|
|
window,
|
|
initial_window_location,
|
|
};
|
|
|
|
touch.set_grab(self, grab, serial);
|
|
return;
|
|
}
|
|
}
|
|
|
|
let pointer = seat.get_pointer().unwrap();
|
|
|
|
// Check that this surface has a click grab.
|
|
if !pointer.has_grab(serial) {
|
|
return;
|
|
}
|
|
|
|
let start_data = pointer.grab_start_data().unwrap();
|
|
|
|
// If the client disconnects after requesting a move
|
|
// we can just ignore the request
|
|
let Some(window) = self.window_for_surface(surface.wl_surface()) else {
|
|
return;
|
|
};
|
|
|
|
// If the focus was for a different surface, ignore the request.
|
|
if start_data.focus.is_none()
|
|
|| !start_data
|
|
.focus
|
|
.as_ref()
|
|
.unwrap()
|
|
.0
|
|
.same_client_as(&surface.wl_surface().id())
|
|
{
|
|
return;
|
|
}
|
|
|
|
let mut initial_window_location = self.space.element_location(&window).unwrap();
|
|
|
|
// If surface is maximized then unmaximize it
|
|
let changed = surface.with_pending_state(|state| {
|
|
if state.states.unset(xdg_toplevel::State::Maximized) {
|
|
state.size = None;
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
});
|
|
if changed {
|
|
surface.send_configure();
|
|
|
|
// NOTE: In real compositor mouse location should be mapped to a new window size
|
|
// For example, you could:
|
|
// 1) transform mouse pointer position from compositor space to window space (location relative)
|
|
// 2) divide the x coordinate by width of the window to get the percentage
|
|
// - 0.0 would be on the far left of the window
|
|
// - 0.5 would be in middle of the window
|
|
// - 1.0 would be on the far right of the window
|
|
// 3) multiply the percentage by new window width
|
|
// 4) by doing that, drag will look a lot more natural
|
|
//
|
|
// but for anvil needs setting location to pointer location is fine
|
|
let pos = pointer.current_location();
|
|
initial_window_location = (pos.x as i32, pos.y as i32).into();
|
|
}
|
|
|
|
let grab = PointerMoveSurfaceGrab {
|
|
start_data,
|
|
window,
|
|
initial_window_location,
|
|
};
|
|
|
|
pointer.set_grab(self, grab, serial, Focus::Clear);
|
|
}
|
|
|
|
fn unconstrain_popup(&self, popup: &PopupSurface) {
|
|
let Ok(root) = find_popup_root_surface(&PopupKind::Xdg(popup.clone())) else {
|
|
return;
|
|
};
|
|
let Some(window) = self.window_for_surface(&root) else {
|
|
return;
|
|
};
|
|
|
|
let mut outputs_for_window = self.space.outputs_for_element(&window);
|
|
if outputs_for_window.is_empty() {
|
|
return;
|
|
}
|
|
|
|
// Get a union of all outputs' geometries.
|
|
let mut outputs_geo = self
|
|
.space
|
|
.output_geometry(&outputs_for_window.pop().unwrap())
|
|
.unwrap();
|
|
for output in outputs_for_window {
|
|
outputs_geo = outputs_geo.merge(self.space.output_geometry(&output).unwrap());
|
|
}
|
|
|
|
let window_geo = self.space.element_geometry(&window).unwrap();
|
|
|
|
// The target geometry for the positioner should be relative to its parent's geometry, so
|
|
// we will compute that here.
|
|
let mut target = outputs_geo;
|
|
target.loc -= get_popup_toplevel_coords(&PopupKind::Xdg(popup.clone()));
|
|
target.loc -= window_geo.loc;
|
|
|
|
popup.with_pending_state(|state| {
|
|
state.geometry = state.positioner.get_unconstrained_geometry(target);
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Should be called on `WlSurface::commit` of xdg toplevel
|
|
fn handle_toplevel_commit(space: &mut Space<WindowElement>, surface: &WlSurface) -> Option<()> {
|
|
let window = space
|
|
.elements()
|
|
.find(|w| w.wl_surface().as_deref() == Some(surface))
|
|
.cloned()?;
|
|
|
|
let mut window_loc = space.element_location(&window)?;
|
|
let geometry = window.geometry();
|
|
|
|
let new_loc: Point<Option<i32>, Logical> = with_states(window.wl_surface().as_deref()?, |states| {
|
|
let data = states.data_map.get::<RefCell<SurfaceData>>()?.borrow_mut();
|
|
|
|
if let ResizeState::Resizing(resize_data) = data.resize_state {
|
|
let edges = resize_data.edges;
|
|
let loc = resize_data.initial_window_location;
|
|
let size = resize_data.initial_window_size;
|
|
|
|
// If the window is being resized by top or left, its location must be adjusted
|
|
// accordingly.
|
|
edges.intersects(ResizeEdge::TOP_LEFT).then(|| {
|
|
let new_x = edges
|
|
.intersects(ResizeEdge::LEFT)
|
|
.then_some(loc.x + (size.w - geometry.size.w));
|
|
|
|
let new_y = edges
|
|
.intersects(ResizeEdge::TOP)
|
|
.then_some(loc.y + (size.h - geometry.size.h));
|
|
|
|
(new_x, new_y).into()
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
})?;
|
|
|
|
if let Some(new_x) = new_loc.x {
|
|
window_loc.x = new_x;
|
|
}
|
|
if let Some(new_y) = new_loc.y {
|
|
window_loc.y = new_y;
|
|
}
|
|
|
|
if new_loc.x.is_some() || new_loc.y.is_some() {
|
|
// If TOP or LEFT side of the window got resized, we have to move it
|
|
space.map_element(window, window_loc, false);
|
|
}
|
|
|
|
Some(())
|
|
}
|