Случайное дерево объектов

This commit is contained in:
2026-02-24 00:39:40 +03:00
parent aeda3ee0d0
commit 1a94cc8bfd
6 changed files with 203 additions and 48 deletions

View File

@@ -60,6 +60,7 @@ pub fn addNewDocument(self: *WindowContext) !void {
const ptr = try self.allocator.create(OpenDocument);
errdefer self.allocator.destroy(ptr);
OpenDocument.init(self.allocator, ptr);
try ptr.document.addRandomShapes(std.crypto.random);
try self.documents.append(self.allocator, ptr);
self.active_document_index = self.documents.items.len - 1;
}

View File

@@ -40,3 +40,43 @@ pub fn addShape(self: *Document, parent: ?*Object, shape: Object.ShapeKind) !voi
try self.addObject(obj);
}
}
fn randomShapeKind(rng: std.Random) Object.ShapeKind {
const shapes_implemented = [_]Object.ShapeKind{ .line, .ellipse, .broken };
return shapes_implemented[rng.intRangeLessThan(usize, 0, shapes_implemented.len)];
}
/// Создаёт случайное количество фигур в документе (в т.ч. вложенных).
/// Используются только реализованные типы: line, ellipse, broken.
/// Ограничение max_total предотвращает экспоненциальный рост и переполнение.
pub fn addRandomShapes(self: *Document, rng: std.Random) !void {
const max_total: usize = 80;
var total_count: usize = 0;
const n_root = rng.intRangeLessThan(usize, 1, 5);
for (0..n_root) |_| {
if (total_count >= max_total) break;
try self.addShape(null, randomShapeKind(rng));
total_count += 1;
}
var stack = std.ArrayList(*Object).empty;
defer stack.deinit(self.allocator);
for (self.objects.items) |*obj| {
try stack.append(self.allocator, obj);
}
while (stack.pop()) |obj| {
if (total_count >= max_total) continue;
const n_children = rng.intRangeLessThan(usize, 0, 2);
const base_len = obj.children.items.len;
for (0..n_children) |_| {
if (total_count >= max_total) break;
try self.addShape(obj, randomShapeKind(rng));
total_count += 1;
}
// Пушим в стек только после всех append, чтобы не держать указатели при реаллокации obj.children
for (obj.children.items[base_len..]) |*child| {
try stack.append(self.allocator, child);
}
}
}

View File

@@ -97,7 +97,7 @@ pub fn createLine(allocator: std.mem.Allocator) !Object {
pub fn createBrokenLine(allocator: std.mem.Allocator) !Object {
var obj = try createWithCommonProperties(allocator, .broken);
errdefer obj.deinit(allocator);
var points = std.ArrayList(Point2).init(allocator);
var points = std.ArrayList(Point2).empty;
try points.appendSlice(allocator, &.{
.{ .x = 0, .y = 0 },
.{ .x = 80, .y = 0 },

View File

@@ -22,16 +22,8 @@ pub fn guiFrame(ctx: *WindowContext) bool {
{
left_panel.leftPanel(ctx);
var back = dvui.box(
@src(),
.{ .dir = .horizontal },
.{ .expand = .both, .padding = dvui.Rect.all(12), .background = true },
);
{
right_panel.rightPanel(ctx);
}
back.deinit();
}
content_row.deinit();
}
root.deinit();

View File

@@ -1,14 +1,112 @@
const dvui = @import("dvui");
const WindowContext = @import("../WindowContext.zig");
const Document = @import("../models/Document.zig");
const Object = Document.Object;
const panel_gap: f32 = 12;
const panel_padding: f32 = 5;
const panel_radius: f32 = 24;
const fill_color = dvui.Color.black.opacity(0.2);
fn shapeLabel(shape: Object.ShapeKind) []const u8 {
return switch (shape) {
.line => "Line",
.ellipse => "Ellipse",
.arc => "Arc",
.broken => "Broken line",
};
}
fn objectTreeRow(obj: *const Object, depth: u32, row_id: *usize) void {
const id = row_id.*;
row_id.* += 1;
const indent_px = depth * 18;
var row = dvui.box(
@src(),
.{ .dir = .horizontal },
.{ .padding = dvui.Rect{ .x = @floatFromInt(indent_px) }, .id_extra = id },
);
{
dvui.labelNoFmt(@src(), shapeLabel(obj.shape), .{}, .{ .id_extra = id });
}
row.deinit();
for (obj.children.items) |*child| {
objectTreeRow(child, depth + 1, row_id);
}
}
fn objectTree(ctx: *WindowContext) void {
const active_doc = ctx.activeDocument();
if (active_doc) |open_doc| {
const doc = &open_doc.document;
if (doc.objects.items.len == 0) {
dvui.label(@src(), "No objects", .{}, .{});
} else {
var row_id: usize = 0;
for (doc.objects.items) |*obj| {
objectTreeRow(obj, 0, &row_id);
}
}
} else {
dvui.label(@src(), "No document", .{}, .{});
}
}
pub fn leftPanel(ctx: *WindowContext) void {
var padding = dvui.Rect.all(panel_gap);
padding.w = 0;
var panel = dvui.box(
@src(),
.{ .dir = .vertical },
.{ .expand = .vertical, .min_size_content = .{ .w = 200 }, .background = true },
.{
.expand = .vertical,
.min_size_content = .{ .w = 220 },
.background = true,
.padding = padding,
},
);
{
dvui.label(@src(), "Tools", .{}, .{});
// Верхняя часть: дерево объектов
var tree_section = dvui.box(
@src(),
.{ .dir = .vertical },
.{
.expand = .both,
.padding = dvui.Rect.all(panel_padding),
.corner_radius = dvui.Rect.all(panel_radius),
.color_fill = fill_color,
.background = true,
},
);
{
dvui.label(@src(), "Objects", .{}, .{});
var scroll = dvui.scrollArea(
@src(),
.{ .vertical = .auto },
.{ .expand = .vertical, .background = false },
);
{
objectTree(ctx);
}
scroll.deinit();
}
tree_section.deinit();
// Нижняя часть: настройки
var settings_section = dvui.box(
@src(),
.{ .dir = .vertical },
.{
.expand = .horizontal,
.margin = .{ .y = 5 },
.padding = dvui.Rect.all(panel_padding),
.corner_radius = dvui.Rect.all(panel_radius),
.color_fill = fill_color,
.background = true,
},
);
{
dvui.label(@src(), "Settings", .{}, .{});
const active_doc = ctx.activeDocument();
if (active_doc) |doc| {
@@ -26,5 +124,7 @@ pub fn leftPanel(ctx: *WindowContext) void {
dvui.label(@src(), "No document", .{}, .{});
}
}
settings_section.deinit();
}
panel.deinit();
}

View File

@@ -5,6 +5,12 @@ const canvas_view = @import("canvas_view.zig");
pub fn rightPanel(ctx: *WindowContext) void {
const fill_color = dvui.Color.black.opacity(0.25);
var back = dvui.box(
@src(),
.{ .dir = .horizontal },
.{ .expand = .both, .padding = dvui.Rect.all(12), .background = true },
);
{
var panel = dvui.box(
@src(),
.{ .dir = .vertical },
@@ -26,13 +32,27 @@ pub fn rightPanel(ctx: *WindowContext) void {
}
}
panel.deinit();
}
back.deinit();
}
fn noDocView(ctx: *WindowContext) void {
var center = dvui.box(
@src(),
.{ .dir = .vertical },
.{ .expand = .both, .padding = dvui.Rect.all(20) },
.{
.expand = .both,
.padding = dvui.Rect.all(20),
},
);
{
var box = dvui.box(
@src(),
.{ .dir = .vertical },
.{
.gravity_x = 0.5,
.gravity_y = 0.5,
},
);
{
dvui.label(@src(), "No document open", .{}, .{});
@@ -42,5 +62,7 @@ fn noDocView(ctx: *WindowContext) void {
};
}
}
box.deinit();
}
center.deinit();
}