From aad2d5c515d1d990999dff4b6e4b135b612a15b1 Mon Sep 17 00:00:00 2001 From: Skyler Lehmkuhl Date: Sun, 21 Jun 2026 00:37:19 -0400 Subject: [PATCH] Onion/image: make Image a fill-type tab (None | Solid | Gradient | Image) Image fill is now a tab in the Fill type row rather than a separate dropdown. When Image is active, an asset-picker combo selects which image; switching to None/Solid/ Gradient clears the image fill (it otherwise overrides them). The Image tab only appears when there are imported image assets. --- .../src/panes/infopanel.rs | 79 +++++++++++-------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/lightningbeam-ui/lightningbeam-editor/src/panes/infopanel.rs b/lightningbeam-ui/lightningbeam-editor/src/panes/infopanel.rs index b916206..ba4b56a 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/panes/infopanel.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/panes/infopanel.rs @@ -812,47 +812,67 @@ impl InfopanelPane { self.shape_section_open = true; ui.add_space(4.0); - // Fill — determine current fill type + // Fill — determine current fill type. `image_fill` takes render priority, + // so when set it's the active type (overriding colour/gradient underneath); + // switching to None/Solid/Gradient clears it. + let has_image = current_image_fill.is_some(); let has_gradient = matches!(&info.fill_gradient, Some(Some(_))); let has_solid = matches!(&info.fill_color, Some(Some(_))); let fill_is_none = matches!(&info.fill_color, Some(None)) && matches!(&info.fill_gradient, Some(None)); let fill_mixed = info.fill_color.is_none() && info.fill_gradient.is_none(); - // Fill type toggle row + // Fill type toggle row: None | Solid | Gradient | Image ui.horizontal(|ui| { ui.label("Fill:"); if fill_mixed { ui.label("--"); } else { - if ui.selectable_label(fill_is_none, "None").clicked() && !fill_is_none { - let action = SetFillPaintAction::solid( - layer_id, time, face_ids.clone(), None, - ); - shared.pending_actions.push(Box::new(action)); + if ui.selectable_label(fill_is_none && !has_image, "None").clicked() { + if has_image { + shared.pending_actions.push(Box::new( + SetImageFillAction::new(layer_id, time, face_ids.clone(), None))); + } + shared.pending_actions.push(Box::new( + SetFillPaintAction::solid(layer_id, time, face_ids.clone(), None))); } - if ui.selectable_label(has_solid || (!has_gradient && !fill_is_none), "Solid").clicked() && !has_solid { - // Switch to solid: use existing color or default to black - let color = info.fill_color.flatten() - .unwrap_or(ShapeColor::rgba(0, 0, 0, 255)); - let action = SetFillPaintAction::solid( - layer_id, time, face_ids.clone(), Some(color), - ); - shared.pending_actions.push(Box::new(action)); + if ui.selectable_label(has_solid && !has_image, "Solid").clicked() { + if has_image { + shared.pending_actions.push(Box::new( + SetImageFillAction::new(layer_id, time, face_ids.clone(), None))); + } + if !has_solid { + let color = info.fill_color.flatten() + .unwrap_or(ShapeColor::rgba(0, 0, 0, 255)); + shared.pending_actions.push(Box::new( + SetFillPaintAction::solid(layer_id, time, face_ids.clone(), Some(color)))); + } } - if ui.selectable_label(has_gradient, "Gradient").clicked() && !has_gradient { - let grad = info.fill_gradient.clone().flatten() - .unwrap_or_default(); - let action = SetFillPaintAction::gradient( - layer_id, time, face_ids.clone(), Some(grad), - ); - shared.pending_actions.push(Box::new(action)); + if ui.selectable_label(has_gradient && !has_image, "Gradient").clicked() { + if has_image { + shared.pending_actions.push(Box::new( + SetImageFillAction::new(layer_id, time, face_ids.clone(), None))); + } + if !has_gradient { + let grad = info.fill_gradient.clone().flatten().unwrap_or_default(); + shared.pending_actions.push(Box::new( + SetFillPaintAction::gradient(layer_id, time, face_ids.clone(), Some(grad)))); + } + } + // Image tab — only offered if there are imported image assets. + if !image_assets.is_empty() || has_image { + if ui.selectable_label(has_image, "Image").clicked() && !has_image { + if let Some((aid, _)) = image_assets.first() { + shared.pending_actions.push(Box::new( + SetImageFillAction::new(layer_id, time, face_ids.clone(), Some(*aid)))); + } + } } } }); // Solid fill color editor - if !fill_mixed && has_solid { + if !fill_mixed && has_solid && !has_image { if let Some(Some(color)) = info.fill_color { ui.horizontal(|ui| { let mut rgba = [color.r, color.g, color.b, color.a]; @@ -868,7 +888,7 @@ impl InfopanelPane { } // Gradient fill editor - if !fill_mixed && has_gradient { + if !fill_mixed && has_gradient && !has_image { if let Some(Some(mut grad)) = info.fill_gradient.clone() { if gradient_stop_editor(ui, &mut grad, &mut self.selected_shape_gradient_stop) { let action = SetFillPaintAction::gradient( @@ -879,22 +899,17 @@ impl InfopanelPane { } } - // Image fill picker (assign/clear an imported image asset on the fill). - if !face_ids.is_empty() { + // Image fill editor — pick which asset (active Image type). + if has_image { ui.horizontal(|ui| { ui.label("Image:"); let selected_text = current_image_fill .and_then(|id| image_assets.iter().find(|(aid, _)| *aid == id)) .map(|(_, n)| n.clone()) - .unwrap_or_else(|| "None".to_string()); + .unwrap_or_else(|| "(missing)".to_string()); egui::ComboBox::from_id_salt(("image_fill", path)) .selected_text(selected_text) .show_ui(ui, |ui| { - if ui.selectable_label(current_image_fill.is_none(), "None").clicked() { - shared.pending_actions.push(Box::new( - SetImageFillAction::new(layer_id, time, face_ids.clone(), None), - )); - } for (aid, name) in &image_assets { if ui.selectable_label(current_image_fill == Some(*aid), name).clicked() { shared.pending_actions.push(Box::new(