diff --git a/README.md b/README.md index 2cbbcd7..1405c16 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,6 @@ height = 150 [display] image_size = 64 - -rounding = 10 padding = 8 timeout = 2000 @@ -58,12 +56,13 @@ foreground = "#1E1E2E" [display.border] size = 4 +radius = 10 color = "#000" [[app]] name = "Telegram Desktop" [app.display] -rounding = 8 +border = { radius = 8 } markup = true [app.display.body] diff --git a/src/config.rs b/src/config.rs index 56b5cc3..2dbe93f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -225,7 +225,6 @@ impl From for Anchor { pub struct DisplayConfig { image_size: Option, - rounding: Option, padding: Option, border: Option, @@ -243,10 +242,6 @@ impl DisplayConfig { self.image_size.unwrap() } - pub fn rounding(&self) -> u8 { - self.rounding.unwrap() - } - pub fn padding(&self) -> u8 { self.padding.unwrap() } @@ -280,10 +275,6 @@ impl DisplayConfig { self.image_size = Some(64); } - if self.rounding.is_none() { - self.rounding = Some(0); - } - if self.padding.is_none() { self.padding = Some(0); } @@ -435,6 +426,7 @@ impl From for Color { #[derive(Debug, Deserialize, Default, Clone)] pub struct Border { size: Option, + radius: Option, color: Option, } @@ -443,6 +435,10 @@ impl Border { self.size.unwrap() } + pub fn radius(&self) -> u8 { + self.radius.unwrap() + } + pub fn color(&self) -> &Color { self.color.as_ref().unwrap() } @@ -452,6 +448,10 @@ impl Border { self.size = Some(0); } + if self.radius.is_none() { + self.radius = Some(0); + } + if self.color.is_none() { self.color = Some(Default::default()); } @@ -528,12 +528,13 @@ impl AppConfig { if let Some(display) = self.display.as_mut() { display.image_size = display.image_size.or(other.image_size); - display.rounding = display.rounding.or(other.rounding); display.padding = display.padding.or(other.padding); if let Some(border) = display.border.as_mut() { let other_border = other.border(); // The other type shall have border + border.size = border.size.or(other_border.size); + border.radius = border.radius.or(other_border.radius); border.color = border.color.clone().or(other_border.color.clone()); } else { display.border = other.border.clone(); diff --git a/src/render/border.rs b/src/render/border.rs index 7086001..fe05b4a 100644 --- a/src/render/border.rs +++ b/src/render/border.rs @@ -11,121 +11,164 @@ pub(crate) struct Border { frame_height: usize, #[builder(setter(into))] - width: Option, + size: usize, #[builder(setter(into))] - radius: Option, + radius: usize, +} + +enum Corner { + TopLeft, + TopRight, + BottomLeft, + BottomRight, } impl Border { pub(crate) fn draw(&self, mut callback: O) { - let coverage = match (self.width, self.radius) { - (None, None) => return, - (Some(width), None) => self.get_bordered_coverage(width), - (None, Some(radius)) => self.get_rounding_coverage(radius), - (Some(width), Some(radius)) => self.get_bordered_rounding_coverage(width, radius), + let coverage = match (self.size, self.radius) { + (0, 0) => return, + (width, 0) => self.get_bordered_coverage(width), + (0, radius) => self.get_corner_coverage(radius), + (width, radius) => self.get_bordered_corner_coverage(width, radius), }; let coverage_size = coverage.len(); - - for (frame_y, y) in (0..coverage_size).zip((0..coverage_size).rev()) { - for (frame_x, x) in (0..coverage_size).zip((0..coverage_size).rev()) { - callback(frame_x, frame_y, coverage[x][y].clone()); - } + Self::draw_corner(0, 0, &coverage, Corner::TopLeft, &mut callback); + Self::draw_corner( + self.frame_width - coverage_size, + 0, + &coverage, + Corner::TopRight, + &mut callback, + ); + Self::draw_corner( + self.frame_width - coverage_size, + self.frame_height - coverage_size, + &coverage, + Corner::BottomRight, + &mut callback, + ); + Self::draw_corner( + 0, + self.frame_height - coverage_size, + &coverage, + Corner::BottomLeft, + &mut callback, + ); + + if self.size != 0 { + // Top + self.draw_rectangle( + coverage_size, + 0, + self.frame_width - coverage_size * 2, + self.size, + &mut callback, + ); + + // Bottom + self.draw_rectangle( + coverage_size, + self.frame_height - self.size, + self.frame_width - coverage_size * 2, + self.size, + &mut callback, + ); + + // Left + self.draw_rectangle( + 0, + coverage_size, + self.size, + self.frame_height - coverage_size * 2, + &mut callback, + ); + + // Right + self.draw_rectangle( + self.frame_width - self.size, + coverage_size, + self.size, + self.frame_height - coverage_size * 2, + &mut callback, + ); } + } - for (frame_y, y) in (0..coverage_size).zip((0..coverage_size).rev()) { - for (frame_x, x) in - (self.frame_width - coverage_size..self.frame_width).zip(0..coverage_size) - { - callback(frame_x, frame_y, coverage[x][y].clone()); - } - } + #[inline] + fn get_bordered_coverage(&self, width: usize) -> Vec>> { + vec![vec![Some(self.color.clone()); width]; width] + } - for (frame_y, y) in - (self.frame_height - coverage_size..self.frame_height).zip(0..coverage_size) - { - for (frame_x, x) in - (self.frame_width - coverage_size..self.frame_width).zip(0..coverage_size) - { - callback(frame_x, frame_y, coverage[x][y].clone()); - } - } + fn get_corner_coverage(&self, radius: usize) -> Vec>> { + let mut coverage = vec![vec![None; radius]; radius]; + self.traverse_circle_with(radius, |inner_x, inner_y, rev_x, rev_y| { + let cell_coverage = Self::get_coverage_by(radius as f32, inner_x as f32, inner_y as f32); - for (frame_y, y) in - (self.frame_height - coverage_size..self.frame_height).zip(0..coverage_size) - { - for (frame_x, x) in (0..coverage_size).zip((0..coverage_size).rev()) { - callback(frame_x, frame_y, coverage[x][y].clone()); + if cell_coverage == 1.0 { + return false; } - } - if let Some(width) = self.width { - for y in 0..width { - for x in coverage_size..self.frame_width - coverage_size { - callback(x, y, self.color.clone()) - } - } + let color = self.background_color.clone() * cell_coverage; + coverage[rev_x][rev_y] = Some(color.clone()); + coverage[rev_y][rev_x] = Some(color); - for y in self.frame_height - width..self.frame_height { - for x in coverage_size..self.frame_width - coverage_size { - callback(x, y, self.color.clone()) - } - } + true + }); - for x in 0..width { - for y in coverage_size..self.frame_height - coverage_size { - callback(x, y, self.color.clone()) - } - } + coverage + } - for x in self.frame_width - width..self.frame_width { - for y in coverage_size..self.frame_height - coverage_size { - callback(x, y, self.color.clone()) + fn get_bordered_corner_coverage(&self, width: usize, radius: usize) -> Vec>> { + let mut coverage = vec![vec![None; radius]; radius]; + let inner_radius = radius.saturating_sub(width); + + self.traverse_circle_with(radius, |inner_x, inner_y, rev_x, rev_y| { + let (x_f32, y_f32) = (rev_x as f32, rev_y as f32); + let outer_color = + self.color.clone() * Self::get_coverage_by(radius as f32, x_f32, y_f32); + + let inner_color = if inner_radius != 0 { + let inner_cell_coverage = Self::get_coverage_by(inner_radius as f32, x_f32, y_f32); + if inner_cell_coverage == 1.0 { + return false; } - } - } - } - fn get_bordered_coverage(&self, width: usize) -> Vec> { - vec![vec![self.color.clone(); width]; width] - } + self.background_color.clone() * inner_cell_coverage + } else { + Bgra::new() + }; - fn get_rounding_coverage(&self, radius: usize) -> Vec> { - let mut coverage = vec![vec![Bgra::new(); radius]; radius]; - for y in 0..radius { - for x in 0..radius { - coverage[x][y] = self.background_color.clone() - * Self::get_coverage_by(radius as f32, x as f32, y as f32); - } - } + let color = inner_color.overlay_on(&outer_color); + coverage[inner_x][inner_y] = Some(color.clone()); + coverage[inner_y][inner_x] = Some(color); + + true + }); coverage } - fn get_bordered_rounding_coverage(&self, width: usize, radius: usize) -> Vec> { - let mut coverage = vec![vec![Bgra::new(); radius]; radius]; - let inner_radius = radius.saturating_sub(width); - - for y in 0..radius { - for x in 0..radius { - let (x_f32, y_f32) = (x as f32, y as f32); - let outer_color = - self.color.clone() * Self::get_coverage_by(radius as f32, x_f32, y_f32); + fn traverse_circle_with bool>( + &self, + radius: usize, + mut calc: Calc, + ) { + for x in 0..radius { + let rev_x = radius - x - 1; - let inner_color = if inner_radius != 0 { - self.background_color.clone() - * Self::get_coverage_by(inner_radius as f32, x_f32, y_f32) - } else { - Bgra::new() - }; + for y in 0..radius { + let rev_y = radius - y - 1; + let to_continue = calc(x, y, rev_x, rev_y); - coverage[x][y] = inner_color.overlay_on(&outer_color); + if !to_continue { + break; + } } } - - coverage } + #[inline] fn get_coverage_by(radius: f32, x: f32, y: f32) -> f32 { let inner_hypot = f32::hypot(x, y); let inner_diff = radius - inner_hypot; @@ -139,4 +182,53 @@ impl Border { 1.0 } } + + #[inline] + fn draw_corner( + x_offset: usize, + y_offset: usize, + coverage: &Vec>>, + corner_type: Corner, + callback: &mut O, + ) { + let coverage_size = coverage.len(); + let mut x_range = x_offset..x_offset + coverage_size; + let y_range = y_offset..y_offset + coverage_size; + + let x_range: &mut dyn Iterator = match corner_type { + Corner::TopLeft | Corner::BottomLeft => &mut x_range, + Corner::TopRight | Corner::BottomRight => &mut x_range.rev(), + }; + + for (x, coverage_row) in x_range.zip(coverage) { + let y_range: &mut dyn Iterator = match corner_type { + Corner::TopLeft | Corner::TopRight => &mut y_range.clone(), + Corner::BottomLeft | Corner::BottomRight => &mut y_range.clone().rev(), + }; + + for (y, coverage_cell) in y_range.zip(coverage_row) { + if let Some(color) = coverage_cell { + callback(x, y, color.clone()); + } else { + break; + } + } + } + } + + #[inline] + fn draw_rectangle( + &self, + x_offset: usize, + y_offset: usize, + width: usize, + height: usize, + callback: &mut O, + ) { + for x in x_offset..width + x_offset { + for y in y_offset..height + y_offset { + callback(x, y, self.color.clone()) + } + } + } } diff --git a/src/render/layer.rs b/src/render/layer.rs index 2d18ba6..3c33f72 100644 --- a/src/render/layer.rs +++ b/src/render/layer.rs @@ -453,8 +453,8 @@ impl NotificationRect { let stride = width as usize * 4; let border = BorderBuilder::default() - .width(border_cfg.size() as usize) - .radius(display.rounding() as usize) + .size(border_cfg.size() as usize) + .radius(border_cfg.radius() as usize) .color(border_cfg.color().into()) .background_color(background.clone()) .frame_width(width as usize)