feat: add animated focus transitions for window titles and taskbar colors
This commit is contained in:
parent
ea12677dac
commit
f55f024d22
BIN
build/ai.o
BIN
build/ai.o
Binary file not shown.
BIN
build/atoms.o
BIN
build/atoms.o
Binary file not shown.
BIN
build/client.o
BIN
build/client.o
Binary file not shown.
Binary file not shown.
BIN
build/keys.o
BIN
build/keys.o
Binary file not shown.
BIN
build/layout.o
BIN
build/layout.o
Binary file not shown.
BIN
build/main.o
BIN
build/main.o
Binary file not shown.
BIN
build/news.o
BIN
build/news.o
Binary file not shown.
Binary file not shown.
BIN
build/panel.o
BIN
build/panel.o
Binary file not shown.
BIN
build/systray.o
BIN
build/systray.o
Binary file not shown.
BIN
build/util.o
BIN
build/util.o
Binary file not shown.
Binary file not shown.
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
86
src/client.c
86
src/client.c
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
src/main.c
11
src/main.c
@ -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) {
|
||||
|
||||
34
src/panel.c
34
src/panel.c
@ -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
src/util.c
146
src/util.c
@ -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);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user