feat: add text glow animation for client titles

feat: enhance alt-tab selection with bold font rendering
refactor: simplify title bar color logic to use focused/unfocused states
perf: increase focus animation duration for smoother transitions
refactor: update build dependencies for decorations and workspace modules
This commit is contained in:
2026-01-26 22:36:06 +00:00
parent 654a197ffd
commit 915a949c00
20 changed files with 113 additions and 38 deletions
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+3 -1
View File
@@ -1,7 +1,9 @@
build/decorations.o: src/decorations.c include/decorations.h \ build/decorations.o: src/decorations.c include/decorations.h \
include/dwn.h include/client.h include/config.h include/util.h include/dwn.h include/client.h include/config.h include/util.h \
include/workspace.h
include/decorations.h: include/decorations.h:
include/dwn.h: include/dwn.h:
include/client.h: include/client.h:
include/config.h: include/config.h:
include/util.h: include/util.h:
include/workspace.h:
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+2 -1
View File
@@ -1,6 +1,6 @@
build/workspace.o: src/workspace.c include/workspace.h include/dwn.h \ build/workspace.o: src/workspace.c include/workspace.h include/dwn.h \
include/client.h include/layout.h include/atoms.h include/util.h \ include/client.h include/layout.h include/atoms.h include/util.h \
include/config.h include/panel.h include/api.h include/config.h include/panel.h include/api.h include/decorations.h
include/workspace.h: include/workspace.h:
include/dwn.h: include/dwn.h:
include/client.h: include/client.h:
@@ -10,3 +10,4 @@ include/util.h:
include/config.h: include/config.h:
include/panel.h: include/panel.h:
include/api.h: include/api.h:
include/decorations.h:
BIN
View File
Binary file not shown.
+1
View File
@@ -72,6 +72,7 @@ Client *client_get_last(void);
void client_start_focus_animation(Client *client, bool gaining_focus); void client_start_focus_animation(Client *client, bool gaining_focus);
unsigned long client_get_animated_title_color(Client *client); unsigned long client_get_animated_title_color(Client *client);
unsigned long client_get_glow_text_color(Client *client);
void client_update_animations(void); void client_update_animations(void);
#endif #endif
+8
View File
@@ -95,6 +95,13 @@ typedef struct {
bool active; bool active;
} ColorAnimation; } ColorAnimation;
typedef struct {
float phase;
float speed;
unsigned long base_color;
bool active;
} TextGlowAnimation;
typedef struct Client Client; typedef struct Client Client;
typedef struct Workspace Workspace; typedef struct Workspace Workspace;
typedef struct Monitor Monitor; typedef struct Monitor Monitor;
@@ -116,6 +123,7 @@ struct Client {
SnapConstraint snap; SnapConstraint snap;
unsigned long taskbar_color; unsigned long taskbar_color;
ColorAnimation title_anim; ColorAnimation title_anim;
TextGlowAnimation text_glow;
Client *next; Client *next;
Client *prev; Client *prev;
Client *mru_next; Client *mru_next;
+1
View File
@@ -58,6 +58,7 @@ unsigned long generate_unique_color(void);
unsigned long adjust_color_for_background(unsigned long color, unsigned long bg); 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 interpolate_color(unsigned long from, unsigned long to, float progress);
unsigned long dim_color(unsigned long color, float factor); unsigned long dim_color(unsigned long color, float factor);
unsigned long glow_color(unsigned long base, float phase);
long get_time_ms(void); long get_time_ms(void);
void sleep_ms(int ms); void sleep_ms(int ms);
+35 -23
View File
@@ -92,6 +92,11 @@ Client *client_create(Window window)
client_update_class(client); client_update_class(client);
client->taskbar_color = generate_unique_color(); client->taskbar_color = generate_unique_color();
client->text_glow.phase = (float)(rand() % 628) / 100.0f;
client->text_glow.speed = 0.8f + (float)(rand() % 40) / 100.0f;
client->text_glow.base_color = client->taskbar_color;
client->text_glow.active = true;
return client; return client;
} }
@@ -1289,7 +1294,7 @@ Client *client_get_last(void)
} }
#define FOCUS_ANIMATION_DURATION_MS 200 #define FOCUS_ANIMATION_DURATION_MS 400
void client_start_focus_animation(Client *client, bool gaining_focus) void client_start_focus_animation(Client *client, bool gaining_focus)
{ {
@@ -1298,7 +1303,7 @@ void client_start_focus_animation(Client *client, bool gaining_focus)
} }
unsigned long full_color = client->taskbar_color; unsigned long full_color = client->taskbar_color;
unsigned long dimmed_color = dim_color(client->taskbar_color, 0.6f); unsigned long dimmed_color = dim_color(client->taskbar_color, 0.35f);
if (gaining_focus) { if (gaining_focus) {
client->title_anim.from_color = dimmed_color; client->title_anim.from_color = dimmed_color;
@@ -1322,17 +1327,16 @@ unsigned long client_get_animated_title_color(Client *client)
Workspace *ws = workspace_get(client->workspace); Workspace *ws = workspace_get(client->workspace);
bool is_focused = (ws != NULL && ws->focused == client); bool is_focused = (ws != NULL && ws->focused == client);
if (client->title_anim.active) { const ColorScheme *colors = config_get_colors();
return interpolate_color(client->title_anim.from_color, return is_focused ? colors->title_focused_bg : colors->title_unfocused_bg;
client->title_anim.to_color, }
client->title_anim.progress);
}
if (is_focused) { unsigned long client_get_glow_text_color(Client *client)
return client->taskbar_color; {
if (client == NULL || !client->text_glow.active) {
return 0xFFFFFF;
} }
return glow_color(client->text_glow.base_color, client->text_glow.phase);
return dim_color(client->taskbar_color, 0.6f);
} }
void client_update_animations(void) void client_update_animations(void)
@@ -1345,23 +1349,31 @@ void client_update_animations(void)
bool needs_redraw = false; bool needs_redraw = false;
for (Client *c = dwn->client_list; c != NULL; c = c->next) { for (Client *c = dwn->client_list; c != NULL; c = c->next) {
if (!c->title_anim.active) { 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;
}
needs_redraw = true;
} }
long elapsed = now - c->title_anim.start_time; if (c->text_glow.active && c->workspace == (unsigned int)dwn->current_workspace) {
c->title_anim.progress = (float)elapsed / (float)FOCUS_ANIMATION_DURATION_MS; c->text_glow.phase += 0.01f * c->text_glow.speed;
if (c->text_glow.phase > 6.283f) {
if (c->title_anim.progress >= 1.0f) { c->text_glow.phase -= 6.283f;
c->title_anim.progress = 1.0f; }
c->title_anim.active = false; needs_redraw = true;
} }
if (c->frame != None && c->workspace == (unsigned int)dwn->current_workspace) { if (c->frame != None && c->workspace == (unsigned int)dwn->current_workspace) {
Workspace *ws = workspace_get(c->workspace); if (!(c->flags & CLIENT_MINIMIZED)) {
bool focused = (ws != NULL && ws->focused == c); Workspace *ws = workspace_get(c->workspace);
decorations_render(c, focused); bool focused = (ws != NULL && ws->focused == c);
needs_redraw = true; decorations_render(c, focused);
}
} }
} }
+20 -9
View File
@@ -8,6 +8,7 @@
#include "client.h" #include "client.h"
#include "config.h" #include "config.h"
#include "util.h" #include "util.h"
#include "workspace.h"
#include <string.h> #include <string.h>
#include <stdbool.h> #include <stdbool.h>
@@ -57,20 +58,27 @@ void decorations_render_title_bar(Client *client, bool focused)
} }
Display *dpy = dwn->display; Display *dpy = dwn->display;
const ColorScheme *colors = config_get_colors();
int title_height = config_get_title_height(); int title_height = config_get_title_height();
int border = client->border_width; int border = client->border_width;
const ColorScheme *colors = config_get_colors();
unsigned long bg_color = client_get_animated_title_color(client); Workspace *ws = workspace_get(client->workspace);
unsigned long fg_color = adjust_color_for_background(colors->title_focused_fg, bg_color); bool is_focused = (ws != NULL && ws->focused == client);
unsigned long bg_color = is_focused ? colors->title_focused_bg : colors->title_unfocused_bg;
unsigned long fg_color = client->taskbar_color;
fg_color = adjust_color_for_background(fg_color, bg_color);
XSetForeground(dpy, dwn->gc, bg_color); XSetForeground(dpy, dwn->gc, bg_color);
XFillRectangle(dpy, client->frame, dwn->gc, XFillRectangle(dpy, client->frame, dwn->gc,
border, border, border, border,
client->width, title_height); client->width, title_height);
if (client->title[0] != '\0' && dwn->xft_font != NULL) { bool is_alt_tab_selection = (dwn->is_alt_tabbing && dwn->alt_tab_client == client);
int text_y = border + (title_height + dwn->xft_font->ascent) / 2; XftFont *title_font = (is_alt_tab_selection && dwn->xft_font_bold != NULL) ?
dwn->xft_font_bold : dwn->xft_font;
if (client->title[0] != '\0' && title_font != NULL) {
int text_y = border + (title_height + title_font->ascent) / 2;
int text_x = border + BUTTON_PADDING; int text_x = border + BUTTON_PADDING;
int max_width = client->width - 3 * (BUTTON_SIZE + BUTTON_PADDING) - 2 * BUTTON_PADDING; int max_width = client->width - 3 * (BUTTON_SIZE + BUTTON_PADDING) - 2 * BUTTON_PADDING;
@@ -82,7 +90,7 @@ void decorations_render_title_bar(Client *client, bool focused)
display_title[sizeof(display_title) - 4] = '\0'; display_title[sizeof(display_title) - 4] = '\0';
XGlyphInfo extents; XGlyphInfo extents;
XftTextExtentsUtf8(dpy, dwn->xft_font, XftTextExtentsUtf8(dpy, title_font,
(const FcChar8 *)display_title, strlen(display_title), &extents); (const FcChar8 *)display_title, strlen(display_title), &extents);
int text_width = extents.xOff; int text_width = extents.xOff;
@@ -100,7 +108,7 @@ void decorations_render_title_bar(Client *client, bool focused)
display_title[cut] = '\0'; display_title[cut] = '\0';
title_truncated = true; title_truncated = true;
XftTextExtentsUtf8(dpy, dwn->xft_font, XftTextExtentsUtf8(dpy, title_font,
(const FcChar8 *)display_title, strlen(display_title), &extents); (const FcChar8 *)display_title, strlen(display_title), &extents);
text_width = extents.xOff; text_width = extents.xOff;
} }
@@ -122,7 +130,7 @@ void decorations_render_title_bar(Client *client, bool focused)
XftColorAllocValue(dpy, DefaultVisual(dpy, dwn->screen), XftColorAllocValue(dpy, DefaultVisual(dpy, dwn->screen),
dwn->colormap, &render_color, &xft_color); dwn->colormap, &render_color, &xft_color);
XftDrawStringUtf8(xft_draw, &xft_color, dwn->xft_font, XftDrawStringUtf8(xft_draw, &xft_color, title_font,
text_x, text_y, text_x, text_y,
(const FcChar8 *)display_title, strlen(display_title)); (const FcChar8 *)display_title, strlen(display_title));
@@ -162,7 +170,10 @@ void decorations_render_buttons(Client *client, bool focused)
return; return;
} }
unsigned long bg_color = client_get_animated_title_color(client); const ColorScheme *colors = config_get_colors();
Workspace *ws = workspace_get(client->workspace);
bool is_focused = (ws != NULL && ws->focused == client);
unsigned long bg_color = is_focused ? colors->title_focused_bg : colors->title_unfocused_bg;
XSetForeground(dpy, dwn->gc, bg_color); XSetForeground(dpy, dwn->gc, bg_color);
XFillRectangle(dpy, client->frame, dwn->gc, close_x, button_y, BUTTON_SIZE, BUTTON_SIZE); XFillRectangle(dpy, client->frame, dwn->gc, close_x, button_y, BUTTON_SIZE, BUTTON_SIZE);
+1 -1
View File
@@ -456,7 +456,7 @@ void panel_render_taskbar(Panel *panel, int x, int *width)
if (minimized) { if (minimized) {
fg = colors->panel_fg; fg = colors->panel_fg;
} else { } else {
fg = c->taskbar_color; fg = client_get_glow_text_color(c);
} }
if (focused && !minimized) { if (focused && !minimized) {
+23
View File
@@ -795,3 +795,26 @@ unsigned long dim_color(unsigned long color, float factor)
return rgb_to_pixel(r, g, b); return rgb_to_pixel(r, g, b);
} }
unsigned long glow_color(unsigned long base, float phase)
{
int r, g, b;
color_to_rgb(base, &r, &g, &b);
float mod_r = 0.25f * sinf(phase);
float mod_g = 0.25f * sinf(phase + 2.094f);
float mod_b = 0.25f * sinf(phase + 4.189f);
r = r + (int)(r * mod_r);
g = g + (int)(g * mod_g);
b = b + (int)(b * mod_b);
if (r > 255) r = 255;
if (r < 0) r = 0;
if (g > 255) g = 255;
if (g < 0) g = 0;
if (b > 255) b = 255;
if (b < 0) b = 0;
return rgb_to_pixel(r, g, b);
}
+17 -1
View File
@@ -12,6 +12,7 @@
#include "config.h" #include "config.h"
#include "panel.h" #include "panel.h"
#include "api.h" #include "api.h"
#include "decorations.h"
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
@@ -555,6 +556,7 @@ void workspace_alt_tab_next(void)
GrabModeAsync, GrabModeAsync, CurrentTime); GrabModeAsync, GrabModeAsync, CurrentTime);
} }
Client *previous_selection = dwn->alt_tab_client;
Client *current = dwn->alt_tab_client; Client *current = dwn->alt_tab_client;
if (current == NULL) { if (current == NULL) {
current = ws->mru_head; current = ws->mru_head;
@@ -566,8 +568,15 @@ void workspace_alt_tab_next(void)
dwn->alt_tab_client = ws->mru_head; dwn->alt_tab_client = ws->mru_head;
} }
if (previous_selection != NULL && previous_selection->frame != None) {
decorations_render(previous_selection, false);
}
if (dwn->alt_tab_client != NULL) { if (dwn->alt_tab_client != NULL) {
client_focus(dwn->alt_tab_client, false); client_focus(dwn->alt_tab_client, false);
if (dwn->alt_tab_client->frame != None) {
decorations_render(dwn->alt_tab_client, true);
}
} }
} }
@@ -585,17 +594,24 @@ void workspace_alt_tab_prev(void)
GrabModeAsync, GrabModeAsync, CurrentTime); GrabModeAsync, GrabModeAsync, CurrentTime);
} }
Client *previous_selection = dwn->alt_tab_client;
Client *current = dwn->alt_tab_client; Client *current = dwn->alt_tab_client;
if (current != NULL && current->mru_prev != NULL) { if (current != NULL && current->mru_prev != NULL) {
dwn->alt_tab_client = current->mru_prev; dwn->alt_tab_client = current->mru_prev;
} else { } else {
dwn->alt_tab_client = ws->mru_tail; dwn->alt_tab_client = ws->mru_tail;
} }
if (previous_selection != NULL && previous_selection->frame != None) {
decorations_render(previous_selection, false);
}
if (dwn->alt_tab_client != NULL) { if (dwn->alt_tab_client != NULL) {
client_focus(dwn->alt_tab_client, false); client_focus(dwn->alt_tab_client, false);
if (dwn->alt_tab_client->frame != None) {
decorations_render(dwn->alt_tab_client, true);
}
} }
} }