侧边栏选中改为滑动的蓝色光条:细线底轨、发光指示与渐淡高亮,切换带回弹动画

This commit is contained in:
gentpan committed 2026-09-13 06:45:57 +05:00
1 parent 0342074489
commit 0829f425cb
7 files changed
+80 -12

No files matched your search

+1
View File
@@ -42,6 +42,7 @@
#### 样式
- 主窗口与设置窗口的侧边栏选中样式:左侧一条两端渐隐的细线,选中项有发光的蓝色光条,切换时带回弹地滑过去,右侧拖出渐淡的蓝色高亮;不再整块填蓝。
- 仪表盘“快捷开关”改为开关样式:防休眠、合盖运行、散热模式各一行,右侧开关,下面一行小字说明当前状态。
- 进程表按窗口宽度自动隐藏次要列,窄窗口里名称列不会被挤没。
- 内存详情的“释放内存”移到标题栏,执行中与完成后在按钮上显示进度和结果;主窗口内存页顶栏同样提供。
@@ -211,6 +211,7 @@ struct IconButton: View {
// MARK: - 侧边栏
/// 侧边栏的一项。选中态不填色:由容器上的 `sidebarGlider()` 画左侧滑动的蓝色光条与淡淡的高亮
struct SidebarButton: View {
let title: String
let symbol: String
@@ -227,20 +228,82 @@ struct SidebarButton: View {
Text(title).dsFont(.sm, weight: isSelected ? .semibold : .regular)
Spacer()
}
.foregroundStyle(isSelected ? DS.Palette.onPrimary : DS.Palette.textPrimary)
.padding(.horizontal, DS.Space.s2)
.foregroundStyle(isSelected ? DS.Palette.primary : hovering ? DS.Palette.textPrimary : DS.Palette.textSecondary)
.padding(.leading, DS.Space.s3)
.padding(.trailing, DS.Space.s2)
.frame(height: DS.Size.controlHeight)
.background(background, in: RoundedRectangle(cornerRadius: DS.Radius.md))
.contentShape(Rectangle())
.animation(DS.Motion.quick, value: isSelected)
.animation(DS.Motion.quick, value: hovering)
}
.buttonStyle(.plain)
.onHover { hovering = $0 }
.accessibilityAddTraits(isSelected ? .isSelected : [])
.anchorPreference(key: SidebarRowsKey.self, value: .bounds) { [SidebarRow(isSelected: isSelected, bounds: $0)] }
}
}
private var background: Color {
if isSelected { return DS.Palette.primary }
return hovering ? DS.Palette.surfaceHover : .clear
struct SidebarRow {
let isSelected: Bool
let bounds: Anchor<CGRect>
}
struct SidebarRowsKey: PreferenceKey {
static let defaultValue: [SidebarRow] = []
static func reduce(value: inout [SidebarRow], nextValue: () -> [SidebarRow]) { value += nextValue() }
}
extension View {
/// 放在包含 SidebarButton 的容器上:左侧一条两端渐隐的细线,选中项位置有一段发光的蓝色光条,
/// 切换时带回弹地滑过去,光条右侧拖出一片渐淡的蓝色高亮
func sidebarGlider() -> some View {
overlayPreferenceValue(SidebarRowsKey.self) { rows in
GeometryReader { proxy in
SidebarGlider(frames: rows.map { proxy[$0.bounds] },
selected: rows.first(where: \.isSelected).map { proxy[$0.bounds] })
}
.allowsHitTesting(false)
}
}
}
private struct SidebarGlider: View {
let frames: [CGRect]
let selected: CGRect?
private static let barWidth: CGFloat = 2
private static let glowWidth = DS.Space.s1 + DS.Space.s1 / 2
var body: some View {
if let first = frames.min(by: { $0.minY < $1.minY }), let last = frames.max(by: { $0.maxY < $1.maxY }) {
ZStack(alignment: .topLeading) {
// 底轨:整列的细线,上下两端渐隐
Rectangle()
.fill(LinearGradient(colors: [.clear, DS.Palette.border, DS.Palette.border, .clear], startPoint: .top, endPoint: .bottom))
.frame(width: DS.Size.stroke, height: last.maxY - first.minY)
.offset(x: first.minX, y: first.minY)
if let selected {
Rectangle()
.fill(LinearGradient(colors: [DS.Palette.primary.opacity(0.14), DS.Palette.primary.opacity(0)],
startPoint: .leading, endPoint: .trailing))
.frame(width: selected.width, height: selected.height)
.offset(x: selected.minX, y: selected.minY)
Capsule()
.fill(DS.Palette.primary)
.frame(width: Self.glowWidth, height: selected.height * 0.6)
.blur(radius: DS.Space.s2)
.offset(x: selected.minX - Self.glowWidth / 2, y: selected.midY - selected.height * 0.3)
Rectangle()
.fill(LinearGradient(colors: [DS.Palette.primary.opacity(0), DS.Palette.primary, DS.Palette.primary.opacity(0)],
startPoint: .top, endPoint: .bottom))
.frame(width: Self.barWidth, height: selected.height)
.offset(x: selected.minX - Self.barWidth / 2, y: selected.minY)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.animation(.spring(response: 0.45, dampingFraction: 0.6), value: selected)
}
}
}
@@ -77,6 +77,7 @@ private struct MainSidebar: View {
IconButton(systemName: "power", help: "退出 OpenStats") { model.quit() }
}
}
.sidebarGlider()
.padding(.horizontal, DS.Space.s3)
// 顶部留出窗口红绿灯按钮的位置
.padding(.top, DS.Size.windowHeader + DS.Space.s2)
@@ -52,6 +52,7 @@ public struct SettingsView: View {
}
Spacer()
}
.sidebarGlider()
.padding(.horizontal, DS.Space.s3)
.padding(.top, DS.Size.windowHeader + DS.Space.s2)
.padding(.bottom, DS.Space.s3)
+3 -2
View File
@@ -46,10 +46,10 @@ Developed and tested on Apple Silicon. The interface is in Simplified Chinese.
<!-- changelog:start -->
<!-- Generated from CHANGELOG.md by Scripts/sync_changelog.py. Do not edit by hand. -->
Not released yet · **45** changes in development · [full changelog](CHANGELOG.md) (kept in Chinese)
Not released yet · **46** changes in development · [full changelog](CHANGELOG.md) (kept in Chinese)
<details open>
<summary><b>2026-09-13</b> · Unreleased · 30 added · 9 style · 6 fixed</summary>
<summary><b>2026-09-13</b> · Unreleased · 30 added · 10 style · 6 fixed</summary>
**Added**
@@ -86,6 +86,7 @@ Not released yet · **45** changes in development · [full changelog](CHANGELOG.
**Style**
- 主窗口与设置窗口的侧边栏选中样式:左侧一条两端渐隐的细线,选中项有发光的蓝色光条,切换时带回弹地滑过去,右侧拖出渐淡的蓝色高亮;不再整块填蓝。
- 仪表盘“快捷开关”改为开关样式:防休眠、合盖运行、散热模式各一行,右侧开关,下面一行小字说明当前状态。
- 进程表按窗口宽度自动隐藏次要列,窄窗口里名称列不会被挤没。
- 内存详情的“释放内存”移到标题栏,执行中与完成后在按钮上显示进度和结果;主窗口内存页顶栏同样提供。
+3 -2
View File
@@ -44,10 +44,10 @@ OpenStats 还没有发布正式版本。第一个版本会在 [Releases](https:/
<!-- changelog:start -->
<!-- 由 Scripts/sync_changelog.py 从 CHANGELOG.md 生成,请勿手改。 -->
尚未发布正式版本 · 开发中 **45** 项改动尚未发布 · [完整更新日志](CHANGELOG.md)
尚未发布正式版本 · 开发中 **46** 项改动尚未发布 · [完整更新日志](CHANGELOG.md)
<details open>
<summary><b>2026-09-13</b> · 未发布 · 新增 30 · 样式 9 · 修复 6</summary>
<summary><b>2026-09-13</b> · 未发布 · 新增 30 · 样式 10 · 修复 6</summary>
**新增**
@@ -84,6 +84,7 @@ OpenStats 还没有发布正式版本。第一个版本会在 [Releases](https:/
**样式**
- 主窗口与设置窗口的侧边栏选中样式:左侧一条两端渐隐的细线,选中项有发光的蓝色光条,切换时带回弹地滑过去,右侧拖出渐淡的蓝色高亮;不再整块填蓝。
- 仪表盘“快捷开关”改为开关样式:防休眠、合盖运行、散热模式各一行,右侧开关,下面一行小字说明当前状态。
- 进程表按窗口宽度自动隐藏次要列,窄窗口里名称列不会被挤没。
- 内存详情的“释放内存”移到标题栏,执行中与完成后在按钮上显示进度和结果;主窗口内存页顶栏同样提供。
+2 -2
View File
@@ -348,7 +348,7 @@
<div class="log">
<article class="log__day">
<div class="log__head"><time datetime="2026-09-13">2026-09-13</time><span class="log__tag">未发布</span></div>
<p class="log__counts">新增 30 · 样式 9 · 修复 6</p>
<p class="log__counts">新增 30 · 样式 10 · 修复 6</p>
<ul class="log__list">
<li><span class="log__kind">新增</span>菜单栏显示 CPU、GPU、内存、网速、CPU 温度和风扇转速。</li>
<li><span class="log__kind">新增</span>菜单栏风格:双行文字、单行文字、图标、圆环、饼图、柱状历史、电量条、状态圆点八种,整体选择一种统一套用,也可以给个别指标单独指定。设置里每种风格都有用示例数据画出的预览和说明,柱状历史注明是最近 10 次采样的变化。</li>
@@ -359,7 +359,7 @@
<li><span class="log__kind">新增</span>网络详情:上下镜像的流量历史;连接探测格子(每 1 / 2 / 5 秒 ping 一次 Cloudflare、Google、阿里云、腾讯或路由器,显示延迟、抖动、丢包);接口、物理地址、Wi-Fi 信号与速率、VPN / 代理;本地 IPv4 / IPv6、路由器、公网 IPv4 / IPv6,点击即可拷贝;归属地显示 SVG 国旗、城市、ASN 与网络运营方;各进程上传下载速率。</li>
<li><span class="log__kind">新增</span>DNS:显示正在使用的 DNS 与配置方式,一键刷新 DNS 缓存,一键切换为自动、Cloudflare、Google、腾讯 DNSPod、阿里云,或手动填写地址(逐个校验为 IPv4 / IPv6)。流量经过 VPN / 代理时提示 DNS 可能被接管。</li>
</ul>
<a class="log__more" href="https://github.com/gentpan/OpenStats/blob/main/CHANGELOG.md">还有 37 项 →</a>
<a class="log__more" href="https://github.com/gentpan/OpenStats/blob/main/CHANGELOG.md">还有 38 项 →</a>
</article>
</div>
<!-- changelog:end -->