mirror of
https://github.com/haven1433/HexManiacAdvance.git
synced 2026-05-19 03:46:11 -05:00
483 lines
22 KiB
C#
483 lines
22 KiB
C#
using HavenSoft.HexManiac.Core.Models;
|
|
using HavenSoft.HexManiac.Core.Models.Runs;
|
|
using HavenSoft.HexManiac.Core.ViewModels.DataFormats;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Windows.Input;
|
|
|
|
namespace HavenSoft.HexManiac.Core.ViewModels {
|
|
public delegate (Point start, Point end) GetSelectionSpan(Point p);
|
|
|
|
public class JumpInfo {
|
|
public int ViewStart { get; }
|
|
public int SelectionStart { get; }
|
|
public ITabContent Tab { get; }
|
|
public JumpInfo(int viewStart, int selectionStart, ITabContent tab = null) {
|
|
(ViewStart, SelectionStart) = (viewStart, selectionStart);
|
|
Tab = tab;
|
|
}
|
|
}
|
|
|
|
public class Selection : ViewModelCore {
|
|
private const int DefaultPreferredWidth = 0x10;
|
|
|
|
private readonly IDataModel model;
|
|
private readonly ChangeHistory<ModelDelta> history;
|
|
private readonly GetSelectionSpan getSpan;
|
|
private readonly StubCommand
|
|
moveSelectionStart = new StubCommand(),
|
|
moveSelectionEnd = new StubCommand(),
|
|
gotoCommand = new StubCommand(),
|
|
resetAlignmentCommand = new StubCommand(),
|
|
forward = new StubCommand(),
|
|
backward = new StubCommand();
|
|
|
|
// these back/forward stacks are not encapsulated in a history object because we want to be able to change a remembered address each time we visit it.
|
|
// if we navigate back, then scroll, then navigate forward, we want to remember the scroll if we go back again.
|
|
private readonly Stack<JumpInfo> backStack = new Stack<JumpInfo>(), forwardStack = new Stack<JumpInfo>();
|
|
|
|
private int preferredWidth = DefaultPreferredWidth, maxWidth = 4;
|
|
|
|
private Point rawSelectionStart; // the actual click point
|
|
private Point selectionStart; // the calculated selection start, which may differ depending on the SelectionSpan
|
|
private Point rawSelectionEnd; // the actual release point
|
|
private Point selectionEnd; // the calculated selection end, which may differ depending on the SelectionSpan
|
|
|
|
public Point SelectionStart {
|
|
get => selectionStart;
|
|
set {
|
|
var index = Scroll.ViewPointToDataIndex(value);
|
|
var max = Scroll.IsSingleTableMode ? Scroll.DataLength - 1 : Scroll.DataLength;
|
|
value = Scroll.DataIndexToViewPoint(index.LimitToRange(Scroll.DataStart, max));
|
|
|
|
if (selectionStart.Equals(value)) return;
|
|
|
|
if (!Scroll.ScrollToPoint(ref value)) {
|
|
PreviewSelectionStartChanged?.Invoke(this, selectionStart);
|
|
}
|
|
|
|
rawSelectionStart = value;
|
|
rawSelectionEnd = value;
|
|
var (start, end) = getSpan(rawSelectionStart);
|
|
TryUpdate(ref selectionStart, start);
|
|
TryUpdate(ref selectionEnd, end, nameof(SelectionEnd));
|
|
}
|
|
}
|
|
|
|
public Point SelectionEnd {
|
|
get => selectionEnd;
|
|
set {
|
|
var index = Scroll.ViewPointToDataIndex(value);
|
|
var max = Scroll.IsSingleTableMode ? Scroll.DataLength - 1 : Scroll.DataLength;
|
|
value = Scroll.DataIndexToViewPoint(index.LimitToRange(Scroll.DataStart, max));
|
|
|
|
if (selectionEnd.Equals(value)) return;
|
|
|
|
Scroll.ScrollToPoint(ref value);
|
|
|
|
rawSelectionEnd = value;
|
|
var startIndex = Scroll.ViewPointToDataIndex(rawSelectionStart);
|
|
var endIndex = Scroll.ViewPointToDataIndex(rawSelectionEnd);
|
|
|
|
// case 1: start/end are the same
|
|
if (startIndex == endIndex) {
|
|
var (start, end) = getSpan(rawSelectionStart);
|
|
TryUpdate(ref selectionStart, start, nameof(SelectionStart));
|
|
TryUpdate(ref selectionEnd, end);
|
|
return;
|
|
}
|
|
|
|
// case 2: start < end
|
|
if (startIndex < endIndex) {
|
|
TryUpdate(ref selectionStart, getSpan(rawSelectionStart).start, nameof(SelectionStart));
|
|
TryUpdate(ref selectionEnd, getSpan(rawSelectionEnd).end);
|
|
return;
|
|
}
|
|
|
|
// case 3: start > end
|
|
if (startIndex > endIndex) {
|
|
TryUpdate(ref selectionEnd, getSpan(rawSelectionEnd).start);
|
|
TryUpdate(ref selectionStart, getSpan(rawSelectionStart).end, nameof(SelectionStart));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
public int PreferredWidth {
|
|
get => preferredWidth;
|
|
set {
|
|
if (TryUpdate(ref preferredWidth, value)) ChangeWidth(maxWidth);
|
|
}
|
|
}
|
|
|
|
private bool autoAdjustDataWidth = true, allowMultipleElementsPerLine = true;
|
|
public bool AutoAdjustDataWidth { get => autoAdjustDataWidth; set => Set(ref autoAdjustDataWidth, value); }
|
|
public bool AllowMultipleElementsPerLine { get => allowMultipleElementsPerLine; set => Set(ref allowMultipleElementsPerLine, value); }
|
|
|
|
public ICommand MoveSelectionStart => moveSelectionStart;
|
|
public ICommand MoveSelectionEnd => moveSelectionEnd;
|
|
public ICommand Goto => gotoCommand;
|
|
public ICommand ResetAlignment => resetAlignmentCommand;
|
|
public ICommand Forward => forward;
|
|
public ICommand Back => backward;
|
|
|
|
public ScrollRegion Scroll { get; }
|
|
|
|
public event EventHandler<ITabContent> RequestTabChanged;
|
|
public event EventHandler<string> OnError;
|
|
|
|
/// <summary>
|
|
/// The owner may have something special going on with the selected point.
|
|
/// Warn the owner before the selection changes, in case they need to do cleanup.
|
|
/// Note that this function is expected to pass the _old_ selection value
|
|
/// </summary>
|
|
public event EventHandler<Point> PreviewSelectionStartChanged;
|
|
|
|
public Selection(ScrollRegion scrollRegion, IDataModel model, ChangeHistory<ModelDelta> history, GetSelectionSpan getSpan = null) {
|
|
this.model = model;
|
|
this.history = history;
|
|
this.getSpan = getSpan ?? GetDefaultSelectionSpan;
|
|
Scroll = scrollRegion;
|
|
Scroll.ScrollChanged += (sender, e) => ShiftSelectionFromScroll(e);
|
|
|
|
moveSelectionStart.CanExecute = args => true;
|
|
moveSelectionStart.Execute = args => MoveSelectionStartExecuted((Direction)args);
|
|
moveSelectionEnd.CanExecute = args => true;
|
|
moveSelectionEnd.Execute = args => MoveSelectionEndExecuted((Direction)args);
|
|
resetAlignmentCommand.CanExecute = args => true;
|
|
resetAlignmentCommand.Execute = args => GotoAddressAndAlign(Scroll.DataIndex, 0x10);
|
|
|
|
gotoCommand = new StubCommand {
|
|
CanExecute = args => {
|
|
if (args is string str) {
|
|
str = str.Replace(PointerRun.PointerStart.ToString(), string.Empty).Replace(PointerRun.PointerEnd.ToString(), string.Empty).ToLower();
|
|
if (str == "null") return false;
|
|
}
|
|
return true;
|
|
},
|
|
Execute = args => {
|
|
if (args is int intArgs) args = intArgs.ToString("X6");
|
|
var address = args.ToString().Trim();
|
|
if (address.StartsWith(ViewPort.GotoMarker.ToString())) address = address.Substring(1);
|
|
if (address.StartsWith(PointerRun.PointerStart.ToString())) address = address.Substring(1);
|
|
if (address.EndsWith(PointerRun.PointerEnd.ToString())) address = address.Substring(0, address.Length - 1);
|
|
if (address.StartsWith("0x")) address = address.Substring(2);
|
|
var offset = 0;
|
|
if (address.Split("+") is string[] addParts && addParts.Length == 2) {
|
|
address = addParts[0];
|
|
int.TryParse(addParts[1], NumberStyles.HexNumber, CultureInfo.CurrentCulture, out offset);
|
|
} else if (address.Split("-") is string[] subtractParts && subtractParts.Length == 2) {
|
|
if (int.TryParse(subtractParts[1], NumberStyles.HexNumber, CultureInfo.CurrentCulture, out offset)) {
|
|
address = subtractParts[0];
|
|
offset = -offset;
|
|
}
|
|
}
|
|
using (ModelCacheScope.CreateScope(this.model)) {
|
|
var minSize = -1;
|
|
if (address.Contains("(") && address.Contains(")")) {
|
|
var addressParts = address.Split(new[] { '(', ')' }, StringSplitOptions.RemoveEmptyEntries);
|
|
address = addressParts[0];
|
|
if (addressParts.Length == 2) int.TryParse(addressParts[1], NumberStyles.HexNumber, CultureInfo.CurrentCulture, out minSize);
|
|
}
|
|
var anchor = this.model.GetAddressFromAnchor(new ModelDelta(), -1, address);
|
|
if (anchor == Pointer.NULL && minSize > 0) {
|
|
var newAnchorStart = model.FindFreeSpace(this.model.FreeSpaceStart, minSize);
|
|
this.model.ObserveAnchorWritten(history.CurrentChange, address, new NoInfoRun(newAnchorStart));
|
|
GotoAddress(newAnchorStart + offset);
|
|
} else if (anchor != Pointer.NULL) {
|
|
GotoAddress(anchor + offset);
|
|
} else if (int.TryParse(address, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out int result)) {
|
|
if (result >= BaseModel.PointerOffset) result -= BaseModel.PointerOffset;
|
|
GotoAddress(result + offset);
|
|
} else {
|
|
address = address.ToLower().Trim();
|
|
var options = model.GetExtendedAutocompleteOptions(address);
|
|
if (options.Count == 1) {
|
|
anchor = this.model.GetAddressFromAnchor(new ModelDelta(), -1, options[0]);
|
|
GotoAddress(anchor + offset);
|
|
} else if (options.Count > 1) {
|
|
var bestMatches = options.Where(option => option.ToLower().Contains(address)).ToList();
|
|
if (bestMatches.Count > 0) {
|
|
var shortestMatch = bestMatches.OrderBy(match => match.Split("/").Last().Length).First();
|
|
anchor = this.model.GetAddressFromAnchor(new ModelDelta(), -1, shortestMatch);
|
|
} else {
|
|
anchor = this.model.GetAddressFromAnchor(new ModelDelta(), -1, options[0]);
|
|
}
|
|
GotoAddress(anchor + offset);
|
|
} else {
|
|
OnError?.Invoke(this, $"Unable to goto address '{address}'");
|
|
}
|
|
}
|
|
}
|
|
},
|
|
};
|
|
backward = new StubCommand {
|
|
CanExecute = args => backStack.Count > 0,
|
|
Execute = args => {
|
|
if (backStack.Count == 0) return;
|
|
var selectionPoint = Scroll.ViewPointToDataIndex(SelectionStart);
|
|
if (selectionPoint < Scroll.DataIndex || selectionPoint > Scroll.DataIndex + Scroll.Width * Scroll.Height) selectionPoint = Scroll.DataIndex;
|
|
var backInfo = backStack.Pop();
|
|
if (backStack.Count == 0) backward.CanExecuteChanged.Invoke(backward, EventArgs.Empty);
|
|
if (backInfo.Tab != null) {
|
|
RequestTabChanged?.Invoke(this, backInfo.Tab);
|
|
return;
|
|
}
|
|
forwardStack.Push(new JumpInfo(Scroll.DataIndex, selectionPoint));
|
|
if (forwardStack.Count == 1) forward.CanExecuteChanged.Invoke(forward, EventArgs.Empty);
|
|
GotoAddressHelper(backInfo.ViewStart);
|
|
SelectionStart = Scroll.DataIndexToViewPoint(backInfo.SelectionStart);
|
|
},
|
|
};
|
|
forward = new StubCommand {
|
|
CanExecute = args => forwardStack.Count > 0,
|
|
Execute = args => {
|
|
if (forwardStack.Count == 0) return;
|
|
var selectionPoint = Scroll.ViewPointToDataIndex(SelectionStart);
|
|
if (selectionPoint < Scroll.DataIndex || selectionPoint > Scroll.DataIndex + Scroll.Width * Scroll.Height) selectionPoint = Scroll.DataIndex;
|
|
backStack.Push(new JumpInfo(Scroll.DataIndex, selectionPoint));
|
|
if (backStack.Count == 1) backward.CanExecuteChanged.Invoke(backward, EventArgs.Empty);
|
|
var forwardInfo = forwardStack.Pop();
|
|
GotoAddressHelper(forwardInfo.ViewStart);
|
|
SelectionStart = Scroll.DataIndexToViewPoint(forwardInfo.SelectionStart);
|
|
if (forwardStack.Count == 0) forward.CanExecuteChanged.Invoke(forward, EventArgs.Empty);
|
|
},
|
|
};
|
|
}
|
|
|
|
public bool IsSelected(Point point) {
|
|
if (point.X < 0 || point.X >= Scroll.Width) return false;
|
|
|
|
var selectionStart = Scroll.ViewPointToDataIndex(SelectionStart);
|
|
var selectionEnd = Scroll.ViewPointToDataIndex(SelectionEnd);
|
|
var middle = Scroll.ViewPointToDataIndex(point);
|
|
|
|
var leftEdge = Math.Min(selectionStart, selectionEnd);
|
|
var rightEdge = Math.Max(selectionStart, selectionEnd);
|
|
|
|
return leftEdge <= middle && middle <= rightEdge;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changing the scrollregion's width visibly moves the selection.
|
|
/// But if we updated the selection using SelectionStart and SelectionEnd, it would auto-scroll.
|
|
/// </summary>
|
|
public void ChangeWidth(int newWidth) {
|
|
maxWidth = newWidth;
|
|
var rawStart = Scroll.ViewPointToDataIndex(rawSelectionStart);
|
|
var rawEnd = Scroll.ViewPointToDataIndex(rawSelectionEnd);
|
|
var start = Scroll.ViewPointToDataIndex(selectionStart);
|
|
var end = Scroll.ViewPointToDataIndex(selectionEnd);
|
|
|
|
Scroll.Width = CoerceWidth(newWidth);
|
|
|
|
rawSelectionStart = Scroll.DataIndexToViewPoint(rawStart);
|
|
rawSelectionEnd = Scroll.DataIndexToViewPoint(rawEnd);
|
|
selectionStart = Scroll.DataIndexToViewPoint(start);
|
|
selectionEnd = Scroll.DataIndexToViewPoint(end);
|
|
}
|
|
|
|
public void GotoAddress(int address) {
|
|
if (address >= model.Count || address < 0) {
|
|
OnError?.Invoke(this, $"Address {address:X2} is not within the size of the data.");
|
|
return;
|
|
}
|
|
|
|
var selectionPoint = Scroll.ViewPointToDataIndex(SelectionStart);
|
|
if (selectionPoint < Scroll.DataIndex || selectionPoint > Scroll.DataIndex + Scroll.Width * Scroll.Height) selectionPoint = Scroll.DataIndex;
|
|
backStack.Push(new JumpInfo(Scroll.DataIndex, selectionPoint));
|
|
if (backStack.Count == 1) backward.CanExecuteChanged.Invoke(backward, EventArgs.Empty);
|
|
if (forwardStack.Count > 0) {
|
|
forwardStack.Clear();
|
|
forward.CanExecuteChanged.Invoke(forward, EventArgs.Empty);
|
|
}
|
|
GotoAddressHelper(address);
|
|
|
|
// if we didn't actually move anything, don't put the jump point in the back-stack
|
|
var jump = backStack.Peek();
|
|
if (Scroll.DataIndex == jump.ViewStart && Scroll.ViewPointToDataIndex(SelectionStart) == jump.SelectionStart) {
|
|
backStack.Pop();
|
|
}
|
|
}
|
|
|
|
public void SetJumpBackTab(ITabContent tab) => backStack.Push(new JumpInfo(-1, -1, tab));
|
|
|
|
public void SetJumpBackPoint(int address) {
|
|
if (backStack.Count > 0) backStack.Pop();
|
|
backStack.Push(new JumpInfo(address, address));
|
|
}
|
|
|
|
private static (Point start, Point end) GetDefaultSelectionSpan(Point p) => (p, p);
|
|
|
|
private void GotoAddressHelper(int address) {
|
|
var destinationRun = model.GetNextRun(address) as ITableRun;
|
|
var destinationIsArray = destinationRun != null && destinationRun.Start <= address;
|
|
int preferredWidth = destinationIsArray ? destinationRun.ElementLength : DefaultPreferredWidth;
|
|
GotoAddressAndAlign(address, preferredWidth, destinationIsArray ? destinationRun.Start : 0);
|
|
}
|
|
|
|
private void GotoAddressAndAlign(int address, int preferredWidth, int tableStart = 0) {
|
|
if (address < Scroll.DataStart || Scroll.DataLength < address) {
|
|
Scroll.ClearTableMode();
|
|
Debug.Assert(Scroll.DataLength == model.Count, "I forgot to update the Scroll.DataLength after expanding the data!");
|
|
}
|
|
|
|
var startAddress = address;
|
|
if (preferredWidth > 1) address -= (address - tableStart) % preferredWidth;
|
|
|
|
// first, change the scroll to view the actual requested address
|
|
Scroll.ScrollValue += Scroll.DataIndexToViewPoint(startAddress).Y;
|
|
|
|
// then, scroll left/right as needed to align everything
|
|
var lastScroll = int.MinValue;
|
|
while (Scroll.DataIndex < address && Scroll.DataIndex != lastScroll) {
|
|
lastScroll = Scroll.DataIndex;
|
|
Scroll.Scroll.Execute(Direction.Right);
|
|
}
|
|
lastScroll = int.MinValue;
|
|
while (Scroll.DataIndex > address && Scroll.DataIndex != lastScroll) {
|
|
lastScroll = Scroll.DataIndex;
|
|
Scroll.Scroll.Execute(Direction.Left);
|
|
}
|
|
|
|
// update the width
|
|
if (autoAdjustDataWidth) PreferredWidth = preferredWidth;
|
|
|
|
// finally, update the selection
|
|
SelectionStart = Scroll.DataIndexToViewPoint(startAddress);
|
|
|
|
if (model.GetNextRun(startAddress) is ITableRun tableRun && tableRun.Start <= startAddress) {
|
|
Scroll.SetTableMode(tableRun.Start, tableRun.Length);
|
|
} else {
|
|
Scroll.ClearTableMode();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// When the scrolling changes, the selection has to move as well.
|
|
/// This is because the selection is in terms of the viewPort, not the overall data.
|
|
/// Nothing in this method notifies because any amount of scrolling means we already need a complete redraw.
|
|
/// </summary>
|
|
private void ShiftSelectionFromScroll(int distance) {
|
|
var rawStart = Scroll.ViewPointToDataIndex(rawSelectionStart);
|
|
var rawEnd = Scroll.ViewPointToDataIndex(rawSelectionEnd);
|
|
var start = Scroll.ViewPointToDataIndex(selectionStart);
|
|
var end = Scroll.ViewPointToDataIndex(selectionEnd);
|
|
|
|
rawStart -= distance;
|
|
rawEnd -= distance;
|
|
start -= distance;
|
|
end -= distance;
|
|
|
|
rawSelectionStart = Scroll.DataIndexToViewPoint(rawStart);
|
|
rawSelectionEnd = Scroll.DataIndexToViewPoint(rawEnd);
|
|
selectionStart = Scroll.DataIndexToViewPoint(start);
|
|
selectionEnd = Scroll.DataIndexToViewPoint(end);
|
|
}
|
|
|
|
private void MoveSelectionStartExecuted(Direction direction) {
|
|
Point dif;
|
|
if (direction == Direction.PageUp) {
|
|
dif = new Point(0, -Scroll.Height);
|
|
} else if (direction == Direction.PageDown) {
|
|
dif = new Point(0, Scroll.Height);
|
|
} else if (direction == Direction.Home) {
|
|
var currentStart = Scroll.ViewPointToDataIndex(selectionStart);
|
|
var currentRun = model.GetNextRun(currentStart);
|
|
if (currentRun.Start > currentStart) {
|
|
dif = new Point(-selectionStart.X, 0); // no run -> jump to start of line
|
|
} else {
|
|
var newStart = currentRun.Start;
|
|
dif = Scroll.DataIndexToViewPoint(newStart) - selectionStart;
|
|
}
|
|
} else if (direction == Direction.End) {
|
|
var currentStart = Scroll.ViewPointToDataIndex(selectionStart);
|
|
var currentRun = model.GetNextRun(currentStart);
|
|
if (currentRun.Start > currentStart) {
|
|
dif = new Point(Scroll.Width - 1 - selectionStart.X, 0); // no run -> jump to end of line
|
|
} else {
|
|
var newStart = currentRun.Start + currentRun.Length - 1;
|
|
dif = Scroll.DataIndexToViewPoint(newStart) - selectionStart;
|
|
}
|
|
} else {
|
|
dif = ScrollRegion.DirectionToDif[direction];
|
|
}
|
|
|
|
var (start, end) = getSpan(rawSelectionEnd);
|
|
if (dif.X < 0 || dif.Y < 0 || direction == Direction.End) {
|
|
// start from the _front_ of selectionEnd
|
|
SelectionStart = start + dif;
|
|
} else {
|
|
// start from the _back_ of selectionEnd
|
|
SelectionStart = end + dif;
|
|
}
|
|
}
|
|
|
|
private void MoveSelectionEndExecuted(Direction direction) {
|
|
Point dif;
|
|
if (direction == Direction.PageUp) {
|
|
dif = new Point(0, -Scroll.Height);
|
|
} else if (direction == Direction.PageDown) {
|
|
dif = new Point(0, Scroll.Height);
|
|
} else if (direction == Direction.Home) {
|
|
var currentStart = Scroll.ViewPointToDataIndex(selectionEnd);
|
|
var currentRun = model.GetNextRun(currentStart);
|
|
if (currentRun.Start > currentStart) {
|
|
dif = new Point(-selectionEnd.X, 0); // no run -> jump to start of line
|
|
} else {
|
|
var newStart = currentRun.Start;
|
|
dif = Scroll.DataIndexToViewPoint(newStart) - selectionEnd;
|
|
}
|
|
} else if (direction == Direction.End) {
|
|
var currentStart = Scroll.ViewPointToDataIndex(selectionEnd);
|
|
var currentRun = model.GetNextRun(currentStart);
|
|
if (currentRun.Start > currentStart) {
|
|
dif = new Point(Scroll.Width - 1 - selectionEnd.X, 0); // no run -> jump to end of line
|
|
} else {
|
|
var newStart = currentRun.Start + currentRun.Length - 1;
|
|
dif = Scroll.DataIndexToViewPoint(newStart) - selectionEnd;
|
|
}
|
|
} else {
|
|
dif = ScrollRegion.DirectionToDif[direction];
|
|
}
|
|
|
|
var (start, end) = getSpan(rawSelectionEnd);
|
|
if (dif.X > 0 || dif.Y > 0 || direction == Direction.Home) {
|
|
// start from the _back_ of selectionEnd
|
|
SelectionEnd = end + dif;
|
|
} else {
|
|
// start from the _front_ of selectionEnd
|
|
SelectionEnd = start + dif;
|
|
}
|
|
}
|
|
|
|
private int CoerceWidth(int width) {
|
|
var desiredWidth = preferredWidth.LimitToRange(1, 0x100);
|
|
if (preferredWidth == -1 || preferredWidth == width) return width;
|
|
if (!allowMultipleElementsPerLine) return desiredWidth;
|
|
|
|
if (desiredWidth < width) {
|
|
int multiple = 2;
|
|
while (desiredWidth * multiple <= width) multiple++;
|
|
return desiredWidth * (multiple - 1);
|
|
}
|
|
var divisors = GetDivisors(desiredWidth).Reverse();
|
|
var newWidth = divisors.FirstOrDefault();
|
|
if (newWidth < 4) return desiredWidth;
|
|
return newWidth;
|
|
}
|
|
|
|
private static IEnumerable<int> GetDivisors(int number) {
|
|
// only actually allow for divisors if the preferred width is 0x10
|
|
if (number == 16) {
|
|
for (int i = 1; i <= number / 2; i++) {
|
|
if (number % i == 0) yield return i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|