improve folders a bit

This commit is contained in:
Skyler Lehmkuhl 2026-01-13 20:30:16 -05:00
parent b19f66e648
commit f4ffa7ecdd
5 changed files with 362 additions and 25 deletions

View File

@ -621,6 +621,8 @@ struct EditorApp {
debug_overlay_visible: bool,
debug_stats_collector: debug_overlay::DebugStatsCollector,
gpu_info: Option<wgpu::AdapterInfo>,
/// Surface texture format for GPU rendering (Rgba8Unorm or Bgra8Unorm depending on platform)
target_format: wgpu::TextureFormat,
}
/// Import filter types for the file dialog
@ -711,6 +713,11 @@ impl EditorApp {
// Extract GPU info for debug overlay
let gpu_info = cc.wgpu_render_state.as_ref().map(|rs| rs.adapter.get_info());
// Get surface format (defaults to Rgba8Unorm if render_state not available)
let target_format = cc.wgpu_render_state.as_ref()
.map(|rs| rs.target_format)
.unwrap_or(wgpu::TextureFormat::Rgba8Unorm);
Self {
layouts,
current_layout_index: 0,
@ -780,6 +787,7 @@ impl EditorApp {
debug_overlay_visible: false,
debug_stats_collector: debug_overlay::DebugStatsCollector::new(),
gpu_info,
target_format,
}
}
@ -3182,6 +3190,7 @@ impl eframe::App for EditorApp {
.map(|g| g.thumbnail_cache())
.unwrap_or(&empty_thumbnail_cache),
effect_thumbnails_to_invalidate: &mut self.effect_thumbnails_to_invalidate,
target_format: self.target_format,
};
render_layout_node(
@ -3413,6 +3422,8 @@ struct RenderContext<'a> {
effect_thumbnail_cache: &'a HashMap<Uuid, Vec<u8>>,
/// Effect IDs whose thumbnails should be invalidated
effect_thumbnails_to_invalidate: &'a mut Vec<Uuid>,
/// Surface texture format for GPU rendering (Rgba8Unorm or Bgra8Unorm depending on platform)
target_format: wgpu::TextureFormat,
}
/// Recursively render a layout node with drag support
@ -3887,6 +3898,7 @@ fn render_pane(
effect_thumbnail_requests: ctx.effect_thumbnail_requests,
effect_thumbnail_cache: ctx.effect_thumbnail_cache,
effect_thumbnails_to_invalidate: ctx.effect_thumbnails_to_invalidate,
target_format: ctx.target_format,
};
pane_instance.render_header(&mut header_ui, &mut shared);
}
@ -3950,6 +3962,7 @@ fn render_pane(
effect_thumbnail_requests: ctx.effect_thumbnail_requests,
effect_thumbnail_cache: ctx.effect_thumbnail_cache,
effect_thumbnails_to_invalidate: ctx.effect_thumbnails_to_invalidate,
target_format: ctx.target_format,
};
// Render pane content (header was already rendered above)

View File

@ -661,6 +661,8 @@ pub struct AssetEntry {
pub extra_info: String,
/// True for built-in effects from the registry (not editable/deletable)
pub is_builtin: bool,
/// Folder this asset belongs to (None = root)
pub folder_id: Option<Uuid>,
}
/// Folder entry for display
@ -712,6 +714,14 @@ struct RenameState {
edit_text: String,
}
/// Inline folder rename editing state
#[derive(Debug, Clone)]
struct FolderRenameState {
folder_id: Uuid,
category: AssetCategory,
edit_text: String,
}
/// Context menu state with position
#[derive(Debug, Clone)]
struct ContextMenuState {
@ -719,6 +729,12 @@ struct ContextMenuState {
position: egui::Pos2,
}
#[derive(Debug, Clone)]
struct FolderContextMenuState {
folder_id: Uuid,
position: egui::Pos2,
}
pub struct AssetLibraryPane {
/// Current search filter text
search_filter: String,
@ -732,15 +748,21 @@ pub struct AssetLibraryPane {
/// Context menu state with position (for assets)
context_menu: Option<ContextMenuState>,
/// Folder context menu state (for folders)
folder_context_menu: Option<FolderContextMenuState>,
/// Pane context menu position (for background right-click)
pane_context_menu: Option<egui::Pos2>,
/// Pending delete confirmation
pending_delete: Option<PendingDelete>,
/// Active rename state
/// Active rename state (for assets)
rename_state: Option<RenameState>,
/// Active folder rename state
folder_rename_state: Option<FolderRenameState>,
/// Current view mode (list or grid)
view_mode: AssetViewMode,
@ -768,9 +790,11 @@ impl AssetLibraryPane {
selected_category: AssetCategory::All,
selected_asset: None,
context_menu: None,
folder_context_menu: None,
pane_context_menu: None,
pending_delete: None,
rename_state: None,
folder_rename_state: None,
view_mode: AssetViewMode::default(),
thumbnail_cache: ThumbnailCache::new(),
current_folders: HashMap::new(),
@ -853,6 +877,28 @@ impl AssetLibraryPane {
}
}
/// Convert DragClipType to core AssetCategory
fn drag_clip_type_to_core_category(clip_type: DragClipType) -> lightningbeam_core::document::AssetCategory {
match clip_type {
DragClipType::Vector => lightningbeam_core::document::AssetCategory::Vector,
DragClipType::Video => lightningbeam_core::document::AssetCategory::Video,
DragClipType::AudioSampled | DragClipType::AudioMidi => lightningbeam_core::document::AssetCategory::Audio,
DragClipType::Image => lightningbeam_core::document::AssetCategory::Images,
DragClipType::Effect => lightningbeam_core::document::AssetCategory::Effects,
}
}
/// Convert DragClipType to UI AssetCategory
fn drag_clip_type_to_category(clip_type: DragClipType) -> AssetCategory {
match clip_type {
DragClipType::Vector => AssetCategory::Vector,
DragClipType::Video => AssetCategory::Video,
DragClipType::AudioSampled | DragClipType::AudioMidi => AssetCategory::Audio,
DragClipType::Image => AssetCategory::Images,
DragClipType::Effect => AssetCategory::Effects,
}
}
/// Collect all assets from the document into a unified list
fn collect_assets(&self, document: &Document) -> Vec<AssetEntry> {
let mut assets = Vec::new();
@ -868,6 +914,7 @@ impl AssetLibraryPane {
dimensions: Some((clip.width, clip.height)),
extra_info: format!("{}x{}", clip.width as u32, clip.height as u32),
is_builtin: false,
folder_id: clip.folder_id,
});
}
@ -882,6 +929,7 @@ impl AssetLibraryPane {
dimensions: Some((clip.width, clip.height)),
extra_info: format!("{:.0}fps", clip.frame_rate),
is_builtin: false,
folder_id: clip.folder_id,
});
}
@ -911,6 +959,7 @@ impl AssetLibraryPane {
dimensions: None,
extra_info,
is_builtin: false,
folder_id: clip.folder_id,
});
}
@ -925,6 +974,7 @@ impl AssetLibraryPane {
dimensions: Some((asset.width as f64, asset.height as f64)),
extra_info: format!("{}x{}", asset.width, asset.height),
is_builtin: false,
folder_id: asset.folder_id,
});
}
@ -939,6 +989,7 @@ impl AssetLibraryPane {
dimensions: None,
extra_info: format!("{:?}", effect_def.category),
is_builtin: true, // Built-in from registry
folder_id: None, // Built-in effects are at root
});
}
@ -960,6 +1011,7 @@ impl AssetLibraryPane {
dimensions: None,
extra_info: format!("{:?}", effect_def.category),
is_builtin: false, // User effect
folder_id: effect_def.folder_id,
});
}
}
@ -1057,6 +1109,7 @@ impl AssetLibraryPane {
dimensions: Some((clip.width, clip.height)),
extra_info: format!("{}x{}", clip.width as u32, clip.height as u32),
is_builtin: false,
folder_id: clip.folder_id,
}));
}
}
@ -1073,6 +1126,7 @@ impl AssetLibraryPane {
dimensions: Some((clip.width, clip.height)),
extra_info: format!("{:.0}fps", clip.frame_rate),
is_builtin: false,
folder_id: clip.folder_id,
}));
}
}
@ -1105,6 +1159,7 @@ impl AssetLibraryPane {
dimensions: None,
extra_info,
is_builtin: false,
folder_id: clip.folder_id,
}));
}
}
@ -1121,6 +1176,7 @@ impl AssetLibraryPane {
dimensions: Some((asset.width as f64, asset.height as f64)),
extra_info: format!("{}x{}", asset.width, asset.height),
is_builtin: false,
folder_id: asset.folder_id,
}));
}
}
@ -1138,6 +1194,7 @@ impl AssetLibraryPane {
dimensions: None,
extra_info: format!("{:?}", effect_def.category),
is_builtin: true,
folder_id: None, // Built-in effects are always at root
}));
}
}
@ -1154,6 +1211,7 @@ impl AssetLibraryPane {
dimensions: None,
extra_info: format!("{:?}", effect.category),
is_builtin: false,
folder_id: effect.folder_id,
}));
}
}
@ -1922,14 +1980,35 @@ impl AssetLibraryPane {
if viewport.intersects(item_rect) {
let response = ui.allocate_rect(item_rect, egui::Sense::click());
// Check if an asset is being dragged and matches this folder's category
let is_valid_drop_target = shared.dragging_asset.as_ref().map(|drag| {
let drag_category = Self::drag_clip_type_to_category(drag.clip_type);
drag_category == self.selected_category
}).unwrap_or(false);
let is_drop_hover = is_valid_drop_target && response.hovered();
// Background
let bg_color = if response.hovered() {
let bg_color = if is_drop_hover {
// Highlight as drop target
egui::Color32::from_rgb(60, 100, 140)
} else if response.hovered() {
egui::Color32::from_rgb(50, 50, 50)
} else {
egui::Color32::from_rgb(35, 35, 35)
};
ui.painter().rect_filled(item_rect, 0.0, bg_color);
// Draw drop target indicator border
if is_drop_hover {
ui.painter().rect_stroke(
item_rect,
0.0,
egui::Stroke::new(2.0, egui::Color32::from_rgb(100, 180, 255)),
egui::StrokeKind::Middle,
);
}
// Folder icon
if let Some(ref icon) = folder_icon {
let icon_size = LIST_THUMBNAIL_SIZE;
@ -1945,14 +2024,33 @@ impl AssetLibraryPane {
);
}
// Folder name
ui.painter().text(
item_rect.min + egui::vec2(LIST_THUMBNAIL_SIZE + 12.0, ITEM_HEIGHT / 2.0),
egui::Align2::LEFT_CENTER,
&folder.name,
egui::FontId::proportional(13.0),
egui::Color32::WHITE,
);
// Folder name (or inline edit field)
let is_renaming = self.folder_rename_state.as_ref().map(|s| s.folder_id == folder.id).unwrap_or(false);
if is_renaming {
// Inline rename text field
let name_rect = egui::Rect::from_min_size(
item_rect.min + egui::vec2(LIST_THUMBNAIL_SIZE + 8.0, (ITEM_HEIGHT - 22.0) / 2.0),
egui::vec2(200.0, 22.0),
);
if let Some(ref mut state) = self.folder_rename_state {
let mut child_ui = ui.new_child(egui::UiBuilder::new().max_rect(name_rect));
ImeTextField::new(&mut state.edit_text)
.font_size(13.0)
.desired_width(name_rect.width())
.request_focus()
.show(&mut child_ui);
}
} else {
ui.painter().text(
item_rect.min + egui::vec2(LIST_THUMBNAIL_SIZE + 12.0, ITEM_HEIGHT / 2.0),
egui::Align2::LEFT_CENTER,
&folder.name,
egui::FontId::proportional(13.0),
egui::Color32::WHITE,
);
}
// Item count
let count_text = format!("{} items", folder.item_count);
@ -1964,10 +2062,32 @@ impl AssetLibraryPane {
egui::Color32::from_rgb(150, 150, 150),
);
// Handle drop: move asset to folder
if is_drop_hover && ui.input(|i| i.pointer.any_released()) {
if let Some(ref drag) = shared.dragging_asset.clone() {
let core_category = Self::drag_clip_type_to_core_category(drag.clip_type);
let action = lightningbeam_core::actions::MoveAssetToFolderAction::new(
core_category,
drag.clip_id,
Some(folder.id),
);
let _ = shared.action_executor.execute(Box::new(action));
*shared.dragging_asset = None;
}
}
// Handle double-click to navigate into folder
if response.double_clicked() {
self.set_current_folder(Some(folder.id));
}
// Handle right-click for context menu
if response.secondary_clicked() {
self.folder_context_menu = Some(FolderContextMenuState {
folder_id: folder.id,
position: ui.ctx().pointer_interact_pos().unwrap_or(egui::pos2(0.0, 0.0)),
});
}
} else {
ui.allocate_space(egui::vec2(rect.width(), ITEM_HEIGHT));
}
@ -2031,14 +2151,35 @@ impl AssetLibraryPane {
egui::Sense::click(),
);
// Check if an asset is being dragged and matches this folder's category
let is_valid_drop_target = shared.dragging_asset.as_ref().map(|drag| {
let drag_category = Self::drag_clip_type_to_category(drag.clip_type);
drag_category == self.selected_category
}).unwrap_or(false);
let is_drop_hover = is_valid_drop_target && response.hovered();
// Background
let bg_color = if response.hovered() {
let bg_color = if is_drop_hover {
// Highlight as drop target
egui::Color32::from_rgb(60, 100, 140)
} else if response.hovered() {
egui::Color32::from_rgb(50, 50, 50)
} else {
egui::Color32::from_rgb(35, 35, 35)
};
ui.painter().rect_filled(rect, 4.0, bg_color);
// Draw drop target indicator border
if is_drop_hover {
ui.painter().rect_stroke(
rect,
4.0,
egui::Stroke::new(2.0, egui::Color32::from_rgb(100, 180, 255)),
egui::StrokeKind::Middle,
);
}
// Folder icon (centered)
if let Some(ref icon) = folder_icon {
let icon_size = 48.0;
@ -2077,10 +2218,32 @@ impl AssetLibraryPane {
egui::Color32::from_rgb(150, 150, 150),
);
// Handle drop: move asset to folder
if is_drop_hover && ui.input(|i| i.pointer.any_released()) {
if let Some(ref drag) = shared.dragging_asset.clone() {
let core_category = Self::drag_clip_type_to_core_category(drag.clip_type);
let action = lightningbeam_core::actions::MoveAssetToFolderAction::new(
core_category,
drag.clip_id,
Some(folder.id),
);
let _ = shared.action_executor.execute(Box::new(action));
*shared.dragging_asset = None;
}
}
// Handle double-click to navigate into folder
if response.double_clicked() {
self.set_current_folder(Some(folder.id));
}
// Handle right-click for context menu
if response.secondary_clicked() {
self.folder_context_menu = Some(FolderContextMenuState {
folder_id: folder.id,
position: ui.ctx().pointer_interact_pos().unwrap_or(egui::pos2(0.0, 0.0)),
});
}
}
LibraryItem::Asset(asset) => {
// Allocate rect for asset grid item (with space for name below)
@ -3118,11 +3281,14 @@ impl PaneRenderer for AssetLibraryPane {
// Detect right-click on pane background (not on items)
// Only allow folder creation in categories with folder support (not "All")
// Don't trigger if we already opened a folder or asset context menu
if self.selected_category != AssetCategory::All {
if ui.input(|i| i.pointer.secondary_clicked()) {
if let Some(pos) = ui.ctx().pointer_interact_pos() {
if list_rect.contains(pos) {
self.pane_context_menu = Some(pos);
if self.folder_context_menu.is_none() && self.context_menu.is_none() {
if let Some(pos) = ui.ctx().pointer_interact_pos() {
if list_rect.contains(pos) {
self.pane_context_menu = Some(pos);
}
}
}
}
@ -3145,12 +3311,23 @@ impl PaneRenderer for AssetLibraryPane {
let asset_name = asset.name.clone();
let asset_category = asset.category;
let asset_is_builtin = asset.is_builtin;
let asset_folder_id = asset.folder_id;
let in_use = Self::is_asset_in_use(
shared.action_executor.document(),
context_asset_id,
asset_category,
);
// Get folders for this category (for Move to Folder submenu)
let folders: Vec<(Uuid, String)> = if let Some(core_cat) = Self::to_core_category(asset_category) {
let tree = document_arc.get_folder_tree(core_cat);
tree.folders.iter()
.map(|(id, f)| (*id, f.name.clone()))
.collect()
} else {
Vec::new()
};
// Show context menu popup at the stored position
let menu_id = egui::Id::new("asset_context_menu");
let menu_response = egui::Area::new(menu_id)
@ -3195,6 +3372,47 @@ impl PaneRenderer for AssetLibraryPane {
});
self.context_menu = None;
}
// Move to Folder submenu (only show if there are folders or asset is not at root)
if !folders.is_empty() || asset_folder_id.is_some() {
ui.separator();
ui.menu_button("Move to Folder", |ui| {
// Move to Root option (if not already at root)
if asset_folder_id.is_some() {
if ui.button("Root").clicked() {
if let Some(core_cat) = Self::to_core_category(asset_category) {
let action = lightningbeam_core::actions::MoveAssetToFolderAction::new(
core_cat,
context_asset_id,
None,
);
let _ = shared.action_executor.execute(Box::new(action));
}
self.context_menu = None;
}
if !folders.is_empty() {
ui.separator();
}
}
// List all folders (except current folder)
for (folder_id, folder_name) in &folders {
if asset_folder_id != Some(*folder_id) {
if ui.button(folder_name).clicked() {
if let Some(core_cat) = Self::to_core_category(asset_category) {
let action = lightningbeam_core::actions::MoveAssetToFolderAction::new(
core_cat,
context_asset_id,
Some(*folder_id),
);
let _ = shared.action_executor.execute(Box::new(action));
}
self.context_menu = None;
}
}
}
});
}
}
});
});
@ -3251,9 +3469,14 @@ impl PaneRenderer for AssetLibraryPane {
})
});
// Close menu if clicked outside
if menu_response.response.clicked_elsewhere() {
self.pane_context_menu = None;
// Close menu on click outside (using primary button release to avoid first-frame issue)
let menu_rect = menu_response.response.rect;
if ui.input(|i| i.pointer.primary_released()) {
if let Some(pos) = ui.ctx().pointer_interact_pos() {
if !menu_rect.contains(pos) {
self.pane_context_menu = None;
}
}
}
// Also close on Escape
@ -3262,6 +3485,72 @@ impl PaneRenderer for AssetLibraryPane {
}
}
// Folder context menu (for rename/delete/etc)
if let Some(ref folder_state) = self.folder_context_menu.clone() {
let folder_id = folder_state.folder_id;
let menu_pos = folder_state.position;
// Get the folder from the document
if let Some(core_category) = Self::to_core_category(self.selected_category) {
let folder_tree = document_arc.get_folder_tree(core_category);
if let Some(folder) = folder_tree.folders.get(&folder_id) {
let folder_name = folder.name.clone();
let menu_id = egui::Id::new("folder_context_menu");
let menu_response = egui::Area::new(menu_id)
.order(egui::Order::Foreground)
.fixed_pos(menu_pos)
.show(ui.ctx(), |ui| {
egui::Frame::popup(ui.style()).show(ui, |ui| {
ui.set_min_width(150.0);
if ui.button("Rename").clicked() {
// Enter rename mode for folder
self.folder_rename_state = Some(FolderRenameState {
folder_id,
category: self.selected_category,
edit_text: folder_name.clone(),
});
self.folder_context_menu = None;
}
if ui.button("Delete").clicked() {
// Execute delete folder action
let action = lightningbeam_core::actions::DeleteFolderAction::new(
core_category,
folder_id,
lightningbeam_core::actions::DeleteStrategy::MoveToParent,
);
let _ = shared.action_executor.execute(Box::new(action));
self.folder_context_menu = None;
}
})
});
// Close menu on click outside (using primary button release to avoid first-frame issue)
let menu_rect = menu_response.response.rect;
if ui.input(|i| i.pointer.primary_released()) {
if let Some(pos) = ui.ctx().pointer_interact_pos() {
if !menu_rect.contains(pos) {
self.folder_context_menu = None;
}
}
}
// Also close on Escape
if ui.input(|i| i.key_pressed(egui::Key::Escape)) {
self.folder_context_menu = None;
}
} else {
self.folder_context_menu = None;
}
} else {
self.folder_context_menu = None;
}
}
// Delete confirmation dialog
if let Some(ref pending) = self.pending_delete.clone() {
let window_id = egui::Id::new("delete_confirm_dialog");
@ -3345,6 +3634,39 @@ impl PaneRenderer for AssetLibraryPane {
self.rename_state = None;
}
}
// Handle folder rename state (Enter to confirm, Escape to cancel)
if let Some(ref state) = self.folder_rename_state.clone() {
let mut should_confirm = false;
let mut should_cancel = false;
// Check for Enter or Escape
ui.input(|i| {
if i.key_pressed(egui::Key::Enter) {
should_confirm = true;
} else if i.key_pressed(egui::Key::Escape) {
should_cancel = true;
}
});
if should_confirm {
let new_name = state.edit_text.trim();
if !new_name.is_empty() {
// Execute rename folder action
if let Some(core_category) = Self::to_core_category(state.category) {
let action = lightningbeam_core::actions::RenameFolderAction::new(
core_category,
state.folder_id,
new_name.to_string(),
);
let _ = shared.action_executor.execute(Box::new(action));
}
}
self.folder_rename_state = None;
} else if should_cancel {
self.folder_rename_state = None;
}
}
}
fn name(&self) -> &str {

View File

@ -201,6 +201,8 @@ pub struct SharedPaneState<'a> {
pub effect_thumbnail_cache: &'a std::collections::HashMap<Uuid, Vec<u8>>,
/// Effect IDs whose thumbnails should be invalidated (e.g., after shader edit)
pub effect_thumbnails_to_invalidate: &'a mut Vec<Uuid>,
/// Surface texture format for GPU rendering (Rgba8Unorm or Bgra8Unorm depending on platform)
pub target_format: wgpu::TextureFormat,
}
/// Trait for pane rendering

View File

@ -629,9 +629,6 @@ impl crate::panes::PaneRenderer for NodeGraphPane {
// Allocate the rect and render the graph editor within it
ui.allocate_ui_at_rect(rect, |ui| {
// Disable debug warning for unaligned widgets (happens when zoomed)
ui.style_mut().debug.show_unaligned = false;
// Check for scroll input to override library's default zoom behavior
let modifiers = ui.input(|i| i.modifiers);
let has_ctrl = modifiers.ctrl || modifiers.command;

View File

@ -59,7 +59,7 @@ pub struct VelloResourcesMap {
}
impl SharedVelloResources {
pub fn new(device: &wgpu::Device, video_manager: std::sync::Arc<std::sync::Mutex<lightningbeam_core::video::VideoManager>>) -> Result<Self, String> {
pub fn new(device: &wgpu::Device, video_manager: std::sync::Arc<std::sync::Mutex<lightningbeam_core::video::VideoManager>>, target_format: wgpu::TextureFormat) -> Result<Self, String> {
let renderer = vello::Renderer::new(
device,
vello::RendererOptions {
@ -120,7 +120,7 @@ impl SharedVelloResources {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba8Unorm, // egui's target format
format: target_format, // Use egui's actual target format
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
@ -161,7 +161,7 @@ impl SharedVelloResources {
module: &hdr_shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba8Unorm, // Output to display-ready texture
format: wgpu::TextureFormat::Rgba8Unorm, // Intermediate texture format (not swapchain)
blend: None, // No blending - direct replacement
write_mask: wgpu::ColorWrites::ALL,
})],
@ -359,6 +359,7 @@ struct VelloCallback {
playback_time: f64, // Current playback time for animation evaluation
video_manager: std::sync::Arc<std::sync::Mutex<lightningbeam_core::video::VideoManager>>,
shape_editing_cache: Option<ShapeEditingCache>, // Cache for vector editing preview
target_format: wgpu::TextureFormat, // Surface format for blit pipelines
}
impl VelloCallback {
@ -380,8 +381,9 @@ impl VelloCallback {
playback_time: f64,
video_manager: std::sync::Arc<std::sync::Mutex<lightningbeam_core::video::VideoManager>>,
shape_editing_cache: Option<ShapeEditingCache>,
target_format: wgpu::TextureFormat,
) -> Self {
Self { rect, pan_offset, zoom, instance_id, document, tool_state, active_layer_id, drag_delta, selection, fill_color, stroke_color, stroke_width, selected_tool, eyedropper_request, playback_time, video_manager, shape_editing_cache }
Self { rect, pan_offset, zoom, instance_id, document, tool_state, active_layer_id, drag_delta, selection, fill_color, stroke_color, stroke_width, selected_tool, eyedropper_request, playback_time, video_manager, shape_editing_cache, target_format }
}
}
@ -407,7 +409,7 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
// Initialize shared resources if not yet created (only happens once for first Stage pane)
if map.shared.is_none() {
map.shared = Some(Arc::new(
SharedVelloResources::new(device, self.video_manager.clone()).expect("Failed to initialize shared Vello resources")
SharedVelloResources::new(device, self.video_manager.clone(), self.target_format).expect("Failed to initialize shared Vello resources")
));
}
@ -6345,6 +6347,7 @@ impl PaneRenderer for StagePane {
*shared.playback_time,
shared.video_manager.clone(),
self.shape_editing_cache.clone(),
shared.target_format,
);
let cb = egui_wgpu::Callback::new_paint_callback(