diff --git a/docs/dunst.5.pod b/docs/dunst.5.pod index b8df0031b..8f0e0ab5b 100644 --- a/docs/dunst.5.pod +++ b/docs/dunst.5.pod @@ -188,11 +188,25 @@ than half of the progress bar height. The corner radius of the progress bar in pixels. Gives the progress bar rounded corners. Set to 0 to disable. +=item B (default: all) + +Define which corners to round when drawing the progress bar. If progress_bar_corner_radius +is set to 0 this option will be ignored. + +See the B setting for the value format. + =item B (default: 0) The corner radius of the icon image in pixels. Gives the icon rounded corners. Set to 0 to disable. +=item B (default: all) + +Define which corners to round when drawing the icon image. If icon_corner_radius +is set to 0 this option will be ignored. + +See the B setting for the value format. + =item B (values: [true/false], default: true) If this is set to true, a notification indicating how many notifications are @@ -420,8 +434,8 @@ replace this and will need new settings. =item B (default: "Adwaita", example: "Adwaita, breeze") -Comma-separated of names of the the themes to use for looking up icons. This has -to be the name of the directory in which the theme is located, not the +Comma-separated list of names of the the themes to use for looking up icons. This +has to be the name of the directory in which the theme is located, not the human-friendly name of the theme. So for example, the theme B is located in F. In this case you have to set the theme to B. @@ -508,6 +522,14 @@ single notification. To avoid the corners clipping the icon or text the corner radius will be automatically lowered to half of the notification height if it exceeds it. +=item B (default: all) + +Define which corners to round when drawing the window. If the corner radius +is set to 0 this option will be ignored. + +Comma-separated list of the corners. The accepted corner values are bottom-right, +bottom-left, top-right, top-left, top, bottom, left, right or all. + =item B (values: [none/do_action/close_current/close_all/context/context_all]) Defines action of mouse click. A touch input in Wayland acts as a mouse left diff --git a/dunstrc b/dunstrc index b92482b77..a8b433851 100644 --- a/dunstrc +++ b/dunstrc @@ -63,9 +63,17 @@ # Corner radius for the progress bar. 0 disables rounded corners. progress_bar_corner_radius = 0 + # Define which corners to round when drawing the progress bar. If progress_bar_corner_radius + # is set to 0 this option will be ignored. + progress_bar_corners = all + # Corner radius for the icon image. icon_corner_radius = 0 + # Define which corners to round when drawing the icon image. If icon_corner_radius + # is set to 0 this option will be ignored. + icon_corners = all + # Show how many messages are currently hidden (because of # notification_limit). indicate_hidden = yes @@ -252,6 +260,13 @@ # notification height to avoid clipping text and/or icons. corner_radius = 0 + # Define which corners to round when drawing the window. If the corner radius + # is set to 0 this option will be ignored. + # + # Comma-separated list of the corners. The accepted corner values are bottom-right, + # bottom-left, top-right, top-left, top, bottom, left, right or all. + corners = all + # Ignore the dbus closeNotification message. # Useful to enforce the timeout set by dunst configuration. Without this # parameter, an application may close the notification sent before the diff --git a/src/draw.c b/src/draw.c index 9cf3e86a9..ac324d053 100644 --- a/src/draw.c +++ b/src/draw.c @@ -492,6 +492,14 @@ static int frame_internal_radius (int r, int w, int h) return ret / s; } +/** + * A small wrapper around cairo_rectange for drawing a scaled rectangle. + */ +static inline void draw_rect(cairo_t *c, double x, double y, double width, double height, double scale) +{ + cairo_rectangle(c, round(x * scale), round(y * scale), round(width * scale), round(height * scale)); +} + /** * Create a path on the given cairo context to draw the background of a notification. * The top corners will get rounded by `corner_radius`, if `first` is set. @@ -500,66 +508,156 @@ static int frame_internal_radius (int r, int w, int h) * TODO: Pass additional frame width information to fix blurry lines due to fractional scaling * X and Y can then be calculated as: x = round(x*scale) + half_frame_width */ -void draw_rounded_rect(cairo_t *c, float x, float y, int width, int height, int corner_radius, double scale, bool first, bool last) +void draw_rounded_rect(cairo_t *c, float x, float y, int width, int height, int corner_radius, double scale, enum corner_pos corners) { + // Fast path for simple rects + if (corners == C_NONE || corner_radius <= 0) { + draw_rect(c, x, y, width, height, scale); + return; + } + width = round(width * scale); height = round(height * scale); x *= scale; y *= scale; corner_radius = round(corner_radius * scale); + // Nothing valid to draw + if (width <= 0 || height <= 0 || scale <= 0) + return; + const double degrees = M_PI / 180.0; + // This seems to be needed for a problem that occurs mostly when using cairo_stroke + // and not cairo_fill. Since the only case where we have partial angles is the progress + // bar and there we use fill, maybe this can be removed completely? + enum corner_pos skip = C_NONE; + + float top_y_off = 0, bot_y_off = 0, top_x_off, bot_x_off; + top_x_off = bot_x_off = MAX(width - corner_radius, corner_radius); + + double bot_left_angle1 = degrees * 90; + double bot_left_angle2 = degrees * 180; + + double top_left_angle1 = degrees * 180; + double top_left_angle2 = degrees * 270; + + double top_right_angle1 = degrees * 270; + double top_right_angle2 = degrees * 360; + + double bot_right_angle1 = degrees * 0; + double bot_right_angle2 = degrees * 90; + + // The trickiest cases to handle are when the width is less than corner_radius and corner_radius * 2, + // because we have to split the angle for the arc in the rounded corner + if (width <= corner_radius) { + double angle1 = 0, angle2 = 0; + + // If there are two corners on the top/bottom they occupy half of the width + if ((corners & C_TOP) == C_TOP) + angle1 = acos(1.0 - ((double)width / 2.0) / (double)corner_radius); + else + angle1 = acos(1.0 - (double)width / (double)corner_radius); + + if ((corners & C_BOT) == C_BOT) + angle2 = acos(1.0 - ((double)width / 2.0) / (double)corner_radius); + else + angle2 = acos(1.0 - (double)width / (double)corner_radius); + + if ((corners & (C_TOP_RIGHT | C_BOT_LEFT)) == (C_TOP_RIGHT | C_BOT_LEFT) && !(corners & C_TOP_LEFT)) { + top_y_off -= corner_radius * (1.0 - sin(angle1)); + } + + if ((corners & (C_TOP_LEFT | C_BOT_RIGHT)) == (C_TOP_LEFT | C_BOT_RIGHT) && !(corners & C_BOT_LEFT)) { + bot_y_off = corner_radius * (1.0 - sin(angle2)); + } + + top_left_angle2 = degrees * 180 + angle1; + top_right_angle1 = degrees * 360 - angle1; + bot_left_angle1 = degrees * 180 - angle2; + bot_right_angle2 = angle2; + + top_x_off = -(corner_radius - width); + bot_x_off = -(corner_radius - width); + + if (corners != C_TOP && corners != C_BOT) + skip = ~corners; + + } else if (width <= corner_radius * 2 && (corners & C_LEFT && corners & C_RIGHT)) { + double angle1 = 0, angle2 = 0; + if (!(corners & C_TOP_LEFT) && corners & C_TOP_RIGHT) + top_x_off = width - corner_radius; + else + angle1 = acos((double)width / (double)corner_radius - 1.0); + + if (!(corners & C_BOT_LEFT) && corners & C_BOT_RIGHT) + bot_x_off = width - corner_radius; + else + angle2 = acos((double)width / (double)corner_radius - 1.0); + + top_right_angle2 = degrees * 360 - angle1; + bot_right_angle1 = angle2; + } + cairo_new_sub_path(c); - if (last) { - // bottom right - cairo_arc(c, - x + width - corner_radius, - y + height - corner_radius, - corner_radius, - degrees * 0, - degrees * 90); - // bottom left - cairo_arc(c, - x + corner_radius, - y + height - corner_radius, - corner_radius, - degrees * 90, - degrees * 180); - } else { - cairo_line_to(c, x + width, y + height); - cairo_line_to(c, x, y + height); - } - - if (first) { - // top left - cairo_arc(c, - x + corner_radius, - y + corner_radius, - corner_radius, - degrees * 180, - degrees * 270); - // top right - cairo_arc(c, - x + width - corner_radius, - y + corner_radius, - corner_radius, - degrees * 270, - degrees * 360); - } else { - cairo_line_to(c, x, y); - cairo_line_to(c, x + width, y); + // bottom left + if (!(skip & C_BOT_LEFT)) { + if (corners & C_BOT_LEFT) { + cairo_arc(c, + x + corner_radius, + y + height - corner_radius, + corner_radius, + bot_left_angle1, + bot_left_angle2); + } else { + cairo_line_to(c, x, y + height); + } } - cairo_close_path(c); -} + // top left + if (!(skip & C_TOP_LEFT)) { + if (corners & C_TOP_LEFT) { + cairo_arc(c, + x + corner_radius, + y + corner_radius, + corner_radius, + top_left_angle1, + top_left_angle2); + } else { + cairo_line_to(c, x, y); + } + } -/** - * A small wrapper around cairo_rectange for drawing a scaled rectangle. - */ -static void draw_rect(cairo_t *c, double x, double y, double width, double height, double scale) { - cairo_rectangle(c, round(x * scale), round(y * scale), round(width * scale), round(height * scale)); + // top right + if (!(skip & C_TOP_RIGHT)) { + if (corners & C_TOP_RIGHT) { + cairo_arc(c, + x + top_x_off, + y + corner_radius + top_y_off, + corner_radius, + top_right_angle1, + top_right_angle2); + } else { + cairo_line_to(c, x + width, y); + } + } + + // bottom right + if (!(skip & C_BOT_RIGHT)) { + if (corners & C_BOT_RIGHT) { + cairo_arc(c, + x + bot_x_off, + y + height - corner_radius + bot_y_off, + corner_radius, + bot_right_angle1, + bot_right_angle2); + } else { + cairo_line_to(c, x + width, y + height); + } + } + + cairo_close_path(c); } static cairo_surface_t *render_background(cairo_surface_t *srf, @@ -569,8 +667,7 @@ static cairo_surface_t *render_background(cairo_surface_t *srf, int width, int height, int corner_radius, - bool first, - bool last, + enum corner_pos corners, int *ret_width, double scale) { @@ -585,36 +682,36 @@ static cairo_surface_t *render_background(cairo_surface_t *srf, /* for correct combination of adjacent areas */ cairo_set_operator(c, CAIRO_OPERATOR_ADD); - if (first) + if (corners & (C_TOP | _C_FIRST)) height += settings.frame_width; - if (last) + if (corners & (C_BOT | _C_LAST)) height += settings.frame_width; else height += settings.separator_height; - draw_rounded_rect(c, x, y, width, height, corner_radius, scale, first, last); + draw_rounded_rect(c, x, y, width, height, corner_radius, scale, corners); /* adding frame */ x += settings.frame_width; - if (first) { + if (corners & (C_TOP | _C_FIRST)) { y += settings.frame_width; height -= settings.frame_width; } width -= 2 * settings.frame_width; - if (last) + if (corners & (C_BOT | _C_LAST)) height -= settings.frame_width; else height -= settings.separator_height; radius_int = frame_internal_radius(corner_radius, settings.frame_width, height); - draw_rounded_rect(c, x, y, width, height, radius_int, scale, first, last); + draw_rounded_rect(c, x, y, width, height, radius_int, scale, corners); cairo_set_source_rgba(c, cl->frame.r, cl->frame.g, cl->frame.b, cl->frame.a); cairo_fill(c); - draw_rounded_rect(c, x, y, width, height, radius_int, scale, first, last); + draw_rounded_rect(c, x, y, width, height, radius_int, scale, corners); cairo_set_source_rgba(c, cl->bg.r, cl->bg.g, cl->bg.b, cl->bg.a); cairo_fill(c); @@ -622,7 +719,7 @@ static cairo_surface_t *render_background(cairo_surface_t *srf, if ( settings.sep_color.type != SEP_FRAME && settings.separator_height > 0 - && !last) { + && (corners & (C_BOT | _C_LAST)) == 0) { struct color sep_color = layout_get_sepcolor(cl, cl_next); cairo_set_source_rgba(c, sep_color.r, sep_color.g, sep_color.b, sep_color.a); @@ -711,7 +808,7 @@ static void render_content(cairo_t *c, struct colored_layout *cl, int width, dou } // else ICON_RIGHT cairo_set_source_surface(c, cl->icon, round(image_x * scale), round(image_y * scale)); - draw_rounded_rect(c, image_x, image_y, image_width, image_height, settings.icon_corner_radius, scale, true, true); + draw_rounded_rect(c, image_x, image_y, image_width, image_height, settings.icon_corner_radius, scale, settings.icon_corners); cairo_fill(c); } @@ -725,7 +822,7 @@ static void render_content(cairo_t *c, struct colored_layout *cl, int width, dou frame_y = settings.padding + h - settings.progress_bar_height, progress_width_without_frame = progress_width - 2 * frame_width, progress_width_1 = progress_width_without_frame * progress / 100, - progress_width_2 = progress_width_without_frame - progress_width_1; + progress_width_2 = progress_width_without_frame - 1; switch (cl->n->progress_bar_alignment) { case PANGO_ALIGN_LEFT: @@ -738,37 +835,41 @@ static void render_content(cairo_t *c, struct colored_layout *cl, int width, dou frame_x = width - progress_width - settings.h_padding; break; } + unsigned int x_bar_1 = frame_x + frame_width, - x_bar_2 = x_bar_1 + progress_width_1; + x_bar_2 = x_bar_1 + 0.5; - double half_frame_width = frame_width / 2.0; + float half_frame_width = (float)frame_width / 2.0f; /* Draw progress bar * TODO: Modify draw_rounded_rect to fix blurry lines due to fractional scaling - * Note: the bar could be drawn a bit smaller, because the frame is drawn on top + * + * Note: for now the bar background is drawn a little bit smaller than the fill, however + * this solution is not particularly solid (basically subracting a pixel or two) */ - // left side (fill) - cairo_set_source_rgba(c, cl->highlight.r, cl->highlight.g, cl->highlight.b, cl->highlight.a); - draw_rounded_rect(c, x_bar_1, frame_y, progress_width_1, progress_height, - settings.progress_bar_corner_radius, scale, true, true); - cairo_fill(c); - // right side (background) + + // back layer (background) cairo_set_source_rgba(c, cl->bg.r, cl->bg.g, cl->bg.b, cl->bg.a); - draw_rounded_rect(c, x_bar_2, frame_y, progress_width_2, progress_height, - settings.progress_bar_corner_radius, scale, true, true); + draw_rounded_rect(c, x_bar_2, frame_y, progress_width_2, progress_height, + settings.progress_bar_corner_radius, scale, settings.progress_bar_corners); + cairo_fill(c); + // top layer (fill) + cairo_set_source_rgba(c, cl->highlight.r, cl->highlight.g, cl->highlight.b, cl->highlight.a); + draw_rounded_rect(c, x_bar_1, frame_y, progress_width_1, progress_height, + settings.progress_bar_corner_radius, scale, settings.progress_bar_corners); cairo_fill(c); // border cairo_set_source_rgba(c, cl->frame.r, cl->frame.g, cl->frame.b, cl->frame.a); cairo_set_line_width(c, frame_width * scale); draw_rounded_rect(c, - frame_x + half_frame_width, - frame_y + half_frame_width, - progress_width - frame_width, + frame_x + half_frame_width + 1, + frame_y, + progress_width - frame_width - 2, progress_height, settings.progress_bar_corner_radius, - scale, true, true); + scale, settings.progress_bar_corners); cairo_stroke(c); } } @@ -777,8 +878,7 @@ static struct dimensions layout_render(cairo_surface_t *srf, struct colored_layout *cl, struct colored_layout *cl_next, struct dimensions dim, - bool first, - bool last) + enum corner_pos corners) { double scale = output->get_scale(); const int cl_h = layout_get_height(cl, scale); @@ -789,16 +889,16 @@ static struct dimensions layout_render(cairo_surface_t *srf, int bg_width = 0; int bg_height = MIN(settings.height, (2 * settings.padding) + cl_h); - cairo_surface_t *content = render_background(srf, cl, cl_next, dim.y, dim.w, bg_height, dim.corner_radius, first, last, &bg_width, scale); + cairo_surface_t *content = render_background(srf, cl, cl_next, dim.y, dim.w, bg_height, dim.corner_radius, corners, &bg_width, scale); cairo_t *c = cairo_create(content); render_content(c, cl, bg_width, scale); /* adding frame */ - if (first) + if (corners & (C_TOP | _C_FIRST)) dim.y += settings.frame_width; - if (last) + if (corners & (C_BOT | _C_LAST)) dim.y += settings.frame_width; if ((2 * settings.padding + cl_h) < settings.height) @@ -872,7 +972,7 @@ void draw(void) cairo_t *c = output->win_get_context(win); - if(c == NULL) { + if (c == NULL) { return; } @@ -886,22 +986,19 @@ void draw(void) round(dim.w * scale), round(dim.h * scale)); - bool first = true; - bool last; + enum corner_pos corners = (settings.corners & C_TOP) | _C_FIRST; for (GSList *iter = layouts; iter; iter = iter->next) { struct colored_layout *cl_this = iter->data; struct colored_layout *cl_next = iter->next ? iter->next->data : NULL; - last = !cl_next; - - if (settings.gap_size) { - first = true; - last = true; - } - dim = layout_render(image_surface, cl_this, cl_next, dim, first, last); + if (settings.gap_size) + corners = settings.corners; + else if (!cl_next) + corners |= (settings.corners & C_BOT) | _C_LAST; - first = false; + dim = layout_render(image_surface, cl_this, cl_next, dim, corners); + corners &= ~(C_TOP | _C_FIRST); } output->display_surface(image_surface, win, &dim); diff --git a/src/draw.h b/src/draw.h index 0f8231701..f31fe9600 100644 --- a/src/draw.h +++ b/src/draw.h @@ -12,7 +12,33 @@ void draw_setup(void); void draw(void); -void draw_rounded_rect(cairo_t *c, float x, float y, int width, int height, int corner_radius, double scale, bool first, bool last); +/** + * Specify which corner to draw in draw_rouned_rect + * + * C_TOP_LEFT 0001 + * C_TOP_RIGHT 0010 + * C_BOT_LEFT 0100 + * C_BOT_RIGHT 1000 + */ +enum corner_pos { + C_NONE = 0, + C_TOP_LEFT = 1 << 0, + C_TOP_RIGHT = 1 << 1, + C_BOT_LEFT = 1 << 2, + C_BOT_RIGHT = 1 << 3, + C_TOP = C_TOP_LEFT | C_TOP_RIGHT, + C_BOT = C_BOT_LEFT | C_BOT_RIGHT, + C_LEFT = C_TOP_LEFT | C_BOT_LEFT, + C_RIGHT = C_TOP_RIGHT | C_BOT_RIGHT, + C_ALL = C_TOP | C_BOT, + + // These two values are internal only and + // should not be used outside of draw.c ! + _C_FIRST = 1 << 4, + _C_LAST = 1 << 5, +}; + +void draw_rounded_rect(cairo_t *c, float x, float y, int width, int height, int corner_radius, double scale, enum corner_pos corners); // TODO get rid of this function by passing scale to everything that needs it. double draw_get_scale(void); diff --git a/src/option_parser.c b/src/option_parser.c index 5935e1a6f..0ce400b58 100644 --- a/src/option_parser.c +++ b/src/option_parser.c @@ -81,7 +81,7 @@ int string_parse_enum_list_to_single(const void *data, char **s, int *ret) int len = string_array_length(s); for (int i = 0; i < len; i++) { if (!string_parse_enum(data, s[i], &tmp)) { - LOG_W("Unknown mouse action value: '%s'", s[i]); + LOG_W("Unknown value: '%s'", s[i]); return false; } tmp_ret |= tmp; @@ -205,6 +205,12 @@ int string_parse_bool(const void *data, const char *s, void *ret) return success; } +int string_parse_corners(const void *data, const char *s, void *ret) +{ + char **s_arr = string_to_array(s, ","); + return string_parse_enum_list_to_single(data, s_arr, ret); +} + int get_setting_id(const char *key, const char *section) { int error_code = 0; int partial_match_id = -1; @@ -403,7 +409,7 @@ bool set_rule(struct setting setting, char* value, char* section) { return set_rule_value(r, setting, value); } -void set_defaults() { +void set_defaults(void) { for (int i = 0; i < G_N_ELEMENTS(allowed_settings); i++) { // FIXME Rule settings can only have a default if they have an // working entry in the settings struct as well. Make an diff --git a/src/option_parser.h b/src/option_parser.h index 38ff10367..22ef89df1 100644 --- a/src/option_parser.h +++ b/src/option_parser.h @@ -13,8 +13,9 @@ int string_parse_enum(const void* data, const char *s, void * ret); int string_parse_sepcolor(const void *data, const char *s, void *ret); int string_parse_bool(const void *data, const char *s, void *ret); +int string_parse_corners(const void *data, const char *s, void *ret); -void set_defaults(); +void set_defaults(void); void save_settings(struct ini *ini); void cmdline_load(int argc, char *argv[]); diff --git a/src/settings.h b/src/settings.h index 4496ca1d1..8b1ce871a 100644 --- a/src/settings.h +++ b/src/settings.h @@ -10,6 +10,7 @@ #include "notification.h" #include "x11/x.h" +#include "draw.h" #define LIST_END (-1) @@ -149,6 +150,9 @@ struct settings { int progress_bar_frame_width; int progress_bar_corner_radius; int icon_corner_radius; + enum corner_pos corners; + enum corner_pos progress_bar_corners; + enum corner_pos icon_corners; bool progress_bar; enum zwlr_layer_shell_v1_layer layer; enum origin_values origin; diff --git a/src/settings_data.h b/src/settings_data.h index 5c68bb4ff..1d65e08b9 100644 --- a/src/settings_data.h +++ b/src/settings_data.h @@ -112,7 +112,7 @@ struct setting { /* * How to add/change a rule * ------------------------ - * + * * - Add variable to `struct rules` in `rules.h` (make sure to read the comment * at the top of the struct) * - Add variable to to `struct notification` in `notification.h` @@ -188,37 +188,37 @@ enum list_type { #define ENUM_END {NULL, 0} static const struct string_to_enum_def verbosity_enum_data[] = { - {"critical", G_LOG_LEVEL_CRITICAL }, - {"crit", G_LOG_LEVEL_CRITICAL }, - {"warning", G_LOG_LEVEL_WARNING }, - {"warn", G_LOG_LEVEL_WARNING }, - {"message", G_LOG_LEVEL_MESSAGE }, - {"mesg", G_LOG_LEVEL_MESSAGE }, - {"info", G_LOG_LEVEL_INFO }, - {"debug", G_LOG_LEVEL_DEBUG }, - {"deb", G_LOG_LEVEL_DEBUG }, + {"critical", G_LOG_LEVEL_CRITICAL }, + {"crit", G_LOG_LEVEL_CRITICAL }, + {"warning", G_LOG_LEVEL_WARNING }, + {"warn", G_LOG_LEVEL_WARNING }, + {"message", G_LOG_LEVEL_MESSAGE }, + {"mesg", G_LOG_LEVEL_MESSAGE }, + {"info", G_LOG_LEVEL_INFO }, + {"debug", G_LOG_LEVEL_DEBUG }, + {"deb", G_LOG_LEVEL_DEBUG }, ENUM_END, }; static const struct string_to_enum_def boolean_enum_data[] = { - {"True", true }, - {"true", true }, - {"On", true }, - {"on", true }, - {"Yes", true }, - {"yes", true }, - {"1", true }, + {"True", true }, + {"true", true }, + {"On", true }, + {"on", true }, + {"Yes", true }, + {"yes", true }, + {"1", true }, {"False", false }, {"false", false }, - {"Off", false }, - {"off", false }, - {"No", false }, - {"no", false }, - {"0", false }, - {"n", false }, - {"y", false }, - {"N", false }, - {"Y", true }, + {"Off", false }, + {"off", false }, + {"No", false }, + {"no", false }, + {"0", false }, + {"n", false }, + {"y", false }, + {"N", false }, + {"Y", true }, ENUM_END, }; @@ -306,15 +306,28 @@ static const struct string_to_enum_def layer_enum_data[] = { }; static const struct string_to_enum_def origin_enum_data[] = { - { "top-left", ORIGIN_TOP_LEFT }, - { "top-center", ORIGIN_TOP_CENTER }, - { "top-right", ORIGIN_TOP_RIGHT }, - { "bottom-left", ORIGIN_BOTTOM_LEFT }, + { "top-left", ORIGIN_TOP_LEFT }, + { "top-center", ORIGIN_TOP_CENTER }, + { "top-right", ORIGIN_TOP_RIGHT }, + { "bottom-left", ORIGIN_BOTTOM_LEFT }, { "bottom-center", ORIGIN_BOTTOM_CENTER }, - { "bottom-right", ORIGIN_BOTTOM_RIGHT }, - { "left-center", ORIGIN_LEFT_CENTER }, - { "right-center", ORIGIN_RIGHT_CENTER }, - { "center", ORIGIN_CENTER }, + { "bottom-right", ORIGIN_BOTTOM_RIGHT }, + { "left-center", ORIGIN_LEFT_CENTER }, + { "right-center", ORIGIN_RIGHT_CENTER }, + { "center", ORIGIN_CENTER }, + ENUM_END, +}; + +static const struct string_to_enum_def corners_enum_data[] = { + { "top-left", C_TOP_LEFT }, + { "top-right", C_TOP_RIGHT }, + { "bottom-left", C_BOT_LEFT }, + { "bottom-right", C_BOT_RIGHT }, + { "left", C_LEFT }, + { "right", C_RIGHT }, + { "top", C_TOP }, + { "bottom", C_BOT }, + { "all", C_ALL }, ENUM_END, }; @@ -1024,6 +1037,36 @@ static const struct setting allowed_settings[] = { .parser = NULL, .parser_data = NULL, }, + { + .name = "corners", + .section = "global", + .description = "Select the corners to round", + .type = TYPE_CUSTOM, + .default_value = "all", + .value = &settings.corners, + .parser = string_parse_corners, + .parser_data = corners_enum_data, + }, + { + .name = "progress_bar_corners", + .section = "global", + .description = "Select the corners to round for the progress bar", + .type = TYPE_CUSTOM, + .default_value = "all", + .value = &settings.progress_bar_corners, + .parser = string_parse_corners, + .parser_data = corners_enum_data, + }, + { + .name = "icon_corners", + .section = "global", + .description = "Select the corners to round for the icon image", + .type = TYPE_CUSTOM, + .default_value = "all", + .value = &settings.icon_corners, + .parser = string_parse_corners, + .parser_data = corners_enum_data, + }, { .name = "progress_bar_height", .section = "global", diff --git a/src/x11/x.c b/src/x11/x.c index 00ae6ff98..8f4bef5fe 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -114,7 +114,7 @@ static void x_win_corners_shape(struct window_x11 *win, const int rad) draw_rounded_rect(cr, 0, 0, width, height, rad, 1, - true, true); + settings.corners); cairo_fill(cr); cairo_show_page(cr); diff --git a/test/draw.c b/test/draw.c index cc564cac5..616da2590 100644 --- a/test/draw.c +++ b/test/draw.c @@ -240,14 +240,16 @@ TEST test_layout_render_no_gaps(void) dim = calculate_dimensions(layouts); image_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); - bool first = true; + enum corner_pos corners = C_TOP; for (GSList *iter = layouts; iter; iter = iter->next) { struct colored_layout *cl_this = iter->data; struct colored_layout *cl_next = iter->next ? iter->next->data : NULL; - dim = layout_render(image_surface, cl_this, cl_next, dim, first, !cl_next); + if (!cl_next) + corners |= C_BOT; + dim = layout_render(image_surface, cl_this, cl_next, dim, corners); - first = false; + corners &= ~C_TOP; } expected_y = get_expected_dimension_y_offset(layout_count); @@ -286,7 +288,7 @@ TEST test_layout_render_gaps(void) struct colored_layout *cl_this = iter->data; struct colored_layout *cl_next = iter->next ? iter->next->data : NULL; - dim = layout_render(image_surface, cl_this, cl_next, dim, true, true); + dim = layout_render(image_surface, cl_this, cl_next, dim, C_ALL); } expected_y = get_expected_dimension_y_offset(layout_count); diff --git a/test/option_parser.c b/test/option_parser.c index 8773fad10..5cdb75866 100644 --- a/test/option_parser.c +++ b/test/option_parser.c @@ -727,6 +727,82 @@ TEST test_string_to_length_invalid(void) PASS(); } +TEST test_string_to_corners(void) +{ + enum corner_pos corners = C_NONE; + + struct setting s; + s.type = TYPE_CUSTOM; + s.value = &corners; + s.parser = string_parse_corners; + s.parser_data = corners_enum_data; + + char buf[50]; + + // do not go until last element, since it's ENUM_END (all 0) + for (int i = 0; i < G_N_ELEMENTS(ellipsize_enum_data)-1; i++) { + sprintf(buf, "Failed in round %i", i); + ASSERTm(buf, set_from_string(&corners, s, corners_enum_data[i].string)); + ASSERT_EQm(buf, corners, corners_enum_data[i].enum_value); + } + + const char* inputs[] = { + "bottom,right", + "top-left, top-right", + "all", + "right,left,top,bottom", + "bottom-left,top-right", + "all,all", // still accepted + "all,top-right", + }; + const enum corner_pos results[] = { + C_BOT | C_RIGHT, + C_TOP_LEFT | C_TOP_RIGHT, + C_ALL, + C_ALL, + C_BOT_LEFT | C_TOP_RIGHT, + C_ALL, + C_ALL, + }; + + ARRAY_SAME_LENGTH(inputs, results); + + for (int i = 0; i < G_N_ELEMENTS(inputs); i++) { + sprintf(buf, "Failed in round %i", i); + ASSERTm(buf, set_from_string(&corners, s, inputs[i])); + ASSERT_EQm(buf, corners, results[i]); + } + PASS(); +} + +TEST test_string_to_corners_invalid(void) +{ + enum corner_pos corners = C_ALL + 1; + + struct setting s; + s.type = TYPE_CUSTOM; + s.value = &corners; + s.name = "corners_test"; + s.parser = string_parse_corners; + s.parser_data = corners_enum_data; + + const char* invalid_inputs[] = { + "none", + "al l", + "right;right", + "bot-left", + "top right, bottom left" + }; + + char buf[50]; + for (int i = 0; i < G_N_ELEMENTS(invalid_inputs); i++) { + sprintf(buf, "Failed in round %i", i); + ASSERT_FALSEm(buf, set_from_string(&corners, s, invalid_inputs[i])); + } + ASSERT_EQm("Enum should not change from invalid values", corners, C_ALL + 1); + PASS(); +} + #define TEST_ENUM(t) { \ ASSERT_EQ(sizeof(t), sizeof(int)); \ } @@ -743,6 +819,7 @@ TEST test_enum_size(void) TEST_ENUM(enum follow_mode); TEST_ENUM(enum mouse_action ); TEST_ENUM(enum zwlr_layer_shell_v1_layer); + TEST_ENUM(enum corner_pos); PASS(); } @@ -791,6 +868,8 @@ SUITE(suite_option_parser) RUN_TEST(test_enum_size); RUN_TEST(test_string_to_length); RUN_TEST(test_string_to_length_invalid); + RUN_TEST(test_string_to_corners); + RUN_TEST(test_string_to_corners_invalid); g_strfreev(argv); }