[proof of concept] textured borders

This commit is contained in:
John Lindgren 2026-02-16 03:59:41 -05:00
parent 654da8c8a9
commit bbba3e03fd
5 changed files with 147 additions and 14 deletions

View file

@ -114,7 +114,7 @@ struct ssd {
struct wlr_scene_tree *tree; struct wlr_scene_tree *tree;
struct ssd_border_subtree { struct ssd_border_subtree {
struct wlr_scene_tree *tree; struct wlr_scene_tree *tree;
struct wlr_scene_rect *top, *bottom, *left, *right; struct wlr_scene_buffer *top, *bottom, *left, *right;
} subtrees[2]; /* indexed by enum ssd_active_state */ } subtrees[2]; /* indexed by enum ssd_active_state */
} border; } border;

View file

@ -14,6 +14,11 @@
struct lab_img; struct lab_img;
/* XXX: using fixed border widths for now */
/* (top is narrower to blend with titlebar) */
#define BORDER_PX_TOP 2
#define BORDER_PX_SIDE 4
/* /*
* Openbox defines 7 types of Gradient background in addition to Solid. * Openbox defines 7 types of Gradient background in addition to Solid.
* Currently, labwc supports only Vertical and SplitVertical. * Currently, labwc supports only Vertical and SplitVertical.

View file

@ -2,7 +2,9 @@
#include <assert.h> #include <assert.h>
#include <wlr/types/wlr_scene.h> #include <wlr/types/wlr_scene.h>
#include "buffer.h"
#include "common/macros.h" #include "common/macros.h"
#include "common/mem.h"
#include "labwc.h" #include "labwc.h"
#include "ssd.h" #include "ssd.h"
#include "ssd-internal.h" #include "ssd-internal.h"
@ -17,13 +19,19 @@ ssd_border_create(struct ssd *ssd)
struct view *view = ssd->view; struct view *view = ssd->view;
struct theme *theme = view->server->theme; struct theme *theme = view->server->theme;
#if 0
/* XXX: not using with textured borders */
int width = view->current.width; int width = view->current.width;
int height = view_effective_height(view, /* use_pending */ false); int height = view_effective_height(view, /* use_pending */ false);
int full_width = width + 2 * theme->border_width; int full_width = width + 2 * theme->border_width;
int corner_width = ssd_get_corner_width(); int corner_width = ssd_get_corner_width();
#endif
ssd->border.tree = wlr_scene_tree_create(ssd->tree); ssd->border.tree = wlr_scene_tree_create(ssd->tree);
#if 0
/* XXX: eliminated this offset */
wlr_scene_node_set_position(&ssd->border.tree->node, -theme->border_width, 0); wlr_scene_node_set_position(&ssd->border.tree->node, -theme->border_width, 0);
#endif
enum ssd_active_state active; enum ssd_active_state active;
FOR_EACH_ACTIVE_STATE(active) { FOR_EACH_ACTIVE_STATE(active) {
@ -31,8 +39,10 @@ ssd_border_create(struct ssd *ssd)
subtree->tree = wlr_scene_tree_create(ssd->border.tree); subtree->tree = wlr_scene_tree_create(ssd->border.tree);
struct wlr_scene_tree *parent = subtree->tree; struct wlr_scene_tree *parent = subtree->tree;
wlr_scene_node_set_enabled(&parent->node, active); wlr_scene_node_set_enabled(&parent->node, active);
float *color = theme->window[active].border_color; float *color = theme->window[active].title_bg.color;
#if 0
/* XXX: old solid color borders */
subtree->left = wlr_scene_rect_create(parent, subtree->left = wlr_scene_rect_create(parent,
theme->border_width, height, color); theme->border_width, height, color);
wlr_scene_node_set_position(&subtree->left->node, 0, 0); wlr_scene_node_set_position(&subtree->left->node, 0, 0);
@ -52,7 +62,78 @@ ssd_border_create(struct ssd *ssd)
wlr_scene_node_set_position(&subtree->top->node, wlr_scene_node_set_position(&subtree->top->node,
theme->border_width + corner_width, theme->border_width + corner_width,
-(ssd->titlebar.height + theme->border_width)); -(ssd->titlebar.height + theme->border_width));
#endif
uint8_t r = color[0] * 255;
uint8_t g = color[1] * 255;
uint8_t b = color[2] * 255;
uint8_t a = color[3] * 255;
/* darker outline */
uint8_t r0 = r / 2;
uint8_t g0 = g / 2;
uint8_t b0 = b / 2;
/* highlight */
uint8_t r1 = MIN(r * 5 / 4, a);
uint8_t g1 = MIN(g * 5 / 4, a);
uint8_t b1 = MIN(b * 5 / 4, a);
uint32_t col = ((uint32_t)a << 24) | ((uint32_t)r << 16)
| ((uint32_t)g << 8) | b;
uint32_t col0 = ((uint32_t)a << 24) | ((uint32_t)r0 << 16)
| ((uint32_t)g0 << 8) | b0;
uint32_t col1 = ((uint32_t)a << 24) | ((uint32_t)r1 << 16)
| ((uint32_t)g1 << 8) | b1;
/* top and left start out the same */
uint32_t *left_data = znew_n(uint32_t, BORDER_PX_SIDE);
uint32_t *top_data = znew_n(uint32_t, BORDER_PX_TOP);
left_data[0] = top_data[0] = col0;
left_data[1] = top_data[1] = col1;
for (int i = 2; i < BORDER_PX_SIDE; i++) {
left_data[i] = col;
} }
for (int i = 2; i < BORDER_PX_TOP; i++) {
top_data[i] = col;
}
/* bottom and right are identical */
uint32_t *right_data = znew_n(uint32_t, BORDER_PX_SIDE);
uint32_t *bottom_data = znew_n(uint32_t, BORDER_PX_SIDE);
for (int i = 0; i < BORDER_PX_SIDE - 1; i++) {
right_data[i] = col;
bottom_data[i] = col;
}
right_data[BORDER_PX_SIDE - 1] = col0;
bottom_data[BORDER_PX_SIDE - 1] = col0;
/* order matters here since the border overlap */
struct lab_data_buffer *bottom_buffer =
buffer_create_from_data(bottom_data, 1, BORDER_PX_SIDE, 4);
subtree->bottom = wlr_scene_buffer_create(parent, &bottom_buffer->base);
wlr_buffer_drop(&bottom_buffer->base);
struct lab_data_buffer *right_buffer =
buffer_create_from_data(right_data, BORDER_PX_SIDE, 1,
4 * BORDER_PX_SIDE);
subtree->right = wlr_scene_buffer_create(parent, &right_buffer->base);
wlr_buffer_drop(&right_buffer->base);
struct lab_data_buffer *left_buffer =
buffer_create_from_data(left_data, BORDER_PX_SIDE, 1,
4 * BORDER_PX_SIDE);
subtree->left = wlr_scene_buffer_create(parent, &left_buffer->base);
wlr_buffer_drop(&left_buffer->base);
struct lab_data_buffer *top_buffer =
buffer_create_from_data(top_data, 1, BORDER_PX_TOP, 4);
subtree->top = wlr_scene_buffer_create(parent, &top_buffer->base);
wlr_buffer_drop(&top_buffer->base);
}
/* Lower textured borders below titlebar for overlap */
wlr_scene_node_lower_to_bottom(&ssd->border.tree->node);
if (view->maximized == VIEW_AXIS_BOTH) { if (view->maximized == VIEW_AXIS_BOTH) {
wlr_scene_node_set_enabled(&ssd->border.tree->node, false); wlr_scene_node_set_enabled(&ssd->border.tree->node, false);
@ -89,10 +170,10 @@ ssd_border_update(struct ssd *ssd)
ssd->margin = ssd_thickness(ssd->view); ssd->margin = ssd_thickness(ssd->view);
} }
struct theme *theme = view->server->theme;
int width = view->current.width; int width = view->current.width;
int height = view_effective_height(view, /* use_pending */ false); int height = view_effective_height(view, /* use_pending */ false);
#if 0
/* XXX: ignoring lots of possible cases */
int full_width = width + 2 * theme->border_width; int full_width = width + 2 * theme->border_width;
int corner_width = ssd_get_corner_width(); int corner_width = ssd_get_corner_width();
@ -127,11 +208,19 @@ ssd_border_update(struct ssd *ssd)
int top_x = ssd->titlebar.height <= 0 || ssd->state.was_squared int top_x = ssd->titlebar.height <= 0 || ssd->state.was_squared
? 0 ? 0
: theme->border_width + corner_width; : theme->border_width + corner_width;
#endif
int title_h = ssd->titlebar.height;
int side_y = -title_h - (BORDER_PX_TOP - 1);
int side_height = title_h + height + (BORDER_PX_TOP - 1) + (BORDER_PX_SIDE - 1);
int top_x = -(BORDER_PX_SIDE - 1);
int top_width = width + 2 * (BORDER_PX_SIDE - 1);
enum ssd_active_state active; enum ssd_active_state active;
FOR_EACH_ACTIVE_STATE(active) { FOR_EACH_ACTIVE_STATE(active) {
struct ssd_border_subtree *subtree = &ssd->border.subtrees[active]; struct ssd_border_subtree *subtree = &ssd->border.subtrees[active];
#if 0
/* XXX: old solid color borders */
wlr_scene_rect_set_size(subtree->left, wlr_scene_rect_set_size(subtree->left,
theme->border_width, side_height); theme->border_width, side_height);
wlr_scene_node_set_position(&subtree->left->node, wlr_scene_node_set_position(&subtree->left->node,
@ -151,6 +240,27 @@ ssd_border_update(struct ssd *ssd)
top_width, theme->border_width); top_width, theme->border_width);
wlr_scene_node_set_position(&subtree->top->node, wlr_scene_node_set_position(&subtree->top->node,
top_x, -(ssd->titlebar.height + theme->border_width)); top_x, -(ssd->titlebar.height + theme->border_width));
#endif
wlr_scene_node_set_position(&subtree->left->node,
-BORDER_PX_SIDE, side_y);
wlr_scene_buffer_set_dest_size(subtree->left,
BORDER_PX_SIDE, side_height);
wlr_scene_node_set_position(&subtree->right->node,
width, side_y);
wlr_scene_buffer_set_dest_size(subtree->right,
BORDER_PX_SIDE, side_height);
wlr_scene_node_set_position(&subtree->bottom->node,
top_x, height);
wlr_scene_buffer_set_dest_size(subtree->bottom,
top_width, BORDER_PX_SIDE);
wlr_scene_node_set_position(&subtree->top->node,
top_x, -title_h - BORDER_PX_TOP);
wlr_scene_buffer_set_dest_size(subtree->top,
top_width, BORDER_PX_TOP);
} }
} }

View file

@ -29,6 +29,7 @@ ssd_titlebar_create(struct ssd *ssd)
struct view *view = ssd->view; struct view *view = ssd->view;
struct theme *theme = view->server->theme; struct theme *theme = view->server->theme;
int width = view->current.width; int width = view->current.width;
bool maximized = view->maximized == VIEW_AXIS_BOTH;
int corner_width = ssd_get_corner_width(); int corner_width = ssd_get_corner_width();
ssd->titlebar.tree = wlr_scene_tree_create(ssd->tree); ssd->titlebar.tree = wlr_scene_tree_create(ssd->tree);
@ -62,7 +63,10 @@ ssd_titlebar_create(struct ssd *ssd)
wlr_scene_buffer_set_filter_mode( wlr_scene_buffer_set_filter_mode(
subtree->bar, WLR_SCALE_FILTER_NEAREST); subtree->bar, WLR_SCALE_FILTER_NEAREST);
} }
wlr_scene_node_set_position(&subtree->bar->node, corner_width, 0); int overlap = maximized ? 0 : BORDER_PX_SIDE - 2;
wlr_scene_node_set_position(&subtree->bar->node, -overlap, 0);
wlr_scene_buffer_set_dest_size(subtree->bar,
width + 2 * overlap, theme->titlebar_height);
subtree->corner_left = wlr_scene_buffer_create(parent, corner_top_left); subtree->corner_left = wlr_scene_buffer_create(parent, corner_top_left);
wlr_scene_node_set_position(&subtree->corner_left->node, wlr_scene_node_set_position(&subtree->corner_left->node,
@ -113,7 +117,6 @@ ssd_titlebar_create(struct ssd *ssd)
ssd_update_title(ssd); ssd_update_title(ssd);
bool maximized = view->maximized == VIEW_AXIS_BOTH;
bool squared = ssd_should_be_squared(ssd); bool squared = ssd_should_be_squared(ssd);
if (maximized) { if (maximized) {
set_alt_button_icon(ssd, LAB_NODE_BUTTON_MAXIMIZE, true); set_alt_button_icon(ssd, LAB_NODE_BUTTON_MAXIMIZE, true);
@ -157,20 +160,25 @@ update_button_state(struct ssd_button *button, enum lab_button_state state,
static void static void
set_squared_corners(struct ssd *ssd, bool enable) set_squared_corners(struct ssd *ssd, bool enable)
{ {
#if 0
struct view *view = ssd->view; struct view *view = ssd->view;
int width = view->current.width; int width = view->current.width;
int corner_width = ssd_get_corner_width(); int corner_width = ssd_get_corner_width();
struct theme *theme = view->server->theme; struct theme *theme = view->server->theme;
int x = enable ? 0 : corner_width; int x = enable ? 0 : corner_width;
#endif
enum ssd_active_state active; enum ssd_active_state active;
FOR_EACH_ACTIVE_STATE(active) { FOR_EACH_ACTIVE_STATE(active) {
struct ssd_titlebar_subtree *subtree = &ssd->titlebar.subtrees[active]; struct ssd_titlebar_subtree *subtree = &ssd->titlebar.subtrees[active];
#if 0
/* XXX: conflicts with overlap of textured borders */
wlr_scene_node_set_position(&subtree->bar->node, x, 0); wlr_scene_node_set_position(&subtree->bar->node, x, 0);
wlr_scene_buffer_set_dest_size(subtree->bar, wlr_scene_buffer_set_dest_size(subtree->bar,
MAX(width - 2 * x, 0), theme->titlebar_height); MAX(width - 2 * x, 0), theme->titlebar_height);
#endif
wlr_scene_node_set_enabled(&subtree->corner_left->node, !enable); wlr_scene_node_set_enabled(&subtree->corner_left->node, !enable);
@ -284,7 +292,6 @@ ssd_titlebar_update(struct ssd *ssd)
if (ssd->state.was_maximized != maximized) { if (ssd->state.was_maximized != maximized) {
set_alt_button_icon(ssd, LAB_NODE_BUTTON_MAXIMIZE, maximized); set_alt_button_icon(ssd, LAB_NODE_BUTTON_MAXIMIZE, maximized);
} }
ssd->state.was_maximized = maximized;
ssd->state.was_squared = squared; ssd->state.was_squared = squared;
} }
@ -299,22 +306,25 @@ ssd_titlebar_update(struct ssd *ssd)
ssd->state.was_omnipresent = view->visible_on_all_workspaces; ssd->state.was_omnipresent = view->visible_on_all_workspaces;
} }
if (width == ssd->state.geometry.width) { if (ssd->state.was_maximized == maximized
&& ssd->state.geometry.width == width) {
return; return;
} }
ssd->state.was_maximized = maximized;
update_visible_buttons(ssd); update_visible_buttons(ssd);
/* Center buttons vertically within titlebar */ /* Center buttons vertically within titlebar */
int y = (theme->titlebar_height - theme->window_button_height) / 2; int y = (theme->titlebar_height - theme->window_button_height) / 2;
int x; int x;
int bg_offset = maximized || squared ? 0 : corner_width;
enum ssd_active_state active; enum ssd_active_state active;
FOR_EACH_ACTIVE_STATE(active) { FOR_EACH_ACTIVE_STATE(active) {
struct ssd_titlebar_subtree *subtree = &ssd->titlebar.subtrees[active]; struct ssd_titlebar_subtree *subtree = &ssd->titlebar.subtrees[active];
int overlap = maximized ? 0 : BORDER_PX_SIDE - 2;
wlr_scene_node_set_position(&subtree->bar->node, -overlap, 0);
wlr_scene_buffer_set_dest_size(subtree->bar, wlr_scene_buffer_set_dest_size(subtree->bar,
MAX(width - bg_offset * 2, 0), theme->titlebar_height); width + 2 * overlap, theme->titlebar_height);
x = theme->window_titlebar_padding_width; x = theme->window_titlebar_padding_width;
struct ssd_button *button; struct ssd_button *button;
@ -513,10 +523,15 @@ ssd_update_hovered_button(struct server *server, struct wlr_scene_node *node)
bool bool
ssd_should_be_squared(struct ssd *ssd) ssd_should_be_squared(struct ssd *ssd)
{ {
#if 1
/* XXX: force squared corners with textured borders */
return true;
#else
struct view *view = ssd->view; struct view *view = ssd->view;
int corner_width = ssd_get_corner_width(); int corner_width = ssd_get_corner_width();
return (view_is_tiled_and_notify_tiled(view) return (view_is_tiled_and_notify_tiled(view)
|| view->current.width < corner_width * 2) || view->current.width < corner_width * 2)
&& view->maximized != VIEW_AXIS_BOTH; && view->maximized != VIEW_AXIS_BOTH;
#endif
} }

View file

@ -49,10 +49,10 @@ ssd_thickness(struct view *view)
} }
struct border thickness = { struct border thickness = {
.top = theme->titlebar_height + theme->border_width, .top = theme->titlebar_height + BORDER_PX_TOP,
.right = theme->border_width, .right = BORDER_PX_SIDE,
.bottom = theme->border_width, .bottom = BORDER_PX_SIDE,
.left = theme->border_width, .left = BORDER_PX_SIDE,
}; };
if (!view_titlebar_visible(view)) { if (!view_titlebar_visible(view)) {
@ -346,10 +346,13 @@ ssd_enable_keybind_inhibit_indicator(struct ssd *ssd, bool enable)
return; return;
} }
#if 0
/* XXX: doesn't work with textured borders */
float *color = enable float *color = enable
? rc.theme->window_toggled_keybinds_color ? rc.theme->window_toggled_keybinds_color
: rc.theme->window[SSD_ACTIVE].border_color; : rc.theme->window[SSD_ACTIVE].border_color;
wlr_scene_rect_set_color(ssd->border.subtrees[SSD_ACTIVE].top, color); wlr_scene_rect_set_color(ssd->border.subtrees[SSD_ACTIVE].top, color);
#endif
} }
bool bool