use std::time::Duration;
use crate::color::{APART, Rgb};
use crate::runtime::{App, Command, Harness};
use crate::widget::{Length, View};
use crate::widgets::{Panel, Popover, Select, Side, SidePanel, Text};
const THEMES: [&str; 4] = ["monochrome", "nordic", "amber", "iris"];
struct Menus {
in_panel: bool,
}
impl App for Menus {
type Msg = usize;
fn update(&mut self, _: usize) -> Command<usize> {
Command::none()
}
fn view(&self, ui: &mut View<'_, usize>) {
let select = |ui: &mut View<'_, usize>| {
ui.add(Select::new(["Alpha", "Beta", "Gamma"]).placeholder("Pick").on_select(|i| i)).id("pick");
};
let panel = SidePanel::new(30).side(Side::Right).open(true);
if self.in_panel {
panel.panel(select).body(|ui| {
ui.add(Text::new("body"));
})
} else {
panel
.panel(|ui| {
ui.add(Text::new("tools"));
})
.body(select)
}
.show(ui);
}
}
fn open_menu(in_panel: bool, theme: &str) -> Harness<Menus> {
let mut h = Harness::new(Menus { in_panel }, 70, 14);
h.set_theme(theme);
h.click_text("Pick");
h.advance(Duration::from_millis(300));
h
}
fn menu_bg(h: &Harness<Menus>, label: &str) -> Option<Rgb> {
let (x, y) = h.find(label).unwrap_or_else(|| panic!("{label} is on screen: {}", h.screen()));
h.bg(u16::try_from(x + 8).unwrap_or(0), u16::try_from(y).unwrap_or(0))
}
#[test]
fn a_menu_over_a_side_panel_is_lifted_apart_from_it_in_every_theme() {
for theme in THEMES {
let h = open_menu(true, theme);
let overlay = h.env().theme().color("overlay");
let panel = h.bg(69, 13).expect("a true-colour panel");
let menu = menu_bg(&h, "Gamma").expect("a true-colour menu");
assert_ne!(Some(menu), overlay, "{theme}: over a panel the menu is not left on the plain overlay tone");
assert!(
menu.perceptual_distance(panel) >= APART,
"{theme}: menu {menu} sits {:.3} from panel {panel}",
menu.perceptual_distance(panel)
);
let highlighted = menu_bg(&h, "Alpha").expect("a true-colour row");
assert!(highlighted.perceptual_distance(menu) >= 0.02, "{theme}: {highlighted} against {menu}");
}
}
#[test]
fn a_menu_over_the_screen_ground_keeps_the_overlay_tone() {
for theme in THEMES {
let h = open_menu(false, theme);
let overlay = h.env().theme().color("overlay");
assert_eq!(menu_bg(&h, "Gamma"), overlay, "{theme}");
}
}
#[test]
fn a_popover_inside_a_panel_is_lifted_and_one_beside_it_is_not() {
struct Popovers;
impl App for Popovers {
type Msg = ();
fn update(&mut self, (): ()) -> Command<()> {
Command::none()
}
fn view(&self, ui: &mut View<'_, ()>) {
ui.row(|ui| {
ui.add_with(Panel::new().title("Panel"), |ui| {
Popover::new(true)
.anchor(|ui| {
ui.add(Text::new("inner"));
})
.content(|ui| {
ui.add(Text::new("lifted"));
})
.show(ui);
for _ in 0..6 {
ui.add(Text::new(" "));
}
})
.width(Length::Cells(30));
ui.column(|ui| {
Popover::new(true)
.anchor(|ui| {
ui.add(Text::new("outer"));
})
.content(|ui| {
ui.add(Text::new("plain"));
})
.show(ui);
})
.fill();
})
.gap(4);
}
}
for theme in THEMES {
let mut h = Harness::new(Popovers, 70, 14);
h.set_theme(theme);
h.advance(Duration::from_millis(300));
let overlay = h.env().theme().color("overlay");
let (px, py) = h.find("Panel").expect("the panel title");
let panel = h.bg(u16::try_from(px + 20).unwrap_or(0), u16::try_from(py + 8).unwrap_or(0)).expect("panel");
let (x, y) = h.find("lifted").expect("the inner popover");
let lifted = h.bg(u16::try_from(x).unwrap_or(0), u16::try_from(y).unwrap_or(0)).expect("inner popover");
assert!(lifted.perceptual_distance(panel) >= APART, "{theme}: {lifted} over {panel}");
let (x, y) = h.find("plain").expect("the outer popover");
assert_eq!(h.bg(u16::try_from(x).unwrap_or(0), u16::try_from(y).unwrap_or(0)), overlay, "{theme}");
}
}
#[test]
fn reduced_colour_depth_leaves_the_palette_cells_alone() {
let mut h = Harness::new(Menus { in_panel: true }, 70, 14);
h.set_depth(crate::color::ColorDepth::Ansi256);
h.click_text("Pick");
h.advance(Duration::from_millis(300));
let (x, y) = h.find("Gamma").expect("the menu");
let cell = &h.buffer()[(u16::try_from(x + 8).unwrap_or(0), u16::try_from(y).unwrap_or(0))];
let overlay = h.env().theme().color("overlay").expect("overlay");
assert_eq!(cell.bg, ratatui_core::style::Color::Indexed(overlay.to_ansi256()));
}