///|
/// A soft constraint for automatic poster layout.
pub(all) enum Constraint {
KeepInside(Rect)
MinimumDistance(Double)
AlignX(Double)
AlignY(Double)
CenterIn(Rect)
} derive(Eq, Debug, ToJson)
///|
pub fn constraint_penalty(
point : Vec2,
constraints : Array[Constraint],
) -> Double {
let mut penalty = 0.0
for constraint in constraints {
match constraint {
KeepInside(bounds) => {
if point.x < bounds.x {
penalty += bounds.x - point.x
}
if point.x > bounds.right() {
penalty += point.x - bounds.right()
}
if point.y < bounds.y {
penalty += bounds.y - point.y
}
if point.y > bounds.bottom() {
penalty += point.y - bounds.bottom()
}
}
MinimumDistance(distance) =>
if point.length() < distance {
penalty += distance - point.length()
}
AlignX(value) => penalty += (point.x - value).abs()
AlignY(value) => penalty += (point.y - value).abs()
CenterIn(bounds) => penalty += point.distance(bounds.center())
}
}
penalty
}
///|
pub fn project_constraints(
point : Vec2,
constraints : Array[Constraint],
iterations : Int,
) -> Vec2 {
let mut current = point
for _ in 0..
current = Vec2::new(
current.x.max(bounds.x).min(bounds.right()),
current.y.max(bounds.y).min(bounds.bottom()),
)
MinimumDistance(distance) =>
if current.length() < distance {
current = current.normalized().scale(distance)
}
AlignX(value) => current = Vec2::new(value, current.y)
AlignY(value) => current = Vec2::new(current.x, value)
CenterIn(bounds) => current = current.lerp(bounds.center(), 0.5)
}
}
}
current
}
///|
pub(all) struct LayoutCandidate {
position : Vec2
score : Double
constraints : Array[Constraint]
} derive(Eq, Debug, ToJson)
///|
pub fn optimize_position(
start : Vec2,
constraints : Array[Constraint],
steps : Int,
step_size : Double,
) -> LayoutCandidate {
let mut position = start
let mut best = constraint_penalty(position, constraints)
for step in 0.. Array[LayoutCandidate] {
let result = []
for start in starts {
result.push(optimize_position(start, constraints, steps, step_size))
}
result
}
///|
pub fn best_candidate(candidates : Array[LayoutCandidate]) -> LayoutCandidate? {
if candidates.length() == 0 {
None
} else {
let mut best = candidates[0]
for candidate in candidates[1:] {
if candidate.score > best.score {
best = candidate
}
}
Some(best)
}
}