2021-03-10 04:00:51 +00:00
|
|
|
|
use crate::{
|
2021-09-06 22:36:09 +00:00
|
|
|
|
fonts::TextStyle,
|
2021-03-25 17:19:20 +00:00
|
|
|
|
geometry::{
|
|
|
|
|
rect::RectF,
|
|
|
|
|
vector::{vec2f, Vector2F},
|
|
|
|
|
},
|
2021-04-07 05:50:13 +00:00
|
|
|
|
json::{ToJson, Value},
|
2021-09-06 22:36:09 +00:00
|
|
|
|
text_layout::{Line, RunStyle},
|
2021-08-26 22:06:00 +00:00
|
|
|
|
DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
|
2021-03-10 04:00:51 +00:00
|
|
|
|
};
|
2021-08-03 19:48:58 +00:00
|
|
|
|
use serde::Deserialize;
|
|
|
|
|
use serde_json::json;
|
|
|
|
|
use smallvec::{smallvec, SmallVec};
|
2021-03-10 04:00:51 +00:00
|
|
|
|
|
|
|
|
|
pub struct Label {
|
|
|
|
|
text: String,
|
2021-08-03 18:07:03 +00:00
|
|
|
|
style: LabelStyle,
|
|
|
|
|
highlight_indices: Vec<usize>,
|
2021-03-22 02:54:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-05 11:45:19 +00:00
|
|
|
|
#[derive(Clone, Debug, Deserialize, Default)]
|
2021-08-03 18:07:03 +00:00
|
|
|
|
pub struct LabelStyle {
|
2021-08-04 21:07:19 +00:00
|
|
|
|
pub text: TextStyle,
|
|
|
|
|
pub highlight_text: Option<TextStyle>,
|
2021-03-10 04:00:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-08-26 22:06:00 +00:00
|
|
|
|
impl From<TextStyle> for LabelStyle {
|
|
|
|
|
fn from(text: TextStyle) -> Self {
|
|
|
|
|
LabelStyle {
|
|
|
|
|
text,
|
|
|
|
|
highlight_text: None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-10 04:00:51 +00:00
|
|
|
|
impl Label {
|
2021-08-26 22:06:00 +00:00
|
|
|
|
pub fn new(text: String, style: impl Into<LabelStyle>) -> Self {
|
2021-03-10 04:00:51 +00:00
|
|
|
|
Self {
|
|
|
|
|
text,
|
2021-08-03 18:07:03 +00:00
|
|
|
|
highlight_indices: Default::default(),
|
2021-08-26 22:06:00 +00:00
|
|
|
|
style: style.into(),
|
2021-03-10 04:00:51 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-03 18:07:03 +00:00
|
|
|
|
pub fn with_highlights(mut self, indices: Vec<usize>) -> Self {
|
|
|
|
|
self.highlight_indices = indices;
|
2021-03-10 04:00:51 +00:00
|
|
|
|
self
|
|
|
|
|
}
|
2021-03-22 02:54:23 +00:00
|
|
|
|
|
2021-09-06 22:36:09 +00:00
|
|
|
|
fn compute_runs(&self) -> SmallVec<[(usize, RunStyle); 8]> {
|
2021-08-26 22:06:00 +00:00
|
|
|
|
let font_id = self.style.text.font_id;
|
2021-08-03 18:07:03 +00:00
|
|
|
|
if self.highlight_indices.is_empty() {
|
2021-09-06 22:36:09 +00:00
|
|
|
|
return smallvec![(
|
|
|
|
|
self.text.len(),
|
|
|
|
|
RunStyle {
|
|
|
|
|
font_id,
|
|
|
|
|
color: self.style.text.color,
|
2021-09-07 09:03:08 +00:00
|
|
|
|
underline: self.style.text.underline,
|
2021-09-06 22:36:09 +00:00
|
|
|
|
}
|
|
|
|
|
)];
|
2021-08-03 18:07:03 +00:00
|
|
|
|
}
|
2021-05-21 20:10:38 +00:00
|
|
|
|
|
2021-08-03 19:48:58 +00:00
|
|
|
|
let highlight_font_id = self
|
|
|
|
|
.style
|
2021-08-04 21:07:19 +00:00
|
|
|
|
.highlight_text
|
|
|
|
|
.as_ref()
|
2021-08-26 22:06:00 +00:00
|
|
|
|
.map_or(font_id, |style| style.font_id);
|
2021-08-03 18:07:03 +00:00
|
|
|
|
|
|
|
|
|
let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
|
|
|
|
|
let mut runs = SmallVec::new();
|
2021-09-06 22:36:09 +00:00
|
|
|
|
let highlight_style = self
|
|
|
|
|
.style
|
|
|
|
|
.highlight_text
|
|
|
|
|
.as_ref()
|
|
|
|
|
.unwrap_or(&self.style.text);
|
2021-08-03 18:07:03 +00:00
|
|
|
|
|
|
|
|
|
for (char_ix, c) in self.text.char_indices() {
|
|
|
|
|
let mut font_id = font_id;
|
2021-08-04 21:07:19 +00:00
|
|
|
|
let mut color = self.style.text.color;
|
2021-09-06 22:36:09 +00:00
|
|
|
|
let mut underline = self.style.text.underline;
|
2021-08-03 18:07:03 +00:00
|
|
|
|
if let Some(highlight_ix) = highlight_indices.peek() {
|
|
|
|
|
if char_ix == *highlight_ix {
|
|
|
|
|
font_id = highlight_font_id;
|
2021-09-06 22:36:09 +00:00
|
|
|
|
color = highlight_style.color;
|
|
|
|
|
underline = highlight_style.underline;
|
2021-08-03 18:07:03 +00:00
|
|
|
|
highlight_indices.next();
|
2021-03-10 04:00:51 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-21 20:10:38 +00:00
|
|
|
|
|
2021-09-06 22:36:09 +00:00
|
|
|
|
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
|
|
|
|
|
{
|
2021-08-03 18:07:03 +00:00
|
|
|
|
*last_len += c.len_utf8();
|
|
|
|
|
false
|
|
|
|
|
} else {
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
true
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if push_new_run {
|
2021-09-06 22:36:09 +00:00
|
|
|
|
runs.push((
|
|
|
|
|
c.len_utf8(),
|
|
|
|
|
RunStyle {
|
|
|
|
|
font_id,
|
|
|
|
|
color,
|
|
|
|
|
underline,
|
|
|
|
|
},
|
|
|
|
|
));
|
2021-08-03 18:07:03 +00:00
|
|
|
|
}
|
2021-03-10 04:00:51 +00:00
|
|
|
|
}
|
2021-08-03 18:07:03 +00:00
|
|
|
|
|
|
|
|
|
runs
|
2021-05-20 06:07:45 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Element for Label {
|
2021-05-21 20:10:38 +00:00
|
|
|
|
type LayoutState = Line;
|
2021-05-20 06:07:45 +00:00
|
|
|
|
type PaintState = ();
|
|
|
|
|
|
|
|
|
|
fn layout(
|
|
|
|
|
&mut self,
|
|
|
|
|
constraint: SizeConstraint,
|
2021-05-28 22:25:15 +00:00
|
|
|
|
cx: &mut LayoutContext,
|
2021-05-20 06:07:45 +00:00
|
|
|
|
) -> (Vector2F, Self::LayoutState) {
|
2021-08-26 22:06:00 +00:00
|
|
|
|
let runs = self.compute_runs();
|
|
|
|
|
let line = cx.text_layout_cache.layout_str(
|
|
|
|
|
self.text.as_str(),
|
|
|
|
|
self.style.text.font_size,
|
|
|
|
|
runs.as_slice(),
|
|
|
|
|
);
|
2021-03-10 04:00:51 +00:00
|
|
|
|
|
|
|
|
|
let size = vec2f(
|
2021-09-30 20:28:45 +00:00
|
|
|
|
line.width()
|
|
|
|
|
.ceil()
|
|
|
|
|
.max(constraint.min.x())
|
|
|
|
|
.min(constraint.max.x()),
|
2021-08-26 22:06:00 +00:00
|
|
|
|
cx.font_cache
|
2021-09-21 16:13:02 +00:00
|
|
|
|
.line_height(self.style.text.font_id, self.style.text.font_size),
|
2021-03-10 04:00:51 +00:00
|
|
|
|
);
|
|
|
|
|
|
2021-05-21 20:10:38 +00:00
|
|
|
|
(size, line)
|
2021-03-10 04:00:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-22 02:54:23 +00:00
|
|
|
|
fn paint(
|
|
|
|
|
&mut self,
|
2021-03-25 17:19:20 +00:00
|
|
|
|
bounds: RectF,
|
2021-09-02 09:42:23 +00:00
|
|
|
|
visible_bounds: RectF,
|
2021-05-21 20:10:38 +00:00
|
|
|
|
line: &mut Self::LayoutState,
|
2021-05-28 22:25:15 +00:00
|
|
|
|
cx: &mut PaintContext,
|
2021-03-22 02:54:23 +00:00
|
|
|
|
) -> Self::PaintState {
|
2021-09-02 12:33:52 +00:00
|
|
|
|
line.paint(bounds.origin(), visible_bounds, bounds.size().y(), cx)
|
2021-03-10 04:00:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-22 02:54:23 +00:00
|
|
|
|
fn dispatch_event(
|
|
|
|
|
&mut self,
|
|
|
|
|
_: &Event,
|
2021-03-25 17:19:20 +00:00
|
|
|
|
_: RectF,
|
2021-03-22 02:54:23 +00:00
|
|
|
|
_: &mut Self::LayoutState,
|
|
|
|
|
_: &mut Self::PaintState,
|
|
|
|
|
_: &mut EventContext,
|
|
|
|
|
) -> bool {
|
2021-03-10 04:00:51 +00:00
|
|
|
|
false
|
|
|
|
|
}
|
2021-04-07 05:50:13 +00:00
|
|
|
|
|
|
|
|
|
fn debug(
|
|
|
|
|
&self,
|
|
|
|
|
bounds: RectF,
|
|
|
|
|
_: &Self::LayoutState,
|
|
|
|
|
_: &Self::PaintState,
|
2021-08-26 22:06:00 +00:00
|
|
|
|
_: &DebugContext,
|
2021-04-07 05:50:13 +00:00
|
|
|
|
) -> Value {
|
|
|
|
|
json!({
|
|
|
|
|
"type": "Label",
|
|
|
|
|
"bounds": bounds.to_json(),
|
2021-08-03 18:07:03 +00:00
|
|
|
|
"text": &self.text,
|
|
|
|
|
"highlight_indices": self.highlight_indices,
|
|
|
|
|
"style": self.style.to_json(),
|
2021-04-07 05:50:13 +00:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-03 18:07:03 +00:00
|
|
|
|
impl ToJson for LabelStyle {
|
2021-04-07 05:50:13 +00:00
|
|
|
|
fn to_json(&self) -> Value {
|
|
|
|
|
json!({
|
2021-08-04 21:07:19 +00:00
|
|
|
|
"text": self.text.to_json(),
|
|
|
|
|
"highlight_text": self.highlight_text
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map_or(serde_json::Value::Null, |style| style.to_json())
|
2021-04-07 05:50:13 +00:00
|
|
|
|
})
|
|
|
|
|
}
|
2021-03-10 04:00:51 +00:00
|
|
|
|
}
|
2021-05-20 06:07:45 +00:00
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
2021-09-06 22:36:09 +00:00
|
|
|
|
use crate::color::Color;
|
2021-08-04 21:07:19 +00:00
|
|
|
|
use crate::fonts::{Properties as FontProperties, Weight};
|
2021-05-20 06:07:45 +00:00
|
|
|
|
|
|
|
|
|
#[crate::test(self)]
|
2021-05-28 22:25:15 +00:00
|
|
|
|
fn test_layout_label_with_highlights(cx: &mut crate::MutableAppContext) {
|
2021-08-26 22:06:00 +00:00
|
|
|
|
let default_style = TextStyle::new(
|
|
|
|
|
"Menlo",
|
|
|
|
|
12.,
|
|
|
|
|
Default::default(),
|
2021-10-26 19:46:46 +00:00
|
|
|
|
None,
|
2021-08-26 22:06:00 +00:00
|
|
|
|
Color::black(),
|
|
|
|
|
cx.font_cache(),
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
let highlight_style = TextStyle::new(
|
|
|
|
|
"Menlo",
|
|
|
|
|
12.,
|
|
|
|
|
*FontProperties::new().weight(Weight::BOLD),
|
2021-10-26 19:46:46 +00:00
|
|
|
|
None,
|
2021-08-26 22:06:00 +00:00
|
|
|
|
Color::new(255, 0, 0, 255),
|
|
|
|
|
cx.font_cache(),
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
let label = Label::new(
|
|
|
|
|
".αβγδε.ⓐⓑⓒⓓⓔ.abcde.".to_string(),
|
|
|
|
|
LabelStyle {
|
|
|
|
|
text: default_style.clone(),
|
|
|
|
|
highlight_text: Some(highlight_style.clone()),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.with_highlights(vec![
|
|
|
|
|
".α".len(),
|
|
|
|
|
".αβ".len(),
|
|
|
|
|
".αβγδ".len(),
|
|
|
|
|
".αβγδε.ⓐ".len(),
|
|
|
|
|
".αβγδε.ⓐⓑ".len(),
|
|
|
|
|
]);
|
|
|
|
|
|
2021-09-06 22:36:09 +00:00
|
|
|
|
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,
|
|
|
|
|
};
|
2021-08-26 22:06:00 +00:00
|
|
|
|
let runs = label.compute_runs();
|
2021-05-20 08:50:49 +00:00
|
|
|
|
assert_eq!(
|
2021-05-21 20:10:38 +00:00
|
|
|
|
runs.as_slice(),
|
2021-05-20 08:50:49 +00:00
|
|
|
|
&[
|
2021-09-06 22:36:09 +00:00
|
|
|
|
(".α".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),
|
2021-05-20 08:50:49 +00:00
|
|
|
|
]
|
|
|
|
|
);
|
2021-05-20 06:07:45 +00:00
|
|
|
|
}
|
|
|
|
|
}
|