@witchcraft/layout 0.1.3 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/README.md +27 -24
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/components/FrameDragHandle.d.vue.ts +15 -0
  4. package/dist/runtime/components/FrameDragHandle.vue +28 -0
  5. package/dist/runtime/components/FrameDragHandle.vue.d.ts +15 -0
  6. package/dist/runtime/components/LayoutDecos.d.vue.ts +2 -4
  7. package/dist/runtime/components/LayoutDecos.vue +10 -29
  8. package/dist/runtime/components/LayoutDecos.vue.d.ts +2 -4
  9. package/dist/runtime/components/LayoutEdges.d.vue.ts +3 -3
  10. package/dist/runtime/components/LayoutEdges.vue +8 -8
  11. package/dist/runtime/components/LayoutEdges.vue.d.ts +3 -3
  12. package/dist/runtime/components/LayoutFrame.d.vue.ts +1 -1
  13. package/dist/runtime/components/LayoutFrame.vue +0 -1
  14. package/dist/runtime/components/LayoutFrame.vue.d.ts +1 -1
  15. package/dist/runtime/components/LayoutShapeSquare.d.vue.ts +3 -1
  16. package/dist/runtime/components/LayoutShapeSquare.vue.d.ts +3 -1
  17. package/dist/runtime/components/LayoutWindow.d.vue.ts +26 -12
  18. package/dist/runtime/components/LayoutWindow.vue +95 -84
  19. package/dist/runtime/components/LayoutWindow.vue.d.ts +26 -12
  20. package/dist/runtime/composables/useFrames.d.ts +15 -13
  21. package/dist/runtime/composables/useFrames.js +59 -39
  22. package/dist/runtime/demo/App.vue +116 -30
  23. package/dist/runtime/demo/DemoControls.d.vue.ts +4 -1
  24. package/dist/runtime/demo/DemoControls.vue +98 -4
  25. package/dist/runtime/demo/DemoControls.vue.d.ts +4 -1
  26. package/dist/runtime/drag/CloseAction.d.ts +26 -5
  27. package/dist/runtime/drag/CloseAction.js +87 -40
  28. package/dist/runtime/drag/DragActionHandler.d.ts +20 -8
  29. package/dist/runtime/drag/DragActionHandler.js +47 -12
  30. package/dist/runtime/drag/FrameDragAction.d.ts +45 -0
  31. package/dist/runtime/drag/FrameDragAction.js +143 -0
  32. package/dist/runtime/drag/SplitAction.d.ts +32 -11
  33. package/dist/runtime/drag/SplitAction.js +82 -24
  34. package/dist/runtime/drag/createDefaultHandlers.d.ts +9 -0
  35. package/dist/runtime/drag/createDefaultHandlers.js +10 -0
  36. package/dist/runtime/drag/defaultDragActions.d.ts +9 -0
  37. package/dist/runtime/drag/defaultDragActions.js +10 -0
  38. package/dist/runtime/drag/types.d.ts +82 -13
  39. package/dist/runtime/drag/types.js +1 -0
  40. package/dist/runtime/helpers/createZoneSideClipPath.d.ts +12 -0
  41. package/dist/runtime/helpers/createZoneSideClipPath.js +17 -0
  42. package/dist/runtime/helpers/doEdgesOverlap.d.ts +3 -1
  43. package/dist/runtime/helpers/doEdgesOverlap.js +5 -5
  44. package/dist/runtime/helpers/getDockBoundaries.d.ts +19 -0
  45. package/dist/runtime/helpers/getDockBoundaries.js +14 -0
  46. package/dist/runtime/helpers/getEdgeLength.d.ts +2 -0
  47. package/dist/runtime/helpers/getEdgeLength.js +5 -0
  48. package/dist/runtime/helpers/getIntersections.js +2 -2
  49. package/dist/runtime/helpers/getIntersectionsCss.js +2 -2
  50. package/dist/runtime/helpers/getMoveEdgeInfo.js +2 -2
  51. package/dist/runtime/helpers/getResizeLimit.js +2 -2
  52. package/dist/runtime/helpers/getShapeSquareCss.js +2 -2
  53. package/dist/runtime/helpers/getVisualEdgeCss.js +2 -2
  54. package/dist/runtime/helpers/getVisualEdges.d.ts +1 -1
  55. package/dist/runtime/helpers/getVisualEdges.js +4 -3
  56. package/dist/runtime/helpers/index.d.ts +4 -0
  57. package/dist/runtime/helpers/index.js +4 -0
  58. package/dist/runtime/helpers/isEdgeEqual.js +2 -4
  59. package/dist/runtime/helpers/isWindowEdge.js +2 -2
  60. package/dist/runtime/helpers/isWindowEdgePoint.js +2 -2
  61. package/dist/runtime/helpers/moveEdge.js +2 -2
  62. package/dist/runtime/helpers/numberToScaledPercent.d.ts +1 -1
  63. package/dist/runtime/helpers/numberToScaledPercent.js +2 -2
  64. package/dist/runtime/helpers/numberToScaledSize.js +2 -2
  65. package/dist/runtime/helpers/rotateFrames.d.ts +7 -0
  66. package/dist/runtime/helpers/rotateFrames.js +36 -0
  67. package/dist/runtime/helpers/scaledPointToPx.d.ts +13 -0
  68. package/dist/runtime/helpers/scaledPointToPx.js +7 -0
  69. package/dist/runtime/helpers/toWindowCoord.js +2 -2
  70. package/dist/runtime/layout/applyFrameChanges.d.ts +10 -0
  71. package/dist/runtime/layout/applyFrameChanges.js +29 -0
  72. package/dist/runtime/layout/createSplitDecoFromDrag.d.ts +6 -1
  73. package/dist/runtime/layout/createSplitDecoFromDrag.js +4 -4
  74. package/dist/runtime/layout/createSplitDecoShapes.d.ts +7 -0
  75. package/dist/runtime/layout/{createSplitDecoEdge.js → createSplitDecoShapes.js} +6 -3
  76. package/dist/runtime/layout/debugFrame.js +2 -1
  77. package/dist/runtime/layout/findSafeSplitEdge.js +2 -2
  78. package/dist/runtime/layout/frameCreate.js +2 -2
  79. package/dist/runtime/layout/getCloseFrameInfo.d.ts +7 -6
  80. package/dist/runtime/layout/getCloseFrameInfo.js +10 -3
  81. package/dist/runtime/layout/getDragZones.d.ts +8 -0
  82. package/dist/runtime/layout/getDragZones.js +32 -0
  83. package/dist/runtime/layout/getFillEmptySpaceInfo.d.ts +65 -0
  84. package/dist/runtime/layout/getFillEmptySpaceInfo.js +69 -0
  85. package/dist/runtime/layout/getFrameCollapseInfo.d.ts +13 -0
  86. package/dist/runtime/layout/getFrameCollapseInfo.js +93 -0
  87. package/dist/runtime/layout/getFrameDockInfo.d.ts +9 -0
  88. package/dist/runtime/layout/getFrameDockInfo.js +82 -0
  89. package/dist/runtime/layout/getFrameDragZones.d.ts +16 -0
  90. package/dist/runtime/layout/getFrameDragZones.js +74 -0
  91. package/dist/runtime/layout/getFrameRearrangeInfo.d.ts +139 -0
  92. package/dist/runtime/layout/getFrameRearrangeInfo.js +87 -0
  93. package/dist/runtime/layout/getFrameSplitInfo.d.ts +7 -5
  94. package/dist/runtime/layout/getFrameSplitInfo.js +10 -3
  95. package/dist/runtime/layout/getFrameSwapInfo.d.ts +9 -0
  96. package/dist/runtime/layout/getFrameSwapInfo.js +27 -0
  97. package/dist/runtime/layout/getFrameTo.js +2 -2
  98. package/dist/runtime/layout/getFrameUncollapseInfo.d.ts +12 -0
  99. package/dist/runtime/layout/getFrameUncollapseInfo.js +88 -0
  100. package/dist/runtime/layout/getFrameUndockInfo.d.ts +13 -0
  101. package/dist/runtime/layout/getFrameUndockInfo.js +51 -0
  102. package/dist/runtime/layout/getFramesRedistributeInfo.d.ts +29 -0
  103. package/dist/runtime/layout/getFramesRedistributeInfo.js +53 -0
  104. package/dist/runtime/layout/getWindowDragZones.d.ts +6 -0
  105. package/dist/runtime/layout/getWindowDragZones.js +49 -0
  106. package/dist/runtime/layout/index.d.ts +14 -5
  107. package/dist/runtime/layout/index.js +14 -5
  108. package/dist/runtime/layout/isPointInRect.d.ts +7 -0
  109. package/dist/runtime/layout/{isPointInFrame.js → isPointInRect.js} +1 -1
  110. package/dist/runtime/layout/resizeFrame.js +2 -2
  111. package/dist/runtime/settings.d.ts +41 -16
  112. package/dist/runtime/settings.js +95 -53
  113. package/dist/runtime/types/index.d.ts +324 -54
  114. package/dist/runtime/types/index.js +54 -20
  115. package/package.json +28 -29
  116. package/src/runtime/components/FrameDragHandle.vue +30 -0
  117. package/src/runtime/components/LayoutDecos.vue +12 -36
  118. package/src/runtime/components/LayoutEdges.vue +27 -23
  119. package/src/runtime/components/LayoutFrame.vue +6 -5
  120. package/src/runtime/components/LayoutShapeSquare.vue +9 -3
  121. package/src/runtime/components/LayoutWindow.vue +110 -101
  122. package/src/runtime/composables/useFrames.ts +80 -50
  123. package/src/runtime/demo/App.vue +126 -36
  124. package/src/runtime/demo/DemoControls.vue +115 -6
  125. package/src/runtime/drag/CloseAction.ts +106 -44
  126. package/src/runtime/drag/DragActionHandler.ts +71 -20
  127. package/src/runtime/drag/FrameDragAction.ts +202 -0
  128. package/src/runtime/drag/SplitAction.ts +106 -34
  129. package/src/runtime/drag/createDefaultHandlers.ts +19 -0
  130. package/src/runtime/drag/defaultDragActions.ts +19 -0
  131. package/src/runtime/drag/types.ts +90 -20
  132. package/src/runtime/helpers/createZoneSideClipPath.ts +41 -0
  133. package/src/runtime/helpers/doEdgesOverlap.ts +11 -5
  134. package/src/runtime/helpers/getDockBoundaries.ts +36 -0
  135. package/src/runtime/helpers/getEdgeLength.ts +10 -0
  136. package/src/runtime/helpers/getIntersections.ts +2 -2
  137. package/src/runtime/helpers/getIntersectionsCss.ts +2 -2
  138. package/src/runtime/helpers/getMoveEdgeInfo.ts +2 -2
  139. package/src/runtime/helpers/getResizeLimit.ts +2 -2
  140. package/src/runtime/helpers/getShapeSquareCss.ts +2 -2
  141. package/src/runtime/helpers/getVisualEdgeCss.ts +2 -2
  142. package/src/runtime/helpers/getVisualEdges.ts +5 -4
  143. package/src/runtime/helpers/index.ts +4 -0
  144. package/src/runtime/helpers/isEdgeEqual.ts +2 -4
  145. package/src/runtime/helpers/isWindowEdge.ts +2 -2
  146. package/src/runtime/helpers/isWindowEdgePoint.ts +2 -2
  147. package/src/runtime/helpers/moveEdge.ts +2 -2
  148. package/src/runtime/helpers/numberToScaledPercent.ts +3 -3
  149. package/src/runtime/helpers/numberToScaledSize.ts +2 -2
  150. package/src/runtime/helpers/rotateFrames.ts +45 -0
  151. package/src/runtime/helpers/scaledPointToPx.ts +13 -0
  152. package/src/runtime/helpers/toWindowCoord.ts +2 -2
  153. package/src/runtime/layout/applyFrameChanges.ts +39 -0
  154. package/src/runtime/layout/createSplitDecoFromDrag.ts +12 -6
  155. package/src/runtime/layout/{createSplitDecoEdge.ts → createSplitDecoShapes.ts} +17 -7
  156. package/src/runtime/layout/debugFrame.ts +1 -1
  157. package/src/runtime/layout/findSafeSplitEdge.ts +3 -3
  158. package/src/runtime/layout/frameCreate.ts +2 -2
  159. package/src/runtime/layout/getCloseFrameInfo.ts +21 -8
  160. package/src/runtime/layout/getDragZones.ts +48 -0
  161. package/src/runtime/layout/getFillEmptySpaceInfo.ts +177 -0
  162. package/src/runtime/layout/getFrameCollapseInfo.ts +164 -0
  163. package/src/runtime/layout/getFrameDockInfo.ts +126 -0
  164. package/src/runtime/layout/getFrameDragZones.ts +100 -0
  165. package/src/runtime/layout/getFrameRearrangeInfo.ts +261 -0
  166. package/src/runtime/layout/getFrameSplitInfo.ts +21 -8
  167. package/src/runtime/layout/getFrameSwapInfo.ts +45 -0
  168. package/src/runtime/layout/getFrameTo.ts +2 -2
  169. package/src/runtime/layout/getFrameUncollapseInfo.ts +160 -0
  170. package/src/runtime/layout/getFrameUndockInfo.ts +97 -0
  171. package/src/runtime/layout/getFramesRedistributeInfo.ts +98 -0
  172. package/src/runtime/layout/getWindowDragZones.ts +59 -0
  173. package/src/runtime/layout/index.ts +14 -5
  174. package/src/runtime/layout/isPointInRect.ts +7 -0
  175. package/src/runtime/layout/resizeFrame.ts +2 -2
  176. package/src/runtime/settings.ts +69 -49
  177. package/src/runtime/types/index.ts +143 -28
  178. package/dist/runtime/layout/closeFrame.d.ts +0 -5
  179. package/dist/runtime/layout/closeFrame.js +0 -13
  180. package/dist/runtime/layout/closeFrames.d.ts +0 -2
  181. package/dist/runtime/layout/closeFrames.js +0 -8
  182. package/dist/runtime/layout/createSplitDecoEdge.d.ts +0 -2
  183. package/dist/runtime/layout/frameSplit.d.ts +0 -16
  184. package/dist/runtime/layout/frameSplit.js +0 -9
  185. package/dist/runtime/layout/isPointInFrame.d.ts +0 -2
  186. package/src/runtime/layout/closeFrame.ts +0 -33
  187. package/src/runtime/layout/closeFrames.ts +0 -14
  188. package/src/runtime/layout/frameSplit.ts +0 -31
  189. package/src/runtime/layout/isPointInFrame.ts +0 -7
@@ -0,0 +1,126 @@
1
+ import { pushIfNotIn } from "@alanscodelog/utils/pushIfNotIn"
2
+ import { walk } from "@alanscodelog/utils/walk"
3
+
4
+ import { applyFrameChanges } from "./applyFrameChanges.js"
5
+ import { getFillEmptySpaceInfo } from "./getFillEmptySpaceInfo.js"
6
+ import { getFramesRedistributeInfo } from "./getFramesRedistributeInfo.js"
7
+ import { getFrameUndockInfo } from "./getFrameUndockInfo.js"
8
+
9
+ import { cloneFrame } from "../helpers/cloneFrame.js"
10
+ import { getDockBoundaries } from "../helpers/getDockBoundaries.js"
11
+ import { oppositeSide } from "../helpers/oppositeSide.js"
12
+ import { settings } from "../settings.js"
13
+ import type { EdgeSide, LayoutChange, LayoutWindow } from "../types/index.js"
14
+ import { LAYOUT_ERROR } from "../types/index.js"
15
+ import { KnownError } from "../utils/KnownError.js"
16
+
17
+ /**
18
+ * Returns a {@link LayoutChange} with the information necessary to dock a frame to a window edge.
19
+ *
20
+ * Changes can be applied to a window with {@link applyFrameChanges}.
21
+ */
22
+ export function getFrameDockInfo(
23
+ win: LayoutWindow,
24
+ frameId: string,
25
+ side: EdgeSide,
26
+ maxPerpendicularLength?: number
27
+ ):
28
+ | LayoutChange
29
+ | KnownError<typeof LAYOUT_ERROR.REDISTRIBUTE_OUT_OF_BOUNDS>
30
+ | KnownError<typeof LAYOUT_ERROR.NO_SPACE_TO_REDISTRIBUTE>
31
+ | KnownError<typeof LAYOUT_ERROR.CANT_LEAVE_NO_UNDOCKED_FRAMES>
32
+ | KnownError<typeof LAYOUT_ERROR.FRAME_ALREADY_DOCKED_ON_SIDE>
33
+ | KnownError<typeof LAYOUT_ERROR.CANT_UNDOCK_COLLAPSED_FRAME>
34
+ | KnownError<typeof LAYOUT_ERROR.NO_FILL_CANDIDATES> {
35
+ // its easier to just clone the window and extract changes later
36
+ // setting the var ensures we don't accidentally mutate the original
37
+ win = walk(win, undefined, { save: true }) as typeof win
38
+ const frame = win.frames[frameId]
39
+ if (!frame) { throw new Error(`Unknown frame ${frameId}`) }
40
+ if (frame.docked === side) throw new Error(`Frame ${frameId} is already docked on side ${side}.`)
41
+
42
+ const alreadyDockedOnSide = Object.values(win.frames).find(f => f.docked === side)
43
+
44
+ if (alreadyDockedOnSide) {
45
+ return new KnownError(LAYOUT_ERROR.FRAME_ALREADY_DOCKED_ON_SIDE, `Frame ${alreadyDockedOnSide.id} already docked on side ${side}.`, { id: alreadyDockedOnSide.id, side })
46
+ }
47
+
48
+ // if the frame is already docked on a different side, undock it first
49
+ // so it keeps its original dimensions when re-docking
50
+ if (frame.docked && frame.docked !== side) {
51
+ const undockInfo = getFrameUndockInfo(win, frameId)
52
+ if (undockInfo instanceof Error) return undockInfo
53
+ applyFrameChanges(win, undockInfo)
54
+ }
55
+
56
+ const isHorizontal = side === "left" || side === "right"
57
+ const perpendicular = isHorizontal ? "width" : "height"
58
+
59
+ const oldFrame = cloneFrame(frame)
60
+ const otherFrameIds = Object.keys(win.frames).filter(_ => _ !== frameId)
61
+ const nonDockedFrameIds = otherFrameIds.filter(id => !win.frames[id].docked)
62
+ if (nonDockedFrameIds.length === 0) {
63
+ return new KnownError(LAYOUT_ERROR.CANT_LEAVE_NO_UNDOCKED_FRAMES, `Can't dock frame ${frameId} because there are no other undocked frames.`, { frameId })
64
+ }
65
+
66
+ // if its the only frame allow it to be as big as it likes
67
+ const effectiveMaxPerpendicular = maxPerpendicularLength ?? settings.maxPerpendicularLengthScaled.width
68
+ const perpendicularLength = otherFrameIds.length > 0 ? Math.min(frame[perpendicular], effectiveMaxPerpendicular) : frame[perpendicular]
69
+
70
+ frame.docked = side
71
+ frame.collapsed = undefined
72
+
73
+ const toExtract = [frame.id]
74
+
75
+ // fills just the hole left by the frame when it was moved
76
+ const changes = getFillEmptySpaceInfo(win, oldFrame, [], [frameId])
77
+ if (changes instanceof Error) return changes
78
+ applyFrameChanges(win, changes)
79
+ pushIfNotIn(toExtract, changes.modified.map(_ => _.id))
80
+
81
+
82
+ // redistribute other non-docked frames to make room for the new dock.
83
+ const sideToPushTowards = oppositeSide(side)
84
+
85
+ const redistributeChanges = getFramesRedistributeInfo(win, sideToPushTowards, nonDockedFrameIds, perpendicularLength)
86
+
87
+ if (redistributeChanges instanceof KnownError) {
88
+ return redistributeChanges
89
+ }
90
+ applyFrameChanges(win, redistributeChanges)
91
+ pushIfNotIn(toExtract, redistributeChanges.modified.map(_ => _.id))
92
+
93
+
94
+ const { minX, maxX, minY, maxY } = getDockBoundaries(win)
95
+ switch (side) {
96
+ case "left":
97
+ frame.x = 0
98
+ frame.y = minY
99
+ frame.width = perpendicularLength
100
+ frame.height = maxY - minY
101
+ break
102
+ case "right":
103
+ frame.x = settings.maxInt - perpendicularLength
104
+ frame.y = minY
105
+ frame.width = perpendicularLength
106
+ frame.height = maxY - minY
107
+ break
108
+ case "top":
109
+ frame.x = minX
110
+ frame.y = 0
111
+ frame.width = maxX - minX
112
+ frame.height = perpendicularLength
113
+ break
114
+ case "bottom":
115
+ frame.x = minX
116
+ frame.y = settings.maxInt - perpendicularLength
117
+ frame.width = maxX - minX
118
+ frame.height = perpendicularLength
119
+ break
120
+ }
121
+
122
+
123
+ const res = toExtract.map(_ => win.frames[_])
124
+
125
+ return { modified: res, created: [], deleted: [] }
126
+ }
@@ -0,0 +1,100 @@
1
+ import { settings } from "../settings.js"
2
+ import type { DragZone } from "../types/index.js"
3
+
4
+ /**
5
+ * Returns frame drag zones (in **unrounded** scaled coordinate space).
6
+ *
7
+ * If the frame is too narrow for both left+right zones, only the center zone is returned.
8
+ *
9
+ * Same logic applies to top/bottom for very short frames.
10
+ *
11
+ * Unrounded because these are for display purposes only.
12
+ */
13
+ export function getFrameDragZones(
14
+ frame: { x: number, y: number, width: number, height: number },
15
+ thresholdPx: number,
16
+ windowPxWidth: number,
17
+ windowPxHeight: number
18
+ ): DragZone[] {
19
+ // we do not round as we might undo this transform and drag along errors
20
+ // plugs this is is for display purposes only
21
+ const thX = thresholdPx / windowPxWidth * settings.maxInt
22
+ const thY = thresholdPx / windowPxHeight * settings.maxInt
23
+ const pxWidth = frame.width / settings.maxInt * windowPxWidth
24
+ const pxHeight = frame.height / settings.maxInt * windowPxHeight
25
+
26
+ if (frame.width <= 3 * thX || frame.height <= 3 * thY) {
27
+ return [{
28
+ type: "frame",
29
+ side: "center", // this is more like center/full
30
+ x: frame.x,
31
+ y: frame.y,
32
+ width: frame.width,
33
+ height: frame.height,
34
+ pxWidth,
35
+ pxHeight
36
+ }]
37
+ }
38
+
39
+ const zones: DragZone[] = []
40
+
41
+ zones.push({
42
+ type: "frame",
43
+ side: "top",
44
+ x: frame.x,
45
+ y: frame.y,
46
+ width: frame.width,
47
+ height: thY,
48
+ pxWidth,
49
+ pxHeight: thresholdPx
50
+ })
51
+
52
+ zones.push({
53
+ type: "frame",
54
+ side: "bottom",
55
+ x: frame.x,
56
+ y: frame.y + frame.height - thY,
57
+ width: frame.width,
58
+ height: thY,
59
+ pxWidth,
60
+ pxHeight: thresholdPx
61
+ })
62
+
63
+ zones.push({
64
+ type: "frame",
65
+ side: "left",
66
+ x: frame.x,
67
+ y: frame.y,
68
+ width: thX,
69
+ height: frame.height,
70
+ pxWidth: thresholdPx,
71
+ pxHeight
72
+ })
73
+
74
+ zones.push({
75
+ type: "frame",
76
+ side: "right",
77
+ x: frame.x + frame.width - thX,
78
+ y: frame.y,
79
+ width: thX,
80
+ height: frame.height,
81
+ pxWidth: thresholdPx,
82
+ pxHeight
83
+ })
84
+
85
+ // center — remaining inner area
86
+ const cw = frame.width - 2 * thX
87
+ const ch = frame.height - 2 * thY
88
+ zones.push({
89
+ type: "frame",
90
+ side: "center",
91
+ x: frame.x + thX,
92
+ y: frame.y + thY,
93
+ width: cw,
94
+ height: ch,
95
+ pxWidth: cw / settings.maxInt * windowPxWidth,
96
+ pxHeight: ch / settings.maxInt * windowPxHeight
97
+ })
98
+
99
+ return zones
100
+ }
@@ -0,0 +1,261 @@
1
+ import { getFillEmptySpaceInfo } from "./getFillEmptySpaceInfo.js"
2
+ import { getFrameSplitInfo } from "./getFrameSplitInfo.js"
3
+ import { getFrameSwapInfo } from "./getFrameSwapInfo.js"
4
+
5
+ import { frameToEdges } from "../helpers/frameToEdges.js"
6
+ import { getSideTouching } from "../helpers/getSideTouching.js"
7
+ import { isEdgeEqual } from "../helpers/isEdgeEqual.js"
8
+ import { oppositeSide } from "../helpers/oppositeSide.js"
9
+ import { sideToDirection } from "../helpers/sideToDirection.js"
10
+ import type { DragZone, LayoutChange, LayoutWindow } from "../types/index.js"
11
+ import { LAYOUT_ERROR } from "../types/index.js"
12
+ import { KnownError } from "../utils/KnownError.js"
13
+
14
+
15
+ /**
16
+ * Returns a {@link LayoutChange} with the information necessary to rearrange a frame relative to another.
17
+ *
18
+ * Changes can be applied to a window with {@link applyFrameChanges}.
19
+ *
20
+ * Rearrangement is usually done by dragging a frame onto another frame's zone.
21
+ *
22
+ * The action taken depends on their placement relative to each other, see examples below.
23
+ *
24
+ * ## Examples
25
+ *
26
+ * Dragging a frame onto itself in the left/right/top/bottom zones splits it and creates a new frame. Center returns an error.
27
+ *
28
+ * Then there are the more typical cases:
29
+ *
30
+ * Shared Edge Case:
31
+ *
32
+ * ┌─────┬─────┬─────┐
33
+ * │A │B │C │
34
+ * │ │ ├─────┤
35
+ * │ │ │D │
36
+ * └─────┴─────┴─────┘
37
+ *
38
+ * A and B here are on the same edge, with A on the left of B.
39
+ *
40
+ * Dragging A onto B will result in the following depending on what drop zone of B the dragged frame lands on:
41
+ *
42
+ * Left - Error that can be safely ignored - A is already on the left of B
43
+ * Right - Swap A and B
44
+ * Top
45
+ * - B is "split" up (we only simulate it to get the positions). The position of the new split frame is taken and applied to A to move it above B.
46
+ * - The gap left behind by A is filled however possible using {@link getFillEmptySpaceInfo}.
47
+ *
48
+ * The result looks like this:
49
+ *
50
+ * ┌───────────┬─────┐
51
+ * │A │C │
52
+ * ├───────────┼─────┤
53
+ * │B │D │
54
+ * └───────────┴─────┘
55
+ *
56
+ * Bottom - Like top, but A ends up on the bottom of B.
57
+ *
58
+ *
59
+ * Partially Shared Edge Case:
60
+ *
61
+ * ┌─────┬─────┬─────┐
62
+ * │A │B │C │
63
+ * │ │ │ │
64
+ * │ │ │ │
65
+ * │ │ ├─────┤
66
+ * │ │ │D │
67
+ * │ │ │ │
68
+ * └─────┴─────┴─────┘
69
+ *
70
+ * Same case but we'll be using B and C here as they share part of an edge but not the whole edge. Dragging B onto C will result in the following:
71
+ *
72
+ * Left
73
+ * - C will be "split" to the left. The position of the new split frame is taken and applied to B. The space left behind is filled resulting in this.
74
+ * - Note how c has shrunk due to the initial location of the split.
75
+ *
76
+ * ┌─────┬────────┬──┐
77
+ * │A │B │C │
78
+ * │ │ │ │
79
+ * │ │ │ │
80
+ * │ ├────────┴──┤
81
+ * │ │D │
82
+ * │ │ │
83
+ * └─────┴───────────┘
84
+ *
85
+ * Right - Like left but C ends up on the right of B.
86
+ *
87
+ * Top
88
+ * ┌─────┬───────────┐
89
+ * │A │B │
90
+ * │ ├───────────┤
91
+ * │ │C │
92
+ * │ ├───────────┤
93
+ * │ │D │
94
+ * │ │ │
95
+ * └─────┴───────────┘
96
+ *
97
+ * Bottom - Like top, but B ends up on the bottom of C.
98
+ *
99
+ * None-Shared Edge Case:
100
+ *
101
+ * Same case but we'll be dragging A over C.
102
+ * ┌─────┬─────┬─────┐
103
+ * │A │B │C │
104
+ * │ │ │ │
105
+ * │ │ │ │
106
+ * │ │ ├─────┤
107
+ * │ │ │D │
108
+ * │ │ │ │
109
+ * └─────┴─────┴─────┘
110
+ *
111
+ * These are a bit easier to reason about because the frame usually ends up at the split location exactly.
112
+ *
113
+ * Left
114
+ * ┌───────────┬──┬──┐
115
+ * │B │A │C │
116
+ * │ │ │ │
117
+ * │ │ │ │
118
+ * │ ├──┴──┤
119
+ * │ │D │
120
+ * │ │ │
121
+ * └───────────┴─────┘
122
+ * Right
123
+ * ┌───────────┬──┬──┐
124
+ * │B │C │A │
125
+ * │ │ │ │
126
+ * │ │ │ │
127
+ * │ ├──┴──┤
128
+ * │ │D │
129
+ * │ │ │
130
+ * └───────────┴─────┘
131
+ * Top
132
+ * ┌───────────┬─────┐
133
+ * │B │A │
134
+ * │ ├─────┤
135
+ * │ │C │
136
+ * │ ├─────┤
137
+ * │ │D │
138
+ * │ │ │
139
+ * └───────────┴─────┘
140
+ * Bottom
141
+ * ┌───────────┬─────┐
142
+ * │B │C │
143
+ * │ ├─────┤
144
+ * │ │A │
145
+ * │ ├─────┤
146
+ * │ │D │
147
+ * │ │ │
148
+ * └───────────┴─────┘
149
+ */
150
+
151
+ export function getFrameRearrangeInfo(
152
+ win: LayoutWindow,
153
+ draggingFrameId: string,
154
+ hoveredFrameId: string,
155
+ zoneSide: DragZone["side"]
156
+ ): LayoutChange<"split" | "swap" | "rearrange">
157
+ | KnownError<typeof LAYOUT_ERROR.CANT_SWAP_WITH_SELF>
158
+ | KnownError<typeof LAYOUT_ERROR.CANT_SPLIT_FRAME_TOO_SMALL>
159
+ | KnownError<typeof LAYOUT_ERROR.CANT_REARRANGE_TO_SAME_RELATIVE_POSITION>
160
+ | KnownError<typeof LAYOUT_ERROR.CANT_REARRANGE_WITH_DOCKED_EDGES>
161
+ | KnownError<typeof LAYOUT_ERROR.CANT_REARRANGE_DOCKED_WITH_NON_DOCKED>
162
+ | KnownError<typeof LAYOUT_ERROR.CANT_SPLIT_DOCKED_FRAME>
163
+ | KnownError<typeof LAYOUT_ERROR.NO_FILL_CANDIDATES> {
164
+ const draggingFrame = { ...win.frames[draggingFrameId] }
165
+ const hoveredFrame = { ...win.frames[hoveredFrameId] }
166
+
167
+ if (draggingFrameId === hoveredFrameId) {
168
+ if (zoneSide === "center") return new KnownError(LAYOUT_ERROR.CANT_SWAP_WITH_SELF, `Can't swap frame with self.`, { frame: hoveredFrame, zoneSide })
169
+ }
170
+
171
+ if (zoneSide === "center") {
172
+ const res = getFrameSwapInfo(win, draggingFrame.id, hoveredFrame.id)
173
+ if (res instanceof KnownError) return res
174
+ return {
175
+ modified: res.modified,
176
+ created: res.created,
177
+ deleted: res.deleted,
178
+ info: "swap"
179
+ }
180
+ }
181
+
182
+ if (draggingFrame.docked && !hoveredFrame.docked) { // center swapping IS allowed above
183
+ return new KnownError(LAYOUT_ERROR.CANT_REARRANGE_DOCKED_WITH_NON_DOCKED, `Can't rearrange docked frame ${draggingFrameId} with non-docked frame ${hoveredFrameId}, can only swap.`, { draggingFrameId, hoveredFrameId, zoneSide })
184
+ }
185
+
186
+ // block edge rearrange on docked frames
187
+ if (hoveredFrame.docked) { // again center swapping docked to undocked and vice versa is allowed
188
+ return new KnownError(LAYOUT_ERROR.CANT_REARRANGE_WITH_DOCKED_EDGES, `Can't rearrange with docked frame ${hoveredFrameId} edge, can only swap.`, { draggingFrameId, hoveredFrameId, zoneSide })
189
+ }
190
+
191
+ const touchingSide = getSideTouching(draggingFrame, hoveredFrame)
192
+ if (touchingSide) {
193
+ const dragEdges = frameToEdges(draggingFrame)
194
+ const hoverEdges = frameToEdges(hoveredFrame)
195
+ const hoverOppositeSide = oppositeSide(touchingSide)
196
+
197
+ if (isEdgeEqual(dragEdges[touchingSide], hoverEdges[hoverOppositeSide])) {
198
+ if (touchingSide === zoneSide) {
199
+ const res = getFrameSwapInfo(win, draggingFrame.id, hoveredFrame.id)
200
+ if (res instanceof KnownError) return res
201
+ return {
202
+ modified: res.modified,
203
+ created: res.created,
204
+ deleted: res.deleted,
205
+ info: "swap"
206
+ }
207
+ }
208
+ if (hoverOppositeSide === zoneSide) {
209
+ return new KnownError(LAYOUT_ERROR.CANT_REARRANGE_TO_SAME_RELATIVE_POSITION, `Frame ${draggingFrameId} is already on the ${zoneSide} of ${hoveredFrameId}`, { draggingFrameId, hoveredFrameId, zoneSide })
210
+ }
211
+ }
212
+ }
213
+
214
+ // copy of where draggedFrame was
215
+ const emptySpace = { ...draggingFrame }
216
+
217
+ const dir = sideToDirection(zoneSide)
218
+ const splitResult = getFrameSplitInfo(hoveredFrame, dir, "midpoint", undefined, { x: 0.001, y: 0.001 })
219
+
220
+ if (splitResult instanceof KnownError) {
221
+ return splitResult
222
+ }
223
+
224
+ const splitFrame = splitResult.modified[0]
225
+ const newFrame = splitResult.created[0]
226
+
227
+ if (draggingFrameId === hoveredFrameId) {
228
+ return { ...splitResult, deleted: [], info: "split" }
229
+ }
230
+
231
+ // hoveredFrame takes the position where it was split
232
+ hoveredFrame.x = splitFrame.x
233
+ hoveredFrame.y = splitFrame.y
234
+ hoveredFrame.width = splitFrame.width
235
+ hoveredFrame.height = splitFrame.height
236
+
237
+ // draggingFrame takes the position of the new frame created by the split
238
+ draggingFrame.x = newFrame.x
239
+ draggingFrame.y = newFrame.y
240
+ draggingFrame.width = newFrame.width
241
+ draggingFrame.height = newFrame.height
242
+
243
+ // even though fill empty info wont mutate, we need the moved versions to give to it
244
+ const winCopy = {
245
+ ...win,
246
+ frames: {
247
+ ...win.frames,
248
+ [hoveredFrame.id]: hoveredFrame,
249
+ [draggingFrame.id]: draggingFrame
250
+ }
251
+ }
252
+ const changes = getFillEmptySpaceInfo(winCopy, emptySpace, [hoveredFrameId, draggingFrameId])
253
+ if (changes instanceof KnownError) return changes
254
+
255
+ // it will also be missing said changes unless it further moved the frames which is not alway
256
+ const notMissing = changes.modified.map(_ => _.id).filter(id => id === hoveredFrame.id || draggingFrame.id)
257
+ if (!notMissing.includes(hoveredFrame.id)) changes.modified.push(hoveredFrame)
258
+ if (!notMissing.includes(draggingFrame.id)) changes.modified.push(draggingFrame)
259
+
260
+ return { ...changes, info: "split" }
261
+ }
@@ -2,26 +2,39 @@ import { findSafeSplitEdgeAndPosition } from "./findSafeSplitEdge.js"
2
2
  import { frameCreate } from "./frameCreate.js"
3
3
 
4
4
  import { cloneFrame } from "../helpers/cloneFrame.js"
5
- import { getMarginSize, getSnapPoint } from "../settings.js"
5
+ import { settings } from "../settings.js"
6
6
  import {
7
7
  type Direction,
8
8
  LAYOUT_ERROR,
9
+ type LayoutChange,
9
10
  type LayoutFrame,
10
11
  type Point,
11
12
  type Size
12
13
  } from "../types/index.js"
13
14
  import { KnownError } from "../utils/KnownError.js"
14
15
 
16
+ /**
17
+ * Returns a {@link LayoutChange} with the information necessary to split a frame in the given direction.
18
+ *
19
+ * Changes can be applied to a window with {@link applyFrameChanges}.
20
+ */
15
21
  export function getFrameSplitInfo(
16
22
  frame: LayoutFrame,
17
23
  dir: Direction,
18
24
  dragPointOrPosition: Point | number | "midpoint" = "midpoint",
19
- minSize: Size = getMarginSize(),
20
- snapAmount: Point = getSnapPoint()
21
- ): {
22
- splitFrame: LayoutFrame
23
- newFrame: LayoutFrame
24
- } | KnownError<typeof LAYOUT_ERROR.CANT_SPLIT_FRAME_TOO_SMALL> {
25
+ minSize: Size = settings.minSizeScaled,
26
+ snapAmount: Point = settings.snapPointScaled
27
+ ): LayoutChange
28
+ | KnownError<typeof LAYOUT_ERROR.CANT_SPLIT_FRAME_TOO_SMALL>
29
+ | KnownError<typeof LAYOUT_ERROR.CANT_SPLIT_DOCKED_FRAME> {
30
+ if (frame.docked) {
31
+ return new KnownError(
32
+ LAYOUT_ERROR.CANT_SPLIT_DOCKED_FRAME,
33
+ `Can't split docked frame ${frame.id}.`,
34
+ { frame }
35
+ )
36
+ }
37
+
25
38
  frame = cloneFrame(frame)
26
39
  let newFrame = { ...frame }
27
40
  const isHorz = dir === "left" || dir === "right"
@@ -64,5 +77,5 @@ export function getFrameSplitInfo(
64
77
  }
65
78
 
66
79
  newFrame = frameCreate({ ...newFrame, id: undefined })
67
- return { splitFrame: frame, newFrame }
80
+ return { modified: [frame], created: [newFrame], deleted: [] }
68
81
  }
@@ -0,0 +1,45 @@
1
+ import type { LayoutChange, LayoutWindow } from "../types/index.js"
2
+ import { LAYOUT_ERROR } from "../types/index.js"
3
+ import { KnownError } from "../utils/KnownError.js"
4
+
5
+ /**
6
+ * Returns a {@link LayoutChange} with the information necessary to swap two frames within the same window.
7
+ *
8
+ * Changes can be applied to a window with {@link applyFrameChanges}.
9
+ */
10
+ export function getFrameSwapInfo(
11
+ window: LayoutWindow,
12
+ frameIdA: string,
13
+ frameIdB: string
14
+ ): LayoutChange
15
+ | KnownError<typeof LAYOUT_ERROR.CANT_SWAP_WITH_SELF> {
16
+ const frameA = { ...window.frames[frameIdA] }
17
+ const frameB = { ...window.frames[frameIdB] }
18
+
19
+ if (frameA.id === frameB.id) {
20
+ return new KnownError(LAYOUT_ERROR.CANT_SWAP_WITH_SELF, `Cannot swap frames with the same id`, { frame: frameA })
21
+ }
22
+
23
+
24
+ const temp = { x: frameA.x, y: frameA.y, width: frameA.width, height: frameA.height }
25
+ frameA.x = frameB.x
26
+ frameA.y = frameB.y
27
+ frameA.width = frameB.width
28
+ frameA.height = frameB.height
29
+ frameB.x = temp.x
30
+ frameB.y = temp.y
31
+ frameB.width = temp.width
32
+ frameB.height = temp.height
33
+
34
+ const dockedA = frameA.docked
35
+ const dockedB = frameB.docked
36
+ frameA.docked = dockedB
37
+ frameB.docked = dockedA
38
+
39
+ const collapsedA = frameA.collapsed
40
+ const collapsedB = frameB.collapsed
41
+ frameA.collapsed = collapsedB
42
+ frameB.collapsed = collapsedA
43
+
44
+ return { modified: [frameA, frameB], created: [], deleted: [] }
45
+ }
@@ -1,4 +1,4 @@
1
- import { getMaxInt } from "../settings.js"
1
+ import { settings } from "../settings.js"
2
2
  import type { EdgeSide, LayoutFrame } from "../types/index.js"
3
3
 
4
4
  export function getFrameTo(
@@ -6,7 +6,7 @@ export function getFrameTo(
6
6
  frame: LayoutFrame,
7
7
  frames: LayoutFrame[]
8
8
  ): LayoutFrame | undefined {
9
- const max = getMaxInt()
9
+ const max = settings.maxInt
10
10
  if ((side === "top" && frame.y === 0)
11
11
  || (side === "left" && frame.y === 0)
12
12
  || (side === "right" && frame.x + frame.width === max)