Skip to content

Commit e52bfbd

Browse files
committed
feat: added builder for ShaderCanvasState
1 parent 15ec8df commit e52bfbd

File tree

4 files changed

+91
-52
lines changed

4 files changed

+91
-52
lines changed

src/backend/cpu.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::ShaderInput;
1+
use crate::{Pixel, ShaderInput};
22

33
use super::{NoUserData, TuiShaderBackend};
44

@@ -9,7 +9,7 @@ pub struct CpuBackend<T> {
99
impl CpuBackend<NoUserData> {
1010
pub fn new<F>(callback: F) -> Self
1111
where
12-
F: Fn(u32, u32) -> [u8; 4] + 'static,
12+
F: Fn(u32, u32) -> Pixel + 'static,
1313
{
1414
Self {
1515
callback: Box::new(CpuShaderCallbackWithoutUserData(callback)),
@@ -20,7 +20,7 @@ impl CpuBackend<NoUserData> {
2020
impl<T> CpuBackend<T> {
2121
pub fn new_with_user_data<F>(callback: F) -> Self
2222
where
23-
F: Fn(u32, u32, &T) -> [u8; 4] + 'static,
23+
F: Fn(u32, u32, &T) -> Pixel + 'static,
2424
{
2525
Self {
2626
callback: Box::new(callback),
@@ -29,7 +29,7 @@ impl<T> CpuBackend<T> {
2929
}
3030

3131
impl<T> TuiShaderBackend<T> for CpuBackend<T> {
32-
fn execute(&mut self, shader_input: &ShaderInput, user_data: &T) -> Vec<[u8; 4]> {
32+
fn execute(&mut self, shader_input: &ShaderInput, user_data: &T) -> Vec<Pixel> {
3333
let width = shader_input.resolution[0];
3434
let height = shader_input.resolution[1];
3535
let mut pixels = Vec::new();
@@ -44,14 +44,14 @@ impl<T> TuiShaderBackend<T> for CpuBackend<T> {
4444
}
4545

4646
pub trait CpuShaderCallback<T> {
47-
fn call(&self, x: u32, y: u32, user_data: &T) -> [u8; 4];
47+
fn call(&self, x: u32, y: u32, user_data: &T) -> Pixel;
4848
}
4949

5050
impl<T, F> CpuShaderCallback<T> for F
5151
where
52-
F: Fn(u32, u32, &T) -> [u8; 4],
52+
F: Fn(u32, u32, &T) -> Pixel,
5353
{
54-
fn call(&self, x: u32, y: u32, user_data: &T) -> [u8; 4] {
54+
fn call(&self, x: u32, y: u32, user_data: &T) -> Pixel {
5555
self(x, y, user_data)
5656
}
5757
}
@@ -60,9 +60,9 @@ where
6060
struct CpuShaderCallbackWithoutUserData<F>(F);
6161
impl<F> CpuShaderCallback<NoUserData> for CpuShaderCallbackWithoutUserData<F>
6262
where
63-
F: Fn(u32, u32) -> [u8; 4],
63+
F: Fn(u32, u32) -> Pixel,
6464
{
65-
fn call(&self, x: u32, y: u32, _user_data: &NoUserData) -> [u8; 4] {
65+
fn call(&self, x: u32, y: u32, _user_data: &NoUserData) -> Pixel {
6666
self.0(x, y)
6767
}
6868
}

src/backend/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::ShaderInput;
1+
use crate::{Pixel, ShaderInput};
22

33
pub mod cpu;
44
pub mod wgpu;
@@ -8,5 +8,5 @@ pub mod wgpu;
88
pub struct NoUserData(f32);
99

1010
pub trait TuiShaderBackend<T> {
11-
fn execute(&mut self, shader_input: &ShaderInput, user_data: &T) -> Vec<[u8; 4]>;
11+
fn execute(&mut self, shader_input: &ShaderInput, user_data: &T) -> Vec<Pixel>;
1212
}

src/backend/wgpu.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::{fs, marker::PhantomData};
33
use pollster::FutureExt as _;
44
use wgpu::util::DeviceExt;
55

6-
use crate::ShaderInput;
6+
use crate::{Pixel, ShaderInput};
77

88
use super::{NoUserData, TuiShaderBackend};
99

@@ -211,7 +211,7 @@ where
211211
}
212212
}
213213

214-
async fn execute_inner(&mut self, shader_input: &ShaderInput, _user_data: &T) -> Vec<[u8; 4]> {
214+
async fn execute_inner(&mut self, shader_input: &ShaderInput, _user_data: &T) -> Vec<Pixel> {
215215
let width = shader_input.resolution[0];
216216
let height = shader_input.resolution[1];
217217
if bytes_per_row(width) != bytes_per_row(self.width) || height != self.height {
@@ -286,13 +286,13 @@ where
286286
.await
287287
.expect("unable to receive message all senders have been dropped")
288288
.expect("on unexpected error occured");
289-
let padded_buffer: Vec<[u8; 4]>;
289+
let padded_buffer: Vec<Pixel>;
290290
{
291291
let view = buffer_slice.get_mapped_range();
292292
padded_buffer = bytemuck::cast_slice(&view).to_vec();
293293
}
294294
self.output_buffer.unmap();
295-
let mut buffer: Vec<[u8; 4]> = Vec::new();
295+
let mut buffer: Vec<Pixel> = Vec::new();
296296
for y in 0..height {
297297
for x in 0..width {
298298
let index = (y * (width + row_padding(width)) + x) as usize;
@@ -308,7 +308,7 @@ impl<T> TuiShaderBackend<T> for WgpuBackend<T>
308308
where
309309
T: Copy + Clone + Default + bytemuck::Pod + bytemuck::Zeroable,
310310
{
311-
fn execute(&mut self, shader_input: &ShaderInput, user_data: &T) -> Vec<[u8; 4]> {
311+
fn execute(&mut self, shader_input: &ShaderInput, user_data: &T) -> Vec<Pixel> {
312312
self.execute_inner(shader_input, user_data).block_on()
313313
}
314314
}

src/lib.rs

Lines changed: 75 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,8 @@ impl<T> StatefulWidget for ShaderCanvas<T> {
114114
) {
115115
let width = area.width;
116116
let height = area.height;
117-
state.shader_input.resolution = [width.into(), height.into()];
118-
state.shader_input.time = state.creation_time.elapsed().as_secs_f32();
119-
let samples = state.backend.execute(&state.shader_input, &state.user_data);
117+
let shader_input = ShaderInput::new(state.instant.elapsed().as_secs_f32(), width, height);
118+
let samples = state.backend.execute(&shader_input, &state.user_data);
120119

121120
for y in 0..height {
122121
for x in 0..width {
@@ -145,34 +144,45 @@ impl<T> StatefulWidget for ShaderCanvas<T> {
145144
}
146145

147146
/// State struct for [`ShaderCanvas`], it holds the [`TuiShaderBackend`].
148-
pub struct ShaderCanvasState<T> {
147+
pub struct ShaderCanvasState<T = NoUserData> {
149148
backend: Box<dyn TuiShaderBackend<T>>,
150-
shader_input: ShaderInput,
151149
user_data: T,
152-
creation_time: Instant,
150+
instant: Instant,
153151
}
154152

155153
impl<T> ShaderCanvasState<T> {
156-
pub fn new(backend: Box<dyn TuiShaderBackend<T>>, user_data: T) -> Self {
154+
pub fn new<B: TuiShaderBackend<T> + 'static>(backend: B, user_data: T) -> Self {
155+
let backend = Box::new(backend);
157156
let creation_time = Instant::now();
158-
let shader_input = ShaderInput::new(creation_time.elapsed().as_secs_f32(), 0, 0);
159157
ShaderCanvasState {
160158
backend,
161-
shader_input,
162159
user_data,
163-
creation_time,
160+
instant: creation_time,
164161
}
165162
}
166163
}
167164

168-
impl ShaderCanvasState<NoUserData> {
165+
impl ShaderCanvasState {
169166
/// Creates a new [`ShaderCanvasState`] using [`WgpuBackend`] as it's
170167
/// [`TuiShaderBackend`].
171168
pub fn wgpu(path_to_fragment_shader: &str, entry_point: &str) -> Self {
172-
let backend = Box::new(WgpuBackend::new(path_to_fragment_shader, entry_point));
169+
let backend = WgpuBackend::new(path_to_fragment_shader, entry_point);
173170
let user_data = NoUserData::default();
174171
Self::new(backend, user_data)
175172
}
173+
174+
pub fn cpu<F>(callback: F) -> Self
175+
where
176+
F: Fn(u32, u32) -> Pixel + 'static,
177+
{
178+
let backend = CpuBackend::new(callback);
179+
let user_data = NoUserData::default();
180+
Self::new(backend, user_data)
181+
}
182+
183+
pub fn builder() -> ShaderCanvasStateBuilder {
184+
ShaderCanvasStateBuilder::default()
185+
}
176186
}
177187

178188
impl<T> ShaderCanvasState<T>
@@ -184,18 +194,7 @@ where
184194
entry_point: &str,
185195
user_data: T,
186196
) -> Self {
187-
let backend = Box::new(WgpuBackend::new(path_to_fragment_shader, entry_point));
188-
Self::new(backend, user_data)
189-
}
190-
}
191-
192-
impl ShaderCanvasState<NoUserData> {
193-
pub fn cpu<F>(callback: F) -> Self
194-
where
195-
F: Fn(u32, u32) -> [u8; 4] + 'static,
196-
{
197-
let backend = Box::new(CpuBackend::new(callback));
198-
let user_data = NoUserData::default();
197+
let backend = WgpuBackend::new(path_to_fragment_shader, entry_point);
199198
Self::new(backend, user_data)
200199
}
201200
}
@@ -206,22 +205,60 @@ where
206205
{
207206
pub fn cpu_with_user_data<F>(callback: F, user_data: T) -> Self
208207
where
209-
F: Fn(u32, u32, &T) -> [u8; 4] + 'static,
208+
F: Fn(u32, u32, &T) -> Pixel + 'static,
210209
{
211-
let backend = Box::new(CpuBackend::new_with_user_data(callback));
210+
let backend = CpuBackend::new_with_user_data(callback);
212211
Self::new(backend, user_data)
213212
}
214213
}
215214

216-
impl Default for ShaderCanvasState<NoUserData> {
215+
impl Default for ShaderCanvasState {
217216
/// Creates a new [`ShaderCanvasState`] instance with a [`WgpuBackend`].
218217
fn default() -> Self {
219-
let backend = Box::new(WgpuBackend::default());
218+
let backend = WgpuBackend::default();
220219
let user_data = NoUserData::default();
221220
Self::new(backend, user_data)
222221
}
223222
}
224223

224+
#[derive(Default)]
225+
pub struct ShaderCanvasStateBuilder<T = NoUserData> {
226+
backend: Option<Box<dyn TuiShaderBackend<T>>>,
227+
user_data: Option<T>,
228+
instant: Option<Instant>,
229+
}
230+
231+
impl<T> ShaderCanvasStateBuilder<T> {
232+
pub fn with_backend<B: TuiShaderBackend<T> + 'static>(mut self, backend: B) -> Self {
233+
self.backend = Some(Box::new(backend));
234+
self
235+
}
236+
237+
pub fn with_user_data(mut self, user_data: T) -> Self {
238+
self.user_data = Some(user_data);
239+
self
240+
}
241+
242+
pub fn with_instant(mut self, instant: Instant) -> Self {
243+
self.instant = Some(instant);
244+
self
245+
}
246+
}
247+
248+
impl ShaderCanvasStateBuilder {
249+
pub fn build(self) -> ShaderCanvasState {
250+
let backend = self
251+
.backend
252+
.unwrap_or_else(|| Box::new(WgpuBackend::default()));
253+
let user_data = NoUserData::default();
254+
let instant = self.instant.unwrap_or_else(|| Instant::now());
255+
ShaderCanvasState {
256+
backend,
257+
user_data,
258+
instant,
259+
}
260+
}
261+
}
225262
#[repr(C)]
226263
#[derive(Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
227264
pub struct ShaderInput {
@@ -289,33 +326,33 @@ pub enum StyleRule {
289326
/// Primarily used in [`CharacterRule::Map`] and [`StyleRule::Map`], it provides access to a Cells color and position
290327
/// allowing to map the output of the shader to more complex behaviour.
291328
pub struct Sample {
292-
value: [u8; 4],
329+
pixel: Pixel,
293330
position: (u16, u16),
294331
}
295332

296333
impl Sample {
297-
fn new(value: [u8; 4], position: (u16, u16)) -> Self {
298-
Self { value, position }
334+
fn new(pixel: Pixel, position: (u16, u16)) -> Self {
335+
Self { pixel, position }
299336
}
300337

301338
/// The red channel of the [`Sample`]
302339
pub fn r(&self) -> u8 {
303-
self.value[0]
340+
self.pixel[0]
304341
}
305342

306343
/// The green channel of the [`Sample`]
307344
pub fn g(&self) -> u8 {
308-
self.value[1]
345+
self.pixel[1]
309346
}
310347

311348
/// The blue channel of the [`Sample`]
312349
pub fn b(&self) -> u8 {
313-
self.value[2]
350+
self.pixel[2]
314351
}
315352

316353
/// The alpha channel of the [`Sample`]
317354
pub fn a(&self) -> u8 {
318-
self.value[3]
355+
self.pixel[3]
319356
}
320357

321358
/// The x coordinate of the [`Sample`]
@@ -329,6 +366,8 @@ impl Sample {
329366
}
330367
}
331368

369+
pub type Pixel = [u8; 4];
370+
332371
#[cfg(test)]
333372
mod tests {
334373
use ratatui::backend::TestBackend;
@@ -351,7 +390,7 @@ mod tests {
351390

352391
#[test]
353392
fn cpu_backend() {
354-
fn cb(_x: u32, _y: u32) -> [u8; 4] {
393+
fn cb(_x: u32, _y: u32) -> Pixel {
355394
[255, 0, 0, 255]
356395
}
357396
let mut context = CpuBackend::new(cb);

0 commit comments

Comments
 (0)