summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Drew DeVault <sir@cmpwn.com>2017-01-14 16:11:48 -0500
committerLibravatar GitHub <noreply@github.com>2017-01-14 16:11:48 -0500
commit81102e8eacbf72ad0c5e81c935a957a8824a0922 (patch)
treef8b51dd1f5214966ad30b0bd7b561afa70dcb4dc
parentMerge pull request #1048 from dkess/proper-output-wrapping (diff)
parentMove awesome config to contrib/ (diff)
downloadsway-81102e8eacbf72ad0c5e81c935a957a8824a0922.tar.gz
sway-81102e8eacbf72ad0c5e81c935a957a8824a0922.tar.zst
sway-81102e8eacbf72ad0c5e81c935a957a8824a0922.zip
Merge pull request #1024 from willakat/master
Add Awesome/Monad style automatic layouts to Sway
-rw-r--r--common/list.c2
-rw-r--r--contrib/awesome.config63
-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.c127
-rw-r--r--sway/commands/move.c8
-rw-r--r--sway/commands/resize.c313
-rw-r--r--sway/commands/workspace_layout.c23
-rw-r--r--sway/container.c26
-rw-r--r--sway/debug_log.c4
-rw-r--r--sway/layout.c781
-rw-r--r--sway/sway.5.txt42
15 files changed, 1072 insertions, 356 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/contrib/awesome.config b/contrib/awesome.config
new file mode 100644
index 00000000..41e17c91
--- /dev/null
+++ b/contrib/awesome.config
@@ -0,0 +1,63 @@
1#
2# Replicate some of Awesome's default layout manipulation configuration for Sway
3#
4# Differences:
5# - Layout switching doesn't use the spacebar (i.e. i3/Sway behavior to switch to/from floating windows)
6# and uses the 'A' key instead (as in auto)
7# - Resizing windows uses i3/Sway's more versatile Mod4+r
8# - no tags
9# - no Maximize/Minize, alternatives to Maximize would be to switch to Stacked/Tabbed layouts
10# via Mod4+w or Mod4+s.
11# - kill focused client is available on Mod4+Shift+q (instead of Mod4+Shift+c, which maps to Sway's
12# config reload)
13# - probably many more ...
14
15# Awesome-style container traversal using Vim-like binding
16set $next j
17set $prev k
18
19#
20# Moving around:
21#
22 # Move your focus around
23 bindsym $mod+$next focus next
24 bindsym $mod+$prev focus prev
25
26 # _move_ the focused window with the same, but add Shift
27 bindsym $mod+Shift+$next move next
28 bindsym $mod+Shift+$prev move prev
29
30#
31# Layout:
32#
33 workspace_layout auto left
34
35 # This is usually bound to $mod+space, but this works well in practice by keeping
36 # all the layout switching keys grouped together.
37 bindsym $mod+a layout auto next
38 bindsym $mod+Shift+a layout auto prev
39
40 # Promote a child to master position in an auto layout
41 bindsym $mod+Control+Return move first
42
43 # Increase/decrease number of master elements in auto layout
44 bindsym $mod+Shift+h layout auto master inc 1
45 bindsym $mod+Shift+l layout auto master inc -1
46
47 # Increase/decrease number of slave element groups in auto layout
48 bindsym $mod+Control+h layout auto ncol inc 1
49 bindsym $mod+Control+l layout auto ncol inc -1
50
51#
52# Resizing containers:
53# Again, not really the way Awesome works well, but in spirit with i3/Sway and it works well.
54#
55mode "resize" {
56 bindsym Left resize shrink width 20 px
57 bindsym Down resize grow height 20 px
58 bindsym Up resize shrink height 20 px
59 bindsym Right resize grow width 20 px
60}
61bindsym $mod+r mode "resize"
62
63new_window pixel 1
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..d04bb4dc 100644
--- a/sway/commands/layout.c
+++ b/sway/commands/layout.c
@@ -3,6 +3,11 @@
3#include "sway/container.h" 3#include "sway/container.h"
4#include "sway/layout.h" 4#include "sway/layout.h"
5 5
6/**
7 * handle "layout auto" command group
8 */
9static struct cmd_results *cmd_layout_auto(swayc_t *container, int argc, char **argv);
10
6struct cmd_results *cmd_layout(int argc, char **argv) { 11struct cmd_results *cmd_layout(int argc, char **argv) {
7 struct cmd_results *error = NULL; 12 struct cmd_results *error = NULL;
8 if (config->reading) return cmd_results_new(CMD_FAILURE, "layout", "Can't be used in config file."); 13 if (config->reading) return cmd_results_new(CMD_FAILURE, "layout", "Can't be used in config file.");
@@ -49,11 +54,14 @@ struct cmd_results *cmd_layout(int argc, char **argv) {
49 } else if (strcasecmp(argv[0], "splitv") == 0) { 54 } else if (strcasecmp(argv[0], "splitv") == 0) {
50 swayc_change_layout(parent, L_VERT); 55 swayc_change_layout(parent, L_VERT);
51 } else if (strcasecmp(argv[0], "toggle") == 0 && argc == 2 && strcasecmp(argv[1], "split") == 0) { 56 } 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)) { 57 if (parent->layout == L_HORIZ && (parent->workspace_layout == L_NONE
58 || parent->workspace_layout == L_HORIZ)) {
53 swayc_change_layout(parent, L_VERT); 59 swayc_change_layout(parent, L_VERT);
54 } else { 60 } else {
55 swayc_change_layout(parent, L_HORIZ); 61 swayc_change_layout(parent, L_HORIZ);
56 } 62 }
63 } else if (strcasecmp(argv[0], "auto") == 0) {
64 return cmd_layout_auto(parent, argc, argv);
57 } 65 }
58 } 66 }
59 67
@@ -64,3 +72,120 @@ struct cmd_results *cmd_layout(int argc, char **argv) {
64 72
65 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 73 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
66} 74}
75
76static struct cmd_results *cmd_layout_auto(swayc_t *container, int argc, char **argv) {
77 // called after checking that argv[0] is auto, so just continue parsing from there
78 struct cmd_results *error = NULL;
79 const char *cmd_name = "layout auto";
80 const char *set_inc_cmd_name = "layout auto [master|ncol] [set|inc]";
81 const char *err_msg = "Allowed arguments are <right|left|top|bottom|next|prev|master|ncol>";
82
83 bool need_layout_update = false;
84 enum swayc_layouts old_layout = container->layout;
85 enum swayc_layouts layout = old_layout;
86
87 if (strcasecmp(argv[1], "left") == 0) {
88 layout = L_AUTO_LEFT;
89 } else if (strcasecmp(argv[1], "right") == 0) {
90 layout = L_AUTO_RIGHT;
91 } else if (strcasecmp(argv[1], "top") == 0) {
92 layout = L_AUTO_TOP;
93 } else if (strcasecmp(argv[1], "bottom") == 0) {
94 layout = L_AUTO_BOTTOM;
95 } else if (strcasecmp(argv[1], "next") == 0) {
96 if (is_auto_layout(container->layout) && container->layout < L_AUTO_LAST) {
97 layout = container->layout + 1;
98 } else {
99 layout = L_AUTO_FIRST;
100 }
101 } else if (strcasecmp(argv[1], "prev") == 0) {
102 if (is_auto_layout(container->layout) && container->layout > L_AUTO_FIRST) {
103 layout = container->layout - 1;
104 } else {
105 layout = L_AUTO_LAST;
106 }
107 } else {
108 bool is_nmaster;
109 bool is_set;
110 if (strcasecmp(argv[1], "master") == 0) {
111 is_nmaster = true;
112 } else if (strcasecmp(argv[1], "ncol") == 0) {
113 is_nmaster = false;
114 } else {
115 return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command. %s",
116 cmd_name, err_msg);
117 }
118 if ((error = checkarg(argc, "auto <master|ncol>", EXPECTED_EQUAL_TO, 4))) {
119 return error;
120 }
121 if (strcasecmp(argv[2], "set") == 0) {
122 is_set = true;
123 } else if (strcasecmp(argv[2], "inc") == 0) {
124 is_set = false;
125 } else {
126 return cmd_results_new(CMD_INVALID, set_inc_cmd_name, "Invalid %s command. %s, "
127 "Argument must be on of <set|inc>",
128 set_inc_cmd_name);
129 }
130 char *end;
131 int n = (int)strtol(argv[3], &end, 10);
132 if (*end) {
133 return cmd_results_new(CMD_INVALID, set_inc_cmd_name, "Invalid %s command "
134 "(argument must be an integer)", set_inc_cmd_name);
135 }
136 if (is_auto_layout(container->layout)) {
137 int inc = 0; /* difference between current master/ncol and requested value */
138 if (is_nmaster) {
139 if (is_set) {
140 if (n < 0) {
141 return cmd_results_new(CMD_INVALID, set_inc_cmd_name, "Invalid %s command "
142 "(master must be >= 0)", set_inc_cmd_name);
143 }
144 inc = n - (int)container->nb_master;
145 } else { /* inc command */
146 if ((int)container->nb_master + n >= 0) {
147 inc = n;
148 }
149 }
150 if (inc) {
151 for (int i = container->nb_master;
152 i >= 0 && i < container->children->length
153 && i != (int)container->nb_master + inc;) {
154 ((swayc_t *)container->children->items[i])->height = -1;
155 ((swayc_t *)container->children->items[i])->width = -1;
156 i += inc > 0 ? 1 : -1;
157 }
158 container->nb_master += inc;
159 need_layout_update = true;
160 }
161 } else { /* ncol modification */
162 if (is_set) {
163 if (n <= 0) {
164 return cmd_results_new(CMD_INVALID, set_inc_cmd_name, "Invalid %s command "
165 "(ncol must be > 0)", set_inc_cmd_name);
166 }
167 inc = n - (int)container->nb_slave_groups;
168 } else { /* inc command */
169 if ((int)container->nb_slave_groups + n > 0) {
170 inc = n;
171 }
172 }
173 if (inc) {
174 container->nb_slave_groups += inc;
175 need_layout_update = true;
176 }
177 }
178 }
179 }
180
181 if (layout != old_layout) {
182 swayc_change_layout(container, layout);
183 update_layout_geometry(container, old_layout);
184 need_layout_update = true;
185 }
186 if (need_layout_update) {
187 update_geometry(container);
188 arrange_windows(container, container->width, container->height);
189 }
190 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
191}
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..c9305773 100644
--- a/sway/commands/workspace_layout.c
+++ b/sway/commands/workspace_layout.c
@@ -3,7 +3,7 @@
3 3
4struct cmd_results *cmd_workspace_layout(int argc, char **argv) { 4struct cmd_results *cmd_workspace_layout(int argc, char **argv) {
5 struct cmd_results *error = NULL; 5 struct cmd_results *error = NULL;
6 if ((error = checkarg(argc, "workspace_layout", EXPECTED_EQUAL_TO, 1))) { 6 if ((error = checkarg(argc, "workspace_layout", EXPECTED_AT_LEAST, 1))) {
7 return error; 7 return error;
8 } 8 }
9 9
@@ -13,8 +13,27 @@ 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") == 0) {
17 if (argc == 1) {
18 config->default_layout = L_AUTO_FIRST;
19 } else {
20 if ((error = checkarg(argc, "workspace_layout auto", EXPECTED_EQUAL_TO, 2))) {
21 return error;
22 }
23 if (strcasecmp(argv[0], "left") == 0) {
24 config->default_layout = L_AUTO_LEFT;
25 } else if (strcasecmp(argv[0], "right") == 0) {
26 config->default_layout = L_AUTO_RIGHT;
27 } else if (strcasecmp(argv[0], "top") == 0) {
28 config->default_layout = L_AUTO_TOP;
29 } else if (strcasecmp(argv[0], "bottom") == 0) {
30 config->default_layout = L_AUTO_BOTTOM;
31 } else {
32 return cmd_results_new(CMD_INVALID, "workspace_layout auto", "Expected 'workspace_layout auto <left|right|top|bottom>'");
33 }
34 }
16 } else { 35 } else {
17 return cmd_results_new(CMD_INVALID, "workspace_layout", "Expected 'workspace_layout <default|stacking|tabbed>'"); 36 return cmd_results_new(CMD_INVALID, "workspace_layout", "Expected 'workspace_layout <default|stacking|tabbed|auto|auto left|auto right|auto top|auto bottom>'");
18 } 37 }
19 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 38 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
20} 39}
diff --git a/sway/container.c b/sway/container.c
index e557f450..cf7d7dda 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,29 @@ 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 =
969 container->layout == L_AUTO_LEFT || container->layout == L_AUTO_RIGHT
970 ? L_HORIZ : L_VERT;
971 enum swayc_layouts new_major =
972 layout == L_AUTO_LEFT || layout == L_AUTO_RIGHT
973 ? L_HORIZ : L_VERT;
974 if (new_major != prev_major) {
975 for (int i = 0; i < container->children->length; ++i) {
976 swayc_t *child = container->children->items[i];
977 double h = child->height;
978 child->height = child->width;
979 child->width = h;
980 }
981 }
982 }
963 if (container->type == C_WORKSPACE) { 983 if (container->type == C_WORKSPACE) {
964 container->workspace_layout = layout; 984 container->workspace_layout = layout;
965 if (layout == L_HORIZ || layout == L_VERT) { 985 if (layout == L_HORIZ || layout == L_VERT || is_auto_layout(layout)) {
966 container->layout = layout; 986 container->layout = layout;
967 } 987 }
968 } else { 988 } else {
969 container->layout = layout; 989 container->layout = layout;
970 } 990 }
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 bb37a360..5e144cd8 100644
--- a/sway/layout.c
+++ b/sway/layout.c
@@ -71,6 +71,14 @@ void add_child(swayc_t *parent, swayc_t *child) {
71 } 71 }
72} 72}
73 73
74static double *get_height(swayc_t *cont) {
75 return &cont->height;
76}
77
78static double *get_width(swayc_t *cont) {
79 return &cont->width;
80}
81
74void insert_child(swayc_t *parent, swayc_t *child, int index) { 82void insert_child(swayc_t *parent, swayc_t *child, int index) {
75 if (index > parent->children->length) { 83 if (index > parent->children->length) {
76 index = parent->children->length; 84 index = parent->children->length;
@@ -86,7 +94,44 @@ void insert_child(swayc_t *parent, swayc_t *child, int index) {
86 if (parent->type == C_WORKSPACE && child->type == C_VIEW && (parent->workspace_layout == L_TABBED || parent->workspace_layout == L_STACKED)) { 94 if (parent->type == C_WORKSPACE && child->type == C_VIEW && (parent->workspace_layout == L_TABBED || parent->workspace_layout == L_STACKED)) {
87 child = new_container(child, parent->workspace_layout); 95 child = new_container(child, parent->workspace_layout);
88 } 96 }
89 97 if (is_auto_layout(parent->layout)) {
98 /* go through each group, adjust the size of the first child of each group */
99 double *(*get_maj_dim)(swayc_t *cont);
100 double *(*get_min_dim)(swayc_t *cont);
101 if (parent->layout == L_AUTO_LEFT || parent->layout == L_AUTO_RIGHT) {
102 get_maj_dim = get_width;
103 get_min_dim = get_height;
104 } else {
105 get_maj_dim = get_height;
106 get_min_dim = get_width;
107 }
108 for (int i = index; i < parent->children->length;) {
109 int start = auto_group_start_index(parent, i);
110 int end = auto_group_end_index(parent, i);
111 swayc_t *first = parent->children->items[start];
112 if (start + 1 < parent->children->length) {
113 /* preserve the group's dimension along major axis */
114 *get_maj_dim(first) = *get_maj_dim(parent->children->items[start + 1]);
115 } else {
116 /* new group, let the apply_layout handle it */
117 first->height = first->width = 0;
118 break;
119 }
120 double remaining = *get_min_dim(parent);
121 for (int j = end - 1; j > start; --j) {
122 swayc_t *sibling = parent->children->items[j];
123 if (sibling == child) {
124 /* the inserted child won't yet have its minor
125 dimension set */
126 remaining -= *get_min_dim(parent) / (end - start);
127 } else {
128 remaining -= *get_min_dim(sibling);
129 }
130 }
131 *get_min_dim(first) = remaining;
132 i = end;
133 }
134 }
90} 135}
91 136
92void add_floating(swayc_t *ws, swayc_t *child) { 137void add_floating(swayc_t *ws, swayc_t *child) {
@@ -118,7 +163,11 @@ swayc_t *add_sibling(swayc_t *fixed, swayc_t *active) {
118 list_add(parent->floating, active); 163 list_add(parent->floating, active);
119 } else { 164 } else {
120 int i = index_child(fixed); 165 int i = index_child(fixed);
121 list_insert(parent->children, i + 1, active); 166 if (is_auto_layout(parent->layout)) {
167 list_add(parent->children, active);
168 } else {
169 list_insert(parent->children, i + 1, active);
170 }
122 } 171 }
123 } 172 }
124 active->parent = parent; 173 active->parent = parent;
@@ -181,6 +230,42 @@ swayc_t *remove_child(swayc_t *child) {
181 break; 230 break;
182 } 231 }
183 } 232 }
233 if (is_auto_layout(parent->layout) && parent->children->length) {
234 /* go through each group, adjust the size of the last child of each group */
235 double *(*get_maj_dim)(swayc_t *cont);
236 double *(*get_min_dim)(swayc_t *cont);
237 if (parent->layout == L_AUTO_LEFT || parent->layout == L_AUTO_RIGHT) {
238 get_maj_dim = get_width;
239 get_min_dim = get_height;
240 } else {
241 get_maj_dim = get_height;
242 get_min_dim = get_width;
243 }
244 for (int j = parent->children->length - 1; j >= i;) {
245 int start = auto_group_start_index(parent, j);
246 int end = auto_group_end_index(parent, j);
247 swayc_t *first = parent->children->items[start];
248 if (i == start) {
249 /* removed element was first child in the current group,
250 use its size along the major axis */
251 *get_maj_dim(first) = *get_maj_dim(child);
252 } else if (start > i) {
253 /* preserve the group's dimension along major axis */
254 *get_maj_dim(first) = *get_maj_dim(parent->children->items[start - 1]);
255 }
256 if (end != parent->children->length) {
257 double remaining = *get_min_dim(parent);
258 for (int k = start; k < end - 1; ++k) {
259 swayc_t *sibling = parent->children->items[k];
260 remaining -= *get_min_dim(sibling);
261 }
262 /* last element of the group gets remaining size, elements
263 that don't change groups keep their ratio */
264 *get_min_dim((swayc_t *) parent->children->items[end - 1]) = remaining;
265 } /* else last group, let apply_layout handle it */
266 j = start - 1;
267 }
268 }
184 } 269 }
185 // Set focused to new container 270 // Set focused to new container
186 if (parent->focused == child) { 271 if (parent->focused == child) {
@@ -246,20 +331,51 @@ void swap_geometry(swayc_t *a, swayc_t *b) {
246 b->height = h; 331 b->height = h;
247} 332}
248 333
334static void swap_children(swayc_t *container, int a, int b) {
335 if (a >= 0 && b >= 0 && a < container->children->length
336 && b < container->children->length
337 && a != b) {
338 swayc_t *pa = (swayc_t *)container->children->items[a];
339 swayc_t *pb = (swayc_t *)container->children->items[b];
340 container->children->items[a] = container->children->items[b];
341 container->children->items[b] = pa;
342 if (is_auto_layout(container->layout)) {
343 size_t ga = auto_group_index(container, a);
344 size_t gb = auto_group_index(container, b);
345 if (ga != gb) {
346 swap_geometry(pa, pb);
347 }
348 }
349 }
350}
351
249void move_container(swayc_t *container, enum movement_direction dir) { 352void move_container(swayc_t *container, enum movement_direction dir) {
250 enum swayc_layouts layout; 353 enum swayc_layouts layout = L_NONE;
251 if (container->is_floating 354 swayc_t *parent = container->parent;
252 || (container->type != C_VIEW && container->type != C_CONTAINER)) { 355 if (container->is_floating || (container->type != C_VIEW && container->type != C_CONTAINER)) {
253 return; 356 return;
254 } 357 }
255 if (dir == MOVE_UP || dir == MOVE_DOWN) { 358 if (dir == MOVE_UP || dir == MOVE_DOWN) {
256 layout = L_VERT; 359 layout = L_VERT;
257 } else if (dir == MOVE_LEFT || dir == MOVE_RIGHT) { 360 } else if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
258 layout = L_HORIZ; 361 layout = L_HORIZ;
259 } else { 362 } else if (dir == MOVE_FIRST) {
363 // swap first child in auto layout with currently focused child
364 if (is_auto_layout(parent->layout)) {
365 int focused_idx = index_child(container);
366 swayc_t *first = parent->children->items[0];
367 if (focused_idx > 0) {
368 list_swap(parent->children, 0, focused_idx);
369 swap_geometry(first, container);
370 }
371 arrange_windows(parent->parent, -1, -1);
372 ipc_event_window(container, "move");
373 set_focused_container_for(parent->parent, container);
374 }
375 return;
376 } else if (! (dir == MOVE_NEXT || dir == MOVE_PREV)) {
260 return; 377 return;
261 } 378 }
262 swayc_t *parent = container->parent;
263 swayc_t *child = container; 379 swayc_t *child = container;
264 bool ascended = false; 380 bool ascended = false;
265 381
@@ -279,19 +395,30 @@ void move_container(swayc_t *container, enum movement_direction dir) {
279 sway_log(L_DEBUG, "container:%p, parent:%p, child %p,", 395 sway_log(L_DEBUG, "container:%p, parent:%p, child %p,",
280 container,parent,child); 396 container,parent,child);
281 if (parent->layout == layout 397 if (parent->layout == layout
398 || (layout == L_NONE && parent->type == C_CONTAINER) /* accept any layout for next/prev direction */
282 || (parent->layout == L_TABBED && layout == L_HORIZ) 399 || (parent->layout == L_TABBED && layout == L_HORIZ)
283 || (parent->layout == L_STACKED && layout == L_VERT)) { 400 || (parent->layout == L_STACKED && layout == L_VERT)
401 || is_auto_layout(parent->layout)) {
284 int diff; 402 int diff;
285 // If it has ascended (parent has moved up), no container is removed 403 // If it has ascended (parent has moved up), no container is removed
286 // so insert it at index, or index+1. 404 // so insert it at index, or index+1.
287 // if it has not, the moved container is removed, so it needs to be 405 // if it has not, the moved container is removed, so it needs to be
288 // inserted at index-1, or index+1 406 // inserted at index-1, or index+1
289 if (ascended) { 407 if (ascended) {
290 diff = dir == MOVE_LEFT || dir == MOVE_UP ? 0 : 1; 408 diff = dir == MOVE_LEFT || dir == MOVE_UP || dir == MOVE_PREV ? 0 : 1;
291 } else { 409 } else {
292 diff = dir == MOVE_LEFT || dir == MOVE_UP ? -1 : 1; 410 diff = dir == MOVE_LEFT || dir == MOVE_UP || dir == MOVE_PREV ? -1 : 1;
411 }
412 int idx = index_child(child);
413 int desired = idx + diff;
414 if (dir == MOVE_NEXT || dir == MOVE_PREV) {
415 // Next/Prev always wrap.
416 if (desired < 0) {
417 desired += parent->children->length;
418 } else if (desired >= parent->children->length) {
419 desired = 0;
420 }
293 } 421 }
294 int desired = index_child(child) + diff;
295 // when it has ascended, legal insertion position is 0:len 422 // when it has ascended, legal insertion position is 0:len
296 // when it has not, legal insertion position is 0:len-1 423 // when it has not, legal insertion position is 0:len-1
297 if (desired >= 0 && desired - ascended < parent->children->length) { 424 if (desired >= 0 && desired - ascended < parent->children->length) {
@@ -304,7 +431,8 @@ void move_container(swayc_t *container, enum movement_direction dir) {
304 // insert it next to focused container 431 // insert it next to focused container
305 if (parent->layout == layout 432 if (parent->layout == layout
306 || (parent->layout == L_TABBED && layout == L_HORIZ) 433 || (parent->layout == L_TABBED && layout == L_HORIZ)
307 || (parent->layout == L_STACKED && layout == L_VERT)) { 434 || (parent->layout == L_STACKED && layout == L_VERT)
435 || is_auto_layout(parent->layout)) {
308 desired = (diff < 0) * parent->children->length; 436 desired = (diff < 0) * parent->children->length;
309 } else { 437 } else {
310 desired = index_child(child->focused) + 1; 438 desired = index_child(child->focused) + 1;
@@ -313,15 +441,19 @@ void move_container(swayc_t *container, enum movement_direction dir) {
313 container->width = container->height = 0; 441 container->width = container->height = 0;
314 } 442 }
315 } 443 }
316 swayc_t *old_parent = remove_child(container); 444 if (container->parent == parent) {
317 insert_child(parent, container, desired); 445 swap_children(parent, idx, desired);
318 destroy_container(old_parent); 446 } else {
319 sway_log(L_DEBUG,"Moving to %p %d", parent, desired); 447 swayc_t *old_parent = remove_child(container);
448 insert_child(parent, container, desired);
449 destroy_container(old_parent);
450 sway_log(L_DEBUG,"Moving to %p %d", parent, desired);
451 }
320 break; 452 break;
321 } 453 }
322 } 454 }
323 // Change parent layout if we need to 455 // Change parent layout if we need to
324 if (parent->children->length == 1 && parent->layout != layout) { 456 if (parent->children->length == 1 && parent->layout != layout && layout != L_NONE) {
325 /* swayc_change_layout(parent, layout); */ 457 /* swayc_change_layout(parent, layout); */
326 parent->layout = layout; 458 parent->layout = layout;
327 continue; 459 continue;
@@ -776,6 +908,26 @@ void update_geometry(swayc_t *container) {
776 } 908 }
777} 909}
778 910
911/**
912 * Layout application prototypes
913 */
914static void apply_horiz_layout(swayc_t *container, const double x,
915 const double y, const double width,
916 const double height, const int start,
917 const int end);
918static void apply_vert_layout(swayc_t *container, const double x,
919 const double y, const double width,
920 const double height, const int start,
921 const int end);
922static void apply_tabbed_or_stacked_layout(swayc_t *container, double x,
923 double y, double width,
924 double height);
925
926static void apply_auto_layout(swayc_t *container, const double x, const double y,
927 const double width, const double height,
928 enum swayc_layouts group_layout,
929 bool master_first);
930
779static void arrange_windows_r(swayc_t *container, double width, double height) { 931static void arrange_windows_r(swayc_t *container, double width, double height) {
780 int i; 932 int i;
781 if (width == -1 || height == -1) { 933 if (width == -1 || height == -1) {
@@ -783,14 +935,15 @@ static void arrange_windows_r(swayc_t *container, double width, double height) {
783 width = container->width; 935 width = container->width;
784 height = container->height; 936 height = container->height;
785 } 937 }
786 // pixels are indivisable. if we don't round the pixels, then the view 938 // pixels are indivisible. if we don't round the pixels, then the view
787 // calculations will be off (e.g. 50.5 + 50.5 = 101, but in reality it's 939 // calculations will be off (e.g. 50.5 + 50.5 = 101, but in reality it's
788 // 50 + 50 = 100). doing it here cascades properly to all width/height/x/y. 940 // 50 + 50 = 100). doing it here cascades properly to all width/height/x/y.
789 width = floor(width); 941 width = floor(width);
790 height = floor(height); 942 height = floor(height);
791 943
792 sway_log(L_DEBUG, "Arranging layout for %p %s %fx%f+%f,%f", container, 944 sway_log(L_DEBUG, "Arranging layout for %p %s %fx%f+%f,%f", container,
793 container->name, container->width, container->height, container->x, container->y); 945 container->name, container->width, container->height, container->x,
946 container->y);
794 947
795 double x = 0, y = 0; 948 double x = 0, y = 0;
796 switch (container->type) { 949 switch (container->type) {
@@ -902,135 +1055,298 @@ static void arrange_windows_r(swayc_t *container, double width, double height) {
902 break; 1055 break;
903 } 1056 }
904 1057
905 double scale = 0;
906 switch (container->layout) { 1058 switch (container->layout) {
907 case L_HORIZ: 1059 case L_HORIZ:
908 default: 1060 default:
909 // Calculate total width 1061 apply_horiz_layout(container, x, y, width, height, 0,
910 for (i = 0; i < container->children->length; ++i) { 1062 container->children->length);
911 double *old_width = &((swayc_t *)container->children->items[i])->width; 1063 break;
912 if (*old_width <= 0) { 1064 case L_VERT:
913 if (container->children->length > 1) { 1065 apply_vert_layout(container, x, y, width, height, 0,
914 *old_width = width / (container->children->length - 1); 1066 container->children->length);
915 } else { 1067 break;
916 *old_width = width; 1068 case L_TABBED:
917 } 1069 case L_STACKED:
918 } 1070 apply_tabbed_or_stacked_layout(container, x, y, width, height);
919 scale += *old_width; 1071 break;
920 } 1072 case L_AUTO_LEFT:
921 1073 apply_auto_layout(container, x, y, width, height, L_VERT, true);
922 // Resize windows 1074 break;
923 if (scale > 0.1) { 1075 case L_AUTO_RIGHT:
924 scale = width / scale; 1076 apply_auto_layout(container, x, y, width, height, L_VERT, false);
925 sway_log(L_DEBUG, "Arranging %p horizontally", container); 1077 break;
926 swayc_t *focused = NULL; 1078 case L_AUTO_TOP:
927 for (i = 0; i < container->children->length; ++i) { 1079 apply_auto_layout(container, x, y, width, height, L_HORIZ, true);
928 swayc_t *child = container->children->items[i]; 1080 break;
929 sway_log(L_DEBUG, "Calculating arrangement for %p:%d (will scale %f by %f)", child, child->type, width, scale); 1081 case L_AUTO_BOTTOM:
930 child->x = x; 1082 apply_auto_layout(container, x, y, width, height, L_HORIZ, false);
931 child->y = y; 1083 break;
932 1084 }
933 if (child == container->focused) {
934 focused = child;
935 }
936 1085
937 if (i == container->children->length - 1) { 1086 // Arrage floating layouts for workspaces last
938 double remaining_width = container->x + width - x; 1087 if (container->type == C_WORKSPACE) {
939 arrange_windows_r(child, remaining_width, height); 1088 for (int i = 0; i < container->floating->length; ++i) {
940 } else { 1089 swayc_t *view = container->floating->items[i];
941 arrange_windows_r(child, child->width * scale, height); 1090 if (view->type == C_VIEW) {
1091 update_geometry(view);
1092 sway_log(L_DEBUG, "Set floating view to %.f x %.f @ %.f, %.f",
1093 view->width, view->height, view->x, view->y);
1094 if (swayc_is_fullscreen(view)) {
1095 wlc_view_bring_to_front(view->handle);
1096 } else if (!container->focused ||
1097 !swayc_is_fullscreen(container->focused)) {
1098 wlc_view_bring_to_front(view->handle);
942 } 1099 }
943 x += child->width;
944 } 1100 }
1101 }
1102 }
1103}
945 1104
946 // update focused view border last because it may 1105void apply_horiz_layout(swayc_t *container, const double x, const double y,
947 // depend on the title bar geometry of its siblings. 1106 const double width, const double height,
948 if (focused && container->children->length > 1) { 1107 const int start, const int end) {
949 update_container_border(focused); 1108 double scale = 0;
1109 // Calculate total width
1110 for (int i = start; i < end; ++i) {
1111 double *old_width = &((swayc_t *)container->children->items[i])->width;
1112 if (*old_width <= 0) {
1113 if (end - start > 1) {
1114 *old_width = width / (end - start - 1);
1115 } else {
1116 *old_width = width;
950 } 1117 }
951 } 1118 }
952 break; 1119 scale += *old_width;
953 case L_VERT: 1120 }
954 // Calculate total height 1121 scale = width / scale;
955 for (i = 0; i < container->children->length; ++i) { 1122
956 double *old_height = &((swayc_t *)container->children->items[i])->height; 1123 // Resize windows
957 if (*old_height <= 0) { 1124 double child_x = x;
958 if (container->children->length > 1) { 1125 if (scale > 0.1) {
959 *old_height = height / (container->children->length - 1); 1126 sway_log(L_DEBUG, "Arranging %p horizontally", container);
960 } else { 1127 swayc_t *focused = NULL;
961 *old_height = height; 1128 for (int i = start; i < end; ++i) {
962 } 1129 swayc_t *child = container->children->items[i];
1130 sway_log(L_DEBUG,
1131 "Calculating arrangement for %p:%d (will scale %f by %f)", child,
1132 child->type, width, scale);
1133 child->x = child_x;
1134 child->y = y;
1135
1136 if (child == container->focused) {
1137 focused = child;
963 } 1138 }
964 scale += *old_height;
965 }
966 // Resize
967 if (scale > 0.1) {
968 scale = height / scale;
969 sway_log(L_DEBUG, "Arranging %p vertically", container);
970 swayc_t *focused = NULL;
971 for (i = 0; i < container->children->length; ++i) {
972 swayc_t *child = container->children->items[i];
973 sway_log(L_DEBUG, "Calculating arrangement for %p:%d (will scale %f by %f)", child, child->type, height, scale);
974 child->x = x;
975 child->y = y;
976
977 if (child == container->focused) {
978 focused = child;
979 }
980 1139
981 if (i == container->children->length - 1) { 1140 if (i == end - 1) {
982 double remaining_height = container->y + height - y; 1141 double remaining_width = x + width - child_x;
983 arrange_windows_r(child, width, remaining_height); 1142 arrange_windows_r(child, remaining_width, height);
984 } else { 1143 } else {
985 arrange_windows_r(child, width, child->height * scale); 1144 arrange_windows_r(child, child->width * scale, height);
986 }
987 y += child->height;
988 } 1145 }
1146 child_x += child->width;
1147 }
1148
1149 // update focused view border last because it may
1150 // depend on the title bar geometry of its siblings.
1151 if (focused && container->children->length > 1) {
1152 update_container_border(focused);
1153 }
1154 }
1155}
989 1156
990 // update focused view border last because it may 1157void apply_vert_layout(swayc_t *container, const double x, const double y,
991 // depend on the title bar geometry of its siblings. 1158 const double width, const double height, const int start,
992 if (focused && container->children->length > 1) { 1159 const int end) {
993 update_container_border(focused); 1160 int i;
1161 double scale = 0;
1162 // Calculate total height
1163 for (i = start; i < end; ++i) {
1164 double *old_height = &((swayc_t *)container->children->items[i])->height;
1165 if (*old_height <= 0) {
1166 if (end - start > 1) {
1167 *old_height = height / (end - start - 1);
1168 } else {
1169 *old_height = height;
994 } 1170 }
995 } 1171 }
996 break; 1172 scale += *old_height;
997 case L_TABBED: 1173 }
998 case L_STACKED: 1174 scale = height / scale;
999 { 1175
1000 swayc_t *focused = NULL; 1176 // Resize
1001 for (i = 0; i < container->children->length; ++i) { 1177 double child_y = y;
1002 swayc_t *child = container->children->items[i]; 1178 if (scale > 0.1) {
1003 child->x = x; 1179 sway_log(L_DEBUG, "Arranging %p vertically", container);
1004 child->y = y; 1180 swayc_t *focused = NULL;
1005 if (child == container->focused) { 1181 for (i = start; i < end; ++i) {
1006 focused = child; 1182 swayc_t *child = container->children->items[i];
1007 } else { 1183 sway_log(L_DEBUG,
1008 arrange_windows_r(child, width, height); 1184 "Calculating arrangement for %p:%d (will scale %f by %f)", child,
1009 } 1185 child->type, height, scale);
1186 child->x = x;
1187 child->y = child_y;
1188
1189 if (child == container->focused) {
1190 focused = child;
1010 } 1191 }
1011 1192
1012 if (focused) { 1193 if (i == end - 1) {
1013 arrange_windows_r(focused, width, height); 1194 double remaining_height = y + height - child_y;
1195 arrange_windows_r(child, width, remaining_height);
1196 } else {
1197 arrange_windows_r(child, width, child->height * scale);
1014 } 1198 }
1015 break; 1199 child_y += child->height;
1200 }
1201
1202 // update focused view border last because it may
1203 // depend on the title bar geometry of its siblings.
1204 if (focused && container->children->length > 1) {
1205 update_container_border(focused);
1016 } 1206 }
1017 } 1207 }
1208}
1018 1209
1019 // Arrage floating layouts for workspaces last 1210void apply_tabbed_or_stacked_layout(swayc_t *container, double x, double y,
1020 if (container->type == C_WORKSPACE) { 1211 double width, double height) {
1021 for (i = 0; i < container->floating->length; ++i) { 1212 int i;
1022 swayc_t *view = container->floating->items[i]; 1213 swayc_t *focused = NULL;
1023 if (view->type == C_VIEW) { 1214 for (i = 0; i < container->children->length; ++i) {
1024 update_geometry(view); 1215 swayc_t *child = container->children->items[i];
1025 sway_log(L_DEBUG, "Set floating view to %.f x %.f @ %.f, %.f", view->width, 1216 child->x = x;
1026 view->height, view->x, view->y); 1217 child->y = y;
1027 if (swayc_is_fullscreen(view)) { 1218 if (child == container->focused) {
1028 wlc_view_bring_to_front(view->handle); 1219 focused = child;
1029 } else if (!container->focused 1220 } else {
1030 || !swayc_is_fullscreen(container->focused)) { 1221 arrange_windows_r(child, width, height);
1031 wlc_view_bring_to_front(view->handle); 1222 }
1223 }
1224
1225 if (focused) {
1226 arrange_windows_r(focused, width, height);
1227 }
1228}
1229
1230void apply_auto_layout(swayc_t *container, const double x, const double y,
1231 const double width, const double height,
1232 enum swayc_layouts group_layout,
1233 bool master_first) {
1234 // Auto layout "container" in width x height @ x, y
1235 // using "group_layout" for each of the groups in the container.
1236 // There is one "master" group, plus container->nb_slave_groups.
1237 // Each group is layed out side by side following the "major" axis.
1238 // The direction of the layout used for groups is the "minor" axis.
1239 // Example:
1240 //
1241 // ---- major axis -->
1242 // +---------+-----------+
1243 // | | | |
1244 // | master | slave 1 | |
1245 // | +-----------+ | minor axis (direction of group_layout)
1246 // | | | |
1247 // | | slave 2 | V
1248 // +---------+-----------+
1249 //
1250 // container with three children (one master and two slaves) and
1251 // a single slave group (containing slave 1 and 2). The master
1252 // group and slave group are layed out using L_VERT.
1253
1254 size_t nb_groups = auto_group_count(container);
1255
1256 // the target dimension of the container along the "major" axis, each
1257 // group in the container will be layed out using "group_layout" along
1258 // the "minor" axis.
1259 double dim_maj;
1260 double pos_maj;
1261
1262 // x and y coords for the next group to be laid out.
1263 const double *group_x, *group_y;
1264
1265 // pos of the next group to layout along the major axis
1266 double pos;
1267
1268 // size of the next group along the major axis.
1269 double group_dim;
1270
1271 // height and width of next group to be laid out.
1272 const double *group_h, *group_w;
1273
1274 switch (group_layout) {
1275 default:
1276 sway_log(L_DEBUG, "Unknown layout type (%d) used in %s()",
1277 group_layout, __func__);
1278 /* fall through */
1279 case L_VERT:
1280 dim_maj = width;
1281 pos_maj = x;
1282
1283 group_x = &pos;
1284 group_y = &y;
1285 group_w = &group_dim;
1286 group_h = &height;
1287 break;
1288 case L_HORIZ:
1289 dim_maj = height;
1290 pos_maj = y;
1291
1292 group_x = &x;
1293 group_y = &pos;
1294 group_w = &width;
1295 group_h = &group_dim;
1296 break;
1297 }
1298
1299 /* Determine the dimension of each of the groups in the layout.
1300 * Dimension will be width for a VERT layout and height for a HORIZ
1301 * layout. */
1302 double old_group_dim[nb_groups];
1303 double old_dim = 0;
1304 for (size_t group = 0; group < nb_groups; ++group) {
1305 int idx;
1306 if (auto_group_bounds(container, group, &idx, NULL)) {
1307 swayc_t *child = container->children->items[idx];
1308 double *dim = group_layout == L_HORIZ ? &child->height : &child->width;
1309 if (*dim <= 0) {
1310 // New child with uninitialized dimension
1311 *dim = dim_maj;
1312 if (nb_groups > 1) {
1313 // child gets a dimension proportional to existing groups,
1314 // it will be later scaled based on to the available size
1315 // in the major axis.
1316 *dim /= (nb_groups - 1);
1032 } 1317 }
1033 } 1318 }
1319 old_dim += *dim;
1320 old_group_dim[group] = *dim;
1321 }
1322 }
1323 double scale = dim_maj / old_dim;
1324
1325 /* Apply layout to each group */
1326 pos = pos_maj;
1327
1328 for (size_t group = 0; group < nb_groups; ++group) {
1329 int start, end; // index of first (inclusive) and last (exclusive) child in the group
1330 if (auto_group_bounds(container, group, &start, &end)) {
1331 // adjusted size of the group
1332 group_dim = old_group_dim[group] * scale;
1333 if (group == nb_groups - 1) {
1334 group_dim = pos_maj + dim_maj - pos; // remaining width
1335 }
1336 sway_log(L_DEBUG, "Arranging container %p column %zu, children [%d,%d[ (%fx%f+%f,%f)",
1337 container, group, start, end, *group_w, *group_h, *group_x, *group_y);
1338 switch (group_layout) {
1339 default:
1340 case L_VERT:
1341 apply_vert_layout(container, *group_x, *group_y, *group_w, *group_h, start, end);
1342 break;
1343 case L_HORIZ:
1344 apply_horiz_layout(container, *group_x, *group_y, *group_w, *group_h, start, end);
1345 break;
1346 }
1347
1348 /* update position for next group */
1349 pos += group_dim;
1034 } 1350 }
1035 } 1351 }
1036} 1352}
@@ -1106,6 +1422,21 @@ swayc_t *get_swayc_in_direction_under(swayc_t *container, enum movement_directio
1106 return parent; 1422 return parent;
1107 } 1423 }
1108 } 1424 }
1425
1426 if (dir == MOVE_PREV || dir == MOVE_NEXT) {
1427 int focused_idx = index_child(container);
1428 if (focused_idx == -1) {
1429 return NULL;
1430 } else {
1431 int desired = (focused_idx + (dir == MOVE_NEXT ? 1 : -1)) %
1432 parent->children->length;
1433 if (desired < 0) {
1434 desired += parent->children->length;
1435 }
1436 return parent->children->items[desired];
1437 }
1438 }
1439
1109 // If moving to an adjacent output we need a starting position (since this 1440 // If moving to an adjacent output we need a starting position (since this
1110 // output might border to multiple outputs). 1441 // output might border to multiple outputs).
1111 struct wlc_point abs_pos; 1442 struct wlc_point abs_pos;
@@ -1128,7 +1459,8 @@ swayc_t *get_swayc_in_direction_under(swayc_t *container, enum movement_directio
1128 while (true) { 1459 while (true) {
1129 // Test if we can even make a difference here 1460 // Test if we can even make a difference here
1130 bool can_move = false; 1461 bool can_move = false;
1131 int diff = 0; 1462 int desired;
1463 int idx = index_child(container);
1132 if (parent->type == C_ROOT) { 1464 if (parent->type == C_ROOT) {
1133 swayc_t *output = swayc_adjacent_output(container, dir, &abs_pos, true); 1465 swayc_t *output = swayc_adjacent_output(container, dir, &abs_pos, true);
1134 if (!output || output == container) { 1466 if (!output || output == container) {
@@ -1137,21 +1469,36 @@ swayc_t *get_swayc_in_direction_under(swayc_t *container, enum movement_directio
1137 sway_log(L_DEBUG, "Moving between outputs"); 1469 sway_log(L_DEBUG, "Moving between outputs");
1138 return get_swayc_in_output_direction(output, dir); 1470 return get_swayc_in_output_direction(output, dir);
1139 } else { 1471 } else {
1140 if (dir == MOVE_LEFT || dir == MOVE_RIGHT) { 1472 if (is_auto_layout(parent->layout)) {
1141 if (parent->layout == L_HORIZ || parent->layout == L_TABBED) { 1473 bool is_major = parent->layout == L_AUTO_LEFT || parent->layout == L_AUTO_RIGHT
1142 can_move = true; 1474 ? dir == MOVE_LEFT || dir == MOVE_RIGHT
1143 diff = dir == MOVE_LEFT ? -1 : 1; 1475 : dir == MOVE_DOWN || dir == MOVE_UP;
1476 size_t gidx = auto_group_index(parent, idx);
1477 if (is_major) {
1478 size_t desired_grp = gidx + (dir == MOVE_RIGHT || dir == MOVE_DOWN ? 1 : -1);
1479 can_move = auto_group_bounds(parent, desired_grp, &desired, NULL);
1480 } else {
1481 desired = idx + (dir == MOVE_RIGHT || dir == MOVE_DOWN ? 1 : -1);
1482 int start, end;
1483 can_move = auto_group_bounds(parent, gidx, &start, &end)
1484 && desired >= start && desired < end;
1144 } 1485 }
1145 } else { 1486 } else {
1146 if (parent->layout == L_VERT || parent->layout == L_STACKED) { 1487 if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
1147 can_move = true; 1488 if (parent->layout == L_HORIZ || parent->layout == L_TABBED) {
1148 diff = dir == MOVE_UP ? -1 : 1; 1489 can_move = true;
1490 desired = idx + (dir == MOVE_LEFT ? -1 : 1);
1491 }
1492 } else {
1493 if (parent->layout == L_VERT || parent->layout == L_STACKED) {
1494 can_move = true;
1495 desired = idx + (dir == MOVE_UP ? -1 : 1);
1496 }
1149 } 1497 }
1150 } 1498 }
1151 } 1499 }
1152 1500
1153 if (can_move) { 1501 if (can_move) {
1154 int desired = index_child(container) + diff;
1155 if (container->is_floating) { 1502 if (container->is_floating) {
1156 if (desired < 0) { 1503 if (desired < 0) {
1157 wrap_candidate = parent->floating->items[parent->floating->length-1]; 1504 wrap_candidate = parent->floating->items[parent->floating->length-1];
@@ -1178,6 +1525,8 @@ swayc_t *get_swayc_in_direction_under(swayc_t *container, enum movement_directio
1178 } 1525 }
1179 } 1526 }
1180 } else { 1527 } else {
1528 sway_log(L_DEBUG, "%s cont %d-%p dir %i sibling %d: %p", __func__,
1529 idx, container, dir, desired, parent->children->items[desired]);
1181 return parent->children->items[desired]; 1530 return parent->children->items[desired];
1182 } 1531 }
1183 } 1532 }
@@ -1233,3 +1582,167 @@ enum swayc_layouts default_layout(swayc_t *output) {
1233 return L_VERT; 1582 return L_VERT;
1234 } 1583 }
1235} 1584}
1585
1586bool is_auto_layout(enum swayc_layouts layout) {
1587 return (layout >= L_AUTO_FIRST) && (layout <= L_AUTO_LAST);
1588}
1589
1590/**
1591 * Return the number of master elements in a container
1592 */
1593static inline size_t auto_master_count(const swayc_t *container) {
1594 sway_assert(container->children->length >= 0, "Container %p has (negative) children %d",
1595 container, container->children->length);
1596 return MIN(container->nb_master, (size_t)container->children->length);
1597}
1598
1599/**
1600 * Return the number of children in the slave groups. This corresponds to the children
1601 * that are not members of the master group.
1602 */
1603static inline size_t auto_slave_count(const swayc_t *container) {
1604 return container->children->length - auto_master_count(container);
1605}
1606
1607/**
1608 * Return the number of slave groups in the container.
1609 */
1610size_t auto_slave_group_count(const swayc_t *container) {
1611 return MIN(container->nb_slave_groups, auto_slave_count(container));
1612}
1613
1614/**
1615 * Return the combined number of master and slave groups in the container.
1616 */
1617size_t auto_group_count(const swayc_t *container) {
1618 return auto_slave_group_count(container)
1619 + (container->children->length && container->nb_master ? 1 : 0);
1620}
1621
1622/**
1623 * given the index of a container's child, return the index of the first child of the group
1624 * which index is a member of.
1625 */
1626int auto_group_start_index(const swayc_t *container, int index) {
1627 if (index < 0 || ! is_auto_layout(container->layout)
1628 || (size_t)index < container->nb_master) {
1629 return 0;
1630 } else {
1631 size_t nb_slaves = auto_slave_count(container);
1632 size_t nb_slave_grp = auto_slave_group_count(container);
1633 size_t grp_sz = nb_slaves / nb_slave_grp;
1634 size_t remainder = nb_slaves % nb_slave_grp;
1635 int idx2 = (nb_slave_grp - remainder) * grp_sz + container->nb_master;
1636 int start_idx;
1637 if (index < idx2) {
1638 start_idx = ((index - container->nb_master) / grp_sz) * grp_sz + container->nb_master;
1639 } else {
1640 start_idx = idx2 + ((index - idx2) / (grp_sz + 1)) * (grp_sz + 1);
1641 }
1642 return MIN(start_idx, container->children->length);
1643 }
1644}
1645
1646/**
1647 * given the index of a container's child, return the index of the first child of the group
1648 * that follows the one which index is a member of.
1649 * This makes the function usable to walk through the groups in a container.
1650 */
1651int auto_group_end_index(const swayc_t *container, int index) {
1652 if (index < 0 || ! is_auto_layout(container->layout)) {
1653 return container->children->length;
1654 } else {
1655 int nxt_idx;
1656 if ((size_t)index < container->nb_master) {
1657 nxt_idx = auto_master_count(container);
1658 } else {
1659 size_t nb_slaves = auto_slave_count(container);
1660 size_t nb_slave_grp = auto_slave_group_count(container);
1661 size_t grp_sz = nb_slaves / nb_slave_grp;
1662 size_t remainder = nb_slaves % nb_slave_grp;
1663 int idx2 = (nb_slave_grp - remainder) * grp_sz + container->nb_master;
1664 if (index < idx2) {
1665 nxt_idx = ((index - container->nb_master) / grp_sz + 1) * grp_sz + container->nb_master;
1666 } else {
1667 nxt_idx = idx2 + ((index - idx2) / (grp_sz + 1) + 1) * (grp_sz + 1);
1668 }
1669 }
1670 return MIN(nxt_idx, container->children->length);
1671 }
1672}
1673
1674/**
1675 * return the index of the Group containing <index>th child of <container>.
1676 * The index is the order of the group along the container's major axis (starting at 0).
1677 */
1678size_t auto_group_index(const swayc_t *container, int index) {
1679 if (index < 0) {
1680 return 0;
1681 }
1682 bool master_first = (container->layout == L_AUTO_LEFT || container->layout == L_AUTO_TOP);
1683 size_t nb_slaves = auto_slave_count(container);
1684 if ((size_t)index < container->nb_master) {
1685 if (master_first || nb_slaves <= 0) {
1686 return 0;
1687 } else {
1688 return auto_slave_group_count(container);
1689 }
1690 } else {
1691 size_t nb_slave_grp = auto_slave_group_count(container);
1692 size_t grp_sz = nb_slaves / nb_slave_grp;
1693 size_t remainder = nb_slaves % nb_slave_grp;
1694 int idx2 = (nb_slave_grp - remainder) * grp_sz + container->nb_master;
1695 size_t grp_idx;
1696 if (index < idx2) {
1697 grp_idx = (index - container->nb_master) / grp_sz;
1698 } else {
1699 grp_idx = (nb_slave_grp - remainder) + (index - idx2) / (grp_sz + 1) ;
1700 }
1701 return grp_idx + (master_first && container-> nb_master ? 1 : 0);
1702 }
1703}
1704
1705/**
1706 * Return the first index (inclusive) and last index (exclusive) of the elements of a group in
1707 * an auto layout.
1708 * If the bounds of the given group can be calculated, they are returned in the start/end
1709 * parameters (int pointers) and the return value will be true.
1710 * The indexes are passed by reference and can be NULL.
1711 */
1712bool auto_group_bounds(const swayc_t *container, size_t group_index, int *start, int *end) {
1713 size_t nb_grp = auto_group_count(container);
1714 if (group_index >= nb_grp) {
1715 return false;
1716 }
1717 bool master_first = (container->layout == L_AUTO_LEFT || container->layout == L_AUTO_TOP);
1718 size_t nb_master = auto_master_count(container);
1719 size_t nb_slave_grp = auto_slave_group_count(container);
1720 int g_start, g_end;
1721 if (nb_master && (master_first ? group_index == 0 : group_index == nb_grp - 1)) {
1722 g_start = 0;
1723 g_end = nb_master;
1724 } else {
1725 size_t nb_slaves = auto_slave_count(container);
1726 size_t grp_sz = nb_slaves / nb_slave_grp;
1727 size_t remainder = nb_slaves % nb_slave_grp;
1728 size_t g0 = master_first && container->nb_master ? 1 : 0;
1729 size_t g1 = g0 + nb_slave_grp - remainder;
1730 if (group_index < g1) {
1731 g_start = container->nb_master + (group_index - g0) * grp_sz;
1732 g_end = g_start + grp_sz;
1733 } else {
1734 size_t g2 = group_index - g1;
1735 g_start = container->nb_master
1736 + (nb_slave_grp - remainder) * grp_sz
1737 + g2 * (grp_sz + 1);
1738 g_end = g_start + grp_sz + 1;
1739 }
1740 }
1741 if (start) {
1742 *start = g_start;
1743 }
1744 if (end) {
1745 *end = g_end;
1746 }
1747 return true;
1748}
diff --git a/sway/sway.5.txt b/sway/sway.5.txt
index 7980ba82..ee1cbddd 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,13 @@ 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 _next_ and _prev_ directions 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. The parent
69 container, or to move the entire container around. 69 focus command will change the focus to the parent of the currently
70 focused container, which is useful, for example, to open a sibling of
71 the parent container, or to move the entire container around.
70 72
71**focus** output <direction|name>:: 73**focus** output <direction|name>::
72 Direction may be one of _up_, _down_, _left_, _right_. The directional focus 74 Direction may be one of _up_, _down_, _left_, _right_. The directional focus
@@ -81,10 +83,28 @@ They are expected to be used with **bindsym** or at runtime through **swaymsg**(
81 83
82**layout** <mode>:: 84**layout** <mode>::
83 Sets the layout mode of the focused container. _mode_ can be one of _splith_, 85 Sets the layout mode of the focused container. _mode_ can be one of _splith_,
84 _splitv_, _toggle split_, _stacking_ or _tabbed_. 86 _splitv_, _toggle split_, _stacking_, _tabbed_.
87
88**layout** auto <mode>::
89 Sets layout to one of the auto modes, i.e. one of _left_, right_, _top_,
90 or _bottom_.
91
92**layout** auto <next|prev>::
93 Cycles between available auto layouts.
94
95**layout** auto [master|ncol] [inc|set] <n>::
96 Modify the number of master elements, respectively slave columns, in the
97 focused container. <n> can be a positive or negative integer. These commands
98 only have an effect if the focused container uses one of the "auto" layouts.
99
100**layout** toggle split::
101 Cycles between available split layouts.
85 102
86**move** <left|right|up|down>:: 103**move** <left|right|up|down|next|prev|first>::
87 Moves the focused container _left_, _right_, _up_, or _down_. 104 Moves the focused container _left_, _right_, _up_, or _down_. Moving to _prev_
105 or _next_ swaps the container with its sibling in the same container. Move
106 _first_ exchanges the focused element in an auto layout with the first
107 element, i.e. promotes the focused element to master position.
88 108
89**move** <container|window> to workspace <name>:: 109**move** <container|window> to workspace <name>::
90 Moves the focused container to the workspace identified by _name_. 110 Moves the focused container to the workspace identified by _name_.
@@ -360,8 +380,8 @@ The default colors are:
360 switch to workspace 2, then invoke the "workspace 2" command again, you 380 switch to workspace 2, then invoke the "workspace 2" command again, you
361 will be returned to workspace 1. Defaults to _no_. 381 will be returned to workspace 1. Defaults to _no_.
362 382
363**workspace_layout** <default|stacking|tabbed>:: 383**workspace_layout** <default|stacking|tabbed|auto|auto left|auto right|auto
364 Specifies the start layout for new workspaces. 384 top|auto bottom>:: Specifies the start layout for new workspaces.
365 385
366**include** <path>:: 386**include** <path>::
367 Includes a sub config file by _path_. _path_ can be either a full path or a 387 Includes a sub config file by _path_. _path_ can be either a full path or a