From d7b5eb037a82a156959a3099ef78f76c738585c3 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Fri, 15 Nov 2024 19:18:29 +0900 Subject: [PATCH] Add scaled_rect_buffer This will be used for menus, OSD and titlebar corners in the future. --- include/common/corner.h | 15 +++ include/common/scaled-rect-buffer.h | 40 +++++++ src/common/meson.build | 1 + src/common/scaled-rect-buffer.c | 167 ++++++++++++++++++++++++++++ src/theme.c | 13 +-- 5 files changed, 227 insertions(+), 9 deletions(-) create mode 100644 include/common/corner.h create mode 100644 include/common/scaled-rect-buffer.h create mode 100644 src/common/scaled-rect-buffer.c diff --git a/include/common/corner.h b/include/common/corner.h new file mode 100644 index 00000000..dacf98d6 --- /dev/null +++ b/include/common/corner.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef LABWC_CORNER_H +#define LABWC_CORNER_H + +enum lab_corner { + LAB_CORNER_UNKNOWN = 0, + LAB_CORNER_TOP_LEFT = 1 << 0, + LAB_CORNER_TOP_RIGHT = 1 << 1, + LAB_CORNER_BOTTOM_LEFT = 1 << 2, + LAB_CORNER_BOTTOM_RIGHT = 1 << 3, + LAB_CORNER_ALL = (LAB_CORNER_TOP_LEFT | LAB_CORNER_TOP_RIGHT + | LAB_CORNER_BOTTOM_LEFT | LAB_CORNER_BOTTOM_RIGHT), +}; + +#endif /* LABWC_CORNER_H */ diff --git a/include/common/scaled-rect-buffer.h b/include/common/scaled-rect-buffer.h new file mode 100644 index 00000000..a7a41375 --- /dev/null +++ b/include/common/scaled-rect-buffer.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef LABWC_SCALED_RECT_BUFFER_H +#define LABWC_SCALED_RECT_BUFFER_H + +#include +#include +#include "common/corner.h" + +struct wlr_scene_tree; +struct wlr_scene_buffer; +struct scaled_scene_buffer; + +#define EDGES_ALL (WLR_DIRECTION_UP| WLR_DIRECTION_DOWN \ + | WLR_DIRECTION_LEFT | WLR_DIRECTION_RIGHT) + +struct scaled_rect_buffer { + struct wlr_scene_buffer *scene_buffer; + struct scaled_scene_buffer *scaled_buffer; + int width; + int height; + int border_width; + int corner_radius; + uint32_t rounded_corners; /* bitmap of lab_corner */ + uint32_t stroked_edges; /* bitmap of wlr_direction */ + float fill_color[4]; + float border_color[4]; +}; + +/** + * Create an auto scaling rounded-rect buffer, providing a wlr_scene_buffer + * node for display. It gets destroyed automatically when the backing + * scaled_scene_buffer is being destroyed which in turn happens automatically + * when the backing wlr_scene_buffer (or one of its parents) is being destroyed. + */ +struct scaled_rect_buffer *scaled_rect_buffer_create( + struct wlr_scene_tree *parent, int width, int height, int border_width, + int corner_radius, uint32_t rounded_corners, uint32_t stroked_edges, + float fill_color[4], float border_color[4]); + +#endif /* LABWC_SCALED_RECT_BUFFER_H */ diff --git a/src/common/meson.build b/src/common/meson.build index d369df4c..d6ad2c64 100644 --- a/src/common/meson.build +++ b/src/common/meson.build @@ -14,6 +14,7 @@ labwc_sources += files( 'parse-bool.c', 'parse-double.c', 'scaled-font-buffer.c', + 'scaled-rect-buffer.c', 'scaled-scene-buffer.c', 'scene-helpers.c', 'set.c', diff --git a/src/common/scaled-rect-buffer.c b/src/common/scaled-rect-buffer.c new file mode 100644 index 00000000..f2b52406 --- /dev/null +++ b/src/common/scaled-rect-buffer.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0-only +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include "buffer.h" +#include "common/corner.h" +#include "common/graphic-helpers.h" +#include "common/list.h" +#include "common/macros.h" +#include "common/mem.h" +#include "common/scaled-scene-buffer.h" +#include "common/scaled-rect-buffer.h" + +static struct wl_list cached_buffers = WL_LIST_INIT(&cached_buffers); + +static void +draw_edge(cairo_t *cairo, int w, int h, int r, uint32_t corners, enum wlr_direction edge) +{ + static const double deg = 0.017453292519943295; + + switch (edge) { + case WLR_DIRECTION_UP: + if (corners & LAB_CORNER_TOP_LEFT) { + cairo_new_sub_path(cairo); + cairo_arc(cairo, r, r, r, 180 * deg, 270 * deg); + } else if (!cairo_has_current_point(cairo)) { + cairo_move_to(cairo, 0, 0); + } + cairo_line_to(cairo, (corners & LAB_CORNER_TOP_RIGHT) ? w - r : w, 0); + break; + case WLR_DIRECTION_RIGHT: + if (corners & LAB_CORNER_TOP_RIGHT) { + cairo_arc(cairo, w - r, r, r, -90 * deg, 0 * deg); + } else if (!cairo_has_current_point(cairo)) { + cairo_move_to(cairo, w, 0); + } + cairo_line_to(cairo, w, (corners & LAB_CORNER_BOTTOM_RIGHT) ? h - r : h); + break; + case WLR_DIRECTION_DOWN: + if (corners & LAB_CORNER_BOTTOM_RIGHT) { + cairo_arc(cairo, w - r, h - r, r, 0 * deg, 90 * deg); + } else if (!cairo_has_current_point(cairo)) { + cairo_move_to(cairo, w, h); + } + cairo_line_to(cairo, (corners & LAB_CORNER_BOTTOM_LEFT) ? r : 0, h); + break; + case WLR_DIRECTION_LEFT: + if (corners & LAB_CORNER_BOTTOM_LEFT) { + cairo_arc(cairo, r, h - r, r, 90 * deg, 180 * deg); + } else if (!cairo_has_current_point(cairo)) { + cairo_move_to(cairo, 0, h); + } + cairo_line_to(cairo, 0, (corners & LAB_CORNER_TOP_LEFT) ? r : 0); + break; + } +} + +static struct lab_data_buffer * +_create_buffer(struct scaled_scene_buffer *scaled_buffer, double scale) +{ + struct scaled_rect_buffer *self = scaled_buffer->data; + struct lab_data_buffer *buffer = buffer_create_cairo(self->width, self->height, scale); + if (!buffer) { + return NULL; + } + + cairo_t *cairo = buffer->cairo; + cairo_save(cairo); + + /* Clear background */ + cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); + cairo_paint(cairo); + cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); + + double w = self->width; + double h = self->height; + double r = self->corner_radius; + + /* Draw rounded rectangle with border */ + enum wlr_direction edges[4] = {WLR_DIRECTION_UP, WLR_DIRECTION_RIGHT, + WLR_DIRECTION_DOWN, WLR_DIRECTION_LEFT}; + for (int i = 0; i < (int)ARRAY_SIZE(edges); i++) { + draw_edge(cairo, w, h, r, self->rounded_corners, edges[i]); + } + cairo_close_path(cairo); + set_cairo_color(cairo, self->fill_color); + cairo_fill(cairo); + + double half_border_width = (double)self->border_width / 2; + w -= self->border_width; + h -= self->border_width; + r = MAX(r - half_border_width, 0); + cairo_translate(cairo, half_border_width, half_border_width); + cairo_set_line_width(cairo, self->border_width); + cairo_set_line_cap(cairo, CAIRO_LINE_CAP_SQUARE); + + for (int i = 0; i < (int)ARRAY_SIZE(edges); i++) { + if (self->stroked_edges & edges[i]) { + set_cairo_color(cairo, self->border_color); + draw_edge(cairo, w, h, r, self->rounded_corners, edges[i]); + cairo_stroke(cairo); + } + } + + cairo_restore(cairo); + + return buffer; +} + +static void +_destroy(struct scaled_scene_buffer *scaled_buffer) +{ + struct scaled_rect_buffer *self = scaled_buffer->data; + scaled_buffer->data = NULL; + free(self); +} + +static bool +_equal(struct scaled_scene_buffer *scaled_buffer_a, struct scaled_scene_buffer *scaled_buffer_b) +{ + struct scaled_rect_buffer *a = scaled_buffer_a->data; + struct scaled_rect_buffer *b = scaled_buffer_b->data; + + return a->width == b->width + && a->height == b->height + && a->border_width == b->border_width + && a->corner_radius == b->corner_radius + && a->rounded_corners == b->rounded_corners + && a->stroked_edges == b->stroked_edges + && !memcmp(a->fill_color, b->fill_color, sizeof(a->fill_color)) + && !memcmp(a->border_color, b->border_color, sizeof(a->border_color)); +} + +static const struct scaled_scene_buffer_impl impl = { + .create_buffer = _create_buffer, + .destroy = _destroy, + .equal = _equal, +}; + +struct scaled_rect_buffer *scaled_rect_buffer_create( + struct wlr_scene_tree *parent, int width, int height, int border_width, + int corner_radius, uint32_t rounded_corners, uint32_t stroked_edges, + float fill_color[4], float border_color[4]) +{ + assert(parent); + struct scaled_rect_buffer *self = znew(*self); + struct scaled_scene_buffer *scaled_buffer = scaled_scene_buffer_create( + parent, &impl, &cached_buffers, /* drop_buffer */ true); + scaled_buffer->data = self; + self->scaled_buffer = scaled_buffer; + self->scene_buffer = scaled_buffer->scene_buffer; + self->width = MAX(width, 1); + self->height = MAX(height, 1); + self->border_width = border_width; + self->corner_radius = corner_radius; + self->rounded_corners = rounded_corners; + self->stroked_edges = stroked_edges; + memcpy(self->fill_color, fill_color, sizeof(self->fill_color)); + memcpy(self->border_color, border_color, sizeof(self->border_color)); + + scaled_scene_buffer_invalidate_cache(scaled_buffer); + + return self; +} diff --git a/src/theme.c b/src/theme.c index bea07400..10d8ca2f 100644 --- a/src/theme.c +++ b/src/theme.c @@ -19,10 +19,11 @@ #include #include #include -#include "common/macros.h" +#include "common/corner.h" #include "common/dir.h" #include "common/font.h" #include "common/graphic-helpers.h" +#include "common/macros.h" #include "common/match.h" #include "common/mem.h" #include "common/parse-bool.h" @@ -49,19 +50,13 @@ struct button { uint8_t state_set; }; -enum corner { - LAB_CORNER_UNKNOWN = 0, - LAB_CORNER_TOP_LEFT, - LAB_CORNER_TOP_RIGHT, -}; - struct rounded_corner_ctx { struct wlr_box *box; double radius; double line_width; float *fill_color; float *border_color; - enum corner corner; + enum lab_corner corner; }; #define zero_array(arr) memset(arr, 0, sizeof(arr)) @@ -164,7 +159,7 @@ create_hover_fallback(struct theme *theme, } static void -create_rounded_buffer(struct theme *theme, enum corner corner, +create_rounded_buffer(struct theme *theme, enum lab_corner corner, struct lab_data_buffer **rounded_buffer, struct lab_data_buffer *icon_buffer) {