#[cfg(target_os = "windows")]
use windows::Win32::Graphics::Direct2D::ID2D1RenderTarget;
use crate::event::Rect;
use crate::primitives::data_table::DataTable;
use crate::primitives::layout_metrics::{pixel_data_table_layout, TextMeasure};
#[cfg(target_os = "windows")]
use crate::theme::Theme;
use crate::DataTableLayout;
pub fn win_data_table_layout(
measure: &dyn TextMeasure,
rect: Rect,
table: &DataTable,
line_height: f32,
) -> DataTableLayout {
pixel_data_table_layout(table, rect.width, rect.height, line_height, measure)
}
#[cfg(target_os = "windows")]
pub fn draw_data_table(
target: &ID2D1RenderTarget,
dwrite: &super::text::DWrite,
rect: Rect,
table: &DataTable,
line_height: f32,
hovered_idx: Option<usize>,
) -> DataTableLayout {
let theme = Theme::default();
let mut surface = super::surface::D2dSurface {
target,
dwrite: Some(dwrite),
};
crate::primitives::data_table::native_surface_paint::paint(
table,
&mut surface,
&theme,
rect,
line_height,
hovered_idx,
)
}
#[cfg(all(test, target_os = "windows"))]
mod tests {
use super::*;
use crate::primitives::data_table::{Column, ColumnAlign, ColumnWidth, DataRow, DataTableHit};
use crate::types::{Decoration, StyledText, WidgetId};
use crate::win::testing::HeadlessSurface;
use crate::win::text::DWrite;
const W: f32 = 300.0;
const H: f32 = 100.0;
const LINE_HEIGHT: f32 = 16.0;
fn table(rows: Vec<DataRow>) -> DataTable {
DataTable {
id: WidgetId::new("table"),
columns: vec![
Column {
title: "Name".into(),
width: ColumnWidth::Flex(1.0),
align: ColumnAlign::Left,
},
Column {
title: "Size".into(),
width: ColumnWidth::Flex(1.0),
align: ColumnAlign::Right,
},
],
rows,
selected_idx: Some(0),
scroll_offset: 0,
sort: None,
has_focus: true,
show_scrollbar: false,
min_total_width: None,
h_scroll: 0.0,
column_overrides: Vec::new(),
footer: None,
}
}
fn row(name: &str, size: &str) -> DataRow {
DataRow {
cells: vec![
StyledText::plain(name.to_string()),
StyledText::plain(size.to_string()),
],
decoration: Decoration::Normal,
}
}
#[test]
fn paint_and_hit_test_round_trip() {
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 table = table(vec![
row("a.txt", "1kb"),
row("b.txt", "2kb"),
row("c.txt", "3kb"),
]);
let rect = Rect::new(0.0, 0.0, W, H);
let layout = surface
.paint(|target| {
draw_data_table(target, &dwrite, rect, &table, LINE_HEIGHT, None);
})
.map(|_| win_data_table_layout(&dwrite, rect, &table, LINE_HEIGHT))
.expect("paint data table");
let hit = layout.hit_test(1.0, 1.0, table.scroll_offset, table.rows.len());
assert_eq!(hit, DataTableHit::Header { col: 0 });
let row_y = layout.header_height + line_height_mid(LINE_HEIGHT);
let hit = layout.hit_test(1.0, row_y, table.scroll_offset, table.rows.len());
assert_eq!(hit, DataTableHit::Row { idx: 0 });
let theme = Theme::default();
let sample_y = (layout.header_height + 2.0) as u32;
let px = surface.pixel_at(2, sample_y);
assert_ne!(
(px.r, px.g, px.b),
(theme.background.r, theme.background.g, theme.background.b),
"selected row should paint a tinted background distinct from the plain body bg"
);
}
fn line_height_mid(h: f32) -> f32 {
h / 2.0
}
#[test]
fn scroll_offset_hit_test_agrees() {
let (dwrite, _, _) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
let mut t = table(vec![row("a", "1"), row("b", "2"), row("c", "3")]);
t.scroll_offset = 1;
let rect = Rect::new(0.0, 0.0, W, H);
let layout = win_data_table_layout(&dwrite, rect, &t, LINE_HEIGHT);
let row_y = layout.header_height + line_height_mid(LINE_HEIGHT);
let hit = layout.hit_test(1.0, row_y, t.scroll_offset, t.rows.len());
assert_eq!(hit, DataTableHit::Row { idx: 1 });
}
#[test]
fn no_paint_layout_matches_paint_layout() {
let table = table(vec![row("a.txt", "1kb"), row("b.txt", "2kb")]);
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_data_table(target, &dwrite, rect, &table, LINE_HEIGHT, None);
})
.map(|_| win_data_table_layout(&dwrite, rect, &table, LINE_HEIGHT))
.expect("paint");
let no_paint = win_data_table_layout(&dwrite, rect, &table, LINE_HEIGHT);
assert_eq!(painted, no_paint);
}
#[test]
fn footer_span_colour_overrides_the_default_foreground() {
use crate::types::{Color, StyledSpan};
let (dwrite, _, _) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
let mut t = table(vec![row("a.txt", "1kb")]);
t.footer = Some(DataRow {
cells: vec![
StyledText {
spans: vec![StyledSpan {
text: "Total".into(),
fg: Some(Color::rgb(255, 0, 0)),
bg: None,
bold: false,
italic: false,
underline: false,
}],
},
StyledText::plain("3"),
],
decoration: Decoration::Normal,
});
let rect = Rect::new(0.0, 0.0, W, H);
let surface = HeadlessSurface::new(W as u32, H as u32).expect("create surface");
surface
.fill_rect(rect, Color::rgb(255, 255, 255))
.expect("fill white bg");
surface
.paint(|target| {
draw_data_table(target, &dwrite, rect, &t, LINE_HEIGHT, None);
})
.expect("paint data table");
let layout = win_data_table_layout(&dwrite, rect, &t, LINE_HEIGHT);
let footer_top = (H - layout.footer_height).max(0.0) as u32;
let mut found_red = false;
for y in footer_top..(H as u32) {
for x in 0..(W as u32) {
let px = surface.pixel_at(x, y);
if px.r > 200 && px.g < 80 && px.b < 80 {
found_red = true;
}
}
}
assert!(
found_red,
"footer 'Total' span should paint in its own red fg, not \
the pre-#1084 whole-cell `theme.foreground`"
);
}
}