начало дерева объектов
This commit is contained in:
@@ -12,6 +12,8 @@ pub const OpenDocument = struct {
|
|||||||
document: Document,
|
document: Document,
|
||||||
cpu_render: CpuRenderEngine,
|
cpu_render: CpuRenderEngine,
|
||||||
canvas: Canvas,
|
canvas: Canvas,
|
||||||
|
/// Выбранный объект в дереве (указатель, не индекс).
|
||||||
|
selected_object: ?*Document.Object = null,
|
||||||
|
|
||||||
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 };
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ pub const line = dvui.entypo.line_graph;
|
|||||||
pub const ellipse = dvui.entypo.circle;
|
pub const ellipse = dvui.entypo.circle;
|
||||||
pub const arc = dvui.entypo.loop;
|
pub const arc = dvui.entypo.loop;
|
||||||
pub const broken = dvui.entypo.flow_line;
|
pub const broken = dvui.entypo.flow_line;
|
||||||
|
pub const trash = dvui.entypo.trash;
|
||||||
|
|||||||
@@ -36,3 +36,28 @@ pub fn addShape(self: *Document, parent: ?*Object, shape_kind: Object.ShapeKind)
|
|||||||
try self.addObject(obj);
|
try self.addObject(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Удаляет объект из документа (из корня или из детей родителя). Возвращает true, если объект был найден и удалён.
|
||||||
|
pub fn removeObject(self: *Document, obj: *Object) bool {
|
||||||
|
for (self.objects.items, 0..) |*item, i| {
|
||||||
|
if (item == obj) {
|
||||||
|
var removed = self.objects.orderedRemove(i);
|
||||||
|
removed.deinit(self.allocator);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (removeFromChildren(self.allocator, &item.children, obj)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn removeFromChildren(allocator: std.mem.Allocator, children: *std.ArrayList(Object), obj: *Object) bool {
|
||||||
|
for (children.items, 0..) |*item, i| {
|
||||||
|
if (item == obj) {
|
||||||
|
var removed = children.orderedRemove(i);
|
||||||
|
removed.deinit(allocator);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (removeFromChildren(allocator, &item.children, obj)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
const std = @import("std");
|
||||||
const dvui = @import("dvui");
|
const dvui = @import("dvui");
|
||||||
const WindowContext = @import("../WindowContext.zig");
|
const WindowContext = @import("../WindowContext.zig");
|
||||||
const Document = @import("../models/Document.zig");
|
const Document = @import("../models/Document.zig");
|
||||||
|
const icons = @import("../icons.zig");
|
||||||
const Object = Document.Object;
|
const Object = Document.Object;
|
||||||
|
|
||||||
const panel_gap: f32 = 12;
|
const panel_gap: f32 = 12;
|
||||||
@@ -8,6 +10,11 @@ const panel_padding: f32 = 5;
|
|||||||
const panel_radius: f32 = 24;
|
const panel_radius: f32 = 24;
|
||||||
const fill_color = dvui.Color.black.opacity(0.2);
|
const fill_color = dvui.Color.black.opacity(0.2);
|
||||||
|
|
||||||
|
const ObjectTreeCallback = union(enum) {
|
||||||
|
select: *Object,
|
||||||
|
delete: *Object,
|
||||||
|
};
|
||||||
|
|
||||||
fn shapeLabel(shape: Object.ShapeKind) []const u8 {
|
fn shapeLabel(shape: Object.ShapeKind) []const u8 {
|
||||||
return switch (shape) {
|
return switch (shape) {
|
||||||
.line => "Line",
|
.line => "Line",
|
||||||
@@ -17,34 +24,92 @@ fn shapeLabel(shape: Object.ShapeKind) []const u8 {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn objectTreeRow(obj: *const Object, depth: u32, row_id: *usize) void {
|
fn objectTreeRow(open_doc: *WindowContext.OpenDocument, obj: *Object, depth: u32, object_callback: *?ObjectTreeCallback) void {
|
||||||
const id = row_id.*;
|
|
||||||
row_id.* += 1;
|
|
||||||
const indent_px = depth * 18;
|
const indent_px = depth * 18;
|
||||||
|
const is_selected: bool = open_doc.selected_object == obj;
|
||||||
|
const row_id = @intFromPtr(obj);
|
||||||
|
|
||||||
|
const focus_color = dvui.themeGet().focus;
|
||||||
|
|
||||||
|
// Визуально строка — это box с подсветкой по hover/selected, а не кнопка.
|
||||||
var row = dvui.box(
|
var row = dvui.box(
|
||||||
@src(),
|
@src(),
|
||||||
.{ .dir = .horizontal },
|
.{ .dir = .horizontal },
|
||||||
.{ .padding = dvui.Rect{ .x = @floatFromInt(indent_px) }, .id_extra = id },
|
.{
|
||||||
|
.id_extra = row_id,
|
||||||
|
.expand = .horizontal,
|
||||||
|
// Постоянной рамки нет; лёгкая заливка по hover и более яркая по selected.
|
||||||
|
// .color_fill = if (is_selected) focus_color.opacity(0.35) else null,
|
||||||
|
.color_fill_hover = focus_color.opacity(0.18),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
{
|
{
|
||||||
dvui.labelNoFmt(@src(), shapeLabel(obj.shape), .{}, .{ .id_extra = id });
|
var hovered: bool = false;
|
||||||
|
const row_data = row.data();
|
||||||
|
if (dvui.clicked(row_data, .{
|
||||||
|
.hovered = &hovered,
|
||||||
|
})) {
|
||||||
|
object_callback.* = .{ .select = obj };
|
||||||
|
}
|
||||||
|
|
||||||
|
const background = is_selected or hovered;
|
||||||
|
var content = dvui.box(@src(), .{ .dir = .horizontal }, .{
|
||||||
|
.expand = .horizontal,
|
||||||
|
.margin = dvui.Rect{ .x = @floatFromInt(indent_px) },
|
||||||
|
.background = background,
|
||||||
|
.color_fill = if (is_selected) focus_color.opacity(0.35) else if (hovered) focus_color.opacity(0.18) else null,
|
||||||
|
});
|
||||||
|
{
|
||||||
|
dvui.labelNoFmt(
|
||||||
|
@src(),
|
||||||
|
shapeLabel(obj.shape),
|
||||||
|
.{},
|
||||||
|
.{ .id_extra = row_id, .expand = .horizontal },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (is_selected or hovered) {
|
||||||
|
const delete_opts: dvui.Options = .{
|
||||||
|
.id_extra = row_id +% 1,
|
||||||
|
.margin = dvui.Rect{ .x = 4 },
|
||||||
|
.padding = dvui.Rect.all(2),
|
||||||
|
.gravity_y = 0.5,
|
||||||
|
.gravity_x = 1.0,
|
||||||
|
};
|
||||||
|
if (dvui.buttonIcon(@src(), "Delete object", icons.trash, .{}, .{}, delete_opts)) {
|
||||||
|
object_callback.* = .{ .delete = obj };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content.deinit();
|
||||||
}
|
}
|
||||||
row.deinit();
|
row.deinit();
|
||||||
for (obj.children.items) |*child| {
|
for (obj.children.items) |*child| {
|
||||||
objectTreeRow(child, depth + 1, row_id);
|
objectTreeRow(open_doc, child, depth + 1, object_callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn objectTree(ctx: *WindowContext) void {
|
fn objectTree(ctx: *WindowContext) void {
|
||||||
const active_doc = ctx.activeDocument();
|
const active_doc = ctx.activeDocument();
|
||||||
|
var object_callback: ?ObjectTreeCallback = null;
|
||||||
if (active_doc) |open_doc| {
|
if (active_doc) |open_doc| {
|
||||||
const doc = &open_doc.document;
|
const doc = &open_doc.document;
|
||||||
if (doc.objects.items.len == 0) {
|
if (doc.objects.items.len == 0) {
|
||||||
dvui.label(@src(), "No objects", .{}, .{});
|
dvui.label(@src(), "No objects", .{}, .{});
|
||||||
} else {
|
} else {
|
||||||
var row_id: usize = 0;
|
|
||||||
for (doc.objects.items) |*obj| {
|
for (doc.objects.items) |*obj| {
|
||||||
objectTreeRow(obj, 0, &row_id);
|
objectTreeRow(open_doc, obj, 0, &object_callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (object_callback) |callback| {
|
||||||
|
switch (callback) {
|
||||||
|
.select => |obj| {
|
||||||
|
open_doc.selected_object = obj;
|
||||||
|
},
|
||||||
|
.delete => |obj| {
|
||||||
|
_ = doc.removeObject(obj);
|
||||||
|
open_doc.selected_object = null;
|
||||||
|
open_doc.canvas.requestRedraw();
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user