gizemo.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. import { Bounds } from './objects/bounds';
  2. import { Rectangle } from './objects/rectangle';
  3. import { Container } from './objects/container';
  4. import { CompObject } from './compObj';
  5. import { Matrix } from './matrix';
  6. import { Events } from "queenjs"
  7. import { RxValue } from '../ReactCtrl/rxValue';
  8. import { DesignComp } from '../../objects/DesignTemp/DesignComp';
  9. import { Transform } from './objects/transform';
  10. import { history } from '../../objects/DesignTemp/factory';
  11. import { designSizeToPx, pxToDesignSize } from '../../module/utils';
  12. type FnGetCompObj = (id:string)=>DesignComp;
  13. const AngleNames = [
  14. "scaleBottomright",
  15. "scaleBottomleft",
  16. "scaleTopleft",
  17. "scaleTopright",
  18. ];
  19. const EdgeNames = ["scaleright", "scaleleft", "scalebottom", "scaletop"];
  20. export class Gizemo extends Events {
  21. aabb = new Bounds();
  22. tempBound = new Bounds();
  23. parent = new Container();
  24. rect = new Rectangle();
  25. pivotIndex = 0;
  26. selected:CompObject[] = [];
  27. history = history;
  28. state = RxValue.create({
  29. transform: {
  30. selected: [] as string[],
  31. x: 0,
  32. y: 0,
  33. w: 0,
  34. h: 0,
  35. sx: 1,
  36. sy: 1,
  37. px: 0,
  38. py: 0,
  39. r: 0,
  40. },
  41. lastId: "",
  42. scaleWidth: 1,
  43. scaleHeight: 1,
  44. mouse: ""
  45. }, this.history)
  46. lastSelChanged = false;
  47. getCompObj: FnGetCompObj;
  48. constructor(getCompObj: FnGetCompObj) {
  49. super()
  50. this.getCompObj = getCompObj;
  51. this.parent.sortableChildren = false;
  52. this.initGizmoEvent();
  53. }
  54. get selectedIds() {
  55. return this.selected.map(item=>item.comp.id);
  56. }
  57. getBound() {
  58. //加上parent的旋转和平移
  59. this.tempBound.clear();
  60. this.tempBound.addFrame(this.parent.transform, 0, 0, this.width, this.height);
  61. return this.tempBound.getRectangle();
  62. }
  63. get width() {
  64. return this.rect.width
  65. }
  66. get height() {
  67. return this.rect.height;
  68. }
  69. childSubs = [] as any[];
  70. addChildWorldNoChange(child: CompObject) {
  71. const m = this.parent.worldTransform.clone();
  72. m.invert();
  73. const wm = child.worldTransform.clone();
  74. wm.prepend(m);
  75. child.setMatrix(wm);
  76. this.parent.addChild(child);
  77. child.updateTransform();
  78. }
  79. initGizmoEvent() {
  80. this.state.onTransformChanged((t, ot)=>{
  81. if (t.selected != ot.selected) {
  82. this.destroy();
  83. this.rect.width = t.w;
  84. this.rect.height = t.h;
  85. this.parent.width = t.w;
  86. this.parent.height = t.h;
  87. this.parent.setTransform(t.x, t.y, t.sx, t.sy, t.r, 0, 0, t.px, t.py)
  88. this.parent.updateTransform();
  89. const objs :any= [];
  90. t.selected.forEach((oid) => {
  91. const obj = new CompObject(this.getCompObj(oid), true)
  92. let box = obj.calculateBounds();
  93. this.aabb.addBounds(box);
  94. objs.push(obj);
  95. this.addChildWorldNoChange(obj);
  96. let first = true;
  97. const s= obj.state.onTransformChanged(()=>{
  98. if (!first) this.updateSize();
  99. first = false;
  100. })
  101. this.childSubs.push(s);
  102. });
  103. this.selected = objs;
  104. this.parent.updateTransform();
  105. this.updateCompState();
  106. this.emit("change");
  107. return;
  108. }
  109. if (t.selected.length < 1) {
  110. this.destroy();
  111. this.emit("change");
  112. return;
  113. }
  114. //只是移动操作
  115. this.parent.x = t.x;
  116. this.parent.y = t.y;
  117. this.rect.width = t.w;
  118. this.rect.height = t.h;
  119. this.parent.scale.x = t.sx;
  120. this.parent.scale.y = t.sy;
  121. this.parent.rotation = t.r;
  122. this.parent.pivot.x = t.px;
  123. this.parent.pivot.y = t.py;
  124. this.parent._boundsID++;
  125. this.parent.updateTransform();
  126. console.log("mouse=>", this.state.mouse);
  127. this.updateCompState();
  128. this.emit("change");
  129. })
  130. }
  131. initScaleSizeEvent() {
  132. this.state.onScaleWidthChanged((x)=>{
  133. const prew = this.parent.scale.x * this.rect.width;
  134. //怎么改对应的偏移值
  135. this.parent.scale.x = x
  136. const w = this.parent.scale.x * this.rect.width;
  137. this.parent.updateTransform();
  138. this.applyChildWidth({scaleX: w / prew })
  139. this.updateCompState();
  140. this.emit("change");
  141. })
  142. }
  143. selectObjs(ids: string[], force=false) {
  144. let noChange = false;
  145. if (!force && this.selected.length == ids.length ) {
  146. if (ids.length == 0) {
  147. noChange = true;
  148. } else if (ids.length == 1) {
  149. if (ids[0] == this.selected[0].comp.id) {
  150. noChange = true;
  151. }
  152. } else {
  153. const id1 = ids.sort().join("");
  154. const id2 = this.selected.map(item=>item.comp.id).sort().join("");
  155. if (id1 == id2) {
  156. noChange = true;
  157. }
  158. }
  159. }
  160. this.lastSelChanged = !noChange;
  161. console.log("changing....", this.lastSelChanged);
  162. if (noChange) {
  163. return;
  164. }
  165. let n = ids.length;
  166. const selected = ids;
  167. let w = 0, h = 0, x = 0, y = 0, sx = 1 , sy = 1, r = 0, px=0, py=0;
  168. if (n == 1) { //单对象
  169. let obj = new CompObject(this.getCompObj(selected[0]));
  170. w = obj.width;
  171. h = obj.height;
  172. const transform = new Transform();
  173. transform.setFromMatrix(obj.worldTransform);
  174. x = transform.position.x;
  175. y = transform.position.y;
  176. sx = transform.scale.x;
  177. sy = transform.scale.y;
  178. r = transform.rotation;
  179. } else if (n>1) {
  180. const bound = new Bounds();
  181. while (n--) {
  182. let obj = new CompObject(this.getCompObj(selected[n]) );
  183. let box = obj.calculateBounds();
  184. bound.addBounds(box);
  185. }
  186. //构建当前对象的转换矩阵
  187. let rect = new Rectangle();
  188. bound.getRectangle(rect);
  189. this.rect = rect;
  190. w = rect.width;
  191. h = rect.height;
  192. x = rect.x;
  193. y = rect.y;
  194. }
  195. this.state.setTransform({selected: ids, x, y, w, h, sx,sy, r, px, py})
  196. }
  197. testClick(sx:number,sy:number)
  198. {
  199. let w = this.width;
  200. let h = this.height;
  201. let local = {x:0,y:0} as any;
  202. this.parent.worldTransform.applyInverse({x:sx,y:sy} as any, local);
  203. if( local.x < 0 || local.x > w ) return false;
  204. if( local.y < 0 || local.y > h ) return false;
  205. return true;
  206. }
  207. rotate(r:number, submit = false) {
  208. const t = {...this.state.transform};
  209. t.r = r;
  210. this.state.setTransform(t);
  211. if (submit) this.history.submit();
  212. }
  213. //index
  214. // 0 --5-- 1
  215. // | |
  216. // 8 4 6
  217. // | |
  218. // 3 --7---2
  219. setPivot(index:number) {
  220. let rect = this.rect;
  221. let pivots = [ { x: 0, y: 0 }, { x: rect.width, y: 0 },
  222. { x: rect.width, y: rect.height },
  223. { x: 0, y: rect.height },
  224. { x: rect.width / 2.0, y: rect.height / 2.0 },
  225. { x: rect.width / 2.0, y: 0 },
  226. { x: rect.width , y: rect.height / 2.0 },
  227. { x: rect.width / 2.0 , y: rect.height},
  228. { x: 0, y: rect.height / 2.0},
  229. ];
  230. let targetPivot = pivots[index];
  231. let point = { x: targetPivot.x, y: targetPivot.y } as any;
  232. this.parent.worldTransform.apply(point, point);
  233. const t = {...this.state.transform};
  234. t.px = targetPivot.x;
  235. t.py = targetPivot.y;
  236. t.x = point.x;
  237. t.y = point.y;
  238. this.pivotIndex = index;
  239. this.state.setTransform(t);
  240. return { x: point.x, y: point.y};
  241. }
  242. setPivot2(x:number, y:number) {
  243. let targetPivot = {x, y}
  244. let point = { x, y } as any;
  245. this.parent.worldTransform.apply(point, point);
  246. const t = {...this.state.transform};
  247. t.px = targetPivot.x;
  248. t.py = targetPivot.y;
  249. t.x = point.x;
  250. t.y = point.y;
  251. this.state.setTransform(t);
  252. return { x: point.x, y: point.y};
  253. }
  254. getPivotXY(index:number) {
  255. let rect = this.rect;
  256. let pivots = [ { x: 0, y: 0 }, { x: rect.width, y: 0 },
  257. { x: rect.width, y: rect.height },
  258. { x: 0, y: rect.height },
  259. { x: rect.width / 2.0, y: rect.height / 2.0 },
  260. { x: rect.width / 2.0, y: 0 },
  261. { x: rect.width , y: rect.height / 2.0 },
  262. { x: rect.width / 2.0 , y: rect.height},
  263. { x: 0, y: rect.height / 2.0},
  264. ];
  265. let targetPivot = pivots[index];
  266. let point = { x: targetPivot.x, y: targetPivot.y } as any;
  267. this.parent.worldTransform.apply(point, point);
  268. let yIndex = 0, xIndex = 0;
  269. if (index == 3) {
  270. yIndex = 0;
  271. xIndex = 2;
  272. } else if (index == 0) {
  273. yIndex = 3;
  274. xIndex = 1;
  275. } else if (index == 1) {
  276. yIndex = 2;
  277. xIndex = 0;
  278. } else if (index == 2) {
  279. yIndex = 1;
  280. xIndex = 3;
  281. }
  282. let pointY = pivots[yIndex];
  283. let pY = { x: pointY.x, y: pointY.y } as any;
  284. this.parent.worldTransform.apply(pY, pY);
  285. let pointX = pivots[xIndex];
  286. let pX = { x: pointX.x, y: pointX.y } as any;
  287. this.parent.worldTransform.apply(pX, pX);
  288. let xVec = { x: (pX.x - point.x), y: (pX.y - point.y) };
  289. let yVec = { x: (pY.x - point.x), y: (pY.y - point.y) };
  290. return { x: xVec, y: yVec };
  291. }
  292. scale(x:number, y:number, submit = false) {
  293. const t = {...this.state.transform};
  294. t.sx = x;
  295. t.sy = y;
  296. this.state.setTransform(t);
  297. if (submit) this.history.submit();
  298. }
  299. scaleX(x:number, submit= false) {
  300. if ( (this.state.mouse == "scaleright" || this.state.mouse == "scaleleft") && this.selected.length == 1 ) {
  301. const child = this.selected[0];
  302. if (child.comp.compKey =='Text'){
  303. const element: HTMLElement | null = document.querySelector(`#editor_${child.comp.id}`);
  304. if (!element) {
  305. return;
  306. }
  307. child.height = element.clientHeight;
  308. }
  309. }
  310. const t = {...this.state.transform};
  311. t.sx = x;
  312. this.state.setTransform(t);
  313. if (submit) this.history.submit();
  314. }
  315. scaleY(y:number, submit= false) {
  316. const t = {...this.state.transform};
  317. t.sy = y;
  318. this.state.setTransform(t);
  319. if (submit) this.history.submit();
  320. }
  321. applyChildWidth(option:{scaleX?:number, scaleY?:number}) {
  322. if (this.selected.length < 1) return;
  323. const obj = this.selected[0];
  324. //先移除
  325. this.parent.removeChildWorldNoChange(obj);
  326. const m = new Matrix();
  327. m.scale(option.scaleX ? option.scaleX : 1, option.scaleY? option.scaleY : 1)
  328. m.invert();
  329. m.prepend(obj.worldTransform)
  330. obj.transform.setFromMatrix(m)
  331. if (option.scaleX) {
  332. obj.width = option.scaleX * obj.width;
  333. }
  334. if (option.scaleY) {
  335. obj.height = option.scaleY * obj.height;
  336. }
  337. obj.updateTransform();
  338. this.parent.addChildWorldNoChange(obj);
  339. }
  340. scaleSize(x:number, y:number) {
  341. let preW = this.parent.scale.x * this.rect.width;
  342. let preH = this.parent.scale.y * this.rect.height;
  343. this.parent.scale.y = y
  344. this.parent.scale.x = x
  345. const Width = this.parent.scale.x * this.rect.width;
  346. const Height = this.parent.scale.y * this.rect.height;
  347. this.parent.updateTransform();
  348. this.applyChildWidth({scaleX: Width / preW, scaleY: Height/preH})
  349. this.updateCompState();
  350. }
  351. scaleWidth(x:number) {
  352. this.state.setScaleWidth(x);
  353. }
  354. scaleHeight(y:number) {
  355. const preH = this.parent.scale.y * this.rect.height;
  356. this.parent.scale.y = y
  357. const h = this.parent.scale.y * this.rect.height;
  358. this.parent.updateTransform();
  359. this.applyChildWidth({scaleY: h / preH});
  360. this.updateCompState();
  361. }
  362. translate(xOffset:number, yOffset:number, submit=false) {
  363. const t = {...this.state.transform};
  364. t.x += xOffset;
  365. t.y += yOffset;
  366. this.state.setTransform(t);
  367. if (submit) this.history.submit();
  368. }
  369. destroy() {//选中的对象坐标转到画布空间坐标
  370. let selected = this.selected;
  371. let n = selected.length;
  372. while (n--) {
  373. let child = selected[n];
  374. this.parent.removeChildWorldNoChange(child)
  375. child.comp.layout.transformMatrix = child.worldTransform.getMatrixStr();
  376. }
  377. this.selected = [];
  378. this.parent.removeChildren(0, this.parent.children.length);
  379. //获取选择对象的aabb(画布空间)
  380. this.aabb.clear();
  381. this.childSubs.forEach(s=>{
  382. s.unsubscribe();
  383. })
  384. this.childSubs = [];
  385. }
  386. updateCompState() {
  387. let n = this.selected.length;
  388. while (n--) {
  389. let child = this.selected[n];
  390. if (child.comp) child.comp.layout.transformMatrix = child.worldTransform.getMatrixStr();
  391. }
  392. }
  393. reSelectCurrElemnts() {
  394. let t = {...this.state.transform}
  395. t.selected = this.state.transform.selected.slice(0)
  396. this.state.setTransform(t);
  397. }
  398. updateSize() {
  399. let selected = this.selected;
  400. let n = selected.length;
  401. this.setPivot(0)
  402. this.aabb.clear();
  403. this.selected.forEach((obj) => {
  404. let box = obj.calculateBounds();
  405. this.aabb.addBounds(box);
  406. });
  407. this.aabb.getRectangle(this.rect);
  408. let x = this.rect.x, y = this.rect.y, sx = 1, sy= 1;
  409. if ( n == 1) {
  410. const s = selected[0].worldTransform.getScale();
  411. sx = s.x;
  412. sy = s.y;
  413. }
  414. const r = n == 1 ?selected[0].rotation : 0;
  415. this.parent.setTransform(x, y, sx, sy, r, 0, 0, 0, 0);
  416. this.parent.updateTransform();
  417. const w = n == 1 ? selected[0].width : this.rect.width;
  418. const h = n == 1 ? selected[0].height : this.rect.height;
  419. this.rect.width = w;
  420. this.rect.height = h;
  421. this.state.transform.w = w
  422. this.state.transform.h = h;
  423. this.state.transform.x = x;
  424. this.state.transform.y = y;
  425. this.state.transform.sx = sx;
  426. this.state.transform.sy = sy;
  427. this.state.transform.r = r;
  428. this.state.transform.px = 0;
  429. this.state.transform.py = 0;
  430. this.emit("change");
  431. }
  432. }