Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Floating Grid Layout #1604

Open
wants to merge 4 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 5 additions & 20 deletions Amethyst.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
4493EAA22139D9F000AA9623 /* ThreeColumnLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4493EAA12139D9EF00AA9623 /* ThreeColumnLayout.swift */; };
AA4AF40D26717DA900D2AE1B /* TwoPaneLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4AF40C26717DA900D2AE1B /* TwoPaneLayout.swift */; };
AAAC6BAC2677DF7B00BEC1B0 /* TwoPaneLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAAC6BAB2677DF7B00BEC1B0 /* TwoPaneLayoutTests.swift */; };
D60A69A02AF115A500EE0235 /* FloatingGridLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60A699F2AF115A500EE0235 /* FloatingGridLayout.swift */; };
F46629C4272AD7A30040C275 /* FourColumnLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46629C3272AD7A30040C275 /* FourColumnLayout.swift */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -251,6 +252,7 @@
4493EAA12139D9EF00AA9623 /* ThreeColumnLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreeColumnLayout.swift; sourceTree = "<group>"; };
AA4AF40C26717DA900D2AE1B /* TwoPaneLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoPaneLayout.swift; sourceTree = "<group>"; };
AAAC6BAB2677DF7B00BEC1B0 /* TwoPaneLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoPaneLayoutTests.swift; sourceTree = "<group>"; };
D60A699F2AF115A500EE0235 /* FloatingGridLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingGridLayout.swift; sourceTree = "<group>"; };
F46629C3272AD7A30040C275 /* FourColumnLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FourColumnLayout.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -358,6 +360,7 @@
4062AD321C1FA48C00DB612B /* ColumnLayout.swift */,
4062AD2A1C1F99EA00DB612B /* FloatingLayout.swift */,
4062AD2C1C1F9B8B00DB612B /* FullscreenLayout.swift */,
D60A699F2AF115A500EE0235 /* FloatingGridLayout.swift */,
4062AD361C1FA83300DB612B /* RowLayout.swift */,
4062AD2E1C1F9D6B00DB612B /* TallLayout.swift */,
AA4AF40C26717DA900D2AE1B /* TwoPaneLayout.swift */,
Expand Down Expand Up @@ -599,7 +602,6 @@
isa = PBXNativeTarget;
buildConfigurationList = 402DB6FB1742E41A00D1C936 /* Build configuration list for PBXNativeTarget "Amethyst" */;
buildPhases = (
4058C4701C4C4F9E00B19D26 /* Lint */,
402DB6DA1742E41A00D1C936 /* Sources */,
402DB6DB1742E41A00D1C936 /* Frameworks */,
402DB6DC1742E41A00D1C936 /* Resources */,
Expand Down Expand Up @@ -743,24 +745,6 @@
};
/* End PBXResourcesBuildPhase section */

/* Begin PBXShellScriptBuildPhase section */
4058C4701C4C4F9E00B19D26 /* Lint */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = Lint;
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\nswiftlint --fix\nswiftlint\n";
};
/* End PBXShellScriptBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
402DB6DA1742E41A00D1C936 /* Sources */ = {
isa = PBXSourcesBuildPhase;
Expand Down Expand Up @@ -822,6 +806,7 @@
40637075224EFED70081299D /* CGInfo.swift in Sources */,
1A4B46EB20AA7717003D5110 /* NSTableView+Amethyst.swift in Sources */,
4045416F268FFDA000861BE8 /* CustomLayout.swift in Sources */,
D60A69A02AF115A500EE0235 /* FloatingGridLayout.swift in Sources */,
40637077224F0EFF0081299D /* Screens.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -1055,7 +1040,7 @@
CURRENT_PROJECT_VERSION = 108;
DEAD_CODE_STRIPPING = YES;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 82P2XLB4UH;
DEVELOPMENT_TEAM = MAWRUVWLA5;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_MODULE_VERIFIER = YES;
FRAMEWORK_SEARCH_PATHS = (
Expand Down
114 changes: 114 additions & 0 deletions Amethyst/Layout/Layouts/FloatingGridLayout.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//
// FloatingGridLayout.swift
// Amethyst
//
// Created by James Froggatt on 31/10/23.
// Copyright © 2015 Ian Ynda-Hummel. All rights reserved.
//

import Silica

class FloatingGridLayout<Window: WindowType>: Layout<Window> {
override static var layoutName: String { return "Floating Grid" }
override static var layoutKey: String { return "floating-grid" }

override var layoutDescription: String { return "" }

override func frameAssignments(_ windowSet: WindowSet<Window>, on screen: Screen) -> [FrameAssignmentOperation<Window>]? {
let screenFrame = screen.adjustedFrame()
let smartWindowMargins = UserConfiguration.shared.smartWindowMargins()
let windowMarginSize = UserConfiguration.shared.windowMarginSize()
let gridGuides = (
horizontal: self.gridGuides(in: screenFrame.width, from: screenFrame.minX),
vertical: self.gridGuides(in: screenFrame.height, from: screenFrame.minY)
)
return windowSet.windows.map { window in
let frameInset: CGFloat = windowMarginSize/2
var frame = window.frame.insetBy(dx: -frameInset, dy: -frameInset)
// Limit windows to screen bounds
// 1. Pull window up left onto screen
if frame.maxX > screenFrame.maxX {
frame.origin.x += screenFrame.maxX - frame.maxX
}
if frame.maxY > screenFrame.maxY {
frame.origin.y += screenFrame.maxY - frame.maxY
}
// 2. Push window down right onto screen
if frame.minX < screenFrame.minX {
frame.origin.x = screenFrame.minX
}
if frame.minY < screenFrame.minY {
frame.origin.y = screenFrame.minY
}
// 3. Trim window's bottom right
if frame.maxX > screenFrame.maxX {
frame.size.width += screenFrame.maxX - frame.maxX
}
if frame.maxY > screenFrame.maxY {
frame.origin.y += screenFrame.maxY - frame.maxY
}
// Align to grid
let oldX = frame.minX
let oldY = frame.minY
frame.origin.x = match(frame.minX, to: gridGuides.horizontal, leadingEdge: true)
frame.origin.y = match(frame.minY, to: gridGuides.vertical, leadingEdge: true)
frame.size.width += oldX - frame.origin.x
frame.size.height += oldY - frame.origin.y
frame.size.width = match(frame.maxX, to: gridGuides.horizontal, leadingEdge: false) - frame.minX
frame.size.height = match(frame.maxY, to: gridGuides.vertical, leadingEdge: false) - frame.minY

let isFullscreen = smartWindowMargins && frame.union(screenFrame) == frame

let resizeRules = ResizeRules(isMain: true, unconstrainedDimension: .horizontal, scaleFactor: 1)
let frameAssignment = FrameAssignment<Window>(
frame: isFullscreen ? screen.adjustedFrame(disableWindowMargins: true) : frame,
window: window,
screenFrame: isFullscreen ? screen.adjustedFrame(disableWindowMargins: true) : screenFrame,
resizeRules: resizeRules,
disableWindowMargins: isFullscreen
)
return FrameAssignmentOperation(frameAssignment: frameAssignment, windowSet: windowSet)
}
}

func gridGuides(in dimension: CGFloat, from origin: CGFloat) -> [CGFloat] {
let baseGridSize: CGFloat = 64
let points = Int(dimension / baseGridSize)
let gridItemSize = dimension / CGFloat(points)
return (0...points).map { CGFloat($0) * gridItemSize + origin }
}

func match(_ value: CGFloat, to guides: [CGFloat], leadingEdge: Bool) -> CGFloat {
var guides = guides
// Prevent offscreen snapping
if leadingEdge {
guides.removeLast()
} else {
guides.removeFirst()
}
// Make it easier to snap to screen edges and avoid stage manager sidebar
if guides.indices.count > 2 {
guides.remove(at: 1)
guides.remove(at: guides.endIndex-2)
}
// Find closest snap guide
let index = guides.firstIndex(where: { $0 >= value }) ?? guides.indices.last!
let previous = guides.indices.contains(index-1) ? guides[index-1] : guides.first!
let next = guides.indices.contains(index) ? guides[index] : guides.last!
if (value - previous) > (next - value) {
return next
} else {
return previous
}
}
}

// To ensure updates when resizing
extension FloatingGridLayout: PanedLayout {
var mainPaneRatio: CGFloat { 0.5 }
var mainPaneCount: Int { 1 }
func recommendMainPaneRawRatio(rawRatio: CGFloat) {}
func increaseMainPaneCount() {}
func decreaseMainPaneCount() {}

}
8 changes: 8 additions & 0 deletions Amethyst/Managers/LayoutType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ enum LayoutType<Window: WindowType> {
case unknownLayout
}

case floatingGrid
case tall
case tallRight
case wide
Expand All @@ -63,6 +64,7 @@ enum LayoutType<Window: WindowType> {

static var standardLayouts: [LayoutType<Window>] {
return [
.floatingGrid,
.tall,
.tallRight,
.wide,
Expand All @@ -84,6 +86,8 @@ enum LayoutType<Window: WindowType> {

var key: String {
switch self {
case .floatingGrid:
return "floating-grid"
case .tall:
return "tall"
case .tallRight:
Expand Down Expand Up @@ -123,6 +127,8 @@ enum LayoutType<Window: WindowType> {

var layoutClass: Layout<Window>.Type {
switch self {
case .floatingGrid:
return FloatingGridLayout<Window>.self
case .tall:
return TallLayout<Window>.self
case .tallRight:
Expand Down Expand Up @@ -162,6 +168,8 @@ enum LayoutType<Window: WindowType> {

static func from(key: String) -> LayoutType<Window> {
switch key {
case "floating-grid":
return .floatingGrid
case "tall":
return .tall
case "tall-right":
Expand Down
1 change: 1 addition & 0 deletions Amethyst/Managers/WindowManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,7 @@ extension WindowManager: WindowTransitionTarget {
switch transition {
case let .switchWindows(window, otherWindow):
guard windows.swap(window: window, withWindow: otherWindow) else {
markAllScreensForReflow(withChange: .focusChanged(window: window))
return
}

Expand Down