首页
/ Bevy UI 圆角迁移指南:BorderRadius 字段变为 CornerRadius,ResolvedBorderRadius 变为 Vec2

Bevy UI 圆角迁移指南:BorderRadius 字段变为 CornerRadius,ResolvedBorderRadius 变为 Vec2

2026-09-05 12:01:28作者:咎竹峻Karen

本篇指南围绕 Bevy UI 的一次破坏性 API 变更展开:为支持椭圆(elliptical)圆角节点,BorderRadius 的四个角字段由 Val 变为 CornerRadiusResolvedBorderRadius 的字段由标量 f32 变为 Vec2。读完本文,你将掌握新旧 API 的逐项对照写法、CornerRadius 的循环/椭圆语义与解析(resolve)规则,并能顺畅地完成现有 UI 代码的迁移。

背景:为什么字段要变成二维

官方迁移指南 border_radius.md(对应 PR 24779)给出的变更说明只有一句话:

In order to support elliptical nodes, the fields of BorderRadius are now CornerRadiuss and the fields of ResolvedBorderRadius are now Vec2s.

即:为了支持椭圆圆角节点,BorderRadius 的字段改为 CornerRadiusResolvedBorderRadius 的字段改为 Vec2。对应的功能发布说明见 Elliptical Border Radius

在旧版 API 中,每个角的圆角半径只有一个 Val,隐含"圆形"语义。新版允许 x(水平)与 y(垂直)半径不同,从而画出一个椭圆的角。所有改动都集中在 bevy_ui 的两个文件里:

迁移对照: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() },即把原值放进 xy 设为 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,可以取 pxpercentvh/vwem/rem 等任意响应式值。

圆形圆角的表示方式:Val::Auto

迁移指南明确说明:圆形圆角(circular corner radius)的表示方式是让 CornerRadius::xCornerRadius::y 其中一个为 Val::Auto

对应的构造辅助函数是 circulargeometry.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 } 椭圆角,分别指定水平/垂直半径

一个容易踩的点是 allcircular 的区别。源码文档测试(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::resolvegeometry.rs)把响应式半径解析为物理像素 Vec2,签名与 Val::resolve 一致:

pub fn resolve(
    self,
    scale_factor: f32,
    size: Vec2,            // 节点尺寸
    viewport_size: Vec2,
    em_size: EmSize,
    rem_size: RemSize,
) -> Vec2

分支逻辑是:

  1. 两轴都是 Auto → 返回 Vec2::ZERO
  2. 一轴为 Auto(即圆形模式)→ 用非 Auto 轴的值对节点最短边 size.min_element() 解析,钳制到 [0, 0.5 * min(size)],然后 splat 到两轴;
  3. 其余情况(椭圆模式)→ x 对 size.x(宽度)解析、y 对 size.y(高度)解析,各自钳制到 [0, 0.5 * size]

同文件的单元测试 corner_radius_resolvegeometry.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.)]);

实现上,allnewtop_left/top_right/bottom_right/bottom_leftleft/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 fnui_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::resolveui_node.rs),签名包含 scale_factornode_sizeviewport_sizeem_sizerem_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_corner has been removed, use CornerRadius::resolve instead.

即单角解析入口下移到了 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 迁移"的典型形态。

迁移检查清单

  1. BorderRadius 四个角字段中的 Val 值包一层 CornerRadius::circular(...).into()(语义等价,推荐 .into() 保持代码简洁);
  2. 构造器调用如 BorderRadius::top_right(vh(10.)) 无需改动即可接受 Val(Val, Val)[Val; 2]CornerRadius 任意类型,需要椭圆角时传入两值即可;
  3. 若依赖了 const 上下文里调用 all/with_* 等,需改为运行期调用;纯 px/percent 常量构造仍可在 const 中使用;
  4. 读取 ResolvedBorderRadius 的地方改为 Vec2 分量运算;
  5. 删除对 BorderRadius::resolve_single_corner 的调用,换成对应角上的 CornerRadius::resolve

完成以上步骤后,你的 UI 代码即兼容新版 Bevy 的椭圆圆角能力,并且可以随时用 CornerRadius::new(px(30.), px(20.)) 这类写法获得 CSS border-radius 风格的多值椭圆角效果。

登录后查看全文
热门项目推荐
相关项目推荐