@witchcraft/layout 0.1.3 → 0.2.0

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,160 @@
1
+ import { pushIfNotIn } from "@alanscodelog/utils/pushIfNotIn"
2
+ import { walk } from "@alanscodelog/utils/walk"
3
+
4
+ import { applyFrameChanges } from "./applyFrameChanges.js"
5
+ import { getFramesRedistributeInfo } from "./getFramesRedistributeInfo.js"
6
+
7
+ import { oppositeSide } from "../helpers/oppositeSide.js"
8
+ import { settings } from "../settings.js"
9
+ import type { EdgeSide, LayoutChange, LayoutWindow } from "../types/index.js"
10
+ import { LAYOUT_ERROR } from "../types/index.js"
11
+ import { KnownError } from "../utils/KnownError.js"
12
+
13
+ /**
14
+ * Returns a {@link LayoutChange} with the information necessary to uncollapse a docked frame.
15
+ *
16
+ * Changes can be applied to a window with {@link applyFrameChanges}.
17
+ *
18
+ * Uncollapsing restores the frame to its pre-collapse size, shrinking neighboring
19
+ * frames to make room.
20
+ */
21
+ export function getFrameUncollapseInfo(
22
+ win: LayoutWindow,
23
+ frameId: string
24
+ ): LayoutChange
25
+ | KnownError<typeof LAYOUT_ERROR.CANT_UNCOLLAPSE_NOT_COLLAPSED>
26
+ | KnownError<typeof LAYOUT_ERROR.REDISTRIBUTE_OUT_OF_BOUNDS>
27
+ | KnownError<typeof LAYOUT_ERROR.NO_SPACE_TO_REDISTRIBUTE> {
28
+ win = walk(win, undefined, { save: true })
29
+ const frame = win.frames[frameId]
30
+ if (!frame) { throw new Error(`Unknown frame ${frameId}`) }
31
+ if (!frame.docked) { throw new Error(`Frame ${frameId} is not docked.`) }
32
+ const toExtract = [frame.id]
33
+
34
+ const storedSize = frame.collapsed
35
+ if (storedSize === false || storedSize === undefined) {
36
+ return new KnownError(
37
+ LAYOUT_ERROR.CANT_UNCOLLAPSE_NOT_COLLAPSED,
38
+ `Frame ${frameId} is not collapsed.`,
39
+ { frame }
40
+ )
41
+ }
42
+
43
+ const isVertical = frame.docked === "left" || frame.docked === "right"
44
+ const sizeKey = isVertical ? "width" : "height" as const
45
+ const posKey = isVertical ? "x" : "y"
46
+ const currentSize = frame[sizeKey]
47
+ const expandAmount = storedSize - currentSize
48
+
49
+ const dockedSide = frame.docked as EdgeSide
50
+
51
+ /**
52
+ * Mirrors the framesToFix logic in getFrameCollapseInfo:
53
+ *
54
+ * The only difference being we can skip the fixes if the frame was fully collapsed to 0.
55
+ *
56
+ * Why is this? Well take this example where A is docked and collapsed and B is docked, in the following two cases we would have no problems expanding as B ends where A begins, but if B shares the edge and A is not fully collapsed it is no longer aligned with the "end" of A.
57
+ *
58
+ * Ok:
59
+ * A - fully collapsed to 0
60
+ * ~ ┌──────────┐
61
+ * │ │B* │
62
+ * │ ├──────────┤
63
+ * │ │ │
64
+ * │ │ │
65
+ * │ └──────────┘
66
+ *
67
+ * Ok:
68
+ * A collapsed to non-zero size
69
+ * Note B still shares A's right edge
70
+ * ┌──┬─────────┐
71
+ * │A~│B* │
72
+ * │ ├─────────┤
73
+ * │ │ │
74
+ * │ │ │
75
+ * └──┴─────────┘
76
+ *
77
+ * Causes issues:
78
+ * A collapsed to non-zero size with B docked first
79
+ * B looses alignment with A causing issues when redistributing
80
+ * ┌────────────┐
81
+ * │B* │
82
+ * ├──┬─────────┤
83
+ * │A~│ │
84
+ * │ │ │
85
+ * └──┴─────────┘
86
+ */
87
+ const framesToFix: ({
88
+ id: string
89
+ posKey: "x" | "y"
90
+ sizeKey: "width" | "height"
91
+ type: "start" | "end"
92
+ })[] = []
93
+
94
+ const opposite = oppositeSide(dockedSide)
95
+ if (frame[sizeKey] !== 0) {
96
+ for (const other of Object.values(win.frames)) {
97
+ if (frame.id === other.id || !other.docked) continue
98
+ if (other.width === 0 || other.height === 0) continue
99
+
100
+ if (other.docked === opposite) continue
101
+ if (dockedSide === "left" || dockedSide === "top") {
102
+ if (other[posKey] !== 0) continue
103
+ other[posKey] = frame[posKey] + frame[sizeKey]
104
+ other[sizeKey] -= frame[sizeKey] // this is collapsed size
105
+ framesToFix.push({
106
+ id: other.id,
107
+ posKey: posKey,
108
+ sizeKey: sizeKey,
109
+ type: "start"
110
+ })
111
+ } else {
112
+ if (other[posKey] + other[sizeKey] !== settings.maxInt) continue
113
+ other[sizeKey] -= frame[sizeKey]
114
+ framesToFix.push({
115
+ id: other.id,
116
+ posKey: posKey,
117
+ sizeKey: sizeKey,
118
+ type: "end"
119
+ })
120
+ }
121
+ }
122
+ }
123
+
124
+ // note fully collapsed frames without an area are already excluded by getFramesRedistributeInfo
125
+ const otherFrameIds = Object.keys(win.frames).filter(id => id !== frameId)
126
+
127
+ const redistributeSide = oppositeSide(frame.docked)
128
+
129
+ const changes = getFramesRedistributeInfo(win, redistributeSide, otherFrameIds, expandAmount)
130
+
131
+ if (changes instanceof KnownError) {
132
+ return changes
133
+ }
134
+
135
+ applyFrameChanges(win, changes)
136
+ pushIfNotIn(toExtract, changes.modified.map(_ => _.id))
137
+
138
+ for (const fix of framesToFix) {
139
+ const other = win.frames[fix.id]
140
+ if (fix.type === "start") {
141
+ const sizeDiff = other[fix.posKey]
142
+ other[fix.posKey] = 0
143
+ other[fix.sizeKey] += sizeDiff
144
+ } else {
145
+ const sizeDiff = settings.maxInt - (other[fix.posKey] + other[fix.sizeKey])
146
+ other[fix.sizeKey] += sizeDiff
147
+ }
148
+ }
149
+
150
+ pushIfNotIn(toExtract, framesToFix.map(_ => _.id))
151
+
152
+ frame[sizeKey] = storedSize
153
+ frame.collapsed = false
154
+
155
+ if (frame.docked === "right" || frame.docked === "bottom") {
156
+ frame[posKey] -= expandAmount
157
+ }
158
+
159
+ return { modified: toExtract.map(_ => win.frames[_]), created: [], deleted: [] }
160
+ }
@@ -0,0 +1,97 @@
1
+ import { settings } from "../settings.js"
2
+ import type { LayoutChange, LayoutFrame, LayoutWindow } from "../types/index.js"
3
+ import { LAYOUT_ERROR } from "../types/index.js"
4
+ import { KnownError } from "../utils/KnownError.js"
5
+
6
+ /**
7
+ * Returns a {@link LayoutChange} with the information necessary to undock a frame.
8
+ *
9
+ * Changes can be applied to a window with {@link applyFrameChanges}.
10
+ *
11
+ * If a frame is not docked it will throw.
12
+ *
13
+ * If the frame was collapsed it will be uncollapsed.
14
+ */
15
+ export function getFrameUndockInfo(
16
+ win: LayoutWindow,
17
+ frameId: string
18
+ ):
19
+ | LayoutChange
20
+ | KnownError<typeof LAYOUT_ERROR.CANT_UNDOCK_COLLAPSED_FRAME> {
21
+ const frame = win.frames[frameId]
22
+ if (!frame) { throw new Error(`Unknown frame ${frameId}`) }
23
+ if (!frame.docked) throw new Error("Can't undock frame that is not docked.")
24
+
25
+ const frameSide = frame.docked
26
+ const isVertical = frameSide === "left" || frameSide === "right"
27
+ const sizeKey = isVertical ? "height" : "width" as const
28
+ const posKey = isVertical ? "y" : "x" as const
29
+ const maxInt = settings.maxInt
30
+ const shrinkStartKey = isVertical ? "top" : "left" as const
31
+ const shrinkEndKey = isVertical ? "bottom" : "right" as const
32
+
33
+ if (frame.collapsed) {
34
+ return new KnownError(LAYOUT_ERROR.CANT_UNDOCK_COLLAPSED_FRAME, `Can't undock collapsed frame ${frame.id}.`, { frame: frame.id })
35
+ }
36
+ const frameClone: LayoutFrame = { ...frame, docked: false, collapsed: false }
37
+ const otherDockedFramesToResize = []
38
+ // if the frame is touching either corner, there could be other docked frames that will need to get expanded like here, * means docked, and B and D would need expanding if we undock A.
39
+ /**
40
+ * ┌─────┬──────┐
41
+ * │A* │B* │
42
+ * │ ├──────┤
43
+ * │ │C │
44
+ * │ ├──────┤
45
+ * │ │D* │
46
+ * └─────┴──────┘
47
+ */
48
+ const dockedSidesToSearch = [
49
+ ...frame[posKey] === 0 ? [shrinkStartKey] : [],
50
+ ...(frame[posKey] + frame[sizeKey]) === maxInt ? [shrinkEndKey] : []
51
+ ]
52
+
53
+ for (const other of Object.values(win.frames)) {
54
+ if (other.id !== frame.id && other.docked && dockedSidesToSearch.includes(other.docked)) {
55
+ otherDockedFramesToResize.push(other)
56
+ }
57
+ }
58
+
59
+ // if there aren't others we can undock
60
+ if (otherDockedFramesToResize.length === 0) {
61
+ return { modified: [frameClone], created: [], deleted: [] }
62
+ }
63
+
64
+ const adjustmentKey = frameSide === "left"
65
+ ? "x"
66
+ : frameSide === "top"
67
+ ? "y"
68
+ : frameSide === "right"
69
+ ? "width"
70
+ : "height"
71
+
72
+ const secondaryAdjustmentKey = frameSide === "left"
73
+ ? "width"
74
+ : frameSide === "top"
75
+ ? "height"
76
+ : frameSide === "right"
77
+ ? "x"
78
+ : "y"
79
+
80
+ const adjustmentValue = frameSide === "left" || frameSide === "top" ? 0 : maxInt
81
+ const otherChanges = otherDockedFramesToResize.map(other => {
82
+ // shrink the ends of the undocking frame
83
+ if (other.docked === shrinkStartKey) {
84
+ frameClone[posKey] += other[sizeKey]
85
+ frameClone[sizeKey] -= other[sizeKey]
86
+ } else if (other.docked === shrinkEndKey) {
87
+ frameClone[sizeKey] -= other[sizeKey]
88
+ }
89
+ return {
90
+ ...other,
91
+ [adjustmentKey]: adjustmentValue,
92
+ [secondaryAdjustmentKey]: other[secondaryAdjustmentKey] + other[adjustmentKey]
93
+ }
94
+ })
95
+
96
+ return { modified: [frameClone, ...otherChanges], created: [], deleted: [] }
97
+ }
@@ -0,0 +1,98 @@
1
+ import { settings } from "../settings.js"
2
+ import type { EdgeSide, LayoutChange, LayoutFrame, LayoutWindow } from "../types/index.js"
3
+ import { LAYOUT_ERROR } from "../types/index.js"
4
+ import { KnownError } from "../utils/KnownError.js"
5
+
6
+
7
+ /**
8
+ * Returns a {@link LayoutChange} with the information necessary to redistribute frames to expand/shrink a certain amount towards the given side.
9
+ *
10
+ * Changes can be applied to a window with {@link applyFrameChanges}.
11
+ *
12
+ * While it checks for bounds/space issues (unless allowOutOfBounds is true), it does not check that all the frames correctly share/fill the start/end edges.
13
+ *
14
+ * If you try to resize a layout like this to the right, you will get issues with frame edges not lining up as frame B would be resized less than A. If you had a frame at the empty space that you excluded, it's right edge would no longer align with B's.
15
+ *
16
+ * See {@link getFrameCollapseInfo} and {@link getFrameUncollapseInfo} for how to work around this (you must move frames like A to share the edge then move them back). Excluding them from the calcuation usually leads to subtler errors. Such as if there are frames after A and B to the right, excluding A would mean A's right edge would stop aligning with those other frames.
17
+ *
18
+ * ┌────────────┐
19
+ * │A │
20
+ * └──┬─────────┤
21
+ * │B │
22
+ * └─────────┘
23
+ *
24
+ * Also note that it automatically excludes all collapsed frames without an area (height or width === 0).
25
+ */
26
+ export function getFramesRedistributeInfo(
27
+ win: LayoutWindow,
28
+ /** Side to expand/shrink to. */
29
+ side: EdgeSide,
30
+ frameIds: string[],
31
+ /** This can be negative to expand the frames instead (e.g. when collapsing a docked frame). */
32
+ amountScaled: number,
33
+ /** Allow the resize span to exceed window bounds. */
34
+ allowOutOfBounds = false
35
+ ): LayoutChange
36
+ | KnownError<typeof LAYOUT_ERROR.REDISTRIBUTE_OUT_OF_BOUNDS>
37
+ | KnownError<typeof LAYOUT_ERROR.NO_SPACE_TO_REDISTRIBUTE> {
38
+ const isLeftOrTop = side === "left" || side === "top"
39
+ const isHorizontalSide = side === "left" || side === "right"
40
+ const posKey = isHorizontalSide ? "x" : "y" as const
41
+ const sizeKey = isHorizontalSide ? "width" : "height" as const
42
+
43
+ const frames = frameIds.map(id => win.frames[id])
44
+
45
+ const edgeSet = new Set<number>()
46
+ for (const frame of frames) {
47
+ edgeSet.add(frame[posKey])
48
+ edgeSet.add(frame[posKey] + frame[sizeKey])
49
+ }
50
+ const uniqueEdges = [...edgeSet].sort((a, b) => a - b)
51
+
52
+ const maxInt = settings.maxInt
53
+ const resizeSpan = uniqueEdges[uniqueEdges.length - 1] - uniqueEdges[0]
54
+ const newSpan = resizeSpan - amountScaled
55
+ if (!allowOutOfBounds && (newSpan > maxInt || newSpan < 0)) {
56
+ return new KnownError(LAYOUT_ERROR.REDISTRIBUTE_OUT_OF_BOUNDS, `Not enough space to resize frames, needed ${newSpan} but min/max as 0/${maxInt}.`, { max: maxInt, min: 0, wanted: newSpan })
57
+ }
58
+
59
+ const anchorEdge = isLeftOrTop ? uniqueEdges[0] : uniqueEdges[uniqueEdges.length - 1]
60
+ const dirMultiplier = isLeftOrTop ? -1 : 1
61
+
62
+ const newEdges: number[] = []
63
+ for (let i = 0; i < uniqueEdges.length; i++) {
64
+ const edge = uniqueEdges[i]
65
+ const distFromAnchor = anchorEdge === uniqueEdges[0] ? edge - anchorEdge : anchorEdge - edge
66
+ const rawMovement = dirMultiplier * distFromAnchor / resizeSpan * amountScaled
67
+ const newEdge = edge + Math.trunc(rawMovement)
68
+ newEdges.push(newEdge)
69
+ }
70
+
71
+ // ensure the edge opposite the anchor lands exactly on the correct position
72
+ const oppositeEdgeIndex = isLeftOrTop ? uniqueEdges.length - 1 : 0
73
+ newEdges[oppositeEdgeIndex] = isLeftOrTop ? anchorEdge + newSpan : anchorEdge - newSpan
74
+
75
+
76
+ const minSize = settings.minSizeScaled[sizeKey]
77
+ const result: LayoutFrame[] = []
78
+ for (const frame of frames) {
79
+ if (frame.collapsed && (frame.width === 0 || frame.height === 0)) continue
80
+ const startEdge = frame[posKey]
81
+ const endEdge = frame[posKey] + frame[sizeKey]
82
+ const newStart = newEdges[uniqueEdges.indexOf(startEdge)]
83
+ const newEnd = newEdges[uniqueEdges.indexOf(endEdge)]
84
+ const newSize = newEnd - newStart
85
+
86
+ if (newSize < minSize) {
87
+ return new KnownError(LAYOUT_ERROR.NO_SPACE_TO_REDISTRIBUTE, `Redistribute would cause frame ${frame.id} to shrink to ${newSize} which is below minimum ${minSize}.`, { frameSizeNeeded: newSize, minFrameSize: minSize })
88
+ }
89
+
90
+ result.push({
91
+ ...frame,
92
+ [posKey]: newStart,
93
+ [sizeKey]: newSize
94
+ })
95
+ }
96
+
97
+ return { modified: result, created: [], deleted: [] }
98
+ }
@@ -0,0 +1,59 @@
1
+ import { numberToScaledPercent } from "../helpers/numberToScaledPercent.js"
2
+ import { settings } from "../settings.js"
3
+ import type { LayoutWindow, WindowEdgeZone } from "../types/index.js"
4
+
5
+ /**
6
+ * Returns window edge drag zones (in scaled coordinates)
7
+ *
8
+ */
9
+ export function getWindowDragZones(
10
+ win: LayoutWindow,
11
+ thresholdPx: number
12
+ ): WindowEdgeZone[] {
13
+ const thX = numberToScaledPercent (thresholdPx, win.pxWidth)
14
+ const thY = numberToScaledPercent (thresholdPx, win.pxHeight)
15
+ const maxInt = settings.maxInt
16
+
17
+ return [
18
+ {
19
+ type: "window",
20
+ side: "top",
21
+ x: 0,
22
+ y: 0,
23
+ width: maxInt,
24
+ height: thY,
25
+ pxWidth: win.pxWidth,
26
+ pxHeight: thresholdPx
27
+ },
28
+ {
29
+ type: "window",
30
+ side: "bottom",
31
+ x: 0,
32
+ y: maxInt - thY,
33
+ width: maxInt,
34
+ height: thY,
35
+ pxWidth: win.pxWidth,
36
+ pxHeight: thresholdPx
37
+ },
38
+ {
39
+ type: "window",
40
+ side: "left",
41
+ x: 0,
42
+ y: 0,
43
+ width: thX,
44
+ height: maxInt,
45
+ pxWidth: thresholdPx,
46
+ pxHeight: win.pxHeight
47
+ },
48
+ {
49
+ type: "window",
50
+ side: "right",
51
+ x: maxInt - thX,
52
+ y: 0,
53
+ width: thX,
54
+ height: maxInt,
55
+ pxWidth: thresholdPx,
56
+ pxHeight: win.pxHeight
57
+ }
58
+ ]
59
+ }
@@ -1,19 +1,28 @@
1
1
  /* Autogenerated Index */
2
2
 
3
- export { closeFrame } from "./closeFrame.js"
4
- export { closeFrames } from "./closeFrames.js"
5
- export { createSplitDecoEdge } from "./createSplitDecoEdge.js"
3
+ export { applyFrameChanges } from "./applyFrameChanges.js"
6
4
  export { createSplitDecoFromDrag } from "./createSplitDecoFromDrag.js"
5
+ export { createSplitDecoShapes } from "./createSplitDecoShapes.js"
7
6
  export { debugFrame } from "./debugFrame.js"
8
7
  export { findFramesTouchingEdge } from "./findFramesTouchingEdge.js"
9
8
  export { findSafeSplitEdgeAndPosition } from "./findSafeSplitEdge.js"
10
9
  export { findVisualEdge } from "./findVisualEdge.js"
11
10
  export { frameCreate } from "./frameCreate.js"
12
- export { frameSplit } from "./frameSplit.js"
13
11
  export { getCloseFrameInfo } from "./getCloseFrameInfo.js"
12
+ export { getDragZones } from "./getDragZones.js"
13
+ export { getFillEmptySpaceInfo } from "./getFillEmptySpaceInfo.js"
14
+ export { getFrameCollapseInfo } from "./getFrameCollapseInfo.js"
15
+ export { getFrameDockInfo } from "./getFrameDockInfo.js"
16
+ export { getFrameDragZones } from "./getFrameDragZones.js"
17
+ export { getFrameRearrangeInfo } from "./getFrameRearrangeInfo.js"
14
18
  export { getFrameSplitInfo } from "./getFrameSplitInfo.js"
19
+ export { getFramesRedistributeInfo } from "./getFramesRedistributeInfo.js"
20
+ export { getFrameSwapInfo } from "./getFrameSwapInfo.js"
15
21
  export { getFrameTo } from "./getFrameTo.js"
16
- export { isPointInFrame } from "./isPointInFrame.js"
22
+ export { getFrameUncollapseInfo } from "./getFrameUncollapseInfo.js"
23
+ export { getFrameUndockInfo } from "./getFrameUndockInfo.js"
24
+ export { getWindowDragZones } from "./getWindowDragZones.js"
25
+ export { isPointInRect } from "./isPointInRect.js"
17
26
  export { layoutAddWindow } from "./layoutAddWindow.js"
18
27
  export { layoutCreate } from "./layoutCreate.js"
19
28
  export { layoutRemoveWindow } from "./layoutRemoveWindow.js"
@@ -0,0 +1,7 @@
1
+ import { inRange } from "../helpers/inRange.js"
2
+ import type { Point } from "../types/index.js"
3
+
4
+ export function isPointInRect(frame: { x: number, y: number, width: number, height: number }, point: Point): boolean {
5
+ return inRange(point.x, frame.x, frame.x + frame.width)
6
+ && inRange(point.y, frame.y, frame.y + frame.height)
7
+ }
@@ -7,7 +7,7 @@ import { getResizeLimit } from "../helpers/getResizeLimit.js"
7
7
  import { getVisualEdges } from "../helpers/getVisualEdges.js"
8
8
  import { isWindowEdge } from "../helpers/isWindowEdge.js"
9
9
  import { resizeByEdge } from "../helpers/resizeByEdge.js"
10
- import { getMarginSize } from "../settings.js"
10
+ import { settings } from "../settings.js"
11
11
  import type { Edge, ExtendedDirection, LayoutFrame, LayoutWindow, Size } from "../types/index.js"
12
12
 
13
13
  export function resizeFrame(
@@ -17,7 +17,7 @@ export function resizeFrame(
17
17
  /** Scaled */
18
18
  distance: Size,
19
19
  /** Scaled */
20
- minSize: Size = getMarginSize()
20
+ minSize: Size = settings.minSizeScaled
21
21
  ): boolean {
22
22
  const originalDistance = distance
23
23
  const frameEdges = frameToEdges(frame)
@@ -1,63 +1,83 @@
1
1
  import type { Point, Size } from "./types/index.js"
2
2
 
3
- const g = {
4
- SCALE: 3,
5
- maxInt: 100 * (10 ** 3),
6
- SNAP_PERCENTAGE_X: 0.5,
7
- SNAP_PERCENTAGE_Y: 0.5,
8
- snapPoint: { x: Math.round(0.5 * (10 ** 3)), y: Math.round(0.5 * (10 ** 3)) },
9
- MARGIN_PERCENTAGE_WIDTH: 10 ** 3,
10
- MARGIN_PERCENTAGE_HEIGHT: 10 ** 3,
11
- marginSize: { width: 10 ** 3, height: 10 ** 3 }
12
- }
13
- export const globalOptions = g
14
- // todo think of better way :/
3
+ export class Settings {
4
+ private _scale = 3
5
+ private _maxInt = 100 * (10 ** 3)
6
+ get scale() { return this._scale }
7
+ set scale(v: number) {
8
+ this._scale = v
9
+ this._maxInt = 100 * (10 ** v)
10
+ this._recalcAll()
11
+ }
12
+
13
+ get maxInt() { return this._maxInt }
15
14
 
16
- export function setScale(scale: number): void {
17
- const max = 100 * (10 ** scale)
18
- if (!Number.isSafeInteger(max)) {
19
- throw new TypeError("Scale too high. Precision will be lost!")
15
+
16
+ private _snapPoint = { x: 0.5, y: 0.5 }
17
+ private _snapPointScaled = { x: Math.round(0.5 * (10 ** 3)), y: Math.round(0.5 * (10 ** 3)) }
18
+
19
+ get snapPoint() { return this._snapPoint }
20
+ set snapPoint(v: number | Point) {
21
+ if (typeof v === "number") { this._snapPoint = { x: v, y: v } } else { this._snapPoint = { x: v.x, y: v.y } }
22
+ this._snapPointScaled = this._scalePoint(this._snapPoint)
20
23
  }
21
- g.SCALE = scale
22
- g.maxInt = max
23
- }
24
24
 
25
- export function getMaxInt(): number {
26
- return g.maxInt
27
- }
25
+ get snapPointScaled() { return this._snapPointScaled }
26
+
27
+
28
+ private _minSize = { width: 10 ** 3, height: 10 ** 3 }
29
+ private _minSizeScaled = { width: 10 ** 3, height: 10 ** 3 }
30
+
31
+ get minSize() { return this._minSize }
32
+ set minSize(v: number | Size) {
33
+ if (typeof v === "number") { this._minSize = { width: v, height: v } } else { this._minSize = { width: v.width, height: v.height } }
34
+ this._minSizeScaled = this._scaleSize(this._minSize)
35
+ }
36
+
37
+ get minSizeScaled() { return this._minSizeScaled }
28
38
 
29
- export function setSnapPercentage(snapPercentage: number | Point): void {
30
- if (typeof snapPercentage === "number") {
31
- g.SNAP_PERCENTAGE_X = snapPercentage
32
- g.SNAP_PERCENTAGE_Y = snapPercentage
33
- } else {
34
- g.SNAP_PERCENTAGE_X = snapPercentage.x
35
- g.SNAP_PERCENTAGE_Y = snapPercentage.y
39
+ private _collapseSize = { width: 0, height: 0 }
40
+ private _collapseSizeScaled = { width: 0, height: 0 }
41
+
42
+ get collapseSize() { return this._collapseSize }
43
+ set collapseSize(v: number | Size) {
44
+ if (typeof v === "number") { this._collapseSize = { width: v, height: v } } else { this._collapseSize = { width: v.width, height: v.height } }
45
+ this._collapseSizeScaled = this._scaleSize(this._collapseSize)
36
46
  }
37
- g.snapPoint = {
38
- x: Math.round(g.SNAP_PERCENTAGE_X * (10 ** g.SCALE)),
39
- y: Math.round(g.SNAP_PERCENTAGE_Y * (10 ** g.SCALE))
47
+
48
+ get collapseSizeScaled() { return this._collapseSizeScaled }
49
+
50
+ private _maxPerpendicularLength = { width: 20, height: 20 }
51
+ private _maxPerpendicularLengthScaled = { width: Math.round(20 * (10 ** 3)), height: Math.round(20 * (10 ** 3)) }
52
+
53
+ get maxPerpendicularLength() { return this._maxPerpendicularLength }
54
+ set maxPerpendicularLength(v: number | Size) {
55
+ if (typeof v === "number") { this._maxPerpendicularLength = { width: v, height: v } } else { this._maxPerpendicularLength = { ...v } }
56
+ this._maxPerpendicularLengthScaled = this._scaleSize(this._maxPerpendicularLength)
40
57
  }
41
- }
42
58
 
43
- export function getSnapPoint(): Readonly<Point> {
44
- return g.snapPoint
45
- }
59
+ get maxPerpendicularLengthScaled() { return this._maxPerpendicularLengthScaled }
46
60
 
47
- export function setMarginPercentage(margin: number | Size): void {
48
- if (typeof margin === "number") {
49
- g.MARGIN_PERCENTAGE_WIDTH = margin
50
- g.MARGIN_PERCENTAGE_HEIGHT = margin
51
- } else {
52
- g.MARGIN_PERCENTAGE_WIDTH = margin.width
53
- g.MARGIN_PERCENTAGE_HEIGHT = margin.height
61
+
62
+ private _scalePoint(p: Point): Point {
63
+ const m = 10 ** this._scale
64
+ return { x: Math.round(p.x * m), y: Math.round(p.y * m) }
54
65
  }
55
- g.marginSize = {
56
- width: Math.round(g.MARGIN_PERCENTAGE_WIDTH * (10 ** g.SCALE)),
57
- height: Math.round(g.MARGIN_PERCENTAGE_HEIGHT * (10 ** g.SCALE))
66
+
67
+ private _scaleSize(s: Size): Size {
68
+ const m = 10 ** this._scale
69
+ return { width: Math.round(s.width * m), height: Math.round(s.height * m) }
58
70
  }
59
- }
60
71
 
61
- export function getMarginSize(): Readonly<Size> {
62
- return g.marginSize
72
+ private _recalcAll() {
73
+ this.snapPoint = this._snapPoint
74
+ this.minSize = this._minSize
75
+ this.collapseSize = this._collapseSize
76
+ this.maxPerpendicularLength = this._maxPerpendicularLength
77
+ }
78
+
79
+ // ==== px sized, don't require recalc
80
+ zoneSizes = { frameEdgePx: 40, windowEdgePx: 20 }
63
81
  }
82
+
83
+ export const settings = new Settings()