Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new feature, drag and drop window arrangement #44

Merged
merged 22 commits into from
Apr 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
100c26b
New member fuctions for Tree::Node and Workspace to swap two windows
takagiy Apr 8, 2020
e3993d0
new action `swap <window-id>` to swap a window for the focused window
takagiy Apr 8, 2020
6947ff1
apply code format
takagiy Apr 8, 2020
1f1068a
workspace: allow to swap windows between distinct workspaces
takagiy Apr 9, 2020
684c17c
window_manager: Call Unmap after swapping windows into other workspaces
takagiy Apr 9, 2020
a1b2976
tree: Prevent the root node from being swapped
takagiy Apr 9, 2020
3e1cede
Implement swapping for floating windows properly
takagiy Apr 9, 2020
dfc5141
Implement window arrangement with drag-and-drop
takagiy Apr 14, 2020
9354fd8
tree node : Initialize parent_ to nullptr
takagiy Apr 15, 2020
3b2ba7b
Make algolithm to determinate to where insert the moved window more i…
takagiy Apr 15, 2020
ff74334
tree : new function Normalize() to remove redundant internal nodes
takagiy Apr 15, 2020
294e631
workspace: Call Tree::Normalize() after moving windows
takagiy Apr 15, 2020
59e7802
Take window gaps into account while determining the dropped location
takagiy Apr 15, 2020
43e14fc
Disable window swapping between distinct workspaces
takagiy Apr 16, 2020
ade5e02
Delete the action "swap" that was mainly for debugging
takagiy Apr 16, 2020
226cdcd
Resolve merge conflict
takagiy Apr 17, 2020
adf17d3
window_manager: Also disable focus on hover while dragging the tiling…
takagiy Apr 17, 2020
483d15d
window_manager: Suppress the warning at the compile-time
takagiy Apr 17, 2020
f8a4fc1
Clarify the intention of codes
takagiy Apr 18, 2020
c1db1eb
window_manager: Take `border_width` value into account during GetDrop…
takagiy Apr 18, 2020
c091a75
window_manager: Normalize client tree after removing window.
takagiy Apr 23, 2020
903224c
Remove NotifySend()ing for debugging
takagiy Apr 23, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 76 additions & 7 deletions src/tree.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ vector<Tree::Node*> Tree::GetLeaves() const {
return root_node_->GetLeaves();
}

void Tree::Normalize() {
root_node_->Normalize();
}

Tree::Node* Tree::root_node() const {
return root_node_.get();
}
Expand Down Expand Up @@ -169,7 +173,7 @@ void Tree::DfsSerializeHelper(Tree::Node* node, string& data) const {
}

Tree::Node::Node(unique_ptr<Client> client)
: client_(std::move(client)), tiling_direction_(TilingDirection::UNSPECIFIED) {
: parent_(), client_(std::move(client)), tiling_direction_(TilingDirection::UNSPECIFIED) {
if (client_) {
Tree::Node::mapper_[client_.get()] = this;
}
Expand All @@ -184,21 +188,81 @@ void Tree::Node::AddChild(unique_ptr<Tree::Node> child) {
children_.push_back(std::move(child));
}

void Tree::Node::RemoveChild(Tree::Node* child) {
unique_ptr<Tree::Node> Tree::Node::RemoveChild(Tree::Node* child) {
unique_ptr<Tree::Node> removed_ptr = nullptr;

child->set_parent(nullptr);
children_.erase(
std::remove_if(children_.begin(), children_.end(),
[&](unique_ptr<Tree::Node>& node) { return node.get() == child; }),
children_.end());
children_.erase(std::remove_if(children_.begin(), children_.end(),
[&](unique_ptr<Tree::Node>& node) {
return node.get() == child &&
(removed_ptr = std::move(node), true);
}),
children_.end());
return removed_ptr;
}

void Tree::Node::InsertChildAfter(unique_ptr<Tree::Node> child, Tree::Node* ref) {
InsertChildBeside(std::move(child), ref, TilingPosition::AFTER);
}

void Tree::Node::InsertChildBeside(unique_ptr<Tree::Node> child, Tree::Node* ref,
TilingPosition tiling_position) {
child->set_parent(this);
ptrdiff_t ref_idx =
std::find_if(children_.begin(), children_.end(),
[&](unique_ptr<Tree::Node>& node) { return node.get() == ref; }) -
children_.begin();
children_.insert(children_.begin() + ref_idx + 1, std::move(child));
ptrdiff_t position = tiling_position == TilingPosition::BEFORE ? 0 : 1;
children_.insert(children_.begin() + ref_idx + position, std::move(child));
}

// Insert a child node, but every current child of this node will be the child of the inserted
// node.
void Tree::Node::InsertChildAboveChildren(unique_ptr<Tree::Node> child) {
for (const auto current_child : children()) {
unique_ptr<Tree::Node> current_child_ptr = RemoveChild(current_child);
child->AddChild(std::move(current_child_ptr));
}

AddChild(std::move(child));
}

// Insert a node as a parent while lifting the current parent up to the grandparent.
void Tree::Node::InsertParent(std::unique_ptr<Tree::Node> parent) {
Tree::Node* parent_raw = parent.get();

parent_->InsertChildAfter(std::move(parent), this);
unique_ptr<Tree::Node> this_ptr = parent_->RemoveChild(this);
parent_raw->AddChild(std::move(this_ptr));
}

void Tree::Node::Swap(Tree::Node* destination) {
if (!leaf() || !destination->leaf() || !parent_ || !destination->parent_) {
return;
}

unique_ptr<Tree::Node>& this_ptr = owning_pointer_();
unique_ptr<Tree::Node>& dest_ptr = destination->owning_pointer_();

this_ptr.swap(dest_ptr);
std::swap(parent_, destination->parent_);
}

// Delete redundant internal nodes below this node.
void Tree::Node::Normalize() {
for (auto& child : children()) {
if (child->children_.size() == 1) {
unique_ptr<Tree::Node> grandchild = child->RemoveChild(child->children_.front().get());
Tree::Node* grandchild_raw = grandchild.get();

InsertChildAfter(std::move(grandchild), child);
RemoveChild(child);

grandchild_raw->Normalize();
} else {
child->Normalize();
}
}
}

Tree::Node* Tree::Node::GetLeftSibling() const {
Expand Down Expand Up @@ -303,4 +367,9 @@ bool Tree::Node::leaf() const {
return children_.empty();
}

unique_ptr<Tree::Node>& Tree::Node::owning_pointer_() const {
return *std::find_if(parent_->children_.begin(), parent_->children_.end(),
[&](unique_ptr<Tree::Node>& node) { return node.get() == this; });
}

} // namespace wmderland
18 changes: 17 additions & 1 deletion src/tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ enum class TilingDirection {
VERTICAL,
};

enum class TilingPosition {
BEFORE,
AFTER,
};

class Tree {
public:
Tree();
Expand All @@ -27,8 +32,15 @@ class Tree {
virtual ~Node();

void AddChild(std::unique_ptr<Tree::Node> child);
void RemoveChild(Tree::Node* child);
std::unique_ptr<Tree::Node> RemoveChild(Tree::Node* child);
void InsertChildAfter(std::unique_ptr<Tree::Node> child, Tree::Node* ref);
void InsertChildBeside(std::unique_ptr<Tree::Node> child, Tree::Node* ref,
TilingPosition tiling_position);
void InsertChildAboveChildren(std::unique_ptr<Tree::Node> child);
void InsertParent(std::unique_ptr<Tree::Node> parent);
void Swap(Tree::Node* destination);

void Normalize();

Tree::Node* GetLeftSibling() const;
Tree::Node* GetRightSibling() const;
Expand All @@ -50,6 +62,8 @@ class Tree {
static std::unordered_map<Client*, Tree::Node*> mapper_;

private:
std::unique_ptr<Tree::Node>& owning_pointer_() const;

std::vector<std::unique_ptr<Tree::Node>> children_;
Tree::Node* parent_;

Expand All @@ -60,6 +74,8 @@ class Tree {
Tree::Node* GetTreeNode(Client* client) const;
std::vector<Tree::Node*> GetLeaves() const;

void Normalize();

Tree::Node* root_node() const;
Tree::Node* current_node() const;
void set_current_node(Tree::Node* node);
Expand Down
159 changes: 148 additions & 11 deletions src/window_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ extern "C" {
#include <cassert>
#include <cstring>
#include <iostream>
#include <vector>

#include "client.h"
#include "log.h"
Expand Down Expand Up @@ -47,6 +48,8 @@ extern "C" {
} while (0)

using std::pair;
using std::tuple;
using std::vector;

namespace wmderland {

Expand Down Expand Up @@ -407,25 +410,40 @@ void WindowManager::OnButtonPress(const XButtonEvent& e) {
c->workspace()->SetFocusedClient(c->window());
c->workspace()->RaiseAllFloatingClients();

if (c->is_floating() && !c->is_fullscreen()) {
c->workspace()->DisableFocusFollowsMouse();
if (c->is_fullscreen()) {
return;
}

if (c->is_floating()) {
c->Raise();
c->set_attr_cache(c->GetXWindowAttributes());

mouse_->btn_pressed_event_ = e;
mouse_->SetCursor(static_cast<Mouse::CursorType>(e.button));
} else if (e.button != Mouse::Button::LEFT) {
return;
}

c->workspace()->DisableFocusFollowsMouse();
mouse_->btn_pressed_event_ = e;
mouse_->SetCursor(static_cast<Mouse::CursorType>(e.button));
}

void WindowManager::OnButtonRelease(const XButtonEvent&) {
void WindowManager::OnButtonRelease(const XButtonEvent& e) {
Client* c = nullptr;
GET_CLIENT_OR_RETURN(mouse_->btn_pressed_event_.subwindow, c);

assert(c->is_floating() && !c->is_fullscreen());
assert(!c->is_fullscreen());
c->workspace()->EnableFocusFollowsMouse();

XWindowAttributes attr = wm_utils::GetXWindowAttributes(mouse_->btn_pressed_event_.subwindow);
cookie_.Put(c->window(), {attr.x, attr.y, attr.width, attr.height});
if (c->is_floating()) {
XWindowAttributes attr = wm_utils::GetXWindowAttributes(mouse_->btn_pressed_event_.subwindow);
cookie_.Put(c->window(), {attr.x, attr.y, attr.width, attr.height});
} else {
tuple<Window, AreaType, TilingDirection, TilingPosition> drop_location =
GetDropLocation(e);

MoveWindow(c->window(), std::get<Window>(drop_location), std::get<AreaType>(drop_location),
std::get<TilingDirection>(drop_location),
std::get<TilingPosition>(drop_location));
}

mouse_->btn_pressed_event_.subwindow = None;
mouse_->SetCursor(Mouse::CursorType::NORMAL);
Expand All @@ -435,6 +453,10 @@ void WindowManager::OnMotionNotify(const XButtonEvent& e) {
Client* c = nullptr;
GET_CLIENT_OR_RETURN(mouse_->btn_pressed_event_.subwindow, c);

if (!c->is_floating()) {
return;
}

const XWindowAttributes& attr = c->attr_cache();
const XButtonEvent& btn_pressed_event = mouse_->btn_pressed_event_;

Expand Down Expand Up @@ -556,14 +578,17 @@ void WindowManager::Unmanage(Window window) {
Client* c = nullptr;
GET_CLIENT_OR_RETURN(window, c);

Workspace* workspace = c->workspace();

// If the client being destroyed is in fullscreen mode, make sure to unset the
// workspace's fullscreen state.
if (c->is_fullscreen()) {
c->workspace()->set_fullscreen(false);
workspace->set_fullscreen(false);
}

// Remove the corresponding client from the client tree.
c->workspace()->Remove(window);
workspace->Remove(window);
workspace->Normalize();
UpdateClientList();
ArrangeWindows();
}
Expand Down Expand Up @@ -680,6 +705,48 @@ void WindowManager::MoveWindowToWorkspace(Window window, int next) {
ArrangeWindows();
}

void WindowManager::MoveWindow(Window window, Window ref, AreaType area_type,
TilingDirection tiling_direction,
TilingPosition tiling_position) {
Client* c = nullptr;
Client* ref_client = nullptr;
GET_CLIENT_OR_RETURN(window, c);
GET_CLIENT_OR_RETURN(ref, ref_client);

// Suppress the warning for the unused variable 'c'.
(void)c;

if (area_type == AreaType::CENTER || ref_client->is_floating()) {
SwapWindows(window, ref);
return;
}

workspaces_[current_]->Move(window, ref, area_type, tiling_direction, tiling_position);

ArrangeWindows();
}

void WindowManager::SwapWindows(Window window0, Window window1) {
Client* c0 = nullptr;
Client* c1 = nullptr;
GET_CLIENT_OR_RETURN(window0, c0);
GET_CLIENT_OR_RETURN(window1, c1);

workspaces_[current_]->Swap(window0, window1);

// Also swap the coordinates of the windows. (Apply it if the window is floating.)
XWindowAttributes attr0 = c0->GetXWindowAttributes();
if (c0->is_floating()) {
XWindowAttributes attr = c1->GetXWindowAttributes();
c0->MoveResize(attr.x, attr.y, attr.width, attr.height);
}
if (c1->is_floating()) {
c1->MoveResize(attr0.x, attr0.y, attr0.width, attr0.height);
}

ArrangeWindows();
}

void WindowManager::SetFloating(Window window, bool floating, bool use_default_size) {
Client* c = nullptr;
GET_CLIENT_OR_RETURN(window, c);
Expand Down Expand Up @@ -855,6 +922,76 @@ Client::Area WindowManager::GetFloatingWindowArea(Window window, bool use_defaul
return area;
}

// Detect where the mouse button was released.
tuple<Window, AreaType, TilingDirection, TilingPosition> WindowManager::GetDropLocation(
const XButtonEvent& e) const {
Client* c = nullptr;
auto it = Client::mapper_.find(e.subwindow);

if (it != Client::mapper_.end()) {
c = it->second;
} else { // If the point dropped at is not on a window, try to find the window which area
// contains the point.
int pad_lt = 1 + config_->gap_width() / 2;
int pad_rb = pad_lt + 2 * config_->border_width();
vector<Client*> clients = workspaces_[current_]->GetClients();
auto it = std::find_if(clients.begin(), clients.end(), [&](Client* client) {
if (client->is_floating()) {
return false;
}

XWindowAttributes attr = client->GetXWindowAttributes();

return e.x >= attr.x - pad_lt && e.x <= attr.x + attr.width + pad_rb &&
e.y >= attr.y - pad_lt && e.y <= attr.y + attr.height + pad_rb;
});

if (it != clients.end()) {
c = *it;
} else {
return std::make_tuple(None, AreaType::UNDEFINED, TilingDirection::UNSPECIFIED,
TilingPosition::AFTER);
}
}

XWindowAttributes attr = c->GetXWindowAttributes();

int width = attr.width + 2 * config_->border_width();
int height = attr.height + 2 * config_->border_width();
int x = e.x - attr.x - 0.5 * width;
int y = e.y - attr.y - 0.5 * height;
double r = x != 0 ? (double)y / x : 0.;
double r_diagonal = width != 0 ? (double)height / width : 0.;

TilingDirection tiling_direction;
// How the point dropped at differ from the center of the window. (Normalized in [-0.5, 0.5])
double d;
// Value of `d` such that it is 35px away from the end of the window.
double d_edge;
if (x == 0 || r >= r_diagonal || r <= -r_diagonal) { // Dropped in the North or the South
tiling_direction = TilingDirection::VERTICAL;
d = (double)y / height;
d_edge = 0.5 - 35. / height;
} else { // Dropped in the East or the West
tiling_direction = TilingDirection::HORIZONTAL;
d = (double)x / width;
d_edge = 0.5 - 35. / width;
}

TilingPosition tiling_position = d >= 0. ? TilingPosition::AFTER : TilingPosition::BEFORE;

AreaType area_type;
if (d >= -0.1 && d <= 0.1) {
area_type = AreaType::CENTER;
} else if (d >= -d_edge && d <= d_edge) {
area_type = AreaType::MID;
} else {
area_type = AreaType::EDGE;
}

return std::make_tuple(c->window(), area_type, tiling_direction, tiling_position);
}

void WindowManager::UpdateClientList() {
XDeleteProperty(dpy_, root_window_, prop_->net[atom::NET_CLIENT_LIST]);

Expand Down
Loading