mirror of
https://github.com/labwc/labwc.git
synced 2026-06-13 14:33:18 -04:00
feat: implement Openbox-style bottom window handle and grips
Add full handle/grip assembly to the bottom of SSD window frames, following the Openbox themerc specification for geometry and theming. Theme parsing: - Parse window.handle.width (handle bar height, default 6) - Parse window.grip.width (corner grip width, default 20) - Parse window.[active|inactive].handle.bg with Solid/Gradient support - Parse window.[active|inactive].grip.bg (inherits from handle if unset) - Pre-render 1px-wide fill buffers and cairo patterns for handle/grip Scene graph (new ssd-handle.c): - Handle assembly replaces bottom border when active, with its own left/right/top borders and three-segment bottom border - Grips at left/right corners for diagonal resize (sw/se-resize) - Center handle for vertical resize (s-resize) - Vertical separator lines between grips and handle using border color - Per Openbox spec, handle_width is content-only height with borders drawn around it (total assembly height = 2*border_width + handle_width) Interactive visual states (grips only): - Hover: 20% black overlay on grip content area - Pressed: 40% black overlay with 1px inset shadow (dark top/left, light bottom/right) for a pushed-in 3D effect - Dragging: 20% overlay with inset shadow maintained - Global hover tracking (server.hovered_handle_ssd/element) ensures proper cleanup when cursor moves across views or to desktop Decoration toggle cycle (ToggleDecorations action): - New LAB_SSD_MODE_BORDER_HANDLE between BORDER and FULL - keepBorder=true: full -> border+handle -> border -> none -> full - keepBorder=false: full -> none -> full (unchanged) Node types and input: - New LAB_NODE_HANDLE, LAB_NODE_GRIP_LEFT, LAB_NODE_GRIP_RIGHT - Integrated into LAB_NODE_BORDER/BORDER_BOTTOM containment so existing Border context mousebinds (Resize) work automatically - Handle/grip descriptors resolved directly in get_cursor_context() bypassing ssd_get_resizing_type() for precise cursor shapes Visibility rules: - Hidden when maximized, shaded, or handle_width is 0 - Hidden in LAB_SSD_MODE_BORDER and LAB_SSD_MODE_NONE states - Bottom border in ssd-border.c disabled when handle is active Documentation: - labwc-theme.5.scd: document all handle/grip theme properties - labwc-actions.5.scd: update ToggleDecorations to 4-state cycle - docs/themerc: add handle/grip default values
This commit is contained in:
parent
4af693a7fd
commit
ba5a0b9829
19 changed files with 1132 additions and 17 deletions
|
|
@ -5,5 +5,6 @@ labwc_sources += files(
|
|||
'ssd-titlebar.c',
|
||||
'ssd-border.c',
|
||||
'ssd-extents.c',
|
||||
'ssd-handle.c',
|
||||
'ssd-shadow.c',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -59,6 +59,18 @@ ssd_border_create(struct ssd *ssd)
|
|||
wlr_scene_node_set_enabled(&ssd->border.tree->node, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* When the handle assembly is active, hide the bottom border
|
||||
* because the handle provides its own bottom border visuals.
|
||||
*/
|
||||
if (ssd->handle.tree && ssd->handle.tree->node.enabled) {
|
||||
FOR_EACH_ACTIVE_STATE(active) {
|
||||
wlr_scene_node_set_enabled(
|
||||
&ssd->border.subtrees[active].bottom->node,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
if (view->current.width > 0 && view->current.height > 0) {
|
||||
/*
|
||||
* The SSD is recreated by a Reconfigure request
|
||||
|
|
@ -152,6 +164,15 @@ ssd_border_update(struct ssd *ssd)
|
|||
top_width, theme->border_width);
|
||||
wlr_scene_node_set_position(&subtree->top->node,
|
||||
top_x, -(ssd->titlebar.height + theme->border_width));
|
||||
|
||||
/*
|
||||
* Hide the bottom border when the handle assembly
|
||||
* is active (handle draws its own borders).
|
||||
*/
|
||||
bool handle_active = ssd->handle.tree
|
||||
&& ssd->handle.tree->node.enabled;
|
||||
wlr_scene_node_set_enabled(
|
||||
&subtree->bottom->node, !handle_active);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
607
src/ssd/ssd-handle.c
Normal file
607
src/ssd/ssd-handle.c
Normal file
|
|
@ -0,0 +1,607 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* SSD handle and grip assembly for the bottom of the window frame.
|
||||
*
|
||||
* Implements Openbox-compatible handle/grip decorations with interactive
|
||||
* visual states (hover, pressed, dragging).
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <wlr/render/pixman.h>
|
||||
#include <wlr/types/wlr_scene.h>
|
||||
#include "buffer.h"
|
||||
#include "common/macros.h"
|
||||
#include "common/scene-helpers.h"
|
||||
#include "config/rcxml.h"
|
||||
#include "labwc.h"
|
||||
#include "node.h"
|
||||
#include "ssd.h"
|
||||
#include "ssd-internal.h"
|
||||
#include "theme.h"
|
||||
#include "view.h"
|
||||
|
||||
/* Inset shadow line width for pressed/dragging state */
|
||||
#define INSET_LINE_WIDTH 1
|
||||
|
||||
/* Overlay colors (premultiplied RGBA) */
|
||||
static const float overlay_hover[4] = { 0.0f, 0.0f, 0.0f, 0.20f };
|
||||
static const float overlay_pressed[4] = { 0.0f, 0.0f, 0.0f, 0.40f };
|
||||
static const float overlay_none[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
|
||||
/* Inset shadow colors (premultiplied) */
|
||||
static const float inset_dark[4] = { 0.0f, 0.0f, 0.0f, 0.30f };
|
||||
static const float inset_light[4] = { 0.10f, 0.10f, 0.10f, 0.10f };
|
||||
|
||||
/* Map element index -> node type for hit detection */
|
||||
static const enum lab_node_type elem_node_types[SSD_HANDLE_ELEMENT_COUNT] = {
|
||||
[SSD_HANDLE_ELEMENT_GRIP_LEFT] = LAB_NODE_GRIP_LEFT,
|
||||
[SSD_HANDLE_ELEMENT_CENTER] = LAB_NODE_HANDLE,
|
||||
[SSD_HANDLE_ELEMENT_GRIP_RIGHT] = LAB_NODE_GRIP_RIGHT,
|
||||
};
|
||||
|
||||
/* Spec for a border/separator rect: geometry + node descriptor type */
|
||||
struct rect_spec {
|
||||
int x, y, w, h;
|
||||
enum lab_node_type desc_type;
|
||||
};
|
||||
|
||||
/* Per-element geometry (shared by textures, overlays, insets) */
|
||||
struct elem_geometry {
|
||||
int x, y, w, h;
|
||||
};
|
||||
|
||||
/*
|
||||
* Compute the layout metrics for the handle/grip assembly.
|
||||
* All coordinates are relative to the handle tree origin, which is
|
||||
* positioned at (-border_width, effective_height) relative to the
|
||||
* SSD root.
|
||||
*/
|
||||
struct handle_layout {
|
||||
int bw; /* border_width */
|
||||
int hw; /* handle_width (height of handle content) */
|
||||
int full_width; /* total width including side borders */
|
||||
int grip_w; /* width of each grip */
|
||||
int handle_w; /* width of center handle (between grips) */
|
||||
int content_h; /* height of textured content (= hw, per Openbox spec) */
|
||||
int total_h; /* total assembly height: 2*bw + hw */
|
||||
/* X positions (relative to handle tree) */
|
||||
int grip_left_x;
|
||||
int handle_x;
|
||||
int grip_right_x;
|
||||
/* Y position of content (below top border line) */
|
||||
int content_y;
|
||||
};
|
||||
|
||||
static struct handle_layout
|
||||
compute_layout(struct theme *theme, int view_width)
|
||||
{
|
||||
struct handle_layout l;
|
||||
l.bw = theme->border_width;
|
||||
l.hw = theme->handle_width;
|
||||
l.full_width = view_width + 2 * l.bw;
|
||||
l.grip_w = theme->grip_width;
|
||||
|
||||
/* Ensure grips don't overflow the available width */
|
||||
if (2 * l.grip_w + 4 * l.bw > l.full_width) {
|
||||
l.grip_w = MAX((l.full_width - 4 * l.bw) / 2, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Center handle width: total minus outer borders (2*bw),
|
||||
* grips (2*grip_w), and inner separators (2*bw).
|
||||
*/
|
||||
l.handle_w = MAX(l.full_width - 4 * l.bw - 2 * l.grip_w, 0);
|
||||
|
||||
/* X coordinates accounting for separators between grips and handle */
|
||||
l.grip_left_x = l.bw;
|
||||
l.handle_x = l.bw + l.grip_w + l.bw;
|
||||
l.grip_right_x = l.bw + l.grip_w + l.bw + l.handle_w + l.bw;
|
||||
|
||||
/* Content Y starts after top border line */
|
||||
l.content_y = l.bw;
|
||||
|
||||
/*
|
||||
* Per Openbox spec, handle_width is the content-only height.
|
||||
* Borders are drawn around it, not subtracted from it.
|
||||
*/
|
||||
l.content_h = l.hw;
|
||||
l.total_h = 2 * l.bw + l.hw;
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
static void
|
||||
compute_rect_specs(const struct handle_layout *l,
|
||||
struct rect_spec specs[HRECT_COUNT])
|
||||
{
|
||||
specs[HRECT_BORDER_LEFT] = (struct rect_spec){
|
||||
0, 0, l->bw, l->total_h, LAB_NODE_GRIP_LEFT };
|
||||
specs[HRECT_BORDER_RIGHT] = (struct rect_spec){
|
||||
l->full_width - l->bw, 0, l->bw, l->total_h,
|
||||
LAB_NODE_GRIP_RIGHT };
|
||||
specs[HRECT_BORDER_BOTTOM_LEFT] = (struct rect_spec){
|
||||
0, l->bw + l->hw, l->bw + l->grip_w + l->bw, l->bw,
|
||||
LAB_NODE_GRIP_LEFT };
|
||||
specs[HRECT_BORDER_BOTTOM_CENTER] = (struct rect_spec){
|
||||
l->handle_x, l->bw + l->hw, l->handle_w, l->bw,
|
||||
LAB_NODE_HANDLE };
|
||||
specs[HRECT_BORDER_BOTTOM_RIGHT] = (struct rect_spec){
|
||||
l->grip_right_x - l->bw, l->bw + l->hw,
|
||||
l->bw + l->grip_w + l->bw, l->bw, LAB_NODE_GRIP_RIGHT };
|
||||
specs[HRECT_BORDER_TOP_LEFT] = (struct rect_spec){
|
||||
l->bw, 0, l->grip_w + l->bw, l->bw,
|
||||
LAB_NODE_GRIP_LEFT };
|
||||
specs[HRECT_BORDER_TOP_CENTER] = (struct rect_spec){
|
||||
l->handle_x, 0, l->handle_w, l->bw,
|
||||
LAB_NODE_HANDLE };
|
||||
specs[HRECT_BORDER_TOP_RIGHT] = (struct rect_spec){
|
||||
l->grip_right_x - l->bw, 0, l->grip_w + l->bw, l->bw,
|
||||
LAB_NODE_GRIP_RIGHT };
|
||||
specs[HRECT_SEPARATOR_LEFT] = (struct rect_spec){
|
||||
l->bw + l->grip_w, l->content_y, l->bw, l->content_h,
|
||||
LAB_NODE_GRIP_LEFT };
|
||||
specs[HRECT_SEPARATOR_RIGHT] = (struct rect_spec){
|
||||
l->grip_right_x - l->bw, l->content_y, l->bw, l->content_h,
|
||||
LAB_NODE_GRIP_RIGHT };
|
||||
}
|
||||
|
||||
static void
|
||||
compute_elem_geometry(const struct handle_layout *l,
|
||||
struct elem_geometry elems[SSD_HANDLE_ELEMENT_COUNT])
|
||||
{
|
||||
elems[SSD_HANDLE_ELEMENT_GRIP_LEFT] = (struct elem_geometry){
|
||||
l->grip_left_x, l->content_y, l->grip_w, l->content_h };
|
||||
elems[SSD_HANDLE_ELEMENT_CENTER] = (struct elem_geometry){
|
||||
l->handle_x, l->content_y, l->handle_w, l->content_h };
|
||||
elems[SSD_HANDLE_ELEMENT_GRIP_RIGHT] = (struct elem_geometry){
|
||||
l->grip_right_x, l->content_y, l->grip_w, l->content_h };
|
||||
}
|
||||
|
||||
static struct wlr_scene_buffer *
|
||||
create_handle_buffer(struct wlr_scene_tree *parent,
|
||||
struct wlr_buffer *buf, int x, int y, int w, int h)
|
||||
{
|
||||
struct wlr_scene_buffer *sbuf =
|
||||
lab_wlr_scene_buffer_create(parent, buf);
|
||||
wlr_scene_node_set_position(&sbuf->node, x, y);
|
||||
wlr_scene_buffer_set_dest_size(sbuf, w, h);
|
||||
if (wlr_renderer_is_pixman(server.renderer)) {
|
||||
wlr_scene_buffer_set_filter_mode(
|
||||
sbuf, WLR_SCALE_FILTER_NEAREST);
|
||||
}
|
||||
return sbuf;
|
||||
}
|
||||
|
||||
static void
|
||||
create_inset_rects(struct wlr_scene_tree *parent,
|
||||
struct ssd_handle_subtree *subtree, int idx,
|
||||
int x, int y, int w, int h)
|
||||
{
|
||||
/* Top inset (dark) */
|
||||
subtree->inset_top[idx] = lab_wlr_scene_rect_create(
|
||||
parent, w, INSET_LINE_WIDTH, inset_dark);
|
||||
wlr_scene_node_set_position(
|
||||
&subtree->inset_top[idx]->node, x, y);
|
||||
wlr_scene_node_set_enabled(
|
||||
&subtree->inset_top[idx]->node, false);
|
||||
|
||||
/* Left inset (dark) */
|
||||
subtree->inset_left[idx] = lab_wlr_scene_rect_create(
|
||||
parent, INSET_LINE_WIDTH, h, inset_dark);
|
||||
wlr_scene_node_set_position(
|
||||
&subtree->inset_left[idx]->node, x, y);
|
||||
wlr_scene_node_set_enabled(
|
||||
&subtree->inset_left[idx]->node, false);
|
||||
|
||||
/* Bottom inset (light highlight) */
|
||||
subtree->inset_bottom[idx] = lab_wlr_scene_rect_create(
|
||||
parent, w, INSET_LINE_WIDTH, inset_light);
|
||||
wlr_scene_node_set_position(
|
||||
&subtree->inset_bottom[idx]->node, x, y + h - INSET_LINE_WIDTH);
|
||||
wlr_scene_node_set_enabled(
|
||||
&subtree->inset_bottom[idx]->node, false);
|
||||
|
||||
/* Right inset (light highlight) */
|
||||
subtree->inset_right[idx] = lab_wlr_scene_rect_create(
|
||||
parent, INSET_LINE_WIDTH, h, inset_light);
|
||||
wlr_scene_node_set_position(
|
||||
&subtree->inset_right[idx]->node,
|
||||
x + w - INSET_LINE_WIDTH, y);
|
||||
wlr_scene_node_set_enabled(
|
||||
&subtree->inset_right[idx]->node, false);
|
||||
}
|
||||
|
||||
void
|
||||
ssd_handle_create(struct ssd *ssd)
|
||||
{
|
||||
assert(ssd);
|
||||
struct view *view = ssd->view;
|
||||
struct theme *theme = rc.theme;
|
||||
|
||||
if (theme->handle_width <= 0) {
|
||||
ssd->handle.tree = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
int width = view->current.width;
|
||||
struct handle_layout l = compute_layout(theme, width);
|
||||
|
||||
ssd->handle.tree = lab_wlr_scene_tree_create(ssd->tree);
|
||||
wlr_scene_node_set_position(&ssd->handle.tree->node,
|
||||
-theme->border_width,
|
||||
view_effective_height(view, /* use_pending */ false));
|
||||
|
||||
/* Initialize element states */
|
||||
for (int i = 0; i < SSD_HANDLE_ELEMENT_COUNT; i++) {
|
||||
ssd->handle.element_states[i] = SSD_HANDLE_STATE_NORMAL;
|
||||
}
|
||||
|
||||
enum ssd_active_state active;
|
||||
FOR_EACH_ACTIVE_STATE(active) {
|
||||
struct ssd_handle_subtree *subtree =
|
||||
&ssd->handle.subtrees[active];
|
||||
subtree->tree = lab_wlr_scene_tree_create(ssd->handle.tree);
|
||||
struct wlr_scene_tree *parent = subtree->tree;
|
||||
wlr_scene_node_set_enabled(&parent->node, active);
|
||||
float *border_color = theme->window[active].border_color;
|
||||
|
||||
/* Border and separator rects */
|
||||
struct rect_spec rspecs[HRECT_COUNT];
|
||||
compute_rect_specs(&l, rspecs);
|
||||
for (int i = 0; i < HRECT_COUNT; i++) {
|
||||
subtree->rects[i] = lab_wlr_scene_rect_create(
|
||||
parent, rspecs[i].w, rspecs[i].h,
|
||||
border_color);
|
||||
wlr_scene_node_set_position(
|
||||
&subtree->rects[i]->node,
|
||||
rspecs[i].x, rspecs[i].y);
|
||||
node_descriptor_create(&subtree->rects[i]->node,
|
||||
rspecs[i].desc_type, view, NULL);
|
||||
}
|
||||
|
||||
/* Per-element geometry (shared by textures, overlays, insets) */
|
||||
struct elem_geometry elems[SSD_HANDLE_ELEMENT_COUNT];
|
||||
compute_elem_geometry(&l, elems);
|
||||
struct wlr_buffer *bufs[SSD_HANDLE_ELEMENT_COUNT] = {
|
||||
&theme->window[active].grip_fill->base,
|
||||
&theme->window[active].handle_fill->base,
|
||||
&theme->window[active].grip_fill->base,
|
||||
};
|
||||
|
||||
for (int i = 0; i < SSD_HANDLE_ELEMENT_COUNT; i++) {
|
||||
/* Texture buffer (1px wide, stretched to fill) */
|
||||
subtree->textures[i] = create_handle_buffer(
|
||||
parent, bufs[i], elems[i].x, elems[i].y,
|
||||
elems[i].w, elems[i].h);
|
||||
node_descriptor_create(
|
||||
&subtree->textures[i]->node,
|
||||
elem_node_types[i], view, NULL);
|
||||
|
||||
/* Overlay rect for hover/pressed dimming */
|
||||
subtree->overlay[i] = lab_wlr_scene_rect_create(
|
||||
parent, elems[i].w, elems[i].h,
|
||||
overlay_none);
|
||||
wlr_scene_node_set_position(
|
||||
&subtree->overlay[i]->node,
|
||||
elems[i].x, elems[i].y);
|
||||
wlr_scene_node_set_enabled(
|
||||
&subtree->overlay[i]->node, false);
|
||||
node_descriptor_create(
|
||||
&subtree->overlay[i]->node,
|
||||
elem_node_types[i], view, NULL);
|
||||
|
||||
/* Inset shadow rects for pressed/dragging state */
|
||||
create_inset_rects(parent, subtree, i,
|
||||
elems[i].x, elems[i].y,
|
||||
elems[i].w, elems[i].h);
|
||||
}
|
||||
}
|
||||
|
||||
if (view->maximized == VIEW_AXIS_BOTH) {
|
||||
wlr_scene_node_set_enabled(
|
||||
&ssd->handle.tree->node, false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ssd_handle_update(struct ssd *ssd)
|
||||
{
|
||||
assert(ssd);
|
||||
|
||||
if (!ssd->handle.tree) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct view *view = ssd->view;
|
||||
struct theme *theme = rc.theme;
|
||||
|
||||
bool should_show = view_handle_visible(view)
|
||||
&& view->maximized != VIEW_AXIS_BOTH;
|
||||
|
||||
if (!should_show) {
|
||||
if (ssd->handle.tree->node.enabled) {
|
||||
wlr_scene_node_set_enabled(
|
||||
&ssd->handle.tree->node, false);
|
||||
}
|
||||
return;
|
||||
} else if (!ssd->handle.tree->node.enabled) {
|
||||
wlr_scene_node_set_enabled(
|
||||
&ssd->handle.tree->node, true);
|
||||
}
|
||||
|
||||
int width = view->current.width;
|
||||
int height = view_effective_height(view, /* use_pending */ false);
|
||||
struct handle_layout l = compute_layout(theme, width);
|
||||
|
||||
/* Update position: handle sits below the client area */
|
||||
wlr_scene_node_set_position(&ssd->handle.tree->node,
|
||||
-theme->border_width, height);
|
||||
|
||||
enum ssd_active_state active;
|
||||
FOR_EACH_ACTIVE_STATE(active) {
|
||||
struct ssd_handle_subtree *subtree =
|
||||
&ssd->handle.subtrees[active];
|
||||
|
||||
float *border_color = theme->window[active].border_color;
|
||||
|
||||
/* Update border and separator rects */
|
||||
struct rect_spec rspecs[HRECT_COUNT];
|
||||
compute_rect_specs(&l, rspecs);
|
||||
for (int i = 0; i < HRECT_COUNT; i++) {
|
||||
wlr_scene_rect_set_size(subtree->rects[i],
|
||||
rspecs[i].w, rspecs[i].h);
|
||||
wlr_scene_node_set_position(
|
||||
&subtree->rects[i]->node,
|
||||
rspecs[i].x, rspecs[i].y);
|
||||
wlr_scene_rect_set_color(subtree->rects[i],
|
||||
border_color);
|
||||
}
|
||||
|
||||
/* Update textures, overlays, and insets per element */
|
||||
struct elem_geometry elems[SSD_HANDLE_ELEMENT_COUNT];
|
||||
compute_elem_geometry(&l, elems);
|
||||
|
||||
for (int i = 0; i < SSD_HANDLE_ELEMENT_COUNT; i++) {
|
||||
int ex = elems[i].x;
|
||||
int ey = elems[i].y;
|
||||
int ew = elems[i].w;
|
||||
int eh = elems[i].h;
|
||||
|
||||
wlr_scene_node_set_position(
|
||||
&subtree->textures[i]->node, ex, ey);
|
||||
wlr_scene_buffer_set_dest_size(
|
||||
subtree->textures[i], ew, eh);
|
||||
|
||||
wlr_scene_rect_set_size(
|
||||
subtree->overlay[i], ew, eh);
|
||||
wlr_scene_node_set_position(
|
||||
&subtree->overlay[i]->node, ex, ey);
|
||||
|
||||
wlr_scene_rect_set_size(
|
||||
subtree->inset_top[i],
|
||||
ew, INSET_LINE_WIDTH);
|
||||
wlr_scene_node_set_position(
|
||||
&subtree->inset_top[i]->node, ex, ey);
|
||||
|
||||
wlr_scene_rect_set_size(
|
||||
subtree->inset_left[i],
|
||||
INSET_LINE_WIDTH, eh);
|
||||
wlr_scene_node_set_position(
|
||||
&subtree->inset_left[i]->node, ex, ey);
|
||||
|
||||
wlr_scene_rect_set_size(
|
||||
subtree->inset_bottom[i],
|
||||
ew, INSET_LINE_WIDTH);
|
||||
wlr_scene_node_set_position(
|
||||
&subtree->inset_bottom[i]->node,
|
||||
ex, ey + eh - INSET_LINE_WIDTH);
|
||||
|
||||
wlr_scene_rect_set_size(
|
||||
subtree->inset_right[i],
|
||||
INSET_LINE_WIDTH, eh);
|
||||
wlr_scene_node_set_position(
|
||||
&subtree->inset_right[i]->node,
|
||||
ex + ew - INSET_LINE_WIDTH, ey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ssd_handle_destroy(struct ssd *ssd)
|
||||
{
|
||||
assert(ssd);
|
||||
|
||||
if (!ssd->handle.tree) {
|
||||
return;
|
||||
}
|
||||
|
||||
wlr_scene_node_destroy(&ssd->handle.tree->node);
|
||||
ssd->handle = (struct ssd_handle_scene){0};
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the visual state of a single handle/grip element.
|
||||
* This toggles the overlay and inset shadow rects on both active
|
||||
* and inactive subtrees (only the enabled one is visible).
|
||||
*/
|
||||
void
|
||||
ssd_handle_set_element_state(struct ssd *ssd, int element,
|
||||
enum ssd_handle_state state)
|
||||
{
|
||||
if (!ssd || !ssd->handle.tree || element < 0
|
||||
|| element >= SSD_HANDLE_ELEMENT_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ssd->handle.element_states[element] == state) {
|
||||
return;
|
||||
}
|
||||
ssd->handle.element_states[element] = state;
|
||||
|
||||
enum ssd_active_state active;
|
||||
FOR_EACH_ACTIVE_STATE(active) {
|
||||
struct ssd_handle_subtree *subtree =
|
||||
&ssd->handle.subtrees[active];
|
||||
struct wlr_scene_rect *overlay = subtree->overlay[element];
|
||||
|
||||
bool show_overlay = false;
|
||||
bool show_insets = false;
|
||||
|
||||
switch (state) {
|
||||
case SSD_HANDLE_STATE_NORMAL:
|
||||
break;
|
||||
case SSD_HANDLE_STATE_HOVER:
|
||||
wlr_scene_rect_set_color(overlay, overlay_hover);
|
||||
show_overlay = true;
|
||||
break;
|
||||
case SSD_HANDLE_STATE_PRESSED:
|
||||
wlr_scene_rect_set_color(overlay, overlay_pressed);
|
||||
show_overlay = true;
|
||||
show_insets = true;
|
||||
break;
|
||||
case SSD_HANDLE_STATE_DRAGGING:
|
||||
wlr_scene_rect_set_color(overlay, overlay_hover);
|
||||
show_overlay = true;
|
||||
show_insets = true;
|
||||
break;
|
||||
}
|
||||
|
||||
wlr_scene_node_set_enabled(&overlay->node, show_overlay);
|
||||
wlr_scene_node_set_enabled(
|
||||
&subtree->inset_top[element]->node, show_insets);
|
||||
wlr_scene_node_set_enabled(
|
||||
&subtree->inset_left[element]->node, show_insets);
|
||||
wlr_scene_node_set_enabled(
|
||||
&subtree->inset_bottom[element]->node, show_insets);
|
||||
wlr_scene_node_set_enabled(
|
||||
&subtree->inset_right[element]->node, show_insets);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ssd_handle_clear_all_states(struct ssd *ssd)
|
||||
{
|
||||
if (!ssd || !ssd->handle.tree) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < SSD_HANDLE_ELEMENT_COUNT; i++) {
|
||||
ssd_handle_set_element_state(ssd, i,
|
||||
SSD_HANDLE_STATE_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine which handle element (if any) a scene node belongs to,
|
||||
* and resolve the owning view/SSD.
|
||||
*
|
||||
* Returns the element index (SSD_HANDLE_ELEMENT_*) or -1 if the
|
||||
* node is not part of any handle. If found, *out_ssd is set to
|
||||
* the owning SSD.
|
||||
*/
|
||||
static int
|
||||
handle_element_from_node(struct wlr_scene_node *node, struct ssd **out_ssd)
|
||||
{
|
||||
*out_ssd = NULL;
|
||||
if (!node) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Walk up the node tree to find a node_descriptor with
|
||||
* a handle/grip type.
|
||||
*/
|
||||
struct wlr_scene_node *n = node;
|
||||
int element = -1;
|
||||
while (n) {
|
||||
if (n->data) {
|
||||
struct node_descriptor *desc = n->data;
|
||||
switch (desc->type) {
|
||||
case LAB_NODE_GRIP_LEFT:
|
||||
element = SSD_HANDLE_ELEMENT_GRIP_LEFT;
|
||||
break;
|
||||
case LAB_NODE_HANDLE:
|
||||
/*
|
||||
* Center handle is identified for correct cursor
|
||||
* shape but visual hover/pressed effects are
|
||||
* intentionally limited to corner grips only.
|
||||
*/
|
||||
element = SSD_HANDLE_ELEMENT_CENTER;
|
||||
break;
|
||||
case LAB_NODE_GRIP_RIGHT:
|
||||
element = SSD_HANDLE_ELEMENT_GRIP_RIGHT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (element >= 0) {
|
||||
struct view *view = desc->view;
|
||||
if (view && view->ssd
|
||||
&& view->ssd->handle.tree) {
|
||||
*out_ssd = view->ssd;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
}
|
||||
n = n->parent ? &n->parent->node : NULL;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update hover state based on the node under the cursor.
|
||||
* Uses global server.hovered_handle_ssd / hovered_handle_element
|
||||
* to track state across views (mirrors ssd_update_hovered_button
|
||||
* pattern).
|
||||
*
|
||||
* Called from cursor_update_common() in cursor.c.
|
||||
*/
|
||||
void
|
||||
ssd_update_hovered_handle(struct wlr_scene_node *node)
|
||||
{
|
||||
struct ssd *new_ssd = NULL;
|
||||
int new_element = -1;
|
||||
int raw_element;
|
||||
|
||||
raw_element = handle_element_from_node(node, &new_ssd);
|
||||
|
||||
if (new_ssd && raw_element >= 0) {
|
||||
/*
|
||||
* Visual hover/pressed effects apply to corner grips
|
||||
* only. The center handle still changes the cursor
|
||||
* shape via node_type_to_edges() but receives no
|
||||
* dimming overlay.
|
||||
*/
|
||||
if (raw_element != SSD_HANDLE_ELEMENT_CENTER) {
|
||||
new_element = raw_element;
|
||||
}
|
||||
}
|
||||
|
||||
struct ssd *old_ssd = server.hovered_handle_ssd;
|
||||
int old_element = server.hovered_handle_element;
|
||||
|
||||
/* Same element on same SSD -- nothing to do */
|
||||
if (old_ssd == new_ssd && old_element == new_element) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Clear hover on old element */
|
||||
if (old_ssd && old_ssd->handle.tree && old_element >= 0) {
|
||||
if (old_ssd->handle.element_states[old_element]
|
||||
== SSD_HANDLE_STATE_HOVER) {
|
||||
ssd_handle_set_element_state(old_ssd,
|
||||
old_element, SSD_HANDLE_STATE_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set hover on new element */
|
||||
if (new_ssd && new_element >= 0) {
|
||||
if (new_ssd->handle.element_states[new_element]
|
||||
== SSD_HANDLE_STATE_NORMAL) {
|
||||
ssd_handle_set_element_state(new_ssd,
|
||||
new_element, SSD_HANDLE_STATE_HOVER);
|
||||
}
|
||||
}
|
||||
|
||||
server.hovered_handle_ssd = new_ssd;
|
||||
server.hovered_handle_element = new_element;
|
||||
}
|
||||
|
|
@ -59,6 +59,18 @@ ssd_thickness(struct view *view)
|
|||
if (!view_titlebar_visible(view)) {
|
||||
thickness.top -= theme->titlebar_height;
|
||||
}
|
||||
|
||||
/*
|
||||
* When the handle is visible, the bottom thickness is the
|
||||
* full handle assembly: top separator (bw) + content (hw)
|
||||
* + bottom border (bw). Per Openbox spec, handle_width is
|
||||
* the content-only height with borders drawn around it.
|
||||
*/
|
||||
if (view_handle_visible(view)) {
|
||||
thickness.bottom = 2 * theme->border_width
|
||||
+ theme->handle_width;
|
||||
}
|
||||
|
||||
return thickness;
|
||||
}
|
||||
|
||||
|
|
@ -122,14 +134,34 @@ ssd_get_resizing_type(const struct ssd *ssd, struct wlr_cursor *cursor)
|
|||
return LAB_NODE_CORNER_TOP_LEFT;
|
||||
} else if (top && right) {
|
||||
return LAB_NODE_CORNER_TOP_RIGHT;
|
||||
} else if (bottom && left) {
|
||||
return LAB_NODE_CORNER_BOTTOM_LEFT;
|
||||
} else if (bottom && right) {
|
||||
return LAB_NODE_CORNER_BOTTOM_RIGHT;
|
||||
} else if (bottom) {
|
||||
/*
|
||||
* When the handle is visible, the grip columns define
|
||||
* the effective corner zones for the bottom edge.
|
||||
* Expand the corner width so that the extents below
|
||||
* the grips produce diagonal resize types matching the
|
||||
* grip layout above them.
|
||||
*/
|
||||
if (view_handle_visible(view)) {
|
||||
struct theme *theme = rc.theme;
|
||||
int grip_col = theme->grip_width + theme->border_width;
|
||||
int wide = MAX(corner_width, grip_col);
|
||||
if (cursor->x < view_box.x + wide) {
|
||||
return LAB_NODE_CORNER_BOTTOM_LEFT;
|
||||
} else if (cursor->x > view_box.x
|
||||
+ view_box.width - wide) {
|
||||
return LAB_NODE_CORNER_BOTTOM_RIGHT;
|
||||
}
|
||||
} else {
|
||||
if (left) {
|
||||
return LAB_NODE_CORNER_BOTTOM_LEFT;
|
||||
} else if (right) {
|
||||
return LAB_NODE_CORNER_BOTTOM_RIGHT;
|
||||
}
|
||||
}
|
||||
return LAB_NODE_BORDER_BOTTOM;
|
||||
} else if (top) {
|
||||
return LAB_NODE_BORDER_TOP;
|
||||
} else if (bottom) {
|
||||
return LAB_NODE_BORDER_BOTTOM;
|
||||
} else if (left) {
|
||||
return LAB_NODE_BORDER_LEFT;
|
||||
} else if (right) {
|
||||
|
|
@ -167,10 +199,14 @@ ssd_create(struct view *view, bool active)
|
|||
*/
|
||||
ssd_titlebar_create(ssd);
|
||||
ssd_border_create(ssd);
|
||||
ssd_handle_create(ssd);
|
||||
if (!view_titlebar_visible(view)) {
|
||||
/* Ensure we keep the old state on Reconfigure or when exiting fullscreen */
|
||||
ssd_set_titlebar(ssd, false);
|
||||
}
|
||||
if (!view_handle_visible(view)) {
|
||||
ssd_set_handle(ssd, false);
|
||||
}
|
||||
ssd->margin = ssd_thickness(view);
|
||||
ssd_set_active(ssd, active);
|
||||
ssd_enable_keybind_inhibit_indicator(ssd, view->inhibits_keybinds);
|
||||
|
|
@ -234,6 +270,7 @@ ssd_update_geometry(struct ssd *ssd)
|
|||
* maximizedDecoration=none
|
||||
*/
|
||||
ssd_set_titlebar(ssd, view_titlebar_visible(view));
|
||||
ssd_set_handle(ssd, view_handle_visible(view));
|
||||
|
||||
if (update_extents) {
|
||||
ssd_extents_update(ssd);
|
||||
|
|
@ -242,6 +279,7 @@ ssd_update_geometry(struct ssd *ssd)
|
|||
if (update_area || state_changed) {
|
||||
ssd_titlebar_update(ssd);
|
||||
ssd_border_update(ssd);
|
||||
ssd_handle_update(ssd);
|
||||
ssd_shadow_update(ssd);
|
||||
}
|
||||
|
||||
|
|
@ -264,6 +302,22 @@ ssd_set_titlebar(struct ssd *ssd, bool enabled)
|
|||
ssd->margin = ssd_thickness(ssd->view);
|
||||
}
|
||||
|
||||
void
|
||||
ssd_set_handle(struct ssd *ssd, bool enabled)
|
||||
{
|
||||
if (!ssd || !ssd->handle.tree) {
|
||||
return;
|
||||
}
|
||||
if (ssd->handle.tree->node.enabled == enabled) {
|
||||
return;
|
||||
}
|
||||
wlr_scene_node_set_enabled(&ssd->handle.tree->node, enabled);
|
||||
ssd_border_update(ssd);
|
||||
ssd_extents_update(ssd);
|
||||
ssd_shadow_update(ssd);
|
||||
ssd->margin = ssd_thickness(ssd->view);
|
||||
}
|
||||
|
||||
void
|
||||
ssd_destroy(struct ssd *ssd)
|
||||
{
|
||||
|
|
@ -277,10 +331,15 @@ ssd_destroy(struct ssd *ssd)
|
|||
server.hovered_button->node) == view) {
|
||||
server.hovered_button = NULL;
|
||||
}
|
||||
if (server.hovered_handle_ssd == ssd) {
|
||||
server.hovered_handle_ssd = NULL;
|
||||
server.hovered_handle_element = -1;
|
||||
}
|
||||
|
||||
/* Destroy subcomponents */
|
||||
ssd_titlebar_destroy(ssd);
|
||||
ssd_border_destroy(ssd);
|
||||
ssd_handle_destroy(ssd);
|
||||
ssd_extents_destroy(ssd);
|
||||
ssd_shadow_destroy(ssd);
|
||||
wlr_scene_node_destroy(&ssd->tree->node);
|
||||
|
|
@ -298,6 +357,8 @@ ssd_mode_parse(const char *mode)
|
|||
return LAB_SSD_MODE_NONE;
|
||||
} else if (!strcasecmp(mode, "border")) {
|
||||
return LAB_SSD_MODE_BORDER;
|
||||
} else if (!strcasecmp(mode, "border-handle")) {
|
||||
return LAB_SSD_MODE_BORDER_HANDLE;
|
||||
} else if (!strcasecmp(mode, "full")) {
|
||||
return LAB_SSD_MODE_FULL;
|
||||
} else {
|
||||
|
|
@ -324,6 +385,11 @@ ssd_set_active(struct ssd *ssd, bool active)
|
|||
&ssd->shadow.subtrees[active_state].tree->node,
|
||||
active == active_state);
|
||||
}
|
||||
if (ssd->handle.tree) {
|
||||
wlr_scene_node_set_enabled(
|
||||
&ssd->handle.subtrees[active_state].tree->node,
|
||||
active == active_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -335,6 +401,7 @@ ssd_enable_shade(struct ssd *ssd, bool enable)
|
|||
}
|
||||
ssd_titlebar_update(ssd);
|
||||
ssd_border_update(ssd);
|
||||
ssd_handle_update(ssd);
|
||||
wlr_scene_node_set_enabled(&ssd->extents.tree->node, !enable);
|
||||
ssd_shadow_update(ssd);
|
||||
}
|
||||
|
|
@ -385,5 +452,13 @@ ssd_debug_get_node_name(const struct ssd *ssd, struct wlr_scene_node *node)
|
|||
if (node == &ssd->extents.tree->node) {
|
||||
return "extents";
|
||||
}
|
||||
if (ssd->handle.tree) {
|
||||
if (node == &ssd->handle.subtrees[SSD_ACTIVE].tree->node) {
|
||||
return "handle.active";
|
||||
}
|
||||
if (node == &ssd->handle.subtrees[SSD_INACTIVE].tree->node) {
|
||||
return "handle.inactive";
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue