diff --git a/UniversalCodePatcher.Avalonia/MainWindow.axaml b/UniversalCodePatcher.Avalonia/MainWindow.axaml index 3d85528..7358ab8 100644 --- a/UniversalCodePatcher.Avalonia/MainWindow.axaml +++ b/UniversalCodePatcher.Avalonia/MainWindow.axaml @@ -12,15 +12,33 @@ + + + + + + + + + + + + + + + + + + diff --git a/UniversalCodePatcher.Avalonia/MainWindow.axaml.cs b/UniversalCodePatcher.Avalonia/MainWindow.axaml.cs index 6238d49..03534cd 100644 --- a/UniversalCodePatcher.Avalonia/MainWindow.axaml.cs +++ b/UniversalCodePatcher.Avalonia/MainWindow.axaml.cs @@ -4,17 +4,43 @@ using Avalonia.Platform.Storage; using Avalonia; using System.Collections.Generic; + +using System.Diagnostics; using System.IO; +using System.Linq; +using System.Text.Json; +using System; +using Avalonia.VisualTree; +using Avalonia.Layout; +using System.IO; + namespace UniversalCodePatcher.Avalonia; public partial class MainWindow : Window { private string? _projectPath; + + private bool _showHiddenFiles; + private bool _isDirty; + private readonly List _recentProjects = new(); + private const int MaxRecent = 5; + private string RecentFile => Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "UniversalCodePatcher", "recent.txt"); + public MainWindow() { InitializeComponent(); + + LoadRecentProjects(); + SourceBox.PropertyChanged += (_, e) => + { + if (e.Property == TextBox.TextProperty) + _isDirty = true; + }; + } private async void OnNewProject(object? sender, RoutedEventArgs e) @@ -28,6 +54,9 @@ private async void OnNewProject(object? sender, RoutedEventArgs e) { _projectPath = path; LoadProject(path); + AddRecentProject(path); + _isDirty = false; + } } @@ -36,6 +65,109 @@ private void OnOpenProject(object? sender, RoutedEventArgs e) OnNewProject(sender, e); } + + private void AddRecentProject(string path) + { + Directory.CreateDirectory(Path.GetDirectoryName(RecentFile)!); + _recentProjects.Remove(path); + _recentProjects.Insert(0, path); + if (_recentProjects.Count > MaxRecent) + _recentProjects.RemoveAt(_recentProjects.Count - 1); + File.WriteAllLines(RecentFile, _recentProjects); + PopulateRecentMenu(); + } + + private void LoadRecentProjects() + { + if (File.Exists(RecentFile)) + _recentProjects.AddRange(File.ReadAllLines(RecentFile)); + PopulateRecentMenu(); + } + + private void PopulateRecentMenu() + { + if (this.FindControl("RecentMenu") is { } recent) + { + recent.Items.Clear(); + foreach (var p in _recentProjects) + { + var item = new MenuItem { Header = p }; + item.Click += (_, _) => { _projectPath = p; LoadProject(p); }; + recent.Items.Add(item); + } + } + } + + private async void OnSaveProject(object? sender, RoutedEventArgs e) + { + if (_projectPath == null) + return; + var picker = TopLevel.GetTopLevel(this)?.StorageProvider; + if (picker == null) + return; + var file = await picker.SaveFilePickerAsync(new FilePickerSaveOptions + { + SuggestedFileName = "project.json" + }); + if (file != null) + { + var state = new { ProjectPath = _projectPath }; + await using var stream = await file.OpenWriteAsync(); + await JsonSerializer.SerializeAsync(stream, state); + _isDirty = false; + } + } + + private async void OnExit(object? sender, RoutedEventArgs e) + { + if (_isDirty) + { + var confirm = new Window + { + Width = 300, + Height = 120, + Title = "Exit", + Content = new StackPanel + { + Margin = new Thickness(20), + Children = + { + new TextBlock { Text = "Unsaved changes. Exit anyway?" }, + new StackPanel + { + Orientation = Orientation.Horizontal, + HorizontalAlignment = HorizontalAlignment.Right, + Children = + { + new Button { Content = "Yes", Margin = new Thickness(5), IsDefault = true }, + new Button { Content = "No", Margin = new Thickness(5), IsCancel = true } + } + } + } + } + }; + var yesButton = ((confirm.Content as StackPanel)!.Children[1] as StackPanel)!.Children[0] as Button; + var noButton = ((confirm.Content as StackPanel)!.Children[1] as StackPanel)!.Children[1] as Button; + bool result = false; + yesButton!.Click += (_, _) => { result = true; confirm.Close(); }; + noButton!.Click += (_, _) => { confirm.Close(); }; + await confirm.ShowDialog(this); + if (!result) + return; + } + (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.Shutdown(); + } + + private void OnUndo(object? sender, RoutedEventArgs e) + { + SourceBox.Undo(); + } + + private void OnRedo(object? sender, RoutedEventArgs e) + { + SourceBox.Redo(); + } +======= private void OnSaveProject(object? sender, RoutedEventArgs e) { // Placeholder for saving project state @@ -48,6 +180,7 @@ private void OnExit(object? sender, RoutedEventArgs e) private void OnUndo(object? sender, RoutedEventArgs e) { } private void OnRedo(object? sender, RoutedEventArgs e) { } + private async void OnAbout(object? sender, RoutedEventArgs e) { @@ -74,10 +207,18 @@ private IEnumerable BuildTree(string path) var root = new TreeViewItem { Header = Path.GetFileName(path), Tag = path }; foreach (var file in Directory.GetFiles(path)) { + + if (!_showHiddenFiles && Path.GetFileName(file).StartsWith('.')) + continue; + root.Items.Add(new TreeViewItem { Header = Path.GetFileName(file), Tag = file }); } foreach (var dir in Directory.GetDirectories(path)) { + + if (!_showHiddenFiles && Path.GetFileName(dir).StartsWith('.')) + continue; + root.Items.Add(BuildSubTree(dir)); } return new[] { root }; @@ -88,12 +229,135 @@ private TreeViewItem BuildSubTree(string dir) var node = new TreeViewItem { Header = Path.GetFileName(dir), Tag = dir }; foreach (var file in Directory.GetFiles(dir)) { + + if (!_showHiddenFiles && Path.GetFileName(file).StartsWith('.')) + continue; + node.Items.Add(new TreeViewItem { Header = Path.GetFileName(file), Tag = file }); } foreach (var sub in Directory.GetDirectories(dir)) { + + if (!_showHiddenFiles && Path.GetFileName(sub).StartsWith('.')) + continue; + node.Items.Add(BuildSubTree(sub)); } return node; } + + private void OnRefresh(object? sender, RoutedEventArgs e) + { + if (_projectPath != null) + LoadProject(_projectPath); + } + + private void OnToggleHidden(object? sender, RoutedEventArgs e) + { + _showHiddenFiles = HiddenToggle.IsChecked == true; + OnRefresh(sender, e); + } + + private void ExpandCollapseAll(bool expand) + { + foreach (var item in ProjectTree.Items) + ExpandNode(item as TreeViewItem, expand); + } + + private void ExpandNode(TreeViewItem? node, bool expand) + { + if (node == null) return; + node.IsExpanded = expand; + foreach (var child in node.Items) + ExpandNode(child as TreeViewItem, expand); + } + + private void OnExpandAll(object? sender, RoutedEventArgs e) => ExpandCollapseAll(true); + private void OnCollapseAll(object? sender, RoutedEventArgs e) => ExpandCollapseAll(false); + + private void OnSelectAllFiles(object? sender, RoutedEventArgs e) + { + foreach (var item in ProjectTree.Items) + SelectNode(item as TreeViewItem); + } + + private void SelectNode(TreeViewItem? node) + { + if (node == null) return; + node.IsSelected = true; + foreach (var child in node.Items) + SelectNode(child as TreeViewItem); + } + + private async void OnFindInFiles(object? sender, RoutedEventArgs e) + { + if (_projectPath == null) return; + var input = new Window + { + Width = 300, + Height = 120, + Title = "Find", + Content = new StackPanel + { + Margin = new Thickness(10), + Children = + { + new TextBox { Name = "QueryBox" }, + new Button { Content = "Search", Margin = new Thickness(0,5,0,0), HorizontalAlignment = HorizontalAlignment.Right } + } + } + }; + var queryBox = input.FindControl("QueryBox"); + var searchButton = input.GetVisualDescendants().OfType