use gpui::{
canvas, div, point, prelude::FluentBuilder, px, size, App, Bounds, Div, IntoElement,
ParentElement, Pixels, RenderOnce, Styled, Window,
};
use crate::theme::{ActiveTheme, Themeable};
pub fn grain() -> Grain {
Grain::new()
}
#[derive(IntoElement)]
pub struct Grain {
width: Option<Pixels>,
height: Option<Pixels>,
full_width: bool,
full_height: bool,
absolute: bool,
intensity: f32,
spacing: f32,
dot_size: f32,
light: bool,
}
impl Grain {
pub fn new() -> Self {
Self {
width: None,
height: None,
full_width: false,
full_height: false,
absolute: false,
intensity: 0.06,
spacing: 4.0,
dot_size: 1.0,
light: false,
}
}
pub fn w(mut self, width: Pixels) -> Self {
self.width = Some(width);
self
}
pub fn h(mut self, height: Pixels) -> Self {
self.height = Some(height);
self
}
pub fn size(mut self, size: Pixels) -> Self {
self.width = Some(size);
self.height = Some(size);
self
}
pub fn w_full(mut self) -> Self {
self.full_width = true;
self
}
pub fn h_full(mut self) -> Self {
self.full_height = true;
self
}
pub fn size_full(mut self) -> Self {
self.full_width = true;
self.full_height = true;
self
}
pub fn absolute(mut self) -> Self {
self.absolute = true;
self
}
pub fn intensity(mut self, intensity: f32) -> Self {
self.intensity = intensity.clamp(0.0, 1.0);
self
}
pub fn spacing(mut self, spacing: f32) -> Self {
self.spacing = spacing.max(2.0);
self
}
pub fn dot_size(mut self, dot_size: f32) -> Self {
self.dot_size = dot_size.max(0.5);
self
}
pub fn light(mut self) -> Self {
self.light = true;
self
}
}
impl Default for Grain {
fn default() -> Self {
Self::new()
}
}
fn hash_position(x: u32, y: u32) -> f32 {
let mut h = x.wrapping_mul(374761393).wrapping_add(y.wrapping_mul(668265263));
h = (h ^ (h >> 13)).wrapping_mul(1274126177);
h = h ^ (h >> 16);
(h & 0xFFFF) as f32 / 65535.0
}
impl RenderOnce for Grain {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let base_color = if self.light {
theme.fg()
} else {
gpui::hsla(0.0, 0.0, 0.0, 1.0)
};
let intensity = self.intensity;
let spacing = self.spacing;
let dot_size = self.dot_size;
let paint_canvas = canvas(
move |bounds, _, _cx| bounds,
move |bounds: Bounds<Pixels>, _, window: &mut Window, _cx| {
paint_grain(window, bounds, base_color, intensity, spacing, dot_size);
},
)
.size_full();
div()
.overflow_hidden()
.when(self.absolute, |this: Div| {
this.absolute().top_0().left_0()
})
.when(self.full_width, |this: Div| this.w_full())
.when(self.full_height, |this: Div| this.h_full())
.when_some(self.width, |this: Div, w| this.w(w))
.when_some(self.height, |this: Div, h| this.h(h))
.child(paint_canvas)
}
}
fn paint_grain(
window: &mut Window,
bounds: Bounds<Pixels>,
base_color: gpui::Hsla,
intensity: f32,
spacing: f32,
dot_size: f32,
) {
let origin_x: f32 = bounds.origin.x.into();
let origin_y: f32 = bounds.origin.y.into();
let width: f32 = bounds.size.width.into();
let height: f32 = bounds.size.height.into();
let dot_px = px(dot_size);
let mut y_offset = 0.0_f32;
let mut row: u32 = 0;
while y_offset < height {
let mut x_offset = 0.0_f32;
let mut col: u32 = 0;
while x_offset < width {
let rand_val = hash_position(col, row);
let alpha = rand_val * intensity;
if alpha > 0.005 {
let color = gpui::hsla(base_color.h, base_color.s, base_color.l, alpha);
window.paint_quad(gpui::fill(
Bounds {
origin: point(px(origin_x + x_offset), px(origin_y + y_offset)),
size: size(dot_px, dot_px),
},
color,
));
}
x_offset += spacing;
col += 1;
}
y_offset += spacing;
row += 1;
}
}