- name
- gpui-patterns
- description
- Common UI patterns and advanced techniques for GPUI applications. Use when implementing modals, lists, forms, state sharing, or complex component compositions.
# GPUI Patterns
This skill provides common UI patterns and advanced techniques for GPUI applications.
## Modal/Overlay Pattern
### Basic Modal
```rust
use gpui::*;
struct Modal {
is_open: bool,
content: SharedString,
}
impl Render for Modal {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.when(self.is_open, |this| {
this.absolute()
.inset_0()
.flex()
.items_center()
.justify_center()
.bg(rgba(0, 0, 0, 0.5))
.on_click(cx.listener(|this, _event, _window, cx| {
this.close(cx);
}))
.child(
div()
.p_6()
.bg(rgb(0x1a1a1a))
.rounded_lg()
.min_w(px(300.0))
.child(self.content.clone())
)
})
}
}
impl Modal {
fn open(&mut self, content: impl Into<SharedString>, cx: &mut Context<Self>) {
self.is_open = true;
self.content = content.into();
cx.notify();
}
fn close(&mut self, cx: &mut Context<Self>) {
self.is_open = false;
cx.notify();
}
}
```
## List Pattern
### Dynamic List Rendering
```rust
struct ListView {
items: Vec<String>,
}
impl Render for ListView {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.v_flex()
.gap_2()
.children(
self.items.iter().enumerate().map(|(index, item)| {
self.render_item(index, item, cx)
})
)
}
}
impl ListView {
fn render_item(
&self,
index: usize,
item: &str,
cx: &Context<Self>,
) -> impl IntoElement {
div()
.p_3()
.bg(rgb(0x1a1a1a))
.rounded(px(4.0))
.flex()
.justify_between()
.child(format!("{}. {}", index + 1, item))
.child(
div()
.px_3()
.py_1()
.bg(rgb(0xef4444))
.rounded(px(4.0))
.cursor_pointer()
.child("Delete")
.on_click(cx.listener(move |this, _event, _window, cx| {
this.remove_item(index, cx);
}))
)
}
fn remove_item(&mut self, index: usize, cx: &mut Context<Self>) {
if index < self.items.len() {
self.items.remove(index);
cx.notify();
}
}
}
```
## Form Pattern
### Form with Validation
```rust
struct LoginForm {
username: SharedString,
password: SharedString,
error: Option<SharedString>,
}
impl Render for LoginForm {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.v_flex()
.gap_4()
.p_6()
.when_some(self.error.clone(), |this, error| {
this.child(
div()
.p_3()
.bg(rgb(0xef4444))
.rounded(px(4.0))
.child(error)
)
})
.child(self.render_input("Username", &self.username, cx))
.child(self.render_input("Password", &self.password, cx))
.child(
div()
.px_6()
.py_3()
.bg(rgb(0x3b82f6))
.rounded(px(6.0))
.cursor_pointer()
.child("Login")
.on_click(cx.listener(|this, _event, _window, cx| {
this.submit(cx);
}))
)
}
}
impl LoginForm {
fn render_input(
&self,
label: &str,
value: &SharedString,
_cx: &Context<Self>,
) -> impl IntoElement {
div()
.v_flex()
.gap_1()
.child(
div().text_sm().child(label)
)
.child(
div()
.w_full()
.px_3()
.py_2()
.bg(rgb(0x1a1a1a))
.border_1()
.border_color(rgb(0x4a4a4a))
.rounded(px(4.0))
.child(value.clone())
)
}
fn submit(&mut self, cx: &mut Context<Self>) {
if self.username.is_empty() || self.password.is_empty() {
self.error = Some("Please fill in all fields".into());
cx.notify();
return;
}
self.error = None;
// Handle login...
}
}
```
## Global State Pattern
### Shared Application State
```rust
#[derive(Clone)]
struct AppState {
user: Option<String>,
theme: String,
}
// Set global state
fn init_app_state(cx: &mut App) {
let state = AppState {
user: None,
theme: "dark".to_string(),
};
cx.set_global(state);
}
// Access global state
fn use_app_state(cx: &App) -> AppState {
cx.global::<AppState>().clone()
}
// Update global state
fn set_user(cx: &mut App, user: String) {
let mut state = cx.global::<AppState>().clone();
state.user = Some(user);
cx.set_global(state);
}
```
## Parent-Child Communication
### Child Notifying Parent
```rust
#[derive(Clone, Debug)]
enum ChildEvent {
ValueChanged(i32),
}
impl EventEmitter<ChildEvent> for Child {}
struct Parent {
child: Entity<Child>,
_subscription: Subscription,
}
impl Parent {
fn new(cx: &mut Context<Self>) -> Self {
let child = cx.new(|_| Child::new());
let subscription = cx.subscribe(&child, |this, _child, event, cx| {
match event {
ChildEvent::ValueChanged(value) => {
println!("Child value changed to: {}", value);
cx.notify();
}
}
});
Self {
child,
_subscription: subscription,
}
}
}
struct Child {
value: i32,
}
impl Child {
fn new() -> Self {
Self { value: 0 }
}
fn set_value(&mut self, value: i32, cx: &mut Context<Self>) {
self.value = value;
cx.emit(ChildEvent::ValueChanged(value));
cx.notify();
}
}
```
## Tab Navigation
### Tab View Pattern
```rust
#[derive(Clone, Copy, PartialEq)]
enum Tab {
Home,
Settings,
Profile,
}
struct TabView {
active_tab: Tab,
}
impl Render for TabView {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.v_flex()
.size_full()
.child(self.render_tabs(cx))
.child(self.render_content())
}
}
impl TabView {
fn render_tabs(&self, cx: &Context<Self>) -> impl IntoElement {
div()
.h_flex()
.gap_2()
.p_2()
.bg(rgb(0x1a1a1a))
.child(self.render_tab("Home", Tab::Home, cx))
.child(self.render_tab("Settings", Tab::Settings, cx))
.child(self.render_tab("Profile", Tab::Profile, cx))
}
fn render_tab(&self, label: &str, tab: Tab, cx: &Context<Self>) -> impl IntoElement {
let is_active = self.active_tab == tab;
div()
.px_4()
.py_2()
.rounded(px(4.0))
.cursor_pointer()
.bg(if is_active { rgb(0x3b82f6) } else { rgb(0x2a2a2a) })
.child(label)
.on_click(cx.listener(move |this, _event, _window, cx| {
this.active_tab = tab;
cx.notify();
}))
}
fn render_content(&self) -> impl IntoElement {
match self.active_tab {
Tab::Home => div().child("Home Content"),
Tab::Settings => div().child("Settings Content"),
Tab::Profile => div().child("Profile Content"),
}
}
}
```
## Loading State Pattern
### Async Data Loading
```rust
enum LoadState<T> {
Idle,
Loading,
Loaded(T),
Error(String),
}
struct DataView {
state: LoadState<Vec<String>>,
}
impl Render for DataView {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
match &self.state {
LoadState::Idle => {
div().child("Click to load")
.on_click(cx.listener(|this, _event, _window, cx| {
this.load_data(cx);
}))
}
LoadState::Loading => {
div().child("Loading...")
}
LoadState::Loaded(items) => {
div()
.v_flex()
.gap_2()
.children(items.iter().map(|item| {
div().child(item.clone())
}))
}
LoadState::Error(error) => {
div()
.p_3()
.bg(rgb(0xef4444))
.child(format!("Error: {}", error))
}
}
}
}
impl DataView {
fn load_data(&mut self, cx: &mut Context<Self>) {
self.state = LoadState::Loading;
cx.notify();
cx.spawn(async move |this, cx| {
match fetch_data().await {
Ok(data) => {
this.update(&mut *cx, |view, cx| {
view.state = LoadState::Loaded(data);
cx.notify();
})?;
}
Err(e) => {
this.update(&mut *cx, |view, cx| {
view.state = LoadState::Error(e.to_string());
cx.notify();
})?;
}
}
Ok(())
}).detach();
}
}
async fn fetch_data() -> Result<Vec<String>, anyhow::Error> {
// Simulate API call
tokio::time::sleep(Duration::from_secs(1)).await;
Ok(vec!["Item 1".to_string(), "Item 2".to_string()])
}
```
## Dropdown Pattern
```rust
struct Dropdown {
Auf GitHub ansehen