feat: add animated focus transitions for window titles and taskbar colors

This commit is contained in:
retoor 2026-01-26 23:16:04 +01:00
parent ea12677dac
commit f55f024d22
22 changed files with 293 additions and 14 deletions

BIN
bin/dwn

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -70,4 +70,8 @@ Client *client_get_prev(Client *client);
Client *client_get_first(void);
Client *client_get_last(void);
void client_start_focus_animation(Client *client, bool gaining_focus);
unsigned long client_get_animated_title_color(Client *client);
void client_update_animations(void);
#endif

View File

@ -87,6 +87,14 @@ typedef struct {
long timestamp;
} SnapConstraint;
typedef struct {
long start_time;
float progress;
unsigned long from_color;
unsigned long to_color;
bool active;
} ColorAnimation;
typedef struct Client Client;
typedef struct Workspace Workspace;
typedef struct Monitor Monitor;
@ -106,6 +114,8 @@ struct Client {
char title[256];
char class[64];
SnapConstraint snap;
unsigned long taskbar_color;
ColorAnimation title_anim;
Client *next;
Client *prev;
Client *mru_next;
@ -166,6 +176,7 @@ typedef struct {
GC gc;
XFontStruct *font;
XftFont *xft_font;
XftFont *xft_font_bold;
Colormap colormap;
Client *drag_client;

View File

@ -53,6 +53,11 @@ char *expand_path(const char *path);
unsigned long parse_color(const char *color_str);
void color_to_rgb(unsigned long color, int *r, int *g, int *b);
unsigned long rgb_to_pixel(int r, int g, int b);
unsigned long generate_unique_color(void);
unsigned long adjust_color_for_background(unsigned long color, unsigned long bg);
unsigned long interpolate_color(unsigned long from, unsigned long to, float progress);
unsigned long dim_color(unsigned long color, float factor);
long get_time_ms(void);
void sleep_ms(int ms);

View File

@ -90,6 +90,7 @@ Client *client_create(Window window)
client_update_title(client);
client_update_class(client);
client->taskbar_color = generate_unique_color();
return client;
}
@ -353,9 +354,12 @@ void client_focus(Client *client, bool update_mru)
Window prev_window = None;
if (ws != NULL && ws->focused != NULL && ws->focused != client) {
prev_window = ws->focused->window;
client_start_focus_animation(ws->focused, false);
client_unfocus(ws->focused);
}
client_start_focus_animation(client, true);
client_sync_log("client_focus: XSetInputFocus");
XSetInputFocus(dwn->display, client->window, RevertToPointerRoot, CurrentTime);
@ -1283,3 +1287,85 @@ Client *client_get_last(void)
}
return c;
}
#define FOCUS_ANIMATION_DURATION_MS 200
void client_start_focus_animation(Client *client, bool gaining_focus)
{
if (client == NULL) {
return;
}
unsigned long full_color = client->taskbar_color;
unsigned long dimmed_color = dim_color(client->taskbar_color, 0.6f);
if (gaining_focus) {
client->title_anim.from_color = dimmed_color;
client->title_anim.to_color = full_color;
} else {
client->title_anim.from_color = full_color;
client->title_anim.to_color = dimmed_color;
}
client->title_anim.start_time = get_time_ms();
client->title_anim.progress = 0.0f;
client->title_anim.active = true;
}
unsigned long client_get_animated_title_color(Client *client)
{
if (client == NULL) {
return 0;
}
Workspace *ws = workspace_get(client->workspace);
bool is_focused = (ws != NULL && ws->focused == client);
if (client->title_anim.active) {
return interpolate_color(client->title_anim.from_color,
client->title_anim.to_color,
client->title_anim.progress);
}
if (is_focused) {
return client->taskbar_color;
}
return dim_color(client->taskbar_color, 0.6f);
}
void client_update_animations(void)
{
if (dwn == NULL) {
return;
}
long now = get_time_ms();
bool needs_redraw = false;
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
if (!c->title_anim.active) {
continue;
}
long elapsed = now - c->title_anim.start_time;
c->title_anim.progress = (float)elapsed / (float)FOCUS_ANIMATION_DURATION_MS;
if (c->title_anim.progress >= 1.0f) {
c->title_anim.progress = 1.0f;
c->title_anim.active = false;
}
if (c->frame != None && c->workspace == (unsigned int)dwn->current_workspace) {
Workspace *ws = workspace_get(c->workspace);
bool focused = (ws != NULL && ws->focused == c);
decorations_render(c, focused);
needs_redraw = true;
}
}
if (needs_redraw) {
panel_render_all();
}
}

View File

@ -46,6 +46,8 @@ void decorations_render(Client *client, bool focused)
void decorations_render_title_bar(Client *client, bool focused)
{
(void)focused;
if (client == NULL || client->frame == None) {
return;
}
@ -59,8 +61,8 @@ void decorations_render_title_bar(Client *client, bool focused)
int title_height = config_get_title_height();
int border = client->border_width;
unsigned long bg_color = focused ? colors->title_focused_bg : colors->title_unfocused_bg;
unsigned long fg_color = focused ? colors->title_focused_fg : colors->title_unfocused_fg;
unsigned long bg_color = client_get_animated_title_color(client);
unsigned long fg_color = adjust_color_for_background(colors->title_focused_fg, bg_color);
XSetForeground(dpy, dwn->gc, bg_color);
XFillRectangle(dpy, client->frame, dwn->gc,
@ -147,7 +149,7 @@ void decorations_render_buttons(Client *client, bool focused)
}
Display *dpy = dwn->display;
const ColorScheme *colors = config_get_colors();
(void)focused;
int title_height = config_get_title_height();
int border = client->border_width;
@ -160,7 +162,7 @@ void decorations_render_buttons(Client *client, bool focused)
return;
}
unsigned long bg_color = focused ? colors->title_focused_bg : colors->title_unfocused_bg;
unsigned long bg_color = client_get_animated_title_color(client);
XSetForeground(dpy, dwn->gc, bg_color);
XFillRectangle(dpy, client->frame, dwn->gc, close_x, button_y, BUTTON_SIZE, BUTTON_SIZE);

View File

@ -773,6 +773,12 @@ int dwn_init(void)
LOG_INFO("Loaded Xft font for UTF-8 support");
}
dwn->xft_font_bold = XftFontOpenName(dwn->display, dwn->screen,
"monospace:size=10:bold:antialias=true");
if (dwn->xft_font_bold == NULL) {
dwn->xft_font_bold = dwn->xft_font;
}
XGCValues gcv;
gcv.foreground = dwn->config->colors.panel_fg;
gcv.background = dwn->config->colors.panel_bg;
@ -872,6 +878,9 @@ void dwn_cleanup(void)
if (dwn->font != NULL) {
XFreeFont(dwn->display, dwn->font);
}
if (dwn->xft_font_bold != NULL && dwn->xft_font_bold != dwn->xft_font) {
XftFontClose(dwn->display, dwn->xft_font_bold);
}
if (dwn->xft_font != NULL) {
XftFontClose(dwn->display, dwn->xft_font);
}
@ -921,6 +930,8 @@ void dwn_run(void)
demo_update();
client_update_animations();
notifications_update();
if (dwn->pending_focus_client != NULL) {

View File

@ -68,12 +68,12 @@ static int panel_text_width(const char *text, int len)
return 0;
}
static void panel_draw_text(Drawable d, int x, int y, const char *text,
int len, unsigned long color)
static void panel_draw_text_with_font(Drawable d, int x, int y, const char *text,
int len, unsigned long color, XftFont *font)
{
if (text == NULL || dwn == NULL || dwn->display == NULL) return;
if (dwn->xft_font != NULL) {
if (font != NULL) {
XftDraw *xft_draw = XftDrawCreate(dwn->display, d,
DefaultVisual(dwn->display, dwn->screen),
dwn->colormap);
@ -88,7 +88,7 @@ static void panel_draw_text(Drawable d, int x, int y, const char *text,
XftColorAllocValue(dwn->display, DefaultVisual(dwn->display, dwn->screen),
dwn->colormap, &render_color, &xft_color);
XftDrawStringUtf8(xft_draw, &xft_color, dwn->xft_font,
XftDrawStringUtf8(xft_draw, &xft_color, font,
x, y, (const FcChar8 *)text, len);
XftColorFree(dwn->display, DefaultVisual(dwn->display, dwn->screen),
@ -102,6 +102,19 @@ static void panel_draw_text(Drawable d, int x, int y, const char *text,
XDrawString(dwn->display, d, dwn->gc, x, y, text, len);
}
static void panel_draw_text(Drawable d, int x, int y, const char *text,
int len, unsigned long color)
{
panel_draw_text_with_font(d, x, y, text, len, color, dwn->xft_font);
}
static void panel_draw_text_bold(Drawable d, int x, int y, const char *text,
int len, unsigned long color)
{
XftFont *font = dwn->xft_font_bold ? dwn->xft_font_bold : dwn->xft_font;
panel_draw_text_with_font(d, x, y, text, len, color, font);
}
static int panel_text_y(int panel_height)
{
if (dwn->xft_font != NULL) {
@ -403,8 +416,6 @@ void panel_render_taskbar(Panel *panel, int x, int *width)
unsigned long bg;
if (minimized) {
bg = colors->workspace_inactive;
} else if (focused) {
bg = colors->workspace_active;
} else {
bg = colors->panel_bg;
}
@ -444,12 +455,15 @@ void panel_render_taskbar(Panel *panel, int x, int *width)
unsigned long fg;
if (minimized) {
fg = colors->panel_fg;
} else if (focused) {
fg = colors->panel_bg;
} else {
fg = colors->panel_fg;
fg = c->taskbar_color;
}
if (focused && !minimized) {
panel_draw_text_bold(panel->buffer, current_x + 4, text_y, title, strlen(title), fg);
} else {
panel_draw_text(panel->buffer, current_x + 4, text_y, title, strlen(title), fg);
}
panel_draw_text(panel->buffer, current_x + 4, text_y, title, strlen(title), fg);
current_x += item_width;
}

View File

@ -20,6 +20,7 @@
#include <pthread.h>
#include <stdatomic.h>
#include <fcntl.h>
#include <math.h>
#define LOG_RING_SIZE 256
@ -649,3 +650,148 @@ char *spawn_capture(const char *cmd)
return output;
}
static void hsl_to_rgb(double h, double s, double l, int *r, int *g, int *b)
{
double c = (1.0 - fabs(2.0 * l - 1.0)) * s;
double x = c * (1.0 - fabs(fmod(h / 60.0, 2.0) - 1.0));
double m = l - c / 2.0;
double rp = 0, gp = 0, bp = 0;
if (h < 60) {
rp = c; gp = x; bp = 0;
} else if (h < 120) {
rp = x; gp = c; bp = 0;
} else if (h < 180) {
rp = 0; gp = c; bp = x;
} else if (h < 240) {
rp = 0; gp = x; bp = c;
} else if (h < 300) {
rp = x; gp = 0; bp = c;
} else {
rp = c; gp = 0; bp = x;
}
*r = (int)((rp + m) * 255);
*g = (int)((gp + m) * 255);
*b = (int)((bp + m) * 255);
}
unsigned long rgb_to_pixel(int r, int g, int b)
{
if (dwn == NULL || dwn->display == NULL) {
return 0;
}
XColor color;
color.red = (unsigned short)(r << 8);
color.green = (unsigned short)(g << 8);
color.blue = (unsigned short)(b << 8);
color.flags = DoRed | DoGreen | DoBlue;
if (XAllocColor(dwn->display, dwn->colormap, &color)) {
return color.pixel;
}
return (unsigned long)((r << 16) | (g << 8) | b);
}
unsigned long generate_unique_color(void)
{
static unsigned int color_index = 0;
double golden_angle = 137.5;
double hue = fmod(color_index * golden_angle, 360.0);
color_index++;
double saturation = 0.75;
double lightness = 0.65;
int r, g, b;
hsl_to_rgb(hue, saturation, lightness, &r, &g, &b);
return rgb_to_pixel(r, g, b);
}
static double calculate_luminance(int r, int g, int b)
{
double rs = r / 255.0;
double gs = g / 255.0;
double bs = b / 255.0;
rs = (rs <= 0.03928) ? rs / 12.92 : pow((rs + 0.055) / 1.055, 2.4);
gs = (gs <= 0.03928) ? gs / 12.92 : pow((gs + 0.055) / 1.055, 2.4);
bs = (bs <= 0.03928) ? bs / 12.92 : pow((bs + 0.055) / 1.055, 2.4);
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
}
unsigned long adjust_color_for_background(unsigned long color, unsigned long bg)
{
int cr, cg, cb;
int br, bg_g, bb;
color_to_rgb(color, &cr, &cg, &cb);
color_to_rgb(bg, &br, &bg_g, &bb);
double color_lum = calculate_luminance(cr, cg, cb);
double bg_lum = calculate_luminance(br, bg_g, bb);
double lighter = (color_lum > bg_lum) ? color_lum : bg_lum;
double darker = (color_lum > bg_lum) ? bg_lum : color_lum;
double contrast = (lighter + 0.05) / (darker + 0.05);
if (contrast < 3.0) {
double factor = (bg_lum > 0.5) ? 0.3 : 1.8;
cr = (int)(cr * factor);
cg = (int)(cg * factor);
cb = (int)(cb * factor);
if (cr > 255) cr = 255;
if (cg > 255) cg = 255;
if (cb > 255) cb = 255;
if (cr < 0) cr = 0;
if (cg < 0) cg = 0;
if (cb < 0) cb = 0;
}
return rgb_to_pixel(cr, cg, cb);
}
unsigned long interpolate_color(unsigned long from, unsigned long to, float progress)
{
if (progress <= 0.0f) return from;
if (progress >= 1.0f) return to;
int fr, fg_c, fb;
int tr, tg, tb;
color_to_rgb(from, &fr, &fg_c, &fb);
color_to_rgb(to, &tr, &tg, &tb);
int r = fr + (int)((tr - fr) * progress);
int g = fg_c + (int)((tg - fg_c) * progress);
int b = fb + (int)((tb - fb) * progress);
return rgb_to_pixel(r, g, b);
}
unsigned long dim_color(unsigned long color, float factor)
{
int r, g, b;
color_to_rgb(color, &r, &g, &b);
r = (int)(r * factor);
g = (int)(g * factor);
b = (int)(b * factor);
if (r > 255) r = 255;
if (g > 255) g = 255;
if (b > 255) b = 255;
if (r < 0) r = 0;
if (g < 0) g = 0;
if (b < 0) b = 0;
return rgb_to_pixel(r, g, b);
}