use std::path::Path;
use iced::{
Alignment, Background, Border, Element, Length, Theme,
widget::{Space, button, column, container, mouse_area, row, scrollable, text},
};
use super::DirectoryTree;
use super::drag::DragMsg;
use super::icon::{Icon, render as icon_render};
use super::message::DirectoryTreeEvent;
use super::node::TreeNode;
const INDENT_STEP: f32 = 16.0;
const INTRA_ROW_GAP: f32 = 6.0;
impl DirectoryTree {
pub fn view<'a, Message, F>(&'a self, on_event: F) -> Element<'a, Message>
where
Message: Clone + 'a,
F: Fn(DirectoryTreeEvent) -> Message + Copy + 'a,
{
let mut rows: Vec<Element<'a, Message>> = Vec::new();
let drop_target = self.drop_target();
let search_visible = self.search.as_ref().map(|s| &s.visible_paths);
render_node(
&self.root,
0,
drop_target,
search_visible,
on_event,
&mut rows,
);
let list = column(rows).spacing(2).padding(4).width(Length::Fill);
scrollable(list)
.width(Length::Fill)
.height(Length::Fill)
.into()
}
}
fn render_node<'a, Message, F>(
node: &'a TreeNode,
depth: u32,
drop_target: Option<&Path>,
search_visible: Option<&std::collections::HashSet<std::path::PathBuf>>,
on_event: F,
out: &mut Vec<Element<'a, Message>>,
) where
Message: Clone + 'a,
F: Fn(DirectoryTreeEvent) -> Message + Copy + 'a,
{
if let Some(visible) = search_visible
&& !visible.contains(&node.path)
{
return;
}
let is_drop_target = drop_target == Some(node.path.as_path());
out.push(render_row(node, depth, is_drop_target, on_event));
let descend = match search_visible {
Some(_) => node.is_dir,
None => node.is_dir && node.is_expanded && node.is_loaded,
};
if descend {
for child in &node.children {
render_node(child, depth + 1, drop_target, search_visible, on_event, out);
}
}
}
fn render_row<'a, Message, F>(
node: &'a TreeNode,
depth: u32,
is_drop_target: bool,
on_event: F,
) -> Element<'a, Message>
where
Message: Clone + 'a,
F: Fn(DirectoryTreeEvent) -> Message + Copy + 'a,
{
let label_str: String = match node.path.file_name() {
Some(n) => n.to_string_lossy().into_owned(),
None => node.path.display().to_string(),
};
let type_icon: Element<'a, Message> = if node.error.is_some() {
icon_render::<Message>(Icon::Error)
} else if node.is_dir {
if node.is_expanded {
icon_render::<Message>(Icon::FolderOpen)
} else {
icon_render::<Message>(Icon::FolderClosed)
}
} else {
icon_render::<Message>(Icon::File)
};
let label_widget = {
let t = text(label_str).size(14);
if node.error.is_some() {
t.color(iced::Color::from_rgb(0.55, 0.55, 0.55))
} else {
t
}
};
let caret: Element<'a, Message> = if node.is_dir {
let caret_icon = if node.is_expanded {
Icon::CaretDown
} else {
Icon::CaretRight
};
let path = node.path.clone();
button(icon_render::<Message>(caret_icon))
.padding(2)
.style(button::text)
.on_press(on_event(DirectoryTreeEvent::Toggled(path)))
.into()
} else {
Space::new()
.width(Length::Fixed(20.0))
.height(Length::Fixed(20.0))
.into()
};
let selection_body = row![
type_icon,
Space::new().width(Length::Fixed(4.0)),
label_widget,
]
.spacing(INTRA_ROW_GAP)
.align_y(Alignment::Center);
let is_selected = node.is_selected;
let path = node.path.clone();
let is_dir = node.is_dir;
let path_for_press = path.clone();
let path_for_enter = path.clone();
let path_for_exit = path.clone();
let path_for_release = path;
let styled_body = container(selection_body)
.width(Length::Fill)
.padding(2)
.style(move |theme: &Theme| {
let palette = theme.extended_palette();
if is_selected {
container::Style {
background: Some(Background::Color(palette.primary.base.color)),
text_color: Some(palette.primary.base.text),
border: Border {
radius: 3.0.into(),
..Default::default()
},
..Default::default()
}
} else if is_drop_target {
container::Style {
background: Some(Background::Color(palette.success.weak.color)),
text_color: Some(palette.success.weak.text),
border: Border {
color: palette.success.strong.color,
width: 1.5,
radius: 3.0.into(),
},
..Default::default()
}
} else {
container::Style::default()
}
});
let select_area = mouse_area(styled_body)
.on_press(on_event(DirectoryTreeEvent::Drag(DragMsg::Pressed(
path_for_press,
is_dir,
))))
.on_enter(on_event(DirectoryTreeEvent::Drag(DragMsg::Entered(
path_for_enter,
))))
.on_exit(on_event(DirectoryTreeEvent::Drag(DragMsg::Exited(
path_for_exit,
))))
.on_release(on_event(DirectoryTreeEvent::Drag(DragMsg::Released(
path_for_release,
))));
let indent_px = INDENT_STEP * depth as f32;
let indent = Space::new().width(Length::Fixed(indent_px));
container(
row![indent, caret, select_area]
.spacing(INTRA_ROW_GAP)
.align_y(Alignment::Center),
)
.width(Length::Fill)
.into()
}
#[allow(dead_code)]
fn display_path(path: &Path) -> String {
path.display().to_string()
}