From b12b2f569ae3cd06ccf8f7f03972f4b8ec760726 Mon Sep 17 00:00:00 2001 From: MetalMaxMX <62585114+MetalMaxMX@users.noreply.github.com> Date: Mon, 11 Dec 2023 14:54:22 -0400 Subject: [PATCH 01/24] Update README.md Change the naming of MIT license to LGPLv3 to reflect the new license being used by the project. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b2b90cddb..2824b88d9 100644 --- a/README.md +++ b/README.md @@ -98,4 +98,4 @@ Please read [CONTRIBUTING.md](https://github.com/flabbet/PixiEditor/blob/master/ ## License -This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/flabbet/PixiEditor/blob/master/LICENSE) - file for details +This project is licensed under the LGPLv3 License - see the [LICENSE.md](https://github.com/flabbet/PixiEditor/blob/master/LICENSE) - file for details From 5fcfbdc906af440996cc8dee11646a44980b91c9 Mon Sep 17 00:00:00 2001 From: warrengalyen Date: Tue, 12 Dec 2023 13:17:46 -0800 Subject: [PATCH 02/24] Add support for reading Interleaved Bitmap palettes (LBM/BBM) This is an image format that was used to store optional palette information, commonly used in DeluxePaint. --- .../Extensions/ServiceCollectionHelpers.cs | 5 +- .../IO/PaletteParsers/DeluxePaintParser.cs | 122 ++++++++++++++++++ 2 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 src/PixiEditor/Models/IO/PaletteParsers/DeluxePaintParser.cs diff --git a/src/PixiEditor/Helpers/Extensions/ServiceCollectionHelpers.cs b/src/PixiEditor/Helpers/Extensions/ServiceCollectionHelpers.cs index fb6470cfc..7af1d8ce8 100644 --- a/src/PixiEditor/Helpers/Extensions/ServiceCollectionHelpers.cs +++ b/src/PixiEditor/Helpers/Extensions/ServiceCollectionHelpers.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using PixiEditor.Extensions; using PixiEditor.Extensions.Common.Localization; using PixiEditor.Extensions.Common.UserPreferences; using PixiEditor.Extensions.Palettes; @@ -10,15 +9,12 @@ using PixiEditor.Models.Commands; using PixiEditor.Models.Controllers; using PixiEditor.Models.DataProviders; -using PixiEditor.Models.IO; using PixiEditor.Models.IO.PaletteParsers; using PixiEditor.Models.IO.PaletteParsers.JascPalFile; using PixiEditor.Models.Localization; using PixiEditor.Models.Preferences; -using PixiEditor.ViewModels; using PixiEditor.ViewModels.SubViewModels.AdditionalContent; using PixiEditor.ViewModels.SubViewModels.Document; -using PixiEditor.ViewModels.SubViewModels.Main; using PixiEditor.ViewModels.SubViewModels.Tools; using PixiEditor.ViewModels.SubViewModels.Tools.Tools; @@ -75,6 +71,7 @@ internal static class ServiceCollectionHelpers // Palette Parsers .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/src/PixiEditor/Models/IO/PaletteParsers/DeluxePaintParser.cs b/src/PixiEditor/Models/IO/PaletteParsers/DeluxePaintParser.cs new file mode 100644 index 000000000..c978be351 --- /dev/null +++ b/src/PixiEditor/Models/IO/PaletteParsers/DeluxePaintParser.cs @@ -0,0 +1,122 @@ +using System.IO; +using System.Text; +using PixiEditor.Extensions.Palettes; +using PixiEditor.Extensions.Palettes.Parsers; + +namespace PixiEditor.Models.IO.PaletteParsers; + +// Reads 8-bit color palette data from Interleaved Bitmap format (LBM/BBM) +// most commonly used by DeluxePaint on Amiga and MS DOS. +// +// https://en.wikipedia.org/wiki/ILBM + +// Note: A BBM file is essentially a LBM without a full image. + +internal class DeluxePaintParser : PaletteFileParser +{ + public override string FileName { get; } = "DeluxePaint Interleaved Bitmap Palette"; + public override string[] SupportedFileExtensions { get; } = new string[] { ".bbm", ".lbm" }; + public override async Task Parse(string path) + { + try + { + return await ParseFile(path); + } + catch + { + return PaletteFileData.Corrupted; + } + } + + private static async Task ParseFile(string path) + { + List colorPalette = new(); + string name = Path.GetFileNameWithoutExtension(path); + + await using (Stream stream = File.OpenRead(path)) + { + byte[] buffer; + string header; + + // read the FORM header that identifies the document as an IFF file + buffer = new byte[4]; + stream.Read(buffer, 0, buffer.Length); + if (Encoding.ASCII.GetString(buffer) != "FORM") + return PaletteFileData.Corrupted; // Form header not found + + // the next value is the size of all the data in the FORM chunk + // We don't actually need this value, but we have to read it + // regardless to advance the stream + ReadInt(stream); + + stream.Read(buffer, 0, buffer.Length); + header = Encoding.ASCII.GetString(buffer); + if (header != "PBM " && header != "ILBM") + return PaletteFileData.Corrupted; // Bitmap header not found + + while (stream.Read(buffer, 0, buffer.Length) == buffer.Length) + { + int chunkLength; + + chunkLength = ReadInt(stream); + + if (Encoding.ASCII.GetString(buffer) != "CMAP") + { + // some other LBM chunk, skip it + if (stream.CanSeek) + { + stream.Seek(chunkLength, SeekOrigin.Current); + } + else + { + for (int i = 0; i < chunkLength; i++) + stream.ReadByte(); + } + } + else + { + // color map chunk + for (int i = 0; i < chunkLength / 3; i++) + { + int[] rgb = new int[3]; + + rgb[0] = stream.ReadByte(); + rgb[1] = stream.ReadByte(); + rgb[2] = stream.ReadByte(); + + colorPalette.Add(new PaletteColor((byte)rgb[0], (byte)rgb[1], (byte)rgb[2])); + } + + // all done so stop reading the rest of the file + break; + } + + // chunks always contain an even number of bytes even if the recorded length is odd + // if the length is odd, then there's a padding byte in the file - just read and discard + if (chunkLength % 2 != 0) + stream.ReadByte(); + } + } + + return new PaletteFileData(name, colorPalette.ToArray()); + } + + public override bool CanSave => false; + + public override async Task Save(string path, PaletteFileData data) + { + throw new SavingNotSupportedException("Saving palette as .bbm or .lbm is not supported."); + } + + private static int ReadInt(Stream stream) + { + byte[] buffer; + + // big endian conversion: http://stackoverflow.com/a/14401341/148962 + + buffer = new byte[4]; + stream.Read(buffer, 0, buffer.Length); + + return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; + } +} From de4b598ad960bf5b035de36bfa79e2d657047ecf Mon Sep 17 00:00:00 2001 From: Equbuxu Date: Wed, 13 Dec 2023 22:11:13 +0300 Subject: [PATCH 03/24] 'Fixed crash report and integrated temp autosave files' --- src/PixiEditor/App.xaml.cs | 58 ++++++++++++------- .../Data/Localization/Languages/en.json | 5 +- .../Models/DataHolders/CrashFilePathInfo.cs | 14 +++++ .../Models/DataHolders/CrashReport.cs | 37 +++++++----- .../Models/Dialogs/OptionsDialog.cs | 6 +- .../ViewModels/CrashReportViewModel.cs | 24 +++++++- .../SubViewModels/Main/FileViewModel.cs | 3 +- .../Views/Dialogs/OptionsPopup.xaml.cs | 1 + src/PixiEditor/Views/MainWindow.xaml.cs | 40 ++++++++++++- 9 files changed, 144 insertions(+), 44 deletions(-) create mode 100644 src/PixiEditor/Models/DataHolders/CrashFilePathInfo.cs diff --git a/src/PixiEditor/App.xaml.cs b/src/PixiEditor/App.xaml.cs index ec746c548..67b8592f1 100644 --- a/src/PixiEditor/App.xaml.cs +++ b/src/PixiEditor/App.xaml.cs @@ -6,6 +6,7 @@ using System.Windows.Media; using System.Windows.Threading; using PixiEditor.Extensions.Common.Localization; +using PixiEditor.Helpers; using PixiEditor.Models.AppExtensions; using PixiEditor.Helpers.UI; using PixiEditor.Models.Controllers; @@ -42,34 +43,37 @@ protected override void OnStartup(StartupEventArgs e) if (ParseArgument("--crash (\"?)([A-z0-9:\\/\\ -_.]+)\\1", arguments, out Group[] groups)) { - CrashReport report = CrashReport.Parse(groups[2].Value); - MainWindow = new CrashReportDialog(report); - MainWindow.Show(); + try + { + CrashReport report = CrashReport.Parse(groups[2].Value); + MainWindow = new CrashReportDialog(report); + MainWindow.Show(); + } + catch (Exception exception) + { + try + { + CrashHelper.SendExceptionInfoToWebhook(exception); + } + finally + { + MessageBox.Show("Fatal error", $"Fatal error while trying to open crash report in App.OnStartup()\n{exception}"); + } + } + return; } - #if !STEAM +#if !STEAM if (!HandleNewInstance()) { return; } - #endif - - LoadingWindow.ShowInNewThread(); - - AddNativeAssets(); - - InitPlatform(); - - ExtensionLoader extensionLoader = new ExtensionLoader(); - extensionLoader.LoadExtensions(); - +#endif + + var extensionLoader = InitApp(); + MainWindow = new MainWindow(extensionLoader); - MainWindow.ContentRendered += (_, _) => - { - LoadingWindow.Instance.SafeClose(); - MainWindow.Activate(); - }; MainWindow.Show(); } @@ -80,6 +84,20 @@ private void InitPlatform() platform.PerformHandshake(); } + public ExtensionLoader InitApp() + { + LoadingWindow.ShowInNewThread(); + + AddNativeAssets(); + + InitPlatform(); + + ExtensionLoader extensionLoader = new ExtensionLoader(); + extensionLoader.LoadExtensions(); + + return extensionLoader; + } + private IPlatform GetActivePlatform() { #if STEAM diff --git a/src/PixiEditor/Data/Localization/Languages/en.json b/src/PixiEditor/Data/Localization/Languages/en.json index ad5cbbdf3..4b934388c 100644 --- a/src/PixiEditor/Data/Localization/Languages/en.json +++ b/src/PixiEditor/Data/Localization/Languages/en.json @@ -589,5 +589,8 @@ "COPY_COLOR": "Copy color", "FAILED_DOWNLOADING_TITLE": "Downloading update failed", - "FAILED_DOWNLOADING": "Failed downloading the update, you might not have enough space on the disk" + "FAILED_DOWNLOADING": "Failed downloading the update, you might not have enough space on the disk", + + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Could not recover all", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "Could not fully recover all files.\nIf you send the crash report to the developers\nthey might be able to help you." } diff --git a/src/PixiEditor/Models/DataHolders/CrashFilePathInfo.cs b/src/PixiEditor/Models/DataHolders/CrashFilePathInfo.cs new file mode 100644 index 000000000..078323c58 --- /dev/null +++ b/src/PixiEditor/Models/DataHolders/CrashFilePathInfo.cs @@ -0,0 +1,14 @@ +namespace PixiEditor.Models.DataHolders; + +public class CrashFilePathInfo +{ + public string? OriginalPath { get; set; } + + public string? AutosavePath { get; set; } + + public CrashFilePathInfo(string originalPath, string autosavePath) + { + OriginalPath = originalPath; + AutosavePath = autosavePath; + } +} diff --git a/src/PixiEditor/Models/DataHolders/CrashReport.cs b/src/PixiEditor/Models/DataHolders/CrashReport.cs index c4643de55..196abcf89 100644 --- a/src/PixiEditor/Models/DataHolders/CrashReport.cs +++ b/src/PixiEditor/Models/DataHolders/CrashReport.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Globalization; using System.IO; using System.IO.Compression; using System.Reflection; @@ -90,7 +91,7 @@ public static CrashReport Parse(string path) public int GetDocumentCount() => ZipFile.Entries.Where(x => x.FullName.EndsWith(".pixi")).Count(); - public List<(string? originalPath, byte[] dotPixiBytes)> RecoverDocuments() + public List<(CrashFilePathInfo originalPath, byte[] dotPixiBytes)> RecoverDocuments() { // Load .pixi files Dictionary recoveredDocuments = new(); @@ -99,27 +100,29 @@ public List<(string? originalPath, byte[] dotPixiBytes)> RecoverDocuments() using Stream stream = entry.Open(); using MemoryStream memStream = new(); stream.CopyTo(memStream); - recoveredDocuments.Add(entry.Name, memStream.ToArray()); + recoveredDocuments.Add(entry.FullName["Documents/".Length..], memStream.ToArray()); } - ZipArchiveEntry? originalPathsEntry = ZipFile.Entries.Where(entry => entry.FullName == "DocumentInfo.json").FirstOrDefault(); - if (originalPathsEntry is null) - return recoveredDocuments.Select, (string?, byte[])>(keyValue => (null, keyValue.Value)).ToList(); - + var originalPathsEntry = ZipFile.Entries.First(entry => entry.FullName == "DocumentInfo.json"); + // Load original paths - Dictionary originalPaths; + Dictionary originalPaths; { using Stream stream = originalPathsEntry.Open(); using StreamReader reader = new(stream); string json = reader.ReadToEnd(); - originalPaths = JsonConvert.DeserializeObject>(json); + originalPaths = JsonConvert.DeserializeObject>(json); + } + + var list = new List<(CrashFilePathInfo originalPath, byte[] dotPixiBytes)>(); + + foreach (var document in recoveredDocuments) + { + var originalPath = originalPaths[document.Key]; + list.Add((originalPath, document.Value)); } - return ( - from docKeyValue in recoveredDocuments - join pathKeyValue in originalPaths on docKeyValue.Key equals pathKeyValue.Key - select (pathKeyValue.Value, docKeyValue.Value) - ).ToList(); + return list; } public void Dispose() @@ -169,20 +172,22 @@ public void Save() // Write the documents into zip int counter = 0; - Dictionary originalPaths = new(); + Dictionary originalPaths = new(); foreach (DocumentViewModel document in vm.DocumentManagerSubViewModel.Documents) { try { string fileName = string.IsNullOrWhiteSpace(document.FullFilePath) ? "Unsaved" : Path.GetFileNameWithoutExtension(document.FullFilePath); - string nameInZip = $"{fileName}-{document.OpenedUTC}-{counter}.pixi".Replace(':', '_'); + string nameInZip = $"{fileName}-{document.OpenedUTC.ToString(CultureInfo.InvariantCulture)}-{counter.ToString(CultureInfo.InvariantCulture)}.pixi" + .Replace(':', '_') + .Replace('/', '_'); byte[] serialized = PixiParser.Serialize(document.ToSerializable()); using Stream documentStream = archive.CreateEntry($"Documents/{nameInZip}").Open(); documentStream.Write(serialized); - originalPaths.Add(nameInZip, document.FullFilePath); + originalPaths.Add(nameInZip, new CrashFilePathInfo(document.FullFilePath, null)); } catch { } counter++; diff --git a/src/PixiEditor/Models/Dialogs/OptionsDialog.cs b/src/PixiEditor/Models/Dialogs/OptionsDialog.cs index a7820a61a..a40ebfc75 100644 --- a/src/PixiEditor/Models/Dialogs/OptionsDialog.cs +++ b/src/PixiEditor/Models/Dialogs/OptionsDialog.cs @@ -2,6 +2,7 @@ using System.Windows.Controls; using System.Windows.Media; using PixiEditor.Extensions.Common.Localization; +using PixiEditor.Extensions.UI; using PixiEditor.Models.Localization; using PixiEditor.Views.Dialogs; @@ -51,9 +52,12 @@ public OptionsDialog(string title, object content, IEnumerable _results.Add(name, value); } - public override bool ShowDialog() + public override bool ShowDialog() => ShowDialog(false); + + public bool ShowDialog(bool topmost) { var popup = new OptionPopup(Title, Content, new(_results.Keys.Select(x => (object)x))); + popup.Topmost = topmost; var popupResult = popup.ShowDialog(); Result = (T)popup.Result; diff --git a/src/PixiEditor/ViewModels/CrashReportViewModel.cs b/src/PixiEditor/ViewModels/CrashReportViewModel.cs index 037c931de..ca61ef68b 100644 --- a/src/PixiEditor/ViewModels/CrashReportViewModel.cs +++ b/src/PixiEditor/ViewModels/CrashReportViewModel.cs @@ -3,8 +3,11 @@ using System.Net.Http; using System.Text; using System.Windows; +using System.Windows.Threading; +using PixiEditor.Extensions.Common.Localization; using PixiEditor.Helpers; using PixiEditor.Models.DataHolders; +using PixiEditor.Models.Dialogs; using PixiEditor.Views; using PixiEditor.Views.Dialogs; @@ -45,11 +48,30 @@ public CrashReportViewModel(CrashReport report) public void RecoverDocuments(object args) { - MainWindow window = MainWindow.CreateWithDocuments(CrashReport.RecoverDocuments()); + MainWindow window = MainWindow.CreateWithRecoveredDocuments(CrashReport, out var showMissingFilesDialog); Application.Current.MainWindow = window; window.Show(); hasRecoveredDocuments = false; + + if (showMissingFilesDialog) + { + var dialog = new OptionsDialog( + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE", + new LocalizedString("CRASH_NOT_ALL_DOCUMENTS_RECOVERED")) + { + { + "SEND", _ => + { + var sendReportDialog = new SendCrashReportWindow(CrashReport); + sendReportDialog.ShowDialog(); + } + }, + "CLOSE" + }; + + dialog.ShowDialog(true); + } } [Conditional("DEBUG")] diff --git a/src/PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs b/src/PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs index b7c9a2a36..a7b9a2e46 100644 --- a/src/PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs +++ b/src/PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs @@ -102,8 +102,7 @@ private void Owner_OnStartupEvent(object sender, System.EventArgs e) { OpenFromPath(file); } - else if ((Owner.DocumentManagerSubViewModel.Documents.Count == 0 - || !args.Contains("--crash")) && !args.Contains("--openedInExisting")) + else if ((Owner.DocumentManagerSubViewModel.Documents.Count == 0 && !args.Contains("--crash")) && !args.Contains("--openedInExisting")) { if (IPreferences.Current.GetPreference("ShowStartupWindow", true)) { diff --git a/src/PixiEditor/Views/Dialogs/OptionsPopup.xaml.cs b/src/PixiEditor/Views/Dialogs/OptionsPopup.xaml.cs index be83a0c92..adc2d63fe 100644 --- a/src/PixiEditor/Views/Dialogs/OptionsPopup.xaml.cs +++ b/src/PixiEditor/Views/Dialogs/OptionsPopup.xaml.cs @@ -39,6 +39,7 @@ public object Result public OptionPopup(string title, object content, ObservableCollection options) { + Title = title; PopupContent = content; Options = options; CancelCommand = new RelayCommand(Cancel); diff --git a/src/PixiEditor/Views/MainWindow.xaml.cs b/src/PixiEditor/Views/MainWindow.xaml.cs index 748c3cf15..c99bde670 100644 --- a/src/PixiEditor/Views/MainWindow.xaml.cs +++ b/src/PixiEditor/Views/MainWindow.xaml.cs @@ -16,11 +16,14 @@ using PixiEditor.Helpers; using PixiEditor.Models.AppExtensions; using PixiEditor.Models.Controllers; +using PixiEditor.Models.DataHolders; +using PixiEditor.Models.Dialogs; using PixiEditor.Models.Enums; using PixiEditor.Models.IO; using PixiEditor.Platform; using PixiEditor.ViewModels.SubViewModels.Document; using PixiEditor.ViewModels.SubViewModels.Tools.Tools; +using PixiEditor.Views.Dialogs; namespace PixiEditor.Views; @@ -79,6 +82,14 @@ public MainWindow(ExtensionLoader extensionLoader) }); DataContext.DocumentManagerSubViewModel.ActiveDocumentChanged += DocumentChanged; + + ContentRendered += OnContentRendered; + } + + private void OnContentRendered(object sender, EventArgs e) + { + LoadingWindow.Instance?.SafeClose(); + Activate(); } private void SetupTranslator() @@ -96,16 +107,39 @@ private void MainWindow_ContentRendered(object sender, EventArgs e) GlobalMouseHook.Instance.Initilize(this); } - public static MainWindow CreateWithDocuments(IEnumerable<(string? originalPath, byte[] dotPixiBytes)> documents) + public static MainWindow CreateWithRecoveredDocuments(CrashReport report, out bool showMissingFilesDialog) { - MainWindow window = new(extLoader); + var app = (App)Application.Current; + MainWindow window = new(app.InitApp()); FileViewModel fileVM = window.services.GetRequiredService(); + var documents = report.RecoverDocuments(); + + var i = 0; foreach (var (path, bytes) in documents) { - fileVM.OpenRecoveredDotPixi(path, bytes); + try + { + fileVM.OpenRecoveredDotPixi(path.OriginalPath, bytes); + i++; + } + catch (Exception e) + { + try + { + fileVM.OpenFromPath(path.AutosavePath, false); + } + catch (Exception deepE) + { + CrashHelper.SendExceptionInfoToWebhook(deepE); + } + + CrashHelper.SendExceptionInfoToWebhook(e); + } } + showMissingFilesDialog = documents.Count != i; + return window; } From 00177726c6e1f0f3769e8b117142ae1e213a0558 Mon Sep 17 00:00:00 2001 From: Equbuxu Date: Wed, 13 Dec 2023 22:19:11 +0300 Subject: [PATCH 04/24] 'Fixed wrong error being passed when PixiEditor has no right to write to a file' --- src/PixiEditor/Models/IO/Exporter.cs | 58 ++++++++++++++++++---------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/src/PixiEditor/Models/IO/Exporter.cs b/src/PixiEditor/Models/IO/Exporter.cs index 512591f7a..ef4cd0203 100644 --- a/src/PixiEditor/Models/IO/Exporter.cs +++ b/src/PixiEditor/Models/IO/Exporter.cs @@ -92,33 +92,23 @@ public static SaveResult TrySave(DocumentViewModel document, string pathWithExte var typeFromPath = SupportedFilesHelper.ParseImageFormat(Path.GetExtension(pathWithExtension)); - if (typeFromPath != FileType.Pixi) + if (typeFromPath == FileType.Pixi) { - var maybeBitmap = document.MaybeRenderWholeImage(); - if (maybeBitmap.IsT0) - return SaveResult.ConcurrencyError; - var bitmap = maybeBitmap.AsT1; + return TrySaveAsPixi(document, pathWithExtension); + } - if (!encodersFactory.ContainsKey(typeFromPath)) - { - return SaveResult.UnknownError; - } + var maybeBitmap = document.MaybeRenderWholeImage(); + if (maybeBitmap.IsT0) + return SaveResult.ConcurrencyError; + var bitmap = maybeBitmap.AsT1; - return TrySaveAs(encodersFactory[typeFromPath](), pathWithExtension, bitmap, exportSize); - } - else + if (!encodersFactory.ContainsKey(typeFromPath)) { - try - { - Parser.PixiParser.Serialize(document.ToSerializable(), pathWithExtension); - } - catch (UnauthorizedAccessException) - { - return SaveResult.SecurityError; - } + return SaveResult.UnknownError; } - return SaveResult.Success; + return TrySaveAs(encodersFactory[typeFromPath](), pathWithExtension, bitmap, exportSize); + } static Dictionary> encodersFactory = new Dictionary>(); @@ -179,6 +169,10 @@ private static SaveResult TrySaveAs(BitmapEncoder encoder, string savePath, Surf { return SaveResult.SecurityError; } + catch (UnauthorizedAccessException e) + { + return SaveResult.SecurityError; + } catch (IOException) { return SaveResult.IoError; @@ -189,4 +183,26 @@ private static SaveResult TrySaveAs(BitmapEncoder encoder, string savePath, Surf } return SaveResult.Success; } + + private static SaveResult TrySaveAsPixi(DocumentViewModel document, string pathWithExtension) + { + try + { + Parser.PixiParser.Serialize(document.ToSerializable(), pathWithExtension); + } + catch (UnauthorizedAccessException e) + { + return SaveResult.SecurityError; + } + catch (IOException) + { + return SaveResult.IoError; + } + catch + { + return SaveResult.UnknownError; + } + + return SaveResult.Success; + } } From 2776c9ffee4397492f21993e7d33373265ca71ac Mon Sep 17 00:00:00 2001 From: Equbuxu Date: Wed, 13 Dec 2023 22:22:36 +0300 Subject: [PATCH 05/24] 'Fixed #581 "Open from clipboard" causes a crash' --- src/PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs b/src/PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs index a7b9a2e46..95d104e8f 100644 --- a/src/PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs +++ b/src/PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs @@ -156,7 +156,7 @@ public void OpenFromClipboard() continue; } - OpenFromPath(dataImage.name, false); + OpenRegularImage(dataImage.image, null); } } From ac50fb70c67ce4cb265c59ecb9855197e912d45d Mon Sep 17 00:00:00 2001 From: Equbuxu Date: Sun, 3 Dec 2023 22:23:18 +0300 Subject: [PATCH 06/24] Fix main canvas not updating on launch --- src/PixiEditor/Models/Rendering/CanvasUpdater.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PixiEditor/Models/Rendering/CanvasUpdater.cs b/src/PixiEditor/Models/Rendering/CanvasUpdater.cs index 74ef464a8..3a9839143 100644 --- a/src/PixiEditor/Models/Rendering/CanvasUpdater.cs +++ b/src/PixiEditor/Models/Rendering/CanvasUpdater.cs @@ -147,6 +147,8 @@ private List Render(AffectedAreasGatherer chunkGatherer, bool reren break; } } + + UpdateAffectedNonRerenderedChunks(chunksToRerender, chunkGatherer.MainImageArea); bool anythingToUpdate = false; foreach (var (_, chunks) in chunksToRerender) @@ -156,8 +158,6 @@ private List Render(AffectedAreasGatherer chunkGatherer, bool reren if (!anythingToUpdate) return new(); - UpdateAffectedNonRerenderedChunks(chunksToRerender, chunkGatherer.MainImageArea); - List infos = new(); UpdateMainImage(chunksToRerender, updatingStoredChunks ? null : chunkGatherer.MainImageArea.GlobalArea.Value, infos); return infos; From 73ecd2738d9039e99f9be9e4379c51fe9b84f9e0 Mon Sep 17 00:00:00 2001 From: CPKreuz Date: Thu, 14 Dec 2023 00:07:36 +0100 Subject: [PATCH 07/24] Bit of previous refactoring --- .../Models/DataHolders/CrashReport.cs | 69 ++++++++++++------- src/PixiEditor/Views/MainWindow.xaml.cs | 39 +++++++---- 2 files changed, 70 insertions(+), 38 deletions(-) diff --git a/src/PixiEditor/Models/DataHolders/CrashReport.cs b/src/PixiEditor/Models/DataHolders/CrashReport.cs index 196abcf89..cc065641a 100644 --- a/src/PixiEditor/Models/DataHolders/CrashReport.cs +++ b/src/PixiEditor/Models/DataHolders/CrashReport.cs @@ -91,40 +91,46 @@ public static CrashReport Parse(string path) public int GetDocumentCount() => ZipFile.Entries.Where(x => x.FullName.EndsWith(".pixi")).Count(); - public List<(CrashFilePathInfo originalPath, byte[] dotPixiBytes)> RecoverDocuments() + public bool TryRecoverDocuments(out List list) { - // Load .pixi files - Dictionary recoveredDocuments = new(); - foreach (ZipArchiveEntry entry in ZipFile.Entries.Where(x => x.FullName.EndsWith(".pixi"))) + try { - using Stream stream = entry.Open(); - using MemoryStream memStream = new(); - stream.CopyTo(memStream); - recoveredDocuments.Add(entry.FullName["Documents/".Length..], memStream.ToArray()); + list = RecoverDocuments(); + } + catch (Exception e) + { + list = null; + CrashHelper.SendExceptionInfoToWebhook(e); + return false; } + return true; + } + + public List RecoverDocuments() + { var originalPathsEntry = ZipFile.Entries.First(entry => entry.FullName == "DocumentInfo.json"); - + // Load original paths - Dictionary originalPaths; + Dictionary paths; { using Stream stream = originalPathsEntry.Open(); using StreamReader reader = new(stream); string json = reader.ReadToEnd(); - originalPaths = JsonConvert.DeserializeObject>(json); + paths = JsonConvert.DeserializeObject>(json); } - var list = new List<(CrashFilePathInfo originalPath, byte[] dotPixiBytes)>(); - - foreach (var document in recoveredDocuments) + // Load .pixi files + List recoveredDocuments = new(); + foreach (var path in paths) { - var originalPath = originalPaths[document.Key]; - list.Add((originalPath, document.Value)); + recoveredDocuments.Add(new RecoveredPixi(path.Value, ZipFile.GetEntry($"Documents/{path.Key}"))); } - return list; + return recoveredDocuments; } + public void Dispose() { ZipFile.Dispose(); @@ -172,8 +178,8 @@ public void Save() // Write the documents into zip int counter = 0; - Dictionary originalPaths = new(); - foreach (DocumentViewModel document in vm.DocumentManagerSubViewModel.Documents) + var originalPaths = new Dictionary(); + foreach (var document in vm.DocumentManagerSubViewModel.Documents) { try { @@ -187,7 +193,7 @@ public void Save() using Stream documentStream = archive.CreateEntry($"Documents/{nameInZip}").Open(); documentStream.Write(serialized); - originalPaths.Add(nameInZip, new CrashFilePathInfo(document.FullFilePath, null)); + originalPaths.Add(nameInZip, document.FullFilePath); } catch { } counter++; @@ -214,10 +220,27 @@ private void ExtractReport() ReportText = Encoding.UTF8.GetString(encodedReport); } - internal class CrashReportUserMessage + + public class RecoveredPixi { - public string Message { get; set; } + public string? Path { get; } + + public ZipArchiveEntry RecoveredEntry { get; } + + public byte[] GetRecoveredBytes() + { + var buffer = new byte[RecoveredEntry.Length]; + using var stream = RecoveredEntry.Open(); - public string Mail { get; set; } + stream.ReadExactly(buffer); + + return buffer; + } + + public RecoveredPixi(string? path, ZipArchiveEntry recoveredEntry) + { + Path = path; + RecoveredEntry = recoveredEntry; + } } } diff --git a/src/PixiEditor/Views/MainWindow.xaml.cs b/src/PixiEditor/Views/MainWindow.xaml.cs index c99bde670..59c4a114c 100644 --- a/src/PixiEditor/Views/MainWindow.xaml.cs +++ b/src/PixiEditor/Views/MainWindow.xaml.cs @@ -109,31 +109,26 @@ private void MainWindow_ContentRendered(object sender, EventArgs e) public static MainWindow CreateWithRecoveredDocuments(CrashReport report, out bool showMissingFilesDialog) { - var app = (App)Application.Current; - MainWindow window = new(app.InitApp()); - FileViewModel fileVM = window.services.GetRequiredService(); - var documents = report.RecoverDocuments(); + var window = GetMainWindow(); + var fileVM = window.services.GetRequiredService(); + + if (!report.TryRecoverDocuments(out var documents)) + { + showMissingFilesDialog = true; + return window; + } var i = 0; - foreach (var (path, bytes) in documents) + foreach (var document in documents) { try { - fileVM.OpenRecoveredDotPixi(path.OriginalPath, bytes); + fileVM.OpenRecoveredDotPixi(document.Path, document.GetRecoveredBytes()); i++; } catch (Exception e) { - try - { - fileVM.OpenFromPath(path.AutosavePath, false); - } - catch (Exception deepE) - { - CrashHelper.SendExceptionInfoToWebhook(deepE); - } - CrashHelper.SendExceptionInfoToWebhook(e); } } @@ -141,6 +136,20 @@ public static MainWindow CreateWithRecoveredDocuments(CrashReport report, out bo showMissingFilesDialog = documents.Count != i; return window; + + MainWindow GetMainWindow() + { + try + { + var app = (App)Application.Current; + return new MainWindow(app.InitApp()); + } + catch (Exception e) + { + Task.Run(() => CrashHelper.SendExceptionInfoToWebhook(e)).Wait(); + throw; + } + } } /// Brings main window to foreground. From 06347551382570ebcb65073bc34f21f020f9a8f8 Mon Sep 17 00:00:00 2001 From: CPKreuz Date: Thu, 14 Dec 2023 00:24:51 +0100 Subject: [PATCH 08/24] Clarified SendExceptionInfoToWebhook is async --- src/PixiEditor/App.xaml.cs | 2 +- src/PixiEditor/Helpers/CrashHelper.cs | 16 +++++++++++++--- .../Models/AppExtensions/ExtensionLoader.cs | 4 ++-- .../Models/DataHolders/CrashFilePathInfo.cs | 14 -------------- .../ViewModels/CrashReportViewModel.cs | 2 +- .../SubViewModels/Main/MiscViewModel.cs | 4 ++-- .../SubViewModels/Main/UpdateViewModel.cs | 2 +- .../Views/Dialogs/HelloTherePopup.xaml.cs | 2 +- src/PixiEditor/Views/MainWindow.xaml.cs | 2 +- 9 files changed, 22 insertions(+), 26 deletions(-) delete mode 100644 src/PixiEditor/Models/DataHolders/CrashFilePathInfo.cs diff --git a/src/PixiEditor/App.xaml.cs b/src/PixiEditor/App.xaml.cs index 67b8592f1..2d4a90a68 100644 --- a/src/PixiEditor/App.xaml.cs +++ b/src/PixiEditor/App.xaml.cs @@ -53,7 +53,7 @@ protected override void OnStartup(StartupEventArgs e) { try { - CrashHelper.SendExceptionInfoToWebhook(exception); + CrashHelper.SendExceptionInfoToWebhook(exception, true); } finally { diff --git a/src/PixiEditor/Helpers/CrashHelper.cs b/src/PixiEditor/Helpers/CrashHelper.cs index a2289ba36..644aeb171 100644 --- a/src/PixiEditor/Helpers/CrashHelper.cs +++ b/src/PixiEditor/Helpers/CrashHelper.cs @@ -112,14 +112,24 @@ public static void AddExceptionMessage(StringBuilder builder, Exception e) } } - public static async Task SendExceptionInfoToWebhook(Exception e, [CallerFilePath] string filePath = "", [CallerMemberName] string memberName = "") + public static void SendExceptionInfoToWebhook(Exception e, bool wait = false, + [CallerFilePath] string filePath = "", [CallerMemberName] string memberName = "") + { + var task = Task.Run(() => SendExceptionInfoToWebhookAsync(e, filePath, memberName)); + if (wait) + { + task.Wait(); + } + } + + public static async Task SendExceptionInfoToWebhookAsync(Exception e, [CallerFilePath] string filePath = "", [CallerMemberName] string memberName = "") { if (DebugViewModel.IsDebugBuild) return; - await SendReportTextToWebhook(CrashReport.Generate(e), $"{filePath}; Method {memberName}"); + await SendReportTextToWebhookAsync(CrashReport.Generate(e), $"{filePath}; Method {memberName}"); } - public static async Task SendReportTextToWebhook(CrashReport report, string catchLocation = null) + public static async Task SendReportTextToWebhookAsync(CrashReport report, string catchLocation = null) { string reportText = report.ReportText; if (catchLocation is not null) diff --git a/src/PixiEditor/Models/AppExtensions/ExtensionLoader.cs b/src/PixiEditor/Models/AppExtensions/ExtensionLoader.cs index 1534f50d2..ec7afc095 100644 --- a/src/PixiEditor/Models/AppExtensions/ExtensionLoader.cs +++ b/src/PixiEditor/Models/AppExtensions/ExtensionLoader.cs @@ -49,7 +49,7 @@ public void InitializeExtensions(ExtensionServices pixiEditorApi) } catch (Exception ex) { - Task.Run(async () => await CrashHelper.SendExceptionInfoToWebhook(ex)); + CrashHelper.SendExceptionInfoToWebhook(ex); } } @@ -86,7 +86,7 @@ private void LoadExtension(string packageJsonPath) catch (Exception ex) { //MessageBox.Show(new LocalizedString("ERROR_LOADING_PACKAGE", packageJsonPath), "ERROR"); - Task.Run(async () => await CrashHelper.SendExceptionInfoToWebhook(ex)); + CrashHelper.SendExceptionInfoToWebhook(ex); } } diff --git a/src/PixiEditor/Models/DataHolders/CrashFilePathInfo.cs b/src/PixiEditor/Models/DataHolders/CrashFilePathInfo.cs deleted file mode 100644 index 078323c58..000000000 --- a/src/PixiEditor/Models/DataHolders/CrashFilePathInfo.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace PixiEditor.Models.DataHolders; - -public class CrashFilePathInfo -{ - public string? OriginalPath { get; set; } - - public string? AutosavePath { get; set; } - - public CrashFilePathInfo(string originalPath, string autosavePath) - { - OriginalPath = originalPath; - AutosavePath = autosavePath; - } -} diff --git a/src/PixiEditor/ViewModels/CrashReportViewModel.cs b/src/PixiEditor/ViewModels/CrashReportViewModel.cs index ca61ef68b..9c4f23d5f 100644 --- a/src/PixiEditor/ViewModels/CrashReportViewModel.cs +++ b/src/PixiEditor/ViewModels/CrashReportViewModel.cs @@ -43,7 +43,7 @@ public CrashReportViewModel(CrashReport report) AttachDebuggerCommand = new(AttachDebugger); if (!IsDebugBuild) - _ = CrashHelper.SendReportTextToWebhook(report); + _ = CrashHelper.SendReportTextToWebhookAsync(report); } public void RecoverDocuments(object args) diff --git a/src/PixiEditor/ViewModels/SubViewModels/Main/MiscViewModel.cs b/src/PixiEditor/ViewModels/SubViewModels/Main/MiscViewModel.cs index eb57ac683..f86a555ef 100644 --- a/src/PixiEditor/ViewModels/SubViewModels/Main/MiscViewModel.cs +++ b/src/PixiEditor/ViewModels/SubViewModels/Main/MiscViewModel.cs @@ -20,7 +20,7 @@ public MiscViewModel(ViewModelMain owner) [Command.Basic("PixiEditor.Links.OpenRepository", "https://github.com/PixiEditor/PixiEditor", "REPOSITORY", "OPEN_REPOSITORY", IconPath = "Globe.png")] [Command.Basic("PixiEditor.Links.OpenLicense", "https://github.com/PixiEditor/PixiEditor/blob/master/LICENSE", "LICENSE", "OPEN_LICENSE", IconPath = "Globe.png")] [Command.Basic("PixiEditor.Links.OpenOtherLicenses", "https://pixieditor.net/docs/Third-party-licenses", "THIRD_PARTY_LICENSES", "OPEN_THIRD_PARTY_LICENSES", IconPath = "Globe.png")] - public static async Task OpenHyperlink(string url) + public static void OpenHyperlink(string url) { try { @@ -28,8 +28,8 @@ public static async Task OpenHyperlink(string url) } catch (Exception e) { + CrashHelper.SendExceptionInfoToWebhook(e); NoticeDialog.Show(title: "Error", message: $"Couldn't open the address {url} in your default browser"); - await CrashHelper.SendExceptionInfoToWebhook(e); } } } diff --git a/src/PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs b/src/PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs index 25e872cef..b689f2436 100644 --- a/src/PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs +++ b/src/PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs @@ -222,7 +222,7 @@ private async void ConditionalUPDATE() } catch (Exception e) { - CrashHelper.SendExceptionInfoToWebhook(e); + CrashHelper.SendExceptionInfoToWebhookAsync(e); NoticeDialog.Show("COULD_NOT_CHECK_FOR_UPDATES", "UPDATE_CHECK_FAILED"); } diff --git a/src/PixiEditor/Views/Dialogs/HelloTherePopup.xaml.cs b/src/PixiEditor/Views/Dialogs/HelloTherePopup.xaml.cs index 27159473f..8ec0bd84f 100644 --- a/src/PixiEditor/Views/Dialogs/HelloTherePopup.xaml.cs +++ b/src/PixiEditor/Views/Dialogs/HelloTherePopup.xaml.cs @@ -238,7 +238,7 @@ private async void HelloTherePopup_OnLoaded(object sender, RoutedEventArgs e) { IsFetchingNews = false; FailedFetchingNews = true; - await CrashHelper.SendExceptionInfoToWebhook(ex); + await CrashHelper.SendExceptionInfoToWebhookAsync(ex); } } } diff --git a/src/PixiEditor/Views/MainWindow.xaml.cs b/src/PixiEditor/Views/MainWindow.xaml.cs index 59c4a114c..628150224 100644 --- a/src/PixiEditor/Views/MainWindow.xaml.cs +++ b/src/PixiEditor/Views/MainWindow.xaml.cs @@ -146,7 +146,7 @@ MainWindow GetMainWindow() } catch (Exception e) { - Task.Run(() => CrashHelper.SendExceptionInfoToWebhook(e)).Wait(); + CrashHelper.SendExceptionInfoToWebhook(e, true); throw; } } From 0334c81c1d028b4820b522540f1d8a6bcfb3a2fc Mon Sep 17 00:00:00 2001 From: CPKreuz Date: Thu, 14 Dec 2023 01:20:28 +0100 Subject: [PATCH 09/24] Added Command Log and State information back in --- .../Models/Commands/CommandController.cs | 3 + .../Models/Commands/CommandLog/CommandLog.cs | 43 ++++ .../Commands/CommandLog/CommandLogEntry.cs | 19 ++ .../Models/Commands/CommandMethods.cs | 19 +- .../Models/DataHolders/CrashReport.cs | 198 +++++++++++++++++- .../DocumentTransformViewModel.cs | 7 +- 6 files changed, 275 insertions(+), 14 deletions(-) create mode 100644 src/PixiEditor/Models/Commands/CommandLog/CommandLog.cs create mode 100644 src/PixiEditor/Models/Commands/CommandLog/CommandLogEntry.cs diff --git a/src/PixiEditor/Models/Commands/CommandController.cs b/src/PixiEditor/Models/Commands/CommandController.cs index b57e62f6a..27e1058b2 100644 --- a/src/PixiEditor/Models/Commands/CommandController.cs +++ b/src/PixiEditor/Models/Commands/CommandController.cs @@ -25,6 +25,8 @@ internal class CommandController public CommandCollection Commands { get; } public List CommandGroups { get; } + + public CommandLog.CommandLog Log { get; } public OneToManyDictionary FilterCommands { get; } @@ -37,6 +39,7 @@ internal class CommandController public CommandController() { Current ??= this; + Log = new CommandLog.CommandLog(); ShortcutsPath = Path.Join( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), diff --git a/src/PixiEditor/Models/Commands/CommandLog/CommandLog.cs b/src/PixiEditor/Models/Commands/CommandLog/CommandLog.cs new file mode 100644 index 000000000..24c5a9800 --- /dev/null +++ b/src/PixiEditor/Models/Commands/CommandLog/CommandLog.cs @@ -0,0 +1,43 @@ +using System.Globalization; +using System.Text; +using PixiEditor.Models.Commands.Commands; + +namespace PixiEditor.Models.Commands.CommandLog; + +internal class CommandLog +{ + private readonly List list = new(MaxEntries); + + private const int MaxEntries = 8; + + public void Log(Command command, bool? canExecute) + { + if (canExecute.HasValue && !list[0].CanExecute.HasValue) + { + list[0].CanExecute = canExecute; + return; + } + + if (list.Count >= MaxEntries) + { + list.RemoveRange(MaxEntries - 1, list.Count - MaxEntries + 1); + } + + list.Insert(0, new CommandLogEntry(command, canExecute, DateTime.Now)); + } + + public string GetSummary(DateTime relativeTime) + { + var builder = new StringBuilder(); + + foreach (var entry in list) + { + var relativeSpan = entry.DateTime - relativeTime; + string canExecute = entry.CanExecute.HasValue ? entry.CanExecute.ToString() : "not executed"; + + builder.AppendLine($"{entry.Command.InternalName} | CanExecute: {canExecute} | {relativeSpan.TotalSeconds.ToString("F3", CultureInfo.InvariantCulture)}s ago | {entry.DateTime.ToString("O", CultureInfo.InvariantCulture)}"); + } + + return builder.ToString(); + } +} diff --git a/src/PixiEditor/Models/Commands/CommandLog/CommandLogEntry.cs b/src/PixiEditor/Models/Commands/CommandLog/CommandLogEntry.cs new file mode 100644 index 000000000..80d564aff --- /dev/null +++ b/src/PixiEditor/Models/Commands/CommandLog/CommandLogEntry.cs @@ -0,0 +1,19 @@ +using PixiEditor.Models.Commands.Commands; + +namespace PixiEditor.Models.Commands.CommandLog; + +internal class CommandLogEntry +{ + public Command Command { get; } + + public bool? CanExecute { get; set; } + + public DateTime DateTime { get; } + + public CommandLogEntry(Command command, bool? commandMethod, DateTime dateTime) + { + Command = command; + CanExecute = commandMethod; + DateTime = dateTime; + } +} diff --git a/src/PixiEditor/Models/Commands/CommandMethods.cs b/src/PixiEditor/Models/Commands/CommandMethods.cs index 5a70f29a4..8adb3d94d 100644 --- a/src/PixiEditor/Models/Commands/CommandMethods.cs +++ b/src/PixiEditor/Models/Commands/CommandMethods.cs @@ -18,11 +18,26 @@ public CommandMethods(Command command, Action execute, CanExecuteEvaluat public void Execute(object parameter) { - if (CanExecute(parameter)) + var log = CommandController.Current?.Log; + ToLog(log, null); + + if (!CanExecute(parameter)) { - _execute(parameter); + ToLog(log, false); + return; } + ToLog(log, true); + + _execute(parameter); } public bool CanExecute(object parameter) => _canExecute.CallEvaluate(_command, parameter); + + private void ToLog(CommandLog.CommandLog? log, bool? canExecute) + { + if (log != null && _command != null) + { + log.Log(_command, canExecute); + } + } } diff --git a/src/PixiEditor/Models/DataHolders/CrashReport.cs b/src/PixiEditor/Models/DataHolders/CrashReport.cs index cc065641a..20be176b8 100644 --- a/src/PixiEditor/Models/DataHolders/CrashReport.cs +++ b/src/PixiEditor/Models/DataHolders/CrashReport.cs @@ -4,10 +4,16 @@ using System.IO.Compression; using System.Reflection; using System.Text; +using System.Windows.Input; using Newtonsoft.Json; +using PixiEditor.Extensions.Common.Localization; +using PixiEditor.Extensions.Common.UserPreferences; using PixiEditor.Helpers; +using PixiEditor.Models.Commands; +using PixiEditor.Models.Enums; using PixiEditor.Parser; using PixiEditor.ViewModels.SubViewModels.Document; +using PixiEditor.Views; namespace PixiEditor.Models.DataHolders; @@ -21,14 +27,59 @@ public static CrashReport Generate(Exception exception) builder .AppendLine($"PixiEditor {VersionHelpers.GetCurrentAssemblyVersionString(moreSpecific: true)} x{IntPtr.Size * 8} crashed on {currentTime:yyyy.MM.dd} at {currentTime:HH:mm:ss} {currentTime:zzz}") + .AppendLine($"Application started {GetFormatted(() => Process.GetCurrentProcess().StartTime, "yyyy.MM.dd HH:hh:ss")}, {GetFormatted(() => DateTime.Now - Process.GetCurrentProcess().StartTime, @"d\ hh\:mm\.ss")} ago") .AppendLine($"Report: {Guid.NewGuid()}\n") .AppendLine("-----System Information----") .AppendLine("General:") .AppendLine($" OS: {Environment.OSVersion.VersionString}") + .AppendLine($" Has Stylus Tablet Device: {GetFormatted(() => HasTabletDevice(TabletDeviceType.Stylus))}") + .AppendLine($" Has Touch Tablet Device: {GetFormatted(() => HasTabletDevice(TabletDeviceType.Touch))}") .AppendLine(); CrashHelper helper = new(); + AppendHardwareInfo(helper, builder); + + builder.AppendLine("\n--------Command Log--------\n"); + + try + { + builder.Append(CommandController.Current.Log.GetSummary(currentTime.LocalDateTime)); + } + catch (Exception cemLogException) + { + builder.AppendLine($"Error ({cemLogException.GetType().FullName}: {cemLogException.Message}) while gathering command log, skipping..."); + } + + builder.AppendLine("\n-----------State-----------"); + + try + { + AppendStateInfo(builder); + } + catch (Exception stateException) + { + builder.AppendLine($"Error ({stateException.GetType().FullName}: {stateException.Message}) while gathering state (Must be bug in GetPreferenceFormatted, GetFormatted or StringBuilder.AppendLine as these should not throw), skipping..."); + } + + CrashHelper.AddExceptionMessage(builder, exception); + + string filename = $"crash-{currentTime:yyyy-MM-dd_HH-mm-ss_fff}.zip"; + string path = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "PixiEditor", + "crash_logs"); + Directory.CreateDirectory(path); + + CrashReport report = new(); + report.FilePath = Path.Combine(path, filename); + report.ReportText = builder.ToString(); + + return report; + } + + private static void AppendHardwareInfo(CrashHelper helper, StringBuilder builder) + { try { helper.GetCPUInformation(builder); @@ -55,21 +106,146 @@ public static CrashReport Generate(Exception exception) { builder.AppendLine($"Error ({memE.GetType().FullName}: {memE.Message}) while gathering memory information, skipping..."); } + } - CrashHelper.AddExceptionMessage(builder, exception); + private static void AppendStateInfo(StringBuilder builder) + { + builder + .AppendLine("Environment:") + .AppendLine($" Thread Count: {GetFormatted(() => Process.GetCurrentProcess().Threads.Count)}") + .AppendLine("\nCulture:") + .AppendLine($" Selected language: {GetPreferenceFormatted("LanguageCode", true, "system")}") + .AppendLine($" Current Culture: {GetFormatted(() => CultureInfo.CurrentCulture)}") + .AppendLine($" Current UI Culture: {GetFormatted(() => CultureInfo.CurrentUICulture)}") + .AppendLine("\nPreferences:") + .AppendLine($" Has shared toolbar enabled: {GetPreferenceFormatted("EnableSharedToolbar", true, false)}") + .AppendLine($" Right click mode: {GetPreferenceFormatted("RightClickMode", true)}") + .AppendLine($" Has Rich presence enabled: {GetPreferenceFormatted("EnableRichPresence", true, true)}") + .AppendLine($" Debug Mode enabled: {GetPreferenceFormatted("IsDebugModeEnabled", true, false)}") + .AppendLine("\nUI:") + .AppendLine($" MainWindow not null: {GetFormatted(() => MainWindow.Current != null)}") + .AppendLine($" MainWindow Size: {GetFormatted(() => MainWindow.Current.RenderSize)}") + .AppendLine($" MainWindow State: {GetFormatted(() => MainWindow.Current.WindowState)}") + .AppendLine("\nViewModels:") + .AppendLine($" Has active updateable change: {GetFormatted(() => ViewModelMain.Current?.DocumentManagerSubViewModel?.ActiveDocument?.UpdateableChangeActive)}") + .AppendLine($" Current Tool: {GetFormattedFromViewModelMain(x => x.ToolsSubViewModel?.ActiveTool?.ToolName)}") + .AppendLine($" Primary Color: {GetFormattedFromViewModelMain(x => x.ColorsSubViewModel?.PrimaryColor)}") + .AppendLine($" Secondary Color: {GetFormattedFromViewModelMain(x => x.ColorsSubViewModel?.SecondaryColor)}") + .Append("\nActive Document: "); - string filename = $"crash-{currentTime:yyyy-MM-dd_HH-mm-ss_fff}.zip"; - string path = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "PixiEditor", - "crash_logs"); - Directory.CreateDirectory(path); + try + { + AppendActiveDocumentInfo(builder); + } + catch (Exception e) + { + builder.AppendLine($"Could not get active document info:\n{e}"); + } + } - CrashReport report = new(); - report.FilePath = Path.Combine(path, filename); - report.ReportText = builder.ToString(); + private static void AppendActiveDocumentInfo(StringBuilder builder) + { + var main = ViewModelMain.Current; + + if (main == null) + { + builder.AppendLine("{ ViewModelMain.Current is null }"); + return; + } - return report; + var manager = main.DocumentManagerSubViewModel; + + if (manager == null) + { + builder.AppendLine("{ DocumentManagerSubViewModel is null }"); + return; + } + + var document = manager.ActiveDocument; + + if (document == null) + { + builder.AppendLine("null"); + return; + } + + builder + .AppendLine() + .AppendLine($" Size: {document.SizeBindable}") + .AppendLine($" Layer Count: {FormatObject(document.StructureHelper.GetAllLayers().Count)}") + .AppendLine($" Has all changes saved: {document.AllChangesSaved}") + .AppendLine($" Horizontal Symmetry Enabled: {document.HorizontalSymmetryAxisEnabledBindable}") + .AppendLine($" Horizontal Symmetry Value: {FormatObject(document.HorizontalSymmetryAxisYBindable)}") + .AppendLine($" Vertical Symmetry Enabled: {document.VerticalSymmetryAxisEnabledBindable}") + .AppendLine($" Vertical Symmetry Value: {FormatObject(document.VerticalSymmetryAxisXBindable)}") + .AppendLine($" Updateable Change Active: {FormatObject(document.UpdateableChangeActive)}") + .AppendLine($" Transform: {FormatObject(document.TransformViewModel)}"); + } + + private static bool HasTabletDevice(TabletDeviceType type) => Tablet.TabletDevices.Cast().Any(tabletDevice => tabletDevice.Type == type); + + private static string GetPreferenceFormatted(string name, bool roaming, T defaultValue = default, string? format = null) + { + try + { + var preferences = IPreferences.Current; + + if (preferences == null) + return "{ Preferences are null }"; + + var value = roaming + ? preferences.GetPreference(name, defaultValue) + : preferences.GetLocalPreference(name, defaultValue); + + return FormatObject(value, format); + } + catch (Exception e) + { + return $$"""{ Failed getting preference: {{e.Message}} }"""; + } + } + + private static string GetFormattedFromViewModelMain(Func getter, string? format = null) + { + var main = ViewModelMain.Current; + + if (main == null) + return "{ ViewModelMain.Current is null }"; + + return GetFormatted(() => getter(main), format); + } + + private static string GetFormatted(Func getter, string? format = null) + { + try + { + var value = getter(); + + return FormatObject(value, format); + } + catch (Exception e) + { + return $$"""{ Failed retrieving: {{e.Message}} }"""; + } + } + + private static string FormatObject(T? value, string? format = null) + { + return value switch + { + null => "null", + IFormattable formattable => formattable.ToString(format, CultureInfo.InvariantCulture), + LocalizedString localizedS => FormatLocalizedString(localizedS), + string s => $"\"{s}\"", + _ => value.ToString() + }; + + string FormatLocalizedString(LocalizedString localizedS) + { + return localizedS.Parameters != null + ? $"{localizedS.Key} @({string.Join(", ", localizedS.Parameters.Select(x => FormatObject(x, format)))})" + : localizedS.Key; + } } public static CrashReport Parse(string path) diff --git a/src/PixiEditor/ViewModels/SubViewModels/Document/TransformOverlays/DocumentTransformViewModel.cs b/src/PixiEditor/ViewModels/SubViewModels/Document/TransformOverlays/DocumentTransformViewModel.cs index 43debd442..e1f0d608f 100644 --- a/src/PixiEditor/ViewModels/SubViewModels/Document/TransformOverlays/DocumentTransformViewModel.cs +++ b/src/PixiEditor/ViewModels/SubViewModels/Document/TransformOverlays/DocumentTransformViewModel.cs @@ -1,4 +1,6 @@ -using System.Windows.Input; +using System.Diagnostics; +using System.Text; +using System.Windows.Input; using ChunkyImageLib.DataHolders; using PixiEditor.DrawingApi.Core.Numerics; using PixiEditor.Extensions.Common.Localization; @@ -10,6 +12,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Document.TransformOverlays; #nullable enable +[DebuggerDisplay("{ToString(),nq}")] internal class DocumentTransformViewModel : NotifyableObject { private DocumentViewModel document; @@ -246,4 +249,6 @@ public void ModifierKeysInlet(bool isShiftDown, bool isCtrlDown, bool isAltDown) break; } } + + public override string ToString() => !TransformActive ? "Not active" : $"Transform Mode: {activeTransformMode}; Corner Freedom: {CornerFreedom}; Side Freedom: {SideFreedom}"; } From 8924a45cfa26c38d301ef389c3f7c9bbd35d95c9 Mon Sep 17 00:00:00 2001 From: CPKreuz Date: Thu, 14 Dec 2023 01:34:02 +0100 Subject: [PATCH 10/24] Try recovering all .pixi files if DocumentInfo.json can't be parsed --- .../Models/DataHolders/CrashReport.cs | 46 +++++++++++++------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/src/PixiEditor/Models/DataHolders/CrashReport.cs b/src/PixiEditor/Models/DataHolders/CrashReport.cs index 20be176b8..61f0ba928 100644 --- a/src/PixiEditor/Models/DataHolders/CrashReport.cs +++ b/src/PixiEditor/Models/DataHolders/CrashReport.cs @@ -285,25 +285,45 @@ public bool TryRecoverDocuments(out List list) public List RecoverDocuments() { - var originalPathsEntry = ZipFile.Entries.First(entry => entry.FullName == "DocumentInfo.json"); + List recoveredDocuments = new(); - // Load original paths - Dictionary paths; + var paths = TryGetOriginalPaths(); + if (paths == null) { - using Stream stream = originalPathsEntry.Open(); - using StreamReader reader = new(stream); - string json = reader.ReadToEnd(); - paths = JsonConvert.DeserializeObject>(json); - } + recoveredDocuments.AddRange( + ZipFile.Entries + .Where(x => + x.FullName.StartsWith("Documents") && + x.FullName.EndsWith(".pixi")) + .Select(entry => new RecoveredPixi(null, entry))); - // Load .pixi files - List recoveredDocuments = new(); - foreach (var path in paths) - { - recoveredDocuments.Add(new RecoveredPixi(path.Value, ZipFile.GetEntry($"Documents/{path.Key}"))); + return recoveredDocuments; } + recoveredDocuments.AddRange(paths.Select(path => new RecoveredPixi(path.Value, ZipFile.GetEntry($"Documents/{path.Key}")))); + return recoveredDocuments; + + Dictionary? TryGetOriginalPaths() + { + var originalPathsEntry = ZipFile.Entries.FirstOrDefault(entry => entry.FullName == "DocumentInfo.json"); + + if (originalPathsEntry == null) + return null; + + try + { + using var stream = originalPathsEntry.Open(); + using var reader = new StreamReader(stream); + string json = reader.ReadToEnd(); + + return JsonConvert.DeserializeObject>(json); + } + catch + { + return null; + } + } } From a617360efa1204236179535ef5c8cf423c1851fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Krysi=C5=84ski?= Date: Thu, 14 Dec 2023 11:02:18 +0100 Subject: [PATCH 11/24] 1.2.1.3 --- src/PixiEditor/Properties/AssemblyInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PixiEditor/Properties/AssemblyInfo.cs b/src/PixiEditor/Properties/AssemblyInfo.cs index dc30994f1..1f16d6126 100644 --- a/src/PixiEditor/Properties/AssemblyInfo.cs +++ b/src/PixiEditor/Properties/AssemblyInfo.cs @@ -50,5 +50,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.1.2")] -[assembly: AssemblyFileVersion("1.2.1.2")] +[assembly: AssemblyVersion("1.2.1.3")] +[assembly: AssemblyFileVersion("1.2.1.3")] From d15d818e1ff820dc5f6de50c08daff28594de49b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Krysi=C5=84ski?= Date: Thu, 14 Dec 2023 14:13:09 +0100 Subject: [PATCH 12/24] Removed debug fella --- .../Changes/Drawing/SelectionToMask_Change.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/PixiEditor.ChangeableDocument/Changes/Drawing/SelectionToMask_Change.cs b/src/PixiEditor.ChangeableDocument/Changes/Drawing/SelectionToMask_Change.cs index 33ac2cdd0..f23e324b1 100644 --- a/src/PixiEditor.ChangeableDocument/Changes/Drawing/SelectionToMask_Change.cs +++ b/src/PixiEditor.ChangeableDocument/Changes/Drawing/SelectionToMask_Change.cs @@ -48,8 +48,6 @@ public override bool InitializeAndValidate(Document target) image.SetBlendMode(blendMode); var selectionImage = FloodFillHelper.FillSelection(target, selection!); - - selectionImage.SaveToDesktop(); image.EnqueueDrawImage(new VecI(0, 0), selectionImage); From 27c6850d66d7338a6fce9b53ccf702558568d097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Krysi=C5=84ski?= Date: Thu, 14 Dec 2023 14:16:23 +0100 Subject: [PATCH 13/24] Conditional SaveToDesktop --- src/ChunkyImageLib/Surface.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ChunkyImageLib/Surface.cs b/src/ChunkyImageLib/Surface.cs index 224076f2a..3e34f1598 100644 --- a/src/ChunkyImageLib/Surface.cs +++ b/src/ChunkyImageLib/Surface.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using PixiEditor.DrawingApi.Core.ColorsImpl; using PixiEditor.DrawingApi.Core.Numerics; @@ -131,6 +132,7 @@ public unsafe bool IsFullyTransparent() return true; } + [Conditional("DEBUG")] public void SaveToDesktop(string filename = "savedSurface.png") { using var final = DrawingSurface.Create(new ImageInfo(Size.X, Size.Y, ColorType.Rgba8888, AlphaType.Premul, ColorSpace.CreateSrgb())); From e4bfaa02bf065ba58890d0624328847da83c3412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Krysi=C5=84ski?= Date: Thu, 14 Dec 2023 14:19:28 +0100 Subject: [PATCH 14/24] Ifdef savetodesktop --- src/ChunkyImageLib/Surface.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ChunkyImageLib/Surface.cs b/src/ChunkyImageLib/Surface.cs index 3e34f1598..f68809b19 100644 --- a/src/ChunkyImageLib/Surface.cs +++ b/src/ChunkyImageLib/Surface.cs @@ -132,7 +132,7 @@ public unsafe bool IsFullyTransparent() return true; } - [Conditional("DEBUG")] +#if DEBUG public void SaveToDesktop(string filename = "savedSurface.png") { using var final = DrawingSurface.Create(new ImageInfo(Size.X, Size.Y, ColorType.Rgba8888, AlphaType.Premul, ColorSpace.CreateSrgb())); @@ -144,6 +144,7 @@ public void SaveToDesktop(string filename = "savedSurface.png") png.SaveTo(stream); } } +#endif private DrawingSurface CreateDrawingSurface() { From c86ec75ddb5cf7cd0c954e46fa92dc89c7a20329 Mon Sep 17 00:00:00 2001 From: Equbuxu Date: Fri, 15 Dec 2023 06:20:34 +0300 Subject: [PATCH 15/24] Fix PeekPixels memory leak --- src/PixiEditor.DrawingApi.Core/Surface/Pixmap.cs | 5 +++++ .../Implementations/SkiaPixmapImplementation.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/PixiEditor.DrawingApi.Core/Surface/Pixmap.cs b/src/PixiEditor.DrawingApi.Core/Surface/Pixmap.cs index 91cbcd8fa..2306a718b 100644 --- a/src/PixiEditor.DrawingApi.Core/Surface/Pixmap.cs +++ b/src/PixiEditor.DrawingApi.Core/Surface/Pixmap.cs @@ -9,6 +9,11 @@ public class Pixmap : NativeObject internal Pixmap(IntPtr objPtr) : base(objPtr) { } + + public static Pixmap InternalCreateFromExistingPointer(IntPtr objPointer) + { + return new Pixmap(objPointer); + } public Pixmap(ImageInfo imgInfo, IntPtr dataPtr) : base(dataPtr) { diff --git a/src/PixiEditor.DrawingApi.Skia/Implementations/SkiaPixmapImplementation.cs b/src/PixiEditor.DrawingApi.Skia/Implementations/SkiaPixmapImplementation.cs index 471beeca7..293e31476 100644 --- a/src/PixiEditor.DrawingApi.Skia/Implementations/SkiaPixmapImplementation.cs +++ b/src/PixiEditor.DrawingApi.Skia/Implementations/SkiaPixmapImplementation.cs @@ -57,7 +57,7 @@ public int GetBytesSize(Pixmap pixmap) public Pixmap CreateFrom(SKPixmap pixmap) { ManagedInstances[pixmap.Handle] = pixmap; - return new Pixmap(pixmap.Info.ToImageInfo(colorSpaceImplementation), pixmap.GetPixels()); + return Pixmap.InternalCreateFromExistingPointer(pixmap.Handle); } } } From 807c3d57ef450cb16e5d77e610ba8a1531bff540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Krysi=C5=84ski?= Date: Sat, 16 Dec 2023 20:14:18 +0100 Subject: [PATCH 16/24] Update version --- src/PixiEditor/Properties/AssemblyInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PixiEditor/Properties/AssemblyInfo.cs b/src/PixiEditor/Properties/AssemblyInfo.cs index 1f16d6126..727d3f20c 100644 --- a/src/PixiEditor/Properties/AssemblyInfo.cs +++ b/src/PixiEditor/Properties/AssemblyInfo.cs @@ -50,5 +50,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.1.3")] -[assembly: AssemblyFileVersion("1.2.1.3")] +[assembly: AssemblyVersion("1.2.1.4")] +[assembly: AssemblyFileVersion("1.2.1.4")] From 00e258f01b23889d0393c91986ee6f46a5fda491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Krysi=C5=84ski?= Date: Sun, 17 Dec 2023 14:15:33 +0100 Subject: [PATCH 17/24] Updated Languages and version --- .../Data/Localization/Languages/ar.json | 5 +- .../Data/Localization/Languages/cs.json | 7 +- .../Data/Localization/Languages/de.json | 5 +- .../Data/Localization/Languages/it.json | 5 +- .../Data/Localization/Languages/pl.json | 5 +- .../Data/Localization/Languages/pt-br.json | 41 +- .../Data/Localization/Languages/ru.json | 73 +- .../Data/Localization/Languages/tr.json | 1167 +++++++++-------- .../Data/Localization/Languages/uk.json | 5 +- .../Data/Localization/Languages/zh.json | 35 +- .../Data/Localization/LocalizationData.json | 18 +- src/PixiEditor/Properties/AssemblyInfo.cs | 4 +- 12 files changed, 700 insertions(+), 670 deletions(-) diff --git a/src/PixiEditor/Data/Localization/Languages/ar.json b/src/PixiEditor/Data/Localization/Languages/ar.json index 96bc3d58e..5e32cd7e9 100644 --- a/src/PixiEditor/Data/Localization/Languages/ar.json +++ b/src/PixiEditor/Data/Localization/Languages/ar.json @@ -580,5 +580,8 @@ "RIGHT_CLICK_MODE": "وضع النقر بزر الماوس الأيمن", "ADD_PRIMARY_COLOR_TO_PALETTE": "أضف اللون الأساسي إلى لوح الألوان", "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "أضف اللون الأساسي إلى اللوحة الحالية", - "COPY_COLOR": "انسخ اللون" + "COPY_COLOR": "انسخ اللون", + "ERROR_FAILED_TO_OPEN_EXPLORER": "لا يمكن فتح قائمة الملفات", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "لا يمكن استيراد الكل", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "لا يمكن استرداد الملفات بشكل كامل.\nإذا قمت بإرسال التقرير إلى المطورين\nقد يكونوا قادرين على مساعدتك." } \ No newline at end of file diff --git a/src/PixiEditor/Data/Localization/Languages/cs.json b/src/PixiEditor/Data/Localization/Languages/cs.json index 860361491..6b9e35fd8 100644 --- a/src/PixiEditor/Data/Localization/Languages/cs.json +++ b/src/PixiEditor/Data/Localization/Languages/cs.json @@ -23,7 +23,7 @@ "UPDATE_CHANNEL_HELP_TOOLTIP": "Aktualizační kanály mohou být aktualizovány jen v samostatných verzích aplikace (stáhnutých z https://pixieditor.net/) Steam a Microsoft Store verze zacházejí s jednotlivými verzemi odlišně.", "DEBUG": "Debug", "ENABLE_DEBUG_MODE": "Aktivovat Debug mód", - "OPEN_CRASH_REPORTS_DIR": "Otevřít složku crash reportů", + "OPEN_CRASH_REPORTS_DIR": "Otevřít složku zpráv o selhání", "DISCORD_RICH_PRESENCE": "Bohatá přítomnost", "ENABLED": "Povoleno", "SHOW_IMAGE_NAME": "Ukázat jméno obrázku", @@ -580,5 +580,8 @@ "RIGHT_CLICK_MODE": "Režim pravého tlačítka", "ADD_PRIMARY_COLOR_TO_PALETTE": "Přidat hlavní barvu do palety", "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "Přidat hlavní barvu do současné palety", - "COPY_COLOR": "Zkopírovat barvu" + "COPY_COLOR": "Zkopírovat barvu", + "ERROR_FAILED_TO_OPEN_EXPLORER": "Nepodařilo se otevřít Průzkumníka Souborů", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Nepodařilo se obnovit všechno", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "Nepodařilo se plně obnovit všechny soubory.\nKdyž pošlete zprávu o selhání vývojářům, možná vám budou schopni pomoct." } \ No newline at end of file diff --git a/src/PixiEditor/Data/Localization/Languages/de.json b/src/PixiEditor/Data/Localization/Languages/de.json index 0e1c34036..1d4c6d321 100644 --- a/src/PixiEditor/Data/Localization/Languages/de.json +++ b/src/PixiEditor/Data/Localization/Languages/de.json @@ -580,5 +580,8 @@ "RIGHT_CLICK_MODE": "Rechtsklick Modus", "ADD_PRIMARY_COLOR_TO_PALETTE": "Primärfarbe zu Palette hinzufügen", "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "Primärfarbe der Farbpalette hinzufügen", - "COPY_COLOR": "Copy color" + "COPY_COLOR": "Farbe kopieren", + "ERROR_FAILED_TO_OPEN_EXPLORER": "Windows Explorer konnte nicht geöffnet werden", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Konnte nicht alle Dokumente wiederherstellen", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "Nichte alle Dateien konnten wiederhergestellt werden.\nDie Entwickler können dir eventuell helfen,\nalle Dateien wieder herzustellen." } \ No newline at end of file diff --git a/src/PixiEditor/Data/Localization/Languages/it.json b/src/PixiEditor/Data/Localization/Languages/it.json index d1d429916..b415b37f2 100644 --- a/src/PixiEditor/Data/Localization/Languages/it.json +++ b/src/PixiEditor/Data/Localization/Languages/it.json @@ -580,5 +580,8 @@ "RIGHT_CLICK_MODE": "Modalità tasto destro", "ADD_PRIMARY_COLOR_TO_PALETTE": "Aggiungi colore primario alla tavolozza", "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "Aggiungi colore primario alla tavolozza corrente", - "COPY_COLOR": "Copia colore" + "COPY_COLOR": "Copia colore", + "ERROR_FAILED_TO_OPEN_EXPLORER": "Couldn't open File Explorer", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Could not recover all", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "Could not fully recover all files.\nIf you send the crash report to the developers\nthey might be able to help you." } \ No newline at end of file diff --git a/src/PixiEditor/Data/Localization/Languages/pl.json b/src/PixiEditor/Data/Localization/Languages/pl.json index 66d12df24..57a9cb53b 100644 --- a/src/PixiEditor/Data/Localization/Languages/pl.json +++ b/src/PixiEditor/Data/Localization/Languages/pl.json @@ -580,5 +580,8 @@ "RIGHT_CLICK_MODE": "Akcja prawego przysiku myszy", "ADD_PRIMARY_COLOR_TO_PALETTE": "Dodaj aktywny kolor do palety", "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "Dodaj aktywny kolor do aktualnej palety", - "COPY_COLOR": "Kopiuj kolor" + "COPY_COLOR": "Kopiuj kolor", + "ERROR_FAILED_TO_OPEN_EXPLORER": "Nie udało się otworzyć eksploratora plików", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Nie udało się odzyskać wszystkich plików", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "Nie udało się odzyskać wszystkich plików. \nJeżeli wyślesz raport awarii do developerów, być może będą w stanie Ci pomóc." } \ No newline at end of file diff --git a/src/PixiEditor/Data/Localization/Languages/pt-br.json b/src/PixiEditor/Data/Localization/Languages/pt-br.json index 2e2ca7c4b..2bee2b945 100644 --- a/src/PixiEditor/Data/Localization/Languages/pt-br.json +++ b/src/PixiEditor/Data/Localization/Languages/pt-br.json @@ -561,24 +561,27 @@ "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE": "Arraste as alças para transformar a escala. Mantenha pressionada a tecla Shift para dimensionar proporcionalmente. Mantenha pressionada a tecla Alt e arraste uma alça lateral para cortar. Arraste as alças externas para girar.", "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE": "Arraste as alças para transformar a escala. Mantenha pressionada a tecla Shift para dimensionar proporcionalmente. Arraste as alças externas para girar.", "TRANSFORM_ACTION_DISPLAY_SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE": "Arraste as alças para transformar a escala. Mantenha pressionada a tecla Shift para dimensionar proporcionalmente.", - "OPEN_DOCUMENTATION": "Open documentation", + "OPEN_DOCUMENTATION": "Abrir documentação", "LOCAL_PALETTE_SOURCE_NAME": "Local", - "ERROR_FORBIDDEN_UNIQUE_NAME": "Extension unique name cannot start with 'pixieditor'.", - "ERROR_MISSING_METADATA": "Extension metadata key '{0}' is missing.", - "ERROR_NO_CLASS_ENTRY": "Extension class entry is missing on path '{0}'.", - "ERROR_NO_ENTRY_ASSEMBLY": "Extension entry assembly is missing on path '{0}'.", - "ERROR_MISSING_ADDITIONAL_CONTENT": "Your current setup doesn't allow loading this extension. Perhaps you don't own it or don't have it installed. You can purchase it here '{0}'.", - "BUY_SUPPORTER_PACK": "Buy Supporter Pack", - "NEWS": "News", - "DISABLE_NEWS_PANEL": "Disable News panel in startup window", - "FAILED_FETCH_NEWS": "Failed to fetch news", - "CROP_TO_SELECTION": "Crop to selection", - "CROP_TO_SELECTION_DESCRIPTIVE": "Crop image to selection", - "SHOW_CONTEXT_MENU": "Show context menu", - "ERASE": "Erase", - "USE_SECONDARY_COLOR": "Use secondary color", - "RIGHT_CLICK_MODE": "Right click mode", - "ADD_PRIMARY_COLOR_TO_PALETTE": "Add primary color to palette", - "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "Add primary color to current palette", - "COPY_COLOR": "Copy color" + "ERROR_FORBIDDEN_UNIQUE_NAME": "O nome exclusivo da extensão não pode começar com \"pixieditor\".", + "ERROR_MISSING_METADATA": "A chave de metadados de extensão '{0}' está ausente.", + "ERROR_NO_CLASS_ENTRY": "A entrada da classe de extensão está ausente no caminho '{0}'.", + "ERROR_NO_ENTRY_ASSEMBLY": "O conjunto de entrada de extensão está ausente no caminho '{0}'.", + "ERROR_MISSING_ADDITIONAL_CONTENT": "Sua configuração atual não permite o carregamento dessa extensão. Talvez você não a possua ou não a tenha instalada. Você pode comprá-la aqui '{0}'", + "BUY_SUPPORTER_PACK": "Comprar Pacote de Apoiador", + "NEWS": "Notícias", + "DISABLE_NEWS_PANEL": "Desativar o painel Notícias na janela de inicialização", + "FAILED_FETCH_NEWS": "Falha ao buscar notícias", + "CROP_TO_SELECTION": "Cortar para a seleção", + "CROP_TO_SELECTION_DESCRIPTIVE": "Cortar imagem para seleção", + "SHOW_CONTEXT_MENU": "Mostrar menu de contexto", + "ERASE": "Apagar", + "USE_SECONDARY_COLOR": "Usar cor secundária", + "RIGHT_CLICK_MODE": "Modo de clique direito", + "ADD_PRIMARY_COLOR_TO_PALETTE": "Adicionar cor primária à paleta", + "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "Adicionar cor primária à paleta atual", + "COPY_COLOR": "Copiar Cor", + "ERROR_FAILED_TO_OPEN_EXPLORER": "Couldn't open File Explorer", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Could not recover all", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "Could not fully recover all files.\nIf you send the crash report to the developers\nthey might be able to help you." } \ No newline at end of file diff --git a/src/PixiEditor/Data/Localization/Languages/ru.json b/src/PixiEditor/Data/Localization/Languages/ru.json index f68051dda..a12f86a8b 100644 --- a/src/PixiEditor/Data/Localization/Languages/ru.json +++ b/src/PixiEditor/Data/Localization/Languages/ru.json @@ -12,7 +12,7 @@ "SHOW_IMAGE_PREVIEW_TASKBAR": "Отображать редактируемую картинку на иконке на панели задач", "RECENT_FILE_LENGTH": "Длина списка недавних файлов", "RECENT_FILE_LENGTH_TOOLTIP": "Сколько файлов показывать в списке Файл > Недавние. По умолчанию - 8.", - "DEFAULT_NEW_SIZE": "Размер нового файла по умолчанию", + "DEFAULT_NEW_SIZE": "Размер холста по умолчанию", "WIDTH": "Ширина", "HEIGHT": "Высота", "TOOLS": "Инструменты", @@ -51,8 +51,8 @@ "LICENSE": "Лицензия", "OPEN_LICENSE": "Открыть лицензию", "THIRD_PARTY_LICENSES": "Лицензии третьих сторон", - "OPEN_THIRD_PARTY_LICENSES": "Открыть лицензии третьих сторон", - "APPLY_TRANSFORM": "Применить трансформирование", + "OPEN_THIRD_PARTY_LICENSES": "Открыть сторонние лицензии", + "APPLY_TRANSFORM": "Применить масштабирование", "INCREASE_TOOL_SIZE": "Увеличить размер кисти", "DECREASE_TOOL_SIZE": "Уменьшить размер кисти", "TO_INSTALL_UPDATE": "для установки обновления {0}", @@ -133,9 +133,9 @@ "MERGE_WITH_BELOW_DESCRIPTIVE": "Объединить выбранный слой со слоем ниже от него", "ADD_REFERENCE_LAYER": "Добавить референс", "DELETE_REFERENCE_LAYER": "Удалить референс", - "TRANSFORM_REFERENCE_LAYER": "Трансформировать референс", + "TRANSFORM_REFERENCE_LAYER": "Масштабировать референс", "TOGGLE_REFERENCE_LAYER_POS": "Изменить позицию референса", - "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "Переключение между двумя положениями референса: поверх рисунка и ниже рисунка", + "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "Переключение между двумя положениями референса: поверх хоста и под холстом", "RESET_REFERENCE_LAYER_POS": "Сбросить положение референса", "CLIP_CANVAS": "Обрезать прозрачные края", "FLIP_IMG_VERTICALLY": "Отразить по вертикали", @@ -162,7 +162,7 @@ "PASTE_AS_NEW_LAYER": "Вставить в новый слой", "PASTE_AS_NEW_LAYER_DESCRIPTIVE": "Вставить изображение из буфера обмена в новый слой", "PASTE_REFERENCE_LAYER": "Вставить референс", - "PASTE_REFERENCE_LAYER_DESCRIPTIVE": "Вставить изображение-референс из буфера обмена", + "PASTE_REFERENCE_LAYER_DESCRIPTIVE": "Вставить референс из буфера обмена", "PASTE_COLOR": "Вставить цвет", "PASTE_COLOR_DESCRIPTIVE": "Вставить HEX-код цвета из буфера обмена", "PASTE_COLOR_SECONDARY": "Вставить цвет как дополнительный", @@ -223,7 +223,7 @@ "CLEAR_SELECTION": "Снять выделение", "INVERT_SELECTION": "Инвертировать выделение", "INVERT_SELECTION_DESCRIPTIVE": "Инвертировать выделенную область", - "TRANSFORM_SELECTED_AREA": "Трансформировать выбранную область", + "TRANSFORM_SELECTED_AREA": "Масштабировать выбранную область", "NUDGE_SELECTED_LEFT": "Сдвинуть выделение влево", "NUDGE_SELECTED_RIGHT": "Сдвинуть выделение вправо", "NUDGE_SELECTED_UP": "Сдвинуть выделение вверх", @@ -275,7 +275,7 @@ "UNNAMED": "Безымянный", "OPEN_COMMAND_DEBUG_WINDOW": "Открыть окно отладки команд", "DELETE": "Удалить", - "USER_PREFS": "Предпочтения пользователя (Локальные)", + "USER_PREFS": "Предпочтения пользователя (Roaming)", "SHORTCUT_FILE": "Сочетания клавиш (Локальные)", "EDITOR_DATA": "Данные редактора (Локальные)", "MOVE_VIEWPORT_TOOLTIP": "Передвигает область просмотра.", @@ -359,7 +359,7 @@ "ENABLE_MASK": "Включить маску", "SELECTED_AREA_EMPTY": "Выбранная область пуста", "NOTHING_TO_COPY": "Нечего копировать", - "REFERENCE_LAYER_PATH": "Путь к референс слою", + "REFERENCE_LAYER_PATH": "Путь к референсу", "FLIP": "Отразить", "ROTATION": "Поворот", "ROT_IMG_90_D": "Повернуть изображение на 90°", @@ -481,10 +481,10 @@ "STOP_IT_TEXT4": "sans undertale reference omg so cool", "LINEAR_DODGE_BLEND_MODE": "Линейный осветлитель (добавить)", "PRESS_ANY_KEY": "Нажмите любую клавишу", - "NONE_SHORTCUT": "Ничего", + "NONE_SHORTCUT": "Нет", "REFERENCE": "Референс", - "PUT_REFERENCE_LAYER_ABOVE": "Показать слой референса", - "PUT_REFERENCE_LAYER_BELOW": "Спрятать слой референса", + "PUT_REFERENCE_LAYER_ABOVE": "Отображать референс поверх холста", + "PUT_REFERENCE_LAYER_BELOW": "Отображать референс под холстом", "TOGGLE_VERTICAL_SYMMETRY": "Переключить вертикальную ось симметрии", "TOGGLE_HORIZONTAL_SYMMETRY": "Переключить горизонтальную ось симметрии", "RESET_VIEWPORT": "Сбросить область просмотра", @@ -533,52 +533,55 @@ "IMPORT_PALETTE_FILE": "Импортировать файл палитры", "IMPORT_MULTIPLE_PALETTE_COLORS": "Импортировать цвета в палитру", "IMPORT_SINGLE_PALETTE_COLOR": "Импортировать цвет в палитру", - "IMPORT_AS_REFERENCE_LAYER": "Импортировать как слой референса", + "IMPORT_AS_REFERENCE_LAYER": "Импортировать как референс", "NAVIGATOR_PICK_ACTION_DISPLAY": "ПКМ чтобы взять цвет, Shift + ПКМ чтобы скопировать цвет в буфер обмена", "OPEN_FILE_FROM_CLIPBOARD": "Открыть из буфера обмена", "OPEN_FILE_FROM_CLIPBOARD_DESCRIPTIVE": "Открыть из буфера обмена", "OPEN_LOCALIZATION_DATA": "Хотите открыть LocalizationData.json? Обновлённая дата добавлена в буфер обмена. Перезапустите чтобы применить изменения.", - "DOWNLOADING_LANGUAGE_FAILED": "Не удалось загрузить язык. API ключ может быть использован слишком часто.", + "DOWNLOADING_LANGUAGE_FAILED": "Не удалось загрузить язык. \nВозможно был превышена допустимая частота запросов для API ключа.", "LOCALIZATION_DATA_NOT_FOUND": "Путь к данным локализации не найден", "APPLY": "Применить", "UPDATE_SOURCE": "Обновить источник", "COPY_TO_CLIPBOARD": "Скопировать в буфер обмена", - "LANGUAGE_FILE_NOT_FOUND": "Файл языка не найден. Идёт поиск {0}", - "PROJECT_ROOT_NOT_FOUND": "Корневой файл проекта PixiEditor не был найден. Идёт поиск PixiEditor.csproj", - "LOCALIZATION_FOLDER_NOT_FOUND": "Путь к папке локализации не найден. Идёт поиск /Data/Localization", + "LANGUAGE_FILE_NOT_FOUND": "Файл языка не найден. \nИщем {0}", + "PROJECT_ROOT_NOT_FOUND": "Корневой файл проекта PixiEditor не был найден. \nИщем PixiEditor.csproj", + "LOCALIZATION_FOLDER_NOT_FOUND": "Путь к папке локализации не найден. \nИщем /Data/Localization", "SELECT_A_LANGUAGE": "Выберите язык", "DONE": "Готово", - "SOURCE_UNSET_OR_MISSING": "Источник отсутствует/не поставлен", + "SOURCE_UNSET_OR_MISSING": "Источник отсутствует/не проставлен", "SOURCE_NEWER": "Источник новее", "SOURCE_UP_TO_DATE": "Источник обновлён", - "SOURCE_OLDER": "Облако новее", - "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Нажмите чтобы брать цвета из рефернс слоя.", + "SOURCE_OLDER": "Облачная версия новее", + "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Кликните для выбора цвета из референса.", "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Нажмите чтобы брать цвета из холста.", - "LOCALIZATION_DEBUG_WINDOW_TITLE": "Окно дебага локализаций", - "COMMAND_DEBUG_WINDOW_TITLE": "Окно дебага команд", - "SHORTCUTS_TITLE": "Ярлыки", - "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_PERSPECTIVE": "Двигайте ручки чтобы изменить масштаб. Удерживайте Ctrl и передвигайте ручку чтобы передвигать ручку без изменений масштаба. Удерживайте Alt и передвигайте сторону ручки чтобы сдвинуть. Выдвиньте из ручки чтобы повернуть.", - "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE": "Двигайте ручки чтобы изменить масштаб. Удерживайте Shift чтобы масштабировать пропорционально. Удерживайте Alt и передвигайте сторону ручки чтобы сдвинуть. Выдвиньте из ручки чтобы повернуть.", - "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE": "Двигайте ручки чтобы изменить масштаб. Удерживайте Shift чтобы масштабировать пропорционально. Выдвиньте из ручек чтобы повернуть.", - "TRANSFORM_ACTION_DISPLAY_SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE": "Двигайте ручки чтобы изменить масштаб. Удерживайте Shift чтобы масштабировать пропорционально.", + "LOCALIZATION_DEBUG_WINDOW_TITLE": "Окно отладки локализаций", + "COMMAND_DEBUG_WINDOW_TITLE": "Окно отладки команд", + "SHORTCUTS_TITLE": "Сочетания клавиш", + "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_PERSPECTIVE": "Перетащите углы и края для масштабирования. Зажмите Ctrl для свободного масштабирования. Удерживайте Alt и перемещайте края для бокового сдвига. Для поворота зажмите и перетащите вблизи выделения.", + "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE": "Перетащите углы и края для масштабирования. Зажмите Shift для пропорционального масштабирования. Удерживайте Alt и перемещайте края для бокового сдвига. Для поворота зажмите и перетащите вблизи выделения.", + "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE": "Двигайте ручки чтобы изменить масштаб. Удерживайте Shift чтобы масштабировать пропорционально. Для поворота зажмите и перетащите вблизи выделения.", + "TRANSFORM_ACTION_DISPLAY_SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE": "Перемещайте углы и края для масштабирования. Зажмите Shift для пропорционального масштабирования.", "OPEN_DOCUMENTATION": "Открыть документацию", "LOCAL_PALETTE_SOURCE_NAME": "Локально", - "ERROR_FORBIDDEN_UNIQUE_NAME": "Уникальное имя приложения не может начинаться с 'pixieditor'.", - "ERROR_MISSING_METADATA": "Ключ метадаты приложения '{0}' отсутствует.", - "ERROR_NO_CLASS_ENTRY": "Запись в классе приложения отсутствует в папке '{0}'.", - "ERROR_NO_ENTRY_ASSEMBLY": "Входная сборка приложения отсутствует в папке '{0}'.", - "ERROR_MISSING_ADDITIONAL_CONTENT": "Ваша нынешняя сборка не позволяет загрузить это приложение. Возможно вы не приобрели его или не установили. Вы можете приобрести его здесь '{0}'.", - "BUY_SUPPORTER_PACK": "Приобретите Supporter Pack", + "ERROR_FORBIDDEN_UNIQUE_NAME": "Уникальное имя расширения не может начинаться с 'pixieditor'.", + "ERROR_MISSING_METADATA": "Ключ метадаты расширения '{0}' отсутствует.", + "ERROR_NO_CLASS_ENTRY": "Запись в классе расширения отсутствует в папке '{0}'.", + "ERROR_NO_ENTRY_ASSEMBLY": "Входная сборка расширения отсутствует в папке '{0}'.", + "ERROR_MISSING_ADDITIONAL_CONTENT": "Ваша нынешняя сборка не позволяет загрузить это расширение. Возможно вы не приобрели его или не установили. Вы можете приобрести его здесь '{0}'.", + "BUY_SUPPORTER_PACK": "Приобрести Supporter Pack", "NEWS": "Новости", "DISABLE_NEWS_PANEL": "Отключить новостную панель в окне приветствия", "FAILED_FETCH_NEWS": "Не удалось загрузить новости", "CROP_TO_SELECTION": "Обрезать выделение", "CROP_TO_SELECTION_DESCRIPTIVE": "Обрезать изображение до выделения", - "SHOW_CONTEXT_MENU": "Показать меню контекста", + "SHOW_CONTEXT_MENU": "Открыть контекстное меню", "ERASE": "Стереть", "USE_SECONDARY_COLOR": "Использовать дополнительный цвет", "RIGHT_CLICK_MODE": "Режим ПКМ", "ADD_PRIMARY_COLOR_TO_PALETTE": "Добавить основной цвет в палитру", "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "Добавить основной цвет в нынешнюю палитру", - "COPY_COLOR": "Скопировать цвет" + "COPY_COLOR": "Скопировать цвет", + "ERROR_FAILED_TO_OPEN_EXPLORER": "Не удалось открыть Проводник", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Не удалось восстановить все файлы", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "Не удалось восстановить все файлы.\nЕсли вы отправите отчет о сбое разработчикам, возможно, они смогут вам помочь." } \ No newline at end of file diff --git a/src/PixiEditor/Data/Localization/Languages/tr.json b/src/PixiEditor/Data/Localization/Languages/tr.json index b1d5ed066..5aa2bd4a3 100644 --- a/src/PixiEditor/Data/Localization/Languages/tr.json +++ b/src/PixiEditor/Data/Localization/Languages/tr.json @@ -1,584 +1,587 @@ { - "RECENT_FILES": "Son Dosyalar", - "OPEN_FILE": "Dosya Aç", - "NEW_FILE": "Yeni", - "RECENT_EMPTY_TEXT": "Çok fazla boşluk", - "LANGUAGE": "Dil", - "GENERAL": "Genel", - "DISCORD": "Discord", - "KEY_BINDINGS": "Tuş Atamaları", - "MISC": "Çeşitli", - "SHOW_STARTUP_WINDOW": "Başlangıç Ekranını Göster", - "SHOW_IMAGE_PREVIEW_TASKBAR": "Görev çubuğunda resim önizlemesini göster", - "RECENT_FILE_LENGTH": "Son dosya listesi uzunluğu", - "RECENT_FILE_LENGTH_TOOLTIP": "Dosya > Son dosyalar altında gösterilen belge sayısı. Varsayılan: 8", - "DEFAULT_NEW_SIZE": "Varsayılan yeni dosya boyutu", - "WIDTH": "Genişlik", - "HEIGHT": "Yükseklik", - "TOOLS": "Araçlar", - "ENABLE_SHARED_TOOLBAR": "Paylaşılan araç çubuğunu etkinleştir", - "AUTOMATIC_UPDATES": "Otomatik Güncellemeler", - "CHECK_FOR_UPDATES": "Başlangıçta güncellemeleri kontrol et", - "UPDATE_STREAM": "Güncelleme akışı", - "UPDATE_CHANNEL_HELP_TOOLTIP": "Güncelleme kanalları yalnızca bağımsız sürümde (https://pixieditor.net'den indirilen) değiştirilebilir.\nSteam ve Microsoft Store sürümleri güncellemeleri ayrı şekilde yönetir.", - "DEBUG": "Hata Ayıklama", - "ENABLE_DEBUG_MODE": "Hata Ayıklama modunu etkinleştir", - "OPEN_CRASH_REPORTS_DIR": "Çökme raporları dizinini aç", - "DISCORD_RICH_PRESENCE": "Zengin Durum", - "ENABLED": "Etkin", - "SHOW_IMAGE_NAME": "Resim adını göster", - "SHOW_IMAGE_SIZE": "Resim boyutunu göster", - "SHOW_LAYER_COUNT": "Katman sayısını göster", - "FILE": "Dosya", - "RECENT": "Son", - "OPEN": "Aç", - "SAVE_PIXI": "Kaydet (.pixi)", - "SAVE_AS_PIXI": "Farklı Kaydet... (.pixi)", - "EXPORT_IMG": "Dışa Aktar (.png, .jpg, vb.)", - "EDIT": "Düzenle", - "EXIT": "Çıkış", - "PERCENTAGE": "Yüzde", - "ABSOLUTE": "Mutlak", - "PRESERVE_ASPECT_RATIO": "En-boy oranını koru", - "ANCHOR_POINT": "Bağlama Noktası", - "RESIZE_IMAGE": "Resmi Yeniden Boyutlandır", - "RESIZE": "Yeniden Boyutlandır", - "DOCUMENTATION": "Dokümantasyon", - "WEBSITE": "Web Sitesi", - "OPEN_WEBSITE": "Web sitesini aç", - "REPOSITORY": "Depo", - "OPEN_REPOSITORY": "Depoyu aç", - "LICENSE": "Lisans", - "OPEN_LICENSE": "Lisansı aç", - "THIRD_PARTY_LICENSES": "Üçüncü Taraf Lisansları", - "OPEN_THIRD_PARTY_LICENSES": "Üçüncü taraf lisanslarını aç", - "APPLY_TRANSFORM": "Dönüşümü Uygula", - "INCREASE_TOOL_SIZE": "Araç boyutunu artır", - "DECREASE_TOOL_SIZE": "Araç boyutunu azalt", - "TO_INSTALL_UPDATE": "güncellemeyi yüklemek için {0}", - "DOWNLOADING_UPDATE": "Güncelleme indiriliyor...", - "UPDATE_READY": "Güncelleme yüklemeye hazır. Şimdi yüklemek istiyor musunuz?", - "NEW_UPDATE": "Yeni güncelleme", - "COULD_NOT_UPDATE_WITHOUT_ADMIN": "Yönetici ayrıcalıkları olmadan güncelleme yapılamadı. Lütfen PixiEditor'ü yönetici olarak çalıştırın.", - "INSUFFICIENT_PERMISSIONS": "Yetersiz izinler", - "UPDATE_CHECK_FAILED": "Güncelleme kontrolü başarısız oldu", - "COULD_NOT_CHECK_FOR_UPDATES": "Güncelleme mevcut olup olmadığı kontrol edilemedi.", - "VERSION": "Sürüm {0}", - "OPEN_TEMP_DIR": "Geçici dizini aç", - "OPEN_LOCAL_APPDATA_DIR": "Yerel AppData dizinini aç", - "OPEN_ROAMING_APPDATA_DIR": "Gezintili AppData dizinini aç", - "OPEN_INSTALLATION_DIR": "Kurulum dizinini aç", - "DUMP_ALL_COMMANDS": "Tüm komutları dök", - "DUMP_ALL_COMMANDS_DESCRIPTIVE": "Tüm komutları bir metin dosyasına dök", - "CRASH": "Çökme", - "CRASH_APP": "Uygulamayı çökert", - "DELETE_USR_PREFS": "Kullanıcı tercihlerini sil (Gezintili AppData)", - "DELETE_SHORTCUT_FILE": "Kısayol dosyasını sil (Gezintili AppData)", - "DELETE_EDITOR_DATA": "Düzenleyici verilerini sil (Yerel AppData)", - "GENERATE_KEY_BINDINGS_TEMPLATE": "Tuş atama şablonu oluştur", - "GENERATE_KEY_BINDINGS_TEMPLATE_DESCRIPTIVE": "Tuş atama json şablonu oluştur", - "VALIDATE_SHORTCUT_MAP": "Kısayol haritasını doğrula", - "VALIDATE_SHORTCUT_MAP_DESCRIPTIVE": "Kısayol haritasını doğrular", - "VALIDATION_KEYS_NOTICE_DIALOG": "Boş anahtarlar: {0}\nBilinmeyen Komutlar: {1}", - "RESULT": "Sonuç", - "CLEAR_RECENT_DOCUMENTS": "Son belgeleri temizle", - "CLEAR_RECENTLY_OPENED_DOCUMENTS": "Yeni açılan belgeleri temizle", - "OPEN_CMD_DEBUG_WINDOW": "Komut hata ayıklama penceresini aç", - "PATH_DOES_NOT_EXIST": "{0} mevcut değil.", - "LOCATION_DOES_NOT_EXIST": "Konum mevcut değil.", - "FILE_NOT_FOUND": "Dosya bulunamadı.", - "ARE_YOU_SURE": "Emin misiniz?", - "ARE_YOU_SURE_PATH_FULL_PATH": "{0}'yi silmek istediğinizden emin misiniz?\nBu veri tüm yüklemeler için kaybolacak.\n(Tam Yol: {1})", - "FAILED_TO_OPEN_FILE": "Dosya açılamadı", - "OLD_FILE_FORMAT": "Eski dosya formatı", - "OLD_FILE_FORMAT_DESCRIPTION": "Bu .pixi dosyası eski formatta kullanılmıştır,\n artık desteklenmiyor ve açılamaz.", - "NOTHING_FOUND": "Hiçbir şey bulunamadı", - "EXPORT": "Dışa Aktar", - "EXPORT_IMAGE": "Resmi Dışa Aktar", - "IMPORT": "İçe Aktar", - "SHORTCUT_TEMPLATES": "Kısayol şablonları", - "RESET_ALL": "Hepsini Sıfırla", - "LAYER": "Katman", - "LAYER_DELETE_SELECTED": "Aktif katmanı/dizini sil", - "LAYER_DELETE_SELECTED_DESCRIPTIVE": "Aktif katmanı veya dizini sil", - "LAYER_DELETE_ALL_SELECTED": "Tüm seçili katmanları/dizinleri sil", - "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "Tüm seçili katmanları ve/veya dizinleri sil", - "DELETE_SELECTED_PIXELS": "Seçili pikselleri sil", - "NEW_FOLDER": "Yeni klasör", - "CREATE_NEW_FOLDER": "Yeni klasör oluştur", - "NEW_LAYER": "Yeni katman", - "CREATE_NEW_LAYER": "Yeni katman oluştur", - "NEW_IMAGE": "Yeni görüntü", - "CREATE_NEW_IMAGE": "Yeni görüntü oluştur", - "SAVE": "Kaydet", - "SAVE_AS": "Farklı Kaydet...", - "IMAGE": "Görüntü", - "SAVE_IMAGE": "Görüntüyü kaydet", - "SAVE_IMAGE_AS": "Yeni olarak görüntüyü kaydet", - "DUPLICATE": "Kopyala", - "DUPLICATE_SELECTED_LAYER": "Seçili katmanı kopyala", - "CREATE_MASK": "Maske oluştur", - "DELETE_MASK": "Maskeyi sil", - "TOGGLE_MASK": "Maskesi aç/kapat", - "APPLY_MASK": "Maskeyi uygula", - "TOGGLE_VISIBILITY": "Görünürlüğü aç/kapat", - "MOVE_MEMBER_UP": "Üyeyi yukarı taşı", - "MOVE_MEMBER_UP_DESCRIPTIVE": "Seçili katmanı veya dizini yukarı taşı", - "MOVE_MEMBER_DOWN": "Üyeyi aşağı taşı", - "MOVE_MEMBER_DOWN_DESCRIPTIVE": "Seçili katmanı veya dizini aşağı taşı", - "MERGE_ALL_SELECTED_LAYERS": "Tüm seçili katmanları birleştir", - "MERGE_WITH_ABOVE": "Seçili katmanı üsttekiyle birleştir", - "MERGE_WITH_ABOVE_DESCRIPTIVE": "Seçili katmanı üzerindekiyle birleştir", - "MERGE_WITH_BELOW": "Seçili katmanı alttakilerle birleştir", - "MERGE_WITH_BELOW_DESCRIPTIVE": "Seçili katmanı altındakilerle birleştir", - "ADD_REFERENCE_LAYER": "Referans Katman Ekle", - "DELETE_REFERENCE_LAYER": "Referans katmanı sil", - "TRANSFORM_REFERENCE_LAYER": "Referans katmanını dönüştür", - "TOGGLE_REFERENCE_LAYER_POS": "Referans katmanının konumunu değiştir", - "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "Referans katmanını en üstte veya en altta arasında değiştir", - "RESET_REFERENCE_LAYER_POS": "Referans katmanının konumunu sıfırla", - "CLIP_CANVAS": "Kanvası Kırp", - "FLIP_IMG_VERTICALLY": "Görüntüyü Dikey Olarak Çevir", - "FLIP_IMG_HORIZONTALLY": "Görüntüyü Yatay Olarak Çevir", - "FLIP_LAYERS_VERTICALLY": "Seçili Katmanları Dikey Olarak Çevir", - "FLIP_LAYERS_HORIZONTALLY": "Seçili Katmanları Yatay Olarak Çevir", - "ROT_IMG_90": "Görüntüyü 90 derece döndür", - "ROT_IMG_180": "Görüntüyü 180 derece döndür", - "ROT_IMG_-90": "Görüntüyü -90 derece döndür", - "ROT_LAYERS_90": "Seçili Katmanları 90 derece döndür", - "ROT_LAYERS_180": "Seçili Katmanları 180 derece döndür", - "ROT_LAYERS_-90": "Seçili Katmanları -90 derece döndür", - "TOGGLE_VERT_SYMMETRY_AXIS": "Dikey simetri eksenini aç/kapat", - "TOGGLE_HOR_SYMMETRY_AXIS": "Yatay simetri eksenini aç/kapat", - "DELETE_PIXELS": "Pikselleri sil", - "DELETE_PIXELS_DESCRIPTIVE": "Seçili pikselleri sil", - "RESIZE_DOCUMENT": "Belgeyi Yeniden Boyutlandır", - "RESIZE_CANVAS": "Kanvası Yeniden Boyutlandır", - "CENTER_CONTENT": "İçeriği Ortala", - "CUT": "Kes", - "CUT_DESCRIPTIVE": "Seçili alanı/katmanları kes", - "PASTE": "Yapıştır", - "PASTE_DESCRIPTIVE": "Pano içeriğini yapıştır", - "PASTE_AS_NEW_LAYER": "Yeni katman olarak yapıştır", - "PASTE_AS_NEW_LAYER_DESCRIPTIVE": "Panodan yeni bir katman olarak yapıştır", - "PASTE_REFERENCE_LAYER": "Referans katmanını yapıştır", - "PASTE_REFERENCE_LAYER_DESCRIPTIVE": "Panodaki içeriği referans katmanı olarak yapıştır", - "PASTE_COLOR": "Rengi yapıştır", - "PASTE_COLOR_DESCRIPTIVE": "Panodan rengi yapıştır", - "PASTE_COLOR_SECONDARY": "İkincil olarak yapıştır", - "PASTE_COLOR_SECONDARY_DESCRIPTIVE": "Panodan rengi ikincil renk olarak yapıştır", - "CLIPBOARD": "Pano", - "COPY": "Kopyala", - "COPY_DESCRIPTIVE": "Panoya kopyala", - "COPY_COLOR_HEX": "Birincil rengi kopyala (HEX)", - "COPY_COLOR_HEX_DESCRIPTIVE": "Birincil rengi HEX kodu olarak kopyala", - "COPY_COLOR_RGB": "Birincil rengi kopyala (RGB)", - "COPY_COLOR_RGB_DESCRIPTIVE": "Birincil rengi RGB kodu olarak kopyala", - "COPY_COLOR_SECONDARY_HEX": "İkincil rengi kopyala (HEX)", - "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "İkincil rengi HEX kodu olarak kopyala", - "COPY_COLOR_SECONDARY_RGB": "İkincil rengi kopyala (RGB)", - "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "İkincil rengi RGB kodu olarak kopyala", - "PALETTE_COLORS": "Palet Renkleri", - "REPLACE_SECONDARY_BY_PRIMARY": "İkincil rengi birincil renk ile değiştir", - "REPLACE_SECONDARY_BY_PRIMARY_DESCRIPTIVE": "İkincil rengi birincil renk ile değiştir", - "REPLACE_PRIMARY_BY_SECONDARY": "Birincil rengi ikincil renk ile değiştir", - "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "Birincil rengi ikincil renk ile değiştir", - "OPEN_PALETTE_BROWSER": "Palet tarayıcısını aç", - "OVERWRITE_PALETTE_CONSENT": "'{0}' paleti zaten mevcut, üzerine yazmak istiyor musunuz?", - "PALETTE_EXISTS": "Palet zaten mevcut", - "REPLACE_PALETTE_CONSENT": "Geçerli palet seçilenle değiştirilsin mi?", - "REPLACE_PALETTE": "Geçerli paleti değiştir", - "SELECT_COLOR_1": "Renk 1'i seç", - "SELECT_COLOR_2": "Renk 2'yi seç", - "SELECT_COLOR_3": "Renk 3'ü seç", - "SELECT_COLOR_4": "Renk 4'ü seç", - "SELECT_COLOR_5": "Renk 5'i seç", - "SELECT_COLOR_6": "Renk 6'yı seç", - "SELECT_COLOR_7": "Renk 7'yi seç", - "SELECT_COLOR_8": "Renk 8'i seç", - "SELECT_COLOR_9": "Renk 9'u seç", - "SELECT_COLOR_10": "Renk 10'u seç", - "SELECT_TOOL": "{0} Aracını Seç", - "SELECT_COLOR_1_DESCRIPTIVE": "Paletin ilk rengini seçin", - "SELECT_COLOR_2_DESCRIPTIVE": "Paletin ikinci rengini seçin", - "SELECT_COLOR_3_DESCRIPTIVE": "Paletin üçüncü rengini seçin", - "SELECT_COLOR_4_DESCRIPTIVE": "Paletin dördüncü rengini seçin", - "SELECT_COLOR_5_DESCRIPTIVE": "Paletin beşinci rengini seçin", - "SELECT_COLOR_6_DESCRIPTIVE": "Paletin altıncı rengini seçin", - "SELECT_COLOR_7_DESCRIPTIVE": "Paletin yedinci rengini seçin", - "SELECT_COLOR_8_DESCRIPTIVE": "Paletin sekizinci rengini seçin", - "SELECT_COLOR_9_DESCRIPTIVE": "Paletin dokuzuncu rengini seçin", - "SELECT_COLOR_10_DESCRIPTIVE": "Paletin onuncu rengini seçin", - "SWAP_COLORS": "Renkleri Değiştir", - "SWAP_COLORS_DESCRIPTIVE": "Birincil ve ikincil renkleri değiştir", - "SEARCH": "Ara", - "COMMAND_SEARCH": "Komut arama", - "OPEN_COMMAND_SEARCH": "Komut arama penceresini aç", - "SELECT": "Seç", - "DESELECT": "Seçimi Kaldır", - "INVERT": "Tersine Çevir", - "SELECTION": "Seçim", - "SELECT_ALL": "Hepsini Seç", - "SELECT_ALL_DESCRIPTIVE": "Hepsini seç", - "CLEAR_SELECTION": "Seçimi Temizle", - "INVERT_SELECTION": "Seçimi Tersine Çevir", - "INVERT_SELECTION_DESCRIPTIVE": "Seçimi tersine çevir", - "TRANSFORM_SELECTED_AREA": "Seçili alanı dönüştür", - "NUDGE_SELECTED_LEFT": "Seçili nesneyi sola kaydır", - "NUDGE_SELECTED_RIGHT": "Seçili nesneyi sağa kaydır", - "NUDGE_SELECTED_UP": "Seçili nesneyi yukarı kaydır", - "NUDGE_SELECTED_DOWN": "Seçili nesneyi aşağı kaydır", - "MASK_FROM_SELECTION": "Yeni maske oluştur", - "MASK_FROM_SELECTION_DESCRIPTIVE": "Seçimi yeni maskeye dönüştür", - "ADD_SELECTION_TO_MASK": "Seçimi maskeye ekle", - "SUBTRACT_SELECTION_FROM_MASK": "Seçimi maskeye çıkar", - "INTERSECT_SELECTION_MASK": "Seçimi maskeyle kesiştir", - "SELECTION_TO_MASK": "Seçimi maskeye dönüştür", - "TO_NEW_MASK": "yeni maske için", - "ADD_TO_MASK": "maskeye ekle", - "SUBTRACT_FROM_MASK": "maskeden çıkar", - "INTERSECT_WITH_MASK": "maskelerle kesiştir", - "STYLUS": "Stylus", - "TOGGLE_PEN_MODE": "Kalem modunu aç/kapat", - "UNDO": "Geri Al", - "UNDO_DESCRIPTIVE": "Son işlemi geri al", - "REDO": "Yinele", - "REDO_DESCRIPTIVE": "Son işlemi yeniden yap", - "WINDOWS": "Windows", - "TOGGLE_GRIDLINES": "Kılavuz çizgilerini aç/kapat", - "ZOOM_IN": "Yakınlaştır", - "ZOOM_OUT": "Uzaklaştır", - "NEW_WINDOW_FOR_IMG": "Mevcut resim için yeni pencere aç", - "CENTER_ACTIVE_VIEWPORT": "Etkin görünümü ortala", - "FLIP_VIEWPORT_HORIZONTALLY": "Görünümü yatay olarak çevir", - "FLIP_VIEWPORT_VERTICALLY": "Görünümü dikey olarak çevir", - "SETTINGS": "Ayarlar", - "OPEN_SETTINGS": "Ayarları aç", - "OPEN_SETTINGS_DESCRIPTIVE": "Ayarlar penceresini aç", - "OPEN_STARTUP_WINDOW": "Başlangıç penceresini aç", - "OPEN_SHORTCUT_WINDOW": "Kısayol penceresini aç", - "OPEN_ABOUT_WINDOW": "Hakkında penceresini aç", - "OPEN_NAVIGATION_WINDOW": "Gezinme penceresini aç", - "ERROR": "Hata", - "INTERNAL_ERROR": "Dahili hata", - "ERROR_SAVE_LOCATION": "Dosyayı belirtilen konuma kaydedemedim", - "ERROR_WHILE_SAVING": "Kaydetme sırasında dahili bir hata oluştu. Lütfen tekrar deneyin.", - "UNKNOWN_ERROR_SAVING": "Kaydetme sırasında bir hata oluştu.", - "FAILED_ASSOCIATE_LOSPEC": "Lospec Palet protokolü ilişkilendirilemedi.", - "REDDIT": "Reddit", - "GITHUB": "GitHub", - "YOUTUBE": "YouTube", - "DONATE": "Bağış Yap", - "YES": "Evet", - "NO": "Hayır", - "CANCEL": "İptal", - "UNNAMED": "İsimsiz", - "OPEN_COMMAND_DEBUG_WINDOW": "Komut hata ayıklama penceresini aç", - "DELETE": "Sil", - "USER_PREFS": "Kullanıcı tercihleri (Gezinti)", - "SHORTCUT_FILE": "Kısayol dosyası (Gezinti)", - "EDITOR_DATA": "Düzenleyici verileri (Yerel)", - "MOVE_VIEWPORT_TOOLTIP": "Görünümü taşı. ({0})", - "MOVE_VIEWPORT_ACTION_DISPLAY": "Görünümü kaydırmak için tıklayın ve sürükleyin", - "MOVE_TOOL_TOOLTIP": "Seçili pikselleri taşır ({0}). Tüm katmanları taşımak için Ctrl tuşuna basılı tutun.", - "MOVE_TOOL_ACTION_DISPLAY": "Seçili pikselleri taşımak için fareyi basılı tutun. Tüm katmanları taşımak için Ctrl tuşuna basılı tutun.", - "PEN_TOOL_TOOLTIP": "Kalem. ({0})", - "PEN_TOOL_ACTION_DISPLAY": "Çizmek için tıklayın ve sürükleyin.", - "PIXEL_PERFECT_SETTING": "Mükemmel piksel", - "RECTANGLE_TOOL_TOOLTIP": "Tuval üzerinde dikdörtgen çizer ({0}). Kare çizmek için Shift tuşunu basılı tutun.", - "RECTANGLE_TOOL_ACTION_DISPLAY_DEFAULT": "Dikdörtgen çizmek için tıklayın ve sürükleyin. Kare çizmek için Shift tuşunu basılı tutun.", - "RECTANGLE_TOOL_ACTION_DISPLAY_SHIFT": "Kare çizmek için tıklayın ve sürükleyin.", - "KEEP_ORIGINAL_IMAGE_SETTING": "Orijinal resmi koru", - "ROTATE_VIEWPORT_TOOLTIP": "Görünümü döndür. ({0})", - "ROTATE_VIEWPORT_ACTION_DISPLAY": "Görünümü döndürmek için tıklayın ve sürükleyin", - "SELECT_TOOL_TOOLTIP": "Alan seçer. ({0})", - "SELECT_TOOL_ACTION_DISPLAY_DEFAULT": "Bir alan seçmek için tıklayın ve sürükleyin. Mevcut seçime eklemek için Shift tuşunu basılı tutun. Çıkarmak için Ctrl tuşuna basılı tutun.", - "SELECT_TOOL_ACTION_DISPLAY_SHIFT": "Mevcut seçime eklemek için tıklayın ve sürükleyin.", - "SELECT_TOOL_ACTION_DISPLAY_CTRL": "Mevcut seçimden çıkarmak için tıklayın ve sürükleyin.", - "ZOOM_TOOL_TOOLTIP": "Görünümü yakınlaştırır ({0}). Yakınlaştırmak için tıklayın, alt tuşunu basılı tutun ve tıklayınarak uzaklaştırın.", - "ZOOM_TOOL_ACTION_DISPLAY_DEFAULT": "Yakınlaştırmak için tıklayın ve sürükleyin. Yakınlaştırmak için Ctrl tuşunu basılı tutun ve tıklayınarak uzaklaştırın.", - "ZOOM_TOOL_ACTION_DISPLAY_CTRL": "Yakınlaştırmak için tıklayın ve sürükleyin. Uzaklaştırmak için Ctrl tuşunu bırakın ve tıklayın.", - "BRIGHTNESS_TOOL_TOOLTIP": "Pikselleri daha parlak veya daha koyu hale getirir ({0}). Pikselleri daha koyu yapmak için Ctrl tuşunu basılı tutun.", - "BRIGHTNESS_TOOL_ACTION_DISPLAY_DEFAULT": "Pikselleri daha parlak hale getirmek için üzerlerine çizin. Pikselleri daha koyu yapmak için Ctrl tuşunu basılı tutun.", - "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "Pikselleri daha koyu yapmak için üzerlerine çizin. Daha parlak hale getirmek için Ctrl tuşunu bırakın.", - "COLOR_PICKER_TOOLTIP": "Tuvalden birincil rengi seçer. ({0})", - "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "Renkleri seçmek için tıklayın. Tuvali gizlemek için Ctrl tuşunu basılı tutun. Referans katmanı gizlemek için Shift tuşunu basılı tutun", - "ELLIPSE_TOOL_TOOLTIP": "Tuval üzerinde bir elips çizer ({0}). Bir daire çizmek için Shift tuşunu basılı tutun.", - "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "Bir elips çizmek için tıklayın ve sürükleyin. Bir daire çizmek için Shift tuşunu basılı tutun.", - "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "Bir daire çizmek için tıklayın ve sürükleyin.", - "ERASER_TOOL_TOOLTIP": "Piksellerden rengi siler. ({0})", - "ERASER_TOOL_ACTION_DISPLAY": "Silmek için tıklayın ve sürükleyin.", - "FLOOD_FILL_TOOL_TOOLTIP": "Alanı renkle doldurur. ({0})", - "FLOOD_FILL_TOOL_ACTION_DISPLAY_DEFAULT": "Doldurmak için bir alana basın. Tüm katmanları dikkate almak için Ctrl tuşunu basılı tutun.", - "FLOOD_FILL_TOOL_ACTION_DISPLAY_CTRL": "Doldurmak için bir alana basın. Yalnızca geçerli katmanları dikkate almak için Ctrl tuşunu bırakın.", - "LASSO_TOOL_TOOLTIP": "Lasso. ({0})", - "LASSO_TOOL_ACTION_DISPLAY_DEFAULT": "Lasso içindeki pikselleri seçmek için tıklayın ve sürükleyin. Mevcut seçime eklemek için Shift tuşunu basılı tutun. Çıkarmak için Ctrl tuşunu basılı tutun.", - "LASSO_TOOL_ACTION_DISPLAY_SHIFT": "Lasso içindeki pikselleri seçmek için tıklayın ve sürükleyin.", - "LASSO_TOOL_ACTION_DISPLAY_CTRL": "Lasso içindeki pikselleri mevcut seçimden çıkarmak için tıklayın ve sürükleyin.", - "LINE_TOOL_TOOLTIP": "Tuval üzerinde çizgi çizer ({0}). Yakalama etkinleştirmek için Shift tuşunu basılı tutun.", - "LINE_TOOL_ACTION_DISPLAY_DEFAULT": "Bir çizgi çizmek için tıklayın ve sürükleyin. Yakalamayı etkinleştirmek için Shift tuşunu basılı tutun.", - "LINE_TOOL_ACTION_DISPLAY_SHIFT": "Yakalama etkinleştirilmiş bir çizgi çizmek için tıklayın ve sürükleyin.", - "MAGIC_WAND_TOOL_TOOLTIP": "Sihirli Değnek ({0}). Seçimi doldurur", - "MAGIC_WAND_ACTION_DISPLAY": "Seçimi doldurmak için tıklayın.", - "PEN_TOOL": "Kalem", - "BRIGHTNESS_TOOL": "Parlaklık", - "COLOR_PICKER_TOOL": "Renk Seçici", - "ELLIPSE_TOOL": "Elips", - "ERASER_TOOL": "Silgi", - "FLOOD_FILL_TOOL": "Doldurma", - "LASSO_TOOL": "Lasso", - "LINE_TOOL": "Çizgi", - "MAGIC_WAND_TOOL": "Sihirli Değnek", - "MOVE_TOOL": "Taşı", - "MOVE_VIEWPORT_TOOL": "Görünümü Taşı", - "RECTANGLE_TOOL": "Dikdörtgen", - "ROTATE_VIEWPORT_TOOL": "Görünümü Döndür", - "SELECT_TOOL_NAME": "Seçim", - "ZOOM_TOOL": "Yakınlaştırma", - "SHAPE_LABEL": "Şekil", - "MODE_LABEL": "Mod", - "SCOPE_LABEL": "Kapsam", - "FILL_SHAPE_LABEL": "Şekli Doldur", - "FILL_COLOR_LABEL": "Renk Doldur", - "TOOL_SIZE_LABEL": "Araç Boyutu", - "STRENGTH_LABEL": "Güç", - "NEW": "Yeni", - "ADD": "Ekle", - "SUBTRACT": "Çıkar", - "INTERSECT": "Kesişim", - "RECTANGLE": "Dikdörtgen", - "CIRCLE": "Daire", - "ABOUT": "Hakkında", - "MINIMIZE": "Simge Durumuna Küçült", - "RESTORE": "Geri Al", - "MAXIMIZE": "Büyüt", - "CLOSE": "Kapat", - "EXPORT_SIZE_HINT": "Görüntüyü paylaşmak isterseniz, en iyi netlik için {0}% önerilir", - "CREATE": "Oluştur", - "BASE_LAYER_NAME": "Ana katman", - "ENABLE_MASK": "Maskeleri Etkinleştir", - "SELECTED_AREA_EMPTY": "Seçilen alan boş", - "NOTHING_TO_COPY": "Kopyalanacak bir şey yok", - "REFERENCE_LAYER_PATH": "Referans katmanı yolu", - "FLIP": "Çevir", - "ROTATION": "Döndürme", - "ROT_IMG_90_D": "Görüntüyü 90° Döndür", - "ROT_IMG_180_D": "Görüntüyü 180° Döndür", - "ROT_IMG_-90_D": "Görüntüyü -90° Döndür", - "ROT_LAYERS_90_D": "Seçili Katmanları 90° Döndür", - "ROT_LAYERS_180_D": "Seçili Katmanları 180° Döndür", - "ROT_LAYERS_-90_D": "Seçili Katmanları -90° Döndür", - "UNNAMED_PALETTE": "Adsız Palet", - "CLICK_SELECT_PRIMARY": "Ana rengi seçmek için tıklayın.", - "PEN_MODE": "Kalem modu", - "VIEW": "Görünüm", - "HORIZONTAL_LINE_SYMMETRY": "Yatay çizgi simetrisi", - "VERTICAL_LINE_SYMMETRY": "Dikey çizgi simetrisi", - "COLOR_PICKER_TITLE": "Renk Seçici", - "COLOR_SLIDERS_TITLE": "Renk Kaydırıcıları", - "PALETTE_TITLE": "Palet", - "SWATCHES_TITLE": "Renk Örnekleri", - "LAYERS_TITLE": "Katmanlar", - "NAVIGATION_TITLE": "Gezinme", - "NORMAL_BLEND_MODE": "Normal", - "DARKEN_BLEND_MODE": "Karartma", - "MULTIPLY_BLEND_MODE": "Çarpma", - "COLOR_BURN_BLEND_MODE": "Renk Yakma", - "LIGHTEN_BLEND_MODE": "Aydınlatma", - "SCREEN_BLEND_MODE": "Ekran", - "COLOR_DODGE_BLEND_MODE": "Renk Geçişi", - "OVERLAY_BLEND_MODE": "Üzerine Yerleştirme", - "SOFT_LIGHT_BLEND_MODE": "Yumuşak Işık", - "HARD_LIGHT_BLEND_MODE": "Sert Işık", - "DIFFERENCE_BLEND_MODE": "Fark", - "EXCLUSION_BLEND_MODE": "Dışlama", - "HUE_BLEND_MODE": "Ton", - "SATURATION_BLEND_MODE": "Doygunluk", - "LUMINOSITY_BLEND_MODE": "Parlaklık", - "COLOR_BLEND_MODE": "Renk", - "NOT_SUPPORTED_BLEND_MODE": "Desteklenmeyen", - "RESTART": "Yeniden Başlat", - "SORT_BY": "Sırala", - "NAME": "İsim", - "COLORS": "Renkler", - "DEFAULT": "Varsayılan", - "ALPHABETICAL": "Alfabetik", - "COLOR_COUNT": "Renk sayısı", - "ANY": "Herhangi biri", - "MAX": "Maksimum", - "MIN": "Minimum", - "EXACT": "Tam", - "ASCENDING": "Artan", - "DESCENDING": "Azalan", - "NAME_IS_TOO_LONG": "İsim çok uzun", - "STOP_IT_TEXT1": "Yeter. Dosya isimlerinizi düzgünleştirin.", - "STOP_IT_TEXT2": "Bu isimleri kopyalamayı bırakabilir misiniz?", - "REPLACER_TOOLTIP": "Palet renginin üzerine sağ tıklayın ve 'Değiştir' seçeneğini seçin veya buraya bırakın.", - "CLICK_TO_CHOOSE_COLOR": "Renk seçmek için tıklayın", - "REPLACE_COLOR": "Rengi değiştir", - "PALETTE_COLOR_TOOLTIP": "Ana renk olarak seçmek için tıklayın. Başka bir palet renginin üzerine sürükleyip bırakarak değiştirin.", - "ADD_FROM_SWATCHES": "Renk örneklerinden ekle", - "ADD_COLOR_TO_PALETTE": "Palet'e renk ekle", - "USE_IN_CURRENT_IMAGE": "Geçerli görüntüde kullan", - "ADD_TO_FAVORITES": "Favorilere ekle", - "BROWSE_PALETTES": "Paletlere göz at", - "LOAD_PALETTE": "Paleti yükle", - "SAVE_PALETTE": "Paleti kaydet", - "FAVORITES": "Favoriler", - "ADD_FROM_CURRENT_PALETTE": "Mevcut paletten ekle", - "OPEN_PALETTES_DIR_TOOLTIP": "Paletler dizinini gezginde aç", - "BROWSE_ON_LOSPEC_TOOLTIP": "Lospec'te paletlere göz at", - "IMPORT_FROM_FILE_TOOLTIP": "Dosyadan içe aktar", - "TOP_LEFT": "Sol üst", - "TOP_CENTER": "Orta üst", - "TOP_RIGHT": "Sağ üst", - "MIDDLE_LEFT": "Sol orta", - "MIDDLE_CENTER": "Orta", - "MIDDLE_RIGHT": "Sağ orta", - "BOTTOM_LEFT": "Sol alt", - "BOTTOM_CENTER": "Orta alt", - "BOTTOM_RIGHT": "Sağ alt", - "CLIP_TO_BELOW": "Alt katmana kırp", - "MOVE_UPWARDS": "Yukarı taşı", - "MOVE_DOWNWARDS": "Aşağı taşı", - "MERGE_SELECTED": "Seçilenleri birleştir", - "LOCK_TRANSPARENCY": "Şeffaflığı kilitle", - "COULD_NOT_LOAD_PALETTE": "Palet alınamadı", - "NO_PALETTES_FOUND": "Palet bulunamadı.", - "LOSPEC_LINK_TEXT": "İşte bazılarına buradan ulaşabilirsiniz: lospec.com/palette-list", - "PALETTE_BROWSER": "Palet Tarayıcısı", - "DELETE_PALETTE_CONFIRMATION": "Bu paleti silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", - "SHORTCUTS_IMPORTED": "{0} kaynaklı kısayollar başarıyla içe aktarıldı.", - "SHORTCUT_PROVIDER_DETECTED": "{0} yüklendiği tespit edildi. Kısayollarını içe aktarmak istiyor musunuz?", - "IMPORT_FROM_INSTALLATION": "Yüklemeyi içe aktar", - "IMPORT_INSTALLATION_OPTION1": "Yüklemeyi içe aktar", - "IMPORT_INSTALLATION_OPTION2": "Varsayılanları kullan", - "IMPORT_FROM_TEMPLATE": "Şablondan içe aktar", - "SHORTCUTS_IMPORTED_SUCCESS": "Kısayollar başarıyla içe aktarıldı.", - "WARNING_RESET_SHORTCUTS_DEFAULT": "Tüm kısayolları varsayılan değerlerine sıfırlamak istediğinizden emin misiniz?", - "SUCCESS": "Başarılı", - "WARNING": "Uyarı", - "ERROR_IMPORTING_IMAGE": "Resim içe aktarılırken bir hata oluştu.", - "SHORTCUTS_CORRUPTED_TITLE": "Bozuk kısayollar dosyası", - "SHORTCUTS_CORRUPTED": "Kısayollar dosyası bozuldu, varsayılanlara sıfırlanıyor.", - "FAILED_DOWNLOAD_PALETTE": "Palet indirilemedi", - "FILE_INCORRECT_FORMAT": "Dosya doğru bir formatta değil", - "INVALID_FILE": "Geçersiz dosya", - "SHORTCUTS_FILE_INCORRECT_FORMAT": "Kısayollar dosyası doğru bir formatta değil", - "UNSUPPORTED_FILE_FORMAT": "Bu dosya formatı desteklenmiyor", - "ALREADY_ASSIGNED": "Zaten atanmış", - "REPLACE": "Değiştir", - "SWAP": "Değiştir", - "SHORTCUT_ALREADY_ASSIGNED_SWAP": "Bu kısayol zaten '{0}' için atanmış\nMevcut kısayolu değiştirmek veya ikisini değiştirmek istiyor musunuz?", - "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "Bu kısayol zaten '{0}' için atanmış\nMevcut kısayolu değiştirmek istiyor musunuz?", - "UNSAVED_CHANGES": "Kaydedilmemiş değişiklikler", - "DOCUMENT_MODIFIED_SAVE": "Belge değiştirildi. Değişiklikleri kaydetmek istiyor musunuz?", - "SESSION_UNSAVED_DATA": "{0} kaydedilmemiş veri içeriyor. Emin misiniz?", - "PROJECT_MAINTAINERS": "Proje Bakımcıları", - "OTHER_AWESOME_CONTRIBUTORS": "Ve diğer harika katkıda bulunanlar", - "HELP": "Yardım", - "STOP_IT_TEXT3": "Hayır gerçekten, yeter.", - "STOP_IT_TEXT4": "Daha iyi yapacak başka bir şeyiniz yok mu?", - "LINEAR_DODGE_BLEND_MODE": "Lineer dodge (Ekleme)", - "PRESS_ANY_KEY": "Herhangi bir tuşa basın", - "NONE_SHORTCUT": "Hiçbiri", - "REFERENCE": "Referans", - "PUT_REFERENCE_LAYER_ABOVE": "Referans katmanını üste yerleştir", - "PUT_REFERENCE_LAYER_BELOW": "Referans katmanını alta yerleştir", - "TOGGLE_VERTICAL_SYMMETRY": "Dikey simetriyi aç/kapat", - "TOGGLE_HORIZONTAL_SYMMETRY": "Yatay simetriyi aç/kapat", - "RESET_VIEWPORT": "Görünümü sıfırla", - "VIEWPORT_SETTINGS": "Görünüm ayarları", - "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "Tıklanıp basılı tutarak seçili katmanlardaki pikselleri taşımak için fareyi kullanın.", - "MOVE_TOOL_ACTION_DISPLAY_CTRL": "Tüm katmanları taşımak için fareyi basılı tutun.", - "CTRL_KEY": "Ctrl", - "SHIFT_KEY": "Shift", - "ALT_KEY": "Alt", - "RENAME": "Yeniden adlandır", - "PIXEL_UNIT": "px", - "OPEN_LOCALIZATION_DEBUG_WINDOW": "Yerelleştirme Hata Ayıklama Penceresini Aç", - "FORCE_OTHER_FLOW_DIRECTION": "Diğer akış yönünü zorla", - "API_KEY": "API Anahtarı", - "LOCALIZATION_VIEW_TYPE": "Yerelleştirme Görünüm Türü", - "LOAD_LANGUAGE_FROM_FILE": "Dili dosyadan yükle", - "LOG_IN": "Giriş yap", - "SYNC": "Senkronize Et", - "NOT_LOGGED_IN": "Giriş Yapılmadı", - "POE_EDITOR_ERROR": "POEditor Hatası: {0} {1}", - "HTTP_ERROR_MESSAGE": "HTTP Hatası: {0} {1}", - "LOGGED_IN": "Giriş Yapıldı", - "SYNCED_SUCCESSFULLY": "Başarıyla Senkronize Edildi", - "EXCEPTION_ERROR": "Hata: {0}", - "DROP_PALETTE": "Paleti Buraya Bırak", - "SECURITY_ERROR": "Güvenlik Hatası", - "SECURITY_ERROR_MSG": "Belirtilen konuma yazma izni yok.", - "IO_ERROR": "G/Ç Hatası", - "IO_ERROR_MSG": "Disk'e yazarken hata oluştu.", - "FAILED_ASSOCIATE_PIXI": ".pixi dosyası PixiEditor ile ilişkilendirilemedi.", - "COULD_NOT_SAVE_PALETTE": "Palet kaydedilirken bir hata oluştu.", - "NO_COLORS_TO_SAVE": "Kaydedilecek renk bulunmuyor.", - "ALL_LAYERS": "Tüm Katmanlar", - "SINGLE_LAYER": "Tek Katman", - "CHOOSE": "Seç", - "REMOVE": "Kaldır", - "FILE_FORMAT_NOT_ASEPRITE_KEYS": "Dosya bir \".aseprite-keys\" dosyası değil", - "FILE_HAS_INVALID_SHORTCUT": "Dosya geçersiz bir kısayol içeriyor", - "FILE_EXTENSION_NOT_SUPPORTED": "Dosya türü '{0}' desteklenmiyor", - "ERROR_READING_FILE": "Dosya okunurken hata oluştu", - "DISCARD_PALETTE": "Paleti At", - "DISCARD_PALETTE_CONFIRMATION": "Mevcut paleti atmak istediğinizden emin misiniz? Bu işlem geri alınamaz.", - "IMPORT_AS_NEW_LAYER": "Yeni katman olarak içe aktar", - "PASTE_AS_PRIMARY_COLOR": "Birincil renk olarak yapıştır", - "IMPORT_AS_NEW_FILE": "Yeni dosya olarak içe aktar", - "IMPORT_PALETTE_FILE": "Palet dosyasını içe aktar", - "IMPORT_MULTIPLE_PALETTE_COLORS": "Renkleri palet içine aktar", - "IMPORT_SINGLE_PALETTE_COLOR": "Renkleri palet içine aktar", - "IMPORT_AS_REFERENCE_LAYER": "Referans katmanı olarak içe aktar", - "NAVIGATOR_PICK_ACTION_DISPLAY": "Renk seçmek için sağ tıklayın, Renkleri panoya kopyalamak için Shift tuşuna basılı tutun", - "OPEN_FILE_FROM_CLIPBOARD": "Panodan aç", - "OPEN_FILE_FROM_CLIPBOARD_DESCRIPTIVE": "Panodan aç", - "OPEN_LOCALIZATION_DATA": "LocalizationData.json dosyasını mı açmak istiyorsunuz?\nGüncellenmiş tarih panoya kopyalandı.\nNot Değişiklikler, yeniden başlatma yapılana kadar uygulanmayacaktır", - "DOWNLOADING_LANGUAGE_FAILED": "Dil indirme başarısız oldu.\nAPI Anahtarı aşırı kullanılmış olabilir.", - "LOCALIZATION_DATA_NOT_FOUND": "Yerelleştirme veri yolu bulunamadı", - "APPLY": "Uygula", - "UPDATE_SOURCE": "Kaynağı Güncelle", - "COPY_TO_CLIPBOARD": "Panoya Kopyala", - "LANGUAGE_FILE_NOT_FOUND": "Dil dosyası bulunamadı.\n'{0}' dosyası aranıyor", - "PROJECT_ROOT_NOT_FOUND": "PixiEditor proje kökü bulunamadı.\nPixiEditor.csproj dosyası aranıyor", - "LOCALIZATION_FOLDER_NOT_FOUND": "Yerelleştirme klasörü bulunamadı.\n/Data/Localization dizini aranıyor", - "SELECT_A_LANGUAGE": "Bir dil seçin", - "DONE": "Tamamlandı", - "SOURCE_UNSET_OR_MISSING": "Kaynak eksik/belirtilmemiş", - "SOURCE_NEWER": "Kaynak daha yeni", - "SOURCE_UP_TO_DATE": "Kaynak güncel", - "SOURCE_OLDER": "Bulut daha yeni", - "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Referans katmanından renk seçmek için tıklayın.", - "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Tuvalden renk seçmek için tıklayın.", - "LOCALIZATION_DEBUG_WINDOW_TITLE": "Yerelleştirme Hata Ayıklama Penceresi", - "COMMAND_DEBUG_WINDOW_TITLE": "Komut Hata Ayıklama Penceresi", - "SHORTCUTS_TITLE": "Kısayollar", - "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_PERSPECTIVE": "Dönüştürmeyi ölçeklendirmek için kolları sürükleyin. Bir kolun üzerine tıklayıp Ctrl tuşunu basılı tutarak kolun serbestçe hareket etmesini sağlayın. Orantılı olarak ölçeklendirmek için Shift tuşunu basılı tutun. Bir yan kolun üzerine tıklayıp Alt tuşunu basılı tutarak kaydırın. Dış kolları sürükleyerek döndürün.", - "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE": "Dönüştürmeyi ölçeklendirmek için kolları sürükleyin. Orantılı olarak ölçeklendirmek için Shift tuşunu basılı tutun. Bir yan kolun üzerine tıklayıp Alt tuşunu basılı tutarak kaydırın. Dış kolları sürükleyerek döndürün.", - "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE": "Dönüştürmeyi ölçeklendirmek için kolları sürükleyin. Shift tuşunu basılı tutunarak orantılı olarak ölçeklendirin. Dış kolları sürükleyerek döndürün.", - "TRANSFORM_ACTION_DISPLAY_SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE": "Dönüştürmeyi ölçeklendirmek için kolları sürükleyin. Shift tuşunu basılı tutarak orantılı olarak ölçeklendirin.", - "OPEN_DOCUMENTATION": "Dokümantasyonu aç", - "LOCAL_PALETTE_SOURCE_NAME": "Yerel", - "ERROR_FORBIDDEN_UNIQUE_NAME": "Uzantı benzersiz adı 'pixieditor' ile başlayamaz.", - "ERROR_MISSING_METADATA": "Uzantı metaverisi '{0}' eksik.", - "ERROR_NO_CLASS_ENTRY": "Uzantı sınıf girişi '{0}' yolda eksik.", - "ERROR_NO_ENTRY_ASSEMBLY": "Uzantı giriş derlemesi '{0}' yolda eksik.", - "ERROR_MISSING_ADDITIONAL_CONTENT": "Mevcut yapılandırmanız bu uzantının yüklenmesine izin vermiyor. Belki de sahibi değilsiniz veya yüklü değil. Uzantıyı buradan satın alabilirsiniz '{0}'.", - "BUY_SUPPORTER_PACK": "Destekçi Paketi Satın Al", - "NEWS": "Haberler", - "DISABLE_NEWS_PANEL": "Başlangıç penceresinde Haberler panelini devre dışı bırak", - "FAILED_FETCH_NEWS": "Haberleri alırken hata oluştu", - "CROP_TO_SELECTION": "Seçime Kırp", - "CROP_TO_SELECTION_DESCRIPTIVE": "Resmi seçime kırp", - "SHOW_CONTEXT_MENU": "Bağlam Menüsünü Göster", - "ERASE": "Sil", - "USE_SECONDARY_COLOR": "İkincil rengi kullan", - "RIGHT_CLICK_MODE": "Sağ Tıklama Modu", - "ADD_PRIMARY_COLOR_TO_PALETTE": "Birincil rengi palete ekle", - "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "Birincil rengi mevcut palete ekle", - "COPY_COLOR": "Rengi Kopyala" + "RECENT_FILES": "Son Dosyalar", + "OPEN_FILE": "Dosya Aç", + "NEW_FILE": "Yeni", + "RECENT_EMPTY_TEXT": "Çok fazla boşluk", + "LANGUAGE": "Dil", + "GENERAL": "Genel", + "DISCORD": "Discord", + "KEY_BINDINGS": "Tuş Atamaları", + "MISC": "Çeşitli", + "SHOW_STARTUP_WINDOW": "Başlangıç Ekranını Göster", + "SHOW_IMAGE_PREVIEW_TASKBAR": "Görev çubuğunda resim önizlemesini göster", + "RECENT_FILE_LENGTH": "Son dosya listesi uzunluğu", + "RECENT_FILE_LENGTH_TOOLTIP": "Dosya > Son dosyalar altında gösterilen belge sayısı. Varsayılan: 8", + "DEFAULT_NEW_SIZE": "Varsayılan yeni dosya boyutu", + "WIDTH": "Genişlik", + "HEIGHT": "Yükseklik", + "TOOLS": "Araçlar", + "ENABLE_SHARED_TOOLBAR": "Paylaşılan araç çubuğunu etkinleştir", + "AUTOMATIC_UPDATES": "Otomatik Güncellemeler", + "CHECK_FOR_UPDATES": "Başlangıçta güncellemeleri kontrol et", + "UPDATE_STREAM": "Güncelleme akışı", + "UPDATE_CHANNEL_HELP_TOOLTIP": "Güncelleme kanalları yalnızca bağımsız sürümde (https://pixieditor.net'den indirilen) değiştirilebilir.\nSteam ve Microsoft Store sürümleri güncellemeleri ayrı şekilde yönetir.", + "DEBUG": "Hata Ayıklama", + "ENABLE_DEBUG_MODE": "Hata Ayıklama modunu etkinleştir", + "OPEN_CRASH_REPORTS_DIR": "Çökme raporları dizinini aç", + "DISCORD_RICH_PRESENCE": "Zengin Durum", + "ENABLED": "Etkin", + "SHOW_IMAGE_NAME": "Resim adını göster", + "SHOW_IMAGE_SIZE": "Resim boyutunu göster", + "SHOW_LAYER_COUNT": "Katman sayısını göster", + "FILE": "Dosya", + "RECENT": "Son", + "OPEN": "Aç", + "SAVE_PIXI": "Kaydet (.pixi)", + "SAVE_AS_PIXI": "Farklı Kaydet... (.pixi)", + "EXPORT_IMG": "Dışa Aktar (.png, .jpg, vb.)", + "EDIT": "Düzenle", + "EXIT": "Çıkış", + "PERCENTAGE": "Yüzde", + "ABSOLUTE": "Mutlak", + "PRESERVE_ASPECT_RATIO": "En-boy oranını koru", + "ANCHOR_POINT": "Bağlama Noktası", + "RESIZE_IMAGE": "Resmi Yeniden Boyutlandır", + "RESIZE": "Yeniden Boyutlandır", + "DOCUMENTATION": "Dokümantasyon", + "WEBSITE": "Web Sitesi", + "OPEN_WEBSITE": "Web sitesini aç", + "REPOSITORY": "Depo", + "OPEN_REPOSITORY": "Depoyu aç", + "LICENSE": "Lisans", + "OPEN_LICENSE": "Lisansı aç", + "THIRD_PARTY_LICENSES": "Üçüncü Taraf Lisansları", + "OPEN_THIRD_PARTY_LICENSES": "Üçüncü taraf lisanslarını aç", + "APPLY_TRANSFORM": "Dönüşümü Uygula", + "INCREASE_TOOL_SIZE": "Araç boyutunu artır", + "DECREASE_TOOL_SIZE": "Araç boyutunu azalt", + "TO_INSTALL_UPDATE": "güncellemeyi yüklemek için {0}", + "DOWNLOADING_UPDATE": "Güncelleme indiriliyor...", + "UPDATE_READY": "Güncelleme yüklemeye hazır. Şimdi yüklemek istiyor musunuz?", + "NEW_UPDATE": "Yeni güncelleme", + "COULD_NOT_UPDATE_WITHOUT_ADMIN": "Yönetici ayrıcalıkları olmadan güncelleme yapılamadı. Lütfen PixiEditor'ü yönetici olarak çalıştırın.", + "INSUFFICIENT_PERMISSIONS": "Yetersiz izinler", + "UPDATE_CHECK_FAILED": "Güncelleme kontrolü başarısız oldu", + "COULD_NOT_CHECK_FOR_UPDATES": "Güncelleme mevcut olup olmadığı kontrol edilemedi.", + "VERSION": "Sürüm {0}", + "OPEN_TEMP_DIR": "Geçici dizini aç", + "OPEN_LOCAL_APPDATA_DIR": "Yerel AppData dizinini aç", + "OPEN_ROAMING_APPDATA_DIR": "Gezintili AppData dizinini aç", + "OPEN_INSTALLATION_DIR": "Kurulum dizinini aç", + "DUMP_ALL_COMMANDS": "Tüm komutları dök", + "DUMP_ALL_COMMANDS_DESCRIPTIVE": "Tüm komutları bir metin dosyasına dök", + "CRASH": "Çökme", + "CRASH_APP": "Uygulamayı çökert", + "DELETE_USR_PREFS": "Kullanıcı tercihlerini sil (Gezintili AppData)", + "DELETE_SHORTCUT_FILE": "Kısayol dosyasını sil (Gezintili AppData)", + "DELETE_EDITOR_DATA": "Düzenleyici verilerini sil (Yerel AppData)", + "GENERATE_KEY_BINDINGS_TEMPLATE": "Tuş atama şablonu oluştur", + "GENERATE_KEY_BINDINGS_TEMPLATE_DESCRIPTIVE": "Tuş atama json şablonu oluştur", + "VALIDATE_SHORTCUT_MAP": "Kısayol haritasını doğrula", + "VALIDATE_SHORTCUT_MAP_DESCRIPTIVE": "Kısayol haritasını doğrular", + "VALIDATION_KEYS_NOTICE_DIALOG": "Boş anahtarlar: {0}\nBilinmeyen Komutlar: {1}", + "RESULT": "Sonuç", + "CLEAR_RECENT_DOCUMENTS": "Son belgeleri temizle", + "CLEAR_RECENTLY_OPENED_DOCUMENTS": "Yeni açılan belgeleri temizle", + "OPEN_CMD_DEBUG_WINDOW": "Komut hata ayıklama penceresini aç", + "PATH_DOES_NOT_EXIST": "{0} mevcut değil.", + "LOCATION_DOES_NOT_EXIST": "Konum mevcut değil.", + "FILE_NOT_FOUND": "Dosya bulunamadı.", + "ARE_YOU_SURE": "Emin misiniz?", + "ARE_YOU_SURE_PATH_FULL_PATH": "{0}'yi silmek istediğinizden emin misiniz?\nBu veri tüm yüklemeler için kaybolacak.\n(Tam Yol: {1})", + "FAILED_TO_OPEN_FILE": "Dosya açılamadı", + "OLD_FILE_FORMAT": "Eski dosya formatı", + "OLD_FILE_FORMAT_DESCRIPTION": "Bu .pixi dosyası eski formatta kullanılmıştır,\n artık desteklenmiyor ve açılamaz.", + "NOTHING_FOUND": "Hiçbir şey bulunamadı", + "EXPORT": "Dışa Aktar", + "EXPORT_IMAGE": "Resmi Dışa Aktar", + "IMPORT": "İçe Aktar", + "SHORTCUT_TEMPLATES": "Kısayol şablonları", + "RESET_ALL": "Hepsini Sıfırla", + "LAYER": "Katman", + "LAYER_DELETE_SELECTED": "Aktif katmanı/dizini sil", + "LAYER_DELETE_SELECTED_DESCRIPTIVE": "Aktif katmanı veya dizini sil", + "LAYER_DELETE_ALL_SELECTED": "Tüm seçili katmanları/dizinleri sil", + "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "Tüm seçili katmanları ve/veya dizinleri sil", + "DELETE_SELECTED_PIXELS": "Seçili pikselleri sil", + "NEW_FOLDER": "Yeni klasör", + "CREATE_NEW_FOLDER": "Yeni klasör oluştur", + "NEW_LAYER": "Yeni katman", + "CREATE_NEW_LAYER": "Yeni katman oluştur", + "NEW_IMAGE": "Yeni görüntü", + "CREATE_NEW_IMAGE": "Yeni görüntü oluştur", + "SAVE": "Kaydet", + "SAVE_AS": "Farklı Kaydet...", + "IMAGE": "Görüntü", + "SAVE_IMAGE": "Görüntüyü kaydet", + "SAVE_IMAGE_AS": "Yeni olarak görüntüyü kaydet", + "DUPLICATE": "Kopyala", + "DUPLICATE_SELECTED_LAYER": "Seçili katmanı kopyala", + "CREATE_MASK": "Maske oluştur", + "DELETE_MASK": "Maskeyi sil", + "TOGGLE_MASK": "Maskesi aç/kapat", + "APPLY_MASK": "Maskeyi uygula", + "TOGGLE_VISIBILITY": "Görünürlüğü aç/kapat", + "MOVE_MEMBER_UP": "Üyeyi yukarı taşı", + "MOVE_MEMBER_UP_DESCRIPTIVE": "Seçili katmanı veya dizini yukarı taşı", + "MOVE_MEMBER_DOWN": "Üyeyi aşağı taşı", + "MOVE_MEMBER_DOWN_DESCRIPTIVE": "Seçili katmanı veya dizini aşağı taşı", + "MERGE_ALL_SELECTED_LAYERS": "Tüm seçili katmanları birleştir", + "MERGE_WITH_ABOVE": "Seçili katmanı üsttekiyle birleştir", + "MERGE_WITH_ABOVE_DESCRIPTIVE": "Seçili katmanı üzerindekiyle birleştir", + "MERGE_WITH_BELOW": "Seçili katmanı alttakilerle birleştir", + "MERGE_WITH_BELOW_DESCRIPTIVE": "Seçili katmanı altındakilerle birleştir", + "ADD_REFERENCE_LAYER": "Referans Katman Ekle", + "DELETE_REFERENCE_LAYER": "Referans katmanı sil", + "TRANSFORM_REFERENCE_LAYER": "Referans katmanını dönüştür", + "TOGGLE_REFERENCE_LAYER_POS": "Referans katmanının konumunu değiştir", + "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "Referans katmanını en üstte veya en altta arasında değiştir", + "RESET_REFERENCE_LAYER_POS": "Referans katmanının konumunu sıfırla", + "CLIP_CANVAS": "Kanvası Kırp", + "FLIP_IMG_VERTICALLY": "Görüntüyü Dikey Olarak Çevir", + "FLIP_IMG_HORIZONTALLY": "Görüntüyü Yatay Olarak Çevir", + "FLIP_LAYERS_VERTICALLY": "Seçili Katmanları Dikey Olarak Çevir", + "FLIP_LAYERS_HORIZONTALLY": "Seçili Katmanları Yatay Olarak Çevir", + "ROT_IMG_90": "Görüntüyü 90 derece döndür", + "ROT_IMG_180": "Görüntüyü 180 derece döndür", + "ROT_IMG_-90": "Görüntüyü -90 derece döndür", + "ROT_LAYERS_90": "Seçili Katmanları 90 derece döndür", + "ROT_LAYERS_180": "Seçili Katmanları 180 derece döndür", + "ROT_LAYERS_-90": "Seçili Katmanları -90 derece döndür", + "TOGGLE_VERT_SYMMETRY_AXIS": "Dikey simetri eksenini aç/kapat", + "TOGGLE_HOR_SYMMETRY_AXIS": "Yatay simetri eksenini aç/kapat", + "DELETE_PIXELS": "Pikselleri sil", + "DELETE_PIXELS_DESCRIPTIVE": "Seçili pikselleri sil", + "RESIZE_DOCUMENT": "Belgeyi Yeniden Boyutlandır", + "RESIZE_CANVAS": "Kanvası Yeniden Boyutlandır", + "CENTER_CONTENT": "İçeriği Ortala", + "CUT": "Kes", + "CUT_DESCRIPTIVE": "Seçili alanı/katmanları kes", + "PASTE": "Yapıştır", + "PASTE_DESCRIPTIVE": "Pano içeriğini yapıştır", + "PASTE_AS_NEW_LAYER": "Yeni katman olarak yapıştır", + "PASTE_AS_NEW_LAYER_DESCRIPTIVE": "Panodan yeni bir katman olarak yapıştır", + "PASTE_REFERENCE_LAYER": "Referans katmanını yapıştır", + "PASTE_REFERENCE_LAYER_DESCRIPTIVE": "Panodaki içeriği referans katmanı olarak yapıştır", + "PASTE_COLOR": "Rengi yapıştır", + "PASTE_COLOR_DESCRIPTIVE": "Panodan rengi yapıştır", + "PASTE_COLOR_SECONDARY": "İkincil olarak yapıştır", + "PASTE_COLOR_SECONDARY_DESCRIPTIVE": "Panodan rengi ikincil renk olarak yapıştır", + "CLIPBOARD": "Pano", + "COPY": "Kopyala", + "COPY_DESCRIPTIVE": "Panoya kopyala", + "COPY_COLOR_HEX": "Birincil rengi kopyala (HEX)", + "COPY_COLOR_HEX_DESCRIPTIVE": "Birincil rengi HEX kodu olarak kopyala", + "COPY_COLOR_RGB": "Birincil rengi kopyala (RGB)", + "COPY_COLOR_RGB_DESCRIPTIVE": "Birincil rengi RGB kodu olarak kopyala", + "COPY_COLOR_SECONDARY_HEX": "İkincil rengi kopyala (HEX)", + "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "İkincil rengi HEX kodu olarak kopyala", + "COPY_COLOR_SECONDARY_RGB": "İkincil rengi kopyala (RGB)", + "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "İkincil rengi RGB kodu olarak kopyala", + "PALETTE_COLORS": "Palet Renkleri", + "REPLACE_SECONDARY_BY_PRIMARY": "İkincil rengi birincil renk ile değiştir", + "REPLACE_SECONDARY_BY_PRIMARY_DESCRIPTIVE": "İkincil rengi birincil renk ile değiştir", + "REPLACE_PRIMARY_BY_SECONDARY": "Birincil rengi ikincil renk ile değiştir", + "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "Birincil rengi ikincil renk ile değiştir", + "OPEN_PALETTE_BROWSER": "Palet tarayıcısını aç", + "OVERWRITE_PALETTE_CONSENT": "'{0}' paleti zaten mevcut, üzerine yazmak istiyor musunuz?", + "PALETTE_EXISTS": "Palet zaten mevcut", + "REPLACE_PALETTE_CONSENT": "Geçerli palet seçilenle değiştirilsin mi?", + "REPLACE_PALETTE": "Geçerli paleti değiştir", + "SELECT_COLOR_1": "Renk 1'i seç", + "SELECT_COLOR_2": "Renk 2'yi seç", + "SELECT_COLOR_3": "Renk 3'ü seç", + "SELECT_COLOR_4": "Renk 4'ü seç", + "SELECT_COLOR_5": "Renk 5'i seç", + "SELECT_COLOR_6": "Renk 6'yı seç", + "SELECT_COLOR_7": "Renk 7'yi seç", + "SELECT_COLOR_8": "Renk 8'i seç", + "SELECT_COLOR_9": "Renk 9'u seç", + "SELECT_COLOR_10": "Renk 10'u seç", + "SELECT_TOOL": "{0} Aracını Seç", + "SELECT_COLOR_1_DESCRIPTIVE": "Paletin ilk rengini seçin", + "SELECT_COLOR_2_DESCRIPTIVE": "Paletin ikinci rengini seçin", + "SELECT_COLOR_3_DESCRIPTIVE": "Paletin üçüncü rengini seçin", + "SELECT_COLOR_4_DESCRIPTIVE": "Paletin dördüncü rengini seçin", + "SELECT_COLOR_5_DESCRIPTIVE": "Paletin beşinci rengini seçin", + "SELECT_COLOR_6_DESCRIPTIVE": "Paletin altıncı rengini seçin", + "SELECT_COLOR_7_DESCRIPTIVE": "Paletin yedinci rengini seçin", + "SELECT_COLOR_8_DESCRIPTIVE": "Paletin sekizinci rengini seçin", + "SELECT_COLOR_9_DESCRIPTIVE": "Paletin dokuzuncu rengini seçin", + "SELECT_COLOR_10_DESCRIPTIVE": "Paletin onuncu rengini seçin", + "SWAP_COLORS": "Renkleri Değiştir", + "SWAP_COLORS_DESCRIPTIVE": "Birincil ve ikincil renkleri değiştir", + "SEARCH": "Ara", + "COMMAND_SEARCH": "Komut arama", + "OPEN_COMMAND_SEARCH": "Komut arama penceresini aç", + "SELECT": "Seç", + "DESELECT": "Seçimi Kaldır", + "INVERT": "Tersine Çevir", + "SELECTION": "Seçim", + "SELECT_ALL": "Hepsini Seç", + "SELECT_ALL_DESCRIPTIVE": "Hepsini seç", + "CLEAR_SELECTION": "Seçimi Temizle", + "INVERT_SELECTION": "Seçimi Tersine Çevir", + "INVERT_SELECTION_DESCRIPTIVE": "Seçimi tersine çevir", + "TRANSFORM_SELECTED_AREA": "Seçili alanı dönüştür", + "NUDGE_SELECTED_LEFT": "Seçili nesneyi sola kaydır", + "NUDGE_SELECTED_RIGHT": "Seçili nesneyi sağa kaydır", + "NUDGE_SELECTED_UP": "Seçili nesneyi yukarı kaydır", + "NUDGE_SELECTED_DOWN": "Seçili nesneyi aşağı kaydır", + "MASK_FROM_SELECTION": "Yeni maske oluştur", + "MASK_FROM_SELECTION_DESCRIPTIVE": "Seçimi yeni maskeye dönüştür", + "ADD_SELECTION_TO_MASK": "Seçimi maskeye ekle", + "SUBTRACT_SELECTION_FROM_MASK": "Seçimi maskeye çıkar", + "INTERSECT_SELECTION_MASK": "Seçimi maskeyle kesiştir", + "SELECTION_TO_MASK": "Seçimi maskeye dönüştür", + "TO_NEW_MASK": "yeni maske için", + "ADD_TO_MASK": "maskeye ekle", + "SUBTRACT_FROM_MASK": "maskeden çıkar", + "INTERSECT_WITH_MASK": "maskelerle kesiştir", + "STYLUS": "Stylus", + "TOGGLE_PEN_MODE": "Kalem modunu aç/kapat", + "UNDO": "Geri Al", + "UNDO_DESCRIPTIVE": "Son işlemi geri al", + "REDO": "Yinele", + "REDO_DESCRIPTIVE": "Son işlemi yeniden yap", + "WINDOWS": "Windows", + "TOGGLE_GRIDLINES": "Kılavuz çizgilerini aç/kapat", + "ZOOM_IN": "Yakınlaştır", + "ZOOM_OUT": "Uzaklaştır", + "NEW_WINDOW_FOR_IMG": "Mevcut resim için yeni pencere aç", + "CENTER_ACTIVE_VIEWPORT": "Etkin görünümü ortala", + "FLIP_VIEWPORT_HORIZONTALLY": "Görünümü yatay olarak çevir", + "FLIP_VIEWPORT_VERTICALLY": "Görünümü dikey olarak çevir", + "SETTINGS": "Ayarlar", + "OPEN_SETTINGS": "Ayarları aç", + "OPEN_SETTINGS_DESCRIPTIVE": "Ayarlar penceresini aç", + "OPEN_STARTUP_WINDOW": "Başlangıç penceresini aç", + "OPEN_SHORTCUT_WINDOW": "Kısayol penceresini aç", + "OPEN_ABOUT_WINDOW": "Hakkında penceresini aç", + "OPEN_NAVIGATION_WINDOW": "Gezinme penceresini aç", + "ERROR": "Hata", + "INTERNAL_ERROR": "Dahili hata", + "ERROR_SAVE_LOCATION": "Dosyayı belirtilen konuma kaydedemedim", + "ERROR_WHILE_SAVING": "Kaydetme sırasında dahili bir hata oluştu. Lütfen tekrar deneyin.", + "UNKNOWN_ERROR_SAVING": "Kaydetme sırasında bir hata oluştu.", + "FAILED_ASSOCIATE_LOSPEC": "Lospec Palet protokolü ilişkilendirilemedi.", + "REDDIT": "Reddit", + "GITHUB": "GitHub", + "YOUTUBE": "YouTube", + "DONATE": "Bağış Yap", + "YES": "Evet", + "NO": "Hayır", + "CANCEL": "İptal", + "UNNAMED": "İsimsiz", + "OPEN_COMMAND_DEBUG_WINDOW": "Komut hata ayıklama penceresini aç", + "DELETE": "Sil", + "USER_PREFS": "Kullanıcı tercihleri (Gezinti)", + "SHORTCUT_FILE": "Kısayol dosyası (Gezinti)", + "EDITOR_DATA": "Düzenleyici verileri (Yerel)", + "MOVE_VIEWPORT_TOOLTIP": "Görünümü taşı. ({0})", + "MOVE_VIEWPORT_ACTION_DISPLAY": "Görünümü kaydırmak için tıklayın ve sürükleyin", + "MOVE_TOOL_TOOLTIP": "Seçili pikselleri taşır ({0}). Tüm katmanları taşımak için Ctrl tuşuna basılı tutun.", + "MOVE_TOOL_ACTION_DISPLAY": "Seçili pikselleri taşımak için fareyi basılı tutun. Tüm katmanları taşımak için Ctrl tuşuna basılı tutun.", + "PEN_TOOL_TOOLTIP": "Kalem. ({0})", + "PEN_TOOL_ACTION_DISPLAY": "Çizmek için tıklayın ve sürükleyin.", + "PIXEL_PERFECT_SETTING": "Mükemmel piksel", + "RECTANGLE_TOOL_TOOLTIP": "Tuval üzerinde dikdörtgen çizer ({0}). Kare çizmek için Shift tuşunu basılı tutun.", + "RECTANGLE_TOOL_ACTION_DISPLAY_DEFAULT": "Dikdörtgen çizmek için tıklayın ve sürükleyin. Kare çizmek için Shift tuşunu basılı tutun.", + "RECTANGLE_TOOL_ACTION_DISPLAY_SHIFT": "Kare çizmek için tıklayın ve sürükleyin.", + "KEEP_ORIGINAL_IMAGE_SETTING": "Orijinal resmi koru", + "ROTATE_VIEWPORT_TOOLTIP": "Görünümü döndür. ({0})", + "ROTATE_VIEWPORT_ACTION_DISPLAY": "Görünümü döndürmek için tıklayın ve sürükleyin", + "SELECT_TOOL_TOOLTIP": "Alan seçer. ({0})", + "SELECT_TOOL_ACTION_DISPLAY_DEFAULT": "Bir alan seçmek için tıklayın ve sürükleyin. Mevcut seçime eklemek için Shift tuşunu basılı tutun. Çıkarmak için Ctrl tuşuna basılı tutun.", + "SELECT_TOOL_ACTION_DISPLAY_SHIFT": "Mevcut seçime eklemek için tıklayın ve sürükleyin.", + "SELECT_TOOL_ACTION_DISPLAY_CTRL": "Mevcut seçimden çıkarmak için tıklayın ve sürükleyin.", + "ZOOM_TOOL_TOOLTIP": "Görünümü yakınlaştırır ({0}). Yakınlaştırmak için tıklayın, alt tuşunu basılı tutun ve tıklayınarak uzaklaştırın.", + "ZOOM_TOOL_ACTION_DISPLAY_DEFAULT": "Yakınlaştırmak için tıklayın ve sürükleyin. Yakınlaştırmak için Ctrl tuşunu basılı tutun ve tıklayınarak uzaklaştırın.", + "ZOOM_TOOL_ACTION_DISPLAY_CTRL": "Yakınlaştırmak için tıklayın ve sürükleyin. Uzaklaştırmak için Ctrl tuşunu bırakın ve tıklayın.", + "BRIGHTNESS_TOOL_TOOLTIP": "Pikselleri daha parlak veya daha koyu hale getirir ({0}). Pikselleri daha koyu yapmak için Ctrl tuşunu basılı tutun.", + "BRIGHTNESS_TOOL_ACTION_DISPLAY_DEFAULT": "Pikselleri daha parlak hale getirmek için üzerlerine çizin. Pikselleri daha koyu yapmak için Ctrl tuşunu basılı tutun.", + "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "Pikselleri daha koyu yapmak için üzerlerine çizin. Daha parlak hale getirmek için Ctrl tuşunu bırakın.", + "COLOR_PICKER_TOOLTIP": "Tuvalden birincil rengi seçer. ({0})", + "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "Renkleri seçmek için tıklayın. Tuvali gizlemek için Ctrl tuşunu basılı tutun. Referans katmanı gizlemek için Shift tuşunu basılı tutun", + "ELLIPSE_TOOL_TOOLTIP": "Tuval üzerinde bir elips çizer ({0}). Bir daire çizmek için Shift tuşunu basılı tutun.", + "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "Bir elips çizmek için tıklayın ve sürükleyin. Bir daire çizmek için Shift tuşunu basılı tutun.", + "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "Bir daire çizmek için tıklayın ve sürükleyin.", + "ERASER_TOOL_TOOLTIP": "Piksellerden rengi siler. ({0})", + "ERASER_TOOL_ACTION_DISPLAY": "Silmek için tıklayın ve sürükleyin.", + "FLOOD_FILL_TOOL_TOOLTIP": "Alanı renkle doldurur. ({0})", + "FLOOD_FILL_TOOL_ACTION_DISPLAY_DEFAULT": "Doldurmak için bir alana basın. Tüm katmanları dikkate almak için Ctrl tuşunu basılı tutun.", + "FLOOD_FILL_TOOL_ACTION_DISPLAY_CTRL": "Doldurmak için bir alana basın. Yalnızca geçerli katmanları dikkate almak için Ctrl tuşunu bırakın.", + "LASSO_TOOL_TOOLTIP": "Lasso. ({0})", + "LASSO_TOOL_ACTION_DISPLAY_DEFAULT": "Lasso içindeki pikselleri seçmek için tıklayın ve sürükleyin. Mevcut seçime eklemek için Shift tuşunu basılı tutun. Çıkarmak için Ctrl tuşunu basılı tutun.", + "LASSO_TOOL_ACTION_DISPLAY_SHIFT": "Lasso içindeki pikselleri seçmek için tıklayın ve sürükleyin.", + "LASSO_TOOL_ACTION_DISPLAY_CTRL": "Lasso içindeki pikselleri mevcut seçimden çıkarmak için tıklayın ve sürükleyin.", + "LINE_TOOL_TOOLTIP": "Tuval üzerinde çizgi çizer ({0}). Yakalama etkinleştirmek için Shift tuşunu basılı tutun.", + "LINE_TOOL_ACTION_DISPLAY_DEFAULT": "Bir çizgi çizmek için tıklayın ve sürükleyin. Yakalamayı etkinleştirmek için Shift tuşunu basılı tutun.", + "LINE_TOOL_ACTION_DISPLAY_SHIFT": "Yakalama etkinleştirilmiş bir çizgi çizmek için tıklayın ve sürükleyin.", + "MAGIC_WAND_TOOL_TOOLTIP": "Sihirli Değnek ({0}). Seçimi doldurur", + "MAGIC_WAND_ACTION_DISPLAY": "Seçimi doldurmak için tıklayın.", + "PEN_TOOL": "Kalem", + "BRIGHTNESS_TOOL": "Parlaklık", + "COLOR_PICKER_TOOL": "Renk Seçici", + "ELLIPSE_TOOL": "Elips", + "ERASER_TOOL": "Silgi", + "FLOOD_FILL_TOOL": "Doldurma", + "LASSO_TOOL": "Lasso", + "LINE_TOOL": "Çizgi", + "MAGIC_WAND_TOOL": "Sihirli Değnek", + "MOVE_TOOL": "Taşı", + "MOVE_VIEWPORT_TOOL": "Görünümü Taşı", + "RECTANGLE_TOOL": "Dikdörtgen", + "ROTATE_VIEWPORT_TOOL": "Görünümü Döndür", + "SELECT_TOOL_NAME": "Seçim", + "ZOOM_TOOL": "Yakınlaştırma", + "SHAPE_LABEL": "Şekil", + "MODE_LABEL": "Mod", + "SCOPE_LABEL": "Kapsam", + "FILL_SHAPE_LABEL": "Şekli Doldur", + "FILL_COLOR_LABEL": "Renk Doldur", + "TOOL_SIZE_LABEL": "Araç Boyutu", + "STRENGTH_LABEL": "Güç", + "NEW": "Yeni", + "ADD": "Ekle", + "SUBTRACT": "Çıkar", + "INTERSECT": "Kesişim", + "RECTANGLE": "Dikdörtgen", + "CIRCLE": "Daire", + "ABOUT": "Hakkında", + "MINIMIZE": "Simge Durumuna Küçült", + "RESTORE": "Geri Al", + "MAXIMIZE": "Büyüt", + "CLOSE": "Kapat", + "EXPORT_SIZE_HINT": "Görüntüyü paylaşmak isterseniz, en iyi netlik için {0}% önerilir", + "CREATE": "Oluştur", + "BASE_LAYER_NAME": "Ana katman", + "ENABLE_MASK": "Maskeleri Etkinleştir", + "SELECTED_AREA_EMPTY": "Seçilen alan boş", + "NOTHING_TO_COPY": "Kopyalanacak bir şey yok", + "REFERENCE_LAYER_PATH": "Referans katmanı yolu", + "FLIP": "Çevir", + "ROTATION": "Döndürme", + "ROT_IMG_90_D": "Görüntüyü 90° Döndür", + "ROT_IMG_180_D": "Görüntüyü 180° Döndür", + "ROT_IMG_-90_D": "Görüntüyü -90° Döndür", + "ROT_LAYERS_90_D": "Seçili Katmanları 90° Döndür", + "ROT_LAYERS_180_D": "Seçili Katmanları 180° Döndür", + "ROT_LAYERS_-90_D": "Seçili Katmanları -90° Döndür", + "UNNAMED_PALETTE": "Adsız Palet", + "CLICK_SELECT_PRIMARY": "Ana rengi seçmek için tıklayın.", + "PEN_MODE": "Kalem modu", + "VIEW": "Görünüm", + "HORIZONTAL_LINE_SYMMETRY": "Yatay çizgi simetrisi", + "VERTICAL_LINE_SYMMETRY": "Dikey çizgi simetrisi", + "COLOR_PICKER_TITLE": "Renk Seçici", + "COLOR_SLIDERS_TITLE": "Renk Kaydırıcıları", + "PALETTE_TITLE": "Palet", + "SWATCHES_TITLE": "Renk Örnekleri", + "LAYERS_TITLE": "Katmanlar", + "NAVIGATION_TITLE": "Gezinme", + "NORMAL_BLEND_MODE": "Normal", + "DARKEN_BLEND_MODE": "Karartma", + "MULTIPLY_BLEND_MODE": "Çarpma", + "COLOR_BURN_BLEND_MODE": "Renk Yakma", + "LIGHTEN_BLEND_MODE": "Aydınlatma", + "SCREEN_BLEND_MODE": "Ekran", + "COLOR_DODGE_BLEND_MODE": "Renk Geçişi", + "OVERLAY_BLEND_MODE": "Üzerine Yerleştirme", + "SOFT_LIGHT_BLEND_MODE": "Yumuşak Işık", + "HARD_LIGHT_BLEND_MODE": "Sert Işık", + "DIFFERENCE_BLEND_MODE": "Fark", + "EXCLUSION_BLEND_MODE": "Dışlama", + "HUE_BLEND_MODE": "Ton", + "SATURATION_BLEND_MODE": "Doygunluk", + "LUMINOSITY_BLEND_MODE": "Parlaklık", + "COLOR_BLEND_MODE": "Renk", + "NOT_SUPPORTED_BLEND_MODE": "Desteklenmeyen", + "RESTART": "Yeniden Başlat", + "SORT_BY": "Sırala", + "NAME": "İsim", + "COLORS": "Renkler", + "DEFAULT": "Varsayılan", + "ALPHABETICAL": "Alfabetik", + "COLOR_COUNT": "Renk sayısı", + "ANY": "Herhangi biri", + "MAX": "Maksimum", + "MIN": "Minimum", + "EXACT": "Tam", + "ASCENDING": "Artan", + "DESCENDING": "Azalan", + "NAME_IS_TOO_LONG": "İsim çok uzun", + "STOP_IT_TEXT1": "Yeter. Dosya isimlerinizi düzgünleştirin.", + "STOP_IT_TEXT2": "Bu isimleri kopyalamayı bırakabilir misiniz?", + "REPLACER_TOOLTIP": "Palet renginin üzerine sağ tıklayın ve 'Değiştir' seçeneğini seçin veya buraya bırakın.", + "CLICK_TO_CHOOSE_COLOR": "Renk seçmek için tıklayın", + "REPLACE_COLOR": "Rengi değiştir", + "PALETTE_COLOR_TOOLTIP": "Ana renk olarak seçmek için tıklayın. Başka bir palet renginin üzerine sürükleyip bırakarak değiştirin.", + "ADD_FROM_SWATCHES": "Renk örneklerinden ekle", + "ADD_COLOR_TO_PALETTE": "Palet'e renk ekle", + "USE_IN_CURRENT_IMAGE": "Geçerli görüntüde kullan", + "ADD_TO_FAVORITES": "Favorilere ekle", + "BROWSE_PALETTES": "Paletlere göz at", + "LOAD_PALETTE": "Paleti yükle", + "SAVE_PALETTE": "Paleti kaydet", + "FAVORITES": "Favoriler", + "ADD_FROM_CURRENT_PALETTE": "Mevcut paletten ekle", + "OPEN_PALETTES_DIR_TOOLTIP": "Paletler dizinini gezginde aç", + "BROWSE_ON_LOSPEC_TOOLTIP": "Lospec'te paletlere göz at", + "IMPORT_FROM_FILE_TOOLTIP": "Dosyadan içe aktar", + "TOP_LEFT": "Sol üst", + "TOP_CENTER": "Orta üst", + "TOP_RIGHT": "Sağ üst", + "MIDDLE_LEFT": "Sol orta", + "MIDDLE_CENTER": "Orta", + "MIDDLE_RIGHT": "Sağ orta", + "BOTTOM_LEFT": "Sol alt", + "BOTTOM_CENTER": "Orta alt", + "BOTTOM_RIGHT": "Sağ alt", + "CLIP_TO_BELOW": "Alt katmana kırp", + "MOVE_UPWARDS": "Yukarı taşı", + "MOVE_DOWNWARDS": "Aşağı taşı", + "MERGE_SELECTED": "Seçilenleri birleştir", + "LOCK_TRANSPARENCY": "Şeffaflığı kilitle", + "COULD_NOT_LOAD_PALETTE": "Palet alınamadı", + "NO_PALETTES_FOUND": "Palet bulunamadı.", + "LOSPEC_LINK_TEXT": "İşte bazılarına buradan ulaşabilirsiniz: lospec.com/palette-list", + "PALETTE_BROWSER": "Palet Tarayıcısı", + "DELETE_PALETTE_CONFIRMATION": "Bu paleti silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "SHORTCUTS_IMPORTED": "{0} kaynaklı kısayollar başarıyla içe aktarıldı.", + "SHORTCUT_PROVIDER_DETECTED": "{0} yüklendiği tespit edildi. Kısayollarını içe aktarmak istiyor musunuz?", + "IMPORT_FROM_INSTALLATION": "Yüklemeyi içe aktar", + "IMPORT_INSTALLATION_OPTION1": "Yüklemeyi içe aktar", + "IMPORT_INSTALLATION_OPTION2": "Varsayılanları kullan", + "IMPORT_FROM_TEMPLATE": "Şablondan içe aktar", + "SHORTCUTS_IMPORTED_SUCCESS": "Kısayollar başarıyla içe aktarıldı.", + "WARNING_RESET_SHORTCUTS_DEFAULT": "Tüm kısayolları varsayılan değerlerine sıfırlamak istediğinizden emin misiniz?", + "SUCCESS": "Başarılı", + "WARNING": "Uyarı", + "ERROR_IMPORTING_IMAGE": "Resim içe aktarılırken bir hata oluştu.", + "SHORTCUTS_CORRUPTED_TITLE": "Bozuk kısayollar dosyası", + "SHORTCUTS_CORRUPTED": "Kısayollar dosyası bozuldu, varsayılanlara sıfırlanıyor.", + "FAILED_DOWNLOAD_PALETTE": "Palet indirilemedi", + "FILE_INCORRECT_FORMAT": "Dosya doğru bir formatta değil", + "INVALID_FILE": "Geçersiz dosya", + "SHORTCUTS_FILE_INCORRECT_FORMAT": "Kısayollar dosyası doğru bir formatta değil", + "UNSUPPORTED_FILE_FORMAT": "Bu dosya formatı desteklenmiyor", + "ALREADY_ASSIGNED": "Zaten atanmış", + "REPLACE": "Değiştir", + "SWAP": "Değiştir", + "SHORTCUT_ALREADY_ASSIGNED_SWAP": "Bu kısayol zaten '{0}' için atanmış\nMevcut kısayolu değiştirmek veya ikisini değiştirmek istiyor musunuz?", + "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "Bu kısayol zaten '{0}' için atanmış\nMevcut kısayolu değiştirmek istiyor musunuz?", + "UNSAVED_CHANGES": "Kaydedilmemiş değişiklikler", + "DOCUMENT_MODIFIED_SAVE": "Belge değiştirildi. Değişiklikleri kaydetmek istiyor musunuz?", + "SESSION_UNSAVED_DATA": "{0} kaydedilmemiş veri içeriyor. Emin misiniz?", + "PROJECT_MAINTAINERS": "Proje Bakımcıları", + "OTHER_AWESOME_CONTRIBUTORS": "Ve diğer harika katkıda bulunanlar", + "HELP": "Yardım", + "STOP_IT_TEXT3": "Hayır gerçekten, yeter.", + "STOP_IT_TEXT4": "Daha iyi yapacak başka bir şeyiniz yok mu?", + "LINEAR_DODGE_BLEND_MODE": "Lineer dodge (Ekleme)", + "PRESS_ANY_KEY": "Herhangi bir tuşa basın", + "NONE_SHORTCUT": "Hiçbiri", + "REFERENCE": "Referans", + "PUT_REFERENCE_LAYER_ABOVE": "Referans katmanını üste yerleştir", + "PUT_REFERENCE_LAYER_BELOW": "Referans katmanını alta yerleştir", + "TOGGLE_VERTICAL_SYMMETRY": "Dikey simetriyi aç/kapat", + "TOGGLE_HORIZONTAL_SYMMETRY": "Yatay simetriyi aç/kapat", + "RESET_VIEWPORT": "Görünümü sıfırla", + "VIEWPORT_SETTINGS": "Görünüm ayarları", + "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "Tıklanıp basılı tutarak seçili katmanlardaki pikselleri taşımak için fareyi kullanın.", + "MOVE_TOOL_ACTION_DISPLAY_CTRL": "Tüm katmanları taşımak için fareyi basılı tutun.", + "CTRL_KEY": "Ctrl", + "SHIFT_KEY": "Shift", + "ALT_KEY": "Alt", + "RENAME": "Yeniden adlandır", + "PIXEL_UNIT": "px", + "OPEN_LOCALIZATION_DEBUG_WINDOW": "Yerelleştirme Hata Ayıklama Penceresini Aç", + "FORCE_OTHER_FLOW_DIRECTION": "Diğer akış yönünü zorla", + "API_KEY": "API Anahtarı", + "LOCALIZATION_VIEW_TYPE": "Yerelleştirme Görünüm Türü", + "LOAD_LANGUAGE_FROM_FILE": "Dili dosyadan yükle", + "LOG_IN": "Giriş yap", + "SYNC": "Senkronize Et", + "NOT_LOGGED_IN": "Giriş Yapılmadı", + "POE_EDITOR_ERROR": "POEditor Hatası: {0} {1}", + "HTTP_ERROR_MESSAGE": "HTTP Hatası: {0} {1}", + "LOGGED_IN": "Giriş Yapıldı", + "SYNCED_SUCCESSFULLY": "Başarıyla Senkronize Edildi", + "EXCEPTION_ERROR": "Hata: {0}", + "DROP_PALETTE": "Paleti Buraya Bırak", + "SECURITY_ERROR": "Güvenlik Hatası", + "SECURITY_ERROR_MSG": "Belirtilen konuma yazma izni yok.", + "IO_ERROR": "G/Ç Hatası", + "IO_ERROR_MSG": "Disk'e yazarken hata oluştu.", + "FAILED_ASSOCIATE_PIXI": ".pixi dosyası PixiEditor ile ilişkilendirilemedi.", + "COULD_NOT_SAVE_PALETTE": "Palet kaydedilirken bir hata oluştu.", + "NO_COLORS_TO_SAVE": "Kaydedilecek renk bulunmuyor.", + "ALL_LAYERS": "Tüm Katmanlar", + "SINGLE_LAYER": "Tek Katman", + "CHOOSE": "Seç", + "REMOVE": "Kaldır", + "FILE_FORMAT_NOT_ASEPRITE_KEYS": "Dosya bir \".aseprite-keys\" dosyası değil", + "FILE_HAS_INVALID_SHORTCUT": "Dosya geçersiz bir kısayol içeriyor", + "FILE_EXTENSION_NOT_SUPPORTED": "Dosya türü '{0}' desteklenmiyor", + "ERROR_READING_FILE": "Dosya okunurken hata oluştu", + "DISCARD_PALETTE": "Paleti At", + "DISCARD_PALETTE_CONFIRMATION": "Mevcut paleti atmak istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "IMPORT_AS_NEW_LAYER": "Yeni katman olarak içe aktar", + "PASTE_AS_PRIMARY_COLOR": "Birincil renk olarak yapıştır", + "IMPORT_AS_NEW_FILE": "Yeni dosya olarak içe aktar", + "IMPORT_PALETTE_FILE": "Palet dosyasını içe aktar", + "IMPORT_MULTIPLE_PALETTE_COLORS": "Renkleri palet içine aktar", + "IMPORT_SINGLE_PALETTE_COLOR": "Renkleri palet içine aktar", + "IMPORT_AS_REFERENCE_LAYER": "Referans katmanı olarak içe aktar", + "NAVIGATOR_PICK_ACTION_DISPLAY": "Renk seçmek için sağ tıklayın, Renkleri panoya kopyalamak için Shift tuşuna basılı tutun", + "OPEN_FILE_FROM_CLIPBOARD": "Panodan aç", + "OPEN_FILE_FROM_CLIPBOARD_DESCRIPTIVE": "Panodan aç", + "OPEN_LOCALIZATION_DATA": "LocalizationData.json dosyasını mı açmak istiyorsunuz?\nGüncellenmiş tarih panoya kopyalandı.\nNot Değişiklikler, yeniden başlatma yapılana kadar uygulanmayacaktır", + "DOWNLOADING_LANGUAGE_FAILED": "Dil indirme başarısız oldu.\nAPI Anahtarı aşırı kullanılmış olabilir.", + "LOCALIZATION_DATA_NOT_FOUND": "Yerelleştirme veri yolu bulunamadı", + "APPLY": "Uygula", + "UPDATE_SOURCE": "Kaynağı Güncelle", + "COPY_TO_CLIPBOARD": "Panoya Kopyala", + "LANGUAGE_FILE_NOT_FOUND": "Dil dosyası bulunamadı.\n'{0}' dosyası aranıyor", + "PROJECT_ROOT_NOT_FOUND": "PixiEditor proje kökü bulunamadı.\nPixiEditor.csproj dosyası aranıyor", + "LOCALIZATION_FOLDER_NOT_FOUND": "Yerelleştirme klasörü bulunamadı.\n/Data/Localization dizini aranıyor", + "SELECT_A_LANGUAGE": "Bir dil seçin", + "DONE": "Tamamlandı", + "SOURCE_UNSET_OR_MISSING": "Kaynak eksik/belirtilmemiş", + "SOURCE_NEWER": "Kaynak daha yeni", + "SOURCE_UP_TO_DATE": "Kaynak güncel", + "SOURCE_OLDER": "Bulut daha yeni", + "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Referans katmanından renk seçmek için tıklayın.", + "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Tuvalden renk seçmek için tıklayın.", + "LOCALIZATION_DEBUG_WINDOW_TITLE": "Yerelleştirme Hata Ayıklama Penceresi", + "COMMAND_DEBUG_WINDOW_TITLE": "Komut Hata Ayıklama Penceresi", + "SHORTCUTS_TITLE": "Kısayollar", + "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_PERSPECTIVE": "Dönüştürmeyi ölçeklendirmek için kolları sürükleyin. Bir kolun üzerine tıklayıp Ctrl tuşunu basılı tutarak kolun serbestçe hareket etmesini sağlayın. Orantılı olarak ölçeklendirmek için Shift tuşunu basılı tutun. Bir yan kolun üzerine tıklayıp Alt tuşunu basılı tutarak kaydırın. Dış kolları sürükleyerek döndürün.", + "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE": "Dönüştürmeyi ölçeklendirmek için kolları sürükleyin. Orantılı olarak ölçeklendirmek için Shift tuşunu basılı tutun. Bir yan kolun üzerine tıklayıp Alt tuşunu basılı tutarak kaydırın. Dış kolları sürükleyerek döndürün.", + "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE": "Dönüştürmeyi ölçeklendirmek için kolları sürükleyin. Shift tuşunu basılı tutunarak orantılı olarak ölçeklendirin. Dış kolları sürükleyerek döndürün.", + "TRANSFORM_ACTION_DISPLAY_SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE": "Dönüştürmeyi ölçeklendirmek için kolları sürükleyin. Shift tuşunu basılı tutarak orantılı olarak ölçeklendirin.", + "OPEN_DOCUMENTATION": "Dokümantasyonu aç", + "LOCAL_PALETTE_SOURCE_NAME": "Yerel", + "ERROR_FORBIDDEN_UNIQUE_NAME": "Uzantı benzersiz adı 'pixieditor' ile başlayamaz.", + "ERROR_MISSING_METADATA": "Uzantı metaverisi '{0}' eksik.", + "ERROR_NO_CLASS_ENTRY": "Uzantı sınıf girişi '{0}' yolda eksik.", + "ERROR_NO_ENTRY_ASSEMBLY": "Uzantı giriş derlemesi '{0}' yolda eksik.", + "ERROR_MISSING_ADDITIONAL_CONTENT": "Mevcut yapılandırmanız bu uzantının yüklenmesine izin vermiyor. Belki de sahibi değilsiniz veya yüklü değil. Uzantıyı buradan satın alabilirsiniz '{0}'.", + "BUY_SUPPORTER_PACK": "Destekçi Paketi Satın Al", + "NEWS": "Haberler", + "DISABLE_NEWS_PANEL": "Başlangıç penceresinde Haberler panelini devre dışı bırak", + "FAILED_FETCH_NEWS": "Haberleri alırken hata oluştu", + "CROP_TO_SELECTION": "Seçime Kırp", + "CROP_TO_SELECTION_DESCRIPTIVE": "Resmi seçime kırp", + "SHOW_CONTEXT_MENU": "Bağlam Menüsünü Göster", + "ERASE": "Sil", + "USE_SECONDARY_COLOR": "İkincil rengi kullan", + "RIGHT_CLICK_MODE": "Sağ Tıklama Modu", + "ADD_PRIMARY_COLOR_TO_PALETTE": "Birincil rengi palete ekle", + "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "Birincil rengi mevcut palete ekle", + "COPY_COLOR": "Rengi Kopyala", + "ERROR_FAILED_TO_OPEN_EXPLORER": "Couldn't open File Explorer", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Could not recover all", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "Could not fully recover all files.\nIf you send the crash report to the developers\nthey might be able to help you." } \ No newline at end of file diff --git a/src/PixiEditor/Data/Localization/Languages/uk.json b/src/PixiEditor/Data/Localization/Languages/uk.json index 24587dca1..09e696e5d 100644 --- a/src/PixiEditor/Data/Localization/Languages/uk.json +++ b/src/PixiEditor/Data/Localization/Languages/uk.json @@ -580,5 +580,8 @@ "RIGHT_CLICK_MODE": "Режим правої кнопки миші", "ADD_PRIMARY_COLOR_TO_PALETTE": "Додати основний колір до палітри", "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "Додати основний колір до поточної палітри", - "COPY_COLOR": "Copy color" + "COPY_COLOR": "Копія кольору", + "ERROR_FAILED_TO_OPEN_EXPLORER": "Не вдалося відкрити Провідник", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Не вдалося відновити все", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "Не вдалося повністю відновити всі файли.\nЯкщо ви надішлете звіт про збій розробникам\nвони можуть вам допомогти." } \ No newline at end of file diff --git a/src/PixiEditor/Data/Localization/Languages/zh.json b/src/PixiEditor/Data/Localization/Languages/zh.json index cc070e5c4..6bbab484e 100644 --- a/src/PixiEditor/Data/Localization/Languages/zh.json +++ b/src/PixiEditor/Data/Localization/Languages/zh.json @@ -1,8 +1,8 @@ { "RECENT_FILES": "最近", - "OPEN_FILE": "打开", + "OPEN_FILE": "打开文件", "NEW_FILE": "新建", - "RECENT_EMPTY_TEXT": "空空如也……", + "RECENT_EMPTY_TEXT": "这里空空如也……", "LANGUAGE": "语言", "GENERAL": "基础设置", "DISCORD": "Discord", @@ -88,7 +88,7 @@ "LOCATION_DOES_NOT_EXIST": "位置不存在。", "FILE_NOT_FOUND": "无法找到文件。", "ARE_YOU_SURE": "你确定吗?", - "ARE_YOU_SURE_PATH_FULL_PATH": "你确定要删除{0}吗?\n所有安装都将丢失此资料。\n(完整位置:{1})", + "ARE_YOU_SURE_PATH_FULL_PATH": "你确定要删除{0}吗?\n所有安装都将丢失此资料。\n(完整路径:{1})", "FAILED_TO_OPEN_FILE": "无法打开文件", "OLD_FILE_FORMAT": "旧文件格式", "OLD_FILE_FORMAT_DESCRIPTION": "此 .pixi 文件使用了已被停止支持的旧文件格式。", @@ -325,7 +325,7 @@ "COLOR_PICKER_TOOL": "选色器", "ELLIPSE_TOOL": "椭圆", "ERASER_TOOL": "橡皮", - "FLOOD_FILL_TOOL": "填满", + "FLOOD_FILL_TOOL": "颜料桶", "LASSO_TOOL": "拉索", "LINE_TOOL": "直线", "MAGIC_WAND_TOOL": "魔法棒", @@ -537,7 +537,7 @@ "NAVIGATOR_PICK_ACTION_DISPLAY": "右键以选择颜色,按住Shift时右键以将其复制至剪贴薄", "OPEN_FILE_FROM_CLIPBOARD": "从剪贴薄中打开", "OPEN_FILE_FROM_CLIPBOARD_DESCRIPTIVE": "从剪贴薄中打开文件", - "OPEN_LOCALIZATION_DATA": "是否打开 LocalizationData.json?更新日期已被加入至剪贴板。\n注意更改只会在重启此程序后启用。", + "OPEN_LOCALIZATION_DATA": "是否打开 LocalizationData.json?\n更新日期已被加入至剪贴板。\n注意更改只会在重启此程序后启用。", "DOWNLOADING_LANGUAGE_FAILED": "无法下载语言。\nAPI密码可能被过度使用。", "LOCALIZATION_DATA_NOT_FOUND": "无法找到本地化资料路径", "APPLY": "应用", @@ -561,17 +561,17 @@ "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE": "拖动手柄以缩放已选像素。按住 Shift 键按比例缩放。按住 Alt 并拖动侧手柄进行剪切。拖动外部手柄进行旋转。", "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE": "拖动手柄以缩放已选像素。按住 Shift 键按比例缩放。拖动外部手柄进行旋转。", "TRANSFORM_ACTION_DISPLAY_SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE": "拖动手柄以缩放已选像素。按住 Shift 键按比例缩放。", - "OPEN_DOCUMENTATION": "Open documentation", - "LOCAL_PALETTE_SOURCE_NAME": "Local", + "OPEN_DOCUMENTATION": "打开文档", + "LOCAL_PALETTE_SOURCE_NAME": "本地", "ERROR_FORBIDDEN_UNIQUE_NAME": "Extension unique name cannot start with 'pixieditor'.", - "ERROR_MISSING_METADATA": "Extension metadata key '{0}' is missing.", - "ERROR_NO_CLASS_ENTRY": "Extension class entry is missing on path '{0}'.", - "ERROR_NO_ENTRY_ASSEMBLY": "Extension entry assembly is missing on path '{0}'.", - "ERROR_MISSING_ADDITIONAL_CONTENT": "Your current setup doesn't allow loading this extension. Perhaps you don't own it or don't have it installed. You can purchase it here '{0}'.", - "BUY_SUPPORTER_PACK": "Buy Supporter Pack", - "NEWS": "News", - "DISABLE_NEWS_PANEL": "Disable News panel in startup window", - "FAILED_FETCH_NEWS": "Failed to fetch news", + "ERROR_MISSING_METADATA": "扩展元数据键 “{0}” 丢失。", + "ERROR_NO_CLASS_ENTRY": "路径“{0}”上缺少扩展类程序。", + "ERROR_NO_ENTRY_ASSEMBLY": "路径“{0}”上缺少扩展程序集。", + "ERROR_MISSING_ADDITIONAL_CONTENT": "你当前的设置不允许加载此扩展。 也许你不拥有它或没有安装它。 你可以在“{0}”处购买。", + "BUY_SUPPORTER_PACK": "购买支持者包", + "NEWS": "新消息", + "DISABLE_NEWS_PANEL": "禁用开始窗口的新消息页面", + "FAILED_FETCH_NEWS": "无法获取新消息", "CROP_TO_SELECTION": "Crop to selection", "CROP_TO_SELECTION_DESCRIPTIVE": "Crop image to selection", "SHOW_CONTEXT_MENU": "Show context menu", @@ -580,5 +580,8 @@ "RIGHT_CLICK_MODE": "Right click mode", "ADD_PRIMARY_COLOR_TO_PALETTE": "Add primary color to palette", "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "Add primary color to current palette", - "COPY_COLOR": "Copy color" + "COPY_COLOR": "Copy color", + "ERROR_FAILED_TO_OPEN_EXPLORER": "Couldn't open File Explorer", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Could not recover all", + "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "Could not fully recover all files.\nIf you send the crash report to the developers\nthey might be able to help you." } \ No newline at end of file diff --git a/src/PixiEditor/Data/Localization/LocalizationData.json b/src/PixiEditor/Data/Localization/LocalizationData.json index 48bbdf59a..98f09b7f2 100644 --- a/src/PixiEditor/Data/Localization/LocalizationData.json +++ b/src/PixiEditor/Data/Localization/LocalizationData.json @@ -13,14 +13,14 @@ "code": "pl", "localeFileName": "pl.json", "iconFileName": "pl.png", - "lastUpdated": "2023-06-25 14:16:00" + "lastUpdated": "2023-12-14 16:59:56" }, { "name": "Deutsch", "code": "de", "localeFileName": "de.json", "iconFileName": "de.png", - "lastUpdated": "2023-06-25 12:55:11" + "lastUpdated": "2023-12-14 16:40:04" }, { "name": "Español", @@ -34,21 +34,21 @@ "code": "zh", "localeFileName": "zh.json", "iconFileName": "zh.png", - "lastUpdated": "2023-06-01 12:32:02" + "lastUpdated": "2023-08-14 07:59:43" }, { "name": "Русский", "code": "ru", "localeFileName": "ru.json", "iconFileName": "ru.png", - "lastUpdated": "2023-06-25 14:17:44" + "lastUpdated": "2023-12-14 16:54:15" }, { "name": "Українська", "code": "uk", "localeFileName": "uk.json", "iconFileName": "uk.png", - "lastUpdated": "2023-06-25 11:35:31" + "lastUpdated": "2023-12-14 16:56:58" }, { "name": "عربي", @@ -56,21 +56,21 @@ "localeFileName": "ar.json", "iconFileName": "ar.png", "rightToLeft": true, - "lastUpdated": "2023-06-25 14:47:47" + "lastUpdated": "2023-12-14 17:25:23" }, { "name": "Čeština", "code": "cs", "localeFileName": "cs.json", "iconFileName": "cs.png", - "lastUpdated": "2023-06-27 18:55:52" + "lastUpdated": "2023-12-14 20:10:32" }, { "name": "Português (Brasil)", "code": "pt-br", "localeFileName": "pt-br.json", "iconFileName": "pt-br.png", - "lastUpdated": "2023-06-01 21:22:27" + "lastUpdated": "2023-07-06 04:00:53" }, { "name": "Magyar", @@ -91,7 +91,7 @@ "code": "tr", "localeFileName": "tr.json", "iconFileName": "tr.png", - "lastUpdated": "2023-06-25 19:43:24" + "lastUpdated": "2023-06-29 22:44:34" } ] } \ No newline at end of file diff --git a/src/PixiEditor/Properties/AssemblyInfo.cs b/src/PixiEditor/Properties/AssemblyInfo.cs index 727d3f20c..249f417ee 100644 --- a/src/PixiEditor/Properties/AssemblyInfo.cs +++ b/src/PixiEditor/Properties/AssemblyInfo.cs @@ -50,5 +50,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.1.4")] -[assembly: AssemblyFileVersion("1.2.1.4")] +[assembly: AssemblyVersion("1.2.2.0")] +[assembly: AssemblyFileVersion("1.2.2.0")] From c79ae5950bb7d29d88baa3b1be6d72742c76aa8a Mon Sep 17 00:00:00 2001 From: Equbuxu Date: Wed, 20 Dec 2023 01:42:02 +0300 Subject: [PATCH 18/24] Hopefully now fix mouse controller for good --- .../Controllers/MouseUpdateController.cs | 133 +++++------------- .../MouseUpdateControllerSession.cs | 114 +++++++++++++++ .../UserControls/Layers/FolderControl.xaml.cs | 8 +- .../UserControls/Layers/LayerControl.xaml.cs | 8 +- .../BrushShapeOverlay/BrushShapeOverlay.cs | 7 +- .../LineToolOverlay/LineToolOverlay.cs | 6 + .../SymmetryOverlay/SymmetryOverlay.cs | 8 +- .../Views/UserControls/PreviewWindow.xaml.cs | 15 +- .../Views/UserControls/Viewport.xaml.cs | 6 +- 9 files changed, 197 insertions(+), 108 deletions(-) create mode 100644 src/PixiEditor/Models/Controllers/MouseUpdateControllerSession.cs diff --git a/src/PixiEditor/Models/Controllers/MouseUpdateController.cs b/src/PixiEditor/Models/Controllers/MouseUpdateController.cs index 2d8b2aedb..df897fc5b 100644 --- a/src/PixiEditor/Models/Controllers/MouseUpdateController.cs +++ b/src/PixiEditor/Models/Controllers/MouseUpdateController.cs @@ -1,126 +1,69 @@ -using System.Diagnostics; -using System.Windows; +using System.Windows; using System.Windows.Input; -using System.Windows.Threading; namespace PixiEditor.Models.Controllers; #nullable enable public class MouseUpdateController : IDisposable { - private const double MouseUpdateIntervalMs = 1000 / 142.0; //142 Hz - - private Thread timerThread; - private readonly AutoResetEvent resetEvent = new(false); - private readonly object lockObj = new(); - private bool isAborted = false; + private bool isDisposed = false; private readonly FrameworkElement element; private readonly MouseEventHandler mouseMoveHandler; + private MouseUpdateControllerSession? session; public MouseUpdateController(FrameworkElement uiElement, MouseEventHandler onMouseMove) { mouseMoveHandler = onMouseMove; element = uiElement; - element.MouseMove += OnMouseMove; - - bool wasThreadCreated = !element.IsLoaded; - element.Loaded += (_, _) => - { - wasThreadCreated = true; - CreateTimerThread(); - }; - - if (!wasThreadCreated) - CreateTimerThread(); - - element.Unloaded += (_, _) => - { - isAborted = true; - }; + + element.Loaded += OnElementLoaded; + element.Unloaded += OnElementUnloaded; + + session ??= new MouseUpdateControllerSession(StartListening, StopListening, mouseMoveHandler); + + element.MouseMove += CallMouseMoveInput; } - - private void CreateTimerThread() - { - timerThread = new Thread(TimerThread); - timerThread.Name = "MouseUpdateController thread"; - timerThread.Start(); - isAborted = false; - } - - private bool IsThreadShouldStop() + + void OnElementLoaded(object o, RoutedEventArgs routedEventArgs) { - return isAborted || timerThread != Thread.CurrentThread || Application.Current is null; + session ??= new MouseUpdateControllerSession(StartListening, StopListening, mouseMoveHandler); } - private void TimerThread() + private void OnElementUnloaded(object o, RoutedEventArgs routedEventArgs) { - try - { - long lastThreadIter = Stopwatch.GetTimestamp(); - - // abort if a new thread was created - while (!IsThreadShouldStop()) - { - // call waitOne periodically instead of waiting infinitely to make sure we crash or exit when resetEvent is disposed - if (!resetEvent.WaitOne(300)) - { - lastThreadIter = Stopwatch.GetTimestamp(); - continue; - } + session.Dispose(); + session = null; + } - lock (lockObj) - { - double sleepDur = Math.Clamp(MouseUpdateIntervalMs - Stopwatch.GetElapsedTime(lastThreadIter).TotalMilliseconds, 0, MouseUpdateIntervalMs); - lastThreadIter += (long)(MouseUpdateIntervalMs * Stopwatch.Frequency / 1000); - if (sleepDur > 0) - Thread.Sleep((int)Math.Round(sleepDur)); - - if (IsThreadShouldStop()) - return; - Application.Current?.Dispatcher.Invoke(() => - { - element.MouseMove += OnMouseMove; - }); - - } - } - } - catch (ObjectDisposedException) - { + private void StartListening() + { + if (isDisposed) return; - } - catch (Exception e) - { - Application.Current?.Dispatcher.BeginInvoke(() => throw new AggregateException("Input handling thread died", e), DispatcherPriority.SystemIdle); - throw; - } + element.MouseMove -= CallMouseMoveInput; + element.MouseMove += CallMouseMoveInput; } - private void OnMouseMove(object sender, MouseEventArgs e) + private void CallMouseMoveInput(object sender, MouseEventArgs e) + { + if (isDisposed) + return; + session?.MouseMoveInput(sender, e); + } + + private void StopListening() { - bool lockWasTaken = false; - try - { - Monitor.TryEnter(lockObj, ref lockWasTaken); - if (lockWasTaken) - { - resetEvent.Set(); - element.MouseMove -= OnMouseMove; - mouseMoveHandler(sender, e); - } - } - finally - { - if (lockWasTaken) - Monitor.Exit(lockObj); - } + if (isDisposed) + return; + element.MouseMove -= CallMouseMoveInput; } public void Dispose() { - element.MouseMove -= OnMouseMove; - isAborted = true; - resetEvent.Dispose(); + element.MouseMove -= CallMouseMoveInput; + element.Loaded -= OnElementLoaded; + element.Unloaded -= OnElementUnloaded; + session?.Dispose(); + isDisposed = true; } } diff --git a/src/PixiEditor/Models/Controllers/MouseUpdateControllerSession.cs b/src/PixiEditor/Models/Controllers/MouseUpdateControllerSession.cs new file mode 100644 index 000000000..c72376c6a --- /dev/null +++ b/src/PixiEditor/Models/Controllers/MouseUpdateControllerSession.cs @@ -0,0 +1,114 @@ +using System.Diagnostics; +using System.Windows; +using System.Windows.Input; +using System.Windows.Threading; + +namespace PixiEditor.Models.Controllers; + +#nullable enable +internal class MouseUpdateControllerSession : IDisposable +{ + private const double IntervalMs = 1000 / 142.0; //142 Hz + + private readonly Action onStartListening; + private readonly Action onStopListening; + private readonly MouseEventHandler onMouseMove; + + private readonly AutoResetEvent resetEvent = new(false); + private readonly object lockObj = new(); + + /// + /// doesn't rely on attaching and detaching mouse move handler, + /// it just ignores mouse move events when not listening.
+ /// Yet it still calls and which can be used to attach and detach event handler elsewhere. + ///
+ private bool isListening = true; + private bool isDisposed = false; + + public MouseUpdateControllerSession(Action onStartListening, Action onStopListening, MouseEventHandler onMouseMove) + { + this.onStartListening = onStartListening; + this.onStopListening = onStopListening; + this.onMouseMove = onMouseMove; + + Thread timerThread = new(TimerLoop); + timerThread.Name = "MouseUpdateController thread"; + timerThread.Start(); + + onStartListening(); + } + + public void MouseMoveInput(object sender, MouseEventArgs e) + { + if (!isListening || isDisposed) + return; + + bool lockWasTaken = false; + try + { + Monitor.TryEnter(lockObj, ref lockWasTaken); + if (lockWasTaken) + { + isListening = false; + onStopListening(); + onMouseMove(sender, e); + resetEvent.Set(); + } + } + finally + { + if (lockWasTaken) + Monitor.Exit(lockObj); + } + } + + public void Dispose() + { + isDisposed = true; + resetEvent.Dispose(); + } + + private void TimerLoop() + { + try + { + long lastThreadIter = Stopwatch.GetTimestamp(); + while (!isDisposed) + { + // call waitOne periodically instead of waiting infinitely to make sure we crash or exit when resetEvent is disposed + if (!resetEvent.WaitOne(300)) + { + lastThreadIter = Stopwatch.GetTimestamp(); + continue; + } + + lock (lockObj) + { + double sleepDur = Math.Clamp(IntervalMs - Stopwatch.GetElapsedTime(lastThreadIter).TotalMilliseconds, 0, IntervalMs); + lastThreadIter += (long)(IntervalMs * Stopwatch.Frequency / 1000); + if (sleepDur > 0) + Thread.Sleep((int)Math.Round(sleepDur)); + + if (isDisposed) + return; + + isListening = true; + Application.Current?.Dispatcher.Invoke(() => + { + if (!isDisposed) + onStartListening(); + }); + } + } + } + catch (ObjectDisposedException) + { + return; + } + catch (Exception e) + { + Application.Current?.Dispatcher.BeginInvoke(() => throw new AggregateException("Input handling thread died", e), DispatcherPriority.SystemIdle); + throw; + } + } +} diff --git a/src/PixiEditor/Views/UserControls/Layers/FolderControl.xaml.cs b/src/PixiEditor/Views/UserControls/Layers/FolderControl.xaml.cs index 590c896a9..9308ec1f3 100644 --- a/src/PixiEditor/Views/UserControls/Layers/FolderControl.xaml.cs +++ b/src/PixiEditor/Views/UserControls/Layers/FolderControl.xaml.cs @@ -32,13 +32,19 @@ public LayersManager Manager private readonly Brush? highlightColor; - private MouseUpdateController mouseUpdateController; + private MouseUpdateController? mouseUpdateController; public FolderControl() { InitializeComponent(); highlightColor = (Brush?)App.Current.Resources["SoftSelectedLayerColor"]; Loaded += OnLoaded; + Unloaded += OnUnloaded; + } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + mouseUpdateController?.Dispose(); } private void OnLoaded(object sender, RoutedEventArgs e) diff --git a/src/PixiEditor/Views/UserControls/Layers/LayerControl.xaml.cs b/src/PixiEditor/Views/UserControls/Layers/LayerControl.xaml.cs index fa7c42ed4..35bfede57 100644 --- a/src/PixiEditor/Views/UserControls/Layers/LayerControl.xaml.cs +++ b/src/PixiEditor/Views/UserControls/Layers/LayerControl.xaml.cs @@ -62,15 +62,21 @@ public RelayCommand MoveToBackCommand nameof(MoveToFrontCommand), typeof(RelayCommand), typeof(LayerControl), new PropertyMetadata(default(RelayCommand))); - private MouseUpdateController mouseUpdateController; + private MouseUpdateController? mouseUpdateController; public LayerControl() { InitializeComponent(); Loaded += LayerControl_Loaded; + Unloaded += LayerControl_Unloaded; highlightColor = (Brush?)App.Current.Resources["SoftSelectedLayerColor"]; } + private void LayerControl_Unloaded(object sender, RoutedEventArgs e) + { + mouseUpdateController?.Dispose(); + } + private void LayerControl_Loaded(object sender, RoutedEventArgs e) { mouseUpdateController = new MouseUpdateController(this, Manager.LayerControl_MouseMove); diff --git a/src/PixiEditor/Views/UserControls/Overlays/BrushShapeOverlay/BrushShapeOverlay.cs b/src/PixiEditor/Views/UserControls/Overlays/BrushShapeOverlay/BrushShapeOverlay.cs index 7e914d367..92fee40f2 100644 --- a/src/PixiEditor/Views/UserControls/Overlays/BrushShapeOverlay/BrushShapeOverlay.cs +++ b/src/PixiEditor/Views/UserControls/Overlays/BrushShapeOverlay/BrushShapeOverlay.cs @@ -65,7 +65,7 @@ public double ZoomboxScale private Pen whitePen = new Pen(Brushes.LightGray, 1); private Point lastMousePos = new(); - private MouseUpdateController mouseUpdateController; + private MouseUpdateController? mouseUpdateController; public BrushShapeOverlay() { @@ -75,10 +75,7 @@ public BrushShapeOverlay() private void ControlUnloaded(object sender, RoutedEventArgs e) { - if (MouseEventSource is null) - return; - - mouseUpdateController.Dispose(); + mouseUpdateController?.Dispose(); } private void ControlLoaded(object sender, RoutedEventArgs e) diff --git a/src/PixiEditor/Views/UserControls/Overlays/LineToolOverlay/LineToolOverlay.cs b/src/PixiEditor/Views/UserControls/Overlays/LineToolOverlay/LineToolOverlay.cs index 6552b56df..ea44de999 100644 --- a/src/PixiEditor/Views/UserControls/Overlays/LineToolOverlay/LineToolOverlay.cs +++ b/src/PixiEditor/Views/UserControls/Overlays/LineToolOverlay/LineToolOverlay.cs @@ -72,12 +72,18 @@ public LineToolOverlay() { Cursor = Cursors.Arrow; Loaded += OnLoaded; + Unloaded += OnUnloaded; } private void OnLoaded(object sender, RoutedEventArgs e) { mouseUpdateController = new MouseUpdateController(this, MouseMoved); } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + mouseUpdateController?.Dispose(); + } private static void OnZoomboxScaleChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { diff --git a/src/PixiEditor/Views/UserControls/Overlays/SymmetryOverlay/SymmetryOverlay.cs b/src/PixiEditor/Views/UserControls/Overlays/SymmetryOverlay/SymmetryOverlay.cs index b26f29927..1f26aa219 100644 --- a/src/PixiEditor/Views/UserControls/Overlays/SymmetryOverlay/SymmetryOverlay.cs +++ b/src/PixiEditor/Views/UserControls/Overlays/SymmetryOverlay/SymmetryOverlay.cs @@ -119,11 +119,17 @@ public double ZoomboxScale private double horizontalAxisY; private double verticalAxisX; - private MouseUpdateController mouseUpdateController; + private MouseUpdateController? mouseUpdateController; public SymmetryOverlay() { Loaded += OnLoaded; + Unloaded += OnUnloaded; + } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + mouseUpdateController?.Dispose(); } private void OnLoaded(object sender, RoutedEventArgs e) diff --git a/src/PixiEditor/Views/UserControls/PreviewWindow.xaml.cs b/src/PixiEditor/Views/UserControls/PreviewWindow.xaml.cs index f0bf94c7e..cab9398c8 100644 --- a/src/PixiEditor/Views/UserControls/PreviewWindow.xaml.cs +++ b/src/PixiEditor/Views/UserControls/PreviewWindow.xaml.cs @@ -54,11 +54,22 @@ public PreviewWindow() { InitializeComponent(); - mouseUpdateController = new MouseUpdateController(imageGrid, ImageGrid_MouseMove); - imageGrid.MouseRightButtonDown += ImageGrid_MouseRightButtonDown; imageGrid.MouseEnter += ImageGrid_MouseEnter; imageGrid.MouseLeave += ImageGrid_MouseLeave; + + imageGrid.Loaded += OnGridLoaded; + imageGrid.Unloaded += OnGridUnloaded; + } + + private void OnGridUnloaded(object sender, RoutedEventArgs e) + { + mouseUpdateController?.Dispose(); + } + + private void OnGridLoaded(object sender, RoutedEventArgs e) + { + mouseUpdateController = new MouseUpdateController(imageGrid, ImageGrid_MouseMove); } private void ImageGrid_MouseLeave(object sender, MouseEventArgs e) diff --git a/src/PixiEditor/Views/UserControls/Viewport.xaml.cs b/src/PixiEditor/Views/UserControls/Viewport.xaml.cs index 76242b6b0..282002146 100644 --- a/src/PixiEditor/Views/UserControls/Viewport.xaml.cs +++ b/src/PixiEditor/Views/UserControls/Viewport.xaml.cs @@ -283,7 +283,7 @@ public VecD Dimensions public Guid GuidValue { get; } = Guid.NewGuid(); - private MouseUpdateController mouseUpdateController; + private MouseUpdateController? mouseUpdateController; public Viewport() { @@ -295,8 +295,6 @@ public Viewport() MainImage!.Loaded += OnImageLoaded; Loaded += OnLoad; Unloaded += OnUnload; - - mouseUpdateController = new MouseUpdateController(this, Image_MouseMove); } public Image? MainImage => (Image?)((Grid?)((Border?)zoombox.AdditionalContent)?.Child)?.Children[1]; @@ -310,11 +308,13 @@ private void ForceRefreshFinalImage() private void OnUnload(object sender, RoutedEventArgs e) { Document?.Operations.RemoveViewport(GuidValue); + mouseUpdateController?.Dispose(); } private void OnLoad(object sender, RoutedEventArgs e) { Document?.Operations.AddOrUpdateViewport(GetLocation()); + mouseUpdateController = new MouseUpdateController(this, Image_MouseMove); } private static void OnDocumentChange(DependencyObject viewportObj, DependencyPropertyChangedEventArgs args) From e602c5ca7e27e5df50bcdc1daa3c11649bc67eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Krysi=C5=84ski?= Date: Mon, 25 Dec 2023 14:45:58 +0100 Subject: [PATCH 19/24] Fixed thread not dying --- .../Models/Controllers/MouseUpdateControllerSession.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/PixiEditor/Models/Controllers/MouseUpdateControllerSession.cs b/src/PixiEditor/Models/Controllers/MouseUpdateControllerSession.cs index c72376c6a..1c0457a2c 100644 --- a/src/PixiEditor/Models/Controllers/MouseUpdateControllerSession.cs +++ b/src/PixiEditor/Models/Controllers/MouseUpdateControllerSession.cs @@ -31,8 +31,11 @@ public MouseUpdateControllerSession(Action onStartListening, Action onStopListen this.onStopListening = onStopListening; this.onMouseMove = onMouseMove; - Thread timerThread = new(TimerLoop); - timerThread.Name = "MouseUpdateController thread"; + Thread timerThread = new(TimerLoop) + { + IsBackground = true, Name = "MouseUpdateController thread" + }; + timerThread.Start(); onStartListening(); From f9236647bb5ba1dec15a53df5f81f1201e67ff08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Krysi=C5=84ski?= Date: Mon, 25 Dec 2023 14:47:38 +0100 Subject: [PATCH 20/24] Updated version to 1.2.2.1 --- src/PixiEditor/Properties/AssemblyInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PixiEditor/Properties/AssemblyInfo.cs b/src/PixiEditor/Properties/AssemblyInfo.cs index 249f417ee..07e768730 100644 --- a/src/PixiEditor/Properties/AssemblyInfo.cs +++ b/src/PixiEditor/Properties/AssemblyInfo.cs @@ -50,5 +50,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.2.0")] -[assembly: AssemblyFileVersion("1.2.2.0")] +[assembly: AssemblyVersion("1.2.2.1")] +[assembly: AssemblyFileVersion("1.2.2.1")] From d675047c490cfaa90bb5e73df9c85d1cc7511f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Krysi=C5=84ski?= Date: Thu, 28 Dec 2023 10:02:04 +0100 Subject: [PATCH 21/24] 1.2.3.0 --- src/PixiEditor/Properties/AssemblyInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PixiEditor/Properties/AssemblyInfo.cs b/src/PixiEditor/Properties/AssemblyInfo.cs index 07e768730..6e247ebf1 100644 --- a/src/PixiEditor/Properties/AssemblyInfo.cs +++ b/src/PixiEditor/Properties/AssemblyInfo.cs @@ -50,5 +50,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.2.1")] -[assembly: AssemblyFileVersion("1.2.2.1")] +[assembly: AssemblyVersion("1.2.3.0")] +[assembly: AssemblyFileVersion("1.2.3.0")] From f530daa28c8557ef964210ab9089ddf0bbfb52e6 Mon Sep 17 00:00:00 2001 From: Equbuxu Date: Wed, 10 Jan 2024 06:42:54 +0300 Subject: [PATCH 22/24] Fix float precision issues when transforming selection --- .../Operations/OperationHelper.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/ChunkyImageLib/Operations/OperationHelper.cs b/src/ChunkyImageLib/Operations/OperationHelper.cs index c08027f1b..9fd396492 100644 --- a/src/ChunkyImageLib/Operations/OperationHelper.cs +++ b/src/ChunkyImageLib/Operations/OperationHelper.cs @@ -120,25 +120,25 @@ public static Matrix3X3 CreateMatrixFromPoints(ShapeCorners corners, VecD size) => CreateMatrixFromPoints((Point)corners.TopLeft, (Point)corners.TopRight, (Point)corners.BottomRight, (Point)corners.BottomLeft, (float)size.X, (float)size.Y); // see https://stackoverflow.com/questions/48416118/perspective-transform-in-skia/72364829#72364829 - public static Matrix3X3 CreateMatrixFromPoints(Point topLeft, Point topRight, Point botRight, Point botLeft, float width, float height) + public static Matrix3X3 CreateMatrixFromPoints(Point topLeft, Point topRight, Point botRight, Point botLeft, double width, double height) { - (float x1, float y1) = (topLeft.X, topLeft.Y); - (float x2, float y2) = (topRight.X, topRight.Y); - (float x3, float y3) = (botRight.X, botRight.Y); - (float x4, float y4) = (botLeft.X, botLeft.Y); - (float w, float h) = (width, height); - - float scaleX = (y1 * x2 * x4 - x1 * y2 * x4 + x1 * y3 * x4 - x2 * y3 * x4 - y1 * x2 * x3 + x1 * y2 * x3 - x1 * y4 * x3 + x2 * y4 * x3) / (x2 * y3 * w + y2 * x4 * w - y3 * x4 * w - x2 * y4 * w - y2 * w * x3 + y4 * w * x3); - float skewX = (-x1 * x2 * y3 - y1 * x2 * x4 + x2 * y3 * x4 + x1 * x2 * y4 + x1 * y2 * x3 + y1 * x4 * x3 - y2 * x4 * x3 - x1 * y4 * x3) / (x2 * y3 * h + y2 * x4 * h - y3 * x4 * h - x2 * y4 * h - y2 * h * x3 + y4 * h * x3); - float transX = x1; - float skewY = (-y1 * x2 * y3 + x1 * y2 * y3 + y1 * y3 * x4 - y2 * y3 * x4 + y1 * x2 * y4 - x1 * y2 * y4 - y1 * y4 * x3 + y2 * y4 * x3) / (x2 * y3 * w + y2 * x4 * w - y3 * x4 * w - x2 * y4 * w - y2 * w * x3 + y4 * w * x3); - float scaleY = (-y1 * x2 * y3 - y1 * y2 * x4 + y1 * y3 * x4 + x1 * y2 * y4 - x1 * y3 * y4 + x2 * y3 * y4 + y1 * y2 * x3 - y2 * y4 * x3) / (x2 * y3 * h + y2 * x4 * h - y3 * x4 * h - x2 * y4 * h - y2 * h * x3 + y4 * h * x3); - float transY = y1; - float persp0 = (x1 * y3 - x2 * y3 + y1 * x4 - y2 * x4 - x1 * y4 + x2 * y4 - y1 * x3 + y2 * x3) / (x2 * y3 * w + y2 * x4 * w - y3 * x4 * w - x2 * y4 * w - y2 * w * x3 + y4 * w * x3); - float persp1 = (-y1 * x2 + x1 * y2 - x1 * y3 - y2 * x4 + y3 * x4 + x2 * y4 + y1 * x3 - y4 * x3) / (x2 * y3 * h + y2 * x4 * h - y3 * x4 * h - x2 * y4 * h - y2 * h * x3 + y4 * h * x3); - float persp2 = 1; - - return new Matrix3X3(scaleX, skewX, transX, skewY, scaleY, transY, persp0, persp1, persp2); + (double x1, double y1) = (topLeft.X, topLeft.Y); + (double x2, double y2) = (topRight.X, topRight.Y); + (double x3, double y3) = (botRight.X, botRight.Y); + (double x4, double y4) = (botLeft.X, botLeft.Y); + (double w, double h) = (width, height); + + double scaleX = (y1 * x2 * x4 - x1 * y2 * x4 + x1 * y3 * x4 - x2 * y3 * x4 - y1 * x2 * x3 + x1 * y2 * x3 - x1 * y4 * x3 + x2 * y4 * x3) / (x2 * y3 * w + y2 * x4 * w - y3 * x4 * w - x2 * y4 * w - y2 * w * x3 + y4 * w * x3); + double skewX = (-x1 * x2 * y3 - y1 * x2 * x4 + x2 * y3 * x4 + x1 * x2 * y4 + x1 * y2 * x3 + y1 * x4 * x3 - y2 * x4 * x3 - x1 * y4 * x3) / (x2 * y3 * h + y2 * x4 * h - y3 * x4 * h - x2 * y4 * h - y2 * h * x3 + y4 * h * x3); + double transX = x1; + double skewY = (-y1 * x2 * y3 + x1 * y2 * y3 + y1 * y3 * x4 - y2 * y3 * x4 + y1 * x2 * y4 - x1 * y2 * y4 - y1 * y4 * x3 + y2 * y4 * x3) / (x2 * y3 * w + y2 * x4 * w - y3 * x4 * w - x2 * y4 * w - y2 * w * x3 + y4 * w * x3); + double scaleY = (-y1 * x2 * y3 - y1 * y2 * x4 + y1 * y3 * x4 + x1 * y2 * y4 - x1 * y3 * y4 + x2 * y3 * y4 + y1 * y2 * x3 - y2 * y4 * x3) / (x2 * y3 * h + y2 * x4 * h - y3 * x4 * h - x2 * y4 * h - y2 * h * x3 + y4 * h * x3); + double transY = y1; + double persp0 = (x1 * y3 - x2 * y3 + y1 * x4 - y2 * x4 - x1 * y4 + x2 * y4 - y1 * x3 + y2 * x3) / (x2 * y3 * w + y2 * x4 * w - y3 * x4 * w - x2 * y4 * w - y2 * w * x3 + y4 * w * x3); + double persp1 = (-y1 * x2 + x1 * y2 - x1 * y3 - y2 * x4 + y3 * x4 + x2 * y4 + y1 * x3 - y4 * x3) / (x2 * y3 * h + y2 * x4 * h - y3 * x4 * h - x2 * y4 * h - y2 * h * x3 + y4 * h * x3); + double persp2 = 1; + + return new Matrix3X3((float)scaleX, (float)skewX, (float)transX, (float)skewY, (float)scaleY, (float)transY, (float)persp0, (float)persp1, (float)persp2); } public static (ShapeCorners, ShapeCorners) CreateStretchedHexagon(VecD centerPos, double hexagonSide, double stretchX) From 96eb9cfe71b545ada3ca16d1bc76622fabbf459d Mon Sep 17 00:00:00 2001 From: Equbuxu Date: Wed, 10 Jan 2024 06:43:39 +0300 Subject: [PATCH 23/24] Fix incorrect constructor being used when creating a path bounds rectangle --- .../Implementations/SkiaPathImplementation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PixiEditor.DrawingApi.Skia/Implementations/SkiaPathImplementation.cs b/src/PixiEditor.DrawingApi.Skia/Implementations/SkiaPathImplementation.cs index d4bf10c8d..b8398a634 100644 --- a/src/PixiEditor.DrawingApi.Skia/Implementations/SkiaPathImplementation.cs +++ b/src/PixiEditor.DrawingApi.Skia/Implementations/SkiaPathImplementation.cs @@ -99,7 +99,7 @@ public void Transform(VectorPath vectorPath, Matrix3X3 matrix) public RectD GetBounds(VectorPath vectorPath) { SKRect rect = ManagedInstances[vectorPath.ObjectPointer].Bounds; - return new RectD(rect.Left, rect.Top, rect.Right, rect.Bottom); + return RectD.FromSides(rect.Left, rect.Right, rect.Top, rect.Bottom); } public void Reset(VectorPath vectorPath) From 28b29982833fe9fe4fa293102597e517231aa0cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Krysi=C5=84ski?= Date: Wed, 10 Jan 2024 16:40:58 +0100 Subject: [PATCH 24/24] Version 1.2.3.1 --- src/PixiEditor/Properties/AssemblyInfo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PixiEditor/Properties/AssemblyInfo.cs b/src/PixiEditor/Properties/AssemblyInfo.cs index 6e247ebf1..385578c6b 100644 --- a/src/PixiEditor/Properties/AssemblyInfo.cs +++ b/src/PixiEditor/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("PixiEditor")] [assembly: AssemblyProduct("PixiEditor")] -[assembly: AssemblyCopyright("Copyright PixiEditor © 2017 - 2023")] +[assembly: AssemblyCopyright("Copyright PixiEditor © 2017 - 2024")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -50,5 +50,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.3.0")] -[assembly: AssemblyFileVersion("1.2.3.0")] +[assembly: AssemblyVersion("1.2.3.1")] +[assembly: AssemblyFileVersion("1.2.3.1")]