MainWindow.axaml.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. using System.Collections.Generic;
  2. using System.Diagnostics;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Threading.Tasks;
  6. using Avalonia.Controls;
  7. using Avalonia.Input;
  8. using Avalonia.Platform.Storage;
  9. using SlamCross.ViewModels;
  10. namespace SlamCross.Views;
  11. public partial class MainWindow : Window
  12. {
  13. private MainWindowViewModel Vm => (MainWindowViewModel)DataContext!;
  14. public MainWindow()
  15. {
  16. InitializeComponent();
  17. DataContext = new MainWindowViewModel();
  18. Vm.ErrorOccurred += msg => Avalonia.Threading.Dispatcher.UIThread.Post(() => ShowMessage(msg));
  19. Closing += (_, e) => { Vm.StopPoll(); Vm.StopStream(); Vm.SaveSettings(); };
  20. if (Vm.Settings.StartMinimized)
  21. WindowState = WindowState.Minimized;
  22. }
  23. private async void ShowMessage(string msg)
  24. {
  25. var ok = new Button { Content = "OK", HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right };
  26. var dlg = new Window
  27. {
  28. Title = "SLAM",
  29. Width = 400,
  30. Height = 150,
  31. Content = new StackPanel
  32. {
  33. Margin = new Avalonia.Thickness(16),
  34. Spacing = 12,
  35. Children =
  36. {
  37. new TextBlock { Text = msg, TextWrapping = Avalonia.Media.TextWrapping.Wrap },
  38. ok
  39. }
  40. }
  41. };
  42. ok.Click += (_, _) => dlg.Close();
  43. await dlg.ShowDialog(this);
  44. }
  45. private async void ImportButton_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
  46. {
  47. var files = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
  48. {
  49. Title = "Import Audio Files",
  50. AllowMultiple = true,
  51. FileTypeFilter = new[]
  52. {
  53. new FilePickerFileType("Audio/Video")
  54. {
  55. Patterns = new[] { "*.mp3","*.wav","*.aac","*.wma","*.m4a","*.mp4","*.wmv","*.avi","*.m4v","*.mov","*.flac","*.ogg" }
  56. }
  57. }
  58. });
  59. if (files.Count > 0)
  60. await Vm.ImportFilesAsync(files.Select(f => f.Path.LocalPath));
  61. }
  62. private async void YTButton_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
  63. {
  64. var dlg = new YTImportWindow();
  65. await dlg.ShowDialog(this);
  66. if (dlg.ResultUrl is string url)
  67. await DownloadAndImportYt(url);
  68. }
  69. private async Task DownloadAndImportYt(string url)
  70. {
  71. var tmpDir = Path.Combine(Path.GetTempPath(), "slam_yt");
  72. Directory.CreateDirectory(tmpDir);
  73. var outTemplate = Path.Combine(tmpDir, "%(title)s.%(ext)s");
  74. var psi = new ProcessStartInfo("yt-dlp")
  75. {
  76. UseShellExecute = false,
  77. CreateNoWindow = true
  78. };
  79. psi.ArgumentList.Add("-x");
  80. psi.ArgumentList.Add("--audio-format"); psi.ArgumentList.Add("mp3");
  81. psi.ArgumentList.Add("-o"); psi.ArgumentList.Add(outTemplate);
  82. psi.ArgumentList.Add(url);
  83. try
  84. {
  85. using var proc = Process.Start(psi);
  86. if (proc != null) await proc.WaitForExitAsync();
  87. }
  88. catch
  89. {
  90. Vm.ShowError("yt-dlp not found. Install it to import from YouTube.");
  91. return;
  92. }
  93. var downloaded = Directory.GetFiles(tmpDir);
  94. if (downloaded.Length > 0)
  95. await Vm.ImportFilesAsync(downloaded, deleteSource: true);
  96. try { Directory.Delete(tmpDir, true); } catch { }
  97. }
  98. private async void SettingsButton_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
  99. {
  100. var dlg = new SettingsWindow(Vm.Settings);
  101. await dlg.ShowDialog(this);
  102. Vm.SaveSettings();
  103. }
  104. private async void PlayKeyButton_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
  105. {
  106. var dlg = new SelectKeyWindow();
  107. await dlg.ShowDialog(this);
  108. if (dlg.ChosenKey is string key)
  109. Vm.SetPlayKey(key);
  110. }
  111. private void StartStopButton_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
  112. {
  113. if (Vm.IsRunning) Vm.StopPoll();
  114. else _ = Vm.StartPollAsync();
  115. }
  116. private void StreamButton_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
  117. {
  118. if (Vm.IsStreaming) { Vm.StopStream(); return; }
  119. var idx = GetSelectedIndices().FirstOrDefault(-1);
  120. if (idx < 0) { Vm.ShowError("Select a track first."); return; }
  121. Vm.StreamToMic(idx);
  122. }
  123. private void StopStreamButton_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
  124. => Vm.StopStream();
  125. private void TrackGrid_DoubleTapped(object? sender, TappedEventArgs e)
  126. {
  127. var selected = GetSelectedIndices().FirstOrDefault(-1);
  128. if (selected < 0) return;
  129. if (Vm.IsRunning)
  130. Vm.PlayTrack(selected);
  131. else
  132. Vm.StreamToMic(selected);
  133. }
  134. private void MenuPlay_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
  135. {
  136. var idx = GetSelectedIndices().FirstOrDefault(-1);
  137. if (idx < 0) return;
  138. if (Vm.IsRunning) Vm.PlayTrack(idx);
  139. else Vm.StreamToMic(idx);
  140. }
  141. private void MenuRefresh_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
  142. => Vm.RefreshTracks();
  143. private async void MenuHotkey_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
  144. {
  145. var idx = GetSelectedIndices().FirstOrDefault(-1);
  146. if (idx < 0) return;
  147. var dlg = new SelectKeyWindow();
  148. await dlg.ShowDialog(this);
  149. if (dlg.ChosenKey is string key)
  150. Vm.SetTrackHotkey(idx, key);
  151. }
  152. private void MenuRemoveHotkey_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
  153. {
  154. foreach (var idx in GetSelectedIndices())
  155. Vm.RemoveHotkey(idx);
  156. }
  157. private async void MenuVolume_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
  158. {
  159. var indices = GetSelectedIndices().ToList();
  160. if (indices.Count == 0) return;
  161. var dlg = new SetVolumeWindow();
  162. await dlg.ShowDialog(this);
  163. if (dlg.Volume >= 0)
  164. Vm.SetTrackVolume(indices, dlg.Volume);
  165. }
  166. private async void MenuTrim_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
  167. {
  168. var idx = GetSelectedIndices().FirstOrDefault(-1);
  169. if (idx < 0) return;
  170. var track = Vm.GetTrack(idx);
  171. if (track == null) return;
  172. var filePath = Vm.GetTrackFilePath(idx);
  173. var dlg = new TrimWindow(filePath, track.StartPos, track.EndPos);
  174. await dlg.ShowDialog(this);
  175. if (dlg.Confirmed)
  176. Vm.SetTrackTrim(idx, dlg.StartPos, dlg.EndPos);
  177. }
  178. private async void MenuRename_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
  179. {
  180. var idx = GetSelectedIndices().FirstOrDefault(-1);
  181. if (idx < 0) return;
  182. var track = Vm.GetTrack(idx);
  183. if (track == null) return;
  184. var dlg = new RenameWindow(track.Name);
  185. await dlg.ShowDialog(this);
  186. if (dlg.NewName is string name)
  187. Vm.RenameTrack(idx, name);
  188. }
  189. private void MenuGoTo_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
  190. {
  191. var idx = GetSelectedIndices().FirstOrDefault(-1);
  192. if (idx < 0) return;
  193. var path = Vm.GetTrackFilePath(idx);
  194. if (!File.Exists(path)) return;
  195. var dir = Path.GetDirectoryName(path)!;
  196. if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(
  197. System.Runtime.InteropServices.OSPlatform.Linux))
  198. Process.Start("xdg-open", dir);
  199. else
  200. Process.Start("explorer.exe", $"/select,\"{path}\"");
  201. }
  202. private async void MenuDelete_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
  203. {
  204. var indices = GetSelectedIndices().ToList();
  205. if (indices.Count == 0) return;
  206. var names = indices.Select(i => Vm.Tracks[i].Name);
  207. var confirm = new ConfirmWindow($"Delete {string.Join(", ", names)}?");
  208. await confirm.ShowDialog(this);
  209. if (confirm.Confirmed)
  210. Vm.DeleteTracks(indices);
  211. }
  212. private IEnumerable<int> GetSelectedIndices()
  213. {
  214. var grid = this.FindControl<DataGrid>("TrackGrid");
  215. if (grid == null) yield break;
  216. foreach (var item in grid.SelectedItems.OfType<TrackItem>())
  217. yield return item.Index;
  218. }
  219. }