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

Window Control

Group: Chrome

Code

pub struct WindowControlDemo {
    status: String,
    fullscreen: bool,
    always_on_top: bool,
}

impl WindowControlDemo {
    pub fn new() -> Self {
        Self {
            status: "click a button to try it".into(),
            fullscreen: false,
            always_on_top: false,
        }
    }

    fn describe(action: &str, result: ServiceResult<()>) -> String {
        match result {
            Ok(()) => format!("{action}: ok"),
            Err(e) => format!("{action}: {e:?}"),
        }
    }

    fn toolbar(&self, variant: usize) -> Toolbar {
        let btn = |id: &str, label: &str| ToolbarButton::Action {
            id: WidgetId::new(id),
            label: label.into(),
            icon: None,
            key_hint: None,
            enabled: true,
            is_active: false,
            tooltip: label.into(),
        };
        let buttons = if variant == 0 {
            vec![
                btn("gallery:wc:title", "Set Title"),
                btn("gallery:wc:fullscreen", "Fullscreen"),
                btn("gallery:wc:always-on-top", "Always-on-top"),
                btn("gallery:wc:minimize", "Minimize"),
                btn("gallery:wc:restore", "Restore"),
                btn("gallery:wc:center", "Center"),
            ]
        } else {
            // A smaller, realistic toolbar — the subset most apps actually
            // expose, without the window-manager-only Minimize/Center pair.
            vec![
                btn("gallery:wc:title", "Set Title"),
                btn("gallery:wc:fullscreen", "Fullscreen"),
                btn("gallery:wc:restore", "Restore"),
            ]
        };
        Toolbar::new(WidgetId::new("gallery:window-control")).with_buttons(buttons)
    }

    fn status_bar(&self) -> StatusBar {
        StatusBar {
            id: WidgetId::new("gallery:window-control:status"),
            left_segments: vec![StatusBarSegment {
                text: format!(" {} ", self.status),
                fg: Color::rgb(255, 255, 255),
                bg: Color::rgb(40, 80, 120),
                bold: false,
                action_id: None,
            }],
            right_segments: vec![],
        }
    }

    fn dispatch(&mut self, id: &WidgetId, backend: &mut dyn Backend) {
        let result;
        let action;
        match id.as_str() {
            "gallery:wc:title" => {
                action = "set_title";
                result = match backend.window() {
                    Some(w) => w.set_title("quadraui gallery"),
                    None => Err(BackendError::Unsupported),
                };
            }
            "gallery:wc:fullscreen" => {
                action = "set_fullscreen";
                self.fullscreen = !self.fullscreen;
                let want = self.fullscreen;
                result = match backend.window() {
                    Some(w) => w.set_fullscreen(want),
                    None => Err(BackendError::Unsupported),
                };
            }
            "gallery:wc:always-on-top" => {
                action = "set_always_on_top";
                self.always_on_top = !self.always_on_top;
                let want = self.always_on_top;
                result = match backend.window() {
                    Some(w) => w.set_always_on_top(want),
                    None => Err(BackendError::Unsupported),
                };
            }
            "gallery:wc:minimize" => {
                action = "minimize";
                result = match backend.window() {
                    Some(w) => w.minimize(),
                    None => Err(BackendError::Unsupported),
                };
            }
            "gallery:wc:restore" => {
                action = "restore";
                result = match backend.window() {
                    Some(w) => w.restore(),
                    None => Err(BackendError::Unsupported),
                };
            }
            "gallery:wc:center" => {
                action = "center";
                result = match backend.window() {
                    Some(w) => w.center(),
                    None => Err(BackendError::Unsupported),
                };
            }
            _ => return,
        }
        self.status = Self::describe(action, result);
    }
}

impl Default for WindowControlDemo {
    fn default() -> Self {
        Self::new()
    }
}

impl Demo for WindowControlDemo {
    fn name(&self) -> &'static str {
        "Window Control"
    }

    fn group(&self) -> &'static str {
        "Chrome"
    }

    fn variants(&self) -> &'static [&'static str] {
        &["Full toolbar", "Common subset"]
    }

    fn render(&self, variant: usize, backend: &mut dyn Backend, area: Rect) {
        let lh = backend.line_height();
        let bar_rect = Rect::new(area.x, area.y, area.width, lh);
        let _ = backend.draw_toolbar_interactive(
            bar_rect,
            &self.toolbar(variant),
            &InteractionState::new(),
        );

        let status_rect = Rect::new(area.x, area.y + lh, area.width, lh);
        let _ = backend.draw_status_bar_interactive(
            status_rect,
            &self.status_bar(),
            &InteractionState::new(),
        );
    }

    fn handle(
        &mut self,
        variant: usize,
        event: &UiEvent,
        backend: &mut dyn Backend,
        area: Rect,
    ) -> Reaction {
        if let UiEvent::MouseDown { position, .. } = event {
            let lh = backend.line_height();
            let bar_rect = Rect::new(area.x, area.y, area.width, lh);
            if position.y >= bar_rect.y && position.y < bar_rect.y + bar_rect.height {
                let bar = self.toolbar(variant);
                let layout = backend.toolbar_layout(bar_rect, &bar);
                if let ToolbarHit::Button(id) = layout.hit_test(position.x, position.y) {
                    self.dispatch(&id, backend);
                    return Reaction::Redraw;
                }
            }
        }
        Reaction::Continue
    }

    fn source(&self) -> &'static str {
        extract_region(SOURCE)
    }

    fn data(&self, _variant: usize) -> serde_json::Value {
        serde_json::json!({
            "status": self.status,
            "fullscreen": self.fullscreen,
            "always_on_top": self.always_on_top,
        })
    }

    fn caps_note(&self, _variant: usize, caps: &BackendCaps) -> Option<String> {
        // `BackendCaps::window_control` is the honest signal for whether
        // `Backend::window()` returns `Some` at all; each button's own
        // `ServiceResult` (shown live in the status line below) then
        // covers which individual operations that toplevel supports.
        if caps.window_control {
            None
        } else {
            Some(
                "This backend has no window handle (BackendCaps::window_control \
                 is false) — expect every button here to report Unsupported."
                    .into(),
            )
        }
    }
}

Backends

VariantTUIGTKmacOSWindows
Full toolbar
Common subset