From b896a67fd410d2275650320f4d1774b21db5f86c Mon Sep 17 00:00:00 2001 From: Roman Pytkov Date: Mon, 23 Feb 2026 22:21:59 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=B7=D0=B8=D1=86=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BC=D1=8B=D1=88=D0=B8=20=D0=B2=20=D0=BA=D0=BE=D0=BE=D1=80?= =?UTF-8?q?=D0=B4=D0=B8=D0=BD=D0=B0=D1=82=D0=B0=D1=85=20=D0=B4=D0=BE=D0=BA?= =?UTF-8?q?=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Canvas.zig | 31 ++++++++++++++++++++++++++++--- src/ui/canvas_view.zig | 27 ++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/Canvas.zig b/src/Canvas.zig index e3269bb..527b8c6 100644 --- a/src/Canvas.zig +++ b/src/Canvas.zig @@ -4,6 +4,7 @@ const dvui = @import("dvui"); const Document = @import("models/Document.zig"); const RenderEngine = @import("render/RenderEngine.zig").RenderEngine; const ImageRect = @import("models/basic_models.zig").ImageRect; +const Point2 = @import("models/basic_models.zig").Point2; const Color = dvui.Color; const Canvas = @This(); @@ -24,6 +25,8 @@ _visible_rect: ?ImageRect = null, _zoom: f32 = 1, _redraw_pending: bool = false, _last_redraw_time_ms: i64 = 0, +/// Позиция курсора в координатах документа (обновляется в handleCanvasMouse). null если вне документа. +cursor_document_point: ?Point2 = null, pub fn init(allocator: std.mem.Allocator, document: *Document, engine: RenderEngine) Canvas { return .{ @@ -42,7 +45,7 @@ pub fn deinit(self: *Canvas) void { /// Заполнить canvas градиентом pub fn redrawExample(self: *Canvas) !void { - const full = self.getScaledImageSize(); + const full = self.getZoomedImageSize(); const vis: ImageRect = self._visible_rect orelse ImageRect{ .x = 0, .y = 0, .w = 0, .h = 0 }; @@ -102,7 +105,7 @@ pub fn processPendingRedraw(self: *Canvas) !void { try self.redrawExample(); } -pub fn getScaledImageSize(self: Canvas) ImageRect { +pub fn getZoomedImageSize(self: Canvas) ImageRect { const doc = self.document; return .{ .x = @intFromFloat(self.pos.x), @@ -112,6 +115,28 @@ pub fn getScaledImageSize(self: Canvas) ImageRect { }; } +/// Перевести точку из системы координат контента скролла (natural scale, начало — левый верхний угол скроллируемой области) +/// в координаты документа (единицы документа до зума). +/// Возвращает null, если точка вне области изображения документа. +pub fn contentPointToDocument(self: Canvas, content_point: dvui.Point, natural_scale: f32) ?Point2 { + 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{ + .x = px_x / self._zoom, + .y = px_y / self._zoom, + }; +} + /// Обновить видимую часть изображения (в пикселях холста) и сохранить в `visible_rect`. /// /// `viewport` и `scroll_offset` ожидаются в *physical* пикселях (т.е. уже умноженные на windowNaturalScale). @@ -134,7 +159,7 @@ pub fn updateVisibleImageRect(self: *Canvas, viewport: dvui.Rect, scroll_offset: } fn computeVisibleImageRect(self: Canvas, viewport: dvui.Rect, scroll_offset: dvui.Point) ImageRect { - const image_rect = self.getScaledImageSize(); + const image_rect = self.getZoomedImageSize(); const img_w: u32 = image_rect.w; const img_h: u32 = image_rect.h; diff --git a/src/ui/canvas_view.zig b/src/ui/canvas_view.zig index 3aa463c..fdabb1b 100644 --- a/src/ui/canvas_view.zig +++ b/src/ui/canvas_view.zig @@ -22,6 +22,7 @@ pub fn canvasView(canvas: *Canvas, content_rect_scale: dvui.RectScale) void { { drawCanvasContent(canvas, scroll); handleCanvasZoom(canvas, scroll); + handleCanvasMouse(canvas, scroll); } scroll.deinit(); @@ -34,7 +35,7 @@ pub fn canvasView(canvas: *Canvas, content_rect_scale: dvui.RectScale) void { fn drawCanvasContent(canvas: *Canvas, scroll: anytype) void { const natural_scale = if (canvas.native_scaling) 1 else dvui.windowNaturalScale(); - const img_size = canvas.getScaledImageSize(); + const img_size = canvas.getZoomedImageSize(); const viewport_rect = scroll.data().contentRect(); const scroll_current = dvui.Point{ .x = canvas.scroll.viewport.x, .y = canvas.scroll.viewport.y }; @@ -118,3 +119,27 @@ fn handleCanvasZoom(canvas: *Canvas, scroll: anytype) void { } } } + +/// Обрабатывает события мыши: переводит позицию курсора в координаты документа и сохраняет в canvas.cursor_document_point. +fn handleCanvasMouse(canvas: *Canvas, scroll: anytype) void { + const natural_scale = if (canvas.native_scaling) 1 else dvui.windowNaturalScale(); + + for (dvui.events()) |*e| { + switch (e.evt) { + .mouse => |*mouse| { + if (mouse.action != .press or mouse.button != .left) continue; + if (!dvui.eventMatchSimple(e, scroll.data())) continue; + + 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, + }; + canvas.cursor_document_point = canvas.contentPointToDocument(content_pt, natural_scale); + if (canvas.cursor_document_point) |point| + std.debug.print("cursor_document_point: {}\n", .{point}); + }, + else => {}, + } + } +}