quadraui 0.6.0

Cross-platform Rust UI toolkit: write one AppLogic and run it unmodified on TUI, GTK4 (Linux), macOS, and Windows backends
Documentation
//! Direct2D / DirectWrite rasteriser for [`crate::Panel`].
//!
//! Painting is implemented in the shared
//! [`crate::primitives::panel::native_surface_paint::paint`] — see that fn's doc
//! for details. This module carries [`win_panel_layout`] for pure layout
//! queries needed by `WinBackend::panel_layout`.
//!
//! Only compiled on `target_os = "windows"` — see `super::mod`'s
//! `#[cfg(target_os = "windows")] mod panel;` and `backend.rs`'s module
//! docs for why the rest of this repo's `--features win` compile gate
//! stays meaningful without a Windows host.
//!
//! # Theme
//!
//! `WinBackend::draw_panel` passes `&self.current_theme` to the shared
//! [`crate::primitives::panel::native_surface_paint::paint`] — the same
//! live theme `Backend::set_theme` writes, the same shape
//! `win::status_bar`'s module doc documents.

#[cfg(test)]
use super::text::DWrite;
use crate::event::Rect;
use crate::primitives::layout_metrics::pixel_panel_layout;
use crate::primitives::panel::{Panel, PanelLayout};
#[cfg(test)]
use crate::theme::Theme;

/// Compute a [`Panel`]'s layout without painting — used by
/// `WinBackend::panel_layout` for no-paint hit-test queries.
/// Shares its action-button width with `gtk_panel_layout` / `mac_panel_layout` via
/// [`pixel_panel_layout`].
pub fn win_panel_layout(rect: Rect, panel: &Panel, line_height: f32) -> PanelLayout {
    pixel_panel_layout(panel, rect.x, rect.y, rect.width, rect.height, line_height)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::primitives::panel::{PanelAction, PanelHit};
    use crate::types::{StyledText, WidgetId};
    use crate::win::testing::HeadlessSurface;

    const W: u32 = 200;
    const H: u32 = 100;
    const LINE_HEIGHT: f32 = 20.0;

    fn panel() -> Panel {
        Panel {
            id: WidgetId::new("p"),
            title: Some(StyledText::plain("Terminal")),
            actions: vec![PanelAction {
                id: WidgetId::new("p:close"),
                icon: "\u{d7}".into(),
                tooltip: "Close".into(),
                is_active: false,
            }],
            accent: None,
            collapsed: false,
        }
    }

    /// Paint `panel` via the shared
    /// [`crate::primitives::panel::native_surface_paint::paint`] through
    /// a [`super::super::surface::D2dSurface`] over `surface`'s headless target — the same
    /// adapter the now-removed `draw_panel` shim used,
    /// exercised here directly.
    fn paint(surface: &HeadlessSurface, dwrite: &DWrite, rect: Rect, panel: &Panel) -> PanelLayout {
        let layout = win_panel_layout(rect, panel, LINE_HEIGHT);
        surface
            .paint(|target| {
                let mut raw = super::super::surface::D2dSurface {
                    target,
                    dwrite: Some(dwrite),
                };
                crate::primitives::panel::native_surface_paint::paint(
                    panel,
                    &layout,
                    &mut raw,
                    &Theme::default(),
                );
            })
            .expect("paint panel");
        layout
    }

    /// Paint↔click round trip: title bar, action button, and content
    /// area each paint their own bg (or, for content, are simply left
    /// unpainted chrome) at the bounds `hit_test` resolves to the
    /// matching `PanelHit` variant.
    #[test]
    fn paint_and_hit_test_round_trip() {
        let surface = HeadlessSurface::new(W, H).expect("create surface");
        let (dwrite, _, _) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
        let panel = panel();
        let rect = Rect::new(0.0, 0.0, W as f32, H as f32);

        let layout = paint(&surface, &dwrite, rect, &panel);
        let theme = Theme::default();

        // Title bar: painted bg matches theme.separator (no accent
        // override), and a click on the body (left of the action
        // button) resolves to `TitleBar`.
        let tb = layout.title_bar_bounds.expect("title bar present");
        let tb_px = surface.pixel_at(2, (tb.y + tb.height / 2.0) as u32);
        assert_eq!(
            (tb_px.r, tb_px.g, tb_px.b),
            (theme.separator.r, theme.separator.g, theme.separator.b)
        );
        let title_hit = layout.hit_test(2.0, tb.y + tb.height / 2.0);
        assert_eq!(title_hit, PanelHit::TitleBar(WidgetId::new("p")));

        // Action button: painted at its own bounds, hit-tests to
        // `Action`.
        let va = &layout.visible_actions[0];
        let action_hit = layout.hit_test(
            va.bounds.x + va.bounds.width / 2.0,
            va.bounds.y + va.bounds.height / 2.0,
        );
        assert_eq!(action_hit, PanelHit::Action(WidgetId::new("p:close")));

        // Content area: below the title bar, hit-tests to `Content`.
        let cb = layout.content_bounds;
        assert!(cb.height > 0.0, "content area should be non-empty");
        let content_hit = layout.hit_test(cb.x + 2.0, cb.y + 2.0);
        assert_eq!(content_hit, PanelHit::Content(WidgetId::new("p")));
    }

    /// A no-title panel has no title bar and the content area fills the
    /// whole bounds.
    #[test]
    fn no_title_panel_has_no_title_bar() {
        let mut panel = panel();
        panel.title = None;
        panel.actions = Vec::new();

        let surface = HeadlessSurface::new(W, H).expect("create surface");
        let (dwrite, _, _) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
        let rect = Rect::new(0.0, 0.0, W as f32, H as f32);

        let layout = paint(&surface, &dwrite, rect, &panel);

        assert!(layout.title_bar_bounds.is_none());
        assert_eq!(layout.content_bounds.height, H as f32);
    }

    /// `win_panel_layout` (no-paint) must produce the same layout
    /// as `paint` — same panel, same rect, same line height.
    #[test]
    fn no_paint_layout_matches_paint_layout() {
        let panel = panel();
        let rect = Rect::new(0.0, 0.0, W as f32, H as f32);

        let surface = HeadlessSurface::new(W, H).expect("create surface");
        let (dwrite, _, _) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
        let painted = paint(&surface, &dwrite, rect, &panel);
        let no_paint = win_panel_layout(rect, &panel, LINE_HEIGHT);

        assert_eq!(painted, no_paint);
    }
}