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
|
|
@ -217,13 +217,14 @@ Actions are used in menus and keyboard/mouse bindings.
|
|||
*<action name="ToggleDecorations" />*
|
||||
Toggle decorations of focused window.
|
||||
|
||||
This is a 3-state action which can be executed multiple times:
|
||||
- Only the titlebar will be hidden, borders and resize area are kept
|
||||
This is a 4-state action which can be executed multiple times:
|
||||
- Titlebar is hidden; borders and handle/grip remain visible
|
||||
- Handle/grip is also hidden; only borders remain
|
||||
- Remaining decorations will be disabled
|
||||
- Decorations will be shown normally
|
||||
|
||||
By disabling the theme configuration 'keepBorder' the first step
|
||||
will be removed and the action only toggles between on and off.
|
||||
By disabling the theme configuration 'keepBorder' the cycle reduces
|
||||
to toggling between full decorations and no decorations.
|
||||
|
||||
*<action name="ToggleFullscreen" />*
|
||||
Toggle fullscreen state of focused window.
|
||||
|
|
@ -526,9 +527,10 @@ Actions that execute other actions. Used in keyboard/mouse bindings.
|
|||
Whether the client is tiled (snapped) to the indicated
|
||||
region. The indicated region may be a glob.
|
||||
|
||||
*decoration* [full|border|none]
|
||||
*decoration* [full|border-handle|border|none]
|
||||
Whether the client has full server-side decorations,
|
||||
borders only, or no server-side decorations.
|
||||
borders with handle/grip, borders only, or no
|
||||
server-side decorations.
|
||||
|
||||
*monitor* [current|left|right|<monitor_name>]
|
||||
Whether the client is on a monitor relative to the to
|
||||
|
|
|
|||
|
|
@ -113,6 +113,33 @@ window.*.title.bg.colorTo.splitTo: #557485
|
|||
Line width (integer) of border drawn around window frames.
|
||||
Default is 1.
|
||||
|
||||
*window.handle.width*
|
||||
Height (integer) of the bottom handle bar in pixels. Despite the name,
|
||||
this controls the vertical size of the handle (Openbox naming
|
||||
convention). When set to 0, the handle/grip assembly is disabled and
|
||||
the standard bottom border is used instead. Default is 6.
|
||||
|
||||
*window.grip.width*
|
||||
Width (integer) of each corner grip in the bottom handle bar, in
|
||||
pixels. Default is 20.
|
||||
|
||||
*window.active.handle.bg* / *window.inactive.handle.bg*
|
||||
Texture for the handle background. Supports Solid, Gradient Vertical,
|
||||
and Gradient SplitVertical. Default is *Solid* with color #a0a0a0
|
||||
(active) / #c0c0c0 (inactive).
|
||||
|
||||
*window.active.handle.bg.color* (and .colorTo, .color.splitTo, .colorTo.splitTo)
|
||||
Colors for the handle background gradient stops. See the texture
|
||||
documentation above for details.
|
||||
|
||||
*window.active.grip.bg* / *window.inactive.grip.bg*
|
||||
Texture for the left and right grip backgrounds. If not set, the grip
|
||||
inherits from the handle background.
|
||||
|
||||
*window.active.grip.bg.color* (and .colorTo, .color.splitTo, .colorTo.splitTo)
|
||||
Colors for the grip background gradient stops. If not set, the grip
|
||||
inherits from the handle background colors.
|
||||
|
||||
*window.titlebar.padding.width*
|
||||
Horizontal titlebar padding size, in pixels, between border and first
|
||||
button on the left/right.
|
||||
|
|
@ -561,7 +588,19 @@ following icons should be added:
|
|||
|
||||
# DEFINITIONS
|
||||
|
||||
The handle is the window edge decoration at the bottom of the window.
|
||||
The handle is the window edge decoration at the bottom of the window. It
|
||||
consists of a center handle bar (for vertical resizing) and two grips at the
|
||||
left and right corners (for diagonal resizing). The handle/grip assembly
|
||||
replaces the standard bottom border when enabled (window.handle.width > 0)
|
||||
and the current decoration state includes the handle. Vertical separator
|
||||
lines of border.width thickness are drawn between the grips and the center
|
||||
handle using the window border color.
|
||||
|
||||
When hovering over a grip element, it dims by 20%. When clicked (pressed
|
||||
but not yet moved), it dims by 40% with an inset shadow effect giving a
|
||||
"pushed in" appearance. During drag-resize, the dimming reduces to 20%
|
||||
while maintaining the inset shadow. The center handle does not receive
|
||||
these visual effects.
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
|
|
|
|||
13
docs/themerc
13
docs/themerc
|
|
@ -58,6 +58,19 @@ window.inactive.shadow.size: 40
|
|||
window.active.shadow.color: #00000060
|
||||
window.inactive.shadow.color: #00000040
|
||||
|
||||
# window bottom handle and grips
|
||||
window.handle.width: 6
|
||||
window.grip.width: 20
|
||||
window.active.handle.bg: Solid
|
||||
window.active.handle.bg.color: #a0a0a0
|
||||
window.inactive.handle.bg: Solid
|
||||
window.inactive.handle.bg.color: #c0c0c0
|
||||
# Grips inherit from handle if not set explicitly
|
||||
# window.active.grip.bg: Solid
|
||||
# window.active.grip.bg.color: #a0a0a0
|
||||
# window.inactive.grip.bg: Solid
|
||||
# window.inactive.grip.bg.color: #c0c0c0
|
||||
|
||||
# Note that "menu", "iconify", "max", "close" buttons colors can be defined
|
||||
# individually by inserting the type after the button node, for example:
|
||||
#
|
||||
|
|
|
|||
|
|
@ -43,6 +43,10 @@ enum lab_node_type {
|
|||
LAB_NODE_BORDER_LEFT,
|
||||
LAB_NODE_BORDER,
|
||||
|
||||
LAB_NODE_HANDLE,
|
||||
LAB_NODE_GRIP_LEFT,
|
||||
LAB_NODE_GRIP_RIGHT,
|
||||
|
||||
LAB_NODE_CLIENT,
|
||||
LAB_NODE_FRAME,
|
||||
LAB_NODE_ROOT,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ enum lab_rotation {
|
|||
enum lab_ssd_mode {
|
||||
LAB_SSD_MODE_NONE = 0,
|
||||
LAB_SSD_MODE_BORDER,
|
||||
LAB_SSD_MODE_BORDER_HANDLE,
|
||||
LAB_SSD_MODE_FULL,
|
||||
LAB_SSD_MODE_INVALID,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -229,6 +229,10 @@ struct server {
|
|||
|
||||
struct ssd_button *hovered_button;
|
||||
|
||||
/* Track which handle/grip element is hovered (for cross-view cleanup) */
|
||||
struct ssd *hovered_handle_ssd;
|
||||
int hovered_handle_element; /* -1 = none */
|
||||
|
||||
/* Tree for all non-layer xdg/xwayland-shell surfaces */
|
||||
struct wlr_scene_tree *workspace_tree;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,35 @@ struct ssd_state_title_width {
|
|||
bool truncated;
|
||||
};
|
||||
|
||||
/* Interaction state for handle and grip elements */
|
||||
enum ssd_handle_state {
|
||||
SSD_HANDLE_STATE_NORMAL = 0,
|
||||
SSD_HANDLE_STATE_HOVER,
|
||||
SSD_HANDLE_STATE_PRESSED, /* clicked but not yet moved */
|
||||
SSD_HANDLE_STATE_DRAGGING, /* clicked and moved */
|
||||
};
|
||||
|
||||
/* Indices into ssd_handle_scene.element_states[] */
|
||||
#define SSD_HANDLE_ELEMENT_GRIP_LEFT 0
|
||||
#define SSD_HANDLE_ELEMENT_CENTER 1
|
||||
#define SSD_HANDLE_ELEMENT_GRIP_RIGHT 2
|
||||
#define SSD_HANDLE_ELEMENT_COUNT 3
|
||||
|
||||
/* Indices into ssd_handle_subtree.rects[] for border/separator rects */
|
||||
enum handle_rect_idx {
|
||||
HRECT_BORDER_LEFT,
|
||||
HRECT_BORDER_RIGHT,
|
||||
HRECT_BORDER_BOTTOM_LEFT,
|
||||
HRECT_BORDER_BOTTOM_CENTER,
|
||||
HRECT_BORDER_BOTTOM_RIGHT,
|
||||
HRECT_BORDER_TOP_LEFT,
|
||||
HRECT_BORDER_TOP_CENTER,
|
||||
HRECT_BORDER_TOP_RIGHT,
|
||||
HRECT_SEPARATOR_LEFT,
|
||||
HRECT_SEPARATOR_RIGHT,
|
||||
HRECT_COUNT,
|
||||
};
|
||||
|
||||
/*
|
||||
* The scene-graph of SSD looks like below. The parentheses indicate the
|
||||
* type of each node (enum lab_node_type, stored in the node_descriptor
|
||||
|
|
@ -50,6 +79,14 @@ struct ssd_state_title_width {
|
|||
* +--extents
|
||||
* +--top
|
||||
* +--...
|
||||
* +--handle (optional, when handle_width > 0)
|
||||
* +--inactive
|
||||
* | +--textures[3] (grip_left, handle, grip_right wlr_scene_buffers)
|
||||
* | +--rects[HRECT_COUNT] (borders and separators, all border_color)
|
||||
* | +--overlay[3] (per-element hover/pressed dimming)
|
||||
* | +--inset shadow rects (per-element top, left, bottom, right)
|
||||
* +--active
|
||||
* +--...
|
||||
*/
|
||||
struct ssd {
|
||||
struct view *view;
|
||||
|
|
@ -127,6 +164,27 @@ struct ssd {
|
|||
} subtrees[2]; /* indexed by enum ssd_active_state */
|
||||
} shadow;
|
||||
|
||||
/* Bottom handle/grip assembly (optional, created when handle_width > 0) */
|
||||
struct ssd_handle_scene {
|
||||
struct wlr_scene_tree *tree;
|
||||
struct ssd_handle_subtree {
|
||||
struct wlr_scene_tree *tree;
|
||||
/* Per-element texture buffers (1px wide, stretched) */
|
||||
struct wlr_scene_buffer *textures[SSD_HANDLE_ELEMENT_COUNT];
|
||||
/* Border and separator rects (all border_color) */
|
||||
struct wlr_scene_rect *rects[HRECT_COUNT];
|
||||
/* Per-element overlays for hover/pressed dimming */
|
||||
struct wlr_scene_rect *overlay[SSD_HANDLE_ELEMENT_COUNT];
|
||||
/* Inset shadow rects (per-element, for pressed/dragging) */
|
||||
struct wlr_scene_rect *inset_top[SSD_HANDLE_ELEMENT_COUNT];
|
||||
struct wlr_scene_rect *inset_left[SSD_HANDLE_ELEMENT_COUNT];
|
||||
struct wlr_scene_rect *inset_bottom[SSD_HANDLE_ELEMENT_COUNT];
|
||||
struct wlr_scene_rect *inset_right[SSD_HANDLE_ELEMENT_COUNT];
|
||||
} subtrees[2]; /* indexed by enum ssd_active_state */
|
||||
/* Interaction state per element */
|
||||
enum ssd_handle_state element_states[SSD_HANDLE_ELEMENT_COUNT];
|
||||
} handle;
|
||||
|
||||
/*
|
||||
* Space between the extremities of the view's wlr_surface
|
||||
* and the max extents of the server-side decorations.
|
||||
|
|
@ -186,4 +244,11 @@ void ssd_shadow_create(struct ssd *ssd);
|
|||
void ssd_shadow_update(struct ssd *ssd);
|
||||
void ssd_shadow_destroy(struct ssd *ssd);
|
||||
|
||||
void ssd_handle_create(struct ssd *ssd);
|
||||
void ssd_handle_update(struct ssd *ssd);
|
||||
void ssd_handle_destroy(struct ssd *ssd);
|
||||
void ssd_handle_set_element_state(struct ssd *ssd, int element,
|
||||
enum ssd_handle_state state);
|
||||
void ssd_handle_clear_all_states(struct ssd *ssd);
|
||||
|
||||
#endif /* LABWC_SSD_INTERNAL_H */
|
||||
|
|
|
|||
|
|
@ -50,8 +50,10 @@ void ssd_set_titlebar(struct ssd *ssd, bool enabled);
|
|||
|
||||
void ssd_enable_keybind_inhibit_indicator(struct ssd *ssd, bool enable);
|
||||
void ssd_enable_shade(struct ssd *ssd, bool enable);
|
||||
void ssd_set_handle(struct ssd *ssd, bool enabled);
|
||||
|
||||
void ssd_update_hovered_button(struct wlr_scene_node *node);
|
||||
void ssd_update_hovered_handle(struct wlr_scene_node *node);
|
||||
|
||||
void ssd_button_free(struct ssd_button *button);
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,16 @@ struct theme_background {
|
|||
struct theme {
|
||||
int border_width;
|
||||
|
||||
/*
|
||||
* Height of the bottom handle bar (Openbox calls this
|
||||
* "window.handle.width" but it is the vertical size).
|
||||
* Set to 0 to disable the handle/grip assembly.
|
||||
*/
|
||||
int handle_width;
|
||||
|
||||
/* Width of each corner grip in the handle bar */
|
||||
int grip_width;
|
||||
|
||||
/*
|
||||
* the space between title bar border and
|
||||
* buttons on the left/right/top
|
||||
|
|
@ -130,6 +140,16 @@ struct theme {
|
|||
struct lab_data_buffer *shadow_corner_top;
|
||||
struct lab_data_buffer *shadow_corner_bottom;
|
||||
struct lab_data_buffer *shadow_edge;
|
||||
|
||||
/* handle and grip backgrounds (Openbox-compatible) */
|
||||
struct theme_background handle_bg;
|
||||
struct theme_background grip_bg;
|
||||
|
||||
/* Pre-rendered handle/grip fill buffers and patterns */
|
||||
cairo_pattern_t *handle_pattern;
|
||||
cairo_pattern_t *grip_pattern;
|
||||
struct lab_data_buffer *handle_fill;
|
||||
struct lab_data_buffer *grip_fill;
|
||||
} window[2];
|
||||
|
||||
/* Derived from font sizes */
|
||||
|
|
|
|||
|
|
@ -562,6 +562,7 @@ bool view_is_tiled_and_notify_tiled(struct view *view);
|
|||
bool view_is_floating(struct view *view);
|
||||
void view_move_to_workspace(struct view *view, struct workspace *workspace);
|
||||
bool view_titlebar_visible(struct view *view);
|
||||
bool view_handle_visible(struct view *view);
|
||||
void view_set_ssd_mode(struct view *view, enum lab_ssd_mode mode);
|
||||
void view_set_decorations(struct view *view, enum lab_ssd_mode mode, bool force_ssd);
|
||||
void view_toggle_fullscreen(struct view *view);
|
||||
|
|
|
|||
|
|
@ -42,6 +42,12 @@ node_type_parse(const char *context)
|
|||
return LAB_NODE_BORDER_BOTTOM;
|
||||
} else if (!strcasecmp(context, "Left")) {
|
||||
return LAB_NODE_BORDER_LEFT;
|
||||
} else if (!strcasecmp(context, "Handle")) {
|
||||
return LAB_NODE_HANDLE;
|
||||
} else if (!strcasecmp(context, "BLGrip")) {
|
||||
return LAB_NODE_GRIP_LEFT;
|
||||
} else if (!strcasecmp(context, "BRGrip")) {
|
||||
return LAB_NODE_GRIP_RIGHT;
|
||||
} else if (!strcasecmp(context, "Frame")) {
|
||||
return LAB_NODE_FRAME;
|
||||
} else if (!strcasecmp(context, "Client")) {
|
||||
|
|
@ -80,9 +86,16 @@ node_type_contains(enum lab_node_type whole, enum lab_node_type part)
|
|||
return part >= LAB_NODE_BUTTON_FIRST
|
||||
&& part <= LAB_NODE_CLIENT;
|
||||
}
|
||||
if (whole == LAB_NODE_HANDLE) {
|
||||
return part == LAB_NODE_GRIP_LEFT
|
||||
|| part == LAB_NODE_GRIP_RIGHT;
|
||||
}
|
||||
if (whole == LAB_NODE_BORDER) {
|
||||
return part >= LAB_NODE_CORNER_TOP_LEFT
|
||||
&& part <= LAB_NODE_BORDER_LEFT;
|
||||
return (part >= LAB_NODE_CORNER_TOP_LEFT
|
||||
&& part <= LAB_NODE_BORDER_LEFT)
|
||||
|| part == LAB_NODE_HANDLE
|
||||
|| part == LAB_NODE_GRIP_LEFT
|
||||
|| part == LAB_NODE_GRIP_RIGHT;
|
||||
}
|
||||
if (whole == LAB_NODE_BORDER_TOP) {
|
||||
return part == LAB_NODE_CORNER_TOP_LEFT
|
||||
|
|
@ -94,7 +107,10 @@ node_type_contains(enum lab_node_type whole, enum lab_node_type part)
|
|||
}
|
||||
if (whole == LAB_NODE_BORDER_BOTTOM) {
|
||||
return part == LAB_NODE_CORNER_BOTTOM_RIGHT
|
||||
|| part == LAB_NODE_CORNER_BOTTOM_LEFT;
|
||||
|| part == LAB_NODE_CORNER_BOTTOM_LEFT
|
||||
|| part == LAB_NODE_HANDLE
|
||||
|| part == LAB_NODE_GRIP_LEFT
|
||||
|| part == LAB_NODE_GRIP_RIGHT;
|
||||
}
|
||||
if (whole == LAB_NODE_BORDER_LEFT) {
|
||||
return part == LAB_NODE_CORNER_TOP_LEFT
|
||||
|
|
@ -123,6 +139,12 @@ node_type_to_edges(enum lab_node_type type)
|
|||
return LAB_EDGES_BOTTOM_RIGHT;
|
||||
case LAB_NODE_CORNER_BOTTOM_LEFT:
|
||||
return LAB_EDGES_BOTTOM_LEFT;
|
||||
case LAB_NODE_HANDLE:
|
||||
return LAB_EDGE_BOTTOM;
|
||||
case LAB_NODE_GRIP_LEFT:
|
||||
return LAB_EDGES_BOTTOM_LEFT;
|
||||
case LAB_NODE_GRIP_RIGHT:
|
||||
return LAB_EDGES_BOTTOM_RIGHT;
|
||||
default:
|
||||
return LAB_EDGE_NONE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -390,6 +390,19 @@ get_cursor_context(void)
|
|||
ret.node = node;
|
||||
ret.type = LAB_NODE_CYCLE_OSD_ITEM;
|
||||
return ret;
|
||||
case LAB_NODE_HANDLE:
|
||||
case LAB_NODE_GRIP_LEFT:
|
||||
case LAB_NODE_GRIP_RIGHT:
|
||||
/*
|
||||
* Handle/grip nodes have precise types
|
||||
* assigned via node_descriptor; use them
|
||||
* directly without ssd_get_resizing_type().
|
||||
*/
|
||||
ret.node = node;
|
||||
ret.view = desc->view;
|
||||
assert(ret.view);
|
||||
ret.type = desc->type;
|
||||
return ret;
|
||||
case LAB_NODE_BUTTON_FIRST...LAB_NODE_BUTTON_LAST:
|
||||
case LAB_NODE_SSD_ROOT:
|
||||
case LAB_NODE_TITLE:
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
#include "resistance.h"
|
||||
#include "resize-outlines.h"
|
||||
#include "ssd.h"
|
||||
#include "ssd-internal.h"
|
||||
#include "view.h"
|
||||
#include "xwayland.h"
|
||||
|
||||
|
|
@ -315,6 +316,24 @@ process_cursor_resize(uint32_t time)
|
|||
static struct view *last_resize_view = NULL;
|
||||
|
||||
assert(server.grabbed_view);
|
||||
|
||||
/*
|
||||
* Transition handle/grip from PRESSED to DRAGGING on
|
||||
* first resize motion.
|
||||
*/
|
||||
struct ssd *ssd = server.grabbed_view->ssd;
|
||||
if (ssd && ssd->handle.tree) {
|
||||
for (int i = 0; i < SSD_HANDLE_ELEMENT_COUNT; i++) {
|
||||
if (i == SSD_HANDLE_ELEMENT_CENTER) {
|
||||
continue;
|
||||
}
|
||||
if (ssd->handle.element_states[i]
|
||||
== SSD_HANDLE_STATE_PRESSED) {
|
||||
ssd_handle_set_element_state(ssd, i,
|
||||
SSD_HANDLE_STATE_DRAGGING);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (server.grabbed_view == last_resize_view) {
|
||||
int32_t refresh = 0;
|
||||
if (output_is_usable(last_resize_view->output)) {
|
||||
|
|
@ -544,6 +563,7 @@ cursor_update_common(const struct cursor_context *ctx,
|
|||
struct wlr_seat *wlr_seat = seat->wlr_seat;
|
||||
|
||||
ssd_update_hovered_button(ctx->node);
|
||||
ssd_update_hovered_handle(ctx->node);
|
||||
|
||||
if (server.input_mode != LAB_INPUT_STATE_PASSTHROUGH) {
|
||||
/*
|
||||
|
|
@ -1150,6 +1170,28 @@ cursor_process_button_press(struct seat *seat, uint32_t button, uint32_t time_ms
|
|||
interactive_set_grab_context(&ctx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set pressed visual state on grip elements (not center handle).
|
||||
* This runs before action processing so the visual
|
||||
* feedback appears immediately on click.
|
||||
*/
|
||||
if (ctx.view && ctx.view->ssd && ctx.view->ssd->handle.tree) {
|
||||
switch (ctx.type) {
|
||||
case LAB_NODE_GRIP_LEFT:
|
||||
ssd_handle_set_element_state(ctx.view->ssd,
|
||||
SSD_HANDLE_ELEMENT_GRIP_LEFT,
|
||||
SSD_HANDLE_STATE_PRESSED);
|
||||
break;
|
||||
case LAB_NODE_GRIP_RIGHT:
|
||||
ssd_handle_set_element_state(ctx.view->ssd,
|
||||
SSD_HANDLE_ELEMENT_GRIP_RIGHT,
|
||||
SSD_HANDLE_STATE_PRESSED);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (server.input_mode == LAB_INPUT_STATE_MENU) {
|
||||
/*
|
||||
* If menu was already opened on press, set a very small value
|
||||
|
|
@ -1272,11 +1314,21 @@ cursor_finish_button_release(struct seat *seat, uint32_t button)
|
|||
if (resize_outlines_enabled(server.grabbed_view)) {
|
||||
resize_outlines_finish(server.grabbed_view);
|
||||
}
|
||||
/*
|
||||
* Clear any handle/grip pressed/dragging state
|
||||
* when finishing interactive resize.
|
||||
*/
|
||||
ssd_handle_clear_all_states(
|
||||
server.grabbed_view ? server.grabbed_view->ssd : NULL);
|
||||
/* Exit interactive move/resize mode */
|
||||
interactive_finish(server.grabbed_view);
|
||||
return true;
|
||||
} else if (server.grabbed_view) {
|
||||
/* Button was released without starting move/resize */
|
||||
/*
|
||||
* Button was released without starting move/resize.
|
||||
* Clear any handle/grip pressed state.
|
||||
*/
|
||||
ssd_handle_clear_all_states(server.grabbed_view->ssd);
|
||||
interactive_cancel(server.grabbed_view);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
} 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 (bottom && right) {
|
||||
} 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;
|
||||
}
|
||||
|
|
|
|||
157
src/theme.c
157
src/theme.c
|
|
@ -532,6 +532,8 @@ static void
|
|||
theme_builtin(struct theme *theme)
|
||||
{
|
||||
theme->border_width = 1;
|
||||
theme->handle_width = 6;
|
||||
theme->grip_width = 20;
|
||||
theme->window_titlebar_padding_height = 0;
|
||||
theme->window_titlebar_padding_width = 0;
|
||||
|
||||
|
|
@ -551,6 +553,30 @@ theme_builtin(struct theme *theme)
|
|||
theme->window[SSD_ACTIVE].title_bg.color_to_split_to[0] = FLT_MIN;
|
||||
theme->window[SSD_INACTIVE].title_bg.color_to_split_to[0] = FLT_MIN;
|
||||
|
||||
/* handle background defaults */
|
||||
theme->window[SSD_ACTIVE].handle_bg.gradient = LAB_GRADIENT_NONE;
|
||||
theme->window[SSD_INACTIVE].handle_bg.gradient = LAB_GRADIENT_NONE;
|
||||
parse_hexstr("#a0a0a0", theme->window[SSD_ACTIVE].handle_bg.color);
|
||||
parse_hexstr("#c0c0c0", theme->window[SSD_INACTIVE].handle_bg.color);
|
||||
theme->window[SSD_ACTIVE].handle_bg.color_split_to[0] = FLT_MIN;
|
||||
theme->window[SSD_INACTIVE].handle_bg.color_split_to[0] = FLT_MIN;
|
||||
theme->window[SSD_ACTIVE].handle_bg.color_to[0] = FLT_MIN;
|
||||
theme->window[SSD_INACTIVE].handle_bg.color_to[0] = FLT_MIN;
|
||||
theme->window[SSD_ACTIVE].handle_bg.color_to_split_to[0] = FLT_MIN;
|
||||
theme->window[SSD_INACTIVE].handle_bg.color_to_split_to[0] = FLT_MIN;
|
||||
|
||||
/* grip background defaults (FLT_MIN sentinel = inherit from handle) */
|
||||
theme->window[SSD_ACTIVE].grip_bg.gradient = LAB_GRADIENT_NONE;
|
||||
theme->window[SSD_INACTIVE].grip_bg.gradient = LAB_GRADIENT_NONE;
|
||||
theme->window[SSD_ACTIVE].grip_bg.color[0] = FLT_MIN;
|
||||
theme->window[SSD_INACTIVE].grip_bg.color[0] = FLT_MIN;
|
||||
theme->window[SSD_ACTIVE].grip_bg.color_split_to[0] = FLT_MIN;
|
||||
theme->window[SSD_INACTIVE].grip_bg.color_split_to[0] = FLT_MIN;
|
||||
theme->window[SSD_ACTIVE].grip_bg.color_to[0] = FLT_MIN;
|
||||
theme->window[SSD_INACTIVE].grip_bg.color_to[0] = FLT_MIN;
|
||||
theme->window[SSD_ACTIVE].grip_bg.color_to_split_to[0] = FLT_MIN;
|
||||
theme->window[SSD_INACTIVE].grip_bg.color_to_split_to[0] = FLT_MIN;
|
||||
|
||||
parse_hexstr("#000000", theme->window[SSD_ACTIVE].label_text_color);
|
||||
parse_hexstr("#000000", theme->window[SSD_INACTIVE].label_text_color);
|
||||
theme->window_label_text_justify = parse_justification("Center");
|
||||
|
|
@ -757,6 +783,82 @@ entry(struct theme *theme, const char *key, const char *value)
|
|||
parse_color(value, theme->window[SSD_INACTIVE].title_bg.color_to_split_to);
|
||||
}
|
||||
|
||||
/* handle width (Openbox "window.handle.width" is actually the height) */
|
||||
if (match_glob(key, "window.handle.width")) {
|
||||
theme->handle_width = get_int_if_positive(
|
||||
value, "window.handle.width");
|
||||
}
|
||||
|
||||
/* grip width */
|
||||
if (match_glob(key, "window.grip.width")) {
|
||||
theme->grip_width = get_int_if_positive(
|
||||
value, "window.grip.width");
|
||||
}
|
||||
|
||||
/* handle background */
|
||||
if (match_glob(key, "window.active.handle.bg")) {
|
||||
theme->window[SSD_ACTIVE].handle_bg.gradient = parse_gradient(value);
|
||||
}
|
||||
if (match_glob(key, "window.inactive.handle.bg")) {
|
||||
theme->window[SSD_INACTIVE].handle_bg.gradient = parse_gradient(value);
|
||||
}
|
||||
if (match_glob(key, "window.active.handle.bg.color")) {
|
||||
parse_color(value, theme->window[SSD_ACTIVE].handle_bg.color);
|
||||
}
|
||||
if (match_glob(key, "window.inactive.handle.bg.color")) {
|
||||
parse_color(value, theme->window[SSD_INACTIVE].handle_bg.color);
|
||||
}
|
||||
if (match_glob(key, "window.active.handle.bg.color.splitTo")) {
|
||||
parse_color(value, theme->window[SSD_ACTIVE].handle_bg.color_split_to);
|
||||
}
|
||||
if (match_glob(key, "window.inactive.handle.bg.color.splitTo")) {
|
||||
parse_color(value, theme->window[SSD_INACTIVE].handle_bg.color_split_to);
|
||||
}
|
||||
if (match_glob(key, "window.active.handle.bg.colorTo")) {
|
||||
parse_color(value, theme->window[SSD_ACTIVE].handle_bg.color_to);
|
||||
}
|
||||
if (match_glob(key, "window.inactive.handle.bg.colorTo")) {
|
||||
parse_color(value, theme->window[SSD_INACTIVE].handle_bg.color_to);
|
||||
}
|
||||
if (match_glob(key, "window.active.handle.bg.colorTo.splitTo")) {
|
||||
parse_color(value, theme->window[SSD_ACTIVE].handle_bg.color_to_split_to);
|
||||
}
|
||||
if (match_glob(key, "window.inactive.handle.bg.colorTo.splitTo")) {
|
||||
parse_color(value, theme->window[SSD_INACTIVE].handle_bg.color_to_split_to);
|
||||
}
|
||||
|
||||
/* grip background */
|
||||
if (match_glob(key, "window.active.grip.bg")) {
|
||||
theme->window[SSD_ACTIVE].grip_bg.gradient = parse_gradient(value);
|
||||
}
|
||||
if (match_glob(key, "window.inactive.grip.bg")) {
|
||||
theme->window[SSD_INACTIVE].grip_bg.gradient = parse_gradient(value);
|
||||
}
|
||||
if (match_glob(key, "window.active.grip.bg.color")) {
|
||||
parse_color(value, theme->window[SSD_ACTIVE].grip_bg.color);
|
||||
}
|
||||
if (match_glob(key, "window.inactive.grip.bg.color")) {
|
||||
parse_color(value, theme->window[SSD_INACTIVE].grip_bg.color);
|
||||
}
|
||||
if (match_glob(key, "window.active.grip.bg.color.splitTo")) {
|
||||
parse_color(value, theme->window[SSD_ACTIVE].grip_bg.color_split_to);
|
||||
}
|
||||
if (match_glob(key, "window.inactive.grip.bg.color.splitTo")) {
|
||||
parse_color(value, theme->window[SSD_INACTIVE].grip_bg.color_split_to);
|
||||
}
|
||||
if (match_glob(key, "window.active.grip.bg.colorTo")) {
|
||||
parse_color(value, theme->window[SSD_ACTIVE].grip_bg.color_to);
|
||||
}
|
||||
if (match_glob(key, "window.inactive.grip.bg.colorTo")) {
|
||||
parse_color(value, theme->window[SSD_INACTIVE].grip_bg.color_to);
|
||||
}
|
||||
if (match_glob(key, "window.active.grip.bg.colorTo.splitTo")) {
|
||||
parse_color(value, theme->window[SSD_ACTIVE].grip_bg.color_to_split_to);
|
||||
}
|
||||
if (match_glob(key, "window.inactive.grip.bg.colorTo.splitTo")) {
|
||||
parse_color(value, theme->window[SSD_INACTIVE].grip_bg.color_to_split_to);
|
||||
}
|
||||
|
||||
if (match_glob(key, "window.active.label.text.color")) {
|
||||
parse_color(value, theme->window[SSD_ACTIVE].label_text_color);
|
||||
}
|
||||
|
|
@ -1410,6 +1512,28 @@ create_backgrounds(struct theme *theme)
|
|||
theme->window[active].titlebar_pattern,
|
||||
theme->titlebar_height);
|
||||
}
|
||||
|
||||
/* Create handle and grip fill buffers if handle is enabled */
|
||||
if (theme->handle_width > 0) {
|
||||
FOR_EACH_ACTIVE_STATE(active) {
|
||||
theme->window[active].handle_pattern =
|
||||
create_titlebar_pattern(
|
||||
&theme->window[active].handle_bg,
|
||||
theme->handle_width);
|
||||
theme->window[active].handle_fill =
|
||||
create_titlebar_fill(
|
||||
theme->window[active].handle_pattern,
|
||||
theme->handle_width);
|
||||
theme->window[active].grip_pattern =
|
||||
create_titlebar_pattern(
|
||||
&theme->window[active].grip_bg,
|
||||
theme->handle_width);
|
||||
theme->window[active].grip_fill =
|
||||
create_titlebar_fill(
|
||||
theme->window[active].grip_pattern,
|
||||
theme->handle_width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -1686,6 +1810,35 @@ post_processing(struct theme *theme)
|
|||
fill_background_colors(&theme->window[SSD_INACTIVE].title_bg);
|
||||
fill_background_colors(&theme->window[SSD_ACTIVE].title_bg);
|
||||
|
||||
/* Clamp handle_width to a sensible maximum */
|
||||
if (theme->handle_width > 100) {
|
||||
wlr_log(WLR_ERROR,
|
||||
"window.handle.width clamped from %d to 100",
|
||||
theme->handle_width);
|
||||
theme->handle_width = 100;
|
||||
}
|
||||
|
||||
/* Clamp grip_width minimum to 1 */
|
||||
if (theme->grip_width < 1) {
|
||||
theme->grip_width = 1;
|
||||
}
|
||||
|
||||
/* Fill handle gradient color defaults */
|
||||
fill_background_colors(&theme->window[SSD_INACTIVE].handle_bg);
|
||||
fill_background_colors(&theme->window[SSD_ACTIVE].handle_bg);
|
||||
|
||||
/* Inherit grip from handle if grip color was not explicitly set */
|
||||
enum ssd_active_state active;
|
||||
FOR_EACH_ACTIVE_STATE(active) {
|
||||
struct theme_background *grip = &theme->window[active].grip_bg;
|
||||
struct theme_background *handle = &theme->window[active].handle_bg;
|
||||
if (grip->color[0] == FLT_MIN) {
|
||||
memcpy(grip, handle, sizeof(*grip));
|
||||
} else {
|
||||
fill_background_colors(grip);
|
||||
}
|
||||
}
|
||||
|
||||
theme->menu_item_height = font_height(&rc.font_menuitem)
|
||||
+ 2 * theme->menu_items_padding_y;
|
||||
|
||||
|
|
@ -1877,5 +2030,9 @@ theme_finish(struct theme *theme)
|
|||
zdrop(&theme->window[active].shadow_corner_top);
|
||||
zdrop(&theme->window[active].shadow_corner_bottom);
|
||||
zdrop(&theme->window[active].shadow_edge);
|
||||
zfree_pattern(theme->window[active].handle_pattern);
|
||||
zfree_pattern(theme->window[active].grip_pattern);
|
||||
zdrop(&theme->window[active].handle_fill);
|
||||
zdrop(&theme->window[active].grip_fill);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
16
src/view.c
16
src/view.c
|
|
@ -1531,6 +1531,8 @@ view_toggle_decorations(struct view *view)
|
|||
assert(view);
|
||||
|
||||
if (rc.ssd_keep_border && view->ssd_mode == LAB_SSD_MODE_FULL) {
|
||||
view_set_ssd_mode(view, LAB_SSD_MODE_BORDER_HANDLE);
|
||||
} else if (view->ssd_mode == LAB_SSD_MODE_BORDER_HANDLE) {
|
||||
view_set_ssd_mode(view, LAB_SSD_MODE_BORDER);
|
||||
} else if (view->ssd_mode != LAB_SSD_MODE_NONE) {
|
||||
view_set_ssd_mode(view, LAB_SSD_MODE_NONE);
|
||||
|
|
@ -1621,6 +1623,19 @@ view_titlebar_visible(struct view *view)
|
|||
return view->ssd_mode == LAB_SSD_MODE_FULL;
|
||||
}
|
||||
|
||||
bool
|
||||
view_handle_visible(struct view *view)
|
||||
{
|
||||
if (rc.theme->handle_width <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (view->shaded) {
|
||||
return false;
|
||||
}
|
||||
return view->ssd_mode == LAB_SSD_MODE_FULL
|
||||
|| view->ssd_mode == LAB_SSD_MODE_BORDER_HANDLE;
|
||||
}
|
||||
|
||||
void
|
||||
view_set_ssd_mode(struct view *view, enum lab_ssd_mode mode)
|
||||
{
|
||||
|
|
@ -1640,6 +1655,7 @@ view_set_ssd_mode(struct view *view, enum lab_ssd_mode mode)
|
|||
if (mode) {
|
||||
decorate(view);
|
||||
ssd_set_titlebar(view->ssd, view_titlebar_visible(view));
|
||||
ssd_set_handle(view->ssd, view_handle_visible(view));
|
||||
} else {
|
||||
undecorate(view);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue