diff --git a/src/models/Document.zig b/src/models/Document.zig index cbdf31f..a0582b7 100644 --- a/src/models/Document.zig +++ b/src/models/Document.zig @@ -1,9 +1,8 @@ const std = @import("std"); const basic_models = @import("basic_models.zig"); +const properties = @import("Property.zig"); +const Property = properties.Property; const Size = basic_models.Size; -const ObjectCommon = basic_models.ObjectCommon; -const Point2 = basic_models.Point2; - const Document = @This(); size: Size, @@ -36,32 +35,93 @@ pub const Layer = struct { } pub fn deinit(self: *Layer) void { + for (self.objects.items) |*obj| obj.deinit(self.allocator); self.objects.deinit(self.allocator); } + + /// Добавить объект в слой (клонирует свойства в allocator слоя). + pub fn addObject(self: *Layer, template: Object) !void { + const obj = try template.clone(self.allocator); + try self.objects.append(self.allocator, obj); + } }; -/// Объект на слое: общие свойства + полиморфные данные по тегу (path, rect, ellipse, ...). -/// Полиморфизм через tagged union — без наследования и vtable, диспетчеризация через switch (obj.data). -pub const Object = struct { - common: ObjectCommon, - data: Data, - - pub const Data = union(enum) { - path: PathData, - rect: RectData, - ellipse: EllipseData, - }; - - /// Точки контура (заглушка; позже — владение через Layer/Document allocator) - pub const PathData = struct {}; - - pub const RectData = struct { - width: f32, - height: f32, - }; - - pub const EllipseData = struct { - radius_x: f32, - radius_y: f32, - }; +/// Тип фигуры: определяет, как RenderEngine интерпретирует свойства и рисует объект. +pub const ShapeKind = enum { + rect, + ellipse, + line, + path, +}; + +/// Объект на слое: тип фигуры + список полиморфных свойств. +/// Типы объектов (прямоугольник, эллипс, линия) задаются конструкторами, +/// которые создают нужный набор свойств; UI и RenderEngine работают с union Property. +pub const Object = struct { + shape: ShapeKind, + properties: std.ArrayList(Property), + + /// Найти свойство по тегу и вернуть вариант 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); + } + + /// Клонировать объект (в т.ч. список свойств) для вставки в другой слой/документ. + pub fn clone(self: Object, allocator: std.mem.Allocator) !Object { + var list = std.ArrayList(Property).empty; + errdefer list.deinit(allocator); + try list.appendSlice(allocator, self.properties.items); + return .{ .shape = self.shape, .properties = list }; + } + + pub fn deinit(self: *Object, allocator: std.mem.Allocator) void { + 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 }; + } + + // --- Публичные конструкторы: базовый объект + одно свойство фигуры --- + + 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; + } }; diff --git a/src/models/Property.zig b/src/models/Property.zig new file mode 100644 index 0000000..7a879ba --- /dev/null +++ b/src/models/Property.zig @@ -0,0 +1,62 @@ +// Модель свойств объекта документа. +// Каждое свойство — отдельный тип в union; UI и RenderEngine работают с полиморфным Property. +// Комплексные значения (размер, радиусы) — один вариант свойства, а не несколько полей. + +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. + position: Point2, + scale: Scale2, + visible: bool, + opacity: f32, + locked: bool, + + // --- Прямоугольник: один вариант --- + size: Size, + + // --- Эллипс: один вариант --- + radii: Radii, + + // --- Линия: конечная точка (относительно position) --- + end_point: Point2, + + // --- Визуал (опционально для будущего) --- + fill_rgba: u32, + stroke_rgba: u32, +}; + +const std = @import("std"); + +/// Общий набор свойств по умолчанию для любого объекта (одно место определения). +/// Конструкторы фигур добавляют сначала его, затем специфичные свойства. +pub fn defaultCommonProperties() []Property { + return .{ + .{ .position = .{ .x = 0, .y = 0 } }, + .{ .scale = .{ .scale_x = 1, .scale_y = 1 } }, + .{ .visible = true }, + .{ .opacity = 1.0 }, + .{ .locked = false }, + }; +} + +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 "common properties" { + const common = defaultCommonProperties(); + try std.testing.expect(common[0].position.x == 0); + try std.testing.expect(common[2].visible == true); +} diff --git a/src/models/basic_models.zig b/src/models/basic_models.zig index f5d7d40..6590afb 100644 --- a/src/models/basic_models.zig +++ b/src/models/basic_models.zig @@ -17,22 +17,18 @@ pub const Size = struct { /// Точка в 2D (документные единицы) pub const Point2 = struct { + x: f32 = 0, + y: f32 = 0, +}; + +/// Радиусы эллипса по осям (одно свойство). +pub const Radii = struct { x: f32, y: f32, }; -/// Трансформ объекта: позиция и масштаб (поворот при необходимости добавить отдельно) -pub const Transform2 = struct { - x: f32 = 0, - y: f32 = 0, +/// Масштаб объекта +pub const Scale2 = struct { scale_x: f32 = 1, scale_y: f32 = 1, }; - -/// Общие свойства любого объекта на слое (видимость, блокировка, непрозрачность, трансформ) -pub const ObjectCommon = struct { - transform: Transform2 = .{}, - visible: bool = true, - locked: bool = false, - opacity: f32 = 1.0, -};