diff --git a/src/WindowContext.zig b/src/WindowContext.zig index 3b77b47..7748376 100644 --- a/src/WindowContext.zig +++ b/src/WindowContext.zig @@ -3,6 +3,7 @@ const Canvas = @import("Canvas.zig"); const CpuRenderEngine = @import("render/CpuRenderEngine.zig"); const RenderEngine = @import("render/RenderEngine.zig").RenderEngine; const Document = @import("models/Document.zig"); +const random_document = @import("models/random_document.zig"); const basic_models = @import("models/basic_models.zig"); const WindowContext = @This(); @@ -60,7 +61,7 @@ pub fn addNewDocument(self: *WindowContext) !void { const ptr = try self.allocator.create(OpenDocument); errdefer self.allocator.destroy(ptr); OpenDocument.init(self.allocator, ptr); - try ptr.document.addRandomShapes(std.crypto.random); + try random_document.addRandomShapes(&ptr.document, std.crypto.random); try self.documents.append(self.allocator, ptr); self.active_document_index = self.documents.items.len - 1; } diff --git a/src/models/Document.zig b/src/models/Document.zig index c3a8157..781ba07 100644 --- a/src/models/Document.zig +++ b/src/models/Document.zig @@ -36,43 +36,3 @@ pub fn addShape(self: *Document, parent: ?*Object, shape_kind: Object.ShapeKind) try self.addObject(obj); } } - -fn randomShapeKind(rng: std.Random) Object.ShapeKind { - const shapes_implemented = [_]Object.ShapeKind{ .line, .ellipse, .broken }; - return shapes_implemented[rng.intRangeLessThan(usize, 0, shapes_implemented.len)]; -} - -/// Создаёт случайное количество фигур в документе (в т.ч. вложенных). -/// Используются только реализованные типы: line, ellipse, broken. -/// Ограничение max_total предотвращает экспоненциальный рост и переполнение. -pub fn addRandomShapes(self: *Document, rng: std.Random) !void { - const max_total: usize = 80; - var total_count: usize = 0; - - const n_root = rng.intRangeLessThan(usize, 1, 5); - for (0..n_root) |_| { - if (total_count >= max_total) break; - try self.addShape(null, randomShapeKind(rng)); - total_count += 1; - } - - var stack = std.ArrayList(*Object).empty; - defer stack.deinit(self.allocator); - for (self.objects.items) |*obj| { - try stack.append(self.allocator, obj); - } - while (stack.pop()) |obj| { - if (total_count >= max_total) continue; - const n_children = rng.intRangeLessThan(usize, 0, 2); - const base_len = obj.children.items.len; - for (0..n_children) |_| { - if (total_count >= max_total) break; - try self.addShape(obj, randomShapeKind(rng)); - total_count += 1; - } - // Пушим в стек только после всех append, чтобы не держать указатели при реаллокации obj.children - for (obj.children.items[base_len..]) |*child| { - try stack.append(self.allocator, child); - } - } -} diff --git a/src/models/Object.zig b/src/models/Object.zig index 164be89..a6b9b13 100644 --- a/src/models/Object.zig +++ b/src/models/Object.zig @@ -10,6 +10,24 @@ pub const ShapeKind = enum { broken, }; +const default_common_data = [_]PropertyData{ + .{ .position = .{ .x = 0, .y = 0 } }, + .{ .angle = 0 }, + .{ .scale = .{ .scale_x = 1, .scale_y = 1 } }, + .{ .visible = true }, + .{ .opacity = 1.0 }, + .{ .locked = false }, + .{ .stroke_rgba = 0x000000FF }, +}; + +pub const defaultCommonProperties: [default_common_data.len]Property = blk: { + var result: [default_common_data.len]Property = undefined; + for (default_common_data, &result) |d, *p| { + p.* = .{ .data = d }; + } + break :blk result; +}; + shape: ShapeKind, properties: std.ArrayList(Property), children: std.ArrayList(Object), @@ -29,7 +47,8 @@ pub fn setProperty(self: *Object, allocator: std.mem.Allocator, prop: Property) return; } } - try self.properties.append(allocator, prop); + std.debug.print("Property not found: {s}\n", .{@tagName(prop.data)}); + return error.PropertyNotFound; } pub fn addChild(self: *Object, allocator: std.mem.Allocator, template: Object) !void { diff --git a/src/models/Property.zig b/src/models/Property.zig index a1fa81d..fdf6716 100644 --- a/src/models/Property.zig +++ b/src/models/Property.zig @@ -21,6 +21,8 @@ pub const Data = union(enum) { fill_rgba: u32, stroke_rgba: u32, + + thickness: f32, }; pub const Property = struct { @@ -45,33 +47,3 @@ pub const Property = struct { }; } }; - -const default_common_data = [_]Data{ - .{ .position = .{ .x = 0, .y = 0 } }, - .{ .angle = 0 }, - .{ .scale = .{ .scale_x = 1, .scale_y = 1 } }, - .{ .visible = true }, - .{ .opacity = 1.0 }, - .{ .locked = false }, -}; - -pub const defaultCommonProperties: [default_common_data.len]Property = blk: { - var result: [default_common_data.len]Property = undefined; - for (default_common_data, &result) |d, *p| { - p.* = .{ .data = d }; - } - break :blk result; -}; - -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" { - try std.testing.expect(defaultCommonProperties[0].data == .position); - try std.testing.expect(defaultCommonProperties[0].data.position.x == 0); - try std.testing.expect(defaultCommonProperties[3].data == .visible); - try std.testing.expect(defaultCommonProperties[3].data.visible == true); -} diff --git a/src/models/random_document.zig b/src/models/random_document.zig new file mode 100644 index 0000000..9219bf7 --- /dev/null +++ b/src/models/random_document.zig @@ -0,0 +1,128 @@ +const std = @import("std"); +const Document = @import("Document.zig"); +const Object = Document.Object; +const shape = @import("shape/shape.zig"); +const basic_models = @import("basic_models.zig"); +const Size = basic_models.Size; +const Point2 = basic_models.Point2; +const Scale2 = basic_models.Scale2; +const Radii = basic_models.Radii; + +fn randFloat(rng: std.Random, min: f32, max: f32) f32 { + return min + (max - min) * rng.float(f32); +} + +fn randRgba(rng: std.Random) u32 { + const r = rng.int(u8); + const g = rng.int(u8); + const b = rng.int(u8); + const a: u8 = @intCast(rng.intRangeLessThan(usize, 128, 256)); + return r | (@as(u32, g) << 8) | (@as(u32, b) << 16) | (@as(u32, a) << 24); +} + +fn randomShapeKind(rng: std.Random) Object.ShapeKind { + const shapes_implemented = [_]Object.ShapeKind{ .line, .ellipse, .broken }; + return shapes_implemented[rng.intRangeLessThan(usize, 0, shapes_implemented.len)]; +} + +/// Случайно заполняет все доступные свойства объекта; позиция — в пределах документа. +fn randomizeObjectProperties(allocator: std.mem.Allocator, doc_size: *const Size, obj: *Object, rng: std.Random) !void { + const margin: f32 = 8; + const max_x = @max(0, doc_size.width - margin); + const max_y = @max(0, doc_size.height - margin); + + try obj.setProperty(allocator, .{ .data = .{ + .position = .{ + .x = randFloat(rng, margin, if (max_x > margin) max_x else margin), + .y = randFloat(rng, margin, if (max_y > margin) max_y else margin), + }, + } }); + try obj.setProperty(allocator, .{ .data = .{ .angle = randFloat(rng, 0, 2 * std.math.pi) } }); + try obj.setProperty(allocator, .{ .data = .{ + .scale = .{ + .scale_x = randFloat(rng, 0.25, 2.0), + .scale_y = randFloat(rng, 0.25, 2.0), + }, + } }); + try obj.setProperty(allocator, .{ .data = .{ .visible = rng.boolean() } }); + try obj.setProperty(allocator, .{ .data = .{ .opacity = randFloat(rng, 0.3, 1.0) } }); + try obj.setProperty(allocator, .{ .data = .{ .locked = rng.boolean() } }); + + const stroke = randRgba(rng); + try obj.setProperty(allocator, .{ .data = .{ .stroke_rgba = stroke } }); + obj.setProperty(allocator, .{ .data = .{ .fill_rgba = randRgba(rng) } }) catch {}; + + switch (obj.shape) { + .line => { + const len = randFloat(rng, 20, @min(doc_size.width, doc_size.height) * 0.5); + const angle = randFloat(rng, 0, 2 * std.math.pi); + try obj.setProperty(allocator, .{ .data = .{ + .end_point = .{ + .x = std.math.cos(angle) * len, + .y = std.math.sin(angle) * len, + }, + } }); + }, + .ellipse => { + const max_r = @min(120, @min(doc_size.width / 4, doc_size.height / 4)); + try obj.setProperty(allocator, .{ .data = .{ + .radii = .{ + .x = randFloat(rng, 8, @max(8, max_r)), + .y = randFloat(rng, 8, @max(8, max_r)), + }, + } }); + }, + .broken => { + var points = std.ArrayList(Point2).empty; + const n = rng.intRangeLessThan(usize, 2, 9); + var x: f32 = 0; + var y: f32 = 0; + for (0..n) |_| { + try points.append(allocator, .{ .x = x, .y = y }); + x += randFloat(rng, -40, 80); + y += randFloat(rng, -30, 60); + } + try obj.setProperty(allocator, .{ .data = .{ .points = points } }); + }, + .arc => {}, + } +} + +/// Создаёт в документе случайное количество фигур (в т.ч. вложенных). +/// У каждой фигуры все доступные свойства задаются случайно; позиция — в пределах документа. +/// Реализованные типы: line, ellipse, broken. +pub fn addRandomShapes(doc: *Document, rng: std.Random) !void { + const max_total: usize = 80; + var total_count: usize = 0; + const allocator = doc.allocator; + + const n_root = rng.intRangeLessThan(usize, 1, 5); + for (0..n_root) |_| { + if (total_count >= max_total) break; + var obj = try shape.createObject(allocator, randomShapeKind(rng)); + try randomizeObjectProperties(allocator, &doc.size, &obj, rng); + try doc.addObject(obj); + total_count += 1; + } + + var stack = std.ArrayList(*Object).empty; + defer stack.deinit(allocator); + for (doc.objects.items) |*obj| { + try stack.append(allocator, obj); + } + while (stack.pop()) |obj| { + if (total_count >= max_total) continue; + const n_children = rng.intRangeLessThan(usize, 0, 2); + const base_len = obj.children.items.len; + for (0..n_children) |_| { + if (total_count >= max_total) break; + var child = try shape.createObject(allocator, randomShapeKind(rng)); + try randomizeObjectProperties(allocator, &doc.size, &child, rng); + try obj.addChild(allocator, child); + total_count += 1; + } + for (obj.children.items[base_len..]) |*child| { + try stack.append(allocator, child); + } + } +} diff --git a/src/models/shape/broken.zig b/src/models/shape/broken.zig index 7fce53d..a82cce5 100644 --- a/src/models/shape/broken.zig +++ b/src/models/shape/broken.zig @@ -15,7 +15,9 @@ pub const default_points = [_]Point2{ /// Теги обязательных свойств (у ломаной нет const default_shape_properties, только default_points). pub fn getRequiredTags() []const std.meta.Tag(PropertyData) { - return &[_]std.meta.Tag(PropertyData){.points}; + return &[_]std.meta.Tag(PropertyData){ + .points, + }; } /// Добавляет к объекту свойства по умолчанию для ломаной (points из default_points). diff --git a/src/models/shape/shape.zig b/src/models/shape/shape.zig index 8762102..0c38ab4 100644 --- a/src/models/shape/shape.zig +++ b/src/models/shape/shape.zig @@ -2,7 +2,7 @@ const std = @import("std"); const Object = @import("../Object.zig"); const Property = @import("../Property.zig").Property; const PropertyData = @import("../Property.zig").Data; -const defaultCommonProperties = @import("../Property.zig").defaultCommonProperties; +const defaultCommonProperties = Object.defaultCommonProperties; const basic_models = @import("../basic_models.zig"); const line = @import("line.zig"); const ellipse = @import("ellipse.zig");