initial commit - v0.1.0
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
|
||||
|
||||
use egui::{text::LayoutJob, Color32};
|
||||
use egui::widgets::text_edit::TextEditOutput;
|
||||
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
/// CodeEditor struct which stores settings for highlighting.
|
||||
pub struct CodeEditor {
|
||||
id: String,
|
||||
numlines: bool,
|
||||
numlines_shift: isize,
|
||||
numlines_only_natural: bool,
|
||||
fontsize: f32,
|
||||
stick_to_bottom: bool,
|
||||
desired_width: f32,
|
||||
}
|
||||
|
||||
impl Hash for CodeEditor {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||
(self.fontsize as u32).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CodeEditor {
|
||||
fn default() -> CodeEditor {
|
||||
CodeEditor {
|
||||
id: String::from("Code Editor"),
|
||||
numlines: true,
|
||||
numlines_shift: 0,
|
||||
numlines_only_natural: false,
|
||||
fontsize: 10.0,
|
||||
stick_to_bottom: false,
|
||||
desired_width: f32::INFINITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CodeEditor {
|
||||
pub fn id_source(self, id_source: impl Into<String>) -> Self {
|
||||
CodeEditor {
|
||||
id: id_source.into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Use custom font size
|
||||
///
|
||||
/// **Default: 10.0**
|
||||
pub fn with_fontsize(self, fontsize: f32) -> Self {
|
||||
CodeEditor { fontsize, ..self }
|
||||
}
|
||||
|
||||
/// Use UI font size
|
||||
pub fn with_ui_fontsize(self, ui: &mut egui::Ui) -> Self {
|
||||
CodeEditor {
|
||||
fontsize: egui::TextStyle::Monospace.resolve(ui.style()).size,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Show or hide lines numbering
|
||||
///
|
||||
/// **Default: true**
|
||||
pub fn with_numlines(self, numlines: bool) -> Self {
|
||||
CodeEditor { numlines, ..self }
|
||||
}
|
||||
|
||||
/// Shift lines numbering by this value
|
||||
///
|
||||
/// **Default: 0**
|
||||
pub fn with_numlines_shift(self, numlines_shift: isize) -> Self {
|
||||
CodeEditor {
|
||||
numlines_shift,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Show lines numbering only above zero, useful for enabling numbering since nth row
|
||||
///
|
||||
/// **Default: false**
|
||||
pub fn with_numlines_only_natural(self, numlines_only_natural: bool) -> Self {
|
||||
CodeEditor {
|
||||
numlines_only_natural,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Should the containing area shrink if the content is small?
|
||||
///
|
||||
/// **Default: false**
|
||||
pub fn auto_shrink(self, shrink: bool) -> Self {
|
||||
CodeEditor {
|
||||
desired_width: if shrink { 0.0 } else { self.desired_width },
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the desired width of the code editor
|
||||
///
|
||||
/// **Default: `f32::INFINITY`**
|
||||
pub fn desired_width(self, width: f32) -> Self {
|
||||
CodeEditor {
|
||||
desired_width: width,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Stick to bottom
|
||||
/// The scroll handle will stick to the bottom position even while the content size
|
||||
/// changes dynamically. This can be useful to simulate terminal UIs or log/info scrollers.
|
||||
/// The scroll handle remains stuck until user manually changes position. Once "unstuck"
|
||||
/// it will remain focused on whatever content viewport the user left it on. If the scroll
|
||||
/// handle is dragged to the bottom it will again become stuck and remain there until manually
|
||||
/// pulled from the end position.
|
||||
///
|
||||
/// **Default: false**
|
||||
pub fn stick_to_bottom(self, stick_to_bottom: bool) -> Self {
|
||||
CodeEditor {
|
||||
stick_to_bottom,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format(&self, string: &str) -> egui::text::TextFormat {
|
||||
let font_id = egui::FontId::monospace(self.fontsize);
|
||||
let color = Color32::WHITE;
|
||||
egui::text::TextFormat::simple(font_id, color)
|
||||
}
|
||||
|
||||
fn numlines_show(&self, ui: &mut egui::Ui, text: &str) {
|
||||
let total = if text.ends_with('\n') || text.is_empty() {
|
||||
text.lines().count() + 1
|
||||
} else {
|
||||
text.lines().count()
|
||||
} as isize;
|
||||
|
||||
let max_indent = total.to_string().len().max(
|
||||
!self.numlines_only_natural as usize * self.numlines_shift.to_string().len(),
|
||||
);
|
||||
let mut counter = (1..=total)
|
||||
.map(|i| {
|
||||
let num = i + self.numlines_shift;
|
||||
if num <= 0 && self.numlines_only_natural {
|
||||
String::new()
|
||||
} else {
|
||||
let label = num.to_string();
|
||||
format!(
|
||||
"{}{label}",
|
||||
" ".repeat(max_indent.saturating_sub(label.len()))
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
let width = max_indent as f32
|
||||
* self.fontsize
|
||||
* 0.5
|
||||
* !(total + self.numlines_shift <= 0 && self.numlines_only_natural) as u8
|
||||
as f32;
|
||||
|
||||
let mut layouter = |ui: &egui::Ui, string: &str, _wrap_width: f32| {
|
||||
let layout_job = egui::text::LayoutJob::single_section(
|
||||
string.to_string(),
|
||||
egui::TextFormat::simple(
|
||||
egui::FontId::monospace(self.fontsize),
|
||||
Color32::WHITE,
|
||||
),
|
||||
);
|
||||
ui.fonts(|f| f.layout_job(layout_job))
|
||||
};
|
||||
|
||||
ui.add(
|
||||
egui::TextEdit::multiline(&mut counter)
|
||||
.id_source(format!("{}_numlines", self.id))
|
||||
.font(egui::TextStyle::Monospace)
|
||||
.interactive(false)
|
||||
.frame(false)
|
||||
.desired_width(width)
|
||||
.layouter(&mut layouter),
|
||||
);
|
||||
}
|
||||
|
||||
/// Show Code Editor
|
||||
pub fn show(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
text: &mut dyn egui::TextBuffer,
|
||||
) -> TextEditOutput {
|
||||
let mut text_edit_output: Option<TextEditOutput> = None;
|
||||
let code_editor = |ui: &mut egui::Ui| {
|
||||
ui.horizontal_top(|h| {
|
||||
if self.numlines {
|
||||
self.numlines_show(h, text.as_str());
|
||||
}
|
||||
egui::ScrollArea::horizontal()
|
||||
.hscroll(true)
|
||||
.id_salt(format!("{}_inner_scroll", self.id))
|
||||
.show(h, |ui| {
|
||||
let output = egui::TextEdit::multiline(text)
|
||||
.id_source(&self.id)
|
||||
.lock_focus(true)
|
||||
.frame(false)
|
||||
.desired_width(self.desired_width)
|
||||
.show(ui);
|
||||
text_edit_output = Some(output);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
egui::ScrollArea::vertical()
|
||||
.id_salt(format!("{}_outer_scroll", self.id))
|
||||
.stick_to_bottom(self.stick_to_bottom)
|
||||
.show(ui, code_editor);
|
||||
|
||||
text_edit_output.expect("TextEditOutput should exist at this point")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user