diff --git a/Directory.Build.props b/Directory.Build.props index b4062adb5049..5731e23a25bd 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -9,6 +9,7 @@ 10.0.26100.67-preview $(TargetFrameworkVersion)-windows$(TargetWindowsVersion) preview + nullable true diff --git a/src/Files.App.Controls/Omnibar/IOmnibarTextMemberPathProvider.cs b/src/Files.App.Controls/Omnibar/IOmnibarTextMemberPathProvider.cs index decf6588f52b..aa99018edafc 100644 --- a/src/Files.App.Controls/Omnibar/IOmnibarTextMemberPathProvider.cs +++ b/src/Files.App.Controls/Omnibar/IOmnibarTextMemberPathProvider.cs @@ -15,6 +15,6 @@ public interface IOmnibarTextMemberPathProvider /// Retrieves the path of the text member as a string. This path can be used to identify the location of the text member. /// /// Returns a string representing the path of the text member. - string GetTextMemberPath(string textMemberPath); + string? GetTextMemberPath(string textMemberPath); } } diff --git a/src/Files.App.Controls/Omnibar/Omnibar.cs b/src/Files.App.Controls/Omnibar/Omnibar.cs index 707dd90bfc58..7e7528f1e84f 100644 --- a/src/Files.App.Controls/Omnibar/Omnibar.cs +++ b/src/Files.App.Controls/Omnibar/Omnibar.cs @@ -275,7 +275,7 @@ private string GetObjectText(object obj) return obj is string text ? text : obj is IOmnibarTextMemberPathProvider textMemberPathProvider - ? textMemberPathProvider.GetTextMemberPath(CurrentSelectedMode.TextMemberPath ?? string.Empty) + ? textMemberPathProvider.GetTextMemberPath(CurrentSelectedMode.TextMemberPath ?? string.Empty) ?? string.Empty : obj.ToString() ?? string.Empty; } diff --git a/src/Files.App.Controls/Sidebar/ISidebarItemModel.cs b/src/Files.App.Controls/Sidebar/ISidebarItemModel.cs index eedd91e8f4d1..9d2a3038f262 100644 --- a/src/Files.App.Controls/Sidebar/ISidebarItemModel.cs +++ b/src/Files.App.Controls/Sidebar/ISidebarItemModel.cs @@ -1,10 +1,24 @@ // Copyright (c) Files Community // Licensed under the MIT License. +using Microsoft.UI.Xaml; + namespace Files.App.Controls { + public interface ISidebarItemPresentationModel + { + string? Text { get; } + + object? ToolTip { get; } + + FrameworkElement? IconElement { get; } + + FrameworkElement? ItemDecorator { get; } + } + public interface ISidebarItemModel : INotifyPropertyChanged { + /// /// The children of this item that will be rendered as child elements of the SidebarItem /// diff --git a/src/Files.App.Controls/Sidebar/SidebarItem.Properties.cs b/src/Files.App.Controls/Sidebar/SidebarItem.Properties.cs index 6d6565235a6a..1ea83c56edc9 100644 --- a/src/Files.App.Controls/Sidebar/SidebarItem.Properties.cs +++ b/src/Files.App.Controls/Sidebar/SidebarItem.Properties.cs @@ -83,7 +83,15 @@ public ISidebarItemModel? Item set { SetValue(ItemProperty, value); } } public static readonly DependencyProperty ItemProperty = - DependencyProperty.Register(nameof(Item), typeof(ISidebarItemModel), typeof(SidebarItem), new PropertyMetadata(null)); + DependencyProperty.Register(nameof(Item), typeof(ISidebarItemModel), typeof(SidebarItem), new PropertyMetadata(null, OnPropertyChanged)); + + public bool UseItemPresentation + { + get { return (bool)GetValue(UseItemPresentationProperty); } + set { SetValue(UseItemPresentationProperty, value); } + } + public static readonly DependencyProperty UseItemPresentationProperty = + DependencyProperty.Register(nameof(UseItemPresentation), typeof(bool), typeof(SidebarItem), new PropertyMetadata(false, OnPropertyChanged)); public bool UseReorderDrop { @@ -136,12 +144,18 @@ public static void OnPropertyChanged(DependencyObject sender, DependencyProperty } else if (e.Property == IsExpandedProperty) { + if (item.UseItemPresentation && item.Item is { } model && model.IsExpanded != item.IsExpanded) + model.IsExpanded = item.IsExpanded; item.UpdateExpansionState(); } else if (e.Property == ItemProperty) { item.HandleItemChange(); } + else if (e.Property == UseItemPresentationProperty && item.UseItemPresentation) + { + item.UpdateItemPresentation(); + } else { Debug.Write(e.Property.ToString()); diff --git a/src/Files.App.Controls/Sidebar/SidebarItem.cs b/src/Files.App.Controls/Sidebar/SidebarItem.cs index 11ceb259ad43..4eee8b2c6dd5 100644 --- a/src/Files.App.Controls/Sidebar/SidebarItem.cs +++ b/src/Files.App.Controls/Sidebar/SidebarItem.cs @@ -90,7 +90,10 @@ protected override void OnApplyTemplate() if (GetTemplateChild("ChevronContainer") is Border chevronContainer) chevronContainer.PointerPressed += ChevronContainer_PointerPressed; if (GetTemplateChild("FlyoutChildrenPresenter") is ItemsRepeater flyoutRepeater) + { flyoutRepeater.ElementPrepared += FlyoutChildrenPresenter_ElementPrepared; + flyoutRepeater.ItemsSource = Item?.Children; + } } if (Owner is null) @@ -124,11 +127,31 @@ private void SidebarItem_Loaded(object sender, RoutedEventArgs e) public void HandleItemChange() { HookupItemChangeListener(null, Item); + if (UseItemPresentation) + UpdateItemPresentation(); + UpdateFlyoutChildrenSource(); UpdateExpansionState(); ReevaluateSelection(); CanDrag = Item?.Path is string path && Path.IsPathRooted(path); } + private void UpdateItemPresentation() + { + var presentation = Item as ISidebarItemPresentationModel; + AutomationProperties.SetAutomationId(this, presentation?.Text ?? string.Empty); + Text = presentation?.Text; + ToolTip = presentation?.ToolTip; + Icon = presentation?.IconElement; + Decorator = presentation?.ItemDecorator; + IsExpanded = Item?.IsExpanded ?? true; + } + + private void UpdateFlyoutChildrenSource() + { + if (GetTemplateChild("FlyoutChildrenPresenter") is ItemsRepeater flyoutRepeater) + flyoutRepeater.ItemsSource = Item?.Children; + } + private void HookupOwners() { // Owner is pushed in by the hosting SidebarView's MenuItemsHost_ElementPrepared (top-level rows) or the parent SidebarItem's FlyoutChildrenPresenter_ElementPrepared (flyout children) before Loaded fires. Static SidebarItems declared directly in XAML (MainPage's SettingsButton in SidebarView.Footer) aren't realized through either path, so resolve Owner via a visual-tree walk for them. OwnerExpansionSupport state is applied by OnOwnerChanged. @@ -181,9 +204,46 @@ private void Item_PropertyChanged(object? sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { + case null: + case "": + if (UseItemPresentation) + UpdateItemPresentation(); + UpdateFlyoutChildrenSource(); + UpdateExpansionState(); + ReevaluateSelection(); + break; + case nameof(ISidebarItemPresentationModel.Text): + if (UseItemPresentation) + { + var presentation = Item as ISidebarItemPresentationModel; + Text = presentation?.Text; + AutomationProperties.SetAutomationId(this, presentation?.Text ?? string.Empty); + } + break; + case nameof(ISidebarItemPresentationModel.ToolTip): + if (UseItemPresentation) + ToolTip = (Item as ISidebarItemPresentationModel)?.ToolTip; + break; + case nameof(ISidebarItemPresentationModel.IconElement): + if (UseItemPresentation) + Icon = (Item as ISidebarItemPresentationModel)?.IconElement; + break; + case nameof(ISidebarItemPresentationModel.ItemDecorator): + if (UseItemPresentation) + Decorator = (Item as ISidebarItemPresentationModel)?.ItemDecorator; + break; + case nameof(ISidebarItemModel.IsExpanded): + if (UseItemPresentation) + IsExpanded = Item?.IsExpanded ?? true; + UpdateExpansionState(); + break; case nameof(ISidebarItemModel.HasUnrealizedChildren): case nameof(ISidebarItemModel.IsLeafWithChildren): + UpdateExpansionState(); + ReevaluateSelection(); + break; case nameof(ISidebarItemModel.Children): + UpdateFlyoutChildrenSource(); UpdateExpansionState(); ReevaluateSelection(); break; diff --git a/src/Files.App.Controls/Sidebar/SidebarStyles.xaml b/src/Files.App.Controls/Sidebar/SidebarStyles.xaml index 004f6683cf04..b3ab9b44b1e8 100644 --- a/src/Files.App.Controls/Sidebar/SidebarStyles.xaml +++ b/src/Files.App.Controls/Sidebar/SidebarStyles.xaml @@ -36,20 +36,6 @@ - - - -