120 lines
3.4 KiB
Rust
120 lines
3.4 KiB
Rust
|
|
use eframe::egui;
|
||
|
|
|
||
|
|
thread_local! {
|
||
|
|
static VISUAL_LINE_MAPPING_CACHE: std::cell::RefCell<Option<(String, f32, Vec<Option<usize>>)>> = std::cell::RefCell::new(None);
|
||
|
|
}
|
||
|
|
|
||
|
|
pub(super) fn get_visual_line_mapping(
|
||
|
|
ui: &egui::Ui,
|
||
|
|
content: &str,
|
||
|
|
available_width: f32,
|
||
|
|
font_size: f32,
|
||
|
|
) -> Vec<Option<usize>> {
|
||
|
|
let should_recalculate = VISUAL_LINE_MAPPING_CACHE.with(|cache| {
|
||
|
|
if let Some((cached_content, cached_width, _)) = cache.borrow().as_ref() {
|
||
|
|
content != cached_content || available_width != *cached_width
|
||
|
|
} else {
|
||
|
|
true
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
if should_recalculate {
|
||
|
|
let visual_lines = calculate_visual_line_mapping(ui, content, available_width, font_size);
|
||
|
|
VISUAL_LINE_MAPPING_CACHE.with(|cache| {
|
||
|
|
*cache.borrow_mut() = Some((content.to_owned(), available_width, visual_lines));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
VISUAL_LINE_MAPPING_CACHE.with(|cache| {
|
||
|
|
cache
|
||
|
|
.borrow()
|
||
|
|
.as_ref()
|
||
|
|
.map(|(_, _, mapping)| mapping.clone())
|
||
|
|
.unwrap_or_default()
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
fn calculate_visual_line_mapping(
|
||
|
|
ui: &egui::Ui,
|
||
|
|
content: &str,
|
||
|
|
available_width: f32,
|
||
|
|
font_size: f32,
|
||
|
|
) -> Vec<Option<usize>> {
|
||
|
|
let mut visual_lines = Vec::new();
|
||
|
|
let font_id = egui::FontId::monospace(font_size);
|
||
|
|
|
||
|
|
for (line_num, line) in content.lines().enumerate() {
|
||
|
|
if line.is_empty() {
|
||
|
|
visual_lines.push(Some(line_num + 1));
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
let galley = ui.fonts(|fonts| {
|
||
|
|
fonts.layout(
|
||
|
|
line.to_string(),
|
||
|
|
font_id.clone(),
|
||
|
|
egui::Color32::WHITE,
|
||
|
|
available_width,
|
||
|
|
)
|
||
|
|
});
|
||
|
|
|
||
|
|
let wrapped_line_count = galley.rows.len().max(1);
|
||
|
|
|
||
|
|
visual_lines.push(Some(line_num + 1));
|
||
|
|
|
||
|
|
for _ in 1..wrapped_line_count {
|
||
|
|
visual_lines.push(None);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
visual_lines
|
||
|
|
}
|
||
|
|
|
||
|
|
pub(super) fn render_line_numbers(
|
||
|
|
ui: &mut egui::Ui,
|
||
|
|
line_count: usize,
|
||
|
|
visual_line_mapping: &[Option<usize>],
|
||
|
|
line_number_width: f32,
|
||
|
|
word_wrap: bool,
|
||
|
|
font_size: f32,
|
||
|
|
) {
|
||
|
|
ui.vertical(|ui| {
|
||
|
|
ui.set_width(line_number_width);
|
||
|
|
ui.spacing_mut().item_spacing.y = 0.0;
|
||
|
|
|
||
|
|
let text_color = ui.visuals().weak_text_color();
|
||
|
|
let bg_color = ui.visuals().extreme_bg_color;
|
||
|
|
|
||
|
|
let line_numbers_rect = ui.available_rect_before_wrap();
|
||
|
|
ui.painter()
|
||
|
|
.rect_filled(line_numbers_rect, 0.0, bg_color);
|
||
|
|
|
||
|
|
let font_id = egui::FontId::monospace(font_size);
|
||
|
|
let line_count_width = line_count.to_string().len();
|
||
|
|
|
||
|
|
if word_wrap {
|
||
|
|
for line_number_opt in visual_line_mapping {
|
||
|
|
let text = if let Some(line_number) = line_number_opt {
|
||
|
|
format!("{:>width$}", line_number, width = line_count_width)
|
||
|
|
} else {
|
||
|
|
" ".repeat(line_count_width)
|
||
|
|
};
|
||
|
|
ui.label(
|
||
|
|
egui::RichText::new(text)
|
||
|
|
.font(font_id.clone())
|
||
|
|
.color(text_color),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
for i in 1..=line_count {
|
||
|
|
let text = format!("{:>width$}", i, width = line_count_width);
|
||
|
|
ui.label(
|
||
|
|
egui::RichText::new(text)
|
||
|
|
.font(font_id.clone())
|
||
|
|
.color(text_color),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|