tree: add window-management mode
This commit is contained in:
parent
1a73bbd075
commit
70a8f47288
20 changed files with 644 additions and 9 deletions
|
|
@ -1,5 +1,9 @@
|
|||
# Unreleased
|
||||
|
||||
- Needs jay-config release.
|
||||
- Needs jay-toml-config release.
|
||||
- Needs jay-compositor release.
|
||||
|
||||
# 1.3.0
|
||||
|
||||
- Needs jay-algorithms release.
|
||||
|
|
|
|||
|
|
@ -955,6 +955,10 @@ impl Client {
|
|||
self.send(&ClientMessage::SetFocusFollowsMouseMode { seat, mode })
|
||||
}
|
||||
|
||||
pub fn set_window_management_enabled(&self, seat: Seat, enabled: bool) {
|
||||
self.send(&ClientMessage::SetWindowManagementEnabled { seat, enabled })
|
||||
}
|
||||
|
||||
pub fn set_input_device_connector(&self, input_device: InputDevice, connector: Connector) {
|
||||
self.send(&ClientMessage::SetInputDeviceConnector {
|
||||
input_device,
|
||||
|
|
|
|||
|
|
@ -483,6 +483,10 @@ pub enum ClientMessage<'a> {
|
|||
RemoveInputMapping {
|
||||
input_device: InputDevice,
|
||||
},
|
||||
SetWindowManagementEnabled {
|
||||
seat: Seat,
|
||||
enabled: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
|||
|
|
@ -414,6 +414,37 @@ impl Seat {
|
|||
pub fn set_focus_follows_mouse_mode(self, mode: FocusFollowsMouseMode) {
|
||||
get!().set_focus_follows_mouse_mode(self, mode);
|
||||
}
|
||||
|
||||
/// Enables or disable window management mode.
|
||||
///
|
||||
/// In window management mode, floating windows can be moved by pressing the left
|
||||
/// mouse button and all windows can be resize by pressing the right mouse button.
|
||||
pub fn set_window_management_enabled(self, enabled: bool) {
|
||||
get!().set_window_management_enabled(self, enabled);
|
||||
}
|
||||
|
||||
/// Sets a key that enables window management mode while pressed.
|
||||
///
|
||||
/// This is a shorthand for
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// self.bind(mod_sym, move || {
|
||||
/// self.set_window_management_enabled(true);
|
||||
/// self.forward();
|
||||
/// self.latch(move || {
|
||||
/// self.set_window_management_enabled(false);
|
||||
/// });
|
||||
/// });
|
||||
/// ```
|
||||
pub fn set_window_management_key<T: Into<ModifiedKeySym>>(self, mod_sym: T) {
|
||||
self.bind(mod_sym, move || {
|
||||
self.set_window_management_enabled(true);
|
||||
self.forward();
|
||||
self.latch(move || {
|
||||
self.set_window_management_enabled(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// A focus-follows-mouse mode.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
# Unreleased
|
||||
|
||||
- Add window management mode.
|
||||
|
||||
# 1.3.0 (2024-05-25)
|
||||
|
||||
- Add remaining layer-shell features.
|
||||
|
|
|
|||
|
|
@ -338,6 +338,16 @@ impl ConfigProxyHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_set_window_management_enabled(
|
||||
&self,
|
||||
seat: Seat,
|
||||
enabled: bool,
|
||||
) -> Result<(), CphError> {
|
||||
let seat = self.get_seat(seat)?;
|
||||
seat.set_window_management_enabled(enabled);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_set_input_device_connector(
|
||||
&self,
|
||||
input_device: InputDevice,
|
||||
|
|
@ -1816,6 +1826,9 @@ impl ConfigProxyHandler {
|
|||
ClientMessage::RemoveInputMapping { input_device } => self
|
||||
.handle_remove_input_mapping(input_device)
|
||||
.wrn("remove_input_mapping")?,
|
||||
ClientMessage::SetWindowManagementEnabled { seat, enabled } => self
|
||||
.handle_set_window_management_enabled(seat, enabled)
|
||||
.wrn("set_window_management_enabled")?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -955,6 +955,11 @@ impl WlSeatGlobal {
|
|||
pub fn set_focus_follows_mouse(&self, focus_follows_mouse: bool) {
|
||||
self.focus_follows_mouse.set(focus_follows_mouse);
|
||||
}
|
||||
|
||||
pub fn set_window_management_enabled(self: &Rc<Self>, enabled: bool) {
|
||||
self.pointer_owner
|
||||
.set_window_management_enabled(self, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
impl CursorUserOwner for WlSeatGlobal {
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ impl NodeSeatState {
|
|||
seat.gesture_owner.revert_to_default(&seat);
|
||||
}
|
||||
while let Some((_, seat)) = self.pointer_grabs.pop() {
|
||||
seat.pointer_owner.revert_to_default(&seat);
|
||||
seat.pointer_owner.grab_node_removed(&seat);
|
||||
}
|
||||
let node_id = node.node_id();
|
||||
while let Some((_, seat)) = self.dnd_targets.pop() {
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ use {
|
|||
ipc::wl_data_source::WlDataSource,
|
||||
wl_seat::{
|
||||
wl_pointer::PendingScroll, Dnd, DroppedDnd, WlSeatError, WlSeatGlobal, BTN_LEFT,
|
||||
BTN_RIGHT, CHANGE_CURSOR_MOVED,
|
||||
BTN_RIGHT, CHANGE_CURSOR_MOVED, CHANGE_TREE,
|
||||
},
|
||||
wl_surface::WlSurface,
|
||||
xdg_toplevel_drag_v1::XdgToplevelDragV1,
|
||||
},
|
||||
state::DeviceHandlerData,
|
||||
tree::{FindTreeUsecase, FoundNode, Node, ToplevelNode, WorkspaceNode},
|
||||
tree::{ContainingNode, FindTreeUsecase, FoundNode, Node, ToplevelNode, WorkspaceNode},
|
||||
utils::{clonecell::CloneCell, smallmap::SmallMap},
|
||||
},
|
||||
std::{
|
||||
|
|
@ -136,6 +136,10 @@ impl PointerOwnerHolder {
|
|||
self.owner.get().revert_to_default(seat)
|
||||
}
|
||||
|
||||
pub fn grab_node_removed(&self, seat: &Rc<WlSeatGlobal>) {
|
||||
self.owner.get().grab_node_removed(seat);
|
||||
}
|
||||
|
||||
pub fn dnd_target_removed(&self, seat: &Rc<WlSeatGlobal>) {
|
||||
self.owner.get().dnd_target_removed(seat);
|
||||
}
|
||||
|
|
@ -187,6 +191,15 @@ impl PointerOwnerHolder {
|
|||
});
|
||||
self.select_element(seat, usecase)
|
||||
}
|
||||
|
||||
pub fn set_window_management_enabled(&self, seat: &Rc<WlSeatGlobal>, enabled: bool) {
|
||||
let owner = self.owner.get();
|
||||
if enabled {
|
||||
owner.enable_window_management(seat);
|
||||
} else {
|
||||
owner.disable_window_management(seat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait PointerOwner {
|
||||
|
|
@ -213,6 +226,9 @@ trait PointerOwner {
|
|||
seat.dropped_dnd.borrow_mut().take();
|
||||
}
|
||||
fn revert_to_default(&self, seat: &Rc<WlSeatGlobal>);
|
||||
fn grab_node_removed(&self, seat: &Rc<WlSeatGlobal>) {
|
||||
self.revert_to_default(seat);
|
||||
}
|
||||
fn dnd_target_removed(&self, seat: &Rc<WlSeatGlobal>) {
|
||||
self.cancel_dnd(seat);
|
||||
}
|
||||
|
|
@ -225,6 +241,12 @@ trait PointerOwner {
|
|||
fn remove_dnd_icon(&self) {
|
||||
// nothing
|
||||
}
|
||||
fn enable_window_management(&self, seat: &Rc<WlSeatGlobal>) {
|
||||
let _ = seat;
|
||||
}
|
||||
fn disable_window_management(&self, seat: &Rc<WlSeatGlobal>) {
|
||||
let _ = seat;
|
||||
}
|
||||
}
|
||||
|
||||
struct SimplePointerOwner<T> {
|
||||
|
|
@ -262,6 +284,9 @@ struct SelectWorkspaceUsecase<S: ?Sized> {
|
|||
selector: S,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct WindowManagementUsecase;
|
||||
|
||||
impl<T: SimplePointerOwnerUsecase> PointerOwner for SimplePointerOwner<T> {
|
||||
fn button(&self, seat: &Rc<WlSeatGlobal>, time_usec: u64, button: u32, state: KeyState) {
|
||||
if state != KeyState::Pressed {
|
||||
|
|
@ -363,6 +388,21 @@ impl<T: SimplePointerOwnerUsecase> PointerOwner for SimplePointerOwner<T> {
|
|||
seat.state.damage();
|
||||
}
|
||||
}
|
||||
|
||||
fn enable_window_management(&self, seat: &Rc<WlSeatGlobal>) {
|
||||
if !T::IS_DEFAULT {
|
||||
return;
|
||||
}
|
||||
seat.pointer_owner.owner.set(Rc::new(SimplePointerOwner {
|
||||
usecase: WindowManagementUsecase,
|
||||
}));
|
||||
seat.changes.or_assign(CHANGE_TREE);
|
||||
seat.apply_changes();
|
||||
}
|
||||
|
||||
fn disable_window_management(&self, seat: &Rc<WlSeatGlobal>) {
|
||||
self.usecase.disable_window_management(seat);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SimplePointerOwnerUsecase> PointerOwner for SimpleGrabPointerOwner<T> {
|
||||
|
|
@ -572,6 +612,10 @@ trait SimplePointerOwnerUsecase: Sized + Clone + 'static {
|
|||
let _ = seat;
|
||||
let _ = node;
|
||||
}
|
||||
|
||||
fn disable_window_management(&self, seat: &Rc<WlSeatGlobal>) {
|
||||
let _ = seat;
|
||||
}
|
||||
}
|
||||
|
||||
impl SimplePointerOwnerUsecase for DefaultPointerUsecase {
|
||||
|
|
@ -795,3 +839,218 @@ impl<S: ?Sized> Drop for SelectWorkspaceUsecase<S> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SimplePointerOwnerUsecase for WindowManagementUsecase {
|
||||
const FIND_TREE_USECASE: FindTreeUsecase = FindTreeUsecase::SelectToplevel;
|
||||
const IS_DEFAULT: bool = false;
|
||||
|
||||
fn default_button(
|
||||
&self,
|
||||
_spo: &SimplePointerOwner<Self>,
|
||||
seat: &Rc<WlSeatGlobal>,
|
||||
button: u32,
|
||||
pn: &Rc<dyn Node>,
|
||||
) -> bool {
|
||||
let Some(tl) = pn.clone().node_into_toplevel() else {
|
||||
return false;
|
||||
};
|
||||
let pos = tl.node_absolute_position();
|
||||
let (x, y) = seat.pointer_cursor.position();
|
||||
let (x, y) = (x.round_down(), y.round_down());
|
||||
let (mut dx, mut dy) = pos.translate(x, y);
|
||||
let owner: Rc<dyn PointerOwner> = if button == BTN_LEFT {
|
||||
seat.pointer_cursor.set_known(KnownCursor::Move);
|
||||
Rc::new(ToplevelGrabPointerOwner {
|
||||
tl,
|
||||
usecase: MoveToplevelGrabPointerOwner { dx, dy },
|
||||
})
|
||||
} else if button == BTN_RIGHT {
|
||||
let mut top = false;
|
||||
let mut right = false;
|
||||
let mut bottom = false;
|
||||
let mut left = false;
|
||||
if dx <= pos.width() / 2 {
|
||||
left = true;
|
||||
} else {
|
||||
right = true;
|
||||
dx = pos.width() - dx;
|
||||
}
|
||||
if dy <= pos.height() / 2 {
|
||||
top = true;
|
||||
} else {
|
||||
bottom = true;
|
||||
dy = pos.height() - dy;
|
||||
}
|
||||
let cursor = match (top, right, bottom, left) {
|
||||
(true, true, false, false) => KnownCursor::NeResize,
|
||||
(false, true, true, false) => KnownCursor::SeResize,
|
||||
(false, false, true, true) => KnownCursor::SwResize,
|
||||
(true, false, false, true) => KnownCursor::NwResize,
|
||||
_ => KnownCursor::Move,
|
||||
};
|
||||
seat.pointer_cursor.set_known(cursor);
|
||||
Rc::new(ToplevelGrabPointerOwner {
|
||||
tl,
|
||||
usecase: ResizeToplevelGrabPointerOwner {
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
left,
|
||||
dx,
|
||||
dy,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
seat.pointer_owner.owner.set(owner);
|
||||
pn.node_seat_state().add_pointer_grab(seat);
|
||||
true
|
||||
}
|
||||
|
||||
fn release_grab(&self, seat: &Rc<WlSeatGlobal>) {
|
||||
seat.pointer_owner
|
||||
.owner
|
||||
.set(Rc::new(SimplePointerOwner { usecase: *self }));
|
||||
seat.changes.or_assign(CHANGE_CURSOR_MOVED);
|
||||
}
|
||||
|
||||
fn disable_window_management(&self, seat: &Rc<WlSeatGlobal>) {
|
||||
seat.pointer_owner.set_default_pointer_owner(seat);
|
||||
seat.apply_changes();
|
||||
}
|
||||
}
|
||||
|
||||
trait WindowManagementGrabUsecase {
|
||||
const BUTTON: u32;
|
||||
|
||||
fn apply_changes(
|
||||
&self,
|
||||
seat: &Rc<WlSeatGlobal>,
|
||||
parent: Rc<dyn ContainingNode>,
|
||||
tl: &Rc<dyn ToplevelNode>,
|
||||
);
|
||||
}
|
||||
|
||||
struct ToplevelGrabPointerOwner<T> {
|
||||
tl: Rc<dyn ToplevelNode>,
|
||||
usecase: T,
|
||||
}
|
||||
|
||||
impl<T> PointerOwner for ToplevelGrabPointerOwner<T>
|
||||
where
|
||||
T: WindowManagementGrabUsecase,
|
||||
{
|
||||
fn button(&self, seat: &Rc<WlSeatGlobal>, _time_usec: u64, button: u32, state: KeyState) {
|
||||
if button != T::BUTTON || state != KeyState::Released {
|
||||
return;
|
||||
}
|
||||
self.tl.node_seat_state().remove_pointer_grab(seat);
|
||||
self.grab_node_removed(seat);
|
||||
}
|
||||
|
||||
fn axis_node(&self, _seat: &Rc<WlSeatGlobal>) -> Option<Rc<dyn Node>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn apply_changes(&self, seat: &Rc<WlSeatGlobal>) {
|
||||
let Some(parent) = self.tl.tl_data().parent.get() else {
|
||||
return;
|
||||
};
|
||||
self.usecase.apply_changes(seat, parent, &self.tl);
|
||||
}
|
||||
|
||||
fn revert_to_default(&self, seat: &Rc<WlSeatGlobal>) {
|
||||
seat.pointer_owner.set_default_pointer_owner(seat);
|
||||
}
|
||||
|
||||
fn grab_node_removed(&self, seat: &Rc<WlSeatGlobal>) {
|
||||
seat.pointer_cursor.set_known(KnownCursor::Default);
|
||||
seat.pointer_owner.owner.set(Rc::new(SimplePointerOwner {
|
||||
usecase: WindowManagementUsecase,
|
||||
}));
|
||||
seat.changes.or_assign(CHANGE_CURSOR_MOVED);
|
||||
seat.apply_changes();
|
||||
}
|
||||
|
||||
fn disable_window_management(&self, seat: &Rc<WlSeatGlobal>) {
|
||||
seat.pointer_owner.set_default_pointer_owner(seat);
|
||||
seat.apply_changes();
|
||||
}
|
||||
}
|
||||
|
||||
struct MoveToplevelGrabPointerOwner {
|
||||
dx: i32,
|
||||
dy: i32,
|
||||
}
|
||||
|
||||
impl WindowManagementGrabUsecase for MoveToplevelGrabPointerOwner {
|
||||
const BUTTON: u32 = BTN_LEFT;
|
||||
|
||||
fn apply_changes(
|
||||
&self,
|
||||
seat: &Rc<WlSeatGlobal>,
|
||||
parent: Rc<dyn ContainingNode>,
|
||||
tl: &Rc<dyn ToplevelNode>,
|
||||
) {
|
||||
let (x, y) = seat.pointer_cursor.position();
|
||||
let (x, y) = (x.round_down() - self.dx, y.round_down() - self.dy);
|
||||
parent.cnode_set_child_position(tl.tl_as_node(), x, y);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ResizeToplevelGrabPointerOwner {
|
||||
top: bool,
|
||||
right: bool,
|
||||
bottom: bool,
|
||||
left: bool,
|
||||
dx: i32,
|
||||
dy: i32,
|
||||
}
|
||||
|
||||
impl WindowManagementGrabUsecase for ResizeToplevelGrabPointerOwner {
|
||||
const BUTTON: u32 = BTN_RIGHT;
|
||||
|
||||
fn apply_changes(
|
||||
&self,
|
||||
seat: &Rc<WlSeatGlobal>,
|
||||
parent: Rc<dyn ContainingNode>,
|
||||
tl: &Rc<dyn ToplevelNode>,
|
||||
) {
|
||||
let (x, y) = seat.pointer_cursor.position();
|
||||
let (x, y) = (x.round_down(), y.round_down());
|
||||
let pos = tl.node_absolute_position();
|
||||
let mut x1 = None;
|
||||
let mut x2 = None;
|
||||
let mut y1 = None;
|
||||
let mut y2 = None;
|
||||
if self.top {
|
||||
let new_v = y - self.dy;
|
||||
if new_v != pos.y1() {
|
||||
y1 = Some(new_v);
|
||||
}
|
||||
}
|
||||
if self.right {
|
||||
let new_v = x + self.dx;
|
||||
if new_v != pos.x2() {
|
||||
x2 = Some(new_v);
|
||||
}
|
||||
}
|
||||
if self.bottom {
|
||||
let new_v = y + self.dy;
|
||||
if new_v != pos.y2() {
|
||||
y2 = Some(new_v);
|
||||
}
|
||||
}
|
||||
if self.left {
|
||||
let new_v = x - self.dx;
|
||||
if new_v != pos.x1() {
|
||||
x1 = Some(new_v);
|
||||
}
|
||||
}
|
||||
if x1.is_some() || x2.is_some() || y1.is_some() || y2.is_some() {
|
||||
parent.cnode_resize_child(tl.tl_as_node(), x1, y1, x2, y2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1543,6 +1543,159 @@ impl ContainingNode for ContainerNode {
|
|||
fn cnode_workspace(self: Rc<Self>) -> Rc<WorkspaceNode> {
|
||||
self.workspace.get()
|
||||
}
|
||||
|
||||
fn cnode_set_child_position(self: Rc<Self>, child: &dyn Node, x: i32, y: i32) {
|
||||
let Some(parent) = self.toplevel_data.parent.get() else {
|
||||
return;
|
||||
};
|
||||
let th = self.state.theme.sizes.title_height.get();
|
||||
if self.mono_child.is_some() {
|
||||
parent.cnode_set_child_position(&*self, x, y - th - 1);
|
||||
} else {
|
||||
let children = self.child_nodes.borrow();
|
||||
let Some(child) = children.get(&child.node_id()) else {
|
||||
return;
|
||||
};
|
||||
let pos = child.body.get();
|
||||
let (x, y) = pos.translate(x, y);
|
||||
parent.cnode_set_child_position(&*self, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
fn cnode_resize_child(
|
||||
self: Rc<Self>,
|
||||
child: &dyn Node,
|
||||
new_x1: Option<i32>,
|
||||
new_y1: Option<i32>,
|
||||
new_x2: Option<i32>,
|
||||
new_y2: Option<i32>,
|
||||
) {
|
||||
let theme = &self.state.theme;
|
||||
let th = theme.sizes.title_height.get();
|
||||
let bw = theme.sizes.border_width.get();
|
||||
let mut left_outside = false;
|
||||
let mut right_outside = false;
|
||||
let mut top_outside = false;
|
||||
let mut bottom_outside = false;
|
||||
if self.mono_child.is_some() {
|
||||
top_outside = true;
|
||||
right_outside = true;
|
||||
bottom_outside = true;
|
||||
left_outside = true;
|
||||
} else {
|
||||
let children = self.child_nodes.borrow();
|
||||
let Some(child) = children.get(&child.node_id()) else {
|
||||
return;
|
||||
};
|
||||
let pos = child.body.get();
|
||||
let split = self.split.get();
|
||||
let mut changed_any = false;
|
||||
let (mut i1, mut i2, new_i1, new_i2, mut ci) = match split {
|
||||
ContainerSplit::Horizontal => {
|
||||
top_outside = true;
|
||||
bottom_outside = true;
|
||||
(pos.x1(), pos.x2(), new_x1, new_x2, self.content_width.get())
|
||||
}
|
||||
ContainerSplit::Vertical => {
|
||||
right_outside = true;
|
||||
left_outside = true;
|
||||
(
|
||||
pos.y1(),
|
||||
pos.y2(),
|
||||
new_y1,
|
||||
new_y2,
|
||||
self.content_height.get(),
|
||||
)
|
||||
}
|
||||
};
|
||||
if ci == 0 {
|
||||
ci = 1;
|
||||
}
|
||||
let (new_delta, between) = match split {
|
||||
ContainerSplit::Horizontal => (self.abs_x1.get(), bw),
|
||||
ContainerSplit::Vertical => (self.abs_y1.get(), bw + th + 1),
|
||||
};
|
||||
let new_i1 = new_i1.map(|v| v - new_delta);
|
||||
let new_i2 = new_i2.map(|v| v - new_delta);
|
||||
let (orig_i1, orig_i2) = (i1, i2);
|
||||
let mut sum_factors = self.sum_factors.get();
|
||||
if let Some(new_i1) = new_i1 {
|
||||
if let Some(peer) = child.prev() {
|
||||
let peer_pos = peer.body.get();
|
||||
let peer_i1 = match self.split.get() {
|
||||
ContainerSplit::Horizontal => peer_pos.x1(),
|
||||
ContainerSplit::Vertical => peer_pos.y1(),
|
||||
};
|
||||
i1 = new_i1.max(peer_i1 + between).min(i2);
|
||||
if i1 != orig_i1 {
|
||||
let peer_factor = (i1 - between - peer_i1) as f64 / ci as f64;
|
||||
sum_factors = sum_factors - peer.factor.get() + peer_factor;
|
||||
peer.factor.set(peer_factor);
|
||||
changed_any = true;
|
||||
}
|
||||
} else {
|
||||
match split {
|
||||
ContainerSplit::Horizontal => left_outside = true,
|
||||
ContainerSplit::Vertical => top_outside = true,
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(new_i2) = new_i2 {
|
||||
if let Some(peer) = child.next() {
|
||||
let peer_pos = peer.body.get();
|
||||
let peer_i2 = match self.split.get() {
|
||||
ContainerSplit::Horizontal => peer_pos.x2(),
|
||||
ContainerSplit::Vertical => peer_pos.y2(),
|
||||
};
|
||||
i2 = new_i2.min(peer_i2 - between).max(i1);
|
||||
if i2 != orig_i2 {
|
||||
let peer_factor = (peer_i2 - between - i2) as f64 / ci as f64;
|
||||
sum_factors = sum_factors - peer.factor.get() + peer_factor;
|
||||
peer.factor.set(peer_factor);
|
||||
changed_any = true;
|
||||
}
|
||||
} else {
|
||||
match split {
|
||||
ContainerSplit::Horizontal => right_outside = true,
|
||||
ContainerSplit::Vertical => bottom_outside = true,
|
||||
}
|
||||
}
|
||||
}
|
||||
if changed_any {
|
||||
let factor = (i2 - i1) as f64 / ci as f64;
|
||||
sum_factors = sum_factors - child.factor.get() + factor;
|
||||
child.factor.set(factor);
|
||||
self.sum_factors.set(sum_factors);
|
||||
self.schedule_layout();
|
||||
}
|
||||
}
|
||||
let pos = self.node_absolute_position();
|
||||
let mut x1 = None;
|
||||
let mut x2 = None;
|
||||
let mut y1 = None;
|
||||
let mut y2 = None;
|
||||
if left_outside {
|
||||
x1 = new_x1.map(|v| v.min(pos.x2()));
|
||||
}
|
||||
if right_outside {
|
||||
x2 = new_x2.map(|v| v.max(x1.unwrap_or(pos.x1())));
|
||||
}
|
||||
if top_outside {
|
||||
y1 = new_y1.map(|v| (v - th - 1).min(pos.y2() - th - 1));
|
||||
}
|
||||
if bottom_outside {
|
||||
y2 = new_y2.map(|v| v.max(y1.unwrap_or(pos.y1()) + th + 1));
|
||||
}
|
||||
if (x1.is_some() && x1 != Some(pos.x1()))
|
||||
|| (x2.is_some() && x2 != Some(pos.x2()))
|
||||
|| (y1.is_some() && y1 != Some(pos.y1()))
|
||||
|| (y2.is_some() && y2 != Some(pos.y2()))
|
||||
{
|
||||
if let Some(parent) = self.toplevel_data.parent.get() {
|
||||
parent.cnode_resize_child(&*self, x1, y1, x2, y2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToplevelNodeBase for ContainerNode {
|
||||
|
|
|
|||
|
|
@ -12,4 +12,23 @@ pub trait ContainingNode: Node {
|
|||
fn cnode_accepts_child(&self, node: &dyn Node) -> bool;
|
||||
fn cnode_child_attention_request_changed(self: Rc<Self>, child: &dyn Node, set: bool);
|
||||
fn cnode_workspace(self: Rc<Self>) -> Rc<WorkspaceNode>;
|
||||
fn cnode_set_child_position(self: Rc<Self>, child: &dyn Node, x: i32, y: i32) {
|
||||
let _ = child;
|
||||
let _ = x;
|
||||
let _ = y;
|
||||
}
|
||||
fn cnode_resize_child(
|
||||
self: Rc<Self>,
|
||||
child: &dyn Node,
|
||||
new_x1: Option<i32>,
|
||||
new_y1: Option<i32>,
|
||||
new_x2: Option<i32>,
|
||||
new_y2: Option<i32>,
|
||||
) {
|
||||
let _ = child;
|
||||
let _ = new_x1;
|
||||
let _ = new_x2;
|
||||
let _ = new_y1;
|
||||
let _ = new_y2;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -708,6 +708,55 @@ impl ContainingNode for FloatNode {
|
|||
fn cnode_workspace(self: Rc<Self>) -> Rc<WorkspaceNode> {
|
||||
self.workspace.get()
|
||||
}
|
||||
|
||||
fn cnode_set_child_position(self: Rc<Self>, _child: &dyn Node, x: i32, y: i32) {
|
||||
let theme = &self.state.theme;
|
||||
let th = theme.sizes.title_height.get();
|
||||
let bw = theme.sizes.border_width.get();
|
||||
let (x, y) = (x - bw, y - th - bw - 1);
|
||||
let pos = self.position.get();
|
||||
if pos.position() != (x, y) {
|
||||
self.position.set(pos.at_point(x, y));
|
||||
self.state.damage();
|
||||
self.schedule_layout();
|
||||
}
|
||||
}
|
||||
|
||||
fn cnode_resize_child(
|
||||
self: Rc<Self>,
|
||||
_child: &dyn Node,
|
||||
new_x1: Option<i32>,
|
||||
new_y1: Option<i32>,
|
||||
new_x2: Option<i32>,
|
||||
new_y2: Option<i32>,
|
||||
) {
|
||||
let theme = &self.state.theme;
|
||||
let th = theme.sizes.title_height.get();
|
||||
let bw = theme.sizes.border_width.get();
|
||||
let pos = self.position.get();
|
||||
let mut x1 = pos.x1();
|
||||
let mut x2 = pos.x2();
|
||||
let mut y1 = pos.y1();
|
||||
let mut y2 = pos.y2();
|
||||
if let Some(v) = new_x1 {
|
||||
x1 = (v - bw).min(x2 - bw - bw);
|
||||
}
|
||||
if let Some(v) = new_x2 {
|
||||
x2 = (v + bw).max(x1 + bw + bw);
|
||||
}
|
||||
if let Some(v) = new_y1 {
|
||||
y1 = (v - th - bw - 1).min(y2 - bw - th - bw - 1);
|
||||
}
|
||||
if let Some(v) = new_y2 {
|
||||
y2 = (v + bw).max(y1 + bw + th + bw + 1);
|
||||
}
|
||||
let new_pos = Rect::new(x1, y1, x2, y2).unwrap();
|
||||
if new_pos != pos {
|
||||
self.position.set(new_pos);
|
||||
self.state.damage();
|
||||
self.schedule_layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StackedNode for FloatNode {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ pub enum SimpleCommand {
|
|||
ToggleMono,
|
||||
ToggleSplit,
|
||||
Forward(bool),
|
||||
EnableWindowManagement(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -316,6 +317,7 @@ pub struct Config {
|
|||
pub idle: Option<Duration>,
|
||||
pub explicit_sync_enabled: Option<bool>,
|
||||
pub focus_follows_mouse: bool,
|
||||
pub window_management_key: Option<ModifiedKeySym>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
|
|||
|
|
@ -111,6 +111,8 @@ impl ActionParser<'_> {
|
|||
"none" => None,
|
||||
"forward" => Forward(true),
|
||||
"consume" => Forward(false),
|
||||
"enable-window-management" => EnableWindowManagement(true),
|
||||
"disable-window-management" => EnableWindowManagement(false),
|
||||
_ => {
|
||||
return Err(ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use {
|
|||
crate::{
|
||||
config::{
|
||||
context::Context,
|
||||
extractor::{arr, bol, opt, recover, val, Extractor, ExtractorError},
|
||||
extractor::{arr, bol, opt, recover, str, val, Extractor, ExtractorError},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::{
|
||||
action::ActionParser,
|
||||
|
|
@ -17,7 +17,10 @@ use {
|
|||
log_level::LogLevelParser,
|
||||
output::OutputsParser,
|
||||
repeat_rate::RepeatRateParser,
|
||||
shortcuts::{ComplexShortcutsParser, ShortcutsParser, ShortcutsParserError},
|
||||
shortcuts::{
|
||||
parse_modified_keysym_str, ComplexShortcutsParser, ShortcutsParser,
|
||||
ShortcutsParserError,
|
||||
},
|
||||
status::StatusParser,
|
||||
theme::ThemeParser,
|
||||
},
|
||||
|
|
@ -97,7 +100,13 @@ impl Parser for ConfigParser<'_> {
|
|||
_,
|
||||
idle_val,
|
||||
),
|
||||
(explicit_sync, repeat_rate_val, complex_shortcuts_val, focus_follows_mouse),
|
||||
(
|
||||
explicit_sync,
|
||||
repeat_rate_val,
|
||||
complex_shortcuts_val,
|
||||
focus_follows_mouse,
|
||||
window_management_key_val,
|
||||
),
|
||||
) = ext.extract((
|
||||
(
|
||||
opt(val("keymap")),
|
||||
|
|
@ -128,6 +137,7 @@ impl Parser for ConfigParser<'_> {
|
|||
opt(val("repeat-rate")),
|
||||
opt(val("complex-shortcuts")),
|
||||
recover(opt(bol("focus-follows-mouse"))),
|
||||
recover(opt(str("window-management-key"))),
|
||||
),
|
||||
))?;
|
||||
let mut keymap = None;
|
||||
|
|
@ -286,6 +296,12 @@ impl Parser for ConfigParser<'_> {
|
|||
}
|
||||
}
|
||||
}
|
||||
let mut window_management_key = None;
|
||||
if let Some(value) = window_management_key_val {
|
||||
if let Some(key) = parse_modified_keysym_str(self.0, value.span, value.value) {
|
||||
window_management_key = Some(key);
|
||||
}
|
||||
}
|
||||
Ok(Config {
|
||||
keymap,
|
||||
repeat_rate,
|
||||
|
|
@ -309,6 +325,7 @@ impl Parser for ConfigParser<'_> {
|
|||
inputs,
|
||||
idle,
|
||||
focus_follows_mouse: focus_follows_mouse.despan().unwrap_or(true),
|
||||
window_management_key,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,10 +175,18 @@ fn parse_action(cx: &Context<'_>, key: &str, value: &Spanned<Value>) -> Option<A
|
|||
}
|
||||
|
||||
fn parse_modified_keysym(cx: &Context<'_>, key: &Spanned<String>) -> Option<ModifiedKeySym> {
|
||||
match ModifiedKeysymParser.parse_string(key.span, &key.value) {
|
||||
parse_modified_keysym_str(cx, key.span, &key.value)
|
||||
}
|
||||
|
||||
pub fn parse_modified_keysym_str(
|
||||
cx: &Context<'_>,
|
||||
span: Span,
|
||||
value: &str,
|
||||
) -> Option<ModifiedKeySym> {
|
||||
match ModifiedKeysymParser.parse_string(span, value) {
|
||||
Ok(k) => Some(k),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse keysym {}: {}", key.value, cx.error(e));
|
||||
log::warn!("Could not parse keysym {}: {}", value, cx.error(e));
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,6 +89,9 @@ impl Action {
|
|||
SimpleCommand::ReloadConfigSo => B::new(reload),
|
||||
SimpleCommand::None => B::new(|| ()),
|
||||
SimpleCommand::Forward(bool) => B::new(move || s.set_forward(bool)),
|
||||
SimpleCommand::EnableWindowManagement(bool) => {
|
||||
B::new(move || s.set_window_management_enabled(bool))
|
||||
}
|
||||
},
|
||||
Action::Multi { actions } => {
|
||||
let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect();
|
||||
|
|
@ -1009,6 +1012,11 @@ fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
|
|||
true => FocusFollowsMouseMode::True,
|
||||
false => FocusFollowsMouseMode::False,
|
||||
});
|
||||
if let Some(window_management_key) = config.window_management_key {
|
||||
persistent
|
||||
.seat
|
||||
.set_window_management_key(window_management_key);
|
||||
}
|
||||
}
|
||||
|
||||
fn create_command(exec: &Exec) -> Command {
|
||||
|
|
|
|||
|
|
@ -573,6 +573,10 @@
|
|||
"focus-follows-mouse": {
|
||||
"type": "boolean",
|
||||
"description": "Configures whether moving the mouse over a window automatically moves the keyboard\nfocus to that window.\n\nThe default is `true`.\n"
|
||||
},
|
||||
"window-management-key": {
|
||||
"type": "string",
|
||||
"description": "Configures a key that will enable window management mode while pressed.\n\nIn window management mode, floating windows can be moved by pressing the left\nmouse button and all windows can be resize by pressing the right mouse button.\n\n- Example:\n\n ```toml\n window-management-key = \"Alt_L\"\n ```\n"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
|
|
@ -1110,7 +1114,9 @@
|
|||
"reload-config-to",
|
||||
"consume",
|
||||
"forward",
|
||||
"none"
|
||||
"none",
|
||||
"enable-window-management",
|
||||
"disable-window-management"
|
||||
]
|
||||
},
|
||||
"Status": {
|
||||
|
|
|
|||
|
|
@ -1095,6 +1095,21 @@ The table has the following fields:
|
|||
|
||||
The value of this field should be a boolean.
|
||||
|
||||
- `window-management-key` (optional):
|
||||
|
||||
Configures a key that will enable window management mode while pressed.
|
||||
|
||||
In window management mode, floating windows can be moved by pressing the left
|
||||
mouse button and all windows can be resize by pressing the right mouse button.
|
||||
|
||||
- Example:
|
||||
|
||||
```toml
|
||||
window-management-key = "Alt_L"
|
||||
```
|
||||
|
||||
The value of this field should be a string.
|
||||
|
||||
|
||||
<a name="types-Connector"></a>
|
||||
### `Connector`
|
||||
|
|
@ -2434,6 +2449,17 @@ The string should have one of the following values:
|
|||
As a special case, if this is the action of a shortcut, the shortcut will be
|
||||
unbound. This can be used in modes to unbind a key.
|
||||
|
||||
- `enable-window-management`:
|
||||
|
||||
Enables window management mode.
|
||||
|
||||
In window management mode, floating windows can be moved by pressing the left
|
||||
mouse button and all windows can be resize by pressing the right mouse button.
|
||||
|
||||
- `disable-window-management`:
|
||||
|
||||
Disables window management mode.
|
||||
|
||||
|
||||
|
||||
<a name="types-Status"></a>
|
||||
|
|
|
|||
|
|
@ -692,6 +692,15 @@ SimpleActionName:
|
|||
|
||||
As a special case, if this is the action of a shortcut, the shortcut will be
|
||||
unbound. This can be used in modes to unbind a key.
|
||||
- value: enable-window-management
|
||||
description: |
|
||||
Enables window management mode.
|
||||
|
||||
In window management mode, floating windows can be moved by pressing the left
|
||||
mouse button and all windows can be resize by pressing the right mouse button.
|
||||
- value: disable-window-management
|
||||
description: |
|
||||
Disables window management mode.
|
||||
|
||||
|
||||
Color:
|
||||
|
|
@ -2127,6 +2136,20 @@ Config:
|
|||
focus to that window.
|
||||
|
||||
The default is `true`.
|
||||
window-management-key:
|
||||
kind: string
|
||||
required: false
|
||||
description: |
|
||||
Configures a key that will enable window management mode while pressed.
|
||||
|
||||
In window management mode, floating windows can be moved by pressing the left
|
||||
mouse button and all windows can be resize by pressing the right mouse button.
|
||||
|
||||
- Example:
|
||||
|
||||
```toml
|
||||
window-management-key = "Alt_L"
|
||||
```
|
||||
|
||||
|
||||
Idle:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue