From 4d4c678548ced566dbb4358e76c28bba60197e2c Mon Sep 17 00:00:00 2001 From: Juan Segura Date: Sun, 1 Jun 2025 17:14:45 +0200 Subject: [PATCH 1/7] Undo & Redo working in sprite editor --- .../ZXGraphics/PaletteBuilderDialog.axaml | 19 +- .../ZXGraphics/PaletteBuilderDialog.axaml.cs | 173 +++++++++++++++++- .../ZXGraphics/SpriteEditor.axaml | 4 +- .../ZXGraphics/SpriteEditor.axaml.cs | 38 +--- .../ZXGraphics/SpritePatternEditor.axaml.cs | 125 ++++++++++--- 5 files changed, 291 insertions(+), 68 deletions(-) diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/PaletteBuilderDialog.axaml b/ZXBStudio/DocumentEditors/ZXGraphics/PaletteBuilderDialog.axaml index 905fc94..2e60542 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/PaletteBuilderDialog.axaml +++ b/ZXBStudio/DocumentEditors/ZXGraphics/PaletteBuilderDialog.axaml @@ -47,7 +47,7 @@ Convert to current palette - + Append new colors to current palette @@ -78,10 +78,12 @@ - + + + @@ -93,10 +95,21 @@ - + + + + + + + + diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/PaletteBuilderDialog.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/PaletteBuilderDialog.axaml.cs index 123c6ed..251c34b 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/PaletteBuilderDialog.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/PaletteBuilderDialog.axaml.cs @@ -33,6 +33,7 @@ using Avalonia.Controls.Shapes; using static CommunityToolkit.Mvvm.ComponentModel.__Internals.__TaskExtensions.TaskAwaitableWithoutEndValidation; using FFmpeg.AutoGen; +using System.Diagnostics; namespace ZXBasicStudio.DocumentEditors.ZXGraphics { @@ -44,6 +45,7 @@ public partial class PaletteBuilderDialog : Window, IDisposable private Rectangle[] rectangulos = new Rectangle[256]; private PaletteColor[] palette = null; + private PaletteColor[] palette512 = null; private string sourceFile = null; private string convertedFile = null; private bool imgSourceLoaded = false; @@ -68,8 +70,9 @@ public PaletteBuilderDialog() // Set the palette palette = ServiceLayer.GetPalette(GraphicsModes.Next); - selectedColorIndex = 0; + selectedColorIndex = 0; DrawPalette(); + DrawColorPicker(); btnFileSource.Tapped += BtnFileSource_Tapped; btnResetPalette.Click += BtnResetPalette_Click; @@ -85,7 +88,10 @@ public PaletteBuilderDialog() btnRefresh.Click += BtnRefresh_Click; btnSaveImage.Click += BtnSaveImage_Click; + btnColorPicker.Click += BtnColorPicker_Click; + btnClose.Click += BtnClose_Click; + } @@ -893,5 +899,170 @@ private void CrearPaletaGPL9bits() } #endregion + + + #region Color Picker + + private int Circles = 8; //16; + private int Sectors = 32; + private double Radius = 200; + + private void BtnColorPicker_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + { + grdColorPicker.IsVisible = true; + } + + + private void DrawColorPicker() + { + Create512Palette(); + + ColorWheelCanvas.Children.Clear(); + double ringWidth = Radius / Circles; + double angleStep = 360.0 / Sectors; + double cx = Radius; + double cy = Radius; + + for (int circle = 0; circle < Circles; circle++) + { + double r1 = ringWidth * circle; + double r2 = ringWidth * (circle + 1); + double value = (circle + 1) / (double)Circles; + + for (int sector = 0; sector < Sectors; sector++) + { + double hue = sector * angleStep; + Color color = FromHSV(hue, 1.0, value); + int idx=ServiceLayer.GetColor(color.R, color.G, color.B, palette512, 5); + var p = palette512[idx]; + var color512=Color.FromRgb(p.Red, p.Green, p.Blue); + DrawSegment(cx, cy, r1, r2, hue, angleStep, color512); + } + } + } + + private void DrawSegment(double cx, double cy, double r1, double r2, double startAngle, double angleSize, Color color) + { + var startRad = Math.PI * startAngle / 180.0; + var endRad = Math.PI * (startAngle + angleSize) / 180.0; + + var p1 = new Point(cx + r1 * Math.Cos(startRad), cy + r1 * Math.Sin(startRad)); + var p2 = new Point(cx + r2 * Math.Cos(startRad), cy + r2 * Math.Sin(startRad)); + var p3 = new Point(cx + r2 * Math.Cos(endRad), cy + r2 * Math.Sin(endRad)); + var p4 = new Point(cx + r1 * Math.Cos(endRad), cy + r1 * Math.Sin(endRad)); + + var path = new Avalonia.Controls.Shapes.Path + { + Fill = new SolidColorBrush(color), + StrokeThickness = 0, + Data = new PathGeometry + { + Figures = new PathFigures + { + new PathFigure + { + StartPoint = p1, + Segments = new PathSegments + { + new LineSegment { Point = p2 }, + new ArcSegment + { + Point = p3, + Size = new Size(r2, r2), + SweepDirection = SweepDirection.Clockwise, + IsLargeArc = angleSize > 180 + }, + new LineSegment { Point = p4 }, + new ArcSegment + { + Point = p1, + Size = new Size(r1, r1), + SweepDirection = SweepDirection.CounterClockwise, + IsLargeArc = angleSize > 180 + } + } + } + } + } + }; + + ColorWheelCanvas.Children.Add(path); + } + + private void ColorWheelCanvas_PointerPressed(object? sender, PointerPressedEventArgs e) + { + var pos = e.GetPosition(ColorWheelCanvas); + double dx = pos.X - Radius; + double dy = pos.Y - Radius; + double distance = Math.Sqrt(dx * dx + dy * dy); + if (distance > Radius) return; + + double angle = Math.Atan2(dy, dx) * 180 / Math.PI; + if (angle < 0) angle += 360; + + int sector = (int)(angle / (360.0 / Sectors)); + int circle = (int)(distance / (Radius / Circles)); + + double hue = sector * (360.0 / Sectors); + double value = (circle + 1) / (double)Circles; + Color color = FromHSV(hue, 1.0, value); + + SelectedColorPreview.Fill = new SolidColorBrush(color); + } + + private static Color FromHSV(double hue, double saturation, double value) + { + double c = value * saturation; + double x = c * (1 - Math.Abs((hue / 60.0 % 2) - 1)); + double m = value - c; + + double r = 0, g = 0, b = 0; + + if (hue < 60) { r = c; g = x; } + else if (hue < 120) { r = x; g = c; } + else if (hue < 180) { g = c; b = x; } + else if (hue < 240) { g = x; b = c; } + else if (hue < 300) { r = x; b = c; } + else { r = c; b = x; } + + byte R = (byte)((r + m) * 255); + byte G = (byte)((g + m) * 255); + byte B = (byte)((b + m) * 255); + + return Color.FromRgb(R, G, B); + } + + private void Create512Palette() + { + palette512 = new PaletteColor[512]; + for (int i = 0; i < 512; i++) + { + int r = ((i & 0b111000000) >> 6)*36; + int g = ((i & 0b000111000) >> 3)*36; + int b = (i & 0b000000111)*36; + if (r > 250) + { + r = 255; + } + if (g > 250) + { + g = 255; + } + if (b > 250) + { + b = 255; + } + + palette512[i] = new PaletteColor() + { + Red = (byte)r, + Green = (byte)g, + Blue = (byte)b, + HasPriority = false + }; + } + } + + #endregion } } diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml index 94da8c8..cf09cf0 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml @@ -27,10 +27,10 @@ - - diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs index d3a2939..821f132 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs @@ -262,37 +262,7 @@ private void _Initialize(string fileName) Sprite[] sprites = dataS.Deserializar(); foreach (var sprite in sprites) - { - /* - // Check attributes for ZX Spectrum mode - if (sprite != null && sprite.GraphicMode == GraphicsModes.ZXSpectrum) - { - // Force ZX Spectrum palette - sprite.Palette = ServiceLayer.GetPalette(GraphicsModes.ZXSpectrum); - foreach (var pattern in sprite.Patterns) - { - int cW = sprite.Width / 8; - int cH = sprite.Height / 8; - int l = cW * cH; - if (pattern.Attributes == null) - { - pattern.Attributes = new AttributeColor[cW * cH]; - for (int n = 0; n < pattern.Attributes.Length; n++) - { - pattern.Attributes[n] = new AttributeColor() - { - Ink = 1, - Paper = 0 - }; - } - } - if (pattern.Attributes.Length != l) - { - pattern.Attributes = pattern.Attributes.Take(l).ToArray(); - } - } - } - */ + { // Check attributes for ZX Spectrum mode if (sprite != null && sprite.Patterns != null) { @@ -357,7 +327,7 @@ private void _Initialize(string fileName) btnRedo.Tapped += BtnRedo_Tapped; btnInvertColorsCell.Tapped += BtnInvertColorsCell_Tapped; btnInvertPixelsCell.Tapped += BtnInvertPixelsCell_Tapped; - + Refresh(); if (SpritePatternsList.Count > 1) @@ -1062,13 +1032,13 @@ private void BtnInvertColorsCell_Tapped(object? sender, TappedEventArgs e) private void BtnRedo_Tapped(object? sender, TappedEventArgs e) { - + ctrlEditor.Redo(); } private void BtnUndo_Tapped(object? sender, TappedEventArgs e) { - + ctrlEditor.Undo(); } #endregion diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs index dbc142c..4e818da 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs @@ -10,6 +10,7 @@ using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; +using System.Threading.Tasks.Sources; using System.Xml.Schema; using ZXBasicStudio.Common; using ZXBasicStudio.DocumentEditors.ZXGraphics.log; @@ -46,6 +47,7 @@ public Sprite SpriteData lastId = _SpriteData.Id; } } + Undo_AddPoint(); Refresh(true); } } @@ -168,44 +170,100 @@ public void Refresh(bool callBack = false) #region Undo and Redo - private List operations = new List(); - private List operationsDeleted = new List(); + private List operations = new List(); + private int operationIndex = -1; public void Undo() { - // Get last operation - var op = operations.LastOrDefault(); + if (operationIndex > operations.Count - 1) + { + operationIndex = operations.Count - 1; + } + if (operationIndex < 0) + { + return; + } + var op = operations[operationIndex]; + if (Pattern_Equals(SpriteData.Patterns[SpriteData.CurrentFrame], op)) + { + operationIndex--; + Undo(); + return; + } if (op != null) { - // Move from operations to operationsDeleted - operations.Remove(op); - // Restore point value (Undo) - SetPoint(op.X, op.Y, op.ColorIndex); - // Remove new operation (Undo don't add operation) - var op2 = operations.LastOrDefault(); - if (op2 != null) - { - operations.Remove(op2); - operationsDeleted.Add(op2); - } + SpriteData.Patterns[SpriteData.CurrentFrame] = op; + Refresh(); } } public void Redo() { - // Get last undo operation - var op = operationsDeleted.LastOrDefault(); + if (operationIndex > operations.Count - 1) + { + return; + } + if (operationIndex < 0) + { + return; + } + var op = operations[operationIndex]; + if (Pattern_Equals(SpriteData.Patterns[SpriteData.CurrentFrame], op)) + { + operationIndex++; + Redo(); + return; + } if (op != null) { - // Delete from operationsDeleted - operationsDeleted.Remove(op); - // Set point value (Undo) - SetPoint(op.X, op.Y, op.ColorIndex); + SpriteData.Patterns[SpriteData.CurrentFrame] = op; + Refresh(); } } + private void Undo_AddPoint() + { + try + { + if (SpriteData == null || SpriteData.Patterns == null) + { + operations.Clear(); + return; + } + + if (operationIndex >= 0 && + operationIndex < operations.Count) + { + var lastOp = operations[operationIndex]; + if (Pattern_Equals(SpriteData.Patterns[SpriteData.CurrentFrame], lastOp)) + { + return; // No changes + } + } + operations = operations.Take(operationIndex+1).ToList(); + + var op = SpriteData.Patterns[SpriteData.CurrentFrame].Clonar(); + operations.Add(op); + operationIndex = operations.Count - 1; + } + catch (Exception ex) + { + Debug.WriteLine($"Error in Undo_AddPoint: {ex.Message}"); + } + } + + private bool Pattern_Equals(Pattern p1, Pattern p2) + { + if (p1 == null || p2 == null) + { + return false; + } + var pc1 = p1.Serializar(); + var pc2 = p2.Serializar(); + return pc1 == pc2; + } #endregion @@ -302,13 +360,6 @@ private void SetPoint(double mx, double my, int value) } int dir = (SpriteData.Width * y) + x; - operations.Add(new Operation() - { - ColorIndex = SpriteData.Patterns[SpriteData.CurrentFrame].RawData[dir], - X = (int)mx, - Y = (int)my - }); - var sprite = SpriteData.Patterns[SpriteData.CurrentFrame]; switch (SpriteData.GraphicMode) @@ -331,6 +382,7 @@ private void SetPoint(double mx, double my, int value) break; } + Undo_AddPoint(); Refresh(false); if (tmr == null) @@ -409,6 +461,7 @@ public void Clear() { SpriteData.Patterns[SpriteData.CurrentFrame].RawData[n] = SecondaryColorIndex; } + Undo_AddPoint(); Refresh(true); } @@ -418,6 +471,7 @@ public void Clear() /// public void Cut() { + Undo_AddPoint(); Copy(); Clear(); } @@ -514,6 +568,7 @@ public async void Paste() } } } + Undo_AddPoint(); Refresh(true); } @@ -536,6 +591,7 @@ public void HorizontalMirror() } } SpriteData.Patterns[SpriteData.CurrentFrame] = pat2; + Undo_AddPoint(); Refresh(true); } @@ -558,6 +614,7 @@ public void VerticalMirror() } } SpriteData.Patterns[SpriteData.CurrentFrame] = pat2; + Undo_AddPoint(); Refresh(true); } @@ -587,6 +644,7 @@ public void RotateLeft() } } SpriteData.Patterns[SpriteData.CurrentFrame] = pattern2; + Undo_AddPoint(); Refresh(true); } @@ -616,6 +674,7 @@ public void RotateRight() } } SpriteData.Patterns[SpriteData.CurrentFrame] = pattern2; + Undo_AddPoint(); Refresh(true); } @@ -644,6 +703,7 @@ public void ShiftUp() } } SpriteData.Patterns[SpriteData.CurrentFrame] = pattern2; + Undo_AddPoint(); Refresh(true); } @@ -672,6 +732,7 @@ public void ShiftRight() } } SpriteData.Patterns[SpriteData.CurrentFrame] = pattern2; + Undo_AddPoint(); Refresh(true); } @@ -700,6 +761,7 @@ public void ShiftDown() } } SpriteData.Patterns[SpriteData.CurrentFrame] = pattern2; + Undo_AddPoint(); Refresh(true); } @@ -728,6 +790,7 @@ public void ShiftLeft() } } SpriteData.Patterns[SpriteData.CurrentFrame] = pattern2; + Undo_AddPoint(); Refresh(true); } @@ -757,6 +820,7 @@ public void MoveUp() } } SpriteData.Patterns[SpriteData.CurrentFrame] = pattern2; + Undo_AddPoint(); Refresh(true); } @@ -786,6 +850,7 @@ public void MoveRight() } } SpriteData.Patterns[SpriteData.CurrentFrame] = pattern2; + Undo_AddPoint(); Refresh(true); } @@ -815,6 +880,7 @@ public void MoveDown() } } SpriteData.Patterns[SpriteData.CurrentFrame] = pattern2; + Undo_AddPoint(); Refresh(true); } @@ -844,6 +910,7 @@ public void MoveLeft() } } SpriteData.Patterns[SpriteData.CurrentFrame] = pattern2; + Undo_AddPoint(); Refresh(true); } @@ -879,6 +946,7 @@ public void Invert() } } SpriteData.Patterns[SpriteData.CurrentFrame] = pattern2; + Undo_AddPoint(); Refresh(true); } @@ -905,6 +973,7 @@ public void Mask() _Mask(0, maxY, ref pattern, ref pattern2); _Mask(maxX, maxY, ref pattern, ref pattern2); SpriteData.Patterns[SpriteData.CurrentFrame] = pattern2; + Undo_AddPoint(); Refresh(true); } From ea7755821b4e65258e3df498f022faf841761cf3 Mon Sep 17 00:00:00 2001 From: Juan Segura Date: Sun, 1 Jun 2025 17:27:55 +0200 Subject: [PATCH 2/7] Modified flag fixed --- ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs index 821f132..669b45b 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs @@ -245,7 +245,7 @@ public bool Initialize(string fileName) private void _Initialize(string fileName) { - _Modified = false; + _Modified = true; ServiceLayer.Initialize(); From 1e2a406f28261c74caff165a48ffc6bcfabecee4 Mon Sep 17 00:00:00 2001 From: Juan Segura Date: Sun, 1 Jun 2025 23:19:37 +0200 Subject: [PATCH 3/7] - Added View/Hide attributes button - Added Invert pixels on selected cell button - Added Invert colors on selected cell button --- README.md | 2 + .../ZXGraphics/SpriteEditor.axaml | 15 ++- .../ZXGraphics/SpriteEditor.axaml.cs | 27 +++- .../ZXGraphics/SpritePatternEditor.axaml.cs | 121 ++++++++++++++++-- .../ZXGraphics/ZXGridImageView.axaml.cs | 17 +++ .../ZXGraphics/ZXSpriteImage.cs | 25 +++- ZXBStudio/Svg/hide.svg | 7 + ZXBStudio/Svg/select.svg | 1 + ZXBStudio/ZXBasicStudio.csproj | 4 + 9 files changed, 200 insertions(+), 19 deletions(-) create mode 100644 ZXBStudio/Svg/hide.svg create mode 100644 ZXBStudio/Svg/select.svg diff --git a/README.md b/README.md index 45a238c..8c611fd 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ Have fun! - Phosphor in MIT License - Steve Schoger in PD License - Thewolfkit in CC Attribution License + - Uxaspects in Apache License + - Uxwb in GPL License - Wishforge.games in CC Attribution License - Yamatsum in MIT License - Zest in MIT License diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml index cf09cf0..98d8a9f 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml @@ -103,14 +103,21 @@ - - + + diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs index 669b45b..1d65b18 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs @@ -325,9 +325,11 @@ private void _Initialize(string fileName) btnUndo.Tapped += BtnUndo_Tapped; btnRedo.Tapped += BtnRedo_Tapped; + + btnViewAttributes.Tapped += BtnViewAttributes_Tapped; btnInvertColorsCell.Tapped += BtnInvertColorsCell_Tapped; - btnInvertPixelsCell.Tapped += BtnInvertPixelsCell_Tapped; - + btnInvertPixelsCell.Tapped += BtnInvertPixelsCell_Tapped; + Refresh(); if (SpritePatternsList.Count > 1) @@ -1018,15 +1020,32 @@ private void BtnImport_Tapped(object? sender, TappedEventArgs e) } + + private void BtnViewAttributes_Tapped(object? sender, TappedEventArgs e) + { + ctrlEditor.ViewAttributes = btnViewAttributes.IsChecked==true; + } + + private void BtnInvertPixelsCell_Tapped(object? sender, TappedEventArgs e) { - + ctrlEditor.InvertPixelsCell = btnInvertPixelsCell.IsChecked == false; + if (ctrlEditor.InvertPixelsCell) + { + btnInvertColorsCell.IsChecked = true; + ctrlEditor.InvertColorsCell = false; + } } private void BtnInvertColorsCell_Tapped(object? sender, TappedEventArgs e) { - + ctrlEditor.InvertColorsCell = btnInvertColorsCell.IsChecked == false; + if (ctrlEditor.InvertColorsCell) + { + btnInvertPixelsCell.IsChecked = true; + ctrlEditor.InvertPixelsCell = false; + } } diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs index 4e818da..d9320ae 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs @@ -83,6 +83,26 @@ public int Zoom public bool Bright { get; set; } public bool Flash { get; set; } + public bool ViewAttributes + { + get + { + return _ViewAttributes; + } + set + { + _ViewAttributes = value; + aspect.ViewAttributes = value; + Refresh(false); + } + } + + private bool _ViewAttributes = true; + + public bool InvertPixelsCell { get; set; } = false; + + public bool InvertColorsCell { get; set; } = false; + #endregion @@ -126,6 +146,8 @@ public SpritePatternEditor() grdEditor.PointerPressed += GrdEditor_PointerPressed; grdEditor.PointerReleased += GrdEditor_PointerReleased; grdEditor.PointerExited += GrdEditor_PointerExited; + + aspect.ViewAttributes = true; } @@ -277,19 +299,31 @@ private bool Pattern_Equals(Pattern p1, Pattern p2) private void GrdEditor_PointerPressed(object? sender, Avalonia.Input.PointerPressedEventArgs e) { var p = e.GetCurrentPoint(grdEditor); - if (p.Properties.IsLeftButtonPressed) + + if (InvertPixelsCell) { - SetPoint(p.Position.X, p.Position.Y, PrimaryColorIndex); - MouseLeftPressed = true; - MouseRightPressed = false; + GrdEditor_InvertPixelsCell(p.Position.X, p.Position.Y); } - else if (p.Properties.IsRightButtonPressed) + else if (InvertColorsCell) { - SetPoint(p.Position.X, p.Position.Y, SecondaryColorIndex); - MouseLeftPressed = false; - MouseRightPressed = true; + GrdEditor_InvertColorsCell(p.Position.X, p.Position.Y); } - } + else + { + if (p.Properties.IsLeftButtonPressed) + { + SetPoint(p.Position.X, p.Position.Y, PrimaryColorIndex); + MouseLeftPressed = true; + MouseRightPressed = false; + } + else if (p.Properties.IsRightButtonPressed) + { + SetPoint(p.Position.X, p.Position.Y, SecondaryColorIndex); + MouseLeftPressed = false; + MouseRightPressed = true; + } + } + } /// @@ -1053,5 +1087,74 @@ private void SetPointValue(int x, int y, int colorIndex, ref Pattern pattern) } #endregion + + + #region Invert pixels and colors + + private void GrdEditor_InvertPixelsCell(double mx, double my) + { + if (SpriteData == null) + { + return; + } + + int x = (int)mx; + int y = (int)my; + + x = x / (_Zoom + 1); + y = y / (_Zoom + 1); + + x = x / 8; + y = y / 8; + + if (x < 0 || y < 0 || x >= SpriteData.Width || y >= SpriteData.Height) + { + return; + } + + for(int py = 0; py < 8; py++) + { + for (int px = 0; px < 8; px++) + { + int dir = ((y * 8 + py) * SpriteData.Width) + (x * 8 + px); + if (dir < SpriteData.Patterns[SpriteData.CurrentFrame].RawData.Length) + { + var value = SpriteData.Patterns[SpriteData.CurrentFrame].RawData[dir]; + if (value == PrimaryColorIndex) + { + SpriteData.Patterns[SpriteData.CurrentFrame].RawData[dir] = SecondaryColorIndex; + } + else if (value == SecondaryColorIndex) + { + SpriteData.Patterns[SpriteData.CurrentFrame].RawData[dir] = PrimaryColorIndex; + } + } + } + } + Refresh(); + } + + private void GrdEditor_InvertColorsCell(double mx, double my) + { + if (SpriteData == null) + { + return; + } + int x = (int)mx; + int y = (int)my; + x = x / (_Zoom + 1); + y = y / (_Zoom + 1); + + var inkBak = PrimaryColorIndex; + var paperBak = SecondaryColorIndex; + var attr=GetAttribute(SpriteData.Patterns[SpriteData.CurrentFrame], x, y); + PrimaryColorIndex = attr.Paper; + SecondaryColorIndex = attr.Ink; + SetAttribute(SpriteData.Patterns[SpriteData.CurrentFrame], x, y); + PrimaryColorIndex = inkBak; + SecondaryColorIndex=paperBak; + Refresh(); + } + #endregion } } diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/ZXGridImageView.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/ZXGridImageView.axaml.cs index 455cf3f..e063b0e 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/ZXGridImageView.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/ZXGridImageView.axaml.cs @@ -14,6 +14,19 @@ namespace ZXBasicStudio.DocumentEditors.ZXGraphics { public partial class ZXGridImageView : UserControl { + public bool ViewAttributes { + get + { + return _ViewAttributes; + } + set + { + _ViewAttributes = value; + InvalidateVisual(); + } + } + private bool _ViewAttributes = false; + private WriteableBitmap? gridImage; private IZXBitmap? backgroundImage; private SKColor gridColor = new SKColor(0x00, 0x00, 0x00, 0xFF); @@ -75,7 +88,9 @@ public ZXGridImageView() private void EnsureGrid() { if (backgroundImage == null) + { return; + } int w = (int)(backgroundImage.PixelSize.Width * Zoom + backgroundImage.PixelSize.Width + 1); int h = (int)(backgroundImage.PixelSize.Height * Zoom + backgroundImage.PixelSize.Height + 1); @@ -83,7 +98,9 @@ private void EnsureGrid() if (gridImage != null) { if (gridImage.PixelSize.Width == w && gridImage.PixelSize.Height == h) + { return; + } gridImage.Dispose(); diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/ZXSpriteImage.cs b/ZXBStudio/DocumentEditors/ZXGraphics/ZXSpriteImage.cs index 0f6a1e5..4b03213 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/ZXSpriteImage.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/ZXSpriteImage.cs @@ -17,7 +17,10 @@ public class ZXSpriteImage : IZXBitmap, IDisposable #endregion #region Public properties + public bool IsEmpty { get; private set; } + public bool ViewAttributes { get; set; } = true; + #endregion #region Constructors @@ -90,9 +93,27 @@ public unsafe void RenderSprite(Sprite Sprite, int FrameNumber) { var attr = GetAttribute(Sprite, frame, x, y); if (colorIndex == 0) - color = Sprite.Palette[attr.Paper]; + { + if (ViewAttributes) + { + color = Sprite.Palette[attr.Paper]; + } + else + { + color = Sprite.Palette[7]; + } + } else - color = Sprite.Palette[attr.Ink]; + { + if(ViewAttributes) + { + color = Sprite.Palette[attr.Ink]; + } + else + { + color = Sprite.Palette[0]; + } + } } break; case GraphicsModes.Monochrome: diff --git a/ZXBStudio/Svg/hide.svg b/ZXBStudio/Svg/hide.svg new file mode 100644 index 0000000..60bcb77 --- /dev/null +++ b/ZXBStudio/Svg/hide.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/ZXBStudio/Svg/select.svg b/ZXBStudio/Svg/select.svg new file mode 100644 index 0000000..c816e73 --- /dev/null +++ b/ZXBStudio/Svg/select.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ZXBStudio/ZXBasicStudio.csproj b/ZXBStudio/ZXBasicStudio.csproj index 2c38e46..e7e38b8 100644 --- a/ZXBStudio/ZXBasicStudio.csproj +++ b/ZXBStudio/ZXBasicStudio.csproj @@ -170,6 +170,7 @@ + @@ -195,6 +196,7 @@ + @@ -359,6 +361,8 @@ + + From 3e0de1ca99e82527920a2dce0eec91a9203280c2 Mon Sep 17 00:00:00 2001 From: Juan Segura Date: Sun, 1 Jun 2025 23:37:36 +0200 Subject: [PATCH 4/7] Added color picker button --- .../ZXGraphics/SpriteEditor.axaml | 4 ++-- .../ZXGraphics/SpriteEditor.axaml.cs | 9 +++++++++ .../ZXGraphics/SpritePatternEditor.axaml.cs | 16 +++++++++++++++- ZXBStudio/Svg/picker.svg | 7 +++++++ ZXBStudio/ZXBasicStudio.csproj | 2 ++ 5 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 ZXBStudio/Svg/picker.svg diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml index 98d8a9f..f38140e 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml @@ -106,8 +106,8 @@ - - + + diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs index 1d65b18..0168356 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs @@ -327,6 +327,7 @@ private void _Initialize(string fileName) btnRedo.Tapped += BtnRedo_Tapped; btnViewAttributes.Tapped += BtnViewAttributes_Tapped; + btnColorPicker.Tapped += BtnColorPicker_Tapped; btnInvertColorsCell.Tapped += BtnInvertColorsCell_Tapped; btnInvertPixelsCell.Tapped += BtnInvertPixelsCell_Tapped; @@ -711,6 +712,9 @@ private void Editor_Command(SpritePatternEditor sender, string command) ctrlProperties.PrimaryColor = sender.PrimaryColorIndex; ctrlProperties.SecondaryColor = sender.SecondaryColorIndex; ctrlPreview.SpriteData = sender.SpriteData; + + btnColorPicker.IsChecked = !ctrlEditor.ColorPicker; + UpdateColorPanel(); } } break; @@ -1026,6 +1030,11 @@ private void BtnViewAttributes_Tapped(object? sender, TappedEventArgs e) ctrlEditor.ViewAttributes = btnViewAttributes.IsChecked==true; } + private void BtnColorPicker_Tapped(object? sender, TappedEventArgs e) + { + ctrlEditor.ColorPicker = btnColorPicker.IsChecked == false; + } + private void BtnInvertPixelsCell_Tapped(object? sender, TappedEventArgs e) { diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs index d9320ae..22693db 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs @@ -103,6 +103,8 @@ public bool ViewAttributes public bool InvertColorsCell { get; set; } = false; + public bool ColorPicker { get; set; } = false; + #endregion @@ -300,7 +302,19 @@ private void GrdEditor_PointerPressed(object? sender, Avalonia.Input.PointerPres { var p = e.GetCurrentPoint(grdEditor); - if (InvertPixelsCell) + if (ColorPicker) + { + int x = (int)p.Position.X; + int y = (int)p.Position.Y; + x = x / (_Zoom + 1); + y = y / (_Zoom + 1); + var atr=GetAttribute(SpriteData.Patterns[SpriteData.CurrentFrame], x,y); + PrimaryColorIndex = atr.Ink; + SecondaryColorIndex = atr.Paper; + ColorPicker = false; + Refresh(true); + } + else if (InvertPixelsCell) { GrdEditor_InvertPixelsCell(p.Position.X, p.Position.Y); } diff --git a/ZXBStudio/Svg/picker.svg b/ZXBStudio/Svg/picker.svg new file mode 100644 index 0000000..cf539f2 --- /dev/null +++ b/ZXBStudio/Svg/picker.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/ZXBStudio/ZXBasicStudio.csproj b/ZXBStudio/ZXBasicStudio.csproj index e7e38b8..baef0c0 100644 --- a/ZXBStudio/ZXBasicStudio.csproj +++ b/ZXBStudio/ZXBasicStudio.csproj @@ -189,6 +189,7 @@ + @@ -362,6 +363,7 @@ + From 4ddbe7417ee9191e1024092afd4b1369f24b3862 Mon Sep 17 00:00:00 2001 From: Juan Segura Date: Mon, 2 Jun 2025 01:33:32 +0200 Subject: [PATCH 5/7] - Fixed attribute bug in paste command - Fixed import image bright mismatch --- .../ZXGraphics/SpriteImportDialog.axaml.cs | 17 +++++++++++------ .../ZXGraphics/SpritePatternEditor.axaml.cs | 1 + 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteImportDialog.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteImportDialog.axaml.cs index 3148690..47e7129 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteImportDialog.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteImportDialog.axaml.cs @@ -330,7 +330,7 @@ private void _UpdatePreview() var dirAttr = (cy * sw) + cx; var attr = pattern.Attributes[dirAttr]; attr.Bright = attr.Bright | (idxAttr > 7); - byte cCol = (byte)(idxAttr & 0b111); + byte cCol = (byte)(idxAttr /*& 0b111*/); if (paper == -1) { attr.Paper = cCol; @@ -406,9 +406,10 @@ private void _UpdatePreview() private int GetColor(byte r, byte g, byte b, PaletteColor[] palette) { - if (r > 0) + bool brigth = false; + if (r > 250 || g > 250 || b > 250) { - + brigth = true; } byte cr = GetColor_CutOff(r); byte cg = GetColor_CutOff(g); @@ -428,6 +429,10 @@ private int GetColor(byte r, byte g, byte b, PaletteColor[] palette) palColor.Green == p.Green && palColor.Blue == p.Blue) { + if (brigth && n<8) + { + return n + 8; + } return n; } } @@ -471,9 +476,9 @@ private byte GetColor_CutOff(byte c) private static double GetColorDistance(PaletteColor c1, PaletteColor c2) { - int rDiff = c1.Red - c2.Red; - int gDiff = c1.Green - c2.Green; - int bDiff = c1.Blue - c2.Blue; + int rDiff = Math.Abs(c1.Red - c2.Red); + int gDiff = Math.Abs(c1.Green - c2.Green); + int bDiff = Math.Abs(c1.Blue - c2.Blue); return Math.Sqrt(rDiff * rDiff + gDiff * gDiff + bDiff * bDiff); } diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs index 22693db..1d1d99a 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs @@ -561,6 +561,7 @@ public async void Paste() else { SpriteData.Patterns[SpriteData.CurrentFrame].RawData = cbPatterns[0].RawData; + SpriteData.Patterns[SpriteData.CurrentFrame].Attributes = cbPatterns[0].Attributes; } } else From 6f2171ab84c2a1c99a1eeb6b8ff90f3f06948e0e Mon Sep 17 00:00:00 2001 From: Juan Segura Date: Sun, 5 Oct 2025 16:04:39 +0200 Subject: [PATCH 6/7] v1.6.0-beta5 --- ZXBStudio/BuildSystem/ZXProjectBuilder.cs | 6 +++++- ZXBStudio/Dialogs/ZXAboutDialog.axaml.cs | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ZXBStudio/BuildSystem/ZXProjectBuilder.cs b/ZXBStudio/BuildSystem/ZXProjectBuilder.cs index 3341b83..80e702e 100644 --- a/ZXBStudio/BuildSystem/ZXProjectBuilder.cs +++ b/ZXBStudio/BuildSystem/ZXProjectBuilder.cs @@ -123,7 +123,11 @@ public class ZXProjectBuilder } } - byte[] binary = File.ReadAllBytes(binFile); + byte[] binary = new byte[0]; + if (File.Exists(binFile)) + { + binary = File.ReadAllBytes(binFile); + } Cleanup(project.ProjectPath, binFile); diff --git a/ZXBStudio/Dialogs/ZXAboutDialog.axaml.cs b/ZXBStudio/Dialogs/ZXAboutDialog.axaml.cs index 2305e45..c855260 100644 --- a/ZXBStudio/Dialogs/ZXAboutDialog.axaml.cs +++ b/ZXBStudio/Dialogs/ZXAboutDialog.axaml.cs @@ -12,8 +12,8 @@ public ZXAboutDialog() { InitializeComponent(); - txtBuild.Text = "1.6.0-beta4"; - txtDate.Text = "2025-05-21"; + txtBuild.Text = "1.6.0-beta5"; + txtDate.Text = "2025-10-05"; btnClose.Click += BtnClose_Click; From fb27aa585cfa1e49b51270379617138d5a648223 Mon Sep 17 00:00:00 2001 From: Juan Segura Date: Sun, 5 Oct 2025 17:35:59 +0200 Subject: [PATCH 7/7] v1.6.0 - beta 5 (reloaded) --- ZXBStudio/Dialogs/SplashScreen.axaml | 3 ++- ZXBStudio/Dialogs/SplashScreen.axaml.cs | 1 + ZXBStudio/Dialogs/ZXAboutDialog.axaml.cs | 4 ++-- ZXBStudio/Program.cs | 3 +++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ZXBStudio/Dialogs/SplashScreen.axaml b/ZXBStudio/Dialogs/SplashScreen.axaml index b4e06de..e64bebd 100644 --- a/ZXBStudio/Dialogs/SplashScreen.axaml +++ b/ZXBStudio/Dialogs/SplashScreen.axaml @@ -12,7 +12,8 @@ Topmost="True"> - + + diff --git a/ZXBStudio/Dialogs/SplashScreen.axaml.cs b/ZXBStudio/Dialogs/SplashScreen.axaml.cs index 1059378..0f58757 100644 --- a/ZXBStudio/Dialogs/SplashScreen.axaml.cs +++ b/ZXBStudio/Dialogs/SplashScreen.axaml.cs @@ -8,6 +8,7 @@ public partial class SplashScreen : Window public SplashScreen() { InitializeComponent(); + lblVersion.Content=$"{Program.Version} | {Program.VersionDate}"; } } } diff --git a/ZXBStudio/Dialogs/ZXAboutDialog.axaml.cs b/ZXBStudio/Dialogs/ZXAboutDialog.axaml.cs index c855260..6aba0e8 100644 --- a/ZXBStudio/Dialogs/ZXAboutDialog.axaml.cs +++ b/ZXBStudio/Dialogs/ZXAboutDialog.axaml.cs @@ -12,8 +12,8 @@ public ZXAboutDialog() { InitializeComponent(); - txtBuild.Text = "1.6.0-beta5"; - txtDate.Text = "2025-10-05"; + txtBuild.Text = Program.Version; + txtDate.Text = Program.VersionDate; btnClose.Click += BtnClose_Click; diff --git a/ZXBStudio/Program.cs b/ZXBStudio/Program.cs index ff67436..3a1420b 100644 --- a/ZXBStudio/Program.cs +++ b/ZXBStudio/Program.cs @@ -8,6 +8,9 @@ namespace ZXBasicStudio { internal class Program { + public static string Version = "1.6.0 - beta 5"; + public static string VersionDate = "2025.10.05"; + // Initialization code. Don't use any Avalonia, third-party APIs or any // SynchronizationContext-reliant code before AppMain is called: things aren't initialized // yet and stuff might break.