diff --git a/src/models/Object.zig b/src/models/Object.zig index 3c913e5..fe48e00 100644 --- a/src/models/Object.zig +++ b/src/models/Object.zig @@ -41,6 +41,7 @@ pub fn getProperty(self: Object, tag: std.meta.Tag(PropertyData)) ?*const Proper return null; } +/// Забирает владение Property 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)) { diff --git a/src/ui/canvas_view.zig b/src/ui/canvas_view.zig index a10259e..0e0bf25 100644 --- a/src/ui/canvas_view.zig +++ b/src/ui/canvas_view.zig @@ -8,6 +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"); const RenderStats = @import("../render/RenderStats.zig"); +const icons = @import("../icons.zig"); 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)); @@ -66,7 +67,10 @@ pub fn canvasView(canvas: *Canvas, selected_object_id: ?u64, content_rect_scale: var properties_box = dvui.box( @src(), .{ .dir = .horizontal }, - .{}, + .{ + .gravity_x = 1.0, + .gravity_y = 0.0, + }, ); { drawPropertiesPanel(canvas, obj); @@ -307,13 +311,12 @@ fn drawPropertiesPanel(canvas: *Canvas, selected_object: *Document.Object) void @src(), .{ .dir = .vertical }, .{ - .gravity_x = 1.0, - .gravity_y = 0.0, .padding = dvui.Rect.all(8), .corner_radius = dvui.Rect.all(8), .background = true, .color_fill = dvui.Color.black.opacity(0.2), - .min_size_content = .{ .w = 220 }, + .min_size_content = .width(300), + .max_size_content = .width(300), .margin = dvui.Rect{ .w = 32, .y = 16, .h = 100 }, }, ); @@ -394,17 +397,12 @@ fn drawPropertyEditor(canvas: *Canvas, obj: *Document.Object, prop: *const Prope switch (prop.data) { .position => |pos| { var next = pos; - const doc = canvas.document; - const min_x = -doc.size.w; - const max_x = doc.size.w; - const min_y = -doc.size.h; - const max_y = doc.size.h; var changed = false; { var subrow = dvui.box(@src(), .{ .dir = .horizontal }, .{ .expand = .horizontal }); dvui.labelNoFmt(@src(), "x:", .{}, .{}); const T = @TypeOf(next.x); - const res = dvui.textEntryNumber(@src(), T, .{ .value = &next.x, .min = @as(T, min_x), .max = @as(T, max_x) }, .{ .expand = .horizontal }); + const res = dvui.textEntryNumber(@src(), T, .{ .value = &next.x }, .{ .expand = .horizontal }); subrow.deinit(); changed = res.changed or changed; } @@ -412,7 +410,7 @@ fn drawPropertyEditor(canvas: *Canvas, obj: *Document.Object, prop: *const Prope var subrow = dvui.box(@src(), .{ .dir = .horizontal }, .{ .expand = .horizontal }); dvui.labelNoFmt(@src(), "y:", .{}, .{}); const T = @TypeOf(next.y); - const res = dvui.textEntryNumber(@src(), T, .{ .value = &next.y, .min = @as(T, min_y), .max = @as(T, max_y) }, .{ .expand = .horizontal }); + const res = dvui.textEntryNumber(@src(), T, .{ .value = &next.y }, .{ .expand = .horizontal }); subrow.deinit(); changed = res.changed or changed; } @@ -483,13 +481,12 @@ fn drawPropertyEditor(canvas: *Canvas, obj: *Document.Object, prop: *const Prope }, .size => |size| { var next = size; - const doc = canvas.document; var changed = false; { var subrow = dvui.box(@src(), .{ .dir = .horizontal }, .{ .expand = .horizontal }); dvui.labelNoFmt(@src(), "w:", .{}, .{}); const T = @TypeOf(next.w); - const res = dvui.textEntryNumber(@src(), T, .{ .value = &next.w, .min = @as(T, 0.0), .max = @as(T, doc.size.w) }, .{ .expand = .horizontal }); + const res = dvui.textEntryNumber(@src(), T, .{ .value = &next.w, .min = @as(T, 0.0) }, .{ .expand = .horizontal }); subrow.deinit(); changed = res.changed or changed; } @@ -497,7 +494,7 @@ fn drawPropertyEditor(canvas: *Canvas, obj: *Document.Object, prop: *const Prope var subrow = dvui.box(@src(), .{ .dir = .horizontal }, .{ .expand = .horizontal }); dvui.labelNoFmt(@src(), "h:", .{}, .{}); const T = @TypeOf(next.h); - const res = dvui.textEntryNumber(@src(), T, .{ .value = &next.h, .min = @as(T, 0.0), .max = @as(T, doc.size.h) }, .{ .expand = .horizontal }); + const res = dvui.textEntryNumber(@src(), T, .{ .value = &next.h, .min = @as(T, 0.0) }, .{ .expand = .horizontal }); subrow.deinit(); changed = res.changed or changed; } @@ -508,13 +505,12 @@ fn drawPropertyEditor(canvas: *Canvas, obj: *Document.Object, prop: *const Prope }, .radii => |radii| { var next = radii; - const doc = canvas.document; var changed = false; { var subrow = dvui.box(@src(), .{ .dir = .horizontal }, .{ .expand = .horizontal }); dvui.labelNoFmt(@src(), "x:", .{}, .{}); const T = @TypeOf(next.x); - const res = dvui.textEntryNumber(@src(), T, .{ .value = &next.x, .min = @as(T, 0.0), .max = @as(T, doc.size.w) }, .{ .expand = .horizontal }); + const res = dvui.textEntryNumber(@src(), T, .{ .value = &next.x, .min = @as(T, 0.0) }, .{ .expand = .horizontal }); subrow.deinit(); changed = res.changed or changed; } @@ -522,7 +518,7 @@ fn drawPropertyEditor(canvas: *Canvas, obj: *Document.Object, prop: *const Prope var subrow = dvui.box(@src(), .{ .dir = .horizontal }, .{ .expand = .horizontal }); dvui.labelNoFmt(@src(), "y:", .{}, .{}); const T = @TypeOf(next.y); - const res = dvui.textEntryNumber(@src(), T, .{ .value = &next.y, .min = @as(T, 0.0), .max = @as(T, doc.size.h) }, .{ .expand = .horizontal }); + const res = dvui.textEntryNumber(@src(), T, .{ .value = &next.y, .min = @as(T, 0.0) }, .{ .expand = .horizontal }); subrow.deinit(); changed = res.changed or changed; } @@ -533,17 +529,12 @@ fn drawPropertyEditor(canvas: *Canvas, obj: *Document.Object, prop: *const Prope }, .end_point => |pt| { var next = pt; - const doc = canvas.document; - const min_x = -doc.size.w; - const max_x = doc.size.w; - const min_y = -doc.size.h; - const max_y = doc.size.h; var changed = false; { var subrow = dvui.box(@src(), .{ .dir = .horizontal }, .{ .expand = .horizontal }); dvui.labelNoFmt(@src(), "x:", .{}, .{}); const T = @TypeOf(next.x); - const res = dvui.textEntryNumber(@src(), T, .{ .value = &next.x, .min = @as(T, min_x), .max = @as(T, max_x) }, .{ .expand = .horizontal }); + const res = dvui.textEntryNumber(@src(), T, .{ .value = &next.x }, .{ .expand = .horizontal }); subrow.deinit(); changed = res.changed or changed; } @@ -551,7 +542,7 @@ fn drawPropertyEditor(canvas: *Canvas, obj: *Document.Object, prop: *const Prope var subrow = dvui.box(@src(), .{ .dir = .horizontal }, .{ .expand = .horizontal }); dvui.labelNoFmt(@src(), "y:", .{}, .{}); const T = @TypeOf(next.y); - const res = dvui.textEntryNumber(@src(), T, .{ .value = &next.y, .min = @as(T, min_y), .max = @as(T, max_y) }, .{ .expand = .horizontal }); + const res = dvui.textEntryNumber(@src(), T, .{ .value = &next.y }, .{ .expand = .horizontal }); subrow.deinit(); changed = res.changed or changed; } @@ -561,7 +552,127 @@ fn drawPropertyEditor(canvas: *Canvas, obj: *Document.Object, prop: *const Prope } }, .points => |points| { - dvui.label(@src(), "Points: {d}", .{points.items.len}, .{}); + var list = points.clone(canvas.allocator) catch { + dvui.label(@src(), "Points: {d}", .{points.items.len}, .{}); + return; + }; + dvui.label(@src(), "Points: {d}", .{list.items.len}, .{}); + + var changed = false; + var to_delete: ?usize = null; + + for (list.items, 0..) |*pt, i| { + // Одна строка: крестик удаления + paned с X/Y пополам + var subrow = dvui.box( + @src(), + .{ .dir = .horizontal }, + .{ + .expand = .horizontal, + .id_extra = i, + }, + ); + { + // Крестик удаления + if (dvui.buttonIcon(@src(), "Delete", icons.cross, .{}, .{}, .{ + .id_extra = i, + .gravity_y = 0.5, + .margin = .{ + .x = 8, + }, + })) { + to_delete = i; + } + + // Панель с X и Y, разделёнными пополам + var split_ratio: f32 = 0.5; + var paned = dvui.paned( + @src(), + .{ + .direction = .horizontal, + .collapsed_size = 0.0, + .split_ratio = &split_ratio, + .handle_size = 0, + }, + .{ + .expand = .horizontal, + }, + ); + { + if (paned.showFirst()) { + var x_box = dvui.box( + @src(), + .{ .dir = .horizontal }, + .{ .expand = .both }, + ); + { + dvui.labelNoFmt(@src(), "x:", .{}, .{ + .gravity_y = 0.5, + }); + const Tx = @TypeOf(pt.x); + const res_x = dvui.textEntryNumber( + @src(), + Tx, + .{ .value = &pt.x }, + .{ .expand = .horizontal }, + ); + changed = res_x.changed or changed; + } + x_box.deinit(); + } + + if (paned.showSecond()) { + var y_box = dvui.box( + @src(), + .{ .dir = .horizontal }, + .{ .expand = .both }, + ); + { + dvui.labelNoFmt(@src(), "y:", .{}, .{ + .gravity_y = 0.5, + }); + const Ty = @TypeOf(pt.y); + const res_y = dvui.textEntryNumber( + @src(), + Ty, + .{ .value = &pt.y }, + .{ .expand = .horizontal }, + ); + changed = res_y.changed or changed; + } + y_box.deinit(); + } + } + paned.deinit(); + } + subrow.deinit(); + } + + // Удаление выбранной точки + if (to_delete) |idx| { + _ = list.orderedRemove(idx); + changed = true; + } + + // Кнопка добавления новой точки (одна на весь список) + if (dvui.button(@src(), "Add point", .{}, .{})) { + const T = @TypeOf(list.items[0]); + const new_point: T = if (list.items.len > 0) + list.items[list.items.len - 1] + else + .{ .x = 0, .y = 0 }; + list.append(canvas.allocator, new_point) catch {}; + changed = true; + } + + if (changed) { + obj.setProperty(canvas.allocator, .{ .data = .{ .points = list } }) catch { + list.deinit(canvas.allocator); + return; + }; + canvas.requestRedraw(); + } else { + list.deinit(canvas.allocator); + } }, .fill_rgba => |rgba| { drawColorEditor(canvas, obj, rgba, true);