Files
oak-gpui/gpui/src/elements/label.rs
T
Max BrunsfeldandNathan Sobo 802f1f4e78 Get new theme structure working
* Fix precedence of extends directives
* Always group color with font properties for text theming

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2021-08-04 14:07:19 -07:00

253 lines
7.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use crate::{
color::Color,
font_cache::FamilyId,
fonts::{FontId, TextStyle},
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
json::{ToJson, Value},
text_layout::Line,
AfterLayoutContext, DebugContext, Element, Event, EventContext, FontCache, LayoutContext,
PaintContext, SizeConstraint,
};
use serde::Deserialize;
use serde_json::json;
use smallvec::{smallvec, SmallVec};
pub struct Label {
text: String,
family_id: FamilyId,
font_size: f32,
style: LabelStyle,
highlight_indices: Vec<usize>,
}
#[derive(Clone, Debug, Default, Deserialize)]
pub struct LabelStyle {
pub text: TextStyle,
pub highlight_text: Option<TextStyle>,
}
impl Label {
pub fn new(text: String, family_id: FamilyId, font_size: f32) -> Self {
Self {
text,
family_id,
font_size,
highlight_indices: Default::default(),
style: Default::default(),
}
}
pub fn with_style(mut self, style: &LabelStyle) -> Self {
self.style = style.clone();
self
}
pub fn with_default_color(mut self, color: Color) -> Self {
self.style.text.color = color;
self
}
pub fn with_highlights(mut self, indices: Vec<usize>) -> Self {
self.highlight_indices = indices;
self
}
fn compute_runs(
&self,
font_cache: &FontCache,
font_id: FontId,
) -> SmallVec<[(usize, FontId, Color); 8]> {
if self.highlight_indices.is_empty() {
return smallvec![(self.text.len(), font_id, self.style.text.color)];
}
let highlight_font_id = self
.style
.highlight_text
.as_ref()
.and_then(|style| {
font_cache
.select_font(self.family_id, &style.font_properties)
.ok()
})
.unwrap_or(font_id);
let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
let mut runs = SmallVec::new();
for (char_ix, c) in self.text.char_indices() {
let mut font_id = font_id;
let mut color = self.style.text.color;
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;
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 {
*last_len += c.len_utf8();
false
} else {
true
}
} else {
true
};
if push_new_run {
runs.push((c.len_utf8(), font_id, color));
}
}
runs
}
}
impl Element for Label {
type LayoutState = Line;
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
cx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) {
let font_id = cx
.font_cache
.select_font(self.family_id, &self.style.text.font_properties)
.unwrap();
let runs = self.compute_runs(&cx.font_cache, font_id);
let line =
cx.text_layout_cache
.layout_str(self.text.as_str(), self.font_size, runs.as_slice());
let size = vec2f(
line.width().max(constraint.min.x()).min(constraint.max.x()),
cx.font_cache.line_height(font_id, self.font_size).ceil(),
);
(size, line)
}
fn after_layout(&mut self, _: Vector2F, _: &mut Self::LayoutState, _: &mut AfterLayoutContext) {
}
fn paint(
&mut self,
bounds: RectF,
line: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> Self::PaintState {
line.paint(
bounds.origin(),
RectF::new(vec2f(0., 0.), bounds.size()),
cx,
)
}
fn dispatch_event(
&mut self,
_: &Event,
_: RectF,
_: &mut Self::LayoutState,
_: &mut Self::PaintState,
_: &mut EventContext,
) -> bool {
false
}
fn debug(
&self,
bounds: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
cx: &DebugContext,
) -> Value {
json!({
"type": "Label",
"bounds": bounds.to_json(),
"text": &self.text,
"highlight_indices": self.highlight_indices,
"font_family": cx.font_cache.family_name(self.family_id).unwrap(),
"font_size": self.font_size,
"style": self.style.to_json(),
})
}
}
impl ToJson for LabelStyle {
fn to_json(&self) -> Value {
json!({
"text": self.text.to_json(),
"highlight_text": self.highlight_text
.as_ref()
.map_or(serde_json::Value::Null, |style| style.to_json())
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fonts::{Properties as FontProperties, Weight};
#[crate::test(self)]
fn test_layout_label_with_highlights(cx: &mut crate::MutableAppContext) {
let menlo = cx.font_cache().load_family(&["Menlo"]).unwrap();
let menlo_regular = cx
.font_cache()
.select_font(menlo, &FontProperties::new())
.unwrap();
let menlo_bold = cx
.font_cache()
.select_font(menlo, FontProperties::new().weight(Weight::BOLD))
.unwrap();
let black = Color::black();
let red = Color::new(255, 0, 0, 255);
let label = Label::new(".αβγδε.ⓐⓑⓒⓓⓔ.abcde.".to_string(), menlo, 12.0)
.with_style(&LabelStyle {
text: TextStyle {
color: black,
font_properties: Default::default(),
},
highlight_text: Some(TextStyle {
color: red,
font_properties: *FontProperties::new().weight(Weight::BOLD),
}),
})
.with_highlights(vec![
".α".len(),
".αβ".len(),
".αβγδ".len(),
".αβγδε.ⓐ".len(),
".αβγδε.ⓐⓑ".len(),
]);
let runs = label.compute_runs(cx.font_cache().as_ref(), menlo_regular);
assert_eq!(
runs.as_slice(),
&[
(".α".len(), menlo_regular, black),
("βγ".len(), menlo_bold, red),
("δ".len(), menlo_regular, black),
("ε".len(), menlo_bold, red),
(".ⓐ".len(), menlo_regular, black),
("ⓑⓒ".len(), menlo_bold, red),
("ⓓⓔ.abcde.".len(), menlo_regular, black),
]
);
}
}