ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Awesome WM 自定义 Widget 开发指南:基于 `wibox.widget.base` 的回调、信号与绘制协议

Awesome WM 自定义 Widget 开发指南:基于 `wibox.widget.base` 的回调、信号与绘制协议 操作系统【免费下载链接】awesomeawesome window manager项目地址https://gitcode.com/gh_mirrors/awes/awesome点击查看免费下载本文是 Awesome WM 官方文档 docs/04-new-widgets.md 的深度展开版。Awesome WM 的整个 UI状态栏、标题栏、通知、托盘等都由 widget 组成而所有 widget 都构建在wibox.widget.base之上。读完本文你将掌握如何从零编写一个可嵌入 wibar / popup / 通知的自定义 widget理解:fit/:draw回调协议、widget::layout_changed与widget::redraw_needed两个核心信号、面向布局的:layout与place_widget_at以及控制子 widget 绘制的四个钩子回调并了解这些机制在 lib/wibox/widget/base.lua 与 lib/wibox/hierarchy.lua 中的底层实现。一切从wibox.widget.base.make_widget开始所有 widget 都必须由wibox.widget.base.make_widget函数生成这是唯一的正规入口。它在 lib/wibox/widget/base.lua 中实现负责完成三件基础工作信号体系生成的 widget 基于gears.object自带信号收发能力。make_widget内部会自动连接widget::updated信号并把它转发为widget::layout_changed与widget::redraw_needed见 base.lua 的make_widget实现同时初始化widget::layout_changed/widget::redraw_needed/button::press/button::release/mouse::enter/mouse::leave等信号的语义。鼠标输入make_widget会自动连接button::press与button::release信号到内部按钮分发器base.handle_button并设置基础状态visible true、opacity 1、is_widget true、forced_width/forced_height nil。通用属性所有 widget 共享的属性在这里被初始化包括children、all_children、forced_width、forced_height、opacity、visible与buttons。创建后返回的 widget 拥有一个:buttons成员函数用于向 widget 注册一组鼠标按钮事件通常配合awful.button使用。从源码结构看make_widget还会把base.widget表中的全部函数如add_button、set_visible、get_all_children等复制到新对象上。make_widget的完整签名支持三个可选参数make_widget(proxy, widget_name, args)。其中proxy用于创建一个代理 widget外观与被代理 widget 完全一致但拥有独立信号生命周期args表可传入enable_properties是否启用自动 getter/setter默认启用与class。此外还有一个便捷构造器wibox.widget.base.empty_widget()返回一个不占空间、不绘制任何内容的空 widget。核心回调一:fit—— 向布局协商尺寸自定义 widget 需要实现的第一组成员函数是:fit。当布局系统需要确定 widget 应该占据多大空间时会调用它。参数是当前可用的空间width、height返回值是 widget 期望的尺寸。function widget:fit(context, width, height) -- Find the maximum square available local m math.min(width, height) return m, m end官方文档特别强调两个要点:fit只是建议。布局系统不一定采纳它返回的尺寸因此 widget 必须能够在任何尺寸下正确绘制自己。:fit必须是确定性的deterministic。对同样的参数反复调用必须返回同样的结果。如果 widget 内部状态更新导致:fit的结果将要变化必须发出widget::layout_changed信号见下文让布局系统重新协商而不是让:fit悄悄返回不同的值。从实现层面看base.lua 中的base.fit_widget(parent, context, widget, width, height)是:fit的唯一合法入口。它做了四件事记录父 widget 对子 widget 的依赖关系record_dependency用于缓存失效的级联若 widget 不可见直接返回0, 0将尺寸参数与返回值都裁剪到非负范围同时过滤 NaN 等非法输入通过gears.cache缓存:fit的结果并在 widget 收到widget::layout_changed时由clear_caches递归清理所有依赖它的缓存。如果 widget 没有fit方法fit_widget会退化为基于子 widget 尺寸计算调用layout_widget布局所有子 widget取覆盖所有子 widget 所需的最大宽高。因此纯容器如wibox.container.background可以不实现:fit。核心回调二:draw—— 用 Cairo 绘制内容:draw是真正把 widget 画上屏幕的回调。参数是绘制上下文context、Cairo 上下文cr以及 widget 的宽高。function widget:draw(context, cr, width, height) cr:move_to(0, 0) cr:line_to(width, height) cr:move_to(0, height) cr:line_to(width, 0) cr:stroke() end官方文档为:draw约定了两条关键的坐标系与裁剪规则坐标系已经就位Cairo 上下文被设置为 widget 左上角在(0, 0)、右下角在(width, height)无需做任何额外变换直接以本地坐标绘制即可。裁剪已被应用绘制该 widget 时负责布局它的 layout 已经为其注册了绘制区域cr上带有合适的 clip因此:draw不可能画到自己的注册区域之外。不要调用cr:reset_clip()——否则重绘将无法正确处理。绘制所需的全部图形能力来自 Cairo路径cr:move_to/cr:line_to/cr:arc、上下文属性cr:set_source_rgb等、pattern、变换transformation与算子operator。也可以使用 Pango 绘制文本。仓库中大量内置 widget 就是这套协议的范例例如 lib/wibox/widget/textbox.lua、lib/wibox/widget/progressbar.lua 以及 lib/wibox/container/arcchart.lua。两个核心信号widget::layout_changed与widget::redraw_needed自定义 widget 的更新机制完全依赖两个预定义信号语义定义在 base.lua 的信号注释中信号触发时机后果widget::layout_changed:fit或:layout的结果将要发生变化重新协商布局受影响区域被重绘同时清理gears.cache中该 widget 及所有依赖它的缓存widget::redraw_needed仅内容需要重绘但:fit/:layout的结果不变直接触发:draw重绘不重新布局官方文档给出的经验法则是如果拿不准就把两个信号都发出去这样永远安全。例如 lib/wibox/container/background.lua 中修改bg时发出widget::redraw_needed而修改 shape 时同时发出两个信号lib/wibox/container/border.lua 中set_width/set_color也会按需组合这两个信号。从底层看widget::layout_changed与缓存失效强绑定make_widget中为每个 widget 注册了widget::layout_changed→clear_caches的处理器而clear_caches会通过依赖表递归清理所有父级缓存。重绘本身则是异步合并的在 lib/wibox/drawable.lua 中收到重绘需求后通过timer.delayed_call调度do_redraw并用_redraw_pending标记保证同一个帧内多次请求只重绘一次。这也解释了为什么:fit必须确定性——重绘是异步批量的如果:fit结果不稳定缓存与布局将产生不可预测的连锁变化。布局类 Widget:layout、:fit_widget与place_widget_at如果 widget 只是画点什么以上内容已经足够。但如果要实现一个布局layout即放置其他 widget 的 widget则需要实现:layout回调。-- For readability local base wibox.widget.base function widget:layout(width, height) local result {} table.insert(result, base.place_widget_at(child, width/2, 0, width/2, height)) return result endbase.place_widget_at(widget, x, y, width, height)返回一个不透明的放置描述表:layout需要把所有子 widget 的放置信息收集到一张表中返回。官方文档允许把子 widget 放到超出自身范围的位置例如负坐标或自身尺寸的两倍——当你需要在自身范围外绘制时就用这个机制。与:fit对应这里也有两条实现规则:layout的结果变化时必须发出widget::layout_changed。永远不要直接调用其他 widget 的:fit必须通过base.fit_widget。因为只有fit_widget会记录父子的依赖关系、处理强制尺寸并走缓存直接调用会绕过缓存系统导致重绘失效时无法级联刷新。从源码看base.lua 中的place_widget_at实际上是对place_widget_via_matrix的封装内部通过gears.matrix.create_translate(x, y)生成变换矩阵。而base.layout_widget(parent, context, widget, width, height)是:layout的合法入口同样带缓存与依赖记录。如果你需要更复杂的放置旋转、缩放等可以直接使用base.place_widget_via_matrix(widget, mat, width, height)传入自定义矩阵。控制子 Widget 绘制四个钩子回调当一个布局 widget 要影响子 widget 的绘制方式时有四个可选的钩子回调参数与:draw基本一致function widget:before_draw_children(context, cr, width, height) function widget:after_draw_children(context, cr, width, height) function widget:before_draw_child(context, index, child, cr, width, height) function widget:after_draw_child(context, index, child, cr, width, height)注意两个差异点before_draw_child/after_draw_child额外携带index子 widget 序号与child子 widget 对象两个参数。这四个回调执行期间Cairo 上下文的 clip 区域更大覆盖所有子 widget 的区域。它们应当只影响子 widget 的绘制方式不应改变绘制覆盖的面积。官方文档给出最典型的用法——把子 widget 半透明地绘制出来function widget:before_draw_children(context, cr, width, height) cr:push_group() end function widget:after_draw_children(context, cr, width, height) cr:pop_group_to_source() cr:paint_with_alpha(0.5) endbefore_draw_children中cr:push_group()把子 widget 的绘制收进一个临时组after_draw_children中cr:pop_group_to_source()取回该组并用paint_with_alpha(0.5)以 50% 透明度整体刷回从而实现整组半透明效果。官方文档用伪代码完整描述了重绘时的调用序列这也是 lib/wibox/hierarchy.lua 中draw阶段的真实执行顺序对应源码中before_draw_children→ 逐子before_draw_child/after_draw_child→after_draw_children的调用链widget:draw(context, cr, width, height) widget:before_draw_children(context, cr, width, height) for child do widget:before_draw_child(context, cr, child_index, child, width, height) cr:save() -- Draw child and all of its children recursively, taking into account the -- position and size given to base.place_widget_at() in :layout(). cr:restore() widget:after_draw_child(context, cr, child_index, child, width, height) end widget:after_draw_children(context, cr, width, height)仓库中的真实例子包括 lib/wibox/container/arcchart.lua用before_draw_children/after_draw_children在绘制前后设置与恢复圆弧状态和 lib/wibox/container/background.lua通过cr:push_group()/cr:pop_group()实现带圆角或形状裁剪的背景绘制pop_group弹出的正是before_draw_children里压入的组。:set_children与声明式布局系统的契约wibox.widget.base中有一个默认实现为空的操作方法set_childrenfunction base.widget:set_children(children) -- luacheck: no unused -- Nothing on purpose end它扮演的角色很特殊使用声明式布局系统wibox.widget { ... }/:setup { ... }设置 widget 时set_children会被递归调用见 base.lua 中drill解析声明式表后调用l:set_children(widgets)的代码。因此官方文档要求自定义 widget 的set_children必须定义良好通常应挂钩到内部的:add/:add_widget方法如果 widget 不接受子 widget则应把set_children重写为什么都不做。注意默认实现什么都不做与定义良好并不矛盾——对无子 widget 的类型而言默认实现即正确的覆盖。只有那些实际持有子 widget 的布局与容器才需要真正实现它例如wibox.layout.fixed、wibox.container.background等。声明式布局的完整语法与组合方式见 docs/03-declarative-layout.md。综合示例一个可嵌入状态栏的完整自定义 Widget把上面的协议串起来可以写一个完整的自定义 widget。以下例子结合了官方文档的:fit/:draw用法与声明式语法wibox.widget.base.make_widget作为layout字段意味着这个表会被解析为一个基于make_widget创建的原始 widget其属性会被设置为fit/draw回调-- 一个红色圆形 widget占据可用高度的正方形区域 local circle { fit function(self, context, width, height) return height, height -- 一个占满高度的正方形 end, draw function(self, context, cr, width, height) cr:set_source_rgb(1, 0, 0) -- 红色 cr:arc(height/2, height/2, height/2, 0, math.pi*2) cr:fill() end, layout wibox.widget.base.make_widget, } s.mywibox : setup { circle, circle, circle, layout wibox.layout.fixed.horizontal, }这个例子展示了完整开发流程layout wibox.widget.base.make_widget创建骨架 → 声明式解析器把fit/draw表项设置为 widget 的方法 → 布局系统通过base.fit_widget获得尺寸、通过 hierarchy 的绘制管线调用:draw。仓库的示例测试tests/examples/wibox/widget/目录下也存在大量以wibox.widget.base.make_widget()作为占位/骨架 widget 的用例可作参考。如果你的 widget 需要随数据变化而更新例如定期刷新文本、进度条记住更新协议-- 内容变了但尺寸没变 widget:emit_signal(widget::redraw_needed) -- 尺寸也会变比如文本变长 widget:emit_signal(widget::layout_changed) -- 不确定时两者都发总结自定义 widget 的完整开发契约可以浓缩为一张清单创建用wibox.widget.base.make_widget创建骨架必要时提供proxy/widget_name/args通过:buttons或add_button注册鼠标交互。尺寸实现确定性的:fit(context, width, height)通过base.fit_widget查询子 widget 尺寸结果变化时发widget::layout_changed。绘制实现:draw(context, cr, width, height)利用已就位的本地坐标系与 Cairo 绘制不调用cr:reset_clip()内容变化时发widget::redraw_needed。布局实现:layout(width, height)并用base.place_widget_at/base.place_widget_via_matrix返回子 widget 放置表:layout结果变化时发widget::layout_changed。子绘制需要影响子 widget 绘制时实现before/after_draw_children与before/after_draw_child四个钩子只改变绘制方式、不改变绘制面积。兼容声明式系统为持有子 widget 的类型正确定义set_children否则重写为 no-op。遵循这套协议自定义 widget 就能与 Awesome WM 的整个 widget 体系wibar、awful.popup、awful.tooltip、naughty通知、标题栏无缝协作并正确响应屏幕、布局与内容的变化。进一步可阅读 docs/03-declarative-layout.md 掌握声明式组合技巧以及 docs/16-using-cairo.md 了解 Cairo 绘制的更多细节。赞分享操作系统【免费下载链接】awesomeawesome window manager项目地址https://gitcode.com/gh_mirrors/awes/awesome点击查看免费下载相关推荐RVC语音转换实战指南低数据量AI变声性能优化深度解析RVC语音转换实战指南低数据量AI变声性能优化深度解析 面对语音转换领域中数据稀缺和音色泄漏的技术挑战Retrieval based Voice Conve人工智能AI 应用语音音频深度学习终极指南如何为dh/dht项目自定义回调函数与扩展BitTorrent协议终极指南如何为dh/dht项目自定义回调函数与扩展BitTorrent协议 GitHub 加速计划中的 dh/dht 项目是一个实现 BitTorrent D微信聊天记录如何永久保存不丢失WeChatMsg 导出备份完整指南微信聊天记录如何永久保存不丢失WeChatMsg 导出备份完整指南 手机一换微信聊天记录说没就没——这不是危言耸听而是每个微信用户迟早都会撞上的现实。这篇上一篇深入理解Polyfactory的BaseFactory构建自定义工厂的核心基石下一篇终极指南ROS2 Navigation Framework导航参数动态配置工具使用教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表