use crate::app_state::AppState;
use crate::cli::ViewArgs;
pub struct LayoutCalculator;
impl LayoutCalculator {
pub fn calculate_header_lines(state: &AppState) -> u16 {
let mut lines = 0u16;
lines += 3;
lines += 4;
if !state.utilization_history.is_empty() {
lines += 5; }
lines += 2;
lines
}
pub fn calculate_content_area(state: &AppState, cols: u16, rows: u16) -> ContentArea {
let header_lines = Self::calculate_header_lines(state);
let function_keys_lines = 1;
let available_rows = rows
.saturating_sub(header_lines)
.saturating_sub(function_keys_lines);
ContentArea {
x: 0,
y: header_lines,
width: cols,
height: available_rows,
available_rows: available_rows as usize,
}
}
pub fn calculate_gpu_display_params(
state: &AppState,
args: &ViewArgs,
content_area: &ContentArea,
) -> GpuDisplayParams {
let is_remote = args.hosts.is_some() || args.hostfile.is_some();
let storage_items_count = Self::calculate_storage_items_count(state, args);
let storage_display_rows = if storage_items_count > 0 {
storage_items_count + 2 } else {
0
};
let gpu_display_rows = if is_remote {
if state.current_tab < state.tabs.len() && state.tabs[state.current_tab] == "All" {
content_area.available_rows } else {
content_area
.available_rows
.saturating_sub(storage_display_rows)
}
} else if state.process_info.is_empty() {
content_area
.available_rows
.saturating_sub(storage_display_rows)
} else {
content_area
.available_rows
.saturating_sub(storage_display_rows)
/ 2
};
let lines_per_gpu = 2; let max_gpu_items = gpu_display_rows / lines_per_gpu;
GpuDisplayParams {
display_rows: gpu_display_rows,
lines_per_gpu,
max_items: max_gpu_items,
start_index: state.gpu_scroll_offset,
storage_rows: storage_display_rows,
}
}
#[allow(dead_code)] pub fn calculate_progress_bar_layout(
width: usize,
num_bars: usize,
padding: usize,
) -> ProgressBarLayout {
let total_padding = padding * 2; let separators = if num_bars > 1 { (num_bars - 1) * 2 } else { 0 };
let available_width = width.saturating_sub(total_padding + separators);
let bar_width = if num_bars > 0 {
available_width / num_bars
} else {
available_width
};
ProgressBarLayout {
bar_width,
left_padding: padding,
right_padding: padding,
separator_width: if num_bars > 1 { 2 } else { 0 },
total_bars: num_bars,
}
}
#[allow(dead_code)] pub fn calculate_table_columns(
available_width: usize,
column_specs: &[ColumnSpec],
) -> Vec<usize> {
let min_total: usize = column_specs.iter().map(|c| c.min_width).sum();
let separator_width = column_specs.len() - 1;
if available_width <= min_total + separator_width {
return column_specs.iter().map(|c| c.min_width).collect();
}
let extra_space = available_width - min_total - separator_width;
let total_weight: f32 = column_specs.iter().map(|c| c.weight).sum();
let mut widths = Vec::new();
for spec in column_specs {
let extra = (extra_space as f32 * spec.weight / total_weight) as usize;
widths.push(spec.min_width + extra);
}
widths
}
fn calculate_storage_items_count(state: &AppState, args: &ViewArgs) -> usize {
let is_remote = args.hosts.is_some() || args.hostfile.is_some();
if state.storage_info.is_empty() {
return 0;
}
if is_remote {
if state.current_tab < state.tabs.len() && state.tabs[state.current_tab] != "All" {
let current_hostname = &state.tabs[state.current_tab];
state
.storage_info
.iter()
.filter(|info| info.host_id == *current_hostname)
.count()
} else {
0
}
} else {
state.storage_info.len()
}
}
}
#[derive(Debug, Clone)]
pub struct ContentArea {
#[allow(dead_code)] pub x: u16,
#[allow(dead_code)] pub y: u16,
#[allow(dead_code)] pub width: u16,
#[allow(dead_code)] pub height: u16,
pub available_rows: usize,
}
#[derive(Debug, Clone)]
pub struct GpuDisplayParams {
#[allow(dead_code)] pub display_rows: usize,
#[allow(dead_code)] pub lines_per_gpu: usize,
pub max_items: usize, #[allow(dead_code)] pub start_index: usize,
#[allow(dead_code)] pub storage_rows: usize,
}
#[derive(Debug, Clone)]
#[allow(dead_code)] pub struct ProgressBarLayout {
pub bar_width: usize,
pub left_padding: usize,
pub right_padding: usize,
pub separator_width: usize,
pub total_bars: usize,
}
#[derive(Debug, Clone)]
#[allow(dead_code)] pub struct ColumnSpec {
pub name: &'static str,
pub min_width: usize,
pub weight: f32, }
#[allow(dead_code)] impl ColumnSpec {
pub fn new(name: &'static str, min_width: usize, weight: f32) -> Self {
Self {
name,
min_width,
weight,
}
}
}
#[allow(dead_code)] pub struct StandardColumns;
#[allow(dead_code)] impl StandardColumns {
pub fn process_table() -> Vec<ColumnSpec> {
vec![
ColumnSpec::new("PID", 6, 0.5),
ColumnSpec::new("User", 12, 1.0),
ColumnSpec::new("Name", 8, 2.0),
ColumnSpec::new("CPU%", 6, 0.5),
ColumnSpec::new("Mem%", 8, 0.5),
ColumnSpec::new("GPU Mem", 8, 1.0),
ColumnSpec::new("State", 8, 0.5),
ColumnSpec::new("Command", 10, 3.0),
]
}
pub fn device_table() -> Vec<ColumnSpec> {
vec![
ColumnSpec::new("Device", 15, 2.0),
ColumnSpec::new("Host", 12, 1.0),
ColumnSpec::new("Utilization", 12, 1.0),
ColumnSpec::new("Memory", 15, 1.5),
ColumnSpec::new("Temperature", 12, 1.0),
ColumnSpec::new("Power", 10, 1.0),
]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calculate_progress_bar_layout() {
let layout = LayoutCalculator::calculate_progress_bar_layout(40, 3, 5);
assert_eq!(layout.bar_width, 8); assert_eq!(layout.left_padding, 5);
assert_eq!(layout.right_padding, 5);
assert_eq!(layout.separator_width, 2);
assert_eq!(layout.total_bars, 3);
}
#[test]
fn test_calculate_table_columns() {
let specs = vec![
ColumnSpec::new("A", 10, 1.0),
ColumnSpec::new("B", 15, 2.0),
ColumnSpec::new("C", 5, 0.5),
];
let widths = LayoutCalculator::calculate_table_columns(50, &specs);
assert_eq!(widths[0], 15); assert_eq!(widths[1], 25); assert_eq!(widths[2], 7); }
}