UI Basics Tutorial

Build interactive user interfaces with Grey

Intermediate Time: 30 minutes

Introduction

Welcome to the GreyOS UI Basics tutorial! In this guide, you'll learn how to create interactive user interfaces using the Grey programming language. Building on what you learned in the Hello World tutorial, you'll now explore how to construct graphical applications that users can interact with.

By the end of this tutorial, you'll understand:

  • How to use Grey's UI components library
  • How to structure layouts for responsive applications
  • How to handle user events and interactions
  • How to apply styling and themes to your application

Prerequisites

Before you begin, make sure you have:

  • Completed the Hello World Tutorial
  • Basic understanding of Grey syntax and concepts
  • A GreyOS account with access to the Grey Shell

Note: This tutorial builds on concepts introduced in the Hello World tutorial. If you haven't completed it yet, we recommend starting there.

Step 1: UI Components

Grey's UI system is built around symbolic components that represent various interface elements. Let's start by exploring the basic components:

Text Component

// Import the UI module
import { UI } from "greyos.ui";

// Create a text component
let greeting = UI.Text {
    content: "Hello, GreyOS User!",
    style: {
        fontSize: "18px",
        color: "#ffffff"
    }
};

Button Component

// Create a button component
let submitButton = UI.Button {
    text: "Click Me",
    style: {
        background: "linear-gradient(135deg, #0062cc, #0084ff)",
        padding: "10px 20px",
        borderRadius: "4px"
    },
    onClick: function() {
        print("Button clicked!");
    }
};

Input Component

// Create an input component
let nameInput = UI.Input {
    placeholder: "Enter your name",
    type: "text",
    style: {
        padding: "8px",
        borderRadius: "4px",
        border: "1px solid rgba(0, 132, 255, 0.5)"
    },
    onChange: function(value) {
        print("Input changed: " + value);
    }
};

Key concept: UI components in Grey are symbolic structures that combine appearance and behavior in a single, cohesive unit.

Step 2: Layout Structure

Now that you understand basic components, let's see how to arrange them in layouts:

Container Component

// Create a container to hold other components
let mainContainer = UI.Container {
    direction: "vertical",
    spacing: "10px",
    padding: "20px",
    children: [
        greeting,
        nameInput,
        submitButton
    ],
    style: {
        background: "rgba(0, 20, 40, 0.7)",
        borderRadius: "8px",
        maxWidth: "500px"
    }
};

Grid Layout

// Create a grid layout
let gridLayout = UI.Grid {
    columns: 2,
    gap: "15px",
    children: [
        UI.Text { content: "Name:" },
        nameInput,
        UI.Text { content: "Action:" },
        submitButton
    ],
    style: {
        padding: "15px",
        background: "rgba(0, 30, 60, 0.5)"
    }
};

Important: All UI components must be properly nested within a container before rendering. Orphaned components will not be displayed.

Step 3: Event Handling

Interactive UIs require event handlers to respond to user actions. Let's look at how to implement them:

Button Click Event

// Create a button with a click handler
let actionButton = UI.Button {
    text: "Submit Form",
    onClick: function() {
        let name = nameInput.getValue();
        greeting.update({ content: "Hello, " + name + "!" });
        notify("Form submitted!");
    }
};

Input Change Event

// Create an input with real-time validation
let emailInput = UI.Input {
    placeholder: "Enter your email",
    type: "email",
    onChange: function(value) {
        if (value.includes("@") && value.includes(".")) {
            this.setStyle({ borderColor: "green" });
        } else {
            this.setStyle({ borderColor: "red" });
        }
    }
};

Custom Events

// Create a custom event system
let themeToggle = UI.Switch {
    value: false,
    onChange: function(isOn) {
        if (isOn) {
            UI.applyTheme("dark");
            eventBus.emit("theme-changed", "dark");
        } else {
            UI.applyTheme("light");
            eventBus.emit("theme-changed", "light");
        }
    }
};

// Listen for theme changes
eventBus.on("theme-changed", function(theme) {
    print("Theme changed to: " + theme);
});

Pro tip: Grey's event system is fully reactive and propagates changes automatically through the UI hierarchy.

Step 4: Creating a Complete UI

Now let's put everything together to create a complete application:

// Import required modules
import { UI } from "greyos.ui";
import { App } from "greyos.app";

// Define the application
symbolic UserProfileApp {
    // App state
    state: {
        username: "",
        email: "",
        bio: "",
        theme: "dark"
    },
    
    // UI Components
    components: {
        form: UI.Container {
            direction: "vertical",
            spacing: "15px",
            children: [
                UI.Text {
                    content: "User Profile",
                    style: { fontSize: "24px", fontWeight: "bold" }
                },
                
                UI.Input {
                    id: "username",
                    placeholder: "Username",
                    onChange: function(value) {
                        UserProfileApp.state.username = value;
                    }
                },
                
                UI.Input {
                    id: "email",
                    placeholder: "Email",
                    type: "email",
                    onChange: function(value) {
                        UserProfileApp.state.email = value;
                    }
                },
                
                UI.TextArea {
                    id: "bio",
                    placeholder: "Tell us about yourself",
                    rows: 4,
                    onChange: function(value) {
                        UserProfileApp.state.bio = value;
                    }
                },
                
                UI.Row {
                    children: [
                        UI.Text { content: "Dark theme:" },
                        UI.Switch {
                            value: true,
                            onChange: function(isOn) {
                                UserProfileApp.state.theme = isOn ? "dark" : "light";
                                UserProfileApp.updateTheme();
                            }
                        }
                    ]
                },
                
                UI.Button {
                    text: "Save Profile",
                    style: {
                        background: "linear-gradient(135deg, #0062cc, #0084ff)",
                        padding: "10px",
                        width: "100%"
                    },
                    onClick: function() {
                        UserProfileApp.saveProfile();
                    }
                }
            ],
            style: {
                padding: "20px",
                background: "rgba(0, 20, 40, 0.7)",
                borderRadius: "8px",
                maxWidth: "500px",
                margin: "0 auto"
            }
        }
    },
    
    // Methods
    saveProfile: function() {
        print("Saving profile...");
        print("Username: " + this.state.username);
        print("Email: " + this.state.email);
        print("Bio: " + this.state.bio);
        print("Theme: " + this.state.theme);
        
        UI.notify({
            message: "Profile saved successfully!",
            type: "success",
            duration: 3000
        });
    },
    
    updateTheme: function() {
        UI.applyTheme(this.state.theme);
    },
    
    // App initialization
    initialize: function() {
        this.updateTheme();
        return this.components.form;
    }
}

// Initialize and render the app
App.render(UserProfileApp.initialize());

This complete example demonstrates:

  • State management within a symbolic structure
  • Component composition and nesting
  • Event handling with business logic
  • Application initialization and rendering

To run this application, save it to a file named profile_app.grey and execute it with:

grey run profile_app.grey

User Profile

Username input field
Email input field
Bio text area
Dark theme:

Next Steps

Congratulations! You've now learned the basics of building user interfaces with Grey. Here are some suggestions for continuing your journey:

  • Enhance your application with more complex layouts and interactions
  • Experiment with advanced UI components like tables, charts, and animations
  • Learn about data binding and state management for larger applications
  • Check out the Universal Absorption Tutorial to learn how to integrate existing code into your Grey applications

Remember: UI in Grey is symbolic, meaning components represent both their visual appearance and behavior together. This unified approach allows for automatic optimization and adaptation to different platforms.