mirror of
https://github.com/BililiveRecorder/BililiveRecorder.git
synced 2024-11-16 03:32:20 +08:00
部分 UI 修改
This commit is contained in:
parent
412cf269ac
commit
3dc6ace0cc
|
@ -1,4 +1,4 @@
|
|||
using BililiveRecorder.FlvProcessor;
|
||||
using BililiveRecorder.FlvProcessor;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
|
@ -6,6 +6,8 @@ namespace BililiveRecorder.Core
|
|||
{
|
||||
public interface IRecordedRoom : INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
Guid Guid { get; }
|
||||
|
||||
int ShortRoomId { get; }
|
||||
int RoomId { get; }
|
||||
string StreamerName { get; }
|
||||
|
|
|
@ -112,6 +112,8 @@ namespace BililiveRecorder.Core
|
|||
private set { if (value != _DownloadSpeedMegaBitps) { _DownloadSpeedMegaBitps = value; TriggerPropertyChanged(nameof(DownloadSpeedMegaBitps)); } }
|
||||
}
|
||||
|
||||
public Guid Guid { get; } = Guid.NewGuid();
|
||||
|
||||
public RecordedRoom(ConfigV1 config,
|
||||
IBasicDanmakuWriter basicDanmakuWriter,
|
||||
Func<int, IStreamMonitor> newIStreamMonitor,
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.ColumnSpan="3" VerticalAlignment="Center" Margin="5,0,0,0"
|
||||
Style="{DynamicResource SubtitleTextBlockStyle}"
|
||||
FontFamily="Microsoft Yahei"
|
||||
TextWrapping="NoWrap" TextTrimming="CharacterEllipsis"
|
||||
Text="{Binding StreamerName,Mode=OneWay}"
|
||||
ToolTip="{Binding StreamerName,Mode=OneWay}"/>
|
||||
|
|
|
@ -41,6 +41,8 @@ namespace BililiveRecorder.WPF.MockData
|
|||
|
||||
public DateTime LastUpdateDateTime { get; set; }
|
||||
|
||||
public Guid Guid { get; } = Guid.NewGuid();
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public void Clip()
|
||||
|
|
|
@ -60,7 +60,6 @@ namespace BililiveRecorder.WPF.MockData
|
|||
{
|
||||
DownloadSpeedPersentage = 109
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private ObservableCollection<IRecordedRoom> Rooms { get; } = new ObservableCollection<IRecordedRoom>();
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
Foreground="{DynamicResource SystemControlPageTextBaseHighBrush}"
|
||||
ui:ThemeManager.IsThemeAware="True"
|
||||
ui:WindowHelper.UseModernWindowStyle="True"
|
||||
ui:TitleBar.IsBackEnabled="False"
|
||||
ui:TitleBar.IsBackButtonVisible="False"
|
||||
ui:TitleBar.IsIconVisible="True"
|
||||
Width="1000" Height="650"
|
||||
MinHeight="400" MinWidth="340"
|
||||
Closing="Window_Closing"
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
<controls:RoomCard DeleteRequested="RoomCard_DeleteRequested" />
|
||||
</ui:ThemeShadowChrome>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="AddRoomCardTemplate">
|
||||
<ui:ThemeShadowChrome IsShadowEnabled="True" Depth="10">
|
||||
<controls:AddRoomCard AddRoomRequested="AddRoomCard_AddRoomRequested"/>
|
||||
|
@ -38,13 +37,71 @@
|
|||
MinColumnSpacing="5" />
|
||||
<converters:RoomListInterceptConverter x:Key="RoomListInterceptConverter"/>
|
||||
</ui:Page.Resources>
|
||||
<ui:ItemsRepeaterScrollHost>
|
||||
<ScrollViewer>
|
||||
<ui:ItemsRepeater
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition x:Name="RoomListRowDefinition" Height="*"/>
|
||||
<RowDefinition Height="3"/>
|
||||
<RowDefinition x:Name="LogRowDefinition" Height="0"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Menu>
|
||||
<Separator/>
|
||||
<MenuItem Header="自动录制">
|
||||
<MenuItem Header="自动录制全部房间" Click="MenuItem_EnableAutoRecAll_Click"/>
|
||||
<MenuItem Header="取消自动录制全部房间" Click="MenuItem_DisableAutoRecAll_Click"/>
|
||||
</MenuItem>
|
||||
<MenuItem Header="排序">
|
||||
<MenuItem Header="房间号" Tag="{x:Static local:SortedBy.RoomId}" Click="MenuItem_SortBy_Click"/>
|
||||
<MenuItem Header="录制状态" Tag="{x:Static local:SortedBy.Status}" Click="MenuItem_SortBy_Click"/>
|
||||
</MenuItem>
|
||||
<MenuItem Header="日志">
|
||||
<MenuItem Header="在本页显示日志" Click="MenuItem_ShowLog_Click"/>
|
||||
<MenuItem Header="不在本页显示日志" Click="MenuItem_HideLog_Click"/>
|
||||
</MenuItem>
|
||||
<Separator/>
|
||||
<MenuItem Header="关于">
|
||||
<MenuItem Header="打开官网"/>
|
||||
<MenuItem Header="关于"/>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<ui:ItemsRepeaterScrollHost Grid.Row="1">
|
||||
<ScrollViewer>
|
||||
<ui:ItemsRepeater
|
||||
HorizontalAlignment="Stretch" Margin="8"
|
||||
Layout="{StaticResource UniformGridLayout}"
|
||||
ItemsSource="{Binding Converter={StaticResource RoomListInterceptConverter},Mode=OneWay}"
|
||||
ItemsSource="{Binding SortedRoomList, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ui:Page}}"
|
||||
ItemTemplate="{StaticResource SelectorTemplate}" />
|
||||
</ScrollViewer>
|
||||
</ui:ItemsRepeaterScrollHost>
|
||||
</ScrollViewer>
|
||||
</ui:ItemsRepeaterScrollHost>
|
||||
<GridSplitter Grid.Row="2" x:Name="Splitter" ResizeDirection="Rows" Visibility="Collapsed"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ShowsPreview="True"/>
|
||||
<ui:ThemeShadowChrome Grid.Row="3" x:Name="LogElement" IsShadowEnabled="True" Depth="10" Margin="5" Visibility="Collapsed"
|
||||
DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:RootPage},Path=DataContext.Logs,Mode=OneWay}">
|
||||
<Border Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
||||
BorderBrush="{DynamicResource SystemControlBackgroundAccentBrush}"
|
||||
BorderThickness="1" CornerRadius="5">
|
||||
<ItemsControl ItemsSource="{Binding Mode=OneWay}" Margin="5" ToolTip="右键点击可以复制单行日志">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border BorderThickness="0,0,0,1" BorderBrush="#FFCCCCCC">
|
||||
<TextBlock Text="{Binding Mode=OneWay}" TextWrapping="Wrap" MouseRightButtonUp="TextBlock_Copy_MouseRightButtonUp"/>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<ItemsControl.Template>
|
||||
<ControlTemplate>
|
||||
<ScrollViewer Loaded="Log_ScrollViewer_Loaded" CanContentScroll="True">
|
||||
<ItemsPresenter/>
|
||||
</ScrollViewer>
|
||||
</ControlTemplate>
|
||||
</ItemsControl.Template>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel IsItemsHost="True"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</Border>
|
||||
</ui:ThemeShadowChrome>
|
||||
</Grid>
|
||||
</ui:Page>
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using BililiveRecorder.Core;
|
||||
using BililiveRecorder.WPF.Controls;
|
||||
using ModernWpf.Controls;
|
||||
|
||||
namespace BililiveRecorder.WPF.Pages
|
||||
{
|
||||
|
@ -18,6 +25,32 @@ namespace BililiveRecorder.WPF.Pages
|
|||
public RoomListPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
SortedRoomList = new SortedItemsSourceView(DataContext);
|
||||
DataContextChanged += RoomListPage_DataContextChanged;
|
||||
}
|
||||
|
||||
private void RoomListPage_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
(SortedRoomList as SortedItemsSourceView).Data = e.NewValue as ICollection<IRecordedRoom>;
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SortedRoomListProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(SortedRoomList),
|
||||
typeof(object),
|
||||
typeof(RoomListPage),
|
||||
new PropertyMetadata(OnPropertyChanged));
|
||||
|
||||
public object SortedRoomList
|
||||
{
|
||||
get => GetValue(SortedRoomListProperty);
|
||||
set => SetValue(SortedRoomListProperty, value);
|
||||
}
|
||||
|
||||
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private async void RoomCard_DeleteRequested(object sender, EventArgs e)
|
||||
|
@ -34,6 +67,7 @@ namespace BililiveRecorder.WPF.Pages
|
|||
if (result == ModernWpf.Controls.ContentDialogResult.Primary)
|
||||
{
|
||||
rec.RemoveRoom(room);
|
||||
rec.SaveConfigToFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,6 +109,241 @@ namespace BililiveRecorder.WPF.Pages
|
|||
}
|
||||
|
||||
rec.AddRoom(roomid);
|
||||
rec.SaveConfigToFile();
|
||||
}
|
||||
|
||||
private async void MenuItem_EnableAutoRecAll_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!(DataContext is IRecorder rec)) return;
|
||||
|
||||
await Task.WhenAll(rec.ToList().Select(rr => Task.Run(() => rr.Start())));
|
||||
rec.SaveConfigToFile();
|
||||
}
|
||||
|
||||
private async void MenuItem_DisableAutoRecAll_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!(DataContext is IRecorder rec)) return;
|
||||
|
||||
await Task.WhenAll(rec.ToList().Select(rr => Task.Run(() => rr.Stop())));
|
||||
rec.SaveConfigToFile();
|
||||
}
|
||||
|
||||
private void MenuItem_SortBy_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
(SortedRoomList as SortedItemsSourceView).SortedBy = (SortedBy)((MenuItem)sender).Tag;
|
||||
}
|
||||
|
||||
private void MenuItem_ShowLog_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Splitter.Visibility = Visibility.Visible;
|
||||
LogElement.Visibility = Visibility.Visible;
|
||||
RoomListRowDefinition.Height = new GridLength(1, GridUnitType.Star);
|
||||
LogRowDefinition.Height = new GridLength(1, GridUnitType.Star);
|
||||
}
|
||||
|
||||
private void MenuItem_HideLog_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Splitter.Visibility = Visibility.Collapsed;
|
||||
LogElement.Visibility = Visibility.Collapsed;
|
||||
RoomListRowDefinition.Height = new GridLength(1, GridUnitType.Star);
|
||||
LogRowDefinition.Height = new GridLength(0);
|
||||
}
|
||||
|
||||
private void Log_ScrollViewer_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
(sender as ScrollViewer)?.ScrollToEnd();
|
||||
}
|
||||
|
||||
private void TextBlock_Copy_MouseRightButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (sender is TextBlock textBlock)
|
||||
{
|
||||
Clipboard.SetText(textBlock.Text);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal enum SortedBy
|
||||
{
|
||||
None = 0,
|
||||
RoomId,
|
||||
Status,
|
||||
}
|
||||
|
||||
internal class SortedItemsSourceView : IList, IReadOnlyList<IRecordedRoom>, IKeyIndexMapping, INotifyCollectionChanged
|
||||
{
|
||||
private ICollection<IRecordedRoom> _data;
|
||||
private SortedBy sortedBy;
|
||||
|
||||
private readonly IRecordedRoom[] NullRoom = new IRecordedRoom[] { null };
|
||||
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
public SortedItemsSourceView(object data)
|
||||
{
|
||||
if (data is not null)
|
||||
{
|
||||
if (data is IList<IRecordedRoom> list)
|
||||
{
|
||||
if (list is INotifyCollectionChanged n) n.CollectionChanged += Data_CollectionChanged;
|
||||
_data = list;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Type not supported.", nameof(data));
|
||||
}
|
||||
}
|
||||
Sort();
|
||||
}
|
||||
|
||||
private void Data_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => Sort();
|
||||
|
||||
public ICollection<IRecordedRoom> Data
|
||||
{
|
||||
get => _data;
|
||||
set
|
||||
{
|
||||
if (_data is INotifyCollectionChanged n1) n1.CollectionChanged -= Data_CollectionChanged;
|
||||
if (value is INotifyCollectionChanged n2) n2.CollectionChanged += Data_CollectionChanged;
|
||||
_data = value;
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
public SortedBy SortedBy { get => sortedBy; set { sortedBy = value; Sort(); } }
|
||||
|
||||
public List<IRecordedRoom> Sorted { get; private set; }
|
||||
|
||||
private void Sort()
|
||||
{
|
||||
if (Data is null)
|
||||
{
|
||||
Sorted = NullRoom.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
IEnumerable<IRecordedRoom> orderedData = SortedBy switch
|
||||
{
|
||||
SortedBy.RoomId => Data.OrderBy(x => x.ShortRoomId == 0 ? x.RoomId : x.ShortRoomId),
|
||||
SortedBy.Status => Data.OrderBy(x => x.IsRecording).ThenBy(x => x.IsMonitoring),
|
||||
_ => Data,
|
||||
};
|
||||
Sorted = orderedData.Concat(NullRoom).ToList();
|
||||
}
|
||||
|
||||
// Instead of tossing out existing elements and re-creating them,
|
||||
// ItemsRepeater will reuse the existing elements and match them up
|
||||
// with the data again.
|
||||
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
public IRecordedRoom this[int index] => Sorted != null ? Sorted[index] : throw new IndexOutOfRangeException();
|
||||
public int Count => Sorted != null ? Sorted.Count : 0;
|
||||
|
||||
public bool IsReadOnly => ((IList)Sorted).IsReadOnly;
|
||||
|
||||
public bool IsFixedSize => ((IList)Sorted).IsFixedSize;
|
||||
|
||||
public object SyncRoot => ((ICollection)Sorted).SyncRoot;
|
||||
|
||||
public bool IsSynchronized => ((ICollection)Sorted).IsSynchronized;
|
||||
|
||||
object IList.this[int index] { get => ((IList)Sorted)[index]; set => ((IList)Sorted)[index] = value; }
|
||||
|
||||
public IEnumerator<IRecordedRoom> GetEnumerator() => Sorted.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
#region IKeyIndexMapping
|
||||
|
||||
private int lastRequestedIndex = IndexNotFound;
|
||||
private const int IndexNotFound = -1;
|
||||
|
||||
// When UniqueIDs are supported, the ItemsRepeater caches the unique ID for each item
|
||||
// with the matching UIElement that represents the item. When a reset occurs the
|
||||
// ItemsRepeater pairs up the already generated UIElements with items in the data
|
||||
// source.
|
||||
// ItemsRepeater uses IndexForUniqueId after a reset to probe the data and identify
|
||||
// the new index of an item to use as the anchor. If that item no
|
||||
// longer exists in the data source it may try using another cached unique ID until
|
||||
// either a match is found or it determines that all the previously visible items
|
||||
// no longer exist.
|
||||
public int IndexFromKey(string uniqueId)
|
||||
{
|
||||
// We'll try to increase our odds of finding a match sooner by starting from the
|
||||
// position that we know was last requested and search forward.
|
||||
var start = lastRequestedIndex;
|
||||
for (var i = start; i < Count; i++)
|
||||
{
|
||||
if ((this[i]?.Guid ?? Guid.Empty).Equals(uniqueId))
|
||||
return i;
|
||||
}
|
||||
|
||||
// Then try searching backward.
|
||||
start = Math.Min(Count - 1, lastRequestedIndex);
|
||||
for (var i = start; i >= 0; i--)
|
||||
{
|
||||
if ((this[i]?.Guid ?? Guid.Empty).Equals(uniqueId))
|
||||
return i;
|
||||
}
|
||||
|
||||
return IndexNotFound;
|
||||
}
|
||||
|
||||
public string KeyFromIndex(int index)
|
||||
{
|
||||
var key = this[index]?.Guid ?? Guid.Empty;
|
||||
lastRequestedIndex = index;
|
||||
return key.ToString();
|
||||
}
|
||||
|
||||
public int Add(object value)
|
||||
{
|
||||
return ((IList)Sorted).Add(value);
|
||||
}
|
||||
|
||||
public bool Contains(object value)
|
||||
{
|
||||
return ((IList)Sorted).Contains(value);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
((IList)Sorted).Clear();
|
||||
}
|
||||
|
||||
public int IndexOf(object value)
|
||||
{
|
||||
return ((IList)Sorted).IndexOf(value);
|
||||
}
|
||||
|
||||
public void Insert(int index, object value)
|
||||
{
|
||||
((IList)Sorted).Insert(index, value);
|
||||
}
|
||||
|
||||
public void Remove(object value)
|
||||
{
|
||||
((IList)Sorted).Remove(value);
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
((IList)Sorted).RemoveAt(index);
|
||||
}
|
||||
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
((ICollection)Sorted).CopyTo(array, index);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user