lernie 0.1.102

lernie: the operator seat — the window and wire client for a yog server
//! **What lifts under the pointer** (DESIGN §4.44 item 5): a row, a link and
//! a tab wear [`hover`] while the pointer is over them and nothing at rest;
//! a chosen row keeps its `RAISED`; and the link's mark is inside its seat.

use super::super::{link, link_marked, row, tab};
use crate::paint_probe::{fills_of, frame::Window, seen_of};
use crate::ui::theme::{INK, INK_FAINT, RAISED, hover};

/// One frame of `body` with the pointer at `at`, after one that laid it —
/// egui reads a hover off the previous frame's rects.
fn under(at: egui::Pos2, body: impl FnMut(&egui::Context)) -> egui::FullOutput {
    let window = Window::sized(300.0, 100.0);
    let mut body = body;
    window.frame(Vec::new(), &mut body);
    window.frame(vec![egui::Event::PointerMoved(at)], &mut body)
}

/// Whether one frame filled anything in the hover wash.
fn lifted(output: &egui::FullOutput) -> bool {
    fills_of(output).iter().any(|(_, fill)| *fill == hover())
}

/// Paint each pickable shape on its own line.
fn pickables(ctx: &egui::Context, chosen: bool) {
    egui::CentralPanel::default().show(ctx, |ui| {
        row(ui, "a row", INK, None, chosen, 0.0);
        link(ui, "a link", INK);
        tab(ui, "a tab", INK);
    });
}

/// **Under the pointer each lifts, and nothing does away from it** — the
/// wash is the hover's alone, the brand at three quarters of the selection's.
#[test]
fn a_row_a_link_and_a_tab_lift_under_the_pointer() {
    let away = under(egui::pos2(290.0, 95.0), |ctx| pickables(ctx, false));
    assert!(!lifted(&away), "bare at rest");
    for words in ["a row", "a link", "a tab"] {
        let laid = under(egui::pos2(290.0, 95.0), |ctx| pickables(ctx, false));
        let at = seen_of(&laid)
            .into_iter()
            .find(|run| run.text == words)
            .unwrap_or_else(|| panic!("{words:?} on the glass"))
            .shown
            .center();
        assert!(lifted(&under(at, |ctx| pickables(ctx, false))), "{words}");
    }
}

/// **A chosen row under the pointer keeps its own, stronger tint.**
#[test]
fn a_chosen_row_keeps_its_raise_under_the_pointer() {
    let at = egui::pos2(20.0, 15.0);
    let output = under(at, |ctx| pickables(ctx, true));
    let fills = fills_of(&output);
    assert!(fills.iter().any(|(_, fill)| *fill == RAISED), "raised");
    let row = fills
        .iter()
        .find(|(rect, _)| rect.contains(at))
        .map(|(_, fill)| *fill);
    assert_ne!(row, Some(hover()), "not washed over");
}

/// **The mark is in the door**: one run on the glass, `<words> <mark>`, and
/// a pointer on the mark lifts the whole seat and is the hand.
#[test]
fn the_mark_is_inside_the_link() {
    let body = |ctx: &egui::Context| {
        egui::CentralPanel::default().show(ctx, |ui| {
            link_marked(ui, "model", INK, ("⏶", INK_FAINT));
        });
    };
    let laid = under(egui::pos2(290.0, 95.0), body);
    let run = seen_of(&laid)
        .into_iter()
        .find(|run| run.text == "model ⏶")
        .expect("one run");
    let on_mark = egui::pos2(run.shown.max.x - 2.0, run.shown.center().y);
    let output = under(on_mark, body);
    assert!(lifted(&output), "the mark lifts the seat");
    assert_eq!(
        output.platform_output.cursor_icon,
        egui::CursorIcon::PointingHand
    );
}

/// **A block row's seat is the block** (bl-232e): its aside and the lines
/// beneath stand inside the one rect the wash and the chosen tint fill.
#[test]
fn a_block_row_is_one_seat_over_every_line() {
    let body = |ctx: &egui::Context| {
        egui::CentralPanel::default().show(ctx, |ui| {
            super::super::Row {
                mark: ("?".to_owned(), INK),
                words: "head".to_owned(),
                ink: INK,
                state: None,
                chosen: false,
                indent: 0.0,
                aside: Some("2m".to_owned()),
                beneath: vec![("beneath".to_owned(), INK_FAINT, egui::TextStyle::Small)],
            }
            .paint(ui);
        });
    };
    let laid = under(egui::pos2(290.0, 95.0), body);
    let at = |text: &str| {
        seen_of(&laid)
            .into_iter()
            .find(|run| run.text == text)
            .unwrap_or_else(|| panic!("{text:?}"))
            .shown
    };
    let (aside, beneath) = (at("2m"), at("beneath"));
    assert!(
        aside.left() > at("? head").right(),
        "the aside at the right end"
    );
    let output = under(beneath.center(), body);
    let block = fills_of(&output)
        .into_iter()
        .find(|(_, fill)| *fill == hover())
        .expect("the pointer on a line beneath lifts the block")
        .0;
    assert!(block.contains(aside.center()) && block.contains(beneath.center()));
}