Refactor: Перенёс логику создания фигур
Я перенёс логику создания объектов фигур из `Document.zig` и `Object.zig` в новый модуль `shape.zig`. Это упрощает добавление новых фигур и улучшает организацию кода.
This commit is contained in:
@@ -4,6 +4,7 @@ const Size = basic_models.Size;
|
|||||||
const Document = @This();
|
const Document = @This();
|
||||||
|
|
||||||
pub const Object = @import("Object.zig");
|
pub const Object = @import("Object.zig");
|
||||||
|
const shape = @import("shape.zig");
|
||||||
|
|
||||||
size: Size,
|
size: Size,
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
@@ -27,13 +28,8 @@ pub fn addObject(self: *Document, template: Object) !void {
|
|||||||
try self.objects.append(self.allocator, obj);
|
try self.objects.append(self.allocator, obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn addShape(self: *Document, parent: ?*Object, shape: Object.ShapeKind) !void {
|
pub fn addShape(self: *Document, parent: ?*Object, shape_kind: Object.ShapeKind) !void {
|
||||||
const obj = switch (shape) {
|
const obj = try shape.createObject(self.allocator, shape_kind);
|
||||||
.line => try Object.createLine(self.allocator),
|
|
||||||
.ellipse => try Object.createEllipse(self.allocator),
|
|
||||||
.broken => try Object.createBrokenLine(self.allocator),
|
|
||||||
.arc => return error.ArcNotImplemented,
|
|
||||||
};
|
|
||||||
if (parent) |p| {
|
if (parent) |p| {
|
||||||
try p.addChild(self.allocator, obj);
|
try p.addChild(self.allocator, obj);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const basic_models = @import("basic_models.zig");
|
|
||||||
const Size = basic_models.Size;
|
|
||||||
const Point2 = basic_models.Point2;
|
|
||||||
const Property = @import("Property.zig").Property;
|
const Property = @import("Property.zig").Property;
|
||||||
const PropertyData = @import("Property.zig").Data;
|
const PropertyData = @import("Property.zig").Data;
|
||||||
const defaultCommonProperties = @import("Property.zig").defaultCommonProperties;
|
|
||||||
const Object = @This();
|
const Object = @This();
|
||||||
|
|
||||||
pub const ShapeKind = enum {
|
pub const ShapeKind = enum {
|
||||||
@@ -68,41 +64,3 @@ pub fn deinit(self: *Object, allocator: std.mem.Allocator) void {
|
|||||||
self.properties.deinit(allocator);
|
self.properties.deinit(allocator);
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn createWithCommonProperties(allocator: std.mem.Allocator, shape: ShapeKind) !Object {
|
|
||||||
var properties_list = std.ArrayList(Property).empty;
|
|
||||||
errdefer properties_list.deinit(allocator);
|
|
||||||
for (defaultCommonProperties) |prop| try properties_list.append(allocator, prop);
|
|
||||||
return .{
|
|
||||||
.shape = shape,
|
|
||||||
.properties = properties_list,
|
|
||||||
.children = std.ArrayList(Object).empty,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn createEllipse(allocator: std.mem.Allocator) !Object {
|
|
||||||
var obj = try createWithCommonProperties(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 createWithCommonProperties(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 createWithCommonProperties(allocator, .broken);
|
|
||||||
errdefer obj.deinit(allocator);
|
|
||||||
var points = std.ArrayList(Point2).empty;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -29,3 +29,11 @@ pub const Scale2 = struct {
|
|||||||
scale_x: f32 = 1,
|
scale_x: f32 = 1,
|
||||||
scale_y: f32 = 1,
|
scale_y: f32 = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Прямоугольник в координатах документа (f32), например локальные границы объекта.
|
||||||
|
pub const Rect = struct {
|
||||||
|
x: f32 = 0,
|
||||||
|
y: f32 = 0,
|
||||||
|
w: f32 = 0,
|
||||||
|
h: f32 = 0,
|
||||||
|
};
|
||||||
|
|||||||
99
src/models/shape.zig
Normal file
99
src/models/shape.zig
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
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 basic_models = @import("basic_models.zig");
|
||||||
|
const line = @import("shape/line.zig");
|
||||||
|
const ellipse = @import("shape/ellipse.zig");
|
||||||
|
const broken = @import("shape/broken.zig");
|
||||||
|
const arc = @import("shape/arc.zig");
|
||||||
|
|
||||||
|
pub const Rect = basic_models.Rect;
|
||||||
|
|
||||||
|
/// Создаёт объект с общими свойствами по умолчанию и специфичными для типа фигуры.
|
||||||
|
pub fn createObject(allocator: std.mem.Allocator, shape_kind: Object.ShapeKind) !Object {
|
||||||
|
var obj = try createWithCommonProperties(allocator, shape_kind);
|
||||||
|
errdefer obj.deinit(allocator);
|
||||||
|
switch (shape_kind) {
|
||||||
|
.line => try line.appendDefaultShapeProperties(allocator, &obj),
|
||||||
|
.ellipse => try ellipse.appendDefaultShapeProperties(allocator, &obj),
|
||||||
|
.broken => try broken.appendDefaultShapeProperties(allocator, &obj),
|
||||||
|
.arc => try arc.appendDefaultShapeProperties(allocator, &obj),
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn createWithCommonProperties(allocator: std.mem.Allocator, shape_kind: Object.ShapeKind) !Object {
|
||||||
|
var properties_list = std.ArrayList(Property).empty;
|
||||||
|
errdefer properties_list.deinit(allocator);
|
||||||
|
for (defaultCommonProperties) |prop| try properties_list.append(allocator, prop);
|
||||||
|
return .{
|
||||||
|
.shape = shape_kind,
|
||||||
|
.properties = properties_list,
|
||||||
|
.children = std.ArrayList(Object).empty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Проверяет, что объект имеет ожидаемый тип и все требуемые для этого типа свойства.
|
||||||
|
pub fn ensure(obj: *const Object, expected_kind: Object.ShapeKind) !void {
|
||||||
|
if (obj.shape != expected_kind) return error.WrongShapeKind;
|
||||||
|
const tags = requiredTagsFor(expected_kind);
|
||||||
|
for (tags) |tag| {
|
||||||
|
if (obj.getProperty(tag) == null) return error.MissingRequiredProperty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn requiredTagsFor(kind: Object.ShapeKind) []const std.meta.Tag(PropertyData) {
|
||||||
|
return switch (kind) {
|
||||||
|
.line => line.getRequiredTags(),
|
||||||
|
.ellipse => ellipse.getRequiredTags(),
|
||||||
|
.broken => broken.getRequiredTags(),
|
||||||
|
.arc => arc.getRequiredTags(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Локальные границы объекта (AABB в своих координатах).
|
||||||
|
pub fn getLocalBounds(obj: *const Object) ?Rect {
|
||||||
|
ensure(obj, obj.shape) catch return null;
|
||||||
|
return switch (obj.shape) {
|
||||||
|
.line => line.getLocalBounds(obj),
|
||||||
|
.ellipse => ellipse.getLocalBounds(obj),
|
||||||
|
.broken => broken.getLocalBounds(obj),
|
||||||
|
.arc => arc.getLocalBounds(obj),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "getLocalBounds" {
|
||||||
|
const shape = @This();
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer _ = gpa.deinit();
|
||||||
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
|
var line_obj = try shape.createObject(allocator, .line);
|
||||||
|
defer line_obj.deinit(allocator);
|
||||||
|
const line_bounds = getLocalBounds(&line_obj);
|
||||||
|
try std.testing.expect(line_bounds != null);
|
||||||
|
try std.testing.expect(line_bounds.?.x == 0);
|
||||||
|
try std.testing.expect(line_bounds.?.y == 0);
|
||||||
|
try std.testing.expect(line_bounds.?.w == 100);
|
||||||
|
try std.testing.expect(line_bounds.?.h == 0);
|
||||||
|
|
||||||
|
var ellipse_obj = try shape.createObject(allocator, .ellipse);
|
||||||
|
defer ellipse_obj.deinit(allocator);
|
||||||
|
const ellipse_bounds = getLocalBounds(&ellipse_obj);
|
||||||
|
try std.testing.expect(ellipse_bounds != null);
|
||||||
|
try std.testing.expect(ellipse_bounds.?.x == -50);
|
||||||
|
try std.testing.expect(ellipse_bounds.?.y == -50);
|
||||||
|
try std.testing.expect(ellipse_bounds.?.w == 100);
|
||||||
|
try std.testing.expect(ellipse_bounds.?.h == 100);
|
||||||
|
|
||||||
|
var broken_obj = try shape.createObject(allocator, .broken);
|
||||||
|
defer broken_obj.deinit(allocator);
|
||||||
|
const broken_bounds = getLocalBounds(&broken_obj);
|
||||||
|
try std.testing.expect(broken_bounds != null);
|
||||||
|
try std.testing.expect(broken_bounds.?.x == 0);
|
||||||
|
try std.testing.expect(broken_bounds.?.y == 0);
|
||||||
|
try std.testing.expect(broken_bounds.?.w == 80);
|
||||||
|
try std.testing.expect(broken_bounds.?.h == 60);
|
||||||
|
}
|
||||||
21
src/models/shape/arc.zig
Normal file
21
src/models/shape/arc.zig
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Object = @import("../Object.zig");
|
||||||
|
const PropertyData = @import("../Property.zig").Data;
|
||||||
|
const Rect = @import("../basic_models.zig").Rect;
|
||||||
|
|
||||||
|
/// Теги обязательных свойств (заглушка: arc пока не реализован).
|
||||||
|
pub fn getRequiredTags() []const std.meta.Tag(PropertyData) {
|
||||||
|
return &[_]std.meta.Tag(PropertyData){};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Добавляет свойства по умолчанию для дуги (заглушка).
|
||||||
|
pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object) !void {
|
||||||
|
_ = allocator;
|
||||||
|
_ = obj;
|
||||||
|
return error.ArcNotImplemented;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Локальные границы дуги (заглушка: возвращает null).
|
||||||
|
pub fn getLocalBounds(_: *const Object) ?Rect {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
47
src/models/shape/broken.zig
Normal file
47
src/models/shape/broken.zig
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Object = @import("../Object.zig");
|
||||||
|
const Property = @import("../Property.zig").Property;
|
||||||
|
const PropertyData = @import("../Property.zig").Data;
|
||||||
|
const Point2 = @import("../basic_models.zig").Point2;
|
||||||
|
const Rect = @import("../basic_models.zig").Rect;
|
||||||
|
|
||||||
|
/// Точки ломаной по умолчанию (для создания).
|
||||||
|
pub const default_points = [_]Point2{
|
||||||
|
.{ .x = 0, .y = 0 },
|
||||||
|
.{ .x = 80, .y = 0 },
|
||||||
|
.{ .x = 80, .y = 60 },
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Теги обязательных свойств (у ломаной нет const default_shape_properties, только default_points).
|
||||||
|
pub fn getRequiredTags() []const std.meta.Tag(PropertyData) {
|
||||||
|
return &[_]std.meta.Tag(PropertyData){.points};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Добавляет к объекту свойства по умолчанию для ломаной (points из default_points).
|
||||||
|
pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object) !void {
|
||||||
|
var points = std.ArrayList(Point2).empty;
|
||||||
|
try points.appendSlice(allocator, &default_points);
|
||||||
|
try obj.properties.append(allocator, .{ .data = .{ .points = points } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Локальные границы ломаной: AABB по всем точкам. Возвращает null, если точек нет.
|
||||||
|
pub fn getLocalBounds(obj: *const Object) ?Rect {
|
||||||
|
const p = obj.getProperty(.points) orelse return null;
|
||||||
|
if (p.points.items.len == 0) return null;
|
||||||
|
var min_x: f32 = p.points.items[0].x;
|
||||||
|
var max_x: f32 = min_x;
|
||||||
|
var min_y: f32 = p.points.items[0].y;
|
||||||
|
var max_y: f32 = min_y;
|
||||||
|
for (p.points.items[1..]) |pt| {
|
||||||
|
min_x = @min(min_x, pt.x);
|
||||||
|
max_x = @max(max_x, pt.x);
|
||||||
|
min_y = @min(min_y, pt.y);
|
||||||
|
max_y = @max(max_y, pt.y);
|
||||||
|
}
|
||||||
|
return .{
|
||||||
|
.x = min_x,
|
||||||
|
.y = min_y,
|
||||||
|
.w = max_x - min_x,
|
||||||
|
.h = max_y - min_y,
|
||||||
|
};
|
||||||
|
}
|
||||||
31
src/models/shape/ellipse.zig
Normal file
31
src/models/shape/ellipse.zig
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Object = @import("../Object.zig");
|
||||||
|
const Property = @import("../Property.zig").Property;
|
||||||
|
const PropertyData = @import("../Property.zig").Data;
|
||||||
|
const Rect = @import("../basic_models.zig").Rect;
|
||||||
|
|
||||||
|
/// Свойства фигуры по умолчанию (для создания и проверки типа). Теги для ensure выводятся отсюда.
|
||||||
|
pub const default_shape_properties = [_]Property{
|
||||||
|
.{ .data = .{ .radii = .{ .x = 50, .y = 50 } } },
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Теги обязательных свойств (выводятся из default_shape_properties).
|
||||||
|
pub fn getRequiredTags() []const std.meta.Tag(PropertyData) {
|
||||||
|
return &([_]std.meta.Tag(PropertyData){std.meta.activeTag(default_shape_properties[0].data)});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Добавляет к объекту свойства по умолчанию для эллипса.
|
||||||
|
pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object) !void {
|
||||||
|
for (default_shape_properties) |prop| try obj.properties.append(allocator, prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Локальные границы эллипса: [-radii.x, -radii.y] .. [radii.x, radii.y].
|
||||||
|
pub fn getLocalBounds(obj: *const Object) ?Rect {
|
||||||
|
const r = obj.getProperty(.radii) orelse return null;
|
||||||
|
return .{
|
||||||
|
.x = -r.radii.x,
|
||||||
|
.y = -r.radii.y,
|
||||||
|
.w = 2 * r.radii.x,
|
||||||
|
.h = 2 * r.radii.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
35
src/models/shape/line.zig
Normal file
35
src/models/shape/line.zig
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Object = @import("../Object.zig");
|
||||||
|
const Property = @import("../Property.zig").Property;
|
||||||
|
const PropertyData = @import("../Property.zig").Data;
|
||||||
|
const Rect = @import("../basic_models.zig").Rect;
|
||||||
|
|
||||||
|
/// Свойства фигуры по умолчанию (для создания и проверки типа). Теги для ensure выводятся отсюда.
|
||||||
|
pub const default_shape_properties = [_]Property{
|
||||||
|
.{ .data = .{ .end_point = .{ .x = 100, .y = 0 } } },
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Теги обязательных свойств (выводятся из default_shape_properties).
|
||||||
|
pub fn getRequiredTags() []const std.meta.Tag(PropertyData) {
|
||||||
|
return &([_]std.meta.Tag(PropertyData){std.meta.activeTag(default_shape_properties[0].data)});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Добавляет к объекту свойства по умолчанию для линии.
|
||||||
|
pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object) !void {
|
||||||
|
for (default_shape_properties) |prop| try obj.properties.append(allocator, prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Локальные границы линии: от (0,0) до end_point.
|
||||||
|
pub fn getLocalBounds(obj: *const Object) ?Rect {
|
||||||
|
const ep = obj.getProperty(.end_point) orelse return null;
|
||||||
|
const min_x = @min(0, ep.end_point.x);
|
||||||
|
const max_x = @max(0, ep.end_point.x);
|
||||||
|
const min_y = @min(0, ep.end_point.y);
|
||||||
|
const max_y = @max(0, ep.end_point.y);
|
||||||
|
return .{
|
||||||
|
.x = min_x,
|
||||||
|
.y = min_y,
|
||||||
|
.w = max_x - min_x,
|
||||||
|
.h = max_y - min_y,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
test "discover tests" {
|
test "discover tests" {
|
||||||
_ = @import("main.zig");
|
_ = @import("main.zig");
|
||||||
_ = @import("models/Property.zig");
|
_ = @import("models/Property.zig");
|
||||||
|
_ = @import("models/shape.zig");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Убедиться, что выполнились все ожидаемые тесты: этот тест пройдёт только если до него дошли (т.е. все предыдущие прошли).
|
// Убедиться, что выполнились все ожидаемые тесты: этот тест пройдёт только если до него дошли (т.е. все предыдущие прошли).
|
||||||
|
|||||||
Reference in New Issue
Block a user