@witchcraft/layout 0.3.1 → 0.4.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.
- package/README.md +62 -70
- package/dist/module.json +1 -1
- package/dist/module.mjs +6 -1
- package/dist/runtime/components/FrameDragHandle.vue +1 -1
- package/dist/runtime/components/LayoutDecosEdges.d.vue.ts +15 -0
- package/dist/runtime/components/LayoutDecosEdges.vue +34 -0
- package/dist/runtime/components/LayoutDecosEdges.vue.d.ts +15 -0
- package/dist/runtime/components/LayoutDecosRects.d.vue.ts +15 -0
- package/dist/runtime/components/LayoutDecosRects.vue +37 -0
- package/dist/runtime/components/LayoutDecosRects.vue.d.ts +15 -0
- package/dist/runtime/components/LayoutDragEdgeGrabbed.d.vue.ts +2 -0
- package/dist/runtime/components/LayoutDragEdgeGrabbed.vue +43 -0
- package/dist/runtime/components/LayoutDragEdgeGrabbed.vue.d.ts +2 -0
- package/dist/runtime/components/LayoutDragEdgeHandle.d.vue.ts +9 -0
- package/dist/runtime/components/LayoutDragEdgeHandle.vue +44 -0
- package/dist/runtime/components/LayoutDragEdgeHandle.vue.d.ts +9 -0
- package/dist/runtime/components/{LayoutDecos.d.vue.ts → LayoutDragEdgeVisible.d.vue.ts} +3 -2
- package/dist/runtime/components/LayoutDragEdgeVisible.vue +38 -0
- package/dist/runtime/components/{LayoutDecos.vue.d.ts → LayoutDragEdgeVisible.vue.d.ts} +3 -2
- package/dist/runtime/components/LayoutDragEdges.d.vue.ts +21 -0
- package/dist/runtime/components/LayoutDragEdges.vue +67 -0
- package/dist/runtime/components/LayoutDragEdges.vue.d.ts +21 -0
- package/dist/runtime/components/LayoutEdgesActiveFrame.d.vue.ts +2 -0
- package/dist/runtime/components/LayoutEdgesActiveFrame.vue +35 -0
- package/dist/runtime/components/LayoutEdgesActiveFrame.vue.d.ts +2 -0
- package/dist/runtime/components/LayoutFrame.d.vue.ts +7 -6
- package/dist/runtime/components/LayoutFrame.vue +18 -14
- package/dist/runtime/components/LayoutFrame.vue.d.ts +7 -6
- package/dist/runtime/components/LayoutIntersectionHandle.d.vue.ts +10 -0
- package/dist/runtime/components/LayoutIntersectionHandle.vue +36 -0
- package/dist/runtime/components/LayoutIntersectionHandle.vue.d.ts +10 -0
- package/dist/runtime/components/LayoutIntersectionVisible.d.vue.ts +6 -0
- package/dist/runtime/components/LayoutIntersectionVisible.vue +34 -0
- package/dist/runtime/components/LayoutIntersectionVisible.vue.d.ts +6 -0
- package/dist/runtime/components/LayoutIntersections.d.vue.ts +21 -0
- package/dist/runtime/components/LayoutIntersections.vue +52 -0
- package/dist/runtime/components/LayoutIntersections.vue.d.ts +21 -0
- package/dist/runtime/components/{LayoutShapeSquare.d.vue.ts → LayoutShapeRect.d.vue.ts} +2 -2
- package/dist/runtime/components/{LayoutShapeSquare.vue.d.ts → LayoutShapeRect.vue.d.ts} +2 -2
- package/dist/runtime/components/LayoutWindow.d.vue.ts +252 -14
- package/dist/runtime/components/LayoutWindow.vue +27 -48
- package/dist/runtime/components/LayoutWindow.vue.d.ts +252 -14
- package/dist/runtime/composables/useFrames.d.ts +859 -2
- package/dist/runtime/composables/useFrames.js +2 -0
- package/dist/runtime/demo/App.vue +122 -74
- package/dist/runtime/demo/DemoControls.vue +7 -5
- package/dist/runtime/demo/index.html +12 -0
- package/dist/runtime/demo/tailwind.css +1 -1
- package/dist/runtime/drag/CloseAction.d.ts +1 -2
- package/dist/runtime/drag/CloseAction.js +1 -1
- package/dist/runtime/drag/DragActionHandler.d.ts +5 -3
- package/dist/runtime/drag/DragActionHandler.js +40 -6
- package/dist/runtime/drag/FrameDragAction.js +6 -7
- package/dist/runtime/drag/SplitAction.d.ts +1 -2
- package/dist/runtime/drag/SplitAction.js +1 -1
- package/dist/runtime/drag/createDefaultHandlers.d.ts +1 -1
- package/dist/runtime/drag/defaultDragActions.d.ts +1 -1
- package/dist/runtime/helpers/consoleDebugWindow.d.ts +43 -0
- package/dist/runtime/helpers/consoleDebugWindow.js +222 -0
- package/dist/runtime/helpers/convertLayoutWindowToWorkspace.js +2 -2
- package/dist/runtime/helpers/framesRedistributeFix.d.ts +87 -0
- package/dist/runtime/helpers/framesRedistributeFix.js +54 -0
- package/dist/runtime/helpers/getMoveEdgeInfo.d.ts +6 -2
- package/dist/runtime/helpers/getMoveEdgeInfo.js +11 -2
- package/dist/runtime/helpers/getPinnedEdgesForCollapsedFrames.d.ts +5 -0
- package/dist/runtime/helpers/getPinnedEdgesForCollapsedFrames.js +18 -0
- package/dist/runtime/helpers/getShapeRectCss.d.ts +7 -0
- package/dist/runtime/helpers/{getShapeSquareCss.js → getShapeRectCss.js} +1 -1
- package/dist/runtime/helpers/index.d.ts +3 -3
- package/dist/runtime/helpers/index.js +3 -3
- package/dist/runtime/helpers/moveEdge.js +3 -1
- package/dist/runtime/helpers/pxSizeToScaledSize.d.ts +2 -0
- package/dist/runtime/helpers/{numberToScaledSize.js → pxSizeToScaledSize.js} +1 -1
- package/dist/runtime/helpers/updateWindowSizeWithEvent.d.ts +3 -0
- package/dist/runtime/helpers/validateLayoutShape.d.ts +5 -0
- package/dist/runtime/helpers/validateLayoutShape.js +28 -0
- package/dist/runtime/layout/applyFrameChanges.js +6 -0
- package/dist/runtime/layout/createSplitDecoShapes.js +1 -1
- package/dist/runtime/layout/getFrameCollapseInfo.d.ts +5 -2
- package/dist/runtime/layout/getFrameCollapseInfo.js +16 -46
- package/dist/runtime/layout/getFrameDockInfo.d.ts +1 -1
- package/dist/runtime/layout/getFrameDockInfo.js +5 -1
- package/dist/runtime/layout/getFrameUncollapseInfo.d.ts +5 -2
- package/dist/runtime/layout/getFrameUncollapseInfo.js +16 -50
- package/dist/runtime/layout/getFramesRedistributeInfo.d.ts +20 -4
- package/dist/runtime/layout/getFramesRedistributeInfo.js +31 -2
- package/dist/runtime/layout/getRelaxFramesInfo.d.ts +7 -0
- package/dist/runtime/layout/getRelaxFramesInfo.js +91 -0
- package/dist/runtime/layout/getUpdateWindowSizeInfo.d.ts +13 -0
- package/dist/runtime/layout/getUpdateWindowSizeInfo.js +65 -0
- package/dist/runtime/layout/index.d.ts +1 -0
- package/dist/runtime/layout/index.js +1 -0
- package/dist/runtime/layout/resizeFrame.js +1 -3
- package/dist/runtime/settings.d.ts +26 -19
- package/dist/runtime/settings.js +57 -22
- package/dist/runtime/types/index.d.ts +1243 -30
- package/dist/runtime/types/index.js +22 -8
- package/dist/runtime/types/vue.d.ts +41 -0
- package/dist/runtime/{drag/types.js → types/vue.js} +1 -0
- package/dist/runtime/utils/cssToKey.d.ts +5 -0
- package/dist/runtime/utils/cssToKey.js +3 -0
- package/package.json +11 -12
- package/src/module.ts +6 -1
- package/src/runtime/components/FrameDragHandle.vue +1 -1
- package/src/runtime/components/LayoutDecosEdges.vue +38 -0
- package/src/runtime/components/LayoutDecosRects.vue +42 -0
- package/src/runtime/components/LayoutDragEdgeGrabbed.vue +50 -0
- package/src/runtime/components/LayoutDragEdgeHandle.vue +52 -0
- package/src/runtime/components/LayoutDragEdgeVisible.vue +45 -0
- package/src/runtime/components/LayoutDragEdges.vue +78 -0
- package/src/runtime/components/LayoutEdgesActiveFrame.vue +43 -0
- package/src/runtime/components/LayoutFrame.vue +22 -15
- package/src/runtime/components/LayoutIntersectionHandle.vue +45 -0
- package/src/runtime/components/LayoutIntersectionVisible.vue +41 -0
- package/src/runtime/components/LayoutIntersections.vue +64 -0
- package/src/runtime/components/{LayoutShapeSquare.vue → LayoutShapeRect.vue} +2 -2
- package/src/runtime/components/LayoutWindow.vue +29 -49
- package/src/runtime/composables/useFrames.ts +7 -2
- package/src/runtime/demo/App.vue +128 -78
- package/src/runtime/demo/DemoControls.vue +7 -5
- package/src/runtime/demo/index.html +12 -0
- package/src/runtime/demo/tailwind.css +3 -0
- package/src/runtime/drag/CloseAction.ts +2 -3
- package/src/runtime/drag/DragActionHandler.ts +48 -10
- package/src/runtime/drag/FrameDragAction.ts +4 -5
- package/src/runtime/drag/SplitAction.ts +4 -3
- package/src/runtime/drag/createDefaultHandlers.ts +2 -1
- package/src/runtime/drag/defaultDragActions.ts +2 -1
- package/src/runtime/helpers/consoleDebugWindow.ts +331 -0
- package/src/runtime/helpers/convertLayoutWindowToWorkspace.ts +2 -2
- package/src/runtime/helpers/framesRedistributeFix.ts +162 -0
- package/src/runtime/helpers/getMoveEdgeInfo.ts +22 -5
- package/src/runtime/helpers/getPinnedEdgesForCollapsedFrames.ts +29 -0
- package/src/runtime/helpers/{getShapeSquareCss.ts → getShapeRectCss.ts} +3 -3
- package/src/runtime/helpers/index.ts +4 -3
- package/src/runtime/helpers/moveEdge.ts +3 -1
- package/src/runtime/helpers/{numberToScaledSize.ts → pxSizeToScaledSize.ts} +2 -2
- package/src/runtime/helpers/updateWindowSizeWithEvent.ts +3 -0
- package/src/runtime/helpers/validateLayoutShape.ts +46 -0
- package/src/runtime/layout/applyFrameChanges.ts +5 -0
- package/src/runtime/layout/createSplitDecoShapes.ts +1 -1
- package/src/runtime/layout/getFrameCollapseInfo.ts +26 -85
- package/src/runtime/layout/getFrameDockInfo.ts +8 -1
- package/src/runtime/layout/getFrameUncollapseInfo.ts +29 -96
- package/src/runtime/layout/getFramesRedistributeInfo.ts +60 -5
- package/src/runtime/layout/getRelaxFramesInfo.ts +139 -0
- package/src/runtime/layout/getUpdateWindowSizeInfo.ts +105 -0
- package/src/runtime/layout/index.ts +1 -0
- package/src/runtime/layout/resizeFrame.ts +4 -3
- package/src/runtime/settings.ts +78 -25
- package/src/runtime/types/index.ts +205 -33
- package/src/runtime/types/vue.ts +41 -0
- package/src/runtime/utils/cssToKey.ts +8 -0
- package/dist/runtime/components/LayoutDecos.vue +0 -26
- package/dist/runtime/components/LayoutEdges.d.vue.ts +0 -17
- package/dist/runtime/components/LayoutEdges.vue +0 -144
- package/dist/runtime/components/LayoutEdges.vue.d.ts +0 -17
- package/dist/runtime/drag/types.d.ts +0 -161
- package/dist/runtime/helpers/clampNumber.d.ts +0 -1
- package/dist/runtime/helpers/clampNumber.js +0 -3
- package/dist/runtime/helpers/getShapeSquareCss.d.ts +0 -7
- package/dist/runtime/helpers/numberToScaledSize.d.ts +0 -2
- package/src/runtime/components/LayoutDecos.vue +0 -28
- package/src/runtime/components/LayoutEdges.vue +0 -170
- package/src/runtime/drag/types.ts +0 -177
- package/src/runtime/helpers/clampNumber.ts +0 -9
- /package/dist/runtime/components/{LayoutShapeSquare.vue → LayoutShapeRect.vue} +0 -0
|
@@ -4,9 +4,10 @@ import { walk } from "@alanscodelog/utils/walk"
|
|
|
4
4
|
import { applyFrameChanges } from "./applyFrameChanges.js"
|
|
5
5
|
import { getFramesRedistributeInfo } from "./getFramesRedistributeInfo.js"
|
|
6
6
|
|
|
7
|
+
import { framesRedistributeFix } from "../helpers/framesRedistributeFix.js"
|
|
8
|
+
import { getPinnedEdgesForCollapsedFrames } from "../helpers/getPinnedEdgesForCollapsedFrames.js"
|
|
7
9
|
import { oppositeSide } from "../helpers/oppositeSide.js"
|
|
8
|
-
import {
|
|
9
|
-
import type { EdgeSide, LayoutChange, LayoutWindow } from "../types/index.js"
|
|
10
|
+
import type { EdgeSide, LayoutChange, LayoutWindow, Size } from "../types/index.js"
|
|
10
11
|
import { LAYOUT_ERROR } from "../types/index.js"
|
|
11
12
|
import { KnownError } from "../utils/KnownError.js"
|
|
12
13
|
|
|
@@ -20,19 +21,33 @@ import { KnownError } from "../utils/KnownError.js"
|
|
|
20
21
|
*/
|
|
21
22
|
export function getFrameUncollapseInfo(
|
|
22
23
|
win: LayoutWindow,
|
|
23
|
-
frameId: string
|
|
24
|
+
frameId: string,
|
|
25
|
+
{
|
|
26
|
+
restoreSize
|
|
27
|
+
}: {
|
|
28
|
+
/** Override the size to restore the frame to. */
|
|
29
|
+
restoreSize?: Size
|
|
30
|
+
} = {}
|
|
24
31
|
): LayoutChange
|
|
25
32
|
| KnownError<typeof LAYOUT_ERROR.CANT_UNCOLLAPSE_NOT_COLLAPSED>
|
|
26
33
|
| KnownError<typeof LAYOUT_ERROR.REDISTRIBUTE_OUT_OF_BOUNDS>
|
|
27
|
-
| KnownError<typeof LAYOUT_ERROR.NO_SPACE_TO_REDISTRIBUTE>
|
|
34
|
+
| KnownError<typeof LAYOUT_ERROR.NO_SPACE_TO_REDISTRIBUTE>
|
|
35
|
+
| KnownError<typeof LAYOUT_ERROR.REDISTRIBUTE_WOULD_RESULT_IN_INVALID_FRAMES> {
|
|
28
36
|
win = walk(win, undefined, { save: true })
|
|
29
37
|
const frame = win.frames[frameId]
|
|
30
38
|
if (!frame) { throw new Error(`Unknown frame ${frameId}`) }
|
|
31
39
|
if (!frame.docked) { throw new Error(`Frame ${frameId} is not docked.`) }
|
|
32
40
|
const toExtract = [frame.id]
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
|
|
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
|
+
|
|
48
|
+
const storedSize = (restoreSize !== undefined ? restoreSize[sizeKey] : frame.collapsed)!
|
|
49
|
+
|
|
50
|
+
if (frame.collapsed === undefined) {
|
|
36
51
|
return new KnownError(
|
|
37
52
|
LAYOUT_ERROR.CANT_UNCOLLAPSE_NOT_COLLAPSED,
|
|
38
53
|
`Frame ${frameId} is not collapsed.`,
|
|
@@ -40,93 +55,21 @@ export function getFrameUncollapseInfo(
|
|
|
40
55
|
)
|
|
41
56
|
}
|
|
42
57
|
|
|
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
58
|
const expandAmount = storedSize - currentSize
|
|
48
59
|
|
|
60
|
+
|
|
49
61
|
const dockedSide = frame.docked as EdgeSide
|
|
50
62
|
|
|
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
|
-
}
|
|
63
|
+
const { applyFixes, toFix } = framesRedistributeFix(win, frame, dockedSide, posKey, sizeKey, "expand")
|
|
123
64
|
|
|
124
65
|
// note fully collapsed frames without an area are already excluded by getFramesRedistributeInfo
|
|
125
66
|
const otherFrameIds = Object.keys(win.frames).filter(id => id !== frameId)
|
|
126
67
|
|
|
127
68
|
const redistributeSide = oppositeSide(frame.docked)
|
|
128
69
|
|
|
129
|
-
const
|
|
70
|
+
const pinnedEdgeCoordinates: number[] = getPinnedEdgesForCollapsedFrames(win, frame, dockedSide, posKey, sizeKey)
|
|
71
|
+
|
|
72
|
+
const changes = getFramesRedistributeInfo(win, redistributeSide, otherFrameIds, expandAmount, { pinnedEdgeCoordinates })
|
|
130
73
|
|
|
131
74
|
if (changes instanceof KnownError) {
|
|
132
75
|
return changes
|
|
@@ -135,19 +78,8 @@ export function getFrameUncollapseInfo(
|
|
|
135
78
|
applyFrameChanges(win, changes)
|
|
136
79
|
pushIfNotIn(toExtract, changes.modified.map(_ => _.id))
|
|
137
80
|
|
|
138
|
-
|
|
139
|
-
|
|
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))
|
|
81
|
+
applyFixes()
|
|
82
|
+
pushIfNotIn(toExtract, toFix)
|
|
151
83
|
|
|
152
84
|
frame[sizeKey] = storedSize
|
|
153
85
|
frame.collapsed = undefined
|
|
@@ -158,3 +90,4 @@ export function getFrameUncollapseInfo(
|
|
|
158
90
|
|
|
159
91
|
return { modified: toExtract.map(_ => win.frames[_]), created: [], deleted: [] }
|
|
160
92
|
}
|
|
93
|
+
|
|
@@ -13,7 +13,7 @@ import { KnownError } from "../utils/KnownError.js"
|
|
|
13
13
|
*
|
|
14
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
15
|
*
|
|
16
|
-
* See {@link
|
|
16
|
+
* See {@link framesRedistributeFix} for the helper that is used internally to get around this.
|
|
17
17
|
*
|
|
18
18
|
* ┌────────────┐
|
|
19
19
|
* │A │
|
|
@@ -22,7 +22,19 @@ import { KnownError } from "../utils/KnownError.js"
|
|
|
22
22
|
* └─────────┘
|
|
23
23
|
*
|
|
24
24
|
* Also note that it automatically excludes all collapsed frames without an area (height or width === 0).
|
|
25
|
+
*
|
|
26
|
+
* ## Pinned Edges
|
|
27
|
+
*
|
|
28
|
+
* Pinned edges will not be moved, space will be redistributed amongst remaining space. If doing this would cause edges to get "flipped", function will return {@link LAYOUT_ERROR.REDISTRIBUTE_WOULD_RESULT_IN_INVALID_FRAMES}.
|
|
29
|
+
*
|
|
30
|
+
* ┌──┬──────────────┐
|
|
31
|
+
* │ │ │
|
|
32
|
+
* │ ├──────────┬───┤
|
|
33
|
+
* │ │ │ │
|
|
34
|
+
* │ │ │ │
|
|
35
|
+
* └──┴──────────┴───┘
|
|
25
36
|
*/
|
|
37
|
+
|
|
26
38
|
export function getFramesRedistributeInfo(
|
|
27
39
|
win: LayoutWindow,
|
|
28
40
|
/** Side to expand/shrink to. */
|
|
@@ -30,11 +42,22 @@ export function getFramesRedistributeInfo(
|
|
|
30
42
|
frameIds: string[],
|
|
31
43
|
/** This can be negative to expand the frames instead (e.g. when collapsing a docked frame). */
|
|
32
44
|
amountScaled: number,
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
{
|
|
46
|
+
allowOutOfBounds = false,
|
|
47
|
+
pinnedEdgeCoordinates,
|
|
48
|
+
skipMinSizeCheck = false
|
|
49
|
+
}: {
|
|
50
|
+
/** Allow the resize span to exceed window bounds. */
|
|
51
|
+
allowOutOfBounds?: boolean
|
|
52
|
+
/** Frame IDs whose position and size must not change. Must be a subset of frameIds. */
|
|
53
|
+
pinnedEdgeCoordinates?: number[]
|
|
54
|
+
/** Skip the per-frame min size check. Frames may shrink very small but not to 0 or below. */
|
|
55
|
+
skipMinSizeCheck?: boolean
|
|
56
|
+
} = {}
|
|
35
57
|
): LayoutChange
|
|
36
58
|
| KnownError<typeof LAYOUT_ERROR.REDISTRIBUTE_OUT_OF_BOUNDS>
|
|
37
|
-
| KnownError<typeof LAYOUT_ERROR.NO_SPACE_TO_REDISTRIBUTE>
|
|
59
|
+
| KnownError<typeof LAYOUT_ERROR.NO_SPACE_TO_REDISTRIBUTE>
|
|
60
|
+
| KnownError<typeof LAYOUT_ERROR.REDISTRIBUTE_WOULD_RESULT_IN_INVALID_FRAMES> {
|
|
38
61
|
const isLeftOrTop = side === "left" || side === "top"
|
|
39
62
|
const isHorizontalSide = side === "left" || side === "right"
|
|
40
63
|
const posKey = isHorizontalSide ? "x" : "y" as const
|
|
@@ -42,6 +65,13 @@ export function getFramesRedistributeInfo(
|
|
|
42
65
|
|
|
43
66
|
const frames = frameIds.map(id => win.frames[id])
|
|
44
67
|
|
|
68
|
+
const pinnedEdges = new Set<number>()
|
|
69
|
+
if (pinnedEdgeCoordinates) {
|
|
70
|
+
for (const coord of pinnedEdgeCoordinates) {
|
|
71
|
+
pinnedEdges.add(coord)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
45
75
|
const edgeSet = new Set<number>()
|
|
46
76
|
for (const frame of frames) {
|
|
47
77
|
edgeSet.add(frame[posKey])
|
|
@@ -59,6 +89,7 @@ export function getFramesRedistributeInfo(
|
|
|
59
89
|
const anchorEdge = isLeftOrTop ? uniqueEdges[0] : uniqueEdges[uniqueEdges.length - 1]
|
|
60
90
|
const dirMultiplier = isLeftOrTop ? -1 : 1
|
|
61
91
|
|
|
92
|
+
// calculate new edges using proportional algo
|
|
62
93
|
const newEdges: number[] = []
|
|
63
94
|
for (let i = 0; i < uniqueEdges.length; i++) {
|
|
64
95
|
const edge = uniqueEdges[i]
|
|
@@ -72,8 +103,31 @@ export function getFramesRedistributeInfo(
|
|
|
72
103
|
const oppositeEdgeIndex = isLeftOrTop ? uniqueEdges.length - 1 : 0
|
|
73
104
|
newEdges[oppositeEdgeIndex] = isLeftOrTop ? anchorEdge + newSpan : anchorEdge - newSpan
|
|
74
105
|
|
|
106
|
+
// if we have pinned edges, force them back to their original positions
|
|
107
|
+
if (pinnedEdges.size > 0) {
|
|
108
|
+
for (let i = 0; i < uniqueEdges.length; i++) {
|
|
109
|
+
if (pinnedEdges.has(uniqueEdges[i])) {
|
|
110
|
+
newEdges[i] = uniqueEdges[i]
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// check for flipped frames - a non-pinned edge should not cross a pinned edge
|
|
115
|
+
for (let i = 0; i < uniqueEdges.length - 1; i++) {
|
|
116
|
+
const startEdge = uniqueEdges[i]
|
|
117
|
+
const endEdge = uniqueEdges[i + 1]
|
|
118
|
+
const newStart = newEdges[i]
|
|
119
|
+
const newEnd = newEdges[i + 1]
|
|
120
|
+
|
|
121
|
+
if (newStart > newEnd) {
|
|
122
|
+
const hasPinnedEdge = pinnedEdges.has(startEdge) || pinnedEdges.has(endEdge)
|
|
123
|
+
if (hasPinnedEdge) {
|
|
124
|
+
return new KnownError(LAYOUT_ERROR.REDISTRIBUTE_WOULD_RESULT_IN_INVALID_FRAMES, `Redistribution would cause frames to cross pinned edge at ${pinnedEdges.has(startEdge) ? startEdge : endEdge}.`, { problemEdgeCoordinates: [startEdge, endEdge] })
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
75
129
|
|
|
76
|
-
const minSize = settings.minSizeScaled[sizeKey]
|
|
130
|
+
const minSize = skipMinSizeCheck ? 0 : settings.minSizeScaled[sizeKey]
|
|
77
131
|
const result: LayoutFrame[] = []
|
|
78
132
|
for (const frame of frames) {
|
|
79
133
|
if (frame.collapsed && (frame.width === 0 || frame.height === 0)) continue
|
|
@@ -83,6 +137,7 @@ export function getFramesRedistributeInfo(
|
|
|
83
137
|
const newEnd = newEdges[uniqueEdges.indexOf(endEdge)]
|
|
84
138
|
const newSize = newEnd - newStart
|
|
85
139
|
|
|
140
|
+
|
|
86
141
|
if (newSize < minSize) {
|
|
87
142
|
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
143
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { pushIfNotIn } from "@alanscodelog/utils/pushIfNotIn"
|
|
2
|
+
import { walk } from "@alanscodelog/utils/walk"
|
|
3
|
+
|
|
4
|
+
import { getMoveEdgeInfo } from "../helpers/getMoveEdgeInfo.js"
|
|
5
|
+
import { settings } from "../settings.js"
|
|
6
|
+
import type { LayoutChange, LayoutWindow } from "../types/index.js"
|
|
7
|
+
import { KnownError } from "../utils/KnownError.js"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Experimental frame "relaxation" algorithm.
|
|
12
|
+
*
|
|
13
|
+
* Useful for expanding frames that have gotten too small. Used internally by {@link getUpdateWindowSizeInfo}
|
|
14
|
+
*/
|
|
15
|
+
export function getRelaxFramesInfo(
|
|
16
|
+
win: LayoutWindow,
|
|
17
|
+
passes: number = 2
|
|
18
|
+
): LayoutChange<any> {
|
|
19
|
+
win = walk(win, undefined, { save: true })
|
|
20
|
+
|
|
21
|
+
const toExtract: string[] = []
|
|
22
|
+
const framesSortedHorizontal = Object.values(win.frames)
|
|
23
|
+
.filter(f => f.width > 0 && f.width < settings.minSizeScaled.width && !f.collapsed)
|
|
24
|
+
.sort((a, b) => a.x - b.x)
|
|
25
|
+
const framesSortedVertical = Object.values(win.frames)
|
|
26
|
+
.filter(f => f.height > 0 && f.height < settings.minSizeScaled.height && !f.collapsed)
|
|
27
|
+
.sort((a, b) => a.y - b.y)
|
|
28
|
+
|
|
29
|
+
for (let pass = 0; pass < passes; pass++) {
|
|
30
|
+
let anyChanged = false
|
|
31
|
+
|
|
32
|
+
for (const orientation of ["horizontal", "vertical"] as const) {
|
|
33
|
+
const sizeKey = orientation === "horizontal" ? "width" : "height" as const
|
|
34
|
+
const posKey = orientation === "horizontal" ? "x" : "y" as const
|
|
35
|
+
const minSize = settings.minSizeScaled[sizeKey]
|
|
36
|
+
|
|
37
|
+
const frames = orientation === "horizontal" ? framesSortedHorizontal : framesSortedVertical
|
|
38
|
+
|
|
39
|
+
if (frames.length === 0) { continue }
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
for (const frame of frames) {
|
|
43
|
+
const deficit = minSize - frame[sizeKey]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if (deficit <= 0) continue
|
|
47
|
+
|
|
48
|
+
// try expanding the right/bottom edge first
|
|
49
|
+
const endEdge = {
|
|
50
|
+
startX: frame.x + frame.width,
|
|
51
|
+
startY: frame.y + frame.height,
|
|
52
|
+
endX: frame.x + frame.width,
|
|
53
|
+
endY: frame.y + frame.height
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const touchingEnd = Object.values(win.frames).filter(f =>
|
|
57
|
+
f.id !== frame.id
|
|
58
|
+
&& f[posKey] === frame[posKey] + frame[sizeKey]
|
|
59
|
+
&& !f.collapsed
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if (touchingEnd.length > 0) {
|
|
63
|
+
const targetPos = {
|
|
64
|
+
x: orientation === "horizontal" ? frame.x + frame.width + deficit : frame.x,
|
|
65
|
+
y: orientation === "vertical" ? frame.y + frame.height + deficit : frame.y
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
const result = getMoveEdgeInfo(touchingEnd, endEdge, targetPos, settings.minSizeScaled, true, true)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if (!(result instanceof KnownError) && result.distance > 0) {
|
|
73
|
+
const expandBy = Math.min(deficit, result.distance)
|
|
74
|
+
|
|
75
|
+
frame[sizeKey] += expandBy
|
|
76
|
+
for (const f of touchingEnd) {
|
|
77
|
+
f[posKey] += expandBy
|
|
78
|
+
f[sizeKey] -= expandBy
|
|
79
|
+
}
|
|
80
|
+
pushIfNotIn(toExtract, [frame.id, ...touchingEnd.map(f => f.id)])
|
|
81
|
+
anyChanged = true
|
|
82
|
+
|
|
83
|
+
if (frame[sizeKey] >= minSize) continue
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// if still too small, try expanding the left/top edge
|
|
88
|
+
if (frame[sizeKey] < minSize) {
|
|
89
|
+
const remainingDeficit = minSize - frame[sizeKey]
|
|
90
|
+
|
|
91
|
+
const startEdge = {
|
|
92
|
+
startX: frame.x,
|
|
93
|
+
startY: frame.y,
|
|
94
|
+
endX: frame.x,
|
|
95
|
+
endY: frame.y
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const touchingStart = Object.values(win.frames).filter(f =>
|
|
99
|
+
f.id !== frame.id
|
|
100
|
+
&& f[posKey] + f[sizeKey] === frame[posKey]
|
|
101
|
+
&& !f.collapsed
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if (touchingStart.length > 0) {
|
|
106
|
+
const targetPos = {
|
|
107
|
+
x: orientation === "horizontal" ? frame.x - remainingDeficit : frame.x,
|
|
108
|
+
y: orientation === "vertical" ? frame.y - remainingDeficit : frame.y
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
const result = getMoveEdgeInfo(touchingStart, startEdge, targetPos, settings.minSizeScaled, true, true)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if (!(result instanceof KnownError) && result.distance > 0) {
|
|
116
|
+
const expandBy = Math.min(remainingDeficit, result.distance)
|
|
117
|
+
|
|
118
|
+
frame[posKey] -= expandBy
|
|
119
|
+
frame[sizeKey] += expandBy
|
|
120
|
+
for (const f of touchingStart) {
|
|
121
|
+
f[sizeKey] -= expandBy
|
|
122
|
+
}
|
|
123
|
+
pushIfNotIn(toExtract, [frame.id, ...touchingStart.map(f => f.id)])
|
|
124
|
+
anyChanged = true
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!anyChanged) { break }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
modified: toExtract.map(id => win.frames[id]),
|
|
136
|
+
created: [],
|
|
137
|
+
deleted: []
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { pushIfNotIn } from "@alanscodelog/utils/pushIfNotIn"
|
|
2
|
+
import { walk } from "@alanscodelog/utils/walk"
|
|
3
|
+
|
|
4
|
+
import { applyFrameChanges } from "../layout/applyFrameChanges.js"
|
|
5
|
+
import { getFrameCollapseInfo } from "../layout/getFrameCollapseInfo.js"
|
|
6
|
+
import { getFrameUncollapseInfo } from "../layout/getFrameUncollapseInfo.js"
|
|
7
|
+
import { getRelaxFramesInfo } from "../layout/getRelaxFramesInfo.js"
|
|
8
|
+
import { settings } from "../settings.js"
|
|
9
|
+
import type { LayoutChange, LayoutWindow, PxSize } from "../types/index.js"
|
|
10
|
+
import { KnownError } from "../utils/KnownError.js"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns info to update the window size, keeping non-0 collapsed frames the same pixel size IF possible. Never returns an error.
|
|
15
|
+
*
|
|
16
|
+
* This sacrifices the min (percentage) size of other frames if needed, although not to the point of making them 0 size (which would break the layout).
|
|
17
|
+
*
|
|
18
|
+
* If there is absolutely no space, it will leave things as they are. This just means that the collapsed frame will shrink further than your ideal pixel width, but percentage size will stay the same.
|
|
19
|
+
*
|
|
20
|
+
* @experimental
|
|
21
|
+
*/
|
|
22
|
+
export function getUpdateWindowSizeInfo(
|
|
23
|
+
win: LayoutWindow,
|
|
24
|
+
event: PxSize,
|
|
25
|
+
{ relaxPasses = 2 }: { relaxPasses?: number } = {}
|
|
26
|
+
): LayoutChange<any> {
|
|
27
|
+
win = walk(win, undefined, { save: true }) as LayoutWindow
|
|
28
|
+
|
|
29
|
+
win.pxWidth = event.pxWidth
|
|
30
|
+
win.pxHeight = event.pxHeight
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
const maxInt = settings.maxInt
|
|
34
|
+
const frames = Object.values(win.frames)
|
|
35
|
+
|
|
36
|
+
const dockedFrameIds = frames.filter(f => f.collapsed).map(f => f.id)
|
|
37
|
+
|
|
38
|
+
if (dockedFrameIds.length === 0) {
|
|
39
|
+
return {
|
|
40
|
+
modified: [],
|
|
41
|
+
created: [],
|
|
42
|
+
deleted: [],
|
|
43
|
+
window: { pxWidth: event.pxWidth, pxHeight: event.pxHeight }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const toExtract: string[] = []
|
|
48
|
+
for (const frameId of dockedFrameIds) {
|
|
49
|
+
const frame = win.frames[frameId]
|
|
50
|
+
const orientation = (frame.docked === "left" || frame.docked === "right") ? "horizontal" : "vertical"
|
|
51
|
+
const sizeKey = orientation === "horizontal" ? "width" : "height" as const
|
|
52
|
+
const targetPxSize = settings.collapseSizePx[sizeKey]
|
|
53
|
+
const targetScaled = Math.round((targetPxSize / (orientation === "horizontal" ? win.pxWidth : win.pxHeight)) * maxInt)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if (frame[sizeKey] === targetScaled) continue
|
|
57
|
+
if (frame[sizeKey] === 0) continue
|
|
58
|
+
|
|
59
|
+
let result: LayoutChange | KnownError<any>
|
|
60
|
+
|
|
61
|
+
// the logic needed to do this is nearly identical to the collapse/uncollapse logic
|
|
62
|
+
// so we abuse the functions a bit, in future might allow a more flexible collapse/uncollapse function
|
|
63
|
+
const wasCollapsed = frame.collapsed
|
|
64
|
+
const wasMinSize = { ...settings.minSize }
|
|
65
|
+
settings.minSize = 1
|
|
66
|
+
if (targetScaled < frame[sizeKey]) {
|
|
67
|
+
// clear temporarily so collapse doesn't error
|
|
68
|
+
frame.collapsed = undefined
|
|
69
|
+
result = getFrameCollapseInfo(win, frame.id, { collapseSizeScaled: { width: targetScaled, height: targetScaled } })
|
|
70
|
+
} else {
|
|
71
|
+
// set to something, could be anything
|
|
72
|
+
frame.collapsed = Infinity
|
|
73
|
+
result = getFrameUncollapseInfo(win, frame.id, { restoreSize: { width: targetScaled, height: targetScaled } })
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!(result instanceof KnownError)) {
|
|
77
|
+
applyFrameChanges(win, result)
|
|
78
|
+
pushIfNotIn(toExtract, result.modified.map(f => f.id))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// restore original collapsed value
|
|
82
|
+
frame.collapsed = wasCollapsed
|
|
83
|
+
settings.minSize = wasMinSize
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
// attempt relaxation
|
|
87
|
+
const result2 = getRelaxFramesInfo(win, relaxPasses)
|
|
88
|
+
if (!(result2 instanceof KnownError)) {
|
|
89
|
+
applyFrameChanges(win, result2)
|
|
90
|
+
pushIfNotIn(toExtract, result2.modified.map(f => f.id))
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
modified: toExtract.map(id => win.frames[id]),
|
|
97
|
+
created: [],
|
|
98
|
+
deleted: [],
|
|
99
|
+
window: {
|
|
100
|
+
pxWidth: event.pxWidth,
|
|
101
|
+
pxHeight: event.pxHeight
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
@@ -21,6 +21,7 @@ export { getFrameSwapInfo } from "./getFrameSwapInfo.js"
|
|
|
21
21
|
export { getFrameTo } from "./getFrameTo.js"
|
|
22
22
|
export { getFrameUncollapseInfo } from "./getFrameUncollapseInfo.js"
|
|
23
23
|
export { getFrameUndockInfo } from "./getFrameUndockInfo.js"
|
|
24
|
+
export { getUpdateWindowSizeInfo } from "./getUpdateWindowSizeInfo.js"
|
|
24
25
|
export { getWindowDragZones } from "./getWindowDragZones.js"
|
|
25
26
|
export { isPointInRect } from "./isPointInRect.js"
|
|
26
27
|
export { layoutAddWindow } from "./layoutAddWindow.js"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { clampNumber } from "@alanscodelog/utils/clampNumber"
|
|
2
|
+
|
|
1
3
|
import { findFramesTouchingEdge } from "./findFramesTouchingEdge.js"
|
|
2
4
|
|
|
3
|
-
import { clampNumber } from "../helpers/clampNumber.js"
|
|
4
5
|
import { frameToEdges } from "../helpers/frameToEdges.js"
|
|
5
6
|
import { getEdgeOrientation } from "../helpers/getEdgeOrientation.js"
|
|
6
7
|
import { getResizeLimit } from "../helpers/getResizeLimit.js"
|
|
@@ -19,7 +20,7 @@ export function resizeFrame(
|
|
|
19
20
|
/** Scaled */
|
|
20
21
|
minSize: Size = settings.minSizeScaled
|
|
21
22
|
): boolean {
|
|
22
|
-
const originalDistance = distance
|
|
23
|
+
// const originalDistance = distance
|
|
23
24
|
const frameEdges = frameToEdges(frame)
|
|
24
25
|
const resizeEdges = dir === "up"
|
|
25
26
|
? [frameEdges.top]
|
|
@@ -85,7 +86,7 @@ export function resizeFrame(
|
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
if (!foundEdge) {
|
|
88
|
-
distance = originalDistance
|
|
89
|
+
// distance = originalDistance
|
|
89
90
|
continue
|
|
90
91
|
}
|
|
91
92
|
|