From 742c2b53fd7560685bf880e2185a68d44b7b14f7 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Fri, 12 Dec 2025 15:26:30 +0900 Subject: [PATCH] cycle: implement scrollable OSD Before this commit, the OSD could overflow the screen when displaying many window items. In this commit, we hide the overflowed items and show a scrollbar to fit the OSD within the screen. --- include/cycle.h | 36 +++++++++++++++ src/cycle/meson.build | 1 + src/cycle/osd-classic.c | 31 ++++++++++--- src/cycle/osd-scroll.c | 95 +++++++++++++++++++++++++++++++++++++++ src/cycle/osd-thumbnail.c | 35 ++++++++++++--- 5 files changed, 185 insertions(+), 13 deletions(-) create mode 100644 src/cycle/osd-scroll.c diff --git a/include/cycle.h b/include/cycle.h index 2e5b57ec..c6e42810 100644 --- a/include/cycle.h +++ b/include/cycle.h @@ -8,6 +8,7 @@ #include "config/types.h" struct output; +struct wlr_box; enum lab_cycle_dir { LAB_CYCLE_DIR_NONE, @@ -67,8 +68,21 @@ struct cycle_osd_output { struct output *output; struct wl_listener tree_destroy; + /* set by cycle_osd_impl->init() */ struct wl_list items; /* struct cycle_osd_item.link */ struct wlr_scene_tree *tree; + /* set by cycle_osd_impl->init() and moved by cycle_osd_scroll_update() */ + struct wlr_scene_tree *items_tree; + + /* used in osd-scroll.c */ + struct cycle_osd_scroll_context { + int top_row_idx; + int nr_rows, nr_cols, nr_visible_rows; + int delta_y; + struct wlr_box bar_area; + struct wlr_scene_tree *bar_tree; + struct lab_scene_rect *bar; + } scroll; }; struct buf; @@ -124,6 +138,28 @@ struct cycle_osd_impl { void (*update)(struct cycle_osd_output *osd_output); }; +#define SCROLLBAR_W 10 + +/** + * Initialize the context and scene for scrolling OSD items. + * + * @output: Output of the OSD + * @bar_area: Area where the scrollbar is drawn + * @delta_y: The vertical delta by which items are scrolled (usually item height) + * @nr_cols: Number of columns in the OSD + * @nr_rows: Number of rows in the OSD + * @nr_visible_rows: Number of visible rows in the OSD + * @border_color: Border color of the scrollbar + * @bg_color: Background color of the scrollbar + */ +void cycle_osd_scroll_init(struct cycle_osd_output *osd_output, + struct wlr_box bar_area, int delta_y, + int nr_cols, int nr_rows, int nr_visible_rows, + float *border_color, float *bg_color); + +/* Scroll the OSD to show server->cycle.selected_view if needed */ +void cycle_osd_scroll_update(struct cycle_osd_output *osd_output); + extern struct cycle_osd_impl cycle_osd_classic_impl; extern struct cycle_osd_impl cycle_osd_thumbnail_impl; diff --git a/src/cycle/meson.build b/src/cycle/meson.build index 07e9f7aa..db244520 100644 --- a/src/cycle/meson.build +++ b/src/cycle/meson.build @@ -2,5 +2,6 @@ labwc_sources += files( 'cycle.c', 'osd-classic.c', 'osd-field.c', + 'osd-scroll.c', 'osd-thumbnail.c', ) diff --git a/src/cycle/osd-classic.c b/src/cycle/osd-classic.c index c0509249..c9b5497d 100644 --- a/src/cycle/osd-classic.c +++ b/src/cycle/osd-classic.c @@ -97,11 +97,16 @@ cycle_osd_classic_init(struct cycle_osd_output *osd_output) if (switcher_theme->width_is_percent) { w = output_box.width * switcher_theme->width / 100; } - int h = nr_views * switcher_theme->item_height + 2 * padding; + int workspace_name_h = 0; if (show_workspace) { /* workspace indicator */ - h += switcher_theme->item_height; + workspace_name_h = switcher_theme->item_height; } + int nr_visible_views = (output_box.height - workspace_name_h - 2 * padding) + / switcher_theme->item_height; + nr_visible_views = MIN(nr_visible_views, nr_views); + int h = workspace_name_h + nr_visible_views * switcher_theme->item_height + + 2 * padding; osd_output->tree = wlr_scene_tree_create(output->cycle_osd_tree); @@ -154,13 +159,17 @@ cycle_osd_classic_init(struct cycle_osd_output *osd_output) goto error; } + float *active_bg_color = switcher_theme->item_active_bg_color; + float *active_border_color = switcher_theme->item_active_border_color; + osd_output->items_tree = wlr_scene_tree_create(osd_output->tree); + /* Draw text for each node */ struct view *view; wl_list_for_each(view, &server->cycle.views, cycle_link) { struct cycle_osd_classic_item *item = znew(*item); wl_list_append(&osd_output->items, &item->base.link); item->base.view = view; - item->base.tree = wlr_scene_tree_create(osd_output->tree); + item->base.tree = wlr_scene_tree_create(osd_output->items_tree); node_descriptor_create(&item->base.tree->node, LAB_NODE_CYCLE_OSD_ITEM, NULL, item); /* @@ -186,9 +195,6 @@ cycle_osd_classic_init(struct cycle_osd_output *osd_output) item->active_tree = wlr_scene_tree_create(item->base.tree); wlr_scene_node_set_enabled(&item->active_tree->node, false); - float *active_bg_color = switcher_theme->item_active_bg_color; - float *active_border_color = switcher_theme->item_active_border_color; - /* Highlight around selected window's item */ struct lab_scene_rect_options highlight_opts = { .border_colors = (float *[1]) {active_border_color}, @@ -215,6 +221,17 @@ cycle_osd_classic_init(struct cycle_osd_output *osd_output) y += switcher_theme->item_height; } + struct wlr_box scrollbar_area = { + .x = w - padding - SCROLLBAR_W, + .y = padding, + .width = SCROLLBAR_W, + .height = h - 2 * padding, + }; + cycle_osd_scroll_init(osd_output, scrollbar_area, + switcher_theme->item_height, + /*nr_cols*/ 1, /*nr_rows*/ nr_views, nr_visible_views, + active_border_color, active_bg_color); + error:; /* Center OSD */ wlr_scene_node_set_position(&osd_output->tree->node, @@ -226,6 +243,8 @@ static void cycle_osd_classic_update(struct cycle_osd_output *osd_output) { struct server *server = osd_output->output->server; + cycle_osd_scroll_update(osd_output); + struct cycle_osd_classic_item *item; wl_list_for_each(item, &osd_output->items, base.link) { bool active = item->base.view == server->cycle.selected_view; diff --git a/src/cycle/osd-scroll.c b/src/cycle/osd-scroll.c new file mode 100644 index 00000000..2bca8021 --- /dev/null +++ b/src/cycle/osd-scroll.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include "common/lab-scene-rect.h" +#include "labwc.h" +#include "cycle.h" +#include "output.h" + +void +cycle_osd_scroll_init(struct cycle_osd_output *osd_output, struct wlr_box bar_area, + int delta_y, int nr_cols, int nr_rows, int nr_visible_rows, + float *border_color, float *bg_color) +{ + if (nr_visible_rows >= nr_rows) { + /* OSD doesn't have so many windows to scroll through */ + return; + } + + struct cycle_osd_scroll_context *scroll = &osd_output->scroll; + scroll->nr_cols = nr_cols; + scroll->nr_rows = nr_rows; + scroll->nr_visible_rows = nr_visible_rows; + scroll->top_row_idx = 0; + scroll->bar_area = bar_area; + scroll->delta_y = delta_y; + scroll->bar_tree = wlr_scene_tree_create(osd_output->tree); + wlr_scene_node_set_position(&scroll->bar_tree->node, + bar_area.x, bar_area.y); + + struct lab_scene_rect_options scrollbar_opts = { + .border_colors = (float *[1]) { border_color }, + .nr_borders = 1, + .border_width = 1, + .bg_color = bg_color, + .width = bar_area.width, + .height = bar_area.height * nr_visible_rows / nr_rows, + }; + scroll->bar = lab_scene_rect_create(scroll->bar_tree, &scrollbar_opts); +} + +static int +get_cycle_idx(struct cycle_osd_output *osd_output) +{ + struct server *server = osd_output->output->server; + + int idx = 0; + struct cycle_osd_item *item; + wl_list_for_each(item, &osd_output->items, link) { + if (item->view == server->cycle.selected_view) { + return idx; + } + idx++; + } + assert(false && "selected view not found in items"); + return -1; +} + +void +cycle_osd_scroll_update(struct cycle_osd_output *osd_output) +{ + struct cycle_osd_scroll_context *scroll = &osd_output->scroll; + if (!scroll->bar) { + return; + } + + int cycle_idx = get_cycle_idx(osd_output); + + /* Update the range of visible rows */ + int bottom_row_idx = scroll->top_row_idx + scroll->nr_visible_rows; + while (cycle_idx < scroll->top_row_idx * scroll->nr_cols) { + scroll->top_row_idx--; + bottom_row_idx--; + } + while (cycle_idx >= bottom_row_idx * scroll->nr_cols) { + scroll->top_row_idx++; + bottom_row_idx++; + } + + /* Vertically move scrollbar by (bar height) / (# of total rows) */ + wlr_scene_node_set_position(&scroll->bar->tree->node, 0, + scroll->bar_area.height * scroll->top_row_idx / scroll->nr_rows); + /* Vertically move items */ + wlr_scene_node_set_position(&osd_output->items_tree->node, 0, + -scroll->delta_y * scroll->top_row_idx); + + /* Hide items outside of visible area */ + int idx = 0; + struct cycle_osd_item *item; + wl_list_for_each(item, &osd_output->items, link) { + bool visible = idx >= scroll->top_row_idx * scroll->nr_cols + && idx < bottom_row_idx * scroll->nr_cols; + wlr_scene_node_set_enabled(&item->tree->node, visible); + idx++; + } +} diff --git a/src/cycle/osd-thumbnail.c b/src/cycle/osd-thumbnail.c index dde48786..d5a0bf3e 100644 --- a/src/cycle/osd-thumbnail.c +++ b/src/cycle/osd-thumbnail.c @@ -194,9 +194,10 @@ create_item_scene(struct wlr_scene_tree *parent, struct view *view, } static void -get_items_geometry(struct output *output, struct theme *theme, - int nr_thumbs, int *nr_rows, int *nr_cols) +get_items_geometry(struct output *output, int nr_thumbs, + int *nr_cols, int *nr_rows, int *nr_visible_rows) { + struct theme *theme = output->server->theme; struct window_switcher_thumbnail_theme *switcher_theme = &theme->osd_window_switcher_thumbnail; int output_width, output_height; @@ -223,6 +224,9 @@ get_items_geometry(struct output *output, struct theme *theme, (*nr_rows)++; *nr_cols = ceilf((float)nr_thumbs / *nr_rows); } + + *nr_visible_rows = MIN(*nr_rows, + (output_height - 2 * padding) / switcher_theme->item_height); } static void @@ -236,18 +240,19 @@ cycle_osd_thumbnail_init(struct cycle_osd_output *osd_output) int padding = theme->osd_border_width + switcher_theme->padding; osd_output->tree = wlr_scene_tree_create(output->cycle_osd_tree); + osd_output->items_tree = wlr_scene_tree_create(osd_output->tree); int nr_views = wl_list_length(&server->cycle.views); assert(nr_views > 0); - int nr_rows, nr_cols; - get_items_geometry(output, theme, nr_views, &nr_rows, &nr_cols); + int nr_cols, nr_rows, nr_visible_rows; + get_items_geometry(output, nr_views, &nr_cols, &nr_rows, &nr_visible_rows); /* items */ struct view *view; int index = 0; wl_list_for_each(view, &server->cycle.views, cycle_link) { struct cycle_osd_thumbnail_item *item = create_item_scene( - osd_output->tree, view, osd_output); + osd_output->items_tree, view, osd_output); if (!item) { break; } @@ -257,14 +262,28 @@ cycle_osd_thumbnail_init(struct cycle_osd_output *osd_output) index++; } + int items_width = switcher_theme->item_width * nr_cols; + int items_height = switcher_theme->item_height * nr_visible_rows; + + struct wlr_box scrollbar_area = { + .x = padding + items_width - SCROLLBAR_W, + .y = padding, + .width = SCROLLBAR_W, + .height = items_height, + }; + cycle_osd_scroll_init(osd_output, scrollbar_area, + switcher_theme->item_height, nr_cols, nr_rows, nr_visible_rows, + switcher_theme->item_active_border_color, + switcher_theme->item_active_bg_color); + /* background */ struct lab_scene_rect_options bg_opts = { .border_colors = (float *[1]) { theme->osd_border_color }, .nr_borders = 1, .border_width = theme->osd_border_width, .bg_color = theme->osd_bg_color, - .width = nr_cols * switcher_theme->item_width + 2 * padding, - .height = nr_rows * switcher_theme->item_height + 2 * padding, + .width = items_width + 2 * padding, + .height = items_height + 2 * padding, }; struct lab_scene_rect *bg = lab_scene_rect_create(osd_output->tree, &bg_opts); @@ -283,6 +302,8 @@ static void cycle_osd_thumbnail_update(struct cycle_osd_output *osd_output) { struct server *server = osd_output->output->server; + cycle_osd_scroll_update(osd_output); + struct cycle_osd_thumbnail_item *item; wl_list_for_each(item, &osd_output->items, base.link) { bool active = (item->base.view == server->cycle.selected_view);