diff --git a/src/models/Object.zig b/src/models/Object.zig index 2a35118..959b6cd 100644 --- a/src/models/Object.zig +++ b/src/models/Object.zig @@ -44,7 +44,7 @@ pub fn getProperty(self: Object, tag: std.meta.Tag(PropertyData)) ?*const Proper pub fn setProperty(self: *Object, allocator: std.mem.Allocator, prop: Property) !void { for (self.properties.items, 0..) |*p, i| { if (std.meta.activeTag(p.data) == std.meta.activeTag(prop.data)) { - if (p.data == .points) p.data.points.deinit(allocator); + if (p.data == .points) allocator.free(p.data.points); self.properties.items[i] = prop; return; } diff --git a/src/models/Property.zig b/src/models/Property.zig index e3b2fd0..8404742 100644 --- a/src/models/Property.zig +++ b/src/models/Property.zig @@ -19,7 +19,8 @@ pub const Data = union(enum) { arc_percent: f32, end_point: Point2_f, - points: std.ArrayList(Point2_f), + /// Владеет памятью; при deinit/clone — free/duplicate. + points: []const Point2_f, /// Замкнутый контур (для ломаной: отрезок последняя–первая точка + заливка). closed: bool, @@ -39,7 +40,7 @@ pub const Property = struct { pub fn deinit(self: *Property, allocator: std.mem.Allocator) void { switch (self.data) { - .points => |*list| list.deinit(allocator), + .points => |slice| allocator.free(slice), else => {}, } self.* = undefined; @@ -47,9 +48,13 @@ pub const Property = struct { pub fn clone(self: Property, allocator: std.mem.Allocator) !Property { return switch (self.data) { - .points => |list| .{ + .points => |slice| .{ .data = .{ - .points = try list.clone(allocator), + .points = blk: { + const copy = try allocator.alloc(Point2_f, slice.len); + @memcpy(copy, slice); + break :blk copy; + }, }, }, else => .{ .data = self.data }, diff --git a/src/models/shape/broken.zig b/src/models/shape/broken.zig index a3aaac5..70d88ea 100644 --- a/src/models/shape/broken.zig +++ b/src/models/shape/broken.zig @@ -4,42 +4,35 @@ const Property = @import("../Property.zig").Property; const PropertyData = @import("../Property.zig").Data; const Point2_f = @import("../basic_models.zig").Point2_f; const Rect_f = @import("../basic_models.zig").Rect_f; +const common = @import("common.zig"); const shape_mod = @import("shape.zig"); -/// Точки ломаной по умолчанию. -pub const default_points = [_]Point2_f{ +/// Свойства фигуры по умолчанию (добавляются к общим). points — слайс на статический массив. +pub const default_shape_properties_points = [_]Point2_f{ .{ .x = 0, .y = 0 }, .{ .x = 80, .y = 0 }, .{ .x = 80, .y = 60 }, }; +pub const default_shape_properties = [_]Property{ + .{ .data = .{ .points = &default_shape_properties_points } }, + .{ .data = .{ .closed = false } }, + .{ .data = .{ .filled = true } }, + .{ .data = .{ .fill_rgba = 0x000000FF } }, +}; -/// Теги обязательных свойств. -pub fn getRequiredTags() []const std.meta.Tag(PropertyData) { - return &[_]std.meta.Tag(PropertyData){ - .points, - }; -} - -/// Добавляет к объекту свойства по умолчанию для ломаной. -pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object) !void { - var points = std.ArrayList(Point2_f).empty; - try points.appendSlice(allocator, &default_points); - try obj.properties.append(allocator, .{ .data = .{ .points = points } }); - try obj.properties.append(allocator, .{ .data = .{ .closed = false } }); - try obj.properties.append(allocator, .{ .data = .{ .filled = true } }); - try obj.properties.append(allocator, .{ .data = .{ .fill_rgba = obj.getProperty(.stroke_rgba).?.stroke_rgba } }); -} +/// Теги обязательных свойств = теги из default_shape_properties. +pub const required_tags = common.tagsFromProperties(&default_shape_properties); /// Локальные границы: AABB по точкам. pub fn getLocalBounds(obj: *const Object) !Rect_f { try shape_mod.ensure(obj, .broken); const p = obj.getProperty(.points).?; - if (p.points.items.len == 0) return error.EmptyPoints; - var min_x: f32 = p.points.items[0].x; + if (p.points.len == 0) return error.EmptyPoints; + var min_x: f32 = p.points[0].x; var max_x: f32 = min_x; - var min_y: f32 = p.points.items[0].y; + var min_y: f32 = p.points[0].y; var max_y: f32 = min_y; - for (p.points.items[1..]) |pt| { + for (p.points[1..]) |pt| { min_x = @min(min_x, pt.x); max_x = @max(max_x, pt.x); min_y = @min(min_y, pt.y); diff --git a/src/models/shape/common.zig b/src/models/shape/common.zig new file mode 100644 index 0000000..32b9181 --- /dev/null +++ b/src/models/shape/common.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const Property = @import("../Property.zig").Property; +const PropertyData = @import("../Property.zig").Data; + +/// Теги свойств — те же, что фигура добавляет к стандартным (из default_shape_properties). +pub fn tagsFromProperties(comptime props: []const Property) [props.len]std.meta.Tag(PropertyData) { + var result: [props.len]std.meta.Tag(PropertyData) = undefined; + for (props, &result) |p, *r| r.* = std.meta.activeTag(p.data); + return result; +} diff --git a/src/models/shape/ellipse.zig b/src/models/shape/ellipse.zig index fc1a192..92e5479 100644 --- a/src/models/shape/ellipse.zig +++ b/src/models/shape/ellipse.zig @@ -1,11 +1,11 @@ const std = @import("std"); const Object = @import("../Object.zig"); const Property = @import("../Property.zig").Property; -const PropertyData = @import("../Property.zig").Data; const Rect_f = @import("../basic_models.zig").Rect_f; +const common = @import("common.zig"); const shape_mod = @import("shape.zig"); -/// Свойства фигуры по умолчанию. +/// Свойства фигуры по умолчанию (добавляются к общим). pub const default_shape_properties = [_]Property{ .{ .data = .{ .radii = .{ .x = 50, .y = 50 } } }, .{ .data = .{ .arc_percent = 100.0 } }, @@ -14,15 +14,8 @@ pub const default_shape_properties = [_]Property{ .{ .data = .{ .fill_rgba = 0x000000FF } }, }; -/// Теги обязательных свойств. -pub fn getRequiredTags() []const std.meta.Tag(PropertyData) { - return &([_]std.meta.Tag(PropertyData){std.meta.activeTag(default_shape_properties[0].data)}); -} - -/// Добавляет к объекту свойства по умолчанию для эллипса. -pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object) !void { - for (default_shape_properties) |prop| try obj.properties.append(allocator, prop); -} +/// Теги обязательных свойств = теги из default_shape_properties. +pub const required_tags = common.tagsFromProperties(&default_shape_properties); /// Локальные границы эллипса: [-radii.x, -radii.y] .. [radii.x, radii.y]. pub fn getLocalBounds(obj: *const Object) !Rect_f { diff --git a/src/models/shape/line.zig b/src/models/shape/line.zig index 7166b04..fe9a289 100644 --- a/src/models/shape/line.zig +++ b/src/models/shape/line.zig @@ -1,24 +1,17 @@ const std = @import("std"); const Object = @import("../Object.zig"); const Property = @import("../Property.zig").Property; -const PropertyData = @import("../Property.zig").Data; const Rect_f = @import("../basic_models.zig").Rect_f; +const common = @import("common.zig"); const shape_mod = @import("shape.zig"); -/// Свойства фигуры по умолчанию. +/// Свойства фигуры по умолчанию (добавляются к общим). pub const default_shape_properties = [_]Property{ .{ .data = .{ .end_point = .{ .x = 100, .y = 200 } } }, }; -/// Теги обязательных свойств. -pub fn getRequiredTags() []const std.meta.Tag(PropertyData) { - return &([_]std.meta.Tag(PropertyData){std.meta.activeTag(default_shape_properties[0].data)}); -} - -/// Добавляет к объекту свойства по умолчанию для линии. -pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object) !void { - for (default_shape_properties) |prop| try obj.properties.append(allocator, prop); -} +/// Теги обязательных свойств = теги из default_shape_properties. +pub const required_tags = common.tagsFromProperties(&default_shape_properties); /// Локальные границы: от (0,0) до end_point. pub fn getLocalBounds(obj: *const Object) !Rect_f { diff --git a/src/models/shape/shape.zig b/src/models/shape/shape.zig index d350458..aff5905 100644 --- a/src/models/shape/shape.zig +++ b/src/models/shape/shape.zig @@ -4,19 +4,37 @@ const Property = @import("../Property.zig").Property; const PropertyData = @import("../Property.zig").Data; const defaultCommonProperties = Object.defaultCommonProperties; const basic_models = @import("../basic_models.zig"); +const Point2_f = basic_models.Point2_f; const line = @import("line.zig"); const ellipse = @import("ellipse.zig"); const broken = @import("broken.zig"); -pub const Rect = basic_models.Rectf; +pub const Rect = basic_models.Rect_f; + +/// Добавляет к объекту список свойств фигуры. Для .points дублирует слайс (объект владеет). +fn appendShapeProperties(allocator: std.mem.Allocator, obj: *Object, props: []const Property) !void { + for (props) |prop| { + if (prop.data == .points) { + const pts = prop.data.points; + const copy = try allocator.alloc(Point2_f, pts.len); + @memcpy(copy, pts); + try obj.properties.append(allocator, .{ .data = .{ .points = copy } }); + } else { + try obj.properties.append(allocator, prop); + } + } +} /// Создаёт объект с дефолтными общими и фигурными свойствами. pub fn createObject(allocator: std.mem.Allocator, shape_kind: Object.ShapeKind) !Object { var obj = try createWithCommonProperties(allocator, shape_kind); errdefer obj.deinit(allocator); switch (shape_kind) { - .line => try line.appendDefaultShapeProperties(allocator, &obj), - .ellipse => try ellipse.appendDefaultShapeProperties(allocator, &obj), - .broken => try broken.appendDefaultShapeProperties(allocator, &obj), + .line => try appendShapeProperties(allocator, &obj, &line.default_shape_properties), + .ellipse => try appendShapeProperties(allocator, &obj, &ellipse.default_shape_properties), + .broken => { + try appendShapeProperties(allocator, &obj, &broken.default_shape_properties); + try obj.setProperty(allocator, .{ .data = .{ .fill_rgba = obj.getProperty(.stroke_rgba).?.stroke_rgba } }); + }, } return obj; } @@ -42,11 +60,12 @@ pub fn ensure(obj: *const Object, expected_kind: Object.ShapeKind) !void { } } +/// Обязательные теги = те свойства, которые фигура добавляет к общим (из default_shape_properties). fn requiredTagsFor(kind: Object.ShapeKind) []const std.meta.Tag(PropertyData) { return switch (kind) { - .line => line.getRequiredTags(), - .ellipse => ellipse.getRequiredTags(), - .broken => broken.getRequiredTags(), + .line => &line.required_tags, + .ellipse => &ellipse.required_tags, + .broken => &broken.required_tags, }; } @@ -71,7 +90,7 @@ test "getLocalBounds" { try std.testing.expect(line_bounds.x == 0); try std.testing.expect(line_bounds.y == 0); try std.testing.expect(line_bounds.w == 100); - try std.testing.expect(line_bounds.h == 0); + try std.testing.expect(line_bounds.h == 200); var ellipse_obj = try shape.createObject(allocator, .ellipse); defer ellipse_obj.deinit(allocator); diff --git a/src/random_document.zig b/src/random_document.zig index e59d2a4..edc60e0 100644 --- a/src/random_document.zig +++ b/src/random_document.zig @@ -74,16 +74,19 @@ fn randomizeObjectProperties(allocator: std.mem.Allocator, doc_size: *const Size } }); }, .broken => { - var points = std.ArrayList(Point2_f).empty; + var list = std.ArrayList(Point2_f).empty; + defer list.deinit(allocator); const n = rng.intRangeLessThan(usize, 2, 9); var x: f32 = 0; var y: f32 = 0; for (0..n) |_| { - try points.append(allocator, .{ .x = x, .y = y }); + try list.append(allocator, .{ .x = x, .y = y }); x += randFloat(rng, -40, 80); y += randFloat(rng, -30, 60); } - try obj.setProperty(allocator, .{ .data = .{ .points = points } }); + const slice = try allocator.alloc(Point2_f, list.items.len); + @memcpy(slice, list.items); + try obj.setProperty(allocator, .{ .data = .{ .points = slice } }); }, } } diff --git a/src/render/cpu/broken.zig b/src/render/cpu/broken.zig index dcbc2fa..e2d2e7d 100644 --- a/src/render/cpu/broken.zig +++ b/src/render/cpu/broken.zig @@ -17,7 +17,7 @@ pub fn draw( allocator: std.mem.Allocator, ) !void { const p_prop = obj.getProperty(.points) orelse return; - const pts = p_prop.points.items; + const pts = p_prop.points; if (pts.len < 2) return; const stroke = if (obj.getProperty(.stroke_rgba)) |s| pipeline.rgbaToPma(s.stroke_rgba) else default_stroke; const thickness = if (obj.getProperty(.thickness)) |t| t.thickness else default_thickness; diff --git a/src/ui/canvas_view.zig b/src/ui/canvas_view.zig index f8e7646..76d86de 100644 --- a/src/ui/canvas_view.zig +++ b/src/ui/canvas_view.zig @@ -6,6 +6,7 @@ const Document = @import("../models/Document.zig"); const Property = @import("../models/Property.zig").Property; const PropertyData = @import("../models/Property.zig").Data; const Rect_i = @import("../models/basic_models.zig").Rect_i; +const Point2_f = @import("../models/basic_models.zig").Point2_f; const Tool = @import("../toolbar/Tool.zig"); const RenderStats = @import("../render/RenderStats.zig"); const icons = @import("../icons.zig"); @@ -564,10 +565,12 @@ fn drawPropertyEditor(canvas: *Canvas, obj: *Document.Object, prop: *const Prope } }, .points => |points| { - var list = points.clone(canvas.allocator) catch { - dvui.label(@src(), "Points: {d}", .{points.items.len}, .{}); + var list = std.ArrayList(Point2_f).empty; + list.appendSlice(canvas.allocator, points) catch { + dvui.label(@src(), "Points: {d}", .{points.len}, .{}); return; }; + defer list.deinit(canvas.allocator); dvui.label(@src(), "Points: {d}", .{list.items.len}, .{}); var changed = false; @@ -677,13 +680,13 @@ fn drawPropertyEditor(canvas: *Canvas, obj: *Document.Object, prop: *const Prope } if (changed) { - obj.setProperty(canvas.allocator, .{ .data = .{ .points = list } }) catch { - list.deinit(canvas.allocator); + const slice = canvas.allocator.alloc(Point2_f, list.items.len) catch return; + @memcpy(slice, list.items); + obj.setProperty(canvas.allocator, .{ .data = .{ .points = slice } }) catch { + canvas.allocator.free(slice); return; }; canvas.requestRedraw(); - } else { - list.deinit(canvas.allocator); } }, .fill_rgba => |rgba| {