diff --git a/src/cli/cli.rs b/src/cli/cli.rs index 7a7945ef..2e09c388 100644 --- a/src/cli/cli.rs +++ b/src/cli/cli.rs @@ -506,6 +506,30 @@ pub fn build_cli() -> Command<'static> { .ignore_case(true) .help("Use a specific tool to pick the colors") ) + .arg( + Arg::new("layout") + .long("layout") + .value_name("layout") + .max_occurrences(1) + .help("Controls how the colors are printed") + .possible_values(["detail", "vertical", "horizontal"]) + .conflicts_with("layout_horizontal") + .conflicts_with("layout_vertical") + ) + .arg( + Arg::new("layout_horizontal") + .short('h') + .help("Alias for --layout horizontal") + .conflicts_with("layout") + .conflicts_with("layout_vertical") + ) + .arg( + Arg::new("layout_vertical") + .short('v') + .help("Alias for --layout vertical") + .conflicts_with("layout") + .conflicts_with("layout_horizontal") + ) } #[test] diff --git a/src/cli/commands/color_commands.rs b/src/cli/commands/color_commands.rs index c3aebcc2..e90b4eab 100644 --- a/src/cli/commands/color_commands.rs +++ b/src/cli/commands/color_commands.rs @@ -20,8 +20,8 @@ macro_rules! color_command { $config: &Config, $color: &Color, ) -> Result<()> { - let output = $body; - out.show_color($config, &output) + out.push_color($body); + Ok(()) } } }; @@ -77,7 +77,7 @@ color_command!(MixCommand, config, matches, color, { mix(&base, color, fraction) }); -color_command!(ColorblindCommand, config, matches, color, { +color_command!(ColorblindCommand, _config, matches, color, { // The type of colorblindness selected (protanopia, deuteranopia, tritanopia) let cb_ty = matches.value_of("type").expect("required argument"); let cb_ty = cb_ty.to_lowercase(); @@ -95,7 +95,7 @@ color_command!(ColorblindCommand, config, matches, color, { color.simulate_colorblindness(cb_ty) }); -color_command!(SetCommand, config, matches, color, { +color_command!(SetCommand, _config, matches, color, { let property = matches.value_of("property").expect("required argument"); let property = property.to_lowercase(); let property = property.as_ref(); diff --git a/src/cli/commands/distinct.rs b/src/cli/commands/distinct.rs index 6e3a7608..c5c82c3a 100644 --- a/src/cli/commands/distinct.rs +++ b/src/cli/commands/distinct.rs @@ -180,8 +180,8 @@ impl GenericCommand for DistinctCommand { print_distance_matrix(&mut stderr.lock(), brush_stderr, &colors, distance_metric)?; } - for color in colors { - out.show_color(config, &color)?; + for color in &colors { + out.push_color(color.clone()); } } diff --git a/src/cli/commands/gradient.rs b/src/cli/commands/gradient.rs index ccb22c2c..9d6ea8a3 100644 --- a/src/cli/commands/gradient.rs +++ b/src/cli/commands/gradient.rs @@ -42,10 +42,8 @@ impl GenericCommand for GradientCommand { let position = Fraction::from(i as f64 / (count as f64 - 1.0)); let color = color_scale.sample(position, &mix).expect("gradient color"); - - out.show_color(config, &color)?; + out.push_color(color); } - Ok(()) } } diff --git a/src/cli/commands/gray.rs b/src/cli/commands/gray.rs index c4f385ae..d98fb38b 100644 --- a/src/cli/commands/gray.rs +++ b/src/cli/commands/gray.rs @@ -5,9 +5,10 @@ use pastel::Color; pub struct GrayCommand; impl GenericCommand for GrayCommand { - fn run(&self, out: &mut Output, matches: &ArgMatches, config: &Config) -> Result<()> { + fn run(&self, out: &mut Output, matches: &ArgMatches, _config: &Config) -> Result<()> { let lightness = number_arg(matches, "lightness")?; let gray = Color::graytone(lightness); - out.show_color(config, &gray) + out.push_color(gray); + Ok(()) } } diff --git a/src/cli/commands/mod.rs b/src/cli/commands/mod.rs index 95c35ca7..bc64485e 100644 --- a/src/cli/commands/mod.rs +++ b/src/cli/commands/mod.rs @@ -79,9 +79,9 @@ impl Command { for color in ColorArgIterator::from_args(config, matches.values_of("color"))? { cmd.run(&mut out, matches, config, &color?)?; } - Ok(()) } - } + }?; + out.finish_colors(config) } } diff --git a/src/cli/commands/pick.rs b/src/cli/commands/pick.rs index da479f68..bba7351d 100644 --- a/src/cli/commands/pick.rs +++ b/src/cli/commands/pick.rs @@ -22,7 +22,7 @@ impl GenericCommand for PickCommand { for color_str in color_strings { let color = ColorArgIterator::from_color_arg(config, &color_str, &mut print_spectrum)?; - out.show_color(config, &color)?; + out.push_color(color); } Ok(()) diff --git a/src/cli/commands/random.rs b/src/cli/commands/random.rs index bdbc82f7..da756b61 100644 --- a/src/cli/commands/random.rs +++ b/src/cli/commands/random.rs @@ -6,7 +6,7 @@ use pastel::random::RandomizationStrategy; pub struct RandomCommand; impl GenericCommand for RandomCommand { - fn run(&self, out: &mut Output, matches: &ArgMatches, config: &Config) -> Result<()> { + fn run(&self, out: &mut Output, matches: &ArgMatches, _config: &Config) -> Result<()> { let strategy_arg = matches.value_of("strategy").expect("required argument"); let count = matches.value_of("number").expect("required argument"); @@ -23,9 +23,8 @@ impl GenericCommand for RandomCommand { }; for _ in 0..count { - out.show_color(config, &strategy.generate())?; + out.push_color(strategy.generate()); } - Ok(()) } } diff --git a/src/cli/commands/show.rs b/src/cli/commands/show.rs index 21f7503e..eac203a5 100644 --- a/src/cli/commands/show.rs +++ b/src/cli/commands/show.rs @@ -3,7 +3,8 @@ use crate::commands::prelude::*; pub struct ShowCommand; impl ColorCommand for ShowCommand { - fn run(&self, out: &mut Output, _: &ArgMatches, config: &Config, color: &Color) -> Result<()> { - out.show_color(config, color) + fn run(&self, out: &mut Output, _: &ArgMatches, _config: &Config, color: &Color) -> Result<()> { + out.push_color(color.clone()); + Ok(()) } } diff --git a/src/cli/commands/sort.rs b/src/cli/commands/sort.rs index 3f246021..0753a005 100644 --- a/src/cli/commands/sort.rs +++ b/src/cli/commands/sort.rs @@ -36,7 +36,7 @@ impl GenericCommand for SortCommand { } for color in colors { - out.show_color(config, &color)?; + out.push_color(color); } Ok(()) diff --git a/src/cli/config.rs b/src/cli/config.rs index d84517df..48f9c321 100644 --- a/src/cli/config.rs +++ b/src/cli/config.rs @@ -8,4 +8,12 @@ pub struct Config<'p> { pub colorpicker: Option<&'p str>, pub interactive_mode: bool, pub brush: Brush, + pub layout: Layout, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Layout { + Detail, + Vertical, + Horizontal, } diff --git a/src/cli/main.rs b/src/cli/main.rs index 5ce0c71e..caa60888 100644 --- a/src/cli/main.rs +++ b/src/cli/main.rs @@ -14,7 +14,7 @@ mod output; mod utility; use commands::Command; -use config::Config; +use config::{Config, Layout}; use error::{PastelError, Result}; use pastel::ansi::{self, Brush, Mode}; @@ -98,6 +98,9 @@ fn run() -> Result { } }; + let layout = std::env::var("PASTEL_LAYOUT").ok(); + let layout = layout.as_ref().map(String::as_str); + let layout = global_matches.value_of("layout").or(layout); let config = Config { padding: 2, colorpicker_width: 48, @@ -105,6 +108,18 @@ fn run() -> Result { interactive_mode, brush: Brush::from_mode(color_mode), colorpicker: global_matches.value_of("color-picker"), + layout: match layout { + Some("detail") => Layout::Detail, + Some("vertical") | Some("v") | Some("|") => Layout::Vertical, + Some("horizontal") | Some("h") | Some("-") => Layout::Horizontal, + Some(wrong) => { + write_stderr(Color::red(), "pastel error", &format!("Unknown layout {wrong:?}")); + std::process::exit(1); + }, + _ if global_matches.is_present("layout_horizontal") => Layout::Horizontal, + _ if global_matches.is_present("layout_vertical") => Layout::Vertical, + _ => Layout::Detail, + }, }; if let Some((subcommand, matches)) = global_matches.subcommand() { diff --git a/src/cli/output.rs b/src/cli/output.rs index c9209ad4..9d1d6523 100644 --- a/src/cli/output.rs +++ b/src/cli/output.rs @@ -1,119 +1,142 @@ use std::io::Write; -use crate::config::Config; +use crate::config::{Config, Layout}; use crate::error::Result; use crate::hdcanvas::Canvas; use crate::utility::similar_colors; +use pastel::ansi::ToAnsiStyle; use pastel::Color; use pastel::Format; // #[derive(Debug)] pub struct Output<'a> { pub handle: &'a mut dyn Write, - colors_shown: usize, + pub colors: Vec, } -impl Output<'_> { - pub fn new(handle: &mut dyn Write) -> Output { - Output { - handle, - colors_shown: 0, +pub fn show_color_tty(handle: &mut dyn Write, config: &Config, color: &Color) -> Result<()> { + if config.layout == Layout::Horizontal || config.layout == Layout::Vertical { + let color = config.brush.paint(" ", color.ansi_style().on(color)); + write!(handle, "{}", color)?; + if config.layout == Layout::Vertical { + writeln!(handle)?; } + return Ok(()); } + let checkerboard_size: usize = 16; + let color_panel_size: usize = 12; - pub fn show_color_tty(&mut self, config: &Config, color: &Color) -> Result<()> { - let checkerboard_size: usize = 16; - let color_panel_size: usize = 12; - - let checkerboard_position_y: usize = 0; - let checkerboard_position_x: usize = config.padding; - let color_panel_position_y: usize = - checkerboard_position_y + (checkerboard_size - color_panel_size) / 2; - let color_panel_position_x: usize = - config.padding + (checkerboard_size - color_panel_size) / 2; - let text_position_x: usize = checkerboard_size + 2 * config.padding; - let text_position_y: usize = 0; - - let mut canvas = Canvas::new(checkerboard_size, 60, config.brush); - canvas.draw_checkerboard( - checkerboard_position_y, - checkerboard_position_x, - checkerboard_size, - checkerboard_size, - &Color::graytone(0.94), - &Color::graytone(0.71), - ); - canvas.draw_rect( - color_panel_position_y, - color_panel_position_x, - color_panel_size, - color_panel_size, - color, - ); + let checkerboard_position_y: usize = 0; + let checkerboard_position_x: usize = config.padding; + let color_panel_position_y: usize = + checkerboard_position_y + (checkerboard_size - color_panel_size) / 2; + let color_panel_position_x: usize = + config.padding + (checkerboard_size - color_panel_size) / 2; + let text_position_x: usize = checkerboard_size + 2 * config.padding; + let text_position_y: usize = 0; - let mut text_y_offset = 0; - let similar = similar_colors(color); - - for (i, nc) in similar.iter().enumerate().take(3) { - if nc.color == *color { - canvas.draw_text( - text_position_y, - text_position_x, - &format!("Name: {}", nc.name), - ); - text_y_offset = 2; - continue; - } + let mut canvas = Canvas::new(checkerboard_size, 60, config.brush); + canvas.draw_checkerboard( + checkerboard_position_y, + checkerboard_position_x, + checkerboard_size, + checkerboard_size, + &Color::graytone(0.94), + &Color::graytone(0.71), + ); + canvas.draw_rect( + color_panel_position_y, + color_panel_position_x, + color_panel_size, + color_panel_size, + color, + ); + + let mut text_y_offset = 0; + let similar = similar_colors(color); - canvas.draw_text(text_position_y + 10 + 2 * i, text_position_x + 7, nc.name); - canvas.draw_rect( - text_position_y + 10 + 2 * i, - text_position_x + 1, - 2, - 5, - &nc.color, + for (i, nc) in similar.iter().enumerate().take(3) { + if nc.color == *color { + canvas.draw_text( + text_position_y, + text_position_x, + &format!("Name: {}", nc.name), ); + text_y_offset = 2; + continue; } - #[allow(clippy::identity_op)] - canvas.draw_text( - text_position_y + 0 + text_y_offset, - text_position_x, - &format!("Hex: {}", color.to_rgb_hex_string(true)), - ); - canvas.draw_text( - text_position_y + 2 + text_y_offset, - text_position_x, - &format!("RGB: {}", color.to_rgb_string(Format::Spaces)), - ); - canvas.draw_text( - text_position_y + 4 + text_y_offset, - text_position_x, - &format!("HSL: {}", color.to_hsl_string(Format::Spaces)), + canvas.draw_text(text_position_y + 10 + 2 * i, text_position_x + 7, nc.name); + canvas.draw_rect( + text_position_y + 10 + 2 * i, + text_position_x + 1, + 2, + 5, + &nc.color, ); + } - canvas.draw_text( - text_position_y + 8 + text_y_offset, - text_position_x, - "Most similar:", - ); + #[allow(clippy::identity_op)] + canvas.draw_text( + text_position_y + 0 + text_y_offset, + text_position_x, + &format!("Hex: {}", color.to_rgb_hex_string(true)), + ); + canvas.draw_text( + text_position_y + 2 + text_y_offset, + text_position_x, + &format!("RGB: {}", color.to_rgb_string(Format::Spaces)), + ); + canvas.draw_text( + text_position_y + 4 + text_y_offset, + text_position_x, + &format!("HSL: {}", color.to_hsl_string(Format::Spaces)), + ); - canvas.print(self.handle) + canvas.draw_text( + text_position_y + 8 + text_y_offset, + text_position_x, + "Most similar:", + ); + + canvas.print(handle) +} + +impl Output<'_> { + pub fn new(handle: &mut dyn Write) -> Output { + Output { + handle, + colors: vec![], + } + } + + pub fn push_color(&mut self, color: Color) { + self.colors.push(color); } - pub fn show_color(&mut self, config: &Config, color: &Color) -> Result<()> { - if config.interactive_mode { - if self.colors_shown < 1 { - writeln!(self.handle)? - }; - self.show_color_tty(config, color)?; + pub fn finish_colors(mut self, config: &Config) -> Result<()> { + let mut first = true; + for color in &self.colors { + if config.interactive_mode { + if config.layout == Layout::Detail { + if first { + writeln!(self.handle)? + }; + show_color_tty(&mut self.handle, config, color)?; + writeln!(self.handle)?; + } else { + show_color_tty(&mut self.handle, config, color)?; + } + } else { + writeln!(self.handle, "{}", color.to_hsl_string(Format::NoSpaces))?; + } + first = false; + } + if !first && config.layout == Layout::Horizontal { writeln!(self.handle)?; - } else { - writeln!(self.handle, "{}", color.to_hsl_string(Format::NoSpaces))?; } - self.colors_shown += 1; - Ok(()) } + }