1pub mod custom;
5pub mod component_values;
7
8use std::hash::{Hash, Hasher};
9use image::{DynamicImage, Rgba, RgbaImage};
10use rusttype::Scale;
11use image::imageops::{FilterType, tile};
12use streamdeck::{DeviceImage, StreamDeck};
13use std::collections::HashMap;
14use std::sync::Arc;
15use std::collections::hash_map::DefaultHasher;
16use std::time::Instant;
17use std::ops::Deref;
18use serde::{Serialize, Deserialize};
19use serde_json::Value;
20use crate::core::button::Component;
21use crate::core::{CoreHandle, UniqueButton};
22use crate::font::get_font_from_collection;
23use crate::images::{AnimationFrame, convert_image, SDImage};
24use crate::modules::UniqueSDModule;
25use crate::thread::rendering::custom::DeviceReference;
26use crate::thread::util::{image_from_horiz_gradient, image_from_solid, image_from_vert_gradient, render_aligned_shadowed_text_on_image, render_aligned_text_on_image, TextAlignment};
27use crate::util::hash_value;
28
29pub struct AnimationCounter {
31 frames: Vec<(AnimationFrame, f32)>,
32 time: Instant,
33 wakeup_time: f32,
34 index: usize,
35 duration: f32,
36 new_frame: bool,
37}
38
39impl AnimationCounter {
40 fn new(frames: Vec<AnimationFrame>) -> AnimationCounter {
41 let mut time_counter = 0.0;
42 let frames: Vec<(AnimationFrame, f32)> = frames.into_iter()
43 .map(|x| {
44 let end_time = time_counter + x.delay;
45 time_counter = end_time;
46 (x, end_time)
47 })
48 .collect();
49
50 let duration = time_counter;
51
52 AnimationCounter {
53 frames,
54 time: Instant::now(),
55 wakeup_time: 0.0,
56 index: 0,
57 duration,
58 new_frame: false
59 }
60 }
61
62 fn get_frame(&self) -> &AnimationFrame {
63 &self.frames[self.index].0
64 }
65
66 fn advance_counter(&mut self) {
67 let time = self.time.elapsed().as_secs_f32();
68
69 if time > self.wakeup_time {
70 let looped_time = time % self.duration;
71 for i in 0..self.frames.len() {
72 if looped_time < self.frames[i].1 {
73 self.index = i;
74 self.new_frame = true;
75 self.wakeup_time = time + self.frames[i].0.delay;
76 break;
77 }
78 }
79 }
80 }
81}
82
83pub async fn process_frame(
85 core: &CoreHandle,
86 streamdeck: &mut StreamDeck,
87 cache: &mut HashMap<u64, (Arc<DeviceImage>, u64)>,
88 counters: &mut HashMap<String, AnimationCounter>,
89 renderer_map: &mut HashMap<u8, (RendererComponent, UniqueButton, Vec<UniqueSDModule>)>,
90 previous_state: &mut HashMap<u8, u64>,
91 missing: &DynamicImage,
92 time: u64
93) {
94
95 for key in 0..core.core.key_count {
96 if let Some((component, button, modules)) = renderer_map.get(&key) {
97 if !component.renderer.is_empty() {
98 let lock = core.core.render_manager.read_renderers().await;
100
101 if let Some(renderer) = lock.get(&component.renderer) {
102 renderer.render(key, button, core, &mut DeviceReference::new(streamdeck, key)).await;
104 previous_state.insert(key, 1);
105 continue;
106 }
107 }
108
109
110 if let ButtonBackground::ExistingImage(identifier) = &component.background {
111 let counter = if let Some(counter) = counters.get_mut(identifier) {
112 Some(counter)
113 } else {
114 if let Some(SDImage::AnimatedImage(frames)) = core.core.image_collection.read().await.get(identifier).cloned() {
115 let counter = AnimationCounter::new(frames);
116 counters.insert(identifier.clone(), counter);
117 Some(counters.get_mut(identifier).unwrap())
118 } else {
119 None
120 }
121 };
122
123 if let Some(counter) = counter {
124 let frame = counter.get_frame();
125
126 let mut hasher: Box<dyn Hasher> = Box::new(DefaultHasher::new());
127
128 component.hash(&mut hasher);
129 frame.index.hash(&mut hasher);
130
131 for module in modules {
132 module.render_hash(core.clone_for(module), &button, &mut hasher);
133 }
134
135 let hash = hasher.finish();
136
137 if counter.new_frame || (hash != *previous_state.get(&key).unwrap_or(&1)) {
138 let variant = cache.get_mut(&hash);
139
140 if component.to_cache && variant.is_some() {
141 let (variant, time_to_die) = variant.unwrap();
142 *time_to_die = time + 20000;
143
144 let previous = previous_state.get(&key).unwrap_or(&1);
145 if hash != *previous {
146 streamdeck.write_button_image(key, variant.deref()).ok();
147 }
148
149 } else {
150 let device_image = convert_image(&core.core.kind, draw_foreground(&component, &button, modules,frame.image.clone(), core).await);
151
152 let arc = Arc::new(device_image);
153
154 if component.to_cache {
155 cache.insert(hash, (arc.clone(), time + 20000));
156 }
157
158 streamdeck.write_button_image(key, arc.deref()).ok();
159 }
160
161 previous_state.insert(key, hash);
162 }
163
164 continue;
166 }
167 }
168
169 let mut hasher: Box<dyn Hasher> = Box::new(DefaultHasher::new());
171
172 component.hash(&mut hasher);
173 for module in modules {
174 module.render_hash(core.clone_for(module), &button, &mut hasher);
175 }
176
177 let hash = hasher.finish();
178
179 let variant = cache.get_mut(&hash);
180
181 if component.to_cache && variant.is_some() {
182 let (variant, time_to_die) = variant.unwrap();
183 *time_to_die = time + 20000;
184
185 let previous = previous_state.get(&key).unwrap_or(&1);
186 if hash != *previous {
187 streamdeck.write_button_image(key, variant.deref()).ok();
188 }
189 } else {
190 let device_image = convert_image(&core.core.kind, draw_foreground(&component, &button, modules, draw_background(component, core, missing).await, core).await);
191
192 let arc = Arc::new(device_image);
193
194 if component.to_cache {
195 cache.insert(hash, (arc.clone(), time + 20000));
196 }
197
198 streamdeck.write_button_image(key, arc.deref()).ok();
199 }
200
201 previous_state.insert(key, hash);
202 } else {
203 let previous = previous_state.get(&key).unwrap_or(&1);
204
205 if *previous != 0 {
206 previous_state.insert(key, 0);
207 streamdeck.set_button_rgb(key, &streamdeck::Colour {
208 r: 0,
209 g: 0,
210 b: 0
211 }).ok();
212 }
213 }
214 }
215
216 for (_, counter) in counters {
217 counter.new_frame = false;
218 counter.advance_counter()
219 };
220}
221
222pub async fn draw_background(renderer: &RendererComponent, core: &CoreHandle, missing: &DynamicImage) -> DynamicImage {
224 match &renderer.background {
225 ButtonBackground::Solid(color) => {
226 image_from_solid(core.core.image_size, Rgba([color.0, color.1, color.2, 255]))
227 }
228
229 ButtonBackground::HorizontalGradient(start, end) => {
230 image_from_horiz_gradient(core.core.image_size, Rgba([start.0, start.1, start.2, 255]), Rgba([end.0, end.1, end.2, 255]))
231 }
232
233 ButtonBackground::VerticalGradient(start, end) => {
234 image_from_vert_gradient(core.core.image_size, Rgba([start.0, start.1, start.2, 255]), Rgba([end.0, end.1, end.2, 255]))
235 }
236
237 ButtonBackground::ExistingImage(identifier) => {
238 if let Some(image) = core.core.image_collection.read().await.get(identifier) {
239 match image {
240 SDImage::SingleImage(image) => {
241 image.resize_to_fill(core.core.image_size.0 as u32, core.core.image_size.1 as u32, FilterType::Triangle)
242 }
243
244 SDImage::AnimatedImage(frames) => {
245 frames[0].image.clone().resize_to_fill(core.core.image_size.0 as u32, core.core.image_size.1 as u32, FilterType::Triangle)
246 }
247 }
248 } else {
249 missing.clone()
250 }
251 }
252
253 ButtonBackground::NewImage(blob) => {
254 if let Ok(image) = SDImage::from_base64(blob, core.core.image_size).await {
255 image.get_image()
256 } else {
257 missing.clone()
258 }
259 }
260 }
261}
262
263pub async fn draw_foreground(renderer: &RendererComponent, button: &UniqueButton, modules: &Vec<UniqueSDModule>, mut background: DynamicImage, core: &CoreHandle) -> DynamicImage {
265 for module in modules {
267 module.render(core.clone_for(module), button, &mut background).await;
268 }
269
270
271 for button_text in &renderer.text {
272 let text = button_text.text.as_str();
273 let scale = Scale { x: button_text.scale.0, y: button_text.scale.1 };
274 let align = button_text.alignment.clone();
275 let padding = button_text.padding;
276 let offset = button_text.offset.clone();
277 let color = button_text.color.clone();
278
279 if let Some(font) = get_font_from_collection(&button_text.font) {
280 if let Some(shadow) = &button_text.shadow {
281 render_aligned_shadowed_text_on_image(
282 core.core.image_size,
283 &mut background,
284 font.as_ref(),
285 text,
286 scale,
287 align,
288 padding,
289 offset,
290 color,
291 shadow.offset.clone(),
292 shadow.color.clone(),
293 )
294 } else {
295 render_aligned_text_on_image(
296 core.core.image_size,
297 &mut background,
298 font.as_ref(),
299 text,
300 scale,
301 align,
302 padding,
303 offset,
304 color,
305 )
306 }
307 }
308 }
309
310 background
311}
312
313pub fn draw_missing_texture(size: (usize, usize)) -> DynamicImage {
315 let mut pattern = RgbaImage::new(16, 16);
316
317 for x in 0..16 {
318 for y in 0..16 {
319 let color = if y < 8 {
320 if x < 8 {
321 Rgba([255, 0, 255, 255])
322 } else {
323 Rgba([0, 0, 0, 255])
324 }
325 } else {
326 if x >= 8 {
327 Rgba([255, 0, 255, 255])
328 } else {
329 Rgba([0, 0, 0, 255])
330 }
331 };
332
333 pattern.put_pixel(x, y, color);
334 }
335 }
336
337 let (iw, ih) = size;
338 let mut frame = RgbaImage::new(iw as u32, ih as u32);
339
340 tile(&mut frame, &pattern);
341
342 let mut missing = DynamicImage::ImageRgba8(frame);
343
344 if let Some(font) = get_font_from_collection("default") {
345 render_aligned_shadowed_text_on_image(
346 (iw, ih),
347 &mut missing,
348 &font,
349 "ГДЕ",
350 Scale { x: 30.0, y: 30.0 },
351 TextAlignment::Center,
352 0,
353 (0.0, -13.0),
354 (255, 0, 255, 255),
355 (2, 2),
356 (0, 0, 0, 255),
357 );
358
359 render_aligned_shadowed_text_on_image(
360 (iw, ih),
361 &mut missing,
362 &font,
363 "Where",
364 Scale { x: 25.0, y: 25.0 },
365 TextAlignment::Center,
366 0,
367 (0.0, 8.0),
368 (255, 0, 255, 255),
369 (1, 1),
370 (0, 0, 0, 255),
371 );
372 }
373
374 missing
375}
376
377pub fn draw_custom_renderer_texture(size: (usize, usize)) -> DynamicImage {
379 let font = get_font_from_collection("default").unwrap();
380 let mut frame = image_from_solid(size, Rgba([55, 55, 55, 255]));
381
382 render_aligned_text_on_image(size, &mut frame, font.deref(), "Custom", Scale::uniform(16.0), TextAlignment::Center, 0, (0.0, -8.0), (255, 255, 255, 255));
383 render_aligned_text_on_image(size, &mut frame, font.deref(), "Renderer", Scale::uniform(16.0), TextAlignment::Center, 0, (0.0, 8.0), (255, 255, 255, 255));
384
385 frame
386}
387
388pub type Color = (u8, u8, u8, u8);
390
391#[derive(Serialize, Deserialize, Debug, Clone, Hash)]
393pub enum ButtonBackground {
394 Solid(Color),
396 HorizontalGradient(Color, Color),
398 VerticalGradient(Color, Color),
400 ExistingImage(String),
402 NewImage(String),
404}
405
406impl Default for ButtonBackground {
407 fn default() -> Self {
408 Self::Solid((0, 0, 0, 0))
409 }
410}
411
412#[derive(Serialize, Deserialize, Debug, Clone)]
414pub struct ButtonText {
415 pub text: String,
417 pub font: String,
419 pub scale: (f32, f32),
421 pub alignment: TextAlignment,
423 pub padding: u32,
425 pub offset: (f32, f32),
427 pub color: Color,
429 pub shadow: Option<ButtonTextShadow>,
431}
432
433impl Hash for ButtonText {
434 fn hash<H: Hasher>(&self, state: &mut H) {
435 self.text.hash(state);
436 self.font.hash(state);
437 ((self.scale.0 * 100.0) as i32).hash(state);
438 ((self.scale.1 * 100.0) as i32).hash(state);
439 self.alignment.hash(state);
440 self.padding.hash(state);
441 ((self.offset.0 * 100.0) as i32).hash(state);
442 ((self.offset.1 * 100.0) as i32).hash(state);
443 self.color.hash(state);
444 self.shadow.hash(state);
445 }
446}
447
448#[derive(Serialize, Deserialize, Debug, Clone, Hash)]
450pub struct ButtonTextShadow {
451 pub offset: (i32, i32),
453 pub color: Color,
455}
456
457#[derive(Serialize, Deserialize, Clone, Debug)]
459pub struct RendererComponent {
460 #[serde(default)]
462 pub renderer: String,
463 #[serde(default)]
465 pub background: ButtonBackground,
466 #[serde(default)]
468 pub text: Vec<ButtonText>,
469 #[serde(default)]
471 pub plugin_blacklist: Vec<String>,
472 #[serde(default = "make_true")]
474 pub to_cache: bool,
475 #[serde(default)]
477 pub custom_data: Value,
478}
479
480fn make_true() -> bool { true }
481
482impl Default for RendererComponent {
483 fn default() -> Self {
484 Self {
485 renderer: "".to_string(),
486 background: ButtonBackground::Solid((255, 255, 255, 255)),
487 text: vec![],
488 plugin_blacklist: vec![],
489 to_cache: true,
490 custom_data: Default::default()
491 }
492 }
493}
494
495impl Hash for RendererComponent {
496 fn hash<H: Hasher>(&self, state: &mut H) {
497 self.renderer.hash(state);
498 self.plugin_blacklist.hash(state);
499 self.text.hash(state);
500 self.to_cache.hash(state);
501 self.background.hash(state);
502 hash_value(&self.custom_data, state);
503 }
504}
505
506impl Component for RendererComponent {
507 const NAME: &'static str = "renderer";
508}
509
510#[derive(Default)]
512pub struct RendererComponentBuilder {
513 component: RendererComponent
514}
515
516impl RendererComponentBuilder {
517 pub fn new() -> Self {
519 Self::default()
520 }
521
522 pub fn renderer(mut self, renderer: &str) -> Self {
524 self.component.renderer = renderer.to_string(); self
525 }
526
527 pub fn background(mut self, background: ButtonBackground) -> Self {
529 self.component.background = background; self
530 }
531
532 pub fn add_text(mut self, text: ButtonText) -> Self {
534 self.component.text.push(text); self
535 }
536
537 pub fn add_to_blacklist(mut self, plugin: &str) -> Self {
539 self.component.plugin_blacklist.push(plugin.to_string()); self
540 }
541
542 pub fn caching(mut self, cache: bool) -> Self {
544 self.component.to_cache = cache; self
545 }
546
547 pub fn build(self) -> RendererComponent {
549 self.into()
550 }
551}
552
553impl From<RendererComponentBuilder> for RendererComponent {
554 fn from(builder: RendererComponentBuilder) -> Self {
555 builder.component
556 }
557}
558
559#[derive(Serialize, Deserialize, Default)]
561pub struct RendererSettings {
562 pub plugin_blacklist: Vec<String>
564}
565
566#[allow(dead_code)]
567pub(crate) fn hash_renderer(renderer: &RendererComponent) -> u64 {
568 let mut hasher = DefaultHasher::new();
569 renderer.hash(&mut hasher);
570 hasher.finish()
571}