Bevy UI 圆角迁移指南:BorderRadius 字段变为 CornerRadius,ResolvedBorderRadius 变为 Vec2
本篇指南围绕 Bevy UI 的一次破坏性 API 变更展开:为支持椭圆(elliptical)圆角节点,BorderRadius 的四个角字段由 Val 变为 CornerRadius,ResolvedBorderRadius 的字段由标量 f32 变为 Vec2。读完本文,你将掌握新旧 API 的逐项对照写法、CornerRadius 的循环/椭圆语义与解析(resolve)规则,并能顺畅地完成现有 UI 代码的迁移。
背景:为什么字段要变成二维
官方迁移指南 border_radius.md(对应 PR 24779)给出的变更说明只有一句话:
In order to support elliptical nodes, the fields of
BorderRadiusare nowCornerRadiuss and the fields ofResolvedBorderRadiusare nowVec2s.
即:为了支持椭圆圆角节点,BorderRadius 的字段改为 CornerRadius,ResolvedBorderRadius 的字段改为 Vec2。对应的功能发布说明见 Elliptical Border Radius。
在旧版 API 中,每个角的圆角半径只有一个 Val,隐含"圆形"语义。新版允许 x(水平)与 y(垂直)半径不同,从而画出一个椭圆的角。所有改动都集中在 bevy_ui 的两个文件里:
- crates/bevy_ui/src/geometry.rs:新增的
CornerRadius类型; - crates/bevy_ui/src/ui_node.rs:
BorderRadius与ResolvedBorderRadius。
迁移对照:BorderRadius 的新旧写法
变更前
BorderRadius {
pub top_left: px(10.),
pub top_right: percent(20.),
pub bottom_right: zero(),
pub bottom_left: vh(5.),
}
变更后
BorderRadius {
pub top_left: CornerRadius::circular(px(10.)),
pub top_right: CornerRadius::circular(percent(20.)),
pub bottom_right: CornerRadius::circular(zero()),
pub bottom_left: CornerRadius::circular(vh(5.)),
}
由于 CornerRadius 实现了 From<Val>(见 geometry.rs),你也可以用 into 完成同样的转换:
BorderRadius {
pub top_left: px(10.).into(),
pub top_right: percent(20.).into(),
pub bottom_right: zero().into(),
pub bottom_left: vh(5.).into(),
}
从源码结构看,From<Val> 的实现是 Self { x, y: auto() },即把原值放进 x、y 设为 Val::Auto——这正好命中下一节讲的"圆形"表示。因此绝大多数只写圆形圆角的旧代码可以靠 .into() 最小改动地迁移。
新类型 CornerRadius:结构与圆形/椭圆语义
CornerRadius 的定义在 crates/bevy_ui/src/geometry.rs:
pub struct CornerRadius {
/// Responsive horizontal radius.
pub x: Val,
/// Responsive vertical radius.
pub y: Val,
}
两个字段都是响应式的 Val,可以取 px、percent、vh/vw、em/rem 等任意响应式值。
圆形圆角的表示方式:Val::Auto
迁移指南明确说明:圆形圆角(circular corner radius)的表示方式是让 CornerRadius::x 或 CornerRadius::y 其中一个为 Val::Auto。
对应的构造辅助函数是 circular(geometry.rs):
/// Creates a circular corner radius, with `radius` resolved relative to the node's
/// shortest side and clamped to half its length.
pub const fn circular(radius: Val) -> Self {
Self {
x: radius,
y: Val::Auto,
}
}
CornerRadius 还提供了两组常量和两个常用构造器:
| 常量/方法 | 定义 | 语义 |
|---|---|---|
CornerRadius::MAX |
{ x: Px(f32::MAX), y: Val::Auto } |
完全圆角:半径为节点最短边的一半,节点呈胶囊形(宽高相等时为圆形) |
CornerRadius::MAX_ELLIPTICAL |
{ x: Px(f32::MAX), y: Px(f32::MAX) } |
完全椭圆角:水平半径为宽的一半,垂直半径为高的一半,节点被画成椭圆 |
CornerRadius::ZERO |
{ x: ZERO, y: ZERO } |
直角 |
circular(radius) |
{ x: radius, y: Auto } |
圆形角,半径相对节点最短边解析,并钳制到其一半 |
all(radius) |
{ x: radius, y: radius } |
两轴同值。注意:由于各轴独立解析(percent 等值对宽高各自取值),解析结果不一定相等 |
new(x, y) |
{ x, y } |
椭圆角,分别指定水平/垂直半径 |
一个容易踩的点是 all 与 circular 的区别。源码文档测试(geometry.rs)表明:对 100x50 的节点,all(px(30.)) 解析为 Vec2::new(30., 25.)——x 轴钳制到宽度一半(50),y 轴钳制到高度一半(25);而 circular(px(30.)) 会先对最短边(50)解析,再钳制到 25,得到 Vec2::splat(25.),保证两轴严格相等、形状是圆弧而不是椭弧。
可传入的输入类型:From 实现一览
BorderRadius 的所有构造函数参数类型是 impl Into<CornerRadius>,因此凡是可以转换为 CornerRadius 的类型都能传入。源码中提供了如下转换(geometry.rs):
From<Val>:px(10.).into(),等价于circular;From<(Val, Val)>:(px(10.), px(20.)).into();From<[Val; 2]>:[px(10.), px(20.)].into()。
此外 BorderRadius 本身实现了 From<T: Into<CornerRadius>>(ui_node.rs),直接调用 Self::all(value),所以 BorderRadius::from(px(10.)) 等于四角全圆角。
一个值得注意的设计细节是 PartialEq 的实现(geometry.rs):{ x: v, y: Auto } == { x: Auto, y: v } 判定为相等。因为 Auto 只是"另一轴未显式设置、按圆形半径解释"的标记,两种摆放方式语义相同。这意味着 CornerRadius::circular(r) 与 { x: r, y: auto() }、{ x: auto(), y: r } 在比较时一致,迁移后做 assert_eq! 时不必纠结 Auto 放在哪一轴。
resolve:解析成物理像素的规则
CornerRadius::resolve(geometry.rs)把响应式半径解析为物理像素 Vec2,签名与 Val::resolve 一致:
pub fn resolve(
self,
scale_factor: f32,
size: Vec2, // 节点尺寸
viewport_size: Vec2,
em_size: EmSize,
rem_size: RemSize,
) -> Vec2
分支逻辑是:
- 两轴都是
Auto→ 返回Vec2::ZERO; - 一轴为
Auto(即圆形模式)→ 用非 Auto 轴的值对节点最短边size.min_element()解析,钳制到[0, 0.5 * min(size)],然后splat到两轴; - 其余情况(椭圆模式)→ x 对
size.x(宽度)解析、y 对size.y(高度)解析,各自钳制到[0, 0.5 * size]。
同文件的单元测试 corner_radius_resolve(geometry.rs)覆盖了这些行为:100x50 节点上 {x: Px(100.), y: Auto} 解析为 vec2(25., 25.)(圆形钳制),{x: Px(40.), y: Px(40.)} 解析为 vec2(40., 25.)(椭圆钳制)。这也印证了 BorderRadius 文档中"半径若超过节点宽/高的一半,会被计算为高/宽的一半"的说明(ui_node.rs)。
BorderRadius 构造器与更新函数不再 const
迁移指南指出:BorderRadius 的构造函数和更新函数不再是 const,以便参数可以接受任意实现 Into<CornerRadius> 的类型:
let n = BorderRadius::top_right(vh(10.));
let m = BorderRadius::top_right([px(10.), px(20.)]);
实现上,all、new、top_left/top_right/bottom_right/bottom_left、left/right/top/bottom 以及 with_* 系列方法全部改为普通函数,参数为 impl Into<CornerRadius>(ui_node.rs)。例如:
pub fn all(radius: impl Into<CornerRadius>) -> Self {
let radius = radius.into();
Self {
top_left: radius,
top_right: radius,
bottom_left: radius,
bottom_right: radius,
}
}
两个例外值得注意:px(f32, f32, f32, f32) 与 percent(f32, f32, f32, f32) 这四个纯浮点参数的便捷构造器仍然是 const fn(ui_node.rs),因为它们内部直接写 CornerRadius::circular(Val::Px(...)),不经过 Into 转换。
BorderRadius 的常量同步扩展:DEFAULT/ZERO(直角)、MAX(胶囊/圆形)、新增的 MAX_ELLIPTICAL(椭圆)(ui_node.rs)。
典型写法速查
结合源码文档示例(ui_node.rs),四角混合圆角、圆角与椭圆角并存的完整写法是:
fn setup_ui(mut commands: Commands) {
commands.spawn((
Node {
width: Val::Px(100.),
height: Val::Px(100.),
border: UiRect::all(Val::Px(2.)),
border_radius: BorderRadius {
// 圆角,x 和 y 半径相等
top_left: CornerRadius::circular(px(10.)),
// From<Val> 简写
top_right: percent(20.).into(),
// 椭圆角
bottom_right: CornerRadius::new(px(30.), px(20.)),
// 结构体字面量
bottom_left: CornerRadius { x: px(10.), y: px(40.) },
},
..Default::default()
},
BackgroundColor(BLUE.into()),
));
}
ResolvedBorderRadius:解析结果从标量到 Vec2
ResolvedBorderRadius 是渲染侧消费的解析结果。变更后(ui_node.rs):
/// The values are in physical pixels.
pub struct ResolvedBorderRadius {
pub top_left: Vec2,
pub top_right: Vec2,
pub bottom_right: Vec2,
pub bottom_left: Vec2,
}
- 每个角是一个
Vec2,单位为物理像素,x/y 分别对应水平/垂直半径; BorderRadius::resolve只是对四角逐一调用CornerRadius::resolve(ui_node.rs),签名包含scale_factor、node_size、viewport_size、em_size、rem_size;- 该类型实现了
From<ResolvedBorderRadius> for [[f32; 4]; 2](ui_node.rs),把四个角摊平成"第一行 4 个 x 半径、第二行 4 个 y 半径"的二维数组,从源码结构看这正是上传给 UI 渲染着色器的布局。bevy_ui_render中的节点矩形、文字、阴影等渲染模块都消费该类型。
如果你之前直接读取 ResolvedBorderRadius.top_left 等字段做算术(它曾是标量),需要改为访问 .x/.y 分量。
被移除的 API:resolve_single_corner
迁移指南最后一项:
BorderRadius::resolve_single_cornerhas been removed, useCornerRadius::resolveinstead.
即单角解析入口下移到了 CornerRadius 上,参数为 (scale_factor, size, viewport_size, em_size, rem_size),返回该角的物理像素 Vec2。需要单角数值(例如自定义裁剪矩形)时,直接对你感兴趣的那个 CornerRadius 字段调用 resolve 即可。
迁移后的真实用法验证
仓库中的示例与插件代码可以作为迁移后的参照:
- UI 边框示例 examples/ui/styling/borders.rs 新增了四组椭圆圆角用例,同时展示了三种写法:结构体字面量
CornerRadius { x: px(25), y: px(8) }、元组转换(px(8), px(25)).into()、数组转换[px(8), px(25)].into(),以及percent半径的椭圆角; - 控件库
bevy_feathers的分段按钮圆角工具 rounded_corners.rs 中,RoundedCorners::to_border_radius通过CornerRadius::from(px(radius))构造圆角,再组合成BorderRadius——这是"旧代码用From迁移"的典型形态。
迁移检查清单
- 把
BorderRadius四个角字段中的Val值包一层CornerRadius::circular(...)或.into()(语义等价,推荐.into()保持代码简洁); - 构造器调用如
BorderRadius::top_right(vh(10.))无需改动即可接受Val、(Val, Val)、[Val; 2]、CornerRadius任意类型,需要椭圆角时传入两值即可; - 若依赖了
const上下文里调用all/with_*等,需改为运行期调用;纯px/percent常量构造仍可在const中使用; - 读取
ResolvedBorderRadius的地方改为Vec2分量运算; - 删除对
BorderRadius::resolve_single_corner的调用,换成对应角上的CornerRadius::resolve。
完成以上步骤后,你的 UI 代码即兼容新版 Bevy 的椭圆圆角能力,并且可以随时用 CornerRadius::new(px(30.), px(20.)) 这类写法获得 CSS border-radius 风格的多值椭圆角效果。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00