Первая версия тулбара

This commit is contained in:
2026-02-25 21:13:38 +03:00
parent 0d546782bb
commit 9202b527e4
9 changed files with 143 additions and 4 deletions

0
assets/icons/.gitkeep Normal file
View File

View File

@@ -9,11 +9,13 @@ const Size_i = basic_models.Size_i;
const Point2_f = @import("models/basic_models.zig").Point2_f; const Point2_f = @import("models/basic_models.zig").Point2_f;
const Color = dvui.Color; const Color = dvui.Color;
const Toolbar = @import("Toolbar.zig");
const Canvas = @This(); const Canvas = @This();
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
document: *Document, document: *Document,
render_engine: RenderEngine, render_engine: RenderEngine,
toolbar: Toolbar,
texture: ?dvui.Texture = null, texture: ?dvui.Texture = null,
pos: dvui.Point = dvui.Point{ .x = 400, .y = 400 }, pos: dvui.Point = dvui.Point{ .x = 400, .y = 400 },
scroll: dvui.ScrollInfo = .{ scroll: dvui.ScrollInfo = .{
@@ -30,15 +32,17 @@ cursor_document_point: ?Point2_f = null,
/// true — рисовать документ (render), false — пример (gradient/squares). /// true — рисовать документ (render), false — пример (gradient/squares).
draw_document: bool = true, draw_document: bool = true,
pub fn init(allocator: std.mem.Allocator, document: *Document, engine: RenderEngine) Canvas { pub fn init(allocator: std.mem.Allocator, document: *Document, engine: RenderEngine, toolbar: Toolbar) Canvas {
return .{ return .{
.allocator = allocator, .allocator = allocator,
.document = document, .document = document,
.render_engine = engine, .render_engine = engine,
.toolbar = toolbar,
}; };
} }
pub fn deinit(self: *Canvas) void { pub fn deinit(self: *Canvas) void {
self.toolbar.deinit();
if (self.texture) |texture| { if (self.texture) |texture| {
dvui.Texture.destroyLater(texture); dvui.Texture.destroyLater(texture);
self.texture = null; self.texture = null;

36
src/Toolbar.zig Normal file
View File

@@ -0,0 +1,36 @@
//! Структура тулбара инструментов. Жизненный цикл совпадает с Canvas.
const tool_interface = @import("tool_interface.zig");
const Toolbar = @This();
/// Описание одного инструмента для тулбара.
pub const ToolDescriptor = struct {
name: []const u8,
/// Иконка в формате TVG (байты).
icon_tvg: []const u8,
/// Реализация интерфейса инструмента (своя для каждого инструмента в tools/).
implementation: *const tool_interface.Tool,
};
/// Вертикальный тулбар инструментов.
tools: []const ToolDescriptor,
selected_index: usize,
pub fn init(tools_list: []const ToolDescriptor) Toolbar {
return .{
.tools = tools_list,
.selected_index = 0,
};
}
pub fn deinit(_: *Toolbar) void {}
pub fn currentDescriptor(self: *const Toolbar) ?*const ToolDescriptor {
if (self.tools.len == 0) return null;
return &self.tools[self.selected_index];
}
pub fn select(self: *Toolbar, index: usize) void {
if (index < self.tools.len) self.selected_index = index;
}

View File

@@ -5,6 +5,8 @@ const RenderEngine = @import("render/RenderEngine.zig").RenderEngine;
const Document = @import("models/Document.zig"); const Document = @import("models/Document.zig");
const random_document = @import("models/random_document.zig"); const random_document = @import("models/random_document.zig");
const basic_models = @import("models/basic_models.zig"); const basic_models = @import("models/basic_models.zig");
const tools = @import("tools.zig");
const Toolbar = @import("Toolbar.zig");
const WindowContext = @This(); const WindowContext = @This();
@@ -17,7 +19,12 @@ pub const OpenDocument = struct {
const default_size = basic_models.Size_f{ .w = 800, .h = 600 }; const default_size = basic_models.Size_f{ .w = 800, .h = 600 };
self.document = Document.init(allocator, default_size); self.document = Document.init(allocator, default_size);
self.cpu_render = CpuRenderEngine.init(allocator, .Squares); self.cpu_render = CpuRenderEngine.init(allocator, .Squares);
self.canvas = Canvas.init(allocator, &self.document, (&self.cpu_render).renderEngine()); self.canvas = Canvas.init(
allocator,
&self.document,
(&self.cpu_render).renderEngine(),
Toolbar.init(&tools.default_tools),
);
} }
pub fn deinit(self: *OpenDocument) void { pub fn deinit(self: *OpenDocument) void {

3
src/icons.zig Normal file
View File

@@ -0,0 +1,3 @@
const dvui = @import("dvui");
pub const line = dvui.entypo.line_graph;

14
src/tool_interface.zig Normal file
View File

@@ -0,0 +1,14 @@
//! Общий интерфейс инструмента. Реализации живут в каталоге tools/.
const Point2_f = @import("models/basic_models.zig").Point2_f;
/// Контекст вызова: холст и точка в координатах документа.
pub const ToolContext = struct {
canvas: *anyopaque,
document_point: Point2_f,
};
/// Интерфейс инструмента: один метод — клик по холсту в позиции курсора.
pub const Tool = struct {
onClick: *const fn (*const ToolContext) void,
};

13
src/tools.zig Normal file
View File

@@ -0,0 +1,13 @@
//! Список инструментов по умолчанию для тулбара. Реализации — в каталоге tools/.
const Toolbar = @import("Toolbar.zig");
const line = @import("tools/line.zig");
const icons = @import("icons.zig");
pub const default_tools = [_]Toolbar.ToolDescriptor{
.{
.name = "Line",
.icon_tvg = icons.line,
.implementation = &line.tool,
},
};

21
src/tools/line.zig Normal file
View File

@@ -0,0 +1,21 @@
//! Инструмент «Линия»: создаёт линию в позиции клика.
const Canvas = @import("../Canvas.zig");
const tool_interface = @import("../tool_interface.zig");
const shape = @import("../models/shape/shape.zig");
fn onClick(ctx: *const tool_interface.ToolContext) void {
const canvas: *Canvas = @alignCast(@ptrCast(ctx.canvas));
var obj = shape.createObject(canvas.document.allocator, .line) catch return;
obj.setProperty(canvas.document.allocator, .{ .data = .{ .position = ctx.document_point } }) catch {
obj.deinit(canvas.document.allocator);
return;
};
canvas.document.addObject(obj) catch {
obj.deinit(canvas.document.allocator);
return;
};
canvas.requestRedraw();
}
pub const tool = tool_interface.Tool{ .onClick = onClick };

View File

@@ -3,6 +3,7 @@ const dvui = @import("dvui");
const dvui_ext = @import("dvui_ext.zig"); const dvui_ext = @import("dvui_ext.zig");
const Canvas = @import("../Canvas.zig"); const Canvas = @import("../Canvas.zig");
const Rect_i = @import("../models/basic_models.zig").Rect_i; const Rect_i = @import("../models/basic_models.zig").Rect_i;
const tool_interface = @import("../tool_interface.zig");
pub fn canvasView(canvas: *Canvas, content_rect_scale: dvui.RectScale) void { pub fn canvasView(canvas: *Canvas, content_rect_scale: dvui.RectScale) void {
var textured = dvui_ext.texturedBox(content_rect_scale, dvui.Rect.all(20)); var textured = dvui_ext.texturedBox(content_rect_scale, dvui.Rect.all(20));
@@ -25,6 +26,7 @@ pub fn canvasView(canvas: *Canvas, content_rect_scale: dvui.RectScale) void {
} }
scroll.deinit(); scroll.deinit();
drawToolbar(canvas);
dvui.label(@src(), "Canvas", .{}, .{ .gravity_x = 0.5, .gravity_y = 0.0 }); dvui.label(@src(), "Canvas", .{}, .{ .gravity_x = 0.5, .gravity_y = 0.0 });
} }
overlay.deinit(); overlay.deinit();
@@ -159,10 +161,49 @@ fn handleCanvasMouse(canvas: *Canvas, scroll: anytype) void {
}; };
const doc_pt = canvas.contentPointToDocument(content_pt, natural_scale); const doc_pt = canvas.contentPointToDocument(content_pt, natural_scale);
canvas.cursor_document_point = if (canvas.isContentPointOnDocument(content_pt, natural_scale)) doc_pt else null; canvas.cursor_document_point = if (canvas.isContentPointOnDocument(content_pt, natural_scale)) doc_pt else null;
if (canvas.cursor_document_point) |point| if (canvas.cursor_document_point) |point| {
std.debug.print("cursor_document_point: {}\n", .{point}); if (canvas.toolbar.currentDescriptor()) |desc| {
var ctx = tool_interface.ToolContext{
.canvas = canvas,
.document_point = point,
};
desc.implementation.onClick(&ctx);
}
}
}, },
else => {}, else => {},
} }
} }
} }
fn drawToolbar(canvas: *Canvas) void {
const tools_list = canvas.toolbar.tools;
if (tools_list.len == 0) return;
var bar = dvui.box(
@src(),
.{ .dir = .vertical },
.{
.gravity_x = 0.0,
.gravity_y = 0.0,
.margin = dvui.Rect{ .x = 8, .y = 8 },
.padding = dvui.Rect.all(6),
.corner_radius = dvui.Rect.all(8),
.background = true,
.color_fill = dvui.Color.black.opacity(0.2),
},
);
{
for (tools_list, 0..) |*tool_desc, i| {
const is_selected = (canvas.toolbar.selected_index == i);
const opts: dvui.Options = .{
.id_extra = i,
.color_fill = if (is_selected) dvui.Color.transparent else undefined,
};
if (dvui.buttonIcon(@src(), tool_desc.name, tool_desc.icon_tvg, .{}, .{}, opts)) {
canvas.toolbar.select(i);
}
}
}
bar.deinit();
}