Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Contexts (App, Visual, Context<T>)

Contexts are your main interface to GPUI. Every interaction with the framework - creating entities, opening windows, rendering UI - goes through a context. GPUI provides three context types, each with progressively more capabilities.

The Three Context Types

App (fewest capabilities)
 ├── VisualContext (adds window/UI capabilities)
     └── Context<T> (adds entity-specific capabilities)

App - Application Context

The base context for all application-level services. Available everywhere.

Capabilities:

  • Create and manage entities
  • Access global state
  • Spawn async tasks
  • Manage platform services

Where you’ll see it:

Application::new().run(|cx: &mut App| {
    // Application startup
});

VisualContext - Window-Aware Context

Extends App with window and UI capabilities. Available when you’re working with windows or views.

Additional capabilities beyond App:

  • Open windows
  • Focus management
  • Access to window-specific services

Where you’ll see it:

cx.open_window(WindowOptions::default(), |cx| {
    // cx is a VisualContext here
    cx.new(|_| MyView::default())
})

Context<T> - Entity-Specific Context

The most powerful context, tied to a specific entity. Extends VisualContext with entity-level services.

Additional capabilities beyond VisualContext:

  • cx.notify() - notify observers of this entity
  • cx.emit() - emit typed events from this entity
  • cx.observe() - observe other entities
  • cx.subscribe() - subscribe to events from other entities
  • cx.entity() / cx.weak_entity() - get handle to self

Where you’ll see it:

let counter = cx.new(|cx: &mut Context<Counter>| {
    // cx knows it's for a Counter entity
    Counter { count: 0 }
});

counter.update(cx, |counter, cx: &mut Context<Counter>| {
    // cx is tied to the counter entity
    counter.count += 1;
    cx.notify();
});

Context Type Hierarchy

All context types implement AppContext, but not all have access to advanced features:

FeatureAppVisualContextContext<T>
Create entities (cx.new())
Global state
Spawn tasks
Open windows
Focus management
cx.notify()
cx.emit()
cx.observe()
cx.subscribe()

Common Context APIs

Creating Entities

Available on all contexts:

let counter = cx.new(|cx| Counter { count: 0 });

Accessing Entities

// Read immutably
let value = counter.read(cx).count;

// Update mutably
counter.update(cx, |counter, cx| {
    counter.count += 1;
    cx.notify();
});

Global State

// Define a global
#[derive(Clone, Default)]
struct Theme {
    background: Rgb,
}

impl Global for Theme {}

// Set global (once)
cx.set_global(Theme::default());

// Read global
let theme = Theme::global(cx);

// Update global
cx.update_global::<Theme, _>(|theme, cx| {
    theme.background = rgb(0x1e1e1e);
});

Spawning Async Tasks

// Spawn on UI thread
cx.spawn(|cx| async move {
    // Async work that can access cx
    load_data().await
})

// Spawn on background thread
cx.background_spawn(async move {
    // Heavy computation, no cx access
    compute_something()
})

Entity-Level APIs (Context<T> only)

// Notify observers
cx.notify();

// Emit typed events
cx.emit(MyEvent { data: 42 });

// Observe another entity
cx.observe(&other_entity, |this, cx| {
    // React to changes
}).detach();

// Subscribe to events
cx.subscribe(&other_entity, |this, other, event, cx| {
    // Handle event
}).detach();

// Get handle to self
let self_handle = cx.entity();
let weak_self = cx.weak_entity();

When to Use Which Context

Use App when:

  • Application startup
  • Managing top-level state
  • Creating initial entities
Application::new().run(|cx: &mut App| {
    let app_state = cx.new(|_| AppState::default());
    // Start your app
});

Use VisualContext when:

  • Opening windows
  • Working with window-level UI
  • No specific entity in focus
cx.open_window(WindowOptions::default(), |cx| {
    // cx is VisualContext
    cx.new(|_| RootView::default())
});

Use Context<T> when:

  • Inside entity methods
  • Reacting to state changes
  • Emitting or observing events
impl MyView {
    fn handle_click(&mut self, cx: &mut Context<Self>) {
        self.count += 1;
        cx.notify(); // Only available on Context<T>
    }
}

Window and WindowContext

There’s also ViewContext<T> which is used during rendering:

impl Render for MyView {
    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
        // cx: a ViewContext<Self> with full entity capabilities
        // window: access to window-specific state via cx.window()
        div().child("Hello")
    }
}

Common Patterns

Pattern 1: Passing Context Down

You can’t store contexts, but you can pass them through function calls:

impl MyView {
    fn build_ui(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
        self.build_header(cx) // Pass cx to helpers
    }

    fn build_header(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
        div().child("Header")
    }
}

Pattern 2: Getting Self Handle

let my_view = cx.new(|cx: &mut Context<MyView>| {
    let self_handle = cx.entity(); // Get handle to entity being created

    // Store it for later or pass to others
    MyView {
        self_ref: self_handle,
    }
});

Pattern 3: Upgrading Context Types

You often receive a less capable context but need a more capable one:

// This won't work - can't upgrade context types directly
// let visual_cx: &mut VisualContext = cx; // ❌

// Instead, you already have the right context type based on where you are:
// - In entity callbacks: Context<T>
// - In window callbacks: VisualContext
// - In app startup: App

Pitfalls

Trying to Store Contexts

// ❌ Bad: Can't store contexts
struct MyView {
    cx: Context<MyView>, // Won't compile
}

// ✅ Good: Contexts are always passed as parameters
impl MyView {
    fn do_something(&mut self, cx: &mut Context<Self>) {
        // Use cx here, don't store it
    }
}

Wrong Context Type

// ❌ Bad: Can't notify from App context
fn update_state(cx: &mut App) {
    cx.notify(); // No such method!
}

// ✅ Good: Use Context<T> for entity operations
fn update_state(cx: &mut Context<MyEntity>) {
    cx.notify(); // Works!
}

Forgetting WeakEntity for Self-References

// ❌ Bad: Strong self-reference creates cycle
let entity = cx.new(|cx| {
    let self_handle = cx.entity();
    MyStruct {
        self_ref: self_handle, // Entity<MyStruct> - cycle!
    }
});

// ✅ Good: Use weak for self-references
let entity = cx.new(|cx| {
    let self_handle = cx.weak_entity();
    MyStruct {
        self_ref: self_handle, // WeakEntity<MyStruct> - no cycle
    }
});

Next Steps