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