- 桌面应用
【免费下载链接】yasb
A highly configurable Windows status bar written in Python.
导读
Media Widget 是 YASB(一个用 Python 编写的 Windows 状态栏)中用于展示“正在播放”信息的核心小组件:它能在任务栏上显示当前播放的歌曲或视频标题、艺术家与专辑封面,内置上一曲 / 播放暂停 / 下一曲按钮,支持超长标题滚动,并可弹出包含进度条、时间标签、来源应用与逐应用音量滑杆的播放控制菜单。本文以官方文档为主体,结合仓库源码(widget 实现、校验模型、SMTC 媒体服务)逐一讲解全部配置项、默认值与底层工作原理,读完你可以独立完成 Media Widget 的接入、外观定制与故障排查。
图片预览:下图为 Media Widget 在状态栏上的实际呈现(来源:文档原图)。
一、组件能力与数据来源
Media Widget 是一个“播放器控制控件”,它展示当前正在播放的歌曲或视频,并具备以下能力:
- 显示专辑封面(缩略图),支持透明度、圆角、边缘渐隐等效果;
- 内联媒体按钮:上一曲(
prev_track)、播放 / 暂停(play/pause)、下一曲(next_track); - 超长标题滚动(
scrolling_label); - 弹出媒体菜单(popup):包含封面大图、标题 / 艺术家 / 来源应用、进度滑块、播放时间与逐应用音量控制。
它的数据来自 Windows 系统的SMTC(Global System Media Transport Controls)。在源码中,WindowsMedia是一个以QSingleton实现的单例服务(src/core/widgets/services/media/media.py),通过winrt.windows.media.control的GlobalSystemMediaTransportControlsSessionManager请求会话、订阅会话列表变化 / 当前会话变化 / 媒体属性 / 时间线 / 播放信息五类事件,并以约 0.1 秒(REFRESH_INTERVAL = 0.1)的轮询间隔插值当前播放位置(_interpolate_and_emit),再把状态通过 Qt 信号分发给 Widget。
重要限制(原文保留):时间线与 seek 功能只有在播放器通过 Windows 媒体 API 上报 position 和 duration 时才会生效。很多浏览器和部分应用不上报,或上报的是无效值,因此 seek 滑块可能会保持禁用、跳动或完全不动。YASB 只展示系统给到的数据——如果来源应用没有暴露可用的时间线,Widget 也无能为力。
此外源码中还有一层保护:MAX_TIMLINE_DURATION = 604800(7 天)——当会话时长超过 7 天或时间线不可用时,进度条与时间线容器会被隐藏(见 media.py 及show_menu中的判断逻辑)。
二、完整配置项总览
将media段写入 YASB 的config.yaml(位于C:/Users/{username}/.config/yasb/或YASB_CONFIG_HOME指定的目录,详见 Configuration.md),type固定为"yasb.media.MediaWidget"。
| 选项 | 类型 | 默认值(文档) | 说明 |
|---|---|---|---|
label | string | "{artist}{s}{title}" | 主标签格式字符串。 |
label_alt | string | "{title}" | 备用标签格式字符串。 |
separator | string | " - " | 动态分隔符{s}的实际文本;位于标签开头或结尾时会被自动剥离,详见下文。 |
class_name | string | "" | 附加到组件上的自定义 CSS 类名。 |
max_field_size | dict | — | 标签最大字段长度。 |
max_field_size.label | integer | 20 | 主标签最大长度。 |
max_field_size.label_alt | integer | 30 | 备用标签最大长度。 |
max_field_size.truncate_whole_label | boolean | false | 超长时是否截断整个标签(而不是只截断超出的字段)。 |
show_thumbnail | boolean | true | 是否显示媒体缩略图。 |
controls_only | boolean | false | 是否只显示媒体控制按钮。 |
controls_left | boolean | true | 控制按钮是否位于左侧。 |
controls_hide | boolean | false | 是否隐藏媒体控制按钮。 |
hide_empty | boolean | true | 无媒体信息时是否隐藏整个组件。 |
thumbnail_alpha | integer | 50 | 缩略图的透明度(alpha)。 |
thumbnail_padding | integer | 8 | 缩略图周围的内边距。 |
thumbnail_corner_radius | integer | 0 | 缩略图圆角半径,0 为直角。 |
symmetric_corner_radius | boolean | false | 是否对缩略图四角使用对称圆角。 |
thumbnail_edge_fade | boolean | false | 是否对缩略图应用边缘渐隐效果。 |
icons | dict | — | 媒体控制按钮图标。 |
icons.prev_track | string | \uf048 | 上一曲按钮图标。 |
icons.next_track | string | \uf051 | 下一曲按钮图标。 |
icons.play | string | \uf04b | 播放按钮图标。 |
icons.pause | string | \uf04c | 暂停按钮图标。 |
media_menu | dict | 见下 | 媒体菜单(弹出层)配置。 |
media_menu_icons | dict | 见下 | 媒体菜单中的图标配置。 |
scrolling_label | dict | 见下 | 标签滚动配置。 |
progress_bar | dict | 见下 | 组件内嵌进度条配置。 |
callbacks | dict | 见下 | 鼠标事件回调。 |
源码校验模型中的真实默认值
文档表格中的“默认值”对应官方推荐预设;代码层面真正的默认值由 Pydantic 校验模型定义(src/core/validation/widgets/yasb/media.py),两者不完全一致,配置时以你显式写入的值优先:
label默认为"{title}",label_alt默认为"{artist} - {title}"(文档表分别为{artist}{s}{title}与{title});hide_empty默认False(文档表为true);max_field_size.label默认15(取值范围 0–200),truncate_whole_label默认True;thumbnail_alpha取值范围0–255;thumbnail_padding取值范围0–200;thumbnail_corner_radius取值范围0–100;media_menu默认blur=True、round_corners=True、border_color="System"、alignment="right"、thumbnail_size=100、max_title_size=150、max_artist_size=40、max_source_size=16;scrolling_label.update_interval_ms默认 33(代码内会被钳制在 4–1000ms),style默认"left",separator默认空格;progress_bar.enabled默认False,alignment默认"bottom"。
官方安装向导预设(src/core/setup/widgets_config.py)中,Media Widget 使用label: "{title}{s}{artist}"、hide_empty: True、controls_hide: True、thumbnail_alpha: 100、thumbnail_edge_fade: True、media_menu.alignment: "center"且show_volume_slider: True,可作为一份开箱即用的参考配置。
三、示例配置(可直接复制)
media: type: "yasb.media.MediaWidget" options: label: "{title}{s}{artist}" label_alt: "{title}" separator: " - " hide_empty: true callbacks: on_left: "toggle_label" on_middle: "do_nothing" on_right: "do_nothing" max_field_size: label: 20 label_alt: 30 show_thumbnail: true controls_only: false controls_left: true controls_hide: false thumbnail_alpha: 80 thumbnail_padding: 8 thumbnail_corner_radius: 16 icons: prev_track: "\ue892" next_track: "\ue893" play: "\ue768" pause: "\ue769" media_menu: blur: false round_corners: true round_corners_type: "normal" border_color: "system" alignment: "right" direction: "down" offset_top: 6 offset_left: 0 thumbnail_corner_radius: 8 thumbnail_size: 120 max_title_size: 60 max_artist_size: 20 max_source_size: 16 show_source: true show_volume_slider: true media_menu_icons: play: "\ue768" pause: "\ue769" prev_track: "\ue892" next_track: "\ue893" scrolling_label: enabled: false update_interval_ms: 33 style: "left" # 可选 "left"、"right"、"bounce"、"bounce-ease" separator: " | " label_padding: 0 always_scroll: false # 缓动曲线参数参考:https://www.desmos.com/calculator/j7eamemxzi ease_slope: 20 ease_pos: 0.8 ease_min: 0.5在状态栏的widgets段(left/center/right任一区)中引用该段即可启用,例如:
bars: bar: widgets: right: ["media"]四、各子配置项详解
4.1 标签与字段(label / label_alt / separator / max_field_size)
- label / label_alt:格式字符串,可用
{title}、{artist}等占位符动态插入媒体信息;label_alt用于展示补充信息,通常配合toggle_label回调在两条标签间切换。 - separator 与
{s}:{s}是动态分隔符占位符。当某段信息在来源端缺失时,为避免标签首尾残留悬空分隔符(如" - - 歌名"),Widget 会调用分词器 tokenizer.py 的clean_string:先按{占位符} / 字面量分词,再丢弃值为空的占位符,最后只保留“前后都有非空占位符”的分隔符。这就是文档所说“动态分隔符会被自动剥离”的实现原理。 - max_field_size:
label/label_alt为对应标签的最大字符数,超长时截断并追加...(见_format_max_field_size,截断方式为text[:max_size - 3] + "...");truncate_whole_label: true时对整条格式化结果截断,否则仅截断单个超长字段。注意:启用scrolling_label后,_format_max_field_size会直接返回原文(滚动标签自身依赖max_field_size.label作为宽度上限)。
4.2 缩略图(show_thumbnail 与视觉处理)
缩略图在 media.py 的_crop_thumbnail中按以下流程处理:
- 按
thumbnail_padding与当前活动标签宽度等比缩放(Image.LANCZOS高质量重采样); - 按组件容器高度纵向居中裁剪;
- 用
thumbnail_alpha值构造 alpha 通道; - 若启用
thumbnail_edge_fade,则对 alpha 通道应用左右两侧约 30% 宽度的渐隐(与容器背景形成平滑过渡,此时圆角不生效); - 否则当
thumbnail_corner_radius > 0时生成圆角遮罩——默认只圆“靠近按钮的对侧”两角(controls_left: true时圆右侧两角),symmetric_corner_radius: true时四角统一圆角。
另外,当会话没有封面时,popup 会回退到内置的 media.png 占位图(_build_empty_thumbnail)。
4.3 控制按钮(controls_only / controls_left / controls_hide / icons)
- 三个按钮按
controls_left决定排在缩略图与文本的左侧还是右侧;controls_only: true时隐藏标签、缩略图与进度条,只保留按钮(源码中会hide()对应控件)。 controls_hide: true时完全不创建按钮(_create_media_button直接返回None),适合只需要信息展示的场景。- 按钮的可用 / 禁用状态来自 SMTC 的
PlaybackInfo快照字段(controls_prev_enabled/controls_play_enabled/controls_next_enabled),不可用时按钮会带上disabledCSS 类。 - 播放 / 暂停按钮图标会根据
is_playing在icons.play与icons.pause间切换(同一逻辑也作用于 popup 的media_menu_icons)。
4.4 媒体菜单(media_menu)
弹出层由工具类PopupWidget(src/core/utils/utilities.py)创建:无边框、置顶、支持背景模糊(acrylic)、圆角与边框色;setPosition按alignment(left / center / right)、direction(up / down)、offset_top/offset_left相对父组件定位。菜单内容在show_menu中动态构建:
- 左侧为按
thumbnail_size裁剪成方形的封面大图(_create_thumbnail_for_popup),可点击唤起来源应用(_open_media_source); - 右侧文本区包含
max_title_size/max_artist_size限制的标题与艺术家; - 控制区含上一曲 / 播放暂停 / 下一曲按钮,以及
show_source: true时的来源应用标签; - 底部为时间线容器:
progress-slider(内部精度 0–1000)加playback-time.current/playback-time.total两个时间标签;用户拖动时通过_on_slider_released把百分比换算成秒,并以 100 纳秒为单位调用try_change_playback_position_async完成 seek; show_volume_slider: true时右侧会出现“逐应用音量”竖滑杆:先用会话的 AUMID 通过pycaw的AudioUtilities.GetAllSessions()匹配音频会话(优先按 AUMID,失败则回退按进程可执行文件名),再用SimpleAudioVolume接口读取 / 设置音量并切换静音(对应mute/unmute图标)。- 无任何会话时,菜单显示
no-media类的“No media playing”占位文案。 - 在组件与 popup 上滚动鼠标滚轮可在多个媒体会话间切换(
WheelEventFilter,向上滚下一个、向下滚上一个)。
media_menu: blur: false # 是否对弹出背景应用模糊效果。 round_corners: true # 是否圆角弹出层。 round_corners_type: "normal" # "normal" 或 "small"(仅 Win11) border_color: "system" # 弹出层边框颜色,可为 HEX、None 或 "system"。 alignment: "right" # 相对组件的对齐方式:"left"、"center" 或 "right"。 direction: "down" # 弹出方向:"up" 或 "down"。 offset_top: 6 # 距组件的垂直偏移。 offset_left: 0 # 距组件的水平偏移。 thumbnail_corner_radius: 8 # 弹出层封面的圆角半径。 thumbnail_size: 120 # 弹出层封面尺寸。 max_title_size: 60 # 弹出层标题最大长度。 max_artist_size: 20 # 弹出层艺术家最大长度。 max_source_size: 16 # 弹出层来源应用名最大长度。 show_source: true # 是否显示媒体来源应用名(由系统/应用元数据解析,如 Spotify、Firefox)。 show_volume_slider: false # 是否显示逐应用音量滑杆。4.5 媒体菜单图标(media_menu_icons)
media_menu_icons: play: "\ue768" # 弹出层播放按钮图标。 pause: "\ue769" # 弹出层暂停按钮图标。 prev_track: "\ue892" # 弹出层上一曲按钮图标。 next_track: "\ue893" # 弹出层下一曲按钮图标。 mute: "\ue994" # 弹出层静音按钮图标。 unmute: "\ue74f" # 弹出层取消静音按钮图标。4.6 滚动标签(scrolling_label)
滚动能力来自工具类ScrollingLabel(src/core/utils/utilities.py),支持四种style:
left/right:循环平移滚动,超出部分用separator拼接重复文本,always_scroll: true时无论文本是否超宽都滚动;bounce:来回弹跳,两侧各留label_padding个字符的边距;bounce-ease:带缓动的弹跳,速度由ease_slope(坡度)、ease_pos(拐点位置)、ease_min(最小速度比例)三条参数控制,公式为(1 + tanh(-slope·(x - pos)))·(1 - min)/2 + min,可通过 Desmos 交互曲线直观调参。
scrolling_label: enabled: false # 是否启用滚动标签。 update_interval_ms: 33 # 滚动更新间隔(毫秒),有效范围 4–1000。 style: "left" # 滚动样式:"left"、"right"、"bounce" 或 "bounce-ease"。 separator: " | " # "left"/"right" 样式中重复文本间的分隔符。 label_padding: 1 # "bounce"/"bounce-ease" 样式两侧的填充字符数,默认各 1 个字符。 ease_slope: 20 # bounce 缓动坡度。缓动曲线参考:https://www.desmos.com/calculator/j7eamemxzi ease_pos: 0.8 # bounce 缓动曲线位置。 ease_min: 0.5 # bounce 缓动曲线的最小值。滚动标签注意事项(原文保留):
- 滚动标签使用
max_field_size限制自身尺寸; - 滚动标签会禁用
thumbnail_padding,此时应改用.media-widget .label { margin: ... }控制间距。
4.7 组件内嵌进度条(progress_bar)
在组件本身(而非 popup)底部叠加一层细进度条,指示当前播放进度;只有当会话启用时间线且时长在 7 天以内时才显示。alignment控制它在容器内的垂直位置。
progress_bar: enabled: false # 是否启用组件上的进度条。 alignment: "bottom" # 进度条在组件容器内的对齐方式:"top"、"bottom" 或 "center"。4.8 可用回调(callbacks)
| 回调 | 说明 |
|---|---|
toggle_label | 在主标签与备用标签间切换显示。 |
toggle_play_pause | 切换播放 / 暂停状态。 |
toggle_media_menu | 打开 / 关闭媒体菜单弹出层。 |
open_media_source | 打开正在播放媒体的来源应用(按 AUMID 激活)。 |
do_nothing | 占位回调,触发时不做任何事。 |
callbacks的键为on_left、on_middle、on_right,分别绑定左 / 中 / 右键。源码细节:toggle_label仅在非controls_only模式下注册;open_media_source通过activate_app_by_aumid激活会话(见 media.py)。
五、来源应用名的解析与“按来源定制样式”
popup 中的source标签显示来源应用名,其解析链在 source_apps.py 中定义,按优先级依次尝试:
AppInfo.get_from_app_user_model_id(aumid)的display_name;shell:AppsFolder的ParseName名称(保证 PWA 不被错误标记为浏览器);- 由 AUMID 反查进程可执行文件,再取进程的
FileDescription; - 由窗口 AUMID(Firefox / Zen 等哈希)反查 PID 得到应用名;
- 最后退化为
_humanize(去掉扩展名、取!后段、下划线转空格等)。
解析结果会按 AUMID 缓存。而get_source_app_class_name会把显示名转为小写并将空格替换为连字符——因此样式表中“来源类名”与组件中看到的来源名一一对应:例如"Windows Media"对应.source.windows-media。这意味着你可以为每个播放器定制专属配色(见下文示例中的.media-menu .source.spotify、.source.chrome等)。
六、样式定制(CSS Classes)
Widget 渲染为 Qt Widgets,可使用 Qt 样式表(qss)定制外观,整体主题样式相关约定见 Styling.md。
6.1 可用样式类
.media-widget {} .media-widget .widget-container {} .media-widget .label {} .media-widget .label.alt {} .media-widget .btn.play {} .media-widget .btn.prev {} .media-widget .btn.next {} .media-widget .btn.disabled {} .media-widget .progress-bar { } .media-widget .progress-bar::chunk {} .media-menu {} .media-menu .no-media {} .media-menu .title {} .media-menu .artist {} .media-menu .source {} .media-menu .btn.play {} .media-menu .btn.prev {} .media-menu .btn.next {} .media-menu .btn.disabled {} .media-menu .thumbnail {} .media-menu .media-timeline-container {} .media-menu .playback-time {} .media-menu .playback-time.current {} .media-menu .playback-time.total {} .media-menu .progress-slider {} .media-menu .progress-slider::groove {} .media-menu .progress-slider::sub-page {} .media-menu .progress-slider::handle {} .media-menu .progress-slider::handle:hover {} .media-menu .app-volume-container {} .media-menu .app-volume-container .volume-slider {} .media-menu .app-volume-container .volume-slider::groove {} .media-menu .app-volume-container .volume-slider::sub-page {} .media-menu .app-volume-container .volume-slider::handle {} .media-menu .app-volume-container .volume-slider::handle:hover {} .media-menu .app-volume-container .mute-button {}6.2 组件样式示例
.media-widget { padding: 0; margin: 0; } .media-widget .label { color: #d2d6e2; padding: 0px; padding-right: 4px; font-size: 12px; } .media-widget .btn { color: #9498a8; padding: 0 4px; margin: 0; font-family: "Segoe Fluent Icons"; font-weight: 400; } .media-widget .btn:hover { color: #babfd3; } .media-widget .btn.play { font-size: 16px; } .media-widget .btn.disabled:hover, .media-widget .btn.disabled { color: #4e525c; font-size: 12px; background-color: rgba(0, 0, 0, 0); } .media-widget .progress-bar { max-height: 2px; background-color: transparent; margin-left: 5px; border: none; } .media-widget .progress-bar::chunk { background-color: #0078D4ee; border-radius: 2px; }6.3 弹出菜单样式示例
.media-menu { min-width: 440px; max-width: 440px; background-color: rgba(31, 39, 49, 0.5); } .media-menu .title, .media-menu .artist, .media-menu .source { font-size: 14px; font-weight: 600; margin-left: 10px; font-family: 'Segoe UI'; } .media-menu .artist { font-size: 13px; color: #6c7086; margin-top: 0px; } .media-menu .source { font-size: 11px; color: #000; border-radius: 3px; background-color: #bac2de; padding: 2px 4px; font-weight: 600; font-family: 'Segoe UI'; margin-top: 10px; } /* 来源类名的规则与组件中看到的来源名一致:空格换成连字符并转为小写。 示例:"Windows Media" 变成 "windows-media" */ .media-menu .source.aimp { background-color: #6f42c1; color: #ffffff; } .media-menu .source.apple-music { background-color: #fa2b56; color: #ffffff; } .media-menu .source.brave { background-color: #fb542b; color: #ffffff; } .media-menu .source.chrome { background-color: #4285f4; color: #ffffff; } .media-menu .source.edge { background-color: #0078d4; color: #ffffff; } .media-menu .source.firefox { background-color: #ff7139; color: #ffffff; } .media-menu .source.foobar2000 { background-color: #444444; color: #ffffff; } .media-menu .source.media-player { background-color: #0078d4; color: #ffffff; } .media-menu .source.murglar { background-color: #8a8a8a; color: #ffffff; } .media-menu .source.musicbee { background-color: #ffcc00; color: #000000; } .media-menu .source.nsmusics { background-color: #e64a19; color: #ffffff; } .media-menu .source.opera { background-color: #ff1b2d; color: #ffffff; } .media-menu .source.qobuz { background-color: #003a6f; color: #ffffff; } .media-menu .source.spotify { background-color: #1db954; color: #ffffff; } .media-menu .source.tidal { background-color: #000000; color: #ffffff; } .media-menu .source.winamp { background-color: #f1a11b; color: #000000; } .media-menu .source.youtube { background-color: #ff0000; color: #ffffff; } .media-menu .source.youtube-music { background-color: #c51f1f; color: #ffffff; } .media-menu .source.zen { background-color: #2ecc71; color: #000000; } .media-menu .btn { font-family: "Segoe Fluent Icons"; font-size: 14px; font-weight: 400; margin: 10px 2px 0px 2px; min-width: 40px; max-width: 40px; min-height: 40px; max-height: 40px; border-radius: 20px; } .media-menu .btn.prev { margin-left: 10px; } .media-menu .btn:hover { color: white; background-color: rgba(255, 255, 255, 0.1); } .media-menu .btn.play { background-color: rgba(255, 255, 255, 0.1); font-size: 20px } .media-menu .btn.disabled:hover, .media-menu .btn.disabled { color: #4e525c; background-color: rgba(0, 0, 0, 0); } .media-menu .playback-time { font-size: 13px; font-family: 'Segoe UI'; color: #7f849c; margin-top: 20px; min-width: 100px; } .media-menu .progress-slider { height: 10px; margin: 5px 4px; border-radius: 3px; } .media-menu .progress-slider::groove { background: transparent; height: 2px; border-radius: 3px; background: rgba(255, 255, 255, 0.1); } .media-menu .progress-slider::groove:hover { background: transparent; height: 6px; border-radius: 3px; background: rgba(255, 255, 255, 0.2); } .media-menu .progress-slider::sub-page { background: white; border-radius: 3px; height: 4px; } .media-menu .app-volume-container { background-color: rgba(255, 255, 255, 0.05); padding: 8px 6px; border-radius: 16px; margin-left: 10px; } .media-menu .app-volume-container .volume-slider::groove { background: rgba(255, 255, 255, 0.1); width: 2px; border-radius: 3px; } .media-menu .app-volume-container .volume-slider::add-page { background: white; border-radius: 3px; } .media-menu .app-volume-container .volume-slider::groove:hover { background: rgba(255, 255, 255, 0.1); width: 6px; border-radius: 3px; } .media-menu .app-volume-container .volume-slider::sub-page { background: rgba(255, 255, 255, 0.1); border-radius: 3px; } .media-menu .app-volume-container .mute-button, .media-menu .app-volume-container .unmute-button { font-size: 16px; color: #ffffff; font-family: "Segoe Fluent Icons"; margin-top: 4px; } .media-menu .app-volume-container .unmute-button { color: #a0a0a0; }注意(原文保留):以上样式示例使用 Segoe Fluent Icons 字体作为按钮图标,你也可以按设计需求使用任何其它图标字体或自定义图标。
七、常见调优建议与排错要点
- 标签不更新 / 按钮灰置:按钮可用状态来自播放器上报的 SMTC
PlaybackInfo;如果来源应用不上报,按钮会带disabled类(可用.media-widget .btn.disabled弱化其视觉权重)。 - seek 滑块不动或跳动:属于来源应用不提供有效时间线的表现,不是配置错误,参见第一节的官方说明。
- 切换会话:在组件或 popup 上滚动滚轮即可在多个媒体会话(如浏览器标签页、Spotify、本地播放器)间切换;源码中 popup 的
WheelEventFilter在切换会话后会重建菜单以同步内容。 - 想要更精简的竖向“唱片封面风”方案:YASB 还提供同源的
media_lite组件("yasb.media_lite.MediaWidget"),支持封面 + 标题/艺术家单行展示与弹出播放器,详见 Media Lite Widget 文档-Media-Lite.md)。 - 图标字体:示例默认使用
"Segoe Fluent Icons";自定义图标时同步修改icons/media_menu_icons与对应字体族即可。 - 配置校验:所有选项均经过 Pydantic 模型(src/core/validation/widgets/yasb/media.py)校验,非法取值(如越界的
thumbnail_alpha、非法style枚举)会在启动时报错,修改配置后保存会自动重载(见 watcher.py 的文件监听机制)。
- 桌面应用
【免费下载链接】yasb
A highly configurable Windows status bar written in Python.
相关推荐
yasb Media Lite 媒体控件配置指南:紧凑竖版封面栏与弹出式播放器的完整实战
yasb Media Lite 媒体控件配置指南:紧凑竖版封面栏与弹出式播放器的完整实战 本文是 yasb(基于 Python 与 Qt 的高度可配置 Wind
桌面应用YASB状态栏完全配置指南:打造专属桌面神器
YASB状态栏完全配置指南:打造专属桌面神器 YASB(Yet Another Status Bar)是一款基于Python开发的高度可配置Windows状态栏
桌面应用ANIMATED TAB BAR与AVFoundation集成:媒体播放状态标签栏指示
ANIMATED TAB BAR与AVFoundation集成:媒体播放状态标签栏指示 你是否曾在使用媒体类App时,因无法直观判断播放状态而反复切换页面?本文
移动开发UI组件前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考