Compare commits

..

2 Commits

Author SHA1 Message Date
0d546782bb Поправлена панель настроек 2026-02-25 19:14:06 +03:00
0ee7be2002 Зум к курсору 2026-02-25 18:26:08 +03:00
3 changed files with 74 additions and 39 deletions

View File

@@ -88,6 +88,10 @@ pub fn addZoom(self: *Canvas, value: f32) void {
self._zoom = @max(self._zoom, 0.01); self._zoom = @max(self._zoom, 0.01);
} }
pub fn getZoom(self: Canvas) f32 {
return self._zoom;
}
pub fn requestRedraw(self: *Canvas) void { pub fn requestRedraw(self: *Canvas) void {
self._redraw_pending = true; self._redraw_pending = true;
} }
@@ -116,23 +120,27 @@ pub fn getZoomedImageSize(self: Canvas) Rect_i {
}; };
} }
pub fn contentPointToDocument(self: Canvas, content_point: dvui.Point, natural_scale: f32) ?Point2_f { /// Возвращает координаты точки контента в координатах документа. Всегда возвращает точку,
/// даже если она за пределами документа
pub fn contentPointToDocument(self: Canvas, content_point: dvui.Point, natural_scale: f32) Point2_f {
const img = self.getZoomedImageSize();
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 .{
.x = px_x / self._zoom,
.y = px_y / self._zoom,
};
}
/// Возвращает true, если точка контента лежит внутри холста (документа).
pub fn isContentPointOnDocument(self: Canvas, content_point: dvui.Point, natural_scale: f32) bool {
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;
const right_n = @as(f32, @floatFromInt(img.x + img.w)) / natural_scale; const right_n = @as(f32, @floatFromInt(img.x + img.w)) / natural_scale;
const bottom_n = @as(f32, @floatFromInt(img.y + img.h)) / natural_scale; const bottom_n = @as(f32, @floatFromInt(img.y + img.h)) / natural_scale;
return content_point.x >= left_n and content_point.x < right_n and
if (content_point.x < left_n or content_point.x >= right_n or content_point.y >= top_n and content_point.y < bottom_n;
content_point.y < top_n or content_point.y >= bottom_n)
return null;
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_f{
.x = px_x / self._zoom,
.y = px_y / self._zoom,
};
} }
pub fn updateVisibleImageRect(self: *Canvas, viewport: dvui.Rect, scroll_offset: dvui.Point) bool { pub fn updateVisibleImageRect(self: *Canvas, viewport: dvui.Rect, scroll_offset: dvui.Point) bool {

View File

@@ -99,15 +99,39 @@ fn handleCanvasZoom(canvas: *Canvas, scroll: anytype) void {
const ctrl = dvui.currentWindow().modifiers.control(); const ctrl = dvui.currentWindow().modifiers.control();
if (!ctrl) return; if (!ctrl) return;
const natural_scale = if (canvas.native_scaling) 1 else dvui.windowNaturalScale();
for (dvui.events()) |*e| { for (dvui.events()) |*e| {
switch (e.evt) { switch (e.evt) {
.mouse => |mouse| { .mouse => |*mouse| {
const action = mouse.action; const action = mouse.action;
if (dvui.eventMatchSimple(e, scroll.data()) and (action == .wheel_x or action == .wheel_y)) { if (dvui.eventMatchSimple(e, scroll.data()) and (action == .wheel_x or action == .wheel_y)) {
switch (action) { switch (action) {
.wheel_y => |y| { .wheel_y => |y| {
const viewport_pt = scroll.data().contentRectScale().pointFromPhysical(mouse.p);
const content_pt = dvui.Point{
.x = viewport_pt.x + canvas.scroll.viewport.x,
.y = viewport_pt.y + canvas.scroll.viewport.y,
};
const doc_pt = canvas.contentPointToDocument(content_pt, natural_scale);
canvas.addZoom(y / 1000); canvas.addZoom(y / 1000);
canvas.requestRedraw(); canvas.requestRedraw();
// Сдвигаем viewport так, чтобы точка под курсором (даже вне холста) не уезжала
const new_zoom = canvas.getZoom();
const img = canvas.getZoomedImageSize();
const new_content_x = (@as(f32, @floatFromInt(img.x)) + doc_pt.x * new_zoom) / natural_scale;
const new_content_y = (@as(f32, @floatFromInt(img.y)) + doc_pt.y * new_zoom) / natural_scale;
canvas.scroll.viewport.x = new_content_x - viewport_pt.x;
canvas.scroll.viewport.y = new_content_y - viewport_pt.y;
const viewport_rect = scroll.data().contentRect();
const content_w = @as(f32, @floatFromInt(img.x + img.w)) / natural_scale;
const content_h = @as(f32, @floatFromInt(img.y + img.h)) / natural_scale;
const max_x = @max(0, content_w - viewport_rect.w + canvas.pos.x);
const max_y = @max(0, content_h - viewport_rect.h + canvas.pos.y);
canvas.scroll.viewport.x = std.math.clamp(canvas.scroll.viewport.x, 0, max_x);
canvas.scroll.viewport.y = std.math.clamp(canvas.scroll.viewport.y, 0, max_y);
}, },
else => {}, else => {},
} }
@@ -133,7 +157,8 @@ fn handleCanvasMouse(canvas: *Canvas, scroll: anytype) void {
.x = viewport_pt.x + canvas.scroll.viewport.x, .x = viewport_pt.x + canvas.scroll.viewport.x,
.y = viewport_pt.y + canvas.scroll.viewport.y, .y = viewport_pt.y + canvas.scroll.viewport.y,
}; };
canvas.cursor_document_point = canvas.contentPointToDocument(content_pt, natural_scale); const doc_pt = canvas.contentPointToDocument(content_pt, natural_scale);
canvas.cursor_document_point = if (canvas.isContentPointOnDocument(content_pt, natural_scale)) doc_pt else null;
if (canvas.cursor_document_point) |point| if (canvas.cursor_document_point) |point|
std.debug.print("cursor_document_point: {}\n", .{point}); std.debug.print("cursor_document_point: {}\n", .{point});
}, },

View File

@@ -66,31 +66,6 @@ pub fn leftPanel(ctx: *WindowContext) void {
}, },
); );
{ {
// Верхняя часть: дерево объектов
var tree_section = dvui.box(
@src(),
.{ .dir = .vertical },
.{
.expand = .both,
.padding = dvui.Rect.all(panel_padding),
.corner_radius = dvui.Rect.all(panel_radius),
.color_fill = fill_color,
.background = true,
},
);
{
dvui.label(@src(), "Objects", .{}, .{});
var scroll = dvui.scrollArea(
@src(),
.{ .vertical = .auto },
.{ .expand = .vertical, .background = false },
);
{
objectTree(ctx);
}
scroll.deinit();
}
tree_section.deinit();
// Нижняя часть: настройки // Нижняя часть: настройки
var settings_section = dvui.box( var settings_section = dvui.box(
@@ -98,6 +73,7 @@ pub fn leftPanel(ctx: *WindowContext) void {
.{ .dir = .vertical }, .{ .dir = .vertical },
.{ .{
.expand = .horizontal, .expand = .horizontal,
.gravity_y = 1.0,
.margin = .{ .y = 5 }, .margin = .{ .y = 5 },
.padding = dvui.Rect.all(panel_padding), .padding = dvui.Rect.all(panel_padding),
.corner_radius = dvui.Rect.all(panel_radius), .corner_radius = dvui.Rect.all(panel_radius),
@@ -130,6 +106,32 @@ pub fn leftPanel(ctx: *WindowContext) void {
} }
} }
settings_section.deinit(); settings_section.deinit();
// Верхняя часть: дерево объектов
var tree_section = dvui.box(
@src(),
.{ .dir = .vertical },
.{
.expand = .both,
.padding = dvui.Rect.all(panel_padding),
.corner_radius = dvui.Rect.all(panel_radius),
.color_fill = fill_color,
.background = true,
},
);
{
dvui.label(@src(), "Objects", .{}, .{});
var scroll = dvui.scrollArea(
@src(),
.{ .vertical = .auto },
.{ .expand = .both, .background = false },
);
{
objectTree(ctx);
}
scroll.deinit();
}
tree_section.deinit();
} }
panel.deinit(); panel.deinit();
} }