use super::{DarklyEngine, ReadbackContext};
use crate::brush::bundle::{Brush, BrushMetadata};
use crate::brush::library::BrushInfo;
pub(crate) const BRUSH_THUMBNAIL_SIZE: (u32, u32) = (320, 120);
pub(crate) const BRUSH_STROKE_RENDER_SIZE: (u32, u32) = (384, 192);
pub(crate) const BRUSH_STROKE_PATH_INSET: f32 = 32.0;
pub(crate) const BRUSH_DAB_RENDER_SIZE: (u32, u32) = (256, 256);
impl DarklyEngine {
pub fn brush_list(&self) -> Vec<BrushInfo> {
self.brush_library.list()
}
pub fn brush_load(&mut self, name: &str) -> Result<(), String> {
let brush = self
.brush_library
.get(name)
.ok_or_else(|| format!("brush '{}' not found", name))?
.clone();
let json = serde_json::to_string(&brush.metadata.graph)
.map_err(|e| format!("failed to serialize graph: {e}"))?;
self.set_brush_graph(&json)?;
Ok(())
}
pub fn brush_save(&mut self, name: &str, category: &str) -> Result<(), String> {
let mut metadata = BrushMetadata::from_graph(name, self.active_brush_graph());
metadata.category = category.to_string();
self.brush_library.insert(Brush::from_metadata(metadata));
self.snapshot_brush_defaults();
let fg = self.preview_theme_fg;
let bg = self.preview_theme_bg;
let mut graph = self.active_brush_graph();
graph.apply_preview_overrides();
let (rw, rh) = BRUSH_STROKE_RENDER_SIZE;
let path = crate::brush::preview_renderer::synthesize_stroke_path(
rw as f32,
rh as f32,
30,
BRUSH_STROKE_PATH_INSET,
);
self.render_preview_and_request_readback(
&graph,
&path,
rw,
rh,
fg,
bg,
ReadbackContext::BrushThumbnailForSave {
name: name.to_string(),
width: rw,
height: rh,
},
);
Ok(())
}
pub fn brush_export(&self, name: &str) -> Result<Vec<u8>, String> {
self.brush_library.export_bytes(name)
}
pub fn brush_thumbnail(&mut self, name: &str) -> Vec<u8> {
if let Some(png) = self.brush_library.thumbnail_png(name) {
return png.to_vec();
}
let already_pending = self.readbacks.any(
|c| matches!(c, ReadbackContext::BrushThumbnailForSave { name: n, .. } if n == name),
);
if already_pending {
return Vec::new();
}
let Some(brush) = self.brush_library.get(name).cloned() else {
return Vec::new();
};
let fg = self.preview_theme_fg;
let bg = self.preview_theme_bg;
let mut graph = brush.metadata.graph.clone();
graph.apply_preview_overrides();
let (rw, rh) = BRUSH_STROKE_RENDER_SIZE;
let path = crate::brush::preview_renderer::synthesize_stroke_path(
rw as f32,
rh as f32,
30,
BRUSH_STROKE_PATH_INSET,
);
self.render_preview_and_request_readback(
&graph,
&path,
rw,
rh,
fg,
bg,
ReadbackContext::BrushThumbnailForSave {
name: name.to_string(),
width: rw,
height: rh,
},
);
Vec::new()
}
pub fn brush_dab_thumbnail(&mut self, name: &str) -> Vec<u8> {
if let Some(png) = self.brush_library.dab_thumbnail_png(name) {
return png.to_vec();
}
let already_pending = self
.readbacks
.any(|c| matches!(c, ReadbackContext::BrushDabThumbnail { name: n, .. } if n == name));
if already_pending {
return Vec::new();
}
let Some(brush) = self.brush_library.get(name).cloned() else {
return Vec::new();
};
let (w, h) = BRUSH_DAB_RENDER_SIZE;
let fg = self.preview_theme_fg;
let bg = self.preview_theme_bg;
let mut graph = brush.metadata.graph.clone();
crate::brush::reset_exposed_scrubs(&mut graph);
let path = crate::brush::preview_renderer::synthesize_dab_path(w as f32, h as f32);
self.render_preview_and_request_readback(
&graph,
&path,
w,
h,
fg,
bg,
ReadbackContext::BrushDabThumbnail {
name: name.to_string(),
width: w,
height: h,
},
);
Vec::new()
}
pub fn brush_import(&mut self, bytes: &[u8]) -> Result<String, String> {
self.brush_library.import_bytes(bytes)
}
}