use {
color,
Color,
Colorable,
FontSize,
Positionable,
Scalar,
Sizeable,
Widget,
};
use event;
use std;
use widget;
pub use self::directory_view::DirectoryView;
pub mod directory_view;
#[derive(WidgetCommon_)]
pub struct FileNavigator<'a> {
#[conrod(common_builder)]
common: widget::CommonBuilder,
pub style: Style,
pub starting_directory: &'a std::path::Path,
pub types: Types<'a>,
show_hidden: bool,
}
#[derive(Copy, Clone)]
pub enum Types<'a> {
All,
WithExtension(&'a [&'a str]),
Directories,
}
pub struct State {
starting_directory: std::path::PathBuf,
directory_stack: Vec<Directory>,
ids: Ids,
}
#[derive(Debug, PartialEq)]
pub struct Directory {
path: std::path::PathBuf,
column_width: Scalar,
}
widget_ids! {
struct Ids {
scrollable_canvas,
scrollbar,
directory_views[],
directory_view_resizers[],
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, WidgetStyle_)]
pub struct Style {
#[conrod(default = "theme.shape_color")]
pub color: Option<Color>,
#[conrod(default = "None")]
pub unselected_color: Option<Option<Color>>,
#[conrod(default = "None")]
pub text_color: Option<Option<Color>>,
#[conrod(default = "theme.font_size_medium")]
pub font_size: Option<FontSize>,
#[conrod(default = "250.0")]
pub column_width: Option<Scalar>,
#[conrod(default = "5.0")]
pub resize_handle_width: Option<Scalar>,
}
#[derive(Clone, Debug)]
pub enum Event {
ChangeDirectory(std::path::PathBuf),
ChangeSelection(Vec<std::path::PathBuf>),
Click(event::Click, Vec<std::path::PathBuf>),
DoubleClick(event::DoubleClick, Vec<std::path::PathBuf>),
Press(event::Press, Vec<std::path::PathBuf>),
Release(event::Release, Vec<std::path::PathBuf>),
}
impl<'a> FileNavigator<'a> {
pub fn new(starting_directory: &'a std::path::Path, types: Types<'a>) -> Self {
FileNavigator {
common: widget::CommonBuilder::default(),
style: Style::default(),
starting_directory: starting_directory,
types: types,
show_hidden: false,
}
}
pub fn all(starting_directory: &'a std::path::Path) -> Self {
Self::new(starting_directory, Types::All)
}
pub fn with_extension(starting_directory: &'a std::path::Path, exts: &'a [&'a str]) -> Self {
Self::new(starting_directory, Types::WithExtension(exts))
}
pub fn directories(starting_directory: &'a std::path::Path) -> Self {
Self::new(starting_directory, Types::Directories)
}
pub fn unselected_color(mut self, color: Color) -> Self {
self.style.unselected_color = Some(Some(color));
self
}
pub fn text_color(mut self, color: Color) -> Self {
self.style.text_color = Some(Some(color));
self
}
pub fn show_hidden_files(mut self, show_hidden: bool) -> Self {
self.show_hidden = show_hidden;
self
}
builder_methods!{
pub font_size { style.font_size = Some(FontSize) }
}
}
impl<'a> Widget for FileNavigator<'a> {
type State = State;
type Style = Style;
type Event = Vec<Event>;
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
State {
directory_stack: Vec::new(),
starting_directory: std::path::PathBuf::new(),
ids: Ids::new(id_gen),
}
}
fn style(&self) -> Self::Style {
self.style.clone()
}
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { id, state, style, rect, ui, .. } = args;
let FileNavigator { starting_directory, types, .. } = self;
if starting_directory != state.starting_directory {
state.update(|state| {
let width = style.column_width(&ui.theme);
let path = starting_directory.to_path_buf();
state.starting_directory = path.clone();
state.directory_stack.clear();
let dir = Directory { path: path, column_width: width };
state.directory_stack.push(dir);
});
}
let color = style.color(&ui.theme);
let unselected_color = style.unselected_color(&ui.theme)
.unwrap_or_else(|| color.plain_contrast().plain_contrast());
let text_color = style.text_color(&ui.theme)
.unwrap_or_else(|| color.plain_contrast());
widget::Rectangle::fill(rect.dim())
.xy(rect.xy())
.color(color::TRANSPARENT)
.parent(id)
.scroll_kids_horizontally()
.set(state.ids.scrollable_canvas, ui);
widget::Scrollbar::x_axis(state.ids.scrollable_canvas)
.color(color.plain_contrast())
.auto_hide(true)
.set(state.ids.scrollbar, ui);
let mut events = Vec::new();
let mut i = 0;
while i < state.directory_stack.len() {
let view_id = match state.ids.directory_views.get(i) {
Some(&id) => id,
None => {
let id_gen = &mut ui.widget_id_generator();
state.update(|state| state.ids.directory_views.resize(i+1, id_gen));
state.ids.directory_views[i]
},
};
let resize_id = match state.ids.directory_view_resizers.get(i) {
Some(&id) => id,
None => {
let id_gen = &mut ui.widget_id_generator();
state.update(|state| state.ids.directory_view_resizers.resize(i+1, id_gen));
state.ids.directory_view_resizers[i]
},
};
let resize_handle_width = style.resize_handle_width(&ui.theme);
let mut column_width = state.directory_stack[i].column_width;
if let Some(resize_rect) = ui.rect_of(resize_id) {
let mut scroll_x = 0.0;
for drag in ui.widget_input(resize_id).drags().left() {
let target_w = column_width + drag.delta_xy[0];
let min_w = resize_rect.w() * 3.0;
let end_w = column_width + (rect.right() - resize_rect.right());
column_width = min_w.max(target_w);
state.update(|state| state.directory_stack[i].column_width = column_width);
if target_w > end_w {
scroll_x += target_w - end_w;
}
}
if scroll_x > 0.0 {
ui.scroll_widget(state.ids.scrollable_canvas, [-scroll_x, 0.0]);
}
}
enum Action { EnterDir(std::path::PathBuf), ExitDir }
let mut maybe_action = None;
let directory_view_width = column_width - resize_handle_width;
let font_size = style.font_size(&ui.theme);
for event in DirectoryView::new(&state.directory_stack[i].path, types)
.h(rect.h())
.w(directory_view_width)
.and(|view| if i == 0 { view.mid_left_of(id) } else { view.right(0.0) })
.color(color)
.unselected_color(unselected_color)
.text_color(text_color)
.font_size(font_size)
.show_hidden_files(self.show_hidden)
.parent(state.ids.scrollable_canvas)
.set(view_id, ui)
{
match event {
directory_view::Event::Selection(paths) => {
if paths.len() == 1 {
let path = &paths[0];
if path.is_dir() {
maybe_action = Some(Action::EnterDir(path.clone()));
} else {
maybe_action = Some(Action::ExitDir);
}
} else {
maybe_action = Some(Action::ExitDir);
}
let event = Event::ChangeSelection(paths);
events.push(event);
},
directory_view::Event::Click(e, paths) =>
events.push(Event::Click(e, paths)),
directory_view::Event::DoubleClick(e, paths) =>
events.push(Event::DoubleClick(e, paths)),
directory_view::Event::Release(e, paths) =>
events.push(Event::Release(e, paths)),
directory_view::Event::Press(press, paths) => {
if let Some(key_press) = press.key() {
use input;
match key_press.key {
input::Key::Right => if paths.len() == 1 {
if paths[0].is_dir() {
}
},
input::Key::Left => {
},
_ => (),
}
}
events.push(Event::Press(press, paths));
},
}
}
match maybe_action {
Some(Action::EnterDir(path)) => {
state.update(|state| {
let num_to_remove = state.directory_stack.len() - 1 - i;
for _ in 0..num_to_remove {
state.directory_stack.pop();
}
let dir = Directory { path: path.clone(), column_width: column_width };
state.directory_stack.push(dir);
let event = Event::ChangeDirectory(path);
events.push(event);
});
let total_w = state.directory_stack.iter().fold(0.0, |t, d| t + d.column_width);
let overlap = total_w - rect.w();
if overlap > 0.0 {
ui.scroll_widget(state.ids.scrollable_canvas, [-overlap, 0.0]);
}
},
Some(Action::ExitDir) => {
let num_to_remove = state.directory_stack.len() - 1 - i;
for _ in 0..num_to_remove {
state.update(|state| { state.directory_stack.pop(); });
}
},
None => (),
}
let resize_color = color.plain_contrast().plain_contrast();
let resize_color = match ui.widget_input(resize_id).mouse() {
Some(mouse) => match mouse.buttons.left().is_down() {
true => resize_color.clicked().alpha(0.5),
false => resize_color.highlighted().alpha(0.2),
},
None => resize_color.alpha(0.2),
};
widget::Rectangle::fill([resize_handle_width, rect.h()])
.color(resize_color)
.right(0.0)
.parent(state.ids.scrollable_canvas)
.set(resize_id, ui);
i += 1;
}
if ui.widget_input(state.ids.scrollable_canvas).presses().mouse().left().next().is_some() {
state.update(|state| {
while state.directory_stack.len() > 1 {
state.directory_stack.pop();
}
});
}
events
}
}
impl<'a> Colorable for FileNavigator<'a> {
builder_method!(color { style.color = Some(Color) });
}