Skip to content

Add clickable menu #1436

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

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions config.mk
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pkg_config_packs := gio-2.0 \
ifneq (0,${WAYLAND})
pkg_config_packs += wayland-client
pkg_config_packs += wayland-cursor
pkg_config_packs += xkbcommon
endif

ifneq (0,${X11})
Expand Down
16 changes: 16 additions & 0 deletions dunstrc
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,22 @@
mouse_middle_click = do_action, close_current
mouse_right_click = close_all

### action menu

built_in_menu = true
built_in_menu_key_navigation = true
menu_frame_color = "#000000"

#set to true to fill the button with the frame color.
#set to false to draw the bounding box with the frame color and frame width.
menu_frame_fill = false
menu_frame_width = 1
menu_height = 30
menu_max_per_row = 4
menu_max_rows = 5
menu_max_width = 100
menu_min_width = 50

# Experimental features that may or may not work correctly. Do not expect them
# to have a consistent behaviour across releases.
[experimental]
Expand Down
200 changes: 192 additions & 8 deletions src/draw.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "settings.h"
#include "utils.h"
#include "icon-lookup.h"
#include "menu.h"

struct colored_layout {
PangoLayout *l;
Expand All @@ -36,6 +37,11 @@ window win;

PangoFontDescription *pango_fdesc;

static int calculate_menu_height(const struct colored_layout *cl);
static int calculate_max_button_width(const struct colored_layout *cl);
static int calculate_menu_per_row(const struct colored_layout *cl);
static int calculate_menu_rows(const struct colored_layout *cl);

// NOTE: Saves some characters
#define COLOR(cl, field) (cl)->n->colors.field

Expand Down Expand Up @@ -233,6 +239,19 @@ static bool have_progress_bar(const struct colored_layout *cl)
!cl->is_xmore);
}

static bool have_built_in_menu(const struct colored_layout *cl)
{
return (g_hash_table_size(cl->n->actions)>0 &&
settings.built_in_menu == true &&
!cl->is_xmore);
}

static bool have_build_in_menu_keyboard(const struct colored_layout *cl)
{
return (have_built_in_menu(cl) &&
settings.built_in_menu_key_navigation == true );
}

static void get_text_size(PangoLayout *l, int *w, int *h, double scale) {
pango_layout_get_pixel_size(l, w, h);
// scale the size down, because it may be rendered at higher DPI
Expand Down Expand Up @@ -321,6 +340,8 @@ static struct dimensions calculate_notification_dimensions(struct colored_layout
if (have_progress_bar(cl))
dim.w = MAX(settings.progress_bar_min_width, dim.w);

dim.h += calculate_menu_height(cl);

dim.h = MAX(settings.height.min, dim.h);
dim.h = MIN(settings.height.max, dim.h);

Expand Down Expand Up @@ -442,6 +463,10 @@ static struct colored_layout *layout_from_notification(cairo_t *c, struct notifi
g_error_free(err);
}

if (have_built_in_menu(cl)) {
menu_init(n);
}

n->first_render = false;
return cl;
}
Expand Down Expand Up @@ -502,7 +527,7 @@ static int layout_get_height(struct colored_layout *cl, double scale)

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;
: MAX(h_text, h_icon) + h_progress_bar + calculate_menu_height(cl);
}

/* Attempt to make internal radius more organic.
Expand Down Expand Up @@ -699,6 +724,153 @@ void draw_rounded_rect(cairo_t *c, float x, float y, int width, int height, int
cairo_close_path(c);
}


static int calculate_max_button_width(const struct colored_layout *cl)
{
int buttons = menu_get_count(cl->n);
if (buttons == 0) {
return 0;
}
const PangoFontDescription *desc = pango_layout_get_font_description(cl->l);
const int fontsize = pango_font_description_get_size(desc) / PANGO_SCALE;
int max_text_width = 0;
for (int i = 0; i < buttons; i++) {
char *label = menu_get_label(cl->n, i);
if (!label) {
continue;
}
int text_width = strlen(label) * fontsize;
if (text_width > max_text_width) {
max_text_width = text_width;
}
}
max_text_width = MAX(settings.menu_min_width, max_text_width);
max_text_width = MIN(settings.menu_max_width, max_text_width);
return max_text_width;
}

static int calculate_menu_per_row(const struct colored_layout *cl)
{
int menu_width = calculate_max_button_width(cl);
if (menu_width <= 0) {
return 0;
}

int menu_count = settings.width.max / menu_width;
menu_count = MIN(settings.menu_max_per_row, menu_count);
return menu_count;
}

static int calculate_menu_rows(const struct colored_layout *cl)
{
int buttons = menu_get_count(cl->n);
if (buttons <= 0) {
return 0;
}

int max_per_row = calculate_menu_per_row(cl);
if (max_per_row < 1) {
max_per_row = 1;
}

int needed_rows = (buttons + max_per_row - 1) / max_per_row;
return MIN(needed_rows, settings.menu_max_rows);
}

static int calculate_menu_height(const struct colored_layout *cl)
{
if (have_built_in_menu(cl)) {
int rows = calculate_menu_rows(cl);
return settings.menu_height * rows + settings.padding * rows;
} else {
return 0;
}
}

static void draw_built_in_menu(cairo_t *c, struct colored_layout *cl, int area_x, int area_y, int area_width,
int area_height, double scale)
{
if (!have_built_in_menu(cl))
return;

int buttons = menu_get_count(cl->n);
if (buttons == 0) {
return;
}

int max_per_row = calculate_menu_per_row(cl);
cl->n->actual_menu_per_row = max_per_row;
int rows = calculate_menu_rows(cl);
int base_button_width = calculate_max_button_width(cl);

pango_layout_set_attributes(cl->l, NULL);
pango_layout_set_font_description(cl->l, NULL);
pango_layout_set_font_description(cl->l, pango_fdesc);
PangoAttrList *attr = pango_attr_list_new();
pango_layout_set_attributes(cl->l, attr);

for (int row = 0; row < rows; row++) {
int buttons_in_row = MIN(buttons - row * max_per_row, max_per_row);
base_button_width = (area_width - settings.h_padding * (buttons_in_row + 1)) / buttons_in_row;

for (int col = 0; col < buttons_in_row; col++) {
int button_index = row * max_per_row + col;
if (button_index >= buttons)
break;

char *label = menu_get_label(cl->n, button_index);
if (!label)
continue;

int x = area_x + settings.h_padding + col * (base_button_width + settings.h_padding);
int y = area_y + row * (settings.menu_height + settings.padding);
menu_set_position(cl->n, button_index, x, y, base_button_width, settings.menu_height);

bool is_selected = have_build_in_menu_keyboard(cl) &&
(button_index == cl->n->selected_menu);

// set the color of the button according to the selected state
if (is_selected) {
// backeffect: use the foreground color as the background color
cairo_set_source_rgb(c, COLOR(cl, fg.r), COLOR(cl, fg.g), COLOR(cl, fg.b));
} else {
// normal state: use the background color as the background color
cairo_set_source_rgb(c, settings.menu_frame_color.r, settings.menu_frame_color.g,
settings.menu_frame_color.b);
}

cairo_set_line_width(c, settings.menu_frame_width);
draw_rounded_rect(c, x, y, base_button_width, settings.menu_height, settings.corner_radius, scale, settings.corners);

// selected items are filled, unselected items are only stroked
if (is_selected || settings.menu_frame_fill)
cairo_fill(c);
else
cairo_stroke(c);

pango_layout_set_text(cl->l, label, -1);

int text_width, text_height;
pango_layout_get_pixel_size(cl->l, &text_width, &text_height);
double text_x = x + (base_button_width - text_width) / 2;
double text_y = y + (settings.menu_height - text_height) / 2;

// reverse the colors for the text
if (is_selected) {
// For selected items: use the background color as the text color
cairo_set_source_rgba(c, COLOR(cl, bg.r), COLOR(cl, bg.g), COLOR(cl, bg.b), COLOR(cl, bg.a));
} else {
// For unselected items: use the foreground color as the text color
cairo_set_source_rgba(c, COLOR(cl, fg.r), COLOR(cl, fg.g), COLOR(cl, fg.b), COLOR(cl, fg.a));
}

cairo_move_to(c, text_x, text_y);
pango_cairo_show_layout(c, cl->l);
}
}
pango_attr_list_unref(attr);
}

static cairo_surface_t *render_background(cairo_surface_t *srf,
struct colored_layout *cl,
struct colored_layout *cl_next,
Expand Down Expand Up @@ -784,10 +956,12 @@ static void render_content(cairo_t *c, struct colored_layout *cl, int width, int
layout_setup(cl, width, height, scale);

// NOTE: Includes paddings!
int h_without_progress_bar = height;
if (have_progress_bar(cl)) {
h_without_progress_bar -= settings.progress_bar_height + settings.padding;
}
int h_text_and_icon = height;
if (have_progress_bar(cl))
h_text_and_icon -= settings.progress_bar_height + settings.padding;

if (have_built_in_menu(cl))
h_text_and_icon -= calculate_menu_height(cl);

int text_h = 0;
if (!cl->n->hide_text) {
Expand All @@ -799,9 +973,9 @@ static void render_content(cairo_t *c, struct colored_layout *cl, int width, int
text_y = settings.padding;

if (settings.vertical_alignment == VERTICAL_CENTER) {
text_y = h_without_progress_bar / 2 - text_h / 2;
text_y = h_text_and_icon / 2 - text_h / 2;
} else if (settings.vertical_alignment == VERTICAL_BOTTOM) {
text_y = h_without_progress_bar - settings.padding - text_h;
text_y = h_text_and_icon - settings.padding - text_h;
if (text_y < 0) text_y = settings.padding;
} // else VERTICAL_TOP

Expand Down Expand Up @@ -867,7 +1041,7 @@ static void render_content(cairo_t *c, struct colored_layout *cl, int width, int
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 = h_without_progress_bar,
frame_y = h_text_and_icon,
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 @@ -922,6 +1096,15 @@ static void render_content(cairo_t *c, struct colored_layout *cl, int width, int
scale, settings.progress_bar_corners);
cairo_stroke(c);
}

if (have_built_in_menu(cl)) {
int y = h_text_and_icon;
if (have_progress_bar(cl)) {
y += settings.progress_bar_height + settings.padding;
}
draw_built_in_menu(c, cl, 0, y, width, height, scale);
}

}

static struct dimensions layout_render(cairo_surface_t *srf,
Expand Down Expand Up @@ -1042,6 +1225,7 @@ void draw(void)
else if (!cl_next)
corners |= (settings.corners & C_BOT) | _C_LAST;

cl_this->n->displayed_top = dim.y;
dim = layout_render(image_surface, cl_this, cl_next, dim, corners);
corners &= ~(C_TOP | _C_FIRST);
}
Expand Down
Loading