enlightenment/src/modules/tiling/window_tree.c

620 lines
16 KiB
C

#include "e.h"
#include "window_tree.h"
#include "e_mod_tiling.h"
void
tiling_window_tree_walk(Window_Tree *root, void (*func)(void *))
{
Eina_Inlist *itr_safe;
Window_Tree *itr;
if (!root)
return;
EINA_INLIST_FOREACH_SAFE(root->children, itr_safe, itr)
{
tiling_window_tree_walk(itr, func);
}
func(root);
}
void
tiling_window_tree_free(Window_Tree *root)
{
tiling_window_tree_walk(root, free);
}
static void
_tiling_window_tree_split_add(Window_Tree *parent, Window_Tree *new_node)
{
/* Make a new node for the parent client and split the weights in half. */
Window_Tree *new_parent_client = calloc(1, sizeof(*new_node));
new_node->parent = parent;
new_parent_client->parent = parent;
new_parent_client->client = parent->client;
parent->client = NULL;
new_parent_client->weight = 0.5;
new_node->weight = 0.5;
parent->children =
eina_inlist_append(parent->children, EINA_INLIST_GET(new_parent_client));
parent->children =
eina_inlist_append(parent->children, EINA_INLIST_GET(new_node));
}
static void
_tiling_window_tree_parent_add(Window_Tree *parent, Window_Tree *new_node, Window_Tree *rel)
{
/* Adjust existing children's weights */
Window_Tree *itr;
int children_count = eina_inlist_count(parent->children);
float weight = 1.0 / (children_count + 1);
new_node->parent = parent;
new_node->weight = weight;
weight *= children_count;
EINA_INLIST_FOREACH(parent->children, itr)
{
itr->weight *= weight;
}
parent->children =
eina_inlist_append_relative(parent->children, EINA_INLIST_GET(new_node),
EINA_INLIST_GET(rel));
}
static int
_tiling_window_tree_split_type_get(Window_Tree *node)
{
int ret = 0;
while (node->parent)
{
ret++;
node = node->parent;
}
return ret % 2;
}
Window_Tree *
tiling_window_tree_add(Window_Tree *root, Window_Tree *parent,
E_Client *client, Tiling_Split_Type split_type)
{
Window_Tree *orig_parent = parent;
Window_Tree *new_node = calloc(1, sizeof(*new_node));
new_node->client = client;
Tiling_Split_Type parent_split_type;
if (split_type > TILING_SPLIT_VERTICAL)
{
free(new_node);
return root;
}
else if (!root)
{
new_node->weight = 1.0;
return new_node;
}
else if (!parent)
{
parent = root;
parent_split_type = _tiling_window_tree_split_type_get(parent);
if ((parent_split_type != split_type) && (parent->children))
{
parent = (Window_Tree *)parent->children;
}
}
parent_split_type = _tiling_window_tree_split_type_get(parent);
if (parent_split_type == split_type)
{
if (parent->children)
{
_tiling_window_tree_parent_add(parent, new_node, NULL);
}
else
{
_tiling_window_tree_split_add(parent, new_node);
}
}
else
{
Window_Tree *grand_parent = parent->parent;
if (grand_parent && grand_parent->children)
{
_tiling_window_tree_parent_add(grand_parent, new_node, orig_parent);
}
else
{
root = calloc(1, sizeof(*root));
_tiling_window_tree_split_add(parent, new_node);
root->weight = 1.0;
root->children =
eina_inlist_append(root->children, EINA_INLIST_GET(parent));
parent->parent = root;
}
}
return root;
}
Window_Tree *
tiling_window_tree_remove(Window_Tree *root, Window_Tree *item)
{
if (root == item)
{
free(item);
return NULL;
}
else if (!item->client)
{
ERR("Tried deleting node %p that doesn't have a client.", item);
return root;
}
Window_Tree *parent = item->parent;
int children_count = eina_inlist_count(item->parent->children);
if (children_count <= 2)
{
Window_Tree *grand_parent = parent->parent;
Window_Tree *item_keep = NULL;
/* Adjust existing children's weights */
EINA_INLIST_FOREACH(parent->children, item_keep)
{
if (item_keep != item)
break;
}
if (!item_keep)
{
/* Special case of deleting the last vertical split item. */
free(item);
free(root);
return NULL;
}
else if (!item_keep->children)
{
parent->client = item_keep->client;
parent->children = NULL;
free(item_keep);
}
else
{
parent->children =
eina_inlist_remove(parent->children, EINA_INLIST_GET(item));
if (grand_parent)
{
/* Update the children's parent. */
{
Eina_Inlist *itr_safe;
Window_Tree *itr;
EINA_INLIST_FOREACH_SAFE(item_keep->children, itr_safe,
itr)
{
/* We are prepending to double-reverse the order. */
grand_parent->children =
eina_inlist_prepend_relative(grand_parent->children,
EINA_INLIST_GET(itr), EINA_INLIST_GET(parent));
itr->weight *= parent->weight;
itr->parent = grand_parent;
}
grand_parent->children =
eina_inlist_remove(grand_parent->children,
EINA_INLIST_GET(parent));
free(parent);
}
}
else
{
/* This is fine, as this is a child of the root so we allow
* two levels. */
item_keep->weight = 1.0;
}
}
}
else
{
Window_Tree *itr;
float weight = 1.0 - item->weight;
parent->children =
eina_inlist_remove(parent->children, EINA_INLIST_GET(item));
/* Adjust existing children's weights */
EINA_INLIST_FOREACH(parent->children, itr)
{
itr->weight /= weight;
}
}
free(item);
return root;
}
Window_Tree *
tiling_window_tree_client_find(Window_Tree *root, E_Client *client)
{
Window_Tree *itr;
if (!client)
return NULL;
if (!root || (root->client == client))
return root;
EINA_INLIST_FOREACH(root->children, itr)
{
Window_Tree *ret;
ret = tiling_window_tree_client_find(itr, client);
if (ret)
return ret;
}
return NULL;
}
void
_tiling_window_tree_level_apply(Window_Tree *root, Evas_Coord x, Evas_Coord y,
Evas_Coord w, Evas_Coord h, int level, Evas_Coord padding)
{
Window_Tree *itr;
Tiling_Split_Type split_type = level % 2;
double total_weight = 0.0;
if (root->client)
{
if (!e_object_is_del(E_OBJECT(root->client)))
tiling_e_client_move_resize_extra(root->client, x, y,
w - padding, h - padding);
return;
}
if (split_type == TILING_SPLIT_HORIZONTAL)
{
EINA_INLIST_FOREACH(root->children, itr)
{
Evas_Coord itw = w * itr->weight;
total_weight += itr->weight;
_tiling_window_tree_level_apply(itr, x, y, itw, h, level + 1, padding);
x += itw;
}
}
else if (split_type == TILING_SPLIT_VERTICAL)
{
EINA_INLIST_FOREACH(root->children, itr)
{
Evas_Coord ith = h * itr->weight;
total_weight += itr->weight;
_tiling_window_tree_level_apply(itr, x, y, w, ith, level + 1, padding);
y += ith;
}
}
/* Adjust the last item's weight in case weight < 1.0 */
((Window_Tree *)root->children->last)->weight += 1.0 - total_weight;
}
void
tiling_window_tree_apply(Window_Tree *root, Evas_Coord x, Evas_Coord y,
Evas_Coord w, Evas_Coord h, Evas_Coord padding)
{
_tiling_window_tree_level_apply(root, x + padding, y + padding,
w - padding, h - padding, 0, padding);
}
static Window_Tree *
_inlist_next(Window_Tree *it)
{
return (Window_Tree *)EINA_INLIST_GET(it)->next;
}
static Window_Tree *
_inlist_prev(Window_Tree *it)
{
return (Window_Tree *)EINA_INLIST_GET(it)->prev;
}
static Eina_Bool
_tiling_window_tree_node_resize_direction(Window_Tree *node,
Window_Tree *parent, double dir_diff, int dir)
{
double weight = 0.0;
double weight_diff;
Window_Tree *children_start;
Window_Tree *itr;
Window_Tree *(*itr_func)(Window_Tree *);
if (dir > 0)
{
itr_func = _inlist_prev;
children_start = (Window_Tree *)parent->children->last;
}
else
{
itr_func = _inlist_next;
children_start = (Window_Tree *)parent->children;
}
itr = (Window_Tree *)children_start;
while (itr != node)
{
weight += itr->weight;
itr = itr_func(itr);
}
/* If it's at the edge, try the grandpa of the parent. */
if (weight == 0.0)
{
if (parent->parent && parent->parent->parent)
{
return _tiling_window_tree_node_resize_direction(parent->parent,
parent->parent->parent, 1.0 + ((dir_diff - 1.) * node->weight),
dir);
}
return EINA_FALSE;
}
weight_diff = itr->weight;
itr->weight *= dir_diff;
weight_diff -= itr->weight;
weight_diff /= weight;
for (itr = children_start; itr != node; itr = itr_func(itr))
{
itr->weight += itr->weight * weight_diff;
}
return EINA_TRUE;
}
Eina_Bool
tiling_window_tree_node_resize(Window_Tree *node, int w_dir, double w_diff,
int h_dir, double h_diff)
{
Window_Tree *parent = node->parent;
Window_Tree *w_parent, *h_parent;
Eina_Bool ret = EINA_FALSE;
/* If we have no parent, means we need to be full screen anyway. */
if (!parent)
return EINA_FALSE;
Window_Tree *grand_parent = parent->parent;
Tiling_Split_Type parent_split_type =
_tiling_window_tree_split_type_get(parent);
/* w_diff related changes. */
if (parent_split_type == TILING_SPLIT_HORIZONTAL)
{
w_parent = parent;
h_parent = grand_parent;
}
else
{
w_parent = grand_parent;
h_parent = parent;
}
if ((h_diff != 1.0) && h_parent)
{
Window_Tree *tmp_node = (h_parent == parent) ? node : parent;
ret = ret ||
_tiling_window_tree_node_resize_direction(tmp_node, h_parent,
h_diff, h_dir);
}
if ((w_diff != 1.0) && w_parent)
{
Window_Tree *tmp_node = (w_parent == parent) ? node : parent;
ret = ret ||
_tiling_window_tree_node_resize_direction(tmp_node, w_parent,
w_diff, w_dir);
}
return ret;
}
int
_tiling_window_tree_edges_get_helper(Window_Tree *node,
Tiling_Split_Type split_type, Eina_Bool gave_up_this,
Eina_Bool gave_up_parent)
{
int ret =
TILING_WINDOW_TREE_EDGE_LEFT | TILING_WINDOW_TREE_EDGE_RIGHT |
TILING_WINDOW_TREE_EDGE_TOP | TILING_WINDOW_TREE_EDGE_BOTTOM;
if (!node->parent)
{
return ret;
}
else if (gave_up_this && gave_up_parent)
{
return 0;
}
else if (gave_up_this)
{
/* Mixed the gave_up vals on purpose, we do it on every call. */
return _tiling_window_tree_edges_get_helper(node->parent, !split_type,
gave_up_parent, gave_up_this);
}
if (EINA_INLIST_GET(node)->prev)
{
gave_up_this = EINA_TRUE;
ret ^=
(split_type ==
TILING_SPLIT_HORIZONTAL) ? TILING_WINDOW_TREE_EDGE_LEFT :
TILING_WINDOW_TREE_EDGE_TOP;
}
if (EINA_INLIST_GET(node)->next)
{
gave_up_this = EINA_TRUE;
ret ^=
(split_type ==
TILING_SPLIT_HORIZONTAL) ? TILING_WINDOW_TREE_EDGE_RIGHT :
TILING_WINDOW_TREE_EDGE_BOTTOM;
}
/* Mixed the gave_up vals on purpose, we do it on every call. */
return ret & _tiling_window_tree_edges_get_helper(node->parent, !split_type,
gave_up_parent, gave_up_this);
}
int
tiling_window_tree_edges_get(Window_Tree *node)
{
Tiling_Split_Type split_type = _tiling_window_tree_split_type_get(node);
return _tiling_window_tree_edges_get_helper(node, !split_type, EINA_FALSE,
EINA_FALSE);
}
/* Node move */
static struct _Node_Move_Context
{
Window_Tree *node;
Window_Tree *ret;
int cross_edge;
int best_match;
} _node_move_ctx;
#define CNODE (_node_move_ctx.node)
#define IF_MATCH_SET_LR(node) _tiling_window_tree_node_move_if_match_set(node, \
CNODE->client->y, CNODE->client->h, node->client->y, node->client->h)
#define IF_MATCH_SET_TB(node) _tiling_window_tree_node_move_if_match_set(node, \
CNODE->client->x, CNODE->client->w, node->client->x, node->client->w)
static void
_tiling_window_tree_node_move_if_match_set(Window_Tree *node, Evas_Coord cx,
Evas_Coord cw, Evas_Coord ox, Evas_Coord ow)
{
Evas_Coord leftx, rightx;
int match;
leftx = _TILE_MAX(cx, ox);
rightx = _TILE_MIN(cx + cw, ox + ow);
match = rightx - leftx;
if (match > _node_move_ctx.best_match)
{
_node_move_ctx.best_match = match;
_node_move_ctx.ret = node;
}
}
static void
_tiling_window_tree_node_move_walker(void *_node)
{
Window_Tree *node = _node;
int p = tiling_g.config->window_padding;
/* We are only interested in nodes with clients. */
if (!node->client)
return;
switch (_node_move_ctx.cross_edge)
{
case TILING_WINDOW_TREE_EDGE_LEFT:
if ((node->client->x + node->client->w + p) == CNODE->client->x)
IF_MATCH_SET_LR(node);
break;
case TILING_WINDOW_TREE_EDGE_RIGHT:
if (node->client->x == (CNODE->client->x + CNODE->client->w + p))
IF_MATCH_SET_LR(node);
break;
case TILING_WINDOW_TREE_EDGE_TOP:
if ((node->client->y + node->client->h + p) == CNODE->client->y)
IF_MATCH_SET_TB(node);
break;
case TILING_WINDOW_TREE_EDGE_BOTTOM:
if (node->client->y == (CNODE->client->y + CNODE->client->h + p))
IF_MATCH_SET_TB(node);
break;
default:
break;
}
}
#undef CNODE
#undef IF_MATCH_SET_LR
#undef IF_MATCH_SET_TB
void
tiling_window_tree_node_move(Window_Tree *node, int cross_edge)
{
Window_Tree *root = node;
/* FIXME: This is very slow and possibly buggy. Can be done much better, but
* is very easy to implement. */
while (root->parent)
root = root->parent;
_node_move_ctx.node = node;
_node_move_ctx.cross_edge = cross_edge;
_node_move_ctx.ret = NULL;
_node_move_ctx.best_match = 0;
tiling_window_tree_walk(root, _tiling_window_tree_node_move_walker);
if (_node_move_ctx.ret)
{
E_Client *ec = node->client;
node->client = _node_move_ctx.ret->client;
_node_move_ctx.ret->client = ec;
}
}
/* End Node move. */
void
tiling_window_tree_dump(Window_Tree *root, int level)
{
int i;
if (!root)
return;
for (i = 0; i < level; i++)
printf(" ");
if (root->children)
printf("\\-");
else
printf("|-");
printf("%f (%p)\n", root->weight, root->client);
Window_Tree *itr;
EINA_INLIST_FOREACH(root->children, itr)
{
tiling_window_tree_dump(itr, level + 1);
}
}