diff --git a/src/bin/schedule/github_notifications.rs b/src/bin/schedule/github_notifications.rs index c461f13..4b88f58 100644 --- a/src/bin/schedule/github_notifications.rs +++ b/src/bin/schedule/github_notifications.rs @@ -1,13 +1,15 @@ -use std::cell::RefCell; +use std::{cell::RefCell, collections::HashSet}; use color_space::{Hsv, ToRgb}; use embedded_graphics::{ mono_font::{iso_8859_10::FONT_8X13, MonoTextStyleBuilder}, pixelcolor::Rgb565, prelude::{DrawTarget, Point, RgbColor}, + primitives::Rectangle, text::Text, - Drawable, + Drawable, Pixel, }; +use rand_xoshiro::rand_core::RngCore; use raspi_oled::github::get_new_notifications; use crate::{ @@ -42,7 +44,6 @@ impl> Schedule for GithubNotifications { return; } let max_lines = 8; - let max_line_length = 16; let mut lines = vec![]; let mut relevant = relevant.into_iter(); while lines.len() < max_lines { @@ -59,9 +60,7 @@ impl> Schedule for GithubNotifications { } lines.push(format!("{} #{}", parts[5], parts[7])); if lines.len() < max_lines { - let mut desc = format!(" {}", x.subject.title); - desc.truncate(desc.floor_char_boundary(max_line_length)); - lines.push(desc); + lines.push(x.subject.title.clone()); } } else { break; @@ -75,6 +74,7 @@ impl> Schedule for GithubNotifications { calls: RefCell::new(0), screen: &GITHUB, lines, + circles: RefCell::new(vec![]), })); } } @@ -84,36 +84,108 @@ struct GithubNotificationsDraw { calls: RefCell, screen: &'static SimpleScreensaver, lines: Vec, + circles: RefCell)>>, } impl> Draw for GithubNotificationsDraw { - fn draw(&self, disp: &mut D, _rng: &mut crate::Rng) -> Result { + fn draw(&self, disp: &mut D, rng: &mut crate::Rng) -> Result { let calls = *self.calls.borrow(); - if calls < 40 { + if calls == 0 { + self.screen + .draw_all_colored(disp, Rgb565::new(0xff >> 3, 0xff >> 2, 0xff >> 3))?; + } + if calls < 70 { let hue = calls as f64 / 40.0 * 360.0; let hsv = Hsv::new(hue, 1.0, 1.0); let rgb = hsv.to_rgb(); let r = rgb.r as u8 >> 3; let g = rgb.g as u8 >> 2; let b = rgb.b as u8 >> 3; - self.screen.draw_all_colored(disp, Rgb565::new(r, g, b))?; + let rgb = Rgb565::new(r, g, b); + let mut x; + let mut y; + loop { + x = rng.next_u32() % 128; + y = rng.next_u32() % 128; + if self.screen.get_pixel(x, y) == Rgb565::WHITE { + break; + } + } + disp.fill_contiguous(&Rectangle::new((x as i32, y as i32).into(), (1, 1).into()), Some(rgb))?; + // advance other circles + let mut circles = self.circles.borrow_mut(); + for ((ox, oy), radius, color, points) in &mut *circles { + *radius += 1; + let mut seen = HashSet::new(); + let mut next_points = vec![]; + loop { + let mut new_points = vec![]; + for (x, y) in &*points { + for (dx, dy) in [(-1, 0), (0, -1), (1, 0), (0, 1)] { + let Some(x) = x.checked_add_signed(dx) else { + continue; + }; + let Some(y) = y.checked_add_signed(dy) else { + continue; + }; + if x >= 128 || y >= 128 { + continue; + } + if self.screen.get_pixel(x, y) != Rgb565::WHITE { + continue; + } + if seen.contains(&(x, y)) { + continue; + } + seen.insert((x, y)); + let dist2 = x.abs_diff(*ox).pow(2) + y.abs_diff(*oy).pow(2); + if dist2 > (*radius - 1).pow(2) && dist2 <= (*radius).pow(2) { + new_points.push((x, y)); + } + } + } + if new_points.is_empty() { + break; + } + next_points.extend_from_slice(&new_points); + *points = new_points; + } + disp.draw_iter( + next_points + .iter() + .map(|&x| Pixel(Point::new(x.0 as _, x.1 as _), *color)), + )?; + *points = next_points; + } + circles.push(((x, y), 0, rgb, vec![(x, y)])); } else { + let idx = calls - 70; disp.clear(Rgb565::BLACK)?; // fit 9 lines + let max_line_length = 16; let text_style_clock = MonoTextStyleBuilder::new() .font(&FONT_8X13) .text_color(Rgb565::WHITE) .build(); for (y, line) in self.lines.iter().enumerate() { - Text::new(line, Point::new(0, (12 + y * 14) as _), text_style_clock).draw(disp)?; + let mut line = if calls >= 139 { + format!(" {line}") + } else if line.len() > max_line_length { + let line = format!(" {line} "); + line[line.ceil_char_boundary(idx % line.len())..].to_string() + } else { + line.clone() + }; + line.truncate(line.floor_char_boundary(max_line_length)); + Text::new(&line, Point::new(0, (12 + y * 14) as _), text_style_clock).draw(disp)?; } } *self.calls.borrow_mut() += 1; - Ok(calls < 90) + Ok(calls < 140) } fn expired(&self) -> bool { - *self.calls.borrow() > 90 + *self.calls.borrow() > 140 } fn as_any(&self) -> &dyn std::any::Any { diff --git a/src/bin/screensaver/mod.rs b/src/bin/screensaver/mod.rs index 7420177..beb4106 100644 --- a/src/bin/screensaver/mod.rs +++ b/src/bin/screensaver/mod.rs @@ -105,6 +105,12 @@ impl SimpleScreensaver { } } + pub fn get_pixel(&self, x: u32, y: u32) -> Rgb565 { + let idx = y as usize * 128 + x as usize; + let (red, green, blue) = (self.data[3 * idx], self.data[3 * idx + 1], self.data[3 * idx + 2]); + Rgb565::new(red >> 3, green >> 2, blue >> 3) + } + pub fn draw_all_colored>(&self, disp: &mut D, color: Rgb565) -> Result<(), D::Error> { disp.fill_contiguous( &Rectangle::new((0, 0).into(), (128, 128).into()),