Browse Source

Merge branch 'dev' of http://124.70.149.18:10880/lianghj/queenshow into dev

qinyan 1 year ago
parent
commit
182bcaa5c9
31 changed files with 3721 additions and 196 deletions
  1. 3 1
      .eslintrc.js
  2. 5 1
      src/modules/editor/components/CompUI/basicUI/Container/component.tsx
  3. 288 0
      src/modules/editor/components/CompUI/basicUI/Transfer/select.tsx
  4. 10 10
      src/modules/editor/components/CompUI/basicUI/View.tsx
  5. 3 2
      src/modules/editor/components/CompUI/basicUI/hooks.ts
  6. 8 5
      src/modules/editor/components/Viewport/Content/index.tsx
  7. 9 5
      src/modules/editor/components/Viewport/Slider/SliderRight/CompTree.tsx
  8. 249 0
      src/modules/editor/controllers/SelectCtrl/ObjsContainer.ts
  9. 137 0
      src/modules/editor/controllers/SelectCtrl/compObj.ts
  10. 90 0
      src/modules/editor/controllers/SelectCtrl/event.ts
  11. 668 69
      src/modules/editor/controllers/SelectCtrl/index.ts
  12. 359 0
      src/modules/editor/controllers/SelectCtrl/matrix.ts
  13. 246 0
      src/modules/editor/controllers/SelectCtrl/objects/bounds.ts
  14. 10 0
      src/modules/editor/controllers/SelectCtrl/objects/const.ts
  15. 388 0
      src/modules/editor/controllers/SelectCtrl/objects/container.ts
  16. 444 0
      src/modules/editor/controllers/SelectCtrl/objects/displayObject.ts
  17. 205 0
      src/modules/editor/controllers/SelectCtrl/objects/mathUtils.ts
  18. 64 0
      src/modules/editor/controllers/SelectCtrl/objects/observablePoint.ts
  19. 29 0
      src/modules/editor/controllers/SelectCtrl/objects/point.ts
  20. 123 0
      src/modules/editor/controllers/SelectCtrl/objects/rectangle.ts
  21. 155 0
      src/modules/editor/controllers/SelectCtrl/objects/transform.ts
  22. 21 0
      src/modules/editor/controllers/SelectCtrl/objects/utils.ts
  23. 71 51
      src/modules/editor/controllers/TransferCtrl/Matrix.ts
  24. 48 42
      src/modules/editor/module/actions/edit.ts
  25. 2 2
      src/modules/editor/module/actions/init.ts
  26. 46 0
      src/modules/editor/module/helpers/index.ts
  27. 2 0
      src/modules/editor/module/index.ts
  28. 4 1
      src/modules/editor/module/stores/index.ts
  29. 11 0
      src/modules/editor/objects/DesignTemp/DesignComp.ts
  30. 21 6
      src/modules/editor/objects/DesignTemp/creates/createCompStyle.ts
  31. 2 1
      src/modules/editor/typings.ts

+ 3 - 1
.eslintrc.js

@@ -20,7 +20,9 @@ module.exports = {
     "prettier/prettier": "off",
     "@typescript-eslint/no-var-requires": "off",
     "@typescript-eslint/no-namespace": "off",
-    "@typescript-eslint/no-this-alias": "off"
+    "@typescript-eslint/no-this-alias": "off",
+    "prefer-const": "off",
+    "@typescript-eslint/adjacent-overload-signatures":"off"
   },
   overrides: [
     {

+ 5 - 1
src/modules/editor/components/CompUI/basicUI/Container/component.tsx

@@ -5,13 +5,14 @@ import { useEditor } from "../../../..";
 import { DesignComp } from "../../../../objects/DesignTemp/DesignComp";
 import { View } from "../View";
 import { CompUI } from "../..";
+import { SelectTransfer } from "../Transfer/select";
 
 export const Component = defineComponent({
   props: {
     compId: string().isRequired,
   },
   setup(props) {
-    const { helper, controls } = useEditor();
+    const { helper, controls , store} = useEditor();
     const { children } = useCompData(props.compId);
     return () => (
       <View compId={props.compId}>
@@ -23,6 +24,9 @@ export const Component = defineComponent({
 
           return <Comp key={compItem.id} compId={compItem.id} />;
         })}
+        {
+          store.currStreamCardId == props.compId && <SelectTransfer />
+        }
       </View>
     );
   },

+ 288 - 0
src/modules/editor/components/CompUI/basicUI/Transfer/select.tsx

@@ -0,0 +1,288 @@
+import { IconRotate } from "@/assets/icons";
+import { CompToolbars } from "@/modules/editor/objects/Toolbars";
+import { css } from "@linaria/core";
+import { defineComponent, onMounted, onUnmounted, ref , nextTick} from "vue";
+import { useEditor } from "../../../..";
+
+export const SelectTransfer = defineComponent({
+  setup() {
+    const editor = useEditor();
+    const { controls } = editor;
+    const { selectCtrl } = controls;
+    const { transferStyle } = selectCtrl;
+
+    const selectRectRef = ref();
+    const rotateRef = ref();
+    const rotateRef2 = ref();
+    const rotateRef3 = ref();
+    const scaleBottomrightRef = ref();
+    const scaleBottomLeftRef = ref();
+    const scaleTopLeftRef = ref();
+    const scaleTopRightRef = ref();
+    const scaleRightRef = ref();
+    const scaleLeftRef = ref();
+
+    const scaleTopRef = ref();
+    const scaleBottomRef = ref();
+
+    onMounted(()=>{
+        nextTick(()=>{
+            selectRectRef.value.editable = "move";
+            rotateRef.value.editable = "rotate";
+            rotateRef2.value.editable = "rotate";
+            rotateRef3.value.editable = "rotate";
+            scaleBottomrightRef.value.editable = "scaleBottomright";
+
+            scaleBottomLeftRef.value.editable = "scaleBottomleft";
+            scaleTopLeftRef.value.editable = "scaleTopleft";
+            scaleTopRightRef.value.editable = "scaleTopright";
+            scaleRightRef.value.editable = "scaleright"
+            scaleLeftRef.value.editable = "scaleleft"
+            scaleTopRef.value.editable = "scaletop"
+            scaleBottomRef.value.editable = "scalebottom"
+        })
+    })
+    return () => {
+      return (
+       (
+          <div
+            class={["absolute transfer z-1001",transferStyle.showGizmo? showgizmo:hideGizmo]}
+          >
+              <div class="">
+                {/* {toolbarOpts.map((item) => {
+                  return item.getVisible.call(editor, comp) ? (
+                    <item.component
+                      class="p-4px"
+                      value={item.getValue?.(comp)}
+                      onClick={() => item.onClick.call(editor, comp)}
+                    />
+                  ) : null;
+                })} */}
+              </div>
+            
+            <div
+              class={["absolute", selctRectStyle]}
+              ref={selectRectRef}
+
+              style={{
+                width: transferStyle.width + "px",
+                height: transferStyle.height + "px",
+                transform: transferStyle.matrix,
+                transformOrigin: `0 0`,
+              }}
+            >
+              <div
+                class={borderStyle}
+              />
+                <>
+                  <div
+                    class={[resizeStyle, scaleBottomRightStyle]}
+                    style={{transform: transferStyle.matrixInvert}}
+                    ref={scaleBottomrightRef}
+                  />
+                  <div
+                    class={[resizeStyle, scaleBottomLeftStyle]}
+                    style={{transform: transferStyle.matrixInvert}}
+                    ref={scaleBottomLeftRef}
+                  />
+                  <div
+                    class={[resizeStyle, scaleTopLeftStyle]}
+                    style={{transform: transferStyle.matrixInvert}}
+                    ref={scaleTopLeftRef}
+                  />
+                   
+                
+                    <div
+                        class={[resizeStyle, scaleTopRightStyle ]}
+                        style={{transform: transferStyle.matrixInvert}}
+                        ref={scaleTopRightRef}
+                    />
+             
+                  
+
+                  
+                    <div class={transformBtnsStyle} ref={rotateRef3}>
+                        <div class={transBtnStyle} ref={rotateRef} style={{transform: transferStyle.matrixInvert}} >
+                            <IconRotate ref={rotateRef2} />
+                        </div>
+                    </div>
+                  
+
+                  <div
+                    class={[resizeHeightBtnCls, scaleTopCls]}
+                    ref={scaleTopRef}
+                  />
+
+                  <div
+                    class={ [resizeHeightBtnCls, scaleBottomCls]}
+                    ref={scaleBottomRef}
+                  />
+
+                  <div
+                    class={ [resizeWidthBtnCls, scaleRightCls]}
+                    ref={scaleRightRef}
+                  />
+                   <div
+                    class={ [resizeWidthBtnCls, scaleLeftCls]}
+                    ref={scaleLeftRef}
+                  />
+                
+                </>
+            </div>
+          </div>
+        )
+      );
+    };
+  },
+});
+const selctRectStyle = css`
+    pointer-events: none;
+`
+const showgizmo = css`
+    display: block;
+    left: 0;
+    top: 0;
+    /* pointer-events: none; */
+`
+const hideGizmo = css`
+    display: none;
+`
+
+const borderStyle = css`
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  outline: 2px solid @inf-primary-color;
+  pointer-events: none;
+  z-index: 999;
+`;
+
+const resizeStyle = css`
+  position: absolute;
+  width: 16px;
+  height: 16px;
+  border-radius: 50%;
+  background-color: #fff;
+  z-index: 999;
+  transform: translate(50%, 50%);
+  pointer-events: auto;
+  box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.2);
+  cursor: nwse-resize;
+  &:hover {
+    border-color: @inf-primary-color;
+  }
+`;
+
+const scaleBottomRightStyle = css`
+  bottom: -8px;
+  right: -8px;
+`
+const scaleBottomLeftStyle = css`
+  bottom: -8px;
+  left: -8px;
+`
+
+const scaleTopLeftStyle = css`
+  top: -8px;
+  left: -8px;
+`
+
+const scaleTopRightStyle = css`
+  top: -8px;
+  right: -8px;
+`
+
+const transformBtnsStyle = css`
+  @apply space-x-10px whitespace-nowrap;
+  position: absolute;
+  bottom: 0;
+  left: 50%;
+  font-size: 16px;
+  z-index: 999;
+  transform: translate(-50%, 50px);
+  pointer-events: auto;
+`;
+
+const transBtnStyle = css`
+  display: inline-block;
+  width: 36px;
+  height: 36px;
+  border-radius: 50%;
+  background-color: #fff;
+  text-align: center;
+  line-height: 36px;
+  font-size: 20px;
+  color: #333;
+  @apply shadow cursor-move;
+
+  &:hover {
+    color: #fff;
+    background-color: @inf-primary-color;
+  }
+`;
+
+const toolbarStyle = css`
+  @apply bg-white shadow rounded space-x-4px p-4px whitespace-nowrap;
+  position: absolute;
+  top: 0;
+  left: 50%;
+  transform: translate(-50%, -60px);
+  z-index: 999;
+`;
+
+const resizeHeightBtnCls = css`
+  position: absolute;
+  width: 30px;
+  height: 8px;
+  border-radius: 4px;
+  left: 50%;
+  transform: translate(-50%, -4px);
+  pointer-events: auto;
+  cursor: ns-resize;
+  background: rgba(255, 255, 255, 0.3);
+  &:hover {
+    background: @inf-primary-color;
+  }
+  @apply shadow;
+  z-index: 999;
+`;
+
+const scaleTopCls = css`
+  top: 0;
+  transform: translate(-50%, -4px);
+`;
+
+const scaleBottomCls = css`
+  bottom: 0;
+  transform: translate(-50%, 4px);
+`;
+
+
+const resizeWidthBtnCls = css`
+  position: absolute;
+  width: 8px;
+  height: 30px;
+  border-radius: 4px;
+  pointer-events: auto;
+  cursor: ew-resize;
+  background: rgba(255, 255, 255, 0.3);
+  &:hover {
+    background: @inf-primary-color;
+  }
+  @apply shadow;
+  z-index: 999;
+`;
+
+const scaleRightCls = css`
+   right: 0;
+   top: 50%;
+   transform: translate(4px, -50%);
+`;
+
+const scaleLeftCls = css`
+  left: 0;
+  top: 50%;
+  transform: translate(-4px, -50%);
+`;

+ 10 - 10
src/modules/editor/components/CompUI/basicUI/View.tsx

@@ -39,17 +39,17 @@ export const View = defineComponent({
           onDblclick={() => emit("dblclick")}
         >
           <div
-            onMousedown={(e) => {
-              if (helper.isGroupCompChild(props.compId)) return;
+            // onMousedown={(e) => {
+            //   if (helper.isGroupCompChild(props.compId)) return;
 
-              e.stopPropagation();
-              if (store.isEditMode) {
-                actions.pickComp(props.compId);
-                if (!helper.isStreamCard(props.compId)) {
-                  controls.transferCtrl.mousedown(e, "move", store.currComp);
-                }
-              }
-            }}
+            //   // e.stopPropagation();
+            //   if (store.isEditMode) {
+            //     actions.pickComp(props.compId);
+            //     if (!helper.isStreamCard(props.compId)) {
+            //       controls.transferCtrl.mousedown(e, "move", store.currComp);
+            //     }
+            //   }
+            // }}
             onMousemove={(e) => {
               if (
                 !store.isEditMode ||

+ 3 - 2
src/modules/editor/components/CompUI/basicUI/hooks.ts

@@ -1,9 +1,10 @@
 import { useEditor } from "@/modules/editor";
-import { onMounted, ref, inject, provide } from "vue";
+import { CompObject } from "@/modules/editor/controllers/SelectCtrl/compObj";
+import { onMounted, ref, inject, provide, compile } from "vue";
 
 export function useCompRef(compId: string) {
   const compRef = ref();
-  const { store, helper } = useEditor();
+  const { store, helper, compObjsMap } = useEditor();
 
   const parentId = compId !== "root" ? (inject("compParentId") as string) : "";
   provide("compParentId", compId);

+ 8 - 5
src/modules/editor/components/Viewport/Content/index.tsx

@@ -8,6 +8,7 @@ import { HotKeyCtrl } from "../../../controllers/HotKeyCtrl";
 import { CompUI } from "../../CompUI";
 import { Transfer } from "../../CompUI/basicUI/Transfer";
 import { StreamCardTransfer } from "../../CompUI/basicUI/Transfer/streamCard";
+// import { SelectTransfer } from "../../CompUI/basicUI/Transfer/streamCard";
 
 export default defineUI({
   setup() {
@@ -28,6 +29,7 @@ export default defineUI({
     const flagRef = ref();
     const containRef = ref();
     const selectCanvasRef = ref();
+    const viewportRef = ref();
 
     return () => {
       const pageRoot = helper.findRootComp();
@@ -38,12 +40,12 @@ export default defineUI({
       if (!flagRef.value) {
         flagRef.value = true;
         setTimeout(() => {
-          actions.onViewReady(pageRoot.$el, selectCanvasRef.value);
+          actions.onViewReady(pageRoot.$el, selectCanvasRef.value, viewportRef.value);
         }, 0);
       }
 
       return (
-        <div class="scrollbar overflow-y-auto h-1/1">
+        <div class="scrollbar overflow-y-auto h-1/1" ref={viewportRef}>
           <div class="relative">
             <div class={"w-375px my-60px mx-auto select-none " + contentCls}>
               <CompUI.Page.Component compId={pageRoot.id}>
@@ -97,14 +99,14 @@ export default defineUI({
                           />
                         )}
 
-                        {store.currCompId &&
+                        {/* {store.currCompId &&
                           store.currStreamCardId &&
                           store.currCompId !== "root" &&
                           !store.textEditingState &&
                           store.currCompId !== store.currStreamCardId &&
                           !state.draging && (
                             <Transfer key={store.currCompId + streamCardIndex} />
-                          )}
+                          )} */}
                       </>
                     );
                   },
@@ -136,9 +138,10 @@ const contentCls = css`
 `;
 const selectCls = css`
   pointer-events: none;
-  position: absolute;
+  position: fixed;
   left: 0;
   top: 0;
   width: 100%;
   height: 100%;
+  z-index: 1000;
 `;

+ 9 - 5
src/modules/editor/components/Viewport/Slider/SliderRight/CompTree.tsx

@@ -7,6 +7,7 @@ import { defineComponent, effect } from "vue";
 import { string } from "vue-types";
 import { useEditor } from "../../../..";
 import { DesignComp } from "../../../../objects/DesignTemp/DesignComp";
+import { CompObject } from "@/modules/editor/controllers/SelectCtrl/compObj";
 
 type TreeItem = {
   key: string;
@@ -54,11 +55,14 @@ export const CompTree = defineComponent({
         v-model={[state.expandedKeys, "expandedKeys"]}
         selectedKeys={[store.currCompId]}
         blockNode={true}
-        onSelect={(ids) =>
-          actions.pickComp(
-            (ids[0] as string) || state.expandedKeys.at(-2) || "root"
-          )
-        }
+        onSelect={(ids) =>{
+          const id = ids[0] as string || state.expandedKeys.at(-2) || "root";
+          if (helper.isStreamCardChild(id)) {
+            controls.selectCtrl.selecteObjs([new CompObject(store.compMap[id])])
+            return;
+          } 
+          actions.pickComp(id)
+        }}
       >
         {{
           title: (data: any) => {

+ 249 - 0
src/modules/editor/controllers/SelectCtrl/ObjsContainer.ts

@@ -0,0 +1,249 @@
+import { Bounds } from './objects/bounds';
+import { Rectangle } from './objects/rectangle';
+import { Container } from './objects/container';
+import { CompObject } from './compObj';
+
+export class ObjsContainer {
+    aabb = new Bounds();
+    tempBound = new Bounds();
+    parent = new Container();
+
+    rect = new Rectangle();
+    pivotIndex = 0;
+    selected:CompObject[] = [];
+
+    constructor(selected:any[]) {
+        this.selected = selected;
+
+        this.parent.sortableChildren = false;
+        if (selected.length > 0) {
+            this.init();
+        }
+    }
+
+    clone() {
+        const ret = new ObjsContainer([]);
+        const parent = this.parent;
+
+        ret.parent.pivot = parent.pivot;
+        ret.parent.skew = parent.skew;
+        ret.parent.scale = parent.scale;
+        ret.parent.rotation = parent.rotation;
+        ret.parent.position = parent.position;
+        ret.rect.copyFrom(this.rect);
+        ret.parent.updateTransform();
+        ret.setPivot(this.pivotIndex);
+
+        return ret;
+    }
+
+    getBound() {
+        //加上parent的旋转和平移
+        this.tempBound.clear();
+        this.tempBound.addFrame(this.parent.transform, 0, 0, this.rect.width, this.rect.height);
+
+        return this.tempBound.getRectangle();
+    }
+
+    get width() {
+        return  this.rect.width
+    }
+
+    get height() {
+        return this.rect.height;
+    }
+
+    testClick(sx:number,sy:number)
+    {
+        let w = this.width;
+        let h = this.height;
+        let local = {x:0,y:0} as any;
+        this.parent.worldTransform.applyInverse({x:sx,y:sy} as any, local);
+
+        if(  local.x < 0 || local.x > w ) return false;
+        if(  local.y < 0 || local.y > h ) return false;
+
+        return true;
+    }
+
+    init() {
+        //获取选择对象的aabb(画布空间)
+        this.aabb.clear();
+        let n = this.selected.length;
+        const selected = this.selected;
+        const selectedRotation = 0;
+
+        if (n == 1) { //单对象
+            let obj = selected[0];
+            //构建当前对象的转换矩阵
+            let rect = new Rectangle(0, 0, obj.width, obj.height);
+            this.rect = rect;
+            this.parent.transform.setFromMatrix(obj.worldTransform);
+            this.parent.updateTransform();
+            this.parent.addChildWorldNoChange(obj);
+            this.parent.updateTransform();
+            return;
+        }
+
+        while (n--) {
+            let obj = selected[n];
+            let box = obj.calculateBounds();
+            this.aabb.addBounds(box);
+        }
+
+        //构建当前对象的转换矩阵
+        let rect = new Rectangle();
+        this.aabb.getRectangle(rect);
+        let center = rect.center;
+
+        this.rect = rect;
+
+        //设置位置
+        let p = this.parent.position;
+        p.x = center.x;
+        p.y = center.y;
+
+        //设置旋转中心点
+        let pivot = this.parent.pivot;
+        pivot.x = rect.width / 2;
+        pivot.y = rect.height / 2;
+
+        //设置旋转
+        this.parent.scale = { x: 1, y: 1 } as any;
+
+        //设置旋转
+        this.parent.rotation = selectedRotation;
+
+        // this.parent.scale = {x:1.5, y:1.5};
+        this.parent.updateTransform();
+
+        //选择的对象坐标从画布空间转到选框坐标,
+        n = selected.length;
+        for (let i = 0; i < n; i++) {
+            let obj = selected[i];
+            this.parent.addChildWorldNoChange(obj);
+        }
+        this.parent.updateTransform();
+    }
+
+    rotate(r:number) {
+        this.parent.rotation = r;
+        this.parent._boundsID++;
+        this.parent.updateTransform();
+
+        this.updateCompState();
+    }
+
+    //index
+    // 0 ---- 1
+    // |  4   |
+    // 3 -----2
+    setPivot(index:number) {
+        let rect = this.rect;
+        let pivots = [{ x: 0, y: 0 }, { x: rect.width, y: 0 }, { x: rect.width, y: rect.height }, { x: 0, y: rect.height }, { x: rect.width / 2, y: rect.height / 2 }];
+        let targetPivot = pivots[index];
+
+        let point = { x: targetPivot.x, y: targetPivot.y } as any;
+
+
+        this.parent.worldTransform.apply(point, point);
+
+        this.parent.pivot = targetPivot as any;
+        this.parent.position.x = point.x;
+        this.parent.position.y = point.y;
+
+        this.parent.updateTransform();
+
+        this.pivotIndex = index;
+        return { x: point.x * 0.5, y: point.y * 0.5 };
+    }
+
+    getPivotXY(index:number) {
+        let rect = this.rect;
+        let pivots = [{ x: 0, y: 0 }, { x: rect.width, y: 0 }, { x: rect.width, y: rect.height }, { x: 0, y: rect.height }, { x: rect.width / 2, y: rect.height / 2 }];
+        let targetPivot = pivots[index];
+
+        let point = { x: targetPivot.x, y: targetPivot.y } as any;
+        this.parent.worldTransform.apply(point, point);
+
+        let yIndex = 0, xIndex = 0;
+        if (index == 3) {
+            yIndex = 0;
+            xIndex = 2;
+        } else if (index == 0) {
+            yIndex = 3;
+            xIndex = 1;
+        } else if (index == 1) {
+            yIndex = 2;
+            xIndex = 0;
+        } else if (index == 2) {
+            yIndex = 1;
+            xIndex = 3;
+        }
+
+        let pointY = pivots[yIndex];
+        let pY = { x: pointY.x, y: pointY.y } as any;
+        this.parent.worldTransform.apply(pY, pY);
+
+        let pointX = pivots[xIndex];
+        let pX = { x: pointX.x, y: pointX.y } as any;
+        this.parent.worldTransform.apply(pX, pX);
+
+        let xVec = { x: (pX.x - point.x) * 0.5, y: (pX.y - point.y) * 0.5 };
+        let yVec = { x: (pY.x - point.x) * 0.5, y: (pY.y - point.y) * 0.5 };
+
+        return { x: xVec, y: yVec };
+    }
+
+    scale(x:number, y:number) {
+        this.parent.scale.x = x;
+        this.parent.scale.y = y;
+        this.parent.updateTransform();
+
+        this.updateCompState();
+    }
+
+    scaleX(x:number) {
+        this.parent.scale.x = x;
+        this.parent.updateTransform();
+        this.updateCompState();
+    }
+    scaleY(y:number) {
+        this.parent.scale.y = y;
+        this.parent.updateTransform();
+        this.updateCompState();
+    }
+
+    translate(x:number, y:number) {
+        this.parent.x += x;
+        this.parent.y += y;
+
+        this.parent.updateTransform();
+
+        this.updateCompState();
+    }
+
+    destroy() {//选中的对象坐标转到画布空间坐标
+        let selected = this.selected;
+        let n = selected.length;
+        while (n--) {
+            let child = selected[n];
+            child.updateTransform();
+
+            //世界坐标
+            child.transform.setFromMatrix(child.worldTransform);
+            child.parent = null;
+            child.transform._parentID = -1;
+            child.updateTransform();
+            child._boundsID = -2;
+            child._lastBoundsID = 0;
+        }
+    }
+    updateCompState() {
+        let n = this.selected.length;
+        while (n--) {
+            let child = this.selected[n];
+            child.comp.layout.transformMatrix = child.worldTransform.getMatrixStr();
+        }
+    }
+}

+ 137 - 0
src/modules/editor/controllers/SelectCtrl/compObj.ts

@@ -0,0 +1,137 @@
+import { DesignComp } from "../../objects/DesignTemp/DesignComp";
+import { Matrix } from "./matrix";
+import { DisplayObject } from "./objects/displayObject";
+import { isRectInter } from "./objects/mathUtils";
+import { Rectangle } from "./objects/rectangle";
+
+function designSizeToPx(value: number) {
+    return value / 2.0;
+}
+function pxToDesignSize(value: number) {
+    return value * 2.0;
+}
+export class CompObject extends DisplayObject {
+    comp:DesignComp;
+    rect = new Rectangle();
+
+    _width = 0;
+    _height = 0;
+
+    constructor(c:DesignComp) {
+        super();
+        this.comp = c;
+        if (c.layout.size ) {
+            if (c.layout.size[0]) {
+                this._width =  designSizeToPx(c.layout.size[0]);
+            }
+            if (c.layout.size[1]) {
+                this._height =  designSizeToPx(c.layout.size[1]);
+            }
+        }
+        if (c.$el) {
+            if (!c.layout.size) {
+                this._width = c.$el.clientWidth;
+                this._height = c.$el.clientHeight;
+                c.layout.size = [pxToDesignSize(this._width), pxToDesignSize(this._height)]
+            } else if (!c.layout.size[0]) {
+                this._width = c.$el.clientWidth;
+                c.layout.size[0] = pxToDesignSize(this._width)
+
+            }else if (!c.layout.size[1]) {
+                this._height = c.$el.clientHeight;
+                c.layout.size[1] = pxToDesignSize(this._height)
+            }
+        }
+        this.transform.setFromMatrix(Matrix.createFromMatrixStr(c.layout.transformMatrix || "matrix(1,0,0,1,0,0)"));
+        this.updateTransform();
+        this._boundsID++;
+    }
+
+    get width() {
+        return  this._width
+    }
+
+    get height() {
+        return this._height
+    }
+
+    getRect(){
+        let p1 = {x:0,y:0} as any;
+        this.worldTransform.apply(p1, p1);
+
+        let p2 = {x: this.width , y:0} as any;
+        this.worldTransform.apply(p2, p2);
+
+        let p3 = {x:this.width,y:this.height} as any;
+        this.worldTransform.apply(p3, p3);
+
+        let p4 = {x:0,y:this.height} as any;
+        this.worldTransform.apply(p4, p4);
+
+        return {p1,p2,p3,p4};
+    }
+
+    //判定兩個矩形有没有相交
+    //依据四个定点是否有相交
+    testRect(posSize:any, more = true) {
+
+        let {x,y,w,h} = posSize;
+
+        let rect1 = {p1:{x,y},p2:{x:x+w,y},p3:{x:x+w,y:y+h},p4:{x:x,y:y+h}};
+
+        let rect2 = this.getRect();
+
+        if( more ) return isRectInter(rect1, rect2);
+
+        //框选模式,包含才匹配
+        let minX = x, maxX = x + w;
+        let minY = y, maxY = y + h;
+
+        let ponts = [rect2.p1, rect2.p2,  rect2.p3, rect2.p4];
+        let n = ponts.length;
+        while( n-- ) {
+            let p = ponts[n];
+            if( p.x < minX || p.x > maxX || p.y < minY || p.y > maxY ) return false;
+        }
+
+        return true;
+    }
+
+    calculateBounds()
+    {
+        this._bounds.clear();
+        this._bounds.addFrame(this.transform, 0,0, this.width, this.height);
+        return this._bounds;
+    }
+
+    testClick(sx:number,sy:number)
+    {
+        let w = this.width;
+        let h = this.height;
+        let local = {x:0,y:0} as any;
+        this.worldTransform.applyInverse({x:sx,y:sy} as any, local);
+
+        if(  local.x < 0 || local.x > w ) return false;
+        if(  local.y < 0 || local.y > h ) return false;
+
+        return true;
+    }
+
+    getBox()
+    {
+        let rect = this.getBounds(false, this.rect);
+        return {x:rect.x, y:rect.y, w:rect.width, h:rect.height};
+    }
+
+    getAabb() {
+        let aabb = this.getBounds(false, this.rect)
+
+        return {xmin:aabb.left,ymin:aabb.top, xmax:aabb.right, ymax:aabb.bottom};
+    }
+
+    center() {
+        let p = {x:this.width/2,y:this.height /2} as any;
+        this.worldTransform.apply(p,p);
+        return p;
+    }
+}

+ 90 - 0
src/modules/editor/controllers/SelectCtrl/event.ts

@@ -0,0 +1,90 @@
+class Event
+{
+    listeners:any = {};
+    //监听事件
+    on(eventname:string, cb:any, cbthis:any)
+    {
+        if( !this.listeners ) this.listeners = {};
+        let evtListeners = this.listeners[eventname];
+        if( !evtListeners ) {
+            evtListeners = [];
+            this.listeners[eventname] = evtListeners;
+        }
+        
+        let existL =  evtListeners.filter((item:any)=>item.cb == cb);
+        if( existL && existL.length > 0  ) {
+            existL[0].cbthis = cbthis;
+            return;
+        }
+        evtListeners.push({cb,cbthis});
+
+        let scope = this;
+        return {
+            off(){
+                scope.off(eventname, cb);
+            }
+        }
+    }
+
+    //取消监听
+    off(eventname:string, cb:any)
+    {
+        if( !this.listeners ) return;
+
+        let evtListeners = this.listeners[eventname];
+        if( !evtListeners ) return;
+        if( !cb ) {
+            this.listeners[eventname] = [];
+            return ;
+        }
+        let n = evtListeners.length;
+        while( n-- ) {
+            if( evtListeners[n].cb == cb){
+                evtListeners.splice(n,1);
+                break;
+            }
+        }
+    }
+    //触发事件
+    emit(eventname:any, ...args:any[])
+    {
+        if( !this.listeners ) return;
+        let evtListeners = this.listeners[eventname];
+        if( !evtListeners || evtListeners.length < 1) return;
+        let copyed = evtListeners.slice(0);
+        let n = copyed.length;
+        
+        for(let i=0;i<n;i++) {
+            let item  = copyed[i];
+            if( !item.cbthis ) item.cbthis = this;
+            item.cb.call(item.cbthis, ...args);
+        }
+    }
+
+    async awaitEmit(eventname:any, ...args:any[])
+    {
+        if( !this.listeners ) return;
+        let evtListeners = this.listeners[eventname];
+        if( !evtListeners || evtListeners.length < 1) return;
+        let copyed = evtListeners.slice(0);
+        let n = copyed.length;
+        
+        let objs = [];
+        for(let i=0;i<n;i++) {
+            let item  = copyed[i];
+            if( !item.cbthis ) item.cbthis = this;
+            
+            let ret = item.cb.call(item.cbthis, ...args);
+            if( ret ) {
+                objs.push( ret );
+            }
+        }
+        if( objs.length < 1 ) return;
+
+        let ret = await Promise.all( objs );
+        
+        return ret;
+    }
+}
+
+export default Event;

+ 668 - 69
src/modules/editor/controllers/SelectCtrl/index.ts

@@ -2,12 +2,25 @@ import { ModuleControl } from "queenjs";
 import { reactive } from "vue";
 import { EditorModule } from "../../module";
 import { DesignComp } from "../../objects/DesignTemp/DesignComp";
-import { Matrix } from "../TransferCtrl/Matrix";
+import { Matrix } from "./matrix";
+import { GroupActionCtrl } from "../TransferCtrl/GroupCtrl";
+import { Point } from "./objects/point";
+import { ObjsContainer } from "./ObjsContainer";
+import Event from "./event";
+import { DisplayObject } from "./objects/displayObject";
+import { CompObject } from "./compObj";
+import { each, eachRight } from "lodash";
+import { string } from "vue-types";
+import { Project, VectorLenth } from "./objects/mathUtils";
 
 /**
  *  页面画布空间进行选择
  */
 const MODE_SEL_RECT = 1;
+const MODE_MOVING = 2;
+const MODE_ROTATE = 3;
+const MODE_SCALE = 4;
+
 const MODE_NONE = 0;
 
 export class SelectCtrl extends ModuleControl<EditorModule> {
@@ -21,144 +34,730 @@ export class SelectCtrl extends ModuleControl<EditorModule> {
   };
 
   transferStyle = reactive({
-    width: "",
-    height: "",
-    transform: {
-      rotate: "0deg",
-      translateX: 0,
-      translateY: 0,
-      scale: 1,
-    },
+    showGizmo: false,
+    width: 0,
+    height: 0,
+    matrix: "matrix(1,0,0,1,0,0)",
+    matrixInvert: "matrix(1,0,0,1,0,0)",
   });
-  selectIds = []; //选中的所有组件ids
 
-  pageEl?:HTMLElement
-  selCanvas= {} as HTMLCanvasElement
+  selected: any[] = []; //选中的所有组件ids
+  mouseDownSelects: any[] = []; //鼠标按下时选中的
+
+  pageEl?: HTMLElement;
+  selCanvas = {} as HTMLCanvasElement;
 
   _downed = false;
-  _selCtx = {} as CanvasRenderingContext2D
+  _selCtx = {} as CanvasRenderingContext2D;
   _state = MODE_SEL_RECT;
 
   _selDownX = 0;
   _selDownY = 0;
   _selBox = {} as DOMRect;
-  _selCanvaseSize = {w: 0, h: 0};
-
-  initEvents(pageEl: HTMLElement, selCanvas: HTMLCanvasElement) {
+  _selCanvaseSize = { w: 0, h: 0 };
+  _downClientX = 0;
+  _downClientY = 0;
+  //groupCtrl = new GroupActionCtrl(this.module);
+  bus = new Event();
+  viewport?:HTMLElement;
+
+  initEvents(pageEl: HTMLElement, selCanvas: HTMLCanvasElement, viewport:HTMLElement) {
+    this.viewport = viewport;
     this.pageEl = pageEl;
     this.selCanvas = selCanvas;
     const b = selCanvas.getBoundingClientRect();
-    selCanvas.width = b.width *2;
-    selCanvas.height = b.height *2;
+    selCanvas.width = b.width * 2;
+    selCanvas.height = b.height * 2;
 
     this._selCtx = selCanvas.getContext("2d") as CanvasRenderingContext2D;
-   
-    this._selCanvaseSize.w = selCanvas.width  *2;
-    this._selCanvaseSize.h = selCanvas.height *2;
 
-    document.addEventListener("mousedown", this.onDocMouseDown.bind(this))
-    document.addEventListener("mousemove", this.onDocMouseMove.bind(this))
-    document.addEventListener("mouseup", this.onDocMouseUp.bind(this))
+    this._selCanvaseSize.w = selCanvas.width * 2;
+    this._selCanvaseSize.h = selCanvas.height * 2;
+
+    document.addEventListener("mousedown", this.onDocMouseDown.bind(this));
+    document.addEventListener("mousemove", this.onDocMouseMove.bind(this));
+    document.addEventListener("mouseup", this.onDocMouseUp.bind(this));
 
     window.addEventListener("resize", this.onResize.bind(this));
   }
 
+  _mouseDownFlag = "";
   onDocMouseDown(e: MouseEvent) {
-    if (!this.pageEl || !this.selCanvas ) return;
+    if (!this.pageEl || !this.selCanvas) return;
 
     let box = this.pageEl.getBoundingClientRect();
-    const pageX = e.clientX- box?.left
-    const pageY = e.clientY- box?.top
-    
+    const pageX = e.clientX - box?.left;
+    const pageY = e.clientY - box?.top;
+
     const card = this.store.currStreamCard.$el;
     box = card.getBoundingClientRect();
     const cardX = pageX;
     const cardY = e.clientY - box.top;
 
     const sel = this.selCanvas.getBoundingClientRect();
-    const selX = (e.clientX - sel.left);
-    const sely = (e.clientY - sel.top);
+    const selX = e.clientX - sel.left;
+    const sely = e.clientY - sel.top;
     this._selDownX = selX;
     this._selDownY = sely;
     this._selBox = sel;
 
-    console.log(cardX,selX, cardY, sely)
+    this._downClientX = e.clientX;
+    this._downClientY = e.clientY;
+
+    console.log(cardX, selX, cardY, sely);
 
     this._downed = true;
-    this._state = MODE_SEL_RECT;
+    this._mouseDownFlag = this.getDivFlag(e.target as any);
+    if (!this._mouseDownFlag) {
+      //选框点击判断
+      let isClickSelRect = false;
+      if (this.selected.length > 0) {
+        isClickSelRect = this.objContainer?.testClick(cardX, cardY) as boolean;
+        if (isClickSelRect) {
+          this._state = MODE_MOVING;
+        }
+      }
+
+      if (!isClickSelRect) {
+        //判断是否有点击到card stream
+        const comps = this.compClickTest(e);
+        this.mouseDownSelects = comps;
+        console.log("comps=>", comps);
+        if (comps.length < 1) {
+            const view =  this.viewport?.getBoundingClientRect() as any;
+            const isOut = (e.clientX < view.left || e.clientX > (view.right) || e.clientY < view.top || e.clientY > view.bottom)
+            if (!isOut) {
+                this._state = MODE_SEL_RECT;
+            }
+        } else {
+          this._state = MODE_MOVING;
+          const obj = this.compMap[comps[0].id];
+          this.selecteObjs([new CompObject(obj)]);
+        }
+      }
+    } else if (this._mouseDownFlag == "rotate") {
+      this._state = MODE_ROTATE;
+    } else if (this._mouseDownFlag == "move") {
+      this._state = MODE_MOVING;
+    } else if (this._mouseDownFlag.indexOf("scale") > -1) {
+      this._state = MODE_SCALE;
+    }
+
+    this._movePreClientX = this._downClientX;
+    this._movePreClientY = this._downClientY;
+  }
+
+  getDivFlag(div: HTMLElement) {
+    let c: any = div;
+    if (!c) return;
+    let i = 0;
+    do {
+      if (c.editable) return c.editable;
+      c = div.parentElement;
+      i += 1;
+      if (i > 3) {
+        return;
+      }
+    } while (c);
+  }
+
+  compClickTest(e: MouseEvent) {
+    const cards = this.store.streamCardIds;
+    let n = cards.length;
+    const compMap = this.store.designData.compMap;
+    //@ts-ignore
+    const pbox = this.pageEl.getBoundingClientRect();
+    const pageX = e.clientX - pbox?.left;
+
+    const Out = [];
+    while (n--) {
+      const cardComp = compMap[cards[n]];
+      const box = cardComp.$el.getBoundingClientRect();
+      const cardY = e.clientY - box.top;
+
+      const cardChilds = cardComp.children.default || [];
+      for (const key of cardChilds) {
+        const c = compMap[key];
+        const m = Matrix.createFromDiv(c.$el);
+        const localp = m.applyInverse(new Point(pageX, cardY));
+        const cw = this.helper.designSizeToPx(c.layout.size?.[0] as number);
+        const ch = this.helper.designSizeToPx(c.layout.size?.[1] as number);
+        const out =
+          localp.x < 0 || localp.x > cw || localp.y < 0 || localp.y > ch;
+        if (!out) {
+          Out.push({
+            id: key,
+            el: c.$el,
+            cardX: pageX,
+            cardY: cardY,
+            cardId: cards[n],
+            startMatrix: m,
+          });
+        }
+      }
+    }
+    return Out;
+  }
+
+  streamCardClickTest(e: MouseEvent) {
+    const cards = this.store.streamCardIds;
+    let n = cards.length;
+    const compMap = this.store.designData.compMap;
+    //@ts-ignore
+    const pbox = this.pageEl.getBoundingClientRect();
+    const pageX = e.clientX - pbox?.left;
+    if (pageX < 0 || pageX > pbox.width) return "";
+    while (n--) {
+      const card = compMap[cards[n]];
+      const box = card.$el.getBoundingClientRect();
+      if (e.clientY >= box.top && e.clientY <= box.bottom)
+        return { id: cards[n], x: pageX, y: e.clientY - box.top };
+    }
+    return "";
+  }
+
+  _moveSelectUpdated = false;
+
+  updateSelects() {
+    if (this._moveSelectUpdated) return;
+    this._moveSelectUpdated = true;
+
+    //鼠标按下并移动中 修正当前选中的对象
+    if (this.selected.length < 1) {
+      //没有被选中的
+      this.selected = this.mouseDownSelects;
+    } else {
+      //当前有选中的
+      let findSeleted = false;
+      let n = this.selected.length;
+      if (this.mouseDownSelects.length > 0) {
+        while (n--) {
+          const item = this.mouseDownSelects.find(
+            (item) => item.id == this.selected[n].id
+          );
+          if (item) findSeleted = true;
+        }
+      }
+      if (!findSeleted) {
+        this.selected = this.mouseDownSelects;
+      }
+    }
+    if (this.selected.length > 0) {
+      this._state = MODE_MOVING;
+    }
+  }
 
+  translate(xOffset:number, yOffset:number) {
+    const objContainer = this.objContainer as ObjsContainer;
+    objContainer.translate(xOffset, yOffset);
+    this.upgateGizmoStyle();
   }
 
+  movingMousemove(e: MouseEvent) {
+    const objContainer = this.objContainer as ObjsContainer;
+
+    objContainer.translate(
+      e.clientX - this._movePreClientX,
+      e.clientY - this._movePreClientY
+    );
+
+    this.upgateGizmoStyle();
+  }
+
+  _movePreClientX = 0;
+  _movePreClientY = 0;
+
   onDocMouseMove(e: MouseEvent) {
-    if (!this.pageEl ) return;
+    if (!this.pageEl) return;
 
     if (!this._downed) {
-        this.checkHover();
+      this.checkHover();
+      return;
     }
 
     switch (this._state) {
-        case MODE_SEL_RECT: //选框模式
-            this.drawSelRect(e);
-            break;
+      case MODE_SEL_RECT: //选框模式
+        this.drawSelRect(e);
+        break;
+      case MODE_MOVING:
+        this.movingMousemove(e);
+        break;
+      case MODE_ROTATE:
+        this.rotateMousemove(e);
+        break;
+      case MODE_SCALE:
+        this.scaleMousemove(e);
     }
+
+    this._movePreClientY = e.clientY;
+    this._movePreClientX = e.clientX;
+  }
+
+  get compMap() {
+    return this.store.designData.compMap;
   }
 
   onDocMouseUp(e: MouseEvent) {
+    let isClick = false;
+    const dx = Math.abs(e.clientX - this._downClientX);
+    const dy = Math.abs(e.clientY - this._downClientY);
+    if (dx < 2 && dy < 2) {
+      isClick = true;
+    }
+    if (isClick) {
+      this._state = MODE_NONE;
+      if (this.mouseDownSelects.length < 1) this.selecteObjs([]);
+      else {
+        const objs = this.mouseDownSelects.map(
+          (item) => new CompObject(this.compMap[item.id])
+        );
+        this.selecteObjs(objs);
+      }
+    }
+
     console.log("up");
+
+    if (this._state == MODE_SEL_RECT && !isClick) {
+      //选择空间转 streamCard空间
+      const card = this.store.currStreamCard;
+      const box = card.$el.getBoundingClientRect();
+      this.rectSelect(
+        this._lastSelRect[0] - box.left,
+        this._lastSelRect[1] - box.top,
+        this._lastSelRect[2],
+        this._lastSelRect[3]
+      );
+    }
+
+    if (this._state == MODE_ROTATE) {
+      this.rotateMouseUp(e);
+    }
+    if (this._state == MODE_SCALE) {
+      this.scaleMouseUp(e);
+    }
     this._state = MODE_NONE;
-    this._selCtx?.clearRect(0, 0, this._selCanvaseSize.w, this._selCanvaseSize.h)
+    this._downed = false;
+    this._moveSelectUpdated = false;
+    this._selCtx?.clearRect(
+      0,
+      0,
+      this._selCanvaseSize.w,
+      this._selCanvaseSize.h
+    );
+
+    this.upgateGizmoStyle();
+  }
+
+  rectSelect(x: number, y: number, width: number, height: number) {
+    const childs =
+      this.compMap[this.store.currStreamCardId].children.default || [];
+    let n = childs.length;
+    const outs = [];
+    while (n--) {
+      const o = new CompObject(this.compMap[childs[n]]);
+      if (o.testRect({ x, y, w: width, h: height }, true)) {
+        //相交
+        outs.push(o);
+      }
+    }
+    console.log(outs);
+
+    this.selecteObjs(outs);
+  }
+
+  upgateGizmoStyle() {
+    if (this.selected.length < 1) {
+      this.transferStyle.showGizmo = false;
+      return;
+    }
+
+    this.transferStyle.showGizmo = false;
+
+    const selector = this.objContainer as ObjsContainer;
+    if (!selector) {
+      return;
+    }
+    this.transferStyle.showGizmo = true;
+
+    let obj = selector.parent;
+    let w = selector.rect.width,
+      h = selector.rect.height;
+
+    let tmp = new Matrix();
+    tmp.copyFrom(obj.worldTransform);
+    // tmp.scale(0.5 , 0.5);
+
+    let matrix = `matrix(${tmp.a},${tmp.b},${tmp.c},${tmp.d},${tmp.tx},${tmp.ty})`;
+    tmp.invert();
+    let matrixInvert = `matrix(${tmp.a},${tmp.b},${tmp.c},${tmp.d},0,0)`;
+
+    this.transferStyle.width = w;
+    this.transferStyle.height = h;
+    this.transferStyle.matrix = matrix;
+    this.transferStyle.matrixInvert = matrixInvert;
   }
-  
-  selectId(id :string) { //选中ids之前 id对应组件必须已经渲染
-    console.log("selectId=>", id)
+
+  selectId(id: string) {
+    //选中ids之前 id对应组件必须已经渲染
+    console.log("selectId=>", id);
   }
 
-  drawSelRect(e:MouseEvent) {
+  _lastSelRect = [0, 0, 0, 0];
 
+  drawSelRect(e: MouseEvent) {
     const ctx = this._selCtx;
 
     const dx = this._selDownX;
     const dy = this._selDownY;
-    const currX =  e.clientX - this._selBox.left;
-    const currY =  e.clientY - this._selBox.top;
-    const x = Math.min(currX, dx), y = Math.min(dy, currY)
-     ctx.clearRect(0, 0, this._selCanvaseSize.w, this._selCanvaseSize.h)
-
-     ctx.fillStyle = "rgba(232, 139, 0, 0.16)";
-     ctx.fillRect(x*2, y*2, Math.abs(currX-dx)*2, Math.abs(currY-dy)*2);
-
-     ctx.lineWidth = 2;
-     ctx.strokeStyle = "#E88B00";
-     ctx.strokeRect(x*2, y*2, Math.abs(currX-dx)*2, Math.abs(currY-dy)*2);
+    const currX = e.clientX - this._selBox.left;
+    const currY = e.clientY - this._selBox.top;
+    const x = Math.min(currX, dx),
+      y = Math.min(dy, currY);
+    ctx.clearRect(0, 0, this._selCanvaseSize.w, this._selCanvaseSize.h);
+
+    ctx.fillStyle = "rgba(232, 139, 0, 0.16)";
+    const w = Math.abs(currX - dx);
+    const h = Math.abs(currY - dy);
+    ctx.fillRect(x * 2, y * 2, w * 2, h * 2);
+
+    ctx.lineWidth = 2;
+    ctx.strokeStyle = "#E88B00";
+    ctx.strokeRect(x * 2, y * 2, w * 2, h * 2);
+
+    this._lastSelRect[0] = x + this._selBox.left;
+    this._lastSelRect[1] = y + this._selBox.top;
+    this._lastSelRect[2] = w;
+    this._lastSelRect[3] = h;
   }
 
   checkHover() {
-    this.selectIds;
+    this.selCanvas;
   }
 
   onResize() {
-
     const b = this.selCanvas.getBoundingClientRect();
-    this.selCanvas.width = b.width *2;
-    this.selCanvas.height = b.height *2;
+    this.selCanvas.width = b.width * 2;
+    this.selCanvas.height = b.height * 2;
     this._selCtx = this.selCanvas.getContext("2d") as CanvasRenderingContext2D;
-    this._selCanvaseSize.w = b.width  *2;
-    this._selCanvaseSize.h = b.height *2;
+    this._selCanvaseSize.w = b.width * 2;
+    this._selCanvaseSize.h = b.height * 2;
   }
   //
-  checkIntersect(compId:string, e:MouseEvent) {
-     const currCard = this.store.currStreamCard.$el;
+  checkIntersect(compId: string, e: MouseEvent) {
+    const currCard = this.store.currStreamCard.$el;
+
+    const comp = this.store.designData.compMap[compId];
+
+    //排除坐标没有在streamCard空间内的坐标
 
-     const comp = this.store.designData.compMap[compId];
+    //把当前的card坐标转为 组件的自己local坐标判断是否在方框外面
+    const cardBox = currCard.getBoundingClientRect();
+    const cardX = e.clientX - cardBox.left;
+    const cardY = e.clientY - cardBox.top;
 
-     //排除坐标没有在streamCard空间内的坐标
+    //const m = Matrix.createFromComp(comp.layout.transform)
+  }
+  objContainer?: ObjsContainer;
+
+  selecteObjs(objs: any[], ContainerBox?: ObjsContainer) {
+    if (this.selected.length == 0 && objs.length == 0) return;
+    if (
+      this.selected.length == 1 &&
+      objs.length == 1 &&
+      this.selected[0] == objs[0]
+    )
+      return;
+
+    if (objs.length == 1) {
+        this.actions.pickComp(objs[0].comp.id);
+    }
+
+    // objs = this.getSceneObjOrderArr(objs);
+
+    const preObjContainer = this.objContainer;
+
+    if (this.objContainer) {
+      this.objContainer.destroy();
+      this.objContainer = undefined;
+    }
+
+    let newObjContainer = undefined;
+
+    if (objs.length > 0 && objs[0]) {
+      newObjContainer = ContainerBox ? ContainerBox : new ObjsContainer(objs);
+      if (ContainerBox) {
+        objs.forEach((obj) => {
+          ContainerBox.parent.addChildWorldNoChange(obj);
+        });
+        ContainerBox.selected = objs;
+        ContainerBox.parent.updateTransform();
+      }
+    }
+    this.objContainer = newObjContainer;
+
+    const pre = this.selected.slice(0);
+    this.selected = objs;
+
+    // if (history) {
+    //     this.editor.history.record({
+    //         undo: () => {
+
+    //             this.selected = pre;
+
+    //             if (preObjContainer) {
+    //                 let parent = preObjContainer.parent;
+    //                 pre.forEach(obj => {
+    //                     parent.addChildWorldNoChange(obj);
+    //                 });
+    //                 parent.updateTransform();
+    //                 this.objContainer = preObjContainer;
+    //             } else {
+    //                 this.objContainer = null;
+    //             }
+
+    //             this.emitChange();
+
+    //         }, redo: () => {
+    //             this.selected = objs;
+
+    //             if (preObjContainer) {
+    //                 preObjContainer.destroy();
+    //             }
+
+    //             if (newObjContainer) {
+    //                 let parent = newObjContainer.parent;
+    //                 objs.forEach(obj => {
+    //                     parent.addChildWorldNoChange(obj);
+    //                 });
+    //                 parent.updateTransform();
+    //                 this.objContainer = newObjContainer;
+    //             } else {
+    //                 this.objContainer = null;
+    //             }
+    //             this.emitChange();
+    //         }
+    //     })
+    // }
+    this.emitChange();
+    this.upgateGizmoStyle();
+
+    return this.selected;
+  }
+
+  emitChange() {
+    const selected = this.selected;
+    if (selected.length && selected[0]) {
+      this.bus.emit("showProps", selected[0].from);
+    } else {
+      this.bus.emit("showProps");
+    }
+    this.bus.emit("selectedChange");
+  }
 
-     //把当前的card坐标转为 组件的自己local坐标判断是否在方框外面
-     const cardBox = currCard.getBoundingClientRect();
-     const cardX = e.clientX - cardBox.left;
-     const cardY = e.clientY - cardBox.top;
-     
-     //const m = Matrix.createFromComp(comp.layout.transform)
+  rotateCenter?: { x: number; y: number };
+  ratatePre = 0;
+  objinitAngleRad = 0;
+  rotateCmd = false;
+  lastRad = 0;
 
+  rotateMousemove(e: MouseEvent) {
+    const card = this.store.currStreamCard;
+
+    const rect = card.$el.getBoundingClientRect();
+    let StartX = e.clientX - rect.left;
+    let StartY = e.clientY - rect.top;
+
+    const objContainer = this.objContainer as ObjsContainer;
+
+    //获取当前屏幕坐标和选框中心点坐标,计算旋转值
+    if (!this.rotateCenter) {
+      //let rect = this.objContainer.parent.getBounds(false);
+      let center = objContainer.setPivot(4);
+
+      this.rotateCenter = center;
+
+      let vec = { x: StartX - center.x, y: StartY - center.y };
+
+      let angle = Math.atan2(vec.y, vec.x);
+      if (angle < 0) angle += 2 * Math.PI;
+      this.ratatePre = angle;
+
+      this.objinitAngleRad = objContainer.parent.rotation;
+
+      this.rotateCmd = true;
+      return;
+    }
+
+    let center = this.rotateCenter;
+    let vec = { x: StartX - center.x, y: StartY - center.y };
+    let angle = Math.atan2(vec.y, vec.x);
+    if (angle < 0) angle += 2 * Math.PI;
+
+    let dta = this.objinitAngleRad + angle - this.ratatePre;
+    if (e.shiftKey) {
+      //规整到0 90 180 270
+      if (dta < 0) dta += 2 * Math.PI;
+      let Deg45 = Math.PI / 4.0;
+      let Deg90 = Math.PI / 2.0;
+      let Deg135 = Deg45 * 3;
+      let Deg225 = Deg45 * 5;
+      let Deg270 = Deg45 * 6;
+      let Deg315 = Deg45 * 7;
+      if (dta < Deg45) {
+        dta = 0;
+      } else if (dta < Deg135) {
+        dta = Deg90;
+      } else if (dta < Deg225) {
+        dta = Math.PI;
+      } else if (dta < Deg315) {
+        dta = Deg270;
+      } else {
+        dta = 0;
+      }
+    }
+    this.lastRad = dta;
+    console.log("rotate=>", dta);
+
+    objContainer.rotate(dta);
+
+    //   this.emit("translateChange", this.objContainer)
+    this.upgateGizmoStyle();
+  }
+
+  rotateMouseUp(e: MouseEvent) {
+    this.rotateCenter = undefined;
+    if (!this.rotateCmd) return;
+    let scope = this;
+    let last = this.lastRad;
+    let initrad = scope.objinitAngleRad;
+
+    // this.editor.history.record({undo:function(){
+    //         scope.objContainer.setPivot(4);
+    //         scope.objContainer.rotate( initrad );
+    //         scope.emitChange();
+    //     }, redo:function(){
+    //         scope.objContainer.setPivot(4);
+    //         scope.objContainer.rotate( last );
+    //         scope.emitChange();
+    // }});
+    this.rotateCmd = false;
+  }
+
+  //缩放选中的对象
+  scalePivot?: any;
+  scaleIndex = 0;
+  mainAxisVector = { x: 0, y: 0 };
+  initScale = { x: 1, y: 1 };
+  mainAxisVectorLenth = 0;
+  xAxisVector = { x: 1, y: 1 };
+  xAxisVectorLength = 0;
+  yAxisVector = { x: 1, y: 1 };
+  yAxisVectorLength = 0;
+  scaleCmd = false;
+  lastScale = { x: 1, y: 1 };
+
+  scaleMousemove(event: MouseEvent) {
+    let dirIndexs = [
+      "scaleBottomright",
+      "scaleBottomleft",
+      "scaleTopleft",
+      "scaleTopright",
+    ];
+    let dirOrth = ["scaleright", "scaleleft", "scalebottom", "scaletop"];
+
+    const rect = this.store.currStreamCard.$el.getBoundingClientRect();
+    let StartX = event.clientX - rect.left;
+    let StartY = event.clientY - rect.top;
+    const objContainer = this.objContainer as ObjsContainer;
+
+    //获取当前屏幕坐标和选框中心点坐标,计算旋转值
+    if (!this.scalePivot) {
+      let dir = this._mouseDownFlag;
+      let scaleIndex = dirIndexs.indexOf(dir);
+      if (scaleIndex == -1) {
+        scaleIndex = dirOrth.indexOf(dir);
+        if (scaleIndex == 2) scaleIndex = 0;
+      }
+      let pivot = objContainer.setPivot(scaleIndex);
+
+      this.scaleIndex = scaleIndex;
+      this.scalePivot = pivot;
+
+      this.mainAxisVector = { x: StartX - pivot.x, y: StartY - pivot.y };
+      let scale = objContainer.parent.scale;
+      this.initScale = { x: scale.x, y: scale.y };
+      this.mainAxisVectorLenth = VectorLenth(
+        this.mainAxisVector.x,
+        this.mainAxisVector.y
+      );
+
+      let ret = objContainer.getPivotXY(scaleIndex);
+
+      this.xAxisVector = ret.x;
+      this.xAxisVectorLength = VectorLenth(ret.x.x, ret.x.y);
+      this.yAxisVector = ret.y;
+      this.yAxisVectorLength = VectorLenth(ret.y.x, ret.y.y);
+      return;
+    }
+
+    this.scaleCmd = true;
+
+    let center = this.scalePivot;
+    let vec = { x: StartX - center.x, y: StartY - center.y };
+
+    if (event.shiftKey) {
+      //按住shift 自由缩放
+      let dtaX = Project(vec, this.xAxisVector) / this.xAxisVectorLength;
+      let dtaY = Project(vec, this.yAxisVector) / this.yAxisVectorLength;
+      this.lastScale.x = dtaX * this.initScale.x;
+      this.lastScale.y = dtaY * this.initScale.y;
+      objContainer.scale(this.lastScale.x, this.lastScale.y);
+    } else {
+      let mainVec = this.mainAxisVector;
+      let dtaScale = Project(vec, mainVec) / this.mainAxisVectorLenth;
+
+      let i = dirOrth.indexOf(this._mouseDownFlag);
+      if (i == -1) {
+        this.lastScale.x = dtaScale * this.initScale.x;
+        this.lastScale.y = dtaScale * this.initScale.y;
+        objContainer.scale(this.lastScale.x, this.lastScale.y);
+      } else if (i == 0 || i == 1) {
+        this.lastScale.x = dtaScale * this.initScale.x;
+        objContainer.scaleX(this.lastScale.x);
+      } else if (i == 2 || i == 3) {
+        this.lastScale.y = dtaScale * this.initScale.y;
+        objContainer.scaleY(this.lastScale.y);
+      }
+    }
+    this.upgateGizmoStyle();
+  }
+
+  scaleMouseUp(event: MouseEvent) {
+    this.scalePivot = undefined;
+    if (this.scaleCmd) {
+      let preScale = { x: this.initScale.x, y: this.initScale.y };
+      let scaleIndex = this.scaleIndex;
+      let lastScale = { x: this.lastScale.x, y: this.lastScale.y };
+
+      // this.editor.history.record({
+      //     undo:()=>{
+      //         this.objContainer.setPivot( scaleIndex );
+      //         this.objContainer.scale(preScale.x, preScale.y);
+
+      //         this.emitChange();
+      //     },
+      //     redo:()=>{
+      //         this.objContainer.setPivot( scaleIndex );
+      //         this.objContainer.scale(lastScale.x, lastScale.y);
+
+      //         this.emitChange();
+      //     }
+      // });
+      this.scaleCmd = false;
+
+      // this.emit("objSizeChanged");
+      // this.editor.draw();
+    }
+    // this.emitTransformed  = false;
   }
 }

+ 359 - 0
src/modules/editor/controllers/SelectCtrl/matrix.ts

@@ -0,0 +1,359 @@
+import { Point } from "./objects/point";
+const PI_2 = Math.PI * 2;
+
+/**
+ * | a | c | tx|
+ * | b | d | ty|
+ * | 0 | 0 | 1 |
+ * ```
+ */
+export class Matrix {
+  a = 1;
+  b = 0;
+  c = 0;
+  d = 1;
+  tx = 0;
+  ty = 0;
+
+  constructor(a = 1, b = 0, c = 0, d = 1, tx = 0, ty = 0) {
+    this.a = a;
+
+    this.b = b;
+    this.c = c;
+
+    this.d = d;
+
+    this.tx = tx;
+    this.ty = ty;
+  }
+
+  static createFromDiv(div: HTMLElement) {
+    const out = new Matrix();
+    out.setFormDiv(div);
+    return out;
+  }
+
+  //matrix(1,0,0,1,0,0)
+  static createFromMatrixStr(str: string) {
+    const out = new Matrix();
+    out.setMatrixStr(str);
+    return out;
+  }
+  
+  setMatrixStr(transformMatrix:string) {
+    const values = transformMatrix.split("(")[1].split(")")[0].split(",");
+    this.a = +values[0];
+    this.b = +values[1];
+    this.c = +values[2];
+    this.d = +values[3];
+
+    this.tx = +values[4]; 
+    this.ty = +values[5];
+  }
+
+  setFormDiv(div: HTMLElement) {
+    let transformMatrix = window
+      .getComputedStyle(div)
+      .getPropertyValue("transform");
+    if (!transformMatrix || transformMatrix === "none") {
+      transformMatrix = "matrix(1,0,0,1,0,0)";
+    }
+    this.setMatrixStr(transformMatrix);
+  }
+
+  applyDiv(div: HTMLElement) {
+    div.style.transform = this.getMatrixStr();
+    div.style.transformOrigin = "0 0";
+    return this;
+  }
+
+  getMatrixStr() {
+    return `matrix(${this.a},${this.b},${this.c},${this.d},${this.tx},${this.ty})`;
+  }
+  
+  fromArray(array: number[]) {
+    this.a = array[0];
+    this.b = array[1];
+    this.c = array[3];
+    this.d = array[4];
+    this.tx = array[2];
+    this.ty = array[5];
+  }
+
+  set(a: number, b: number, c: number, d: number, tx: number, ty: number) {
+    this.a = a;
+    this.b = b;
+    this.c = c;
+    this.d = d;
+    this.tx = tx;
+    this.ty = ty;
+
+    return this;
+  }
+
+  array = new Float32Array(9);
+  toArray(transpose: boolean, out: any) {
+    if (!this.array) {
+      this.array = new Float32Array(9);
+    }
+
+    const array = out || this.array;
+
+    if (transpose) {
+      array[0] = this.a;
+      array[1] = this.b;
+      array[2] = 0;
+      array[3] = this.c;
+      array[4] = this.d;
+      array[5] = 0;
+      array[6] = this.tx;
+      array[7] = this.ty;
+      array[8] = 1;
+    } else {
+      array[0] = this.a;
+      array[1] = this.c;
+      array[2] = this.tx;
+      array[3] = this.b;
+      array[4] = this.d;
+      array[5] = this.ty;
+      array[6] = 0;
+      array[7] = 0;
+      array[8] = 1;
+    }
+
+    return array;
+  }
+
+  apply(pos: Point, newPos?: Point) {
+    newPos = newPos || new Point();
+    const x = pos.x;
+    const y = pos.y;
+    newPos.x = this.a * x + this.c * y + this.tx;
+    newPos.y = this.b * x + this.d * y + this.ty;
+    return newPos;
+  }
+
+  applyInverse(pos: Point, newPos?: Point) {
+    newPos = newPos || new Point();
+
+    const id = 1 / (this.a * this.d + this.c * -this.b);
+
+    const x = pos.x;
+    const y = pos.y;
+
+    newPos.x =
+      this.d * id * x +
+      -this.c * id * y +
+      (this.ty * this.c - this.tx * this.d) * id;
+    newPos.y =
+      this.a * id * y +
+      -this.b * id * x +
+      (-this.ty * this.a + this.tx * this.b) * id;
+
+    return newPos;
+  }
+
+  translate(x: number, y: number) {
+    this.tx += x;
+    this.ty += y;
+
+    return this;
+  }
+  scale(x: number, y: number) {
+    this.a *= x;
+    this.d *= y;
+    this.c *= x;
+    this.b *= y;
+    this.tx *= x;
+    this.ty *= y;
+
+    return this;
+  }
+
+  rotate(angle: number) {
+    const cos = Math.cos(angle);
+    const sin = Math.sin(angle);
+
+    const a1 = this.a;
+    const c1 = this.c;
+    const tx1 = this.tx;
+
+    this.a = a1 * cos - this.b * sin;
+    this.b = a1 * sin + this.b * cos;
+    this.c = c1 * cos - this.d * sin;
+    this.d = c1 * sin + this.d * cos;
+    this.tx = tx1 * cos - this.ty * sin;
+    this.ty = tx1 * sin + this.ty * cos;
+
+    return this;
+  }
+
+  append(matrix: Matrix) {
+    const a1 = this.a;
+    const b1 = this.b;
+    const c1 = this.c;
+    const d1 = this.d;
+
+    this.a = matrix.a * a1 + matrix.b * c1;
+    this.b = matrix.a * b1 + matrix.b * d1;
+    this.c = matrix.c * a1 + matrix.d * c1;
+    this.d = matrix.c * b1 + matrix.d * d1;
+
+    this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx;
+    this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty;
+
+    return this;
+  }
+
+  setTransform(
+    x: number,
+    y: number,
+    pivotX: number,
+    pivotY: number,
+    scaleX: number,
+    scaleY: number,
+    rotation: number,
+    skewX: number,
+    skewY: number
+  ) {
+    this.a = Math.cos(rotation + skewY) * scaleX;
+    this.b = Math.sin(rotation + skewY) * scaleX;
+    this.c = -Math.sin(rotation - skewX) * scaleY;
+    this.d = Math.cos(rotation - skewX) * scaleY;
+
+    this.tx = x - (pivotX * this.a + pivotY * this.c);
+    this.ty = y - (pivotX * this.b + pivotY * this.d);
+
+    return this;
+  }
+
+  prepend(matrix: Matrix) {
+    const tx1 = this.tx;
+
+    if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) {
+      const a1 = this.a;
+      const c1 = this.c;
+
+      this.a = a1 * matrix.a + this.b * matrix.c;
+      this.b = a1 * matrix.b + this.b * matrix.d;
+      this.c = c1 * matrix.a + this.d * matrix.c;
+      this.d = c1 * matrix.b + this.d * matrix.d;
+    }
+
+    this.tx = tx1 * matrix.a + this.ty * matrix.c + matrix.tx;
+    this.ty = tx1 * matrix.b + this.ty * matrix.d + matrix.ty;
+
+    return this;
+  }
+
+  decompose(transform:any)
+  {
+      // sort out rotation / skew..
+      const a = this.a;
+      const b = this.b;
+      const c = this.c;
+      const d = this.d;
+
+      const skewX = -Math.atan2(-c, d);
+      const skewY = Math.atan2(b, a);
+
+      const delta = Math.abs(skewX + skewY);
+
+      transform.rotation = skewY;
+      transform.skew.x = transform.skew.y = 0;
+
+      if (delta < 0.00001 || Math.abs(PI_2 - delta) < 0.00001)
+      {
+          transform.rotation = skewY;
+          transform.skew.x = transform.skew.y = 0;
+      }
+      else
+      {
+          transform.rotation = 0;
+          transform.skew.x = skewX;
+          transform.skew.y = skewY;
+      }
+
+      // next set scale
+      transform.scale.x = Math.sqrt((a * a) + (b * b));
+      transform.scale.y = Math.sqrt((c * c) + (d * d));
+
+      // next set position
+      transform.position.x = this.tx;
+      transform.position.y = this.ty;
+
+      return transform;
+  }
+
+  invert() {
+    const a1 = this.a;
+    const b1 = this.b;
+    const c1 = this.c;
+    const d1 = this.d;
+    const tx1 = this.tx;
+    const n = a1 * d1 - b1 * c1;
+
+    this.a = d1 / n;
+    this.b = -b1 / n;
+    this.c = -c1 / n;
+    this.d = a1 / n;
+    this.tx = (c1 * this.ty - d1 * tx1) / n;
+    this.ty = -(a1 * this.ty - b1 * tx1) / n;
+
+    return this;
+  }
+
+  identity() {
+    this.a = 1;
+    this.b = 0;
+    this.c = 0;
+    this.d = 1;
+    this.tx = 0;
+    this.ty = 0;
+
+    return this;
+  }
+
+  clone() {
+    const matrix = new Matrix();
+
+    matrix.a = this.a;
+    matrix.b = this.b;
+    matrix.c = this.c;
+    matrix.d = this.d;
+    matrix.tx = this.tx;
+    matrix.ty = this.ty;
+
+    return matrix;
+  }
+
+  copyTo(matrix: Matrix) {
+    matrix.a = this.a;
+    matrix.b = this.b;
+    matrix.c = this.c;
+    matrix.d = this.d;
+    matrix.tx = this.tx;
+    matrix.ty = this.ty;
+
+    return matrix;
+  }
+
+  copyFrom(matrix: Matrix) {
+    this.a = matrix.a;
+    this.b = matrix.b;
+    this.c = matrix.c;
+    this.d = matrix.d;
+    this.tx = matrix.tx;
+    this.ty = matrix.ty;
+
+    return this;
+  }
+
+  static get IDENTITY() {
+    return new Matrix();
+  }
+
+  static get TEMP_MATRIX() {
+    return new Matrix();
+  }
+}

+ 246 - 0
src/modules/editor/controllers/SelectCtrl/objects/bounds.ts

@@ -0,0 +1,246 @@
+import { Matrix } from "../matrix";
+import { Point } from "./point";
+import { Rectangle } from "./rectangle";
+
+export class Bounds {
+  minX = Infinity;
+  minY = Infinity;
+  maxX = -Infinity;
+  maxY = -Infinity;
+  rect?: Rectangle;
+  updateID = 0;
+
+  constructor() {
+    this.minX = Infinity;
+    this.minY = Infinity;
+
+    this.maxX = -Infinity;
+    this.maxY = -Infinity;
+  }
+
+  isEmpty() {
+    return this.minX > this.maxX || this.minY > this.maxY;
+  }
+
+  clear() {
+    this.updateID++;
+
+    this.minX = Infinity;
+    this.minY = Infinity;
+    this.maxX = -Infinity;
+    this.maxY = -Infinity;
+  }
+
+  getRectangle(rect?: Rectangle) {
+    if (this.minX > this.maxX || this.minY > this.maxY) {
+      return Rectangle.EMPTY;
+    }
+
+    rect = rect || new Rectangle(0, 0, 1, 1);
+
+    rect.x = this.minX;
+    rect.y = this.minY;
+    rect.width = this.maxX - this.minX;
+    rect.height = this.maxY - this.minY;
+
+    return rect;
+  }
+
+  addPoint(point: Point) {
+    this.minX = Math.min(this.minX, point.x);
+    this.maxX = Math.max(this.maxX, point.x);
+    this.minY = Math.min(this.minY, point.y);
+    this.maxY = Math.max(this.maxY, point.y);
+  }
+
+  addQuad(vertices: number[]) {
+    let minX = this.minX;
+    let minY = this.minY;
+    let maxX = this.maxX;
+    let maxY = this.maxY;
+
+    let x = vertices[0];
+    let y = vertices[1];
+
+    minX = x < minX ? x : minX;
+    minY = y < minY ? y : minY;
+    maxX = x > maxX ? x : maxX;
+    maxY = y > maxY ? y : maxY;
+
+    x = vertices[2];
+    y = vertices[3];
+    minX = x < minX ? x : minX;
+    minY = y < minY ? y : minY;
+    maxX = x > maxX ? x : maxX;
+    maxY = y > maxY ? y : maxY;
+
+    x = vertices[4];
+    y = vertices[5];
+    minX = x < minX ? x : minX;
+    minY = y < minY ? y : minY;
+    maxX = x > maxX ? x : maxX;
+    maxY = y > maxY ? y : maxY;
+
+    x = vertices[6];
+    y = vertices[7];
+    minX = x < minX ? x : minX;
+    minY = y < minY ? y : minY;
+    maxX = x > maxX ? x : maxX;
+    maxY = y > maxY ? y : maxY;
+
+    this.minX = minX;
+    this.minY = minY;
+    this.maxX = maxX;
+    this.maxY = maxY;
+  }
+
+  addBounds(bounds: Bounds) {
+    const minX = this.minX;
+    const minY = this.minY;
+    const maxX = this.maxX;
+    const maxY = this.maxY;
+
+    this.minX = bounds.minX < minX ? bounds.minX : minX;
+    this.minY = bounds.minY < minY ? bounds.minY : minY;
+    this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX;
+    this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY;
+  }
+
+  addBoundsMask(bounds: Bounds, mask: Bounds) {
+    const _minX = bounds.minX > mask.minX ? bounds.minX : mask.minX;
+    const _minY = bounds.minY > mask.minY ? bounds.minY : mask.minY;
+    const _maxX = bounds.maxX < mask.maxX ? bounds.maxX : mask.maxX;
+    const _maxY = bounds.maxY < mask.maxY ? bounds.maxY : mask.maxY;
+
+    if (_minX <= _maxX && _minY <= _maxY) {
+      const minX = this.minX;
+      const minY = this.minY;
+      const maxX = this.maxX;
+      const maxY = this.maxY;
+
+      this.minX = _minX < minX ? _minX : minX;
+      this.minY = _minY < minY ? _minY : minY;
+      this.maxX = _maxX > maxX ? _maxX : maxX;
+      this.maxY = _maxY > maxY ? _maxY : maxY;
+    }
+  }
+  addFrame(transform:any, x0:number, y0:number, x1:number, y1:number) {
+      this.addFrameMatrix(transform.worldTransform, x0, y0, x1, y1);
+  }
+
+  addFrameMatrix(
+    matrix: Matrix,
+    x0: number,
+    y0: number,
+    x1: number,
+    y1: number
+  ) {
+    const a = matrix.a;
+    const b = matrix.b;
+    const c = matrix.c;
+    const d = matrix.d;
+    const tx = matrix.tx;
+    const ty = matrix.ty;
+
+    let minX = this.minX;
+    let minY = this.minY;
+    let maxX = this.maxX;
+    let maxY = this.maxY;
+
+    let x = a * x0 + c * y0 + tx;
+    let y = b * x0 + d * y0 + ty;
+
+    minX = x < minX ? x : minX;
+    minY = y < minY ? y : minY;
+    maxX = x > maxX ? x : maxX;
+    maxY = y > maxY ? y : maxY;
+
+    x = a * x1 + c * y0 + tx;
+    y = b * x1 + d * y0 + ty;
+    minX = x < minX ? x : minX;
+    minY = y < minY ? y : minY;
+    maxX = x > maxX ? x : maxX;
+    maxY = y > maxY ? y : maxY;
+
+    x = a * x0 + c * y1 + tx;
+    y = b * x0 + d * y1 + ty;
+    minX = x < minX ? x : minX;
+    minY = y < minY ? y : minY;
+    maxX = x > maxX ? x : maxX;
+    maxY = y > maxY ? y : maxY;
+
+    x = a * x1 + c * y1 + tx;
+    y = b * x1 + d * y1 + ty;
+    minX = x < minX ? x : minX;
+    minY = y < minY ? y : minY;
+    maxX = x > maxX ? x : maxX;
+    maxY = y > maxY ? y : maxY;
+
+    this.minX = minX;
+    this.minY = minY;
+    this.maxX = maxX;
+    this.maxY = maxY;
+  }
+
+  addBoundsMatrix(bounds: Bounds, matrix: Matrix) {
+    this.addFrameMatrix(
+      matrix,
+      bounds.minX,
+      bounds.minY,
+      bounds.maxX,
+      bounds.maxY
+    );
+  }
+
+  addBoundsArea(bounds: Bounds, area: Rectangle) {
+    const _minX = bounds.minX > area.x ? bounds.minX : area.x;
+    const _minY = bounds.minY > area.y ? bounds.minY : area.y;
+    const _maxX =
+      bounds.maxX < area.x + area.width ? bounds.maxX : area.x + area.width;
+    const _maxY =
+      bounds.maxY < area.y + area.height ? bounds.maxY : area.y + area.height;
+
+    if (_minX <= _maxX && _minY <= _maxY) {
+      const minX = this.minX;
+      const minY = this.minY;
+      const maxX = this.maxX;
+      const maxY = this.maxY;
+
+      this.minX = _minX < minX ? _minX : minX;
+      this.minY = _minY < minY ? _minY : minY;
+      this.maxX = _maxX > maxX ? _maxX : maxX;
+      this.maxY = _maxY > maxY ? _maxY : maxY;
+    }
+  }
+
+  pad(paddingX: number, paddingY: number) {
+    paddingX = paddingX || 0;
+    paddingY = paddingY || (paddingY !== 0 ? paddingX : 0);
+
+    if (!this.isEmpty()) {
+      this.minX -= paddingX;
+      this.maxX += paddingX;
+      this.minY -= paddingY;
+      this.maxY += paddingY;
+    }
+  }
+
+  addFramePad(
+    x0: number,
+    y0: number,
+    x1: number,
+    y1: number,
+    padX: number,
+    padY: number
+  ) {
+    x0 -= padX;
+    y0 -= padY;
+    x1 += padX;
+    y1 += padY;
+
+    this.minX = this.minX < x0 ? this.minX : x0;
+    this.maxX = this.maxX > x1 ? this.maxX : x1;
+    this.minY = this.minY < y0 ? this.minY : y0;
+    this.maxY = this.maxY > y1 ? this.maxY : y1;
+  }
+}

+ 10 - 0
src/modules/editor/controllers/SelectCtrl/objects/const.ts

@@ -0,0 +1,10 @@
+export const PI_2 = Math.PI * 2;
+export const RAD_TO_DEG = 180 / Math.PI;
+export const DEG_TO_RAD = Math.PI / 180;
+export const SHAPES = {
+    POLY: 0,
+    RECT: 1,
+    CIRC: 2,
+    ELIP: 3,
+    RREC: 4,
+};

+ 388 - 0
src/modules/editor/controllers/SelectCtrl/objects/container.ts

@@ -0,0 +1,388 @@
+import { removeItems } from "./utils";
+import { DisplayObject } from "./displayObject";
+import { Transform } from "./transform";
+
+function sortChildren(a: any, b: any) {
+  if (a.zIndex === b.zIndex) {
+    return a._lastSortedIndex - b._lastSortedIndex;
+  }
+
+  return a.zIndex - b.zIndex;
+}
+
+const IDENTITY = Transform.IDENTITY;
+export class Container extends DisplayObject {
+  children: DisplayObject[] = [];
+  sortableChildren = true;
+  sortDirty = false;
+
+  constructor() {
+    super();
+
+    this.sortableChildren = true;
+
+    this.sortDirty = false;
+  }
+
+  onChildrenChange() {
+    /* empty */
+  }
+
+  //保持child的坐标位置不变
+  addChildWorldNoChange(child: DisplayObject) {
+    const m = this.worldTransform.clone();
+    m.invert();
+    const wm = child.worldTransform.clone();
+    wm.prepend(m);
+    // m.append( child.worldTransform );
+
+    child.transform.setFromMatrix(wm);
+
+    this.addChild(child);
+
+    child.updateTransform();
+  }
+
+  removeChildWorldNoChange(child: DisplayObject) {
+    const m = child.worldTransform.clone();
+    child.transform.setFromMatrix(m);
+
+    this.removeChild(child);
+
+    child.updateTransform();
+  }
+
+  addChild(child: DisplayObject) {
+    // if the child has a parent then lets remove it as PixiJS objects can only exist in one place
+    if (child.parent) {
+      child.parent.removeChild(child);
+    }
+
+    child.parent = this;
+    this.sortDirty = true;
+
+    // ensure child transform will be recalculated
+    child.transform._parentID = -1;
+
+    this.children.push(child);
+
+    // ensure bounds will be recalculated
+    this._boundsID++;
+
+    // TODO - lets either do all callbacks or all events.. not both!
+    this.onChildrenChange();
+    //   this.emit("childAdded", child, this, this.children.length - 1);
+    //   child.emit("added", this);
+
+    return child;
+  }
+
+  addChildAt(child: DisplayObject, index: number) {
+    if (index < 0 || index > this.children.length) {
+      throw new Error(
+        `${child}addChildAt: The index ${index} supplied is out of bounds ${this.children.length}`
+      );
+    }
+
+    if (child.parent) {
+      child.parent.removeChild(child);
+    }
+
+    child.parent = this;
+    this.sortDirty = true;
+
+    // ensure child transform will be recalculated
+    child.transform._parentID = -1;
+
+    this.children.splice(index, 0, child);
+
+    // ensure bounds will be recalculated
+    this._boundsID++;
+
+    // TODO - lets either do all callbacks or all events.. not both!
+    this.onChildrenChange();
+    // child.emit("added", this);
+    // this.emit("childAdded", child, this, index);
+
+    return child;
+  }
+
+  /**
+   * Swaps the position of 2 Display Objects within this container.
+   *
+   */
+  swapChildren(child: any, child2: any) {
+    if (child === child2) {
+      return;
+    }
+
+    const index1 = this.getChildIndex(child);
+    const index2 = this.getChildIndex(child2);
+
+    this.children[index1] = child2;
+    this.children[index2] = child;
+    this.onChildrenChange();
+  }
+
+  /**
+   * Returns the index position of a child DisplayObject instance
+   */
+  getChildIndex(child: DisplayObject) {
+    const index = this.children.indexOf(child);
+
+    if (index === -1) {
+      throw new Error(
+        "The supplied DisplayObject must be a child of the caller"
+      );
+    }
+
+    return index;
+  }
+
+  /**
+   * Changes the position of an existing child in the display object container
+   */
+  setChildIndex(child: DisplayObject, index: number) {
+    if (index < 0 || index >= this.children.length) {
+      throw new Error(
+        `The index ${index} supplied is out of bounds ${this.children.length}`
+      );
+    }
+
+    const currentIndex = this.getChildIndex(child);
+
+    removeItems(this.children, currentIndex, 1); // remove from old position
+    this.children.splice(index, 0, child); // add at new position
+
+    this.onChildrenChange();
+  }
+
+  /**
+   * Returns the child at the specified index
+   */
+  getChildAt(index: number) {
+    if (index < 0 || index >= this.children.length) {
+      throw new Error(`getChildAt: Index (${index}) does not exist.`);
+    }
+
+    return this.children[index];
+  }
+
+  /**
+   * Removes one or more children from the container.
+   *
+   */
+  removeChild(child: DisplayObject) {
+    const index = this.children.indexOf(child);
+
+    if (index === -1) return null;
+
+    child.parent = null;
+    // ensure child transform will be recalculated
+    child.transform._parentID = -1;
+    removeItems(this.children, index, 1);
+
+    // ensure bounds will be recalculated
+    this._boundsID++;
+
+    // TODO - lets either do all callbacks or all events.. not both!
+    this.onChildrenChange();
+    //   child.emit("removed", this);
+    //   this.emit("childRemoved", child, this, index);
+
+    return child;
+  }
+
+  /**
+   * Removes a child from the specified index position.
+   */
+  removeChildAt(index: number) {
+    const child = this.getChildAt(index);
+
+    // ensure child transform will be recalculated..
+    child.parent = null;
+    child.transform._parentID = -1;
+    removeItems(this.children, index, 1);
+
+    // ensure bounds will be recalculated
+    this._boundsID++;
+
+    // TODO - lets either do all callbacks or all events.. not both!
+    this.onChildrenChange();
+    // child.emit("removed", this);
+    // this.emit("childRemoved", child, this, index);
+
+    return child;
+  }
+
+  /**
+   * Removes all children from this container that are within the begin and end indexes.
+   *
+   */
+  removeChildren(beginIndex = 0, endIndex: number) {
+    const begin = beginIndex;
+    const end = typeof endIndex === "number" ? endIndex : this.children.length;
+    const range = end - begin;
+    let removed;
+
+    if (range > 0 && range <= end) {
+      removed = this.children.splice(begin, range);
+
+      for (let i = 0; i < removed.length; ++i) {
+        removed[i].parent = null;
+        if (removed[i].transform) {
+          removed[i].transform._parentID = -1;
+        }
+      }
+
+      this._boundsID++;
+
+      this.onChildrenChange();
+
+      for (let i = 0; i < removed.length; ++i) {
+        // removed[i].emit("removed", this);
+        // this.emit("childRemoved", removed[i], this, i);
+      }
+
+      return removed;
+    } else if (range === 0 && this.children.length === 0) {
+      return [];
+    }
+
+    throw new RangeError(
+      "removeChildren: numeric values are outside the acceptable range."
+    );
+  }
+
+  /**
+   * Sorts children by zIndex. Previous order is mantained for 2 children with the same zIndex.
+   */
+  sortChildren() {
+    let sortRequired = false;
+
+    for (let i = 0, j = this.children.length; i < j; ++i) {
+      const child = this.children[i];
+
+      child._lastSortedIndex = i;
+
+      if (!sortRequired && child.zIndex !== 0) {
+        sortRequired = true;
+      }
+    }
+
+    if (sortRequired && this.children.length > 1) {
+      this.children.sort(sortChildren);
+    }
+
+    this.sortDirty = false;
+  }
+
+  /**
+   * Updates the transform on all children of this container for rendering
+   */
+  updateTransform() {
+    if (this.sortableChildren && this.sortDirty) {
+      this.sortChildren();
+    }
+
+    this._boundsID++;
+
+    this.transform.updateTransform(
+      this.parent ? this.parent.transform : IDENTITY
+    );
+
+    // TODO: check render flags, how to process stuff here
+    this.worldAlpha = this.alpha * (this.parent ? this.parent.worldAlpha : 1);
+
+    for (let i = 0, j = this.children.length; i < j; ++i) {
+      const child = this.children[i];
+
+      if (child.visible) {
+        child.updateTransform();
+      }
+    }
+  }
+
+  /**
+   * Recalculates the bounds of the container.
+   *
+   */
+  calculateBounds() {
+    this._bounds.clear();
+
+    this._calculateBounds();
+
+    for (let i = 0; i < this.children.length; i++) {
+      const child = this.children[i];
+
+      if (!child.visible) {
+        continue;
+      }
+
+      //child.calculateBounds();
+
+      // TODO: filter+mask, need to mask both somehow
+      //   if (child._mask) {
+      //     child._mask.calculateBounds();
+      //     this._bounds.addBoundsMask(child._bounds, child._mask._bounds);
+      //   } else if (child.filterArea) {
+      //     this._bounds.addBoundsArea(child._bounds, child.filterArea);
+      //   } else {
+      this._bounds.addBounds(child._bounds);
+      //   }
+    }
+
+    this._lastBoundsID = this._boundsID;
+  }
+
+  /**
+   * Recalculates the bounds of the object. Override this to
+   * calculate the bounds of the specific object (not including children).
+   */
+  _calculateBounds() {
+    // FILL IN//
+  }
+
+  /**
+   * The width of the Container, setting this will actually modify the scale to achieve the value set
+   *
+   * @member {number}
+   */
+  get width() {
+    return this.scale.x * this.getLocalBounds().width;
+  }
+  _width = 0;
+
+  set width(value) {
+    const width = this.getLocalBounds().width;
+
+    if (width !== 0) {
+      this.scale.x = value / width;
+    } else {
+      this.scale.x = 1;
+    }
+    this._width = value;
+  }
+
+  /**
+   * The height of the Container, setting this will actually modify the scale to achieve the value set
+   * @member {number}
+   */
+  get height() {
+    return this.scale.y * this.getLocalBounds().height;
+  }
+  _height = 0;
+
+  
+  set height(value) {
+    const height = this.getLocalBounds().height;
+
+    if (height !== 0) {
+      this.scale.y = value / height;
+    } else {
+      this.scale.y = 1;
+    }
+    this._height = value;
+  }
+  containerUpdateTransform = this.updateTransform;
+}

+ 444 - 0
src/modules/editor/controllers/SelectCtrl/objects/displayObject.ts

@@ -0,0 +1,444 @@
+import { RAD_TO_DEG, DEG_TO_RAD } from "./const";
+import { Bounds } from "./bounds";
+import { Point } from "./point";
+import { Transform } from "./transform";
+import { Rectangle } from "./rectangle";
+
+export class DisplayObject {
+
+  tempDisplayObjectParent?:DisplayObject;
+
+  transform = new Transform();
+
+  visible = true;
+
+  parent?:any;
+
+  alpha = 1;
+
+  /**
+   * The multiplied alpha of the displayObject.
+   */
+  worldAlpha = 1;
+
+  /**
+   * Which index in the children array the display component was before the previous zIndex sort.
+   * Used by containers to help sort objects with the same zIndex, by using previous array index as the decider.
+   */
+  _lastSortedIndex = 0;
+
+  /**
+   * The zIndex of the displayObject.
+   * A higher value will mean it will be rendered on top of other displayObjects within the same container.
+   */
+  _zIndex = 0;
+
+  /**
+   * The bounds object, this is used to calculate and store the bounds of the displayObject.
+   */
+  _bounds = new Bounds();
+  _boundsID = 0;
+  _lastBoundsID = -1;
+  _boundsRect?:Rectangle;
+
+  _localBoundsRect?:Rectangle;
+
+  /**
+   * If the object has been destroyed via destroy(). If true, it should not be used.
+   */
+  _destroyed = false;
+
+  get _tempDisplayObjectParent() {
+    if (!this.tempDisplayObjectParent) {
+      this.tempDisplayObjectParent = new DisplayObject();
+    }
+
+    return this.tempDisplayObjectParent as DisplayObject;
+  }
+
+  /**
+   * Updates the object transform for rendering.
+   * TODO - Optimization pass!
+   */
+  updateTransform() {
+    if (this.parent) {
+      this.transform.updateTransform(this.parent.transform);
+      // multiply the alphas..
+      this.worldAlpha = this.alpha * this.parent.worldAlpha;
+    } else {
+      this.transform.updateTransform(this._tempDisplayObjectParent.transform);
+      this.worldAlpha = this.alpha;
+    }
+
+    this._boundsID++;
+  }
+
+  /**
+   * Recursively updates transform of all objects from the root to this one
+   * internal function for toLocal()
+   */
+  _recursivePostUpdateTransform() {
+    if (this.parent) {
+      this.parent._recursivePostUpdateTransform();
+      this.transform.updateTransform(this.parent.transform);
+    } else {
+      this.transform.updateTransform(this._tempDisplayObjectParent.transform);
+    }
+  }
+  
+  calculateBounds() {
+    console.log("please implement me")
+  }
+
+  getBounds(skipUpdate:boolean, rect?:Rectangle) {
+    if (!skipUpdate) {
+      if (!this.parent) {
+        this.parent = this._tempDisplayObjectParent;
+        this.updateTransform();
+        this.parent = undefined;
+      } else {
+        this._recursivePostUpdateTransform();
+        this.updateTransform();
+      }
+    }
+
+    if (this._boundsID !== this._lastBoundsID) {
+      this.calculateBounds();
+      this._lastBoundsID = this._boundsID;
+    }
+
+    if (!rect) {
+      if (!this._boundsRect) {
+        this._boundsRect = new Rectangle();
+      }
+
+      rect = this._boundsRect;
+    }
+
+    return this._bounds.getRectangle(rect);
+  }
+
+  getLocalBounds(rect?:Rectangle) {
+    const transformRef = this.transform;
+    const parentRef = this.parent;
+
+    this.parent = undefined;
+    this.transform = this._tempDisplayObjectParent.transform;
+
+    if (!rect) {
+      if (!this._localBoundsRect) {
+        this._localBoundsRect = new Rectangle();
+      }
+
+      rect = this._localBoundsRect;
+    }
+
+    const bounds = this.getBounds(false, rect);
+
+    this.parent = parentRef;
+    this.transform = transformRef;
+
+    return bounds;
+  }
+
+  /**
+   * Calculates the global position of the display object.
+   */
+  toGlobal(position:Point, point:Point, skipUpdate = false) {
+    if (!skipUpdate) {
+      this._recursivePostUpdateTransform();
+
+      // this parent check is for just in case the item is a root object.
+      // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly
+      // this is mainly to avoid a parent check in the main loop. Every little helps for performance :)
+      if (!this.parent) {
+        this.parent = this._tempDisplayObjectParent;
+        this.displayObjectUpdateTransform();
+        this.parent = undefined;
+      } else {
+        this.displayObjectUpdateTransform();
+      }
+    }
+    // don't need to update the lot
+    return this.worldTransform.apply(position, point);
+  }
+
+  /**
+   * Calculates the local position of the display object relative to another point.
+   *
+   * @param {PIXI.IPoint} position - The world origin to calculate from.
+   * @param {PIXI.DisplayObject} [from] - The DisplayObject to calculate the global position from.
+   * @param {PIXI.IPoint} [point] - A Point object in which to store the value, optional
+   *  (otherwise will create a new Point).
+   * @param {boolean} [skipUpdate=false] - Should we skip the update transform
+   * @return {PIXI.IPoint} A point object representing the position of this object
+   */
+  toLocal(position:Point, from:DisplayObject, point:Point, skipUpdate:boolean) {
+    if (from) {
+      position = from.toGlobal(position, point, skipUpdate);
+    }
+
+    if (!skipUpdate) {
+      this._recursivePostUpdateTransform();
+
+      // this parent check is for just in case the item is a root object.
+      // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly
+      // this is mainly to avoid a parent check in the main loop. Every little helps for performance :)
+      if (!this.parent) {
+        this.parent = this._tempDisplayObjectParent;
+        this.displayObjectUpdateTransform();
+        this.parent = undefined;
+      } else {
+        this.displayObjectUpdateTransform();
+      }
+    }
+
+    // simply apply the matrix..
+    return this.worldTransform.applyInverse(position, point);
+  }
+
+  setParent(container:any) {
+    if (!container || !container.addChild) {
+      throw new Error("setParent: Argument must be a Container");
+    }
+
+    container.addChild(this);
+
+    return container;
+  }
+
+  setTransform(
+    x = 0,
+    y = 0,
+    scaleX = 1,
+    scaleY = 1,
+    rotation = 0,
+    skewX = 0,
+    skewY = 0,
+    pivotX = 0,
+    pivotY = 0
+  ) {
+    this.position.x = x;
+    this.position.y = y;
+    this.scale.x = !scaleX ? 1 : scaleX;
+    this.scale.y = !scaleY ? 1 : scaleY;
+    this.rotation = rotation;
+    this.skew.x = skewX;
+    this.skew.y = skewY;
+    this.pivot.x = pivotX;
+    this.pivot.y = pivotY;
+
+    return this;
+  }
+
+  /**
+   * Base destroy method for generic display objects. This will automatically
+   * remove the display object from its parent Container as well as remove
+   * all current event listeners and internal references. Do not use a DisplayObject
+   * after calling `destroy()`.
+   *
+   */
+  sortDirty = false;
+
+  destroy() {
+
+    if (this.parent) {
+      this.parent.removeChild(this);
+    }
+
+
+    this.parent = undefined;
+    this._destroyed = true;
+  }
+
+  /**
+   * The position of the displayObject on the x axis relative to the local coordinates of the parent.
+   * An alias to position.x
+   */
+  get x() {
+    return this.position.x;
+  }
+
+  set x( value // eslint-disable-line require-jsdoc
+  ) {
+    this.transform.position.x = value;
+  }
+
+  /**
+   * The position of the displayObject on the y axis relative to the local coordinates of the parent.
+   * An alias to position.y
+   *
+   * @member {number}
+   */
+  get y() {
+    return this.position.y;
+  }
+
+  set y(
+    value // eslint-disable-line require-jsdoc
+  ) {
+    this.transform.position.y = value;
+  }
+
+  /**
+   * Current transform of the object based on world (parent) factors.
+   *
+   * @member {PIXI.Matrix}
+   * @readonly
+   */
+  get worldTransform() {
+    return this.transform.worldTransform;
+  }
+
+  get localTransform() {
+    return this.transform.localTransform;
+  }
+
+  /**
+   * The coordinate of the object relative to the local coordinates of the parent.
+   * Assignment by value since pixi-v4.
+   *
+   * @member {PIXI.IPoint}
+   */
+  get position() {
+    return this.transform.position;
+  }
+
+  set position(
+    value // eslint-disable-line require-jsdoc
+  ) {
+    this.transform.position.copyFrom(value);
+  }
+
+  /**
+   * The scale factor of the object.
+   * Assignment by value since pixi-v4.
+   *
+   * @member {PIXI.IPoint}
+   */
+  get scale() {
+    return this.transform.scale;
+  }
+
+  set scale(
+    value // eslint-disable-line require-jsdoc
+  ) {
+    this.transform.scale.copyFrom(value);
+  }
+
+  /**
+   * The pivot point of the displayObject that it rotates around.
+   * Assignment by value since pixi-v4.
+   *
+   * @member {PIXI.IPoint}
+   */
+  get pivot() {
+    return this.transform.pivot;
+  }
+
+  set pivot( value ) {
+    this.transform.pivot.copyFrom(value);
+  }
+
+  //保持当前对象位置不变,修改pivot
+  setPivotWorldNoChange(x:number, y:number) {
+    const targetPivot = { x, y };
+    const point = { x: targetPivot.x, y: targetPivot.y };
+    this.worldTransform.apply(point as any, point as any);
+
+    this.pivot = targetPivot as any;
+    this.position.x = point.x;
+    this.position.y = point.y;
+
+    this.updateTransform();
+  }
+  /**
+   * The skew factor for the object in radians.
+   * Assignment by value since pixi-v4.
+   */
+  get skew() {
+    return this.transform.skew;
+  }
+
+  set skew(
+    value // eslint-disable-line require-jsdoc
+  ) {
+    this.transform.skew.copyFrom(value);
+  }
+
+  /**
+   * The rotation of the object in radians.
+   * 'rotation' and 'angle' have the same effect on a display object; rotation is in radians, angle is in degrees.
+   */
+  get rotation() {
+    return this.transform.rotation;
+  }
+
+  set rotation(
+    value // eslint-disable-line require-jsdoc
+  ) {
+    this.transform.rotation = value;
+  }
+
+  /**
+   * The angle of the object in degrees.
+   * 'rotation' and 'angle' have the same effect on a display object; rotation is in radians, angle is in degrees.
+   *
+   * @member {number}
+   */
+  get angle() {
+    return this.transform.rotation * RAD_TO_DEG;
+  }
+
+  set angle(
+    value // eslint-disable-line require-jsdoc
+  ) {
+    this.transform.rotation = value * DEG_TO_RAD;
+  }
+
+  /**
+   * The zIndex of the displayObject.
+   * If a container has the sortableChildren property set to true, children will be automatically
+   * sorted by zIndex value; a higher value will mean it will be moved towards the end of the array,
+   * and thus rendered on top of other displayObjects within the same container.
+   *
+   * @member {number}
+   */
+  get zIndex() {
+    return this._zIndex;
+  }
+
+  set zIndex(
+    value // eslint-disable-line require-jsdoc
+  ) {
+    this._zIndex = value;
+    if (this.parent) {
+      this.parent.sortDirty = true;
+    }
+  }
+
+  get worldVisible() {
+    let item:any = this;
+    do {
+      if (!item.visible) {
+        return false;
+      }
+
+      item = item.parent;
+    } while (item);
+    return true;
+  }
+
+  getGlobalPosition( point = new Point(),skipUpdate = false ) {
+    if (this.parent) {
+      this.parent.toGlobal(this.position, point, skipUpdate);
+    } else {
+      point.x = this.position.x;
+      point.y = this.position.y;
+    }
+    return point;
+  }
+
+  displayObjectUpdateTransform = this.updateTransform;
+}
+

+ 205 - 0
src/modules/editor/controllers/SelectCtrl/objects/mathUtils.ts

@@ -0,0 +1,205 @@
+import { Point } from "./point";
+
+ // 计算 |p1 p2| X |p1 p|
+ function GetCross( p1:Point, p2:Point, p:Point)
+ {
+     return (p2.x - p1.x) * (p.y - p1.y) -(p.x - p1.x) * (p2.y - p1.y);
+ }
+ 
+ function IsPointInRect(p:Point, rect:any)
+ {
+     const p1 = rect.p1;
+     const p2 = rect.p2;
+     const p3 = rect.p3;
+     const p4 = rect.p4;
+     return GetCross(p1,p2,p) * GetCross(p3,p4,p) >= 0 && GetCross(p2,p3,p) * GetCross(p4,p1,p) >= 0;
+     //return false;
+ }
+
+ function areVecsEqual(v1:any, v2:any) {
+     return v1.x == v2.x && v1.y == v2.y;
+ }
+ 
+ /**
+  * Returns an indicator of where the specified point
+  * {@code (px,py)} lies with respect to the line segment from
+  * {@code (x1,y1)} to {@code (x2,y2)}.
+  * The return value can be either 1, -1, or 0 and indicates
+  * in which direction the specified line must pivot around its
+  * first end point, {@code (x1,y1)}, in order to point at the
+  * specified point {@code (px,py)}.
+  * <p>A return value of 1 indicates that the line segment must
+  * turn in the direction that takes the positive X axis towards
+  * the negative Y axis.  In the default coordinate system used by
+  * Java 2D, this direction is counterclockwise.
+  * <p>A return value of -1 indicates that the line segment must
+  * turn in the direction that takes the positive X axis towards
+  * the positive Y axis.  In the default coordinate system, this
+  * direction is clockwise.
+  * <p>A return value of 0 indicates that the point lies
+  * exactly on the line segment.  Note that an indicator value
+  * of 0 is rare and not useful for determining collinearity
+  * because of floating point rounding issues.
+  * <p>If the point is colinear with the line segment, but
+  * not between the end points, then the value will be -1 if the point
+  * lies "beyond {@code (x1,y1)}" or 1 if the point lies
+  * "beyond {@code (x2,y2)}".
+  *
+  * @param x1 the X coordinate of the start point of the
+  *           specified line segment
+  * @param y1 the Y coordinate of the start point of the
+  *           specified line segment
+  * @param x2 the X coordinate of the end point of the
+  *           specified line segment
+  * @param y2 the Y coordinate of the end point of the
+  *           specified line segment
+  * @param px the X coordinate of the specified point to be
+  *           compared with the specified line segment
+  * @param py the Y coordinate of the specified point to be
+  *           compared with the specified line segment
+  * @return an integer that indicates the position of the third specified
+  *                  coordinates with respect to the line segment formed
+  *                  by the first two specified coordinates.
+  */
+ 
+  function relativeCCW( x1:number,  y1:number, x2:number, y2:number, px:number, py:number)
+ {
+     x2 -= x1;
+     y2 -= y1;
+     px -= x1;
+     py -= y1;
+     let ccw = px * y2 - py * x2;
+     if (ccw == 0.0) {
+         // The point is colinear, classify based on which side of
+         // the segment the point falls on.  We can calculate a
+         // relative value using the projection of px,py onto the
+         // segment - a negative value indicates the point projects
+         // outside of the segment in the direction of the particular
+         // endpoint used as the origin for the projection.
+         ccw = px * x2 + py * y2;
+         if (ccw > 0.0) {
+             // Reverse the projection to be relative to the original x2,y2
+             // x2 and y2 are simply negated.
+             // px and py need to have (x2 - x1) or (y2 - y1) subtracted
+             //    from them (based on the original values)
+             // Since we really want to get a positive answer when the
+             //    point is "beyond (x2,y2)", then we want to calculate
+             //    the inverse anyway - thus we leave x2 & y2 negated.
+             px -= x2;
+             py -= y2;
+             ccw = px * x2 + py * y2;
+             if (ccw < 0.0) {
+                 ccw = 0.0;
+             }
+         }
+     }
+     return (ccw < 0.0) ? -1 : ((ccw > 0.0) ? 1 : 0);
+ }
+ 
+ /**
+      * Tests if the line segment from {@code (x1,y1)} to
+      * {@code (x2,y2)} intersects the line segment from {@code (x3,y3)}
+      * to {@code (x4,y4)}.
+      *
+      * @param x1 the X coordinate of the start point of the first
+      *           specified line segment
+      * @param y1 the Y coordinate of the start point of the first
+      *           specified line segment
+      * @param x2 the X coordinate of the end point of the first
+      *           specified line segment
+      * @param y2 the Y coordinate of the end point of the first
+      *           specified line segment
+      * @param x3 the X coordinate of the start point of the second
+      *           specified line segment
+      * @param y3 the Y coordinate of the start point of the second
+      *           specified line segment
+      * @param x4 the X coordinate of the end point of the second
+      *           specified line segment
+      * @param y4 the Y coordinate of the end point of the second
+      *           specified line segment
+      * @return <code>true</code> if the first specified line segment
+      *                  and the second specified line segment intersect
+      *                  each other; <code>false</code> otherwise.
+      * @since 1.2
+      */
+ 
+ export function isLineIntersect( line1:any, line2:any)
+ {
+     const x1 = line1.p1.x, x2=line1.p2.x, y1 = line1.p1.y,y2 = line1.p2.y;
+ 
+     const x3 = line2.p1.x, x4 = line2.p2.x, y3 = line2.p1.y, y4 = line2.p2.y;
+ 
+     return ((relativeCCW(x1, y1, x2, y2, x3, y3) * relativeCCW(x1, y1, x2, y2, x4, y4) <= 0)
+             && (relativeCCW(x3, y3, x4, y4, x1, y1) * relativeCCW(x3, y3, x4, y4, x2, y2) <= 0));
+ }
+ 
+ export  function isRectInter(rect1:any, rect2:any)
+ {
+    const lines1 = [
+         {p1:rect1.p1, p2:rect1.p2},
+         {p1:rect1.p3, p2:rect1.p4},
+         {p1:rect1.p1, p2:rect1.p4},
+         {p1:rect1.p2, p2:rect1.p3},
+     ];
+     const lines2 = [
+         {p1:rect2.p1, p2:rect2.p2},
+         {p1:rect2.p3, p2:rect2.p4},
+         {p1:rect2.p1, p2:rect2.p4},
+         {p1:rect2.p2, p2:rect2.p3},
+     ]
+ 
+     //判定线段是否相交
+     for( let i=0; i<4; i++) {    
+         for(let k=0; k<4;k++) {
+             const isIn = isLineIntersect(lines1[i], lines2[k]);
+             if( isIn ) return true;
+         }
+     }
+     
+     let isIn = IsPointInRect(rect1.p1, rect2);
+     if( isIn ) return true;
+     isIn = IsPointInRect(rect1.p2, rect2);
+     if( isIn ) return true;
+     isIn = IsPointInRect(rect1.p3, rect2);
+     if( isIn ) return true;
+     isIn = IsPointInRect(rect1.p4, rect2);
+     if( isIn ) return true;
+ 
+     isIn = IsPointInRect(rect2.p1, rect1);
+     if( isIn ) return true;
+ 
+     isIn = IsPointInRect(rect2.p2, rect1);
+     if( isIn ) return true;
+ 
+     isIn = IsPointInRect(rect2.p3, rect1);
+     if( isIn ) return true;
+     
+     isIn = IsPointInRect(rect2.p4, rect1);
+     if( isIn ) return true;
+ 
+ 
+     return false;
+ };
+ 
+ //v1 向量到v2的投影
+ export function Project(v1:any,v2:any)
+ {
+    return Vectordot(v1, v2) / VectorLenth(v2.x, v2.y)
+ }
+ const MATH_FLOAT_SMALL = 1.0e-37;
+ 
+ export function Angle(v1:any, v2:any)
+ {
+    const dz = v1.x * v2.y - v1.y * v2.x;
+     return Math.atan2(Math.abs(dz) + MATH_FLOAT_SMALL, Vectordot(v1, v2));
+ }
+ 
+ export function Vectordot(v1:any,v2:any)
+ {
+     return (v1.x * v2.x + v1.y * v2.y);
+ }
+ 
+ export function VectorLenth(x:number,y:number)
+ {
+     return Math.sqrt(x * x + y * y);
+ }

+ 64 - 0
src/modules/editor/controllers/SelectCtrl/objects/observablePoint.ts

@@ -0,0 +1,64 @@
+import { any } from "vue-types";
+import { Point } from "./point";
+
+export class ObservablePoint {
+  cb: any;
+  scope: any;
+  _x = 0;
+  _y = 0;
+  constructor(cb:any, scope:any, x=0 , y=0) {
+    this._x = x;
+    this._y = y;
+
+    this.cb = cb;
+    this.scope = scope;
+  }
+  clone(cb = this.cb, scope = this.scope) {
+    return new ObservablePoint(cb, scope, this._x, this._y);
+  }
+  set(x = 0, y = x) {
+    if (this._x !== x || this._y !== y) {
+      this._x = x;
+      this._y = y;
+      this.cb.call(this.scope);
+    }
+  }
+  copyFrom(p: Point) {
+    if (this._x !== p.x || this._y !== p.y) {
+      this._x = p.x;
+      this._y = p.y;
+      this.cb.call(this.scope);
+    }
+    return this;
+  }
+  copyTo(p: Point) {
+    p.set(this._x, this._y);
+
+    return p;
+  }
+  equals(p: Point) {
+    return p.x === this._x && p.y === this._y;
+  }
+  get x() {
+    return this._x;
+  }
+  set x(
+    value // eslint-disable-line require-jsdoc
+  ) {
+    if (this._x !== value) {
+      this._x = value;
+      this.cb.call(this.scope);
+    }
+  }
+
+  get y() {
+    return this._y;
+  }
+
+  set y(value) {
+    if (this._y !== value) {
+      this._y = value;
+      this.cb.call(this.scope);
+    }
+  }
+}

+ 29 - 0
src/modules/editor/controllers/SelectCtrl/objects/point.ts

@@ -0,0 +1,29 @@
+export class Point {
+  x = 0;
+  y = 0;
+  constructor(x = 0, y = 0) {
+    this.x = x;
+    this.y = y;
+  }
+  clone() {
+    return new Point(this.x, this.y);
+  }
+  copyFrom(p: Point) {
+    this.set(p.x, p.y);
+
+    return this;
+  }
+  copyTo(p: Point) {
+    p.set(this.x, this.y);
+
+    return p;
+  }
+  equals(p: Point) {
+    return p.x === this.x && p.y === this.y;
+  }
+
+  set(x = 0, y = x) {
+    this.x = x;
+    this.y = y;
+  }
+}

+ 123 - 0
src/modules/editor/controllers/SelectCtrl/objects/rectangle.ts

@@ -0,0 +1,123 @@
+export class Rectangle {
+  x = 0;
+  y = 0;
+  width = 0;
+  height = 0;
+  constructor(x = 0, y = 0, width = 0, height = 0) {
+    this.x = Number(x);
+    this.y = Number(y);
+    this.width = Number(width);
+    this.height = Number(height);
+  }
+
+  get center() {
+    return { x: this.x + this.width / 2.0, y: this.y + this.height / 2.0 };
+  }
+
+  get left() {
+    return this.x;
+  }
+
+  get right() {
+    return this.x + this.width;
+  }
+
+  get top() {
+    return this.y;
+  }
+
+  get bottom() {
+    return this.y + this.height;
+  }
+
+  static get EMPTY() {
+    return new Rectangle(0, 0, 0, 0);
+  }
+
+  clone() {
+    return new Rectangle(this.x, this.y, this.width, this.height);
+  }
+
+  copyFrom(rectangle: Rectangle) {
+    this.x = rectangle.x;
+    this.y = rectangle.y;
+    this.width = rectangle.width;
+    this.height = rectangle.height;
+
+    return this;
+  }
+
+  copyTo(rectangle: Rectangle) {
+    rectangle.x = this.x;
+    rectangle.y = this.y;
+    rectangle.width = this.width;
+    rectangle.height = this.height;
+
+    return rectangle;
+  }
+
+  contains(x: number, y: number) {
+    if (this.width <= 0 || this.height <= 0) {
+      return false;
+    }
+
+    if (x >= this.x && x < this.x + this.width) {
+      if (y >= this.y && y < this.y + this.height) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  pad(paddingX = 0, paddingY = paddingX) {
+    this.x -= paddingX;
+    this.y -= paddingY;
+
+    this.width += paddingX * 2;
+    this.height += paddingY * 2;
+
+    return this;
+  }
+
+  fit(rectangle: Rectangle) {
+    const x1 = Math.max(this.x, rectangle.x);
+    const x2 = Math.min(this.x + this.width, rectangle.x + rectangle.width);
+    const y1 = Math.max(this.y, rectangle.y);
+    const y2 = Math.min(this.y + this.height, rectangle.y + rectangle.height);
+
+    this.x = x1;
+    this.width = Math.max(x2 - x1, 0);
+    this.y = y1;
+    this.height = Math.max(y2 - y1, 0);
+
+    return this;
+  }
+
+  ceil(resolution = 1, eps = 0.001) {
+    const x2 = Math.ceil((this.x + this.width - eps) * resolution) / resolution;
+    const y2 =
+      Math.ceil((this.y + this.height - eps) * resolution) / resolution;
+
+    this.x = Math.floor((this.x + eps) * resolution) / resolution;
+    this.y = Math.floor((this.y + eps) * resolution) / resolution;
+
+    this.width = x2 - this.x;
+    this.height = y2 - this.y;
+
+    return this;
+  }
+  enlarge(rectangle: Rectangle) {
+    const x1 = Math.min(this.x, rectangle.x);
+    const x2 = Math.max(this.x + this.width, rectangle.x + rectangle.width);
+    const y1 = Math.min(this.y, rectangle.y);
+    const y2 = Math.max(this.y + this.height, rectangle.y + rectangle.height);
+
+    this.x = x1;
+    this.width = x2 - x1;
+    this.y = y1;
+    this.height = y2 - y1;
+
+    return this;
+  }
+}

+ 155 - 0
src/modules/editor/controllers/SelectCtrl/objects/transform.ts

@@ -0,0 +1,155 @@
+import { ObservablePoint } from "./observablePoint";
+import { Matrix } from "../matrix";
+
+export class Transform {
+  static IDENTITY = new Transform();
+
+  worldTransform = new Matrix();
+
+  localTransform = new Matrix();
+
+  position: ObservablePoint = new ObservablePoint(this.onChange, this, 0, 0);
+
+  scale: ObservablePoint = new ObservablePoint(this.onChange, this, 1, 1);
+
+  pivot: ObservablePoint = new ObservablePoint(this.onChange, this, 0, 0);
+
+  skew: ObservablePoint = new ObservablePoint(this.updateSkew, this, 0, 0);
+
+  _rotation = 0;
+
+  // The X-coordinate value of the normalized local X axis,
+  //the first column of the local transformation matrix without a scale.
+  _cx = 1;
+
+  /**
+   * The Y-coordinate value of the normalized local X axis,
+   * the first column of the local transformation matrix without a scale.
+   */
+  _sx = 0;
+
+  /**
+   * The X-coordinate value of the normalized local Y axis,
+   * the second column of the local transformation matrix without a scale.
+   */
+  _cy = 0;
+
+  /**
+   * The Y-coordinate value of the normalized local Y axis,
+   * the second column of the local transformation matrix without a scale.
+   */
+  _sy = 1;
+
+  _localID = 0;
+
+  /**
+   * The locally unique ID of the local transform
+   * used to calculate the current local transformation matrix.
+   */
+  _currentLocalID = 0;
+
+  /**
+   * The locally unique ID of the world transform.
+   */
+  _worldID = 0;
+
+  /**
+   * The locally unique ID of the parent's world transform
+   * used to calculate the current world transformation matrix.
+   */
+  _parentID = 0;
+
+  onChange() {
+    this._localID++;
+  }
+
+  /**
+   * Called when the skew or the rotation changes.
+   */
+  updateSkew() {
+    this._cx = Math.cos(this._rotation + this.skew.y);
+    this._sx = Math.sin(this._rotation + this.skew.y);
+    this._cy = -Math.sin(this._rotation - this.skew.x); // cos, added PI/2
+    this._sy = Math.cos(this._rotation - this.skew.x); // sin, added PI/2
+    this._localID++;
+  }
+
+  /**
+   * Updates the local transformation matrix.
+   */
+  updateLocalTransform() {
+    const lt = this.localTransform;
+
+    if (this._localID !== this._currentLocalID) {
+      // get the matrix values of the displayobject based on its transform properties..
+      lt.a = this._cx * this.scale.x;
+      lt.b = this._sx * this.scale.x;
+      lt.c = this._cy * this.scale.y;
+      lt.d = this._sy * this.scale.y;
+
+      lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c);
+      lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d);
+      this._currentLocalID = this._localID;
+
+      // force an update..
+      this._parentID = -1;
+    }
+  }
+
+  /**
+   * Updates the local and the world transformation matrices.
+   */
+  updateTransform(parentTransform:Transform) {
+    const lt = this.localTransform;
+
+    if (this._localID !== this._currentLocalID) {
+      // get the matrix values of the displayobject based on its transform properties..
+      lt.a = this._cx * this.scale.x;
+      lt.b = this._sx * this.scale.x;
+      lt.c = this._cy * this.scale.y;
+      lt.d = this._sy * this.scale.y;
+
+      lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c);
+      lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d);
+      this._currentLocalID = this._localID;
+
+      // force an update..
+      this._parentID = -1;
+    }
+
+    if (this._parentID !== parentTransform._worldID) {
+      // concat the parent matrix with the objects transform.
+      const pt = parentTransform.worldTransform;
+      const wt = this.worldTransform;
+
+      wt.a = lt.a * pt.a + lt.b * pt.c;
+      wt.b = lt.a * pt.b + lt.b * pt.d;
+      wt.c = lt.c * pt.a + lt.d * pt.c;
+      wt.d = lt.c * pt.b + lt.d * pt.d;
+      wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx;
+      wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty;
+
+      this._parentID = parentTransform._worldID;
+
+      // update the id of the transform..
+      this._worldID++;
+    }
+  }
+
+  setFromMatrix(matrix:Matrix) {
+    matrix.decompose(this);
+    this._localID++;
+  }
+
+
+  get rotation() {
+    return this._rotation;
+  }
+
+  set rotation( value :number ) {
+    if (this._rotation !== value) {
+      this._rotation = value;
+      this.updateSkew();
+    }
+  }
+}

+ 21 - 0
src/modules/editor/controllers/SelectCtrl/objects/utils.ts

@@ -0,0 +1,21 @@
+export function removeItems(arr:any[], startIdx:number, removeCount:number)
+{
+    const length = arr.length;
+    let i;
+
+    if (startIdx >= length || removeCount === 0)
+    {
+        return;
+    }
+
+    removeCount = (startIdx + removeCount > length ? length - startIdx : removeCount);
+
+    const len = length - removeCount;
+
+    for (i = startIdx; i < len; ++i)
+    {
+        arr[i] = arr[i + removeCount];
+    }
+
+    arr.length = len;
+}

+ 71 - 51
src/modules/editor/controllers/TransferCtrl/Matrix.ts

@@ -6,6 +6,23 @@ export class Matrix {
   e = 0;
   f = 0;
 
+  clone() {
+     const out = new Matrix();
+     out.a = this.a;
+     out.b = this.b;
+     out.c = this.c;
+     out.d = this.d;
+     out.e = this.e;
+     out.f = this.f;
+     return out;
+  }
+
+  static createFromDiv(div: HTMLElement) {
+     const out = new Matrix();
+     out.setFormDiv(div);
+     return out;
+  }
+
   setFormDiv(div: HTMLElement) {
     let transformMatrix = window
       .getComputedStyle(div)
@@ -13,34 +30,37 @@ export class Matrix {
     if (!transformMatrix || transformMatrix === "none") {
       transformMatrix = "matrix(1,0,0,1,0,0)";
     }
+
+    
+    const pivot = window.getComputedStyle(div).transformOrigin
+    let px = 0;
+    let py = 0;
+    if (pivot) {
+      const flags = pivot.split(" ")
+      px = +flags[0].substring(0, flags[0].length-2)
+      py = +flags[1].substring(0, flags[1].length-2)
+    }
+
     const values = transformMatrix.split("(")[1].split(")")[0].split(",");
     this.a = +values[0];
     this.b = +values[1];
     this.c = +values[2];
     this.d = +values[3];
-    this.e = +values[4];
-    this.f = +values[5];
+
+    this.e = +values[4]; //- ((px * this.a) + (py * this.c));
+    this.f = +values[5]; //- ((px * this.b) + (py * this.d));
   }
 
   apply(div: HTMLElement) {
-    const b = this.b;
-    const a = this.a;
-    const angle = Math.round(Math.atan2(b, a) * (180 / Math.PI));
-
-    // 计算缩放比例
-    const scaleX = Math.sqrt(a * a + b * b);
-    // const scaleY = Math.sqrt(c*c + d*d);
-
-    // 计算平移距离
-    const translateX = this.e;
-    const translateY = this.f;
-    //console.log('angle:', angle, 'scaleX:', scaleX, 'scaleY:', scaleY, 'translateX:', translateX, 'translateY:', translateY)
-
-    div.style.transform = `translateX(${translateX}rem) translateY(${translateY}rem) rotate(${angle}deg) scale(${scaleX})`;
-
+    div.style.transform = this.getMatrixStr();
+    div.style.transformOrigin = "0 0";
     return this;
   }
-
+  
+  getMatrixStr() {
+    return `matrix(${this.a},${this.b},${this.c},${this.d},${this.e},${this.f})`
+  }
+  
   getX() {
     return this.e;
   }
@@ -88,35 +108,35 @@ export class Matrix {
   //   return this;
   // }
 
-  // /**
-  //  * Rotates current matrix accumulative by angle.
-  //  * @param {number} angle - angle in radians
-  //  */
-  // rotate(angle) {
-  //   var cos = Math.cos(angle),
-  //     sin = Math.sin(angle);
-  //   this.transform(cos, sin, -sin, cos, 0, 0);
-  //   return this;
-  // }
+  /**
+   * Rotates current matrix accumulative by angle.
+   * @param {number} angle - angle in radians
+   */
+  rotate(angle:number) {
+    const cos = Math.cos(angle),
+      sin = Math.sin(angle);
+    this.transform(cos, sin, -sin, cos, 0, 0);
+    return this;
+  }
 
-  // /**
-  //  * Helper method to make a rotation based on an angle in degrees.
-  //  * @param {number} angle - angle in degrees
-  //  */
-  // rotateDeg(angle) {
-  //   this.rotate(angle * 0.017453292519943295);
-  //   return this;
-  // }
+  /**
+   * Helper method to make a rotation based on an angle in degrees.
+   * @param {number} angle - angle in degrees
+   */
+  rotateDeg(angle:number) {
+    this.rotate(angle * 0.017453292519943295);
+    return this;
+  }
 
-  // /**
-  //  * Scales current matrix accumulative.
-  //  * @param {number} sx - scale factor x (1 does nothing)
-  //  * @param {number} sy - scale factor y (1 does nothing)
-  //  */
-  // scale(sx, sy) {
-  //   this.transform(sx, 0, 0, sy, 0, 0);
-  //   return this;
-  // }
+  /**
+   * Scales current matrix accumulative.
+   * @param {number} sx - scale factor x (1 does nothing)
+   * @param {number} sy - scale factor y (1 does nothing)
+   */
+  scale(sx:number, sy:number) {
+    this.transform(sx, 0, 0, sy, 0, 0);
+    return this;
+  }
 
   // /**
   //  * Scales current matrix on x axis accumulative.
@@ -314,12 +334,12 @@ export class Matrix {
   //  * @param {number} y - value for y
   //  * @returns {{x: number, y: number}} A new transformed point object
   //  */
-  // applyToPoint(x, y) {
-  //   return {
-  //     x: x * this.a + y * this.c + this.e,
-  //     y: x * this.b + y * this.d + this.f,
-  //   };
-  // }
+  applyToPoint(x:number, y:number) {
+    return {
+      x: x * this.a + y * this.c + this.e,
+      y: x * this.b + y * this.d + this.f,
+    };
+  }
 
   // /**
   //  * Apply current matrix to array with point objects or point pairs.

+ 48 - 42
src/modules/editor/module/actions/edit.ts

@@ -4,85 +4,87 @@ import { EditorModule } from "..";
 import { ScreenshotCtrl } from "../../controllers/ScreenshotCtrl";
 import { DesignComp } from "../../objects/DesignTemp/DesignComp";
 import { ICompKeys, Layout } from "../../typings";
+import { CompObject } from "../../controllers/SelectCtrl/compObj";
 
 export const editActions = EditorModule.action({
   // 通过拖拽添加组件到画布
   async dragCompToDesign(event: MouseEvent, compKey: ICompKeys) {
     await this.actions.addCompToDesign(compKey);
+
     const cardPoints = this.helper.getPointOffsetWith(
       event,
       this.store.currStreamCard.$el
     );
     const { currComp } = this.store;
-    currComp.translate(
-      375 - (currComp.layout.size?.[0] || 750) / 2,
-      this.helper.pxToDesignSize(cardPoints.y)
-    );
+    let selCtrl = this.controls.selectCtrl
+    selCtrl.translate(this.helper.designSizeToPx(375 - (currComp.layout.size?.[0] || 750) / 2), cardPoints.y);
+    this.helper.extendStreamCard(this.store.currStreamCardId);
+
   },
   // 通过点击添加组件到画布
   async clickCompToDesign(compKey: ICompKeys) {
+
+    const bound = this.helper.getCardCompBound(this.store.currCompId);
+
     await this.actions.addCompToDesign(compKey);
-    const { currStreamCard, currComp } = this.store;
+    const { currComp } = this.store;
 
-    const y = currStreamCard.getH();
-    currStreamCard.setH(y + currComp.getH());
-    currComp.translate(0, y);
+    //添加组件到当前选中的组件下面
+    const obj = new CompObject(currComp);
+    const selectCtrl = this.controls.selectCtrl;
+    selectCtrl.translate(0, bound.y + bound.h);
+
+    //扩展
+    this.helper.extendStreamCard(this.store.currStreamCardId);
   },
 
+  
+
   // 添加组件到画布
   async addCompToDesign(compKey: ICompKeys, index?: number) {
     if (!this.store.currStreamCardId) {
       //必须选中一个streamCard
       return;
     }
-
     if (compKey == "Container") {
       // index = this.store.streamCardIds.indexOf(this.store.currStreamCardId) + 1;
       const compId = await this.store.insertDesignContent(compKey, index);
       this.actions.pickComp(compId);
       return;
     }
-
-    // if (
-    //   index === undefined &&
-    //   this.store.currComp?.compKey === "Container"
-    //   // this.store.pageCompIds.includes(this.store.currComp.id)
-    // ) {
-
     const compId = await this.store.insertCompContainer(
       compKey,
       this.store.currStreamCard
     );
-    this.actions.pickComp(compId);
-    this.actions.setCompPosition(this.store.currComp);
-    // } else {
-    //   compId = await this.store.insertDesignContent(compKey, index);
-    //   this.actions.pickComp(compId);
-    // }
+    const addedComp = this.store.compMap[compId]
+    this.actions.setCompPositionFloat(addedComp);
+
+    this.controls.selectCtrl.selecteObjs([new CompObject(addedComp)])
   },
+
   // 切换当前组件
   pickComp(compId: string) {
     const { store, helper } = this;
     // 组合模式下,切换组件
-    if (store.currCompId && store.groupModeStatus) {
-      const enableGroupIds = helper
-        .findParentComp(compId)
-        ?.getChildIds() as string[];
-      const comps = helper.getCompTrees(compId);
-      while (comps.length) {
-        const comp = comps.pop() as DesignComp;
-        const index = store.groupIds.indexOf(comp.id);
-        if (index >= 0) {
-          const groupIds = [...store.groupIds];
-          groupIds.splice(index, 1);
-          store.setGroupIds(groupIds);
-        } else if (enableGroupIds.includes(comp.id)) {
-          store.groupIds.push(comp.id);
-          return;
-        }
-      }
-      return;
-    }
+    // if (store.currCompId && store.groupModeStatus) {
+    //   const enableGroupIds = helper
+    //     .findParentComp(compId)
+    //     ?.getChildIds() as string[];
+    //   const comps = helper.getCompTrees(compId);
+    //   while (comps.length) {
+    //     const comp = comps.pop() as DesignComp;
+    //     const index = store.groupIds.indexOf(comp.id);
+    //     if (index >= 0) {
+    //       const groupIds = [...store.groupIds];
+    //       groupIds.splice(index, 1);
+    //       store.setGroupIds(groupIds);
+    //     } else if (enableGroupIds.includes(comp.id)) {
+    //       store.groupIds.push(comp.id);
+    //       return;
+    //     }
+    //   }
+    //   return;
+    // }
     // let nextCompId = compId;
     // if (this.store.isEditPage) {
     //   const comps = this.helper.getCompTrees(compId);
@@ -91,7 +93,6 @@ export const editActions = EditorModule.action({
     if (this.store.currCompId == compId) {
       return;
     }
-
     this.store.setCurrComp(compId);
     if (this.store.currCompId == this.store.currStreamCardId) {
       this.controls.transferCtrl.destroy();
@@ -233,6 +234,11 @@ export const editActions = EditorModule.action({
     set(comp, path, value);
   },
 
+   // 设置组件浮动
+   setCompPositionFloat(comp: DesignComp) {
+    comp.layout.position = "absolute"
+  },
+
   // 设置组件浮动
   setCompPosition(comp: DesignComp) {
     comp.layout.position =

+ 2 - 2
src/modules/editor/module/actions/init.ts

@@ -29,8 +29,8 @@ export const initActions = EditorModule.action({
     this.store.setMode(v);
   },
 
-  onViewReady(pageEl, selEl) {
+  onViewReady(pageEl, selEl, viewPort) {
     this.store.currStreamCardId = this.store.streamCardIds[0];
-    this.controls.selectCtrl.initEvents(pageEl, selEl);
+    this.controls.selectCtrl.initEvents(pageEl, selEl, viewPort);
   },
 });

+ 46 - 0
src/modules/editor/module/helpers/index.ts

@@ -1,4 +1,5 @@
 import { EditorModule } from "..";
+import { CompObject } from "../../controllers/SelectCtrl/compObj";
 import { DesignComp } from "../../objects/DesignTemp/DesignComp";
 import { createCompStyle } from "../../objects/DesignTemp/creates/createCompStyle";
 import { Layout } from "../../typings";
@@ -10,6 +11,10 @@ export const helpers = EditorModule.helper({
   pxToDesignSize(value: number) {
     return value * 2;
   },
+  designSizeToPx(value: number) {
+    return value / 2.0;
+  },
+  
   findComp(compId: string) {
     const { compMap } = this.store.designData;
     const comp = compMap[compId];
@@ -32,6 +37,20 @@ export const helpers = EditorModule.helper({
     }
     return false;
   },
+  isStreamCardChild(compId:string) {
+    if (compId == "root" || this.helper.isStreamCard(compId) ) {
+      return false;
+    }
+    const cards = this.store.streamCardIds
+    let n = cards.length;
+    const compMap = this.store.designData.compMap;
+    while(n--) {
+       const childs = compMap[cards[n]].children.default || [];
+       if (childs.indexOf(compId) > -1 ) return true;
+    }
+    return false;
+  },
+
   findParentComp(compId: string): DesignComp | undefined {
     const comp = this.helper.findComp(compId);
     if (comp) return this.helper.findComp(this.store.compPids[compId]);
@@ -105,4 +124,31 @@ export const helpers = EditorModule.helper({
       y: e.clientY - domRect.top,
     };
   },
+
+  getCardCompBound(compId :string) {
+    const compMap = this.store.designData.compMap
+    const c = compMap[compId];
+    const obj = new CompObject(c);
+    const bound = obj.getBox();
+
+    return bound;
+  },
+
+  extendStreamCard(streamCardId: string) {
+    const compMap = this.store.designData.compMap;
+    const card = compMap[streamCardId];
+    const childs = card.children.default || [];
+    let maxH = 0,
+      n = childs.length;
+    while (n--) {
+      const c = childs[n];
+      const aabb = this.helper.getCardCompBound(c);
+      maxH = Math.max(maxH, aabb.y + aabb.h);
+    }
+    maxH = this.helper.pxToDesignSize(maxH) + 10;
+    const cardH = card.layout.size?.[1] as number;
+    if (cardH < maxH ) {
+      card.setH(maxH);
+    }
+  },
 });

+ 2 - 0
src/modules/editor/module/index.ts

@@ -15,6 +15,7 @@ import { https } from "./https";
 import { store } from "./stores";
 import { DragAddCtrl } from "../controllers/DragAddCtrl";
 import { SelectCtrl } from "../controllers/SelectCtrl";
+import { CompObject } from "../controllers/SelectCtrl/compObj";
 
 export class EditorModule extends ModuleRoot {
   config = this.setConfig({
@@ -46,6 +47,7 @@ export class EditorModule extends ModuleRoot {
     compUICtrl: new CompUICtrl(this),
     selectCtrl: new SelectCtrl(this),
   };
+  compObjsMap = new Map<string, CompObject>();
 
   onReady() {
     this.actions.init();

+ 4 - 1
src/modules/editor/module/stores/index.ts

@@ -28,7 +28,10 @@ export const store = EditorModule.store({
     isPreview(state) {
       return state.mode === "preview";
     },
-
+    compMap(state) {
+      return state.designData.compMap;
+    },
+    
     currComp(state) {
       return state.designData.compMap[state.currCompId];
     },

+ 11 - 0
src/modules/editor/objects/DesignTemp/DesignComp.ts

@@ -56,6 +56,17 @@ export class DesignComp {
   getH() {
     return this.layout.size?.[1] || 0;
   }
+  getBoundRect() {
+    const out = {x:0, y: 0, w: 0, h:0}
+    if (!this.$el) return out;
+    const r= this.$el.getBoundingClientRect();
+    out.w = r.width;
+    out.h = r.height;
+    out.x = r.left;
+    out.y = r.top;
+    return out;
+ }
+
   setH(height: number) {
     if (!this.layout.size) this.layout.size = [];
     this.layout.size[1] = height;

+ 21 - 6
src/modules/editor/objects/DesignTemp/creates/createCompStyle.ts

@@ -1,6 +1,7 @@
 import { EditorModule } from "@/modules/editor/module";
 import { Layout } from "@/modules/editor/typings";
 import { compMasks } from "./CompMasks";
+import { Matrix } from "@/modules/editor/controllers/TransferCtrl/Matrix";
 
 export function createCompStyle(module: EditorModule, layout: Layout) {
   const { designToNaturalSize } = module.helper;
@@ -55,18 +56,32 @@ export function createCompStyle(module: EditorModule, layout: Layout) {
     style.position = layout.position;
   }
 
-  const styleTransform = parseTransform(transform);
-
-  if (styleTransform) {
-    style.transform = styleTransform;
+  if (layout.transformMatrix) {
+    style.transform = layout.transformMatrix;
+  } else {
+    //转换成matrix形式
+    const m = new Matrix();
+    const transform = layout.transform;
+    if (transform) {
+      m.translate(transform.x || 0, transform.y || 0);
+      if (transform.s != undefined) {
+        m.scale(transform.s, transform.s);
+      }
+      if (transform.r != undefined) {
+        m.rotateDeg(transform.r);
+      }
+    }
+    style.transform = m.getMatrixStr();
+    // const s = 
+    // style.transform = parseTransform(transform);
   }
-
+  style.transformOrigin = "0 0";
+  
   if (layout.background) {
     if (layout.background.color) {
       style.backgroundColor = layout.background.color;
     }
   }
-
   return style;
 }
 

+ 2 - 1
src/modules/editor/typings.ts

@@ -19,7 +19,8 @@ export type Layout = {
   zIndex?: number;
   margin?: string;
   padding?: string;
-
+  transformMatrix?: string;
+  
   background?: Background;
 };