feat: add round borders (#22)

Co-authored-by: Erik Reider <35975961+ErikReider@users.noreply.github.com>
This commit is contained in:
William McKinnon 2022-10-09 11:47:34 -04:00 committed by GitHub
parent 22a6aba9f0
commit 90a80e7041
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 301 additions and 18 deletions

View file

@ -1,15 +1,17 @@
# swayFX: A Beautiful Sway Fork
# SwayFX: A Beautiful Sway Fork
Sway is an incredible window manager, and certainly one of the most well established wayland window managers. However, it is restricted to only include the functionality that existed in i3. This fork ditches the simple wlr_renderer, and replaces it with a fancy GLES2 renderer with functionality borrowed from the original simple renderer and [Hyprland](https://github.com/vaxerski/Hyprland). This, along with a couple of minor changes, expands sway's featureset to include the following:
Sway is an incredible window manager, and certainly one of the most well established wayland window managers. However, it is restricted to only include the functionality that existed in i3. This fork ditches the simple wlr_renderer, and extends it to render fancy GLES2 effects. This, along with a couple of minor changes, expands sway's featureset to include the following:
+ **Anti-aliased rounded corners**
+ **Anti-aliased rounded corners and borders**
+ **Scratchpad treated as minimize**: Allows docks, or panels with a taskbar, to correctly interpret minimize / unminimize requests ([thanks to LCBCrion](https://github.com/swaywm/sway/issues/6457))
+ **Default to not compiling swaybar**: Many users replace swaybar with the far more capable [waybar](https://github.com/Alexays/Waybar), swayFX cuts out the bloat by not including swaybar by default
+ **Default to not compiling swaybar**: Many users replace swaybar with the far more capable [waybar](https://github.com/Alexays/Waybar), SwayFX cuts out the bloat by not including swaybar by default
+ **Add a nix flake to the repo**: Allows nixos users to easily contribute to and test this project
## Configuration
+ Corner radius: `corner_radius <val>`
## Roadmap:
+ fade in / out animations
+ window movement animations
+ drop shadows
+ blur

View file

@ -2,6 +2,9 @@
#define _SWAY_OPENGL_H
#include <GLES2/gl2.h>
#include <stdbool.h>
enum corner_location {NONE, TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT};
struct gles2_tex_shader {
GLuint program;
@ -29,6 +32,21 @@ struct fx_renderer {
GLint color;
GLint pos_attrib;
} quad;
struct {
GLuint program;
GLint proj;
GLint color;
GLint pos_attrib;
GLint is_top_left;
GLint is_top_right;
GLint is_bottom_left;
GLint is_bottom_right;
GLint width;
GLint height;
GLint position;
GLint radius;
GLint thickness;
} corner;
struct gles2_tex_shader tex_rgba;
struct gles2_tex_shader tex_rgbx;
struct gles2_tex_shader tex_ext;
@ -55,4 +73,8 @@ bool fx_render_texture_with_matrix(struct fx_renderer *renderer, struct wlr_text
void fx_render_rect(struct fx_renderer *renderer, const struct wlr_box *box,
const float color[static 4], const float projection[static 9]);
void fx_render_border_corner(struct fx_renderer *renderer, const struct wlr_box *box,
const float color[static 4], const float projection[static 9],
enum corner_location corner_location, int radius, int border_thickness);
#endif

View file

@ -1,6 +1,8 @@
#ifndef _SWAY_SHADERS_H
#define _SWAY_SHADERS_H
#include <GLES2/gl2.h>
// Colored quads
const GLchar quad_vertex_src[] =
"uniform mat3 proj;\n"
@ -53,7 +55,7 @@ const GLchar tex_fragment_src_rgba[] =
" vec2 corner_distance = min(gl_FragCoord.xy - position, position + vec2(width, height) - gl_FragCoord.xy);\n"
" if (max(corner_distance.x, corner_distance.y) < radius) {\n"
" float d = radius - distance(corner_distance, vec2(radius, radius));\n"
" float smooth = smoothstep(-1.0f, 1.0f, d);\n"
" float smooth = smoothstep(-1.0f, 0.5f, d);\n"
" gl_FragColor = mix(vec4(0), gl_FragColor, smooth);\n"
" }\n"
"}\n";
@ -74,7 +76,7 @@ const GLchar tex_fragment_src_rgbx[] =
" vec2 corner_distance = min(gl_FragCoord.xy - position, position + vec2(width, height) - gl_FragCoord.xy);\n"
" if (max(corner_distance.x, corner_distance.y) < radius) {\n"
" float d = radius - distance(corner_distance, vec2(radius, radius));\n"
" float smooth = smoothstep(-1.0f, 1.0f, d);\n"
" float smooth = smoothstep(-1.0f, 0.5f, d);\n"
" gl_FragColor = mix(vec4(0), gl_FragColor, smooth);\n"
" }\n"
"}\n";
@ -96,9 +98,65 @@ const GLchar tex_fragment_src_external[] =
" vec2 corner_distance = min(gl_FragCoord.xy - position, position + vec2(width, height) - gl_FragCoord.xy);\n"
" if (max(corner_distance.x, corner_distance.y) < radius) {\n"
" float d = radius - distance(corner_distance, vec2(radius, radius));\n"
" float smooth = smoothstep(-1.0f, 1.0f, d);\n"
" float smooth = smoothstep(-1.0f, 0.5f, d);\n"
" gl_FragColor = mix(vec4(0), gl_FragColor, smooth);\n"
" }\n"
"}\n";
const GLchar corner_fragment_src[] =
"precision mediump float;\n"
"varying vec4 v_color;\n"
"varying vec2 v_texcoord;\n"
"\n"
"uniform bool is_top_left;\n"
"uniform bool is_top_right;\n"
"uniform bool is_bottom_left;\n"
"uniform bool is_bottom_right;\n"
"\n"
"uniform float width;\n"
"uniform float height;\n"
"uniform vec2 position;\n"
"uniform float radius;\n"
"uniform float thickness;\n"
"\n"
"float roundedBoxSDF(vec2 center, vec2 size, float radius) {\n"
" return length(max(abs(center) - size + radius, 0.0)) - radius;\n"
"}\n"
"\n"
"void main() {\n"
" gl_FragColor = v_color;\n"
" vec2 size = vec2(width, height);\n"
" vec2 pos = vec2(position.x - (width + thickness) * 0.5, position.y -\n"
" (width + thickness) * 0.5);\n"
" vec2 rel_pos = gl_FragCoord.xy - pos - size - thickness * 0.5;\n"
"\n"
" float distance = roundedBoxSDF(\n"
" rel_pos,\n" // Center
" (size - thickness) * 0.5,\n" // Size
" radius + thickness * 0.5\n" // Radius
" );\n"
"\n"
" float smoothedAlphaOuter = 1.0 - smoothstep(-1.0, 1.0, distance - thickness * 0.5);\n"
// Creates a inner circle that isn't as anti-aliased as the outer ring
" float smoothedAlphaInner = 1.0 - smoothstep(-1.0, 0.5, distance + thickness * 0.5);\n"
" gl_FragColor = mix(vec4(0), gl_FragColor, smoothedAlphaOuter - smoothedAlphaInner);\n"
"\n"
// top left
" if (is_top_left && (rel_pos.y > 0.0 || rel_pos.x > 0.0)) {\n"
" discard;\n"
" }\n"
// top right
" else if (is_top_right && (rel_pos.y > 0.0 || rel_pos.x < 0.0)) {\n"
" discard;\n"
" }\n"
// bottom left
" else if (is_bottom_left && (rel_pos.y < 0.0 || rel_pos.x > 0.0)) {\n"
" discard;\n"
" }\n"
// bottom right
" else if (is_bottom_right && (rel_pos.y < 0.0 || rel_pos.x < 0.0)) {\n"
" discard;\n"
" }\n"
"}\n";
#endif

View file

@ -130,6 +130,25 @@ struct fx_renderer *fx_renderer_create(struct wlr_egl *egl) {
renderer->shaders.quad.color = glGetUniformLocation(prog, "color");
renderer->shaders.quad.pos_attrib = glGetAttribLocation(prog, "pos");
// Border corners
prog = link_program(quad_vertex_src, corner_fragment_src);
renderer->shaders.corner.program = prog;
if (!renderer->shaders.corner.program) {
goto error;
}
renderer->shaders.corner.proj = glGetUniformLocation(prog, "proj");
renderer->shaders.corner.color = glGetUniformLocation(prog, "color");
renderer->shaders.corner.pos_attrib = glGetAttribLocation(prog, "pos");
renderer->shaders.corner.is_top_left = glGetUniformLocation(prog, "is_top_left");
renderer->shaders.corner.is_top_right = glGetUniformLocation(prog, "is_top_right");
renderer->shaders.corner.is_bottom_left = glGetUniformLocation(prog, "is_bottom_left");
renderer->shaders.corner.is_bottom_right = glGetUniformLocation(prog, "is_bottom_right");
renderer->shaders.corner.width = glGetUniformLocation(prog, "width");
renderer->shaders.corner.height = glGetUniformLocation(prog, "height");
renderer->shaders.corner.position = glGetUniformLocation(prog, "position");
renderer->shaders.corner.radius = glGetUniformLocation(prog, "radius");
renderer->shaders.corner.thickness = glGetUniformLocation(prog, "thickness");
prog = link_program(tex_vertex_src, tex_fragment_src_rgba);
renderer->shaders.tex_rgba.program = prog;
if (!renderer->shaders.tex_rgba.program) {
@ -183,6 +202,7 @@ struct fx_renderer *fx_renderer_create(struct wlr_egl *egl) {
error:
glDeleteProgram(renderer->shaders.quad.program);
glDeleteProgram(renderer->shaders.corner.program);
glDeleteProgram(renderer->shaders.tex_rgba.program);
glDeleteProgram(renderer->shaders.tex_rgbx.program);
glDeleteProgram(renderer->shaders.tex_ext.program);
@ -332,7 +352,8 @@ bool fx_render_texture_with_matrix(struct fx_renderer *renderer, struct wlr_text
return fx_render_subtexture_with_matrix(renderer, wlr_texture, &src_box, dst_box, matrix, alpha, radius);
}
void fx_render_rect(struct fx_renderer *renderer, const struct wlr_box *box, const float color[static 4], const float projection[static 9]) {
void fx_render_rect(struct fx_renderer *renderer, const struct wlr_box *box,
const float color[static 4], const float projection[static 9]) {
if (box->width == 0 || box->height == 0) {
return;
}
@ -368,3 +389,69 @@ void fx_render_rect(struct fx_renderer *renderer, const struct wlr_box *box, con
glDisableVertexAttribArray(renderer->shaders.quad.pos_attrib);
}
void fx_render_border_corner(struct fx_renderer *renderer, const struct wlr_box *box,
const float color[static 4], const float projection[static 9],
enum corner_location corner_location, int radius, int border_thickness) {
if (box->width == 0 || box->height == 0) {
return;
}
assert(box->width > 0 && box->height > 0);
float matrix[9];
wlr_matrix_project_box(matrix, box, WL_OUTPUT_TRANSFORM_NORMAL, 0, projection);
float gl_matrix[9];
wlr_matrix_multiply(gl_matrix, renderer->projection, matrix);
// TODO: investigate why matrix is flipped prior to this cmd
// wlr_matrix_multiply(gl_matrix, flip_180, gl_matrix);
wlr_matrix_transpose(gl_matrix, gl_matrix);
if (color[3] == 1.0 && !radius) {
glDisable(GL_BLEND);
} else {
glEnable(GL_BLEND);
}
glUseProgram(renderer->shaders.corner.program);
glUniformMatrix3fv(renderer->shaders.corner.proj, 1, GL_FALSE, gl_matrix);
glUniform4f(renderer->shaders.corner.color, color[0], color[1], color[2], color[3]);
glUniform1f(renderer->shaders.corner.is_top_left, false);
glUniform1f(renderer->shaders.corner.is_top_right, false);
glUniform1f(renderer->shaders.corner.is_bottom_left, false);
glUniform1f(renderer->shaders.corner.is_bottom_right, false);
switch (corner_location) {
case TOP_LEFT:
glUniform1f(renderer->shaders.corner.is_top_left, true);
break;
case TOP_RIGHT:
glUniform1f(renderer->shaders.corner.is_top_right, true);
break;
case BOTTOM_LEFT:
glUniform1f(renderer->shaders.corner.is_bottom_left, true);
break;
case BOTTOM_RIGHT:
glUniform1f(renderer->shaders.corner.is_bottom_right, true);
break;
default:
return;
}
glUniform1f(renderer->shaders.corner.width, box->width);
glUniform1f(renderer->shaders.corner.height, box->height);
glUniform2f(renderer->shaders.corner.position, box->x, box->y);
glUniform1f(renderer->shaders.corner.radius, radius);
glUniform1f(renderer->shaders.corner.thickness, border_thickness);
glVertexAttribPointer(renderer->shaders.corner.pos_attrib, 2, GL_FLOAT, GL_FALSE,
0, verts);
glEnableVertexAttribArray(renderer->shaders.corner.pos_attrib);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(renderer->shaders.corner.pos_attrib);
}

View file

@ -166,7 +166,8 @@ static void render_surface_iterator(struct sway_output *output,
}
scale_box(&dst_box, wlr_output->scale);
render_texture(wlr_output, output_damage, texture, &src_box, &dst_box, matrix, alpha, corner_radius);
render_texture(wlr_output, output_damage, texture, &src_box, &dst_box,
matrix, alpha, corner_radius * wlr_output->scale);
wlr_presentation_surface_sampled_on_output(server.presentation, surface,
wlr_output);
@ -253,6 +254,41 @@ damage_finish:
pixman_region32_fini(&damage);
}
// _box.x and .y are expected to be layout-local
// _box.width and .height are expected to be output-buffer-local
void render_border_corner(struct sway_output *output, pixman_region32_t *output_damage,
const struct wlr_box *_box, float color[static 4], int border_thickness,
enum corner_location corner_location) {
struct wlr_output *wlr_output = output->wlr_output;
struct fx_renderer *renderer = output->server->renderer;
struct wlr_box box;
memcpy(&box, _box, sizeof(struct wlr_box));
box.x -= output->lx * wlr_output->scale;
box.y -= output->ly * wlr_output->scale;
pixman_region32_t damage;
pixman_region32_init(&damage);
pixman_region32_union_rect(&damage, &damage, box.x, box.y,
box.width, box.height);
pixman_region32_intersect(&damage, &damage, output_damage);
bool damaged = pixman_region32_not_empty(&damage);
if (!damaged) {
goto damage_finish;
}
int nrects;
pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects);
for (int i = 0; i < nrects; ++i) {
scissor_output(wlr_output, &rects[i]);
fx_render_border_corner(renderer, &box, color, wlr_output->transform_matrix,
corner_location, config->corner_radius * wlr_output->scale, border_thickness);
}
damage_finish:
pixman_region32_fini(&damage);
}
void premultiply_alpha(float color[4], float opacity) {
color[3] *= opacity;
color[0] *= color[3];
@ -348,7 +384,7 @@ static void render_saved_view(struct sway_view *view,
scale_box(&dst_box, wlr_output->scale);
render_texture(wlr_output, damage, saved_buf->buffer->texture,
&saved_buf->source_box, &dst_box, matrix, alpha, corner_radius);
&saved_buf->source_box, &dst_box, matrix, alpha, corner_radius * wlr_output->scale);
}
// FIXME: we should set the surface that this saved buffer originates from
@ -360,7 +396,7 @@ static void render_saved_view(struct sway_view *view,
* Render a view's surface and left/bottom/right borders.
*/
static void render_view(struct sway_output *output, pixman_region32_t *damage,
struct sway_container *con, struct border_colors *colors) {
struct sway_container *con, struct border_colors *colors, bool has_titlebar) {
struct sway_view *view = con->view;
if (!wl_list_empty(&view->saved_buffers)) {
render_saved_view(view, output, damage, view->container->alpha, config->corner_radius);
@ -384,6 +420,15 @@ static void render_view(struct sway_output *output, pixman_region32_t *damage,
box.y = floor(state->content_y);
box.width = state->border_thickness;
box.height = state->content_height;
// adjust sizing for rounded border corners
if (config->corner_radius) {
if (!has_titlebar) {
box.y += config->corner_radius;
box.height -= 2 * config->corner_radius;
} else {
box.height -= config->corner_radius;
}
}
scale_box(&box, output_scale);
render_rect(output, damage, &box, color);
}
@ -403,6 +448,15 @@ static void render_view(struct sway_output *output, pixman_region32_t *damage,
box.y = floor(state->content_y);
box.width = state->border_thickness;
box.height = state->content_height;
// adjust sizing for rounded border corners
if (config->corner_radius) {
if (!has_titlebar) {
box.y += config->corner_radius;
box.height -= 2 * config->corner_radius;
} else {
box.height -= config->corner_radius;
}
}
scale_box(&box, output_scale);
render_rect(output, damage, &box, color);
}
@ -418,8 +472,35 @@ static void render_view(struct sway_output *output, pixman_region32_t *damage,
box.y = floor(state->content_y + state->content_height);
box.width = state->width;
box.height = state->border_thickness;
// adjust sizing for rounded border corners
if (config->corner_radius) {
box.x += config->corner_radius;
box.width -= 2 * config->corner_radius;
}
scale_box(&box, output_scale);
render_rect(output, damage, &box, color);
// rounded bottom left & bottom right border corners
if (config->corner_radius) {
int size = 2 * (config->corner_radius + state->border_thickness);
int scaled_thickness = state->border_thickness * output_scale;
if (state->border_left) {
box.width = size;
box.height = size;
box.x = floor(state->x);
box.y = floor(state->y + state->height - size);
scale_box(&box, output_scale);
render_border_corner(output, damage, &box, color, scaled_thickness, BOTTOM_LEFT);
}
if (state->border_right) {
box.width = size;
box.height = size;
box.x = floor(state->x + state->width - size);
box.y = floor(state->y + state->height - size);
scale_box(&box, output_scale);
render_border_corner(output, damage, &box, color, scaled_thickness, BOTTOM_RIGHT);
}
}
}
}
@ -705,8 +786,38 @@ static void render_top_border(struct sway_output *output,
box.y = floor(state->y);
box.width = state->width;
box.height = state->border_thickness;
// adjust sizing for rounded border corners
if (config->corner_radius) {
box.x += config->corner_radius;
box.width -= 2 * config->corner_radius;
}
scale_box(&box, output_scale);
render_rect(output, output_damage, &box, color);
// render rounded top corner borders if corner_radius is set > 0
if (config->corner_radius) {
int size = 2 * (config->corner_radius + state->border_thickness);
int scaled_thickness = state->border_thickness * output_scale;
// top left
if (state->border_left) {
box.width = size;
box.height = size;
box.x = floor(state->x);
box.y = floor(state->y);
scale_box(&box, output_scale);
render_border_corner(output, output_damage, &box, color, scaled_thickness, TOP_LEFT);
}
// top right
if (state->border_right) {
box.width = size;
box.height = size;
box.x = floor(state->x + state->width - size);
box.y = floor(state->y);
scale_box(&box, output_scale);
render_border_corner(output, output_damage, &box, color, scaled_thickness, TOP_RIGHT);
}
}
}
struct parent_data {
@ -756,14 +867,15 @@ static void render_containers_linear(struct sway_output *output,
marks_texture = child->marks_unfocused;
}
if (state->border == B_NORMAL) {
bool has_titlebar = state->border == B_NORMAL;
render_view(output, damage, child, colors, has_titlebar);
if (has_titlebar) {
render_titlebar(output, damage, child, floor(state->x),
floor(state->y), state->width, colors,
title_texture, marks_texture);
} else if (state->border == B_PIXEL) {
render_top_border(output, damage, child, colors);
}
render_view(output, damage, child, colors);
} else {
render_container(output, damage, child,
parent->focused || child->current.focused);
@ -841,7 +953,7 @@ static void render_containers_tabbed(struct sway_output *output,
// Render surface and left/right/bottom borders
if (current->view) {
render_view(output, damage, current, current_colors);
render_view(output, damage, current, current_colors, true);
} else {
render_container(output, damage, current,
parent->focused || current->current.focused);
@ -904,7 +1016,7 @@ static void render_containers_stacked(struct sway_output *output,
// Render surface and left/right/bottom borders
if (current->view) {
render_view(output, damage, current, current_colors);
render_view(output, damage, current, current_colors, true);
} else {
render_container(output, damage, current,
parent->focused || current->current.focused);
@ -992,14 +1104,16 @@ static void render_floating_container(struct sway_output *soutput,
marks_texture = con->marks_unfocused;
}
if (con->current.border == B_NORMAL) {
bool has_titlebar = con->current.border == B_NORMAL;
render_view(soutput, damage, con, colors, has_titlebar);
if (has_titlebar) {
render_titlebar(soutput, damage, con, floor(con->current.x),
floor(con->current.y), con->current.width, colors,
title_texture, marks_texture);
has_titlebar = true;
} else if (con->current.border == B_PIXEL) {
render_top_border(soutput, damage, con, colors);
}
render_view(soutput, damage, con, colors);
} else {
render_container(soutput, damage, con, con->current.focused);
}