floem/views/editor/
layout.rs1use crate::{
2 peniko::Color,
3 text::{LayoutLine, TextLayout},
4};
5use floem_editor_core::buffer::rope_text::RopeText;
6
7use super::{phantom_text::PhantomTextLine, visual_line::TextLayoutProvider};
8
9#[derive(Clone, Debug)]
10pub struct LineExtraStyle {
11 pub x: f64,
12 pub y: f64,
13 pub width: Option<f64>,
14 pub height: f64,
15 pub bg_color: Option<Color>,
16 pub under_line: Option<Color>,
17 pub wave_line: Option<Color>,
18}
19
20#[derive(Clone)]
21pub struct TextLayoutLine {
22 pub extra_style: Vec<LineExtraStyle>,
25 pub text: TextLayout,
26 pub whitespaces: Option<Vec<(char, (f64, f64))>>,
27 pub indent: f64,
28 pub phantom_text: PhantomTextLine,
29}
30
31impl TextLayoutLine {
32 pub fn line_count(&self) -> usize {
34 self.relevant_layouts().count().max(1)
35 }
36
37 pub fn relevant_layouts(&self) -> impl Iterator<Item = &'_ LayoutLine> + '_ {
40 self.text
44 .lines()
45 .iter()
46 .flat_map(|l| l.layout_opt())
47 .flat_map(|ls| ls.iter())
48 .filter(|l| !l.glyphs.is_empty())
49 }
50
51 pub fn layout_cols<'a>(
53 &'a self,
54 text_prov: impl TextLayoutProvider + 'a,
55 line: usize,
56 ) -> impl Iterator<Item = (usize, usize)> + 'a {
57 let mut prefix = None;
58 if self.text.lines().len() == 1 {
60 let line_start = self.text.lines_range()[0].start;
61 if let Some(layouts) = self.text.lines()[0].layout_opt() {
62 if !layouts.is_empty() && layouts.iter().all(|l| l.glyphs.is_empty()) {
64 prefix = Some((line_start, line_start));
66 }
67 }
68 }
69
70 let line_v = line;
71 let iter = self
72 .text
73 .lines()
74 .iter()
75 .zip(self.text.lines_range().iter())
76 .filter_map(|(line, line_range)| line.layout_opt().map(|ls| (line, line_range, ls)))
77 .flat_map(|(line, line_range, ls)| ls.iter().map(move |l| (line, line_range, l)))
78 .filter(|(_, _, l)| !l.glyphs.is_empty())
79 .map(move |(tl_line, line_range, l)| {
80 let line_start = line_range.start;
81 tl_line.align();
82
83 let start = line_start + l.glyphs[0].start;
84 let end = line_start + l.glyphs.last().unwrap().end;
85
86 let text = text_prov.rope_text();
87 let pre_end = text_prov.before_phantom_col(line_v, end);
91 let line_offset = text.offset_of_line(line);
92
93 let line_end = text.line_end_col(line, true);
95
96 let end = if pre_end <= line_end {
97 let after = text.slice_to_cow(line_offset + pre_end..line_offset + line_end);
98 if after.starts_with(' ') && !after.starts_with(" ") {
99 end + 1
100 } else {
101 end
102 }
103 } else {
104 end
105 };
106
107 (start, end)
108 });
109
110 prefix.into_iter().chain(iter)
111 }
112
113 pub fn start_layout_cols<'a>(
115 &'a self,
116 text_prov: impl TextLayoutProvider + 'a,
117 line: usize,
118 ) -> impl Iterator<Item = usize> + 'a {
119 self.layout_cols(text_prov, line).map(|(start, _)| start)
120 }
121
122 pub fn get_layout_y(&self, nth: usize) -> Option<f32> {
124 self.text.layout_runs().nth(nth).map(|run| run.line_y)
125 }
126
127 pub fn get_layout_x(&self, nth: usize) -> Option<(f32, f32)> {
129 self.text.layout_runs().nth(nth).map(|run| {
130 (
131 run.glyphs.first().map(|g| g.x).unwrap_or(0.0),
132 run.glyphs.last().map(|g| g.x + g.w).unwrap_or(0.0),
133 )
134 })
135 }
136}