Skip to content

Commit

Permalink
fix: layout is now correct; also removed layout preferences for now
Browse files Browse the repository at this point in the history
  • Loading branch information
louis.pontoise authored and lwouis committed Mar 10, 2020
1 parent bb95e55 commit a1b5266
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 143 deletions.
6 changes: 0 additions & 6 deletions alt-tab-macos/api-wrappers/CGWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,6 @@ extension CGWindow {
return layer() == 0
}

// workaround: some apps like chrome use a window to implement the search popover
func isReasonablyBig() -> Bool {
let windowBounds = CGRect(dictionaryRepresentation: bounds()!)!
return windowBounds.width > Preferences.minimumWindowSize && windowBounds.height > Preferences.minimumWindowSize
}

func id() -> CGWindowID? {
return value(kCGWindowNumber, CGWindowID.self)
}
Expand Down
71 changes: 30 additions & 41 deletions alt-tab-macos/logic/Preferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,35 @@ import Cocoa
import Carbon.HIToolbox.Events

class Preferences {
// the following constant are not exposed as preferences but may be in the future, probably through macro preferences
static let windowMaterial = NSVisualEffectView.Material.dark
static let iconSize = CGFloat(32)
static let fontColor = NSColor.white
static let fontHeight = CGFloat(15)
static let font = NSFont.systemFont(ofSize: fontHeight)
static let windowPadding = CGFloat(23)
static let cellPadding = CGFloat(5)
static let fontIconSize = CGFloat(20)
static let maxScreenUsage = CGFloat(0.8)
static let minCellsPerRow = CGFloat(4)
static let maxCellsPerRow = CGFloat(6)
static let nCellsRows = CGFloat(4)

static let themeMacro = MacroPreferenceHelper<(CGFloat, CGFloat, CGFloat, NSColor, NSColor)>([
MacroPreference(" macOS", (0, 5, 20, .clear, NSColor(red: 0, green: 0, blue: 0, alpha: 0.3))),
MacroPreference("❖ Windows 10", (2, 0, 0, .white, .clear))
])
static let metaKeyMacro = MacroPreferenceHelper<([Int], NSEvent.ModifierFlags)>([
MacroPreference("⌥ option", ([kVK_Option, kVK_RightOption], .option)),
MacroPreference("⌃ control", ([kVK_Control, kVK_RightControl], .control)),
MacroPreference("⌘ command", ([kVK_Command, kVK_RightCommand], .command))
])
static let showOnScreenMacro = MacroPreferenceHelper<ShowOnScreenPreference>([
MacroPreference("Main screen", ShowOnScreenPreference.MAIN),
MacroPreference("Screen including mouse", ShowOnScreenPreference.MOUSE),
])

static var defaults: [String: String] = [
"maxScreenUsage": "80",
"maxThumbnailsPerRow": "4",
"iconSize": "32",
"fontHeight": "15",
"tabKeyCode": String(kVK_Tab),
"metaKey": metaKeyMacro.macros[0].label,
"windowDisplayDelay": "0",
Expand All @@ -16,44 +40,18 @@ class Preferences {
"hideSpaceNumberLabels": String(false)
]
static var rawValues = [String: String]()
static var minimumWindowSize = CGFloat(200)
static var emptyThumbnailWidth = CGFloat(200)
static var emptyThumbnailHeight = CGFloat(emptyThumbnailWidth * 9 / 16)
static var fontColor = NSColor.white
static var windowMaterial = NSVisualEffectView.Material.dark
static var windowPadding = CGFloat(23)
static var interItemPadding = CGFloat(4)
static var fontIconSize = CGFloat(20)
static var cellPadding = CGFloat(6)

static var cellBorderWidth: CGFloat?
static var cellCornerRadius: CGFloat?
static var maxScreenUsage: CGFloat?
static var maxThumbnailsPerRow: CGFloat?
static var iconSize: CGFloat?
static var fontHeight: CGFloat?
static var tabKeyCode: UInt16?
static var highlightBorderColor: NSColor?
static var highlightBackgroundColor: NSColor?
static var metaKeyCodes: [UInt16]?
static var metaModifierFlag: NSEvent.ModifierFlags?
static var windowDisplayDelay: DispatchTimeInterval?
static var windowCornerRadius: CGFloat?
static var font: NSFont?
static var showOnScreen: ShowOnScreenPreference?
static var hideSpaceNumberLabels = false
static var themeMacro = MacroPreferenceHelper<(CGFloat, CGFloat, CGFloat, NSColor, NSColor)>([
MacroPreference(" macOS", (0, 5, 20, .clear, NSColor(red: 0, green: 0, blue: 0, alpha: 0.3))),
MacroPreference("❖ Windows 10", (2, 0, 0, .white, .clear))
])
static var metaKeyMacro = MacroPreferenceHelper<([Int], NSEvent.ModifierFlags)>([
MacroPreference("⌥ option", ([kVK_Option, kVK_RightOption], .option)),
MacroPreference("⌃ control", ([kVK_Control, kVK_RightControl], .control)),
MacroPreference("⌘ command", ([kVK_Command, kVK_RightCommand], .command))
])
static var showOnScreenMacro = MacroPreferenceHelper<ShowOnScreenPreference>([
MacroPreference("Main screen", ShowOnScreenPreference.MAIN),
MacroPreference("Screen including mouse", ShowOnScreenPreference.MOUSE),
])
static var hideSpaceNumberLabels: Bool?

private static let defaultsFile = fileFromPreferencesFolder("alt-tab-macos-defaults.json")
private static let userFile = fileFromPreferencesFolder("alt-tab-macos.json")
Expand Down Expand Up @@ -85,15 +83,6 @@ class Preferences {

static func updateAndValidateFromString(_ valueName: String, _ value: String) throws {
switch valueName {
case "maxScreenUsage":
maxScreenUsage = try CGFloat(CGFloat(value).orThrow() / 100)
case "maxThumbnailsPerRow":
maxThumbnailsPerRow = try CGFloat(value).orThrow()
case "iconSize":
iconSize = try CGFloat(value).orThrow()
case "fontHeight":
fontHeight = try CGFloat(value).orThrow()
font = NSFont.systemFont(ofSize: fontHeight!)
case "tabKeyCode":
tabKeyCode = try UInt16(value).orThrow()
case "metaKey":
Expand Down
12 changes: 0 additions & 12 deletions alt-tab-macos/logic/Screen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,6 @@ class Screen {
return NSScreen.screens.first { NSMouseInRect(NSEvent.mouseLocation, $0.frame, false) }
}

static func thumbnailMaxSize(_ screen: NSScreen) -> NSSize {
let frame = screen.visibleFrame
let width = (frame.width * Preferences.maxScreenUsage! - Preferences.windowPadding * 2) / Preferences.maxThumbnailsPerRow! - Preferences.interItemPadding
let height = width * (frame.height / frame.width)
return NSSize(width: width, height: height)
}

static func thumbnailPanelMaxSize(_ screen: NSScreen) -> NSSize {
let frame = screen.visibleFrame
return NSSize(width: frame.width * Preferences.maxScreenUsage!, height: frame.height * Preferences.maxScreenUsage!)
}

static func repositionPanel(_ window: NSWindow, _ screen: NSScreen, _ alignment: VerticalAlignment) {
let screenFrame = screen.visibleFrame
let panelFrame = window.frame
Expand Down
78 changes: 51 additions & 27 deletions alt-tab-macos/ui/Cell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ typealias MouseMovedCallback = (Cell) -> Void
class Cell: NSCollectionViewItem {
var thumbnail = NSImageView()
var appIcon = NSImageView()
var label = CellTitle(Preferences.fontHeight!)
var label = CellTitle(Preferences.fontHeight)
var minimizedIcon = FontIcon(FontIcon.sfSymbolCircledMinusSign, Preferences.fontIconSize, .white)
var hiddenIcon = FontIcon(FontIcon.sfSymbolCircledDotSign, Preferences.fontIconSize, .white)
var spaceIcon = FontIcon(FontIcon.sfSymbolCircledNumber0, Preferences.fontIconSize, .white)
Expand All @@ -20,7 +20,13 @@ class Cell: NSCollectionViewItem {
let vStackView = makeVStackView(hStackView)
let shadow = Cell.makeShadow(.gray)
thumbnail.shadow = shadow
thumbnail.wantsLayer = true
thumbnail.layer!.backgroundColor = NSColor.blue.cgColor
appIcon.shadow = shadow
appIcon.wantsLayer = true
appIcon.layer!.backgroundColor = NSColor.green.cgColor
vStackView.wantsLayer = true
vStackView.layer!.backgroundColor = NSColor.gray.cgColor
view = vStackView
}

Expand All @@ -39,30 +45,31 @@ class Cell: NSCollectionViewItem {
}
}

func updateWithNewContent(_ element: Window, _ mouseDownCallback: @escaping MouseDownCallback, _ mouseMovedCallback: @escaping MouseMovedCallback, _ screen: NSScreen) {
func updateRecycledCellWithNewContent(_ element: Window, _ mouseDownCallback: @escaping MouseDownCallback, _ mouseMovedCallback: @escaping MouseMovedCallback, _ screen: NSScreen) {
window = element
thumbnail.image = element.thumbnail
let (width, height) = Cell.computeDownscaledSize(element.thumbnail, screen)
thumbnail.image?.size = NSSize(width: width, height: height)
thumbnail.frame.size = NSSize(width: width, height: height)
let thumbnailSize = Cell.thumbnailSize(element.thumbnail, screen)
thumbnail.image?.size = thumbnailSize
thumbnail.frame.size = thumbnailSize
appIcon.image = element.icon
appIcon.image?.size = NSSize(width: Preferences.iconSize!, height: Preferences.iconSize!)
appIcon.frame.size = NSSize(width: Preferences.iconSize!, height: Preferences.iconSize!)
let appIconSize = NSSize(width: Preferences.iconSize, height: Preferences.iconSize)
appIcon.image?.size = appIconSize
appIcon.frame.size = appIconSize
label.string = element.title
// workaround: setting string on NSTextView change the font (most likely a Cocoa bug)
label.font = Preferences.font!
label.font = Preferences.font
hiddenIcon.isHidden = !window!.isHidden
minimizedIcon.isHidden = !window!.isMinimized
spaceIcon.isHidden = element.spaceIndex == nil || Spaces.isSingleSpace || Preferences.hideSpaceNumberLabels
spaceIcon.isHidden = element.spaceIndex == nil || Spaces.isSingleSpace || Preferences.hideSpaceNumberLabels!
if !spaceIcon.isHidden {
if element.isOnAllSpaces {
spaceIcon.setStar()
} else {
spaceIcon.setNumber(UInt32(element.spaceIndex!))
}
}
let fontIconWidth = CGFloat([minimizedIcon, hiddenIcon, spaceIcon].filter { !$0.isHidden }.count) * (Preferences.fontIconSize + Preferences.interItemPadding)
label.textContainer!.size.width = thumbnail.frame.width - Preferences.iconSize! - Preferences.interItemPadding - fontIconWidth
let fontIconWidth = CGFloat([minimizedIcon, hiddenIcon, spaceIcon].filter { !$0.isHidden }.count) * (Preferences.fontIconSize + Preferences.cellPadding)
label.textContainer!.size.width = view.frame.width - Preferences.iconSize - Preferences.cellPadding * 3 - fontIconWidth
self.mouseDownCallback = mouseDownCallback
self.mouseMovedCallback = mouseMovedCallback
if view.trackingAreas.count > 0 {
Expand All @@ -71,20 +78,6 @@ class Cell: NSCollectionViewItem {
view.addTrackingArea(NSTrackingArea(rect: view.bounds, options: [.mouseMoved, .activeAlways], owner: self, userInfo: nil))
}

static func computeDownscaledSize(_ image: NSImage?, _ screen: NSScreen) -> (Int, Int) {
if let image_ = image {
let imageRatio = image_.size.width / image_.size.height
let thumbnailMaxSize = Screen.thumbnailMaxSize(screen)
let thumbnailWidth = Int(floor(thumbnailMaxSize.height * imageRatio))
if thumbnailWidth <= Int(thumbnailMaxSize.width) {
return (thumbnailWidth, Int(thumbnailMaxSize.height))
} else {
return (Int(thumbnailMaxSize.width), Int(floor(thumbnailMaxSize.width / imageRatio)))
}
}
return (Int(Preferences.emptyThumbnailWidth), Int((Preferences.emptyThumbnailHeight)))
}

static func makeShadow(_ color: NSColor) -> NSShadow {
let shadow = NSShadow()
shadow.shadowColor = color
Expand All @@ -95,7 +88,9 @@ class Cell: NSCollectionViewItem {

private func makeHStackView() -> NSStackView {
let hStackView = NSStackView()
hStackView.spacing = Preferences.interItemPadding
hStackView.spacing = Preferences.cellPadding
label.wantsLayer = true
label.layer!.backgroundColor = NSColor.brown.cgColor
hStackView.setViews([appIcon, label, hiddenIcon, minimizedIcon, spaceIcon], in: .leading)
return hStackView
}
Expand All @@ -109,8 +104,37 @@ class Cell: NSCollectionViewItem {
vStackView.layer!.borderColor = .clear
vStackView.edgeInsets = NSEdgeInsets(top: Preferences.cellPadding, left: Preferences.cellPadding, bottom: Preferences.cellPadding, right: Preferences.cellPadding)
vStackView.orientation = .vertical
vStackView.spacing = Preferences.interItemPadding
vStackView.spacing = Preferences.cellPadding
vStackView.setViews([hStackView, thumbnail], in: .leading)
return vStackView
}

static func widthMax(_ screen: NSScreen) -> CGFloat {
return ThumbnailsPanel.widthMax(screen) / Preferences.minCellsPerRow - Preferences.cellPadding
}

static func widthMin(_ screen: NSScreen) -> CGFloat {
return ThumbnailsPanel.widthMax(screen) / Preferences.maxCellsPerRow - Preferences.cellPadding
}

static func height(_ screen: NSScreen) -> CGFloat {
return ThumbnailsPanel.heightMax(screen) / Preferences.nCellsRows - Preferences.cellPadding
}

static func width(_ image: NSImage?, _ screen: NSScreen) -> CGFloat {
return max(thumbnailSize(image, screen).width + Preferences.cellPadding * 2, ThumbnailsPanel.widthMin(screen))
}

static func thumbnailSize(_ image: NSImage?, _ screen: NSScreen) -> NSSize {
let thumbnailWidthMin = Cell.widthMin(screen) - Preferences.cellPadding * 2
let thumbnailHeightMax = Cell.height(screen) - Preferences.cellPadding * 3 - Preferences.iconSize
let thumbnailWidthMax = Cell.widthMax(screen) - Preferences.cellPadding * 2
guard let image = image else { return NSSize(width: thumbnailWidthMin, height: thumbnailHeightMax) }
let imageRatio = image.size.width / image.size.height
let thumbnailRatio = thumbnailWidthMax / thumbnailHeightMax
if thumbnailRatio > imageRatio {
return NSSize(width: image.size.width * thumbnailHeightMax / image.size.height, height: thumbnailHeightMax)
}
return NSSize(width: thumbnailWidthMax, height: image.size.height * thumbnailWidthMax / image.size.width)
}
}
44 changes: 23 additions & 21 deletions alt-tab-macos/ui/CollectionViewCenterFlowLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,48 @@ class CollectionViewCenterFlowLayout: NSCollectionViewFlowLayout {
var currentScreen: NSScreen?

override func layoutAttributesForElements(in rect: CGRect) -> [NSCollectionViewLayoutAttributes] {
let attributes = super.layoutAttributesForElements(in: rect)
if attributes.isEmpty {
return attributes
}
let attributes_ = super.layoutAttributesForElements(in: rect)
guard !attributes_.isEmpty else { return attributes_ }
let attributes = NSArray(array: attributes_, copyItems: true) as! [NSCollectionViewLayoutAttributes]
var currentRow: [NSCollectionViewLayoutAttributes] = []
var currentRowY = CGFloat(0)
var currentRowWidth = CGFloat(0)
var previousRowMaxY = CGFloat(0)
var currentRowMaxY = CGFloat(0)
var widestRow = CGFloat(0)
var totalHeight = CGFloat(0)
attributes.enumerated().forEach {
let isNewRow = abs($1.frame.origin.y - currentRowY) > Screen.thumbnailMaxSize(currentScreen!).height
for (index, attribute) in attributes.enumerated() {
let isNewRow = abs(attribute.frame.origin.y - currentRowY) > Cell.height(currentScreen!)
if isNewRow {
computeOriginXForAllItems(currentRowWidth - minimumInteritemSpacing, previousRowMaxY, currentRow)
computeOriginXForAllItems(currentRowWidth - Preferences.cellPadding, previousRowMaxY, currentRow)
currentRow.removeAll()
currentRowY = $1.frame.origin.y
currentRowY = attribute.frame.origin.y
currentRowWidth = 0
previousRowMaxY += currentRowMaxY + minimumLineSpacing
previousRowMaxY += currentRowMaxY + Preferences.cellPadding
currentRowMaxY = 0
}
currentRow.append($1)
currentRowWidth += $1.frame.size.width + minimumInteritemSpacing
currentRow.append(attribute)
currentRowWidth += attribute.frame.size.width + Preferences.cellPadding
widestRow = max(widestRow, currentRowWidth)
currentRowMaxY = max(currentRowMaxY, $1.frame.size.height)
if $0 == attributes.count - 1 {
computeOriginXForAllItems(currentRowWidth - minimumInteritemSpacing, previousRowMaxY, currentRow)
currentRowMaxY = max(currentRowMaxY, attribute.frame.size.height)
if index == attributes.count - 1 {
computeOriginXForAllItems(currentRowWidth - Preferences.cellPadding, previousRowMaxY, currentRow)
totalHeight = previousRowMaxY + currentRowMaxY
}
}
collectionView!.setFrameSize(NSSize(width: widestRow - minimumInteritemSpacing, height: totalHeight))
let newWidth = widestRow - Preferences.cellPadding
collectionView!.bounds.origin.x = (collectionView!.frame.size.width - newWidth) / 2
collectionView!.frame.size.width = newWidth
collectionView!.frame.size.height = totalHeight
return attributes
}

func computeOriginXForAllItems(_ currentRowWidth: CGFloat, _ previousRowMaxHeight: CGFloat, _ currentRow: [NSCollectionViewLayoutAttributes]) {
var marginLeft = floor((collectionView!.frame.size.width - currentRowWidth) / 2)
currentRow.forEach {
$0.frame.origin.x = marginLeft
$0.frame.origin.y = previousRowMaxHeight
marginLeft += $0.frame.size.width + minimumInteritemSpacing
private func computeOriginXForAllItems(_ currentRowWidth: CGFloat, _ previousRowMaxHeight: CGFloat, _ currentRow: [NSCollectionViewLayoutAttributes]) {
var marginLeft = (collectionView!.frame.size.width - currentRowWidth) / 2
for attribute in currentRow {
attribute.frame.origin.x = marginLeft
attribute.frame.origin.y = previousRowMaxHeight
marginLeft += attribute.frame.size.width + Preferences.cellPadding
}
}
}
4 changes: 0 additions & 4 deletions alt-tab-macos/ui/PreferencesWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,6 @@ class PreferencesWindow: NSWindow, NSWindowDelegate {
makeLabelWithInput("Tab key", rawName: "tabKeyCode", width: 33, suffixText: "KeyCodes Reference", suffixUrl: "https://eastmanreference.com/complete-list-of-applescript-key-codes", validator: tabKeyCodeValidator),
makeHorizontalSeparator(),
makeLabelWithDropdown("Theme", rawName: "theme", values: Preferences.themeMacro.labels),
makeLabelWithSlider("Max screen usage", rawName: "maxScreenUsage", minValue: 10, maxValue: 100, numberOfTickMarks: 0, unitText: "%"),
makeLabelWithSlider("Max thumbnails per row", rawName: "maxThumbnailsPerRow", minValue: 3, maxValue: 16, numberOfTickMarks: 0),
makeLabelWithSlider("Apps icon size", rawName: "iconSize", minValue: 12, maxValue: 64, numberOfTickMarks: 0, unitText: "px"),
makeLabelWithSlider("Window font size", rawName: "fontHeight", minValue: 12, maxValue: 64, numberOfTickMarks: 0, unitText: "px"),
makeLabelWithCheckbox("Hide space number labels", rawName: "hideSpaceNumberLabels"),
makeHorizontalSeparator(),
makeLabelWithSlider("Window apparition delay", rawName: "windowDisplayDelay", minValue: 0, maxValue: 2000, numberOfTickMarks: 0, unitText: "ms"),
Expand Down
Loading

0 comments on commit a1b5266

Please sign in to comment.