Merge pull request #278 from mahkoh/jorth/tile-drag
Implement dragging of tiles and workspaces
This commit is contained in:
commit
074c9f54a0
34 changed files with 1430 additions and 63 deletions
|
|
@ -1,5 +1,9 @@
|
||||||
# Unreleased
|
# Unreleased
|
||||||
|
|
||||||
|
- Needs jay-config release.
|
||||||
|
- Needs jay-toml-config release.
|
||||||
|
- Needs jay-compositor release.
|
||||||
|
|
||||||
# 1.6.0
|
# 1.6.0
|
||||||
|
|
||||||
- Needs jay-algorithms release.
|
- Needs jay-algorithms release.
|
||||||
|
|
|
||||||
|
|
@ -748,6 +748,14 @@ impl Client {
|
||||||
self.send(&ClientMessage::SetFlipMargin { device, margin });
|
self.send(&ClientMessage::SetFlipMargin { device, margin });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_ui_drag_enabled(&self, enabled: bool) {
|
||||||
|
self.send(&ClientMessage::SetUiDragEnabled { enabled });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_ui_drag_threshold(&self, threshold: i32) {
|
||||||
|
self.send(&ClientMessage::SetUiDragThreshold { threshold });
|
||||||
|
}
|
||||||
|
|
||||||
pub fn connector_connected(&self, connector: Connector) -> bool {
|
pub fn connector_connected(&self, connector: Connector) -> bool {
|
||||||
let res = self.send_with_response(&ClientMessage::ConnectorConnected { connector });
|
let res = self.send_with_response(&ClientMessage::ConnectorConnected { connector });
|
||||||
get_response!(res, false, ConnectorConnected { connected });
|
get_response!(res, false, ConnectorConnected { connected });
|
||||||
|
|
|
||||||
|
|
@ -517,6 +517,12 @@ pub enum ClientMessage<'a> {
|
||||||
device: DrmDevice,
|
device: DrmDevice,
|
||||||
margin: Duration,
|
margin: Duration,
|
||||||
},
|
},
|
||||||
|
SetUiDragEnabled {
|
||||||
|
enabled: bool,
|
||||||
|
},
|
||||||
|
SetUiDragThreshold {
|
||||||
|
threshold: i32,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
|
|
||||||
|
|
@ -234,3 +234,17 @@ pub fn set_idle(timeout: Option<Duration>) {
|
||||||
pub fn set_explicit_sync_enabled(enabled: bool) {
|
pub fn set_explicit_sync_enabled(enabled: bool) {
|
||||||
get!().set_explicit_sync_enabled(enabled);
|
get!().set_explicit_sync_enabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enables or disables dragging of tiles and workspaces.
|
||||||
|
///
|
||||||
|
/// The default is `true`.
|
||||||
|
pub fn set_ui_drag_enabled(enabled: bool) {
|
||||||
|
get!().set_ui_drag_enabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the distance at which ui dragging starts.
|
||||||
|
///
|
||||||
|
/// The default is `10`.
|
||||||
|
pub fn set_ui_drag_threshold(threshold: i32) {
|
||||||
|
get!().set_ui_drag_threshold(threshold);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
# Unreleased
|
# Unreleased
|
||||||
|
|
||||||
|
- Various bugfixes.
|
||||||
|
- Tiles and workspaces can now be dragged with the mouse.
|
||||||
|
|
||||||
# 1.6.0 (2024-09-25)
|
# 1.6.0 (2024-09-25)
|
||||||
|
|
||||||
- Various bugfixes.
|
- Various bugfixes.
|
||||||
|
|
|
||||||
|
|
@ -267,6 +267,8 @@ fn start_compositor2(
|
||||||
ei_clients: EiClients::new(),
|
ei_clients: EiClients::new(),
|
||||||
slow_ei_clients: Default::default(),
|
slow_ei_clients: Default::default(),
|
||||||
cpu_worker,
|
cpu_worker,
|
||||||
|
ui_drag_enabled: Cell::new(true),
|
||||||
|
ui_drag_threshold_squared: Cell::new(10),
|
||||||
});
|
});
|
||||||
state.tracker.register(ClientId::from_raw(0));
|
state.tracker.register(ClientId::from_raw(0));
|
||||||
create_dummy_output(&state);
|
create_dummy_output(&state);
|
||||||
|
|
@ -558,6 +560,7 @@ fn create_dummy_output(state: &Rc<State>) {
|
||||||
status: Default::default(),
|
status: Default::default(),
|
||||||
scroll: Default::default(),
|
scroll: Default::default(),
|
||||||
pointer_positions: Default::default(),
|
pointer_positions: Default::default(),
|
||||||
|
pointer_down: Default::default(),
|
||||||
lock_surface: Default::default(),
|
lock_surface: Default::default(),
|
||||||
hardware_cursor: Default::default(),
|
hardware_cursor: Default::default(),
|
||||||
update_render_data_scheduled: Cell::new(false),
|
update_render_data_scheduled: Cell::new(false),
|
||||||
|
|
|
||||||
|
|
@ -759,6 +759,16 @@ impl ConfigProxyHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_set_ui_drag_enabled(&self, enabled: bool) {
|
||||||
|
self.state.ui_drag_enabled.set(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_set_ui_drag_threshold(&self, threshold: i32) {
|
||||||
|
let threshold = threshold.max(1);
|
||||||
|
let squared = threshold.saturating_mul(threshold);
|
||||||
|
self.state.ui_drag_threshold_squared.set(squared);
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_set_direct_scanout_enabled(
|
fn handle_set_direct_scanout_enabled(
|
||||||
&self,
|
&self,
|
||||||
device: Option<DrmDevice>,
|
device: Option<DrmDevice>,
|
||||||
|
|
@ -882,8 +892,10 @@ impl ConfigProxyHandler {
|
||||||
Some(l) => l.to_ref(),
|
Some(l) => l.to_ref(),
|
||||||
};
|
};
|
||||||
let config = WsMoveConfig {
|
let config = WsMoveConfig {
|
||||||
|
make_visible_always: false,
|
||||||
make_visible_if_empty: true,
|
make_visible_if_empty: true,
|
||||||
source_is_destroyed: false,
|
source_is_destroyed: false,
|
||||||
|
before: None,
|
||||||
};
|
};
|
||||||
move_ws_to_output(&link, &output, config);
|
move_ws_to_output(&link, &output, config);
|
||||||
ws.desired_output.set(output.global.output_id.clone());
|
ws.desired_output.set(output.global.output_id.clone());
|
||||||
|
|
@ -1949,6 +1961,10 @@ impl ConfigProxyHandler {
|
||||||
ClientMessage::SetFlipMargin { device, margin } => self
|
ClientMessage::SetFlipMargin { device, margin } => self
|
||||||
.handle_set_flip_margin(device, margin)
|
.handle_set_flip_margin(device, margin)
|
||||||
.wrn("set_flip_margin")?,
|
.wrn("set_flip_margin")?,
|
||||||
|
ClientMessage::SetUiDragEnabled { enabled } => self.handle_set_ui_drag_enabled(enabled),
|
||||||
|
ClientMessage::SetUiDragThreshold { threshold } => {
|
||||||
|
self.handle_set_ui_drag_threshold(threshold)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -722,6 +722,9 @@ pub fn create_render_pass(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(highlight) = seat.ui_drag_highlight() {
|
||||||
|
renderer.render_highlight(&highlight.move_(-rect.x1(), -rect.y1()));
|
||||||
|
}
|
||||||
if let Some(drag) = seat.toplevel_drag() {
|
if let Some(drag) = seat.toplevel_drag() {
|
||||||
drag.render(&mut renderer, &rect, x, y);
|
drag.render(&mut renderer, &rect, x, y);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,7 @@ pub struct WlSeatGlobal {
|
||||||
cursor_user_group: Rc<CursorUserGroup>,
|
cursor_user_group: Rc<CursorUserGroup>,
|
||||||
pointer_cursor: Rc<CursorUser>,
|
pointer_cursor: Rc<CursorUser>,
|
||||||
tree_changed: Rc<AsyncEvent>,
|
tree_changed: Rc<AsyncEvent>,
|
||||||
|
tree_changed_needs_layout: Cell<bool>,
|
||||||
selection: CloneCell<Option<Rc<dyn DynDataSource>>>,
|
selection: CloneCell<Option<Rc<dyn DynDataSource>>>,
|
||||||
selection_serial: Cell<u32>,
|
selection_serial: Cell<u32>,
|
||||||
primary_selection: CloneCell<Option<Rc<dyn DynDataSource>>>,
|
primary_selection: CloneCell<Option<Rc<dyn DynDataSource>>>,
|
||||||
|
|
@ -198,6 +199,7 @@ pub struct WlSeatGlobal {
|
||||||
hold_bindings: PerClientBindings<ZwpPointerGestureHoldV1>,
|
hold_bindings: PerClientBindings<ZwpPointerGestureHoldV1>,
|
||||||
tablet: TabletSeatData,
|
tablet: TabletSeatData,
|
||||||
ei_seats: CopyHashMap<(ClientId, EiSeatId), Rc<EiSeat>>,
|
ei_seats: CopyHashMap<(ClientId, EiSeatId), Rc<EiSeat>>,
|
||||||
|
ui_drag_highlight: Cell<Option<Rect>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const CHANGE_CURSOR_MOVED: u32 = 1 << 0;
|
const CHANGE_CURSOR_MOVED: u32 = 1 << 0;
|
||||||
|
|
@ -239,6 +241,7 @@ impl WlSeatGlobal {
|
||||||
cursor_user_group,
|
cursor_user_group,
|
||||||
pointer_cursor: cursor_user,
|
pointer_cursor: cursor_user,
|
||||||
tree_changed: Default::default(),
|
tree_changed: Default::default(),
|
||||||
|
tree_changed_needs_layout: Default::default(),
|
||||||
selection: Default::default(),
|
selection: Default::default(),
|
||||||
selection_serial: Cell::new(0),
|
selection_serial: Cell::new(0),
|
||||||
primary_selection: Default::default(),
|
primary_selection: Default::default(),
|
||||||
|
|
@ -267,12 +270,16 @@ impl WlSeatGlobal {
|
||||||
hold_bindings: Default::default(),
|
hold_bindings: Default::default(),
|
||||||
tablet: Default::default(),
|
tablet: Default::default(),
|
||||||
ei_seats: Default::default(),
|
ei_seats: Default::default(),
|
||||||
|
ui_drag_highlight: Default::default(),
|
||||||
});
|
});
|
||||||
slf.pointer_cursor.set_owner(slf.clone());
|
slf.pointer_cursor.set_owner(slf.clone());
|
||||||
let seat = slf.clone();
|
let seat = slf.clone();
|
||||||
let future = state.eng.spawn("seat handler", async move {
|
let future = state.eng.spawn("seat handler", async move {
|
||||||
loop {
|
loop {
|
||||||
seat.tree_changed.triggered().await;
|
seat.tree_changed.triggered().await;
|
||||||
|
if seat.tree_changed_needs_layout.take() {
|
||||||
|
seat.state.eng.yield_now().await;
|
||||||
|
}
|
||||||
seat.state.tree_changed_sent.set(false);
|
seat.state.tree_changed_sent.set(false);
|
||||||
seat.changes.or_assign(CHANGE_TREE);
|
seat.changes.or_assign(CHANGE_TREE);
|
||||||
// log::info!("tree_changed");
|
// log::info!("tree_changed");
|
||||||
|
|
@ -314,6 +321,10 @@ impl WlSeatGlobal {
|
||||||
self.pointer_owner.toplevel_drag()
|
self.pointer_owner.toplevel_drag()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ui_drag_highlight(&self) -> Option<Rect> {
|
||||||
|
self.ui_drag_highlight.get()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_data_device(&self, device: &Rc<WlDataDevice>) {
|
pub fn add_data_device(&self, device: &Rc<WlDataDevice>) {
|
||||||
let mut dd = self.data_devices.borrow_mut();
|
let mut dd = self.data_devices.borrow_mut();
|
||||||
dd.entry(device.client.id)
|
dd.entry(device.client.id)
|
||||||
|
|
@ -764,6 +775,18 @@ impl WlSeatGlobal {
|
||||||
.start_drag(self, origin, source, icon, serial)
|
.start_drag(self, origin, source, icon, serial)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn start_tile_drag(self: &Rc<Self>, tl: &Rc<dyn ToplevelNode>) {
|
||||||
|
if self.state.ui_drag_enabled.get() {
|
||||||
|
self.pointer_owner.start_tile_drag(self, tl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_workspace_drag(self: &Rc<Self>, ws: &Rc<WorkspaceNode>) {
|
||||||
|
if self.state.ui_drag_enabled.get() {
|
||||||
|
self.pointer_owner.start_workspace_drag(self, ws);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cancel_dnd(self: &Rc<Self>) {
|
pub fn cancel_dnd(self: &Rc<Self>) {
|
||||||
self.pointer_owner.cancel_dnd(self);
|
self.pointer_owner.cancel_dnd(self);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ pub struct NodeSeatState {
|
||||||
dnd_targets: SmallMap<SeatId, Rc<WlSeatGlobal>, 1>,
|
dnd_targets: SmallMap<SeatId, Rc<WlSeatGlobal>, 1>,
|
||||||
tablet_pad_foci: SmallMap<TabletPadId, Rc<TabletPad>, 1>,
|
tablet_pad_foci: SmallMap<TabletPadId, Rc<TabletPad>, 1>,
|
||||||
tablet_tool_foci: SmallMap<TabletToolId, Rc<TabletTool>, 1>,
|
tablet_tool_foci: SmallMap<TabletToolId, Rc<TabletTool>, 1>,
|
||||||
|
ui_drags: SmallMap<SeatId, Rc<WlSeatGlobal>, 1>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NodeSeatState {
|
impl NodeSeatState {
|
||||||
|
|
@ -101,6 +102,14 @@ impl NodeSeatState {
|
||||||
self.pointer_grabs.remove(&seat.id);
|
self.pointer_grabs.remove(&seat.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn add_ui_drag(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
|
self.ui_drags.insert(seat.id, seat.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn remove_ui_drag(&self, seat: &WlSeatGlobal) {
|
||||||
|
self.ui_drags.remove(&seat.id);
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn add_tablet_pad_focus(&self, pad: &Rc<TabletPad>) {
|
pub(super) fn add_tablet_pad_focus(&self, pad: &Rc<TabletPad>) {
|
||||||
self.tablet_pad_foci.insert(pad.id, pad.clone());
|
self.tablet_pad_foci.insert(pad.id, pad.clone());
|
||||||
}
|
}
|
||||||
|
|
@ -176,6 +185,9 @@ impl NodeSeatState {
|
||||||
while let Some((_, seat)) = self.pointer_grabs.pop() {
|
while let Some((_, seat)) = self.pointer_grabs.pop() {
|
||||||
seat.pointer_owner.grab_node_removed(&seat);
|
seat.pointer_owner.grab_node_removed(&seat);
|
||||||
}
|
}
|
||||||
|
while let Some((_, seat)) = self.ui_drags.pop() {
|
||||||
|
seat.pointer_owner.revert_to_default(&seat);
|
||||||
|
}
|
||||||
let node_id = node.node_id();
|
let node_id = node.node_id();
|
||||||
while let Some((_, seat)) = self.dnd_targets.pop() {
|
while let Some((_, seat)) = self.dnd_targets.pop() {
|
||||||
seat.pointer_owner.dnd_target_removed(&seat);
|
seat.pointer_owner.dnd_target_removed(&seat);
|
||||||
|
|
@ -1100,8 +1112,11 @@ impl WlSeatGlobal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trigger_tree_changed(&self) {
|
pub fn trigger_tree_changed(&self, needs_layout: bool) {
|
||||||
// log::info!("trigger_tree_changed");
|
// log::info!("trigger_tree_changed");
|
||||||
|
if needs_layout {
|
||||||
|
self.tree_changed_needs_layout.set(true);
|
||||||
|
}
|
||||||
self.tree_changed.trigger();
|
self.tree_changed.trigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,18 @@ use {
|
||||||
ipc,
|
ipc,
|
||||||
ipc::wl_data_source::WlDataSource,
|
ipc::wl_data_source::WlDataSource,
|
||||||
wl_seat::{
|
wl_seat::{
|
||||||
wl_pointer::PendingScroll, Dnd, DroppedDnd, WlSeatError, WlSeatGlobal, BTN_LEFT,
|
wl_pointer::PendingScroll, Dnd, DroppedDnd, NodeSeatState, WlSeatError,
|
||||||
BTN_RIGHT, CHANGE_CURSOR_MOVED, CHANGE_TREE,
|
WlSeatGlobal, BTN_LEFT, BTN_RIGHT, CHANGE_CURSOR_MOVED, CHANGE_TREE,
|
||||||
},
|
},
|
||||||
wl_surface::{dnd_icon::DndIcon, WlSurface},
|
wl_surface::{dnd_icon::DndIcon, WlSurface},
|
||||||
xdg_toplevel_drag_v1::XdgToplevelDragV1,
|
xdg_toplevel_drag_v1::XdgToplevelDragV1,
|
||||||
},
|
},
|
||||||
tree::{ContainingNode, FindTreeUsecase, FoundNode, Node, ToplevelNode, WorkspaceNode},
|
rect::Rect,
|
||||||
|
tree::{
|
||||||
|
move_ws_to_output, ContainerNode, ContainerSplit, ContainingNode, FindTreeUsecase,
|
||||||
|
FoundNode, Node, PlaceholderNode, TddType, ToplevelNode, WorkspaceDragDestination,
|
||||||
|
WorkspaceNode, WsMoveConfig,
|
||||||
|
},
|
||||||
utils::{clonecell::CloneCell, smallmap::SmallMap},
|
utils::{clonecell::CloneCell, smallmap::SmallMap},
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
|
|
@ -173,7 +178,7 @@ impl PointerOwnerHolder {
|
||||||
usecase.node_focus(seat, node);
|
usecase.node_focus(seat, node);
|
||||||
}
|
}
|
||||||
self.owner.set(Rc::new(SimplePointerOwner { usecase }));
|
self.owner.set(Rc::new(SimplePointerOwner { usecase }));
|
||||||
seat.trigger_tree_changed();
|
seat.trigger_tree_changed(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_toplevel(&self, seat: &Rc<WlSeatGlobal>, selector: impl ToplevelSelector) {
|
pub fn select_toplevel(&self, seat: &Rc<WlSeatGlobal>, selector: impl ToplevelSelector) {
|
||||||
|
|
@ -202,11 +207,22 @@ impl PointerOwnerHolder {
|
||||||
owner.disable_window_management(seat);
|
owner.disable_window_management(seat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn start_tile_drag(&self, seat: &Rc<WlSeatGlobal>, tl: &Rc<dyn ToplevelNode>) {
|
||||||
|
self.owner.get().start_tile_drag(seat, tl);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_workspace_drag(&self, seat: &Rc<WlSeatGlobal>, ws: &Rc<WorkspaceNode>) {
|
||||||
|
self.owner.get().start_workspace_drag(seat, ws);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait PointerOwner {
|
trait PointerOwner {
|
||||||
fn button(&self, seat: &Rc<WlSeatGlobal>, time_usec: u64, button: u32, state: KeyState);
|
fn button(&self, seat: &Rc<WlSeatGlobal>, time_usec: u64, button: u32, state: KeyState);
|
||||||
fn axis_node(&self, seat: &Rc<WlSeatGlobal>) -> Option<Rc<dyn Node>>;
|
fn axis_node(&self, seat: &Rc<WlSeatGlobal>) -> Option<Rc<dyn Node>> {
|
||||||
|
let _ = seat;
|
||||||
|
None
|
||||||
|
}
|
||||||
fn apply_changes(&self, seat: &Rc<WlSeatGlobal>);
|
fn apply_changes(&self, seat: &Rc<WlSeatGlobal>);
|
||||||
fn start_drag(
|
fn start_drag(
|
||||||
&self,
|
&self,
|
||||||
|
|
@ -249,6 +265,15 @@ trait PointerOwner {
|
||||||
fn disable_window_management(&self, seat: &Rc<WlSeatGlobal>) {
|
fn disable_window_management(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
let _ = seat;
|
let _ = seat;
|
||||||
}
|
}
|
||||||
|
fn start_tile_drag(&self, seat: &Rc<WlSeatGlobal>, tl: &Rc<dyn ToplevelNode>) {
|
||||||
|
let _ = seat;
|
||||||
|
let _ = tl;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_workspace_drag(&self, seat: &Rc<WlSeatGlobal>, ws: &Rc<WorkspaceNode>) {
|
||||||
|
let _ = seat;
|
||||||
|
let _ = ws;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SimplePointerOwner<T> {
|
struct SimplePointerOwner<T> {
|
||||||
|
|
@ -386,7 +411,7 @@ impl<T: SimplePointerOwnerUsecase> PointerOwner for SimplePointerOwner<T> {
|
||||||
fn revert_to_default(&self, seat: &Rc<WlSeatGlobal>) {
|
fn revert_to_default(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
if !T::IS_DEFAULT {
|
if !T::IS_DEFAULT {
|
||||||
seat.pointer_owner.set_default_pointer_owner(seat);
|
seat.pointer_owner.set_default_pointer_owner(seat);
|
||||||
seat.trigger_tree_changed();
|
seat.trigger_tree_changed(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -458,6 +483,14 @@ impl<T: SimplePointerOwnerUsecase> PointerOwner for SimpleGrabPointerOwner<T> {
|
||||||
self.node.node_seat_state().remove_pointer_grab(seat);
|
self.node.node_seat_state().remove_pointer_grab(seat);
|
||||||
seat.pointer_owner.set_default_pointer_owner(seat);
|
seat.pointer_owner.set_default_pointer_owner(seat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn start_tile_drag(&self, seat: &Rc<WlSeatGlobal>, tl: &Rc<dyn ToplevelNode>) {
|
||||||
|
self.usecase.start_tile_drag(self, seat, tl);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_workspace_drag(&self, seat: &Rc<WlSeatGlobal>, ws: &Rc<WorkspaceNode>) {
|
||||||
|
self.usecase.start_workspace_drag(self, seat, ws);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PointerOwner for DndPointerOwner {
|
impl PointerOwner for DndPointerOwner {
|
||||||
|
|
@ -496,10 +529,6 @@ impl PointerOwner for DndPointerOwner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn axis_node(&self, _seat: &Rc<WlSeatGlobal>) -> Option<Rc<dyn Node>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_changes(&self, seat: &Rc<WlSeatGlobal>) {
|
fn apply_changes(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
let (x, y) = seat.pointer_cursor.position();
|
let (x, y) = seat.pointer_cursor.position();
|
||||||
let (x_int, y_int) = (x.round_down(), y.round_down());
|
let (x_int, y_int) = (x.round_down(), y.round_down());
|
||||||
|
|
@ -620,6 +649,54 @@ trait SimplePointerOwnerUsecase: Sized + Clone + 'static {
|
||||||
fn disable_window_management(&self, seat: &Rc<WlSeatGlobal>) {
|
fn disable_window_management(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
let _ = seat;
|
let _ = seat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn start_tile_drag(
|
||||||
|
&self,
|
||||||
|
grab: &SimpleGrabPointerOwner<Self>,
|
||||||
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
tl: &Rc<dyn ToplevelNode>,
|
||||||
|
) {
|
||||||
|
let _ = grab;
|
||||||
|
let _ = seat;
|
||||||
|
let _ = tl;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_workspace_drag(
|
||||||
|
&self,
|
||||||
|
grab: &SimpleGrabPointerOwner<Self>,
|
||||||
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
ws: &Rc<WorkspaceNode>,
|
||||||
|
) {
|
||||||
|
let _ = grab;
|
||||||
|
let _ = seat;
|
||||||
|
let _ = ws;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DefaultPointerUsecase {
|
||||||
|
fn prepare_new_usecase(&self, grab: &SimpleGrabPointerOwner<Self>, seat: &Rc<WlSeatGlobal>) {
|
||||||
|
{
|
||||||
|
let mut stack = seat.pointer_stack.borrow_mut();
|
||||||
|
for node in stack.drain(1..).rev() {
|
||||||
|
node.node_on_leave(seat);
|
||||||
|
node.node_seat_state().leave(seat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grab.node.node_seat_state().remove_pointer_grab(seat);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_ui_drag<T: UiDragUsecase>(
|
||||||
|
&self,
|
||||||
|
grab: &SimpleGrabPointerOwner<Self>,
|
||||||
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
usecase: T,
|
||||||
|
) {
|
||||||
|
self.prepare_new_usecase(grab, seat);
|
||||||
|
usecase.node_seat_state().add_ui_drag(seat);
|
||||||
|
let pointer_owner = Rc::new(UiDragPointerOwner { usecase });
|
||||||
|
seat.pointer_owner.owner.set(pointer_owner.clone());
|
||||||
|
pointer_owner.apply_changes(seat);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SimplePointerOwnerUsecase for DefaultPointerUsecase {
|
impl SimplePointerOwnerUsecase for DefaultPointerUsecase {
|
||||||
|
|
@ -680,14 +757,7 @@ impl SimplePointerOwnerUsecase for DefaultPointerUsecase {
|
||||||
pos_x: Cell::new(Fixed::from_int(0)),
|
pos_x: Cell::new(Fixed::from_int(0)),
|
||||||
pos_y: Cell::new(Fixed::from_int(0)),
|
pos_y: Cell::new(Fixed::from_int(0)),
|
||||||
});
|
});
|
||||||
{
|
self.prepare_new_usecase(grab, seat);
|
||||||
let mut stack = seat.pointer_stack.borrow_mut();
|
|
||||||
for node in stack.drain(1..).rev() {
|
|
||||||
node.node_on_leave(seat);
|
|
||||||
node.node_seat_state().leave(seat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
grab.node.node_seat_state().remove_pointer_grab(seat);
|
|
||||||
// {
|
// {
|
||||||
// let old = seat.keyboard_node.set(seat.state.root.clone());
|
// let old = seat.keyboard_node.set(seat.state.root.clone());
|
||||||
// old.seat_state().unfocus(seat);
|
// old.seat_state().unfocus(seat);
|
||||||
|
|
@ -701,6 +771,38 @@ impl SimplePointerOwnerUsecase for DefaultPointerUsecase {
|
||||||
fn release_grab(&self, seat: &Rc<WlSeatGlobal>) {
|
fn release_grab(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
seat.pointer_owner.set_default_pointer_owner(seat);
|
seat.pointer_owner.set_default_pointer_owner(seat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn start_tile_drag(
|
||||||
|
&self,
|
||||||
|
grab: &SimpleGrabPointerOwner<Self>,
|
||||||
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
tl: &Rc<dyn ToplevelNode>,
|
||||||
|
) {
|
||||||
|
self.start_ui_drag(
|
||||||
|
grab,
|
||||||
|
seat,
|
||||||
|
TileDragUsecase {
|
||||||
|
tl: tl.clone(),
|
||||||
|
destination: Default::default(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_workspace_drag(
|
||||||
|
&self,
|
||||||
|
grab: &SimpleGrabPointerOwner<Self>,
|
||||||
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
ws: &Rc<WorkspaceNode>,
|
||||||
|
) {
|
||||||
|
self.start_ui_drag(
|
||||||
|
grab,
|
||||||
|
seat,
|
||||||
|
WorkspaceDragUsecase {
|
||||||
|
ws: ws.clone(),
|
||||||
|
destination: Default::default(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait NodeSelectorUsecase: Sized + 'static {
|
trait NodeSelectorUsecase: Sized + 'static {
|
||||||
|
|
@ -945,10 +1047,6 @@ where
|
||||||
self.grab_node_removed(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>) {
|
fn apply_changes(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
let Some(parent) = self.tl.tl_data().parent.get() else {
|
let Some(parent) = self.tl.tl_data().parent.get() else {
|
||||||
return;
|
return;
|
||||||
|
|
@ -1050,3 +1148,232 @@ impl WindowManagementGrabUsecase for ResizeToplevelGrabPointerOwner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait UiDragUsecase: 'static {
|
||||||
|
fn node_seat_state(&self) -> &NodeSeatState;
|
||||||
|
fn left_button_up(&self, seat: &Rc<WlSeatGlobal>);
|
||||||
|
fn apply_changes(&self, seat: &Rc<WlSeatGlobal>) -> Option<Rect>;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UiDragPointerOwner<T> {
|
||||||
|
usecase: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> UiDragPointerOwner<T>
|
||||||
|
where
|
||||||
|
T: UiDragUsecase,
|
||||||
|
{
|
||||||
|
fn do_revert_to_default(&self, seat: &Rc<WlSeatGlobal>, needs_layout: bool) {
|
||||||
|
self.usecase.node_seat_state().remove_ui_drag(seat);
|
||||||
|
if let Some(rect) = seat.ui_drag_highlight.take() {
|
||||||
|
seat.state.damage(rect);
|
||||||
|
}
|
||||||
|
seat.pointer_owner.set_default_pointer_owner(seat);
|
||||||
|
seat.trigger_tree_changed(needs_layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> PointerOwner for UiDragPointerOwner<T>
|
||||||
|
where
|
||||||
|
T: UiDragUsecase,
|
||||||
|
{
|
||||||
|
fn button(&self, seat: &Rc<WlSeatGlobal>, _time_usec: u64, button: u32, state: KeyState) {
|
||||||
|
if button == BTN_RIGHT {
|
||||||
|
self.do_revert_to_default(seat, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if button != BTN_LEFT || state != KeyState::Released {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.apply_changes(seat);
|
||||||
|
self.usecase.left_button_up(seat);
|
||||||
|
self.do_revert_to_default(seat, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_changes(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
|
let new_highlight = self.usecase.apply_changes(seat);
|
||||||
|
let prev_highlight = seat.ui_drag_highlight.replace(new_highlight);
|
||||||
|
if prev_highlight != new_highlight {
|
||||||
|
if let Some(rect) = prev_highlight {
|
||||||
|
seat.state.damage(rect);
|
||||||
|
}
|
||||||
|
if let Some(rect) = new_highlight {
|
||||||
|
seat.state.damage(rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn revert_to_default(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
|
self.do_revert_to_default(seat, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TileDragUsecase {
|
||||||
|
tl: Rc<dyn ToplevelNode>,
|
||||||
|
destination: Cell<Option<TddType>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiDragUsecase for TileDragUsecase {
|
||||||
|
fn node_seat_state(&self) -> &NodeSeatState {
|
||||||
|
self.tl.node_seat_state()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn left_button_up(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
|
let Some(dest) = self.destination.take() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let src = self.tl.clone();
|
||||||
|
let Some(src_parent) = src.tl_data().parent.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let detach = || {
|
||||||
|
let placeholder = Rc::new(PlaceholderNode::new_empty(&seat.state));
|
||||||
|
src_parent
|
||||||
|
.clone()
|
||||||
|
.cnode_replace_child(src.tl_as_node(), placeholder.clone());
|
||||||
|
placeholder
|
||||||
|
};
|
||||||
|
let new_container = |workspace: &Rc<WorkspaceNode>| {
|
||||||
|
src_parent
|
||||||
|
.clone()
|
||||||
|
.cnode_remove_child2(src.tl_as_node(), true);
|
||||||
|
let cn = ContainerNode::new(
|
||||||
|
&seat.state,
|
||||||
|
&workspace,
|
||||||
|
src.clone(),
|
||||||
|
ContainerSplit::Horizontal,
|
||||||
|
);
|
||||||
|
workspace.set_container(&cn);
|
||||||
|
};
|
||||||
|
match dest {
|
||||||
|
TddType::Replace(dst) => {
|
||||||
|
let Some(dst_parent) = dst.tl_data().parent.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let placeholder = detach();
|
||||||
|
dst_parent.cnode_replace_child(dst.tl_as_node(), src);
|
||||||
|
src_parent.cnode_replace_child(placeholder.tl_as_node(), dst);
|
||||||
|
}
|
||||||
|
TddType::Split {
|
||||||
|
node,
|
||||||
|
split,
|
||||||
|
before,
|
||||||
|
} => {
|
||||||
|
let data = node.tl_data();
|
||||||
|
let Some(pn) = data.parent.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(ws) = data.workspace.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let placeholder = detach();
|
||||||
|
let cn = ContainerNode::new(&seat.state, &ws, node.clone(), split);
|
||||||
|
pn.cnode_replace_child(node.tl_as_node(), cn.clone());
|
||||||
|
match before {
|
||||||
|
true => cn.add_child_before(node.tl_as_node(), src),
|
||||||
|
false => cn.add_child_after(node.tl_as_node(), src),
|
||||||
|
}
|
||||||
|
src_parent.cnode_remove_child(placeholder.tl_as_node());
|
||||||
|
}
|
||||||
|
TddType::Insert {
|
||||||
|
container,
|
||||||
|
neighbor,
|
||||||
|
before,
|
||||||
|
} => {
|
||||||
|
let placeholder = detach();
|
||||||
|
match before {
|
||||||
|
true => container.add_child_before(neighbor.tl_as_node(), src),
|
||||||
|
false => container.add_child_after(neighbor.tl_as_node(), src),
|
||||||
|
};
|
||||||
|
src_parent.cnode_remove_child(placeholder.tl_as_node());
|
||||||
|
}
|
||||||
|
TddType::NewWorkspace { output } => {
|
||||||
|
new_container(&output.ensure_workspace());
|
||||||
|
}
|
||||||
|
TddType::NewContainer { workspace } => {
|
||||||
|
new_container(&workspace);
|
||||||
|
}
|
||||||
|
TddType::MoveToWorkspace { workspace } => {
|
||||||
|
src_parent.cnode_remove_child(src.tl_as_node());
|
||||||
|
seat.state.map_tiled_on(src, &workspace);
|
||||||
|
}
|
||||||
|
TddType::MoveToNewWorkspace { output } => {
|
||||||
|
let ws = output.generate_workspace();
|
||||||
|
src_parent.cnode_remove_child(src.tl_as_node());
|
||||||
|
seat.state.map_tiled_on(src, &ws);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_changes(&self, seat: &Rc<WlSeatGlobal>) -> Option<Rect> {
|
||||||
|
let (x, y) = seat.pointer_cursor.position();
|
||||||
|
let dest = seat.state.root.tile_drag_destination(
|
||||||
|
self.tl.node_id(),
|
||||||
|
x.round_down(),
|
||||||
|
y.round_down(),
|
||||||
|
);
|
||||||
|
match dest {
|
||||||
|
None => {
|
||||||
|
self.destination.take();
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Some(d) => {
|
||||||
|
self.destination.set(Some(d.ty));
|
||||||
|
Some(d.highlight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WorkspaceDragUsecase {
|
||||||
|
ws: Rc<WorkspaceNode>,
|
||||||
|
destination: Cell<Option<WorkspaceDragDestination>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiDragUsecase for WorkspaceDragUsecase {
|
||||||
|
fn node_seat_state(&self) -> &NodeSeatState {
|
||||||
|
self.ws.node_seat_state()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn left_button_up(&self, _seat: &Rc<WlSeatGlobal>) {
|
||||||
|
let Some(dest) = self.destination.take() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let ws = self.ws.clone();
|
||||||
|
let output = dest.output.clone();
|
||||||
|
if ws.is_dummy || output.is_dummy {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let link = match &*ws.output_link.borrow() {
|
||||||
|
None => return,
|
||||||
|
Some(l) => l.to_ref(),
|
||||||
|
};
|
||||||
|
let config = WsMoveConfig {
|
||||||
|
make_visible_always: true,
|
||||||
|
make_visible_if_empty: true,
|
||||||
|
source_is_destroyed: false,
|
||||||
|
before: dest.before.clone(),
|
||||||
|
};
|
||||||
|
move_ws_to_output(&link, &output, config);
|
||||||
|
ws.desired_output.set(output.global.output_id.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_changes(&self, seat: &Rc<WlSeatGlobal>) -> Option<Rect> {
|
||||||
|
let (x, y) = seat.pointer_cursor.position();
|
||||||
|
let dest =
|
||||||
|
seat.state
|
||||||
|
.root
|
||||||
|
.workspace_drag_destination(self.ws.id, x.round_down(), y.round_down());
|
||||||
|
match dest {
|
||||||
|
None => {
|
||||||
|
self.destination.take();
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Some(d) => {
|
||||||
|
let hl = d.highlight;
|
||||||
|
self.destination.set(Some(d));
|
||||||
|
Some(hl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,9 @@ use {
|
||||||
renderer::Renderer,
|
renderer::Renderer,
|
||||||
state::State,
|
state::State,
|
||||||
tree::{
|
tree::{
|
||||||
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor,
|
default_tile_drag_destination, ContainerSplit, Direction, FindTreeResult,
|
||||||
StackedNode, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode,
|
FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor, StackedNode,
|
||||||
|
TileDragDestination, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode,
|
||||||
},
|
},
|
||||||
utils::{clonecell::CloneCell, copyhashmap::CopyHashMap, linkedlist::LinkedNode},
|
utils::{clonecell::CloneCell, copyhashmap::CopyHashMap, linkedlist::LinkedNode},
|
||||||
wire::WlSurfaceId,
|
wire::WlSurfaceId,
|
||||||
|
|
@ -467,6 +468,17 @@ impl ToplevelNodeBase for Xwindow {
|
||||||
fn tl_admits_children(&self) -> bool {
|
fn tl_admits_children(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tl_tile_drag_destination(
|
||||||
|
self: Rc<Self>,
|
||||||
|
source: NodeId,
|
||||||
|
split: Option<ContainerSplit>,
|
||||||
|
abs_bounds: Rect,
|
||||||
|
abs_x: i32,
|
||||||
|
abs_y: i32,
|
||||||
|
) -> Option<TileDragDestination> {
|
||||||
|
default_tile_drag_destination(self, source, split, abs_bounds, abs_x, abs_y)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StackedNode for Xwindow {
|
impl StackedNode for Xwindow {
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@ use {
|
||||||
renderer::Renderer,
|
renderer::Renderer,
|
||||||
state::State,
|
state::State,
|
||||||
tree::{
|
tree::{
|
||||||
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor,
|
default_tile_drag_destination, ContainerSplit, Direction, FindTreeResult,
|
||||||
OutputNode, ToplevelData, ToplevelNode, ToplevelNodeBase, ToplevelNodeId,
|
FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor, OutputNode, TileDragDestination,
|
||||||
WorkspaceNode,
|
ToplevelData, ToplevelNode, ToplevelNodeBase, ToplevelNodeId, WorkspaceNode,
|
||||||
},
|
},
|
||||||
utils::{clonecell::CloneCell, hash_map_ext::HashMapExt},
|
utils::{clonecell::CloneCell, hash_map_ext::HashMapExt},
|
||||||
wire::{xdg_toplevel::*, XdgToplevelId},
|
wire::{xdg_toplevel::*, XdgToplevelId},
|
||||||
|
|
@ -667,6 +667,17 @@ impl ToplevelNodeBase for XdgToplevel {
|
||||||
fn tl_admits_children(&self) -> bool {
|
fn tl_admits_children(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tl_tile_drag_destination(
|
||||||
|
self: Rc<Self>,
|
||||||
|
source: NodeId,
|
||||||
|
split: Option<ContainerSplit>,
|
||||||
|
abs_bounds: Rect,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
) -> Option<TileDragDestination> {
|
||||||
|
default_tile_drag_destination(self, source, split, abs_bounds, x, y)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl XdgSurfaceExt for XdgToplevel {
|
impl XdgSurfaceExt for XdgToplevel {
|
||||||
|
|
|
||||||
|
|
@ -71,8 +71,13 @@ impl Rect {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub fn new_unchecked(x1: i32, y1: i32, x2: i32, y2: i32) -> Self {
|
||||||
|
Self::new(x1, y1, x2, y2).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
#[expect(dead_code)]
|
#[expect(dead_code)]
|
||||||
fn new_unchecked(x1: i32, y1: i32, x2: i32, y2: i32) -> Self {
|
fn new_unchecked_danger(x1: i32, y1: i32, x2: i32, y2: i32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
raw: RectRaw { x1, y1, x2, y2 },
|
raw: RectRaw { x1, y1, x2, y2 },
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,9 +70,9 @@ fn subtract1() {
|
||||||
#[test]
|
#[test]
|
||||||
fn rects_to_bands() {
|
fn rects_to_bands() {
|
||||||
let rects = [
|
let rects = [
|
||||||
Rect::new_unchecked(0, 0, 10, 10),
|
Rect::new_unchecked_danger(0, 0, 10, 10),
|
||||||
Rect::new_unchecked(5, 0, 30, 10),
|
Rect::new_unchecked_danger(5, 0, 30, 10),
|
||||||
Rect::new_unchecked(30, 5, 50, 15),
|
Rect::new_unchecked_danger(30, 5, 50, 15),
|
||||||
];
|
];
|
||||||
let r = Region::from_rects(&rects[..]);
|
let r = Region::from_rects(&rects[..]);
|
||||||
// println!("{:#?}", r.rects);
|
// println!("{:#?}", r.rects);
|
||||||
|
|
@ -104,8 +104,8 @@ fn rects_to_bands() {
|
||||||
#[test]
|
#[test]
|
||||||
fn rects_to_bands2() {
|
fn rects_to_bands2() {
|
||||||
let rects = [
|
let rects = [
|
||||||
Rect::new_unchecked(0, 0, 10, 10),
|
Rect::new_unchecked_danger(0, 0, 10, 10),
|
||||||
Rect::new_unchecked(0, 10, 10, 20),
|
Rect::new_unchecked_danger(0, 10, 10, 20),
|
||||||
];
|
];
|
||||||
let r = Region::from_rects(&rects[..]);
|
let r = Region::from_rects(&rects[..]);
|
||||||
// println!("{:#?}", r.rects);
|
// println!("{:#?}", r.rects);
|
||||||
|
|
|
||||||
|
|
@ -317,11 +317,11 @@ impl Renderer<'_> {
|
||||||
render_highlight: bool,
|
render_highlight: bool,
|
||||||
) {
|
) {
|
||||||
if render_highlight {
|
if render_highlight {
|
||||||
self.render_highlight(tl_data, bounds);
|
self.render_tl_highlight(tl_data, bounds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_highlight(&mut self, tl_data: &ToplevelData, bounds: Option<&Rect>) {
|
fn render_tl_highlight(&mut self, tl_data: &ToplevelData, bounds: Option<&Rect>) {
|
||||||
if tl_data.render_highlight.get() == 0 {
|
if tl_data.render_highlight.get() == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -333,6 +333,12 @@ impl Renderer<'_> {
|
||||||
self.base.fill_scaled_boxes(slice::from_ref(bounds), &color);
|
self.base.fill_scaled_boxes(slice::from_ref(bounds), &color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn render_highlight(&mut self, rect: &Rect) {
|
||||||
|
let color = self.state.theme.colors.highlight.get();
|
||||||
|
self.base.ops.push(GfxApiOpt::Sync);
|
||||||
|
self.base.fill_boxes(slice::from_ref(rect), &color);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render_surface(&mut self, surface: &WlSurface, x: i32, y: i32, bounds: Option<&Rect>) {
|
pub fn render_surface(&mut self, surface: &WlSurface, x: i32, y: i32, bounds: Option<&Rect>) {
|
||||||
let (x, y) = self.base.scale_point(x, y);
|
let (x, y) = self.base.scale_point(x, y);
|
||||||
self.render_surface_scaled(surface, x, y, None, bounds, false);
|
self.render_surface_scaled(surface, x, y, None, bounds, false);
|
||||||
|
|
|
||||||
13
src/state.rs
13
src/state.rs
|
|
@ -218,6 +218,8 @@ pub struct State {
|
||||||
pub ei_clients: EiClients,
|
pub ei_clients: EiClients,
|
||||||
pub slow_ei_clients: AsyncQueue<Rc<EiClient>>,
|
pub slow_ei_clients: AsyncQueue<Rc<EiClient>>,
|
||||||
pub cpu_worker: Rc<CpuWorker>,
|
pub cpu_worker: Rc<CpuWorker>,
|
||||||
|
pub ui_drag_enabled: Cell<bool>,
|
||||||
|
pub ui_drag_threshold_squared: Cell<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl Drop for State {
|
// impl Drop for State {
|
||||||
|
|
@ -586,7 +588,7 @@ impl State {
|
||||||
}
|
}
|
||||||
let seats = self.globals.seats.lock();
|
let seats = self.globals.seats.lock();
|
||||||
for seat in seats.values() {
|
for seat in seats.values() {
|
||||||
seat.trigger_tree_changed();
|
seat.trigger_tree_changed(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1240,6 +1242,15 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ui_drag_threshold_reached(&self, (x1, y1): (i32, i32), (x2, y2): (i32, i32)) -> bool {
|
||||||
|
if !self.ui_drag_enabled.get() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let dx = x1 - x2;
|
||||||
|
let dy = y1 - y2;
|
||||||
|
dx * dx + dy * dy > self.ui_drag_threshold_squared.get()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,7 @@ impl ConnectorHandler {
|
||||||
status: self.state.status.clone(),
|
status: self.state.status.clone(),
|
||||||
scroll: Default::default(),
|
scroll: Default::default(),
|
||||||
pointer_positions: Default::default(),
|
pointer_positions: Default::default(),
|
||||||
|
pointer_down: Default::default(),
|
||||||
lock_surface: Default::default(),
|
lock_surface: Default::default(),
|
||||||
hardware_cursor: Default::default(),
|
hardware_cursor: Default::default(),
|
||||||
jay_outputs: Default::default(),
|
jay_outputs: Default::default(),
|
||||||
|
|
@ -231,8 +232,10 @@ impl ConnectorHandler {
|
||||||
&& ws.desired_output.get() == output_id)
|
&& ws.desired_output.get() == output_id)
|
||||||
|| ws_to_move.is_empty();
|
|| ws_to_move.is_empty();
|
||||||
let config = WsMoveConfig {
|
let config = WsMoveConfig {
|
||||||
|
make_visible_always: false,
|
||||||
make_visible_if_empty: make_visible,
|
make_visible_if_empty: make_visible,
|
||||||
source_is_destroyed: false,
|
source_is_destroyed: false,
|
||||||
|
before: None,
|
||||||
};
|
};
|
||||||
move_ws_to_output(&ws, &on, config);
|
move_ws_to_output(&ws, &on, config);
|
||||||
}
|
}
|
||||||
|
|
@ -304,8 +307,10 @@ impl ConnectorHandler {
|
||||||
ws.visible_on_desired_output.set(ws.visible.get());
|
ws.visible_on_desired_output.set(ws.visible.get());
|
||||||
}
|
}
|
||||||
let config = WsMoveConfig {
|
let config = WsMoveConfig {
|
||||||
|
make_visible_always: false,
|
||||||
make_visible_if_empty: ws.visible.get(),
|
make_visible_if_empty: ws.visible.get(),
|
||||||
source_is_destroyed: true,
|
source_is_destroyed: true,
|
||||||
|
before: None,
|
||||||
};
|
};
|
||||||
move_ws_to_output(&ws, &target, config);
|
move_ws_to_output(&ws, &target, config);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -508,7 +508,6 @@ pub trait Node: 'static {
|
||||||
|
|
||||||
// TYPE CONVERTERS
|
// TYPE CONVERTERS
|
||||||
|
|
||||||
#[cfg_attr(not(feature = "it"), expect(dead_code))]
|
|
||||||
fn node_into_float(self: Rc<Self>) -> Option<Rc<FloatNode>> {
|
fn node_into_float(self: Rc<Self>) -> Option<Rc<FloatNode>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use {
|
||||||
collect_kb_foci, collect_kb_foci2,
|
collect_kb_foci, collect_kb_foci2,
|
||||||
tablet::{TabletTool, TabletToolChanges, TabletToolId},
|
tablet::{TabletTool, TabletToolChanges, TabletToolId},
|
||||||
wl_pointer::PendingScroll,
|
wl_pointer::PendingScroll,
|
||||||
NodeSeatState, SeatId, WlSeatGlobal, BTN_LEFT,
|
NodeSeatState, SeatId, WlSeatGlobal, BTN_LEFT, BTN_RIGHT,
|
||||||
},
|
},
|
||||||
rect::Rect,
|
rect::Rect,
|
||||||
renderer::Renderer,
|
renderer::Renderer,
|
||||||
|
|
@ -17,8 +17,9 @@ use {
|
||||||
state::State,
|
state::State,
|
||||||
text::TextTexture,
|
text::TextTexture,
|
||||||
tree::{
|
tree::{
|
||||||
walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FindTreeUsecase,
|
default_tile_drag_bounds, walker::NodeVisitor, ContainingNode, Direction,
|
||||||
FoundNode, Node, NodeId, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode,
|
FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, TddType, TileDragDestination,
|
||||||
|
ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
asyncevent::AsyncEvent,
|
asyncevent::AsyncEvent,
|
||||||
|
|
@ -53,6 +54,15 @@ pub enum ContainerSplit {
|
||||||
Vertical,
|
Vertical,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ContainerSplit {
|
||||||
|
pub fn other(self) -> Self {
|
||||||
|
match self {
|
||||||
|
ContainerSplit::Horizontal => ContainerSplit::Vertical,
|
||||||
|
ContainerSplit::Vertical => ContainerSplit::Horizontal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Axis> for ContainerSplit {
|
impl From<Axis> for ContainerSplit {
|
||||||
fn from(a: Axis) -> Self {
|
fn from(a: Axis) -> Self {
|
||||||
match a {
|
match a {
|
||||||
|
|
@ -547,6 +557,7 @@ impl ContainerNode {
|
||||||
|
|
||||||
fn pointer_move(
|
fn pointer_move(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
|
seat: &Rc<WlSeatGlobal>,
|
||||||
id: CursorType,
|
id: CursorType,
|
||||||
cursor: &CursorUser,
|
cursor: &CursorUser,
|
||||||
x: Fixed,
|
x: Fixed,
|
||||||
|
|
@ -574,7 +585,13 @@ impl ContainerNode {
|
||||||
if let Some(op) = &seat_state.op {
|
if let Some(op) = &seat_state.op {
|
||||||
match op.kind {
|
match op.kind {
|
||||||
SeatOpKind::Move => {
|
SeatOpKind::Move => {
|
||||||
// todo
|
if let CursorType::Seat(_) = id {
|
||||||
|
if self.state.ui_drag_threshold_reached((x, y), (op.x, op.y)) {
|
||||||
|
let node = op.child.node.clone();
|
||||||
|
drop(seats);
|
||||||
|
seat.start_tile_drag(&node);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SeatOpKind::Resize {
|
SeatOpKind::Resize {
|
||||||
dist_left,
|
dist_left,
|
||||||
|
|
@ -1143,18 +1160,44 @@ impl ContainerNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn toggle_mono(self: &Rc<Self>) {
|
||||||
|
if self.mono_child.is_some() {
|
||||||
|
self.set_mono(None);
|
||||||
|
} else if let Some(last) = self.focus_history.last() {
|
||||||
|
self.set_mono(Some(&*last.node));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn button(
|
fn button(
|
||||||
self: Rc<Self>,
|
self: Rc<Self>,
|
||||||
id: CursorType,
|
id: CursorType,
|
||||||
seat: &Rc<WlSeatGlobal>,
|
seat: &Rc<WlSeatGlobal>,
|
||||||
time_usec: u64,
|
time_usec: u64,
|
||||||
pressed: bool,
|
pressed: bool,
|
||||||
|
button: u32,
|
||||||
) {
|
) {
|
||||||
let mut seat_datas = self.cursors.borrow_mut();
|
let mut seat_datas = self.cursors.borrow_mut();
|
||||||
let seat_data = match seat_datas.get_mut(&id) {
|
let seat_data = match seat_datas.get_mut(&id) {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
if button == BTN_RIGHT && pressed {
|
||||||
|
if self.mono_child.is_some() || self.split.get() == ContainerSplit::Horizontal {
|
||||||
|
if seat_data.y < self.state.theme.sizes.title_height.get() {
|
||||||
|
self.toggle_mono();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for child in self.children.iter() {
|
||||||
|
if child.title_rect.get().contains(seat_data.x, seat_data.y) {
|
||||||
|
self.toggle_mono();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if button != BTN_LEFT {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if seat_data.op.is_none() {
|
if seat_data.op.is_none() {
|
||||||
if !pressed {
|
if !pressed {
|
||||||
return;
|
return;
|
||||||
|
|
@ -1207,20 +1250,220 @@ impl ContainerNode {
|
||||||
seat.set_tl_floating(child.node.clone(), true);
|
seat.set_tl_floating(child.node.clone(), true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
seat_data.op = Some(SeatOp { child, kind })
|
seat_data.op = Some(SeatOp {
|
||||||
|
child,
|
||||||
|
kind,
|
||||||
|
x: seat_data.x,
|
||||||
|
y: seat_data.y,
|
||||||
|
})
|
||||||
} else if !pressed {
|
} else if !pressed {
|
||||||
let op = seat_data.op.take().unwrap();
|
seat_data.op = None;
|
||||||
drop(seat_datas);
|
drop(seat_datas);
|
||||||
if op.kind == SeatOpKind::Move {
|
}
|
||||||
// todo
|
}
|
||||||
|
|
||||||
|
fn tile_drag_destination_mono_titles(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
source: NodeId,
|
||||||
|
abs_bounds: Rect,
|
||||||
|
abs_x: i32,
|
||||||
|
abs_y: i32,
|
||||||
|
) -> Option<TileDragDestination> {
|
||||||
|
let mut prev_is_source = false;
|
||||||
|
let mut prev_center = 0;
|
||||||
|
for child in self.children.iter() {
|
||||||
|
if child.node.node_id() == source {
|
||||||
|
prev_is_source = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let rect = child.title_rect.get();
|
||||||
|
let center = (rect.x1() + rect.x2()) / 2;
|
||||||
|
if !prev_is_source {
|
||||||
|
let rect = Rect::new(prev_center, 0, center, rect.height())?
|
||||||
|
.move_(self.abs_x1.get(), self.abs_y1.get())
|
||||||
|
.intersect(abs_bounds);
|
||||||
|
if rect.contains(abs_x, abs_y) {
|
||||||
|
return Some(TileDragDestination {
|
||||||
|
highlight: rect,
|
||||||
|
ty: TddType::Insert {
|
||||||
|
container: self.clone(),
|
||||||
|
neighbor: child.node.clone(),
|
||||||
|
before: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prev_center = center;
|
||||||
|
prev_is_source = false;
|
||||||
|
}
|
||||||
|
if prev_is_source {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let last = self.children.last()?;
|
||||||
|
let rect = Rect::new(
|
||||||
|
prev_center,
|
||||||
|
0,
|
||||||
|
self.width.get(),
|
||||||
|
self.state.theme.sizes.title_height.get(),
|
||||||
|
)?
|
||||||
|
.move_(self.abs_x1.get(), self.abs_y1.get())
|
||||||
|
.intersect(abs_bounds);
|
||||||
|
if rect.contains(abs_x, abs_y) {
|
||||||
|
return Some(TileDragDestination {
|
||||||
|
highlight: rect,
|
||||||
|
ty: TddType::Insert {
|
||||||
|
container: self.clone(),
|
||||||
|
neighbor: last.node.clone(),
|
||||||
|
before: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tile_drag_destination_mono(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
mc: &ContainerChild,
|
||||||
|
source: NodeId,
|
||||||
|
abs_bounds: Rect,
|
||||||
|
abs_x: i32,
|
||||||
|
abs_y: i32,
|
||||||
|
) -> Option<TileDragDestination> {
|
||||||
|
let th = self.state.theme.sizes.title_height.get();
|
||||||
|
if abs_y < self.abs_y1.get() + th {
|
||||||
|
return self.tile_drag_destination_mono_titles(source, abs_bounds, abs_x, abs_y);
|
||||||
|
}
|
||||||
|
let body = self.mono_body.get();
|
||||||
|
let bounds = body
|
||||||
|
.move_(self.abs_x1.get(), self.abs_y1.get())
|
||||||
|
.intersect(abs_bounds);
|
||||||
|
return mc
|
||||||
|
.node
|
||||||
|
.clone()
|
||||||
|
.tl_tile_drag_destination(source, None, bounds, abs_x, abs_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tile_drag_destination(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
source: NodeId,
|
||||||
|
abs_bounds: Rect,
|
||||||
|
abs_x: i32,
|
||||||
|
abs_y: i32,
|
||||||
|
) -> Option<TileDragDestination> {
|
||||||
|
if source == self.node_id() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if let Some(mc) = self.mono_child.get() {
|
||||||
|
return self.tile_drag_destination_mono(&mc, source, abs_bounds, abs_x, abs_y);
|
||||||
|
}
|
||||||
|
let mut prev_is_source = false;
|
||||||
|
let mut prev_border_start = 0;
|
||||||
|
let split = self.split.get();
|
||||||
|
for child in self.children.iter() {
|
||||||
|
if child.node.node_id() == source {
|
||||||
|
prev_is_source = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let start_drag_bounds = child.node.tl_tile_drag_bounds(split, true);
|
||||||
|
let end_drag_bounds = child.node.tl_tile_drag_bounds(split, false);
|
||||||
|
let body = child.body.get();
|
||||||
|
let main_body_rect = {
|
||||||
|
match split {
|
||||||
|
ContainerSplit::Horizontal => Rect::new(
|
||||||
|
body.x1() + start_drag_bounds,
|
||||||
|
body.y1(),
|
||||||
|
body.x2() - end_drag_bounds,
|
||||||
|
body.y2(),
|
||||||
|
)?,
|
||||||
|
ContainerSplit::Vertical => Rect::new(
|
||||||
|
body.x1(),
|
||||||
|
body.y1() + start_drag_bounds,
|
||||||
|
body.x2(),
|
||||||
|
body.y2() - end_drag_bounds,
|
||||||
|
)?,
|
||||||
|
}
|
||||||
|
.move_(self.abs_x1.get(), self.abs_y1.get())
|
||||||
|
.intersect(abs_bounds)
|
||||||
|
};
|
||||||
|
if main_body_rect.contains(abs_x, abs_y) {
|
||||||
|
return child.node.clone().tl_tile_drag_destination(
|
||||||
|
source,
|
||||||
|
Some(split),
|
||||||
|
main_body_rect,
|
||||||
|
abs_x,
|
||||||
|
abs_y,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !prev_is_source {
|
||||||
|
let left_border_rect = {
|
||||||
|
match split {
|
||||||
|
ContainerSplit::Horizontal => Rect::new(
|
||||||
|
prev_border_start,
|
||||||
|
body.y1(),
|
||||||
|
body.x1() + start_drag_bounds,
|
||||||
|
body.y2(),
|
||||||
|
)?,
|
||||||
|
ContainerSplit::Vertical => Rect::new(
|
||||||
|
body.x1(),
|
||||||
|
prev_border_start,
|
||||||
|
body.x2(),
|
||||||
|
body.y1() + start_drag_bounds,
|
||||||
|
)?,
|
||||||
|
}
|
||||||
|
.move_(self.abs_x1.get(), self.abs_y1.get())
|
||||||
|
.intersect(abs_bounds)
|
||||||
|
};
|
||||||
|
if left_border_rect.contains(abs_x, abs_y) {
|
||||||
|
return Some(TileDragDestination {
|
||||||
|
highlight: left_border_rect,
|
||||||
|
ty: TddType::Insert {
|
||||||
|
container: self.clone(),
|
||||||
|
neighbor: child.node.clone(),
|
||||||
|
before: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prev_is_source = false;
|
||||||
|
prev_border_start = match split {
|
||||||
|
ContainerSplit::Horizontal => body.x2() - end_drag_bounds,
|
||||||
|
ContainerSplit::Vertical => body.y2() - end_drag_bounds,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if prev_is_source {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let last = self.children.last()?;
|
||||||
|
let body = last.body.get();
|
||||||
|
let right_border_rect = match split {
|
||||||
|
ContainerSplit::Horizontal => {
|
||||||
|
Rect::new(prev_border_start, body.y1(), body.x2(), body.y2())?
|
||||||
|
}
|
||||||
|
ContainerSplit::Vertical => {
|
||||||
|
Rect::new(body.x1(), prev_border_start, body.x2(), body.y2())?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.move_(self.abs_x1.get(), self.abs_y1.get())
|
||||||
|
.intersect(abs_bounds);
|
||||||
|
if right_border_rect.contains(abs_x, abs_y) {
|
||||||
|
return Some(TileDragDestination {
|
||||||
|
highlight: right_border_rect,
|
||||||
|
ty: TddType::Insert {
|
||||||
|
container: self.clone(),
|
||||||
|
neighbor: last.node.clone(),
|
||||||
|
before: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SeatOp {
|
struct SeatOp {
|
||||||
child: NodeRef<ContainerChild>,
|
child: NodeRef<ContainerChild>,
|
||||||
kind: SeatOpKind,
|
kind: SeatOpKind,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
|
@ -1382,11 +1625,8 @@ impl Node for ContainerNode {
|
||||||
state: KeyState,
|
state: KeyState,
|
||||||
_serial: u32,
|
_serial: u32,
|
||||||
) {
|
) {
|
||||||
if button != BTN_LEFT {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let id = CursorType::Seat(seat.id());
|
let id = CursorType::Seat(seat.id());
|
||||||
self.button(id, seat, time_usec, state == KeyState::Pressed);
|
self.button(id, seat, time_usec, state == KeyState::Pressed, button);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_on_axis_event(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, event: &PendingScroll) {
|
fn node_on_axis_event(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, event: &PendingScroll) {
|
||||||
|
|
@ -1429,6 +1669,7 @@ impl Node for ContainerNode {
|
||||||
fn node_on_pointer_enter(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, x: Fixed, y: Fixed) {
|
fn node_on_pointer_enter(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, x: Fixed, y: Fixed) {
|
||||||
// log::info!("node_on_pointer_enter");
|
// log::info!("node_on_pointer_enter");
|
||||||
self.pointer_move(
|
self.pointer_move(
|
||||||
|
seat,
|
||||||
CursorType::Seat(seat.id()),
|
CursorType::Seat(seat.id()),
|
||||||
seat.pointer_cursor(),
|
seat.pointer_cursor(),
|
||||||
x,
|
x,
|
||||||
|
|
@ -1437,6 +1678,14 @@ impl Node for ContainerNode {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn node_on_leave(&self, seat: &WlSeatGlobal) {
|
||||||
|
let mut seats = self.cursors.borrow_mut();
|
||||||
|
let id = CursorType::Seat(seat.id());
|
||||||
|
if let Some(seat_state) = seats.get_mut(&id) {
|
||||||
|
seat_state.op = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn node_on_pointer_unfocus(&self, seat: &Rc<WlSeatGlobal>) {
|
fn node_on_pointer_unfocus(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
// log::info!("unfocus");
|
// log::info!("unfocus");
|
||||||
let mut seats = self.cursors.borrow_mut();
|
let mut seats = self.cursors.borrow_mut();
|
||||||
|
|
@ -1459,6 +1708,7 @@ impl Node for ContainerNode {
|
||||||
fn node_on_pointer_motion(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, x: Fixed, y: Fixed) {
|
fn node_on_pointer_motion(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, x: Fixed, y: Fixed) {
|
||||||
// log::info!("node_on_pointer_motion");
|
// log::info!("node_on_pointer_motion");
|
||||||
self.pointer_move(
|
self.pointer_move(
|
||||||
|
seat,
|
||||||
CursorType::Seat(seat.id()),
|
CursorType::Seat(seat.id()),
|
||||||
seat.pointer_cursor(),
|
seat.pointer_cursor(),
|
||||||
x,
|
x,
|
||||||
|
|
@ -1480,7 +1730,14 @@ impl Node for ContainerNode {
|
||||||
y: Fixed,
|
y: Fixed,
|
||||||
) {
|
) {
|
||||||
tool.cursor().set_known(KnownCursor::Default);
|
tool.cursor().set_known(KnownCursor::Default);
|
||||||
self.pointer_move(CursorType::TabletTool(tool.id), tool.cursor(), x, y, true);
|
self.pointer_move(
|
||||||
|
tool.seat(),
|
||||||
|
CursorType::TabletTool(tool.id),
|
||||||
|
tool.cursor(),
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_on_tablet_tool_apply_changes(
|
fn node_on_tablet_tool_apply_changes(
|
||||||
|
|
@ -1492,10 +1749,10 @@ impl Node for ContainerNode {
|
||||||
y: Fixed,
|
y: Fixed,
|
||||||
) {
|
) {
|
||||||
let id = CursorType::TabletTool(tool.id);
|
let id = CursorType::TabletTool(tool.id);
|
||||||
self.pointer_move(id, tool.cursor(), x, y, false);
|
self.pointer_move(tool.seat(), id, tool.cursor(), x, y, false);
|
||||||
if let Some(changes) = changes {
|
if let Some(changes) = changes {
|
||||||
if let Some(pressed) = changes.down {
|
if let Some(pressed) = changes.down {
|
||||||
self.button(id, tool.seat(), time_usec, pressed);
|
self.button(id, tool.seat(), time_usec, pressed, BTN_LEFT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1891,6 +2148,31 @@ impl ToplevelNodeBase for ContainerNode {
|
||||||
fn tl_admits_children(&self) -> bool {
|
fn tl_admits_children(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tl_tile_drag_destination(
|
||||||
|
self: Rc<Self>,
|
||||||
|
source: NodeId,
|
||||||
|
_split: Option<ContainerSplit>,
|
||||||
|
abs_bounds: Rect,
|
||||||
|
abs_x: i32,
|
||||||
|
abs_y: i32,
|
||||||
|
) -> Option<TileDragDestination> {
|
||||||
|
self.tile_drag_destination(source, abs_bounds, abs_x, abs_y)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tl_tile_drag_bounds(&self, split: ContainerSplit, start: bool) -> i32 {
|
||||||
|
if split != self.split.get() {
|
||||||
|
return default_tile_drag_bounds(self, split);
|
||||||
|
}
|
||||||
|
let child = match start {
|
||||||
|
true => self.children.first(),
|
||||||
|
false => self.children.last(),
|
||||||
|
};
|
||||||
|
let Some(child) = child else {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
child.node.tl_tile_drag_bounds(split, start) / 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn direction_to_split(dir: Direction) -> (ContainerSplit, bool) {
|
fn direction_to_split(dir: Direction) -> (ContainerSplit, bool) {
|
||||||
|
|
@ -1902,3 +2184,118 @@ fn direction_to_split(dir: Direction) -> (ContainerSplit, bool) {
|
||||||
Direction::Unspecified => (ContainerSplit::Horizontal, true),
|
Direction::Unspecified => (ContainerSplit::Horizontal, true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tile_drag_destination_in_mono(
|
||||||
|
tl: Rc<dyn ToplevelNode>,
|
||||||
|
abs_bounds: Rect,
|
||||||
|
abs_x: i32,
|
||||||
|
abs_y: i32,
|
||||||
|
) -> TileDragDestination {
|
||||||
|
let mut x1 = abs_bounds.x1();
|
||||||
|
let mut x2 = abs_bounds.x2();
|
||||||
|
let mut y1 = abs_bounds.y1();
|
||||||
|
let mut y2 = abs_bounds.y2();
|
||||||
|
let dx = (x2 - x1) / 3;
|
||||||
|
let dy = (y2 - y1) / 3;
|
||||||
|
let mut split_before = true;
|
||||||
|
let mut split = ContainerSplit::Horizontal;
|
||||||
|
if abs_x < x1 + dx {
|
||||||
|
x2 = x1 + dx;
|
||||||
|
} else if abs_x > x2 - dx {
|
||||||
|
split_before = false;
|
||||||
|
x1 = x2 - dx;
|
||||||
|
} else {
|
||||||
|
split = ContainerSplit::Vertical;
|
||||||
|
x1 += dx;
|
||||||
|
x2 -= dx;
|
||||||
|
if abs_y < y1 + dy {
|
||||||
|
y2 = y1 + dy;
|
||||||
|
} else if abs_y > y2 - dy {
|
||||||
|
split_before = false;
|
||||||
|
y1 = y2 - dy;
|
||||||
|
} else {
|
||||||
|
let rect = Rect::new_unchecked(x1, y1 + dy, x2, y2 - dy);
|
||||||
|
return TileDragDestination {
|
||||||
|
highlight: rect,
|
||||||
|
ty: TddType::Replace(tl),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let rect = Rect::new_unchecked(x1, y1, x2, y2);
|
||||||
|
TileDragDestination {
|
||||||
|
highlight: rect,
|
||||||
|
ty: TddType::Split {
|
||||||
|
node: tl,
|
||||||
|
split,
|
||||||
|
before: split_before,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tile_drag_destination_in_split(
|
||||||
|
tl: Rc<dyn ToplevelNode>,
|
||||||
|
split: ContainerSplit,
|
||||||
|
abs_bounds: Rect,
|
||||||
|
mut abs_x: i32,
|
||||||
|
mut abs_y: i32,
|
||||||
|
) -> TileDragDestination {
|
||||||
|
let mut x1 = abs_bounds.x1();
|
||||||
|
let mut x2 = abs_bounds.x2();
|
||||||
|
let mut y1 = abs_bounds.y1();
|
||||||
|
let mut y2 = abs_bounds.y2();
|
||||||
|
macro_rules! swap {
|
||||||
|
() => {
|
||||||
|
if split == ContainerSplit::Horizontal {
|
||||||
|
mem::swap(&mut x1, &mut y1);
|
||||||
|
mem::swap(&mut x2, &mut y2);
|
||||||
|
mem::swap(&mut abs_x, &mut abs_y);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
swap!();
|
||||||
|
let mut split_before = false;
|
||||||
|
let mut split_after = false;
|
||||||
|
let dx = (x2 - x1) / 3;
|
||||||
|
if abs_x < x1 + dx {
|
||||||
|
split_before = true;
|
||||||
|
x2 = x1 + dx;
|
||||||
|
} else if abs_x < x2 - dx {
|
||||||
|
x1 += dx;
|
||||||
|
x2 -= dx;
|
||||||
|
} else {
|
||||||
|
split_after = true;
|
||||||
|
x1 = x2 - dx;
|
||||||
|
}
|
||||||
|
swap!();
|
||||||
|
let rect = Rect::new(x1, y1, x2, y2).unwrap();
|
||||||
|
let ty = if split_before || split_after {
|
||||||
|
TddType::Split {
|
||||||
|
node: tl,
|
||||||
|
split: split.other(),
|
||||||
|
before: split_before,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TddType::Replace(tl)
|
||||||
|
};
|
||||||
|
TileDragDestination {
|
||||||
|
highlight: rect,
|
||||||
|
ty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_tile_drag_destination(
|
||||||
|
tl: Rc<dyn ToplevelNode>,
|
||||||
|
source: NodeId,
|
||||||
|
split: Option<ContainerSplit>,
|
||||||
|
abs_bounds: Rect,
|
||||||
|
abs_x: i32,
|
||||||
|
abs_y: i32,
|
||||||
|
) -> Option<TileDragDestination> {
|
||||||
|
if tl.node_id() == source {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(match split {
|
||||||
|
None => tile_drag_destination_in_mono(tl, abs_bounds, abs_x, abs_y),
|
||||||
|
Some(s) => tile_drag_destination_in_split(tl, s, abs_bounds, abs_x, abs_y),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ use {
|
||||||
state::State,
|
state::State,
|
||||||
tree::{
|
tree::{
|
||||||
walker::NodeVisitor, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId,
|
walker::NodeVisitor, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId,
|
||||||
OutputNode, StackedNode,
|
OutputNode, StackedNode, TileDragDestination, WorkspaceDragDestination,
|
||||||
|
WorkspaceNodeId,
|
||||||
},
|
},
|
||||||
utils::{copyhashmap::CopyHashMap, linkedlist::LinkedList},
|
utils::{copyhashmap::CopyHashMap, linkedlist::LinkedList},
|
||||||
},
|
},
|
||||||
|
|
@ -83,6 +84,36 @@ impl DisplayNode {
|
||||||
state.damage(self.extents.get());
|
state.damage(self.extents.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tile_drag_destination(
|
||||||
|
&self,
|
||||||
|
source: NodeId,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
) -> Option<TileDragDestination> {
|
||||||
|
for output in self.outputs.lock().values() {
|
||||||
|
let pos = output.node_absolute_position();
|
||||||
|
if pos.contains(x, y) {
|
||||||
|
return output.tile_drag_destination(source, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn workspace_drag_destination(
|
||||||
|
&self,
|
||||||
|
source: WorkspaceNodeId,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
) -> Option<WorkspaceDragDestination> {
|
||||||
|
for output in self.outputs.lock().values() {
|
||||||
|
let pos = output.node_absolute_position();
|
||||||
|
if pos.contains(x, y) {
|
||||||
|
return output.workspace_drag_destination(source, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node for DisplayNode {
|
impl Node for DisplayNode {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ use {
|
||||||
text::TextTexture,
|
text::TextTexture,
|
||||||
tree::{
|
tree::{
|
||||||
walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FindTreeUsecase,
|
walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FindTreeUsecase,
|
||||||
FoundNode, Node, NodeId, StackedNode, ToplevelNode, WorkspaceNode,
|
FoundNode, Node, NodeId, StackedNode, TileDragDestination, ToplevelNode, WorkspaceNode,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
asyncevent::AsyncEvent, clonecell::CloneCell, double_click_state::DoubleClickState,
|
asyncevent::AsyncEvent, clonecell::CloneCell, double_click_state::DoubleClickState,
|
||||||
|
|
@ -528,6 +528,26 @@ impl FloatNode {
|
||||||
self.set_workspace(&ws);
|
self.set_workspace(&ws);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tile_drag_destination(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
source: NodeId,
|
||||||
|
abs_x: i32,
|
||||||
|
abs_y: i32,
|
||||||
|
) -> Option<TileDragDestination> {
|
||||||
|
let child = self.child.get()?;
|
||||||
|
let theme = &self.state.theme.sizes;
|
||||||
|
let bw = theme.border_width.get();
|
||||||
|
let th = theme.title_height.get();
|
||||||
|
let pos = self.position.get();
|
||||||
|
let body = Rect::new(
|
||||||
|
pos.x1() + bw,
|
||||||
|
pos.y1() + bw + th + 1,
|
||||||
|
pos.x2() - bw,
|
||||||
|
pos.y2() - bw,
|
||||||
|
)?;
|
||||||
|
child.tl_tile_drag_destination(source, None, body, abs_x, abs_y)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for FloatNode {
|
impl Debug for FloatNode {
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,8 @@ use {
|
||||||
text::TextTexture,
|
text::TextTexture,
|
||||||
tree::{
|
tree::{
|
||||||
walker::NodeVisitor, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node,
|
walker::NodeVisitor, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node,
|
||||||
NodeId, StackedNode, WorkspaceNode,
|
NodeId, StackedNode, TddType, TileDragDestination, WorkspaceDragDestination,
|
||||||
|
WorkspaceNode, WorkspaceNodeId,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap,
|
asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap,
|
||||||
|
|
@ -73,6 +74,7 @@ pub struct OutputNode {
|
||||||
pub status: CloneCell<Rc<String>>,
|
pub status: CloneCell<Rc<String>>,
|
||||||
pub scroll: Scroller,
|
pub scroll: Scroller,
|
||||||
pub pointer_positions: CopyHashMap<PointerType, (i32, i32)>,
|
pub pointer_positions: CopyHashMap<PointerType, (i32, i32)>,
|
||||||
|
pub pointer_down: CopyHashMap<SeatId, (i32, i32)>,
|
||||||
pub lock_surface: CloneCell<Option<Rc<ExtSessionLockSurfaceV1>>>,
|
pub lock_surface: CloneCell<Option<Rc<ExtSessionLockSurfaceV1>>>,
|
||||||
pub hardware_cursor: CloneCell<Option<Rc<dyn HardwareCursor>>>,
|
pub hardware_cursor: CloneCell<Option<Rc<dyn HardwareCursor>>>,
|
||||||
pub hardware_cursor_needs_render: Cell<bool>,
|
pub hardware_cursor_needs_render: Cell<bool>,
|
||||||
|
|
@ -522,6 +524,10 @@ impl OutputNode {
|
||||||
return ws;
|
return ws;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.generate_workspace()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_workspace(self: &Rc<Self>) -> Rc<WorkspaceNode> {
|
||||||
let name = 'name: {
|
let name = 'name: {
|
||||||
for i in 1.. {
|
for i in 1.. {
|
||||||
let name = i.to_string();
|
let name = i.to_string();
|
||||||
|
|
@ -854,6 +860,9 @@ impl OutputNode {
|
||||||
Some(p) => p,
|
Some(p) => p,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
if let PointerType::Seat(s) = id {
|
||||||
|
self.pointer_down.set(s, (x, y));
|
||||||
|
}
|
||||||
let (x, y) = self.non_exclusive_rect_rel.get().translate(x, y);
|
let (x, y) = self.non_exclusive_rect_rel.get().translate(x, y);
|
||||||
if y >= self.state.theme.sizes.title_height.get() {
|
if y >= self.state.theme.sizes.title_height.get() {
|
||||||
return;
|
return;
|
||||||
|
|
@ -937,6 +946,143 @@ impl OutputNode {
|
||||||
};
|
};
|
||||||
self.global.connector.connector.set_tearing_enabled(enabled);
|
self.global.connector.connector.set_tearing_enabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tile_drag_destination(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
source: NodeId,
|
||||||
|
x_abs: i32,
|
||||||
|
y_abs: i32,
|
||||||
|
) -> Option<TileDragDestination> {
|
||||||
|
if self.state.lock.locked.get() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
for stacked in self.state.root.stacked.rev_iter() {
|
||||||
|
let Some(float) = stacked.deref().clone().node_into_float() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if !float.node_visible() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let pos = float.node_absolute_position();
|
||||||
|
if !pos.contains(x_abs, y_abs) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return float.tile_drag_destination(source, x_abs, y_abs);
|
||||||
|
}
|
||||||
|
let rect = self.non_exclusive_rect.get();
|
||||||
|
if !rect.contains(x_abs, y_abs) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let Some(ws) = self.workspace.get() else {
|
||||||
|
return Some(TileDragDestination {
|
||||||
|
highlight: rect,
|
||||||
|
ty: TddType::NewWorkspace {
|
||||||
|
output: self.clone(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if ws.fullscreen.is_some() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let th = self.state.theme.sizes.title_height.get();
|
||||||
|
if y_abs < rect.y1() + th + 1 {
|
||||||
|
let rd = &*self.render_data.borrow();
|
||||||
|
let (x, _) = rect.translate(x_abs, y_abs);
|
||||||
|
let mut last_x2 = 0;
|
||||||
|
for t in &rd.titles {
|
||||||
|
if x < t.x2 {
|
||||||
|
return Some(TileDragDestination {
|
||||||
|
highlight: Rect::new_sized(rect.x1() + t.x1, rect.y1(), t.x2 - t.x1, th)?,
|
||||||
|
ty: TddType::MoveToWorkspace {
|
||||||
|
workspace: t.ws.clone(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
last_x2 = t.x2;
|
||||||
|
}
|
||||||
|
return Some(TileDragDestination {
|
||||||
|
highlight: Rect::new_sized(
|
||||||
|
rect.x1() + last_x2,
|
||||||
|
rect.y1(),
|
||||||
|
rect.x2() - last_x2,
|
||||||
|
th,
|
||||||
|
)?,
|
||||||
|
ty: TddType::MoveToNewWorkspace {
|
||||||
|
output: self.clone(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let thp1 = self.state.theme.sizes.title_height.get() + 1;
|
||||||
|
let rect = Rect::new(rect.x1(), rect.y1() + thp1, rect.x2(), rect.y2())?;
|
||||||
|
if !rect.contains(x_abs, y_abs) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let Some(c) = ws.container.get() else {
|
||||||
|
return Some(TileDragDestination {
|
||||||
|
highlight: rect,
|
||||||
|
ty: TddType::NewContainer { workspace: ws },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
c.tile_drag_destination(source, rect, x_abs, y_abs)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn workspace_drag_destination(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
source: WorkspaceNodeId,
|
||||||
|
x_abs: i32,
|
||||||
|
y_abs: i32,
|
||||||
|
) -> Option<WorkspaceDragDestination> {
|
||||||
|
let rect = self.non_exclusive_rect.get();
|
||||||
|
if !rect.contains(x_abs, y_abs) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let th = self.state.theme.sizes.title_height.get();
|
||||||
|
if y_abs - rect.y1() > th + 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let rd = &*self.render_data.borrow();
|
||||||
|
let (x, _) = rect.translate(x_abs, y_abs);
|
||||||
|
let mut prev_is_source = false;
|
||||||
|
let mut prev_center = 0;
|
||||||
|
for t in &rd.titles {
|
||||||
|
if t.ws.id == source {
|
||||||
|
prev_is_source = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let center = (t.x1 + t.x2) / 2;
|
||||||
|
if x < center {
|
||||||
|
return if prev_is_source {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(WorkspaceDragDestination {
|
||||||
|
highlight: Rect::new_sized(
|
||||||
|
rect.x1() + prev_center,
|
||||||
|
rect.y1(),
|
||||||
|
center - prev_center,
|
||||||
|
th,
|
||||||
|
)?,
|
||||||
|
output: self.clone(),
|
||||||
|
before: Some(t.ws.clone()),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
prev_center = center;
|
||||||
|
prev_is_source = false;
|
||||||
|
}
|
||||||
|
if prev_is_source {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
return Some(WorkspaceDragDestination {
|
||||||
|
highlight: Rect::new_sized(
|
||||||
|
rect.x1() + prev_center,
|
||||||
|
rect.y1(),
|
||||||
|
rect.x2() - prev_center,
|
||||||
|
th,
|
||||||
|
)?,
|
||||||
|
output: self.clone(),
|
||||||
|
before: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OutputTitle {
|
pub struct OutputTitle {
|
||||||
|
|
@ -1131,7 +1277,11 @@ impl Node for OutputNode {
|
||||||
state: KeyState,
|
state: KeyState,
|
||||||
_serial: u32,
|
_serial: u32,
|
||||||
) {
|
) {
|
||||||
if state != KeyState::Pressed || button != BTN_LEFT {
|
if button != BTN_LEFT {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if state != KeyState::Pressed {
|
||||||
|
self.pointer_down.remove(&seat.id());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.button(PointerType::Seat(seat.id()));
|
self.button(PointerType::Seat(seat.id()));
|
||||||
|
|
@ -1175,6 +1325,10 @@ impl Node for OutputNode {
|
||||||
self.state.tree_changed();
|
self.state.tree_changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn node_on_leave(&self, seat: &WlSeatGlobal) {
|
||||||
|
self.pointer_down.remove(&seat.id());
|
||||||
|
}
|
||||||
|
|
||||||
fn node_on_pointer_enter(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, x: Fixed, y: Fixed) {
|
fn node_on_pointer_enter(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, x: Fixed, y: Fixed) {
|
||||||
self.pointer_move(PointerType::Seat(seat.id()), x, y);
|
self.pointer_move(PointerType::Seat(seat.id()), x, y);
|
||||||
}
|
}
|
||||||
|
|
@ -1186,6 +1340,22 @@ impl Node for OutputNode {
|
||||||
|
|
||||||
fn node_on_pointer_motion(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, x: Fixed, y: Fixed) {
|
fn node_on_pointer_motion(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, x: Fixed, y: Fixed) {
|
||||||
self.pointer_move(PointerType::Seat(seat.id()), x, y);
|
self.pointer_move(PointerType::Seat(seat.id()), x, y);
|
||||||
|
if let Some((down_x, down_y)) = self.pointer_down.get(&seat.id()) {
|
||||||
|
if self
|
||||||
|
.state
|
||||||
|
.ui_drag_threshold_reached((x.round_down(), y.round_down()), (down_x, down_y))
|
||||||
|
{
|
||||||
|
let rd = self.render_data.borrow_mut();
|
||||||
|
for title in &rd.titles {
|
||||||
|
if down_x >= title.x1 && down_x < title.x2 {
|
||||||
|
let ws = title.ws.clone();
|
||||||
|
drop(rd);
|
||||||
|
seat.start_workspace_drag(&ws);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_on_tablet_tool_leave(&self, tool: &Rc<TabletTool>, _time_usec: u64) {
|
fn node_on_tablet_tool_leave(&self, tool: &Rc<TabletTool>, _time_usec: u64) {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ use {
|
||||||
state::State,
|
state::State,
|
||||||
text::TextTexture,
|
text::TextTexture,
|
||||||
tree::{
|
tree::{
|
||||||
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor,
|
default_tile_drag_destination, ContainerSplit, Direction, FindTreeResult,
|
||||||
|
FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor, TileDragDestination,
|
||||||
ToplevelData, ToplevelNode, ToplevelNodeBase,
|
ToplevelData, ToplevelNode, ToplevelNodeBase,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
|
|
@ -62,6 +63,17 @@ impl PlaceholderNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_empty(state: &Rc<State>) -> Self {
|
||||||
|
Self {
|
||||||
|
id: state.node_ids.next(),
|
||||||
|
toplevel: ToplevelData::new(state, String::new(), None),
|
||||||
|
destroyed: Default::default(),
|
||||||
|
update_textures_scheduled: Default::default(),
|
||||||
|
state: state.clone(),
|
||||||
|
textures: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_destroyed(&self) -> bool {
|
pub fn is_destroyed(&self) -> bool {
|
||||||
self.destroyed.get()
|
self.destroyed.get()
|
||||||
}
|
}
|
||||||
|
|
@ -222,4 +234,15 @@ impl ToplevelNodeBase for PlaceholderNode {
|
||||||
fn tl_admits_children(&self) -> bool {
|
fn tl_admits_children(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tl_tile_drag_destination(
|
||||||
|
self: Rc<Self>,
|
||||||
|
source: NodeId,
|
||||||
|
split: Option<ContainerSplit>,
|
||||||
|
abs_bounds: Rect,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
) -> Option<TileDragDestination> {
|
||||||
|
default_tile_drag_destination(self, source, split, abs_bounds, x, y)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,10 @@ use {
|
||||||
},
|
},
|
||||||
rect::Rect,
|
rect::Rect,
|
||||||
state::State,
|
state::State,
|
||||||
tree::{ContainingNode, Direction, Node, OutputNode, PlaceholderNode, WorkspaceNode},
|
tree::{
|
||||||
|
ContainerNode, ContainerSplit, ContainingNode, Direction, Node, NodeId, OutputNode,
|
||||||
|
PlaceholderNode, WorkspaceNode,
|
||||||
|
},
|
||||||
utils::{
|
utils::{
|
||||||
clonecell::CloneCell,
|
clonecell::CloneCell,
|
||||||
copyhashmap::CopyHashMap,
|
copyhashmap::CopyHashMap,
|
||||||
|
|
@ -202,6 +205,20 @@ pub trait ToplevelNodeBase: Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tl_admits_children(&self) -> bool;
|
fn tl_admits_children(&self) -> bool;
|
||||||
|
|
||||||
|
fn tl_tile_drag_destination(
|
||||||
|
self: Rc<Self>,
|
||||||
|
source: NodeId,
|
||||||
|
split: Option<ContainerSplit>,
|
||||||
|
abs_bounds: Rect,
|
||||||
|
abs_x: i32,
|
||||||
|
abs_y: i32,
|
||||||
|
) -> Option<TileDragDestination>;
|
||||||
|
|
||||||
|
fn tl_tile_drag_bounds(&self, split: ContainerSplit, start: bool) -> i32 {
|
||||||
|
let _ = start;
|
||||||
|
default_tile_drag_bounds(self, split)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FullscreenedData {
|
pub struct FullscreenedData {
|
||||||
|
|
@ -532,3 +549,42 @@ impl ToplevelData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct TileDragDestination {
|
||||||
|
pub highlight: Rect,
|
||||||
|
pub ty: TddType,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum TddType {
|
||||||
|
Replace(Rc<dyn ToplevelNode>),
|
||||||
|
Split {
|
||||||
|
node: Rc<dyn ToplevelNode>,
|
||||||
|
split: ContainerSplit,
|
||||||
|
before: bool,
|
||||||
|
},
|
||||||
|
Insert {
|
||||||
|
container: Rc<ContainerNode>,
|
||||||
|
neighbor: Rc<dyn ToplevelNode>,
|
||||||
|
before: bool,
|
||||||
|
},
|
||||||
|
NewWorkspace {
|
||||||
|
output: Rc<OutputNode>,
|
||||||
|
},
|
||||||
|
NewContainer {
|
||||||
|
workspace: Rc<WorkspaceNode>,
|
||||||
|
},
|
||||||
|
MoveToWorkspace {
|
||||||
|
workspace: Rc<WorkspaceNode>,
|
||||||
|
},
|
||||||
|
MoveToNewWorkspace {
|
||||||
|
output: Rc<OutputNode>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_tile_drag_bounds<T: ToplevelNodeBase + ?Sized>(t: &T, split: ContainerSplit) -> i32 {
|
||||||
|
const FACTOR: i32 = 5;
|
||||||
|
match split {
|
||||||
|
ContainerSplit::Horizontal => t.node_absolute_position().width() / FACTOR,
|
||||||
|
ContainerSplit::Vertical => t.node_absolute_position().height() / FACTOR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -379,8 +379,10 @@ impl ContainingNode for WorkspaceNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WsMoveConfig {
|
pub struct WsMoveConfig {
|
||||||
|
pub make_visible_always: bool,
|
||||||
pub make_visible_if_empty: bool,
|
pub make_visible_if_empty: bool,
|
||||||
pub source_is_destroyed: bool,
|
pub source_is_destroyed: bool,
|
||||||
|
pub before: Option<Rc<WorkspaceNode>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_ws_to_output(
|
pub fn move_ws_to_output(
|
||||||
|
|
@ -390,8 +392,19 @@ pub fn move_ws_to_output(
|
||||||
) {
|
) {
|
||||||
let source = ws.output.get();
|
let source = ws.output.get();
|
||||||
ws.set_output(&target);
|
ws.set_output(&target);
|
||||||
target.workspaces.add_last_existing(&ws);
|
'link: {
|
||||||
if config.make_visible_if_empty && target.workspace.is_none() && !target.is_dummy {
|
if let Some(before) = config.before {
|
||||||
|
if let Some(link) = &*before.output_link.borrow() {
|
||||||
|
link.prepend_existing(ws);
|
||||||
|
break 'link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target.workspaces.add_last_existing(&ws);
|
||||||
|
}
|
||||||
|
let make_visible = !target.is_dummy
|
||||||
|
&& (config.make_visible_always
|
||||||
|
|| (config.make_visible_if_empty && target.workspace.is_none()));
|
||||||
|
if make_visible {
|
||||||
target.show_workspace(&ws);
|
target.show_workspace(&ws);
|
||||||
} else {
|
} else {
|
||||||
ws.set_visible(false);
|
ws.set_visible(false);
|
||||||
|
|
@ -423,3 +436,9 @@ pub fn move_ws_to_output(
|
||||||
target.state.damage(target.global.pos.get());
|
target.state.damage(target.global.pos.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct WorkspaceDragDestination {
|
||||||
|
pub highlight: Rect,
|
||||||
|
pub output: Rc<OutputNode>,
|
||||||
|
pub before: Option<Rc<WorkspaceNode>>,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,12 @@ pub struct Status {
|
||||||
pub separator: Option<String>,
|
pub separator: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct UiDrag {
|
||||||
|
pub enabled: Option<bool>,
|
||||||
|
pub threshold: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum OutputMatch {
|
pub enum OutputMatch {
|
||||||
Any(Vec<OutputMatch>),
|
Any(Vec<OutputMatch>),
|
||||||
|
|
@ -342,6 +348,7 @@ pub struct Config {
|
||||||
pub vrr: Option<Vrr>,
|
pub vrr: Option<Vrr>,
|
||||||
pub tearing: Option<Tearing>,
|
pub tearing: Option<Tearing>,
|
||||||
pub libei: Libei,
|
pub libei: Libei,
|
||||||
|
pub ui_drag: UiDrag,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ pub mod shortcuts;
|
||||||
mod status;
|
mod status;
|
||||||
mod tearing;
|
mod tearing;
|
||||||
mod theme;
|
mod theme;
|
||||||
|
mod ui_drag;
|
||||||
mod vrr;
|
mod vrr;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,11 @@ use {
|
||||||
status::StatusParser,
|
status::StatusParser,
|
||||||
tearing::TearingParser,
|
tearing::TearingParser,
|
||||||
theme::ThemeParser,
|
theme::ThemeParser,
|
||||||
|
ui_drag::UiDragParser,
|
||||||
vrr::VrrParser,
|
vrr::VrrParser,
|
||||||
},
|
},
|
||||||
spanned::SpannedErrorExt,
|
spanned::SpannedErrorExt,
|
||||||
Action, Config, Libei, Theme,
|
Action, Config, Libei, Theme, UiDrag,
|
||||||
},
|
},
|
||||||
toml::{
|
toml::{
|
||||||
toml_span::{DespanExt, Span, Spanned},
|
toml_span::{DespanExt, Span, Spanned},
|
||||||
|
|
@ -112,6 +113,7 @@ impl Parser for ConfigParser<'_> {
|
||||||
vrr_val,
|
vrr_val,
|
||||||
tearing_val,
|
tearing_val,
|
||||||
libei_val,
|
libei_val,
|
||||||
|
ui_drag_val,
|
||||||
),
|
),
|
||||||
) = ext.extract((
|
) = ext.extract((
|
||||||
(
|
(
|
||||||
|
|
@ -147,6 +149,7 @@ impl Parser for ConfigParser<'_> {
|
||||||
opt(val("vrr")),
|
opt(val("vrr")),
|
||||||
opt(val("tearing")),
|
opt(val("tearing")),
|
||||||
opt(val("libei")),
|
opt(val("libei")),
|
||||||
|
opt(val("ui-drag")),
|
||||||
),
|
),
|
||||||
))?;
|
))?;
|
||||||
let mut keymap = None;
|
let mut keymap = None;
|
||||||
|
|
@ -338,6 +341,15 @@ impl Parser for ConfigParser<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut ui_drag = UiDrag::default();
|
||||||
|
if let Some(value) = ui_drag_val {
|
||||||
|
match value.parse(&mut UiDragParser(self.0)) {
|
||||||
|
Ok(v) => ui_drag = v,
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse ui-drag setting: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(Config {
|
Ok(Config {
|
||||||
keymap,
|
keymap,
|
||||||
repeat_rate,
|
repeat_rate,
|
||||||
|
|
@ -365,6 +377,7 @@ impl Parser for ConfigParser<'_> {
|
||||||
vrr,
|
vrr,
|
||||||
tearing,
|
tearing,
|
||||||
libei,
|
libei,
|
||||||
|
ui_drag,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
49
toml-config/src/config/parsers/ui_drag.rs
Normal file
49
toml-config/src/config/parsers/ui_drag.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
extractor::{bol, int, opt, recover, Extractor, ExtractorError},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
parsers::exec::ExecParserError,
|
||||||
|
UiDrag,
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{DespanExt, Span, Spanned},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum UiDragParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Exec(#[from] ExecParserError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extract(#[from] ExtractorError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UiDragParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl Parser for UiDragParser<'_> {
|
||||||
|
type Value = UiDrag;
|
||||||
|
type Error = UiDragParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
|
let (enabled, threshold) =
|
||||||
|
ext.extract((recover(opt(bol("enabled"))), recover(opt(int("threshold")))))?;
|
||||||
|
Ok(UiDrag {
|
||||||
|
enabled: enabled.despan(),
|
||||||
|
threshold: threshold.despan().map(|v| v as i32),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,7 +24,7 @@ use {
|
||||||
keyboard::{Keymap, ModifiedKeySym},
|
keyboard::{Keymap, ModifiedKeySym},
|
||||||
logging::set_log_level,
|
logging::set_log_level,
|
||||||
on_devices_enumerated, on_idle, quit, reload, set_default_workspace_capture,
|
on_devices_enumerated, on_idle, quit, reload, set_default_workspace_capture,
|
||||||
set_explicit_sync_enabled, set_idle,
|
set_explicit_sync_enabled, set_idle, set_ui_drag_enabled, set_ui_drag_threshold,
|
||||||
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
|
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
|
||||||
switch_to_vt,
|
switch_to_vt,
|
||||||
theme::{reset_colors, reset_font, reset_sizes, set_font},
|
theme::{reset_colors, reset_font, reset_sizes, set_font},
|
||||||
|
|
@ -1055,6 +1055,12 @@ fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
set_libei_socket_enabled(config.libei.enable_socket.unwrap_or(false));
|
set_libei_socket_enabled(config.libei.enable_socket.unwrap_or(false));
|
||||||
|
if let Some(enabled) = config.ui_drag.enabled {
|
||||||
|
set_ui_drag_enabled(enabled);
|
||||||
|
}
|
||||||
|
if let Some(threshold) = config.ui_drag.threshold {
|
||||||
|
set_ui_drag_threshold(threshold);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_command(exec: &Exec) -> Command {
|
fn create_command(exec: &Exec) -> Command {
|
||||||
|
|
|
||||||
|
|
@ -589,6 +589,10 @@
|
||||||
"libei": {
|
"libei": {
|
||||||
"description": "Configures the libei settings.\n\n- Example:\n\n ```toml\n libei.enable-socket = true\n ```\n",
|
"description": "Configures the libei settings.\n\n- Example:\n\n ```toml\n libei.enable-socket = true\n ```\n",
|
||||||
"$ref": "#/$defs/Libei"
|
"$ref": "#/$defs/Libei"
|
||||||
|
},
|
||||||
|
"ui-drag": {
|
||||||
|
"description": "Configures the ui-drag settings.\n\n- Example:\n\n ```toml\n ui-drag = { enabled = false, threshold = 20 }\n ```\n",
|
||||||
|
"$ref": "#/$defs/UiDrag"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
|
|
@ -1342,6 +1346,21 @@
|
||||||
"flip-rotate-270"
|
"flip-rotate-270"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"UiDrag": {
|
||||||
|
"description": "Describes ui-drag settings.\n\n- Example:\n\n ```toml\n ui-drag = { enabled = false, threshold = 20 }\n ```\n",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Enables or disables dragging of tiles and workspaces.\n\nThe default is `true`.\n"
|
||||||
|
},
|
||||||
|
"threshold": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Sets the distance at which ui dragging starts.\n\nThe default is `10`.\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": []
|
||||||
|
},
|
||||||
"Vrr": {
|
"Vrr": {
|
||||||
"description": "Describes VRR settings.\n\n- Example:\n\n ```toml\n vrr = { mode = \"always\", cursor-hz = 90 }\n ```\n",
|
"description": "Describes VRR settings.\n\n- Example:\n\n ```toml\n vrr = { mode = \"always\", cursor-hz = 90 }\n ```\n",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
|
||||||
|
|
@ -1154,6 +1154,18 @@ The table has the following fields:
|
||||||
|
|
||||||
The value of this field should be a [Libei](#types-Libei).
|
The value of this field should be a [Libei](#types-Libei).
|
||||||
|
|
||||||
|
- `ui-drag` (optional):
|
||||||
|
|
||||||
|
Configures the ui-drag settings.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
ui-drag = { enabled = false, threshold = 20 }
|
||||||
|
```
|
||||||
|
|
||||||
|
The value of this field should be a [UiDrag](#types-UiDrag).
|
||||||
|
|
||||||
|
|
||||||
<a name="types-Connector"></a>
|
<a name="types-Connector"></a>
|
||||||
### `Connector`
|
### `Connector`
|
||||||
|
|
@ -2981,6 +2993,40 @@ The string should have one of the following values:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="types-UiDrag"></a>
|
||||||
|
### `UiDrag`
|
||||||
|
|
||||||
|
Describes ui-drag settings.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
ui-drag = { enabled = false, threshold = 20 }
|
||||||
|
```
|
||||||
|
|
||||||
|
Values of this type should be tables.
|
||||||
|
|
||||||
|
The table has the following fields:
|
||||||
|
|
||||||
|
- `enabled` (optional):
|
||||||
|
|
||||||
|
Enables or disables dragging of tiles and workspaces.
|
||||||
|
|
||||||
|
The default is `true`.
|
||||||
|
|
||||||
|
The value of this field should be a boolean.
|
||||||
|
|
||||||
|
- `threshold` (optional):
|
||||||
|
|
||||||
|
Sets the distance at which ui dragging starts.
|
||||||
|
|
||||||
|
The default is `10`.
|
||||||
|
|
||||||
|
The value of this field should be a number.
|
||||||
|
|
||||||
|
The numbers should be integers.
|
||||||
|
|
||||||
|
|
||||||
<a name="types-Vrr"></a>
|
<a name="types-Vrr"></a>
|
||||||
### `Vrr`
|
### `Vrr`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2264,6 +2264,17 @@ Config:
|
||||||
```toml
|
```toml
|
||||||
libei.enable-socket = true
|
libei.enable-socket = true
|
||||||
```
|
```
|
||||||
|
ui-drag:
|
||||||
|
ref: UiDrag
|
||||||
|
required: false
|
||||||
|
description: |
|
||||||
|
Configures the ui-drag settings.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
ui-drag = { enabled = false, threshold = 20 }
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
Idle:
|
Idle:
|
||||||
|
|
@ -2588,3 +2599,31 @@ Format:
|
||||||
description: ""
|
description: ""
|
||||||
- value: xbgr16161616f
|
- value: xbgr16161616f
|
||||||
description: ""
|
description: ""
|
||||||
|
|
||||||
|
|
||||||
|
UiDrag:
|
||||||
|
kind: table
|
||||||
|
description: |
|
||||||
|
Describes ui-drag settings.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
ui-drag = { enabled = false, threshold = 20 }
|
||||||
|
```
|
||||||
|
fields:
|
||||||
|
enabled:
|
||||||
|
kind: boolean
|
||||||
|
required: false
|
||||||
|
description: |
|
||||||
|
Enables or disables dragging of tiles and workspaces.
|
||||||
|
|
||||||
|
The default is `true`.
|
||||||
|
threshold:
|
||||||
|
kind: number
|
||||||
|
integer_only: true
|
||||||
|
required: false
|
||||||
|
description: |
|
||||||
|
Sets the distance at which ui dragging starts.
|
||||||
|
|
||||||
|
The default is `10`.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue