use std::cell::{Cell, RefCell};
use std::rc::Rc;
use teksilo_canvas::{Point, Rect, Size, SizeProposal};
use teksilo_core::accessibility::AccessNodeBuilder;
use teksilo_core::binding::BindingLevel;
use teksilo_core::build_context::BuildContext;
use teksilo_core::signal::Signal;
use teksilo_core::widget::{LayoutContext, Widget, WidgetPlacement};
use teksilo_core::widget_builder::HandlerSet;
use teksilo_core::widget_id::WidgetId;
use teksilo_data::{DragEligibility, RowState, SelectionMode};
use super::a11y::CellA11y;
use super::body::{BodyRow, SharedColumnWidths};
use super::column::{CellContext, Column};
use super::selection::{CellSelectionModel, TableSelectionMode};
use crate::common::row_metrics::SharedRowMetrics;
use crate::data_views::{RowSelection, ViewId, default_placeholder};
use crate::table_view::column::EditTriggers;
const BUFFER_ROWS: usize = 5;
pub(crate) type LenFn = Rc<dyn Fn() -> usize>;
pub(crate) type WithItemFn<T> = Rc<dyn Fn(usize, &dyn Fn(&T))>;
pub(crate) struct BodyPane<T: 'static> {
pub(crate) len_fn: LenFn,
pub(crate) with_item_fn: WithItemFn<T>,
pub(crate) drag_fn: Rc<dyn Fn(usize) -> DragEligibility>,
pub(crate) row_state_fn: Rc<dyn Fn(usize) -> RowState>,
pub(crate) columns: Vec<Column<T>>,
pub(crate) display_indices: Rc<RefCell<Vec<usize>>>,
pub(crate) column_widths: SharedColumnWidths,
pub(crate) pane_boundaries: super::PaneBoundaries,
pub(crate) scroll_x: Signal<f32>,
pub(crate) row_metrics: SharedRowMetrics,
pub(crate) selection_mode: TableSelectionMode,
pub(crate) selection: Option<RowSelection>,
pub(crate) cell_selection: Option<CellSelectionModel>,
pub(crate) scroll_y: Signal<f32>,
pub(crate) viewport_height: Rc<Cell<f32>>,
pub(crate) editing_cell: Signal<Option<(usize, usize)>>,
pub(crate) focused_cell: Signal<Option<(usize, usize)>>,
pub(crate) reorderable: bool,
pub(crate) export: crate::data_views::RowExport<T>,
pub(crate) snapshot_out_fn: crate::data_views::SnapshotOutFn,
pub(crate) anchor_fn: Rc<dyn Fn(usize) -> crate::data_views::RowAnchor>,
pub(crate) editing_anchor: Rc<RefCell<Option<crate::data_views::RowAnchor>>>,
pub(crate) view_id: ViewId,
pub(crate) on_row_activate: Option<Rc<dyn Fn(usize, &mut teksilo_core::widget::EventContext)>>,
pub(crate) activate_on: crate::data_views::ActivateOn,
pub(crate) edit_triggers: crate::table_view::EditTriggers,
pub(crate) on_cell_edit_request:
Option<Rc<dyn Fn(usize, &str, &mut teksilo_core::widget::EventContext)>>,
pub(crate) on_cell_edit_dismissed:
Option<Rc<dyn Fn(usize, &str, &mut teksilo_core::widget::EventContext)>>,
pub(crate) drag_anchor: WidgetId,
pub(crate) version: Signal<u64>,
pub(crate) total_refresh: Signal<u64>,
pub(crate) prev_built_start: Rc<Cell<usize>>,
pub(crate) prev_built_end: Rc<Cell<usize>>,
pub(crate) row_entries: Vec<(usize, WidgetId)>,
pub(crate) cell_map: Rc<RefCell<Vec<((usize, usize), WidgetId)>>>,
}
impl<T: 'static> BodyPane<T> {
fn visible_range(&self) -> (usize, usize) {
self.row_metrics.borrow_mut().visible_range(
self.scroll_y.get(),
self.viewport_height.get(),
(self.len_fn)(),
BUFFER_ROWS,
)
}
}
impl<T: 'static> std::fmt::Debug for BodyPane<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BodyPane")
.field("rows", &(self.len_fn)())
.field("columns", &self.columns.len())
.finish()
}
}
impl<T: 'static> Widget for BodyPane<T> {
fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
let anchor_fn = self.anchor_fn.clone();
crate::data_views::reconcile_editing_row(&self.editing_cell, &self.editing_anchor, &|i| {
anchor_fn(i)
});
let version = self.version.clone();
version.bind_to(ctx.self_id(), ctx.binding_registry(), BindingLevel::Rebuild);
self.scroll_y.bind_to(
ctx.self_id(),
ctx.binding_registry(),
BindingLevel::Relayout,
);
ctx.register_animated_signal(&self.scroll_y);
let len = self.len_fn.clone();
let vp_h = self.viewport_height.clone();
let (initial_start, initial_end) = self.visible_range();
self.prev_built_start.set(initial_start);
self.prev_built_end.set(initial_end);
let v_for_scroll = version.clone();
let scroll_handle = self.scroll_y.observe({
let pbs = self.prev_built_start.clone();
let pbe = self.prev_built_end.clone();
let metrics = self.row_metrics.clone();
move |y| {
let count = (len)();
let (visible_start, visible_end) =
metrics.borrow_mut().visible_range(*y, vp_h.get(), count, 0);
if visible_start < pbs.get() || visible_end > pbe.get() {
let new_start = visible_start.saturating_sub(BUFFER_ROWS);
let new_end = (visible_end + BUFFER_ROWS).min(count);
pbs.set(new_start);
pbe.set(new_end);
v_for_scroll.set(v_for_scroll.get() + 1);
}
}
});
ctx.own_handle(scroll_handle);
if let Some(ref sel) = self.selection {
let v = version.clone();
let counter = Rc::new(Cell::new(0_u64));
let handle = sel.observe_for_rebuild(move || {
counter.set(counter.get() + 1);
v.set(counter.get());
});
ctx.own_handle(handle);
}
if let Some(ref cs) = self.cell_selection {
let v = version.clone();
let counter = Rc::new(Cell::new(0_u64));
ctx.effect(&cs.selection_signal(), move |_| {
counter.set(counter.get() + 1);
v.set(counter.get());
});
}
let v_for_edit = version.clone();
let edit_counter = Rc::new(Cell::new(0_u64));
ctx.effect(&self.editing_cell, move |_| {
edit_counter.set(edit_counter.get() + 1);
v_for_edit.set(edit_counter.get());
});
self.row_entries.clear();
let mut cell_entries: Vec<((usize, usize), WidgetId)> = Vec::new();
let (start, end) = self.visible_range();
let columns = self.columns.clone();
let with_item_fn = self.with_item_fn.clone();
let display_indices = self.display_indices.borrow().clone();
let display_col_ids: Rc<Vec<String>> = Rc::new(
display_indices
.iter()
.map(|&i| columns[i].id.clone())
.collect(),
);
let editing_state = self.editing_cell.get();
let row_widths_handle = self.column_widths.clone();
let selection_mode = self.selection_mode;
let is_drag_source = self.export.is_drag_source(self.reorderable);
ctx.begin_view_focus_for(self.drag_anchor);
for row_idx in start..end {
let row_anchor = (self.anchor_fn)(row_idx);
let row_selected_for_a11y = match (selection_mode, &self.selection) {
(TableSelectionMode::SingleRow | TableSelectionMode::MultiRow, Some(s)) => {
s.is_selected(row_idx)
}
_ => false,
};
let resident = read_item_local(&with_item_fn, row_idx, |_| ()).is_some();
let loading = !resident && (self.row_state_fn)(row_idx) == RowState::Loading;
let mut cell_ids: Vec<WidgetId> = Vec::with_capacity(display_indices.len());
for (display_pos, &col_idx) in display_indices.iter().enumerate() {
let col = &columns[col_idx];
let is_editing = editing_state == Some((row_idx, display_pos));
let is_selected = match (selection_mode, &self.selection, &self.cell_selection) {
(TableSelectionMode::SingleRow | TableSelectionMode::MultiRow, Some(s), _) => {
s.is_selected(row_idx)
}
(
TableSelectionMode::SingleCell | TableSelectionMode::MultiCell,
_,
Some(cs),
) => cs.is_selected(row_idx, display_pos),
_ => false,
};
let is_focused = self.focused_cell.get() == Some((row_idx, display_pos));
let cell_ctx = CellContext {
row_index: row_idx,
col_id: col.id.clone(),
col_index: display_pos,
is_selected,
is_focused,
is_hovered: false,
is_editing,
depth: None,
is_tree_column: false,
};
let cell_widget = if loading {
Some(default_placeholder())
} else {
read_item_local(&with_item_fn, row_idx, |item| (col.cell)(item, &cell_ctx))
};
if let Some(widget) = cell_widget {
let inner_id = ctx.add_boxed(widget);
if is_editing {
ctx.focus_into(inner_id);
}
let cell_a11y = CellA11y::new(
inner_id,
row_idx + 2, display_pos + 1,
is_selected,
);
let cell_id = ctx.add(cell_a11y);
let focused_for_cell = self.focused_cell.clone();
let editing_for_cell = self.editing_cell.clone();
let cell_sel_for_click = self.cell_selection.clone();
let row_for_cell = row_idx;
let col_for_cell = display_pos;
let mode_for_cell = selection_mode;
let cell_handlers =
HandlerSet::new().on_pointer_event(move |event, _ctx| match event {
teksilo_core::event::WidgetEvent::PointerDown {
button: teksilo_core::event::PointerButton::Primary,
modifiers,
..
} => {
if editing_for_cell.get().is_some() {
return teksilo_core::event::EventResponse::Ignored;
}
focused_for_cell.set(Some((row_for_cell, col_for_cell)));
if let Some(ref cs) = cell_sel_for_click {
match mode_for_cell {
TableSelectionMode::SingleCell => {
cs.select(row_for_cell, col_for_cell);
}
TableSelectionMode::MultiCell => {
if modifiers.shift() {
cs.extend_to(row_for_cell, col_for_cell);
} else if modifiers.command() {
cs.toggle(row_for_cell, col_for_cell);
} else {
cs.select(row_for_cell, col_for_cell);
}
}
_ => {}
}
}
teksilo_core::event::EventResponse::Ignored
}
_ => teksilo_core::event::EventResponse::Ignored,
});
ctx.apply_handlers(cell_id, cell_handlers);
if let Some(edit_handlers) = cell_edit_handlers(
col.effective_edit_triggers(self.edit_triggers),
&self.on_cell_edit_request,
&self.editing_cell,
&row_anchor,
display_pos,
&col.id,
) {
ctx.apply_handlers(cell_id, edit_handlers);
}
if let Some(dismiss) = cell_edit_dismiss_handler(
&self.on_cell_edit_dismissed,
&self.editing_cell,
&display_col_ids,
&row_anchor,
display_pos,
) {
ctx.apply_handlers(cell_id, dismiss);
}
cell_entries.push(((row_idx, display_pos), cell_id));
cell_ids.push(cell_id);
}
}
let needs_measure = self.row_metrics.borrow().needs_measure();
let row_height = if needs_measure {
None
} else {
Some(self.row_metrics.borrow_mut().row_height(row_idx))
};
let row_widget = BodyRow::new(
cell_ids,
row_idx + 2,
row_selected_for_a11y,
row_height,
row_widths_handle.clone(),
self.pane_boundaries,
self.scroll_x.clone(),
);
let row_id = ctx.add(row_widget);
let mut row_handlers = HandlerSet::new();
if let Some(ref sel) = self.selection {
let click_anchor = row_anchor.clone();
let sel_for_click = sel.clone();
let editing_for_click = self.editing_cell.clone();
if matches!(
selection_mode,
TableSelectionMode::SingleRow | TableSelectionMode::MultiRow
) {
let pending_collapse = Rc::new(Cell::new(false));
row_handlers = row_handlers.on_pointer_event(move |event, ctx| match event {
teksilo_core::event::WidgetEvent::PointerDown {
button: teksilo_core::event::PointerButton::Primary,
modifiers,
..
} => {
if editing_for_click.get().is_some() {
return teksilo_core::event::EventResponse::Ignored;
}
if ctx.press_claimed_by_interactive_child() {
pending_collapse.set(false);
return teksilo_core::event::EventResponse::Ignored;
}
let Some(row_index_for_click) = click_anchor.index() else {
return teksilo_core::event::EventResponse::Ignored;
};
if modifiers.command() && sel_for_click.mode() == SelectionMode::Multi {
sel_for_click.toggle(row_index_for_click);
pending_collapse.set(false);
} else if modifiers.shift()
&& sel_for_click.mode() == SelectionMode::Multi
{
sel_for_click.extend_to(row_index_for_click);
pending_collapse.set(false);
} else if sel_for_click.is_selected(row_index_for_click) {
pending_collapse.set(true);
} else {
sel_for_click.select(row_index_for_click);
pending_collapse.set(false);
}
teksilo_core::event::EventResponse::Ignored
}
teksilo_core::event::WidgetEvent::PointerUp {
button: teksilo_core::event::PointerButton::Primary,
..
} => {
if ctx.press_claimed_by_interactive_child() {
return teksilo_core::event::EventResponse::Ignored;
}
if pending_collapse.replace(false)
&& let Some(row) = click_anchor.index()
{
sel_for_click.select(row);
}
teksilo_core::event::EventResponse::Ignored
}
_ => teksilo_core::event::EventResponse::Ignored,
});
}
}
if is_drag_source {
let drag_row = row_idx;
let view_id = self.view_id;
let anchor = self.drag_anchor;
let drag_gate = self.drag_fn.clone();
let with_item_for_preview = self.with_item_fn.clone();
let columns_for_preview = self.columns.clone();
let display_for_preview = self.display_indices.clone();
let widths_for_preview = self.column_widths.clone();
let metrics_for_preview = self.row_metrics.clone();
let sel_for_drag = self.selection.clone();
let export_for_drag = self.export.clone();
let with_item_for_drag = self.with_item_fn.clone();
let snapshot_for_drag = self.snapshot_out_fn.clone();
row_handlers = row_handlers.on_drag(move |phase, ctx| {
if let teksilo_core::gesture::DragPhase::Started { .. } = phase {
if (drag_gate)(drag_row) == DragEligibility::NoDrag {
return;
}
let rows: Vec<usize> = match sel_for_drag.as_ref() {
Some(s) if s.is_selected(drag_row) => {
let mut v = s.selected_indices();
v.sort_unstable();
if v.len() <= 1 { vec![drag_row] } else { v }
}
_ => vec![drag_row],
};
let read = |i: usize, f: &mut dyn FnMut(&T)| -> bool {
read_item_local(&with_item_for_drag, i, |t| f(t)).is_some()
};
let Some(payload) =
export_for_drag.build_payload(view_id, rows, &read, &snapshot_for_drag)
else {
return;
};
let display = display_for_preview.borrow().clone();
let cells: Vec<Box<dyn Widget>> =
read_item_local(&with_item_for_preview, drag_row, |item| {
display
.iter()
.enumerate()
.map(|(display_pos, &col_idx)| {
let col = &columns_for_preview[col_idx];
let cell_ctx = CellContext {
row_index: drag_row,
col_id: col.id.clone(),
col_index: display_pos,
is_selected: false,
is_focused: false,
is_hovered: false,
is_editing: false,
depth: None,
is_tree_column: false,
};
(col.cell)(item, &cell_ctx)
})
.collect::<Vec<_>>()
})
.unwrap_or_default();
if cells.is_empty() {
ctx.start_drag(anchor, payload);
return;
}
let widths = widths_for_preview.borrow().clone();
let h = metrics_for_preview.borrow_mut().row_height(drag_row);
let total_w = widths.iter().sum::<f32>().max(120.0);
let preview = Box::new(crate::drag_preview::DragPreview::new(
total_w,
h,
Box::new(CellRowPreview::new(cells, widths, h)),
)) as Box<dyn Widget>;
ctx.start_drag_with_preview(anchor, payload, preview);
}
});
}
if let Some(ref cb) = self.on_row_activate {
let cb = cb.clone();
let a = row_anchor.clone();
let handlers = match self.activate_on {
crate::data_views::ActivateOn::SingleClick => {
let a = a.clone();
HandlerSet::new().on_tap(move |_tap, ctx| {
if let Some(cur) = a.index() {
cb(cur, ctx)
}
})
}
crate::data_views::ActivateOn::DoubleClick => {
HandlerSet::new().on_double_tap(move |_tap, ctx| {
if let Some(cur) = a.index() {
cb(cur, ctx)
}
})
}
};
ctx.apply_handlers(row_id, handlers);
}
ctx.apply_handlers(row_id, row_handlers);
self.row_entries.push((row_idx, row_id));
}
ctx.end_view_focus();
*self.cell_map.borrow_mut() = cell_entries;
self.row_entries.iter().map(|(_, id)| *id).collect()
}
fn layout_response(
&self,
proposal: SizeProposal,
_ctx: &LayoutContext,
) -> teksilo_core::widget::LayoutResponse {
crate::common::viewport::viewport_size(
proposal,
&self.viewport_height,
Size::new(400.0, 300.0),
)
.into()
}
fn place_children(
&self,
bounds: Rect,
_proposal: SizeProposal,
children: &mut [WidgetPlacement],
ctx: &LayoutContext,
) {
crate::common::viewport::record_viewport_height(&self.viewport_height, bounds.height);
if self.row_metrics.borrow().needs_measure() {
let count = (self.len_fn)();
let pre_total = self.row_metrics.borrow_mut().total_height(count);
let mut measured = Vec::with_capacity(children.len());
for (i, child) in children.iter().enumerate() {
if let Some(size) = ctx.child_size(child.id, SizeProposal::with_width(bounds.width))
{
let (model_index, _) = self.row_entries[i];
measured.push((model_index, size.height));
}
}
let anchor = self
.row_metrics
.borrow_mut()
.observe_measured(&measured, self.scroll_y.get());
if anchor.abs() > 0.01 {
self.scroll_y.set((self.scroll_y.get() + anchor).max(0.0));
}
let (vs, ve) = self.row_metrics.borrow_mut().visible_range(
self.scroll_y.get(),
self.viewport_height.get(),
count,
0,
);
if vs < self.prev_built_start.get() || ve > self.prev_built_end.get() {
self.prev_built_start.set(vs.saturating_sub(BUFFER_ROWS));
self.prev_built_end.set((ve + BUFFER_ROWS).min(count));
self.version.set(self.version.get() + 1);
}
let post_total = self.row_metrics.borrow_mut().total_height(count);
if (post_total - pre_total).abs() > 0.01 {
self.total_refresh.set(self.total_refresh.get() + 1);
}
}
let scroll_y = self.scroll_y.get();
for (i, child) in children.iter_mut().enumerate() {
let (model_index, _) = self.row_entries[i];
let (top, height) = {
let mut m = self.row_metrics.borrow_mut();
(m.row_top(model_index), m.row_height(model_index))
};
let y = bounds.y + top - scroll_y;
child.origin = Point::new(bounds.x, y);
child.size = Size::new(bounds.width, height);
}
}
fn accessibility(&self, builder: &mut AccessNodeBuilder) {
builder.set_role(teksilo_core::accesskit::Role::RowGroup);
}
fn children(&self) -> Vec<WidgetId> {
self.row_entries.iter().map(|(_, id)| *id).collect()
}
fn clips_children(&self) -> bool {
true
}
}
fn read_item_local<T, R>(
with_item_fn: &WithItemFn<T>,
idx: usize,
f: impl FnOnce(&T) -> R,
) -> Option<R> {
let f_cell: Cell<Option<_>> = Cell::new(Some(f));
let slot: Cell<Option<R>> = Cell::new(None);
(with_item_fn)(idx, &|item: &T| {
if let Some(f) = f_cell.take() {
slot.set(Some(f(item)));
}
});
slot.into_inner()
}
pub(crate) struct CellRowPreview {
cells: Vec<Box<dyn Widget>>,
widths: Vec<f32>,
height: f32,
ids: Vec<WidgetId>,
}
impl CellRowPreview {
pub(crate) fn new(cells: Vec<Box<dyn Widget>>, widths: Vec<f32>, height: f32) -> Self {
Self {
cells,
widths,
height,
ids: Vec::new(),
}
}
}
impl std::fmt::Debug for CellRowPreview {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CellRowPreview")
.field("cells", &self.ids.len())
.finish()
}
}
impl Widget for CellRowPreview {
fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
self.ids = std::mem::take(&mut self.cells)
.into_iter()
.map(|w| ctx.add_boxed(w))
.collect();
self.ids.clone()
}
fn layout_response(
&self,
_proposal: SizeProposal,
_ctx: &LayoutContext,
) -> teksilo_core::widget::LayoutResponse {
Size::new(self.widths.iter().sum::<f32>().max(1.0), self.height).into()
}
fn place_children(
&self,
bounds: Rect,
_proposal: SizeProposal,
children: &mut [WidgetPlacement],
_ctx: &LayoutContext,
) {
let mut x = bounds.x;
for (i, child) in children.iter_mut().enumerate() {
let w = self.widths.get(i).copied().unwrap_or(0.0);
child.origin = Point::new(x, bounds.y);
child.size = Size::new(w, bounds.height);
x += w;
}
}
fn children(&self) -> Vec<WidgetId> {
self.ids.clone()
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn cell_edit_handlers(
triggers: EditTriggers,
request: &Option<Rc<dyn Fn(usize, &str, &mut teksilo_core::widget::EventContext)>>,
editing_cell: &Signal<Option<(usize, usize)>>,
row_anchor: &crate::data_views::RowAnchor,
display_pos: usize,
col_id: &str,
) -> Option<HandlerSet> {
let single = triggers.contains(EditTriggers::SINGLE_CLICK);
let double = triggers.contains(EditTriggers::DOUBLE_CLICK);
if !(single || double) {
return None;
}
let request = request.clone()?;
let open = {
let editing_cell = editing_cell.clone();
let anchor = row_anchor.clone();
let col_id = col_id.to_string();
move |ctx: &mut teksilo_core::widget::EventContext| {
if let Some(row) = anchor.index() {
editing_cell.set(Some((row, display_pos)));
request(row, &col_id, ctx);
}
}
};
let open = Rc::new(open);
let mut handlers = HandlerSet::new();
if single {
let open = open.clone();
handlers = handlers.on_tap(move |_tap, ctx| open(ctx));
}
if double {
if !single {
handlers = handlers.on_double_tap(move |_tap, ctx| open(ctx));
}
}
Some(handlers)
}
pub(crate) fn cell_edit_dismiss_handler(
dismissed: &Option<Rc<dyn Fn(usize, &str, &mut teksilo_core::widget::EventContext)>>,
editing_cell: &Signal<Option<(usize, usize)>>,
display_col_ids: &Rc<Vec<String>>,
row_anchor: &crate::data_views::RowAnchor,
display_pos: usize,
) -> Option<HandlerSet> {
let dismissed = dismissed.clone()?;
let editing_cell = editing_cell.clone();
let col_ids = display_col_ids.clone();
let anchor = row_anchor.clone();
Some(HandlerSet::new().on_pointer_event(move |event, ctx| {
if let teksilo_core::event::WidgetEvent::PointerDown {
button: teksilo_core::event::PointerButton::Primary,
..
} = event
&& let Some((edit_row, edit_col)) = editing_cell.get()
&& (edit_col != display_pos || anchor.index() != Some(edit_row))
&& let Some(col_id) = col_ids.get(edit_col)
{
dismissed(edit_row, col_id, ctx);
}
teksilo_core::event::EventResponse::Ignored
}))
}
pub(crate) fn root_edit_dismiss_handler(
dismissed: &Option<Rc<dyn Fn(usize, &str, &mut teksilo_core::widget::EventContext)>>,
editing_cell: &Signal<Option<(usize, usize)>>,
display_col_ids: &Rc<Vec<String>>,
) -> Option<HandlerSet> {
let dismissed = dismissed.clone()?;
let editing_cell = editing_cell.clone();
let col_ids = display_col_ids.clone();
Some(HandlerSet::new().on_pointer_event(move |event, ctx| {
if let teksilo_core::event::WidgetEvent::PointerDown {
button: teksilo_core::event::PointerButton::Primary,
..
} = event
&& !ctx.press_claimed_by_interactive_child()
&& let Some((edit_row, edit_col)) = editing_cell.get()
&& let Some(col_id) = col_ids.get(edit_col)
{
dismissed(edit_row, col_id, ctx);
}
teksilo_core::event::EventResponse::Ignored
}))
}