# GPUI-RSX

English | [简体中文](./README_CN.md)

A Rust procedural macro that provides JSX-like syntax for GPUI, making UI development more concise and intuitive.

## ✨ Features

- 🎨 **HTML-like Syntax** - React JSX-like development experience
- 🚀 **Zero Runtime Overhead** - Expands to native GPUI code at compile time
- 📦 **Lightweight** - Only depends on `syn`, `quote`, `proc-macro2`
- 🔧 **Flexible** - Supports expressions, conditional rendering, component composition
- 💡 **Type Safe** - Full compile-time type checking
- 🧩 **Fragment Support** - Return multiple root elements with `<>...</>`
- 🔁 **For-loop Sugar** - Iterate with `{for item in iter { ... }}`
- 🎨 **Full Tailwind Colors** - 242 built-in colors + arbitrary hex values

## 📦 Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
gpui = "0.1"
gpui-rsx = "0.1"
```

## 🚀 Quick Start

### Get Started in 5 Minutes

```rust
use gpui::*;
use gpui_rsx::rsx;

struct CounterView {
    count: i32,
}

impl Render for CounterView {
    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
        rsx! {
            <div class="flex flex-col gap-4 p-4">
                <h1>{format!("Count: {}", self.count)}</h1>
                <div class="flex gap-2">
                    <button
                        bg={rgb(0x3b82f6)}
                        text_color={rgb(0xffffff)}
                        px_4
                        py_2
                        rounded_md
                        onClick={cx.listener(|view, _, cx| {
                            view.count += 1;
                            cx.notify();
                        })}
                    >
                        {"Increment"}
                    </button>
                    <button
                        bg={rgb(0xef4444)}
                        text_color={rgb(0xffffff)}
                        px_4
                        py_2
                        rounded_md
                        onClick={cx.listener(|view, _, cx| {
                            view.count -= 1;
                            cx.notify();
                        })}
                    >
                        {"Decrement"}
                    </button>
                </div>
            </div>
        }
    }
}

fn main() {
    App::new().run(|cx: &mut AppContext| {
        cx.open_window(WindowOptions::default(), |cx| {
            cx.new_view(|_cx| CounterView { count: 0 })
        });
    });
}
```

### Before & After

#### ❌ Traditional GPUI (Verbose)

```rust
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
    div()
        .flex()
        .flex_col()
        .gap_4()
        .p_4()
        .child(
            div()
                .text_xl()
                .font_bold()
                .child(format!("Count: {}", self.count))
        )
        .child(
            div()
                .flex()
                .gap_2()
                .child(
                    div()
                        .bg(rgb(0x3b82f6))
                        .text_color(rgb(0xffffff))
                        .px_4()
                        .py_2()
                        .rounded_md()
                        .on_click(cx.listener(|view, _, cx| {
                            view.count += 1;
                            cx.notify();
                        }))
                        .child("Increment")
                )
                .child(
                    div()
                        .bg(rgb(0xef4444))
                        .text_color(rgb(0xffffff))
                        .px_4()
                        .py_2()
                        .rounded_md()
                        .on_click(cx.listener(|view, _, cx| {
                            view.count -= 1;
                            cx.notify();
                        }))
                        .child("Decrement")
                )
        )
}
```

#### ✅ With GPUI-RSX (Concise)

See the Quick Start example above.

**Code Reduction: ~50%** ✨

## 📖 Syntax Guide

### 1. Basic Elements

```rust
rsx! {
    <div>{"Hello GPUI"}</div>
}
```

Expands to:
```rust
div().child("Hello GPUI")
```

### 2. Fragment (Multiple Root Elements)

When you need to return multiple elements without a wrapper:

```rust
rsx! {
    <>
        <div>{"First"}</div>
        <div>{"Second"}</div>
        <div>{"Third"}</div>
    </>
}
```

Expands to:
```rust
vec![
    div().child("First"),
    div().child("Second"),
    div().child("Third"),
]
```

### 3. Attributes

#### Boolean Attributes (Flags)

```rust
rsx! {
    <div flex flex_col />
}
```

Expands to:
```rust
div().flex().flex_col()
```

#### Value Attributes

```rust
rsx! {
    <div gap={px(16.0)} bg={rgb(0xffffff)} />
}
```

Expands to:
```rust
div().gap(px(16.0)).bg(rgb(0xffffff))
```

### 4. Class Attribute

The `class` attribute accepts a Tailwind-like string that expands into multiple GPUI method calls:

```rust
rsx! {
    <div class="flex flex-col gap-4 p-4" />
}
```

Expands to:
```rust
div().flex().flex_col().gap(px(4.0)).p(px(4.0))
```

> **Note:** `class` only accepts string literals. For dynamic styling, use individual attributes (e.g., `bg={color_var}`).

#### Supported class patterns

**Layout:**
- `flex`, `flex-col`, `flex-row`, `flex-wrap`, `flex-1`, `flex-none`, `flex-auto`
- `items-center`, `items-start`, `items-end`
- `justify-center`, `justify-between`

**Spacing** (numeric values become `px(n)`):
- `gap-4` → `.gap(px(4.0))`
- `p-4`, `px-4`, `py-4`, `pt-4`, `pb-4`, `pl-4`, `pr-4`
- `m-4`, `mx-4`, `my-4`, `mt-4`, `mb-4`, `ml-4`, `mr-4`
- `w-64`, `h-32`
- Fractional values: `p-0.5` → `.p(px(0.5))`

**Sizing:**
- `w-full`, `h-full`, `size-full`

**Text:**
- `text-xs`, `text-sm`, `text-base`, `text-lg`, `text-xl`
- `text-2xl`, `text-3xl`, `text-4xl`, `text-5xl`
- `font-bold`

**Border:**
- `border` → `.border_1()`
- `border-2` → `.border_2()`, `border-4` → `.border_4()`
- `rounded-sm`, `rounded-md`, `rounded-lg`, `rounded-xl`, `rounded-full`, `rounded-none`

**Colors** (full Tailwind palette):
- `text-red-500` → `.text_color(rgb(0xef4444))`
- `bg-blue-600` → `.bg(rgb(0x2563eb))`
- `border-green-500` → `.border_color(rgb(0x22c55e))`
- Arbitrary hex: `bg-[#ff0000]`, `text-[#333]`, `border-[#abc]`

**Effects:**
- `shadow-sm`, `shadow-md`, `shadow-lg`
- `overflow-hidden`, `overflow-scroll`
- `cursor-pointer`, `cursor-default`, `cursor-text`

**Supported colors:** slate, gray, zinc, neutral, stone, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose (shades 50-950) + white, black

### 5. Event Handling

```rust
rsx! {
    <button onClick={cx.listener(|view, _, cx| {
        println!("clicked");
    })}>
        {"Click me"}
    </button>
}
```

Supported events (camelCase / snake_case):

| Event | Method |
|-------|--------|
| `onClick` / `on_click` | `.on_click(handler)` |
| `onMouseDown` / `on_mouse_down` | `.on_mouse_down(handler)` |
| `onMouseUp` / `on_mouse_up` | `.on_mouse_up(handler)` |
| `onMouseMove` / `on_mouse_move` | `.on_mouse_move(handler)` |
| `onMouseDownOut` / `on_mouse_down_out` | `.on_mouse_down_out(handler)` |
| `onMouseUpOut` / `on_mouse_up_out` | `.on_mouse_up_out(handler)` |
| `onKeyDown` / `on_key_down` | `.on_key_down(handler)` |
| `onKeyUp` / `on_key_up` | `.on_key_up(handler)` |
| `onFocus` / `on_focus` | `.on_focus(handler)` |
| `onBlur` / `on_blur` | `.on_blur(handler)` |
| `onHover` / `on_hover` | `.on_hover(handler)` |
| `onScrollWheel` / `on_scroll_wheel` | `.on_scroll_wheel(handler)` |
| `onDrag` / `on_drag` | `.on_drag(handler)` |
| `onDrop` / `on_drop` | `.on_drop(handler)` |
| `onAction` / `on_action` | `.on_action(handler)` |

### 6. Nested Elements

```rust
rsx! {
    <div>
        <h1>{"Title"}</h1>
        <p>{"Description"}</p>
        <div>
            <button>{"Action 1"}</button>
            <button>{"Action 2"}</button>
        </div>
    </div>
}
```

### 7. Expressions

```rust
rsx! {
    <div>
        {format!("Count: {}", self.count)}
        {self.render_child_component()}
        {if self.show {
            rsx! { <span>{"Visible"}</span> }
        } else {
            rsx! { <span>{"Hidden"}</span> }
        }}
    </div>
}
```

### 8. List Rendering

#### Using iterators (traditional)

```rust
rsx! {
    <div>
        {self.items.iter().map(|item| {
            rsx! {
                <div key={item.id}>
                    {item.name.clone()}
                </div>
            }
        }).collect::<Vec<_>>()}
    </div>
}
```

#### Using for-loop syntax sugar

```rust
rsx! {
    <ul>
        {for item in &self.items {
            <li>{item.name.clone()}</li>
        }}
    </ul>
}
```

Expands to:
```rust
div().children((&self.items).into_iter().map(|item| {
    div().child(item.name.clone())
}))
```

For-loops also support ranges and method calls:

```rust
rsx! {
    <div>
        {for i in 0..5 {
            <span>{i}</span>
        }}
    </div>
}
```

### 9. Spread Syntax

```rust
rsx! {
    <div>
        {...items.iter().map(|item| rsx! { <span>{item}</span> })}
    </div>
}
```

### 10. Attribute Mapping Reference

camelCase attributes are automatically mapped to GPUI snake_case methods:

| RSX Attribute | GPUI Method |
|---------------|-------------|
| `zIndex` | `.z_index()` |
| `opacity` | `.opacity()` |
| `visible` | `.visible()` |
| `invisible` (flag) | `.visible(false)` |
| `width` / `height` | `.w()` / `.h()` |
| `minWidth` / `maxWidth` | `.min_w()` / `.max_w()` |
| `minHeight` / `maxHeight` | `.min_h()` / `.max_h()` |
| `gapX` / `gapY` | `.gap_x()` / `.gap_y()` |
| `flexBasis` | `.basis()` |
| `flexGrow` / `flexShrink` | `.flex_grow()` / `.flex_shrink()` |
| `flexOrder` | `.order()` |
| `fontSize` | `.font_size()` |
| `lineHeight` | `.line_height()` |
| `fontWeight` | `.font_weight()` |
| `textAlign` | `.text_align()` |
| `textDecoration` | `.text_decoration()` |
| `borderRadius` | `.border_radius()` |
| `borderTop` / `borderBottom` | `.border_t()` / `.border_b()` |
| `borderLeft` / `borderRight` | `.border_l()` / `.border_r()` |
| `roundedTop` / `roundedBottom` | `.rounded_t()` / `.rounded_b()` |
| `roundedTopLeft` / `roundedTopRight` | `.rounded_tl()` / `.rounded_tr()` |
| `roundedBottomLeft` / `roundedBottomRight` | `.rounded_bl()` / `.rounded_br()` |
| `boxShadow` | `.shadow()` |
| `overflowX` / `overflowY` | `.overflow_x_hidden()` / `.overflow_y_hidden()` |
| `inset` | `.inset()` |

Attributes not in this table are passed through as-is (e.g., `bg={color}` → `.bg(color)`).

### 11. Conditional Styling with `when` and `whenSome`

#### when - Apply styles based on condition

```rust
rsx! {
    <div
        flex
        when={(is_active, |this| {
            this.bg(rgb(0x3b82f6))
                .text_color(rgb(0xffffff))
        })}
    >
        {"Button"}
    </div>
}
```

#### whenSome - Apply styles when Option has value

```rust
let custom_width: Option<f32> = Some(200.0);

rsx! {
    <div
        flex
        whenSome={(custom_width, |this, w| this.w(px(w)))}
    >
        {"Content"}
    </div>
}
```

#### Multiple conditions

```rust
rsx! {
    <button
        class="px-4 py-2 rounded-md"
        when={(is_selected, |this| this.bg(rgb(0x3b82f6)))}
        when={(is_disabled, |this| this.bg(rgb(0xe5e7eb)))}
        whenSome={(custom_color, |this, color| this.bg(rgb(color)))}
    >
        {"Button"}
    </button>
}
```

### 12. Styled Flag (Default Tag Styles)

The `styled` flag injects sensible default styles based on the tag name:

```rust
rsx! {
    <h1 styled>{"Title"}</h1>
    // Expands to: div().text_3xl().font_bold().child("Title")

    <button styled>{"Click"}</button>
    // Expands to: div().cursor_pointer().child("Click")
}
```

Default styles per tag:

| Tag | Default Styles |
|-----|---------------|
| `h1` | `text-3xl font-bold` |
| `h2` | `text-2xl font-bold` |
| `h3` | `text-xl font-bold` |
| `h4` | `text-lg font-bold` |
| `h5` | `text-base font-bold` |
| `h6` | `text-sm font-bold` |
| `button`, `a` | `cursor-pointer` |
| `input`, `textarea` | `px-2 py-1` |
| `ul`, `ol` | `flex flex-col` |

User attributes are applied after defaults and can override them.

## 🎯 Complete Example

### Todo App

```rust
use gpui::*;
use gpui_rsx::rsx;

struct TodoApp {
    todos: Vec<Todo>,
    input: String,
}

struct Todo {
    id: usize,
    text: String,
    completed: bool,
}

impl Render for TodoApp {
    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
        rsx! {
            <div class="flex flex-col gap-4 p-4">
                <h1 class="text-2xl font-bold">
                    {"Todo List"}
                </h1>

                <div class="flex gap-2">
                    <input
                        placeholder="Add a todo..."
                        value={self.input.clone()}
                    />
                    <button
                        class="bg-blue-500 text-white px-4 py-2 rounded-md"
                        onClick={cx.listener(|view, _, cx| {
                            view.add_todo();
                            cx.notify();
                        })}
                    >
                        {"Add"}
                    </button>
                </div>

                <div class="flex flex-col gap-2">
                    {for todo in self.todos.iter() {
                        <div
                            class="flex gap-2 items-center p-2 rounded-md"
                            bg={if todo.completed {
                                rgb(0xf3f4f6)
                            } else {
                                rgb(0xffffff)
                            }}
                        >
                            <span>{todo.text.clone()}</span>
                        </div>
                    }}
                </div>
            </div>
        }
    }
}

impl TodoApp {
    fn add_todo(&mut self) {
        if !self.input.is_empty() {
            self.todos.push(Todo {
                id: self.todos.len(),
                text: self.input.clone(),
                completed: false,
            });
            self.input.clear();
        }
    }
}
```

## 🔧 Advanced Usage

### Custom Components

```rust
fn render_card(&self, title: &str, content: &str) -> impl IntoElement {
    rsx! {
        <div class="rounded-lg shadow-md p-6">
            <h2 class="text-xl font-bold">
                {title}
            </h2>
            <p class="text-gray-600">
                {content}
            </p>
        </div>
    }
}

fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
    rsx! {
        <div>
            {self.render_card("Title 1", "Content 1")}
            {self.render_card("Title 2", "Content 2")}
        </div>
    }
}
```

### Conditional Rendering

```rust
rsx! {
    <div>
        {if self.loading {
            rsx! { <div>{"Loading..."}</div> }
        } else if let Some(error) = &self.error {
            rsx! { <div class="text-red-500">{error.clone()}</div> }
        } else {
            rsx! { <div>{self.render_content()}</div> }
        }}
    </div>
}
```

### Dynamic Styling

```rust
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
    let bg_color = if self.is_active {
        rgb(0x3b82f6)
    } else {
        rgb(0x6b7280)
    };

    rsx! {
        <div bg={bg_color} class="px-4 py-2 rounded-md">
            {"Button"}
        </div>
    }
}
```

## 📊 Performance

GPUI-RSX is a **compile-time macro** that expands to the same code as hand-written GPUI, with **zero runtime overhead**.

| Metric | Traditional GPUI | GPUI-RSX |
|--------|------------------|----------|
| Code Size | 100 lines | 50 lines (-50%) |
| Runtime Performance | Baseline | Same |
| Type Safety | ✅ | ✅ |
| Compile-time Checking | ✅ | ✅ |

## 🛠️ Development

### Build

```bash
cd gpui-rsx
cargo build
```

### Test

```bash
cargo test --test macro_tests
```

### Run Examples

```bash
# Counter example
cargo run --example counter

# Todo app example
cargo run --example todo_app
```

### Expand Macros (Debugging)

```bash
# Install cargo-expand
cargo install cargo-expand

# View expanded code
cargo expand --lib
```

## 💡 Best Practices

### 1. Component Splitting

Break complex UIs into small, reusable components:

```rust
// ✅ Recommended: Split into multiple methods
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
    rsx! {
        <div>
            {self.render_header()}
            {self.render_content()}
            {self.render_footer()}
        </div>
    }
}

fn render_header(&self) -> impl IntoElement {
    rsx! { <header>{"Header"}</header> }
}
```

### 2. Use Constants

Extract repeated styles as constants:

```rust
const PRIMARY_BG: Rgb = rgb(0x3b82f6);
const PRIMARY_TEXT: Rgb = rgb(0xffffff);

rsx! {
    <button bg={PRIMARY_BG} text_color={PRIMARY_TEXT}>
        {"Button"}
    </button>
}
```

### 3. Avoid Over-nesting

```rust
// ❌ Not recommended: Over-nested
rsx! {
    <div>
        <div>
            <div>
                <div>
                    {"Content"}
                </div>
            </div>
        </div>
    </div>
}

// ✅ Recommended: Flatten structure
rsx! {
    <div class="container">
        {"Content"}
    </div>
}
```

## 🐛 FAQ

### Q1: How to use variables in RSX?

```rust
let title = "Hello";
rsx! {
    <div>{title}</div>
}
```

### Q2: How to handle Option types?

```rust
rsx! {
    <div>
        {if let Some(text) = &self.optional_text {
            rsx! { <span>{text.clone()}</span> }
        } else {
            rsx! { <span>{"No text"}</span> }
        }}
    </div>
}
```

### Q3: What does the expanded macro code look like?

Use `cargo expand` to view:

```bash
cargo expand --lib
```

### Q4: Which elements are supported?

All GPUI-supported elements can be used, such as `div`, `button`, `input`, `span`, etc.

### Q5: Can I use dynamic class values?

No. The `class` attribute only accepts string literals. For dynamic styling, use individual attributes:

```rust
// ❌ Won't work
rsx! { <div class={my_class} /> }

// ✅ Use individual attributes
rsx! {
    <div bg={dynamic_color} flex />
}

// ✅ Or use `when` for conditional styles
rsx! {
    <div when={(is_active, |this| this.bg(rgb(0x3b82f6)))} />
}
```

## 🤝 Contributing

Contributions are welcome! Feel free to submit Issues or Pull Requests.

### Development Workflow

1. Fork the project
2. Create a feature branch: `git checkout -b feature/amazing-feature`
3. Commit changes: `git commit -m 'Add amazing feature'`
4. Push branch: `git push origin feature/amazing-feature`
5. Submit a Pull Request

### Code Standards

- Use `rustfmt` to format code
- Use `clippy` to check code quality
- Add tests for new features
- Update documentation

## 📝 License

MIT License

## 🙏 Acknowledgments

Inspired by:
- [Dioxus RSX](https://dioxuslabs.com/) - RSX syntax design
- [Yew html! macro](https://yew.rs/) - html! macro
- [React JSX](https://react.dev/) - JSX syntax
- [GPUI](https://www.gpui.rs/) - Underlying UI framework

---

**Make GPUI development more enjoyable!** 🎉
