diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index e86fb75f..1dc59a8c 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -278,7 +278,7 @@ this is for compatibility with Openbox. ## WINDOW SWITCHER ``` - + @@ -287,10 +287,14 @@ this is for compatibility with Openbox. ``` -** +** *show* [yes|no] Draw the OnScreenDisplay when switching between windows. Default is yes. + *style* [classic|thumbnail] Configures the style of the OnScreenDisplay. + "classic" displays window information like icons and titles in a vertical list. + "thumbnail" shows window thumbnail, icon and title in grids. + *preview* [yes|no] Preview the contents of the selected window when switching between windows. Default is yes. @@ -302,7 +306,7 @@ this is for compatibility with Openbox. are shown). ** - Define window switcher fields. + Define window switcher fields when using **. *content* defines what the field shows and can be any of: diff --git a/docs/labwc-theme.5.scd b/docs/labwc-theme.5.scd index dc249611..efc3a1a5 100644 --- a/docs/labwc-theme.5.scd +++ b/docs/labwc-theme.5.scd @@ -303,6 +303,10 @@ all are supported. Text color of on-screen-display. Inherits *window.active.label.text.color* if not set. +*osd.window-switcher.style-classic* + Theme for window switcher when using . + See below for details. + *osd.window-switcher.style-classic.width* Width of window switcher in pixels. Width can also be a percentage of the monitor width by adding '%' as suffix (e.g. 70%). Default is 600. @@ -328,6 +332,40 @@ all are supported. If not set, the font size derived from is used. +*osd.window-switcher.style-thumbnail* + Theme for window switcher when using . + See below for details. + +*osd.window-switcher.style-thumbnail.width.max* + Maximum width of window switcher in pixels. Width can also be a percentage of + the monitor width by adding '%' as suffix (e.g. 70%). Default is 80%. + +*osd.window-switcher.style-thumbnail.padding* + Padding of window switcher in pixels. This is the space between the + window-switcher border and its items. Default is 4. + +*osd.window-switcher.style-thumbnail.item.width* + Width of window switcher items in pixels. Default is 300. + +*osd.window-switcher.style-thumbnail.item.height* + Height of window switcher items in pixels. Default is 250. + +*osd.window-switcher.style-thumbnail.item.padding* + Padding of window switcher items in pixels. This is the space between the + border around selected items and window thumbnail. Default is 2. + +*osd.window-switcher.style-thumbnail.item.active.border.width* + Border width of selected window switcher items in pixels. Default is 2. + +*osd.window-switcher.style-thumbnail.item.active.border.color* + Color of border around selected window switcher items. Default is #589bda. + +*osd.window-switcher.style-thumbnail.item.active.bg.color* + Color of selected window switcher items. Default is #c7e2fc. + +*osd.window-switcher.style-thumbnail.item.icon.size* + Size of window icons in window switcher items in pixels. Default is 60. + *osd.window-switcher.preview.border.width* Border width of the outlines shown as the preview of the window selected by window switcher. Inherits *osd.border.width* if not set. diff --git a/docs/rc.xml.all b/docs/rc.xml.all index 91dc7791..940b2f96 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -75,7 +75,7 @@ - + diff --git a/docs/themerc b/docs/themerc index a4a70799..7b251a3c 100644 --- a/docs/themerc +++ b/docs/themerc @@ -99,6 +99,17 @@ osd.window-switcher.style-classic.item.padding.y: 1 osd.window-switcher.style-classic.item.active.border.width: 2 # The icon size the same as the font size by default # osd.window-switcher.style-classic.item.icon.size: 50 + +osd.window-switcher.style-thumbnail.width.max: 80% +osd.window-switcher.style-thumbnail.padding: 4 +osd.window-switcher.style-thumbnail.item.width: 300 +osd.window-switcher.style-thumbnail.item.height: 250 +osd.window-switcher.style-thumbnail.item.padding: 10 +osd.window-switcher.style-thumbnail.item.active.border.width: 2 +osd.window-switcher.style-thumbnail.item.active.border.color: #589bda +osd.window-switcher.style-thumbnail.item.active.bg.color: #c7e2fc +osd.window-switcher.style-thumbnail.item.icon.size: 60 + osd.window-switcher.preview.border.width: 1 osd.window-switcher.preview.border.color: #dddda6,#000000,#dddda6 diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 86daf4e4..0da38114 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -175,6 +175,7 @@ struct rcxml { bool outlines; enum lab_view_criteria criteria; struct wl_list fields; /* struct window_switcher_field.link */ + enum window_switcher_style style; } window_switcher; struct wl_list window_rules; /* struct window_rule.link */ diff --git a/include/config/types.h b/include/config/types.h index 75f207ac..e832a658 100644 --- a/include/config/types.h +++ b/include/config/types.h @@ -107,4 +107,9 @@ enum lab_window_type { LAB_WINDOW_TYPE_LEN }; +enum window_switcher_style { + WINDOW_SWITCHER_CLASSIC, + WINDOW_SWITCHER_THUMBNAIL, +}; + #endif /* LABWC_CONFIG_TYPES_H */ diff --git a/include/osd.h b/include/osd.h index ad0b3bce..a5c2a4cc 100644 --- a/include/osd.h +++ b/include/osd.h @@ -83,5 +83,6 @@ struct osd_impl { }; extern struct osd_impl osd_classic_impl; +extern struct osd_impl osd_thumbnail_impl; #endif // LABWC_OSD_H diff --git a/include/theme.h b/include/theme.h index 6f4a81c6..75f3c8c0 100644 --- a/include/theme.h +++ b/include/theme.h @@ -180,6 +180,21 @@ struct theme { int item_height; } osd_window_switcher_classic; + struct window_switcher_thumbnail_theme { + int max_width; + int padding; + int item_width; + int item_height; + int item_padding; + int item_active_border_width; + float item_active_border_color[4]; + float item_active_bg_color[4]; + int item_icon_size; + bool max_width_is_percent; + + int title_height; + } osd_window_switcher_thumbnail; + int osd_window_switcher_preview_border_width; float osd_window_switcher_preview_border_color[3][4]; diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 280c0256..26b56ec6 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1210,6 +1210,12 @@ entry(xmlNode *node, char *nodename, char *content) /* */ } else if (!strcasecmp(nodename, "show.windowSwitcher")) { set_bool(content, &rc.window_switcher.show); + } else if (!strcasecmp(nodename, "style.windowSwitcher")) { + if (!strcasecmp(content, "classic")) { + rc.window_switcher.style = WINDOW_SWITCHER_CLASSIC; + } else if (!strcasecmp(content, "thumbnail")) { + rc.window_switcher.style = WINDOW_SWITCHER_THUMBNAIL; + } } else if (!strcasecmp(nodename, "preview.windowSwitcher")) { set_bool(content, &rc.window_switcher.preview); } else if (!strcasecmp(nodename, "outlines.windowSwitcher")) { @@ -1431,6 +1437,7 @@ rcxml_init(void) rc.snap_tiling_events_mode = LAB_TILING_EVENTS_ALWAYS; rc.window_switcher.show = true; + rc.window_switcher.style = WINDOW_SWITCHER_CLASSIC; rc.window_switcher.preview = true; rc.window_switcher.outlines = true; rc.window_switcher.criteria = LAB_VIEW_CRITERIA_CURRENT_WORKSPACE diff --git a/src/osd/meson.build b/src/osd/meson.build index 4f4da2cb..17b4bf51 100644 --- a/src/osd/meson.build +++ b/src/osd/meson.build @@ -2,4 +2,5 @@ labwc_sources += files( 'osd.c', 'osd-classic.c', 'osd-field.c', + 'osd-thumbnail.c', ) diff --git a/src/osd/osd-thumbnail.c b/src/osd/osd-thumbnail.c new file mode 100644 index 00000000..2f37d50d --- /dev/null +++ b/src/osd/osd-thumbnail.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include +#include +#include "config/rcxml.h" +#include "config/types.h" +#include "common/array.h" +#include "common/box.h" +#include "common/lab-scene-rect.h" +#include "common/scaled-font-buffer.h" +#include "common/scaled-icon-buffer.h" +#include "labwc.h" +#include "osd.h" +#include "output.h" +#include "theme.h" + +struct osd_thumbnail_scene_item { + struct view *view; + struct wlr_scene_tree *tree; + struct scaled_font_buffer *normal_title; + struct scaled_font_buffer *active_title; + struct lab_scene_rect *active_bg; +}; + +static void +render_node(struct server *server, struct wlr_render_pass *pass, + struct wlr_scene_node *node, int x, int y) +{ + switch (node->type) { + case WLR_SCENE_NODE_TREE: { + struct wlr_scene_tree *tree = wlr_scene_tree_from_node(node); + struct wlr_scene_node *child; + wl_list_for_each(child, &tree->children, link) { + render_node(server, pass, child, x + node->x, y + node->y); + } + break; + } + case WLR_SCENE_NODE_BUFFER: { + struct wlr_scene_buffer *scene_buffer = + wlr_scene_buffer_from_node(node); + if (!scene_buffer->buffer) { + break; + } + struct wlr_texture *texture = wlr_texture_from_buffer( + server->renderer, scene_buffer->buffer); + if (!texture) { + break; + } + wlr_render_pass_add_texture(pass, &(struct wlr_render_texture_options){ + .texture = texture, + .src_box = scene_buffer->src_box, + .dst_box = { + .x = x, + .y = y, + .width = scene_buffer->dst_width, + .height = scene_buffer->dst_height, + }, + .transform = scene_buffer->transform, + }); + wlr_texture_destroy(texture); + break; + } + case WLR_SCENE_NODE_RECT: + /* should be unreached */ + wlr_log(WLR_ERROR, "ignoring rect"); + break; + } +} + +static struct wlr_buffer * +render_thumb(struct output *output, struct view *view) +{ + struct server *server = output->server; + struct wlr_buffer *buffer = wlr_allocator_create_buffer(server->allocator, + view->current.width, view->current.height, + &output->wlr_output->swapchain->format); + struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass( + server->renderer, buffer, NULL); + render_node(server, pass, &view->content_tree->node, 0, 0); + if (!wlr_render_pass_submit(pass)) { + wlr_log(WLR_ERROR, "failed to submit render pass"); + wlr_buffer_drop(buffer); + return NULL; + } + return buffer; +} + +static struct scaled_font_buffer * +create_title(struct wlr_scene_tree *parent, + struct window_switcher_thumbnail_theme *switcher_theme, + const char *title, const float *title_color, + const float *bg_color, int y) +{ + struct scaled_font_buffer *buffer = + scaled_font_buffer_create(parent); + scaled_font_buffer_update(buffer, title, + switcher_theme->item_width - 2 * switcher_theme->item_padding, + &rc.font_osd, title_color, bg_color); + wlr_scene_node_set_position(&buffer->scene_buffer->node, + (switcher_theme->item_width - buffer->width) / 2, y); + return buffer; +} + +static struct osd_thumbnail_scene_item * +create_item_scene(struct wlr_scene_tree *parent, struct view *view, + struct output *output) +{ + struct server *server = output->server; + struct theme *theme = server->theme; + struct window_switcher_thumbnail_theme *switcher_theme = + &theme->osd_window_switcher_thumbnail; + int padding = theme->border_width + switcher_theme->item_padding; + int title_y = switcher_theme->item_height - padding - switcher_theme->title_height; + struct wlr_box thumb_bounds = { + .x = padding, + .y = padding, + .width = switcher_theme->item_width - 2 * padding, + .height = title_y - 2 * padding, + }; + if (thumb_bounds.width <= 0 || thumb_bounds.height <= 0) { + wlr_log(WLR_ERROR, "too small thumbnail area"); + return NULL; + } + + struct osd_thumbnail_scene_item *item = + wl_array_add(&output->osd_scene.items, sizeof(*item)); + item->tree = wlr_scene_tree_create(parent); + item->view = view; + + /* background for selected item */ + struct lab_scene_rect_options opts = { + .width = switcher_theme->item_width, + .height = switcher_theme->item_height, + .bg_color = switcher_theme->item_active_bg_color, + .nr_borders = 1, + .border_colors = (float *[1]) { switcher_theme->item_active_border_color }, + .border_width = switcher_theme->item_active_border_width, + }; + item->active_bg = lab_scene_rect_create(item->tree, &opts); + + /* thumbnail */ + struct wlr_buffer *thumb_buffer = render_thumb(output, view); + if (thumb_buffer) { + struct wlr_scene_buffer *thumb_scene_buffer = + wlr_scene_buffer_create(item->tree, thumb_buffer); + wlr_buffer_drop(thumb_buffer); + struct wlr_box thumb_box = box_fit_within( + thumb_buffer->width, thumb_buffer->height, + &thumb_bounds); + wlr_scene_buffer_set_dest_size(thumb_scene_buffer, + thumb_box.width, thumb_box.height); + wlr_scene_node_set_position(&thumb_scene_buffer->node, + thumb_box.x, thumb_box.y); + } + + /* title */ + const char *title = view_get_string_prop(view, "title"); + if (title) { + item->normal_title = create_title(item->tree, switcher_theme, + title, theme->osd_label_text_color, + theme->osd_bg_color, title_y); + item->active_title = create_title(item->tree, switcher_theme, + title, theme->osd_label_text_color, + switcher_theme->item_active_bg_color, title_y); + } + + /* icon */ + int icon_size = switcher_theme->item_icon_size; + struct scaled_icon_buffer *icon_buffer = scaled_icon_buffer_create( + item->tree, server, icon_size, icon_size); + scaled_icon_buffer_set_view(icon_buffer, view); + int x = (switcher_theme->item_width - icon_size) / 2; + int y = title_y - padding - icon_size + 10; /* slide by 10px */ + wlr_scene_node_set_position(&icon_buffer->scene_buffer->node, x, y); + + return item; +} + +static void +get_items_geometry(struct output *output, struct theme *theme, + int nr_thumbs, int *nr_rows, int *nr_cols) +{ + struct window_switcher_thumbnail_theme *thumb_theme = + &theme->osd_window_switcher_thumbnail; + int output_width = output->wlr_output->width / output->wlr_output->scale; + + int max_bg_width = thumb_theme->max_width; + if (thumb_theme->max_width_is_percent) { + max_bg_width = output_width * thumb_theme->max_width / 100; + } + + *nr_rows = 1; + *nr_cols = nr_thumbs; + while (1) { + assert(*nr_rows <= nr_thumbs); + int bg_width = *nr_cols * thumb_theme->item_width + + theme->osd_border_width + thumb_theme->padding; + if (bg_width < max_bg_width) { + break; + } + if (*nr_rows >= nr_thumbs) { + break; + } + (*nr_rows)++; + *nr_cols = ceilf((float)nr_thumbs / *nr_rows); + } +} + +static void +osd_thumbnail_create(struct output *output, struct wl_array *views) +{ + assert(!output->osd_scene.tree); + + struct theme *theme = output->server->theme; + struct window_switcher_thumbnail_theme *switcher_theme = + &theme->osd_window_switcher_thumbnail; + int padding = theme->osd_border_width + switcher_theme->padding; + + output->osd_scene.tree = wlr_scene_tree_create(output->osd_tree); + + int nr_views = wl_array_len(views); + assert(nr_views > 0); + int nr_rows, nr_cols; + get_items_geometry(output, theme, nr_views, &nr_rows, &nr_cols); + + /* items */ + struct view **view; + int index = 0; + wl_array_for_each(view, views) { + struct osd_thumbnail_scene_item *item = create_item_scene( + output->osd_scene.tree, *view, output); + if (!item) { + break; + } + int x = (index % nr_cols) * switcher_theme->item_width + padding; + int y = (index / nr_cols) * switcher_theme->item_height + padding; + wlr_scene_node_set_position(&item->tree->node, x, y); + index++; + } + + /* background */ + struct lab_scene_rect_options bg_opts = { + .width = nr_cols * switcher_theme->item_width + 2 * padding, + .height = nr_rows * switcher_theme->item_height + 2 * padding, + .bg_color = theme->osd_bg_color, + .nr_borders = 1, + .border_width = theme->osd_border_width, + .border_colors = (float *[1]) { theme->osd_border_color }, + }; + struct lab_scene_rect *bg = + lab_scene_rect_create(output->osd_scene.tree, &bg_opts); + wlr_scene_node_lower_to_bottom(&bg->tree->node); + + /* center */ + struct wlr_box usable = output_usable_area_in_layout_coords(output); + int lx = usable.x + (usable.width - bg_opts.width) / 2; + int ly = usable.y + (usable.height - bg_opts.height) / 2; + wlr_scene_node_set_position(&output->osd_scene.tree->node, lx, ly); +} + +static void +osd_thumbnail_update(struct output *output) +{ + struct osd_thumbnail_scene_item *item; + wl_array_for_each(item, &output->osd_scene.items) { + bool active = (item->view == output->server->osd_state.cycle_view); + wlr_scene_node_set_enabled(&item->active_bg->tree->node, active); + wlr_scene_node_set_enabled( + &item->active_title->scene_buffer->node, active); + wlr_scene_node_set_enabled( + &item->normal_title->scene_buffer->node, !active); + } +} + +struct osd_impl osd_thumbnail_impl = { + .create = osd_thumbnail_create, + .update = osd_thumbnail_update, +}; diff --git a/src/osd/osd.c b/src/osd/osd.c index 9bef5ce3..28714d72 100644 --- a/src/osd/osd.c +++ b/src/osd/osd.c @@ -265,7 +265,16 @@ update_osd(struct server *server) struct wl_array views; wl_array_init(&views); view_array_append(server, &views, rc.window_switcher.criteria); - struct osd_impl *osd_impl = &osd_classic_impl; + + struct osd_impl *osd_impl = NULL; + switch (rc.window_switcher.style) { + case WINDOW_SWITCHER_CLASSIC: + osd_impl = &osd_classic_impl; + break; + case WINDOW_SWITCHER_THUMBNAIL: + osd_impl = &osd_thumbnail_impl; + break; + } if (!wl_array_len(&views) || !server->osd_state.cycle_view) { osd_finish(server); diff --git a/src/theme.c b/src/theme.c index f874e585..45e42c15 100644 --- a/src/theme.c +++ b/src/theme.c @@ -616,6 +616,17 @@ theme_builtin(struct theme *theme, struct server *server) theme->osd_window_switcher_classic.item_active_border_width = 2; theme->osd_window_switcher_classic.item_icon_size = -1; + theme->osd_window_switcher_thumbnail.max_width = 80; + theme->osd_window_switcher_thumbnail.max_width_is_percent = true; + theme->osd_window_switcher_thumbnail.padding = 4; + theme->osd_window_switcher_thumbnail.item_width = 300; + theme->osd_window_switcher_thumbnail.item_height = 250; + theme->osd_window_switcher_thumbnail.item_padding = 10; + theme->osd_window_switcher_thumbnail.item_active_border_width = 2; + parse_color("#589bda", theme->osd_window_switcher_thumbnail.item_active_border_color); + parse_color("#c7e2fc", theme->osd_window_switcher_thumbnail.item_active_bg_color); + theme->osd_window_switcher_thumbnail.item_icon_size = 60; + /* inherit settings in post_processing() if not set elsewhere */ theme->osd_window_switcher_preview_border_width = INT_MIN; zero_array(theme->osd_window_switcher_preview_border_color); @@ -681,6 +692,8 @@ entry(struct theme *theme, const char *key, const char *value) struct window_switcher_classic_theme *switcher_classic_theme = &theme->osd_window_switcher_classic; + struct window_switcher_thumbnail_theme *switcher_thumb_theme = + &theme->osd_window_switcher_thumbnail; /* * Note that in order for the pattern match to apply to more than just @@ -992,6 +1005,47 @@ entry(struct theme *theme, const char *key, const char *value) get_int_if_positive(value, "osd.window-switcher.style-classic.item.icon.size"); } + /* thumbnail window switcher */ + if (match_glob(key, "osd.window-switcher.style-thumbnail.width.max")) { + if (strrchr(value, '%')) { + switcher_thumb_theme->max_width_is_percent = true; + } else { + switcher_thumb_theme->max_width_is_percent = false; + } + switcher_thumb_theme->max_width = get_int_if_positive( + value, "osd.window-switcher.style-thumbnail.width.max"); + } + if (match_glob(key, "osd.window-switcher.style-thumbnail.padding")) { + switcher_thumb_theme->padding = get_int_if_positive( + value, "osd.window-switcher.style-thumbnail.padding"); + } + if (match_glob(key, "osd.window-switcher.style-thumbnail.item.width")) { + switcher_thumb_theme->item_width = get_int_if_positive( + value, "osd.window-switcher.style-thumbnail.item.width"); + } + if (match_glob(key, "osd.window-switcher.style-thumbnail.item.height")) { + switcher_thumb_theme->item_height = get_int_if_positive( + value, "osd.window-switcher.style-thumbnail.item.height"); + } + if (match_glob(key, "osd.window-switcher.style-thumbnail.item.padding")) { + switcher_thumb_theme->item_padding = get_int_if_positive( + value, "osd.window-switcher.style-thumbnail.item.padding"); + } + if (match_glob(key, "osd.window-switcher.style-thumbnail.item.active.border.width")) { + switcher_thumb_theme->item_active_border_width = get_int_if_positive( + value, "osd.window-switcher.style-thumbnail.item.active.border.width"); + } + if (match_glob(key, "osd.window-switcher.style-thumbnail.item.active.border.color")) { + parse_color(value, switcher_thumb_theme->item_active_border_color); + } + if (match_glob(key, "osd.window-switcher.style-thumbnail.item.active.bg.color")) { + parse_color(value, switcher_thumb_theme->item_active_bg_color); + } + if (match_glob(key, "osd.window-switcher.style-thumbnail.item.icon.size")) { + switcher_thumb_theme->item_icon_size = get_int_if_positive( + value, "osd.window-switcher.style-thumbnail.item.icon.size"); + } + if (match_glob(key, "osd.window-switcher.preview.border.width")) { theme->osd_window_switcher_preview_border_width = get_int_if_positive( @@ -1594,6 +1648,8 @@ post_processing(struct theme *theme) { struct window_switcher_classic_theme *switcher_classic_theme = &theme->osd_window_switcher_classic; + struct window_switcher_thumbnail_theme *switcher_thumb_theme = + &theme->osd_window_switcher_thumbnail; theme->titlebar_height = get_titlebar_height(theme); @@ -1607,6 +1663,7 @@ post_processing(struct theme *theme) + 2 * theme->menu_items_padding_y; int osd_font_height = font_height(&rc.font_osd); + switcher_thumb_theme->title_height = osd_font_height; if (switcher_classic_theme->item_icon_size <= 0) { switcher_classic_theme->item_icon_size = osd_font_height; }