use densa_core::Rect;
use densa_grid::{CellText, GridModel, SortOrder};
use densa_ui::{AccessAction, AccessNode, AccessRequest, EventCx, Handled, PaintCx, Role};
use super::DataGrid;
const ROWS: u32 = 1 << 12;
const HEADER: u32 = ROWS;
const HEADER_CELLS: u32 = HEADER + 1;
const MAX_COLUMNS: u32 = 1 << 10;
const CELLS: u32 = HEADER_CELLS + MAX_COLUMNS;
fn row_part(row: usize) -> u32 {
(row % ROWS as usize) as u32
}
fn cell_part(row: Option<usize>, column: usize) -> Option<u32> {
let column = u32::try_from(column).ok().filter(|c| *c < MAX_COLUMNS)?;
Some(match row {
None => HEADER_CELLS + column,
Some(row) => CELLS + row_part(row) * MAX_COLUMNS + column,
})
}
fn part_row(part: u32) -> Option<u32> {
if part < ROWS {
return Some(part);
}
let cell = part.checked_sub(CELLS)?;
let row = cell / MAX_COLUMNS;
(row < ROWS).then_some(row)
}
impl<M: GridModel> DataGrid<M> {
fn shown_rows(&self) -> std::ops::Range<usize> {
let shown = self
.visible_rows()
.min(self.model.rows().saturating_sub(self.first_row));
self.first_row..self.first_row + shown
}
pub(super) fn describe(&self, cx: &PaintCx<'_>, node: &mut AccessNode) {
node.set_role(Role::Table);
let (Some(frame), Some(layout)) = (self.frame(cx.rect()), self.layout_of()) else {
return;
};
let width = frame.rect.width();
let max_x = self.max_scroll_x(width);
for (action, can) in [
(AccessAction::ScrollUp, self.first_row > 0),
(
AccessAction::ScrollDown,
self.first_row < self.last_first_row(),
),
(AccessAction::ScrollLeft, self.scroll_x > 0.0),
(AccessAction::ScrollRight, self.scroll_x < max_x),
] {
if can {
node.add_action(action);
}
}
let count = self.columns.len().min(layout.column_count());
let cell_rect = |column: usize, top: f32, bottom: f32| {
let half = layout.gap() as f32 / 2.0;
let left = frame.rect.left() + layout.column_x(column) as f32
- half
- self.shift_of(column, width);
let right = left + layout.column_width(column) as f32 + 2.0 * half;
Rect::from_edges(left, top, right, bottom)
};
let mut header = AccessNode::new(Role::Row);
header.set_bounds(frame.header);
for column in 0..count {
let Some(part) = cell_part(None, column) else {
break;
};
let mut cell = AccessNode::new(Role::ColumnHeader);
let title = self.columns[column].title.as_ref();
match self.sort_order() {
Some((c, SortOrder::Ascending)) if c == column => {
cell.set_label(format!("{title}, sorted ascending"));
}
Some((c, _)) if c == column => {
cell.set_label(format!("{title}, sorted descending"));
}
_ => cell.set_label(title),
}
cell.set_bounds(cell_rect(column, frame.header.top(), frame.header.bottom()));
header.push_part(part, cell);
}
node.push_part(HEADER, header);
let row_height = layout.row_height() as f32;
let palette = super::palette(cx.theme());
let mut text = CellText::new();
for (slot, row) in self.shown_rows().enumerate() {
let top = frame.body.top() + slot as f32 * row_height;
let bottom = (top + row_height).min(frame.body.bottom());
let mut item = AccessNode::new(Role::Row);
item.set_bounds(Rect::from_edges(
frame.rect.left(),
top,
frame.rect.right(),
bottom,
));
item.set_selected(self.selected_rows().contains(&row));
item.add_action(AccessAction::Click);
item.add_action(AccessAction::ScrollIntoView);
for column in 0..count {
let Some(part) = cell_part(Some(row), column) else {
break;
};
text.clear();
self.model.cell(row, column, &mut text, &palette);
let mut cell = AccessNode::new(Role::Cell);
cell.set_value(text.as_str());
cell.set_bounds(cell_rect(column, top, bottom));
cell.add_action(AccessAction::Click);
item.push_part(part, cell);
}
node.push_part(row_part(row), item);
}
if let Some(row) = self.selected().filter(|r| self.shown_rows().contains(r)) {
node.set_focused_part(row_part(row));
}
}
pub(super) fn act(&mut self, cx: &mut EventCx<'_>, request: &AccessRequest<'_>) -> Handled {
let Some(part) = request.part else {
return self.scroll_table(cx, request.action);
};
let Some(number) = part_row(part) else {
return Handled::No;
};
let Some(row) = self.shown_rows().find(|r| row_part(*r) == number) else {
return Handled::No;
};
match request.action {
AccessAction::Click => {
self.select_row(cx, row);
Handled::Yes
}
AccessAction::ScrollIntoView => {
self.reveal_row(row);
cx.request_paint();
Handled::Yes
}
_ => Handled::No,
}
}
fn scroll_table(&mut self, cx: &mut EventCx<'_>, action: AccessAction) -> Handled {
let page = self.whole_rows().max(1);
let max_x = self.max_scroll_x(cx.rect().width());
let sideways = cx.px(48.0);
let before = (self.first_row, self.scroll_x);
match action {
AccessAction::ScrollUp => self.first_row = self.first_row.saturating_sub(page),
AccessAction::ScrollDown => {
self.first_row = self
.first_row
.saturating_add(page)
.min(self.last_first_row());
}
AccessAction::ScrollLeft => self.scroll_x = (self.scroll_x - sideways).max(0.0),
AccessAction::ScrollRight => self.scroll_x = (self.scroll_x + sideways).min(max_x),
_ => return Handled::No,
}
if (self.first_row, self.scroll_x) == before {
return Handled::No;
}
cx.request_paint();
Handled::Yes
}
}