From 503cfc93d78ba7f77b55e7f74ccdd59c6eba738d Mon Sep 17 00:00:00 2001 From: VladiStep Date: Sat, 24 Dec 2022 20:37:53 +0300 Subject: [PATCH 01/18] Extracted "Tab" and "TabTitleConverter" classes to the separate file. --- UndertaleModTool/MainWindow.xaml.cs | 229 -------------------------- UndertaleModTool/Tab.cs | 247 ++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+), 229 deletions(-) create mode 100644 UndertaleModTool/Tab.cs diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index c68905829..ba3a6132a 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -51,235 +51,6 @@ namespace UndertaleModTool { - public class Tab : INotifyPropertyChanged - { - public static readonly BitmapImage ClosedIcon = new(new Uri(@"/Resources/X.png", UriKind.RelativeOrAbsolute)); - public static readonly BitmapImage ClosedHoverIcon = new(new Uri(@"/Resources/X_Down.png", UriKind.RelativeOrAbsolute)); - - private static readonly MainWindow mainWindow = Application.Current.MainWindow as MainWindow; - - public event PropertyChangedEventHandler PropertyChanged; - - private object _currentObject; - - [PropertyChanged.DoNotNotify] // Prevents "PropertyChanged.Invoke()" injection on compile - public object CurrentObject - { - get => _currentObject; - set - { - object prevObj = _currentObject; - _currentObject = value; - - SetTabTitleBinding(value, prevObj); - - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentObject))); - mainWindow.RaiseOnSelectedChanged(); - } - } - public string TabTitle { get; set; } = "Untitled"; - public bool IsCustomTitle { get; set; } - public int TabIndex { get; set; } - public bool AutoClose { get; set; } = false; - - public ObservableCollection History { get; } = new(); - public int HistoryPosition { get; set; } - - public Tab(object obj, int tabIndex, string tabTitle = null) - { - CurrentObject = obj; - TabIndex = tabIndex; - AutoClose = obj is DescriptionView; - - IsCustomTitle = tabTitle is not null; - if (IsCustomTitle) - { - if (tabTitle.Length > 64) - TabTitle = tabTitle[..64] + "..."; - else - TabTitle = tabTitle; - } - } - - public static string GetTitleForObject(object obj) - { - if (obj is null) - return null; - - string title = null; - - if (obj is DescriptionView view) - { - if (view.Heading.Contains("Welcome")) - { - title = "Welcome!"; - } - else - { - title = view.Heading; - } - } - else if (obj is UndertaleNamedResource namedRes) - { - string content = namedRes.Name?.Content; - - string header = obj switch - { - UndertaleAudioGroup => "Audio Group", - UndertaleSound => "Sound", - UndertaleSprite => "Sprite", - UndertaleBackground => "Background", - UndertalePath => "Path", - UndertaleScript => "Script", - UndertaleShader => "Shader", - UndertaleFont => "Font", - UndertaleTimeline => "Timeline", - UndertaleGameObject => "Game Object", - UndertaleRoom => "Room", - UndertaleExtension => "Extension", - UndertaleTexturePageItem => "Texture Page Item", - UndertaleCode => "Code", - UndertaleVariable => "Variable", - UndertaleFunction => "Function", - UndertaleCodeLocals => "Code Locals", - UndertaleEmbeddedTexture => "Embedded Texture", - UndertaleEmbeddedAudio => "Embedded Audio", - UndertaleTextureGroupInfo => "Texture Group Info", - UndertaleEmbeddedImage => "Embedded Image", - UndertaleSequence => "Sequence", - UndertaleAnimationCurve => "Animation Curve", - _ => null - }; - - if (header is not null) - title = header + " - " + content; - else - Debug.WriteLine($"Could not handle type {obj.GetType()}"); - } - else if (obj is UndertaleString str) - { - string stringFirstLine = str.Content; - if (stringFirstLine is not null) - { - if (stringFirstLine.Length == 0) - stringFirstLine = "(empty string)"; - else - { - int stringLength = StringTitleConverter.NewLineRegex.Match(stringFirstLine).Index; - if (stringLength != 0) - stringFirstLine = stringFirstLine[..stringLength] + " ..."; - } - } - - title = "String - " + stringFirstLine; - } - else if (obj is UndertaleChunkVARI) - { - title = "Variables Overview"; - } - else if (obj is GeneralInfoEditor) - { - title = "General Info"; - } - else if (obj is GlobalInitEditor) - { - title = "Global Init"; - } - else if (obj is GameEndEditor) - { - title = "Game End"; - } - else - { - Debug.WriteLine($"Could not handle type {obj.GetType()}"); - } - - if (title is not null) - { - // "\t" is displayed as 8 spaces. - // So, replace all "\t" with spaces, - // in order to properly shorten the title. - title = title.Replace("\t", " "); - - if (title.Length > 64) - title = title[..64] + "..."; - } - - return title; - } - - public static void SetTabTitleBinding(object obj, object prevObj, TextBlock textBlock = null) - { - if (textBlock is null) - { - var cont = mainWindow.TabController.ItemContainerGenerator.ContainerFromIndex(mainWindow.CurrentTabIndex); - textBlock = MainWindow.FindVisualChild(cont); - } - else - obj = (textBlock.DataContext as Tab)?.CurrentObject; - - if (obj is null || textBlock is null) - return; - - bool objNamed = obj is UndertaleNamedResource; - bool objString = obj is UndertaleString; - - if (prevObj is not null) - { - bool pObjNamed = prevObj is UndertaleNamedResource; - bool pObjString = prevObj is UndertaleString; - - // If both objects have the same type (one of above) - // or both objects are not "UndertaleNamedResource", - // then there's no need to change the binding - if (pObjNamed && objNamed || pObjString && objString || !(pObjNamed || objNamed)) - return; - } - - MultiBinding binding = new() - { - Converter = TabTitleConverter.Instance, - Mode = BindingMode.OneWay - }; - binding.Bindings.Add(new Binding() { Mode = BindingMode.OneTime }); - - // These bindings are only for notification - binding.Bindings.Add(new Binding("CurrentObject") { Mode = BindingMode.OneWay }); - if (objNamed) - binding.Bindings.Add(new Binding("CurrentObject.Name.Content") { Mode = BindingMode.OneWay }); - else if (objString) - binding.Bindings.Add(new Binding("CurrentObject.Content") { Mode = BindingMode.OneWay }); - - textBlock.SetBinding(TextBlock.TextProperty, binding); - } - - public override string ToString() - { - // for ease of debugging - return GetType().FullName + " - {" + CurrentObject?.ToString() + '}'; - } - } - public class TabTitleConverter : IMultiValueConverter - { - public static TabTitleConverter Instance { get; } = new(); - - public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) - { - if (values[0] is not Tab tab) - return null; - - if (!tab.IsCustomTitle) - tab.TabTitle = Tab.GetTitleForObject(tab.CurrentObject); - - return tab.TabTitle; - } - - public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } - } - /// /// Logika interakcji dla klasy MainWindow.xaml /// diff --git a/UndertaleModTool/Tab.cs b/UndertaleModTool/Tab.cs new file mode 100644 index 000000000..8a58a433c --- /dev/null +++ b/UndertaleModTool/Tab.cs @@ -0,0 +1,247 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Media.Imaging; +using UndertaleModLib.Models; +using UndertaleModLib; +using System.Windows; + +namespace UndertaleModTool +{ + public class Tab : INotifyPropertyChanged + { + public static readonly BitmapImage ClosedIcon = new(new Uri(@"/Resources/X.png", UriKind.RelativeOrAbsolute)); + public static readonly BitmapImage ClosedHoverIcon = new(new Uri(@"/Resources/X_Down.png", UriKind.RelativeOrAbsolute)); + + private static readonly MainWindow mainWindow = Application.Current.MainWindow as MainWindow; + + public event PropertyChangedEventHandler PropertyChanged; + + private object _currentObject; + + [PropertyChanged.DoNotNotify] // Prevents "PropertyChanged.Invoke()" injection on compile + public object CurrentObject + { + get => _currentObject; + set + { + object prevObj = _currentObject; + _currentObject = value; + + SetTabTitleBinding(value, prevObj); + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentObject))); + mainWindow.RaiseOnSelectedChanged(); + } + } + public string TabTitle { get; set; } = "Untitled"; + public bool IsCustomTitle { get; set; } + public int TabIndex { get; set; } + public bool AutoClose { get; set; } = false; + + public ObservableCollection History { get; } = new(); + public int HistoryPosition { get; set; } + + public Tab(object obj, int tabIndex, string tabTitle = null) + { + CurrentObject = obj; + TabIndex = tabIndex; + AutoClose = obj is DescriptionView; + + IsCustomTitle = tabTitle is not null; + if (IsCustomTitle) + { + if (tabTitle.Length > 64) + TabTitle = tabTitle[..64] + "..."; + else + TabTitle = tabTitle; + } + } + + public static string GetTitleForObject(object obj) + { + if (obj is null) + return null; + + string title = null; + + if (obj is DescriptionView view) + { + if (view.Heading.Contains("Welcome")) + { + title = "Welcome!"; + } + else + { + title = view.Heading; + } + } + else if (obj is UndertaleNamedResource namedRes) + { + string content = namedRes.Name?.Content; + + string header = obj switch + { + UndertaleAudioGroup => "Audio Group", + UndertaleSound => "Sound", + UndertaleSprite => "Sprite", + UndertaleBackground => "Background", + UndertalePath => "Path", + UndertaleScript => "Script", + UndertaleShader => "Shader", + UndertaleFont => "Font", + UndertaleTimeline => "Timeline", + UndertaleGameObject => "Game Object", + UndertaleRoom => "Room", + UndertaleExtension => "Extension", + UndertaleTexturePageItem => "Texture Page Item", + UndertaleCode => "Code", + UndertaleVariable => "Variable", + UndertaleFunction => "Function", + UndertaleCodeLocals => "Code Locals", + UndertaleEmbeddedTexture => "Embedded Texture", + UndertaleEmbeddedAudio => "Embedded Audio", + UndertaleTextureGroupInfo => "Texture Group Info", + UndertaleEmbeddedImage => "Embedded Image", + UndertaleSequence => "Sequence", + UndertaleAnimationCurve => "Animation Curve", + _ => null + }; + + if (header is not null) + title = header + " - " + content; + else + Debug.WriteLine($"Could not handle type {obj.GetType()}"); + } + else if (obj is UndertaleString str) + { + string stringFirstLine = str.Content; + if (stringFirstLine is not null) + { + if (stringFirstLine.Length == 0) + stringFirstLine = "(empty string)"; + else + { + int stringLength = StringTitleConverter.NewLineRegex.Match(stringFirstLine).Index; + if (stringLength != 0) + stringFirstLine = stringFirstLine[..stringLength] + " ..."; + } + } + + title = "String - " + stringFirstLine; + } + else if (obj is UndertaleChunkVARI) + { + title = "Variables Overview"; + } + else if (obj is GeneralInfoEditor) + { + title = "General Info"; + } + else if (obj is GlobalInitEditor) + { + title = "Global Init"; + } + else if (obj is GameEndEditor) + { + title = "Game End"; + } + else + { + Debug.WriteLine($"Could not handle type {obj.GetType()}"); + } + + if (title is not null) + { + // "\t" is displayed as 8 spaces. + // So, replace all "\t" with spaces, + // in order to properly shorten the title. + title = title.Replace("\t", " "); + + if (title.Length > 64) + title = title[..64] + "..."; + } + + return title; + } + + public static void SetTabTitleBinding(object obj, object prevObj, TextBlock textBlock = null) + { + if (textBlock is null) + { + var cont = mainWindow.TabController.ItemContainerGenerator.ContainerFromIndex(mainWindow.CurrentTabIndex); + textBlock = MainWindow.FindVisualChild(cont); + } + else + obj = (textBlock.DataContext as Tab)?.CurrentObject; + + if (obj is null || textBlock is null) + return; + + bool objNamed = obj is UndertaleNamedResource; + bool objString = obj is UndertaleString; + + if (prevObj is not null) + { + bool pObjNamed = prevObj is UndertaleNamedResource; + bool pObjString = prevObj is UndertaleString; + + // If both objects have the same type (one of above) + // or both objects are not "UndertaleNamedResource", + // then there's no need to change the binding + if (pObjNamed && objNamed || pObjString && objString || !(pObjNamed || objNamed)) + return; + } + + MultiBinding binding = new() + { + Converter = TabTitleConverter.Instance, + Mode = BindingMode.OneWay + }; + binding.Bindings.Add(new Binding() { Mode = BindingMode.OneTime }); + + // These bindings are only for notification + binding.Bindings.Add(new Binding("CurrentObject") { Mode = BindingMode.OneWay }); + if (objNamed) + binding.Bindings.Add(new Binding("CurrentObject.Name.Content") { Mode = BindingMode.OneWay }); + else if (objString) + binding.Bindings.Add(new Binding("CurrentObject.Content") { Mode = BindingMode.OneWay }); + + textBlock.SetBinding(TextBlock.TextProperty, binding); + } + + public override string ToString() + { + // for ease of debugging + return GetType().FullName + " - {" + CurrentObject?.ToString() + '}'; + } + } + public class TabTitleConverter : IMultiValueConverter + { + public static TabTitleConverter Instance { get; } = new(); + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values[0] is not Tab tab) + return null; + + if (!tab.IsCustomTitle) + tab.TabTitle = Tab.GetTitleForObject(tab.CurrentObject); + + return tab.TabTitle; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} From 35f8652550203b61fe85ed893080a629573445c2 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Sat, 24 Dec 2022 21:53:27 +0300 Subject: [PATCH 02/18] Added new classes for the last tab content state; documented all tabs related classes. --- UndertaleModTool/Tab.cs | 54 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/UndertaleModTool/Tab.cs b/UndertaleModTool/Tab.cs index 8a58a433c..9a9ae61d6 100644 --- a/UndertaleModTool/Tab.cs +++ b/UndertaleModTool/Tab.cs @@ -16,17 +16,25 @@ namespace UndertaleModTool { + /// + /// Stores various information about a tab in the object editor. + /// public class Tab : INotifyPropertyChanged { + /// The default icon for the close button. public static readonly BitmapImage ClosedIcon = new(new Uri(@"/Resources/X.png", UriKind.RelativeOrAbsolute)); + + /// The icon for the hovered close button. public static readonly BitmapImage ClosedHoverIcon = new(new Uri(@"/Resources/X_Down.png", UriKind.RelativeOrAbsolute)); private static readonly MainWindow mainWindow = Application.Current.MainWindow as MainWindow; + /// public event PropertyChangedEventHandler PropertyChanged; private object _currentObject; + /// The currently opened object in this tab. [PropertyChanged.DoNotNotify] // Prevents "PropertyChanged.Invoke()" injection on compile public object CurrentObject { @@ -42,14 +50,34 @@ public object CurrentObject mainWindow.RaiseOnSelectedChanged(); } } + + /// The tab title. + /// "Untitled" by default. public string TabTitle { get; set; } = "Untitled"; + + /// Whether the title of this tab is autogenerated. public bool IsCustomTitle { get; set; } + + /// The index of this tab. public int TabIndex { get; set; } + + /// Whether this tab should be closed automatically. public bool AutoClose { get; set; } = false; + + /// The history of objects opened in this tab. public ObservableCollection History { get; } = new(); + + /// The current position in the opened object history. public int HistoryPosition { get; set; } + /// The last state of the opened editor. + public TabContentState LastContentState { get; set; } + + /// Initializes a new instance of . + /// The object that should be open. + /// The tab index. + /// The tab title. public Tab(object obj, int tabIndex, string tabTitle = null) { CurrentObject = obj; @@ -66,6 +94,7 @@ public Tab(object obj, int tabIndex, string tabTitle = null) } } + /// Generates a tab title depending on a type of the object. public static string GetTitleForObject(object obj) { if (obj is null) @@ -173,6 +202,10 @@ public static string GetTitleForObject(object obj) return title; } + /// Changes a data binding of the title. + /// The current object. + /// The previous object. + /// A reference to the that displays the title. public static void SetTabTitleBinding(object obj, object prevObj, TextBlock textBlock = null) { if (textBlock is null) @@ -218,16 +251,36 @@ public static void SetTabTitleBinding(object obj, object prevObj, TextBlock text textBlock.SetBinding(TextBlock.TextProperty, binding); } + /// public override string ToString() { // for ease of debugging return GetType().FullName + " - {" + CurrentObject?.ToString() + '}'; } } + + /// A base class for the information of a tab content state. + public class TabContentState + { + /// The scroll position of the object editor. + public double MainScrollPosition; + } + + /// Stores the information about the tab with a code. + public class CodeTabState : TabContentState + { + /// The code line number. + public int LineNumber; + } + + + /// A converter that generates the tab title from the tab reference. public class TabTitleConverter : IMultiValueConverter { + /// A static instance of . public static TabTitleConverter Instance { get; } = new(); + /// public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if (values[0] is not Tab tab) @@ -239,6 +292,7 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur return tab.TabTitle; } + /// public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotImplementedException(); From 53a6e0d77ae5c1cfd1009b8e5c6bfe6725ac603e Mon Sep 17 00:00:00 2001 From: VladiStep Date: Tue, 10 Jan 2023 15:43:06 +0300 Subject: [PATCH 03/18] Code editor fixes and improvements 1) Code editor preserves caret position after compiling, between "Decompiled" and "Disassembled" tabs, and between the tabs. 2) The changes from the PR #1148. 3) A little performance improvement of "NameGenerator" and "NumberGenerator" (code editor instance is declared on class init). --- .../Editors/UndertaleCodeEditor.xaml | 17 +- .../Editors/UndertaleCodeEditor.xaml.cs | 113 ++++++++--- UndertaleModTool/MainWindow.xaml.cs | 36 +++- UndertaleModTool/Tab.cs | 185 +++++++++++++++++- 4 files changed, 308 insertions(+), 43 deletions(-) diff --git a/UndertaleModTool/Editors/UndertaleCodeEditor.xaml b/UndertaleModTool/Editors/UndertaleCodeEditor.xaml index d22a14074..5b876aa9c 100644 --- a/UndertaleModTool/Editors/UndertaleCodeEditor.xaml +++ b/UndertaleModTool/Editors/UndertaleCodeEditor.xaml @@ -1,12 +1,13 @@  + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="clr-namespace:UndertaleModTool" + xmlns:undertale="clr-namespace:UndertaleModLib.Models;assembly=UndertaleModLib" + mc:Ignorable="d" + d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance undertale:UndertaleCode}" + DataContextChanged="UserControl_DataContextChanged" Unloaded="DataUserControl_Unloaded"> diff --git a/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs b/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs index fb16146fc..6faac223a 100644 --- a/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs +++ b/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs @@ -56,12 +56,14 @@ public partial class UndertaleCodeEditor : DataUserControl public bool DecompiledYet = false; public bool DecompiledSkipped = false; public SearchPanel DecompiledSearchPanel; + public static (int Caret, int Line) OverriddenDecompPos; public bool DisassemblyFocused = false; public bool DisassemblyChanged = false; public bool DisassembledYet = false; public bool DisassemblySkipped = false; public SearchPanel DisassemblySearchPanel; + public static (int Caret, int Line) OverriddenDisasmPos; public static RoutedUICommand Compile = new RoutedUICommand("Compile code", "Compile", typeof(UndertaleCodeEditor)); @@ -131,8 +133,8 @@ public UndertaleCodeEditor() DecompiledEditor.Options.ConvertTabsToSpaces = true; - DecompiledEditor.TextArea.TextView.ElementGenerators.Add(new NumberGenerator()); - DecompiledEditor.TextArea.TextView.ElementGenerators.Add(new NameGenerator()); + DecompiledEditor.TextArea.TextView.ElementGenerators.Add(new NumberGenerator(this)); + DecompiledEditor.TextArea.TextView.ElementGenerators.Add(new NameGenerator(this)); DecompiledEditor.TextArea.TextView.Options.HighlightCurrentLine = true; DecompiledEditor.TextArea.TextView.CurrentLineBackground = new SolidColorBrush(Color.FromRgb(60, 60, 60)); @@ -162,7 +164,7 @@ public UndertaleCodeEditor() } } - DisassemblyEditor.TextArea.TextView.ElementGenerators.Add(new NameGenerator()); + DisassemblyEditor.TextArea.TextView.ElementGenerators.Add(new NameGenerator(this)); DisassemblyEditor.TextArea.TextView.Options.HighlightCurrentLine = true; DisassemblyEditor.TextArea.TextView.CurrentLineBackground = new SolidColorBrush(Color.FromRgb(60, 60, 60)); @@ -176,6 +178,12 @@ public UndertaleCodeEditor() DisassemblyEditor.TextArea.SelectionCornerRadius = 0; } + private void DataUserControl_Unloaded(object sender, RoutedEventArgs e) + { + OverriddenDecompPos = default; + OverriddenDisasmPos = default; + } + private void SearchPanel_LostFocus(object sender, RoutedEventArgs e) { SearchPanel panel = sender as SearchPanel; @@ -231,8 +239,7 @@ private async void UserControl_DataContextChanged(object sender, DependencyPrope CurrentDecompiled is not null && CurrentDecompiled != code) { DecompiledSkipped = true; - DecompiledEditor_LostFocus(sender, null); - + await DecompiledLostFocusBody(sender, null); } else if (DisassemblyTab.IsSelected && DisassemblyFocused && DisassemblyChanged && CurrentDisassembled is not null && CurrentDisassembled != code) @@ -241,9 +248,14 @@ private async void UserControl_DataContextChanged(object sender, DependencyPrope DisassemblyEditor_LostFocus(sender, null); } - DecompiledEditor_LostFocus(sender, null); + await DecompiledLostFocusBody(sender, null); DisassemblyEditor_LostFocus(sender, null); + DecompiledYet = false; + DisassembledYet = false; + CurrentDecompiled = null; + CurrentDisassembled = null; + if (MainWindow.CodeEditorDecompile != Unstated) //if opened from the code search results "link" { if (MainWindow.CodeEditorDecompile == DontDecompile && code != CurrentDisassembled) @@ -309,6 +321,21 @@ private void DisassembleCode(UndertaleCode code, bool first) string text; + int caretOffset = 0; + int currLine = 1; + if (!first) + { + caretOffset = DisassemblyEditor.CaretOffset; + currLine = DisassemblyEditor.Document.GetLineByOffset(caretOffset).LineNumber; + } + else if (OverriddenDisasmPos != default) + { + caretOffset = OverriddenDisasmPos.Caret; + currLine = OverriddenDisasmPos.Line; + + OverriddenDisasmPos = default; + } + DisassemblyEditor.TextArea.ClearSelection(); if (code.ParentEntry != null) { @@ -327,6 +354,16 @@ private void DisassembleCode(UndertaleCode code, bool first) DisassemblyEditor.Document.BeginUpdate(); DisassemblyEditor.Document.Text = text; + + if (caretOffset <= text.Length) + { + DisassemblyEditor.CaretOffset = caretOffset; + if (currLine != -1) + DisassemblyEditor.ScrollToLine(currLine); + else + DisassemblyEditor.ScrollToEnd(); + } + DisassemblyEditor.Document.EndUpdate(); if (first) @@ -398,7 +435,24 @@ private string UpdateGettextJSON(string json) private async Task DecompileCode(UndertaleCode code, bool first, LoaderDialog existingDialog = null) { DecompiledEditor.IsReadOnly = true; + + int caretOffset = 0; + int currLine = 1; + if (!first) + { + caretOffset = DecompiledEditor.CaretOffset; + currLine = DecompiledEditor.Document.GetLineByOffset(caretOffset).LineNumber; + } + else if (OverriddenDecompPos != default) + { + caretOffset = OverriddenDecompPos.Caret; + currLine = OverriddenDecompPos.Line; + + OverriddenDecompPos = default; + } + DecompiledEditor.TextArea.ClearSelection(); + if (code.ParentEntry != null) { DecompiledEditor.Text = "// This code entry is a reference to an anonymous function within " + code.ParentEntry.Name.Content + ", view it there"; @@ -539,6 +593,15 @@ private async Task DecompileCode(UndertaleCode code, bool first, LoaderDialog ex CurrentLocals.Add(local.Name.Content); } + if (caretOffset <= decompiled.Length) + { + DecompiledEditor.CaretOffset = caretOffset; + if (currLine != -1) + DecompiledEditor.ScrollToLine(currLine); + else + DecompiledEditor.ScrollToEnd(); + } + if (existingDialog is not null) //if code was edited (and compiles after it) { dataa.GMLCacheChanged.Add(code.Name.Content); @@ -547,10 +610,12 @@ private async Task DecompileCode(UndertaleCode code, bool first, LoaderDialog ex openSaveDialog = mainWindow.IsSaving; } } + DecompiledEditor.Document.EndUpdate(); DecompiledEditor.IsReadOnly = false; if (first) DecompiledEditor.Document.UndoStack.ClearAll(); + DecompiledChanged = false; CurrentDecompiled = code; @@ -796,10 +861,12 @@ private void DisassemblyEditor_LostFocus(object sender, RoutedEventArgs e) // Based on https://stackoverflow.com/questions/28379206/custom-hyperlinks-using-avalonedit public class NumberGenerator : VisualLineElementGenerator { - readonly static Regex regex = new Regex(@"-?\d+\.?"); + private static readonly Regex regex = new Regex(@"-?\d+\.?"); + private readonly UndertaleCodeEditor codeEditorInst; - public NumberGenerator() + public NumberGenerator(UndertaleCodeEditor codeEditorInst) { + this.codeEditorInst = codeEditorInst; } Match FindMatch(int startOffset, Regex r) @@ -866,20 +933,13 @@ public override VisualLineElement ConstructElement(int offset) var doc = CurrentContext.Document; var textArea = CurrentContext.TextView.GetService(typeof(TextArea)) as TextArea; var editor = textArea.GetService(typeof(TextEditor)) as TextEditor; - var parent = VisualTreeHelper.GetParent(editor); - do - { - if ((parent as FrameworkElement) is UserControl) - break; - parent = VisualTreeHelper.GetParent(parent); - } while (parent != null); line.Clicked += (text) => { if (text.EndsWith(".")) return; if (int.TryParse(text, out int id)) { - (parent as UndertaleCodeEditor).DecompiledFocused = true; + codeEditorInst.DecompiledFocused = true; UndertaleData data = mainWindow.Data; List possibleObjects = new List(); @@ -920,7 +980,7 @@ public override VisualLineElement ConstructElement(int offset) { doc.Replace(line.ParentVisualLine.StartOffset + line.RelativeTextOffset, text.Length, (obj as UndertaleNamedResource).Name.Content, null); - (parent as UndertaleCodeEditor).DecompiledChanged = true; + codeEditorInst.DecompiledChanged = true; } }; contextMenu.Items.Add(item); @@ -935,7 +995,7 @@ public override VisualLineElement ConstructElement(int offset) { doc.Replace(line.ParentVisualLine.StartOffset + line.RelativeTextOffset, text.Length, "0x" + id.ToString("X6"), null); - (parent as UndertaleCodeEditor).DecompiledChanged = true; + codeEditorInst.DecompiledChanged = true; } }; contextMenu.Items.Add(item); @@ -952,7 +1012,7 @@ public override VisualLineElement ConstructElement(int offset) { doc.Replace(line.ParentVisualLine.StartOffset + line.RelativeTextOffset, text.Length, myKey, null); - (parent as UndertaleCodeEditor).DecompiledChanged = true; + codeEditorInst.DecompiledChanged = true; } }; contextMenu.Items.Add(item); @@ -971,10 +1031,12 @@ public override VisualLineElement ConstructElement(int offset) public class NameGenerator : VisualLineElementGenerator { - readonly static Regex regex = new Regex(@"[_a-zA-Z][_a-zA-Z0-9]*"); + private static readonly Regex regex = new Regex(@"[_a-zA-Z][_a-zA-Z0-9]*"); + private readonly UndertaleCodeEditor codeEditorInst; - public NameGenerator() + public NameGenerator(UndertaleCodeEditor codeEditorInst) { + this.codeEditorInst = codeEditorInst; } Match FindMatch(int startOffset, Regex r) @@ -1046,13 +1108,6 @@ public override VisualLineElement ConstructElement(int offset) var doc = CurrentContext.Document; var textArea = CurrentContext.TextView.GetService(typeof(TextArea)) as TextArea; var editor = textArea.GetService(typeof(TextEditor)) as TextEditor; - var parent = VisualTreeHelper.GetParent(editor); - do - { - if ((parent as FrameworkElement) is UserControl) - break; - parent = VisualTreeHelper.GetParent(parent); - } while (parent != null); // Process the content of this identifier/function if (func) @@ -1114,7 +1169,7 @@ public override VisualLineElement ConstructElement(int offset) data.BuiltinList.GlobalArray.ContainsKey(m.Value)) return new ColorVisualLineText(m.Value, CurrentContext.VisualLine, m.Length, new SolidColorBrush(Color.FromRgb(0x58, 0xE3, 0x5A))); - if ((parent as UndertaleCodeEditor).CurrentLocals.Contains(m.Value)) + if (codeEditorInst.CurrentLocals.Contains(m.Value)) return new ColorVisualLineText(m.Value, CurrentContext.VisualLine, m.Length, new SolidColorBrush(Color.FromRgb(0xFF, 0xF8, 0x99))); return null; diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index ba3a6132a..9c9328b96 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -130,6 +130,8 @@ public enum ScrollDirection Left, Right } + + // TODO: move this to the code editor public static CodeEditorMode CodeEditorDecompile { get; set; } = CodeEditorMode.Unstated; private int progressValue; @@ -3234,6 +3236,9 @@ public void CloseTab(int tabIndex, bool addDefaultTab = true) if (Tabs.Count == 0) { + if (!closingTab.AutoClose) + CurrentTab.SaveTabContentState(DataEditor); + CurrentTabIndex = -1; CurrentTab = null; @@ -3250,6 +3255,8 @@ public void CloseTab(int tabIndex, bool addDefaultTab = true) } else { + bool tabIsChanged = false; + for (int i = tabIndex; i < Tabs.Count; i++) Tabs[i].TabIndex = i; @@ -3262,8 +3269,14 @@ public void CloseTab(int tabIndex, bool addDefaultTab = true) // switch to the last tab currIndex = Tabs.Count - 1; } - else if (currIndex != 0) - currIndex -= 1; + else + { + if (currIndex != 0) + currIndex -= 1; + + tabIsChanged = true; + CurrentTab.SaveTabContentState(DataEditor); + } } else if (currIndex > tabIndex) { @@ -3273,7 +3286,15 @@ public void CloseTab(int tabIndex, bool addDefaultTab = true) TabController.SelectionChanged += TabController_SelectionChanged; CurrentTabIndex = currIndex; - CurrentTab = Tabs[CurrentTabIndex]; + Tab newTab = Tabs[CurrentTabIndex]; + + if (tabIsChanged) + newTab.PrepareCodeEditor(); + + CurrentTab = newTab; + + if (tabIsChanged) + _ = CurrentTab.RestoreTabContentState(DataEditor); } } } @@ -3303,10 +3324,17 @@ private void TabController_SelectionChanged(object sender, SelectionChangedEvent { if (TabController.SelectedIndex >= 0) { + CurrentTab?.SaveTabContentState(DataEditor); + ScrollToTab(CurrentTabIndex); - CurrentTab = Tabs[CurrentTabIndex]; + Tab newTab = Tabs[CurrentTabIndex]; + newTab.PrepareCodeEditor(); + CurrentTab = newTab; + UpdateObjectLabel(CurrentTab.CurrentObject); + + _ = CurrentTab.RestoreTabContentState(DataEditor); } } diff --git a/UndertaleModTool/Tab.cs b/UndertaleModTool/Tab.cs index 9a9ae61d6..a07e6188d 100644 --- a/UndertaleModTool/Tab.cs +++ b/UndertaleModTool/Tab.cs @@ -13,6 +13,8 @@ using UndertaleModLib.Models; using UndertaleModLib; using System.Windows; +using System.Windows.Media; +using System.Windows.Threading; namespace UndertaleModTool { @@ -251,6 +253,172 @@ public static void SetTabTitleBinding(object obj, object prevObj, TextBlock text textBlock.SetBinding(TextBlock.TextProperty, binding); } + /// Saves the current tab content state. + /// A reference to the object editor of main window. + public void SaveTabContentState(ContentControl dataEditor) + { + if (dataEditor is null + || dataEditor.Content is null + || dataEditor.Content is DescriptionView) + return; + + UserControl editor; + try + { + var contPres = VisualTreeHelper.GetChild(dataEditor, 0); + editor = (UserControl)VisualTreeHelper.GetChild(contPres, 0); + } + catch + { + mainWindow.ShowWarning("The last tab content state can't be saved - \"UserControl\" is not found."); + return; + } + + double mainScrollPos = MainWindow.GetNearestParent(dataEditor)?.VerticalOffset ?? 0; + + switch (editor) + { + case UndertaleCodeEditor codeEditor: + #pragma warning disable CA1416 + bool isDecompiledOpen = codeEditor.CodeModeTabs.SelectedIndex == 0; + + var textEditor = codeEditor.DecompiledEditor; + (int, int) decompCodePos; + int caretOffset, linePos; + // If the overridden position wasn't read + if (UndertaleCodeEditor.OverriddenDecompPos != default) + { + decompCodePos = UndertaleCodeEditor.OverriddenDecompPos; + UndertaleCodeEditor.OverriddenDecompPos = default; + } + else + { + caretOffset = textEditor.CaretOffset; + linePos = textEditor.Document.GetLineByOffset(caretOffset).LineNumber; + decompCodePos = (caretOffset, linePos); + } + + textEditor = codeEditor.DisassemblyEditor; + (int, int) disasmCodePos; + if (UndertaleCodeEditor.OverriddenDisasmPos != default) + { + disasmCodePos = UndertaleCodeEditor.OverriddenDisasmPos; + UndertaleCodeEditor.OverriddenDisasmPos = default; + } + else + { + caretOffset = textEditor.CaretOffset; + linePos = textEditor.Document.GetLineByOffset(caretOffset).LineNumber; + disasmCodePos = (caretOffset, linePos); + } + #pragma warning restore CA1416 + + LastContentState = new CodeTabState() + { + MainScrollPosition = mainScrollPos, + DecompiledCodePosition = decompCodePos, + DisassemblyCodePosition = disasmCodePos, + IsDecompiledOpen = isDecompiledOpen + }; + break; + + case UndertaleRoomEditor roomEditor: + + break; + + default: + LastContentState = new() + { + MainScrollPosition = mainScrollPos + }; + break; + } + } + + /// Restores the last tab content state. + /// A reference to the object editor of main window. + public async Task RestoreTabContentState(ContentControl dataEditor) + { + if (dataEditor is null + || dataEditor.Content is null + || dataEditor.Content is DescriptionView + || LastContentState is null) + return; + + UserControl editor; + try + { + // Wait until the new editor will be loaded + await mainWindow.Dispatcher.InvokeAsync(() => { }, DispatcherPriority.ApplicationIdle); + + var contPres = VisualTreeHelper.GetChild(dataEditor, 0); + editor = (UserControl)VisualTreeHelper.GetChild(contPres, 0); + } + catch + { + mainWindow.ShowWarning("The last tab content state can't be restored - \"UserControl\" is not found."); + return; + } + + if (LastContentState.MainScrollPosition != 0) + { + ScrollViewer mainScrollViewer = MainWindow.GetNearestParent(dataEditor); + if (mainScrollViewer is null) + { + mainWindow.ShowWarning("The last tab content state can't be restored - \"ScrollViewer\" is not found."); + return; + } + + mainScrollViewer.ScrollToVerticalOffset(LastContentState.MainScrollPosition); + } + + // if "LastContentState" is an instance of "TabContentState" (e.g. not "CodeTabState") + if (!LastContentState.GetType().IsSubclassOf(typeof(TabContentState))) + { + LastContentState = null; + return; + } + + switch (LastContentState) + { + case CodeTabState: + // processed in "Tab.PrepareCodeEditor()" + break; + + case RoomTabState roomTabState: + + break; + + default: + Debug.WriteLine($"The content state of a tab \"{this}\" is unknown?"); + break; + } + + LastContentState = null; + } + + + /// + /// Prepares the code editor before opening the code entry + /// by setting a corresponding mode ("Decompiled" or "Disassembly") and restoring the code positions. + /// + /// Does nothing if it's not a code tab. + public void PrepareCodeEditor() + { + if (LastContentState is CodeTabState codeTabState) + { + if (codeTabState.IsDecompiledOpen) + MainWindow.CodeEditorDecompile = MainWindow.CodeEditorMode.Decompile; + else + MainWindow.CodeEditorDecompile = MainWindow.CodeEditorMode.DontDecompile; + + #pragma warning disable CA1416 + UndertaleCodeEditor.OverriddenDecompPos = codeTabState.DecompiledCodePosition; + UndertaleCodeEditor.OverriddenDisasmPos = codeTabState.DisassemblyCodePosition; + #pragma warning restore CA1416 + } + } + /// public override string ToString() { @@ -269,8 +437,21 @@ public class TabContentState /// Stores the information about the tab with a code. public class CodeTabState : TabContentState { - /// The code line number. - public int LineNumber; + /// The decompiled code position (absolute caret offset and line number). + public (int Caret, int Line) DecompiledCodePosition; + + /// The disassembly code position (absolute caret offset and line number). + public (int Caret, int Line) DisassemblyCodePosition; + + /// Whether the "Decompiled" tab is open. + public bool IsDecompiledOpen; + } + + /// Stores the information about the tab with a room. + public class RoomTabState : TabContentState + { + /// The scroll position of the room editor preview. + public (double Left, double Top) RoomPreviewScrollPosition; } From d68164f6a25b403a87a8ae9a3202c315ce03d0f5 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Thu, 12 Jan 2023 01:04:18 +0300 Subject: [PATCH 04/18] Improved code editor caret position saving and restoring. --- .../Editors/UndertaleCodeEditor.xaml.cs | 60 +++++++++++-------- UndertaleModTool/Tab.cs | 36 +++++++---- 2 files changed, 59 insertions(+), 37 deletions(-) diff --git a/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs b/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs index 6faac223a..c3b7147e4 100644 --- a/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs +++ b/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs @@ -56,14 +56,14 @@ public partial class UndertaleCodeEditor : DataUserControl public bool DecompiledYet = false; public bool DecompiledSkipped = false; public SearchPanel DecompiledSearchPanel; - public static (int Caret, int Line) OverriddenDecompPos; + public static (int Line, int Column) OverriddenDecompPos; public bool DisassemblyFocused = false; public bool DisassemblyChanged = false; public bool DisassembledYet = false; public bool DisassemblySkipped = false; public SearchPanel DisassemblySearchPanel; - public static (int Caret, int Line) OverriddenDisasmPos; + public static (int Line, int Column) OverriddenDisasmPos; public static RoutedUICommand Compile = new RoutedUICommand("Compile code", "Compile", typeof(UndertaleCodeEditor)); @@ -315,23 +315,44 @@ public async Task SaveChanges() await CompileCommandBody(null, null); } + private void RestoreCaretPosition(TextEditor textEditor, int linePos, int columnPos) + { + if (linePos <= textEditor.LineCount) + { + int lineLen = textEditor.Document.GetLineByNumber(linePos).Length; + textEditor.TextArea.Caret.Line = linePos; + if (columnPos != -1) + textEditor.TextArea.Caret.Column = columnPos; + else + textEditor.TextArea.Caret.Column = lineLen + 1; + + textEditor.ScrollToLine(linePos); + } + else + { + textEditor.CaretOffset = textEditor.Text.Length; + textEditor.ScrollToEnd(); + } + } + private void DisassembleCode(UndertaleCode code, bool first) { code.UpdateAddresses(); string text; - int caretOffset = 0; int currLine = 1; + int currColumn = 1; if (!first) { - caretOffset = DisassemblyEditor.CaretOffset; - currLine = DisassemblyEditor.Document.GetLineByOffset(caretOffset).LineNumber; + var caret = DisassemblyEditor.TextArea.Caret; + currLine = caret.Line; + currColumn = caret.Column; } else if (OverriddenDisasmPos != default) { - caretOffset = OverriddenDisasmPos.Caret; currLine = OverriddenDisasmPos.Line; + currColumn = OverriddenDisasmPos.Column; OverriddenDisasmPos = default; } @@ -355,14 +376,7 @@ private void DisassembleCode(UndertaleCode code, bool first) DisassemblyEditor.Document.BeginUpdate(); DisassemblyEditor.Document.Text = text; - if (caretOffset <= text.Length) - { - DisassemblyEditor.CaretOffset = caretOffset; - if (currLine != -1) - DisassemblyEditor.ScrollToLine(currLine); - else - DisassemblyEditor.ScrollToEnd(); - } + RestoreCaretPosition(DisassemblyEditor, currLine, currColumn); DisassemblyEditor.Document.EndUpdate(); @@ -436,17 +450,18 @@ private async Task DecompileCode(UndertaleCode code, bool first, LoaderDialog ex { DecompiledEditor.IsReadOnly = true; - int caretOffset = 0; int currLine = 1; + int currColumn = 1; if (!first) { - caretOffset = DecompiledEditor.CaretOffset; - currLine = DecompiledEditor.Document.GetLineByOffset(caretOffset).LineNumber; + var caret = DecompiledEditor.TextArea.Caret; + currLine = caret.Line; + currColumn = caret.Column; } else if (OverriddenDecompPos != default) { - caretOffset = OverriddenDecompPos.Caret; currLine = OverriddenDecompPos.Line; + currColumn = OverriddenDecompPos.Column; OverriddenDecompPos = default; } @@ -593,14 +608,7 @@ private async Task DecompileCode(UndertaleCode code, bool first, LoaderDialog ex CurrentLocals.Add(local.Name.Content); } - if (caretOffset <= decompiled.Length) - { - DecompiledEditor.CaretOffset = caretOffset; - if (currLine != -1) - DecompiledEditor.ScrollToLine(currLine); - else - DecompiledEditor.ScrollToEnd(); - } + RestoreCaretPosition(DecompiledEditor, currLine, currColumn); if (existingDialog is not null) //if code was edited (and compiles after it) { diff --git a/UndertaleModTool/Tab.cs b/UndertaleModTool/Tab.cs index a07e6188d..234e9d0a5 100644 --- a/UndertaleModTool/Tab.cs +++ b/UndertaleModTool/Tab.cs @@ -284,7 +284,7 @@ public void SaveTabContentState(ContentControl dataEditor) var textEditor = codeEditor.DecompiledEditor; (int, int) decompCodePos; - int caretOffset, linePos; + int linePos, columnPos; // If the overridden position wasn't read if (UndertaleCodeEditor.OverriddenDecompPos != default) { @@ -293,9 +293,16 @@ public void SaveTabContentState(ContentControl dataEditor) } else { - caretOffset = textEditor.CaretOffset; - linePos = textEditor.Document.GetLineByOffset(caretOffset).LineNumber; - decompCodePos = (caretOffset, linePos); + var caret = textEditor.TextArea.Caret; + linePos = caret.Line; + columnPos = caret.Column; + + int lineLen = textEditor.Document.GetLineByNumber(linePos).Length; + // If caret is at the end of line + if (lineLen == columnPos - 1) + columnPos = -1; + + decompCodePos = (linePos, columnPos); } textEditor = codeEditor.DisassemblyEditor; @@ -307,9 +314,16 @@ public void SaveTabContentState(ContentControl dataEditor) } else { - caretOffset = textEditor.CaretOffset; - linePos = textEditor.Document.GetLineByOffset(caretOffset).LineNumber; - disasmCodePos = (caretOffset, linePos); + var caret = textEditor.TextArea.Caret; + linePos = caret.Line; + columnPos = caret.Column; + + int lineLen = textEditor.Document.GetLineByNumber(linePos).Length; + // If caret is at the end of line + if (lineLen == columnPos - 1) + columnPos = -1; + + disasmCodePos = (linePos, columnPos); } #pragma warning restore CA1416 @@ -437,11 +451,11 @@ public class TabContentState /// Stores the information about the tab with a code. public class CodeTabState : TabContentState { - /// The decompiled code position (absolute caret offset and line number). - public (int Caret, int Line) DecompiledCodePosition; + /// The decompiled code position. + public (int Line, int Column) DecompiledCodePosition; - /// The disassembly code position (absolute caret offset and line number). - public (int Caret, int Line) DisassemblyCodePosition; + /// The disassembly code position. + public (int Line, int Column) DisassemblyCodePosition; /// Whether the "Decompiled" tab is open. public bool IsDecompiledOpen; From 2336ce252cfdc105680bf56bd6c862462faecca0 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Thu, 12 Jan 2023 20:51:21 +0300 Subject: [PATCH 05/18] Code editor tab restores its content state even if it's the same code entry. --- .../Editors/UndertaleCodeEditor.xaml.cs | 17 ++++++++++++++++- UndertaleModTool/MainWindow.xaml.cs | 10 ++++++++-- UndertaleModTool/Tab.cs | 16 +++++++++++++--- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs b/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs index c3b7147e4..a95d41aff 100644 --- a/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs +++ b/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs @@ -315,7 +315,22 @@ public async Task SaveChanges() await CompileCommandBody(null, null); } - private void RestoreCaretPosition(TextEditor textEditor, int linePos, int columnPos) + public void RestoreState(CodeTabState tabState) + { + if (tabState.IsDecompiledOpen) + CodeModeTabs.SelectedItem = DecompiledTab; + else + CodeModeTabs.SelectedItem = DisassemblyTab; + + TextEditor textEditor = DecompiledEditor; + (int linePos, int columnPos) = tabState.DecompiledCodePosition; + RestoreCaretPosition(textEditor, linePos, columnPos); + + textEditor = DisassemblyEditor; + (linePos, columnPos) = tabState.DisassemblyCodePosition; + RestoreCaretPosition(textEditor, linePos, columnPos); + } + private static void RestoreCaretPosition(TextEditor textEditor, int linePos, int columnPos) { if (linePos <= textEditor.LineCount) { diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index 9c9328b96..c3dc2ca48 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -3289,7 +3289,10 @@ public void CloseTab(int tabIndex, bool addDefaultTab = true) Tab newTab = Tabs[CurrentTabIndex]; if (tabIsChanged) - newTab.PrepareCodeEditor(); + { + if (closingTab.CurrentObject != newTab.CurrentObject) + newTab.PrepareCodeEditor(); + } CurrentTab = newTab; @@ -3329,7 +3332,10 @@ private void TabController_SelectionChanged(object sender, SelectionChangedEvent ScrollToTab(CurrentTabIndex); Tab newTab = Tabs[CurrentTabIndex]; - newTab.PrepareCodeEditor(); + + if (CurrentTab?.CurrentObject != newTab.CurrentObject) + newTab.PrepareCodeEditor(); + CurrentTab = newTab; UpdateObjectLabel(CurrentTab.CurrentObject); diff --git a/UndertaleModTool/Tab.cs b/UndertaleModTool/Tab.cs index 234e9d0a5..626d5ce98 100644 --- a/UndertaleModTool/Tab.cs +++ b/UndertaleModTool/Tab.cs @@ -395,8 +395,12 @@ public async Task RestoreTabContentState(ContentControl dataEditor) switch (LastContentState) { - case CodeTabState: - // processed in "Tab.PrepareCodeEditor()" + case CodeTabState codeTabState: + // Is executed only if it's the same code entry (between new and old tabs) + #pragma warning disable CA1416 + if (!codeTabState.IsStateRestored) + (editor as UndertaleCodeEditor).RestoreState(codeTabState); + #pragma warning restore CA1416 break; case RoomTabState roomTabState: @@ -421,14 +425,16 @@ public void PrepareCodeEditor() { if (LastContentState is CodeTabState codeTabState) { + #pragma warning disable CA1416 if (codeTabState.IsDecompiledOpen) MainWindow.CodeEditorDecompile = MainWindow.CodeEditorMode.Decompile; else MainWindow.CodeEditorDecompile = MainWindow.CodeEditorMode.DontDecompile; - #pragma warning disable CA1416 UndertaleCodeEditor.OverriddenDecompPos = codeTabState.DecompiledCodePosition; UndertaleCodeEditor.OverriddenDisasmPos = codeTabState.DisassemblyCodePosition; + + codeTabState.IsStateRestored = true; #pragma warning restore CA1416 } } @@ -441,6 +447,7 @@ public override string ToString() } } + /// A base class for the information of a tab content state. public class TabContentState { @@ -459,6 +466,9 @@ public class CodeTabState : TabContentState /// Whether the "Decompiled" tab is open. public bool IsDecompiledOpen; + + /// Whether this state was already restored (applied to the code editor). + public bool IsStateRestored; } /// Stores the information about the tab with a room. From ad4e926fda50ddef1c9f8745bd4d80b3e57d74f9 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Fri, 13 Jan 2023 17:04:56 +0300 Subject: [PATCH 06/18] Implemented room editor tab content saving and restoring, reduced the delay on tab switching. --- .../Editors/UndertaleRoomEditor.xaml.cs | 4 + UndertaleModTool/MainWindow.xaml.cs | 4 +- UndertaleModTool/Tab.cs | 136 +++++++++++++++++- 3 files changed, 139 insertions(+), 5 deletions(-) diff --git a/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs b/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs index dfc545e4f..fe5b6a9b5 100644 --- a/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs +++ b/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs @@ -147,6 +147,10 @@ private void UndertaleRoomEditor_DataContextChanged(object sender, DependencyPro ScrollViewer viewer = MainWindow.FindVisualChild(RoomObjectsTree); viewer.ScrollToVerticalOffset(0); viewer.ScrollToHorizontalOffset(0); + + RoomGraphics.ClearValue(LayoutTransformProperty); + RoomGraphicsScroll.ScrollToVerticalOffset(0); + RoomGraphicsScroll.ScrollToHorizontalOffset(0); } UndertaleCachedImageLoader.Reset(); diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index c3dc2ca48..423969fdd 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -3297,7 +3297,7 @@ public void CloseTab(int tabIndex, bool addDefaultTab = true) CurrentTab = newTab; if (tabIsChanged) - _ = CurrentTab.RestoreTabContentState(DataEditor); + CurrentTab.RestoreTabContentState(DataEditor); } } } @@ -3340,7 +3340,7 @@ private void TabController_SelectionChanged(object sender, SelectionChangedEvent UpdateObjectLabel(CurrentTab.CurrentObject); - _ = CurrentTab.RestoreTabContentState(DataEditor); + CurrentTab.RestoreTabContentState(DataEditor); } } diff --git a/UndertaleModTool/Tab.cs b/UndertaleModTool/Tab.cs index 626d5ce98..7df9fc96f 100644 --- a/UndertaleModTool/Tab.cs +++ b/UndertaleModTool/Tab.cs @@ -15,6 +15,7 @@ using System.Windows; using System.Windows.Media; using System.Windows.Threading; +using static UndertaleModLib.Models.UndertaleRoom; namespace UndertaleModTool { @@ -337,7 +338,31 @@ public void SaveTabContentState(ContentControl dataEditor) break; case UndertaleRoomEditor roomEditor: + ScrollViewer roomPreviewViewer = roomEditor.RoomGraphicsScroll; + (double Left, double Top) previewScrollPos = (roomPreviewViewer.HorizontalOffset, roomPreviewViewer.VerticalOffset); + bool[] objTreeItemsStates = new bool[5] + { + roomEditor.BGItems.IsExpanded, + roomEditor.ViewItems.IsExpanded, + roomEditor.GameObjItems.IsExpanded, + roomEditor.TileItems.IsExpanded, + roomEditor.LayerItems.IsExpanded + }; + ScrollViewer treeObjViewer = MainWindow.FindVisualChild(roomEditor.RoomObjectsTree); + (double Left, double Top) treeScrollPos = (treeObjViewer.HorizontalOffset, treeObjViewer.VerticalOffset); + object selectedObj = roomEditor.RoomObjectsTree.SelectedItem; + if (selectedObj is TreeViewItem item) + selectedObj = item.DataContext; + + LastContentState = new RoomTabState() + { + RoomPreviewScrollPosition = previewScrollPos, + RoomPreviewTransform = roomEditor.RoomGraphics.LayoutTransform, + ObjectTreeItemsStates = objTreeItemsStates, + ObjectsTreeScrollPosition = treeScrollPos, + SelectedObject = selectedObj + }; break; default: @@ -351,7 +376,7 @@ public void SaveTabContentState(ContentControl dataEditor) /// Restores the last tab content state. /// A reference to the object editor of main window. - public async Task RestoreTabContentState(ContentControl dataEditor) + public void RestoreTabContentState(ContentControl dataEditor) { if (dataEditor is null || dataEditor.Content is null @@ -362,8 +387,8 @@ public async Task RestoreTabContentState(ContentControl dataEditor) UserControl editor; try { - // Wait until the new editor will be loaded - await mainWindow.Dispatcher.InvokeAsync(() => { }, DispatcherPriority.ApplicationIdle); + // Wait until the new editor layout will be loaded + dataEditor.UpdateLayout(); var contPres = VisualTreeHelper.GetChild(dataEditor, 0); editor = (UserControl)VisualTreeHelper.GetChild(contPres, 0); @@ -404,7 +429,96 @@ public async Task RestoreTabContentState(ContentControl dataEditor) break; case RoomTabState roomTabState: + var roomEditor = editor as UndertaleRoomEditor; + + roomEditor.RoomGraphics.LayoutTransform = roomTabState.RoomPreviewTransform; + roomEditor.RoomGraphics.UpdateLayout(); + + ScrollViewer roomPreviewViewer = roomEditor.RoomGraphicsScroll; + roomPreviewViewer.ScrollToHorizontalOffset(roomTabState.RoomPreviewScrollPosition.Left); + roomPreviewViewer.ScrollToVerticalOffset(roomTabState.RoomPreviewScrollPosition.Top); + + // (Sadly, arrays don't support destructuring like tuples) + roomEditor.BGItems.IsExpanded = roomTabState.ObjectTreeItemsStates[0]; + roomEditor.ViewItems.IsExpanded = roomTabState.ObjectTreeItemsStates[1]; + roomEditor.GameObjItems.IsExpanded = roomTabState.ObjectTreeItemsStates[2]; + roomEditor.TileItems.IsExpanded = roomTabState.ObjectTreeItemsStates[3]; + roomEditor.LayerItems.IsExpanded = roomTabState.ObjectTreeItemsStates[4]; + roomEditor.RoomRootItem.UpdateLayout(); + // Select the object + if (roomTabState.SelectedObject is not UndertaleRoom) + { + TreeViewItem objList = null; + Layer layer = null; + switch (roomTabState.SelectedObject) + { + case Background: + objList = roomEditor.BGItems; + break; + + case View: + objList = roomEditor.ViewItems; + break; + + case GameObject gameObj: + var room = roomEditor.DataContext as UndertaleRoom; + if (room.Flags.HasFlag(RoomEntryFlags.IsGMS2)) + { + layer = room.Layers + .FirstOrDefault(l => l.LayerType is LayerType.Instances + && (l.InstancesData.Instances?.Any(x => x.InstanceID == gameObj.InstanceID) ?? false)); + objList = roomEditor.LayerItems.ItemContainerGenerator.ContainerFromItem(layer) as TreeViewItem; + } + else + objList = roomEditor.GameObjItems; + break; + + case Tile tile: + room = roomEditor.DataContext as UndertaleRoom; + if (room.Flags.HasFlag(RoomEntryFlags.IsGMS2)) + { + layer = room.Layers + .FirstOrDefault(l => l.LayerType is LayerType.Assets + && (l.AssetsData.LegacyTiles?.Any(x => x.InstanceID == tile.InstanceID) ?? false)); + objList = roomEditor.LayerItems.ItemContainerGenerator.ContainerFromItem(layer) as TreeViewItem; + } + else + objList = roomEditor.TileItems; + break; + + case Layer: + objList = roomEditor.LayerItems; + break; + + case SpriteInstance spr: + room = roomEditor.DataContext as UndertaleRoom; + layer = room.Layers + .FirstOrDefault(l => l.LayerType is LayerType.Assets + && (l.AssetsData.Sprites?.Any(x => x.Name == spr.Name) ?? false)); + objList = roomEditor.LayerItems.ItemContainerGenerator.ContainerFromItem(layer) as TreeViewItem; + break; + } + if (objList is null) + return; + + objList.IsExpanded = true; + objList.BringIntoView(); + objList.UpdateLayout(); + + TreeViewItem objItem = objList?.ItemContainerGenerator.ContainerFromItem(roomTabState.SelectedObject) as TreeViewItem; + if (objItem is null) + return; + objItem.IsSelected = true; + objItem.Focus(); + + roomEditor.RoomRootItem.UpdateLayout(); + } + + ScrollViewer treeObjViewer = MainWindow.FindVisualChild(roomEditor.RoomObjectsTree); + treeObjViewer.ScrollToHorizontalOffset(roomTabState.ObjectsTreeScrollPosition.Left); + treeObjViewer.ScrollToVerticalOffset(roomTabState.ObjectsTreeScrollPosition.Top); + treeObjViewer.UpdateLayout(); break; default: @@ -476,6 +590,22 @@ public class RoomTabState : TabContentState { /// The scroll position of the room editor preview. public (double Left, double Top) RoomPreviewScrollPosition; + + /// The scale of the room editor preview. + public Transform RoomPreviewTransform; + + /// The scroll position of the room objects tree. + public (double Left, double Top) ObjectsTreeScrollPosition; + + /// The states of the room objects tree items. + /// + /// An order of the states is following: + /// Backgrounds, views, game objects, tiles, layers. + /// + public bool[] ObjectTreeItemsStates; + + /// The selected room object. + public object SelectedObject; } From 7ef74f7216079e60d1d6ecfb8f3b3de270c99db7 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Sat, 14 Jan 2023 00:20:25 +0300 Subject: [PATCH 07/18] Implemented font editor tab content saving and restoring. --- .../Editors/UndertaleFontEditor.xaml | 3 +- UndertaleModTool/Tab.cs | 65 ++++++++++++++++--- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/UndertaleModTool/Editors/UndertaleFontEditor.xaml b/UndertaleModTool/Editors/UndertaleFontEditor.xaml index b1dfd0cc8..e90a55ed6 100644 --- a/UndertaleModTool/Editors/UndertaleFontEditor.xaml +++ b/UndertaleModTool/Editors/UndertaleFontEditor.xaml @@ -93,7 +93,8 @@ Glyphs: - diff --git a/UndertaleModTool/Tab.cs b/UndertaleModTool/Tab.cs index 7df9fc96f..7bca023f8 100644 --- a/UndertaleModTool/Tab.cs +++ b/UndertaleModTool/Tab.cs @@ -365,6 +365,33 @@ public void SaveTabContentState(ContentControl dataEditor) }; break; + case UndertaleFontEditor fontEditor: + UndertaleFont.Glyph glyph = null; + if (fontEditor.GlyphsGrid.SelectedItem is not null) + { + glyph = fontEditor.GlyphsGrid.SelectedItem as UndertaleFont.Glyph; + if (glyph is null) + { + Debug.WriteLine("Can't save the selected glyph of the font editor - \"SelectedItem\" is not a glyph."); + return; + } + } + + ScrollViewer glyphsViewer = MainWindow.FindVisualChild(fontEditor.GlyphsGrid); + if (glyphsViewer is null) + { + Debug.WriteLine("Can't save the scroll position of the font editor glyphs - \"ScrollViewer\" is not found."); + return; + } + + LastContentState = new FontTabState() + { + MainScrollPosition = mainScrollPos, + SelectedGlyph = glyph, + GlyphsScrollPosition = glyphsViewer.VerticalOffset + }; + break; + default: LastContentState = new() { @@ -399,17 +426,13 @@ public void RestoreTabContentState(ContentControl dataEditor) return; } - if (LastContentState.MainScrollPosition != 0) + ScrollViewer mainScrollViewer = MainWindow.GetNearestParent(dataEditor); + if (mainScrollViewer is null) { - ScrollViewer mainScrollViewer = MainWindow.GetNearestParent(dataEditor); - if (mainScrollViewer is null) - { - mainWindow.ShowWarning("The last tab content state can't be restored - \"ScrollViewer\" is not found."); - return; - } - - mainScrollViewer.ScrollToVerticalOffset(LastContentState.MainScrollPosition); + mainWindow.ShowWarning("The last tab content state can't be restored - \"ScrollViewer\" is not found."); + return; } + mainScrollViewer.ScrollToVerticalOffset(LastContentState.MainScrollPosition); // if "LastContentState" is an instance of "TabContentState" (e.g. not "CodeTabState") if (!LastContentState.GetType().IsSubclassOf(typeof(TabContentState))) @@ -521,6 +544,20 @@ public void RestoreTabContentState(ContentControl dataEditor) treeObjViewer.UpdateLayout(); break; + case FontTabState fontTabState: + var fontEditor = editor as UndertaleFontEditor; + + ScrollViewer glyphsViewer = MainWindow.FindVisualChild(fontEditor.GlyphsGrid); + if (glyphsViewer is null) + { + Debug.WriteLine("Can't restore the scroll position of the font editor glyphs - \"ScrollViewer\" is not found."); + return; + } + glyphsViewer.ScrollToVerticalOffset(fontTabState.GlyphsScrollPosition); + + fontEditor.GlyphsGrid.SelectedItem = fontTabState.SelectedGlyph; + break; + default: Debug.WriteLine($"The content state of a tab \"{this}\" is unknown?"); break; @@ -608,6 +645,16 @@ public class RoomTabState : TabContentState public object SelectedObject; } + /// Stores the information about the tab with a font. + public class FontTabState : TabContentState + { + /// The selected font glyph. + public UndertaleFont.Glyph SelectedGlyph; + + /// The scroll position of the glyphs grid. + public double GlyphsScrollPosition; + } + /// A converter that generates the tab title from the tab reference. public class TabTitleConverter : IMultiValueConverter From 943ab7c5dd5f93330338717eca0000679d799973 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Sat, 14 Jan 2023 15:43:01 +0300 Subject: [PATCH 08/18] Implemented general info editor tab content saving and restoring. --- .../Editors/UndertaleGeneralInfoEditor.xaml | 5 +- UndertaleModTool/Tab.cs | 58 +++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/UndertaleModTool/Editors/UndertaleGeneralInfoEditor.xaml b/UndertaleModTool/Editors/UndertaleGeneralInfoEditor.xaml index 9430f6cf3..ed9035323 100644 --- a/UndertaleModTool/Editors/UndertaleGeneralInfoEditor.xaml +++ b/UndertaleModTool/Editors/UndertaleGeneralInfoEditor.xaml @@ -143,8 +143,9 @@ Room order - - + diff --git a/UndertaleModTool/Tab.cs b/UndertaleModTool/Tab.cs index 7bca023f8..256b83c1c 100644 --- a/UndertaleModTool/Tab.cs +++ b/UndertaleModTool/Tab.cs @@ -392,6 +392,32 @@ public void SaveTabContentState(ContentControl dataEditor) }; break; + case UndertaleGeneralInfoEditor genInfoEditor: + object room = null; + double roomListPos = 0; + if (genInfoEditor.RoomListExpander.IsExpanded) + { + room = genInfoEditor.RoomListGrid.SelectedItem; + + ScrollViewer roomListViewer = MainWindow.FindVisualChild(genInfoEditor.RoomListGrid); + if (roomListViewer is null) + { + Debug.WriteLine("Can't save the scroll position of the global info editor room list - \"ScrollViewer\" is not found."); + return; + } + + roomListPos = roomListViewer.VerticalOffset; + } + + LastContentState = new GeneralInfoTabState() + { + MainScrollPosition = mainScrollPos, + SelectedRoom = room, + RoomListScrollPosition = roomListPos, + IsRoomListExpanded = genInfoEditor.RoomListExpander.IsExpanded + }; + break; + default: LastContentState = new() { @@ -558,6 +584,25 @@ public void RestoreTabContentState(ContentControl dataEditor) fontEditor.GlyphsGrid.SelectedItem = fontTabState.SelectedGlyph; break; + case GeneralInfoTabState genInfoTabState: + var genInfoEditor = editor as UndertaleGeneralInfoEditor; + + genInfoEditor.RoomListExpander.IsExpanded = genInfoTabState.IsRoomListExpanded; + genInfoEditor.UpdateLayout(); + if (genInfoTabState.IsRoomListExpanded) + { + ScrollViewer roomListViewer = MainWindow.FindVisualChild(genInfoEditor.RoomListGrid); + if (roomListViewer is null) + { + Debug.WriteLine("Can't restore the scroll position of the general info editor room list - \"ScrollViewer\" is not found."); + return; + } + roomListViewer.ScrollToVerticalOffset(genInfoTabState.RoomListScrollPosition); + + genInfoEditor.RoomListGrid.SelectedItem = genInfoTabState.SelectedRoom; + } + break; + default: Debug.WriteLine($"The content state of a tab \"{this}\" is unknown?"); break; @@ -655,6 +700,19 @@ public class FontTabState : TabContentState public double GlyphsScrollPosition; } + /// Stores the information about the tab with a general info. + public class GeneralInfoTabState : TabContentState + { + /// The selected room. + public object SelectedRoom; + + /// The scroll position of the room list grid. + public double RoomListScrollPosition; + + /// Whether the room list is expanded. + public bool IsRoomListExpanded; + } + /// A converter that generates the tab title from the tab reference. public class TabTitleConverter : IMultiValueConverter From 86bfc735f604f65a7664c9cb37ca01743c2e24c6 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Sat, 14 Jan 2023 23:31:18 +0300 Subject: [PATCH 09/18] Implemented global init editor tab content saving and restoring. --- .../Editors/UndertaleGlobalInitEditor.xaml | 3 +- UndertaleModTool/Tab.cs | 42 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/UndertaleModTool/Editors/UndertaleGlobalInitEditor.xaml b/UndertaleModTool/Editors/UndertaleGlobalInitEditor.xaml index c99fa96ec..0fc9705d7 100644 --- a/UndertaleModTool/Editors/UndertaleGlobalInitEditor.xaml +++ b/UndertaleModTool/Editors/UndertaleGlobalInitEditor.xaml @@ -11,7 +11,8 @@ - diff --git a/UndertaleModTool/Tab.cs b/UndertaleModTool/Tab.cs index 256b83c1c..e838ba5ae 100644 --- a/UndertaleModTool/Tab.cs +++ b/UndertaleModTool/Tab.cs @@ -418,6 +418,24 @@ public void SaveTabContentState(ContentControl dataEditor) }; break; + case UndertaleGlobalInitEditor globalInitEditor: + object script = globalInitEditor.ScriptsGrid.SelectedItem; + + ScrollViewer scriptsViewer = MainWindow.FindVisualChild(globalInitEditor.ScriptsGrid); + if (scriptsViewer is null) + { + Debug.WriteLine("Can't save the scroll position of the font editor glyphs - \"ScrollViewer\" is not found."); + return; + } + + LastContentState = new GlobalInitTabState() + { + MainScrollPosition = mainScrollPos, + SelectedScript = script, + ScriptListScrollPosition = scriptsViewer.VerticalOffset + }; + break; + default: LastContentState = new() { @@ -603,6 +621,20 @@ public void RestoreTabContentState(ContentControl dataEditor) } break; + case GlobalInitTabState globalInitTabState: + var globalInitEditor = editor as UndertaleGlobalInitEditor; + + ScrollViewer scriptsViewer = MainWindow.FindVisualChild(globalInitEditor.ScriptsGrid); + if (scriptsViewer is null) + { + Debug.WriteLine("Can't restore the scroll position of the global init editor scripts - \"ScrollViewer\" is not found."); + return; + } + scriptsViewer.ScrollToVerticalOffset(globalInitTabState.ScriptListScrollPosition); + + globalInitEditor.ScriptsGrid.SelectedItem = globalInitTabState.SelectedScript; + break; + default: Debug.WriteLine($"The content state of a tab \"{this}\" is unknown?"); break; @@ -713,6 +745,16 @@ public class GeneralInfoTabState : TabContentState public bool IsRoomListExpanded; } + /// Stores the information about the tab with global init scripts. + public class GlobalInitTabState : TabContentState + { + /// The selected script. + public object SelectedScript; + + /// The scroll position of the script list grid. + public double ScriptListScrollPosition; + } + /// A converter that generates the tab title from the tab reference. public class TabTitleConverter : IMultiValueConverter From 24201642d4fa7b7c375024568ac26000615d9a5e Mon Sep 17 00:00:00 2001 From: VladiStep Date: Sun, 15 Jan 2023 00:11:11 +0300 Subject: [PATCH 10/18] Fixed #1154 --- UndertaleModTool/MainWindow.xaml | 2 +- UndertaleModTool/MainWindow.xaml.cs | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/UndertaleModTool/MainWindow.xaml b/UndertaleModTool/MainWindow.xaml index db352c813..90377c746 100644 --- a/UndertaleModTool/MainWindow.xaml +++ b/UndertaleModTool/MainWindow.xaml @@ -531,7 +531,7 @@ - + diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index 423969fdd..3980f0a5d 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -3552,6 +3552,16 @@ private void TabTitleText_Initialized(object sender, EventArgs e) { Tab.SetTabTitleBinding(null, null, sender as TextBlock); } + + private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e) + { + ScrollViewer viewer = sender as ScrollViewer; + + // Prevent receiving the mouse wheel event if there is nowhere to scroll. + if (viewer.ComputedVerticalScrollBarVisibility != Visibility.Visible + && e.Source == viewer) + e.Handled = true; + } } public class GeneralInfoEditor From a3a6a3b565737913a798fc051f718d7aeef0150d Mon Sep 17 00:00:00 2001 From: VladiStep Date: Mon, 16 Jan 2023 00:06:25 +0300 Subject: [PATCH 11/18] Implemented sprite editor tab content saving and restoring. --- UndertaleModTool/Tab.cs | 69 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/UndertaleModTool/Tab.cs b/UndertaleModTool/Tab.cs index e838ba5ae..bdebffb20 100644 --- a/UndertaleModTool/Tab.cs +++ b/UndertaleModTool/Tab.cs @@ -436,6 +436,35 @@ public void SaveTabContentState(ContentControl dataEditor) }; break; + case UndertaleSpriteEditor spriteEditor: + object texture = spriteEditor.TextureList.SelectedItem; + + ScrollViewer texturesViewer = MainWindow.FindVisualChild(spriteEditor.TextureList); + if (texturesViewer is null) + { + Debug.WriteLine("Can't save the scroll position of the sprite editor textures - \"ScrollViewer\" is not found."); + return; + } + + object mask = spriteEditor.MaskList.SelectedItem; + + ScrollViewer masksViewer = MainWindow.FindVisualChild(spriteEditor.MaskList); + if (masksViewer is null) + { + Debug.WriteLine("Can't save the scroll position of the sprite editor masks - \"ScrollViewer\" is not found."); + return; + } + + LastContentState = new SpriteTabState() + { + MainScrollPosition = mainScrollPos, + SelectedTexture = texture, + TextureListScrollPosition = texturesViewer.VerticalOffset, + SelectedMask = mask, + MaskListScrollPosition = masksViewer.VerticalOffset + }; + break; + default: LastContentState = new() { @@ -635,6 +664,30 @@ public void RestoreTabContentState(ContentControl dataEditor) globalInitEditor.ScriptsGrid.SelectedItem = globalInitTabState.SelectedScript; break; + case SpriteTabState spriteTabState: + var spriteEditor = editor as UndertaleSpriteEditor; + + ScrollViewer texturesViewer = MainWindow.FindVisualChild(spriteEditor.TextureList); + if (texturesViewer is null) + { + Debug.WriteLine("Can't restore the scroll position of the sprite editor textures - \"ScrollViewer\" is not found."); + return; + } + texturesViewer.ScrollToVerticalOffset(spriteTabState.TextureListScrollPosition); + + spriteEditor.TextureList.SelectedItem = spriteTabState.SelectedTexture; + + ScrollViewer masksViewer = MainWindow.FindVisualChild(spriteEditor.MaskList); + if (masksViewer is null) + { + Debug.WriteLine("Can't restore the scroll position of the sprite editor masks - \"ScrollViewer\" is not found."); + return; + } + masksViewer.ScrollToVerticalOffset(spriteTabState.MaskListScrollPosition); + + spriteEditor.MaskList.SelectedItem = spriteTabState.SelectedMask; + break; + default: Debug.WriteLine($"The content state of a tab \"{this}\" is unknown?"); break; @@ -755,6 +808,22 @@ public class GlobalInitTabState : TabContentState public double ScriptListScrollPosition; } + /// Stores the information about the tab with a sprite. + public class SpriteTabState : TabContentState + { + /// The selected sprite texture. + public object SelectedTexture; + + /// The scroll position of the textures grid. + public double TextureListScrollPosition; + + /// The selected sprite mask. + public object SelectedMask; + + /// The scroll position of the masks grid. + public double MaskListScrollPosition; + } + /// A converter that generates the tab title from the tab reference. public class TabTitleConverter : IMultiValueConverter From 3d6745edb0e7b0b97435002b1083c8eba5171e38 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Mon, 16 Jan 2023 17:58:23 +0300 Subject: [PATCH 12/18] Implemented background (GMS 2 tile set) editor tab content saving and restoring. --- UndertaleModTool/Tab.cs | 55 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/UndertaleModTool/Tab.cs b/UndertaleModTool/Tab.cs index bdebffb20..b18cd0eb1 100644 --- a/UndertaleModTool/Tab.cs +++ b/UndertaleModTool/Tab.cs @@ -465,6 +465,33 @@ public void SaveTabContentState(ContentControl dataEditor) }; break; + case UndertaleBackgroundEditor backgroundEditor: + object tile = null; + double scrollPos = -1; + + if (mainWindow.IsGMS2 == Visibility.Visible) + { + tile = backgroundEditor.TileIdList.SelectedItem; + + ScrollViewer tilesViewer = MainWindow.FindVisualChild(backgroundEditor.TileIdList); + if (tilesViewer is null) + { + Debug.WriteLine("Can't save the scroll position of the tile set editor tiles - \"ScrollViewer\" is not found."); + return; + } + + scrollPos = tilesViewer.VerticalOffset; + } + + LastContentState = new TileSetTabState() + { + MainScrollPosition = mainScrollPos, + SelectedTile = tile, + TileListScrollPosition = scrollPos + }; + break; + + default: LastContentState = new() { @@ -688,6 +715,25 @@ public void RestoreTabContentState(ContentControl dataEditor) spriteEditor.MaskList.SelectedItem = spriteTabState.SelectedMask; break; + case TileSetTabState tileSetTabState: + var backgroundEditor = editor as UndertaleBackgroundEditor; + + if (tileSetTabState.TileListScrollPosition != -1) + { + ScrollViewer tilesViewer = MainWindow.FindVisualChild(backgroundEditor.TileIdList); + if (tilesViewer is null) + { + Debug.WriteLine("Can't restore the scroll position of the tile set editor tiles - \"ScrollViewer\" is not found."); + return; + } + tilesViewer.ScrollToVerticalOffset(tileSetTabState.TileListScrollPosition); + + backgroundEditor.TileIdList.SelectedItem = tileSetTabState.SelectedTile; + } + + break; + + default: Debug.WriteLine($"The content state of a tab \"{this}\" is unknown?"); break; @@ -824,6 +870,15 @@ public class SpriteTabState : TabContentState public double MaskListScrollPosition; } + /// Stores the information about the tab with a tile set (or a background). + public class TileSetTabState : TabContentState + { + /// The selected tile. + public object SelectedTile; + + /// The scroll position of the tile list grid. + public double TileListScrollPosition; + } /// A converter that generates the tab title from the tab reference. public class TabTitleConverter : IMultiValueConverter From 3aeeae8e4ec8618b08546e5be70c8cb5eade5b9a Mon Sep 17 00:00:00 2001 From: VladiStep Date: Mon, 16 Jan 2023 18:29:14 +0300 Subject: [PATCH 13/18] Fixed #1155, simplified the tab state methods. --- UndertaleModTool/MainWindow.xaml.cs | 13 ++++++++----- UndertaleModTool/Tab.cs | 9 +++++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index 3980f0a5d..bf17220b1 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -3212,6 +3212,9 @@ private void OpenInTab(object obj, bool isNewTab = false, string tabTitle = null CurrentTab.History.Add(obj); CurrentTab.HistoryPosition++; } + + if (DataEditor.IsLoaded) + GetNearestParent(DataEditor)?.ScrollToTop(); } public void CloseTab(bool addDefaultTab = true) // close the current tab @@ -3237,7 +3240,7 @@ public void CloseTab(int tabIndex, bool addDefaultTab = true) if (Tabs.Count == 0) { if (!closingTab.AutoClose) - CurrentTab.SaveTabContentState(DataEditor); + CurrentTab.SaveTabContentState(); CurrentTabIndex = -1; CurrentTab = null; @@ -3275,7 +3278,7 @@ public void CloseTab(int tabIndex, bool addDefaultTab = true) currIndex -= 1; tabIsChanged = true; - CurrentTab.SaveTabContentState(DataEditor); + CurrentTab.SaveTabContentState(); } } else if (currIndex > tabIndex) @@ -3297,7 +3300,7 @@ public void CloseTab(int tabIndex, bool addDefaultTab = true) CurrentTab = newTab; if (tabIsChanged) - CurrentTab.RestoreTabContentState(DataEditor); + CurrentTab.RestoreTabContentState(); } } } @@ -3327,7 +3330,7 @@ private void TabController_SelectionChanged(object sender, SelectionChangedEvent { if (TabController.SelectedIndex >= 0) { - CurrentTab?.SaveTabContentState(DataEditor); + CurrentTab?.SaveTabContentState(); ScrollToTab(CurrentTabIndex); @@ -3340,7 +3343,7 @@ private void TabController_SelectionChanged(object sender, SelectionChangedEvent UpdateObjectLabel(CurrentTab.CurrentObject); - CurrentTab.RestoreTabContentState(DataEditor); + CurrentTab.RestoreTabContentState(); } } diff --git a/UndertaleModTool/Tab.cs b/UndertaleModTool/Tab.cs index b18cd0eb1..30ed9b4b2 100644 --- a/UndertaleModTool/Tab.cs +++ b/UndertaleModTool/Tab.cs @@ -255,9 +255,9 @@ public static void SetTabTitleBinding(object obj, object prevObj, TextBlock text } /// Saves the current tab content state. - /// A reference to the object editor of main window. - public void SaveTabContentState(ContentControl dataEditor) + public void SaveTabContentState() { + ContentControl dataEditor = mainWindow.DataEditor; if (dataEditor is null || dataEditor.Content is null || dataEditor.Content is DescriptionView) @@ -502,9 +502,10 @@ public void SaveTabContentState(ContentControl dataEditor) } /// Restores the last tab content state. - /// A reference to the object editor of main window. - public void RestoreTabContentState(ContentControl dataEditor) + public void RestoreTabContentState() { + ContentControl dataEditor = mainWindow.DataEditor; + if (dataEditor is null || dataEditor.Content is null || dataEditor.Content is DescriptionView From 4e6fb1327c1bd2fdb87f12ebc0b210bf22155141 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Tue, 17 Jan 2023 00:38:52 +0300 Subject: [PATCH 14/18] Implemented texture group info editor tab content saving and restoring. --- .../UndertaleTextureGroupInfoEditor.xaml | 25 +++--- UndertaleModTool/Tab.cs | 80 +++++++++++++++++++ 2 files changed, 95 insertions(+), 10 deletions(-) diff --git a/UndertaleModTool/Editors/UndertaleTextureGroupInfoEditor.xaml b/UndertaleModTool/Editors/UndertaleTextureGroupInfoEditor.xaml index 19a83c06a..b11daeab1 100644 --- a/UndertaleModTool/Editors/UndertaleTextureGroupInfoEditor.xaml +++ b/UndertaleModTool/Editors/UndertaleTextureGroupInfoEditor.xaml @@ -57,8 +57,9 @@ Texture Pages - - + @@ -101,8 +102,9 @@ Sprites - - + @@ -145,8 +147,9 @@ Spine Sprites - - + @@ -189,8 +192,9 @@ Fonts - - + @@ -233,8 +237,9 @@ Tilesets - - + diff --git a/UndertaleModTool/Tab.cs b/UndertaleModTool/Tab.cs index 30ed9b4b2..8927ef512 100644 --- a/UndertaleModTool/Tab.cs +++ b/UndertaleModTool/Tab.cs @@ -491,6 +491,41 @@ public void SaveTabContentState() }; break; + case UndertaleTextureGroupInfoEditor textureGroupEditor: + var lists = new (Expander, DataGrid)[5] + { + (textureGroupEditor.TextureListExpander, textureGroupEditor.TextureListGrid), + (textureGroupEditor.SpriteListExpander, textureGroupEditor.SpriteListGrid), + (textureGroupEditor.SpineSprListExpander, textureGroupEditor.SpineSprListGrid), + (textureGroupEditor.FontListExpander, textureGroupEditor.FontListGrid), + (textureGroupEditor.TilesetListExpander, textureGroupEditor.TilesetListGrid) + }; + var stateList = new (bool IsExpanded, double ScrollPos, object SelectedItem)[5]; + for (int i = 0; i < stateList.Length; i++) + { + var list = lists[i]; + if (list.Item1.IsExpanded) + { + stateList[i].IsExpanded = list.Item1.IsExpanded; + stateList[i].SelectedItem = list.Item2.SelectedItem; + + ScrollViewer viewer = MainWindow.FindVisualChild(list.Item2); + if (viewer is null) + { + Debug.WriteLine("Can't save the scroll positions of the texture group info editor room lists - \"ScrollViewer\" is not found."); + return; + } + + stateList[i].ScrollPos = viewer.VerticalOffset; + } + } + + LastContentState = new TextureGroupTabState() + { + MainScrollPosition = mainScrollPos, + GroupListsStates = stateList + }; + break; default: LastContentState = new() @@ -734,6 +769,39 @@ public void RestoreTabContentState() break; + case TextureGroupTabState textureGroupTabState: + var textureGroupEditor = editor as UndertaleTextureGroupInfoEditor; + + var lists = new (Expander, DataGrid)[5] + { + (textureGroupEditor.TextureListExpander, textureGroupEditor.TextureListGrid), + (textureGroupEditor.SpriteListExpander, textureGroupEditor.SpriteListGrid), + (textureGroupEditor.SpineSprListExpander, textureGroupEditor.SpineSprListGrid), + (textureGroupEditor.FontListExpander, textureGroupEditor.FontListGrid), + (textureGroupEditor.TilesetListExpander, textureGroupEditor.TilesetListGrid) + }; + + for (int i = 0; i < lists.Length; i++) + { + var list = lists[i]; + var (isExpanded, scrollPos, selectedItem) = textureGroupTabState.GroupListsStates[i]; + + list.Item1.IsExpanded = isExpanded; + list.Item1.UpdateLayout(); + if (isExpanded) + { + ScrollViewer viewer = MainWindow.FindVisualChild(list.Item2); + if (viewer is null) + { + Debug.WriteLine("Can't restore the scroll position of the tile set editor tiles - \"ScrollViewer\" is not found."); + return; + } + viewer.ScrollToVerticalOffset(scrollPos); + + list.Item2.SelectedItem = selectedItem; + } + } + break; default: Debug.WriteLine($"The content state of a tab \"{this}\" is unknown?"); @@ -881,6 +949,18 @@ public class TileSetTabState : TabContentState public double TileListScrollPosition; } + /// Stores the information about the tab with a texture group. + public class TextureGroupTabState : TabContentState + { + /// The states of the texture group lists. + /// + /// An order of the states is following: + /// Texture pages, sprites, spine sprites, fonts, tilesets. + /// + public (bool IsExpanded, double ScrollPos, object SelectedItem)[] GroupListsStates; + } + + /// A converter that generates the tab title from the tab reference. public class TabTitleConverter : IMultiValueConverter { From 2287d0a21b568966672206afc8c220dd4d8c3e96 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Tue, 17 Jan 2023 19:56:35 +0300 Subject: [PATCH 15/18] Closed #1156, fixed #1157 --- .../Editors/UndertaleCodeEditor.xaml | 2 +- .../Editors/UndertaleCodeEditor.xaml.cs | 59 +++++++++++++------ UndertaleModTool/MainWindow.xaml.cs | 26 +++++++- 3 files changed, 66 insertions(+), 21 deletions(-) diff --git a/UndertaleModTool/Editors/UndertaleCodeEditor.xaml b/UndertaleModTool/Editors/UndertaleCodeEditor.xaml index 5b876aa9c..dfa154432 100644 --- a/UndertaleModTool/Editors/UndertaleCodeEditor.xaml +++ b/UndertaleModTool/Editors/UndertaleCodeEditor.xaml @@ -7,7 +7,7 @@ xmlns:undertale="clr-namespace:UndertaleModLib.Models;assembly=UndertaleModLib" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance undertale:UndertaleCode}" - DataContextChanged="UserControl_DataContextChanged" Unloaded="DataUserControl_Unloaded"> + DataContextChanged="UserControl_DataContextChanged" Loaded="UndertaleCodeEditor_Loaded" Unloaded="UndertaleCodeEditor_Unloaded"> diff --git a/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs b/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs index a95d41aff..f574d343f 100644 --- a/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs +++ b/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs @@ -178,7 +178,7 @@ public UndertaleCodeEditor() DisassemblyEditor.TextArea.SelectionCornerRadius = 0; } - private void DataUserControl_Unloaded(object sender, RoutedEventArgs e) + private void UndertaleCodeEditor_Unloaded(object sender, RoutedEventArgs e) { OverriddenDecompPos = default; OverriddenDisasmPos = default; @@ -205,6 +205,35 @@ private void SearchPanel_LostFocus(object sender, RoutedEventArgs e) noMatchesTT.IsOpen = false; } + private void UndertaleCodeEditor_Loaded(object sender, RoutedEventArgs e) + { + FillInCodeViewer(); + } + private void FillInCodeViewer(bool overrideFirst = false) + { + UndertaleCode code = DataContext as UndertaleCode; + if (DisassemblyTab.IsSelected && code != CurrentDisassembled) + { + if (!overrideFirst) + { + DisassembleCode(code, !DisassembledYet); + DisassembledYet = true; + } + else + DisassembleCode(code, true); + } + if (DecompiledTab.IsSelected && code != CurrentDecompiled) + { + if (!overrideFirst) + { + _ = DecompileCode(code, !DecompiledYet); + DecompiledYet = true; + } + else + _ = DecompileCode(code, true); + } + } + private async void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e) { UndertaleCode code = this.DataContext as UndertaleCode; @@ -212,20 +241,21 @@ private async void TabControl_SelectionChanged(object sender, SelectionChangedEv Directory.CreateDirectory(TempPath); if (code == null) return; + DecompiledSearchPanel.Close(); DisassemblySearchPanel.Close(); + await DecompiledLostFocusBody(sender, null); DisassemblyEditor_LostFocus(sender, null); - if (DisassemblyTab.IsSelected && code != CurrentDisassembled) - { - DisassembleCode(code, !DisassembledYet); - DisassembledYet = true; - } - if (DecompiledTab.IsSelected && code != CurrentDecompiled) + + if (!IsLoaded) { - _ = DecompileCode(code, !DecompiledYet); - DecompiledYet = true; + // If it's not loaded, then "FillInCodeViewer()" will be executed on load. + // This prevents a bug with freezing on code opening. + return; } + + FillInCodeViewer(); } private async void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) @@ -277,16 +307,7 @@ private async void UserControl_DataContextChanged(object sender, DependencyPrope MainWindow.CodeEditorDecompile = Unstated; } else - { - if (DisassemblyTab.IsSelected && code != CurrentDisassembled) - { - DisassembleCode(code, true); - } - if (DecompiledTab.IsSelected && code != CurrentDecompiled) - { - _ = DecompileCode(code, true); - } - } + FillInCodeViewer(true); } public static readonly RoutedEvent CtrlKEvent = EventManager.RegisterRoutedEvent( diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index bf17220b1..cef1ceda8 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -2147,7 +2147,31 @@ public void OpenCodeFile(string name, CodeEditorMode editorDecompile) { Focus(); - CodeEditorDecompile = editorDecompile; + if (Selected == code) + { + #pragma warning disable CA1416 + var codeEditor = FindVisualChild(DataEditor); + if (codeEditor is null) + { + Debug.WriteLine("Cannot select the code editor mode tab - its instance is not found."); + } + else + { + if (editorDecompile == CodeEditorMode.Decompile + && !codeEditor.DecompiledTab.IsSelected) + { + codeEditor.CodeModeTabs.SelectedItem = codeEditor.DecompiledTab; + } + else if (editorDecompile == CodeEditorMode.DontDecompile + && !codeEditor.DisassemblyTab.IsSelected) + { + codeEditor.CodeModeTabs.SelectedItem = codeEditor.DisassemblyTab; + } + } + #pragma warning restore CA1416 + } + else + CodeEditorDecompile = editorDecompile; HighlightObject(code); ChangeSelection(code); From a940c6f74f1f3a93731de81df4a436fc06469000 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Fri, 3 Mar 2023 12:50:18 +0300 Subject: [PATCH 16/18] Fixed #1129 --- UndertaleModTool/MainWindow.xaml.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index cef1ceda8..9c464d820 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -3322,6 +3322,7 @@ public void CloseTab(int tabIndex, bool addDefaultTab = true) } CurrentTab = newTab; + UpdateObjectLabel(CurrentTab.CurrentObject); if (tabIsChanged) CurrentTab.RestoreTabContentState(); From f0eee24fc07e0050bd867c04fb265df1537f9bc2 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Thu, 9 Mar 2023 18:35:08 +0300 Subject: [PATCH 17/18] Fixed bug with room editor tab not restoring the opened object. --- UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs b/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs index 510aa857d..102ba7355 100644 --- a/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs +++ b/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs @@ -129,7 +129,8 @@ private void ExportAsPNG_Click(object sender, RoutedEventArgs e) private void UndertaleRoomEditor_Loaded(object sender, RoutedEventArgs e) { - RoomRootItem.IsSelected = true; + if (ObjectEditor.Content is null) + RoomRootItem.IsSelected = true; } private void UndertaleRoomEditor_Unloaded(object sender, RoutedEventArgs e) { From b3f31d6704404e9466121a8bf474ddacecc683ae Mon Sep 17 00:00:00 2001 From: VladiStep Date: Wed, 22 Mar 2023 21:37:47 +0300 Subject: [PATCH 18/18] The code scroll position saves separately. --- .../Editors/UndertaleCodeEditor.xaml.cs | 25 ++++++++++++------- UndertaleModTool/Tab.cs | 25 +++++++++++++------ 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs b/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs index fdf49930a..4ff247674 100644 --- a/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs +++ b/UndertaleModTool/Editors/UndertaleCodeEditor.xaml.cs @@ -57,14 +57,14 @@ public partial class UndertaleCodeEditor : DataUserControl public bool DecompiledYet = false; public bool DecompiledSkipped = false; public SearchPanel DecompiledSearchPanel; - public static (int Line, int Column) OverriddenDecompPos; + public static (int Line, int Column, double ScrollPos) OverriddenDecompPos; public bool DisassemblyFocused = false; public bool DisassemblyChanged = false; public bool DisassembledYet = false; public bool DisassemblySkipped = false; public SearchPanel DisassemblySearchPanel; - public static (int Line, int Column) OverriddenDisasmPos; + public static (int Line, int Column, double ScrollPos) OverriddenDisasmPos; public static RoutedUICommand Compile = new RoutedUICommand("Compile code", "Compile", typeof(UndertaleCodeEditor)); @@ -354,14 +354,14 @@ public void RestoreState(CodeTabState tabState) CodeModeTabs.SelectedItem = DisassemblyTab; TextEditor textEditor = DecompiledEditor; - (int linePos, int columnPos) = tabState.DecompiledCodePosition; - RestoreCaretPosition(textEditor, linePos, columnPos); + (int linePos, int columnPos, double scrollPos) = tabState.DecompiledCodePosition; + RestoreCaretPosition(textEditor, linePos, columnPos, scrollPos); textEditor = DisassemblyEditor; - (linePos, columnPos) = tabState.DisassemblyCodePosition; - RestoreCaretPosition(textEditor, linePos, columnPos); + (linePos, columnPos, scrollPos) = tabState.DisassemblyCodePosition; + RestoreCaretPosition(textEditor, linePos, columnPos, scrollPos); } - private static void RestoreCaretPosition(TextEditor textEditor, int linePos, int columnPos) + private static void RestoreCaretPosition(TextEditor textEditor, int linePos, int columnPos, double scrollPos) { if (linePos <= textEditor.LineCount) { @@ -373,6 +373,7 @@ private static void RestoreCaretPosition(TextEditor textEditor, int linePos, int textEditor.TextArea.Caret.Column = lineLen + 1; textEditor.ScrollToLine(linePos); + textEditor.ScrollToVerticalOffset(scrollPos); } else { @@ -450,16 +451,19 @@ private void DisassembleCode(UndertaleCode code, bool first) int currLine = 1; int currColumn = 1; + double scrollPos = 0; if (!first) { var caret = DisassemblyEditor.TextArea.Caret; currLine = caret.Line; currColumn = caret.Column; + scrollPos = DisassemblyEditor.VerticalOffset; } else if (OverriddenDisasmPos != default) { currLine = OverriddenDisasmPos.Line; currColumn = OverriddenDisasmPos.Column; + scrollPos = OverriddenDisasmPos.ScrollPos; OverriddenDisasmPos = default; } @@ -483,7 +487,7 @@ private void DisassembleCode(UndertaleCode code, bool first) DisassemblyEditor.Document.BeginUpdate(); DisassemblyEditor.Document.Text = text; - RestoreCaretPosition(DisassemblyEditor, currLine, currColumn); + RestoreCaretPosition(DisassemblyEditor, currLine, currColumn, scrollPos); DisassemblyEditor.Document.EndUpdate(); @@ -559,16 +563,19 @@ private async Task DecompileCode(UndertaleCode code, bool first, LoaderDialog ex int currLine = 1; int currColumn = 1; + double scrollPos = 0; if (!first) { var caret = DecompiledEditor.TextArea.Caret; currLine = caret.Line; currColumn = caret.Column; + scrollPos = DecompiledEditor.VerticalOffset; } else if (OverriddenDecompPos != default) { currLine = OverriddenDecompPos.Line; currColumn = OverriddenDecompPos.Column; + scrollPos = OverriddenDecompPos.ScrollPos; OverriddenDecompPos = default; } @@ -715,7 +722,7 @@ private async Task DecompileCode(UndertaleCode code, bool first, LoaderDialog ex CurrentLocals.Add(local.Name.Content); } - RestoreCaretPosition(DecompiledEditor, currLine, currColumn); + RestoreCaretPosition(DecompiledEditor, currLine, currColumn, scrollPos); if (existingDialog is not null) //if code was edited (and compiles after it) { diff --git a/UndertaleModTool/Tab.cs b/UndertaleModTool/Tab.cs index 8927ef512..76400c05c 100644 --- a/UndertaleModTool/Tab.cs +++ b/UndertaleModTool/Tab.cs @@ -284,8 +284,9 @@ public void SaveTabContentState() bool isDecompiledOpen = codeEditor.CodeModeTabs.SelectedIndex == 0; var textEditor = codeEditor.DecompiledEditor; - (int, int) decompCodePos; + (int, int, double) decompCodePos; int linePos, columnPos; + double codeScrollPos; // If the overridden position wasn't read if (UndertaleCodeEditor.OverriddenDecompPos != default) { @@ -297,17 +298,18 @@ public void SaveTabContentState() var caret = textEditor.TextArea.Caret; linePos = caret.Line; columnPos = caret.Column; + codeScrollPos = textEditor.VerticalOffset; int lineLen = textEditor.Document.GetLineByNumber(linePos).Length; // If caret is at the end of line if (lineLen == columnPos - 1) columnPos = -1; - decompCodePos = (linePos, columnPos); + decompCodePos = (linePos, columnPos, codeScrollPos); } textEditor = codeEditor.DisassemblyEditor; - (int, int) disasmCodePos; + (int, int, double) disasmCodePos; if (UndertaleCodeEditor.OverriddenDisasmPos != default) { disasmCodePos = UndertaleCodeEditor.OverriddenDisasmPos; @@ -318,23 +320,26 @@ public void SaveTabContentState() var caret = textEditor.TextArea.Caret; linePos = caret.Line; columnPos = caret.Column; + codeScrollPos = textEditor.VerticalOffset; int lineLen = textEditor.Document.GetLineByNumber(linePos).Length; // If caret is at the end of line if (lineLen == columnPos - 1) columnPos = -1; - disasmCodePos = (linePos, columnPos); + disasmCodePos = (linePos, columnPos, codeScrollPos); } - #pragma warning restore CA1416 LastContentState = new CodeTabState() { MainScrollPosition = mainScrollPos, DecompiledCodePosition = decompCodePos, DisassemblyCodePosition = disasmCodePos, + DecompiledScrollPos = codeEditor.DecompiledEditor.VerticalOffset, + DisassemblyScrollPos = codeEditor.DisassemblyEditor.VerticalOffset, IsDecompiledOpen = isDecompiledOpen }; + #pragma warning restore CA1416 break; case UndertaleRoomEditor roomEditor: @@ -855,10 +860,16 @@ public class TabContentState public class CodeTabState : TabContentState { /// The decompiled code position. - public (int Line, int Column) DecompiledCodePosition; + public (int Line, int Column, double ScrollPos) DecompiledCodePosition; /// The disassembly code position. - public (int Line, int Column) DisassemblyCodePosition; + public (int Line, int Column, double ScrollPos) DisassemblyCodePosition; + + /// The scroll position of decompiled code. + public double DecompiledScrollPos; + + /// The scroll position of disassembly code. + public double DisassemblyScrollPos; /// Whether the "Decompiled" tab is open. public bool IsDecompiledOpen;