diff --git a/meson.build b/meson.build index 07049d3..939c320 100644 --- a/meson.build +++ b/meson.build @@ -20,6 +20,7 @@ src = files( 'src/core/raster.c', 'src/ui/uicontroller.c', 'src/ui/vektorcanvas.c', + 'src/ui/widgets/colorwheel.c', 'src/application/applicationstate.c' ) diff --git a/src/application/applicationstate.c b/src/application/applicationstate.c index d86f72e..8cf451a 100644 --- a/src/application/applicationstate.c +++ b/src/application/applicationstate.c @@ -1,9 +1,11 @@ #include "./applicationstate.h" #include "glib.h" +#include "gtk/gtk.h" #include "gtk/gtkrevealer.h" #include "src/core/primitives.h" #include "src/core/raster.h" #include "src/ui/vektorcanvas.h" +#include "src/ui/widgets/colorwheel.h" #include "src/util/color.h" typedef struct button_tool_set_data { @@ -19,7 +21,7 @@ static void appstate_set_tool(GtkButton* button, gpointer user_data) { // setting tool makes the sub-tools menu to close gtk_revealer_set_reveal_child(data->revealer, FALSE); - // setting tool also resets selected primitive + // setting tool also resets selected shape data->state->selectedShape = NULL; } @@ -29,6 +31,18 @@ static void appstate_reveal_subtools(GtkButton* button, gpointer user_data) { gtk_revealer_set_reveal_child(revealer, !visible); } +static void appstate_on_color_change(VektorColorWheel* wheel, gpointer user_data) { + VektorColor c = vektor_color_wheel_get_color(wheel); + VektorAppState* appstate = (VektorAppState*)user_data; + appstate->currentColor = c; + + if(appstate->selectedShape != NULL) { + appstate->selectedShape->style.stroke_color = c; + } + + gtk_gl_area_queue_render(GTK_GL_AREA(appstate->widgetState->workspaceCanvas)); +} + static void canvas_onclick(GtkGestureClick* gesture, int n_press, double x, double y, gpointer user_data) { @@ -46,7 +60,6 @@ static void canvas_onclick(GtkGestureClick* gesture, int n_press, double x, V2 normalized_coords = (V2){(2 * (x / widget_w)) - 1, 1 - (2 * (y / widget_h))}; - g_debug("<%f , %f>", normalized_coords.x, normalized_coords.y); vektor_appstate_canvas_click(state, normalized_coords.x, normalized_coords.y); gtk_gl_area_queue_render(GTK_GL_AREA(widget)); @@ -57,14 +70,14 @@ void vektor_appstate_canvas_click(VektorAppState* state, double x, double y) { begin_click_dispatch: if (state->selectedTool == VektorLineTool) { - // create new polyline primitive if none is selected + // create new polyline shape if none is selected if (state->selectedShape == NULL) { VektorPolyline* line = vektor_polyline_new(); VektorPrimitive linePrimitive = (VektorPrimitive){.kind = VEKTOR_POLYLINE, .polyline = line}; VektorStyle style = - (VektorStyle){.stroke_color = (VektorColor){255, 0, 0, 1}, + (VektorStyle){.stroke_color = state->currentColor, .stroke_width = 0.01}; VektorShape shape = (VektorShape){ .primitive = linePrimitive, .z_index = 0, .style = style}; @@ -96,6 +109,8 @@ void vektor_appstate_new(VektorWidgetState* wstate, VektorAppState* stateOut) { stateOut->shapeBuffer = malloc(sizeof(VektorShapeBuffer)); *stateOut->shapeBuffer = (VektorShapeBuffer){0}; stateOut->canvas = malloc(sizeof(VektorCanvas)); + stateOut->widgetState = wstate; + stateOut->currentColor = vektor_color_blank; vektor_canvas_init(wstate, stateOut->canvas, stateOut->shapeBuffer); // link all the buttons @@ -111,6 +126,10 @@ void vektor_appstate_new(VektorWidgetState* wstate, VektorAppState* stateOut) { G_CALLBACK(appstate_reveal_subtools), wstate->workspaceRevealerShapes); + // hook relevant stuff to master color picker + g_signal_connect(G_OBJECT(wstate->workspaceColorPicker), "color-changed", + G_CALLBACK(appstate_on_color_change), stateOut); + // Add click gesture to canvas GtkGesture* canvasClickGesture = gtk_gesture_click_new(); g_signal_connect(G_OBJECT(canvasClickGesture), "pressed", diff --git a/src/application/applicationstate.h b/src/application/applicationstate.h index e541683..f2ddf6b 100644 --- a/src/application/applicationstate.h +++ b/src/application/applicationstate.h @@ -9,9 +9,13 @@ typedef enum VektorAppTool { VektorLineTool } VektorAppTool; typedef struct VektorAppState { + VektorWidgetState* widgetState; + VektorAppTool selectedTool; VektorShape* selectedShape; + VektorColor currentColor; + // Logic space VektorShapeBuffer* shapeBuffer; // View space diff --git a/src/ui/uicontroller.c b/src/ui/uicontroller.c index b0c0bbe..f211339 100644 --- a/src/ui/uicontroller.c +++ b/src/ui/uicontroller.c @@ -4,8 +4,11 @@ #include "gtk/gtk.h" #include "gtk/gtkcssprovider.h" #include "gtk/gtkrevealer.h" +#include "src/ui/widgets/colorwheel.h" void vektor_uictrl_init(GtkApplication* app, VektorWidgetState* stateOut) { + g_type_ensure(VEKTOR_TYPE_COLOR_WHEEL); + GtkBuilder* builder = gtk_builder_new(); GError* error = NULL; @@ -27,10 +30,11 @@ void vektor_uictrl_init(GtkApplication* app, VektorWidgetState* stateOut) { GtkIconTheme* theme = gtk_icon_theme_get_for_display(gdk_display_get_default()); - if (gtk_icon_theme_has_icon(theme, "vektor-circle-symbolic")) + + /*if (gtk_icon_theme_has_icon(theme, "vektor-circle-symbolic")) g_print("GTK sees it!\n"); else - g_print("Still invisible...\n"); + g_print("Still invisible...\n");*/ // populate state stateOut->window = @@ -50,6 +54,8 @@ void vektor_uictrl_init(GtkApplication* app, VektorWidgetState* stateOut) { GTK_BUTTON(gtk_builder_get_object(builder, "button_rectangletool")); stateOut->workspaceButtonCircletool = GTK_BUTTON(gtk_builder_get_object(builder, "button_circletool")); + stateOut->workspaceColorPicker = + VEKTOR_COLOR_WHEEL(gtk_builder_get_object(builder, "color_picker")); // Set window properties gtk_window_set_application(stateOut->window, app); @@ -60,9 +66,5 @@ void vektor_uictrl_init(GtkApplication* app, VektorWidgetState* stateOut) { } void vektor_uictrl_map(VektorWidgetState* state) { - - // set the workspace divider to 7:3 ratio - int window_width = gtk_widget_get_width(GTK_WIDGET(state->window)); - g_print("%i", window_width); gtk_paned_set_position(state->workspacePaned, 800 * .7); } diff --git a/src/ui/uicontroller.h b/src/ui/uicontroller.h index 5ebd16d..fa600e4 100644 --- a/src/ui/uicontroller.h +++ b/src/ui/uicontroller.h @@ -3,6 +3,7 @@ #include "gtk/gtk.h" #include "gtk/gtkrevealer.h" +#include "src/ui/widgets/colorwheel.h" /* Global application widget state, holding references to @@ -19,6 +20,8 @@ typedef struct VektorWidgetState { GtkButton* workspaceButtonRecttool; GtkButton* workspaceButtonCircletool; + VektorColorWheel* workspaceColorPicker; + // GtkWidget* Workspace } VektorWidgetState; diff --git a/src/ui/vektorcanvas.c b/src/ui/vektorcanvas.c index 2b9accf..09d5a24 100644 --- a/src/ui/vektorcanvas.c +++ b/src/ui/vektorcanvas.c @@ -56,9 +56,6 @@ static void init_shader(void) { GLuint vertex = compile_shader(GL_VERTEX_SHADER, vert_src); GLuint fragment = compile_shader(GL_FRAGMENT_SHADER, frag_src); - printf("%s\n", vert_src); - printf("%s\n", frag_src); - shader_program = glCreateProgram(); glAttachShader(shader_program, vertex); glAttachShader(shader_program, fragment); @@ -99,11 +96,6 @@ static gboolean render(GtkGLArea* area, GdkGLContext* context, VektorShapeBuffer* prims) { vektor_rasterize(&vb, prims); - for (size_t i = 0; i < vb.count; i++) { - printf("Vertex %zu: x=%f, y=%f\n", i, vb.vertices[i].coords.x, - vb.vertices[i].coords.y); - } - glBufferData(GL_ARRAY_BUFFER, vb.count * sizeof(Vertex), vb.vertices, GL_STATIC_DRAW); @@ -122,7 +114,7 @@ static gboolean render(GtkGLArea* area, GdkGLContext* context, glDrawArrays(GL_TRIANGLES, 0, vb.count); GLenum err = glGetError(); - printf("OpenGL error: %x\n", err); + //printf("OpenGL error: %x\n", err); // glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); glBindVertexArray(0); glUseProgram(0); diff --git a/src/ui/widgets/colorwheel.c b/src/ui/widgets/colorwheel.c new file mode 100644 index 0000000..2f8693c --- /dev/null +++ b/src/ui/widgets/colorwheel.c @@ -0,0 +1,301 @@ +#include "colorwheel.h" +#include "cairo.h" +#include "gtk/gtk.h" +#include "gtk/gtkshortcut.h" + +#define M_PI 3.14159265358979323846 + +enum { COLOR_CHANGED, LAST_SIGNAL }; +static guint signals[LAST_SIGNAL]; + +struct _VektorColorWheel { + GtkDrawingArea parent_instance; + + double hue; + double saturation; + double lightness; + + gboolean dragging_wheel; + gboolean dragging_triangle; +}; + +G_DEFINE_TYPE(VektorColorWheel, vektor_color_wheel, GTK_TYPE_DRAWING_AREA) + +static gboolean point_in_triangle( + double px, double py, + double ax, double ay, + double bx, double by, + double cx, double cy, + double* u, double* v, double* w) +{ + double denom = + (by - cy)*(ax - cx) + + (cx - bx)*(ay - cy); + + *u = + ((by - cy)*(px - cx) + + (cx - bx)*(py - cy)) / denom; + + *v = + ((cy - ay)*(px - cx) + + (ax - cx)*(py - cy)) / denom; + + *w = 1 - *u - *v; + + return (*u >= 0 && *v >= 0 && *w >= 0); +} + +static void vektor_color_wheel_snapshot(GtkWidget* widget, GtkSnapshot* snapshot) { + VektorColorWheel* self = VEKTOR_COLOR_WHEEL(widget); + + int width = gtk_widget_get_width(widget); + int height = gtk_widget_get_height(widget); + + graphene_rect_t bounds = GRAPHENE_RECT_INIT(0,0,width,height); + cairo_t* cr = gtk_snapshot_append_cairo(snapshot, &bounds); + + double cx = width / 2.0; + double cy = height / 2.0; + + double outer_radius = MIN(width, height) / 2.0; + double wheel_radius = outer_radius * 0.95; + double inner_radius = wheel_radius * 0.9; + + double triangle_radius = wheel_radius * 0.75; + + // wheel draw + for(int a = 0; a < 360; a++) { + double angle_1 = a*(M_PI / 180.0); + double angle_2 = (a + 1) * (M_PI / 180.0); + + cairo_new_path(cr); + cairo_arc(cr, cx, cy, wheel_radius, angle_1, angle_2); + cairo_arc_negative(cr, cx, cy, inner_radius, angle_2, angle_1); + cairo_close_path(cr); + + float r,g,b; + gtk_hsv_to_rgb(a / 360.0, 1.0, 1.0, &r, &g, &b); + + cairo_set_source_rgb(cr, r, g, b); + cairo_fill(cr); + } + + // triangle draw + double ax = cx + triangle_radius; + double ay = cy; + + double bx = cx - 0.5 * triangle_radius; + double by = cy + 0.866 * triangle_radius; + + double cx2 = cx - 0.5 * triangle_radius; + double cy2 = cy - 0.866 * triangle_radius; + + cairo_new_path(cr); + + cairo_move_to(cr, ax, ay); + cairo_line_to(cr, bx, by); + cairo_line_to(cr, cx2, cy2); + cairo_close_path(cr); + + cairo_save(cr); + cairo_clip(cr); + + // base color (pure hue at full brightness) + float r, g, b; + gtk_hsv_to_rgb(self->hue, 1.0, 1.0, &r, &g, &b); + cairo_set_source_rgb(cr, r, g, b); + cairo_paint(cr); + + // White gradient: from pure hue (right) → white (bottom) + cairo_pattern_t* white = cairo_pattern_create_linear(ax, ay, bx, by); + cairo_pattern_add_color_stop_rgba(white, 0.0, 1,1,1, 0.0); // transparent at pure hue + cairo_pattern_add_color_stop_rgba(white, 1.0, 1,1,1, 1.0); // opaque white at bottom + cairo_set_source(cr, white); + cairo_paint(cr); + cairo_pattern_destroy(white); + + // Black gradient: from pure hue (right) → black (top) + cairo_pattern_t* black = cairo_pattern_create_linear(ax, ay, cx2, cy2); + cairo_pattern_add_color_stop_rgba(black, 0.0, 0,0,0, 0.0); // transparent at pure hue + cairo_pattern_add_color_stop_rgba(black, 1.0, 0,0,0, 1.0); // opaque black at top + cairo_set_source(cr, black); + cairo_paint(cr); + cairo_pattern_destroy(black); + + cairo_restore(cr); + + // triangle outline + cairo_set_source_rgb(cr,.1,.1,.1); + cairo_move_to(cr, ax, ay); + cairo_line_to(cr, bx, by); + cairo_line_to(cr, cx2, cy2); + cairo_close_path(cr); + + cairo_set_source_rgb(cr,.1,.1,.1); + cairo_stroke(cr); + + // selectors draw + + // triangle selector + double chroma_weight = self->saturation * self->lightness; + double white_weight = (1.0 - self->saturation) * self->lightness; + double black_weight = 1.0 - self->lightness; + + double px = ax * chroma_weight + bx * white_weight + cx2 * black_weight; + double py = ay * chroma_weight + by * white_weight + cy2 * black_weight; + + cairo_arc(cr, px, py, 5, 0, 2*M_PI); + + float fr, fg, fb; + vektor_colorout_wheel_get_color(self, &fr, &fg, &fb); + cairo_set_source_rgb(cr, fr, fg, fb); + cairo_fill_preserve(cr); + cairo_set_source_rgb(cr, 0.1, 0.1, 0.1); + cairo_set_line_width(cr, 2.0); + cairo_stroke(cr); + + // wheel selector + double selector_angle = self->hue * 2 * M_PI; + double selector_width = 0.08; + + cairo_new_path(cr); + + cairo_arc(cr, + cx, cy, + wheel_radius, + selector_angle - selector_width, + selector_angle + selector_width); + + cairo_arc_negative(cr, + cx, cy, + inner_radius, + selector_angle + selector_width, + selector_angle - selector_width); + + cairo_close_path(cr); + + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_stroke(cr); + + cairo_destroy(cr); +} + +static void on_click(GtkGestureClick* gesture, int n_press, double x, double y, gpointer data) { + VektorColorWheel* wheel = VEKTOR_COLOR_WHEEL(data); + GtkWidget* widget = GTK_WIDGET(wheel); + + int width = gtk_widget_get_width(widget); + int height = gtk_widget_get_height(widget); + + double outer_radius = MIN(width, height) / 2.0; + double wheel_radius = outer_radius * 0.95; + double inner_radius = wheel_radius * 0.9; + + double triangle_radius = wheel_radius * 0.75; + + double cx = width / 2.0; + double cy = height / 2.0; + + double ax = cx + triangle_radius; + double ay = cy; + + double bx = cx - 0.5 * triangle_radius; + double by = cy + 0.866 * triangle_radius; + + double cx2 = cx - 0.5 * triangle_radius; + double cy2 = cy - 0.866 * triangle_radius; + + double u,v,w; + if(point_in_triangle(x,y, ax,ay, bx,by, cx2,cy2, &u,&v,&w)) { + + double denom = u + v; + if (denom > 0.0001) { // avoid div-by-zero at black vertex + wheel->saturation = u / denom; + } else { + wheel->saturation = 0.0; // arbitrary, since S irrelevant at V=0 + } + wheel->lightness = denom; + g_signal_emit(wheel, signals[COLOR_CHANGED], 0); + + } else { + double dx = x - cx; + double dy = y - cy; + double dist = sqrt(dx*dx+dy*dy); + + if(dist > inner_radius && dist < outer_radius) { + + double angle = atan2(dy, dx); + if(angle < 0) { angle += 2 * M_PI; } + + wheel->hue = angle / (2*M_PI); + g_signal_emit(wheel, signals[COLOR_CHANGED], 0); + } + } + + gtk_widget_queue_draw(widget); +} + +static void on_drag(GtkGestureDrag* gesture, double offset_x, double offset_y, gpointer data) { + double x,y; + gtk_gesture_drag_get_start_point(gesture,&x,&y); + + x += offset_x; + y += offset_y; + + on_click(NULL,0,x,y,data); +} + +static void vektor_color_wheel_init(VektorColorWheel* self) { + GtkGesture* click = gtk_gesture_click_new(); + gtk_widget_add_controller(GTK_WIDGET(self), GTK_EVENT_CONTROLLER(click)); + + g_signal_connect(click, "pressed", G_CALLBACK(on_click), self); + + GtkGesture* drag = gtk_gesture_drag_new(); + gtk_widget_add_controller(GTK_WIDGET(self), GTK_EVENT_CONTROLLER(drag)); + + g_signal_connect(drag, "drag-update", G_CALLBACK(on_drag), self); +} + +static void vektor_color_wheel_class_init(VektorColorWheelClass* klass) { + GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass); + widget_class->snapshot = vektor_color_wheel_snapshot; + + signals[COLOR_CHANGED] = + g_signal_new( + "color-changed", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0 + ); +} + +GtkWidget* vektor_color_wheel_new(void) { + return g_object_new(VEKTOR_TYPE_COLOR_WHEEL, NULL); +} + +VektorColor vektor_color_wheel_get_color(VektorColorWheel* wheel) { + float r,g,b; + gtk_hsv_to_rgb(wheel->hue, + wheel->saturation, + wheel->lightness, + &r, &g, &b); + + return (VektorColor) { + .r = (unsigned char)(r*255), + .g = (unsigned char)(g*255), + .b = (unsigned char)(b*255) + }; +} + +void vektor_colorout_wheel_get_color(VektorColorWheel* wheel, float* r, float* g, float* b) { + gtk_hsv_to_rgb(wheel->hue, + wheel->saturation, + wheel->lightness, + r, g, b); +} \ No newline at end of file diff --git a/src/ui/widgets/colorwheel.h b/src/ui/widgets/colorwheel.h new file mode 100644 index 0000000..3ddc952 --- /dev/null +++ b/src/ui/widgets/colorwheel.h @@ -0,0 +1,14 @@ +#ifndef VKTR_COLORWHEEL_H +#define VKTR_COLORWHEEL_H + +#include "gtk/gtk.h" +#include "src/util/color.h" + +#define VEKTOR_TYPE_COLOR_WHEEL vektor_color_wheel_get_type() +G_DECLARE_FINAL_TYPE(VektorColorWheel, vektor_color_wheel, VEKTOR, COLOR_WHEEL, GtkDrawingArea) + +GtkWidget* vektor_color_wheel_new(void); +VektorColor vektor_color_wheel_get_color(VektorColorWheel* wheel); +void vektor_colorout_wheel_get_color(VektorColorWheel* wheel, float* r, float* g, float* b); + +#endif \ No newline at end of file diff --git a/ui/main.ui b/ui/main.ui index 31b76fe..d2028d1 100644 --- a/ui/main.ui +++ b/ui/main.ui @@ -133,8 +133,36 @@ - - Sidepanel + + vertical + true + true + + + + true + true + 6 + 6 + 6 + 6 + + + + true + true + + + + + + + + + Modifiers + + +