Compare commits

..

30 Commits

Author SHA1 Message Date
Beriff
90eec8d301 chore: add modifier pipeline 2026-03-15 16:30:49 +07:00
Beriff
e9d1e5c47e fix(minor): link geometry_updated signal to zooming 2026-03-14 19:41:57 +07:00
31b2196976 fix: add bevel edge case to miter join 2026-03-14 12:31:03 +00:00
Beriff
bd586cda6a fix: selection quad bug 2026-03-14 18:49:52 +07:00
Beriff
ae58a60be9 Merge branch 'main' of https://bundleofsticks.store/git/Frox/Vektor 2026-03-14 18:49:02 +07:00
Beriff
43b6d284dd chore: refactor to use shape nodes 2026-03-14 18:25:02 +07:00
1fb4b1c1e1 fix: make the selection box thickness scale invariant 2026-03-14 15:07:42 +05:30
af6b0c4d30 feat: implement miter joins 2026-03-14 14:37:03 +05:30
Beriff
e054fc4fe7 fix(minor): apply canvas transform handle bbox detection 2026-03-14 02:19:46 +07:00
Beriff
e7dc799f54 chore: decouple rendering & geometry generation logic 2026-03-14 01:50:44 +07:00
Beriff
5e883e2d27 chore(minor): clean up circle_handles_updated logic 2026-03-12 21:46:21 +07:00
Beriff
09b84a2aa8 fix: adjust rect & circle handle logic 2026-03-12 21:43:03 +07:00
Beriff
7bc94d3a96 feat(untested): add handle dragging 2026-03-12 11:20:43 +07:00
Beriff
f96d6066ee chore: adjust build flags 2026-03-12 10:45:05 +07:00
237bb02a8c feat: add shape transforms 2026-03-11 14:11:03 +00:00
Beriff
562cbc12da feat: handle drawing 2026-03-11 21:04:11 +07:00
Beriff
ed9aca01e4 feat(untested): add handles base 2026-03-11 14:19:05 +07:00
Beriff
6c8ca19fbf feat: add circle tool 2026-03-11 09:46:30 +07:00
00031d145e fix: pass canvas scale to rasterizer 2026-03-10 18:51:48 +00:00
b0930c9e02 feat: add canvas pan and rotate 2026-03-10 18:44:11 +00:00
858a1f2c1a feat: add canvas zoom 2026-03-10 16:27:52 +00:00
22b6700768 feat: add circles 2026-03-10 15:36:19 +00:00
143a33558d fix: shrink buffers when unused fraction gets too large 2026-03-10 14:48:55 +00:00
Maxim
2d6746c99c Merge pull request #1 from Froxwin/bobbert
Bobbert
2026-03-10 20:29:17 +07:00
Beriff
64dd2d6e40 fix: update canvas real-time 2026-03-10 17:19:50 +07:00
Beriff
1d168f7be4 feat(experimental): add selection tool 2026-03-10 15:55:02 +07:00
Beriff
232b5c8f90 feat(experimental): add selection boxes 2026-03-10 15:31:03 +07:00
Beriff
61f9f1eed0 feat(experimental): add rectangle shape support 2026-03-10 02:02:22 +07:00
Beriff
2bdcbfae1f feat: add polygon tool 2026-03-09 23:26:12 +07:00
Beriff
9b4248981e feat: improve color picker UX 2026-03-09 21:08:47 +07:00
27 changed files with 1786 additions and 377 deletions

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22">
<defs>
<style id="current-color-scheme" type="text/css">
.ColorScheme-Text { color:#444444; } .ColorScheme-Highlight { color:#4285f4; } .ColorScheme-NeutralText { color:#ff9800; } .ColorScheme-PositiveText { color:#4caf50; } .ColorScheme-NegativeText { color:#f44336; }
</style>
</defs>
<path style="fill:currentColor" class="ColorScheme-Text" d="M 14.574219 1.0058594 C 13.520146 0.87298937 10.770478 2.75775 8.0605469 5.46875 C 6.8520776 6.67795 5.8032796 7.8729 5 9 C 5.9414561 9.29995 6.7002076 10.0582 7 11 C 8.1266713 10.19649 9.3243336 9.1522594 10.533203 7.9433594 C 13.607725 4.8675594 15.546263 1.8205187 14.863281 1.1367188 C 14.793083 1.0660188 14.697306 1.0216494 14.574219 1.0058594 z M 4.5 10.330078 L 4.5 10.332031 C 1.0001889 11.270271 3.6248533 13.4865 1 15 C 4.4998111 15 6.25 13.248178 6.25 12.080078 C 6.25 11.497798 6.3093545 10.426978 4.5 10.330078 z" transform="translate(3 3)"/>
</svg>

After

Width:  |  Height:  |  Size: 1005 B

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" version="1.1">
<defs>
<style id="current-color-scheme" type="text/css">
.ColorScheme-Text { color:#444444; } .ColorScheme-Highlight { color:#4285f4; } .ColorScheme-NeutralText { color:#ff9800; } .ColorScheme-PositiveText { color:#4caf50; } .ColorScheme-NegativeText { color:#f44336; }
</style>
</defs>
<g transform="translate(3,3)">
<path style="fill:currentColor" class="ColorScheme-Text" d="M 12.778,1.2222 C 12.778,1.2222 12.278,0.72224 11.778,1.2222 L 10,3 13,6 14.778,4.2222 C 15.278,3.7222 14.778,3.2222 14.778,3.2222 Z M 9,4 1,12 V 15 H 4 L 12,7 Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 646 B

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" version="1.1">
<defs>
<style id="current-color-scheme" type="text/css">
.ColorScheme-Text { color:#444444; } .ColorScheme-Highlight { color:#4285f4; } .ColorScheme-NeutralText { color:#ff9800; } .ColorScheme-PositiveText { color:#4caf50; } .ColorScheme-NegativeText { color:#f44336; }
</style>
</defs>
<path style="fill:currentColor" class="ColorScheme-Text" d="M 16.98,6.5 5.9551,7.502 5.5,8 v 10 l 0.8203,0.385 5.7481,-4.7912 5.7086,2.8532 0.719,-0.502 -1,-8.9997 z m -0.423,1.043 0.845,7.6 -5.1793,-2.5903 -0.543,0.0625 L 6.5,16.932 V 8.457 Z"/>
<path style="fill:currentColor" class="ColorScheme-Text" d="m 14,13 a 2,2 0 0 1 -2,2 2,2 0 0 1 -2,-2 2,2 0 0 1 2,-2 2,2 0 0 1 2,2 z M 8,8 A 2,2 0 0 1 6,10 2,2 0 0 1 4,8 2,2 0 0 1 6,6 2,2 0 0 1 8,8 Z m 0,10 a 2,2 0 0 1 -2,2 2,2 0 0 1 -2,-2 2,2 0 0 1 2,-2 2,2 0 0 1 2,2 z m 12,-2 a 2,2 0 0 1 -2,2 2,2 0 0 1 -2,-2 2,2 0 0 1 2,-2 2,2 0 0 1 2,2 z M 19,7 a 2,2 0 0 1 -2,2 2,2 0 0 1 -2,-2 2,2 0 0 1 2,-2 2,2 0 0 1 2,2 z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22">
<defs>
<style id="current-color-scheme" type="text/css">
.ColorScheme-Text { color:#444444; } .ColorScheme-Highlight { color:#4285f4; } .ColorScheme-NeutralText { color:#ff9800; } .ColorScheme-PositiveText { color:#4caf50; } .ColorScheme-NegativeText { color:#f44336; }
</style>
</defs>
<path style="fill:currentColor" class="ColorScheme-Text" d="M 3.9960938 1 L 4.0117188 12.535156 L 6.3339844 10.255859 L 6.7714844 9.8242188 L 7.0097656 10.404297 L 8.9023438 15 L 10.363281 14.328125 L 8.3808594 9.7792969 L 8.1347656 9.2128906 L 8.7285156 9.15625 L 11.996094 8.8457031 L 3.9960938 1 z" transform="translate(3 3)"/>
</svg>

After

Width:  |  Height:  |  Size: 719 B

View File

@@ -10,14 +10,22 @@ project(
], ],
) )
gtk = dependency('gtk4', required: true) c_args = meson.get_compiler('c').get_supported_arguments([
epoxy = dependency('epoxy') '-Wno-unused-variable',
'-Wno-unused-parameter',
'-Wno-pedantic'
])
add_project_arguments(c_args, language: 'c')
gtk = dependency('gtk4', required: true, include_type: 'system')
epoxy = dependency('epoxy', include_type: 'system')
src = files( src = files(
'src/main.c', 'src/main.c',
'src/core/matrix.c', 'src/core/matrix.c',
'src/core/primitives.c', 'src/core/primitives.c',
'src/core/raster.c', 'src/core/raster.c',
'src/core/modifier.c',
'src/ui/uicontroller.c', 'src/ui/uicontroller.c',
'src/ui/vektorcanvas.c', 'src/ui/vektorcanvas.c',
'src/ui/widgets/colorwheel.c', 'src/ui/widgets/colorwheel.c',

View File

@@ -0,0 +1,40 @@
#version 320 es
precision mediump float;
in vec2 vPos;
out vec4 FragColor;
uniform float uTime;
uniform float uScale;
uniform vec4 uColor1;
uniform vec4 uColor2;
uniform vec2 uMin;
uniform vec2 uMax;
void main()
{
float borderWidth = 0.008 / uScale;
float distX = min(vPos.x - uMin.x, uMax.x - vPos.x);
float distY = min(vPos.y - uMin.y, uMax.y - vPos.y);
float dist = min(distX, distY);
if (dist > borderWidth)
discard;
float dash_length = 0.025;
float gap_length = 0.015;
float total = dash_length + gap_length;
float speed = 0.3;
float distance_along = (vPos.x + vPos.y) * 20.0;
float t = mod( distance_along * total + uTime * speed, total);
if (t < dash_length)
FragColor = uColor2;
else
FragColor = uColor1;
}

View File

@@ -7,9 +7,11 @@ layout (location = 1) in vec4 aColor;
uniform mat4 uProjection; uniform mat4 uProjection;
out vec4 vColor; out vec4 vColor;
out vec2 vPos;
void main() void main()
{ {
gl_Position = uProjection * vec4(aPos, 0.0, 1.0); gl_Position = uProjection * vec4(aPos, 0.0, 1.0);
vPos = aPos;
vColor = aColor; vColor = aColor;
} }

View File

@@ -1,3 +1,5 @@
#include "src/core/matrix.h"
#include "src/core/modifier.h"
#include "src/ui/uicontroller.h" #include "src/ui/uicontroller.h"
#include "stdlib.h" #include "stdlib.h"
@@ -6,7 +8,7 @@
#include "gtk/gtk.h" #include "gtk/gtk.h"
#include "gtk/gtkrevealer.h" #include "gtk/gtkrevealer.h"
#include "src/core/primitives.h" #include "src/core/primitives.h"
#include "src/core/raster.h"
#include "src/ui/vektorcanvas.h" #include "src/ui/vektorcanvas.h"
#include "src/ui/widgets/colorwheel.h" #include "src/ui/widgets/colorwheel.h"
#include "src/util/color.h" #include "src/util/color.h"
@@ -22,10 +24,14 @@ static void appstate_set_tool(GtkButton* button, gpointer user_data) {
data->state->selectedTool = data->tool; data->state->selectedTool = data->tool;
// setting tool makes the sub-tools menu to close // setting tool makes the sub-tools menu to close
gtk_revealer_set_reveal_child(data->revealer, FALSE); // (ADD NEW REVEALERS HERE)
gtk_revealer_set_reveal_child(
data->state->widgetState->workspaceRevealerShapes, FALSE);
// setting tool also resets selected shape // setting tool also resets selected shape
data->state->selectedShape = NULL; // NOTE: isn't needed anymore, as you would
// want to be able to select & edit existing shapes
// data->state->selectedShape = NULL;
} }
static void appstate_reveal_subtools(GtkButton* button, gpointer user_data) { static void appstate_reveal_subtools(GtkButton* button, gpointer user_data) {
@@ -34,13 +40,14 @@ static void appstate_reveal_subtools(GtkButton* button, gpointer user_data) {
gtk_revealer_set_reveal_child(revealer, !visible); gtk_revealer_set_reveal_child(revealer, !visible);
} }
static void appstate_on_color_change(VektorColorWheel* wheel, gpointer user_data) { static void appstate_on_color_change(VektorColorWheel* wheel,
gpointer user_data) {
VektorColor c = vektor_color_wheel_get_color(wheel); VektorColor c = vektor_color_wheel_get_color(wheel);
VektorAppState* appstate = (VektorAppState*)user_data; VektorAppState* appstate = (VektorAppState*)user_data;
appstate->currentColor = c; appstate->currentColor = c;
if (appstate->selectedShape != NULL) { if (appstate->selectedShape != NULL) {
appstate->selectedShape->style.stroke_color = c; appstate->selectedShape->base->style.stroke_color = c;
} }
// set entry fields under the color selector // set entry fields under the color selector
@@ -48,24 +55,28 @@ static void appstate_on_color_change(VektorColorWheel* wheel, gpointer user_data
str_r = g_strdup_printf("%d", c.r); str_r = g_strdup_printf("%d", c.r);
str_g = g_strdup_printf("%d", c.g); str_g = g_strdup_printf("%d", c.g);
str_b = g_strdup_printf("%d", c.b); str_b = g_strdup_printf("%d", c.b);
gtk_editable_set_text(GTK_EDITABLE(appstate->widgetState->sidepanelEntryR), str_r); gtk_editable_set_text(GTK_EDITABLE(appstate->widgetState->sidepanelEntryR),
gtk_editable_set_text(GTK_EDITABLE(appstate->widgetState->sidepanelEntryG), str_g); str_r);
gtk_editable_set_text(GTK_EDITABLE(appstate->widgetState->sidepanelEntryB), str_b); gtk_editable_set_text(GTK_EDITABLE(appstate->widgetState->sidepanelEntryG),
str_g);
gtk_editable_set_text(GTK_EDITABLE(appstate->widgetState->sidepanelEntryB),
str_b);
gtk_gl_area_queue_render(GTK_GL_AREA(appstate->widgetState->workspaceCanvas)); vektor_canvas_geometry_changed(appstate->renderInfo);
} }
static void appstate_on_entry_update(GtkEntry* entry, gpointer user_data) { static void appstate_on_entry_update(GtkEntry* entry, gpointer user_data) {
VektorWidgetState* widgetState = (VektorWidgetState*)user_data; VektorWidgetState* widgetState = (VektorWidgetState*)user_data;
unsigned char r = (unsigned char)atoi(gtk_editable_get_text(GTK_EDITABLE(widgetState->sidepanelEntryR))); unsigned char r = (unsigned char)atoi(
unsigned char g = (unsigned char)atoi(gtk_editable_get_text(GTK_EDITABLE(widgetState->sidepanelEntryG))); gtk_editable_get_text(GTK_EDITABLE(widgetState->sidepanelEntryR)));
unsigned char b = (unsigned char)atoi(gtk_editable_get_text(GTK_EDITABLE(widgetState->sidepanelEntryB))); unsigned char g = (unsigned char)atoi(
gtk_editable_get_text(GTK_EDITABLE(widgetState->sidepanelEntryG)));
unsigned char b = (unsigned char)atoi(
gtk_editable_get_text(GTK_EDITABLE(widgetState->sidepanelEntryB)));
g_print("%d", r);
vektor_color_wheel_set_color( vektor_color_wheel_set_color(
VEKTOR_COLOR_WHEEL(widgetState->workspaceColorPicker), VEKTOR_COLOR_WHEEL(widgetState->workspaceColorPicker),
(VektorColor){.r = r, .g = g, .b = b} (VektorColor){.r = r, .g = g, .b = b});
);
} }
static void canvas_onclick(GtkGestureClick* gesture, int n_press, double x, static void canvas_onclick(GtkGestureClick* gesture, int n_press, double x,
@@ -79,19 +90,22 @@ static void canvas_onclick(GtkGestureClick* gesture, int n_press, double x,
int widget_w = gtk_widget_get_width(widget); int widget_w = gtk_widget_get_width(widget);
int widget_h = gtk_widget_get_height(widget); int widget_h = gtk_widget_get_height(widget);
int canvas_w = state->canvas->width;
int canvas_h = state->canvas->height;
V2 normalized_coords = V2 normalized_coords =
(V2){(2 * (x / widget_w)) - 1, 1 - (2 * (y / widget_h))}; (V2){(2 * (x / widget_w)) - 1, 1 - (2 * (y / widget_h))};
vektor_appstate_canvas_click(state, normalized_coords.x, vektor_appstate_canvas_click(state, normalized_coords.x,
normalized_coords.y); normalized_coords.y);
gtk_gl_area_queue_render(GTK_GL_AREA(widget));
// technically there are cases when a click would not result in change of
// the geometry but this is more concise then writing it inside that
// function a bunch of times and burder future click dispatches with
// handling this signal
vektor_canvas_geometry_changed(state->renderInfo);
} }
void vektor_appstate_canvas_click(VektorAppState* state, double x, double y) { void vektor_appstate_canvas_click(VektorAppState* state, double x, double y) {
V2 pos = (V2){x, y}; V2 pos =
m33_transform(m33_inverse(state->renderInfo->canvasMat), (V2){x, y});
begin_click_dispatch: begin_click_dispatch:
if (state->selectedTool == VektorLineTool) { if (state->selectedTool == VektorLineTool) {
@@ -101,34 +115,210 @@ begin_click_dispatch:
VektorPolyline* line = vektor_polyline_new(); VektorPolyline* line = vektor_polyline_new();
VektorPrimitive linePrimitive = VektorPrimitive linePrimitive =
(VektorPrimitive){.kind = VEKTOR_POLYLINE, .polyline = line}; (VektorPrimitive){.kind = VEKTOR_POLYLINE, .polyline = line};
VektorStyle style = VektorStyle style = (VektorStyle){
(VektorStyle){.stroke_color = state->currentColor, .stroke_color = state->currentColor, .stroke_width = 0.01};
.stroke_width = 0.01};
VektorShape shape = (VektorShape){
.primitive = linePrimitive, .z_index = 0, .style = style};
vektor_shapebuffer_add_shape(state->shapeBuffer, vektor_shape_new(linePrimitive, style, 0));
vektor_shapenodebuf_add(state->shapeBuffer,
vektor_shapenode_new(vektor_shape_new(
linePrimitive, style, 0)));
state->selectedShape = state->selectedShape =
&(state->shapeBuffer->shapes[state->shapeBuffer->count - 1]); &(state->shapeBuffer->nodes[state->shapeBuffer->count - 1]);
} else if (state->selectedShape->primitive.kind != VEKTOR_POLYLINE) { } else if (state->selectedShape->base->primitive.kind !=
VEKTOR_POLYLINE) {
// selecting a tool resets the selection, so this condition // selecting a tool resets the selection, so this condition
// should not happen // should not happen
g_warning("Invalid selected primitive; polyline expected"); g_warning("Invalid selected primitive; polyline expected");
state->selectedShape = NULL; vektor_appstate_deselect_shape(state);
goto begin_click_dispatch; // retry goto begin_click_dispatch; // retry
} }
vektor_polyline_add_point(state->selectedShape->primitive.polyline, vektor_polyline_add_point(state->selectedShape->base->primitive.polyline,
pos); pos);
state->selectedShape->base->bbox =
vektor_primitive_get_bbox(state->selectedShape->base->primitive);
vektor_shapes_update_bbox(state->shapeBuffer); // polyline's handle count is not fixed, so we have to add them manually
vektor_shape_add_handle(state->selectedShape->base, pos);
} else if (state->selectedTool == VektorPolygonTool) {
// create new polygon shape if none is selected
if (state->selectedShape == NULL) {
for (size_t i = 0; i < state->shapeBuffer->count; i++) { VektorPolygon* polygon = vektor_polygon_new();
g_print("<%f,%f>-<%f,%f>\n", state->shapeBuffer->shapes[i].bbox.min.x, state->shapeBuffer->shapes[i].bbox.min.y, state->shapeBuffer->shapes[i].bbox.max.x, state->shapeBuffer->shapes[i].bbox.max.y); VektorPrimitive polygonPrimitive =
(VektorPrimitive){.kind = VEKTOR_POLYGON, .polygon = polygon};
VektorStyle style = (VektorStyle){
.stroke_color = state->currentColor, .stroke_width = 0.01};
vektor_shapenodebuf_add(state->shapeBuffer,
vektor_shapenode_new(vektor_shape_new(
polygonPrimitive, style, 0)));
state->selectedShape =
&(state->shapeBuffer->nodes[state->shapeBuffer->count - 1]);
} else if (state->selectedShape->base->primitive.kind !=
VEKTOR_POLYGON) {
g_warning("Invalid selected primitive; polygon expected");
vektor_appstate_deselect_shape(state);
goto begin_click_dispatch; // retry
} }
vektor_polygon_add_point(state->selectedShape->base->primitive.polygon,
pos);
state->selectedShape->base->bbox =
vektor_primitive_get_bbox(state->selectedShape->base->primitive);
// polygon's handle count is not fixed, so we have to add them manually
vektor_shape_add_handle(state->selectedShape->base, pos);
} else if (state->selectedTool == VektorCircleTool) {
VektorCircle* circle = vektor_circle_new();
VektorPrimitive circlePrimitive =
(VektorPrimitive){.kind = VEKTOR_CIRCLE, .circle = *circle};
VektorStyle style = (VektorStyle){.stroke_color = state->currentColor,
.stroke_width = 0.01};
vektor_shapenodebuf_add(
state->shapeBuffer,
vektor_shapenode_new(vektor_shape_new(circlePrimitive, style, 0)));
state->selectedShape =
&(state->shapeBuffer->nodes[state->shapeBuffer->count - 1]);
vektor_circle_free(circle);
vektor_circle_set_center(&state->selectedShape->base->primitive.circle,
pos);
vektor_circle_set_radius(&state->selectedShape->base->primitive.circle,
0.1f);
state->selectedShape->base->bbox =
vektor_primitive_get_bbox(state->selectedShape->base->primitive);
vektor_circle_create_handles(
&state->selectedShape->base->primitive.circle,
&state->selectedShape->base->handles,
&state->selectedShape->base->handleCount);
} else if (state->selectedTool == VektorRectangleTool) {
VektorRectangle* rect = vektor_rectangle_new();
VektorPrimitive rectPrimitive =
(VektorPrimitive){.kind = VEKTOR_RECTANGLE, .rectangle = *rect};
VektorStyle style = (VektorStyle){.stroke_color = state->currentColor,
.stroke_width = 0.01};
vektor_shapenodebuf_add(
state->shapeBuffer,
vektor_shapenode_new(vektor_shape_new(rectPrimitive, style, 0)));
state->selectedShape =
&(state->shapeBuffer->nodes[state->shapeBuffer->count - 1]);
vektor_rectangle_free(rect);
vektor_rectangle_set_start(
&state->selectedShape->base->primitive.rectangle, pos);
vektor_rectangle_set_end(
&state->selectedShape->base->primitive.rectangle,
vec2_add(pos, (V2){0.1f, 0.1f}));
vektor_rectangle_create_handles(
&state->selectedShape->base->primitive.rectangle,
&state->selectedShape->base->handles,
&state->selectedShape->base->handleCount);
state->selectedShape->base->bbox =
vektor_primitive_get_bbox(state->selectedShape->base->primitive);
} else if (state->selectedTool == VektorSelectionTool) {
for (size_t i = 0; i < state->shapeBuffer->count; i++) {
VektorBBox bbox = vektor_primitive_get_bbox(
state->shapeBuffer->nodes[i].base->primitive);
// expand the bbox a little so its not painful to
// try to grab handles located on the border of said bbox
bbox = vektor_bbox_expand(bbox, 0.02);
if (vektor_bbox_isinside(bbox, pos)) {
state->selectedShape = &(state->shapeBuffer->nodes[i]);
return;
}
}
// was clicked outside any shapes - reset selection
vektor_appstate_deselect_shape(state);
}
}
void vektor_appstate_canvas_drag_begin(GtkGestureDrag* gesture, gdouble x,
gdouble y, gpointer user_data) {
GtkWidget* widget =
gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture));
VektorAppState* state = (VektorAppState*)user_data;
int widget_w = gtk_widget_get_width(widget);
int widget_h = gtk_widget_get_height(widget);
V2 position = (V2){(2 * (x / widget_w)) - 1, 1 - (2 * (y / widget_h))};
position = m33_transform(m33_inverse(state->renderInfo->canvasMat),
(V2){position.x, position.y});
if (state->selectedShape != NULL) {
VektorShapeNode* selectedShape = state->selectedShape;
// get selected shape's handles and check
// if we click any of them
for (size_t i = 0; i < selectedShape->base->handleCount; i++) {
VektorBBox bbox =
vektor_shape_get_handle_bbox(selectedShape->base->handles[i]);
if (vektor_bbox_isinside(bbox, position)) {
// clicked inside handle
state->heldHandleIndex = i;
vektor_canvas_geometry_changed(state->renderInfo);
break;
}
}
}
}
void vektor_appstate_canvas_drag_update(GtkGestureDrag* gesture, gdouble x,
gdouble y, gpointer user_data) {
// ---- setup normalized coordinates (boilerplate) ----
gdouble start_x, start_y;
gtk_gesture_drag_get_start_point(gesture, &start_x, &start_y);
GtkWidget* widget =
gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture));
VektorAppState* state = (VektorAppState*)user_data;
int widget_w = gtk_widget_get_width(widget);
int widget_h = gtk_widget_get_height(widget);
V2 position = (V2){(2 * ((x + start_x) / widget_w)) - 1,
1 - (2 * ((y + start_y) / widget_h))};
position = m33_transform(m33_inverse(state->renderInfo->canvasMat),
(V2){position.x, position.y});
// drag handle if selected
if (state->selectedShape != NULL && state->heldHandleIndex != -1) {
state->selectedShape->base->handles[state->heldHandleIndex] = position;
vektor_shape_handles_updated(state->selectedShape->base,
&state->heldHandleIndex);
vektor_canvas_geometry_changed(state->renderInfo);
}
}
void vektor_appstate_canvas_drag_end(GtkGestureDrag* gesture, gdouble x,
gdouble y, gpointer user_data) {
VektorAppState* state = (VektorAppState*)user_data;
// if we were dragging a handle
if (state->selectedShape != NULL && state->heldHandleIndex != -1) {
state->heldHandleIndex = -1; // ...then remove handle drag flag
vektor_canvas_geometry_changed(state->renderInfo);
} }
} }
@@ -138,22 +328,63 @@ void vektor_appstate_new(VektorWidgetState* wstate, VektorAppState* stateOut) {
data_linetool->tool = VektorLineTool; data_linetool->tool = VektorLineTool;
data_linetool->revealer = wstate->workspaceRevealerShapes; data_linetool->revealer = wstate->workspaceRevealerShapes;
button_tool_set_data* data_polygontool =
malloc(sizeof(button_tool_set_data));
data_polygontool->state = stateOut;
data_polygontool->tool = VektorPolygonTool;
data_polygontool->revealer = wstate->workspaceRevealerShapes;
button_tool_set_data* data_rectangletool =
malloc(sizeof(button_tool_set_data));
data_rectangletool->state = stateOut;
data_rectangletool->tool = VektorRectangleTool;
data_rectangletool->revealer = wstate->workspaceRevealerShapes;
button_tool_set_data* data_selecttool =
malloc(sizeof(button_tool_set_data));
data_selecttool->state = stateOut;
data_selecttool->tool = VektorSelectionTool;
button_tool_set_data* data_circletool =
malloc(sizeof(button_tool_set_data));
data_circletool->state = stateOut;
data_circletool->tool = VektorCircleTool;
data_circletool->revealer = wstate->workspaceRevealerShapes;
// populate appstate // populate appstate
stateOut->shapeBuffer = malloc(sizeof(VektorShapeBuffer)); stateOut->startupTime = g_get_monotonic_time();
*stateOut->shapeBuffer = (VektorShapeBuffer){0}; stateOut->shapeBuffer = malloc(sizeof(VektorShapeNodeBuffer));
*stateOut->shapeBuffer = (VektorShapeNodeBuffer){0};
stateOut->canvas = malloc(sizeof(VektorCanvas)); stateOut->canvas = malloc(sizeof(VektorCanvas));
stateOut->widgetState = wstate; stateOut->widgetState = wstate;
stateOut->currentColor = vektor_color_solid(0, 0, 0); stateOut->currentColor = vektor_color_solid(0, 0, 0);
stateOut->selectedShape = NULL; stateOut->selectedShape = NULL;
vektor_canvas_init(wstate, stateOut->canvas, stateOut->shapeBuffer); stateOut->heldHandleIndex = -1;
VektorCanvasRenderInfo* renderInfo = malloc(sizeof(VektorCanvasRenderInfo));
renderInfo->zoom = 1;
renderInfo->panX = 0;
renderInfo->panY = 0;
renderInfo->rotation = 0;
m33_to_gl4(m33_identity(), renderInfo->canvasTransform);
renderInfo->selectedShape = &(stateOut->selectedShape);
renderInfo->shapes = stateOut->shapeBuffer;
renderInfo->startupTime = stateOut->startupTime;
renderInfo->canvasMat = m33_identity();
vektor_canvas_init(wstate, stateOut->canvas, renderInfo);
stateOut->renderInfo = renderInfo;
// link all the buttons // link all the buttons
g_signal_connect(G_OBJECT(wstate->workspaceButtonLinetool), "clicked", g_signal_connect(G_OBJECT(wstate->workspaceButtonLineTool), "clicked",
G_CALLBACK(appstate_set_tool), data_linetool);
g_signal_connect(G_OBJECT(wstate->workspaceButtonRecttool), "clicked",
G_CALLBACK(appstate_set_tool), data_linetool);
g_signal_connect(G_OBJECT(wstate->workspaceButtonCircletool), "clicked",
G_CALLBACK(appstate_set_tool), data_linetool); G_CALLBACK(appstate_set_tool), data_linetool);
g_signal_connect(G_OBJECT(wstate->workspaceButtonRectTool), "clicked",
G_CALLBACK(appstate_set_tool), data_rectangletool);
g_signal_connect(G_OBJECT(wstate->workspaceButtonCircleTool), "clicked",
G_CALLBACK(appstate_set_tool), data_circletool);
g_signal_connect(G_OBJECT(wstate->workspaceButtonPolygonTool), "clicked",
G_CALLBACK(appstate_set_tool), data_polygontool);
g_signal_connect(G_OBJECT(wstate->workspaceButtonSelectionTool), "clicked",
G_CALLBACK(appstate_set_tool), data_selecttool);
// hook subtool revealers to their master buttons // hook subtool revealers to their master buttons
g_signal_connect(G_OBJECT(wstate->workspaceButtonMasterShapes), "clicked", g_signal_connect(G_OBJECT(wstate->workspaceButtonMasterShapes), "clicked",
@@ -166,12 +397,14 @@ void vektor_appstate_new(VektorWidgetState* wstate, VektorAppState* stateOut) {
// hook rgb entries change // hook rgb entries change
g_signal_connect(G_OBJECT(wstate->sidepanelEntryR), "activate", g_signal_connect(G_OBJECT(wstate->sidepanelEntryR), "activate",
G_CALLBACK(appstate_on_entry_update), stateOut->widgetState); G_CALLBACK(appstate_on_entry_update),
stateOut->widgetState);
g_signal_connect(G_OBJECT(wstate->sidepanelEntryG), "activate", g_signal_connect(G_OBJECT(wstate->sidepanelEntryG), "activate",
G_CALLBACK(appstate_on_entry_update), stateOut->widgetState); G_CALLBACK(appstate_on_entry_update),
stateOut->widgetState);
g_signal_connect(G_OBJECT(wstate->sidepanelEntryB), "activate", g_signal_connect(G_OBJECT(wstate->sidepanelEntryB), "activate",
G_CALLBACK(appstate_on_entry_update), stateOut->widgetState); G_CALLBACK(appstate_on_entry_update),
stateOut->widgetState);
// Add click gesture to canvas // Add click gesture to canvas
GtkGesture* canvasClickGesture = gtk_gesture_click_new(); GtkGesture* canvasClickGesture = gtk_gesture_click_new();
@@ -179,4 +412,21 @@ void vektor_appstate_new(VektorWidgetState* wstate, VektorAppState* stateOut) {
G_CALLBACK(canvas_onclick), stateOut); G_CALLBACK(canvas_onclick), stateOut);
gtk_widget_add_controller(GTK_WIDGET(wstate->workspaceCanvas), gtk_widget_add_controller(GTK_WIDGET(wstate->workspaceCanvas),
GTK_EVENT_CONTROLLER(canvasClickGesture)); GTK_EVENT_CONTROLLER(canvasClickGesture));
// Add drag gesture to canvas
GtkGesture* canvasDragGesture = gtk_gesture_drag_new();
g_signal_connect(G_OBJECT(canvasDragGesture), "drag-update",
G_CALLBACK(vektor_appstate_canvas_drag_update), stateOut);
g_signal_connect(G_OBJECT(canvasDragGesture), "drag-begin",
G_CALLBACK(vektor_appstate_canvas_drag_begin), stateOut);
g_signal_connect(G_OBJECT(canvasDragGesture), "drag-end",
G_CALLBACK(vektor_appstate_canvas_drag_end), stateOut);
gtk_widget_add_controller(GTK_WIDGET(wstate->workspaceCanvas),
GTK_EVENT_CONTROLLER(canvasDragGesture));
}
void vektor_appstate_deselect_shape(VektorAppState* state) {
state->heldHandleIndex = -1;
state->selectedShape = NULL;
} }

View File

@@ -4,26 +4,36 @@
#include "../core/primitives.h" #include "../core/primitives.h"
#include "../ui/uicontroller.h" #include "../ui/uicontroller.h"
#include "../ui/vektorcanvas.h" #include "../ui/vektorcanvas.h"
#include "src/core/raster.h" #include "src/core/modifier.h"
typedef enum VektorAppTool { VektorLineTool } VektorAppTool; typedef enum VektorAppTool {
VektorSelectionTool,
VektorLineTool,
VektorPolygonTool,
VektorRectangleTool,
VektorCircleTool
} VektorAppTool;
typedef struct VektorAppState { typedef struct VektorAppState {
gint64 startupTime;
VektorWidgetState* widgetState; VektorWidgetState* widgetState;
VektorAppTool selectedTool; VektorAppTool selectedTool;
VektorShape* selectedShape; VektorShapeNode* selectedShape;
int heldHandleIndex;
VektorColor currentColor; VektorColor currentColor;
// Logic space // Logic space
VektorShapeBuffer* shapeBuffer; VektorShapeNodeBuffer* shapeBuffer;
// View space // View space
VektorCanvas* canvas; VektorCanvas* canvas;
VektorCanvasRenderInfo* renderInfo;
} VektorAppState; } VektorAppState;
void vektor_appstate_new(VektorWidgetState* wstate, VektorAppState* stateOut); void vektor_appstate_new(VektorWidgetState* wstate, VektorAppState* stateOut);
void vektor_appstate_canvas_click(VektorAppState* state, double x, double y); void vektor_appstate_canvas_click(VektorAppState* state, double x, double y);
void vektor_appstate_deselect_shape(VektorAppState* state);
#endif #endif

View File

@@ -87,3 +87,25 @@ V2 m33_transform(const M33 mat, const V2 v) {
return (V2){mat.m[0][0] * v.x + mat.m[0][1] * v.y + mat.m[0][2], return (V2){mat.m[0][0] * v.x + mat.m[0][1] * v.y + mat.m[0][2],
mat.m[1][0] * v.x + mat.m[1][1] * v.y + mat.m[1][2]}; mat.m[1][0] * v.x + mat.m[1][1] * v.y + mat.m[1][2]};
} }
void m33_to_gl4(const M33 m, float out[16]) {
out[0] = m.m[0][0];
out[1] = m.m[1][0];
out[2] = 0.0f;
out[3] = 0.0f;
out[4] = m.m[0][1];
out[5] = m.m[1][1];
out[6] = 0.0f;
out[7] = 0.0f;
out[8] = 0.0f;
out[9] = 0.0f;
out[10] = 1.0f;
out[11] = 0.0f;
out[12] = m.m[0][2];
out[13] = m.m[1][2];
out[14] = 0.0f;
out[15] = 1.0f;
}

View File

@@ -22,4 +22,6 @@ M33 m33_inverse(const M33 m);
V2 m33_transform(const M33 mat, const V2 v); V2 m33_transform(const M33 mat, const V2 v);
void m33_to_gl4(const M33 m, float out[16]);
#endif // MATRIX_H_ #endif // MATRIX_H_

83
src/core/modifier.c Normal file
View File

@@ -0,0 +1,83 @@
#include "modifier.h"
#include <glib.h>
VektorShapeNode vektor_shapenode_new(VektorShape* shape) {
VektorShapeNode node = (VektorShapeNode){
.base = shape,
.modifier_count = 0,
.evaluated = shape,
.base_dirty = true
};
return node;
}
void vektor_shapenode_free(VektorShapeNode* shapeNode) {
if(shapeNode->base == shapeNode->evaluated) {
free(shapeNode->base); // avoid double free()
} else {
free(shapeNode->base);
free(shapeNode->evaluated);
}
free(shapeNode->modifiers);
}
VektorShape* vektor_shapenode_get_evaluated(VektorShapeNode* shapeNode) {
return shapeNode->evaluated;
}
VektorShape vektor_modifier_apply(VektorModifier* mod, VektorShape* input) {
mod->cachedEvaluatedShape = mod->apply(mod, input);
return mod->cachedEvaluatedShape;
}
// lots of copies by value here, could be problematic
void vektor_shapenode_update(VektorShapeNode* shapeNode) {
// if the base is dirty, apply EVERY modifier
if(shapeNode->base_dirty) {
VektorShape* evaluated = shapeNode->base;
for(size_t i = 0; i < shapeNode->modifier_count; i++) {
*evaluated = vektor_modifier_apply(&shapeNode->modifiers[i], evaluated);
shapeNode->modifiers[i].dirty = false;
}
shapeNode->evaluated = evaluated;
shapeNode->base_dirty = false;
return;
}
// if the base is not dirty, start applying modifiers upstream
// starting from the first dirty
bool encountered_dirty = false;
for(size_t i = 0; i < shapeNode->modifier_count; i++) {
if(shapeNode->modifiers[i].dirty) { encountered_dirty = true; }
if(encountered_dirty) {
if(i == 0) {
vektor_modifier_apply(&shapeNode->modifiers[i], shapeNode->base);
} else {
vektor_modifier_apply(&shapeNode->modifiers[i], &shapeNode->modifiers[i - 1].cachedEvaluatedShape);
}
shapeNode->modifiers[i].dirty = false;
}
}
if (encountered_dirty) {
*shapeNode->evaluated = shapeNode->modifiers[shapeNode->modifier_count - 1].cachedEvaluatedShape;
}
}
void vektor_shapenodebuf_add(VektorShapeNodeBuffer* buffer,
VektorShapeNode node) {
if (buffer->count >= buffer->capacity) {
buffer->capacity = buffer->capacity ? buffer->capacity * 2 : 4;
buffer->nodes =
realloc(buffer->nodes, sizeof(VektorShapeNode) * buffer->capacity);
}
buffer->nodes[buffer->count++] = node;
if (buffer->count <= buffer->capacity / 4) {
buffer->capacity /= 2;
buffer->nodes =
realloc(buffer->nodes, sizeof(VektorShapeNode) * buffer->capacity);
}
}

53
src/core/modifier.h Normal file
View File

@@ -0,0 +1,53 @@
#ifndef VKTR_MODIFIER_H
#define VKTR_MODIFIER_H
#include "src/core/primitives.h"
typedef enum {
VEKTOR_MODIFIER_IDENTITY,
VEKTOR_MODIFIER_BEVEL
} VektorModifierType;
typedef struct VektorModifier {
VektorModifierType type;
bool enabled;
bool dirty;
void* parameters;
VektorShape (*apply)(struct VektorModifier* mod, VektorShape* input);
VektorShape cachedEvaluatedShape;
} VektorModifier;
typedef struct VektorShapeNode {
VektorShape* base;
VektorShape* evaluated;
VektorModifier* modifiers;
size_t modifier_count;
bool base_dirty;
} VektorShapeNode;
typedef struct VektorShapeNodeBuffer {
VektorShapeNode* nodes;
size_t count;
size_t capacity;
} VektorShapeNodeBuffer;
VektorShape vektor_modifier_apply(VektorModifier* mod, VektorShape* input);
VektorShapeNode vektor_shapenode_new(VektorShape* shape);
void vektor_shapenode_free(VektorShapeNode* shapeNode);
VektorShape* vektor_shapenode_get_evaluated(VektorShapeNode* shapeNode);
void vektor_shapenode_update(VektorShapeNode* shapeNode);
void vektor_shapenode_modifier_add(VektorShapeNode* shapeNode,
VektorModifier* mod);
void vektor_shapenode_modifier_remove(VektorShapeNode* shapeNode,
VektorModifier* mod);
void vektor_shapenode_free(VektorShapeNode* shapeNode);
void vektor_shapenodebuf_add(VektorShapeNodeBuffer* buffer,
VektorShapeNode node);
#endif

View File

@@ -0,0 +1,16 @@
#include "../modifier.h"
static VektorShape vektor_m_identity_apply(VektorModifier* m, VektorShape* input) {
return *input;
}
VektorModifier vektor_m_identity_new() {
return (VektorModifier) {
.type = VEKTOR_MODIFIER_IDENTITY,
.enabled = true,
.dirty = true,
.parameters = NULL,
.apply = vektor_m_identity_apply
};
}

View File

@@ -1,8 +1,13 @@
#include "primitives.h" #include "primitives.h"
#include "glib.h"
#include "src/core/matrix.h"
#include "src/core/vector.h"
#include <assert.h> #include <assert.h>
#include <math.h> #include <math.h>
#include <stddef.h> #include <stddef.h>
// ------ PER-PRIMITIVE METHODS ------
VektorPolyline* vektor_polyline_new(void) { VektorPolyline* vektor_polyline_new(void) {
VektorPolyline* pl = malloc(sizeof(VektorPolyline)); VektorPolyline* pl = malloc(sizeof(VektorPolyline));
pl->count = 0; pl->count = 0;
@@ -17,6 +22,11 @@ void vektor_polyline_add_point(VektorPolyline* pl, V2 point) {
pl->points = realloc(pl->points, sizeof(V2) * pl->capacity); pl->points = realloc(pl->points, sizeof(V2) * pl->capacity);
} }
pl->points[pl->count++] = point; pl->points[pl->count++] = point;
if (pl->count <= pl->capacity / 4) {
pl->capacity /= 2;
pl->points = realloc(pl->points, sizeof(V2) * pl->capacity);
}
} }
void vektor_polyline_free(VektorPolyline* pl) { void vektor_polyline_free(VektorPolyline* pl) {
@@ -40,6 +50,11 @@ void vektor_polygon_add_point(VektorPolygon* pg, V2 point) {
pg->points = realloc(pg->points, sizeof(V2) * pg->capacity); pg->points = realloc(pg->points, sizeof(V2) * pg->capacity);
} }
pg->points[pg->count++] = point; pg->points[pg->count++] = point;
if (pg->count <= pg->capacity / 4) {
pg->capacity /= 2;
pg->points = realloc(pg->points, sizeof(V2) * pg->capacity);
}
} }
void vektor_polygon_free(VektorPolygon* pg) { void vektor_polygon_free(VektorPolygon* pg) {
@@ -49,6 +64,418 @@ void vektor_polygon_free(VektorPolygon* pg) {
free(pg); free(pg);
} }
VektorCircle* vektor_circle_new(void) {
VektorCircle* circ = malloc(sizeof(VektorCircle));
circ->center = (V2){0, 0};
circ->radius = 0;
return circ;
}
void vektor_circle_set_center(VektorCircle* circle, V2 point) {
circle->center = point;
}
void vektor_circle_set_radius(VektorCircle* circle, double radius) {
circle->radius = radius;
}
void vektor_circle_free(VektorCircle* circle) { free(circle); }
VektorRectangle* vektor_rectangle_new(void) {
VektorRectangle* rct = malloc(sizeof(VektorRectangle));
rct->start = (V2){.x = 0, .y = 0};
rct->end = (V2){.x = 0, .y = 0};
return rct;
}
void vektor_rectangle_set_end(VektorRectangle* rct, V2 point) {
rct->end = point;
}
void vektor_rectangle_set_start(VektorRectangle* rct, V2 point) {
rct->start = point;
}
void vektor_rectangle_free(VektorRectangle* rct) { free(rct); }
VektorBBox vektor_polyline_get_bbox(VektorPrimitive prim) {
V2 first = prim.polyline->points[0];
float min_x = first.x;
float max_x = first.x;
float min_y = first.y;
float max_y = first.y;
for (size_t i = 1; i < prim.polygon->count; i++) {
V2 p = prim.polygon->points[i];
min_x = fminf(min_x, p.x);
min_y = fminf(min_y, p.y);
max_x = fmaxf(max_x, p.x);
max_y = fmaxf(max_y, p.y);
}
return (VektorBBox){(V2){min_x, min_y}, (V2){max_x, max_y}};
}
VektorBBox vektor_polygon_get_bbox(VektorPrimitive prim) {
V2 first = prim.polygon->points[0];
float min_x = first.x;
float max_x = first.x;
float min_y = first.y;
float max_y = first.y;
for (size_t i = 1; i < prim.polygon->count; i++) {
V2 p = prim.polygon->points[i];
min_x = fminf(min_x, p.x);
min_y = fminf(min_y, p.y);
max_x = fmaxf(max_x, p.x);
max_y = fmaxf(max_y, p.y);
}
return (VektorBBox){(V2){min_x, min_y}, (V2){max_x, max_y}};
}
VektorBBox vektor_rectangle_get_bbox(VektorPrimitive prim) {
return (VektorBBox){prim.rectangle.start, prim.rectangle.end};
}
VektorBBox vektor_circle_get_bbox(VektorPrimitive prim) {
return (VektorBBox){
vec2_sub(prim.circle.center, vec2_fromfloat(prim.circle.radius)),
vec2_add(prim.circle.center, vec2_fromfloat(prim.circle.radius))};
}
VektorBBox vektor_primitive_get_bbox(VektorPrimitive prim) {
switch (prim.kind) {
case VEKTOR_POLYLINE:
return vektor_polyline_get_bbox(prim);
break;
case VEKTOR_POLYGON:
return vektor_polygon_get_bbox(prim);
break;
case VEKTOR_RECTANGLE:
return vektor_rectangle_get_bbox(prim);
break;
case VEKTOR_CIRCLE:
return vektor_circle_get_bbox(prim);
break;
default:
// TODO: fill in all primitives
break;
}
}
// ------ PRIMITIVE HANDLES GENERATION ------
/* [n]: polyline vertices */
void vektor_polyline_create_handles(VektorPolyline* polyline, V2** handleArr,
size_t* count) {
*count = 0;
*handleArr = NULL;
}
/* [n]: polygon vertices */
void vektor_polygon_create_handles(VektorPolygon* polygon, V2** handleArr,
size_t* count) {
*count = 0;
*handleArr = NULL;
}
/* [0]: center; [1]: radius */
void vektor_circle_create_handles(VektorCircle* circle, V2** handleArr,
size_t* count) {
*count = 2;
*handleArr = (V2*)malloc(sizeof(V2) * (*count));
(*handleArr)[0] = circle->center;
(*handleArr)[1] = (V2){circle->radius + circle->center.x, circle->center.y};
}
/* [0]: center; [1-4]: corners (l2r, t2b); */
void vektor_rectangle_create_handles(VektorRectangle* rectangle, V2** handleArr,
size_t* count) {
*count = 5;
free(*handleArr);
*handleArr = (V2*)malloc(sizeof(V2) * (*count));
V2 halfdist = vec2_scale(vec2_sub(rectangle->end, rectangle->start), 0.5f);
V2 center = vec2_add(rectangle->start, halfdist);
(*handleArr)[0] = center;
(*handleArr)[1] = vec2_add(center, vec2_mul(halfdist, (V2){-1.0f, 1.0f}));
(*handleArr)[2] = vec2_add(center, halfdist);
(*handleArr)[3] = vec2_add(center, vec2_mul(halfdist, (V2){-1.0f, -1.0f}));
(*handleArr)[4] = vec2_add(center, vec2_mul(halfdist, (V2){1.0f, -1.0f}));
}
void vektor_shape_create_handles(VektorShape* shape) {
switch (shape->primitive.kind) {
case VEKTOR_POLYLINE:
vektor_polyline_create_handles(shape->primitive.polyline,
&shape->handles, &shape->handleCount);
break;
case VEKTOR_POLYGON:
vektor_polygon_create_handles(shape->primitive.polygon, &shape->handles,
&shape->handleCount);
break;
case VEKTOR_CIRCLE:
vektor_circle_create_handles(&shape->primitive.circle, &shape->handles,
&shape->handleCount);
break;
case VEKTOR_RECTANGLE:
vektor_rectangle_create_handles(&shape->primitive.rectangle,
&shape->handles, &shape->handleCount);
break;
}
}
// ------ AUXILIARY HANDLE METHODS ------
void vektor_shape_add_handle(VektorShape* shape, V2 handle) {
// could be optimised with capacity property
// but this function is only called when adding new
// points to polyline and polygon, so it should
// not be that much of an overhead
shape->handles =
realloc(shape->handles, sizeof(V2) * shape->handleCount + 1);
shape->handles[shape->handleCount++] = handle;
}
VektorBBox vektor_shape_get_handle_bbox(V2 handle) {
return vektor_bbox_fromcenter(handle, 0.02);
}
// ------ PRIMITIVE HANDLES UPDATING ------
void vektor_polyline_handles_updated(VektorPolyline* polyline, V2** handles,
size_t* count, int* heldHandleIndex) {
if (*count != polyline->count) {
g_warning("handle count & point count mismatch in polyline");
return;
}
for (size_t i = 0; i < *count; i++) {
polyline->points[i] = (*handles)[i];
}
}
void vektor_polygon_handles_updated(VektorPolygon* polygon, V2** handles,
size_t* count, int* heldHandleIndex) {
if (*count != polygon->count) {
g_warning("handle count & point count mismatch in polygon");
return;
}
for (size_t i = 0; i < *count; i++) {
polygon->points[i] = (*handles)[i];
}
}
void vektor_circle_handles_updated(VektorCircle* circle, V2** handles,
size_t* count, int* heldHandleIndex) {
if (*count != 2) {
g_warning("unexpected circle handle count (%zu)", *count);
return;
}
if (*heldHandleIndex == 0) { // dragging center
V2 translation = vec2_sub((*handles)[0], circle->center);
circle->center = (*handles)[0];
(*handles)[1] = vec2_add(translation, (*handles)[1]);
} else {
circle->radius = vec2_length(vec2_sub((*handles)[0], (*handles)[1]));
}
}
// this shi is big because it dynamically handles handle remapping when
// rectangle enters an invalid state (end < start)
// creating the illusion of an invertable rect, while also keeping it
// valid at all times
void vektor_rectangle_handles_updated(VektorRectangle* rectangle, V2** handles,
size_t* count, int* heldHandleIndex) {
if (*count != 5) {
g_warning("unexpected rectangle handle count (%zu)", *count);
return;
}
V2 start = rectangle->start;
V2 end = rectangle->end;
switch (*heldHandleIndex) {
case 0: // center drag
{
V2 oldCenter = vec2_scale(vec2_add(start, end), 0.5f);
V2 newCenter = (*handles)[0];
V2 translation = vec2_sub(newCenter, oldCenter);
start = vec2_add(start, translation);
end = vec2_add(end, translation);
break;
}
case 1: // top-left
start.x = (*handles)[1].x;
end.y = (*handles)[1].y;
break;
case 2: // top-right
end.x = (*handles)[2].x;
end.y = (*handles)[2].y;
break;
case 3: // bottom-left
start.x = (*handles)[3].x;
start.y = (*handles)[3].y;
break;
case 4: // bottom-right
end.x = (*handles)[4].x;
start.y = (*handles)[4].y;
break;
default:
return;
}
// Store raw values before normalization
float raw_min_x = start.x;
float raw_max_x = end.x;
float raw_min_y = start.y;
float raw_max_y = end.y;
// Normalize rectangle
float min_x = fminf(start.x, end.x);
float max_x = fmaxf(start.x, end.x);
float min_y = fminf(start.y, end.y);
float max_y = fmaxf(start.y, end.y);
bool flipX = raw_min_x > raw_max_x;
bool flipY = raw_min_y > raw_max_y;
// Remap handle if we crossed axes
if (*heldHandleIndex != 0) {
if (flipX) {
switch (*heldHandleIndex) {
case 1:
*heldHandleIndex = 2;
break;
case 2:
*heldHandleIndex = 1;
break;
case 3:
*heldHandleIndex = 4;
break;
case 4:
*heldHandleIndex = 3;
break;
}
}
if (flipY) {
switch (*heldHandleIndex) {
case 1:
*heldHandleIndex = 3;
break;
case 3:
*heldHandleIndex = 1;
break;
case 2:
*heldHandleIndex = 4;
break;
case 4:
*heldHandleIndex = 2;
break;
}
}
}
VektorRectangle properRect = {.start = {min_x, min_y},
.end = {max_x, max_y}};
vektor_rectangle_set_start(rectangle, properRect.start);
vektor_rectangle_set_end(rectangle, properRect.end);
// regenerate handle positions
vektor_rectangle_create_handles(&properRect, handles, count);
}
void vektor_shape_handles_updated(VektorShape* shape, int* heldHandleIndex) {
switch (shape->primitive.kind) {
case VEKTOR_POLYLINE:
vektor_polyline_handles_updated(shape->primitive.polyline,
&shape->handles, &shape->handleCount,
heldHandleIndex);
break;
case VEKTOR_POLYGON:
vektor_polygon_handles_updated(shape->primitive.polygon,
&shape->handles, &shape->handleCount,
heldHandleIndex);
break;
case VEKTOR_CIRCLE:
vektor_circle_handles_updated(&shape->primitive.circle, &shape->handles,
&shape->handleCount, heldHandleIndex);
break;
case VEKTOR_RECTANGLE:
vektor_rectangle_handles_updated(&shape->primitive.rectangle,
&shape->handles, &shape->handleCount,
heldHandleIndex);
break;
}
}
// ------ BBOX METHODS ------
bool vektor_bbox_isinside(VektorBBox bbox, V2 point) {
return point.x >= bbox.min.x && point.y >= bbox.min.y &&
point.x <= bbox.max.x && point.y <= bbox.max.y;
}
VektorBBox vektor_bbox_fromcenter(V2 center, float dist) {
V2 v2dist = vec2_fromfloat(dist);
V2 min = vec2_sub(center, v2dist);
V2 max = vec2_add(center, v2dist);
return (VektorBBox){min, max};
}
VektorBBox vektor_bbox_expand(VektorBBox bbox, float val) {
return (VektorBBox){vec2_sub(bbox.min, vec2_fromfloat(val)),
vec2_add(bbox.max, vec2_fromfloat(val))};
}
// ------ SHAPE METHODS ------
VektorShape* vektor_shape_new(VektorPrimitive prim, VektorStyle style,
int z_index) {
VektorShape* shape = malloc(sizeof(VektorShape));
*shape = (VektorShape){.primitive = prim,
.style = style,
.transform = m33_identity(),
.z_index = z_index,
.bbox = vektor_primitive_get_bbox(prim)};
/*
create_handles() allocates new buffer for handles,
and even if the local shape variable goes out of scope and deallocates,
the passed value's pointer to an array of handles remains valid in the
passed copy.
*/
vektor_shape_create_handles(shape);
return shape;
}
void vektor_shapes_update_bbox(VektorShapeBuffer* buffer) {
for (size_t i = 0; i < buffer->count; i++) {
buffer->shapes[i].bbox =
vektor_primitive_get_bbox(buffer->shapes[i].primitive);
}
}
void vektor_shapebuffer_add_shape(VektorShapeBuffer* buffer, void vektor_shapebuffer_add_shape(VektorShapeBuffer* buffer,
VektorShape shape) { VektorShape shape) {
if (buffer->count >= buffer->capacity) { if (buffer->count >= buffer->capacity) {
@@ -57,57 +484,10 @@ void vektor_shapebuffer_add_shape(VektorShapeBuffer* buffer,
realloc(buffer->shapes, sizeof(VektorShape) * buffer->capacity); realloc(buffer->shapes, sizeof(VektorShape) * buffer->capacity);
} }
buffer->shapes[buffer->count++] = shape; buffer->shapes[buffer->count++] = shape;
}
VektorBBox polyline_mk_bbox(VektorPrimitive prim) { if (buffer->count <= buffer->capacity / 4) {
float min_x, max_x, min_y, max_y; buffer->capacity /= 2;
for (size_t i = 0; i < prim.polyline->count; i++) { buffer->shapes =
V2 p = prim.polyline->points[i]; realloc(buffer->shapes, sizeof(VektorShape) * buffer->capacity);
min_x = fminf(min_x, p.x);
min_y = fminf(min_y, p.y);
max_x = fminf(max_x, p.x);
max_y = fminf(max_y, p.y);
}
return (VektorBBox){(V2){min_x, min_y}, (V2){max_x, max_y}};
}
VektorBBox polygon_mk_bbox(VektorPrimitive prim) {
float min_x, max_x, min_y, max_y;
for (size_t i = 0; i < prim.polygon->count; i++) {
V2 p = prim.polygon->points[i];
min_x = fminf(min_x, p.x);
min_y = fminf(min_y, p.y);
max_x = fminf(max_x, p.x);
max_y = fminf(max_y, p.y);
}
return (VektorBBox){(V2){min_x, min_y}, (V2){max_x, max_y}};
}
VektorBBox vektor_mk_bbox(VektorPrimitive prim) {
switch (prim.kind) {
case VEKTOR_POLYLINE:
return polyline_mk_bbox(prim);
break;
case VEKTOR_POLYGON:
return polygon_mk_bbox(prim);
break;
default:
// TODO: fill in all primitives
break;
}
}
VektorShape vektor_shape_new(VektorPrimitive prim, VektorStyle style,
int z_index) {
return (VektorShape){.primitive = prim, .style = style, .z_index = z_index, .bbox=vektor_mk_bbox(prim)};
}
void vektor_shapes_update_bbox(VektorShapeBuffer* buffer) {
for (size_t i = 0; i < buffer->count; i++) {
buffer->shapes[i].bbox = vektor_mk_bbox(buffer->shapes[i].primitive);
} }
} }

View File

@@ -1,6 +1,7 @@
#ifndef PRIMITIVES_H_ #ifndef PRIMITIVES_H_
#define PRIMITIVES_H_ #define PRIMITIVES_H_
#include "src/core/matrix.h"
#include "src/util/color.h" #include "src/util/color.h"
#include "stddef.h" #include "stddef.h"
#include "stdlib.h" #include "stdlib.h"
@@ -23,10 +24,16 @@ typedef struct {
double radius; double radius;
} VektorCircle; } VektorCircle;
typedef struct {
V2 start;
V2 end;
} VektorRectangle;
typedef enum { typedef enum {
VEKTOR_POLYLINE, VEKTOR_POLYLINE,
VEKTOR_POLYGON, VEKTOR_POLYGON,
VEKTOR_CIRCLE VEKTOR_CIRCLE,
VEKTOR_RECTANGLE
} VektorPrimitiveKind; } VektorPrimitiveKind;
typedef struct { typedef struct {
@@ -35,17 +42,10 @@ typedef struct {
VektorPolyline* polyline; VektorPolyline* polyline;
VektorPolygon* polygon; VektorPolygon* polygon;
VektorCircle circle; VektorCircle circle;
VektorRectangle rectangle;
}; };
} VektorPrimitive; } VektorPrimitive;
VektorPolyline* vektor_polyline_new(void);
void vektor_polyline_add_point(VektorPolyline* pl, V2 point);
void vektor_polyline_free(VektorPolyline* pl);
VektorPolygon* vektor_polygon_new(void);
void vektor_polygon_add_point(VektorPolygon* pl, V2 point);
void vektor_polygon_free(VektorPolygon* pl);
typedef struct { typedef struct {
VektorColor stroke_color; VektorColor stroke_color;
float stroke_width; float stroke_width;
@@ -59,18 +59,70 @@ typedef struct {
typedef struct { typedef struct {
VektorStyle style; VektorStyle style;
int z_index; int z_index;
M33 transform;
VektorBBox bbox; VektorBBox bbox;
VektorPrimitive primitive; VektorPrimitive primitive;
V2* handles;
size_t handleCount;
} VektorShape; } VektorShape;
VektorBBox polyline_mk_bbox(VektorPrimitive prim); VektorPolyline* vektor_polyline_new(void);
VektorBBox polygon_mk_bbox(VektorPrimitive prim); void vektor_polyline_add_point(VektorPolyline* pl, V2 point);
void vektor_polyline_free(VektorPolyline* pl);
VektorBBox vektor_mk_bbox(VektorPrimitive prim); VektorPolygon* vektor_polygon_new(void);
void vektor_polygon_add_point(VektorPolygon* pl, V2 point);
void vektor_polygon_free(VektorPolygon* pl);
VektorShape vektor_shape_new(VektorPrimitive prim, VektorStyle style, VektorCircle* vektor_circle_new(void);
void vektor_circle_set_center(VektorCircle* circle, V2 point);
void vektor_circle_set_radius(VektorCircle* circle, double radius);
void vektor_circle_free(VektorCircle* circle);
VektorRectangle* vektor_rectangle_new(void);
void vektor_rectangle_set_end(VektorRectangle* rct, V2 point);
void vektor_rectangle_set_start(VektorRectangle* rct, V2 point);
void vektor_rectangle_free(VektorRectangle* rct);
VektorShape* vektor_shape_new(VektorPrimitive prim, VektorStyle style,
int z_index); int z_index);
VektorBBox vektor_polyline_get_bbox(VektorPrimitive prim);
VektorBBox vektor_polygon_get_bbox(VektorPrimitive prim);
VektorBBox vektor_circle_get_bbox(VektorPrimitive prim);
VektorBBox vektor_rectangle_get_bbox(VektorPrimitive prim);
VektorBBox vektor_primitive_get_bbox(VektorPrimitive prim);
bool vektor_bbox_isinside(VektorBBox bbox, V2 point);
VektorBBox vektor_bbox_fromcenter(V2 center, float dist);
VektorBBox vektor_bbox_expand(VektorBBox bbox, float val);
// shape handles
void vektor_polyline_create_handles(VektorPolyline* polyline, V2** handleArr,
size_t* count);
void vektor_polygon_create_handles(VektorPolygon* polygon, V2** handleArr,
size_t* count);
void vektor_circle_create_handles(VektorCircle* circle, V2** handleArr,
size_t* count);
void vektor_rectangle_create_handles(VektorRectangle* rectangle, V2** handleArr,
size_t* count);
void vektor_shape_create_handles(VektorShape* shape);
void vektor_shape_add_handle(VektorShape* shape, V2 handle);
VektorBBox vektor_shape_get_handle_bbox(V2 handle);
/* reconstructs the shape based on handles alone */
void vektor_polyline_handles_updated(VektorPolyline* polyline, V2** handles,
size_t* count, int* heldHandleIndex);
void vektor_polygon_handles_updated(VektorPolygon* polygon, V2** handles,
size_t* count, int* heldHandleIndex);
void vektor_circle_handles_updated(VektorCircle* circle, V2** handles,
size_t* count, int* heldHandleIndex);
void vektor_rectangle_handles_updated(VektorRectangle* rectangle, V2** handles,
size_t* count, int* heldHandleIndex);
void vektor_shape_handles_updated(VektorShape* shape, int* heldHandleIndex);
typedef struct { typedef struct {
VektorShape* shapes; VektorShape* shapes;
size_t count; size_t count;

View File

@@ -1,9 +1,16 @@
#include "raster.h" #include "raster.h"
#include "epoxy/gl.h" #include "epoxy/gl.h"
#include "glib.h"
#include "primitives.h" #include "primitives.h"
#include "src/core/matrix.h"
#include "src/core/modifier.h"
#include "src/core/vector.h"
#include "stddef.h" #include "stddef.h"
#include <math.h>
#include <stddef.h> #include <stddef.h>
#define PI 3.14159265358979323846
void vektor_edgebuffer_add_edge(EdgeBuffer* buffer, Edge edge) { void vektor_edgebuffer_add_edge(EdgeBuffer* buffer, Edge edge) {
if (buffer->count >= buffer->capacity) { if (buffer->count >= buffer->capacity) {
buffer->capacity = buffer->capacity ? buffer->capacity * 2 : 4; buffer->capacity = buffer->capacity ? buffer->capacity * 2 : 4;
@@ -12,39 +19,88 @@ void vektor_edgebuffer_add_edge(EdgeBuffer* buffer, Edge edge) {
buffer->edges[buffer->count++] = edge; buffer->edges[buffer->count++] = edge;
} }
void vektor_polyline_flatten(EdgeBuffer* buffer, VektorPolyline* line, void vektor_polyline_tessellate(EdgeBuffer* buffer, VektorPolyline* line,
size_t j) { size_t j, double scale) {
for (size_t i = 0; i + 1 < line->count; i++) { for (size_t i = 0; i + 1 < line->count; i++) {
vektor_edgebuffer_add_edge( vektor_edgebuffer_add_edge(
buffer, (Edge){line->points[i], line->points[i + 1], 0, j}); buffer, (Edge){line->points[i], line->points[i + 1], 0, j});
} }
} }
void vektor_polygon_flatten(EdgeBuffer* buffer, VektorPolygon* pg, size_t j) { void vektor_polygon_tessellate(EdgeBuffer* buffer, VektorPolygon* polygon,
size_t n = pg->count; size_t j, double scale) {
if (n < 3) for (size_t i = 0; i + 1 < polygon->count; i++) {
vektor_edgebuffer_add_edge(
buffer, (Edge){polygon->points[i], polygon->points[i + 1], 0, j});
}
vektor_edgebuffer_add_edge(
buffer,
(Edge){polygon->points[polygon->count - 1], polygon->points[0], 0, j});
}
void vektor_circle_tessellate(EdgeBuffer* buffer, VektorCircle* circle,
size_t j, double scale) {
double err = 0.0025;
size_t res = PI * sqrt((scale * circle->radius) / (2 * err));
for (size_t i = 0; i < res; i++) {
double theta1 = (2 * PI * i) / res;
double theta2 = (2 * PI * (i + 1)) / res;
V2 p1 = (V2){circle->center.x + circle->radius * cos(theta1),
circle->center.y + circle->radius * sin(theta1)};
V2 p2 = (V2){circle->center.x + circle->radius * cos(theta2),
circle->center.y + circle->radius * sin(theta2)};
vektor_edgebuffer_add_edge(buffer, (Edge){p1, p2, 0, j});
}
}
void vektor_rectangle_tessellate(EdgeBuffer* buffer, VektorRectangle* rct,
size_t j, double scale) {
if (vec2_equals(rct->end, rct->start)) {
return; return;
for (size_t i = 0; i < n; i++) {
V2 p1 = pg->points[i];
V2 p2 = pg->points[(i + 1) % n];
int winding = (p1.y < p2.y) ? +1 : -1;
vektor_edgebuffer_add_edge(buffer, (Edge){p1, p2, winding, j});
}
} }
void vektor_rasterize(VertexBuffer* vb, VektorShapeBuffer* shapes) { Edge top = (Edge){rct->start, (V2){rct->end.x, rct->start.y}, 0, j};
Edge right = (Edge){(V2){rct->end.x, rct->start.y}, rct->end, 0, j};
Edge bottom = (Edge){(V2){rct->start.x, rct->end.y}, rct->end, 0, j};
Edge left = (Edge){rct->start, (V2){rct->start.x, rct->end.y}, 0, j};
vektor_edgebuffer_add_edge(buffer, top);
vektor_edgebuffer_add_edge(buffer, right);
vektor_edgebuffer_add_edge(buffer, bottom);
vektor_edgebuffer_add_edge(buffer, left);
}
void vektor_vb_rasterize(VertexBuffer* vb, VektorShapeNodeBuffer* nodebuf,
double scale) {
for (size_t i = 0; i < nodebuf->count; i++) {
EdgeBuffer edges = {0}; EdgeBuffer edges = {0};
for (size_t i = 0; i < shapes->count; i++) {
VektorPrimitive* p = &shapes->shapes[i].primitive; vektor_shapenode_update(&nodebuf->nodes[i]);
VektorShape* currentShape = vektor_shapenode_get_evaluated(&nodebuf->nodes[i]);
VektorPrimitive* p = &currentShape->primitive;
VektorStyle style = currentShape->style;
M33 transform = currentShape->transform;
switch (p->kind) { switch (p->kind) {
case VEKTOR_POLYLINE: case VEKTOR_POLYLINE:
vektor_polyline_flatten(&edges, p->polyline, i); vektor_polyline_tessellate(&edges, p->polyline, i, scale);
vektor_edges_to_triangles(vb, &edges, &transform, style, FALSE);
break; break;
case VEKTOR_POLYGON: case VEKTOR_POLYGON:
vektor_polygon_flatten(&edges, p->polygon, i); vektor_polygon_tessellate(&edges, p->polygon, i, scale);
vektor_edges_to_triangles(vb, &edges, &transform, style, TRUE);
break;
case VEKTOR_CIRCLE:
vektor_circle_tessellate(&edges, &p->circle, i, scale);
vektor_edges_to_triangles(vb, &edges, &transform, style, TRUE);
break;
case VEKTOR_RECTANGLE:
vektor_rectangle_tessellate(&edges, &p->rectangle, i, scale);
vektor_edges_to_triangles(vb, &edges, &transform, style, TRUE);
break; break;
default: default:
@@ -52,11 +108,10 @@ void vektor_rasterize(VertexBuffer* vb, VektorShapeBuffer* shapes) {
break; break;
} }
} }
vektor_edges_to_triangles(vb, &edges, shapes);
} }
void vb_add_triangle(VertexBuffer* vb, V2 v0, V2 v1, V2 v2, VektorColor color) { void vektor_vb_add_triangle(VertexBuffer* vb, V2 v0, V2 v1, V2 v2,
VektorColor color) {
if (vb->count + 3 >= vb->capacity) { if (vb->count + 3 >= vb->capacity) {
vb->capacity = vb->capacity ? vb->capacity * 2 : 8; vb->capacity = vb->capacity ? vb->capacity * 2 : 8;
vb->vertices = realloc(vb->vertices, sizeof(Vertex) * vb->capacity); vb->vertices = realloc(vb->vertices, sizeof(Vertex) * vb->capacity);
@@ -64,35 +119,132 @@ void vb_add_triangle(VertexBuffer* vb, V2 v0, V2 v1, V2 v2, VektorColor color) {
vb->vertices[vb->count++] = (Vertex){v0, color}; vb->vertices[vb->count++] = (Vertex){v0, color};
vb->vertices[vb->count++] = (Vertex){v1, color}; vb->vertices[vb->count++] = (Vertex){v1, color};
vb->vertices[vb->count++] = (Vertex){v2, color}; vb->vertices[vb->count++] = (Vertex){v2, color};
if (vb->count <= vb->capacity / 4) {
vb->capacity /= 2;
vb->vertices = realloc(vb->vertices, sizeof(Vertex) * vb->capacity);
}
} }
void vektor_edge_to_triangles(VertexBuffer* vb, Edge e, void vektor_vb_add_quad(VertexBuffer* vb, V2 a, V2 b, VektorColor color) {
VektorShapeBuffer* shape_buffer) {
float dx = e.p2.x - e.p1.x;
float dy = e.p2.y - e.p1.y;
float len = sqrtf(dx * dx + dy * dy);
if (len == 0)
return;
float px = float minx = fminf(a.x, b.x);
-dy / len * (shape_buffer->shapes[e.shape_id].style.stroke_width / 2); float maxx = fmaxf(a.x, b.x);
float py = float miny = fminf(a.y, b.y);
dx / len * (shape_buffer->shapes[e.shape_id].style.stroke_width / 2); float maxy = fmaxf(a.y, b.y);
V2 v0 = {e.p1.x + px, e.p1.y + py}; V2 tl = {minx, miny};
V2 v1 = {e.p1.x - px, e.p1.y - py}; V2 bl = {minx, maxy};
V2 v2 = {e.p2.x + px, e.p2.y + py}; V2 br = {maxx, maxy};
V2 v3 = {e.p2.x - px, e.p2.y - py}; V2 tr = {maxx, miny};
vb_add_triangle(vb, v0, v1, v2, vektor_vb_add_triangle(vb, tl, bl, br, color);
shape_buffer->shapes[e.shape_id].style.stroke_color); vektor_vb_add_triangle(vb, tl, br, tr, color);
vb_add_triangle(vb, v2, v1, v3, }
shape_buffer->shapes[e.shape_id].style.stroke_color);
Edge edge_transform(const Edge* e, const M33* t) {
Edge out = *e;
out.p1 = m33_transform(*t, e->p1);
out.p2 = m33_transform(*t, e->p2);
return out;
}
V2 line_intersection(V2 p, V2 r, V2 q, V2 s) {
float t = vec2_cross(vec2_sub(q, p), s) / vec2_cross(r, s);
return vec2_add(p, vec2_scale(r, t));
} }
void vektor_edges_to_triangles(VertexBuffer* vb, EdgeBuffer* edges, void vektor_edges_to_triangles(VertexBuffer* vb, EdgeBuffer* edges,
VektorShapeBuffer* shape_buffer) { M33* transform, VektorStyle style, bool closed) {
if (!edges || edges->count < 1)
return;
float hw = style.stroke_width * 0.5f;
for (size_t i = 0; i < edges->count; i++) { for (size_t i = 0; i < edges->count; i++) {
vektor_edge_to_triangles(vb, edges->edges[i], shape_buffer); Edge e = edge_transform(&edges->edges[i], transform);
V2 d = vec2_normalize(vec2_sub(e.p2, e.p1));
V2 n = vec2_perp(d);
V2 off = vec2_scale(n, hw);
V2 v0 = vec2_add(e.p1, off);
V2 v1 = vec2_sub(e.p1, off);
V2 v2 = vec2_add(e.p2, off);
V2 v3 = vec2_sub(e.p2, off);
vektor_vb_add_triangle(vb, v0, v1, v2, style.stroke_color);
vektor_vb_add_triangle(vb, v2, v1, v3, style.stroke_color);
}
size_t limit = closed ? edges->count : edges->count - 1;
for (size_t i = 0; i < limit; i++) {
Edge e1 = edge_transform(&edges->edges[i], transform);
Edge e2 =
edge_transform(&edges->edges[(i + 1) % edges->count], transform);
V2 corner = e1.p2;
V2 d1 = vec2_normalize(vec2_sub(e1.p2, e1.p1));
V2 d2 = vec2_normalize(vec2_sub(e2.p2, e2.p1));
V2 n1 = vec2_perp(d1);
V2 n2 = vec2_perp(d2);
V2 off1 = vec2_scale(n1, hw);
V2 off2 = vec2_scale(n2, hw);
V2 v10 = vec2_add(e1.p1, off1);
V2 v11 = vec2_sub(e1.p1, off1);
V2 v12 = vec2_add(e1.p2, off1);
V2 v13 = vec2_sub(e1.p2, off1);
V2 v20 = vec2_add(e2.p1, off2);
V2 v21 = vec2_sub(e2.p1, off2);
V2 v22 = vec2_add(e2.p2, off2);
V2 v23 = vec2_sub(e2.p2, off2);
V2 outer1 = vec2_add(corner, off1);
V2 outer2 = vec2_add(corner, off2);
V2 inner1 = vec2_sub(corner, off1);
V2 inner2 = vec2_sub(corner, off2);
float cos_theta = vec2_dot(d1, d2);
float sin_half = sqrtf((1.0f - cos_theta) * 0.5f);
float miter_len = hw / sin_half;
if (miter_len > 4.0 * hw || miter_len < 1.05 * hw) {
vektor_vb_add_triangle(vb, outer1, corner, outer2,
style.stroke_color);
vektor_vb_add_triangle(vb, inner1, corner, inner2,
style.stroke_color);
continue;
}
V2 outer_miter = line_intersection(vec2_add(corner, off1), d1,
vec2_add(corner, off2), d2);
V2 inner_miter = line_intersection(vec2_sub(corner, off1), d1,
vec2_sub(corner, off2), d2);
V2 mo = vec2_sub(outer_miter, corner);
V2 mi = vec2_sub(inner_miter, corner);
V2 vo1 = line_intersection(corner, vec2_normalize(vec2_perp(mo)),
vec2_add(corner, off1), d1);
V2 vo2 = line_intersection(corner,
vec2_negate(vec2_normalize(vec2_perp(mo))),
vec2_add(corner, off2), d2);
V2 vi1 = line_intersection(corner, vec2_normalize(vec2_perp(mi)),
vec2_sub(corner, off1), d1);
V2 vi2 = line_intersection(corner,
vec2_negate(vec2_normalize(vec2_perp(mi))),
vec2_sub(corner, off2), d2);
vektor_vb_add_triangle(vb, vo1, outer_miter, vo2, style.stroke_color);
vektor_vb_add_triangle(vb, vi1, inner_miter, vi2, style.stroke_color);
} }
} }

View File

@@ -4,6 +4,7 @@
#include "primitives.h" #include "primitives.h"
#include "../util/color.h" #include "../util/color.h"
#include "src/core/modifier.h"
#include "stddef.h" #include "stddef.h"
#include "vector.h" #include "vector.h"
#include <stddef.h> #include <stddef.h>
@@ -23,8 +24,14 @@ typedef struct {
void vektor_edgebuffer_add_edge(EdgeBuffer* edges, Edge edge); void vektor_edgebuffer_add_edge(EdgeBuffer* edges, Edge edge);
void vektor_polyline_flatten(EdgeBuffer* edges, VektorPolyline* line, size_t i); void vektor_polyline_tessellate(EdgeBuffer* edges, VektorPolyline* line,
void vektor_polygon_flatten(EdgeBuffer* buffer, VektorPolygon* line, size_t i); size_t i, double scale);
void vektor_polygon_tessellate(EdgeBuffer* buffer, VektorPolygon* polygon,
size_t i, double scale);
void vektor_circle_tessellate(EdgeBuffer* buffer, VektorCircle* circle,
size_t i, double scale);
void vektor_rectangle_tessellate(EdgeBuffer* buffer, VektorRectangle* rct,
size_t i, double scale);
typedef struct { typedef struct {
V2 coords; V2 coords;
@@ -37,11 +44,15 @@ typedef struct {
size_t capacity; size_t capacity;
} VertexBuffer; } VertexBuffer;
void vb_add_triangle(VertexBuffer* vb, V2 v0, V2 v1, V2 v2, VektorColor color); void vektor_vb_add_triangle(VertexBuffer* vb, V2 v0, V2 v1, V2 v2,
VektorColor color);
void vektor_vb_add_quad(VertexBuffer* vb, V2 v0, V2 v1, VektorColor color);
void vektor_edge_to_triangles(VertexBuffer* vb, Edge e, void vektor_edge_to_triangles(VertexBuffer* vb, Edge e,
VektorShapeBuffer* shape_buffer); VektorShapeNodeBuffer* node_buffer);
void vektor_edges_to_triangles(VertexBuffer* vb, EdgeBuffer* edges, void vektor_edges_to_triangles(VertexBuffer* vb, EdgeBuffer* edges,
VektorShapeBuffer* shape_buffer); M33* transform, VektorStyle style, bool closed);
void vektor_rasterize(VertexBuffer* vb, VektorShapeBuffer* shapes); void vektor_vb_rasterize(VertexBuffer* vb, VektorShapeNodeBuffer* shapes,
double scale);
#endif // RASTER_H_ #endif // RASTER_H_

View File

@@ -14,14 +14,24 @@ typedef struct {
double z; double z;
} V3; } V3;
static inline V2 vec2_fromfloat(const float f) { return (V2){f, f}; }
static inline V3 vec2_vector(const V2 v) { return (V3){v.x, v.y, 0}; } static inline V3 vec2_vector(const V2 v) { return (V3){v.x, v.y, 0}; }
static inline V3 vec2_point(const V2 v) { return (V3){v.x, v.y, 1}; } static inline V3 vec2_point(const V2 v) { return (V3){v.x, v.y, 1}; }
static inline V2 vec2_perp(const V2 v) { return (V2){-v.y, v.x}; }
static inline V2 vec2_negate(const V2 v) { return (V2){-v.x, -v.y}; }
static inline V2 vec2_add(const V2 v1, const V2 v2) { static inline V2 vec2_add(const V2 v1, const V2 v2) {
return (V2){v1.x + v2.x, v1.y + v2.y}; return (V2){v1.x + v2.x, v1.y + v2.y};
} }
static inline bool vec2_equals(const V2 v1, const V2 v2) {
return (bool)(v1.x == v2.x && v1.y == v2.x);
}
static inline V2 vec2_sub(const V2 v1, const V2 v2) { static inline V2 vec2_sub(const V2 v1, const V2 v2) {
return (V2){v1.x - v2.x, v1.y - v2.y}; return (V2){v1.x - v2.x, v1.y - v2.y};
} }
@@ -42,16 +52,16 @@ static inline double vec2_cross(const V2 a, const V2 b) {
return a.x * b.y - a.y * b.x; return a.x * b.y - a.y * b.x;
} }
static inline double vec2_norm(const V2 v) { static inline double vec2_length(const V2 v) {
return sqrt(v.x * v.x + v.y * v.y); return sqrt(v.x * v.x + v.y * v.y);
} }
static inline double vec2_quadrance(const V2 v) { static inline double vec2_lengthsq(const V2 v) {
return (v.x * v.x + v.y * v.y); return (v.x * v.x + v.y * v.y);
} }
static inline V2 vec2_normalize(const V2 v) { static inline V2 vec2_normalize(const V2 v) {
return vec2_scale(v, 1 / vec2_norm(v)); return vec2_scale(v, 1 / vec2_length(v));
} }
#endif // VECTOR_H_ #endif // VECTOR_H_

View File

@@ -1,17 +1,17 @@
#include "glib.h"
#include "gtk/gtk.h" #include "gtk/gtk.h"
#include "src/application/applicationstate.h" #include "src/application/applicationstate.h"
#include "src/core/primitives.h"
#include "stdio.h" #include "stdio.h"
#include "stdlib.h" #include "stdlib.h"
#include "./application/applicationstate.h" #include "./application/applicationstate.h"
#include "./core/raster.h"
#include "./ui/uicontroller.h" #include "./ui/uicontroller.h"
#include "./ui/vektorcanvas.h"
#include "./util/color.h"
static void on_map(GtkWidget* window, gpointer user_data) { static int update_callback(gpointer data) {
vektor_uictrl_map((VektorWidgetState*)user_data); VektorAppState* appstate = (VektorAppState*)data;
gtk_gl_area_queue_render(
GTK_GL_AREA(appstate->widgetState->workspaceCanvas));
return G_SOURCE_CONTINUE;
} }
static void activate(GtkApplication* app, gpointer user_data) { static void activate(GtkApplication* app, gpointer user_data) {
@@ -22,8 +22,7 @@ static void activate(GtkApplication* app, gpointer user_data) {
VektorAppState* app_state = (VektorAppState*)malloc(sizeof(VektorAppState)); VektorAppState* app_state = (VektorAppState*)malloc(sizeof(VektorAppState));
vektor_appstate_new(widget_state, app_state); vektor_appstate_new(widget_state, app_state);
g_signal_connect(widget_state->window, "map", G_CALLBACK(on_map), g_timeout_add(1, update_callback, app_state);
widget_state);
gtk_window_present(widget_state->window); gtk_window_present(widget_state->window);
} }

View File

@@ -31,11 +31,6 @@ void vektor_uictrl_init(GtkApplication* app, VektorWidgetState* stateOut) {
GtkIconTheme* theme = GtkIconTheme* theme =
gtk_icon_theme_get_for_display(gdk_display_get_default()); gtk_icon_theme_get_for_display(gdk_display_get_default());
/*if (gtk_icon_theme_has_icon(theme, "vektor-circle-symbolic"))
g_print("GTK sees it!\n");
else
g_print("Still invisible...\n");*/
// populate state // populate state
stateOut->window = stateOut->window =
GTK_WINDOW(gtk_builder_get_object(builder, "main_window")); GTK_WINDOW(gtk_builder_get_object(builder, "main_window"));
@@ -50,12 +45,16 @@ void vektor_uictrl_init(GtkApplication* app, VektorWidgetState* stateOut) {
GTK_BUTTON(gtk_builder_get_object(builder, "button_shapetools")); GTK_BUTTON(gtk_builder_get_object(builder, "button_shapetools"));
stateOut->workspaceRevealerShapes = stateOut->workspaceRevealerShapes =
GTK_REVEALER(gtk_builder_get_object(builder, "shape_revealer")); GTK_REVEALER(gtk_builder_get_object(builder, "shape_revealer"));
stateOut->workspaceButtonLinetool = stateOut->workspaceButtonLineTool =
GTK_BUTTON(gtk_builder_get_object(builder, "button_linetool")); GTK_BUTTON(gtk_builder_get_object(builder, "button_linetool"));
stateOut->workspaceButtonRecttool = stateOut->workspaceButtonRectTool =
GTK_BUTTON(gtk_builder_get_object(builder, "button_rectangletool")); GTK_BUTTON(gtk_builder_get_object(builder, "button_rectangletool"));
stateOut->workspaceButtonCircletool = stateOut->workspaceButtonCircleTool =
GTK_BUTTON(gtk_builder_get_object(builder, "button_circletool")); GTK_BUTTON(gtk_builder_get_object(builder, "button_circletool"));
stateOut->workspaceButtonPolygonTool =
GTK_BUTTON(gtk_builder_get_object(builder, "button_polygontool"));
stateOut->workspaceButtonSelectionTool =
GTK_BUTTON(gtk_builder_get_object(builder, "button_selecttool"));
stateOut->workspaceColorPicker = stateOut->workspaceColorPicker =
VEKTOR_COLOR_WHEEL(gtk_builder_get_object(builder, "color_picker")); VEKTOR_COLOR_WHEEL(gtk_builder_get_object(builder, "color_picker"));
@@ -71,10 +70,9 @@ void vektor_uictrl_init(GtkApplication* app, VektorWidgetState* stateOut) {
gtk_window_set_title(stateOut->window, "Vektor"); gtk_window_set_title(stateOut->window, "Vektor");
gtk_window_set_default_size(stateOut->window, 800, 600); gtk_window_set_default_size(stateOut->window, 800, 600);
// Set dimensions
gtk_paned_set_position(stateOut->workspacePaned, 800 * .7);
gtk_paned_set_position(stateOut->sidepanelPaned, 250);
g_object_unref(builder); g_object_unref(builder);
} }
void vektor_uictrl_map(VektorWidgetState* state) {
gtk_paned_set_position(state->workspacePaned, 800 * .7);
gtk_paned_set_position(state->sidepanelPaned, 250);
}

View File

@@ -17,9 +17,11 @@ typedef struct VektorWidgetState {
GtkButton* workspaceButtonMasterShapes; GtkButton* workspaceButtonMasterShapes;
GtkRevealer* workspaceRevealerShapes; GtkRevealer* workspaceRevealerShapes;
GtkButton* workspaceButtonLinetool; GtkButton* workspaceButtonLineTool;
GtkButton* workspaceButtonRecttool; GtkButton* workspaceButtonRectTool;
GtkButton* workspaceButtonCircletool; GtkButton* workspaceButtonCircleTool;
GtkButton* workspaceButtonPolygonTool;
GtkButton* workspaceButtonSelectionTool;
VektorColorWheel* workspaceColorPicker; VektorColorWheel* workspaceColorPicker;

View File

@@ -1,8 +1,11 @@
#include "epoxy/gl.h" #include "epoxy/gl.h"
#include "glib.h"
#include "gtk/gtk.h" #include "gtk/gtk.h"
#include "../core/raster.h" #include "../core/raster.h"
#include "src/core/matrix.h"
#include "src/core/primitives.h" #include "src/core/primitives.h"
#include "src/util/color.h"
#include "uicontroller.h" #include "uicontroller.h"
#include "vektorcanvas.h" #include "vektorcanvas.h"
#include <epoxy/gl_generated.h> #include <epoxy/gl_generated.h>
@@ -27,9 +30,23 @@ char* read_file(const char* path) {
return buffer; return buffer;
} }
static GLuint shader_program; static GLuint standard_shader_program;
static GLuint selection_shader_program;
// shader uniforms
static GLuint shader_standard_uProjMatrixLoc;
static GLuint shader_selection_uProjMatrixLoc;
static GLuint shader_selection_uTimeLoc;
static GLuint shader_selection_uScaleLoc;
static GLuint shader_selection_uC1Loc;
static GLuint shader_selection_uC2Loc;
static GLuint shader_selection_uMinLoc;
static GLuint shader_selection_uMaxLoc;
static GLuint vao; static GLuint vao;
VertexBuffer vb; VertexBuffer vb;
static size_t shape_vertex_count = 0;
static GLuint compile_shader(GLenum type, const char* src) { static GLuint compile_shader(GLenum type, const char* src) {
GLuint shader = glCreateShader(type); GLuint shader = glCreateShader(type);
@@ -46,17 +63,11 @@ static GLuint compile_shader(GLenum type, const char* src) {
return shader; return shader;
} }
static void init_shader(void) { static GLuint create_shader_program(char* frag, char* vert) {
char* vert_src = read_file("./shaders/triangle.vert.glsl"); GLuint vertex = compile_shader(GL_VERTEX_SHADER, vert);
char* frag_src = read_file("./shaders/triangle.frag.glsl"); GLuint fragment = compile_shader(GL_FRAGMENT_SHADER, frag);
if (!vert_src || !frag_src) GLuint shader_program = glCreateProgram();
g_error("Failed to load shader files");
GLuint vertex = compile_shader(GL_VERTEX_SHADER, vert_src);
GLuint fragment = compile_shader(GL_FRAGMENT_SHADER, frag_src);
shader_program = glCreateProgram();
glAttachShader(shader_program, vertex); glAttachShader(shader_program, vertex);
glAttachShader(shader_program, fragment); glAttachShader(shader_program, fragment);
glLinkProgram(shader_program); glLinkProgram(shader_program);
@@ -71,6 +82,42 @@ static void init_shader(void) {
glDeleteShader(vertex); glDeleteShader(vertex);
glDeleteShader(fragment); glDeleteShader(fragment);
return shader_program;
}
static void init_shader(void) {
char* vert_src = read_file("./shaders/triangle.vert.glsl");
char* frag_src = read_file("./shaders/triangle.frag.glsl");
char* selection_frag_src = read_file("./shaders/selection.frag.glsl");
if (!vert_src || !frag_src)
g_error("Failed to load shader files");
standard_shader_program = create_shader_program(frag_src, vert_src);
selection_shader_program =
create_shader_program(selection_frag_src, vert_src);
shader_standard_uProjMatrixLoc =
glGetUniformLocation(standard_shader_program, "uProjection");
shader_selection_uProjMatrixLoc =
glGetUniformLocation(selection_shader_program, "uProjection");
shader_selection_uTimeLoc =
glGetUniformLocation(selection_shader_program, "uTime");
shader_selection_uScaleLoc =
glGetUniformLocation(selection_shader_program, "uScale");
shader_selection_uC1Loc =
glGetUniformLocation(selection_shader_program, "uColor1");
shader_selection_uC2Loc =
glGetUniformLocation(selection_shader_program, "uColor2");
shader_selection_uMinLoc =
glGetUniformLocation(selection_shader_program, "uMin");
shader_selection_uMaxLoc =
glGetUniformLocation(selection_shader_program, "uMax");
if (shader_selection_uMinLoc == -1 || shader_selection_uMaxLoc == -1)
g_warning("Selection shader: uMin/uMax uniform not found in shader!");
} }
static void init_geometry(void) { static void init_geometry(void) {
@@ -92,19 +139,52 @@ static void init_geometry(void) {
glBindVertexArray(0); glBindVertexArray(0);
} }
static gboolean render(GtkGLArea* area, GdkGLContext* context,
VektorShapeBuffer* prims) { void vektor_canvas_geometry_changed(VektorCanvasRenderInfo* renderInfo) {
vektor_rasterize(&vb, prims); vb.count = 0;
vektor_vb_rasterize(&vb, renderInfo->shapes, renderInfo->zoom);
shape_vertex_count = vb.count;
if (renderInfo->selectedShape != NULL &&
*(renderInfo->selectedShape) != NULL) {
VektorShapeNode* selectedShape = *renderInfo->selectedShape;
// create handle quads if a shape is selected
for (size_t i = 0; i < selectedShape->base->handleCount; i++) {
V2 handle = selectedShape->base->handles[i];
VektorBBox handleBbox = vektor_shape_get_handle_bbox(handle);
vektor_vb_add_quad(&vb, handleBbox.min, handleBbox.max,
vektor_color_new(255, 255, 255, 255));
}
shape_vertex_count = vb.count;
// create selection quad if a shape is selected
VektorBBox bbox =
vektor_primitive_get_bbox(selectedShape->base->primitive);
// expand it a little so it is not inset
bbox = vektor_bbox_expand(bbox, 0.03f);
vektor_vb_add_quad(&vb, bbox.min, bbox.max,
vektor_color_new(255, 255, 255, 255));
}
}
static gboolean render(GtkGLArea* a, GdkGLContext* ctx,
VektorCanvasRenderInfo* renderInfo) {
// vektor_canvas_geometry_changed(renderInfo);
glBufferData(GL_ARRAY_BUFFER, vb.count * sizeof(Vertex), vb.vertices, glBufferData(GL_ARRAY_BUFFER, vb.count * sizeof(Vertex), vb.vertices,
GL_STATIC_DRAW); GL_STATIC_DRAW);
glUseProgram(shader_program); // PASS 1 - draw shape vertices
glUseProgram(standard_shader_program);
GLuint uProjectionLoc = glGetUniformLocation(shader_program, "uProjection"); // float projectionMatrix[16] = {1, 0, 0, 0, 0, 1, 0, 0,
float projectionMatrix[16] = {1, 0, 0, 0, 0, 1, 0, 0, // 0, 0, 1, 0, 0, 0, 0, 1};
0, 0, 1, 0, 0, 0, 0, 1}; glUniformMatrix4fv(shader_standard_uProjMatrixLoc, 1, GL_FALSE,
glUniformMatrix4fv(uProjectionLoc, 1, GL_FALSE, projectionMatrix); renderInfo->canvasTransform);
glBindVertexArray(vao); glBindVertexArray(vao);
glDisable(GL_CULL_FACE); glDisable(GL_CULL_FACE);
@@ -112,10 +192,34 @@ static gboolean render(GtkGLArea* area, GdkGLContext* context,
glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, vb.count); glDrawArrays(GL_TRIANGLES, 0, shape_vertex_count);
GLenum err = glGetError();
//printf("OpenGL error: %x\n", err); // PASS 2 - draw selection quads
// glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); if (vb.count > shape_vertex_count) {
// g_print("vdelta: %zu\n", vb.count - shape_vertex_count);
float time =
(g_get_monotonic_time() - renderInfo->startupTime) / 10000000.0f;
// re-fetch bbox (we know a shape is selected)
VektorBBox bbox = vektor_primitive_get_bbox(
(*(renderInfo->selectedShape))->base->primitive);
bbox = vektor_bbox_expand(bbox, 0.03f);
glUseProgram(selection_shader_program);
glUniformMatrix4fv(shader_selection_uProjMatrixLoc, 1, GL_FALSE,
renderInfo->canvasTransform);
glUniform1f(shader_selection_uTimeLoc, time);
glUniform1f(shader_selection_uScaleLoc, renderInfo->zoom);
glUniform2f(shader_selection_uMinLoc, bbox.min.x, bbox.min.y);
glUniform2f(shader_selection_uMaxLoc, bbox.max.x, bbox.max.y);
glUniform4f(shader_selection_uC1Loc, 0, 0, 0, 0);
glUniform4f(shader_selection_uC2Loc, 0.46, 0.46, 1, 1);
glDrawArrays(GL_TRIANGLES, shape_vertex_count,
vb.count - shape_vertex_count);
}
glBindVertexArray(0); glBindVertexArray(0);
glUseProgram(0); glUseProgram(0);
@@ -160,8 +264,110 @@ static void realize(GtkGLArea* area, gpointer user_data) {
init_geometry(); init_geometry();
} }
static void on_scroll(GtkEventControllerScroll* controller, double dx,
double dy, gpointer user_data) {
VektorCanvasRenderInfo* s = user_data;
GdkModifierType state = gtk_event_controller_get_current_event_state(
GTK_EVENT_CONTROLLER(controller));
// if (state & GDK_CONTROL_MASK) {
if (dy < 0)
s->zoom *= 1.1f;
else if (dy > 0)
s->zoom *= 0.9f;
M33 mat =
m33_mul(m33_translate(s->panX, s->panY),
m33_mul(m33_rotate(s->rotation), m33_scale(s->zoom, s->zoom)));
m33_to_gl4(mat, s->canvasTransform);
s->canvasMat = mat;
GtkWidget* widget =
gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(controller));
vektor_canvas_geometry_changed(s);
}
static void on_pan_begin(GtkGestureDrag* gesture, double start_x,
double start_y, gpointer user_data) {
VektorCanvasRenderInfo* s = user_data;
GdkModifierType state = gtk_event_controller_get_current_event_state(
GTK_EVENT_CONTROLLER(gesture));
if (!(state & GDK_SHIFT_MASK)) {
s->drag_start_x = s->panX;
s->drag_start_y = s->panY;
} else {
double x, y;
gtk_gesture_drag_get_start_point(gesture, &x, &y);
s->mouse_start_x = x;
s->mouse_start_y = y;
double cx = VKTR_CANVAS_WIDTH * 0.5;
double cy = VKTR_CANVAS_HEIGHT * 0.5;
double dx = x - cx;
double dy = y - cy;
s->dragStartAngle = atan2(dy, dx);
s->dragStartRotation = s->rotation;
}
}
static void on_pan_drag(GtkGestureDrag* gesture, double offset_x,
double offset_y, gpointer user_data) {
VektorCanvasRenderInfo* s = user_data;
GdkModifierType state = gtk_event_controller_get_current_event_state(
GTK_EVENT_CONTROLLER(gesture));
if (!(state & GDK_SHIFT_MASK)) {
s->panX = s->drag_start_x + offset_x / (80 * s->zoom);
s->panY = s->drag_start_y - offset_y / (80 * s->zoom);
M33 mat = m33_mul(
m33_translate(s->panX, s->panY),
m33_mul(m33_rotate(s->rotation), m33_scale(s->zoom, s->zoom)));
m33_to_gl4(mat, s->canvasTransform);
s->canvasMat = mat;
} else {
double x, y;
gtk_gesture_drag_get_offset(gesture, &x, &y);
double mx = s->mouse_start_x + x;
double my = s->mouse_start_y + y;
double cx = VKTR_CANVAS_WIDTH * 0.5;
double cy = VKTR_CANVAS_HEIGHT * 0.5;
double dx = mx - cx;
double dy = my - cy;
double angle = -atan2(dy, dx);
s->rotation = s->dragStartRotation + (angle - s->dragStartAngle);
M33 mat = m33_mul(
m33_translate(s->panX, s->panY),
m33_mul(m33_rotate(s->rotation), m33_scale(s->zoom, s->zoom)));
m33_to_gl4(mat, s->canvasTransform);
s->canvasMat = mat;
}
GtkWidget* widget =
gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture));
//gtk_gl_area_queue_render(GTK_GL_AREA(widget));
vektor_canvas_geometry_changed(s);
}
void vektor_canvas_init(VektorWidgetState* state, VektorCanvas* canvasOut, void vektor_canvas_init(VektorWidgetState* state, VektorCanvas* canvasOut,
VektorShapeBuffer* prims) { VektorCanvasRenderInfo* renderInfo) {
canvasOut->canvasWidget = state->workspaceCanvas; canvasOut->canvasWidget = state->workspaceCanvas;
canvasOut->width = VKTR_CANVAS_WIDTH; canvasOut->width = VKTR_CANVAS_WIDTH;
canvasOut->height = VKTR_CANVAS_HEIGHT; canvasOut->height = VKTR_CANVAS_HEIGHT;
@@ -176,5 +382,21 @@ void vektor_canvas_init(VektorWidgetState* state, VektorCanvas* canvasOut,
g_signal_connect(canvasOut->canvasWidget, "realize", G_CALLBACK(realize), g_signal_connect(canvasOut->canvasWidget, "realize", G_CALLBACK(realize),
NULL); NULL);
g_signal_connect(canvasOut->canvasWidget, "render", G_CALLBACK(render), g_signal_connect(canvasOut->canvasWidget, "render", G_CALLBACK(render),
prims); renderInfo);
GtkEventController* scroll =
gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_VERTICAL);
gtk_widget_add_controller(GTK_WIDGET(canvasOut->canvasWidget), scroll);
g_signal_connect(scroll, "scroll", G_CALLBACK(on_scroll), renderInfo);
GtkGesture* pan = gtk_gesture_drag_new();
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pan), GDK_BUTTON_MIDDLE);
gtk_widget_add_controller(GTK_WIDGET(canvasOut->canvasWidget),
GTK_EVENT_CONTROLLER(pan));
g_signal_connect(pan, "drag-begin", G_CALLBACK(on_pan_begin), renderInfo);
g_signal_connect(pan, "drag-update", G_CALLBACK(on_pan_drag), renderInfo);
} }

View File

@@ -4,6 +4,9 @@
#include "../core/raster.h" #include "../core/raster.h"
#include "../util/color.h" #include "../util/color.h"
#include "gtk/gtk.h" #include "gtk/gtk.h"
#include "src/core/matrix.h"
#include "src/core/modifier.h"
#include "src/core/primitives.h"
#include "uicontroller.h" #include "uicontroller.h"
typedef struct VektorCanvas { typedef struct VektorCanvas {
@@ -18,8 +21,31 @@ typedef struct VektorCanvas {
int height; int height;
} VektorCanvas; } VektorCanvas;
typedef struct VektorCanvasRenderInfo {
gint64 startupTime;
VektorShapeNodeBuffer* shapes;
// a pointer to appstate->selectedShape
VektorShapeNode** selectedShape;
float zoom;
float panX;
float panY;
float rotation;
float dragStartRotation;
double dragStartAngle;
double drag_start_x;
double drag_start_y;
double mouse_start_x;
double mouse_start_y;
M33 canvasMat;
float canvasTransform[16];
} VektorCanvasRenderInfo;
void vektor_canvas_init(VektorWidgetState* state, VektorCanvas* canvasOut, void vektor_canvas_init(VektorWidgetState* state, VektorCanvas* canvasOut,
VektorShapeBuffer* shapes); VektorCanvasRenderInfo* renderInfo);
void vektor_canvas_geometry_changed(VektorCanvasRenderInfo* renderInfo);
// void vektor_canvas_update(VektorCanvas* canvas); // void vektor_canvas_update(VektorCanvas* canvas);
// void vektor_canvas_fill(VektorCanvas* canvas, VektorColor color); // void vektor_canvas_fill(VektorCanvas* canvas, VektorColor color);
// void vektor_canvas_drawfrom(VektorFramebuffer* fb, VektorCanvas* canvas); // void vektor_canvas_drawfrom(VektorFramebuffer* fb, VektorCanvas* canvas);

View File

@@ -21,36 +21,23 @@ struct _VektorColorWheel {
G_DEFINE_TYPE(VektorColorWheel, vektor_color_wheel, GTK_TYPE_DRAWING_AREA) G_DEFINE_TYPE(VektorColorWheel, vektor_color_wheel, GTK_TYPE_DRAWING_AREA)
static gboolean point_in_triangle( static gboolean point_in_triangle(double px, double py, double ax, double ay,
double px, double py, double bx, double by, double cx, double cy,
double ax, double ay, double* u, double* v, double* w) {
double bx, double by, double denom = (by - cy) * (ax - cx) + (cx - bx) * (ay - cy);
double cx, double cy,
double* u, double* v, double* w)
{
double denom =
(by - cy)*(ax - cx) +
(cx - bx)*(ay - cy);
*u = *u = ((by - cy) * (px - cx) + (cx - bx) * (py - cy)) / denom;
((by - cy)*(px - cx) +
(cx - bx)*(py - cy)) / denom;
*v = *v = ((cy - ay) * (px - cx) + (ax - cx) * (py - cy)) / denom;
((cy - ay)*(px - cx) +
(ax - cx)*(py - cy)) / denom;
*w = 1 - *u - *v; *w = 1 - *u - *v;
return (*u >= 0 && *v >= 0 && *w >= 0); return (*u >= 0 && *v >= 0 && *w >= 0);
} }
static void closest_point_on_segment( static void closest_point_on_segment(double px, double py, double ax, double ay,
double px, double py, double bx, double by, double* rx,
double ax, double ay, double* ry) {
double bx, double by,
double *rx, double *ry)
{
double abx = bx - ax; double abx = bx - ax;
double aby = by - ay; double aby = by - ay;
@@ -59,20 +46,19 @@ static void closest_point_on_segment(
double t = (apx * abx + apy * aby) / (abx * abx + aby * aby); double t = (apx * abx + apy * aby) / (abx * abx + aby * aby);
if (t < 0) t = 0; if (t < 0)
if (t > 1) t = 1; t = 0;
if (t > 1)
t = 1;
*rx = ax + abx * t; *rx = ax + abx * t;
*ry = ay + aby * t; *ry = ay + aby * t;
} }
static void closest_point_on_triangle( static void closest_point_on_triangle(double px, double py, double ax,
double px, double py, double ay, double bx, double by,
double ax, double ay, double cx, double cy, double* rx,
double bx, double by, double* ry) {
double cx, double cy,
double *rx, double *ry)
{
double p1x, p1y; double p1x, p1y;
double p2x, p2y; double p2x, p2y;
double p3x, p3y; double p3x, p3y;
@@ -85,12 +71,20 @@ static void closest_point_on_triangle(
double d2 = (px - p2x) * (px - p2x) + (py - p2y) * (py - p2y); double d2 = (px - p2x) * (px - p2x) + (py - p2y) * (py - p2y);
double d3 = (px - p3x) * (px - p3x) + (py - p3y) * (py - p3y); double d3 = (px - p3x) * (px - p3x) + (py - p3y) * (py - p3y);
if (d1 <= d2 && d1 <= d3) { *rx = p1x; *ry = p1y; } if (d1 <= d2 && d1 <= d3) {
else if (d2 <= d3) { *rx = p2x; *ry = p2y; } *rx = p1x;
else { *rx = p3x; *ry = p3y; } *ry = p1y;
} else if (d2 <= d3) {
*rx = p2x;
*ry = p2y;
} else {
*rx = p3x;
*ry = p3y;
}
} }
static void vektor_color_wheel_snapshot(GtkWidget* widget, GtkSnapshot* snapshot) { static void vektor_color_wheel_snapshot(GtkWidget* widget,
GtkSnapshot* snapshot) {
VektorColorWheel* self = VEKTOR_COLOR_WHEEL(widget); VektorColorWheel* self = VEKTOR_COLOR_WHEEL(widget);
int width = gtk_widget_get_width(widget); int width = gtk_widget_get_width(widget);
@@ -153,16 +147,20 @@ static void vektor_color_wheel_snapshot(GtkWidget* widget, GtkSnapshot* snapshot
// White gradient: from pure hue (right) → white (bottom) // White gradient: from pure hue (right) → white (bottom)
cairo_pattern_t* white = cairo_pattern_create_linear(ax, ay, bx, by); 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, 0.0, 1, 1, 1,
cairo_pattern_add_color_stop_rgba(white, 1.0, 1,1,1, 1.0); // opaque white at bottom 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_set_source(cr, white);
cairo_paint(cr); cairo_paint(cr);
cairo_pattern_destroy(white); cairo_pattern_destroy(white);
// Black gradient: from pure hue (right) → black (top) // Black gradient: from pure hue (right) → black (top)
cairo_pattern_t* black = cairo_pattern_create_linear(ax, ay, cx2, cy2); 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, 0.0, 0, 0, 0,
cairo_pattern_add_color_stop_rgba(black, 1.0, 0,0,0, 1.0); // opaque black at top 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_set_source(cr, black);
cairo_paint(cr); cairo_paint(cr);
cairo_pattern_destroy(black); cairo_pattern_destroy(black);
@@ -205,15 +203,10 @@ static void vektor_color_wheel_snapshot(GtkWidget* widget, GtkSnapshot* snapshot
cairo_new_path(cr); cairo_new_path(cr);
cairo_arc(cr, cairo_arc(cr, cx, cy, wheel_radius, selector_angle - selector_width,
cx, cy,
wheel_radius,
selector_angle - selector_width,
selector_angle + selector_width); selector_angle + selector_width);
cairo_arc_negative(cr, cairo_arc_negative(cr, cx, cy, inner_radius,
cx, cy,
inner_radius,
selector_angle + selector_width, selector_angle + selector_width,
selector_angle - selector_width); selector_angle - selector_width);
@@ -225,7 +218,8 @@ static void vektor_color_wheel_snapshot(GtkWidget* widget, GtkSnapshot* snapshot
cairo_destroy(cr); cairo_destroy(cr);
} }
static void on_click(GtkGestureClick* gesture, int n_press, double x, double y, gpointer data) { static void on_click(GtkGestureClick* gesture, int n_press, double x, double y,
gpointer data) {
VektorColorWheel* wheel = VEKTOR_COLOR_WHEEL(data); VektorColorWheel* wheel = VEKTOR_COLOR_WHEEL(data);
GtkWidget* widget = GTK_WIDGET(wheel); GtkWidget* widget = GTK_WIDGET(wheel);
@@ -234,7 +228,6 @@ static void on_click(GtkGestureClick* gesture, int n_press, double x, double y,
double outer_radius = MIN(width, height) / 2.0; double outer_radius = MIN(width, height) / 2.0;
double wheel_radius = outer_radius * 0.95; double wheel_radius = outer_radius * 0.95;
double inner_radius = wheel_radius * 0.9;
double triangle_radius = wheel_radius * 0.75; double triangle_radius = wheel_radius * 0.75;
@@ -251,8 +244,21 @@ static void on_click(GtkGestureClick* gesture, int n_press, double x, double y,
double cy2 = cy - 0.866 * triangle_radius; double cy2 = cy - 0.866 * triangle_radius;
double u, v, w; double u, v, w;
gboolean inside = point_in_triangle(x,y, ax,ay, bx,by, cx2,cy2, &u,&v,&w); gboolean inside =
if(inside) { // pick point in the triangle point_in_triangle(x, y, ax, ay, bx, by, cx2, cy2, &u, &v, &w);
if (wheel->dragging_triangle) {
if (!inside) { // if outside triangle, snap to its edge
double sx, sy;
closest_point_on_triangle(x, y, ax, ay, bx, by, cx2, cy2, &sx, &sy);
x = sx;
y = sy;
point_in_triangle(x, y, ax, ay, bx, by, cx2, cy2, &u, &v, &w);
}
double denom = u + v; double denom = u + v;
if (denom > 0.0001) { // avoid div-by-zero at black vertex if (denom > 0.0001) { // avoid div-by-zero at black vertex
@@ -261,54 +267,25 @@ static void on_click(GtkGestureClick* gesture, int n_press, double x, double y,
wheel->saturation = 0.0; // arbitrary, since S irrelevant at V=0 wheel->saturation = 0.0; // arbitrary, since S irrelevant at V=0
} }
wheel->lightness = denom; wheel->lightness = denom;
g_signal_emit(wheel, signals[COLOR_CHANGED], 0);
} else { } else { // dragging wheel
double dx = x - cx; double dx = x - cx;
double dy = y - cy; double dy = y - cy;
double dist = sqrt(dx*dx+dy*dy);
if(dist > inner_radius && dist < outer_radius) { // pick point on color wheel
double angle = atan2(dy, dx); double angle = atan2(dy, dx);
if(angle < 0) { angle += 2 * M_PI; } if (angle < 0) {
angle += 2 * M_PI;
}
wheel->hue = angle / (2 * M_PI); wheel->hue = angle / (2 * M_PI);
}
g_signal_emit(wheel, signals[COLOR_CHANGED], 0); g_signal_emit(wheel, signals[COLOR_CHANGED], 0);
} else if (dist < inner_radius) { // snap to triangle edge
double sx,sy;
closest_point_on_triangle(
x,y,
ax,ay,
bx,by,
cx2,cy2,
&sx,&sy
);
x = sx;
y = sy;
point_in_triangle(x,y, ax,ay, bx,by, cx2,cy2, &u,&v,&w);
// calculate triangle point
double denom = u + v;
if (denom > 0.0001) {
wheel->saturation = u / denom;
} else {
wheel->saturation = 0.0;
}
wheel->lightness = denom;
g_signal_emit(wheel, signals[COLOR_CHANGED], 0);
}
}
gtk_widget_queue_draw(widget); gtk_widget_queue_draw(widget);
} }
static void on_drag(GtkGestureDrag* gesture, double offset_x, double offset_y, gpointer data) { static void on_drag(GtkGestureDrag* gesture, double offset_x, double offset_y,
gpointer data) {
double x, y; double x, y;
gtk_gesture_drag_get_start_point(gesture, &x, &y); gtk_gesture_drag_get_start_point(gesture, &x, &y);
@@ -318,6 +295,40 @@ static void on_drag(GtkGestureDrag* gesture, double offset_x, double offset_y, g
on_click(NULL, 0, x, y, data); on_click(NULL, 0, x, y, data);
} }
static void on_drag_begin(GtkGestureDrag* gesture, gdouble start_x,
gdouble start_y, gpointer user_data) {
// set dragging_wheel or dragging_triangle which are used
// to determine to where the cursor should snap to
VektorColorWheel* wheel = VEKTOR_COLOR_WHEEL(user_data);
GtkWidget* widget = GTK_WIDGET(wheel);
int width = gtk_widget_get_width(widget);
int height = gtk_widget_get_height(widget);
double cx = width / 2.0;
double cy = height / 2.0;
double dx = start_x - cx;
double dy = start_y - cy;
double dist = sqrt(dx * dx + dy * dy);
double outer_radius = MIN(width, height) / 2.0;
double wheel_radius = outer_radius * 0.95;
double inner_radius = wheel_radius * 0.9;
if (dist > inner_radius) {
wheel->dragging_wheel = TRUE;
wheel->dragging_triangle = FALSE;
} else if (dist < inner_radius) {
wheel->dragging_wheel = FALSE;
wheel->dragging_triangle = TRUE;
} else {
wheel->dragging_wheel = FALSE;
wheel->dragging_triangle = FALSE;
}
}
static void vektor_color_wheel_init(VektorColorWheel* self) { static void vektor_color_wheel_init(VektorColorWheel* self) {
GtkGesture* click = gtk_gesture_click_new(); GtkGesture* click = gtk_gesture_click_new();
gtk_widget_add_controller(GTK_WIDGET(self), GTK_EVENT_CONTROLLER(click)); gtk_widget_add_controller(GTK_WIDGET(self), GTK_EVENT_CONTROLLER(click));
@@ -328,6 +339,7 @@ static void vektor_color_wheel_init(VektorColorWheel* self) {
gtk_widget_add_controller(GTK_WIDGET(self), GTK_EVENT_CONTROLLER(drag)); gtk_widget_add_controller(GTK_WIDGET(self), GTK_EVENT_CONTROLLER(drag));
g_signal_connect(drag, "drag-update", G_CALLBACK(on_drag), self); g_signal_connect(drag, "drag-update", G_CALLBACK(on_drag), self);
g_signal_connect(drag, "drag-begin", G_CALLBACK(on_drag_begin), self);
} }
static void vektor_color_wheel_class_init(VektorColorWheelClass* klass) { static void vektor_color_wheel_class_init(VektorColorWheelClass* klass) {
@@ -335,17 +347,8 @@ static void vektor_color_wheel_class_init(VektorColorWheelClass* klass) {
widget_class->snapshot = vektor_color_wheel_snapshot; widget_class->snapshot = vektor_color_wheel_snapshot;
signals[COLOR_CHANGED] = signals[COLOR_CHANGED] =
g_signal_new( g_signal_new("color-changed", G_TYPE_FROM_CLASS(klass),
"color-changed", G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
0
);
} }
GtkWidget* vektor_color_wheel_new(void) { GtkWidget* vektor_color_wheel_new(void) {
@@ -354,24 +357,17 @@ GtkWidget* vektor_color_wheel_new(void) {
VektorColor vektor_color_wheel_get_color(VektorColorWheel* wheel) { VektorColor vektor_color_wheel_get_color(VektorColorWheel* wheel) {
float r, g, b; float r, g, b;
gtk_hsv_to_rgb(wheel->hue, gtk_hsv_to_rgb(wheel->hue, wheel->saturation, wheel->lightness, &r, &g, &b);
wheel->saturation,
wheel->lightness,
&r, &g, &b);
return (VektorColor) { return (VektorColor){.r = (unsigned char)(r * 255),
.r = (unsigned char)(r*255),
.g = (unsigned char)(g * 255), .g = (unsigned char)(g * 255),
.b = (unsigned char)(b * 255), .b = (unsigned char)(b * 255),
.a = 255 .a = 255};
};
} }
void vektor_color_wheel_get_colorout(VektorColorWheel* wheel, float* r, float* g, float* b) { void vektor_color_wheel_get_colorout(VektorColorWheel* wheel, float* r,
gtk_hsv_to_rgb(wheel->hue, float* g, float* b) {
wheel->saturation, gtk_hsv_to_rgb(wheel->hue, wheel->saturation, wheel->lightness, r, g, b);
wheel->lightness,
r, g, b);
} }
void vektor_color_wheel_set_color(VektorColorWheel* wheel, VektorColor c) { void vektor_color_wheel_set_color(VektorColorWheel* wheel, VektorColor c) {

View File

@@ -5,11 +5,13 @@
#include "src/util/color.h" #include "src/util/color.h"
#define VEKTOR_TYPE_COLOR_WHEEL vektor_color_wheel_get_type() #define VEKTOR_TYPE_COLOR_WHEEL vektor_color_wheel_get_type()
G_DECLARE_FINAL_TYPE(VektorColorWheel, vektor_color_wheel, VEKTOR, COLOR_WHEEL, GtkDrawingArea) G_DECLARE_FINAL_TYPE(VektorColorWheel, vektor_color_wheel, VEKTOR, COLOR_WHEEL,
GtkDrawingArea)
GtkWidget* vektor_color_wheel_new(void); GtkWidget* vektor_color_wheel_new(void);
VektorColor vektor_color_wheel_get_color(VektorColorWheel* wheel); VektorColor vektor_color_wheel_get_color(VektorColorWheel* wheel);
void vektor_color_wheel_get_colorout(VektorColorWheel* wheel, float* r, float* g, float* b); void vektor_color_wheel_get_colorout(VektorColorWheel* wheel, float* r,
float* g, float* b);
void vektor_color_wheel_set_color(VektorColorWheel* wheel, VektorColor c); void vektor_color_wheel_set_color(VektorColorWheel* wheel, VektorColor c);
#endif #endif

View File

@@ -31,6 +31,29 @@
</object> </object>
</child> </child>
<!--Another topbar for style control-->
<child>
<object class="GtkBox">
<property name="orientation">horizontal</property>
<property name="hexpand">true</property>
<child>
<object class="GtkImage">
<property name="icon-name">tool-pencil-symbolic</property>
<property name="margin-start">10</property>
</object>
</child>
<child>
<object class="GtkEntry" id="thickness_entry">
<property name="text">1</property>
<property name="margin-start">10</property>
</object>
</child>
</object>
</child>
<!--Main area below--> <!--Main area below-->
<child> <child>
<object class="GtkPaned" id="workspace_paned"> <object class="GtkPaned" id="workspace_paned">
@@ -65,6 +88,14 @@
<!--Tool buttons--> <!--Tool buttons-->
<!--Select tool-->
<child>
<object class="GtkButton" id="button_selecttool">
<property name="icon-name">tool-select-symbolic</property>
<property name="halign">start</property>
</object>
</child>
<!--Shape tool (row container)--> <!--Shape tool (row container)-->
<child> <child>
<object class="GtkBox"> <object class="GtkBox">
@@ -101,6 +132,13 @@
</object> </object>
</child> </child>
<!--Polygon tool-->
<child>
<object class="GtkButton" id="button_polygontool">
<property name="icon-name">tool-polygon-symbolic</property>
</object>
</child>
<!--Circle tool--> <!--Circle tool-->
<child> <child>
<object class="GtkButton" id="button_circletool"> <object class="GtkButton" id="button_circletool">