use fluidattacks_designs_domain::table::{
marks_selection, row_fill, tinted_text, RowFill, BORDER, CELL_DIVIDER, CELL_PADDING_X,
ROW_HEIGHT, ROW_HOVER_BG, SELECTED_MARKER, SELECTED_MARKER_PX, SELECTED_TINT,
SELECTED_TINT_ALPHA, SELECTION_PADDING, SELECTION_WIDTH, TEXT_SIZE,
};
use fluidattacks_designs_domain::typography::FONT_PRIMARY;
use gpui::prelude::FluentBuilder as _;
use gpui::{
div, px, relative, rgb, rgba, App, Div, Entity, InteractiveElement as _, MouseButton,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _, SharedString, Stateful,
Styled as _,
};
use crate::checkbox::Checkbox;
use crate::table::state::TableState;
use crate::theme::{ActiveTheme as _, Theme};
pub fn body_row(
state: &Entity<TableState>,
position: usize,
width: f32,
cx: &App,
) -> Stateful<Div> {
let id = crate::table::part(state, format!("tr-{position}"));
let read = state.read(cx);
let theme = cx.theme();
let hover_bg = theme.shade(ROW_HOVER_BG);
let Some(&row) = read.view().get(position) else {
return div().id(id).min_h(px(ROW_HEIGHT));
};
let checked = read.is_selected(row);
let selected = marks_selection(checked, read.checkboxes());
let fill = row_fill(read.row_color(row), selected);
let checkbox = read
.checkboxes()
.then(|| selection_cell(state, position, checked, theme));
let cells = shown_cells(read, row, theme);
div()
.id(id)
.flex()
.flex_none()
.w(px(width))
.min_w(relative(1.0))
.min_h(px(ROW_HEIGHT))
.border_1()
.border_color(rgb(theme.shade(BORDER)))
.map(|item| match fill {
RowFill::Tint(color) => {
let tint = theme.shade(color);
item.bg(rgb(tint)).text_color(rgb(tinted_text(tint)))
}
RowFill::Selection => item.bg(rgba(
theme.shade(SELECTED_TINT) << 8 | u32::from(SELECTED_TINT_ALPHA),
)),
RowFill::Plain => item,
})
.when(selected, |item| {
item.border_l(px(SELECTED_MARKER_PX))
.border_color(rgb(theme.shade(SELECTED_MARKER)))
})
.when(fill.hovers(), |item| {
item.hover(move |hovered| hovered.bg(rgb(hover_bg)))
})
.children(checkbox)
.children(cells)
.map(|item| selects(item, state, position))
}
fn selection_cell(
state: &Entity<TableState>,
position: usize,
checked: bool,
theme: &Theme,
) -> Div {
let toggling = state.clone();
div()
.flex()
.flex_none()
.items_center()
.w(px(SELECTION_WIDTH))
.pl(px(SELECTION_PADDING))
.border_r_1()
.border_color(rgb(theme.shade(CELL_DIVIDER)))
.on_mouse_down(MouseButton::Left, |_, _win, app| app.stop_propagation())
.child(
Checkbox::new(
crate::table::part(state, format!("pick-{position}")),
checked,
)
.on_change(move |_, _win, app| {
toggling.update(app, |table, inner| table.toggle_row(position, inner));
}),
)
}
fn selects(row: Stateful<Div>, state: &Entity<TableState>, position: usize) -> Stateful<Div> {
let pressing = state.clone();
let dragging = state.clone();
let releasing = state.clone();
row.on_mouse_down(
MouseButton::Left,
move |event: &MouseDownEvent, _win, app| {
let held = event.modifiers;
pressing.update(app, |table, inner| {
table.press_row(position, held.shift, held.secondary(), inner);
});
},
)
.on_mouse_move(move |event: &MouseMoveEvent, _win, app| {
let held = event.dragging();
dragging.update(app, |table, inner| {
table.drag_over_row(position, held, inner);
});
})
.on_mouse_up(MouseButton::Left, move |_: &MouseUpEvent, _win, app| {
releasing.update(app, |table, _| table.end_row_drag());
})
}
fn shown_cells(read: &TableState, row: usize, theme: &Theme) -> Vec<Div> {
read.order()
.iter()
.enumerate()
.filter_map(|(place, &column)| {
let entry = read.columns().get(column)?;
entry.visible.then(|| {
cell(
read.cell(row, column),
entry.width,
read.cell_color(row, column),
place == 0,
theme,
)
})
})
.collect()
}
fn cell(value: &str, width: f32, tint: Option<u32>, first: bool, theme: &Theme) -> Div {
div()
.flex()
.flex_none()
.items_center()
.w(px(width))
.px(px(CELL_PADDING_X))
.overflow_hidden()
.when(first, |item| {
item.border_r_1()
.border_color(rgb(theme.shade(CELL_DIVIDER)))
})
.when_some(tint, |item, color| {
let painted = theme.shade(color);
item.bg(rgb(painted)).text_color(rgb(tinted_text(painted)))
})
.font_family(FONT_PRIMARY)
.text_size(px(TEXT_SIZE.px()))
.line_height(px(TEXT_SIZE.line_height_px()))
.child(
div()
.flex_1()
.min_w_0()
.child(SharedString::from(value.to_owned())),
)
}