Phase 3.5a: SetImageFillAction + Info-Panel image-fill picker
- SetImageFillAction (core): set/clear `image_fill` on the selected VectorGraph fills, with per-fill undo (mirrors SetFillPaintAction). Image takes render priority; clearing reveals the colour/gradient underneath. - Info Panel Shape section: an "Image:" combo listing the document's image assets (+ None) for the selected fill(s), showing the current assignment. Assign/clear pushes the action. This lets an existing shape be given (or cleared of) an image fill, complementing the import/drop placement. Next: 3.5b — persist image assets in the .beam container.
This commit is contained in:
parent
659bc5fb02
commit
6fc3a131a6
|
|
@ -40,6 +40,7 @@ pub mod raster_fill;
|
|||
pub mod add_raster_keyframe;
|
||||
pub mod move_layer;
|
||||
pub mod set_fill_paint;
|
||||
pub mod set_image_fill;
|
||||
|
||||
pub use add_clip_instance::AddClipInstanceAction;
|
||||
pub use add_effect::AddEffectAction;
|
||||
|
|
@ -75,5 +76,6 @@ pub use raster_fill::RasterFillAction;
|
|||
pub use add_raster_keyframe::AddRasterKeyframeAction;
|
||||
pub use move_layer::MoveLayerAction;
|
||||
pub use set_fill_paint::SetFillPaintAction;
|
||||
pub use set_image_fill::SetImageFillAction;
|
||||
pub use change_bpm::ChangeBpmAction;
|
||||
pub use change_fps::ChangeFpsAction;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
//! Action that sets or clears the image fill on one or more VectorGraph fills.
|
||||
//!
|
||||
//! `image_fill` is an asset id the renderer maps onto the fill's bounding box; it
|
||||
//! takes priority over colour/gradient. Setting `None` clears it (the colour/gradient
|
||||
//! underneath shows again).
|
||||
|
||||
use crate::action::Action;
|
||||
use crate::document::Document;
|
||||
use crate::layer::AnyLayer;
|
||||
use crate::vector_graph::FillId;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct SetImageFillAction {
|
||||
layer_id: Uuid,
|
||||
time: f64,
|
||||
fill_ids: Vec<FillId>,
|
||||
/// `Some(asset_id)` to set, `None` to clear.
|
||||
new_image: Option<Uuid>,
|
||||
/// Per-fill previous `image_fill`, for undo.
|
||||
old: Vec<(FillId, Option<Uuid>)>,
|
||||
}
|
||||
|
||||
impl SetImageFillAction {
|
||||
pub fn new(layer_id: Uuid, time: f64, fill_ids: Vec<FillId>, image: Option<Uuid>) -> Self {
|
||||
Self { layer_id, time, fill_ids, new_image: image, old: Vec::new() }
|
||||
}
|
||||
|
||||
fn get_graph_mut<'a>(
|
||||
document: &'a mut Document,
|
||||
layer_id: &Uuid,
|
||||
time: f64,
|
||||
) -> Result<&'a mut crate::vector_graph::VectorGraph, String> {
|
||||
let layer = document
|
||||
.get_layer_mut(layer_id)
|
||||
.ok_or_else(|| format!("Layer {} not found", layer_id))?;
|
||||
match layer {
|
||||
AnyLayer::Vector(vl) => vl
|
||||
.graph_at_time_mut(time)
|
||||
.ok_or_else(|| format!("No keyframe at time {}", time)),
|
||||
_ => Err("Not a vector layer".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Action for SetImageFillAction {
|
||||
fn execute(&mut self, document: &mut Document) -> Result<(), String> {
|
||||
let graph = Self::get_graph_mut(document, &self.layer_id, self.time)?;
|
||||
self.old.clear();
|
||||
for &fid in &self.fill_ids {
|
||||
self.old.push((fid, graph.fill(fid).image_fill));
|
||||
graph.fill_mut(fid).image_fill = self.new_image;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rollback(&mut self, document: &mut Document) -> Result<(), String> {
|
||||
let graph = Self::get_graph_mut(document, &self.layer_id, self.time)?;
|
||||
for &(fid, old) in &self.old {
|
||||
graph.fill_mut(fid).image_fill = old;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
if self.new_image.is_some() { "Set image fill" } else { "Clear image fill" }.to_string()
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
use eframe::egui::{self, DragValue, Ui};
|
||||
use lightningbeam_core::brush_settings::{bundled_brushes, BrushSettings};
|
||||
use lightningbeam_core::actions::{SetDocumentPropertiesAction, SetShapePropertiesAction, SetFillPaintAction};
|
||||
use lightningbeam_core::actions::{SetDocumentPropertiesAction, SetShapePropertiesAction, SetFillPaintAction, SetImageFillAction};
|
||||
use lightningbeam_core::gradient::ShapeGradient;
|
||||
use lightningbeam_core::layer::{AnyLayer, LayerTrait};
|
||||
use lightningbeam_core::selection::FocusSelection;
|
||||
|
|
@ -785,6 +785,26 @@ impl InfopanelPane {
|
|||
let edge_ids: Vec<lightningbeam_core::vector_graph::EdgeId> = shared.selection.selected_edges()
|
||||
.iter().map(|eid| lightningbeam_core::vector_graph::EdgeId(eid.0)).collect();
|
||||
|
||||
// Image-fill state for the selected fills + the document's image assets, for
|
||||
// the picker below. Gathered now (immutable read) before `shared` is borrowed mut.
|
||||
let image_assets: Vec<(Uuid, String)> = {
|
||||
let doc = shared.action_executor.document();
|
||||
let mut v: Vec<(Uuid, String)> = doc.image_assets.iter()
|
||||
.map(|(id, a)| (*id, a.name.clone())).collect();
|
||||
v.sort_by(|a, b| a.1.cmp(&b.1));
|
||||
v
|
||||
};
|
||||
let current_image_fill: Option<Uuid> = {
|
||||
let doc = shared.action_executor.document();
|
||||
match doc.get_layer(&layer_id) {
|
||||
Some(lightningbeam_core::layer::AnyLayer::Vector(vl)) => vl
|
||||
.graph_at_time(time)
|
||||
.and_then(|g| face_ids.first().map(|&fid| g.fill(fid).image_fill))
|
||||
.flatten(),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
|
||||
egui::CollapsingHeader::new("Shape")
|
||||
.id_salt(("shape", path))
|
||||
.default_open(self.shape_section_open)
|
||||
|
|
@ -859,6 +879,33 @@ impl InfopanelPane {
|
|||
}
|
||||
}
|
||||
|
||||
// Image fill picker (assign/clear an imported image asset on the fill).
|
||||
if !face_ids.is_empty() {
|
||||
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());
|
||||
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(
|
||||
SetImageFillAction::new(layer_id, time, face_ids.clone(), Some(*aid)),
|
||||
));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Stroke color
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Stroke:");
|
||||
|
|
|
|||
Loading…
Reference in New Issue