feat: chip列表稳定排序+自动滚动到选中网卡
- sortedInterfaces 不再将活动接口移到最前,改为物理网卡在前、虚拟网卡 在后各自按名称排序,切换网卡时列表顺序不再跳动 - 新增 scrollToActiveChip 函数,活动接口变化时自动滚动 Flickable 使选中 chip 完整可见,左侧截断对齐左缘、右侧截断对齐右缘 - 用 NumberAnimation(200ms OutCubic)驱动 contentX 平滑滚动,用户拖拽时 自动停止动画避免争抢 - popup 打开时用 Qt.callLater 直接定位活动 chip(无动画)
This commit is contained in:
+61
-7
@@ -48,23 +48,21 @@ AppletItem {
|
|||||||
|| name.startsWith("enp") || name.startsWith("eth")
|
|| name.startsWith("enp") || name.startsWith("eth")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 排序后的接口列表:活动接口最前,其次物理网卡,最后虚拟网卡
|
// 排序后的接口列表:物理网卡在前,虚拟网卡在后,各自按名称排序
|
||||||
// 设计原因:用户希望启动监测的网卡在最左边,方便查看和切换
|
// 设计原因:保持稳定排序,活动接口不再移到最前,避免切换网卡时 chip 顺序跳动;
|
||||||
|
// 活动 chip 若被截断,由 chipFlickable 自动滚动露出完整样式
|
||||||
readonly property var sortedInterfaces: {
|
readonly property var sortedInterfaces: {
|
||||||
if (!root.ready || root.networkInterfaces.length === 0) return []
|
if (!root.ready || root.networkInterfaces.length === 0) return []
|
||||||
var physical = []
|
var physical = []
|
||||||
var virtual = []
|
var virtual = []
|
||||||
for (var i = 0; i < root.networkInterfaces.length; i++) {
|
for (var i = 0; i < root.networkInterfaces.length; i++) {
|
||||||
var name = root.networkInterfaces[i]
|
var name = root.networkInterfaces[i]
|
||||||
if (name === root.activeInterface) continue
|
|
||||||
if (root.isPhysicalIf(name)) physical.push(name)
|
if (root.isPhysicalIf(name)) physical.push(name)
|
||||||
else virtual.push(name)
|
else virtual.push(name)
|
||||||
}
|
}
|
||||||
physical.sort()
|
physical.sort()
|
||||||
virtual.sort()
|
virtual.sort()
|
||||||
var result = []
|
return physical.concat(virtual)
|
||||||
if (root.activeInterface) result.push(root.activeInterface)
|
|
||||||
return result.concat(physical).concat(virtual)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 紧凑格式化速度(用于任务栏图标和 tooltip,不带单位后缀,节省空间)
|
// 紧凑格式化速度(用于任务栏图标和 tooltip,不带单位后缀,节省空间)
|
||||||
@@ -316,6 +314,9 @@ AppletItem {
|
|||||||
// C++ 后端 m_refreshTimer 持续更新,popup 通过属性绑定自动显示最新数据。
|
// C++ 后端 m_refreshTimer 持续更新,popup 通过属性绑定自动显示最新数据。
|
||||||
if (popupVisible) {
|
if (popupVisible) {
|
||||||
toolTip.close()
|
toolTip.close()
|
||||||
|
// popup 打开时确保活动 chip 完整可见:直接定位不播放动画,避免看到打开瞬间的滚动;
|
||||||
|
// Qt.callLater 等待首次打开时 Repeater 完成实例化与布局后再计算位置
|
||||||
|
Qt.callLater(function() { chipFlickable.scrollToActiveChip(false) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,8 +544,9 @@ AppletItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 接口切换 chip 列表:水平滚动,活动接口在最左,物理网卡次之,虚拟网卡在后
|
// 接口切换 chip 列表:水平滚动,物理网卡在前、虚拟网卡在后,顺序稳定不随切换变化
|
||||||
// chip 少时分散撑满一行;chip 多超出宽度时固定宽度 + 水平滚动
|
// chip 少时分散撑满一行;chip 多超出宽度时固定宽度 + 水平滚动
|
||||||
|
// 活动 chip 不在可视区域或被截断时,自动滚动露出完整样式(见 scrollToActiveChip)
|
||||||
Flickable {
|
Flickable {
|
||||||
id: chipFlickable
|
id: chipFlickable
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -557,6 +559,57 @@ AppletItem {
|
|||||||
flickableDirection: Flickable.HorizontalFlick
|
flickableDirection: Flickable.HorizontalFlick
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
|
// 平滑滚动动画:直接赋值 contentX 会被 Flickable 的拖拽/回弹行为覆盖,
|
||||||
|
// 用 NumberAnimation 驱动 contentX 实现自动滚动,平滑且不冲突
|
||||||
|
NumberAnimation {
|
||||||
|
id: chipScrollAnim
|
||||||
|
target: chipFlickable
|
||||||
|
property: "contentX"
|
||||||
|
duration: 200
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户开始拖拽/滑动时停止自动滚动,避免动画与用户操作争抢
|
||||||
|
onMovementStarted: chipScrollAnim.stop()
|
||||||
|
|
||||||
|
// 自动滚动使活动接口的 chip 完整可见:
|
||||||
|
// 列表顺序稳定后活动 chip 可能位于可视区域外或被截断,
|
||||||
|
// 切换接口或 popup 打开时调用,将其滚动露出完整样式。
|
||||||
|
// animated=false 用于 popup 刚打开时立即定位,跳过滚动过程
|
||||||
|
function scrollToActiveChip(animated) {
|
||||||
|
if (!visible || width <= 0) return
|
||||||
|
var idx = root.sortedInterfaces.indexOf(root.activeInterface)
|
||||||
|
if (idx < 0) return
|
||||||
|
var chip = chipRepeater.itemAt(idx)
|
||||||
|
if (!chip) return
|
||||||
|
var targetX = contentX
|
||||||
|
if (chip.x < contentX) {
|
||||||
|
// chip 在可视区左侧之外:回滚使 chip 左边缘与可视区左缘对齐
|
||||||
|
targetX = chip.x
|
||||||
|
} else if (chip.x + chip.width > contentX + width) {
|
||||||
|
// chip 在可视区右侧之外或被截断:滚动使 chip 右边缘与可视区右缘对齐
|
||||||
|
targetX = chip.x + chip.width - width
|
||||||
|
}
|
||||||
|
// 钳制到合法滚动范围 [0, contentWidth - width],避免触发边界回弹
|
||||||
|
targetX = Math.max(0, Math.min(targetX, Math.max(0, contentWidth - width)))
|
||||||
|
if (Math.abs(targetX - contentX) < 1) return
|
||||||
|
chipScrollAnim.stop()
|
||||||
|
if (animated) {
|
||||||
|
chipScrollAnim.to = targetX
|
||||||
|
chipScrollAnim.start()
|
||||||
|
} else {
|
||||||
|
contentX = targetX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 活动接口变化时(用户点击 chip 或后端自动切换)自动滚动露出活动 chip
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
function onActiveInterfaceChanged() {
|
||||||
|
chipFlickable.scrollToActiveChip(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 等宽分配宽度:假设所有 chip 等宽撑满时的单个宽度
|
// 等宽分配宽度:假设所有 chip 等宽撑满时的单个宽度
|
||||||
readonly property real equalChipWidth: root.sortedInterfaces.length > 0
|
readonly property real equalChipWidth: root.sortedInterfaces.length > 0
|
||||||
? (width - 6 * (root.sortedInterfaces.length - 1)) / root.sortedInterfaces.length
|
? (width - 6 * (root.sortedInterfaces.length - 1)) / root.sortedInterfaces.length
|
||||||
@@ -567,6 +620,7 @@ AppletItem {
|
|||||||
spacing: 6
|
spacing: 6
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
|
id: chipRepeater
|
||||||
model: root.sortedInterfaces
|
model: root.sortedInterfaces
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|||||||
Reference in New Issue
Block a user