use std::sync::Arc;
use crate::color::Color;
use crate::draw_ctx::DrawCtx;
use crate::event::{Event, EventResult, MouseButton};
use crate::geometry::{Rect, Size};
use crate::text::Font;
use crate::widget::Widget;
use super::config::{CellInfo, TableRows};
use super::state::TableState;
pub(super) struct TableBody {
pub(super) bounds: Rect,
pub(super) children: Vec<Box<dyn Widget>>,
pub(super) font: Arc<Font>,
pub(super) state: TableState,
}
impl TableBody {
fn first_visible_row(&self, top_down_y: f64) -> usize {
let rows = self.state.rows.borrow();
let n = rows.count();
match &*rows {
TableRows::Homogeneous { height, .. } => {
if *height <= 0.0 {
return 0;
}
((top_down_y / *height).floor().max(0.0) as usize).min(n)
}
TableRows::Heterogeneous { heights } => {
let mut y = 0.0;
for (i, &h) in heights.iter().enumerate() {
if y + h > top_down_y {
return i;
}
y += h;
}
n
}
}
}
}
impl Widget for TableBody {
fn type_name(&self) -> &'static str {
"TableBody"
}
fn bounds(&self) -> Rect {
self.bounds
}
fn set_bounds(&mut self, b: Rect) {
self.bounds = b;
}
fn children(&self) -> &[Box<dyn Widget>] {
&self.children
}
fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
&mut self.children
}
fn layout(&mut self, available: Size) -> Size {
let widths = self.state.widths.borrow();
let sum_w: f64 = widths.iter().sum();
drop(widths);
let w = if sum_w > 0.0 { sum_w } else { available.width };
let total_h = self.state.rows.borrow().total_height().max(1.0);
self.bounds = Rect::new(0.0, 0.0, w, total_h);
Size::new(w, total_h)
}
fn paint(&mut self, ctx: &mut dyn DrawCtx) {
let v = ctx.visuals();
let widths = self.state.widths.borrow().clone();
if widths.is_empty() {
return;
}
let total_h = self.bounds.height;
let total_w: f64 = widths.iter().sum();
let rows = self.state.rows.borrow();
let n = rows.count();
if n == 0 {
return;
}
let vp = self.state.viewport_cell.get();
let visible_top = vp.y.max(0.0);
let visible_bottom = (vp.y + vp.height).min(total_h);
let striped = self.state.striped.get();
let overline_pred = self.state.overline_pred.borrow();
let selection_pred = self.state.selection_pred.borrow();
let hovered_row = self.state.hovered_row.get();
let first = self.first_visible_row(visible_top);
let mut y_td = rows.top_down_y_at(first);
let mut painter_opt = self.state.cell_painter.borrow_mut();
for i in first..n {
if y_td >= visible_bottom + 0.5 {
break;
}
let h = rows.height_at(i);
let row_y_yup = total_h - y_td - h;
let selected = selection_pred
.as_ref()
.map(|p| p(i))
.unwrap_or(false);
if selected {
ctx.set_fill_color(v.selection_bg);
ctx.begin_path();
ctx.rect(0.0, row_y_yup, total_w, h);
ctx.fill();
} else if striped && i % 2 == 0 {
ctx.set_fill_color(Color::rgba(0.5, 0.5, 0.5, 0.07));
ctx.begin_path();
ctx.rect(0.0, row_y_yup, total_w, h);
ctx.fill();
}
if !selected && hovered_row == Some(i) {
ctx.set_fill_color(Color::rgba(
v.accent.r,
v.accent.g,
v.accent.b,
0.10,
));
ctx.begin_path();
ctx.rect(0.0, row_y_yup, total_w, h);
ctx.fill();
}
if let Some(pred) = overline_pred.as_ref() {
if pred(i) {
ctx.set_stroke_color(v.accent);
ctx.set_line_width(1.5);
ctx.begin_path();
ctx.move_to(0.0, row_y_yup + h);
ctx.line_to(total_w, row_y_yup + h);
ctx.stroke();
}
}
if let Some(painter) = painter_opt.as_mut() {
let mut x = 0.0;
for (col, &cw) in widths.iter().enumerate() {
let info = CellInfo {
row: i,
col,
rect: Rect::new(x, row_y_yup, cw, h),
selected,
visuals: &v,
font: &self.font,
};
ctx.save();
ctx.clip_rect(x, row_y_yup, cw, h);
painter(&info, ctx);
ctx.restore();
x += cw;
}
}
y_td += h;
}
ctx.set_stroke_color(Color::rgba(v.separator.r, v.separator.g, v.separator.b, 0.4));
ctx.set_line_width(1.0);
let visible_top_yup = total_h - visible_bottom;
let visible_bot_yup = total_h - visible_top;
let mut sx = 0.0;
for (c, &cw) in widths.iter().enumerate() {
sx += cw;
if c + 1 < widths.len() {
ctx.begin_path();
ctx.move_to(sx, visible_top_yup);
ctx.line_to(sx, visible_bot_yup);
ctx.stroke();
}
}
}
fn on_event(&mut self, event: &Event) -> EventResult {
if let Event::MouseMove { pos } = event {
let mut new_hover: Option<usize> = None;
if pos.x >= 0.0
&& pos.x <= self.bounds.width
&& pos.y >= 0.0
&& pos.y <= self.bounds.height
{
let total_h = self.bounds.height;
let rows = self.state.rows.borrow();
let n = rows.count();
let y_td = total_h - pos.y;
let mut y_acc = 0.0;
for i in 0..n {
let h = rows.height_at(i);
if y_td >= y_acc && y_td <= y_acc + h {
new_hover = Some(i);
break;
}
y_acc += h;
}
}
let prev = self.state.hovered_row.get();
if prev != new_hover {
self.state.hovered_row.set(new_hover);
crate::animation::request_draw();
}
}
if !self.state.sense_click.get() {
return EventResult::Ignored;
}
if let Event::MouseUp {
pos,
button: MouseButton::Left,
..
} = event
{
if pos.x < 0.0 || pos.x > self.bounds.width || pos.y < 0.0 || pos.y > self.bounds.height
{
return EventResult::Ignored;
}
let total_h = self.bounds.height;
let widths = self.state.widths.borrow().clone();
let rows = self.state.rows.borrow();
let n = rows.count();
let y_td = total_h - pos.y;
let mut y_acc = 0.0;
for i in 0..n {
let h = rows.height_at(i);
if y_td >= y_acc && y_td <= y_acc + h {
let mut x = 0.0;
let mut col_hit = 0;
for (col, &cw) in widths.iter().enumerate() {
if pos.x >= x && pos.x < x + cw {
col_hit = col;
break;
}
x += cw;
}
drop(rows);
if let Some(cb) = self.state.on_row_click.borrow_mut().as_mut() {
cb(i, col_hit);
}
crate::animation::request_draw();
return EventResult::Consumed;
}
y_acc += h;
}
}
EventResult::Ignored
}
}