1
0
Fork 0
forked from wry/wry

tree: implement tile dragging

This commit is contained in:
Julian Orth 2024-09-30 18:31:19 +02:00
parent 83fd9f211e
commit 132986df2a
17 changed files with 925 additions and 52 deletions

View file

@ -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() {
drag.render(&mut renderer, &rect, x, y);
}

View file

@ -171,6 +171,7 @@ pub struct WlSeatGlobal {
cursor_user_group: Rc<CursorUserGroup>,
pointer_cursor: Rc<CursorUser>,
tree_changed: Rc<AsyncEvent>,
tree_changed_needs_layout: Cell<bool>,
selection: CloneCell<Option<Rc<dyn DynDataSource>>>,
selection_serial: Cell<u32>,
primary_selection: CloneCell<Option<Rc<dyn DynDataSource>>>,
@ -198,6 +199,7 @@ pub struct WlSeatGlobal {
hold_bindings: PerClientBindings<ZwpPointerGestureHoldV1>,
tablet: TabletSeatData,
ei_seats: CopyHashMap<(ClientId, EiSeatId), Rc<EiSeat>>,
ui_drag_highlight: Cell<Option<Rect>>,
}
const CHANGE_CURSOR_MOVED: u32 = 1 << 0;
@ -239,6 +241,7 @@ impl WlSeatGlobal {
cursor_user_group,
pointer_cursor: cursor_user,
tree_changed: Default::default(),
tree_changed_needs_layout: Default::default(),
selection: Default::default(),
selection_serial: Cell::new(0),
primary_selection: Default::default(),
@ -267,12 +270,16 @@ impl WlSeatGlobal {
hold_bindings: Default::default(),
tablet: Default::default(),
ei_seats: Default::default(),
ui_drag_highlight: Default::default(),
});
slf.pointer_cursor.set_owner(slf.clone());
let seat = slf.clone();
let future = state.eng.spawn("seat handler", async move {
loop {
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.changes.or_assign(CHANGE_TREE);
// log::info!("tree_changed");
@ -314,6 +321,10 @@ impl WlSeatGlobal {
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>) {
let mut dd = self.data_devices.borrow_mut();
dd.entry(device.client.id)
@ -764,6 +775,10 @@ impl WlSeatGlobal {
.start_drag(self, origin, source, icon, serial)
}
pub fn start_tile_drag(self: &Rc<Self>, tl: &Rc<dyn ToplevelNode>) {
self.pointer_owner.start_tile_drag(self, tl);
}
pub fn cancel_dnd(self: &Rc<Self>) {
self.pointer_owner.cancel_dnd(self);
}

View file

@ -64,6 +64,7 @@ pub struct NodeSeatState {
dnd_targets: SmallMap<SeatId, Rc<WlSeatGlobal>, 1>,
tablet_pad_foci: SmallMap<TabletPadId, Rc<TabletPad>, 1>,
tablet_tool_foci: SmallMap<TabletToolId, Rc<TabletTool>, 1>,
ui_drags: SmallMap<SeatId, Rc<WlSeatGlobal>, 1>,
}
impl NodeSeatState {
@ -101,6 +102,14 @@ impl NodeSeatState {
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>) {
self.tablet_pad_foci.insert(pad.id, pad.clone());
}
@ -176,6 +185,9 @@ impl NodeSeatState {
while let Some((_, seat)) = self.pointer_grabs.pop() {
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();
while let Some((_, seat)) = self.dnd_targets.pop() {
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");
if needs_layout {
self.tree_changed_needs_layout.set(true);
}
self.tree_changed.trigger();
}

View file

@ -7,13 +7,17 @@ use {
ipc,
ipc::wl_data_source::WlDataSource,
wl_seat::{
wl_pointer::PendingScroll, Dnd, DroppedDnd, WlSeatError, WlSeatGlobal, BTN_LEFT,
BTN_RIGHT, CHANGE_CURSOR_MOVED, CHANGE_TREE,
wl_pointer::PendingScroll, Dnd, DroppedDnd, NodeSeatState, WlSeatError,
WlSeatGlobal, BTN_LEFT, BTN_RIGHT, CHANGE_CURSOR_MOVED, CHANGE_TREE,
},
wl_surface::{dnd_icon::DndIcon, WlSurface},
xdg_toplevel_drag_v1::XdgToplevelDragV1,
},
tree::{ContainingNode, FindTreeUsecase, FoundNode, Node, ToplevelNode, WorkspaceNode},
rect::Rect,
tree::{
ContainerNode, ContainerSplit, ContainingNode, FindTreeUsecase, FoundNode, Node,
PlaceholderNode, TddType, ToplevelNode, WorkspaceNode,
},
utils::{clonecell::CloneCell, smallmap::SmallMap},
},
std::{
@ -173,7 +177,7 @@ impl PointerOwnerHolder {
usecase.node_focus(seat, node);
}
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) {
@ -202,11 +206,18 @@ impl PointerOwnerHolder {
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);
}
}
trait PointerOwner {
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 start_drag(
&self,
@ -249,6 +260,10 @@ trait PointerOwner {
fn disable_window_management(&self, seat: &Rc<WlSeatGlobal>) {
let _ = seat;
}
fn start_tile_drag(&self, seat: &Rc<WlSeatGlobal>, tl: &Rc<dyn ToplevelNode>) {
let _ = seat;
let _ = tl;
}
}
struct SimplePointerOwner<T> {
@ -386,7 +401,7 @@ impl<T: SimplePointerOwnerUsecase> PointerOwner for SimplePointerOwner<T> {
fn revert_to_default(&self, seat: &Rc<WlSeatGlobal>) {
if !T::IS_DEFAULT {
seat.pointer_owner.set_default_pointer_owner(seat);
seat.trigger_tree_changed();
seat.trigger_tree_changed(false);
}
}
@ -458,6 +473,10 @@ impl<T: SimplePointerOwnerUsecase> PointerOwner for SimpleGrabPointerOwner<T> {
self.node.node_seat_state().remove_pointer_grab(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);
}
}
impl PointerOwner for DndPointerOwner {
@ -496,10 +515,6 @@ impl PointerOwner for DndPointerOwner {
}
}
fn axis_node(&self, _seat: &Rc<WlSeatGlobal>) -> Option<Rc<dyn Node>> {
None
}
fn apply_changes(&self, seat: &Rc<WlSeatGlobal>) {
let (x, y) = seat.pointer_cursor.position();
let (x_int, y_int) = (x.round_down(), y.round_down());
@ -620,6 +635,43 @@ trait SimplePointerOwnerUsecase: Sized + Clone + 'static {
fn disable_window_management(&self, seat: &Rc<WlSeatGlobal>) {
let _ = seat;
}
fn start_tile_drag(
&self,
grab: &SimpleGrabPointerOwner<Self>,
seat: &Rc<WlSeatGlobal>,
tl: &Rc<dyn ToplevelNode>,
) {
let _ = grab;
let _ = seat;
let _ = tl;
}
}
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 {
@ -680,14 +732,7 @@ impl SimplePointerOwnerUsecase for DefaultPointerUsecase {
pos_x: Cell::new(Fixed::from_int(0)),
pos_y: Cell::new(Fixed::from_int(0)),
});
{
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);
self.prepare_new_usecase(grab, seat);
// {
// let old = seat.keyboard_node.set(seat.state.root.clone());
// old.seat_state().unfocus(seat);
@ -701,6 +746,22 @@ impl SimplePointerOwnerUsecase for DefaultPointerUsecase {
fn release_grab(&self, seat: &Rc<WlSeatGlobal>) {
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(),
},
);
}
}
trait NodeSelectorUsecase: Sized + 'static {
@ -945,10 +1006,6 @@ where
self.grab_node_removed(seat);
}
fn axis_node(&self, _seat: &Rc<WlSeatGlobal>) -> Option<Rc<dyn Node>> {
None
}
fn apply_changes(&self, seat: &Rc<WlSeatGlobal>) {
let Some(parent) = self.tl.tl_data().parent.get() else {
return;
@ -1050,3 +1107,179 @@ 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)
}
}
}
}

View file

@ -11,8 +11,9 @@ use {
renderer::Renderer,
state::State,
tree::{
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor,
StackedNode, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode,
default_tile_drag_destination, ContainerSplit, Direction, FindTreeResult,
FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor, StackedNode,
TileDragDestination, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode,
},
utils::{clonecell::CloneCell, copyhashmap::CopyHashMap, linkedlist::LinkedNode},
wire::WlSurfaceId,
@ -467,6 +468,17 @@ impl ToplevelNodeBase for Xwindow {
fn tl_admits_children(&self) -> bool {
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 {

View file

@ -25,9 +25,9 @@ use {
renderer::Renderer,
state::State,
tree::{
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor,
OutputNode, ToplevelData, ToplevelNode, ToplevelNodeBase, ToplevelNodeId,
WorkspaceNode,
default_tile_drag_destination, ContainerSplit, Direction, FindTreeResult,
FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor, OutputNode, TileDragDestination,
ToplevelData, ToplevelNode, ToplevelNodeBase, ToplevelNodeId, WorkspaceNode,
},
utils::{clonecell::CloneCell, hash_map_ext::HashMapExt},
wire::{xdg_toplevel::*, XdgToplevelId},
@ -667,6 +667,17 @@ impl ToplevelNodeBase for XdgToplevel {
fn tl_admits_children(&self) -> bool {
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 {

View file

@ -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)]
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 {
raw: RectRaw { x1, y1, x2, y2 },
}

View file

@ -70,9 +70,9 @@ fn subtract1() {
#[test]
fn rects_to_bands() {
let rects = [
Rect::new_unchecked(0, 0, 10, 10),
Rect::new_unchecked(5, 0, 30, 10),
Rect::new_unchecked(30, 5, 50, 15),
Rect::new_unchecked_danger(0, 0, 10, 10),
Rect::new_unchecked_danger(5, 0, 30, 10),
Rect::new_unchecked_danger(30, 5, 50, 15),
];
let r = Region::from_rects(&rects[..]);
// println!("{:#?}", r.rects);
@ -104,8 +104,8 @@ fn rects_to_bands() {
#[test]
fn rects_to_bands2() {
let rects = [
Rect::new_unchecked(0, 0, 10, 10),
Rect::new_unchecked(0, 10, 10, 20),
Rect::new_unchecked_danger(0, 0, 10, 10),
Rect::new_unchecked_danger(0, 10, 10, 20),
];
let r = Region::from_rects(&rects[..]);
// println!("{:#?}", r.rects);

View file

@ -317,11 +317,11 @@ impl Renderer<'_> {
render_highlight: bool,
) {
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 {
return;
}
@ -333,6 +333,12 @@ impl Renderer<'_> {
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>) {
let (x, y) = self.base.scale_point(x, y);
self.render_surface_scaled(surface, x, y, None, bounds, false);

View file

@ -586,7 +586,7 @@ impl State {
}
let seats = self.globals.seats.lock();
for seat in seats.values() {
seat.trigger_tree_changed();
seat.trigger_tree_changed(false);
}
}

View file

@ -508,7 +508,6 @@ pub trait Node: 'static {
// TYPE CONVERTERS
#[cfg_attr(not(feature = "it"), expect(dead_code))]
fn node_into_float(self: Rc<Self>) -> Option<Rc<FloatNode>> {
None
}

View file

@ -17,8 +17,9 @@ use {
state::State,
text::TextTexture,
tree::{
walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FindTreeUsecase,
FoundNode, Node, NodeId, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode,
default_tile_drag_bounds, walker::NodeVisitor, ContainingNode, Direction,
FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, TddType, TileDragDestination,
ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode,
},
utils::{
asyncevent::AsyncEvent,
@ -53,6 +54,15 @@ pub enum ContainerSplit {
Vertical,
}
impl ContainerSplit {
pub fn other(self) -> Self {
match self {
ContainerSplit::Horizontal => ContainerSplit::Vertical,
ContainerSplit::Vertical => ContainerSplit::Horizontal,
}
}
}
impl From<Axis> for ContainerSplit {
fn from(a: Axis) -> Self {
match a {
@ -547,6 +557,7 @@ impl ContainerNode {
fn pointer_move(
self: &Rc<Self>,
seat: &Rc<WlSeatGlobal>,
id: CursorType,
cursor: &CursorUser,
x: Fixed,
@ -574,7 +585,16 @@ impl ContainerNode {
if let Some(op) = &seat_state.op {
match op.kind {
SeatOpKind::Move => {
// todo
if let CursorType::Seat(_) = id {
const DRAG_DIST: i32 = 10;
let dx = x - op.x;
let dy = y - op.y;
if dx * dx + dy * dy > DRAG_DIST * DRAG_DIST {
let node = op.child.node.clone();
drop(seats);
seat.start_tile_drag(&node);
}
}
}
SeatOpKind::Resize {
dist_left,
@ -1233,20 +1253,220 @@ impl ContainerNode {
seat.set_tl_floating(child.node.clone(), true);
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 {
let op = seat_data.op.take().unwrap();
seat_data.op = None;
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 {
child: NodeRef<ContainerChild>,
kind: SeatOpKind,
x: i32,
y: i32,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
@ -1452,6 +1672,7 @@ impl Node for ContainerNode {
fn node_on_pointer_enter(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, x: Fixed, y: Fixed) {
// log::info!("node_on_pointer_enter");
self.pointer_move(
seat,
CursorType::Seat(seat.id()),
seat.pointer_cursor(),
x,
@ -1460,6 +1681,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>) {
// log::info!("unfocus");
let mut seats = self.cursors.borrow_mut();
@ -1482,6 +1711,7 @@ impl Node for ContainerNode {
fn node_on_pointer_motion(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, x: Fixed, y: Fixed) {
// log::info!("node_on_pointer_motion");
self.pointer_move(
seat,
CursorType::Seat(seat.id()),
seat.pointer_cursor(),
x,
@ -1503,7 +1733,14 @@ impl Node for ContainerNode {
y: Fixed,
) {
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(
@ -1515,7 +1752,7 @@ impl Node for ContainerNode {
y: Fixed,
) {
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(pressed) = changes.down {
self.button(id, tool.seat(), time_usec, pressed, BTN_LEFT);
@ -1914,6 +2151,31 @@ impl ToplevelNodeBase for ContainerNode {
fn tl_admits_children(&self) -> bool {
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) {
@ -1925,3 +2187,118 @@ fn direction_to_split(dir: Direction) -> (ContainerSplit, bool) {
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),
})
}

View file

@ -9,7 +9,7 @@ use {
state::State,
tree::{
walker::NodeVisitor, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId,
OutputNode, StackedNode,
OutputNode, StackedNode, TileDragDestination,
},
utils::{copyhashmap::CopyHashMap, linkedlist::LinkedList},
},
@ -83,6 +83,21 @@ impl DisplayNode {
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
}
}
impl Node for DisplayNode {

View file

@ -15,7 +15,7 @@ use {
text::TextTexture,
tree::{
walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FindTreeUsecase,
FoundNode, Node, NodeId, StackedNode, ToplevelNode, WorkspaceNode,
FoundNode, Node, NodeId, StackedNode, TileDragDestination, ToplevelNode, WorkspaceNode,
},
utils::{
asyncevent::AsyncEvent, clonecell::CloneCell, double_click_state::DoubleClickState,
@ -528,6 +528,26 @@ impl FloatNode {
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 {

View file

@ -33,7 +33,7 @@ use {
text::TextTexture,
tree::{
walker::NodeVisitor, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node,
NodeId, StackedNode, WorkspaceNode,
NodeId, StackedNode, TddType, TileDragDestination, WorkspaceNode,
},
utils::{
asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap,
@ -522,6 +522,10 @@ impl OutputNode {
return ws;
}
}
self.generate_workspace()
}
pub fn generate_workspace(self: &Rc<Self>) -> Rc<WorkspaceNode> {
let name = 'name: {
for i in 1.. {
let name = i.to_string();
@ -937,6 +941,85 @@ impl OutputNode {
};
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 struct OutputTitle {

View file

@ -10,7 +10,8 @@ use {
state::State,
text::TextTexture,
tree::{
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor,
default_tile_drag_destination, ContainerSplit, Direction, FindTreeResult,
FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor, TileDragDestination,
ToplevelData, ToplevelNode, ToplevelNodeBase,
},
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 {
self.destroyed.get()
}
@ -222,4 +234,15 @@ impl ToplevelNodeBase for PlaceholderNode {
fn tl_admits_children(&self) -> bool {
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)
}
}

View file

@ -11,7 +11,10 @@ use {
},
rect::Rect,
state::State,
tree::{ContainingNode, Direction, Node, OutputNode, PlaceholderNode, WorkspaceNode},
tree::{
ContainerNode, ContainerSplit, ContainingNode, Direction, Node, NodeId, OutputNode,
PlaceholderNode, WorkspaceNode,
},
utils::{
clonecell::CloneCell,
copyhashmap::CopyHashMap,
@ -202,6 +205,20 @@ pub trait ToplevelNodeBase: Node {
}
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 {
@ -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,
}
}