Skip to content

Add iconify and restore features on window frame #103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions include/twin.h
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@ struct _twin_window {
twin_rect_t client;
twin_rect_t damage;
bool active;
bool iconify;
bool client_grab;
bool want_focus;
bool draw_queued;
Expand All @@ -459,8 +460,8 @@ struct _twin_window {
*/
typedef enum _twin_icon {
TwinIconMenu,
TwinIconMinimize,
TwinIconMaximize,
TwinIconIconify,
TwinIconRestore,
TwinIconClose,
TwinIconResize,
} twin_icon_t;
Expand Down
9 changes: 7 additions & 2 deletions src/draw-common.c
Original file line number Diff line number Diff line change
Expand Up @@ -163,12 +163,17 @@ void twin_shadow_border(twin_pixmap_t *shadow,
offset = min(offset_x, offset_y);

switch (shadow->window->style) {
/*
* Draw a black border starting from the top y position of the window's
* client area plus CONFIG_SHADOW_BLUR / 2 + 1, to prevent twin_stack_blur()
* from blurring shadows over the window frame.
*/
case TwinWindowApplication:
y_start = TWIN_TITLE_HEIGHT;
y_start = TWIN_TITLE_HEIGHT + CONFIG_SHADOW_BLUR / 2 + 1;
break;
case TwinWindowPlain:
default:
y_start = 0;
y_start = CONFIG_SHADOW_BLUR / 2 + 1;
break;
}

Expand Down
16 changes: 8 additions & 8 deletions src/icon.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ static const signed char _twin_itable[] = {
's',
'e',
#define TWIN_MENU_LEN 43
/* Minimize */
#define TWIN_MINIMIZE_POS TWIN_MENU_POS + TWIN_MENU_LEN
/* Iconify */
#define TWIN_ICONIFY_POS TWIN_MENU_POS + TWIN_MENU_LEN
'm', L(0), G(0.8),
'd', L(0), B(1),
'd', R(1), B(1),
Expand All @@ -49,9 +49,9 @@ static const signed char _twin_itable[] = {
'w', G(0.05),
'p',
'e',
#define TWIN_MINIMIZE_LEN 17
/* Maximize */
#define TWIN_MAXIMIZE_POS TWIN_MINIMIZE_POS + TWIN_MINIMIZE_LEN
#define TWIN_ICONIFY_LEN 17
/* Restore */
#define TWIN_RESTORE_POS TWIN_ICONIFY_POS + TWIN_ICONIFY_LEN
'm', L(0), T(0),
'd', L(0), G(0.2),
'd', R(1), G(0.2),
Expand All @@ -64,9 +64,9 @@ static const signed char _twin_itable[] = {
'x',
's',
'e',
#define TWIN_MAXIMIZE_LEN 28
#define TWIN_RESTORE_LEN 28
/* Close */
#define TWIN_CLOSE_POS TWIN_MAXIMIZE_POS + TWIN_MAXIMIZE_LEN
#define TWIN_CLOSE_POS TWIN_RESTORE_POS + TWIN_RESTORE_LEN
'm', L(0), T(0),
'd', L(0), T(0.1),
'd', G(0.4), G(0.5),
Expand Down Expand Up @@ -103,7 +103,7 @@ static const signed char _twin_itable[] = {
/* clang-format on */

const uint16_t _twin_icons[] = {
TWIN_MENU_POS, TWIN_MINIMIZE_POS, TWIN_MAXIMIZE_POS,
TWIN_MENU_POS, TWIN_ICONIFY_POS, TWIN_RESTORE_POS,
TWIN_CLOSE_POS, TWIN_RESIZE_POS,
};

Expand Down
8 changes: 8 additions & 0 deletions src/screen.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

#include "twin_private.h"

#define TWIN_BW 0
#define TWIN_TITLE_HEIGHT 20

twin_screen_t *twin_screen_create(twin_coord_t width,
twin_coord_t height,
twin_put_begin_t put_begin,
Expand Down Expand Up @@ -135,6 +138,11 @@ static void twin_screen_span_pixmap(twin_screen_t maybe_unused *screen,
return;
if (p->y + p->height <= y)
return;

/* Skip drawing the window's client area if the window is iconified. */
if (p->window->iconify && y >= p->y + TWIN_BW + TWIN_TITLE_HEIGHT + TWIN_BW)
return;

/* bounds check in x */
p_left = left;
if (p_left < p->x)
Expand Down
80 changes: 63 additions & 17 deletions src/window.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ twin_window_t *twin_window_create(twin_screen_t *screen,
window->screen = screen;
window->style = style;
window->active = false;
window->iconify = false;
switch (window->style) {
case TwinWindowApplication:
left = TWIN_BW;
Expand Down Expand Up @@ -179,7 +180,7 @@ bool twin_window_valid_range(twin_window_t *window,
y < window->pixmap->y + window->pixmap->height - offset_y) {
if (y < window->pixmap->y + (window->client.top))
return !twin_pixmap_transparent(window->pixmap, x, y);
return true;
return !window->iconify;
}
return false;
}
Expand Down Expand Up @@ -234,8 +235,8 @@ static void twin_window_frame(twin_window_t *window)
twin_fixed_t text_width;
twin_fixed_t title_right;
twin_fixed_t close_x;
twin_fixed_t max_x;
twin_fixed_t min_x;
twin_fixed_t restore_x;
twin_fixed_t iconify_x;
twin_fixed_t resize_x;
twin_fixed_t resize_y;
const char *name;
Expand Down Expand Up @@ -265,8 +266,8 @@ static void twin_window_frame(twin_window_t *window)


close_x = c_right - t_arc_2 - icon_size;
max_x = close_x - bw - icon_size;
min_x = max_x - bw - icon_size;
restore_x = close_x - bw - icon_size;
iconify_x = restore_x - bw - icon_size;
resize_x = twin_int_to_fixed(window->client.right);
resize_y = twin_int_to_fixed(window->client.bottom);

Expand Down Expand Up @@ -314,14 +315,14 @@ static void twin_window_frame(twin_window_t *window)
twin_icon_draw(pixmap, TwinIconMenu, m);

twin_matrix_identity(&m);
twin_matrix_translate(&m, min_x, icon_y);
twin_matrix_translate(&m, iconify_x, icon_y);
twin_matrix_scale(&m, icon_size, icon_size);
twin_icon_draw(pixmap, TwinIconMinimize, m);
twin_icon_draw(pixmap, TwinIconIconify, m);

twin_matrix_identity(&m);
twin_matrix_translate(&m, max_x, icon_y);
twin_matrix_translate(&m, restore_x, icon_y);
twin_matrix_scale(&m, icon_size, icon_size);
twin_icon_draw(pixmap, TwinIconMaximize, m);
twin_icon_draw(pixmap, TwinIconRestore, m);

twin_matrix_identity(&m);
twin_matrix_translate(&m, close_x, icon_y);
Expand Down Expand Up @@ -494,21 +495,66 @@ bool twin_window_dispatch(twin_window_t *window, twin_event_t *event)
twin_event_t ev = *event;
bool delegate = true;

twin_fixed_t bw = twin_int_to_fixed(TWIN_TITLE_BW);
twin_fixed_t t_h = twin_int_to_fixed(window->client.top) - bw;
twin_fixed_t t_arc_2 = t_h * 2 / 3;
twin_fixed_t c_right = twin_int_to_fixed(window->client.right) - bw / 2;
twin_fixed_t name_height = t_h - bw - bw / 2;
twin_fixed_t icon_size = name_height * 8 / 10;
twin_fixed_t menu_x = t_arc_2;
twin_fixed_t text_x = menu_x + icon_size + bw;
twin_fixed_t text_width;
twin_fixed_t title_right;
twin_path_t *path = twin_path_create();
const char *name = window->name;

text_width = twin_width_utf8(path, name);
twin_path_destroy(path);
title_right = (text_x + text_width + bw + icon_size + bw + icon_size + bw +
icon_size + t_arc_2);

if (title_right < c_right)
c_right = title_right;

twin_fixed_t close_x = c_right - t_arc_2 - icon_size;
twin_fixed_t restore_x = close_x - bw - icon_size;
twin_fixed_t iconify_x = restore_x - bw - icon_size;
int local_x, local_y;

switch (ev.kind) {
case TwinEventButtonDown:
local_y = ev.u.pointer.screen_y - window->pixmap->y;
if (local_y >= 0 && local_y <= TWIN_BW + TWIN_TITLE_HEIGHT + TWIN_BW) {
local_x = ev.u.pointer.screen_x - window->pixmap->x;
if (local_x > twin_fixed_to_int(iconify_x) &&
local_x < twin_fixed_to_int(restore_x)) {
window->iconify = true;
twin_pixmap_damage(window->pixmap, 0, 0, window->pixmap->width,
window->pixmap->height);
} else if (local_x > twin_fixed_to_int(restore_x) &&
local_x < twin_fixed_to_int(close_x)) {
window->iconify = false;
twin_pixmap_damage(window->pixmap, 0, 0, window->pixmap->width,
window->pixmap->height);
}
}
case TwinEventActivate:
/* Set window active. */
/*
* When the box is trigger by TwinEventButtonDown, its window's title
* bar needs to change color and be put onto the toppest layer.
* A iconified window is inactive. When the box is triggered by
* TwinEventButtonDown and the window is not iconified, it becomes
* active. For a window to be considered active, it must be the topmost
* window on the screen. The window's title bar turns blue to indicate
* the active state.
*/
if (!window->active) {
if (window->iconify)
window->active = false;
else
window->active = true;
twin_window_frame(window);
if (window != window->screen->top->window) {
window->screen->top->window->active = false;
twin_window_frame(window->screen->top->window);
}
twin_window_frame(window);
if (window != window->screen->top->window) {
window->screen->top->window->active = false;
twin_window_frame(window->screen->top->window);
}
#if defined(CONFIG_DROP_SHADOW)
/* Handle drop shadow. */
Expand Down