Красота
This commit is contained in:
@@ -33,6 +33,8 @@ cursor_document_point: ?Point2_f = null,
|
|||||||
draw_document: bool = true,
|
draw_document: bool = true,
|
||||||
/// Rect тулбара (из предыдущего кадра) для исключения кликов по нему из handleCanvasMouse.
|
/// Rect тулбара (из предыдущего кадра) для исключения кликов по нему из handleCanvasMouse.
|
||||||
toolbar_rect_scale: ?dvui.RectScale = null,
|
toolbar_rect_scale: ?dvui.RectScale = null,
|
||||||
|
/// Rect панели свойств (из предыдущего кадра) для исключения кликов по нему из handleCanvasMouse.
|
||||||
|
properties_rect_scale: ?dvui.RectScale = null,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, document: *Document, engine: RenderEngine) Canvas {
|
pub fn init(allocator: std.mem.Allocator, document: *Document, engine: RenderEngine) Canvas {
|
||||||
return .{
|
return .{
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ pub const OpenDocument = struct {
|
|||||||
&self.document,
|
&self.document,
|
||||||
(&self.cpu_render).renderEngine(),
|
(&self.cpu_render).renderEngine(),
|
||||||
);
|
);
|
||||||
|
self.selected_object = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *OpenDocument) void {
|
pub fn deinit(self: *OpenDocument) void {
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ 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 Document = @import("../models/Document.zig");
|
||||||
|
const Property = @import("../models/Property.zig").Property;
|
||||||
|
const PropertyData = @import("../models/Property.zig").Data;
|
||||||
const Rect_i = @import("../models/basic_models.zig").Rect_i;
|
const Rect_i = @import("../models/basic_models.zig").Rect_i;
|
||||||
const Tool = @import("../toolbar/Tool.zig");
|
const Tool = @import("../toolbar/Tool.zig");
|
||||||
|
|
||||||
pub fn canvasView(canvas: *Canvas, content_rect_scale: dvui.RectScale) void {
|
pub fn canvasView(canvas: *Canvas, selected_object: ?*Document.Object, 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));
|
||||||
{
|
{
|
||||||
var overlay = dvui.overlay(@src(), .{ .expand = .both });
|
var overlay = dvui.overlay(@src(), .{ .expand = .both });
|
||||||
@@ -45,6 +48,27 @@ pub fn canvasView(canvas: *Canvas, content_rect_scale: dvui.RectScale) void {
|
|||||||
canvas.toolbar_rect_scale = toolbar_box.data().contentRectScale();
|
canvas.toolbar_rect_scale = toolbar_box.data().contentRectScale();
|
||||||
toolbar_box.deinit();
|
toolbar_box.deinit();
|
||||||
|
|
||||||
|
// Панель свойств поверх scroll (правый верхний угол)
|
||||||
|
if (selected_object) |obj| {
|
||||||
|
var properties_box = dvui.box(
|
||||||
|
@src(),
|
||||||
|
.{ .dir = .horizontal },
|
||||||
|
.{
|
||||||
|
.expand = .none,
|
||||||
|
.background = false,
|
||||||
|
.gravity_x = 1.0,
|
||||||
|
.gravity_y = 0.0,
|
||||||
|
.margin = dvui.Rect{ .w = 8, .y = 8 },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
{
|
||||||
|
drawPropertiesPanel(canvas, obj);
|
||||||
|
}
|
||||||
|
// Сохраняем rect панели свойств для следующего кадра — в handleCanvasMouse исключаем из него клики
|
||||||
|
canvas.properties_rect_scale = properties_box.data().contentRectScale();
|
||||||
|
properties_box.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
dvui.label(@src(), "Canvas", .{}, .{ .gravity_x = 0.5, .gravity_y = 0.0 });
|
dvui.label(@src(), "Canvas", .{}, .{ .gravity_x = 0.5, .gravity_y = 0.0 });
|
||||||
}
|
}
|
||||||
overlay.deinit();
|
overlay.deinit();
|
||||||
@@ -181,6 +205,12 @@ fn handleCanvasMouse(canvas: *Canvas, scroll: *dvui.ScrollAreaWidget) void {
|
|||||||
const r = trs.r;
|
const r = trs.r;
|
||||||
if (pt.x >= 0 and pt.x * trs.s < r.w and pt.y >= 0 and pt.y * trs.s < r.h) continue;
|
if (pt.x >= 0 and pt.x * trs.s < r.w and pt.y >= 0 and pt.y * trs.s < r.h) continue;
|
||||||
}
|
}
|
||||||
|
// Не обрабатывать клик, если он попал в область панели свойств (rect с предыдущего кадра).
|
||||||
|
if (canvas.properties_rect_scale) |prs| {
|
||||||
|
const pt = prs.pointFromPhysical(mouse.p);
|
||||||
|
const r = prs.r;
|
||||||
|
if (pt.x >= 0 and pt.x * prs.s < r.w and pt.y >= 0 and pt.y * prs.s < r.h) continue;
|
||||||
|
}
|
||||||
|
|
||||||
const viewport_pt = scroll_data.contentRectScale().pointFromPhysical(mouse.p);
|
const viewport_pt = scroll_data.contentRectScale().pointFromPhysical(mouse.p);
|
||||||
const content_pt = dvui.Point{
|
const content_pt = dvui.Point{
|
||||||
@@ -242,3 +272,218 @@ fn drawToolbar(canvas: *Canvas) void {
|
|||||||
}
|
}
|
||||||
bar.deinit();
|
bar.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn drawPropertiesPanel(canvas: *Canvas, selected_object: *Document.Object) void {
|
||||||
|
var panel = dvui.box(
|
||||||
|
@src(),
|
||||||
|
.{ .dir = .vertical },
|
||||||
|
.{
|
||||||
|
.gravity_x = 1.0,
|
||||||
|
.gravity_y = 0.0,
|
||||||
|
.margin = dvui.Rect{ .x = 8, .y = 8 },
|
||||||
|
.padding = dvui.Rect.all(8),
|
||||||
|
.corner_radius = dvui.Rect.all(8),
|
||||||
|
.background = true,
|
||||||
|
.color_fill = dvui.Color.black.opacity(0.2),
|
||||||
|
.min_size_content = .{ .w = 220 },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
{
|
||||||
|
dvui.label(@src(), "Properties", .{}, .{});
|
||||||
|
for (selected_object.properties.items, 0..) |*prop, i| {
|
||||||
|
drawPropertyEditor(canvas, selected_object, prop, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panel.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drawPropertyEditor(canvas: *Canvas, obj: *Document.Object, prop: *const Property, row_index: usize) void {
|
||||||
|
const row_id: usize = row_index * 16;
|
||||||
|
var row = dvui.box(
|
||||||
|
@src(),
|
||||||
|
.{ .dir = .vertical },
|
||||||
|
.{
|
||||||
|
.id_extra = row_id,
|
||||||
|
.expand = .horizontal,
|
||||||
|
.padding = dvui.Rect{ .y = 2 },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
{
|
||||||
|
const tag = std.meta.activeTag(prop.data);
|
||||||
|
dvui.labelNoFmt(@src(), propertyLabel(tag), .{}, .{});
|
||||||
|
|
||||||
|
switch (prop.data) {
|
||||||
|
.position => |pos| {
|
||||||
|
var next = pos;
|
||||||
|
const doc = canvas.document;
|
||||||
|
const min_x = -doc.size.w;
|
||||||
|
const max_x = doc.size.w;
|
||||||
|
const min_y = -doc.size.h;
|
||||||
|
const max_y = doc.size.h;
|
||||||
|
var changed = false;
|
||||||
|
changed = dvui.sliderEntry(@src(), "x: {d:0.2}", .{ .value = &next.x, .min = min_x, .max = max_x, .interval = 0.1 }, .{ .expand = .horizontal }) or changed;
|
||||||
|
changed = dvui.sliderEntry(@src(), "y: {d:0.2}", .{ .value = &next.y, .min = min_y, .max = max_y, .interval = 0.1 }, .{ .expand = .horizontal }) or changed;
|
||||||
|
if (changed) {
|
||||||
|
obj.setProperty(canvas.document.allocator, .{ .data = .{ .position = next } }) catch {};
|
||||||
|
canvas.requestRedraw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.angle => |angle| {
|
||||||
|
var next = angle;
|
||||||
|
if (dvui.sliderEntry(@src(), "{d:0.2} rad", .{ .value = &next, .min = -std.math.pi * 2.0, .max = std.math.pi * 2.0, .interval = 0.01 }, .{ .expand = .horizontal })) {
|
||||||
|
obj.setProperty(canvas.document.allocator, .{ .data = .{ .angle = next } }) catch {};
|
||||||
|
canvas.requestRedraw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.scale => |scale| {
|
||||||
|
var next = scale;
|
||||||
|
var changed = false;
|
||||||
|
changed = dvui.sliderEntry(@src(), "x: {d:0.2}", .{ .value = &next.scale_x, .min = 0.0, .max = 10.0, .interval = 0.01 }, .{ .expand = .horizontal }) or changed;
|
||||||
|
changed = dvui.sliderEntry(@src(), "y: {d:0.2}", .{ .value = &next.scale_y, .min = 0.0, .max = 10.0, .interval = 0.01 }, .{ .expand = .horizontal }) or changed;
|
||||||
|
if (changed) {
|
||||||
|
obj.setProperty(canvas.document.allocator, .{ .data = .{ .scale = next } }) catch {};
|
||||||
|
canvas.requestRedraw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.visible => |v| {
|
||||||
|
var next = v;
|
||||||
|
if (dvui.checkbox(@src(), &next, "Visible", .{})) {
|
||||||
|
obj.setProperty(canvas.document.allocator, .{ .data = .{ .visible = next } }) catch {};
|
||||||
|
canvas.requestRedraw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.opacity => |opacity| {
|
||||||
|
var next = opacity;
|
||||||
|
if (dvui.sliderEntry(@src(), "{d:0.2}", .{ .value = &next, .min = 0.0, .max = 1.0, .interval = 0.01 }, .{ .expand = .horizontal })) {
|
||||||
|
obj.setProperty(canvas.document.allocator, .{ .data = .{ .opacity = next } }) catch {};
|
||||||
|
canvas.requestRedraw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.locked => |v| {
|
||||||
|
var next = v;
|
||||||
|
if (dvui.checkbox(@src(), &next, "Locked", .{})) {
|
||||||
|
obj.setProperty(canvas.document.allocator, .{ .data = .{ .locked = next } }) catch {};
|
||||||
|
canvas.requestRedraw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.size => |size| {
|
||||||
|
var next = size;
|
||||||
|
const doc = canvas.document;
|
||||||
|
var changed = false;
|
||||||
|
changed = dvui.sliderEntry(@src(), "w: {d:0.2}", .{ .value = &next.w, .min = 0.0, .max = doc.size.w, .interval = 1.0 }, .{ .expand = .horizontal }) or changed;
|
||||||
|
changed = dvui.sliderEntry(@src(), "h: {d:0.2}", .{ .value = &next.h, .min = 0.0, .max = doc.size.h, .interval = 1.0 }, .{ .expand = .horizontal }) or changed;
|
||||||
|
if (changed) {
|
||||||
|
obj.setProperty(canvas.document.allocator, .{ .data = .{ .size = next } }) catch {};
|
||||||
|
canvas.requestRedraw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.radii => |radii| {
|
||||||
|
var next = radii;
|
||||||
|
const doc = canvas.document;
|
||||||
|
var changed = false;
|
||||||
|
changed = dvui.sliderEntry(@src(), "x: {d:0.2}", .{ .value = &next.x, .min = 0.0, .max = doc.size.w, .interval = 1.0 }, .{ .expand = .horizontal }) or changed;
|
||||||
|
changed = dvui.sliderEntry(@src(), "y: {d:0.2}", .{ .value = &next.y, .min = 0.0, .max = doc.size.h, .interval = 1.0 }, .{ .expand = .horizontal }) or changed;
|
||||||
|
if (changed) {
|
||||||
|
obj.setProperty(canvas.document.allocator, .{ .data = .{ .radii = next } }) catch {};
|
||||||
|
canvas.requestRedraw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.end_point => |pt| {
|
||||||
|
var next = pt;
|
||||||
|
const doc = canvas.document;
|
||||||
|
const min_x = -doc.size.w;
|
||||||
|
const max_x = doc.size.w;
|
||||||
|
const min_y = -doc.size.h;
|
||||||
|
const max_y = doc.size.h;
|
||||||
|
var changed = false;
|
||||||
|
changed = dvui.sliderEntry(@src(), "x: {d:0.2}", .{ .value = &next.x, .min = min_x, .max = max_x, .interval = 0.1 }, .{ .expand = .horizontal }) or changed;
|
||||||
|
changed = dvui.sliderEntry(@src(), "y: {d:0.2}", .{ .value = &next.y, .min = min_y, .max = max_y, .interval = 0.1 }, .{ .expand = .horizontal }) or changed;
|
||||||
|
if (changed) {
|
||||||
|
obj.setProperty(canvas.document.allocator, .{ .data = .{ .end_point = next } }) catch {};
|
||||||
|
canvas.requestRedraw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.points => |points| {
|
||||||
|
dvui.label(@src(), "Points: {d}", .{points.items.len}, .{});
|
||||||
|
},
|
||||||
|
.fill_rgba => |rgba| {
|
||||||
|
drawColorEditor(canvas, obj, rgba, true);
|
||||||
|
},
|
||||||
|
.stroke_rgba => |rgba| {
|
||||||
|
drawColorEditor(canvas, obj, rgba, false);
|
||||||
|
},
|
||||||
|
.thickness => |t| {
|
||||||
|
var next = t;
|
||||||
|
if (dvui.sliderEntry(@src(), "{d:0.2}", .{ .value = &next, .min = 0.0, .max = 100.0, .interval = 0.1 }, .{ .expand = .horizontal })) {
|
||||||
|
obj.setProperty(canvas.document.allocator, .{ .data = .{ .thickness = next } }) catch {};
|
||||||
|
canvas.requestRedraw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drawColorEditor(canvas: *Canvas, obj: *Document.Object, rgba: u32, is_fill: bool) void {
|
||||||
|
var comps = rgbaToComponents(rgba);
|
||||||
|
var changed = false;
|
||||||
|
changed = dvui.sliderEntry(@src(), "r: {d:0.0}", .{ .value = &comps.r, .min = 0, .max = 255, .interval = 1 }, .{ .expand = .horizontal }) or changed;
|
||||||
|
changed = dvui.sliderEntry(@src(), "g: {d:0.0}", .{ .value = &comps.g, .min = 0, .max = 255, .interval = 1 }, .{ .expand = .horizontal }) or changed;
|
||||||
|
changed = dvui.sliderEntry(@src(), "b: {d:0.0}", .{ .value = &comps.b, .min = 0, .max = 255, .interval = 1 }, .{ .expand = .horizontal }) or changed;
|
||||||
|
changed = dvui.sliderEntry(@src(), "a: {d:0.0}", .{ .value = &comps.a, .min = 0, .max = 255, .interval = 1 }, .{ .expand = .horizontal }) or changed;
|
||||||
|
if (changed) {
|
||||||
|
const next = componentsToRgba(comps);
|
||||||
|
if (is_fill) {
|
||||||
|
obj.setProperty(canvas.document.allocator, .{ .data = .{ .fill_rgba = next } }) catch {};
|
||||||
|
} else {
|
||||||
|
obj.setProperty(canvas.document.allocator, .{ .data = .{ .stroke_rgba = next } }) catch {};
|
||||||
|
}
|
||||||
|
canvas.requestRedraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn propertyLabel(tag: std.meta.Tag(PropertyData)) []const u8 {
|
||||||
|
return switch (tag) {
|
||||||
|
.position => "Position",
|
||||||
|
.angle => "Angle",
|
||||||
|
.scale => "Scale",
|
||||||
|
.visible => "Visible",
|
||||||
|
.opacity => "Opacity",
|
||||||
|
.locked => "Locked",
|
||||||
|
.size => "Size",
|
||||||
|
.radii => "Radii",
|
||||||
|
.end_point => "End point",
|
||||||
|
.points => "Points",
|
||||||
|
.fill_rgba => "Fill color",
|
||||||
|
.stroke_rgba => "Stroke color",
|
||||||
|
.thickness => "Thickness",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const RgbaComponents = struct {
|
||||||
|
r: f32,
|
||||||
|
g: f32,
|
||||||
|
b: f32,
|
||||||
|
a: f32,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn rgbaToComponents(rgba: u32) RgbaComponents {
|
||||||
|
return .{
|
||||||
|
.r = @floatFromInt((rgba >> 24) & 0xFF),
|
||||||
|
.g = @floatFromInt((rgba >> 16) & 0xFF),
|
||||||
|
.b = @floatFromInt((rgba >> 8) & 0xFF),
|
||||||
|
.a = @floatFromInt((rgba >> 0) & 0xFF),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn componentsToRgba(comps: RgbaComponents) u32 {
|
||||||
|
return (@as(u32, toByte(comps.r)) << 24) |
|
||||||
|
(@as(u32, toByte(comps.g)) << 16) |
|
||||||
|
(@as(u32, toByte(comps.b)) << 8) |
|
||||||
|
(@as(u32, toByte(comps.a)) << 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toByte(value: f32) u8 {
|
||||||
|
const clamped = std.math.clamp(@round(value), 0.0, 255.0);
|
||||||
|
return @intFromFloat(clamped);
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ pub fn rightPanel(ctx: *WindowContext) void {
|
|||||||
const active_doc = ctx.activeDocument();
|
const active_doc = ctx.activeDocument();
|
||||||
if (active_doc) |doc| {
|
if (active_doc) |doc| {
|
||||||
const content_rect_scale = panel.data().contentRectScale();
|
const content_rect_scale = panel.data().contentRectScale();
|
||||||
canvas_view.canvasView(&doc.canvas, content_rect_scale);
|
canvas_view.canvasView(&doc.canvas, doc.selected_object, content_rect_scale);
|
||||||
} else {
|
} else {
|
||||||
noDocView(ctx);
|
noDocView(ctx);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user