Clean up build warnings
Resolve all compiler warnings across daw-backend, lightningbeam-core, and lightningbeam-editor: - Delete dead code: the superseded CPU raster tools in raster_tool.rs (EffectBrush/Smudge/Gradient/Transform/Warp/Liquify/Selection — replaced by the GPU path), plus orphaned helpers and never-read struct fields. - Mechanical fixes: drop unused imports/variables/mut, underscore unused params, `drop(&x)` -> `let _ = x`, deprecated egui::Rounding -> CornerRadius, snake_case rename, elided-lifetime Cow<'_, [u8]>. - Keep the WIP CSS theming system (theme.rs/theme_render.rs) under #[allow(dead_code)] rather than deleting it. Editor checks warning-free; 293 core tests pass.
This commit is contained in:
parent
3c3a482e2e
commit
83057b754a
|
|
@ -116,7 +116,6 @@ pub struct Engine {
|
||||||
|
|
||||||
// Current tempo map — kept in sync with SetTempo/SetTempoMap commands.
|
// Current tempo map — kept in sync with SetTempo/SetTempoMap commands.
|
||||||
tempo_map: crate::TempoMap,
|
tempo_map: crate::TempoMap,
|
||||||
current_fps: f64,
|
|
||||||
// Current time signature — updated by SetTempo, used when SetTempoMap fires.
|
// Current time signature — updated by SetTempo, used when SetTempoMap fires.
|
||||||
time_sig: (u32, u32),
|
time_sig: (u32, u32),
|
||||||
}
|
}
|
||||||
|
|
@ -197,7 +196,6 @@ impl Engine {
|
||||||
timing_sum_total_us: 0,
|
timing_sum_total_us: 0,
|
||||||
timing_overrun_count: 0,
|
timing_overrun_count: 0,
|
||||||
tempo_map: crate::TempoMap::constant(120.0),
|
tempo_map: crate::TempoMap::constant(120.0),
|
||||||
current_fps: 30.0,
|
|
||||||
time_sig: (4, 4),
|
time_sig: (4, 4),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
//! correct). If the base is somehow not resident we skip rather than corrupt.
|
//! correct). If the base is somehow not resident we skip rather than corrupt.
|
||||||
|
|
||||||
/// Normalize a buffer to full length `n`; an empty/short buffer becomes transparent.
|
/// Normalize a buffer to full length `n`; an empty/short buffer becomes transparent.
|
||||||
fn normalize(buf: &[u8], n: usize) -> std::borrow::Cow<[u8]> {
|
fn normalize(buf: &[u8], n: usize) -> std::borrow::Cow<'_, [u8]> {
|
||||||
if buf.len() == n {
|
if buf.len() == n {
|
||||||
std::borrow::Cow::Borrowed(buf)
|
std::borrow::Cow::Borrowed(buf)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1161,7 +1161,7 @@ impl VectorGraph {
|
||||||
/// faces that lie inside it (inheriting its colour/rule); faces outside it — or in a
|
/// faces that lie inside it (inheriting its colour/rule); faces outside it — or in a
|
||||||
/// hole — are dropped.
|
/// hole — are dropped.
|
||||||
fn retrace_fills_after_cut(&mut self, new_edges: &[EdgeId]) {
|
fn retrace_fills_after_cut(&mut self, new_edges: &[EdgeId]) {
|
||||||
use kurbo::{ParamCurve, Shape};
|
use kurbo::Shape;
|
||||||
let new_set: HashSet<EdgeId> = new_edges
|
let new_set: HashSet<EdgeId> = new_edges
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&&e| !e.is_none() && !self.edges[e.idx()].deleted)
|
.filter(|&&e| !e.is_none() && !self.edges[e.idx()].deleted)
|
||||||
|
|
@ -1642,21 +1642,6 @@ impl VectorGraph {
|
||||||
// Boundary tracing internals
|
// Boundary tracing internals
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
/// Find the nearest non-deleted edge to a point. Returns (EdgeId, t, distance).
|
|
||||||
fn nearest_edge_to_point(&self, point: Point) -> Option<(EdgeId, f64, f64)> {
|
|
||||||
let mut best: Option<(EdgeId, f64, f64)> = None;
|
|
||||||
for (i, e) in self.edges.iter().enumerate() {
|
|
||||||
if e.deleted {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let eid = EdgeId(i as u32);
|
|
||||||
let (t, dist) = nearest_point_on_cubic(&e.curve, point);
|
|
||||||
if best.is_none() || dist < best.unwrap().2 {
|
|
||||||
best = Some((eid, t, dist));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
best
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build a BezPath from a boundary (without storing it as a fill).
|
/// Build a BezPath from a boundary (without storing it as a fill).
|
||||||
/// Handles `EdgeId::NONE` separators to start new contours (holes).
|
/// Handles `EdgeId::NONE` separators to start new contours (holes).
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,6 @@ pub enum CurveEditAction {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct CurveDragState {
|
pub struct CurveDragState {
|
||||||
pub keyframe_index: usize,
|
pub keyframe_index: usize,
|
||||||
pub original_time: f64,
|
|
||||||
pub original_value: f32,
|
|
||||||
pub current_time: f64,
|
pub current_time: f64,
|
||||||
pub current_value: f32,
|
pub current_value: f32,
|
||||||
}
|
}
|
||||||
|
|
@ -263,8 +261,6 @@ pub fn render_curve_lane(
|
||||||
let kf = &keyframes[idx];
|
let kf = &keyframes[idx];
|
||||||
*drag_state = Some(CurveDragState {
|
*drag_state = Some(CurveDragState {
|
||||||
keyframe_index: idx,
|
keyframe_index: idx,
|
||||||
original_time: kf.time,
|
|
||||||
original_value: kf.value,
|
|
||||||
current_time: kf.time,
|
current_time: kf.time,
|
||||||
current_value: kf.value,
|
current_value: kf.value,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,6 @@ pub struct DocumentHint {
|
||||||
pub has_raster: bool,
|
pub has_raster: bool,
|
||||||
pub has_vector: bool,
|
pub has_vector: bool,
|
||||||
pub current_time: f64,
|
pub current_time: f64,
|
||||||
pub doc_width: u32,
|
|
||||||
pub doc_height: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Export type selection
|
/// Export type selection
|
||||||
|
|
@ -577,7 +575,7 @@ impl ExportDialog {
|
||||||
|
|
||||||
if ui.button("Choose location...").clicked() {
|
if ui.button("Choose location...").clicked() {
|
||||||
let ext = self.current_extension();
|
let ext = self.current_extension();
|
||||||
let mut dialog = rfd::FileDialog::new()
|
let dialog = rfd::FileDialog::new()
|
||||||
.set_directory(&self.output_dir)
|
.set_directory(&self.output_dir)
|
||||||
.set_file_name(&self.output_filename)
|
.set_file_name(&self.output_filename)
|
||||||
.add_filter(ext.to_uppercase(), &[ext]);
|
.add_filter(ext.to_uppercase(), &[ext]);
|
||||||
|
|
|
||||||
|
|
@ -567,7 +567,7 @@ impl ExportOrchestrator {
|
||||||
let gpu = state.gpu_resources.as_mut().unwrap();
|
let gpu = state.gpu_resources.as_mut().unwrap();
|
||||||
let output_view = state.output_texture_view.as_ref().unwrap();
|
let output_view = state.output_texture_view.as_ref().unwrap();
|
||||||
|
|
||||||
let mut encoder = video_exporter::render_frame_to_gpu_rgba(
|
let encoder = video_exporter::render_frame_to_gpu_rgba(
|
||||||
document,
|
document,
|
||||||
state.settings.time,
|
state.settings.time,
|
||||||
w, h,
|
w, h,
|
||||||
|
|
|
||||||
|
|
@ -1586,11 +1586,6 @@ impl GpuBrushEngine {
|
||||||
crate::debug_overlay::update_gpu_memory(count, total);
|
crate::debug_overlay::update_gpu_memory(count, total);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the cached display texture for a raster layer keyframe.
|
|
||||||
pub fn get_layer_texture(&self, kf_id: &Uuid) -> Option<&CanvasPair> {
|
|
||||||
self.raster_layer_cache.get(kf_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ensure a low-res proxy texture exists for `kf_id` (uploaded once; proxies are
|
/// Ensure a low-res proxy texture exists for `kf_id` (uploaded once; proxies are
|
||||||
/// immutable). Bumps recency and evicts the least-recently-used past the budget.
|
/// immutable). Bumps recency and evicts the least-recently-used past the budget.
|
||||||
/// `pixels` is sRGB-premultiplied RGBA of length `w * h * 4`.
|
/// `pixels` is sRGB-premultiplied RGBA of length `w * h * 4`.
|
||||||
|
|
@ -1778,18 +1773,6 @@ impl GpuBrushEngine {
|
||||||
self.displacement_bufs.insert(id, DisplacementBuffer { buf, width, height });
|
self.displacement_bufs.insert(id, DisplacementBuffer { buf, width, height });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Overwrite the displacement buffer contents with the provided data.
|
|
||||||
pub fn upload_displacement_buf(
|
|
||||||
&self,
|
|
||||||
queue: &wgpu::Queue,
|
|
||||||
id: &Uuid,
|
|
||||||
data: &[[f32; 2]],
|
|
||||||
) {
|
|
||||||
if let Some(db) = self.displacement_bufs.get(id) {
|
|
||||||
queue.write_buffer(&db.buf, 0, bytemuck::cast_slice(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Zero out a displacement buffer (reset all displacements to (0,0)).
|
/// Zero out a displacement buffer (reset all displacements to (0,0)).
|
||||||
pub fn clear_displacement_buf(&self, queue: &wgpu::Queue, id: &Uuid) {
|
pub fn clear_displacement_buf(&self, queue: &wgpu::Queue, id: &Uuid) {
|
||||||
if let Some(db) = self.displacement_bufs.get(id) {
|
if let Some(db) = self.displacement_bufs.get(id) {
|
||||||
|
|
|
||||||
|
|
@ -2234,40 +2234,6 @@ impl EditorApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Porter-Duff "over" composite of `src` onto `dst` at canvas offset `(ox, oy)`.
|
|
||||||
/// Both buffers are sRGB-encoded premultiplied RGBA.
|
|
||||||
fn composite_over(
|
|
||||||
dst: &mut [u8], dst_w: u32, dst_h: u32,
|
|
||||||
src: &[u8], src_w: u32, src_h: u32,
|
|
||||||
ox: i32, oy: i32,
|
|
||||||
) {
|
|
||||||
for row in 0..src_h {
|
|
||||||
let dy = oy + row as i32;
|
|
||||||
if dy < 0 || dy >= dst_h as i32 { continue; }
|
|
||||||
for col in 0..src_w {
|
|
||||||
let dx = ox + col as i32;
|
|
||||||
if dx < 0 || dx >= dst_w as i32 { continue; }
|
|
||||||
let si = ((row * src_w + col) * 4) as usize;
|
|
||||||
let di = ((dy as u32 * dst_w + dx as u32) * 4) as usize;
|
|
||||||
let sa = src[si + 3] as u32;
|
|
||||||
if sa == 0 { continue; }
|
|
||||||
let da = dst[di + 3] as u32;
|
|
||||||
// out_a = src_a + dst_a * (255 - src_a) / 255
|
|
||||||
let out_a = sa + da * (255 - sa) / 255;
|
|
||||||
dst[di + 3] = out_a as u8;
|
|
||||||
if out_a > 0 {
|
|
||||||
for c in 0..3 {
|
|
||||||
// premul over: out = src + dst*(1-src_a/255)
|
|
||||||
// v is in [0, 255²], so one /255 brings it back to [0, 255]
|
|
||||||
let v = src[si + c] as u32 * 255
|
|
||||||
+ dst[di + c] as u32 * (255 - sa);
|
|
||||||
dst[di + c] = (v / 255).min(255) as u8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Commit a floating raster selection: composite it into the keyframe's
|
/// Commit a floating raster selection: composite it into the keyframe's
|
||||||
/// `raw_pixels` and record a `RasterStrokeAction` for undo.
|
/// `raw_pixels` and record a `RasterStrokeAction` for undo.
|
||||||
/// Clears `selection.raster_floating` and `selection.raster_selection`.
|
/// Clears `selection.raster_floating` and `selection.raster_selection`.
|
||||||
|
|
@ -2824,7 +2790,7 @@ impl EditorApp {
|
||||||
let canvas_before = kf.raw_pixels.clone();
|
let canvas_before = kf.raw_pixels.clone();
|
||||||
let canvas_w = kf.width;
|
let canvas_w = kf.width;
|
||||||
let canvas_h = kf.height;
|
let canvas_h = kf.height;
|
||||||
drop(kf); // release immutable borrow before taking mutable
|
let _ = kf; // release immutable borrow before taking mutable
|
||||||
|
|
||||||
use lightningbeam_core::selection::{RasterFloatingSelection, RasterSelection};
|
use lightningbeam_core::selection::{RasterFloatingSelection, RasterSelection};
|
||||||
self.selection.raster_floating = Some(RasterFloatingSelection {
|
self.selection.raster_floating = Some(RasterFloatingSelection {
|
||||||
|
|
@ -3276,8 +3242,6 @@ impl EditorApp {
|
||||||
has_raster: false,
|
has_raster: false,
|
||||||
has_vector: false,
|
has_vector: false,
|
||||||
current_time: doc.current_time,
|
current_time: doc.current_time,
|
||||||
doc_width: doc.width as u32,
|
|
||||||
doc_height: doc.height as u32,
|
|
||||||
};
|
};
|
||||||
scan(&doc.root.children, &mut h);
|
scan(&doc.root.children, &mut h);
|
||||||
h
|
h
|
||||||
|
|
@ -3636,7 +3600,7 @@ impl EditorApp {
|
||||||
|
|
||||||
let doc = self.action_executor.document();
|
let doc = self.action_executor.document();
|
||||||
let (doc_w, doc_h) = (doc.width as u32, doc.height as u32);
|
let (doc_w, doc_h) = (doc.width as u32, doc.height as u32);
|
||||||
drop(doc);
|
let _ = doc;
|
||||||
let mut layer = RasterLayer::new(layer_name);
|
let mut layer = RasterLayer::new(layer_name);
|
||||||
layer.ensure_keyframe_at(self.playback_time, doc_w, doc_h);
|
layer.ensure_keyframe_at(self.playback_time, doc_w, doc_h);
|
||||||
let action = lightningbeam_core::actions::AddLayerAction::new(AnyLayer::Raster(layer))
|
let action = lightningbeam_core::actions::AddLayerAction::new(AnyLayer::Raster(layer))
|
||||||
|
|
@ -5775,7 +5739,7 @@ impl eframe::App for EditorApp {
|
||||||
.map(|(&lid, _)| lid);
|
.map(|(&lid, _)| lid);
|
||||||
if let Some(layer_id) = recording_layer {
|
if let Some(layer_id) = recording_layer {
|
||||||
// First, find the clip instance and clip id
|
// First, find the clip instance and clip id
|
||||||
let (clip_id, instance_id, timeline_start, trim_start) = {
|
let (clip_id, instance_id, _timeline_start, _trim_start) = {
|
||||||
let document = self.action_executor.document();
|
let document = self.action_executor.document();
|
||||||
document.get_layer(&layer_id)
|
document.get_layer(&layer_id)
|
||||||
.and_then(|layer| {
|
.and_then(|layer| {
|
||||||
|
|
@ -6551,7 +6515,6 @@ impl eframe::App for EditorApp {
|
||||||
pending_graph_loads: &self.pending_graph_loads,
|
pending_graph_loads: &self.pending_graph_loads,
|
||||||
clipboard_consumed: &mut clipboard_consumed,
|
clipboard_consumed: &mut clipboard_consumed,
|
||||||
keymap: &self.keymap,
|
keymap: &self.keymap,
|
||||||
commit_raster_floating_if_any: &mut self.commit_raster_floating_if_any,
|
|
||||||
pending_node_group: &mut self.pending_node_group,
|
pending_node_group: &mut self.pending_node_group,
|
||||||
pending_node_ungroup: &mut self.pending_node_ungroup,
|
pending_node_ungroup: &mut self.pending_node_ungroup,
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
|
@ -7512,7 +7475,7 @@ fn render_pane(
|
||||||
// Active tab highlight with per-corner rounding
|
// Active tab highlight with per-corner rounding
|
||||||
if is_active {
|
if is_active {
|
||||||
let cr = corner_r as u8;
|
let cr = corner_r as u8;
|
||||||
let rounding = egui::Rounding {
|
let rounding = egui::CornerRadius {
|
||||||
nw: if i == 0 { cr } else { 0 },
|
nw: if i == 0 { cr } else { 0 },
|
||||||
sw: if i == 0 { cr } else { 0 },
|
sw: if i == 0 { cr } else { 0 },
|
||||||
ne: if i == n - 1 { cr } else { 0 },
|
ne: if i == n - 1 { cr } else { 0 },
|
||||||
|
|
@ -7557,7 +7520,7 @@ fn render_pane(
|
||||||
if tab_response.hovered() && !is_active {
|
if tab_response.hovered() && !is_active {
|
||||||
ui.painter().rect_filled(
|
ui.painter().rect_filled(
|
||||||
tab_rect,
|
tab_rect,
|
||||||
egui::Rounding {
|
egui::CornerRadius {
|
||||||
nw: if i == 0 { corner_r as u8 } else { 0 },
|
nw: if i == 0 { corner_r as u8 } else { 0 },
|
||||||
sw: if i == 0 { corner_r as u8 } else { 0 },
|
sw: if i == 0 { corner_r as u8 } else { 0 },
|
||||||
ne: if i == n - 1 { corner_r as u8 } else { 0 },
|
ne: if i == n - 1 { corner_r as u8 } else { 0 },
|
||||||
|
|
|
||||||
|
|
@ -238,7 +238,7 @@ pub fn gradient_stop_editor(
|
||||||
// ── Paint handles ─────────────────────────────────────────────────────
|
// ── Paint handles ─────────────────────────────────────────────────────
|
||||||
// handle_rects was built before any deletions this frame; guard against OOB.
|
// handle_rects was built before any deletions this frame; guard against OOB.
|
||||||
for (i, h_rect) in handle_rects.iter().enumerate().take(gradient.stops.len()) {
|
for (i, h_rect) in handle_rects.iter().enumerate().take(gradient.stops.len()) {
|
||||||
let col = ShapeColor_to_Color32(gradient.stops[i].color);
|
let col = shape_color_to_color32(gradient.stops[i].color);
|
||||||
let is_selected = *selected_stop == Some(i);
|
let is_selected = *selected_stop == Some(i);
|
||||||
let stroke = Stroke::new(
|
let stroke = Stroke::new(
|
||||||
if is_selected { 2.0 } else { 1.0 },
|
if is_selected { 2.0 } else { 1.0 },
|
||||||
|
|
@ -308,7 +308,7 @@ pub fn gradient_stop_editor(
|
||||||
|
|
||||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn ShapeColor_to_Color32(c: ShapeColor) -> Color32 {
|
fn shape_color_to_color32(c: ShapeColor) -> Color32 {
|
||||||
Color32::from_rgba_unmultiplied(c.r, c.g, c.b, c.a)
|
Color32::from_rgba_unmultiplied(c.r, c.g, c.b, c.a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
/// - Document settings (when nothing is focused)
|
/// - Document settings (when nothing is focused)
|
||||||
|
|
||||||
use eframe::egui::{self, DragValue, Ui};
|
use eframe::egui::{self, DragValue, Ui};
|
||||||
use lightningbeam_core::brush_settings::{bundled_brushes, BrushSettings};
|
use lightningbeam_core::brush_settings::bundled_brushes;
|
||||||
use lightningbeam_core::actions::{SetDocumentPropertiesAction, SetShapePropertiesAction, SetFillPaintAction, SetImageFillAction};
|
use lightningbeam_core::actions::{SetDocumentPropertiesAction, SetShapePropertiesAction, SetFillPaintAction, SetImageFillAction};
|
||||||
use lightningbeam_core::gradient::ShapeGradient;
|
use lightningbeam_core::gradient::ShapeGradient;
|
||||||
use lightningbeam_core::layer::{AnyLayer, LayerTrait};
|
use lightningbeam_core::layer::{AnyLayer, LayerTrait};
|
||||||
|
|
@ -1439,40 +1439,6 @@ impl InfopanelPane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw a brush dab preview into `rect` approximating the brush falloff shape.
|
|
||||||
///
|
|
||||||
/// Renders N concentric filled circles from outermost to innermost. Because each
|
|
||||||
/// inner circle overwrites the pixels of all outer circles beneath it, the visible
|
|
||||||
/// alpha at distance `d` from the centre equals the alpha of the innermost circle
|
|
||||||
/// whose radius ≥ `d`. This step-approximates the actual brush falloff formula:
|
|
||||||
/// `opa = ((1 − r) / (1 − hardness))²` for `r > hardness`, 1 inside the hard core.
|
|
||||||
fn paint_brush_dab(painter: &egui::Painter, rect: egui::Rect, s: &BrushSettings) {
|
|
||||||
let center = rect.center();
|
|
||||||
let max_r = (rect.width().min(rect.height()) / 2.0 - 2.0).max(1.0);
|
|
||||||
let h = s.hardness;
|
|
||||||
let a = s.opaque;
|
|
||||||
|
|
||||||
const N: usize = 12;
|
|
||||||
for i in 0..N {
|
|
||||||
// t: normalized radial position of this ring, 1.0 = outermost edge
|
|
||||||
let t = 1.0 - i as f32 / N as f32;
|
|
||||||
let r = max_r * t;
|
|
||||||
|
|
||||||
let opa_weight = if h >= 1.0 || t <= h {
|
|
||||||
1.0f32
|
|
||||||
} else {
|
|
||||||
let x = (1.0 - t) / (1.0 - h).max(1e-4);
|
|
||||||
(x * x).min(1.0)
|
|
||||||
};
|
|
||||||
|
|
||||||
let alpha = (opa_weight * a * 220.0).min(220.0) as u8;
|
|
||||||
painter.circle_filled(
|
|
||||||
center, r,
|
|
||||||
egui::Color32::from_rgba_unmultiplied(200, 200, 220, alpha),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert MIDI note number to note name (e.g. 60 -> "C4")
|
/// Convert MIDI note number to note name (e.g. 60 -> "C4")
|
||||||
fn midi_note_name(note: u8) -> String {
|
fn midi_note_name(note: u8) -> String {
|
||||||
const NAMES: [&str; 12] = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
|
const NAMES: [&str; 12] = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
|
||||||
|
|
|
||||||
|
|
@ -342,9 +342,6 @@ pub struct SharedPaneState<'a> {
|
||||||
pub clipboard_consumed: &'a mut bool,
|
pub clipboard_consumed: &'a mut bool,
|
||||||
/// Remappable keyboard shortcut manager
|
/// Remappable keyboard shortcut manager
|
||||||
pub keymap: &'a crate::keymap::KeymapManager,
|
pub keymap: &'a crate::keymap::KeymapManager,
|
||||||
/// Set by raster selection tools when they need main to commit the floating
|
|
||||||
/// selection before starting a new interaction.
|
|
||||||
pub commit_raster_floating_if_any: &'a mut bool,
|
|
||||||
/// Set by MenuAction::Group when focus is Nodes — consumed by node graph pane
|
/// Set by MenuAction::Group when focus is Nodes — consumed by node graph pane
|
||||||
pub pending_node_group: &'a mut bool,
|
pub pending_node_group: &'a mut bool,
|
||||||
/// Set by MenuAction::Group (ungroup variant) when focus is Nodes — consumed by node graph pane
|
/// Set by MenuAction::Group (ungroup variant) when focus is Nodes — consumed by node graph pane
|
||||||
|
|
|
||||||
|
|
@ -240,12 +240,6 @@ impl PianoRollPane {
|
||||||
matches!(note % 12, 1 | 3 | 6 | 8 | 10)
|
matches!(note % 12, 1 | 3 | 6 | 8 | 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn note_name(note: u8) -> String {
|
|
||||||
let names = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
|
|
||||||
let octave = (note / 12) as i32 - 1;
|
|
||||||
format!("{}{}", names[note as usize % 12], octave)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Note resolution ──────────────────────────────────────────────────
|
// ── Note resolution ──────────────────────────────────────────────────
|
||||||
|
|
||||||
fn resolve_notes(events: &[daw_backend::audio::midi::MidiEvent]) -> Vec<ResolvedNote> {
|
fn resolve_notes(events: &[daw_backend::audio::midi::MidiEvent]) -> Vec<ResolvedNote> {
|
||||||
|
|
@ -736,45 +730,6 @@ impl PianoRollPane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate pitch bend MIDI events for a note based on the zone and target semitones.
|
|
||||||
fn generate_pitch_bend_events(
|
|
||||||
note_start: f64,
|
|
||||||
note_duration: f64,
|
|
||||||
zone: PitchBendZone,
|
|
||||||
semitones: f32,
|
|
||||||
channel: u8,
|
|
||||||
pitch_bend_range: f32,
|
|
||||||
) -> Vec<daw_backend::audio::midi::MidiEvent> {
|
|
||||||
use daw_backend::audio::midi::MidiEvent;
|
|
||||||
let num_steps: usize = 128;
|
|
||||||
let mut events = Vec::new();
|
|
||||||
let encode_bend = |normalized: f32| -> (u8, u8) {
|
|
||||||
let value_14 = (normalized * 8191.0 + 8192.0).clamp(0.0, 16383.0) as i16;
|
|
||||||
((value_14 & 0x7F) as u8, ((value_14 >> 7) & 0x7F) as u8)
|
|
||||||
};
|
|
||||||
// Use t directly (0..=1 across the full note) — same formula as the visual ghost.
|
|
||||||
// Start: peak → 0 (ramps down over full note)
|
|
||||||
// Middle: 0 → peak → 0 (sine arch, peaks at center)
|
|
||||||
// End: 0 → peak (ramps up over full note)
|
|
||||||
for i in 0..=num_steps {
|
|
||||||
let t = i as f64 / num_steps as f64;
|
|
||||||
let t_f32 = t as f32;
|
|
||||||
// Cosine ease curves: Start+End at equal value = perfectly flat (partition of unity).
|
|
||||||
// Start: (1+cos(πt))/2 — peaks at t=0, smooth decay to 0 at t=1
|
|
||||||
// End: (1-cos(πt))/2 — 0 at t=0, smooth rise to peak at t=1
|
|
||||||
// Middle: sin(πt) — arch peaking at t=0.5
|
|
||||||
let normalized = match zone {
|
|
||||||
PitchBendZone::Start => semitones / pitch_bend_range * (1.0 + (std::f32::consts::PI * t_f32).cos()) * 0.5,
|
|
||||||
PitchBendZone::Middle => semitones / pitch_bend_range * (std::f32::consts::PI * t_f32).sin(),
|
|
||||||
PitchBendZone::End => semitones / pitch_bend_range * (1.0 - (std::f32::consts::PI * t_f32).cos()) * 0.5,
|
|
||||||
};
|
|
||||||
let timestamp = note_start + t * note_duration;
|
|
||||||
let (lsb, msb) = encode_bend(normalized);
|
|
||||||
events.push(MidiEvent::new(daw_backend::Beats(timestamp), 0xE0 | channel, lsb, msb));
|
|
||||||
}
|
|
||||||
events
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find the lowest available MIDI channel (1–15) not already used by any note
|
/// Find the lowest available MIDI channel (1–15) not already used by any note
|
||||||
/// overlapping [note_start, note_end], excluding the note being assigned itself.
|
/// overlapping [note_start, note_end], excluding the note being assigned itself.
|
||||||
/// Returns the note's current channel unchanged if it is already uniquely assigned (non-zero).
|
/// Returns the note's current channel unchanged if it is already uniquely assigned (non-zero).
|
||||||
|
|
@ -2408,7 +2363,7 @@ impl PaneRenderer for PianoRollPane {
|
||||||
let doc = shared.action_executor.document();
|
let doc = shared.action_executor.document();
|
||||||
let is_measures = doc.timeline_mode == lightningbeam_core::document::TimelineMode::Measures;
|
let is_measures = doc.timeline_mode == lightningbeam_core::document::TimelineMode::Measures;
|
||||||
let tempo_map = doc.tempo_map();
|
let tempo_map = doc.tempo_map();
|
||||||
drop(doc);
|
let _ = doc;
|
||||||
|
|
||||||
if is_measures {
|
if is_measures {
|
||||||
// Auto-detect grid when selection changes
|
// Auto-detect grid when selection changes
|
||||||
|
|
|
||||||
|
|
@ -682,17 +682,13 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
|
||||||
if let Some(b_id) = self.ctx.pending_tool_readback_b {
|
if let Some(b_id) = self.ctx.pending_tool_readback_b {
|
||||||
if let Ok(mut gpu_brush) = shared.gpu_brush.lock() {
|
if let Ok(mut gpu_brush) = shared.gpu_brush.lock() {
|
||||||
let dims = gpu_brush.canvases.get(&b_id).map(|c| (c.width, c.height));
|
let dims = gpu_brush.canvases.get(&b_id).map(|c| (c.width, c.height));
|
||||||
if let Some((w, h)) = dims {
|
if let Some((_w, _h)) = dims {
|
||||||
if let Some(pixels) = gpu_brush.readback_canvas(device, queue, b_id) {
|
if let Some(pixels) = gpu_brush.readback_canvas(device, queue, b_id) {
|
||||||
let results = RASTER_READBACK_RESULTS.get_or_init(|| {
|
let results = RASTER_READBACK_RESULTS.get_or_init(|| {
|
||||||
Arc::new(Mutex::new(std::collections::HashMap::new()))
|
Arc::new(Mutex::new(std::collections::HashMap::new()))
|
||||||
});
|
});
|
||||||
if let Ok(mut map) = results.lock() {
|
if let Ok(mut map) = results.lock() {
|
||||||
map.insert(self.ctx.instance_id_for_readback, RasterReadbackResult {
|
map.insert(self.ctx.instance_id_for_readback, RasterReadbackResult {
|
||||||
layer_id: uuid::Uuid::nil(), // unused; routing via pending_undo_before
|
|
||||||
time: 0.0,
|
|
||||||
canvas_width: w,
|
|
||||||
canvas_height: h,
|
|
||||||
pixels,
|
pixels,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -763,10 +759,6 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
|
||||||
});
|
});
|
||||||
if let Ok(mut map) = results.lock() {
|
if let Ok(mut map) = results.lock() {
|
||||||
map.insert(self.ctx.instance_id_for_readback, RasterReadbackResult {
|
map.insert(self.ctx.instance_id_for_readback, RasterReadbackResult {
|
||||||
layer_id: pending.layer_id,
|
|
||||||
time: pending.time,
|
|
||||||
canvas_width: pending.canvas_width,
|
|
||||||
canvas_height: pending.canvas_height,
|
|
||||||
pixels,
|
pixels,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -3173,7 +3165,6 @@ enum PendingWarpOp {
|
||||||
disp_data: Option<Vec<[f32; 2]>>,
|
disp_data: Option<Vec<[f32; 2]>>,
|
||||||
grid_cols: u32,
|
grid_cols: u32,
|
||||||
grid_rows: u32,
|
grid_rows: u32,
|
||||||
w: u32, h: u32,
|
|
||||||
final_commit: bool,
|
final_commit: bool,
|
||||||
layer_id: uuid::Uuid,
|
layer_id: uuid::Uuid,
|
||||||
time: f64,
|
time: f64,
|
||||||
|
|
@ -3190,7 +3181,6 @@ enum PendingWarpOp {
|
||||||
anchor_canvas_id: uuid::Uuid,
|
anchor_canvas_id: uuid::Uuid,
|
||||||
disp_buf_id: uuid::Uuid,
|
disp_buf_id: uuid::Uuid,
|
||||||
display_canvas_id: uuid::Uuid,
|
display_canvas_id: uuid::Uuid,
|
||||||
w: u32, h: u32,
|
|
||||||
final_commit: bool,
|
final_commit: bool,
|
||||||
layer_id: uuid::Uuid,
|
layer_id: uuid::Uuid,
|
||||||
time: f64,
|
time: f64,
|
||||||
|
|
@ -3251,10 +3241,6 @@ static EYEDROPPER_RESULTS: OnceLock<Arc<Mutex<std::collections::HashMap<u64, (eg
|
||||||
struct PendingRasterDabs {
|
struct PendingRasterDabs {
|
||||||
/// Keyframe UUID — indexes the canvas texture pair in `GpuBrushEngine`.
|
/// Keyframe UUID — indexes the canvas texture pair in `GpuBrushEngine`.
|
||||||
keyframe_id: uuid::Uuid,
|
keyframe_id: uuid::Uuid,
|
||||||
/// Layer UUID — used for the undo readback result.
|
|
||||||
layer_id: uuid::Uuid,
|
|
||||||
/// Playback time of the keyframe.
|
|
||||||
time: f64,
|
|
||||||
/// Canvas dimensions (pixels).
|
/// Canvas dimensions (pixels).
|
||||||
canvas_width: u32,
|
canvas_width: u32,
|
||||||
canvas_height: u32,
|
canvas_height: u32,
|
||||||
|
|
@ -3379,10 +3365,6 @@ static TRANSFORM_READBACK_RESULTS: OnceLock<Arc<Mutex<std::collections::HashMap<
|
||||||
|
|
||||||
/// Result stored by `prepare()` after a stroke-end readback.
|
/// Result stored by `prepare()` after a stroke-end readback.
|
||||||
struct RasterReadbackResult {
|
struct RasterReadbackResult {
|
||||||
layer_id: uuid::Uuid,
|
|
||||||
time: f64,
|
|
||||||
canvas_width: u32,
|
|
||||||
canvas_height: u32,
|
|
||||||
/// Raw RGBA pixels from the completed stroke.
|
/// Raw RGBA pixels from the completed stroke.
|
||||||
pixels: Vec<u8>,
|
pixels: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
@ -4201,7 +4183,7 @@ impl StagePane {
|
||||||
|
|
||||||
// Update connected edges: shift the adjacent control point by the same delta
|
// Update connected edges: shift the adjacent control point by the same delta
|
||||||
for &edge_id in &connected_edges {
|
for &edge_id in &connected_edges {
|
||||||
let [v0, v1] = graph.edge(edge_id).vertices;
|
let [v0, _v1] = graph.edge(edge_id).vertices;
|
||||||
let mut curve = graph.edge(edge_id).curve;
|
let mut curve = graph.edge(edge_id).curve;
|
||||||
|
|
||||||
if v0 == vertex_id {
|
if v0 == vertex_id {
|
||||||
|
|
@ -5609,58 +5591,6 @@ impl StagePane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lift the pixels enclosed by the current `raster_selection` into a
|
|
||||||
/// `RasterFloatingSelection`, punching a transparent hole in `raw_pixels`.
|
|
||||||
///
|
|
||||||
/// Call this immediately after a marquee / lasso selection is finalized so
|
|
||||||
/// that all downstream operations (drag-move, copy, cut, stroke-masking)
|
|
||||||
/// see a consistent `raster_floating` whenever a selection is active.
|
|
||||||
/// Build an R8 mask buffer (0 = outside, 255 = inside) from a selection.
|
|
||||||
fn build_selection_mask(
|
|
||||||
sel: &lightningbeam_core::selection::RasterSelection,
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
) -> Vec<u8> {
|
|
||||||
let mut mask = vec![0u8; (width * height) as usize];
|
|
||||||
let (x0, y0, x1, y1) = sel.bounding_rect();
|
|
||||||
let bx0 = x0.max(0) as u32;
|
|
||||||
let by0 = y0.max(0) as u32;
|
|
||||||
let bx1 = (x1 as u32).min(width);
|
|
||||||
let by1 = (y1 as u32).min(height);
|
|
||||||
for y in by0..by1 {
|
|
||||||
for x in bx0..bx1 {
|
|
||||||
if sel.contains_pixel(x as i32, y as i32) {
|
|
||||||
mask[(y * width + x) as usize] = 255;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mask
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build an R8 mask buffer for the float canvas (0 = outside selection, 255 = inside).
|
|
||||||
/// Coordinates are in float-local space: pixel (fx, fy) corresponds to document pixel
|
|
||||||
/// (float_x+fx, float_y+fy).
|
|
||||||
fn build_float_mask(
|
|
||||||
sel: &lightningbeam_core::selection::RasterSelection,
|
|
||||||
float_x: i32, float_y: i32,
|
|
||||||
float_w: u32, float_h: u32,
|
|
||||||
) -> Vec<u8> {
|
|
||||||
let mut mask = vec![0u8; (float_w * float_h) as usize];
|
|
||||||
let (x0, y0, x1, y1) = sel.bounding_rect();
|
|
||||||
let bx0 = (x0 - float_x).max(0) as u32;
|
|
||||||
let by0 = (y0 - float_y).max(0) as u32;
|
|
||||||
let bx1 = ((x1 - float_x) as u32).min(float_w);
|
|
||||||
let by1 = ((y1 - float_y) as u32).min(float_h);
|
|
||||||
for fy in by0..by1 {
|
|
||||||
for fx in bx0..bx1 {
|
|
||||||
if sel.contains_pixel(float_x + fx as i32, float_y + fy as i32) {
|
|
||||||
mask[(fy * float_w + fx) as usize] = 255;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mask
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allocate the three A/B/C GPU canvases and build a [`crate::raster_tool::RasterWorkspace`]
|
/// Allocate the three A/B/C GPU canvases and build a [`crate::raster_tool::RasterWorkspace`]
|
||||||
/// for a new raster tool operation.
|
/// for a new raster tool operation.
|
||||||
///
|
///
|
||||||
|
|
@ -5699,7 +5629,6 @@ impl StagePane {
|
||||||
a_canvas_id: a_id,
|
a_canvas_id: a_id,
|
||||||
b_canvas_id: b_id,
|
b_canvas_id: b_id,
|
||||||
c_canvas_id: c_id,
|
c_canvas_id: c_id,
|
||||||
mask_texture: None,
|
|
||||||
width: w,
|
width: w,
|
||||||
height: h,
|
height: h,
|
||||||
x,
|
x,
|
||||||
|
|
@ -5724,7 +5653,7 @@ impl StagePane {
|
||||||
let layer_id = (*shared.active_layer_id)?;
|
let layer_id = (*shared.active_layer_id)?;
|
||||||
let time = *shared.playback_time;
|
let time = *shared.playback_time;
|
||||||
|
|
||||||
let (doc_w, doc_h) = {
|
let (_doc_w, _doc_h) = {
|
||||||
let doc = shared.action_executor.document();
|
let doc = shared.action_executor.document();
|
||||||
(doc.width as u32, doc.height as u32)
|
(doc.width as u32, doc.height as u32)
|
||||||
};
|
};
|
||||||
|
|
@ -5733,7 +5662,7 @@ impl StagePane {
|
||||||
// returns None (no workspace) if there's no active keyframe to lift from.
|
// returns None (no workspace) if there's no active keyframe to lift from.
|
||||||
|
|
||||||
// Read keyframe id and pixels.
|
// Read keyframe id and pixels.
|
||||||
let (kf_id, w, h, pixels) = {
|
let (_kf_id, w, h, pixels) = {
|
||||||
let doc = shared.action_executor.document();
|
let doc = shared.action_executor.document();
|
||||||
let AnyLayer::Raster(rl) = doc.get_layer(&layer_id)? else { return None };
|
let AnyLayer::Raster(rl) = doc.get_layer(&layer_id)? else { return None };
|
||||||
let kf = rl.keyframe_at(time)?;
|
let kf = rl.keyframe_at(time)?;
|
||||||
|
|
@ -5753,7 +5682,6 @@ impl StagePane {
|
||||||
a_canvas_id: a_id,
|
a_canvas_id: a_id,
|
||||||
b_canvas_id: b_id,
|
b_canvas_id: b_id,
|
||||||
c_canvas_id: c_id,
|
c_canvas_id: c_id,
|
||||||
mask_texture: None,
|
|
||||||
width: w,
|
width: w,
|
||||||
height: h,
|
height: h,
|
||||||
x: 0,
|
x: 0,
|
||||||
|
|
@ -5761,9 +5689,6 @@ impl StagePane {
|
||||||
source: WorkspaceSource::Layer {
|
source: WorkspaceSource::Layer {
|
||||||
layer_id,
|
layer_id,
|
||||||
time,
|
time,
|
||||||
kf_id,
|
|
||||||
canvas_w: doc_w,
|
|
||||||
canvas_h: doc_h,
|
|
||||||
},
|
},
|
||||||
before_pixels: pixels.clone(),
|
before_pixels: pixels.clone(),
|
||||||
};
|
};
|
||||||
|
|
@ -6135,8 +6060,6 @@ impl StagePane {
|
||||||
));
|
));
|
||||||
self.pending_raster_dabs = Some(PendingRasterDabs {
|
self.pending_raster_dabs = Some(PendingRasterDabs {
|
||||||
keyframe_id: canvas_id,
|
keyframe_id: canvas_id,
|
||||||
layer_id,
|
|
||||||
time,
|
|
||||||
canvas_width,
|
canvas_width,
|
||||||
canvas_height,
|
canvas_height,
|
||||||
initial_pixels: None, // canvas already initialized via lazy GPU init
|
initial_pixels: None, // canvas already initialized via lazy GPU init
|
||||||
|
|
@ -6220,8 +6143,6 @@ impl StagePane {
|
||||||
));
|
));
|
||||||
self.pending_raster_dabs = Some(PendingRasterDabs {
|
self.pending_raster_dabs = Some(PendingRasterDabs {
|
||||||
keyframe_id,
|
keyframe_id,
|
||||||
layer_id: active_layer_id,
|
|
||||||
time: kf_time,
|
|
||||||
canvas_width,
|
canvas_width,
|
||||||
canvas_height,
|
canvas_height,
|
||||||
initial_pixels: Some(initial_pixels),
|
initial_pixels: Some(initial_pixels),
|
||||||
|
|
@ -6299,8 +6220,6 @@ impl StagePane {
|
||||||
let (dabs, dab_bbox) = BrushEngine::compute_dabs(&seg, stroke_state, dt);
|
let (dabs, dab_bbox) = BrushEngine::compute_dabs(&seg, stroke_state, dt);
|
||||||
self.pending_raster_dabs = Some(PendingRasterDabs {
|
self.pending_raster_dabs = Some(PendingRasterDabs {
|
||||||
keyframe_id: canvas_id,
|
keyframe_id: canvas_id,
|
||||||
layer_id,
|
|
||||||
time,
|
|
||||||
canvas_width: cw,
|
canvas_width: cw,
|
||||||
canvas_height: ch,
|
canvas_height: ch,
|
||||||
initial_pixels: None,
|
initial_pixels: None,
|
||||||
|
|
@ -6363,8 +6282,6 @@ impl StagePane {
|
||||||
if !dabs.is_empty() {
|
if !dabs.is_empty() {
|
||||||
self.pending_raster_dabs = Some(PendingRasterDabs {
|
self.pending_raster_dabs = Some(PendingRasterDabs {
|
||||||
keyframe_id: canvas_id,
|
keyframe_id: canvas_id,
|
||||||
layer_id,
|
|
||||||
time,
|
|
||||||
canvas_width: cw,
|
canvas_width: cw,
|
||||||
canvas_height: ch,
|
canvas_height: ch,
|
||||||
initial_pixels: None,
|
initial_pixels: None,
|
||||||
|
|
@ -6424,8 +6341,6 @@ impl StagePane {
|
||||||
if let Some(kf_id) = kf_id {
|
if let Some(kf_id) = kf_id {
|
||||||
self.pending_raster_dabs = Some(PendingRasterDabs {
|
self.pending_raster_dabs = Some(PendingRasterDabs {
|
||||||
keyframe_id: kf_id,
|
keyframe_id: kf_id,
|
||||||
layer_id: ub_layer,
|
|
||||||
time: ub_time,
|
|
||||||
canvas_width: ub_cw,
|
canvas_width: ub_cw,
|
||||||
canvas_height: ub_ch,
|
canvas_height: ub_ch,
|
||||||
initial_pixels: None,
|
initial_pixels: None,
|
||||||
|
|
@ -6941,7 +6856,7 @@ impl StagePane {
|
||||||
|
|
||||||
fn handle_quick_select_tool(
|
fn handle_quick_select_tool(
|
||||||
&mut self,
|
&mut self,
|
||||||
ui: &mut egui::Ui,
|
_ui: &mut egui::Ui,
|
||||||
response: &egui::Response,
|
response: &egui::Response,
|
||||||
world_pos: egui::Vec2,
|
world_pos: egui::Vec2,
|
||||||
shared: &mut SharedPaneState,
|
shared: &mut SharedPaneState,
|
||||||
|
|
@ -8431,7 +8346,6 @@ impl StagePane {
|
||||||
world_pos: egui::Vec2,
|
world_pos: egui::Vec2,
|
||||||
shared: &mut SharedPaneState,
|
shared: &mut SharedPaneState,
|
||||||
) {
|
) {
|
||||||
use lightningbeam_core::tool::Tool;
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
// Ensure we're on a raster layer.
|
// Ensure we're on a raster layer.
|
||||||
|
|
@ -8469,7 +8383,6 @@ impl StagePane {
|
||||||
disp_data: Some(disp_data),
|
disp_data: Some(disp_data),
|
||||||
grid_cols: ws.grid_cols,
|
grid_cols: ws.grid_cols,
|
||||||
grid_rows: ws.grid_rows,
|
grid_rows: ws.grid_rows,
|
||||||
w: ws.anchor_w, h: ws.anchor_h,
|
|
||||||
final_commit: true,
|
final_commit: true,
|
||||||
layer_id: ws.layer_id,
|
layer_id: ws.layer_id,
|
||||||
time: ws.time,
|
time: ws.time,
|
||||||
|
|
@ -8695,7 +8608,6 @@ impl StagePane {
|
||||||
disp_data: Some(disp_data),
|
disp_data: Some(disp_data),
|
||||||
grid_cols: ws.grid_cols,
|
grid_cols: ws.grid_cols,
|
||||||
grid_rows: ws.grid_rows,
|
grid_rows: ws.grid_rows,
|
||||||
w: ws.anchor_w, h: ws.anchor_h,
|
|
||||||
final_commit: false,
|
final_commit: false,
|
||||||
layer_id: ws.layer_id,
|
layer_id: ws.layer_id,
|
||||||
time: ws.time,
|
time: ws.time,
|
||||||
|
|
@ -8705,7 +8617,7 @@ impl StagePane {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
let (ws_layer_id, ws_display_id, ws_float_offset) = (ws.layer_id, ws.display_canvas_id, ws.float_offset);
|
let (ws_layer_id, ws_display_id, ws_float_offset) = (ws.layer_id, ws.display_canvas_id, ws.float_offset);
|
||||||
drop(ws); // release borrow of warp_state
|
let _ = ws; // release borrow of warp_state
|
||||||
|
|
||||||
// Display canvas is initialised by Init (zero-displacement apply), so it always
|
// Display canvas is initialised by Init (zero-displacement apply), so it always
|
||||||
// has valid content. For full-layer warp, override the layer blit unconditionally.
|
// has valid content. For full-layer warp, override the layer blit unconditionally.
|
||||||
|
|
@ -8778,7 +8690,6 @@ impl StagePane {
|
||||||
anchor_canvas_id: ls.anchor_canvas_id,
|
anchor_canvas_id: ls.anchor_canvas_id,
|
||||||
disp_buf_id: ls.disp_buf_id,
|
disp_buf_id: ls.disp_buf_id,
|
||||||
display_canvas_id: ls.display_canvas_id,
|
display_canvas_id: ls.display_canvas_id,
|
||||||
w: ls.anchor_w, h: ls.anchor_h,
|
|
||||||
final_commit: true,
|
final_commit: true,
|
||||||
layer_id: ls.layer_id,
|
layer_id: ls.layer_id,
|
||||||
time: ls.time,
|
time: ls.time,
|
||||||
|
|
@ -8950,7 +8861,6 @@ impl StagePane {
|
||||||
anchor_canvas_id: anchor_id,
|
anchor_canvas_id: anchor_id,
|
||||||
disp_buf_id: disp_buf,
|
disp_buf_id: disp_buf,
|
||||||
display_canvas_id: display_id,
|
display_canvas_id: display_id,
|
||||||
w, h,
|
|
||||||
final_commit: false,
|
final_commit: false,
|
||||||
layer_id: ls_layer_id,
|
layer_id: ls_layer_id,
|
||||||
time,
|
time,
|
||||||
|
|
@ -9013,7 +8923,7 @@ impl StagePane {
|
||||||
} else { None }
|
} else { None }
|
||||||
} else { None }
|
} else { None }
|
||||||
} else { None };
|
} else { None };
|
||||||
drop(doc);
|
let _ = doc;
|
||||||
r
|
r
|
||||||
} else { None };
|
} else { None };
|
||||||
|
|
||||||
|
|
@ -10706,7 +10616,6 @@ impl StagePane {
|
||||||
|
|
||||||
// Alt+click: set source point for clone/healing tools.
|
// Alt+click: set source point for clone/healing tools.
|
||||||
{
|
{
|
||||||
use lightningbeam_core::tool::Tool;
|
|
||||||
let tool_uses_alt = crate::tools::raster_tool_def(shared.selected_tool)
|
let tool_uses_alt = crate::tools::raster_tool_def(shared.selected_tool)
|
||||||
.map_or(false, |d| d.uses_alt_click());
|
.map_or(false, |d| d.uses_alt_click());
|
||||||
if tool_uses_alt
|
if tool_uses_alt
|
||||||
|
|
@ -11602,7 +11511,6 @@ impl PaneRenderer for StagePane {
|
||||||
disp_data: Some(disp_data),
|
disp_data: Some(disp_data),
|
||||||
grid_cols: ws.grid_cols,
|
grid_cols: ws.grid_cols,
|
||||||
grid_rows: ws.grid_rows,
|
grid_rows: ws.grid_rows,
|
||||||
w: ws.anchor_w, h: ws.anchor_h,
|
|
||||||
final_commit: true,
|
final_commit: true,
|
||||||
layer_id: ws.layer_id,
|
layer_id: ws.layer_id,
|
||||||
time: ws.time,
|
time: ws.time,
|
||||||
|
|
@ -11623,7 +11531,6 @@ impl PaneRenderer for StagePane {
|
||||||
anchor_canvas_id: ls.anchor_canvas_id,
|
anchor_canvas_id: ls.anchor_canvas_id,
|
||||||
disp_buf_id: ls.disp_buf_id,
|
disp_buf_id: ls.disp_buf_id,
|
||||||
display_canvas_id: ls.display_canvas_id,
|
display_canvas_id: ls.display_canvas_id,
|
||||||
w: ls.anchor_w, h: ls.anchor_h,
|
|
||||||
final_commit: true,
|
final_commit: true,
|
||||||
layer_id: ls.layer_id,
|
layer_id: ls.layer_id,
|
||||||
time: ls.time,
|
time: ls.time,
|
||||||
|
|
|
||||||
|
|
@ -483,7 +483,6 @@ struct AutomationLaneRender {
|
||||||
value_max: f32,
|
value_max: f32,
|
||||||
accent_color: egui::Color32,
|
accent_color: egui::Color32,
|
||||||
playback_time: f64,
|
playback_time: f64,
|
||||||
kind: AutomationLaneKind,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pending automation keyframe edit action from curve lane interaction
|
/// Pending automation keyframe edit action from curve lane interaction
|
||||||
|
|
@ -1160,7 +1159,7 @@ impl TimelinePane {
|
||||||
*shared.recording_clips.get(&layer_id).unwrap_or(&0), daw_backend::Beats::ZERO);
|
*shared.recording_clips.get(&layer_id).unwrap_or(&0), daw_backend::Beats::ZERO);
|
||||||
let doc_clip_id = shared.action_executor.document_mut().add_audio_clip(doc_clip);
|
let doc_clip_id = shared.action_executor.document_mut().add_audio_clip(doc_clip);
|
||||||
|
|
||||||
let mut clip_instance = ClipInstance::new(doc_clip_id)
|
let clip_instance = ClipInstance::new(doc_clip_id)
|
||||||
.with_timeline_start(start_time);
|
.with_timeline_start(start_time);
|
||||||
|
|
||||||
if let Some(layer) = shared.action_executor.document_mut().get_layer_mut(&layer_id) {
|
if let Some(layer) = shared.action_executor.document_mut().get_layer_mut(&layer_id) {
|
||||||
|
|
@ -3230,7 +3229,6 @@ impl TimelinePane {
|
||||||
value_max: lane.value_max,
|
value_max: lane.value_max,
|
||||||
accent_color: lane_accent,
|
accent_color: lane_accent,
|
||||||
playback_time,
|
playback_time,
|
||||||
kind: lane.kind,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4054,7 +4052,6 @@ impl TimelinePane {
|
||||||
value_max: lane.value_max,
|
value_max: lane.value_max,
|
||||||
accent_color: lane_accent,
|
accent_color: lane_accent,
|
||||||
playback_time,
|
playback_time,
|
||||||
kind: lane.kind,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5409,12 +5406,10 @@ impl PaneRenderer for TimelinePane {
|
||||||
let editing_clip_id = shared.editing_clip_id;
|
let editing_clip_id = shared.editing_clip_id;
|
||||||
let mut context_layers = document.context_layers(editing_clip_id.as_ref());
|
let mut context_layers = document.context_layers(editing_clip_id.as_ref());
|
||||||
// Prepend master track as the first row when enabled (only at root context, not inside clips)
|
// Prepend master track as the first row when enabled (only at root context, not inside clips)
|
||||||
let master_any_layer;
|
let _master_any_layer;
|
||||||
if self.show_master_track && editing_clip_id.is_none() {
|
if self.show_master_track && editing_clip_id.is_none() {
|
||||||
master_any_layer = Some(AnyLayer::Group(document.master_layer.clone()));
|
_master_any_layer = Some(AnyLayer::Group(document.master_layer.clone()));
|
||||||
context_layers.insert(0, master_any_layer.as_ref().unwrap());
|
context_layers.insert(0, _master_any_layer.as_ref().unwrap());
|
||||||
} else {
|
|
||||||
master_any_layer = None;
|
|
||||||
}
|
}
|
||||||
// Use virtual row count (includes expanded group children) for height calculations
|
// Use virtual row count (includes expanded group children) for height calculations
|
||||||
let layer_count = build_timeline_rows(&context_layers).len();
|
let layer_count = build_timeline_rows(&context_layers).len();
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
//! allocates and validates them in [`begin_raster_workspace`]; tools only
|
//! allocates and validates them in [`begin_raster_workspace`]; tools only
|
||||||
//! dispatch shaders.
|
//! dispatch shaders.
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
|
|
||||||
|
|
@ -25,11 +24,6 @@ pub enum WorkspaceSource {
|
||||||
Layer {
|
Layer {
|
||||||
layer_id: Uuid,
|
layer_id: Uuid,
|
||||||
time: f64,
|
time: f64,
|
||||||
/// The keyframe's own UUID (the A-canvas key in `GpuBrushEngine`).
|
|
||||||
kf_id: Uuid,
|
|
||||||
/// Full canvas dimensions (may differ from workspace dims for floating selections).
|
|
||||||
canvas_w: u32,
|
|
||||||
canvas_h: u32,
|
|
||||||
},
|
},
|
||||||
/// Operating on the floating selection.
|
/// Operating on the floating selection.
|
||||||
Float,
|
Float,
|
||||||
|
|
@ -51,9 +45,6 @@ pub struct RasterWorkspace {
|
||||||
pub b_canvas_id: Uuid,
|
pub b_canvas_id: Uuid,
|
||||||
/// C canvas (Rgba16Float) — scratch; tools accumulate dabs here across the stroke.
|
/// C canvas (Rgba16Float) — scratch; tools accumulate dabs here across the stroke.
|
||||||
pub c_canvas_id: Uuid,
|
pub c_canvas_id: Uuid,
|
||||||
/// Optional R8Unorm selection mask (same pixel dimensions as A/B/C).
|
|
||||||
/// `None` means the entire workspace is selected.
|
|
||||||
pub mask_texture: Option<Arc<wgpu::Texture>>,
|
|
||||||
/// Pixel dimensions. A, B, C, and mask are all guaranteed to be this size.
|
/// Pixel dimensions. A, B, C, and mask are all guaranteed to be this size.
|
||||||
pub width: u32,
|
pub width: u32,
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
|
|
@ -69,40 +60,6 @@ pub struct RasterWorkspace {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RasterWorkspace {
|
impl RasterWorkspace {
|
||||||
/// Panic-safe bounds check. Asserts that every GPU canvas exists and has
|
|
||||||
/// the dimensions declared by this workspace. Called by the framework
|
|
||||||
/// before `begin()` and before each `update()`.
|
|
||||||
pub fn validate(&self, gpu: &crate::gpu_brush::GpuBrushEngine) {
|
|
||||||
for (name, id) in [
|
|
||||||
("A", self.a_canvas_id),
|
|
||||||
("B", self.b_canvas_id),
|
|
||||||
("C", self.c_canvas_id),
|
|
||||||
] {
|
|
||||||
let canvas = gpu.canvases.get(&id).unwrap_or_else(|| {
|
|
||||||
panic!(
|
|
||||||
"RasterWorkspace::validate: buffer '{}' (id={}) not found in GpuBrushEngine",
|
|
||||||
name, id
|
|
||||||
)
|
|
||||||
});
|
|
||||||
assert_eq!(
|
|
||||||
canvas.width, self.width,
|
|
||||||
"RasterWorkspace::validate: buffer '{}' width {} != workspace width {}",
|
|
||||||
name, canvas.width, self.width
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
canvas.height, self.height,
|
|
||||||
"RasterWorkspace::validate: buffer '{}' height {} != workspace height {}",
|
|
||||||
name, canvas.height, self.height
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let expected = (self.width * self.height * 4) as usize;
|
|
||||||
assert_eq!(
|
|
||||||
self.before_pixels.len(), expected,
|
|
||||||
"RasterWorkspace::validate: before_pixels.len()={} != expected {}",
|
|
||||||
self.before_pixels.len(), expected
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the three canvas UUIDs as an array (convenient for bulk removal).
|
/// Returns the three canvas UUIDs as an array (convenient for bulk removal).
|
||||||
pub fn canvas_ids(&self) -> [Uuid; 3] {
|
pub fn canvas_ids(&self) -> [Uuid; 3] {
|
||||||
[self.a_canvas_id, self.b_canvas_id, self.c_canvas_id]
|
[self.a_canvas_id, self.b_canvas_id, self.c_canvas_id]
|
||||||
|
|
@ -208,11 +165,6 @@ pub trait RasterTool: Send + Sync {
|
||||||
/// the operation was a no-op (e.g. the pointer never moved).
|
/// the operation was a no-op (e.g. the pointer never moved).
|
||||||
fn finish(&mut self, ws: &RasterWorkspace) -> bool;
|
fn finish(&mut self, ws: &RasterWorkspace) -> bool;
|
||||||
|
|
||||||
/// Called on **Escape** or tool switch mid-stroke. The caller restores the
|
|
||||||
/// source pixels from `ws.before_pixels` without creating an undo entry; the
|
|
||||||
/// tool just cleans up internal state.
|
|
||||||
fn cancel(&mut self, ws: &RasterWorkspace);
|
|
||||||
|
|
||||||
/// Called once per frame (in the VelloCallback construction, UI thread) to
|
/// Called once per frame (in the VelloCallback construction, UI thread) to
|
||||||
/// extract pending GPU work accumulated by `begin()` / `update()`.
|
/// extract pending GPU work accumulated by `begin()` / `update()`.
|
||||||
///
|
///
|
||||||
|
|
@ -377,384 +329,7 @@ impl RasterTool for BrushRasterTool {
|
||||||
self.has_dabs
|
self.has_dabs
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cancel(&mut self, _ws: &RasterWorkspace) {
|
|
||||||
self.pending = None;
|
|
||||||
self.has_dabs = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn take_pending_gpu_work(&mut self) -> Option<Box<dyn PendingGpuWork>> {
|
fn take_pending_gpu_work(&mut self) -> Option<Box<dyn PendingGpuWork>> {
|
||||||
self.pending.take().map(|w| w as Box<dyn PendingGpuWork>)
|
self.pending.take().map(|w| w as Box<dyn PendingGpuWork>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── EffectBrushTool ───────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/// Raster tool for effect brushes (Blur, Sharpen, Dodge, Burn, Sponge, Desaturate).
|
|
||||||
///
|
|
||||||
/// C accumulates a per-pixel influence weight (R channel, 0–255).
|
|
||||||
/// The composite pass applies the effect to A, scaled by C.r, writing to B:
|
|
||||||
/// `B = lerp(A, effect(A), C.r)`
|
|
||||||
///
|
|
||||||
/// Using C as an influence map (rather than accumulating modified pixels) prevents
|
|
||||||
/// overlapping dabs from compounding the effect beyond the C.r cap (255).
|
|
||||||
///
|
|
||||||
/// # GPU implementation (TODO)
|
|
||||||
/// Requires a dedicated `effect_brush_composite.wgsl` shader that reads A and C,
|
|
||||||
/// applies the blend-mode-specific filter to A, and blends by C.r → B.
|
|
||||||
pub struct EffectBrushTool {
|
|
||||||
brush: BrushSettings,
|
|
||||||
blend_mode: RasterBlendMode,
|
|
||||||
has_dabs: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EffectBrushTool {
|
|
||||||
pub fn new(brush: BrushSettings, blend_mode: RasterBlendMode) -> Self {
|
|
||||||
Self { brush, blend_mode, has_dabs: false }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RasterTool for EffectBrushTool {
|
|
||||||
fn begin(&mut self, _ws: &RasterWorkspace, _pos: egui::Vec2, _dt: f32, _settings: &crate::tools::RasterToolSettings) {}
|
|
||||||
fn update(&mut self, _ws: &RasterWorkspace, _pos: egui::Vec2, _dt: f32, _settings: &crate::tools::RasterToolSettings) {
|
|
||||||
self.has_dabs = true; // placeholder
|
|
||||||
}
|
|
||||||
fn finish(&mut self, _ws: &RasterWorkspace) -> bool { self.has_dabs }
|
|
||||||
fn cancel(&mut self, _ws: &RasterWorkspace) { self.has_dabs = false; }
|
|
||||||
// GPU shaders not yet implemented; take_pending_gpu_work returns None (default).
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── SmudgeTool ────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/// Raster tool for the smudge brush.
|
|
||||||
///
|
|
||||||
/// `begin()`: copy A → C so C starts with the source pixels for color pickup.
|
|
||||||
/// `update()`: dispatch smudge dabs using `blend_mode=2` (reads C as source,
|
|
||||||
/// writes smear to C); then composite C over A → B.
|
|
||||||
/// Because the smudge shader reads from `canvas_src` (C.src) and writes to
|
|
||||||
/// `canvas_dst` (C.dst), existing dabs are preserved in the smear history.
|
|
||||||
///
|
|
||||||
/// # GPU implementation (TODO)
|
|
||||||
/// Requires an initial A → C copy in `begin()` (via GPU copy command).
|
|
||||||
/// The smudge dab dispatch then uses `render_dabs(c_id, smudge_dabs, ...)`.
|
|
||||||
/// The composite pass is `composite_a_c_to_b` (same as BrushRasterTool).
|
|
||||||
pub struct SmudgeTool {
|
|
||||||
brush: BrushSettings,
|
|
||||||
has_dabs: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SmudgeTool {
|
|
||||||
pub fn new(brush: BrushSettings) -> Self {
|
|
||||||
Self { brush, has_dabs: false }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RasterTool for SmudgeTool {
|
|
||||||
fn begin(&mut self, _ws: &RasterWorkspace, _pos: egui::Vec2, _dt: f32, _settings: &crate::tools::RasterToolSettings) {}
|
|
||||||
fn update(&mut self, _ws: &RasterWorkspace, _pos: egui::Vec2, _dt: f32, _settings: &crate::tools::RasterToolSettings) {
|
|
||||||
self.has_dabs = true; // placeholder
|
|
||||||
}
|
|
||||||
fn finish(&mut self, _ws: &RasterWorkspace) -> bool { self.has_dabs }
|
|
||||||
fn cancel(&mut self, _ws: &RasterWorkspace) { self.has_dabs = false; }
|
|
||||||
// GPU shaders not yet implemented; take_pending_gpu_work returns None (default).
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── GradientRasterTool ────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
use crate::gpu_brush::GpuGradientStop;
|
|
||||||
use lightningbeam_core::gradient::{GradientExtend, GradientType, ShapeGradient};
|
|
||||||
|
|
||||||
fn gradient_stops_to_gpu(gradient: &ShapeGradient) -> Vec<GpuGradientStop> {
|
|
||||||
gradient.stops.iter().map(|s| {
|
|
||||||
GpuGradientStop::from_srgb_u8(s.position, s.color.r, s.color.g, s.color.b, s.color.a)
|
|
||||||
}).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gradient_extend_to_u32(extend: GradientExtend) -> u32 {
|
|
||||||
match extend {
|
|
||||||
GradientExtend::Pad => 0,
|
|
||||||
GradientExtend::Reflect => 1,
|
|
||||||
GradientExtend::Repeat => 2,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gradient_kind_to_u32(kind: GradientType) -> u32 {
|
|
||||||
match kind {
|
|
||||||
GradientType::Linear => 0,
|
|
||||||
GradientType::Radial => 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PendingGradientWork {
|
|
||||||
a_id: Uuid,
|
|
||||||
b_id: Uuid,
|
|
||||||
stops: Vec<GpuGradientStop>,
|
|
||||||
start: (f32, f32),
|
|
||||||
end: (f32, f32),
|
|
||||||
opacity: f32,
|
|
||||||
extend_mode: u32,
|
|
||||||
kind: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PendingGpuWork for PendingGradientWork {
|
|
||||||
fn execute(&self, device: &wgpu::Device, queue: &wgpu::Queue, gpu: &mut crate::gpu_brush::GpuBrushEngine) {
|
|
||||||
gpu.apply_gradient_fill(
|
|
||||||
device, queue,
|
|
||||||
&self.a_id, &self.b_id,
|
|
||||||
&self.stops,
|
|
||||||
self.start, self.end,
|
|
||||||
self.opacity, self.extend_mode, self.kind,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Raster tool for gradient fills.
|
|
||||||
///
|
|
||||||
/// `begin()` records the canvas-local start position.
|
|
||||||
/// `update()` recomputes gradient parameters from settings and queues a
|
|
||||||
/// `PendingGradientWork` that calls `apply_gradient_fill` in `prepare()`.
|
|
||||||
/// `finish()` returns whether any gradient was dispatched.
|
|
||||||
pub struct GradientRasterTool {
|
|
||||||
start_canvas: egui::Vec2,
|
|
||||||
end_canvas: egui::Vec2,
|
|
||||||
pending: Option<Box<PendingGradientWork>>,
|
|
||||||
has_dispatched: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GradientRasterTool {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
start_canvas: egui::Vec2::ZERO,
|
|
||||||
end_canvas: egui::Vec2::ZERO,
|
|
||||||
pending: None,
|
|
||||||
has_dispatched: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RasterTool for GradientRasterTool {
|
|
||||||
fn begin(&mut self, ws: &RasterWorkspace, pos: egui::Vec2, _dt: f32, _settings: &crate::tools::RasterToolSettings) {
|
|
||||||
let canvas_pos = pos - egui::vec2(ws.x as f32, ws.y as f32);
|
|
||||||
self.start_canvas = canvas_pos;
|
|
||||||
self.end_canvas = canvas_pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, ws: &RasterWorkspace, pos: egui::Vec2, _dt: f32, settings: &crate::tools::RasterToolSettings) {
|
|
||||||
self.end_canvas = pos - egui::vec2(ws.x as f32, ws.y as f32);
|
|
||||||
let gradient = &settings.gradient;
|
|
||||||
self.pending = Some(Box::new(PendingGradientWork {
|
|
||||||
a_id: ws.a_canvas_id,
|
|
||||||
b_id: ws.b_canvas_id,
|
|
||||||
stops: gradient_stops_to_gpu(gradient),
|
|
||||||
start: (self.start_canvas.x, self.start_canvas.y),
|
|
||||||
end: (self.end_canvas.x, self.end_canvas.y),
|
|
||||||
opacity: settings.gradient_opacity,
|
|
||||||
extend_mode: gradient_extend_to_u32(gradient.extend),
|
|
||||||
kind: gradient_kind_to_u32(gradient.kind),
|
|
||||||
}));
|
|
||||||
self.has_dispatched = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish(&mut self, _ws: &RasterWorkspace) -> bool { self.has_dispatched }
|
|
||||||
|
|
||||||
fn cancel(&mut self, _ws: &RasterWorkspace) {
|
|
||||||
self.pending = None;
|
|
||||||
self.has_dispatched = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn take_pending_gpu_work(&mut self) -> Option<Box<dyn PendingGpuWork>> {
|
|
||||||
self.pending.take().map(|w| w as Box<dyn PendingGpuWork>)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── TransformRasterTool ───────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
use crate::gpu_brush::RasterTransformGpuParams;
|
|
||||||
|
|
||||||
struct PendingTransformWork {
|
|
||||||
a_id: Uuid,
|
|
||||||
b_id: Uuid,
|
|
||||||
params: RasterTransformGpuParams,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PendingGpuWork for PendingTransformWork {
|
|
||||||
fn execute(&self, device: &wgpu::Device, queue: &wgpu::Queue, gpu: &mut crate::gpu_brush::GpuBrushEngine) {
|
|
||||||
gpu.render_transform(device, queue, &self.a_id, &self.b_id, self.params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Raster tool for affine transforms (move, scale, rotate, shear).
|
|
||||||
///
|
|
||||||
/// `begin()` stores the initial canvas dimensions and queues an identity
|
|
||||||
/// transform so B is initialised on the first frame.
|
|
||||||
/// `update()` recomputes the inverse affine matrix from the current handle
|
|
||||||
/// positions and queues a new `PendingTransformWork`.
|
|
||||||
///
|
|
||||||
/// The inverse matrix maps output pixel coordinates back to source pixel
|
|
||||||
/// coordinates: `src = M_inv * dst + b`
|
|
||||||
/// where `M_inv = [[a00, a01], [a10, a11]]` and `b = [b0, b1]`.
|
|
||||||
///
|
|
||||||
/// # GPU implementation
|
|
||||||
/// Fully wired — uses `GpuBrushEngine::render_transform`. Handle interaction
|
|
||||||
/// logic (drag, rotate, scale) is handled by the tool's `update()` caller in
|
|
||||||
/// `stage.rs` which computes and passes in the `RasterTransformGpuParams`.
|
|
||||||
pub struct TransformRasterTool {
|
|
||||||
pending: Option<Box<PendingTransformWork>>,
|
|
||||||
has_dispatched: bool,
|
|
||||||
canvas_w: u32,
|
|
||||||
canvas_h: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TransformRasterTool {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
pending: None,
|
|
||||||
has_dispatched: false,
|
|
||||||
canvas_w: 0,
|
|
||||||
canvas_h: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queue a transform with the given inverse-affine matrix.
|
|
||||||
/// Called by the stage handler after computing handle positions.
|
|
||||||
pub fn set_transform(
|
|
||||||
&mut self,
|
|
||||||
ws: &RasterWorkspace,
|
|
||||||
params: RasterTransformGpuParams,
|
|
||||||
) {
|
|
||||||
self.pending = Some(Box::new(PendingTransformWork {
|
|
||||||
a_id: ws.a_canvas_id,
|
|
||||||
b_id: ws.b_canvas_id,
|
|
||||||
params,
|
|
||||||
}));
|
|
||||||
self.has_dispatched = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RasterTool for TransformRasterTool {
|
|
||||||
fn begin(&mut self, ws: &RasterWorkspace, _pos: egui::Vec2, _dt: f32, _settings: &crate::tools::RasterToolSettings) {
|
|
||||||
self.canvas_w = ws.width;
|
|
||||||
self.canvas_h = ws.height;
|
|
||||||
// Queue identity transform so B shows the source immediately.
|
|
||||||
let identity = RasterTransformGpuParams {
|
|
||||||
a00: 1.0, a01: 0.0,
|
|
||||||
a10: 0.0, a11: 1.0,
|
|
||||||
b0: 0.0, b1: 0.0,
|
|
||||||
src_w: ws.width, src_h: ws.height,
|
|
||||||
dst_w: ws.width, dst_h: ws.height,
|
|
||||||
_pad0: 0, _pad1: 0,
|
|
||||||
};
|
|
||||||
self.set_transform(ws, identity);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _ws: &RasterWorkspace, _pos: egui::Vec2, _dt: f32, _settings: &crate::tools::RasterToolSettings) {
|
|
||||||
// Handle interaction and matrix updates are driven from stage.rs via set_transform().
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish(&mut self, _ws: &RasterWorkspace) -> bool { self.has_dispatched }
|
|
||||||
|
|
||||||
fn cancel(&mut self, _ws: &RasterWorkspace) {
|
|
||||||
self.pending = None;
|
|
||||||
self.has_dispatched = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn take_pending_gpu_work(&mut self) -> Option<Box<dyn PendingGpuWork>> {
|
|
||||||
self.pending.take().map(|w| w as Box<dyn PendingGpuWork>)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── WarpRasterTool ────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/// Raster tool for warp / mesh deformation.
|
|
||||||
///
|
|
||||||
/// Uses a displacement buffer (managed by `GpuBrushEngine`) that maps each
|
|
||||||
/// output pixel to a source offset. The displacement grid is updated by
|
|
||||||
/// dragging control points; the warp shader reads anchor pixels + displacement
|
|
||||||
/// → B each frame.
|
|
||||||
///
|
|
||||||
/// # GPU implementation (TODO)
|
|
||||||
/// Requires: `create_displacement_buf`, `apply_warp` already exist in
|
|
||||||
/// `GpuBrushEngine`. Wire brush-drag interaction to update displacement
|
|
||||||
/// entries and call `apply_warp`.
|
|
||||||
pub struct WarpRasterTool {
|
|
||||||
has_dispatched: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WarpRasterTool {
|
|
||||||
pub fn new() -> Self { Self { has_dispatched: false } }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RasterTool for WarpRasterTool {
|
|
||||||
fn begin(&mut self, _ws: &RasterWorkspace, _pos: egui::Vec2, _dt: f32, _settings: &crate::tools::RasterToolSettings) {}
|
|
||||||
fn update(&mut self, _ws: &RasterWorkspace, _pos: egui::Vec2, _dt: f32, _settings: &crate::tools::RasterToolSettings) {
|
|
||||||
self.has_dispatched = true; // placeholder
|
|
||||||
}
|
|
||||||
fn finish(&mut self, _ws: &RasterWorkspace) -> bool { self.has_dispatched }
|
|
||||||
fn cancel(&mut self, _ws: &RasterWorkspace) { self.has_dispatched = false; }
|
|
||||||
// take_pending_gpu_work: default (None) — full GPU wiring is TODO.
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── LiquifyRasterTool ─────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/// Raster tool for liquify (per-pixel displacement painting).
|
|
||||||
///
|
|
||||||
/// Similar to `WarpRasterTool` but uses a full per-pixel displacement map
|
|
||||||
/// (grid_cols = grid_rows = 0 in `apply_warp`) painted by brush strokes.
|
|
||||||
/// Each dab accumulates displacement in the push/pull/swirl direction.
|
|
||||||
///
|
|
||||||
/// # GPU implementation (TODO)
|
|
||||||
/// Requires: a dab-to-displacement shader that accumulates per-pixel offsets
|
|
||||||
/// into the displacement buffer, then `apply_warp` reads it → B.
|
|
||||||
pub struct LiquifyRasterTool {
|
|
||||||
has_dispatched: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LiquifyRasterTool {
|
|
||||||
pub fn new() -> Self { Self { has_dispatched: false } }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RasterTool for LiquifyRasterTool {
|
|
||||||
fn begin(&mut self, _ws: &RasterWorkspace, _pos: egui::Vec2, _dt: f32, _settings: &crate::tools::RasterToolSettings) {}
|
|
||||||
fn update(&mut self, _ws: &RasterWorkspace, _pos: egui::Vec2, _dt: f32, _settings: &crate::tools::RasterToolSettings) {
|
|
||||||
self.has_dispatched = true; // placeholder
|
|
||||||
}
|
|
||||||
fn finish(&mut self, _ws: &RasterWorkspace) -> bool { self.has_dispatched }
|
|
||||||
fn cancel(&mut self, _ws: &RasterWorkspace) { self.has_dispatched = false; }
|
|
||||||
// take_pending_gpu_work: default (None) — full GPU wiring is TODO.
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── SelectionTool ─────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/// Raster selection tool (Magic Wand / Quick Select).
|
|
||||||
///
|
|
||||||
/// C (RGBA8) acts as the growing selection; C.r = mask value (0 or 255).
|
|
||||||
/// Each `update()` frame a flood-fill / region-grow shader extends C.r.
|
|
||||||
/// The composite pass draws A + a tinted overlay from C.r → B so the user
|
|
||||||
/// sees the growing selection boundary.
|
|
||||||
///
|
|
||||||
/// `finish()` returns false (commit does not write pixels back to the layer;
|
|
||||||
/// instead the caller extracts C.r into the standalone `R8Unorm` selection
|
|
||||||
/// texture via `shared.raster_selection`).
|
|
||||||
///
|
|
||||||
/// # GPU implementation (TODO)
|
|
||||||
/// Requires: a flood-fill compute shader seeded by the click position that
|
|
||||||
/// grows the selection in C.r; and a composite shader that tints selected
|
|
||||||
/// pixels blue/cyan for preview.
|
|
||||||
pub struct SelectionTool {
|
|
||||||
has_selection: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SelectionTool {
|
|
||||||
pub fn new() -> Self { Self { has_selection: false } }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RasterTool for SelectionTool {
|
|
||||||
fn begin(&mut self, _ws: &RasterWorkspace, _pos: egui::Vec2, _dt: f32, _settings: &crate::tools::RasterToolSettings) {}
|
|
||||||
fn update(&mut self, _ws: &RasterWorkspace, _pos: egui::Vec2, _dt: f32, _settings: &crate::tools::RasterToolSettings) {
|
|
||||||
self.has_selection = true; // placeholder
|
|
||||||
}
|
|
||||||
/// Selection tools never trigger a pixel readback/commit on mouseup.
|
|
||||||
/// The caller reads C.r directly into the selection mask texture.
|
|
||||||
fn finish(&mut self, _ws: &RasterWorkspace) -> bool { false }
|
|
||||||
fn cancel(&mut self, _ws: &RasterWorkspace) { self.has_selection = false; }
|
|
||||||
// take_pending_gpu_work: default (None) — full GPU wiring is TODO.
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -232,15 +232,6 @@ impl TestModeState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Store geometry context for panic capture.
|
|
||||||
/// Called before risky operations (e.g. region select) so the panic hook
|
|
||||||
/// can include it in the crash file for easier reproduction.
|
|
||||||
pub fn set_pending_geometry(&self, context: serde_json::Value) {
|
|
||||||
if let Ok(mut guard) = self.pending_geometry.try_lock() {
|
|
||||||
*guard = Some(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear the pending geometry context (call after the operation succeeds).
|
/// Clear the pending geometry context (call after the operation succeeds).
|
||||||
pub fn clear_pending_geometry(&self) {
|
pub fn clear_pending_geometry(&self) {
|
||||||
if let Ok(mut guard) = self.pending_geometry.try_lock() {
|
if let Ok(mut guard) = self.pending_geometry.try_lock() {
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ impl ThemeMode {
|
||||||
|
|
||||||
/// Background type for CSS backgrounds
|
/// Background type for CSS backgrounds
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
#[allow(dead_code)]
|
||||||
pub enum Background {
|
pub enum Background {
|
||||||
Solid(egui::Color32),
|
Solid(egui::Color32),
|
||||||
LinearGradient {
|
LinearGradient {
|
||||||
|
|
@ -456,6 +457,7 @@ impl Theme {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invalidate the cache (call on stylesheet reload or mode change)
|
/// Invalidate the cache (call on stylesheet reload or mode change)
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn invalidate_cache(&self) {
|
pub fn invalidate_cache(&self) {
|
||||||
self.cache.borrow_mut().clear();
|
self.cache.borrow_mut().clear();
|
||||||
}
|
}
|
||||||
|
|
@ -518,6 +520,7 @@ impl Theme {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience: resolve and extract a dimension with fallback
|
/// Convenience: resolve and extract a dimension with fallback
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn dimension(&self, context: &[&str], ctx: &egui::Context, property: &str, fallback: f32) -> f32 {
|
pub fn dimension(&self, context: &[&str], ctx: &egui::Context, property: &str, fallback: f32) -> f32 {
|
||||||
let style = self.resolve(context, ctx);
|
let style = self.resolve(context, ctx);
|
||||||
match property {
|
match property {
|
||||||
|
|
@ -534,6 +537,7 @@ impl Theme {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Paint background for a region (handles solid/gradient/image)
|
/// Paint background for a region (handles solid/gradient/image)
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn paint_bg(
|
pub fn paint_bg(
|
||||||
&self,
|
&self,
|
||||||
context: &[&str],
|
context: &[&str],
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use eframe::egui;
|
||||||
use crate::theme::Background;
|
use crate::theme::Background;
|
||||||
|
|
||||||
/// Paint a background into the given rect
|
/// Paint a background into the given rect
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn paint_background(
|
pub fn paint_background(
|
||||||
painter: &egui::Painter,
|
painter: &egui::Painter,
|
||||||
rect: egui::Rect,
|
rect: egui::Rect,
|
||||||
|
|
@ -35,6 +36,7 @@ pub fn paint_background(
|
||||||
/// - 90deg = left to right
|
/// - 90deg = left to right
|
||||||
/// - 180deg = top to bottom (default)
|
/// - 180deg = top to bottom (default)
|
||||||
/// - 270deg = right to left
|
/// - 270deg = right to left
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn paint_linear_gradient(
|
pub fn paint_linear_gradient(
|
||||||
painter: &egui::Painter,
|
painter: &egui::Painter,
|
||||||
rect: egui::Rect,
|
rect: egui::Rect,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue