use windows::Win32::Graphics::Direct2D::ID2D1RenderTarget;
use super::text::{fill_rect, pop_clip, push_clip, DWrite};
use crate::event::Rect;
use crate::primitives::layout_metrics::pixel_minimap_layout_scaled;
use crate::primitives::minimap::{
color_at_column, minimap_font_px, render_mode, truncate_to_columns, Minimap, MinimapLayout,
MinimapRenderMode, MinimapScale, MinimapSpan, SpanCursor, VisibleMinimapLine, COLUMN_CAPACITY,
};
use crate::theme::Theme;
pub fn win_minimap_layout(minimap: &Minimap, rect: Rect) -> MinimapLayout {
win_minimap_layout_scaled(minimap, rect, MinimapScale::One)
}
pub(crate) fn win_minimap_layout_scaled(
minimap: &Minimap,
rect: Rect,
scale: MinimapScale,
) -> MinimapLayout {
pixel_minimap_layout_scaled(minimap, rect, scale)
}
pub fn draw_minimap(
target: &ID2D1RenderTarget,
dwrite: &DWrite,
rect: Rect,
minimap: &Minimap,
theme: &Theme,
) -> MinimapLayout {
draw_minimap_scaled(target, dwrite, rect, minimap, theme, MinimapScale::One)
}
pub(crate) fn draw_minimap_scaled(
target: &ID2D1RenderTarget,
dwrite: &DWrite,
rect: Rect,
minimap: &Minimap,
theme: &Theme,
scale: MinimapScale,
) -> MinimapLayout {
let layout = win_minimap_layout_scaled(minimap, rect, scale);
if layout.visible_lines.is_empty() {
return layout;
}
let _ = fill_rect(target, rect, theme.background);
let hl = &layout.viewport_highlight;
if hl.height > 0.0 {
let tint = theme.background.blend(theme.accent_bg, 0.25);
let _ = fill_rect(target, *hl, tint);
}
let row_px = layout
.visible_lines
.first()
.map(|v| v.bounds.height as f64)
.unwrap_or(0.0);
let mode = render_mode(row_px);
push_clip(target, rect);
let mut spans = SpanCursor::new(&minimap.syntax_spans);
for vline in &layout.visible_lines {
let Some(line) = minimap.lines.get(vline.start_line_idx) else {
continue;
};
let row_spans = spans.row_spans(vline.start_line_idx);
match mode {
MinimapRenderMode::Characters => paint_row_glyphs(
target,
dwrite,
vline,
&line.text,
row_spans,
theme,
scale.cell_w_px() as f32,
),
MinimapRenderMode::ColumnBlocks => paint_row_blocks(
target,
vline,
&line.text,
row_spans,
theme,
scale.cell_w_px() as f32,
),
}
}
pop_clip(target);
layout
}
fn paint_row_glyphs(
target: &ID2D1RenderTarget,
dwrite: &DWrite,
vline: &VisibleMinimapLine,
text: &str,
row_spans: &[MinimapSpan],
theme: &Theme,
cell_w: f32,
) {
let row_px = vline.bounds.height as f64;
if !crate::primitives::minimap::is_legible(row_px) {
paint_row_blocks(target, vline, text, row_spans, theme, cell_w);
return;
}
let font_px = minimap_font_px(row_px);
let truncated = truncate_to_columns(text, COLUMN_CAPACITY);
let fg = row_spans
.first()
.map(|s| s.color)
.unwrap_or(theme.foreground);
let _ = dwrite.draw_text_minimap(target, truncated, vline.bounds, fg, font_px);
}
fn paint_row_blocks(
target: &ID2D1RenderTarget,
vline: &VisibleMinimapLine,
text: &str,
row_spans: &[MinimapSpan],
theme: &Theme,
cell_w: f32,
) {
for (col, ch) in text.chars().enumerate().take(COLUMN_CAPACITY) {
if ch.is_whitespace() {
continue;
}
let color = color_at_column(row_spans, col, theme.foreground);
let block = Rect::new(
vline.bounds.x + col as f32 * cell_w,
vline.bounds.y,
cell_w,
vline.bounds.height,
);
let _ = fill_rect(target, block, color);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::minimap::{MinimapHit, MinimapLine, ROW_PITCH_PX};
use crate::types::{Color, WidgetId};
use crate::win::msg::pt_to_dip;
use crate::win::testing::HeadlessSurface;
const W: f32 = 200.0;
const H: f32 = 200.0;
fn minimap_from(lines: Vec<&str>, total_buffer_lines: usize) -> Minimap {
Minimap {
id: WidgetId::new("mm"),
lines: lines
.into_iter()
.enumerate()
.map(|(i, t)| MinimapLine {
text: t.into(),
line_idx: i,
})
.collect(),
syntax_spans: Vec::new(),
visible_row_start: 0,
visible_row_count: 0,
total_buffer_lines,
}
}
#[test]
fn draw_minimap_paints_column_blocks_and_returns_layout() {
let surface = HeadlessSurface::new(W as u32, H as u32).expect("create surface");
let (dwrite, _, _) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
let theme = Theme {
background: Color::rgb(255, 255, 255),
foreground: Color::rgb(0, 0, 0),
..Theme::default()
};
let mm = minimap_from(vec!["fn main() {}"; 8], 8);
let rect = Rect::new(0.0, 0.0, W, H);
let layout = surface
.paint(|target| {
draw_minimap(target, &dwrite, rect, &mm, &theme);
})
.map(|_| win_minimap_layout(&mm, rect))
.expect("paint minimap");
assert!(!layout.visible_lines.is_empty());
let mut painted_any = false;
for x in 0..W as u32 {
for y in 0..(ROW_PITCH_PX as u32 * 8).max(8) {
let px = surface.pixel_at(x, y);
if (px.r, px.g, px.b) != (255, 255, 255) {
painted_any = true;
}
}
}
assert!(painted_any, "expected draw_minimap to paint visible blocks");
}
#[test]
fn paint_and_click_round_trip_at_nonzero_origin() {
let origin_x = 7.0_f32;
let origin_y = 13.0_f32;
let surface = HeadlessSurface::new(W as u32, H as u32).expect("create surface");
let (dwrite, _, _) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
let theme = Theme::default();
let mm = minimap_from(vec!["x"; 8], 8);
let rect = Rect::new(origin_x, origin_y, 40.0, 100.0);
let layout = surface
.paint(|target| {
draw_minimap(target, &dwrite, rect, &mm, &theme);
})
.map(|_| win_minimap_layout(&mm, rect))
.expect("paint minimap");
assert_eq!(
layout.hit_test(origin_x + 20.0, origin_y + 50.0),
MinimapHit::Seek { fraction: 0.5 }
);
assert_eq!(
layout.hit_test(origin_x + 20.0, origin_y),
MinimapHit::Seek { fraction: 0.0 }
);
}
#[test]
fn no_paint_layout_matches_paint_layout() {
let mm = minimap_from(vec!["fn main() {}"; 8], 8);
let rect = Rect::new(0.0, 0.0, W, H);
let (dwrite, _, _) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
let surface = HeadlessSurface::new(W as u32, H as u32).expect("create surface");
let painted = surface
.paint(|target| {
draw_minimap(target, &dwrite, rect, &mm, &Theme::default());
})
.map(|_| win_minimap_layout(&mm, rect))
.expect("paint");
let no_paint = win_minimap_layout(&mm, rect);
assert_eq!(painted, no_paint);
}
#[test]
fn zero_size_rect_is_a_no_op() {
let surface = HeadlessSurface::new(W as u32, H as u32).expect("create surface");
let (dwrite, _, _) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
let theme = Theme {
background: Color::rgb(255, 255, 255),
..Theme::default()
};
let mm = minimap_from(vec!["fn main() {}"; 8], 8);
let rect = Rect::new(0.0, 0.0, 0.0, H);
surface
.fill_rect(Rect::new(0.0, 0.0, W, H), Color::rgb(255, 255, 255))
.expect("fill background");
surface
.paint(|target| {
draw_minimap(target, &dwrite, rect, &mm, &theme);
})
.expect("paint minimap");
let px = surface.pixel_at(1, 1);
assert_eq!(
(px.r, px.g, px.b),
(255, 255, 255),
"a zero-width minimap should paint nothing at all",
);
}
#[test]
fn row_pitch_is_independent_of_strip_height_and_file_length() {
let short = minimap_from(vec!["fn main() {}"; 3], 3);
let long_lines: Vec<String> = (0..10_000).map(|i| format!("line {i}")).collect();
let long_refs: Vec<&str> = long_lines.iter().map(String::as_str).collect();
let long = minimap_from(long_refs, 10_000);
let tall = win_minimap_layout(&short, Rect::new(0.0, 0.0, 40.0, 800.0));
let short_strip = win_minimap_layout(&short, Rect::new(0.0, 0.0, 40.0, 16.0));
let long_layout = win_minimap_layout(&long, Rect::new(0.0, 0.0, 40.0, 400.0));
assert_eq!(tall.visible_lines[0].bounds.height as f64, ROW_PITCH_PX);
assert_eq!(
short_strip.visible_lines[0].bounds.height as f64,
ROW_PITCH_PX
);
assert_eq!(
long_layout.visible_lines[0].bounds.height as f64,
ROW_PITCH_PX
);
}
#[test]
fn characters_branch_paints_a_truncated_line_directly() {
let surface = HeadlessSurface::new(W as u32, H as u32).expect("create surface");
let (dwrite, _, _) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
let theme = Theme {
background: Color::rgb(255, 255, 255),
foreground: Color::rgb(0, 0, 0),
..Theme::default()
};
let long_line = "y".repeat(10_000);
let vline = VisibleMinimapLine {
start_line_idx: 0,
bounds: Rect::new(0.0, 0.0, W, 20.0),
};
surface
.paint(|target| {
paint_row_glyphs(target, &dwrite, &vline, &long_line, &[], &theme, 1.0);
})
.expect("paint glyphs");
let mut painted_any = false;
for x in 0..W as u32 {
for y in 0..20u32 {
let px = surface.pixel_at(x, y);
if (px.r, px.g, px.b) != (255, 255, 255) {
painted_any = true;
}
}
}
assert!(painted_any, "expected the Characters branch to paint text");
}
#[test]
fn characters_mode_requests_a_minimap_sized_format_not_the_editor_size() {
let surface = HeadlessSurface::new(W as u32, H as u32).expect("create surface");
let (dwrite, _, _) = DWrite::new("Segoe UI", 24.0, None).expect("create DWrite");
let editor_dip = pt_to_dip(24.0) as f64;
assert_eq!(
dwrite.minimap_format_cache_len(),
0,
"cache must start empty"
);
let theme = Theme::default();
let mm = minimap_from(vec!["fn main() {}"; 4], 4);
let rect = Rect::new(0.0, 0.0, W, H);
surface
.paint(|target| {
draw_minimap_scaled(target, &dwrite, rect, &mm, &theme, MinimapScale::Two);
})
.expect("paint minimap");
let row_px = win_minimap_layout_scaled(&mm, rect, MinimapScale::Two)
.visible_lines
.first()
.map(|v| v.bounds.height as f64)
.expect("at least one visible row");
assert!(
row_px < 24.0,
"the row pitch ({row_px}) must stay far below the 24pt editor font for this \
test to actually distinguish the two sizes"
);
assert_eq!(
dwrite.minimap_format_cache_len(),
1,
"a Characters-mode minimap paint should have created and cached exactly one \
minimap-sized text format via DWrite::minimap_text_format -- not painted \
through the editor-size format, which never touches this cache at all"
);
assert!(
dwrite.has_cached_minimap_px(row_px),
"the cached entry must be keyed on the resolved minimap size ({row_px}), not \
some other size"
);
assert!(
!dwrite.has_cached_minimap_px(editor_dip),
"the cache must not hold an entry at the editor's own DIP size ({editor_dip}) \
-- that would mean the minimap painted through the editor-size format"
);
}
#[test]
fn characters_branch_falls_back_to_blocks_below_the_legibility_floor() {
const WHITE: (u8, u8, u8) = (255, 255, 255);
const BLACK: (u8, u8, u8) = (0, 0, 0);
const CELL_W: u32 = 1;
let surface = HeadlessSurface::new(W as u32, H as u32).expect("create surface");
surface
.fill_rect(Rect::new(0.0, 0.0, W, H), Color::rgb(255, 255, 255))
.expect("fill background white");
let (dwrite, _, _) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
let theme = Theme {
background: Color::rgb(255, 255, 255),
foreground: Color::rgb(0, 0, 0),
..Theme::default()
};
let vline = VisibleMinimapLine {
start_line_idx: 0,
bounds: Rect::new(0.0, 0.0, W, 2.0),
};
assert!(
!crate::primitives::minimap::is_legible(vline.bounds.height as f64),
"this test's row height must resolve below the legibility floor"
);
let text = "fn main() {}";
let non_blank_col = 0u32;
let blank_col = 2u32;
assert_eq!(text.chars().nth(non_blank_col as usize), Some('f'));
assert_eq!(text.chars().nth(blank_col as usize), Some(' '));
surface
.paint(|target| {
paint_row_glyphs(target, &dwrite, &vline, text, &[], &theme, CELL_W as f32);
})
.expect("paint glyphs");
assert_eq!(
dwrite.minimap_format_cache_len(),
0,
"a below-floor call must fall back to paint_row_blocks rather than ever \
asking DirectWrite for a minimap text format"
);
for y in 0..2u32 {
let painted = surface.pixel_at(non_blank_col * CELL_W, y);
assert_eq!(
(painted.r, painted.g, painted.b),
BLACK,
"expected a column block at the non-blank character's column ({non_blank_col}, {y})"
);
let blank = surface.pixel_at(blank_col * CELL_W, y);
assert_eq!(
(blank.r, blank.g, blank.b),
WHITE,
"a blank-character column ({blank_col}, {y}) must stay unpainted"
);
}
}
}