use std::collections::HashMap;
use teksilo_core::signal::Signal;
use crate::common::row_metrics::SharedRowMetrics;
use crate::table_view::column::{Column, PinnedSide};
use crate::table_view::selection::CellSelectionModel;
pub(crate) fn scroll_to_row(
row: usize,
row_metrics: &SharedRowMetrics,
scroll_y: &Signal<f32>,
max_scroll_y: &Signal<f32>,
) {
let Ok(mut metrics) = row_metrics.try_borrow_mut() else {
return;
};
let target = metrics.row_top(row);
drop(metrics);
let max = max_scroll_y.get();
scroll_y.set(target.clamp(0.0, max));
}
pub(crate) fn ensure_row_visible(
row: usize,
row_metrics: &SharedRowMetrics,
scroll_y: &Signal<f32>,
max_scroll_y: &Signal<f32>,
viewport_height: f32,
laid_out: bool,
) {
if !laid_out {
return;
}
let Ok(mut metrics) = row_metrics.try_borrow_mut() else {
return;
};
let scroll = scroll_y.get();
let new_scroll =
metrics.scroll_for_ensure_visible(row, scroll, viewport_height, max_scroll_y.get());
drop(metrics);
if (new_scroll - scroll).abs() > f32::EPSILON {
scroll_y.set(new_scroll);
}
}
pub(crate) fn set_column_width(signal: &Signal<HashMap<String, f32>>, col_id: &str, width: f32) {
let mut m = signal.get();
let changed = if width.is_finite() && width > 0.0 {
m.insert(col_id.to_string(), width) != Some(width)
} else {
m.remove(col_id).is_some()
};
if changed {
signal.set(m);
}
}
pub(crate) fn set_column_widths(
signal: &Signal<HashMap<String, f32>>,
widths: HashMap<String, f32>,
) {
if signal.get() != widths {
signal.set(widths);
}
}
pub(crate) fn set_column_pinning(
signal: &Signal<HashMap<String, PinnedSide>>,
col_id: &str,
side: PinnedSide,
) {
let mut m = signal.get();
let changed = if matches!(side, PinnedSide::None) {
m.remove(col_id).is_some()
} else {
m.insert(col_id.to_string(), side) != Some(side)
};
if changed {
signal.set(m);
}
}
pub(crate) fn set_filter(signal: &Signal<HashMap<String, String>>, col_id: &str, text: &str) {
let mut m = signal.get();
let changed = if text.is_empty() {
m.remove(col_id).is_some()
} else {
m.insert(col_id.to_string(), text.to_string()).as_deref() != Some(text)
};
if changed {
signal.set(m);
}
}
pub(crate) fn set_if_changed<T: Clone + PartialEq + 'static>(signal: &Signal<T>, next: T) {
if signal.get() != next {
signal.set(next);
}
}
pub(crate) fn resolve_edit_target<T: 'static>(
row: usize,
col_id: &str,
columns: &[Column<T>],
display_indices: &[usize],
row_count: usize,
) -> Option<(usize, usize)> {
if row >= row_count {
return None;
}
let decl_index = columns.iter().position(|c| c.id == col_id)?;
let display_pos = display_indices.iter().position(|&i| i == decl_index)?;
Some((row, display_pos))
}
pub(crate) fn remap_cell_state(
focused_cell: &Signal<Option<(usize, usize)>>,
editing_cell: &Signal<Option<(usize, usize)>>,
cell_selection: Option<&CellSelectionModel>,
old_to_new: &[Option<usize>],
) {
let remap = |cell: Option<(usize, usize)>| {
cell.and_then(|(row, col)| old_to_new.get(col).copied().flatten().map(|nc| (row, nc)))
};
let old_focus = focused_cell.get();
let new_focus = remap(old_focus);
if new_focus != old_focus {
focused_cell.set(new_focus);
}
let old_edit = editing_cell.get();
let new_edit = remap(old_edit);
if new_edit != old_edit {
editing_cell.set(new_edit);
}
if let Some(cs) = cell_selection {
cs.remap_columns(old_to_new);
}
}