struct lab_data_buffer *buffer_create_cairo(uint32_t logical_width,
uint32_t logical_height, float scale);
-/*
- * Create a buffer from an image surface, for display as an icon.
- *
- * The surface is either adopted by the buffer (which takes ownership),
- * or copied and then destroyed.
- *
- * This function allows non-ARGB32 source images and converts to
- * CAIRO_FORMAT_ARGB32 if needed.
- */
-struct lab_data_buffer *buffer_convert_cairo_surface_for_icon(
- cairo_surface_t *surface, uint32_t icon_size, float scale);
-
/*
* Create a buffer which holds (and takes ownership of) raw pixel data
* in pre-multiplied ARGB32 format.
struct wlr_box *box_b);
/*
- * Fits and centers a content box (width & height) within a bounding box
- * (max_width & max_height). The content box is downscaled if necessary
- * (preserving aspect ratio) but not upscaled.
+ * Fits and centers a content box (width & height) within a bounding box.
+ * The content box is downscaled if necessary (preserving aspect ratio) but
+ * not upscaled.
*
* The returned x & y coordinates are the centered content position
* relative to the top-left corner of the bounding box.
*/
-struct wlr_box box_fit_within(int width, int height, int max_width,
- int max_height);
+struct wlr_box box_fit_within(int width, int height, struct wlr_box *bounding_box);
#endif /* LABWC_BOX_H */
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef LABWC_SCALED_IMG_BUFFER_H
+#define LABWC_SCALED_IMG_BUFFER_H
+
+#include <stdbool.h>
+
+struct wlr_scene_tree;
+struct wlr_scene_node;
+struct wlr_scene_buffer;
+struct lab_img;
+
+struct scaled_img_buffer {
+ struct scaled_scene_buffer *scaled_buffer;
+ struct wlr_scene_buffer *scene_buffer;
+ struct lab_img *img;
+ int width;
+ int height;
+ int padding;
+};
+
+/*
+ * Create an auto scaling image buffer, providing a wlr_scene_buffer node for
+ * display. It gets destroyed automatically when the backing scaled_scene_buffer
+ * is being destroyed which in turn happens automatically when the backing
+ * wlr_scene_buffer (or one of its parents) is being destroyed.
+ */
+struct scaled_img_buffer *scaled_img_buffer_create(struct wlr_scene_tree *parent,
+ struct lab_img *img, int width, int height, int padding);
+
+/* Update image, width, height and padding of the scaled_img_buffer */
+void scaled_img_buffer_update(struct scaled_img_buffer *self,
+ struct lab_img *img, int width, int height, int padding);
+
+/* Obtain scaled_img_buffer from wlr_scene_node */
+struct scaled_img_buffer *scaled_img_buffer_from_node(struct wlr_scene_node *node);
+
+#endif /* LABWC_SCALED_IMG_BUFFER_H */
void desktop_entry_init(struct server *server);
void desktop_entry_finish(struct server *server);
-struct lab_data_buffer *desktop_entry_icon_lookup(struct server *server,
+struct lab_img *desktop_entry_icon_lookup(struct server *server,
const char *app_id, int size, float scale);
/**
#ifndef LABWC_IMG_PNG_H
#define LABWC_IMG_PNG_H
-struct lab_data_buffer;
-
-void img_png_load(const char *filename, struct lab_data_buffer **buffer,
- int size, float scale);
+struct lab_data_buffer *img_png_load(const char *filename);
#endif /* LABWC_IMG_PNG_H */
#ifndef LABWC_IMG_SVG_H
#define LABWC_IMG_SVG_H
+#include <librsvg/rsvg.h>
+
struct lab_data_buffer;
-void img_svg_load(const char *filename, struct lab_data_buffer **buffer,
- int size, float scale);
+RsvgHandle *img_svg_load(const char *filename);
+
+struct lab_data_buffer *img_svg_render(RsvgHandle *svg, int w, int h,
+ int padding, double scale);
#endif /* LABWC_IMG_SVG_H */
struct lab_data_buffer;
/**
- * img_xbm_from_bitmap() - create button from monochrome bitmap
+ * img_xbm_load_from_bitmap() - create button from monochrome bitmap
* @bitmap: bitmap data array in hexadecimal xbm format
- * @buffer: cairo-surface-buffer to create
* @rgba: color
*
* Example bitmap: char button[6] = { 0x3f, 0x3f, 0x21, 0x21, 0x21, 0x3f };
*/
-void img_xbm_from_bitmap(const char *bitmap, struct lab_data_buffer **buffer,
- float *rgba);
+struct lab_data_buffer *img_xbm_load_from_bitmap(const char *bitmap, float *rgba);
/* img_xbm_load - Convert xbm file to buffer with cairo surface */
-void img_xbm_load(const char *filename, struct lab_data_buffer **buffer,
- float *rgba);
+struct lab_data_buffer *img_xbm_load(const char *filename, float *rgba);
#endif /* LABWC_IMG_XBM_H */
struct lab_data_buffer;
-void img_xpm_load(const char *filename, struct lab_data_buffer **buffer,
- int size, float scale);
+struct lab_data_buffer *img_xpm_load(const char *filename);
#endif /* LABWC_IMG_XPM_H */
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef LABWC_IMG_H
+#define LABWC_IMG_H
+
+#include <cairo.h>
+#include <stdint.h>
+#include <wayland-util.h>
+
+struct lab_img_cache;
+
+enum lab_img_type {
+ LAB_IMG_PNG,
+ LAB_IMG_SVG,
+ LAB_IMG_XBM,
+ LAB_IMG_XPM,
+};
+
+struct lab_img {
+ struct theme *theme; /* Used by modifier functions */
+ struct wl_array modifiers; /* lab_img_modifier_func_t */
+ struct lab_img_cache *cache;
+};
+
+struct lab_img *lab_img_load(enum lab_img_type type, const char *path,
+ float *xbm_color);
+
+/**
+ * lab_img_load_from_bitmap() - create button from monochrome bitmap
+ * @bitmap: bitmap data array in hexadecimal xbm format
+ * @rgba: color
+ *
+ * Example bitmap: char button[6] = { 0x3f, 0x3f, 0x21, 0x21, 0x21, 0x3f };
+ */
+struct lab_img *lab_img_load_from_bitmap(const char *bitmap, float *rgba);
+
+typedef void (*lab_img_modifier_func_t)(struct theme *theme, cairo_t *cairo,
+ int w, int h);
+
+/**
+ * lab_img_copy() - Copy lab_img
+ * @img: source image
+ *
+ * This function duplicates lab_img, but its internal cache for the image is
+ * shared.
+ */
+struct lab_img *lab_img_copy(struct lab_img *img);
+
+/**
+ * lab_img_add_modifier() - Add a modifier function to lab_img
+ * @img: source image
+ * @modifier: function that applies modifications to the image.
+ * @theme: pointer to theme passed to @modifier.
+ *
+ * "Modifiers" are functions that perform some additional drawing operation
+ * after the image is rendered on a buffer with lab_img_render(). For example,
+ * hover effects for window buttons can be drawn over the rendered image.
+ */
+void lab_img_add_modifier(struct lab_img *img, lab_img_modifier_func_t modifier,
+ struct theme *theme);
+
+/**
+ * lab_img_render() - Render lab_img to a buffer
+ * @img: source image
+ * @width: width of the created buffer
+ * @height: height of the created buffer
+ * @padding: padding around the rendered image in the buffer
+ * @scale: scale of the created buffer
+ */
+struct lab_data_buffer *lab_img_render(struct lab_img *img,
+ int width, int height, int padding, double scale);
+
+/**
+ * lab_img_destroy() - destroy lab_img
+ * @img: lab_img to destroy
+ */
+void lab_img_destroy(struct lab_img *img);
+
+#endif /* LABWC_IMG_H */
} title;
char *app_id;
+ struct lab_img *icon_img;
} state;
/* An invisible area around the view which allows resizing */
struct wlr_scene_tree *parent, struct wlr_buffer *buffer, int x, int y);
struct ssd_part *add_scene_button(struct wl_list *part_list,
enum ssd_part_type type, struct wlr_scene_tree *parent,
- struct lab_data_buffer *buffers[LAB_BS_ALL + 1], int x, int y,
+ struct lab_img *buffers[LAB_BS_ALL + 1], int x, int y,
struct view *view);
-void update_window_icon_buffer(struct wlr_scene_node *button_node,
- struct lab_data_buffer *buffer);
/* SSD internal helpers */
struct ssd_part *ssd_get_part(
#include <wlr/render/wlr_renderer.h>
#include "ssd.h"
+struct lab_img;
+
enum lab_justification {
LAB_JUSTIFY_LEFT,
LAB_JUSTIFY_CENTER,
*
* Elements in buttons[0] are all NULL since LAB_SSD_BUTTON_FIRST is 1.
*/
- struct lab_data_buffer *buttons
+ struct lab_img *button_imgs
[LAB_SSD_BUTTON_LAST + 1][LAB_BS_ALL + 1];
struct lab_data_buffer *corner_top_left_normal;
#include <drm_fourcc.h>
#include <wlr/interfaces/wlr_buffer.h>
#include "buffer.h"
-#include "common/box.h"
#include "common/mem.h"
static const struct wlr_buffer_impl data_buffer_impl;
return buffer;
}
-struct lab_data_buffer *
-buffer_convert_cairo_surface_for_icon(cairo_surface_t *surface,
- uint32_t icon_size, float scale)
-{
- assert(cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE);
-
- /*
- * Compute logical size for display and decide whether we can
- * use the image data directly (fast path). Requirements are:
- *
- * - The pixel format must be ARGB32.
- * - The image must not be so large as to need downsampling by
- * more than 2x when displayed at the target scale. wlr_scene
- * uses linear interpolation without pixel averaging, which
- * starts to skip samples if downsampling more than 2x,
- * resulting in a grainy look.
- */
- int width = cairo_image_surface_get_width(surface);
- int height = cairo_image_surface_get_height(surface);
- struct wlr_box logical =
- box_fit_within(width, height, icon_size, icon_size);
- struct lab_data_buffer *buffer;
-
- if (cairo_image_surface_get_format(surface) == CAIRO_FORMAT_ARGB32
- && width <= 2 * logical.width * scale
- && height <= 2 * logical.height * scale) {
- buffer = buffer_adopt_cairo_surface(surface);
- /* set logical size for display */
- buffer->logical_width = logical.width;
- buffer->logical_height = logical.height;
- } else {
- /* convert to ARGB32 and scale for display (slow path) */
- buffer = buffer_create_cairo(logical.width,
- logical.height, scale);
-
- cairo_t *cairo = cairo_create(buffer->surface);
- cairo_scale(cairo, (double)logical.width / width,
- (double)logical.height / height);
- cairo_set_source_surface(cairo, surface, 0, 0);
- cairo_pattern_set_filter(cairo_get_source(cairo),
- CAIRO_FILTER_GOOD);
- cairo_paint(cairo);
-
- /* ensure pixel data is updated */
- cairo_surface_flush(buffer->surface);
- /* destroy original cairo surface & context */
- cairo_surface_destroy(surface);
- cairo_destroy(cairo);
- }
-
- return buffer;
-}
-
struct lab_data_buffer *
buffer_create_from_data(void *pixel_data, uint32_t width, uint32_t height,
uint32_t stride)
}
struct wlr_box
-box_fit_within(int width, int height, int max_width, int max_height)
+box_fit_within(int width, int height, struct wlr_box *bound)
{
struct wlr_box box;
- if (width <= max_width && height <= max_height) {
+ if (width <= bound->width && height <= bound->height) {
/* No downscaling needed */
box.width = width;
box.height = height;
- } else if (width * max_height > height * max_width) {
+ } else if (width * bound->height > height * bound->width) {
/* Wider content, fit width */
- box.width = max_width;
- box.height = (height * max_width + (width / 2)) / width;
+ box.width = bound->width;
+ box.height = (height * bound->width + (width / 2)) / width;
} else {
/* Taller content, fit height */
- box.width = (width * max_height + (height / 2)) / height;
- box.height = max_height;
+ box.width = (width * bound->height + (height / 2)) / height;
+ box.height = bound->height;
}
/* Compute centered position */
- box.x = (max_width - box.width) / 2;
- box.y = (max_height - box.height) / 2;
+ box.x = bound->x + (bound->width - box.width) / 2;
+ box.y = bound->y + (bound->height - box.height) / 2;
return box;
}
'parse-bool.c',
'parse-double.c',
'scaled-font-buffer.c',
+ 'scaled-img-buffer.c',
'scaled-rect-buffer.c',
'scaled-scene-buffer.c',
'scene-helpers.c',
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <wayland-server-core.h>
+#include <wlr/types/wlr_scene.h>
+#include "buffer.h"
+#include "common/list.h"
+#include "common/mem.h"
+#include "common/scaled-img-buffer.h"
+#include "common/scaled-scene-buffer.h"
+#include "img/img.h"
+#include "node.h"
+
+static struct wl_list cached_buffers = WL_LIST_INIT(&cached_buffers);
+
+static struct lab_data_buffer *
+_create_buffer(struct scaled_scene_buffer *scaled_buffer, double scale)
+{
+ struct scaled_img_buffer *self = scaled_buffer->data;
+ struct lab_data_buffer *buffer = lab_img_render(self->img,
+ self->width, self->height, self->padding, scale);
+ return buffer;
+}
+
+static void
+_destroy(struct scaled_scene_buffer *scaled_buffer)
+{
+ struct scaled_img_buffer *self = scaled_buffer->data;
+ free(self);
+}
+
+static bool
+_equal(struct scaled_scene_buffer *scaled_buffer_a,
+ struct scaled_scene_buffer *scaled_buffer_b)
+{
+ struct scaled_img_buffer *a = scaled_buffer_a->data;
+ struct scaled_img_buffer *b = scaled_buffer_b->data;
+
+ return a->img == b->img
+ && a->width == b->width
+ && a->height == b->height
+ && a->padding == b->padding;
+}
+
+static struct scaled_scene_buffer_impl impl = {
+ .create_buffer = _create_buffer,
+ .destroy = _destroy,
+ .equal = _equal,
+};
+
+struct scaled_img_buffer *
+scaled_img_buffer_create(struct wlr_scene_tree *parent, struct lab_img *img,
+ int width, int height, int padding)
+{
+ struct scaled_scene_buffer *scaled_buffer = scaled_scene_buffer_create(
+ parent, &impl, &cached_buffers, /* drop_buffer */ true);
+ struct scaled_img_buffer *self = znew(*self);
+ self->scaled_buffer = scaled_buffer;
+ self->scene_buffer = scaled_buffer->scene_buffer;
+ self->img = img;
+ self->width = width;
+ self->height = height;
+ self->padding = padding;
+
+ scaled_buffer->data = self;
+
+ scaled_scene_buffer_request_update(scaled_buffer, width, height);
+
+ return self;
+}
+
+void
+scaled_img_buffer_update(struct scaled_img_buffer *self, struct lab_img *img,
+ int width, int height, int padding)
+{
+ self->img = img;
+ self->width = width;
+ self->height = height;
+ self->padding = padding;
+ scaled_scene_buffer_request_update(self->scaled_buffer, width, height);
+}
+
+struct scaled_img_buffer *
+scaled_img_buffer_from_node(struct wlr_scene_node *node)
+{
+ struct scaled_scene_buffer *scaled_buffer =
+ node_scaled_scene_buffer_from_node(node);
+ assert(scaled_buffer->impl == &impl);
+ return scaled_buffer->data;
+}
#include "common/macros.h"
#include "common/mem.h"
#include "common/string-helpers.h"
-#include "config.h"
#include "desktop-entry.h"
-#include "img/img-png.h"
-#include "img/img-xpm.h"
-
-#if HAVE_RSVG
-#include "img/img-svg.h"
-#endif
+#include "img/img.h"
#include "labwc.h"
return entry;
}
-struct lab_data_buffer *
+static enum lab_img_type
+convert_img_type(enum sfdo_icon_file_format fmt)
+{
+ switch (fmt) {
+ case SFDO_ICON_FILE_FORMAT_PNG:
+ return LAB_IMG_PNG;
+ case SFDO_ICON_FILE_FORMAT_SVG:
+ return LAB_IMG_SVG;
+ case SFDO_ICON_FILE_FORMAT_XPM:
+ return LAB_IMG_XPM;
+ default:
+ abort();
+ }
+}
+
+struct lab_img *
desktop_entry_icon_lookup(struct server *server, const char *app_id, int size,
float scale)
{
return NULL;
}
- struct lab_data_buffer *icon_buffer = NULL;
-
wlr_log(WLR_DEBUG, "loading icon file %s", ctx.path);
-
- switch (ctx.format) {
- case SFDO_ICON_FILE_FORMAT_PNG:
- img_png_load(ctx.path, &icon_buffer, size, scale);
- break;
- case SFDO_ICON_FILE_FORMAT_SVG:
-#if HAVE_RSVG
- img_svg_load(ctx.path, &icon_buffer, size, scale);
-#endif
- break;
- case SFDO_ICON_FILE_FORMAT_XPM:
- img_xpm_load(ctx.path, &icon_buffer, size, scale);
- break;
- }
+ struct lab_img *img = lab_img_load(convert_img_type(ctx.format), ctx.path, NULL);
free(ctx.path);
- return icon_buffer;
+
+ return img;
}
const char *
* Copyright (C) Johan Malm 2023
*/
#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
#include <cairo.h>
#include <png.h>
#include <stdbool.h>
#include <stdlib.h>
#include <wlr/util/log.h>
#include "buffer.h"
-#include "img/img-png.h"
#include "common/string-helpers.h"
-#include "labwc.h"
+#include "img/img-png.h"
/*
* cairo_image_surface_create_from_png() does not gracefully handle non-png
#undef PNG_BYTES_TO_CHECK
-void
-img_png_load(const char *filename, struct lab_data_buffer **buffer, int size,
- float scale)
+struct lab_data_buffer *
+img_png_load(const char *filename)
{
- if (*buffer) {
- wlr_buffer_drop(&(*buffer)->base);
- *buffer = NULL;
- }
if (string_null_or_empty(filename)) {
- return;
+ return NULL;
}
if (!ispng(filename)) {
- return;
+ return NULL;
}
cairo_surface_t *image = cairo_image_surface_create_from_png(filename);
if (cairo_surface_status(image)) {
- wlr_log(WLR_ERROR, "error reading png button '%s'", filename);
+ wlr_log(WLR_ERROR, "error reading png file '%s'", filename);
cairo_surface_destroy(image);
- return;
+ return NULL;
}
- *buffer = buffer_convert_cairo_surface_for_icon(image, size, scale);
+ return buffer_adopt_cairo_surface(image);
}
#include <stdlib.h>
#include <wlr/util/log.h>
#include "buffer.h"
-#include "img/img-svg.h"
#include "common/string-helpers.h"
+#include "img/img-svg.h"
#include "labwc.h"
-void
-img_svg_load(const char *filename, struct lab_data_buffer **buffer, int size,
- float scale)
+RsvgHandle *
+img_svg_load(const char *filename)
{
- if (*buffer) {
- wlr_buffer_drop(&(*buffer)->base);
- *buffer = NULL;
- }
if (string_null_or_empty(filename)) {
- return;
+ return NULL;
}
GError *err = NULL;
- RsvgRectangle viewport = { .width = size, .height = size };
RsvgHandle *svg = rsvg_handle_new_from_file(filename, &err);
if (err) {
wlr_log(WLR_DEBUG, "error reading svg %s-%s", filename, err->message);
* rsvg_handle_new_from_file() returns NULL if an error occurs,
* so there is no need to free svg here.
*/
- return;
+ return NULL;
}
+ return svg;
+}
- *buffer = buffer_create_cairo(size, size, scale);
- cairo_surface_t *image = (*buffer)->surface;
+struct lab_data_buffer *
+img_svg_render(RsvgHandle *svg, int w, int h, int padding, double scale)
+{
+ struct lab_data_buffer *buffer = buffer_create_cairo(w, h, scale);
+ cairo_surface_t *image = buffer->surface;
cairo_t *cr = cairo_create(image);
+ GError *err = NULL;
+ RsvgRectangle viewport = {
+ .x = padding,
+ .y = padding,
+ .width = w - 2 * padding,
+ .height = h - 2 * padding,
+ };
rsvg_handle_render_document(svg, cr, &viewport, &err);
if (err) {
- wlr_log(WLR_ERROR, "error rendering svg %s-%s", filename, err->message);
+ wlr_log(WLR_ERROR, "error rendering svg: %s", err->message);
g_error_free(err);
goto error;
}
-
if (cairo_surface_status(image)) {
- wlr_log(WLR_ERROR, "error reading svg button '%s'", filename);
+ wlr_log(WLR_ERROR, "error reading svg file");
goto error;
}
- cairo_surface_flush(image);
+ cairo_surface_flush(buffer->surface);
cairo_destroy(cr);
- g_object_unref(svg);
- return;
+ return buffer;
error:
- wlr_buffer_drop(&(*buffer)->base);
- *buffer = NULL;
+ wlr_buffer_drop(&buffer->base);
cairo_destroy(cr);
g_object_unref(svg);
+ return NULL;
}
#include <stdlib.h>
#include <string.h>
#include <drm_fourcc.h>
+#include "img/img.h"
#include "img/img-xbm.h"
#include "common/grab-file.h"
#include "common/mem.h"
return pixmap;
}
-void
-img_xbm_from_bitmap(const char *bitmap, struct lab_data_buffer **buffer,
- float *rgba)
+struct lab_data_buffer *
+img_xbm_load_from_bitmap(const char *bitmap, float *rgba)
{
struct pixmap pixmap = {0};
- if (*buffer) {
- wlr_buffer_drop(&(*buffer)->base);
- *buffer = NULL;
- }
color = argb32(rgba);
pixmap = parse_xbm_builtin(bitmap, 6);
- *buffer = buffer_create_from_data(pixmap.data, pixmap.width, pixmap.height,
+
+ return buffer_create_from_data(pixmap.data, pixmap.width, pixmap.height,
pixmap.width * 4);
}
-void
-img_xbm_load(const char *filename, struct lab_data_buffer **buffer,
- float *rgba)
+struct lab_data_buffer *
+img_xbm_load(const char *filename, float *rgba)
{
struct pixmap pixmap = {0};
- if (*buffer) {
- wlr_buffer_drop(&(*buffer)->base);
- *buffer = NULL;
- }
if (string_null_or_empty(filename)) {
- return;
+ return NULL;
}
color = argb32(rgba);
}
buf_reset(&token_buf);
if (!pixmap.data) {
- return;
+ return NULL;
}
- /* Create buffer */
- if (pixmap.data) {
- *buffer = buffer_create_from_data(pixmap.data, pixmap.width,
- pixmap.height, pixmap.width * 4);
- }
+ return buffer_create_from_data(pixmap.data, pixmap.width,
+ pixmap.height, pixmap.width * 4);
}
return surface;
}
-void
-img_xpm_load(const char *filename, struct lab_data_buffer **buffer, int size,
- float scale)
+struct lab_data_buffer *
+img_xpm_load(const char *filename)
{
- if (*buffer) {
- wlr_buffer_drop(&(*buffer)->base);
- *buffer = NULL;
- }
-
struct file_handle h = {0};
h.infile = fopen(filename, "rb");
if (!h.infile) {
wlr_log(WLR_ERROR, "error opening '%s'", filename);
- return;
+ return NULL;
}
cairo_surface_t *surface = xpm_load_to_surface(&h);
+ struct lab_data_buffer *buffer = NULL;
if (surface) {
- *buffer = buffer_convert_cairo_surface_for_icon(surface, size,
- scale);
+ buffer = buffer_adopt_cairo_surface(surface);
} else {
wlr_log(WLR_ERROR, "error loading '%s'", filename);
}
fclose(h.infile);
buf_reset(&h.buf);
+
+ return buffer;
}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <assert.h>
+#include <wlr/util/log.h>
+#include "buffer.h"
+#include "config.h"
+#include "common/box.h"
+#include "common/graphic-helpers.h"
+#include "common/macros.h"
+#include "common/mem.h"
+#include "common/string-helpers.h"
+#include "img/img.h"
+#include "img/img-png.h"
+#if HAVE_RSVG
+#include "img/img-svg.h"
+#endif
+#include "img/img-xbm.h"
+#include "img/img-xpm.h"
+#include "labwc.h"
+#include "theme.h"
+
+struct lab_img_cache {
+ enum lab_img_type type;
+ /* lab_img_cache is refcounted to be shared by multiple lab_imgs */
+ int refcount;
+
+ /* Handler for the loaded image file */
+ struct lab_data_buffer *buffer; /* for PNG/XBM/XPM image */
+#if HAVE_RSVG
+ RsvgHandle *svg; /* for SVG image */
+#endif
+};
+
+static struct lab_img *
+create_img(struct lab_img_cache *cache)
+{
+ struct lab_img *img = znew(*img);
+ img->cache = cache;
+ cache->refcount++;
+ wl_array_init(&img->modifiers);
+ return img;
+}
+
+struct lab_img *
+lab_img_load(enum lab_img_type type, const char *path, float *xbm_color)
+{
+ if (string_null_or_empty(path)) {
+ return NULL;
+ }
+
+ struct lab_img_cache *cache = znew(*cache);
+ cache->type = type;
+
+ switch (type) {
+ case LAB_IMG_PNG:
+ cache->buffer = img_png_load(path);
+ break;
+ case LAB_IMG_XBM:
+ assert(xbm_color);
+ cache->buffer = img_xbm_load(path, xbm_color);
+ break;
+ case LAB_IMG_XPM:
+ cache->buffer = img_xpm_load(path);
+ break;
+ case LAB_IMG_SVG:
+#if HAVE_RSVG
+ cache->svg = img_svg_load(path);
+#endif
+ break;
+ }
+
+ bool img_is_loaded = (bool)cache->buffer;
+#if HAVE_RSVG
+ img_is_loaded |= (bool)cache->svg;
+#endif
+
+ if (img_is_loaded) {
+ return create_img(cache);
+ } else {
+ free(cache);
+ return NULL;
+ }
+}
+
+struct lab_img *
+lab_img_load_from_bitmap(const char *bitmap, float *rgba)
+{
+ struct lab_data_buffer *buffer = img_xbm_load_from_bitmap(bitmap, rgba);
+ if (!buffer) {
+ return NULL;
+ }
+
+ struct lab_img_cache *cache = znew(*cache);
+ cache->type = LAB_IMG_XBM;
+ cache->buffer = buffer;
+
+ return create_img(cache);
+}
+
+struct lab_img *
+lab_img_copy(struct lab_img *img)
+{
+ struct lab_img *new_img = create_img(img->cache);
+ wl_array_copy(&new_img->modifiers, &img->modifiers);
+ return new_img;
+}
+
+void
+lab_img_add_modifier(struct lab_img *img, lab_img_modifier_func_t modifier,
+ struct theme *theme)
+{
+ img->theme = theme;
+ lab_img_modifier_func_t *mod = wl_array_add(&img->modifiers, sizeof(*mod));
+ *mod = modifier;
+}
+
+/*
+ * Takes a source surface from PNG/XBM/XPM file and output a buffer for the
+ * given size. The source surface is placed at the center of the output buffer
+ * and shrunk if it overflows from the output buffer.
+ */
+static struct lab_data_buffer *
+render_cairo_surface(cairo_surface_t *surface, int width, int height,
+ int padding, double scale)
+{
+ assert(surface);
+ int src_w = cairo_image_surface_get_width(surface);
+ int src_h = cairo_image_surface_get_height(surface);
+
+ struct lab_data_buffer *buffer =
+ buffer_create_cairo(width, height, scale);
+ cairo_t *cairo = cairo_create(buffer->surface);
+
+ struct wlr_box container = {
+ .x = padding,
+ .y = padding,
+ .width = width - 2 * padding,
+ .height = height - 2 * padding,
+ };
+
+ struct wlr_box src_box = box_fit_within(src_w, src_h, &container);
+ double scene_scale = MIN(1.0, (double_t)container.width / (double)src_w);
+ cairo_translate(cairo, src_box.x, src_box.y);
+ cairo_scale(cairo, scene_scale, scene_scale);
+ cairo_set_source_surface(cairo, surface, 0, 0);
+ cairo_pattern_set_filter(cairo_get_source(cairo), CAIRO_FILTER_GOOD);
+ cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(cairo);
+
+ cairo_destroy(cairo);
+
+ return buffer;
+}
+
+struct lab_data_buffer *
+lab_img_render(struct lab_img *img, int width, int height, int padding,
+ double scale)
+{
+ struct lab_data_buffer *buffer = NULL;
+
+ /* Render the image into the buffer for the given size */
+ switch (img->cache->type) {
+ case LAB_IMG_PNG:
+ case LAB_IMG_XBM:
+ case LAB_IMG_XPM:
+ buffer = render_cairo_surface(img->cache->buffer->surface,
+ width, height, padding, scale);
+ break;
+#if HAVE_RSVG
+ case LAB_IMG_SVG:
+ buffer = img_svg_render(img->cache->svg, width, height,
+ padding, scale);
+ break;
+#endif
+ default:
+ break;
+ }
+
+ if (!buffer) {
+ return NULL;
+ }
+
+ /* Apply modifiers to the buffer (e.g. draw hover overlay) */
+ cairo_t *cairo = cairo_create(buffer->surface);
+ lab_img_modifier_func_t *modifier;
+ wl_array_for_each(modifier, &img->modifiers) {
+ cairo_save(cairo);
+ (*modifier)(img->theme, cairo, width, height);
+ cairo_restore(cairo);
+ }
+
+ cairo_surface_flush(buffer->surface);
+ cairo_destroy(cairo);
+
+ return buffer;
+}
+
+void
+lab_img_destroy(struct lab_img *img)
+{
+ if (!img) {
+ return;
+ }
+
+ struct lab_img_cache *cache = img->cache;
+ cache->refcount--;
+
+ if (cache->refcount == 0) {
+ if (cache->buffer) {
+ wlr_buffer_drop(&cache->buffer->base);
+ }
+#if HAVE_RSVG
+ if (cache->svg) {
+ g_object_unref(cache->svg);
+ }
+#endif
+ free(cache);
+ }
+
+ wl_array_release(&img->modifiers);
+ free(img);
+}
labwc_sources += files(
+ 'img.c',
'img-png.c',
'img-xbm.c',
'img-xpm.c'
#include "common/box.h"
#include "common/list.h"
#include "common/mem.h"
+#include "common/scaled-img-buffer.h"
#include "labwc.h"
#include "node.h"
#include "ssd-internal.h"
return part;
}
-static struct wlr_box
-get_scale_box(struct lab_data_buffer *buffer, int container_width,
- int container_height)
-{
- return box_fit_within(buffer->logical_width, buffer->logical_height,
- container_width, container_height);
-}
-
-void
-update_window_icon_buffer(struct wlr_scene_node *button_node,
- struct lab_data_buffer *buffer)
-{
- struct wlr_scene_buffer *scene_buffer =
- wlr_scene_buffer_from_node(button_node);
-
- struct wlr_box icon_geo = get_scale_box(buffer,
- rc.theme->window_button_width,
- rc.theme->window_button_height);
-
- wlr_scene_buffer_set_buffer(scene_buffer, &buffer->base);
- wlr_scene_buffer_set_dest_size(scene_buffer,
- icon_geo.width, icon_geo.height);
- wlr_scene_node_set_position(button_node, icon_geo.x, icon_geo.y);
-}
-
struct ssd_part *
add_scene_button(struct wl_list *part_list, enum ssd_part_type type,
struct wlr_scene_tree *parent,
- struct lab_data_buffer *buffers[LAB_BS_ALL + 1],
+ struct lab_img *imgs[LAB_BS_ALL + 1],
int x, int y, struct view *view)
{
struct ssd_part *button_root = add_scene_part(part_list, type);
/* Icons */
struct wlr_scene_node *nodes[LAB_BS_ALL + 1] = {0};
for (uint8_t state_set = 0; state_set <= LAB_BS_ALL; state_set++) {
- if (!buffers[state_set]) {
+ if (!imgs[state_set]) {
continue;
}
- struct lab_data_buffer *icon_buffer = buffers[state_set];
- struct wlr_box icon_geo = get_scale_box(icon_buffer,
- rc.theme->window_button_width, rc.theme->window_button_height);
- struct ssd_part *icon_part = add_scene_buffer(part_list, type,
- parent, &icon_buffer->base, icon_geo.x, icon_geo.y);
- /* Make sure big icons are scaled down if necessary */
- wlr_scene_buffer_set_dest_size(
- wlr_scene_buffer_from_node(icon_part->node),
- icon_geo.width, icon_geo.height);
+ struct ssd_part *icon_part = add_scene_part(part_list, type);
+ struct scaled_img_buffer *img_buffer = scaled_img_buffer_create(
+ parent, imgs[state_set], rc.theme->window_button_width,
+ rc.theme->window_button_height, /* padding */ 0);
+ assert(img_buffer);
+ icon_part->node = &img_buffer->scene_buffer->node;
wlr_scene_node_set_enabled(icon_part->node, false);
nodes[state_set] = icon_part->node;
}
#include "config.h"
#include "common/mem.h"
#include "common/scaled-font-buffer.h"
+#include "common/scaled-img-buffer.h"
#include "common/scene-helpers.h"
#include "common/string-helpers.h"
#if HAVE_LIBSFDO
#include "desktop-entry.h"
#endif
+#include "img/img.h"
#include "labwc.h"
#include "node.h"
#include "ssd-internal.h"
int y = (theme->titlebar_height - theme->window_button_height) / 2;
wl_list_for_each(b, &rc.title_buttons_left, link) {
- struct lab_data_buffer **buffers =
- theme->window[active].buttons[b->type];
+ struct lab_img **imgs =
+ theme->window[active].button_imgs[b->type];
add_scene_button(&subtree->parts, b->type, parent,
- buffers, x, y, view);
+ imgs, x, y, view);
x += theme->window_button_width + theme->window_button_spacing;
}
x = width - theme->window_titlebar_padding_width + theme->window_button_spacing;
wl_list_for_each_reverse(b, &rc.title_buttons_right, link) {
x -= theme->window_button_width + theme->window_button_spacing;
- struct lab_data_buffer **buffers =
- theme->window[active].buttons[b->type];
+ struct lab_img **imgs =
+ theme->window[active].button_imgs[b->type];
add_scene_button(&subtree->parts, b->type, parent,
- buffers, x, y, view);
+ imgs, x, y, view);
}
} FOR_EACH_END
if (ssd->state.app_id) {
zfree(ssd->state.app_id);
}
+ if (ssd->state.icon_img) {
+ lab_img_destroy(ssd->state.icon_img);
+ }
wlr_scene_node_destroy(&ssd->titlebar.tree->node);
ssd->titlebar.tree = NULL;
* was considered, but these settings have distinct purposes
* already and are zero by default.
*/
- int hpad = theme->window_button_width / 10;
- int icon_size = MIN(theme->window_button_width - 2 * hpad,
- theme->window_button_height);
+ int icon_padding = theme->window_button_width / 10;
+ int icon_size = MIN(theme->window_button_width - 2 * icon_padding,
+ theme->window_button_height - 2 * icon_padding);
/*
* Load/render icons at the max scale of any usable output (at
*/
float icon_scale = output_max_scale(ssd->view->server);
- struct lab_data_buffer *icon_buffer = desktop_entry_icon_lookup(
+ struct lab_img *icon_img = desktop_entry_icon_lookup(
ssd->view->server, app_id, icon_size, icon_scale);
- if (!icon_buffer) {
+ if (!icon_img) {
wlr_log(WLR_DEBUG, "icon could not be loaded for %s", app_id);
return;
}
/* Replace all the buffers in the button with the window icon */
struct ssd_button *button = node_ssd_button_from_node(part->node);
for (uint8_t state_set = 0; state_set <= LAB_BS_ALL; state_set++) {
- if (button->nodes[state_set]) {
- update_window_icon_buffer(button->nodes[state_set],
- icon_buffer);
+ struct wlr_scene_node *node = button->nodes[state_set];
+ if (node) {
+ struct scaled_img_buffer *img_buffer =
+ scaled_img_buffer_from_node(node);
+ scaled_img_buffer_update(img_buffer, icon_img,
+ theme->window_button_width,
+ theme->window_button_height,
+ icon_padding);
}
}
} FOR_EACH_END
- wlr_buffer_drop(&icon_buffer->base);
+ if (ssd->state.icon_img) {
+ lab_img_destroy(ssd->state.icon_img);
+ }
+ ssd->state.icon_img = icon_img;
#endif
}
#include "common/parse-double.h"
#include "common/string-helpers.h"
#include "config/rcxml.h"
-#include "img/img-png.h"
+#include "img/img.h"
#include "labwc.h"
-
-#if HAVE_RSVG
-#include "img/img-svg.h"
-#endif
-
-#include "img/img-xbm.h"
#include "theme.h"
#include "buffer.h"
#include "ssd.h"
}
}
-struct icon_drawing_context {
- struct lab_data_buffer *buffer;
- cairo_t *cairo;
-};
-
-static struct icon_drawing_context
-copy_icon_buffer(struct theme *theme, struct lab_data_buffer *icon_buffer)
-{
- assert(icon_buffer);
-
- cairo_surface_t *surface = icon_buffer->surface;
- int icon_width = cairo_image_surface_get_width(surface);
- int icon_height = cairo_image_surface_get_height(surface);
-
- int width = theme->window_button_width;
- int height = theme->window_button_height;
-
- /*
- * Proportionately increase size of hover_buffer if the non-hover
- * 'donor' buffer is larger than the allocated space. It will get
- * scaled down again by wlroots when rendered and as required by the
- * current output scale.
- *
- * This ensures that icons > width or > height keep their aspect ratio
- * and are rendered the same as without the hover overlay.
- */
- double scale = (width && height) ?
- MAX((double)icon_width / width, (double)icon_height / height) : 1.0;
- if (scale < 1.0) {
- scale = 1.0;
- }
- int buffer_width = (double)width * scale;
- int buffer_height = (double)height * scale;
- struct lab_data_buffer *buffer = buffer_create_cairo(
- buffer_width, buffer_height, 1.0);
- cairo_t *cairo = cairo_create(buffer->surface);
-
- cairo_set_source_surface(cairo, surface,
- (buffer_width - icon_width) / 2, (buffer_height - icon_height) / 2);
- cairo_paint(cairo);
-
- /*
- * Scale cairo context so that we can draw hover overlay or rounded
- * corner on this buffer in the scene coordinates.
- */
- cairo_scale(cairo, scale, scale);
-
- return (struct icon_drawing_context){
- .buffer = buffer,
- .cairo = cairo,
- };
-}
-
+/* Draw rounded-rectangular hover overlay on the button buffer */
static void
-create_hover_fallback(struct theme *theme,
- struct lab_data_buffer **hover_buffer,
- struct lab_data_buffer *icon_buffer)
+draw_hover_overlay_on_button(struct theme *theme, cairo_t *cairo, int w, int h)
{
- assert(icon_buffer);
- assert(!*hover_buffer);
-
- int width = theme->window_button_width;
- int height = theme->window_button_height;
-
- struct icon_drawing_context ctx = copy_icon_buffer(theme, icon_buffer);
- *hover_buffer = ctx.buffer;
- cairo_t *cairo = ctx.cairo;
-
/* Overlay (pre-multiplied alpha) */
float overlay_color[4] = { 0.15f, 0.15f, 0.15f, 0.3f};
set_cairo_color(cairo, overlay_color);
- int radius = theme->window_button_hover_bg_corner_radius;
+ int r = theme->window_button_hover_bg_corner_radius;
cairo_new_sub_path(cairo);
- cairo_arc(cairo, radius, radius, radius, 180 * deg, 270 * deg);
- cairo_line_to(cairo, width - radius, 0);
- cairo_arc(cairo, width - radius, radius, radius, -90 * deg, 0 * deg);
- cairo_line_to(cairo, width, height - radius);
- cairo_arc(cairo, width - radius, height - radius, radius, 0 * deg, 90 * deg);
- cairo_line_to(cairo, radius, height);
- cairo_arc(cairo, radius, height - radius, radius, 90 * deg, 180 * deg);
+ 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);
-
- cairo_surface_flush(cairo_get_target(cairo));
- cairo_destroy(cairo);
}
+/* Round the buffer for the leftmost button in the titlebar */
static void
-create_rounded_buffer(struct theme *theme, enum corner corner,
- struct lab_data_buffer **rounded_buffer,
- struct lab_data_buffer *icon_buffer)
+round_left_corner_button(struct theme *theme, cairo_t *cairo, int w, int h)
{
- struct icon_drawing_context ctx = copy_icon_buffer(theme, icon_buffer);
- *rounded_buffer = ctx.buffer;
- cairo_t *cairo = ctx.cairo;
-
- int width = theme->window_button_width;
- int height = theme->window_button_height;
-
/*
- * Round the corner button by cropping the region within the window
- * border. See the picture in #2189 for reference.
+ * Position of the topleft corner of the titlebar relative to the
+ * leftmost button
*/
- int margin_x = theme->window_titlebar_padding_width;
- int margin_y = (theme->titlebar_height - theme->window_button_height) / 2;
- float white[4] = {1, 1, 1, 1};
- struct rounded_corner_ctx rounded_ctx = {
- .box = &(struct wlr_box){
- .width = margin_x + width,
- .height = margin_y + height,
- },
- .radius = rc.corner_radius,
- .line_width = theme->border_width,
- .fill_color = white,
- .border_color = white,
- .corner = corner,
- };
- struct lab_data_buffer *mask_buffer = rounded_rect(&rounded_ctx);
+ double x = -theme->window_titlebar_padding_width;
+ double y = -(theme->titlebar_height - theme->window_button_height) / 2;
+
+ double r = rc.corner_radius - (double)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_set_source_surface(cairo, mask_buffer->surface,
- (corner == LAB_CORNER_TOP_LEFT) ? -margin_x : 0,
- -margin_y);
- cairo_paint(cairo);
+ cairo_fill(cairo);
+}
- cairo_surface_flush(cairo_get_target(cairo));
- cairo_destroy(cairo);
- wlr_buffer_drop(&mask_buffer->base);
+/* Round the buffer for the rightmost button in the titlebar */
+static void
+round_right_corner_button(struct theme *theme, 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(theme, cairo, w, h);
}
/*
static void
load_button(struct theme *theme, struct button *b, int active)
{
- struct lab_data_buffer *(*buttons)[LAB_BS_ALL + 1] =
- theme->window[active].buttons;
- struct lab_data_buffer **buffer = &buttons[b->type][b->state_set];
+ 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];
- zdrop(buffer);
-
- int size = theme->window_button_height;
- float scale = 1; /* TODO: account for output scale */
+ assert(!*img);
/* PNG */
get_button_filename(filename, sizeof(filename), b->name,
active ? "-active.png" : "-inactive.png");
- img_png_load(filename, buffer, size, scale);
+ *img = lab_img_load(LAB_IMG_PNG, filename, rgba);
#if HAVE_RSVG
/* SVG */
- if (!*buffer) {
+ if (!*img) {
get_button_filename(filename, sizeof(filename), b->name,
active ? "-active.svg" : "-inactive.svg");
- img_svg_load(filename, buffer, size, scale);
+ *img = lab_img_load(LAB_IMG_SVG, filename, rgba);
}
#endif
/* XBM */
- if (!*buffer) {
+ if (!*img) {
get_button_filename(filename, sizeof(filename), b->name, ".xbm");
- img_xbm_load(filename, buffer, rgba);
+ *img = lab_img_load(LAB_IMG_XBM, filename, rgba);
}
/*
* XBM (alternative name)
* For example max_hover_toggled instead of max_toggled_hover
*/
- if (!*buffer && b->alt_name) {
+ if (!*img && b->alt_name) {
get_button_filename(filename, sizeof(filename),
b->alt_name, ".xbm");
- img_xbm_load(filename, buffer, rgba);
+ *img = lab_img_load(LAB_IMG_XBM, filename, rgba);
}
/*
* Applicable to basic buttons such as max, max_toggled and iconify.
* There are no bitmap fallbacks for *_hover icons.
*/
- if (!*buffer && b->fallback_button) {
- img_xbm_from_bitmap(b->fallback_button, buffer, rgba);
+ 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 (!*buffer && (b->state_set & LAB_BS_HOVERD)) {
- uint8_t non_hover_state_set = b->state_set & ~LAB_BS_HOVERD;
- create_hover_fallback(theme, buffer,
- buttons[b->type][non_hover_state_set]);
+ if (!*img && (b->state_set & LAB_BS_HOVERD)) {
+ struct lab_img *non_hover_img =
+ button_imgs[b->type][b->state_set & ~LAB_BS_HOVERD];
+ *img = lab_img_copy(non_hover_img);
+ lab_img_add_modifier(*img,
+ draw_hover_overlay_on_button, theme);
}
/*
* If the loaded button is at the corner of the titlebar, also create
* rounded variants.
*/
- uint8_t rounded_state_set = b->state_set | LAB_BS_ROUNDED;
+ struct lab_img **rounded_img =
+ &button_imgs[b->type][b->state_set | LAB_BS_ROUNDED];
struct title_button *leftmost_button;
wl_list_for_each(leftmost_button,
&rc.title_buttons_left, link) {
if (leftmost_button->type == b->type) {
- create_rounded_buffer(theme, LAB_CORNER_TOP_LEFT,
- &buttons[b->type][rounded_state_set], *buffer);
+ *rounded_img = lab_img_copy(*img);
+ lab_img_add_modifier(*rounded_img,
+ round_left_corner_button, theme);
}
break;
}
wl_list_for_each_reverse(rightmost_button,
&rc.title_buttons_right, link) {
if (rightmost_button->type == b->type) {
- create_rounded_buffer(theme, LAB_CORNER_TOP_RIGHT,
- &buttons[b->type][rounded_state_set], *buffer);
+ *rounded_img = lab_img_copy(*img);
+ lab_img_add_modifier(*rounded_img,
+ round_right_corner_button, theme);
}
break;
}
create_shadows(theme);
}
+static void destroy_img(struct lab_img **img)
+{
+ lab_img_destroy(*img);
+ *img = NULL;
+}
+
void
theme_finish(struct theme *theme)
{
type <= LAB_SSD_BUTTON_LAST; type++) {
for (uint8_t state_set = 0; state_set <= LAB_BS_ALL;
state_set++) {
- zdrop(&theme->window[THEME_INACTIVE]
- .buttons[type][state_set]);
- zdrop(&theme->window[THEME_ACTIVE]
- .buttons[type][state_set]);
+ destroy_img(&theme->window[THEME_INACTIVE]
+ .button_imgs[type][state_set]);
+ destroy_img(&theme->window[THEME_ACTIVE]
+ .button_imgs[type][state_set]);
}
}