use std::ops::Range;
use gpui::{
App, BorderStyle, Bounds, Font, Global, Hsla, LineLayout, PaintQuad, Pixels, Point, TextRun,
Window, WrappedLineLayout, fill, outline, point, px,
};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum CaretShape {
#[default]
Bar,
Block,
Underline,
}
impl Global for CaretShape {}
pub(crate) fn caret_shape(cx: &App) -> CaretShape {
cx.try_global::<CaretShape>().copied().unwrap_or_default()
}
pub(crate) fn set_caret_shape(shape: CaretShape, cx: &mut App) {
cx.set_global(shape);
cx.refresh_windows();
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum CaretHeight {
#[default]
Line,
Text,
}
impl Global for CaretHeight {}
pub(crate) fn caret_height(cx: &App) -> CaretHeight {
cx.try_global::<CaretHeight>().copied().unwrap_or_default()
}
pub(crate) fn set_caret_height(height: CaretHeight, cx: &mut App) {
cx.set_global(height);
cx.refresh_windows();
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum InactiveCaret {
#[default]
Hollow,
Hidden,
}
impl Global for InactiveCaret {}
pub(crate) fn inactive_caret(cx: &App) -> InactiveCaret {
cx.try_global::<InactiveCaret>()
.copied()
.unwrap_or_default()
}
pub(crate) fn set_inactive_caret(inactive: InactiveCaret, cx: &mut App) {
cx.set_global(inactive);
cx.refresh_windows();
}
impl CaretShape {
pub fn shown(self, window_active: bool, inactive: InactiveCaret) -> bool {
window_active || self != Self::Block || inactive == InactiveCaret::Hollow
}
pub fn quad(
self,
mut bar: Bounds<Pixels>,
line_height: Pixels,
height: CaretHeight,
width: Pixels,
color: Hsla,
hollow: bool,
) -> PaintQuad {
if self != Self::Bar {
bar.size.width = width;
}
match self {
Self::Bar => fill(bar, color),
Self::Block => {
if height == CaretHeight::Line {
bar.origin.y -= (line_height - bar.size.height) / 2.0;
bar.size.height = line_height;
}
match hollow {
true => outline(bar, color, BorderStyle::Solid),
false => fill(bar, color),
}
}
Self::Underline => {
let thickness = px(2.0).min(bar.size.height);
bar.origin.y += bar.size.height - thickness;
bar.size.height = thickness;
fill(bar, color)
}
}
}
pub fn cuts_out(self, hollow: bool) -> bool {
self == Self::Block && !hollow
}
}
pub fn zero_width(font: Font, font_size: Pixels, window: &Window) -> Pixels {
let run = TextRun {
len: 1,
font,
color: Hsla::default(),
background_color: None,
underline: None,
strikethrough: None,
};
window
.text_system()
.shape_line("0".into(), font_size, &[run], None)
.width
}
pub fn paint_covered(
layout: &LineLayout,
range: &Range<usize>,
at: Point<Pixels>,
line_height: Pixels,
color: Hsla,
window: &mut Window,
) {
let mut glyphs = layout
.runs
.iter()
.flat_map(|run| run.glyphs.iter().map(move |glyph| (run.font_id, glyph)))
.filter(|(_, glyph)| range.contains(&glyph.index))
.peekable();
let Some(left) = glyphs.peek().map(|(_, glyph)| glyph.position.x) else {
return;
};
let baseline = (line_height - layout.ascent - layout.descent) / 2. + layout.ascent;
for (font_id, glyph) in glyphs {
let origin = point(
at.x + glyph.position.x - left,
at.y + baseline + glyph.position.y,
);
let _ = match glyph.is_emoji {
true => window.paint_emoji(origin, font_id, glyph.id, layout.font_size),
false => window.paint_glyph(origin, font_id, glyph.id, layout.font_size, color),
};
}
}
pub fn recoloured(runs: Vec<TextRun>, range: &Range<usize>, color: Hsla) -> Vec<TextRun> {
let mut out = Vec::with_capacity(runs.len() + 2);
let mut at = 0;
for run in runs {
let end = at + run.len;
for (start, stop, inside) in [
(at, end.min(range.start), false),
(at.max(range.start), end.min(range.end), true),
(at.max(range.end), end, false),
] {
if stop <= start {
continue;
}
out.push(TextRun {
len: stop - start,
color: if inside { color } else { run.color },
..run.clone()
});
}
at = end;
}
out
}
pub fn character_advance(line: &WrappedLineLayout, text: &str, offset: usize) -> Option<Pixels> {
if offset >= text.len() || !text.is_char_boundary(offset) {
return None;
}
if line
.wrap_boundaries()
.iter()
.any(|boundary| line.runs()[boundary.run_ix].glyphs[boundary.glyph_ix].index == offset)
{
return None;
}
let end = super::next_boundary(text, offset);
Some((line.unwrapped_layout.x_for_index(end) - line.unwrapped_layout.x_for_index(offset)).abs())
}