弹窗高度随内容自动调整:数据到达后变高不再露出滚动条

弹窗打开时按平铺布局测一次高度,之后公网 IP、归属地等数据陆续到达,内容变高(实测 860 → 911pt)而窗口不变,底部露出一小截滚动条。

- PageScroll 在弹窗里用 onGeometryChange 记下滚动内容高度与可视高度,差值通过 reportPopoverOverflow 环境值交给面板
- StatusPanel.adjustHeight(by:):按差值调整窗口高度、顶边不动,上限为菜单栏下方到屏幕底边的可用高度,屏幕放不下时才保留滚动
- 打开时的平铺测量保留作为起始高度;展开 / 收起区块、数据刷新都会自动校正
This commit is contained in:
gentpan committed 2026-09-16 19:48:18 +05:00
1 parent 84542374bc
commit 165963de84
6 files changed
+66 -12

No files matched your search

+4
View File
@@ -7,6 +7,10 @@
### 2026-09-16
#### 修复
- 网络弹窗偶尔差一点放不下、露出一小截滚动条:弹窗打开时按当时的内容算高度,之后公网 IP、归属地等数据陆续到达,内容变高了窗口却不跟着变。现在内容一变高或变矮,窗口高度随之调整到正好放下,只有屏幕放不下时才出现滚动条;展开、收起区块时同样适用。
#### 样式
- 主窗口在 macOS 26 上改用液态玻璃:侧边栏是一块浮在窗口里的玻璃面板,红绿灯按钮落在面板里,选中项垫一块淡蓝色圆角底并随切换滑动;顶栏右侧“在菜单栏显示”一组收进玻璃胶囊。主按钮是蓝色玻璃胶囊、次要按钮与图标按钮是透明玻璃,风扇模式等快捷切换是玻璃胶囊,开关换成系统开关(拖动时滑块变成玻璃)。设置分组与数据卡片保持实色,与系统设置一致。macOS 14、15 上保持原来的样式。
@@ -16,19 +16,24 @@ final class StatusPanel: NSPanel {
private var lastDismissal = Date.distantPast
/// 面板隐藏时不保留 SwiftUI 视图,避免后台随数据刷新重绘
private let makeContent: () -> NSView
private var makeContent: (() -> NSView)?
/// 平铺布局(不含滚动容器)的视图,只用来测量内容的自然高度
private let makeMeasuringContent: () -> NSView
private let width: CGFloat
init<Content: View, Measuring: View>(width: CGFloat, content: @escaping () -> Content, measuring: @escaping () -> Measuring) {
self.width = width
makeContent = { NSHostingView(rootView: content()) }
makeMeasuringContent = { NSHostingView(rootView: measuring()) }
super.init(contentRect: NSRect(x: 0, y: 0, width: width, height: DS.Size.panelMinHeight),
styleMask: [.borderless, .nonactivatingPanel],
backing: .buffered,
defer: true)
// 打开时按平铺布局测出的高度只是起点:真实内容会随数据刷新变高变矮,由滚动区域上报差值再校正
makeContent = { [weak self] in
NSHostingView(rootView: content().environment(\.reportPopoverOverflow) { overflow in
self?.adjustHeight(by: overflow)
})
}
isFloatingPanel = true
level = .statusBar
collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary, .transient, .ignoresCycle]
@@ -56,7 +61,7 @@ final class StatusPanel: NSPanel {
self.anchor = anchor
anchorScreen = screen
let content = makeContent()
guard let content = makeContent?() else { return }
setFrame(targetFrame(), display: false)
content.frame = NSRect(origin: .zero, size: frame.size)
contentView = PanelContainer.make(containing: content, cornerRadius: DS.Radius.xl)
@@ -81,6 +86,15 @@ final class StatusPanel: NSPanel {
invalidateShadow()
}
/// 滚动内容比可视区域高出(或矮出)一截时调整窗口高度,顶边不动;屏幕放不下时停在最高处,由滚动条兜底
func adjustHeight(by overflow: CGFloat) {
guard contentView != nil, abs(overflow) > 1 else { return }
let height = min(max(frame.height + overflow, DS.Size.panelMinHeight), availableHeight())
guard abs(height - frame.height) > 1 else { return }
setFrame(NSRect(x: frame.minX, y: frame.maxY - height, width: width, height: height), display: true)
invalidateShadow()
}
func dismiss() {
guard isVisible, !isPinned else { return }
lastDismissal = Date()
@@ -116,16 +130,22 @@ final class StatusPanel: NSPanel {
let visible = (anchorScreen ?? NSScreen.main)?.visibleFrame ?? .zero
let margin = DS.Space.s2
let top = min(anchor.minY - DS.Size.panelGap, visible.maxY)
let available = max(DS.Size.panelMinHeight, top - visible.minY - margin)
let natural = makeMeasuringContent().fittingSize.height
let height = min(max(natural, DS.Size.panelMinHeight), available)
let height = min(max(natural, DS.Size.panelMinHeight), availableHeight())
var x = anchor.midX - width / 2
x = min(max(x, visible.minX + margin), visible.maxX - width - margin)
return NSRect(x: x, y: top - height, width: width, height: height)
}
/// 菜单栏下方到屏幕底边(留一点边距)能放下的最大高度
private func availableHeight() -> CGFloat {
let visible = (anchorScreen ?? NSScreen.main)?.visibleFrame ?? .zero
let top = min(anchor.minY - DS.Size.panelGap, visible.maxY)
return max(DS.Size.panelMinHeight, top - visible.minY - DS.Space.s2)
}
// MARK: 事件
private func installMonitors() {
@@ -9,6 +9,8 @@ extension EnvironmentValues {
@Entry var isDetailPage = false
/// 菜单栏弹窗:区块去掉卡片底色,用细分隔线隔开,排得更紧凑
@Entry var isPopover = false
/// 菜单栏弹窗:滚动内容的实际高度比可视区域高出(负数为矮出)多少,交给面板调整窗口高度
@Entry var reportPopoverOverflow: (@MainActor @Sendable (CGFloat) -> Void)? = nil
}
// MARK: - 卡片
@@ -851,7 +853,10 @@ struct DSSlider: View {
struct PageScroll<Content: View>: View {
@Environment(\.isSnapshot) private var isSnapshot
@Environment(\.isPopover) private var isPopover
@Environment(\.reportPopoverOverflow) private var reportOverflow
@ViewBuilder var content: Content
@State private var contentHeight: CGFloat = 0
@State private var viewportHeight: CGFloat = 0
var body: some View {
// 弹窗里区块之间靠分隔线隔开,不再额外留间距
@@ -863,10 +868,27 @@ struct PageScroll<Content: View>: View {
stack
} else {
// 内容放得下时不回弹,避免点击时整页轻微抖动
ScrollView { stack.overlayScrollers() }
.scrollBounceBehavior(.basedOnSize)
ScrollView {
stack
.overlayScrollers()
.onGeometryChange(for: CGFloat.self) { $0.size.height } action: { height in
contentHeight = height
report()
}
}
.scrollBounceBehavior(.basedOnSize)
.onGeometryChange(for: CGFloat.self) { $0.size.height } action: { height in
viewportHeight = height
report()
}
}
}
/// 弹窗里内容高度一变(数据刷新、展开收起),就让面板把窗口调到正好放下;屏幕放不下时才出现滚动
private func report() {
guard let reportOverflow, contentHeight > 0, viewportHeight > 0 else { return }
reportOverflow(contentHeight - viewportHeight)
}
}
// MARK: - 徽章 / 标签
+6 -2
View File
@@ -59,10 +59,14 @@ system language by default; switch it in Settings → General → Language.
<!-- changelog:start -->
<!-- Generated from CHANGELOG.md by Scripts/sync_changelog.py. Do not edit by hand. -->
Latest release **0.3.2** (2026-09-16) · **6** changes in development · [full changelog](CHANGELOG.md) (kept in Chinese)
Latest release **0.3.2** (2026-09-16) · **7** changes in development · [full changelog](CHANGELOG.md) (kept in Chinese)
<details open>
<summary><b>2026-09-16</b> · Unreleased · 5 style · 1 added</summary>
<summary><b>2026-09-16</b> · Unreleased · 1 fixed · 5 style · 1 added</summary>
**Fixed**
- 网络弹窗偶尔差一点放不下、露出一小截滚动条:弹窗打开时按当时的内容算高度,之后公网 IP、归属地等数据陆续到达,内容变高了窗口却不跟着变。现在内容一变高或变矮,窗口高度随之调整到正好放下,只有屏幕放不下时才出现滚动条;展开、收起区块时同样适用。
**Style**
+6 -2
View File
@@ -55,10 +55,14 @@ CPU 不分性能核与能效核,部分功耗与频率读数可能不显示。
<!-- changelog:start -->
<!-- 由 Scripts/sync_changelog.py 从 CHANGELOG.md 生成,请勿手改。 -->
最新版本 **0.3.2**(2026-09-16) · 开发中 **6** 项改动尚未发布 · [完整更新日志](CHANGELOG.md)
最新版本 **0.3.2**(2026-09-16) · 开发中 **7** 项改动尚未发布 · [完整更新日志](CHANGELOG.md)
<details open>
<summary><b>2026-09-16</b> · 未发布 · 样式 5 · 新增 1</summary>
<summary><b>2026-09-16</b> · 未发布 · 修复 1 · 样式 5 · 新增 1</summary>
**修复**
- 网络弹窗偶尔差一点放不下、露出一小截滚动条:弹窗打开时按当时的内容算高度,之后公网 IP、归属地等数据陆续到达,内容变高了窗口却不跟着变。现在内容一变高或变矮,窗口高度随之调整到正好放下,只有屏幕放不下时才出现滚动条;展开、收起区块时同样适用。
**样式**
+1 -1
View File
@@ -13,7 +13,7 @@ settings:
SWIFT_VERSION: "6.0"
MARKETING_VERSION: "0.3.2"
# 构建号四位数字,每次 make build 加一;营销版本只在发布时用 Scripts/version.sh patch / minor 修改
CURRENT_PROJECT_VERSION: "0078"
CURRENT_PROJECT_VERSION: "0081"
MACOSX_DEPLOYMENT_TARGET: "14.0"
# 本地开发使用 ad-hoc 签名;发布时改为 Developer ID(见 docs/PLAN.md)
CODE_SIGN_STYLE: Manual