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>
This commit is contained in:
co-authored by
Nathan Sobo
parent
5761756fb4
commit
802f1f4e78
+18
-7
@@ -1,11 +1,15 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use crate::json::ToJson;
|
||||
use pathfinder_color::ColorU;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use serde::{
|
||||
de::{self, Unexpected},
|
||||
Deserialize, Deserializer,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
@@ -39,13 +43,20 @@ impl<'de> Deserialize<'de> for Color {
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let mut rgba = u32::deserialize(deserializer)?;
|
||||
|
||||
if rgba <= 0xFFFFFF {
|
||||
rgba = (rgba << 8) + 0xFF;
|
||||
let literal: Cow<str> = Deserialize::deserialize(deserializer)?;
|
||||
if let Some(digits) = literal.strip_prefix('#') {
|
||||
if let Ok(value) = u32::from_str_radix(digits, 16) {
|
||||
if digits.len() == 6 {
|
||||
return Ok(Color::from_u32((value << 8) | 0xFF));
|
||||
} else if digits.len() == 8 {
|
||||
return Ok(Color::from_u32(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self::from_u32(rgba))
|
||||
Err(de::Error::invalid_value(
|
||||
Unexpected::Str(literal.as_ref()),
|
||||
&"#RRGGBB[AA]",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+35
-28
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
color::Color,
|
||||
font_cache::FamilyId,
|
||||
fonts::{deserialize_font_properties, deserialize_option_font_properties, FontId, Properties},
|
||||
fonts::{FontId, TextStyle},
|
||||
geometry::{
|
||||
rect::RectF,
|
||||
vector::{vec2f, Vector2F},
|
||||
@@ -25,14 +25,8 @@ pub struct Label {
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
pub struct LabelStyle {
|
||||
#[serde(default = "Color::black")]
|
||||
pub color: Color,
|
||||
#[serde(default)]
|
||||
pub highlight_color: Option<Color>,
|
||||
#[serde(default, deserialize_with = "deserialize_font_properties")]
|
||||
pub font_properties: Properties,
|
||||
#[serde(default, deserialize_with = "deserialize_option_font_properties")]
|
||||
pub highlight_font_properties: Option<Properties>,
|
||||
pub text: TextStyle,
|
||||
pub highlight_text: Option<TextStyle>,
|
||||
}
|
||||
|
||||
impl Label {
|
||||
@@ -52,7 +46,7 @@ impl Label {
|
||||
}
|
||||
|
||||
pub fn with_default_color(mut self, color: Color) -> Self {
|
||||
self.style.color = color;
|
||||
self.style.text.color = color;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -67,13 +61,18 @@ impl Label {
|
||||
font_id: FontId,
|
||||
) -> SmallVec<[(usize, FontId, Color); 8]> {
|
||||
if self.highlight_indices.is_empty() {
|
||||
return smallvec![(self.text.len(), font_id, self.style.color)];
|
||||
return smallvec![(self.text.len(), font_id, self.style.text.color)];
|
||||
}
|
||||
|
||||
let highlight_font_id = self
|
||||
.style
|
||||
.highlight_font_properties
|
||||
.and_then(|properties| font_cache.select_font(self.family_id, &properties).ok())
|
||||
.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();
|
||||
@@ -81,11 +80,16 @@ impl Label {
|
||||
|
||||
for (char_ix, c) in self.text.char_indices() {
|
||||
let mut font_id = font_id;
|
||||
let mut color = self.style.color;
|
||||
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_color.unwrap_or(self.style.color);
|
||||
color = self
|
||||
.style
|
||||
.highlight_text
|
||||
.as_ref()
|
||||
.unwrap_or(&self.style.text)
|
||||
.color;
|
||||
highlight_indices.next();
|
||||
}
|
||||
}
|
||||
@@ -121,7 +125,7 @@ impl Element for Label {
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let font_id = cx
|
||||
.font_cache
|
||||
.select_font(self.family_id, &self.style.font_properties)
|
||||
.select_font(self.family_id, &self.style.text.font_properties)
|
||||
.unwrap();
|
||||
let runs = self.compute_runs(&cx.font_cache, font_id);
|
||||
let line =
|
||||
@@ -185,40 +189,43 @@ impl Element for Label {
|
||||
impl ToJson for LabelStyle {
|
||||
fn to_json(&self) -> Value {
|
||||
json!({
|
||||
"default_color": self.color.to_json(),
|
||||
"default_font_properties": self.font_properties.to_json(),
|
||||
"highlight_color": self.highlight_color.to_json(),
|
||||
"highlight_font_properties": self.highlight_font_properties.to_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 font_kit::properties::Weight;
|
||||
|
||||
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, &Properties::new())
|
||||
.select_font(menlo, &FontProperties::new())
|
||||
.unwrap();
|
||||
let menlo_bold = cx
|
||||
.font_cache()
|
||||
.select_font(menlo, Properties::new().weight(Weight::BOLD))
|
||||
.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 {
|
||||
color: black,
|
||||
highlight_color: Some(red),
|
||||
highlight_font_properties: Some(*Properties::new().weight(Weight::BOLD)),
|
||||
..Default::default()
|
||||
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(),
|
||||
|
||||
+62
-34
@@ -1,15 +1,25 @@
|
||||
use crate::json::{json, ToJson};
|
||||
use crate::{
|
||||
color::Color,
|
||||
json::{json, ToJson},
|
||||
};
|
||||
pub use font_kit::{
|
||||
metrics::Metrics,
|
||||
properties::{Properties, Stretch, Style, Weight},
|
||||
};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use serde::{de, Deserialize};
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct FontId(pub usize);
|
||||
|
||||
pub type GlyphId = u32;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct TextStyle {
|
||||
pub color: Color,
|
||||
pub font_properties: Properties,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Deserialize)]
|
||||
enum WeightJson {
|
||||
@@ -25,16 +35,53 @@ enum WeightJson {
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PropertiesJson {
|
||||
struct TextStyleJson {
|
||||
color: Color,
|
||||
weight: Option<WeightJson>,
|
||||
#[serde(default)]
|
||||
italic: bool,
|
||||
}
|
||||
|
||||
impl Into<Properties> for PropertiesJson {
|
||||
fn into(self) -> Properties {
|
||||
let mut result = Properties::new();
|
||||
result.weight = match self.weight.unwrap_or(WeightJson::normal) {
|
||||
impl<'de> Deserialize<'de> for TextStyle {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let json = Value::deserialize(deserializer)?;
|
||||
if json.is_object() {
|
||||
let style_json: TextStyleJson =
|
||||
serde_json::from_value(json).map_err(de::Error::custom)?;
|
||||
Ok(style_json.into())
|
||||
} else {
|
||||
Ok(Self {
|
||||
color: serde_json::from_value(json).map_err(de::Error::custom)?,
|
||||
font_properties: Properties::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for TextStyle {
|
||||
fn from(color: Color) -> Self {
|
||||
Self {
|
||||
color,
|
||||
font_properties: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToJson for TextStyle {
|
||||
fn to_json(&self) -> Value {
|
||||
json!({
|
||||
"color": self.color.to_json(),
|
||||
"font_properties": self.font_properties.to_json(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<TextStyle> for TextStyleJson {
|
||||
fn into(self) -> TextStyle {
|
||||
let weight = match self.weight.unwrap_or(WeightJson::normal) {
|
||||
WeightJson::thin => Weight::THIN,
|
||||
WeightJson::extra_light => Weight::EXTRA_LIGHT,
|
||||
WeightJson::light => Weight::LIGHT,
|
||||
@@ -45,37 +92,18 @@ impl Into<Properties> for PropertiesJson {
|
||||
WeightJson::extra_bold => Weight::EXTRA_BOLD,
|
||||
WeightJson::black => Weight::BLACK,
|
||||
};
|
||||
if self.italic {
|
||||
result.style = Style::Italic;
|
||||
let style = if self.italic {
|
||||
Style::Italic
|
||||
} else {
|
||||
Style::Normal
|
||||
};
|
||||
TextStyle {
|
||||
color: self.color,
|
||||
font_properties: *Properties::new().weight(weight).style(style),
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize_option_font_properties<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<Properties>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let json: Option<PropertiesJson> = Deserialize::deserialize(deserializer)?;
|
||||
Ok(json.map(Into::into))
|
||||
}
|
||||
|
||||
pub fn deserialize_font_properties<'de, D>(deserializer: D) -> Result<Properties, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let json: PropertiesJson = Deserialize::deserialize(deserializer)?;
|
||||
Ok(json.into())
|
||||
}
|
||||
|
||||
pub fn font_properties_from_json(
|
||||
value: serde_json::Value,
|
||||
) -> Result<Properties, serde_json::Error> {
|
||||
Ok(serde_json::from_value::<PropertiesJson>(value)?.into())
|
||||
}
|
||||
|
||||
impl ToJson for Properties {
|
||||
fn to_json(&self) -> crate::json::Value {
|
||||
json!({
|
||||
|
||||
Reference in New Issue
Block a user