Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Opus/App.config
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<appSettings>
<add key="ScreenCapture.LoggingEnabled" value="false"/>
<add key="MouseUtils.GlobalDragDelay" value="0"/>
<add key="InstructionRenderer.QuickHotkeysEnabled" value="false"/>
</appSettings>
<log4net>
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
Expand Down
44 changes: 44 additions & 0 deletions Opus/UI/HexGrid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,50 @@ public void EnsureCellsVisible(Bounds bounds, bool scrollToCenter = false)
}
}

/// <summary>
/// Checks if scrolling the grid if necessary so that the cells in the coordinates given by bounds
/// are completely visible.
/// </summary>
public bool CheckCellsVisible(Vector2 position, out Rectangle scrollTargetRect, bool scrollToCenter = false)
{
return CheckCellsVisible(new Bounds(position, position), out scrollTargetRect, scrollToCenter);
}

/// <summary>
/// Checks if scrolling the grid if necessary so that the cells in the coordinates given by bounds
/// are completely visible.
/// </summary>
public bool CheckCellsVisible(Bounds bounds, out Rectangle scrollTargetRect, bool scrollToCenter = false)
{
var bottomLeft = GetCellLocationLocal(bounds.Min);
var topRight = GetCellLocationLocal(bounds.Max.Add(new Vector2(0, 1))); // Add (0, 1) so we avoid the exit button in the top-right corner of the screen
scrollTargetRect = new Rectangle(bottomLeft.X - HexWidth / 2, topRight.Y - HexHeight / 2,
topRight.X - bottomLeft.X + HexWidth, bottomLeft.Y - topRight.Y + HexHeight);
if (scrollToCenter)
{
return m_area.CheckScrollToCenterIfNecessary(scrollTargetRect);
}
else
{
return m_area.CheckScrollMinimalIfNecessary(scrollTargetRect);
}
}

/// <summary>
/// Applies scrolling to a specific rect.
/// </summary>
public void ApplyCellsVisible(Rectangle rect, bool scrollToCenter = false)
{
if (scrollToCenter)
{
m_area.ScrollToCenterIfNecessary(rect);
}
else
{
m_area.ScrollMinimalIfNecessary(rect);
}
}

/// <summary>
/// Scrolls the grid so that the center of the cell at the specified coordinates is in the middle of the grid.
/// </summary>
Expand Down
28 changes: 21 additions & 7 deletions Opus/UI/Rendering/InstructionRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public class InstructionRenderer
{ Instruction.Repeat, Keys.V }
};

public static bool QuickHotkeysEnabled { get; set; } = false;

public InstructionRenderer(ProgramGrid grid)
{
m_grid = grid;
Expand All @@ -42,15 +44,27 @@ public void Render(Vector2 position, Instruction instruction, int delay = 0)
var gridLocation = m_grid.GetCellLocation(position);

int keyTime = delay;
int clickTime = delay + 50;
int clickTime = delay + 50;

KeyDown(key);
MouseUtils.SetCursorPosition(gridLocation);
ThreadUtils.SleepOrAbort(keyTime);
MouseUtils.LeftClick(clickTime);
if (!QuickHotkeysEnabled)
{
KeyDown(key);
MouseUtils.SetCursorPosition(gridLocation);
ThreadUtils.SleepOrAbort(keyTime);
MouseUtils.LeftClick(clickTime);

KeyUp(key);
ThreadUtils.SleepOrAbort(keyTime);
KeyUp(key);
ThreadUtils.SleepOrAbort(keyTime);
}
else
{
MouseUtils.SetCursorPosition(gridLocation);
ThreadUtils.SleepOrAbort(keyTime);
KeyDown(key);
ThreadUtils.SleepOrAbort(keyTime);
KeyUp(key);
ThreadUtils.SleepOrAbort(keyTime);
}
}
}
}
Expand Down
82 changes: 66 additions & 16 deletions Opus/UI/Rendering/ProgramRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ public class ProgramRenderer
private InstructionRenderer m_instructionRenderer;
private int m_renderDelay = 10;

private List<Instruction> lastCopy;
private bool lastRowErrored = false;

public ProgramRenderer(ProgramGrid grid, Program program, IEnumerable<Arm> arms)
{
m_grid = grid;
Expand Down Expand Up @@ -62,6 +65,8 @@ private void RenderRow(int startTime, int endTime, int armIndex, List<Instructio
sm_log.Info(Invariant($"Rendering arm {armIndex} from {startTime} to {endTime}"));
m_grid.EnsureCellVisible(new Vector2(startTime, armIndex));

int chainCopy = 0;

for (int timeIndex = startTime; timeIndex <= endTime; timeIndex++)
{
if (!instructions[timeIndex].IsRenderable())
Expand All @@ -71,33 +76,41 @@ private void RenderRow(int startTime, int endTime, int armIndex, List<Instructio

// It's quite common for an arm to have similar instructions to the previous arm, so
// try to copy them if possible.
int numToCopy = FindCopyableInstructions(timeIndex, endTime, armIndex);
int numToCopy = FindCopyableInstructions(timeIndex, endTime, armIndex, out var copyList);

// Don't bother for single instructions as it's quicker to just recreate them
if (numToCopy > 1)
{
m_grid.EnsureCellsVisible(new Vector2(startTime, armIndex - 1), new Vector2(numToCopy, 2));
CopyInstructionsFromPrevious(timeIndex, numToCopy, armIndex);
CopyInstructionsFromPrevious(timeIndex, numToCopy, armIndex, out chainCopy, copyList);

if (chainCopy > 0)
{
chainCopy = numToCopy;
}
timeIndex += numToCopy - 1;
}
else
{
lastCopy = null;
m_instructionRenderer.Render(new Vector2(timeIndex, armIndex), instructions[timeIndex], m_renderDelay);
}
}

EnsureRowCorrect(startTime, endTime, armIndex, instructions);
EnsureRowCorrect(startTime, endTime, armIndex, instructions, chainCopy);
}

/// <summary>
/// Finds the number of instructions that are the same between the specified arm and the
/// previous arm, starting at startTime.
/// </summary>
private int FindCopyableInstructions(int startTime, int endTime, int armIndex)
private int FindCopyableInstructions(int startTime, int endTime, int armIndex, out List<Instruction> copyList)
{
copyList = null;
int result = 0;
if (armIndex == 0)
{
return 0;
return result;
}

var instructions = m_program.GetArmInstructions(m_arms[armIndex]);
Expand All @@ -119,24 +132,39 @@ private int FindCopyableInstructions(int startTime, int endTime, int armIndex)
int lastRenderable = instructions.FindLastIndex(lastSame, lastSame - startTime + 1, i => i.IsRenderable());
if (lastRenderable < 0)
{
return 0;
return result;
}
else
{
return lastRenderable - startTime + 1;
result = lastRenderable - startTime + 1;
copyList = instructions.GetRange(startTime, result);
return result;
}
}

return lastSame - startTime + 1;
result = lastSame - startTime + 1;
copyList = instructions.GetRange(startTime, result);
return result;
}

private void CopyInstructionsFromPrevious(int timeIndex, int width, int armIndex)
private void CopyInstructionsFromPrevious(int timeIndex, int width, int armIndex, out int chainCopy, List<Instruction> copyList = null)
{
chainCopy = 0;

// Select the instructions to copy
var sourcePos = new Vector2(timeIndex, armIndex - 1);
var dragStart = m_grid.GetCellLocation(new Vector2(timeIndex + width - 1, armIndex));
var dragEnd = m_grid.GetCellLocation(sourcePos);
MouseUtils.LeftDrag(dragStart, dragEnd);

if (lastCopy == null || copyList == null || !copyList.SequenceEqual(lastCopy))
{
var dragStart = m_grid.GetCellLocation(new Vector2(timeIndex + width - 1, armIndex));
MouseUtils.LeftDrag(dragStart, dragEnd);
lastCopy = copyList;
}
else
{
chainCopy = 1;
}

// Copy them
KeyDown(Keys.ControlKey);
Expand All @@ -148,7 +176,7 @@ private void CopyInstructionsFromPrevious(int timeIndex, int width, int armIndex
ThreadUtils.SleepOrAbort(m_renderDelay);
}

private void EnsureRowCorrect(int startTime, int endTime, int armIndex, List<Instruction> instructions)
private void EnsureRowCorrect(int startTime, int endTime, int armIndex, List<Instruction> instructions, int chainCopy = 0)
{
const int maxRetries = 5;
int retryCount = 0;
Expand All @@ -157,10 +185,16 @@ private void EnsureRowCorrect(int startTime, int endTime, int armIndex, List<Ins
var errors = FindErrors(startTime, endTime, armIndex, instructions);
while (errors.Any())
{
lastCopy = null;
if (lastRowErrored)
{
retryCount = 1;
}

if (prevErrors.HasValue && errors.Count() < prevErrors.Value)
{
// The number of errors has decreased, so reset the retry count
retryCount = 0;
retryCount = 1;
}
else
{
Expand All @@ -172,17 +206,33 @@ private void EnsureRowCorrect(int startTime, int endTime, int armIndex, List<Ins
}
}

m_renderDelay += 10;
sm_log.Info("Increasing render delay to " + m_renderDelay);
if (retryCount > 1)
{
// First retry did not work, increase render delay
m_renderDelay += 10;
sm_log.Info("Increasing render delay to " + m_renderDelay);
}

foreach (int timeIndex in errors)
{
sm_log.Info(Invariant($"Re-rendering instruction for arm {armIndex} at time {timeIndex}"));
m_instructionRenderer.Render(new Vector2(timeIndex, armIndex), instructions[timeIndex], m_renderDelay);
if (chainCopy > 0)
{
m_grid.EnsureCellsVisible(new Vector2(startTime, armIndex - 1), new Vector2(chainCopy, 2));
CopyInstructionsFromPrevious(timeIndex, chainCopy, armIndex, out chainCopy);
}
else
{
m_instructionRenderer.Render(new Vector2(timeIndex, armIndex), instructions[timeIndex], m_renderDelay);
}
}

errors = FindErrors(startTime, endTime, armIndex, instructions);
}
if (retryCount == 0)
{
lastRowErrored = false;
}
}

private IEnumerable<int> FindErrors(int startTime, int endTime, int armIndex, List<Instruction> instructions)
Expand Down
46 changes: 36 additions & 10 deletions Opus/UI/Rendering/SolutionRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Windows.Forms;
using Opus.Solution;
using static System.FormattableString;
using System.Drawing;

namespace Opus.UI.Rendering
{
Expand Down Expand Up @@ -68,11 +69,15 @@ private void RenderTracks()
var objects = Solution.GetObjects<Track>();
foreach (var obj in objects)
{
// To minimize scrollling, first try to get as much as possible of the path visible
Screen.Grid.EnsureCellsVisible(obj.GetBounds());

var pos = obj.GetWorldPosition();
Screen.Grid.EnsureCellVisible(pos);

var toolLocation = Sidebar.ScrollTo(Sidebar.Mechanisms, Sidebar.Mechanisms[obj.Type]);
var gridLocation = Screen.Grid.GetScreenLocationForCell(pos);

MouseUtils.LeftDrag(toolLocation, gridLocation);

RenderTrackPath(obj);
Expand All @@ -84,22 +89,43 @@ private void RenderTrackPath(Track track)
var trackPos = track.GetWorldPosition();
sm_log.Info(Invariant($"Rendering track at {trackPos}"));

// To minimize scrollling, first try to get as much as possible of the path visible
Screen.Grid.EnsureCellsVisible(track.GetBounds());

var position = trackPos.Add(track.Path.Skip(1).First());
var prevPosition = trackPos;
foreach (var offset in track.Path.Skip(1))
Screen.Grid.EnsureCellVisible(position, scrollToCenter: true); // scroll to center to minimise number of scrolls if it's a long track

Point location = Screen.Grid.GetScreenLocationForCell(trackPos.Add(track.Path.Skip(1).First()));
Point prevLocation = Screen.Grid.GetScreenLocationForCell(prevPosition);
Rectangle checkRect;

MouseUtils.LeftDrag(prevLocation, location, keepMouseDown: true);

prevPosition = position;

foreach (var offset in track.Path.Skip(2))
{
var position = trackPos.Add(offset);
Screen.Grid.EnsureCellVisible(position, scrollToCenter: true); // scroll to center to minimise number of scrolls if it's a long track
position = trackPos.Add(offset);
var scrollCheck = Screen.Grid.CheckCellsVisible(position, out checkRect, scrollToCenter: true); // scroll to center to minimise number of scrolls if it's a long track

// We need to recalculate both locations because the grid may have scrolled
var location = Screen.Grid.GetScreenLocationForCell(position);
var prevLocation = Screen.Grid.GetScreenLocationForCell(prevPosition);
MouseUtils.LeftDrag(prevLocation, location);
if (scrollCheck)
{
// Release mouse and then scroll;
MouseUtils.LeftUp();
Screen.Grid.ApplyCellsVisible(checkRect, scrollToCenter: true);
// We need to recalculate both locations because the grid has scrolled
prevLocation = Screen.Grid.GetScreenLocationForCell(prevPosition);
}
else
{
prevLocation = location;
}
location = Screen.Grid.GetScreenLocationForCell(position);
MouseUtils.LeftDrag(prevLocation, location, keepMouseDown: true);

prevPosition = position;
}

// Release the mouse when done.
MouseUtils.LeftUp();
}

private void RenderArms()
Expand Down
Loading