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
//! macOS (Core Graphics + Core Text) rasteriser for
//! [`crate::primitives::diff_view::DiffView`].
//!
//! Painting is implemented in the shared
//! [`crate::primitives::diff_view::native_surface_paint::paint`] — see that fn's doc for
//! the two named divergences (row/header text vertical alignment; header-label
//! ellipsize vs. hard-clip) found while unifying the platform-specific diff view
//! implementations. Callers reach the paint through [`crate::Backend::draw_diff_view`].
//!
//! The shared paint inherits a fix: `MacBackend::draw_diff_view` previously painted
//! nothing and returned `visible_rows: 0`, which silently pinned every host's scroll
//! clamp to zero — this is corrected via [`DiffView::layout`].

#[cfg(test)]
use crate::primitives::diff_view::{DiffView, DiffViewLayout};
#[cfg(test)]
use crate::theme::Theme;

#[cfg(test)]
mod tests {
    use super::super::headless::BitmapSurface;
    use super::super::text::make_font;
    use super::super::MacBackend;
    use super::*;
    use crate::event::{Rect as QRect, Viewport};
    use crate::primitives::diff_view::DiffMode;
    use crate::types::WidgetId;
    use crate::Backend;

    const W: u32 = 400;
    const H: u32 = 200;

    /// Divider width in points — mirrors the shared
    /// `primitives::diff_view::DIFF_DIVIDER_W` value the geometry uses.
    const DIVIDER_PX: f64 = crate::primitives::diff_view::DIFF_DIVIDER_W as f64;

    fn sample_view(mode: DiffMode) -> DiffView {
        let left = "one\ntwo\nthree\n";
        let right = "one\nTWO\nthree\n";
        DiffView {
            id: WidgetId::new("diff"),
            left: left.into(),
            right: right.into(),
            left_label: None,
            right_label: None,
            hunks: crate::diff::compute_hunks(left, right),
            mode,
            editability: Default::default(),
            scroll_offset: 0,
            focused_pane: Default::default(),
            has_focus: false,
        }
    }

    /// Index of the first row of `kind` in the flattened row list, i.e.
    /// the screen row it lands on when `scroll_offset == 0`.
    fn first_row_of(view: &DiffView, kind: crate::primitives::diff_view::DiffRowKind) -> usize {
        view.hunks
            .iter()
            .flat_map(|h| h.rows.iter())
            .position(|r| r.kind == kind)
            .expect("fixture should contain the requested row kind")
    }

    /// Paint `view` via the real [`Backend::draw_diff_view`] trait
    /// method — which now routes through the shared `native_surface_paint::paint`
    /// — rather than the deprecated free-function shim, so these tests
    /// don't trip the `-D warnings`-denied `deprecated` lint (CLAUDE.md
    /// rule 3; mirrors `gtk::backend`'s identical test-migration note).
    fn paint_via_backend(view: &DiffView, rect: QRect) -> (BitmapSurface, DiffViewLayout, f64) {
        let surface = BitmapSurface::new(W, H);
        surface.fill(1.0, 1.0, 1.0, 1.0);
        let mut backend = MacBackend::new();
        backend.set_current_font(make_font("Menlo", 12.0).expect("Menlo installed"));
        let lh = backend.line_height() as f64;
        backend.begin_frame(Viewport::new(W as f32, H as f32, 1.0));
        let captured = std::cell::RefCell::new(None);
        backend.enter_frame_scope(surface.context_ptr(), |b| {
            *captured.borrow_mut() = Some(b.draw_diff_view(rect, view));
        });
        backend.end_frame();
        (surface, captured.into_inner().expect("layout captured"), lh)
    }

    /// The degenerate impl this replaced returned `visible_rows: 0` and
    /// painted nothing, which pinned host scroll clamps to zero.
    #[test]
    fn visible_rows_is_not_stuck_at_zero() {
        let view = sample_view(DiffMode::SideBySide);
        let (_surface, layout, lh) =
            paint_via_backend(&view, QRect::new(0.0, 0.0, W as f32, H as f32));
        assert!(
            layout.visible_rows > 0,
            "visible_rows must reflect the real row capacity, not the old 0 stub",
        );
        assert_eq!(layout.visible_rows, (H as f64 / lh).floor() as usize);
        assert_eq!(layout.total_rows, view.total_rows());
    }

    #[test]
    fn changed_row_paints_removed_and_added_backgrounds() {
        let view = sample_view(DiffMode::SideBySide);
        let (surface, _layout, lh) =
            paint_via_backend(&view, QRect::new(0.0, 0.0, W as f32, H as f32));
        let theme = Theme::default();

        // No labels, so no header row offsets the content rows: screen
        // row N sits at `N * line_height`.
        let changed = first_row_of(&view, crate::primitives::diff_view::DiffRowKind::Changed);
        let row_y = (lh * (changed as f64 + 0.5)) as u32;
        let left_w = ((W as f64 - DIVIDER_PX) / 2.0).floor();

        // Probe the right end of the left pane, clear of glyphs.
        let (r, g, b, _) = surface.pixel(left_w as u32 - 4, row_y);
        assert_eq!(
            (r, g, b),
            (
                theme.diff_removed_bg.r,
                theme.diff_removed_bg.g,
                theme.diff_removed_bg.b
            ),
            "left pane of a Changed row should carry diff_removed_bg",
        );

        let (r, g, b, _) = surface.pixel(W - 4, row_y);
        assert_eq!(
            (r, g, b),
            (
                theme.diff_added_bg.r,
                theme.diff_added_bg.g,
                theme.diff_added_bg.b
            ),
            "right pane of a Changed row should carry diff_added_bg",
        );
    }

    #[test]
    fn divider_paints_border_colour() {
        let view = sample_view(DiffMode::SideBySide);
        let (surface, _layout, lh) =
            paint_via_backend(&view, QRect::new(0.0, 0.0, W as f32, H as f32));
        let theme = Theme::default();
        let left_w = ((W as f64 - DIVIDER_PX) / 2.0).floor();
        let (r, g, b, _) = surface.pixel(left_w as u32, (lh * 0.5) as u32);
        assert_eq!(
            (r, g, b),
            (theme.border_fg.r, theme.border_fg.g, theme.border_fg.b),
            "the 1pt pane divider should be painted in border_fg",
        );
    }

    /// Non-zero-origin regression guard (LESSONS.md:159-181): the view
    /// must paint at the requested origin, and nothing above/left of it.
    #[test]
    fn paints_at_a_nonzero_origin_only() {
        let origin_x = 40.0_f32;
        let origin_y = 24.0_f32;
        let view = sample_view(DiffMode::SideBySide);
        let (surface, layout, lh) = paint_via_backend(
            &view,
            QRect::new(origin_x, origin_y, W as f32 - origin_x, H as f32 - origin_y),
        );
        let theme = Theme::default();

        assert_eq!(
            layout.visible_rows,
            ((H as f32 - origin_y) as f64 / lh).floor() as usize,
            "visible_rows must be derived from the rect actually handed in",
        );

        // The divider sits at origin_x + floor((w-1)/2), not at W/2.
        let w = (W as f32 - origin_x) as f64;
        let left_w = ((w - DIVIDER_PX) / 2.0).floor();
        let div_x = (origin_x as f64 + left_w) as u32;
        let probe_y = (origin_y as f64 + lh * 0.5) as u32;
        let (r, g, b, _) = surface.pixel(div_x, probe_y);
        assert_eq!(
            (r, g, b),
            (theme.border_fg.r, theme.border_fg.g, theme.border_fg.b),
            "divider must follow the requested origin",
        );

        // Untouched white above and to the left.
        assert_eq!(
            {
                let (r, g, b, _) = surface.pixel(div_x, origin_y as u32 - 4);
                (r, g, b)
            },
            (255, 255, 255),
            "nothing should paint above the requested origin",
        );
        assert_eq!(
            {
                let (r, g, b, _) = surface.pixel(4, probe_y);
                (r, g, b)
            },
            (255, 255, 255),
            "nothing should paint left of the requested origin",
        );
    }

    #[test]
    fn unified_mode_counts_hunk_headers_in_total_rows() {
        let view = sample_view(DiffMode::Unified);
        let (_surface, layout, _lh) =
            paint_via_backend(&view, QRect::new(0.0, 0.0, W as f32, H as f32));
        let hunk_count = view.hunks.len();
        assert!(hunk_count > 0, "fixture should produce at least one hunk");
        assert_eq!(
            layout.total_rows,
            view.total_rows() + hunk_count,
            "unified mode reports content rows plus one @@ header per hunk",
        );
    }

    #[test]
    fn header_row_paints_labels_strip() {
        let mut view = sample_view(DiffMode::SideBySide);
        view.left_label = Some("a/main.rs".into());
        view.right_label = Some("b/main.rs".into());
        let (surface, layout, lh) =
            paint_via_backend(&view, QRect::new(0.0, 0.0, W as f32, H as f32));
        let theme = Theme::default();

        // Header strip occupies the first row; probe clear of glyphs.
        let left_w = ((W as f64 - DIVIDER_PX) / 2.0).floor();
        let (r, g, b, _) = surface.pixel(left_w as u32 - 4, (lh * 0.5) as u32);
        assert_eq!(
            (r, g, b),
            (theme.header_bg.r, theme.header_bg.g, theme.header_bg.b),
        );
        // ...and the header steals one row from the content capacity.
        assert_eq!(layout.visible_rows, ((H as f64 - lh) / lh).floor() as usize,);
    }

    #[test]
    fn zero_sized_rect_returns_zero_visible_rows_without_painting() {
        let view = sample_view(DiffMode::SideBySide);
        let (surface, layout, _lh) = paint_via_backend(&view, QRect::new(0.0, 0.0, 0.0, 0.0));
        assert_eq!(layout.visible_rows, 0);
        assert_eq!(layout.total_rows, view.total_rows());
        assert_eq!(
            {
                let (r, g, b, _) = surface.pixel(1, 1);
                (r, g, b)
            },
            (255, 255, 255),
        );
    }
}