diff --git a/icons/hicolor/scalable/actions/tool-brush-symbolic.svg b/icons/hicolor/scalable/actions/tool-brush-symbolic.svg new file mode 100644 index 0000000..d6150fe --- /dev/null +++ b/icons/hicolor/scalable/actions/tool-brush-symbolic.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/icons/hicolor/scalable/actions/tool-pencil-symbolic.svg b/icons/hicolor/scalable/actions/tool-pencil-symbolic.svg new file mode 100644 index 0000000..10f5846 --- /dev/null +++ b/icons/hicolor/scalable/actions/tool-pencil-symbolic.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/src/application/applicationstate.c b/src/application/applicationstate.c index d906f72..6112a2b 100644 --- a/src/application/applicationstate.c +++ b/src/application/applicationstate.c @@ -47,7 +47,7 @@ static void appstate_on_color_change(VektorColorWheel* wheel, appstate->currentColor = c; if (appstate->selectedShape != NULL) { - appstate->selectedShape->base.style.stroke_color = c; + appstate->selectedShape->base->style.stroke_color = c; } // set entry fields under the color selector @@ -125,7 +125,7 @@ begin_click_dispatch: state->selectedShape = &(state->shapeBuffer->nodes[state->shapeBuffer->count - 1]); - } else if (state->selectedShape->base.primitive.kind != + } else if (state->selectedShape->base->primitive.kind != VEKTOR_POLYLINE) { // selecting a tool resets the selection, so this condition // should not happen @@ -134,13 +134,13 @@ begin_click_dispatch: goto begin_click_dispatch; // retry } - vektor_polyline_add_point(state->selectedShape->base.primitive.polyline, + vektor_polyline_add_point(state->selectedShape->base->primitive.polyline, pos); - state->selectedShape->base.bbox = - vektor_primitive_get_bbox(state->selectedShape->base.primitive); + state->selectedShape->base->bbox = + vektor_primitive_get_bbox(state->selectedShape->base->primitive); // polyline's handle count is not fixed, so we have to add them manually - vektor_shape_add_handle(&state->selectedShape->base, pos); + vektor_shape_add_handle(state->selectedShape->base, pos); } else if (state->selectedTool == VektorPolygonTool) { // create new polygon shape if none is selected @@ -158,20 +158,20 @@ begin_click_dispatch: state->selectedShape = &(state->shapeBuffer->nodes[state->shapeBuffer->count - 1]); - } else if (state->selectedShape->base.primitive.kind != + } 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, + vektor_polygon_add_point(state->selectedShape->base->primitive.polygon, pos); - state->selectedShape->base.bbox = - vektor_primitive_get_bbox(state->selectedShape->base.primitive); + 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); + vektor_shape_add_handle(state->selectedShape->base, pos); } else if (state->selectedTool == VektorCircleTool) { @@ -189,18 +189,18 @@ begin_click_dispatch: vektor_circle_free(circle); - vektor_circle_set_center(&state->selectedShape->base.primitive.circle, + vektor_circle_set_center(&state->selectedShape->base->primitive.circle, pos); - vektor_circle_set_radius(&state->selectedShape->base.primitive.circle, + vektor_circle_set_radius(&state->selectedShape->base->primitive.circle, 0.1f); - state->selectedShape->base.bbox = - vektor_primitive_get_bbox(state->selectedShape->base.primitive); + 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); + &state->selectedShape->base->primitive.circle, + &state->selectedShape->base->handles, + &state->selectedShape->base->handleCount); } else if (state->selectedTool == VektorRectangleTool) { VektorRectangle* rect = vektor_rectangle_new(); @@ -218,22 +218,22 @@ begin_click_dispatch: vektor_rectangle_free(rect); vektor_rectangle_set_start( - &state->selectedShape->base.primitive.rectangle, pos); + &state->selectedShape->base->primitive.rectangle, pos); vektor_rectangle_set_end( - &state->selectedShape->base.primitive.rectangle, + &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->primitive.rectangle, + &state->selectedShape->base->handles, + &state->selectedShape->base->handleCount); - state->selectedShape->base.bbox = - vektor_primitive_get_bbox(state->selectedShape->base.primitive); + 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); + 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 @@ -268,9 +268,9 @@ void vektor_appstate_canvas_drag_begin(GtkGestureDrag* gesture, gdouble x, // get selected shape's handles and check // if we click any of them - for (size_t i = 0; i < selectedShape->base.handleCount; i++) { + for (size_t i = 0; i < selectedShape->base->handleCount; i++) { VektorBBox bbox = - vektor_shape_get_handle_bbox(selectedShape->base.handles[i]); + vektor_shape_get_handle_bbox(selectedShape->base->handles[i]); if (vektor_bbox_isinside(bbox, position)) { // clicked inside handle state->heldHandleIndex = i; @@ -303,8 +303,8 @@ void vektor_appstate_canvas_drag_update(GtkGestureDrag* gesture, gdouble x, // 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->selectedShape->base->handles[state->heldHandleIndex] = position; + vektor_shape_handles_updated(state->selectedShape->base, &state->heldHandleIndex); vektor_canvas_geometry_changed(state->renderInfo); } diff --git a/src/core/modifier.c b/src/core/modifier.c index fe984ff..da43734 100644 --- a/src/core/modifier.c +++ b/src/core/modifier.c @@ -1,14 +1,69 @@ #include "modifier.h" +#include -VektorShapeNode vektor_shapenode_new(VektorShape shape) { +VektorShapeNode vektor_shapenode_new(VektorShape* shape) { VektorShapeNode node = (VektorShapeNode){ - .base = shape, .modifier_count = 0, .evaluated = shape}; + .base = shape, + .modifier_count = 0, + .evaluated = shape, + .base_dirty = true + }; return node; } -VektorShape* vektor_shapenode_get_evaluated(VektorShapeNode* shapeNode) { +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; + } - return &shapeNode->evaluated; } void vektor_shapenodebuf_add(VektorShapeNodeBuffer* buffer, diff --git a/src/core/modifier.h b/src/core/modifier.h index 0f17f8e..b37ad96 100644 --- a/src/core/modifier.h +++ b/src/core/modifier.h @@ -3,7 +3,10 @@ #include "src/core/primitives.h" -typedef enum { VEKTOR_MODIFIER_BEVEL } VektorModifierType; +typedef enum { + VEKTOR_MODIFIER_IDENTITY, + VEKTOR_MODIFIER_BEVEL +} VektorModifierType; typedef struct VektorModifier { VektorModifierType type; @@ -11,13 +14,14 @@ typedef struct VektorModifier { bool dirty; void* parameters; - VektorShape (*apply)(struct VektorModifier mod, VektorShape input); + VektorShape (*apply)(struct VektorModifier* mod, VektorShape* input); + VektorShape cachedEvaluatedShape; } VektorModifier; typedef struct VektorShapeNode { - VektorShape base; - VektorShape evaluated; + VektorShape* base; + VektorShape* evaluated; VektorModifier* modifiers; size_t modifier_count; @@ -31,8 +35,12 @@ typedef struct VektorShapeNodeBuffer { size_t capacity; } VektorShapeNodeBuffer; -VektorShapeNode vektor_shapenode_new(VektorShape shape); +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, diff --git a/src/core/modifiers/m_identity.c b/src/core/modifiers/m_identity.c new file mode 100644 index 0000000..f731796 --- /dev/null +++ b/src/core/modifiers/m_identity.c @@ -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 + }; +} + diff --git a/src/core/primitives.c b/src/core/primitives.c index 2744bf0..225db61 100644 --- a/src/core/primitives.c +++ b/src/core/primitives.c @@ -450,9 +450,10 @@ VektorBBox vektor_bbox_expand(VektorBBox bbox, float val) { // ------ SHAPE METHODS ------ -VektorShape vektor_shape_new(VektorPrimitive prim, VektorStyle style, +VektorShape* vektor_shape_new(VektorPrimitive prim, VektorStyle style, int z_index) { - VektorShape shape = (VektorShape){.primitive = prim, + VektorShape* shape = malloc(sizeof(VektorShape)); + *shape = (VektorShape){.primitive = prim, .style = style, .transform = m33_identity(), .z_index = z_index, @@ -464,7 +465,7 @@ VektorShape vektor_shape_new(VektorPrimitive prim, VektorStyle style, the passed value's pointer to an array of handles remains valid in the passed copy. */ - vektor_shape_create_handles(&shape); + vektor_shape_create_handles(shape); return shape; } diff --git a/src/core/primitives.h b/src/core/primitives.h index 32e919d..33b5b46 100644 --- a/src/core/primitives.h +++ b/src/core/primitives.h @@ -85,7 +85,7 @@ 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, +VektorShape* vektor_shape_new(VektorPrimitive prim, VektorStyle style, int z_index); VektorBBox vektor_polyline_get_bbox(VektorPrimitive prim); diff --git a/src/core/raster.c b/src/core/raster.c index 72f6c0d..41d29ec 100644 --- a/src/core/raster.c +++ b/src/core/raster.c @@ -74,9 +74,13 @@ void vektor_vb_rasterize(VertexBuffer* vb, VektorShapeNodeBuffer* nodebuf, double scale) { for (size_t i = 0; i < nodebuf->count; i++) { EdgeBuffer edges = {0}; - VektorPrimitive* p = &nodebuf->nodes[i].base.primitive; - VektorStyle style = nodebuf->nodes[i].base.style; - M33 transform = nodebuf->nodes[i].base.transform; + + vektor_shapenode_update(&nodebuf->nodes[i]); + VektorShape* currentShape = vektor_shapenode_get_evaluated(&nodebuf->nodes[i]); + + VektorPrimitive* p = ¤tShape->primitive; + VektorStyle style = currentShape->style; + M33 transform = currentShape->transform; switch (p->kind) { case VEKTOR_POLYLINE: diff --git a/src/ui/vektorcanvas.c b/src/ui/vektorcanvas.c index eef4f0a..21d95bc 100644 --- a/src/ui/vektorcanvas.c +++ b/src/ui/vektorcanvas.c @@ -151,8 +151,8 @@ void vektor_canvas_geometry_changed(VektorCanvasRenderInfo* renderInfo) { 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]; + 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)); @@ -162,7 +162,7 @@ void vektor_canvas_geometry_changed(VektorCanvasRenderInfo* renderInfo) { // create selection quad if a shape is selected VektorBBox bbox = - vektor_primitive_get_bbox(selectedShape->base.primitive); + vektor_primitive_get_bbox(selectedShape->base->primitive); // expand it a little so it is not inset bbox = vektor_bbox_expand(bbox, 0.03f); @@ -202,7 +202,7 @@ static gboolean render(GtkGLArea* a, GdkGLContext* ctx, // re-fetch bbox (we know a shape is selected) VektorBBox bbox = vektor_primitive_get_bbox( - (*(renderInfo->selectedShape))->base.primitive); + (*(renderInfo->selectedShape))->base->primitive); bbox = vektor_bbox_expand(bbox, 0.03f); glUseProgram(selection_shader_program); diff --git a/ui/main.ui b/ui/main.ui index 0e19809..80014cc 100644 --- a/ui/main.ui +++ b/ui/main.ui @@ -31,6 +31,29 @@ + + + + horizontal + true + + + + tool-pencil-symbolic + 10 + + + + + + 1 + 10 + + + + + +