如果你熟悉 tailwindcss ,那么你一定对 GPUI 的 API 感到似曾相识。因为 GPUI 关于样式的 API 就是直接抄的 tailwindcss。
是的,GPUI 对 UI 的样式做了原子化的设计:
div()
.flex()
.size_full()
.p_4()
.bg(rgb(0xf0f0f0))
.text_color(black())
.child(nav())
ElementId
ElementId 是 GPUI 元素的唯一标识符。它用于在 GPUI 组件树中引用元素。因此保持 id 的唯一性很重要,这个和前端的 DOM 元素的 id 类似。通过唯一的 id,我们可以很方便的对某个元素绑定事件。为元素添加 id 之后,类型也从 E 类型变成 Stateful<E> 类型。
div().id("my-element").child("Hello, World!")
Render vs RenderOnce
所有的元素,要么实现 RenderOnce,要么实现 Render。不过,RenderOnce一般用于定义组件,而 Render 一般用于定义视图(可以绘制到屏幕上的对象)。RenderOnce 会消费 self 的所有权,而 Render 则对自身可变引用。
// Render - 视图持续存在,多次渲染
impl Render for MyView {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
// 可以访问和修改 self 的状态
div().child(Label::new("Hello"))
}
}
// RenderOnce - 一次性组件
impl RenderOnce for MyComponent {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
// 消费 self,通常用于构建可重用的组件模式
div().child(Label::new("Component content"))
}
}
主要区别:
Render实现了Render的类型通常是具有持久状态的实体,在每一帧渲染时都会调用render方法,可以访问和修改组件的内部状态。RenderOnce通常用于构建无状态或简单状态的UI片段,通过消费self来创建一次性的UI元素。
在 GPUI 中,可以简单粗暴地认为,RenderOnce 用于定义组件,而 Render 用于定义视图。
上下文(Context)
从上面的Render 和RenderOnce 可知,Render 的 render 方法的签名里带有 cx: &mut Context<Self> 上下文参数。这是一个实体上下文实例,用于处理上下文级别的事务。这些上下文是传递给函数的引用,使得可以与全局状态、窗口、实体和系统服务进行交互。
UI
整体上,只要你对 tailwindcss 熟悉,那么你对 GPUI 的 API 就不会陌生。但是有一些样式和效果, GPUI 未必有对应的 API。
动画和过渡效果
GPUI 只支持简单的动画,可以通过.with_animation(id, animation, animator) 来实现。
目前未找到对应的过渡效果相关的 API,当然,理论上,我们也可以自己实现过滤效果的,只是这个实现成本很大,这意味着我们需要自己维护状态管理,以及对组件更细节的控制。
如果组件需要状态管理,单纯地实现RenderOnce 无法满足要求,我们必须实现Render 。
官方的示例中,旋转动画效果作用在 svg 上的,原因是 svg 元素实现了with_transformation 方法,原因是,在底层 api 上,只有 paint_svg 方法提供了过渡效果的参数:
pub fn paint_svg(
&mut self,
bounds: Bounds<Pixels>,
path: SharedString,
mut data: Option<&[u8]>,
transformation: TransformationMatrix, //提供了过渡效果的参数
color: Hsla,
cx: &App,
) -> Result<()>
普通的组件并不是通过 paint_svg 方法绘制的,所以其实有一些很常见的动画效果,可能也需要我们从底部组件上实现。
父子组件的样式继承
在前端中,多数框架或者样式库都支持在父组件上添加样式,子组件会继承父组件的样式。这在做效果时非常的高效有用。然而 GPUI 中,暂时还没找到控制的方法。 一个简单的例子,当鼠标移入 button 组件时,button 的背景应该改变颜色,子元素 span 的颜色也应该一并改变。在前端里,特别是 tailwindcss 中,我们可以这样做:
<button class="bg-blue-500 hover:bg-blue-700 text-white hover:text-slate-300 ">
<span> primary </span>
</button>
这里的 span 会继承 button 的 text 样式。但是在 gpui 中, 我暂时未找到在 hover 事件中控制子元素的 API。
其他
当然,gpui 只实现了部分 tailwindcss 常见的样式 api,其他不太常用的效果,只能自己自行实现了。
比如,前端组件里比较常见的环形 ⭕️ 进度条, 在 gpui 里如果只使用顶层 API,就很难实现。