const std = @import("std"); const dvui = @import("dvui"); const WindowContext = @import("../WindowContext.zig"); const Document = @import("../models/Document.zig"); const icons = @import("../icons.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); const ObjectTreeCallback = union(enum) { select: u64, delete: u64, }; fn shapeLabel(shape: Object.ShapeKind) []const u8 { return switch (shape) { .line => "Line", .ellipse => "Ellipse", .arc => "Arc", .broken => "Broken line", }; } 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_id == obj.id; const row_id: usize = @intCast(obj.id); const focus_color = dvui.themeGet().focus; var row = dvui.box( @src(), .{ .dir = .horizontal }, .{ .id_extra = row_id, .expand = .horizontal, }, ); { var hovered: bool = false; const row_data = row.data(); var select_row: bool = false; // Ручная обработка hover/click по строке без пометки события как handled, // чтобы кнопка удаления могла нормально получать свои события. for (dvui.events()) |*e| { switch (e.evt) { .mouse => |*mouse| { if (!dvui.eventMatchSimple(e, row_data)) continue; hovered = true; if (mouse.action == .release and mouse.button == .left) { select_row = true; } }, else => {}, } } 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 (hovered) { const delete_opts: dvui.Options = .{ .id_extra = row_id +% 1, .margin = dvui.Rect{ .x = 4, .w = 6 }, .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.id }; select_row = false; } } } content.deinit(); if (select_row) { object_callback.* = .{ .select = obj.id }; } } row.deinit(); for (obj.children.items) |*child| { objectTreeRow(open_doc, child, depth + 1, object_callback); } } fn objectTree(ctx: *WindowContext) void { const active_doc = ctx.activeDocument(); var object_callback: ?ObjectTreeCallback = null; if (active_doc) |open_doc| { const doc = &open_doc.document; if (doc.objects.items.len == 0) { dvui.label(@src(), "No objects", .{}, .{}); } else { for (doc.objects.items) |*obj| { objectTreeRow(open_doc, obj, 0, &object_callback); } } if (object_callback) |callback| { switch (callback) { .select => |obj_id| { if (open_doc.selected_object_id == obj_id) { open_doc.selected_object_id = null; } else { open_doc.selected_object_id = obj_id; } }, .delete => |obj_id| { _ = doc.removeObjectById(ctx.allocator, obj_id); if (open_doc.selected_object_id == obj_id) open_doc.selected_object_id = null; open_doc.canvas.requestRedraw(); }, } } } 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 = 220 }, .max_size_content = .{ .h = undefined, .w = 220 }, .background = true, .padding = padding, }, ); { // Нижняя часть: настройки var settings_section = dvui.box( @src(), .{ .dir = .vertical }, .{ .expand = .horizontal, .gravity_y = 1.0, .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| { const canvas = &doc.canvas; if (dvui.checkbox(@src(), &canvas.native_scaling, "Scaling", .{})) {} if (dvui.checkbox(@src(), &canvas.draw_document, "Draw document", .{})) { canvas.requestRedraw(); } if (dvui.checkbox(@src(), &canvas.show_render_stats, "Show stats", .{})) {} if (!canvas.draw_document) { if (dvui.button(@src(), if (doc.cpu_render.type == .Gradient) "Gradient" else "Squares", .{}, .{})) { if (doc.cpu_render.type == .Gradient) { doc.cpu_render.type = .Squares; } else { doc.cpu_render.type = .Gradient; } canvas.requestRedraw(); } } if (dvui.button(@src(), "Add random shapes", .{}, .{})) { canvas.addRandomShapes() catch {}; } if (dvui.button(@src(), "Request redraw", .{}, .{})) { canvas.requestRedraw(); } } else { dvui.label(@src(), "No document", .{}, .{}); } } settings_section.deinit(); // Верхняя часть: дерево объектов 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", .{}, .{ .font = .{ .id = dvui.themeGet().font_heading.id, .line_height_factor = dvui.themeGet().font_heading.line_height_factor, .size = dvui.themeGet().font_heading.size + 8, }, .gravity_x = 0.5 }); var scroll = dvui.scrollArea( @src(), .{ .vertical = .auto, .horizontal = .auto }, .{ .expand = .both, .background = false }, ); { objectTree(ctx); } scroll.deinit(); } tree_section.deinit(); } panel.deinit(); }