Partial trash implementation
This commit is contained in:
parent
174fc53e45
commit
1ba5be1116
6 changed files with 228 additions and 87 deletions
17
Cargo.lock
generated
17
Cargo.lock
generated
|
|
@ -980,6 +980,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"systemicons",
|
"systemicons",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"trash",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4999,6 +5000,22 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "trash"
|
||||||
|
version = "3.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8c646008e5144d988005bec12b1e56f5e0a951e957176686815eba8b025e0418"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"objc",
|
||||||
|
"once_cell",
|
||||||
|
"scopeguard",
|
||||||
|
"url",
|
||||||
|
"windows 0.44.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ttf-parser"
|
name = "ttf-parser"
|
||||||
version = "0.15.2"
|
version = "0.15.2"
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ lexical-sort = "0.3.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
serde = { version = "1", features = ["serde_derive"] }
|
serde = { version = "1", features = ["serde_derive"] }
|
||||||
tokio = { version = "1" }
|
tokio = { version = "1" }
|
||||||
|
trash = "3.1.2"
|
||||||
# Internationalization
|
# Internationalization
|
||||||
i18n-embed = { version = "0.13", features = ["fluent-system", "desktop-requester"] }
|
i18n-embed = { version = "0.13", features = ["fluent-system", "desktop-requester"] }
|
||||||
i18n-embed-fl = "0.6"
|
i18n-embed-fl = "0.6"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
empty-folder = Empty folder
|
empty-folder = Empty folder
|
||||||
empty-folder-hidden = Empty folder (has hidden items)
|
empty-folder-hidden = Empty folder (has hidden items)
|
||||||
|
trash = Trash
|
||||||
|
|
||||||
# Context Pages
|
# Context Pages
|
||||||
|
|
||||||
|
|
@ -22,3 +23,4 @@ new-folder = New folder
|
||||||
copy = Copy
|
copy = Copy
|
||||||
paste = Paste
|
paste = Paste
|
||||||
select-all = Select all
|
select-all = Select all
|
||||||
|
move-to-trash = Move to trash
|
||||||
|
|
|
||||||
60
src/main.rs
60
src/main.rs
|
|
@ -10,7 +10,13 @@ use cosmic::{
|
||||||
widget::{self, segmented_button},
|
widget::{self, segmented_button},
|
||||||
Application, ApplicationExt, Element,
|
Application, ApplicationExt, Element,
|
||||||
};
|
};
|
||||||
use std::{any::TypeId, env, path::PathBuf, process, time::Instant};
|
use std::{
|
||||||
|
any::TypeId,
|
||||||
|
env, fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
process,
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
|
|
||||||
use config::{AppTheme, Config, CONFIG_VERSION};
|
use config::{AppTheme, Config, CONFIG_VERSION};
|
||||||
mod config;
|
mod config;
|
||||||
|
|
@ -23,7 +29,7 @@ mod localize;
|
||||||
|
|
||||||
mod mime_icon;
|
mod mime_icon;
|
||||||
|
|
||||||
use tab::Tab;
|
use tab::{Location, Tab};
|
||||||
mod tab;
|
mod tab;
|
||||||
|
|
||||||
/// Runs application with these settings
|
/// Runs application with these settings
|
||||||
|
|
@ -100,6 +106,7 @@ pub struct Flags {
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
Copy,
|
Copy,
|
||||||
|
MoveToTrash,
|
||||||
NewFile,
|
NewFile,
|
||||||
NewFolder,
|
NewFolder,
|
||||||
Paste,
|
Paste,
|
||||||
|
|
@ -113,6 +120,7 @@ impl Action {
|
||||||
pub fn message(self, entity: segmented_button::Entity) -> Message {
|
pub fn message(self, entity: segmented_button::Entity) -> Message {
|
||||||
match self {
|
match self {
|
||||||
Action::Copy => Message::Copy(Some(entity)),
|
Action::Copy => Message::Copy(Some(entity)),
|
||||||
|
Action::MoveToTrash => Message::MoveToTrash(Some(entity)),
|
||||||
Action::NewFile => Message::NewFile(Some(entity)),
|
Action::NewFile => Message::NewFile(Some(entity)),
|
||||||
Action::NewFolder => Message::NewFolder(Some(entity)),
|
Action::NewFolder => Message::NewFolder(Some(entity)),
|
||||||
Action::Paste => Message::Paste(Some(entity)),
|
Action::Paste => Message::Paste(Some(entity)),
|
||||||
|
|
@ -131,6 +139,7 @@ pub enum Message {
|
||||||
AppTheme(AppTheme),
|
AppTheme(AppTheme),
|
||||||
Config(Config),
|
Config(Config),
|
||||||
Copy(Option<segmented_button::Entity>),
|
Copy(Option<segmented_button::Entity>),
|
||||||
|
MoveToTrash(Option<segmented_button::Entity>),
|
||||||
NewFile(Option<segmented_button::Entity>),
|
NewFile(Option<segmented_button::Entity>),
|
||||||
NewFolder(Option<segmented_button::Entity>),
|
NewFolder(Option<segmented_button::Entity>),
|
||||||
Paste(Option<segmented_button::Entity>),
|
Paste(Option<segmented_button::Entity>),
|
||||||
|
|
@ -172,9 +181,8 @@ pub struct App {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn open_tab<P: Into<PathBuf>>(&mut self, path: P) -> Command<Message> {
|
fn open_tab(&mut self, location: Location) -> Command<Message> {
|
||||||
let path = path.into();
|
let tab = Tab::new(location.clone());
|
||||||
let tab = Tab::new(path.clone());
|
|
||||||
let entity = self
|
let entity = self
|
||||||
.tab_model
|
.tab_model
|
||||||
.insert()
|
.insert()
|
||||||
|
|
@ -183,18 +191,17 @@ impl App {
|
||||||
.closable()
|
.closable()
|
||||||
.activate()
|
.activate()
|
||||||
.id();
|
.id();
|
||||||
Command::batch([self.update_title(), self.rescan_tab(entity, path)])
|
Command::batch([self.update_title(), self.rescan_tab(entity, location)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rescan_tab<P: Into<PathBuf>>(
|
fn rescan_tab(
|
||||||
&mut self,
|
&mut self,
|
||||||
entity: segmented_button::Entity,
|
entity: segmented_button::Entity,
|
||||||
path: P,
|
location: Location,
|
||||||
) -> Command<Message> {
|
) -> Command<Message> {
|
||||||
let path = path.into();
|
|
||||||
Command::perform(
|
Command::perform(
|
||||||
async move {
|
async move {
|
||||||
match tokio::task::spawn_blocking(move || tab::rescan(path)).await {
|
match tokio::task::spawn_blocking(move || location.scan()).await {
|
||||||
Ok(items) => message::app(Message::TabRescan(entity, items)),
|
Ok(items) => message::app(Message::TabRescan(entity, items)),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("failed to rescan: {}", err);
|
log::warn!("failed to rescan: {}", err);
|
||||||
|
|
@ -313,11 +320,18 @@ impl Application for App {
|
||||||
let mut commands = Vec::new();
|
let mut commands = Vec::new();
|
||||||
|
|
||||||
for arg in env::args().skip(1) {
|
for arg in env::args().skip(1) {
|
||||||
commands.push(app.open_tab(arg));
|
let location = match fs::canonicalize(&arg) {
|
||||||
|
Ok(absolute) => Location::Path(absolute),
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("failed to canonicalize {:?}: {}", arg, err);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
commands.push(app.open_tab(location));
|
||||||
}
|
}
|
||||||
|
|
||||||
if app.tab_model.iter().next().is_none() {
|
if app.tab_model.iter().next().is_none() {
|
||||||
commands.push(app.open_tab(home_dir()));
|
commands.push(app.open_tab(Location::Path(home_dir())));
|
||||||
}
|
}
|
||||||
|
|
||||||
(app, Command::batch(commands))
|
(app, Command::batch(commands))
|
||||||
|
|
@ -344,6 +358,9 @@ impl Application for App {
|
||||||
Message::Copy(entity_opt) => {
|
Message::Copy(entity_opt) => {
|
||||||
log::warn!("TODO: COPY");
|
log::warn!("TODO: COPY");
|
||||||
}
|
}
|
||||||
|
Message::MoveToTrash(entity_opt) => {
|
||||||
|
log::warn!("TODO: MOVE TO TRASH");
|
||||||
|
}
|
||||||
Message::NewFile(entity_opt) => {
|
Message::NewFile(entity_opt) => {
|
||||||
log::warn!("TODO: NEW FILE");
|
log::warn!("TODO: NEW FILE");
|
||||||
}
|
}
|
||||||
|
|
@ -424,7 +441,7 @@ impl Application for App {
|
||||||
match self.tab_model.data_mut::<Tab>(entity) {
|
match self.tab_model.data_mut::<Tab>(entity) {
|
||||||
Some(tab) => {
|
Some(tab) => {
|
||||||
if tab.update(tab_message) {
|
if tab.update(tab_message) {
|
||||||
update_opt = Some((tab.title(), tab.path.clone()));
|
update_opt = Some((tab.title(), tab.location.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
|
@ -439,11 +456,11 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
Message::TabNew => {
|
Message::TabNew => {
|
||||||
let active = self.tab_model.active();
|
let active = self.tab_model.active();
|
||||||
let path = match self.tab_model.data::<Tab>(active) {
|
let location = match self.tab_model.data::<Tab>(active) {
|
||||||
Some(tab) => tab.path.clone(),
|
Some(tab) => tab.location.clone(),
|
||||||
None => home_dir(),
|
None => Location::Path(home_dir()),
|
||||||
};
|
};
|
||||||
return self.open_tab(path);
|
return self.open_tab(location);
|
||||||
}
|
}
|
||||||
Message::TabRescan(entity, items) => match self.tab_model.data_mut::<Tab>(entity) {
|
Message::TabRescan(entity, items) => match self.tab_model.data_mut::<Tab>(entity) {
|
||||||
Some(tab) => {
|
Some(tab) => {
|
||||||
|
|
@ -492,6 +509,7 @@ impl Application for App {
|
||||||
tab::View::List => (tab::View::Grid, "view-grid-symbolic"),
|
tab::View::List => (tab::View::Grid, "view-grid-symbolic"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//TODO: use nav bar instead, dynamically show items
|
||||||
vec![row![
|
vec![row![
|
||||||
widget::button(widget::icon::from_name("list-add-symbolic").size(16).icon())
|
widget::button(widget::icon::from_name("list-add-symbolic").size(16).icon())
|
||||||
.on_press(Message::TabNew)
|
.on_press(Message::TabNew)
|
||||||
|
|
@ -505,6 +523,14 @@ impl Application for App {
|
||||||
.on_press(Message::TabMessage(active, tab::Message::Parent))
|
.on_press(Message::TabMessage(active, tab::Message::Parent))
|
||||||
.padding(space_xxs)
|
.padding(space_xxs)
|
||||||
.style(style::Button::Icon),
|
.style(style::Button::Icon),
|
||||||
|
widget::button(
|
||||||
|
widget::icon::from_name("user-trash-full-symbolic")
|
||||||
|
.size(16)
|
||||||
|
.icon()
|
||||||
|
)
|
||||||
|
.on_press(Message::TabMessage(active, tab::Message::Trash))
|
||||||
|
.padding(space_xxs)
|
||||||
|
.style(style::Button::Icon),
|
||||||
widget::button(widget::icon::from_name(view_icon).size(16).icon())
|
widget::button(widget::icon::from_name(view_icon).size(16).icon())
|
||||||
.on_press(Message::TabMessage(active, tab::Message::View(view)))
|
.on_press(Message::TabMessage(active, tab::Message::View(view)))
|
||||||
.padding(space_xxs)
|
.padding(space_xxs)
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ pub fn context_menu<'a>(entity: segmented_button::Entity) -> Element<'a, Message
|
||||||
menu_button!(widget::text(label)).on_press(Message::TabContextAction(entity, action))
|
menu_button!(widget::text(label)).on_press(Message::TabContextAction(entity, action))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//TODO: change items based on selection
|
||||||
widget::container(column!(
|
widget::container(column!(
|
||||||
menu_action(fl!("new-file"), Action::NewFile),
|
menu_action(fl!("new-file"), Action::NewFile),
|
||||||
menu_action(fl!("new-folder"), Action::NewFolder),
|
menu_action(fl!("new-folder"), Action::NewFolder),
|
||||||
|
|
@ -41,6 +42,8 @@ pub fn context_menu<'a>(entity: segmented_button::Entity) -> Element<'a, Message
|
||||||
menu_action(fl!("paste"), Action::Paste),
|
menu_action(fl!("paste"), Action::Paste),
|
||||||
menu_action(fl!("select-all"), Action::SelectAll),
|
menu_action(fl!("select-all"), Action::SelectAll),
|
||||||
horizontal_rule(1),
|
horizontal_rule(1),
|
||||||
|
menu_action(fl!("move-to-trash"), Action::MoveToTrash),
|
||||||
|
horizontal_rule(1),
|
||||||
menu_action(fl!("properties"), Action::Properties),
|
menu_action(fl!("properties"), Action::Properties),
|
||||||
))
|
))
|
||||||
.padding(1)
|
.padding(1)
|
||||||
|
|
|
||||||
232
src/tab.rs
232
src/tab.rs
|
|
@ -155,9 +155,9 @@ fn open_command(path: &PathBuf) -> process::Command {
|
||||||
command
|
command
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rescan(tab_path: PathBuf) -> Vec<Item> {
|
pub fn scan_path(tab_path: &PathBuf) -> Vec<Item> {
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
match fs::read_dir(&tab_path) {
|
match fs::read_dir(tab_path) {
|
||||||
Ok(entries) => {
|
Ok(entries) => {
|
||||||
for entry_res in entries {
|
for entry_res in entries {
|
||||||
let entry = match entry_res {
|
let entry = match entry_res {
|
||||||
|
|
@ -211,7 +211,7 @@ pub fn rescan(tab_path: PathBuf) -> Vec<Item> {
|
||||||
|
|
||||||
items.push(Item {
|
items.push(Item {
|
||||||
name,
|
name,
|
||||||
metadata,
|
metadata_opt: Some(metadata),
|
||||||
hidden,
|
hidden,
|
||||||
path,
|
path,
|
||||||
icon_handle_grid,
|
icon_handle_grid,
|
||||||
|
|
@ -224,7 +224,66 @@ pub fn rescan(tab_path: PathBuf) -> Vec<Item> {
|
||||||
log::warn!("failed to read directory {:?}: {}", tab_path, err);
|
log::warn!("failed to read directory {:?}: {}", tab_path, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items.sort_by(|a, b| match (a.metadata.is_dir(), b.metadata.is_dir()) {
|
items.sort_by(|a, b| lexical_sort::natural_lexical_cmp(&a.name, &b.name));
|
||||||
|
items
|
||||||
|
}
|
||||||
|
|
||||||
|
// This config statement is from trash::os_limited, inverted
|
||||||
|
#[cfg(not(any(
|
||||||
|
target_os = "windows",
|
||||||
|
all(
|
||||||
|
unix,
|
||||||
|
not(target_os = "macos"),
|
||||||
|
not(target_os = "ios"),
|
||||||
|
not(target_os = "android")
|
||||||
|
)
|
||||||
|
)))]
|
||||||
|
pub fn scan_trash() -> Vec<Item> {
|
||||||
|
log::warn!("viewing trash not supported on this platform");
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
// This config statement is from trash::os_limited
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "windows",
|
||||||
|
all(
|
||||||
|
unix,
|
||||||
|
not(target_os = "macos"),
|
||||||
|
not(target_os = "ios"),
|
||||||
|
not(target_os = "android")
|
||||||
|
)
|
||||||
|
))]
|
||||||
|
pub fn scan_trash() -> Vec<Item> {
|
||||||
|
let mut items: Vec<Item> = Vec::new();
|
||||||
|
match trash::os_limited::list() {
|
||||||
|
Ok(entries) => {
|
||||||
|
for entry in entries {
|
||||||
|
let path = entry.original_path();
|
||||||
|
let name = entry.name;
|
||||||
|
|
||||||
|
//TODO: configurable size
|
||||||
|
let (icon_handle_grid, icon_handle_list) = (
|
||||||
|
mime_icon(&path, ICON_SIZE_GRID),
|
||||||
|
mime_icon(&path, ICON_SIZE_LIST),
|
||||||
|
);
|
||||||
|
|
||||||
|
items.push(Item {
|
||||||
|
name,
|
||||||
|
//TODO: how will we get proper info on if this is a file or directory?
|
||||||
|
metadata_opt: None,
|
||||||
|
hidden: false,
|
||||||
|
path,
|
||||||
|
icon_handle_grid,
|
||||||
|
icon_handle_list,
|
||||||
|
select_time: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("failed to read trash items: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items.sort_by(|a, b| match (a.is_dir(), b.is_dir()) {
|
||||||
(true, false) => Ordering::Less,
|
(true, false) => Ordering::Less,
|
||||||
(false, true) => Ordering::Greater,
|
(false, true) => Ordering::Greater,
|
||||||
_ => lexical_sort::natural_lexical_cmp(&a.name, &b.name),
|
_ => lexical_sort::natural_lexical_cmp(&a.name, &b.name),
|
||||||
|
|
@ -232,18 +291,34 @@ pub fn rescan(tab_path: PathBuf) -> Vec<Item> {
|
||||||
items
|
items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Location {
|
||||||
|
Path(PathBuf),
|
||||||
|
Trash,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Location {
|
||||||
|
pub fn scan(&self) -> Vec<Item> {
|
||||||
|
match self {
|
||||||
|
Self::Path(path) => scan_path(path),
|
||||||
|
Self::Trash => scan_trash(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
Click(Option<usize>),
|
Click(Option<usize>),
|
||||||
Home,
|
Home,
|
||||||
Parent,
|
Parent,
|
||||||
|
Trash,
|
||||||
View(View),
|
View(View),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub metadata: Metadata,
|
pub metadata_opt: Option<Metadata>,
|
||||||
pub hidden: bool,
|
pub hidden: bool,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub icon_handle_grid: widget::icon::Handle,
|
pub icon_handle_grid: widget::icon::Handle,
|
||||||
|
|
@ -252,6 +327,10 @@ pub struct Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Item {
|
impl Item {
|
||||||
|
pub fn is_dir(&self) -> bool {
|
||||||
|
self.metadata_opt.as_ref().map_or(false, |x| x.is_dir())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn property_view(&self, core: &Core) -> Element<crate::Message> {
|
pub fn property_view(&self, core: &Core) -> Element<crate::Message> {
|
||||||
let mut section = widget::settings::view_section("");
|
let mut section = widget::settings::view_section("");
|
||||||
section = section.add(widget::settings::item::item_row(vec![
|
section = section.add(widget::settings::item::item_row(vec![
|
||||||
|
|
@ -263,44 +342,46 @@ impl Item {
|
||||||
|
|
||||||
//TODO: translate!
|
//TODO: translate!
|
||||||
//TODO: correct display of folder size?
|
//TODO: correct display of folder size?
|
||||||
if !self.metadata.is_dir() {
|
if let Some(ref metadata) = self.metadata_opt {
|
||||||
section = section.add(widget::settings::item::item(
|
if !metadata.is_dir() {
|
||||||
"Size",
|
section = section.add(widget::settings::item::item(
|
||||||
widget::text(format_size(self.metadata.len())),
|
"Size",
|
||||||
));
|
widget::text(format_size(metadata.len())),
|
||||||
}
|
));
|
||||||
|
}
|
||||||
|
|
||||||
if let Ok(time) = self.metadata.accessed() {
|
if let Ok(time) = metadata.accessed() {
|
||||||
section = section.add(widget::settings::item(
|
section = section.add(widget::settings::item(
|
||||||
"Accessed",
|
"Accessed",
|
||||||
widget::text(
|
widget::text(
|
||||||
chrono::DateTime::<chrono::Local>::from(time)
|
chrono::DateTime::<chrono::Local>::from(time)
|
||||||
.format("%c")
|
.format("%c")
|
||||||
.to_string(),
|
.to_string(),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(time) = self.metadata.modified() {
|
if let Ok(time) = metadata.modified() {
|
||||||
section = section.add(widget::settings::item(
|
section = section.add(widget::settings::item(
|
||||||
"Modified",
|
"Modified",
|
||||||
widget::text(
|
widget::text(
|
||||||
chrono::DateTime::<chrono::Local>::from(time)
|
chrono::DateTime::<chrono::Local>::from(time)
|
||||||
.format("%c")
|
.format("%c")
|
||||||
.to_string(),
|
.to_string(),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(time) = self.metadata.created() {
|
if let Ok(time) = metadata.created() {
|
||||||
section = section.add(widget::settings::item(
|
section = section.add(widget::settings::item(
|
||||||
"Created",
|
"Created",
|
||||||
widget::text(
|
widget::text(
|
||||||
chrono::DateTime::<chrono::Local>::from(time)
|
chrono::DateTime::<chrono::Local>::from(time)
|
||||||
.format("%c")
|
.format("%c")
|
||||||
.to_string(),
|
.to_string(),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
section.into()
|
section.into()
|
||||||
|
|
@ -311,7 +392,7 @@ impl fmt::Debug for Item {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("Item")
|
f.debug_struct("Item")
|
||||||
.field("name", &self.name)
|
.field("name", &self.name)
|
||||||
.field("metadata", &self.metadata)
|
.field("metadata_opt", &self.metadata_opt)
|
||||||
.field("hidden", &self.hidden)
|
.field("hidden", &self.hidden)
|
||||||
.field("path", &self.path)
|
.field("path", &self.path)
|
||||||
// icon_handles
|
// icon_handles
|
||||||
|
|
@ -328,7 +409,7 @@ pub enum View {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Tab {
|
pub struct Tab {
|
||||||
pub path: PathBuf,
|
pub location: Location,
|
||||||
//TODO
|
//TODO
|
||||||
pub context_menu: Option<Point>,
|
pub context_menu: Option<Point>,
|
||||||
pub items_opt: Option<Vec<Item>>,
|
pub items_opt: Option<Vec<Item>>,
|
||||||
|
|
@ -336,15 +417,9 @@ pub struct Tab {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tab {
|
impl Tab {
|
||||||
pub fn new(path: PathBuf) -> Self {
|
pub fn new(location: Location) -> Self {
|
||||||
Self {
|
Self {
|
||||||
path: match fs::canonicalize(&path) {
|
location,
|
||||||
Ok(absolute) => absolute,
|
|
||||||
Err(err) => {
|
|
||||||
log::warn!("failed to canonicalize {:?}: {}", path, err);
|
|
||||||
path
|
|
||||||
}
|
|
||||||
},
|
|
||||||
context_menu: None,
|
context_menu: None,
|
||||||
items_opt: None,
|
items_opt: None,
|
||||||
view: View::Grid,
|
view: View::Grid,
|
||||||
|
|
@ -353,7 +428,14 @@ impl Tab {
|
||||||
|
|
||||||
pub fn title(&self) -> String {
|
pub fn title(&self) -> String {
|
||||||
//TODO: better title
|
//TODO: better title
|
||||||
format!("{}", self.path.display())
|
match &self.location {
|
||||||
|
Location::Path(path) => {
|
||||||
|
format!("{}", path.display())
|
||||||
|
}
|
||||||
|
Location::Trash => {
|
||||||
|
fl!("trash")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, message: Message) -> bool {
|
pub fn update(&mut self, message: Message) -> bool {
|
||||||
|
|
@ -365,8 +447,8 @@ impl Tab {
|
||||||
if Some(i) == click_i_opt {
|
if Some(i) == click_i_opt {
|
||||||
if let Some(select_time) = item.select_time {
|
if let Some(select_time) = item.select_time {
|
||||||
if select_time.elapsed() < DOUBLE_CLICK_DURATION {
|
if select_time.elapsed() < DOUBLE_CLICK_DURATION {
|
||||||
if item.metadata.is_dir() {
|
if item.is_dir() {
|
||||||
cd = Some(item.path.clone());
|
cd = Some(Location::Path(item.path.clone()));
|
||||||
} else {
|
} else {
|
||||||
let mut command = open_command(&item.path);
|
let mut command = open_command(&item.path);
|
||||||
match command.spawn() {
|
match command.spawn() {
|
||||||
|
|
@ -392,19 +474,24 @@ impl Tab {
|
||||||
self.context_menu = None;
|
self.context_menu = None;
|
||||||
}
|
}
|
||||||
Message::Home => {
|
Message::Home => {
|
||||||
cd = Some(crate::home_dir());
|
cd = Some(Location::Path(crate::home_dir()));
|
||||||
}
|
}
|
||||||
Message::Parent => {
|
Message::Parent => {
|
||||||
if let Some(parent) = self.path.parent() {
|
if let Location::Path(path) = &self.location {
|
||||||
cd = Some(parent.to_owned());
|
if let Some(parent) = path.parent() {
|
||||||
|
cd = Some(Location::Path(parent.to_owned()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Message::Trash => {
|
||||||
|
cd = Some(Location::Trash);
|
||||||
|
}
|
||||||
Message::View(view) => {
|
Message::View(view) => {
|
||||||
self.view = view;
|
self.view = view;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(path) = cd {
|
if let Some(location) = cd {
|
||||||
self.path = path;
|
self.location = location;
|
||||||
self.items_opt = None;
|
self.items_opt = None;
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -433,8 +520,8 @@ impl Tab {
|
||||||
)
|
)
|
||||||
.align_x(Horizontal::Center)
|
.align_x(Horizontal::Center)
|
||||||
.align_y(Vertical::Center)
|
.align_y(Vertical::Center)
|
||||||
.height(Length::Fill)
|
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -483,7 +570,9 @@ impl Tab {
|
||||||
return self.empty_view(hidden > 0, core);
|
return self.empty_view(hidden > 0, core);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
widget::flex_row(children).into()
|
widget::scrollable(widget::flex_row(children))
|
||||||
|
.width(Length::Fill)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_view(&self, core: &Core) -> Element<Message> {
|
pub fn list_view(&self, core: &Core) -> Element<Message> {
|
||||||
|
|
@ -527,10 +616,14 @@ impl Tab {
|
||||||
.into(),
|
.into(),
|
||||||
widget::text(item.name.clone()).into(),
|
widget::text(item.name.clone()).into(),
|
||||||
widget::horizontal_space(Length::Fill).into(),
|
widget::horizontal_space(Length::Fill).into(),
|
||||||
widget::text(if item.metadata.is_dir() {
|
widget::text(if item.is_dir() {
|
||||||
"\u{2014}".to_string()
|
"\u{2014}".to_string()
|
||||||
} else {
|
} else {
|
||||||
format_size(item.metadata.len())
|
if let Some(ref metadata) = item.metadata_opt {
|
||||||
|
format_size(metadata.len())
|
||||||
|
} else {
|
||||||
|
"\u{2014}".to_string()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.into(),
|
.into(),
|
||||||
// Hack to make room for scroll bar
|
// Hack to make room for scroll bar
|
||||||
|
|
@ -557,17 +650,16 @@ impl Tab {
|
||||||
return self.empty_view(hidden > 0, core);
|
return self.empty_view(hidden > 0, core);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
widget::column::with_children(children).into()
|
widget::scrollable(widget::column::with_children(children))
|
||||||
|
.width(Length::Fill)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(&self, core: &Core) -> Element<Message> {
|
pub fn view(&self, core: &Core) -> Element<Message> {
|
||||||
widget::container(
|
widget::container(match self.view {
|
||||||
widget::scrollable(match self.view {
|
View::Grid => self.grid_view(core),
|
||||||
View::Grid => self.grid_view(core),
|
View::List => self.list_view(core),
|
||||||
View::List => self.list_view(core),
|
})
|
||||||
})
|
|
||||||
.width(Length::Fill),
|
|
||||||
)
|
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.into()
|
.into()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue