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

This commit is contained in:
2026-01-26 23:16:04 +01:00
parent ea12677dac
commit f55f024d22
22 changed files with 293 additions and 14 deletions
+86
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();
}
}
+6 -4
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);
+11
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) {
+24 -10
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;
}
+146
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);
}