diff --git a/src/Canvas.zig b/src/Canvas.zig index 17722ed..c2455fb 100644 --- a/src/Canvas.zig +++ b/src/Canvas.zig @@ -88,6 +88,10 @@ pub fn addZoom(self: *Canvas, value: f32) void { self._zoom = @max(self._zoom, 0.01); } +pub fn getZoom(self: Canvas) f32 { + return self._zoom; +} + pub fn requestRedraw(self: *Canvas) void { 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 left_n = @as(f32, @floatFromInt(img.x)) / 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 bottom_n = @as(f32, @floatFromInt(img.y + img.h)) / natural_scale; - - if (content_point.x < left_n or content_point.x >= right_n or - 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, - }; + return content_point.x >= left_n and content_point.x < right_n and + content_point.y >= top_n and content_point.y < bottom_n; } pub fn updateVisibleImageRect(self: *Canvas, viewport: dvui.Rect, scroll_offset: dvui.Point) bool { diff --git a/src/ui/canvas_view.zig b/src/ui/canvas_view.zig index ea60149..e067b1e 100644 --- a/src/ui/canvas_view.zig +++ b/src/ui/canvas_view.zig @@ -99,15 +99,39 @@ fn handleCanvasZoom(canvas: *Canvas, scroll: anytype) void { const ctrl = dvui.currentWindow().modifiers.control(); if (!ctrl) return; + const natural_scale = if (canvas.native_scaling) 1 else dvui.windowNaturalScale(); + for (dvui.events()) |*e| { switch (e.evt) { - .mouse => |mouse| { + .mouse => |*mouse| { const action = mouse.action; if (dvui.eventMatchSimple(e, scroll.data()) and (action == .wheel_x or action == .wheel_y)) { switch (action) { .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.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 => {}, } @@ -133,7 +157,8 @@ fn handleCanvasMouse(canvas: *Canvas, scroll: anytype) void { .x = viewport_pt.x + canvas.scroll.viewport.x, .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| std.debug.print("cursor_document_point: {}\n", .{point}); },