@witchcraft/layout 0.4.2 → 0.4.4
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/dist/module.json +1 -1
- package/dist/runtime/components/LayoutDragEdgeGrabbed.vue +1 -1
- package/dist/runtime/components/LayoutDragEdgeHandle.vue +1 -2
- package/dist/runtime/components/LayoutWindow.d.vue.ts +12 -0
- package/dist/runtime/components/LayoutWindow.vue +5 -4
- package/dist/runtime/components/LayoutWindow.vue.d.ts +12 -0
- package/dist/runtime/composables/useFrames.d.ts +38 -22
- package/dist/runtime/composables/useFrames.js +44 -8
- package/dist/runtime/demo/tailwind.css +1 -1
- package/dist/runtime/drag/CloseAction.d.ts +3 -0
- package/dist/runtime/drag/CloseAction.js +5 -0
- package/dist/runtime/drag/DragActionHandler.d.ts +4 -1
- package/dist/runtime/drag/DragActionHandler.js +2 -2
- package/dist/runtime/drag/FrameDragAction.d.ts +3 -0
- package/dist/runtime/drag/FrameDragAction.js +5 -0
- package/dist/runtime/drag/SplitAction.d.ts +3 -0
- package/dist/runtime/drag/SplitAction.js +8 -3
- package/dist/runtime/helpers/findEdgesTouchingWindow.d.ts +7 -0
- package/dist/runtime/helpers/findEdgesTouchingWindow.js +20 -0
- package/dist/runtime/helpers/index.d.ts +8 -1
- package/dist/runtime/helpers/index.js +8 -1
- package/dist/runtime/layout/debugFrame.js +5 -1
- package/dist/runtime/layout/getFrameCollapseInfo.d.ts +1 -1
- package/dist/runtime/layout/getFrameCollapseInfo.js +9 -29
- package/dist/runtime/layout/getFrameDockInfo.d.ts +1 -1
- package/dist/runtime/layout/getFrameDockInfo.js +61 -44
- package/dist/runtime/layout/getFrameExpandInfo.d.ts +11 -0
- package/dist/runtime/layout/getFrameExpandInfo.js +49 -0
- package/dist/runtime/layout/getFrameShrinkInfo.d.ts +14 -0
- package/dist/runtime/layout/getFrameShrinkInfo.js +48 -0
- package/dist/runtime/layout/getFrameUncollapseInfo.d.ts +1 -1
- package/dist/runtime/layout/getFrameUncollapseInfo.js +6 -20
- package/dist/runtime/layout/getUpdateWindowSizeInfo.js +10 -12
- package/dist/runtime/layout/index.d.ts +3 -0
- package/dist/runtime/layout/index.js +3 -0
- package/dist/runtime/settings.js +1 -1
- package/dist/runtime/types/index.d.ts +72 -0
- package/dist/runtime/types/index.js +2 -1
- package/dist/runtime/types/vue.d.ts +12 -3
- package/package.json +3 -2
- package/src/runtime/components/LayoutDragEdgeGrabbed.vue +1 -1
- package/src/runtime/components/LayoutDragEdgeHandle.vue +1 -2
- package/src/runtime/components/LayoutWindow.vue +8 -6
- package/src/runtime/composables/useFrames.ts +61 -32
- package/src/runtime/demo/tailwind.css +4 -0
- package/src/runtime/drag/CloseAction.ts +7 -0
- package/src/runtime/drag/DragActionHandler.ts +3 -3
- package/src/runtime/drag/FrameDragAction.ts +9 -0
- package/src/runtime/drag/SplitAction.ts +11 -3
- package/src/runtime/helpers/findEdgesTouchingWindow.ts +30 -0
- package/src/runtime/helpers/index.ts +8 -1
- package/src/runtime/layout/debugFrame.ts +6 -1
- package/src/runtime/layout/getFrameCollapseInfo.ts +12 -41
- package/src/runtime/layout/getFrameDockInfo.ts +77 -57
- package/src/runtime/layout/getFrameExpandInfo.ts +84 -0
- package/src/runtime/layout/getFrameShrinkInfo.ts +87 -0
- package/src/runtime/layout/getFrameUncollapseInfo.ts +8 -30
- package/src/runtime/layout/getUpdateWindowSizeInfo.ts +16 -22
- package/src/runtime/layout/index.ts +3 -0
- package/src/runtime/settings.ts +1 -1
- package/src/runtime/types/index.ts +41 -1
- package/src/runtime/types/vue.ts +5 -3
- package/dist/runtime/drag/defaultDragActions.d.ts +0 -9
- package/dist/runtime/drag/defaultDragActions.js +0 -10
- package/src/runtime/drag/defaultDragActions.ts +0 -20
|
@@ -13,6 +13,9 @@ import type { KnownError } from "../utils/KnownError.js"
|
|
|
13
13
|
|
|
14
14
|
export class FrameDragAction implements IDragAction {
|
|
15
15
|
name = "frameDrag" as const
|
|
16
|
+
|
|
17
|
+
minDragDistance = 5
|
|
18
|
+
|
|
16
19
|
state: {
|
|
17
20
|
lastReturn?: LayoutChange<"split" | "swap" | "rearrange" | "dock"> | KnownError
|
|
18
21
|
} = {
|
|
@@ -52,12 +55,15 @@ export class FrameDragAction implements IDragAction {
|
|
|
52
55
|
hooks: FrameDragAction["hooks"] = {},
|
|
53
56
|
config?: {
|
|
54
57
|
debug?: boolean | string
|
|
58
|
+
/** Minimum pixel distance the user must drag before the action is allowed (decos shown and action applied). Defaults to 5. */
|
|
59
|
+
minDragDistance?: number
|
|
55
60
|
}
|
|
56
61
|
) {
|
|
57
62
|
if (handleEvent !== undefined) this.handleEvent = handleEvent
|
|
58
63
|
if (modifyDecos !== undefined) this.modifyDecos = modifyDecos
|
|
59
64
|
this.hooks = hooks
|
|
60
65
|
if (config?.debug) this.debug = true
|
|
66
|
+
if (config?.minDragDistance !== undefined) this.minDragDistance = config.minDragDistance
|
|
61
67
|
|
|
62
68
|
this.reset()
|
|
63
69
|
}
|
|
@@ -149,6 +155,9 @@ export class FrameDragAction implements IDragAction {
|
|
|
149
155
|
_e: PointerEvent | undefined,
|
|
150
156
|
state: DragState
|
|
151
157
|
): ActionDragChangeResult {
|
|
158
|
+
if (state.dragDistance <= this.minDragDistance) {
|
|
159
|
+
return { updateEdges: false, shapes: [], showDragging: false }
|
|
160
|
+
}
|
|
152
161
|
const { win, draggingFrameId, dragHoveredFrame } = state
|
|
153
162
|
const matchedZone = getDragZones(state, settings.zoneSizes)
|
|
154
163
|
|
|
@@ -16,6 +16,7 @@ export type DragChangeType = "start" | "move" | "end"
|
|
|
16
16
|
|
|
17
17
|
export class SplitAction implements IDragAction {
|
|
18
18
|
name = "split" as const
|
|
19
|
+
minDragDistance = 5
|
|
19
20
|
|
|
20
21
|
state: {
|
|
21
22
|
allowed: true
|
|
@@ -57,6 +58,8 @@ export class SplitAction implements IDragAction {
|
|
|
57
58
|
config?: {
|
|
58
59
|
debug?: boolean | string
|
|
59
60
|
splitHints?: Partial<SplitAction["splitHints"]>
|
|
61
|
+
/** Minimum pixel distance the user must drag before the action is allowed (decos shown and action applied). Defaults to 5. */
|
|
62
|
+
minDragDistance?: number
|
|
60
63
|
}
|
|
61
64
|
) {
|
|
62
65
|
if (handleEvent !== undefined) this.handleEvent = handleEvent
|
|
@@ -65,6 +68,7 @@ export class SplitAction implements IDragAction {
|
|
|
65
68
|
this.hooks = hooks
|
|
66
69
|
this.reset()
|
|
67
70
|
if (config?.debug) this.debug = true
|
|
71
|
+
if (config?.minDragDistance !== undefined) this.minDragDistance = config.minDragDistance
|
|
68
72
|
if (config?.splitHints?.actions) this.splitHints.actions = config.splitHints.actions
|
|
69
73
|
if (config?.splitHints?.transformError) this.splitHints.transformError = config.splitHints.transformError
|
|
70
74
|
}
|
|
@@ -106,7 +110,8 @@ export class SplitAction implements IDragAction {
|
|
|
106
110
|
canHandleRequest(e: PointerEvent | KeyboardEvent, state: DragState): boolean {
|
|
107
111
|
const { draggingEdges } = state
|
|
108
112
|
if (draggingEdges.length !== 1) return false
|
|
109
|
-
|
|
113
|
+
// hint should not be shown when dragging from window edge but we should still handle the event
|
|
114
|
+
this.setTextHints(state.isDragging === "edge" && !state.isDraggingFromWindowEdge ? true : undefined)
|
|
110
115
|
if (this.handleEvent(e, state)) {
|
|
111
116
|
this.hooks.onStart?.()
|
|
112
117
|
return true
|
|
@@ -154,7 +159,7 @@ export class SplitAction implements IDragAction {
|
|
|
154
159
|
dragDirections[oppositeOrientation]!,
|
|
155
160
|
dragPoint!
|
|
156
161
|
)
|
|
157
|
-
this.setTextHints(canSplit)
|
|
162
|
+
this.setTextHints(state.isDragging === "edge" && !state.isDraggingFromWindowEdge ? canSplit : undefined)
|
|
158
163
|
if (!(canSplit instanceof Error)) {
|
|
159
164
|
this.state.allowed = true
|
|
160
165
|
this.state.res = canSplit
|
|
@@ -173,7 +178,10 @@ export class SplitAction implements IDragAction {
|
|
|
173
178
|
_e: PointerEvent | undefined,
|
|
174
179
|
state: DragState
|
|
175
180
|
): ActionDragChangeResult {
|
|
176
|
-
const { dragHoveredFrame } = state
|
|
181
|
+
const { dragHoveredFrame, dragDistance } = state
|
|
182
|
+
if (dragDistance <= this.minDragDistance) {
|
|
183
|
+
return { updateEdges: false, shapes: [], showDragging: false }
|
|
184
|
+
}
|
|
177
185
|
let ok = false
|
|
178
186
|
if (dragHoveredFrame) {
|
|
179
187
|
if (this.state.lastPoint?.x === state.dragPoint?.x && this.state.lastPoint?.y === state.dragPoint?.y && this.state.lastReturn) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { settings } from "../settings.js"
|
|
2
|
+
import type { EdgeSide, LayoutFrame } from "../types/index.js"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Checks which window edges a frame touches and how.
|
|
6
|
+
*
|
|
7
|
+
* @returns Record mapping touched sides to `"full"` (spans the entire edge) or `"partial"` (touches but doesn't span fully). Only touched sides are included as keys.
|
|
8
|
+
*/
|
|
9
|
+
export function findEdgesTouchingWindow(frame: LayoutFrame): Partial<Record<EdgeSide, "full" | "partial">> {
|
|
10
|
+
const maxInt = settings.maxInt
|
|
11
|
+
const result: Partial<Record<EdgeSide, "full" | "partial">> = {}
|
|
12
|
+
|
|
13
|
+
const spansVertical = frame.y === 0 && frame.y + frame.height === maxInt
|
|
14
|
+
const spansHorizontal = frame.x === 0 && frame.x + frame.width === maxInt
|
|
15
|
+
|
|
16
|
+
if (frame.x === 0) {
|
|
17
|
+
result.left = spansVertical ? "full" : "partial"
|
|
18
|
+
}
|
|
19
|
+
if (frame.x + frame.width === maxInt) {
|
|
20
|
+
result.right = spansVertical ? "full" : "partial"
|
|
21
|
+
}
|
|
22
|
+
if (frame.y === 0) {
|
|
23
|
+
result.top = spansHorizontal ? "full" : "partial"
|
|
24
|
+
}
|
|
25
|
+
if (frame.y + frame.height === maxInt) {
|
|
26
|
+
result.bottom = spansHorizontal ? "full" : "partial"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return result
|
|
30
|
+
}
|
|
@@ -12,6 +12,7 @@ export { assertValidWinAndFrameIds } from "./assertValidWinAndFrameIds.js"
|
|
|
12
12
|
export { assertWindowHasActiveFrame } from "./assertWindowHasActiveFrame.js"
|
|
13
13
|
export { cloneFrame } from "./cloneFrame.js"
|
|
14
14
|
export { cloneFrames } from "./cloneFrames.js"
|
|
15
|
+
export { consoleDebugWindow } from "./consoleDebugWindow.js"
|
|
15
16
|
export { containsEdge } from "./containsEdge.js"
|
|
16
17
|
export { convertLayoutWindowToWorkspace } from "./convertLayoutWindowToWorkspace.js"
|
|
17
18
|
export { copySize } from "./copySize.js"
|
|
@@ -24,17 +25,21 @@ export { doesEdgeContinueEdge } from "./doesEdgeContinueEdge.js"
|
|
|
24
25
|
export { edgeToPoints } from "./edgeToPoints.js"
|
|
25
26
|
export { findDraggableEdge } from "./findDraggableEdge.js"
|
|
26
27
|
export { findFrameDraggableEdges } from "./findFrameDraggableEdges.js"
|
|
28
|
+
export { framesRedistributeFix } from "./framesRedistributeFix.js"
|
|
27
29
|
export { frameToEdges } from "./frameToEdges.js"
|
|
28
30
|
export { frameToPoints } from "./frameToPoints.js"
|
|
31
|
+
export { getDockBoundaries } from "./getDockBoundaries.js"
|
|
29
32
|
export { getEdgeLength } from "./getEdgeLength.js"
|
|
30
33
|
export { getEdgeOrientation } from "./getEdgeOrientation.js"
|
|
31
34
|
export { getEdgeSharedDirection } from "./getEdgeSharedDirection.js"
|
|
32
35
|
export { getEdgeSide } from "./getEdgeSide.js"
|
|
33
36
|
export { getFrameById } from "./getFrameById.js"
|
|
34
37
|
export { getFrameConstant } from "./getFrameConstant.js"
|
|
38
|
+
export { findEdgesTouchingWindow } from "./findEdgesTouchingWindow.js"
|
|
35
39
|
export { getIntersections } from "./getIntersections.js"
|
|
36
40
|
export { getIntersectionsCss } from "./getIntersectionsCss.js"
|
|
37
41
|
export { getMoveEdgeInfo } from "./getMoveEdgeInfo.js"
|
|
42
|
+
export { getPinnedEdgesForCollapsedFrames } from "./getPinnedEdgesForCollapsedFrames.js"
|
|
38
43
|
export { getResizeLimit } from "./getResizeLimit.js"
|
|
39
44
|
export { getShapeRectCss } from "./getShapeRectCss.js"
|
|
40
45
|
export { getSideTouching } from "./getSideTouching.js"
|
|
@@ -55,9 +60,10 @@ export { isWindowEdge } from "./isWindowEdge.js"
|
|
|
55
60
|
export { isWindowEdgePoint } from "./isWindowEdgePoint.js"
|
|
56
61
|
export { moveEdge } from "./moveEdge.js"
|
|
57
62
|
export { numberToScaledPercent } from "./numberToScaledPercent.js"
|
|
58
|
-
export { pxSizeToScaledSize } from "./pxSizeToScaledSize.js"
|
|
59
63
|
export { oppositeSide } from "./oppositeSide.js"
|
|
64
|
+
export { pxSizeToScaledSize } from "./pxSizeToScaledSize.js"
|
|
60
65
|
export { resizeByEdge } from "./resizeByEdge.js"
|
|
66
|
+
export { rotateLayout } from "./rotateFrames.js"
|
|
61
67
|
export { scaledPointToPx } from "./scaledPointToPx.js"
|
|
62
68
|
export { sideToDirection } from "./sideToDirection.js"
|
|
63
69
|
export { sideToOrientation } from "./sideToOrientation.js"
|
|
@@ -67,3 +73,4 @@ export { toId } from "./toId.js"
|
|
|
67
73
|
export { toWindowCoord } from "./toWindowCoord.js"
|
|
68
74
|
export { unionEdges } from "./unionEdges.js"
|
|
69
75
|
export { updateWindowWithEvent } from "./updateWindowSizeWithEvent.js"
|
|
76
|
+
export { validateLayoutShape } from "./validateLayoutShape.js"
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { keys } from "@alanscodelog/utils/keys"
|
|
2
|
+
|
|
1
3
|
import type { LayoutFrame } from "../types/index.js"
|
|
2
4
|
|
|
5
|
+
const builtinProperties = ["id", "x", "y", "width", "height", "docked", "collapsed"]
|
|
3
6
|
export function debugFrame(frame: LayoutFrame): string {
|
|
4
7
|
const f = frame
|
|
5
|
-
|
|
8
|
+
const otherProperties = keys(f).filter(k => !builtinProperties.includes(k)).map(k => `${k}: ${f[k]}`)
|
|
9
|
+
const otherPropertiesString = otherProperties.length > 0 ? `, ${otherProperties.join(", ")}` : ""
|
|
10
|
+
return `id: ${f.id.slice(0, 4)}, x: ${f.x}, y: ${f.y}, w: ${f.width}, h: ${f.height}\ndocked: ${f.docked}, collapsed: ${f.collapsed}${otherPropertiesString}`
|
|
6
11
|
}
|
|
@@ -2,13 +2,10 @@ import { pushIfNotIn } from "@alanscodelog/utils/pushIfNotIn"
|
|
|
2
2
|
import { walk } from "@alanscodelog/utils/walk"
|
|
3
3
|
|
|
4
4
|
import { applyFrameChanges } from "./applyFrameChanges.js"
|
|
5
|
-
import {
|
|
5
|
+
import { getFrameShrinkInfo } from "./getFrameShrinkInfo.js"
|
|
6
6
|
|
|
7
|
-
import { framesRedistributeFix } from "../helpers/framesRedistributeFix.js"
|
|
8
|
-
import { getPinnedEdgesForCollapsedFrames } from "../helpers/getPinnedEdgesForCollapsedFrames.js"
|
|
9
|
-
import { oppositeSide } from "../helpers/oppositeSide.js"
|
|
10
7
|
import { settings } from "../settings.js"
|
|
11
|
-
import type {
|
|
8
|
+
import type { LayoutChange, LayoutWindow, Size } from "../types/index.js"
|
|
12
9
|
import { LAYOUT_ERROR } from "../types/index.js"
|
|
13
10
|
import { KnownError } from "../utils/KnownError.js"
|
|
14
11
|
|
|
@@ -32,7 +29,8 @@ export function getFrameCollapseInfo(
|
|
|
32
29
|
} = {}
|
|
33
30
|
): LayoutChange
|
|
34
31
|
| KnownError<typeof LAYOUT_ERROR.CANT_COLLAPSE_NOT_DOCKED>
|
|
35
|
-
| KnownError<typeof LAYOUT_ERROR.REDISTRIBUTE_WOULD_RESULT_IN_INVALID_FRAMES>
|
|
32
|
+
| KnownError<typeof LAYOUT_ERROR.REDISTRIBUTE_WOULD_RESULT_IN_INVALID_FRAMES>
|
|
33
|
+
| KnownError<typeof LAYOUT_ERROR.CANT_RESIZE_SINGLE_FRAME> {
|
|
36
34
|
collapseSizeScaled = collapseSizeScaled ?? settings.getCollapseSizeScaled(win)
|
|
37
35
|
win = walk(win, undefined, { save: true }) as typeof win
|
|
38
36
|
const frame = win.frames[frameId]
|
|
@@ -51,55 +49,28 @@ export function getFrameCollapseInfo(
|
|
|
51
49
|
|
|
52
50
|
const isVertical = frame.docked === "left" || frame.docked === "right"
|
|
53
51
|
const sizeKey = isVertical ? "width" : "height" as const
|
|
54
|
-
const posKey = isVertical ? "x" : "y"
|
|
55
|
-
const oppositePosKey = isVertical ? "y" : "x"
|
|
56
|
-
const oppositeSizeKey = isVertical ? "height" : "width"
|
|
57
|
-
|
|
58
52
|
|
|
59
53
|
const currentSize = frame[sizeKey]
|
|
60
|
-
const
|
|
61
|
-
const shrinkAmount = currentSize - collapseAmount
|
|
62
|
-
|
|
63
|
-
const dockedSide = frame.docked as EdgeSide
|
|
64
|
-
|
|
65
|
-
const { applyFixes, toFix } = framesRedistributeFix(win, frame, dockedSide, posKey, sizeKey, "shrink")
|
|
66
|
-
|
|
67
|
-
// note fully collapsed frames without an area are already excluded by getFramesRedistributeInfo
|
|
68
|
-
const otherFrameIds = Object.keys(win.frames).filter(id => id !== frameId)
|
|
69
|
-
|
|
70
|
-
const redistributeSide = oppositeSide(dockedSide)
|
|
54
|
+
const shrinkAmount = currentSize - collapseSizeScaled[sizeKey]
|
|
71
55
|
|
|
72
|
-
const
|
|
56
|
+
const shrinkResult = getFrameShrinkInfo(win, frameId, shrinkAmount, frame.docked, { allowOutOfBounds: true })
|
|
73
57
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (changes instanceof KnownError) {
|
|
77
|
-
// we should never get out of bounds and because this is a collapse there should always be space
|
|
78
|
-
if (changes.code === LAYOUT_ERROR.REDISTRIBUTE_OUT_OF_BOUNDS || changes.code === LAYOUT_ERROR.NO_SPACE_TO_REDISTRIBUTE) {
|
|
79
|
-
changes.message = `This error should never happen, please file a bug report: ${changes.message}`
|
|
80
|
-
throw changes
|
|
81
|
-
}
|
|
82
|
-
return changes
|
|
58
|
+
if (shrinkResult instanceof KnownError) {
|
|
59
|
+
return shrinkResult
|
|
83
60
|
}
|
|
84
|
-
applyFrameChanges(win, changes)
|
|
85
|
-
pushIfNotIn(toExtract, changes.modified.map(_ => _.id))
|
|
86
|
-
|
|
87
61
|
|
|
88
|
-
|
|
89
|
-
pushIfNotIn(toExtract,
|
|
62
|
+
applyFrameChanges(win, shrinkResult)
|
|
63
|
+
pushIfNotIn(toExtract, shrinkResult.modified.map(_ => _.id))
|
|
90
64
|
|
|
91
|
-
if (frame.docked === "right" || frame.docked === "bottom") {
|
|
92
|
-
frame[posKey] = frame[posKey] + (frame[sizeKey] - collapseSizeScaled[sizeKey])
|
|
93
|
-
}
|
|
94
|
-
frame[sizeKey] = collapseSizeScaled[sizeKey]
|
|
95
65
|
frame.collapsed = currentSize
|
|
96
66
|
// when we collapse to 0 it's a special case where the frame will always fit the entire window edge
|
|
97
67
|
// for proper uncollapsing later
|
|
98
68
|
if (collapseSizeScaled[sizeKey] === 0) {
|
|
69
|
+
const oppositePosKey = isVertical ? "y" : "x"
|
|
70
|
+
const oppositeSizeKey = isVertical ? "height" : "width"
|
|
99
71
|
frame[oppositePosKey] = 0
|
|
100
72
|
frame[oppositeSizeKey] = settings.maxInt
|
|
101
73
|
}
|
|
102
74
|
|
|
103
75
|
return { modified: toExtract.map(_ => win.frames[_]), created: [], deleted: [] }
|
|
104
76
|
}
|
|
105
|
-
|
|
@@ -3,10 +3,12 @@ import { walk } from "@alanscodelog/utils/walk"
|
|
|
3
3
|
|
|
4
4
|
import { applyFrameChanges } from "./applyFrameChanges.js"
|
|
5
5
|
import { getFillEmptySpaceInfo } from "./getFillEmptySpaceInfo.js"
|
|
6
|
+
import { getFrameShrinkInfo } from "./getFrameShrinkInfo.js"
|
|
6
7
|
import { getFramesRedistributeInfo } from "./getFramesRedistributeInfo.js"
|
|
7
8
|
import { getFrameUndockInfo } from "./getFrameUndockInfo.js"
|
|
8
9
|
|
|
9
10
|
import { cloneFrame } from "../helpers/cloneFrame.js"
|
|
11
|
+
import { findEdgesTouchingWindow } from "../helpers/findEdgesTouchingWindow.js"
|
|
10
12
|
import { getDockBoundaries } from "../helpers/getDockBoundaries.js"
|
|
11
13
|
import { getPinnedEdgesForCollapsedFrames } from "../helpers/getPinnedEdgesForCollapsedFrames.js"
|
|
12
14
|
import { oppositeSide } from "../helpers/oppositeSide.js"
|
|
@@ -33,7 +35,8 @@ export function getFrameDockInfo(
|
|
|
33
35
|
| KnownError<typeof LAYOUT_ERROR.CANT_LEAVE_NO_UNDOCKED_FRAMES>
|
|
34
36
|
| KnownError<typeof LAYOUT_ERROR.FRAME_ALREADY_DOCKED_ON_SIDE>
|
|
35
37
|
| KnownError<typeof LAYOUT_ERROR.CANT_UNDOCK_COLLAPSED_FRAME>
|
|
36
|
-
| KnownError<typeof LAYOUT_ERROR.NO_FILL_CANDIDATES>
|
|
38
|
+
| KnownError<typeof LAYOUT_ERROR.NO_FILL_CANDIDATES>
|
|
39
|
+
| KnownError<typeof LAYOUT_ERROR.CANT_RESIZE_SINGLE_FRAME> {
|
|
37
40
|
// its easier to just clone the window and extract changes later
|
|
38
41
|
// setting the var ensures we don't accidentally mutate the original
|
|
39
42
|
win = walk(win, undefined, { save: true }) as typeof win
|
|
@@ -57,8 +60,9 @@ export function getFrameDockInfo(
|
|
|
57
60
|
|
|
58
61
|
const isHorizontal = side === "left" || side === "right"
|
|
59
62
|
const perpendicular = isHorizontal ? "width" : "height"
|
|
63
|
+
const posKey = isHorizontal ? "x" : "y"
|
|
64
|
+
const sizeKey = isHorizontal ? "width" : "height"
|
|
60
65
|
|
|
61
|
-
const oldFrame = cloneFrame(frame)
|
|
62
66
|
const otherFrameIds = Object.keys(win.frames).filter(_ => _ !== frameId)
|
|
63
67
|
const nonDockedFrameIds = otherFrameIds.filter(id => !win.frames[id].docked)
|
|
64
68
|
if (nonDockedFrameIds.length === 0) {
|
|
@@ -66,68 +70,84 @@ export function getFrameDockInfo(
|
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
// if its the only frame allow it to be as big as it likes
|
|
69
|
-
const
|
|
70
|
-
const perpendicularLength = otherFrameIds.length > 0 ? Math.min(frame[perpendicular],
|
|
73
|
+
const maxPerpendicular = maxPerpendicularLength ?? settings.maxPerpendicularLengthScaled.width
|
|
74
|
+
const perpendicularLength = otherFrameIds.length > 0 ? Math.min(frame[perpendicular], maxPerpendicular) : frame[perpendicular]
|
|
71
75
|
|
|
72
76
|
frame.docked = side
|
|
73
77
|
frame.collapsed = undefined
|
|
74
78
|
|
|
75
79
|
const toExtract = [frame.id]
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (changes instanceof Error) return changes
|
|
80
|
-
applyFrameChanges(win, changes)
|
|
81
|
-
pushIfNotIn(toExtract, changes.modified.map(_ => _.id))
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// redistribute other non-docked frames to make room for the new dock.
|
|
85
|
-
const sideToPushTowards = oppositeSide(side)
|
|
86
|
-
|
|
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 })
|
|
93
|
-
|
|
94
|
-
if (redistributeChanges instanceof KnownError) {
|
|
95
|
-
return redistributeChanges
|
|
81
|
+
if (frame[perpendicular] === perpendicularLength) {
|
|
82
|
+
return { modified: toExtract.map(_ => win.frames[_]), created: [], deleted: [] }
|
|
96
83
|
}
|
|
97
|
-
applyFrameChanges(win, redistributeChanges)
|
|
98
|
-
pushIfNotIn(toExtract, redistributeChanges.modified.map(_ => _.id))
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const { minX, maxX, minY, maxY } = getDockBoundaries(win)
|
|
102
|
-
switch (side) {
|
|
103
|
-
case "left":
|
|
104
|
-
frame.x = 0
|
|
105
|
-
frame.y = minY
|
|
106
|
-
frame.width = perpendicularLength
|
|
107
|
-
frame.height = maxY - minY
|
|
108
|
-
break
|
|
109
|
-
case "right":
|
|
110
|
-
frame.x = settings.maxInt - perpendicularLength
|
|
111
|
-
frame.y = minY
|
|
112
|
-
frame.width = perpendicularLength
|
|
113
|
-
frame.height = maxY - minY
|
|
114
|
-
break
|
|
115
|
-
case "top":
|
|
116
|
-
frame.x = minX
|
|
117
|
-
frame.y = 0
|
|
118
|
-
frame.width = maxX - minX
|
|
119
|
-
frame.height = perpendicularLength
|
|
120
|
-
break
|
|
121
|
-
case "bottom":
|
|
122
|
-
frame.x = minX
|
|
123
|
-
frame.y = settings.maxInt - perpendicularLength
|
|
124
|
-
frame.width = maxX - minX
|
|
125
|
-
frame.height = perpendicularLength
|
|
126
|
-
break
|
|
127
|
-
}
|
|
128
|
-
|
|
129
84
|
|
|
130
|
-
|
|
85
|
+
// check if the frame touches the full edge it will be docked to (not just it's dock boundary)
|
|
86
|
+
// if so, we can use getFrameShrinkInfo instead of fill-empty-space + redistribute
|
|
87
|
+
// this feels better as otherwise, the other frames feel like they are unnecessarily moved to the other side
|
|
88
|
+
// this way where the frame *was* feels like it's taken into account
|
|
89
|
+
const touchesEntireEdge = findEdgesTouchingWindow(frame)[side] === "full"
|
|
90
|
+
|
|
91
|
+
if (touchesEntireEdge) {
|
|
92
|
+
const shrinkAmount = frame[sizeKey] - perpendicularLength
|
|
93
|
+
if (shrinkAmount > 0) {
|
|
94
|
+
const shrinkResult = getFrameShrinkInfo(win, frameId, shrinkAmount, side)
|
|
95
|
+
if (shrinkResult instanceof KnownError) {
|
|
96
|
+
return shrinkResult
|
|
97
|
+
}
|
|
98
|
+
applyFrameChanges(win, shrinkResult)
|
|
99
|
+
pushIfNotIn(toExtract, shrinkResult.modified.map(_ => _.id))
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
const oldFrame = cloneFrame(frame)
|
|
103
|
+
|
|
104
|
+
// fills just the hole left by the frame when it was moved
|
|
105
|
+
const changes = getFillEmptySpaceInfo(win, oldFrame, [], [frameId])
|
|
106
|
+
if (changes instanceof Error) return changes
|
|
107
|
+
applyFrameChanges(win, changes)
|
|
108
|
+
pushIfNotIn(toExtract, changes.modified.map(_ => _.id))
|
|
109
|
+
|
|
110
|
+
// redistribute other non-docked frames to make room for the new dock.
|
|
111
|
+
const sideToPushTowards = oppositeSide(side)
|
|
112
|
+
|
|
113
|
+
const pinnedEdgeCoordinates: number[] = getPinnedEdgesForCollapsedFrames(win, frame, side, posKey, sizeKey)
|
|
114
|
+
|
|
115
|
+
const redistributeChanges = getFramesRedistributeInfo(win, sideToPushTowards, nonDockedFrameIds, perpendicularLength, { pinnedEdgeCoordinates })
|
|
116
|
+
|
|
117
|
+
if (redistributeChanges instanceof KnownError) {
|
|
118
|
+
return redistributeChanges
|
|
119
|
+
}
|
|
120
|
+
applyFrameChanges(win, redistributeChanges)
|
|
121
|
+
pushIfNotIn(toExtract, redistributeChanges.modified.map(_ => _.id))
|
|
122
|
+
|
|
123
|
+
const { minX, maxX, minY, maxY } = getDockBoundaries(win)
|
|
124
|
+
switch (side) {
|
|
125
|
+
case "left":
|
|
126
|
+
frame.x = 0
|
|
127
|
+
frame.y = minY
|
|
128
|
+
frame.width = perpendicularLength
|
|
129
|
+
frame.height = maxY - minY
|
|
130
|
+
break
|
|
131
|
+
case "right":
|
|
132
|
+
frame.x = settings.maxInt - perpendicularLength
|
|
133
|
+
frame.y = minY
|
|
134
|
+
frame.width = perpendicularLength
|
|
135
|
+
frame.height = maxY - minY
|
|
136
|
+
break
|
|
137
|
+
case "top":
|
|
138
|
+
frame.x = minX
|
|
139
|
+
frame.y = 0
|
|
140
|
+
frame.width = maxX - minX
|
|
141
|
+
frame.height = perpendicularLength
|
|
142
|
+
break
|
|
143
|
+
case "bottom":
|
|
144
|
+
frame.x = minX
|
|
145
|
+
frame.y = settings.maxInt - perpendicularLength
|
|
146
|
+
frame.width = maxX - minX
|
|
147
|
+
frame.height = perpendicularLength
|
|
148
|
+
break
|
|
149
|
+
}
|
|
150
|
+
}
|
|
131
151
|
|
|
132
|
-
return { modified:
|
|
152
|
+
return { modified: toExtract.map(_ => win.frames[_]), created: [], deleted: [] }
|
|
133
153
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { pushIfNotIn } from "@alanscodelog/utils/pushIfNotIn"
|
|
2
|
+
import { walk } from "@alanscodelog/utils/walk"
|
|
3
|
+
|
|
4
|
+
import { applyFrameChanges } from "./applyFrameChanges.js"
|
|
5
|
+
import { getFramesRedistributeInfo } from "./getFramesRedistributeInfo.js"
|
|
6
|
+
|
|
7
|
+
import { findEdgesTouchingWindow } from "../helpers/findEdgesTouchingWindow.js"
|
|
8
|
+
import { framesRedistributeFix } from "../helpers/framesRedistributeFix.js"
|
|
9
|
+
import { getPinnedEdgesForCollapsedFrames } from "../helpers/getPinnedEdgesForCollapsedFrames.js"
|
|
10
|
+
import { oppositeSide } from "../helpers/oppositeSide.js"
|
|
11
|
+
import type { EdgeSide, LayoutChange, LayoutWindow } from "../types/index.js"
|
|
12
|
+
import { LAYOUT_ERROR } from "../types/index.js"
|
|
13
|
+
import { KnownError } from "../utils/KnownError.js"
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Returns a {@link LayoutChange} with the information necessary to expand a frame touching a window edge by a given amount.
|
|
17
|
+
*
|
|
18
|
+
* Changes can be applied to a window with {@link applyFrameChanges}.
|
|
19
|
+
*
|
|
20
|
+
* It expands the frame by redistributing space from neighboring frames.
|
|
21
|
+
*/
|
|
22
|
+
export function getFrameExpandInfo(
|
|
23
|
+
win: LayoutWindow,
|
|
24
|
+
frameId: string,
|
|
25
|
+
amount: number,
|
|
26
|
+
expandSide: EdgeSide
|
|
27
|
+
): LayoutChange
|
|
28
|
+
| KnownError<typeof LAYOUT_ERROR.REDISTRIBUTE_OUT_OF_BOUNDS>
|
|
29
|
+
| KnownError<typeof LAYOUT_ERROR.NO_SPACE_TO_REDISTRIBUTE>
|
|
30
|
+
| KnownError<typeof LAYOUT_ERROR.REDISTRIBUTE_WOULD_RESULT_IN_INVALID_FRAMES>
|
|
31
|
+
| KnownError<typeof LAYOUT_ERROR.CANT_RESIZE_SINGLE_FRAME> {
|
|
32
|
+
win = walk(win, undefined, { save: true })
|
|
33
|
+
const frame = win.frames[frameId]
|
|
34
|
+
if (!frame) { throw new Error(`Unknown frame ${frameId}`) }
|
|
35
|
+
if (!frame.docked) { throw new Error(`Frame ${frameId} is not docked.`) }
|
|
36
|
+
const toExtract = [frame.id]
|
|
37
|
+
|
|
38
|
+
if (Object.keys(win.frames).length === 1) {
|
|
39
|
+
return new KnownError(LAYOUT_ERROR.CANT_RESIZE_SINGLE_FRAME, `Frame ${frameId} is the only frame, cannot expand.`, { frame })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// verify the frame actually touches the specified edge
|
|
43
|
+
if (!(expandSide in findEdgesTouchingWindow(frame))) {
|
|
44
|
+
throw new Error(`Frame ${frameId} does not touch the ${expandSide} edge.`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
const isVertical = frame.docked === "left" || frame.docked === "right"
|
|
49
|
+
const sizeKey = isVertical ? "width" : "height" as const
|
|
50
|
+
const posKey = isVertical ? "x" : "y"
|
|
51
|
+
const currentSize = frame[sizeKey]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
const dockedSide = frame.docked as EdgeSide
|
|
55
|
+
|
|
56
|
+
const { applyFixes, toFix } = framesRedistributeFix(win, frame, dockedSide, posKey, sizeKey, "expand")
|
|
57
|
+
|
|
58
|
+
// note fully collapsed frames without an area are already excluded by getFramesRedistributeInfo
|
|
59
|
+
const otherFrameIds = Object.keys(win.frames).filter(id => id !== frameId)
|
|
60
|
+
|
|
61
|
+
const redistributeSide = oppositeSide(frame.docked)
|
|
62
|
+
|
|
63
|
+
const pinnedEdgeCoordinates: number[] = getPinnedEdgesForCollapsedFrames(win, frame, dockedSide, posKey, sizeKey)
|
|
64
|
+
|
|
65
|
+
const changes = getFramesRedistributeInfo(win, redistributeSide, otherFrameIds, amount, { pinnedEdgeCoordinates })
|
|
66
|
+
|
|
67
|
+
if (changes instanceof KnownError) {
|
|
68
|
+
return changes
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
applyFrameChanges(win, changes)
|
|
72
|
+
pushIfNotIn(toExtract, changes.modified.map(_ => _.id))
|
|
73
|
+
|
|
74
|
+
applyFixes()
|
|
75
|
+
pushIfNotIn(toExtract, toFix)
|
|
76
|
+
|
|
77
|
+
frame[sizeKey] = currentSize + amount
|
|
78
|
+
|
|
79
|
+
if (frame.docked === "right" || frame.docked === "bottom") {
|
|
80
|
+
frame[posKey] -= amount
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { modified: toExtract.map(_ => win.frames[_]), created: [], deleted: [] }
|
|
84
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { pushIfNotIn } from "@alanscodelog/utils/pushIfNotIn"
|
|
2
|
+
import { walk } from "@alanscodelog/utils/walk"
|
|
3
|
+
|
|
4
|
+
import { applyFrameChanges } from "./applyFrameChanges.js"
|
|
5
|
+
import { getFramesRedistributeInfo } from "./getFramesRedistributeInfo.js"
|
|
6
|
+
|
|
7
|
+
import { findEdgesTouchingWindow } from "../helpers/findEdgesTouchingWindow.js"
|
|
8
|
+
import { framesRedistributeFix } from "../helpers/framesRedistributeFix.js"
|
|
9
|
+
import { getPinnedEdgesForCollapsedFrames } from "../helpers/getPinnedEdgesForCollapsedFrames.js"
|
|
10
|
+
import { oppositeSide } from "../helpers/oppositeSide.js"
|
|
11
|
+
import type { EdgeSide, LayoutChange, LayoutWindow } from "../types/index.js"
|
|
12
|
+
import { LAYOUT_ERROR } from "../types/index.js"
|
|
13
|
+
import { KnownError } from "../utils/KnownError.js"
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Returns a {@link LayoutChange} with the information necessary to shrink a frame touching a window edge by a given amount.
|
|
17
|
+
*
|
|
18
|
+
* Changes can be applied to a window with {@link applyFrameChanges}.
|
|
19
|
+
*
|
|
20
|
+
* It shrinks the frame by redistributing the freed space to neighboring frames.
|
|
21
|
+
*/
|
|
22
|
+
export function getFrameShrinkInfo(
|
|
23
|
+
win: LayoutWindow,
|
|
24
|
+
frameId: string,
|
|
25
|
+
amount: number,
|
|
26
|
+
shrinkSide: EdgeSide,
|
|
27
|
+
{
|
|
28
|
+
allowOutOfBounds = false
|
|
29
|
+
}: {
|
|
30
|
+
/** Allow the resize span to exceed window bounds. */
|
|
31
|
+
allowOutOfBounds?: boolean
|
|
32
|
+
} = {}
|
|
33
|
+
): LayoutChange
|
|
34
|
+
| KnownError<typeof LAYOUT_ERROR.REDISTRIBUTE_WOULD_RESULT_IN_INVALID_FRAMES>
|
|
35
|
+
| KnownError<typeof LAYOUT_ERROR.CANT_RESIZE_SINGLE_FRAME> {
|
|
36
|
+
win = walk(win, undefined, { save: true }) as typeof win
|
|
37
|
+
const frame = win.frames[frameId]
|
|
38
|
+
if (!frame) throw new Error(`Unknown frame ${frameId}`)
|
|
39
|
+
|
|
40
|
+
if (Object.keys(win.frames).length === 1) {
|
|
41
|
+
return new KnownError(LAYOUT_ERROR.CANT_RESIZE_SINGLE_FRAME, `Frame ${frameId} is the only frame, cannot shrink.`, { frame })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// verify the frame actually touches the specified edge
|
|
45
|
+
if (!(shrinkSide in findEdgesTouchingWindow(frame))) {
|
|
46
|
+
throw new Error(`Frame ${frameId} does not touch the ${shrinkSide} edge.`)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const isVertical = shrinkSide === "left" || shrinkSide === "right"
|
|
50
|
+
const sizeKey = isVertical ? "width" : "height" as const
|
|
51
|
+
const posKey = isVertical ? "x" : "y"
|
|
52
|
+
|
|
53
|
+
const toExtract = [frame.id]
|
|
54
|
+
|
|
55
|
+
const { applyFixes, toFix } = framesRedistributeFix(win, frame, shrinkSide, posKey, sizeKey, "shrink")
|
|
56
|
+
|
|
57
|
+
// note fully collapsed frames without an area are already excluded by getFramesRedistributeInfo
|
|
58
|
+
const otherFrameIds = Object.keys(win.frames).filter(id => id !== frameId)
|
|
59
|
+
|
|
60
|
+
const redistributeSide = oppositeSide(shrinkSide)
|
|
61
|
+
|
|
62
|
+
const pinnedEdgeCoordinates: number[] = getPinnedEdgesForCollapsedFrames(win, frame, shrinkSide, posKey, sizeKey)
|
|
63
|
+
|
|
64
|
+
const changes = getFramesRedistributeInfo(win, redistributeSide, otherFrameIds, -amount, { allowOutOfBounds, pinnedEdgeCoordinates })
|
|
65
|
+
|
|
66
|
+
if (changes instanceof KnownError) {
|
|
67
|
+
// we should never get out of bounds and because this is a shrink there should always be space
|
|
68
|
+
if (changes.code === LAYOUT_ERROR.REDISTRIBUTE_OUT_OF_BOUNDS || changes.code === LAYOUT_ERROR.NO_SPACE_TO_REDISTRIBUTE) {
|
|
69
|
+
changes.message = `This error should never happen, please file a bug report: ${changes.message}`
|
|
70
|
+
throw changes
|
|
71
|
+
}
|
|
72
|
+
return changes
|
|
73
|
+
}
|
|
74
|
+
applyFrameChanges(win, changes)
|
|
75
|
+
pushIfNotIn(toExtract, changes.modified.map(_ => _.id))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
applyFixes()
|
|
79
|
+
pushIfNotIn(toExtract, toFix)
|
|
80
|
+
|
|
81
|
+
if (shrinkSide === "right" || shrinkSide === "bottom") {
|
|
82
|
+
frame[posKey] = frame[posKey] + amount
|
|
83
|
+
}
|
|
84
|
+
frame[sizeKey] = frame[sizeKey] - amount
|
|
85
|
+
|
|
86
|
+
return { modified: toExtract.map(_ => win.frames[_]), created: [], deleted: [] }
|
|
87
|
+
}
|