Skip to content
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
23 changes: 23 additions & 0 deletions src/Files.App.Controls/Omnibar/Omnibar.Properties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Markup;
using Microsoft.UI.Xaml.Shapes;
using System.Linq;
using System.Collections.Generic;

namespace Files.App.Controls
{
public partial class Omnibar
{
[GeneratedDependencyProperty]
public partial IList<OmnibarMode>? Modes { get; set; }

[GeneratedDependencyProperty]
public partial OmnibarMode? CurrentSelectedMode { get; set; }
}
}
180 changes: 180 additions & 0 deletions src/Files.App.Controls/Omnibar/Omnibar.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Markup;
using Microsoft.UI.Xaml.Shapes;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI;
using Windows.ApplicationModel.Contacts;

namespace Files.App.Controls
{
// Content
[ContentProperty(Name = nameof(Modes))]
// Template parts
[TemplatePart(Name = "PART_ModesHostGrid", Type = typeof(Grid))]
// Visual states
[TemplateVisualState(Name = "Focused", GroupName = "FocusStates")]
[TemplateVisualState(Name = "Normal", GroupName = "FocusStates")]
public partial class Omnibar : Control
{
private const string ModesHostGrid = "PART_ModesHostGrid";
private const string AutoSuggestPopup = "PART_AutoSuggestPopup";
private const string AutoSuggestBoxBorder = "PART_AutoSuggestBoxBorder";

private Grid? _modesHostGrid;
private Popup? _autoSuggestPopup;
private Border? _autoSuggestBoxBorder;
private bool _isFocused;
private bool _stillHasFocus;

public Omnibar()
{
DefaultStyleKey = typeof(Omnibar);

Modes ??= [];
}

protected override void OnApplyTemplate()
{
_modesHostGrid = GetTemplateChild(ModesHostGrid) as Grid
?? throw new MissingFieldException($"Could not find {ModesHostGrid} in the given {nameof(Omnibar)}'s style.");
_autoSuggestPopup = GetTemplateChild(AutoSuggestPopup) as Popup
?? throw new MissingFieldException($"Could not find {AutoSuggestPopup} in the given {nameof(Omnibar)}'s style.");
_autoSuggestBoxBorder = GetTemplateChild(AutoSuggestBoxBorder) as Border
?? throw new MissingFieldException($"Could not find {AutoSuggestBoxBorder} in the given {nameof(Omnibar)}'s style.");

if (Modes is null)
return;

// Add shadow to the popup and set the proper width
_autoSuggestBoxBorder!.Translation = new(0, 0, 32);
_autoSuggestBoxBorder!.Width = _modesHostGrid!.ActualWidth;

// Populate the modes
foreach (var mode in Modes)
{
// Insert a divider
if (_modesHostGrid.Children.Count is not 0)
{
var divider = new Rectangle()
{
Fill = (SolidColorBrush)Application.Current.Resources["DividerStrokeColorDefaultBrush"],
Height = 20,
Margin = new(2,0,2,0),
Width = 1,
};

_modesHostGrid.ColumnDefinitions.Add(new() { Width = GridLength.Auto });
Grid.SetColumn(divider, _modesHostGrid.Children.Count);
_modesHostGrid.Children.Add(divider);
}

// Insert the mode
_modesHostGrid.ColumnDefinitions.Add(new() { Width = GridLength.Auto });
Grid.SetColumn(mode, _modesHostGrid.Children.Count);
_modesHostGrid.Children.Add(mode);
mode.Host = this;
}

_modesHostGrid.SizeChanged += ModesHostGrid_SizeChanged;

GotFocus += Omnibar_GotFocus;
LostFocus += Omnibar_LostFocus;
LosingFocus += Omnibar_LosingFocus;

UpdateVisualStates();

base.OnApplyTemplate();
}

// Methods

internal void ChangeMode(OmnibarMode modeToExpand)
{
if (_modesHostGrid is null || Modes is null)
throw new NullReferenceException();

// Reset
foreach (var column in _modesHostGrid.ColumnDefinitions)
column.Width = GridLength.Auto;
foreach (var mode in Modes)
VisualStateManager.GoToState(mode, "Unfocused", true);

// Expand the given mode
VisualStateManager.GoToState(modeToExpand, "Focused", true);
_modesHostGrid.ColumnDefinitions[_modesHostGrid.Children.IndexOf(modeToExpand)].Width = new(1, GridUnitType.Star);

CurrentSelectedMode = modeToExpand;

UpdateVisualStates();
}

private void UpdateVisualStates()
{
VisualStateManager.GoToState(this, _isFocused ? "Focused" : "Normal", true);

if (CurrentSelectedMode is not null && _autoSuggestPopup is not null)
{
// Close anyway
if (_autoSuggestPopup.IsOpen && CurrentSelectedMode.SuggestionItemsSource is null)
VisualStateManager.GoToState(this, "PopupClosed", true);

// Decide open or close
if (_isFocused != _autoSuggestPopup.IsOpen)
VisualStateManager.GoToState(this, _isFocused && CurrentSelectedMode.SuggestionItemsSource is not null ? "PopupOpened" : "PopupClosed", true);
}

if (CurrentSelectedMode is not null)
VisualStateManager.GoToState(
CurrentSelectedMode,
_isFocused
? "Focused"
: CurrentSelectedMode.ContentOnInactive is null
? "CurrentUnfocusedWithoutInactiveMode"
: "CurrentUnfocusedWithInactiveMode",
true);
}

// Events

private void ModesHostGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
_autoSuggestBoxBorder!.Width = _modesHostGrid!.ActualWidth;
}

private void Omnibar_GotFocus(object sender, RoutedEventArgs e)
{
_isFocused = true;
UpdateVisualStates();
}

private void Omnibar_LosingFocus(UIElement sender, LosingFocusEventArgs args)
{
// Ignore when user clicks on the TextBox or the button area of an OmnibarMode, Omnibar still has focus anyway
if (args.NewFocusedElement?.GetType() is not { } focusedType ||
focusedType == typeof(TextBox) ||
focusedType == typeof(OmnibarMode) ||
focusedType == typeof(Omnibar))
{
_stillHasFocus = true;
}
}

private void Omnibar_LostFocus(object sender, RoutedEventArgs e)
{
if (_stillHasFocus)
{
_stillHasFocus = false;
return;
}

_isFocused = false;
UpdateVisualStates();
}
}
}
114 changes: 114 additions & 0 deletions src/Files.App.Controls/Omnibar/Omnibar.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<!-- Copyright (c) Files Community. Licensed under the MIT License. -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Files.App.Controls">

<!--<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<SolidColorBrush x:Key="OmnibarModeDivisiderBrush" Color="{ThemeResource DividerStrokeColorDefaultBrush}" />
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<SolidColorBrush x:Key="OmnibarModeDivisiderBrush" Color="{ThemeResource DividerStrokeColorDefaultBrush}" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>-->

<x:Double x:Key="OmnibarDefaultHeight">38</x:Double>
<CornerRadius x:Key="OmnibarDefaultCornerRadius">19</CornerRadius>
<Thickness x:Key="OmnibarFocusedBorderThickness">2</Thickness>
<Thickness x:Key="OmnibarUnfocusedBorderThickness">1</Thickness>
<Thickness x:Key="OmnibarUnfocusedRootPadding">1</Thickness>

<Style BasedOn="{StaticResource DefaultOmnibarStyle}" TargetType="local:Omnibar" />

<Style x:Key="DefaultOmnibarStyle" TargetType="local:Omnibar">
<Setter Property="IsTabStop" Value="True" />
<Setter Property="UseSystemFocusVisuals" Value="True" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="Background" Value="{ThemeResource ControlFillColorDefaultBrush}" />
<Setter Property="Padding" Value="{StaticResource OmnibarUnfocusedRootPadding}" />
<Setter Property="BorderBrush" Value="{ThemeResource CircleElevationBorderBrush}" />
<Setter Property="BorderThickness" Value="{StaticResource OmnibarUnfocusedBorderThickness}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="CornerRadius" Value="{StaticResource OmnibarDefaultCornerRadius}" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="IsFocusEngagementEnabled" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:Omnibar">
<Grid x:Name="PART_RootGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Input area -->
<Grid
x:Name="PART_ModesHostGrid"
Grid.Row="0"
Height="{StaticResource OmnibarDefaultHeight}"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}" />

<!-- Auto-suggest box area -->
<Popup
x:Name="PART_AutoSuggestPopup"
Grid.Row="1"
HorizontalAlignment="Stretch"
IsOpen="False"
ShouldConstrainToRootBounds="False">

<Border
x:Name="PART_AutoSuggestBoxBorder"
MaxHeight="200"
Background="{ThemeResource AcrylicBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource SurfaceStrokeColorFlyoutBrush}"
BorderThickness="1"
CornerRadius="{ThemeResource OverlayCornerRadius}">
<Border.Shadow>
<ThemeShadow />
</Border.Shadow>

<ListView
Padding="0,2"
HorizontalAlignment="Stretch"
IsItemClickEnabled="True"
ItemTemplate="{Binding CurrentSelectedMode.SuggestionItemTemplate, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
ItemsSource="{Binding CurrentSelectedMode.SuggestionItemsSource, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
SelectionMode="None" />

</Border>
</Popup>

<VisualStateManager.VisualStateGroups>

<VisualStateGroup x:Name="PointerStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Focused">
<VisualState.Setters>
<Setter Target="PART_ModesHostGrid.Margin" Value="-1" />
<Setter Target="PART_ModesHostGrid.BorderThickness" Value="{StaticResource OmnibarFocusedBorderThickness}" />
<Setter Target="PART_ModesHostGrid.BorderBrush" Value="{ThemeResource AccentFillColorDefaultBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>

<VisualStateGroup x:Name="PopupVisibilityStates">
<VisualState x:Name="PopupClosed" />
<VisualState x:Name="PopupOpened">
<VisualState.Setters>
<Setter Target="PART_AutoSuggestPopup.IsOpen" Value="True" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>

</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

</ResourceDictionary>
47 changes: 47 additions & 0 deletions src/Files.App.Controls/Omnibar/OmnibarMode.Properties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Markup;
using Microsoft.UI.Xaml.Shapes;
using System.Linq;
using System.Collections.Generic;

namespace Files.App.Controls
{
public partial class OmnibarMode
{
[GeneratedDependencyProperty]
public partial string? Text { get; set; }

[GeneratedDependencyProperty]
public partial string? TextPlaceholder { get; set; }

[GeneratedDependencyProperty]
public partial string? ModeName { get; set; }

[GeneratedDependencyProperty]
public partial FrameworkElement? ContentOnInactive { get; set; }

[GeneratedDependencyProperty]
public partial FrameworkElement? IconOnActive { get; set; }

[GeneratedDependencyProperty]
public partial FrameworkElement? IconOnInactive { get; set; }

[GeneratedDependencyProperty]
public partial object? SuggestionItemsSource { get; set; }

[GeneratedDependencyProperty]
public partial DataTemplate? SuggestionItemTemplate { get; set; }

[GeneratedDependencyProperty]
public partial bool IsDefault { get; set; }

[GeneratedDependencyProperty]
internal partial Omnibar? Host { get; set; }
}
}
Loading
Loading