@witchcraft/layout 0.3.1 → 0.4.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.
- 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/getFrameUndockInfo.js +4 -1
- 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/getFrameUndockInfo.ts +7 -1
- 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
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { oppositeSide } from "./oppositeSide.js"
|
|
2
|
+
|
|
3
|
+
import { settings } from "../settings.js"
|
|
4
|
+
import type { EdgeSide, LayoutFrame, LayoutWindow } from "../types/index.js"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Prepares frames that need to be temporarily repositioned before redistribution
|
|
8
|
+
* and returns functions to apply/revert the fixes after redistribution. Used mostly internally for collapse/uncollapse of docked frames and similar operations.
|
|
9
|
+
*
|
|
10
|
+
* It exists because 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.
|
|
11
|
+
*
|
|
12
|
+
* This helper takes frames like A, moves the edge so it's shared then moved them back. Just 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.
|
|
13
|
+
*
|
|
14
|
+
* ┌────────────┐
|
|
15
|
+
* │A │
|
|
16
|
+
* └──┬─────────┤
|
|
17
|
+
* │B │
|
|
18
|
+
* └─────────┘
|
|
19
|
+
*
|
|
20
|
+
* It has two modes, shrink and expand meant for collapse/uncollapse operations.
|
|
21
|
+
*
|
|
22
|
+
* ## Expand
|
|
23
|
+
*
|
|
24
|
+
* When multiple frames are docked* they may share an edge like this:
|
|
25
|
+
* For example, if we collapse A, we need to allow everything else to expand,
|
|
26
|
+
* but if we expand D it will overflow the bounds of the window and we'd get an error.
|
|
27
|
+
* To avoid this, we shrink these frames towards the opposite side first.
|
|
28
|
+
*
|
|
29
|
+
* collapsed size/amount
|
|
30
|
+
* ├──┘
|
|
31
|
+
* │ shrink amount
|
|
32
|
+
* │ ├───┘
|
|
33
|
+
* ├──────┬───────────┐
|
|
34
|
+
* │A*│ │B* │
|
|
35
|
+
* │ ├──────┬────┤
|
|
36
|
+
* │ │ │E │C* │
|
|
37
|
+
* │ │ │ │
|
|
38
|
+
* ├──┴───┴──────┤ │
|
|
39
|
+
* │D* │ │
|
|
40
|
+
* └──────┬──────┴────┘
|
|
41
|
+
* │we would shrink D to here
|
|
42
|
+
*
|
|
43
|
+
* D.x = A.x + A.width, and then subtract the difference from it's width
|
|
44
|
+
*
|
|
45
|
+
* ***IMPROTANT: It needs to stay aligned with the right side of A and the rest of the frames, or redistribute doesn't know how to redistribute it properly.
|
|
46
|
+
*
|
|
47
|
+
* When redistribute expands it again by shrinkAmount it will end up at A.x + collapseSize.
|
|
48
|
+
*
|
|
49
|
+
* This works fine when collapseSize is 0, but otherwise breaks so we keep a list of
|
|
50
|
+
* fixes to make after redistributing to place it's left edge back at the right place (it's right edge will have been moved by redistribute).
|
|
51
|
+
*
|
|
52
|
+
* ## Shrink
|
|
53
|
+
*
|
|
54
|
+
* Mirrors collapse, 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
|
+
*/
|
|
88
|
+
|
|
89
|
+
export function framesRedistributeFix(
|
|
90
|
+
win: LayoutWindow,
|
|
91
|
+
frame: LayoutFrame,
|
|
92
|
+
dockedSide: EdgeSide,
|
|
93
|
+
posKey: "x" | "y",
|
|
94
|
+
sizeKey: "width" | "height",
|
|
95
|
+
mode: "shrink" | "expand" = "shrink"
|
|
96
|
+
): {
|
|
97
|
+
applyFixes: () => void
|
|
98
|
+
toFix: string[]
|
|
99
|
+
} {
|
|
100
|
+
const framesToFix: ({
|
|
101
|
+
id: string
|
|
102
|
+
posKey: "x" | "y"
|
|
103
|
+
sizeKey: "width" | "height"
|
|
104
|
+
type: "start" | "end"
|
|
105
|
+
})[] = []
|
|
106
|
+
|
|
107
|
+
const opposite = oppositeSide(dockedSide)
|
|
108
|
+
|
|
109
|
+
// expand skips if frame was fully collapsed to 0
|
|
110
|
+
if (mode === "expand" && frame[sizeKey] === 0) {
|
|
111
|
+
return {
|
|
112
|
+
applyFixes: () => {},
|
|
113
|
+
toFix: []
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (const other of Object.values(win.frames)) {
|
|
118
|
+
if (frame.id === other.id || !other.docked) continue
|
|
119
|
+
|
|
120
|
+
// expand skips zero-area frames
|
|
121
|
+
if (mode === "expand" && (other.width === 0 || other.height === 0)) continue
|
|
122
|
+
|
|
123
|
+
if (other.docked === opposite) continue
|
|
124
|
+
if (dockedSide === "left" || dockedSide === "top") {
|
|
125
|
+
if (other[posKey] !== 0) continue
|
|
126
|
+
other[posKey] = frame[posKey] + frame[sizeKey]
|
|
127
|
+
other[sizeKey] -= frame[sizeKey]
|
|
128
|
+
framesToFix.push({
|
|
129
|
+
id: other.id,
|
|
130
|
+
posKey,
|
|
131
|
+
sizeKey,
|
|
132
|
+
type: "start"
|
|
133
|
+
})
|
|
134
|
+
} else {
|
|
135
|
+
if (other[posKey] + other[sizeKey] !== settings.maxInt) continue
|
|
136
|
+
other[sizeKey] -= frame[sizeKey]
|
|
137
|
+
framesToFix.push({
|
|
138
|
+
id: other.id,
|
|
139
|
+
posKey,
|
|
140
|
+
sizeKey,
|
|
141
|
+
type: "end"
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
applyFixes: () => {
|
|
148
|
+
for (const fix of framesToFix) {
|
|
149
|
+
const other = win.frames[fix.id]
|
|
150
|
+
if (fix.type === "start") {
|
|
151
|
+
const sizeDiff = other[fix.posKey]
|
|
152
|
+
other[fix.posKey] = 0
|
|
153
|
+
other[fix.sizeKey] += sizeDiff
|
|
154
|
+
} else {
|
|
155
|
+
const sizeDiff = settings.maxInt - (other[fix.posKey] + other[fix.sizeKey])
|
|
156
|
+
other[fix.sizeKey] += sizeDiff
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
toFix: framesToFix.map(f => f.id)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import { clampNumber } from "
|
|
1
|
+
import { clampNumber } from "@alanscodelog/utils/clampNumber"
|
|
2
|
+
|
|
2
3
|
import { getEdgeOrientation } from "./getEdgeOrientation.js"
|
|
3
4
|
import { getResizeLimit } from "./getResizeLimit.js"
|
|
4
5
|
|
|
5
6
|
import { settings } from "../settings.js"
|
|
6
7
|
import type {
|
|
7
8
|
Direction,
|
|
8
|
-
Edge,
|
|
9
|
+
Edge,
|
|
10
|
+
LayoutFrame,
|
|
9
11
|
Point,
|
|
10
|
-
Size
|
|
12
|
+
Size
|
|
13
|
+
} from "../types/index.js"
|
|
14
|
+
import { LAYOUT_ERROR } from "../types/index.js"
|
|
15
|
+
import { KnownError } from "../utils/KnownError.js"
|
|
11
16
|
|
|
12
17
|
export function getMoveEdgeInfo(
|
|
13
18
|
touchingFrames: LayoutFrame[],
|
|
@@ -15,7 +20,9 @@ export function getMoveEdgeInfo(
|
|
|
15
20
|
/** Window scaled/snaped position. See {@link toWindowCoord} */
|
|
16
21
|
position: Point,
|
|
17
22
|
margin: Size = settings.minSizeScaled,
|
|
18
|
-
clamp = true
|
|
23
|
+
clamp = true,
|
|
24
|
+
/** Skip the collapsed frame check. Used internally during window resize adjustments. */
|
|
25
|
+
ignoreCollapsedCheck = false
|
|
19
26
|
): {
|
|
20
27
|
x: number
|
|
21
28
|
y: number
|
|
@@ -23,7 +30,17 @@ export function getMoveEdgeInfo(
|
|
|
23
30
|
isPassedLimit: boolean
|
|
24
31
|
pos: number
|
|
25
32
|
distance: number
|
|
26
|
-
} {
|
|
33
|
+
} | KnownError<typeof LAYOUT_ERROR.CANT_RESIZE_COLLAPSED_FRAME> {
|
|
34
|
+
// prevent moving edges that touch collapsed frames
|
|
35
|
+
if (!ignoreCollapsedCheck) {
|
|
36
|
+
for (const frame of touchingFrames) {
|
|
37
|
+
// we check with a falsy check on PURPOSE, frames collapsed to 0 aren't an issue, they are ignored anyways
|
|
38
|
+
if (frame.collapsed) {
|
|
39
|
+
return new KnownError(LAYOUT_ERROR.CANT_RESIZE_COLLAPSED_FRAME, `Cannot resize non-0 collapsed frame ${frame.id}.`, { frame })
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
27
44
|
const { x: posX, y: posY } = position
|
|
28
45
|
|
|
29
46
|
const edgeDirection = getEdgeOrientation(edge)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function getPinnedEdgesForCollapsedFrames(
|
|
2
|
+
win: { frames: Record<string, any> },
|
|
3
|
+
frame: { id: string },
|
|
4
|
+
dockedSide: string,
|
|
5
|
+
posKey: string,
|
|
6
|
+
sizeKey: string
|
|
7
|
+
): number[] {
|
|
8
|
+
const pinnedEdgeCoordinates: number[] = []
|
|
9
|
+
const isHorizontal = dockedSide === "left" || dockedSide === "right"
|
|
10
|
+
|
|
11
|
+
for (const other of Object.values(win.frames)) {
|
|
12
|
+
if (frame.id === other.id || !other.collapsed) continue
|
|
13
|
+
|
|
14
|
+
const otherIsHorizontal = other.docked === "left" || other.docked === "right"
|
|
15
|
+
|
|
16
|
+
// only pin collapsed frames in the same direction
|
|
17
|
+
if (isHorizontal === otherIsHorizontal) {
|
|
18
|
+
if (other.docked === "left" || other.docked === "top") {
|
|
19
|
+
const edge = other[posKey] + other[sizeKey]
|
|
20
|
+
pinnedEdgeCoordinates.push(edge)
|
|
21
|
+
} else {
|
|
22
|
+
const edge = other[posKey]
|
|
23
|
+
pinnedEdgeCoordinates.push(edge)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return pinnedEdgeCoordinates
|
|
29
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { settings } from "../settings.js"
|
|
2
|
-
import type {
|
|
2
|
+
import type { BaseRect } from "../types/index.js"
|
|
3
3
|
|
|
4
|
-
export function
|
|
5
|
-
obj:
|
|
4
|
+
export function getShapeRectCss(
|
|
5
|
+
obj: BaseRect,
|
|
6
6
|
pad?: string
|
|
7
7
|
): {
|
|
8
8
|
x: string
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { clampNumber } from "@alanscodelog/utils/clampNumber"
|
|
2
|
+
|
|
1
3
|
/* Autogenerated Index */
|
|
2
4
|
|
|
3
5
|
export { addPointsToIntersection } from "./addPointsToIntersection.js"
|
|
@@ -8,7 +10,6 @@ export { assertLayoutHasActiveWindow } from "./assertLayoutHasActiveWindow.js"
|
|
|
8
10
|
export { assertValidWinAndFrame } from "./assertValidWinAndFrame.js"
|
|
9
11
|
export { assertValidWinAndFrameIds } from "./assertValidWinAndFrameIds.js"
|
|
10
12
|
export { assertWindowHasActiveFrame } from "./assertWindowHasActiveFrame.js"
|
|
11
|
-
export { clampNumber } from "./clampNumber.js"
|
|
12
13
|
export { cloneFrame } from "./cloneFrame.js"
|
|
13
14
|
export { cloneFrames } from "./cloneFrames.js"
|
|
14
15
|
export { containsEdge } from "./containsEdge.js"
|
|
@@ -35,7 +36,7 @@ export { getIntersections } from "./getIntersections.js"
|
|
|
35
36
|
export { getIntersectionsCss } from "./getIntersectionsCss.js"
|
|
36
37
|
export { getMoveEdgeInfo } from "./getMoveEdgeInfo.js"
|
|
37
38
|
export { getResizeLimit } from "./getResizeLimit.js"
|
|
38
|
-
export {
|
|
39
|
+
export { getShapeRectCss } from "./getShapeRectCss.js"
|
|
39
40
|
export { getSideTouching } from "./getSideTouching.js"
|
|
40
41
|
export { getVisualEdgeCss } from "./getVisualEdgeCss.js"
|
|
41
42
|
export { getVisualEdges } from "./getVisualEdges.js"
|
|
@@ -54,7 +55,7 @@ export { isWindowEdge } from "./isWindowEdge.js"
|
|
|
54
55
|
export { isWindowEdgePoint } from "./isWindowEdgePoint.js"
|
|
55
56
|
export { moveEdge } from "./moveEdge.js"
|
|
56
57
|
export { numberToScaledPercent } from "./numberToScaledPercent.js"
|
|
57
|
-
export {
|
|
58
|
+
export { pxSizeToScaledSize } from "./pxSizeToScaledSize.js"
|
|
58
59
|
export { oppositeSide } from "./oppositeSide.js"
|
|
59
60
|
export { resizeByEdge } from "./resizeByEdge.js"
|
|
60
61
|
export { scaledPointToPx } from "./scaledPointToPx.js"
|
|
@@ -16,6 +16,8 @@ export function moveEdge(
|
|
|
16
16
|
margin: Size = settings.minSizeScaled
|
|
17
17
|
): void {
|
|
18
18
|
if (!edge || !touchingFrames) return
|
|
19
|
-
const
|
|
19
|
+
const result = getMoveEdgeInfo(touchingFrames, edge, position, margin)
|
|
20
|
+
if (result instanceof Error) return
|
|
21
|
+
const { pos, dir, distance } = result
|
|
20
22
|
resizeByEdge(touchingFrames, edge, dir, pos, distance)
|
|
21
23
|
}
|
|
@@ -5,8 +5,8 @@ import { numberToScaledPercent } from "./numberToScaledPercent.js"
|
|
|
5
5
|
import { settings } from "../settings.js"
|
|
6
6
|
import type { LayoutWindow, PxSize, Size } from "../types/index.js"
|
|
7
7
|
|
|
8
|
-
export function
|
|
9
|
-
win: LayoutWindow,
|
|
8
|
+
export function pxSizeToScaledSize(
|
|
9
|
+
win: Pick<LayoutWindow, "pxWidth" | "pxHeight">,
|
|
10
10
|
size: PxSize | number,
|
|
11
11
|
scale: number = settings.maxInt
|
|
12
12
|
): Size {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { LayoutWindow, PxPos, PxSize } from "../types/index.js"
|
|
2
2
|
|
|
3
3
|
const props = ["pxWidth", "pxHeight", "pxX", "pxY"] as const
|
|
4
|
+
/**
|
|
5
|
+
* See also the experimental {@link getUpdateWindowSizeInfo} for the function that will likely replace this.
|
|
6
|
+
*/
|
|
4
7
|
export function updateWindowWithEvent(win: LayoutWindow, event: PxPos & PxSize): void {
|
|
5
8
|
for (const prop of props) {
|
|
6
9
|
if (event[prop] && win[prop] !== event[prop]) {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { LayoutFrame } from "../types/index.js"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* For testing, validates the resulting layout is valid (frames do not overlap, have no gaps between them and form a rectangular area.
|
|
5
|
+
*/
|
|
6
|
+
export function validateLayoutShape(frames: Pick<LayoutFrame, "x" | "y" | "width" | "height">[]): boolean {
|
|
7
|
+
const n = frames.length
|
|
8
|
+
if (n === 0) return false
|
|
9
|
+
|
|
10
|
+
// check area mismatch (non-rectangular, or has gaps)
|
|
11
|
+
let totalArea = 0
|
|
12
|
+
let minX = Infinity, minY = Infinity
|
|
13
|
+
let maxX = -Infinity, maxY = -Infinity
|
|
14
|
+
|
|
15
|
+
for (const f of frames) {
|
|
16
|
+
totalArea += f.width * f.height
|
|
17
|
+
minX = Math.min(minX, f.x)
|
|
18
|
+
minY = Math.min(minY, f.y)
|
|
19
|
+
maxX = Math.max(maxX, f.x + f.width)
|
|
20
|
+
maxY = Math.max(maxY, f.y + f.height)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const boundingArea = (maxX - minX) * (maxY - minY)
|
|
24
|
+
|
|
25
|
+
if (Math.abs(totalArea - boundingArea) > 0.001) {
|
|
26
|
+
return false
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// check overlaps
|
|
30
|
+
for (let i = 0; i < n; i++) {
|
|
31
|
+
for (let j = i + 1; j < n; j++) {
|
|
32
|
+
const a = frames[i]
|
|
33
|
+
const b = frames[j]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if (a.x < b.x + b.width
|
|
37
|
+
&& a.x + a.width > b.x
|
|
38
|
+
&& a.y < b.y + b.height
|
|
39
|
+
&& a.y + a.height > b.y) {
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return true
|
|
46
|
+
}
|
|
@@ -39,6 +39,6 @@ export function createSplitDecoShapes(
|
|
|
39
39
|
|
|
40
40
|
return [
|
|
41
41
|
{ type: "edge", data: edge, attrs: { class: classes.splitEdge ?? "deco-split-edge bg-red-500" } },
|
|
42
|
-
{ type: "
|
|
42
|
+
{ type: "rect", data: newFrame, attrs: { class: classes.splitNewFrame ?? "deco-split-new-frame bg-blue-500/50" } }
|
|
43
43
|
]
|
|
44
44
|
}
|
|
@@ -4,6 +4,8 @@ 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
10
|
import { settings } from "../settings.js"
|
|
9
11
|
import type { EdgeSide, LayoutChange, LayoutWindow, Size } from "../types/index.js"
|
|
@@ -21,9 +23,17 @@ import { KnownError } from "../utils/KnownError.js"
|
|
|
21
23
|
*/
|
|
22
24
|
export function getFrameCollapseInfo(
|
|
23
25
|
win: LayoutWindow,
|
|
24
|
-
frameId: string
|
|
26
|
+
frameId: string,
|
|
27
|
+
{
|
|
28
|
+
collapseSizeScaled
|
|
29
|
+
}: {
|
|
30
|
+
/** Override the target collapse size. If provided, the frame will collapse to this size instead of settings.collapseSizeScaled. */
|
|
31
|
+
collapseSizeScaled?: Size
|
|
32
|
+
} = {}
|
|
25
33
|
): LayoutChange
|
|
26
|
-
| KnownError<typeof LAYOUT_ERROR.CANT_COLLAPSE_NOT_DOCKED>
|
|
34
|
+
| KnownError<typeof LAYOUT_ERROR.CANT_COLLAPSE_NOT_DOCKED>
|
|
35
|
+
| KnownError<typeof LAYOUT_ERROR.REDISTRIBUTE_WOULD_RESULT_IN_INVALID_FRAMES> {
|
|
36
|
+
collapseSizeScaled = collapseSizeScaled ?? settings.getCollapseSizeScaled(win)
|
|
27
37
|
win = walk(win, undefined, { save: true }) as typeof win
|
|
28
38
|
const frame = win.frames[frameId]
|
|
29
39
|
if (!frame) throw new Error(`Unknown frame ${frameId}`)
|
|
@@ -45,120 +55,51 @@ export function getFrameCollapseInfo(
|
|
|
45
55
|
const oppositePosKey = isVertical ? "y" : "x"
|
|
46
56
|
const oppositeSizeKey = isVertical ? "height" : "width"
|
|
47
57
|
|
|
48
|
-
const collapseSize: Size = settings.collapseSizeScaled
|
|
49
58
|
|
|
50
59
|
const currentSize = frame[sizeKey]
|
|
51
|
-
const collapseAmount =
|
|
60
|
+
const collapseAmount = collapseSizeScaled[sizeKey]
|
|
52
61
|
const shrinkAmount = currentSize - collapseAmount
|
|
53
62
|
|
|
54
63
|
const dockedSide = frame.docked as EdgeSide
|
|
55
64
|
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* When multiple frames are docked* they may share an edge like this:
|
|
59
|
-
* For example, if we collapse A, we need to allow everything else to expand,
|
|
60
|
-
* but if we expand D it will overflow the bounds of the window and we'd get an error.
|
|
61
|
-
* To avoid this, we shrink these frames towards the opposite side first.
|
|
62
|
-
*
|
|
63
|
-
* collapsed size/amount
|
|
64
|
-
* ├──┘
|
|
65
|
-
* │ shrink amount
|
|
66
|
-
* │ ├───┘
|
|
67
|
-
* ├──────┬───────────┐
|
|
68
|
-
* │A*│ │B* │
|
|
69
|
-
* │ ├──────┬────┤
|
|
70
|
-
* │ │ │E │C* │
|
|
71
|
-
* │ │ │ │
|
|
72
|
-
* ├──┴───┴──────┤ │
|
|
73
|
-
* │D* │ │
|
|
74
|
-
* └──────┬──────┴────┘
|
|
75
|
-
* │we would shrink D to here
|
|
76
|
-
*
|
|
77
|
-
* D.x = A.x + A.width, and then subtract the difference from it's width
|
|
78
|
-
*
|
|
79
|
-
* ***IMPROTANT: It needs to stay aligned with the right side of A and the rest of the frames, or redistribute doesn't know how to redistribute it properly.
|
|
80
|
-
*
|
|
81
|
-
* When redistribute expands it again by shrinkAmount it will end up at A.x + collapseSize.
|
|
82
|
-
*
|
|
83
|
-
* This works fine when collapseSize is 0, but otherwise breaks so we keep a list of
|
|
84
|
-
* fixes to make after redistributing to place it's left edge back at the right place (it's right edge will have been moved by redistribute).
|
|
85
|
-
*/
|
|
86
|
-
|
|
87
|
-
const framesToFix: ({
|
|
88
|
-
id: string
|
|
89
|
-
posKey: "x" | "y"
|
|
90
|
-
sizeKey: "width" | "height"
|
|
91
|
-
type: "start" | "end"
|
|
92
|
-
})[] = []
|
|
93
|
-
const opposite = oppositeSide(dockedSide)
|
|
94
|
-
for (const other of Object.values(win.frames)) {
|
|
95
|
-
if (frame.id === other.id || !other.docked) continue
|
|
96
|
-
|
|
97
|
-
if (other.docked === opposite) continue
|
|
98
|
-
if (dockedSide === "left" || dockedSide === "top") {
|
|
99
|
-
if (other[posKey] !== 0) continue
|
|
100
|
-
other[posKey] = frame[posKey] + frame[sizeKey]
|
|
101
|
-
other[sizeKey] -= frame[sizeKey]
|
|
102
|
-
framesToFix.push({
|
|
103
|
-
id: other.id,
|
|
104
|
-
posKey,
|
|
105
|
-
sizeKey,
|
|
106
|
-
type: "start"
|
|
107
|
-
})
|
|
108
|
-
} else {
|
|
109
|
-
if (other[posKey] + other[sizeKey] !== settings.maxInt) continue
|
|
110
|
-
other[sizeKey] -= frame[sizeKey]
|
|
111
|
-
framesToFix.push({
|
|
112
|
-
id: other.id,
|
|
113
|
-
posKey,
|
|
114
|
-
sizeKey,
|
|
115
|
-
type: "end"
|
|
116
|
-
})
|
|
117
|
-
}
|
|
118
|
-
}
|
|
65
|
+
const { applyFixes, toFix } = framesRedistributeFix(win, frame, dockedSide, posKey, sizeKey, "shrink")
|
|
119
66
|
|
|
120
67
|
// note fully collapsed frames without an area are already excluded by getFramesRedistributeInfo
|
|
121
68
|
const otherFrameIds = Object.keys(win.frames).filter(id => id !== frameId)
|
|
122
69
|
|
|
123
70
|
const redistributeSide = oppositeSide(dockedSide)
|
|
124
71
|
|
|
125
|
-
const
|
|
72
|
+
const pinnedEdgeCoordinates: number[] = getPinnedEdgesForCollapsedFrames(win, frame, dockedSide, posKey, sizeKey)
|
|
73
|
+
|
|
74
|
+
const changes = getFramesRedistributeInfo(win, redistributeSide, otherFrameIds, -shrinkAmount, { allowOutOfBounds: true, pinnedEdgeCoordinates })
|
|
126
75
|
|
|
127
76
|
if (changes instanceof KnownError) {
|
|
128
77
|
// we should never get out of bounds and because this is a collapse there should always be space
|
|
129
|
-
if (changes.code === LAYOUT_ERROR.REDISTRIBUTE_OUT_OF_BOUNDS || LAYOUT_ERROR.NO_SPACE_TO_REDISTRIBUTE) {
|
|
78
|
+
if (changes.code === LAYOUT_ERROR.REDISTRIBUTE_OUT_OF_BOUNDS || changes.code === LAYOUT_ERROR.NO_SPACE_TO_REDISTRIBUTE) {
|
|
130
79
|
changes.message = `This error should never happen, please file a bug report: ${changes.message}`
|
|
131
80
|
throw changes
|
|
132
81
|
}
|
|
133
|
-
return changes
|
|
82
|
+
return changes
|
|
134
83
|
}
|
|
135
84
|
applyFrameChanges(win, changes)
|
|
136
85
|
pushIfNotIn(toExtract, changes.modified.map(_ => _.id))
|
|
137
86
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
pushIfNotIn(toExtract, framesToFix.map(_ => _.id))
|
|
87
|
+
|
|
88
|
+
applyFixes()
|
|
89
|
+
pushIfNotIn(toExtract, toFix)
|
|
150
90
|
|
|
151
91
|
if (frame.docked === "right" || frame.docked === "bottom") {
|
|
152
|
-
frame[posKey] = frame[posKey] + (frame[sizeKey] -
|
|
92
|
+
frame[posKey] = frame[posKey] + (frame[sizeKey] - collapseSizeScaled[sizeKey])
|
|
153
93
|
}
|
|
154
|
-
frame[sizeKey] =
|
|
94
|
+
frame[sizeKey] = collapseSizeScaled[sizeKey]
|
|
155
95
|
frame.collapsed = currentSize
|
|
156
96
|
// when we collapse to 0 it's a special case where the frame will always fit the entire window edge
|
|
157
97
|
// for proper uncollapsing later
|
|
158
|
-
if (
|
|
98
|
+
if (collapseSizeScaled[sizeKey] === 0) {
|
|
159
99
|
frame[oppositePosKey] = 0
|
|
160
100
|
frame[oppositeSizeKey] = settings.maxInt
|
|
161
101
|
}
|
|
162
102
|
|
|
163
103
|
return { modified: toExtract.map(_ => win.frames[_]), created: [], deleted: [] }
|
|
164
104
|
}
|
|
105
|
+
|
|
@@ -8,6 +8,7 @@ import { getFrameUndockInfo } from "./getFrameUndockInfo.js"
|
|
|
8
8
|
|
|
9
9
|
import { cloneFrame } from "../helpers/cloneFrame.js"
|
|
10
10
|
import { getDockBoundaries } from "../helpers/getDockBoundaries.js"
|
|
11
|
+
import { getPinnedEdgesForCollapsedFrames } from "../helpers/getPinnedEdgesForCollapsedFrames.js"
|
|
11
12
|
import { oppositeSide } from "../helpers/oppositeSide.js"
|
|
12
13
|
import { settings } from "../settings.js"
|
|
13
14
|
import type { EdgeSide, LayoutChange, LayoutWindow } from "../types/index.js"
|
|
@@ -28,6 +29,7 @@ export function getFrameDockInfo(
|
|
|
28
29
|
| LayoutChange
|
|
29
30
|
| KnownError<typeof LAYOUT_ERROR.REDISTRIBUTE_OUT_OF_BOUNDS>
|
|
30
31
|
| KnownError<typeof LAYOUT_ERROR.NO_SPACE_TO_REDISTRIBUTE>
|
|
32
|
+
| KnownError<typeof LAYOUT_ERROR.REDISTRIBUTE_WOULD_RESULT_IN_INVALID_FRAMES>
|
|
31
33
|
| KnownError<typeof LAYOUT_ERROR.CANT_LEAVE_NO_UNDOCKED_FRAMES>
|
|
32
34
|
| KnownError<typeof LAYOUT_ERROR.FRAME_ALREADY_DOCKED_ON_SIDE>
|
|
33
35
|
| KnownError<typeof LAYOUT_ERROR.CANT_UNDOCK_COLLAPSED_FRAME>
|
|
@@ -82,7 +84,12 @@ export function getFrameDockInfo(
|
|
|
82
84
|
// redistribute other non-docked frames to make room for the new dock.
|
|
83
85
|
const sideToPushTowards = oppositeSide(side)
|
|
84
86
|
|
|
85
|
-
const
|
|
87
|
+
const posKey = isHorizontal ? "x" : "y"
|
|
88
|
+
const sizeKey = isHorizontal ? "width" : "height"
|
|
89
|
+
|
|
90
|
+
const pinnedEdgeCoordinates: number[] = getPinnedEdgesForCollapsedFrames(win, frame, side, posKey, sizeKey)
|
|
91
|
+
|
|
92
|
+
const redistributeChanges = getFramesRedistributeInfo(win, sideToPushTowards, nonDockedFrameIds, perpendicularLength, { pinnedEdgeCoordinates })
|
|
86
93
|
|
|
87
94
|
if (redistributeChanges instanceof KnownError) {
|
|
88
95
|
return redistributeChanges
|