refactor(types): file metadata
This commit is contained in:
@@ -119,7 +119,7 @@ where
|
||||
|
||||
let file_sizes = scan_folder(path.clone()); // TODO: better
|
||||
{
|
||||
let mut app = app .lock().unwrap();
|
||||
let mut app = app.lock().unwrap();
|
||||
app.ui_state.set_base_folder(file_sizes, path.into_os_string().into_string().expect("could not convert path to string"));
|
||||
app.render();
|
||||
}
|
||||
|
||||
@@ -1,17 +1,42 @@
|
||||
use ::std::fmt;
|
||||
use tui::buffer::Buffer;
|
||||
use tui::layout::Rect;
|
||||
use tui::style::{Style, Color};
|
||||
use tui::widgets::{Widget};
|
||||
|
||||
use crate::ui::{FileMetadata, FileType};
|
||||
|
||||
pub const MINIMUM_HEIGHT: u16 = 2;
|
||||
pub const MINIMUM_WIDTH: u16 = 8;
|
||||
|
||||
pub struct DisplaySize(pub f64);
|
||||
|
||||
impl fmt::Display for DisplaySize{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.0 > 999_999_999.0 {
|
||||
write!(f, "{:.1}G", self.0 / 1_000_000_000.0)
|
||||
} else if self.0 > 999_999.0 {
|
||||
write!(f, "{:.1}M", self.0 / 1_000_000.0)
|
||||
} else if self.0 > 999.0 {
|
||||
write!(f, "{:.1}K", self.0 / 1000.0)
|
||||
} else {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// format!("{}/ {} ({:.0}%)", name, DisplaySize(size as f64),percentage * 100.0),
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RectWithText {
|
||||
pub struct FileSizeRect {
|
||||
pub rect: RectFloat,
|
||||
pub text: String,
|
||||
pub file_metadata: FileMetadata,
|
||||
// pub text: String, // TODO: removeme
|
||||
pub selected: bool,
|
||||
pub file_name: String, // TODO: better
|
||||
// pub file_name: String, // TODO: better
|
||||
// pub file_size: u64, // TODO: better
|
||||
// pub file_percentage: u8, // TODO: better
|
||||
// pub file_type: FileType,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -24,7 +49,7 @@ pub struct RectFloat {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RectangleGrid {
|
||||
rectangles: Vec<RectWithText>
|
||||
rectangles: Vec<FileSizeRect>
|
||||
}
|
||||
|
||||
pub mod boundaries {
|
||||
@@ -42,7 +67,7 @@ pub mod boundaries {
|
||||
}
|
||||
|
||||
impl<'a> RectangleGrid {
|
||||
pub fn new (rectangles: Vec<RectWithText>) -> Self {
|
||||
pub fn new (rectangles: Vec<FileSizeRect>) -> Self {
|
||||
RectangleGrid { rectangles }
|
||||
}
|
||||
}
|
||||
@@ -185,21 +210,21 @@ impl<'a> Widget for RectangleGrid {
|
||||
if area.width < 2 || area.height < 2 {
|
||||
return;
|
||||
}
|
||||
for rect_with_text in &self.rectangles {
|
||||
let rounded_x = rect_with_text.rect.x.round();
|
||||
let rounded_y = rect_with_text.rect.y.round();
|
||||
for file_size_rect in &self.rectangles {
|
||||
let rounded_x = file_size_rect.rect.x.round();
|
||||
let rounded_y = file_size_rect.rect.y.round();
|
||||
let mut rect = Rect {
|
||||
x: rounded_x as u16,
|
||||
y: rounded_y as u16,
|
||||
width: ((rect_with_text.rect.x - rounded_x) + rect_with_text.rect.width).round() as u16,
|
||||
height: ((rect_with_text.rect.y - rounded_y) + rect_with_text.rect.height).round() as u16,
|
||||
width: ((file_size_rect.rect.x - rounded_x) + file_size_rect.rect.width).round() as u16,
|
||||
height: ((file_size_rect.rect.y - rounded_y) + file_size_rect.rect.height).round() as u16,
|
||||
};
|
||||
|
||||
// fix rounding errors
|
||||
if (rect_with_text.rect.x + rect_with_text.rect.width).round() as u16 > rect.x + rect.width {
|
||||
if (file_size_rect.rect.x + file_size_rect.rect.width).round() as u16 > rect.x + rect.width {
|
||||
rect.width += 1;
|
||||
}
|
||||
if (rect_with_text.rect.y + rect_with_text.rect.height).round() as u16 > rect.y + rect.height {
|
||||
if (file_size_rect.rect.y + file_size_rect.rect.height).round() as u16 > rect.y + rect.height {
|
||||
rect.height += 1;
|
||||
}
|
||||
|
||||
@@ -219,13 +244,29 @@ impl<'a> Widget for RectangleGrid {
|
||||
// TODO: we should not accept a rectangle with a width of less than 8 so that the text
|
||||
// will be at least partly legible... these rectangles should be created with a small
|
||||
// height instead
|
||||
let text = if rect_with_text.selected { format!("=> {} <=", rect_with_text.text) } else { rect_with_text.text.to_owned() }; // TODO: better
|
||||
let text = if file_size_rect.selected {
|
||||
let name = &file_size_rect.file_metadata.name;
|
||||
let size = DisplaySize(file_size_rect.file_metadata.size as f64);
|
||||
let percentage = file_size_rect.file_metadata.percentage;
|
||||
match file_size_rect.file_metadata.file_type {
|
||||
FileType::File => format!("=> {} {} ({:.0}%) <=", name, size, percentage * 100.0),
|
||||
FileType::Folder => format!("=> {}/ {} ({:.0}%) <=", name, size, percentage * 100.0)
|
||||
}
|
||||
} else {
|
||||
let name = &file_size_rect.file_metadata.name;
|
||||
let size = DisplaySize(file_size_rect.file_metadata.size as f64);
|
||||
let percentage = file_size_rect.file_metadata.percentage;
|
||||
match file_size_rect.file_metadata.file_type {
|
||||
FileType::File => format!("{} {} ({:.0}%)", name, size, percentage * 100.0),
|
||||
FileType::Folder => format!("{}/ {} ({:.0}%)", name, size, percentage * 100.0)
|
||||
}
|
||||
};
|
||||
let display_text = truncate_middle(&text, max_text_length);
|
||||
let text_length = display_text.len(); // TODO: better
|
||||
|
||||
let text_start_position = ((rect.width - text_length as u16) as f64 / 2.0).ceil() as u16 + rect.x;
|
||||
|
||||
let text_style = if rect_with_text.selected {
|
||||
let text_style = if file_size_rect.selected {
|
||||
Style::default().bg(Color::White).fg(Color::Black)
|
||||
} else {
|
||||
Style::default()
|
||||
|
||||
@@ -21,11 +21,18 @@ impl fmt::Display for DisplaySize{
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum FileType {
|
||||
File,
|
||||
Folder,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FilePercentage {
|
||||
pub file_name: String,
|
||||
pub percentage: f64,
|
||||
pub actual_file_name: String,
|
||||
pub struct FileMetadata {
|
||||
pub name: String,
|
||||
pub size: u64,
|
||||
pub percentage: f64, // 1.0 is 100% (0.5 is 50%, etc.)
|
||||
pub file_type: FileType,
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
@@ -53,8 +60,8 @@ impl State {
|
||||
pub fn update_files(&mut self) {
|
||||
if let Some(base_folder) = &self.base_folder {
|
||||
let current_folder = base_folder.path(&self.current_folder_names);
|
||||
let file_percentages = calculate_percentages(current_folder.expect("could not find current folder"));
|
||||
self.tiles.change_files(file_percentages);
|
||||
let file_list = calculate_utilization(current_folder.expect("could not find current folder"));
|
||||
self.tiles.change_files(file_list);
|
||||
}
|
||||
}
|
||||
pub fn get_current_path(&self) -> Option<PathBuf> {
|
||||
@@ -84,12 +91,12 @@ impl State {
|
||||
}
|
||||
pub fn enter_selected(&mut self) {
|
||||
if let Some(base_folder) = &self.base_folder {
|
||||
if let Some(file_percentage) = &self.tiles.currently_selected() {
|
||||
if let Some(file_size_rect) = &self.tiles.currently_selected() {
|
||||
let path_to_selected = &mut self.current_folder_names.clone();
|
||||
path_to_selected.push(String::from(&file_percentage.file_name));
|
||||
path_to_selected.push(String::from(&file_size_rect.file_metadata.name));
|
||||
if let Some(_) = base_folder.path(&path_to_selected) {
|
||||
// there is a folder at this path!
|
||||
self.current_folder_names.push(String::from(&file_percentage.file_name));
|
||||
self.current_folder_names.push(String::from(&file_size_rect.file_metadata.name));
|
||||
self.update_files();
|
||||
self.tiles.set_selected_index(&0);
|
||||
}
|
||||
@@ -102,40 +109,42 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_percentages (folder: &Folder) -> Vec<FilePercentage> {
|
||||
let mut file_percentages = Vec::new();
|
||||
pub fn calculate_utilization(folder: &Folder) -> Vec<FileMetadata> {
|
||||
let mut file_list = Vec::new();
|
||||
let total_size = folder.size();
|
||||
for (name, file_or_folder) in &folder.contents {
|
||||
match file_or_folder {
|
||||
FileOrFolder::Folder(folder) => {
|
||||
let size = folder.size();
|
||||
let percentage = size as f64 / total_size as f64;
|
||||
let file_percentage = FilePercentage {
|
||||
file_name: format!("{}/ {} ({:.0}%)", name, DisplaySize(size as f64),percentage * 100.0),
|
||||
actual_file_name: String::from(name), // TODO: better
|
||||
let file_metadata = FileMetadata {
|
||||
name: String::from(name),
|
||||
percentage,
|
||||
size,
|
||||
file_type: FileType::Folder
|
||||
};
|
||||
file_percentages.push(file_percentage);
|
||||
file_list.push(file_metadata);
|
||||
},
|
||||
FileOrFolder::File(file) => {
|
||||
let size = file.size;
|
||||
let percentage = size as f64 / total_size as f64;
|
||||
let file_percentage = FilePercentage {
|
||||
file_name: format!("{} {} ({:.0}%)", name, DisplaySize(size as f64),percentage * 100.0),
|
||||
actual_file_name: String::from(name),
|
||||
let file_metadata = FileMetadata {
|
||||
name: String::from(name),
|
||||
percentage,
|
||||
size,
|
||||
file_type: FileType::File
|
||||
};
|
||||
file_percentages.push(file_percentage);
|
||||
file_list.push(file_metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file_percentages.sort_by(|a, b| {
|
||||
file_list.sort_by(|a, b| {
|
||||
if a.percentage == b.percentage {
|
||||
a.file_name.partial_cmp(&b.file_name).unwrap()
|
||||
a.name.partial_cmp(&b.name).unwrap()
|
||||
} else {
|
||||
b.percentage.partial_cmp(&a.percentage).unwrap()
|
||||
}
|
||||
});
|
||||
file_percentages
|
||||
file_list
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use tui::layout::Rect;
|
||||
|
||||
use crate::ui::TreeMap;
|
||||
use crate::ui::FilePercentage;
|
||||
use crate::ui::rectangle_grid::{RectWithText, RectFloat, MINIMUM_HEIGHT, MINIMUM_WIDTH};
|
||||
use crate::ui::FileMetadata;
|
||||
use crate::ui::rectangle_grid::{FileSizeRect, RectFloat, MINIMUM_HEIGHT, MINIMUM_WIDTH};
|
||||
|
||||
fn first_is_right_of_second(first: &RectFloat, second: &RectFloat) -> bool {
|
||||
first.x >= second.x + second.width
|
||||
@@ -86,10 +86,10 @@ fn rects_are_aligned_bottom(first: &RectFloat, second: &RectFloat) -> bool {
|
||||
}
|
||||
|
||||
pub struct Tiles {
|
||||
pub rectangles: Vec<RectWithText>,
|
||||
pub rectangles: Vec<FileSizeRect>,
|
||||
selected_index: Option<usize>,
|
||||
area: Option<Rect>,
|
||||
files: Vec<FilePercentage>,
|
||||
files: Vec<FileMetadata>,
|
||||
}
|
||||
|
||||
impl Tiles {
|
||||
@@ -101,8 +101,8 @@ impl Tiles {
|
||||
area: None,
|
||||
}
|
||||
}
|
||||
pub fn change_files(&mut self, file_percentages: Vec<FilePercentage>) {
|
||||
self.files = file_percentages;
|
||||
pub fn change_files(&mut self, file_list: Vec<FileMetadata>) {
|
||||
self.files = file_list;
|
||||
self.selected_index = Some(0);
|
||||
self.fill();
|
||||
}
|
||||
@@ -147,7 +147,7 @@ impl Tiles {
|
||||
next_selected.selected = true;
|
||||
self.selected_index = Some(*next_index);
|
||||
}
|
||||
pub fn currently_selected (&self) -> Option<&RectWithText> {
|
||||
pub fn currently_selected (&self) -> Option<&FileSizeRect> {
|
||||
match &self.selected_index {
|
||||
Some(selected_index) => self.rectangles.get(*selected_index),
|
||||
None => None,
|
||||
@@ -169,7 +169,7 @@ impl Tiles {
|
||||
match next_rectangle_index {
|
||||
Some(existing_candidate_index) => {
|
||||
|
||||
let existing_candidate: &RectWithText = self.rectangles.get(existing_candidate_index).expect(&format!("could not find existing candidate at index {}", existing_candidate_index));
|
||||
let existing_candidate: &FileSizeRect = self.rectangles.get(existing_candidate_index).expect(&format!("could not find existing candidate at index {}", existing_candidate_index));
|
||||
|
||||
if rects_are_aligned_left(&existing_candidate.rect, &candidate.rect) {
|
||||
let existing_candidate_overlap = get_horizontal_overlap(&existing_candidate.rect, ¤tly_selected.rect);
|
||||
@@ -209,7 +209,7 @@ impl Tiles {
|
||||
match next_rectangle_index {
|
||||
Some(existing_candidate_index) => {
|
||||
|
||||
let existing_candidate: &RectWithText = self.rectangles.get(existing_candidate_index).expect(&format!("could not find existing candidate at index {}", existing_candidate_index));
|
||||
let existing_candidate: &FileSizeRect = self.rectangles.get(existing_candidate_index).expect(&format!("could not find existing candidate at index {}", existing_candidate_index));
|
||||
|
||||
if rects_are_aligned_right(&existing_candidate.rect, &candidate.rect) {
|
||||
let existing_candidate_overlap = get_horizontal_overlap(&existing_candidate.rect, ¤tly_selected.rect);
|
||||
@@ -248,7 +248,7 @@ impl Tiles {
|
||||
match next_rectangle_index {
|
||||
Some(existing_candidate_index) => {
|
||||
|
||||
let existing_candidate: &RectWithText = self.rectangles.get(existing_candidate_index).expect(&format!("could not find existing candidate at index {}", existing_candidate_index));
|
||||
let existing_candidate: &FileSizeRect = self.rectangles.get(existing_candidate_index).expect(&format!("could not find existing candidate at index {}", existing_candidate_index));
|
||||
|
||||
if rects_are_aligned_top(&existing_candidate.rect, &candidate.rect) {
|
||||
|
||||
@@ -290,7 +290,7 @@ impl Tiles {
|
||||
match next_rectangle_index {
|
||||
Some(existing_candidate_index) => {
|
||||
|
||||
let existing_candidate: &RectWithText = self.rectangles.get(existing_candidate_index).expect(&format!("could not find existing candidate at index {}", existing_candidate_index));
|
||||
let existing_candidate: &FileSizeRect = self.rectangles.get(existing_candidate_index).expect(&format!("could not find existing candidate at index {}", existing_candidate_index));
|
||||
|
||||
if rects_are_aligned_bottom(&existing_candidate.rect, &candidate.rect) {
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::ui::FilePercentage;
|
||||
use crate::ui::rectangle_grid::{RectWithText, RectFloat} ;
|
||||
use crate::ui::FileMetadata;
|
||||
use crate::ui::rectangle_grid::{FileSizeRect, RectFloat} ;
|
||||
|
||||
const HEIGHT_WIDTH_RATIO: f64 = 2.5;
|
||||
|
||||
pub struct TreeMap {
|
||||
pub rectangles: Vec<RectWithText>,
|
||||
pub rectangles: Vec<FileSizeRect>,
|
||||
empty_space: RectFloat,
|
||||
total_size: f64,
|
||||
}
|
||||
@@ -17,22 +17,23 @@ impl TreeMap {
|
||||
empty_space,
|
||||
}
|
||||
}
|
||||
fn layoutrow(&mut self, row: Vec<FilePercentage>) {
|
||||
let row_total = row.iter().fold(0.0, |acc, file_percentage| {
|
||||
let size = file_percentage.percentage * self.total_size;
|
||||
fn layoutrow(&mut self, row: Vec<FileMetadata>) {
|
||||
let row_total = row.iter().fold(0.0, |acc, file_metadata| {
|
||||
let size = file_metadata.percentage * self.total_size;
|
||||
acc + size
|
||||
});
|
||||
if self.empty_space.width <= self.empty_space.height * HEIGHT_WIDTH_RATIO {
|
||||
let mut x = self.empty_space.x;
|
||||
let mut row_height = 0.0;
|
||||
for file_percentage in row {
|
||||
let size = file_percentage.percentage * self.total_size;
|
||||
for file_metadata in row {
|
||||
let size = file_metadata.percentage * self.total_size;
|
||||
let width = (size / row_total) * self.empty_space.width as f64;
|
||||
let height = size / width;
|
||||
let rect_with_text = RectWithText {
|
||||
let rect_with_text = FileSizeRect {
|
||||
rect: RectFloat {x, y: self.empty_space.y, width: width , height: height },
|
||||
text: file_percentage.file_name.clone(),
|
||||
file_name: file_percentage.actual_file_name.clone(), // TODO: better
|
||||
file_metadata,
|
||||
// text: file_percentage.file_name.clone(),
|
||||
// file_name: file_percentage.actual_file_name.clone(), // TODO: better
|
||||
selected: false,
|
||||
};
|
||||
x += rect_with_text.rect.width;
|
||||
@@ -46,16 +47,17 @@ impl TreeMap {
|
||||
} else {
|
||||
let mut y = self.empty_space.y;
|
||||
let mut row_width = 0.0;
|
||||
for file_percentage in row {
|
||||
let size = file_percentage.percentage * self.total_size;
|
||||
for file_metadata in row {
|
||||
let size = file_metadata.percentage * self.total_size;
|
||||
let height = (size / row_total) * self.empty_space.height as f64;
|
||||
let width = size / height;
|
||||
|
||||
let mut rect_with_text = RectWithText {
|
||||
let mut rect_with_text = FileSizeRect {
|
||||
rect: RectFloat { x: self.empty_space.x, y, width: width, height: height },
|
||||
text: file_percentage.file_name.clone(),
|
||||
file_name: file_percentage.actual_file_name.clone(), // TODO: better
|
||||
selected: false,
|
||||
file_metadata,
|
||||
// text: file_percentage.file_name.clone(),
|
||||
// file_name: file_percentage.actual_file_name.clone(), // TODO: better
|
||||
selected: false,
|
||||
};
|
||||
y += rect_with_text.rect.height;
|
||||
if row_width > width {
|
||||
@@ -71,9 +73,9 @@ impl TreeMap {
|
||||
}
|
||||
}
|
||||
|
||||
fn worst (&self, row: Vec<FilePercentage>, length: f64) -> f64 {
|
||||
let sum = row.iter().fold(0.0, |accum, file_percentage| {
|
||||
let size = file_percentage.percentage * self.total_size;
|
||||
fn worst (&self, row: Vec<FileMetadata>, length: f64) -> f64 {
|
||||
let sum = row.iter().fold(0.0, |accum, file_metadata| {
|
||||
let size = file_metadata.percentage * self.total_size;
|
||||
accum + size
|
||||
});
|
||||
let mut worst_aspect_ratio = 0.0;
|
||||
@@ -99,7 +101,7 @@ impl TreeMap {
|
||||
worst_aspect_ratio
|
||||
}
|
||||
|
||||
pub fn squarify (&mut self, mut children: Vec<FilePercentage>, row: Vec<FilePercentage>) {
|
||||
pub fn squarify (&mut self, mut children: Vec<FileMetadata>, row: Vec<FileMetadata>) {
|
||||
let length = if self.empty_space.height * HEIGHT_WIDTH_RATIO < self.empty_space.width {
|
||||
self.empty_space.height * 2.5
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user