use crate::{Color, DrawCtx, Rect};
use super::super::value::RowValue;
use super::{editor_pill_rect, paint_pill_bg};
const SEGMENT_PAD: f64 = 3.0;
const GLYPH_WIDTH: f64 = 6.5;
pub(crate) fn segment_rects(editor_area: Rect, count: usize, scale: f64) -> Vec<Rect> {
if count == 0 {
return Vec::new();
}
let pill = editor_pill_rect(editor_area, scale);
let seg_w = pill.width / count as f64;
(0..count)
.map(|i| Rect::new(pill.x + seg_w * i as f64, pill.y, seg_w, pill.height))
.collect()
}
pub(crate) fn variant_at(editor_area: Rect, count: usize, x: f64, scale: f64) -> Option<usize> {
segment_rects(editor_area, count, scale)
.into_iter()
.position(|r| x >= r.x && x <= r.x + r.width)
}
pub(crate) fn paint_strip_chrome(
ctx: &mut dyn DrawCtx,
editor_area: Rect,
value: RowValue,
variants: &[std::sync::Arc<str>],
scale: f64,
) -> Vec<(Rect, bool)> {
let pill = editor_pill_rect(editor_area, scale);
paint_pill_bg(ctx, pill, scale);
if variants.is_empty() {
return Vec::new();
}
let current = value.as_short_text().unwrap_or("");
let selected = variants.iter().position(|v| v.as_ref() == current);
let visuals = ctx.visuals().clone();
let rects = segment_rects(editor_area, variants.len(), scale);
for (i, r) in rects.iter().enumerate() {
if Some(i) == selected {
ctx.set_fill_color(visuals.accent);
ctx.begin_path();
ctx.rounded_rect(
r.x + SEGMENT_PAD * 0.5 * scale,
r.y + SEGMENT_PAD * 0.5 * scale,
(r.width - SEGMENT_PAD * scale).max(0.0),
(r.height - SEGMENT_PAD * scale).max(0.0),
2.0 * scale,
);
ctx.fill();
} else if i > 0 {
ctx.set_stroke_color(visuals.window_stroke);
ctx.set_line_width(1.0);
ctx.begin_path();
ctx.move_to(r.x, r.y + 2.0 * scale);
ctx.line_to(r.x, r.y + r.height - 2.0 * scale);
ctx.stroke();
}
}
rects
.into_iter()
.enumerate()
.map(|(i, r)| (r, Some(i) == selected))
.collect()
}
pub(crate) fn segment_ink(ctx: &dyn DrawCtx, selected: bool) -> Color {
if selected {
Color::rgba(0.98, 0.98, 0.98, 1.0)
} else {
ctx.visuals().text_color
}
}
pub(crate) fn paint_segment_label(
ctx: &mut dyn DrawCtx,
r: Rect,
variant: &str,
selected: bool,
scale: f64,
) {
let label = fit_label(variant, r.width - SEGMENT_PAD * 2.0 * scale, scale);
if label.is_empty() {
return;
}
let ink = segment_ink(ctx, selected);
ctx.set_fill_color(ink);
ctx.set_font_size(11.0 * scale);
let est_w = label.chars().count() as f64 * GLYPH_WIDTH * scale;
let text_x = (r.x + (r.width - est_w) * 0.5).max(r.x + 2.0 * scale);
let text_y = r.y + r.height * 0.5 - 4.0 * scale;
ctx.fill_text(&label, text_x, text_y);
}
pub(crate) fn paint_editor(
ctx: &mut dyn DrawCtx,
editor_area: Rect,
value: RowValue,
variants: &[std::sync::Arc<str>],
scale: f64,
) {
for (i, (r, selected)) in paint_strip_chrome(ctx, editor_area, value, variants, scale)
.into_iter()
.enumerate()
{
paint_segment_label(ctx, r, &variants[i], selected, scale);
}
}
fn fit_label(variant: &str, width: f64, scale: f64) -> String {
if width <= 0.0 {
return String::new();
}
let max_chars = (width / (GLYPH_WIDTH * scale)).floor() as usize;
if max_chars == 0 {
return String::new();
}
if variant.chars().count() <= max_chars {
return variant.to_string();
}
variant.chars().take(max_chars).collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn area() -> Rect {
Rect::new(100.0, 0.0, 80.0, 22.0)
}
#[test]
fn segments_tile_the_pill_without_gaps() {
let rects = segment_rects(area(), 4, 1.0);
assert_eq!(rects.len(), 4);
let pill = editor_pill_rect(area(), 1.0);
assert!((rects[0].x - pill.x).abs() < 1e-9);
let last = rects[3];
assert!((last.x + last.width - (pill.x + pill.width)).abs() < 1e-9);
for pair in rects.windows(2) {
assert!((pair[0].x + pair[0].width - pair[1].x).abs() < 1e-9);
}
}
#[test]
fn variant_at_maps_each_quarter_to_its_index() {
let a = area();
let pill = editor_pill_rect(a, 1.0);
for i in 0..4 {
let x = pill.x + pill.width * (i as f64 + 0.5) / 4.0;
assert_eq!(variant_at(a, 4, x, 1.0), Some(i), "at x = {}", x);
}
}
#[test]
fn variant_at_outside_the_strip_is_none() {
let a = area();
let pill = editor_pill_rect(a, 1.0);
assert_eq!(variant_at(a, 4, pill.x - 5.0, 1.0), None);
assert_eq!(variant_at(a, 4, pill.x + pill.width + 5.0, 1.0), None);
assert_eq!(variant_at(a, 0, pill.x + 1.0, 1.0), None);
}
#[test]
fn fit_label_truncates_to_a_prefix() {
assert_eq!(fit_label("Combine", 100.0, 1.0), "Combine");
assert_eq!(fit_label("Subtract & Replace", 20.0, 1.0), "Sub");
assert_eq!(fit_label("Combine", 0.0, 1.0), "");
}
}