use crate::primitives::text_display::{TextDisplay, TextDisplayLayout, TextDisplayLineMeasure};
use crate::theme::Theme;
#[allow(clippy::too_many_arguments)]
pub fn draw_text_display(
cr: >k4::cairo::Context,
layout: >k4::pango::Layout,
x: f64,
y: f64,
w: f64,
h: f64,
display: &TextDisplay,
theme: &Theme,
line_height: f64,
char_width: f64,
) {
let rect = crate::event::Rect::new(x as f32, y as f32, w as f32, h as f32);
let mut surface = super::surface::CairoSurface {
cr,
layout: Some(layout),
translucent_fill: false,
};
crate::primitives::text_display::paint(
display,
rect,
&mut surface,
theme,
line_height as f32,
char_width as f32,
);
}
pub fn gtk_text_display_layout(
display: &TextDisplay,
rect: crate::event::Rect,
line_height: f64,
char_width: f64,
) -> TextDisplayLayout {
let body_h = if display.title.is_some() {
(rect.height as f64 - line_height).max(0.0)
} else {
rect.height as f64
};
if body_h <= 0.0 {
return display.layout(0.0, 0.0, |_| {
TextDisplayLineMeasure::new(line_height as f32)
});
}
let gutter_px = 12.0_f32;
let body_width_px = if display.show_scrollbar {
(rect.width - gutter_px).max(0.0)
} else {
rect.width
};
let col_budget = crate::primitives::text_display::px_to_cols(body_width_px, char_width as f32);
let measure = |i: usize| {
let rows = crate::primitives::text_display::wrap_row_count(&display.lines[i], col_budget);
TextDisplayLineMeasure::new(rows as f32 * line_height as f32)
};
if display.show_scrollbar {
display.layout_with_scrollbar(rect.width, body_h as f32, gutter_px, 8.0, measure)
} else {
display.layout(rect.width, body_h as f32, measure)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::text_display::{TextDisplayHit, TextDisplayLine};
use crate::types::{Decoration, StyledSpan, WidgetId};
fn display(show_scrollbar: bool) -> TextDisplay {
TextDisplay {
id: WidgetId::new("td"),
lines: (0..20)
.map(|i| TextDisplayLine {
spans: vec![StyledSpan::plain(format!("line{i}"))],
decoration: Decoration::Normal,
timestamp: None,
})
.collect(),
scroll_offset: 0,
auto_scroll: false,
max_lines: 0,
has_focus: false,
title: None,
show_scrollbar,
}
}
fn scrollbar_thumb_round_trip_at(x: f32, y: f32) {
let rect = crate::event::Rect::new(x, y, 20.0, 5.0);
let d = display(true);
let layout = gtk_text_display_layout(&d, rect, 1.0, 1.0);
let thumb = layout.thumb_bounds.expect("thumb bounds present");
assert!(
thumb.x < rect.width,
"thumb.x={} must stay inside the local frame",
thumb.x
);
let hit = layout.hit_test(thumb.x + 0.5, thumb.y + 0.5);
assert_eq!(hit, TextDisplayHit::ScrollbarThumb);
}
#[test]
fn scrollbar_thumb_paint_and_click_round_trip() {
scrollbar_thumb_round_trip_at(0.0, 0.0);
}
#[test]
fn scrollbar_thumb_paint_and_click_round_trip_at_nonzero_origin() {
scrollbar_thumb_round_trip_at(7.0, 13.0);
}
#[test]
fn wrap_reports_multi_row_height_for_a_long_line() {
let mut d = display(false);
d.lines = vec![TextDisplayLine {
spans: vec![StyledSpan::plain(
"the quick brown fox jumps over the lazy dog",
)],
decoration: Decoration::Normal,
timestamp: None,
}];
let rect = crate::event::Rect::new(0.0, 0.0, 80.0, 100.0);
let layout = gtk_text_display_layout(&d, rect, 16.0, 8.0);
let vis = layout.visible_lines.first().expect("line 0 visible");
assert!(
vis.bounds.height > 16.0,
"expected wrapped height > one line_height, got {}",
vis.bounds.height
);
}
#[test]
fn draw_and_layout_agree_on_wrapped_row_count() {
let mut d = display(false);
d.lines = vec![TextDisplayLine {
spans: vec![StyledSpan::plain(
"the quick brown fox jumps over the lazy dog",
)],
decoration: Decoration::Normal,
timestamp: None,
}];
let rect = crate::event::Rect::new(0.0, 0.0, 80.0, 300.0);
let from_layout = gtk_text_display_layout(&d, rect, 16.0, 8.0).visible_lines[0]
.bounds
.height;
let col_budget = crate::primitives::text_display::px_to_cols(80.0, 8.0);
let from_paint_math =
crate::primitives::text_display::wrap_row_count(&d.lines[0], col_budget) as f32 * 16.0;
assert_eq!(from_layout, from_paint_math);
}
}