If you are familiar with Tailwind CSS, you will find the API of GPUI quite familiar. This is because GPUI’s styling API is directly inspired by Tailwind CSS.
Indeed, GPUI has adopted an atomic design for UI styling:
div()
.flex()
.size_full()
.p_4()
.bg(rgb(0xf0f0f0))
.text_color(black())
.child(nav())
ElementId
The ElementId is a unique identifier for GPUI elements. It is used to reference elements within the GPUI component tree. Therefore, maintaining the uniqueness of the id is crucial, similar to the id of DOM elements in front-end development. With a unique id, we can easily bind events to a specific element. After adding an id to an element, its type changes from <span>E</span> to <span>Stateful<E></span>.
div().id("my-element").child("Hello, World!")
Render vs RenderOnce
All elements must either implement <span>RenderOnce</span> or <span>Render</span>. However, <span>RenderOnce</span> is generally used to define components, while <span>Render</span> is typically used to define views (objects that can be drawn on the screen).<span>RenderOnce</span> consumes the ownership of <span>self</span>, while <span>Render</span> has a mutable reference to itself.
// Render - View persists, rendered multiple times
impl Render for MyView {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
// Can access and modify self's state
div().child(Label::new("Hello"))
}
}
// RenderOnce - One-time component
impl RenderOnce for MyComponent {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
// Consumes self, typically used to build reusable component patterns
div().child(Label::new("Component content"))
}
}
Main Differences:
- •
<span>Render</span>implementations are typically entities with persistent state, and the render method is called on each frame render, allowing access to and modification of the component’s internal state. - •
<span>RenderOnce</span>is usually used to build stateless or simple state UI fragments, creating one-time UI elements by consuming self.
In GPUI, it can be simply understood that <span>RenderOnce</span> is used to define components, while <span>Render</span> is used to define views.
Context
From the above, we can see that the render method signature of <span>Render</span> includes a context parameter <span>cx: &mut Context<Self></span>. This is an instance of entity context used to handle context-level transactions. These contexts are passed as references to functions, allowing interaction with global state, windows, entities, and system services.
UI
Overall, as long as you are familiar with Tailwind CSS, you will not find GPUI’s API unfamiliar. However, there are some styles and effects that GPUI may not have corresponding APIs for.
Animations and Transition Effects
GPUI only supports simple animations, which can be implemented using <span>.with_animation(id, animation, animator)</span>. Currently, there is no corresponding API for transition effects. Of course, theoretically, we could implement filtering effects ourselves, but this comes with a high implementation cost, meaning we need to manage state ourselves and have finer control over components. If a component requires state management, simply implementing <span>RenderOnce</span> will not suffice; we must implement <span>Render</span>.
In the official example, the rotation animation effect is applied to an SVG because the SVG element implements the <span>with_transformation</span> method. This is because, at the lower API level, only the <span>paint_svg</span> method provides parameters for transition effects:
pub fn paint_svg(
&mut self,
bounds: Bounds<Pixels>,
path: SharedString,
mut data: Option<&[u8]>,
transformation: TransformationMatrix, // Provides parameters for transition effects
color: Hsla,
cx: &App,
) -> Result<()>
Ordinary components are not drawn using the paint_svg method, so there are some common animation effects that may also need to be implemented from the bottom components.
Style Inheritance of Parent and Child Components
In the front-end, most frameworks or style libraries support adding styles to parent components, with child components inheriting the styles of the parent. This is very efficient and useful for creating effects. However, in GPUI, I have not yet found a way to control this. A simple example is that when the mouse hovers over a button component, the button’s background should change color, and the child element’s span color should also change. In the front-end, especially in Tailwind CSS, we can do this:
<button class="bg-blue-500 hover:bg-blue-700 text-white hover:text-slate-300 ">
<span> primary </span>
</button>
Here, the span will inherit the text style of the button. However, in GPUI, I have not yet found an API to control child elements during hover events.
Others
GPUI only implements a portion of the common Tailwind CSS style APIs; other less commonly used effects must be implemented by ourselves.
For example, a common circular ⭕️ progress bar in front-end components is quite difficult to implement in GPUI if only using the top-level API.