Skip to content

Commit

Permalink
Merge pull request #1342 from bynect/dynamic-height
Browse files Browse the repository at this point in the history
Implement dynamic height and change vertical padding
  • Loading branch information
bynect committed May 6, 2024
2 parents b00dca6 + 2b407f2 commit 20033b8
Show file tree
Hide file tree
Showing 15 changed files with 413 additions and 205 deletions.
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ dunstify: dunstify.o
${CC} -o ${@} dunstify.o ${DUNSTIFY_CFLAGS} ${DUNSTIFY_LDFLAGS}
endif

.PHONY: test test-valgrind test-coverage
.PHONY: test test-valgrind test-coverage functional-tests
test: test/test clean-coverage-run
# Make sure an error code is returned when the test fails
/usr/bin/env bash -c 'set -euo pipefail;\
Expand Down Expand Up @@ -124,6 +124,9 @@ test/%.o: test/%.c src/%.c
test/test: ${OBJ} ${TEST_OBJ}
${CC} -o ${@} ${TEST_OBJ} $(filter-out ${TEST_OBJ:test/%=src/%},${OBJ}) ${CFLAGS} ${LDFLAGS}

functional-tests: dunst dunstify
PREFIX=. ./test/functional-tests/test.sh

.PHONY: doc doc-doxygen
doc: docs/dunst.1 docs/dunst.5 docs/dunstctl.1

Expand Down Expand Up @@ -213,7 +216,7 @@ clean-wayland-protocols:
install-service install-service-dbus install-service-systemd \
uninstall uninstall-dunstctl uninstall-dunstrc \
uninstall-service uninstall-service-dbus uninstall-service-systemd \
uninstall-keepconf uninstall-purge
uninstall-keepconf uninstall-purge
install: install-dunst install-dunstctl install-dunstrc install-service

install-dunst: dunst doc
Expand Down
59 changes: 36 additions & 23 deletions docs/dunst.5.pod
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,11 @@ old geometry config as follows:

In the new config you can then set the following variables (make sure to remove
any negative signs)
width = <width>
height = <height>
offset = <offset>
origin = top-right # or top-left, or any other direction you prefer

width = <width>
height = <height>
offset = <offset>
origin = top-right # or top-left, or any other direction you prefer

=item B<width>

Expand All @@ -96,8 +97,9 @@ specify a constant width or two numbers for the minimum and maximum width. The
notification will expand from the minimum width as neccesary.

Examples:
width = 300 # constant width of 300
width = (0, 300) # width between 0 and 300

width = 300 # constant width of 300
width = (0, 300) # width between 0 and 300

When setting a width bigger than the screen, dunst will clamp the width to the
screen width. So if you want the notifcation to stretch the entire screen
Expand All @@ -106,7 +108,16 @@ screens exceed (e.g. 10000).

=item B<height>

The maximum height of a single notification.
The height of each notification in pixels. This can be a single number to
specify a constant height or two numbers for the minimum and maximum width. The
notification will expand from the minimum height as neccesary.

Examples:

height = 300 # constant height of 300
height = (0, 300) # height between 0 and 300

Note that unlike width, different notifications can have diffrent height values.

=item B<notification_limit> (default: 20)

Expand All @@ -123,15 +134,16 @@ more information.
The origin of the notification window on the screen. It can then be moved with
offset.
Origin can be one of:
top-left
top-center
top-right
bottom-left
bottom-center
bottom-right
left-center
center
right-center

top-left
top-center
top-right
bottom-left
bottom-center
bottom-right
left-center
center
right-center

=item B<offset> format: (horizontal, vertical)

Expand All @@ -140,8 +152,9 @@ of the screen specified by B<origin>. A negative offset will lead to the
notification being off screen.

Examples:
origin = top-right
offset = (10, 300) # a margin of 10 pixels from the right and 300 pixels from the top

origin = top-right
offset = (10, 300) # a margin of 10 pixels from the right and 300 pixels from the top

For backwards compatibility the syntax NxN is also accepted.

Expand Down Expand Up @@ -416,7 +429,7 @@ removed from the format.
=item B<vertical_alignment> (values: [top/center/bottom], default: center)

Defines how the text and icon should be aligned vertically within the
notification. If icons are disabled, this option has no effect.
notification.

=item B<show_age_threshold> (default: 60)

Expand Down Expand Up @@ -563,13 +576,13 @@ actions to be executed in sequence.

B<Defaults:>

=over 12
=over 8

=item * C<mouse_left_click="close_current">
=item * C<mouse_left_click=close_current>

=item * C<mouse_middle_click="do_action, close_current">
=item * C<mouse_middle_click=do_action, close_current>

=item * C<mouse_right_click="close_all">
=item * C<mouse_right_click=close_all>

=back

Expand Down
4 changes: 2 additions & 2 deletions dunstrc
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
# constant width of 300
width = 300

# The maximum height of a single notification, excluding the frame.
height = 300
# The height of a single notification, excluding the frame.
height = (0, 300)

# Position the notification in the top right corner
origin = top-right
Expand Down
134 changes: 70 additions & 64 deletions src/draw.c
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ static void layout_setup(struct colored_layout *cl, int width, int height, doubl
int icon_width = cl->icon ? get_icon_width(cl->icon, scale) + horizontal_padding : 0;
int text_width = width - 2 * settings.h_padding - (cl->n->icon_position == ICON_TOP ? 0 : icon_width);
int progress_bar_height = have_progress_bar(cl) ? settings.progress_bar_height + settings.padding : 0;
int max_text_height = MAX(0, settings.height.max - progress_bar_height - 2 * settings.padding);
int max_text_height = MAX(0, height - progress_bar_height - 2 * settings.padding);
layout_setup_pango(cl->l, text_width, max_text_height, cl->n->word_wrap, cl->n->ellipsize, cl->n->alignment);
}

Expand Down Expand Up @@ -245,11 +245,13 @@ static struct dimensions calculate_notification_dimensions(struct colored_layout
dim.h += progress_bar_height;
dim.w = dim.text_width + icon_width + 2 * settings.h_padding;

dim.h = MIN(settings.height.max, dim.h + settings.padding * 2);
dim.w = MAX(settings.width.min, dim.w);
if (have_progress_bar(cl))
dim.w = MAX(settings.progress_bar_min_width, dim.w);

dim.h = MIN(settings.height.max, dim.h + settings.padding * 2);
dim.h = MAX(settings.height.min, dim.h);

dim.w = MAX(settings.width.min, dim.w);
dim.w = MIN(settings.width.max, dim.w);

cl->n->displayed_height = dim.h;
Expand Down Expand Up @@ -426,11 +428,9 @@ static int layout_get_height(struct colored_layout *cl, double scale)
}


if (cl->n->icon_position == ICON_TOP && cl->n->icon) {
return h_icon + h_text + h_progress_bar + vertical_padding;
} else {
return MAX(h_text, h_icon) + h_progress_bar;
}
return (cl->n->icon_position == ICON_TOP && cl->n->icon)
? h_icon + h_text + h_progress_bar + vertical_padding
: MAX(h_text, h_icon) + h_progress_bar;
}

/* Attempt to make internal radius more organic.
Expand Down Expand Up @@ -705,88 +705,96 @@ static cairo_surface_t *render_background(cairo_surface_t *srf,
round(width * scale), round(height * scale));
}

static void render_content(cairo_t *c, struct colored_layout *cl, int width, double scale)
static void render_content(cairo_t *c, struct colored_layout *cl, int width, int height, double scale)
{
// Redo layout setup, while knowing the width. This is to make
// alignment work correctly
layout_setup(cl, width, settings.height.max, scale);
layout_setup(cl, width, height, scale);

const int h = layout_get_height(cl, scale);
LOG_D("Layout height %i", h);
int h_without_progress_bar = h;
// NOTE: Includes paddings!
int h_without_progress_bar = height;
if (have_progress_bar(cl)) {
h_without_progress_bar -= settings.progress_bar_height + settings.padding;
h_without_progress_bar -= settings.progress_bar_height + settings.padding;
}

int text_h = 0;
if (!cl->n->hide_text) {
int h_text = 0;
get_text_size(cl->l, NULL, &h_text, scale);
get_text_size(cl->l, NULL, &text_h, scale);
}

int text_x = settings.h_padding,
text_y = settings.padding + h_without_progress_bar / 2 - h_text / 2;

// text positioning
if (cl->icon) {
// vertical alignment
if (settings.vertical_alignment == VERTICAL_TOP) {
text_y = settings.padding;
} else if (settings.vertical_alignment == VERTICAL_BOTTOM) {
text_y = h_without_progress_bar + settings.padding - h_text;
if (text_y < 0)
text_y = settings.padding;
} // else VERTICAL_CENTER

// icon position
if (cl->n->icon_position == ICON_LEFT) {
text_x = get_icon_width(cl->icon, scale) + settings.h_padding + get_horizontal_text_icon_padding(cl->n);
} else if (cl->n->icon_position == ICON_TOP) {
text_y = get_icon_height(cl->icon, scale) + settings.padding + get_vertical_text_icon_padding(cl->n);
} // else ICON_RIGHT
}
cairo_move_to(c, round(text_x * scale), round(text_y * scale));
// text vertical alignment
int text_x = settings.h_padding,
text_y = settings.padding;

cairo_set_source_rgba(c, COLOR(cl, fg.r), COLOR(cl, fg.g), COLOR(cl, fg.b), COLOR(cl, fg.a));
pango_cairo_update_layout(c, cl->l);
pango_cairo_show_layout(c, cl->l);
}
if (settings.vertical_alignment == VERTICAL_CENTER) {
text_y = h_without_progress_bar / 2 - text_h / 2;
} else if (settings.vertical_alignment == VERTICAL_BOTTOM) {
text_y = h_without_progress_bar - settings.padding - text_h;
} // else VERTICAL_TOP

// icon positioning
if (cl->icon) {
unsigned int image_width = get_icon_width(cl->icon, scale),
image_height = get_icon_height(cl->icon, scale),
image_x = width - settings.h_padding - image_width,
image_y = settings.padding + h_without_progress_bar/2 - image_height/2;
if (cl->icon && cl->n->icon_position != ICON_OFF) {
int image_width = get_icon_width(cl->icon, scale),
image_height = get_icon_height(cl->icon, scale),
image_x = width - settings.h_padding - image_width,
image_y = text_y,
v_padding = get_vertical_text_icon_padding(cl->n);

// vertical alignment
if (settings.vertical_alignment == VERTICAL_TOP) {
image_y = settings.padding;
} else if (settings.vertical_alignment == VERTICAL_BOTTOM) {
image_y = h_without_progress_bar + settings.padding - image_height;
if (image_y < settings.padding || image_y > h_without_progress_bar)
image_y = settings.padding;
} // else VERTICAL_CENTER
switch (settings.vertical_alignment) {
case VERTICAL_TOP:
if (cl->n->icon_position == ICON_TOP) {
// Shift text downward
text_y += image_height + v_padding;
}
break;
case VERTICAL_CENTER:
if (cl->n->icon_position == ICON_TOP) {
// Adjust text and image by half
image_y -= (image_height + v_padding) / 2;
text_y += (image_height + v_padding) / 2;
} else {
image_y += text_h / 2 - image_height / 2;
}
break;
case VERTICAL_BOTTOM:
if (cl->n->icon_position == ICON_TOP) {
image_y -= image_height + v_padding;
} else {
image_y -= image_height - text_h;
}
break;
}

// icon position
if (cl->n->icon_position == ICON_LEFT) {
if (cl->n->icon_position == ICON_TOP) {
image_x = (width - image_width) / 2;
} else if (cl->n->icon_position == ICON_LEFT) {
image_x = settings.h_padding;
} else if (cl->n->icon_position == ICON_TOP) {
image_y = settings.padding;
image_x = width/2 - image_width/2;
text_x += image_width + get_horizontal_text_icon_padding(cl->n);
} // 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, settings.icon_corners);
cairo_fill(c);
}

// text positioning
if (!cl->n->hide_text) {
cairo_move_to(c, round(text_x * scale), round(text_y * scale));
cairo_set_source_rgba(c, COLOR(cl, fg.r), COLOR(cl, fg.g), COLOR(cl, fg.b), COLOR(cl, fg.a));
pango_cairo_update_layout(c, cl->l);
pango_cairo_show_layout(c, cl->l);
}

// progress bar positioning
if (have_progress_bar(cl)) {
int progress = MIN(cl->n->progress, 100);
unsigned int frame_x = 0;
unsigned int frame_width = settings.progress_bar_frame_width,
progress_width = MIN(width - 2 * settings.h_padding, settings.progress_bar_max_width),
progress_height = settings.progress_bar_height - frame_width,
frame_y = settings.padding + h - settings.progress_bar_height,
frame_y = h_without_progress_bar,
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 - 1;
Expand Down Expand Up @@ -853,11 +861,12 @@ static struct dimensions layout_render(cairo_surface_t *srf,

int bg_width = 0;
int bg_height = MIN(settings.height.max, (2 * settings.padding) + cl_h);
bg_height = MAX(settings.height.min, bg_height);

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);
render_content(c, cl, bg_width, bg_height, scale);

/* adding frame */
if (corners & (C_TOP | _C_FIRST))
Expand All @@ -866,10 +875,7 @@ static struct dimensions layout_render(cairo_surface_t *srf,
if (corners & (C_BOT | _C_LAST))
dim.y += settings.frame_width;

if ((2 * settings.padding + cl_h) < settings.height.max)
dim.y += cl_h + 2 * settings.padding;
else
dim.y += settings.height.max;
dim.y += bg_height;

if (settings.gap_size)
dim.y += settings.gap_size;
Expand Down
7 changes: 5 additions & 2 deletions src/settings.c
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,14 @@ void check_and_correct_settings(struct settings *s) {
DIE("setting width min (%i) is always greather than max (%i)", s->width.min, s->width.max);
}

if (s->height.min == INT_MIN) {
s->height.min = 0;
}
if (s->height.min < 0 || s->height.max < 0) {
DIE("setting height does not support negative values");
}
if (s->height.min != s->height.max) {
LOG_W("Dynamic height is not yet supported");
if (s->height.min > s->height.max) {
DIE("setting height min (%i) is always greather than max (%i)", s->height.min, s->height.max);
}

if (s->offset.x == INT_MIN || s->offset.y == INT_MAX) {
Expand Down
4 changes: 2 additions & 2 deletions src/settings_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -1528,9 +1528,9 @@ static const struct setting allowed_settings[] = {
{
.name = "height",
.section = "global",
.description = "The maximum height of a single notification, excluding the frame.",
.description = "The height of a notification, excluding the frame.",
.type = TYPE_LENGTH,
.default_value = "300",
.default_value = "(0, 300)",
.value = &settings.height,
.parser = NULL,
.parser_data = NULL,
Expand Down
Loading

0 comments on commit 20033b8

Please sign in to comment.