From 06018ee69e5bec82a4635fd33dbd756f09d7a0af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Emre=20T=C3=BClek?= Date: Wed, 11 Dec 2024 20:56:28 +0300 Subject: [PATCH] Add project files. --- Radio.sln | 25 +++ Radio/FodyWeavers.xml | 7 + Radio/IconPaths.cs | 29 +++ Radio/MainWindow.xaml | 182 +++++++++++++++++ Radio/MainWindow.xaml.cs | 399 ++++++++++++++++++++++++++++++++++++++ Radio/PopupWindow.xaml | 29 +++ Radio/PopupWindow.xaml.cs | 63 ++++++ Radio/Radio.cs | 55 ++++++ Radio/Radio.csproj | 69 +++++++ Radio/RadioPlugin.cs | 20 ++ Radio/RadioViewModel.cs | 349 +++++++++++++++++++++++++++++++++ Radio/Style.xaml | 146 ++++++++++++++ 12 files changed, 1373 insertions(+) create mode 100644 Radio.sln create mode 100644 Radio/FodyWeavers.xml create mode 100644 Radio/IconPaths.cs create mode 100644 Radio/MainWindow.xaml create mode 100644 Radio/MainWindow.xaml.cs create mode 100644 Radio/PopupWindow.xaml create mode 100644 Radio/PopupWindow.xaml.cs create mode 100644 Radio/Radio.cs create mode 100644 Radio/Radio.csproj create mode 100644 Radio/RadioPlugin.cs create mode 100644 Radio/RadioViewModel.cs create mode 100644 Radio/Style.xaml diff --git a/Radio.sln b/Radio.sln new file mode 100644 index 0000000..0f231ab --- /dev/null +++ b/Radio.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35327.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Radio", "Radio\Radio.csproj", "{9546A907-D0F0-4D80-88E1-B1EE743C0A2B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9546A907-D0F0-4D80-88E1-B1EE743C0A2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9546A907-D0F0-4D80-88E1-B1EE743C0A2B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9546A907-D0F0-4D80-88E1-B1EE743C0A2B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9546A907-D0F0-4D80-88E1-B1EE743C0A2B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EB6325A6-44A3-4FC1-A484-432BEFEABE13} + EndGlobalSection +EndGlobal diff --git a/Radio/FodyWeavers.xml b/Radio/FodyWeavers.xml new file mode 100644 index 0000000..8b8d956 --- /dev/null +++ b/Radio/FodyWeavers.xml @@ -0,0 +1,7 @@ + + + + LibVLCSharp + + + \ No newline at end of file diff --git a/Radio/IconPaths.cs b/Radio/IconPaths.cs new file mode 100644 index 0000000..8d4abbd --- /dev/null +++ b/Radio/IconPaths.cs @@ -0,0 +1,29 @@ +using System.Windows.Media; + +namespace Radio +{ + public static class IconPaths + { + private static readonly string PlayPath = "M 6.1057828,63.999624 A 6.1512566,6.1512566 0 0 1 3.0623268,63.187481 C 0.97539178,62.004946 -0.32200322,59.70923 -0.32200322,57.217092 V 6.7826921 c 0,-2.499092 1.297432,-4.787778 3.38433002,-5.97038902 a 6.1095183,6.1095183 0 0 1 6.22082,0.07861 L 52.387174,26.692465 a 6.2608214,6.2608214 0 0 1 0,10.608612 L 9.2761928,63.109548 a 6.1738653,6.1738653 0 0 1 -3.17041,0.890454 z"; + private static readonly string PausePath = "M 7.6906505,2.9120095 C 7.1110983,4.0497216 7.1110983,5.5431249 7.1110983,8.5333333 V 55.466629 c 0,2.986655 0,4.48002 0.5795522,5.621323 a 5.3333268,5.3333268 0 0 0 2.3324575,2.328905 C 11.160858,64 12.654223,64 15.644432,64 h 0.711117 c 2.986655,0 4.479983,0 5.621324,-0.57959 a 5.3333268,5.3333268 0 0 0 2.332419,-2.33242 c 0.57959,-1.137712 0.57959,-2.631116 0.57959,-5.621324 V 8.533371 c 0,-2.9866552 0,-4.4800206 -0.57959,-5.6213237 A 5.3333268,5.3333268 0 0 0 21.976873,0.57958976 C 20.839085,-1.875e-7 19.345757,-1.875e-7 16.355549,-1.875e-7 h -0.711117 c -2.986656,0 -4.480021,0 -5.621324,0.5795899475 A 5.3333268,5.3333268 0 0 0 7.6906505,2.9120095 Z m 31.9999825,0 c -0.57959,1.1377121 -0.57959,2.6311154 -0.57959,5.6213238 V 55.466629 c 0,2.986655 0,4.48002 0.583142,5.621323 a 5.3333268,5.3333268 0 0 0 2.328905,2.328905 C 43.160802,64 44.654168,64 47.644414,64 h 0.711155 c 2.986655,0 4.479983,0 5.621323,-0.57959 a 5.3333268,5.3333268 0 0 0 2.328867,-2.33242 c 0.583143,-1.137712 0.583143,-2.631116 0.583143,-5.621324 V 8.533371 c 0,-2.9866552 0,-4.4800206 -0.57959,-5.6213237 A 5.3333268,5.3333268 0 0 0 53.976892,0.57958976 C 52.839029,3.7607738e-5 51.345777,3.7607738e-5 48.355569,3.7607738e-5 h -0.711155 c -2.986656,0 -4.479983,0 -5.621324,0.579552152262 A 5.3333268,5.3333268 0 0 0 39.694185,2.9120473 Z"; + private static readonly string StopPath = "M -4.6743125e-7,55.322732 C -4.6743125e-7,60.780371 3.2982428,64 8.8344619,64 H 55.165575 c 5.536215,0 8.834458,-3.219629 8.834458,-8.677268 V 8.6773065 C 64.000033,3.2196673 60.70179,4.6743145e-7 55.165575,4.6743145e-7 H 8.8344619 C 3.2982466,4.6743145e-7 -4.6743125e-7,3.2197051 -4.6743125e-7,8.6773065 Z"; + private static readonly string ForwardPath = "M 56.00125,-3.1715222e-5 A 2.6668035,2.6668035 0 0 0 53.33443,2.6667883 V 25.406273 L 14.537441,2.1851013 A 5.8553003,5.8553003 0 0 0 8.575459,2.1121513 C 6.575344,3.2450853 5.331967,5.4384953 5.331967,7.8336293 V 56.169463 c 0,2.395096 1.243339,4.588544 3.243492,5.721931 a 5.8569672,5.8569672 0 0 0 5.961982,-0.07559 L 53.33443,38.59701 v 22.739484 a 2.6668035,2.6668035 0 0 0 5.333603,0 V 2.6667883 A 2.6668035,2.6668035 0 0 0 56.00125,-3.1715222e-5 Z"; + private static readonly string BackwardPath = "M 7.9987735,-1.374354e-4 A 2.666805,2.666805 0 0 1 10.665558,2.6666466 V 25.406183 L 49.462569,2.1849586 a 5.8553038,5.8553038 0 0 1 5.961985,-0.07295 c 2.000116,1.132935 3.243494,3.333074 3.243494,5.721557 V 56.169391 c 0,2.395135 -1.24334,4.588585 -3.243494,5.721973 a 5.8569707,5.8569707 0 0 1 -5.961985,-0.0756 L 10.665558,38.59689 v 22.739498 a 2.666805,2.666805 0 0 1 -5.3336065,0 V 2.6666846 A 2.666805,2.666805 0 0 1 7.9987735,-9.9435399e-5 Z"; + private static readonly string SoundHighPath = "M 54.24702,51.514546 49.249451,47.516368 C 60.389048,35.34333 60.389048,16.734448 49.249301,4.5614426 l 4.997527,-3.9979844 c 13.0043,14.5207958 13.004108,36.4302908 1.5e-4,50.9510878 z m -12.670155,-40.814983 -5.00343,4.00266 c 5.975084,6.391292 5.974892,16.282234 1.5e-4,22.673525 l 5.00343,4.00266 c 7.838187,-8.74158 7.838187,-21.937456 -1.5e-4,-30.678845 z M 28.79015,0.22367581 12.146035,13.32064 H 0 V 38.945255 H 12.164805 L 28.79015,51.776332 Z M 6.4061536,19.726793 h 7.9607674 l 8.017077,-6.309195 V 38.738786 L 14.348151,32.539102 H 6.4061536 Z"; + private static readonly string SoundLowPath = "M 41.937839,41.511882 36.890992,37.474487 C 42.917536,31.027736 42.91773,21.050968 36.89084,14.604216 l 5.046805,-4.037426 c 7.906395,8.817243 7.906395,22.127624 1.51e-4,30.945092 z M 29.039974,0 12.251432,13.210612 H 0 v 25.84697 H 12.270364 L 29.039974,52 Z M 6.4617424,19.672354 h 8.0298466 l 8.086645,-6.363943 v 25.54091 L 14.472655,32.59584 H 6.4617424 Z"; + private static readonly string SoundMutePath = "M 54.926931,26.181819 64,35.254888 l -4.593629,4.593629 -9.073069,-9.073069 -9.073069,9.073069 -4.593627,-4.593629 9.073069,-9.073069 -9.073069,-9.073069 4.593627,-4.593627 9.073069,9.073069 9.073069,-9.073069 L 64,17.10875 Z M 29.198769,0 12.318424,13.28285 H 0 V 39.271153 H 12.33746 L 29.198769,52.284342 Z M 6.4970761,19.779925 H 14.57083 l 8.130864,-6.398742 v 25.68057 L 14.551793,32.774078 H 6.4970761 Z"; + private static readonly string NotaSinglePath = "M 101.33169,47.896905 v 26.66441 18.81441 A 13.333162,13.333162 0 0 0 93.334706,90.668559 13.333162,13.333162 0 0 0 80.001525,104.00174 13.333162,13.333162 0 0 0 93.334706,117.33492 13.333162,13.333162 0 0 0 106.66789,104.00174 V 71.893703 L 128,61.229599 Z"; + private static readonly string NotaDualPath = "M 80.001037,0 21.331624,10.652384 V 26.77916 h -5e-4 V 61.334097 A 13.333162,13.333162 0 0 0 13.333181,58.667459 13.333162,13.333162 0 0 0 0,72.000641 13.333162,13.333162 0 0 0 13.333181,85.333822 13.333162,13.333162 0 0 0 26.666362,72.000641 a 13.333162,13.333162 0 0 0 -0.0015,-0.05957 V 31.172933 L 74.6648,22.457657 v 33.53926 c -2.228098,-1.673233 -4.9969,-2.66517 -7.997954,-2.66517 -7.363365,0 -13.334646,5.972744 -13.334646,13.336109 0,7.363365 5.971281,13.332206 13.334646,13.332206 7.365365,0 13.334156,-5.968841 13.334156,-13.332206 V 21.488859 Z"; + + public static Geometry Play() { return Geometry.Parse(PlayPath); } + public static Geometry Pause() { return Geometry.Parse(PausePath); } + public static Geometry Stop() { return Geometry.Parse(StopPath); } + public static Geometry Forward() { return Geometry.Parse(ForwardPath); } + public static Geometry Backward() { return Geometry.Parse(BackwardPath); } + public static Geometry SoundHigh() { return Geometry.Parse(SoundHighPath); } + public static Geometry SoundLow() { return Geometry.Parse(SoundLowPath); } + public static Geometry SoundMute() { return Geometry.Parse(SoundMutePath); } + public static Geometry NotaSingle() { return Geometry.Parse(NotaSinglePath); } + public static Geometry NotaDual() { return Geometry.Parse(NotaDualPath); } + } +} diff --git a/Radio/MainWindow.xaml b/Radio/MainWindow.xaml new file mode 100644 index 0000000..e6419d4 --- /dev/null +++ b/Radio/MainWindow.xaml @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Radio/MainWindow.xaml.cs b/Radio/MainWindow.xaml.cs new file mode 100644 index 0000000..2a3354c --- /dev/null +++ b/Radio/MainWindow.xaml.cs @@ -0,0 +1,399 @@ +using System.Collections.ObjectModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Threading; +using LibVLCSharp.Shared; +using Newtonsoft.Json; +using Widgets.Common; + +namespace Radio +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window, IWidgetWindow + { + public readonly static string WidgetName = "Radio"; + public readonly static string SettingsFile = "settings.radio.json"; + private readonly Config config = new(SettingsFile); + private RadioViewModel.SettingsStruct Settings = RadioViewModel.Default; + public RadioViewModel Radio { get; set; } + private DispatcherTimer? _timer; + private DoubleAnimation? MarqueeAnimation; + + public MainWindow() + { + LoadSettings(); + + Radio = new RadioViewModel(Settings); + InitializeComponent(); + DataContext = Radio; + } + + // Window to WdigetWindow + public WidgetWindow WidgetWindow() + { + return new WidgetWindow(this, WidgetDefaultStruct()); + } + + // WidgetWindow default settings + public static WidgetDefaultStruct WidgetDefaultStruct() + { + return new() + { + Width = 400, + MinHeight = 200, + MaxHeight = 600, + SizeToContent = SizeToContent.Height + }; + } + + // config file load + private void LoadSettings() + { + try + { + Settings.FontSize = PropertyParser.ToFloat(config.GetValue("font_size"), Settings.FontSize); + Settings.PrimaryColor = PropertyParser.ToColorBrush(config.GetValue("primary_color"), Settings.PrimaryColor.ToString()); + Settings.PrimaryColorLight = PropertyParser.ToColorBrush(config.GetValue("primary_color_light"), Settings.PrimaryColorLight.ToString()); + Settings.SecondaryColor = PropertyParser.ToColorBrush(config.GetValue("secondary_color"), Settings.SecondaryColor.ToString()); + Settings.SecondaryColorLight = PropertyParser.ToColorBrush(config.GetValue("secondary_color_light"), Settings.SecondaryColorLight.ToString()); + Settings.TextColor = PropertyParser.ToColorBrush(config.GetValue("text_color"), Settings.TextColor.ToString()); + Settings.Volume = PropertyParser.ToInt(config.GetValue("radio_volume"), Settings.Volume); + Settings.RadioIndex = PropertyParser.ToInt(config.GetValue("radio_index"), Settings.RadioIndex); + var stringJson = PropertyParser.ToString(config.GetValue("radio_list")); + Settings.RadioList = JsonConvert.DeserializeObject>(stringJson) ?? Settings.RadioList; + } + catch (Exception) + { + config.Add("font_size", Settings.FontSize); + config.Add("primary_color", Settings.PrimaryColor); + config.Add("primary_color_light", Settings.PrimaryColorLight); + config.Add("secondary_color", Settings.SecondaryColor); + config.Add("secondary_color_light", Settings.SecondaryColorLight); + config.Add("text_color", Settings.TextColor); + config.Add("radio_volume", Settings.Volume); + config.Add("radio_index", Settings.RadioIndex); + config.Add("radio_list", Settings.RadioList ?? []); + + config.Save(); + } + } + + + private void OrderedRadioListAndUpdateSettings() + { + var orderedRadioList = Radio.RadioList.OrderBy(item => item.Name).ToList(); + var selectedRadio = Radio.SelectedRadio; + Radio.RadioList.Clear(); + + foreach (var radio in orderedRadioList) + { + Radio.RadioList.Add(radio); + } + + Radio.SelectedRadio = selectedRadio; + RadioListBox.SelectedItem = Radio.SelectedRadio; + Settings.RadioList = Radio.RadioList; + Settings.RadioIndex = RadioListBox.SelectedIndex; + + RadioListBox.ScrollIntoView(RadioListBox.SelectedItem); + + config.Add("font_size", Settings.FontSize); + config.Add("primary_color", Settings.PrimaryColor); + config.Add("primary_color_light", Settings.PrimaryColorLight); + config.Add("secondary_color", Settings.SecondaryColor); + config.Add("secondary_color_light", Settings.SecondaryColorLight); + config.Add("text_color", Settings.TextColor); + config.Add("radio_volume", Settings.Volume); + config.Add("radio_index", Settings.RadioIndex); + config.Add("radio_list", Settings.RadioList ?? []); + + config.Save(); + } + + + // After content rendering + protected override void OnContentRendered(EventArgs e) + { + var mainWindowsResource = this.Resources.MergedDictionaries; + var styleResource = mainWindowsResource.FirstOrDefault(d => d.Source.OriginalString == "Style.xaml"); + + if (styleResource != null) + { + styleResource["PrimaryColor"] = Settings.PrimaryColor.Color; + styleResource["PrimaryColorLight"] = Settings.PrimaryColorLight.Color; + styleResource["SecondaryColor"] = Settings.SecondaryColor.Color; + styleResource["SecondaryColorLight"] = Settings.SecondaryColorLight.Color; + styleResource["TextColor"] = Settings.TextColor.Color; + styleResource["AnimateBrush_1"] = Settings.PrimaryColor; + styleResource["AnimateBrush_2"] = Settings.PrimaryColorLight; + } + + Lazy_SelectionChanged(); + RadioListBox.ScrollIntoView(RadioListBox.SelectedItem); + base.OnContentRendered(e); + } + + // Add new radio with popup window + private void AddRadioWindow_Click(object sender, RoutedEventArgs e) + { + var popup = new PopupWindow + { + Owner = this, + Width = 300, + SizeToContent = SizeToContent.Height + }; + + if (popup.ShowDialog() == true && popup.NewRadio != null) + { + Radio.RadioList.Add(popup.NewRadio); + Radio.SelectedRadio = popup.NewRadio; + OrderedRadioListAndUpdateSettings(); + } + } + + // Delete radio with delete key + private void DeleteRadio(Radio? radioList = null) + { + radioList ??= Radio.SelectedRadio; + + if (radioList != null) + { + MessageBoxResult messageBoxResult = MessageBox.Show( + $"Are you sure delete {radioList.Name} radio?", + "Delete Confirmation", MessageBoxButton.YesNo + ); + + if (messageBoxResult == MessageBoxResult.Yes) + { + var willdeleteIndex = RadioListBox.Items.IndexOf(radioList); + Radio.RadioList.Remove(radioList); + if(willdeleteIndex > 0) + { + RadioListBox.SelectedItem = RadioListBox.Items[willdeleteIndex-1]; + Radio.SelectedRadio = (Radio)RadioListBox.SelectedItem; + } + + OrderedRadioListAndUpdateSettings(); + } + } + } + + // RadioListbox item context menu + // Delete Radio + // Edit Radio + public void ListBox_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e) + { + if (sender is ListBox listBox && e.OriginalSource is DependencyObject source) + { + var listBoxItem = ItemsControl.ContainerFromElement(listBox, source) as ListBoxItem; + + if (listBoxItem?.DataContext is Radio radio) + { + ContextMenu contextMenu = new(); + MenuItem option1 = new() { Header = "Edit", Icon = "🖉" }; + option1.Click += (sender, e) => + { + var popup = new PopupWindow(radio) + { + Owner = this, + Width = 300, + SizeToContent = SizeToContent.Height, + }; + + if (popup.ShowDialog() == true && popup.NewRadio != null) + { + if (Radio.RadioList.Contains(popup.NewRadio)) + { + OrderedRadioListAndUpdateSettings(); + } + } + }; + MenuItem option2 = new() { Header = "Delete", Icon = "✗" }; + option2.Click += (sender, e) => + { + DeleteRadio(radio); + }; + contextMenu.Items.Add(option1); + contextMenu.Items.Add(option2); + + contextMenu.IsOpen = true; + } + } + + e.Handled = true; + } + + // When window closed + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + + Radio.Stop(); + Radio.Dispose(); + + config.Add("radio_index", RadioListBox.SelectedIndex); + config.Add("radio_list", Settings.RadioList); + config.Save(); + + Logger.Info($"{WidgetName} is closed"); + } + + // Sortcut keys + protected override void OnKeyDown(KeyEventArgs e) + { + base.OnKeyDown(e); + + if (e.Key == Key.MediaPlayPause || e.Key == Key.Space || e.Key == Key.Enter) + { + PlayButton_Click(this, e); + } + else if (e.Key == Key.MediaNextTrack || e.Key == Key.Right || e.Key == Key.Down) + { + ForwardButton_Click(this, e); + } + else if (e.Key == Key.MediaPreviousTrack || e.Key == Key.Left || e.Key == Key.Up) + { + BackwardButton_Click(this, e); + } + else if (e.Key == Key.Delete) + { + DeleteRadio(); + } + + e.Handled = true; + } + + // Animated text update for content length + private void ScrollingTextChanging(object? sender, SizeChangedEventArgs e) + { + if (Radio.PlayerState() == VLCState.Playing) + { + double windowWidth = ActualWidth; + ScrollingText.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + MarqueeAnimation = new DoubleAnimation + { + From = windowWidth, + To = ScrollingText.DesiredSize.Width * -1, + Duration = new Duration(TimeSpan.FromSeconds(10)), + RepeatBehavior = RepeatBehavior.Forever + }; + TextTransform.BeginAnimation(TranslateTransform.XProperty, MarqueeAnimation); + } + } + + // Play & Stop Button + private void PlayButton_Click(object sender, RoutedEventArgs e) + { + var storyboard = (Storyboard)FindResource("AnimateNotes"); + + if (Radio.PlayerState() != VLCState.Playing) + { + Radio.Play(); + TextTransform.BeginAnimation(TranslateTransform.XProperty, MarqueeAnimation); + storyboard.Begin(); + return; + } + + if (Radio.PlayerState() != VLCState.Stopped) + { + Radio.Stop(); + TextTransform.BeginAnimation(TranslateTransform.XProperty, null); + storyboard.Stop(); + return; + } + } + + // Backward Button + private void BackwardButton_Click(object sender, RoutedEventArgs e) + { + RadioListBox.SelectedIndex = Math.Max(RadioListBox.SelectedIndex - 1, 0); + RadioListBox.ScrollIntoView(RadioListBox.SelectedItem); + } + + // Forward Button + private void ForwardButton_Click(object sender, RoutedEventArgs e) + { + RadioListBox.SelectedIndex = Math.Min(RadioListBox.SelectedIndex + 1, RadioListBox.Items.Count); + RadioListBox.ScrollIntoView(RadioListBox.SelectedItem); + } + + // Delay selected radio changing + private void Lazy_SelectionChanged() + { + _timer = new DispatcherTimer + { + Interval = TimeSpan.FromSeconds(1) + }; + _timer.Tick += Selection_Timer_Tick; + } + + // Delay timer tick + private async void Selection_Timer_Tick(object? sender, EventArgs e) + { + _timer?.Stop(); + + if (Radio.PlayerState() == VLCState.Playing) + { + Radio.Stop(); + await Task.Delay(500); + Radio.Play(); + } + } + + // Radio Listbox selected chaned + private void RadioListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + _timer?.Stop(); + ListBoxItem selectedItem = (ListBoxItem)RadioListBox.ItemContainerGenerator.ContainerFromItem(RadioListBox.SelectedItem); + + if (selectedItem == null) return; + + BackwardButton.IsEnabled = !(RadioListBox.SelectedIndex <= 0); + ForwardButton.IsEnabled = !(RadioListBox.SelectedIndex >= RadioListBox.Items.Count - 1); + + _timer?.Start(); + } + + // When Mohusewhell event only 1 item change + private void RadioListScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e) + { + var listBox = sender as ListBox; + var scrollViewer = FindScrollViewer(listBox); + + if (listBox != null && scrollViewer != null) + { + var scrollAmount = 1; + + if (e.Delta > 0) + { + scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - Math.Abs(scrollAmount)); + } + else + { + scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + Math.Abs(scrollAmount)); + } + e.Handled = true; + } + } + + // Helper function for scrollviewer + private static ScrollViewer? FindScrollViewer(DependencyObject? element) + { + if (element is ScrollViewer scrollViewer) return scrollViewer; + + for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++) + { + var child = VisualTreeHelper.GetChild(element, i); + var result = FindScrollViewer(child); + if (result != null) return result; + } + + return null; + } + } +} \ No newline at end of file diff --git a/Radio/PopupWindow.xaml b/Radio/PopupWindow.xaml new file mode 100644 index 0000000..1959743 --- /dev/null +++ b/Radio/PopupWindow.xaml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + +