Merge pull request #420 from dj95/feature/round_corners

Added: support for round corners
This commit is contained in:
Nikos Tsipinakis 2018-05-28 14:03:32 +03:00 committed by GitHub
commit 15c252afb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 167 additions and 6 deletions

View File

@ -41,6 +41,7 @@ settings_t defaults = {
.ignore_newline = false, .ignore_newline = false,
.line_height = 0, /* if line height < font height, it will be raised to font height */ .line_height = 0, /* if line height < font height, it will be raised to font height */
.notification_height = 0, /* if notification height < font height and padding, it will be raised */ .notification_height = 0, /* if notification height < font height and padding, it will be raised */
.corner_radius = 0,
.separator_height = 2, /* height of the separator line between two notifications */ .separator_height = 2, /* height of the separator line between two notifications */
.padding = 0, .padding = 0,

View File

@ -28,6 +28,7 @@ pkg_config_packs := dbus-1 \
pangocairo \ pangocairo \
x11 \ x11 \
xinerama \ xinerama \
xext \
"xrandr >= 1.5" \ "xrandr >= 1.5" \
xscrnsaver xscrnsaver

View File

@ -460,6 +460,18 @@ By enabling this setting dunst will not be able to detect when a monitor is
connected or disconnected which might break follow mode if the screen layout connected or disconnected which might break follow mode if the screen layout
changes. changes.
=item B<corner_radius> (default: 0)
Define the corner radius in pixels. A corner radius of 0 will result in
rectangular shaped notifications.
By enabling this setting the outer border and the frame will be shaped.
If you have multiple notifications, the whole window is shaped, not every
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.
=back =back
=head2 Shortcut section =head2 Shortcut section

View File

@ -207,6 +207,13 @@
# debug: all less than unimportant stuff # debug: all less than unimportant stuff
verbosity = mesg verbosity = mesg
# Define the corner radius of the notification window
# in pixel size. If the radius is 0, you have no rounded
# corners.
# The radius will be automatically lowered if it exceeds half of the
# notification height to avoid clipping text and/or icons.
corner_radius = 0
### Legacy ### Legacy
# Use the Xinerama extension instead of RandR for multi-monitor support. # Use the Xinerama extension instead of RandR for multi-monitor support.

View File

@ -172,6 +172,8 @@ static struct dimensions calculate_dimensions(GSList *layouts)
dim.h += 2 * settings.frame_width; dim.h += 2 * settings.frame_width;
dim.h += (g_slist_length(layouts) - 1) * settings.separator_height; dim.h += (g_slist_length(layouts) - 1) * settings.separator_height;
dim.corner_radius = settings.corner_radius;
int text_width = 0, total_width = 0; int text_width = 0, total_width = 0;
for (GSList *iter = layouts; iter; iter = iter->next) { for (GSList *iter = layouts; iter; iter = iter->next) {
colored_layout *cl = iter->data; colored_layout *cl = iter->data;
@ -217,6 +219,8 @@ static struct dimensions calculate_dimensions(GSList *layouts)
dim.h += h; dim.h += h;
text_width = MAX(w, text_width); text_width = MAX(w, text_width);
} }
dim.corner_radius = MIN(dim.corner_radius, h/2);
} }
if (dim.w <= 0) { if (dim.w <= 0) {
@ -389,12 +393,67 @@ static int layout_get_height(colored_layout *cl)
return MAX(h, h_icon); return MAX(h, h_icon);
} }
/**
* 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.
* Respectably the same for `last` with the bottom corners.
*/
static void draw_rounded_rect(cairo_t *c, int x, int y, int width, int height, int corner_radius, bool first, bool last)
{
const float degrees = M_PI / 180.0;
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);
}
cairo_close_path(c);
}
static cairo_surface_t *render_background(cairo_surface_t *srf, static cairo_surface_t *render_background(cairo_surface_t *srf,
colored_layout *cl, colored_layout *cl,
colored_layout *cl_next, colored_layout *cl_next,
int y, int y,
int width, int width,
int height, int height,
int corner_radius,
bool first, bool first,
bool last, bool last,
int *ret_width) int *ret_width)
@ -403,12 +462,15 @@ static cairo_surface_t *render_background(cairo_surface_t *srf,
cairo_t *c = cairo_create(srf); cairo_t *c = cairo_create(srf);
if (first) height += settings.frame_width; if (first)
if (last) height += settings.frame_width; height += settings.frame_width;
else height += settings.separator_height; if (last)
height += settings.frame_width;
else
height += settings.separator_height;
cairo_set_source_rgb(c, cl->frame.r, cl->frame.g, cl->frame.b); cairo_set_source_rgb(c, cl->frame.r, cl->frame.g, cl->frame.b);
cairo_rectangle(c, x, y, width, height); draw_rounded_rect(c, x, y, width, height, corner_radius, first, last);
cairo_fill(c); cairo_fill(c);
/* adding frame */ /* adding frame */
@ -426,7 +488,7 @@ static cairo_surface_t *render_background(cairo_surface_t *srf,
height -= settings.separator_height; height -= settings.separator_height;
cairo_set_source_rgb(c, cl->bg.r, cl->bg.g, cl->bg.b); cairo_set_source_rgb(c, cl->bg.r, cl->bg.g, cl->bg.b);
cairo_rectangle(c, x, y, width, height); draw_rounded_rect(c, x, y, width, height, corner_radius, first, last);
cairo_fill(c); cairo_fill(c);
if ( settings.sep_color != SEP_FRAME if ( settings.sep_color != SEP_FRAME
@ -504,7 +566,7 @@ static struct dimensions layout_render(cairo_surface_t *srf,
int bg_width = 0; int bg_width = 0;
int bg_height = MAX(settings.notification_height, (2 * settings.padding) + cl_h); int bg_height = MAX(settings.notification_height, (2 * settings.padding) + cl_h);
cairo_surface_t *content = render_background(srf, cl, cl_next, dim.y, dim.w, bg_height, first, last, &bg_width); cairo_surface_t *content = render_background(srf, cl, cl_next, dim.y, dim.w, bg_height, dim.corner_radius, first, last, &bg_width);
cairo_t *c = cairo_create(content); cairo_t *c = cairo_create(content);
render_content(c, cl, bg_width); render_content(c, cl, bg_width);

View File

@ -368,6 +368,12 @@ void load_settings(char *cmdline_config_path)
"Transparency. range 0-100" "Transparency. range 0-100"
); );
settings.corner_radius = option_get_int(
"global",
"corner_radius", "-corner_radius", defaults.corner_radius,
"Window corner radius"
);
{ {
char *c = option_get_string( char *c = option_get_string(
"global", "global",

View File

@ -84,6 +84,7 @@ typedef struct _settings {
keyboard_shortcut history_ks; keyboard_shortcut history_ks;
keyboard_shortcut context_ks; keyboard_shortcut context_ks;
bool force_xinerama; bool force_xinerama;
int corner_radius;
} settings_t; } settings_t;
extern settings_t settings; extern settings_t settings;

View File

@ -6,6 +6,7 @@
#include <X11/Xatom.h> #include <X11/Xatom.h>
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <X11/Xutil.h> #include <X11/Xutil.h>
#include <X11/extensions/shape.h>
#include <assert.h> #include <assert.h>
#include <cairo-xlib.h> #include <cairo-xlib.h>
#include <cairo.h> #include <cairo.h>
@ -80,6 +81,72 @@ static void x_win_move(window_x11 *win, int x, int y, int width, int height)
} }
} }
static void x_win_round_corners(window_x11 *win, const int rad)
{
const int width = win->dim.w;
const int height = win->dim.h;
const int dia = 2 * rad;
const int degrees = 64; // the factor to convert degrees to XFillArc's angle param
Pixmap mask = XCreatePixmap(xctx.dpy, win->xwin, width, height, 1);
XGCValues xgcv;
GC shape_gc = XCreateGC(xctx.dpy, mask, 0, &xgcv);
XSetForeground(xctx.dpy, shape_gc, 0);
XFillRectangle(xctx.dpy,
mask,
shape_gc,
0,
0,
width,
height);
XSetForeground(xctx.dpy, shape_gc, 1);
/* To mark all pixels, which should get exposed, we
* use a circle for every corner and two overlapping rectangles */
unsigned const int centercoords[] = {
0, 0,
width - dia - 1, 0,
0, height - dia - 1,
width - dia - 1, height - dia - 1,
};
for (int i = 0; i < sizeof(centercoords)/sizeof(unsigned int); i = i+2) {
XFillArc(xctx.dpy,
mask,
shape_gc,
centercoords[i],
centercoords[i+1],
dia,
dia,
degrees * 0,
degrees * 360);
}
XFillRectangle(xctx.dpy,
mask,
shape_gc,
rad,
0,
width-dia,
height);
XFillRectangle(xctx.dpy,
mask,
shape_gc,
0,
rad,
width,
height-dia);
XShapeCombineMask(xctx.dpy, win->xwin, ShapeBounding, 0, 0, mask, ShapeSet);
XFreePixmap(xctx.dpy, mask);
XShapeSelectInput(xctx.dpy,
win->xwin, ShapeNotifyMask);
}
void x_display_surface(cairo_surface_t *srf, window_x11 *win, const struct dimensions *dim) void x_display_surface(cairo_surface_t *srf, window_x11 *win, const struct dimensions *dim)
{ {
x_win_move(win, dim->x, dim->y, dim->w, dim->h); x_win_move(win, dim->x, dim->y, dim->w, dim->h);
@ -89,6 +156,8 @@ void x_display_surface(cairo_surface_t *srf, window_x11 *win, const struct dimen
cairo_paint(win->c_ctx); cairo_paint(win->c_ctx);
cairo_show_page(win->c_ctx); cairo_show_page(win->c_ctx);
x_win_round_corners(win, dim->corner_radius);
XFlush(xctx.dpy); XFlush(xctx.dpy);
} }

View File

@ -31,6 +31,8 @@ struct dimensions {
int y; int y;
int w; int w;
int h; int h;
int corner_radius;
}; };
typedef struct _xctx { typedef struct _xctx {