Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Persist the order of widgets in the Dashboard #1198

Merged
merged 7 commits into from
Jun 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ internal sealed class WidgetCustomState
{
[JsonPropertyName("host")]
public string Host { get; set; }

[JsonPropertyName("position")]
public int Position { get; set; } = -1;
}

[JsonSerializable(typeof(WidgetCustomState))]
Expand Down
13 changes: 12 additions & 1 deletion tools/Dashboard/DevHome.Dashboard/Helpers/WidgetHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Windows.Widgets;
using Microsoft.Windows.Widgets.Hosts;

Expand Down Expand Up @@ -73,13 +74,23 @@ public static bool IsIncludedWidgetProvider(WidgetProviderDefinition provider)
return include;
}

public static string CreateWidgetCustomState()
public static string CreateWidgetCustomState(int ordinal)
{
var state = new WidgetCustomState
{
Host = DevHomeHostName,
Position = ordinal,
};

return JsonSerializer.Serialize(state, SourceGenerationContext.Default.WidgetCustomState);
}

public static async Task SetPositionCustomStateAsync(Widget widget, int ordinal)
{
var stateStr = await widget.GetCustomStateAsync();
var state = JsonSerializer.Deserialize(stateStr, SourceGenerationContext.Default.WidgetCustomState);
state.Position = ordinal;
stateStr = JsonSerializer.Serialize(state, SourceGenerationContext.Default.WidgetCustomState);
await widget.SetCustomStateAsync(stateStr);
}
}
67 changes: 51 additions & 16 deletions tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ public partial class DashboardView : ToolPage

private static bool _widgetHostInitialized;

private static SortedDictionary<string, BitmapImage> _widgetLightIconCache;
private static SortedDictionary<string, BitmapImage> _widgetDarkIconCache;
private static Dictionary<string, BitmapImage> _widgetLightIconCache;
private static Dictionary<string, BitmapImage> _widgetDarkIconCache;

private readonly Version minSupportedVersion400 = new (423, 3800);
private readonly Version minSupportedVersion500 = new (523, 3300);
Expand All @@ -64,8 +64,8 @@ public DashboardView()
PinnedWidgets = new ObservableCollection<WidgetViewModel>();
PinnedWidgets.CollectionChanged += OnPinnedWidgetsCollectionChanged;

_widgetLightIconCache = new SortedDictionary<string, BitmapImage>();
_widgetDarkIconCache = new SortedDictionary<string, BitmapImage>();
_widgetLightIconCache = new Dictionary<string, BitmapImage>();
_widgetDarkIconCache = new Dictionary<string, BitmapImage>();

_renderer = new AdaptiveCardRenderer();
_dispatcher = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();
Expand Down Expand Up @@ -240,7 +240,7 @@ private async void OnLoaded(object sender, RoutedEventArgs e)

await ConfigureWidgetRenderer(_renderer);

RestorePinnedWidgets(null, null);
RestorePinnedWidgets();

LoadingWidgetsProgressRing.Visibility = Visibility.Collapsed;
}
Expand Down Expand Up @@ -329,14 +329,20 @@ public static async Task<BitmapImage> WidgetIconToBitmapImage(IRandomAccessStrea
return itemImage;
}

private async void RestorePinnedWidgets(object sender, RoutedEventArgs e)
private async void RestorePinnedWidgets()
{
Log.Logger()?.ReportInfo("DashboardView", "Get widgets for current host");
var pinnedWidgets = _widgetHost.GetWidgets();
if (pinnedWidgets != null)
{
Log.Logger()?.ReportInfo("DashboardView", $"Found {pinnedWidgets.Length} widgets for this host");
var restoredWidgetsWithPosition = new SortedDictionary<int, Widget>();
var restoredWidgetsWithoutPosition = new SortedDictionary<int, Widget>();
var numUnorderedWidgets = 0;

// Widgets do not come from the host in a deterministic order, so save their order in each widget's CustomState.
// Iterate through all the widgets and put them in order. If a widget does not have a position assigned to it,
// append it at the end. If a position is missing, just show the next widget in order.
foreach (var widget in pinnedWidgets)
{
try
Expand All @@ -346,10 +352,24 @@ private async void RestorePinnedWidgets(object sender, RoutedEventArgs e)
if (!string.IsNullOrEmpty(stateStr))
{
var stateObj = System.Text.Json.JsonSerializer.Deserialize(stateStr, SourceGenerationContext.Default.WidgetCustomState);

if (stateObj.Host == WidgetHelpers.DevHomeHostName)
{
var size = await widget.GetSizeAsync();
AddWidgetToPinnedWidgetsAsync(widget, size);
var position = stateObj.Position;
if (position >= 0)
{
if (!restoredWidgetsWithPosition.TryAdd(position, widget))
{
// If there was an error and a widget with this position is alredy there,
// treat this widget as unordered and put it into the unordered map.
restoredWidgetsWithoutPosition.Add(numUnorderedWidgets++, widget);
}
}
else
{
// Widgets with no position will get the default of -1. Append these at the end.
restoredWidgetsWithoutPosition.Add(numUnorderedWidgets++, widget);
}
}
}
}
Expand All @@ -358,6 +378,18 @@ private async void RestorePinnedWidgets(object sender, RoutedEventArgs e)
Log.Logger()?.ReportError("DashboardView", $"RestorePinnedWidgets(): ", ex);
}
}

// Now that we've ordered the widgets, put them in their final collection.
var finalPlace = 0;
foreach (var orderedWidget in restoredWidgetsWithPosition)
krschau marked this conversation as resolved.
Show resolved Hide resolved
{
await PlaceWidget(orderedWidget, finalPlace++);
}

foreach (var orderedWidget in restoredWidgetsWithoutPosition)
{
await PlaceWidget(orderedWidget, finalPlace++);
}
}
else
{
Expand All @@ -366,6 +398,14 @@ private async void RestorePinnedWidgets(object sender, RoutedEventArgs e)
}
}

private async Task PlaceWidget(KeyValuePair<int, Widget> orderedWidget, int finalPlace)
{
var widget = orderedWidget.Value;
var size = await widget.GetSizeAsync();
await InsertWidgetInPinnedWidgetsAsync(widget, size, finalPlace);
await WidgetHelpers.SetPositionCustomStateAsync(widget, finalPlace);
}

private async void AddWidget_Click(object sender, RoutedEventArgs e)
{
// If this is the first time we're initializing the Dashboard, or if initialization failed last time, initialize now.
Expand Down Expand Up @@ -414,7 +454,8 @@ private async void AddWidget_Click(object sender, RoutedEventArgs e)
if (newWidget != null)
{
// Set custom state on new widget.
var newCustomState = WidgetHelpers.CreateWidgetCustomState();
var position = PinnedWidgets.Count;
var newCustomState = WidgetHelpers.CreateWidgetCustomState(position);
Log.Logger()?.ReportDebug("DashboardView", $"SetCustomState: {newCustomState}");
await newWidget.SetCustomStateAsync(newCustomState);

Expand All @@ -424,17 +465,11 @@ private async void AddWidget_Click(object sender, RoutedEventArgs e)
{
var size = WidgetHelpers.GetDefaultWidgetSize(widgetDef.GetWidgetCapabilities());
await newWidget.SetSizeAsync(size);
AddWidgetToPinnedWidgetsAsync(newWidget, size);
await InsertWidgetInPinnedWidgetsAsync(newWidget, size, position);
}
}
}

private async void AddWidgetToPinnedWidgetsAsync(Widget widget, WidgetSize size)
{
Log.Logger()?.ReportDebug("DashboardView", $"Add widget to pinned widgets, id = {widget.Id}");
await InsertWidgetInPinnedWidgetsAsync(widget, size, PinnedWidgets.Count);
}

private async Task InsertWidgetInPinnedWidgetsAsync(Widget widget, WidgetSize size, int index)
{
var widgetDefintionId = widget.DefinitionId;
Expand Down