pub use ratatui::style::{Color, Modifier, Style};
pub use ratatui::widgets::Borders;
use ratatui::widgets::{Scrollbar, ScrollbarOrientation};
#[derive(Debug, Clone)]
pub struct BaseStyle {
pub border: Option<BorderStyle>,
pub padding: Padding,
pub bg: Option<Color>,
pub fg: Option<Color>,
}
impl Default for BaseStyle {
fn default() -> Self {
Self {
border: Some(BorderStyle::default()),
padding: Padding::default(),
bg: None,
fg: Some(Color::Reset),
}
}
}
pub trait ComponentStyle {
fn base(&self) -> &BaseStyle;
fn border(&self) -> Option<&BorderStyle> {
self.base().border.as_ref()
}
fn padding(&self) -> &Padding {
&self.base().padding
}
fn bg(&self) -> Option<Color> {
self.base().bg
}
fn fg(&self) -> Option<Color> {
self.base().fg
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Padding {
pub top: u16,
pub right: u16,
pub bottom: u16,
pub left: u16,
}
impl Padding {
pub fn all(v: u16) -> Self {
Self {
top: v,
right: v,
bottom: v,
left: v,
}
}
pub fn xy(x: u16, y: u16) -> Self {
Self {
top: y,
right: x,
bottom: y,
left: x,
}
}
pub fn new(top: u16, right: u16, bottom: u16, left: u16) -> Self {
Self {
top,
right,
bottom,
left,
}
}
pub fn horizontal(&self) -> u16 {
self.left + self.right
}
pub fn vertical(&self) -> u16 {
self.top + self.bottom
}
}
#[derive(Debug, Clone)]
pub struct BorderStyle {
pub borders: Borders,
pub style: Style,
pub focused_style: Option<Style>,
}
impl Default for BorderStyle {
fn default() -> Self {
Self {
borders: Borders::ALL,
style: Style::default().fg(Color::DarkGray),
focused_style: Some(Style::default().fg(Color::Cyan)),
}
}
}
impl BorderStyle {
pub fn all() -> Self {
Self::default()
}
pub fn none() -> Self {
Self {
borders: Borders::NONE,
..Default::default()
}
}
pub fn style_for_focus(&self, is_focused: bool) -> Style {
if is_focused {
self.focused_style.unwrap_or(self.style)
} else {
self.style
}
}
}
#[derive(Debug, Clone)]
pub struct SelectionStyle {
pub style: Option<Style>,
pub marker: Option<&'static str>,
pub disabled: bool,
}
impl Default for SelectionStyle {
fn default() -> Self {
Self {
style: Some(
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
),
marker: Some("> "),
disabled: false,
}
}
}
impl SelectionStyle {
pub fn disabled() -> Self {
Self {
style: None,
marker: None,
disabled: true,
}
}
pub fn marker_only(marker: &'static str) -> Self {
Self {
style: None,
marker: Some(marker),
disabled: false,
}
}
pub fn style_only(style: Style) -> Self {
Self {
style: Some(style),
marker: None,
disabled: false,
}
}
}
#[derive(Debug, Clone)]
pub struct ScrollbarStyle {
pub thumb: Style,
pub track: Style,
pub begin: Style,
pub end: Style,
pub thumb_symbol: Option<&'static str>,
pub track_symbol: Option<&'static str>,
pub begin_symbol: Option<&'static str>,
pub end_symbol: Option<&'static str>,
}
impl Default for ScrollbarStyle {
fn default() -> Self {
Self {
thumb: Style::default().fg(Color::Cyan),
track: Style::default().fg(Color::DarkGray),
begin: Style::default().fg(Color::DarkGray),
end: Style::default().fg(Color::DarkGray),
thumb_symbol: Some("█"),
track_symbol: Some("│"),
begin_symbol: None,
end_symbol: None,
}
}
}
impl ScrollbarStyle {
pub fn build(&self, orientation: ScrollbarOrientation) -> Scrollbar<'static> {
let mut scrollbar = Scrollbar::new(orientation)
.thumb_style(self.thumb)
.track_style(self.track)
.begin_style(self.begin)
.end_style(self.end)
.track_symbol(self.track_symbol)
.begin_symbol(self.begin_symbol)
.end_symbol(self.end_symbol);
if let Some(symbol) = self.thumb_symbol {
scrollbar = scrollbar.thumb_symbol(symbol);
}
scrollbar
}
}
use ratatui::text::{Line, Span};
pub fn highlight_substring(
text: &str,
query: &str,
base_style: Style,
highlight_style: Style,
) -> Line<'static> {
if query.is_empty() {
return Line::styled(text.to_string(), base_style);
}
if !text.is_ascii() || !query.is_ascii() {
return Line::styled(text.to_string(), base_style);
}
let text_lower = text.to_lowercase();
let query_lower = query.to_lowercase();
let mut spans = Vec::new();
let mut last_end = 0;
for (start, _) in text_lower.match_indices(&query_lower) {
if start > last_end {
spans.push(Span::styled(text[last_end..start].to_string(), base_style));
}
let end = start + query.len();
spans.push(Span::styled(text[start..end].to_string(), highlight_style));
last_end = end;
}
if last_end < text.len() {
spans.push(Span::styled(text[last_end..].to_string(), base_style));
}
if spans.is_empty() {
Line::styled(text.to_string(), base_style)
} else {
Line::from(spans)
}
}