tree: support toggling floating with double clicks
This commit is contained in:
parent
a588b9044d
commit
d425768760
11 changed files with 137 additions and 14 deletions
|
|
@ -465,6 +465,14 @@ impl Client {
|
|||
*self.on_new_input_device.borrow_mut() = Some(Rc::new(f));
|
||||
}
|
||||
|
||||
pub fn set_double_click_interval(&self, usec: u64) {
|
||||
self.send(&ClientMessage::SetDoubleClickIntervalUsec { usec });
|
||||
}
|
||||
|
||||
pub fn set_double_click_distance(&self, dist: i32) {
|
||||
self.send(&ClientMessage::SetDoubleClickDistance { dist });
|
||||
}
|
||||
|
||||
pub fn connector_set_position(&self, connector: Connector, x: i32, y: i32) {
|
||||
self.send(&ClientMessage::ConnectorSetPosition { connector, x, y });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -346,6 +346,12 @@ pub enum ClientMessage<'a> {
|
|||
connector: Connector,
|
||||
transform: Transform,
|
||||
},
|
||||
SetDoubleClickIntervalUsec {
|
||||
usec: u64,
|
||||
},
|
||||
SetDoubleClickDistance {
|
||||
dist: i32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use {
|
|||
Axis, Direction, ModifiedKeySym, Workspace,
|
||||
},
|
||||
serde::{Deserialize, Serialize},
|
||||
std::time::Duration,
|
||||
};
|
||||
|
||||
/// An input device.
|
||||
|
|
@ -257,6 +258,8 @@ impl Seat {
|
|||
}
|
||||
|
||||
/// Toggles whether the currently focused window is floating.
|
||||
///
|
||||
/// You can do the same by double-clicking on the header.
|
||||
pub fn toggle_floating(self) {
|
||||
get!().toggle_floating(self);
|
||||
}
|
||||
|
|
@ -329,3 +332,28 @@ pub fn on_new_seat<F: Fn(Seat) + 'static>(f: F) {
|
|||
pub fn on_new_input_device<F: Fn(InputDevice) + 'static>(f: F) {
|
||||
get!().on_new_input_device(f)
|
||||
}
|
||||
|
||||
/// Sets the maximum time between two clicks to be registered as a double click by the
|
||||
/// compositor.
|
||||
///
|
||||
/// This only affects interactions with the compositor UI and has no effect on
|
||||
/// applications.
|
||||
///
|
||||
/// The default is 400 ms.
|
||||
pub fn set_double_click_time(duration: Duration) {
|
||||
let usec = duration.as_micros().min(u64::MAX as u128);
|
||||
get!().set_double_click_interval(usec as u64)
|
||||
}
|
||||
|
||||
/// Sets the maximum distance between two clicks to be registered as a double click by the
|
||||
/// compositor.
|
||||
///
|
||||
/// This only affects interactions with the compositor UI and has no effect on
|
||||
/// applications.
|
||||
///
|
||||
/// Setting a negative distance disables double clicks.
|
||||
///
|
||||
/// The default is 5.
|
||||
pub fn set_double_click_distance(distance: i32) {
|
||||
get!().set_double_click_distance(distance)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,6 +205,8 @@ fn start_compositor2(
|
|||
drm_feedback_ids: Default::default(),
|
||||
direct_scanout_enabled: Cell::new(true),
|
||||
output_transforms: Default::default(),
|
||||
double_click_interval_usec: Cell::new(400 * 1000),
|
||||
double_click_distance: Cell::new(5),
|
||||
});
|
||||
state.tracker.register(ClientId::from_raw(0));
|
||||
create_dummy_output(&state);
|
||||
|
|
|
|||
|
|
@ -615,6 +615,14 @@ impl ConfigProxyHandler {
|
|||
self.state.default_workspace_capture.set(capture);
|
||||
}
|
||||
|
||||
fn handle_set_double_click_interval_usec(&self, usec: u64) {
|
||||
self.state.double_click_interval_usec.set(usec);
|
||||
}
|
||||
|
||||
fn handle_set_double_click_distance(&self, dist: i32) {
|
||||
self.state.double_click_distance.set(dist);
|
||||
}
|
||||
|
||||
fn handle_get_seat_workspace(&self, seat: Seat) -> Result<(), CphError> {
|
||||
let seat = self.get_seat(seat)?;
|
||||
let output = seat.get_output();
|
||||
|
|
@ -1355,6 +1363,12 @@ impl ConfigProxyHandler {
|
|||
} => self
|
||||
.handle_connector_set_transform(connector, transform)
|
||||
.wrn("connector_set_transform")?,
|
||||
ClientMessage::SetDoubleClickIntervalUsec { usec } => {
|
||||
self.handle_set_double_click_interval_usec(usec)
|
||||
}
|
||||
ClientMessage::SetDoubleClickDistance { dist } => {
|
||||
self.handle_set_double_click_distance(dist)
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ use {
|
|||
time::now_usec,
|
||||
tree::{
|
||||
generic_node_visitor, ContainerNode, ContainerSplit, Direction, FloatNode, FoundNode,
|
||||
Node, OutputNode, WorkspaceNode,
|
||||
Node, OutputNode, ToplevelNode, WorkspaceNode,
|
||||
},
|
||||
utils::{
|
||||
asyncevent::AsyncEvent,
|
||||
|
|
@ -623,6 +623,10 @@ impl WlSeatGlobal {
|
|||
Some(tl) => tl,
|
||||
_ => return,
|
||||
};
|
||||
self.set_tl_floating(tl, floating);
|
||||
}
|
||||
|
||||
pub fn set_tl_floating(self: &Rc<Self>, tl: Rc<dyn ToplevelNode>, floating: bool) {
|
||||
let data = tl.tl_data();
|
||||
if data.is_fullscreen.get() {
|
||||
return;
|
||||
|
|
@ -634,15 +638,13 @@ impl WlSeatGlobal {
|
|||
Some(p) => p,
|
||||
_ => return,
|
||||
};
|
||||
if let Some(cn) = parent.node_into_containing_node() {
|
||||
if !floating {
|
||||
cn.cnode_remove_child2(tl.tl_as_node(), true);
|
||||
self.state.map_tiled(tl);
|
||||
} else if let Some(ws) = data.workspace.get() {
|
||||
cn.cnode_remove_child2(tl.tl_as_node(), true);
|
||||
let (width, height) = data.float_size(&ws);
|
||||
self.state.map_floating(tl, width, height, &ws, None);
|
||||
}
|
||||
if !floating {
|
||||
parent.cnode_remove_child2(tl.tl_as_node(), true);
|
||||
self.state.map_tiled(tl);
|
||||
} else if let Some(ws) = data.workspace.get() {
|
||||
parent.cnode_remove_child2(tl.tl_as_node(), true);
|
||||
let (width, height) = data.float_size(&ws);
|
||||
self.state.map_floating(tl, width, height, &ws, None);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -154,6 +154,8 @@ pub struct State {
|
|||
pub drm_feedback_ids: DrmFeedbackIds,
|
||||
pub direct_scanout_enabled: Cell<bool>,
|
||||
pub output_transforms: RefCell<AHashMap<Rc<OutputId>, Transform>>,
|
||||
pub double_click_interval_usec: Cell<u64>,
|
||||
pub double_click_distance: Cell<i32>,
|
||||
}
|
||||
|
||||
// impl Drop for State {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ use {
|
|||
},
|
||||
utils::{
|
||||
clonecell::CloneCell,
|
||||
double_click_state::DoubleClickState,
|
||||
errorfmt::ErrorFmt,
|
||||
linkedlist::{LinkedList, LinkedNode, NodeRef},
|
||||
numcell::NumCell,
|
||||
|
|
@ -146,6 +147,7 @@ struct SeatState {
|
|||
x: i32,
|
||||
y: i32,
|
||||
op: Option<SeatOp>,
|
||||
double_click_state: DoubleClickState,
|
||||
}
|
||||
|
||||
impl ContainerChild {
|
||||
|
|
@ -521,6 +523,7 @@ impl ContainerNode {
|
|||
x,
|
||||
y,
|
||||
op: None,
|
||||
double_click_state: Default::default(),
|
||||
});
|
||||
let mut changed = false;
|
||||
changed |= mem::replace(&mut seat_state.x, x) != x;
|
||||
|
|
@ -1176,7 +1179,7 @@ impl Node for ContainerNode {
|
|||
fn node_on_button(
|
||||
self: Rc<Self>,
|
||||
seat: &Rc<WlSeatGlobal>,
|
||||
_time_usec: u64,
|
||||
time_usec: u64,
|
||||
button: u32,
|
||||
state: KeyState,
|
||||
_serial: u32,
|
||||
|
|
@ -1232,6 +1235,15 @@ impl Node for ContainerNode {
|
|||
}
|
||||
return;
|
||||
};
|
||||
if seat_data
|
||||
.double_click_state
|
||||
.click(&self.state, time_usec, seat_data.x, seat_data.y)
|
||||
&& kind == SeatOpKind::Move
|
||||
{
|
||||
drop(seat_datas);
|
||||
seat.set_tl_floating(child.node.clone(), true);
|
||||
return;
|
||||
}
|
||||
seat_data.op = Some(SeatOp { child, kind })
|
||||
} else if state == KeyState::Released {
|
||||
let op = seat_data.op.take().unwrap();
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ use {
|
|||
StackedNode, ToplevelNode, WorkspaceNode,
|
||||
},
|
||||
utils::{
|
||||
clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt,
|
||||
linkedlist::LinkedNode,
|
||||
clonecell::CloneCell, copyhashmap::CopyHashMap, double_click_state::DoubleClickState,
|
||||
errorfmt::ErrorFmt, linkedlist::LinkedNode,
|
||||
},
|
||||
},
|
||||
ahash::AHashMap,
|
||||
|
|
@ -57,6 +57,7 @@ struct SeatState {
|
|||
op_active: bool,
|
||||
dist_hor: i32,
|
||||
dist_ver: i32,
|
||||
double_click_state: DoubleClickState,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
|
|
@ -228,6 +229,7 @@ impl FloatNode {
|
|||
op_active: false,
|
||||
dist_hor: 0,
|
||||
dist_ver: 0,
|
||||
double_click_state: Default::default(),
|
||||
});
|
||||
seat_state.x = x;
|
||||
seat_state.y = y;
|
||||
|
|
@ -462,7 +464,7 @@ impl Node for FloatNode {
|
|||
fn node_on_button(
|
||||
self: Rc<Self>,
|
||||
seat: &Rc<WlSeatGlobal>,
|
||||
_time_usec: u64,
|
||||
time_usec: u64,
|
||||
button: u32,
|
||||
state: KeyState,
|
||||
_serial: u32,
|
||||
|
|
@ -479,6 +481,17 @@ impl Node for FloatNode {
|
|||
if state != KeyState::Pressed {
|
||||
return;
|
||||
}
|
||||
if seat_data
|
||||
.double_click_state
|
||||
.click(&self.state, time_usec, seat_data.x, seat_data.y)
|
||||
&& seat_data.op_type == OpType::Move
|
||||
{
|
||||
if let Some(tl) = self.child.get() {
|
||||
drop(seat_datas);
|
||||
seat.set_tl_floating(tl, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
seat_data.op_active = true;
|
||||
let pos = self.position.get();
|
||||
match seat_data.op_type {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ pub mod cell_ext;
|
|||
pub mod clonecell;
|
||||
pub mod copyhashmap;
|
||||
pub mod debug_fn;
|
||||
pub mod double_click_state;
|
||||
pub mod errorfmt;
|
||||
pub mod fdcloser;
|
||||
pub mod hex;
|
||||
|
|
|
|||
35
src/utils/double_click_state.rs
Normal file
35
src/utils/double_click_state.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use crate::state::State;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DoubleClickState {
|
||||
last_click: Option<(u64, i32, i32)>,
|
||||
}
|
||||
|
||||
impl DoubleClickState {
|
||||
pub fn click(&mut self, state: &State, time_usec: u64, x: i32, y: i32) -> bool {
|
||||
let res = self.click_(state, time_usec, x, y);
|
||||
if !res {
|
||||
self.last_click = Some((time_usec, x, y));
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn click_(&mut self, state: &State, time_usec: u64, x: i32, y: i32) -> bool {
|
||||
let Some((last_usec, last_x, last_y)) = self.last_click.take() else {
|
||||
return false;
|
||||
};
|
||||
if time_usec.wrapping_sub(last_usec) > state.double_click_interval_usec.get() {
|
||||
return false;
|
||||
}
|
||||
let max_dist = state.double_click_distance.get();
|
||||
if max_dist < 0 {
|
||||
return false;
|
||||
}
|
||||
let dist_x = last_x - x;
|
||||
let dist_y = last_y - y;
|
||||
if dist_x * dist_x + dist_y * dist_y > max_dist * max_dist {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue