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

Add clickable menu #1436

Open
wants to merge 5 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
15 changes: 15 additions & 0 deletions dunstrc
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,21 @@
mouse_middle_click = do_action, close_current
mouse_right_click = close_all

### action menu

built_in_menu = 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
172 changes: 164 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,13 @@ 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 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 +334,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 +457,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 +521,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 +718,132 @@ 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 = 300 / 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);
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.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.padding + col * (base_button_width + settings.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);

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_rect(c, x, y, base_button_width, settings.menu_height, scale);

if (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;

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 +929,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 +946,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 +1014,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 +1069,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
23 changes: 23 additions & 0 deletions src/input.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,24 @@ struct notification *get_notification_at(const int y) {
return NULL;
}

bool handle_builtin_menu_click(int x, int y) {
if (!settings.built_in_menu) {
return false;
}

struct notification *n = get_notification_at(y);
if (n) {
if (menu_get_count(n) > 0) {
struct menu *m = menu_get_at(n, x, y);
if (m) {
signal_action_invoked(n, m->key);
return true;
}
}
}
return false;
}

void input_handle_click(unsigned int button, bool button_down, int mouse_x, int mouse_y){
LOG_I("Pointer handle button %i: %i", button, button_down);

Expand All @@ -63,6 +81,11 @@ void input_handle_click(unsigned int button, bool button_down, int mouse_x, int
return;
}

if (settings.built_in_menu){
if (handle_builtin_menu_click( mouse_x, mouse_y))
return;
}

enum mouse_action *acts;

switch (button) {
Expand Down
85 changes: 85 additions & 0 deletions src/menu.c
Original file line number Diff line number Diff line change
Expand Up @@ -392,4 +392,89 @@ static gpointer context_menu_thread(gpointer data)

return NULL;
}


gboolean menu_init(struct notification *n)
{
if (n->actions == NULL) {
return false;
}

gpointer p_key;
gpointer p_value;
GHashTableIter iter;
n->menus = g_array_sized_new(FALSE, FALSE, sizeof(struct menu),
g_hash_table_size(n->actions));

g_hash_table_iter_init(&iter, n->actions);
while (g_hash_table_iter_next(&iter, &p_key, &p_value)) {
char *key = (char *)p_key;
char *value = (char *)p_value;
struct menu button = {.value = g_strdup(value),
.key = g_strdup(key),
.x = 0,
.y = 0,
.width = 0,
.height = 0};

g_array_append_val(n->menus, button);
}
return true;
}

int menu_get_count(struct notification *n)
{
if (!n->menus) {
return 0;
}
return n->menus->len;
}

char *menu_get_label(struct notification *n, int index)
{
if (index < 0 || index >= n->menus->len || !n->menus)
return NULL;
struct menu *button = &g_array_index(n->menus, struct menu, index);
return button->value;
}

void menu_set_position(struct notification *n, int index, int x, int y,
int width, int height)
{
if (index < 0 || index >= n->menus->len || !n->menus)
return;
struct menu *button = &g_array_index(n->menus, struct menu, index);
button->x = x;
button->y = y;
button->width = width;
button->height = height;
}

void menu_free_array(struct notification *n)
{
if (!n->menus)
return;
for (guint i = 0; i < n->menus->len; i++) {
struct menu *button = &g_array_index(n->menus, struct menu, i);
g_free(button->value);
g_free(button->key);
}
g_array_free(n->menus, TRUE);
n->menus = NULL;
}

struct menu *menu_get_at(struct notification *n, int x, int y)
{
if (!n->menus)
return NULL;
for (guint i = 0; i < n->menus->len; i++) {
struct menu *button = &g_array_index(n->menus, struct menu, i);
if (x >= button->x && x <= button->x + button->width &&
y >= button->y && y <= button->y + button->height) {
return button;
}
}
return NULL;
}

/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
Loading
Loading