feat: Добавил толщину линий, унифицировал геометрию
Переименовал основные геометрические модели (Point, Size, Rect, Scale, Radii), явно разделив их на типы с плавающей точкой (_f) и целочисленные (_i). Обновил использование этих типов во всем проекте для улучшения типобезопасности и ясности. Ввел новое свойство thickness для объектов и реализовал его применение при отрисовке линий и ломаных. Добавил Point2_i для целочисленных координат буфера в конвейере отрисовки.
This commit is contained in:
@@ -4,9 +4,9 @@ const dvui = @import("dvui");
|
||||
const Document = @import("models/Document.zig");
|
||||
const RenderEngine = @import("render/RenderEngine.zig").RenderEngine;
|
||||
const basic_models = @import("models/basic_models.zig");
|
||||
const ImageRect = basic_models.ImageRect;
|
||||
const ImageSize = basic_models.ImageSize;
|
||||
const Point2 = @import("models/basic_models.zig").Point2;
|
||||
const Rect_i = basic_models.Rect_i;
|
||||
const Size_i = basic_models.Size_i;
|
||||
const Point2_f = @import("models/basic_models.zig").Point2_f;
|
||||
const Color = dvui.Color;
|
||||
|
||||
const Canvas = @This();
|
||||
@@ -22,11 +22,11 @@ scroll: dvui.ScrollInfo = .{
|
||||
},
|
||||
native_scaling: bool = true,
|
||||
redraw_throttle_ms: u32 = 50,
|
||||
_visible_rect: ?ImageRect = null,
|
||||
_visible_rect: ?Rect_i = null,
|
||||
_zoom: f32 = 1,
|
||||
_redraw_pending: bool = false,
|
||||
_last_redraw_time_ms: i64 = 0,
|
||||
cursor_document_point: ?Point2 = null,
|
||||
cursor_document_point: ?Point2_f = null,
|
||||
/// true — рисовать документ (render), false — пример (gradient/squares).
|
||||
draw_document: bool = true,
|
||||
|
||||
@@ -48,7 +48,7 @@ pub fn deinit(self: *Canvas) void {
|
||||
fn redraw(self: *Canvas) !void {
|
||||
const full = self.getZoomedImageSize();
|
||||
|
||||
const vis: ImageRect = self._visible_rect orelse ImageRect{ .x = 0, .y = 0, .w = 0, .h = 0 };
|
||||
const vis: Rect_i = self._visible_rect orelse Rect_i{ .x = 0, .y = 0, .w = 0, .h = 0 };
|
||||
|
||||
if (vis.w == 0 or vis.h == 0) {
|
||||
if (self.texture) |tex| {
|
||||
@@ -58,7 +58,7 @@ fn redraw(self: *Canvas) !void {
|
||||
return;
|
||||
}
|
||||
|
||||
const canvas_size: ImageSize = .{ .w = full.w, .h = full.h };
|
||||
const canvas_size: Size_i = .{ .w = full.w, .h = full.h };
|
||||
const new_texture = if (self.draw_document)
|
||||
self.render_engine.render(self.document, canvas_size, vis) catch null
|
||||
else
|
||||
@@ -106,17 +106,17 @@ pub fn processPendingRedraw(self: *Canvas) !void {
|
||||
try self.redraw();
|
||||
}
|
||||
|
||||
pub fn getZoomedImageSize(self: Canvas) ImageRect {
|
||||
pub fn getZoomedImageSize(self: Canvas) Rect_i {
|
||||
const doc = self.document;
|
||||
return .{
|
||||
.x = @intFromFloat(self.pos.x),
|
||||
.y = @intFromFloat(self.pos.y),
|
||||
.w = @intFromFloat(doc.size.width * self._zoom),
|
||||
.h = @intFromFloat(doc.size.height * self._zoom),
|
||||
.w = @intFromFloat(doc.size.w * self._zoom),
|
||||
.h = @intFromFloat(doc.size.h * self._zoom),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn contentPointToDocument(self: Canvas, content_point: dvui.Point, natural_scale: f32) ?Point2 {
|
||||
pub fn contentPointToDocument(self: Canvas, content_point: dvui.Point, natural_scale: f32) ?Point2_f {
|
||||
const img = self.getZoomedImageSize();
|
||||
const left_n = @as(f32, @floatFromInt(img.x)) / natural_scale;
|
||||
const top_n = @as(f32, @floatFromInt(img.y)) / natural_scale;
|
||||
@@ -129,7 +129,7 @@ pub fn contentPointToDocument(self: Canvas, content_point: dvui.Point, natural_s
|
||||
|
||||
const px_x = content_point.x * natural_scale - @as(f32, @floatFromInt(img.x));
|
||||
const px_y = content_point.y * natural_scale - @as(f32, @floatFromInt(img.y));
|
||||
return Point2{
|
||||
return Point2_f{
|
||||
.x = px_x / self._zoom,
|
||||
.y = px_y / self._zoom,
|
||||
};
|
||||
@@ -148,7 +148,7 @@ pub fn updateVisibleImageRect(self: *Canvas, viewport: dvui.Rect, scroll_offset:
|
||||
return false;
|
||||
}
|
||||
|
||||
fn computeVisibleImageRect(self: Canvas, viewport: dvui.Rect, scroll_offset: dvui.Point) ImageRect {
|
||||
fn computeVisibleImageRect(self: Canvas, viewport: dvui.Rect, scroll_offset: dvui.Point) Rect_i {
|
||||
const image_rect = self.getZoomedImageSize();
|
||||
|
||||
const img_w: u32 = image_rect.w;
|
||||
@@ -163,7 +163,7 @@ fn computeVisibleImageRect(self: Canvas, viewport: dvui.Rect, scroll_offset: dvu
|
||||
const vis_x: u32 = @intCast(std.math.clamp(raw_x, 0, @as(i64, img_w) - @as(i64, vis_w)));
|
||||
const vis_y: u32 = @intCast(std.math.clamp(raw_y, 0, @as(i64, img_h) - @as(i64, vis_h)));
|
||||
|
||||
return ImageRect{
|
||||
return Rect_i{
|
||||
.x = vis_x,
|
||||
.y = vis_y,
|
||||
.w = vis_w,
|
||||
|
||||
@@ -14,7 +14,7 @@ pub const OpenDocument = struct {
|
||||
canvas: Canvas,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, self: *OpenDocument) void {
|
||||
const default_size = basic_models.Size{ .width = 800, .height = 600 };
|
||||
const default_size = basic_models.Size_f{ .w = 800, .h = 600 };
|
||||
self.document = Document.init(allocator, default_size);
|
||||
self.cpu_render = CpuRenderEngine.init(allocator, .Squares);
|
||||
self.canvas = Canvas.init(allocator, &self.document, (&self.cpu_render).renderEngine());
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
const std = @import("std");
|
||||
const basic_models = @import("basic_models.zig");
|
||||
const Size = basic_models.Size;
|
||||
const Size_f = basic_models.Size_f;
|
||||
const Document = @This();
|
||||
|
||||
pub const Object = @import("Object.zig");
|
||||
const shape = @import("shape/shape.zig");
|
||||
|
||||
size: Size,
|
||||
size: Size_f,
|
||||
allocator: std.mem.Allocator,
|
||||
objects: std.ArrayList(Object),
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, size: Size) Document {
|
||||
pub fn init(allocator: std.mem.Allocator, size: Size_f) Document {
|
||||
return .{
|
||||
.size = size,
|
||||
.allocator = allocator,
|
||||
|
||||
@@ -18,6 +18,7 @@ const default_common_data = [_]PropertyData{
|
||||
.{ .opacity = 1.0 },
|
||||
.{ .locked = false },
|
||||
.{ .stroke_rgba = 0x000000FF },
|
||||
.{ .thickness = 2.0 },
|
||||
};
|
||||
|
||||
pub const defaultCommonProperties: [default_common_data.len]Property = blk: {
|
||||
@@ -47,7 +48,6 @@ pub fn setProperty(self: *Object, allocator: std.mem.Allocator, prop: Property)
|
||||
return;
|
||||
}
|
||||
}
|
||||
std.debug.print("Property not found: {s}\n", .{@tagName(prop.data)});
|
||||
return error.PropertyNotFound;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
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;
|
||||
const Point2_f = basic_models.Point2_f;
|
||||
const Scale2_f = basic_models.Scale2_f;
|
||||
const Size_f = basic_models.Size_f;
|
||||
const Radii_f = basic_models.Radii_f;
|
||||
|
||||
pub const Data = union(enum) {
|
||||
position: Point2,
|
||||
position: Point2_f,
|
||||
angle: f32,
|
||||
scale: Scale2,
|
||||
scale: Scale2_f,
|
||||
visible: bool,
|
||||
opacity: f32,
|
||||
locked: bool,
|
||||
|
||||
size: Size,
|
||||
radii: Radii,
|
||||
end_point: Point2,
|
||||
size: Size_f,
|
||||
radii: Radii_f,
|
||||
end_point: Point2_f,
|
||||
|
||||
points: std.ArrayList(Point2),
|
||||
points: std.ArrayList(Point2_f),
|
||||
|
||||
fill_rgba: u32,
|
||||
stroke_rgba: u32,
|
||||
|
||||
@@ -1,37 +1,43 @@
|
||||
pub const ImageRect = struct {
|
||||
pub const Rect_i = struct {
|
||||
x: u32,
|
||||
y: u32,
|
||||
w: u32,
|
||||
h: u32,
|
||||
};
|
||||
|
||||
pub const ImageSize = struct {
|
||||
pub const Size_i = struct {
|
||||
w: u32,
|
||||
h: u32,
|
||||
};
|
||||
|
||||
pub const Size = struct {
|
||||
width: f32,
|
||||
height: f32,
|
||||
pub const Size_f = struct {
|
||||
w: f32,
|
||||
h: f32,
|
||||
};
|
||||
|
||||
pub const Point2 = struct {
|
||||
pub const Point2_f = struct {
|
||||
x: f32 = 0,
|
||||
y: f32 = 0,
|
||||
};
|
||||
|
||||
pub const Radii = struct {
|
||||
/// Целочисленная точка (например, координаты в буфере пикселей).
|
||||
pub const Point2_i = struct {
|
||||
x: i32 = 0,
|
||||
y: i32 = 0,
|
||||
};
|
||||
|
||||
pub const Radii_f = struct {
|
||||
x: f32,
|
||||
y: f32,
|
||||
};
|
||||
|
||||
pub const Scale2 = struct {
|
||||
pub const Scale2_f = struct {
|
||||
scale_x: f32 = 1,
|
||||
scale_y: f32 = 1,
|
||||
};
|
||||
|
||||
/// Прямоугольник в координатах документа (f32), например локальные границы объекта.
|
||||
pub const Rect = struct {
|
||||
pub const Rect_f = struct {
|
||||
x: f32 = 0,
|
||||
y: f32 = 0,
|
||||
w: f32 = 0,
|
||||
|
||||
@@ -3,10 +3,10 @@ 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;
|
||||
const Size_f = basic_models.Size_f;
|
||||
const Point2_f = basic_models.Point2_f;
|
||||
const Scale2_f = basic_models.Scale2_f;
|
||||
const Radii_f = basic_models.Radii_f;
|
||||
|
||||
fn randFloat(rng: std.Random, min: f32, max: f32) f32 {
|
||||
return min + (max - min) * rng.float(f32);
|
||||
@@ -26,10 +26,10 @@ fn randomShapeKind(rng: std.Random) Object.ShapeKind {
|
||||
}
|
||||
|
||||
/// Случайно заполняет все доступные свойства объекта; позиция — в пределах документа.
|
||||
fn randomizeObjectProperties(allocator: std.mem.Allocator, doc_size: *const Size, obj: *Object, rng: std.Random) !void {
|
||||
fn randomizeObjectProperties(allocator: std.mem.Allocator, doc_size: *const Size_f, 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);
|
||||
const max_x = @max(0, doc_size.w - margin);
|
||||
const max_y = @max(0, doc_size.h - margin);
|
||||
|
||||
try obj.setProperty(allocator, .{ .data = .{
|
||||
.position = .{
|
||||
@@ -44,17 +44,19 @@ fn randomizeObjectProperties(allocator: std.mem.Allocator, doc_size: *const Size
|
||||
.scale_y = randFloat(rng, 0.25, 2.0),
|
||||
},
|
||||
} });
|
||||
try obj.setProperty(allocator, .{ .data = .{ .visible = rng.boolean() } });
|
||||
try obj.setProperty(allocator, .{ .data = .{ .visible = true } });
|
||||
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 {};
|
||||
const thickness = randFloat(rng, max_x * 0.01, max_x * 0.1);
|
||||
try obj.setProperty(allocator, .{ .data = .{ .thickness = thickness } });
|
||||
|
||||
switch (obj.shape) {
|
||||
.line => {
|
||||
const len = randFloat(rng, 20, @min(doc_size.width, doc_size.height) * 0.5);
|
||||
const len = randFloat(rng, 20, @min(doc_size.w, doc_size.h) * 0.5);
|
||||
const angle = randFloat(rng, 0, 2 * std.math.pi);
|
||||
try obj.setProperty(allocator, .{ .data = .{
|
||||
.end_point = .{
|
||||
@@ -64,7 +66,7 @@ fn randomizeObjectProperties(allocator: std.mem.Allocator, doc_size: *const Size
|
||||
} });
|
||||
},
|
||||
.ellipse => {
|
||||
const max_r = @min(120, @min(doc_size.width / 4, doc_size.height / 4));
|
||||
const max_r = @min(120, @min(doc_size.w / 4, doc_size.h / 4));
|
||||
try obj.setProperty(allocator, .{ .data = .{
|
||||
.radii = .{
|
||||
.x = randFloat(rng, 8, @max(8, max_r)),
|
||||
@@ -73,7 +75,7 @@ fn randomizeObjectProperties(allocator: std.mem.Allocator, doc_size: *const Size
|
||||
} });
|
||||
},
|
||||
.broken => {
|
||||
var points = std.ArrayList(Point2).empty;
|
||||
var points = std.ArrayList(Point2_f).empty;
|
||||
const n = rng.intRangeLessThan(usize, 2, 9);
|
||||
var x: f32 = 0;
|
||||
var y: f32 = 0;
|
||||
@@ -96,7 +98,7 @@ pub fn addRandomShapes(doc: *Document, rng: std.Random) !void {
|
||||
var total_count: usize = 0;
|
||||
const allocator = doc.allocator;
|
||||
|
||||
const n_root = rng.intRangeLessThan(usize, 1, 5);
|
||||
const n_root = rng.intRangeLessThan(usize, 6, 15);
|
||||
for (0..n_root) |_| {
|
||||
if (total_count >= max_total) break;
|
||||
var obj = try shape.createObject(allocator, randomShapeKind(rng));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const std = @import("std");
|
||||
const Object = @import("../Object.zig");
|
||||
const PropertyData = @import("../Property.zig").Data;
|
||||
const Rect = @import("../basic_models.zig").Rect;
|
||||
const Rect_f = @import("../basic_models.zig").Rect_f;
|
||||
const shape_mod = @import("shape.zig");
|
||||
|
||||
/// Теги обязательных свойств (заглушка: arc пока не реализован).
|
||||
@@ -17,7 +17,7 @@ pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object)
|
||||
}
|
||||
|
||||
/// Локальные границы дуги (заглушка: пока не реализовано).
|
||||
pub fn getLocalBounds(obj: *const Object) !Rect {
|
||||
pub fn getLocalBounds(obj: *const Object) !Rect_f {
|
||||
try shape_mod.ensure(obj, .arc);
|
||||
return error.ArcNotImplemented;
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ 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;
|
||||
const Point2_f = @import("../basic_models.zig").Point2_f;
|
||||
const Rect_f = @import("../basic_models.zig").Rect_f;
|
||||
const shape_mod = @import("shape.zig");
|
||||
|
||||
/// Точки ломаной по умолчанию (для создания).
|
||||
pub const default_points = [_]Point2{
|
||||
pub const default_points = [_]Point2_f{
|
||||
.{ .x = 0, .y = 0 },
|
||||
.{ .x = 80, .y = 0 },
|
||||
.{ .x = 80, .y = 60 },
|
||||
@@ -22,13 +22,13 @@ pub fn getRequiredTags() []const std.meta.Tag(PropertyData) {
|
||||
|
||||
/// Добавляет к объекту свойства по умолчанию для ломаной (points из default_points).
|
||||
pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object) !void {
|
||||
var points = std.ArrayList(Point2).empty;
|
||||
var points = std.ArrayList(Point2_f).empty;
|
||||
try points.appendSlice(allocator, &default_points);
|
||||
try obj.properties.append(allocator, .{ .data = .{ .points = points } });
|
||||
}
|
||||
|
||||
/// Локальные границы ломаной: AABB по всем точкам.
|
||||
pub fn getLocalBounds(obj: *const Object) !Rect {
|
||||
pub fn getLocalBounds(obj: *const Object) !Rect_f {
|
||||
try shape_mod.ensure(obj, .broken);
|
||||
const p = obj.getProperty(.points).?;
|
||||
if (p.points.items.len == 0) return error.EmptyPoints;
|
||||
|
||||
@@ -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 Rect = @import("../basic_models.zig").Rect;
|
||||
const Rect_f = @import("../basic_models.zig").Rect_f;
|
||||
const shape_mod = @import("shape.zig");
|
||||
|
||||
/// Свойства фигуры по умолчанию (для создания и проверки типа). Теги для ensure выводятся отсюда.
|
||||
@@ -21,7 +21,7 @@ pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object)
|
||||
}
|
||||
|
||||
/// Локальные границы эллипса: [-radii.x, -radii.y] .. [radii.x, radii.y].
|
||||
pub fn getLocalBounds(obj: *const Object) !Rect {
|
||||
pub fn getLocalBounds(obj: *const Object) !Rect_f {
|
||||
try shape_mod.ensure(obj, .ellipse);
|
||||
const r = obj.getProperty(.radii).?;
|
||||
return .{
|
||||
|
||||
@@ -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 Rect = @import("../basic_models.zig").Rect;
|
||||
const Rect_f = @import("../basic_models.zig").Rect_f;
|
||||
const shape_mod = @import("shape.zig");
|
||||
|
||||
/// Свойства фигуры по умолчанию (для создания и проверки типа). Теги для ensure выводятся отсюда.
|
||||
@@ -21,7 +21,7 @@ pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object)
|
||||
}
|
||||
|
||||
/// Локальные границы линии: от (0,0) до end_point.
|
||||
pub fn getLocalBounds(obj: *const Object) !Rect {
|
||||
pub fn getLocalBounds(obj: *const Object) !Rect_f {
|
||||
try shape_mod.ensure(obj, .line);
|
||||
const ep = obj.getProperty(.end_point).?;
|
||||
const min_x = @min(0, ep.end_point.x);
|
||||
|
||||
@@ -9,7 +9,7 @@ const ellipse = @import("ellipse.zig");
|
||||
const broken = @import("broken.zig");
|
||||
const arc = @import("arc.zig");
|
||||
|
||||
pub const Rect = basic_models.Rect;
|
||||
pub const Rect = basic_models.Rectf;
|
||||
|
||||
/// Создаёт объект с общими свойствами по умолчанию и специфичными для типа фигуры.
|
||||
pub fn createObject(allocator: std.mem.Allocator, shape_kind: Object.ShapeKind) !Object {
|
||||
|
||||
@@ -5,8 +5,8 @@ const RenderEngine = @import("RenderEngine.zig").RenderEngine;
|
||||
const Document = @import("../models/Document.zig");
|
||||
const basic_models = @import("../models/basic_models.zig");
|
||||
const cpu_draw = @import("cpu/draw.zig");
|
||||
const ImageSize = basic_models.ImageSize;
|
||||
const ImageRect = basic_models.ImageRect;
|
||||
const Size_i = basic_models.Size_i;
|
||||
const Rect_i = basic_models.Rect_i;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Color = dvui.Color;
|
||||
|
||||
@@ -35,7 +35,7 @@ pub fn exampleReset(self: *CpuRenderEngine) void {
|
||||
self.gradient_end = Color.PMA{ .r = random.int(u8), .g = random.int(u8), .b = random.int(u8), .a = 255 };
|
||||
}
|
||||
|
||||
fn renderGradient(self: CpuRenderEngine, pixels: []Color.PMA, width: u32, height: u32, full_w: u32, full_h: u32, visible_rect: ImageRect) void {
|
||||
fn renderGradient(self: CpuRenderEngine, pixels: []Color.PMA, width: u32, height: u32, full_w: u32, full_h: u32, visible_rect: Rect_i) void {
|
||||
var y: u32 = 0;
|
||||
while (y < height) : (y += 1) {
|
||||
var x: u32 = 0;
|
||||
@@ -61,7 +61,7 @@ fn renderGradient(self: CpuRenderEngine, pixels: []Color.PMA, width: u32, height
|
||||
}
|
||||
}
|
||||
|
||||
fn renderSquares(self: CpuRenderEngine, pixels: []Color.PMA, canvas_size: ImageSize, visible_rect: ImageRect) void {
|
||||
fn renderSquares(self: CpuRenderEngine, pixels: []Color.PMA, canvas_size: Size_i, visible_rect: Rect_i) void {
|
||||
_ = self;
|
||||
|
||||
const colors = [_]Color.PMA{
|
||||
@@ -152,7 +152,7 @@ fn renderSquares(self: CpuRenderEngine, pixels: []Color.PMA, canvas_size: ImageS
|
||||
}
|
||||
}
|
||||
|
||||
pub fn example(self: CpuRenderEngine, canvas_size: ImageSize, visible_rect: ImageRect) !?dvui.Texture {
|
||||
pub fn example(self: CpuRenderEngine, canvas_size: Size_i, visible_rect: Rect_i) !?dvui.Texture {
|
||||
const full_w = canvas_size.w;
|
||||
const full_h = canvas_size.h;
|
||||
|
||||
@@ -175,7 +175,7 @@ pub fn renderEngine(self: *CpuRenderEngine) RenderEngine {
|
||||
}
|
||||
|
||||
/// Растеризует документ: фон + рекурсивная отрисовка фигур через конвейер (трансформ, прозрачность, наложение).
|
||||
pub fn renderDocument(self: *CpuRenderEngine, document: *const Document, canvas_size: ImageSize, visible_rect: ImageRect) !?dvui.Texture {
|
||||
pub fn renderDocument(self: *CpuRenderEngine, document: *const Document, canvas_size: Size_i, visible_rect: Rect_i) !?dvui.Texture {
|
||||
const width = visible_rect.w;
|
||||
const height = visible_rect.h;
|
||||
const pixels = try self._allocator.alloc(Color.PMA, @as(usize, width) * height);
|
||||
|
||||
@@ -12,14 +12,14 @@ pub const RenderEngine = union(enum) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn example(self: RenderEngine, canvas_size: basic_models.ImageSize, visible_rect: basic_models.ImageRect) !?dvui.Texture {
|
||||
pub fn example(self: RenderEngine, canvas_size: basic_models.Size_i, visible_rect: basic_models.Rect_i) !?dvui.Texture {
|
||||
return switch (self) {
|
||||
.cpu => |cpu_r| cpu_r.example(canvas_size, visible_rect),
|
||||
};
|
||||
}
|
||||
|
||||
/// Растеризует документ в текстуру (размер и видимая область в пикселях холста).
|
||||
pub fn render(self: RenderEngine, document: *const Document, canvas_size: basic_models.ImageSize, visible_rect: basic_models.ImageRect) !?dvui.Texture {
|
||||
pub fn render(self: RenderEngine, document: *const Document, canvas_size: basic_models.Size_i, visible_rect: basic_models.Rect_i) !?dvui.Texture {
|
||||
return switch (self) {
|
||||
.cpu => |cpu_r| cpu_r.renderDocument(document, canvas_size, visible_rect),
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ const Color = @import("dvui").Color;
|
||||
|
||||
const Object = Document.Object;
|
||||
const default_stroke: Color.PMA = .{ .r = 0, .g = 0, .b = 0, .a = 255 };
|
||||
const default_thickness: f32 = 2.0;
|
||||
|
||||
/// Рисует ломаную по точкам в локальных координатах. Обводка по stroke_rgba.
|
||||
pub fn draw(ctx: *DrawContext, obj: *const Object) void {
|
||||
@@ -14,9 +15,9 @@ pub fn draw(ctx: *DrawContext, obj: *const Object) void {
|
||||
const pts = p_prop.points.items;
|
||||
if (pts.len < 2) return;
|
||||
const stroke = if (obj.getProperty(.stroke_rgba)) |s| pipeline.rgbaToPma(s.stroke_rgba) else default_stroke;
|
||||
|
||||
const thickness = if (obj.getProperty(.thickness)) |t| t.thickness else default_thickness;
|
||||
var i: usize = 0;
|
||||
while (i + 1 < pts.len) : (i += 1) {
|
||||
line.drawLine(ctx, pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y, stroke);
|
||||
line.drawLine(ctx, pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y, stroke, thickness);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,17 +6,17 @@ const ellipse = @import("ellipse.zig");
|
||||
const broken = @import("broken.zig");
|
||||
const arc = @import("arc.zig");
|
||||
const basic_models = @import("../../models/basic_models.zig");
|
||||
const ImageRect = basic_models.ImageRect;
|
||||
const ImageSize = basic_models.ImageSize;
|
||||
const Rect_i = basic_models.Rect_i;
|
||||
const Size_i = basic_models.Size_i;
|
||||
|
||||
const Object = Document.Object;
|
||||
const DrawContext = pipeline.DrawContext;
|
||||
const Transform = pipeline.Transform;
|
||||
|
||||
fn getLocalTransform(obj: *const Object) Transform {
|
||||
const pos = if (obj.getProperty(.position)) |p| p.position else basic_models.Point2{ .x = 0, .y = 0 };
|
||||
const pos = if (obj.getProperty(.position)) |p| p.position else basic_models.Point2_f{ .x = 0, .y = 0 };
|
||||
const angle = if (obj.getProperty(.angle)) |p| p.angle else 0;
|
||||
const scale = if (obj.getProperty(.scale)) |p| p.scale else basic_models.Scale2{ .scale_x = 1, .scale_y = 1 };
|
||||
const scale = if (obj.getProperty(.scale)) |p| p.scale else basic_models.Scale2_f{ .scale_x = 1, .scale_y = 1 };
|
||||
const opacity = if (obj.getProperty(.opacity)) |p| p.opacity else 1.0;
|
||||
return .{
|
||||
.position = pos,
|
||||
@@ -53,12 +53,12 @@ pub fn drawDocument(
|
||||
pixels: []@import("dvui").Color.PMA,
|
||||
buf_width: u32,
|
||||
buf_height: u32,
|
||||
visible_rect: ImageRect,
|
||||
visible_rect: Rect_i,
|
||||
document: *const Document,
|
||||
canvas_size: ImageSize,
|
||||
canvas_size: Size_i,
|
||||
) void {
|
||||
const scale_x: f32 = if (document.size.width > 0) @as(f32, @floatFromInt(canvas_size.w)) / document.size.width else 0;
|
||||
const scale_y: f32 = if (document.size.height > 0) @as(f32, @floatFromInt(canvas_size.h)) / document.size.height else 0;
|
||||
const scale_x: f32 = if (document.size.w > 0) @as(f32, @floatFromInt(canvas_size.w)) / document.size.w else 0;
|
||||
const scale_y: f32 = if (document.size.h > 0) @as(f32, @floatFromInt(canvas_size.h)) / document.size.h else 0;
|
||||
|
||||
var ctx = DrawContext{
|
||||
.pixels = pixels,
|
||||
@@ -68,7 +68,8 @@ pub fn drawDocument(
|
||||
.scale_x = scale_x,
|
||||
.scale_y = scale_y,
|
||||
};
|
||||
|
||||
// вывести visible_rect
|
||||
std.debug.print("visible_rect: {{ x: {}, y: {}, w: {}, h: {} }}\n", .{ visible_rect.x, visible_rect.y, visible_rect.w, visible_rect.h });
|
||||
const identity = Transform{};
|
||||
for (document.objects.items) |*obj| {
|
||||
drawObject(&ctx, obj, identity);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
const std = @import("std");
|
||||
const Document = @import("../../models/Document.zig");
|
||||
const pipeline = @import("pipeline.zig");
|
||||
const DrawContext = pipeline.DrawContext;
|
||||
@@ -6,6 +5,7 @@ const Color = @import("dvui").Color;
|
||||
|
||||
const Object = Document.Object;
|
||||
const default_stroke: Color.PMA = .{ .r = 0, .g = 0, .b = 0, .a = 255 };
|
||||
const default_thickness: f32 = 2.0;
|
||||
|
||||
/// Рисует линию в локальных координатах: от (0,0) до end_point. Растеризация в координатах буфера (без пробелов при зуме).
|
||||
pub fn draw(ctx: *DrawContext, obj: *const Object) void {
|
||||
@@ -13,25 +13,21 @@ pub fn draw(ctx: *DrawContext, obj: *const Object) void {
|
||||
const end_x = ep_prop.end_point.x;
|
||||
const end_y = ep_prop.end_point.y;
|
||||
const stroke = if (obj.getProperty(.stroke_rgba)) |s| pipeline.rgbaToPma(s.stroke_rgba) else default_stroke;
|
||||
|
||||
drawLine(ctx, 0, 0, end_x, end_y, stroke);
|
||||
const thickness = if (obj.getProperty(.thickness)) |t| t.thickness else default_thickness;
|
||||
drawLine(ctx, 0, 0, end_x, end_y, stroke, thickness);
|
||||
}
|
||||
|
||||
/// Линия по локальным координатам фигуры: переводит концы в буфер и рисует в пикселях буфера.
|
||||
pub fn drawLine(ctx: *DrawContext, x0: f32, y0: f32, x1: f32, y1: f32, color: Color.PMA) void {
|
||||
pub fn drawLine(ctx: *DrawContext, x0: f32, y0: f32, x1: f32, y1: f32, color: Color.PMA, thickness: f32) void {
|
||||
const w0 = ctx.localToWorld(x0, y0);
|
||||
const w1 = ctx.localToWorld(x1, y1);
|
||||
const b0 = ctx.worldToBufferF(w0.x, w0.y);
|
||||
const b1 = ctx.worldToBufferF(w1.x, w1.y);
|
||||
const bx0: i32 = @intFromFloat(std.math.round(b0.x));
|
||||
const by0: i32 = @intFromFloat(std.math.round(b0.y));
|
||||
const bx1: i32 = @intFromFloat(std.math.round(b1.x));
|
||||
const by1: i32 = @intFromFloat(std.math.round(b1.y));
|
||||
drawLineInBuffer(ctx, bx0, by0, bx1, by1, color);
|
||||
const b0 = ctx.worldToBuffer(w0.x, w0.y);
|
||||
const b1 = ctx.worldToBuffer(w1.x, w1.y);
|
||||
drawLineInBuffer(ctx, b0.x, b0.y, b1.x, b1.y, color, thickness);
|
||||
}
|
||||
|
||||
/// Брезенхем в координатах буфера; пиксели вне [0, buf_width) x [0, buf_height) пропускаются.
|
||||
pub fn drawLineInBuffer(ctx: *DrawContext, bx0: i32, by0: i32, bx1: i32, by1: i32, color: Color.PMA) void {
|
||||
fn drawLineInBuffer(ctx: *DrawContext, bx0: i32, by0: i32, bx1: i32, by1: i32, color: Color.PMA, thickness: f32) void {
|
||||
const bw: i32 = @intCast(ctx.buf_width);
|
||||
const bh: i32 = @intCast(ctx.buf_height);
|
||||
const dx: i32 = @intCast(@abs(bx1 - bx0));
|
||||
@@ -42,6 +38,8 @@ pub fn drawLineInBuffer(ctx: *DrawContext, bx0: i32, by0: i32, bx1: i32, by1: i3
|
||||
var x = bx0;
|
||||
var y = by0;
|
||||
|
||||
_ = thickness;
|
||||
|
||||
while (true) {
|
||||
if (x >= 0 and x < bw and y >= 0 and y < bh) {
|
||||
ctx.blendPixelAtBuffer(@intCast(x), @intCast(y), color);
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
const std = @import("std");
|
||||
const dvui = @import("dvui");
|
||||
const basic_models = @import("../../models/basic_models.zig");
|
||||
const Point2 = basic_models.Point2;
|
||||
const Scale2 = basic_models.Scale2;
|
||||
const ImageRect = basic_models.ImageRect;
|
||||
const Point2_f = basic_models.Point2_f;
|
||||
const Point2_i = basic_models.Point2_i;
|
||||
const Scale2_f = basic_models.Scale2_f;
|
||||
const Rect_i = basic_models.Rect_i;
|
||||
const Color = dvui.Color;
|
||||
|
||||
/// Трансформ объекта в мировых координатах документа (позиция, угол, масштаб, непрозрачность).
|
||||
pub const Transform = struct {
|
||||
position: Point2 = .{},
|
||||
position: Point2_f = .{},
|
||||
angle: f32 = 0,
|
||||
scale: Scale2 = .{},
|
||||
scale: Scale2_f = .{},
|
||||
opacity: f32 = 1.0,
|
||||
|
||||
/// Композиция: мировой трансформ = parent * local (local в пространстве родителя).
|
||||
@@ -41,7 +42,7 @@ pub const DrawContext = struct {
|
||||
pixels: []Color.PMA,
|
||||
buf_width: u32,
|
||||
buf_height: u32,
|
||||
visible_rect: ImageRect,
|
||||
visible_rect: Rect_i,
|
||||
scale_x: f32,
|
||||
scale_y: f32,
|
||||
transform: Transform = .{},
|
||||
@@ -51,7 +52,7 @@ pub const DrawContext = struct {
|
||||
}
|
||||
|
||||
/// Локальные координаты фигуры -> мировые (документ).
|
||||
pub fn localToWorld(self: *const DrawContext, local_x: f32, local_y: f32) Point2 {
|
||||
pub fn localToWorld(self: *const DrawContext, local_x: f32, local_y: f32) Point2_f {
|
||||
const t = &self.transform;
|
||||
const cos_a = std.math.cos(t.angle);
|
||||
const sin_a = std.math.sin(t.angle);
|
||||
@@ -61,8 +62,8 @@ pub const DrawContext = struct {
|
||||
};
|
||||
}
|
||||
|
||||
/// Мировые координаты документа -> координаты в буфере (могут быть вне [0, buf_w] x [0, buf_h]).
|
||||
pub fn worldToBufferF(self: *const DrawContext, wx: f32, wy: f32) Point2 {
|
||||
/// Мировые координаты документа -> координаты в буфере (float; могут быть вне [0, buf_w] x [0, buf_h]).
|
||||
pub fn worldToBufferF(self: *const DrawContext, wx: f32, wy: f32) Point2_f {
|
||||
const canvas_x = wx * self.scale_x;
|
||||
const canvas_y = wy * self.scale_y;
|
||||
const vx = @as(f32, @floatFromInt(self.visible_rect.x));
|
||||
@@ -73,8 +74,17 @@ pub const DrawContext = struct {
|
||||
};
|
||||
}
|
||||
|
||||
/// Мировые координаты документа -> целочисленные координаты в буфере (округление до ближайшего пикселя).
|
||||
pub fn worldToBuffer(self: *const DrawContext, wx: f32, wy: f32) Point2_i {
|
||||
const b = self.worldToBufferF(wx, wy);
|
||||
return .{
|
||||
.x = @intFromFloat(std.math.round(b.x)),
|
||||
.y = @intFromFloat(std.math.round(b.y)),
|
||||
};
|
||||
}
|
||||
|
||||
/// Координаты буфера -> мировые (документ). scale_x/scale_y не должны быть 0.
|
||||
pub fn bufferToWorld(self: *const DrawContext, buf_x: f32, buf_y: f32) Point2 {
|
||||
pub fn bufferToWorld(self: *const DrawContext, buf_x: f32, buf_y: f32) Point2_f {
|
||||
const vx = @as(f32, @floatFromInt(self.visible_rect.x));
|
||||
const vy = @as(f32, @floatFromInt(self.visible_rect.y));
|
||||
const canvas_x = buf_x + vx;
|
||||
@@ -88,7 +98,7 @@ pub const DrawContext = struct {
|
||||
}
|
||||
|
||||
/// Мировые координаты -> локальные фигуры (обратное к localToWorld).
|
||||
pub fn worldToLocal(self: *const DrawContext, wx: f32, wy: f32) Point2 {
|
||||
pub fn worldToLocal(self: *const DrawContext, wx: f32, wy: f32) Point2_f {
|
||||
const t = &self.transform;
|
||||
const dx = wx - t.position.x;
|
||||
const dy = wy - t.position.y;
|
||||
|
||||
@@ -2,7 +2,7 @@ const std = @import("std");
|
||||
const dvui = @import("dvui");
|
||||
const dvui_ext = @import("dvui_ext.zig");
|
||||
const Canvas = @import("../Canvas.zig");
|
||||
const ImageRect = @import("../models/basic_models.zig").ImageRect;
|
||||
const Rect_i = @import("../models/basic_models.zig").Rect_i;
|
||||
|
||||
pub fn canvasView(canvas: *Canvas, content_rect_scale: dvui.RectScale) void {
|
||||
var textured = dvui_ext.texturedBox(content_rect_scale, dvui.Rect.all(20));
|
||||
@@ -67,7 +67,7 @@ fn drawCanvasContent(canvas: *Canvas, scroll: anytype) void {
|
||||
);
|
||||
{
|
||||
if (canvas.texture) |tex| {
|
||||
const vis = canvas._visible_rect orelse ImageRect{ .x = 0, .y = 0, .w = 0, .h = 0 };
|
||||
const vis = canvas._visible_rect orelse Rect_i{ .x = 0, .y = 0, .w = 0, .h = 0 };
|
||||
const left = @as(f32, @floatFromInt(img_size.x + vis.x)) / natural_scale;
|
||||
const top = @as(f32, @floatFromInt(img_size.y + vis.y)) / natural_scale;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user