Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement dynamic height and change vertical padding #1342

Merged
merged 10 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
bynect marked this conversation as resolved.
Show resolved Hide resolved
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