mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
Start work on underlined text
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
8e5c709057
commit
9346aa300d
@ -1,6 +1,7 @@
|
||||
use gpui::{
|
||||
color::Color,
|
||||
fonts::{Properties, Weight},
|
||||
text_layout::RunStyle,
|
||||
DebugContext, Element as _, Quad,
|
||||
};
|
||||
use log::LevelFilter;
|
||||
@ -55,31 +56,39 @@ impl gpui::Element for TextElement {
|
||||
) -> Self::PaintState {
|
||||
let font_size = 12.;
|
||||
let family = cx.font_cache.load_family(&["SF Pro Display"]).unwrap();
|
||||
let normal = cx
|
||||
.font_cache
|
||||
.select_font(family, &Default::default())
|
||||
.unwrap();
|
||||
let bold = cx
|
||||
.font_cache
|
||||
.select_font(
|
||||
family,
|
||||
&Properties {
|
||||
weight: Weight::BOLD,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let normal = RunStyle {
|
||||
font_id: cx
|
||||
.font_cache
|
||||
.select_font(family, &Default::default())
|
||||
.unwrap(),
|
||||
color: Color::default(),
|
||||
underline: false,
|
||||
};
|
||||
let bold = RunStyle {
|
||||
font_id: cx
|
||||
.font_cache
|
||||
.select_font(
|
||||
family,
|
||||
&Properties {
|
||||
weight: Weight::BOLD,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
color: Color::default(),
|
||||
underline: false,
|
||||
};
|
||||
|
||||
let text = "Hello world!";
|
||||
let line = cx.text_layout_cache.layout_str(
|
||||
text,
|
||||
font_size,
|
||||
&[
|
||||
(1, normal, Color::default()),
|
||||
(1, bold, Color::default()),
|
||||
(1, normal, Color::default()),
|
||||
(1, bold, Color::default()),
|
||||
(text.len() - 4, normal, Color::default()),
|
||||
(1, normal.clone()),
|
||||
(1, bold.clone()),
|
||||
(1, normal.clone()),
|
||||
(1, bold.clone()),
|
||||
(text.len() - 4, normal.clone()),
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
use crate::{
|
||||
color::Color,
|
||||
fonts::{FontId, TextStyle},
|
||||
fonts::TextStyle,
|
||||
geometry::{
|
||||
rect::RectF,
|
||||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
json::{ToJson, Value},
|
||||
text_layout::Line,
|
||||
text_layout::{Line, RunStyle},
|
||||
DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
@ -48,10 +47,17 @@ impl Label {
|
||||
self
|
||||
}
|
||||
|
||||
fn compute_runs(&self) -> SmallVec<[(usize, FontId, Color); 8]> {
|
||||
fn compute_runs(&self) -> SmallVec<[(usize, RunStyle); 8]> {
|
||||
let font_id = self.style.text.font_id;
|
||||
if self.highlight_indices.is_empty() {
|
||||
return smallvec![(self.text.len(), font_id, self.style.text.color)];
|
||||
return smallvec![(
|
||||
self.text.len(),
|
||||
RunStyle {
|
||||
font_id,
|
||||
color: self.style.text.color,
|
||||
underline: false,
|
||||
}
|
||||
)];
|
||||
}
|
||||
|
||||
let highlight_font_id = self
|
||||
@ -62,25 +68,31 @@ impl Label {
|
||||
|
||||
let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
|
||||
let mut runs = SmallVec::new();
|
||||
let highlight_style = self
|
||||
.style
|
||||
.highlight_text
|
||||
.as_ref()
|
||||
.unwrap_or(&self.style.text);
|
||||
|
||||
for (char_ix, c) in self.text.char_indices() {
|
||||
let mut font_id = font_id;
|
||||
let mut color = self.style.text.color;
|
||||
let mut underline = self.style.text.underline;
|
||||
if let Some(highlight_ix) = highlight_indices.peek() {
|
||||
if char_ix == *highlight_ix {
|
||||
font_id = highlight_font_id;
|
||||
color = self
|
||||
.style
|
||||
.highlight_text
|
||||
.as_ref()
|
||||
.unwrap_or(&self.style.text)
|
||||
.color;
|
||||
color = highlight_style.color;
|
||||
underline = highlight_style.underline;
|
||||
highlight_indices.next();
|
||||
}
|
||||
}
|
||||
|
||||
let push_new_run = if let Some((last_len, last_font_id, last_color)) = runs.last_mut() {
|
||||
if font_id == *last_font_id && color == *last_color {
|
||||
let last_run: Option<&mut (usize, RunStyle)> = runs.last_mut();
|
||||
let push_new_run = if let Some((last_len, last_style)) = last_run {
|
||||
if font_id == last_style.font_id
|
||||
&& color == last_style.color
|
||||
&& underline == last_style.underline
|
||||
{
|
||||
*last_len += c.len_utf8();
|
||||
false
|
||||
} else {
|
||||
@ -91,7 +103,14 @@ impl Label {
|
||||
};
|
||||
|
||||
if push_new_run {
|
||||
runs.push((c.len_utf8(), font_id, color));
|
||||
runs.push((
|
||||
c.len_utf8(),
|
||||
RunStyle {
|
||||
font_id,
|
||||
color,
|
||||
underline,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,6 +196,7 @@ impl ToJson for LabelStyle {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::color::Color;
|
||||
use crate::fonts::{Properties as FontProperties, Weight};
|
||||
|
||||
#[crate::test(self)]
|
||||
@ -185,6 +205,7 @@ mod tests {
|
||||
"Menlo",
|
||||
12.,
|
||||
Default::default(),
|
||||
false,
|
||||
Color::black(),
|
||||
cx.font_cache(),
|
||||
)
|
||||
@ -193,6 +214,7 @@ mod tests {
|
||||
"Menlo",
|
||||
12.,
|
||||
*FontProperties::new().weight(Weight::BOLD),
|
||||
false,
|
||||
Color::new(255, 0, 0, 255),
|
||||
cx.font_cache(),
|
||||
)
|
||||
@ -212,21 +234,27 @@ mod tests {
|
||||
".αβγδε.ⓐⓑ".len(),
|
||||
]);
|
||||
|
||||
let default_run_style = RunStyle {
|
||||
font_id: default_style.font_id,
|
||||
color: default_style.color,
|
||||
underline: default_style.underline,
|
||||
};
|
||||
let highlight_run_style = RunStyle {
|
||||
font_id: highlight_style.font_id,
|
||||
color: highlight_style.color,
|
||||
underline: highlight_style.underline,
|
||||
};
|
||||
let runs = label.compute_runs();
|
||||
assert_eq!(
|
||||
runs.as_slice(),
|
||||
&[
|
||||
(".α".len(), default_style.font_id, default_style.color),
|
||||
("βγ".len(), highlight_style.font_id, highlight_style.color),
|
||||
("δ".len(), default_style.font_id, default_style.color),
|
||||
("ε".len(), highlight_style.font_id, highlight_style.color),
|
||||
(".ⓐ".len(), default_style.font_id, default_style.color),
|
||||
("ⓑⓒ".len(), highlight_style.font_id, highlight_style.color),
|
||||
(
|
||||
"ⓓⓔ.abcde.".len(),
|
||||
default_style.font_id,
|
||||
default_style.color
|
||||
),
|
||||
(".α".len(), default_run_style),
|
||||
("βγ".len(), highlight_run_style),
|
||||
("δ".len(), default_run_style),
|
||||
("ε".len(), highlight_run_style),
|
||||
(".ⓐ".len(), default_run_style),
|
||||
("ⓑⓒ".len(), highlight_run_style),
|
||||
("ⓓⓔ.abcde.".len(), default_run_style),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ impl Element for Text {
|
||||
let shaped_line = cx.text_layout_cache.layout_str(
|
||||
line,
|
||||
self.style.font_size,
|
||||
&[(line.len(), font_id, self.style.color)],
|
||||
&[(line.len(), self.style.to_run())],
|
||||
);
|
||||
let wrap_boundaries = wrapper
|
||||
.wrap_shaped_line(line, &shaped_line, constraint.max.x())
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::{
|
||||
color::Color,
|
||||
json::{json, ToJson},
|
||||
text_layout::RunStyle,
|
||||
FontCache,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
@ -24,12 +25,14 @@ pub struct TextStyle {
|
||||
pub font_id: FontId,
|
||||
pub font_size: f32,
|
||||
pub font_properties: Properties,
|
||||
pub underline: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct HighlightStyle {
|
||||
pub color: Color,
|
||||
pub font_properties: Properties,
|
||||
pub underline: bool,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
@ -55,9 +58,11 @@ struct TextStyleJson {
|
||||
color: Color,
|
||||
family: String,
|
||||
weight: Option<WeightJson>,
|
||||
size: f32,
|
||||
#[serde(default)]
|
||||
italic: bool,
|
||||
size: f32,
|
||||
#[serde(default)]
|
||||
underline: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -66,6 +71,8 @@ struct HighlightStyleJson {
|
||||
weight: Option<WeightJson>,
|
||||
#[serde(default)]
|
||||
italic: bool,
|
||||
#[serde(default)]
|
||||
underline: bool,
|
||||
}
|
||||
|
||||
impl TextStyle {
|
||||
@ -73,6 +80,7 @@ impl TextStyle {
|
||||
font_family_name: impl Into<Arc<str>>,
|
||||
font_size: f32,
|
||||
font_properties: Properties,
|
||||
underline: bool,
|
||||
color: Color,
|
||||
font_cache: &FontCache,
|
||||
) -> anyhow::Result<Self> {
|
||||
@ -85,9 +93,18 @@ impl TextStyle {
|
||||
font_id,
|
||||
font_size,
|
||||
font_properties,
|
||||
underline,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_run(&self) -> RunStyle {
|
||||
RunStyle {
|
||||
font_id: self.font_id,
|
||||
color: self.color,
|
||||
underline: self.underline,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_json(json: TextStyleJson) -> anyhow::Result<Self> {
|
||||
FONT_CACHE.with(|font_cache| {
|
||||
if let Some(font_cache) = font_cache.borrow().as_ref() {
|
||||
@ -96,6 +113,7 @@ impl TextStyle {
|
||||
json.family,
|
||||
json.size,
|
||||
font_properties,
|
||||
json.underline,
|
||||
json.color,
|
||||
font_cache,
|
||||
)
|
||||
@ -114,6 +132,7 @@ impl HighlightStyle {
|
||||
Self {
|
||||
color: json.color,
|
||||
font_properties,
|
||||
underline: json.underline,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -123,6 +142,7 @@ impl From<Color> for HighlightStyle {
|
||||
Self {
|
||||
color,
|
||||
font_properties: Default::default(),
|
||||
underline: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -161,6 +181,7 @@ impl<'de> Deserialize<'de> for HighlightStyle {
|
||||
Ok(Self {
|
||||
color: serde_json::from_value(json).map_err(de::Error::custom)?,
|
||||
font_properties: Properties::new(),
|
||||
underline: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -8,14 +8,13 @@ pub mod current {
|
||||
}
|
||||
|
||||
use crate::{
|
||||
color::Color,
|
||||
executor,
|
||||
fonts::{FontId, GlyphId, Metrics as FontMetrics, Properties as FontProperties},
|
||||
geometry::{
|
||||
rect::{RectF, RectI},
|
||||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
text_layout::LineLayout,
|
||||
text_layout::{LineLayout, RunStyle},
|
||||
AnyAction, ClipboardItem, Menu, Scene,
|
||||
};
|
||||
use anyhow::Result;
|
||||
@ -146,12 +145,7 @@ pub trait FontSystem: Send + Sync {
|
||||
subpixel_shift: Vector2F,
|
||||
scale_factor: f32,
|
||||
) -> Option<(RectI, Vec<u8>)>;
|
||||
fn layout_line(
|
||||
&self,
|
||||
text: &str,
|
||||
font_size: f32,
|
||||
runs: &[(usize, FontId, Color)],
|
||||
) -> LineLayout;
|
||||
fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout;
|
||||
fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec<usize>;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
use crate::{
|
||||
color::Color,
|
||||
fonts::{FontId, GlyphId, Metrics, Properties},
|
||||
geometry::{
|
||||
rect::{RectF, RectI},
|
||||
@ -7,7 +6,7 @@ use crate::{
|
||||
vector::{vec2f, vec2i, Vector2F},
|
||||
},
|
||||
platform,
|
||||
text_layout::{Glyph, LineLayout, Run},
|
||||
text_layout::{Glyph, LineLayout, Run, RunStyle},
|
||||
};
|
||||
use cocoa::appkit::{CGFloat, CGPoint};
|
||||
use core_foundation::{
|
||||
@ -87,12 +86,7 @@ impl platform::FontSystem for FontSystem {
|
||||
.rasterize_glyph(font_id, font_size, glyph_id, subpixel_shift, scale_factor)
|
||||
}
|
||||
|
||||
fn layout_line(
|
||||
&self,
|
||||
text: &str,
|
||||
font_size: f32,
|
||||
runs: &[(usize, FontId, Color)],
|
||||
) -> LineLayout {
|
||||
fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout {
|
||||
self.0.read().layout_line(text, font_size, runs)
|
||||
}
|
||||
|
||||
@ -210,12 +204,7 @@ impl FontSystemState {
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_line(
|
||||
&self,
|
||||
text: &str,
|
||||
font_size: f32,
|
||||
runs: &[(usize, FontId, Color)],
|
||||
) -> LineLayout {
|
||||
fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout {
|
||||
let font_id_attr_name = CFString::from_static_string("zed_font_id");
|
||||
|
||||
// Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
|
||||
@ -227,20 +216,20 @@ impl FontSystemState {
|
||||
let last_run: RefCell<Option<(usize, FontId)>> = Default::default();
|
||||
let font_runs = runs
|
||||
.iter()
|
||||
.filter_map(|(len, font_id, _)| {
|
||||
.filter_map(|(len, style)| {
|
||||
let mut last_run = last_run.borrow_mut();
|
||||
if let Some((last_len, last_font_id)) = last_run.as_mut() {
|
||||
if font_id == last_font_id {
|
||||
if style.font_id == *last_font_id {
|
||||
*last_len += *len;
|
||||
None
|
||||
} else {
|
||||
let result = (*last_len, *last_font_id);
|
||||
*last_len = *len;
|
||||
*last_font_id = *font_id;
|
||||
*last_font_id = style.font_id;
|
||||
Some(result)
|
||||
}
|
||||
} else {
|
||||
*last_run = Some((*len, *font_id));
|
||||
*last_run = Some((*len, style.font_id));
|
||||
None
|
||||
}
|
||||
})
|
||||
@ -415,9 +404,8 @@ extern "C" {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::MutableAppContext;
|
||||
|
||||
use super::*;
|
||||
use crate::MutableAppContext;
|
||||
use font_kit::properties::{Style, Weight};
|
||||
use platform::FontSystem as _;
|
||||
|
||||
@ -426,13 +414,25 @@ mod tests {
|
||||
// This is failing intermittently on CI and we don't have time to figure it out
|
||||
let fonts = FontSystem::new();
|
||||
let menlo = fonts.load_family("Menlo").unwrap();
|
||||
let menlo_regular = fonts.select_font(&menlo, &Properties::new()).unwrap();
|
||||
let menlo_italic = fonts
|
||||
.select_font(&menlo, &Properties::new().style(Style::Italic))
|
||||
.unwrap();
|
||||
let menlo_bold = fonts
|
||||
.select_font(&menlo, &Properties::new().weight(Weight::BOLD))
|
||||
.unwrap();
|
||||
let menlo_regular = RunStyle {
|
||||
font_id: fonts.select_font(&menlo, &Properties::new()).unwrap(),
|
||||
color: Default::default(),
|
||||
underline: false,
|
||||
};
|
||||
let menlo_italic = RunStyle {
|
||||
font_id: fonts
|
||||
.select_font(&menlo, &Properties::new().style(Style::Italic))
|
||||
.unwrap(),
|
||||
color: Default::default(),
|
||||
underline: false,
|
||||
};
|
||||
let menlo_bold = RunStyle {
|
||||
font_id: fonts
|
||||
.select_font(&menlo, &Properties::new().weight(Weight::BOLD))
|
||||
.unwrap(),
|
||||
color: Default::default(),
|
||||
underline: false,
|
||||
};
|
||||
assert_ne!(menlo_regular, menlo_italic);
|
||||
assert_ne!(menlo_regular, menlo_bold);
|
||||
assert_ne!(menlo_italic, menlo_bold);
|
||||
@ -440,18 +440,14 @@ mod tests {
|
||||
let line = fonts.layout_line(
|
||||
"hello world",
|
||||
16.0,
|
||||
&[
|
||||
(2, menlo_bold, Default::default()),
|
||||
(4, menlo_italic, Default::default()),
|
||||
(5, menlo_regular, Default::default()),
|
||||
],
|
||||
&[(2, menlo_bold), (4, menlo_italic), (5, menlo_regular)],
|
||||
);
|
||||
assert_eq!(line.runs.len(), 3);
|
||||
assert_eq!(line.runs[0].font_id, menlo_bold);
|
||||
assert_eq!(line.runs[0].font_id, menlo_bold.font_id);
|
||||
assert_eq!(line.runs[0].glyphs.len(), 2);
|
||||
assert_eq!(line.runs[1].font_id, menlo_italic);
|
||||
assert_eq!(line.runs[1].font_id, menlo_italic.font_id);
|
||||
assert_eq!(line.runs[1].glyphs.len(), 4);
|
||||
assert_eq!(line.runs[2].font_id, menlo_regular);
|
||||
assert_eq!(line.runs[2].font_id, menlo_regular.font_id);
|
||||
assert_eq!(line.runs[2].glyphs.len(), 5);
|
||||
}
|
||||
|
||||
@ -459,18 +455,26 @@ mod tests {
|
||||
fn test_glyph_offsets() -> anyhow::Result<()> {
|
||||
let fonts = FontSystem::new();
|
||||
let zapfino = fonts.load_family("Zapfino")?;
|
||||
let zapfino_regular = fonts.select_font(&zapfino, &Properties::new())?;
|
||||
let zapfino_regular = RunStyle {
|
||||
font_id: fonts.select_font(&zapfino, &Properties::new())?,
|
||||
color: Default::default(),
|
||||
underline: false,
|
||||
};
|
||||
let menlo = fonts.load_family("Menlo")?;
|
||||
let menlo_regular = fonts.select_font(&menlo, &Properties::new())?;
|
||||
let menlo_regular = RunStyle {
|
||||
font_id: fonts.select_font(&menlo, &Properties::new())?,
|
||||
color: Default::default(),
|
||||
underline: false,
|
||||
};
|
||||
|
||||
let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈";
|
||||
let line = fonts.layout_line(
|
||||
text,
|
||||
16.0,
|
||||
&[
|
||||
(9, zapfino_regular, Color::default()),
|
||||
(13, menlo_regular, Color::default()),
|
||||
(text.len() - 22, zapfino_regular, Color::default()),
|
||||
(9, zapfino_regular),
|
||||
(13, menlo_regular),
|
||||
(text.len() - 22, zapfino_regular),
|
||||
],
|
||||
);
|
||||
assert_eq!(
|
||||
@ -536,15 +540,19 @@ mod tests {
|
||||
fn test_layout_line_bom_char() {
|
||||
let fonts = FontSystem::new();
|
||||
let font_ids = fonts.load_family("Helvetica").unwrap();
|
||||
let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
|
||||
let style = RunStyle {
|
||||
font_id: fonts.select_font(&font_ids, &Default::default()).unwrap(),
|
||||
color: Default::default(),
|
||||
underline: false,
|
||||
};
|
||||
|
||||
let line = "\u{feff}";
|
||||
let layout = fonts.layout_line(line, 16., &[(line.len(), font_id, Default::default())]);
|
||||
let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
|
||||
assert_eq!(layout.len, line.len());
|
||||
assert!(layout.runs.is_empty());
|
||||
|
||||
let line = "a\u{feff}b";
|
||||
let layout = fonts.layout_line(line, 16., &[(line.len(), font_id, Default::default())]);
|
||||
let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
|
||||
assert_eq!(layout.len, line.len());
|
||||
assert_eq!(layout.runs.len(), 1);
|
||||
assert_eq!(layout.runs[0].glyphs.len(), 2);
|
||||
|
@ -24,6 +24,13 @@ pub struct TextLayoutCache {
|
||||
fonts: Arc<dyn platform::FontSystem>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RunStyle {
|
||||
pub color: Color,
|
||||
pub font_id: FontId,
|
||||
pub underline: bool,
|
||||
}
|
||||
|
||||
impl TextLayoutCache {
|
||||
pub fn new(fonts: Arc<dyn platform::FontSystem>) -> Self {
|
||||
Self {
|
||||
@ -44,7 +51,7 @@ impl TextLayoutCache {
|
||||
&'a self,
|
||||
text: &'a str,
|
||||
font_size: f32,
|
||||
runs: &'a [(usize, FontId, Color)],
|
||||
runs: &'a [(usize, RunStyle)],
|
||||
) -> Line {
|
||||
let key = &CacheKeyRef {
|
||||
text,
|
||||
@ -95,7 +102,7 @@ impl<'a> Hash for (dyn CacheKey + 'a) {
|
||||
struct CacheKeyValue {
|
||||
text: String,
|
||||
font_size: OrderedFloat<f32>,
|
||||
runs: SmallVec<[(usize, FontId, Color); 1]>,
|
||||
runs: SmallVec<[(usize, RunStyle); 1]>,
|
||||
}
|
||||
|
||||
impl CacheKey for CacheKeyValue {
|
||||
@ -120,11 +127,11 @@ impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct CacheKeyRef<'a> {
|
||||
text: &'a str,
|
||||
font_size: OrderedFloat<f32>,
|
||||
runs: &'a [(usize, FontId, Color)],
|
||||
runs: &'a [(usize, RunStyle)],
|
||||
}
|
||||
|
||||
impl<'a> CacheKey for CacheKeyRef<'a> {
|
||||
@ -133,10 +140,34 @@ impl<'a> CacheKey for CacheKeyRef<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialEq for CacheKeyRef<'a> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.text == other.text
|
||||
&& self.font_size == other.font_size
|
||||
&& self.runs.len() == other.runs.len()
|
||||
&& self.runs.iter().zip(other.runs.iter()).all(
|
||||
|((len_a, style_a), (len_b, style_b))| {
|
||||
len_a == len_b && style_a.font_id == style_b.font_id
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Hash for CacheKeyRef<'a> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.text.hash(state);
|
||||
self.font_size.hash(state);
|
||||
for (len, style_id) in self.runs {
|
||||
len.hash(state);
|
||||
style_id.font_id.hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Line {
|
||||
layout: Arc<LineLayout>,
|
||||
color_runs: SmallVec<[(u32, Color); 32]>,
|
||||
style_runs: SmallVec<[(u32, Color, bool); 32]>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
@ -163,12 +194,12 @@ pub struct Glyph {
|
||||
}
|
||||
|
||||
impl Line {
|
||||
fn new(layout: Arc<LineLayout>, runs: &[(usize, FontId, Color)]) -> Self {
|
||||
let mut color_runs = SmallVec::new();
|
||||
for (len, _, color) in runs {
|
||||
color_runs.push((*len as u32, *color));
|
||||
fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
|
||||
let mut style_runs = SmallVec::new();
|
||||
for (len, style) in runs {
|
||||
style_runs.push((*len as u32, style.color, style.underline));
|
||||
}
|
||||
Self { layout, color_runs }
|
||||
Self { layout, style_runs }
|
||||
}
|
||||
|
||||
pub fn runs(&self) -> &[Run] {
|
||||
@ -213,11 +244,12 @@ impl Line {
|
||||
cx: &mut PaintContext,
|
||||
) {
|
||||
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
|
||||
let baseline_origin = vec2f(0., padding_top + self.layout.ascent);
|
||||
let baseline_offset = vec2f(0., padding_top + self.layout.ascent);
|
||||
|
||||
let mut color_runs = self.color_runs.iter();
|
||||
let mut color_end = 0;
|
||||
let mut style_runs = self.style_runs.iter();
|
||||
let mut run_end = 0;
|
||||
let mut color = Color::black();
|
||||
let mut underline_start = None;
|
||||
|
||||
for run in &self.layout.runs {
|
||||
let max_glyph_width = cx
|
||||
@ -226,7 +258,7 @@ impl Line {
|
||||
.x();
|
||||
|
||||
for glyph in &run.glyphs {
|
||||
let glyph_origin = origin + baseline_origin + glyph.position;
|
||||
let glyph_origin = origin + baseline_offset + glyph.position;
|
||||
|
||||
if glyph_origin.x() + max_glyph_width < visible_bounds.origin().x() {
|
||||
continue;
|
||||
@ -235,12 +267,31 @@ impl Line {
|
||||
break;
|
||||
}
|
||||
|
||||
if glyph.index >= color_end {
|
||||
if let Some(next_run) = color_runs.next() {
|
||||
color_end += next_run.0 as usize;
|
||||
color = next_run.1;
|
||||
if glyph.index >= run_end {
|
||||
if let Some((run_len, run_color, run_underlined)) = style_runs.next() {
|
||||
if let Some(underline_origin) = underline_start {
|
||||
if !*run_underlined || *run_color != color {
|
||||
cx.scene.push_quad(scene::Quad {
|
||||
bounds: RectF::from_points(
|
||||
underline_origin,
|
||||
glyph_origin + vec2f(0., 1.),
|
||||
),
|
||||
background: Some(color),
|
||||
border: Default::default(),
|
||||
corner_radius: 0.,
|
||||
});
|
||||
underline_start = None;
|
||||
}
|
||||
}
|
||||
|
||||
if *run_underlined {
|
||||
underline_start.get_or_insert(glyph_origin);
|
||||
}
|
||||
|
||||
run_end += *run_len as usize;
|
||||
color = *run_color;
|
||||
} else {
|
||||
color_end = self.layout.len;
|
||||
run_end = self.layout.len;
|
||||
color = Color::black();
|
||||
}
|
||||
}
|
||||
@ -253,6 +304,16 @@ impl Line {
|
||||
color,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(underline_start) = underline_start.take() {
|
||||
let line_end = origin + baseline_offset + vec2f(self.layout.width, 0.);
|
||||
cx.scene.push_quad(scene::Quad {
|
||||
bounds: RectF::from_points(underline_start, line_end + vec2f(0., 1.)),
|
||||
background: Some(color),
|
||||
border: Default::default(),
|
||||
corner_radius: 0.,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,7 +329,7 @@ impl Line {
|
||||
let baseline_origin = vec2f(0., padding_top + self.layout.ascent);
|
||||
|
||||
let mut boundaries = boundaries.into_iter().peekable();
|
||||
let mut color_runs = self.color_runs.iter();
|
||||
let mut color_runs = self.style_runs.iter();
|
||||
let mut color_end = 0;
|
||||
let mut color = Color::black();
|
||||
|
||||
@ -519,7 +580,14 @@ impl LineWrapper {
|
||||
.layout_line(
|
||||
&c.to_string(),
|
||||
self.font_size,
|
||||
&[(1, self.font_id, Default::default())],
|
||||
&[(
|
||||
1,
|
||||
RunStyle {
|
||||
font_id: self.font_id,
|
||||
color: Default::default(),
|
||||
underline: false,
|
||||
},
|
||||
)],
|
||||
)
|
||||
.width
|
||||
}
|
||||
@ -528,10 +596,7 @@ impl LineWrapper {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
color::Color,
|
||||
fonts::{Properties, Weight},
|
||||
};
|
||||
use crate::fonts::{Properties, Weight};
|
||||
|
||||
#[crate::test(self)]
|
||||
fn test_wrap_line(cx: &mut crate::MutableAppContext) {
|
||||
@ -600,28 +665,30 @@ mod tests {
|
||||
|
||||
let family = font_cache.load_family(&["Helvetica"]).unwrap();
|
||||
let font_id = font_cache.select_font(family, &Default::default()).unwrap();
|
||||
let normal = font_cache.select_font(family, &Default::default()).unwrap();
|
||||
let bold = font_cache
|
||||
.select_font(
|
||||
family,
|
||||
&Properties {
|
||||
weight: Weight::BOLD,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let normal = RunStyle {
|
||||
font_id,
|
||||
color: Default::default(),
|
||||
underline: false,
|
||||
};
|
||||
let bold = RunStyle {
|
||||
font_id: font_cache
|
||||
.select_font(
|
||||
family,
|
||||
&Properties {
|
||||
weight: Weight::BOLD,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
color: Default::default(),
|
||||
underline: false,
|
||||
};
|
||||
|
||||
let text = "aa bbb cccc ddddd eeee";
|
||||
let line = text_layout_cache.layout_str(
|
||||
text,
|
||||
16.0,
|
||||
&[
|
||||
(4, normal, Color::default()),
|
||||
(5, bold, Color::default()),
|
||||
(6, normal, Color::default()),
|
||||
(1, bold, Color::default()),
|
||||
(7, normal, Color::default()),
|
||||
],
|
||||
&[(4, normal), (5, bold), (6, normal), (1, bold), (7, normal)],
|
||||
);
|
||||
|
||||
let mut wrapper = LineWrapper::new(font_id, 16., font_system);
|
||||
|
@ -2,7 +2,7 @@
|
||||
background = "$surface.0"
|
||||
|
||||
[workspace.tab]
|
||||
text = "$text.2"
|
||||
text = { extends = "$text.2", underline = true }
|
||||
padding = { left = 10, right = 10 }
|
||||
icon_close = "$text.0.color"
|
||||
icon_dirty = "$status.info"
|
||||
|
@ -23,7 +23,7 @@ bad = "#b7372e"
|
||||
|
||||
[syntax]
|
||||
keyword = { color = "#0086c0", weight = "bold" }
|
||||
function = "#dcdcaa"
|
||||
function = { color = "#dcdcaa", underline = true }
|
||||
string = "#cb8f77"
|
||||
type = "#4ec9b0"
|
||||
number = "#b5cea8"
|
||||
|
@ -17,10 +17,15 @@ pub use display_map::DisplayPoint;
|
||||
use display_map::*;
|
||||
pub use element::*;
|
||||
use gpui::{
|
||||
action, color::Color, font_cache::FamilyId, fonts::Properties as FontProperties,
|
||||
geometry::vector::Vector2F, keymap::Binding, text_layout, AppContext, ClipboardItem, Element,
|
||||
ElementBox, Entity, FontCache, ModelHandle, MutableAppContext, RenderContext, Task,
|
||||
TextLayoutCache, View, ViewContext, WeakViewHandle,
|
||||
action,
|
||||
color::Color,
|
||||
font_cache::FamilyId,
|
||||
fonts::Properties as FontProperties,
|
||||
geometry::vector::Vector2F,
|
||||
keymap::Binding,
|
||||
text_layout::{self, RunStyle},
|
||||
AppContext, ClipboardItem, Element, ElementBox, Entity, FontCache, ModelHandle,
|
||||
MutableAppContext, RenderContext, Task, TextLayoutCache, View, ViewContext, WeakViewHandle,
|
||||
};
|
||||
use postage::watch;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -2330,7 +2335,7 @@ impl Snapshot {
|
||||
|
||||
pub fn line_height(&self, font_cache: &FontCache) -> f32 {
|
||||
let font_id = font_cache.default_font(self.font_family);
|
||||
font_cache.line_height(font_id, self.font_size)
|
||||
font_cache.line_height(font_id, self.font_size).ceil()
|
||||
}
|
||||
|
||||
pub fn em_width(&self, font_cache: &FontCache) -> f32 {
|
||||
@ -2355,7 +2360,14 @@ impl Snapshot {
|
||||
.layout_str(
|
||||
"1".repeat(digit_count).as_str(),
|
||||
font_size,
|
||||
&[(digit_count, font_id, Color::black())],
|
||||
&[(
|
||||
digit_count,
|
||||
RunStyle {
|
||||
font_id,
|
||||
color: Color::black(),
|
||||
underline: false,
|
||||
},
|
||||
)],
|
||||
)
|
||||
.width())
|
||||
}
|
||||
@ -2392,7 +2404,14 @@ impl Snapshot {
|
||||
layouts.push(Some(layout_cache.layout_str(
|
||||
&line_number,
|
||||
self.font_size,
|
||||
&[(line_number.len(), font_id, color)],
|
||||
&[(
|
||||
line_number.len(),
|
||||
RunStyle {
|
||||
font_id,
|
||||
color,
|
||||
underline: false,
|
||||
},
|
||||
)],
|
||||
)));
|
||||
}
|
||||
}
|
||||
@ -2429,7 +2448,14 @@ impl Snapshot {
|
||||
layout_cache.layout_str(
|
||||
line,
|
||||
self.font_size,
|
||||
&[(line.len(), font_id, style.placeholder_text.color)],
|
||||
&[(
|
||||
line.len(),
|
||||
RunStyle {
|
||||
font_id,
|
||||
color: style.placeholder_text.color,
|
||||
underline: false,
|
||||
},
|
||||
)],
|
||||
)
|
||||
})
|
||||
.collect());
|
||||
@ -2485,7 +2511,14 @@ impl Snapshot {
|
||||
}
|
||||
|
||||
line.push_str(line_chunk);
|
||||
styles.push((line_chunk.len(), font_id, style.color));
|
||||
styles.push((
|
||||
line_chunk.len(),
|
||||
RunStyle {
|
||||
font_id,
|
||||
color: style.color,
|
||||
underline: style.underline,
|
||||
},
|
||||
));
|
||||
prev_font_id = font_id;
|
||||
prev_font_properties = style.font_properties;
|
||||
}
|
||||
@ -2518,8 +2551,11 @@ impl Snapshot {
|
||||
self.font_size,
|
||||
&[(
|
||||
self.display_snapshot.line_len(row) as usize,
|
||||
font_id,
|
||||
Color::black(),
|
||||
RunStyle {
|
||||
font_id,
|
||||
color: Color::black(),
|
||||
underline: false,
|
||||
},
|
||||
)],
|
||||
))
|
||||
}
|
||||
|
@ -180,10 +180,12 @@ impl Default for EditorStyle {
|
||||
text: HighlightStyle {
|
||||
color: Color::from_u32(0xff0000ff),
|
||||
font_properties: Default::default(),
|
||||
underline: false,
|
||||
},
|
||||
placeholder_text: HighlightStyle {
|
||||
color: Color::from_u32(0x00ff00ff),
|
||||
font_properties: Default::default(),
|
||||
underline: false,
|
||||
},
|
||||
background: Default::default(),
|
||||
gutter_background: Default::default(),
|
||||
|
Loading…
Reference in New Issue
Block a user