Красота
This commit is contained in:
@@ -2,10 +2,13 @@ const std = @import("std");
|
||||
const dvui = @import("dvui");
|
||||
const dvui_ext = @import("dvui_ext.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 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 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();
|
||||
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 });
|
||||
}
|
||||
overlay.deinit();
|
||||
@@ -181,6 +205,12 @@ fn handleCanvasMouse(canvas: *Canvas, scroll: *dvui.ScrollAreaWidget) void {
|
||||
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;
|
||||
}
|
||||
// Не обрабатывать клик, если он попал в область панели свойств (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 content_pt = dvui.Point{
|
||||
@@ -242,3 +272,218 @@ fn drawToolbar(canvas: *Canvas) void {
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user