UI update, almost complete support of Taskbar, classes separation and organization
This commit is contained in:
126
Views/MainWindow.xaml
Normal file
126
Views/MainWindow.xaml
Normal file
@ -0,0 +1,126 @@
|
||||
<Window x:Class="unison.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:emoji="clr-namespace:Emoji.Wpf;assembly=Emoji.Wpf"
|
||||
xmlns:clr="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:local="clr-namespace:unison"
|
||||
mc:Ignorable="d"
|
||||
Title="unison"
|
||||
Closing="Window_Closing" Icon="/unison.ico" ResizeMode="CanMinimize" SizeToContent="WidthAndHeight">
|
||||
|
||||
<Grid Background="{DynamicResource {x:Static SystemColors.ControlLightLightBrushKey}}" MinHeight="270">
|
||||
<Grid x:Name="TopLayout" Margin="10,0,10,0" VerticalAlignment="Stretch" Width="Auto" Height="Auto">
|
||||
<Grid x:Name="Display" HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="225,0,0,0" Height="Auto" Width="Auto">
|
||||
<GroupBox Height="220" VerticalAlignment="Center">
|
||||
<GroupBox.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="🎶"/>
|
||||
</StackPanel>
|
||||
</GroupBox.Header>
|
||||
<Grid>
|
||||
<Grid x:Name="CurrentSong" Margin="10,0,10,0" VerticalAlignment="Top" MinHeight="80">
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
|
||||
<TextBlock x:Name="SongTitle" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Normal" FontSize="20" Text="Title"/>
|
||||
<TextBlock x:Name="SongArtist" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Bold" FontSize="18" Text="Artist"/>
|
||||
<TextBlock x:Name="SongAlbum" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Normal" FontSize="16" Text="Album"/>
|
||||
<TextBlock x:Name="Bitrate" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Normal" Text="Bitrate"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid x:Name="Controls" VerticalAlignment="Top" Margin="10,106,10,0">
|
||||
<StackPanel HorizontalAlignment="Stretch" Orientation="Vertical" VerticalAlignment="Top">
|
||||
<Grid HorizontalAlignment="Center" VerticalAlignment="Top">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Name="CurrentTime" Text="0:00" TextWrapping="Wrap" HorizontalAlignment="Left"/>
|
||||
<Slider x:Name="TimeSlider" MinWidth="320" Margin="5,0,5,0" HorizontalAlignment="Center" Maximum="100"/>
|
||||
<TextBlock x:Name="EndTime" Text="0:00" TextWrapping="Wrap" Height="18" HorizontalAlignment="Right"/>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,5,0,0">
|
||||
<Button x:Name="PreviousTrack" Click="Previous_Clicked" FontSize="18" Background="{x:Null}" BorderBrush="{x:Null}" FontWeight="Bold" HorizontalAlignment="Left">
|
||||
<emoji:TextBlock ColorBlend="True" Text="⏪" FontSize="20"/>
|
||||
</Button>
|
||||
<Button x:Name="PauseButton" Click="Pause_Clicked" FontSize="18" FontWeight="Bold" Background="{x:Null}" BorderBrush="{x:Null}" Margin="10,0,10,0">
|
||||
<emoji:TextBlock x:Name="PauseButtonEmoji" ColorBlend="True" Text="⏯️" FontSize="20"/>
|
||||
</Button>
|
||||
<Button x:Name="NextTrack" Click="Next_Clicked" FontSize="18" Background="{x:Null}" BorderBrush="{x:Null}" FontWeight="Bold" HorizontalAlignment="Right">
|
||||
<emoji:TextBlock ColorBlend="True" Text="⏩" FontSize="20"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<Grid VerticalAlignment="Stretch" Margin="0,2.5,0,0">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Center" Margin="0,6,0,0">
|
||||
<TextBlock Text="🔈" TextWrapping="Wrap" HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||
<Slider x:Name="VolumeSlider" Maximum="100" Value="50" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" MinWidth="180" FlowDirection="LeftToRight" Margin="5,0,5,0" Foreground="{x:Null}" Background="{x:Null}"/>
|
||||
<TextBlock Text="🔊" TextWrapping="Wrap" HorizontalAlignment="Right" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Bottom" MinHeight="35">
|
||||
<Border BorderThickness="0,0,0,0" BorderBrush="Black" HorizontalAlignment="Stretch" VerticalAlignment="Top" SnapsToDevicePixels="True">
|
||||
<Button x:Name="Random" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Background="{x:Null}" BorderBrush="{x:Null}" FontSize="18" Click="Random_Clicked">
|
||||
<emoji:TextBlock ColorBlend="True" Text="🔀" FontSize="18" Margin="0" Padding="0, 0, 0, 2" />
|
||||
</Button>
|
||||
</Border>
|
||||
<Border BorderThickness="0, 0, 0, 2" BorderBrush="Black" VerticalAlignment="Top" Margin="5,0,0,0">
|
||||
<Button x:Name="Repeat" Background="{x:Null}" FontSize="18" BorderBrush="{x:Null}" Click="Repeat_Clicked">
|
||||
<emoji:TextBlock ColorBlend="True" Text="🔁" FontSize="18" Margin="0" Padding="0, 0, 0, 2" />
|
||||
</Button>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" VerticalAlignment="Bottom" MinHeight="35">
|
||||
<Border BorderThickness="0,0,0,0" BorderBrush="Black" HorizontalAlignment="Stretch" VerticalAlignment="Top" SnapsToDevicePixels="True" Margin="0,0,5,0">
|
||||
<Button x:Name="Single" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" BorderBrush="{x:Null}" Background="{x:Null}" Click="Single_Clicked">
|
||||
<emoji:TextBlock ColorBlend="True" Text="🔂" FontSize="18" Margin="0" Padding="0, 0, 0, 2" TextDecorations="{x:Null}"/>
|
||||
</Button>
|
||||
</Border>
|
||||
<Border BorderThickness="0,0,0,0" BorderBrush="Black" HorizontalAlignment="Stretch" VerticalAlignment="Top" SnapsToDevicePixels="True">
|
||||
<Button x:Name="Consume" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" BorderBrush="{x:Null}" Background="{x:Null}" Click="Consume_Clicked">
|
||||
<emoji:TextBlock ColorBlend="True" Text="🆓" FontSize="18" Margin="0" Padding="0, 0, 0, 2"/>
|
||||
</Button>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
<Border x:Name="Cover_Border" Padding="2" HorizontalAlignment="Left" VerticalAlignment="Top" MinWidth="219" MinHeight="219" MaxWidth="219" MaxHeight="219" BorderThickness="1" BorderBrush="#FFD5DFE5" CornerRadius="4">
|
||||
<Grid>
|
||||
<Border Name="mask" Background="White" CornerRadius="3"/>
|
||||
<StackPanel>
|
||||
<StackPanel.OpacityMask>
|
||||
<VisualBrush Visual="{Binding ElementName=mask}"/>
|
||||
</StackPanel.OpacityMask>
|
||||
<Image HorizontalAlignment="Center" VerticalAlignment="Center" Source="/images/nocover.png" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
<Grid x:Name="BottomLayout" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Background="{DynamicResource {x:Static SystemColors.ControlLightBrushKey}}" Width="Auto" MinHeight="40">
|
||||
<Button x:Name="Snapcast" HorizontalAlignment="Left" VerticalAlignment="Center" Click="Snapcast_Clicked" Margin="10,0,0,0" Padding="5, 2" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<emoji:TextBlock Text="🔊"/>
|
||||
<TextBlock x:Name="SnapcastText" Text="Start Snapcast" Margin="5, 0, 0, 0"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<TextBlock x:Name="Connection" HorizontalAlignment="Center" Text="Not connected" TextWrapping="Wrap" VerticalAlignment="Center" TextAlignment="Center" MinWidth="350" Foreground="{DynamicResource {x:Static SystemColors.ControlDarkDarkBrushKey}}"/>
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,10,0">
|
||||
<Button x:Name="Shuffle" Padding="5, 2" HorizontalAlignment="Right" Margin="0,0,10,0" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<emoji:TextBlock Text="🔁"/>
|
||||
<TextBlock Text="Shuffle" Margin="5, 0, 0, 0"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button x:Name="Settings" Padding="5, 2" Click="Settings_Clicked" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<emoji:TextBlock Text="🛠️"/>
|
||||
<TextBlock Text="Settings" Margin="5, 0, 0, 0"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Image Margin="0,0,503,127" Stretch="Fill" Source="pack://siteoforigin:,,,/Image1.png"/>
|
||||
</Grid>
|
||||
</Window>
|
241
Views/MainWindow.xaml.cs
Normal file
241
Views/MainWindow.xaml.cs
Normal file
@ -0,0 +1,241 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Threading;
|
||||
using MPDCtrl.Models;
|
||||
using System.Windows.Interop;
|
||||
using unison.Views;
|
||||
|
||||
namespace unison
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private readonly MPC _mpd = new();
|
||||
private bool _connected;
|
||||
public int _currentVolume;
|
||||
private bool _currentRandom;
|
||||
private bool _currentRepeat;
|
||||
private bool _currentSingle;
|
||||
private bool _currentConsume;
|
||||
private double _currentElapsed;
|
||||
|
||||
private string _mpdHost = "192.168.1.13";
|
||||
private int _mpdPort = 6600;
|
||||
private string _mpdPassword = null;
|
||||
|
||||
Settings SettingsWindow = new Settings();
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitHwnd();
|
||||
InitializeComponent();
|
||||
|
||||
WindowState = WindowState.Minimized;
|
||||
|
||||
ConnectToMPD();
|
||||
DispatcherTimer timer = new DispatcherTimer();
|
||||
timer.Interval = TimeSpan.FromSeconds(0.2);
|
||||
timer.Tick += Timer_Tick;
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
public async void ConnectToMPD()
|
||||
{
|
||||
Trace.WriteLine("Trying to connect...");
|
||||
_connected = await _mpd.MpdCommandConnectionStart(_mpdHost, _mpdPort, _mpdPassword);
|
||||
if (_connected)
|
||||
{
|
||||
await _mpd.MpdQueryStatus();
|
||||
await Task.Delay(5);
|
||||
_currentVolume = _mpd.MpdStatus.MpdVolume;
|
||||
_currentRandom = _mpd.MpdStatus.MpdRandom;
|
||||
_currentRepeat = _mpd.MpdStatus.MpdRepeat;
|
||||
_currentSingle = _mpd.MpdStatus.MpdSingle;
|
||||
_currentConsume = _mpd.MpdStatus.MpdConsume;
|
||||
_currentElapsed = _mpd.MpdStatus.MpdSongElapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void Timer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
LoopMPD();
|
||||
UpdateInterface();
|
||||
}
|
||||
|
||||
public void CheckStatus<T>(ref T a, T b)
|
||||
{
|
||||
if (Comparer<T>.Default.Compare(a, b) != 0)
|
||||
a = b;
|
||||
}
|
||||
|
||||
public async void LoopMPD()
|
||||
{
|
||||
if (!_connected)
|
||||
return;
|
||||
|
||||
var status = await _mpd.MpdQueryStatus();
|
||||
//Trace.WriteLine(status.ResultText);
|
||||
await Task.Delay(5);
|
||||
|
||||
if (status != null)
|
||||
{
|
||||
CheckStatus(ref _currentVolume, _mpd.MpdStatus.MpdVolume);
|
||||
CheckStatus(ref _currentRandom, _mpd.MpdStatus.MpdRandom);
|
||||
CheckStatus(ref _currentRepeat, _mpd.MpdStatus.MpdRepeat);
|
||||
CheckStatus(ref _currentSingle, _mpd.MpdStatus.MpdSingle);
|
||||
CheckStatus(ref _currentConsume, _mpd.MpdStatus.MpdConsume);
|
||||
CheckStatus(ref _currentElapsed, _mpd.MpdStatus.MpdSongElapsed);
|
||||
}
|
||||
|
||||
await _mpd.MpdQueryCurrentSong();
|
||||
await Task.Delay(5);
|
||||
}
|
||||
|
||||
public void UpdateButton(ref Button button, bool b)
|
||||
{
|
||||
if (b)
|
||||
button.Foreground = SystemColors.GradientActiveCaptionBrush;
|
||||
else
|
||||
button.Foreground = SystemColors.DesktopBrush;
|
||||
}
|
||||
|
||||
public string FormatSeconds(double time)
|
||||
{
|
||||
TimeSpan timespan = TimeSpan.FromSeconds(time);
|
||||
return timespan.ToString(@"mm\:ss");
|
||||
}
|
||||
|
||||
public void UpdateInterface()
|
||||
{
|
||||
if (_mpd.MpdCurrentSong != null)
|
||||
{
|
||||
SongTitle.Text = _mpd.MpdCurrentSong.Title;
|
||||
SongTitle.ToolTip = _mpd.MpdCurrentSong.File;
|
||||
SongArtist.Text = _mpd.MpdCurrentSong.Artist;
|
||||
SongAlbum.Text = _mpd.MpdCurrentSong.Album + " (" + _mpd.MpdCurrentSong.Date + ")";
|
||||
Bitrate.Text = _mpd.MpdCurrentSong.File.Substring(_mpd.MpdCurrentSong.File.LastIndexOf(".") + 1);
|
||||
Bitrate.Text += " – ";
|
||||
Bitrate.Text += _mpd.MpdStatus.MpdBitrate + "kbps";
|
||||
|
||||
CurrentTime.Text = FormatSeconds(_currentElapsed);
|
||||
EndTime.Text = FormatSeconds(_mpd.MpdStatus.MpdSongTime);
|
||||
|
||||
TimeSlider.Value = _currentElapsed / _mpd.MpdCurrentSong.TimeSort * 100;
|
||||
}
|
||||
|
||||
if (VolumeSlider.Value != _currentVolume)
|
||||
{
|
||||
VolumeSlider.Value = _currentVolume;
|
||||
VolumeSlider.ToolTip = _currentVolume;
|
||||
}
|
||||
|
||||
if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Play)
|
||||
PauseButtonEmoji.Text = "⏸️";
|
||||
else if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Pause)
|
||||
PauseButtonEmoji.Text = "▶️";
|
||||
|
||||
SnapcastHandler snapcast = (SnapcastHandler)Application.Current.Properties["snapcast"];
|
||||
if (snapcast.Started)
|
||||
SnapcastText.Text = "Stop Snapcast";
|
||||
else
|
||||
SnapcastText.Text = "Start Snapcast";
|
||||
|
||||
Connection.Text = (_connected ? "✔️" : "❌") + _mpd.MpdHost + ":" + _mpd.MpdPort;
|
||||
|
||||
UpdateButton(ref Random, _currentRandom);
|
||||
UpdateButton(ref Repeat, _currentRepeat);
|
||||
UpdateButton(ref Single, _currentSingle);
|
||||
UpdateButton(ref Consume, _currentConsume);
|
||||
}
|
||||
|
||||
public async void Pause_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Play)
|
||||
await _mpd.MpdPlaybackPause();
|
||||
else if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Pause)
|
||||
await _mpd.MpdPlaybackPlay(_currentVolume);
|
||||
}
|
||||
|
||||
public async void Previous_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await _mpd.MpdPlaybackPrev(_currentVolume);
|
||||
}
|
||||
|
||||
public async void Next_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await _mpd.MpdPlaybackNext(_currentVolume);
|
||||
}
|
||||
|
||||
public async void Random_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await _mpd.MpdSetRandom(!_currentRandom);
|
||||
}
|
||||
|
||||
private async void Repeat_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await _mpd.MpdSetRepeat(!_currentRepeat);
|
||||
}
|
||||
|
||||
private async void Single_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await _mpd.MpdSetSingle(!_currentSingle);
|
||||
}
|
||||
|
||||
private async void Consume_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await _mpd.MpdSetConsume(!_currentConsume);
|
||||
}
|
||||
|
||||
public async void ChangeVolume(int value)
|
||||
{
|
||||
await _mpd.MpdSetVolume(value);
|
||||
}
|
||||
|
||||
public void Snapcast_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SnapcastHandler snapcast = (SnapcastHandler)Application.Current.Properties["snapcast"];
|
||||
if (!snapcast.Started)
|
||||
snapcast.Start(_mpd.MpdHost);
|
||||
else
|
||||
snapcast.Stop();
|
||||
}
|
||||
|
||||
public void Settings_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (SettingsWindow.WindowState == WindowState.Normal)
|
||||
{
|
||||
SettingsWindow.Hide();
|
||||
SettingsWindow.WindowState = WindowState.Minimized;
|
||||
}
|
||||
else if (SettingsWindow.WindowState == WindowState.Minimized)
|
||||
{
|
||||
SettingsWindow.WindowState = WindowState.Normal;
|
||||
SettingsWindow.Show();
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_Closing(object sender, CancelEventArgs e)
|
||||
{
|
||||
e.Cancel = true;
|
||||
WindowState = WindowState.Minimized;
|
||||
Hide();
|
||||
}
|
||||
|
||||
protected override void OnSourceInitialized(EventArgs e)
|
||||
{
|
||||
base.OnSourceInitialized(e);
|
||||
HotkeyHandler hk = (HotkeyHandler)Application.Current.Properties["hotkeys"];
|
||||
hk.Activate(this);
|
||||
}
|
||||
|
||||
public void InitHwnd()
|
||||
{
|
||||
WindowInteropHelper helper = new(this);
|
||||
helper.EnsureHandle();
|
||||
}
|
||||
}
|
||||
}
|
118
Views/Settings.xaml
Normal file
118
Views/Settings.xaml
Normal file
@ -0,0 +1,118 @@
|
||||
<Window x:Class="unison.Views.Settings"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:emoji="clr-namespace:Emoji.Wpf;assembly=Emoji.Wpf"
|
||||
xmlns:local="clr-namespace:unison.Views"
|
||||
mc:Ignorable="d"
|
||||
Closing="Window_Closing" Title="Settings" ResizeMode="CanMinimize" Icon="/unison.ico" WindowStyle="ToolWindow" SizeToContent="WidthAndHeight">
|
||||
<Grid>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TabControl Margin="10">
|
||||
<TabItem Header="MPD">
|
||||
<DockPanel Margin="8">
|
||||
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
||||
<GroupBox.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<emoji:TextBlock Text="📶 Connection"/>
|
||||
</StackPanel>
|
||||
</GroupBox.Header>
|
||||
<Grid VerticalAlignment="Top">
|
||||
<StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Text="Host" TextWrapping="Wrap" Margin="5,0,0,0"/>
|
||||
<TextBox Text="192.168.0.1" TextWrapping="Wrap" Width="250" Margin="10,2,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="0,5,0,0">
|
||||
<TextBlock Text="Port" TextWrapping="Wrap" Margin="5,0,0,0"/>
|
||||
<TextBox Text="6600" TextWrapping="Wrap" Width="250" Margin="10,2,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="0,5,0,0">
|
||||
<TextBlock Text="Password" TextWrapping="Wrap" Margin="5,0,0,0"/>
|
||||
<TextBox Text="" TextWrapping="Wrap" Width="250" Margin="10,2,0,0"/>
|
||||
</StackPanel>
|
||||
<Button Content="Connect" Margin="0,10,0,0" Width="120"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="Snapcast">
|
||||
<DockPanel Margin="8">
|
||||
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
||||
<GroupBox.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<emoji:TextBlock Text="🔊 Snapcast"/>
|
||||
</StackPanel>
|
||||
</GroupBox.Header>
|
||||
<Grid VerticalAlignment="Top">
|
||||
<StackPanel>
|
||||
<StackPanel>
|
||||
<CheckBox Margin="5, 5, 0, 0">
|
||||
<TextBlock Text="Launch at startup" TextWrapping="Wrap"/>
|
||||
</CheckBox>
|
||||
<TextBlock Text="Executable path" TextWrapping="Wrap" Margin="5,5,0,0"/>
|
||||
<TextBox Text="snapclient_0.25.0-1_win64" TextWrapping="Wrap" Width="250" Margin="10,2,0,0"/>
|
||||
<TextBlock TextWrapping="Wrap" Margin="5,5,0,0" TextAlignment="Left" Width="250">
|
||||
You can change to your own locally installed version of the Snapcast client with an <Run FontStyle="Italic" FontWeight="DemiBold">absolute</Run> path.
|
||||
</TextBlock>
|
||||
<Button Content="Reset" Margin="0,10,0,0" Width="120"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="About" Height="20" VerticalAlignment="Bottom">
|
||||
<DockPanel Margin="8">
|
||||
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
||||
<GroupBox.Header>
|
||||
<emoji:TextBlock Text="🎶 unison"/>
|
||||
</GroupBox.Header>
|
||||
<Grid VerticalAlignment="Top">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock TextWrapping="Wrap" Margin="0,0,0,10" VerticalAlignment="Top">
|
||||
<Run Text="Version:"/>
|
||||
<Run Text="{Binding GetVersion, Mode = OneWay}"/>
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top">
|
||||
unison is free software. It is built with the following technologies:<LineBreak/>
|
||||
※ <Hyperlink NavigateUri="https://torum.github.io/MPDCtrl/" RequestNavigate="Hyperlink_RequestNavigate">MPDCtrl</Hyperlink><LineBreak/>
|
||||
※ <Hyperlink NavigateUri="https://github.com/hardcodet/wpf-notifyicon" RequestNavigate="Hyperlink_RequestNavigate">wpf-notifyicon</Hyperlink><LineBreak/>
|
||||
※ <Hyperlink NavigateUri="https://github.com/samhocevar/emoji.wpf" RequestNavigate="Hyperlink_RequestNavigate">Emoji.WPF</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Margin="0,10,0,0">
|
||||
Source code freely available
|
||||
<Hyperlink NavigateUri="https://git.n700.ovh/keb/unison" RequestNavigate="Hyperlink_RequestNavigate">
|
||||
here
|
||||
</Hyperlink>.
|
||||
</TextBlock>
|
||||
<TextBlock Margin="0,10,0,0">
|
||||
Made by
|
||||
<Hyperlink NavigateUri="https://marchal.dev" RequestNavigate="Hyperlink_RequestNavigate">
|
||||
Théo Marchal
|
||||
</Hyperlink>.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0" Margin="0,10,0,0">
|
||||
<GroupBox.Header>
|
||||
<emoji:TextBlock Text="📝 License"/>
|
||||
</GroupBox.Header>
|
||||
<Grid VerticalAlignment="Top">
|
||||
<TextBlock Text="{Binding GetLicense, Mode = OneWay}" TextWrapping="Wrap" Width="400" TextAlignment="Justify" />
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
55
Views/Settings.xaml.cs
Normal file
55
Views/Settings.xaml.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Windows.Navigation;
|
||||
|
||||
namespace unison.Views
|
||||
{
|
||||
public partial class Settings : Window
|
||||
{
|
||||
public string GetVersion => Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||
|
||||
public Settings()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
|
||||
WindowState = WindowState.Minimized;
|
||||
}
|
||||
|
||||
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
|
||||
{
|
||||
ProcessStartInfo psi = new(e.Uri.AbsoluteUri);
|
||||
psi.UseShellExecute = true;
|
||||
Process.Start(psi);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void Window_Closing(object sender, CancelEventArgs e)
|
||||
{
|
||||
e.Cancel = true;
|
||||
WindowState = WindowState.Minimized;
|
||||
Hide();
|
||||
}
|
||||
|
||||
public string GetLicense
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
var Reader = new StreamReader("LICENSE");
|
||||
string file = "";
|
||||
file = file + Reader.ReadToEnd();
|
||||
return file;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
return e.Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
53
Views/Systray.xaml
Normal file
53
Views/Systray.xaml
Normal file
@ -0,0 +1,53 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:tb="http://www.hardcodet.net/taskbar"
|
||||
xmlns:local="clr-namespace:unison">
|
||||
|
||||
<tb:TaskbarIcon x:Name="SystrayTaskbar" xmlns:emoji="clr-namespace:Emoji.Wpf;assembly=Emoji.Wpf"
|
||||
IconSource="/unison.ico" ToolTipText="{Binding GetAppText}" DoubleClickCommand="{Binding ShowWindowCommand}" x:Key="SystrayTaskbar">
|
||||
<tb:TaskbarIcon.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem IsEnabled="False">
|
||||
<MenuItem.Icon>
|
||||
<Image Source="/unison.ico" Width="16" Height="16"/>
|
||||
</MenuItem.Icon>
|
||||
<MenuItem.Header>
|
||||
<TextBlock Text="{Binding GetAppText}" />
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Show window" Command="{Binding ShowWindowCommand}">
|
||||
<MenuItem.Icon>
|
||||
<Image Width="16" Height="16" emoji:Image.Source="▶️" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Name="test_snapcast_item" Header="{Binding SnapcastText}" Command="{Binding Snapcast}">
|
||||
<MenuItem.Icon>
|
||||
<Image Width="16" Height="16" emoji:Image.Source="🔊" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<!--<MenuItem Header="Shuffle" Command="{Binding Shuffle}">
|
||||
<MenuItem.Icon>
|
||||
<Image Width="16" Height="16" emoji:Image.Source="🔀" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>-->
|
||||
<MenuItem Header="Settings" Command="{Binding Settings}">
|
||||
<MenuItem.Icon>
|
||||
<Image Width="16" Height="16" emoji:Image.Source="🛠️" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Exit" Command="{Binding ExitApplicationCommand}">
|
||||
<MenuItem.Icon>
|
||||
<Image Width="16" Height="16" emoji:Image.Source="❌" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
</tb:TaskbarIcon.ContextMenu>
|
||||
<tb:TaskbarIcon.DataContext>
|
||||
<local:NotifyIconViewModel />
|
||||
</tb:TaskbarIcon.DataContext>
|
||||
</tb:TaskbarIcon>
|
||||
|
||||
</ResourceDictionary>
|
23
Views/Systray.xaml.cs
Normal file
23
Views/Systray.xaml.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
/*namespace unison
|
||||
{
|
||||
public partial class Systray : ResourceDictionary, INotifyPropertyChanged
|
||||
{
|
||||
public Systray()
|
||||
{
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
private void NotifyPropertyChanged(string propertyName = "")
|
||||
{
|
||||
if (PropertyChanged != null)
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}*/
|
Reference in New Issue
Block a user