rgpui 0.1.1

GUI UI framework
Documentation
use rgpui::single_instance::{SingleInstance, send_activate_to_existing};
use rgpui::{
    App, Bounds, Context, Entity, Keystroke, Toast, ToastPosition, ToastStack, TrayIconEvent,
    TrayMenuItem, Window, WindowBackgroundAppearance, WindowBounds, WindowKind, WindowOptions, div,
    prelude::*, px, rgb, rgba, size,
};
use rgpui_platform::application;

const APP_ID: &str = "com.example.daemon-app";

struct OverlayView {
    toast_stack: Entity<ToastStack>,
}

impl Render for OverlayView {
    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        div()
            .flex()
            .flex_col()
            .size_full()
            .justify_center()
            .items_center()
            .child(
                div()
                    .flex()
                    .flex_col()
                    .gap_3()
                    .p_6()
                    .rounded(px(12.0))
                    .bg(rgba(0x000000dd))
                    .text_color(rgb(0xffffff))
                    .shadow_lg()
                    .max_w(px(400.0))
                    .child(
                        div()
                            .text_xl()
                            .font_weight(rgpui::FontWeight::BOLD)
                            .child("Daemon App Overlay"),
                    )
                    .child(
                        div()
                            .text_sm()
                            .text_color(rgba(0xffffffaa))
                            .child("This overlay window is always on top."),
                    )
                    .child(
                        div()
                            .text_sm()
                            .text_color(rgba(0xffffffaa))
                            .child("Uses WindowKind::Overlay + transparent background."),
                    ),
            )
            .child(self.toast_stack.clone())
    }
}

struct SettingsView;

impl Render for SettingsView {
    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        div()
            .flex()
            .flex_col()
            .gap_4()
            .p_6()
            .size_full()
            .bg(rgb(0xfafafa))
            .text_color(rgb(0x333333))
            .child(
                div()
                    .text_xl()
                    .font_weight(rgpui::FontWeight::BOLD)
                    .child("Settings"),
            )
            .child(
                div()
                    .text_sm()
                    .child("This is a normal settings window opened from the tray menu."),
            )
            .child(
                div()
                    .text_sm()
                    .text_color(rgb(0x888888))
                    .child(format!("Application ID: {}", APP_ID)),
            )
    }
}

fn main() {
    let _instance = match SingleInstance::acquire(APP_ID) {
        Ok(instance) => instance,
        Err(_) => {
            eprintln!("Another instance is already running. Sending activation signal.");
            let _ = send_activate_to_existing(APP_ID);
            std::process::exit(0);
        }
    };

    application().run(|cx: &mut App| {
        cx.set_keep_alive_without_windows(true);

        setup_tray(cx);
        setup_global_hotkey(cx);

        let _ = cx.show_notification("Daemon App", "Application started in background");

        cx.activate(true);
    });
}

fn setup_tray(cx: &mut App) {
    cx.set_tray_tooltip("Daemon App");

    cx.set_tray_menu(vec![
        TrayMenuItem::Action {
            label: "Show Overlay".into(),
            id: "show_overlay".into(),
        },
        TrayMenuItem::Action {
            label: "Settings".into(),
            id: "settings".into(),
        },
        TrayMenuItem::Separator,
        TrayMenuItem::Action {
            label: "Quit".into(),
            id: "quit".into(),
        },
    ]);

    cx.on_tray_icon_event(|event, _cx| match event {
        TrayIconEvent::LeftClick => {
            eprintln!("Tray icon left-clicked");
        }
        TrayIconEvent::RightClick => {
            eprintln!("Tray icon right-clicked");
        }
        TrayIconEvent::DoubleClick => {
            eprintln!("Tray icon double-clicked");
        }
    });

    cx.on_tray_menu_action(|id, cx| match id.as_ref() {
        "show_overlay" => {
            open_overlay(cx);
            cx.activate(true);
        }
        "settings" => {
            open_settings(cx);
            cx.activate(true);
        }
        "quit" => {
            cx.quit();
        }
        _ => {}
    });
}

fn setup_global_hotkey(cx: &mut App) {
    let keystroke = Keystroke::parse("cmd-shift-k").expect("valid keystroke");
    if let Err(err) = cx.register_global_hotkey(1, &keystroke) {
        eprintln!("Failed to register global hotkey: {}", err);
    }

    cx.on_global_hotkey(move |id, _cx| {
        if id == 1 {
            eprintln!("Global hotkey triggered (Cmd+Shift+K)");
        }
    });
}

fn open_overlay(cx: &mut App) {
    let bounds = Bounds::centered(None, size(px(500.), px(300.)), cx);
    cx.open_window(
        WindowOptions {
            window_bounds: Some(WindowBounds::Windowed(bounds)),
            kind: WindowKind::Overlay,
            titlebar: None,
            focus: true,
            show: true,
            window_background: WindowBackgroundAppearance::Transparent,
            ..Default::default()
        },
        |window, cx| {
            let toast_stack = cx.new(|_| ToastStack::new().with_position(ToastPosition::TopRight));
            let toast_stack_handle = toast_stack.clone();

            cx.new(|cx| {
                toast_stack_handle.update(cx, |stack, cx| {
                    stack.push(
                        Toast::new("Overlay Opened").body("This toast auto-dismisses in 3 seconds"),
                        window,
                        cx,
                    );
                });

                OverlayView { toast_stack }
            })
        },
    )
    .ok();
}

fn open_settings(cx: &mut App) {
    let bounds = Bounds::centered(None, size(px(400.), px(300.)), cx);
    cx.open_window(
        WindowOptions {
            window_bounds: Some(WindowBounds::Windowed(bounds)),
            kind: WindowKind::Normal,
            titlebar: Some(rgpui::TitlebarOptions {
                title: Some("Daemon App Settings".into()),
                ..Default::default()
            }),
            focus: true,
            show: true,
            ..Default::default()
        },
        |_, cx| cx.new(|_| SettingsView),
    )
    .ok();
}