use crate::key::Binding;
use rusty_bubbletea::model::{Cmd, Msg};
use rusty_lipgloss::{color::Color, style::Style, TOP};
pub trait KeyMap {
fn short_help(&self) -> Vec<Binding>;
fn full_help(&self) -> Vec<Vec<Binding>>;
}
#[derive(Debug, Clone)]
pub struct Styles {
pub ellipsis: Style,
pub short_key: Style,
pub short_desc: Style,
pub short_separator: Style,
pub full_key: Style,
pub full_desc: Style,
pub full_separator: Style,
}
pub fn default_styles(is_dark: bool) -> Styles {
let light_dark = rusty_lipgloss::color::light_dark(is_dark);
let key_style =
Style::new().foreground_color(light_dark(Color::parse("#909090"), Color::parse("#626262")));
let desc_style =
Style::new().foreground_color(light_dark(Color::parse("#B2B2B2"), Color::parse("#4A4A4A")));
let sep_style =
Style::new().foreground_color(light_dark(Color::parse("#DADADA"), Color::parse("#3C3C3C")));
Styles {
short_key: key_style.clone(),
short_desc: desc_style.clone(),
short_separator: sep_style.clone(),
ellipsis: sep_style.clone(),
full_key: key_style,
full_desc: desc_style,
full_separator: sep_style,
}
}
pub fn default_dark_styles() -> Styles {
default_styles(true)
}
pub fn default_light_styles() -> Styles {
default_styles(false)
}
#[derive(Debug, Clone)]
pub struct Model {
pub show_all: bool,
pub short_separator: String,
pub full_separator: String,
pub ellipsis: String,
pub styles: Styles,
width: usize,
}
pub fn new() -> Model {
Model {
show_all: false,
short_separator: " • ".to_string(),
full_separator: " ".to_string(),
ellipsis: "…".to_string(),
styles: default_dark_styles(),
width: 0,
}
}
impl Model {
pub fn update(&mut self, _msg: &dyn Msg) -> Cmd {
None
}
pub fn view(&self, k: &dyn KeyMap) -> String {
if self.show_all {
return self.full_help_view(&k.full_help());
}
self.short_help_view(&k.short_help())
}
pub fn set_width(&mut self, w: usize) {
self.width = w;
}
pub fn width(&self) -> usize {
self.width
}
pub fn short_help_view(&self, bindings: &[Binding]) -> String {
if bindings.is_empty() {
return String::new();
}
let mut b = String::new();
let mut total_width = 0;
let separator = self
.styles
.short_separator
.clone()
.inline(true)
.render(&self.short_separator);
for (i, kb) in bindings.iter().enumerate() {
if !kb.enabled() {
continue;
}
let sep = if total_width > 0 && i < bindings.len() {
separator.clone()
} else {
String::new()
};
let str = sep
+ &self
.styles
.short_key
.clone()
.inline(true)
.render(&kb.help().key)
+ " "
+ &self
.styles
.short_desc
.clone()
.inline(true)
.render(&kb.help().desc);
let w = rusty_lipgloss::size::width(&str);
if let (tail, false) = self.should_add_item(total_width, w) {
if !tail.is_empty() {
b.push_str(&tail);
}
break;
}
total_width += w;
b.push_str(&str);
}
b
}
pub fn full_help_view(&self, groups: &[Vec<Binding>]) -> String {
if groups.is_empty() {
return String::new();
}
let mut out: Vec<String> = Vec::new();
let mut total_width = 0;
let separator = self
.styles
.full_separator
.clone()
.inline(true)
.render(&self.full_separator);
for (i, group) in groups.iter().enumerate() {
if group.is_empty() || !should_render_column(group) {
continue;
}
let mut keys: Vec<String> = Vec::new();
let mut descriptions: Vec<String> = Vec::new();
let sep = if total_width > 0 && i < groups.len() {
separator.clone()
} else {
String::new()
};
for kb in group {
if !kb.enabled() {
continue;
}
keys.push(kb.help().key);
descriptions.push(kb.help().desc);
}
let key_col = self.styles.full_key.clone().render(&keys.join("\n"));
let desc_col = self
.styles
.full_desc
.clone()
.render(&descriptions.join("\n"));
let col = rusty_lipgloss::join::join_horizontal(TOP, &[&sep, &key_col, " ", &desc_col]);
let w = rusty_lipgloss::size::width(&col);
if let (tail, false) = self.should_add_item(total_width, w) {
if !tail.is_empty() {
out.push(tail);
}
break;
}
total_width += w;
out.push(col);
}
let refs: Vec<&str> = out.iter().map(|s| s.as_str()).collect();
rusty_lipgloss::join::join_horizontal(TOP, &refs)
}
fn should_add_item(&self, total_width: usize, width: usize) -> (String, bool) {
if self.width > 0 && total_width + width > self.width {
let tail = String::from(" ")
+ &self
.styles
.ellipsis
.clone()
.inline(true)
.render(&self.ellipsis);
if total_width + rusty_lipgloss::size::width(&tail) < self.width {
return (tail, false);
}
}
(String::new(), true)
}
}
fn should_render_column(b: &[Binding]) -> bool {
b.iter().any(|v| v.enabled())
}