aboutsummaryrefslogtreecommitdiff
path: root/helix-view/src/graphics.rs
diff options
context:
space:
mode:
Diffstat (limited to 'helix-view/src/graphics.rs')
-rw-r--r--helix-view/src/graphics.rs1330
1 files changed, 665 insertions, 665 deletions
diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs
index 7033b7a4..fb3c8b3f 100644
--- a/helix-view/src/graphics.rs
+++ b/helix-view/src/graphics.rs
@@ -1,665 +1,665 @@
-use bitflags::bitflags;
-use serde::{Deserialize, Serialize};
-use std::{
- cmp::{max, min},
- str::FromStr,
-};
-
-#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
-#[serde(rename_all = "lowercase")]
-/// UNSTABLE
-pub enum CursorKind {
- /// █
- Block,
- /// |
- Bar,
- /// _
- Underline,
- /// Hidden cursor, can set cursor position with this to let IME have correct cursor position.
- Hidden,
-}
-
-impl Default for CursorKind {
- fn default() -> Self {
- Self::Block
- }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct Margin {
- pub left: u16,
- pub right: u16,
- pub top: u16,
- pub bottom: u16,
-}
-
-impl Margin {
- pub fn none() -> Self {
- Self {
- left: 0,
- right: 0,
- top: 0,
- bottom: 0,
- }
- }
-
- /// Set uniform margin for all sides.
- pub fn all(value: u16) -> Self {
- Self {
- left: value,
- right: value,
- top: value,
- bottom: value,
- }
- }
-
- /// Set the margin of left and right sides to specified value.
- pub fn horizontal(value: u16) -> Self {
- Self {
- left: value,
- right: value,
- top: 0,
- bottom: 0,
- }
- }
-
- /// Set the margin of top and bottom sides to specified value.
- pub fn vertical(value: u16) -> Self {
- Self {
- left: 0,
- right: 0,
- top: value,
- bottom: value,
- }
- }
-
- /// Get the total width of the margin (left + right)
- pub fn width(&self) -> u16 {
- self.left + self.right
- }
-
- /// Get the total height of the margin (top + bottom)
- pub fn height(&self) -> u16 {
- self.top + self.bottom
- }
-}
-
-/// 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. (x, y) = (0, 0) is at the top left corner of the screen.
-#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)]
-pub struct Rect {
- pub x: u16,
- pub y: u16,
- pub width: u16,
- pub height: u16,
-}
-
-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,
- }
- }
-
- #[inline]
- pub fn area(self) -> u16 {
- self.width * self.height
- }
-
- #[inline]
- pub fn left(self) -> u16 {
- self.x
- }
-
- #[inline]
- pub fn right(self) -> u16 {
- self.x.saturating_add(self.width)
- }
-
- #[inline]
- pub fn top(self) -> u16 {
- self.y
- }
-
- #[inline]
- pub fn bottom(self) -> u16 {
- self.y.saturating_add(self.height)
- }
-
- // Returns a new Rect with width reduced from the left side.
- // This changes the `x` coordinate and clamps it to the right
- // edge of the original Rect.
- pub fn clip_left(self, width: u16) -> Rect {
- let width = std::cmp::min(width, self.width);
- Rect {
- x: self.x.saturating_add(width),
- width: self.width.saturating_sub(width),
- ..self
- }
- }
-
- // Returns a new Rect with width reduced from the right side.
- // This does _not_ change the `x` coordinate.
- pub fn clip_right(self, width: u16) -> Rect {
- Rect {
- width: self.width.saturating_sub(width),
- ..self
- }
- }
-
- // Returns a new Rect with height reduced from the top.
- // This changes the `y` coordinate and clamps it to the bottom
- // edge of the original Rect.
- pub fn clip_top(self, height: u16) -> Rect {
- let height = std::cmp::min(height, self.height);
- Rect {
- y: self.y.saturating_add(height),
- height: self.height.saturating_sub(height),
- ..self
- }
- }
-
- // Returns a new Rect with height reduced from the bottom.
- // This does _not_ change the `y` coordinate.
- pub fn clip_bottom(self, height: u16) -> Rect {
- Rect {
- height: self.height.saturating_sub(height),
- ..self
- }
- }
-
- pub fn with_height(self, height: u16) -> Rect {
- // new height may make area > u16::max_value, so use new()
- Self::new(self.x, self.y, self.width, height)
- }
-
- pub fn with_width(self, width: u16) -> Rect {
- Self::new(self.x, self.y, width, self.height)
- }
-
- pub fn inner(self, margin: &Margin) -> Rect {
- if self.width < margin.width() || self.height < margin.height() {
- Rect::default()
- } else {
- Rect {
- x: self.x + margin.left,
- y: self.y + margin.top,
- width: self.width - margin.width(),
- height: self.height - margin.height(),
- }
- }
- }
-
- /// Calculate the union between two [`Rect`]s.
- pub fn union(self, other: Rect) -> Rect {
- // Example:
- //
- // If `Rect` A is positioned at `(0, 0)` with a width and height of `5`,
- // and `Rect` B is positioned at `(5, 0)` with a width and height of `2`,
- // then this is the resulting union:
- //
- // x1 = min(0, 5) => x1 = 0
- // y1 = min(0, 0) => y1 = 0
- // x2 = max(0 + 5, 5 + 2) => x2 = 7
- // y2 = max(0 + 5, 0 + 2) => y2 = 5
- 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,
- }
- }
-
- /// Calculate the intersection between two [`Rect`]s.
- pub fn intersection(self, other: Rect) -> Rect {
- // Example:
- //
- // If `Rect` A is positioned at `(0, 0)` with a width and height of `5`,
- // and `Rect` B is positioned at `(5, 0)` with a width and height of `2`,
- // then this is the resulting intersection:
- //
- // x1 = max(0, 5) => x1 = 5
- // y1 = max(0, 0) => y1 = 0
- // x2 = min(0 + 5, 5 + 2) => x2 = 5
- // y2 = min(0 + 5, 0 + 2) => y2 = 2
- 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,
- LightRed,
- LightGreen,
- LightYellow,
- LightBlue,
- LightMagenta,
- LightCyan,
- LightGray,
- White,
- Rgb(u8, u8, u8),
- Indexed(u8),
-}
-
-#[cfg(feature = "term")]
-impl From<Color> 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::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::LightGray => CColor::Grey,
- 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;
- }
-}
-
-impl FromStr for Modifier {
- type Err = &'static str;
-
- fn from_str(modifier: &str) -> Result<Self, Self::Err> {
- match modifier {
- "bold" => Ok(Self::BOLD),
- "dim" => Ok(Self::DIM),
- "italic" => Ok(Self::ITALIC),
- "underlined" => Ok(Self::UNDERLINED),
- "slow_blink" => Ok(Self::SLOW_BLINK),
- "rapid_blink" => Ok(Self::RAPID_BLINK),
- "reversed" => Ok(Self::REVERSED),
- "hidden" => Ok(Self::HIDDEN),
- "crossed_out" => Ok(Self::CROSSED_OUT),
- _ => Err("Invalid modifier"),
- }
- }
-}
-
-/// 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[(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[(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[(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[(0, 0)].style(),
-/// );
-/// ```
-#[derive(Debug, Clone, Copy, PartialEq)]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
-pub struct Style {
- pub fg: Option<Color>,
- pub bg: Option<Color>,
- 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);
- }
-
- #[test]
- fn test_rect_chop_from_left() {
- let rect = Rect::new(0, 0, 20, 30);
- assert_eq!(Rect::new(10, 0, 10, 30), rect.clip_left(10));
- assert_eq!(
- Rect::new(20, 0, 0, 30),
- rect.clip_left(40),
- "x should be clamped to original width if new width is bigger"
- );
- }
-
- #[test]
- fn test_rect_chop_from_right() {
- let rect = Rect::new(0, 0, 20, 30);
- assert_eq!(Rect::new(0, 0, 10, 30), rect.clip_right(10));
- }
-
- #[test]
- fn test_rect_chop_from_top() {
- let rect = Rect::new(0, 0, 20, 30);
- assert_eq!(Rect::new(0, 10, 20, 20), rect.clip_top(10));
- assert_eq!(
- Rect::new(0, 30, 20, 0),
- rect.clip_top(50),
- "y should be clamped to original height if new height is bigger"
- );
- }
-
- #[test]
- fn test_rect_chop_from_bottom() {
- let rect = Rect::new(0, 0, 20, 30);
- assert_eq!(Rect::new(0, 0, 20, 20), rect.clip_bottom(10));
- }
-
- fn styles() -> Vec<Style> {
- vec![
- Style::default(),
- Style::default().fg(Color::Yellow),
- Style::default().bg(Color::Yellow),
- Style::default().add_modifier(Modifier::BOLD),
- Style::default().remove_modifier(Modifier::BOLD),
- Style::default().add_modifier(Modifier::ITALIC),
- Style::default().remove_modifier(Modifier::ITALIC),
- Style::default().add_modifier(Modifier::ITALIC | Modifier::BOLD),
- Style::default().remove_modifier(Modifier::ITALIC | Modifier::BOLD),
- ]
- }
-
- #[test]
- fn combined_patch_gives_same_result_as_individual_patch() {
- let styles = styles();
- for &a in &styles {
- for &b in &styles {
- for &c in &styles {
- for &d in &styles {
- let combined = a.patch(b.patch(c.patch(d)));
-
- assert_eq!(
- Style::default().patch(a).patch(b).patch(c).patch(d),
- Style::default().patch(combined)
- );
- }
- }
- }
- }
- }
-}
+use bitflags::bitflags;
+use serde::{Deserialize, Serialize};
+use std::{
+ cmp::{max, min},
+ str::FromStr,
+};
+
+#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
+#[serde(rename_all = "lowercase")]
+/// UNSTABLE
+pub enum CursorKind {
+ /// █
+ Block,
+ /// |
+ Bar,
+ /// _
+ Underline,
+ /// Hidden cursor, can set cursor position with this to let IME have correct cursor position.
+ Hidden,
+}
+
+impl Default for CursorKind {
+ fn default() -> Self {
+ Self::Block
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Margin {
+ pub left: u16,
+ pub right: u16,
+ pub top: u16,
+ pub bottom: u16,
+}
+
+impl Margin {
+ pub fn none() -> Self {
+ Self {
+ left: 0,
+ right: 0,
+ top: 0,
+ bottom: 0,
+ }
+ }
+
+ /// Set uniform margin for all sides.
+ pub fn all(value: u16) -> Self {
+ Self {
+ left: value,
+ right: value,
+ top: value,
+ bottom: value,
+ }
+ }
+
+ /// Set the margin of left and right sides to specified value.
+ pub fn horizontal(value: u16) -> Self {
+ Self {
+ left: value,
+ right: value,
+ top: 0,
+ bottom: 0,
+ }
+ }
+
+ /// Set the margin of top and bottom sides to specified value.
+ pub fn vertical(value: u16) -> Self {
+ Self {
+ left: 0,
+ right: 0,
+ top: value,
+ bottom: value,
+ }
+ }
+
+ /// Get the total width of the margin (left + right)
+ pub fn width(&self) -> u16 {
+ self.left + self.right
+ }
+
+ /// Get the total height of the margin (top + bottom)
+ pub fn height(&self) -> u16 {
+ self.top + self.bottom
+ }
+}
+
+/// 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. (x, y) = (0, 0) is at the top left corner of the screen.
+#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)]
+pub struct Rect {
+ pub x: u16,
+ pub y: u16,
+ pub width: u16,
+ pub height: u16,
+}
+
+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,
+ }
+ }
+
+ #[inline]
+ pub fn area(self) -> u16 {
+ self.width * self.height
+ }
+
+ #[inline]
+ pub fn left(self) -> u16 {
+ self.x
+ }
+
+ #[inline]
+ pub fn right(self) -> u16 {
+ self.x.saturating_add(self.width)
+ }
+
+ #[inline]
+ pub fn top(self) -> u16 {
+ self.y
+ }
+
+ #[inline]
+ pub fn bottom(self) -> u16 {
+ self.y.saturating_add(self.height)
+ }
+
+ // Returns a new Rect with width reduced from the left side.
+ // This changes the `x` coordinate and clamps it to the right
+ // edge of the original Rect.
+ pub fn clip_left(self, width: u16) -> Rect {
+ let width = std::cmp::min(width, self.width);
+ Rect {
+ x: self.x.saturating_add(width),
+ width: self.width.saturating_sub(width),
+ ..self
+ }
+ }
+
+ // Returns a new Rect with width reduced from the right side.
+ // This does _not_ change the `x` coordinate.
+ pub fn clip_right(self, width: u16) -> Rect {
+ Rect {
+ width: self.width.saturating_sub(width),
+ ..self
+ }
+ }
+
+ // Returns a new Rect with height reduced from the top.
+ // This changes the `y` coordinate and clamps it to the bottom
+ // edge of the original Rect.
+ pub fn clip_top(self, height: u16) -> Rect {
+ let height = std::cmp::min(height, self.height);
+ Rect {
+ y: self.y.saturating_add(height),
+ height: self.height.saturating_sub(height),
+ ..self
+ }
+ }
+
+ // Returns a new Rect with height reduced from the bottom.
+ // This does _not_ change the `y` coordinate.
+ pub fn clip_bottom(self, height: u16) -> Rect {
+ Rect {
+ height: self.height.saturating_sub(height),
+ ..self
+ }
+ }
+
+ pub fn with_height(self, height: u16) -> Rect {
+ // new height may make area > u16::max_value, so use new()
+ Self::new(self.x, self.y, self.width, height)
+ }
+
+ pub fn with_width(self, width: u16) -> Rect {
+ Self::new(self.x, self.y, width, self.height)
+ }
+
+ pub fn inner(self, margin: &Margin) -> Rect {
+ if self.width < margin.width() || self.height < margin.height() {
+ Rect::default()
+ } else {
+ Rect {
+ x: self.x + margin.left,
+ y: self.y + margin.top,
+ width: self.width - margin.width(),
+ height: self.height - margin.height(),
+ }
+ }
+ }
+
+ /// Calculate the union between two [`Rect`]s.
+ pub fn union(self, other: Rect) -> Rect {
+ // Example:
+ //
+ // If `Rect` A is positioned at `(0, 0)` with a width and height of `5`,
+ // and `Rect` B is positioned at `(5, 0)` with a width and height of `2`,
+ // then this is the resulting union:
+ //
+ // x1 = min(0, 5) => x1 = 0
+ // y1 = min(0, 0) => y1 = 0
+ // x2 = max(0 + 5, 5 + 2) => x2 = 7
+ // y2 = max(0 + 5, 0 + 2) => y2 = 5
+ 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,
+ }
+ }
+
+ /// Calculate the intersection between two [`Rect`]s.
+ pub fn intersection(self, other: Rect) -> Rect {
+ // Example:
+ //
+ // If `Rect` A is positioned at `(0, 0)` with a width and height of `5`,
+ // and `Rect` B is positioned at `(5, 0)` with a width and height of `2`,
+ // then this is the resulting intersection:
+ //
+ // x1 = max(0, 5) => x1 = 5
+ // y1 = max(0, 0) => y1 = 0
+ // x2 = min(0 + 5, 5 + 2) => x2 = 5
+ // y2 = min(0 + 5, 0 + 2) => y2 = 2
+ 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,
+ LightRed,
+ LightGreen,
+ LightYellow,
+ LightBlue,
+ LightMagenta,
+ LightCyan,
+ LightGray,
+ White,
+ Rgb(u8, u8, u8),
+ Indexed(u8),
+}
+
+#[cfg(feature = "term")]
+impl From<Color> 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::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::LightGray => CColor::Grey,
+ 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;
+ }
+}
+
+impl FromStr for Modifier {
+ type Err = &'static str;
+
+ fn from_str(modifier: &str) -> Result<Self, Self::Err> {
+ match modifier {
+ "bold" => Ok(Self::BOLD),
+ "dim" => Ok(Self::DIM),
+ "italic" => Ok(Self::ITALIC),
+ "underlined" => Ok(Self::UNDERLINED),
+ "slow_blink" => Ok(Self::SLOW_BLINK),
+ "rapid_blink" => Ok(Self::RAPID_BLINK),
+ "reversed" => Ok(Self::REVERSED),
+ "hidden" => Ok(Self::HIDDEN),
+ "crossed_out" => Ok(Self::CROSSED_OUT),
+ _ => Err("Invalid modifier"),
+ }
+ }
+}
+
+/// 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[(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[(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[(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[(0, 0)].style(),
+/// );
+/// ```
+#[derive(Debug, Clone, Copy, PartialEq)]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+pub struct Style {
+ pub fg: Option<Color>,
+ pub bg: Option<Color>,
+ 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);
+ }
+
+ #[test]
+ fn test_rect_chop_from_left() {
+ let rect = Rect::new(0, 0, 20, 30);
+ assert_eq!(Rect::new(10, 0, 10, 30), rect.clip_left(10));
+ assert_eq!(
+ Rect::new(20, 0, 0, 30),
+ rect.clip_left(40),
+ "x should be clamped to original width if new width is bigger"
+ );
+ }
+
+ #[test]
+ fn test_rect_chop_from_right() {
+ let rect = Rect::new(0, 0, 20, 30);
+ assert_eq!(Rect::new(0, 0, 10, 30), rect.clip_right(10));
+ }
+
+ #[test]
+ fn test_rect_chop_from_top() {
+ let rect = Rect::new(0, 0, 20, 30);
+ assert_eq!(Rect::new(0, 10, 20, 20), rect.clip_top(10));
+ assert_eq!(
+ Rect::new(0, 30, 20, 0),
+ rect.clip_top(50),
+ "y should be clamped to original height if new height is bigger"
+ );
+ }
+
+ #[test]
+ fn test_rect_chop_from_bottom() {
+ let rect = Rect::new(0, 0, 20, 30);
+ assert_eq!(Rect::new(0, 0, 20, 20), rect.clip_bottom(10));
+ }
+
+ fn styles() -> Vec<Style> {
+ vec![
+ Style::default(),
+ Style::default().fg(Color::Yellow),
+ Style::default().bg(Color::Yellow),
+ Style::default().add_modifier(Modifier::BOLD),
+ Style::default().remove_modifier(Modifier::BOLD),
+ Style::default().add_modifier(Modifier::ITALIC),
+ Style::default().remove_modifier(Modifier::ITALIC),
+ Style::default().add_modifier(Modifier::ITALIC | Modifier::BOLD),
+ Style::default().remove_modifier(Modifier::ITALIC | Modifier::BOLD),
+ ]
+ }
+
+ #[test]
+ fn combined_patch_gives_same_result_as_individual_patch() {
+ let styles = styles();
+ for &a in &styles {
+ for &b in &styles {
+ for &c in &styles {
+ for &d in &styles {
+ let combined = a.patch(b.patch(c.patch(d)));
+
+ assert_eq!(
+ Style::default().patch(a).patch(b).patch(c).patch(d),
+ Style::default().patch(combined)
+ );
+ }
+ }
+ }
+ }
+ }
+}