use windows::Win32::Graphics::Direct2D::ID2D1RenderTarget;
use super::text::DWrite;
use crate::event::Rect;
use crate::primitives::message_list::MessageList;
pub fn draw_message_list(
target: &ID2D1RenderTarget,
dwrite: &DWrite,
list: &MessageList,
x: f32,
y: f32,
max_y: f32,
line_height: f32,
) {
let mut surface = super::surface::D2dSurface {
target,
dwrite: Some(dwrite),
};
let rect = Rect::new(x, y, 0.0, max_y - y);
crate::primitives::message_list::native_surface_paint::paint(
list,
&mut surface,
rect,
line_height,
);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::message_list::MessageRow;
use crate::types::{Color, StyledSpan, WidgetId};
use crate::win::testing::HeadlessSurface;
const W: u32 = 240;
const H: u32 = 160;
const LINE_HEIGHT: f32 = 16.0;
const PANEL_BG: Color = Color::rgb(13, 13, 13);
fn dwrite() -> DWrite {
DWrite::new("Segoe UI", 10.0, None)
.expect("create DWrite")
.0
}
fn sample_list() -> MessageList {
MessageList {
id: WidgetId::new("ml"),
rows: vec![
MessageRow::new("You:", Color::rgb(255, 220, 0), 0.0),
MessageRow::new("hi there", Color::rgb(220, 220, 220), 8.0),
MessageRow::new("AI:", Color::rgb(0, 200, 255), 0.0),
MessageRow::new("hello", Color::rgb(220, 220, 220), 8.0),
],
scroll_top: 0,
}
}
fn paint(list: &MessageList) -> HeadlessSurface {
let surface = HeadlessSurface::new(W, H).expect("create surface");
surface
.fill_rect(Rect::new(0.0, 0.0, W as f32, H as f32), PANEL_BG)
.expect("fill panel bg");
let dwrite = dwrite();
surface
.paint(|target| {
draw_message_list(target, &dwrite, list, 0.0, 0.0, H as f32, LINE_HEIGHT);
})
.expect("paint");
surface
}
fn pixel_differs_from(s: &HeadlessSurface, x: u32, y: u32, base: Color) -> bool {
let px = s.pixel_at(x, y);
(px.r, px.g, px.b) != (base.r, base.g, base.b)
}
#[test]
fn rows_paint_glyphs_above_panel_bg() {
let list = sample_list();
let s = paint(&list);
let mut found = [false; 3];
for (band, slot) in found.iter_mut().enumerate() {
let band_u = band as u32;
let y_top = band_u * LINE_HEIGHT as u32;
let y_bot = (band_u + 1) * LINE_HEIGHT as u32;
'scan: for y in y_top..y_bot.min(H) {
for x in 0..40u32.min(W) {
if pixel_differs_from(&s, x, y, PANEL_BG) {
*slot = true;
break 'scan;
}
}
}
}
assert!(
found.iter().all(|f| *f),
"expected non-panel-bg pixels in rows 0..3, found = {:?}",
found
);
}
#[test]
fn scroll_top_skips_leading_rows() {
let mut list = sample_list();
list.scroll_top = 2;
let s = paint(&list);
let mut has_paint = false;
'outer: for y in 0..LINE_HEIGHT as u32 {
for x in 0..30u32.min(W) {
if pixel_differs_from(&s, x, y, PANEL_BG) {
has_paint = true;
break 'outer;
}
}
}
assert!(has_paint, "scrolled top band should have row paint");
}
#[test]
fn rows_past_max_y_are_clipped_without_crashing() {
let mut list = sample_list();
for i in 0..200 {
list.rows.push(MessageRow::new(
format!("row {i}"),
Color::rgb(200, 200, 200),
0.0,
));
}
let s = paint(&list);
let px = s.pixel_at(W - 1, H - 1);
assert_eq!(
(px.r, px.g, px.b),
(PANEL_BG.r, PANEL_BG.g, PANEL_BG.b),
"far bottom-right corner should remain untouched panel bg"
);
}
#[test]
fn styled_row_paints_per_span_colour() {
let mut list = sample_list();
list.rows = vec![MessageRow {
text: "bold text".into(),
fg: Color::rgb(220, 220, 220),
indent: 0.0,
spans: vec![
StyledSpan {
text: "bold".into(),
fg: Some(Color::rgb(255, 0, 0)),
bg: None,
bold: true,
italic: false,
underline: false,
},
StyledSpan::plain(" text"),
],
scale: 1.0,
}];
let s = paint(&list);
let mut has_paint = false;
'outer: for y in 0..LINE_HEIGHT as u32 {
for x in 0..60u32.min(W) {
if pixel_differs_from(&s, x, y, PANEL_BG) {
has_paint = true;
break 'outer;
}
}
}
assert!(has_paint, "styled row should paint glyph pixels");
}
}