342 lines
11 KiB
C
Raw Normal View History

2025-12-28 03:14:31 +01:00
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
2025-12-28 03:14:31 +01:00
* Window decorations implementation
*/
#include "decorations.h"
#include "client.h"
#include "config.h"
#include "util.h"
#include <string.h>
#include <stdbool.h>
#include <X11/Xft/Xft.h>
#define BUTTON_SIZE 16
#define BUTTON_PADDING 4
#define RESIZE_EDGE 8
void decorations_init(void)
{
LOG_DEBUG("Decorations system initialized");
}
void decorations_cleanup(void)
{
}
void decorations_render(Client *client, bool focused)
{
if (client == NULL || client->frame == None) {
return;
}
if (dwn == NULL || dwn->display == NULL || dwn->config == NULL) {
return;
}
decorations_render_title_bar(client, focused);
decorations_render_buttons(client, focused);
decorations_render_border(client, focused);
}
void decorations_render_title_bar(Client *client, bool focused)
{
if (client == NULL || client->frame == None) {
return;
}
if (dwn == NULL || dwn->display == NULL || dwn->gc == None || dwn->config == NULL) {
return;
}
Display *dpy = dwn->display;
const ColorScheme *colors = config_get_colors();
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;
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;
int text_x = border + BUTTON_PADDING;
int max_width = client->width - 3 * (BUTTON_SIZE + BUTTON_PADDING) - 2 * BUTTON_PADDING;
char display_title[256];
strncpy(display_title, client->title, sizeof(display_title) - 4);
2025-12-28 03:14:31 +01:00
display_title[sizeof(display_title) - 4] = '\0';
XGlyphInfo extents;
XftTextExtentsUtf8(dpy, dwn->xft_font,
(const FcChar8 *)display_title, strlen(display_title), &extents);
int text_width = extents.xOff;
bool title_truncated = false;
while (text_width > max_width && strlen(display_title) > 3) {
size_t len = strlen(display_title);
size_t cut = len - 1;
while (cut > 0 && (display_title[cut] & 0xC0) == 0x80) {
cut--;
2025-12-28 03:14:31 +01:00
}
if (cut > 0) cut--;
2025-12-28 03:14:31 +01:00
while (cut > 0 && (display_title[cut] & 0xC0) == 0x80) {
cut--;
}
display_title[cut] = '\0';
title_truncated = true;
XftTextExtentsUtf8(dpy, dwn->xft_font,
(const FcChar8 *)display_title, strlen(display_title), &extents);
text_width = extents.xOff;
}
if (title_truncated) {
strncat(display_title, "...", sizeof(display_title) - strlen(display_title) - 1);
}
XftDraw *xft_draw = XftDrawCreate(dpy, client->frame,
DefaultVisual(dpy, dwn->screen),
dwn->colormap);
if (xft_draw != NULL) {
XftColor xft_color;
XRenderColor render_color;
render_color.red = ((fg_color >> 16) & 0xFF) * 257;
render_color.green = ((fg_color >> 8) & 0xFF) * 257;
render_color.blue = (fg_color & 0xFF) * 257;
render_color.alpha = 0xFFFF;
XftColorAllocValue(dpy, DefaultVisual(dpy, dwn->screen),
dwn->colormap, &render_color, &xft_color);
XftDrawStringUtf8(xft_draw, &xft_color, dwn->xft_font,
text_x, text_y,
(const FcChar8 *)display_title, strlen(display_title));
XftColorFree(dpy, DefaultVisual(dpy, dwn->screen),
dwn->colormap, &xft_color);
XftDrawDestroy(xft_draw);
}
}
}
void decorations_render_buttons(Client *client, bool focused)
{
if (client == NULL || client->frame == None) {
return;
}
if (dwn == NULL || dwn->display == NULL || dwn->gc == None || dwn->config == NULL) {
return;
}
Display *dpy = dwn->display;
const ColorScheme *colors = config_get_colors();
int title_height = config_get_title_height();
int border = client->border_width;
int button_y = border + (title_height - BUTTON_SIZE) / 2;
int close_x = border + client->width - BUTTON_SIZE - BUTTON_PADDING;
int max_x = close_x - BUTTON_SIZE - BUTTON_PADDING;
int min_x = max_x - BUTTON_SIZE - BUTTON_PADDING;
unsigned long bg_color = 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);
XSetForeground(dpy, dwn->gc, 0xcc4444);
2025-12-28 03:14:31 +01:00
XDrawLine(dpy, client->frame, dwn->gc,
close_x + 3, button_y + 3,
close_x + BUTTON_SIZE - 4, button_y + BUTTON_SIZE - 4);
XDrawLine(dpy, client->frame, dwn->gc,
close_x + BUTTON_SIZE - 4, button_y + 3,
close_x + 3, button_y + BUTTON_SIZE - 4);
XSetForeground(dpy, dwn->gc, bg_color);
XFillRectangle(dpy, client->frame, dwn->gc, max_x, button_y, BUTTON_SIZE, BUTTON_SIZE);
XSetForeground(dpy, dwn->gc, 0x44cc44);
2025-12-28 03:14:31 +01:00
XDrawRectangle(dpy, client->frame, dwn->gc,
max_x + 3, button_y + 3,
BUTTON_SIZE - 7, BUTTON_SIZE - 7);
XSetForeground(dpy, dwn->gc, bg_color);
XFillRectangle(dpy, client->frame, dwn->gc, min_x, button_y, BUTTON_SIZE, BUTTON_SIZE);
XSetForeground(dpy, dwn->gc, 0xcccc44);
2025-12-28 03:14:31 +01:00
XDrawLine(dpy, client->frame, dwn->gc,
min_x + 3, button_y + BUTTON_SIZE - 5,
min_x + BUTTON_SIZE - 4, button_y + BUTTON_SIZE - 5);
}
void decorations_render_border(Client *client, bool focused)
{
if (client == NULL || client->frame == None) {
return;
}
if (dwn == NULL || dwn->display == NULL || dwn->config == NULL) {
return;
}
const ColorScheme *colors = config_get_colors();
unsigned long border_color = focused ? colors->border_focused : colors->border_unfocused;
XSetWindowBorder(dwn->display, client->frame, border_color);
}
ButtonType decorations_hit_test_button(Client *client, int x, int y)
{
if (client == NULL) {
return BUTTON_COUNT;
2025-12-28 03:14:31 +01:00
}
int title_height = config_get_title_height();
int border = client->border_width;
if (y < border || y > border + title_height) {
return BUTTON_COUNT;
}
int button_y = border + (title_height - BUTTON_SIZE) / 2;
int close_x = border + client->width - BUTTON_SIZE - BUTTON_PADDING;
int max_x = close_x - BUTTON_SIZE - BUTTON_PADDING;
int min_x = max_x - BUTTON_SIZE - BUTTON_PADDING;
if (x >= close_x && x < close_x + BUTTON_SIZE &&
y >= button_y && y < button_y + BUTTON_SIZE) {
return BUTTON_CLOSE;
}
if (x >= max_x && x < max_x + BUTTON_SIZE &&
y >= button_y && y < button_y + BUTTON_SIZE) {
return BUTTON_MAXIMIZE;
}
if (x >= min_x && x < min_x + BUTTON_SIZE &&
y >= button_y && y < button_y + BUTTON_SIZE) {
return BUTTON_MINIMIZE;
}
return BUTTON_COUNT;
}
bool decorations_hit_test_title_bar(Client *client, int x, int y)
{
if (client == NULL) {
return false;
}
int title_height = config_get_title_height();
int border = client->border_width;
if (y >= border && y < border + title_height) {
ButtonType btn = decorations_hit_test_button(client, x, y);
return btn == BUTTON_COUNT;
}
return false;
}
bool decorations_hit_test_resize_area(Client *client, int x, int y, int *direction)
{
if (client == NULL || direction == NULL) {
return false;
}
int title_height = config_get_title_height();
int border = client->border_width;
int frame_width = client->width + 2 * border;
int frame_height = client->height + title_height + 2 * border;
*direction = 0;
bool left = (x < RESIZE_EDGE);
bool right = (x > frame_width - RESIZE_EDGE);
bool top = (y < RESIZE_EDGE);
bool bottom = (y > frame_height - RESIZE_EDGE);
if (!left && !right && !top && !bottom) {
return false;
}
if (left) *direction |= 1;
if (right) *direction |= 2;
if (top) *direction |= 4;
if (bottom) *direction |= 8;
return true;
}
void decorations_button_press(Client *client, ButtonType button)
{
if (client == NULL) {
return;
}
switch (button) {
case BUTTON_CLOSE:
LOG_DEBUG("Close button pressed for %s", client->title);
client_close(client);
break;
case BUTTON_MAXIMIZE:
LOG_DEBUG("Maximize button pressed for %s", client->title);
client_toggle_fullscreen(client);
break;
case BUTTON_MINIMIZE:
LOG_DEBUG("Minimize button pressed for %s", client->title);
client_minimize(client);
break;
default:
break;
}
}
void decorations_draw_text(Window window, GC gc, int x, int y,
const char *text, unsigned long color)
{
if (text == NULL || dwn == NULL || dwn->display == NULL) {
return;
}
if (dwn->xft_font != NULL) {
XftDraw *xft_draw = XftDrawCreate(dwn->display, window,
DefaultVisual(dwn->display, dwn->screen),
dwn->colormap);
if (xft_draw != NULL) {
XftColor xft_color;
XRenderColor render_color;
render_color.red = ((color >> 16) & 0xFF) * 257;
render_color.green = ((color >> 8) & 0xFF) * 257;
render_color.blue = (color & 0xFF) * 257;
render_color.alpha = 0xFFFF;
XftColorAllocValue(dwn->display, DefaultVisual(dwn->display, dwn->screen),
dwn->colormap, &render_color, &xft_color);
XftDrawStringUtf8(xft_draw, &xft_color, dwn->xft_font,
x, y, (const FcChar8 *)text, strlen(text));
XftColorFree(dwn->display, DefaultVisual(dwn->display, dwn->screen),
dwn->colormap, &xft_color);
XftDrawDestroy(xft_draw);
return;
}
}
XSetForeground(dwn->display, gc, color);
XDrawString(dwn->display, window, gc, x, y, text, strlen(text));
}