From d4933235517ec9569f62f025c492d2f0a483f339 Mon Sep 17 00:00:00 2001 From: Wei-Hsin Yeh Date: Tue, 12 Nov 2024 09:49:36 +0800 Subject: [PATCH] Render drop shadow for active window Implement the twin_window_drop_shadow() function to handle the pixels within the drop shadow area of the active window's pixel map. The drop shadow effect of the window is only visible when the window is on the top layer, ensuring the active window stands out visually. Add the twin_stack_blur() function to implement Mario's Stack Blur algorithm, which blurs the target pixel map. Additionally, create a blur window in apps_blur() to show the effect of twin_stack_blur(). Implement the twin_shadow_border() function to create a darker border of the active window that gives a more dimensional appearance. The function twin_window_drop_shadow() will first apply the twin_shadow_border() and then apply twin_stack_blur() to blur the darker border. Furthermore, implement the twin_cover() function to cover the target pixels with the desired color. Ref: https://melatonin.dev/blog/implementing-marios-stack-blur-15-times-in-cpp/ Close #34 Signed-off-by: Wei-Hsin Yeh --- apps/multi.c | 52 ++++++++++ configs/Kconfig | 22 +++++ include/twin.h | 34 +++++++ include/twin_private.h | 55 ++++++++++- src/draw-common.c | 218 +++++++++++++++++++++++++++++++++++++++++ src/image.c | 2 +- src/pixmap.c | 3 + src/screen.c | 2 +- src/window.c | 98 ++++++++++++++++-- 9 files changed, 477 insertions(+), 9 deletions(-) diff --git a/apps/multi.c b/apps/multi.c index c33fd30..3b60493 100644 --- a/apps/multi.c +++ b/apps/multi.c @@ -4,9 +4,11 @@ * All rights reserved. */ +#include #include "apps_multi.h" #define D(x) twin_double_to_fixed(x) +#define ASSET_PATH "assets/" static void apps_line_start(twin_screen_t *screen, int x, int y, int w, int h) { @@ -272,6 +274,55 @@ static void apps_flower_start(twin_screen_t *screen, int x, int y, int w, int h) twin_window_show(window); } +static void apps_blur(twin_screen_t *screen, int x, int y, int w, int h) +{ + twin_pixmap_t *raw_background = NULL; +#if defined(CONFIG_LOADER_PNG) + raw_background = twin_pixmap_from_file(ASSET_PATH "tux.png", TWIN_ARGB32); +#endif + if (!raw_background) + return; + twin_window_t *window = twin_window_create( + screen, TWIN_ARGB32, TwinWindowApplication, x, y, w, h); + twin_window_set_name(window, "Blur"); + twin_pixmap_t *scaled_background = twin_pixmap_create( + TWIN_ARGB32, window->pixmap->width, window->pixmap->height); + twin_fixed_t sx, sy; + sx = twin_fixed_div( + twin_int_to_fixed(raw_background->width), + twin_int_to_fixed(window->client.right - window->client.left)); + sy = twin_fixed_div( + twin_int_to_fixed(raw_background->height), + twin_int_to_fixed(window->client.bottom - window->client.top)); + + twin_matrix_scale(&raw_background->transform, sx, sy); + twin_operand_t srcop = { + .source_kind = TWIN_PIXMAP, + .u.pixmap = raw_background, + }; + + twin_composite(scaled_background, 0, 0, &srcop, 0, 0, 0, 0, 0, TWIN_SOURCE, + scaled_background->width, scaled_background->height); + + twin_pointer_t src, dst; + for (int y = window->client.top; y < window->client.bottom; y++) + for (int x = window->client.left; x < window->client.right; x++) { + src = + twin_pixmap_pointer(scaled_background, x - window->client.left, + y - window->client.top); + dst = twin_pixmap_pointer(window->pixmap, x, y); + *dst.argb32 = *src.argb32 | 0xff000000; + } + twin_stack_blur(window->pixmap, 5, window->client.left, + window->client.right, window->client.top, + window->client.bottom); + + twin_pixmap_destroy(scaled_background); + twin_pixmap_destroy(raw_background); + twin_window_show(window); + return; +} + void apps_multi_start(twin_screen_t *screen, const char *name, int x, @@ -286,4 +337,5 @@ void apps_multi_start(twin_screen_t *screen, apps_ascii_start(screen, x += 20, y += 20, w, h); apps_jelly_start(screen, x += 20, y += 20, w / 2, h); apps_flower_start(screen, x += 20, y += 20, w, h); + apps_blur(screen, x += 20, y += 20, w / 2, h / 2); } diff --git a/configs/Kconfig b/configs/Kconfig index 7e281f1..516d650 100644 --- a/configs/Kconfig +++ b/configs/Kconfig @@ -55,6 +55,28 @@ config CURSOR default n depends on !BACKEND_VNC +config DROP_SHADOW + bool "Render drop shadow for active window" + default y + +config HORIZONTAL_OFFSET + int "Horizontal offset" + default 1 + range 1 10 + depends on DROP_SHADOW + +config VERTICAL_OFFSET + int "Vertical offset" + default 1 + range 1 10 + depends on DROP_SHADOW + +config SHADOW_BLUR + int "Shadow blur radius" + default 10 + range 1 10 + depends on DROP_SHADOW + endmenu menu "Image Loaders" diff --git a/include/twin.h b/include/twin.h index 28a1d17..07c2011 100644 --- a/include/twin.h +++ b/include/twin.h @@ -195,6 +195,15 @@ typedef struct _twin_pixmap { * Pixels */ twin_animation_t *animation; + +#if defined(CONFIG_DROP_SHADOW) + /* + * When the pixel map is within the active window, it will have a drop + * shadow to enhance its visual distinction. + */ + bool shadow; +#endif + twin_pointer_t p; /* * When representing a window, this point @@ -423,6 +432,13 @@ typedef void (*twin_destroy_func_t)(twin_window_t *window); struct _twin_window { twin_screen_t *screen; twin_pixmap_t *pixmap; + +#if defined(CONFIG_DROP_SHADOW) + /* Set the shadow range for horizontal and vertical directions. */ + twin_coord_t shadow_x; + twin_coord_t shadow_y; +#endif + twin_window_style_t style; twin_rect_t client; twin_rect_t damage; @@ -652,8 +668,26 @@ void twin_fill(twin_pixmap_t *dst, * draw-common.c */ +/* Blur the specified area in the pixel map. */ +void twin_stack_blur(twin_pixmap_t *px, + int radius, + twin_coord_t left, + twin_coord_t right, + twin_coord_t top, + twin_coord_t bottom); + void twin_premultiply_alpha(twin_pixmap_t *px); +/* + * Overwrite the original pixel values for a specified number of pixels in + * width. + */ +void twin_cover(twin_pixmap_t *dst, + twin_argb32_t color, + twin_coord_t x, + twin_coord_t y, + twin_coord_t width); + /* * event.c */ diff --git a/include/twin_private.h b/include/twin_private.h index e4ee7c2..21bb825 100644 --- a/include/twin_private.h +++ b/include/twin_private.h @@ -181,6 +181,15 @@ typedef int64_t twin_xfixed_t; (((t) = twin_get_8(d, i) + twin_get_8(s, i)), (twin_argb32_t) twin_sat(t) \ << (i)) +#define _twin_add_ARGB(s, d, i, t) (((t) = (s) + twin_get_8(d, i))) +#define _twin_add(s, d, t) (((t) = (s) + (d))) +#define _twin_div(d, den, i, t) \ + (((t) = (d) / (den)), (t) = twin_get_8((t), 0), \ + (twin_argb32_t) twin_sat(t) << (i)) +#define _twin_sub_ARGB(s, d, i, t) (((t) = (s) - twin_get_8(d, i))) +#define _twin_sub(s, d, t) (((t) = (s) - (d))) +#define twin_put_8(d, i, t) (((t) = (d) << (i))) + #define twin_argb32_to_rgb16(s) \ ((((s) >> 3) & 0x001f) | (((s) >> 5) & 0x07e0) | (((s) >> 8) & 0xf800)) #define twin_rgb16_to_argb32(s) \ @@ -188,6 +197,35 @@ typedef int64_t twin_xfixed_t; ((((s) << 5) & 0xfc00) | (((s) >> 1) & 0x300)) | \ ((((s) << 8) & 0xf80000) | (((s) << 3) & 0x70000)) | 0xff000000) +#ifndef min +#if defined(__GNUC__) || defined(__clang__) +#define min(x, y) \ + ({ \ + typeof(x) _x = (x); \ + typeof(y) _y = (y); \ + (void) (&_x == &_y); \ + _x < _y ? _x : _y; \ + }) +#else +/* Generic implementation: potential side effects */ +#define min(x, y) ((x) < (y) ? (x) : (y)) +#endif +#endif +#ifndef max +#if defined(__GNUC__) || defined(__clang__) +#define max(x, y) \ + ({ \ + typeof(x) _x = (x); \ + typeof(y) _y = (y); \ + (void) (&_x == &_y); \ + _x > _y ? _x : _y; \ + }) +#else +/* Generic implementation: potential side effects */ +#define max(x, y) ((x) > (y) ? (x) : (y)) +#endif +#endif + typedef union { twin_pointer_t p; twin_argb32_t c; @@ -468,7 +506,7 @@ void _twin_path_sfinish(twin_path_t *path); #define twin_glyph_snap_y(g) (twin_glyph_snap_x(g) + twin_glyph_n_snap_x(g)) /* - * dispatch stuff + * Dispatch stuff */ typedef struct _twin_queue { struct _twin_queue *next; @@ -593,6 +631,21 @@ void _twin_button_init(twin_button_t *button, twin_style_t font_style, twin_dispatch_proc_t dispatch); +/* + * Visual effect stuff + */ + +#if defined(CONFIG_DROP_SHADOW) +/* + * Add a shadow with the specified color, horizontal offset, and vertical + * offset. + */ +void twin_shadow_border(twin_pixmap_t *shadow, + twin_argb32_t color, + twin_coord_t shift_x, + twin_coord_t shift_y); +#endif + /* utility */ #ifdef _MSC_VER diff --git a/src/draw-common.c b/src/draw-common.c index 005bd1f..4c84e1d 100644 --- a/src/draw-common.c +++ b/src/draw-common.c @@ -7,6 +7,209 @@ #include "twin_private.h" +#define TWIN_TITLE_HEIGHT 20 + +static void _twin_apply_stack_blur(twin_pixmap_t *trg_px, + twin_pixmap_t *src_px, + int radius, + int first_str, + int first_end, + int second_str, + int second_end, + bool horiz_scan) +{ + int den = (radius + 1) * (radius + 1); + twin_pointer_t src_ptr, trg_ptr, old_ptr, new_ptr; + twin_argb32_t sumInR, sumOutR, sumR, sumInG, sumOutG, sumG, sumInB, sumOutB, + sumB, sumInA, sumOutA, sumA, _cur, _old, _new, _src; + uint16_t tmpR, tmpG, tmpB, tmpA; + for (int first = first_str; first < first_end; first++) { + sumInR = sumOutR = sumR = sumInG = sumOutG = sumG = sumInB = sumOutB = + sumB = sumInA = sumOutA = sumA = 0x00000000; + + /* Initialize sumOut by padding. */ + if (horiz_scan) + src_ptr = twin_pixmap_pointer(src_px, second_str, first); + else + src_ptr = twin_pixmap_pointer(src_px, first, second_str); + _src = *src_ptr.argb32; + + for (int i = second_str; i < second_str + radius; i++) { + sumOutR = _twin_add_ARGB(sumOutR, _src, 0, tmpR); + sumOutG = _twin_add_ARGB(sumOutG, _src, 8, tmpG); + sumOutB = _twin_add_ARGB(sumOutB, _src, 16, tmpB); + sumOutA = _twin_add_ARGB(sumOutA, _src, 24, tmpA); + for (int j = 0; j < (i - second_str) + 1; j++) { + sumR = _twin_add_ARGB(sumR, _src, 0, tmpR); + sumG = _twin_add_ARGB(sumG, _src, 8, tmpG); + sumB = _twin_add_ARGB(sumB, _src, 16, tmpB); + sumA = _twin_add_ARGB(sumA, _src, 24, tmpA); + } + } + + /* Initialize sumIn. */ + for (int i = second_str; i < second_str + radius; i++) { + if (horiz_scan) + src_ptr = twin_pixmap_pointer(src_px, i, first); + else + src_ptr = twin_pixmap_pointer(src_px, first, i); + _src = *src_ptr.argb32; + sumInR = _twin_add_ARGB(sumInR, _src, 0, tmpR); + sumInG = _twin_add_ARGB(sumInG, _src, 8, tmpG); + sumInB = _twin_add_ARGB(sumInB, _src, 16, tmpB); + sumInA = _twin_add_ARGB(sumInA, _src, 24, tmpA); + for (int j = 0; j < radius - (i - second_str); j++) { + sumR = _twin_add_ARGB(sumR, _src, 0, tmpR); + sumG = _twin_add_ARGB(sumG, _src, 8, tmpG); + sumB = _twin_add_ARGB(sumB, _src, 16, tmpB); + sumA = _twin_add_ARGB(sumA, _src, 24, tmpA); + } + } + + for (int cur = second_str; cur < second_end; cur++) { + if (horiz_scan) { + src_ptr = twin_pixmap_pointer(src_px, cur, first); + trg_ptr = twin_pixmap_pointer(trg_px, cur, first); + old_ptr = twin_pixmap_pointer( + src_px, max(cur - radius, second_str), first); + new_ptr = twin_pixmap_pointer( + src_px, min(cur + radius, second_end - 1), first); + } else { + src_ptr = twin_pixmap_pointer(src_px, first, cur); + trg_ptr = twin_pixmap_pointer(trg_px, first, cur); + old_ptr = twin_pixmap_pointer(src_px, first, + max(cur - radius, second_str)); + new_ptr = twin_pixmap_pointer( + src_px, first, min(cur + radius, second_end - 1)); + } + _cur = *src_ptr.argb32; + _old = *old_ptr.argb32; + _new = *new_ptr.argb32; + /* STEP 1 : sumOut + current */ + sumOutR = _twin_add_ARGB(sumOutR, _cur, 0, tmpR); + sumOutG = _twin_add_ARGB(sumOutG, _cur, 8, tmpG); + sumOutB = _twin_add_ARGB(sumOutB, _cur, 16, tmpB); + sumOutA = _twin_add_ARGB(sumOutA, _cur, 24, tmpA); + /* STEP 2 : sumIn + new */ + sumInR = _twin_add_ARGB(sumInR, _new, 0, tmpR); + sumInG = _twin_add_ARGB(sumInG, _new, 8, tmpG); + sumInB = _twin_add_ARGB(sumInB, _new, 16, tmpB); + sumInA = _twin_add_ARGB(sumInA, _new, 24, tmpA); + /* STEP 3 : sum + sumIn */ + sumR = _twin_add(sumR, sumInR, tmpR); + sumG = _twin_add(sumG, sumInG, tmpG); + sumB = _twin_add(sumB, sumInB, tmpB); + sumA = _twin_add(sumA, sumInA, tmpA); + /* STEP 4 : sum / denominator */ + *trg_ptr.argb32 = + (_twin_div(sumR, den, 0, tmpR) | _twin_div(sumG, den, 8, tmpG) | + _twin_div(sumB, den, 16, tmpB) | + _twin_div(sumA, den, 24, tmpA)); + /* STEP 5 : sum - sumOut */ + sumR = _twin_sub(sumR, sumOutR, tmpR); + sumG = _twin_sub(sumG, sumOutG, tmpG); + sumB = _twin_sub(sumB, sumOutB, tmpB); + sumA = _twin_sub(sumA, sumOutA, tmpA); + /* STEP 6 : sumOut - old */ + sumOutR = _twin_sub_ARGB(sumOutR, _old, 0, tmpR); + sumOutG = _twin_sub_ARGB(sumOutG, _old, 8, tmpG); + sumOutB = _twin_sub_ARGB(sumOutB, _old, 16, tmpB); + sumOutA = _twin_sub_ARGB(sumOutA, _old, 24, tmpA); + /* STEP 7 : sumIn - current */ + sumInR = _twin_sub_ARGB(sumInR, _cur, 0, tmpR); + sumInG = _twin_sub_ARGB(sumInG, _cur, 8, tmpG); + sumInB = _twin_sub_ARGB(sumInB, _cur, 16, tmpB); + sumInA = _twin_sub_ARGB(sumInA, _cur, 24, tmpA); + } + } +} + +void twin_stack_blur(twin_pixmap_t *px, + int radius, + twin_coord_t left, + twin_coord_t right, + twin_coord_t top, + twin_coord_t bottom) +{ + if (px->format != TWIN_ARGB32) + return; + twin_pixmap_t *tmp_px = + twin_pixmap_create(px->format, px->width, px->height); + memcpy(tmp_px->p.v, px->p.v, + px->width * px->height * twin_bytes_per_pixel(px->format)); + /* + * Originally, performing a 2D convolution on each pixel takes O(width * + * height * k²). However, by first scanning horizontally and then vertically + * across the pixel map, and applying a 1D convolution to each pixel, the + * complexity is reduced to O(2 * width * height * k). + */ + /* Horizontally scan. */ + _twin_apply_stack_blur(tmp_px, px, radius, top, bottom, left, right, true); + /* Vertically scan. */ + _twin_apply_stack_blur(px, tmp_px, radius, left, right, top, bottom, false); + twin_pixmap_destroy(tmp_px); + return; +} + +#if defined(CONFIG_DROP_SHADOW) +void twin_shadow_border(twin_pixmap_t *shadow, + twin_argb32_t color, + twin_coord_t offset_x, + twin_coord_t offset_y) +{ + twin_coord_t right_edge = (*shadow).width - (*shadow).window->shadow_x, + bottom_edge = (*shadow).height - (*shadow).window->shadow_y, + right_span = right_edge, clip, corner_offset, y_start, + offset = min(offset_x, offset_y); + + switch (shadow->window->style) { + case TwinWindowApplication: + y_start = TWIN_TITLE_HEIGHT; + break; + case TwinWindowPlain: + default: + y_start = 0; + break; + } + + for (twin_coord_t y = y_start; y < (*shadow).height; y++) { + /* Render the right edge of the shadow border. */ + if (y < bottom_edge) { + /* + * Create a shadow with a diagonal shape, extending from the + * top-left to the bottom-right. + */ + clip = min((y - y_start), offset_x); + twin_cover(shadow, color, right_edge, y, clip); + } else { + /* Calculate the range of the corner. */ + right_span++; + } + /* Render the bottom edge of the shadow border. */ + if (y >= bottom_edge && y < bottom_edge + offset_y) { + /* + * Create a shadow with a diagonal shape, extending from the + * top-left to the bottom-right. + */ + clip = max(0, y - bottom_edge); + twin_cover(shadow, color, clip, y, right_edge - clip); + /* Render the bottom-right corner of the shadow border. */ + /* + * Handle the case where the vertical shadow offset is larger than + * the horizontal shadow offset. + */ + corner_offset = min(right_span - right_edge, offset); + for (twin_coord_t i = 0; i < corner_offset; i++) { + /* The corner's pixels are symmetrical to the diagonal. */ + twin_cover(shadow, color, right_edge + i, y, 1); + twin_cover(shadow, color, right_edge + (y - bottom_edge), + bottom_edge + i, 1); + } + } + } +} +#endif + static twin_argb32_t _twin_apply_alpha(twin_argb32_t v) { uint16_t t1, t2, t3; @@ -46,3 +249,18 @@ void twin_premultiply_alpha(twin_pixmap_t *px) p.argb32[x] = _twin_apply_alpha(p.argb32[x]); } } + +void twin_cover(twin_pixmap_t *dst, + twin_argb32_t color, + twin_coord_t x, + twin_coord_t y, + twin_coord_t width) +{ + if (x < 0 || y < 0 || width < 0 || x + width > dst->width || + y >= dst->height) + return; + for (twin_coord_t i = 0; i < width; i++) { + twin_pointer_t pt = twin_pixmap_pointer(dst, x + i, y); + *pt.argb32 = color; + } +} diff --git a/src/image.c b/src/image.c index 21829e0..017be4e 100644 --- a/src/image.c +++ b/src/image.c @@ -39,7 +39,7 @@ IIF(LOADER_HAS(GIF))( \ _(gif) \ ) \ - IIF(LOADER_HAS(TVG))( \ + IIF(LOADER_HAS(TVG))( \ _(tvg) \ ) /* clang-format on */ diff --git a/src/pixmap.c b/src/pixmap.c index 1dca92a..a1b0d45 100644 --- a/src/pixmap.c +++ b/src/pixmap.c @@ -45,6 +45,9 @@ twin_pixmap_t *twin_pixmap_create(twin_format_t format, pixmap->stride = stride; pixmap->disable = 0; pixmap->animation = NULL; +#if defined(CONFIG_DROP_SHADOW) + pixmap->shadow = false; +#endif pixmap->p.v = pixmap + 1; memset(pixmap->p.v, '\0', space); return pixmap; diff --git a/src/screen.c b/src/screen.c index dab844b..f8a816e 100644 --- a/src/screen.c +++ b/src/screen.c @@ -135,7 +135,7 @@ static void twin_screen_span_pixmap(twin_screen_t maybe_unused *screen, return; if (p->y + p->height <= y) return; - /* bounds check in x*/ + /* bounds check in x */ p_left = left; if (p_left < p->x) p_left = p->x; diff --git a/src/window.c b/src/window.c index 794107a..3222975 100644 --- a/src/window.c +++ b/src/window.c @@ -18,6 +18,7 @@ #define TWIN_TITLE_HEIGHT 20 #define TWIN_RESIZE_SIZE ((TWIN_TITLE_HEIGHT + 4) / 5) #define TWIN_TITLE_BW ((TWIN_TITLE_HEIGHT + 11) / 12) +#define SHADOW_COLOR 0xff000000 twin_window_t *twin_window_create(twin_screen_t *screen, twin_format_t format, @@ -39,8 +40,8 @@ twin_window_t *twin_window_create(twin_screen_t *screen, case TwinWindowApplication: left = TWIN_BW; top = TWIN_BW + TWIN_TITLE_HEIGHT + TWIN_BW; - right = TWIN_BW + TWIN_RESIZE_SIZE; - bottom = TWIN_BW + TWIN_RESIZE_SIZE; + right = TWIN_BW; + bottom = TWIN_BW; break; case TwinWindowPlain: default: @@ -56,7 +57,22 @@ twin_window_t *twin_window_create(twin_screen_t *screen, window->client.top = top; window->client.right = width - right; window->client.bottom = height - bottom; +#if defined(CONFIG_DROP_SHADOW) + /* Handle drop shadow. */ + /* + * Add a shadowed area to the pixel map of the window to create a drop + * shadow effect. This calculation method is based on the range of + * CONFIG_HORIZONTAL_OFFSET, CONFIG_VERTICAL_OFFSET, and CONFIG_SHADOW_BLUR. + */ + window->shadow_x = 2 * CONFIG_HORIZONTAL_OFFSET + CONFIG_SHADOW_BLUR; + window->shadow_y = 2 * CONFIG_VERTICAL_OFFSET + CONFIG_SHADOW_BLUR; + window->pixmap = twin_pixmap_create(format, width + window->shadow_x, + height + window->shadow_y); +#else window->pixmap = twin_pixmap_create(format, width, height); +#endif + if (!window->pixmap) + return NULL; twin_pixmap_clip(window->pixmap, window->client.left, window->client.top, window->client.right, window->client.bottom); twin_pixmap_origin_to_clip(window->pixmap); @@ -137,20 +153,30 @@ bool twin_window_valid_range(twin_window_t *window, twin_coord_t x, twin_coord_t y) { + twin_coord_t offset_x = 0, offset_y = 0; +#if defined(CONFIG_DROP_SHADOW) + /* Handle drop shadow. */ + /* + * When the click coordinates fall within the drop shadow area, it will not + * be in the valid range of the window. + */ + offset_x = window->shadow_x, offset_y = window->shadow_y; +#endif + switch (window->style) { case TwinWindowPlain: default: if (window->pixmap->x <= x && - x < window->pixmap->x + window->pixmap->width && + x < window->pixmap->x + window->pixmap->width - offset_x && window->pixmap->y <= y && - y < window->pixmap->y + window->pixmap->height) + y < window->pixmap->y + window->pixmap->height - offset_y) return true; return false; case TwinWindowApplication: if (window->pixmap->x <= x && - x < window->pixmap->x + window->pixmap->width && + x < window->pixmap->x + window->pixmap->width - offset_x && window->pixmap->y <= y && - y < window->pixmap->y + window->pixmap->height) { + y < window->pixmap->y + window->pixmap->height - offset_y) { if (y < window->pixmap->y + (window->client.top)) return !twin_pixmap_transparent(window->pixmap, x, y); return true; @@ -316,6 +342,62 @@ static void twin_window_frame(twin_window_t *window) twin_path_destroy(path); } +#if defined(CONFIG_DROP_SHADOW) +static void twin_window_drop_shadow(twin_window_t *window) +{ + twin_pixmap_t *prev_active_pix = window->screen->top, + *active_pix = window->pixmap; + twin_source_u src; + twin_coord_t y, ori_wid, ori_hei; + + /* Remove the drop shadow from the previously active pixel map. */ + if (prev_active_pix) { + src.c = 0x00000000; + for (y = 0; y < prev_active_pix->height; y++) { + if (y < prev_active_pix->height - prev_active_pix->window->shadow_y) + twin_cover( + prev_active_pix, src.c, + prev_active_pix->width - prev_active_pix->window->shadow_x, + y, prev_active_pix->window->shadow_x); + else + twin_cover(prev_active_pix, src.c, 0, y, + prev_active_pix->width); + } + prev_active_pix->shadow = false; + } + + /* Mark the previously active pixel map as damaged to update its changes. */ + if (prev_active_pix && active_pix != prev_active_pix) + twin_pixmap_damage(prev_active_pix, 0, 0, prev_active_pix->width, + prev_active_pix->height); + + /* + * The shadow effect of the window only becomes visible when the window is + * active. + */ + active_pix->shadow = true; + ori_wid = active_pix->width - active_pix->window->shadow_x; + ori_hei = active_pix->height - active_pix->window->shadow_y; + /* + * Create a darker border of the active window that gives a more + * dimensional appearance. + */ + /* The shift offset and color of the shadow can be selected by the user. */ + twin_shadow_border(active_pix, SHADOW_COLOR, CONFIG_VERTICAL_OFFSET, + CONFIG_HORIZONTAL_OFFSET); + + /* Add a blur effect to the shadow of the active window. */ + /* Right side of the active window */ + twin_stack_blur(active_pix, CONFIG_SHADOW_BLUR, ori_wid, + ori_wid + active_pix->window->shadow_x, 0, + ori_hei + active_pix->window->shadow_y); + /* Bottom side of the active window */ + twin_stack_blur(active_pix, CONFIG_SHADOW_BLUR, 0, + ori_wid + active_pix->window->shadow_x, ori_hei, + ori_hei + active_pix->window->shadow_y); +} +#endif + void twin_window_draw(twin_window_t *window) { twin_pixmap_t *pixmap = window->pixmap; @@ -428,6 +510,10 @@ bool twin_window_dispatch(twin_window_t *window, twin_event_t *event) twin_window_frame(window->screen->top->window); } } +#if defined(CONFIG_DROP_SHADOW) + /* Handle drop shadow. */ + twin_window_drop_shadow(window); +#endif if (window->client.left <= ev.u.pointer.x && ev.u.pointer.x < window->client.right && window->client.top <= ev.u.pointer.y &&