use bitflags::bitflags; use std::cmp::{max, min}; #[derive(Debug, Clone, Copy, PartialEq)] /// UNSTABLE pub enum CursorKind { /// █ Block, /// | Bar, /// _ Underline, /// Hidden cursor, can set cursor position with this to let IME have correct cursor position. Hidden, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Margin { pub vertical: u16, pub horizontal: u16, } /// A simple rectangle used in the computation of the layout and to give widgets an hint about the /// area they are supposed to render to. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub struct Rect { pub x: u16, pub y: u16, pub width: u16, pub height: u16, } impl Default for Rect { fn default() -> Rect { Rect { x: 0, y: 0, width: 0, height: 0, } } } impl Rect { /// Creates a new rect, with width and height limited to keep the area under max u16. /// If clipped, aspect ratio will be preserved. pub fn new(x: u16, y: u16, width: u16, height: u16) -> Rect { let max_area = u16::max_value(); let (clipped_width, clipped_height) = if u32::from(width) * u32::from(height) > u32::from(max_area) { let aspect_ratio = f64::from(width) / f64::from(height); let max_area_f = f64::from(max_area); let height_f = (max_area_f / aspect_ratio).sqrt(); let width_f = height_f * aspect_ratio; (width_f as u16, height_f as u16) } else { (width, height) }; Rect { x, y, width: clipped_width, height: clipped_height, } } pub fn area(self) -> u16 { self.width * self.height } pub fn left(self) -> u16 { self.x } pub fn right(self) -> u16 { self.x.saturating_add(self.width) } pub fn top(self) -> u16 { self.y } pub fn bottom(self) -> u16 { self.y.saturating_add(self.height) } pub fn inner(self, margin: &Margin) -> Rect { if self.width < 2 * margin.horizontal || self.height < 2 * margin.vertical { Rect::default() } else { Rect { x: self.x + margin.horizontal, y: self.y + margin.vertical, width: self.width - 2 * margin.horizontal, height: self.height - 2 * margin.vertical, } } } pub fn union(self, other: Rect) -> Rect { let x1 = min(self.x, other.x); let y1 = min(self.y, other.y); let x2 = max(self.x + self.width, other.x + other.width); let y2 = max(self.y + self.height, other.y + other.height); Rect { x: x1, y: y1, width: x2 - x1, height: y2 - y1, } } pub fn intersection(self, other: Rect) -> Rect { let x1 = max(self.x, other.x); let y1 = max(self.y, other.y); let x2 = min(self.x + self.width, other.x + other.width); let y2 = min(self.y + self.height, other.y + other.height); Rect { x: x1, y: y1, width: x2 - x1, height: y2 - y1, } } pub fn intersects(self, other: Rect) -> bool { self.x < other.x + other.width && self.x + self.width > other.x && self.y < other.y + other.height && self.y + self.height > other.y } } #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Color { Reset, Black, Red, Green, Yellow, Blue, Magenta, Cyan, Gray, DarkGray, LightRed, LightGreen, LightYellow, LightBlue, LightMagenta, LightCyan, White, Rgb(u8, u8, u8), Indexed(u8), } #[cfg(feature = "term")] impl From for crossterm::style::Color { fn from(color: Color) -> Self { use crossterm::style::Color as CColor; match color { Color::Reset => CColor::Reset, Color::Black => CColor::Black, Color::Red => CColor::DarkRed, Color::Green => CColor::DarkGreen, Color::Yellow => CColor::DarkYellow, Color::Blue => CColor::DarkBlue, Color::Magenta => CColor::DarkMagenta, Color::Cyan => CColor::DarkCyan, Color::Gray => CColor::Grey, Color::DarkGray => CColor::DarkGrey, Color::LightRed => CColor::Red, Color::LightGreen => CColor::Green, Color::LightBlue => CColor::Blue, Color::LightYellow => CColor::Yellow, Color::LightMagenta => CColor::Magenta, Color::LightCyan => CColor::Cyan, Color::White => CColor::White, Color::Indexed(i) => CColor::AnsiValue(i), Color::Rgb(r, g, b) => CColor::Rgb { r, g, b }, } } } bitflags! { /// Modifier changes the way a piece of text is displayed. /// /// They are bitflags so they can easily be composed. /// /// ## Examples /// /// ```rust /// # use helix_view::graphics::Modifier; /// /// let m = Modifier::BOLD | Modifier::ITALIC; /// ``` #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Modifier: u16 { const BOLD = 0b0000_0000_0001; const DIM = 0b0000_0000_0010; const ITALIC = 0b0000_0000_0100; const UNDERLINED = 0b0000_0000_1000; const SLOW_BLINK = 0b0000_0001_0000; const RAPID_BLINK = 0b0000_0010_0000; const REVERSED = 0b0000_0100_0000; const HIDDEN = 0b0000_1000_0000; const CROSSED_OUT = 0b0001_0000_0000; } } /// Style let you control the main characteristics of the displayed elements. /// /// ```rust /// # use helix_view::graphics::{Color, Modifier, Style}; /// Style::default() /// .fg(Color::Black) /// .bg(Color::Green) /// .add_modifier(Modifier::ITALIC | Modifier::BOLD); /// ``` /// /// It represents an incremental change. If you apply the styles S1, S2, S3 to a cell of the /// terminal buffer, the style of this cell will be the result of the merge of S1, S2 and S3, not /// just S3. /// /// ```rust /// # use helix_view::graphics::{Rect, Color, Modifier, Style}; /// # use helix_tui::buffer::Buffer; /// let styles = [ /// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC), /// Style::default().bg(Color::Red), /// Style::default().fg(Color::Yellow).remove_modifier(Modifier::ITALIC), /// ]; /// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1)); /// for style in &styles { /// buffer.get_mut(0, 0).set_style(*style); /// } /// assert_eq!( /// Style { /// fg: Some(Color::Yellow), /// bg: Some(Color::Red), /// add_modifier: Modifier::BOLD, /// sub_modifier: Modifier::empty(), /// }, /// buffer.get(0, 0).style(), /// ); /// ``` /// /// The default implementation returns a `Style` that does not modify anything. If you wish to /// reset all properties until that point use [`Style::reset`]. /// /// ``` /// # use helix_view::graphics::{Rect, Color, Modifier, Style}; /// # use helix_tui::buffer::Buffer; /// let styles = [ /// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC), /// Style::reset().fg(Color::Yellow), /// ]; /// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1)); /// for style in &styles { /// buffer.get_mut(0, 0).set_style(*style); /// } /// assert_eq!( /// Style { /// fg: Some(Color::Yellow), /// bg: Some(Color::Reset), /// add_modifier: Modifier::empty(), /// sub_modifier: Modifier::empty(), /// }, /// buffer.get(0, 0).style(), /// ); /// ``` #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Style { pub fg: Option, pub bg: Option, pub add_modifier: Modifier, pub sub_modifier: Modifier, } impl Default for Style { fn default() -> Style { Style { fg: None, bg: None, add_modifier: Modifier::empty(), sub_modifier: Modifier::empty(), } } } impl Style { /// Returns a `Style` resetting all properties. pub fn reset() -> Style { Style { fg: Some(Color::Reset), bg: Some(Color::Reset), add_modifier: Modifier::empty(), sub_modifier: Modifier::all(), } } /// Changes the foreground color. /// /// ## Examples /// /// ```rust /// # use helix_view::graphics::{Color, Style}; /// let style = Style::default().fg(Color::Blue); /// let diff = Style::default().fg(Color::Red); /// assert_eq!(style.patch(diff), Style::default().fg(Color::Red)); /// ``` pub fn fg(mut self, color: Color) -> Style { self.fg = Some(color); self } /// Changes the background color. /// /// ## Examples /// /// ```rust /// # use helix_view::graphics::{Color, Style}; /// let style = Style::default().bg(Color::Blue); /// let diff = Style::default().bg(Color::Red); /// assert_eq!(style.patch(diff), Style::default().bg(Color::Red)); /// ``` pub fn bg(mut self, color: Color) -> Style { self.bg = Some(color); self } /// Changes the text emphasis. /// /// When applied, it adds the given modifier to the `Style` modifiers. /// /// ## Examples /// /// ```rust /// # use helix_view::graphics::{Color, Modifier, Style}; /// let style = Style::default().add_modifier(Modifier::BOLD); /// let diff = Style::default().add_modifier(Modifier::ITALIC); /// let patched = style.patch(diff); /// assert_eq!(patched.add_modifier, Modifier::BOLD | Modifier::ITALIC); /// assert_eq!(patched.sub_modifier, Modifier::empty()); /// ``` pub fn add_modifier(mut self, modifier: Modifier) -> Style { self.sub_modifier.remove(modifier); self.add_modifier.insert(modifier); self } /// Changes the text emphasis. /// /// When applied, it removes the given modifier from the `Style` modifiers. /// /// ## Examples /// /// ```rust /// # use helix_view::graphics::{Color, Modifier, Style}; /// let style = Style::default().add_modifier(Modifier::BOLD | Modifier::ITALIC); /// let diff = Style::default().remove_modifier(Modifier::ITALIC); /// let patched = style.patch(diff); /// assert_eq!(patched.add_modifier, Modifier::BOLD); /// assert_eq!(patched.sub_modifier, Modifier::ITALIC); /// ``` pub fn remove_modifier(mut self, modifier: Modifier) -> Style { self.add_modifier.remove(modifier); self.sub_modifier.insert(modifier); self } /// Results in a combined style that is equivalent to applying the two individual styles to /// a style one after the other. /// /// ## Examples /// ``` /// # use helix_view::graphics::{Color, Modifier, Style}; /// let style_1 = Style::default().fg(Color::Yellow); /// let style_2 = Style::default().bg(Color::Red); /// let combined = style_1.patch(style_2); /// assert_eq!( /// Style::default().patch(style_1).patch(style_2), /// Style::default().patch(combined)); /// ``` pub fn patch(mut self, other: Style) -> Style { self.fg = other.fg.or(self.fg); self.bg = other.bg.or(self.bg); self.add_modifier.remove(other.sub_modifier); self.add_modifier.insert(other.add_modifier); self.sub_modifier.remove(other.add_modifier); self.sub_modifier.insert(other.sub_modifier); self } } #[cfg(test)] mod tests { use super::*; #[test] fn test_rect_size_truncation() { for width in 256u16..300u16 { for height in 256u16..300u16 { let rect = Rect::new(0, 0, width, height); rect.area(); // Should not panic. assert!(rect.width < width || rect.height < height); // The target dimensions are rounded down so the math will not be too precise // but let's make sure the ratios don't diverge crazily. assert!( (f64::from(rect.width) / f64::from(rect.height) - f64::from(width) / f64::from(height)) .abs() < 1.0 ) } } // One dimension below 255, one above. Area above max u16. let width = 900; let height = 100; let rect = Rect::new(0, 0, width, height); assert_ne!(rect.width, 900); assert_ne!(rect.height, 100); assert!(rect.width < width || rect.height < height); } #[test] fn test_rect_size_preservation() { for width in 0..256u16 { for height in 0..256u16 { let rect = Rect::new(0, 0, width, height); rect.area(); // Should not panic. assert_eq!(rect.width, width); assert_eq!(rect.height, height); } } // One dimension below 255, one above. Area below max u16. let rect = Rect::new(0, 0, 300, 100); assert_eq!(rect.width, 300); assert_eq!(rect.height, 100); } fn styles() -> Vec