diff --git a/lightningbeam-ui/lightningbeam-core/src/clip.rs b/lightningbeam-ui/lightningbeam-core/src/clip.rs index 27abc8a..407ffc7 100644 --- a/lightningbeam-ui/lightningbeam-core/src/clip.rs +++ b/lightningbeam-ui/lightningbeam-core/src/clip.rs @@ -17,7 +17,7 @@ use crate::object::Transform; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use uuid::Uuid; -use vello::kurbo::{Rect, Affine, Shape as KurboShape}; +use vello::kurbo::{Rect, Shape as KurboShape}; /// Vector clip containing nested layers /// diff --git a/lightningbeam-ui/lightningbeam-core/src/file_io.rs b/lightningbeam-ui/lightningbeam-core/src/file_io.rs index 3e188a1..f629138 100644 --- a/lightningbeam-ui/lightningbeam-core/src/file_io.rs +++ b/lightningbeam-ui/lightningbeam-core/src/file_io.rs @@ -15,6 +15,7 @@ use std::path::{Path, PathBuf}; use zip::write::FileOptions; use zip::{CompressionMethod, ZipArchive, ZipWriter}; use flacenc::error::Verify; +use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STANDARD}; /// File format version pub const BEAM_VERSION: &str = "1.0.0"; @@ -272,7 +273,7 @@ pub fn save_beam( // Priority 3: No original file - encode PCM as FLAC eprintln!("📊 [SAVE_BEAM] Encoding PCM to FLAC for pool {} (no original file)", entry.pool_index); // Embedded data is always PCM - encode as FLAC - let audio_bytes = base64::decode(&embedded_data.data_base64) + let audio_bytes = BASE64_STANDARD.decode(&embedded_data.data_base64) .map_err(|e| format!("Failed to decode base64 audio data for pool index {}: {}", entry.pool_index, e))?; let zip_filename = format!("media/audio/{}.flac", entry.pool_index); @@ -511,13 +512,13 @@ pub fn load_beam(path: &Path) -> Result { flac_decode_time += flac_decode_start.elapsed().as_secs_f64() * 1000.0; Some(daw_backend::audio::pool::EmbeddedAudioData { - data_base64: base64::encode(&pcm_bytes), + data_base64: BASE64_STANDARD.encode(&pcm_bytes), format: "wav".to_string(), // Mark as WAV since it's now PCM }) } else { // Lossy format - store as-is Some(daw_backend::audio::pool::EmbeddedAudioData { - data_base64: base64::encode(&audio_bytes), + data_base64: BASE64_STANDARD.encode(&audio_bytes), format: format.clone(), }) }; diff --git a/lightningbeam-ui/lightningbeam-core/src/hit_test.rs b/lightningbeam-ui/lightningbeam-core/src/hit_test.rs index 3811a4b..a9d66fd 100644 --- a/lightningbeam-ui/lightningbeam-core/src/hit_test.rs +++ b/lightningbeam-ui/lightningbeam-core/src/hit_test.rs @@ -3,7 +3,7 @@ //! Provides functions for testing if points or rectangles intersect with //! shapes and objects, taking into account transform hierarchies. -use crate::clip::{ClipInstance, VectorClip, VideoClip}; +use crate::clip::ClipInstance; use crate::layer::VectorLayer; use crate::object::ShapeInstance; use crate::shape::Shape; diff --git a/lightningbeam-ui/lightningbeam-core/src/layer_tree.rs b/lightningbeam-ui/lightningbeam-core/src/layer_tree.rs index 456bd10..701171e 100644 --- a/lightningbeam-ui/lightningbeam-core/src/layer_tree.rs +++ b/lightningbeam-ui/lightningbeam-core/src/layer_tree.rs @@ -4,7 +4,6 @@ //! Layers can be nested within other layers for organizational purposes. use serde::{Deserialize, Serialize}; -use uuid::Uuid; /// Node in the layer tree #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/lightningbeam-ui/lightningbeam-core/src/renderer.rs b/lightningbeam-ui/lightningbeam-core/src/renderer.rs index dfbe33c..d3aa05a 100644 --- a/lightningbeam-ui/lightningbeam-core/src/renderer.rs +++ b/lightningbeam-ui/lightningbeam-core/src/renderer.rs @@ -6,7 +6,6 @@ use crate::animation::TransformProperty; use crate::clip::ImageAsset; use crate::document::Document; use crate::layer::{AnyLayer, LayerTrait, VectorLayer}; -use crate::object::ShapeInstance; use kurbo::{Affine, Shape}; use std::collections::HashMap; use std::sync::Arc; diff --git a/lightningbeam-ui/lightningbeam-editor/src/default_instrument.rs b/lightningbeam-ui/lightningbeam-editor/src/default_instrument.rs index 48e9caf..ed9c56d 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/default_instrument.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/default_instrument.rs @@ -3,8 +3,6 @@ /// This module provides a default instrument (bass synthesizer) for MIDI tracks /// until the user implements the node editor to load custom instruments. -use std::path::PathBuf; - /// Embedded default MIDI instrument preset (bass synthesizer) const DEFAULT_MIDI_INSTRUMENT: &str = include_str!("../../../src/assets/instruments/synthesizers/bass.json"); diff --git a/lightningbeam-ui/lightningbeam-editor/src/main.rs b/lightningbeam-ui/lightningbeam-editor/src/main.rs index a67d5c7..7f6b4af 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/main.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/main.rs @@ -156,7 +156,7 @@ enum SplitPreviewMode { /// Icon cache for pane type icons struct IconCache { - icons: HashMap, + icons: HashMap, assets_path: std::path::PathBuf, } @@ -172,15 +172,46 @@ impl IconCache { } } - fn get_or_load(&mut self, pane_type: PaneType) -> Option<&egui_extras::RetainedImage> { + fn get_or_load(&mut self, pane_type: PaneType, ctx: &egui::Context) -> Option<&egui::TextureHandle> { if !self.icons.contains_key(&pane_type) { - // Load and cache the icon + // Load SVG and rasterize using resvg let icon_path = self.assets_path.join(pane_type.icon_file()); - if let Ok(image) = egui_extras::RetainedImage::from_svg_bytes( - pane_type.icon_file(), - &std::fs::read(&icon_path).unwrap_or_default(), - ) { - self.icons.insert(pane_type, image); + if let Ok(svg_data) = std::fs::read(&icon_path) { + // Rasterize at reasonable size for pane icons + let render_size = 64; + + if let Ok(tree) = resvg::usvg::Tree::from_data(&svg_data, &resvg::usvg::Options::default()) { + let pixmap_size = tree.size().to_int_size(); + let scale_x = render_size as f32 / pixmap_size.width() as f32; + let scale_y = render_size as f32 / pixmap_size.height() as f32; + let scale = scale_x.min(scale_y); + + let final_size = resvg::usvg::Size::from_wh( + pixmap_size.width() as f32 * scale, + pixmap_size.height() as f32 * scale, + ).unwrap_or(resvg::usvg::Size::from_wh(render_size as f32, render_size as f32).unwrap()); + + if let Some(mut pixmap) = resvg::tiny_skia::Pixmap::new( + final_size.width() as u32, + final_size.height() as u32, + ) { + let transform = resvg::tiny_skia::Transform::from_scale(scale, scale); + resvg::render(&tree, transform, &mut pixmap.as_mut()); + + // Convert RGBA8 to egui ColorImage + let rgba_data = pixmap.data(); + let size = [pixmap.width() as usize, pixmap.height() as usize]; + let color_image = egui::ColorImage::from_rgba_unmultiplied(size, rgba_data); + + // Upload to GPU + let texture = ctx.load_texture( + pane_type.icon_file(), + color_image, + egui::TextureOptions::LINEAR, + ); + self.icons.insert(pane_type, texture); + } + } } } self.icons.get(&pane_type) @@ -1103,7 +1134,7 @@ impl EditorApp { let layer_name = format!("Layer {}", layer_count + 1); let action = lightningbeam_core::actions::AddLayerAction::new_vector(layer_name); - self.action_executor.execute(Box::new(action)); + let _ = self.action_executor.execute(Box::new(action)); // Select the newly created layer (last child in the document) if let Some(last_layer) = self.action_executor.document().root.children.last() { @@ -1135,7 +1166,7 @@ impl EditorApp { // Create audio layer in document let audio_layer = AudioLayer::new_sampled(layer_name.clone()); let action = lightningbeam_core::actions::AddLayerAction::new(AnyLayer::Audio(audio_layer)); - self.action_executor.execute(Box::new(action)); + let _ = self.action_executor.execute(Box::new(action)); // Get the newly created layer ID if let Some(last_layer) = self.action_executor.document().root.children.last() { @@ -1168,7 +1199,7 @@ impl EditorApp { // Create MIDI layer in document let midi_layer = AudioLayer::new_midi(layer_name.clone()); let action = lightningbeam_core::actions::AddLayerAction::new(AnyLayer::Audio(midi_layer)); - self.action_executor.execute(Box::new(action)); + let _ = self.action_executor.execute(Box::new(action)); // Get the newly created layer ID if let Some(last_layer) = self.action_executor.document().root.children.last() { @@ -1602,7 +1633,7 @@ impl EditorApp { /// Import an audio file via daw-backend fn import_audio(&mut self, path: &std::path::Path) { use daw_backend::io::audio_file::AudioFile; - use lightningbeam_core::clip::{AudioClip, AudioClipType}; + use lightningbeam_core::clip::AudioClip; let name = path.file_stem() .and_then(|s| s.to_str()) @@ -1768,7 +1799,7 @@ impl EditorApp { std::thread::spawn(move || { use lightningbeam_core::video::extract_audio_from_video; - use lightningbeam_core::clip::{AudioClip, AudioClipType}; + use lightningbeam_core::clip::AudioClip; // Extract audio from video (slow FFmpeg operation) match extract_audio_from_video(&path_clone) { @@ -2196,7 +2227,7 @@ impl eframe::App for EditorApp { } } else { // No audio system available, execute without backend - self.action_executor.execute(action); + let _ = self.action_executor.execute(action); } } @@ -2660,8 +2691,8 @@ fn render_pane( // Load and render icon if available if let Some(pane_type) = pane_type { - if let Some(icon) = ctx.icon_cache.get_or_load(pane_type) { - let icon_texture_id = icon.texture_id(ui.ctx()); + if let Some(icon) = ctx.icon_cache.get_or_load(pane_type, ui.ctx()) { + let icon_texture_id = icon.id(); let icon_rect = icon_button_rect.shrink(2.0); // Small padding inside button ui.painter().image( icon_texture_id, @@ -2698,10 +2729,10 @@ fn render_pane( for pane_type_option in PaneType::all() { // Load icon for this pane type - if let Some(icon) = ctx.icon_cache.get_or_load(*pane_type_option) { + if let Some(icon) = ctx.icon_cache.get_or_load(*pane_type_option, ui.ctx()) { ui.horizontal(|ui| { // Show icon - let icon_texture_id = icon.texture_id(ui.ctx()); + let icon_texture_id = icon.id(); let icon_size = egui::vec2(16.0, 16.0); ui.add(egui::Image::new((icon_texture_id, icon_size))); diff --git a/lightningbeam-ui/lightningbeam-editor/src/panes/asset_library.rs b/lightningbeam-ui/lightningbeam-editor/src/panes/asset_library.rs index 6a6e555..0eccac1 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/panes/asset_library.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/panes/asset_library.rs @@ -1093,7 +1093,7 @@ impl AssetLibraryPane { // Use egui's built-in ScrollArea for scrolling let scroll_area_rect = rect; - ui.allocate_ui_at_rect(scroll_area_rect, |ui| { + ui.allocate_new_ui(egui::UiBuilder::new().max_rect(scroll_area_rect), |ui| { egui::ScrollArea::vertical() .id_salt(("asset_list_scroll", path)) .auto_shrink([false, false]) @@ -1423,7 +1423,7 @@ impl AssetLibraryPane { let total_height = GRID_SPACING + rows as f32 * (item_height + GRID_SPACING); // Use egui's built-in ScrollArea for scrolling - ui.allocate_ui_at_rect(rect, |ui| { + ui.allocate_new_ui(egui::UiBuilder::new().max_rect(rect), |ui| { egui::ScrollArea::vertical() .id_salt(("asset_grid_scroll", path)) .auto_shrink([false, false]) diff --git a/lightningbeam-ui/lightningbeam-editor/src/panes/stage.rs b/lightningbeam-ui/lightningbeam-editor/src/panes/stage.rs index 6db5c7f..397d5db 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/panes/stage.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/panes/stage.rs @@ -829,7 +829,7 @@ impl egui_wgpu::CallbackTrait for VelloCallback { // 7. Draw path drawing preview if let lightningbeam_core::tool::ToolState::DrawingPath { ref points, .. } = self.tool_state { - use vello::kurbo::{BezPath, Point}; + use vello::kurbo::BezPath; if points.len() >= 2 { // Build a simple line path from the raw points for preview @@ -1175,15 +1175,15 @@ impl egui_wgpu::CallbackTrait for VelloCallback { // Copy the pixel from texture to staging buffer encoder.copy_texture_to_buffer( - wgpu::ImageCopyTexture { + wgpu::TexelCopyTextureInfo { texture, mip_level: 0, origin: wgpu::Origin3d { x: tex_x, y: tex_y, z: 0 }, aspect: wgpu::TextureAspect::All, }, - wgpu::ImageCopyBuffer { + wgpu::TexelCopyBufferInfo { buffer: &staging_buffer, - layout: wgpu::ImageDataLayout { + layout: wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(bytes_per_row), rows_per_image: Some(1), @@ -1554,7 +1554,6 @@ impl StagePane { use std::collections::HashMap; // Separate shape instances from clip instances - use lightningbeam_core::object::Transform; let mut shape_instance_positions = HashMap::new(); let mut clip_instance_transforms = HashMap::new(); @@ -1795,7 +1794,7 @@ impl StagePane { // Create and execute action immediately let action = AddShapeAction::new(active_layer_id, shape, object); - shared.action_executor.execute(Box::new(action)); + let _ = shared.action_executor.execute(Box::new(action)); // Clear tool state to stop preview rendering *shared.tool_state = ToolState::Idle; @@ -1923,7 +1922,7 @@ impl StagePane { // Create and execute action immediately let action = AddShapeAction::new(active_layer_id, shape, object); - shared.action_executor.execute(Box::new(action)); + let _ = shared.action_executor.execute(Box::new(action)); // Clear tool state to stop preview rendering *shared.tool_state = ToolState::Idle; @@ -2015,7 +2014,7 @@ impl StagePane { // Create and execute action immediately let action = AddShapeAction::new(active_layer_id, shape, object); - shared.action_executor.execute(Box::new(action)); + let _ = shared.action_executor.execute(Box::new(action)); // Clear tool state to stop preview rendering *shared.tool_state = ToolState::Idle; @@ -2106,7 +2105,7 @@ impl StagePane { // Create and execute action immediately let action = AddShapeAction::new(active_layer_id, shape, object); - shared.action_executor.execute(Box::new(action)); + let _ = shared.action_executor.execute(Box::new(action)); // Clear tool state to stop preview rendering *shared.tool_state = ToolState::Idle; @@ -2390,7 +2389,7 @@ impl StagePane { // Create and execute action immediately let action = AddShapeAction::new(active_layer_id, shape, object); - shared.action_executor.execute(Box::new(action)); + let _ = shared.action_executor.execute(Box::new(action)); } } @@ -2449,7 +2448,7 @@ impl StagePane { 2.0, // tolerance - could be made configurable lightningbeam_core::gap_handling::GapHandlingMode::BridgeSegment, ); - shared.action_executor.execute(Box::new(action)); + let _ = shared.action_executor.execute(Box::new(action)); println!("Paint bucket action executed"); } } @@ -3937,7 +3936,6 @@ impl StagePane { if delta.x.abs() > 0.01 || delta.y.abs() > 0.01 { if let Some(active_layer_id) = shared.active_layer_id { use std::collections::HashMap; - use lightningbeam_core::object::Transform; let mut shape_instance_positions = HashMap::new(); let mut clip_instance_transforms = HashMap::new(); @@ -4273,7 +4271,7 @@ impl PaneRenderer for StagePane { let mut add_layer_action = lightningbeam_core::actions::AddLayerAction::new(new_layer); // Execute immediately to get the layer ID - add_layer_action.execute(shared.action_executor.document_mut()); + let _ = add_layer_action.execute(shared.action_executor.document_mut()); target_layer_id = add_layer_action.created_layer_id(); // Update active layer to the new layer diff --git a/lightningbeam-ui/lightningbeam-editor/src/panes/timeline.rs b/lightningbeam-ui/lightningbeam-editor/src/panes/timeline.rs index 49a218b..06fbf75 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/panes/timeline.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/panes/timeline.rs @@ -165,7 +165,7 @@ impl TimelinePane { let layers: Vec<_> = document.root.children.iter().rev().collect(); let layer = layers.get(hovered_layer_index)?; - let layer_data = layer.layer(); + let _layer_data = layer.layer(); let clip_instances = match layer { lightningbeam_core::layer::AnyLayer::Vector(vl) => &vl.clip_instances, @@ -848,7 +848,7 @@ impl TimelinePane { // Mute button // TODO: Replace with SVG icon (volume-up-fill.svg / volume-mute.svg) - let mute_response = ui.allocate_ui_at_rect(mute_button_rect, |ui| { + let mute_response = ui.allocate_new_ui(egui::UiBuilder::new().max_rect(mute_button_rect), |ui| { let mute_text = if is_muted { "🔇" } else { "🔊" }; let button = egui::Button::new(mute_text) .fill(if is_muted { @@ -872,7 +872,7 @@ impl TimelinePane { // Solo button // TODO: Replace with SVG headphones icon - let solo_response = ui.allocate_ui_at_rect(solo_button_rect, |ui| { + let solo_response = ui.allocate_new_ui(egui::UiBuilder::new().max_rect(solo_button_rect), |ui| { let button = egui::Button::new("🎧") .fill(if is_soloed { egui::Color32::from_rgba_unmultiplied(100, 200, 100, 100) @@ -895,7 +895,7 @@ impl TimelinePane { // Lock button // TODO: Replace with SVG lock/lock-open icons - let lock_response = ui.allocate_ui_at_rect(lock_button_rect, |ui| { + let lock_response = ui.allocate_new_ui(egui::UiBuilder::new().max_rect(lock_button_rect), |ui| { let lock_text = if is_locked { "🔒" } else { "🔓" }; let button = egui::Button::new(lock_text) .fill(if is_locked { @@ -918,7 +918,7 @@ impl TimelinePane { } // Volume slider (nonlinear: 0-70% slider = 0-100% volume, 70-100% slider = 100-200% volume) - let volume_response = ui.allocate_ui_at_rect(volume_slider_rect, |ui| { + let volume_response = ui.allocate_new_ui(egui::UiBuilder::new().max_rect(volume_slider_rect), |ui| { // Map volume (0.0-2.0) to slider position (0.0-1.0) let slider_value = if current_volume <= 1.0 { // 0.0-1.0 volume maps to 0.0-0.7 slider (70%) @@ -1086,7 +1086,7 @@ impl TimelinePane { // Instance positioned on the layer's timeline using timeline_start // The layer itself has start_time, so the absolute timeline position is: // layer.start_time + instance.timeline_start - let layer_data = layer.layer(); + let _layer_data = layer.layer(); let mut instance_start = clip_instance.timeline_start; // Apply drag offset preview for selected clips with snapping @@ -1428,7 +1428,7 @@ impl TimelinePane { if clicked_layer_index < layer_count { let layers: Vec<_> = document.root.children.iter().rev().collect(); if let Some(layer) = layers.get(clicked_layer_index) { - let layer_data = layer.layer(); + let _layer_data = layer.layer(); // Get clip instances for this layer let clip_instances = match layer { @@ -1605,7 +1605,7 @@ impl TimelinePane { // Iterate through all layers to find selected clip instances for layer in &document.root.children { let layer_id = layer.id(); - let layer_data = layer.layer(); + let _layer_data = layer.layer(); let clip_instances = match layer { lightningbeam_core::layer::AnyLayer::Vector(vl) => {