From 5fd1ec15222b1d42258cd8af3d62281ea966b81a Mon Sep 17 00:00:00 2001 From: ale <ale@incal.net> Date: Sun, 10 Mar 2019 20:45:31 +0000 Subject: [PATCH] Start adding humanization UI controls --- Makefile.am | 3 +- aux/verify-voice.c | 2 +- engine/voice.c | 5 +- engine/voice.h | 2 +- plugin/ui.c | 66 +++++++- plugin/widgets/knob.c | 371 ++++++++++++++++++++++++++++++++++++++++++ plugin/widgets/knob.h | 72 ++++++++ 7 files changed, 514 insertions(+), 7 deletions(-) create mode 100644 plugin/widgets/knob.c create mode 100644 plugin/widgets/knob.h diff --git a/Makefile.am b/Makefile.am index 20018fe..d36a5f3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -35,7 +35,8 @@ hydrumkit_la_LIBADD = libengine.la hydrumkit_ui_la_SOURCES = \ plugin/ui.c \ plugin/uris.h \ - plugin/lv2_util.h + plugin/lv2_util.h \ + plugin/widgets/knob.c plugin/widgets/knob.h hydrumkit_ui_la_CPPFLAGS = $(GTK_CFLAGS) hydrumkit_ui_la_LDFLAGS = -module -avoid-version -shared hydrumkit_ui_la_LIBADD = libengine.la $(GTK_LIBS) diff --git a/aux/verify-voice.c b/aux/verify-voice.c index c71f823..6c12cb2 100644 --- a/aux/verify-voice.c +++ b/aux/verify-voice.c @@ -21,7 +21,7 @@ void write_sample(const char *filename, float *data_l, float *data_r, int num_fr info.samplerate = SR; info.channels = 2; - info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; + info.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; sndfile = sf_open(filename, SFM_WRITE, &info); sf_writef_float(sndfile, buf, num_frames); diff --git a/engine/voice.c b/engine/voice.c index a0a7f9d..f8369c8 100644 --- a/engine/voice.c +++ b/engine/voice.c @@ -44,6 +44,7 @@ int voice_noteon(struct voice *voice, struct instrument *ins, int note, int velo voice->in_preamble = 1; voice->preamble_countdown = preamble; } else { + voice->in_preamble = 0; voice->playing = 1; voice->ticks = 0; } @@ -52,7 +53,7 @@ int voice_noteon(struct voice *voice, struct instrument *ins, int note, int velo } int voice_noteoff_if_note(struct voice *voice, int note) { - if ((voice->playing || voice->preamble_countdown) && voice->note == note) { + if ((voice->playing || voice->in_preamble) && voice->note == note) { adsr_release(&voice->adsr); return 1; } @@ -60,7 +61,7 @@ int voice_noteoff_if_note(struct voice *voice, int note) { } int voice_noteoff_if_mute_group(struct voice *voice, int mute_group) { - if ((voice->playing || voice->preamble_countdown) && voice->mute_group == mute_group) { + if ((voice->playing || voice->in_preamble) && voice->mute_group == mute_group) { adsr_release(&voice->adsr); return 1; } diff --git a/engine/voice.h b/engine/voice.h index aedd0ac..0a49b32 100644 --- a/engine/voice.h +++ b/engine/voice.h @@ -26,7 +26,7 @@ int voice_noteoff_if_mute_group(struct voice *, int); void voice_process(struct voice *, float *, float *, int); static inline int voice_is_playing(struct voice *voice) { - return voice->playing | voice->in_preamble; + return voice->playing || voice->in_preamble; } #endif diff --git a/plugin/ui.c b/plugin/ui.c index d1f5576..04c7615 100644 --- a/plugin/ui.c +++ b/plugin/ui.c @@ -22,6 +22,7 @@ #include <glib.h> #include <gobject/gclosure.h> #include <gtk/gtk.h> +#include "plugin/widgets/knob.h" #include <stdint.h> #include <stdlib.h> @@ -30,6 +31,8 @@ #define SAMPLER_UI_URI "http://lv2.incal.net/plugins/hydrumkit#ui" +#define KNOB_SIZE 32 + struct plugin_ui { LV2_Atom_Forge forge; LV2_URID_Map *map; @@ -44,8 +47,20 @@ struct plugin_ui { GtkWidget *load_dialog; GtkWidget *button_box; GtkWidget *box; + GtkWidget *humanizer_box; + GtkWidget *humanizer_button; + GtkWidget *humanizer_latency_max_knob; + GtkWidget *humanizer_latency_stddev_knob; + GtkWidget *humanizer_latency_laid_back_knob; + GtkWidget *humanizer_velocity_knob; GtkWidget *window; /* For optional show interface. */ + int humanizer_enabled; + float humanizer_latency_max_value; + float humanizer_latency_stddev_value; + float humanizer_latency_laid_back_value; + float humanizer_velocity_value; + char *filename; uint8_t forge_buf[1024]; @@ -95,6 +110,22 @@ static void on_load_dialog_response(GtkDialog *widget, gint response, void *hand }; } +static void on_humanizer_button_click(GtkButton *widget, void *handle) { + struct plugin_ui *ui = (struct plugin_ui *)handle; + + ui->humanizer_enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); + printf("humanizer %s\n", ui->humanizer_enabled ? "enabled" : "disabled"); +} + +static void on_humanizer_knob_change(KnobWidget *widget, void *handle) { + struct plugin_ui *ui = (struct plugin_ui *)handle; + printf("humanizer value change: latency=%g/%g/%g velocity=%g\n", + ui->humanizer_latency_max_value, + ui->humanizer_latency_stddev_value, + ui->humanizer_latency_laid_back_value, + ui->humanizer_velocity_value); +} + static int path_exists(const char *path) { struct stat stbuf; return (0 == stat(path, &stbuf)) ? 1 : 0; @@ -138,9 +169,14 @@ static LV2UI_Handle instantiate(const LV2UI_Descriptor *descriptor, // Construct Gtk UI ui->box = gtk_grid_new(); + gtk_orientable_set_orientation(GTK_ORIENTABLE(ui->box), GTK_ORIENTATION_VERTICAL); ui->label = gtk_label_new("No drumkit loaded."); ui->button_box = gtk_grid_new(); + ui->humanizer_box = gtk_grid_new(); + // Create the file chooser (we use a custom GtkButton instead of the + // default GtkFileChooserButton because the latter takes way too + // much UI real estate). filter = gtk_file_filter_new(); gtk_file_filter_add_pattern(filter, "drumkit.xml"); @@ -170,14 +206,33 @@ static LV2UI_Handle instantiate(const LV2UI_Descriptor *descriptor, } free(homebuf); } - + + // Create the humanizer controls. + ui->humanizer_button = gtk_check_button_new_with_label("Humanize"); + ui->humanizer_latency_max_knob = knob_widget_new(&ui->humanizer_latency_max_value, KNOB_SIZE, 0); + ui->humanizer_latency_stddev_knob = knob_widget_new(&ui->humanizer_latency_stddev_value, KNOB_SIZE, 0); + ui->humanizer_latency_laid_back_knob = knob_widget_new(&ui->humanizer_latency_laid_back_value, KNOB_SIZE, 0); + ui->humanizer_velocity_knob = knob_widget_new(&ui->humanizer_velocity_value, KNOB_SIZE, 0); + g_signal_connect(ui->humanizer_button, "clicked", G_CALLBACK(on_humanizer_button_click), ui); + g_signal_connect(ui->humanizer_latency_max_knob, "value-changed", G_CALLBACK(on_humanizer_knob_change), ui); + g_signal_connect(ui->humanizer_latency_stddev_knob, "value-changed", G_CALLBACK(on_humanizer_knob_change), ui); + g_signal_connect(ui->humanizer_latency_laid_back_knob, "value-changed", G_CALLBACK(on_humanizer_knob_change), ui); + g_signal_connect(ui->humanizer_velocity_knob, "value-changed", G_CALLBACK(on_humanizer_knob_change), ui); + + // Place all elements inside their containers. gtk_container_set_border_width(GTK_CONTAINER(ui->box), 4); - gtk_container_add(GTK_CONTAINER(ui->box), ui->button_box); gtk_container_add(GTK_CONTAINER(ui->button_box), ui->label); gtk_widget_set_hexpand(ui->label, TRUE); gtk_widget_set_halign(ui->label, GTK_ALIGN_CENTER); gtk_container_add(GTK_CONTAINER(ui->button_box), ui->load_button); gtk_widget_set_hexpand(ui->load_button, FALSE); + gtk_container_add(GTK_CONTAINER(ui->box), ui->button_box); + gtk_container_add(GTK_CONTAINER(ui->humanizer_box), ui->humanizer_button); + gtk_container_add(GTK_CONTAINER(ui->humanizer_box), ui->humanizer_latency_max_knob); + gtk_container_add(GTK_CONTAINER(ui->humanizer_box), ui->humanizer_latency_stddev_knob); + gtk_container_add(GTK_CONTAINER(ui->humanizer_box), ui->humanizer_latency_laid_back_knob); + gtk_container_add(GTK_CONTAINER(ui->humanizer_box), ui->humanizer_velocity_knob); + gtk_container_add(GTK_CONTAINER(ui->box), ui->humanizer_box); g_signal_connect(ui->load_dialog, "response", G_CALLBACK(on_load_dialog_response), ui); @@ -202,6 +257,13 @@ static void cleanup(LV2UI_Handle handle) { gtk_widget_destroy(ui->load_dialog); gtk_widget_destroy(ui->load_button); gtk_widget_destroy(ui->button_box); + gtk_widget_destroy(ui->humanizer_button); + gtk_widget_destroy(ui->humanizer_latency_max_knob); + gtk_widget_destroy(ui->humanizer_latency_stddev_knob); + gtk_widget_destroy(ui->humanizer_latency_laid_back_knob); + gtk_widget_destroy(ui->humanizer_velocity_knob); + gtk_widget_destroy(ui->humanizer_box); + gtk_widget_destroy(ui->box); free(ui); } diff --git a/plugin/widgets/knob.c b/plugin/widgets/knob.c new file mode 100644 index 0000000..a6b9657 --- /dev/null +++ b/plugin/widgets/knob.c @@ -0,0 +1,371 @@ +/* + * gui/widgets/knob.c - knob + * + * Copyright (C) 2018 Alexandros Theodotou + * Copyright (C) 2010 Paul Davis + * + * This file is part of Zrythm + * + * Zrythm is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Zrythm 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Zrythm. If not, see <https://www.gnu.org/licenses/>. + */ + +/** \file + */ +#include <stdint.h> +#include <stdlib.h> +#include <math.h> + +#include "plugin/widgets/knob.h" + +#include <gtk/gtk.h> + +G_DEFINE_TYPE (KnobWidget, knob_widget, GTK_TYPE_DRAWING_AREA) + +enum { + VALUE_CHANGED, + LAST_SIGNAL +}; + +static guint knob_signals[LAST_SIGNAL] = { 0 }; + +/** + * Draws the knob. + */ +static int +draw_cb (GtkWidget * widget, cairo_t * cr, void* data) +{ + guint width, height; + GdkRGBA color; + GtkStyleContext *context; + KnobWidget * self = (KnobWidget *) data; + + context = gtk_widget_get_style_context (widget); + + width = gtk_widget_get_allocated_width (widget); + height = gtk_widget_get_allocated_height (widget); + + gtk_render_background (context, cr, 0, 0, width, height); + cairo_pattern_t* shade_pattern; + + const float scale = width; + const float pointer_thickness = 3.0 * (scale/80); //(if the knob is 80 pixels wide, we want a 3-pix line on it) + + const float start_angle = ((180 - 65) * G_PI) / 180; + const float end_angle = ((360 + 65) * G_PI) / 180; + + const float value_angle = start_angle + (*self->value * (end_angle - start_angle)); + const float zero_angle = start_angle + (self->zero * (end_angle - start_angle)); + + float value_x = cos (value_angle); + float value_y = sin (value_angle); + + float xc = 0.5 + width/ 2.0; + float yc = 0.5 + height/ 2.0; + + cairo_translate (cr, xc, yc); //after this, everything is based on the center of the knob + + //get the knob color from the theme + + float center_radius = 0.48*scale; + float border_width = 0.8; + + + if (self->arc) + { + center_radius = scale*0.33; + + float inner_progress_radius = scale*0.38; + float outer_progress_radius = scale*0.48; + float progress_width = (outer_progress_radius-inner_progress_radius); + float progress_radius = inner_progress_radius + progress_width/2.0; + + /* dark surrounding arc background */ + cairo_set_source_rgb (cr, 0.3, 0.3, 0.3 ); + cairo_set_line_width (cr, progress_width); + cairo_arc (cr, 0, 0, progress_radius, start_angle, end_angle); + cairo_stroke (cr); + + //look up the surrounding arc colors from the config + // TODO + + //vary the arc color over the travel of the knob + float intensity = fabsf (*self->value - self->zero) / MAX(self->zero, (1.f - self->zero)); + const float intensity_inv = 1.0 - intensity; + float r = intensity_inv * self->end_color.red + + intensity * self->start_color.red; + float g = intensity_inv * self->end_color.green + + intensity * self->start_color.green; + float b = intensity_inv * self->end_color.blue + + intensity * self->start_color.blue; + + //draw the arc + cairo_set_source_rgb (cr, r,g,b); + cairo_set_line_width (cr, progress_width); + if (zero_angle > value_angle) + { + cairo_arc (cr, 0, 0, progress_radius, value_angle, zero_angle); + } + else + { + cairo_arc (cr, 0, 0, progress_radius, zero_angle, value_angle); + } + cairo_stroke (cr); + + //shade the arc + if (!self->flat) + { + shade_pattern = cairo_pattern_create_linear (0.0, -yc, 0.0, yc); //note we have to offset the pattern from our centerpoint + cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1,1,1, 0.15); + cairo_pattern_add_color_stop_rgba (shade_pattern, 0.5, 1,1,1, 0.0); + cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 1,1,1, 0.0); + cairo_set_source (cr, shade_pattern); + cairo_arc (cr, 0, 0, outer_progress_radius-1, 0, 2.0*G_PI); + cairo_fill (cr); + cairo_pattern_destroy (shade_pattern); + } + +#if 0 //black border + const float start_angle_x = cos (start_angle); + const float start_angle_y = sin (start_angle); + const float end_angle_x = cos (end_angle); + const float end_angle_y = sin (end_angle); + + cairo_set_source_rgb (cr, 0, 0, 0 ); + cairo_set_line_width (cr, border_width); + cairo_move_to (cr, (outer_progress_radius * start_angle_x), (outer_progress_radius * start_angle_y)); + cairo_line_to (cr, (inner_progress_radius * start_angle_x), (inner_progress_radius * start_angle_y)); + cairo_stroke (cr); + cairo_move_to (cr, (outer_progress_radius * end_angle_x), (outer_progress_radius * end_angle_y)); + cairo_line_to (cr, (inner_progress_radius * end_angle_x), (inner_progress_radius * end_angle_y)); + cairo_stroke (cr); + cairo_arc (cr, 0, 0, outer_progress_radius, start_angle, end_angle); + cairo_stroke (cr); +#endif + } + + if (!self->flat) + { + //knob shadow + cairo_save(cr); + cairo_translate(cr, pointer_thickness+1, pointer_thickness+1 ); + cairo_set_source_rgba (cr, 0, 0, 0, 0.1 ); + cairo_arc (cr, 0, 0, center_radius-1, 0, 2.0*G_PI); + cairo_fill (cr); + cairo_restore(cr); + + //inner circle +#define KNOB_COLOR 0, 90, 0, 0 + cairo_set_source_rgba(cr, KNOB_COLOR); /* knob color */ + cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI); + cairo_fill (cr); + + //gradient + if (self->bevel) + { + //knob gradient + shade_pattern = cairo_pattern_create_linear (0.0, -yc, 0.0, yc); //note we have to offset the gradient from our centerpoint + cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1,1,1, 0.2); + cairo_pattern_add_color_stop_rgba (shade_pattern, 0.2, 1,1,1, 0.2); + cairo_pattern_add_color_stop_rgba (shade_pattern, 0.8, 0,0,0, 0.2); + cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 0,0,0, 0.2); + cairo_set_source (cr, shade_pattern); + cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI); + cairo_fill (cr); + cairo_pattern_destroy (shade_pattern); + + //flat top over beveled edge + cairo_set_source_rgba (cr, 90, 0, 0, 0.5 ); + cairo_arc (cr, 0, 0, center_radius-pointer_thickness, 0, 2.0*G_PI); + cairo_fill (cr); + } + else + { + //radial gradient + shade_pattern = cairo_pattern_create_radial ( -center_radius, -center_radius, 1, -center_radius, -center_radius, center_radius*2.5 ); //note we have to offset the gradient from our centerpoint + cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1,1,1, 0.2); + cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 0,0,0, 0.3); + cairo_set_source (cr, shade_pattern); + cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI); + cairo_fill (cr); + cairo_pattern_destroy (shade_pattern); + } + } + else + { + /* color inner circle */ + cairo_set_source_rgba(cr, 70, 70, 70, 0.2); + cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI); + cairo_fill (cr); + } + + + //black knob border + cairo_set_line_width (cr, border_width); + cairo_set_source_rgba (cr, 0,0,0, 1 ); + cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI); + cairo_stroke (cr); + + //line shadow + if (!self->flat) { + cairo_save(cr); + cairo_translate(cr, 1, 1 ); + cairo_set_source_rgba (cr, 0,0,0,0.3 ); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_width (cr, pointer_thickness); + cairo_move_to (cr, (center_radius * value_x), (center_radius * value_y)); + cairo_line_to (cr, ((center_radius*0.4) * value_x), ((center_radius*0.4) * value_y)); + cairo_stroke (cr); + cairo_restore(cr); + } + + //line + cairo_set_source_rgba (cr, 1,1,1, 1 ); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_width (cr, pointer_thickness); + cairo_move_to (cr, (center_radius * value_x), (center_radius * value_y)); + cairo_line_to (cr, ((center_radius*0.4) * value_x), ((center_radius*0.4) * value_y)); + cairo_stroke (cr); + + //highlight if grabbed or if mouse is hovering over me + if (self->hover) + { + cairo_set_source_rgba (cr, 1,1,1, 0.12 ); + cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI); + cairo_fill (cr); + } + + cairo_identity_matrix(cr); +} + +static void +on_crossing (GtkWidget * widget, GdkEvent *event, void * data) +{ + KnobWidget * self = (KnobWidget *) data; + switch (gdk_event_get_event_type (event)) + { + case GDK_ENTER_NOTIFY: + self->hover = 1; + break; + + case GDK_LEAVE_NOTIFY: + if (!gtk_gesture_drag_get_offset (self->drag, + NULL, + NULL)) + self->hover = 0; + break; + } + gtk_widget_queue_draw(widget); +} + +static double clamp +(double x, double upper, double lower) +{ + return MIN(upper, MAX(x, lower)); +} + +static void +drag_update (GtkGestureDrag * gesture, + gdouble offset_x, + gdouble offset_y, + gpointer user_data) +{ + KnobWidget * self = (KnobWidget *) user_data; + offset_y = - offset_y; + int use_y = abs(offset_y - self->last_y) > abs(offset_x - self->last_x); + *self->value = clamp (*self->value + 0.004 * (use_y ? offset_y - self->last_y : offset_x - self->last_x), + 1.0f, 0.0f); + self->last_x = offset_x; + self->last_y = offset_y; + gtk_widget_queue_draw ((GtkWidget *)user_data); +} + +static void +drag_end (GtkGestureDrag *gesture, + gdouble offset_x, + gdouble offset_y, + gpointer user_data) +{ + KnobWidget * self = (KnobWidget *) user_data; + self->last_x = 0; + self->last_y = 0; + + g_signal_emit(self, knob_signals[VALUE_CHANGED], 0); +} + + +/** + * Creates a knob widget with the given options and binds it to the given value. + */ +KnobWidget * +knob_widget_new (float * value, + int size, + float zero) +{ + KnobWidget * self = g_object_new (KNOB_WIDGET_TYPE, NULL); + self->value = value; + self->size = size; /* default 30 */ + self->hover = 0; + self->zero = zero; /* default 0.05f */ + self->value = value; + self->arc = 1; + self->bevel = 1; + self->flat = 1; + self->start_color.red = 0.8; + self->start_color.green = 0.8; + self->start_color.blue = 0.8; + self->end_color.red = 0.7; + self->end_color.red = 0.7; + self->end_color.red = 0.7; + self->last_x = 0; + self->last_y = 0; + + /* set size */ + gtk_widget_set_size_request (GTK_WIDGET (self), size, size); + + /* connect signals */ + g_signal_connect (G_OBJECT (self), "draw", + G_CALLBACK (draw_cb), self); + g_signal_connect (G_OBJECT (self), "enter-notify-event", + G_CALLBACK (on_crossing), self); + g_signal_connect (G_OBJECT(self), "leave-notify-event", + G_CALLBACK (on_crossing), self); + g_signal_connect (G_OBJECT(self->drag), "drag-update", + G_CALLBACK (drag_update), self); + g_signal_connect (G_OBJECT(self->drag), "drag-end", + G_CALLBACK (drag_end), self); + return self; +} + +static void +knob_widget_init (KnobWidget * self) +{ + /* make it able to notify */ + gtk_widget_set_has_window (GTK_WIDGET (self), TRUE); + int crossing_mask = GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK; + gtk_widget_add_events (GTK_WIDGET (self), crossing_mask); + self->drag = GTK_GESTURE_DRAG (gtk_gesture_drag_new (GTK_WIDGET (&self->parent_instance))); +} + +static void +knob_widget_class_init (KnobWidgetClass * klass) +{ + knob_signals[VALUE_CHANGED] = + g_signal_new ("value-changed", + G_TYPE_FROM_CLASS(G_OBJECT_CLASS(klass)), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(KnobWidgetClass, value_changed), + NULL, NULL, + NULL, + G_TYPE_NONE, 0); +} diff --git a/plugin/widgets/knob.h b/plugin/widgets/knob.h new file mode 100644 index 0000000..c9ef71f --- /dev/null +++ b/plugin/widgets/knob.h @@ -0,0 +1,72 @@ +/* + * gui/widgets/knob.h - knob + * + * Copyright (C) 2018 Alexandros Theodotou + * + * This file is part of Zrythm + * + * Zrythm is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Zrythm 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Zrythm. If not, see <https://www.gnu.org/licenses/>. + */ + +/** \file + */ + +#ifndef __GUI_WIDGETS_KNOB_H__ +#define __GUI_WIDGETS_KNOB_H__ + +#include <gtk/gtk.h> + +#define KNOB_WIDGET_TYPE (knob_widget_get_type ()) +#define KNOB_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), KNOB_WIDGET_TYPE, Knob)) +#define KNOB_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), KNOB_WIDGET, KnobWidgetClass)) +#define IS_KNOB_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), KNOB_WIDGET_TYPE)) +#define IS_KNOB_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), KNOB_WIDGET_TYPE)) +#define KNOB_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), KNOB_WIDGET_TYPE, KnobWidgetClass)) + +typedef struct KnobWidget +{ + GtkDrawingArea parent_instance; + float * value; ///< value to bind to. + ///< this value will get updated as the knob turns + int size; ///< size in px + int hover; ///< used to detect if hovering or not + float zero; ///< zero point 0.0-1.0 */ + int arc; ///< draw arc around the knob or not + int bevel; ///< bevel + int flat; ///< flat or 3D + GdkColor start_color; ///< color away from zero point + GdkColor end_color; ///< color close to zero point + GtkGestureDrag *drag; ///< used for drag gesture + double last_x; ///< used in gesture drag + double last_y; ///< used in gesture drag +} KnobWidget; + +typedef struct KnobWidgetClass +{ + GtkDrawingAreaClass parent_class; + + void (* value_changed)(KnobWidget *); +} KnobWidgetClass; + +/** + * Creates a knob widget with the given options and binds it to the given value. + */ +KnobWidget * +knob_widget_new (float * value, + int size, + float zero); + + + +#endif -- GitLab