diff --git a/README.md b/README.md index 7c21314..02e8d1f 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,12 @@ The color can be `#RGB[A]` or `#RRGGBB[AA]` silicon ./target/test.rs -o test.png --background '#fff0' ``` +Show window title + +```bash +silicon ./target/test.rs -o test.png --window-title "target/test.rs" +``` + see `silicon --help` for detail ## Adding new syntaxes / themes diff --git a/src/bin/silicon/config.rs b/src/bin/silicon/config.rs index af7385e..516dde3 100644 --- a/src/bin/silicon/config.rs +++ b/src/bin/silicon/config.rs @@ -150,6 +150,10 @@ pub struct Config { #[structopt(long)] pub no_window_controls: bool, + /// Show window title + #[structopt(long, value_name = "WINDOW_TITLE")] + pub window_title: Option, + /// Hide the line number. #[structopt(long)] pub no_line_number: bool, @@ -269,10 +273,10 @@ impl Config { let formatter = ImageFormatterBuilder::new() .line_pad(self.line_pad) .window_controls(!self.no_window_controls) + .window_title(self.window_title.clone()) .line_number(!self.no_line_number) .font(self.font.clone().unwrap_or_default()) .round_corner(!self.no_round_corner) - .window_controls(!self.no_window_controls) .shadow_adder(self.get_shadow_adder()?) .tab_width(self.tab_width) .highlight_lines(self.highlight_lines.clone().unwrap_or_default()) diff --git a/src/formatter.rs b/src/formatter.rs index b021300..7a6c3f3 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -15,6 +15,19 @@ pub struct ImageFormatter { /// pad of top of the code area /// Default: 50 code_pad_top: u32, + /// Title bar padding + /// Default: 15 + title_bar_pad: u32, + /// Whether to show window controls or not + window_controls: bool, + /// Width for window controls + /// Default: 120 + window_controls_width: u32, + /// Height for window controls + /// Default: 40 + window_controls_height: u32, + /// Window title + window_title: Option, /// show line number /// Default: true line_number: bool, @@ -52,6 +65,8 @@ pub struct ImageFormatterBuilder { highlight_lines: Vec, /// Whether show the window controls window_controls: bool, + /// Window title + window_title: Option, /// Whether round the corner of the image round_corner: bool, /// Shadow adder, @@ -69,6 +84,7 @@ impl + Default> ImageFormatterBuilder { line_pad: 2, line_number: true, window_controls: true, + window_title: None, round_corner: true, tab_width: 4, ..Default::default() @@ -105,6 +121,12 @@ impl + Default> ImageFormatterBuilder { self } + /// Window title + pub fn window_title(mut self, title: Option) -> Self { + self.window_title = title; + self + } + /// Whether round the corner pub fn round_corner(mut self, b: bool) -> Self { self.round_corner = b; @@ -136,11 +158,17 @@ impl + Default> ImageFormatterBuilder { FontCollection::new(&self.font)? }; - let code_pad_top = if self.window_controls { 50 } else { 0 }; + let title_bar = self.window_controls || self.window_title.is_some(); Ok(ImageFormatter { line_pad: self.line_pad, code_pad: 25, + code_pad_top: if title_bar { 50 } else { 0 }, + title_bar_pad: 15, + window_controls: self.window_controls, + window_controls_width: 120, + window_controls_height: 40, + window_title: self.window_title, line_number: self.line_number, line_number_pad: 6, line_number_chars: 0, @@ -148,7 +176,6 @@ impl + Default> ImageFormatterBuilder { round_corner: self.round_corner, shadow_adder: self.shadow_adder, tab_width: self.tab_width, - code_pad_top, font, line_offset: self.line_offset, }) @@ -161,7 +188,7 @@ struct Drawable { /// max number of line of the picture max_lineno: u32, /// arguments for draw_text_mut - drawables: Vec<(u32, u32, Color, FontStyle, String)>, + drawables: Vec<(u32, u32, Option, FontStyle, String)>, } impl ImageFormatter { @@ -214,7 +241,7 @@ impl ImageFormatter { drawables.push(( width, height, - style.foreground, + Some(style.foreground), style.font_style.into(), text.to_owned(), )); @@ -226,6 +253,29 @@ impl ImageFormatter { max_lineno = i as u32; } + if self.window_title.is_some() { + let title = self.window_title.as_ref().unwrap(); + let title_width = self.font.get_text_len(title); + + let ctrls_offset = if self.window_controls { + self.window_controls_width + self.title_bar_pad + } else { + 0 + }; + let ctrls_center = self.window_controls_height / 2; + + drawables.push(( + ctrls_offset + self.title_bar_pad, + self.title_bar_pad + ctrls_center - self.font.get_font_height() / 2, + None, + FontStyle::BOLD, + title.to_string(), + )); + + let title_bar_width = ctrls_offset + title_width + self.title_bar_pad * 2; + max_width = max_width.max(title_bar_width); + } + Drawable { max_width, max_lineno, @@ -288,10 +338,8 @@ impl ImageFormatter { let foreground = theme.settings.foreground.unwrap(); let background = theme.settings.background.unwrap(); - let foreground = foreground.to_rgba(); - let background = background.to_rgba(); - - let mut image = DynamicImage::ImageRgba8(RgbaImage::from_pixel(size.0, size.1, background)); + let mut image = + DynamicImage::ImageRgba8(RgbaImage::from_pixel(size.0, size.1, background.to_rgba())); if !self.highlight_lines.is_empty() { let highlight_lines = self @@ -302,18 +350,23 @@ impl ImageFormatter { self.highlight_lines(&mut image, highlight_lines); } if self.line_number { - self.draw_line_number(&mut image, drawables.max_lineno, foreground); + self.draw_line_number(&mut image, drawables.max_lineno, foreground.to_rgba()); } for (x, y, color, style, text) in drawables.drawables { - let color = color.to_rgba(); + let color = color.unwrap_or(foreground).to_rgba(); self.font .draw_text_mut(&mut image, color, x, y, style, &text); } - // draw_window_controls == true - if self.code_pad_top != 0 { - add_window_controls(&mut image); + if self.window_controls { + let params = WindowControlsParams { + width: self.window_controls_width, + height: self.window_controls_height, + padding: self.title_bar_pad, + radius: self.window_controls_width / 3 / 4, + }; + add_window_controls(&mut image, ¶ms); } if self.round_corner { diff --git a/src/utils.rs b/src/utils.rs index 5145cfb..4214099 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -66,8 +66,15 @@ impl ToRgba for syntect::highlighting::Color { } } +pub struct WindowControlsParams { + pub width: u32, + pub height: u32, + pub padding: u32, + pub radius: u32, +} + /// Add the window controls for image -pub(crate) fn add_window_controls(image: &mut DynamicImage) { +pub(crate) fn add_window_controls(image: &mut DynamicImage, params: &WindowControlsParams) { let color = [ ("#FF5F56", "#E0443E"), ("#FFBD2E", "#DEA123"), @@ -77,27 +84,40 @@ pub(crate) fn add_window_controls(image: &mut DynamicImage) { let mut background = image.get_pixel(37, 37); background.0[3] = 0; - let mut title_bar = RgbaImage::from_pixel(120 * 3, 40 * 3, background); + let mut title_bar = RgbaImage::from_pixel(params.width * 3, params.height * 3, background); + let step = (params.radius * 2) as i32; + let spacer = (step * 2) as i32; + let center_y = (params.height / 2) as i32; for (i, (fill, outline)) in color.iter().enumerate() { draw_filled_circle_mut( &mut title_bar, - (((i * 40) as i32 + 20) * 3, 20 * 3), - 11 * 3, + ((i as i32 * spacer + step) * 3, center_y * 3), + (params.radius + 1) as i32 * 3, outline.to_rgba().unwrap(), ); draw_filled_circle_mut( &mut title_bar, - (((i * 40) as i32 + 20) * 3, 20 * 3), - 10 * 3, + ((i as i32 * spacer + step) * 3, center_y * 3), + params.radius as i32 * 3, fill.to_rgba().unwrap(), ); } // create a big image and resize it to blur the edge // it looks better than `blur()` - let title_bar = resize(&title_bar, 120, 40, FilterType::Triangle); + let title_bar = resize( + &title_bar, + params.width, + params.height, + FilterType::Triangle, + ); - copy_alpha(&title_bar, image.as_mut_rgba8().unwrap(), 15, 15); + copy_alpha( + &title_bar, + image.as_mut_rgba8().unwrap(), + params.padding, + params.padding, + ); } #[derive(Clone, Debug)]