From 3f54517e96ccff233b1560627995024e137dbdfd Mon Sep 17 00:00:00 2001
From: sharkiecodes <lanyi_stroud@brown.edu>
Date: Tue, 11 Mar 2025 16:27:30 -0400
Subject: Doing outpainting implementation

---
 src/client/views/DocumentDecorations.tsx | 158 +++++++++++++++++++++++++------
 1 file changed, 127 insertions(+), 31 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 54e050f9f..d7ff0d06a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,6 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
+import { DimensionField } from '../../fields/DimensionField';
 import { IconButton } from '@dash/components';
 import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -473,6 +474,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const tl = docView.screenToContentsTransform().inverse().transformPoint(0, 0);
         return project([e.clientX + this._offset.x, e.clientY + this._offset.y], tl, [tl[0] + fixedAspect, tl[1] + 1]);
     };
+
+    // Modify the onPointerMove method to handle shift+click during resize
     onPointerMove = (e: PointerEvent): boolean => {
         const first = DocumentView.Selected()[0];
         const effectiveAcl = GetEffectiveAcl(first.Document);
@@ -491,17 +494,135 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         !this._interactionLock && runInAction(async () => {   // resize selected docs if we're not in the middle of a resize (ie, throttle input events to frame rate)
             this._interactionLock = true;
             this._snapPt = thisPt;
-            e.ctrlKey && (DocumentView.Selected().forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions()));
-            const hasFixedAspect = DocumentView.Selected().map(dv => dv.Document).some(this.hasFixedAspect);
-            const scaleAspect = {x:scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y};
-            DocumentView.Selected().forEach(docView => 
-                this.resizeView(docView, refPt, scaleAspect, { dragHdl, ctrlKey:e.ctrlKey })); // prettier-ignore
+            
+            // Special handling for shift+click (outpainting mode)
+            if (e.shiftKey && DocumentView.Selected().some(dv => dv.ComponentView instanceof ImageBox)) {
+                DocumentView.Selected().forEach(docView => {
+                    if (docView.ComponentView instanceof ImageBox) {
+                        // Set flag for outpainting mode
+                        docView.Document._outpaintingResize = true;
+                        
+                        // Adjust only the document dimensions without scaling internal content
+                        this.resizeViewForOutpainting(docView, refPt, scale, { dragHdl, shiftKey: e.shiftKey });
+                    }
+                    else {
+                        // Handle regular resize for non-image components
+                        e.ctrlKey && !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions();
+                        const hasFixedAspect = this.hasFixedAspect(docView.Document);
+                        const scaleAspect = {x:scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y};
+                        this.resizeView(docView, refPt, scaleAspect, { dragHdl, ctrlKey: e.ctrlKey });
+                    }
+                });
+            } else {
+                // Regular resize behavior (existing code)
+                e.ctrlKey && (DocumentView.Selected().forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions()));
+                const hasFixedAspect = DocumentView.Selected().map(dv => dv.Document).some(this.hasFixedAspect);
+                const scaleAspect = {x:scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y};
+                DocumentView.Selected().forEach(docView => 
+                    this.resizeView(docView, refPt, scaleAspect, { dragHdl, ctrlKey:e.ctrlKey }));
+            }
+            
             await new Promise<void>(res => { setTimeout(() => { res(this._interactionLock = undefined)})});
-        }); // prettier-ignore
+        });
 
         return false;
     };
 
+    resizeViewForOutpainting = (
+        docView: DocumentView,
+        refPt: number[],
+        scale: { x: number; y: number },
+        opts: { dragHdl: string; shiftKey: boolean }
+    ) => {
+        const doc = docView.Document;
+    
+        if (doc.isGroup) {
+            DocListCast(doc.data)
+                .map(member => DocumentView.getDocumentView(member, docView)!)
+                .forEach(member => this.resizeViewForOutpainting(member, refPt, scale, opts));
+            doc.xPadding = NumCast(doc.xPadding) * scale.x;
+            doc.yPadding = NumCast(doc.yPadding) * scale.y;
+            return;
+        }
+    
+        if (!doc._outpaintingOriginalWidth || !doc._outpaintingOriginalHeight) {
+            doc._outpaintingOriginalWidth = NumCast(doc._width);
+            doc._outpaintingOriginalHeight = NumCast(doc._height);
+    
+            // Initialize or update the _originalDims ObjectField correctly
+             doc._originalDims = new DimensionField(doc._outpaintingOriginalWidth, doc._outpaintingOriginalHeight);
+        }
+    
+        // Calculate new boundary dimensions
+        const originalWidth = NumCast(doc._width);
+        const originalHeight = NumCast(doc._height);
+        const newWidth = Math.max(NumCast(doc._width_min, 25), originalWidth * scale.x);
+        const newHeight = Math.max(NumCast(doc._height_min, 10), originalHeight * scale.y);
+    
+        // Apply new dimensions
+        doc._width = newWidth;
+        doc._height = newHeight;
+    
+        const refCent = docView.screenToViewTransform().transformPoint(refPt[0], refPt[1]);
+        const { deltaX, deltaY } = this.realignRefPt(doc, refCent, originalWidth, originalHeight);
+        doc.x = NumCast(doc.x) + deltaX;
+        doc.y = NumCast(doc.y) + deltaY;
+    
+        doc._layout_modificationDate = new DateField();
+    
+        // Trigger outpainting
+        doc._needsOutpainting = true;
+    
+        // Store metadata needed for outpainting
+        doc._outpaintingMetadata = JSON.stringify({
+            originalWidth: doc._outpaintingOriginalWidth,
+            originalHeight: doc._outpaintingOriginalHeight,
+            newWidth,
+            newHeight,
+            scaleX: scale.x,
+            scaleY: scale.y,
+            anchorHandle: opts.dragHdl
+        });
+    };
+
+    @action
+    onPointerUp = (): void => {
+        SnappingManager.SetIsResizing(undefined);
+        SnappingManager.clearSnapLines();
+        
+        // Check if any outpainting needs to be processed
+        DocumentView.Selected().forEach(view => {
+            if (view.Document._needsOutpainting && view.ComponentView instanceof ImageBox) {
+                // Trigger outpainting process in the ImageBox component
+                (view.ComponentView as ImageBox).processOutpainting();
+                
+                // Clear flags
+                view.Document._needsOutpainting = false;
+                view.Document._outpaintingResize = false;
+            }
+        });
+        
+        this._resizeHdlId = '';
+        this._resizeUndo?.end();
+
+        // detect layout_autoHeight gesture and apply
+        DocumentView.Selected().forEach(view => {
+            NumCast(view.Document._height) < 20 && (view.layoutDoc._layout_autoHeight = true);
+        });
+        // need to change points for resize, or else rotation/control points will fail.
+        this._inkDragDocs
+            .map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
+            .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
+                doc[DocData].data = new InkField(inkPts.map(
+                    (ipt) => ({// (new x — oldx) + newWidth * (oldxpoint /oldWidth)
+                        X: NumCast(doc.x) - x + (NumCast(doc._width) * ipt.X) / width,
+                        Y: NumCast(doc.y) - y + (NumCast(doc._height) * ipt.Y) / height,
+                    }))); // prettier-ignore
+                Doc.SetNativeWidth(doc, undefined);
+                Doc.SetNativeHeight(doc, undefined);
+            });
+    };
+
     //
     // determines how much to resize, and determines the resize reference point
     //
@@ -606,31 +727,6 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         };
     };
 
-    @action
-    onPointerUp = (): void => {
-        SnappingManager.SetIsResizing(undefined);
-        SnappingManager.clearSnapLines();
-        this._resizeHdlId = '';
-        this._resizeUndo?.end();
-
-        // detect layout_autoHeight gesture and apply
-        DocumentView.Selected().forEach(view => {
-            NumCast(view.Document._height) < 20 && (view.layoutDoc._layout_autoHeight = true);
-        });
-        // need to change points for resize, or else rotation/control points will fail.
-        this._inkDragDocs
-            .map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
-            .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
-                doc[DocData].data = new InkField(inkPts.map(
-                    (ipt) => ({// (new x — oldx) + newWidth * (oldxpoint /oldWidth)
-                        X: NumCast(doc.x) - x + (NumCast(doc.width) * ipt.X) / width,
-                        Y: NumCast(doc.y) - y + (NumCast(doc.height) * ipt.Y) / height,
-                    }))); // prettier-ignore
-                Doc.SetNativeWidth(doc, undefined);
-                Doc.SetNativeHeight(doc, undefined);
-            });
-    };
-
     @computed get selectionTitle(): string {
         if (DocumentView.Selected().length === 1) {
             const selected = DocumentView.Selected()[0];
-- 
cgit v1.2.3-70-g09d2

