Skip to content

Instantly share code, notes, and snippets.

@krypt-lynx
Last active October 3, 2020 00:43
Show Gist options
  • Save krypt-lynx/aac8129b8dd9525c7585435f5dd19f96 to your computer and use it in GitHub Desktop.
Save krypt-lynx/aac8129b8dd9525c7585435f5dd19f96 to your computer and use it in GitHub Desktop.
column widths using Cassowary algorithm
static bool RecacheColumnWidths_prefix(PawnTable __instance)
{
ClSimplexSolver solver = new ClSimplexSolver();
solver.AutoSolve = false;
var cachedColumnWidths = (List<float>)typeof(PawnTable)
.GetField("cachedColumnWidths", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(__instance);
var def = (PawnTableDef)typeof(PawnTable)
.GetField("def", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(__instance);
var cachedSize = (Vector2)typeof(PawnTable)
.GetField("cachedSize", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(__instance);
float width = cachedSize.x - 16f;
List<float> columnOptimal = new List<float>();
// variables representing column widths
List<ClVariable> columnVars = new List<ClVariable>();
// expression for summ of column widths
ClLinearExpression widthExpr = new ClLinearExpression();
HashSet<int> distinctPriorities = new HashSet<int>();
// summ of optimal column widths
float optimalWidth = 0;
foreach (var column in def.columns)
{
float optimalColumnWidth;
if (column.ignoreWhenCalculatingOptimalTableSize)
{
optimalColumnWidth = 0;
}
else
{
optimalColumnWidth = GetOptimalWidth(__instance, column);
}
optimalWidth += optimalColumnWidth;
columnOptimal.Add(optimalColumnWidth);
var columnVar = new ClVariable(column.defName); // name is used for debug purposes only
columnVars.Add(columnVar);
widthExpr.AddExpression(columnVar); // building summ
distinctPriorities.Add(column.widthPriority);
}
// variable representing flexability of columns. Will be equal to 1 if all column widths are optimal and something else if not.
var flex = new ClVariable("flex");
// constraining summ of column widths to window width
solver.AddConstraint(widthExpr ^ width, ClStrength.Strong);
// To make prorities work sorting them in order and using order as priority in solver
// And hope for the best, because of priorities bug.
// But we are fine if we have less then 10 different priorities (2^n < 1000)
var priorities = distinctPriorities.ToList();
priorities.Sort();
var orderForPriority = priorities.Select((x, i) => (x, i)).ToDictionary(xi => xi.Item1, xi => xi.Item2);
// to make columns with equal priorities break together bind them together with ClStrength.Strong priority
var priorityVars = priorities.Select(x => new ClVariable($"priority_{x}")).ToArray();
for (int i = 0; i < def.columns.Count; i++)
{
var column = def.columns[i];
var p = orderForPriority[column.widthPriority];
var columnWidthExpr = priorityVars[p] * columnOptimal[i] * width / optimalWidth - columnVars[i]; // == 0. Equation for width proportional resize
solver.AddConstraint(new ClLinearConstraint(columnWidthExpr, ClStrength.Strong));
solver.AddConstraint(columnVars[i] >= GetMinWidth(__instance, column), ClStrength.Medium);
solver.AddConstraint(columnVars[i] <= GetMaxWidth(__instance, column), ClStrength.Medium);
}
for (int i = 0; i < priorities.Count; i++)
{
var priorityVar = priorityVars[i];
// binding priority variables to flex var
solver.AddConstraint(new ClLinearConstraint(priorityVar - flex, ClStrength.Weak, Mathf.Pow(2, i)));
}
// do the magic
solver.Solve();
// copy results to widths cache
cachedColumnWidths.Clear();
for (int i = 0; i < def.columns.Count; i++)
{
cachedColumnWidths.Add((float)columnVars[i].Value);
}
return false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment