Skip to content

nicasa-app/ui-native

Repository files navigation

UI Native

⚠️ Work In Progress: This project is actively being developed. APIs may change without notice.

A declarative, reactive UI framework for building native macOS applications with Rust.

🎯 Features

  • Declarative API: Build UIs using a fluent, chainable API similar to SwiftUI
  • Reactive State: Automatic UI updates with use_state hook
  • Type-Safe: Leverage Rust's type system for compile-time safety
  • Native Performance: Direct bindings to AppKit via objc2
  • 30+ Components: Rich set of pre-built components
  • Chain-able Methods: Style and configure components with method chaining
  • Event Handling: Simple callbacks for user interactions

πŸ“¦ Installation

Add to your Cargo.toml:

[dependencies]
ui-native = { path = "../ui-native" }

πŸš€ Quick Start

use ui_native::prelude::*;

fn main() {
    let mut count = use_state(|| 0);
    
    let ui = rect()
        .width(Size::fill())
        .height(Size::fill())
        .background((240, 240, 245))
        .child(
            Button::new()
                .title("Click Me!")
                .on_press(move |_| {
                    *count.write() += 1;
                    println!("Count: {}", count.read());
                })
                .width(Size::Fixed(200.0))
                .height(Size::Fixed(44.0))
                .builder()
        );
    
    let window = ui.build();
}

πŸ“š Core Concepts

1. Reactive State

Use use_state to create reactive values that automatically update the UI:

let count = use_state(|| 0);

// Read value
println!("Current: {}", count.read());

// Write value (triggers updates)
*count.write() += 1;

// Subscribe to changes
count.subscribe(|value| {
    println!("Changed to: {}", value);
});

2. Layout System

Flexible sizing with Size enum:

rect()
    .width(Size::Fixed(200.0))      // Fixed width
    .height(Size::Percent(50.0))    // 50% of parent
    .width(Size::Fill)               // Fill available space
    .height(Size::Auto)              // Based on content

3. Styling

Chain styling methods for any component:

rect()
    .background((15, 163, 242))           // RGB color
    .corner_radius(10.0)                   // Rounded corners
    .shadow((0., 4., 20., 4., (0,0,0,80))) // Shadow effect
    .alpha(0.9)                            // Opacity
    .padding(EdgeInsets::all(16.0))        // Padding

4. Event Handling

Simple callback-based event handling:

Button::new()
    .title("Save")
    .on_press(|_| {
        println!("Saving...");
    })

🧩 Available Components

Basic Components

  • rect() - Container view
  • Label - Static text display
  • Button - Clickable button
  • TextField - Text input field
  • TextView - Multi-line text editor
  • Image - Image display

Input Controls

  • Slider - Value slider
  • Stepper - Increment/decrement control
  • Checkbox - Checkbox toggle
  • RadioButton - Radio button selection
  • Toggle - On/off switch
  • Picker - Dropdown picker
  • ComboBox - Dropdown with text input
  • SegmentedControl - Multi-segment selector
  • DatePicker - Date selection
  • ColorPicker - Color selection
  • SearchField - Search input

Containers

  • Stack (hstack/vstack) - Horizontal/vertical layout
  • ScrollView - Scrollable container
  • SplitView - Split panel container
  • TabView - Tabbed interface
  • BoxView - Grouped container with border/title
  • VisualEffectView - Blur/vibrancy effects

Data Display

  • ListView - List of items
  • TableView - Table data display
  • CollectionView - Grid layout (image galleries, etc.)
  • ProgressBar - Progress indicator

Windows & Dialogs

  • Window - Application window
  • Panel - Lightweight utility window
  • OpenPanel - File open dialog
  • SavePanel - File save dialog
  • Alert - Alert dialog

UI Elements

  • Toolbar - Window toolbar
  • Menu - Context/menu bar menu

πŸ“– Examples

Counter App

use ui_native::prelude::*;

fn counter_app() {
    let count = use_state(|| 0);
    let count_inc = count.clone();
    let count_dec = count.clone();
    
    let ui = rect()
        .child(
            // Display
            Label::new().text(format!("Count: {}", count.read()))
                .font_size(72.)
                .font_weight(FontWeight::Bold)
                .builder()
        )
        .child(
            // Buttons
            Stack::horizontal()
                .spacing(12.0)
                .child(
                    Button::new()
                        .title("βž•")
                        .on_press(move |_| *count_inc.write() += 1)
                        .builder()
                )
                .child(
                    Button::new()
                        .title("βž–")
                        .on_press(move |_| *count_dec.write() -= 1)
                        .builder()
                )
                .builder()
        );
}

Form Example

use ui_native::prelude::*;

fn form_example() {
    let name = use_state(|| String::new());
    let email = use_state(|| String::new());
    
    vstack()
        .spacing(16.0)
        .child(
            TextField::new()
                .placeholder("Name")
                .width(Size::fill())
                .builder()
        )
        .child(
            TextField::new()
                .placeholder("Email")
                .width(Size::fill())
                .builder()
        )
        .child(
            Button::new()
                .title("Submit")
                .on_press(|_| {
                    println!("Form submitted!");
                })
                .builder()
        )
        .builder()
}

Image Gallery

use ui_native::prelude::*;

fn gallery() {
    hstack()
        .spacing(8.0)
        .child(
            Image::from_path("/path/to/image1.png")
                .scale_mode(ImageScaleMode::AspectFit)
                .width(Size::Fixed(200.0))
                .height(Size::Fixed(200.0))
                .corner_radius(8.0)
                .builder()
        )
        .child(
            Image::from_path("/path/to/image2.png")
                .scale_mode(ImageScaleMode::AspectFit)
                .width(Size::Fixed(200.0))
                .height(Size::Fixed(200.0))
                .corner_radius(8.0)
                .builder()
        )
        .builder()
}

Window & File Dialogs

use ui_native::prelude::*;

// Create a window
fn create_window() {
    let window = Window::new()
        .title("My App")
        .size(800.0, 600.0)
        .center()
        .show();
}

// Open file dialog
fn open_file() {
    if let Some(paths) = OpenPanel::new()
        .title("Select Image")
        .can_choose_files(true)
        .allow_multiple_selection(false)
        .allowed_file_types(vec!["png".into(), "jpg".into()])
        .show()
    {
        println!("Selected: {:?}", paths);
    }
}

// Save file dialog
fn save_file() {
    if let Some(path) = SavePanel::new()
        .title("Save Image")
        .filename("untitled.png")
        .allowed_file_types(vec!["png".into()])
        .show()
    {
        println!("Save to: {}", path);
    }
}

Visual Effects & Modern UI

use ui_native::prelude::*;

fn modern_panel() {
    Panel::new()
        .title("Settings")
        .size(400.0, 300.0)
        .center()
        .show();
    
    // Blur effect background
    VisualEffectView::new()
        .material(Material::Sidebar)
        .blend_mode(BlendMode::BehindWindow)
        .emphasized(true)
        .width(Size::fill())
        .height(Size::fill())
        .builder();
}

ComboBox & TextView

use ui_native::prelude::*;

fn advanced_inputs() {
    vstack()
        .spacing(16.0)
        .child(
            // Dropdown with text input
            ComboBox::new()
                .add_items(vec![
                    "Option 1".into(),
                    "Option 2".into(),
                    "Option 3".into(),
                ])
                .placeholder("Select or type...")
                .autocomplete(true)
                .width(Size::fill())
                .builder()
        )
        .child(
            // Multi-line text editor
            TextView::new()
                .text("Enter your notes here...")
                .word_wrap(true)
                .font_size(14.0)
                .width(400.0)
                .height(200.0)
        )
        .builder()
}

Collection View (Grid Layout)

use ui_native::prelude::*;

fn image_grid() {
    CollectionView::new()
        .item_size(150.0, 150.0)
        .item_spacing(10.0)
        .line_spacing(10.0)
        .columns(3)
        .selectable(true)
        .allow_multiple_selection(true)
        .width(500.0)
        .height(400.0);
}

🎨 Styling Guide

Colors

// RGB tuple
.background((255, 100, 50))

// RGBA tuple
.background((255, 100, 50, 128))

// Named colors
.background(Color::blue())
.background(Color::red())
.background(Color::gray(128))

// Custom color
.background(Color::rgb(15, 163, 242))

Fonts

.font_size(16.0)
.font_weight(FontWeight::Bold)
.font_weight(FontWeight::Medium)
.font_weight(FontWeight::Light)

Shadows

// (offset_x, offset_y, blur_radius, spread_radius, color)
.shadow((0., 4., 20., 4., (0, 0, 0, 80)))

Corners

.corner_radius(10.0)                    // Uniform
.corner_radius(CornerRadius::all(10.0)) // Uniform
.corner_radius(CornerRadius::new(
    10.0,  // top_left
    10.0,  // top_right
    10.0,  // bottom_right
    10.0   // bottom_left
))

Layout

.padding(EdgeInsets::all(16.0))
.padding(EdgeInsets::symmetric(12.0, 16.0)) // vertical, horizontal
.margin(EdgeInsets::new(8.0, 12.0, 8.0, 12.0))

.center()      // Center in parent
.center_x()    // Center horizontally
.center_y()    // Center vertically

πŸ—οΈ Architecture

Core Modules

  • core - Core types, state, layout, style, event systems
  • builder - UIBuilder and chainable API
  • components - All UI components

Design Principles

  1. Declarative: Describe what you want, not how to build it
  2. Composable: Combine small components into complex UIs
  3. Type-Safe: Compile-time guarantees
  4. Performance: Zero-cost abstractions over AppKit
  5. Professional: Production-ready code quality

πŸ”§ Advanced Usage

Custom Components

Create your own components by wrapping UIBuilder:

pub struct MyButton {
    builder: UIBuilder<NSButton>,
}

impl MyButton {
    pub fn new() -> Self {
        Self {
            builder: Button::new()
                .background((0, 122, 255))
                .corner_radius(8.0)
                .builder()
        }
    }
    
    pub fn on_click<F>(self, callback: F) -> Self
    where F: FnMut(&AnyObject) + 'static
    {
        // Implement event handling
        self
    }
}

State Management

For complex apps, use multiple states:

struct AppState {
    user: State<Option<User>>,
    settings: State<Settings>,
    theme: State<Theme>,
}

impl AppState {
    fn new() -> Self {
        Self {
            user: use_state(|| None),
            settings: use_state(Settings::default),
            theme: use_state(|| Theme::Light),
        }
    }
}

πŸ“„ License

This project is designed to be open-sourced. Please add appropriate license before publishing.

🀝 Contributing

Contributions welcome! Guidelines:

  1. Follow Rust API guidelines
  2. Add tests for new features
  3. Update documentation
  4. Maintain code quality standards

πŸ” Status

Current Status: Beta - Core functionality complete, API may evolve

Completed:

  • βœ… Core architecture
  • βœ… State management
  • βœ… Layout system
  • βœ… Style system
  • βœ… Event handling
  • βœ… 30+ components
  • βœ… Window management
  • βœ… File dialogs
  • βœ… Visual effects
  • βœ… Examples and documentation

Future Enhancements:

  • Animations and transitions
  • Gesture recognizers
  • Accessibility support
  • Custom drawing/Canvas
  • Performance optimizations
  • More complex layout algorithms

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages