Убраны комментарии лишние и улучшены модели
This commit is contained in:
@@ -2,12 +2,14 @@ const std = @import("std");
|
||||
const basic_models = @import("basic_models.zig");
|
||||
const properties = @import("Property.zig");
|
||||
const Property = properties.Property;
|
||||
const PropertyData = properties.Data;
|
||||
const Point2 = basic_models.Point2;
|
||||
const Size = basic_models.Size;
|
||||
const Object = @import("Object.zig");
|
||||
const Document = @This();
|
||||
|
||||
size: Size,
|
||||
allocator: std.mem.Allocator,
|
||||
/// Корневые объекты документа (вложенность через Object.children).
|
||||
objects: std.ArrayList(Object),
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, size: Size) Document {
|
||||
@@ -23,112 +25,7 @@ pub fn deinit(self: *Document) void {
|
||||
self.objects.deinit(self.allocator);
|
||||
}
|
||||
|
||||
/// Добавить корневой объект в документ (клонирует в allocator документа).
|
||||
pub fn addObject(self: *Document, template: Object) !void {
|
||||
const obj = try template.clone(self.allocator);
|
||||
try self.objects.append(self.allocator, obj);
|
||||
}
|
||||
|
||||
/// Тип фигуры: определяет, как RenderEngine интерпретирует свойства и рисует объект.
|
||||
pub const ShapeKind = enum {
|
||||
rect,
|
||||
ellipse,
|
||||
line,
|
||||
path,
|
||||
};
|
||||
|
||||
/// Объект документа: тип фигуры, свойства и вложенные дочерние объекты.
|
||||
/// Типы объектов задаются конструкторами; UI и RenderEngine работают с union Property.
|
||||
pub const Object = struct {
|
||||
shape: ShapeKind,
|
||||
properties: std.ArrayList(Property),
|
||||
/// Вложенные объекты (дерево).
|
||||
children: std.ArrayList(Object),
|
||||
|
||||
/// Найти свойство по тегу и вернуть вариант union (caller делает switch).
|
||||
pub fn getProperty(self: Object, tag: std.meta.Tag(Property)) ?Property {
|
||||
for (self.properties.items) |prop| {
|
||||
if (std.meta.activeTag(prop) == tag) return prop;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Установить свойство: если уже есть с таким тегом — заменить, иначе добавить.
|
||||
pub fn setProperty(self: *Object, allocator: std.mem.Allocator, prop: Property) !void {
|
||||
for (self.properties.items, 0..) |*p, i| {
|
||||
if (std.meta.activeTag(p.*) == std.meta.activeTag(prop)) {
|
||||
self.properties.items[i] = prop;
|
||||
return;
|
||||
}
|
||||
}
|
||||
try self.properties.append(allocator, prop);
|
||||
}
|
||||
|
||||
/// Добавить дочерний объект (клонирует в переданный allocator).
|
||||
pub fn addChild(self: *Object, allocator: std.mem.Allocator, template: Object) !void {
|
||||
const obj = try template.clone(allocator);
|
||||
try self.children.append(allocator, obj);
|
||||
}
|
||||
|
||||
/// Клонировать объект рекурсивно (свойства и дети).
|
||||
pub fn clone(self: Object, allocator: std.mem.Allocator) !Object {
|
||||
var properties_list = std.ArrayList(Property).empty;
|
||||
errdefer properties_list.deinit(allocator);
|
||||
try properties_list.appendSlice(allocator, self.properties.items);
|
||||
|
||||
var children_list = std.ArrayList(Object).empty;
|
||||
errdefer children_list.deinit(allocator);
|
||||
for (self.children.items) |child| {
|
||||
try children_list.append(allocator, try child.clone(allocator));
|
||||
}
|
||||
|
||||
return .{
|
||||
.shape = self.shape,
|
||||
.properties = properties_list,
|
||||
.children = children_list,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Object, allocator: std.mem.Allocator) void {
|
||||
for (self.children.items) |*child| child.deinit(allocator);
|
||||
self.children.deinit(allocator);
|
||||
self.properties.deinit(allocator);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// Базовый объект с общим набором свойств (для внутреннего использования конструкторами).
|
||||
fn createWithCommon(allocator: std.mem.Allocator, shape: ShapeKind) !Object {
|
||||
const common = properties.defaultCommonProperties();
|
||||
var properties_list = std.ArrayList(Property).empty;
|
||||
errdefer properties_list.deinit(allocator);
|
||||
try properties_list.appendSlice(allocator, &common);
|
||||
return .{
|
||||
.shape = shape,
|
||||
.properties = properties_list,
|
||||
.children = std.ArrayList(Object).empty,
|
||||
};
|
||||
}
|
||||
|
||||
// --- Публичные конструкторы: базовый объект + одно свойство фигуры ---
|
||||
|
||||
pub fn createRect(allocator: std.mem.Allocator) !Object {
|
||||
var obj = try createWithCommon(allocator, .rect);
|
||||
errdefer obj.deinit(allocator);
|
||||
try obj.properties.append(allocator, .{ .size = .{ .width = 100, .height = 100 } });
|
||||
return obj;
|
||||
}
|
||||
|
||||
pub fn createEllipse(allocator: std.mem.Allocator) !Object {
|
||||
var obj = try createWithCommon(allocator, .ellipse);
|
||||
errdefer obj.deinit(allocator);
|
||||
try obj.properties.append(allocator, .{ .radii = .{ .x = 50, .y = 50 } });
|
||||
return obj;
|
||||
}
|
||||
|
||||
pub fn createLine(allocator: std.mem.Allocator) !Object {
|
||||
var obj = try createWithCommon(allocator, .line);
|
||||
errdefer obj.deinit(allocator);
|
||||
try obj.properties.append(allocator, .{ .end_point = .{ .x = 100, .y = 0 } });
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
|
||||
117
src/models/Object.zig
Normal file
117
src/models/Object.zig
Normal file
@@ -0,0 +1,117 @@
|
||||
const std = @import("std");
|
||||
const basic_models = @import("basic_models.zig");
|
||||
const defaultCommonProperties = @import("Property.zig").defaultCommonProperties;
|
||||
const Property = @import("Property.zig").Property;
|
||||
const PropertyData = @import("Property.zig").Data;
|
||||
const Point2 = basic_models.Point2;
|
||||
const Size = basic_models.Size;
|
||||
const Object = @This();
|
||||
|
||||
pub const ShapeKind = enum {
|
||||
rect,
|
||||
line,
|
||||
ellipse,
|
||||
arc,
|
||||
broken,
|
||||
};
|
||||
|
||||
shape: ShapeKind,
|
||||
properties: std.ArrayList(Property),
|
||||
children: std.ArrayList(Object),
|
||||
|
||||
pub fn getProperty(self: Object, tag: std.meta.Tag(PropertyData)) ?*const PropertyData {
|
||||
for (self.properties.items) |*prop| {
|
||||
if (std.meta.activeTag(prop.data) == tag) return &prop.data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn setProperty(self: *Object, allocator: std.mem.Allocator, prop: Property) !void {
|
||||
for (self.properties.items, 0..) |*p, i| {
|
||||
if (std.meta.activeTag(p.data) == std.meta.activeTag(prop.data)) {
|
||||
if (p.data == .points) p.data.points.deinit(allocator);
|
||||
self.properties.items[i] = prop;
|
||||
return;
|
||||
}
|
||||
}
|
||||
try self.properties.append(allocator, prop);
|
||||
}
|
||||
|
||||
pub fn addChild(self: *Object, allocator: std.mem.Allocator, template: Object) !void {
|
||||
const obj = try template.clone(allocator);
|
||||
try self.children.append(allocator, obj);
|
||||
}
|
||||
|
||||
pub fn clone(self: Object, allocator: std.mem.Allocator) !Object {
|
||||
var properties_list = std.ArrayList(Property).empty;
|
||||
errdefer properties_list.deinit(allocator);
|
||||
for (self.properties.items) |prop| {
|
||||
try properties_list.append(allocator, try prop.clone(allocator));
|
||||
}
|
||||
|
||||
var children_list = std.ArrayList(Object).empty;
|
||||
errdefer children_list.deinit(allocator);
|
||||
for (self.children.items) |child| {
|
||||
try children_list.append(allocator, try child.clone(allocator));
|
||||
}
|
||||
|
||||
return .{
|
||||
.shape = self.shape,
|
||||
.properties = properties_list,
|
||||
.children = children_list,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Object, allocator: std.mem.Allocator) void {
|
||||
for (self.children.items) |*child| child.deinit(allocator);
|
||||
self.children.deinit(allocator);
|
||||
for (self.properties.items) |*prop| prop.deinit(allocator);
|
||||
self.properties.deinit(allocator);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
fn createWithCommon(allocator: std.mem.Allocator, shape: ShapeKind) !Object {
|
||||
const common = defaultCommonProperties();
|
||||
var properties_list = std.ArrayList(Property).empty;
|
||||
errdefer properties_list.deinit(allocator);
|
||||
for (common) |d| try properties_list.append(allocator, .{ .data = d });
|
||||
return .{
|
||||
.shape = shape,
|
||||
.properties = properties_list,
|
||||
.children = std.ArrayList(Object).empty,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn createRect(allocator: std.mem.Allocator) !Object {
|
||||
var obj = try createWithCommon(allocator, .rect);
|
||||
errdefer obj.deinit(allocator);
|
||||
try obj.properties.append(allocator, .{ .data = .{ .size = .{ .width = 100, .height = 100 } } });
|
||||
return obj;
|
||||
}
|
||||
|
||||
pub fn createEllipse(allocator: std.mem.Allocator) !Object {
|
||||
var obj = try createWithCommon(allocator, .ellipse);
|
||||
errdefer obj.deinit(allocator);
|
||||
try obj.properties.append(allocator, .{ .data = .{ .radii = .{ .x = 50, .y = 50 } } });
|
||||
return obj;
|
||||
}
|
||||
|
||||
pub fn createLine(allocator: std.mem.Allocator) !Object {
|
||||
var obj = try createWithCommon(allocator, .line);
|
||||
errdefer obj.deinit(allocator);
|
||||
try obj.properties.append(allocator, .{ .data = .{ .end_point = .{ .x = 100, .y = 0 } } });
|
||||
return obj;
|
||||
}
|
||||
|
||||
pub fn createBrokenLine(allocator: std.mem.Allocator) !Object {
|
||||
var obj = try createWithCommon(allocator, .broken);
|
||||
errdefer obj.deinit(allocator);
|
||||
var points = std.ArrayList(Point2).init(allocator);
|
||||
try points.appendSlice(allocator, &.{
|
||||
.{ .x = 0, .y = 0 },
|
||||
.{ .x = 80, .y = 0 },
|
||||
.{ .x = 80, .y = 60 },
|
||||
});
|
||||
try obj.properties.append(allocator, .{ .data = .{ .points = points } });
|
||||
return obj;
|
||||
}
|
||||
@@ -1,62 +1,73 @@
|
||||
// Модель свойств объекта документа.
|
||||
// Каждое свойство — отдельный тип в union; UI и RenderEngine работают с полиморфным Property.
|
||||
// Комплексные значения (размер, радиусы) — один вариант свойства, а не несколько полей.
|
||||
|
||||
const std = @import("std");
|
||||
const basic_models = @import("basic_models.zig");
|
||||
const Point2 = basic_models.Point2;
|
||||
const Scale2 = basic_models.Scale2;
|
||||
const Size = basic_models.Size;
|
||||
const Radii = basic_models.Radii;
|
||||
|
||||
/// Одно свойство объекта: полиморфный union.
|
||||
/// Варианты — целостные значения (size, radii), не разбитые на отдельные поля.
|
||||
/// UI перебирает свойства и по тегу показывает нужный редактор;
|
||||
/// RenderEngine по тегу фигуры читает нужные свойства для отрисовки.
|
||||
pub const Property = union(enum) {
|
||||
// --- Общие для всех фигур (см. defaultCommonProperties) ---
|
||||
/// Левый верхний угол bbox для rect/path; центр для ellipse; начало для line.
|
||||
pub const Data = union(enum) {
|
||||
position: Point2,
|
||||
angle: f32,
|
||||
scale: Scale2,
|
||||
visible: bool,
|
||||
opacity: f32,
|
||||
locked: bool,
|
||||
|
||||
// --- Прямоугольник: один вариант ---
|
||||
size: Size,
|
||||
|
||||
// --- Эллипс: один вариант ---
|
||||
radii: Radii,
|
||||
|
||||
// --- Линия: конечная точка (относительно position) ---
|
||||
end_point: Point2,
|
||||
|
||||
// --- Визуал (опционально для будущего) ---
|
||||
points: std.ArrayList(Point2),
|
||||
|
||||
fill_rgba: u32,
|
||||
stroke_rgba: u32,
|
||||
};
|
||||
|
||||
const std = @import("std");
|
||||
pub const Property = struct {
|
||||
data: Data,
|
||||
|
||||
/// Общий набор свойств по умолчанию для любого объекта (одно место определения).
|
||||
/// Конструкторы фигур добавляют сначала его, затем специфичные свойства.
|
||||
pub fn defaultCommonProperties() []Property {
|
||||
return .{
|
||||
.{ .position = .{ .x = 0, .y = 0 } },
|
||||
.{ .scale = .{ .scale_x = 1, .scale_y = 1 } },
|
||||
.{ .visible = true },
|
||||
.{ .opacity = 1.0 },
|
||||
.{ .locked = false },
|
||||
};
|
||||
pub fn deinit(self: *Property, allocator: std.mem.Allocator) void {
|
||||
switch (self.data) {
|
||||
.points => |*list| list.deinit(allocator),
|
||||
else => {},
|
||||
}
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn clone(self: Property, allocator: std.mem.Allocator) !Property {
|
||||
return switch (self.data) {
|
||||
.points => |list| .{
|
||||
.data = .{
|
||||
.points = try list.clone(allocator),
|
||||
},
|
||||
},
|
||||
else => .{ .data = self.data },
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const default_common_data: []const Data = .{
|
||||
.{ .position = .{ .x = 0, .y = 0 } },
|
||||
.{ .angle = 0 },
|
||||
.{ .scale = .{ .scale_x = 1, .scale_y = 1 } },
|
||||
.{ .visible = true },
|
||||
.{ .opacity = 1.0 },
|
||||
.{ .locked = false },
|
||||
};
|
||||
|
||||
pub fn defaultCommonProperties() []const Data {
|
||||
return &default_common_data;
|
||||
}
|
||||
|
||||
test "Property is union" {
|
||||
const p: Property = .{ .opacity = 0.5 };
|
||||
try std.testing.expect(p == .opacity);
|
||||
try std.testing.expect(p.opacity == 0.5);
|
||||
test "Property wrapper and Data" {
|
||||
const p = Property{ .data = .{ .opacity = 0.5 } };
|
||||
try std.testing.expect(p.data == .opacity);
|
||||
try std.testing.expect(p.data.opacity == 0.5);
|
||||
}
|
||||
|
||||
test "common properties" {
|
||||
const common = defaultCommonProperties();
|
||||
try std.testing.expect(common[0] == .position);
|
||||
try std.testing.expect(common[0].position.x == 0);
|
||||
try std.testing.expect(common[2].visible == true);
|
||||
}
|
||||
|
||||
@@ -15,19 +15,16 @@ pub const Size = struct {
|
||||
height: f32,
|
||||
};
|
||||
|
||||
/// Точка в 2D (документные единицы)
|
||||
pub const Point2 = struct {
|
||||
x: f32 = 0,
|
||||
y: f32 = 0,
|
||||
};
|
||||
|
||||
/// Радиусы эллипса по осям (одно свойство).
|
||||
pub const Radii = struct {
|
||||
x: f32,
|
||||
y: f32,
|
||||
};
|
||||
|
||||
/// Масштаб объекта
|
||||
pub const Scale2 = struct {
|
||||
scale_x: f32 = 1,
|
||||
scale_y: f32 = 1,
|
||||
|
||||
Reference in New Issue
Block a user