Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Cassowary Laravel Package

php-tui/cassowary

A PHP implementation of the Cassowary constraint solver for building flexible terminal interfaces. Add and remove linear equality and inequality constraints with priorities to automatically calculate widget sizes and positions in PHP-TUI layouts.

Deep Wiki
Context7

Getting Started

Install it as a normal Composer dependency:

composer require php-tui/cassowary

The package is a PHP port of the Cassowary linear constraint solver — the same algorithm behind Apple's Auto Layout. You give it variables, declare relationships between them, and it solves for a consistent set of numeric values. It is a pure solving library: no layout, no rendering, no widgets. If you are already using php-tui, it is pulled in transitively by the layout system; adding it directly is only needed when you want to drive layouts yourself.

Look first at:

  • Solver — the entry point (addConstraint, removeConstraint, addEditVariable, solve).
  • Variable — the unknowns (typically x, y, width, height per widget).
  • Constraint — the factory methods equal, lessThanOrEqual, greaterThanOrEqual, and Constraint::fromExpression(...).
  • Strength — required(), strong(), medium(), weak(); the priority system that lets preferred layouts bend when required ones cannot.

First use case — a centered box inside a 80-column terminal:

use PhpTui\Cassowary\{Solver, Variable, Constraint, Strength};

$solver = new Solver();

$left   = new Variable('left');
$width  = new Variable('width');
$center = new Variable('center');

$solver->addConstraint(Constraint::equal($left, 0));                        // required
$solver->addConstraint(Constraint::greaterThanOrEqual($width, 10));
$solver->addConstraint(Constraint::lessThanOrEqual($width, 60));
$solver->addConstraint(
    Constraint::equal($center, 40)->withStrength(Strength::required())
);
$solver->addConstraint(
    Constraint::equal($left + $width / 2, $center)->withStrength(Strength::strong())
);

$solver->solve();

printf("left=%d width=%d\n", $solver->value($left), $solver->value($width));

Once that compiles you have everything you need to reason about the rest of the package.

Implementation Patterns

One variable set per axis. The most common real-world pattern is to model your layout as four variables (left, top, width, height) per widget, plus derived variables for right = left + width and bottom = top + height. Build these once when a widget is created and reuse them for the widget's lifetime — recreating variables on every frame is the easiest way to make the solver slow.

Separate structural from cosmetic constraints. Structural relationships (siblings touch, parent contains child) should be required. Cosmetic preferences (center it, prefer this widget larger) should be strong or medium. Never make two required constraints that can contradict each other — solver throws rather than returning a compromise.

Wrap the solver in a layout engine. Don't sprinkle Solver calls through your render loop. Build a small Layout class that:

  1. Registers variables for each node.
  2. Adds required structural constraints once.
  3. Adds edit variables for the things that change (terminal resize, cursor position, scroll offset).
  4. Calls suggestValue() on the edit variables when input arrives.
  5. Calls solve() and reads back coordinates for rendering.

This is precisely how php-tui itself wires Cassowary in: a render pass mutates edit variables, then solve() updates the layout tree.

Use edit variables for anything interactive. For a resizable pane or a draggable split, prefer addEditVariable($var, $strength) plus suggestValue($var, $target) over removeConstraint/addConstraint pairs. Adding and removing constraints on every frame is significantly slower and can leave floating-point debris across solves.

Cache and re-solve lazily. Cache the solver and the variable graph between frames. Only rebuild from scratch when the widget tree structurally changes (a panel is added/removed). For everything else, mutate edit variables and re-solve — that path is designed to be cheap.

Integrate with php-tui at the widget level. If you are writing your own widget for a php-tui app, expose the usual Layout hint API, and inside the widget's render() resolve your constraints against the rectangle the framework hands you. Do not fight the framework's own layout pass; treat Cassowary as a way to compute internal offsets within your bounding box.

Compose expressions, don't invent new variables. Cassowary lets you write $a + $b*0.5 - $c*2. Prefer expressions like Constraint::equal($right, $parentRight - 1) over introducing an intermediate $margin variable — each variable is solver state you have to keep track of.

Gotchas and Tips

  • Over-constrained systems throw. The first time you get an UnsatisfiableConstraint it almost always means you declared two required constraints on the same variable with conflicting values. Bisect by removing constraints, or downgrade the suspicious one to strong — the solver will then return a best-effort answer.
  • Required vs. strong are not "more important". required means "must hold or fail". strong/medium/weak are preference weights that only interact with each other. If nothing else can bend, the solver will throw, not silently pick the strong one.
  • Strengths must be tuned by relationship, not by number. The typical mistake is assigning strong() to everything "important". Reserve strong for one or two constraints per axis; use medium/weak for the rest. If you need a numeric strength, Strength::create(750.0) gives you finer control than the named buckets.
  • Values are floats. $solver->value($var) returns a float. Terminal columns are integers, so round before rendering (round(), not floor() — flooring accumulates drift across nested layouts).
  • Name every variable. Pass a string to new Variable('panel.left'). When the solver throws an unsatisfiable-constraint error, having readable names in the message is the difference between a ten-second fix and a thirty-minute one.
  • Solve order matters for edit variables. Call suggestValue() on edit variables before solve(), not after. Values suggested after solving are used on the next pass, which produces subtle frame-lag bugs.
  • Watch out for addEditVariable duplication. Adding the same variable as an edit variable twice (once per component that thinks it owns it) is easy to do in a widget tree. Track owners explicitly or wrap edit-variable registration behind a single manager.
  • Debugging tip: if a value comes back NaN, you likely have a constraint referencing a variable you never added any bounded constraint for. Add Constraint::greaterThanOrEqual($var, 0) as a cheap safety net.
  • Extension point: subclassing Constraint to attach metadata (widget ID, debug label) and reading that metadata back from inside your layout engine is the cleanest way to trace which widget produced a bad constraint — the base class is small and the solver only calls into it for the expression and strength.
  • Version pinning: this is a small, low-traffic package (20 stars). Pin a minor version in composer.json and read the changelog before upgrading; the constraint API surface is stable but sparse, so any change is likely to break your code rather than go unnoticed.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings