summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/list.c2
-rw-r--r--include/list.h2
-rw-r--r--include/sway/container.h20
-rw-r--r--include/sway/focus.h6
-rw-r--r--include/sway/layout.h7
-rw-r--r--sway/commands/focus.c4
-rw-r--r--sway/commands/layout.c79
-rw-r--r--sway/commands/move.c8
-rw-r--r--sway/commands/resize.c313
-rw-r--r--sway/commands/workspace_layout.c10
-rw-r--r--sway/container.c25
-rw-r--r--sway/debug_log.c4
-rw-r--r--sway/layout.c688
-rw-r--r--sway/sway.5.txt41
14 files changed, 860 insertions, 349 deletions
diff --git a/common/list.c b/common/list.c
index dd864a9b..39cc10e1 100644
--- a/common/list.c
+++ b/common/list.c
@@ -76,7 +76,7 @@ int list_seq_find(list_t *list, int compare(const void *item, const void *data),
76 return -1; 76 return -1;
77} 77}
78 78
79static void list_swap(list_t *list, int src, int dest) { 79void list_swap(list_t *list, int src, int dest) {
80 void *tmp = list->items[src]; 80 void *tmp = list->items[src];
81 list->items[src] = list->items[dest]; 81 list->items[src] = list->items[dest];
82 list->items[dest] = tmp; 82 list->items[dest] = tmp;
diff --git a/include/list.h b/include/list.h
index f478b6bb..7eead4ac 100644
--- a/include/list.h
+++ b/include/list.h
@@ -22,4 +22,6 @@ void list_qsort(list_t *list, int compare(const void *left, const void *right));
22int list_seq_find(list_t *list, int compare(const void *item, const void *cmp_to), const void *cmp_to); 22int list_seq_find(list_t *list, int compare(const void *item, const void *cmp_to), const void *cmp_to);
23// stable sort since qsort is not guaranteed to be stable 23// stable sort since qsort is not guaranteed to be stable
24void list_stable_sort(list_t *list, int compare(const void *a, const void *b)); 24void list_stable_sort(list_t *list, int compare(const void *a, const void *b));
25// swap two elements in a list
26void list_swap(list_t *list, int src, int dest);
25#endif 27#endif
diff --git a/include/sway/container.h b/include/sway/container.h
index 2bedd136..ff65628c 100644
--- a/include/sway/container.h
+++ b/include/sway/container.h
@@ -37,6 +37,16 @@ enum swayc_layouts {
37 L_STACKED, 37 L_STACKED,
38 L_TABBED, 38 L_TABBED,
39 L_FLOATING, /**< A psuedo-container, removed from the tree, to hold floating windows */ 39 L_FLOATING, /**< A psuedo-container, removed from the tree, to hold floating windows */
40
41 /* Awesome/Monad style auto layouts */
42 L_AUTO_LEFT,
43 L_AUTO_RIGHT,
44 L_AUTO_TOP,
45 L_AUTO_BOTTOM,
46
47 L_AUTO_FIRST = L_AUTO_LEFT,
48 L_AUTO_LAST = L_AUTO_BOTTOM,
49
40 // Keep last 50 // Keep last
41 L_LAYOUTS, 51 L_LAYOUTS,
42}; 52};
@@ -144,6 +154,16 @@ struct sway_container {
144 struct wlc_geometry title_bar_geometry; 154 struct wlc_geometry title_bar_geometry;
145 struct wlc_geometry actual_geometry; 155 struct wlc_geometry actual_geometry;
146 int border_thickness; 156 int border_thickness;
157
158 /**
159 * Number of master views in auto layouts.
160 */
161 size_t nb_master;
162
163 /**
164 * Number of slave groups (e.g. columns) in auto layouts.
165 */
166 size_t nb_slave_groups;
147}; 167};
148 168
149enum visibility_mask { 169enum visibility_mask {
diff --git a/include/sway/focus.h b/include/sway/focus.h
index b532edc2..652cdccc 100644
--- a/include/sway/focus.h
+++ b/include/sway/focus.h
@@ -6,7 +6,10 @@ enum movement_direction {
6 MOVE_UP, 6 MOVE_UP,
7 MOVE_DOWN, 7 MOVE_DOWN,
8 MOVE_PARENT, 8 MOVE_PARENT,
9 MOVE_CHILD 9 MOVE_CHILD,
10 MOVE_NEXT,
11 MOVE_PREV,
12 MOVE_FIRST
10}; 13};
11 14
12#include "container.h" 15#include "container.h"
@@ -40,4 +43,3 @@ extern bool suspend_workspace_cleanup;
40bool move_focus(enum movement_direction direction); 43bool move_focus(enum movement_direction direction);
41 44
42#endif 45#endif
43
diff --git a/include/sway/layout.h b/include/sway/layout.h
index b982365c..fbedcdb3 100644
--- a/include/sway/layout.h
+++ b/include/sway/layout.h
@@ -75,4 +75,11 @@ void swayc_log(log_importance_t verbosity, swayc_t *cont, const char* format, ..
75 */ 75 */
76enum swayc_layouts default_layout(swayc_t *output); 76enum swayc_layouts default_layout(swayc_t *output);
77 77
78bool is_auto_layout(enum swayc_layouts layout);
79int auto_group_start_index(const swayc_t *container, int index);
80int auto_group_end_index(const swayc_t *container, int index);
81size_t auto_group_count(const swayc_t *container);
82size_t auto_group_index(const swayc_t *container, int index);
83bool auto_group_bounds(const swayc_t *container, size_t group_index, int *start, int *end);
84
78#endif 85#endif
diff --git a/sway/commands/focus.c b/sway/commands/focus.c
index 8442305f..0be442ca 100644
--- a/sway/commands/focus.c
+++ b/sway/commands/focus.c
@@ -46,6 +46,10 @@ struct cmd_results *cmd_focus(int argc, char **argv) {
46 move_focus(MOVE_PARENT); 46 move_focus(MOVE_PARENT);
47 } else if (strcasecmp(argv[0], "child") == 0) { 47 } else if (strcasecmp(argv[0], "child") == 0) {
48 move_focus(MOVE_CHILD); 48 move_focus(MOVE_CHILD);
49 } else if (strcasecmp(argv[0], "next") == 0) {
50 move_focus(MOVE_NEXT);
51 } else if (strcasecmp(argv[0], "prev") == 0) {
52 move_focus(MOVE_PREV);
49 } else if (strcasecmp(argv[0], "mode_toggle") == 0) { 53 } else if (strcasecmp(argv[0], "mode_toggle") == 0) {
50 int i; 54 int i;
51 swayc_t *workspace = swayc_active_workspace(); 55 swayc_t *workspace = swayc_active_workspace();
diff --git a/sway/commands/layout.c b/sway/commands/layout.c
index 08336150..0cdac1b4 100644
--- a/sway/commands/layout.c
+++ b/sway/commands/layout.c
@@ -49,11 +49,88 @@ struct cmd_results *cmd_layout(int argc, char **argv) {
49 } else if (strcasecmp(argv[0], "splitv") == 0) { 49 } else if (strcasecmp(argv[0], "splitv") == 0) {
50 swayc_change_layout(parent, L_VERT); 50 swayc_change_layout(parent, L_VERT);
51 } else if (strcasecmp(argv[0], "toggle") == 0 && argc == 2 && strcasecmp(argv[1], "split") == 0) { 51 } else if (strcasecmp(argv[0], "toggle") == 0 && argc == 2 && strcasecmp(argv[1], "split") == 0) {
52 if (parent->layout == L_HORIZ && (parent->workspace_layout == L_NONE || parent->workspace_layout == L_HORIZ)) { 52 if (parent->layout == L_HORIZ && (parent->workspace_layout == L_NONE ||
53 parent->workspace_layout == L_HORIZ)) {
53 swayc_change_layout(parent, L_VERT); 54 swayc_change_layout(parent, L_VERT);
54 } else { 55 } else {
55 swayc_change_layout(parent, L_HORIZ); 56 swayc_change_layout(parent, L_HORIZ);
56 } 57 }
58 } else if (strcasecmp(argv[0], "auto_left") == 0) {
59 swayc_change_layout(parent, L_AUTO_LEFT);
60 } else if (strcasecmp(argv[0], "auto_right") == 0) {
61 swayc_change_layout(parent, L_AUTO_RIGHT);
62 } else if (strcasecmp(argv[0], "auto_top") == 0) {
63 swayc_change_layout(parent, L_AUTO_TOP);
64 } else if (strcasecmp(argv[0], "auto_bot") == 0) {
65 swayc_change_layout(parent, L_AUTO_BOTTOM);
66 } else if (strcasecmp(argv[0], "incnmaster") == 0) {
67 const char *name = "layout incnmaster";
68 if ((error = checkarg(argc, name,
69 EXPECTED_EQUAL_TO, 2))) {
70 return error;
71 }
72 char *end;
73 int inc = (int) strtol(argv[1], &end, 10);
74 if (*end) {
75 return cmd_results_new(CMD_INVALID, name, "Invalid %s command "
76 "(argument must be an integer)", name);
77
78 }
79 swayc_t *container = get_focused_view(swayc_active_workspace());
80 if (container && inc &&
81 is_auto_layout(container->parent->layout) &&
82 ((int)container->parent->nb_master + inc >= 0)) {
83 for (int i = container->parent->nb_master;
84 i >= 0 && i < container->parent->children->length &&
85 i != (int) container->parent->nb_master + inc;) {
86 ((swayc_t *) container->parent->children->items[i])->height = -1;
87 ((swayc_t *) container->parent->children->items[i])->width = -1;
88 i += inc > 0 ? 1 : -1;
89 }
90 container->parent->nb_master += inc;
91 }
92 } else if ((strcasecmp(argv[0], "incncol") == 0) && argc ==2) {
93 const char *name = "layout incncol";
94 if ((error = checkarg(argc, name,
95 EXPECTED_EQUAL_TO, 2))) {
96 return error;
97 }
98 char *end;
99 int inc = (int) strtol(argv[1], &end, 10);
100 if (*end) {
101 return cmd_results_new(CMD_INVALID, name, "Invalid %s command "
102 "(argument must be an integer)", name);
103
104 }
105 swayc_t *container = get_focused_view(swayc_active_workspace());
106 if (container && inc && is_auto_layout(container->parent->layout) &&
107 ((int)container->parent->nb_slave_groups + inc >= 1)) {
108 container->parent->nb_slave_groups += inc;
109 }
110 } else if (strcasecmp(argv[0], "auto") == 0) {
111 if ((error = checkarg(argc, "auto", EXPECTED_EQUAL_TO, 2))) {
112 return error;
113 }
114 swayc_t *container = get_focused_view(swayc_active_workspace());
115 swayc_t *parent = container->parent;
116 enum swayc_layouts layout;
117 if (strcasecmp(argv[1], "next") == 0) {
118 if (is_auto_layout(parent->layout) && parent->layout < L_AUTO_LAST) {
119 layout = parent->layout + 1;
120 } else {
121 layout = L_AUTO_FIRST;
122 }
123 } else if (strcasecmp(argv[1], "prev") == 0) {
124 if (is_auto_layout(parent->layout) && parent->layout > L_AUTO_FIRST) {
125 layout = parent->layout - 1;
126 } else {
127 layout = L_AUTO_LAST;
128 }
129 } else {
130 return cmd_results_new(CMD_FAILURE, "layout auto",
131 "Must be one of <prev|next>.");
132 }
133 swayc_change_layout(parent, layout);
57 } 134 }
58 } 135 }
59 136
diff --git a/sway/commands/move.c b/sway/commands/move.c
index 4819d9ef..0b134494 100644
--- a/sway/commands/move.c
+++ b/sway/commands/move.c
@@ -13,7 +13,7 @@ struct cmd_results *cmd_move(int argc, char **argv) {
13 if ((error = checkarg(argc, "move", EXPECTED_AT_LEAST, 1))) { 13 if ((error = checkarg(argc, "move", EXPECTED_AT_LEAST, 1))) {
14 return error; 14 return error;
15 } 15 }
16 const char* expected_syntax = "Expected 'move <left|right|up|down>' or " 16 const char* expected_syntax = "Expected 'move <left|right|up|down|next|prev|first>' or "
17 "'move <container|window> to workspace <name>' or " 17 "'move <container|window> to workspace <name>' or "
18 "'move <container|window|workspace> to output <name|direction>' or " 18 "'move <container|window|workspace> to output <name|direction>' or "
19 "'move position mouse'"; 19 "'move position mouse'";
@@ -27,6 +27,12 @@ struct cmd_results *cmd_move(int argc, char **argv) {
27 move_container(view, MOVE_UP); 27 move_container(view, MOVE_UP);
28 } else if (strcasecmp(argv[0], "down") == 0) { 28 } else if (strcasecmp(argv[0], "down") == 0) {
29 move_container(view, MOVE_DOWN); 29 move_container(view, MOVE_DOWN);
30 } else if (strcasecmp(argv[0], "next") == 0) {
31 move_container(view, MOVE_NEXT);
32 } else if (strcasecmp(argv[0], "prev") == 0) {
33 move_container(view, MOVE_PREV);
34 } else if (strcasecmp(argv[0], "first") == 0) {
35 move_container(view, MOVE_FIRST);
30 } else if (strcasecmp(argv[0], "container") == 0 || strcasecmp(argv[0], "window") == 0) { 36 } else if (strcasecmp(argv[0], "container") == 0 || strcasecmp(argv[0], "window") == 0) {
31 // "move container ... 37 // "move container ...
32 if ((error = checkarg(argc, "move container/window", EXPECTED_AT_LEAST, 4))) { 38 if ((error = checkarg(argc, "move container/window", EXPECTED_AT_LEAST, 4))) {
diff --git a/sway/commands/resize.c b/sway/commands/resize.c
index 2c5b3f6b..28b20dc4 100644
--- a/sway/commands/resize.c
+++ b/sway/commands/resize.c
@@ -63,224 +63,135 @@ static bool resize_floating(int amount, bool use_width) {
63} 63}
64 64
65static bool resize_tiled(int amount, bool use_width) { 65static bool resize_tiled(int amount, bool use_width) {
66 swayc_t *parent = get_focused_view(swayc_active_workspace()); 66 swayc_t *container = get_focused_view(swayc_active_workspace());
67 swayc_t *focused = parent; 67 swayc_t *parent = container->parent;
68 swayc_t *sibling; 68 int idx_focused = 0;
69 if (!parent) { 69 bool use_major = false;
70 return true; 70 size_t nb_before = 0;
71 } 71 size_t nb_after = 0;
72 // Find the closest parent container which has siblings of the proper layout. 72
73 // Then apply the resize to all of them. 73 // 1. Identify a container ancestor that will allow the focused child to grow in the requested
74 int i; 74 // direction.
75 if (use_width) { 75 while (container->parent) {
76 int lnumber = 0; 76 parent = container->parent;
77 int rnumber = 0; 77 if ((parent->children && parent->children->length > 1)
78 while (parent->parent) { 78 && (is_auto_layout(parent->layout)
79 if (parent->parent->layout == L_HORIZ && parent->parent->children) { 79 || (use_width ? parent->layout == L_HORIZ : parent->layout == L_VERT))) {
80 for (i = 0; i < parent->parent->children->length; i++) { 80 // check if container has siblings that can provide/absorb the space needed for
81 sibling = parent->parent->children->items[i]; 81 // the resize operation.
82 if (sibling->x != focused->x) { 82 use_major = use_width
83 if (sibling->x < parent->x) { 83 ? parent->layout == L_AUTO_LEFT || parent->layout == L_AUTO_RIGHT
84 lnumber++; 84 : parent->layout == L_AUTO_TOP || parent->layout == L_AUTO_BOTTOM;
85 } else if (sibling->x > parent->x) { 85 // Note: use_major will be false for L_HORIZ and L_VERT
86 rnumber++; 86
87 } 87 idx_focused = index_child(container);
88 } 88 if (idx_focused < 0) {
89 } 89 sway_log(L_ERROR, "Something weird is happening, child container not "
90 if (rnumber || lnumber) { 90 "present in its parent's children list.");
91 break; 91 continue;
92 }
93 } 92 }
94 parent = parent->parent; 93 if (use_major) {
95 } 94 nb_before = auto_group_index(parent, idx_focused);
96 if (parent == &root_container) { 95 nb_after = auto_group_count(parent) - nb_before - 1;
97 return true;
98 }
99 sway_log(L_DEBUG, "Found the proper parent: %p. It has %d l conts, and %d r conts", parent->parent, lnumber, rnumber);
100 //TODO: Ensure rounding is done in such a way that there are NO pixel leaks
101 bool valid = true;
102 for (i = 0; i < parent->parent->children->length; i++) {
103 sibling = parent->parent->children->items[i];
104 if (sibling->x != focused->x) {
105 if (sibling->x < parent->x) {
106 double pixels = -1 * amount;
107 pixels /= lnumber;
108 if (rnumber) {
109 if ((sibling->width + pixels/2) < min_sane_w) {
110 valid = false;
111 break;
112 }
113 } else {
114 if ((sibling->width + pixels) < min_sane_w) {
115 valid = false;
116 break;
117 }
118 }
119 } else if (sibling->x > parent->x) {
120 double pixels = -1 * amount;
121 pixels /= rnumber;
122 if (lnumber) {
123 if ((sibling->width + pixels/2) < min_sane_w) {
124 valid = false;
125 break;
126 }
127 } else {
128 if ((sibling->width + pixels) < min_sane_w) {
129 valid = false;
130 break;
131 }
132 }
133 }
134 } else { 96 } else {
135 double pixels = amount; 97 nb_before = idx_focused - auto_group_start_index(parent, idx_focused);
136 if (parent->width + pixels < min_sane_w) { 98 nb_after = auto_group_end_index(parent, idx_focused) - idx_focused - 1;
137 valid = false; 99 sway_log(L_DEBUG, "+++ focused: %d, start: %d, end: %d, before: %d, after: %d",
138 break; 100 idx_focused,
139 } 101 (int)auto_group_start_index(parent, idx_focused),
102 (int)auto_group_end_index(parent, idx_focused),
103 (int)nb_before, (int)nb_after);
104
140 } 105 }
141 } 106 if (nb_before || nb_after) {
142 if (valid) { 107 break;
143 for (i = 0; i < parent->parent->children->length; i++) {
144 sibling = parent->parent->children->items[i];
145 if (sibling->x != focused->x) {
146 if (sibling->x < parent->x) {
147 double pixels = -1 * amount;
148 pixels /= lnumber;
149 if (rnumber) {
150 recursive_resize(sibling, pixels/2, WLC_RESIZE_EDGE_RIGHT);
151 } else {
152 recursive_resize(sibling, pixels, WLC_RESIZE_EDGE_RIGHT);
153 }
154 } else if (sibling->x > parent->x) {
155 double pixels = -1 * amount;
156 pixels /= rnumber;
157 if (lnumber) {
158 recursive_resize(sibling, pixels/2, WLC_RESIZE_EDGE_LEFT);
159 } else {
160 recursive_resize(sibling, pixels, WLC_RESIZE_EDGE_LEFT);
161 }
162 }
163 } else {
164 if (rnumber != 0 && lnumber != 0) {
165 double pixels = amount;
166 pixels /= 2;
167 recursive_resize(parent, pixels, WLC_RESIZE_EDGE_LEFT);
168 recursive_resize(parent, pixels, WLC_RESIZE_EDGE_RIGHT);
169 } else if (rnumber) {
170 recursive_resize(parent, amount, WLC_RESIZE_EDGE_RIGHT);
171 } else if (lnumber) {
172 recursive_resize(parent, amount, WLC_RESIZE_EDGE_LEFT);
173 }
174 }
175 } 108 }
176 // Recursive resize does not handle positions, let arrange_windows
177 // take care of that.
178 arrange_windows(swayc_active_workspace(), -1, -1);
179 } 109 }
110 container = parent; /* continue up the tree to the next ancestor */
111 }
112 if (parent == &root_container) {
180 return true; 113 return true;
181 } else { 114 }
182 int tnumber = 0; 115 sway_log(L_DEBUG, "Found the proper parent: %p. It has %zu before conts, "
183 int bnumber = 0; 116 "and %zu after conts", parent, nb_before, nb_after);
184 while (parent->parent) { 117 // 2. Ensure that the resize operation will not make one of the resized containers drop
185 if (parent->parent->layout == L_VERT) { 118 // below the "sane" size threshold.
186 for (i = 0; i < parent->parent->children->length; i++) { 119 bool valid = true;
187 sibling = parent->parent->children->items[i]; 120 swayc_t *focused = parent->children->items[idx_focused];
188 if (sibling->y != focused->y) { 121 int start = use_major ? 0 : auto_group_start_index(parent, idx_focused);
189 if (sibling->y < parent->y) { 122 int end = use_major ? parent->children->length : auto_group_end_index(parent, idx_focused);
190 bnumber++; 123 sway_log(L_DEBUG, "Check children of container %p [%d,%d[", container, start, end);
191 } else if (sibling->y > parent->y) { 124 for (int i = start; i < end; ) {
192 tnumber++; 125 swayc_t *sibling = parent->children->items[i];
193 } 126 double pixels = amount;
194 } 127 bool is_before = use_width ? sibling->x < focused->x : sibling->y < focused->y;
195 } 128 bool is_after = use_width ? sibling->x > focused->x : sibling->y > focused->y;
196 if (bnumber || tnumber) { 129 if (is_before || is_after) {
197 break; 130 pixels = -pixels;
198 } 131 pixels /= is_before ? nb_before : nb_after;
132 if (nb_after != 0 && nb_before != 0) {
133 pixels /= 2;
199 } 134 }
200 parent = parent->parent;
201 } 135 }
202 if (parent->parent == NULL || parent->parent->children == NULL) { 136 sway_log(L_DEBUG, "Check container %p: width %g vs %d, height %g vs %d", sibling, sibling->width + pixels, min_sane_w, sibling->height + pixels, min_sane_h);
203 return true; 137 if (use_width ?
138 sibling->width + pixels < min_sane_w :
139 sibling->height + pixels < min_sane_h) {
140 valid = false;
141 sway_log(L_DEBUG, "Container size no longer sane");
142 break;
204 } 143 }
205 sway_log(L_DEBUG, "Found the proper parent: %p. It has %d b conts, and %d t conts", parent->parent, bnumber, tnumber); 144 i = use_major ? auto_group_end_index(parent, i) : (i + 1);
206 //TODO: Ensure rounding is done in such a way that there are NO pixel leaks 145 sway_log(L_DEBUG, "+++++ check %i", i);
207 bool valid = true; 146 }
208 for (i = 0; i < parent->parent->children->length; i++) { 147 // 3. Apply the size change
209 sibling = parent->parent->children->items[i]; 148 if (valid) {
210 if (sibling->y != focused->y) { 149 for (int i = start; i < end; ) {
211 if (sibling->y < parent->y) { 150 int next_i = use_major ? auto_group_end_index(parent, i) : (i + 1);
212 double pixels = -1 * amount; 151 swayc_t *sibling = parent->children->items[i];
213 pixels /= bnumber; 152 double pixels = amount;
214 if (tnumber) { 153 bool is_before = use_width ? sibling->x < focused->x : sibling->y < focused->y;
215 if ((sibling->height + pixels/2) < min_sane_h) { 154 bool is_after = use_width ? sibling->x > focused->x : sibling->y > focused->y;
216 valid = false; 155 if (is_before || is_after) {
217 break; 156 pixels = -pixels;
218 } 157 pixels /= is_before ? nb_before : nb_after;
219 } else { 158 if (nb_after != 0 && nb_before != 0) {
220 if ((sibling->height + pixels) < min_sane_h) { 159 pixels /= 2;
221 valid = false; 160 }
222 break; 161 sway_log(L_DEBUG, "%p: %s", sibling, is_before ? "before" : "after");
223 } 162 if (use_major) {
224 } 163 for (int j = i; j < next_i; ++j) {
225 } else if (sibling->y > parent->y) { 164 recursive_resize(parent->children->items[j], pixels,
226 double pixels = -1 * amount; 165 use_width ?
227 pixels /= tnumber; 166 (is_before ? WLC_RESIZE_EDGE_RIGHT : WLC_RESIZE_EDGE_LEFT) :
228 if (bnumber) { 167 (is_before ? WLC_RESIZE_EDGE_BOTTOM : WLC_RESIZE_EDGE_TOP));
229 if ((sibling->height + pixels/2) < min_sane_h) {
230 valid = false;
231 break;
232 }
233 } else {
234 if ((sibling->height + pixels) < min_sane_h) {
235 valid = false;
236 break;
237 }
238 } 168 }
169 } else {
170 recursive_resize(sibling, pixels,
171 use_width ?
172 (is_before ? WLC_RESIZE_EDGE_RIGHT : WLC_RESIZE_EDGE_LEFT) :
173 (is_before ? WLC_RESIZE_EDGE_BOTTOM : WLC_RESIZE_EDGE_TOP));
239 } 174 }
240 } else { 175 } else {
241 double pixels = amount; 176 if (use_major) {
242 if (parent->height + pixels < min_sane_h) { 177 for (int j = i; j < next_i; ++j) {
243 valid = false; 178 recursive_resize(parent->children->items[j], pixels / 2,
244 break; 179 use_width ? WLC_RESIZE_EDGE_LEFT : WLC_RESIZE_EDGE_TOP);
245 } 180 recursive_resize(parent->children->items[j], pixels / 2,
246 } 181 use_width ? WLC_RESIZE_EDGE_RIGHT : WLC_RESIZE_EDGE_BOTTOM);
247 }
248 if (valid) {
249 for (i = 0; i < parent->parent->children->length; i++) {
250 sibling = parent->parent->children->items[i];
251 if (sibling->y != focused->y) {
252 if (sibling->y < parent->y) {
253 double pixels = -1 * amount;
254 pixels /= bnumber;
255 if (tnumber) {
256 recursive_resize(sibling, pixels/2, WLC_RESIZE_EDGE_BOTTOM);
257 } else {
258 recursive_resize(sibling, pixels, WLC_RESIZE_EDGE_BOTTOM);
259 }
260 } else if (sibling->x > parent->x) {
261 double pixels = -1 * amount;
262 pixels /= tnumber;
263 if (bnumber) {
264 recursive_resize(sibling, pixels/2, WLC_RESIZE_EDGE_TOP);
265 } else {
266 recursive_resize(sibling, pixels, WLC_RESIZE_EDGE_TOP);
267 }
268 } 182 }
269 } else { 183 } else {
270 if (bnumber != 0 && tnumber != 0) { 184 recursive_resize(sibling, pixels / 2,
271 double pixels = amount/2; 185 use_width ? WLC_RESIZE_EDGE_LEFT : WLC_RESIZE_EDGE_TOP);
272 recursive_resize(parent, pixels, WLC_RESIZE_EDGE_TOP); 186 recursive_resize(sibling, pixels / 2,
273 recursive_resize(parent, pixels, WLC_RESIZE_EDGE_BOTTOM); 187 use_width ? WLC_RESIZE_EDGE_RIGHT : WLC_RESIZE_EDGE_BOTTOM);
274 } else if (tnumber) {
275 recursive_resize(parent, amount, WLC_RESIZE_EDGE_TOP);
276 } else if (bnumber) {
277 recursive_resize(parent, amount, WLC_RESIZE_EDGE_BOTTOM);
278 }
279 } 188 }
280 } 189 }
281 arrange_windows(swayc_active_workspace(), -1, -1); 190 i = next_i;
282 } 191 }
283 return true; 192 // Recursive resize does not handle positions, let arrange_windows
193 // take care of that.
194 arrange_windows(swayc_active_workspace(), -1, -1);
284 } 195 }
285 return true; 196 return true;
286} 197}
diff --git a/sway/commands/workspace_layout.c b/sway/commands/workspace_layout.c
index b7b4b033..3e0a12ce 100644
--- a/sway/commands/workspace_layout.c
+++ b/sway/commands/workspace_layout.c
@@ -13,8 +13,16 @@ struct cmd_results *cmd_workspace_layout(int argc, char **argv) {
13 config->default_layout = L_STACKED; 13 config->default_layout = L_STACKED;
14 } else if (strcasecmp(argv[0], "tabbed") == 0) { 14 } else if (strcasecmp(argv[0], "tabbed") == 0) {
15 config->default_layout = L_TABBED; 15 config->default_layout = L_TABBED;
16 } else if (strcasecmp(argv[0], "auto_left") == 0) {
17 config->default_layout = L_AUTO_LEFT;
18 } else if (strcasecmp(argv[0], "auto_right") == 0) {
19 config->default_layout = L_AUTO_RIGHT;
20 } else if (strcasecmp(argv[0], "auto_top") == 0) {
21 config->default_layout = L_AUTO_TOP;
22 } else if (strcasecmp(argv[0], "auto_bottom") == 0) {
23 config->default_layout = L_AUTO_BOTTOM;
16 } else { 24 } else {
17 return cmd_results_new(CMD_INVALID, "workspace_layout", "Expected 'workspace_layout <default|stacking|tabbed>'"); 25 return cmd_results_new(CMD_INVALID, "workspace_layout", "Expected 'workspace_layout <default|stacking|tabbed|auto_left|auto_right|auto_top|auto_bottom>'");
18 } 26 }
19 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 27 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
20} 28}
diff --git a/sway/container.c b/sway/container.c
index e557f450..3bdc8b6c 100644
--- a/sway/container.c
+++ b/sway/container.c
@@ -32,6 +32,8 @@ static swayc_t *new_swayc(enum swayc_types type) {
32 c->layout = L_NONE; 32 c->layout = L_NONE;
33 c->workspace_layout = L_NONE; 33 c->workspace_layout = L_NONE;
34 c->type = type; 34 c->type = type;
35 c->nb_master = 1;
36 c->nb_slave_groups = 1;
35 if (type != C_VIEW) { 37 if (type != C_VIEW) {
36 c->children = create_list(); 38 c->children = create_list();
37 } 39 }
@@ -960,11 +962,28 @@ swayc_t *swayc_tabbed_stacked_parent(swayc_t *con) {
960} 962}
961 963
962swayc_t *swayc_change_layout(swayc_t *container, enum swayc_layouts layout) { 964swayc_t *swayc_change_layout(swayc_t *container, enum swayc_layouts layout) {
965 // if layout change modifies the auto layout's major axis, swap width and height
966 // to preserve current ratios.
967 if (is_auto_layout(layout) && is_auto_layout(container->layout)) {
968 enum swayc_layouts prev_major = (container->layout == L_AUTO_LEFT ||
969 container->layout == L_AUTO_RIGHT)
970 ? L_HORIZ : L_VERT;
971 enum swayc_layouts new_major = (layout == L_AUTO_LEFT || layout == L_AUTO_RIGHT)
972 ? L_HORIZ : L_VERT;
973 if (new_major != prev_major) {
974 for (int i = 0; i < container->children->length; ++i) {
975 swayc_t *child = container->children->items[i];
976 double h = child->height;
977 child->height = child->width;
978 child->width = h;
979 }
980 }
981 }
963 if (container->type == C_WORKSPACE) { 982 if (container->type == C_WORKSPACE) {
964 container->workspace_layout = layout; 983 container->workspace_layout = layout;
965 if (layout == L_HORIZ || layout == L_VERT) { 984 if (layout == L_HORIZ || layout == L_VERT || is_auto_layout(layout)) {
966 container->layout = layout; 985 container->layout = layout;
967 } 986 }
968 } else { 987 } else {
969 container->layout = layout; 988 container->layout = layout;
970 } 989 }
diff --git a/sway/debug_log.c b/sway/debug_log.c
index 8d891a44..d1eafae8 100644
--- a/sway/debug_log.c
+++ b/sway/debug_log.c
@@ -38,6 +38,10 @@ static void container_log(const swayc_t *c, int depth) {
38 c->layout == L_STACKED ? "Stack": 38 c->layout == L_STACKED ? "Stack":
39 c->layout == L_TABBED ? "Tab": 39 c->layout == L_TABBED ? "Tab":
40 c->layout == L_FLOATING ? "Float": 40 c->layout == L_FLOATING ? "Float":
41 c->layout == L_AUTO_LEFT ? "A_lft":
42 c->layout == L_AUTO_RIGHT ? "A_rgt":
43 c->layout == L_AUTO_TOP ? "A_top":
44 c->layout == L_AUTO_BOTTOM ? "A_bot":
41 "Unknown"); 45 "Unknown");
42 fprintf(stderr, "w:%4.f|h:%4.f|", c->width, c->height); 46 fprintf(stderr, "w:%4.f|h:%4.f|", c->width, c->height);
43 fprintf(stderr, "x:%4.f|y:%4.f|", c->x, c->y); 47 fprintf(stderr, "x:%4.f|y:%4.f|", c->x, c->y);
diff --git a/sway/layout.c b/sway/layout.c
index ea4a680d..e2f31848 100644
--- a/sway/layout.c
+++ b/sway/layout.c
@@ -118,7 +118,11 @@ swayc_t *add_sibling(swayc_t *fixed, swayc_t *active) {
118 list_add(parent->floating, active); 118 list_add(parent->floating, active);
119 } else { 119 } else {
120 int i = index_child(fixed); 120 int i = index_child(fixed);
121 list_insert(parent->children, i + 1, active); 121 if (is_auto_layout(parent->layout)) {
122 list_add(parent->children, active);
123 } else {
124 list_insert(parent->children, i + 1, active);
125 }
122 } 126 }
123 } 127 }
124 active->parent = parent; 128 active->parent = parent;
@@ -247,19 +251,32 @@ void swap_geometry(swayc_t *a, swayc_t *b) {
247} 251}
248 252
249void move_container(swayc_t *container, enum movement_direction dir) { 253void move_container(swayc_t *container, enum movement_direction dir) {
250 enum swayc_layouts layout; 254 enum swayc_layouts layout = L_NONE;
251 if (container->is_floating 255 swayc_t *parent = container->parent;
252 || (container->type != C_VIEW && container->type != C_CONTAINER)) { 256 if (container->is_floating || (container->type != C_VIEW && container->type != C_CONTAINER)) {
253 return; 257 return;
254 } 258 }
255 if (dir == MOVE_UP || dir == MOVE_DOWN) { 259 if (dir == MOVE_UP || dir == MOVE_DOWN) {
256 layout = L_VERT; 260 layout = L_VERT;
257 } else if (dir == MOVE_LEFT || dir == MOVE_RIGHT) { 261 } else if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
258 layout = L_HORIZ; 262 layout = L_HORIZ;
259 } else { 263 } else if (dir == MOVE_FIRST) {
264 // swap first child in auto layout with currently focused child
265 if (is_auto_layout(parent->layout)) {
266 int focused_idx = index_child(container);
267 swayc_t *first = parent->children->items[0];
268 if (focused_idx > 0) {
269 list_swap(parent->children, 0, focused_idx);
270 swap_geometry(first, container);
271 }
272 arrange_windows(parent->parent, -1, -1);
273 ipc_event_window(container, "move");
274 set_focused_container_for(parent->parent, container);
275 }
276 return;
277 } else if (! (dir == MOVE_NEXT || dir == MOVE_PREV)) {
260 return; 278 return;
261 } 279 }
262 swayc_t *parent = container->parent;
263 swayc_t *child = container; 280 swayc_t *child = container;
264 bool ascended = false; 281 bool ascended = false;
265 282
@@ -279,19 +296,53 @@ void move_container(swayc_t *container, enum movement_direction dir) {
279 sway_log(L_DEBUG, "container:%p, parent:%p, child %p,", 296 sway_log(L_DEBUG, "container:%p, parent:%p, child %p,",
280 container,parent,child); 297 container,parent,child);
281 if (parent->layout == layout 298 if (parent->layout == layout
299 || layout == L_NONE /* accept any layout for next/prev direction */
282 || (parent->layout == L_TABBED && layout == L_HORIZ) 300 || (parent->layout == L_TABBED && layout == L_HORIZ)
283 || (parent->layout == L_STACKED && layout == L_VERT)) { 301 || (parent->layout == L_STACKED && layout == L_VERT)
302 || is_auto_layout(parent->layout)) {
284 int diff; 303 int diff;
285 // If it has ascended (parent has moved up), no container is removed 304 // If it has ascended (parent has moved up), no container is removed
286 // so insert it at index, or index+1. 305 // so insert it at index, or index+1.
287 // if it has not, the moved container is removed, so it needs to be 306 // if it has not, the moved container is removed, so it needs to be
288 // inserted at index-1, or index+1 307 // inserted at index-1, or index+1
289 if (ascended) { 308 if (ascended) {
290 diff = dir == MOVE_LEFT || dir == MOVE_UP ? 0 : 1; 309 diff = dir == MOVE_LEFT || dir == MOVE_UP || dir == MOVE_PREV ? 0 : 1;
291 } else { 310 } else {
292 diff = dir == MOVE_LEFT || dir == MOVE_UP ? -1 : 1; 311 diff = dir == MOVE_LEFT || dir == MOVE_UP || dir == MOVE_PREV ? -1 : 1;
312 }
313 int idx = index_child(child);
314 int desired = idx + diff;
315 if (dir == MOVE_NEXT || dir == MOVE_PREV) {
316 // Next/Prev always wrap.
317 if (desired < 0) {
318 desired += parent->children->length;
319 } else if (desired >= parent->children->length) {
320 desired = 0;
321 }
322 // if move command makes container change from master to slave
323 // (or the contrary), reset its geometry an the one of the replaced item.
324 if (parent->nb_master &&
325 (size_t) parent->children->length > parent->nb_master) {
326 swayc_t *swap_geom = NULL;
327 // if child is being promoted/demoted, it will swap geometry
328 // with the sibling being demoted/promoted.
329 if ((dir == MOVE_NEXT && desired == 0)
330 || (dir == MOVE_PREV && (size_t) desired == parent->nb_master - 1)) {
331 swap_geom = parent->children->items[parent->nb_master - 1];
332 } else if ((dir == MOVE_NEXT && (size_t) desired == parent->nb_master)
333 || (dir == MOVE_PREV && desired == parent->children->length - 1)) {
334 swap_geom = parent->children->items[parent->nb_master];
335 }
336 if (swap_geom) {
337 double h = child->height;
338 double w = child->width;
339 child->width = swap_geom->width;
340 child->height = swap_geom->height;
341 swap_geom->width = w;
342 swap_geom->height = h;
343 }
344 }
293 } 345 }
294 int desired = index_child(child) + diff;
295 // when it has ascended, legal insertion position is 0:len 346 // when it has ascended, legal insertion position is 0:len
296 // when it has not, legal insertion position is 0:len-1 347 // when it has not, legal insertion position is 0:len-1
297 if (desired >= 0 && desired - ascended < parent->children->length) { 348 if (desired >= 0 && desired - ascended < parent->children->length) {
@@ -304,7 +355,8 @@ void move_container(swayc_t *container, enum movement_direction dir) {
304 // insert it next to focused container 355 // insert it next to focused container
305 if (parent->layout == layout 356 if (parent->layout == layout
306 || (parent->layout == L_TABBED && layout == L_HORIZ) 357 || (parent->layout == L_TABBED && layout == L_HORIZ)
307 || (parent->layout == L_STACKED && layout == L_VERT)) { 358 || (parent->layout == L_STACKED && layout == L_VERT)
359 || is_auto_layout(parent->layout)) {
308 desired = (diff < 0) * parent->children->length; 360 desired = (diff < 0) * parent->children->length;
309 } else { 361 } else {
310 desired = index_child(child->focused) + 1; 362 desired = index_child(child->focused) + 1;
@@ -321,7 +373,7 @@ void move_container(swayc_t *container, enum movement_direction dir) {
321 } 373 }
322 } 374 }
323 // Change parent layout if we need to 375 // Change parent layout if we need to
324 if (parent->children->length == 1 && parent->layout != layout) { 376 if (parent->children->length == 1 && parent->layout != layout && layout != L_NONE) {
325 /* swayc_change_layout(parent, layout); */ 377 /* swayc_change_layout(parent, layout); */
326 parent->layout = layout; 378 parent->layout = layout;
327 continue; 379 continue;
@@ -769,6 +821,26 @@ void update_geometry(swayc_t *container) {
769 } 821 }
770} 822}
771 823
824/**
825 * Layout application prototypes
826 */
827static void apply_horiz_layout(swayc_t *container, const double x,
828 const double y, const double width,
829 const double height, const int start,
830 const int end);
831static void apply_vert_layout(swayc_t *container, const double x,
832 const double y, const double width,
833 const double height, const int start,
834 const int end);
835static void apply_tabbed_or_stacked_layout(swayc_t *container, double x,
836 double y, double width,
837 double height);
838
839static void apply_auto_layout(swayc_t *container, const double x, const double y,
840 const double width, const double height,
841 enum swayc_layouts group_layout,
842 bool master_first);
843
772static void arrange_windows_r(swayc_t *container, double width, double height) { 844static void arrange_windows_r(swayc_t *container, double width, double height) {
773 int i; 845 int i;
774 if (width == -1 || height == -1) { 846 if (width == -1 || height == -1) {
@@ -776,14 +848,15 @@ static void arrange_windows_r(swayc_t *container, double width, double height) {
776 width = container->width; 848 width = container->width;
777 height = container->height; 849 height = container->height;
778 } 850 }
779 // pixels are indivisable. if we don't round the pixels, then the view 851 // pixels are indivisible. if we don't round the pixels, then the view
780 // calculations will be off (e.g. 50.5 + 50.5 = 101, but in reality it's 852 // calculations will be off (e.g. 50.5 + 50.5 = 101, but in reality it's
781 // 50 + 50 = 100). doing it here cascades properly to all width/height/x/y. 853 // 50 + 50 = 100). doing it here cascades properly to all width/height/x/y.
782 width = floor(width); 854 width = floor(width);
783 height = floor(height); 855 height = floor(height);
784 856
785 sway_log(L_DEBUG, "Arranging layout for %p %s %fx%f+%f,%f", container, 857 sway_log(L_DEBUG, "Arranging layout for %p %s %fx%f+%f,%f", container,
786 container->name, container->width, container->height, container->x, container->y); 858 container->name, container->width, container->height, container->x,
859 container->y);
787 860
788 double x = 0, y = 0; 861 double x = 0, y = 0;
789 switch (container->type) { 862 switch (container->type) {
@@ -895,135 +968,298 @@ static void arrange_windows_r(swayc_t *container, double width, double height) {
895 break; 968 break;
896 } 969 }
897 970
898 double scale = 0;
899 switch (container->layout) { 971 switch (container->layout) {
900 case L_HORIZ: 972 case L_HORIZ:
901 default: 973 default:
902 // Calculate total width 974 apply_horiz_layout(container, x, y, width, height, 0,
903 for (i = 0; i < container->children->length; ++i) { 975 container->children->length);
904 double *old_width = &((swayc_t *)container->children->items[i])->width; 976 break;
905 if (*old_width <= 0) { 977 case L_VERT:
906 if (container->children->length > 1) { 978 apply_vert_layout(container, x, y, width, height, 0,
907 *old_width = width / (container->children->length - 1); 979 container->children->length);
908 } else { 980 break;
909 *old_width = width; 981 case L_TABBED:
910 } 982 case L_STACKED:
911 } 983 apply_tabbed_or_stacked_layout(container, x, y, width, height);
912 scale += *old_width; 984 break;
913 } 985 case L_AUTO_LEFT:
914 986 apply_auto_layout(container, x, y, width, height, L_VERT, true);
915 // Resize windows 987 break;
916 if (scale > 0.1) { 988 case L_AUTO_RIGHT:
917 scale = width / scale; 989 apply_auto_layout(container, x, y, width, height, L_VERT, false);
918 sway_log(L_DEBUG, "Arranging %p horizontally", container); 990 break;
919 swayc_t *focused = NULL; 991 case L_AUTO_TOP:
920 for (i = 0; i < container->children->length; ++i) { 992 apply_auto_layout(container, x, y, width, height, L_HORIZ, true);
921 swayc_t *child = container->children->items[i]; 993 break;
922 sway_log(L_DEBUG, "Calculating arrangement for %p:%d (will scale %f by %f)", child, child->type, width, scale); 994 case L_AUTO_BOTTOM:
923 child->x = x; 995 apply_auto_layout(container, x, y, width, height, L_HORIZ, false);
924 child->y = y; 996 break;
925 997 }
926 if (child == container->focused) {
927 focused = child;
928 }
929 998
930 if (i == container->children->length - 1) { 999 // Arrage floating layouts for workspaces last
931 double remaining_width = container->x + width - x; 1000 if (container->type == C_WORKSPACE) {
932 arrange_windows_r(child, remaining_width, height); 1001 for (int i = 0; i < container->floating->length; ++i) {
933 } else { 1002 swayc_t *view = container->floating->items[i];
934 arrange_windows_r(child, child->width * scale, height); 1003 if (view->type == C_VIEW) {
1004 update_geometry(view);
1005 sway_log(L_DEBUG, "Set floating view to %.f x %.f @ %.f, %.f",
1006 view->width, view->height, view->x, view->y);
1007 if (swayc_is_fullscreen(view)) {
1008 wlc_view_bring_to_front(view->handle);
1009 } else if (!container->focused ||
1010 !swayc_is_fullscreen(container->focused)) {
1011 wlc_view_bring_to_front(view->handle);
935 } 1012 }
936 x += child->width;
937 } 1013 }
1014 }
1015 }
1016}
938 1017
939 // update focused view border last because it may 1018void apply_horiz_layout(swayc_t *container, const double x, const double y,
940 // depend on the title bar geometry of its siblings. 1019 const double width, const double height,
941 if (focused && container->children->length > 1) { 1020 const int start, const int end) {
942 update_container_border(focused); 1021 double scale = 0;
1022 // Calculate total width
1023 for (int i = start; i < end; ++i) {
1024 double *old_width = &((swayc_t *)container->children->items[i])->width;
1025 if (*old_width <= 0) {
1026 if (end - start > 1) {
1027 *old_width = width / (end - start - 1);
1028 } else {
1029 *old_width = width;
943 } 1030 }
944 } 1031 }
945 break; 1032 scale += *old_width;
946 case L_VERT: 1033 }
947 // Calculate total height 1034 scale = width / scale;
948 for (i = 0; i < container->children->length; ++i) { 1035
949 double *old_height = &((swayc_t *)container->children->items[i])->height; 1036 // Resize windows
950 if (*old_height <= 0) { 1037 double child_x = x;
951 if (container->children->length > 1) { 1038 if (scale > 0.1) {
952 *old_height = height / (container->children->length - 1); 1039 sway_log(L_DEBUG, "Arranging %p horizontally", container);
953 } else { 1040 swayc_t *focused = NULL;
954 *old_height = height; 1041 for (int i = start; i < end; ++i) {
955 } 1042 swayc_t *child = container->children->items[i];
1043 sway_log(L_DEBUG,
1044 "Calculating arrangement for %p:%d (will scale %f by %f)", child,
1045 child->type, width, scale);
1046 child->x = child_x;
1047 child->y = y;
1048
1049 if (child == container->focused) {
1050 focused = child;
956 } 1051 }
957 scale += *old_height;
958 }
959 // Resize
960 if (scale > 0.1) {
961 scale = height / scale;
962 sway_log(L_DEBUG, "Arranging %p vertically", container);
963 swayc_t *focused = NULL;
964 for (i = 0; i < container->children->length; ++i) {
965 swayc_t *child = container->children->items[i];
966 sway_log(L_DEBUG, "Calculating arrangement for %p:%d (will scale %f by %f)", child, child->type, height, scale);
967 child->x = x;
968 child->y = y;
969
970 if (child == container->focused) {
971 focused = child;
972 }
973 1052
974 if (i == container->children->length - 1) { 1053 if (i == end - 1) {
975 double remaining_height = container->y + height - y; 1054 double remaining_width = x + width - child_x;
976 arrange_windows_r(child, width, remaining_height); 1055 arrange_windows_r(child, remaining_width, height);
977 } else { 1056 } else {
978 arrange_windows_r(child, width, child->height * scale); 1057 arrange_windows_r(child, child->width * scale, height);
979 }
980 y += child->height;
981 } 1058 }
1059 child_x += child->width;
1060 }
982 1061
983 // update focused view border last because it may 1062 // update focused view border last because it may
984 // depend on the title bar geometry of its siblings. 1063 // depend on the title bar geometry of its siblings.
985 if (focused && container->children->length > 1) { 1064 if (focused && container->children->length > 1) {
986 update_container_border(focused); 1065 update_container_border(focused);
1066 }
1067 }
1068}
1069
1070void apply_vert_layout(swayc_t *container, const double x, const double y,
1071 const double width, const double height, const int start,
1072 const int end) {
1073 int i;
1074 double scale = 0;
1075 // Calculate total height
1076 for (i = start; i < end; ++i) {
1077 double *old_height = &((swayc_t *)container->children->items[i])->height;
1078 if (*old_height <= 0) {
1079 if (end - start > 1) {
1080 *old_height = height / (end - start - 1);
1081 } else {
1082 *old_height = height;
987 } 1083 }
988 } 1084 }
989 break; 1085 scale += *old_height;
990 case L_TABBED: 1086 }
991 case L_STACKED: 1087 scale = height / scale;
992 { 1088
993 swayc_t *focused = NULL; 1089 // Resize
994 for (i = 0; i < container->children->length; ++i) { 1090 double child_y = y;
995 swayc_t *child = container->children->items[i]; 1091 if (scale > 0.1) {
996 child->x = x; 1092 sway_log(L_DEBUG, "Arranging %p vertically", container);
997 child->y = y; 1093 swayc_t *focused = NULL;
998 if (child == container->focused) { 1094 for (i = start; i < end; ++i) {
999 focused = child; 1095 swayc_t *child = container->children->items[i];
1000 } else { 1096 sway_log(L_DEBUG,
1001 arrange_windows_r(child, width, height); 1097 "Calculating arrangement for %p:%d (will scale %f by %f)", child,
1002 } 1098 child->type, height, scale);
1099 child->x = x;
1100 child->y = child_y;
1101
1102 if (child == container->focused) {
1103 focused = child;
1003 } 1104 }
1004 1105
1005 if (focused) { 1106 if (i == end - 1) {
1006 arrange_windows_r(focused, width, height); 1107 double remaining_height = y + height - child_y;
1108 arrange_windows_r(child, width, remaining_height);
1109 } else {
1110 arrange_windows_r(child, width, child->height * scale);
1007 } 1111 }
1008 break; 1112 child_y += child->height;
1113 }
1114
1115 // update focused view border last because it may
1116 // depend on the title bar geometry of its siblings.
1117 if (focused && container->children->length > 1) {
1118 update_container_border(focused);
1009 } 1119 }
1010 } 1120 }
1121}
1011 1122
1012 // Arrage floating layouts for workspaces last 1123void apply_tabbed_or_stacked_layout(swayc_t *container, double x, double y,
1013 if (container->type == C_WORKSPACE) { 1124 double width, double height) {
1014 for (i = 0; i < container->floating->length; ++i) { 1125 int i;
1015 swayc_t *view = container->floating->items[i]; 1126 swayc_t *focused = NULL;
1016 if (view->type == C_VIEW) { 1127 for (i = 0; i < container->children->length; ++i) {
1017 update_geometry(view); 1128 swayc_t *child = container->children->items[i];
1018 sway_log(L_DEBUG, "Set floating view to %.f x %.f @ %.f, %.f", view->width, 1129 child->x = x;
1019 view->height, view->x, view->y); 1130 child->y = y;
1020 if (swayc_is_fullscreen(view)) { 1131 if (child == container->focused) {
1021 wlc_view_bring_to_front(view->handle); 1132 focused = child;
1022 } else if (!container->focused 1133 } else {
1023 || !swayc_is_fullscreen(container->focused)) { 1134 arrange_windows_r(child, width, height);
1024 wlc_view_bring_to_front(view->handle); 1135 }
1136 }
1137
1138 if (focused) {
1139 arrange_windows_r(focused, width, height);
1140 }
1141}
1142
1143void apply_auto_layout(swayc_t *container, const double x, const double y,
1144 const double width, const double height,
1145 enum swayc_layouts group_layout,
1146 bool master_first) {
1147 // Auto layout "container" in width x height @ x, y
1148 // using "group_layout" for each of the groups in the container.
1149 // There is one "master" group, plus container->nb_slave_groups.
1150 // Each group is layed out side by side following the "major" axis.
1151 // The direction of the layout used for groups is the "minor" axis.
1152 // Example:
1153 //
1154 // ---- major axis -->
1155 // +---------+-----------+
1156 // | | | |
1157 // | master | slave 1 | |
1158 // | +-----------+ | minor axis (direction of group_layout)
1159 // | | | |
1160 // | | slave 2 | V
1161 // +---------+-----------+
1162 //
1163 // container with three children (one master and two slaves) and
1164 // a single slave group (containing slave 1 and 2). The master
1165 // group and slave group are layed out using L_VERT.
1166
1167 size_t nb_groups = auto_group_count(container);
1168
1169 // the target dimension of the container along the "major" axis, each
1170 // group in the container will be layed out using "group_layout" along
1171 // the "minor" axis.
1172 double dim_maj;
1173 double pos_maj;
1174
1175 // x and y coords for the next group to be laid out.
1176 const double *group_x, *group_y;
1177
1178 // pos of the next group to layout along the major axis
1179 double pos;
1180
1181 // size of the next group along the major axis.
1182 double group_dim;
1183
1184 // height and width of next group to be laid out.
1185 const double *group_h, *group_w;
1186
1187 switch (group_layout) {
1188 default:
1189 sway_log(L_DEBUG, "Unknown layout type (%d) used in %s()",
1190 group_layout, __func__);
1191 /* fall through */
1192 case L_VERT:
1193 dim_maj = width;
1194 pos_maj = x;
1195
1196 group_x = &pos;
1197 group_y = &y;
1198 group_w = &group_dim;
1199 group_h = &height;
1200 break;
1201 case L_HORIZ:
1202 dim_maj = height;
1203 pos_maj = y;
1204
1205 group_x = &x;
1206 group_y = &pos;
1207 group_w = &width;
1208 group_h = &group_dim;
1209 break;
1210 }
1211
1212 /* Determine the dimension of each of the groups in the layout.
1213 * Dimension will be width for a VERT layout and height for a HORIZ
1214 * layout. */
1215 double old_group_dim[nb_groups];
1216 double old_dim = 0;
1217 for (size_t group = 0; group < nb_groups; ++group) {
1218 int idx;
1219 if (auto_group_bounds(container, group, &idx, NULL)) {
1220 swayc_t *child = container->children->items[idx];
1221 double *dim = group_layout == L_HORIZ ? &child->height : &child->width;
1222 if (*dim <= 0) {
1223 // New child with uninitialized dimension
1224 *dim = dim_maj;
1225 if (nb_groups > 1) {
1226 // child gets a dimension proportional to existing groups,
1227 // it will be later scaled based on to the available size
1228 // in the major axis.
1229 *dim /= (nb_groups - 1);
1025 } 1230 }
1026 } 1231 }
1232 old_dim += *dim;
1233 old_group_dim[group] = *dim;
1234 }
1235 }
1236 double scale = dim_maj / old_dim;
1237
1238 /* Apply layout to each group */
1239 pos = pos_maj;
1240
1241 for (size_t group = 0; group < nb_groups; ++group) {
1242 int start, end; // index of first (inclusive) and last (exclusive) child in the group
1243 if (auto_group_bounds(container, group, &start, &end)) {
1244 // adjusted size of the group
1245 group_dim = old_group_dim[group] * scale;
1246 if (group == nb_groups - 1) {
1247 group_dim = pos_maj + dim_maj - pos; // remaining width
1248 }
1249 sway_log(L_DEBUG, "Arranging container %p column %zu, children [%d,%d[ (%fx%f+%f,%f)",
1250 container, group, start, end, *group_w, *group_h, *group_x, *group_y);
1251 switch (group_layout) {
1252 default:
1253 case L_VERT:
1254 apply_vert_layout(container, *group_x, *group_y, *group_w, *group_h, start, end);
1255 break;
1256 case L_HORIZ:
1257 apply_horiz_layout(container, *group_x, *group_y, *group_w, *group_h, start, end);
1258 break;
1259 }
1260
1261 /* update position for next group */
1262 pos += group_dim;
1027 } 1263 }
1028 } 1264 }
1029} 1265}
@@ -1099,6 +1335,21 @@ swayc_t *get_swayc_in_direction_under(swayc_t *container, enum movement_directio
1099 return parent; 1335 return parent;
1100 } 1336 }
1101 } 1337 }
1338
1339 if (dir == MOVE_PREV || dir == MOVE_NEXT) {
1340 int focused_idx = index_child(container);
1341 if (focused_idx == -1) {
1342 return NULL;
1343 } else {
1344 int desired = (focused_idx + (dir == MOVE_NEXT ? 1 : -1)) %
1345 parent->children->length;
1346 if (desired < 0) {
1347 desired += parent->children->length;
1348 }
1349 return parent->children->items[desired];
1350 }
1351 }
1352
1102 // If moving to an adjacent output we need a starting position (since this 1353 // If moving to an adjacent output we need a starting position (since this
1103 // output might border to multiple outputs). 1354 // output might border to multiple outputs).
1104 struct wlc_point abs_pos; 1355 struct wlc_point abs_pos;
@@ -1121,7 +1372,8 @@ swayc_t *get_swayc_in_direction_under(swayc_t *container, enum movement_directio
1121 while (true) { 1372 while (true) {
1122 // Test if we can even make a difference here 1373 // Test if we can even make a difference here
1123 bool can_move = false; 1374 bool can_move = false;
1124 int diff = 0; 1375 int desired;
1376 int idx = index_child(container);
1125 if (parent->type == C_ROOT) { 1377 if (parent->type == C_ROOT) {
1126 swayc_t *output = swayc_adjacent_output(container, dir, &abs_pos, true); 1378 swayc_t *output = swayc_adjacent_output(container, dir, &abs_pos, true);
1127 if (!output || output == container) { 1379 if (!output || output == container) {
@@ -1130,21 +1382,36 @@ swayc_t *get_swayc_in_direction_under(swayc_t *container, enum movement_directio
1130 sway_log(L_DEBUG, "Moving between outputs"); 1382 sway_log(L_DEBUG, "Moving between outputs");
1131 return get_swayc_in_output_direction(output, dir); 1383 return get_swayc_in_output_direction(output, dir);
1132 } else { 1384 } else {
1133 if (dir == MOVE_LEFT || dir == MOVE_RIGHT) { 1385 if (is_auto_layout(parent->layout)) {
1134 if (parent->layout == L_HORIZ || parent->layout == L_TABBED) { 1386 bool is_major = parent->layout == L_AUTO_LEFT || parent->layout == L_AUTO_RIGHT
1135 can_move = true; 1387 ? dir == MOVE_LEFT || dir == MOVE_RIGHT
1136 diff = dir == MOVE_LEFT ? -1 : 1; 1388 : dir == MOVE_DOWN || dir == MOVE_UP;
1389 size_t gidx = auto_group_index(parent, idx);
1390 if (is_major) {
1391 size_t desired_grp = gidx + (dir == MOVE_RIGHT || dir == MOVE_DOWN ? 1 : -1);
1392 can_move = auto_group_bounds(parent, desired_grp, &desired, NULL);
1393 } else {
1394 desired = idx + (dir == MOVE_RIGHT || dir == MOVE_DOWN ? 1 : -1);
1395 int start, end;
1396 can_move = auto_group_bounds(parent, gidx, &start, &end)
1397 && desired >= start && desired < end;
1137 } 1398 }
1138 } else { 1399 } else {
1139 if (parent->layout == L_VERT || parent->layout == L_STACKED) { 1400 if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
1140 can_move = true; 1401 if (parent->layout == L_HORIZ || parent->layout == L_TABBED) {
1141 diff = dir == MOVE_UP ? -1 : 1; 1402 can_move = true;
1403 desired = idx + (dir == MOVE_LEFT ? -1 : 1);
1404 }
1405 } else {
1406 if (parent->layout == L_VERT || parent->layout == L_STACKED) {
1407 can_move = true;
1408 desired = idx + (dir == MOVE_UP ? -1 : 1);
1409 }
1142 } 1410 }
1143 } 1411 }
1144 } 1412 }
1145 1413
1146 if (can_move) { 1414 if (can_move) {
1147 int desired = index_child(container) + diff;
1148 if (container->is_floating) { 1415 if (container->is_floating) {
1149 if (desired < 0) { 1416 if (desired < 0) {
1150 wrap_candidate = parent->floating->items[parent->floating->length-1]; 1417 wrap_candidate = parent->floating->items[parent->floating->length-1];
@@ -1171,6 +1438,8 @@ swayc_t *get_swayc_in_direction_under(swayc_t *container, enum movement_directio
1171 } 1438 }
1172 } 1439 }
1173 } else { 1440 } else {
1441 sway_log(L_DEBUG, "%s cont %d-%p dir %i sibling %d: %p", __func__,
1442 idx, container, dir, desired, parent->children->items[desired]);
1174 return parent->children->items[desired]; 1443 return parent->children->items[desired];
1175 } 1444 }
1176 } 1445 }
@@ -1226,3 +1495,164 @@ enum swayc_layouts default_layout(swayc_t *output) {
1226 return L_VERT; 1495 return L_VERT;
1227 } 1496 }
1228} 1497}
1498
1499bool is_auto_layout(enum swayc_layouts layout) {
1500 return (layout >= L_AUTO_FIRST) && (layout <= L_AUTO_LAST);
1501}
1502
1503/**
1504 * Return the number of master elements in a container
1505 */
1506static inline size_t auto_master_count(const swayc_t *container) {
1507 return MIN(container->nb_master, container->children->length);
1508}
1509
1510/**
1511 * Return the number of children in the slave groups. This corresponds to the children
1512 * that are not members of the master group.
1513 */
1514static inline size_t auto_slave_count(const swayc_t *container) {
1515 return container->children->length - auto_master_count(container);
1516}
1517
1518/**
1519 * Return the number of slave groups in the container.
1520 */
1521size_t auto_slave_group_count(const swayc_t *container) {
1522 return MIN(container->nb_slave_groups, auto_slave_count(container));
1523}
1524
1525/**
1526 * Return the combined number of master and slave groups in the container.
1527 */
1528size_t auto_group_count(const swayc_t *container) {
1529 return auto_slave_group_count(container) + (container->nb_master ? 1 : 0);
1530}
1531
1532/**
1533 * given the index of a container's child, return the index of the first child of the group
1534 * which index is a member of.
1535 */
1536int auto_group_start_index(const swayc_t *container, int index) {
1537 if (index < 0 || ! is_auto_layout(container->layout)
1538 || (size_t) index < container->nb_master) {
1539 return 0;
1540 } else {
1541 size_t nb_slaves = auto_slave_count(container);
1542 size_t nb_slave_grp = auto_slave_group_count(container);
1543 size_t grp_sz = nb_slaves / nb_slave_grp;
1544 size_t remainder = nb_slaves % nb_slave_grp;
1545 int idx2 = (nb_slave_grp - remainder) * grp_sz + container->nb_master;
1546 int start_idx;
1547 if (index < idx2) {
1548 start_idx = ((index - container->nb_master) / grp_sz) * grp_sz + container->nb_master;
1549 } else {
1550 start_idx = idx2 + ((index - idx2) / (grp_sz + 1)) * (grp_sz + 1);
1551 }
1552 return MIN(start_idx, container->children->length);
1553 }
1554}
1555
1556/**
1557 * given the index of a container's child, return the index of the first child of the group
1558 * that follows the one which index is a member of.
1559 * This makes the function usable to walk through the groups in a container.
1560 */
1561int auto_group_end_index(const swayc_t *container, int index) {
1562 if (index < 0 || ! is_auto_layout(container->layout)) {
1563 return container->children->length;
1564 } else {
1565 int nxt_idx;
1566 if ((size_t)index < container->nb_master) {
1567 nxt_idx = auto_master_count(container);
1568 } else {
1569 size_t nb_slaves = auto_slave_count(container);
1570 size_t nb_slave_grp = auto_slave_group_count(container);
1571 size_t grp_sz = nb_slaves / nb_slave_grp;
1572 size_t remainder = nb_slaves % nb_slave_grp;
1573 int idx2 = (nb_slave_grp - remainder) * grp_sz + container->nb_master;
1574 if (index < idx2) {
1575 nxt_idx = ((index - container->nb_master) / grp_sz + 1) * grp_sz + container->nb_master;
1576 } else {
1577 nxt_idx = idx2 + ((index - idx2) / (grp_sz + 1) + 1) * (grp_sz + 1);
1578 }
1579 }
1580 return MIN(nxt_idx, container->children->length);
1581 }
1582}
1583
1584/**
1585 * return the index of the Group containing <index>th child of <container>.
1586 * The index is the order of the group along the container's major axis (starting at 0).
1587 */
1588size_t auto_group_index(const swayc_t *container, int index) {
1589 if (index < 0) {
1590 return 0;
1591 }
1592 bool master_first = (container->layout == L_AUTO_LEFT || container->layout == L_AUTO_TOP);
1593 size_t nb_slaves = auto_slave_count(container);
1594 if ((size_t) index < container->nb_master) {
1595 if (master_first || nb_slaves <= 0) {
1596 return 0;
1597 } else {
1598 return auto_slave_group_count(container);
1599 }
1600 } else {
1601 size_t nb_slave_grp = auto_slave_group_count(container);
1602 size_t grp_sz = nb_slaves / nb_slave_grp;
1603 size_t remainder = nb_slaves % nb_slave_grp;
1604 int idx2 = (nb_slave_grp - remainder) * grp_sz + container->nb_master;
1605 size_t grp_idx;
1606 if (index < idx2) {
1607 grp_idx = (index - container->nb_master) / grp_sz;
1608 } else {
1609 grp_idx = (nb_slave_grp - remainder) + (index - idx2) / (grp_sz + 1) ;
1610 }
1611 return grp_idx + (master_first && container-> nb_master ? 1 : 0);
1612 }
1613}
1614
1615/**
1616 * Return the first index (inclusive) and last index (exclusive) of the elements of a group in
1617 * an auto layout.
1618 * If the bounds of the given group can be calculated, they are returned in the start/end
1619 * parameters (int pointers) and the return value will be true.
1620 * The indexes are passed by reference and can be NULL.
1621 */
1622bool auto_group_bounds(const swayc_t *container, size_t group_index, int *start, int *end) {
1623 size_t nb_grp = auto_group_count(container);
1624 if (group_index >= nb_grp) {
1625 return false;
1626 }
1627 bool master_first = (container->layout == L_AUTO_LEFT || container->layout == L_AUTO_TOP);
1628 size_t nb_master = auto_master_count(container);
1629 size_t nb_slave_grp = auto_slave_group_count(container);
1630 int g_start, g_end;
1631 if (nb_master && (master_first ? group_index == 0 : group_index == nb_grp - 1)) {
1632 g_start = 0;
1633 g_end = nb_master;
1634 } else {
1635 size_t nb_slaves = auto_slave_count(container);
1636 size_t grp_sz = nb_slaves / nb_slave_grp;
1637 size_t remainder = nb_slaves % nb_slave_grp;
1638 size_t g0 = master_first && container->nb_master ? 1 : 0;
1639 size_t g1 = g0 + nb_slave_grp - remainder;
1640 if (group_index < g1) {
1641 g_start = container->nb_master + (group_index - g0) * grp_sz;
1642 g_end = g_start + grp_sz;
1643 } else {
1644 size_t g2 = group_index - g1;
1645 g_start = container->nb_master
1646 + (nb_slave_grp - remainder) * grp_sz
1647 + g2 * (grp_sz + 1);
1648 g_end = g_start + grp_sz + 1;
1649 }
1650 }
1651 if (start) {
1652 *start = g_start;
1653 }
1654 if (end) {
1655 *end = g_end;
1656 }
1657 return true;
1658}
diff --git a/sway/sway.5.txt b/sway/sway.5.txt
index 7980ba82..cbff6cef 100644
--- a/sway/sway.5.txt
+++ b/sway/sway.5.txt
@@ -39,7 +39,7 @@ The following commands may only be used in the configuration file.
39**set** <name> <value>:: 39**set** <name> <value>::
40 Sets variable $name to _value_. You can use the new variable in the arguments 40 Sets variable $name to _value_. You can use the new variable in the arguments
41 of future commands. 41 of future commands.
42 42
43**orientation** <horizontal|vertical|auto>:: 43**orientation** <horizontal|vertical|auto>::
44 Sets the default container layout for tiled containers. 44 Sets the default container layout for tiled containers.
45 45
@@ -62,11 +62,14 @@ They are expected to be used with **bindsym** or at runtime through **swaymsg**(
62 Make focused view floating, non-floating, or the opposite of what it is now. 62 Make focused view floating, non-floating, or the opposite of what it is now.
63 63
64**focus** <direction>:: 64**focus** <direction>::
65 Direction may be one of _up_, _down_, _left_, _right_, _parent_, or _child_. 65 Direction may be one of _up_, _down_, _left_, _right_, _next_, _prev_,
66 The directional focus commands will move the focus in that direction. The parent 66 _parent_, or _child_. The directional focus commands will move the focus
67 focus command will change the focus to the parent of the currently focused 67 in that direction. The auto_next and auto_prev will focus the next,
68 container, which is useful, for example, to open a sibling of the parent 68 respectively previous, element in the current container if it is using
69 container, or to move the entire container around. 69 one of the _auto_ layouts. The parent focus command will change the
70 focus to the parent of the currently focused container, which is useful,
71 for example, to open a sibling of the parent container, or to move the
72 entire container around.
70 73
71**focus** output <direction|name>:: 74**focus** output <direction|name>::
72 Direction may be one of _up_, _down_, _left_, _right_. The directional focus 75 Direction may be one of _up_, _down_, _left_, _right_. The directional focus
@@ -81,10 +84,28 @@ They are expected to be used with **bindsym** or at runtime through **swaymsg**(
81 84
82**layout** <mode>:: 85**layout** <mode>::
83 Sets the layout mode of the focused container. _mode_ can be one of _splith_, 86 Sets the layout mode of the focused container. _mode_ can be one of _splith_,
84 _splitv_, _toggle split_, _stacking_ or _tabbed_. 87 _splitv_, _toggle split_, _stacking_, _tabbed_, _auto_left_, _auto_right_,
88 _auto_top, _auto_bottom_.
89
90**layout** auto <next|prev>::
91 Cycles between available auto layouts.
92
93**layout** <incnmaster|incncol> <n>::
94 Modify the number of master elements, respectively slave columns, in the
95 focused container. <n> can be a positive or negative integer. These commands
96 only have an effect if the focused container uses one of the "auto" layouts.
97
98**layout** toggle split::
99 Cycles between available split layouts.
100
101**layout** promote::
102 Swap the focused element with the first in the one of the auto layouts.
85 103
86**move** <left|right|up|down>:: 104**move** <left|right|up|down|next|prev|first>::
87 Moves the focused container _left_, _right_, _up_, or _down_. 105 Moves the focused container _left_, _right_, _up_, or _down_. Moving to _prev_
106 or _next_ swaps the container with its sibling in the same container. Move
107 _first_ exchanges the focused element in an auto layout with the first
108 element, i.e. promotes the focused element to master position.
88 109
89**move** <container|window> to workspace <name>:: 110**move** <container|window> to workspace <name>::
90 Moves the focused container to the workspace identified by _name_. 111 Moves the focused container to the workspace identified by _name_.
@@ -360,7 +381,7 @@ The default colors are:
360 switch to workspace 2, then invoke the "workspace 2" command again, you 381 switch to workspace 2, then invoke the "workspace 2" command again, you
361 will be returned to workspace 1. Defaults to _no_. 382 will be returned to workspace 1. Defaults to _no_.
362 383
363**workspace_layout** <default|stacking|tabbed>:: 384**workspace_layout** <default|stacking|tabbed|auto_left|auto_right|auto_top|auto_bottom>::
364 Specifies the start layout for new workspaces. 385 Specifies the start layout for new workspaces.
365 386
366**include** <path>:: 387**include** <path>::