Skip to content

Commit

Permalink
feat(i3wm): rounded corners
Browse files Browse the repository at this point in the history
Signed-off-by: Joseph Benden <joe@benden.us>
  • Loading branch information
jbenden committed Sep 25, 2023
1 parent 26608b7 commit ec45e3e
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 1 deletion.
1 change: 1 addition & 0 deletions include/config_directives.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ CFGFUN(for_window, const char *command);
CFGFUN(gaps, const char *workspace, const char *type, const long value);
CFGFUN(smart_borders, const char *enable);
CFGFUN(smart_gaps, const char *enable);
CFGFUN(border_radius, const long radius);
CFGFUN(floating_minimum_size, const long width, const long height);
CFGFUN(floating_maximum_size, const long width, const long height);
CFGFUN(default_orientation, const char *orientation);
Expand Down
3 changes: 3 additions & 0 deletions include/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,9 @@ struct Config {

/* Disable gaps if there is only one container on the workspace */
smart_gaps_t smart_gaps;

/* Border radius specified in pixel units */
int32_t border_radius;
};

/**
Expand Down
3 changes: 3 additions & 0 deletions include/data.h
Original file line number Diff line number Diff line change
Expand Up @@ -809,4 +809,7 @@ struct Con {

/* The colormap for this con if a custom one is used. */
xcb_colormap_t colormap;

/* Border radius specified in pixel units */
uint32_t border_radius;
};
6 changes: 6 additions & 0 deletions parser-specs/config.spec
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ state INITIAL:
'gaps' -> GAPS
'smart_borders' -> SMART_BORDERS
'smart_gaps' -> SMART_GAPS
'border_radius' -> BORDER_RADIUS
'floating_minimum_size' -> FLOATING_MINIMUM_SIZE_WIDTH
'floating_maximum_size' -> FLOATING_MAXIMUM_SIZE_WIDTH
'floating_modifier' -> FLOATING_MODIFIER
Expand Down Expand Up @@ -105,6 +106,11 @@ state INCLUDE:
pattern = string
-> call cfg_include($pattern)

# border_radius <radius>
state BORDER_RADIUS:
radius = number
-> call cfg_border_radius(&radius)

# floating_minimum_size <width> x <height>
state FLOATING_MINIMUM_SIZE_WIDTH:
width = number
Expand Down
7 changes: 7 additions & 0 deletions src/con.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Con *con_new_skeleton(Con *parent, i3Window *window) {
new->border_style = new->max_user_border_style = config.default_border;
new->current_border_width = -1;
new->window_icon_padding = -1;
new->border_radius = config.border_radius;
if (window) {
new->depth = window->depth;
} else {
Expand Down Expand Up @@ -1761,6 +1762,12 @@ static Rect con_border_style_rect_without_title(Con *con) {
if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) {
result.height += border_width;
}

result.width += config.border_radius * 2;
result.height += config.border_radius * 2;
result.x -= config.border_radius;
result.y -= config.border_radius;

return result;
}

Expand Down
2 changes: 2 additions & 0 deletions src/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
config.gaps.bottom = 0;
config.gaps.left = 0;

config.border_radius = 0;

/* Set default urgency reset delay to 500ms */
if (config.workspace_urgency_timer == 0)
config.workspace_urgency_timer = 0.5;
Expand Down
4 changes: 4 additions & 0 deletions src/config_directives.c
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,10 @@ CFGFUN(smart_gaps, const char *enable) {
config.smart_gaps = boolstr(enable) ? SMART_GAPS_ON : SMART_GAPS_OFF;
}

CFGFUN(border_radius, const long radius) {
config.border_radius = radius;
}

CFGFUN(floating_minimum_size, const long width, const long height) {
config.floating_minimum_width = width;
config.floating_minimum_height = height;
Expand Down
139 changes: 138 additions & 1 deletion src/x.c
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,140 @@ static size_t x_get_border_rectangles(Con *con, xcb_rectangle_t rectangles[4]) {
return count;
}

static void x_shape_title(Con *con) {
if (con->border_radius == 0) {
return;
}

if (con->layout != L_TABBED && con->layout != L_STACKED) {
return;
}

uint16_t w = con->rect.width;
uint16_t h = con->rect.height;

xcb_pixmap_t pid = xcb_generate_id(conn);

xcb_create_pixmap(conn, 1, pid, con->frame.id, w, h);

xcb_gcontext_t black = xcb_generate_id(conn);
xcb_gcontext_t white = xcb_generate_id(conn);

xcb_create_gc(conn, black, pid, XCB_GC_FOREGROUND, (uint32_t[]){0, 0});
xcb_create_gc(conn, white, pid, XCB_GC_FOREGROUND, (uint32_t[]){1, 0});

int32_t r = con->border_radius;
int32_t d = r * 2;

// clang-format off
xcb_rectangle_t bounding = { 0, 0, w, h };

xcb_arc_t arcs[] = {
{ 0 , 1, d, d, 0, 360 << 6 },
{ w - d - 1, 1, d, d, 0, 360 << 6 },
};

xcb_rectangle_t rects[] = {
{ r, 0, w - d, h },
{ 0, r, w , h - r },
};
// clang-format on

xcb_poly_fill_rectangle(conn, pid, black, 1, &bounding);
xcb_poly_fill_rectangle(conn, pid, white, 2, rects);
xcb_poly_fill_arc(conn, pid, white, 2, arcs);

xcb_shape_mask(conn, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, con->frame.id, 0, 0, pid);
xcb_shape_mask(conn, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_CLIP, con->frame.id, 0, 0, pid);

xcb_free_pixmap(conn, pid);
}

static bool has_outer_gaps(gaps_t gaps) {
return gaps.top > 0 ||
gaps.right > 0 ||
gaps.bottom > 0 ||
gaps.left > 0;
}

static bool smart_gaps_active(Con *con) {
return config.smart_gaps == SMART_GAPS_ON && con_num_visible_children(con->parent) <= 1;
}

static bool smart_gaps_has_gaps(Con *con) {
return smart_gaps_active(con) && !has_outer_gaps(calculate_effective_gaps(con));
}

/*
* Round window corners when possible
*
*/
static void x_shape_window(Con *con) {
if (con->border_radius == 0) {
return;
}

if (con->parent->type == CT_DOCKAREA) {
return;
}

if (con->fullscreen_mode ||
(!con_is_floating(con) && smart_gaps_has_gaps(con))) {
xcb_shape_mask(conn, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, con->frame.id, 0, 0, XCB_NONE);
xcb_shape_mask(conn, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_CLIP, con->frame.id, 0, 0, XCB_NONE);
return;
}

const xcb_query_extension_reply_t *shape_query;
shape_query = xcb_get_extension_data(conn, &xcb_shape_id);

if (!shape_query->present) {
return;
}

uint16_t w = con->rect.width;
uint16_t h = con->rect.height;
uint16_t dh = con->deco_rect.height;

xcb_pixmap_t pid = xcb_generate_id(conn);

xcb_create_pixmap(conn, 1, pid, con->frame.id, w, h);

xcb_gcontext_t black = xcb_generate_id(conn);
xcb_gcontext_t white = xcb_generate_id(conn);

xcb_create_gc(conn, black, pid, XCB_GC_FOREGROUND, (uint32_t[]){0, 0});
xcb_create_gc(conn, white, pid, XCB_GC_FOREGROUND, (uint32_t[]){1, 0});

int32_t r = con->border_radius;
int32_t d = r * 2;

// clang-format off
xcb_rectangle_t bounding = { 0, 0, w, h };

xcb_arc_t arcs[] = {
{ 0 , -dh , d, d, 0, 360 << 6 },
{ 0 , h - d - 1, d, d, 0, 360 << 6 },
{ w - d - 1, -dh , d, d, 0, 360 << 6 },
{ w - d - 1, h - d - 1, d, d, 0, 360 << 6 },
};

xcb_rectangle_t rects[] = {
{ r, 0 , w - d, h },
{ 0, r - dh, w , h - d + dh },
};
// clang-format on

xcb_poly_fill_rectangle(conn, pid, black, 1, &bounding);
xcb_poly_fill_rectangle(conn, pid, white, 2, rects);
xcb_poly_fill_arc(conn, pid, white, 4, arcs);

xcb_shape_mask(conn, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, con->frame.id, 0, 0, pid);
xcb_shape_mask(conn, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_CLIP, con->frame.id, 0, 0, pid);

xcb_free_pixmap(conn, pid);
}

/*
* Draws the decoration of the given container onto its parent.
*
Expand Down Expand Up @@ -757,6 +891,7 @@ void x_draw_decoration(Con *con) {

x_draw_decoration_after_title(con, p, dest_surface);
copy_pixmaps:
x_shape_window(con);
draw_util_copy_surface(&(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height);
}

Expand Down Expand Up @@ -1035,11 +1170,13 @@ void x_push_node(Con *con) {
* TODO: Should this work the same way for L_TABBED? */
if (!con->parent ||
con->parent->layout != L_STACKED ||
TAILQ_FIRST(&(con->parent->focus_head)) == con)
TAILQ_FIRST(&(con->parent->focus_head)) == con) {
/* Render the decoration now to make the correct decoration visible
* from the very first moment. Later calls will be cached, so this
* doesn’t hurt performance. */
x_deco_recurse(con);
x_shape_title(con);
}
}

DLOG("setting rect (%d, %d, %d, %d)\n", rect.x, rect.y, rect.width, rect.height);
Expand Down
1 change: 1 addition & 0 deletions testcases/t/201-config-parser.t
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '
gaps
smart_borders
smart_gaps
border_radius
floating_minimum_size
floating_maximum_size
floating_modifier
Expand Down

0 comments on commit ec45e3e

Please sign in to comment.