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