Skip to content

Commit

Permalink
feat: improve display of wide/tall thumbnails (closes #3791)
Browse files Browse the repository at this point in the history
  • Loading branch information
lwouis committed Nov 1, 2024
1 parent f9542f9 commit 45bbc93
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 66 deletions.
4 changes: 1 addition & 3 deletions src/logic/Appearance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,10 @@ class Appearance {
maxWidthOnScreen = 0.9
maxHeightOnScreen = 0.8
windowMinWidthInRow = 0.1
windowMaxWidthInRow = 0.9
windowMaxWidthInRow = 0.15
switch currentSize {
case .small:
rowsCount = isHorizontalScreen ? 5 : 8
windowMinWidthInRow = 0.08
windowMaxWidthInRow = 0.9
iconSize = 30
fontHeight = 14
case .medium:
Expand Down
8 changes: 5 additions & 3 deletions src/ui/generic-components/TrafficLightButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ import Cocoa
import Foundation

class TrafficLightButton: NSButton {
static let size = CGFloat(16)
static let spacing = CGFloat(8)
var isMouseOver = false
var type: TrafficLightButtonType!
var window_: Window?

init(_ type: TrafficLightButtonType, _ tooltip: String, _ size: CGFloat) {
super.init(frame: .init(origin: .zero, size: .init(width: size, height: size)))
init(_ type: TrafficLightButtonType, _ tooltip: String) {
super.init(frame: .init(origin: .zero, size: .init(width: TrafficLightButton.size, height: TrafficLightButton.size)))
self.type = type
target = self
action = #selector(onClick)
fit(size, size)
fit(TrafficLightButton.size, TrafficLightButton.size)
addTrackingArea(NSTrackingArea(rect: bounds, options: [.mouseEnteredAndExited, .activeInKeyWindow], owner: self, userInfo: nil))
toolTip = tooltip
appearance = .init(named: .aqua)
Expand Down
104 changes: 44 additions & 60 deletions src/ui/main-window/ThumbnailView.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import Cocoa

class ThumbnailView: NSStackView {
static let windowsControlSize = CGFloat(16)
static let windowsControlSpacing = CGFloat(8)
static let noOpenWindowToolTip = NSLocalizedString("App is running but has no open window", comment: "")
var window_: Window?
var thumbnail = NSImageView()
var windowlessIcon = NSImageView()
var thumbnailContainer: NSStackView!
var appIcon = NSImageView()
var label = ThumbnailTitleView(Appearance.fontHeight)
var fullscreenIcon = ThumbnailFontIconView(symbol: .circledPlusSign, tooltip: NSLocalizedString("Window is fullscreen", comment: ""))
Expand All @@ -14,10 +14,10 @@ class ThumbnailView: NSStackView {
var spaceIcon = ThumbnailFontIconView(symbol: .circledNumber0)
var dockLabelIcon = ThumbnailFilledFontIconView(ThumbnailFontIconView(symbol: .filledCircledNumber0, size: dockLabelLabelSize(),
color: NSColor(srgbRed: 1, green: 0.30, blue: 0.25, alpha: 1), shadow: nil), backgroundColor: NSColor.white, size: dockLabelLabelSize())
var quitIcon = TrafficLightButton(.quit, NSLocalizedString("Quit app", comment: ""), windowsControlSize)
var closeIcon = TrafficLightButton(.close, NSLocalizedString("Close window", comment: ""), windowsControlSize)
var minimizeIcon = TrafficLightButton(.miniaturize, NSLocalizedString("Minimize/Deminimize window", comment: ""), windowsControlSize)
var maximizeIcon = TrafficLightButton(.fullscreen, NSLocalizedString("Fullscreen/Defullscreen window", comment: ""), windowsControlSize)
var quitIcon = TrafficLightButton(.quit, NSLocalizedString("Quit app", comment: ""))
var closeIcon = TrafficLightButton(.close, NSLocalizedString("Close window", comment: ""))
var minimizeIcon = TrafficLightButton(.miniaturize, NSLocalizedString("Minimize/Deminimize window", comment: ""))
var maximizeIcon = TrafficLightButton(.fullscreen, NSLocalizedString("Fullscreen/Defullscreen window", comment: ""))
var windowlessAppIndicator = WindowlessAppIndicator(tooltip: ThumbnailView.noOpenWindowToolTip)

var hStackView: NSStackView!
Expand All @@ -27,7 +27,6 @@ class ThumbnailView: NSStackView {
var dragAndDropTimer: Timer?
var indexInRecycledViews: Int!
var isShowingWindowControls = false
var windowlessIcon = NSImageView()

var isFirstInRow = false
var isLastInRow = false
Expand Down Expand Up @@ -56,9 +55,7 @@ class ThumbnailView: NSStackView {
windowlessIcon.shadow = shadow
appIcon.shadow = shadow
windowControlIcons = [quitIcon, closeIcon, minimizeIcon, maximizeIcon]

addViews()
addWindowControls()
addDockLabelIcon()
addWindowlessIndicator()
setAccessibilityChildren([])
Expand All @@ -74,30 +71,42 @@ class ThumbnailView: NSStackView {
vStackView.layer!.borderWidth = CGFloat(1)
vStackView.edgeInsets = NSEdgeInsets(top: Appearance.edgeInsetsSize, left: Appearance.edgeInsetsSize,
bottom: Appearance.edgeInsetsSize, right: Appearance.edgeInsetsSize)

setViews([vStackView], in: .leading)
if Preferences.appearanceStyle == .appIcons {
// The label is outside and below the selected icon in AppIcons style
hStackView = NSStackView(views: [appIcon])
vStackView.setViews([hStackView], in: .leading)
label.alignment = .center
setViews([vStackView], in: .leading)
addSubview(label)
label.isHidden = true

vStackView.translatesAutoresizingMaskIntoConstraints = false
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
vStackView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
vStackView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
vStackView.topAnchor.constraint(equalTo: self.topAnchor),

vStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
vStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
vStackView.topAnchor.constraint(equalTo: topAnchor),
label.topAnchor.constraint(equalTo: vStackView.bottomAnchor, constant: Appearance.intraCellPadding),
self.bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: Appearance.intraCellPadding)
bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: Appearance.intraCellPadding)
])
} else {
hStackView = NSStackView(views: [appIcon, label, hiddenIcon, fullscreenIcon, minimizedIcon, spaceIcon])
vStackView.setViews([hStackView, thumbnail, windowlessIcon], in: .leading)
setViews([vStackView], in: .leading)
thumbnailContainer = NSStackView(views: [thumbnail, windowlessIcon])
thumbnailContainer.orientation = .vertical
// thumbnailContainer.alignment = .leading
// thumbnailContainer.distribution = .equalCentering
vStackView.setViews([hStackView, thumbnailContainer], in: .leading)
NSLayoutConstraint.activate([
vStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
vStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
vStackView.topAnchor.constraint(equalTo: topAnchor),
vStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
thumbnailContainer.leftAnchor.constraint(equalTo: leftAnchor),
thumbnailContainer.rightAnchor.constraint(equalTo: rightAnchor),
])
if Preferences.appearanceStyle == .thumbnails {
windowControlIcons.forEach {
thumbnailContainer.addSubview($0, positioned: .above, relativeTo: nil)
$0.isHidden = true
}
}
}
}

Expand All @@ -109,22 +118,12 @@ class ThumbnailView: NSStackView {
addSubview(windowlessAppIndicator, positioned: .above, relativeTo: nil)
}

private func addWindowControls() {
thumbnail.addSubview(quitIcon, positioned: .above, relativeTo: nil)
thumbnail.addSubview(closeIcon, positioned: .above, relativeTo: nil)
thumbnail.addSubview(minimizeIcon, positioned: .above, relativeTo: nil)
thumbnail.addSubview(maximizeIcon, positioned: .above, relativeTo: nil)
windowControlIcons.forEach { $0.isHidden = true }
}

func showOrHideWindowControls(_ shouldShowWindowControls: Bool) {
let shouldShow = shouldShowWindowControls && !Preferences.hideColoredCircles && !Appearance.hideThumbnails
if isShowingWindowControls != shouldShow {
if Preferences.appearanceStyle == .thumbnails, isShowingWindowControls != shouldShow {
isShowingWindowControls = shouldShow
let target = (window_?.isWindowlessApp ?? true) ? windowlessIcon : thumbnail
target.addSubview(quitIcon, positioned: .above, relativeTo: nil)
var xOffset = CGFloat(3)
var yOffset = CGFloat(2 + ThumbnailView.windowsControlSize)
var xOffset = 3 + Appearance.edgeInsetsSize
var yOffset = 2 + TrafficLightButton.size
windowControlIcons.forEach { icon in
icon.isHidden = !shouldShow ||
(icon.type == .quit && !(window_?.application.canBeQuit() ?? true)) ||
Expand All @@ -133,11 +132,11 @@ class ThumbnailView: NSStackView {
if !icon.isHidden {
icon.setFrameOrigin(NSPoint(
x: xOffset,
y: target.frame.height - yOffset))
xOffset += ThumbnailView.windowsControlSize + ThumbnailView.windowsControlSpacing
if xOffset + ThumbnailView.windowsControlSize > target.frame.width {
xOffset = 3
yOffset += ThumbnailView.windowsControlSize + ThumbnailView.windowsControlSpacing
y: thumbnailContainer.frame.height - yOffset))
xOffset += TrafficLightButton.size + TrafficLightButton.spacing
if xOffset + TrafficLightButton.size > thumbnailContainer.frame.width {
xOffset = 3 + Appearance.edgeInsetsSize
yOffset += TrafficLightButton.size + TrafficLightButton.spacing
}
}
// Force the icons to redraw, or after clicking the fullscreen button,
Expand Down Expand Up @@ -365,36 +364,21 @@ class ThumbnailView: NSStackView {
let leftRightEdgeInsetsSize = ThumbnailView.getLeftRightEdgeInsetsSize()
let topBottomEdgeInsetsSize = ThumbnailView.getTopBottomEdgeInsetsSize()
var width = CGFloat(0)
var contentWidth = CGFloat(0)
if Preferences.appearanceStyle == .thumbnails {
// Preferred to the width of the image, and the minimum width may be set to be large.
if element.isWindowlessApp {
width = (windowlessIcon.frame.size.width + leftRightEdgeInsetsSize).rounded()
contentWidth = windowlessIcon.frame.size.width
} else {
width = (thumbnail.frame.size.width + leftRightEdgeInsetsSize).rounded()
contentWidth = thumbnail.frame.size.width
}
} else {
let contentWidth = max(hStackView.frame.size.width, Appearance.iconSize)
let frameWidth = contentWidth + leftRightEdgeInsetsSize
width = max(frameWidth, widthMin).rounded()
contentWidth = max(hStackView.frame.size.width, Appearance.iconSize)
}
let frameWidth = (contentWidth + leftRightEdgeInsetsSize).rounded()
width = max(frameWidth, widthMin).rounded()
assignIfDifferent(&frame.size.width, width)
assignIfDifferent(&frame.size.height, newHeight)

if logger.isDebugEnabled() {
logger.d(window_?.title)
printSubviewFrames(of: self)
}
}

func printSubviewFrames(of view: NSView, indent: String = "", isLast: Bool = true) {
let indentSymbol = isLast ? "└── " : "├── "
logger.d("\(indent)\(indentSymbol)View: \(type(of: view)), Frame: \(view.frame)")
let newIndent = indent + (isLast ? " " : "")

for (index, subview) in view.subviews.enumerated() {
let isLastSubview = index == view.subviews.count - 1
printSubviewFrames(of: subview, indent: newIndent, isLast: isLastSubview)
}
}

func setLabelWidth() {
Expand Down

0 comments on commit 45bbc93

Please sign in to comment.