feat: improve display of wide/tall thumbnails (closes #3791)
This commit is contained in:
1 parent
f9542f9214
commit
45bbc93bee
3 files changed
+50
-66
No files matched your search
@@ -83,12 +83,10 @@ class Appearance {
|
|||||||
maxWidthOnScreen = 0.9
|
maxWidthOnScreen = 0.9
|
||||||
maxHeightOnScreen = 0.8
|
maxHeightOnScreen = 0.8
|
||||||
windowMinWidthInRow = 0.1
|
windowMinWidthInRow = 0.1
|
||||||
windowMaxWidthInRow = 0.9
|
windowMaxWidthInRow = 0.15
|
||||||
switch currentSize {
|
switch currentSize {
|
||||||
case .small:
|
case .small:
|
||||||
rowsCount = isHorizontalScreen ? 5 : 8
|
rowsCount = isHorizontalScreen ? 5 : 8
|
||||||
windowMinWidthInRow = 0.08
|
|
||||||
windowMaxWidthInRow = 0.9
|
|
||||||
iconSize = 30
|
iconSize = 30
|
||||||
fontHeight = 14
|
fontHeight = 14
|
||||||
case .medium:
|
case .medium:
|
||||||
|
|||||||
@@ -2,16 +2,18 @@ import Cocoa
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class TrafficLightButton: NSButton {
|
class TrafficLightButton: NSButton {
|
||||||
|
static let size = CGFloat(16)
|
||||||
|
static let spacing = CGFloat(8)
|
||||||
var isMouseOver = false
|
var isMouseOver = false
|
||||||
var type: TrafficLightButtonType!
|
var type: TrafficLightButtonType!
|
||||||
var window_: Window?
|
var window_: Window?
|
||||||
|
|
||||||
init(_ type: TrafficLightButtonType, _ tooltip: String, _ size: CGFloat) {
|
init(_ type: TrafficLightButtonType, _ tooltip: String) {
|
||||||
super.init(frame: .init(origin: .zero, size: .init(width: size, height: size)))
|
super.init(frame: .init(origin: .zero, size: .init(width: TrafficLightButton.size, height: TrafficLightButton.size)))
|
||||||
self.type = type
|
self.type = type
|
||||||
target = self
|
target = self
|
||||||
action = #selector(onClick)
|
action = #selector(onClick)
|
||||||
fit(size, size)
|
fit(TrafficLightButton.size, TrafficLightButton.size)
|
||||||
addTrackingArea(NSTrackingArea(rect: bounds, options: [.mouseEnteredAndExited, .activeInKeyWindow], owner: self, userInfo: nil))
|
addTrackingArea(NSTrackingArea(rect: bounds, options: [.mouseEnteredAndExited, .activeInKeyWindow], owner: self, userInfo: nil))
|
||||||
toolTip = tooltip
|
toolTip = tooltip
|
||||||
appearance = .init(named: .aqua)
|
appearance = .init(named: .aqua)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class ThumbnailView: NSStackView {
|
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: "")
|
static let noOpenWindowToolTip = NSLocalizedString("App is running but has no open window", comment: "")
|
||||||
var window_: Window?
|
var window_: Window?
|
||||||
var thumbnail = NSImageView()
|
var thumbnail = NSImageView()
|
||||||
|
var windowlessIcon = NSImageView()
|
||||||
|
var thumbnailContainer: NSStackView!
|
||||||
var appIcon = NSImageView()
|
var appIcon = NSImageView()
|
||||||
var label = ThumbnailTitleView(Appearance.fontHeight)
|
var label = ThumbnailTitleView(Appearance.fontHeight)
|
||||||
var fullscreenIcon = ThumbnailFontIconView(symbol: .circledPlusSign, tooltip: NSLocalizedString("Window is fullscreen", comment: ""))
|
var fullscreenIcon = ThumbnailFontIconView(symbol: .circledPlusSign, tooltip: NSLocalizedString("Window is fullscreen", comment: ""))
|
||||||
@@ -14,10 +14,10 @@ class ThumbnailView: NSStackView {
|
|||||||
var spaceIcon = ThumbnailFontIconView(symbol: .circledNumber0)
|
var spaceIcon = ThumbnailFontIconView(symbol: .circledNumber0)
|
||||||
var dockLabelIcon = ThumbnailFilledFontIconView(ThumbnailFontIconView(symbol: .filledCircledNumber0, size: dockLabelLabelSize(),
|
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())
|
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 quitIcon = TrafficLightButton(.quit, NSLocalizedString("Quit app", comment: ""))
|
||||||
var closeIcon = TrafficLightButton(.close, NSLocalizedString("Close window", comment: ""), windowsControlSize)
|
var closeIcon = TrafficLightButton(.close, NSLocalizedString("Close window", comment: ""))
|
||||||
var minimizeIcon = TrafficLightButton(.miniaturize, NSLocalizedString("Minimize/Deminimize window", comment: ""), windowsControlSize)
|
var minimizeIcon = TrafficLightButton(.miniaturize, NSLocalizedString("Minimize/Deminimize window", comment: ""))
|
||||||
var maximizeIcon = TrafficLightButton(.fullscreen, NSLocalizedString("Fullscreen/Defullscreen window", comment: ""), windowsControlSize)
|
var maximizeIcon = TrafficLightButton(.fullscreen, NSLocalizedString("Fullscreen/Defullscreen window", comment: ""))
|
||||||
var windowlessAppIndicator = WindowlessAppIndicator(tooltip: ThumbnailView.noOpenWindowToolTip)
|
var windowlessAppIndicator = WindowlessAppIndicator(tooltip: ThumbnailView.noOpenWindowToolTip)
|
||||||
|
|
||||||
var hStackView: NSStackView!
|
var hStackView: NSStackView!
|
||||||
@@ -27,7 +27,6 @@ class ThumbnailView: NSStackView {
|
|||||||
var dragAndDropTimer: Timer?
|
var dragAndDropTimer: Timer?
|
||||||
var indexInRecycledViews: Int!
|
var indexInRecycledViews: Int!
|
||||||
var isShowingWindowControls = false
|
var isShowingWindowControls = false
|
||||||
var windowlessIcon = NSImageView()
|
|
||||||
|
|
||||||
var isFirstInRow = false
|
var isFirstInRow = false
|
||||||
var isLastInRow = false
|
var isLastInRow = false
|
||||||
@@ -56,9 +55,7 @@ class ThumbnailView: NSStackView {
|
|||||||
windowlessIcon.shadow = shadow
|
windowlessIcon.shadow = shadow
|
||||||
appIcon.shadow = shadow
|
appIcon.shadow = shadow
|
||||||
windowControlIcons = [quitIcon, closeIcon, minimizeIcon, maximizeIcon]
|
windowControlIcons = [quitIcon, closeIcon, minimizeIcon, maximizeIcon]
|
||||||
|
|
||||||
addViews()
|
addViews()
|
||||||
addWindowControls()
|
|
||||||
addDockLabelIcon()
|
addDockLabelIcon()
|
||||||
addWindowlessIndicator()
|
addWindowlessIndicator()
|
||||||
setAccessibilityChildren([])
|
setAccessibilityChildren([])
|
||||||
@@ -74,30 +71,42 @@ class ThumbnailView: NSStackView {
|
|||||||
vStackView.layer!.borderWidth = CGFloat(1)
|
vStackView.layer!.borderWidth = CGFloat(1)
|
||||||
vStackView.edgeInsets = NSEdgeInsets(top: Appearance.edgeInsetsSize, left: Appearance.edgeInsetsSize,
|
vStackView.edgeInsets = NSEdgeInsets(top: Appearance.edgeInsetsSize, left: Appearance.edgeInsetsSize,
|
||||||
bottom: Appearance.edgeInsetsSize, right: Appearance.edgeInsetsSize)
|
bottom: Appearance.edgeInsetsSize, right: Appearance.edgeInsetsSize)
|
||||||
|
setViews([vStackView], in: .leading)
|
||||||
if Preferences.appearanceStyle == .appIcons {
|
if Preferences.appearanceStyle == .appIcons {
|
||||||
// The label is outside and below the selected icon in AppIcons style
|
// The label is outside and below the selected icon in AppIcons style
|
||||||
hStackView = NSStackView(views: [appIcon])
|
hStackView = NSStackView(views: [appIcon])
|
||||||
vStackView.setViews([hStackView], in: .leading)
|
vStackView.setViews([hStackView], in: .leading)
|
||||||
label.alignment = .center
|
label.alignment = .center
|
||||||
setViews([vStackView], in: .leading)
|
|
||||||
addSubview(label)
|
addSubview(label)
|
||||||
label.isHidden = true
|
label.isHidden = true
|
||||||
|
|
||||||
vStackView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
vStackView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
|
vStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
vStackView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
|
vStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
vStackView.topAnchor.constraint(equalTo: self.topAnchor),
|
vStackView.topAnchor.constraint(equalTo: topAnchor),
|
||||||
|
|
||||||
label.topAnchor.constraint(equalTo: vStackView.bottomAnchor, constant: Appearance.intraCellPadding),
|
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 {
|
} else {
|
||||||
hStackView = NSStackView(views: [appIcon, label, hiddenIcon, fullscreenIcon, minimizedIcon, spaceIcon])
|
hStackView = NSStackView(views: [appIcon, label, hiddenIcon, fullscreenIcon, minimizedIcon, spaceIcon])
|
||||||
vStackView.setViews([hStackView, thumbnail, windowlessIcon], in: .leading)
|
thumbnailContainer = NSStackView(views: [thumbnail, windowlessIcon])
|
||||||
setViews([vStackView], in: .leading)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,22 +118,12 @@ class ThumbnailView: NSStackView {
|
|||||||
addSubview(windowlessAppIndicator, positioned: .above, relativeTo: nil)
|
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) {
|
func showOrHideWindowControls(_ shouldShowWindowControls: Bool) {
|
||||||
let shouldShow = shouldShowWindowControls && !Preferences.hideColoredCircles && !Appearance.hideThumbnails
|
let shouldShow = shouldShowWindowControls && !Preferences.hideColoredCircles && !Appearance.hideThumbnails
|
||||||
if isShowingWindowControls != shouldShow {
|
if Preferences.appearanceStyle == .thumbnails, isShowingWindowControls != shouldShow {
|
||||||
isShowingWindowControls = shouldShow
|
isShowingWindowControls = shouldShow
|
||||||
let target = (window_?.isWindowlessApp ?? true) ? windowlessIcon : thumbnail
|
var xOffset = 3 + Appearance.edgeInsetsSize
|
||||||
target.addSubview(quitIcon, positioned: .above, relativeTo: nil)
|
var yOffset = 2 + TrafficLightButton.size
|
||||||
var xOffset = CGFloat(3)
|
|
||||||
var yOffset = CGFloat(2 + ThumbnailView.windowsControlSize)
|
|
||||||
windowControlIcons.forEach { icon in
|
windowControlIcons.forEach { icon in
|
||||||
icon.isHidden = !shouldShow ||
|
icon.isHidden = !shouldShow ||
|
||||||
(icon.type == .quit && !(window_?.application.canBeQuit() ?? true)) ||
|
(icon.type == .quit && !(window_?.application.canBeQuit() ?? true)) ||
|
||||||
@@ -133,11 +132,11 @@ class ThumbnailView: NSStackView {
|
|||||||
if !icon.isHidden {
|
if !icon.isHidden {
|
||||||
icon.setFrameOrigin(NSPoint(
|
icon.setFrameOrigin(NSPoint(
|
||||||
x: xOffset,
|
x: xOffset,
|
||||||
y: target.frame.height - yOffset))
|
y: thumbnailContainer.frame.height - yOffset))
|
||||||
xOffset += ThumbnailView.windowsControlSize + ThumbnailView.windowsControlSpacing
|
xOffset += TrafficLightButton.size + TrafficLightButton.spacing
|
||||||
if xOffset + ThumbnailView.windowsControlSize > target.frame.width {
|
if xOffset + TrafficLightButton.size > thumbnailContainer.frame.width {
|
||||||
xOffset = 3
|
xOffset = 3 + Appearance.edgeInsetsSize
|
||||||
yOffset += ThumbnailView.windowsControlSize + ThumbnailView.windowsControlSpacing
|
yOffset += TrafficLightButton.size + TrafficLightButton.spacing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Force the icons to redraw, or after clicking the fullscreen button,
|
// Force the icons to redraw, or after clicking the fullscreen button,
|
||||||
@@ -365,36 +364,21 @@ class ThumbnailView: NSStackView {
|
|||||||
let leftRightEdgeInsetsSize = ThumbnailView.getLeftRightEdgeInsetsSize()
|
let leftRightEdgeInsetsSize = ThumbnailView.getLeftRightEdgeInsetsSize()
|
||||||
let topBottomEdgeInsetsSize = ThumbnailView.getTopBottomEdgeInsetsSize()
|
let topBottomEdgeInsetsSize = ThumbnailView.getTopBottomEdgeInsetsSize()
|
||||||
var width = CGFloat(0)
|
var width = CGFloat(0)
|
||||||
|
var contentWidth = CGFloat(0)
|
||||||
if Preferences.appearanceStyle == .thumbnails {
|
if Preferences.appearanceStyle == .thumbnails {
|
||||||
// Preferred to the width of the image, and the minimum width may be set to be large.
|
// Preferred to the width of the image, and the minimum width may be set to be large.
|
||||||
if element.isWindowlessApp {
|
if element.isWindowlessApp {
|
||||||
width = (windowlessIcon.frame.size.width + leftRightEdgeInsetsSize).rounded()
|
contentWidth = windowlessIcon.frame.size.width
|
||||||
} else {
|
} else {
|
||||||
width = (thumbnail.frame.size.width + leftRightEdgeInsetsSize).rounded()
|
contentWidth = thumbnail.frame.size.width
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let contentWidth = max(hStackView.frame.size.width, Appearance.iconSize)
|
contentWidth = max(hStackView.frame.size.width, Appearance.iconSize)
|
||||||
let frameWidth = contentWidth + leftRightEdgeInsetsSize
|
|
||||||
width = max(frameWidth, widthMin).rounded()
|
|
||||||
}
|
}
|
||||||
|
let frameWidth = (contentWidth + leftRightEdgeInsetsSize).rounded()
|
||||||
|
width = max(frameWidth, widthMin).rounded()
|
||||||
assignIfDifferent(&frame.size.width, width)
|
assignIfDifferent(&frame.size.width, width)
|
||||||
assignIfDifferent(&frame.size.height, newHeight)
|
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() {
|
func setLabelWidth() {
|
||||||
|
|||||||
Reference in new issue
Block a user