Определение позиции дочернего объекта
This commit is contained in:
@@ -13,19 +13,6 @@ 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 {
|
|
||||||
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_f{ .scale_x = 1, .scale_y = 1 };
|
|
||||||
const opacity = if (obj.getProperty(.opacity)) |p| p.opacity else 1.0;
|
|
||||||
return .{
|
|
||||||
.position = pos,
|
|
||||||
.angle = angle,
|
|
||||||
.scale = scale,
|
|
||||||
.opacity = opacity,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isVisible(obj: *const Object) bool {
|
fn isVisible(obj: *const Object) bool {
|
||||||
return if (obj.getProperty(.visible)) |p| p.visible else true;
|
return if (obj.getProperty(.visible)) |p| p.visible else true;
|
||||||
}
|
}
|
||||||
@@ -37,7 +24,7 @@ fn drawObject(
|
|||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
) !void {
|
) !void {
|
||||||
if (!isVisible(obj)) return;
|
if (!isVisible(obj)) return;
|
||||||
const local = getLocalTransform(obj);
|
const local = Transform.init(obj);
|
||||||
const world = Transform.compose(parent_transform, local);
|
const world = Transform.compose(parent_transform, local);
|
||||||
ctx.setTransform(world);
|
ctx.setTransform(world);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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 Document = @import("../../models/Document.zig");
|
||||||
const Point2_f = basic_models.Point2_f;
|
const Point2_f = basic_models.Point2_f;
|
||||||
const Point2_i = basic_models.Point2_i;
|
const Point2_i = basic_models.Point2_i;
|
||||||
const Scale2_f = basic_models.Scale2_f;
|
const Scale2_f = basic_models.Scale2_f;
|
||||||
@@ -14,6 +15,19 @@ pub const Transform = struct {
|
|||||||
scale: Scale2_f = .{},
|
scale: Scale2_f = .{},
|
||||||
opacity: f32 = 1.0,
|
opacity: f32 = 1.0,
|
||||||
|
|
||||||
|
pub fn init(obj: *const Document.Object) Transform {
|
||||||
|
const pos = if (obj.getProperty(.position)) |p| p.position else 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 Scale2_f{ .scale_x = 1, .scale_y = 1 };
|
||||||
|
const opacity = if (obj.getProperty(.opacity)) |p| p.opacity else 1.0;
|
||||||
|
return .{
|
||||||
|
.position = pos,
|
||||||
|
.angle = angle,
|
||||||
|
.scale = scale,
|
||||||
|
.opacity = opacity,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Композиция: world = parent * local.
|
/// Композиция: world = parent * local.
|
||||||
pub fn compose(parent: Transform, local: Transform) Transform {
|
pub fn compose(parent: Transform, local: Transform) Transform {
|
||||||
const cos_a = std.math.cos(parent.angle);
|
const cos_a = std.math.cos(parent.angle);
|
||||||
@@ -36,6 +50,20 @@ pub const Transform = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Мировые -> локальные для заданного трансформа.
|
||||||
|
pub fn worldToLocalTransform(t: Transform, wx: f32, wy: f32) Point2_f {
|
||||||
|
const dx = wx - t.position.x;
|
||||||
|
const dy = wy - t.position.y;
|
||||||
|
const cos_a = std.math.cos(-t.angle);
|
||||||
|
const sin_a = std.math.sin(-t.angle);
|
||||||
|
const sx = if (t.scale.scale_x != 0) t.scale.scale_x else 1.0;
|
||||||
|
const sy = if (t.scale.scale_y != 0) t.scale.scale_y else 1.0;
|
||||||
|
return .{
|
||||||
|
.x = (dx * cos_a - dy * sin_a) / sx,
|
||||||
|
.y = (dx * sin_a + dy * cos_a) / sy,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Конвейер отрисовки: локальные координаты -> трансформ -> буфер.
|
/// Конвейер отрисовки: локальные координаты -> трансформ -> буфер.
|
||||||
pub const DrawContext = struct {
|
pub const DrawContext = struct {
|
||||||
pixels: []Color.PMA,
|
pixels: []Color.PMA,
|
||||||
@@ -100,17 +128,7 @@ pub const DrawContext = struct {
|
|||||||
|
|
||||||
/// Мировые -> локальные.
|
/// Мировые -> локальные.
|
||||||
pub fn worldToLocal(self: *const DrawContext, wx: f32, wy: f32) Point2_f {
|
pub fn worldToLocal(self: *const DrawContext, wx: f32, wy: f32) Point2_f {
|
||||||
const t = &self.transform;
|
return worldToLocalTransform(self.transform, wx, wy);
|
||||||
const dx = wx - t.position.x;
|
|
||||||
const dy = wy - t.position.y;
|
|
||||||
const cos_a = std.math.cos(-t.angle);
|
|
||||||
const sin_a = std.math.sin(-t.angle);
|
|
||||||
const sx = if (t.scale.scale_x != 0) t.scale.scale_x else 1.0;
|
|
||||||
const sy = if (t.scale.scale_y != 0) t.scale.scale_y else 1.0;
|
|
||||||
return .{
|
|
||||||
.x = (dx * cos_a - dy * sin_a) / sx,
|
|
||||||
.y = (dx * sin_a + dy * cos_a) / sy,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Смешивает цвет в пикселе буфера с учётом opacity трансформа. В replace_mode просто перезаписывает пиксель.
|
/// Смешивает цвет в пикселе буфера с учётом opacity трансформа. В replace_mode просто перезаписывает пиксель.
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
const Point2_f = @import("../models/basic_models.zig").Point2_f;
|
const basic_models = @import("../models/basic_models.zig");
|
||||||
|
const Point2_f = basic_models.Point2_f;
|
||||||
const Canvas = @import("../Canvas.zig");
|
const Canvas = @import("../Canvas.zig");
|
||||||
const Document = @import("../models/Document.zig");
|
const Document = @import("../models/Document.zig");
|
||||||
|
const pipeline = @import("../render/cpu/pipeline.zig");
|
||||||
|
const Transform = pipeline.Transform;
|
||||||
|
|
||||||
pub const ToolContext = struct {
|
pub const ToolContext = struct {
|
||||||
canvas: *Canvas,
|
canvas: *Canvas,
|
||||||
@@ -8,11 +11,41 @@ pub const ToolContext = struct {
|
|||||||
selected_object_id: ?u64,
|
selected_object_id: ?u64,
|
||||||
|
|
||||||
pub fn addObject(self: *const ToolContext, template: Document.Object) !void {
|
pub fn addObject(self: *const ToolContext, template: Document.Object) !void {
|
||||||
try self.canvas.document.addObjectUnderParentId(self.selected_object_id, template);
|
var obj = template;
|
||||||
|
const local_pos = self.computeLocalPosition();
|
||||||
|
try obj.setProperty(self.canvas.document.allocator, .{ .data = .{ .position = local_pos } });
|
||||||
|
try self.canvas.document.addObjectUnderParentId(self.selected_object_id, obj);
|
||||||
self.canvas.requestRedraw();
|
self.canvas.requestRedraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn computeLocalPosition(self: *const ToolContext) Point2_f {
|
||||||
|
if (self.selected_object_id) |parent_id| {
|
||||||
|
if (findWorldTransformById(self.canvas.document, parent_id)) |parent_world| {
|
||||||
|
return pipeline.worldToLocalTransform(parent_world, self.document_point.x, self.document_point.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return self.document_point;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn findWorldTransformById(doc: *Document, target_id: u64) ?Transform {
|
||||||
|
const identity = Transform{};
|
||||||
|
for (doc.objects.items) |*obj| {
|
||||||
|
if (findWorldTransformInTree(obj, identity, target_id)) |t| return t;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findWorldTransformInTree(obj: *const Document.Object, parent: Transform, target_id: u64) ?Transform {
|
||||||
|
const local = Transform.init(obj);
|
||||||
|
const world = Transform.compose(parent, local);
|
||||||
|
if (obj.id == target_id) return world;
|
||||||
|
for (obj.children.items) |*child| {
|
||||||
|
if (findWorldTransformInTree(child, world, target_id)) |t| return t;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
pub const Tool = struct {
|
pub const Tool = struct {
|
||||||
onCanvasClick: *const fn (*const ToolContext) anyerror!void,
|
onCanvasClick: *const fn (*const ToolContext) anyerror!void,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ fn onCanvasClick(ctx: *const Tool.ToolContext) !void {
|
|||||||
const canvas = ctx.canvas;
|
const canvas = ctx.canvas;
|
||||||
var obj = shape.createObject(canvas.document.allocator, .arc) catch return;
|
var obj = shape.createObject(canvas.document.allocator, .arc) catch return;
|
||||||
defer obj.deinit(canvas.allocator);
|
defer obj.deinit(canvas.allocator);
|
||||||
try obj.setProperty(canvas.document.allocator, .{ .data = .{ .position = ctx.document_point } });
|
|
||||||
try ctx.addObject(obj);
|
try ctx.addObject(obj);
|
||||||
}
|
}
|
||||||
pub const tool = Tool.Tool{ .onCanvasClick = onCanvasClick };
|
pub const tool = Tool.Tool{ .onCanvasClick = onCanvasClick };
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ fn onCanvasClick(ctx: *const Tool.ToolContext) !void {
|
|||||||
const canvas = ctx.canvas;
|
const canvas = ctx.canvas;
|
||||||
var obj = shape.createObject(canvas.document.allocator, .broken) catch return;
|
var obj = shape.createObject(canvas.document.allocator, .broken) catch return;
|
||||||
defer obj.deinit(canvas.allocator);
|
defer obj.deinit(canvas.allocator);
|
||||||
try obj.setProperty(canvas.document.allocator, .{ .data = .{ .position = ctx.document_point } });
|
|
||||||
try ctx.addObject(obj);
|
try ctx.addObject(obj);
|
||||||
}
|
}
|
||||||
pub const tool = Tool.Tool{ .onCanvasClick = onCanvasClick };
|
pub const tool = Tool.Tool{ .onCanvasClick = onCanvasClick };
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ fn onCanvasClick(ctx: *const Tool.ToolContext) !void {
|
|||||||
const canvas = ctx.canvas;
|
const canvas = ctx.canvas;
|
||||||
var obj = shape.createObject(canvas.document.allocator, .ellipse) catch return;
|
var obj = shape.createObject(canvas.document.allocator, .ellipse) catch return;
|
||||||
defer obj.deinit(canvas.allocator);
|
defer obj.deinit(canvas.allocator);
|
||||||
try obj.setProperty(canvas.document.allocator, .{ .data = .{ .position = ctx.document_point } });
|
|
||||||
try ctx.addObject(obj);
|
try ctx.addObject(obj);
|
||||||
}
|
}
|
||||||
pub const tool = Tool.Tool{ .onCanvasClick = onCanvasClick };
|
pub const tool = Tool.Tool{ .onCanvasClick = onCanvasClick };
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ fn onCanvasClick(ctx: *const Tool.ToolContext) !void {
|
|||||||
const canvas = ctx.canvas;
|
const canvas = ctx.canvas;
|
||||||
var obj = shape.createObject(canvas.document.allocator, .line) catch return;
|
var obj = shape.createObject(canvas.document.allocator, .line) catch return;
|
||||||
defer obj.deinit(canvas.allocator);
|
defer obj.deinit(canvas.allocator);
|
||||||
try obj.setProperty(canvas.document.allocator, .{ .data = .{ .position = ctx.document_point } });
|
|
||||||
try ctx.addObject(obj);
|
try ctx.addObject(obj);
|
||||||
}
|
}
|
||||||
pub const tool = Tool.Tool{ .onCanvasClick = onCanvasClick };
|
pub const tool = Tool.Tool{ .onCanvasClick = onCanvasClick };
|
||||||
|
|||||||
@@ -58,23 +58,23 @@ pub fn canvasView(canvas: *Canvas, selected_object_id: ?u64, content_rect_scale:
|
|||||||
// Панель свойств поверх scroll (правый верхний угол)
|
// Панель свойств поверх scroll (правый верхний угол)
|
||||||
if (selected_object_id) |obj_id| {
|
if (selected_object_id) |obj_id| {
|
||||||
if (canvas.document.findObjectById(obj_id)) |obj| {
|
if (canvas.document.findObjectById(obj_id)) |obj| {
|
||||||
var properties_box = dvui.box(
|
var properties_box = dvui.box(
|
||||||
@src(),
|
@src(),
|
||||||
.{ .dir = .horizontal },
|
.{ .dir = .horizontal },
|
||||||
.{
|
.{
|
||||||
.expand = .none,
|
.expand = .none,
|
||||||
.background = false,
|
.background = false,
|
||||||
.gravity_x = 1.0,
|
.gravity_x = 1.0,
|
||||||
.gravity_y = 0.0,
|
.gravity_y = 0.0,
|
||||||
.margin = dvui.Rect{ .w = 32, .y = 16, .h = 100 },
|
.margin = dvui.Rect{ .w = 32, .y = 16, .h = 100 },
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
{
|
{
|
||||||
drawPropertiesPanel(canvas, obj);
|
drawPropertiesPanel(canvas, obj);
|
||||||
}
|
}
|
||||||
// Сохраняем rect панели свойств для следующего кадра — в handleCanvasMouse исключаем из него клики
|
// Сохраняем rect панели свойств для следующего кадра — в handleCanvasMouse исключаем из него клики
|
||||||
canvas.properties_rect_scale = properties_box.data().contentRectScale();
|
canvas.properties_rect_scale = properties_box.data().contentRectScale();
|
||||||
properties_box.deinit();
|
properties_box.deinit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,8 +192,8 @@ fn handleCanvasZoom(canvas: *Canvas, scroll: anytype) void {
|
|||||||
};
|
};
|
||||||
const doc_pt = canvas.contentPointToDocument(content_pt, natural_scale);
|
const doc_pt = canvas.contentPointToDocument(content_pt, natural_scale);
|
||||||
|
|
||||||
// canvas.addZoom(y / 1000);
|
canvas.addZoom(y / 1000);
|
||||||
canvas.multZoom(1 + y / 3000);
|
// canvas.multZoom(1 + y / 3000);
|
||||||
canvas.requestRedraw();
|
canvas.requestRedraw();
|
||||||
|
|
||||||
// Сдвигаем viewport так, чтобы точка под курсором (даже вне холста) не уезжала
|
// Сдвигаем viewport так, чтобы точка под курсором (даже вне холста) не уезжала
|
||||||
|
|||||||
Reference in New Issue
Block a user