diff --git a/README.md b/README.md index 6eda66c..2dbe168 100644 --- a/README.md +++ b/README.md @@ -46,23 +46,21 @@ This solution contains several projects that work together to provide the ZX Bas ## Credits - Icons from [SVG REPO](https://www.svgrepo.com/): + - SVG Repo CC0 License + - Ananthanath A X Kalaiism in PD License - Blivesta in MIT License - Dazzle Ui in CC Attribution License - Kde in GPL License - - Konstantin Filatov in CC Attribution + - Konstantin Filatov in CC Attribution License + - Krystonschwarze in CC Attribution License - Mohamed Raouf in CC Attribution License + - Neuicons in MIT License via - Phosphor in MIT License - Steve Schoger in PD License + - Siemens in MIT 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 - - Neuicons in MIT License via - - Dazzle Ui in CC Attribution License - - Siemens in MIT License - - Zest in MIT License - - Ananthanath A X Kalaiism in PD License - - Siemens in MIT License - - SVG Repo CC0 License diff --git a/ZXBSInstaller.Log/ServiceLayer.cs b/ZXBSInstaller.Log/ServiceLayer.cs index 4c8a65c..be60f49 100644 --- a/ZXBSInstaller.Log/ServiceLayer.cs +++ b/ZXBSInstaller.Log/ServiceLayer.cs @@ -1905,7 +1905,8 @@ private static ExternalTools_Version[] GetTBBLueMAMEVersions() // All versions var v = new ExternalTools_Version() { - DownloadUrl = "https://github.com/Threetwosevensixseven/NexCreator/raw/master/bootroms/tbblue.zip", + //DownloadUrl = "https://github.com/Threetwosevensixseven/NexCreator/raw/master/bootroms/tbblue.zip", + DownloadUrl = "https://www.specnext.com/forum/download/file.php?id=1164", OperatingSystem = OperatingSystems.All, Version = "3.02.03", }; diff --git a/ZXBSInstaller/ZXBSInstaller.csproj b/ZXBSInstaller/ZXBSInstaller.csproj index d23c315..dc4319e 100644 --- a/ZXBSInstaller/ZXBSInstaller.csproj +++ b/ZXBSInstaller/ZXBSInstaller.csproj @@ -6,7 +6,7 @@ app.manifest true zxbs.ico - 1.0.2.0 + 1.0.3 diff --git a/ZXBStudio/Assets/zxGraphics_map.png b/ZXBStudio/Assets/zxGraphics_map.png index 52848ea..91d74bb 100644 Binary files a/ZXBStudio/Assets/zxGraphics_map.png and b/ZXBStudio/Assets/zxGraphics_map.png differ diff --git a/ZXBStudio/Assets/zxmaps_map.png b/ZXBStudio/Assets/zxmaps_map.png new file mode 100644 index 0000000..415f68f Binary files /dev/null and b/ZXBStudio/Assets/zxmaps_map.png differ diff --git a/ZXBStudio/Assets/zxGraphics_til.png b/ZXBStudio/Assets/zxmaps_til.png similarity index 54% rename from ZXBStudio/Assets/zxGraphics_til.png rename to ZXBStudio/Assets/zxmaps_til.png index fd075ab..3e12359 100644 Binary files a/ZXBStudio/Assets/zxGraphics_til.png and b/ZXBStudio/Assets/zxmaps_til.png differ diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/FontGDUExportDialog.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/FontGDUExportDialog.axaml.cs index f907cec..b0f0144 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/FontGDUExportDialog.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/FontGDUExportDialog.axaml.cs @@ -175,7 +175,7 @@ private void CreateExample_BIN() { case FileTypes.Font: sb.AppendLine("' Example of use of custom font type"); - sb.AppendLine("POKE (uinteger 23606, @MyFont-256)"); + sb.AppendLine("POKE (uinteger 23606, @MyFont(0,0)-256)"); sb.AppendLine("PRINT \"Hello World!\""); sb.AppendLine("STOP"); sb.AppendLine(""); @@ -189,7 +189,7 @@ private void CreateExample_BIN() break; case FileTypes.UDG: sb.AppendLine("' Example of use of UDG/GDU"); - sb.AppendLine("POKE (uinteger 23675, @MyUDG)"); + sb.AppendLine("POKE (uinteger 23675, @MyUDG(0,0))"); sb.AppendLine("PRINT \"UDG/GDU Table\""); sb.AppendLine("FOR n=0 TO 20"); sb.AppendLine(" PRINT (144+n);\" - \";CHR(n+65);\": \";CHR(144+n)"); @@ -245,13 +245,13 @@ private void CreateExample_ASM() sb.AppendLine("' Example of use of the asm format"); if (fileType.FileType == FileTypes.Font) { - sb.AppendLine(string.Format("POKE (uinteger 23606, @{0}-256)", txtLabelName.Text)); + sb.AppendLine(string.Format("POKE (uinteger 23606, @{0}(0,0)-256)", txtLabelName.Text)); sb.AppendLine("PRINT \"Hello World!\""); sb.AppendLine("STOP"); } else { - sb.AppendLine(string.Format("POKE (uinteger 23675, @{0})", txtLabelName.Text)); + sb.AppendLine(string.Format("POKE (uinteger 23675, @{0}(0,0))", txtLabelName.Text)); sb.AppendLine("PRINT \"UDG/GDU Table\""); sb.AppendLine("FOR n=0 TO 20"); sb.AppendLine(" PRINT (144+n);\" - \";CHR(n+65);\": \";CHR(144+n)"); @@ -275,7 +275,7 @@ private void CreateExample_DIM() switch (fileType.FileType) { case FileTypes.UDG: - sb.AppendLine(string.Format("POKE (uinteger 23675, @{0})", txtLabelName.Text)); + sb.AppendLine(string.Format("POKE (uinteger 23675, @{0}(0))", txtLabelName.Text)); sb.AppendLine("PRINT \"UDG/GDU Table\""); sb.AppendLine("FOR n=0 TO 20"); sb.AppendLine(" PRINT (144+n);\" - \";CHR(n+65);\": \";CHR(144+n)"); @@ -283,7 +283,7 @@ private void CreateExample_DIM() sb.AppendLine("STOP"); break; case FileTypes.Font: - sb.AppendLine(string.Format("POKE (uinteger 23606, @{0}-256)", txtLabelName.Text)); + sb.AppendLine(string.Format("POKE (uinteger 23606, @{0}(0,0)-256)", txtLabelName.Text)); sb.AppendLine("PRINT \"Hello World!\""); sb.AppendLine("STOP"); break; diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/ImageViewImportControl.cs b/ZXBStudio/DocumentEditors/ZXGraphics/ImageViewImportControl.cs index 422b891..8d8b5b1 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/ImageViewImportControl.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/ImageViewImportControl.cs @@ -9,12 +9,15 @@ using SixLabors.ImageSharp.PixelFormats; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Net; using System.Runtime.Intrinsics.X86; using System.Text; using System.Threading; using System.Threading.Tasks; +using ZXBasicStudio.DocumentEditors.NextDows.log; +using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; namespace ZXBasicStudio.DocumentEditors.ZXGraphics { @@ -136,20 +139,12 @@ public ImageViewImportControl() public async void LoadImage(IStorageFile file) { - try - { - await using (var stream = await file.OpenReadAsync()) - { - imageData = SixLabors.ImageSharp.Image.Load(stream); - } - this.InvalidateVisual(); - } - catch (Exception ex) - { - } + imageData = ZXGraphics.log.ServiceLayer.LoadImage(file); + this.InvalidateVisual(); } + public void Refresh() { this.InvalidateVisual(); @@ -200,7 +195,7 @@ public override void Render(DrawingContext context) } else { - context.FillRectangle(brushWhite, r0); + context.FillRectangle(brushWhite, r0); } pair = !pair; } @@ -268,13 +263,13 @@ public override void Render(DrawingContext context) int x2 = SpriteWidth * _Zoom; int y2 = SpriteHeight * _Zoom; - var r0 = new Rect(x2, 0, 400-x2, 400); + var r0 = new Rect(x2, 0, 400 - x2, 400); context.FillRectangle(brushMask, r0); - var r1 = new Rect(0, y2, x2, 400-y2); + var r1 = new Rect(0, y2, x2, 400 - y2); context.FillRectangle(brushMask, r1); context.DrawRectangle(penRed, new Rect(-1, -1, x2, y2)); - } + } } catch (Exception ex) { @@ -282,7 +277,7 @@ public override void Render(DrawingContext context) } } -#endregion + #endregion #region Mouse diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SelectExportTypeControl.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/SelectExportTypeControl.axaml.cs index aca2dab..e17021f 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SelectExportTypeControl.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SelectExportTypeControl.axaml.cs @@ -46,6 +46,38 @@ public SelectExportTypeControl() } + public bool InitializeTile() + { + ExportTypesList = new List(); + ExportTypesList.Add(new ExportTypeDescrioptionItem() + { + Description = "Boriel Basic's PutChars embeded library.", + ExportType = ExportTypes.PutChars, + Image = "/Svg/Seal.svg", + Name = "PutChars" + }); + lstOptions.ItemsSource = ExportTypesList; + lstOptions.SelectionChanged += LstOptions_SelectionChanged; + return true; + } + + + public bool InitializeMap() + { + ExportTypesList = new List(); + ExportTypesList.Add(new ExportTypeDescrioptionItem() + { + Description = "Raw array of bytes, one byte for tile", + ExportType = ExportTypes.Array, + Image = "/Svg/boxes-stacked-solid.svg", + Name = "Array" + }); + lstOptions.ItemsSource = ExportTypesList; + lstOptions.SelectionChanged += LstOptions_SelectionChanged; + return true; + } + + public bool InitializeSprite() { ExportTypesList = new List(); diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml index f38140e..1513953 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml @@ -1,193 +1,193 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Zoom 24x - - Frame - - - - - - Paper - - - 0 - - - - - - Ink - - - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:svg="using:Avalonia.Svg.Skia" + xmlns:local="using:ZXBasicStudio.DocumentEditors.ZXGraphics" + xmlns:zxd="using:ZXBasicStudio.Controls.DockSystem" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + Focusable="True" + Background="#ff202020" + x:Class="ZXBasicStudio.DocumentEditors.ZXGraphics.SpriteEditor"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Zoom 24x + + Frame + + + + + + Paper + + + 0 + + + + + + Ink + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs index 3d27d3f..ac5948d 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteEditor.axaml.cs @@ -133,12 +133,12 @@ public override bool SaveDocument(TextWriter OutputLog) try { var masterList = SpritePatternsList.Select(d => d.SpriteData).ToArray(); - var sprList = new List(); + var sprList = new List(); foreach (var spr in masterList) { sprList.Add(spr); //.Clonar()); } - foreach (Sprite spr in sprList) + foreach (ZXMapsTile spr in sprList) { if (spr == null) { @@ -259,7 +259,7 @@ private void _Initialize(string fileName) var dataS = Encoding.UTF8.GetString(data); if (!string.IsNullOrEmpty(dataS)) { - Sprite[] sprites = dataS.Deserializar(); + ZXMapsTile[] sprites = dataS.Deserializar(); foreach (var sprite in sprites) { @@ -560,7 +560,7 @@ private void SpriteProperties_FrameUpdate(SpritePropertiesControl sender, string } - private void SpriteList_AddSprite(Sprite spriteData) + private void SpriteList_AddSprite(ZXMapsTile spriteData) { SpritePatternControl selectedSprite = null; int id = 0; @@ -631,7 +631,7 @@ private void SpriteList_Delete(SpritePatternControl control) } - private void SpriteList_Insert(Sprite spriteData) + private void SpriteList_Insert(ZXMapsTile spriteData) { var current = spriteData.CurrentFrame; var curPat = spriteData.Patterns[current]; @@ -650,7 +650,7 @@ private void SpriteList_Insert(Sprite spriteData) } - private void SpriteList_Clone(Sprite spriteData) + private void SpriteList_Clone(ZXMapsTile spriteData) { SpritePatternControl selectedSprite = null; int id = SpritePatternsList.Where(d => d.SpriteData != null).Max(d => d.SpriteData.Id) + 1; @@ -659,7 +659,7 @@ private void SpriteList_Clone(Sprite spriteData) { return; } - var sd = spriteData.Clonar(); + var sd = spriteData.Clonar(); sd.Id = id; sd.Name = spriteData.Name + " - copy"; spc.SpriteData = sd; @@ -673,7 +673,7 @@ private void SpriteList_Clone(Sprite spriteData) } - private void SpriteList_Modified(Sprite spriteData) + private void SpriteList_Modified(ZXMapsTile spriteData) { if (!_Modified) { @@ -1174,7 +1174,7 @@ private void Import() } - private void Import_Command(Sprite sprite, string command) + private void Import_Command(ZXMapsTile sprite, string command) { switch (command) { diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteExportDialog.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteExportDialog.axaml.cs index 4d64348..e25c1a0 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteExportDialog.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteExportDialog.axaml.cs @@ -24,7 +24,7 @@ namespace ZXBasicStudio.DocumentEditors.ZXGraphics public partial class SpriteExportDialog : Window { private string fileName = ""; - private IEnumerable sprites = null; + private IEnumerable sprites = null; private FileTypeConfig fileType = null; @@ -47,7 +47,7 @@ public SpriteExportDialog() } - public bool Initialize(string fileName, IEnumerable spritesData) + public bool Initialize(string fileName, IEnumerable spritesData) { this.fileName = fileName; this.sprites = spritesData; diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteImportDialog.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteImportDialog.axaml.cs index 47e7129..3f3d917 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpriteImportDialog.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpriteImportDialog.axaml.cs @@ -44,9 +44,9 @@ public partial class SpriteImportDialog : Window, IDisposable private ExportConfig exportConfig = null; */ - private Action CallBackCommand = null; - private IEnumerable sprites = null; - private Sprite sprite = null; + private Action CallBackCommand = null; + private IEnumerable sprites = null; + private ZXMapsTile sprite = null; private string spriteName = ""; private GraphicsModes spriteMode = GraphicsModes.Monochrome; @@ -80,7 +80,7 @@ public SpriteImportDialog() btnImport.Tapped += BtnImport_Tapped; } - public bool Initialize(string fileName, IEnumerable sprites, Action callBackCommand) + public bool Initialize(string fileName, IEnumerable sprites, Action callBackCommand) { this.CallBackCommand = callBackCommand; this.sprites = sprites; @@ -119,12 +119,13 @@ public void Dispose() private async void BtnFile_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) { - var fileTypes = new FilePickerFileType[4]; + var fileTypes = new FilePickerFileType[5]; fileTypes[0] = new FilePickerFileType("All files") { Patterns = new[] { "*", "*.*" } }; fileTypes[1] = new FilePickerFileType("BMP files") { Patterns = new[] { "*.bmp" } }; fileTypes[2] = new FilePickerFileType("JPG files") { Patterns = new[] { "*.jpg", "*.jpeg" } }; fileTypes[3] = new FilePickerFileType("PNG files") { Patterns = new[] { "*.png" } }; - /*fileTypes[4] = new FilePickerFileType("SCR Spectrum screen files") { Patterns = new[] { "*.scr" } };*/ + fileTypes[4] = new FilePickerFileType("ZX Paintbrush SCR files") { Patterns = new[] { "*.scr" } }; + /*fileTypes[5] = new FilePickerFileType("SCR Spectrum screen files") { Patterns = new[] { "*.scr" } };*/ var select = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions { @@ -247,7 +248,7 @@ private void _UpdatePreview() GetProperties(); - var s = new Sprite(); + var s = new ZXMapsTile(); s.CurrentFrame = 0; s.DefaultColor = 7; s.Export = true; @@ -622,7 +623,7 @@ private async void Import() for (int n = 0; n < sprite.Patterns.Count(); n++) { - var spr = sprite.Clonar(); + var spr = sprite.Clonar(); spr.Patterns = spr.Patterns.Skip(n).Take(1).ToList(); spr.Frames = 1; spr.Name = sprName + "_" + n.ToString(); diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternControl.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternControl.axaml.cs index 05cbb5d..ce98b17 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternControl.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternControl.axaml.cs @@ -18,7 +18,7 @@ public partial class SpritePatternControl : UserControl /// /// Sprite data /// - public Sprite SpriteData + public ZXMapsTile SpriteData { get { @@ -82,7 +82,7 @@ public bool SettingsChanged private bool _SettingsChanged = false; private bool refreshing = false; private bool newSprite = true; - private Sprite _SpriteData = null; + private ZXMapsTile _SpriteData = null; #endregion @@ -101,7 +101,7 @@ public SpritePatternControl() /// Data of the sprite, if is null, the "Add" icon is visible and no properties are shown /// CallBak for actions command, line "ADD", "CLONE", "DELETE" or "SELECTED" /// - public bool Initialize(Sprite spriteData, Action callBackCommand) + public bool Initialize(ZXMapsTile spriteData, Action callBackCommand) { this.SpriteData = spriteData; this.CallBackCommand = callBackCommand; @@ -271,7 +271,7 @@ public void ApplySettings(bool askForApply) } else { - var sp = _SpriteData.Clonar(); + var sp = _SpriteData.Clonar(); if (sp.Width != _SpriteData.Width || sp.Height != _SpriteData.Height) { @@ -321,7 +321,7 @@ public void ApplySettings(bool askForApply) private void AddNew() { - var sp = new Sprite() + var sp = new ZXMapsTile() { CurrentFrame = 0, DefaultColor = 0, diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs index 1c280e8..4f91b95 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpritePatternEditor.axaml.cs @@ -26,7 +26,7 @@ public partial class SpritePatternEditor : UserControl /// /// Actual Sprite data /// - public Sprite SpriteData + public ZXMapsTile SpriteData { get { @@ -110,7 +110,7 @@ public bool ViewAttributes #region Private fields - private Sprite _SpriteData = null; + private ZXMapsTile _SpriteData = null; private int _Zoom = 24; private Action CallBackCommand = null; private int? lastId = null; diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpritePreviewControl.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/SpritePreviewControl.axaml.cs index c02bb82..e815456 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpritePreviewControl.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpritePreviewControl.axaml.cs @@ -23,7 +23,7 @@ public partial class SpritePreviewControl : UserControl, IDisposable /// /// Sprite data /// - public Sprite? SpriteData { get; set; } + public ZXMapsTile? SpriteData { get; set; } public int Zoom { get; set; } = 4; #endregion @@ -60,7 +60,7 @@ public SpritePreviewControl() /// Data of the sprite, if is null, the "Add" icon is visible and no properties are shown /// CallBak for actions command, line "ADD", "CLONE", "DELETE" or "SELECTED" /// - public bool Initialize(Sprite spriteData) + public bool Initialize(ZXMapsTile spriteData) { this.SpriteData = spriteData; diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/SpritePropertiesControl.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/SpritePropertiesControl.axaml.cs index c5b3b74..7502743 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/SpritePropertiesControl.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/SpritePropertiesControl.axaml.cs @@ -20,7 +20,7 @@ public partial class SpritePropertiesControl : UserControl /// /// Sprite data /// - public Sprite SpriteData + public ZXMapsTile SpriteData { get { @@ -68,7 +68,7 @@ public bool IsSelected private bool _SettingsChanged = false; private bool refreshing = false; private bool newSprite = true; - private Sprite _SpriteData = null; + private ZXMapsTile _SpriteData = null; #endregion @@ -88,7 +88,7 @@ public SpritePropertiesControl() /// Data of the sprite, if is null, the "Add" icon is visible and no properties are shown /// CallBak for actions command, line "ADD", "CLONE", "DELETE" or "SELECTED" /// - public bool Initialize(Sprite spriteData, Action callBackCommand) + public bool Initialize(ZXMapsTile spriteData, Action callBackCommand) { this.SpriteData = spriteData; this.CallBackCommand = callBackCommand; diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/ZXGridImageView.axaml.cs b/ZXBStudio/DocumentEditors/ZXGraphics/ZXGridImageView.axaml.cs index e063b0e..e8dbeb5 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/ZXGridImageView.axaml.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/ZXGridImageView.axaml.cs @@ -5,6 +5,7 @@ using Avalonia.Media; using Avalonia.Media.Imaging; using Avalonia.Skia; +using FFmpeg.AutoGen; using SkiaSharp; using System; using System.Diagnostics; @@ -14,7 +15,8 @@ namespace ZXBasicStudio.DocumentEditors.ZXGraphics { public partial class ZXGridImageView : UserControl { - public bool ViewAttributes { + public bool ViewAttributes + { get { return _ViewAttributes; @@ -51,6 +53,7 @@ public int Zoom } } + public SKColor GridColor { get => gridColor; @@ -61,6 +64,18 @@ public SKColor GridColor } } + + public SKColor Grid8x8Color + { + get => grid8x8Color; + set + { + grid8x8Color = value; + InvalidateVisual(); + } + } + + public IZXBitmap? BackgroundImage { get => backgroundImage; @@ -79,6 +94,7 @@ public IZXBitmap? BackgroundImage } public bool Show8x8Grid { get; set; } = true; + public bool ShowTileGrid { get; set; } = true; public ZXGridImageView() { @@ -123,34 +139,51 @@ private void EnsureGrid() using var paint = new SKPaint { Color = gridColor, StrokeWidth = 1, IsAntialias = false }; using var paintGrid = new SKPaint { Color = grid8x8Color, StrokeWidth = 1, IsAntialias = false }; - //Draw vertical lines - for (int x = 0; x < gridImage.PixelSize.Width + 1; x += Zoom + 1) + if (Show8x8Grid) { - if ((x % 8) == 0) + //Draw vertical lines + for (int x = 0; x < gridImage.PixelSize.Width + 1; x += Zoom + 1) { - canvas.DrawLine(new SKPoint(x, 0), new SKPoint(x, gridImage.PixelSize.Height), paintGrid); + if ((x % 8) == 0) + { + canvas.DrawLine(new SKPoint(x, 0), new SKPoint(x, gridImage.PixelSize.Height), paintGrid); + } + else + { + canvas.DrawLine(new SKPoint(x, 0), new SKPoint(x, gridImage.PixelSize.Height), paint); + } } - else + + //Draw horizontal lines + for (int y = 0; y < gridImage.PixelSize.Height + 1; y += Zoom + 1) { - canvas.DrawLine(new SKPoint(x, 0), new SKPoint(x, gridImage.PixelSize.Height), paint); + if ((y % 8) == 0) + { + canvas.DrawLine(new SKPoint(0, y), new SKPoint(gridImage.PixelSize.Width, y), paintGrid); + } + else + { + canvas.DrawLine(new SKPoint(0, y), new SKPoint(gridImage.PixelSize.Width, y), paint); + } } } - //Draw horizontal lines - for (int y = 0; y < gridImage.PixelSize.Height + 1; y += Zoom + 1) + if (ShowTileGrid) { - if ((y % 8) == 0) { - canvas.DrawLine(new SKPoint(0, y), new SKPoint(gridImage.PixelSize.Width, y), paintGrid); + int limit = gridImage.PixelSize.Width + 1; + canvas.DrawLine(new SKPoint(0, 0), new SKPoint(0, gridImage.PixelSize.Height), paintGrid); + canvas.DrawLine(new SKPoint(limit, 0), new SKPoint(limit, gridImage.PixelSize.Height), paintGrid); } - else { - canvas.DrawLine(new SKPoint(0, y), new SKPoint(gridImage.PixelSize.Width, y), paint); + int limit = gridImage.PixelSize.Height + 1; + canvas.DrawLine(new SKPoint(0, 0), new SKPoint(gridImage.PixelSize.Height, 0), paintGrid); + canvas.DrawLine(new SKPoint(0, limit), new SKPoint(gridImage.PixelSize.Height, limit), paintGrid); } } - } + public override void Render(DrawingContext context) { EnsureGrid(); @@ -163,20 +196,22 @@ public override void Render(DrawingContext context) context.DrawImage(backgroundImage, new Rect(0, 0, gridImage.Size.Width, gridImage.Size.Height)); - if (zoom > 4) + if (zoom > 4 || ShowTileGrid) + { context.DrawImage(gridImage, new Rect(0, 0, gridImage.Size.Width, gridImage.Size.Height)); + } } protected override void OnPointerPressed(PointerPressedEventArgs e) { base.OnPointerPressed(e); - Debug.WriteLine($"OnPointerPressed: {e.GetPosition(this)}"); + //Debug.WriteLine($"OnPointerPressed: {e.GetPosition(this)}"); } protected override void OnPointerMoved(PointerEventArgs e) { base.OnPointerMoved(e); - Debug.WriteLine($"OnPointerMoved: {this.Width}/{this.Height} : {e.GetPosition(this)}"); + //Debug.WriteLine($"OnPointerMoved: {this.Width}/{this.Height} : {e.GetPosition(this)}"); } } } \ No newline at end of file diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/ZXSpriteImage.cs b/ZXBStudio/DocumentEditors/ZXGraphics/ZXSpriteImage.cs index 4b03213..eff30ec 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/ZXSpriteImage.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/ZXSpriteImage.cs @@ -30,7 +30,7 @@ public ZXSpriteImage() Clear(Colors.White); } - public ZXSpriteImage(Sprite Sprite, int FrameNumber) + public ZXSpriteImage(ZXMapsTile Sprite, int FrameNumber) { bitmap = new WriteableBitmap(new PixelSize(Sprite.Width, Sprite.Height), new Vector(72, 72), Avalonia.Platform.PixelFormat.Rgba8888, Avalonia.Platform.AlphaFormat.Opaque); @@ -60,7 +60,7 @@ public unsafe void Clear(Color ClearColor) IsEmpty = true; } - public unsafe void RenderSprite(Sprite Sprite, int FrameNumber) + public unsafe void RenderSprite(ZXMapsTile Sprite, int FrameNumber) { try { @@ -151,7 +151,7 @@ uint ToRgba(Color Color) { return (uint)((255 << 24) | (Color.B << 16) | (Color.G << 8) | Color.R); } - private AttributeColor GetAttribute(Sprite Sprite, Pattern Pattern, int X, int Y) + private AttributeColor GetAttribute(ZXMapsTile Sprite, Pattern Pattern, int X, int Y) { int cW = Sprite.Width / 8; int cX = X / 8; diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/log/ExportManager.cs b/ZXBStudio/DocumentEditors/ZXGraphics/log/ExportManager.cs index b0c94ef..20369ad 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/log/ExportManager.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/log/ExportManager.cs @@ -1,5 +1,6 @@ using CoreSpectrum.SupportClasses; using Newtonsoft.Json; +using SixLabors.ImageSharp.Formats.Gif; using System; using System.Collections.Generic; using System.IO; @@ -11,6 +12,7 @@ using ZXBasicStudio.Common; using ZXBasicStudio.Common.TAPTools; using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; +using ZXBasicStudio.DocumentEditors.ZXMaps.Neg; using ZXBasicStudio.DocumentModel.Enums; using ZXBasicStudio.DocumentModel.Interfaces; using static System.Runtime.InteropServices.JavaScript.JSType; @@ -88,7 +90,24 @@ public bool Build(string BuildPath, ZXBuildStage Stage, ZXBuildType BuildType, Z } } break; - + case FileTypes.Tile: + { + var tiles = CreateTiles(fileData); + if (!ExportTiles(exportConfig, tiles)) + { + return false; + } + } + break; + case FileTypes.Map: + { + var map = CreateMap(fileData); + if (!ExportMap(exportConfig, map)) + { + return false; + } + } + break; } } @@ -389,12 +408,12 @@ public static void Export_TAP(FileTypeConfig fileType, Pattern[] patterns, Expor #region Sprites - private Sprite[] CreateSprites(byte[] fileData) + private ZXMapsTile[] CreateSprites(byte[] fileData) { try { var dataS = Encoding.UTF8.GetString(fileData); - var sprites = dataS.Deserializar(); + var sprites = dataS.Deserializar(); // Check attributes for ZX Spectrum mode foreach (var sprite in sprites) @@ -433,7 +452,7 @@ private Sprite[] CreateSprites(byte[] fileData) /// ExportConfig of the file /// Sprites to convert /// - public bool ExportSprites(ExportConfig exportConfig, IEnumerable sprites) + public bool ExportSprites(ExportConfig exportConfig, IEnumerable sprites) { // Export depending on the type string exportedData = ""; @@ -480,7 +499,7 @@ public bool ExportSprites(ExportConfig exportConfig, IEnumerable sprites /// ExportConfig information /// Sprites to convert /// string with the conversion commands for the export dialog samble textbox - public static string Export_Sprite_PutChars(ExportConfig exportConfig, IEnumerable sprites) + public static string Export_Sprite_PutChars(ExportConfig exportConfig, IEnumerable sprites) { string res = Export_Sprite_PutChars_Check(exportConfig, sprites); if (res.StartsWith("ERROR:")) @@ -506,7 +525,7 @@ public static string Export_Sprite_PutChars(ExportConfig exportConfig, IEnumerab } } - private static string Export_Sprite_PutChars_Check(ExportConfig exportConfig, IEnumerable sprites) + private static string Export_Sprite_PutChars_Check(ExportConfig exportConfig, IEnumerable sprites) { foreach (var sprite in sprites) { @@ -529,7 +548,7 @@ private static string Export_Sprite_PutChars_Check(ExportConfig exportConfig, IE #region DIM - public static string Export_Sprite_PutChars_DIM(ExportConfig exportConfig, IEnumerable sprites) + public static string Export_Sprite_PutChars_DIM(ExportConfig exportConfig, IEnumerable sprites) { int min = 0; switch (exportConfig.ArrayBase) @@ -740,7 +759,7 @@ public static string Export_Sprite_PutChars_DIM(ExportConfig exportConfig, IEnum } - private static string Export_Sprite_PutChars_Pattern(Sprite sprite, int n, ExportConfig exportConfig, int firstItem) + private static string Export_Sprite_PutChars_Pattern(ZXMapsTile sprite, int n, ExportConfig exportConfig, int firstItem) { var sb = new StringBuilder(); @@ -805,7 +824,7 @@ private static string Export_Sprite_PutChars_Pattern(Sprite sprite, int n, Expor } - private static string Export_Sprite_PutChars_Attribute(Sprite sprite, int n, ExportConfig exportConfig, int firstItem) + private static string Export_Sprite_PutChars_Attribute(ZXMapsTile sprite, int n, ExportConfig exportConfig, int firstItem) { var sb = new StringBuilder(); @@ -896,7 +915,7 @@ private static string Export_Sprite_PutChars_Attribute(Sprite sprite, int n, Exp #region ASM - public static string Export_Sprite_PutChars_ASM(ExportConfig exportConfig, IEnumerable sprites) + public static string Export_Sprite_PutChars_ASM(ExportConfig exportConfig, IEnumerable sprites) { int min = 0; @@ -988,7 +1007,7 @@ public static string Export_Sprite_PutChars_ASM(ExportConfig exportConfig, IEnum } - private static string Export_Sprite_PutChars_Pattern_ASM(Sprite sprite, int n, ExportConfig exportConfig) + private static string Export_Sprite_PutChars_Pattern_ASM(ZXMapsTile sprite, int n, ExportConfig exportConfig) { var sb = new StringBuilder(); @@ -1030,7 +1049,7 @@ private static string Export_Sprite_PutChars_Pattern_ASM(Sprite sprite, int n, E } - private static string Export_Sprite_PutChars_Attribute_ASM(Sprite sprite, int n, ExportConfig exportConfig) + private static string Export_Sprite_PutChars_Attribute_ASM(ZXMapsTile sprite, int n, ExportConfig exportConfig) { var sb = new StringBuilder(); @@ -1087,7 +1106,7 @@ private static string Export_Sprite_PutChars_Attribute_ASM(Sprite sprite, int n, #region TAP and BIN - public static string Export_Sprite_PutChars_BIN(ExportConfig exportConfig, IEnumerable sprites) + public static string Export_Sprite_PutChars_BIN(ExportConfig exportConfig, IEnumerable sprites) { var binData = Export_Sprite_PutChars_GetBinaryData(sprites); ServiceLayer.Files_SaveFileData(exportConfig.ExportFilePath, binData); @@ -1095,7 +1114,7 @@ public static string Export_Sprite_PutChars_BIN(ExportConfig exportConfig, IEnum } - public static string Export_Sprite_PutChars_TAP(ExportConfig exportConfig, IEnumerable sprites) + public static string Export_Sprite_PutChars_TAP(ExportConfig exportConfig, IEnumerable sprites) { var binData = Export_Sprite_PutChars_GetBinaryData(sprites); binData = ServiceLayer.Bin2Tap(exportConfig.ZXFileName, exportConfig.ZXAddress, binData); @@ -1104,7 +1123,7 @@ public static string Export_Sprite_PutChars_TAP(ExportConfig exportConfig, IEnum } - private static byte[] Export_Sprite_PutChars_GetBinaryData(IEnumerable sprites) + private static byte[] Export_Sprite_PutChars_GetBinaryData(IEnumerable sprites) { var binData = new List(); @@ -1137,7 +1156,7 @@ private static byte[] Export_Sprite_PutChars_GetBinaryData(IEnumerable s /// ExportConfig information /// Sprites to convert /// string with the conversion commands for the export dialog samble textbox - public static string Export_Sprite_MaskedSprites(ExportConfig exportConfig, IEnumerable sprites) + public static string Export_Sprite_MaskedSprites(ExportConfig exportConfig, IEnumerable sprites) { var res = Export_Sprite_MaskedSprites_Check(exportConfig, sprites); if (res.StartsWith("ERROR:")) @@ -1172,7 +1191,7 @@ public static string Export_Sprite_MaskedSprites(ExportConfig exportConfig, IEnu /// masking, frame count, and dimensions if it is marked for export. /// A string indicating the result of the validation. Returns "OK" if all sprites are valid for export; /// otherwise, returns an error message describing the first encountered issue. - private static string Export_Sprite_MaskedSprites_Check(ExportConfig exportConfig, IEnumerable sprites) + private static string Export_Sprite_MaskedSprites_Check(ExportConfig exportConfig, IEnumerable sprites) { string txt = ""; foreach (var sprite in sprites) @@ -1205,7 +1224,7 @@ private static string Export_Sprite_MaskedSprites_Check(ExportConfig exportConfi } - private static byte[] Export_Sprite_MaskedSprites_GenerateData(ExportConfig exportConfig, Sprite sprite) + private static byte[] Export_Sprite_MaskedSprites_GenerateData(ExportConfig exportConfig, ZXMapsTile sprite) { try { @@ -1240,7 +1259,7 @@ private static byte[] Export_Sprite_MaskedSprites_GenerateData(ExportConfig expo #region DIM - public static string Export_Sprite_MaskedSprites_DIM(ExportConfig exportConfig, IEnumerable sprites) + public static string Export_Sprite_MaskedSprites_DIM(ExportConfig exportConfig, IEnumerable sprites) { var sb = new StringBuilder(); sb.AppendLine("'- Sprite definitions --------------------------------------"); @@ -1326,7 +1345,7 @@ public static string Export_Sprite_MaskedSprites_DIM(ExportConfig exportConfig, #region ASM - public static string Export_Sprite_MaskedSprites_ASM(ExportConfig exportConfig, IEnumerable sprites) + public static string Export_Sprite_MaskedSprites_ASM(ExportConfig exportConfig, IEnumerable sprites) { var sb = new StringBuilder(); sb.AppendLine("'- Sprite definitions --------------------------------------"); @@ -1387,7 +1406,7 @@ public static string Export_Sprite_MaskedSprites_ASM(ExportConfig exportConfig, #region TAP and BIN - public static string Export_Sprite_MaskedSprites_BIN(ExportConfig exportConfig, IEnumerable sprites) + public static string Export_Sprite_MaskedSprites_BIN(ExportConfig exportConfig, IEnumerable sprites) { var binData = new List(); foreach (var sprite in sprites) @@ -1408,7 +1427,7 @@ public static string Export_Sprite_MaskedSprites_BIN(ExportConfig exportConfig, } - public static string Export_Sprite_MaskedSprites_TAP(ExportConfig exportConfig, IEnumerable sprites) + public static string Export_Sprite_MaskedSprites_TAP(ExportConfig exportConfig, IEnumerable sprites) { var binData = new List(); foreach (var sprite in sprites) @@ -1436,5 +1455,794 @@ public static string Export_Sprite_MaskedSprites_TAP(ExportConfig exportConfig, #endregion + + + #region Tiles + + private ZXMapsTile[] CreateTiles(byte[] fileData) + { + try + { + var dataS = Encoding.UTF8.GetString(fileData); + var tiles = dataS.Deserializar(); + + // Check attributes for ZX Spectrum mode + foreach (var tile in tiles) + { + if (tile != null && tile.GraphicMode == GraphicsModes.ZXSpectrum) + { + foreach (var pattern in tile.Patterns) + { + if (pattern.Attributes == null) + { + int cW = tile.Width / 8; + int cH = tile.Height / 8; + pattern.Attributes = new AttributeColor[cW * cH]; + foreach (var attr in pattern.Attributes) + { + attr.Ink = 1; + attr.Paper = 0; + } + } + } + } + } + + return tiles; + } + catch + { + return null; + } + } + + + public bool ExportTiles(ExportConfig exportConfig, IEnumerable tiles) + { + // Export depending on the type + string exportedData = ""; + bool createTextFile = false; + + switch (exportConfig.ExportType) + { + case ExportTypes.PutChars: + exportedData = Export_Tiles_PutChars(exportConfig, tiles); + createTextFile = true; + break; + default: + return true; + } + + if (!string.IsNullOrEmpty(exportedData) && createTextFile) + { + var sb = new StringBuilder(); + sb.AppendLine("'------------------------------------------------------------------------------"); + sb.AppendLine("'- Build data generated by ZXBasicStudio --------------------------------------"); + sb.AppendLine("'- Do not modify this file, its contents are deleted at each build ------------"); + sb.AppendLine("'------------------------------------------------------------------------------"); + sb.AppendLine(""); + sb.Append(exportedData); + ServiceLayer.Files_SaveFileString(exportConfig.ExportFilePath, sb.ToString()); + } + return true; + } + + + + #region PutChars + + /// + /// Generate the export data for PutChars Tiles + /// + /// ExportConfig information + /// Tiles to convert + /// string with the conversion commands for the export dialog samble textbox + public static string Export_Tiles_PutChars(ExportConfig exportConfig, IEnumerable tiles) + { + string res = Export_Tile_PutChars_Check(exportConfig, tiles); + if (res.StartsWith("ERROR:")) + { + if (OutputLog != null) + { + OutputLog.WriteLine(res); + } + return res; + } + switch (exportConfig.ExportDataType) + { + case ExportDataTypes.DIM: + return Export_Tile_PutChars_DIM(exportConfig, tiles); + case ExportDataTypes.ASM: + return Export_Tile_PutChars_ASM(exportConfig, tiles); + case ExportDataTypes.BIN: + return Export_Tile_PutChars_BIN(exportConfig, tiles); + case ExportDataTypes.TAP: + return Export_Tile_PutChars_TAP(exportConfig, tiles); + default: + return "ERROR: Not implemented!"; + } + } + + + private static string Export_Tile_PutChars_Check(ExportConfig exportConfig, IEnumerable tiles) + { + return "OK"; + } + + + #region DIM + + public static string Export_Tile_PutChars_DIM(ExportConfig exportConfig, IEnumerable tiles) + { + int min = 0; + switch (exportConfig.ArrayBase) + { + case 1: + min = 1; + break; + case 2: + { + var settings = ServiceLayer.GetProjectSettings(); + if (settings != null) + { + if (settings.ArrayBase == 1) + { + min = 1; + } + } + + } + break; + } + + var sb = new StringBuilder(); + sb.AppendLine("'- Tile definitions --------------------------------------"); + + var t = tiles.ElementAt(0); + if (t == null) + { + return ""; + } + var max = tiles.Count() - 1; // The last tile is always null + var num = max + min; + sb.AppendLine(string.Format( + "DIM {0}({1},{2}) AS UByte => {{ _", + exportConfig.LabelName, + num - 1, + ((t.Width / 8) * t.Height) - 1 + min + )); + + for (int n = 0; n < max; n++) + { + var tile = tiles.ElementAt(n); + if (tile == null) + { + continue; + } + // Content + sb.AppendLine("\t{ _"); + var data = Export_Tile_PutChars_Pattern(tile, 0, exportConfig); + sb.Append(data); + if (n == (max - 1)) + { + sb.AppendLine("\t} _"); + } + else + { + sb.AppendLine("\t}, _"); + } + + } + // Footer + sb.AppendLine("}"); + sb.AppendLine(""); + + + // Attributes + if (exportConfig.IncludeAttr) + { + sb.AppendLine(string.Format( + "DIM {0}_Attr({1},{2}) AS UByte => {{ _", + exportConfig.LabelName, + num - 1, + ((t.Width / 8) * (t.Height / 8)) - 1 + min + )); + foreach (var tile in tiles) + { + if (tile == null) + { + continue; + } + // Content + var data = Export_Tile_PutChars_Attribute(tile, 0, exportConfig, 0); + sb.Append(data); + // Footer + } + sb.AppendLine(""); + + sb.Append(" _\r\n}"); + sb.AppendLine(""); + } + return sb.ToString(); + } + + + private static string Export_Tile_PutChars_Pattern(ZXMapsTile tile, int n, ExportConfig exportConfig) + { + var sb = new StringBuilder(); + + var pattern = tile.Patterns[n]; + var data = ServiceLayer.Files_CreateBinDataUpDown(pattern, tile.Width, tile.Height); + + int col = 0; + int row = 0; + foreach (var d in data) + { + if (col == 0) + { + if (row == 0) + { + row = 1; + } + else + { + sb.AppendLine(", _"); + } + sb.Append("\t\t"); + } + if (col > 0) + { + sb.Append(","); + } + var x = string.Format("${0:X2}", d); + sb.Append(x); + + col++; + if (col >= 8) + { + col = 0; + } + } + sb.AppendLine(" _"); + + return sb.ToString(); + } + + + private static string Export_Tile_PutChars_Attribute(ZXMapsTile tile, int n, ExportConfig exportConfig, int firstItem) + { + var sb = new StringBuilder(); + + var pattern = tile.Patterns[n]; + + if (pattern.Attributes == null) + { + return ""; + } + + if (tile.Masked) + { + if (tile.Frames > 2) + { + if (n > firstItem) + { + sb.AppendLine(", _"); + } + sb.AppendLine("\t{ _"); + } + } + else + if (tile.Frames > 1) + { + if (n > firstItem) + { + sb.AppendLine(", _"); + } + sb.AppendLine("\t{ _"); + } + + int col = 0; + int row = 0; + int max = (tile.Width / 8) * (tile.Height / 8); + int idx = 0; + foreach (var d in pattern.Attributes) + { + if (idx++ >= max) + { + break; + } + if (col == 0) + { + if (row == 0) + { + row = 1; + } + else + { + sb.AppendLine(", _"); + } + sb.Append("\t"); + if (tile.Frames > 1) + { + sb.Append("\t"); + } + } + if (col > 0) + { + sb.Append(","); + } + var x = string.Format("${0:X2}", d.Attribute); + sb.Append(x); + + col++; + if (col >= 8) + { + col = 0; + } + } + sb.AppendLine(" _"); + + if (tile.Frames == 1) + { + sb.Append("}"); + } + else + { + sb.Append("\t}"); + } + + return sb.ToString(); + } + + #endregion + + + #region ASM + + + public static string Export_Tile_PutChars_ASM(ExportConfig exportConfig, IEnumerable tiles) + { + int min = 0; + + var sb = new StringBuilder(); + sb.AppendLine("'- Tile definitions --------------------------------------"); + + // All tiles + foreach (var tile in tiles) + { + if (tile == null || !tile.Export) + { + continue; + } + + if (tile.Frames == 0) + { + continue; + } + + // Header + sb.AppendLine(string.Format( + "{0}{1}:", + exportConfig.LabelName, + tile.Name.Replace(" ", "_"))); + sb.AppendLine("ASM"); + + // Data frames + for (int n = 0; n < tile.Frames; n++) + { + if (tile.Masked && (n % 2) == 1) + { + continue; + } + // Content + var data = Export_Tile_PutChars_Pattern_ASM(tile, n, exportConfig); + sb.Append(data); + } + sb.AppendLine("END ASM"); + sb.AppendLine(""); + + // Mask frames + if (tile.Masked) + { + // Header + sb.AppendLine(string.Format( + "{0}{1}_Mask:", + exportConfig.LabelName, + tile.Name.Replace(" ", "_"))); + sb.AppendLine("ASM"); + for (int n = 0; n < tile.Frames; n++) + { + if ((n % 2) == 0) + { + continue; + } + // Content + var data = Export_Tile_PutChars_Pattern_ASM(tile, n, exportConfig); + sb.Append(data); + } + sb.AppendLine("END ASM"); + sb.AppendLine(""); + } + + // Attributes + if (exportConfig.IncludeAttr && tile.GraphicMode == GraphicsModes.ZXSpectrum) + { + // Header + sb.AppendLine(string.Format( + "{0}{1}_Attr:", + exportConfig.LabelName, + tile.Name.Replace(" ", "_"))); + sb.AppendLine("ASM"); + for (int n = 0; n < tile.Frames; n++) + { + if (tile.Masked && (n % 2) == 1) + { + continue; + } + // Content + var data = Export_Tile_PutChars_Attribute_ASM(tile, n, exportConfig); + sb.Append(data); + } + sb.AppendLine("END ASM"); + sb.AppendLine(""); + } + } + + return sb.ToString(); + } + + + private static string Export_Tile_PutChars_Pattern_ASM(ZXMapsTile tile, int n, ExportConfig exportConfig) + { + var sb = new StringBuilder(); + + var pattern = tile.Patterns[n]; + var data = ServiceLayer.Files_CreateBinDataUpDown(pattern, tile.Width, tile.Height); + + int col = 0; + int row = 0; + foreach (var d in data) + { + if (col == 0) + { + if (row == 0) + { + row = 1; + } + else + { + sb.AppendLine(""); + } + sb.Append("\tDB "); + } + if (col > 0) + { + sb.Append(","); + } + var x = string.Format("${0:X2}", d); + sb.Append(x); + + col++; + if (col >= 8) + { + col = 0; + } + } + sb.AppendLine(""); + + return sb.ToString(); + } + + + private static string Export_Tile_PutChars_Attribute_ASM(ZXMapsTile tile, int n, ExportConfig exportConfig) + { + var sb = new StringBuilder(); + + var pattern = tile.Patterns[n]; + + if (pattern.Attributes == null) + { + return ""; + } + + int col = 0; + int row = 0; + int max = (tile.Width / 8) * (tile.Height / 8); + int idx = 0; + foreach (var d in pattern.Attributes) + { + if (idx++ >= max) + { + break; + } + if (col == 0) + { + if (row == 0) + { + row = 1; + } + else + { + sb.AppendLine(""); + } + sb.Append("\tDB "); + } + if (col > 0) + { + sb.Append(","); + } + var x = string.Format("${0:X2}", d.Attribute); + sb.Append(x); + + col++; + if (col >= 8) + { + col = 0; + } + } + sb.AppendLine(""); + + return sb.ToString(); + } + + #endregion + + + #region TAP and BIN + + + public static string Export_Tile_PutChars_BIN(ExportConfig exportConfig, IEnumerable tiles) + { + var binData = Export_Tile_PutChars_GetBinaryData(tiles); + ServiceLayer.Files_SaveFileData(exportConfig.ExportFilePath, binData); + return ""; + } + + + public static string Export_Tile_PutChars_TAP(ExportConfig exportConfig, IEnumerable tiles) + { + var binData = Export_Tile_PutChars_GetBinaryData(tiles); + binData = ServiceLayer.Bin2Tap(exportConfig.ZXFileName, exportConfig.ZXAddress, binData); + ServiceLayer.Files_SaveFileData(exportConfig.ExportFilePath, binData); + return ""; + } + + + private static byte[] Export_Tile_PutChars_GetBinaryData(IEnumerable tiles) + { + var binData = new List(); + + foreach (var tile in tiles) + { + if (tile != null) + { + foreach (var pattern in tile.Patterns) + { + var data = ServiceLayer.Files_CreateBinDataUpDown(pattern, tile.Width, tile.Height); + binData.AddRange(data); + } + } + } + + return binData.ToArray(); + } + + #endregion + + #endregion + + #endregion + + + #region Maps + + private ZXMapsMap CreateMap(byte[] fileData) + { + try + { + var dataS = Encoding.UTF8.GetString(fileData); + var map = dataS.Deserializar(); + return map; + } + catch + { + return null; + } + } + + + public bool ExportMap(ExportConfig exportConfig, ZXMapsMap map) + { + // Export depending on the type + string exportedData = ""; + bool createTextFile = false; + + switch (exportConfig.ExportType) + { + case ExportTypes.Array: + exportedData = Export_Map_Array(exportConfig, map); + createTextFile = true; + break; + default: + return true; + } + + if (!string.IsNullOrEmpty(exportedData) && createTextFile) + { + var sb = new StringBuilder(); + sb.AppendLine("'------------------------------------------------------------------------------"); + sb.AppendLine("'- Build data generated by ZXBasicStudio --------------------------------------"); + sb.AppendLine("'- Do not modify this file, its contents are deleted at each build ------------"); + sb.AppendLine("'------------------------------------------------------------------------------"); + sb.AppendLine(""); + sb.Append(exportedData); + ServiceLayer.Files_SaveFileString(exportConfig.ExportFilePath, sb.ToString()); + } + return true; + } + + + /// + /// Generate the export data for Map as Array + /// + /// ExportConfig information + /// Tiles to convert + /// string with the conversion commands for the export dialog samble textbox + public static string Export_Map_Array(ExportConfig exportConfig, ZXMapsMap map) + { + string res = Export_Map_Check(exportConfig, map); + if (res.StartsWith("ERROR:")) + { + if (OutputLog != null) + { + OutputLog.WriteLine(res); + } + return res; + } + + switch (exportConfig.ExportDataType) + { + case ExportDataTypes.DIM: + return Export_Map_Array_DIM(exportConfig, map); + //case ExportDataTypes.ASM: + // return Export_Tile_PutChars_ASM(exportConfig, tiles); + //case ExportDataTypes.BIN: + // return Export_Tile_PutChars_BIN(exportConfig, tiles); + //case ExportDataTypes.TAP: + // return Export_Tile_PutChars_TAP(exportConfig, tiles); + default: + return "ERROR: Not implemented!"; + } + } + + + private static string Export_Map_Check(ExportConfig exportConfig, ZXMapsMap map) + { + return "OK"; + } + + + private static string Export_Map_Array_DIM(ExportConfig exportConfig, ZXMapsMap map) + { + int min = 0; + switch (exportConfig.ArrayBase) + { + case 1: + min = 1; + break; + case 2: + { + var settings = ServiceLayer.GetProjectSettings(); + if (settings != null) + { + if (settings.ArrayBase == 1) + { + min = 1; + } + } + + } + break; + } + + var sb = new StringBuilder(); + sb.AppendLine("'- Map definitions --------------------------------------"); + + + sb.AppendLine($"DIM {exportConfig.LabelName}({map.Width - 1 + min},{map.Height - 1 + min}) AS UByte => {{ _"); + + for (int x = 0; x < map.Width; x++) + { + sb.Append("\t{ "); + for (int y = 0; y < map.Height; y++) + { + var d = $"{map.TileArrays[x, y].TileId.ToString("000")}"; + sb.Append(d); + if (y == (map.Height - 1)) + { + if (x == (map.Width - 1)) + { + sb.AppendLine("} _"); + } + else + { + sb.AppendLine("}, _"); + } + } + else + { + sb.Append(","); + } + } + } + sb.AppendLine("}"); + sb.AppendLine(""); + + return sb.ToString(); + } + + + + private static string Export_Map_Array_DIM_DEPRECATED(ExportConfig exportConfig, ZXMapsMap map) + { + int min = 0; + switch (exportConfig.ArrayBase) + { + case 1: + min = 1; + break; + case 2: + { + var settings = ServiceLayer.GetProjectSettings(); + if (settings != null) + { + if (settings.ArrayBase == 1) + { + min = 1; + } + } + + } + break; + } + + var sb = new StringBuilder(); + sb.AppendLine("'- Map definitions --------------------------------------"); + + + sb.AppendLine($"DIM {exportConfig.LabelName}({map.Height - 1 + min},{map.Width - 1 + min}) AS UByte => {{ _"); + + for (int y = 0; y < map.Height; y++) + { + sb.Append("\t{ "); + for (int x = 0; x < map.Width; x++) + { + var d = $"{map.TileArrays[x, y].TileId.ToString("000")}"; + sb.Append(d); + if (x == (map.Width - 1)) + { + if (y == (map.Height - 1)) + { + sb.AppendLine("} _"); + } + else + { + sb.AppendLine("}, _"); + } + } + else + { + sb.Append(","); + } + } + } + sb.AppendLine("}"); + sb.AppendLine(""); + + return sb.ToString(); + } + + #endregion + } } diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/log/ServiceLayer.cs b/ZXBStudio/DocumentEditors/ZXGraphics/log/ServiceLayer.cs index da4a606..554eed6 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/log/ServiceLayer.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/log/ServiceLayer.cs @@ -1,22 +1,24 @@ -using ZXBasicStudio.DocumentEditors.ZXGraphics.dat; -using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; +using Avalonia.Controls.Shapes; +using Avalonia.Metadata; +using Avalonia.Platform.Storage; +using AvaloniaEdit; +using Newtonsoft.Json; +using SixLabors.ImageSharp.PixelFormats; using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing.Imaging; using System.IO; using System.Linq; +using System.Runtime; using ZXBasicStudio.Classes; using ZXBasicStudio.Common; -using System.Runtime; -using Newtonsoft.Json; using ZXBasicStudio.Common.TAPTools; +using ZXBasicStudio.DocumentEditors.ZXGraphics.dat; +using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; using ZXBasicStudio.DocumentModel.Classes; -using ZXBasicStudio.IntegratedDocumentTypes.ZXGraphics; using ZXBasicStudio.IntegratedDocumentTypes.CodeDocuments.Basic; -using System.Drawing.Imaging; -using Avalonia.Metadata; -using Avalonia.Controls.Shapes; -using AvaloniaEdit; -using System.Diagnostics; +using ZXBasicStudio.IntegratedDocumentTypes.ZXGraphics; namespace ZXBasicStudio.DocumentEditors.ZXGraphics.log { @@ -506,6 +508,42 @@ public static ExportConfig Export_Sprite_GetDefaultConfig(string fileName) } + /// + /// Creates the default export config for a tile document + /// + /// Document file name + /// The new export configuration + public static ExportConfig Export_Tile_GetDefaultConfig(string fileName) + { + var docType = ZXDocumentProvider.GetDocumentTypeInstance(typeof(ZXBasicDocument)); + var exportConfig = new ExportConfig(); + exportConfig.ArrayBase = 0; + exportConfig.AutoExport = true; + exportConfig.ExportFilePath = fileName + ".bas"; + exportConfig.ExportType = ExportTypes.PutChars; + exportConfig.LabelName = System.IO.Path.GetFileNameWithoutExtension(fileName).Replace(" ", "_"); + return exportConfig; + } + + + /// + /// Creates the default export config for a map document + /// + /// Document file name + /// The new export configuration + public static ExportConfig Export_Map_GetDefaultConfig(string fileName) + { + var docType = ZXDocumentProvider.GetDocumentTypeInstance(typeof(ZXBasicDocument)); + var exportConfig = new ExportConfig(); + exportConfig.ArrayBase = 0; + exportConfig.AutoExport = true; + exportConfig.ExportFilePath = fileName + ".bas"; + exportConfig.ExportType = ExportTypes.Array; + exportConfig.LabelName = System.IO.Path.GetFileNameWithoutExtension(fileName).Replace(" ", "_"); + return exportConfig; + } + + public static bool Export_SetConfigFile(string fileName, ExportConfig exportConfig) { try @@ -615,7 +653,7 @@ public static PaletteColor[] GetPalette(GraphicsModes mode) /// Old Width of the sprite, the new must set in sprite parameter /// Old Height of the spritye, the new must set in sprite parameter /// True if OK or False if error - public static bool SpriteData_Resize(ref Sprite sprite, int oldWidth, int oldHeight) + public static bool SpriteData_Resize(ref ZXMapsTile sprite, int oldWidth, int oldHeight) { for (int p = 0; p < sprite.Patterns.Count; p++) { @@ -653,7 +691,7 @@ public static bool SpriteData_Resize(ref Sprite sprite, int oldWidth, int oldHei /// Old sprite with new graphic mode property set to target. Patterns will be updated. /// Old graphic mode, the new must set in sprite parameter /// True if OK or False if error - public static bool SpriteData_ChangeMode(ref Sprite sprite, GraphicsModes oldMode) + public static bool SpriteData_ChangeMode(ref ZXMapsTile sprite, GraphicsModes oldMode) { // TODO: Do it!!! return true; @@ -666,7 +704,7 @@ public static bool SpriteData_ChangeMode(ref Sprite sprite, GraphicsModes oldMod /// Old sprite with new mask status property set to target. Patterns will be updated. /// Old mask value, the new must set in sprite parameter /// True if OK or False if error - public static bool SpriteData_ChangeMasked(ref Sprite sprite, bool oldMasked) + public static bool SpriteData_ChangeMasked(ref ZXMapsTile sprite, bool oldMasked) { // TODO: Do it!!! return true; @@ -679,7 +717,7 @@ public static bool SpriteData_ChangeMasked(ref Sprite sprite, bool oldMasked) /// Old sprite with new Frames property set to target. Patterns will be updated. /// Old Frames value, the new must set in sprite parameter /// True if OK or False if error - public static bool SpriteData_ChangeFrames(ref Sprite sprite, byte oldFrames) + public static bool SpriteData_ChangeFrames(ref ZXMapsTile sprite, byte oldFrames) { // TODO: Do it!!! return true; @@ -688,7 +726,6 @@ public static bool SpriteData_ChangeFrames(ref Sprite sprite, byte oldFrames) #endregion - #region Tools public static int GetColor(byte r, byte g, byte b, PaletteColor[] palette, byte cutOff = 5) @@ -765,5 +802,100 @@ private static double GetColorDistance(PaletteColor c1, PaletteColor c2) } #endregion + + + #region Screen + + /// + /// Obtiene el offset dentro de la memoria de pantalla del ZX Spectrum + /// para el byte que contiene el píxel (x,y). + /// + /// Coordenada X (0-255) + /// Coordenada Y (0-191) + /// Offset (0-6143) + public static int GetSpectrumScreenOffset(int x, int y) + { + if (x < 0 || x > 255 || y<0 || y>191) + { + return -1; + } + + int offset = + ((y & 0b11000000) << 5) | // Bits 6-7 -> 11-12 + ((y & 0b00000111) << 8) | // Bits 0-2 -> 8-10 + ((y & 0b00111000) << 2) | // Bits 3-5 -> 5-7 + (x >> 3); // Columna (0-31) + return offset; + } + + #endregion + + + #region Images + + public static SixLabors.ImageSharp.Image LoadImage(IStorageFile file) + { + try + { + SixLabors.ImageSharp.Image imageData = null; + + using (var stream = file.OpenReadAsync().Result) + { + if (file.Name.ToLower().EndsWith(".scr")) + { + // Load SCR file + var scrData = new byte[6912]; + stream.ReadAsync(scrData, 0, 6912).GetAwaiter(); + imageData = new SixLabors.ImageSharp.Image(256, 192); + var palete = ZXGraphics.log.ServiceLayer.GetPalette(GraphicsModes.ZXSpectrum); + for (int y = 0; y < 192; y++) + { + for (int cx = 0; cx < 32; cx++) + { + int pixelIndex = ZXGraphics.log.ServiceLayer.GetSpectrumScreenOffset(cx * 8, y); + int attrIndex = 6144 + (((y / 8) * 32) + cx); + var attr = new AttributeColor() + { + Attribute = scrData[attrIndex] + }; + var paper = palete[attr.Paper]; + var colorOFF = new Rgba32(paper.Red, paper.Green, paper.Blue); + var ink = palete[attr.Ink]; + var colorON = new Rgba32(ink.Red, ink.Green, ink.Blue); + + byte byteData = scrData[pixelIndex]; + string bits = $"{byteData:B8}"; + for (int bx = 0; bx < 8; bx++) + { + Rgba32 color = new Rgba32(0); + if (bits.Substring(bx, 1) == "0") + { + color = colorOFF; + } + else + { + color = colorON; + } + int x = (cx * 8) + bx; + imageData[x, y] = color; + } + } + } + } + else + { + // Load other image formats + imageData = SixLabors.ImageSharp.Image.Load(stream); + } + } + return imageData; + } + catch (Exception ex) + { + return null; + } + } + + #endregion } } \ No newline at end of file diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/neg/ExportTypes.cs b/ZXBStudio/DocumentEditors/ZXGraphics/neg/ExportTypes.cs index b08d00b..3656f39 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/neg/ExportTypes.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/neg/ExportTypes.cs @@ -20,10 +20,13 @@ public enum ExportTypes Dim, Data, - // Sprites + // Sprites and tiles PutChars, MaskedSprites, GUSprite, - FourSprites + FourSprites, + + // Maps + Array, } } diff --git a/ZXBStudio/DocumentEditors/ZXGraphics/neg/Sprite.cs b/ZXBStudio/DocumentEditors/ZXGraphics/neg/Sprite.cs index a7f1319..a85991d 100644 --- a/ZXBStudio/DocumentEditors/ZXGraphics/neg/Sprite.cs +++ b/ZXBStudio/DocumentEditors/ZXGraphics/neg/Sprite.cs @@ -3,28 +3,29 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; -namespace ZXBasicStudio.DocumentEditors.ZXGraphics.neg +namespace ZXBasicStudio.DocumentEditors.ZXMaps.Neg { /// - /// Represents an sprite with his properties + /// Represents an tile with his properties /// - public class Sprite + public class Tile { /// - /// Id of the sprite + /// Id of the tile /// public int Id { get; set; } /// - /// Name of the sprite + /// Name of the tile /// public string Name { get; set; } /// - /// Graphics mode of the sprite + /// Graphics mode of the tile /// public GraphicsModes GraphicMode { get; set; } /// - /// Width of the sprite in pixels + /// Width of the tile in pixels /// public int Width { get; set; } /// @@ -32,11 +33,11 @@ public class Sprite /// public int Height { get; set; } /// - /// True if the sprite is masked (not suported in all modes) + /// True if the tile is masked (not suported in all modes) /// public bool Masked { get; set; } /// - /// Number of frames for the sprite + /// Number of frames for the tile /// public byte Frames { get; set; } /// @@ -44,7 +45,7 @@ public class Sprite /// public byte CurrentFrame { get; set; } /// - /// Patterns for the sprite (one pattern for frame) + /// Patterns for the tile (one pattern for frame) /// public List Patterns { get; set; } /// @@ -52,11 +53,11 @@ public class Sprite /// public byte DefaultColor { get; set; } /// - /// Palete color for the sprite + /// Palete color for the tile /// public PaletteColor[] Palette { get; set; } /// - /// True when the sprite was auto-exported + /// True when the tile was auto-exported /// public bool Export { get; set; } } diff --git a/ZXBStudio/DocumentEditors/ZXMaps/LayerItemControl.axaml b/ZXBStudio/DocumentEditors/ZXMaps/LayerItemControl.axaml new file mode 100644 index 0000000..665ac75 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/LayerItemControl.axaml @@ -0,0 +1,22 @@ + + + + + + + + + + diff --git a/ZXBStudio/DocumentEditors/ZXMaps/LayerItemControl.axaml.cs b/ZXBStudio/DocumentEditors/ZXMaps/LayerItemControl.axaml.cs new file mode 100644 index 0000000..09008dd --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/LayerItemControl.axaml.cs @@ -0,0 +1,61 @@ +using Avalonia; +using Avalonia.Controls; +//using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Avalonia.Threading; +using AvaloniaEdit.Folding; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ZXBasicStudio.Common; +using ZXBasicStudio.DocumentEditors.NextDows.neg; +using ZXBasicStudio.DocumentEditors.ZXGraphics; +using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; +using ZXBasicStudio.DocumentEditors.ZXMaps.Neg; +using ZXBasicStudio.DocumentEditors.ZXTextEditor.Classes.Folding; +using ZXBasicStudio.DocumentModel.Classes; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps +{ + public partial class LayerItemControl : UserControl + { + public ZXMapsLayer Layer { get; set; } + + public bool Selected + { + get + { + return _Selected; + } + set + { + _Selected = value; + Refresh(); + } + } + private bool _Selected = false; + + + public LayerItemControl() + { + InitializeComponent(); + } + + + public bool Initialize(ZXMapsLayer layer) + { + btnShowLayer.IsVisible = layer.Visible; + btnHideLayer.IsVisible = !layer.Visible; + txtLayerName.Text = layer.Name; + _Selected = layer.Selected; + return true; + } + + + public void Refresh() + { + svgSelected.IsVisible = _Selected; + } + } +} \ No newline at end of file diff --git a/ZXBStudio/DocumentEditors/ZXMaps/LayersControl.axaml b/ZXBStudio/DocumentEditors/ZXMaps/LayersControl.axaml new file mode 100644 index 0000000..6f86c4a --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/LayersControl.axaml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/ZXBStudio/DocumentEditors/ZXMaps/LayersControl.axaml.cs b/ZXBStudio/DocumentEditors/ZXMaps/LayersControl.axaml.cs new file mode 100644 index 0000000..b132aab --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/LayersControl.axaml.cs @@ -0,0 +1,70 @@ +using Avalonia; +using Avalonia.Controls; +//using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Avalonia.Threading; +using AvaloniaEdit.Folding; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ZXBasicStudio.Common; +using ZXBasicStudio.DocumentEditors.NextDows.neg; +using ZXBasicStudio.DocumentEditors.ZXGraphics; +using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; +using ZXBasicStudio.DocumentEditors.ZXMaps.Neg; +using ZXBasicStudio.DocumentEditors.ZXTextEditor.Classes.Folding; +using ZXBasicStudio.DocumentModel.Classes; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps +{ + public partial class LayersControl : UserControl + { + private List Layers = null; + private List LayerItems = new List(); + + + public LayersControl() + { + InitializeComponent(); + } + + + public void AddLayer(ZXMapsLayer layer) + { + + } + + + public bool Initialize(List layers) + { + this.Layers = layers; + Refresh(); + return true; + } + + + public void Refresh() + { + pnlLayers.Children.Clear(); + LayerItems.Clear(); + bool first = true; + foreach (ZXMapsLayer layer in Layers.OrderBy(d=>d.Order)) + { + var li=new LayerItemControl(); + LayerItems.Add(li); + pnlLayers.Children.Add(li); + li.Initialize(layer); + if (first) + { + li.Selected = true; + first = false; + } + else + { + li.Selected = false; + } + } + } + } +} \ No newline at end of file diff --git a/ZXBStudio/DocumentEditors/ZXMaps/Log/ServiceLayer_Maps.cs b/ZXBStudio/DocumentEditors/ZXMaps/Log/ServiceLayer_Maps.cs new file mode 100644 index 0000000..321f633 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/Log/ServiceLayer_Maps.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ZXBasicStudio.Classes; +using ZXBasicStudio.Common; +using ZXBasicStudio.DocumentEditors.ZXMaps.Neg; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps.Log +{ + public static class ServiceLayer_Maps + { + + public static ZXMapsMap Maps_LoadMapByName(string name) + { + try + { + var fileName = Path.Combine(ZXProjectManager.Current.ProjectPath, name + ".zxmap"); + if (File.Exists(fileName)) + { + var json = File.ReadAllText(fileName); + return json.Deserializar(); + } + else + { + return null; + } + } + catch (Exception ex) + { + //MessageBox.Show($"Error loading file {FileName}: {ex.Message}"); + return null; + } + } + + + public static bool Maps_SaveMap(string fileName, ZXMapsMap map) + { + try + { + //var fileName = Path.Combine(ZXProjectManager.Current.ProjectPath, map.Name + ".zxmap"); + var json = map.Serializar(); + File.WriteAllText(fileName, json); + return true; + } + catch (Exception ex) + { + //MessageBox.Show($"Error saving file {FileName}: {ex.Message}"); + return false; + } + } + + + public static ZXMapsTiles Tiles_LoadMapsTiles(string fileName) + { + try + { + var json = File.ReadAllText(fileName); + return json.Deserializar(); + } + catch (Exception ex) + { + //MessageBox.Show($"Error saving file {FileName}: {ex.Message}"); + return null; + } + } + } +} diff --git a/ZXBStudio/DocumentEditors/ZXMaps/MapExportDialog.axaml b/ZXBStudio/DocumentEditors/ZXMaps/MapExportDialog.axaml new file mode 100644 index 0000000..1b62f6f --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/MapExportDialog.axaml @@ -0,0 +1,75 @@ + + + + + + + + + Options + + + + + + + Enable build: + + + Data type: + + DIM (data as array) + ASM (DEFB inside ASM Block) + .BIN file (INCBIN "file.bin") + .TAP file (LOAD "" CODE) + + + File path: + + + + Var prefix: + + + Array base: + + 0 (default) + 1 + From project settings + + + Transpose data (swap X and Y) + + + + + You have not selected any sprite to export. Check the "Include in the export" box of the sprites you want to include in the export. + + + Example of usage + + + + + + + + + + + + + + + + diff --git a/ZXBStudio/DocumentEditors/ZXMaps/MapExportDialog.axaml.cs b/ZXBStudio/DocumentEditors/ZXMaps/MapExportDialog.axaml.cs new file mode 100644 index 0000000..7d092a0 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/MapExportDialog.axaml.cs @@ -0,0 +1,385 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Platform.Storage; +using MsBox.Avalonia; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using ZXBasicStudio.Classes; +using ZXBasicStudio.Common; +using ZXBasicStudio.DocumentEditors.ZXGraphics.log; +using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; +using ZXBasicStudio.DocumentEditors.ZXMaps.Neg; +using ZXBasicStudio.DocumentModel.Classes; +using ZXBasicStudio.DocumentModel.Interfaces; +using ZXBasicStudio.Extensions; +using ZXBasicStudio.IntegratedDocumentTypes.CodeDocuments.Basic; +using static System.Net.Mime.MediaTypeNames; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps +{ + public partial class MapExportDialog : Window + { + private string fileName = ""; + private ZXMapsMap map = null; + + + private FileTypeConfig fileType = null; + private Pattern[] patterns = null; + private ExportConfig exportConfig = null; + + + public MapExportDialog() + { + InitializeComponent(); + + btnCancel.Tapped += BtnCancel_Tapped; + btnCopy.Tapped += BtnCopy_Tapped; + btnExport.Tapped += BtnExport_Tapped; + btnOutputFile.Tapped += BtnOutputFile_Tapped; + cmbDataType.SelectionChanged += CmbDataType_SelectionChanged; + cmbArrayBase.SelectionChanged += CmbArrayBase_SelectionChanged; + btnSave.Tapped += BtnSave_Tapped; + } + + + public bool Initialize(string fileName, ZXMapsMap map) + { + this.fileName = fileName; + this.map = map; + + if (map == null) + { + return false; + } + + var p = Path.GetDirectoryName(fileName); + p = Path.Combine(p, map.Name + ".zxmap"); + exportConfig = ServiceLayer.Export_GetConfigFile(map.Name + ".zbs"); + if (exportConfig == null) + { + exportConfig = ServiceLayer.Export_Map_GetDefaultConfig(p); + } + + this.cmbSelectExportType.Initialize(ExportType_Changed); + cmbSelectExportType.InitializeMap(); + + txtOutputFile.Text = exportConfig.ExportFilePath; + txtLabelName.Text = exportConfig.LabelName; + chkAuto.IsChecked = exportConfig.AutoExport; + cmbDataType.SelectedIndex = (int)exportConfig.ExportDataType; + cmbArrayBase.SelectedIndex = exportConfig.ArrayBase.ToInteger(); + + cmbSelectExportType.ExportType = exportConfig.ExportType; + + return true; + } + + + #region ExportOptions + + private void ExportType_Changed(ExportTypes exportType) + { + exportConfig.ExportType = exportType; + Refresh(); + } + + + private void Refresh() + { + grdOptions.IsVisible = true; + + chkAuto.IsVisible = true; + + lblOutputFile.IsVisible = true; + txtOutputFile.IsVisible = true; + btnOutputFile.IsVisible = true; + + lblLabelName.IsVisible = true; + txtLabelName.IsVisible = true; + + lblArrayBase.IsVisible = true; + cmbDataType.IsVisible = true; + cmbArrayBase.IsVisible = true; + + bool canExport = false; + if (map == null || map.TileArrays==null || map.TileArrays.Length == 0) + { + txtError.IsVisible = true; + } + else + { + canExport = true; + txtError.IsVisible = false; + } + + switch (exportConfig.ExportType) + { + case ExportTypes.Array: + CreateExportPath(".bas"); + if (canExport) + { + CreateExample_Array(); + } + break; + default: + grdOptions.IsVisible = false; + break; + } + } + + + private void CreateExportPath(string extension) + { + if (string.IsNullOrEmpty(exportConfig.ExportFilePath)) + { + txtOutputFile.Text = fileType.FileName + extension; + var spName = Path.GetFileNameWithoutExtension(fileType.FileName.ToStringNoNull()); + txtLabelName.Text = spName; + if (spName.Length > 10) + { + spName = spName.Substring(0, 10); + } + } + } + + + #endregion + + + #region Examples + + private void GetConfigFromUI() + { + if (exportConfig == null) + { + exportConfig = new ExportConfig(); + } + exportConfig.ArrayBase = cmbArrayBase.SelectedIndex.ToInteger(); + exportConfig.AutoExport = chkAuto.IsChecked == true; + exportConfig.ExportDataType = (ExportDataTypes)cmbDataType.SelectedIndex.ToInteger(); + exportConfig.ExportFilePath = txtOutputFile.Text.ToStringNoNull(); + exportConfig.ExportType = cmbSelectExportType.ExportType; + exportConfig.LabelName = txtLabelName.Text.ToStringNoNull(); + exportConfig.ZXAddress = 0; + exportConfig.ZXFileName = ""; + } + + private void CreateExample_Array() + { + if (map == null || map.TileArrays==null || map.TileArrays.Length == 0) + { + txtCode.Text = ""; + } + + var res = ExportManager.Export_Map_Array(exportConfig, map); + if (res.StartsWith("ERROR:")) + { + txtError.Text = res; + txtError.IsVisible = true; + return; + } + else + { + txtError.IsVisible = false; + } + + var sb = new StringBuilder(); + switch (exportConfig.ExportDataType) + { + case ExportDataTypes.DIM: + { + sb.AppendLine("'- Includes -----------------------------------------------"); + sb.AppendLine("#INCLUDE "); + sb.AppendLine(""); + sb.AppendLine(string.Format("' Can use: #INCLUDE \"{0}\"", + Path.GetFileName(exportConfig.ExportFilePath))); + sb.AppendLine(res); + sb.AppendLine(""); + sb.AppendLine("'- Draw tile --------------------------------------------"); + + //sb.AppendLine(string.Format( + // "putChars(10,5,{0},{1},@{2}({3},0))", + // tile.Width / 8, + // tile.Height / 8, + // exportConfig.LabelName, + // tile.Frames == 1 ? "0" : "0,0")); + sb.AppendLine(""); + } + break; + + case ExportDataTypes.ASM: + { + sb.AppendLine("'- Includes -----------------------------------------------"); + sb.AppendLine("#INCLUDE "); + sb.AppendLine(""); + sb.AppendLine("'- Draw tile --------------------------------------------"); + //sb.AppendLine(string.Format( + // "putChars(10,5,{0},{1},@{2}{3})", + // tile.Width / 8, + // tile.Height / 8, + // exportConfig.LabelName, + // tile.Name.Replace(" ", "_"))); + sb.AppendLine(""); + sb.AppendLine("' This section must not be executed"); + sb.AppendLine(string.Format("' Can use: #INCLUDE \"{0}\"", + Path.GetFileName(exportConfig.ExportFilePath))); + sb.AppendLine(res); + } + break; + + case ExportDataTypes.BIN: + { + sb.AppendLine("'- Includes -----------------------------------------------"); + sb.AppendLine("#INCLUDE "); + sb.AppendLine(""); + sb.AppendLine("'- Draw tile --------------------------------------------"); + //sb.AppendLine(string.Format( + // "putChars(10,5,{0},{1},@{2})", + // tile.Width / 8, + // tile.Height / 8, + // exportConfig.LabelName)); + sb.AppendLine(""); + sb.AppendLine("' This section must not be executed"); + sb.AppendLine(string.Format( + "{0}:", + exportConfig.LabelName)); + sb.AppendLine("ASM"); + sb.AppendLine(string.Format("\tINCBIN \"{0}\"", + Path.GetFileName(exportConfig.ExportFilePath))); + sb.AppendLine("END ASM"); + } + break; + + case ExportDataTypes.TAP: + { + sb.AppendLine("'- Includes -----------------------------------------------"); + sb.AppendLine("#INCLUDE "); + sb.AppendLine(""); + sb.AppendLine("' Load .tap data ------------------------------------------"); + sb.AppendLine("LOAD \"\" CODE"); + sb.AppendLine(""); + sb.AppendLine("'- Draw tile --------------------------------------------"); + //sb.AppendLine(string.Format( + // "putChars(10,5,{0},{1},@{2})", + // tile.Width / 8, + // tile.Height / 8, + // exportConfig.LabelName)); + sb.AppendLine(""); + } + break; + } + + txtCode.Text = sb.ToString(); + } + + #endregion + + + #region Export + + private void Export() + { + GetConfigFromUI(); + var em = new ExportManager(); + em.Initialize(FileTypes.Map); + em.ExportMap(exportConfig, map); + } + + #endregion + + + #region Buttons + + private void BtnSave_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + GetConfigFromUI(); + var p = Path.GetDirectoryName(fileName); + p = Path.Combine(p, map.Name + ".zxmap.zbs"); + ServiceLayer.Export_SetConfigFile(p, exportConfig); + Export(); + //this.Close(); + } + + private async void BtnOutputFile_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + var fileTypes = new FilePickerFileType[2]; + fileTypes[1] = new FilePickerFileType("All files") { Patterns = new[] { "*", "*.*" } }; + fileTypes[0] = new FilePickerFileType("Maps files") { Patterns = new[] { "*.zxmapdata" } }; + + var select = await StorageProvider.SaveFilePickerAsync(new Avalonia.Platform.Storage.FilePickerSaveOptions + { + ShowOverwritePrompt = true, + FileTypeChoices = fileTypes, + Title = "Select Export path...", + }); + + if (select != null) + { + txtOutputFile.Text = Path.GetFullPath(select.Path.LocalPath); + } + } + + + private void BtnExport_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + GetConfigFromUI(); + Export(); + this.Close(); + } + + + private void BtnCopy_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + TopLevel.GetTopLevel(this)?.Clipboard?.SetTextAsync(txtCode.Text); + } + + + private void BtnCancel_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.Close(); + } + + + + private void CmbArrayBase_SelectionChanged(object? sender, SelectionChangedEventArgs e) + { + var idx = cmbArrayBase.SelectedIndex; + exportConfig.ArrayBase = idx; + Refresh(); + } + + + private void CmbDataType_SelectionChanged(object? sender, SelectionChangedEventArgs e) + { + var idx = cmbDataType.SelectedIndex; + exportConfig.ExportDataType = (ExportDataTypes)idx; + + var ext = ""; + switch (exportConfig.ExportDataType) + { + case ExportDataTypes.ASM: + case ExportDataTypes.DIM: + ext = ".bas"; + break; + case ExportDataTypes.BIN: + ext = ".bin"; + break; + case ExportDataTypes.TAP: + ext = ".tap"; + break; + default: + return; + } + txtOutputFile.Text = Path.Combine(Path.GetDirectoryName(txtOutputFile.Text), Path.GetFileNameWithoutExtension(txtOutputFile.Text) + ext); + exportConfig.ExportFilePath = txtOutputFile.Text; + Refresh(); + } + + #endregion + } +} diff --git a/ZXBStudio/DocumentEditors/ZXMaps/MapFileSelectorControl.axaml b/ZXBStudio/DocumentEditors/ZXMaps/MapFileSelectorControl.axaml new file mode 100644 index 0000000..0e9719c --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/MapFileSelectorControl.axaml @@ -0,0 +1,15 @@ + + + File Name: + + + + diff --git a/ZXBStudio/DocumentEditors/ZXMaps/MapFileSelectorControl.axaml.cs b/ZXBStudio/DocumentEditors/ZXMaps/MapFileSelectorControl.axaml.cs new file mode 100644 index 0000000..64f1d2a --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/MapFileSelectorControl.axaml.cs @@ -0,0 +1,147 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives.PopupPositioning; +using Avalonia.Interactivity; + +//using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Avalonia.Threading; +using AvaloniaEdit.Folding; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using ZXBasicStudio.Classes; +using ZXBasicStudio.Common; +using ZXBasicStudio.DocumentEditors.NextDows.neg; +using ZXBasicStudio.DocumentEditors.ZXGraphics; +using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; +using ZXBasicStudio.DocumentEditors.ZXMaps.Log; +using ZXBasicStudio.DocumentEditors.ZXMaps.Neg; +using ZXBasicStudio.DocumentEditors.ZXTextEditor.Classes.Folding; +using ZXBasicStudio.DocumentModel.Classes; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps +{ + public partial class MapFileSelectorControl : UserControl + { + public ZXMapsMap Map + { + get + { + return _Map; + } + set + { + _Map = value; + Refresh(); + } + } + + private ZXMapsMap _Map = null; + + private int CurrentTap = 0; + private Action Comando = null; + private bool comboUpdating = false; + + + public MapFileSelectorControl() + { + InitializeComponent(); + } + + + public bool Initialize(ZXMapsMap map, Action callBackComando) + { + this._Map = map; + this.Comando = callBackComando; + + FillCombo(); + return true; + } + + + public bool Initialize(string mapName, Action callBackComando) + { + this.Comando = callBackComando; + if (!string.IsNullOrEmpty(mapName)) + { + Map = ServiceLayer_Maps.Maps_LoadMapByName(mapName); + } + FillCombo(); + return true; + } + + + public void Refresh() + { + + } + + + private void FillCombo() + { + try + { + if (comboUpdating) + { + return; + } + comboUpdating = true; + + var files = Directory.GetFiles(ZXProjectManager.Current.ProjectPath, "*.zxmap"). + Select(Path.GetFileNameWithoutExtension). + OrderBy(f => f). + ToList(); + var sel = cmbFileName.SelectedValue; + cmbFileName.ItemsSource = null; + cmbFileName.Items.Clear(); + + var numFiles = files.Count(); + if (numFiles == 0) + { + files.Add(""); + cmbFileName.ItemsSource = files; + cmbFileName.SelectedIndex = 0; + } + else + { + cmbFileName.ItemsSource = files; + if (sel == null) + { + cmbFileName.SelectedValue = Map?.Name; + } + else + { + cmbFileName.SelectedValue = sel; + } + } + } + catch (Exception ex) + { + // TODO: Tratar excepciones + } + finally + { + comboUpdating = false; + } + } + + + private void cmbFileName_SelectionChanged(object? sender, SelectionChangedEventArgs e) + { + try + { + var name = cmbFileName.Text; + Map = ServiceLayer_Maps.Maps_LoadMapByName(name); + Comando?.Invoke(this, "SELECTED"); + } + catch (Exception ex) + { + //MessageBox.Show($"Error loading file {FileName}: {ex.Message}"); + } + + } + } +} \ No newline at end of file diff --git a/ZXBStudio/DocumentEditors/ZXMaps/MapPatternEditor.axaml b/ZXBStudio/DocumentEditors/ZXMaps/MapPatternEditor.axaml new file mode 100644 index 0000000..64ad870 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/MapPatternEditor.axaml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + Zoom 24x + + + Frame + + + + + + Paper + + + 0 + + + + + + Ink + + + 1 + + + + + + + + + diff --git a/ZXBStudio/DocumentEditors/ZXMaps/MapPatternEditor.axaml.cs b/ZXBStudio/DocumentEditors/ZXMaps/MapPatternEditor.axaml.cs new file mode 100644 index 0000000..065866b --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/MapPatternEditor.axaml.cs @@ -0,0 +1,687 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Input; +using Avalonia.Media; +using Avalonia.Platform; +using Avalonia.Themes.Fluent; +using Avalonia.Threading; +using Svg; +using System; +using System.Collections.Generic; +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; +using ZXBasicStudio.DocumentEditors.ZXGraphics.log; +using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; +using ZXBasicStudio.DocumentEditors.ZXMaps.Neg; +using ZXBasicStudio.Extensions; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps +{ + public partial class MapPatternEditor : UserControl + { + #region Private properties + + /// + /// last zoom value + /// + private int lastZoom = 0; + + /// + /// Zooms values + /// + private int[] zooms = new int[] + { + 1,2,4,8,16,24,32,48,64 + }; + private int pixelSize = 72; + + private byte actualFrame = 0; + + #endregion + + + #region Public properties + + /// + /// Zoom + /// + public int Zoom + { + get + { + return _Zoom; + } + set + { + _Zoom = value; + Refresh(false); + } + } + + /// + /// Color index of the primary color + /// + public int PrimaryColorIndex { get; set; } + + /// + /// Color index of the secondary color + /// + public int SecondaryColorIndex { get; set; } + + public bool Bright { get; set; } + public bool Flash { get; set; } + + private bool _ViewAttributes = true; + + public bool InvertPixelsCell { get; set; } = false; + + public bool InvertColorsCell { get; set; } = false; + + public bool ColorPicker { get; set; } = false; + + public ZXMapsMap Map + { + get + { + return _map; + } + set + { + _map = value; + Refresh(false); + } + } + + public ZXMapsTiles Tiles + { + get + { + return _tiles; + } + set + { + _tiles = value; + Refresh(false); + } + } + + public int CurrentTile { get; set; } + + #endregion + + + #region Private fields + + private ZXMapsMap _map = null; + private ZXMapsTiles _tiles = null; + private int _Zoom = 24; + private Action CallBackCommand = null; + private int? lastId = null; + private ZXGridImageView[,] images = null; + private PaletteColor[] ZXSpectrumPalette = null; + + /// + /// True when mouse left button is pressed + /// + private bool MouseLeftPressed = false; + /// + /// True when moude right button is pressed + /// + private bool MouseRightPressed = false; + + /// + /// Dispatcher for refresh operations + /// + private DispatcherTimer tmr = null; + + #endregion + + + #region Public Methods + + public MapPatternEditor() + { + InitializeComponent(); + + ZXSpectrumPalette = ServiceLayer.GetPalette(GraphicsModes.ZXSpectrum); + PrimaryColorIndex = 0; + SecondaryColorIndex = 7; + + cnvEditor.PointerMoved += CnvEditor_PointerMoved; + cnvEditor.PointerPressed += CnvEditor_PointerPressed; + + sldZoom.PropertyChanged += SldZoom_PropertyChanged; + txtFrame.PropertyChanged += TxtFrame_PropertyChanged; + + btnClear.Tapped += BtnClear_Tapped; + btnExport.Tapped += BtnExport_Tapped; + + btnUndo.Tapped += BtnUndo_Tapped; + btnRedo.Tapped += BtnRedo_Tapped; + + btnPaper.Tapped += BtnPaper_Click; + btnInk.Tapped += BtnInk_Tapped; + + UpdateColorPanel(); + } + + + /// + /// Initializes the control + /// + /// CallBack for commands: "REFRESH" + /// True if OK or False if error + public bool Initialize(ZXMapsMap map, ZXMapsTiles tileData, Action callBackCommand) + { + this._map = map; + this._tiles = tileData; + this.CallBackCommand = callBackCommand; + Refresh(false); + return true; + } + + + /// + /// Refresh the draw area + /// + public void Refresh(bool withCallBack) + { + if (_map == null || _tiles == null) + { + cnvEditor.Children.Clear(); + return; + } + + + if (images == null) + { + images = new ZXGridImageView[_map.Width, _map.Height]; + for (int y = 0; y < _map.Height; y++) + { + for (int x = 0; x < _map.Width; x++) + { + var i = new ZXGridImageView(); + i.Show8x8Grid = false; + i.ViewAttributes = false; + i.ShowTileGrid = true; + i.Grid8x8Color = new SkiaSharp.SKColor(64, 0, 0); + + //i.PointerEntered += I_PointerEntered; + //i.PointerExited += I_PointerExited; + //i.PointerPressed += I_PointerPressed; + //i.PointerReleased += I_PointerReleased; + //i.PointerMoved += I_PointerMoved; + //i.Tag = x + (y * _map.Width); + images[x, y] = i; + cnvEditor.Children.Add(i); + } + } + } + + if (_map.TileArrays == null) + { + _map.TileArrays = new ZXMapsTileArray[_map.Width, _map.Height]; + for (int y = 0; y < _map.Height; y++) + { + for (int x = 0; x < _map.Width; x++) + { + _map.TileArrays[x, y] = new ZXMapsTileArray() + { + Id = x + (y * _map.Width), + Properties = new List(), + TileId = 0, + X = x, + Y = y, + Z = 0 + }; + } + } + } + + for (int y = 0; y < _map.Height; y++) + { + int yy = y * _Zoom * 4; + for (int x = 0; x < _map.Width; x++) + { + var i = images[x, y]; + i.Zoom = _Zoom; + Canvas.SetLeft(i, x * i.Width); + Canvas.SetTop(i, y * i.Height); + if (x < _map.TileArrays.GetLength(0) && + y < _map.TileArrays.GetLength(1)) + { + var td = _map.TileArrays[x, y]; + if (td != null) + { + var tile = _tiles.Tiles.FirstOrDefault(d => d != null && d.Id == td.TileId); + //tile.Palette[0] = new PaletteColor() { Red = 0x00, Green = 0x00, Blue = 0x00 }; + //tile.Palette[1]= new PaletteColor() { Red = 0x00, Green = 0x00, Blue = 0xa0 }; + + var aspect = new TileImage(); + aspect.RenderTile(tile, 0); + i.BackgroundImage = aspect; + } + } + } + } + + try + { + cnvEditor.Width = images[0, 0].Width * _map.Width; + cnvEditor.Height = images[0, 0].Height * _map.Height; + } + catch { } + + this.InvalidateVisual(); + + if (withCallBack) + { + CallBackCommand?.Invoke(this, "REFRESH"); + } + } + + #endregion + + + #region Undo and Redo + + private List operations = new List(); + private int operationIndex = -1; + + public void Undo() + { + if (operationIndex > operations.Count - 1) + { + operationIndex = operations.Count - 1; + } + if (operationIndex < 0) + { + return; + } + var op = operations[operationIndex]; + if (op == null) + { + return; + } + _map.TileArrays[op.X, op.Y] = op; + operationIndex--; + Refresh(true); + } + + + public void Redo() + { + if (operationIndex > operations.Count - 1) + { + return; + } + if (operationIndex < 0) + { + return; + } + var op = operations[operationIndex]; + _map.TileArrays[op.X, op.Y] = op; + operationIndex++; + Refresh(true); + } + + #endregion + + + #region Drawing events + + /// + /// Event for mouse pressed + /// + /// + /// + private void CnvEditor_PointerPressed(object? sender, Avalonia.Input.PointerPressedEventArgs e) + { + var p = e.GetCurrentPoint(cnvEditor); + var w = images[0, 0].Width; + var h = images[0, 0].Height; + int x = (int)(p.Position.X / w); + int y = (int)(p.Position.Y / h); + + if (p.Properties.IsLeftButtonPressed) + { + SetTile(x, y, CurrentTile); + } + if (p.Properties.IsRightButtonPressed) + { + SetTile(x, y, 0); + } + } + + + /// + /// Event for pointer moved + /// + /// + /// + private void CnvEditor_PointerMoved(object? sender, Avalonia.Input.PointerEventArgs e) + { + if (e.Properties.IsLeftButtonPressed || e.Properties.IsRightButtonPressed) + { + var p = e.GetCurrentPoint(cnvEditor); + var w = images[0, 0].Width; + var h = images[0, 0].Height; + int x = (int)(p.Position.X / w); + int y = (int)(p.Position.Y / h); + + if (e.Properties.IsLeftButtonPressed) + { + SetTile(x, y, CurrentTile); + } + else if (e.Properties.IsRightButtonPressed) + { + SetTile(x, y, 0); + } + } + } + + + private int lastX = -1; + private int lastY = -1; + private int lastValue = -1; + + /// + /// Set point to a color value + /// + /// Absolute x + /// Absolute y + /// Value of the point + private void SetTile(int x, int y, int value) + { + if (_map == null) + { + return; + } + + if (lastX == x && lastY == y && lastValue == value) + { + return; + } + var ta = _map.TileArrays[x, y]; + operations.Add(ta.Clonar()); + operationIndex = operations.Count - 1; + lastX = ta.X; + lastY = ta.Y; + lastValue = value; + + if (ta.TileId != value) + { + ta.TileId = value; + Refresh(true); + } + } + + + private void Tmr_Tick(object? sender, EventArgs e) + { + Refresh(true); + tmr.Stop(); + } + + #endregion + + + #region Icon actions + + + /// + /// Clear Tile + /// + public void Clear() + { + for (int y = 0; y < _map.Height; y++) + { + for (int x = 0; x < _map.Width; x++) + { + SetTile(x, y, 0); + } + } + Refresh(true); + } + + #endregion + + + #region Color + + private void BtnPaper_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + { + if (ctrlColorPicker.IsVisible) + { + ctrlColorPicker.IsVisible = false; + return; + } + var Tile = Tiles.Tiles[0]; + //Tile.GraphicMode = GraphicsModes.ZXSpectrum; + //Tile.Palette=ZXGraphics.log.ServiceLayer.GetPalette(Tile.GraphicMode); + ctrlColorPicker.IsVisible = true; + var p = ServiceLayer.GetPalette(GraphicsModes.ZXSpectrum); + ctrlColorPicker.Inicialize(GraphicsModes.ZXSpectrum, p, SecondaryColorIndex, ColorPickerPaper_Action); + } + + + private void BtnInk_Tapped(object? sender, TappedEventArgs e) + { + if (ctrlColorPicker.IsVisible) + { + ctrlColorPicker.IsVisible = false; + return; + } + var Tile = Tiles.Tiles[0]; + //Tile.GraphicMode = GraphicsModes.ZXSpectrum; + //Tile.Palette=ZXGraphics.log.ServiceLayer.GetPalette(Tile.GraphicMode); + ctrlColorPicker.IsVisible = true; + var p = ServiceLayer.GetPalette(GraphicsModes.ZXSpectrum); + ctrlColorPicker.Inicialize(GraphicsModes.ZXSpectrum, p, PrimaryColorIndex, ColorPickerInk_Action); + } + + + public void UpdateColorPanel() + { + if (Tiles == null) + { + return; + } + + switch (Tiles.GraphicMode) + { + case GraphicsModes.Monochrome: + { + if (Tiles.Tiles[0].Palette == null) + { + Tiles.Tiles[0].Palette = ServiceLayer.GetPalette(Tiles.GraphicMode); + } + var ink = Tiles.Tiles[0].Palette[1]; + var paper = Tiles.Tiles[0].Palette[0]; + grdPaper.Background = new SolidColorBrush(Color.FromRgb(paper.Red, paper.Green, paper.Blue)); + txtPaper.Foreground = new SolidColorBrush(Color.FromRgb(ink.Red, ink.Green, ink.Blue)); + txtPaper.Text = "0"; + grdInk.Background = new SolidColorBrush(Color.FromRgb(ink.Red, ink.Green, ink.Blue)); + txtInk.Foreground = new SolidColorBrush(Color.FromRgb(paper.Red, paper.Green, paper.Blue)); + txtInk.Text = "1"; + } + break; + + case GraphicsModes.ZXSpectrum: + case GraphicsModes.Next: + { + var ink = Tiles.Tiles[0].Palette[PrimaryColorIndex]; + var paper = Tiles.Tiles[0].Palette[SecondaryColorIndex]; + grdPaper.Background = new SolidColorBrush(Color.FromRgb(paper.Red, paper.Green, paper.Blue)); + txtPaper.Foreground = new SolidColorBrush(Color.FromRgb(ink.Red, ink.Green, ink.Blue)); + txtPaper.Text = SecondaryColorIndex.ToString(); + grdInk.Background = new SolidColorBrush(Color.FromRgb(ink.Red, ink.Green, ink.Blue)); + txtInk.Foreground = new SolidColorBrush(Color.FromRgb(paper.Red, paper.Green, paper.Blue)); + txtInk.Text = PrimaryColorIndex.ToString(); + } + break; + } + } + + + + private void ColorPickerPaper_Action(string command, int indexColor) + { + SecondaryColorIndex = indexColor; + UpdatePalette(); + UpdateColorPanel(); + CallBackCommand(this, "REFRESH"); + } + + + private void ColorPickerInk_Action(string command, int indexColor) + { + PrimaryColorIndex = indexColor; + UpdatePalette(); + UpdateColorPanel(); + CallBackCommand(this, "REFRESH"); + } + + + private void UpdatePalette() + { + if (Tiles.GraphicMode == GraphicsModes.Monochrome) + { + var p = ServiceLayer.GetPalette(GraphicsModes.ZXSpectrum); + foreach (var t in Tiles.Tiles) + { + if (t == null) + { + continue; + } + t.Palette[0] = p[SecondaryColorIndex]; + t.Palette[1] = p[PrimaryColorIndex]; + } + UpdateColorPanel(); + } + Refresh(true); + } + + #endregion + + + + #region Main editor + + /// + /// Zoom changed + /// + /// + /// + private void SldZoom_PropertyChanged(object? sender, Avalonia.AvaloniaPropertyChangedEventArgs e) + { + int z = (int)sldZoom.Value; + if (z == 0 || z == lastZoom) + { + return; + } + lastZoom = z; + + z = zooms[z - 1]; + pixelSize = z * 4; + txtZoom.Text = "Zoom " + z.ToString() + "x"; + Zoom = z; + } + + + public void ZoomIn() + { + int v = sldZoom.Value.ToInteger(); + if (v < zooms.Length - 1) + { + sldZoom.Value = v - 1; + } + } + + + public void ZoomOut() + { + int v = sldZoom.Value.ToInteger(); + if (v > 0) + { + sldZoom.Value = v - 1; + } + + } + + + private void TxtFrame_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + { + // TODO: Multiple frames per tile? + /* + byte v = txtFrame.Text.ToByte(); + if (actualFrame == v || + ctrlProperties.TileData == null || + v < 0 || + v >= (ctrlProperties.TileData.Frames)) + { + return; + } + if (v > 255) + { + v = 255; + } + actualFrame = v; + if (TileData != null) + { + TileData.CurrentFrame = actualFrame; + Refresh(); + } + Refresh(); + */ + } + + #endregion + + + #region ToolBar + + + private void BtnRedo_Tapped(object? sender, TappedEventArgs e) + { + Redo(); + } + + + private void BtnUndo_Tapped(object? sender, TappedEventArgs e) + { + Undo(); + } + + + /// + /// Clear click + /// + /// + /// + private void BtnClear_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.Clear(); + } + + + private void BtnExport_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + Export(); + } + + + public void Export() + { + CallBackCommand(this, "EXPORT"); + } + + #endregion + } +} diff --git a/ZXBStudio/DocumentEditors/ZXMaps/MapPropertiesControl.axaml b/ZXBStudio/DocumentEditors/ZXMaps/MapPropertiesControl.axaml new file mode 100644 index 0000000..d662de6 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/MapPropertiesControl.axaml @@ -0,0 +1,112 @@ + + + + + + + Map type: + + + Multidirectional scroll + + + Map width: + + + + (tiles) + + Map height: + + + + (tiles) + + Tile width: + + + + (pixels) + + Tile height: + + + + (pixels) + + Mapping type: + + Sequential + Coordinates + Blocks + + + + + + + + + + + + + Name: + + + Layer type: + + Tiles + Masks + Sprites + + + + + + + + + + + + + + Name: + + + + + + + + + + + + + Tile id: + + + + + + + + + + + diff --git a/ZXBStudio/DocumentEditors/ZXMaps/MapPropertiesControl.axaml.cs b/ZXBStudio/DocumentEditors/ZXMaps/MapPropertiesControl.axaml.cs new file mode 100644 index 0000000..87d1e09 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/MapPropertiesControl.axaml.cs @@ -0,0 +1,189 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives.PopupPositioning; +using Avalonia.Interactivity; + +//using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Avalonia.Threading; +using AvaloniaEdit.Folding; +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using ZXBasicStudio.Classes; +using ZXBasicStudio.Common; +using ZXBasicStudio.DocumentEditors.NextDows.neg; +using ZXBasicStudio.DocumentEditors.ZXGraphics; +using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; +using ZXBasicStudio.DocumentEditors.ZXMaps.Neg; +using ZXBasicStudio.DocumentEditors.ZXTextEditor.Classes.Folding; +using ZXBasicStudio.DocumentModel.Classes; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps +{ + public partial class MapPropertiesControl : UserControl + { + public ZXMapsMap Map + { + get + { + return _Map; + } + set + { + _Map = value; + Refresh(); + } + } + private ZXMapsMap _Map = null; + + public ZXMapsTiles Tiles + { + get + { + return _Tiles; + } + set + { + _Tiles = value; + Refresh(); + } + } + private ZXMapsTiles _Tiles = null; + + private Action Command = null; + + + public MapPropertiesControl() + { + InitializeComponent(); + } + + + public bool Initialize(ZXMapsMap map, ZXMapsTiles tiles, Action callBackCommand) + { + this._Map = map; + this._Tiles = tiles; + this.Command = callBackCommand; + + cmbMapType.SelectionChanged += CmbMapType_SelectionChanged; + txtMapWidth.ValueChanged += TxtMapWidth_ValueChanged; + txtMapHeight.ValueChanged += TxtMapHeight_ValueChanged; + txtMapWidth.ValueChanged += TxtMapWidth_ValueChanged; + txtMapTileWidth.ValueChanged += TxtMapTileWidth_ValueChanged; + txtMapTileHeight.ValueChanged += TxtMapTileHeight_ValueChanged; + //cmbMappingType.SelectionChanged += CmbMappingType_SelectionChanged; + + Refresh(); + + return true; + } + + + public void Refresh() + { + if (Map == null) + { + tabControl.SelectedIndex = 0; + //tabControl.IsVisible = false; + } + else + { + // Map + cmbMapType.SelectedIndex = (int)Map.MapType; + txtMapHeight.Value = Map.Height; + txtMapWidth.Value = Map.Width; + txtMapTileHeight.Value = Map.TileHeight; + txtMapTileWidth.Value = Map.TileWidth; + cmbMappingType.SelectedIndex = (int)Map.MappingType; + // Layer + var layer = Map.Layers?.FirstOrDefault(d => d.Selected); + if (layer == null) + { + tabControl.SelectedIndex = 0; + } + else + { + txtLayerName.Text = layer.Name; + cmbLayerType.SelectedIndex = (int)layer.LayersType; + } + } + + if(_Map!=null && _Tiles != null) + { + if (_Map.TileWidth != _Tiles.Width) + { + txtMapTileWidth.Background = new SolidColorBrush(Colors.Red); + } + else + { + txtMapTileWidth.Background = null; + } + if (_Map.TileHeight != _Tiles.Height) + { + txtMapTileHeight.Background = new SolidColorBrush(Colors.Red); + } + else + { + txtMapTileHeight.Background = null; + } + } + } + + + #region Values changed + + private void CmbMappingType_SelectionChanged(object? sender, SelectionChangedEventArgs e) + { + Map.MappingType = (ZXMapsMappingTypes)cmbMappingType.SelectedIndex; + Command?.Invoke(this, "UPDATE"); + Refresh(); + } + + + private void TxtMapTileHeight_ValueChanged(object? sender, NumericUpDownValueChangedEventArgs e) + { + Map.TileHeight = txtMapTileHeight.Text.ToInteger(); + Command?.Invoke(this, "UPDATE"); + Refresh(); + } + + + private void TxtMapTileWidth_ValueChanged(object? sender, NumericUpDownValueChangedEventArgs e) + { + Map.TileWidth = txtMapTileWidth.Text.ToInteger(); + Command?.Invoke(this, "UPDATE"); + Refresh(); + } + + + private void TxtMapHeight_ValueChanged(object? sender, NumericUpDownValueChangedEventArgs e) + { + Map.Height = txtMapHeight.Text.ToInteger(); + Command?.Invoke(this, "UPDATE"); + Refresh(); + } + + + private void TxtMapWidth_ValueChanged(object? sender, NumericUpDownValueChangedEventArgs e) + { + Map.Width = txtMapWidth.Text.ToInteger(); + Command?.Invoke(this, "UPDATE"); + Refresh(); + } + + + private void CmbMapType_SelectionChanged(object? sender, SelectionChangedEventArgs e) + { + Map.MapType = (ZXMapsTypes)cmbMapType.SelectedIndex; + Command?.Invoke(this, "UPDATE"); + Refresh(); + } + + #endregion + + } +} \ No newline at end of file diff --git a/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsLayer.cs b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsLayer.cs new file mode 100644 index 0000000..45f8084 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsLayer.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps.Neg +{ + /// + /// Represents a map layer within the ZX Maps system, providing properties to control its visibility, rendering + /// order, and data type. + /// + /// Use this class to manage the display and organization of individual layers in a ZX Maps + /// document. The layer's type, order, and visibility can be customized to control how map data is presented and + /// interacted with. The specific kind of data represented by the layer is determined by the LayersType + /// property. + public class ZXMapsLayer + { + /// + /// Gets or sets the unique identifier for the entity. + /// + public int Id { get; set; } + /// + /// Gets or sets the name of the entity. + /// + public string Name { get; set; } + /// + /// Gets or sets the order in which the item appears in a collection. + /// + public int Order { get; set; } + /// + /// Gets or sets a value indicating whether the associated element is visible. + /// + public bool Visible { get; set; } + /// + /// Gets or sets a value indicating whether the item is selected. + /// + public bool Selected { get; set; } + /// + /// Gets or sets the type of map layers to display. + /// + public ZXMapsLayersType LayersType { get; set; } + } +} diff --git a/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsLayersType.cs b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsLayersType.cs new file mode 100644 index 0000000..a72eae3 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsLayersType.cs @@ -0,0 +1,14 @@ +namespace ZXBasicStudio.DocumentEditors.ZXMaps.Neg +{ + /// + /// Specifies the types of layers that can be used in ZX Maps, such as tiles, masks, and sprites. + /// + /// Use this enumeration to indicate the layer type when configuring or rendering map elements in + /// ZX Maps. Each value represents a distinct visual or functional layer within the map system. + public enum ZXMapsLayersType + { + Tiles = 0, + Masks = 1, + Sprites = 2 + } +} \ No newline at end of file diff --git a/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsMap.cs b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsMap.cs new file mode 100644 index 0000000..f877e47 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsMap.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps.Neg +{ + /// + /// Represents a map + /// + public class ZXMapsMap + { + public string Name { get; set; } + public ZXMapsTypes MapType { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public int TileWidth { get; set; } + public int TileHeight { get; set; } + public ZXMapsMappingTypes MappingType { get; set; } + public List PropertyDefinitons { get; set; } + public List Layers { get; set; } + public ZXMapsTileArray[,] TileArrays { get; set; } + } +} diff --git a/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsMappingTypes.cs b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsMappingTypes.cs new file mode 100644 index 0000000..591150a --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsMappingTypes.cs @@ -0,0 +1,9 @@ +namespace ZXBasicStudio.DocumentEditors.ZXMaps.Neg +{ + public enum ZXMapsMappingTypes + { + Sequential = 0, + Coordinates = 1, + Blocks = 2 + } +} \ No newline at end of file diff --git a/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsProperty.cs b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsProperty.cs new file mode 100644 index 0000000..ed8edfb --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsProperty.cs @@ -0,0 +1,8 @@ +namespace ZXBasicStudio.DocumentEditors.ZXMaps.Neg +{ + public class ZXMapsProperty + { + public int Id { get; set; } + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsPropertyDefiniton.cs b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsPropertyDefiniton.cs new file mode 100644 index 0000000..484df27 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsPropertyDefiniton.cs @@ -0,0 +1,11 @@ +namespace ZXBasicStudio.DocumentEditors.ZXMaps.Neg +{ + public class ZXMapsPropertyDefiniton + { + public int Id { get; set; } + public string Name { get; set; } + public ZXMapsPropertyTypes PropertyType { get; set; } + public int MinValue { get; set; } + public int MaxValue { get; set; } + } +} \ No newline at end of file diff --git a/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsPropertyTypes.cs b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsPropertyTypes.cs new file mode 100644 index 0000000..20ca864 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsPropertyTypes.cs @@ -0,0 +1,10 @@ +namespace ZXBasicStudio.DocumentEditors.ZXMaps.Neg +{ + public enum ZXMapsPropertyTypes + { + Byte = 0, + Integer = 1, + String = 2, + Bit = 3, + } +} \ No newline at end of file diff --git a/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsRoom.cs b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsRoom.cs new file mode 100644 index 0000000..ebf89a8 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsRoom.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps.Neg +{ + public class ZXMapsRoom + { + public int Id { get; set; } + public string Name { get; set; } + public int IdLayer { get; set; } + public List Properties { get; set; } = new List(); + } +} diff --git a/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsTile.cs b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsTile.cs new file mode 100644 index 0000000..30ef700 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsTile.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ZXBasicStudio.DocumentEditors.ZXGraphics.neg +{ + /// + /// Represents a tile with his properties + /// + public class ZXMapsTile + { + /// + /// Id of the tile + /// + public int Id { get; set; } + /// + /// Name of the tile + /// + public string Name { get; set; } + /// + /// Graphics mode of the tile + /// + public GraphicsModes GraphicMode { get; set; } + /// + /// Width of the tile in pixels + /// + public int Width { get; set; } + /// + /// Height in pixels + /// + public int Height { get; set; } + /// + /// True if the tile is masked (not suported in all modes) + /// + public bool Masked { get; set; } + /// + /// Number of frames for the tile + /// + public byte Frames { get; set; } + /// + /// Current frame + /// + public byte CurrentFrame { get; set; } + /// + /// Patterns for the tile (one pattern for frame) + /// + public List Patterns { get; set; } + /// + /// Default transparent or background color + /// + public byte DefaultColor { get; set; } + /// + /// Palete color for the tile + /// + public PaletteColor[] Palette { get; set; } + /// + /// True when the tile was auto-exported + /// + public bool Export { get; set; } + } +} diff --git a/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsTileArray.cs b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsTileArray.cs new file mode 100644 index 0000000..5f932c8 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsTileArray.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps.Neg +{ + /// + /// Tile reference into a map + /// + public class ZXMapsTileArray + { + /// + /// Unique Id + /// + public int Id { get; set; } + /// + /// Tile id in this map position + /// + public int TileId { get; set; } + /// + /// X position in this map + /// + public int X { get; set; } + /// + /// Y position in this map + /// + public int Y { get; set; } + /// + /// Z position in this map + /// + public int Z { get; set; } + /// + /// Properties for this tile in this place of the map + /// + public List Properties { get; set; } + } +} diff --git a/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsTiles.cs b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsTiles.cs new file mode 100644 index 0000000..2fa1cf1 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsTiles.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps.Neg +{ + public class ZXMapsTiles + { + public string Name { get; set; } + public GraphicsModes GraphicMode { get; set; } + public int Width { get; set; } + public int Height { get; set; } + + public string DefaultMap { get; set; } + public List Tiles { get; set; } + } +} diff --git a/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsTypes.cs b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsTypes.cs new file mode 100644 index 0000000..3f274c6 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/Neg/ZXMapsTypes.cs @@ -0,0 +1,10 @@ +namespace ZXBasicStudio.DocumentEditors.ZXMaps.Neg +{ + public enum ZXMapsTypes + { + MultidirectionalScroll = 0, + Rooms = 1, + HorizontalScroll = 2, + VerticalScroll = 3, + } +} \ No newline at end of file diff --git a/ZXBStudio/DocumentEditors/ZXMaps/TileEditor.axaml b/ZXBStudio/DocumentEditors/ZXMaps/TileEditor.axaml new file mode 100644 index 0000000..69c2418 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/TileEditor.axaml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ZXBStudio/DocumentEditors/ZXMaps/TileEditor.axaml.cs b/ZXBStudio/DocumentEditors/ZXMaps/TileEditor.axaml.cs new file mode 100644 index 0000000..8dabb66 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/TileEditor.axaml.cs @@ -0,0 +1,1123 @@ +using Avalonia.Controls; +using System.Diagnostics; +using ZXBasicStudio.DocumentEditors.ZXGraphics.log; +using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; +using ZXBasicStudio.DocumentEditors.ZXMaps.Neg; +using System; +using System.Linq; +using ZXBasicStudio.Common; +using Avalonia; +using ZXBasicStudio.DocumentModel.Classes; +using System.IO; +using ZXBasicStudio.Classes; +using AvaloniaEdit.Folding; +using Avalonia.Threading; +using ZXBasicStudio.DocumentEditors.ZXTextEditor.Classes.Folding; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using ShimSkiaSharp; +using Avalonia.Interactivity; +using Avalonia.Input; +using AvaloniaEdit; +using System.Reflection; +using FFmpeg.AutoGen; +using Avalonia.Media; +using ZXBasicStudio.DocumentEditors.ZXMaps.Log; +using ZXBasicStudio.Controls; +using System.Data; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps +{ + /// + /// Editor for GDUs and Fonts + /// + public partial class TileEditor : ZXDocumentEditorBase, IObserver + { + #region Events + + public override event EventHandler? DocumentRestored; + public override event EventHandler? DocumentModified; + public override event EventHandler? DocumentSaved; + public override event EventHandler? RequestSaveDocument; + + #endregion + + + #region ZXDocumentBase properties + + public override string DocumentName + { + get + { + return Path.GetFileName(FileName); + } + } + + + public override string DocumentPath + { + get + { + return Path.GetFullPath(FileName); + } + } + + + public override bool Modified + { + get + { + return _Modified; + } + } + + private bool _Modified = false; + + protected virtual AbstractFoldingStrategy? foldingStrategy { get { return null; } } + + private Guid documentTypeId = Guid.Empty; + + private int selectedTileId = 0; + + #endregion + + + #region IObserver implementation for modified document notifications + + public void OnCompleted() + { + + } + + public void OnError(Exception error) + { + + } + + public void OnNext(AvaloniaPropertyChangedEventArgs value) + { + + } + + + + #endregion + + + #region Private variables + + private string FileName = ""; + + private ZXMapsTiles TileMain = null; + private int tileWidth = 16; + private int tileHeight = 16; + private ZXMapsMap Map = null; + + /// + /// List of Tiles patterns controls in the set + /// + private List TilePatternsList = null; + + private FoldingManager? fManager; + private DispatcherTimer? updateFoldingsTimer; + + #endregion + + + #region ZXDocumentBase functions + + public override bool SaveDocument(TextWriter OutputLog) + { + try + { + var masterList = TilePatternsList.Select(d => d.TileData).ToArray(); + var sprList = new List(); + foreach (var spr in masterList) + { + sprList.Add(spr); //.Clonar()); + } + foreach (ZXMapsTile spr in sprList) + { + if (spr == null) + { + continue; + } + + if (spr.Frames < spr.Patterns.Count()) + { + spr.Patterns = spr.Patterns.Take(spr.Frames).ToList(); + } + } + TileMain.Tiles = sprList; + var dataJSon = TileMain.Serializar(); + if (!ServiceLayer.Files_SaveFileString(FileName, dataJSon)) + { + return false; + } + + if (Map != null) + { + var json = Map.Serializar(); + var fileName = Path.Combine(ZXProjectManager.Current.ProjectPath, Map.Name + ".zxmap"); + if (!ServiceLayer.Files_SaveFileString(fileName, json)) + { + return false; + } + } + + _Modified = false; + DocumentSaved?.Invoke(this, EventArgs.Empty); + return true; + } + catch (Exception ex) + { + OutputLog.WriteLine($"Error saving file {FileName}: {ex.Message}"); + return false; + } + } + + + public override bool RenameDocument(string NewName, TextWriter OutputLog) + { + FileName = NewName; + //ServiceLayer_Maps.Maps_RemoveMap(FileName); + //ServiceLayer_Maps.Maps_AddMap(FileName,Map); + return true; + } + + + public override bool CloseDocument(TextWriter OutputLog, bool ForceClose) + { + //ServiceLayer_Maps.Maps_RemoveMap(FileName); + return true; + } + + + public override void Dispose() + { + } + + + public override void Activated() + { + this.Focus(); + ctrlPreview.Start(); + base.Activated(); + } + + + public override void Deactivated() + { + ctrlPreview.Stop(); + base.Deactivated(); + } + + #endregion + + + /// + /// Constructor, without parameters is mandatory to cal Initialize + /// + public TileEditor() + { + InitializeComponent(); + } + + + /// + /// Constructor + /// + /// Name of the file + public TileEditor(string fileName) + { + InitializeComponent(); + new Thread(() => Initialize(fileName)).Start(); + } + + + /// + /// Initialize the system + /// + /// + /// True if OK, or False if error + public bool Initialize(string fileName) + { + Dispatcher.UIThread.InvokeAsync(() => + { + _Initialize(fileName); + }); + return true; + } + + + private void _Initialize(string fileName) + { + _Modified = true; + + ServiceLayer.Initialize(); + + FileName = fileName; + + TilePatternsList = new List(); + + TileMain = ServiceLayer_Maps.Tiles_LoadMapsTiles(fileName); + if (TileMain == null) + { + TileMain = new ZXMapsTiles() + { + GraphicMode = GraphicsModes.Monochrome, + Height = 16, + Width = 16, + Name = Path.GetFileName(fileName).ToStringNoNull().Replace(".zxtil", ""), + Tiles = new List() + }; + } + + wpTileList.ItemWidth = (TileMain.Width * 4) + 4; + wpTileList.ItemHeight = (TileMain.Height * 4) + 4; + + foreach (var Tile in TileMain.Tiles) + { + // Create pattern list + var tpc = new TilePatternControl(); + tpc.Initialize(Tile, TileList_Command, TileMain.Width, TileMain.Height); + TilePatternsList.Add(tpc); + wpTileList.Children.Add(tpc); + wpTileList.InvalidateMeasure(); + } + + if (TilePatternsList.Count == 0) + { + TileList_AddTile(null); + ctrlEditor.Initialize(Editor_Command); + ctrlPreview.Initialize(null); + ctrlTileProperties.Initialize(TileMain, TileProperties_Command); + } + else + { + ctrlEditor.Initialize(Editor_Command); + ctrlPreview.Initialize(TilePatternsList[0].TileData); + ctrlTileProperties.Initialize(TileMain, TileProperties_Command); + } + + ctrlMapFileSelector.Initialize(TileMain.DefaultMap, MapFileSelector_Command); + ctrlMapProperties.Initialize(ctrlMapFileSelector.Map, TileMain, MapProperties_Command); + ctrlMapEditor.Initialize(ctrlMapFileSelector.Map, TileMain, MapEditor_Command); + + btnTileMoveLeft.Tapped += BtnTileMoveLeft_Tapped; + btnTileMoveRight.Tapped += BtnTileMoveRight_Tapped; + btnTileInfoHide.Tapped += BtnTileInfoHide_Tapped; + btnTileInfoShow.Tapped += BtnTileInfoShow_Tapped; + btnTileDelete.Tapped += BtnTileDelete_Tapped; + + Tiles_Renum(); + Refresh(); + + if (TilePatternsList.Count > 1) + { + TileList_Command(TilePatternsList.ElementAt(0), "SELECTED"); + } + + InitializeShortcuts(); + + this.AddHandler(KeyDownEvent, Keyboard_Down, handledEventsToo: true); + this.Focus(); + grdProcesando.IsVisible = false; + _Modified = false; + + // Calcular altura inicial del WrapPanel + CalculateWrapPanelHeight(); + + // Suscribirse a cambios de tamaño para recalcular la altura + wpTileList.SizeChanged += (s, e) => CalculateWrapPanelHeight(); + + tileWidth = TileMain.Width; + tileHeight = TileMain.Height; + } + + private void BtnTileDelete_Tapped(object? sender, TappedEventArgs e) + { + var ctrl = TilePatternsList.FirstOrDefault(d => d.TileData.Id == selectedTileId); + if (ctrl != null) + { + TileList_Delete(ctrl); + } + } + + public void Refresh() + { + ctrlEditor.Refresh(); + } + + + #region TileList + + private void TileList_Command(TilePatternControl sender, string command) + { + switch (command) + { + case "ADD": + if (sender.TileData != null) + { + sender.TileData.Id = TilePatternsList.Count - 1; + } + TileList_AddTile(null); + ctrlEditor.TileData = sender.TileData; + ctrlPreview.TileData = sender.TileData; + ctrlPreview.Refresh(); + TileList_Modified(sender.TileData); + break; + case "SELECTED": + TileList_Unselect(sender); + ctrlEditor.TileData = sender.TileData; + ctrlPreview.TileData = sender.TileData; + ctrlEditor.Frame = 0; + ctrlPreview.Refresh(); + TileProperties_FrameUpdate(ctrlTileProperties, command); + if (sender.TileData != null) + { + ctrlMapEditor.CurrentTile = sender.TileData.Id; + } + selectedTileId = sender.TileData?.Id ?? 0; + break; + } + } + + + private void BtnTileMoveRight_Tapped(object? sender, TappedEventArgs e) + { + if (selectedTileId == (TileMain.Tiles.Count - 1)) + { + return; + } + + var temp = TileMain.Tiles[selectedTileId]; + TileMain.Tiles[selectedTileId] = TileMain.Tiles[selectedTileId + 1]; + TileMain.Tiles[selectedTileId + 1] = temp; + selectedTileId++; + Tiles_Renum(); + Tiles_Select(selectedTileId); + } + + + private void BtnTileMoveLeft_Tapped(object? sender, TappedEventArgs e) + { + if (selectedTileId < 1) + { + return; + } + + var temp = TileMain.Tiles[selectedTileId]; + TileMain.Tiles[selectedTileId] = TileMain.Tiles[selectedTileId - 1]; + TileMain.Tiles[selectedTileId - 1] = temp; + selectedTileId--; + Tiles_Renum(); + Tiles_Select(selectedTileId); + } + + + private void Tiles_Select(int id) + { + var tpc = TilePatternsList.FirstOrDefault(d => + d != null && + d.TileData != null && + d.TileData.Id == id); + if (tpc != null) + { + tpc.Select(); + } + } + + + private void Tiles_Renum() + { + for (int n = 0; n < TilePatternsList.Count; n++) + { + if (TilePatternsList[n].TileData == null || TileMain.Tiles[n] == null) + { + TilePatternsList[n].TileData = null; + TileMain.Tiles[n] = null; + continue; + } + + TileMain.Tiles[n].Id = n; + var spc = TilePatternsList[n]; + if (spc.TileData != null) + { + spc.TileData = TileMain.Tiles[n]; + spc.TileData.Id = n; + spc.IsSelected = false; + if (n == selectedTileId) + { + ctrlEditor.TileData = TileMain.Tiles[n]; + ctrlEditor.Refresh(); + ctrlPreview.TileData = TileMain.Tiles[n]; + ctrlPreview.Refresh(); + } + } + } + ctrlMapEditor.Refresh(true); + } + + + private void BtnTileInfoHide_Tapped(object? sender, TappedEventArgs e) + { + btnTileInfoShow.IsVisible = true; + btnTileInfoHide.IsVisible = false; + foreach (var ctrl in TilePatternsList) + { + ctrl.InfoVisible = false; + } + } + + + private void BtnTileInfoShow_Tapped(object? sender, TappedEventArgs e) + { + btnTileInfoShow.IsVisible = false; + btnTileInfoHide.IsVisible = true; + foreach (var ctrl in TilePatternsList) + { + ctrl.InfoVisible = true; + } + } + + + private void TileList_AddTile(ZXMapsTile TileData) + { + if (TileData!=null && TileData.Palette == null) + { + TileData.Palette = ServiceLayer.GetPalette(TileData.GraphicMode); + } + TileMain.Tiles.Add(TileData); + + TilePatternControl selectedTile = null; + int id = 0; + if (TilePatternsList.Count > 0) + { + var sds = TilePatternsList.Where(d => d.TileData != null); + if (sds.Any()) + { + id = sds.Max(d => d.TileData.Id) + 1; + } + } + for (int n = 0; n < TilePatternsList.Count; n++) + { + var spc = TilePatternsList[n]; + if (spc.TileData != null && spc.TileData.Id < 0) + { + spc.TileData.Id = id; + if (string.IsNullOrEmpty(spc.TileData.Name)) + { + spc.TileData.Name = "Tile " + spc.TileData.Id.ToString(); + } + spc.TileData.Export = true; + selectedTile = spc; + break; + } + if (spc.TileData == null && TileData != null) + { + TilePatternsList[n].TileData = TileData; + selectedTile = TilePatternsList[n]; + break; + } + } + + // Add void Tile + var TilePattern = new TilePatternControl(); + TilePatternsList.Add(TilePattern); + wpTileList.Children.Add(TilePattern); + TilePattern.Initialize(null, TileList_Command, TileMain.Width, TileMain.Height); + wpTileList.InvalidateMeasure(); + CalculateWrapPanelHeight(); + TileList_Unselect(selectedTile); + } + + + private void TileList_Unselect(TilePatternControl selected) + { + foreach (var control in TilePatternsList) + { + if (control == selected) + { + control.IsSelected = true; + } + else + { + control.IsSelected = false; + } + } + } + + + private void TileList_Delete(TilePatternControl control) + { + try + { + var t = TileMain.Tiles.FirstOrDefault(d => d.Id == control.TileData.Id); + if (t != null) + { + TileMain.Tiles.Remove(t); + } + TilePatternsList.Remove(control); + wpTileList.Children.Remove(control); + ctrlEditor.TileData = null; + ctrlPreview.TileData = null; + ctrlTileProperties.TileData = null; + control = null; + CalculateWrapPanelHeight(); + Tiles_Renum(); + } + catch { } + } + + + private void TileList_Insert(ZXMapsTile TileData) + { + var current = TileData.CurrentFrame; + var curPat = TileData.Patterns[current]; + + var pat = curPat.Clonar(); + pat.RawData = new int[pat.RawData.Length]; + + var pats = TileData.Patterns.Take(current).ToList(); + pats.Add(pat); + pats.AddRange(TileData.Patterns.Skip(current)); + TileData.Patterns = pats; + TileData.Frames++; + ctrlTileProperties.Refresh(); + ctrlEditor.Refresh(); + } + + + private void TileList_Modified(ZXMapsTile TileData) + { + if (!_Modified) + { + _Modified = true; + DocumentModified?.Invoke(this, EventArgs.Empty); + } + + if (TileData == null) + { + return; + } + + var ctrl = TilePatternsList.FirstOrDefault(d => d.TileData != null && d.TileData.Id == TileData.Id); + if (ctrl != null) + { + ctrl.TileData = TileData; + ctrl.Refresh(); + } + + var td = TileMain.Tiles.FirstOrDefault(d => d != null && d.Id == TileData.Id); + if (td != null) + { + td = TileData; + } + else + { + for (int n = 0; n < TileMain.Tiles.Count; n++) + { + if (TileMain.Tiles[n] == null) + { + TileMain.Tiles[n] = TileData; + break; + } + } + } + + wpTileList.ItemWidth = (TileMain.Width * 4) + 4; + wpTileList.ItemHeight = (TileMain.Height * 4) + 4; + + // Calcular altura del WrapPanel basada en el contenido + CalculateWrapPanelHeight(); + wpTileList.InvalidateMeasure(); + + ctrlMapEditor.Refresh(false); + } + + private void CalculateWrapPanelHeight() + { + if (wpTileList.ItemWidth <= 0 || wpTileList.ItemHeight <= 0 || wpTileList.Children.Count == 0) + { + return; + } + + // Obtener el ancho disponible del ScrollViewer padre + double availableWidth = wpTileList.Bounds.Width; + + // Si el ancho no está disponible aún, intentar obtenerlo del control padre + if (availableWidth <= 0) + { + var parent = wpTileList.Parent as Control; + if (parent != null) + { + availableWidth = parent.Bounds.Width; + } + } + + // Si aún no está disponible, usar una estimación basada en el número de elementos + if (availableWidth <= 0) + { + // Estimación: asumir que el ScrollViewer tiene aproximadamente 160-180 px de ancho + // Esto es relativo al contenedor padre + availableWidth = 160; + } + + // Asegurar que hay espacio suficiente + if (availableWidth < wpTileList.ItemWidth) + { + availableWidth = wpTileList.ItemWidth; + } + + // Calcular el número de columnas que caben + int itemsPerRow = Math.Max(1, (int)(availableWidth / wpTileList.ItemWidth)); + + // Calcular el número de filas necesarias + int totalRows = Math.Max(1, (int)Math.Ceiling((double)wpTileList.Children.Count / itemsPerRow)); + + // Calcular la altura total (número de filas * altura de cada item) + double calculatedHeight = totalRows * wpTileList.ItemHeight; + + wpTileList.Height = calculatedHeight; + } + + #endregion + + + #region Tile preview + + private void TilePreview_Command(TilePreviewControl sender, string command) + { + + } + + + private void TileProperties_FrameUpdate(TilePropertiesControl sender, string command) + { + if (sender.TileData == null) + { + return; + } + + // TODO: Multiple frames per tile? + /* + int f = sender.TileData.Frames - 1; + if (f < 0) + { + f = 0; + } + else if (f > 255) + { + f = 255; + } + txtFrame.Maximum = f; + txtFrame.Text = f.ToString(); + txtFrame.UpdateLayout(); + */ + TileProperties_Command(sender, "REFRESH"); + } + #endregion + + + #region Tile properties + + private void TileProperties_Command(TilePropertiesControl sender, string command) + { + try + { + var selectedTile = TilePatternsList.FirstOrDefault(d => d.IsSelected)?.TileData; + + switch (command) + { + case "MODE": + { + switch (sender.TileData.GraphicMode) + { + case GraphicsModes.Monochrome: + ctrlEditor.PrimaryColorIndex = 1; + ctrlEditor.SecondaryColorIndex = 0; + break; + case GraphicsModes.ZXSpectrum: + ctrlEditor.PrimaryColorIndex = 0; + ctrlEditor.SecondaryColorIndex = 7; + break; + } + ctrlEditor.TileData = selectedTile; + TileList_Modified(selectedTile); + ctrlEditor.UpdateColorPanel(); + } + break; + case "NAME": + break; + case "SIZE": + ResizePatterns(); + break; + } + } + catch { } + } + + + private void ResizePatterns() + { + for (int n = 0; n < TilePatternsList.Count; n++) + { + var pattern = TilePatternsList[n]; + if (pattern != null && pattern.TileData != null) + { + if (pattern.TileData.Patterns != null && pattern.TileData.Patterns.Count > 0) + { + pattern.TileData.Patterns[0] = ResizePattern(pattern.TileData.Patterns[0]); + } + pattern.TileData.Width = TileMain.Width; + pattern.TileData.Height = TileMain.Height; + pattern.Refresh(); + } + } + tileWidth = TileMain.Width; + tileHeight = TileMain.Height; + + ctrlPreview.TileData.Width = TileMain.Width; + ctrlPreview.TileData.Height = TileMain.Height; + ctrlPreview.Refresh(); + ctrlEditor.TileData.Width = TileMain.Width; + ctrlEditor.TileData.Height = TileMain.Height; + ctrlEditor.Refresh(); + } + + + private Pattern ResizePattern(Pattern pattern) + { + var pat = new Pattern() + { + Id = pattern.Id, + Name = pattern.Name, + Number = pattern.Number, + RawData = new int[TileMain.Width * TileMain.Height], + Attributes = new AttributeColor[(TileMain.Width / 8) * (TileMain.Height / 8)] + }; + for (int n = 0; n < pat.Attributes.Length; n++) + { + pat.Attributes[n] = new AttributeColor() + { + Paper = ctrlEditor.SecondaryColorIndex, + Ink = ctrlEditor.PrimaryColorIndex + }; + } + + for (int y = 0; y < TileMain.Height; y++) + { + for (int x = 0; x < TileMain.Width; x++) + { + int colorIndex = 0; + if (x < tileWidth && y < tileHeight) + { + colorIndex = pattern.RawData[(y * tileWidth) + x]; + } + + pat.RawData[(y * TileMain.Width) + x] = colorIndex; + } + } + return pat; + } + + #endregion + + + + private void TileList_Clone(ZXMapsTile TileData) + { + TilePatternControl selectedTile = null; + int id = TilePatternsList.Where(d => d.TileData != null).Max(d => d.TileData.Id) + 1; + var spc = TilePatternsList.FirstOrDefault(d => d.TileData == null); + if (spc == null) + { + return; + } + var sd = TileData.Clonar(); + sd.Id = id; + sd.Name = TileData.Name + " - copy"; + spc.TileData = sd; + + var TilePattern = new TilePatternControl(); + TilePatternsList.Add(TilePattern); + wpTileList.Children.Add(TilePattern); + TilePattern.Initialize(null, TileList_Command, TileMain.Width, TileMain.Height); + wpTileList.InvalidateMeasure(); + CalculateWrapPanelHeight(); + + spc.Select(); + } + + + + + + #region Tile editor + + private void Editor_Command(TilePatternEditor sender, string command) + { + try + { + switch (command) + { + case "ADD": + var tile = sender.TileData; + if (tile == null) + { + return; + } + TileList_AddTile(tile); + TileList_Modified(sender.TileData); + ctrlPreview.TileData = tile; + ctrlPreview.Refresh(); + ctrlTileProperties.TileData = TileMain; + break; + + case "REFRESH": + { + var cur = TilePatternsList.FirstOrDefault(d => d.IsSelected); + if (cur != null) + { + cur.TileData = sender.TileData; + cur.Refresh(); + TileList_Modified(sender.TileData); + ctrlTileProperties.TileData = TileMain; + ctrlTileProperties.PrimaryColor = sender.PrimaryColorIndex; + ctrlTileProperties.SecondaryColor = sender.SecondaryColorIndex; + ctrlPreview.TileData = sender.TileData; + + //btnColorPicker.IsChecked = !ctrlEditor.ColorPicker; + ctrlEditor.UpdateColorPanel(); + } + } + break; + + case "IMPORT": + Import(); + break; + + case "EXPORT": + Tiles_Export(); + break; + } + } + catch { } + } + + + private void Tiles_Export() + { + var dlg = new TileExportDialog(); + dlg.Initialize(FileName, TilePatternsList.Select(d => d.TileData)); + dlg.ShowDialog(this.VisualRoot as Window); + } + + #endregion + + + #region Keyboard shortcus + + private Dictionary _keybCommands = new Dictionary(); + + internal static Dictionary keyboardCommands = new Dictionary { + { "Save", new ZXKeybCommand{ CommandId = Guid.Parse("87f7d73b-d28a-44f4-ba0c-41baa4de238c"), CommandName = "Save", Key = Key.S, Modifiers = KeyModifiers.Control } }, + { "Copy",new ZXKeybCommand{ CommandId = Guid.Parse("fee014bb-222b-42e3-80f3-048325b70e34"), CommandName = "Copy", Key = Key.C, Modifiers = KeyModifiers.Control } }, + { "Cut",new ZXKeybCommand{ CommandId = Guid.Parse("1edf352f-238b-421e-b69f-613dc63c0e47"), CommandName = "Cut", Key = Key.X, Modifiers = KeyModifiers.Control } }, + { "Paste", new ZXKeybCommand{ CommandId = Guid.Parse("f5d450b0-d126-4f62-885b-b3e28e638542"), CommandName = "Paste", Key = Key.V, Modifiers = KeyModifiers.Control } }, + { "Undo", new ZXKeybCommand{ CommandId = Guid.Parse("912c1887-ab37-4c0a-9aee-65d84b4521c7"), CommandName = "Undo", Key = Key.Z, Modifiers = KeyModifiers.Control } }, + { "Redo", new ZXKeybCommand{ CommandId = Guid.Parse("c5c506f0-d5e4-429f-ad21-af8cee7d1d9a"), CommandName = "Redo", Key = Key.Z, Modifiers = KeyModifiers.Control | KeyModifiers.Shift } }, + + { "Clear", new ZXKeybCommand{ CommandId = Guid.Parse("246e1449-0a64-4327-a8d1-f1dbecbc69d2"), CommandName = "Clear", Key = Key.Q, Modifiers = KeyModifiers.Control | KeyModifiers.Shift } }, + { "Rotate Right", new ZXKeybCommand{ CommandId = new Guid("490e8830-f268-48f3-8989-92d6e22b8790"), CommandName = "Rotate Right", Key = Key.R, Modifiers = KeyModifiers.None } }, + { "Rotate Left", new ZXKeybCommand{ CommandId = new Guid("1da1bae1-2242-4061-9fa2-9265fc7f74b1"), CommandName = "Rotate Left", Key = Key.R, Modifiers = KeyModifiers.Shift } }, + { "Horizontal Mirror", new ZXKeybCommand{ CommandId = new Guid("aad6fb75-15c7-4f09-aa1c-af1e1b62f849"), CommandName = "Horizontal Mirror", Key = Key.H, Modifiers = KeyModifiers.None } }, + { "Vertical Mirror", new ZXKeybCommand{ CommandId = new Guid("3e20f906-c2e5-46b1-aa6d-8ee503769917"), CommandName = "Vertical Mirror", Key = Key.V, Modifiers = KeyModifiers.None } }, + { "Shift Up", new ZXKeybCommand{ CommandId = new Guid("7a2f0381-b603-4c5f-aefd-08ebafc74e5c"), CommandName = "Shift Up", Key = Key.Up, Modifiers = KeyModifiers.None } }, + { "Shift Right", new ZXKeybCommand{ CommandId = new Guid("75a310dd-66ec-41a5-bc8d-1b3408fd4f41"), CommandName = "Shift Right", Key = Key.Right, Modifiers = KeyModifiers.None } }, + { "Shift Down", new ZXKeybCommand{ CommandId = new Guid("2a73e9cd-f318-4a15-bdc7-daf35429c6e5"), CommandName = "Shift Down", Key = Key.Down, Modifiers = KeyModifiers.None } }, + { "Shift Left", new ZXKeybCommand{ CommandId = new Guid("1c8f1ec5-596c-4e7f-9fb6-9d5ff527b37a"), CommandName = "Shift Left", Key = Key.Left, Modifiers = KeyModifiers.None } }, + { "Move Up", new ZXKeybCommand{ CommandId = new Guid("6c202c3d-921f-43f7-b810-33febc8ad947"), CommandName = "Move Up", Key = Key.Up, Modifiers = KeyModifiers.Shift } }, + { "Move Right", new ZXKeybCommand{ CommandId = new Guid("8e2e96d0-0aa4-426e-a340-32cb53368000"), CommandName = "Move Right", Key = Key.Right, Modifiers = KeyModifiers.Shift } }, + { "Move Down", new ZXKeybCommand{ CommandId = new Guid("bdd1c206-e3ec-4a7d-8e61-42b620097b31"), CommandName = "Move Down", Key = Key.Down, Modifiers = KeyModifiers.Shift } }, + { "Move Left", new ZXKeybCommand{ CommandId = new Guid("6a6b98cf-5427-4d85-a230-6ab18544e312"), CommandName = "Move Left", Key = Key.Left, Modifiers = KeyModifiers.Shift } }, + { "Invert", new ZXKeybCommand{ CommandId = new Guid("e88125e8-f671-4085-ae8c-8d1f4866738a"), CommandName = "Invert", Key = Key.I, Modifiers = KeyModifiers.None } }, + { "Mask", new ZXKeybCommand{ CommandId = new Guid("efd5a1f4-cab8-4003-8370-8adf81176081"), CommandName = "Mask", Key = Key.M, Modifiers = KeyModifiers.None } }, + { "Export", new ZXKeybCommand{ CommandId = new Guid("321119c0-5c9b-40b5-9385-a62ca013f83c"), CommandName = "Export", Key = Key.E, Modifiers = KeyModifiers.None } }, + { "Zoom In", new ZXKeybCommand{ CommandId = new Guid("7e958c3d-c135-46b2-b041-63b8c7828787"), CommandName = "Zoom In", Key = Key.Add, Modifiers = KeyModifiers.None } }, + { "Zoom Out", new ZXKeybCommand{ CommandId = new Guid("d69843bb-8ad0-4eea-be58-29646dddaeed"), CommandName = "Zoom Out", Key = Key.Subtract, Modifiers = KeyModifiers.None } }, + }; + + + private void InitializeShortcuts() + { + DisableCommand(ApplicationCommands.Cut); + DisableCommand(ApplicationCommands.Copy); + DisableCommand(ApplicationCommands.Paste); + + _keybCommands = new Dictionary() + { + { keyboardCommands["Save"].CommandId, () => { RequestSaveDocument?.Invoke(this, EventArgs.Empty); } }, + { keyboardCommands["Copy"].CommandId, () => { ctrlEditor.Copy(); } }, + { keyboardCommands["Cut"].CommandId, () => { ctrlEditor.Cut(); } }, + { keyboardCommands["Paste"].CommandId, () => { ctrlEditor.Paste(); } }, + { keyboardCommands["Undo"].CommandId, () => { ctrlEditor.Undo(); } }, + { keyboardCommands["Redo"].CommandId, () => { ctrlEditor.Redo(); } }, + + { keyboardCommands["Clear"].CommandId, () => { ctrlEditor.Clear(); } }, + { keyboardCommands["Rotate Right"].CommandId, () => { ctrlEditor.RotateRight(); } }, + { keyboardCommands["Rotate Left"].CommandId, () => { ctrlEditor.RotateLeft(); } }, + { keyboardCommands["Horizontal Mirror"].CommandId, () => { ctrlEditor.HorizontalMirror(); } }, + { keyboardCommands["Vertical Mirror"].CommandId, () => { ctrlEditor.VerticalMirror(); } }, + { keyboardCommands["Shift Up"].CommandId, () => { ctrlEditor.ShiftUp(); } }, + { keyboardCommands["Shift Right"].CommandId, () => { ctrlEditor.ShiftRight(); } }, + { keyboardCommands["Shift Down"].CommandId, () => { ctrlEditor.ShiftDown(); } }, + { keyboardCommands["Shift Left"].CommandId, () => { ctrlEditor.ShiftLeft(); } }, + { keyboardCommands["Move Up"].CommandId, () => { ctrlEditor.MoveUp(); } }, + { keyboardCommands["Move Right"].CommandId, () => { ctrlEditor.MoveRight(); } }, + { keyboardCommands["Move Down"].CommandId, () => { ctrlEditor.MoveDown(); } }, + { keyboardCommands["Move Left"].CommandId, () => { ctrlEditor.MoveLeft(); } }, + { keyboardCommands["Invert"].CommandId, () => { ctrlEditor.Invert(); } }, + { keyboardCommands["Mask"].CommandId, () => { ctrlEditor.Mask(); } }, + { keyboardCommands["Export"].CommandId, () => { ctrlEditor.Export(); } }, + { keyboardCommands["Zoom In"].CommandId, () => { ctrlEditor.ZoomIn(); } }, + { keyboardCommands["Zoom Out"].CommandId, () => { ctrlEditor.ZoomOut(); } }, + }; + } + + + private void DisableCommand(RoutedCommand cut) + { + var field = typeof(RoutedCommand).GetField("k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); + KeyGesture g = new KeyGesture(Key.None, KeyModifiers.None); + field.SetValue(cut, g); + } + + private void Keyboard_Down(object? sender, Avalonia.Input.KeyEventArgs e) + { + var commandId = ZXKeybMapper.GetCommandId(documentTypeId, e.Key, e.KeyModifiers); + + if (commandId != null && _keybCommands.ContainsKey(commandId.Value)) + { + _keybCommands[commandId.Value](); + } + } + + private void tabControlEditor_SelectionChanged(object? sender, SelectionChangedEventArgs e) + { + if (tabControlEditor == null) + { + return; + } + + if (tabControlEditor.SelectedIndex == 1) + { + ctrlMapEditor.Refresh(false); + } + } + + #endregion + + + #region Import tiles + + private void Import() + { + var dlg = new TileImportDialog(); + dlg.Initialize(TilePatternsList.Select(d => d.TileData), Import_Command); + dlg.ShowDialog(this.VisualRoot as Window); + } + + + private void Import_Command(ZXMapsTile Tile, string command) + { + try + { + switch (command) + { + case "ADD": + Tile.Id = TilePatternsList.Count - 1; + TileList_AddTile(Tile); + ctrlEditor.TileData = Tile; + ctrlPreview.TileData = Tile; + ctrlPreview.Refresh(); + ctrlMapEditor.Refresh(false); + break; + } + } + catch { } + } + + #endregion + + + #region MapFileSelector + + private void MapFileSelector_Command(MapFileSelectorControl sender, string command) + { + Map = sender.Map; + var old = TileMain.DefaultMap; + if (Map == null) + { + TileMain.DefaultMap = null; + } + else + { + TileMain.DefaultMap = Map.Name; + } + ctrlMapProperties.Map = sender.Map; + + if (old != TileMain.DefaultMap) + { + _Modified = true; + DocumentModified?.Invoke(this, EventArgs.Empty); + ctrlMapEditor.Map = Map; + } + } + + #endregion + + + #region MapProperties + + private void MapProperties_Command(MapPropertiesControl control, string arg2) + { + + } + + #endregion + + + #region MapEditor + + private void MapEditor_Command(MapPatternEditor editor, string comando) + { + switch (comando) + { + case "REFRESH": + { + foreach(var tc in TilePatternsList) + { + tc.Refresh(); + } + } + break; + case "EXPORT": + Map_Export(); + break; + } + } + + + private void Map_Export() + { + var dlg = new MapExportDialog(); + dlg.Initialize(FileName, Map); + dlg.ShowDialog(this.VisualRoot as Window); + } + + #endregion + } +} diff --git a/ZXBStudio/DocumentEditors/ZXMaps/TileExportDialog.axaml b/ZXBStudio/DocumentEditors/ZXMaps/TileExportDialog.axaml new file mode 100644 index 0000000..4d314ef --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/TileExportDialog.axaml @@ -0,0 +1,75 @@ + + + + + + + + + Options + + + + + + + Enable build: + + + Data type: + + DIM (data as array) + ASM (DEFB inside ASM Block) + .BIN file (INCBIN "file.bin") + .TAP file (LOAD "" CODE) + + + File path: + + + + Var prefix: + + + Array base: + + 0 (default) + 1 + From project settings + + + Export attributes + + + + + You have not selected any sprite to export. Check the "Include in the export" box of the sprites you want to include in the export. + + + Example of usage + + + + + + + + + + + + + + + + diff --git a/ZXBStudio/DocumentEditors/ZXMaps/TileExportDialog.axaml.cs b/ZXBStudio/DocumentEditors/ZXMaps/TileExportDialog.axaml.cs new file mode 100644 index 0000000..72094db --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/TileExportDialog.axaml.cs @@ -0,0 +1,386 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Platform.Storage; +using MsBox.Avalonia; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using ZXBasicStudio.Classes; +using ZXBasicStudio.Common; +using ZXBasicStudio.DocumentEditors.ZXGraphics.log; +using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; +using ZXBasicStudio.DocumentModel.Classes; +using ZXBasicStudio.DocumentModel.Interfaces; +using ZXBasicStudio.Extensions; +using ZXBasicStudio.IntegratedDocumentTypes.CodeDocuments.Basic; +using static System.Net.Mime.MediaTypeNames; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps +{ + public partial class TileExportDialog : Window + { + private string fileName = ""; + private IEnumerable tiles = null; + + + private FileTypeConfig fileType = null; + private Pattern[] patterns = null; + private ExportConfig exportConfig = null; + + + public TileExportDialog() + { + InitializeComponent(); + + btnCancel.Tapped += BtnCancel_Tapped; + btnCopy.Tapped += BtnCopy_Tapped; + btnExport.Tapped += BtnExport_Tapped; + btnOutputFile.Tapped += BtnOutputFile_Tapped; + cmbDataType.SelectionChanged += CmbDataType_SelectionChanged; + cmbArrayBase.SelectionChanged += CmbArrayBase_SelectionChanged; + chkAttr.Click += ChkAttr_Click; + btnSave.Tapped += BtnSave_Tapped; + } + + + public bool Initialize(string fileName, IEnumerable tilesData) + { + this.fileName = fileName; + this.tiles = tilesData; + + exportConfig = ServiceLayer.Export_GetConfigFile(fileName + ".zbs"); + if (exportConfig == null) + { + exportConfig = ServiceLayer.Export_Tile_GetDefaultConfig(fileName); + } + + this.cmbSelectExportType.Initialize(ExportType_Changed); + cmbSelectExportType.InitializeTile(); + + txtOutputFile.Text = exportConfig.ExportFilePath; + txtLabelName.Text = exportConfig.LabelName; + chkAuto.IsChecked = exportConfig.AutoExport; + cmbDataType.SelectedIndex = (int)exportConfig.ExportDataType; + cmbArrayBase.SelectedIndex = exportConfig.ArrayBase.ToInteger(); + chkAttr.IsChecked = exportConfig.IncludeAttr; + + cmbSelectExportType.ExportType = exportConfig.ExportType; + + return true; + } + + + #region ExportOptions + + private void ExportType_Changed(ExportTypes exportType) + { + exportConfig.ExportType = exportType; + Refresh(); + } + + + private void Refresh() + { + grdOptions.IsVisible = true; + + chkAuto.IsVisible = true; + + lblOutputFile.IsVisible = true; + txtOutputFile.IsVisible = true; + btnOutputFile.IsVisible = true; + + lblLabelName.IsVisible = true; + txtLabelName.IsVisible = true; + + lblArrayBase.IsVisible = true; + cmbDataType.IsVisible = true; + cmbArrayBase.IsVisible = true; + + bool canExport = false; + if (tiles == null || tiles.Where(d => d != null && d.Export).Count() == 0) + { + txtError.IsVisible = true; + } + else + { + canExport = true; + txtError.IsVisible = false; + } + + switch (exportConfig.ExportType) + { + case ExportTypes.PutChars: + CreateExportPath(".bas"); + if (canExport) + { + CreateExample_PutChars(); + } + break; + default: + grdOptions.IsVisible = false; + break; + } + } + + + private void CreateExportPath(string extension) + { + if (string.IsNullOrEmpty(exportConfig.ExportFilePath)) + { + txtOutputFile.Text = fileType.FileName + extension; + var spName = Path.GetFileNameWithoutExtension(fileType.FileName.ToStringNoNull()); + txtLabelName.Text = spName; + if (spName.Length > 10) + { + spName = spName.Substring(0, 10); + } + } + } + + + #endregion + + + #region Examples + + private void GetConfigFromUI() + { + if (exportConfig == null) + { + exportConfig = new ExportConfig(); + } + exportConfig.ArrayBase = cmbArrayBase.SelectedIndex.ToInteger(); + exportConfig.AutoExport = chkAuto.IsChecked == true; + exportConfig.ExportDataType = (ExportDataTypes)cmbDataType.SelectedIndex.ToInteger(); + exportConfig.ExportFilePath = txtOutputFile.Text.ToStringNoNull(); + exportConfig.ExportType = cmbSelectExportType.ExportType; + exportConfig.LabelName = txtLabelName.Text.ToStringNoNull(); + exportConfig.ZXAddress = 0; + exportConfig.ZXFileName = ""; + exportConfig.IncludeAttr = chkAttr.IsChecked == true; + } + + private void CreateExample_PutChars() + { + if (tiles == null || tiles.Count() == 0) + { + txtCode.Text = ""; + } + + var tile = tiles.ElementAt(0); + var res = ExportManager.Export_Tiles_PutChars(exportConfig, tiles); + if (res.StartsWith("ERROR:")) + { + txtError.Text = res; + txtError.IsVisible = true; + return; + } + else + { + txtError.IsVisible = false; + } + + var sb = new StringBuilder(); + switch (exportConfig.ExportDataType) + { + case ExportDataTypes.DIM: + { + sb.AppendLine("'- Includes -----------------------------------------------"); + sb.AppendLine("#INCLUDE "); + sb.AppendLine(""); + sb.AppendLine(string.Format("' Can use: #INCLUDE \"{0}\"", + Path.GetFileName(exportConfig.ExportFilePath))); + sb.AppendLine(res); + sb.AppendLine(""); + sb.AppendLine("'- Draw tile --------------------------------------------"); + + sb.AppendLine(string.Format( + "putChars(10,5,{0},{1},@{2}({3},0))", + tile.Width / 8, + tile.Height / 8, + exportConfig.LabelName, + tile.Frames == 1 ? "0" : "0,0")); + sb.AppendLine(""); + } + break; + + case ExportDataTypes.ASM: + { + sb.AppendLine("'- Includes -----------------------------------------------"); + sb.AppendLine("#INCLUDE "); + sb.AppendLine(""); + sb.AppendLine("'- Draw tile --------------------------------------------"); + sb.AppendLine(string.Format( + "putChars(10,5,{0},{1},@{2}{3})", + tile.Width / 8, + tile.Height / 8, + exportConfig.LabelName, + tile.Name.Replace(" ", "_"))); + sb.AppendLine(""); + sb.AppendLine("' This section must not be executed"); + sb.AppendLine(string.Format("' Can use: #INCLUDE \"{0}\"", + Path.GetFileName(exportConfig.ExportFilePath))); + sb.AppendLine(res); + } + break; + + case ExportDataTypes.BIN: + { + sb.AppendLine("'- Includes -----------------------------------------------"); + sb.AppendLine("#INCLUDE "); + sb.AppendLine(""); + sb.AppendLine("'- Draw tile --------------------------------------------"); + sb.AppendLine(string.Format( + "putChars(10,5,{0},{1},@{2})", + tile.Width / 8, + tile.Height / 8, + exportConfig.LabelName)); + sb.AppendLine(""); + sb.AppendLine("' This section must not be executed"); + sb.AppendLine(string.Format( + "{0}:", + exportConfig.LabelName)); + sb.AppendLine("ASM"); + sb.AppendLine(string.Format("\tINCBIN \"{0}\"", + Path.GetFileName(exportConfig.ExportFilePath))); + sb.AppendLine("END ASM"); + } + break; + + case ExportDataTypes.TAP: + { + sb.AppendLine("'- Includes -----------------------------------------------"); + sb.AppendLine("#INCLUDE "); + sb.AppendLine(""); + sb.AppendLine("' Load .tap data ------------------------------------------"); + sb.AppendLine("LOAD \"\" CODE"); + sb.AppendLine(""); + sb.AppendLine("'- Draw tile --------------------------------------------"); + sb.AppendLine(string.Format( + "putChars(10,5,{0},{1},@{2})", + tile.Width / 8, + tile.Height / 8, + exportConfig.LabelName)); + sb.AppendLine(""); + } + break; + } + + txtCode.Text = sb.ToString(); + } + + #endregion + + + #region Export + + private void Export() + { + GetConfigFromUI(); + var em = new ExportManager(); + em.Initialize(FileTypes.Tile); + em.ExportTiles(exportConfig, tiles); + } + + #endregion + + + #region Buttons + + private void BtnSave_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + GetConfigFromUI(); + ServiceLayer.Export_SetConfigFile(fileName + ".zbs", exportConfig); + Export(); + //this.Close(); + } + + private async void BtnOutputFile_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + var fileTypes = new FilePickerFileType[2]; + fileTypes[1] = new FilePickerFileType("All files") { Patterns = new[] { "*", "*.*" } }; + fileTypes[0] = new FilePickerFileType("Tile files") { Patterns = new[] { "*.zxtiledata" } }; + + var select = await StorageProvider.SaveFilePickerAsync(new Avalonia.Platform.Storage.FilePickerSaveOptions + { + ShowOverwritePrompt = true, + FileTypeChoices = fileTypes, + Title = "Select Export path...", + }); + + if (select != null) + { + txtOutputFile.Text = Path.GetFullPath(select.Path.LocalPath); + } + } + + + private void BtnExport_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + GetConfigFromUI(); + Export(); + this.Close(); + } + + + private void BtnCopy_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + TopLevel.GetTopLevel(this)?.Clipboard?.SetTextAsync(txtCode.Text); + } + + + private void BtnCancel_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.Close(); + } + + + + private void CmbArrayBase_SelectionChanged(object? sender, SelectionChangedEventArgs e) + { + var idx = cmbArrayBase.SelectedIndex; + exportConfig.ArrayBase = idx; + Refresh(); + } + + + private void CmbDataType_SelectionChanged(object? sender, SelectionChangedEventArgs e) + { + var idx = cmbDataType.SelectedIndex; + exportConfig.ExportDataType = (ExportDataTypes)idx; + + var ext = ""; + switch (exportConfig.ExportDataType) + { + case ExportDataTypes.ASM: + case ExportDataTypes.DIM: + ext = ".bas"; + break; + case ExportDataTypes.BIN: + ext = ".bin"; + break; + case ExportDataTypes.TAP: + ext = ".tap"; + break; + default: + return; + } + txtOutputFile.Text = Path.Combine(Path.GetDirectoryName(txtOutputFile.Text), Path.GetFileNameWithoutExtension(txtOutputFile.Text) + ext); + exportConfig.ExportFilePath = txtOutputFile.Text; + Refresh(); + } + + + private void ChkAttr_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + { + exportConfig.IncludeAttr = chkAttr.IsChecked.ToBoolean(); + Refresh(); + } + + #endregion + } +} diff --git a/ZXBStudio/DocumentEditors/ZXMaps/TileImage.cs b/ZXBStudio/DocumentEditors/ZXMaps/TileImage.cs new file mode 100644 index 0000000..b580dec --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/TileImage.cs @@ -0,0 +1,235 @@ +using Avalonia; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ZXBasicStudio.DocumentEditors.ZXGraphics; +using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps +{ + public class TileImage : IZXBitmap, IDisposable + { + #region Private fields + private WriteableBitmap? bitmap; + #endregion + + #region Public properties + + public bool IsEmpty { get; private set; } + public bool ViewAttributes { get; set; } = true; + + #endregion + + #region Constructors + + public TileImage() + { + bitmap = new WriteableBitmap(new PixelSize(8, 8), new Vector(72, 72), Avalonia.Platform.PixelFormat.Rgba8888, Avalonia.Platform.AlphaFormat.Opaque); + + Clear(Colors.White); + } + + + public TileImage(ZXMapsTile Tile, int FrameNumber) + { + bitmap = new WriteableBitmap(new PixelSize(Tile.Width, Tile.Height), new Vector(72, 72), Avalonia.Platform.PixelFormat.Rgba8888, Avalonia.Platform.AlphaFormat.Opaque); + + RenderTile(Tile, FrameNumber); + } + + #endregion + + #region Public functions + + public unsafe void Clear(Color ClearColor) + { + if (bitmap == null) //disposed + return; + + using var lockData = bitmap.Lock(); + uint* data = (uint*)lockData.Address; + uint color = ToRgba(ClearColor); + + for (int y = 0; y < bitmap.PixelSize.Height; y++) + { + for (int x = 0; x < bitmap.PixelSize.Width; x++) + { + data[y * lockData.RowBytes / 4 + x] = color; + } + + } + + IsEmpty = true; + } + + + public unsafe void RenderTile(ZXMapsTile Tile, int FrameNumber) + { + try + { + if (Tile == null) + { + return; + } + if (bitmap == null) //disposed + throw new ObjectDisposedException("ZXTileImage"); + + if (bitmap.PixelSize.Width != Tile.Width || bitmap.PixelSize.Height != Tile.Height) + { + bitmap.Dispose(); + bitmap = new WriteableBitmap(new PixelSize(Tile.Width, Tile.Height), new Vector(72,72), Avalonia.Platform.PixelFormat.Rgba8888, Avalonia.Platform.AlphaFormat.Opaque); + } + + using var lockData = bitmap.Lock(); + uint* data = (uint*)lockData.Address; + + var frame = Tile.Patterns[FrameNumber]; + int index = 0; + + for (int y = 0; y < Tile.Height; y++) + { + for (int x = 0; x < Tile.Width; x++) + { + int colorIndex = frame.RawData[index++]; + + PaletteColor color; + + switch (Tile.GraphicMode) + { + case GraphicsModes.ZXSpectrum: + { + var attr = GetAttribute(Tile, frame, x, y); + if (colorIndex == 0) + { + if (ViewAttributes) + { + color = Tile.Palette[attr.Paper]; + } + else + { + color = Tile.Palette[7]; + } + } + else + { + if(ViewAttributes) + { + color = Tile.Palette[attr.Ink]; + } + else + { + color = Tile.Palette[0]; + } + } + } + break; + case GraphicsModes.Monochrome: + if (colorIndex > Tile.Palette.Length - 1) + { + colorIndex = Tile.Palette.Length - 1; + } + color = Tile.Palette[colorIndex]; + break; + default: + color = new PaletteColor { Red = 0xFF, Green = 0xFF, Blue = 0xFF }; + break; + } + + data[y * lockData.RowBytes / 4 + x] = ToRgba(color); + + } + } + + IsEmpty = false; + } catch(Exception ex) + { + + } + } + + #endregion + + #region Private functions + + uint ToRgba(PaletteColor Color) + { + return (uint)((255 << 24) | (Color.Blue << 16) | (Color.Green << 8) | Color.Red); + } + uint ToRgba(Color Color) + { + return (uint)((255 << 24) | (Color.B << 16) | (Color.G << 8) | Color.R); + } + private AttributeColor GetAttribute(ZXMapsTile Tile, Pattern Pattern, int X, int Y) + { + int cW = Tile.Width / 8; + int cX = X / 8; + int cY = Y / 8; + int dir = (cY * cW) + cX; + if (Pattern.Attributes == null) + { + Pattern.Attributes = new AttributeColor[(Tile.Width + Tile.Height) / 8]; + for (int n = 0; n < Pattern.Attributes.Length; n++) + { + Pattern.Attributes[n] = new AttributeColor() + { + Attribute = 56 // Paper 7, ink 0 + }; + } + } + if (dir > Pattern.Attributes.Length) + { + return new AttributeColor() + { + Attribute = 56 // Paper 7, ink 0 + }; + } + return Pattern.Attributes[dir]; + } + + #endregion + + #region IZXBitmap implementation + public Size Size + { + get + { + return bitmap.Size; + } + } + + + public PixelSize PixelSize + { + get + { + return bitmap.PixelSize; + } + } + + + public void Draw(DrawingContext context, Rect sourceRect, Rect destRect) + { + ((IImage)bitmap).Draw(context, sourceRect, destRect); + } + + #endregion + + #region IDisposable implementation + + public void Dispose() + { + if (bitmap == null) + return; + + bitmap.Dispose(); + bitmap = null; + IsEmpty = true; + } + + #endregion + } +} diff --git a/ZXBStudio/DocumentEditors/ZXMaps/TileImportDialog.axaml b/ZXBStudio/DocumentEditors/ZXMaps/TileImportDialog.axaml new file mode 100644 index 0000000..47ff475 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/TileImportDialog.axaml @@ -0,0 +1,97 @@ + + + + + Source + + + + Move the image with the arrow keys (shift faster) + + + + + + + Zoom 1x + + + + + Target + + + + Mode: + + Monochrome + ZX Spectrum + + + Width: + + + + + Height: + + + + + + + Append to actual tiles + + + + + Insert blank tile at start + + + + + Remove duplicated tiles + + + + + Invert palette (only in monochrome mode) + + + + + Cutoff threshold: + + + + + + + + + + + + + + diff --git a/ZXBStudio/DocumentEditors/ZXMaps/TileImportDialog.axaml.cs b/ZXBStudio/DocumentEditors/ZXMaps/TileImportDialog.axaml.cs new file mode 100644 index 0000000..a774940 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/TileImportDialog.axaml.cs @@ -0,0 +1,682 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Metadata; +using Avalonia.Platform.Storage; +using Avalonia.Threading; +using MsBox.Avalonia.Dto; +using MsBox.Avalonia.Enums; +using MsBox.Avalonia; +using SixLabors.ImageSharp.PixelFormats; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using ZXBasicStudio.Classes; +using ZXBasicStudio.Common; +using ZXBasicStudio.DocumentEditors.ZXGraphics.log; +using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; +using ZXBasicStudio.DocumentModel.Classes; +using ZXBasicStudio.DocumentModel.Interfaces; +using ZXBasicStudio.Extensions; +using ZXBasicStudio.IntegratedDocumentTypes.CodeDocuments.Basic; +using static System.Net.Mime.MediaTypeNames; +using Avalonia.Controls.Presenters; +using Avalonia.Input; +using System.Formats.Tar; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps +{ + public partial class TileImportDialog : Window, IDisposable + { + /* + private string fileName = ""; + + + private FileTypeConfig fileType = null; + private Pattern[] patterns = null; + private ExportConfig exportConfig = null; + */ + + private Action CallBackCommand = null; + private IEnumerable tiles = null; + private ZXMapsTile tile = null; + + private GraphicsModes tileMode = GraphicsModes.Monochrome; + private int tileWidth = 16; + private int tileHeight = 16; + private int tileZoom = 4; + private int cutOff = 5; + private int tileFrames = 1; + private bool appendToTiles = false; + private bool insertBlankAtStart = true; + private bool removeDuplicatedTiles = true; + private DispatcherTimer tmr = null; + private bool resetFocus = false; + private bool invert = true; + + public TileImportDialog() + { + InitializeComponent(); + + btnFile.Tapped += BtnFile_Tapped; + sldZoom.PropertyChanged += SldZoom_PropertyChanged; + + grdImport.KeyDown += GrdImport_KeyDown; + + cmbMode.SelectionChanged += CmbMode_SelectionChanged; + txtWidth.ValueChanged += TxtWidth_ValueChanged; + txtHeight.ValueChanged += TxtHeight_ValueChanged; + sldCutOff.PropertyChanged += sldCutOff_PropertyChanged; + + btnCancel.Tapped += BtnCancel_Tapped; + btnImport.Tapped += BtnImport_Tapped; + } + + public bool Initialize(IEnumerable tiles, Action callBackCommand) + { + this.CallBackCommand = callBackCommand; + this.tiles = tiles; + + tileMode = GraphicsModes.Monochrome; + tileWidth = 16; + tileHeight = 16; + + cmbMode.SelectedIndex = 0; + txtWidth.Text = tileWidth.ToString(); + txtHeight.Text = tileHeight.ToString(); + sldZoom.Value = tileZoom; + + tmr = new DispatcherTimer(TimeSpan.FromMilliseconds(1000), DispatcherPriority.Normal, UpdatePreview); + + return true; + } + + public void Dispose() + { + if (tmr != null) + { + tmr.Stop(); + tmr = null; + } + } + + #region Image source + + private async void BtnFile_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + var fileTypes = new FilePickerFileType[5]; + fileTypes[0] = new FilePickerFileType("All files") { Patterns = new[] { "*", "*.*" } }; + fileTypes[1] = new FilePickerFileType("BMP files") { Patterns = new[] { "*.bmp" } }; + fileTypes[2] = new FilePickerFileType("JPG files") { Patterns = new[] { "*.jpg", "*.jpeg" } }; + fileTypes[3] = new FilePickerFileType("PNG files") { Patterns = new[] { "*.png" } }; + fileTypes[4] = new FilePickerFileType("ZX Paintbrush SCR files") { Patterns = new[] { "*.scr" } }; + /*fileTypes[5] = new FilePickerFileType("SCR Spectrum screen files") { Patterns = new[] { "*.scr" } };*/ + + var select = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + AllowMultiple = false, + FileTypeFilter = fileTypes, + Title = "Select file to import...", + }); + + if (select != null && select.Count > 0) + { + cnvSource.LoadImage(select[0]); + } + } + + + private int lastZoom = 0; + + /// + /// Zoom changed + /// + /// + /// + private void SldZoom_PropertyChanged(object? sender, Avalonia.AvaloniaPropertyChangedEventArgs e) + { + int z = (int)sldZoom.Value; + if (z == 0 || z == lastZoom) + { + return; + } + lastZoom = z; + + txtZoom.Text = "Zoom " + z.ToString() + "x"; + cnvSource.Zoom = z; + } + + + /// + /// CutOff changed + /// + /// + /// + private void sldCutOff_PropertyChanged(object? sender, Avalonia.AvaloniaPropertyChangedEventArgs e) + { + cutOff = (int)sldCutOff.Value; + } + + + private void GrdImport_KeyDown(object? sender, Avalonia.Input.KeyEventArgs e) + { + switch (e.Key) + { + case Avalonia.Input.Key.Up: + if (e.KeyModifiers == Avalonia.Input.KeyModifiers.Shift) + { + cnvSource.OffsetY += 8; + } + else + { + cnvSource.OffsetY++; + } + break; + case Avalonia.Input.Key.Down: + if (e.KeyModifiers == Avalonia.Input.KeyModifiers.Shift) + { + cnvSource.OffsetY -= 8; + } + else + { + cnvSource.OffsetY--; + } + break; + case Avalonia.Input.Key.Left: + if (e.KeyModifiers == Avalonia.Input.KeyModifiers.Shift) + { + cnvSource.OffsetX += 8; + } + else + { + cnvSource.OffsetX++; + } + break; + case Avalonia.Input.Key.Right: + if (e.KeyModifiers == Avalonia.Input.KeyModifiers.Shift) + { + cnvSource.OffsetX -= 8; + } + else + { + cnvSource.OffsetX--; + } + break; + } + } + + #endregion + + + #region Preview + + + private void UpdatePreview(object userState, EventArgs e) + { + _UpdatePreview(); + if (resetFocus) + { + btnFile.Focus(); + resetFocus = false; + } + } + + + private void _UpdatePreview() + { + try + { + ReadProperties(); + + var imgData = cnvSource.imageData; + if (imgData == null) + { + return; + } + + GetProperties(); + + var s = new ZXMapsTile(); + s.CurrentFrame = 0; + s.DefaultColor = 7; + s.Export = true; + s.Frames = 1; + s.GraphicMode = tileMode; + s.Height = tileHeight; + s.Id = 0; + s.Masked = false; + s.Name = ""; + s.Palette = ServiceLayer.GetPalette(tileMode); + s.Patterns = new List(); + s.Width = tileWidth; + + int numAttr = (tileWidth / 8) * (tileHeight / 8); + var attrsDefault = new AttributeColor[numAttr]; + + for (int n = 0; n < numAttr; n++) + { + var attr = new AttributeColor(); + attr.Paper = 7; + attr.Ink = 0; + attr.Bright = false; + attr.Flash = false; + attrsDefault[n] = attr; + } + + var w = scrPreview.Bounds.Width; + var h = scrPreview.Bounds.Width; + double ix = (tileWidth + 2) * 4; + double iy = (tileHeight + 2) * 4; + int sw = tileWidth / 8; + int sh = tileHeight / 8; + int px = 0; + int py = 0; + int xs = 0; + int ys = 0; + int paper = -1; + int ink = -1; + int prevX = 0; + int prevY = 0; + int anchoPreview = 0; + int altoPreview = 0; + int maxX = (int)(cnvSource.Width / tileWidth); + int maxY = (int)(cnvSource.Height / tileHeight); + + pnlPreview.Children.Clear(); + + tileFrames = maxX * maxY; + + int frameNumber = 0; + int framesX = imgData.Width / tileWidth; + int framesY = imgData.Height / tileHeight; + + for (int fy = 0; fy < framesY; fy++) + { + cnvSource.OffsetY = fy * tileHeight; + for (int fx = 0; fx < framesX; fx++) + { + cnvSource.OffsetX = fx * tileWidth; + + var attrs = attrsDefault.Clonar(); + var pattern = new Pattern(); + pattern.Attributes = attrs; // new AttributeColor[(tileWidth / 8) * (tileHeight / 8)]; + pattern.Id = frameNumber; + pattern.Name = ""; + pattern.Number = frameNumber.ToString(); + pattern.RawData = new int[tileWidth * tileHeight]; + + int dir = 0; + for (int cy = 0; cy < sh; cy++) + { + py = cy * 8; + for (int cx = 0; cx < sw; cx++) + { + //ys = cnvSource.OffsetY + py; + ys = (fy * tileHeight) + py; + + px = cx * 8; + paper = -1; + ink = -1; + for (int y = 0; y < 8; y++) + { + //xs = (cnvSource.OffsetX /*+ (n * tileWidth)*/) + px; + xs = (fx * tileWidth) + px; + + dir = (((cy * 8) + y) * tileWidth) + (cx * 8); + for (int x = 0; x < 8; x++) + { + if (xs >= 0 && xs < imgData.Width && + ys >= 0 && ys < imgData.Height) + { + var c = imgData[xs, ys]; + var idxAttr = GetColor(c.R, c.G, c.B, s.Palette); + if (tileMode == GraphicsModes.ZXSpectrum) + { + // Fijar el color + var dirAttr = (cy * sw) + cx; + var attr = pattern.Attributes[dirAttr]; + attr.Bright = attr.Bright | (idxAttr > 7); + byte cCol = (byte)(idxAttr /*& 0b111*/); + if (paper == -1) + { + attr.Paper = cCol; + paper = cCol; + } + else if (ink == -1 && paper != cCol) + { + attr.Ink = cCol; + ink = cCol; + } + if (cCol == paper) + { + idxAttr = 0; + } + else + { + idxAttr = 7; + } + } + else if (tileMode == GraphicsModes.Monochrome && invert) + { + if (idxAttr == 0) + { + idxAttr = 1; + } + else + { + idxAttr = 0; + } + } + pattern.RawData[dir] = idxAttr; + } + else + { + pattern.RawData[dir] = 0; + } + dir++; + xs++; + } + ys++; + } + } + } + + bool addToPatterns = true; + if (removeDuplicatedTiles) + { + if (PatternExists(s.Patterns, pattern)) + { + addToPatterns = false; + } + } + if (addToPatterns) + { + s.Patterns.Add(pattern); + + var prev = new ZXGraphics.ZXSpriteImage(); + var img = new Avalonia.Controls.Image(); + img.Width = tileWidth * tileZoom; + img.Height = tileHeight * tileZoom; + img.Source = prev; + pnlPreview.Children.Add(img); + + Canvas.SetLeft(img, prevX); + Canvas.SetTop(img, prevY); + prevX += (tileWidth * 4); + + if (anchoPreview < prevX) + { + anchoPreview = prevX; + } + if ((prevX + sw) > w) + { + prevY += (tileHeight * 4); + prevX = 0; + if (altoPreview < prevY) + { + altoPreview = prevY; + } + } + + prev.RenderSprite(s, frameNumber); + frameNumber++; + } + } + } + + cnvSource.OffsetX = 0; + cnvSource.OffsetY = 0; + + tile = s; + pnlPreview.Width = anchoPreview + (tileWidth * 4); + pnlPreview.Height = altoPreview + (tileHeight * 4); + } + catch (Exception ex) + { + + } + } + + private bool PatternExists(List patterns, Pattern pattern) + { + foreach (var p1 in patterns) + { + bool igual = true; + for (int n = 0; n < p1.RawData.Count(); n++) + { + if (p1.RawData[n] != pattern.RawData[n]) + { + igual = false; + break; + } + } + if (igual) + { + return true; + } + } + return false; + } + + private int GetColor(byte r, byte g, byte b, PaletteColor[] palette) + { + bool brigth = false; + if (r > 250 || g > 250 || b > 250) + { + brigth = true; + } + byte cr = GetColor_CutOff(r); + byte cg = GetColor_CutOff(g); + byte cb = GetColor_CutOff(b); + PaletteColor targetColor = new PaletteColor() + { + Blue = cb, + Green = cg, + Red = cr + }; + + var palColor = palette.OrderBy(c => GetColorDistance(c, targetColor)).First(); + for (int n = 0; n < palette.Count(); n++) + { + var p = palette[n]; + if (palColor.Red == p.Red && + palColor.Green == p.Green && + palColor.Blue == p.Blue) + { + if (brigth && n < 8) + { + return n + 8; + } + return n; + } + } + return 0; + } + + + private byte GetColor_CutOff(byte c) + { + if (cutOff == 5) + { + return c; + } + + if (cutOff < 5) + { + int ic = c - (25 * (5 - cutOff)); + if (ic < 0) + { + return 0; + } + else + { + return ic.ToByte(); + } + } + else + { + int ic = c + (25 * (cutOff - 5)); + if (ic > 255) + { + return 255; + } + else + { + return ic.ToByte(); + } + } + } + + + private static double GetColorDistance(PaletteColor c1, PaletteColor c2) + { + 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); + } + + + private void GetProperties() + { + tileWidth = txtWidth.Text.ToInteger(); + tileHeight = txtHeight.Text.ToInteger(); + tileMode = (GraphicsModes)cmbMode.SelectedIndex; + } + + private void CmbMode_SelectionChanged(object? sender, SelectionChangedEventArgs e) + { + resetFocus = true; + if (cmbMode.SelectedIndex == 0) + { + pnlInvert.IsEnabled = true; + }else + { + pnlInvert.IsEnabled = false; + } + //btnFile.Focus(); + } + + + private void ChangeMode(GraphicsModes oldMode, GraphicsModes newMode) + { + resetFocus = true; + } + + + private void TxtWidth_ValueChanged(object? sender, NumericUpDownValueChangedEventArgs e) + { + resetFocus = true; + cnvSource.SpriteWidth = txtWidth.Text.ToInteger(); + cnvSource.Refresh(); + } + + private void TxtHeight_ValueChanged(object? sender, NumericUpDownValueChangedEventArgs e) + { + resetFocus = true; + cnvSource.SpriteHeight = txtHeight.Text.ToInteger(); + cnvSource.Refresh(); + } + + #endregion + + + #region Buttons + + private void BtnImport_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + Import(); + } + + private void BtnCancel_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.Close(); + this.Dispose(); + } + + #endregion + + + #region Import + + private async void Import() + { + try + { + GetProperties(); + + if (!appendToTiles) + { + // Check if existing tiles an overwrite + if (tiles.Any()) + { + if (!await this.ShowConfirm("Confirm overwrite", "The existing tiles will be deleted.\r\nAre you sure you want to overwrite it?")) + { + return; + } + + } + } + + ReadProperties(); + + for (int n = 0; n < tile.Patterns.Count(); n++) + { + var spr = tile.Clonar(); + spr.Id = n; + spr.Patterns = spr.Patterns.Skip(n).Take(1).ToList(); + spr.Frames = 1; + spr.Name = n.ToString(); + var spr2 = tiles.FirstOrDefault(d => d != null && d.Name == spr.Name); + if (spr2 == null) + { + var id = 0; + if (tiles.Count() > 0 && tiles.ElementAt(0) != null) + { + id = tiles.Where(d => d != null).Max(d => d.Id) + 1; + } + spr.Id = id; + CallBackCommand?.Invoke(spr, "ADD"); + } + else + { + spr.Id = spr2.Id; + CallBackCommand?.Invoke(tile, "UPDATE"); + } + } + + this.Close(); + this.Dispose(); + } + catch (Exception ex) + { + this.ShowError("ERROR importing image", ex.Message + ex.StackTrace); + } + } + + + private void ReadProperties() + { + tileMode = (GraphicsModes)cmbMode.SelectedIndex; + tileWidth = txtWidth.Text.ToInteger(); + tileHeight = txtHeight.Text.ToInteger(); + appendToTiles = chkAppend.IsChecked.ToBoolean(); + insertBlankAtStart = chkInsertBlank.IsChecked.ToBoolean(); + removeDuplicatedTiles = chkRemoveDuplicated.IsChecked.ToBoolean(); + invert = chkInvert.IsChecked.ToBoolean(); + if (tileMode != GraphicsModes.Monochrome) + { + invert = false; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/ZXBStudio/DocumentEditors/ZXMaps/TilePatternControl.axaml b/ZXBStudio/DocumentEditors/ZXMaps/TilePatternControl.axaml new file mode 100644 index 0000000..bacdbc2 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/TilePatternControl.axaml @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/ZXBStudio/DocumentEditors/ZXMaps/TilePatternControl.axaml.cs b/ZXBStudio/DocumentEditors/ZXMaps/TilePatternControl.axaml.cs new file mode 100644 index 0000000..04e15ed --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/TilePatternControl.axaml.cs @@ -0,0 +1,419 @@ +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using ZXBasicStudio.Common; +using ZXBasicStudio.DocumentEditors.ZXGraphics.log; +using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps +{ + public partial class TilePatternControl : UserControl + { + #region Public properties + + /// + /// Tile data + /// + public ZXMapsTile TileData + { + get + { + return _TileData; + } + set + { + _TileData = value; + IsSelected = true; + //ApplySettings(false); + Refresh(); + } + } + + /// + /// CallBack for commands: "ADD", "CLONE", "DELETE", "SELECT", "MODE", "SIZE" + /// + public Action CallBackCommand { get; set; } + + /// + /// True when then control is selected + /// + public bool IsSelected + { + get + { + return _IsSelected; + } + set + { + if (_IsSelected != value) + { + _IsSelected = value; + Refresh(); + } + } + } + + public bool InfoVisible + { + get + { + return _InfoVisible; + } + set + { + _InfoVisible = value; + Refresh(); + } + } + + /// + /// The settings has changed + /// + public bool SettingsChanged + { + get + { + return _SettingsChanged; + } + set + { + _SettingsChanged = value; + Refresh(); + } + } + + #endregion + + + #region Private fields + + private bool _IsSelected = false; + private bool _SettingsChanged = false; + private bool refreshing = false; + private bool newTile = true; + private ZXMapsTile _TileData = null; + + private int tileWidth = 16; + private int tileHeight = 16; + + private bool _InfoVisible = true; + + #endregion + + + #region Constructor and public methods + + public TilePatternControl() + { + InitializeComponent(); + } + + + /// + /// Initializes the control + /// + /// Data of the Tile, if is null, the "Add" icon is visible and no properties are shown + /// CallBak for actions command, line "ADD", "CLONE", "DELETE" or "SELECTED" + /// + public bool Initialize(ZXMapsTile TileData, Action callBackCommand, int tileWidth, int tileHeight) + { + this.TileData = TileData; + this.CallBackCommand = callBackCommand; + this.tileWidth = tileWidth; + this.tileHeight = tileHeight; + + this.PointerPressed += TilePropertiesControl_PointerPressed; + + btnNew.Tapped += BtnNew_Tapped; + + _SettingsChanged = false; + newTile = true; + + Select(); + + return true; + } + + + public void Refresh() + { + if (refreshing) + { + return; + } + refreshing = true; + + try + { + if (TileData != null) + { + tileWidth = TileData.Width; + tileHeight = TileData.Height; + } + + var w = tileWidth * 4; + var h = tileHeight * 4; + var w4 = w + 4; + var h4 = h + 4; + + if (this.Width != w4 || this.Height != h4) + { + this.Width = w + 4; + this.Height = h + 4; + cnvPoints.Width = w; + cnvPoints.Height = h; + } + + if (TileData == null) + { + btnNew.IsVisible = true; + cnvPoints.IsVisible = false; + lblNumber.IsVisible = false; + return; + } + + if (_IsSelected) + { + brdMain.BorderBrush = new SolidColorBrush(Colors.Red); + } + else + { + brdMain.BorderBrush = new SolidColorBrush(Colors.Gray); + } + + btnNew.IsVisible = false; + + cnvPoints.IsVisible = true; + + if (TileData.Patterns == null || TileData.Patterns.Count == 0) + { + TileData.Patterns = new List(); + TileData.Patterns.Add(new Pattern() + { + RawData = new int[tileHeight*tileHeight], + Id = 0, + Name = "", + Number = "" + }); + } + if (TileData.Palette == null || TileData.Palette.Length == 0) + { + TileData.Palette = ServiceLayer.GetPalette(TileData.GraphicMode); + } + + // Delete background + { + var r = new Rectangle(); + r.Width = cnvPoints.Width; + r.Height = cnvPoints.Height; + r.Fill = new SolidColorBrush(new Color(255, 0x28, 0x28, 0x28)); + cnvPoints.Children.Add(r); + Canvas.SetTop(r, 0); + Canvas.SetLeft(r, 0); + } + + cnvPoints.Width = TileData.Width * 4; + cnvPoints.Height = TileData.Height * 4; + + cnvPoints.Children.Clear(); + int index = 0; + var frame = TileData.Patterns[0]; + for (int y = 0; y < TileData.Height; y++) + { + for (int x = 0; x < TileData.Width; x++) + { + var colorIndex = frame.RawData[index]; + var r = new Rectangle(); + r.Width = 4; + r.Height = 4; + //var palette = TileData.Palette[p]; + + //r.Fill = new SolidColorBrush(new Color(255, palette.Red, palette.Green, palette.Blue)); + + switch (TileData.GraphicMode) + { + case GraphicsModes.ZXSpectrum: + { + var attr = GetAttribute(frame, x, y); + PaletteColor palette = null; + if (colorIndex == 0) + { + palette = TileData.Palette[attr.Paper]; + } + else + { + palette = TileData.Palette[attr.Ink]; + } + r.Fill = new SolidColorBrush(new Color(255, palette.Red, palette.Green, palette.Blue)); + } + break; + case GraphicsModes.Monochrome: + case GraphicsModes.Next: + { + var palette = TileData.Palette[colorIndex]; + r.Fill = new SolidColorBrush(new Color(255, palette.Red, palette.Green, palette.Blue)); + } + break; + + } + + cnvPoints.Children.Add(r); + Canvas.SetTop(r, y * 4); + Canvas.SetLeft(r, x * 4); + index++; + } + } + + lblNumber.IsVisible = _InfoVisible; + if (_InfoVisible) + { + lblNumber.Text = TileData.Id.ToStringNoNull(); + } + } + catch (Exception ex) + { + } + finally + { + refreshing = false; + } + } + + + private AttributeColor GetAttribute(Pattern pattern, int x, int y) + { + int cW = TileData.Width / 8; + int cX = x / 8; + int cY = y / 8; + return pattern.Attributes[(cY * cW) + cX]; + } + + + public void Select() + { + _IsSelected = true; + Refresh(); + CallBackCommand(this, "SELECTED"); + } + + + public void ApplySettings(bool askForApply) + { + /* + if (_TileData == null) + { + return; + } + else + { + var sp = _TileData.Clonar(); + + if (sp.Width != _TileData.Width || sp.Height != _TileData.Height) + { + if (!ServiceLayer.TileData_Resize(ref sp, _TileData.Width, _TileData.Height)) + { + // TODO: Report error + return; + } + } + if (sp.GraphicMode != _TileData.GraphicMode) + { + if (!ServiceLayer.TileData_ChangeMode(ref sp, _TileData.GraphicMode)) + { + // TODO: Report error + return; + } + } + if (sp.Masked != _TileData.Masked) + { + if (!ServiceLayer.TileData_ChangeMasked(ref sp, _TileData.Masked)) + { + // TODO: Report error + return; + } + } + if (sp.Frames != _TileData.Frames) + { + if (!ServiceLayer.TileData_ChangeFrames(ref sp, _TileData.Frames)) + { + // TODO: Report error + return; + } + } + + _TileData = sp; + //CallBackCommand?.Invoke(this, "UPDATE"); + //Refresh(); + newTile = false; + SettingsChanged = false; + } + */ + } + + #endregion + + + #region Private methods + + private void AddNew() + { + var sp = new ZXMapsTile() + { + CurrentFrame = 0, + DefaultColor = 0, + Frames = 1, + GraphicMode = GraphicsModes.Monochrome, + Height = tileWidth, + Id = -1, + Masked = false, + Name = "", + Patterns = new List(), + Width = tileHeight + }; + sp.Palette = ServiceLayer.GetPalette(sp.GraphicMode); + sp.Patterns.Add(CreatePattern()); + TileData = sp; + _IsSelected = true; + Refresh(); + + CallBackCommand?.Invoke(this, "ADD"); + } + + + private Pattern CreatePattern() + { + var pat = new Pattern() + { + Id = 0, + Name = "", + Number = "0", + RawData = new int[tileWidth*tileHeight] + }; + return pat; + } + + + #endregion + + + #region Button events + + private void BtnNew_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + AddNew(); + } + + + private void TilePropertiesControl_PointerPressed(object? sender, Avalonia.Input.PointerPressedEventArgs e) + { + Select(); + } + + #endregion + + } +} diff --git a/ZXBStudio/DocumentEditors/ZXMaps/TilePatternEditor.axaml b/ZXBStudio/DocumentEditors/ZXMaps/TilePatternEditor.axaml new file mode 100644 index 0000000..529a0a4 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/TilePatternEditor.axaml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Zoom 24x + + + Frame + + + + + + Paper + + + 0 + + + + + + Ink + + + 1 + + + + + + + + + diff --git a/ZXBStudio/DocumentEditors/ZXMaps/TilePatternEditor.axaml.cs b/ZXBStudio/DocumentEditors/ZXMaps/TilePatternEditor.axaml.cs new file mode 100644 index 0000000..63b3e32 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/TilePatternEditor.axaml.cs @@ -0,0 +1,1666 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Input; +using Avalonia.Media; +using Avalonia.Platform; +using Avalonia.Themes.Fluent; +using Avalonia.Threading; +using System; +using System.Collections.Generic; +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; +using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; +using ZXBasicStudio.Extensions; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps +{ + public partial class TilePatternEditor : UserControl + { + #region Private properties + + /// + /// last zoom value + /// + private int lastZoom = 0; + + /// + /// Zooms values + /// + private int[] zooms = new int[] + { + 1,2,4,8,16,24,32,48,64 + }; + + private byte actualFrame = 0; + + #endregion + + + #region Public properties + + /// + /// Actual Tile data + /// + public ZXMapsTile TileData + { + get + { + return _TileData; + } + set + { + _TileData = value; + if (_TileData == null) + { + lastId = null; + return; + } + if (lastId != _TileData.Id) + { + _TileData.CurrentFrame = 0; + lastId = _TileData.Id; + } + txtFrame.MaxHeight = _TileData.Frames - 1; + Undo_AddPoint(); + Refresh(true); + } + } + + + /// + /// Zoom + /// + public int Zoom + { + get + { + return _Zoom; + } + set + { + _Zoom = value; + Refresh(true); + } + } + + + /// + /// Color index of the primary color + /// + public int PrimaryColorIndex { get; set; } + + /// + /// Color index of the secondary color + /// + public int SecondaryColorIndex { get; set; } + + 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; + + public bool ColorPicker { get; set; } = false; + + public int Frame + { + get + { + return txtFrame.Text.ToInteger(); + } + set + { + txtFrame.Text = value.ToString(); + } + } + + #endregion + + + #region Private fields + + private ZXMapsTile _TileData = null; + private int _Zoom = 24; + private Action CallBackCommand = null; + private int? lastId = null; + private TileImage aspect = new TileImage(); + + /// + /// True when mouse left button is pressed + /// + private bool MouseLeftPressed = false; + /// + /// True when moude right button is pressed + /// + private bool MouseRightPressed = false; + + /// + /// Dispatcher for refresh operations + /// + private DispatcherTimer tmr = null; + + #endregion + + + #region Public Methods + + public TilePatternEditor() + { + InitializeComponent(); + + grdEditor.BackgroundImage = aspect; + + PrimaryColorIndex = 1; + SecondaryColorIndex = 0; + + grdEditor.PointerMoved += GrdEditor_PointerMoved; + grdEditor.PointerPressed += GrdEditor_PointerPressed; + grdEditor.PointerReleased += GrdEditor_PointerReleased; + grdEditor.PointerExited += GrdEditor_PointerExited; + + aspect.ViewAttributes = true; + + sldZoom.PropertyChanged += SldZoom_PropertyChanged; + txtFrame.PropertyChanged += TxtFrame_PropertyChanged; + + btnClear.Tapped += BtnClear_Tapped; + btnCut.Tapped += BtnCut_Tapped; + btnCopy.Tapped += BtnCopy_Tapped; + btnPaste.Tapped += BtnPaste_Tapped; + btnHMirror.Tapped += BtnHMirror_Tapped; + btnVMirror.Tapped += BtnVMirror_Tapped; + btnRotateLeft.Tapped += BtnRotateLeft_Tapped; + btnRotateRight.Tapped += BtnRotateRight_Tapped; + btnShiftUp.Tapped += BtnShiftUp_Tapped; + btnShiftRight.Tapped += BtnShiftRight_Tapped; + btnShiftDown.Tapped += BtnShiftDown_Tapped; + btnShiftLeft.Tapped += BtnShiftLeft_Tapped; + btnMoveUp.Tapped += BtnMoveUp_Tapped; + btnMoveRight.Tapped += BtnMoveRight_Tapped; + btnMoveDown.Tapped += BtnMoveDown_Tapped; + btnMoveLeft.Tapped += BtnMoveLeft_Tapped; + btnInvert.Tapped += BtnInvert_Tapped; + btnMask.Tapped += BtnMask_Tapped; + btnExport.Tapped += BtnExport_Tapped; + btnImport.Tapped += BtnImport_Tapped; + + btnUndo.Tapped += BtnUndo_Tapped; + btnRedo.Tapped += BtnRedo_Tapped; + + //btnViewAttributes.Tapped += BtnViewAttributes_Tapped; + //btnColorPicker.Tapped += BtnColorPicker_Tapped; + //btnInvertColorsCell.Tapped += BtnInvertColorsCell_Tapped; + //btnInvertPixelsCell.Tapped += BtnInvertPixelsCell_Tapped; + + btnPaper.Tapped += BtnPaper_Click; + btnInk.Tapped += BtnInk_Tapped; + UpdateColorPanel(); + + } + + + /// + /// Initializes the control + /// + /// CallBack for commands: "REFRESH" + /// True if OK or False if error + public bool Initialize(Action callBackCommand) + { + this.CallBackCommand = callBackCommand; + return true; + } + + + /// + /// Refresh the draw area + /// + public void Refresh(bool callBack = false) + { + //cnvEditor.Children.Clear(); + if (TileData == null) + { + aspect.Clear(Colors.White); + grdEditor.InvalidateVisual(); + return; + } + + + aspect.RenderTile(TileData, TileData.CurrentFrame); + grdEditor.Zoom = _Zoom; + this.InvalidateVisual(); + + if (callBack) + { + CallBackCommand?.Invoke(this, "REFRESH"); + } + } + + #endregion + + + #region Undo and Redo + + private List operations = new List(); + private int operationIndex = -1; + + public void Undo() + { + if (operationIndex > operations.Count - 1) + { + operationIndex = operations.Count - 1; + } + if (operationIndex < 0) + { + return; + } + var op = operations[operationIndex]; + if (Pattern_Equals(TileData.Patterns[TileData.CurrentFrame], op)) + { + operationIndex--; + Undo(); + return; + } + if (op != null) + { + TileData.Patterns[TileData.CurrentFrame] = op; + Refresh(); + } + CallBackCommand(this, "REFRESH"); + } + + + public void Redo() + { + if (operationIndex > operations.Count - 1) + { + return; + } + if (operationIndex < 0) + { + return; + } + var op = operations[operationIndex]; + if (Pattern_Equals(TileData.Patterns[TileData.CurrentFrame], op)) + { + operationIndex++; + Redo(); + return; + } + if (op != null) + { + TileData.Patterns[TileData.CurrentFrame] = op; + Refresh(); + } + } + + + private void Undo_AddPoint() + { + try + { + if (TileData == null || TileData.Patterns == null) + { + operations.Clear(); + return; + } + + if (operationIndex >= 0 && + operationIndex < operations.Count) + { + var lastOp = operations[operationIndex]; + if (Pattern_Equals(TileData.Patterns[TileData.CurrentFrame], lastOp)) + { + return; // No changes + } + } + operations = operations.Take(operationIndex + 1).ToList(); + + var op = TileData.Patterns[TileData.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 + + + #region Drawing events + + /// + /// Event for mouse pressed + /// + /// + /// + private void GrdEditor_PointerPressed(object? sender, Avalonia.Input.PointerPressedEventArgs e) + { + var p = e.GetCurrentPoint(grdEditor); + + 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(TileData.Patterns[TileData.CurrentFrame], x, y); + PrimaryColorIndex = atr.Ink; + SecondaryColorIndex = atr.Paper; + ColorPicker = false; + Refresh(true); + } + else if (InvertPixelsCell) + { + GrdEditor_InvertPixelsCell(p.Position.X, p.Position.Y); + } + else if (InvertColorsCell) + { + 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; + } + } + } + + + /// + /// Event for mouse released + /// + /// + /// + private void GrdEditor_PointerReleased(object? sender, Avalonia.Input.PointerReleasedEventArgs e) + { + MouseLeftPressed = false; + MouseRightPressed = false; + } + + + /// + /// Event for pointer moved outside control + /// + /// + /// + private void GrdEditor_PointerExited(object? sender, Avalonia.Input.PointerEventArgs e) + { + MouseLeftPressed = false; + MouseRightPressed = false; + } + + + /// + /// Event for pointer moved + /// + /// + /// + private void GrdEditor_PointerMoved(object? sender, Avalonia.Input.PointerEventArgs e) + { + var p = e.GetCurrentPoint(grdEditor); + if (MouseLeftPressed) + { + SetPoint(p.Position.X, p.Position.Y, PrimaryColorIndex); + } + else if (MouseRightPressed) + { + SetPoint(p.Position.X, p.Position.Y, SecondaryColorIndex); + } + } + + + /// + /// Set point to a color value + /// + /// Absolute x + /// Absolute y + /// Value of the point + private void SetPoint(double mx, double my, int value) + { + if (TileData == null) + { + return; + } + + int x = (int)mx; + int y = (int)my; + + x = x / (_Zoom + 1); + y = y / (_Zoom + 1); + + if (x < 0 || y < 0 || x >= TileData.Width || y >= TileData.Height) + { + return; + } + + int dir = (TileData.Width * y) + x; + var Tile = TileData.Patterns[TileData.CurrentFrame]; + + switch (TileData.GraphicMode) + { + case GraphicsModes.Monochrome: + Tile.RawData[dir] = value; + break; + case GraphicsModes.ZXSpectrum: + { + if (value == PrimaryColorIndex) + { + Tile.RawData[dir] = 1; + } + else + { + Tile.RawData[dir] = 0; + } + SetAttribute(Tile, x, y); + } + break; + } + + Undo_AddPoint(); + Refresh(false); + + if (tmr == null) + { + tmr = new DispatcherTimer(); + tmr.Tick += Tmr_Tick; + tmr.Interval = TimeSpan.FromMilliseconds(250); + } + tmr.Stop(); + tmr.Start(); + } + + + private void SetAttribute(Pattern pattern, int x, int y) + { + int cW = TileData.Width / 8; + int cX = x / 8; + int cY = y / 8; + var attr = pattern.Attributes[(cY * cW) + cX]; + attr.Ink = PrimaryColorIndex; + attr.Paper = SecondaryColorIndex; + attr.Flash = false; + switch (TileData.GraphicMode) + { + case GraphicsModes.Monochrome: + attr.Bright = false; + break; + case GraphicsModes.ZXSpectrum: + if (PrimaryColorIndex > 7 || SecondaryColorIndex > 7) + { + attr.Bright = true; + } + else + { + attr.Bright = false; + } + break; + } + pattern.Attributes[(cY * cW) + cX] = attr; + } + + + private AttributeColor GetAttribute(Pattern pattern, int x, int y) + { + if (pattern.Attributes == null) + { + pattern.Attributes = new AttributeColor[(TileData.Width / 8) * (TileData.Height / 8)]; + } + int cW = TileData.Width / 8; + int cX = x / 8; + int cY = y / 8; + return pattern.Attributes[(cY * cW) + cX]; + } + + + private void Tmr_Tick(object? sender, EventArgs e) + { + Refresh(true); + tmr.Stop(); + } + + #endregion + + + #region Icon actions + + + /// + /// Clear Tile + /// + public void Clear() + { + if (TileData == null || TileData.Patterns == null || + TileData.CurrentFrame >= (TileData.Patterns.Count) || + TileData.Patterns[TileData.CurrentFrame].RawData == null) + { + return; + } + for (int n = 0; n < TileData.Patterns[TileData.CurrentFrame].RawData.Length; n++) + { + TileData.Patterns[TileData.CurrentFrame].RawData[n] = SecondaryColorIndex; + } + Undo_AddPoint(); + Refresh(true); + } + + + /// + /// Cut patterns to clipboard + /// + public void Cut() + { + Undo_AddPoint(); + Copy(); + Clear(); + } + + + /// + /// Copy patterns to clipboard + /// + public void Copy() + { + var patterns = new Pattern[1] { TileData.Patterns[TileData.CurrentFrame] }; + TopLevel.GetTopLevel(this)?.Clipboard?.SetTextAsync(patterns.Serializar()).Wait(); + } + + + /// + /// Paste patterns from clipboard + /// + public async void Paste() + { + var pattern = TileData.Patterns[TileData.CurrentFrame]; + var cbData = await TopLevel.GetTopLevel(this).Clipboard.GetTextAsync(); + if (string.IsNullOrEmpty(cbData)) + { + return; + } + var cbPatterns = cbData.Deserializar(); + if (cbPatterns == null) + { + return; + } + + if (cbPatterns.Length == 1) + { + if (cbPatterns[0].RawData == null) + { + TileData.Patterns[TileData.CurrentFrame].RawData = ServiceLayer.PointData2RawData(cbPatterns[0].Data, 8, 8); + } + else + { + TileData.Patterns[TileData.CurrentFrame].RawData = cbPatterns[0].RawData; + TileData.Patterns[TileData.CurrentFrame].Attributes = cbPatterns[0].Attributes; + } + } + else + { + // Create an empty pattern + var pat2 = pattern.Clonar(); + pat2.RawData = new int[TileData.Width * TileData.Height]; + + if (cbPatterns[0].RawData == null) + { + // Paste from PointData + int ox = 0; + int oy = 0; + int dir = 0; + for (int n = 0; n < cbPatterns.Length; n++) + { + //int d1 = (n / TileData.Width); + //int d2 = (d1 * TileData.Width) - n; + for (int py = 0; py < 8; py++) + { + for (int px = 0; px < 8; px++) + { + var po = cbPatterns[n].Data.FirstOrDefault(d => d.X == px && d.Y == py); + if (po != null) + { + dir = ((oy + py) * TileData.Width) + (ox + px); + + if (dir < pattern.RawData.Length) + { + TileData.Patterns[TileData.CurrentFrame].RawData[dir] = po.ColorIndex; + //pattern.RawData[dir] = po.ColorIndex; + } + } + } + } + ox += 8; + if (ox >= TileData.Width) + { + ox = 0; + oy += 8; + } + } + //TileData.Patterns[TileData.CurrentFrame].Data = pat2.Data; + } + else + { + // Paste from RawData + var dat = TileData.Patterns[TileData.CurrentFrame].RawData; + var cbDat = cbPatterns[0].RawData; + for (int n = 0; n < cbDat.Length && n < dat.Length; n++) + { + dat[n] = cbDat[n]; + } + } + } + Undo_AddPoint(); + Refresh(true); + } + + + /// + /// Horizontal mirror of selected patterns + /// + public void HorizontalMirror() + { + int maxWidth = TileData.Width; + int maxHeight = TileData.Height; + var pat1 = TileData.Patterns[TileData.CurrentFrame]; + var pat2 = pat1.Clonar(); + for (int y = 0; y < maxHeight; y++) + { + for (int x = 0; x < maxWidth; x++) + { + var pd1 = GetPointValue(x, y, pat1); + SetPointValue(maxWidth - x - 1, y, pd1, ref pat2); + } + } + TileData.Patterns[TileData.CurrentFrame] = pat2; + Undo_AddPoint(); + Refresh(true); + } + + + /// + /// Vertical mirror of selected patterns + /// + public void VerticalMirror() + { + int maxWidth = TileData.Width; + int maxHeight = TileData.Height; + var pat1 = TileData.Patterns[TileData.CurrentFrame]; + var pat2 = pat1.Clonar(); + for (int y = 0; y < maxHeight; y++) + { + for (int x = 0; x < maxWidth; x++) + { + var pd1 = GetPointValue(x, y, pat1); + SetPointValue(x, maxHeight - y - 1, pd1, ref pat2); + } + } + TileData.Patterns[TileData.CurrentFrame] = pat2; + Undo_AddPoint(); + Refresh(true); + } + + + /// + /// Rotate left of selected patterns + /// + public void RotateLeft() + { + if (TileData.Width != TileData.Height) + { + Window.GetTopLevel(this)?.ShowError("Can't do this!", "Only square graphics can be rotated (with the same width as height)."); + return; + } + + var pattern = TileData.Patterns[TileData.CurrentFrame]; + var pattern2 = pattern.Clonar(); + + int maxWidth = TileData.Width; + int maxHeight = TileData.Height; + for (int y = 0; y < maxHeight; y++) + { + for (int x = 0; x < maxWidth; x++) + { + var pd1 = GetPointValue(x, y, pattern); + SetPointValue(y, maxWidth - x - 1, pd1, ref pattern2); + } + } + TileData.Patterns[TileData.CurrentFrame] = pattern2; + Undo_AddPoint(); + Refresh(true); + } + + + /// + /// Rotate right of selected patterns + /// + public void RotateRight() + { + if (TileData.Width != TileData.Height) + { + Window.GetTopLevel(this)?.ShowError("Can't do this!", "Only square graphics can be rotated (with the same width as height)."); + return; + } + + var pattern = TileData.Patterns[TileData.CurrentFrame]; + var pattern2 = pattern.Clonar(); + + int maxWidth = TileData.Width; + int maxHeight = TileData.Height; + for (int y = 0; y < maxHeight; y++) + { + for (int x = 0; x < maxWidth; x++) + { + var pd1 = GetPointValue(x, y, pattern); + SetPointValue(maxHeight - y - 1, x, pd1, ref pattern2); + } + } + TileData.Patterns[TileData.CurrentFrame] = pattern2; + Undo_AddPoint(); + Refresh(true); + } + + + /// + /// Move up and put the disappearing pixels at the bottom. + /// + public void ShiftUp() + { + var pattern = TileData.Patterns[TileData.CurrentFrame]; + var pattern2 = pattern.Clonar(); + + int maxWidth = TileData.Width; + int maxHeight = TileData.Height; + for (int y = 0; y < maxHeight; y++) + { + for (int x = 0; x < maxWidth; x++) + { + var pd1 = GetPointValue(x, y, pattern); + int y2 = y - 1; + if (y2 < 0) + { + y2 = maxHeight - 1; + } + SetPointValue(x, y2, pd1, ref pattern2); + } + } + TileData.Patterns[TileData.CurrentFrame] = pattern2; + Undo_AddPoint(); + Refresh(true); + } + + + /// + /// Move right and put the disappearing pixels at the left. + /// + public void ShiftRight() + { + var pattern = TileData.Patterns[TileData.CurrentFrame]; + var pattern2 = pattern.Clonar(); + + int maxWidth = TileData.Width; + int maxHeight = TileData.Height; + for (int y = 0; y < maxHeight; y++) + { + for (int x = 0; x < maxWidth; x++) + { + var pd1 = GetPointValue(x, y, pattern); + int x2 = x + 1; + if (x2 >= maxWidth) + { + x2 = 0; + } + SetPointValue(x2, y, pd1, ref pattern2); + } + } + TileData.Patterns[TileData.CurrentFrame] = pattern2; + Undo_AddPoint(); + Refresh(true); + } + + + /// + /// Move down and put the disappearing pixels at the top. + /// + public void ShiftDown() + { + var pattern = TileData.Patterns[TileData.CurrentFrame]; + var pattern2 = pattern.Clonar(); + + int maxWidth = TileData.Width; + int maxHeight = TileData.Height; + for (int y = 0; y < maxHeight; y++) + { + for (int x = 0; x < maxWidth; x++) + { + var pd1 = GetPointValue(x, y, pattern); + int y2 = y + 1; + if (y2 >= maxHeight) + { + y2 = 0; + } + SetPointValue(x, y2, pd1, ref pattern2); + } + } + TileData.Patterns[TileData.CurrentFrame] = pattern2; + Undo_AddPoint(); + Refresh(true); + } + + + /// + /// Move left and put the disappearing pixels at the right. + /// + public void ShiftLeft() + { + var pattern = TileData.Patterns[TileData.CurrentFrame]; + var pattern2 = pattern.Clonar(); + + int maxWidth = TileData.Width; + int maxHeight = TileData.Height; + for (int y = 0; y < maxHeight; y++) + { + for (int x = 0; x < maxWidth; x++) + { + var pd1 = GetPointValue(x, y, pattern); + int x2 = x - 1; + if (x2 < 0) + { + x2 = maxWidth - 1; + } + SetPointValue(x2, y, pd1, ref pattern2); + } + } + TileData.Patterns[TileData.CurrentFrame] = pattern2; + Undo_AddPoint(); + Refresh(true); + } + + + /// + /// Move up + /// + public void MoveUp() + { + var pattern = TileData.Patterns[TileData.CurrentFrame]; + var pattern2 = pattern.Clonar(); + + int maxWidth = TileData.Width; + int maxHeight = TileData.Height; + for (int y = 0; y < maxHeight; y++) + { + for (int x = 0; x < maxWidth; x++) + { + var pd1 = GetPointValue(x, y, pattern); + int y2 = y - 1; + if (y2 < 0) + { + y2 = maxHeight - 1; + pd1 = 0; + } + SetPointValue(x, y2, pd1, ref pattern2); + } + } + TileData.Patterns[TileData.CurrentFrame] = pattern2; + Undo_AddPoint(); + Refresh(true); + } + + + /// + /// Move rigth + /// + public void MoveRight() + { + var pattern = TileData.Patterns[TileData.CurrentFrame]; + var pattern2 = pattern.Clonar(); + + int maxWidth = TileData.Width; + int maxHeight = TileData.Height; + for (int y = 0; y < maxHeight; y++) + { + for (int x = 0; x < maxWidth; x++) + { + var pd1 = GetPointValue(x, y, pattern); + int x2 = x + 1; + if (x2 >= maxWidth) + { + x2 = 0; + pd1 = 0; + } + SetPointValue(x2, y, pd1, ref pattern2); + } + } + TileData.Patterns[TileData.CurrentFrame] = pattern2; + Undo_AddPoint(); + Refresh(true); + } + + + /// + /// Move down + /// + public void MoveDown() + { + var pattern = TileData.Patterns[TileData.CurrentFrame]; + var pattern2 = pattern.Clonar(); + + int maxWidth = TileData.Width; + int maxHeight = TileData.Height; + for (int y = 0; y < maxHeight; y++) + { + for (int x = 0; x < maxWidth; x++) + { + var pd1 = GetPointValue(x, y, pattern); + int y2 = y + 1; + if (y2 >= maxHeight) + { + y2 = 0; + pd1 = 0; + } + SetPointValue(x, y2, pd1, ref pattern2); + } + } + TileData.Patterns[TileData.CurrentFrame] = pattern2; + Undo_AddPoint(); + Refresh(true); + } + + + /// + /// Move left + /// + public void MoveLeft() + { + var pattern = TileData.Patterns[TileData.CurrentFrame]; + var pattern2 = pattern.Clonar(); + + int maxWidth = TileData.Width; + int maxHeight = TileData.Height; + for (int y = 0; y < maxHeight; y++) + { + for (int x = 0; x < maxWidth; x++) + { + var pd1 = GetPointValue(x, y, pattern); + int x2 = x - 1; + if (x2 < 0) + { + x2 = maxWidth - 1; + pd1 = 0; + } + SetPointValue(x2, y, pd1, ref pattern2); + } + } + TileData.Patterns[TileData.CurrentFrame] = pattern2; + Undo_AddPoint(); + Refresh(true); + } + + + /// + /// Invert pixels + /// + public void Invert() + { + var pattern = TileData.Patterns[TileData.CurrentFrame]; + var pattern2 = pattern.Clonar(); + + int maxWidth = TileData.Width; + int maxHeight = TileData.Height; + for (int y = 0; y < maxHeight; y++) + { + for (int x = 0; x < maxWidth; x++) + { + var pd1 = GetPointValue(x, y, pattern); + if (pd1 == PrimaryColorIndex) + { + pd1 = SecondaryColorIndex; + } + else if (pd1 == SecondaryColorIndex) + { + pd1 = PrimaryColorIndex; + } + else + { + + } + SetPointValue(x, y, pd1, ref pattern2); + } + } + TileData.Patterns[TileData.CurrentFrame] = pattern2; + Undo_AddPoint(); + Refresh(true); + } + + + /// + /// Create a mask + /// + public void Mask() + { + var pattern = TileData.Patterns[TileData.CurrentFrame]; + int maxX = (TileData.Width) - 1; + int maxY = (TileData.Height) - 1; + + var pattern2 = new Pattern() + { + Id = pattern.Id, + Name = pattern.Name, + Number = pattern.Number, + RawData = new int[TileData.Width * TileData.Height] + }; + + _Mask(0, 0, ref pattern, ref pattern2); + _Mask(maxX, 0, ref pattern, ref pattern2); + _Mask(0, maxY, ref pattern, ref pattern2); + _Mask(maxX, maxY, ref pattern, ref pattern2); + TileData.Patterns[TileData.CurrentFrame] = pattern2; + Undo_AddPoint(); + Refresh(true); + } + + + /// + /// Create a mask (fills pattern2 from pattern1 data) called recursively + /// + /// Coord x + /// Coord Y + /// Original patterns + /// Void pattens + private void _Mask(int x, int y, ref Pattern pattern1, ref Pattern pattern2) + { + var p1 = GetPointValue(x, y, pattern1); + if (p1 != 0) + { + return; + } + var p2 = GetPointValue(x, y, pattern2); + if (p2 != 0) + { + return; + } + p2 = 1; + SetPointValue(x, y, p2, ref pattern2); + + if (x > 1) + { + _Mask(x - 1, y, ref pattern1, ref pattern2); + } + if (x < (TileData.Width) - 1) + { + _Mask(x + 1, y, ref pattern1, ref pattern2); + } + if (y > 1) + { + _Mask(x, y - 1, ref pattern1, ref pattern2); + } + if (y < (TileData.Height) - 1) + { + _Mask(x, y + 1, ref pattern1, ref pattern2); + } + } + + + /// + /// Get a point (pixel) from the selected pattern + /// + /// X coord + /// Y coord + /// Pattern to use + /// Point data or null if no data + private int GetPointValue(int x, int y, Pattern pattern) + { + if (x < 0 || y < 0 || x > (TileData.Width - 1) || y > (TileData.Height - 1)) + { + return SecondaryColorIndex; + } + return pattern.RawData[(TileData.Width * y) + x]; + } + + + /// + /// Set a point (pixel) from the selected pattern + /// + /// X coord + /// Y coord + /// Color index to set + /// Pattern to use + private void SetPointValue(int x, int y, int colorIndex, ref Pattern pattern) + { + if (x < 0 || y < 0 || x > (TileData.Width - 1) || y > (TileData.Height - 1)) + { + return; + } + pattern.RawData[(TileData.Width * y) + x] = colorIndex; + } + + #endregion + + + #region Invert pixels and colors + + private void GrdEditor_InvertPixelsCell(double mx, double my) + { + if (TileData == 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 >= TileData.Width || y >= TileData.Height) + { + return; + } + + for (int py = 0; py < 8; py++) + { + for (int px = 0; px < 8; px++) + { + int dir = ((y * 8 + py) * TileData.Width) + (x * 8 + px); + if (dir < TileData.Patterns[TileData.CurrentFrame].RawData.Length) + { + var value = TileData.Patterns[TileData.CurrentFrame].RawData[dir]; + if (value == PrimaryColorIndex) + { + TileData.Patterns[TileData.CurrentFrame].RawData[dir] = SecondaryColorIndex; + } + else if (value == SecondaryColorIndex) + { + TileData.Patterns[TileData.CurrentFrame].RawData[dir] = PrimaryColorIndex; + } + } + } + } + Refresh(); + } + + private void GrdEditor_InvertColorsCell(double mx, double my) + { + if (TileData == 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(TileData.Patterns[TileData.CurrentFrame], x, y); + if (attr == null) + { + return; + } + PrimaryColorIndex = attr.Paper; + SecondaryColorIndex = attr.Ink; + SetAttribute(TileData.Patterns[TileData.CurrentFrame], x, y); + PrimaryColorIndex = inkBak; + SecondaryColorIndex = paperBak; + Refresh(); + } + #endregion + + + + #region Color + + private void BtnPaper_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + { + if (ctrlColorPicker.IsVisible) + { + ctrlColorPicker.IsVisible = false; + return; + } + var Tile = TileData; + if (Tile.GraphicMode == GraphicsModes.Monochrome) + { + return; + } + ctrlColorPicker.IsVisible = true; + ctrlColorPicker.Inicialize(Tile.GraphicMode, Tile.Palette, SecondaryColorIndex, ColorPickerPaper_Action); + } + + + private void BtnInk_Tapped(object? sender, TappedEventArgs e) + { + if (ctrlColorPicker.IsVisible) + { + ctrlColorPicker.IsVisible = false; + return; + } + var Tile = TileData; + if (Tile.GraphicMode == GraphicsModes.Monochrome) + { + return; + } + ctrlColorPicker.IsVisible = true; + ctrlColorPicker.Inicialize(Tile.GraphicMode, Tile.Palette, PrimaryColorIndex, ColorPickerInk_Action); + } + + + public void UpdateColorPanel() + { + var Tile = TileData; + if (Tile == null) + { + return; + } + switch (Tile.GraphicMode) + { + case GraphicsModes.Monochrome: + { + var ink = Tile.Palette[1]; + var paper = Tile.Palette[0]; + grdPaper.Background = new SolidColorBrush(Color.FromRgb(paper.Red, paper.Green, paper.Blue)); + txtPaper.Foreground = new SolidColorBrush(Color.FromRgb(ink.Red, ink.Green, ink.Blue)); + txtPaper.Text = "0"; + grdInk.Background = new SolidColorBrush(Color.FromRgb(ink.Red, ink.Green, ink.Blue)); + txtInk.Foreground = new SolidColorBrush(Color.FromRgb(paper.Red, paper.Green, paper.Blue)); + txtInk.Text = "1"; + } + break; + + case GraphicsModes.ZXSpectrum: + case GraphicsModes.Next: + { + var ink = Tile.Palette[PrimaryColorIndex]; + var paper = Tile.Palette[SecondaryColorIndex]; + grdPaper.Background = new SolidColorBrush(Color.FromRgb(paper.Red, paper.Green, paper.Blue)); + txtPaper.Foreground = new SolidColorBrush(Color.FromRgb(ink.Red, ink.Green, ink.Blue)); + txtPaper.Text = SecondaryColorIndex.ToString(); + grdInk.Background = new SolidColorBrush(Color.FromRgb(ink.Red, ink.Green, ink.Blue)); + txtInk.Foreground = new SolidColorBrush(Color.FromRgb(paper.Red, paper.Green, paper.Blue)); + txtInk.Text = PrimaryColorIndex.ToString(); + } + break; + } + } + + + + private void ColorPickerPaper_Action(string command, int indexColor) + { + SecondaryColorIndex = indexColor; + UpdateColorPanel(); + } + + + private void ColorPickerInk_Action(string command, int indexColor) + { + PrimaryColorIndex = indexColor; + UpdateColorPanel(); + } + + #endregion + + + + #region Main editor + + + private void Refresh() + { + // TODO: Multiple frames per tile? + /* + txtFrame.Text = actualFrame.ToString(); + if (ctrlProperties.TileData != null) + { + txtFrame.Maximum = ctrlProperties.TileData.Frames - 1; + } + else + { + txtFrame.Maximum = 0; + } + txtFrame.UpdateLayout(); + */ + } + + + /// + /// Zoom changed + /// + /// + /// + private void SldZoom_PropertyChanged(object? sender, Avalonia.AvaloniaPropertyChangedEventArgs e) + { + int z = (int)sldZoom.Value; + if (z == 0 || z == lastZoom) + { + return; + } + lastZoom = z; + + z = zooms[z - 1]; + txtZoom.Text = "Zoom " + z.ToString() + "x"; + Zoom = z; + } + + + public void ZoomIn() + { + int v = sldZoom.Value.ToInteger(); + if (v < zooms.Length - 1) + { + sldZoom.Value = v - 1; + } + } + + + public void ZoomOut() + { + int v = sldZoom.Value.ToInteger(); + if (v > 0) + { + sldZoom.Value = v - 1; + } + + } + + + private void TxtFrame_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + { + // TODO: Multiple frames per tile? + /* + byte v = txtFrame.Text.ToByte(); + if (actualFrame == v || + ctrlProperties.TileData == null || + v < 0 || + v >= (ctrlProperties.TileData.Frames)) + { + return; + } + if (v > 255) + { + v = 255; + } + actualFrame = v; + if (TileData != null) + { + TileData.CurrentFrame = actualFrame; + Refresh(); + } + Refresh(); + */ + } + + #endregion + + + #region ToolBar + + /// + /// Clear click + /// + /// + /// + private void BtnClear_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.Clear(); + } + + /// + /// Cut click + /// + /// + /// + private void BtnCut_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.Cut(); + } + + + //Copy click + private void BtnCopy_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.Copy(); + } + + + /// + /// Paste click + /// + /// + /// + private void BtnPaste_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.Paste(); + } + + + /// + /// Horizontal mirror click + /// + /// + /// + private void BtnHMirror_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.HorizontalMirror(); + } + + + /// + /// Vertical mirror click + /// + /// + /// + private void BtnVMirror_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.VerticalMirror(); + } + + + /// + /// Rotate left + /// + /// + /// + private void BtnRotateLeft_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.RotateLeft(); + } + + + /// + /// Rotate right + /// + /// + /// + private void BtnRotateRight_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.RotateRight(); + } + + + /// + /// Shift up + /// + /// + /// + private void BtnShiftUp_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.ShiftUp(); + } + + + /// + /// Shift right + /// + /// + /// + private void BtnShiftRight_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.ShiftRight(); + } + + + /// + /// Shift down + /// + /// + /// + private void BtnShiftDown_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.ShiftDown(); + } + + + /// + /// Shift left + /// + /// + /// + private void BtnShiftLeft_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.ShiftLeft(); + } + + + /// + /// Move up + /// + /// + /// + private void BtnMoveUp_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.MoveUp(); + } + + + /// + /// Move right + /// + /// + /// + private void BtnMoveRight_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.MoveRight(); + } + + + /// + /// Move down + /// + /// + /// + private void BtnMoveDown_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.MoveDown(); + } + + + /// + /// Move left + /// + /// + /// + private void BtnMoveLeft_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.MoveLeft(); + } + + + /// + /// Invert + /// + /// + /// + private void BtnInvert_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.Invert(); + } + + + /// + /// Mask + /// + /// + /// + private void BtnMask_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + this.Mask(); + } + + + private void BtnExport_Tapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + Export(); + } + + + public void Export() + { + CallBackCommand(this, "EXPORT"); + } + + + private void BtnImport_Tapped(object? sender, TappedEventArgs e) + { + CallBackCommand(this, "IMPORT"); + } + + + private void BtnRedo_Tapped(object? sender, TappedEventArgs e) + { + Redo(); + } + + + private void BtnUndo_Tapped(object? sender, TappedEventArgs e) + { + Undo(); + } + + #endregion + + } +} diff --git a/ZXBStudio/DocumentEditors/ZXMaps/TilePreviewControl.axaml b/ZXBStudio/DocumentEditors/ZXMaps/TilePreviewControl.axaml new file mode 100644 index 0000000..6ca3356 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/TilePreviewControl.axaml @@ -0,0 +1,17 @@ + + + .zxmap file + + None + + + + + + diff --git a/ZXBStudio/DocumentEditors/ZXMaps/TilePreviewControl.axaml.cs b/ZXBStudio/DocumentEditors/ZXMaps/TilePreviewControl.axaml.cs new file mode 100644 index 0000000..284ab8e --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/TilePreviewControl.axaml.cs @@ -0,0 +1,143 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Threading; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection.Metadata.Ecma335; +using System.Runtime.InteropServices; +using ZXBasicStudio.Common; +using ZXBasicStudio.DocumentEditors.ZXGraphics.log; +using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps +{ + public partial class TilePreviewControl : UserControl, IDisposable + { + #region Public properties + + /// + /// Tile data + /// + public ZXMapsTile? TileData { get; set; } + public int Zoom { get; set; } = 4; + + #endregion + + #region Private fields + + private int frameNumber = 0; + private int speed = 1; + private DispatcherTimer tmr; + private Color emptyColor = new Color(255, 0x28, 0x28, 0x28); + TileImage aspect = new TileImage(); + + /// + /// Speeds in milliseconds + /// + private int[] speeds = new int[] { 600000, 1000, 500, 250, 200, 125, 100, 66, 50, 20 }; + + #endregion + + public TilePreviewControl() + { + tmr = new DispatcherTimer(TimeSpan.FromMilliseconds(speeds[2]), DispatcherPriority.Normal, Refresh); + tmr.Stop(); + aspect.Clear(emptyColor); + InitializeComponent(); + imgPreview.Source = aspect; + imgPreview.Width = aspect.Size.Width; + imgPreview.Height = aspect.Size.Height; + } + + /// + /// Initializes the control + /// + /// Data of the Tile, if is null, the "Add" icon is visible and no properties are shown + /// CallBak for actions command, line "ADD", "CLONE", "DELETE" or "SELECTED" + /// + public bool Initialize(ZXMapsTile TileData) + { + this.TileData = TileData; + + //this.cmbSpeed.SelectionChanged += CmbSpeed_SelectionChanged; + tmr.Interval = TimeSpan.FromMilliseconds(speeds[speed]); + + return true; + } + + public void Start() + { + tmr.Start(); + } + + public void Stop() + { + tmr.Stop(); + } + + //private void CmbSpeed_SelectionChanged(object? sender, SelectionChangedEventArgs e) + //{ + //var s = cmbSpeed.SelectedIndex.ToInteger(); + //if (s < 0 || s >= speeds.Length) + //{ + // s = 2; + //} + //speed = s; + //tmr.Interval = TimeSpan.FromMilliseconds(speeds[speed]); + //} + + public void Refresh(object? sender = null, EventArgs? e = null) + { + if (TileData == null || TileData.Patterns == null || TileData.Patterns.Count == 0) + { + // Delete background + + if (aspect.IsEmpty) + return; + + aspect.Clear(emptyColor); + imgPreview.Width = aspect.Size.Width; + imgPreview.Height = aspect.Size.Height; + imgPreview.InvalidateVisual(); + return; + } + + if (tmr == null) + { + tmr = new DispatcherTimer(TimeSpan.FromMilliseconds(speeds[speed]), DispatcherPriority.Normal, Refresh); + } + + if (TileData.Masked) + { + frameNumber += 2; + } + else + { + frameNumber++; + } + + if (frameNumber >= TileData.Frames) + { + frameNumber = 0; + } + + imgPreview.Width = TileData.Width * Zoom; + imgPreview.Height = TileData.Height * Zoom; + + aspect.RenderTile(TileData, frameNumber); + imgPreview.InvalidateVisual(); + } + + public void Dispose() + { + Stop(); + aspect.Dispose(); + imgPreview.Source = null; + } + } +} diff --git a/ZXBStudio/DocumentEditors/ZXMaps/TilePropertiesControl.axaml b/ZXBStudio/DocumentEditors/ZXMaps/TilePropertiesControl.axaml new file mode 100644 index 0000000..8d2e570 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/TilePropertiesControl.axaml @@ -0,0 +1,31 @@ + + + + Name: + + + Width: + + + + + Height: + + + + + Mode: + + Monochrome + ZX Spectrum + + + + diff --git a/ZXBStudio/DocumentEditors/ZXMaps/TilePropertiesControl.axaml.cs b/ZXBStudio/DocumentEditors/ZXMaps/TilePropertiesControl.axaml.cs new file mode 100644 index 0000000..7fca1e9 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/TilePropertiesControl.axaml.cs @@ -0,0 +1,185 @@ +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using SkiaSharp; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using ZXBasicStudio.Common; +using ZXBasicStudio.DocumentEditors.ZXMaps.Neg; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps +{ + public partial class TilePropertiesControl : UserControl + { + #region Public properties + + /// + /// Tile data + /// + public ZXMapsTiles TileData + { + get + { + return _TileData; + } + set + { + _TileData = value; + IsSelected = true; + } + } + + /// + /// CallBack for commands: "SIZE", "MODE", "NAME" + /// + public Action CallBackCommand { get; set; } + + /// + /// True when then control is selected + /// + public bool IsSelected + { + get + { + return _IsSelected; + } + set + { + _IsSelected = value; + Refresh(); + } + } + + + public int PrimaryColor = 1; + public int SecondaryColor = 0; + + #endregion + + + #region Private fields + + private bool _IsSelected = false; + private bool _SettingsChanged = false; + private bool refreshing = false; + private bool newTile = true; + private ZXMapsTiles _TileData = null; + + #endregion + + + #region Constructor and public methods + + public TilePropertiesControl() + { + InitializeComponent(); + Refresh(); + } + + + /// + /// Initializes the control + /// + /// Data of the Tile, if is null, the "Add" icon is visible and no properties are shown + /// CallBak for actions command, line "ADD", "CLONE", "DELETE" or "SELECTED" + /// + public bool Initialize(ZXMapsTiles TileData, Action callBackCommand) + { + this.TileData = TileData; + this.CallBackCommand = callBackCommand; + + txtName.TextChanged += TxtName_TextChanged; + cmbMode.SelectionChanged += CmbMode_SelectionChanged; + txtWidth.ValueChanged += TxtWidth_ValueChanged; + txtHeight.ValueChanged += TxtHeight_ValueChanged; + + _SettingsChanged = false; + + return true; + } + + + public void Refresh() + { + if (refreshing) + { + return; + } + refreshing = true; + + try + { + if (TileData == null) + { + pnlProperties.IsVisible = false; + return; + } + else + { + pnlProperties.IsVisible = true; + } + + txtName.Text = TileData.Name; + txtWidth.Text = TileData.Width.ToStringNoNull(); + txtHeight.Text = TileData.Height.ToStringNoNull(); + cmbMode.SelectedIndex = (int)TileData.GraphicMode; + + txtHeight.Value = TileData.Height; + txtName.Text = TileData.Name; + txtWidth.Value = TileData.Width; + cmbMode.SelectedIndex = (byte)TileData.GraphicMode; + + } + catch { } + finally + { + refreshing = false; + } + } + + #endregion + + + #region Private methods + + + + #endregion + + + #region Button events + + private void CmbMode_SelectionChanged(object? sender, SelectionChangedEventArgs e) + { + TileData.GraphicMode = (ZXGraphics.neg.GraphicsModes)cmbMode.SelectedIndex; + CallBackCommand(this, "MODE"); + } + + + private void TxtWidth_ValueChanged(object? sender, NumericUpDownValueChangedEventArgs e) + { + TileData.Width = txtWidth.Value.ToInteger(); + CallBackCommand(this, "SIZE"); + } + + + private void TxtHeight_ValueChanged(object? sender, NumericUpDownValueChangedEventArgs e) + { + TileData.Height = txtHeight.Value.ToInteger(); + CallBackCommand(this, "SIZE"); + } + + + private void TxtName_TextChanged(object? sender, TextChangedEventArgs e) + { + TileData.Name = txtName.Text.ToStringNoNull(); + CallBackCommand(this, "NAME"); + } + + #endregion + + } +} diff --git a/ZXBStudio/DocumentEditors/ZXMaps/ZXMapsEditor.axaml b/ZXBStudio/DocumentEditors/ZXMaps/ZXMapsEditor.axaml new file mode 100644 index 0000000..7944d40 --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/ZXMapsEditor.axaml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + Open .zxtil and use tile editor to edit map + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Zoom 24x + + Width + + + + Height + + + + + + + + + + + + + + Preview + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ZXBStudio/DocumentEditors/ZXMaps/ZXMapsEditor.axaml.cs b/ZXBStudio/DocumentEditors/ZXMaps/ZXMapsEditor.axaml.cs new file mode 100644 index 0000000..02019ce --- /dev/null +++ b/ZXBStudio/DocumentEditors/ZXMaps/ZXMapsEditor.axaml.cs @@ -0,0 +1,330 @@ +using Avalonia; +using Avalonia.Controls; +//using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Avalonia.Threading; +using AvaloniaEdit.Folding; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ZXBasicStudio.Common; +using ZXBasicStudio.Dialogs; +using ZXBasicStudio.DocumentEditors.NextDows.neg; +using ZXBasicStudio.DocumentEditors.ZXGraphics; +using ZXBasicStudio.DocumentEditors.ZXGraphics.neg; +using ZXBasicStudio.DocumentEditors.ZXMaps.Log; +using ZXBasicStudio.DocumentEditors.ZXMaps.Neg; +using ZXBasicStudio.DocumentEditors.ZXTextEditor.Classes.Folding; +using ZXBasicStudio.DocumentModel.Classes; + +namespace ZXBasicStudio.DocumentEditors.ZXMaps +{ + public partial class ZXMapsEditor : ZXDocumentEditorBase//, IObserver + { + #region Events + + public override event EventHandler? DocumentRestored; + public override event EventHandler? DocumentModified; + public override event EventHandler? DocumentSaved; + public override event EventHandler? RequestSaveDocument; + + #endregion + + + #region ZXDocumentBase properties + + public override string DocumentName + { + get + { + return Path.GetFileName(FileName); + } + } + + + public override string DocumentPath + { + get + { + return Path.GetFullPath(FileName); + } + } + + public override bool Modified + { + get + { + return _Modified; + } + } + + private bool _Modified = false; + + protected virtual AbstractFoldingStrategy? foldingStrategy { get { return null; } } + + private Guid documentTypeId = Guid.Empty; + + #endregion + + + #region IObserver implementation for modified document notifications + + public void OnCompleted() + { + + } + + public void OnError(Exception error) + { + + } + + public void OnNext(AvaloniaPropertyChangedEventArgs value) + { + + } + + #endregion + + + #region Private variables + + private string FileName = ""; + + /// + /// List of sprites patterns controls in the set + /// + private List SpritePatternsList = null; + + /// + /// last zoom value + /// + private int lastZoom = 0; + + /// + /// Zooms values + /// + private int[] zooms = new int[] + { + 1,2,4,8,16,24,32,48,64 + }; + + private byte actualFrame = 0; + + private FoldingManager? fManager; + private DispatcherTimer? updateFoldingsTimer; + + #endregion + + + #region ZXDocumentBase functions + + public override bool SaveDocument(TextWriter OutputLog) + { + try + { + var json = Map.Serializar(); + File.WriteAllText(FileName, json); + _Modified = false; + DocumentSaved?.Invoke(this, EventArgs.Empty); + return true; + } + catch (Exception ex) + { + OutputLog.WriteLine($"Error saving file {FileName}: {ex.Message}"); + return false; + } + } + + + public override bool RenameDocument(string NewName, TextWriter OutputLog) + { + Map.Name = Path.GetFileNameWithoutExtension(NewName); + FileName = NewName; + return true; + } + + + public override bool CloseDocument(TextWriter OutputLog, bool ForceClose) + { + return true; + } + + + public override void Dispose() + { + } + + + public override void Activated() + { + this.Focus(); + //ctrlPreview.Start(); + base.Activated(); + } + + + public override void Deactivated() + { + //ctrlPreview.Stop(); + base.Deactivated(); + } + + #endregion + + + + + + public List Controls { get; internal set; } + + public int WindowWidth { get; set; } + public int WindowHeight { get; set; } + + + /// + /// Zoom level of the editor + /// + public int Zoom { get; set; } + + + /// + /// Defines is grid is visible + /// + public bool IsGridOn { get; set; } + + + private ControlItem Viewport = null; + //private PaletteColor[] Palette=null; + + + private ZXMapsMap Map = null; + + + public ZXMapsEditor(string fileName) + { + FileName = fileName; + if (File.Exists(FileName)) + { + try + { + var fileData = File.ReadAllText(FileName); + if (!string.IsNullOrEmpty(fileData)) + { + Map = fileData.Deserializar(); + } + } + catch (Exception ex) + { + //MessageBox.Show($"Error loading file {FileName}: {ex.Message}"); + } + } + + if (Map == null) + { + Map = new ZXMapsMap(); + Map.Name = Path.GetFileNameWithoutExtension(FileName); + Map.Height = 12; + Map.Width=16; + Map.MappingType = ZXMapsMappingTypes.Sequential; + Map.MapType = ZXMapsTypes.Rooms; + Map.TileHeight = 16; + Map.TileWidth = 16; + + Map.Layers = new List(); + Map.Layers.Add(new ZXMapsLayer() + { + Id = 0, + LayersType = ZXMapsLayersType.Tiles, + Name = "Default layer", + Order = 0, + Selected = true, + Visible = true + }); + + Map.PropertyDefinitons = new List(); + ServiceLayer_Maps.Maps_SaveMap(FileName, Map); + } + + InitializeComponent(); + + ctrlLayers.Initialize(Map.Layers); + ctrlProperties.Initialize(Map,null,MapProperties_Command); + } + + private void MapProperties_Command(MapPropertiesControl control, string arg2) + { + //throw new NotImplementedException(); + } + + public bool Initialize() + { + Controls = new List(); + //Palette = log.ServiceLayer.CrateNextDefaultPalette(); + Zoom = 4; + WindowWidth = 300; + WindowHeight = 226; + Refresh(); + + return true; + } + + + + + /// + /// Refresh the editor UI + /// + public void Refresh() + { + //cnvEditor.Children.Clear(); + Viewport = Controls.FirstOrDefault(d => d.Id == 0); + if (Viewport == null) + { + Viewport = new ControlItem() + { + ContainerId = 0, + ControlType = ControlsTypes.Panel, + Height = WindowHeight, + Id = 0, + Ink = 0, + Left = 0, + Name = "Main panel", + Paper = 255, + Top = 0, + Visible = true, + Width = WindowWidth, + }; + Viewport.Properties = new List(); + Controls.Add(Viewport); + } + + foreach (var control in Controls.OrderBy(d => d.Id)) + { + switch (control.ControlType) + { + case ControlsTypes.Panel: + Draw_Panel(control); + break; + } + } + + //cnvEditor.Width = WindowWidth * Zoom; + //cnvEditor.Height = WindowHeight * Zoom; + } + + + public void Draw_Panel(ControlItem control) + { + //var r = new Rectangle(); + //r.Width = Zoom * control.Width; + //r.Height = Zoom *control.Height; + //r.Stroke = Palette[control.Paper].Brush; + //r.Fill = Palette[control.Paper].Brush; + ////cnvEditor.Children.Add(r); + //Canvas.SetTop(r, control.Top*Zoom); + //Canvas.SetLeft(r, control.Left*Zoom); + } + } +} diff --git a/ZXBStudio/DocumentModel/Classes/ZXDocumentProvider.cs b/ZXBStudio/DocumentModel/Classes/ZXDocumentProvider.cs index 872318b..b98cd18 100644 --- a/ZXBStudio/DocumentModel/Classes/ZXDocumentProvider.cs +++ b/ZXBStudio/DocumentModel/Classes/ZXDocumentProvider.cs @@ -13,6 +13,7 @@ using ZXBasicStudio.IntegratedDocumentTypes.NextDows; using ZXBasicStudio.IntegratedDocumentTypes.TapeDocuments.ZXTapeBuilder; using ZXBasicStudio.IntegratedDocumentTypes.Resources.ZXRamDisk; +using ZXBasicStudio.IntegratedDocumentTypes.ZXMaps; namespace ZXBasicStudio.DocumentModel.Classes { @@ -36,6 +37,8 @@ static ZXDocumentProvider() _docTypes.Add(new UDGDocument()); _docTypes.Add(new FontDocument()); _docTypes.Add(new SpriteDocument()); + _docTypes.Add(new ZXMapsDocument()); + _docTypes.Add(new ZXTilemapDocument()); // NextDows //_docTypes.Add(new ZXFormsDocument()); //TODO: Load external document types diff --git a/ZXBStudio/IntegratedDocumentTypes/ZXMaps/ZXMapsDocument.cs b/ZXBStudio/IntegratedDocumentTypes/ZXMaps/ZXMapsDocument.cs new file mode 100644 index 0000000..8ff093d --- /dev/null +++ b/ZXBStudio/IntegratedDocumentTypes/ZXMaps/ZXMapsDocument.cs @@ -0,0 +1,72 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using Avalonia.Svg.Skia; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ZXBasicStudio.Classes; +using ZXBasicStudio.DocumentEditors.NextDows.log; +using ZXBasicStudio.DocumentModel.Enums; +using ZXBasicStudio.DocumentModel.Interfaces; + +namespace ZXBasicStudio.IntegratedDocumentTypes.ZXMaps +{ + public class ZXMapsDocument : IZXDocumentType + { + static readonly string[] _docExtensions = { ".zxmap" }; + static readonly string _docName = "ZXMaps file"; + static readonly string _docDesc = "ZXMaps allows you to create different types of maps using tiles generated by the ZXGraphics sprite and tile editor."; + static readonly string _docCat = "Graphics"; + static readonly string _docAspect = "/Svg/Documents/file-map.svg"; + static readonly Guid _docId = Guid.Parse("2e2cdf61-a43e-44ef-99ac-172a724a3e62"); + + static readonly ZXMapsDocumentFactory _factory = new ZXMapsDocumentFactory(); + Bitmap? _icon; + + public string[] DocumentExtensions => _docExtensions; + public string DocumentName => _docName; + public string DocumentDescription => _docDesc; + public string DocumentCategory => _docCat; + public string? DocumentAspect => _docAspect; + + private static readonly ExportManager _exportManager = new ExportManager(); + + + public ZXMapsDocument() + { + _exportManager.Initialize(DocumentEditors.ZXGraphics.neg.FileTypes.Map); + } + + + public Bitmap DocumentIcon + { + get + { + if (_icon == null) + { + _icon = new Bitmap(AssetLoader.Open(new Uri("avares://ZXBasicStudio/Assets/zxmaps_map.png"))); + } + + return _icon; + } + } + + public bool CanCreate => true; + + public bool CanEdit => true; + + public IZXDocumentFactory DocumentFactory => _factory; + + public IZXDocumentBuilder? DocumentBuilder => _exportManager; + + public ZXBuildStage? DocumentBuildStage => ZXBuildStage.PreBuild; + + Guid IZXDocumentType.DocumentTypeId => _docId; + + ZXKeybCommand[]? IZXDocumentType.EditorCommands => new ZXKeybCommand[0]; + } +} diff --git a/ZXBStudio/IntegratedDocumentTypes/ZXMaps/ZXMapsDocumentFactory.cs b/ZXBStudio/IntegratedDocumentTypes/ZXMaps/ZXMapsDocumentFactory.cs new file mode 100644 index 0000000..fb3635f --- /dev/null +++ b/ZXBStudio/IntegratedDocumentTypes/ZXMaps/ZXMapsDocumentFactory.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ZXBasicStudio.DocumentEditors.ZXMaps; +using ZXBasicStudio.DocumentModel.Classes; +using ZXBasicStudio.DocumentModel.Interfaces; + +namespace ZXBasicStudio.IntegratedDocumentTypes.ZXMaps +{ + public class ZXMapsDocumentFactory : IZXDocumentFactory + { + public bool CreateDocument(string Path, TextWriter OutputLog) + { + var type = ZXDocumentProvider.GetDocumentType(Path); + + if (type is not ZXMapsDocument) + { + OutputLog.WriteLine($"Document {Path} is not an ZXMaps file, internal document handling error, operation aborted."); + return false; + } + + if (File.Exists(Path)) + { + OutputLog.WriteLine($"File {Path} already exists, cannot create."); + return false; + } + + try + { + File.Create(Path).Dispose(); + return true; + } + catch (Exception ex) + { + OutputLog.WriteLine($"Error creating file {Path}: {ex.Message}"); + return false; + } + } + + public ZXDocumentEditorBase? CreateEditor(string Path, TextWriter OutputLog) + { + var type = ZXDocumentProvider.GetDocumentType(Path); + + if (type is not ZXMapsDocument) + { + OutputLog.WriteLine($"Document {Path} is not an ZXMaps file, internal document handling error, operation aborted."); + return null; + } + ZXMapsEditor editor = new ZXMapsEditor(Path); + return editor; + } + } +} diff --git a/ZXBStudio/IntegratedDocumentTypes/ZXMaps/ZXTilemapDocument.cs b/ZXBStudio/IntegratedDocumentTypes/ZXMaps/ZXTilemapDocument.cs new file mode 100644 index 0000000..c01f3e2 --- /dev/null +++ b/ZXBStudio/IntegratedDocumentTypes/ZXMaps/ZXTilemapDocument.cs @@ -0,0 +1,102 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using Avalonia.Svg.Skia; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ZXBasicStudio.Classes; +using ZXBasicStudio.DocumentEditors.ZXGraphics; +using ZXBasicStudio.DocumentEditors.ZXGraphics.log; +using ZXBasicStudio.DocumentModel.Enums; +using ZXBasicStudio.DocumentModel.Interfaces; + +namespace ZXBasicStudio.IntegratedDocumentTypes.ZXGraphics +{ + public class ZXTilemapDocument : IZXDocumentType + { + static readonly string[] _docExtensions = { ".zxtil" }; + static readonly string _docName = "Tilemap file"; + static readonly string _docDesc = "Tilemap files allow you to create and modify a set of tiles for use in maps. Once created, it can be used in the zxmap built-in editor."; + static readonly string _docCat = "Graphics"; + static readonly string _docAspect = "/Svg/Documents/file-tile.svg"; + static readonly Guid _docId = Guid.Parse("baed2950-b902-44dd-aac6-4912b533684f"); + public static Guid Id => _docId; + + static readonly ZXTilemapDocumentFactory _factory = new ZXTilemapDocumentFactory(); + Bitmap? _icon; + + public string[] DocumentExtensions => _docExtensions; + public string DocumentName => _docName; + public string DocumentDescription => _docDesc; + public string DocumentCategory => _docCat; + public string? DocumentAspect => _docAspect; + + private static readonly ExportManager _exportManager = new ExportManager(); + + static readonly ZXKeybCommand[] _editCommands = new ZXKeybCommand[] + { + SpriteEditor.keyboardCommands["Save"], + SpriteEditor.keyboardCommands["Cut"], + SpriteEditor.keyboardCommands["Copy"], + SpriteEditor.keyboardCommands["Paste"], + SpriteEditor.keyboardCommands["Clear"], + SpriteEditor.keyboardCommands["Undo"], + SpriteEditor.keyboardCommands["Redo"], + SpriteEditor.keyboardCommands["Rotate Right"], + SpriteEditor.keyboardCommands["Rotate Left"], + SpriteEditor.keyboardCommands["Horizontal Mirror"], + SpriteEditor.keyboardCommands["Vertical Mirror"], + SpriteEditor.keyboardCommands["Shift Up"], + SpriteEditor.keyboardCommands["Shift Right"], + SpriteEditor.keyboardCommands["Shift Down"], + SpriteEditor.keyboardCommands["Shift Left"], + SpriteEditor.keyboardCommands["Move Up"], + SpriteEditor.keyboardCommands["Move Right"], + SpriteEditor.keyboardCommands["Move Down"], + SpriteEditor.keyboardCommands["Move Left"], + SpriteEditor.keyboardCommands["Invert"], + SpriteEditor.keyboardCommands["Mask"], + SpriteEditor.keyboardCommands["Export"], + SpriteEditor.keyboardCommands["Zoom In"], + SpriteEditor.keyboardCommands["Zoom Out"], + }; + + + public ZXTilemapDocument() + { + _exportManager.Initialize(DocumentEditors.ZXGraphics.neg.FileTypes.Sprite); + } + + + public Bitmap DocumentIcon + { + get + { + if (_icon == null) + { + _icon = new Bitmap(AssetLoader.Open(new Uri("avares://ZXBasicStudio/Assets/zxmaps_til.png"))); + } + + return _icon; + } + } + + public bool CanCreate => true; + + public bool CanEdit => true; + + public IZXDocumentFactory DocumentFactory => _factory; + + public IZXDocumentBuilder? DocumentBuilder => _exportManager; + + public ZXBuildStage? DocumentBuildStage => ZXBuildStage.PreBuild; + + Guid IZXDocumentType.DocumentTypeId => _docId; + + ZXKeybCommand[]? IZXDocumentType.EditorCommands => _editCommands; + } +} diff --git a/ZXBStudio/IntegratedDocumentTypes/ZXMaps/ZXTilemapDocumentFactory.cs b/ZXBStudio/IntegratedDocumentTypes/ZXMaps/ZXTilemapDocumentFactory.cs new file mode 100644 index 0000000..81356da --- /dev/null +++ b/ZXBStudio/IntegratedDocumentTypes/ZXMaps/ZXTilemapDocumentFactory.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ZXBasicStudio.DocumentEditors.ZXGraphics; +using ZXBasicStudio.DocumentEditors.ZXMaps; +using ZXBasicStudio.DocumentEditors.ZXTextEditor.Controls; +using ZXBasicStudio.DocumentModel.Classes; +using ZXBasicStudio.DocumentModel.Interfaces; + +namespace ZXBasicStudio.IntegratedDocumentTypes.ZXGraphics +{ + public class ZXTilemapDocumentFactory : IZXDocumentFactory + { + public bool CreateDocument(string Path, TextWriter OutputLog) + { + var type = ZXDocumentProvider.GetDocumentType(Path); + + if (type is not ZXTilemapDocument) + { + OutputLog.WriteLine($"Document {Path} is not a tilemap file, internal document handling error, operation aborted."); + return false; + } + + if (File.Exists(Path)) + { + OutputLog.WriteLine($"File {Path} already exists, cannot create."); + return false; + } + + try + { + File.Create(Path).Dispose(); + return true; + } + catch (Exception ex) + { + OutputLog.WriteLine($"Error creating file {Path}: {ex.Message}"); + return false; + } + } + + public ZXDocumentEditorBase? CreateEditor(string Path, TextWriter OutputLog) + { + var type = ZXDocumentProvider.GetDocumentType(Path); + + if (type is not ZXTilemapDocument) + { + OutputLog.WriteLine($"Document {Path} is not a tile file, internal document handling error, operation aborted."); + return null; + } + TileEditor editor = new TileEditor(Path); + return editor; + } + } +} diff --git a/ZXBStudio/MainWindow.axaml.cs b/ZXBStudio/MainWindow.axaml.cs index 9b9a918..2fa9637 100644 --- a/ZXBStudio/MainWindow.axaml.cs +++ b/ZXBStudio/MainWindow.axaml.cs @@ -1485,7 +1485,7 @@ private async void BuildAndRun(object? sender, Avalonia.Interactivity.RoutedEven try { var emulatorName = Path.GetFileNameWithoutExtension(emulatorPath); - if (emulatorName.ToLower()=="apprun") + if (emulatorName.ToLower() == "apprun") { emulatorName = "mame"; } @@ -2532,19 +2532,19 @@ private string BuildAndRun_MAME(string emulatorPath, string nextDrive, ZXBuildSe // emulatorPath = emulatorPath.Replace("/AppRun", ""); //} - var basePath =Directory.GetParent(Directory.GetParent(emulatorPath).FullName).FullName; - var sdimagePath= Path.Combine(basePath, "nextsdimage", "cspect-next-2gb.img"); + var basePath = Directory.GetParent(Directory.GetParent(emulatorPath).FullName).FullName; + var sdimagePath = Path.Combine(basePath, "nextsdimage", "cspect-next-2gb.img"); // Delete /nextzxos/autoexec.1st Hdfmonkey($"rm \"{sdimagePath}\" /nextzxos/autoexec.1st", basePath); // Create autoexec.bas - var nextPath =Path.GetFileNameWithoutExtension(project.GetMainFile()); + var nextPath = Path.GetFileNameWithoutExtension(project.GetMainFile()); string autoexec = $@"#autostart 10 10 CD ""{nextPath}"" 20 .nexload {nextPath}.nex "; - var bytes=Common.Txt2Bas.Txt2BasConverter.Text2Bas(autoexec); + var bytes = Common.Txt2Bas.Txt2BasConverter.Text2Bas(autoexec); var autoexecPath = Path.Combine(basePath, "hdfmonkey", "autoexec.bas"); File.WriteAllBytes(autoexecPath, bytes); @@ -2607,17 +2607,17 @@ 20 .nexload {nextPath}.nex private bool Hdfmonkey(string parameters, string basePath) { var hdfPath = Path.Combine(basePath, "hdfmonkey"); + string hdfmonkeyExe = Path.Combine(hdfPath, "hdfmonkey.exe"); - string hdfmonkeyExe = "hdfmonkey.exe"; - if (!OperatingSystem.IsWindows()) + if (!File.Exists(hdfmonkeyExe)) { - hdfmonkeyExe = "hdfmonkey"; + hdfmonkeyExe = Path.Combine(hdfPath, "hdfmonkey"); } outLog.Writer.WriteLine($"> hdfmonkey {parameters}"); var psi = new ProcessStartInfo() { - FileName = Path.Combine(hdfPath, hdfmonkeyExe), + FileName = hdfmonkeyExe, Arguments = parameters, WorkingDirectory = hdfPath, UseShellExecute = false, diff --git a/ZXBStudio/Program.cs b/ZXBStudio/Program.cs index a25ee63..c24d3c3 100644 --- a/ZXBStudio/Program.cs +++ b/ZXBStudio/Program.cs @@ -42,6 +42,8 @@ public static void Main(string[] args) { SetVersion(); + //var id= Guid.NewGuid(); + if (args.Contains("--version")) { Console.WriteLine($"{Version}"); diff --git a/ZXBStudio/Svg/Documents/file-blank.svg b/ZXBStudio/Svg/Documents/file-blank.svg new file mode 100644 index 0000000..e5703ec --- /dev/null +++ b/ZXBStudio/Svg/Documents/file-blank.svg @@ -0,0 +1,43 @@ + + diff --git a/ZXBStudio/Svg/Documents/file-map.svg b/ZXBStudio/Svg/Documents/file-map.svg index e5703ec..030552e 100644 --- a/ZXBStudio/Svg/Documents/file-map.svg +++ b/ZXBStudio/Svg/Documents/file-map.svg @@ -3,8 +3,8 @@ viewBox="0 0 384 512" version="1.1" id="svg12" - sodipodi:docname="file-zx-tape.svg" - inkscape:version="1.2.2 (732a01da63, 2022-12-09)" + sodipodi:docname="file-map.svg" + inkscape:version="1.4 (86a8ad7, 2024-10-11)" xml:space="preserve" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" @@ -20,24 +20,21 @@ inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" showgrid="false" - inkscape:zoom="1.4183568" - inkscape:cx="374.02436" - inkscape:cy="333.83701" - inkscape:window-width="1841" - inkscape:window-height="1270" - inkscape:window-x="951" - inkscape:window-y="453" + inkscape:zoom="0.50146486" + inkscape:cx="143.57935" + inkscape:cy="307.10028" + inkscape:window-width="1424" + inkscape:window-height="820" + inkscape:window-x="0" + inkscape:window-y="0" inkscape:window-maximized="0" inkscape:current-layer="svg12" /> + style="fill:#ffffff" /> diff --git a/ZXBStudio/Svg/Documents/file-tile.svg b/ZXBStudio/Svg/Documents/file-tile.svg index e5703ec..a123629 100644 --- a/ZXBStudio/Svg/Documents/file-tile.svg +++ b/ZXBStudio/Svg/Documents/file-tile.svg @@ -3,8 +3,8 @@ viewBox="0 0 384 512" version="1.1" id="svg12" - sodipodi:docname="file-zx-tape.svg" - inkscape:version="1.2.2 (732a01da63, 2022-12-09)" + sodipodi:docname="file-tile.svg" + inkscape:version="1.4 (86a8ad7, 2024-10-11)" xml:space="preserve" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" @@ -20,24 +20,39 @@ inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" showgrid="false" - inkscape:zoom="1.4183568" - inkscape:cx="374.02436" - inkscape:cy="333.83701" - inkscape:window-width="1841" - inkscape:window-height="1270" - inkscape:window-x="951" - inkscape:window-y="453" - inkscape:window-maximized="0" + inkscape:zoom="1.0029297" + inkscape:cx="-33.402141" + inkscape:cy="344.49074" + inkscape:window-width="1920" + inkscape:window-height="1009" + inkscape:window-x="2638" + inkscape:window-y="-8" + inkscape:window-maximized="1" inkscape:current-layer="svg12" /> + style="fill:#ffffff" /> diff --git a/ZXBStudio/Svg/White/check.svg b/ZXBStudio/Svg/White/check.svg new file mode 100644 index 0000000..3405511 --- /dev/null +++ b/ZXBStudio/Svg/White/check.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/ZXBStudio/Svg/add.svg b/ZXBStudio/Svg/add.svg new file mode 100644 index 0000000..15436c4 --- /dev/null +++ b/ZXBStudio/Svg/add.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/ZXBStudio/Svg/show.svg b/ZXBStudio/Svg/show.svg new file mode 100644 index 0000000..ea0a452 --- /dev/null +++ b/ZXBStudio/Svg/show.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ZXBStudio/ZXBasicStudio.csproj b/ZXBStudio/ZXBasicStudio.csproj index 84719dd..045e5fc 100644 --- a/ZXBStudio/ZXBasicStudio.csproj +++ b/ZXBStudio/ZXBasicStudio.csproj @@ -68,8 +68,8 @@ - + @@ -121,6 +121,7 @@ + @@ -202,6 +203,7 @@ + @@ -316,7 +318,8 @@ - + + @@ -363,15 +366,19 @@ + + + + @@ -632,6 +639,48 @@ ZXGridImageView.axaml + + LayerItemControl.axaml + + + MapFileSelectorControl.axaml + + + MapPropertiesControl.axaml + + + LayersControl.axaml + + + MapPatternEditor.axaml + + + MapExportDialog.axaml + + + TileExportDialog.axaml + + + TileImportDialog.axaml + + + TilePropertiesControl.axaml + + + TilePreviewControl.axaml + + + TilePatternControl.axaml + + + TilePatternEditor.axaml + + + TileEditor.axaml + + + ZXMapsEditor.axaml + ZXKeyView.axaml diff --git a/ZXBasicStudio.Log/Class1.cs b/ZXBasicStudio.Log/Class1.cs new file mode 100644 index 0000000..46431c3 --- /dev/null +++ b/ZXBasicStudio.Log/Class1.cs @@ -0,0 +1,7 @@ +namespace ZXBasicStudio.Log +{ + public class Class1 + { + + } +} diff --git a/ZXBasicStudio.Log/ZXBasicStudio.Log.csproj b/ZXBasicStudio.Log/ZXBasicStudio.Log.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/ZXBasicStudio.Log/ZXBasicStudio.Log.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + +