Inevitably when building an app you have various state and data that you want accessible from anywhere in an app.
When using gpui, thanks to Rust’s powerful trait system, you can use an extension to do this–and it is pretty handy.
Let’s take a look.
How it works
Here’s the basic idea:
- Define a trait with the methods you want to add to
App - Implement that trait for the
Apptype - Use your methods wherever you have a reference to
App
Minimally:
pub trait MyAppExt {
fn say_hello(&self) -> String;
}
impl MyAppExt for App {
fn say_hello(&self) -> String {
"Hello from my App extension!".to_string()
}
}
With this trait defined, you can use it wherever you have an App reference:
impl SomeStruct {
fn greet(&self, cx: &App) {
let greeting = cx.say_hello();
println!("{}", greeting);
}
}
Example: Adding Convenient, Global Theme Access
Let’s walk adding an example where you can access the theme anywhere from where you have an App reference.
Let’s start by defining a basic theme:
use gpui::Hsla;
#[derive(Clone, Debug)]
pub struct Theme {
fg: Hsla,
bg: Hsla,
accent: Hsla,
}
impl Default for Theme {
fn default() -> Self {
Theme {
fg: hsla(220.0 / 360.0, 9.0 / 100.0, 72.0 / 100.0, 1.0),
bg: hsla(220.0 / 360.0, 14.0 / 100.0, 18.0 / 100.0, 1.0),
accent: hsla(207.0 / 360.0, 82.0 / 100.0, 66.0 / 100.0, 1.0),
}
}
}
Now we need to make it accessible globally:
To make something accessible globally in gpui we need to create a global wrapper:
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use gpui::Global;
#[derive(Clone, Debug)]
pub struct GlobalTheme(pub Arc<Theme>);
impl Deref for GlobalTheme {
type Target = Arc<Theme>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for GlobalTheme {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Global for GlobalTheme {}
This wrapper lets us store our theme in App’s global state and access it easily with dereferencing.
Then we can define a trait that we will use to extend App:
pub trait ActiveTheme {
fn theme(&self) -> &Arc<Theme>;
}
This is a simple one, but you could define anything here… A whole theme registry, logic for swapping themes, etc.
Then we implement our extension on App:
impl ActiveTheme for App {
fn theme(&self) -> &Arc<Theme> {
&self.global::<GlobalTheme>().0
}
}
Now we can use our extension in our UI:
pub struct AppExtensionExample {}
impl Render for AppExtensionExample {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.size_full()
.items_center()
.justify_center()
.text_xs()
.text_color(cx.theme().fg)
.bg(cx.theme().bg)
.child("Here are our theme colors!")
.children(vec![
div().w_6().rounded_full().bg(cx.theme().fg),
div().w_6().rounded_full().bg(cx.theme().bg),
div().w_6().rounded_full().bg(cx.theme().accent),
])
}
}
Here is everything together (or grab the example)
//! # GPUI App extension example
//!
//! Add gpui to your Cargo.toml:
//! gpui = { git = "https://github.com/zed-industries/zed", rev = "c04c5812b6295ab683fbf1900499330cbc2b3058"}
use gpui:{
bounds, div, hsla, point, px, size, App, AppContext as _, Application, Context, FocusHandle,
Global, Hsla, IntoElement, Menu, ParentElement as _, Render, Styled as _, TitlebarOptions,
Window, WindowBounds, WindowOptions,
};
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct Theme {
fg: Hsla,
bg: Hsla,
accent: Hsla,
}
impl Default for Theme {
fn default() -> Self {
Theme {
fg: hsla(220.0 / 360.0, 9.0 / 100.0, 72.0 / 100.0, 1.0),
bg: hsla(220.0 / 360.0, 14.0 / 100.0, 18.0 / 100.0, 1.0),
accent: hsla(207.0 / 360.0, 82.0 / 100.0, 66.0 / 100.0, 1.0),
}
}
}
impl Theme {
pub fn get_global(cx: &App) -> &Arc<Theme> {
&cx.global::<GlobalTheme>().0
}
}
#[derive(Clone, Debug)]
pub struct GlobalTheme(pub Arc<Theme>);
impl Deref for GlobalTheme {
type Target = Arc<Theme>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for GlobalTheme {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Global for GlobalTheme {}
pub trait ActiveTheme {
fn theme(&self) -> &Arc<Theme>;
}
impl ActiveTheme for App {
fn theme(&self) -> &Arc<Theme> {
&self.global::<GlobalTheme>().0
}
}
pub struct AppExtensionExample {
focus_handle: FocusHandle,
}
impl Render for AppExtensionExample {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let theme_colors = div().flex().gap(px(12.)).children(vec![
div()
.size_6()
.rounded_full()
.bg(cx.theme().fg)
.border_1()
.border_color(gpui::white().alpha(0.12)),
div()
.size_6()
.rounded_full()
.bg(cx.theme().bg)
.border_1()
.border_color(gpui::white().alpha(0.12)),
div()
.size_6()
.rounded_full()
.bg(cx.theme().accent)
.border_1()
.border_color(gpui::white().alpha(0.12)),
]);
div()
.flex()
.flex_col()
.flex_initial()
.p_4()
.w(px(200.0))
.h(px(160.0))
.justify_center()
.items_center()
.text_center()
.text_xs()
.text_color(cx.theme().fg)
.bg(cx.theme().bg)
.gap(px(6.))
.child("Our theme colors!")
.child(theme_colors)
}
}
fn main() {
Application::new().run(|cx: &mut App| {
cx.set_menus(vec![Menu {
name: "App Extensions".into(),
items: vec![],
}]);
cx.set_global(GlobalTheme(Arc::new(Theme::default())));
let window = cx
.open_window(
WindowOptions {
titlebar: Some(TitlebarOptions {
title: Some("App Extensions".into()),
..Default::default()
}),
window_bounds: Some(WindowBounds::Windowed(bounds(
point(px(0.0), px(0.0)),
size(px(200.), px(160.)),
))),
..Default::default()
},
|_window, cx| {
cx.new(|cx| AppExtensionExample {
focus_handle: cx.focus_handle(),
})
},
)
.unwrap();
window
.update(cx, |view, window, cx| {
window.focus(&view.focus_handle);
cx.activate(true);
})
.unwrap();
})
}
Enjoy! I know gpui is still missing tons of examples. Let me know over on bluesky if there is anything specific you want to see.