Compare commits

..

3 Commits

Author SHA1 Message Date
d1722e3b6b работающее сохранение 2026-03-02 17:50:05 +03:00
37eea6ed89 Нормальный json 2026-03-02 17:37:00 +03:00
8ea5d97c2d Начало фикса json 2026-03-02 17:19:55 +03:00
14 changed files with 102 additions and 419 deletions

View File

@@ -92,7 +92,7 @@ pub fn exampleReset(self: *Canvas) !void {
} }
pub fn addRandomShapes(self: *Canvas) !void { pub fn addRandomShapes(self: *Canvas) !void {
try random_document.addRandomShapes(self.document, std.crypto.random); try random_document.addRandomShapes(self.document, self.allocator, std.crypto.random);
self.requestRedraw(); self.requestRedraw();
} }

View File

@@ -17,7 +17,7 @@ pub const OpenDocument = struct {
pub fn init(allocator: std.mem.Allocator, self: *OpenDocument) void { pub fn init(allocator: std.mem.Allocator, self: *OpenDocument) void {
const default_size = basic_models.Size_f{ .w = 800, .h = 600 }; const default_size = basic_models.Size_f{ .w = 800, .h = 600 };
self.document = Document.init(allocator, default_size); self.document = Document.init(default_size);
self.cpu_render = CpuRenderEngine.init(allocator, .Squares); self.cpu_render = CpuRenderEngine.init(allocator, .Squares);
self.canvas = Canvas.init( self.canvas = Canvas.init(
allocator, allocator,
@@ -38,8 +38,8 @@ pub const OpenDocument = struct {
self.selected_object_id = null; self.selected_object_id = null;
} }
pub fn deinit(self: *OpenDocument) void { pub fn deinit(self: *OpenDocument, allocator: std.mem.Allocator) void {
self.document.deinit(); self.document.deinit(allocator);
self.canvas.deinit(); self.canvas.deinit();
} }
}; };
@@ -60,7 +60,7 @@ pub fn init(allocator: std.mem.Allocator) !WindowContext {
pub fn deinit(self: *WindowContext) void { pub fn deinit(self: *WindowContext) void {
for (self.documents.items) |ptr| { for (self.documents.items) |ptr| {
ptr.deinit(); ptr.deinit(self.allocator);
self.allocator.destroy(ptr); self.allocator.destroy(ptr);
} }
self.documents.deinit(self.allocator); self.documents.deinit(self.allocator);
@@ -85,7 +85,7 @@ pub fn addDocument(self: *WindowContext, doc: Document) !void {
const ptr = try self.allocator.create(OpenDocument); const ptr = try self.allocator.create(OpenDocument);
errdefer self.allocator.destroy(ptr); errdefer self.allocator.destroy(ptr);
var doc_mut = doc; var doc_mut = doc;
errdefer doc_mut.deinit(); errdefer doc_mut.deinit(self.allocator);
OpenDocument.initWithDocument(self.allocator, ptr, doc_mut); OpenDocument.initWithDocument(self.allocator, ptr, doc_mut);
try self.documents.append(self.allocator, ptr); try self.documents.append(self.allocator, ptr);
self.active_document_index = self.documents.items.len - 1; self.active_document_index = self.documents.items.len - 1;
@@ -100,7 +100,7 @@ pub fn setActiveDocument(self: *WindowContext, index: usize) void {
pub fn closeDocument(self: *WindowContext, index: usize) void { pub fn closeDocument(self: *WindowContext, index: usize) void {
if (index >= self.documents.items.len) return; if (index >= self.documents.items.len) return;
const open_doc = self.documents.items[index]; const open_doc = self.documents.items[index];
open_doc.deinit(); open_doc.deinit(self.allocator);
self.allocator.destroy(open_doc); self.allocator.destroy(open_doc);
_ = self.documents.orderedRemove(index); _ = self.documents.orderedRemove(index);

View File

@@ -7,71 +7,86 @@ pub const Object = @import("Object.zig");
const shape = @import("shape/shape.zig"); const shape = @import("shape/shape.zig");
size: Size_f, size: Size_f,
allocator: std.mem.Allocator,
objects: std.ArrayList(Object), objects: std.ArrayList(Object),
next_object_id: u64, next_object_id: u64,
pub fn init(allocator: std.mem.Allocator, size: Size_f) Document { pub fn init(size: Size_f) Document {
return .{ return .{
.size = size, .size = size,
.allocator = allocator,
.objects = std.ArrayList(Object).empty, .objects = std.ArrayList(Object).empty,
.next_object_id = 1, .next_object_id = 1,
}; };
} }
pub fn deinit(self: *Document) void { pub fn deinit(self: *Document, allocator: std.mem.Allocator) void {
for (self.objects.items) |*obj| obj.deinit(self.allocator); for (self.objects.items) |*obj| obj.deinit(allocator);
self.objects.deinit(self.allocator); self.objects.deinit(allocator);
} }
pub fn addObject(self: *Document, template: Object) !void { pub fn clone(self: *const Document, allocator: std.mem.Allocator) !Document {
const obj = try template.clone(self.allocator, &self.next_object_id); var objects_list = std.ArrayList(Object).empty;
try self.objects.append(self.allocator, obj); errdefer {
for (objects_list.items) |*obj| obj.deinit(allocator);
objects_list.deinit(allocator);
}
var next_id = self.next_object_id;
for (self.objects.items) |obj| {
try objects_list.append(allocator, try obj.clone(allocator, &next_id));
}
return .{
.size = self.size,
.objects = objects_list,
.next_object_id = next_id,
};
}
pub fn addObject(self: *Document, allocator: std.mem.Allocator, template: Object) !void {
const obj = try template.clone(allocator, &self.next_object_id);
try self.objects.append(allocator, obj);
} }
/// Добавляет объект в документ: как ребёнка родителя (если id найден), иначе в корень. /// Добавляет объект в документ: как ребёнка родителя (если id найден), иначе в корень.
pub fn addObjectUnderParentId(self: *Document, parent_id: ?u64, template: Object) !void { pub fn addObjectUnderParentId(self: *Document, allocator: std.mem.Allocator, parent_id: ?u64, template: Object) !void {
if (parent_id) |id| { if (parent_id) |id| {
if (self.findObjectById(id)) |parent| { if (self.findObjectById(id)) |parent| {
try parent.addChild(self.allocator, template, &self.next_object_id); try parent.addChild(allocator, template, &self.next_object_id);
return; return;
} }
} }
try self.addObject(template); try self.addObject(allocator, template);
} }
pub fn addShape(self: *Document, parent: ?*Object, shape_kind: Object.ShapeKind) !void { pub fn addShape(self: *Document, allocator: std.mem.Allocator, parent: ?*Object, shape_kind: Object.ShapeKind) !void {
const obj = try shape.createObject(self.allocator, shape_kind); const obj = try shape.createObject(allocator, shape_kind);
if (parent) |p| { if (parent) |p| {
try p.addChild(self.allocator, obj, &self.next_object_id); try p.addChild(allocator, obj, &self.next_object_id);
} else { } else {
try self.addObject(obj); try self.addObject(allocator, obj);
} }
} }
/// Удаляет объект из документа (из корня или из детей родителя). Возвращает true, если объект был найден и удалён. /// Удаляет объект из документа (из корня или из детей родителя). Возвращает true, если объект был найден и удалён.
pub fn removeObject(self: *Document, obj: *Object) bool { pub fn removeObject(self: *Document, allocator: std.mem.Allocator, obj: *Object) bool {
for (self.objects.items, 0..) |*item, i| { for (self.objects.items, 0..) |*item, i| {
if (item == obj) { if (item == obj) {
var removed = self.objects.orderedRemove(i); var removed = self.objects.orderedRemove(i);
removed.deinit(self.allocator); removed.deinit(allocator);
return true; return true;
} }
if (removeFromChildren(self.allocator, &item.children, obj)) return true; if (removeFromChildren(allocator, &item.children, obj)) return true;
} }
return false; return false;
} }
/// Удаляет объект по id. Возвращает true, если объект был найден и удалён. /// Удаляет объект по id. Возвращает true, если объект был найден и удалён.
pub fn removeObjectById(self: *Document, obj_id: u64) bool { pub fn removeObjectById(self: *Document, allocator: std.mem.Allocator, obj_id: u64) bool {
for (self.objects.items, 0..) |*item, i| { for (self.objects.items, 0..) |*item, i| {
if (item.id == obj_id) { if (item.id == obj_id) {
var removed = self.objects.orderedRemove(i); var removed = self.objects.orderedRemove(i);
removed.deinit(self.allocator); removed.deinit(allocator);
return true; return true;
} }
if (removeFromChildrenById(self.allocator, &item.children, obj_id)) return true; if (removeFromChildrenById(allocator, &item.children, obj_id)) return true;
} }
return false; return false;
} }

View File

@@ -1,365 +0,0 @@
const std = @import("std");
const Document = @import("../models/Document.zig");
const Object = Document.Object;
const Property = @import("../models/Property.zig").Property;
const PropertyData = @import("../models/Property.zig").Data;
const basic_models = @import("../models/basic_models.zig");
const Point2_f = basic_models.Point2_f;
const Size_f = basic_models.Size_f;
const Scale2_f = basic_models.Scale2_f;
const Radii_f = basic_models.Radii_f;
pub const json_version: u32 = 1;
const JsonError = std.json.Stringify.Error;
pub fn saveToFile(doc: *const Document, path: []const u8) !void {
var file = try std.fs.cwd().createFile(path, .{ .truncate = true });
defer file.close();
var buffer: [4096]u8 = undefined;
var writer = file.writer(&buffer);
var jw: std.json.Stringify = .{
.writer = &writer.interface,
.options = .{ .whitespace = .indent_2 },
};
try writeDocument(&jw, doc);
try writer.interface.flush();
}
pub fn loadFromFile(allocator: std.mem.Allocator, path: []const u8) !Document {
var file = try std.fs.cwd().openFile(path, .{});
defer file.close();
const data = try file.readToEndAlloc(allocator, std.math.maxInt(usize));
defer allocator.free(data);
var parsed = try std.json.parseFromSlice(std.json.Value, allocator, data, .{ .parse_numbers = true });
defer parsed.deinit();
return try parseDocumentValue(allocator, parsed.value);
}
fn writeDocument(jw: *std.json.Stringify, doc: *const Document) JsonError!void {
try jw.beginObject();
try jw.objectField("version");
try jw.write(json_version);
try jw.objectField("size");
try writeSize(jw, doc.size);
try jw.objectField("next_object_id");
try jw.write(doc.next_object_id);
try jw.objectField("objects");
try writeObjectArray(jw, doc.objects.items);
try jw.endObject();
}
fn writeSize(jw: *std.json.Stringify, size: Size_f) JsonError!void {
try jw.beginObject();
try jw.objectField("w");
try jw.write(size.w);
try jw.objectField("h");
try jw.write(size.h);
try jw.endObject();
}
fn writePoint(jw: *std.json.Stringify, pt: Point2_f) JsonError!void {
try jw.beginObject();
try jw.objectField("x");
try jw.write(pt.x);
try jw.objectField("y");
try jw.write(pt.y);
try jw.endObject();
}
fn writeScale(jw: *std.json.Stringify, scale: Scale2_f) JsonError!void {
try jw.beginObject();
try jw.objectField("scale_x");
try jw.write(scale.scale_x);
try jw.objectField("scale_y");
try jw.write(scale.scale_y);
try jw.endObject();
}
fn writeRadii(jw: *std.json.Stringify, radii: Radii_f) JsonError!void {
try jw.beginObject();
try jw.objectField("x");
try jw.write(radii.x);
try jw.objectField("y");
try jw.write(radii.y);
try jw.endObject();
}
fn writeObjectArray(jw: *std.json.Stringify, objects: []const Object) JsonError!void {
try jw.beginArray();
for (objects) |obj| {
try writeObject(jw, obj);
}
try jw.endArray();
}
fn writeObject(jw: *std.json.Stringify, obj: Object) JsonError!void {
try jw.beginObject();
try jw.objectField("id");
try jw.write(obj.id);
try jw.objectField("shape");
try jw.write(@tagName(obj.shape));
try jw.objectField("properties");
try writeProperties(jw, obj.properties.items);
try jw.objectField("children");
try writeObjectArray(jw, obj.children.items);
try jw.endObject();
}
fn writeProperties(jw: *std.json.Stringify, props: []const Property) JsonError!void {
try jw.beginArray();
for (props) |prop| {
try writeProperty(jw, prop);
}
try jw.endArray();
}
fn writeProperty(jw: *std.json.Stringify, prop: Property) JsonError!void {
try jw.beginObject();
try jw.objectField("tag");
try jw.write(@tagName(prop.data));
try jw.objectField("value");
switch (prop.data) {
.position => |p| try writePoint(jw, p),
.angle => |v| try jw.write(v),
.scale => |s| try writeScale(jw, s),
.visible => |v| try jw.write(v),
.opacity => |v| try jw.write(v),
.locked => |v| try jw.write(v),
.size => |s| try writeSize(jw, s),
.radii => |r| try writeRadii(jw, r),
.end_point => |p| try writePoint(jw, p),
.points => |list| {
try jw.beginArray();
for (list.items) |pt| {
try writePoint(jw, pt);
}
try jw.endArray();
},
.fill_rgba => |v| try jw.write(v),
.stroke_rgba => |v| try jw.write(v),
.thickness => |v| try jw.write(v),
}
try jw.endObject();
}
fn parseDocumentValue(allocator: std.mem.Allocator, value: std.json.Value) !Document {
const obj = try expectObject(value);
const version_val = try getField(obj, "version");
const version = try valueToU32(version_val);
if (version != json_version) return error.UnsupportedVersion;
const size_val = try getField(obj, "size");
const size = try parseSize(size_val);
var document = Document{
.size = size,
.allocator = allocator,
.objects = std.ArrayList(Object).empty,
.next_object_id = 1,
};
errdefer document.deinit();
const objects_val = try getField(obj, "objects");
const objects_arr = try expectArray(objects_val);
for (objects_arr.items) |item| {
const obj_item = try parseObject(allocator, item);
try document.objects.append(allocator, obj_item);
}
if (obj.get("next_object_id")) |next_val| {
const next_id = try valueToU64(next_val);
document.next_object_id = next_id;
} else {
document.next_object_id = computeNextObjectId(document.objects.items);
}
return document;
}
fn parseObject(allocator: std.mem.Allocator, value: std.json.Value) !Object {
const obj = try expectObject(value);
const id = try valueToU64(try getField(obj, "id"));
const shape_str = try valueToString(try getField(obj, "shape"));
const shape = std.meta.stringToEnum(Object.ShapeKind, shape_str) orelse return error.InvalidJson;
var properties = std.ArrayList(Property).empty;
errdefer {
for (properties.items) |*p| p.deinit(allocator);
properties.deinit(allocator);
}
var children = std.ArrayList(Object).empty;
errdefer {
for (children.items) |*c| c.deinit(allocator);
children.deinit(allocator);
}
const props_val = try getField(obj, "properties");
const props_arr = try expectArray(props_val);
for (props_arr.items) |item| {
try properties.append(allocator, try parseProperty(allocator, item));
}
const children_val = try getField(obj, "children");
const children_arr = try expectArray(children_val);
for (children_arr.items) |item| {
try children.append(allocator, try parseObject(allocator, item));
}
return .{
.id = id,
.shape = shape,
.properties = properties,
.children = children,
};
}
fn parseProperty(allocator: std.mem.Allocator, value: std.json.Value) !Property {
const obj = try expectObject(value);
const tag = try valueToString(try getField(obj, "tag"));
const val = try getField(obj, "value");
const data: PropertyData = if (std.mem.eql(u8, tag, "position")) blk: {
break :blk .{ .position = try parsePoint(val) };
} else if (std.mem.eql(u8, tag, "angle")) blk: {
break :blk .{ .angle = try valueToF32(val) };
} else if (std.mem.eql(u8, tag, "scale")) blk: {
break :blk .{ .scale = try parseScale(val) };
} else if (std.mem.eql(u8, tag, "visible")) blk: {
break :blk .{ .visible = try valueToBool(val) };
} else if (std.mem.eql(u8, tag, "opacity")) blk: {
break :blk .{ .opacity = try valueToF32(val) };
} else if (std.mem.eql(u8, tag, "locked")) blk: {
break :blk .{ .locked = try valueToBool(val) };
} else if (std.mem.eql(u8, tag, "size")) blk: {
break :blk .{ .size = try parseSize(val) };
} else if (std.mem.eql(u8, tag, "radii")) blk: {
break :blk .{ .radii = try parseRadii(val) };
} else if (std.mem.eql(u8, tag, "end_point")) blk: {
break :blk .{ .end_point = try parsePoint(val) };
} else if (std.mem.eql(u8, tag, "points")) blk: {
const arr = try expectArray(val);
var list = std.ArrayList(Point2_f).empty;
errdefer list.deinit(allocator);
for (arr.items) |item| {
try list.append(allocator, try parsePoint(item));
}
break :blk .{ .points = list };
} else if (std.mem.eql(u8, tag, "fill_rgba")) blk: {
break :blk .{ .fill_rgba = try valueToU32(val) };
} else if (std.mem.eql(u8, tag, "stroke_rgba")) blk: {
break :blk .{ .stroke_rgba = try valueToU32(val) };
} else if (std.mem.eql(u8, tag, "thickness")) blk: {
break :blk .{ .thickness = try valueToF32(val) };
} else {
return error.InvalidJson;
};
return .{ .data = data };
}
fn parsePoint(value: std.json.Value) !Point2_f {
const obj = try expectObject(value);
const x = try valueToF32(try getField(obj, "x"));
const y = try valueToF32(try getField(obj, "y"));
return .{ .x = x, .y = y };
}
fn parseSize(value: std.json.Value) !Size_f {
const obj = try expectObject(value);
const w = try valueToF32(try getField(obj, "w"));
const h = try valueToF32(try getField(obj, "h"));
return .{ .w = w, .h = h };
}
fn parseScale(value: std.json.Value) !Scale2_f {
const obj = try expectObject(value);
const sx = try valueToF32(try getField(obj, "scale_x"));
const sy = try valueToF32(try getField(obj, "scale_y"));
return .{ .scale_x = sx, .scale_y = sy };
}
fn parseRadii(value: std.json.Value) !Radii_f {
const obj = try expectObject(value);
const x = try valueToF32(try getField(obj, "x"));
const y = try valueToF32(try getField(obj, "y"));
return .{ .x = x, .y = y };
}
fn expectObject(value: std.json.Value) !std.json.ObjectMap {
return switch (value) {
.object => |o| o,
else => error.InvalidJson,
};
}
fn expectArray(value: std.json.Value) !std.json.Array {
return switch (value) {
.array => |a| a,
else => error.InvalidJson,
};
}
fn getField(obj: std.json.ObjectMap, key: []const u8) !std.json.Value {
return obj.get(key) orelse error.InvalidJson;
}
fn valueToString(value: std.json.Value) ![]const u8 {
return switch (value) {
.string => |s| s,
else => error.InvalidJson,
};
}
fn valueToBool(value: std.json.Value) !bool {
return switch (value) {
.bool => |b| b,
else => error.InvalidJson,
};
}
fn valueToF32(value: std.json.Value) !f32 {
return switch (value) {
.float => |f| @floatCast(f),
.integer => |i| @floatFromInt(i),
else => error.InvalidJson,
};
}
fn valueToU32(value: std.json.Value) !u32 {
const v = switch (value) {
.integer => |i| i,
else => return error.InvalidJson,
};
if (v < 0 or v > std.math.maxInt(u32)) return error.InvalidJson;
return @intCast(v);
}
fn valueToU64(value: std.json.Value) !u64 {
const v = switch (value) {
.integer => |i| i,
else => return error.InvalidJson,
};
if (v < 0) return error.InvalidJson;
return @intCast(v);
}
fn computeNextObjectId(objects: []const Object) u64 {
var max_id: u64 = 0;
for (objects) |obj| {
max_id = @max(max_id, maxIdInObject(obj));
}
return max_id + 1;
}
fn maxIdInObject(obj: Object) u64 {
var max_id = obj.id;
for (obj.children.items) |child| {
max_id = @max(max_id, maxIdInObject(child));
}
return max_id;
}

View File

@@ -0,0 +1,33 @@
const std = @import("std");
const Document = @import("../models/Document.zig");
/// Сохраняет значение произвольного типа T в JSON-файл.
pub fn saveToFile(comptime T: type, value: *const T, path: []const u8) !void {
var file = try std.fs.cwd().createFile(path, .{ .truncate = true });
defer file.close();
var buffer: [4096]u8 = undefined;
var writer = file.writer(&buffer);
try std.json.Stringify.value(value, .{ .whitespace = .indent_2 }, &writer.interface);
try writer.interface.flush();
}
/// Загружает значение типа T из JSON-файла.
/// Для Document после разбора делается клон через allocator, т.к. парсер выделяет память
/// из арены — при закрытии документа её нельзя освобождать нашим аллокатором.
pub fn loadFromFile(comptime T: type, allocator: std.mem.Allocator, path: []const u8) !T {
var file = try std.fs.cwd().openFile(path, .{});
defer file.close();
const data = try file.readToEndAlloc(allocator, std.math.maxInt(usize));
defer allocator.free(data);
var parsed = try std.json.parseFromSlice(T, allocator, data, .{ .ignore_unknown_fields = true });
if (T == Document) {
defer parsed.deinit();
return try parsed.value.clone(allocator);
}
return parsed.value;
}

View File

@@ -90,17 +90,16 @@ fn randomizeObjectProperties(allocator: std.mem.Allocator, doc_size: *const Size
} }
/// Создаёт в документе случайные фигуры (line, ellipse, broken). /// Создаёт в документе случайные фигуры (line, ellipse, broken).
pub fn addRandomShapes(doc: *Document, rng: std.Random) !void { pub fn addRandomShapes(doc: *Document, allocator: std.mem.Allocator, rng: std.Random) !void {
const max_total: usize = 80; const max_total: usize = 80;
var total_count: usize = 0; var total_count: usize = 0;
const allocator = doc.allocator;
const n_root = rng.intRangeLessThan(usize, 6, 15); const n_root = rng.intRangeLessThan(usize, 6, 15);
for (0..n_root) |_| { for (0..n_root) |_| {
if (total_count >= max_total) break; if (total_count >= max_total) break;
var obj = try shape.createObject(allocator, randomShapeKind(rng)); var obj = try shape.createObject(allocator, randomShapeKind(rng));
try randomizeObjectProperties(allocator, &doc.size, &obj, rng); try randomizeObjectProperties(allocator, &doc.size, &obj, rng);
try doc.addObject(obj); try doc.addObject(allocator, obj);
total_count += 1; total_count += 1;
} }

View File

@@ -13,8 +13,8 @@ pub const ToolContext = struct {
pub fn addObject(self: *const ToolContext, template: Document.Object) !void { pub fn addObject(self: *const ToolContext, template: Document.Object) !void {
var obj = template; var obj = template;
const local_pos = self.computeLocalPosition(); const local_pos = self.computeLocalPosition();
try obj.setProperty(self.canvas.document.allocator, .{ .data = .{ .position = local_pos } }); try obj.setProperty(self.canvas.allocator, .{ .data = .{ .position = local_pos } });
try self.canvas.document.addObjectUnderParentId(self.selected_object_id, obj); try self.canvas.document.addObjectUnderParentId(self.canvas.allocator, self.selected_object_id, obj);
self.canvas.requestRedraw(); self.canvas.requestRedraw();
} }

View File

@@ -3,7 +3,7 @@ const shape = @import("../../models/shape/shape.zig");
fn onCanvasClick(ctx: *const Tool.ToolContext) !void { fn onCanvasClick(ctx: *const Tool.ToolContext) !void {
const canvas = ctx.canvas; const canvas = ctx.canvas;
var obj = shape.createObject(canvas.document.allocator, .arc) catch return; var obj = shape.createObject(canvas.allocator, .arc) catch return;
defer obj.deinit(canvas.allocator); defer obj.deinit(canvas.allocator);
try ctx.addObject(obj); try ctx.addObject(obj);
} }

View File

@@ -3,7 +3,7 @@ const shape = @import("../../models/shape/shape.zig");
fn onCanvasClick(ctx: *const Tool.ToolContext) !void { fn onCanvasClick(ctx: *const Tool.ToolContext) !void {
const canvas = ctx.canvas; const canvas = ctx.canvas;
var obj = shape.createObject(canvas.document.allocator, .broken) catch return; var obj = shape.createObject(canvas.allocator, .broken) catch return;
defer obj.deinit(canvas.allocator); defer obj.deinit(canvas.allocator);
try ctx.addObject(obj); try ctx.addObject(obj);
} }

View File

@@ -3,7 +3,7 @@ const shape = @import("../../models/shape/shape.zig");
fn onCanvasClick(ctx: *const Tool.ToolContext) !void { fn onCanvasClick(ctx: *const Tool.ToolContext) !void {
const canvas = ctx.canvas; const canvas = ctx.canvas;
var obj = shape.createObject(canvas.document.allocator, .ellipse) catch return; var obj = shape.createObject(canvas.allocator, .ellipse) catch return;
defer obj.deinit(canvas.allocator); defer obj.deinit(canvas.allocator);
try ctx.addObject(obj); try ctx.addObject(obj);
} }

View File

@@ -5,7 +5,7 @@ const shape = @import("../../models/shape/shape.zig");
fn onCanvasClick(ctx: *const Tool.ToolContext) !void { fn onCanvasClick(ctx: *const Tool.ToolContext) !void {
const canvas = ctx.canvas; const canvas = ctx.canvas;
var obj = shape.createObject(canvas.document.allocator, .line) catch return; var obj = shape.createObject(canvas.allocator, .line) catch return;
defer obj.deinit(canvas.allocator); defer obj.deinit(canvas.allocator);
try ctx.addObject(obj); try ctx.addObject(obj);
} }

View File

@@ -417,7 +417,7 @@ fn drawPropertyEditor(canvas: *Canvas, obj: *Document.Object, prop: *const Prope
changed = res.changed or changed; changed = res.changed or changed;
} }
if (changed) { if (changed) {
obj.setProperty(canvas.document.allocator, .{ .data = .{ .position = next } }) catch {}; obj.setProperty(canvas.allocator, .{ .data = .{ .position = next } }) catch {};
canvas.requestRedraw(); canvas.requestRedraw();
} }
}, },
@@ -431,7 +431,7 @@ fn drawPropertyEditor(canvas: *Canvas, obj: *Document.Object, prop: *const Prope
subrow.deinit(); subrow.deinit();
if (res.changed) { if (res.changed) {
next = degrees * std.math.pi / 180.0; next = degrees * std.math.pi / 180.0;
obj.setProperty(canvas.document.allocator, .{ .data = .{ .angle = next } }) catch {}; obj.setProperty(canvas.allocator, .{ .data = .{ .angle = next } }) catch {};
canvas.requestRedraw(); canvas.requestRedraw();
} }
} }
@@ -456,28 +456,28 @@ fn drawPropertyEditor(canvas: *Canvas, obj: *Document.Object, prop: *const Prope
changed = res.changed or changed; changed = res.changed or changed;
} }
if (changed) { if (changed) {
obj.setProperty(canvas.document.allocator, .{ .data = .{ .scale = next } }) catch {}; obj.setProperty(canvas.allocator, .{ .data = .{ .scale = next } }) catch {};
canvas.requestRedraw(); canvas.requestRedraw();
} }
}, },
.visible => |v| { .visible => |v| {
var next = v; var next = v;
if (dvui.checkbox(@src(), &next, "Visible", .{})) { if (dvui.checkbox(@src(), &next, "Visible", .{})) {
obj.setProperty(canvas.document.allocator, .{ .data = .{ .visible = next } }) catch {}; obj.setProperty(canvas.allocator, .{ .data = .{ .visible = next } }) catch {};
canvas.requestRedraw(); canvas.requestRedraw();
} }
}, },
.opacity => |opacity| { .opacity => |opacity| {
var next = opacity; var next = opacity;
if (dvui.sliderEntry(@src(), "{d:0.2}", .{ .value = &next, .min = 0.0, .max = 1.0, .interval = 0.01 }, .{ .expand = .horizontal })) { if (dvui.sliderEntry(@src(), "{d:0.2}", .{ .value = &next, .min = 0.0, .max = 1.0, .interval = 0.01 }, .{ .expand = .horizontal })) {
obj.setProperty(canvas.document.allocator, .{ .data = .{ .opacity = next } }) catch {}; obj.setProperty(canvas.allocator, .{ .data = .{ .opacity = next } }) catch {};
canvas.requestRedraw(); canvas.requestRedraw();
} }
}, },
.locked => |v| { .locked => |v| {
var next = v; var next = v;
if (dvui.checkbox(@src(), &next, "Locked", .{})) { if (dvui.checkbox(@src(), &next, "Locked", .{})) {
obj.setProperty(canvas.document.allocator, .{ .data = .{ .locked = next } }) catch {}; obj.setProperty(canvas.allocator, .{ .data = .{ .locked = next } }) catch {};
canvas.requestRedraw(); canvas.requestRedraw();
} }
}, },
@@ -502,7 +502,7 @@ fn drawPropertyEditor(canvas: *Canvas, obj: *Document.Object, prop: *const Prope
changed = res.changed or changed; changed = res.changed or changed;
} }
if (changed) { if (changed) {
obj.setProperty(canvas.document.allocator, .{ .data = .{ .size = next } }) catch {}; obj.setProperty(canvas.allocator, .{ .data = .{ .size = next } }) catch {};
canvas.requestRedraw(); canvas.requestRedraw();
} }
}, },
@@ -527,7 +527,7 @@ fn drawPropertyEditor(canvas: *Canvas, obj: *Document.Object, prop: *const Prope
changed = res.changed or changed; changed = res.changed or changed;
} }
if (changed) { if (changed) {
obj.setProperty(canvas.document.allocator, .{ .data = .{ .radii = next } }) catch {}; obj.setProperty(canvas.allocator, .{ .data = .{ .radii = next } }) catch {};
canvas.requestRedraw(); canvas.requestRedraw();
} }
}, },
@@ -556,7 +556,7 @@ fn drawPropertyEditor(canvas: *Canvas, obj: *Document.Object, prop: *const Prope
changed = res.changed or changed; changed = res.changed or changed;
} }
if (changed) { if (changed) {
obj.setProperty(canvas.document.allocator, .{ .data = .{ .end_point = next } }) catch {}; obj.setProperty(canvas.allocator, .{ .data = .{ .end_point = next } }) catch {};
canvas.requestRedraw(); canvas.requestRedraw();
} }
}, },
@@ -578,7 +578,7 @@ fn drawPropertyEditor(canvas: *Canvas, obj: *Document.Object, prop: *const Prope
const res = dvui.textEntryNumber(@src(), T, .{ .value = &next, .min = @as(T, 0.0), .max = @as(T, 100.0) }, .{ .expand = .horizontal }); const res = dvui.textEntryNumber(@src(), T, .{ .value = &next, .min = @as(T, 0.0), .max = @as(T, 100.0) }, .{ .expand = .horizontal });
subrow.deinit(); subrow.deinit();
if (res.changed) { if (res.changed) {
obj.setProperty(canvas.document.allocator, .{ .data = .{ .thickness = next } }) catch {}; obj.setProperty(canvas.allocator, .{ .data = .{ .thickness = next } }) catch {};
canvas.requestRedraw(); canvas.requestRedraw();
} }
} }
@@ -597,9 +597,9 @@ fn drawColorEditor(canvas: *Canvas, obj: *Document.Object, rgba: u32, is_fill: b
)) { )) {
const next = colorToRgba(hsv.toColor()); const next = colorToRgba(hsv.toColor());
if (is_fill) { if (is_fill) {
obj.setProperty(canvas.document.allocator, .{ .data = .{ .fill_rgba = next } }) catch {}; obj.setProperty(canvas.allocator, .{ .data = .{ .fill_rgba = next } }) catch {};
} else { } else {
obj.setProperty(canvas.document.allocator, .{ .data = .{ .stroke_rgba = next } }) catch {}; obj.setProperty(canvas.allocator, .{ .data = .{ .stroke_rgba = next } }) catch {};
} }
canvas.requestRedraw(); canvas.requestRedraw();
} }

View File

@@ -122,7 +122,7 @@ fn objectTree(ctx: *WindowContext) void {
} }
}, },
.delete => |obj_id| { .delete => |obj_id| {
_ = doc.removeObjectById(obj_id); _ = doc.removeObjectById(ctx.allocator, obj_id);
if (open_doc.selected_object_id == obj_id) if (open_doc.selected_object_id == obj_id)
open_doc.selected_object_id = null; open_doc.selected_object_id = null;
open_doc.canvas.requestRedraw(); open_doc.canvas.requestRedraw();

View File

@@ -1,7 +1,8 @@
const std = @import("std"); const std = @import("std");
const dvui = @import("dvui"); const dvui = @import("dvui");
const WindowContext = @import("../WindowContext.zig"); const WindowContext = @import("../WindowContext.zig");
const document_json = @import("../persistence/document_json.zig"); const Document = @import("../models/Document.zig");
const json_io = @import("../persistence/json_io.zig");
pub fn menuBar(ctx: *WindowContext) void { pub fn menuBar(ctx: *WindowContext) void {
var m = dvui.menu(@src(), .horizontal, .{ .background = true, .expand = .horizontal }); var m = dvui.menu(@src(), .horizontal, .{ .background = true, .expand = .horizontal });
@@ -35,7 +36,7 @@ fn openDocumentDialog(ctx: *WindowContext) void {
defer ctx.allocator.free(path_z); defer ctx.allocator.free(path_z);
const path = path_z[0..path_z.len]; const path = path_z[0..path_z.len];
const doc = document_json.loadFromFile(ctx.allocator, path) catch |err| { const doc = json_io.loadFromFile(Document, ctx.allocator, path) catch |err| {
std.debug.print("Open file error: {}\n", .{err}); std.debug.print("Open file error: {}\n", .{err});
return; return;
}; };
@@ -58,7 +59,7 @@ fn saveAsDialog(ctx: *WindowContext) void {
defer ctx.allocator.free(path_z); defer ctx.allocator.free(path_z);
const path_raw = path_z[0..path_z.len]; const path_raw = path_z[0..path_z.len];
document_json.saveToFile(&open_doc.document, path_raw) catch |err| { json_io.saveToFile(Document, &open_doc.document, path_raw) catch |err| {
std.debug.print("Save file error: {}\n", .{err}); std.debug.print("Save file error: {}\n", .{err});
return; return;
}; };