查看: 4539|回复: 3

Python+Slint圆形进度条:Path与ArcTo圆弧计算

[复制链接]
发表于 6 天前 | 显示全部楼层 |阅读模式
在 Slint 中做圆形进度条,核心不是用图片或 Canvas 逐帧绘制,而是用 Path 组合 MoveTo、ArcTo 两个路径命令,把 progress 值映射成圆弧终点坐标。原文给出的实现包含 Python 启动入口、Slint 界面结构、圆弧角度换算、满圆边界处理、动画、Timer 自动演示以及滑块双向绑定,适合用作桌面端 Python + Slint 的自定义控件练习。下面按工程入口、圆弧实现、边界与交互几个部分重组说明。

一、工程入口与运行方式
原文工程至少包含 main.py 和 main.slint 两个文件。main.py 只负责加载 Slint 文件并运行主窗口:
  1. # -*- coding: utf-8 -*-
  2. import slint
  3. ui = slint.load_file("main.slint")
  4. window = ui.MainWindow()
  5. window.run()
复制代码
这里 load_file("main.slint") 使用相对路径,运行时应保证工作目录或文件位置能正确解析到 main.slint。MainWindow 在 main.slint 中导出,Python 侧通过 ui.MainWindow() 实例化窗口,再调用 run() 进入事件循环。

二、界面属性与动画基础
MainWindow 继承 Window,并声明 progress 和 auto-play 两个属性。progress 取值 0.0~1.0,默认 0.35;auto-play 控制自动演示是否开启。动画通过 animate 关键字绑定到 progress:
  1. property <float> progress: 0.35;
  2. property <bool> auto-play: false;
  3. animate progress { duration: 200ms; easing: ease-in-out; }
复制代码
当 progress 被滑块、按钮或 Timer 修改时,Slint 会在 200ms 内按 ease-in-out 过渡,而不是立即跳变。这个细节对进度条观感很关键,因为圆弧长度和中心百分比文字都会随 progress 变化。

三、圆形区域的尺寸与坐标系统
圆形进度条放在一个 Rectangle 中。代码用 diameter 计算圆直径,取容器宽高较小值的 0.75,再用 ring-x、ring-y 把圆居中。Path 使用 viewbox-width: 200、viewbox-height: 200 的坐标系统,所以圆弧坐标按 0~200 计算,而不是直接按实际像素计算;stroke-width: 12px 控制圆环线宽,fill: transparent 表示只描边不填充。

四、ArcTo 圆弧终点计算
进度圆弧起点固定在正上方 (100,0)。progress 乘以 360 得到角度,再减 90 是因为要把起点偏移到以顶部为起点的坐标系中。终点坐标由 cos/sin 计算,下面片段同时包含背景轨道之外的关键进度圆弧逻辑:
  1. Path {
  2.     x: parent.ring-x;
  3.     y: parent.ring-y;
  4.     width: parent.diameter;
  5.     height: parent.diameter;
  6.     viewbox-width: 200;
  7.     viewbox-height: 200;
  8.     stroke-width: 12px;
  9.     stroke: #06b6d4;
  10.     fill: transparent;
  11.     visible: progress > 0.005 && progress < 0.995;
  12.     MoveTo { x: 100; y: 0; }
  13.     ArcTo {
  14.         x: 100 + 100 * cos((progress * 360.0 - 90.0) * 1deg);
  15.         y: 100 + 100 * sin((progress * 360.0 - 90.0) * 1deg);
  16.         radius-x: 100;
  17.         radius-y: 100;
  18.         large-arc: progress > 0.5;
  19.         sweep: true;
  20.     }
  21. }
复制代码
ArcTo 参数含义:x/y 是终点坐标;radius-x/radius-y 是椭圆弧半径;large-arc 表示是否走大于 180° 的大弧;sweep 表示绘制方向。progress=0.5 时角度为 90°,正好半圆;progress>0.5 时圆弧超过半圆,因此 large-arc 设为 true,否则 ArcTo 会选择较短弧段。progress=1.0 时角度为 270°,如果直接依赖单个 ArcTo,终点与起点重合会导致圆弧长度为 0,这也是需要单独处理满圆的原因。

五、边界处理:0、满圆和可见性
原文用 visible 把动态进度圆弧限制在 progress > 0.005 且 progress < 0.995 的区间。低于下限时只显示背景轨道;达到上限时隐藏动态圆弧,改用两段半圆弧拼接成完整圆环。满圆覆盖代码与背景轨道绘制方式一致:
  1. Path {
  2.     x: parent.ring-x;
  3.     y: parent.ring-y;
  4.     width: parent.diameter;
  5.     height: parent.diameter;
  6.     viewbox-width: 200;
  7.     viewbox-height: 200;
  8.     stroke-width: 12px;
  9.     stroke: #06b6d4;
  10.     fill: transparent;
  11.     visible: progress >= 0.995;
  12.     MoveTo { x: 100; y: 0; }
  13.     ArcTo {
  14.         x: 100; y: 200;
  15.         radius-x: 100; radius-y: 100;
  16.         large-arc: false; sweep: true;
  17.     }
  18.     ArcTo {
  19.         x: 100; y: 0;
  20.         radius-x: 100; radius-y: 100;
  21.         large-arc: false; sweep: true;
  22.     }
  23. }
复制代码
这种处理避免了 ArcTo 在起点终点重合时画不出完整圆的问题,也让接近 0 或接近 1 的边界状态有明确显示逻辑。

六、自动演示与正弦映射
Timer 每 30ms 触发一次,running 绑定 auto-play。触发时通过 sin 计算新进度:
  1. Timer {
  2.     interval: 30ms;
  3.     running: auto-play;
  4.     triggered => {
  5.         root.progress = (sin((root.progress * 360.0 + 3.0) * 1deg) + 1.0) * 0.5;
  6.     }
  7. }
复制代码
这行代码把 progress 先映射到角度,再用 sin 得到 -1~1,随后加 1 乘 0.5 映射到 0~1。结合 animate progress 的 200ms 动画,自动演示不是硬切,而是平滑摆动。注意 Timer 的 interval 和 animate 的 duration 是两套时间参数:前者决定触发频率,后者决定属性变化的过渡时间。

七、滑块、按钮与双向绑定
滑块 value 与 slider-val 双向绑定,slider-val 又由 root.progress * 100.0 定义,因此滑块 0~100 对应进度 0%~100%:
  1. property <float> slider-val: root.progress * 100.0;
  2. Slider {
  3.     minimum: 0.0;
  4.     maximum: 100.0;
  5.     value <=> slider-val;
  6. }
复制代码
按钮直接设置 root.progress 为 0.0、0.25、0.50、0.75、1.0,用于快捷跳转。中心文字通过 round(progress * 100) 拼接百分号显示。由于圆弧、文字、滑块都依赖同一个 progress 属性,交互链路比较集中,排查时应优先检查 progress 是否被正确更新,以及 slider-val 双向绑定是否形成循环赋值或单位换算错误。

八、适用场景与实现取舍
这套实现依赖 Slint 的矢量 Path 和 ArcTo,不需要贴图或 Canvas 逐帧绘制,缩放时不会失真,代码量也集中在路径参数和属性绑定上。适合桌面工具、下载/安装进度、任务完成度等需要圆形进度反馈的界面。需要留意的兼容性细节包括:Slint 版本是否支持所用 Path/ArcTo 属性、角度单位是否写成 1deg、large-arc 在 progress>0.5 时的切换、满圆边界 0.995 与 0.005 的阈值,以及 load_file 的相对路径解析。按原文思路排错时,可以先确认 progress 是否在 0~1 内,再检查 ArcTo 终点坐标、large-arc 和 visible 条件是否与当前进度匹配。
回复

使用道具 举报

发表于 6 天前 | 显示全部楼层

Re: Python+Slint圆形进度条:Path与ArcTo圆弧计算

这套做法思路挺完整的,用 Path 和 ArcTo 把进度映射成几何坐标,比 Canvas 逐帧重绘省心很多,而且坐标系统固定在 200×200 的 viewbox 里,外层只改 diameter 和 ring-x/ring-y 就能自适应容器尺寸,这个分离做得干净。 几个可以再抠一下的细节: 一是 large-arc 的判定。progress 正好等于 0.5 时是 90°,半圆起点终点关于圆心对称,此时 large-arc 取 true 或 false 画出来的都是同一个半圆,所以用大于号、大于等于号都行;真正要留意的是非常接近 0.5 的地方,比如 0.499 和 0.501,圆弧长度几乎一样但 large-arc 翻转,好在视觉上连续,不会出现跳变。 二是满圆的可见性阈值。0.995 对应约 358.2°,动态圆弧还剩一点点缺口,瞬间切换成完整圆环时,那个小缺口会“啪”地补上。如果觉得不够顺滑,可以把阈值收紧到 0.9995 左右,让跳变发生在肉眼几乎分辨不出的地方;反过来接近 0 的下限同理,0.005 对应的那点小弧出现时也会有一丝突兀。 三是动画和 Timer 的叠加。animate 200ms 配上 30ms 一次的 Timer 修改 progress,每次赋值都会重新启动一次过渡,实际显示值会一直追在目标值后面,产生平滑但带滞后感的效果。自动演示只是想看个来回摆动
回复 支持 反对

使用道具 举报

发表于 6 天前 | 显示全部楼层

Re: Python+Slint圆形进度条:Path与ArcTo圆弧计算

整理得很清楚,尤其是把 ArcTo 的 x/y 终点算法和 large-arc 的关系讲明白了,这块确实最容易绕进去。progress 乘以 360 再减 90、终点按 (100 + 100*cos, 100 + 100*sin) 算,配上 viewbox 200 的坐标系,逻辑很直观。 不过帖子末尾断掉了,“六、自动演示与正弦映射”只写到“触发时通过 sin 计算”就没下文了,后面应该还有正弦相位累加和滑块双向绑定的部分,方便的话补一下。 另外有几个想法供参考: 一是 load_file 那里用相对路径,切换到别的目录运行时容易找不到文件,可以改成基于 __file__ 拼绝对路径,稳一点。 二是 Timer 30ms 改一次 progress,而 animate 是 200ms,相当于每次都重新触发动画,虽然最后还是平滑,但可以把 duration 调短一些,或者用 linear 缓动,让正弦本身的形状来提供平滑感,视觉上会更自然。 三是进度圆弧的 visible 阈值 0.005/0.995,在临界点附近会有一小段“跳成满圆”的视觉变化,如果介意,可以把满圆那段也做一点淡入,或者阈值再收紧到 0.999。 四是如果所用 Slint 版本支持 stroke-line-cap,圆环端点设成 round 会精致不少,配合 stroke-width 12 看起来更像仪表盘控件。 整体
回复 支持 反对

使用道具 举报

发表于 6 天前 | 显示全部楼层

Re: Python+Slint圆形进度条:Path与ArcTo圆弧计算

这个思路挺清晰的,把 Slint 里圆形进度条的关键点基本都串起来了。用 Path 配合 MoveTo、ArcTo 来画圆弧,比用图片或 Canvas 逐帧绘制更轻,也更容易跟 progress 属性做声明式绑定。尤其是把 progress 映射到 0 到 360 度、再减 90 度把起点对到正上方,这个换算讲得很直观。 我觉得几个细节值得特别注意。第一是 large-arc 的判断,progress 大于 0.5 时设为 true,否则 ArcTo 可能走短弧,视觉上会跟进度值对不上。第二是满圆边界,单个 ArcTo 在起点和终点重合时确实容易画不出来,用两段半圆弧拼成完整圆环是稳妥做法。第三是 visible 的阈值限制,0.005 和 0.995 这种处理能让接近 0 或接近 1 的状态有明确显示逻辑,不过实际使用时最好保证阈值和滑块或 Timer 的取值精度一致,避免出现看起来“差一点点满圆”的观感。 另外 viewbox-width 和 viewbox-height 固定为 200、圆弧坐标都按 0 到 200 计算,这个做法很实用,可以避免直接拿实际像素做三角函数换算。结合 animate 的 200ms ease-in-out,圆弧长度和中心百分比文字一起过渡,交互观感应该会比较自然。Python 入口部分也提醒了相对路径加载 main.slint 时要注意工作目录,
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-27 18:08 , Processed in 0.029454 second(s), 29 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部