diff --git a/src/WindowContext.zig b/src/WindowContext.zig index 1dc8c05..58695a4 100644 --- a/src/WindowContext.zig +++ b/src/WindowContext.zig @@ -12,8 +12,8 @@ pub const OpenDocument = struct { document: Document, cpu_render: CpuRenderEngine, canvas: Canvas, - /// Выбранный объект в дереве (указатель, не индекс). - selected_object: ?*Document.Object = null, + /// Выбранный объект в дереве (id объекта). + selected_object_id: ?u64 = null, pub fn init(allocator: std.mem.Allocator, self: *OpenDocument) void { const default_size = basic_models.Size_f{ .w = 800, .h = 600 }; @@ -24,7 +24,7 @@ pub const OpenDocument = struct { &self.document, (&self.cpu_render).renderEngine(), ); - self.selected_object = null; + self.selected_object_id = null; } pub fn deinit(self: *OpenDocument) void { diff --git a/src/models/Document.zig b/src/models/Document.zig index f9905aa..7ce22d6 100644 --- a/src/models/Document.zig +++ b/src/models/Document.zig @@ -9,12 +9,14 @@ const shape = @import("shape/shape.zig"); size: Size_f, allocator: std.mem.Allocator, objects: std.ArrayList(Object), +next_object_id: u64, pub fn init(allocator: std.mem.Allocator, size: Size_f) Document { return .{ .size = size, .allocator = allocator, .objects = std.ArrayList(Object).empty, + .next_object_id = 1, }; } @@ -24,14 +26,14 @@ pub fn deinit(self: *Document) void { } pub fn addObject(self: *Document, template: Object) !void { - const obj = try template.clone(self.allocator); + const obj = try template.clone(self.allocator, &self.next_object_id); try self.objects.append(self.allocator, obj); } pub fn addShape(self: *Document, parent: ?*Object, shape_kind: Object.ShapeKind) !void { const obj = try shape.createObject(self.allocator, shape_kind); if (parent) |p| { - try p.addChild(self.allocator, obj); + try p.addChild(self.allocator, obj, &self.next_object_id); } else { try self.addObject(obj); } @@ -50,6 +52,27 @@ pub fn removeObject(self: *Document, obj: *Object) bool { return false; } +/// Удаляет объект по id. Возвращает true, если объект был найден и удалён. +pub fn removeObjectById(self: *Document, obj_id: u64) bool { + for (self.objects.items, 0..) |*item, i| { + if (item.id == obj_id) { + var removed = self.objects.orderedRemove(i); + removed.deinit(self.allocator); + return true; + } + if (removeFromChildrenById(self.allocator, &item.children, obj_id)) return true; + } + return false; +} + +pub fn findObjectById(self: *Document, obj_id: u64) ?*Object { + for (self.objects.items) |*item| { + if (item.id == obj_id) return item; + if (findInChildrenById(&item.children, obj_id)) |found| return found; + } + return null; +} + fn removeFromChildren(allocator: std.mem.Allocator, children: *std.ArrayList(Object), obj: *Object) bool { for (children.items, 0..) |*item, i| { if (item == obj) { @@ -61,3 +84,23 @@ fn removeFromChildren(allocator: std.mem.Allocator, children: *std.ArrayList(Obj } return false; } + +fn removeFromChildrenById(allocator: std.mem.Allocator, children: *std.ArrayList(Object), obj_id: u64) bool { + for (children.items, 0..) |*item, i| { + if (item.id == obj_id) { + var removed = children.orderedRemove(i); + removed.deinit(allocator); + return true; + } + if (removeFromChildrenById(allocator, &item.children, obj_id)) return true; + } + return false; +} + +fn findInChildrenById(children: *std.ArrayList(Object), obj_id: u64) ?*Object { + for (children.items) |*item| { + if (item.id == obj_id) return item; + if (findInChildrenById(&item.children, obj_id)) |found| return found; + } + return null; +} diff --git a/src/models/Object.zig b/src/models/Object.zig index e73cf85..3c913e5 100644 --- a/src/models/Object.zig +++ b/src/models/Object.zig @@ -29,6 +29,7 @@ pub const defaultCommonProperties: [default_common_data.len]Property = blk: { break :blk result; }; +id: u64, shape: ShapeKind, properties: std.ArrayList(Property), children: std.ArrayList(Object), @@ -51,12 +52,12 @@ pub fn setProperty(self: *Object, allocator: std.mem.Allocator, prop: Property) return error.PropertyNotFound; } -pub fn addChild(self: *Object, allocator: std.mem.Allocator, template: Object) !void { - const obj = try template.clone(allocator); +pub fn addChild(self: *Object, allocator: std.mem.Allocator, template: Object, next_id: *u64) !void { + const obj = try template.clone(allocator, next_id); try self.children.append(allocator, obj); } -pub fn clone(self: Object, allocator: std.mem.Allocator) !Object { +pub fn clone(self: Object, allocator: std.mem.Allocator, next_id: *u64) !Object { var properties_list = std.ArrayList(Property).empty; errdefer properties_list.deinit(allocator); for (self.properties.items) |prop| { @@ -66,16 +67,23 @@ pub fn clone(self: Object, allocator: std.mem.Allocator) !Object { var children_list = std.ArrayList(Object).empty; errdefer children_list.deinit(allocator); for (self.children.items) |child| { - try children_list.append(allocator, try child.clone(allocator)); + try children_list.append(allocator, try child.clone(allocator, next_id)); } return .{ + .id = allocId(next_id), .shape = self.shape, .properties = properties_list, .children = children_list, }; } +fn allocId(next_id: *u64) u64 { + const id = next_id.*; + next_id.* += 1; + return id; +} + pub fn deinit(self: *Object, allocator: std.mem.Allocator) void { for (self.children.items) |*child| child.deinit(allocator); self.children.deinit(allocator); diff --git a/src/models/shape/shape.zig b/src/models/shape/shape.zig index c612315..d93b666 100644 --- a/src/models/shape/shape.zig +++ b/src/models/shape/shape.zig @@ -29,6 +29,7 @@ fn createWithCommonProperties(allocator: std.mem.Allocator, shape_kind: Object.S errdefer properties_list.deinit(allocator); for (defaultCommonProperties) |prop| try properties_list.append(allocator, prop); return .{ + .id = 0, .shape = shape_kind, .properties = properties_list, .children = std.ArrayList(Object).empty, diff --git a/src/random_document.zig b/src/random_document.zig index 727ece3..2f0b76f 100644 --- a/src/random_document.zig +++ b/src/random_document.zig @@ -117,7 +117,7 @@ pub fn addRandomShapes(doc: *Document, rng: std.Random) !void { if (total_count >= max_total) break; var child = try shape.createObject(allocator, randomShapeKind(rng)); try randomizeObjectProperties(allocator, &doc.size, &child, rng); - try obj.addChild(allocator, child); + try obj.addChild(allocator, child, &doc.next_object_id); total_count += 1; } for (obj.children.items[base_len..]) |*child| { diff --git a/src/ui/canvas_view.zig b/src/ui/canvas_view.zig index e3fc274..6d911c2 100644 --- a/src/ui/canvas_view.zig +++ b/src/ui/canvas_view.zig @@ -8,7 +8,7 @@ const PropertyData = @import("../models/Property.zig").Data; const Rect_i = @import("../models/basic_models.zig").Rect_i; const Tool = @import("../toolbar/Tool.zig"); -pub fn canvasView(canvas: *Canvas, selected_object: ?*Document.Object, content_rect_scale: dvui.RectScale) void { +pub fn canvasView(canvas: *Canvas, selected_object_id: ?u64, content_rect_scale: dvui.RectScale) void { var textured = dvui_ext.texturedBox(content_rect_scale, dvui.Rect.all(20)); { var overlay = dvui.overlay(@src(), .{ .expand = .both }); @@ -56,7 +56,8 @@ pub fn canvasView(canvas: *Canvas, selected_object: ?*Document.Object, content_r toolbar_box.deinit(); // Панель свойств поверх scroll (правый верхний угол) - if (selected_object) |obj| { + if (selected_object_id) |obj_id| { + if (canvas.document.findObjectById(obj_id)) |obj| { var properties_box = dvui.box( @src(), .{ .dir = .horizontal }, @@ -74,6 +75,7 @@ pub fn canvasView(canvas: *Canvas, selected_object: ?*Document.Object, content_r // Сохраняем rect панели свойств для следующего кадра — в handleCanvasMouse исключаем из него клики canvas.properties_rect_scale = properties_box.data().contentRectScale(); properties_box.deinit(); + } } dvui.label(@src(), "Canvas", .{}, .{ .gravity_x = 0.5, .gravity_y = 0.0 }); diff --git a/src/ui/left_panel.zig b/src/ui/left_panel.zig index 231d767..50569b5 100644 --- a/src/ui/left_panel.zig +++ b/src/ui/left_panel.zig @@ -11,8 +11,8 @@ const panel_radius: f32 = 24; const fill_color = dvui.Color.black.opacity(0.2); const ObjectTreeCallback = union(enum) { - select: *Object, - delete: *Object, + select: u64, + delete: u64, }; fn shapeLabel(shape: Object.ShapeKind) []const u8 { @@ -26,8 +26,8 @@ fn shapeLabel(shape: Object.ShapeKind) []const u8 { fn objectTreeRow(open_doc: *WindowContext.OpenDocument, obj: *Object, depth: u32, object_callback: *?ObjectTreeCallback) void { const indent_px = depth * 18; - const is_selected: bool = open_doc.selected_object == obj; - const row_id = @intFromPtr(obj); + const is_selected: bool = open_doc.selected_object_id == obj.id; + const row_id: usize = @intCast(obj.id); const focus_color = dvui.themeGet().focus; @@ -83,14 +83,14 @@ fn objectTreeRow(open_doc: *WindowContext.OpenDocument, obj: *Object, depth: u32 .gravity_x = 1.0, }; if (dvui.buttonIcon(@src(), "Delete object", icons.trash, .{}, .{}, delete_opts)) { - object_callback.* = .{ .delete = obj }; + object_callback.* = .{ .delete = obj.id }; } } } content.deinit(); if (select_row) { - object_callback.* = .{ .select = obj }; + object_callback.* = .{ .select = obj.id }; } } row.deinit(); @@ -113,16 +113,16 @@ fn objectTree(ctx: *WindowContext) void { } if (object_callback) |callback| { switch (callback) { - .select => |obj| { - if (open_doc.selected_object == obj) { - open_doc.selected_object = null; + .select => |obj_id| { + if (open_doc.selected_object_id == obj_id) { + open_doc.selected_object_id = null; } else { - open_doc.selected_object = obj; + open_doc.selected_object_id = obj_id; } }, - .delete => |obj| { - _ = doc.removeObject(obj); - open_doc.selected_object = null; + .delete => |obj_id| { + _ = doc.removeObjectById(obj_id); + open_doc.selected_object_id = null; open_doc.canvas.requestRedraw(); }, } diff --git a/src/ui/right_panel.zig b/src/ui/right_panel.zig index 1283113..905157a 100644 --- a/src/ui/right_panel.zig +++ b/src/ui/right_panel.zig @@ -26,7 +26,7 @@ pub fn rightPanel(ctx: *WindowContext) void { const active_doc = ctx.activeDocument(); if (active_doc) |doc| { const content_rect_scale = panel.data().contentRectScale(); - canvas_view.canvasView(&doc.canvas, doc.selected_object, content_rect_scale); + canvas_view.canvasView(&doc.canvas, doc.selected_object_id, content_rect_scale); } else { noDocView(ctx); }