color_scale.rs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. use ansiterm::{Colour, Style};
  2. use palette::{FromColor, Oklab, Srgb};
  3. use crate::{
  4. fs::{dir_action::RecurseOptions, feature::git::GitCache, fields::Size, DotFilter, File},
  5. output::{table::TimeType, tree::TreeDepth},
  6. };
  7. #[derive(PartialEq, Eq, Debug, Copy, Clone)]
  8. pub struct ColorScaleOptions {
  9. pub mode: ColorScaleMode,
  10. pub min_luminance: isize,
  11. pub size: bool,
  12. pub age: bool,
  13. }
  14. #[derive(PartialEq, Eq, Debug, Copy, Clone)]
  15. pub enum ColorScaleMode {
  16. Fixed,
  17. Gradient,
  18. }
  19. #[derive(Copy, Clone, Debug)]
  20. pub struct ColorScaleInformation {
  21. pub options: ColorScaleOptions,
  22. pub accessed: Option<Extremes>,
  23. pub changed: Option<Extremes>,
  24. pub created: Option<Extremes>,
  25. pub modified: Option<Extremes>,
  26. pub size: Option<Extremes>,
  27. }
  28. impl ColorScaleInformation {
  29. pub fn from_color_scale(
  30. color_scale: ColorScaleOptions,
  31. files: &[File<'_>],
  32. dot_filter: DotFilter,
  33. git: Option<&GitCache>,
  34. git_ignoring: bool,
  35. r: Option<RecurseOptions>,
  36. ) -> Option<Self> {
  37. if color_scale.mode == ColorScaleMode::Fixed {
  38. None
  39. } else {
  40. let mut information = Self {
  41. options: color_scale,
  42. accessed: None,
  43. changed: None,
  44. created: None,
  45. modified: None,
  46. size: None,
  47. };
  48. update_information_recursively(
  49. &mut information,
  50. files,
  51. dot_filter,
  52. git,
  53. git_ignoring,
  54. TreeDepth::root(),
  55. r,
  56. );
  57. Some(information)
  58. }
  59. }
  60. pub fn adjust_style(&self, mut style: Style, value: f32, range: Option<Extremes>) -> Style {
  61. if let (Some(fg), Some(range)) = (style.foreground, range) {
  62. let mut ratio = ((value - range.min) / (range.max - range.min)).clamp(0.0, 1.0);
  63. if ratio.is_nan() {
  64. ratio = 1.0;
  65. }
  66. style.foreground = Some(adjust_luminance(
  67. fg,
  68. ratio,
  69. self.options.min_luminance as f32 / 100.0,
  70. ));
  71. }
  72. style
  73. }
  74. pub fn apply_time_gradient(&self, style: Style, file: &File<'_>, time_type: TimeType) -> Style {
  75. let range = match time_type {
  76. TimeType::Modified => self.modified,
  77. TimeType::Changed => self.changed,
  78. TimeType::Accessed => self.accessed,
  79. TimeType::Created => self.created,
  80. };
  81. if let Some(file_time) = time_type.get_corresponding_time(file) {
  82. self.adjust_style(style, file_time.timestamp_millis() as f32, range)
  83. } else {
  84. style
  85. }
  86. }
  87. }
  88. fn update_information_recursively(
  89. information: &mut ColorScaleInformation,
  90. files: &[File<'_>],
  91. dot_filter: DotFilter,
  92. git: Option<&GitCache>,
  93. git_ignoring: bool,
  94. depth: TreeDepth,
  95. r: Option<RecurseOptions>,
  96. ) {
  97. for file in files {
  98. if information.options.age {
  99. Extremes::update(
  100. file.created_time().map(|x| x.timestamp_millis() as f32),
  101. &mut information.created,
  102. );
  103. Extremes::update(
  104. file.modified_time().map(|x| x.timestamp_millis() as f32),
  105. &mut information.modified,
  106. );
  107. Extremes::update(
  108. file.accessed_time().map(|x| x.timestamp_millis() as f32),
  109. &mut information.accessed,
  110. );
  111. Extremes::update(
  112. file.changed_time().map(|x| x.timestamp_millis() as f32),
  113. &mut information.changed,
  114. );
  115. }
  116. if information.options.size {
  117. let size = match file.size() {
  118. Size::Some(size) => Some(size as f32),
  119. _ => None,
  120. };
  121. Extremes::update(size, &mut information.size);
  122. }
  123. if file.is_directory() && r.is_some_and(|x| !x.is_too_deep(depth.0)) {
  124. match file.to_dir() {
  125. Ok(dir) => {
  126. let files: Vec<File<'_>> = dir
  127. .files(dot_filter, git, git_ignoring, false, false)
  128. .flatten()
  129. .collect();
  130. update_information_recursively(
  131. information,
  132. &files,
  133. dot_filter,
  134. git,
  135. git_ignoring,
  136. depth.deeper(),
  137. r,
  138. );
  139. }
  140. Err(_) => todo!(),
  141. }
  142. };
  143. }
  144. }
  145. #[derive(Copy, Clone, Debug)]
  146. pub struct Extremes {
  147. max: f32,
  148. min: f32,
  149. }
  150. impl Extremes {
  151. fn update(maybe_value: Option<f32>, maybe_range: &mut Option<Extremes>) {
  152. match (maybe_value, maybe_range) {
  153. (Some(value), Some(range)) => {
  154. if value > range.max {
  155. range.max = value;
  156. } else if value < range.min {
  157. range.min = value;
  158. };
  159. }
  160. (Some(value), rel) => {
  161. let _ = rel.insert({
  162. Extremes {
  163. max: value,
  164. min: value,
  165. }
  166. });
  167. }
  168. _ => (),
  169. };
  170. }
  171. }
  172. fn adjust_luminance(color: Colour, x: f32, min_l: f32) -> Colour {
  173. let color = Srgb::from_components(color.into_rgb()).into_linear();
  174. let mut lab: Oklab = Oklab::from_color(color);
  175. lab.l = (min_l + (1.0 - min_l) * (-4.0 * (1.0 - x)).exp()).clamp(0.0, 1.0);
  176. let adjusted_rgb: Srgb<f32> = Srgb::from_color(lab);
  177. Colour::RGB(
  178. (adjusted_rgb.red * 255.0).round() as u8,
  179. (adjusted_rgb.green * 255.0).round() as u8,
  180. (adjusted_rgb.blue * 255.0).round() as u8,
  181. )
  182. }