use egui::{Color32, Pos2, Response, Sense, Stroke, vec2};
use super::super::{BRAND, INK_WEAK, RADIUS, RAISED, ROW, RULE, space};
pub fn row(
ui: &mut egui::Ui,
words: &str,
ink: Color32,
state: Option<Color32>,
chosen: bool,
indent: f32,
) -> Response {
marked(ui, ("", ink), words, ink, state, chosen, indent)
}
pub fn row_width(ui: &egui::Ui, words: &str) -> f32 {
let font = egui::TextStyle::Body.resolve(ui.style());
let said = ui.fonts(|fonts| {
fonts
.layout_no_wrap(words.to_owned(), font, Color32::PLACEHOLDER)
.size()
.x
});
RULE + space::M + said + space::S
}
pub fn marked(
ui: &mut egui::Ui,
mark: (&str, Color32),
words: &str,
ink: Color32,
state: Option<Color32>,
chosen: bool,
indent: f32,
) -> Response {
Row {
mark: (mark.0.to_owned(), mark.1),
words: words.to_owned(),
ink,
state,
chosen,
indent,
aside: None,
beneath: Vec::new(),
}
.paint(ui)
}
pub struct Row {
pub mark: (String, Color32),
pub words: String,
pub ink: Color32,
pub state: Option<Color32>,
pub chosen: bool,
pub indent: f32,
pub aside: Option<String>,
pub beneath: Vec<(String, Color32, egui::TextStyle)>,
}
impl Row {
pub fn paint(&self, ui: &mut egui::Ui) -> Response {
let said = [self.mark.0.as_str(), self.words.as_str()]
.into_iter()
.filter(|part| !part.is_empty())
.collect::<Vec<_>>()
.join(" ");
let height = ROW
+ self
.beneath
.iter()
.map(|(_, _, s)| tall(ui, s))
.sum::<f32>();
let (rect, seat) =
ui.allocate_exact_size(vec2(ui.available_width(), height), Sense::click());
seat.widget_info(|| {
egui::WidgetInfo::selected(
egui::WidgetType::SelectableLabel,
ui.is_enabled(),
self.chosen,
&said,
)
});
if !ui.is_rect_visible(rect) {
return seat;
}
let fill = if self.chosen {
RAISED
} else if seat.hovered() {
super::super::hover()
} else {
Color32::TRANSPARENT
};
ui.painter().rect_filled(rect, RADIUS, fill);
if let Some(accent) = self.chosen.then_some(BRAND).or(self.state) {
super::rule_at(ui, rect.left_top(), rect.height(), accent);
}
if seat.has_focus() {
ui.painter()
.rect_stroke(rect, RADIUS, Stroke::new(1.0, BRAND));
}
let left = rect.min.x + RULE + self.indent + space::M;
let mut right = rect.max.x - space::S;
let middle = rect.min.y + ROW / 2.0;
if let Some(aside) = &self.aside {
let small = egui::TextStyle::Small;
let galley = elided(ui, aside, 0, (INK_WEAK, INK_WEAK), &small, f32::INFINITY);
right -= galley.size().x;
let at = Pos2::new(right, middle - galley.size().y / 2.0);
ui.painter().galley(at, galley, INK_WEAK);
right -= space::S;
}
let lead = said.len().saturating_sub(self.words.len());
let body = egui::TextStyle::Body;
let inks = (self.mark.1, self.ink);
let galley = elided(ui, &said, lead, inks, &body, right - left);
let at = Pos2::new(left, middle - galley.size().y / 2.0);
ui.painter().galley(at, galley, self.ink);
let mut top = rect.min.y + ROW;
for (line, ink, style) in &self.beneath {
let galley = elided(
ui,
line,
0,
(*ink, *ink),
style,
rect.max.x - space::S - left,
);
ui.painter().galley(Pos2::new(left, top), galley, *ink);
top += tall(ui, style);
}
seat
}
}
fn tall(ui: &egui::Ui, style: &egui::TextStyle) -> f32 {
let font = style.resolve(ui.style());
ui.fonts(|fonts| fonts.row_height(&font)) + space::XXS
}
fn elided(
ui: &egui::Ui,
said: &str,
lead: usize,
(marked, ink): (Color32, Color32),
style: &egui::TextStyle,
width: f32,
) -> std::sync::Arc<egui::Galley> {
let font = style.resolve(ui.style());
let mut job = egui::text::LayoutJob::simple_singleline(said.to_owned(), font, ink);
if let Some(first) = job.sections.first_mut() {
first.byte_range = lead..said.len();
let mut mark = first.clone();
mark.byte_range = 0..lead;
mark.format.color = marked;
job.sections.insert(0, mark);
}
job.wrap = egui::text::TextWrapping::truncate_at_width(width.max(0.0));
ui.fonts(|fonts| fonts.layout_job(job))
}