Put the edge controls where the mask is
Feather, falloff, grow/shrink/close/open and their amount, on the selected layer. All four read the one distance field, so all four are live — nothing recomputes except a compound morphology, and the session keys on that separately so a feather drag rebuilds nothing. Shown only for sources that go through the distance field. A gradient carries its own falloff in its geometry, and offering a second one would be two controls fighting over the same edge. Picking an operation seeds a small amount if none is set. Selecting "Grow" and seeing nothing happen would read as a broken control rather than as a radius of zero.
This commit is contained in:
@@ -1105,6 +1105,86 @@ impl DevelopSession {
|
||||
}
|
||||
}
|
||||
|
||||
/// Edge transition half-width, in fractions of the shorter edge.
|
||||
pub fn set_mask_feather(&mut self, id: &str, feather: f32) {
|
||||
if let Some(layer) = self.graph.masks_mut().get_mut(id) {
|
||||
layer.feather = feather.clamp(0.0, 1.0);
|
||||
self.history.record(&self.graph, Edit::Op(OpId("mask-feather")));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_mask_falloff(&mut self, id: &str, index: usize) {
|
||||
use dr_pipeline::mask::Falloff;
|
||||
let Some(&falloff) = Falloff::ALL.get(index) else {
|
||||
return;
|
||||
};
|
||||
if let Some(layer) = self.graph.masks_mut().get_mut(id) {
|
||||
layer.falloff = falloff;
|
||||
self.history.record(&self.graph, Edit::Discrete);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_mask_morphology(&mut self, id: &str, index: usize) {
|
||||
use dr_pipeline::mask::Morphology;
|
||||
let Some(&morphology) = Morphology::ALL.get(index) else {
|
||||
return;
|
||||
};
|
||||
if let Some(layer) = self.graph.masks_mut().get_mut(id) {
|
||||
layer.morphology = morphology;
|
||||
// Picking an operation with no amount set would appear to do
|
||||
// nothing, and the user would reasonably conclude it is broken.
|
||||
if morphology != Morphology::None && layer.morph_radius <= 0.0 {
|
||||
layer.morph_radius = 0.006;
|
||||
}
|
||||
self.history.record(&self.graph, Edit::Discrete);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_mask_morph_radius(&mut self, id: &str, radius: f32) {
|
||||
if let Some(layer) = self.graph.masks_mut().get_mut(id) {
|
||||
layer.morph_radius = radius.clamp(0.0, 1.0);
|
||||
self.history.record(&self.graph, Edit::Op(OpId("mask-morph")));
|
||||
}
|
||||
}
|
||||
|
||||
/// Which falloff a layer uses, as an index into `Falloff::ALL`.
|
||||
pub fn mask_falloff(&self, id: &str) -> usize {
|
||||
use dr_pipeline::mask::Falloff;
|
||||
self.graph.masks().get(id).map_or(0, |l| {
|
||||
Falloff::ALL.iter().position(|&f| f == l.falloff).unwrap_or(0)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn mask_morphology(&self, id: &str) -> usize {
|
||||
use dr_pipeline::mask::Morphology;
|
||||
self.graph.masks().get(id).map_or(0, |l| {
|
||||
Morphology::ALL
|
||||
.iter()
|
||||
.position(|&m| m == l.morphology)
|
||||
.unwrap_or(0)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn mask_feather(&self, id: &str) -> f32 {
|
||||
self.graph.masks().get(id).map_or(0.0, |l| l.feather)
|
||||
}
|
||||
|
||||
pub fn mask_morph_radius(&self, id: &str) -> f32 {
|
||||
self.graph.masks().get(id).map_or(0.0, |l| l.morph_radius)
|
||||
}
|
||||
|
||||
/// Whether the edge controls apply to this layer.
|
||||
///
|
||||
/// Only sources that go through the distance field. A gradient carries its
|
||||
/// own falloff in its geometry, so offering a second one would be two
|
||||
/// controls fighting over the same edge.
|
||||
pub fn mask_is_shapeable(&self, id: &str) -> bool {
|
||||
use dr_pipeline::mask::MaskSource;
|
||||
self.graph.masks().get(id).is_some_and(|l| {
|
||||
matches!(l.source, MaskSource::Subject { .. } | MaskSource::Regions { .. })
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_mask_opacity(&mut self, id: &str, opacity: f32) {
|
||||
if let Some(layer) = self.graph.masks_mut().get_mut(id) {
|
||||
layer.opacity = opacity.clamp(0.0, 1.0);
|
||||
|
||||
Reference in New Issue
Block a user