Skip to content

Commit 1099d29

Browse files
committed
feat: tabbed layout ux improvements (e/w focus for tab next/prev, middle click close, scroll for cycle, hide_lone_tab config option)
1 parent 4b78e86 commit 1099d29

8 files changed

Lines changed: 89 additions & 9 deletions

File tree

examples/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ Controls how window decorations (titlebars, borders) are displayed:
124124

125125
Clients that do not support `xdg-decoration` keep their own client-side decorations in `none` and `tabs` modes.
126126

127+
```
128+
doorsctl config hide_lone_tab true|false
129+
```
130+
131+
When true, the tab bar is hidden when a tabbed container has only one window.
132+
127133
```
128134
doorsctl config edge_scroller_pointer_focus true|false
129135
```

include/tabs.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ extern float color_tab_text[4];
3434
extern float color_tab_text_active[4];
3535
extern float color_tab_sep[4];
3636

37-
int tab_bar_height(void);
37+
size_t tabbed_leaf_count(node_t *n);
38+
int tab_bar_height(node_t *n);
3839

3940
void tabs_create(node_t *n);
4041
void tabs_destroy(node_t *n);

include/types.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ extern bool allow_tearing;
237237
extern bool auto_float_dialogs;
238238
extern decoration_mode_t decoration_mode;
239239
extern bool enable_animations;
240+
extern bool hide_lone_tab;
240241
extern workspace_anim_direction_t workspace_anim_direction;
241242
extern bool workspace_anim_slide_up;
242243
extern int mapping_events_count;

src/cursor.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,22 @@ void cursor_button(struct wl_listener *listener, void *data) {
727727
}
728728
}
729729

730+
// middle-click on tab bar closes the tab
731+
if (event->button == BTN_MIDDLE) {
732+
for (output_t *m = mon_head; m != NULL; m = m->next) {
733+
desktop_t *d = m->desk;
734+
if (d == NULL)
735+
continue;
736+
737+
node_t *tab_leaf = tabs_hit_test_desktop(d, server.cursor->x, server.cursor->y);
738+
if (tab_leaf != NULL) {
739+
kill_node(d, tab_leaf);
740+
server.cursor_buttons |= 1 << (event->button - 272);
741+
return;
742+
}
743+
}
744+
}
745+
730746
double sx, sy;
731747
struct wlr_surface *surface = NULL;
732748
void *type = desktop_type_at(server.cursor->x, server.cursor->y, &surface, &sx, &sy);
@@ -931,6 +947,33 @@ void cursor_button(struct wl_listener *listener, void *data) {
931947
void cursor_axis(struct wl_listener *listener, void *data) {
932948
(void)listener;
933949
struct wlr_pointer_axis_event *event = data;
950+
951+
// cycle through tabs when cursor is over a tab bar
952+
for (output_t *m = mon_head; m != NULL; m = m->next) {
953+
desktop_t *d = m->desk;
954+
if (d == NULL)
955+
continue;
956+
957+
node_t *tab_leaf = tabs_hit_test_desktop(d, server.cursor->x, server.cursor->y);
958+
if (tab_leaf != NULL) {
959+
node_t *tab_node = tabbed_ancestor(tab_leaf);
960+
if (tab_node != NULL) {
961+
node_t *next;
962+
double delta = event->delta_discrete != 0 ? (double)event->delta_discrete : event->delta;
963+
if (delta < 0)
964+
next = tab_prev_leaf(tab_node, d->focus);
965+
else
966+
next = tab_next_leaf(tab_node, d->focus);
967+
968+
if (next != NULL && next != d->focus) {
969+
focus_node(m, d, next);
970+
arrange(m, d, true);
971+
}
972+
}
973+
return;
974+
}
975+
}
976+
934977
wlr_seat_pointer_notify_axis(server.seat, event->time_msec, event->orientation, event->delta,
935978
event->delta_discrete, event->source, event->relative_direction);
936979
wlr_idle_notifier_v1_notify_activity(server.idle_notifier, server.seat);

src/ipc_cmd/config.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ void ipc_cmd_config(char **args, int num, int client_fd) {
113113
snprintf(buf, sizeof(buf), "%s\n", mode);
114114
send_success(client_fd, buf);
115115
}
116+
} else if (streq("hide_lone_tab", *args)) {
117+
ipc_handle_bool(args, num, client_fd, &hide_lone_tab, IPC_FLAG_COMMIT);
116118
} else if (streq("gapless_monocle", *args)) {
117119
ipc_handle_bool(args, num, client_fd, &gapless_monocle, IPC_FLAG_COMMIT);
118120
} else if (streq("decoration_mode", *args)) {

src/keyboard.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "scroller.h"
1111
#include "seat.h"
1212
#include "server.h"
13+
#include "tabs.h"
1314
#include "toplevel.h"
1415
#include "transaction.h"
1516
#include "tree.h"
@@ -288,6 +289,18 @@ void focus_west(void) {
288289
if (mon == NULL || mon->desk == NULL || mon->desk->focus == NULL)
289290
return;
290291

292+
// cycle to previous tab
293+
node_t *tab_anc = tabbed_ancestor(mon->desk->focus);
294+
if (tab_anc != NULL) {
295+
node_t *prev = tab_prev_leaf(tab_anc, mon->desk->focus);
296+
if (prev != NULL && prev != mon->desk->focus) {
297+
focus_node(mon, mon->desk, prev);
298+
arrange(mon, mon->desk, true);
299+
wlr_log(WLR_DEBUG, "Focused previous tab");
300+
}
301+
return;
302+
}
303+
291304
if (mon->desk->layout == LAYOUT_SCROLLER) {
292305
if (scroller_focus_prev(mon->desk)) {
293306
focus_node(mon, mon->desk, mon->desk->focus);
@@ -327,6 +340,18 @@ void focus_east(void) {
327340
if (mon == NULL || mon->desk == NULL || mon->desk->focus == NULL)
328341
return;
329342

343+
// cycle to next tab
344+
node_t *tab_anc = tabbed_ancestor(mon->desk->focus);
345+
if (tab_anc != NULL) {
346+
node_t *next = tab_next_leaf(tab_anc, mon->desk->focus);
347+
if (next != NULL && next != mon->desk->focus) {
348+
focus_node(mon, mon->desk, next);
349+
arrange(mon, mon->desk, true);
350+
wlr_log(WLR_DEBUG, "Focused next tab");
351+
}
352+
return;
353+
}
354+
330355
if (mon->desk->layout == LAYOUT_SCROLLER) {
331356
if (scroller_focus_next(mon->desk)) {
332357
focus_node(mon, mon->desk, mon->desk->focus);

src/tabs.c

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ float color_tab_sep[4] = {
5656
1.0f
5757
};
5858

59-
int tab_bar_height(void) {
59+
int tab_bar_height(node_t *n) {
60+
if (hide_lone_tab && n && tabbed_leaf_count(n) <= 1)
61+
return 0;
6062
return TAB_BAR_HEIGHT;
6163
}
6264

@@ -88,7 +90,7 @@ static size_t collect_leaves(node_t *n, node_t **out, size_t cap) {
8890
return used;
8991
}
9092

91-
static size_t count_leaves(node_t *n) {
93+
size_t tabbed_leaf_count(node_t *n) {
9294
return collect_leaves(n, NULL, 0);
9395
}
9496

@@ -112,7 +114,7 @@ node_t *tab_focus_leaf(node_t *tabbed_node, node_t *focus) {
112114
node_t *tab_next_leaf(node_t *tabbed_node, node_t *focus) {
113115
if (tabbed_node == NULL)
114116
return NULL;
115-
size_t n = count_leaves(tabbed_node);
117+
size_t n = tabbed_leaf_count(tabbed_node);
116118
if (n == 0)
117119
return NULL;
118120
node_t **arr = calloc(n, sizeof(node_t *));
@@ -134,7 +136,7 @@ node_t *tab_next_leaf(node_t *tabbed_node, node_t *focus) {
134136
node_t *tab_prev_leaf(node_t *tabbed_node, node_t *focus) {
135137
if (tabbed_node == NULL)
136138
return NULL;
137-
size_t n = count_leaves(tabbed_node);
139+
size_t n = tabbed_leaf_count(tabbed_node);
138140
if (n == 0)
139141
return NULL;
140142
node_t **arr = calloc(n, sizeof(node_t *));
@@ -177,7 +179,7 @@ static void destroy_entries(struct tab_bar_t *bar) {
177179
static void build_entries(struct tab_bar_t *bar) {
178180
destroy_entries(bar);
179181

180-
size_t count = count_leaves(bar->owner);
182+
size_t count = tabbed_leaf_count(bar->owner);
181183
if (count == 0)
182184
return;
183185

src/tree.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ bool allow_tearing = false;
3838
bool auto_float_dialogs = false;
3939
decoration_mode_t decoration_mode = DECORATION_ALWAYS;
4040
bool enable_animations = false;
41+
bool hide_lone_tab = false;
4142
workspace_anim_direction_t workspace_anim_direction = WORKSPACE_ANIM_VERTICAL;
4243
bool workspace_anim_slide_up = false;
4344
int mapping_events_count = 0;
@@ -621,8 +622,7 @@ void apply_layout(output_t *m, desktop_t *d, node_t *n, struct wlr_box rect,
621622
bool show_deco = decoration_mode == DECORATION_ALWAYS || decoration_mode == DECORATION_TABS;
622623

623624
if (show_deco) {
624-
int bar_h = tab_bar_height();
625-
625+
int bar_h = tab_bar_height(n);
626626
int wg = compute_window_gap(d);
627627
struct wlr_box bar_rect = {
628628
.x = rect.x,
@@ -642,7 +642,7 @@ void apply_layout(output_t *m, desktop_t *d, node_t *n, struct wlr_box rect,
642642

643643
struct wlr_box content_rect = rect;
644644
if (show_deco) {
645-
int bar_h = tab_bar_height();
645+
int bar_h = tab_bar_height(n);
646646
content_rect.y += bar_h;
647647
content_rect.height = (bar_h < content_rect.height) ? content_rect.height - bar_h : 0;
648648
}

0 commit comments

Comments
 (0)