From ed4ebcfa763f66ecd69cae57df2c949a7a9a8a45 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sat, 13 Jun 2026 20:23:29 +0800
Subject: [PATCH] Prepare 7.9.1 usage tips release
---
src/Apptag/AppGridCollectionView.swift | 97 +++++++++++++++++++------------
src/Scripts/usage_tips_qa.sh | 34 +++++++++--
src/CHANGELOG.md | 8 ++
src/Apptag/Info.plist | 4
src/Apptag/Localization/zh-Hans.json | 2
src/Apptag/Localization/zh-Hant.json | 2
6 files changed, 98 insertions(+), 49 deletions(-)
diff --git a/src/Apptag/AppGridCollectionView.swift b/src/Apptag/AppGridCollectionView.swift
index e5df3a5..ae2714e 100644
--- a/src/Apptag/AppGridCollectionView.swift
+++ b/src/Apptag/AppGridCollectionView.swift
@@ -38,8 +38,8 @@
}
enum AppGridUsageTipsMetrics {
- static let barHeight: CGFloat = 84
- static let reservedHeight: CGFloat = 128
+ static let barHeight: CGFloat = 136
+ static let reservedHeight: CGFloat = 176
static let bottomMargin: CGFloat = 20
static let horizontalInset: CGFloat = 24
static let minWidth: CGFloat = 640
@@ -943,44 +943,42 @@
let paddingRight: CGFloat = 16
let iconSize: CGFloat = 24
let iconTextGap: CGFloat = 10
- let titleDetailGap: CGFloat = 22
- let detailControlsGap: CGFloat = 24
+ let controlZoneWidth: CGFloat = 176
let buttonGap: CGFloat = 6
let buttonSize: CGFloat = 48
+ let buttonDotsGap: CGFloat = 6
+ let dotsHeight: CGFloat = 10
let dotsWidth = dotsView.preferredWidth
let fullTitleWidth = ceil(titleLabel.attributedStringValue.size().width)
- let centerY = visualFrame.midY
+ let titleX = visualFrame.minX + paddingLeft + iconSize + iconTextGap
+ let buttonsWidth = buttonSize * 2 + buttonGap
+ let buttonsGroupMinX = visualFrame.maxX - paddingRight - buttonsWidth
+ let textRight = visualFrame.maxX - controlZoneWidth
+ let availableTextWidth = max(1, textRight - titleX)
+ let titleWidth = min(fullTitleWidth, availableTextWidth)
+ let titleHeight: CGFloat = 32
+ let detailHeight: CGFloat = 68
+ let lineGap: CGFloat = 8
+ let textBlockHeight = titleHeight + lineGap + detailHeight
+ let textBlockY = visualFrame.minY + max(0, (visualFrame.height - textBlockHeight) / 2)
+
iconView.frame = NSRect(
x: visualFrame.minX + paddingLeft,
- y: centerY - iconSize / 2,
+ y: visualFrame.midY - iconSize / 2,
width: iconSize,
height: iconSize
)
- let titleX = iconView.frame.maxX + iconTextGap
- let buttonsWidth = buttonSize * 2 + buttonGap
- let trailingWidth = paddingRight + buttonsWidth + detailControlsGap
- let availableTextWidth = max(1, visualFrame.maxX - titleX - titleDetailGap - trailingWidth)
- let minimumReadableDetailWidth: CGFloat = 220
- let titleWidth: CGFloat
- if availableTextWidth >= fullTitleWidth + minimumReadableDetailWidth {
- titleWidth = fullTitleWidth
- } else if availableTextWidth >= fullTitleWidth {
- titleWidth = fullTitleWidth
- } else {
- titleWidth = max(1, availableTextWidth)
- }
-
titleLabel.frame = NSRect(
x: titleX,
- y: visualFrame.minY,
+ y: textBlockY,
width: titleWidth,
- height: visualFrame.height
+ height: titleHeight
)
- let buttonY = visualFrame.minY + 10
- let buttonsGroupMinX = visualFrame.maxX - paddingRight - buttonsWidth
+ let controlGroupHeight = buttonSize + buttonDotsGap + dotsHeight
+ let buttonY = visualFrame.minY + max(0, (visualFrame.height - controlGroupHeight) / 2)
nextButton.frame = NSRect(
x: buttonsGroupMinX + buttonSize + buttonGap,
y: buttonY,
@@ -995,18 +993,18 @@
)
dotsView.frame = NSRect(
x: buttonsGroupMinX + (buttonsWidth - dotsWidth) / 2,
- y: previousButton.frame.maxY + 6,
+ y: previousButton.frame.maxY + buttonDotsGap,
width: dotsWidth,
- height: 10
+ height: dotsHeight
)
- let detailX = titleLabel.frame.maxX + titleDetailGap
- let detailRight = previousButton.frame.minX - detailControlsGap
+ let detailX = titleX
+ let detailRight = textRight
detailScrollView.frame = NSRect(
x: detailX,
- y: visualFrame.minY,
+ y: titleLabel.frame.maxY + lineGap,
width: max(1, detailRight - detailX),
- height: visualFrame.height
+ height: detailHeight
)
layoutDetailLabel()
}
@@ -1048,10 +1046,10 @@
func preferredWidth(maxAvailableWidth: CGFloat) -> CGFloat {
let padding: CGFloat = 22 + 16
- let fixedWidth: CGFloat = 24 + 10 + 22 + 24 + 48 + 6 + 48
+ let fixedWidth: CGFloat = 24 + 10 + 24 + 48 + 6 + 48
let titleWidth = ceil(titleLabel.attributedStringValue.size().width)
- let detailWidth = ceil(detailLabel.attributedStringValue.size().width)
- let contentWidth = padding + fixedWidth + titleWidth + detailWidth
+ let detailWidth = measuredDetailLineWidth()
+ let contentWidth = padding + fixedWidth + max(titleWidth, detailWidth)
let preferredWidth = max(AppGridUsageTipsMetrics.minWidth, contentWidth)
return min(maxAvailableWidth, preferredWidth)
}
@@ -1081,7 +1079,7 @@
addSubview(iconView)
configureLabel(titleLabel, font: titleFont, lineBreakMode: .byClipping)
- configureLabel(detailLabel, font: detailFont, lineBreakMode: .byClipping)
+ configureDetailLabel()
detailScrollView.drawsBackground = false
detailScrollView.hasVerticalScroller = false
@@ -1112,6 +1110,19 @@
label.lineBreakMode = lineBreakMode
label.maximumNumberOfLines = 1
label.font = font
+ }
+
+ private func configureDetailLabel() {
+ detailLabel.cell = NSTextFieldCell(textCell: "")
+ detailLabel.isEditable = false
+ detailLabel.isSelectable = false
+ detailLabel.drawsBackground = false
+ detailLabel.isBordered = false
+ detailLabel.lineBreakMode = .byWordWrapping
+ detailLabel.maximumNumberOfLines = 2
+ detailLabel.font = detailFont
+ detailLabel.cell?.usesSingleLineMode = false
+ detailLabel.cell?.wraps = true
}
private func claimInteractionFocus() {
@@ -1180,9 +1191,17 @@
}
private func layoutDetailLabel() {
- let textWidth = detailLabel.attributedStringValue.size().width
- let width = max(detailScrollView.contentView.bounds.width, ceil(textWidth) + 18)
+ let width = max(1, detailScrollView.contentView.bounds.width)
+ detailLabel.preferredMaxLayoutWidth = width
detailLabel.frame = NSRect(x: 0, y: 0, width: width, height: detailScrollView.bounds.height)
+ }
+
+ private func measuredDetailLineWidth() -> CGFloat {
+ let attributes: [NSAttributedString.Key: Any] = [.font: detailFont]
+ let lineWidths = detailLabel.stringValue
+ .components(separatedBy: .newlines)
+ .map { NSAttributedString(string: $0, attributes: attributes).size().width }
+ return ceil(lineWidths.max() ?? detailLabel.attributedStringValue.size().width)
}
private func currentVisualFrame() -> NSRect {
@@ -1198,8 +1217,10 @@
private func formattedTipDetail(_ text: String) -> String {
text
- .replacingOccurrences(of: " > ", with: " -> ")
- .replacingOccurrences(of: ">", with: "->")
+ .replacingOccurrences(of: "\\s*(?:-->|->|>|→|>)\\s*", with: "\n", options: .regularExpression)
+ .replacingOccurrences(of: "[ \\t]{2,}", with: " ", options: .regularExpression)
+ .replacingOccurrences(of: "\\n{2,}", with: "\n", options: .regularExpression)
+ .trimmingCharacters(in: .whitespacesAndNewlines)
}
private func updateColors() {
diff --git a/src/Apptag/Info.plist b/src/Apptag/Info.plist
index 900e665..6493cc7 100644
--- a/src/Apptag/Info.plist
+++ b/src/Apptag/Info.plist
@@ -19,9 +19,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
- <string>7.9.0</string>
+ <string>7.9.1</string>
<key>CFBundleVersion</key>
- <string>20260612.1121</string>
+ <string>20260613.2020</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
diff --git a/src/Apptag/Localization/zh-Hans.json b/src/Apptag/Localization/zh-Hans.json
index 27e243f..22ddaee 100644
--- a/src/Apptag/Localization/zh-Hans.json
+++ b/src/Apptag/Localization/zh-Hans.json
@@ -221,7 +221,7 @@
"usageTips.tip4.title": "技巧4-复制应用:",
"usageTips.tip4.detail": "长按应用图标启动拖动 > 按住 Alt/Option 拖动到容器是复制",
"usageTips.tip5.title": "技巧5-解除标签:",
- "usageTips.tip5.detail": "长按应用图标启动拖动 > 把应用拖动到容器间的空白处",
+ "usageTips.tip5.detail": "长按应用图标启动拖动 > 拖动到容器间的空白处",
"usageTips.tip6.title": "技巧6-标签排序:",
"usageTips.tip6.detail": "长按标签启动拖动 > 拖动到目标位置,放手即可",
"usageTips.tip7.title": "技巧7-应用排序:",
diff --git a/src/Apptag/Localization/zh-Hant.json b/src/Apptag/Localization/zh-Hant.json
index 05b30ac..6ac7032 100644
--- a/src/Apptag/Localization/zh-Hant.json
+++ b/src/Apptag/Localization/zh-Hant.json
@@ -221,7 +221,7 @@
"usageTips.tip4.title": "技巧4-複製應用:",
"usageTips.tip4.detail": "長按應用程式圖示開始拖曳 > 按住 Alt/Option 拖到容器即可複製",
"usageTips.tip5.title": "技巧5-解除標籤:",
- "usageTips.tip5.detail": "長按應用程式圖示開始拖曳 > 把應用拖到容器之間的空白處",
+ "usageTips.tip5.detail": "長按應用程式圖示開始拖曳 > 拖到容器之間的空白處",
"usageTips.tip6.title": "技巧6-標籤排序:",
"usageTips.tip6.detail": "長按標籤開始拖曳 > 拖到目標位置後放開",
"usageTips.tip7.title": "技巧7-應用排序:",
diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md
index b296679..83a1e1c 100644
--- a/src/CHANGELOG.md
+++ b/src/CHANGELOG.md
@@ -1,5 +1,13 @@
# TagLauncher Changelog
+## [7.9.1] — 2026-06-13
+
+- 修复新建空标签后 App Grid 左侧标签栏和主体区域可能不显示该标签的问题;空标签现在会按当前视图显示最小容器,避免用户以为创建失败
+- 修复拖动应用到容器外空白处解除标签时,部分 macOS 14 环境可能没有反应的问题;容器内部空白仍不会触发解除标签,保持产品口径一致
+- App Grid 新增原生 AppKit 底部使用技巧悬浮条,支持翻页、可在设置中关闭、不会穿透点击到底层应用,并为 29 个语种的较长文案预留换行展示能力
+- 优化使用技巧悬浮条排版:标题完整展示,正文分隔符按换行处理,页签指示点移动到左右箭头下方,右侧控制区固定,减少遮挡和误触
+- 版本号更新为 `7.9.1`,Build 更新为 `20260613.2020`
+
## [7.9.0] — 2026-06-11
- 新增容器内 App 手动拖拽排序:同一容器内拖动 App 可改变显示顺序,不改变 App 与标签的归属关系
diff --git a/src/Scripts/usage_tips_qa.sh b/src/Scripts/usage_tips_qa.sh
index 78834f8..3bc4a2a 100755
--- a/src/Scripts/usage_tips_qa.sh
+++ b/src/Scripts/usage_tips_qa.sh
@@ -86,9 +86,9 @@
"Usage tips must be implemented as a native AppKit NSView",
)
require(
- r"enum\s+AppGridUsageTipsMetrics\s*\{(?P<body>.*?)static\s+let\s+reservedHeight\s*:\s*CGFloat\s*=\s*128",
+ r"enum\s+AppGridUsageTipsMetrics\s*\{(?P<body>.*?)static\s+let\s+barHeight\s*:\s*CGFloat\s*=\s*136(?P<body2>.*?)static\s+let\s+reservedHeight\s*:\s*CGFloat\s*=\s*176",
app_grid,
- "Usage tips overlay must reserve stable bottom space for the native HUD bar",
+ "Usage tips overlay must reserve a design-reviewed multi-line bottom space for the native HUD bar",
)
require(
r"NSVisualEffectView\s*\(",
@@ -125,19 +125,34 @@
"Usage tips must ask the host to recompute width after localized text changes",
)
require(
- r"availableTextWidth\s*=\s*max\s*\(\s*1\s*,\s*visualFrame\.maxX\s*-\s*titleX\s*-\s*titleDetailGap\s*-\s*trailingWidth\s*\)",
+ r"controlZoneWidth\s*:\s*CGFloat\s*=\s*176",
+ app_grid,
+ "Usage tips layout must reserve a fixed right-side control zone",
+)
+require(
+ r"availableTextWidth\s*=\s*max\s*\(\s*1\s*,\s*textRight\s*-\s*titleX\s*\)",
app_grid,
"Usage tips layout must allocate text from actual available AppGrid width",
)
require(
- r"dotsView\.frame\s*=\s*NSRect\s*\((?P<body>.*?)x\s*:\s*buttonsGroupMinX\s*\+\s*\(buttonsWidth\s*-\s*dotsWidth\)\s*/\s*2(?P<body2>.*?)y\s*:\s*previousButton\.frame\.maxY\s*\+\s*6",
+ r"dotsView\.frame\s*=\s*NSRect\s*\((?P<body>.*?)x\s*:\s*buttonsGroupMinX\s*\+\s*\(buttonsWidth\s*-\s*dotsWidth\)\s*/\s*2(?P<body2>.*?)y\s*:\s*previousButton\.frame\.maxY\s*\+\s*buttonDotsGap",
app_grid,
"Usage tips page dots must sit centered below the previous/next arrow buttons",
)
require(
- r"detailRight\s*=\s*previousButton\.frame\.minX\s*-\s*detailControlsGap",
+ r"detailRight\s*=\s*textRight",
app_grid,
- "Usage tips detail text must stop before the arrow controls when dots move below them",
+ "Usage tips detail text must stop before the fixed right-side arrow control zone",
+)
+require(
+ r"configureDetailLabel\s*\(\s*\)(?P<body>.*?)detailLabel\.lineBreakMode\s*=\s*\.byWordWrapping",
+ app_grid,
+ "Usage tip detail text must wrap naturally inside the reserved text column",
+)
+require(
+ r"detailLabel\.maximumNumberOfLines\s*=\s*2",
+ app_grid,
+ "Usage tip detail text must use the design-reviewed two-line body layout",
)
require(
r"return\s+min\s*\(\s*maxAvailableWidth\s*,\s*preferredWidth\s*\)",
@@ -346,7 +361,12 @@
require(
r"formattedTipDetail\s*\(_\s+text\s*:\s*String\s*\)",
app_grid,
- "Usage tips detail text must normalize > separators to -> for display",
+ "Usage tips detail text must normalize separators for display",
+)
+require(
+ r"replacingOccurrences\s*\(\s*of\s*:\s*\"\\\\s\*\(\?:-->\|->\|>\|→\|>\)\\\\s\*\"\s*,\s*with\s*:\s*\"\\n\"",
+ app_grid,
+ "Usage tips detail text must convert -->, ->, and > separators into hard line breaks",
)
require(
r"setUsageTipsBubbleDisabled\s*\(\s*inside\s*\)",
--
Gitblit v1.9.3