use std::sync::Arc;
use crate::draw_ctx::DrawCtx;
use crate::event::{Event, EventResult, MouseButton};
use crate::geometry::{Point, Rect, Size};
use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
use crate::widget::Widget;
use super::drag::{paint_drop_child_highlight, paint_drop_line, paint_ghost};
use super::node::{flatten_visible, DropPosition, FlatRow};
use super::row::{icon_color, TreeRow, EXPAND_W};
use super::{RowMeta, TreeView, SCROLLBAR_W};
impl Widget for TreeView {
fn type_name(&self) -> &'static str {
"TreeView"
}
fn bounds(&self) -> Rect {
self.bounds
}
fn set_bounds(&mut self, b: Rect) {
self.bounds = b;
}
fn children(&self) -> &[Box<dyn Widget>] {
&self.row_widgets
}
fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
&mut self.row_widgets
}
fn is_focusable(&self) -> bool {
true
}
fn margin(&self) -> Insets {
self.base.margin
}
fn widget_base(&self) -> Option<&WidgetBase> {
Some(&self.base)
}
fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
Some(&mut self.base)
}
fn h_anchor(&self) -> HAnchor {
self.base.h_anchor
}
fn v_anchor(&self) -> VAnchor {
self.base.v_anchor
}
fn min_size(&self) -> Size {
self.base.min_size
}
fn max_size(&self) -> Size {
self.base.max_size
}
fn hit_test(&self, local_pos: Point) -> bool {
if self.drag.is_some() || self.dragging_scrollbar {
return true;
}
let b = self.bounds();
local_pos.x >= 0.0
&& local_pos.x <= b.width
&& local_pos.y >= 0.0
&& local_pos.y <= b.height
}
fn layout(&mut self, available: Size) -> Size {
let rows = flatten_visible(&self.nodes);
self.content_height = rows.len() as f64 * self.row_height;
self.scroll_offset = self.scroll_offset.clamp(0.0, self.max_scroll());
let h = available.height;
let w = available.width - SCROLLBAR_W;
let rh = self.row_height;
let ind = self.indent_width;
let font_size = self.font_size;
let visible_rows: Vec<&FlatRow> = rows
.iter()
.filter(|flat| {
!self
.drag
.as_ref()
.map_or(false, |d| d.live && d.node_idx == flat.node_idx)
})
.collect();
let new_sig = self.row_content_signature();
let can_reuse = self.last_row_content_sig == Some(new_sig)
&& self.row_widgets.len() == visible_rows.len()
&& !self.row_widgets.is_empty();
if can_reuse {
for (i, flat) in visible_rows.iter().enumerate() {
let y_bot = h - (i as f64 + 1.0) * rh + self.scroll_offset;
let row = &mut self.row_widgets[i];
row.layout(Size::new(w, rh));
row.set_bounds(Rect::new(0.0, y_bot, w, rh));
if let Some(meta) = self.row_metas.get_mut(i) {
debug_assert_eq!(meta.node_idx, flat.node_idx);
if let Some(ref mut tr) = meta.toggle_rect {
tr.y = y_bot + (rh - tr.height) * 0.5;
}
}
}
return available;
}
self.row_widgets.clear();
self.row_metas.clear();
for (i, flat) in visible_rows.iter().enumerate() {
let node = &self.nodes[flat.node_idx];
let y_bot = h - (i as f64 + 1.0) * rh + self.scroll_offset;
let mut tree_row = TreeRow::new(
flat.node_idx,
flat.depth,
flat.has_children,
node.is_expanded,
node.is_selected,
self.hovered_row == Some(i),
self.focused,
node.icon,
node.label.clone(),
Arc::clone(&self.font),
font_size,
ind,
rh,
);
tree_row.layout(Size::new(w, rh));
tree_row.set_bounds(Rect::new(0.0, y_bot, w, rh));
let toggle_rect = if flat.has_children {
let tlb = tree_row.toggle_local_bounds;
Some(Rect::new(tlb.x, y_bot + tlb.y, tlb.width, tlb.height))
} else {
None
};
self.row_metas.push(RowMeta {
node_idx: flat.node_idx,
toggle_rect,
});
self.row_widgets.push(Box::new(tree_row));
}
self.last_row_content_sig = Some(new_sig);
available
}
fn paint(&mut self, ctx: &mut dyn DrawCtx) {
let h = self.bounds.height;
let w = self.bounds.width;
let content_w = w - SCROLLBAR_W;
let v = ctx.visuals().clone();
ctx.set_fill_color(v.window_fill);
ctx.begin_path();
ctx.rect(0.0, 0.0, w, h);
ctx.fill();
let sb_x = self.scrollbar_x();
if self.content_height > h {
ctx.set_fill_color(v.scroll_track);
ctx.begin_path();
ctx.rect(sb_x, 0.0, SCROLLBAR_W, h);
ctx.fill();
if let Some((thumb_y, thumb_h)) = self.thumb_metrics() {
let thumb_color = if self.dragging_scrollbar {
v.scroll_thumb_dragging
} else if self.hovered_scrollbar {
v.scroll_thumb_hovered
} else {
v.scroll_thumb
};
ctx.set_fill_color(thumb_color);
ctx.begin_path();
ctx.rounded_rect(sb_x + 2.0, thumb_y, SCROLLBAR_W - 4.0, thumb_h, 3.0);
ctx.fill();
}
}
ctx.clip_rect(0.0, 0.0, content_w, h);
let rows = flatten_visible(&self.nodes);
if let Some(drop_target) = self.drop_target {
if self.drag.as_ref().map_or(false, |d| d.live) {
let rh = self.row_height;
let off = self.scroll_offset;
let ind = self.indent_width;
let ref_node = match drop_target {
DropPosition::Before(ni)
| DropPosition::After(ni)
| DropPosition::AsChild(ni) => ni,
};
if let Some(ri) = rows.iter().position(|r| r.node_idx == ref_node) {
let y_bot = h - (ri as f64 + 1.0) * rh + off;
let indent = rows[ri].depth as f64 * ind + EXPAND_W;
match drop_target {
DropPosition::Before(_) => {
paint_drop_line(ctx, indent, y_bot + rh, content_w - indent)
}
DropPosition::After(_) => {
paint_drop_line(ctx, indent, y_bot, content_w - indent)
}
DropPosition::AsChild(_) => {
paint_drop_child_highlight(ctx, y_bot, content_w, rh)
}
}
}
}
}
if let Some(drag) = &self.drag {
if drag.live {
let label = self.nodes[drag.node_idx].label.clone();
let ic = icon_color(self.nodes[drag.node_idx].icon);
let pos = drag.current_pos;
let rh = self.row_height;
let font = Arc::clone(&self.font);
let fs = self.font_size;
paint_ghost(ctx, &label, pos, content_w, rh, &font, fs, ic);
}
}
}
fn on_event(&mut self, event: &Event) -> EventResult {
let result = match event {
Event::FocusGained => {
self.focused = true;
EventResult::Consumed
}
Event::FocusLost => {
self.focused = false;
EventResult::Consumed
}
Event::MouseWheel { delta_y, .. } => {
self.scroll_offset =
(self.scroll_offset + delta_y * 40.0).clamp(0.0, self.max_scroll());
self.hovered_row = None;
EventResult::Consumed
}
Event::MouseMove { pos } => self.handle_mouse_move(*pos),
Event::MouseDown {
pos,
button: MouseButton::Left,
modifiers,
} => self.handle_mouse_down(*pos, *modifiers),
Event::MouseUp {
button: MouseButton::Left,
pos,
..
} => self.handle_mouse_up(*pos),
Event::KeyDown { key, modifiers } => self.handle_key_down(key, *modifiers),
_ => EventResult::Ignored,
};
if result == EventResult::Consumed {
crate::animation::request_draw();
}
result
}
}