1use super::*;
9impl<T: 'static> Widget for TreeTableView<T> {
10 fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
11 let self_id = ctx.self_id();
12 ctx.enabled_when(self_id, self.enabled.clone());
13
14 self.adopt_style_heights(ctx);
15 let row_h = self.effective_row_height();
16 let header_h = self.effective_header_height();
17 let indent_per_level = self.effective_indent();
18
19 let version = ctx.signal(0_u64);
20 version.bind_to(ctx.self_id(), ctx.binding_registry(), BindingLevel::Rebuild);
21
22 self.scroll_y.bind_to(
23 ctx.self_id(),
24 ctx.binding_registry(),
25 BindingLevel::Relayout,
26 );
27 ctx.register_animated_signal(&self.scroll_y);
28
29 self.scroll_x.bind_to(
30 ctx.self_id(),
31 ctx.binding_registry(),
32 BindingLevel::Relayout,
33 );
34 ctx.register_animated_signal(&self.scroll_x);
35
36 self.pane_total_refresh.bind_to(
41 ctx.self_id(),
42 ctx.binding_registry(),
43 BindingLevel::Relayout,
44 );
45 self.reveal.bind(ctx);
49
50 self.column_widths_signal.bind_to(
51 ctx.self_id(),
52 ctx.binding_registry(),
53 BindingLevel::Relayout,
54 );
55 self.resize_preview_x.bind_to(
59 ctx.self_id(),
60 ctx.binding_registry(),
61 BindingLevel::RepaintOnly,
62 );
63 self.focused_cell.bind_to(
64 ctx.self_id(),
65 ctx.binding_registry(),
66 BindingLevel::RepaintOnly,
67 );
68 self.focused_cell.bind_to(
73 ctx.self_id(),
74 ctx.binding_registry(),
75 BindingLevel::AccessibilityOnly,
76 );
77
78 self.view_focused = ctx.begin_view_focus();
88 ctx.end_view_focus();
89 self.focus_visible = ctx.focus_visible();
90 crate::table_view::selection::follow_the_selection_in_single_mode(
91 ctx,
92 self.selection_mode,
93 &self.focused_cell,
94 self.row_selection.as_ref(),
95 self.cell_selection.as_ref(),
96 );
97 self.reveal_current_row_on_focus(ctx);
98 self.view_focused.bind_to(
99 ctx.self_id(),
100 ctx.binding_registry(),
101 BindingLevel::RepaintOnly,
102 );
103 self.focus_visible.bind_to(
104 ctx.self_id(),
105 ctx.binding_registry(),
106 BindingLevel::RepaintOnly,
107 );
108 self.drop_feedback.bind_to(
111 ctx.self_id(),
112 ctx.binding_registry(),
113 BindingLevel::RepaintOnly,
114 );
115
116 let v_for_proj = version.clone();
122 let proj_ver = Rc::new(Cell::new(0_u64));
123 let prev_visible_count = Rc::new(Cell::new(self.source.visible_count()));
124 ctx.effect(&self.source.version_signal(), {
125 let metrics = self.row_metrics.clone();
126 let src = self.source.clone();
127 let row_sel = self.row_selection.clone();
128 let cell_sel = self.cell_selection.clone();
129 let selection_mode = self.selection_mode;
130 let focused = self.focused_cell.clone();
131 let prev_visible_count = prev_visible_count.clone();
132 move |_| {
133 metrics
134 .borrow_mut()
135 .apply_divergence(src.first_changed_index(), src.visible_count());
136 if let Some(ref rs) = row_sel {
139 rs.prune();
140 }
141 let new_visible_count = src.visible_count();
155 if let Some(ref cs) = cell_sel
156 && new_visible_count != prev_visible_count.get()
157 {
158 cs.clear();
159 }
160 prev_visible_count.set(new_visible_count);
161 crate::table_view::selection::put_the_cursor_on_the_selection(
165 selection_mode,
166 &focused,
167 row_sel.as_ref(),
168 cell_sel.as_ref(),
169 );
170 let next = proj_ver.get() + 1;
171 proj_ver.set(next);
172 v_for_proj.set(next);
173 }
174 });
175
176 let v_for_sort = version.clone();
187 let sv = Rc::new(Cell::new(0_u64));
188 ctx.effect(&self.sort_signal, move |_| {
189 let next = sv.get() + 1;
190 sv.set(next);
191 v_for_sort.set(next);
192 });
193 let v_for_order = version.clone();
194 let ov = Rc::new(Cell::new(0_u64));
195 ctx.effect(&self.column_order_signal, move |_| {
196 let next = ov.get() + 1;
197 ov.set(next);
198 v_for_order.set(next);
199 });
200 let v_for_pin = version.clone();
201 let pv = Rc::new(Cell::new(0_u64));
202 ctx.effect(&self.column_pinning_signal, move |_| {
203 let next = pv.get() + 1;
204 pv.set(next);
205 v_for_pin.set(next);
206 });
207 let v_for_filter = version.clone();
210 let fv = Rc::new(Cell::new(0_u64));
211 ctx.effect(&self.filters_signal, move |_| {
212 let next = fv.get() + 1;
213 fv.set(next);
214 v_for_filter.set(next);
215 });
216 let display_indices = self.display_order();
223
224 {
231 let old_display = self.display_indices.borrow();
232 if !old_display.is_empty() {
233 let old_to_new: Vec<Option<usize>> = old_display
234 .iter()
235 .map(|&decl_idx| {
236 let id = &self.columns[decl_idx].spec.id;
237 display_indices
238 .iter()
239 .position(|&new_decl_idx| self.columns[new_decl_idx].spec.id == *id)
240 })
241 .collect();
242 drop(old_display);
243 imperative::remap_cell_state(
244 &self.focused_cell,
245 &self.editing_cell,
246 self.cell_selection.as_ref(),
247 &old_to_new,
248 );
249 }
250 }
251 *self.display_indices.borrow_mut() = display_indices.clone();
252 let tree_decl = self.tree_column_decl_index();
253 let tree_display_pos = display_indices
254 .iter()
255 .position(|&i| i == tree_decl)
256 .unwrap_or(0);
257
258 let line_height = row_h;
260
261 let column_ids_in_display_order: Vec<String> = display_indices
262 .iter()
263 .map(|&i| self.columns[i].spec.id.clone())
264 .collect();
265 let display_col_to_id: Rc<dyn Fn(usize) -> Option<String>> = {
266 let ids = column_ids_in_display_order;
267 Rc::new(move |pos| ids.get(pos).cloned())
268 };
269 let display_col_triggers: Rc<dyn Fn(usize) -> EditTriggers> = {
273 let view_triggers = self.edit_triggers;
274 let per_display_column: Vec<EditTriggers> = display_indices
275 .iter()
276 .map(|&i| self.columns[i].effective_edit_triggers(view_triggers))
277 .collect();
278 Rc::new(move |pos| {
279 per_display_column
280 .get(pos)
281 .copied()
282 .unwrap_or(EditTriggers::NONE)
283 })
284 };
285
286 let full_width = self.full_width_rows();
289 let pinned_reveal = self.pinned_reveal();
290 let navigator: Rc<dyn RowNavigator> = Rc::new(
291 TreeNavigator::new(self.source.clone())
292 .with_full_width(full_width.as_ref().map(|fw| fw.at.clone()))
293 .with_pinned(pinned_reveal.clone()),
294 );
295 let type_ahead_label: Option<Rc<dyn Fn(usize) -> Option<String>>> =
298 self.type_ahead_label.clone().map(|user| {
299 let src = self.source.clone();
300 Rc::new(move |i: usize| src.with_row_str(i, &|item| user(item)))
301 as Rc<dyn Fn(usize) -> Option<String>>
302 });
303
304 let key_cfg = keyboard::KeyHandlerConfig {
305 navigator,
306 col_count: display_indices.len().max(1),
307 tree_column_display_pos: tree_display_pos,
312 focused_cell: self.focused_cell.clone(),
313 cell_map: self.cell_map.clone(),
314 selection_mode: self.selection_mode,
315 selection: self.row_selection.clone(),
316 cell_selection: self.cell_selection.clone(),
317 scroll_y: self.scroll_y.clone(),
318 max_scroll_y: self.max_scroll_y.clone(),
319 viewport_height: self.viewport_height.clone(),
320 body_bounds: self.body_bounds.clone(),
321 row_metrics: self.row_metrics.clone(),
322 tab_traversal: self.tab_traversal,
323 editing_cell: self.editing_cell.clone(),
324 display_col_to_id,
325 display_col_triggers,
326 on_cell_edit_request: self.on_cell_edit_request.clone(),
327 on_row_activate: self.on_row_activate.clone(),
328 type_ahead: self.type_ahead.clone(),
329 type_ahead_label,
330 type_ahead_timeout: self.type_ahead_timeout,
331 column_widths: self.column_widths.clone(),
332 pane_boundaries: *self.pane_boundaries.borrow(),
333 scroll_x: self.scroll_x.clone(),
334 max_scroll_x: self.max_scroll_x.clone(),
335 middle_viewport_width: self.middle_viewport_width.clone(),
336 };
337
338 let sorted_here: Rc<dyn Fn() -> bool> = {
352 let sort = self.sort_signal.clone();
353 let own: Vec<String> = self.columns.iter().map(|c| c.spec.id.clone()).collect();
354 Rc::new(move || sort.get().is_some_and(|(id, _)| own.contains(&id)))
355 };
356 let (reorder_perform, reparent_perform) = if self.reorderable {
357 let follow: Rc<dyn Fn(usize)> = {
358 let focused = self.focused_cell.clone();
359 let sel = self.row_selection.clone();
360 Rc::new(move |new_flat: usize| {
361 let col = focused.get().map(|(_, c)| c).unwrap_or(0);
362 focused.set(Some((new_flat, col)));
363 if let Some(ref s) = sel {
364 s.select(new_flat);
365 }
366 })
367 };
368 let name_of: Rc<dyn Fn(usize) -> Option<String>> = {
369 let source = self.source.clone();
370 let label = self.type_ahead_label.clone();
371 Rc::new(move |index: usize| {
372 let label = label.as_ref()?;
373 source.with_row_str(index, &|item| label(item))
374 })
375 };
376 let sibling = {
377 let source = self.source.clone();
378 let follow = follow.clone();
379 let name_of = name_of.clone();
380 let sorted = sorted_here.clone();
381 Rc::new(
382 move |mv: crate::common::ordered_move::OrderedMove,
383 flat: usize,
384 ctx: &mut EventContext| {
385 if sorted() {
386 return;
387 }
388 let name = (name_of)(flat);
389 let Some(new_flat) = source.sibling_move(flat, mv) else {
390 return;
391 };
392 follow(new_flat);
393 let (pos, size) = source.sibling_position(new_flat);
394 ctx.announce(crate::common::ordered_move::move_announcement(
395 name.as_deref(),
396 pos.saturating_sub(1),
397 size,
398 ));
399 },
400 ) as crate::common::ordered_move::MoveRow
401 };
402 let reparent = {
403 let source = self.source.clone();
404 let follow = follow.clone();
405 let sorted = sorted_here.clone();
406 Rc::new(
407 move |mv: crate::common::ordered_move::TreeMove,
408 flat: usize,
409 ctx: &mut EventContext| {
410 if sorted() {
411 return;
412 }
413 let name = (name_of)(flat);
414 let Some(new_flat) = source.reparent(flat, mv) else {
415 return;
416 };
417 follow(new_flat);
418 let level = source.meta(new_flat).map_or(1, |m| m.depth + 1);
419 ctx.announce(crate::common::ordered_move::reparent_announcement(
420 name.as_deref(),
421 level,
422 ));
423 },
424 ) as crate::common::ordered_move::TreeReparentRow
425 };
426 (Some(sibling), Some(reparent))
427 } else {
428 (None, None)
429 };
430
431 {
437 let cfg = key_cfg.clone();
438 crate::common::standard_edit::register_standard_edit_action(
439 ctx,
440 teksilo_core::shortcut::StandardEdit::SelectAll,
441 move |_ctx| keyboard::run_select_all(&cfg),
442 );
443 }
444 let mut shared_key = keyboard::build_key_handler(key_cfg);
445 let reorder_key = reorder_perform.clone();
446 let reparent_key = reparent_perform.clone();
447 let focused_kbd = self.focused_cell.clone();
448 let sel_kbd = self.row_selection.clone();
449 let key_handler = move |event: &teksilo_core::event::WidgetEvent,
450 ctx: &mut EventContext|
451 -> EventResponse {
452 use teksilo_core::event::WidgetEvent;
453 if let Some(ref sibling) = reorder_key
454 && let Some(ref reparent) = reparent_key
455 && let WidgetEvent::KeyDown { key, modifiers, .. } = event
456 && (modifiers.alt() || modifiers.command())
459 {
460 let row = focused_kbd.get().map(|(r, _)| r).or_else(|| {
461 sel_kbd
462 .as_ref()
463 .and_then(|s| s.selected_indices().first().copied())
464 });
465 if let Some(flat_idx) = row {
466 if let Some(mv) = crate::common::ordered_move::OrderedMove::from_key(
467 *key,
468 *modifiers,
469 crate::common::ordered_move::MoveAxis::Vertical,
470 ctx.is_rtl(),
471 ) {
472 sibling(mv, flat_idx, ctx);
473 return EventResponse::Handled;
474 }
475 if let Some(mv) = crate::common::ordered_move::TreeMove::from_key(
476 *key,
477 *modifiers,
478 ctx.is_rtl(),
479 ) {
480 reparent(mv, flat_idx, ctx);
481 return EventResponse::Handled;
482 }
483 }
484 }
485 shared_key(event, ctx)
486 };
487
488 let mut handlers = HandlerSet::new()
493 .on_key(key_handler)
494 .clips_children(true)
495 .focusable(true);
496 {
497 use crate::common::scrollable::{
498 ScrollableAxes, ScrollableBehavior, handle_scroll_event, shift_wheel_remap,
499 };
500 let axes = ScrollableAxes::new(
501 self.scroll_x.clone(),
502 self.scroll_y.clone(),
503 self.max_scroll_x.clone(),
504 self.max_scroll_y.clone(),
505 );
506 let behavior = ScrollableBehavior::new(axes.clone())
507 .with_scroller(self.scroller.clone())
508 .axes(PanAxes::BOTH)
509 .overscroll(self.overscroll_behavior)
510 .smooth(self.smooth_scrolling)
511 .smooth_duration(self.smooth_scroll_duration)
512 .line_height(line_height)
513 .reduced_motion(ctx.prefers_reduced_motion())
514 .physics(ctx.theme().input.scroll_physics);
515 let scroller = behavior.scroller();
520 let options = behavior.options();
521 let behavior = behavior.before(move |event, ctx| {
529 shift_wheel_remap(event, ctx)
530 .map(|remapped| handle_scroll_event(&remapped, &axes, &scroller, &options, ctx))
531 });
532 handlers = behavior.install(handlers);
533 }
534
535 if self.export.is_drop_target(self.reorderable) || self.on_foreign_drop.is_some() {
555 let my_model_id = self.model_id;
556 let source_for_hover = self.source.clone();
557 let metrics_for_hover = self.row_metrics.clone();
558 let scroll_for_hover = self.scroll_y.clone();
559 let header_h_for_hover = header_h;
560 let feedback_for_hover = self.drop_feedback.clone();
561 let sorted_for_hover = sorted_here.clone();
562 let reorderable_hover = self.reorderable;
563 let export_for_hover = self.export.clone();
564 let has_foreign_hook_hover = self.on_foreign_drop.is_some();
565 let bounds_for_hover = self.body_bounds.clone();
566 let pinned_for_hover = (self.pinned_layout.clone(), self.pinned_built.clone());
567 handlers = handlers.on_drag_hover(move |payload, position, ctx| {
568 if payload.has_typed::<ColumnReorderDragData>() {
576 feedback_for_hover.set(None);
577 return teksilo_core::DropFeedback::NoFeedback;
578 }
579 let viz_width = bounds_for_hover.get().width.max(1.0);
582 let count = source_for_hover.visible_count();
583 if count == 0 {
584 feedback_for_hover.set(None);
585 return teksilo_core::DropFeedback::NoFeedback;
586 }
587 let rd = payload.get_typed::<RowDragData<T>>();
588 let is_same_view = rd.is_some_and(|r| r.source == my_model_id);
589 let reorder_ok = is_same_view && reorderable_hover && !sorted_for_hover();
590 let foreign_ok = !is_same_view
594 && (has_foreign_hook_hover
595 || export_for_hover.accepts_foreign_export(payload, my_model_id));
596 if !reorder_ok && !foreign_ok {
597 feedback_for_hover.set(None);
598 return teksilo_core::DropFeedback::NoFeedback;
599 }
600 let y = position.y - header_h_for_hover;
601 let spot = DropSpot::at(
602 &metrics_for_hover,
603 &pinned_for_hover,
604 count,
605 scroll_for_hover.get().max(0.0),
606 y,
607 );
608 let row_idx = spot.row;
609 let drop_pos = crate::common::drop_bands::drop_position_in_row(
615 y - spot.top,
616 spot.height,
617 ctx.pointer_kind(),
618 );
619 let (effective, depth) = if reorder_ok {
627 match (source_for_hover.dnd.can_accept_fn)(
628 payload,
629 row_idx,
630 drop_pos,
631 my_model_id,
632 ) {
633 DropResponse::Reject => {
634 if !foreign_ok {
635 feedback_for_hover.set(None);
636 return teksilo_core::DropFeedback::NoFeedback;
637 }
638 (DropPosition::Before, 0)
639 }
640 DropResponse::Accept => (drop_pos, source_for_hover.depth(row_idx)),
641 DropResponse::Redirect(p) => (p, source_for_hover.depth(row_idx)),
642 }
643 } else {
644 (DropPosition::Before, 0)
646 };
647 if effective == DropPosition::Into {
648 feedback_for_hover.set(Some(DropViz::Rect {
649 top: spot.top,
650 height: spot.height,
651 width: viz_width,
652 depth,
653 }));
654 teksilo_core::DropFeedback::HighlightRect {
655 rect: Rect::new(0.0, spot.top, viz_width, spot.height),
656 color: drop_into_tint(),
657 }
658 } else {
659 let insertion_y = spot.insertion_y;
660 feedback_for_hover.set(Some(DropViz::Line {
661 y: insertion_y,
662 width: viz_width,
663 depth,
664 }));
665 teksilo_core::DropFeedback::InsertionLine {
666 y: insertion_y,
667 width: viz_width,
668 }
669 }
670 });
671
672 let drop_model_id = self.model_id;
673 let source_for_drop = self.source.clone();
674 let metrics_for_drop = self.row_metrics.clone();
675 let scroll_for_drop = self.scroll_y.clone();
676 let header_h_for_drop = header_h;
677 let feedback_for_drop = self.drop_feedback.clone();
678 let sorted_for_drop = sorted_here.clone();
679 let reorderable_drop = self.reorderable;
680 let on_foreign_for_drop = self.on_foreign_drop.clone();
681 let proxy_for_foreign_hook = self.proxy.clone();
682 let export_for_drop = self.export.clone();
683 let pinned_for_drop = (self.pinned_layout.clone(), self.pinned_built.clone());
684 handlers = handlers.on_drop(move |mut payload, position, ctx| {
685 feedback_for_drop.set(None);
686 if payload.has_typed::<ColumnReorderDragData>() {
690 return false;
691 }
692 let count = source_for_drop.visible_count();
693 if count == 0 {
694 return false;
695 }
696 let y = position.y - header_h_for_drop;
697 let spot = DropSpot::at(
698 &metrics_for_drop,
699 &pinned_for_drop,
700 count,
701 scroll_for_drop.get().max(0.0),
702 y,
703 );
704 let (flat_idx, ins) = (spot.row, spot.insertion);
705 let drop_pos = crate::common::drop_bands::drop_position_in_row(
711 y - spot.top,
712 spot.height,
713 ctx.pointer_kind(),
714 );
715 let is_same_view = payload
716 .get_typed::<RowDragData<T>>()
717 .is_some_and(|rd| rd.source == drop_model_id);
718 if is_same_view && (!reorderable_drop || sorted_for_drop()) {
719 return false;
720 }
721 if (reorderable_drop || !is_same_view)
726 && (source_for_drop.dnd.accept_drop_fn)(
727 &payload,
728 flat_idx,
729 drop_pos,
730 drop_model_id,
731 )
732 {
733 if is_same_view {
734 export_for_drop.note_self_reorder();
735 }
736 return true;
737 }
738 if export_for_drop.foreign_receive(&mut payload, drop_model_id, ins, ctx) {
741 return true;
742 }
743 if let Some(ref hook) = on_foreign_for_drop
747 && let Some(ref p) = proxy_for_foreign_hook
748 && let Some(node) = p.visible_node_id(flat_idx)
749 {
750 return hook(&payload, node, drop_pos, ctx);
751 }
752 false
753 });
754
755 let feedback_for_leave = self.drop_feedback.clone();
756 handlers = handlers.on_drag_leave(move |_ctx| {
757 feedback_for_leave.set(None);
758 });
759
760 let scroll_for_tick = self.scroll_y.clone();
761 let max_scroll_for_tick = self.max_scroll_y.clone();
762 let viewport_for_tick = self.viewport_height.clone();
763 let header_h_for_tick = header_h;
764 handlers = handlers.on_drag_tick(move |pos, ctx| {
765 let body_h = (viewport_for_tick.get() - header_h_for_tick).max(0.0);
768 let y = pos.y - header_h_for_tick;
769 let band = crate::common::drag_autoscroll::band_for(ctx.pointer_kind());
770 let delta = crate::common::drag_autoscroll::step(y, body_h, band);
771 if delta.abs() > 0.01 {
772 let max = max_scroll_for_tick.get();
773 let new_y = (scroll_for_tick.get() + delta).clamp(0.0, max);
774 scroll_for_tick.set(new_y);
775 }
776 });
777 }
778
779 handlers = self.export.install_completion(handlers);
789
790 ctx.apply_self_handlers(handlers);
791
792 self.header_row_id = None;
795 self.body_pane_id = None;
796 self.scrollbar_id = None;
797 self.h_scrollbar_id = None;
798 self.empty_id = None;
799 self.pinned_stack_id = None;
800
801 if self.show_header {
808 let header = TableHeader::new(self.columns.iter().map(|c| c.spec.clone()).collect())
809 .widths(self.column_widths_signal.clone())
810 .sort(self.sort_signal.clone())
811 .order(self.column_order_signal.clone())
812 .pinning(self.column_pinning_signal.clone())
813 .filters(self.filters_signal.clone())
814 .scroll_x(self.scroll_x.clone())
815 .resize_policy(self.column_resize_policy)
816 .stretch_last_column(self.stretch_last_column)
817 .hosted(HeaderLink {
818 table_id: self.table_id,
819 widths: self.column_widths.clone(),
820 display_indices: self.display_indices.clone(),
821 pane_boundaries: self.pane_boundaries.clone(),
822 strip_width: self.header_strip_width.clone(),
823 resize_state: self.resize_state.clone(),
824 resize_target: self.resize_target.clone(),
825 resize_preview_x: self.resize_preview_x.clone(),
826 frozen_widths: self.column_widths_frozen.clone(),
827 });
828 self.header_row_id = Some(ctx.add(header));
829 }
830
831 let row_count = self.source.visible_count();
836
837 let (vis_start, vis_end) = self.visible_range();
845 (self.source.dnd.request_window_fn)(vis_start..vis_end);
846 if (self.source.dnd.can_fetch_more_fn)() && vis_end + BUFFER_ROWS >= row_count {
847 (self.source.dnd.fetch_more_fn)();
848 }
849
850 if row_count > 0 {
851 let pane = body_pane::TreeBodyPane::<T> {
852 source: self.source.clone(),
853 editing_anchor: self.editing_anchor.clone(),
854 columns: self.columns.clone(),
855 display_indices: self.display_indices.clone(),
856 column_widths: self.column_widths.clone(),
857 pane_boundaries: *self.pane_boundaries.borrow(),
858 scroll_x: self.scroll_x.clone(),
859 tree_display_pos,
860 indent_per_level,
861 full_width: full_width.clone(),
862 pinned_reveal: pinned_reveal.clone(),
863 row_metrics: self.row_metrics.clone(),
864 selection_mode: self.selection_mode,
865 selection: self.row_selection.clone(),
866 cell_selection: self.cell_selection.clone(),
867 scroll_y: self.scroll_y.clone(),
868 viewport_height: self.viewport_height.clone(),
869 editing_cell: self.editing_cell.clone(),
870 focused_cell: self.focused_cell.clone(),
871 reorderable: self.reorderable,
872 reorder_perform: reorder_perform.clone(),
873 reparent_perform: reparent_perform.clone(),
874 model_id: self.model_id,
875 export: self.export.clone(),
876 drag_anchor: ctx.self_id(),
877 on_row_activate: self.on_row_activate.clone(),
878 activate_on: self.activate_on,
879 edit_triggers: self.edit_triggers,
880 on_cell_edit_request: self.on_cell_edit_request.clone(),
881 on_cell_edit_dismissed: self.on_cell_edit_dismissed.clone(),
882 version: self.pane_version.clone(),
883 prev_built_start: self.pane_built_start.clone(),
884 prev_built_end: self.pane_built_end.clone(),
885 total_refresh: self.pane_total_refresh.clone(),
886 row_entries: Vec::new(),
887 row_roots: Vec::new(),
888 row_map: self.row_map.clone(),
889 cell_map: self.cell_map.clone(),
890 reveal: self.reveal.clone(),
891 };
892 self.body_pane_id = Some(ctx.add(pane));
893 if let Some(ref pinned_reveal) = pinned_reveal {
894 self.pinned_stack_id = Some(self.add_pinned_stack(
895 ctx,
896 &display_indices,
897 tree_display_pos,
898 indent_per_level,
899 full_width.clone(),
900 pinned_reveal.clone(),
901 ));
902 }
903 if let Some(handlers) = crate::table_view::body_pane::root_edit_dismiss_handler(
907 &self.on_cell_edit_dismissed,
908 &self.editing_cell,
909 &Rc::new(
910 display_indices
911 .iter()
912 .map(|&i| self.columns[i].spec.id.clone())
913 .collect::<Vec<_>>(),
914 ),
915 ) {
916 ctx.apply_self_handlers(handlers);
917 }
918 } else if let Some(ref f) = self.empty_view {
919 self.empty_id = Some(ctx.add_boxed(f()));
921 }
922
923 if self.show_internal_scrollbars {
925 let sb = ScrollBar::new(
926 ScrollBarOrientation::Vertical,
927 self.scroll_y.clone(),
928 self.max_scroll_y.clone(),
929 self.viewport_ratio_y.clone(),
930 )
931 .visual(match self.scroll_bar_style {
932 ScrollBarMode::Permanent => ScrollBarVisual::Permanent,
933 ScrollBarMode::Overlay => ScrollBarVisual::Overlay,
934 ScrollBarMode::Thin => ScrollBarVisual::Thin,
935 });
936 self.scrollbar_id = Some(ctx.add(sb));
937
938 let hsb = ScrollBar::new(
940 ScrollBarOrientation::Horizontal,
941 self.scroll_x.clone(),
942 self.max_scroll_x.clone(),
943 self.viewport_ratio_x.clone(),
944 )
945 .visual(match self.scroll_bar_style {
946 ScrollBarMode::Permanent => ScrollBarVisual::Permanent,
947 ScrollBarMode::Overlay => ScrollBarVisual::Overlay,
948 ScrollBarMode::Thin => ScrollBarVisual::Thin,
949 });
950 self.h_scrollbar_id = Some(ctx.add(hsb));
951 }
952
953 let mut children: Vec<WidgetId> = Vec::new();
957 if let Some(id) = self.body_pane_id {
958 children.push(id);
959 }
960 if let Some(id) = self.empty_id {
961 children.push(id);
962 }
963 if let Some(id) = self.pinned_stack_id {
966 children.push(id);
967 }
968 if let Some(id) = self.scrollbar_id {
969 children.push(id);
970 }
971 if let Some(id) = self.h_scrollbar_id {
972 children.push(id);
973 }
974 if let Some(id) = self.header_row_id {
975 children.push(id);
976 }
977 let _ = (header_h, row_h);
978 children
979 }
980
981 fn layout_response(
982 &self,
983 proposal: SizeProposal,
984 _ctx: &LayoutContext,
985 ) -> teksilo_core::widget::LayoutResponse {
986 let size = crate::common::viewport::viewport_size(
990 proposal,
991 &self.viewport_height,
992 Size::new(400.0, 300.0),
993 );
994 if proposal.height.is_some() {
995 self.laid_out.set(true);
999 }
1000 size.into()
1001 }
1002
1003 fn place_children(
1004 &self,
1005 bounds: Rect,
1006 _proposal: SizeProposal,
1007 children: &mut [WidgetPlacement],
1008 ctx: &LayoutContext,
1009 ) {
1010 self.scroller
1014 .borrow_mut()
1015 .set_viewport(teksilo_canvas::Vec2::new(bounds.width, bounds.height));
1016 if children.is_empty() {
1017 return;
1018 }
1019 let rtl = ctx.is_rtl();
1020 let header_h = self.effective_header_height();
1021 let body_height_provisional = (bounds.height - header_h).max(0.0);
1022
1023 let total_height = self
1027 .row_metrics
1028 .borrow_mut()
1029 .total_height(self.source.visible_count());
1030 let needs_v_scrollbar =
1031 self.show_internal_scrollbars && total_height > body_height_provisional + 0.5;
1032 let reserves_v_bar = needs_v_scrollbar && self.scroll_bar_style == ScrollBarMode::Permanent;
1035 let body_width = if reserves_v_bar {
1036 (bounds.width - SCROLLBAR_THICKNESS).max(0.0)
1037 } else {
1038 bounds.width
1039 };
1040 let band_left = if rtl && reserves_v_bar {
1044 bounds.x + SCROLLBAR_THICKNESS
1045 } else {
1046 bounds.x
1047 };
1048 let scrollbar_x = if rtl {
1049 bounds.x
1050 } else {
1051 bounds.x + bounds.width - SCROLLBAR_THICKNESS
1052 };
1053 self.header_strip_width.set(body_width);
1056
1057 let overrides = width_overrides(
1058 self.column_widths_signal.get(),
1059 self.column_widths_frozen.as_ref(),
1060 self.columns.iter().map(|c| c.spec.id.as_str()),
1061 );
1062 let display = self.display_indices.borrow().clone();
1063 let widths = layout::ColumnSolver::resolve_in_order(
1064 &self.columns,
1065 &display,
1066 body_width,
1067 cp::MIN_COLUMN_WIDTH_DEFAULT,
1068 &overrides,
1069 self.stretch_last_column,
1070 );
1071
1072 let boundaries = *self.pane_boundaries.borrow();
1074 let (leading_w, middle_content_w, trailing_w) = layout::pane_widths(&widths, boundaries);
1075 let middle_viewport_w = (body_width - leading_w - trailing_w).max(0.0);
1076 let max_x = (middle_content_w - middle_viewport_w).max(0.0);
1077 self.max_scroll_x.set(max_x);
1078 self.middle_viewport_width.set(middle_viewport_w);
1079 let x_ratio = if middle_content_w > 0.0 {
1080 (middle_viewport_w / middle_content_w).clamp(0.0, 1.0)
1081 } else {
1082 1.0
1083 };
1084 self.viewport_ratio_x.set(x_ratio);
1085 {
1086 let current = self.scroll_x.get();
1087 let clamped = current.clamp(0.0, max_x);
1088 if (clamped - current).abs() > 0.001 {
1089 self.scroll_x.set(clamped);
1090 }
1091 }
1092
1093 *self.column_widths.borrow_mut() = widths;
1094
1095 let needs_h_scrollbar = self.show_internal_scrollbars && max_x > 0.5;
1096 let reserves_h_bar = needs_h_scrollbar && self.scroll_bar_style == ScrollBarMode::Permanent;
1097 let body_height = if reserves_h_bar {
1098 (body_height_provisional - SCROLLBAR_THICKNESS).max(0.0)
1099 } else {
1100 body_height_provisional
1101 };
1102
1103 let max_y = (total_height - body_height).max(0.0);
1104 self.max_scroll_y.set(max_y);
1105 let y_ratio = if total_height > 0.0 {
1106 (body_height / total_height).clamp(0.0, 1.0)
1107 } else {
1108 1.0
1109 };
1110 self.viewport_ratio_y.set(y_ratio);
1111 self.clamp_scroll();
1112
1113 let body_origin_y = bounds.y + header_h;
1114 self.body_bounds
1116 .set(Rect::new(band_left, body_origin_y, body_width, body_height));
1117
1118 let pinned_extent = if self.pinned_stack_id.is_some() {
1122 let lines = {
1123 let mut metrics = self.row_metrics.borrow_mut();
1124 pinned::stack_at(
1125 &*self.source,
1126 &mut metrics,
1127 self.scroll_y.get(),
1128 self.pinned_ancestors,
1129 )
1130 };
1131 let rows: Vec<usize> = lines.iter().map(|line| line.row).collect();
1132 if self.pinned_rows.get() != rows {
1133 self.pinned_rows.set(rows);
1134 }
1135 let shown = pinned::shown_lines(&lines, &self.pinned_built.borrow());
1139 let extent = lines[..shown]
1140 .iter()
1141 .map(|line| line.top + line.height)
1142 .fold(0.0_f32, f32::max);
1143 *self.pinned_layout.borrow_mut() = lines;
1144 extent.min(body_height)
1145 } else {
1146 0.0
1147 };
1148
1149 let mut next = 0;
1150
1151 if self.body_pane_id.is_none() {
1155 self.reveal.clear();
1156 }
1157
1158 if self.body_pane_id.is_some() {
1161 if let Some(child) = children.get_mut(next) {
1162 child.origin = Point::new(band_left, body_origin_y);
1163 child.size = Size::new(body_width, body_height);
1164 }
1165 next += 1;
1166 }
1167
1168 if self.empty_id.is_some() {
1170 if let Some(child) = children.get_mut(next) {
1171 child.origin = Point::new(band_left, body_origin_y);
1172 child.size = Size::new(body_width, body_height);
1173 }
1174 next += 1;
1175 }
1176
1177 if self.pinned_stack_id.is_some() {
1180 if let Some(child) = children.get_mut(next) {
1181 child.origin = Point::new(band_left, body_origin_y);
1182 child.size = if pinned_extent > 0.0 {
1183 Size::new(body_width, pinned_extent)
1184 } else {
1185 Size::ZERO
1186 };
1187 }
1188 next += 1;
1189 }
1190
1191 if self.scrollbar_id.is_some() {
1193 if let Some(child) = children.get_mut(next) {
1194 if needs_v_scrollbar {
1195 child.origin = Point::new(scrollbar_x, body_origin_y);
1196 child.size = Size::new(SCROLLBAR_THICKNESS, body_height);
1197 } else {
1198 child.origin = bounds.origin();
1199 child.size = Size::ZERO;
1200 }
1201 }
1202 next += 1;
1203 }
1204
1205 if self.h_scrollbar_id.is_some() {
1207 if let Some(child) = children.get_mut(next) {
1208 if needs_h_scrollbar {
1209 let h_x = if rtl {
1210 band_left + trailing_w
1211 } else {
1212 band_left + leading_w
1213 };
1214 child.origin = Point::new(h_x, body_origin_y + body_height);
1215 child.size = Size::new(middle_viewport_w, SCROLLBAR_THICKNESS);
1216 } else {
1217 child.origin = bounds.origin();
1218 child.size = Size::ZERO;
1219 }
1220 }
1221 next += 1;
1222 }
1223
1224 if self.header_row_id.is_some()
1227 && let Some(child) = children.get_mut(next)
1228 {
1229 child.origin = Point::new(band_left, bounds.y);
1230 child.size = Size::new(body_width, header_h);
1231 }
1232 }
1233
1234 fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
1235 let header_h = self.effective_header_height();
1236 let colors = &ctx.theme.colors;
1237 let scroll_y = self.scroll_y.get();
1238 let body_origin_y = bounds.y + header_h;
1239 let body_height = (bounds.height - header_h).max(0.0);
1240 let widths = self.column_widths.borrow();
1241 let body_width = widths.iter().sum::<f32>();
1242 let body_width_for_paint = if body_width > 0.0 {
1243 body_width.min(bounds.width)
1244 } else {
1245 bounds.width
1246 };
1247 let rtl = ctx.layout_direction == teksilo_core::environment::LayoutDirection::RightToLeft;
1249 let content_left = if rtl {
1250 bounds.x + bounds.width - body_width_for_paint
1251 } else {
1252 bounds.x
1253 };
1254
1255 let row_count = self.source.visible_count();
1258 let (first_visible, last_visible) =
1259 self.row_metrics
1260 .borrow_mut()
1261 .visible_range(scroll_y, body_height, row_count, 0);
1262
1263 canvas.set_clip(Rect::new(
1269 content_left,
1270 body_origin_y,
1271 body_width_for_paint,
1272 body_height,
1273 ));
1274
1275 if self.alternating_rows {
1276 let mut m = self.row_metrics.borrow_mut();
1277 for row_idx in first_visible..last_visible {
1278 if row_idx % 2 == 1 {
1279 let y = body_origin_y + m.row_top(row_idx) - scroll_y;
1280 let h = m.row_height(row_idx);
1281 let rect = Rect::new(content_left, y, body_width_for_paint, h);
1282 canvas.fill_rect(rect, SurfaceRole::AltRow.resolve(colors));
1283 }
1284 }
1285 }
1286
1287 if let Some(ref sel) = self.row_selection
1288 && matches!(
1289 self.selection_mode,
1290 TableSelectionMode::SingleRow | TableSelectionMode::MultiRow
1291 )
1292 {
1293 let bg = if self.view_focused.get() && ctx.window_active {
1297 SurfaceRole::Selected.resolve(colors)
1298 } else {
1299 SurfaceRole::SelectedInactive.resolve(colors)
1300 };
1301 let mut m = self.row_metrics.borrow_mut();
1302 for row_idx in sel.selected_indices() {
1303 let y = body_origin_y + m.row_top(row_idx) - scroll_y;
1304 let h = m.row_height(row_idx);
1305 if y + h < body_origin_y || y > body_origin_y + body_height {
1306 continue;
1307 }
1308 let rect = Rect::new(content_left, y, body_width_for_paint, h);
1309 canvas.fill_rect(rect, bg);
1310 }
1311 }
1312
1313 let line_color = BorderRole::Divider.resolve(colors);
1314 let line_w = cp::GRID_LINE_THICKNESS.max(1.0);
1315 if matches!(self.grid_lines, GridLines::Horizontal | GridLines::Both) {
1316 let mut m = self.row_metrics.borrow_mut();
1317 for row_idx in first_visible..last_visible {
1318 let bottom = m.row_top(row_idx) + m.row_height(row_idx);
1319 let y = body_origin_y + bottom - scroll_y - line_w;
1320 let rect = Rect::new(content_left, y, body_width_for_paint, line_w);
1321 canvas.fill_rect(rect, line_color);
1322 }
1323 }
1324
1325 let boundaries = *self.pane_boundaries.borrow();
1328 let scroll_x = self.scroll_x.get();
1329 let content_bounds = Rect::new(
1330 content_left,
1331 body_origin_y,
1332 body_width_for_paint,
1333 body_height,
1334 );
1335 let (leading_rect, middle_rect, trailing_rect) =
1336 layout::band_rects(content_bounds, &widths, boundaries, rtl);
1337
1338 let full_width = self.full_width_rows();
1341 let bands: Vec<(f32, f32)> = match &full_width {
1342 Some(fw) => {
1343 let mut m = self.row_metrics.borrow_mut();
1344 (first_visible..last_visible)
1345 .filter(|&row| (fw.at)(row))
1346 .map(|row| (body_origin_y + m.row_top(row) - scroll_y, m.row_height(row)))
1347 .collect()
1348 }
1349 None => Vec::new(),
1350 };
1351 let is_band_row = |row: usize| full_width.as_ref().is_some_and(|fw| (fw.at)(row));
1352
1353 if matches!(self.grid_lines, GridLines::Vertical | GridLines::Both) {
1354 let leading_end = boundaries.leading_count.min(widths.len());
1355 let middle_end = boundaries.middle_end.min(widths.len()).max(leading_end);
1356 for (span_top, span_bottom) in
1357 spans_between(body_origin_y, body_origin_y + body_height, &bands)
1358 {
1359 let in_span =
1360 |pane: Rect| Rect::new(pane.x, span_top, pane.width, span_bottom - span_top);
1361 crate::table_view::draw_pane_dividers(
1362 canvas,
1363 in_span(leading_rect),
1364 &widths[..leading_end],
1365 0.0,
1366 rtl,
1367 line_color,
1368 line_w,
1369 );
1370 crate::table_view::draw_pane_dividers(
1371 canvas,
1372 in_span(middle_rect),
1373 &widths[leading_end..middle_end],
1374 scroll_x,
1375 rtl,
1376 line_color,
1377 line_w,
1378 );
1379 crate::table_view::draw_pane_dividers(
1380 canvas,
1381 in_span(trailing_rect),
1382 &widths[middle_end..],
1383 0.0,
1384 rtl,
1385 line_color,
1386 line_w,
1387 );
1388 }
1389 }
1390
1391 if self.view_focused.get()
1395 && self.focus_visible.get()
1396 && let Some((focus_row, _)) = self.focused_cell.get()
1397 && is_band_row(focus_row)
1398 {
1399 let (focus_top, focus_h) = {
1400 let mut m = self.row_metrics.borrow_mut();
1401 (m.row_top(focus_row), m.row_height(focus_row))
1402 };
1403 let y = body_origin_y + focus_top - scroll_y;
1404 if y + focus_h >= body_origin_y && y <= body_origin_y + body_height {
1405 canvas.set_clip(content_bounds);
1406 let inset = cp::FOCUS_RING_INSET;
1407 let stroke = cp::GRID_LINE_THICKNESS.max(1.5);
1408 let ring_color = BorderRole::Focused.resolve(colors);
1409 let (rx, ry) = (content_left + inset, y + inset);
1410 let rw = (body_width_for_paint - inset * 2.0).max(0.0);
1411 let rh = (focus_h - inset * 2.0).max(0.0);
1412 stroke_ring(canvas, Rect::new(rx, ry, rw, rh), stroke, ring_color);
1413 canvas.clear_clip();
1414 }
1415 } else if self.view_focused.get()
1416 && self.focus_visible.get()
1417 && let Some((focus_row, focus_col)) = self.focused_cell.get()
1418 && focus_col < widths.len()
1419 && let Some(x_off) = layout::column_logical_x(
1420 &widths,
1421 boundaries,
1422 scroll_x,
1423 body_width_for_paint,
1424 focus_col,
1425 )
1426 {
1427 let cell_w = widths[focus_col];
1428 let (focus_top, focus_h) = {
1429 let mut m = self.row_metrics.borrow_mut();
1430 (m.row_top(focus_row), m.row_height(focus_row))
1431 };
1432 let y = body_origin_y + focus_top - scroll_y;
1433 if y + focus_h >= body_origin_y && y <= body_origin_y + body_height {
1434 let pane_rect = if focus_col < boundaries.leading_count {
1435 leading_rect
1436 } else if focus_col >= boundaries.middle_end {
1437 trailing_rect
1438 } else {
1439 middle_rect
1440 };
1441 canvas.set_clip(pane_rect);
1442 let inset = cp::FOCUS_RING_INSET;
1443 let stroke = cp::GRID_LINE_THICKNESS.max(1.5);
1444 let ring_color = BorderRole::Focused.resolve(colors);
1445 let rx = if rtl {
1446 content_left + body_width_for_paint - x_off - cell_w + inset
1447 } else {
1448 content_left + x_off + inset
1449 };
1450 let ry = y + inset;
1451 let rw = (cell_w - inset * 2.0).max(0.0);
1452 let rh = (focus_h - inset * 2.0).max(0.0);
1453 stroke_ring(canvas, Rect::new(rx, ry, rw, rh), stroke, ring_color);
1454 canvas.clear_clip();
1455 }
1456 }
1457
1458 canvas.clear_clip();
1459
1460 let nothing_indicated = self.focused_cell.get().is_none()
1464 && self
1465 .row_selection
1466 .as_ref()
1467 .is_none_or(|s| s.selected_indices().is_empty())
1468 && self.cell_selection.as_ref().is_none_or(|s| s.count() == 0);
1469 if self.view_focused.get() && self.focus_visible.get() && nothing_indicated {
1470 let inset = 1.0_f32;
1471 let rect = Rect::new(
1472 bounds.x + inset,
1473 bounds.y + inset,
1474 (bounds.width - inset * 2.0).max(0.0),
1475 (bounds.height - inset * 2.0).max(0.0),
1476 );
1477 canvas.stroke_rect(rect, BorderRole::Focused.resolve(colors), 1.5);
1478 }
1479
1480 if let Some(x) = self.resize_preview_x.get() {
1482 let thickness = cp::GRID_LINE_THICKNESS.max(1.5);
1483 canvas.fill_rect(
1484 Rect::new(x - thickness * 0.5, bounds.y, thickness, bounds.height),
1485 BorderRole::Focused.resolve(colors),
1486 );
1487 }
1488 }
1489
1490 fn wants_post_paint(&self) -> bool {
1491 true
1492 }
1493
1494 fn post_paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
1496 self.paint_drop_feedback(bounds, canvas, ctx);
1497 }
1498
1499 fn context_menu_key_target(&self) -> Option<WidgetId> {
1513 let index = self.focused_cell.get().map(|(row, _col)| row).or_else(|| {
1514 self.row_selection
1515 .as_ref()
1516 .and_then(|s| s.selected_indices().first().copied())
1517 })?;
1518 let map = self.row_map.borrow();
1519 map.iter().find(|(i, _)| *i == index).map(|(_, id)| *id)
1520 }
1521
1522 fn accessibility(&self, builder: &mut AccessNodeBuilder) {
1523 builder.set_role(teksilo_core::accesskit::Role::TreeGrid);
1524 if self
1537 .row_selection
1538 .as_ref()
1539 .is_some_and(|selection| selection.mode() == teksilo_data::SelectionMode::Multi)
1540 {
1541 builder.set_multiselectable(true);
1542 }
1543
1544 if let Some(ref label) = self.a11y_label {
1545 builder.set_name(label.resolve_now());
1546 }
1547 let row_count = self.source.visible_count() + if self.show_header { 1 } else { 0 };
1548 let col_count = self.columns.len();
1549 let n = builder.inner_mut();
1550 n.set_row_count(row_count);
1551 n.set_column_count(col_count);
1552
1553 if let Some((row, col)) = self.focused_cell.get()
1560 && let Some(cell_id) = self.realized_cell(row, col)
1561 {
1562 builder.set_active_descendant(widget_id_to_node_id(cell_id));
1563 }
1564 }
1565
1566 fn as_any(&self) -> Option<&dyn std::any::Any> {
1567 Some(self)
1568 }
1569
1570 fn children(&self) -> Vec<WidgetId> {
1571 let mut out: Vec<WidgetId> = Vec::new();
1574 if let Some(id) = self.body_pane_id {
1575 out.push(id);
1576 }
1577 if let Some(id) = self.empty_id {
1578 out.push(id);
1579 }
1580 if let Some(id) = self.pinned_stack_id {
1581 out.push(id);
1582 }
1583 if let Some(id) = self.scrollbar_id {
1584 out.push(id);
1585 }
1586 if let Some(id) = self.h_scrollbar_id {
1587 out.push(id);
1588 }
1589 if let Some(id) = self.header_row_id {
1590 out.push(id);
1591 }
1592 out
1593 }
1594
1595 fn accessibility_children(&self) -> Option<Vec<WidgetId>> {
1596 let out: Vec<WidgetId> = [
1600 self.header_row_id,
1601 self.body_pane_id,
1602 self.empty_id,
1603 self.pinned_stack_id,
1604 self.scrollbar_id,
1605 self.h_scrollbar_id,
1606 ]
1607 .into_iter()
1608 .flatten()
1609 .collect();
1610 if out.is_empty() { None } else { Some(out) }
1611 }
1612
1613 fn clips_children(&self) -> bool {
1614 true
1615 }
1616}
1617
1618impl<T: 'static> TreeTableView<T> {
1619 fn paint_drop_feedback(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
1622 if self.drop_feedback.get().is_none() {
1623 return;
1624 }
1625 let colors = &ctx.theme.colors;
1626 let header_h = self.effective_header_height();
1627 let body_origin_y = bounds.y + header_h;
1628 let body_height = (bounds.height - header_h).max(0.0);
1629 let widths = self.column_widths.borrow();
1630 let body_width = widths.iter().sum::<f32>();
1631 let body_width_for_paint = if body_width > 0.0 {
1632 body_width.min(bounds.width)
1633 } else {
1634 bounds.width
1635 };
1636 let rtl = ctx.layout_direction == teksilo_core::environment::LayoutDirection::RightToLeft;
1637 let content_left = if rtl {
1638 bounds.x + bounds.width - body_width_for_paint
1639 } else {
1640 bounds.x
1641 };
1642 let boundaries = *self.pane_boundaries.borrow();
1643 let scroll_x = self.scroll_x.get();
1644 canvas.set_clip(Rect::new(
1645 content_left,
1646 body_origin_y,
1647 body_width_for_paint,
1648 body_height,
1649 ));
1650
1651 let drop_indent_origin = |depth: usize| -> f32 {
1662 let step = self.effective_indent();
1663 let tree_decl = self.tree_column_decl_index();
1664 let tree_slot = self
1665 .display_indices
1666 .borrow()
1667 .iter()
1668 .position(|&i| i == tree_decl)
1669 .unwrap_or(0);
1670 let col_x = layout::column_logical_x(
1671 &widths,
1672 boundaries,
1673 scroll_x,
1674 body_width_for_paint,
1675 tree_slot,
1676 )
1677 .unwrap_or(0.0);
1678 (col_x + depth as f32 * step).clamp(0.0, body_width_for_paint)
1679 };
1680 match self.drop_feedback.get() {
1681 Some(DropViz::Line { y, depth, .. }) => {
1682 let recipe = ctx
1683 .theme
1684 .style_slots
1685 .list_container
1686 .as_ref()
1687 .map(|s| s.insertion())
1688 .unwrap_or_default();
1689 let line_color = recipe.role.resolve(colors);
1690 let thickness = recipe.thickness;
1691 let line_y = body_origin_y + y - thickness * 0.5;
1692 let indent = drop_indent_origin(depth);
1693 let x = if rtl {
1696 content_left
1697 } else {
1698 content_left + indent
1699 };
1700 canvas.fill_rect(
1701 Rect::new(x, line_y, body_width_for_paint - indent, thickness),
1702 line_color,
1703 );
1704 }
1705 Some(DropViz::Rect {
1710 top, height, depth, ..
1711 }) => {
1712 let into = ctx
1713 .theme
1714 .style_slots
1715 .list_container
1716 .as_ref()
1717 .map(|s| s.drop_into())
1718 .unwrap_or_default();
1719 let color = into.role.resolve(colors);
1720 let indent = drop_indent_origin(depth);
1721 let x = if rtl {
1722 content_left
1723 } else {
1724 content_left + indent
1725 };
1726 let rect = Rect::new(
1727 x + into.inset,
1728 body_origin_y + top + into.inset,
1729 (body_width_for_paint - indent - into.inset * 2.0).max(0.0),
1730 (height - into.inset * 2.0).max(0.0),
1731 );
1732 let radius = teksilo_tokens::CornerRadius::uniform(into.corner_radius);
1733 canvas.fill_rounded_rect(rect, radius, color.with_alpha(into.fill_alpha));
1734 canvas.stroke_rounded_rect(rect, radius, color, into.thickness);
1735 }
1736 None => {}
1737 }
1738
1739 canvas.clear_clip();
1740 }
1741
1742 fn add_pinned_stack(
1751 &self,
1752 ctx: &mut BuildContext,
1753 display_indices: &[usize],
1754 tree_display_pos: usize,
1755 indent_per_level: f32,
1756 full_width: Option<full_width::FullWidthRows<T>>,
1757 reveal: pinned::PinnedReveal,
1758 ) -> WidgetId {
1759 let on_pick: Rc<dyn Fn(usize, &mut EventContext)> = {
1760 let selection = self.row_selection.clone();
1761 let cells = self.cell_selection.clone();
1762 let mode = self.selection_mode;
1763 let focused = self.focused_cell.clone();
1764 let scroll_y = self.scroll_y.clone();
1765 let max_scroll_y = self.max_scroll_y.clone();
1766 let viewport = self.viewport_height.clone();
1767 let activate = match self.activate_on {
1768 crate::data_views::ActivateOn::SingleClick => self.on_row_activate.clone(),
1769 crate::data_views::ActivateOn::DoubleClick => None,
1770 };
1771 Rc::new(move |row, ctx| {
1772 let col = focused.get().map_or(0, |(_row, col)| col);
1773 match mode {
1774 TableSelectionMode::SingleRow | TableSelectionMode::MultiRow => {
1775 if let Some(s) = &selection {
1776 s.select(row);
1777 }
1778 }
1779 TableSelectionMode::SingleCell | TableSelectionMode::MultiCell => {
1780 if let Some(cs) = &cells {
1781 cs.select(row, col);
1782 }
1783 }
1784 TableSelectionMode::None => {}
1785 }
1786 focused.set(Some((row, col)));
1787 let scroll = scroll_y.get();
1788 let target = reveal.scroll_for(row, scroll, viewport.get(), max_scroll_y.get());
1789 if (target - scroll).abs() > f32::EPSILON {
1790 scroll_y.set(target);
1791 }
1792 if let Some(activate) = &activate {
1793 activate(row, ctx);
1794 }
1795 })
1796 };
1797 let end_edit = self.on_cell_edit_dismissed.clone().map(|dismissed| {
1800 let editing = self.editing_cell.clone();
1801 let col_ids: Vec<String> = display_indices
1802 .iter()
1803 .map(|&i| self.columns[i].spec.id.clone())
1804 .collect();
1805 Rc::new(move |ctx: &mut EventContext| {
1806 if let Some((row, col)) = editing.get()
1807 && let Some(col_id) = col_ids.get(col)
1808 {
1809 dismissed(row, col_id, ctx);
1810 }
1811 }) as Rc<dyn Fn(&mut EventContext)>
1812 });
1813 ctx.add(pinned::PinnedStack {
1814 rows: self.pinned_rows.clone(),
1815 built: self.pinned_built.clone(),
1816 layout: self.pinned_layout.clone(),
1817 copies: pinned::RowCopies {
1818 source: self.source.clone(),
1819 columns: self.columns.clone(),
1820 display_indices: display_indices.to_vec(),
1821 column_widths: self.column_widths.clone(),
1822 pane_boundaries: *self.pane_boundaries.borrow(),
1823 scroll_x: self.scroll_x.clone(),
1824 tree_display_pos,
1825 indent_per_level,
1826 row_metrics: self.row_metrics.clone(),
1827 full_width,
1828 },
1829 on_pick,
1830 end_edit,
1831 scroll_y: self.scroll_y.clone(),
1832 total_refresh: self.pane_total_refresh.clone(),
1833 pane_rows: (self.pane_built_start.clone(), self.pane_built_end.clone()),
1834 lines: Vec::new(),
1835 copy_ids: Vec::new(),
1836 })
1837 }
1838}
1839
1840fn stroke_ring(canvas: &mut Canvas, rect: Rect, stroke: f32, color: teksilo_tokens::Color) {
1842 let Rect {
1843 x,
1844 y,
1845 width: w,
1846 height: h,
1847 } = rect;
1848 canvas.fill_rect(Rect::new(x, y, w, stroke), color);
1849 canvas.fill_rect(Rect::new(x, y + h - stroke, w, stroke), color);
1850 canvas.fill_rect(Rect::new(x, y, stroke, h), color);
1851 canvas.fill_rect(Rect::new(x + w - stroke, y, stroke, h), color);
1852}
1853
1854#[derive(Debug, Clone, Copy)]
1861struct DropSpot {
1862 row: usize,
1863 top: f32,
1864 height: f32,
1865 insertion: usize,
1867 insertion_y: f32,
1868}
1869
1870impl DropSpot {
1871 fn at(
1872 metrics: &SharedRowMetrics,
1873 (lines, built): &(Rc<RefCell<Vec<pinned::StackLine>>>, Rc<RefCell<Vec<usize>>>),
1874 count: usize,
1875 scroll: f32,
1876 y: f32,
1877 ) -> Self {
1878 if let Some(line) = pinned::line_at(&lines.borrow(), &built.borrow(), y) {
1879 let before = y - line.top < line.height * 0.5;
1881 return Self {
1882 row: line.row,
1883 top: line.top,
1884 height: line.height,
1885 insertion: if before { line.row } else { line.row + 1 },
1886 insertion_y: if before {
1887 line.top
1888 } else {
1889 line.top + line.height
1890 },
1891 };
1892 }
1893 let content_y = y + scroll;
1894 let mut m = metrics.borrow_mut();
1895 m.resize(count);
1896 let insertion = m.insertion_index(content_y);
1897 let row = m.row_at(content_y);
1898 Self {
1899 row,
1900 top: m.row_top(row) - scroll,
1901 height: m.row_height(row),
1902 insertion,
1903 insertion_y: m.row_top(insertion) - scroll,
1904 }
1905 }
1906}
1907
1908pub(super) fn spans_between(top: f32, bottom: f32, bands: &[(f32, f32)]) -> Vec<(f32, f32)> {
1911 let mut spans = Vec::with_capacity(bands.len() + 1);
1912 let mut from = top;
1913 for &(y, height) in bands {
1914 if y > from {
1915 spans.push((from, y.min(bottom)));
1916 }
1917 from = from.max(y + height);
1918 }
1919 if bottom > from {
1920 spans.push((from, bottom));
1921 }
1922 spans
1923}