1use std::rc::Rc;
5
6use kurbo::{Affine, BezPath, Point, Vec2};
7use oxframe_core::{Color, Lerp};
8
9use crate::theme::IntoPaint;
10use oxframe_text::{Lang, TextLayout, TextSpan, TextStyle, Token};
11
12#[derive(Debug, Clone, PartialEq, Default)]
14pub struct RichSpan {
15 pub text: String,
17 pub color: Option<Color>,
19 pub weight: Option<f64>,
21 pub italic: Option<bool>,
23 pub size: Option<f64>,
25 pub family: Option<String>,
27}
28
29impl RichSpan {
30 pub fn new(text: impl Into<String>) -> Self {
32 Self {
33 text: text.into(),
34 ..Default::default()
35 }
36 }
37 pub fn color(mut self, c: impl IntoPaint) -> Self {
39 self.color = Some(c.into_paint());
40 self
41 }
42 pub fn weight(mut self, w: f64) -> Self {
44 self.weight = Some(w);
45 self
46 }
47 pub fn bold(self) -> Self {
49 self.weight(700.0)
50 }
51 pub fn italic(mut self) -> Self {
53 self.italic = Some(true);
54 self
55 }
56 pub fn size(mut self, s: f64) -> Self {
58 self.size = Some(s);
59 self
60 }
61 pub fn family(mut self, f: impl Into<String>) -> Self {
63 self.family = Some(f.into());
64 self
65 }
66}
67
68pub fn span(text: impl Into<String>) -> RichSpan {
70 RichSpan::new(text)
71}
72
73impl Lerp for RichSpan {
76 fn lerp(&self, to: &Self, t: f64) -> Self {
77 if t < 0.5 { self.clone() } else { to.clone() }
78 }
79}
80
81#[derive(Debug, Clone, PartialEq)]
83pub struct CodeTheme {
84 pub plain: Color,
86 pub keyword: Color,
88 pub types: Color,
90 pub function: Color,
92 pub macros: Color,
94 pub string: Color,
96 pub number: Color,
98 pub comment: Color,
100 pub attribute: Color,
102 pub punct: Color,
104}
105
106impl CodeTheme {
107 pub fn dark() -> Self {
109 let h = |s| Color::from_hex(s).expect("valid color");
110 Self {
111 plain: h("#abb2bf"),
112 keyword: h("#c678dd"),
113 types: h("#e5c07b"),
114 function: h("#61afef"),
115 macros: h("#56b6c2"),
116 string: h("#98c379"),
117 number: h("#d19a66"),
118 comment: h("#7f848e"),
119 attribute: h("#e06c75"),
120 punct: h("#abb2bf"),
121 }
122 }
123
124 pub fn light() -> Self {
126 let h = |s| Color::from_hex(s).expect("valid color");
127 Self {
128 plain: h("#383a42"),
129 keyword: h("#a626a4"),
130 types: h("#c18401"),
131 function: h("#4078f2"),
132 macros: h("#0184bc"),
133 string: h("#50a14f"),
134 number: h("#986801"),
135 comment: h("#6b6f78"),
136 attribute: h("#e45649"),
137 punct: h("#383a42"),
138 }
139 }
140
141 pub fn color(&self, t: Token) -> Color {
143 match t {
144 Token::Plain => self.plain,
145 Token::Keyword => self.keyword,
146 Token::Type => self.types,
147 Token::Function => self.function,
148 Token::Macro => self.macros,
149 Token::String => self.string,
150 Token::Number => self.number,
151 Token::Comment => self.comment,
152 Token::Attribute => self.attribute,
153 Token::Punct => self.punct,
154 }
155 }
156}
157
158impl Default for CodeTheme {
159 fn default() -> Self {
160 Self::dark()
161 }
162}
163
164pub(crate) struct Styled {
167 pub layout: Rc<TextLayout>,
168 pub colors: Vec<Option<Color>>,
169}
170
171pub(crate) fn rich(spans: &[RichSpan], style: &TextStyle) -> Styled {
172 let ts: Vec<TextSpan> = spans
173 .iter()
174 .map(|s| TextSpan {
175 text: s.text.clone(),
176 family: s.family.clone(),
177 size: s.size,
178 weight: s.weight,
179 italic: s.italic,
180 })
181 .collect();
182 Styled {
183 layout: oxframe_text::layout_rich(&ts, style),
184 colors: spans.iter().map(|s| s.color).collect(),
185 }
186}
187
188pub(crate) fn code(
191 src: &str,
192 lang: Lang,
193 theme: &CodeTheme,
194 style: &TextStyle,
195 focus: Vec2,
196 dim: f64,
197) -> Styled {
198 let (a, b) = (focus.x.round(), focus.y.round());
199 let focused = |line: usize| a <= 0.0 && b <= 0.0 || (line as f64 >= a && line as f64 <= b);
200 let mut spans = Vec::new();
201 for (range, tok) in oxframe_text::highlight(src, lang) {
202 let mut start = range.start;
204 for (i, ch) in src[range.clone()].char_indices() {
205 if ch == '\n' {
206 spans.push((start..range.start + i + 1, tok));
207 start = range.start + i + 1;
208 }
209 }
210 if start < range.end {
211 spans.push((start..range.end, tok));
212 }
213 }
214 let mut out = Vec::with_capacity(spans.len());
215 for (r, tok) in spans {
216 let line = src[..r.start].matches('\n').count() + 1;
217 let mut c = theme.color(tok);
218 if !focused(line) {
219 c = c.with_alpha((f64::from(c.a) * dim.clamp(0.0, 1.0)) as f32);
220 }
221 out.push(RichSpan {
222 text: src[r].to_owned(),
223 color: Some(c),
224 ..Default::default()
225 });
226 }
227 rich(&out, style)
228}
229
230pub(crate) fn morph(
232 from: &TextLayout,
233 to: &TextLayout,
234 t: f64,
235) -> (Vec<(BezPath, f64)>, (f64, f64)) {
236 let a: Vec<char> = from.info.iter().map(|g| g.ch).collect();
237 let b: Vec<char> = to.info.iter().map(|g| g.ch).collect();
238 let (n, m) = (a.len(), b.len());
240 let mut dp = vec![vec![0u16; m + 1]; n + 1];
241 for i in (0..n).rev() {
242 for j in (0..m).rev() {
243 dp[i][j] = if a[i] == b[j] {
244 dp[i + 1][j + 1] + 1
245 } else {
246 dp[i + 1][j].max(dp[i][j + 1])
247 };
248 }
249 }
250 let mut pairs = Vec::new();
251 let (mut i, mut j) = (0, 0);
252 while i < n && j < m {
253 if a[i] == b[j] {
254 pairs.push((i, j));
255 i += 1;
256 j += 1;
257 } else if dp[i + 1][j] >= dp[i][j + 1] {
258 i += 1;
259 } else {
260 j += 1;
261 }
262 }
263 let off_from = Vec2::new(-from.width / 2.0, -from.height / 2.0);
264 let off_to = Vec2::new(-to.width / 2.0, -to.height / 2.0);
265 let mut out = Vec::new();
266 let mut matched_a = vec![false; n];
267 let mut matched_b = vec![false; m];
268 for &(i, j) in &pairs {
269 matched_a[i] = true;
270 matched_b[j] = true;
271 let pa = from.info[i].origin + off_from;
272 let pb = to.info[j].origin + off_to;
273 let shift = Lerp::lerp(&Point::ZERO, &(pb - pa).to_point(), t).to_vec2() + off_from;
274 out.push((Affine::translate(shift) * from.glyphs[i].clone(), 1.0));
275 }
276 for (i, g) in from.glyphs.iter().enumerate() {
277 if !matched_a[i] {
278 out.push((Affine::translate(off_from) * g.clone(), 1.0 - t));
279 }
280 }
281 for (j, g) in to.glyphs.iter().enumerate() {
282 if !matched_b[j] {
283 out.push((Affine::translate(off_to) * g.clone(), t));
284 }
285 }
286 let size = (
287 from.width.lerp(&to.width, t),
288 from.height.lerp(&to.height, t),
289 );
290 (out, size)
291}