Files
Zivro/src/ui/left_panel.zig

227 lines
7.6 KiB
Zig

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 == .press 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 };
}
}
}
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(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 (!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 {};
}
} 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();
}