// Nested multi-panel layout driven by one persistent solver.
//
// Layout: window
// +--------------+------------------+
// | sidebar | main |
// | | +----+--------+ |
// | | |list| detail | |
// | | +----+--------+ |
// +--------------+------------------+
//
// Relationships a plain box model cannot express on its own:
// - the sidebar keeps a soft 1:3 proportion against the main area,
// - the mini list prefers a 40% share of the main area (soft),
// - every panel has a hard minimum,
// - dragging the sidebar is a suggestion, not an assignment.
///|
struct Panel {
handle : @cassowary.Variable
name : String
}
///|
fn report(
solver : @cassowary.Solver,
panels : Array[Panel],
label : String,
) -> Unit raise {
assert_true(solver.is_satisfied())
let mut line = "\{label}: "
for panel in panels {
line += "\{panel.name}=\{solver.value(panel.handle)} "
}
let worst = solver.maximum_required_violation()
println("\{line}| required.max=\{worst}")
}
///|
fn main raise {
let solver = @cassowary.Solver::new()
let window = @cassowary.Variable::new("window")
let sidebar = @cassowary.Variable::new("sidebar")
let main_area = @cassowary.Variable::new("main")
let list = @cassowary.Variable::new("list")
let detail = @cassowary.Variable::new("detail")
let gap = 12.0
let panels : Array[Panel] = [
{ handle: window, name: "window", },
{ handle: sidebar, name: "sidebar", },
{ handle: main_area, name: "main", },
{ handle: list, name: "list", },
{ handle: detail, name: "detail", },
]
// Hard structure: window = sidebar + gap + main, main = list + detail.
solver.add_constraint(
@cassowary.Constraint::new(
@cassowary.Expression::new(
[(window, 1.0), (sidebar, -1.0), (main_area, -1.0)],
constant=-gap,
),
@cassowary.Relation::Equal,
),
)
solver.add_constraint(
@cassowary.Constraint::new(
@cassowary.Expression::new([
(main_area, 1.0),
(list, -1.0),
(detail, -1.0),
]),
@cassowary.Relation::Equal,
),
)
// Hard minimums.
for
(variable, minimum) in [
(sidebar, 180.0),
(main_area, 420.0),
(list, 120.0),
(detail, 200.0),
] {
solver.add_constraint(
@cassowary.Constraint::new(
@cassowary.Expression::new([(variable, 1.0)], constant=-minimum),
@cassowary.Relation::GreaterEqual,
),
)
}
// Soft relationships: 1:3 sidebar/main proportion and a 40% list share.
solver.add_constraint(
@cassowary.Constraint::new(
@cassowary.Expression::new([(sidebar, 3.0), (main_area, -1.0)]),
@cassowary.Relation::Equal,
strength=@cassowary.medium(),
),
)
solver.add_constraint(
@cassowary.Constraint::new(
@cassowary.Expression::new([(list, 5.0), (main_area, -2.0)]),
@cassowary.Relation::Equal,
strength=@cassowary.strong(),
),
)
// Both edit handles can be satisfied when sufficient space remains. Equal
// strengths do not define write ordering when suggestions are incompatible.
solver.add_edit_variable(window, @cassowary.strong())
solver.add_edit_variable(sidebar, @cassowary.strong())
solver.suggest_value(window, 1280.0)
assert_eq(solver.value(window), 1280.0)
report(solver, panels, "open at 1280")
// A window resize keeps every required relationship, including nested ones.
solver.suggest_value(window, 1024.0)
assert_eq(solver.value(window), 1024.0)
report(solver, panels, "resize to 1024")
// The compatible drag and window suggestions outweigh the medium proportion.
solver.suggest_value(sidebar, 500.0)
assert_eq(solver.value(window), 1024.0)
assert_eq(solver.value(sidebar), 500.0)
assert_eq(solver.value(main_area), 512.0)
assert_true((solver.value(list) - 204.8).abs() <= 1.0e-8)
assert_true((solver.value(detail) - 307.2).abs() <= 1.0e-8)
report(solver, panels, "drag sidebar to 500")
// A window narrower than the required minimums cannot be honored. Check the
// hard bounds and minimum combined edit error, not an arbitrary tied optimum.
solver.suggest_value(window, 300.0)
solver.suggest_value(sidebar, 200.0)
assert_true(solver.value(window) >= 612.0 - 1.0e-8)
let edit_error = (solver.value(window) - 300.0).abs() +
(solver.value(sidebar) - 200.0).abs()
assert_true((edit_error - 332.0).abs() <= 1.0e-8)
report(solver, panels, "shrink to 300")
println("(the suggested width is a preference; required minimums win)")
println("all required relationships hold: true (asserted at every step)")
}