use std::ops::Range;
use std::sync::Arc;
use ratatui::buffer::Buffer;
use ratatui::layout::{Offset, Position, Rect};
use ratatui::style::Style;
use ratatui::text::{Line, Span};
use super::controller::settings::theme::groups::{Groups, HLGroup};
use super::render::selection::Exclusions;
use super::render::RenderContext;
pub struct Resized {
pub width: u16,
}
pub struct Rethemed;
pub struct Ticked;
pub struct Reformatted;
pub trait Element: Send {
type Context;
fn render(&self, context: &Self::Context, area: Rect, buf: &mut Buffer);
}
pub trait Block {
fn height(&self, context: &RenderContext) -> u16;
fn render_local(&self, context: &RenderContext, buf: &mut Buffer);
fn animated(&self) -> bool {
false
}
fn local_selection_exclusions(&self, _: &RenderContext, _: &mut Exclusions) {}
fn link_at(&self, _: &RenderContext, _: Position) -> Option<Link> {
None
}
fn selection_exclusions(
&self,
context: &RenderContext,
area: Rect,
exclusions: &mut Exclusions,
) {
let mut local = Exclusions::default();
self.local_selection_exclusions(&context.with_width(area.width), &mut local);
exclusions.extend(local + Offset::new(i32::from(area.x), i32::from(area.y)));
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Link {
pub target: Arc<str>,
pub cells: Vec<Rect>,
}
impl Link {
pub fn moved(self, offset: Offset) -> Self {
Self {
cells: self.cells.into_iter().map(|cells| cells + offset).collect(),
..self
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HLSpan<T = Range<usize>> {
pub content: T,
pub kind: HLGroup,
pub mark: Option<HLGroup>,
pub target: Option<Arc<str>>,
}
impl<T> HLSpan<T> {
pub fn new(content: impl Into<T>, kind: HLGroup) -> Self {
Self {
content: content.into(),
kind,
mark: None,
target: None,
}
}
pub fn marked(self, mark: HLGroup) -> Self {
Self {
mark: Some(mark),
..self
}
}
pub fn with_content<U>(&self, content: impl Into<U>) -> HLSpan<U> {
HLSpan {
content: content.into(),
kind: self.kind,
mark: self.mark,
target: self.target.clone(),
}
}
pub fn style(&self, groups: &Groups) -> Style {
let style = groups.style(self.kind);
match self.mark {
Some(mark) => style.patch(groups.style(mark)),
None => style,
}
}
}
pub type TextSpan = HLSpan<String>;
pub trait Text: Send {
type Context: ?Sized;
fn spans(&self, context: &Self::Context) -> Vec<Span<'static>>;
fn line(&self, context: &Self::Context) -> Line<'static> {
Line::from(self.spans(context))
}
}
impl Text for [TextSpan] {
type Context = Groups;
fn spans(&self, groups: &Groups) -> Vec<Span<'static>> {
self.iter()
.map(|span| Span::styled(span.content.clone(), span.style(groups)))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Local;
impl Block for Local {
fn height(&self, _: &RenderContext) -> u16 {
1
}
fn render_local(&self, context: &RenderContext, buf: &mut Buffer) {
buf[(context.width() - 1, 0)].set_symbol("x");
}
fn local_selection_exclusions(&self, context: &RenderContext, exclusions: &mut Exclusions) {
exclusions.push(Rect::new(context.width() - 1, 0, 1, 1));
}
}
#[test]
fn selection_geometry_is_translated_once_at_the_block_boundary() {
let context = RenderContext::new(8, 0);
let area = Rect::new(3, 2, 2, 1);
let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 4));
buffer.set_style(
area,
ratatui::style::Style::new().bg(ratatui::style::Color::Blue),
);
let mut exclusions = Exclusions::default();
Block::selection_exclusions(&Local, &context, area, &mut exclusions);
assert_eq!(exclusions, [Rect::new(4, 2, 1, 1)].into());
}
}