refactor(types): file metadata

This commit is contained in:
Aram Drevekenin
2020-03-28 17:57:23 +01:00
parent 6ea7e1b503
commit 8c55710dcd
5 changed files with 121 additions and 69 deletions

View File

@@ -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();
}

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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, &currently_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, &currently_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) {

View File

@@ -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 {