diff --git a/src/HexManiac.Core/ViewModels/ImageEditorViewModel.cs b/src/HexManiac.Core/ViewModels/ImageEditorViewModel.cs index edad6353..d812e0c1 100644 --- a/src/HexManiac.Core/ViewModels/ImageEditorViewModel.cs +++ b/src/HexManiac.Core/ViewModels/ImageEditorViewModel.cs @@ -99,7 +99,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels { public event EventHandler RefreshSelection; private void RaiseRefreshSelection(params Point[] toSelect) { selectedPixels = new bool[PixelWidth, PixelHeight]; - foreach (var s in toSelect) selectedPixels[s.X, s.Y] = true; + foreach (var s in toSelect) { + if (WithinImage(s)) selectedPixels[s.X, s.Y] = true; + } RefreshSelection?.Invoke(this, EventArgs.Empty); } @@ -118,6 +120,11 @@ namespace HavenSoft.HexManiac.Core.ViewModels { public int SpritePointer { get; } + private StubCommand setCursorSize; + public ICommand SetCursorSize => StubCommand(ref setCursorSize, arg => CursorSize = int.Parse(arg)); + private int cursorSize = 1; + public int CursorSize { get => cursorSize; set => Set(ref cursorSize, value, arg => BlockPreview.Clear()); } + public ImageEditorViewModel(ChangeHistory history, IDataModel model, int address) { this.history = history; this.model = model; @@ -262,6 +269,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { SelectedTool = ImageEditorTools.Draw; } BlockPreview.Clear(); + if (CursorSize == 0) CursorSize = 1; break; case nameof(sc.Color): Palette.PushColorsToModel(); // this causes a Render @@ -362,20 +370,20 @@ namespace HavenSoft.HexManiac.Core.ViewModels { point = parent.ToSpriteSpace(point); if (parent.WithinImage(point)) { var tile = parent.eyeDropperStrategy.Tile; - if (tile == null) { - drawSize = 1; - drawPoint = point; - parent.PixelData[parent.PixelIndex(point)] = element.Color; - parent.pixels[point.X, point.Y] = element.Index; + if (tile == null || !parent.BlockPreview.Enabled) { + drawSize = parent.CursorSize; + tile = new int[drawSize, drawSize]; + for (int x = 0; x < drawSize; x++) for (int y = 0; y < drawSize; y++) tile[x, y] = element.Index; } else { drawSize = tile.GetLength(0); - drawPoint = new Point(point.X - point.X % drawSize, point.Y - point.Y % drawSize); - for (int x = 0; x < drawSize; x++) { - for (int y = 0; y < drawSize; y++) { - var (xx, yy) = (drawPoint.X + x, drawPoint.Y + y); - parent.PixelData[parent.PixelIndex(xx, yy)] = parent.Palette.Elements[tile[x, y]].Color; - parent.pixels[xx, yy] = tile[x, y]; - } + } + + drawPoint = new Point(point.X - point.X % drawSize, point.Y - point.Y % drawSize); + for (int x = 0; x < drawSize; x++) { + for (int y = 0; y < drawSize; y++) { + var (xx, yy) = (drawPoint.X + x, drawPoint.Y + y); + parent.PixelData[parent.PixelIndex(xx, yy)] = parent.Palette.Elements[tile[x, y]].Color; + parent.pixels[xx, yy] = tile[x, y]; } } parent.NotifyPropertyChanged(nameof(PixelData)); @@ -388,13 +396,13 @@ namespace HavenSoft.HexManiac.Core.ViewModels { point = parent.ToSpriteSpace(point); if (parent.WithinImage(point)) { var tile = parent.eyeDropperStrategy.Tile; - if (tile == null) { - drawPoint = point; - drawSize = 1; + if (tile == null || !parent.BlockPreview.Enabled) { + drawSize = parent.CursorSize; } else { drawSize = tile.GetLength(0); - drawPoint = new Point(point.X - point.X % drawSize, point.Y - point.Y % drawSize); } + + drawPoint = new Point(point.X - point.X % drawSize, point.Y - point.Y % drawSize); } else { drawPoint = default; drawSize = 0; @@ -583,8 +591,6 @@ namespace HavenSoft.HexManiac.Core.ViewModels { public EyeDropperTool(ImageEditorViewModel parent) => this.parent = parent; - public bool ShowSelectionRect(Point subPixelPosition) => false; - public void ToolDown(Point point) { underPixels = null; // old selection lost selectionStart = parent.ToSpriteSpace(point); @@ -630,6 +636,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { for (int x = 0; x < selectionWidth; x++) for (int y = 0; y < selectionHeight; y++) { underPixels[x, y] = parent.pixels[selectionStart.X + x, selectionStart.Y + y]; } + parent.CursorSize = 0; parent.BlockPreview.Set(parent.PixelData, parent.PixelWidth, selectionStart, selectionWidth); } } diff --git a/src/HexManiac.Tests/ImageEditorTests.cs b/src/HexManiac.Tests/ImageEditorTests.cs index f57ba9a7..0eeac352 100644 --- a/src/HexManiac.Tests/ImageEditorTests.cs +++ b/src/HexManiac.Tests/ImageEditorTests.cs @@ -505,6 +505,7 @@ namespace HavenSoft.HexManiac.Tests { Assert.Equal(2, editor.BlockPreview.PixelHeight); Assert.Equal(4, editor.BlockPreview.PixelData.Length); Assert.All(4.Range(), i => Assert.Equal(Rgb(31, 31, 31), editor.BlockPreview.PixelData[i])); + Assert.Equal(0, editor.CursorSize); } [Fact] @@ -560,5 +561,36 @@ namespace HavenSoft.HexManiac.Tests { Assert.False(editor.ShowSelectionRect(4, 2)); } + + [Fact] + public void Draw_SetCursorSize_LargeCursor() { + editor.SelectedTool = ImageEditorTools.Draw; + + editor.SetCursorSize.Execute("2"); + editor.Hover(default); + + Assert.All( new[] { + new Point(4, 4), + new Point(4, 5), + new Point(5, 4), + new Point(5, 5), + }, p => Assert.True(editor.ShowSelectionRect(p))); + } + + [Fact] + public void EyeDropperBlock_SelectColor_DrawSinglePixel() { + editor.EyeDropperDown(0, 0); + editor.Hover(1, 1); + editor.EyeDropperUp(1, 1); + + editor.Palette.SelectionStart = 1; + editor.Hover(0, 0); + + Assert.All(new[] { + new Point(4, 5), + new Point(5, 4), + new Point(5, 5), + }, p => Assert.False(editor.ShowSelectionRect(p))); + } } } diff --git a/src/HexManiac.WPF/Controls/ImageEditorView.xaml b/src/HexManiac.WPF/Controls/ImageEditorView.xaml index 996c7db4..e89b64dd 100644 --- a/src/HexManiac.WPF/Controls/ImageEditorView.xaml +++ b/src/HexManiac.WPF/Controls/ImageEditorView.xaml @@ -23,6 +23,19 @@ + + + - @@ -136,7 +138,30 @@ - + + + + + + + + + + + + diff --git a/src/HexManiac.WPF/Controls/TileImage.cs b/src/HexManiac.WPF/Controls/TileImage.cs index 4c16938c..28e90742 100644 --- a/src/HexManiac.WPF/Controls/TileImage.cs +++ b/src/HexManiac.WPF/Controls/TileImage.cs @@ -77,7 +77,7 @@ namespace HavenSoft.HexManiac.WPF.Controls { public class EqualityToBooleanConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - return Equals(value, parameter); + return Equals(value.ToString(), parameter.ToString()); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { @@ -85,6 +85,23 @@ namespace HavenSoft.HexManiac.WPF.Controls { } } + public class BooleanToVisibilityConverter : IValueConverter { + private readonly System.Windows.Controls.BooleanToVisibilityConverter core = new System.Windows.Controls.BooleanToVisibilityConverter(); + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { + if (value is bool b) { + if (parameter == null) return b ? Visibility.Visible : Visibility.Hidden; + if (parameter is Visibility v && v == Visibility.Hidden) return b ? Visibility.Visible : Visibility.Hidden; + } + return core.Convert(value, targetType, parameter, culture); + } + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { + if (value is Visibility vis) { + return vis == Visibility.Visible; + } + return core.ConvertBack(value, targetType, parameter, culture); + } + } + public class PixelImage : Image { private IPixelViewModel ViewModel => DataContext as IPixelViewModel; diff --git a/src/HexManiac.WPF/Windows/App.xaml b/src/HexManiac.WPF/Windows/App.xaml index 0a580a4a..b608fbac 100644 --- a/src/HexManiac.WPF/Windows/App.xaml +++ b/src/HexManiac.WPF/Windows/App.xaml @@ -4,7 +4,7 @@ xmlns:hsc="clr-namespace:HavenSoft.HexManiac.WPF.Controls" xmlns:hsv="clr-namespace:HavenSoft.HexManiac.WPF.Resources"> - +