use crate::visual_line::VisualLine;
#[derive(Clone, Copy)]
pub(super) struct Layout {
pub sidebar_cols: u16,
pub image_col: u16, pub image_cols: u16, pub image_rows: u16, pub status_row: u16, pub cell_w: u16, pub cell_h: u16, }
pub(super) struct ScrollState {
pub y_offset: u32, pub img_h: u32, pub vp_w: u32, pub vp_h: u32, }
impl Layout {
pub(super) fn viewport_width_pt(&self, ppi: f64) -> f64 {
self.image_cols as f64 * self.cell_w as f64 * 72.0 / ppi
}
pub(super) fn viewport_height_pt(&self, ppi: f64) -> f64 {
self.image_rows as f64 * self.cell_h as f64 * 72.0 / ppi
}
pub(super) fn sidebar_width_pt(&self, ppi: f64) -> f64 {
self.sidebar_cols as f64 * self.cell_w as f64 * 72.0 / ppi
}
pub(super) fn align_tile_height_pt(&self, tile_height_pt: f64, ppi: f64) -> f64 {
let raw_px = (tile_height_pt * ppi / 72.0).round() as u32;
let cell_h = self.cell_h as u32;
let aligned_px = raw_px.div_ceil(cell_h) * cell_h;
aligned_px as f64 * 72.0 / ppi
}
}
pub(super) fn compute_layout(
term_cols: u16,
term_rows: u16,
pixel_w: u16,
pixel_h: u16,
sidebar_cols: u16,
) -> Layout {
let image_col = sidebar_cols;
let image_cols = term_cols.saturating_sub(sidebar_cols);
let image_rows = term_rows.saturating_sub(1);
let status_row = term_rows.saturating_sub(1);
let cell_w = if term_cols > 0 {
pixel_w / term_cols
} else {
1
};
let cell_h = if term_rows > 0 {
pixel_h / term_rows
} else {
1
};
Layout {
sidebar_cols,
image_col,
image_cols,
image_rows,
status_row,
cell_w,
cell_h,
}
}
pub(super) fn vp_dims(layout: &Layout, img_w: u32, img_h: u32) -> (u32, u32) {
let vp_w = (layout.image_cols as u32 * layout.cell_w as u32).min(img_w);
let vp_h = (layout.image_rows as u32 * layout.cell_h as u32).min(img_h);
(vp_w, vp_h)
}
pub(super) fn visual_line_offset(
visual_lines: &[VisualLine],
max_scroll: u32,
line_num: u32,
) -> u32 {
let idx = (line_num as usize).saturating_sub(1); if idx < visual_lines.len() {
visual_lines[idx.saturating_sub(1)].y_px.min(max_scroll)
} else {
0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compute_layout_basic() {
let l = compute_layout(80, 24, 1280, 576, 6);
assert_eq!(l.sidebar_cols, 6);
assert_eq!(l.image_col, 6);
assert_eq!(l.image_cols, 74);
assert_eq!(l.image_rows, 23);
assert_eq!(l.status_row, 23);
assert_eq!(l.cell_w, 16); assert_eq!(l.cell_h, 24); }
#[test]
fn compute_layout_zero_cols_no_panic() {
let l = compute_layout(0, 0, 0, 0, 0);
assert_eq!(l.image_cols, 0);
assert_eq!(l.cell_w, 1); assert_eq!(l.cell_h, 1);
}
#[test]
fn visual_line_offset_first_line() {
let vls = vec![
VisualLine {
y_pt: 0.0,
y_px: 0,
md_block_range: None,
md_offset: None,
},
VisualLine {
y_pt: 0.0,
y_px: 100,
md_block_range: None,
md_offset: None,
},
];
assert_eq!(visual_line_offset(&vls, 1000, 1), 0);
}
#[test]
fn visual_line_offset_middle_line() {
let vls = vec![
VisualLine {
y_pt: 0.0,
y_px: 0,
md_block_range: None,
md_offset: None,
},
VisualLine {
y_pt: 0.0,
y_px: 100,
md_block_range: None,
md_offset: None,
},
VisualLine {
y_pt: 0.0,
y_px: 200,
md_block_range: None,
md_offset: None,
},
];
assert_eq!(visual_line_offset(&vls, 1000, 3), 100);
}
#[test]
fn visual_line_offset_out_of_range() {
let vls = vec![VisualLine {
y_pt: 0.0,
y_px: 0,
md_block_range: None,
md_offset: None,
}];
assert_eq!(visual_line_offset(&vls, 1000, 99), 0);
}
#[test]
fn visual_line_offset_clamps_to_max() {
let vls = vec![
VisualLine {
y_pt: 0.0,
y_px: 0,
md_block_range: None,
md_offset: None,
},
VisualLine {
y_pt: 0.0,
y_px: 500,
md_block_range: None,
md_offset: None,
},
];
assert_eq!(visual_line_offset(&vls, 100, 2), 0);
let vls2 = vec![
VisualLine {
y_pt: 0.0,
y_px: 0,
md_block_range: None,
md_offset: None,
},
VisualLine {
y_pt: 0.0,
y_px: 9999,
md_block_range: None,
md_offset: None,
},
VisualLine {
y_pt: 0.0,
y_px: 10000,
md_block_range: None,
md_offset: None,
},
];
assert_eq!(visual_line_offset(&vls2, 500, 3), 500);
}
#[test]
fn vp_dims_viewport_smaller_than_image() {
let l = compute_layout(80, 24, 1280, 576, 6);
let (vp_w, vp_h) = vp_dims(&l, 2000, 5000);
assert_eq!(vp_w, 1184);
assert_eq!(vp_h, 552);
}
#[test]
fn vp_dims_viewport_larger_than_image() {
let l = compute_layout(80, 24, 1280, 576, 6);
let (vp_w, vp_h) = vp_dims(&l, 100, 200);
assert_eq!(vp_w, 100);
assert_eq!(vp_h, 200);
}
#[test]
fn layout_pt_conversions() {
let l = compute_layout(80, 24, 1280, 576, 6);
let ppi = 144.0;
assert_eq!(l.viewport_width_pt(ppi), 592.0);
assert_eq!(l.viewport_height_pt(ppi), 276.0);
assert_eq!(l.sidebar_width_pt(ppi), 48.0);
}
#[test]
fn align_tile_height_rounds_up_to_cell_boundary() {
let l = compute_layout(80, 24, 1280, 576, 6);
let ppi = 144.0;
assert_eq!(l.align_tile_height_pt(276.0, ppi), 276.0);
assert_eq!(l.align_tile_height_pt(277.0, ppi), 288.0);
}
}