diff --git a/TeXmacs/progs/generic/format-geometry-edit.scm b/TeXmacs/progs/generic/format-geometry-edit.scm index fb393bfdd42c7b9bc5a447ee8ad517887090fcaf..384574e70c355d6b5c13ed23a78ef3aff56d3c69 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,145 @@ (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-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))))) diff --git a/TeXmacs/tests/tmu/201_38.tmu b/TeXmacs/tests/tmu/201_38.tmu new file mode 100644 index 0000000000000000000000000000000000000000..3ebeb940abf6a3a351cd80ecdd8d155288eeab5a --- /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 0000000000000000000000000000000000000000..f0963db287f475cda2bb3ca4936ef2e8765c8068 --- /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 diff --git a/src/Edit/Interface/edit_interface.hpp b/src/Edit/Interface/edit_interface.hpp index 02927df0e2c53f5a55618d78f234883357ec37ca..7209a97955e97720e8d210254c3b3cc10a8456da 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; 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 6ced0fff332f21cc42f257ffc84c4f780ace1b1a..ab75dd68fbe8012efb755d63a37fb18d5a6e7d50 100644 --- a/src/Edit/Interface/edit_repaint.cpp +++ b/src/Edit/Interface/edit_repaint.cpp @@ -173,6 +173,60 @@ 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. + 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); + } } void