Skip to content

Commit

Permalink
Ui: Rewrite targeting code.
Browse files Browse the repository at this point in the history
This makes horizontal movement "strict" (elements must line up)
and vertical movement "loose" (elements don't have to line up).
  • Loading branch information
madewokherd committed Aug 9, 2024
1 parent f234a6a commit 00147b7
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 104 deletions.
10 changes: 4 additions & 6 deletions xalia/Ui/TargetMoveRoutine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,17 @@ private void DoMove(InputState state)
{
if (Math.Abs((int)state.XAxis) > Math.Abs((int)state.YAxis))
{
double bias = state.YAxis / Math.Abs((double)state.XAxis);
if (state.XAxis > 0)
Main.TargetMove(UiMain.Direction.Right, bias);
Main.TargetMove(UiMain.Direction.Right);
else
Main.TargetMove(UiMain.Direction.Left, bias);
Main.TargetMove(UiMain.Direction.Left);
}
else
{
double bias = state.XAxis / Math.Abs((double)state.YAxis);
if (state.YAxis > 0)
Main.TargetMove(UiMain.Direction.Down, bias);
Main.TargetMove(UiMain.Direction.Down);
else
Main.TargetMove(UiMain.Direction.Up, bias);
Main.TargetMove(UiMain.Direction.Up);
}
}
}
Expand Down
185 changes: 87 additions & 98 deletions xalia/Ui/UiMain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,43 @@ internal enum Direction
throw new ArgumentException("invalid Direction value");
}

internal void TargetMove(Direction direction, double bias=0)
private void GetBoundsRelation((int,int,int,int) a, (int,int,int,int) b,
out int x_direction, out int y_direction)
{
// Determine what "direction" an (x,y,w,h) is from another
if (BoundsIntersect(a, b))
{
// figure out the length of the shared x/y borders
var shared_x = Math.Min(a.Item1 + a.Item3, b.Item1 + b.Item3) - Math.Max(a.Item1, b.Item1);
var shared_y = Math.Min(a.Item2 + a.Item4, b.Item2 + b.Item4) - Math.Max(a.Item2, b.Item2);
int xofs = (a.Item1 * 2 + a.Item3) - (b.Item1 * 2 + b.Item3);
int yofs = (a.Item2 * 2 + a.Item4) - (b.Item2 * 2 + b.Item4);

if (Math.Abs(xofs) - shared_x > Math.Abs(yofs) - shared_y)
yofs = 0;
else if (Math.Abs(xofs) - shared_x < Math.Abs(yofs) - shared_y)
xofs = 0;
x_direction = Math.Sign(xofs);
y_direction = Math.Sign(yofs);
return;
}

if (a.Item1 >= b.Item1 + b.Item3)
x_direction = 1;
else if (a.Item1 + a.Item3 <= b.Item1)
x_direction = -1;
else
x_direction = 0;

if (a.Item2 >= b.Item2 + b.Item4)
y_direction = 1;
else if (a.Item2 + a.Item4 <= b.Item2)
y_direction = -1;
else
y_direction = 0;
}

internal void TargetMove(Direction direction)
{
if (TargetedElement is null)
return;
Expand All @@ -768,124 +804,77 @@ internal void TargetMove(Direction direction, double bias=0)
return;
}

var candidates = new List<KeyValuePair<UiDomElement, (int, int, int, int)>>(targetable_elements);

int current_perpendicular;

current_bounds = TranslateBox(current_bounds, direction);

UiDomElement best_element = null;
// These are actually distance squared so we can skip sqrt.
long best_edge_distance = 0;
long best_center_distance = 0;

foreach (var kvp in targetable_elements)
{
var candidate_element = kvp.Key;
var candidate_bounds = kvp.Value;

if (candidate_element == TargetedElement)
continue;
var is_horizontal = (direction == Direction.Left || direction == Direction.Right);

candidate_bounds = TranslateBox(candidate_bounds, direction);
if (is_horizontal)
{
// Filter out anything that'd be vertical movement, or no horizontal movement
candidates.RemoveAll((KeyValuePair<UiDomElement,(int,int,int,int)> kvp) =>
{
if (kvp.Key == TargetedElement)
return true;
GetBoundsRelation(current_bounds, TranslateBox(kvp.Value, direction), out var xd, out var yd);
if (xd == 0)
return true;
if (yd != 0)
return true;
return false;
});

if (candidate_bounds.Item1 + candidate_bounds.Item3 > current_bounds.Item1 &&
current_bounds.Item1 + current_bounds.Item3 > candidate_bounds.Item1 &&
candidate_bounds.Item2 + candidate_bounds.Item4 > current_bounds.Item2 &&
current_bounds.Item2 + current_bounds.Item4 > candidate_bounds.Item2)
if (candidates.Count == 0)
{
// candidate intersects current target
int current_center_x = current_bounds.Item1 + current_bounds.Item3 / 2;
int current_center_y = current_bounds.Item2 + current_bounds.Item4 / 2;
int candidate_center_x = candidate_bounds.Item1 + candidate_bounds.Item3 / 2;
int candidate_center_y = candidate_bounds.Item2 + candidate_bounds.Item4 / 2;
AdjustValue(TargetedElement, direction);
return;
}
}

int center_dx = candidate_center_x - current_center_x;
int center_dy = candidate_center_y - current_center_y;
current_perpendicular = current_bounds.Item2 * 2 + current_bounds.Item4;

if (center_dx <= 0)
{
// candidate center is not to the right of target
continue;
}
var best_perpendicular = int.MaxValue;
var best_bounds = (0, 0, 0, 0);
UiDomElement best_element = null;
foreach (var kvp in candidates)
{
if (kvp.Key == TargetedElement)
continue;

if (center_dx < Math.Abs(center_dy))
{
// candidate is more up/down than right.
continue;
}
var box = TranslateBox(kvp.Value, direction);
int box_perpendicular;

/* This value of edge_distance will be negative. This is intentional. */
int edge_distance = candidate_bounds.Item1 - (current_bounds.Item1 + current_bounds.Item3);
int center_distance = center_dx * center_dx + center_dy * center_dy;
box_perpendicular = Math.Abs(box.Item2 * 2 + box.Item4 - current_perpendicular);

if (best_element is null ||
edge_distance < best_edge_distance ||
(edge_distance == best_edge_distance && center_distance < best_center_distance))
{
best_element = candidate_element;
best_edge_distance = edge_distance;
best_center_distance = center_distance;
}
continue;
}
GetBoundsRelation(current_bounds, box, out var xd, out var yd);

if (candidate_bounds.Item1 < current_bounds.Item1 + current_bounds.Item3)
{
// candidate's left edge must be to the right of current target
if (xd >= 0)
// This would be moving "backwards" or "sideways"
continue;
}

// Calculate edge distance
long dx = candidate_bounds.Item1 - (current_bounds.Item1 + current_bounds.Item3);
long dy;

int y_diff_start = candidate_bounds.Item2 - current_bounds.Item2;

if (bias != 0)
if (!(best_element is null))
{
y_diff_start -= (int)(Math.Round(bias * dx));
}
GetBoundsRelation(best_bounds, box, out var bxd, out var byd);

int y_diff_end = y_diff_start + candidate_bounds.Item4;
if (bxd < 0)
// best_element is closer along the axis we're moving
continue;

if (y_diff_end < 0)
dy = -y_diff_end;
else if (y_diff_start > current_bounds.Item4)
dy = y_diff_start;
else
dy = 0;
if (dy != 0)
{
// Use the far edge/corner rather than the near in this case
dx += candidate_bounds.Item3;
dy += candidate_bounds.Item4;
}
var candidate_edge_distance = (dx * dx) + (dy * dy) * 4;

// Calculate centerpoint distance, with bias
var candidate_biased_y = candidate_bounds.Item2 + (int)Math.Round(candidate_bounds.Item4 * (1 - bias) / 2);
candidate_biased_y -= (int)Math.Round(bias * dx);
var current_biased_y = current_bounds.Item2 + (int)Math.Round(current_bounds.Item4 * (1 + bias) / 2);
dy = candidate_biased_y - current_biased_y;
var candidate_center_distance = (dx * dx) + (dy * dy) * 4;

if (best_element is null ||
candidate_edge_distance < best_edge_distance ||
(candidate_edge_distance == best_edge_distance && candidate_center_distance < best_center_distance))
{
best_element = candidate_element;
best_edge_distance = candidate_edge_distance;
best_center_distance = candidate_center_distance;
continue;
if (bxd == 0 && box_perpendicular > best_perpendicular)
// similar distance along the axis we're moving, but best is less diagonal
continue;
}

best_element = kvp.Key;
best_bounds = box;
best_perpendicular = box_perpendicular;
}

if (best_element is null)
{
if (ScrollAncestor(TargetedElement, direction))
return;

AdjustValue(TargetedElement, direction);

return;
}

TargetedElement = best_element;

Expand Down

0 comments on commit 00147b7

Please sign in to comment.