From 003644a9fda93d5e527968a139aa6c5c3405f4ba Mon Sep 17 00:00:00 2001 From: AXeonV <2607343351@qq.com> Date: Sat, 6 Dec 2025 18:37:26 +0800 Subject: [PATCH 1/5] support mouse drag to move & resize images --- .../progs/generic/format-geometry-edit.scm | 139 +++++++++++++++++- src/Edit/Interface/edit_repaint.cpp | 37 +++++ 2 files changed, 175 insertions(+), 1 deletion(-) diff --git a/TeXmacs/progs/generic/format-geometry-edit.scm b/TeXmacs/progs/generic/format-geometry-edit.scm index fb393bfdd..a2ba69515 100644 --- a/TeXmacs/progs/generic/format-geometry-edit.scm +++ b/TeXmacs/progs/generic/format-geometry-edit.scm @@ -14,7 +14,9 @@ (texmacs-module (generic format-geometry-edit) (:use (utils edit selections) (generic embedded-edit) - (generic format-drd))) + (generic format-drd) + (kernel gui kbd-handlers) + (utils library length))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Customizable step changes for length modifications @@ -506,3 +508,138 @@ (length-scale (tree-ref t 2) scale mult) (set! pinch-modified? (!= (tree->stree t) old)) (tree-go-to t :end))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Image mouse dragging for moving and resizing +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(define image-drag-start-x #f) +(define image-drag-start-y #f) +(define image-resize-handle #f) +(define image-resize-start-x #f) +(define image-resize-start-y #f) +(define image-resize-orig-w #f) +(define image-resize-orig-h #f) +(define image-resize-orig-xoff #f) +(define image-resize-orig-yoff #f) + +(define (image-get-bbox t) + (and-with rect (tree-bounding-rectangle t) + (and (== (length rect) 4) rect))) + +(define (image-point-on-handle? t) + (and-with bbox (image-get-bbox t) + (let* ((mpos (get-mouse-position)) + (mx (car mpos)) (my (cadr mpos)) + (x1 (car bbox)) (y1 (cadr bbox)) + (x2 (caddr bbox)) (y2 (cadddr bbox)) + (midx (/ (+ x1 x2) 2)) (midy (/ (+ y1 y2) 2)) + (hs 6000) + (near? (lambda (a b) (< (abs (- a b)) hs)))) + (cond + ((and (near? mx x1) (near? my y2)) 'nw) + ((and (near? mx midx) (near? my y2)) 'n) + ((and (near? mx x2) (near? my y2)) 'ne) + ((and (near? mx x2) (near? my midy)) 'e) + ((and (near? mx x2) (near? my y1)) 'se) + ((and (near? mx midx) (near? my y1)) 's) + ((and (near? mx x1) (near? my y1)) 'sw) + ((and (near? mx x1) (near? my midy)) 'w) + (else #f))))) + +(define (image-get-dimensions t) + (let* ((w-str (tm->string (tree-ref t 1))) + (h-str (tm->string (tree-ref t 2))) + (w (and w-str (not (string-null? w-str)) (length-decode w-str))) + (h (and h-str (not (string-null? h-str)) (length-decode h-str)))) + (or (and w h (list w h)) + (and-with bbox (image-get-bbox t) + (list (- (caddr bbox) (car bbox)) (- (cadddr bbox) (cadr bbox))))))) + +(define (tmpt->cm v) (/ v 60472.0)) +(define (cm->str v) (string-append (number->string v) "cm")) + +(define (image-set-size! t w h) + (when (> w 0.1) (tree-set! t 1 (cm->str w))) + (when (> h 0.1) (tree-set! t 2 (cm->str h))) + (refresh-window)) + +(define (image-apply-resize t handle dx dy) + (when (and image-resize-orig-w image-resize-orig-h) + (let* ((ow (tmpt->cm image-resize-orig-w)) + (oh (tmpt->cm image-resize-orig-h)) + (sx (tmpt->cm dx)) (sy (tmpt->cm dy)) + (nw (- ow sx)) (nh (+ oh sy)) + (xoff (or image-resize-orig-xoff 0)) + (yoff (or image-resize-orig-yoff 0)) + (set-xoff! (lambda () (tree-set! t 3 (cm->str (tmpt->cm (+ xoff dx)))))) + (set-yoff! (lambda () (tree-set! t 4 (cm->str (tmpt->cm (- yoff dy))))))) + (case handle + ((se) (image-set-size! t (+ ow sx) (- oh sy))) + ((sw) (when (> nw 0.1) (set-xoff!) (image-set-size! t nw (- oh sy)))) + ((ne) (when (> nh 0.1) (set-yoff!) (image-set-size! t (+ ow sx) nh))) + ((nw) (when (and (> nw 0.1) (> nh 0.1)) (set-xoff!) (set-yoff!) (image-set-size! t nw nh))) + ((e) (when (> (+ ow sx) 0.1) (tree-set! t 1 (cm->str (+ ow sx))) (refresh-window))) + ((w) (when (> nw 0.1) (set-xoff!) (tree-set! t 1 (cm->str nw)) (refresh-window))) + ((n) (when (> nh 0.1) (set-yoff!) (tree-set! t 2 (cm->str nh)) (refresh-window))) + ((s) (when (> (- oh sy) 0.1) (tree-set! t 2 (cm->str (- oh sy))) (refresh-window))))))) + +(define (image-apply-move t dx dy) + (define (update-offset idx delta update-start!) + (let* ((old (or (tm->string (tree-ref t idx)) "")) + (delta-cm (cm->str (tmpt->cm delta))) + (new-val (if (string-null? old) delta-cm (length-add old delta-cm)))) + (tree-set! t idx new-val) + (update-start!) + (refresh-window))) + (when (> (abs dx) 3000) + (update-offset 3 dx (lambda () (set! image-drag-start-x (+ image-drag-start-x dx))))) + (when (> (abs dy) 3000) + (update-offset 4 (- dy) (lambda () (set! image-drag-start-y (+ image-drag-start-y dy)))))) + +(define (image-reset-drag-state!) + (set! image-drag-start-x #f) + (set! image-drag-start-y #f) + (set! image-resize-handle #f) + (set! image-resize-start-x #f) + (set! image-resize-start-y #f) + (set! image-resize-orig-w #f) + (set! image-resize-orig-h #f) + (set! image-resize-orig-xoff #f) + (set! image-resize-orig-yoff #f)) + +(define (image-decode-offset t idx) + (let ((s (tm->string (tree-ref t idx)))) + (if (and s (not (string-null? s))) (length-decode s) 0))) + +(tm-define (mouse-event key x y mods time data) + (:require (and (tree-innermost image-context? #t) + (in? key '("start-drag-left" "dragging-left" "end-drag-left")))) + (and-with t (tree-innermost image-context? #t) + (cond + ((== key "start-drag-left") + (image-reset-drag-state!) + (let ((handle (image-point-on-handle? t))) + (if handle + (let ((dims (image-get-dimensions t))) + (set! image-resize-handle handle)· + (set! image-resize-start-x x) + (set! image-resize-start-y y) + (set! image-resize-orig-w (if dims (car dims) 60472)) + (set! image-resize-orig-h (if dims (cadr dims) 60472)) + (set! image-resize-orig-xoff (image-decode-offset t 3)) + (set! image-resize-orig-yoff (image-decode-offset t 4))) + (begin + (set! image-drag-start-x x) + (set! image-drag-start-y y)))) + (former key x y mods time data)) + ((== key "dragging-left") + (if image-resize-handle + (when (and image-resize-start-x image-resize-start-y) + (image-apply-resize t image-resize-handle + (- x image-resize-start-x) (- y image-resize-start-y))) + (when (and image-drag-start-x image-drag-start-y) + (image-apply-move t (- x image-drag-start-x) (- y image-drag-start-y))))) + ((== key "end-drag-left") + (image-reset-drag-state!) + (former key x y mods time data))))) diff --git a/src/Edit/Interface/edit_repaint.cpp b/src/Edit/Interface/edit_repaint.cpp index 6ced0fff3..29aecb440 100644 --- a/src/Edit/Interface/edit_repaint.cpp +++ b/src/Edit/Interface/edit_repaint.cpp @@ -173,6 +173,43 @@ edit_interface_rep::draw_selection (renderer ren, rectangle r) { ren->draw_rectangles (selection_rects & visible); #endif } + + // Draw image resize handles when cursor is inside an image + // Check if any ancestor of current path is an IMAGE + path p= path_up (tp); + while (!is_nil (p) && p != rp) { + tree st= subtree (et, p); + if (is_func (st, IMAGE)) { + // Found an IMAGE ancestor + path image_path= p; + // Get the selection rectangle for the image + selection sel= eb->find_check_selection (image_path * 0, image_path * 1); + if (sel->valid && !is_nil (sel->rs)) { + // Get the bounding box of the image + rectangle bbox= least_upper_bound (sel->rs); + SI x1= bbox->x1, y1= bbox->y1, x2= bbox->x2, y2= bbox->y2; + SI mx= (x1 + x2) / 2, my= (y1 + y2) / 2; + SI hs= 10 * ren->pixel; // handle size (radius) - visual size + + // Draw 8 resize handles: 4 corners + 4 edge midpoints + color handle_col= rgb_color (0, 120, 215); // Blue color + ren->set_pencil (pencil (handle_col, ren->pixel)); + ren->set_brush (brush (handle_col)); + + // Array of handle center positions: sw, se, nw, ne, s, n, w, e + SI hx[8]= {x1, x2, x1, x2, mx, mx, x1, x2}; + SI hy[8]= {y1, y1, y2, y2, y1, y2, my, my}; + + for (int i= 0; i < 8; i++) { + SI cx= hx[i], cy= hy[i]; + ren->fill_arc (cx - hs, cy - hs, cx + hs, cy + hs, 0, 64 * 360); + ren->arc (cx - hs, cy - hs, cx + hs, cy + hs, 0, 64 * 360); + } + } + break; + } + p= path_up (p); + } } void -- Gitee From 5bc9c6592dea4dddd6c8b4d769908b000197a69b Mon Sep 17 00:00:00 2001 From: AXeonV <2607343351@qq.com> Date: Sat, 6 Dec 2025 19:58:03 +0800 Subject: [PATCH 2/5] WIP --- TeXmacs/progs/generic/format-geometry-edit.scm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeXmacs/progs/generic/format-geometry-edit.scm b/TeXmacs/progs/generic/format-geometry-edit.scm index a2ba69515..483b5e9c6 100644 --- a/TeXmacs/progs/generic/format-geometry-edit.scm +++ b/TeXmacs/progs/generic/format-geometry-edit.scm @@ -622,7 +622,7 @@ (let ((handle (image-point-on-handle? t))) (if handle (let ((dims (image-get-dimensions t))) - (set! image-resize-handle handle)· + (set! image-resize-handle handle) (set! image-resize-start-x x) (set! image-resize-start-y y) (set! image-resize-orig-w (if dims (car dims) 60472)) -- Gitee From 45da636b1672ba40e67154c2eb252fc7c7f22c0f Mon Sep 17 00:00:00 2001 From: AXeonV <2607343351@qq.com> Date: Sat, 6 Dec 2025 23:58:03 +0800 Subject: [PATCH 3/5] fix update issue with resize handles --- src/Edit/Interface/edit_interface.hpp | 2 ++ src/Edit/Interface/edit_repaint.cpp | 28 +++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/Edit/Interface/edit_interface.hpp b/src/Edit/Interface/edit_interface.hpp index 02927df0e..faea542d4 100644 --- a/src/Edit/Interface/edit_interface.hpp +++ b/src/Edit/Interface/edit_interface.hpp @@ -75,6 +75,7 @@ protected: rectangles selection_rects; array alt_selection_rects; rectangle last_visible; + rectangle last_image_handles_rect; rectangles env_rects; rectangles foc_rects; rectangles sem_rects; @@ -140,6 +141,7 @@ public: void draw_env (renderer ren); void draw_cursor (renderer ren); void draw_selection (renderer ren, rectangle r); + void draw_resize_handles (renderer ren); void draw_graphics (renderer ren); void draw_keys (renderer ren); void draw_pre (renderer win, renderer ren, rectangle r); diff --git a/src/Edit/Interface/edit_repaint.cpp b/src/Edit/Interface/edit_repaint.cpp index 29aecb440..a7cb61ad4 100644 --- a/src/Edit/Interface/edit_repaint.cpp +++ b/src/Edit/Interface/edit_repaint.cpp @@ -174,6 +174,11 @@ edit_interface_rep::draw_selection (renderer ren, rectangle r) { #endif } + draw_resize_handles (ren); +} + +void +edit_interface_rep::draw_resize_handles (renderer ren) { // Draw image resize handles when cursor is inside an image // Check if any ancestor of current path is an IMAGE path p= path_up (tp); @@ -190,16 +195,27 @@ edit_interface_rep::draw_selection (renderer ren, rectangle r) { SI x1= bbox->x1, y1= bbox->y1, x2= bbox->x2, y2= bbox->y2; SI mx= (x1 + x2) / 2, my= (y1 + y2) / 2; SI hs= 10 * ren->pixel; // handle size (radius) - visual size + + // Calculate current handles rectangle (bbox + handles) + rectangle current_handles_rect (x1 - hs, y1 - hs, x2 + hs, y2 + hs); + // Check if we need to invalidate due to change + if (is_zero (last_image_handles_rect) || + last_image_handles_rect != current_handles_rect) { + if (!is_zero (last_image_handles_rect)) invalidate (last_image_handles_rect); + invalidate (current_handles_rect); + last_image_handles_rect= current_handles_rect; + } + // Draw 8 resize handles: 4 corners + 4 edge midpoints color handle_col= rgb_color (0, 120, 215); // Blue color ren->set_pencil (pencil (handle_col, ren->pixel)); ren->set_brush (brush (handle_col)); - + // Array of handle center positions: sw, se, nw, ne, s, n, w, e SI hx[8]= {x1, x2, x1, x2, mx, mx, x1, x2}; SI hy[8]= {y1, y1, y2, y2, y1, y2, my, my}; - + for (int i= 0; i < 8; i++) { SI cx= hx[i], cy= hy[i]; ren->fill_arc (cx - hs, cy - hs, cx + hs, cy + hs, 0, 64 * 360); @@ -210,6 +226,14 @@ edit_interface_rep::draw_selection (renderer ren, rectangle r) { } p= path_up (p); } + + // If we are not inside an image but we were previously, invalidate the old area + if (is_nil (p) || p == rp) { // We traversed to root without finding image + if (!is_zero (last_image_handles_rect)) { + invalidate (last_image_handles_rect); + last_image_handles_rect= rectangle (0, 0, 0, 0); + } + } } void -- Gitee From 872ceffad03af0dee26e2508205662a1714841e6 Mon Sep 17 00:00:00 2001 From: AXeonV <2607343351@qq.com> Date: Sun, 7 Dec 2025 11:30:13 +0800 Subject: [PATCH 4/5] WIP --- .../progs/generic/format-geometry-edit.scm | 45 +++++---- src/Edit/Interface/edit_interface.hpp | 2 +- src/Edit/Interface/edit_repaint.cpp | 91 +++++++++---------- 3 files changed, 69 insertions(+), 69 deletions(-) diff --git a/TeXmacs/progs/generic/format-geometry-edit.scm b/TeXmacs/progs/generic/format-geometry-edit.scm index 483b5e9c6..384574e70 100644 --- a/TeXmacs/progs/generic/format-geometry-edit.scm +++ b/TeXmacs/progs/generic/format-geometry-edit.scm @@ -527,6 +527,8 @@ (and-with rect (tree-bounding-rectangle t) (and (== (length rect) 4) rect))) +(define image-handle-hitbox 6000) + (define (image-point-on-handle? t) (and-with bbox (image-get-bbox t) (let* ((mpos (get-mouse-position)) @@ -534,18 +536,20 @@ (x1 (car bbox)) (y1 (cadr bbox)) (x2 (caddr bbox)) (y2 (cadddr bbox)) (midx (/ (+ x1 x2) 2)) (midy (/ (+ y1 y2) 2)) - (hs 6000) - (near? (lambda (a b) (< (abs (- a b)) hs)))) - (cond - ((and (near? mx x1) (near? my y2)) 'nw) - ((and (near? mx midx) (near? my y2)) 'n) - ((and (near? mx x2) (near? my y2)) 'ne) - ((and (near? mx x2) (near? my midy)) 'e) - ((and (near? mx x2) (near? my y1)) 'se) - ((and (near? mx midx) (near? my y1)) 's) - ((and (near? mx x1) (near? my y1)) 'sw) - ((and (near? mx x1) (near? my midy)) 'w) - (else #f))))) + (hs image-handle-hitbox) + (near? (lambda (a b) (< (abs (- a b)) hs))) + (handles `((nw ,x1 ,y2) + (n ,midx ,y2) + (ne ,x2 ,y2) + (e ,x2 ,midy) + (se ,x2 ,y1) + (s ,midx ,y1) + (sw ,x1 ,y1) + (w ,x1 ,midy)))) + (let loop ((hspec handles)) + (if (null? hspec) #f + (let* ((h (car hspec)) (hx (cadr h)) (hy (caddr h))) + (if (and (near? mx hx) (near? my hy)) (car h) (loop (cdr hspec))))))))) (define (image-get-dimensions t) (let* ((w-str (tm->string (tree-ref t 1))) @@ -569,7 +573,7 @@ (let* ((ow (tmpt->cm image-resize-orig-w)) (oh (tmpt->cm image-resize-orig-h)) (sx (tmpt->cm dx)) (sy (tmpt->cm dy)) - (nw (- ow sx)) (nh (+ oh sy)) + (nw (- ow sx)) (nh (- oh sy)) (xoff (or image-resize-orig-xoff 0)) (yoff (or image-resize-orig-yoff 0)) (set-xoff! (lambda () (tree-set! t 3 (cm->str (tmpt->cm (+ xoff dx)))))) @@ -590,12 +594,15 @@ (delta-cm (cm->str (tmpt->cm delta))) (new-val (if (string-null? old) delta-cm (length-add old delta-cm)))) (tree-set! t idx new-val) - (update-start!) - (refresh-window))) - (when (> (abs dx) 3000) - (update-offset 3 dx (lambda () (set! image-drag-start-x (+ image-drag-start-x dx))))) - (when (> (abs dy) 3000) - (update-offset 4 (- dy) (lambda () (set! image-drag-start-y (+ image-drag-start-y dy)))))) + (update-start!))) + (let ((changed? #f)) + (when (> (abs dx) 3000) + (update-offset 3 dx (lambda () (set! image-drag-start-x (+ image-drag-start-x dx)))) + (set! changed? #t)) + (when (> (abs dy) 3000) + (update-offset 4 (- dy) (lambda () (set! image-drag-start-y (+ image-drag-start-y dy)))) + (set! changed? #t)) + (when changed? (refresh-window)))) (define (image-reset-drag-state!) (set! image-drag-start-x #f) diff --git a/src/Edit/Interface/edit_interface.hpp b/src/Edit/Interface/edit_interface.hpp index faea542d4..7209a9795 100644 --- a/src/Edit/Interface/edit_interface.hpp +++ b/src/Edit/Interface/edit_interface.hpp @@ -75,7 +75,7 @@ protected: rectangles selection_rects; array alt_selection_rects; rectangle last_visible; - rectangle last_image_handles_rect; + rectangle last_image_handles; rectangles env_rects; rectangles foc_rects; rectangles sem_rects; diff --git a/src/Edit/Interface/edit_repaint.cpp b/src/Edit/Interface/edit_repaint.cpp index a7cb61ad4..ab75dd68f 100644 --- a/src/Edit/Interface/edit_repaint.cpp +++ b/src/Edit/Interface/edit_repaint.cpp @@ -173,66 +173,59 @@ edit_interface_rep::draw_selection (renderer ren, rectangle r) { ren->draw_rectangles (selection_rects & visible); #endif } - + draw_resize_handles (ren); } void edit_interface_rep::draw_resize_handles (renderer ren) { - // Draw image resize handles when cursor is inside an image + // Draw image resize handles when cursor is inside an image. + SI hs = 10 * ren->pixel; // handle radius for visuals + rectangle new_image_handles= rectangle (0, 0, 0, 0); + SI x1= 0, y1= 0, x2= 0, y2= 0, mx= 0, my= 0; + bool have_bbox= false; + // Check if any ancestor of current path is an IMAGE - path p= path_up (tp); - while (!is_nil (p) && p != rp) { + for (path p= path_up (tp); !is_nil (p) && p != rp; p= path_up (p)) { tree st= subtree (et, p); - if (is_func (st, IMAGE)) { - // Found an IMAGE ancestor - path image_path= p; - // Get the selection rectangle for the image - selection sel= eb->find_check_selection (image_path * 0, image_path * 1); - if (sel->valid && !is_nil (sel->rs)) { - // Get the bounding box of the image - rectangle bbox= least_upper_bound (sel->rs); - SI x1= bbox->x1, y1= bbox->y1, x2= bbox->x2, y2= bbox->y2; - SI mx= (x1 + x2) / 2, my= (y1 + y2) / 2; - SI hs= 10 * ren->pixel; // handle size (radius) - visual size - - // Calculate current handles rectangle (bbox + handles) - rectangle current_handles_rect (x1 - hs, y1 - hs, x2 + hs, y2 + hs); - - // Check if we need to invalidate due to change - if (is_zero (last_image_handles_rect) || - last_image_handles_rect != current_handles_rect) { - if (!is_zero (last_image_handles_rect)) invalidate (last_image_handles_rect); - invalidate (current_handles_rect); - last_image_handles_rect= current_handles_rect; - } + if (!is_func (st, IMAGE)) continue; + + selection sel= eb->find_check_selection (p * 0, p * 1); + if (!sel->valid || is_nil (sel->rs)) break; // image not drawable now + + rectangle bbox = least_upper_bound (sel->rs); + x1 = bbox->x1; + y1 = bbox->y1; + x2 = bbox->x2; + y2 = bbox->y2; + mx = (x1 + x2) / 2; + my = (y1 + y2) / 2; + new_image_handles= rectangle (x1 - hs, y1 - hs, x2 + hs, y2 + hs); + have_bbox = true; + break; // only the closest IMAGE ancestor + } - // Draw 8 resize handles: 4 corners + 4 edge midpoints - color handle_col= rgb_color (0, 120, 215); // Blue color - ren->set_pencil (pencil (handle_col, ren->pixel)); - ren->set_brush (brush (handle_col)); + if (new_image_handles != last_image_handles) { + if (!is_zero (last_image_handles)) invalidate (last_image_handles); + if (!is_zero (new_image_handles)) invalidate (new_image_handles); + last_image_handles= new_image_handles; + } - // Array of handle center positions: sw, se, nw, ne, s, n, w, e - SI hx[8]= {x1, x2, x1, x2, mx, mx, x1, x2}; - SI hy[8]= {y1, y1, y2, y2, y1, y2, my, my}; + if (!have_bbox) return; // nothing to draw - for (int i= 0; i < 8; i++) { - SI cx= hx[i], cy= hy[i]; - ren->fill_arc (cx - hs, cy - hs, cx + hs, cy + hs, 0, 64 * 360); - ren->arc (cx - hs, cy - hs, cx + hs, cy + hs, 0, 64 * 360); - } - } - break; - } - p= path_up (p); - } + // Draw 8 resize handles: 4 corners + 4 edge midpoints + color handle_col= rgb_color (0, 120, 215); // Blue color + ren->set_pencil (pencil (handle_col, ren->pixel)); + ren->set_brush (brush (handle_col)); - // If we are not inside an image but we were previously, invalidate the old area - if (is_nil (p) || p == rp) { // We traversed to root without finding image - if (!is_zero (last_image_handles_rect)) { - invalidate (last_image_handles_rect); - last_image_handles_rect= rectangle (0, 0, 0, 0); - } + // Array of handle center positions: sw, se, nw, ne, s, n, w, e + SI hx[8]= {x1, x2, x1, x2, mx, mx, x1, x2}; + SI hy[8]= {y1, y1, y2, y2, y1, y2, my, my}; + + for (int i= 0; i < 8; i++) { + SI cx= hx[i], cy= hy[i]; + ren->fill_arc (cx - hs, cy - hs, cx + hs, cy + hs, 0, 64 * 360); + ren->arc (cx - hs, cy - hs, cx + hs, cy + hs, 0, 64 * 360); } } -- Gitee From 95569822be0940650528f480333f25208c2260ec Mon Sep 17 00:00:00 2001 From: AXeonV <2607343351@qq.com> Date: Sun, 7 Dec 2025 14:06:54 +0800 Subject: [PATCH 5/5] add devel and test files --- TeXmacs/tests/tmu/201_38.tmu | 18 +++ devel/201_38.md | 223 +++++++++++++++++++++++++++++++++++ 2 files changed, 241 insertions(+) create mode 100644 TeXmacs/tests/tmu/201_38.tmu create mode 100644 devel/201_38.md diff --git a/TeXmacs/tests/tmu/201_38.tmu b/TeXmacs/tests/tmu/201_38.tmu new file mode 100644 index 000000000..3ebeb940a --- /dev/null +++ b/TeXmacs/tests/tmu/201_38.tmu @@ -0,0 +1,18 @@ +> + +> + +<\body> + the line of text before the image box + + |example-dtrace.svg>|11.876703267627994cm|4.408651938087049cm|1.63494cm|0.219755cm> + + the line of text after the image box + + +<\initial> + <\collection> + + + + diff --git a/devel/201_38.md b/devel/201_38.md new file mode 100644 index 000000000..f0963db28 --- /dev/null +++ b/devel/201_38.md @@ -0,0 +1,223 @@ +# [201_38] 图片支持鼠标移动和缩放 + +## 如何测试 +1. 打开 `TeXmacs/tests/tmu/201_38.tmu`,点击图片应该看到出现八个蓝色控制点 +2. 左右拖动图片可以移动图片位置(目前上下移动图片有问题,这是因为图片碰撞箱模型用的是move_box而不是shift_box,我暂时不做模型的变更防止在其他某些地方出问题) +3. 拖动八个控制点可以调整图片的大小,应该符合主流编辑器的操作习惯(目前上面三个操作点的拖动有问题,这也是因为当前图片模型不支持上下移动) +4. 点击上下方文本,看图片上的蓝色控制点是否消失 +5. 多次操作,检查操作点的绘制是否出现延迟或拖影 + +## 2025/12/07 +### What +图片应该可以用鼠标来操作:放大、缩小、拖动等 + +之前的版本中只能调整上方的参数来改变图片的位置和大小,这样不够直观 + +### How +注意到每次鼠标点击图片等控件(也就是在文本和图片来回切换或对图片直接操作)时都会触发 `edit_interface_rep::draw_selection`,故在这之后接入 `draw_resize_handles`,具体实现如下: +```cpp +void +edit_interface_rep::draw_resize_handles (renderer ren) { + // Draw image resize handles when cursor is inside an image. + SI hs = 10 * ren->pixel; // handle radius for visuals + rectangle new_image_handles= rectangle (0, 0, 0, 0); + SI x1= 0, y1= 0, x2= 0, y2= 0, mx= 0, my= 0; + bool have_bbox= false; + + // Check if any ancestor of current path is an IMAGE + for (path p= path_up (tp); !is_nil (p) && p != rp; p= path_up (p)) { + tree st= subtree (et, p); + if (!is_func (st, IMAGE)) continue; + + selection sel= eb->find_check_selection (p * 0, p * 1); + if (!sel->valid || is_nil (sel->rs)) break; // image not drawable now + + rectangle bbox = least_upper_bound (sel->rs); + x1 = bbox->x1; + y1 = bbox->y1; + x2 = bbox->x2; + y2 = bbox->y2; + mx = (x1 + x2) / 2; + my = (y1 + y2) / 2; + new_image_handles= rectangle (x1 - hs, y1 - hs, x2 + hs, y2 + hs); + have_bbox = true; + break; // only the closest IMAGE ancestor + } + + if (new_image_handles != last_image_handles) { + if (!is_zero (last_image_handles)) invalidate (last_image_handles); + if (!is_zero (new_image_handles)) invalidate (new_image_handles); + last_image_handles= new_image_handles; + } + + if (!have_bbox) return; // nothing to draw + + // Draw 8 resize handles: 4 corners + 4 edge midpoints + color handle_col= rgb_color (0, 120, 215); // Blue color + ren->set_pencil (pencil (handle_col, ren->pixel)); + ren->set_brush (brush (handle_col)); + + // Array of handle center positions: sw, se, nw, ne, s, n, w, e + SI hx[8]= {x1, x2, x1, x2, mx, mx, x1, x2}; + SI hy[8]= {y1, y1, y2, y2, y1, y2, my, my}; + + for (int i= 0; i < 8; i++) { + SI cx= hx[i], cy= hy[i]; + ren->fill_arc (cx - hs, cy - hs, cx + hs, cy + hs, 0, 64 * 360); + ren->arc (cx - hs, cy - hs, cx + hs, cy + hs, 0, 64 * 360); + } +} +``` +绘制了八个操作点,用于图片的缩放。另外需要单独考虑更新逻辑,及时 invalidate 旧的 handles 并重绘 + +#### 为什么不在 `apply_changes` 里整体重新更新? +1. apply_changes 是一个大的更新引擎,频繁调用会影响性能开销,这种图像边框八个点的重绘没必要在这个大引擎中完成 +2. apply_changes 存在一定的idle(可能是防止调用过于频繁而设置的,我试过放到这里面统一更新但发现“不跟手”),这点会导致移动图片时这八个操作点存在延迟,视觉上不美观。放在 draw_resize_handles 里单独处理非常迅速,开销小,功能耦合性低 + +并在前端对鼠标事件作出相应,及时更新图片的大小和位置,具体实现如下: +```scheme +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Image mouse dragging for moving and resizing +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(define image-drag-start-x #f) +(define image-drag-start-y #f) +(define image-resize-handle #f) +(define image-resize-start-x #f) +(define image-resize-start-y #f) +(define image-resize-orig-w #f) +(define image-resize-orig-h #f) +(define image-resize-orig-xoff #f) +(define image-resize-orig-yoff #f) + +(define (image-get-bbox t) + (and-with rect (tree-bounding-rectangle t) + (and (== (length rect) 4) rect))) + +(define image-handle-hitbox 6000) + +(define (image-point-on-handle? t) + (and-with bbox (image-get-bbox t) + (let* ((mpos (get-mouse-position)) + (mx (car mpos)) (my (cadr mpos)) + (x1 (car bbox)) (y1 (cadr bbox)) + (x2 (caddr bbox)) (y2 (cadddr bbox)) + (midx (/ (+ x1 x2) 2)) (midy (/ (+ y1 y2) 2)) + (hs image-handle-hitbox) + (near? (lambda (a b) (< (abs (- a b)) hs))) + (handles `((nw ,x1 ,y2) + (n ,midx ,y2) + (ne ,x2 ,y2) + (e ,x2 ,midy) + (se ,x2 ,y1) + (s ,midx ,y1) + (sw ,x1 ,y1) + (w ,x1 ,midy)))) + (let loop ((hspec handles)) + (if (null? hspec) #f + (let* ((h (car hspec)) (hx (cadr h)) (hy (caddr h))) + (if (and (near? mx hx) (near? my hy)) (car h) (loop (cdr hspec))))))))) + +(define (image-get-dimensions t) + (let* ((w-str (tm->string (tree-ref t 1))) + (h-str (tm->string (tree-ref t 2))) + (w (and w-str (not (string-null? w-str)) (length-decode w-str))) + (h (and h-str (not (string-null? h-str)) (length-decode h-str)))) + (or (and w h (list w h)) + (and-with bbox (image-get-bbox t) + (list (- (caddr bbox) (car bbox)) (- (cadddr bbox) (cadr bbox))))))) + +(define (tmpt->cm v) (/ v 60472.0)) +(define (cm->str v) (string-append (number->string v) "cm")) + +(define (image-set-size! t w h) + (when (> w 0.1) (tree-set! t 1 (cm->str w))) + (when (> h 0.1) (tree-set! t 2 (cm->str h))) + (refresh-window)) + +(define (image-apply-resize t handle dx dy) + (when (and image-resize-orig-w image-resize-orig-h) + (let* ((ow (tmpt->cm image-resize-orig-w)) + (oh (tmpt->cm image-resize-orig-h)) + (sx (tmpt->cm dx)) (sy (tmpt->cm dy)) + (nw (- ow sx)) (nh (- oh sy)) + (xoff (or image-resize-orig-xoff 0)) + (yoff (or image-resize-orig-yoff 0)) + (set-xoff! (lambda () (tree-set! t 3 (cm->str (tmpt->cm (+ xoff dx)))))) + (set-yoff! (lambda () (tree-set! t 4 (cm->str (tmpt->cm (- yoff dy))))))) + (case handle + ((se) (image-set-size! t (+ ow sx) (- oh sy))) + ((sw) (when (> nw 0.1) (set-xoff!) (image-set-size! t nw (- oh sy)))) + ((ne) (when (> nh 0.1) (set-yoff!) (image-set-size! t (+ ow sx) nh))) + ((nw) (when (and (> nw 0.1) (> nh 0.1)) (set-xoff!) (set-yoff!) (image-set-size! t nw nh))) + ((e) (when (> (+ ow sx) 0.1) (tree-set! t 1 (cm->str (+ ow sx))) (refresh-window))) + ((w) (when (> nw 0.1) (set-xoff!) (tree-set! t 1 (cm->str nw)) (refresh-window))) + ((n) (when (> nh 0.1) (set-yoff!) (tree-set! t 2 (cm->str nh)) (refresh-window))) + ((s) (when (> (- oh sy) 0.1) (tree-set! t 2 (cm->str (- oh sy))) (refresh-window))))))) + +(define (image-apply-move t dx dy) + (define (update-offset idx delta update-start!) + (let* ((old (or (tm->string (tree-ref t idx)) "")) + (delta-cm (cm->str (tmpt->cm delta))) + (new-val (if (string-null? old) delta-cm (length-add old delta-cm)))) + (tree-set! t idx new-val) + (update-start!))) + (let ((changed? #f)) + (when (> (abs dx) 3000) + (update-offset 3 dx (lambda () (set! image-drag-start-x (+ image-drag-start-x dx)))) + (set! changed? #t)) + (when (> (abs dy) 3000) + (update-offset 4 (- dy) (lambda () (set! image-drag-start-y (+ image-drag-start-y dy)))) + (set! changed? #t)) + (when changed? (refresh-window)))) + +(define (image-reset-drag-state!) + (set! image-drag-start-x #f) + (set! image-drag-start-y #f) + (set! image-resize-handle #f) + (set! image-resize-start-x #f) + (set! image-resize-start-y #f) + (set! image-resize-orig-w #f) + (set! image-resize-orig-h #f) + (set! image-resize-orig-xoff #f) + (set! image-resize-orig-yoff #f)) + +(define (image-decode-offset t idx) + (let ((s (tm->string (tree-ref t idx)))) + (if (and s (not (string-null? s))) (length-decode s) 0))) + +(tm-define (mouse-event key x y mods time data) + (:require (and (tree-innermost image-context? #t) + (in? key '("start-drag-left" "dragging-left" "end-drag-left")))) + (and-with t (tree-innermost image-context? #t) + (cond + ((== key "start-drag-left") + (image-reset-drag-state!) + (let ((handle (image-point-on-handle? t))) + (if handle + (let ((dims (image-get-dimensions t))) + (set! image-resize-handle handle) + (set! image-resize-start-x x) + (set! image-resize-start-y y) + (set! image-resize-orig-w (if dims (car dims) 60472)) + (set! image-resize-orig-h (if dims (cadr dims) 60472)) + (set! image-resize-orig-xoff (image-decode-offset t 3)) + (set! image-resize-orig-yoff (image-decode-offset t 4))) + (begin + (set! image-drag-start-x x) + (set! image-drag-start-y y)))) + (former key x y mods time data)) + ((== key "dragging-left") + (if image-resize-handle + (when (and image-resize-start-x image-resize-start-y) + (image-apply-resize t image-resize-handle + (- x image-resize-start-x) (- y image-resize-start-y))) + (when (and image-drag-start-x image-drag-start-y) + (image-apply-move t (- x image-drag-start-x) (- y image-drag-start-y))))) + ((== key "end-drag-left") + (image-reset-drag-state!) + (former key x y mods time data))))) +``` +这里涉及坐标换算和参数转换,较为复杂这里不赘述。 + +本质上就是前端根据鼠标的位置和移动参数设置图片的宽度、高度、X坐标、Y坐标 \ No newline at end of file -- Gitee