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:
retoor 2026-01-26 23:36:06 +01:00
parent f55f024d22
commit b59c279bb0
20 changed files with 113 additions and 38 deletions

BIN
bin/dwn

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,7 +1,9 @@
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/dwn.h:
include/client.h:
include/config.h:
include/util.h:
include/workspace.h:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +1,6 @@
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/config.h include/panel.h include/api.h
include/config.h include/panel.h include/api.h include/decorations.h
include/workspace.h:
include/dwn.h:
include/client.h:
@ -10,3 +10,4 @@ include/util.h:
include/config.h:
include/panel.h:
include/api.h:
include/decorations.h:

Binary file not shown.

View File

@ -72,6 +72,7 @@ Client *client_get_last(void);
void client_start_focus_animation(Client *client, bool gaining_focus);
unsigned long client_get_animated_title_color(Client *client);
unsigned long client_get_glow_text_color(Client *client);
void client_update_animations(void);
#endif

View File

@ -95,6 +95,13 @@ typedef struct {
bool active;
} ColorAnimation;
typedef struct {
float phase;
float speed;
unsigned long base_color;
bool active;
} TextGlowAnimation;
typedef struct Client Client;
typedef struct Workspace Workspace;
typedef struct Monitor Monitor;
@ -116,6 +123,7 @@ struct Client {
SnapConstraint snap;
unsigned long taskbar_color;
ColorAnimation title_anim;
TextGlowAnimation text_glow;
Client *next;
Client *prev;
Client *mru_next;

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 interpolate_color(unsigned long from, unsigned long to, float progress);
unsigned long dim_color(unsigned long color, float factor);
unsigned long glow_color(unsigned long base, float phase);
long get_time_ms(void);
void sleep_ms(int ms);

View File

@ -92,6 +92,11 @@ Client *client_create(Window window)
client_update_class(client);
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;
}
@ -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)
{
@ -1298,7 +1303,7 @@ void client_start_focus_animation(Client *client, bool gaining_focus)
}
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) {
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);
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);
}
const ColorScheme *colors = config_get_colors();
return is_focused ? colors->title_focused_bg : colors->title_unfocused_bg;
}
if (is_focused) {
return client->taskbar_color;
unsigned long client_get_glow_text_color(Client *client)
{
if (client == NULL || !client->text_glow.active) {
return 0xFFFFFF;
}
return dim_color(client->taskbar_color, 0.6f);
return glow_color(client->text_glow.base_color, client->text_glow.phase);
}
void client_update_animations(void)
@ -1345,23 +1349,31 @@ void client_update_animations(void)
bool needs_redraw = false;
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
if (!c->title_anim.active) {
continue;
if (c->title_anim.active) {
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;
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->text_glow.active && c->workspace == (unsigned int)dwn->current_workspace) {
c->text_glow.phase += 0.01f * c->text_glow.speed;
if (c->text_glow.phase > 6.283f) {
c->text_glow.phase -= 6.283f;
}
needs_redraw = true;
}
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 (!(c->flags & CLIENT_MINIMIZED)) {
Workspace *ws = workspace_get(c->workspace);
bool focused = (ws != NULL && ws->focused == c);
decorations_render(c, focused);
}
}
}

View File

@ -8,6 +8,7 @@
#include "client.h"
#include "config.h"
#include "util.h"
#include "workspace.h"
#include <string.h>
#include <stdbool.h>
@ -57,20 +58,27 @@ void decorations_render_title_bar(Client *client, bool focused)
}
Display *dpy = dwn->display;
const ColorScheme *colors = config_get_colors();
int title_height = config_get_title_height();
int border = client->border_width;
const ColorScheme *colors = config_get_colors();
unsigned long bg_color = client_get_animated_title_color(client);
unsigned long fg_color = adjust_color_for_background(colors->title_focused_fg, bg_color);
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;
unsigned long fg_color = client->taskbar_color;
fg_color = adjust_color_for_background(fg_color, bg_color);
XSetForeground(dpy, dwn->gc, bg_color);
XFillRectangle(dpy, client->frame, dwn->gc,
border, border,
client->width, title_height);
if (client->title[0] != '\0' && dwn->xft_font != NULL) {
int text_y = border + (title_height + dwn->xft_font->ascent) / 2;
bool is_alt_tab_selection = (dwn->is_alt_tabbing && dwn->alt_tab_client == client);
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 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';
XGlyphInfo extents;
XftTextExtentsUtf8(dpy, dwn->xft_font,
XftTextExtentsUtf8(dpy, title_font,
(const FcChar8 *)display_title, strlen(display_title), &extents);
int text_width = extents.xOff;
@ -100,7 +108,7 @@ void decorations_render_title_bar(Client *client, bool focused)
display_title[cut] = '\0';
title_truncated = true;
XftTextExtentsUtf8(dpy, dwn->xft_font,
XftTextExtentsUtf8(dpy, title_font,
(const FcChar8 *)display_title, strlen(display_title), &extents);
text_width = extents.xOff;
}
@ -122,7 +130,7 @@ void decorations_render_title_bar(Client *client, bool focused)
XftColorAllocValue(dpy, DefaultVisual(dpy, dwn->screen),
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,
(const FcChar8 *)display_title, strlen(display_title));
@ -162,7 +170,10 @@ void decorations_render_buttons(Client *client, bool focused)
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);
XFillRectangle(dpy, client->frame, dwn->gc, close_x, button_y, BUTTON_SIZE, BUTTON_SIZE);

View File

@ -456,7 +456,7 @@ void panel_render_taskbar(Panel *panel, int x, int *width)
if (minimized) {
fg = colors->panel_fg;
} else {
fg = c->taskbar_color;
fg = client_get_glow_text_color(c);
}
if (focused && !minimized) {

View File

@ -795,3 +795,26 @@ unsigned long dim_color(unsigned long color, float factor)
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);
}

View File

@ -12,6 +12,7 @@
#include "config.h"
#include "panel.h"
#include "api.h"
#include "decorations.h"
#include <string.h>
#include <stdio.h>
@ -551,10 +552,11 @@ void workspace_alt_tab_next(void)
if (!dwn->is_alt_tabbing) {
dwn->is_alt_tabbing = true;
dwn->alt_tab_client = ws->focused;
XGrabKeyboard(dwn->display, dwn->root, True,
XGrabKeyboard(dwn->display, dwn->root, True,
GrabModeAsync, GrabModeAsync, CurrentTime);
}
Client *previous_selection = dwn->alt_tab_client;
Client *current = dwn->alt_tab_client;
if (current == NULL) {
current = ws->mru_head;
@ -566,8 +568,15 @@ void workspace_alt_tab_next(void)
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) {
client_focus(dwn->alt_tab_client, false);
if (dwn->alt_tab_client->frame != None) {
decorations_render(dwn->alt_tab_client, true);
}
}
}
@ -581,21 +590,28 @@ void workspace_alt_tab_prev(void)
if (!dwn->is_alt_tabbing) {
dwn->is_alt_tabbing = true;
dwn->alt_tab_client = ws->focused;
XGrabKeyboard(dwn->display, dwn->root, True,
XGrabKeyboard(dwn->display, dwn->root, True,
GrabModeAsync, GrabModeAsync, CurrentTime);
}
Client *previous_selection = dwn->alt_tab_client;
Client *current = dwn->alt_tab_client;
if (current != NULL && current->mru_prev != NULL) {
dwn->alt_tab_client = current->mru_prev;
} else {
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) {
client_focus(dwn->alt_tab_client, false);
if (dwn->alt_tab_client->frame != None) {
decorations_render(dwn->alt_tab_client, true);
}
}
}