mirror of
https://github.com/labwc/labwc.git
synced 2025-10-29 05:40:24 -04:00
ssd: add window drop-shadows (#1648)
Add optional drop-shadows to windows using server-side decoration. Shadows can be enabled/disabled rc.xml and their appearance configured in themerc. The default is no shadows to preserve current behaviour. The shadows are drawn in fixed corner and edge buffers shared between all windows, the edges are scaled to size depending on the size of each window. Two sets of buffers are used to give the different appearances for active and inactive windows. I use separate corner/edge buffers for a few reasons: - It avoids needing to store a separate large shadow buffer per window - It avoids needing to redraw the shadows when the window is being resized - Compositing the shadows onto the desktop should be faster as there are overall fewer pixels to blend, and scaling up the edge buffers only requires reading a tiny buffer which is then replicated.
This commit is contained in:
parent
b0ba585ff8
commit
ce0d2c2966
13 changed files with 606 additions and 0 deletions
|
|
@ -401,6 +401,9 @@ extending outward from the snapped edge.
|
|||
Even when disabling server side decorations via ToggleDecorations,
|
||||
keep a small border (and resize area) around the window. Default is yes.
|
||||
|
||||
*<theme><dropShadows>* [yes|no]
|
||||
Should drop-shadows be rendered behind windows. Default is no.
|
||||
|
||||
*<theme><font place="">*
|
||||
The font to use for a specific element of a window, menu or OSD.
|
||||
Places can be any of:
|
||||
|
|
|
|||
|
|
@ -130,6 +130,20 @@ This syntax is not documented on the openbox.org wiki, but is supported by
|
|||
openbox and is used by many popular themes. For the sake of brevity, these
|
||||
elements are not listed here, but are supported.
|
||||
|
||||
*window.active.shadow.size*
|
||||
Size of the drop-shadow for the focused window, in pixels. Default is 60.
|
||||
|
||||
*window.inactive.shadow.size*
|
||||
Size of drop-shadows for non-focused windows, in pixels. Default is 40.
|
||||
|
||||
*window.active.shadow.color*
|
||||
Color of the drop-shadow for the focused window, including opacity. Default
|
||||
is #00000060 (black with 38% opacity).
|
||||
|
||||
*window.inactive.shadow.color*
|
||||
Color of drop-shadows for non-focused windows, including opacity. Default is
|
||||
#00000040 (black with 25% opacity).
|
||||
|
||||
*menu.items.bg.color*
|
||||
Background color of inactive menu items.
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
<name></name>
|
||||
<cornerRadius>8</cornerRadius>
|
||||
<keepBorder>yes</keepBorder>
|
||||
<dropShadows>no</dropShadows>
|
||||
<font place="ActiveWindow">
|
||||
<name>sans</name>
|
||||
<size>10</size>
|
||||
|
|
|
|||
|
|
@ -33,6 +33,12 @@ window.label.text.justify: center
|
|||
window.active.button.unpressed.image.color: #000000
|
||||
window.inactive.button.unpressed.image.color: #000000
|
||||
|
||||
# window drop-shadows
|
||||
window.active.shadow.size: 60
|
||||
window.inactive.shadow.size: 40
|
||||
window.active.shadow.color: #00000060
|
||||
window.inactive.shadow.color: #00000040
|
||||
|
||||
# Note that "menu", "iconify", "max", "close" buttons colors can be defined
|
||||
# individually by inserting the type after the button node, for example:
|
||||
#
|
||||
|
|
|
|||
|
|
@ -64,10 +64,12 @@ struct rcxml {
|
|||
char *theme_name;
|
||||
int corner_radius;
|
||||
bool ssd_keep_border;
|
||||
bool shadows_enabled;
|
||||
struct font font_activewindow;
|
||||
struct font font_inactivewindow;
|
||||
struct font font_menuitem;
|
||||
struct font font_osd;
|
||||
|
||||
/* Pointer to current theme */
|
||||
struct theme *theme;
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,12 @@ struct ssd {
|
|||
struct ssd_sub_tree inactive;
|
||||
} border;
|
||||
|
||||
struct {
|
||||
struct wlr_scene_tree *tree;
|
||||
struct ssd_sub_tree active;
|
||||
struct ssd_sub_tree inactive;
|
||||
} shadow;
|
||||
|
||||
/*
|
||||
* Space between the extremities of the view's wlr_surface
|
||||
* and the max extents of the server-side decorations.
|
||||
|
|
@ -150,4 +156,8 @@ void ssd_extents_create(struct ssd *ssd);
|
|||
void ssd_extents_update(struct ssd *ssd);
|
||||
void ssd_extents_destroy(struct ssd *ssd);
|
||||
|
||||
void ssd_shadow_create(struct ssd *ssd);
|
||||
void ssd_shadow_update(struct ssd *ssd);
|
||||
void ssd_shadow_destroy(struct ssd *ssd);
|
||||
|
||||
#endif /* LABWC_SSD_INTERNAL_H */
|
||||
|
|
|
|||
|
|
@ -9,6 +9,13 @@
|
|||
#define SSD_BUTTON_WIDTH 26
|
||||
#define SSD_EXTENDED_AREA 8
|
||||
|
||||
/*
|
||||
* Shadows should start at a point inset from the actual window border, see
|
||||
* discussion on https://github.com/labwc/labwc/pull/1648. This constant
|
||||
* specifies inset as a multiple of visible shadow size.
|
||||
*/
|
||||
#define SSD_SHADOW_INSET 0.3
|
||||
|
||||
/*
|
||||
* Sequence these according to the order they should be processed for
|
||||
* press and hover events. Bear in mind that some of their respective
|
||||
|
|
|
|||
|
|
@ -93,6 +93,12 @@ struct theme {
|
|||
struct theme_snapping_overlay
|
||||
snapping_overlay_region, snapping_overlay_edge;
|
||||
|
||||
/* window drop-shadows */
|
||||
int window_active_shadow_size;
|
||||
int window_inactive_shadow_size;
|
||||
float window_active_shadow_color[4];
|
||||
float window_inactive_shadow_color[4];
|
||||
|
||||
/* textures */
|
||||
struct lab_data_buffer *button_close_active_unpressed;
|
||||
struct lab_data_buffer *button_maximize_active_unpressed;
|
||||
|
|
@ -124,6 +130,13 @@ struct theme {
|
|||
struct lab_data_buffer *corner_top_left_inactive_normal;
|
||||
struct lab_data_buffer *corner_top_right_inactive_normal;
|
||||
|
||||
struct lab_data_buffer *shadow_corner_top_active;
|
||||
struct lab_data_buffer *shadow_corner_bottom_active;
|
||||
struct lab_data_buffer *shadow_edge_active;
|
||||
struct lab_data_buffer *shadow_corner_top_inactive;
|
||||
struct lab_data_buffer *shadow_corner_bottom_inactive;
|
||||
struct lab_data_buffer *shadow_edge_inactive;
|
||||
|
||||
/* not set in rc.xml/themerc, but derived from font & padding_height */
|
||||
int osd_window_switcher_item_height;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -884,6 +884,8 @@ entry(xmlNode *node, char *nodename, char *content)
|
|||
rc.corner_radius = atoi(content);
|
||||
} else if (!strcasecmp(nodename, "keepBorder.theme")) {
|
||||
set_bool(content, &rc.ssd_keep_border);
|
||||
} else if (!strcasecmp(nodename, "dropShadows.theme")) {
|
||||
set_bool(content, &rc.shadows_enabled);
|
||||
} else if (!strcmp(nodename, "name.font.theme")) {
|
||||
fill_font(nodename, content, font_place);
|
||||
} else if (!strcmp(nodename, "size.font.theme")) {
|
||||
|
|
@ -1192,6 +1194,7 @@ rcxml_init(void)
|
|||
rc.xdg_shell_server_side_deco = true;
|
||||
rc.ssd_keep_border = true;
|
||||
rc.corner_radius = 8;
|
||||
rc.shadows_enabled = false;
|
||||
|
||||
init_font_defaults(&rc.font_activewindow);
|
||||
init_font_defaults(&rc.font_inactivewindow);
|
||||
|
|
|
|||
|
|
@ -5,4 +5,5 @@ labwc_sources += files(
|
|||
'ssd_titlebar.c',
|
||||
'ssd_border.c',
|
||||
'ssd_extents.c',
|
||||
'ssd_shadow.c',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@ ssd_create(struct view *view, bool active)
|
|||
ssd->tree = wlr_scene_tree_create(view->scene_tree);
|
||||
wlr_scene_node_lower_to_bottom(&ssd->tree->node);
|
||||
ssd->titlebar.height = view->server->theme->title_height;
|
||||
ssd_shadow_create(ssd);
|
||||
ssd_extents_create(ssd);
|
||||
ssd_border_create(ssd);
|
||||
ssd_titlebar_create(ssd);
|
||||
|
|
@ -238,6 +239,7 @@ ssd_update_geometry(struct ssd *ssd)
|
|||
if (ssd->state.was_maximized != maximized) {
|
||||
ssd_border_update(ssd);
|
||||
ssd_titlebar_update(ssd);
|
||||
ssd_shadow_update(ssd);
|
||||
/*
|
||||
* Not strictly necessary as ssd_titlebar_update()
|
||||
* already sets state.was_maximized but to future
|
||||
|
|
@ -250,6 +252,7 @@ ssd_update_geometry(struct ssd *ssd)
|
|||
ssd_extents_update(ssd);
|
||||
ssd_border_update(ssd);
|
||||
ssd_titlebar_update(ssd);
|
||||
ssd_shadow_update(ssd);
|
||||
ssd->state.geometry = current;
|
||||
}
|
||||
|
||||
|
|
@ -263,6 +266,7 @@ ssd_titlebar_hide(struct ssd *ssd)
|
|||
ssd->titlebar.height = 0;
|
||||
ssd_border_update(ssd);
|
||||
ssd_extents_update(ssd);
|
||||
ssd_shadow_update(ssd);
|
||||
ssd->margin = ssd_thickness(ssd->view);
|
||||
}
|
||||
|
||||
|
|
@ -286,6 +290,7 @@ ssd_destroy(struct ssd *ssd)
|
|||
ssd_titlebar_destroy(ssd);
|
||||
ssd_border_destroy(ssd);
|
||||
ssd_extents_destroy(ssd);
|
||||
ssd_shadow_destroy(ssd);
|
||||
wlr_scene_node_destroy(&ssd->tree->node);
|
||||
|
||||
free(ssd);
|
||||
|
|
@ -337,8 +342,16 @@ ssd_set_active(struct ssd *ssd, bool active)
|
|||
}
|
||||
wlr_scene_node_set_enabled(&ssd->border.active.tree->node, active);
|
||||
wlr_scene_node_set_enabled(&ssd->titlebar.active.tree->node, active);
|
||||
if (ssd->shadow.active.tree) {
|
||||
wlr_scene_node_set_enabled(
|
||||
&ssd->shadow.active.tree->node, active);
|
||||
}
|
||||
wlr_scene_node_set_enabled(&ssd->border.inactive.tree->node, !active);
|
||||
wlr_scene_node_set_enabled(&ssd->titlebar.inactive.tree->node, !active);
|
||||
if (ssd->shadow.inactive.tree) {
|
||||
wlr_scene_node_set_enabled(
|
||||
&ssd->shadow.inactive.tree->node, !active);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -349,6 +362,7 @@ ssd_enable_shade(struct ssd *ssd, bool enable)
|
|||
}
|
||||
ssd_border_update(ssd);
|
||||
wlr_scene_node_set_enabled(&ssd->extents.tree->node, !enable);
|
||||
ssd_shadow_update(ssd);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
316
src/ssd/ssd_shadow.c
Normal file
316
src/ssd/ssd_shadow.c
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#include <assert.h>
|
||||
#include "common/scene-helpers.h"
|
||||
#include "labwc.h"
|
||||
#include "buffer.h"
|
||||
#include "ssd-internal.h"
|
||||
#include "theme.h"
|
||||
#include "view.h"
|
||||
#include <cairo.h>
|
||||
|
||||
#define FOR_EACH_STATE(ssd, tmp) FOR_EACH(tmp, \
|
||||
&(ssd)->shadow.active, \
|
||||
&(ssd)->shadow.inactive)
|
||||
|
||||
/*
|
||||
* Implements point_accepts_input for a buffer which never accepts input
|
||||
* because drop-shadows should never catch clicks!
|
||||
*/
|
||||
static bool
|
||||
never_accepts_input(struct wlr_scene_buffer *buffer, double *sx, double *sy)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Setup transform and scale for shadow corner buffers. Cropping is applied if
|
||||
* the window is short or narrow enough that corners would overlap, the amount
|
||||
* to crop is controlled by vertical_overlap and horizontal_overlap. Cropping
|
||||
* is applied before rotation so switch_axes should be true for the bottom-left
|
||||
* and top-right corners to crop horizontally instead of vertically.
|
||||
*/
|
||||
static void
|
||||
corner_scale_crop(struct wlr_scene_buffer *buffer, int horizontal_overlap,
|
||||
int vertical_overlap, int corner_size, bool switch_axes)
|
||||
{
|
||||
int width = corner_size - horizontal_overlap;
|
||||
int height = corner_size - vertical_overlap;
|
||||
/* Crop is applied before rotation so gets the axis flip */
|
||||
struct wlr_fbox src_box = {
|
||||
.x = switch_axes ? vertical_overlap : horizontal_overlap,
|
||||
.y = switch_axes ? horizontal_overlap : vertical_overlap,
|
||||
.width = switch_axes ? height : width,
|
||||
.height = switch_axes ? width : height,
|
||||
};
|
||||
wlr_scene_buffer_set_source_box(buffer, &src_box);
|
||||
/* But scaling is applied after rotation so no axis flip */
|
||||
wlr_scene_buffer_set_dest_size(buffer, width, height);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the position, scaling, and visibility for a single part of a window
|
||||
* drop-shadow.
|
||||
*/
|
||||
static void
|
||||
set_shadow_part_geometry(struct ssd_part *part, int width, int height,
|
||||
int titlebar_height, int corner_size, int inset,
|
||||
int visible_shadow_width)
|
||||
{
|
||||
struct wlr_scene_buffer *scene_buf =
|
||||
wlr_scene_buffer_from_node(part->node);
|
||||
/*
|
||||
* If the shadow inset is greater than half the overall window height
|
||||
* or width (eg. becaused the window is shaded or because we have a
|
||||
* small window with massive shadows) then the corners would overlap
|
||||
* which looks horrible. To avoid this, when the window is too narrow
|
||||
* or short we hide the edges on that axis and clip off the portion of
|
||||
* the corners which would overlap. This does produce slight
|
||||
* aberrations in the shadow shape where corners meet but it's not too
|
||||
* noticeable.
|
||||
*/
|
||||
bool show_topbottom = width > inset * 2;
|
||||
bool show_sides = height > inset * 2;
|
||||
/* These values are the overlap on each corner (half total overlap) */
|
||||
int horizontal_overlap = MAX(0, inset - width / 2);
|
||||
int vertical_overlap = MAX(0, inset - height / 2);
|
||||
|
||||
/*
|
||||
* If window width or height is odd then making the corners equally
|
||||
* sized when the edge is hidden would leave a single pixel gap
|
||||
* between the corners. Showing a single pixel edge between clipped
|
||||
* corners looks bad because the edge-piece doesn't match up with the
|
||||
* corners after the corners are clipped. So fill the gap by making
|
||||
* the top-left and bottom-right corners one pixel wider (if the width
|
||||
* is odd) or taller (if the height is odd).
|
||||
*/
|
||||
if (part->type == LAB_SSD_PART_CORNER_TOP_LEFT
|
||||
|| part->type == LAB_SSD_PART_CORNER_BOTTOM_RIGHT) {
|
||||
if (horizontal_overlap > 0) {
|
||||
horizontal_overlap -= width % 2;
|
||||
}
|
||||
if (vertical_overlap > 0) {
|
||||
vertical_overlap -= height % 2;
|
||||
}
|
||||
}
|
||||
|
||||
int x;
|
||||
int y;
|
||||
|
||||
switch (part->type) {
|
||||
case LAB_SSD_PART_CORNER_BOTTOM_RIGHT:
|
||||
x = width - inset + horizontal_overlap;
|
||||
y = -titlebar_height + height - inset + vertical_overlap;
|
||||
wlr_scene_node_set_position(part->node, x, y);
|
||||
corner_scale_crop(scene_buf, horizontal_overlap,
|
||||
vertical_overlap, corner_size, false);
|
||||
break;
|
||||
case LAB_SSD_PART_CORNER_BOTTOM_LEFT:
|
||||
x = -visible_shadow_width;
|
||||
y = -titlebar_height + height - inset + vertical_overlap;
|
||||
wlr_scene_node_set_position(part->node, x, y);
|
||||
corner_scale_crop(scene_buf, horizontal_overlap,
|
||||
vertical_overlap, corner_size, true);
|
||||
break;
|
||||
case LAB_SSD_PART_CORNER_TOP_LEFT:
|
||||
x = -visible_shadow_width;
|
||||
y = -titlebar_height - visible_shadow_width;
|
||||
wlr_scene_node_set_position(part->node, x, y);
|
||||
corner_scale_crop(scene_buf, horizontal_overlap,
|
||||
vertical_overlap, corner_size, false);
|
||||
break;
|
||||
case LAB_SSD_PART_CORNER_TOP_RIGHT:
|
||||
x = width - inset + horizontal_overlap;
|
||||
y = -titlebar_height - visible_shadow_width;
|
||||
wlr_scene_node_set_position(part->node, x, y);
|
||||
corner_scale_crop(scene_buf, horizontal_overlap,
|
||||
vertical_overlap, corner_size, true);
|
||||
break;
|
||||
case LAB_SSD_PART_RIGHT:
|
||||
x = width;
|
||||
y = -titlebar_height + inset;
|
||||
wlr_scene_node_set_position(part->node, x, y);
|
||||
wlr_scene_buffer_set_dest_size(
|
||||
scene_buf, visible_shadow_width, height - 2 * inset);
|
||||
wlr_scene_node_set_enabled(part->node, show_sides);
|
||||
break;
|
||||
case LAB_SSD_PART_BOTTOM:
|
||||
x = inset;
|
||||
y = -titlebar_height + height;
|
||||
wlr_scene_node_set_position(part->node, x, y);
|
||||
wlr_scene_buffer_set_dest_size(
|
||||
scene_buf, width - 2 * inset, visible_shadow_width);
|
||||
wlr_scene_node_set_enabled(part->node, show_topbottom);
|
||||
break;
|
||||
case LAB_SSD_PART_LEFT:
|
||||
x = -visible_shadow_width;
|
||||
y = -titlebar_height + inset;
|
||||
wlr_scene_node_set_position(part->node, x, y);
|
||||
wlr_scene_buffer_set_dest_size(
|
||||
scene_buf, visible_shadow_width, height - 2 * inset);
|
||||
wlr_scene_node_set_enabled(part->node, show_sides);
|
||||
break;
|
||||
case LAB_SSD_PART_TOP:
|
||||
x = inset;
|
||||
y = -titlebar_height - visible_shadow_width;
|
||||
wlr_scene_node_set_position(part->node, x, y);
|
||||
wlr_scene_buffer_set_dest_size(
|
||||
scene_buf, width - 2 * inset, visible_shadow_width);
|
||||
wlr_scene_node_set_enabled(part->node, show_topbottom);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
set_shadow_geometry(struct ssd *ssd)
|
||||
{
|
||||
struct view *view = ssd->view;
|
||||
struct theme *theme = view->server->theme;
|
||||
int titlebar_height = ssd->titlebar.height;
|
||||
int width = view->current.width;
|
||||
int height = view_effective_height(view, false) + titlebar_height;
|
||||
|
||||
struct ssd_part *part;
|
||||
struct ssd_sub_tree *subtree;
|
||||
|
||||
FOR_EACH_STATE(ssd, subtree) {
|
||||
if (!subtree->tree) {
|
||||
/* Looks like this type of shadow is disabled */
|
||||
continue;
|
||||
}
|
||||
|
||||
bool active = subtree == &ssd->shadow.active;
|
||||
int visible_shadow_width = active
|
||||
? theme->window_active_shadow_size
|
||||
: theme->window_inactive_shadow_size;
|
||||
/* inset as a proportion of shadow width */
|
||||
double inset_proportion = SSD_SHADOW_INSET;
|
||||
/* inset in actual pixels */
|
||||
int inset = inset_proportion * (double)visible_shadow_width;
|
||||
|
||||
/*
|
||||
* Total size of corner buffers including inset and visible
|
||||
* portion. Top and bottom are the same size (only the cutout
|
||||
* is different). The buffers are square so width == height.
|
||||
*/
|
||||
int corner_size = active
|
||||
? theme->shadow_corner_top_active->unscaled_height
|
||||
: theme->shadow_corner_top_inactive->unscaled_height;
|
||||
|
||||
wl_list_for_each(part, &subtree->parts, link) {
|
||||
set_shadow_part_geometry(part, width, height,
|
||||
titlebar_height, corner_size, inset,
|
||||
visible_shadow_width);
|
||||
}
|
||||
} FOR_EACH_END
|
||||
}
|
||||
|
||||
static void
|
||||
make_shadow(struct wl_list *parts, enum ssd_part_type type,
|
||||
struct wlr_scene_tree *parent, struct wlr_buffer *buf,
|
||||
enum wl_output_transform tx)
|
||||
{
|
||||
struct ssd_part *part = add_scene_buffer(
|
||||
parts, type, parent, buf, 0, 0);
|
||||
struct wlr_scene_buffer *scene_buf =
|
||||
wlr_scene_buffer_from_node(part->node);
|
||||
wlr_scene_buffer_set_transform(scene_buf, tx);
|
||||
scene_buf->point_accepts_input = never_accepts_input;
|
||||
/*
|
||||
* Pixman has odd behaviour with bilinear filtering on buffers only one
|
||||
* pixel wide/tall. Use nearest-neighbour scaling to workaround.
|
||||
*/
|
||||
scene_buf->filter_mode = WLR_SCALE_FILTER_NEAREST;
|
||||
}
|
||||
|
||||
void
|
||||
ssd_shadow_create(struct ssd *ssd)
|
||||
{
|
||||
assert(ssd);
|
||||
assert(!ssd->shadow.tree);
|
||||
|
||||
ssd->shadow.tree = wlr_scene_tree_create(ssd->tree);
|
||||
|
||||
struct theme *theme = ssd->view->server->theme;
|
||||
struct wlr_buffer *corner_top_buffer;
|
||||
struct wlr_buffer *corner_bottom_buffer;
|
||||
struct wlr_buffer *edge_buffer;
|
||||
struct ssd_sub_tree *subtree;
|
||||
struct wlr_scene_tree *parent;
|
||||
|
||||
FOR_EACH_STATE(ssd, subtree) {
|
||||
if (subtree == &ssd->shadow.active) {
|
||||
if (theme->window_active_shadow_size == 0) {
|
||||
/* Active window shadows are disabled */
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (theme->window_inactive_shadow_size == 0) {
|
||||
/* Inactive window shadows are disabled */
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
subtree->tree = wlr_scene_tree_create(ssd->shadow.tree);
|
||||
parent = subtree->tree;
|
||||
if (subtree == &ssd->shadow.active) {
|
||||
corner_top_buffer = &theme->shadow_corner_top_active->base;
|
||||
corner_bottom_buffer = &theme->shadow_corner_bottom_active->base;
|
||||
edge_buffer = &theme->shadow_edge_active->base;
|
||||
} else {
|
||||
corner_top_buffer = &theme->shadow_corner_top_inactive->base;
|
||||
corner_bottom_buffer = &theme->shadow_corner_bottom_inactive->base;
|
||||
edge_buffer = &theme->shadow_edge_inactive->base;
|
||||
}
|
||||
wl_list_init(&subtree->parts);
|
||||
|
||||
make_shadow(&subtree->parts,
|
||||
LAB_SSD_PART_CORNER_BOTTOM_RIGHT, parent,
|
||||
corner_bottom_buffer, WL_OUTPUT_TRANSFORM_NORMAL);
|
||||
make_shadow(&subtree->parts, LAB_SSD_PART_CORNER_BOTTOM_LEFT,
|
||||
parent, corner_bottom_buffer, WL_OUTPUT_TRANSFORM_90);
|
||||
make_shadow(&subtree->parts, LAB_SSD_PART_CORNER_TOP_LEFT,
|
||||
parent, corner_top_buffer, WL_OUTPUT_TRANSFORM_180);
|
||||
make_shadow(&subtree->parts, LAB_SSD_PART_CORNER_TOP_RIGHT,
|
||||
parent, corner_top_buffer, WL_OUTPUT_TRANSFORM_270);
|
||||
make_shadow(&subtree->parts, LAB_SSD_PART_RIGHT, parent,
|
||||
edge_buffer, WL_OUTPUT_TRANSFORM_NORMAL);
|
||||
make_shadow(&subtree->parts, LAB_SSD_PART_BOTTOM, parent,
|
||||
edge_buffer, WL_OUTPUT_TRANSFORM_90);
|
||||
make_shadow(&subtree->parts, LAB_SSD_PART_LEFT, parent,
|
||||
edge_buffer, WL_OUTPUT_TRANSFORM_180);
|
||||
make_shadow(&subtree->parts, LAB_SSD_PART_TOP, parent,
|
||||
edge_buffer, WL_OUTPUT_TRANSFORM_270);
|
||||
|
||||
} FOR_EACH_END
|
||||
|
||||
ssd_shadow_update(ssd);
|
||||
}
|
||||
|
||||
void
|
||||
ssd_shadow_update(struct ssd *ssd)
|
||||
{
|
||||
assert(ssd);
|
||||
assert(ssd->shadow.tree);
|
||||
|
||||
struct view *view = ssd->view;
|
||||
bool maximized = view->maximized == VIEW_AXIS_BOTH;
|
||||
bool show_shadows =
|
||||
rc.shadows_enabled && !maximized && !view_is_tiled(ssd->view);
|
||||
wlr_scene_node_set_enabled(&ssd->shadow.tree->node, show_shadows);
|
||||
if (show_shadows) {
|
||||
set_shadow_geometry(ssd);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ssd_shadow_destroy(struct ssd *ssd)
|
||||
{
|
||||
assert(ssd);
|
||||
assert(ssd->shadow.tree);
|
||||
|
||||
wlr_scene_node_destroy(&ssd->shadow.tree->node);
|
||||
ssd->shadow.tree = NULL;
|
||||
}
|
||||
216
src/theme.c
216
src/theme.c
|
|
@ -26,6 +26,7 @@
|
|||
#include "common/match.h"
|
||||
#include "common/mem.h"
|
||||
#include "common/parse-bool.h"
|
||||
#include "common/parse-double.h"
|
||||
#include "common/string-helpers.h"
|
||||
#include "config/rcxml.h"
|
||||
#include "button/button-png.h"
|
||||
|
|
@ -502,6 +503,11 @@ theme_builtin(struct theme *theme, struct server *server)
|
|||
parse_hexstr("#000000",
|
||||
theme->window_inactive_button_close_unpressed_image_color);
|
||||
|
||||
theme->window_active_shadow_size = 60;
|
||||
theme->window_inactive_shadow_size = 40;
|
||||
parse_hexstr("#00000060", theme->window_active_shadow_color);
|
||||
parse_hexstr("#00000040", theme->window_inactive_shadow_color);
|
||||
|
||||
parse_hexstr("#fcfbfa", theme->menu_items_bg_color);
|
||||
parse_hexstr("#000000", theme->menu_items_text_color);
|
||||
parse_hexstr("#e1dedb", theme->menu_items_active_bg_color);
|
||||
|
|
@ -689,6 +695,30 @@ entry(struct theme *theme, const char *key, const char *value)
|
|||
theme->window_inactive_button_close_unpressed_image_color);
|
||||
}
|
||||
|
||||
/* window drop-shadows */
|
||||
if (match_glob(key, "window.active.shadow.size")) {
|
||||
theme->window_active_shadow_size = atoi(value);
|
||||
if (theme->window_active_shadow_size < 0) {
|
||||
wlr_log(WLR_ERROR, "window.active.shadow.size cannot "
|
||||
"be negative, clamping it to 0.");
|
||||
theme->window_active_shadow_size = 0;
|
||||
}
|
||||
}
|
||||
if (match_glob(key, "window.inactive.shadow.size")) {
|
||||
theme->window_inactive_shadow_size = atoi(value);
|
||||
if (theme->window_inactive_shadow_size < 0) {
|
||||
wlr_log(WLR_ERROR, "window.inactive.shadow.size cannot "
|
||||
"be negative, clamping it to 0.");
|
||||
theme->window_inactive_shadow_size = 0;
|
||||
}
|
||||
}
|
||||
if (match_glob(key, "window.active.shadow.color")) {
|
||||
parse_hexstr(value, theme->window_active_shadow_color);
|
||||
}
|
||||
if (match_glob(key, "window.inactive.shadow.color")) {
|
||||
parse_hexstr(value, theme->window_inactive_shadow_color);
|
||||
}
|
||||
|
||||
if (match_glob(key, "menu.width.min")) {
|
||||
theme->menu_min_width = atoi(value);
|
||||
}
|
||||
|
|
@ -1053,6 +1083,185 @@ create_corners(struct theme *theme)
|
|||
theme->corner_top_right_inactive_normal = rounded_rect(&ctx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Draw the buffer used to render the edges of window drop-shadows. The buffer
|
||||
* is 1 pixel tall and `visible_size` pixels wide and can be rotated and scaled for the
|
||||
* different edges. The buffer is drawn as would be found at the right-hand
|
||||
* edge of a window. The gradient has a color of `start_color` at its left edge
|
||||
* fading to clear at its right edge.
|
||||
*/
|
||||
static void
|
||||
shadow_edge_gradient(struct lab_data_buffer *buffer,
|
||||
int visible_size, int total_size, float start_color[4])
|
||||
{
|
||||
if (!buffer) {
|
||||
/* This type of shadow is disabled, do nothing */
|
||||
return;
|
||||
}
|
||||
|
||||
assert(buffer->format == DRM_FORMAT_ARGB8888);
|
||||
uint8_t *pixels = buffer->data;
|
||||
|
||||
/* Inset portion which is obscured */
|
||||
int inset = total_size - visible_size;
|
||||
|
||||
/* Standard deviation normalised against the shadow width, squared */
|
||||
double variance = 0.3 * 0.3;
|
||||
|
||||
for (int x = 0; x < visible_size; x++) {
|
||||
/*
|
||||
* x normalised against total shadow width. We add on inset here
|
||||
* because we don't bother drawing inset for the edge shadow
|
||||
* buffers but still need the pattern to line up with the corner
|
||||
* shadow buffers which do have inset drawn.
|
||||
*/
|
||||
double xn = (double)(x + inset) / (double)total_size;
|
||||
|
||||
/* Gausian dropoff */
|
||||
double alpha = exp(-(xn * xn) / variance);
|
||||
|
||||
/* RGBA values are all pre-multiplied */
|
||||
pixels[4 * x] = start_color[2] * alpha * 255;
|
||||
pixels[4 * x + 1] = start_color[1] * alpha * 255;
|
||||
pixels[4 * x + 2] = start_color[0] * alpha * 255;
|
||||
pixels[4 * x + 3] = start_color[3] * alpha * 255;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Draw the buffer used to render the corners of window drop-shadows. The
|
||||
* shadow looks better if the buffer is inset behind the window, so the buffer
|
||||
* is square with a size of radius+inset. The buffer is drawn for the
|
||||
* bottom-right corner but can be rotated for other corners. The gradient fades
|
||||
* from `start_color` at the top-left to clear at the opposite edge.
|
||||
*
|
||||
* If the window is translucent we don't want the shadow to be visible through
|
||||
* it. For the bottom corners of the window this is easy, we just erase the
|
||||
* square of the buffer which will be behind the window. For the top it's a
|
||||
* little more complicated because the titlebar can have rounded corners.
|
||||
* However, the titlebar itself is always opaque so we only have to erase the
|
||||
* L-shaped area of the buffer which can appear behind the non-titlebar part of
|
||||
* the window.
|
||||
*/
|
||||
static void
|
||||
shadow_corner_gradient(struct lab_data_buffer *buffer, int visible_size,
|
||||
int total_size, int titlebar_height, float start_color[4])
|
||||
{
|
||||
if (!buffer) {
|
||||
/* This type of shadow is disabled, do nothing */
|
||||
return;
|
||||
}
|
||||
|
||||
assert(buffer->format == DRM_FORMAT_ARGB8888);
|
||||
uint8_t *pixels = buffer->data;
|
||||
|
||||
/* Standard deviation normalised against the shadow width, squared */
|
||||
double variance = 0.3 * 0.3;
|
||||
|
||||
int inset = total_size - visible_size;
|
||||
|
||||
for (int y = 0; y < total_size; y++) {
|
||||
uint8_t *pixel_row = &pixels[y * buffer->stride];
|
||||
for (int x = 0; x < total_size; x++) {
|
||||
/* x and y normalised against total shadow width */
|
||||
double x_norm = (double)(x) / (double)total_size;
|
||||
double y_norm = (double)(y) / (double)total_size;
|
||||
/*
|
||||
* For Gaussian drop-off in 2d you can just calculate
|
||||
* the outer product of the horizontal and vertical
|
||||
* profiles.
|
||||
*/
|
||||
double gauss_x = exp(-(x_norm * x_norm) / variance);
|
||||
double gauss_y = exp(-(y_norm * y_norm) / variance);
|
||||
double alpha = gauss_x * gauss_y;
|
||||
|
||||
/*
|
||||
* Erase the L-shaped region which could be visible
|
||||
* through a transparent window but not obscured by the
|
||||
* titlebar. If inset is smaller than the titlebar
|
||||
* height then there's nothing to do, this is handled by
|
||||
* (inset - titlebar_height) being negative.
|
||||
*/
|
||||
bool in1 = x < inset && y < inset - titlebar_height;
|
||||
bool in2 = x < inset - titlebar_height && y < inset;
|
||||
if (in1 || in2) {
|
||||
alpha = 0.0;
|
||||
}
|
||||
|
||||
/* RGBA values are all pre-multiplied */
|
||||
pixel_row[4 * x] = start_color[2] * alpha * 255;
|
||||
pixel_row[4 * x + 1] = start_color[1] * alpha * 255;
|
||||
pixel_row[4 * x + 2] = start_color[0] * alpha * 255;
|
||||
pixel_row[4 * x + 3] = start_color[3] * alpha * 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
create_shadows(struct theme *theme)
|
||||
{
|
||||
/* Size of shadow visible extending beyond the window */
|
||||
int visible_active_size = theme->window_active_shadow_size;
|
||||
int visible_inactive_size = theme->window_inactive_shadow_size;
|
||||
/* How far inside the window the shadow inset begins */
|
||||
int inset_active = (double)visible_active_size * SSD_SHADOW_INSET;
|
||||
int inset_inactive = (double)visible_inactive_size * SSD_SHADOW_INSET;
|
||||
/* Total width including visible and obscured portion */
|
||||
int total_active_size = visible_active_size + inset_active;
|
||||
int total_inactive_size = visible_inactive_size + inset_inactive;
|
||||
|
||||
/*
|
||||
* Edge shadows don't need to be inset so the buffers are sized just for
|
||||
* the visible width. Corners are inset so the buffers are larger for
|
||||
* this.
|
||||
*/
|
||||
if (visible_active_size > 0) {
|
||||
theme->shadow_edge_active = buffer_create_cairo(
|
||||
visible_active_size, 1, 1.0, true);
|
||||
theme->shadow_corner_top_active = buffer_create_cairo(
|
||||
total_active_size, total_active_size, 1.0, true);
|
||||
theme->shadow_corner_bottom_active = buffer_create_cairo(
|
||||
total_active_size, total_active_size, 1.0, true);
|
||||
if (!theme->shadow_corner_top_active
|
||||
|| !theme->shadow_corner_bottom_active
|
||||
|| !theme->shadow_edge_active) {
|
||||
wlr_log(WLR_ERROR, "Failed to allocate shadow buffer");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (visible_inactive_size > 0) {
|
||||
theme->shadow_edge_inactive = buffer_create_cairo(
|
||||
visible_inactive_size, 1, 1.0, true);
|
||||
theme->shadow_corner_top_inactive = buffer_create_cairo(
|
||||
total_inactive_size, total_inactive_size, 1.0, true);
|
||||
theme->shadow_corner_bottom_inactive = buffer_create_cairo(
|
||||
total_inactive_size, total_inactive_size, 1.0, true);
|
||||
if (!theme->shadow_corner_top_inactive
|
||||
|| !theme->shadow_corner_bottom_inactive
|
||||
|| !theme->shadow_edge_inactive) {
|
||||
wlr_log(WLR_ERROR, "Failed to allocate shadow buffer");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
shadow_edge_gradient(theme->shadow_edge_active, visible_active_size,
|
||||
total_active_size, theme->window_active_shadow_color);
|
||||
shadow_edge_gradient(theme->shadow_edge_inactive, visible_inactive_size,
|
||||
total_inactive_size, theme->window_inactive_shadow_color);
|
||||
shadow_corner_gradient(theme->shadow_corner_top_active,
|
||||
visible_active_size, total_active_size,
|
||||
theme->title_height, theme->window_active_shadow_color);
|
||||
shadow_corner_gradient(theme->shadow_corner_bottom_active,
|
||||
visible_active_size, total_active_size, 0,
|
||||
theme->window_active_shadow_color);
|
||||
shadow_corner_gradient(theme->shadow_corner_top_inactive,
|
||||
visible_inactive_size, total_inactive_size,
|
||||
theme->title_height, theme->window_inactive_shadow_color);
|
||||
shadow_corner_gradient(theme->shadow_corner_bottom_inactive,
|
||||
visible_inactive_size, total_inactive_size, 0,
|
||||
theme->window_inactive_shadow_color);
|
||||
}
|
||||
|
||||
static void
|
||||
fill_colors_with_osd_theme(struct theme *theme, float colors[3][4])
|
||||
{
|
||||
|
|
@ -1172,6 +1381,7 @@ theme_init(struct theme *theme, struct server *server, const char *theme_name)
|
|||
post_processing(theme);
|
||||
create_corners(theme);
|
||||
load_buttons(theme);
|
||||
create_shadows(theme);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -1181,4 +1391,10 @@ theme_finish(struct theme *theme)
|
|||
zdrop(&theme->corner_top_left_inactive_normal);
|
||||
zdrop(&theme->corner_top_right_active_normal);
|
||||
zdrop(&theme->corner_top_right_inactive_normal);
|
||||
zdrop(&theme->shadow_corner_top_active);
|
||||
zdrop(&theme->shadow_corner_bottom_active);
|
||||
zdrop(&theme->shadow_edge_active);
|
||||
zdrop(&theme->shadow_corner_top_inactive);
|
||||
zdrop(&theme->shadow_corner_bottom_inactive);
|
||||
zdrop(&theme->shadow_edge_inactive);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue