mirror of
https://github.com/labwc/labwc.git
synced 2025-10-29 05:40:24 -04:00
These are just lists of enum lab_node_type, with a bounded size and no middle-insertions/removals, so linked lists are overkill. Also, the use of wl_list_for_each[_reverse] just to access the first or last entry in the list (corner button) was weird.
1824 lines
59 KiB
C
1824 lines
59 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Theme engine for labwc
|
|
*
|
|
* Copyright (C) Johan Malm 2020-2023
|
|
*/
|
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
#include "theme.h"
|
|
#include <assert.h>
|
|
#include <cairo.h>
|
|
#include <drm_fourcc.h>
|
|
#include <glib.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <wlr/util/box.h>
|
|
#include <wlr/util/log.h>
|
|
#include <wlr/render/pixman.h>
|
|
#include <strings.h>
|
|
#include "common/macros.h"
|
|
#include "common/dir.h"
|
|
#include "common/font.h"
|
|
#include "common/graphic-helpers.h"
|
|
#include "common/match.h"
|
|
#include "common/mem.h"
|
|
#include "common/parse-bool.h"
|
|
#include "common/string-helpers.h"
|
|
#include "config/rcxml.h"
|
|
#include "img/img.h"
|
|
#include "labwc.h"
|
|
#include "buffer.h"
|
|
#include "ssd.h"
|
|
|
|
struct button {
|
|
const char *name;
|
|
const char *alt_name;
|
|
const char *fallback_button; /* built-in 6x6 button */
|
|
enum lab_node_type type;
|
|
uint8_t state_set;
|
|
};
|
|
|
|
enum rounded_corner {
|
|
ROUNDED_CORNER_TOP_LEFT,
|
|
ROUNDED_CORNER_TOP_RIGHT
|
|
};
|
|
|
|
struct rounded_corner_ctx {
|
|
struct wlr_box *box;
|
|
double radius;
|
|
double line_width;
|
|
cairo_pattern_t *fill_pattern;
|
|
float *border_color;
|
|
enum rounded_corner corner;
|
|
};
|
|
|
|
#define zero_array(arr) memset(arr, 0, sizeof(arr))
|
|
|
|
static struct lab_data_buffer *rounded_rect(struct rounded_corner_ctx *ctx);
|
|
|
|
/* 1 degree in radians (=2π/360) */
|
|
static const double deg = 0.017453292519943295;
|
|
|
|
static void
|
|
zdrop(struct lab_data_buffer **buffer)
|
|
{
|
|
if (*buffer) {
|
|
wlr_buffer_drop(&(*buffer)->base);
|
|
*buffer = NULL;
|
|
}
|
|
}
|
|
|
|
/* Draw rounded-rectangular hover overlay on the button buffer */
|
|
static void
|
|
draw_hover_overlay_on_button(cairo_t *cairo, int w, int h)
|
|
{
|
|
/* Overlay (pre-multiplied alpha) */
|
|
float overlay_color[4] = { 0.15f, 0.15f, 0.15f, 0.3f};
|
|
set_cairo_color(cairo, overlay_color);
|
|
int r = rc.theme->window_button_hover_bg_corner_radius;
|
|
|
|
cairo_new_sub_path(cairo);
|
|
cairo_arc(cairo, r, r, r, 180 * deg, 270 * deg);
|
|
cairo_line_to(cairo, w - r, 0);
|
|
cairo_arc(cairo, w - r, r, r, -90 * deg, 0 * deg);
|
|
cairo_line_to(cairo, w, h - r);
|
|
cairo_arc(cairo, w - r, h - r, r, 0 * deg, 90 * deg);
|
|
cairo_line_to(cairo, r, h);
|
|
cairo_arc(cairo, r, h - r, r, 90 * deg, 180 * deg);
|
|
cairo_close_path(cairo);
|
|
cairo_fill(cairo);
|
|
}
|
|
|
|
/* Round the buffer for the leftmost button in the titlebar */
|
|
static void
|
|
round_left_corner_button(cairo_t *cairo, int w, int h)
|
|
{
|
|
/*
|
|
* Position of the topleft corner of the titlebar relative to the
|
|
* leftmost button
|
|
*/
|
|
double x = -rc.theme->window_titlebar_padding_width;
|
|
double y = -(rc.theme->titlebar_height - rc.theme->window_button_height) / 2;
|
|
|
|
double r = rc.corner_radius - (double)rc.theme->border_width / 2.0;
|
|
|
|
cairo_new_sub_path(cairo);
|
|
cairo_arc(cairo, x + r, y + r, r, deg * 180, deg * 270);
|
|
cairo_line_to(cairo, w, y);
|
|
cairo_line_to(cairo, w, h);
|
|
cairo_line_to(cairo, x, h);
|
|
cairo_close_path(cairo);
|
|
|
|
cairo_set_source_rgba(cairo, 1, 1, 1, 1);
|
|
cairo_set_operator(cairo, CAIRO_OPERATOR_DEST_IN);
|
|
cairo_fill(cairo);
|
|
}
|
|
|
|
/* Round the buffer for the rightmost button in the titlebar */
|
|
static void
|
|
round_right_corner_button(cairo_t *cairo, int w, int h)
|
|
{
|
|
/*
|
|
* Horizontally flip the cairo context so we can reuse
|
|
* round_left_corner_button() for rounding the rightmost button.
|
|
*/
|
|
cairo_scale(cairo, -1, 1);
|
|
cairo_translate(cairo, -w, 0);
|
|
round_left_corner_button(cairo, w, h);
|
|
}
|
|
|
|
/*
|
|
* Scan theme directories with button names (name + postfix) and write the full
|
|
* path of the found button file to @buf. An empty string is set if a button
|
|
* file is not found.
|
|
*/
|
|
static void
|
|
get_button_filename(char *buf, size_t len, const char *name, const char *postfix)
|
|
{
|
|
buf[0] = '\0';
|
|
|
|
char filename[4096];
|
|
snprintf(filename, sizeof(filename), "%s%s", name, postfix);
|
|
|
|
struct wl_list paths;
|
|
paths_theme_create(&paths, rc.theme_name, filename);
|
|
|
|
/*
|
|
* You can't really merge buttons, so let's just iterate forwards
|
|
* and stop on the first hit
|
|
*/
|
|
struct path *path;
|
|
wl_list_for_each(path, &paths, link) {
|
|
if (access(path->string, R_OK) == 0) {
|
|
snprintf(buf, len, "%s", path->string);
|
|
break;
|
|
}
|
|
}
|
|
paths_destroy(&paths);
|
|
}
|
|
|
|
static void
|
|
load_button(struct theme *theme, struct button *b, int active)
|
|
{
|
|
struct lab_img *(*button_imgs)[LAB_BS_ALL + 1] =
|
|
theme->window[active].button_imgs;
|
|
struct lab_img **img = &button_imgs[b->type][b->state_set];
|
|
float *rgba = theme->window[active].button_colors[b->type];
|
|
char filename[4096];
|
|
|
|
assert(!*img);
|
|
|
|
/* PNG */
|
|
get_button_filename(filename, sizeof(filename), b->name,
|
|
active ? "-active.png" : "-inactive.png");
|
|
*img = lab_img_load(LAB_IMG_PNG, filename, rgba);
|
|
|
|
#if HAVE_RSVG
|
|
/* SVG */
|
|
if (!*img) {
|
|
get_button_filename(filename, sizeof(filename), b->name,
|
|
active ? "-active.svg" : "-inactive.svg");
|
|
*img = lab_img_load(LAB_IMG_SVG, filename, rgba);
|
|
}
|
|
#endif
|
|
|
|
/* XBM */
|
|
if (!*img) {
|
|
get_button_filename(filename, sizeof(filename), b->name, ".xbm");
|
|
*img = lab_img_load(LAB_IMG_XBM, filename, rgba);
|
|
}
|
|
|
|
/*
|
|
* XBM (alternative name)
|
|
* For example max_hover_toggled instead of max_toggled_hover
|
|
*/
|
|
if (!*img && b->alt_name) {
|
|
get_button_filename(filename, sizeof(filename),
|
|
b->alt_name, ".xbm");
|
|
*img = lab_img_load(LAB_IMG_XBM, filename, rgba);
|
|
}
|
|
|
|
/*
|
|
* Builtin bitmap
|
|
*
|
|
* Applicable to basic buttons such as max, max_toggled and iconify.
|
|
* There are no bitmap fallbacks for *_hover icons.
|
|
*/
|
|
if (!*img && b->fallback_button) {
|
|
*img = lab_img_load_from_bitmap(b->fallback_button, rgba);
|
|
}
|
|
|
|
/*
|
|
* If hover-icons do not exist, add fallbacks by copying the non-hover
|
|
* variant and then adding an overlay.
|
|
*/
|
|
if (!*img && (b->state_set & LAB_BS_HOVERED)) {
|
|
struct lab_img *non_hover_img =
|
|
button_imgs[b->type][b->state_set & ~LAB_BS_HOVERED];
|
|
*img = lab_img_copy(non_hover_img);
|
|
lab_img_add_modifier(*img,
|
|
draw_hover_overlay_on_button);
|
|
}
|
|
|
|
/*
|
|
* If the loaded button is at the corner of the titlebar, also create
|
|
* rounded variants.
|
|
*/
|
|
struct lab_img **rounded_img =
|
|
&button_imgs[b->type][b->state_set | LAB_BS_ROUNDED];
|
|
|
|
if (rc.nr_title_buttons_left > 0
|
|
&& b->type == rc.title_buttons_left[0]) {
|
|
*rounded_img = lab_img_copy(*img);
|
|
lab_img_add_modifier(*rounded_img, round_left_corner_button);
|
|
}
|
|
if (rc.nr_title_buttons_right > 0
|
|
&& b->type == rc.title_buttons_right
|
|
[rc.nr_title_buttons_right - 1]) {
|
|
*rounded_img = lab_img_copy(*img);
|
|
lab_img_add_modifier(*rounded_img, round_right_corner_button);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We use the following button filename schema: "BUTTON [TOGGLED] [STATE]"
|
|
* with the words separated by underscore, and the following meaning:
|
|
* - BUTTON can be one of 'max', 'iconify', 'close', 'menu'
|
|
* - TOGGLED is either 'toggled' or nothing
|
|
* - STATE is 'hover' or nothing. In future, 'pressed' may be supported too.
|
|
*
|
|
* We believe that this is how the vast majority of extant openbox themes out
|
|
* there are constructed and it is consistent with the openbox.org wiki. But
|
|
* please be aware that it is actually different to vanilla Openbox which uses:
|
|
* "BUTTON [STATE] [TOGGLED]" following an unfortunate commit in 2014 which
|
|
* broke themes and led to some distros patching Openbox:
|
|
* https://github.com/danakj/openbox/commit/35e92e4c2a45b28d5c2c9b44b64aeb4222098c94
|
|
*
|
|
* Arch Linux and Debian patch Openbox to keep the old syntax (the one we use).
|
|
* https://gitlab.archlinux.org/archlinux/packaging/packages/openbox/-/blob/main/debian-887908.patch?ref_type=heads
|
|
* This patch does the following:
|
|
* - reads "%s_toggled_pressed.xbm" and "%s_toggled_hover.xbm" instead of the
|
|
* 'hover_toggled' equivalents.
|
|
* - parses 'toggled.unpressed', toggled.pressed' and 'toggled.hover' instead
|
|
* of the other way around ('*.toggled') when processing themerc.
|
|
*
|
|
* For compatibility with distros which do not apply similar patches, we support
|
|
* the hover-before-toggle too, for example:
|
|
*
|
|
* .name = "max_toggled_hover",
|
|
* .alt_name = "max_hover_toggled",
|
|
*
|
|
* ...in the button array definition below.
|
|
*/
|
|
static void
|
|
load_buttons(struct theme *theme)
|
|
{
|
|
struct button buttons[] = { {
|
|
.name = "menu",
|
|
.fallback_button = (const char[]){ 0x00, 0x21, 0x33, 0x1E, 0x0C, 0x00 },
|
|
.type = LAB_NODE_BUTTON_WINDOW_MENU,
|
|
.state_set = 0,
|
|
}, {
|
|
.name = "iconify",
|
|
.fallback_button = (const char[]){ 0x00, 0x00, 0x00, 0x00, 0x3f, 0x3f },
|
|
.type = LAB_NODE_BUTTON_ICONIFY,
|
|
.state_set = 0,
|
|
}, {
|
|
.name = "max",
|
|
.fallback_button = (const char[]){ 0x3f, 0x3f, 0x21, 0x21, 0x21, 0x3f },
|
|
.type = LAB_NODE_BUTTON_MAXIMIZE,
|
|
.state_set = 0,
|
|
}, {
|
|
.name = "max_toggled",
|
|
.fallback_button = (const char[]){ 0x3e, 0x22, 0x2f, 0x29, 0x39, 0x0f },
|
|
.type = LAB_NODE_BUTTON_MAXIMIZE,
|
|
.state_set = LAB_BS_TOGGLED,
|
|
}, {
|
|
.name = "shade",
|
|
.fallback_button = (const char[]){ 0x3f, 0x3f, 0x00, 0x0c, 0x1e, 0x3f },
|
|
.type = LAB_NODE_BUTTON_SHADE,
|
|
.state_set = 0,
|
|
}, {
|
|
.name = "shade_toggled",
|
|
.fallback_button = (const char[]){ 0x3f, 0x3f, 0x00, 0x3f, 0x1e, 0x0c },
|
|
.type = LAB_NODE_BUTTON_SHADE,
|
|
.state_set = LAB_BS_TOGGLED,
|
|
}, {
|
|
.name = "desk",
|
|
.fallback_button = (const char[]){ 0x33, 0x33, 0x00, 0x00, 0x33, 0x33 },
|
|
.type = LAB_NODE_BUTTON_OMNIPRESENT,
|
|
.state_set = 0,
|
|
}, {
|
|
.name = "desk_toggled",
|
|
.fallback_button = (const char[]){ 0x00, 0x1e, 0x1a, 0x16, 0x1e, 0x00 },
|
|
.type = LAB_NODE_BUTTON_OMNIPRESENT,
|
|
.state_set = LAB_BS_TOGGLED,
|
|
}, {
|
|
.name = "close",
|
|
.fallback_button = (const char[]){ 0x33, 0x3f, 0x1e, 0x1e, 0x3f, 0x33 },
|
|
.type = LAB_NODE_BUTTON_CLOSE,
|
|
.state_set = 0,
|
|
}, {
|
|
.name = "menu_hover",
|
|
.type = LAB_NODE_BUTTON_WINDOW_MENU,
|
|
.state_set = LAB_BS_HOVERED,
|
|
/* no fallback (non-hover variant is used instead) */
|
|
}, {
|
|
.name = "iconify_hover",
|
|
.type = LAB_NODE_BUTTON_ICONIFY,
|
|
.state_set = LAB_BS_HOVERED,
|
|
/* no fallback (non-hover variant is used instead) */
|
|
}, {
|
|
.name = "max_hover",
|
|
.type = LAB_NODE_BUTTON_MAXIMIZE,
|
|
.state_set = LAB_BS_HOVERED,
|
|
/* no fallback (non-hover variant is used instead) */
|
|
}, {
|
|
.name = "max_toggled_hover",
|
|
.alt_name = "max_hover_toggled",
|
|
.type = LAB_NODE_BUTTON_MAXIMIZE,
|
|
.state_set = LAB_BS_TOGGLED | LAB_BS_HOVERED,
|
|
/* no fallback (non-hover variant is used instead) */
|
|
}, {
|
|
.name = "shade_hover",
|
|
.type = LAB_NODE_BUTTON_SHADE,
|
|
.state_set = LAB_BS_HOVERED,
|
|
/* no fallback (non-hover variant is used instead) */
|
|
}, {
|
|
.name = "shade_toggled_hover",
|
|
.alt_name = "shade_hover_toggled",
|
|
.type = LAB_NODE_BUTTON_SHADE,
|
|
.state_set = LAB_BS_TOGGLED | LAB_BS_HOVERED,
|
|
/* no fallback (non-hover variant is used instead) */
|
|
}, {
|
|
.name = "desk_hover",
|
|
/* no fallback (non-hover variant is used instead) */
|
|
.type = LAB_NODE_BUTTON_OMNIPRESENT,
|
|
.state_set = LAB_BS_HOVERED,
|
|
}, {
|
|
.name = "desk_toggled_hover",
|
|
.alt_name = "desk_hover_toggled",
|
|
.type = LAB_NODE_BUTTON_OMNIPRESENT,
|
|
.state_set = LAB_BS_TOGGLED | LAB_BS_HOVERED,
|
|
/* no fallback (non-hover variant is used instead) */
|
|
}, {
|
|
.name = "close_hover",
|
|
.type = LAB_NODE_BUTTON_CLOSE,
|
|
.state_set = LAB_BS_HOVERED,
|
|
/* no fallback (non-hover variant is used instead) */
|
|
}, };
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(buttons); ++i) {
|
|
struct button *b = &buttons[i];
|
|
load_button(theme, b, THEME_INACTIVE);
|
|
load_button(theme, b, THEME_ACTIVE);
|
|
}
|
|
}
|
|
|
|
static int
|
|
hex_to_dec(char c)
|
|
{
|
|
if (c >= '0' && c <= '9') {
|
|
return c - '0';
|
|
}
|
|
if (c >= 'a' && c <= 'f') {
|
|
return c - 'a' + 10;
|
|
}
|
|
if (c >= 'A' && c <= 'F') {
|
|
return c - 'A' + 10;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* parse_hexstr - parse #rrggbb
|
|
* @hex: hex string to be parsed
|
|
* @rgba: pointer to float[4] for return value
|
|
*/
|
|
static void
|
|
parse_hexstr(const char *hex, float *rgba)
|
|
{
|
|
if (hex[0] != '#') {
|
|
return;
|
|
}
|
|
|
|
size_t len = strlen(hex);
|
|
if (len == 4) {
|
|
/* #fff is shorthand for #f0f0f0, per theme spec */
|
|
rgba[0] = (hex_to_dec(hex[1]) * 16) / 255.0;
|
|
rgba[1] = (hex_to_dec(hex[2]) * 16) / 255.0;
|
|
rgba[2] = (hex_to_dec(hex[3]) * 16) / 255.0;
|
|
} else if (len >= 7) {
|
|
rgba[0] = (hex_to_dec(hex[1]) * 16 + hex_to_dec(hex[2])) / 255.0;
|
|
rgba[1] = (hex_to_dec(hex[3]) * 16 + hex_to_dec(hex[4])) / 255.0;
|
|
rgba[2] = (hex_to_dec(hex[5]) * 16 + hex_to_dec(hex[6])) / 255.0;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
rgba[3] = 1.0;
|
|
|
|
if (len >= 9 && hex[7] == ' ') {
|
|
/* Deprecated #aabbcc 100 alpha encoding to support openbox themes */
|
|
rgba[3] = atoi(hex + 8) / 100.0;
|
|
wlr_log(WLR_ERROR,
|
|
"The theme uses deprecated alpha notation %s, please convert to "
|
|
"#rrggbbaa to ensure your config works on newer labwc releases", hex);
|
|
} else if (len == 9) {
|
|
/* Inline alpha encoding like #aabbccff */
|
|
rgba[3] = (hex_to_dec(hex[7]) * 16 + hex_to_dec(hex[8])) / 255.0;
|
|
} else if (len > 7) {
|
|
/* More than just #aabbcc */
|
|
wlr_log(WLR_ERROR, "invalid alpha color encoding: '%s'", hex);
|
|
}
|
|
|
|
/* Pre-multiply everything as expected by wlr_scene */
|
|
rgba[0] *= rgba[3];
|
|
rgba[1] *= rgba[3];
|
|
rgba[2] *= rgba[3];
|
|
}
|
|
|
|
static void
|
|
parse_hexstrs(const char *hexes, float colors[3][4])
|
|
{
|
|
gchar **elements = g_strsplit(hexes, ",", -1);
|
|
for (int i = 0; elements[i] && i < 3; i++) {
|
|
parse_hexstr(elements[i], colors[i]);
|
|
}
|
|
g_strfreev(elements);
|
|
}
|
|
|
|
static void
|
|
parse_color(const char *str, float *rgba)
|
|
{
|
|
if (str[0] == '#') {
|
|
parse_hexstr(str, rgba);
|
|
} else if (!strncasecmp(str, "rgb:", 4)) {
|
|
size_t len = strlen(str);
|
|
if (len == 9 && str[5] == '/' && str[7] == '/') {
|
|
/* rgb:R/G/B */
|
|
rgba[0] = (hex_to_dec(str[4]) * 16) / 255.0;
|
|
rgba[1] = (hex_to_dec(str[6]) * 16) / 255.0;
|
|
rgba[2] = (hex_to_dec(str[8]) * 16) / 255.0;
|
|
rgba[3] = 1.0;
|
|
} else if (len == 12 && str[6] == '/' && str[9] == '/') {
|
|
/* rgb:RR/GG/BB */
|
|
rgba[0] = (hex_to_dec(str[4]) * 16 + hex_to_dec(str[5])) / 255.0;
|
|
rgba[1] = (hex_to_dec(str[7]) * 16 + hex_to_dec(str[8])) / 255.0;
|
|
rgba[2] = (hex_to_dec(str[10]) * 16 + hex_to_dec(str[11])) / 255.0;
|
|
rgba[3] = 1.0;
|
|
}
|
|
} else {
|
|
uint32_t argb = 0;
|
|
if (lookup_named_color(str, &argb)) {
|
|
rgba[0] = ((argb >> 16) & 0xFF) / 255.0;
|
|
rgba[1] = ((argb >> 8) & 0xFF) / 255.0;
|
|
rgba[2] = (argb & 0xFF) / 255.0;
|
|
rgba[3] = 1.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static enum lab_gradient
|
|
parse_gradient(const char *str)
|
|
{
|
|
/*
|
|
* Parsing of "texture" strings is very loose, following Openbox:
|
|
* just a case-insensitive match of substrings of interest, with
|
|
* no regard for ordering nor whitespace.
|
|
*/
|
|
char *lower = g_ascii_strdown(str, -1);
|
|
enum lab_gradient gradient = LAB_GRADIENT_NONE;
|
|
|
|
if (strstr(lower, "gradient")) {
|
|
if (strstr(lower, "splitvertical")) {
|
|
gradient = LAB_GRADIENT_SPLITVERTICAL;
|
|
} else if (strstr(lower, "vertical")) {
|
|
gradient = LAB_GRADIENT_VERTICAL;
|
|
}
|
|
}
|
|
|
|
g_free(lower);
|
|
return gradient;
|
|
}
|
|
|
|
static enum lab_justification
|
|
parse_justification(const char *str)
|
|
{
|
|
if (!strcasecmp(str, "Center")) {
|
|
return LAB_JUSTIFY_CENTER;
|
|
} else if (!strcasecmp(str, "Right")) {
|
|
return LAB_JUSTIFY_RIGHT;
|
|
} else {
|
|
return LAB_JUSTIFY_LEFT;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We generally use Openbox defaults, but if no theme file can be found it's
|
|
* better to populate the theme variables with some sane values as no-one
|
|
* wants to use openbox without a theme - it'll all just be black and white.
|
|
*
|
|
* Openbox doesn't actual start if it can't find a theme. As it's normally
|
|
* packaged with Clearlooks, this is not a problem, but for labwc I thought
|
|
* this was a bit hard-line. People might want to try labwc without having
|
|
* Openbox (and associated themes) installed.
|
|
*
|
|
* theme_builtin() applies a theme that is similar to vanilla GTK
|
|
*/
|
|
static void
|
|
theme_builtin(struct theme *theme, struct server *server)
|
|
{
|
|
theme->border_width = 1;
|
|
theme->window_titlebar_padding_height = 0;
|
|
theme->window_titlebar_padding_width = 0;
|
|
|
|
parse_hexstr("#aaaaaa", theme->window[THEME_ACTIVE].border_color);
|
|
parse_hexstr("#aaaaaa", theme->window[THEME_INACTIVE].border_color);
|
|
|
|
parse_hexstr("#ff0000", theme->window_toggled_keybinds_color);
|
|
|
|
theme->window[THEME_ACTIVE].title_bg.gradient = LAB_GRADIENT_NONE;
|
|
theme->window[THEME_INACTIVE].title_bg.gradient = LAB_GRADIENT_NONE;
|
|
parse_hexstr("#e1dedb", theme->window[THEME_ACTIVE].title_bg.color);
|
|
parse_hexstr("#f6f5f4", theme->window[THEME_INACTIVE].title_bg.color);
|
|
theme->window[THEME_ACTIVE].title_bg.color_split_to[0] = FLT_MIN;
|
|
theme->window[THEME_INACTIVE].title_bg.color_split_to[0] = FLT_MIN;
|
|
theme->window[THEME_ACTIVE].title_bg.color_to[0] = FLT_MIN;
|
|
theme->window[THEME_INACTIVE].title_bg.color_to[0] = FLT_MIN;
|
|
theme->window[THEME_ACTIVE].title_bg.color_to_split_to[0] = FLT_MIN;
|
|
theme->window[THEME_INACTIVE].title_bg.color_to_split_to[0] = FLT_MIN;
|
|
|
|
parse_hexstr("#000000", theme->window[THEME_ACTIVE].label_text_color);
|
|
parse_hexstr("#000000", theme->window[THEME_INACTIVE].label_text_color);
|
|
theme->window_label_text_justify = parse_justification("Center");
|
|
|
|
theme->window_button_width = 26;
|
|
theme->window_button_height = 26;
|
|
theme->window_button_spacing = 0;
|
|
theme->window_button_hover_bg_corner_radius = 0;
|
|
|
|
for (enum lab_node_type type = LAB_NODE_BUTTON_FIRST;
|
|
type <= LAB_NODE_BUTTON_LAST; type++) {
|
|
parse_hexstr("#000000",
|
|
theme->window[THEME_INACTIVE].button_colors[type]);
|
|
parse_hexstr("#000000",
|
|
theme->window[THEME_ACTIVE].button_colors[type]);
|
|
}
|
|
|
|
theme->window[THEME_ACTIVE].shadow_size = 60;
|
|
theme->window[THEME_INACTIVE].shadow_size = 40;
|
|
parse_hexstr("#00000060", theme->window[THEME_ACTIVE].shadow_color);
|
|
parse_hexstr("#00000040", theme->window[THEME_INACTIVE].shadow_color);
|
|
|
|
theme->menu_overlap_x = 0;
|
|
theme->menu_overlap_y = 0;
|
|
theme->menu_min_width = 20;
|
|
theme->menu_max_width = 200;
|
|
theme->menu_border_width = INT_MIN;
|
|
theme->menu_border_color[0] = FLT_MIN;
|
|
|
|
theme->menu_items_padding_x = 7;
|
|
theme->menu_items_padding_y = 4;
|
|
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);
|
|
parse_hexstr("#000000", theme->menu_items_active_text_color);
|
|
|
|
theme->menu_separator_line_thickness = 1;
|
|
theme->menu_separator_padding_width = 6;
|
|
theme->menu_separator_padding_height = 3;
|
|
parse_hexstr("#888888", theme->menu_separator_color);
|
|
|
|
parse_hexstr("#589bda", theme->menu_title_bg_color);
|
|
theme->menu_title_text_justify = parse_justification("Center");
|
|
parse_hexstr("#ffffff", theme->menu_title_text_color);
|
|
|
|
theme->osd_window_switcher_classic.width = 600;
|
|
theme->osd_window_switcher_classic.width_is_percent = false;
|
|
theme->osd_window_switcher_classic.padding = 4;
|
|
theme->osd_window_switcher_classic.item_padding_x = 10;
|
|
theme->osd_window_switcher_classic.item_padding_y = 1;
|
|
theme->osd_window_switcher_classic.item_active_border_width = 2;
|
|
theme->osd_window_switcher_classic.item_icon_size = -1;
|
|
|
|
theme->osd_window_switcher_thumbnail.max_width = 80;
|
|
theme->osd_window_switcher_thumbnail.max_width_is_percent = true;
|
|
theme->osd_window_switcher_thumbnail.padding = 4;
|
|
theme->osd_window_switcher_thumbnail.item_width = 300;
|
|
theme->osd_window_switcher_thumbnail.item_height = 250;
|
|
theme->osd_window_switcher_thumbnail.item_padding = 10;
|
|
theme->osd_window_switcher_thumbnail.item_active_border_width = 2;
|
|
parse_color("#589bda", theme->osd_window_switcher_thumbnail.item_active_border_color);
|
|
parse_color("#c7e2fc", theme->osd_window_switcher_thumbnail.item_active_bg_color);
|
|
theme->osd_window_switcher_thumbnail.item_icon_size = 60;
|
|
|
|
/* inherit settings in post_processing() if not set elsewhere */
|
|
theme->osd_window_switcher_preview_border_width = INT_MIN;
|
|
zero_array(theme->osd_window_switcher_preview_border_color);
|
|
theme->osd_window_switcher_preview_border_color[0][0] = FLT_MIN;
|
|
|
|
theme->osd_workspace_switcher_boxes_width = 20;
|
|
theme->osd_workspace_switcher_boxes_height = 20;
|
|
theme->osd_workspace_switcher_boxes_border_width = 2;
|
|
|
|
/* inherit settings in post_processing() if not set elsewhere */
|
|
theme->osd_bg_color[0] = FLT_MIN;
|
|
theme->osd_border_width = INT_MIN;
|
|
theme->osd_border_color[0] = FLT_MIN;
|
|
theme->osd_label_text_color[0] = FLT_MIN;
|
|
|
|
if (wlr_renderer_is_pixman(server->renderer)) {
|
|
/* Draw only outlined overlay by default to save CPU resource */
|
|
theme->snapping_overlay_region.bg_enabled = false;
|
|
theme->snapping_overlay_edge.bg_enabled = false;
|
|
theme->snapping_overlay_region.border_enabled = true;
|
|
theme->snapping_overlay_edge.border_enabled = true;
|
|
} else {
|
|
theme->snapping_overlay_region.bg_enabled = true;
|
|
theme->snapping_overlay_edge.bg_enabled = true;
|
|
theme->snapping_overlay_region.border_enabled = false;
|
|
theme->snapping_overlay_edge.border_enabled = false;
|
|
}
|
|
|
|
parse_hexstr("#8080b380", theme->snapping_overlay_region.bg_color);
|
|
parse_hexstr("#8080b380", theme->snapping_overlay_edge.bg_color);
|
|
|
|
/* inherit settings in post_processing() if not set elsewhere */
|
|
theme->snapping_overlay_region.border_width = INT_MIN;
|
|
theme->snapping_overlay_edge.border_width = INT_MIN;
|
|
zero_array(theme->snapping_overlay_region.border_color);
|
|
theme->snapping_overlay_region.border_color[0][0] = FLT_MIN;
|
|
zero_array(theme->snapping_overlay_edge.border_color);
|
|
theme->snapping_overlay_edge.border_color[0][0] = FLT_MIN;
|
|
|
|
/* magnifier */
|
|
parse_hexstr("#ff0000", theme->mag_border_color);
|
|
theme->mag_border_width = 1;
|
|
}
|
|
|
|
static int
|
|
get_int_if_positive(const char *content, const char *field)
|
|
{
|
|
int value = atoi(content);
|
|
if (value < 0) {
|
|
wlr_log(WLR_ERROR,
|
|
"%s cannot be negative, clamping it to 0.", field);
|
|
value = 0;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static void
|
|
entry(struct theme *theme, const char *key, const char *value)
|
|
{
|
|
if (!key || !value) {
|
|
return;
|
|
}
|
|
|
|
struct window_switcher_classic_theme *switcher_classic_theme =
|
|
&theme->osd_window_switcher_classic;
|
|
struct window_switcher_thumbnail_theme *switcher_thumb_theme =
|
|
&theme->osd_window_switcher_thumbnail;
|
|
|
|
/*
|
|
* Note that in order for the pattern match to apply to more than just
|
|
* the first instance, "else if" cannot be used throughout this function
|
|
*/
|
|
if (match_glob(key, "border.width")) {
|
|
theme->border_width = get_int_if_positive(
|
|
value, "border.width");
|
|
}
|
|
if (match_glob(key, "window.titlebar.padding.width")) {
|
|
theme->window_titlebar_padding_width = get_int_if_positive(
|
|
value, "window.titlebar.padding.width");
|
|
}
|
|
if (match_glob(key, "window.titlebar.padding.height")) {
|
|
theme->window_titlebar_padding_height = get_int_if_positive(
|
|
value, "window.titlebar.padding.height");
|
|
}
|
|
if (match_glob(key, "titlebar.height")) {
|
|
wlr_log(WLR_ERROR, "titlebar.height is no longer supported");
|
|
}
|
|
if (match_glob(key, "padding.height")) {
|
|
wlr_log(WLR_INFO, "padding.height is no longer supported");
|
|
}
|
|
|
|
if (match_glob(key, "window.active.border.color")) {
|
|
parse_color(value, theme->window[THEME_ACTIVE].border_color);
|
|
}
|
|
if (match_glob(key, "window.inactive.border.color")) {
|
|
parse_color(value, theme->window[THEME_INACTIVE].border_color);
|
|
}
|
|
/* border.color is obsolete, but handled for backward compatibility */
|
|
if (match_glob(key, "border.color")) {
|
|
parse_color(value, theme->window[THEME_ACTIVE].border_color);
|
|
parse_color(value, theme->window[THEME_INACTIVE].border_color);
|
|
}
|
|
|
|
if (match_glob(key, "window.active.indicator.toggled-keybind.color")) {
|
|
parse_color(value, theme->window_toggled_keybinds_color);
|
|
}
|
|
|
|
if (match_glob(key, "window.active.title.bg")) {
|
|
theme->window[THEME_ACTIVE].title_bg.gradient = parse_gradient(value);
|
|
}
|
|
if (match_glob(key, "window.inactive.title.bg")) {
|
|
theme->window[THEME_INACTIVE].title_bg.gradient = parse_gradient(value);
|
|
}
|
|
if (match_glob(key, "window.active.title.bg.color")) {
|
|
parse_color(value, theme->window[THEME_ACTIVE].title_bg.color);
|
|
}
|
|
if (match_glob(key, "window.inactive.title.bg.color")) {
|
|
parse_color(value, theme->window[THEME_INACTIVE].title_bg.color);
|
|
}
|
|
if (match_glob(key, "window.active.title.bg.color.splitTo")) {
|
|
parse_color(value, theme->window[THEME_ACTIVE].title_bg.color_split_to);
|
|
}
|
|
if (match_glob(key, "window.inactive.title.bg.color.splitTo")) {
|
|
parse_color(value, theme->window[THEME_INACTIVE].title_bg.color_split_to);
|
|
}
|
|
if (match_glob(key, "window.active.title.bg.colorTo")) {
|
|
parse_color(value, theme->window[THEME_ACTIVE].title_bg.color_to);
|
|
}
|
|
if (match_glob(key, "window.inactive.title.bg.colorTo")) {
|
|
parse_color(value, theme->window[THEME_INACTIVE].title_bg.color_to);
|
|
}
|
|
if (match_glob(key, "window.active.title.bg.colorTo.splitTo")) {
|
|
parse_color(value, theme->window[THEME_ACTIVE].title_bg.color_to_split_to);
|
|
}
|
|
if (match_glob(key, "window.inactive.title.bg.colorTo.splitTo")) {
|
|
parse_color(value, theme->window[THEME_INACTIVE].title_bg.color_to_split_to);
|
|
}
|
|
|
|
if (match_glob(key, "window.active.label.text.color")) {
|
|
parse_color(value, theme->window[THEME_ACTIVE].label_text_color);
|
|
}
|
|
if (match_glob(key, "window.inactive.label.text.color")) {
|
|
parse_color(value, theme->window[THEME_INACTIVE].label_text_color);
|
|
}
|
|
if (match_glob(key, "window.label.text.justify")) {
|
|
theme->window_label_text_justify = parse_justification(value);
|
|
}
|
|
|
|
if (match_glob(key, "window.button.width")) {
|
|
theme->window_button_width = atoi(value);
|
|
if (theme->window_button_width < 1) {
|
|
wlr_log(WLR_ERROR, "window.button.width cannot "
|
|
"be less than 1, clamping it to 1.");
|
|
theme->window_button_width = 1;
|
|
}
|
|
}
|
|
if (match_glob(key, "window.button.height")) {
|
|
theme->window_button_height = atoi(value);
|
|
if (theme->window_button_height < 1) {
|
|
wlr_log(WLR_ERROR, "window.button.height cannot "
|
|
"be less than 1, clamping it to 1.");
|
|
theme->window_button_height = 1;
|
|
}
|
|
}
|
|
if (match_glob(key, "window.button.spacing")) {
|
|
theme->window_button_spacing = get_int_if_positive(
|
|
value, "window.button.spacing");
|
|
}
|
|
if (match_glob(key, "window.button.hover.bg.corner-radius")) {
|
|
theme->window_button_hover_bg_corner_radius = get_int_if_positive(
|
|
value, "window.button.hover.bg.corner-radius");
|
|
}
|
|
|
|
/* universal button */
|
|
if (match_glob(key, "window.active.button.unpressed.image.color")) {
|
|
for (enum lab_node_type type = LAB_NODE_BUTTON_FIRST;
|
|
type <= LAB_NODE_BUTTON_LAST; type++) {
|
|
parse_color(value,
|
|
theme->window[THEME_ACTIVE].button_colors[type]);
|
|
}
|
|
}
|
|
if (match_glob(key, "window.inactive.button.unpressed.image.color")) {
|
|
for (enum lab_node_type type = LAB_NODE_BUTTON_FIRST;
|
|
type <= LAB_NODE_BUTTON_LAST; type++) {
|
|
parse_color(value,
|
|
theme->window[THEME_INACTIVE].button_colors[type]);
|
|
}
|
|
}
|
|
|
|
/* individual buttons */
|
|
if (match_glob(key, "window.active.button.menu.unpressed.image.color")) {
|
|
parse_color(value, theme->window[THEME_ACTIVE]
|
|
.button_colors[LAB_NODE_BUTTON_WINDOW_MENU]);
|
|
parse_color(value, theme->window[THEME_ACTIVE]
|
|
.button_colors[LAB_NODE_BUTTON_WINDOW_ICON]);
|
|
}
|
|
if (match_glob(key, "window.active.button.iconify.unpressed.image.color")) {
|
|
parse_color(value, theme->window[THEME_ACTIVE]
|
|
.button_colors[LAB_NODE_BUTTON_ICONIFY]);
|
|
}
|
|
if (match_glob(key, "window.active.button.max.unpressed.image.color")) {
|
|
parse_color(value, theme->window[THEME_ACTIVE]
|
|
.button_colors[LAB_NODE_BUTTON_MAXIMIZE]);
|
|
}
|
|
if (match_glob(key, "window.active.button.shade.unpressed.image.color")) {
|
|
parse_color(value, theme->window[THEME_ACTIVE]
|
|
.button_colors[LAB_NODE_BUTTON_SHADE]);
|
|
}
|
|
if (match_glob(key, "window.active.button.desk.unpressed.image.color")) {
|
|
parse_color(value, theme->window[THEME_ACTIVE]
|
|
.button_colors[LAB_NODE_BUTTON_OMNIPRESENT]);
|
|
}
|
|
if (match_glob(key, "window.active.button.close.unpressed.image.color")) {
|
|
parse_color(value, theme->window[THEME_ACTIVE]
|
|
.button_colors[LAB_NODE_BUTTON_CLOSE]);
|
|
}
|
|
if (match_glob(key, "window.inactive.button.menu.unpressed.image.color")) {
|
|
parse_color(value, theme->window[THEME_INACTIVE]
|
|
.button_colors[LAB_NODE_BUTTON_WINDOW_MENU]);
|
|
parse_color(value, theme->window[THEME_INACTIVE]
|
|
.button_colors[LAB_NODE_BUTTON_WINDOW_ICON]);
|
|
}
|
|
if (match_glob(key, "window.inactive.button.iconify.unpressed.image.color")) {
|
|
parse_color(value, theme->window[THEME_INACTIVE]
|
|
.button_colors[LAB_NODE_BUTTON_ICONIFY]);
|
|
}
|
|
if (match_glob(key, "window.inactive.button.max.unpressed.image.color")) {
|
|
parse_color(value, theme->window[THEME_INACTIVE]
|
|
.button_colors[LAB_NODE_BUTTON_MAXIMIZE]);
|
|
}
|
|
if (match_glob(key, "window.inactive.button.shade.unpressed.image.color")) {
|
|
parse_color(value, theme->window[THEME_INACTIVE]
|
|
.button_colors[LAB_NODE_BUTTON_SHADE]);
|
|
}
|
|
if (match_glob(key, "window.inactive.button.desk.unpressed.image.color")) {
|
|
parse_color(value, theme->window[THEME_INACTIVE]
|
|
.button_colors[LAB_NODE_BUTTON_OMNIPRESENT]);
|
|
}
|
|
if (match_glob(key, "window.inactive.button.close.unpressed.image.color")) {
|
|
parse_color(value, theme->window[THEME_INACTIVE]
|
|
.button_colors[LAB_NODE_BUTTON_CLOSE]);
|
|
}
|
|
|
|
/* window drop-shadows */
|
|
if (match_glob(key, "window.active.shadow.size")) {
|
|
theme->window[THEME_ACTIVE].shadow_size = get_int_if_positive(
|
|
value, "window.active.shadow.size");
|
|
}
|
|
if (match_glob(key, "window.inactive.shadow.size")) {
|
|
theme->window[THEME_INACTIVE].shadow_size = get_int_if_positive(
|
|
value, "window.inactive.shadow.size");
|
|
}
|
|
if (match_glob(key, "window.active.shadow.color")) {
|
|
parse_color(value, theme->window[THEME_ACTIVE].shadow_color);
|
|
}
|
|
if (match_glob(key, "window.inactive.shadow.color")) {
|
|
parse_color(value, theme->window[THEME_INACTIVE].shadow_color);
|
|
}
|
|
|
|
if (match_glob(key, "menu.overlap.x")) {
|
|
theme->menu_overlap_x = atoi(value);
|
|
}
|
|
if (match_glob(key, "menu.overlap.y")) {
|
|
theme->menu_overlap_y = atoi(value);
|
|
}
|
|
if (match_glob(key, "menu.width.min")) {
|
|
theme->menu_min_width = get_int_if_positive(
|
|
value, "menu.width.min");
|
|
}
|
|
if (match_glob(key, "menu.width.max")) {
|
|
theme->menu_max_width = get_int_if_positive(
|
|
value, "menu.width.max");
|
|
}
|
|
if (match_glob(key, "menu.border.width")) {
|
|
theme->menu_border_width = get_int_if_positive(
|
|
value, "menu.border.width");
|
|
}
|
|
if (match_glob(key, "menu.border.color")) {
|
|
parse_color(value, theme->menu_border_color);
|
|
}
|
|
|
|
if (match_glob(key, "menu.items.padding.x")) {
|
|
theme->menu_items_padding_x = get_int_if_positive(
|
|
value, "menu.items.padding.x");
|
|
}
|
|
if (match_glob(key, "menu.items.padding.y")) {
|
|
theme->menu_items_padding_y = get_int_if_positive(
|
|
value, "menu.items.padding.y");
|
|
}
|
|
if (match_glob(key, "menu.items.bg.color")) {
|
|
parse_color(value, theme->menu_items_bg_color);
|
|
}
|
|
if (match_glob(key, "menu.items.text.color")) {
|
|
parse_color(value, theme->menu_items_text_color);
|
|
}
|
|
if (match_glob(key, "menu.items.active.bg.color")) {
|
|
parse_color(value, theme->menu_items_active_bg_color);
|
|
}
|
|
if (match_glob(key, "menu.items.active.text.color")) {
|
|
parse_color(value, theme->menu_items_active_text_color);
|
|
}
|
|
|
|
if (match_glob(key, "menu.separator.width")) {
|
|
theme->menu_separator_line_thickness = get_int_if_positive(
|
|
value, "menu.separator.width");
|
|
}
|
|
if (match_glob(key, "menu.separator.padding.width")) {
|
|
theme->menu_separator_padding_width = get_int_if_positive(
|
|
value, "menu.separator.padding.width");
|
|
}
|
|
if (match_glob(key, "menu.separator.padding.height")) {
|
|
theme->menu_separator_padding_height = get_int_if_positive(
|
|
value, "menu.separator.padding.height");
|
|
}
|
|
if (match_glob(key, "menu.separator.color")) {
|
|
parse_color(value, theme->menu_separator_color);
|
|
}
|
|
|
|
if (match_glob(key, "menu.title.bg.color")) {
|
|
parse_color(value, theme->menu_title_bg_color);
|
|
}
|
|
if (match_glob(key, "menu.title.text.justify")) {
|
|
theme->menu_title_text_justify = parse_justification(value);
|
|
}
|
|
if (match_glob(key, "menu.title.text.color")) {
|
|
parse_color(value, theme->menu_title_text_color);
|
|
}
|
|
|
|
if (match_glob(key, "osd.bg.color")) {
|
|
parse_color(value, theme->osd_bg_color);
|
|
}
|
|
if (match_glob(key, "osd.border.width")) {
|
|
theme->osd_border_width = get_int_if_positive(
|
|
value, "osd.border.width");
|
|
}
|
|
if (match_glob(key, "osd.border.color")) {
|
|
parse_color(value, theme->osd_border_color);
|
|
}
|
|
/* classic window switcher */
|
|
if (match_glob(key, "osd.window-switcher.style-classic.width")
|
|
|| match_glob(key, "osd.window-switcher.width")) {
|
|
if (strrchr(value, '%')) {
|
|
switcher_classic_theme->width_is_percent = true;
|
|
} else {
|
|
switcher_classic_theme->width_is_percent = false;
|
|
}
|
|
switcher_classic_theme->width = get_int_if_positive(value,
|
|
"osd.window-switcher.style-classic.width");
|
|
}
|
|
if (match_glob(key, "osd.window-switcher.style-classic.padding")
|
|
|| match_glob(key, "osd.window-switcher.padding")) {
|
|
switcher_classic_theme->padding = get_int_if_positive(value,
|
|
"osd.window-switcher.style-classic.padding");
|
|
}
|
|
if (match_glob(key, "osd.window-switcher.style-classic.item.padding.x")
|
|
|| match_glob(key, "osd.window-switcher.item.padding.x")) {
|
|
switcher_classic_theme->item_padding_x =
|
|
get_int_if_positive(value,
|
|
"osd.window-switcher.style-classic.item.padding.x");
|
|
}
|
|
if (match_glob(key, "osd.window-switcher.style-classic.item.padding.y")
|
|
|| match_glob(key, "osd.window-switcher.item.padding.y")) {
|
|
switcher_classic_theme->item_padding_y =
|
|
get_int_if_positive(value,
|
|
"osd.window-switcher.style-classic.item.padding.y");
|
|
}
|
|
if (match_glob(key, "osd.window-switcher.style-classic.item.active.border.width")
|
|
|| match_glob(key, "osd.window-switcher.item.active.border.width")) {
|
|
switcher_classic_theme->item_active_border_width =
|
|
get_int_if_positive(value,
|
|
"osd.window-switcher.style-classic.item.active.border.width");
|
|
}
|
|
if (match_glob(key, "osd.window-switcher.style-classic.item.icon.size")
|
|
|| match_glob(key, "osd.window-switcher.item.icon.size")) {
|
|
switcher_classic_theme->item_icon_size =
|
|
get_int_if_positive(value,
|
|
"osd.window-switcher.style-classic.item.icon.size");
|
|
}
|
|
/* thumbnail window switcher */
|
|
if (match_glob(key, "osd.window-switcher.style-thumbnail.width.max")) {
|
|
if (strrchr(value, '%')) {
|
|
switcher_thumb_theme->max_width_is_percent = true;
|
|
} else {
|
|
switcher_thumb_theme->max_width_is_percent = false;
|
|
}
|
|
switcher_thumb_theme->max_width = get_int_if_positive(
|
|
value, "osd.window-switcher.style-thumbnail.width.max");
|
|
}
|
|
if (match_glob(key, "osd.window-switcher.style-thumbnail.padding")) {
|
|
switcher_thumb_theme->padding = get_int_if_positive(
|
|
value, "osd.window-switcher.style-thumbnail.padding");
|
|
}
|
|
if (match_glob(key, "osd.window-switcher.style-thumbnail.item.width")) {
|
|
switcher_thumb_theme->item_width = get_int_if_positive(
|
|
value, "osd.window-switcher.style-thumbnail.item.width");
|
|
}
|
|
if (match_glob(key, "osd.window-switcher.style-thumbnail.item.height")) {
|
|
switcher_thumb_theme->item_height = get_int_if_positive(
|
|
value, "osd.window-switcher.style-thumbnail.item.height");
|
|
}
|
|
if (match_glob(key, "osd.window-switcher.style-thumbnail.item.padding")) {
|
|
switcher_thumb_theme->item_padding = get_int_if_positive(
|
|
value, "osd.window-switcher.style-thumbnail.item.padding");
|
|
}
|
|
if (match_glob(key, "osd.window-switcher.style-thumbnail.item.active.border.width")) {
|
|
switcher_thumb_theme->item_active_border_width = get_int_if_positive(
|
|
value, "osd.window-switcher.style-thumbnail.item.active.border.width");
|
|
}
|
|
if (match_glob(key, "osd.window-switcher.style-thumbnail.item.active.border.color")) {
|
|
parse_color(value, switcher_thumb_theme->item_active_border_color);
|
|
}
|
|
if (match_glob(key, "osd.window-switcher.style-thumbnail.item.active.bg.color")) {
|
|
parse_color(value, switcher_thumb_theme->item_active_bg_color);
|
|
}
|
|
if (match_glob(key, "osd.window-switcher.style-thumbnail.item.icon.size")) {
|
|
switcher_thumb_theme->item_icon_size = get_int_if_positive(
|
|
value, "osd.window-switcher.style-thumbnail.item.icon.size");
|
|
}
|
|
|
|
if (match_glob(key, "osd.window-switcher.preview.border.width")) {
|
|
theme->osd_window_switcher_preview_border_width =
|
|
get_int_if_positive(
|
|
value, "osd.window-switcher.preview.border.width");
|
|
}
|
|
if (match_glob(key, "osd.window-switcher.preview.border.color")) {
|
|
parse_hexstrs(value, theme->osd_window_switcher_preview_border_color);
|
|
}
|
|
if (match_glob(key, "osd.workspace-switcher.boxes.width")) {
|
|
theme->osd_workspace_switcher_boxes_width =
|
|
get_int_if_positive(
|
|
value, "osd.workspace-switcher.boxes.width");
|
|
}
|
|
if (match_glob(key, "osd.workspace-switcher.boxes.height")) {
|
|
theme->osd_workspace_switcher_boxes_height =
|
|
get_int_if_positive(
|
|
value, "osd.workspace-switcher.boxes.height");
|
|
}
|
|
if (match_glob(key, "osd.workspace-switcher.boxes.border.width")) {
|
|
theme->osd_workspace_switcher_boxes_border_width =
|
|
get_int_if_positive(
|
|
value, "osd.workspace-switcher.boxes.border.width");
|
|
}
|
|
if (match_glob(key, "osd.label.text.color")) {
|
|
parse_color(value, theme->osd_label_text_color);
|
|
}
|
|
if (match_glob(key, "snapping.overlay.region.bg.enabled")) {
|
|
set_bool(value, &theme->snapping_overlay_region.bg_enabled);
|
|
}
|
|
if (match_glob(key, "snapping.overlay.edge.bg.enabled")) {
|
|
set_bool(value, &theme->snapping_overlay_edge.bg_enabled);
|
|
}
|
|
if (match_glob(key, "snapping.overlay.region.border.enabled")) {
|
|
set_bool(value, &theme->snapping_overlay_region.border_enabled);
|
|
}
|
|
if (match_glob(key, "snapping.overlay.edge.border.enabled")) {
|
|
set_bool(value, &theme->snapping_overlay_edge.border_enabled);
|
|
}
|
|
if (match_glob(key, "snapping.overlay.region.bg.color")) {
|
|
parse_color(value, theme->snapping_overlay_region.bg_color);
|
|
}
|
|
if (match_glob(key, "snapping.overlay.edge.bg.color")) {
|
|
parse_color(value, theme->snapping_overlay_edge.bg_color);
|
|
}
|
|
if (match_glob(key, "snapping.overlay.region.border.width")) {
|
|
theme->snapping_overlay_region.border_width = get_int_if_positive(
|
|
value, "snapping.overlay.region.border.width");
|
|
}
|
|
if (match_glob(key, "snapping.overlay.edge.border.width")) {
|
|
theme->snapping_overlay_edge.border_width = get_int_if_positive(
|
|
value, "snapping.overlay.edge.border.width");
|
|
}
|
|
if (match_glob(key, "snapping.overlay.region.border.color")) {
|
|
parse_hexstrs(value, theme->snapping_overlay_region.border_color);
|
|
}
|
|
if (match_glob(key, "snapping.overlay.edge.border.color")) {
|
|
parse_hexstrs(value, theme->snapping_overlay_edge.border_color);
|
|
}
|
|
|
|
if (match_glob(key, "magnifier.border.width")) {
|
|
theme->mag_border_width = get_int_if_positive(
|
|
value, "magnifier.border.width");
|
|
}
|
|
if (match_glob(key, "magnifier.border.color")) {
|
|
parse_color(value, theme->mag_border_color);
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_config_line(char *line, char **key, char **value)
|
|
{
|
|
char *p = strchr(line, ':');
|
|
if (!p) {
|
|
return;
|
|
}
|
|
*p = '\0';
|
|
*key = string_strip(line);
|
|
*value = string_strip(++p);
|
|
}
|
|
|
|
static void
|
|
process_line(struct theme *theme, char *line)
|
|
{
|
|
if (line[0] == '\0' || line[0] == '#') {
|
|
return;
|
|
}
|
|
char *key = NULL, *value = NULL;
|
|
parse_config_line(line, &key, &value);
|
|
entry(theme, key, value);
|
|
}
|
|
|
|
static void
|
|
theme_read(struct theme *theme, struct wl_list *paths)
|
|
{
|
|
bool should_merge_config = rc.merge_config;
|
|
struct wl_list *(*iter)(struct wl_list *list);
|
|
iter = should_merge_config ? paths_get_prev : paths_get_next;
|
|
|
|
for (struct wl_list *elm = iter(paths); elm != paths; elm = iter(elm)) {
|
|
struct path *path = wl_container_of(elm, path, link);
|
|
FILE *stream = fopen(path->string, "r");
|
|
if (!stream) {
|
|
continue;
|
|
}
|
|
|
|
wlr_log(WLR_INFO, "read theme %s", path->string);
|
|
|
|
char *line = NULL;
|
|
size_t len = 0;
|
|
while (getline(&line, &len, stream) != -1) {
|
|
char *p = strrchr(line, '\n');
|
|
if (p) {
|
|
*p = '\0';
|
|
}
|
|
process_line(theme, line);
|
|
}
|
|
zfree(line);
|
|
fclose(stream);
|
|
if (!should_merge_config) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static struct lab_data_buffer *
|
|
rounded_rect(struct rounded_corner_ctx *ctx)
|
|
{
|
|
double w = ctx->box->width;
|
|
double h = ctx->box->height;
|
|
double r = ctx->radius;
|
|
|
|
struct lab_data_buffer *buffer;
|
|
/* TODO: scale */
|
|
buffer = buffer_create_cairo(w, h, 1);
|
|
|
|
cairo_surface_t *surf = buffer->surface;
|
|
cairo_t *cairo = cairo_create(surf);
|
|
|
|
/* set transparent background */
|
|
cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
|
|
cairo_paint(cairo);
|
|
|
|
/*
|
|
* Create outline path and fill. Illustration of top-left corner buffer:
|
|
*
|
|
* _,,ooO"""""""""+
|
|
* ,oO"' ^ |
|
|
* ,o" | |
|
|
* o" |r |
|
|
* o' | |
|
|
* O r v |
|
|
* O<--------->+ |
|
|
* O |
|
|
* O |
|
|
* O |
|
|
* +--------------------+
|
|
*/
|
|
cairo_set_line_width(cairo, 0.0);
|
|
cairo_new_sub_path(cairo);
|
|
switch (ctx->corner) {
|
|
case ROUNDED_CORNER_TOP_LEFT:
|
|
cairo_arc(cairo, r, r, r, 180 * deg, 270 * deg);
|
|
cairo_line_to(cairo, w, 0);
|
|
cairo_line_to(cairo, w, h);
|
|
cairo_line_to(cairo, 0, h);
|
|
break;
|
|
case ROUNDED_CORNER_TOP_RIGHT:
|
|
cairo_arc(cairo, w - r, r, r, -90 * deg, 0 * deg);
|
|
cairo_line_to(cairo, w, h);
|
|
cairo_line_to(cairo, 0, h);
|
|
cairo_line_to(cairo, 0, 0);
|
|
break;
|
|
}
|
|
cairo_close_path(cairo);
|
|
cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
|
|
/*
|
|
* We need to offset the fill pattern vertically by the border
|
|
* width to line up with the rest of the titlebar. This is done
|
|
* by applying a transformation matrix to the pattern temporarily.
|
|
* It would be better to copy the pattern, but cairo does not
|
|
* provide a simple way to this.
|
|
*/
|
|
cairo_matrix_t matrix;
|
|
cairo_matrix_init_translate(&matrix, 0, -ctx->line_width);
|
|
cairo_pattern_set_matrix(ctx->fill_pattern, &matrix);
|
|
cairo_set_source(cairo, ctx->fill_pattern);
|
|
cairo_fill_preserve(cairo);
|
|
cairo_stroke(cairo);
|
|
|
|
/* Reset the fill pattern transformation matrix afterward */
|
|
cairo_matrix_init_identity(&matrix);
|
|
cairo_pattern_set_matrix(ctx->fill_pattern, &matrix);
|
|
|
|
/*
|
|
* Stroke horizontal and vertical borders, shown by Xs and Ys
|
|
* respectively in the figure below:
|
|
*
|
|
* _,,ooO"XXXXXXXXX
|
|
* ,oO"' |
|
|
* ,o" |
|
|
* o" |
|
|
* o' |
|
|
* O |
|
|
* Y |
|
|
* Y |
|
|
* Y |
|
|
* Y |
|
|
* Y--------------------+
|
|
*/
|
|
cairo_set_line_cap(cairo, CAIRO_LINE_CAP_BUTT);
|
|
set_cairo_color(cairo, ctx->border_color);
|
|
cairo_set_line_width(cairo, ctx->line_width);
|
|
double half_line_width = ctx->line_width / 2.0;
|
|
switch (ctx->corner) {
|
|
case ROUNDED_CORNER_TOP_LEFT:
|
|
cairo_move_to(cairo, half_line_width, h);
|
|
cairo_line_to(cairo, half_line_width, r);
|
|
cairo_move_to(cairo, r, half_line_width);
|
|
cairo_line_to(cairo, w, half_line_width);
|
|
break;
|
|
case ROUNDED_CORNER_TOP_RIGHT:
|
|
cairo_move_to(cairo, 0, half_line_width);
|
|
cairo_line_to(cairo, w - r, half_line_width);
|
|
cairo_move_to(cairo, w - half_line_width, r);
|
|
cairo_line_to(cairo, w - half_line_width, h);
|
|
break;
|
|
}
|
|
cairo_stroke(cairo);
|
|
|
|
/*
|
|
* If radius==0 the borders stroked above go right up to (and including)
|
|
* the corners, so there is not need to do any more.
|
|
*/
|
|
if (!r) {
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Stroke the arc section of the border of the corner piece.
|
|
*
|
|
* Note: This figure is drawn at a more zoomed in scale compared with
|
|
* those above.
|
|
*
|
|
* ,,ooooO"" ^
|
|
* ,ooo""' | |
|
|
* ,oOO" | | line-thickness
|
|
* ,OO" | |
|
|
* ,OO" _,,ooO"" v
|
|
* ,O" ,oO"'
|
|
* ,O' ,o"
|
|
* ,O' o"
|
|
* o' o'
|
|
* O O
|
|
* O---------O +
|
|
* <----------------->
|
|
* radius
|
|
*
|
|
* We handle the edge-case where line-thickness > radius by merely
|
|
* setting line-thickness = radius and in effect drawing a quadrant of a
|
|
* circle. In this case the X and Y borders butt up against the arc and
|
|
* overlap each other (as their line-thicknesses are greater than the
|
|
* line-thickness of the arc). As a result, there is no inner rounded
|
|
* corners.
|
|
*
|
|
* So, in order to have inner rounded corners cornerRadius should be
|
|
* greater than border.width.
|
|
*
|
|
* Also, see diagrams in https://github.com/labwc/labwc/pull/990
|
|
*/
|
|
double line_width = MIN(ctx->line_width, r);
|
|
cairo_set_line_width(cairo, line_width);
|
|
half_line_width = line_width / 2.0;
|
|
switch (ctx->corner) {
|
|
case ROUNDED_CORNER_TOP_LEFT:
|
|
cairo_move_to(cairo, half_line_width, r);
|
|
cairo_arc(cairo, r, r, r - half_line_width, 180 * deg, 270 * deg);
|
|
break;
|
|
case ROUNDED_CORNER_TOP_RIGHT:
|
|
cairo_move_to(cairo, w - r, half_line_width);
|
|
cairo_arc(cairo, w - r, r, r - half_line_width, -90 * deg, 0 * deg);
|
|
break;
|
|
}
|
|
cairo_stroke(cairo);
|
|
|
|
out:
|
|
cairo_surface_flush(surf);
|
|
cairo_destroy(cairo);
|
|
|
|
return buffer;
|
|
}
|
|
|
|
static void
|
|
add_color_stop_rgba_premult(cairo_pattern_t *pattern, float offset,
|
|
const float c[4])
|
|
{
|
|
float alpha = c[3];
|
|
|
|
if (alpha == 0.0f) {
|
|
cairo_pattern_add_color_stop_rgba(pattern, offset, 0, 0, 0, 0);
|
|
} else {
|
|
cairo_pattern_add_color_stop_rgba(pattern, offset,
|
|
c[0] / alpha, c[1] / alpha, c[2] / alpha, alpha);
|
|
}
|
|
}
|
|
|
|
static cairo_pattern_t *
|
|
create_titlebar_pattern(const struct theme_background *bg, int height)
|
|
{
|
|
cairo_pattern_t *pattern;
|
|
|
|
switch (bg->gradient) {
|
|
case LAB_GRADIENT_VERTICAL:
|
|
pattern = cairo_pattern_create_linear(0, 0, 0, height);
|
|
add_color_stop_rgba_premult(pattern, 0, bg->color);
|
|
add_color_stop_rgba_premult(pattern, 1, bg->color_to);
|
|
break;
|
|
|
|
case LAB_GRADIENT_SPLITVERTICAL:
|
|
pattern = cairo_pattern_create_linear(0, 0, 0, height);
|
|
add_color_stop_rgba_premult(pattern, 0, bg->color_split_to);
|
|
add_color_stop_rgba_premult(pattern, 0.5, bg->color);
|
|
add_color_stop_rgba_premult(pattern, 0.5, bg->color_to);
|
|
add_color_stop_rgba_premult(pattern, 1, bg->color_to_split_to);
|
|
break;
|
|
|
|
case LAB_GRADIENT_NONE:
|
|
default:
|
|
pattern = color_to_pattern(bg->color);
|
|
break;
|
|
}
|
|
|
|
return pattern;
|
|
}
|
|
|
|
static struct lab_data_buffer *
|
|
create_titlebar_fill(cairo_pattern_t *pattern, int height)
|
|
{
|
|
/* create 1px wide buffer to be stretched horizontally */
|
|
struct lab_data_buffer *fill = buffer_create_cairo(1, height, 1);
|
|
|
|
cairo_t *cairo = cairo_create(fill->surface);
|
|
cairo_set_source(cairo, pattern);
|
|
cairo_paint(cairo);
|
|
cairo_surface_flush(fill->surface);
|
|
cairo_destroy(cairo);
|
|
|
|
return fill;
|
|
}
|
|
|
|
static void
|
|
create_backgrounds(struct theme *theme)
|
|
{
|
|
for (int active = THEME_INACTIVE; active <= THEME_ACTIVE; active++) {
|
|
theme->window[active].titlebar_pattern = create_titlebar_pattern(
|
|
&theme->window[active].title_bg,
|
|
theme->titlebar_height);
|
|
theme->window[active].titlebar_fill = create_titlebar_fill(
|
|
theme->window[active].titlebar_pattern,
|
|
theme->titlebar_height);
|
|
}
|
|
}
|
|
|
|
static void
|
|
create_corners(struct theme *theme)
|
|
{
|
|
int corner_width = ssd_get_corner_width();
|
|
|
|
struct wlr_box box = {
|
|
.x = 0,
|
|
.y = 0,
|
|
.width = corner_width + theme->border_width,
|
|
.height = theme->titlebar_height + theme->border_width,
|
|
};
|
|
|
|
for (int active = THEME_INACTIVE; active <= THEME_ACTIVE; active++) {
|
|
struct rounded_corner_ctx ctx = {
|
|
.box = &box,
|
|
.radius = rc.corner_radius,
|
|
.line_width = theme->border_width,
|
|
.fill_pattern = theme->window[active].titlebar_pattern,
|
|
.border_color = theme->window[active].border_color,
|
|
.corner = ROUNDED_CORNER_TOP_LEFT,
|
|
};
|
|
theme->window[active].corner_top_left_normal = rounded_rect(&ctx);
|
|
ctx.corner = ROUNDED_CORNER_TOP_RIGHT;
|
|
theme->window[active].corner_top_right_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;
|
|
|
|
/* Gaussian 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_shadow(struct theme *theme, int active)
|
|
{
|
|
/* Size of shadow visible extending beyond the window */
|
|
int visible_size = theme->window[active].shadow_size;
|
|
/* How far inside the window the shadow inset begins */
|
|
int inset = (double)visible_size * SSD_SHADOW_INSET;
|
|
/* Total width including visible and obscured portion */
|
|
int total_size = visible_size + inset;
|
|
|
|
/*
|
|
* 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_size > 0) {
|
|
theme->window[active].shadow_edge = buffer_create_cairo(
|
|
visible_size, 1, 1.0);
|
|
theme->window[active].shadow_corner_top = buffer_create_cairo(
|
|
total_size, total_size, 1.0);
|
|
theme->window[active].shadow_corner_bottom = buffer_create_cairo(
|
|
total_size, total_size, 1.0);
|
|
if (!theme->window[active].shadow_corner_top
|
|
|| !theme->window[active].shadow_corner_bottom
|
|
|| !theme->window[active].shadow_edge) {
|
|
wlr_log(WLR_ERROR, "Failed to allocate shadow buffer");
|
|
return;
|
|
}
|
|
}
|
|
|
|
shadow_edge_gradient(theme->window[active].shadow_edge, visible_size,
|
|
total_size, theme->window[active].shadow_color);
|
|
shadow_corner_gradient(theme->window[active].shadow_corner_top,
|
|
visible_size, total_size,
|
|
theme->titlebar_height, theme->window[active].shadow_color);
|
|
shadow_corner_gradient(theme->window[active].shadow_corner_bottom,
|
|
visible_size, total_size, 0,
|
|
theme->window[active].shadow_color);
|
|
}
|
|
|
|
static void
|
|
create_shadows(struct theme *theme)
|
|
{
|
|
create_shadow(theme, THEME_INACTIVE);
|
|
create_shadow(theme, THEME_ACTIVE);
|
|
}
|
|
|
|
static void
|
|
copy_color_scaled(float dest[4], const float src[4], float scale)
|
|
{
|
|
/* RGB values are premultiplied so must not exceed alpha */
|
|
dest[0] = fminf(src[0] * scale, src[3]);
|
|
dest[1] = fminf(src[1] * scale, src[3]);
|
|
dest[2] = fminf(src[2] * scale, src[3]);
|
|
dest[3] = src[3]; /* alpha */
|
|
}
|
|
|
|
static void
|
|
fill_background_colors(struct theme_background *bg)
|
|
{
|
|
/* color.splitTo is color * 5/4, per Openbox theme spec */
|
|
if (bg->color_split_to[0] == FLT_MIN) {
|
|
copy_color_scaled(bg->color_split_to, bg->color, 1.25f);
|
|
}
|
|
/* colorTo has no default in Openbox; just re-use "color" */
|
|
if (bg->color_to[0] == FLT_MIN) {
|
|
memcpy(bg->color_to, bg->color, sizeof(bg->color_to));
|
|
}
|
|
/* colorTo.splitTo is colorTo * 17/16, per Openbox theme spec */
|
|
if (bg->color_to_split_to[0] == FLT_MIN) {
|
|
copy_color_scaled(bg->color_to_split_to, bg->color_to, 1.0625f);
|
|
}
|
|
}
|
|
|
|
static void
|
|
fill_colors_with_osd_theme(struct theme *theme, float colors[3][4])
|
|
{
|
|
memcpy(colors[0], theme->osd_bg_color, sizeof(colors[0]));
|
|
memcpy(colors[1], theme->osd_label_text_color, sizeof(colors[1]));
|
|
memcpy(colors[2], theme->osd_bg_color, sizeof(colors[2]));
|
|
}
|
|
|
|
static int
|
|
get_titlebar_height(struct theme *theme)
|
|
{
|
|
int h = MAX(font_height(&rc.font_activewindow),
|
|
font_height(&rc.font_inactivewindow));
|
|
if (h < theme->window_button_height) {
|
|
h = theme->window_button_height;
|
|
}
|
|
h += 2 * theme->window_titlebar_padding_height;
|
|
return h;
|
|
}
|
|
|
|
static void
|
|
post_processing(struct theme *theme)
|
|
{
|
|
struct window_switcher_classic_theme *switcher_classic_theme =
|
|
&theme->osd_window_switcher_classic;
|
|
struct window_switcher_thumbnail_theme *switcher_thumb_theme =
|
|
&theme->osd_window_switcher_thumbnail;
|
|
|
|
theme->titlebar_height = get_titlebar_height(theme);
|
|
|
|
fill_background_colors(&theme->window[THEME_INACTIVE].title_bg);
|
|
fill_background_colors(&theme->window[THEME_ACTIVE].title_bg);
|
|
|
|
theme->menu_item_height = font_height(&rc.font_menuitem)
|
|
+ 2 * theme->menu_items_padding_y;
|
|
|
|
theme->menu_header_height = font_height(&rc.font_menuheader)
|
|
+ 2 * theme->menu_items_padding_y;
|
|
|
|
int osd_font_height = font_height(&rc.font_osd);
|
|
switcher_thumb_theme->title_height = osd_font_height;
|
|
if (switcher_classic_theme->item_icon_size <= 0) {
|
|
switcher_classic_theme->item_icon_size = osd_font_height;
|
|
}
|
|
int osd_field_height =
|
|
MAX(osd_font_height, switcher_classic_theme->item_icon_size);
|
|
switcher_classic_theme->item_height = osd_field_height
|
|
+ 2 * switcher_classic_theme->item_padding_y
|
|
+ 2 * switcher_classic_theme->item_active_border_width;
|
|
|
|
if (rc.corner_radius >= theme->titlebar_height) {
|
|
rc.corner_radius = theme->titlebar_height - 1;
|
|
}
|
|
|
|
if (rc.resize_corner_range < 0) {
|
|
rc.resize_corner_range = theme->titlebar_height / 2;
|
|
}
|
|
|
|
int min_button_hover_radius =
|
|
MIN(theme->window_button_width, theme->window_button_height) / 2;
|
|
if (theme->window_button_hover_bg_corner_radius > min_button_hover_radius) {
|
|
theme->window_button_hover_bg_corner_radius = min_button_hover_radius;
|
|
}
|
|
|
|
if (theme->menu_max_width < theme->menu_min_width) {
|
|
wlr_log(WLR_ERROR,
|
|
"Adjusting menu.width.max: .max (%d) lower than .min (%d)",
|
|
theme->menu_max_width, theme->menu_min_width);
|
|
theme->menu_max_width = theme->menu_min_width;
|
|
}
|
|
|
|
if (theme->menu_border_width == INT_MIN) {
|
|
theme->menu_border_width = theme->border_width;
|
|
}
|
|
if (theme->menu_border_color[0] == FLT_MIN) {
|
|
memcpy(theme->menu_border_color,
|
|
theme->window[THEME_ACTIVE].border_color,
|
|
sizeof(theme->menu_border_color));
|
|
}
|
|
|
|
/* Inherit OSD settings if not set */
|
|
if (theme->osd_bg_color[0] == FLT_MIN) {
|
|
memcpy(theme->osd_bg_color,
|
|
theme->window[THEME_ACTIVE].title_bg.color,
|
|
sizeof(theme->osd_bg_color));
|
|
}
|
|
if (theme->osd_border_width == INT_MIN) {
|
|
theme->osd_border_width = theme->border_width;
|
|
}
|
|
if (theme->osd_label_text_color[0] == FLT_MIN) {
|
|
memcpy(theme->osd_label_text_color,
|
|
theme->window[THEME_ACTIVE].label_text_color,
|
|
sizeof(theme->osd_label_text_color));
|
|
}
|
|
if (theme->osd_border_color[0] == FLT_MIN) {
|
|
/*
|
|
* As per https://openbox.org/help/Themes#osd.border.color
|
|
* we should fall back to window.active.border.color but
|
|
* that is usually the same as window.active.title.bg.color
|
|
* and thus the fallback for osd.bg.color. Which would mean
|
|
* they are both the same color and thus the border is invisible.
|
|
*
|
|
* Instead, we fall back to osd.label.text.color which in turn
|
|
* falls back to window.active.label.text.color.
|
|
*/
|
|
memcpy(theme->osd_border_color, theme->osd_label_text_color,
|
|
sizeof(theme->osd_border_color));
|
|
}
|
|
if (theme->osd_workspace_switcher_boxes_width == 0) {
|
|
theme->osd_workspace_switcher_boxes_height = 0;
|
|
}
|
|
if (theme->osd_workspace_switcher_boxes_height == 0) {
|
|
theme->osd_workspace_switcher_boxes_width = 0;
|
|
}
|
|
if (switcher_classic_theme->width_is_percent) {
|
|
switcher_classic_theme->width =
|
|
MIN(switcher_classic_theme->width, 100);
|
|
}
|
|
if (theme->osd_window_switcher_preview_border_width == INT_MIN) {
|
|
theme->osd_window_switcher_preview_border_width =
|
|
theme->osd_border_width;
|
|
}
|
|
if (theme->osd_window_switcher_preview_border_color[0][0] == FLT_MIN) {
|
|
fill_colors_with_osd_theme(theme,
|
|
theme->osd_window_switcher_preview_border_color);
|
|
}
|
|
|
|
if (theme->snapping_overlay_region.border_width == INT_MIN) {
|
|
theme->snapping_overlay_region.border_width =
|
|
theme->osd_border_width;
|
|
}
|
|
if (theme->snapping_overlay_edge.border_width == INT_MIN) {
|
|
theme->snapping_overlay_edge.border_width =
|
|
theme->osd_border_width;
|
|
}
|
|
if (theme->snapping_overlay_region.border_color[0][0] == FLT_MIN) {
|
|
fill_colors_with_osd_theme(theme,
|
|
theme->snapping_overlay_region.border_color);
|
|
}
|
|
if (theme->snapping_overlay_edge.border_color[0][0] == FLT_MIN) {
|
|
fill_colors_with_osd_theme(theme,
|
|
theme->snapping_overlay_edge.border_color);
|
|
}
|
|
}
|
|
|
|
void
|
|
theme_init(struct theme *theme, struct server *server, const char *theme_name)
|
|
{
|
|
/*
|
|
* Set some default values. This is particularly important on
|
|
* reconfigure as not all themes set all options
|
|
*/
|
|
theme_builtin(theme, server);
|
|
|
|
struct wl_list paths;
|
|
|
|
if (theme_name) {
|
|
/*
|
|
* Read
|
|
* - <data-dir>/share/themes/$theme_name/labwc/themerc
|
|
* - <data-dir>/share/themes/$theme_name/openbox-3/themerc
|
|
*/
|
|
paths_theme_create(&paths, theme_name, "themerc");
|
|
theme_read(theme, &paths);
|
|
paths_destroy(&paths);
|
|
}
|
|
|
|
/* Read <config-dir>/labwc/themerc-override */
|
|
paths_config_create(&paths, "themerc-override");
|
|
theme_read(theme, &paths);
|
|
paths_destroy(&paths);
|
|
|
|
post_processing(theme);
|
|
create_backgrounds(theme);
|
|
create_corners(theme);
|
|
load_buttons(theme);
|
|
create_shadows(theme);
|
|
}
|
|
|
|
static void destroy_img(struct lab_img **img)
|
|
{
|
|
lab_img_destroy(*img);
|
|
*img = NULL;
|
|
}
|
|
|
|
void
|
|
theme_finish(struct theme *theme)
|
|
{
|
|
for (enum lab_node_type type = LAB_NODE_BUTTON_FIRST;
|
|
type <= LAB_NODE_BUTTON_LAST; type++) {
|
|
for (uint8_t state_set = LAB_BS_DEFAULT;
|
|
state_set <= LAB_BS_ALL; state_set++) {
|
|
destroy_img(&theme->window[THEME_INACTIVE]
|
|
.button_imgs[type][state_set]);
|
|
destroy_img(&theme->window[THEME_ACTIVE]
|
|
.button_imgs[type][state_set]);
|
|
}
|
|
}
|
|
|
|
for (int active = THEME_INACTIVE; active <= THEME_ACTIVE; active++) {
|
|
zfree_pattern(theme->window[active].titlebar_pattern);
|
|
zdrop(&theme->window[active].titlebar_fill);
|
|
zdrop(&theme->window[active].corner_top_left_normal);
|
|
zdrop(&theme->window[active].corner_top_right_normal);
|
|
zdrop(&theme->window[active].shadow_corner_top);
|
|
zdrop(&theme->window[active].shadow_corner_bottom);
|
|
zdrop(&theme->window[active].shadow_edge);
|
|
}
|
|
}
|