GUI toolkit for LV2 plugins
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

505 lines
12 KiB

/*
* Copyright (C) 2020 Alexandros Theodotou <alex at zrythm dot org>
*
* This file is part of ZToolkit
*
* ZToolkit is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* ZToolkit is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Affero Public License
* along with ZToolkit. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <string.h>
#include "ztoolkit/ztk.h"
/** Height of the separator, excluding padding. */
#define SEPARATOR_HEIGHT 2
/** Padding to leave left/right/bot/top of the
* text. */
#define PADDING 2
static double
get_height (
ZtkComboBox * self)
{
double height = PADDING * 2;
for (int i = 0; i < self->num_elements; i++)
{
ZtkComboBoxElement * el = &self->elements[i];
if (el->is_separator)
{
height += SEPARATOR_HEIGHT + PADDING * 2;
}
else
{
/* the app is not set until the widget gets
* added to the app */
ZtkWidget * widget = (ZtkWidget *) self;
if (!widget->app)
continue;
cairo_t* cr =
(cairo_t*) puglGetContext (
widget->app->view);
cairo_text_extents_t extents;
cairo_set_font_size (cr, self->font_size);
cairo_text_extents (cr, el->label, &extents);
height += (int) extents.height + PADDING * 2;
}
}
return height;
}
static double
get_width (
ZtkComboBox * self)
{
double width = 12;
for (int i = 0; i < self->num_elements; i++)
{
ZtkComboBoxElement * el = &self->elements[i];
if (el->is_separator)
continue;
/* the app is not set until the widget gets
* added to the app */
ZtkWidget * widget = (ZtkWidget *) self;
if (!widget->app)
continue;
cairo_t* cr =
(cairo_t*) puglGetContext (
widget->app->view);
cairo_text_extents_t extents;
cairo_set_font_size (cr, self->font_size);
cairo_text_extents (cr, el->label, &extents);
width =
/* *2 for the element, *2 for the frame */
MAX (width, extents.width + PADDING * 4);
}
return width;
}
static void
get_dimensions (
ZtkComboBox * self,
ZtkRect * rect)
{
double height = get_height (self);
double width = get_width (self);
rect->width = width;
rect->height = height;
if (self->upwards)
{
if (self->backwards)
{
rect->x =
(self->parent->rect.x +
self->parent->rect.width) - width;
rect->y = self->parent->rect.y - height;
}
else
{
rect->x = self->parent->rect.x;
rect->y = self->parent->rect.y - height;
}
}
else /* downwards */
{
if (self->backwards)
{
rect->x =
(self->parent->rect.x +
self->parent->rect.width) - width;
rect->y =
self->parent->rect.y +
self->parent->rect.height;
}
else
{
rect->x = self->parent->rect.x;
rect->y =
self->parent->rect.y +
self->parent->rect.height;
}
}
}
static void
draw_cb (
ZtkWidget * w,
cairo_t * cr,
ZtkRect * draw_rect,
void * data)
{
ZtkComboBox * self = (ZtkComboBox *) w;
ZtkRect rect;
get_dimensions (self, &rect);
/* draw bg and frame */
ztk_color_set_for_cairo (
&self->frame_color, cr);
cairo_rectangle (
cr, rect.x, rect.y, rect.width,
rect.height);
cairo_fill (cr);
ztk_color_set_for_cairo (
&self->bg_color, cr);
cairo_rectangle (
cr, rect.x + PADDING, rect.y + PADDING,
rect.width - PADDING * 2,
rect.height - PADDING * 2);
cairo_fill (cr);
double height = rect.y + PADDING;
double next_height;
/* find element hit */
for (int i = 0; i < self->num_elements; i++)
{
ZtkComboBoxElement * el = &self->elements[i];
if (el->is_separator)
{
next_height =
height + SEPARATOR_HEIGHT +
PADDING * 2;
/* draw the element bg */
if (self->hovered_idx == i)
{
if (w->state & ZTK_WIDGET_STATE_PRESSED)
{
ztk_color_set_for_cairo (
&self->click_color, cr);
}
else
{
ztk_color_set_for_cairo (
&self->hover_color, cr);
}
cairo_rectangle (
cr, rect.x + PADDING, height,
rect.width - PADDING * 2,
next_height - height);
cairo_fill (cr);
}
/* draw the separator */
ztk_color_set_for_cairo (
&self->separator_color, cr);
cairo_rectangle (
cr, rect.x + PADDING * 2,
height + PADDING,
rect.width - PADDING * 4,
SEPARATOR_HEIGHT);
cairo_fill (cr);
height = next_height;
}
else
{
cairo_text_extents_t extents;
cairo_set_font_size (cr, self->font_size);
cairo_text_extents (
cr, el->label, &extents);
next_height =
height + extents.height +
PADDING * 2;
/* draw the element bg */
if (self->hovered_idx == i)
{
if (w->state & ZTK_WIDGET_STATE_PRESSED)
{
ztk_color_set_for_cairo (
&self->click_color, cr);
}
else
{
ztk_color_set_for_cairo (
&self->hover_color, cr);
}
cairo_rectangle (
cr, rect.x + PADDING, height,
rect.width - PADDING * 2,
next_height - height);
cairo_fill (cr);
}
/* draw the text */
if (self->hovered_idx == i)
{
if (w->state & ZTK_WIDGET_STATE_PRESSED)
{
ztk_color_set_for_cairo (
&self->text_click_color, cr);
}
else
{
ztk_color_set_for_cairo (
&self->text_hover_color, cr);
}
}
else
{
ztk_color_set_for_cairo (
&self->text_normal_color, cr);
}
cairo_move_to (
cr, rect.x + PADDING * 2,
height + PADDING + extents.height);
cairo_show_text (
cr, el->label);
height = next_height;
}
}
}
static void
update_cb (
ZtkWidget * w,
void * data)
{
}
static int
motion_cb (
ZtkWidget * widget,
const PuglEventMotion * event,
void * data)
{
/* set hovered */
ZtkComboBox * self = (ZtkComboBox *) widget;
double y = event->y;
double height = widget->rect.y + PADDING;
double next_height;
/* find element hit */
for (int i = 0; i < self->num_elements; i++)
{
ZtkComboBoxElement * el = &self->elements[i];
if (el->is_separator)
{
next_height =
height + SEPARATOR_HEIGHT +
PADDING * 2;
if (y >= height && y < next_height)
{
self->hovered_idx = i;
return 0;
}
height = next_height;
}
else
{
cairo_t* cr =
(cairo_t*) puglGetContext (
((ZtkWidget *) self)->app->view);
cairo_text_extents_t extents;
cairo_set_font_size (
cr, self->font_size);
cairo_text_extents (
cr, el->label, &extents);
next_height =
height + extents.height +
PADDING * 2;
if (y >= height && y < next_height)
{
self->hovered_idx = i;
return 0;
}
height = next_height;
}
}
self->hovered_idx = -1;
return 0;
}
static void
free_cb (
ZtkWidget * w,
void * data)
{
ZtkComboBox * self = (ZtkComboBox *) w;
free (self);
}
static int
button_event_cb (
ZtkWidget * widget,
const PuglEventButton * btn,
void * data)
{
ZtkComboBox * self = (ZtkComboBox *) widget;
const PuglEvent * ev = (const PuglEvent *) btn;
if (ztk_widget_is_hit (widget, btn->x, btn->y))
{
/* skip if already removed */
if (!ztk_app_contains_widget (
widget->app, widget))
return 0;
/* skip if not release or no valid hover */
if (ev->type != PUGL_BUTTON_RELEASE ||
self->hovered_idx < 0)
return 0;
ZtkComboBoxElement * el =
&self->elements[self->hovered_idx];
if (!el->is_separator)
{
/* activate */
el->activate_cb (
widget, el->activate_cb_data);
/* remove from app to hide the
* combobox */
ztk_app_remove_widget (
widget->app, widget);
return 0;
}
}
/* clicked outside */
else
{
/* remove widget */
ztk_app_remove_widget (
widget->app, widget);
}
return 0;
}
/**
* Initializes the defaults.
*/
static void
ztk_combo_box_init (
ZtkComboBox * self)
{
strcpy (self->font_name, "Cantarrel");
self->font_size = 12.0;
ztk_color_parse_hex (
&self->text_normal_color, "#DDDDDD");
ztk_color_parse_hex (
&self->text_hover_color, "#EEEEEE");
ztk_color_parse_hex (
&self->text_click_color, "#FFFFFF");
ztk_color_parse_hex (
&self->bg_color, "#323232");
ztk_color_parse_hex (
&self->frame_color, "#646464");
ztk_color_parse_hex (
&self->separator_color, "#AAAAAA");
ztk_color_parse_hex (
&self->hover_color, "#646464");
ztk_color_parse_hex (
&self->click_color, "#868686");
}
/**
* Creates a new combobox.
*
* @param parent The parent widget to spawn on.
* @param spawn_upwards Spawn upwards instead of
* downards.
*/
ZtkComboBox *
ztk_combo_box_new (
ZtkWidget * parent,
int spawn_upwards,
int spawn_backwards)
{
ZtkComboBox * self =
calloc (1, sizeof (ZtkComboBox));
ZtkRect rect = { 0, 0, 0, 0 };
ztk_widget_init (
(ZtkWidget *) self, ZTK_WIDGET_TYPE_COMBO_BOX,
&rect, update_cb, draw_cb,
free_cb);
ZtkWidget * widget = (ZtkWidget *) self;
/* catch button events */
widget->button_event_cb = button_event_cb;
widget->motion_event_cb = motion_cb;
self->parent = parent;
self->upwards = spawn_upwards;
self->backwards = spawn_backwards;
ztk_combo_box_init (self);
self->hovered_idx = -1;
/* update dimensions */
get_dimensions (self, &widget->rect);
return self;
}
/**
* @param data Data related to the current element
* to pass to the activate callback.
*/
void
ztk_combo_box_add_text_element (
ZtkComboBox * self,
const char * label,
ZtkWidgetActivateCallback activate_cb,
void * data)
{
ZtkComboBoxElement * el =
&self->elements[self->num_elements++];
strcpy (el->label, label);
el->is_separator = 0;
el->activate_cb = activate_cb;
el->activate_cb_data = data;
/* update dimensions */
ZtkWidget * widget = (ZtkWidget *) self;
get_dimensions (self, &widget->rect);
}
void
ztk_combo_box_add_separator (
ZtkComboBox * self)
{
ZtkComboBoxElement * el =
&self->elements[self->num_elements++];
el->is_separator = 1;
/* update dimensions */
ZtkWidget * widget = (ZtkWidget *) self;
get_dimensions (self, &widget->rect);
}
void
ztk_combo_box_clear (
ZtkComboBox * self)
{
self->num_elements = 0;
}