too many to add

This commit is contained in:
TheXamlGuy
2024-01-29 22:03:36 +00:00
parent 3917639e8d
commit c546ca2343
44 changed files with 573 additions and 94 deletions
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Application
x:Class="App1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<!-- Other app resources here -->
</ResourceDictionary>
</Application.Resources>
</Application>
+50
View File
@@ -0,0 +1,50 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Microsoft.UI.Xaml.Shapes;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace App1
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class App : Application
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
}
/// <summary>
/// Invoked when the application is launched.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
m_window = new MainWindow();
m_window.Activate();
}
private Window m_window;
}
}
+48
View File
@@ -0,0 +1,48 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>App1</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Platforms>x86;x64;ARM64</Platforms>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<PublishProfile>win10-$(Platform).pubxml</PublishProfile>
<UseWinUI>true</UseWinUI>
<EnableMsixTooling>true</EnableMsixTooling>
</PropertyGroup>
<ItemGroup>
<Content Include="Assets\SplashScreen.scale-200.png" />
<Content Include="Assets\LockScreenLogo.scale-200.png" />
<Content Include="Assets\Square150x150Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Assets\StoreLogo.png" />
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.230913002" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.755" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<!--
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
Tools extension to be activated for this project even if the Windows App SDK Nuget
package has not yet been restored.
-->
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<ProjectCapability Include="Msix"/>
</ItemGroup>
<!--
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
Explorer "Package and Publish" context menu entry to be enabled for this project even if
the Windows App SDK Nuget package has not yet been restored.
-->
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
</PropertyGroup>
</Project>
Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

+43
View File
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8" ?>
<Window
x:Class="App1.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:local="using:App1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<FlipView Width="400">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Green">
<Grid>
<ItemsControl>
<Button />
<Button />
<Button />
<Button />
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</Grid>
<Grid HorizontalAlignment="Right" Background="Green">
<Grid>
<ItemsControl>
<Button />
<Button />
<Button />
<Button />
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</Grid>
</FlipView>
</Window>
+31
View File
@@ -0,0 +1,31 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace App1
{
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
}
}
+51
View File
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap">
<Identity
Name="f69ee404-ac85-43ce-8664-73aeecf48925"
Publisher="CN=dan_c"
Version="1.0.0.0" />
<mp:PhoneIdentity PhoneProductId="f69ee404-ac85-43ce-8664-73aeecf48925" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>App1</DisplayName>
<PublisherDisplayName>dan_c</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="App1"
Description="App1"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>
+10
View File
@@ -0,0 +1,10 @@
{
"profiles": {
"App1 (Package)": {
"commandName": "MsixPackage"
},
"App1 (Unpackaged)": {
"commandName": "Project"
}
}
}
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="App1.app"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- The ID below informs the system that this application is compatible with OS features first introduced in Windows 10.
It is necessary to support features in unpackaged applications, for example the custom titlebar implementation.
For more info see https://docs.microsoft.com/windows/apps/windows-app-sdk/use-windows-app-sdk-run-time#declare-os-compatibility-in-your-application-manifest -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
</assembly>
@@ -0,0 +1,27 @@
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Windows.Storage.Streams;
namespace Hyperbar.UI.Windows;
public class StreamToImageSourceConverter :
ValueConverter<Stream, ImageSource>
{
protected override ImageSource? ConvertTo(Stream value,
Type? targetType,
object? parameter,
string? language)
{
if (value == null)
{
return default;
}
IRandomAccessStream randomAccessStream =
WindowsRuntimeStreamExtensions.AsRandomAccessStream(value);
BitmapImage bitmapImage = new();
bitmapImage.SetSource(randomAccessStream);
return bitmapImage;
}
}
+16 -6
View File
@@ -9,9 +9,14 @@ public class TemplateGenerator : DataTemplateSelector
protected override DataTemplate SelectTemplateCore(object item) protected override DataTemplate SelectTemplateCore(object item)
{ {
string xamlString = @" string xamlString = @"
<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' <DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:ui='using:Hyperbar.UI.Windows'> xmlns:ui=""using:Hyperbar.UI.Windows"">
<ui:TemplateGeneratorControl /> <Grid>
<ui:TemplateGeneratorControl VerticalContentAlignment=""Stretch""
HorizontalContentAlignment=""Stretch""
HorizontalAlignment=""Stretch""
VerticalAlignment=""Stretch""/>
</Grid>
</DataTemplate>"; </DataTemplate>";
return (DataTemplate)XamlReader.Load(xamlString); return (DataTemplate)XamlReader.Load(xamlString);
@@ -20,9 +25,14 @@ public class TemplateGenerator : DataTemplateSelector
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{ {
string xamlString = @" string xamlString = @"
<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' <DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:ui='using:Hyperbar.UI.Windows'> xmlns:ui=""using:Hyperbar.UI.Windows"">
<ui:TemplateGeneratorControl /> <Grid>
<ui:TemplateGeneratorControl VerticalContentAlignment=""Stretch""
HorizontalContentAlignment=""Stretch""
HorizontalAlignment=""Stretch""
VerticalAlignment=""Stretch""/>
</Grid>
</DataTemplate>"; </DataTemplate>";
return (DataTemplate)XamlReader.Load(xamlString); return (DataTemplate)XamlReader.Load(xamlString);
@@ -17,9 +17,5 @@ public class TemplateGeneratorControl :
{ {
Content = templatedViewModel.TemplateFactory.Create(DataContext.GetType().Name); Content = templatedViewModel.TemplateFactory.Create(DataContext.GetType().Name);
} }
else
{
}
} }
} }
@@ -4,8 +4,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:interactions="using:Microsoft.Xaml.Interactions.Core" xmlns:interactions="using:Microsoft.Xaml.Interactions.Core"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:interactivity="using:Microsoft.Xaml.Interactivity">
Visibility="{x:Bind ViewModel.Visible, Mode=OneWay}">
<UserControl.Resources> <UserControl.Resources>
<SolidColorBrush x:Key="ButtonBackground" Color="Transparent" /> <SolidColorBrush x:Key="ButtonBackground" Color="Transparent" />
<SolidColorBrush x:Key="ButtonBorderBrush" Color="Transparent" /> <SolidColorBrush x:Key="ButtonBorderBrush" Color="Transparent" />
@@ -1,39 +1,43 @@
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace Hyperbar.Widget.MediaController.Windows; namespace Hyperbar.Widget.MediaController.Windows;
public class MediaButtonViewModel(IServiceFactory serviceFactory, [NotificationHandler(nameof(PlaybackButtonType))]
public partial class MediaButtonViewModel(IServiceFactory serviceFactory,
IMediator mediator, IMediator mediator,
IDisposer disposer, IDisposer disposer,
ITemplateFactory templateFactory, ITemplateFactory templateFactory,
PlaybackButtonType buttonType, PlaybackButtonType playbackButtonType,
Guid guid = default, Guid guid = default,
string? text = null, string? text = null,
string? icon = null, string? icon = null,
RelayCommand? command = null) : RelayCommand? command = null) :
WidgetButtonViewModel(serviceFactory, mediator, disposer, templateFactory, guid, text, icon, command), WidgetButtonViewModel(serviceFactory, mediator, disposer, templateFactory, guid, text, icon, command),
IInitialization, IInitialization
INotificationHandler<Changed<PlaybackInformation>>
{ {
public Task Handle(Changed<PlaybackInformation> notification, [ObservableProperty]
private PlaybackButtonType playbackButtonType = playbackButtonType;
public Task Handle(Changed<MediaControllerPlaybackStatus> notification,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
if (notification.Value is PlaybackInformation information) if (notification.Value is MediaControllerPlaybackStatus information)
{ {
switch (buttonType) //switch (buttonType)
{ //{
case PlaybackButtonType.Play: // case PlaybackButtonType.Play:
Visible = information.Status is PlaybackStatus.Paused; // Visible = information.Status is PlaybackStatus.Paused;
break; // break;
case PlaybackButtonType.Pause: // case PlaybackButtonType.Pause:
Visible = information.Status is PlaybackStatus.Playing; // Visible = information.Status is PlaybackStatus.Playing;
break; // break;
} //}
} }
return Task.CompletedTask; return Task.CompletedTask;
} }
public override async Task InitializeAsync() => //public override async Task InitializeAsync() =>
await Mediator.PublishAsync<Request<PlaybackInformation>>(); // await Mediator.PublishAsync<Request<PlaybackInformation>>();
} }
@@ -1,18 +1,25 @@
using Windows.Media.Control; using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Windows.Graphics.Imaging;
using Windows.Media.Control;
using Windows.Storage.Streams;
namespace Hyperbar.Widget.MediaController.Windows; namespace Hyperbar.Widget.MediaController.Windows;
public class MediaController : public class MediaController :
INotificationHandler<Play>, INotificationHandler<Play>,
INotificationHandler<Pause>, INotificationHandler<Pause>,
INotificationHandler<Request<PlaybackInformation>>, INotificationHandler<Request<MediaControllerPlaybackStatus>>,
INotificationHandler<Request<MediaInformation>>, INotificationHandler<Request<MediaInformation>>,
IDisposable IDisposable
{ {
private readonly AsyncLock asyncLock = new(); private readonly AsyncLock asyncLock = new();
private readonly IDisposer disposer; private readonly IDisposer disposer;
private readonly IMediator mediator; private readonly IMediator mediator;
private readonly GlobalSystemMediaTransportControlsSession session; private GlobalSystemMediaTransportControlsSession? session;
private GlobalSystemMediaTransportControlsSessionPlaybackStatus playbackStatus;
public MediaController(IMediator mediator, public MediaController(IMediator mediator,
IDisposer disposer, IDisposer disposer,
@@ -31,6 +38,8 @@ public class MediaController :
public void Dispose() public void Dispose()
{ {
session = null;
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
disposer.Dispose(this); disposer.Dispose(this);
} }
@@ -49,7 +58,7 @@ public class MediaController :
await UpdateMediaPlaybackPropertiesAsync(); await UpdateMediaPlaybackPropertiesAsync();
} }
public async Task Handle(Request<PlaybackInformation> args, public async Task Handle(Request<MediaControllerPlaybackStatus> args,
CancellationToken cancellationToken) => await UpdateMediaPlaybackPropertiesAsync(); CancellationToken cancellationToken) => await UpdateMediaPlaybackPropertiesAsync();
public async Task Handle(Request<MediaInformation> args, public async Task Handle(Request<MediaInformation> args,
@@ -68,9 +77,14 @@ public class MediaController :
try try
{ {
GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo = session.GetPlaybackInfo(); GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo = session.GetPlaybackInfo();
await mediator.PublishAsync(new Changed<PlaybackInformation>(
new PlaybackInformation((PlaybackStatus)playbackInfo.PlaybackStatus)));
if (playbackInfo.PlaybackStatus != playbackStatus)
{
playbackStatus = playbackInfo.PlaybackStatus;
await mediator.PublishAsync(new Changed<MediaControllerPlaybackStatus>(
new MediaControllerPlaybackStatus((PlaybackStatus)playbackStatus)));
}
} }
catch catch
{ {
@@ -81,20 +95,31 @@ public class MediaController :
private async Task UpdateMediaPropertiesAsync() private async Task UpdateMediaPropertiesAsync()
{ {
using (await asyncLock) if (session is not null)
{ {
try using (await asyncLock)
{ {
GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties = try
await session.TryGetMediaPropertiesAsync(); {
await mediator.PublishAsync(new Changed<MediaInformation>(new MediaInformation(mediaProperties.Title, //
mediaProperties.Artist)));
} //InMemoryRandomAccessStream randomAccessStream = new InMemoryRandomAccessStream();
catch
{
//// Copy the image stream to the random access stream
//await d.AsStream().CopyToAsync(randomAccessStream.AsStreamForWrite());
GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties =
await session.TryGetMediaPropertiesAsync();
IRandomAccessStreamWithContentType randomAccessStream = await mediaProperties.Thumbnail.OpenReadAsync();
await mediator.PublishAsync(new Changed<MediaInformation>(new MediaInformation(mediaProperties.Title,
mediaProperties.Artist, randomAccessStream.AsStream())));
}
catch
{
}
} }
} }
} }
@@ -3,11 +3,10 @@
namespace Hyperbar.Widget.MediaController.Windows; namespace Hyperbar.Widget.MediaController.Windows;
public class MediaControllerManager(IMediator mediator, public class MediaControllerManager(IMediator mediator,
IFactory<GlobalSystemMediaTransportControlsSession, MediaController> factory, IFactory<GlobalSystemMediaTransportControlsSession, MediaController> factory) :
IDispatcher dispatcher,
IDisposer disposer) :
IInitializer IInitializer
{ {
private readonly AsyncLock asyncLock = new(); private readonly AsyncLock asyncLock = new();
private readonly List<KeyValuePair<GlobalSystemMediaTransportControlsSession, MediaController>> cache = []; private readonly List<KeyValuePair<GlobalSystemMediaTransportControlsSession, MediaController>> cache = [];
private GlobalSystemMediaTransportControlsSessionManager? mediaTransportControlsSessionManager; private GlobalSystemMediaTransportControlsSessionManager? mediaTransportControlsSessionManager;
@@ -31,7 +30,8 @@ public class MediaControllerManager(IMediator mediator,
if (factory.Create(session) is MediaController mediaController) if (factory.Create(session) is MediaController mediaController)
{ {
await mediator.PublishAsync(new Created<MediaController>(mediaController)); await mediator.PublishAsync(new Created<MediaController>(mediaController));
cache.Add(new KeyValuePair<GlobalSystemMediaTransportControlsSession, MediaController>(session, mediaController)); cache.Add(new KeyValuePair<GlobalSystemMediaTransportControlsSession, MediaController>(session,
mediaController));
} }
} }
@@ -1,4 +1,4 @@
namespace Hyperbar.Widget.MediaController.Windows; namespace Hyperbar.Widget.MediaController.Windows;
public record PlaybackInformation(PlaybackStatus Status) : public record MediaControllerPlaybackStatus(PlaybackStatus Status) :
INotification; INotification;
@@ -0,0 +1,31 @@
using CommunityToolkit.Mvvm.Input;
namespace Hyperbar.Widget.MediaController.Windows;
public class MediaControllerPlaybackStatusHandler(IMediator mediator,
IServiceFactory factory) :
INotificationHandler<Changed<MediaControllerPlaybackStatus>>
{
public async Task Handle(Changed<MediaControllerPlaybackStatus> notification,
CancellationToken cancellationToken)
{
if (notification.Value is MediaControllerPlaybackStatus mediaControllerPlaybackInformation)
{
if (mediaControllerPlaybackInformation.Status is PlaybackStatus.Playing)
{
await mediator.PublishAsync(new Replaced<WidgetComponentViewModel>(2, factory.Create<MediaButtonViewModel>(
PlaybackButtonType.Pause, "Pause", "\uE769",
new RelayCommand(async () => await mediator.PublishAsync<Pause>()))), nameof(MediaControllerViewModel),
cancellationToken);
}
if (mediaControllerPlaybackInformation.Status is PlaybackStatus.Paused)
{
await mediator.PublishAsync(new Replaced<WidgetComponentViewModel>(2, factory.Create<MediaButtonViewModel>(
PlaybackButtonType.Pause, "Play", "\uE768",
new RelayCommand(async () => await mediator.PublishAsync<Play>()))), nameof(MediaControllerViewModel),
cancellationToken);
}
}
}
}
@@ -3,11 +3,12 @@
x:Class="Hyperbar.Widget.MediaController.Windows.MediaControllerView" x:Class="Hyperbar.Widget.MediaController.Windows.MediaControllerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="using:Hyperbar.UI.Windows"> xmlns:windows="using:Hyperbar.UI.Windows">
<Grid Width="400" Background="red"> <Grid>
<ItemsControl <ItemsControl
HorizontalAlignment="Center" HorizontalAlignment="Right"
ItemTemplateSelector="{Binding Converter={ui:DataTemplateConverter}}" HorizontalContentAlignment="Right"
ItemTemplateSelector="{Binding Converter={windows:DataTemplateConverter}}"
ItemsSource="{Binding}"> ItemsSource="{Binding}">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
@@ -2,6 +2,7 @@
namespace Hyperbar.Widget.MediaController.Windows; namespace Hyperbar.Widget.MediaController.Windows;
[NotificationHandler(nameof(MediaControllerViewModel))]
public class MediaControllerViewModel : public class MediaControllerViewModel :
ObservableCollectionViewModel<WidgetComponentViewModel>, ObservableCollectionViewModel<WidgetComponentViewModel>,
ITemplatedViewModel ITemplatedViewModel
@@ -19,10 +20,6 @@ public class MediaControllerViewModel :
"Previous", "\uEB9E", "Previous", "\uEB9E",
new RelayCommand(async () => await mediator.PublishAsync<Previous>())); new RelayCommand(async () => await mediator.PublishAsync<Previous>()));
Add<MediaButtonViewModel>(PlaybackButtonType.Play,
"Play", "\uE768",
new RelayCommand(async () => await mediator.PublishAsync<Play>()));
Add<MediaButtonViewModel>(PlaybackButtonType.Pause, Add<MediaButtonViewModel>(PlaybackButtonType.Pause,
"Pause", "\uE769", "Pause", "\uE769",
new RelayCommand(async () => await mediator.PublishAsync<Pause>())); new RelayCommand(async () => await mediator.PublishAsync<Pause>()));
@@ -1,4 +1,3 @@
using Hyperbar.Widget;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Windows.Media.Control; using Windows.Media.Control;
@@ -22,6 +21,7 @@ public class MediaControllerWidget :
.AddHandler<MediaControllerHandler>() .AddHandler<MediaControllerHandler>()
.AddTransient<IFactory<MediaController, MediaControllerViewModel?>, MediaControllerViewModelFactory>() .AddTransient<IFactory<MediaController, MediaControllerViewModel?>, MediaControllerViewModelFactory>()
.AddCache<MediaController, MediaControllerViewModel>() .AddCache<MediaController, MediaControllerViewModel>()
.AddHandler<MediaControllerPlaybackStatusHandler>()
.AddContentTemplate<MediaControllerViewModel, MediaControllerView>() .AddContentTemplate<MediaControllerViewModel, MediaControllerView>()
.AddContentTemplate<MediaInformationViewModel, MediaInformationView>() .AddContentTemplate<MediaInformationViewModel, MediaInformationView>()
.AddContentTemplate<MediaButtonViewModel, MediaButtonView>(); .AddContentTemplate<MediaButtonViewModel, MediaButtonView>();
@@ -3,11 +3,9 @@
x:Class="Hyperbar.Widget.MediaController.Windows.MediaControllerWidgetView" x:Class="Hyperbar.Widget.MediaController.Windows.MediaControllerWidgetView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="using:Hyperbar.UI.Windows"> xmlns:windows="using:Hyperbar.UI.Windows">
<FlipView <FlipView
x:Name="FlipView" Width="360"
Width="400" ItemTemplateSelector="{Binding Converter={windows:DataTemplateConverter}}"
Background="Transparent"
ItemTemplateSelector="{Binding Converter={ui:DataTemplateConverter}}"
ItemsSource="{Binding}" /> ItemsSource="{Binding}" />
</UserControl> </UserControl>
@@ -1,5 +1,7 @@
namespace Hyperbar.Widget.MediaController.Windows; namespace Hyperbar.Widget.MediaController.Windows;
public record MediaInformation(string Title, string Description); public record MediaInformation(string Title,
string Description,
Stream ThumbnailSource);
@@ -4,27 +4,35 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:interactions="using:Microsoft.Xaml.Interactions.Core" xmlns:interactions="using:Microsoft.Xaml.Interactions.Core"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"> xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
<Grid> xmlns:windows="using:Hyperbar.UI.Windows">
<Grid Width="216">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="40" /> <ColumnDefinition Width="40" />
<ColumnDefinition Width="8" /> <ColumnDefinition Width="8" />
<ColumnDefinition Width="80" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Border <Border
Grid.Column="0" Grid.Column="0"
Width="40" Width="40"
Height="40" Height="40">
Background="Red" /> <Image Source="{Binding ThumbnailSource, Converter={windows:StreamToImageSourceConverter}}" />
</Border>
<StackPanel Grid.Column="2"> <StackPanel Grid.Column="2">
<TextBlock Style="{ThemeResource BaseTextBlockStyle}" Text="{Binding Title}" /> <TextBlock
Style="{ThemeResource BaseTextBlockStyle}"
Text="{x:Bind ViewModel.Title, Mode=OneWay}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap" />
<TextBlock <TextBlock
Opacity="0.7" Opacity="0.7"
Style="{ThemeResource CaptionTextBlockStyle}" Style="{ThemeResource CaptionTextBlockStyle}"
Text="{Binding Description}" /> Text="{x:Bind ViewModel.Description, Mode=OneWay}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap" />
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<interactions:EventTriggerBehavior EventName="Loaded"> <interactions:EventTriggerBehavior EventName="Loaded">
<interactions:InvokeCommandAction Command="{Binding Initialize}" /> <interactions:InvokeCommandAction Command="{x:Bind ViewModel.Initialize}" />
</interactions:EventTriggerBehavior> </interactions:EventTriggerBehavior>
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
</StackPanel> </StackPanel>
@@ -8,4 +8,6 @@ public sealed partial class MediaInformationView :
{ {
public MediaInformationView() => public MediaInformationView() =>
this.InitializeComponent(ref _contentLoaded); this.InitializeComponent(ref _contentLoaded);
private MediaInformationViewModel ViewModel => (MediaInformationViewModel)DataContext;
} }
@@ -1,5 +1,5 @@
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using Hyperbar.Widget; using Microsoft.UI.Xaml.Media;
namespace Hyperbar.Widget.MediaController.Windows; namespace Hyperbar.Widget.MediaController.Windows;
@@ -14,6 +14,9 @@ public partial class MediaInformationViewModel(IServiceFactory serviceFactory,
[ObservableProperty] [ObservableProperty]
private string? description; private string? description;
[ObservableProperty]
private Stream? thumbnailSource;
[ObservableProperty] [ObservableProperty]
private string? title; private string? title;
@@ -24,6 +27,7 @@ public partial class MediaInformationViewModel(IServiceFactory serviceFactory,
{ {
Title = value.Title; Title = value.Title;
Description = value.Description; Description = value.Description;
ThumbnailSource = value.ThumbnailSource;
} }
return Task.CompletedTask; return Task.CompletedTask;
@@ -1,6 +1,8 @@
using Hyperbar.Interop.Windows; using Hyperbar.Interop.Windows;
using Hyperbar.UI.Windows; using Hyperbar.UI.Windows;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml.Markup;
using System.Collections.Generic;
namespace Hyperbar.Widget.Windows; namespace Hyperbar.Widget.Windows;
@@ -16,6 +18,7 @@ public static class IServiceCollectionExtensions
services.AddTransient<IProxyServiceCollection<IWidgetBuilder>>(provider => services.AddTransient<IProxyServiceCollection<IWidgetBuilder>>(provider =>
new ProxyServiceCollection<IWidgetBuilder>(services => new ProxyServiceCollection<IWidgetBuilder>(services =>
{ {
services.AddSingleton(provider.GetRequiredService<IList<IXamlMetadataProvider>>());
services.AddSingleton(provider.GetRequiredService<IDispatcher>()); services.AddSingleton(provider.GetRequiredService<IDispatcher>());
services.AddTransient<IFactory<IWidgetHost, WidgetContainerViewModel?>, services.AddTransient<IFactory<IWidgetHost, WidgetContainerViewModel?>,
WidgetContainerFactory>(); WidgetContainerFactory>();
@@ -29,7 +32,8 @@ public static class IServiceCollectionExtensions
services.AddHandler<WidgetViewModelEnumerator>(); services.AddHandler<WidgetViewModelEnumerator>();
services.AddTransient<IWidgetView, WidgetView>(); services.AddTransient<IWidgetView, WidgetView>();
services.AddTransient<IInitializer, WidgetResourceInitialization>(); services.AddTransient<IInitializer, WidgetResourceInitializer>();
services.AddTransient<IInitializer, WidgetXamlMetadataInitializer>();
services.AddContentTemplate<WidgetContainerViewModel, WidgetContainerView>(); services.AddContentTemplate<WidgetContainerViewModel, WidgetContainerView>();
services.AddContentTemplate<WidgetButtonViewModel, WidgetButtonView>(); services.AddContentTemplate<WidgetButtonViewModel, WidgetButtonView>();
+2 -2
View File
@@ -3,8 +3,8 @@
x:Class="Hyperbar.Widget.Windows.WidgetBarView" x:Class="Hyperbar.Widget.Windows.WidgetBarView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="using:Hyperbar.UI.Windows"> xmlns:windows="using:Hyperbar.UI.Windows">
<ItemsControl ItemTemplateSelector="{Binding Converter={ui:DataTemplateConverter}}" ItemsSource="{Binding}"> <ItemsControl ItemTemplateSelector="{Binding Converter={windows:DataTemplateConverter}}" ItemsSource="{Binding}">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" /> <StackPanel Orientation="Horizontal" />
@@ -3,7 +3,7 @@ using Windows.Storage;
namespace Hyperbar.Widget.Windows; namespace Hyperbar.Widget.Windows;
internal class WidgetResourceInitialization(IWidgetAssembly widgetAssembly) : internal class WidgetResourceInitializer(IWidgetAssembly widgetAssembly) :
IInitializer IInitializer
{ {
public async Task InitializeAsync() public async Task InitializeAsync()
+2 -2
View File
@@ -5,9 +5,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:interactions="using:Microsoft.Xaml.Interactions.Core" xmlns:interactions="using:Microsoft.Xaml.Interactions.Core"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:ui="using:Hyperbar.UI.Windows"> xmlns:windows="using:Hyperbar.UI.Windows">
<Grid> <Grid>
<ItemsControl ItemTemplateSelector="{Binding Converter={ui:DataTemplateConverter}}" ItemsSource="{Binding Mode=TwoWay}"> <ItemsControl ItemTemplateSelector="{Binding Converter={windows:DataTemplateConverter}}" ItemsSource="{Binding Mode=TwoWay}">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="8" /> <StackPanel Orientation="Horizontal" Spacing="8" />
@@ -0,0 +1,20 @@
using Microsoft.UI.Xaml.Markup;
namespace Hyperbar.Widget.Windows;
public class WidgetXamlMetadataInitializer(IWidgetAssembly widgetAssembly,
IList<IXamlMetadataProvider> xamlMetadataProviders) :
IInitializer
{
public Task InitializeAsync()
{
foreach (IXamlMetadataProvider xamlMetadataProvider in widgetAssembly.Assembly.ExportedTypes
.Where(type => type.IsAssignableTo(typeof(IXamlMetadataProvider)))
.Select(metadataType => (IXamlMetadataProvider)Activator.CreateInstance(metadataType)!))
{
xamlMetadataProviders.Add(xamlMetadataProvider);
}
return Task.CompletedTask;
}
}
+4 -7
View File
@@ -1,5 +1,4 @@
using CustomExtensions.WinUI; using Hyperbar.Controls.Windows;
using Hyperbar.Controls.Windows;
using Hyperbar.UI.Windows; using Hyperbar.UI.Windows;
using Hyperbar.Widget; using Hyperbar.Widget;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
@@ -9,17 +8,14 @@ using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using System.Reflection; using System.Reflection;
using Hyperbar.Widget.Windows; using Hyperbar.Widget.Windows;
using Microsoft.UI.Xaml.Markup;
namespace Hyperbar.Windows; namespace Hyperbar.Windows;
public partial class App : public partial class App :
Application Application
{ {
public App() public App() => InitializeComponent();
{
InitializeComponent();
ApplicationExtensionHost.Initialize(this);
}
protected override async void OnLaunched(LaunchActivatedEventArgs args) protected override async void OnLaunched(LaunchActivatedEventArgs args)
{ {
@@ -37,6 +33,7 @@ public partial class App :
services.AddDefault(); services.AddDefault();
services.AddWidget(); services.AddWidget();
services.AddWidgetWindows(); services.AddWidgetWindows();
services.AddXamlMetadataProvider();
services.AddHostedService<AppService>(); services.AddHostedService<AppService>();
@@ -0,0 +1,26 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Markup;
using System.Reflection;
namespace Hyperbar.Widget;
public static class IServiceCollectionExtensions
{
public static IServiceCollection AddXamlMetadataProvider(this IServiceCollection services)
{
object? appProvider = Application.Current.GetType().GetProperty("_AppProvider", BindingFlags.NonPublic | BindingFlags.Instance)?
.GetValue(Application.Current);
object? provider = appProvider?.GetType().GetProperty("Provider", BindingFlags.NonPublic | BindingFlags.Instance)?
.GetValue(appProvider);
PropertyInfo? othersProviderProperty = provider?.GetType().GetProperty("OtherProviders", BindingFlags.NonPublic | BindingFlags.Instance,
null, typeof(List<IXamlMetadataProvider>), [], null);
List<IXamlMetadataProvider> xamlMetadataProviders = othersProviderProperty?.GetValue(provider) as List<IXamlMetadataProvider> ?? [];
services.AddSingleton<IList<IXamlMetadataProvider>>(xamlMetadataProviders);
return services;
}
}
+27 -1
View File
@@ -21,7 +21,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbar.Widget.Primary.Win
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbar.Widget.MediaController.Windows", "Hyperbar.Widget.MediaController.Windows\Hyperbar.Widget.MediaController.Windows.csproj", "{ACBB1C58-1DB6-40E1-ABF1-71F2D2F0EC73}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbar.Widget.MediaController.Windows", "Hyperbar.Widget.MediaController.Windows\Hyperbar.Widget.MediaController.Windows.csproj", "{ACBB1C58-1DB6-40E1-ABF1-71F2D2F0EC73}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hyperbar.Widget.Windows", "Hyperbar.Widget.Windows\Hyperbar.Widget.Windows.csproj", "{E7322176-B67F-4A22-AFDB-7430A6AD44B6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbar.Widget.Windows", "Hyperbar.Widget.Windows\Hyperbar.Widget.Windows.csproj", "{E7322176-B67F-4A22-AFDB-7430A6AD44B6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App1", "App1\App1.csproj", "{AB161079-E1AA-4A9B-B64C-D27A68245A2C}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -203,6 +205,30 @@ Global
{E7322176-B67F-4A22-AFDB-7430A6AD44B6}.Release|x64.Build.0 = Release|Any CPU {E7322176-B67F-4A22-AFDB-7430A6AD44B6}.Release|x64.Build.0 = Release|Any CPU
{E7322176-B67F-4A22-AFDB-7430A6AD44B6}.Release|x86.ActiveCfg = Release|Any CPU {E7322176-B67F-4A22-AFDB-7430A6AD44B6}.Release|x86.ActiveCfg = Release|Any CPU
{E7322176-B67F-4A22-AFDB-7430A6AD44B6}.Release|x86.Build.0 = Release|Any CPU {E7322176-B67F-4A22-AFDB-7430A6AD44B6}.Release|x86.Build.0 = Release|Any CPU
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Debug|Any CPU.ActiveCfg = Debug|x64
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Debug|Any CPU.Build.0 = Debug|x64
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Debug|Any CPU.Deploy.0 = Debug|x64
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Debug|ARM64.ActiveCfg = Debug|ARM64
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Debug|ARM64.Build.0 = Debug|ARM64
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Debug|ARM64.Deploy.0 = Debug|ARM64
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Debug|x64.ActiveCfg = Debug|x64
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Debug|x64.Build.0 = Debug|x64
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Debug|x64.Deploy.0 = Debug|x64
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Debug|x86.ActiveCfg = Debug|x86
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Debug|x86.Build.0 = Debug|x86
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Debug|x86.Deploy.0 = Debug|x86
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Release|Any CPU.ActiveCfg = Release|x64
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Release|Any CPU.Build.0 = Release|x64
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Release|Any CPU.Deploy.0 = Release|x64
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Release|ARM64.ActiveCfg = Release|ARM64
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Release|ARM64.Build.0 = Release|ARM64
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Release|ARM64.Deploy.0 = Release|ARM64
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Release|x64.ActiveCfg = Release|x64
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Release|x64.Build.0 = Release|x64
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Release|x64.Deploy.0 = Release|x64
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Release|x86.ActiveCfg = Release|x86
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Release|x86.Build.0 = Release|x86
{AB161079-E1AA-4A9B-B64C-D27A68245A2C}.Release|x86.Deploy.0 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -19,6 +19,7 @@ public partial class ObservableCollectionViewModel<TItem> :
INotificationHandler<Created<TItem>>, INotificationHandler<Created<TItem>>,
INotificationHandler<Inserted<TItem>>, INotificationHandler<Inserted<TItem>>,
INotificationHandler<Moved<TItem>>, INotificationHandler<Moved<TItem>>,
INotificationHandler<Replaced<TItem>>,
IDisposable IDisposable
where TItem : where TItem :
IDisposable IDisposable
@@ -250,6 +251,17 @@ public partial class ObservableCollectionViewModel<TItem> :
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task Handle(Replaced<TItem> notification,
CancellationToken cancellationToken)
{
if (notification.Value is TItem item)
{
Replace(notification.Index, item);
}
return Task.CompletedTask;
}
public int IndexOf(TItem item) => public int IndexOf(TItem item) =>
collection.IndexOf(item); collection.IndexOf(item);
@@ -272,6 +284,22 @@ public partial class ObservableCollectionViewModel<TItem> :
} }
} }
public bool Replace(int index, TItem item)
{
if (index <= Count - 1)
{
RemoveItem(index);
}
else
{
index = Count;
}
Insert(index, item);
return true;
}
public bool Move(int index, TItem item) public bool Move(int index, TItem item)
{ {
int oldIndex = collection.IndexOf(item); int oldIndex = collection.IndexOf(item);
+6
View File
@@ -0,0 +1,6 @@
namespace Hyperbar;
public record Replaced<TValue>(int Index, TValue Value, object? Target = null) :
INotification;