#![forbid(unsafe_code)]
use ftui_style::Style;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum FocusIndicatorKind {
#[default]
StyleOverlay,
Underline,
Border,
None,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct FocusIndicator {
kind: FocusIndicatorKind,
style: Style,
}
impl Default for FocusIndicator {
fn default() -> Self {
Self {
kind: FocusIndicatorKind::StyleOverlay,
style: Style::new().reverse(),
}
}
}
impl FocusIndicator {
#[must_use]
pub fn style_overlay(style: Style) -> Self {
Self {
kind: FocusIndicatorKind::StyleOverlay,
style,
}
}
#[must_use]
pub fn underline() -> Self {
Self {
kind: FocusIndicatorKind::Underline,
style: Style::new().underline(),
}
}
#[must_use]
pub fn border() -> Self {
Self {
kind: FocusIndicatorKind::Border,
style: Style::new().bold(),
}
}
#[must_use]
pub fn none() -> Self {
Self {
kind: FocusIndicatorKind::None,
style: Style::new(),
}
}
#[must_use]
pub fn with_style(mut self, style: Style) -> Self {
self.style = style;
self
}
#[must_use]
pub fn with_kind(mut self, kind: FocusIndicatorKind) -> Self {
self.kind = kind;
self
}
#[inline]
#[must_use]
pub fn kind(&self) -> FocusIndicatorKind {
self.kind
}
#[inline]
#[must_use]
pub fn style(&self) -> Style {
self.style
}
#[inline]
#[must_use]
pub fn is_visible(&self) -> bool {
self.kind != FocusIndicatorKind::None
}
#[must_use]
pub fn apply_to(&self, base: Style) -> Style {
if self.kind == FocusIndicatorKind::None {
return base;
}
self.style.merge(&base)
}
}
#[cfg(test)]
mod tests {
use super::*;
use ftui_render::cell::PackedRgba;
#[test]
fn default_is_reverse_overlay() {
let ind = FocusIndicator::default();
assert_eq!(ind.kind(), FocusIndicatorKind::StyleOverlay);
assert!(ind.is_visible());
}
#[test]
fn underline_indicator() {
let ind = FocusIndicator::underline();
assert_eq!(ind.kind(), FocusIndicatorKind::Underline);
assert!(ind.is_visible());
}
#[test]
fn border_indicator() {
let ind = FocusIndicator::border();
assert_eq!(ind.kind(), FocusIndicatorKind::Border);
assert!(ind.is_visible());
}
#[test]
fn none_indicator_not_visible() {
let ind = FocusIndicator::none();
assert_eq!(ind.kind(), FocusIndicatorKind::None);
assert!(!ind.is_visible());
}
#[test]
fn with_style_builder() {
let style = Style::new().bold().italic();
let ind = FocusIndicator::underline().with_style(style);
assert_eq!(ind.style(), style);
assert_eq!(ind.kind(), FocusIndicatorKind::Underline);
}
#[test]
fn with_kind_builder() {
let ind = FocusIndicator::default().with_kind(FocusIndicatorKind::Border);
assert_eq!(ind.kind(), FocusIndicatorKind::Border);
}
#[test]
fn apply_to_merges_styles() {
let base = Style::new().fg(PackedRgba::rgb(255, 0, 0));
let ind = FocusIndicator::style_overlay(Style::new().bold());
let result = ind.apply_to(base);
assert_eq!(result.fg, Some(PackedRgba::rgb(255, 0, 0)));
}
#[test]
fn apply_to_none_returns_base() {
let base = Style::new().fg(PackedRgba::rgb(255, 0, 0)).bold();
let ind = FocusIndicator::none();
let result = ind.apply_to(base);
assert_eq!(result, base);
}
#[test]
fn style_overlay_constructor() {
let style = Style::new().italic();
let ind = FocusIndicator::style_overlay(style);
assert_eq!(ind.kind(), FocusIndicatorKind::StyleOverlay);
assert_eq!(ind.style(), style);
}
}