521 lines
15 KiB
C#
521 lines
15 KiB
C#
using System;
|
|
using System.Windows;
|
|
using System.Windows.Automation;
|
|
using System.Windows.Automation.Peers;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media;
|
|
using System.Windows.Shapes;
|
|
using TextBlock = System.Windows.Controls.TextBlock;
|
|
|
|
namespace WPFluent.Controls;
|
|
|
|
public partial class PersonPicture : Control
|
|
{
|
|
static PersonPicture()
|
|
{
|
|
DefaultStyleKeyProperty.OverrideMetadata(typeof(PersonPicture), new FrameworkPropertyMetadata(typeof(PersonPicture)));
|
|
}
|
|
|
|
public PersonPicture()
|
|
{
|
|
TemplateSettings = new PersonPictureTemplateSettings();
|
|
|
|
Unloaded += OnUnloaded;
|
|
SizeChanged += OnSizeChanged;
|
|
}
|
|
|
|
protected override AutomationPeer OnCreateAutomationPeer()
|
|
{
|
|
return new PersonPictureAutomationPeer(this);
|
|
}
|
|
|
|
public override void OnApplyTemplate()
|
|
{
|
|
base.OnApplyTemplate();
|
|
|
|
m_initialsTextBlock = (TextBlock)GetTemplateChild("InitialsTextBlock");
|
|
|
|
m_badgeNumberTextBlock = (TextBlock)GetTemplateChild("BadgeNumberTextBlock");
|
|
m_badgeGlyphIcon = (FontIcon)GetTemplateChild("BadgeGlyphIcon");
|
|
m_badgingEllipse = (Ellipse)GetTemplateChild("BadgingEllipse");
|
|
m_badgingBackgroundEllipse = (Ellipse)GetTemplateChild("BadgingBackgroundEllipse");
|
|
|
|
UpdateBadge();
|
|
UpdateIfReady();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper to determine the initials that should be shown.
|
|
/// </summary>
|
|
string GetInitials()
|
|
{
|
|
if (!string.IsNullOrEmpty(Initials))
|
|
{
|
|
return Initials;
|
|
}
|
|
else if (!string.IsNullOrEmpty(m_displayNameInitials))
|
|
{
|
|
return m_displayNameInitials;
|
|
}
|
|
else
|
|
{
|
|
return m_contactDisplayNameInitials;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper to determine the image source that should be shown.
|
|
/// </summary>
|
|
ImageSource GetImageSource()
|
|
{
|
|
if (ProfilePicture != null)
|
|
{
|
|
return ProfilePicture;
|
|
}
|
|
else
|
|
{
|
|
return m_contactImageSource;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates Control elements, if available, with the latest values.
|
|
/// </summary>
|
|
void UpdateIfReady()
|
|
{
|
|
string initials = GetInitials();
|
|
ImageSource imageSrc = GetImageSource();
|
|
|
|
var templateSettings = TemplateSettings;
|
|
templateSettings.ActualInitials = initials;
|
|
if (imageSrc != null)
|
|
{
|
|
var imageBrush = templateSettings.ActualImageBrush;
|
|
if (imageBrush == null)
|
|
{
|
|
imageBrush = new ImageBrush();
|
|
imageBrush.Stretch = Stretch.UniformToFill;
|
|
templateSettings.ActualImageBrush = imageBrush;
|
|
}
|
|
|
|
imageBrush.ImageSource = imageSrc;
|
|
}
|
|
else
|
|
{
|
|
templateSettings.ActualImageBrush = null!;
|
|
}
|
|
|
|
// If the control is converted to 'Group-mode', we'll clear individual-specific information.
|
|
// When IsGroup evaluates to false, we will restore state.
|
|
if (IsGroup)
|
|
{
|
|
VisualStateManager.GoToState(this, "Group", false);
|
|
}
|
|
else
|
|
{
|
|
if (imageSrc != null)
|
|
{
|
|
VisualStateManager.GoToState(this, "Photo", false);
|
|
}
|
|
else if (!string.IsNullOrEmpty(initials))
|
|
{
|
|
VisualStateManager.GoToState(this, "Initials", false);
|
|
}
|
|
else
|
|
{
|
|
VisualStateManager.GoToState(this, "NoPhotoOrInitials", false);
|
|
}
|
|
}
|
|
|
|
UpdateAutomationName();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the state of the Badging element.
|
|
/// </summary>
|
|
void UpdateBadge()
|
|
{
|
|
if (BadgeImageSource != null)
|
|
{
|
|
UpdateBadgeImageSource();
|
|
}
|
|
else if (BadgeNumber != 0)
|
|
{
|
|
UpdateBadgeNumber();
|
|
}
|
|
else if (!string.IsNullOrEmpty(BadgeGlyph))
|
|
{
|
|
UpdateBadgeGlyph();
|
|
}
|
|
// No badge properties set, so clear the badge XAML
|
|
else
|
|
{
|
|
VisualStateManager.GoToState(this, "NoBadge", false);
|
|
|
|
var badgeNumberTextBlock = m_badgeNumberTextBlock;
|
|
if (badgeNumberTextBlock != null)
|
|
{
|
|
badgeNumberTextBlock.Text = "";
|
|
}
|
|
|
|
var badgeGlyphIcon = m_badgeGlyphIcon;
|
|
if (badgeGlyphIcon != null)
|
|
{
|
|
badgeGlyphIcon.Glyph = "";
|
|
}
|
|
}
|
|
|
|
UpdateAutomationName();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates Badging Number text element.
|
|
/// </summary>
|
|
void UpdateBadgeNumber()
|
|
{
|
|
if (m_badgingEllipse == null || m_badgeNumberTextBlock == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int badgeNumber = BadgeNumber;
|
|
|
|
if (badgeNumber <= 0)
|
|
{
|
|
VisualStateManager.GoToState(this, "NoBadge", false);
|
|
m_badgeNumberTextBlock.Text = "";
|
|
return;
|
|
}
|
|
|
|
// should have badging number to show if we are here
|
|
VisualStateManager.GoToState(this, "BadgeWithoutImageSource", false);
|
|
|
|
if (badgeNumber <= 99)
|
|
{
|
|
m_badgeNumberTextBlock.Text = badgeNumber.ToString();
|
|
}
|
|
else
|
|
{
|
|
m_badgeNumberTextBlock.Text = "99+";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates Badging Glyph element.
|
|
/// </summary>
|
|
void UpdateBadgeGlyph()
|
|
{
|
|
if (m_badgingEllipse == null || m_badgeGlyphIcon == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string badgeGlyph = BadgeGlyph;
|
|
|
|
if (string.IsNullOrEmpty(badgeGlyph))
|
|
{
|
|
VisualStateManager.GoToState(this, "NoBadge", false);
|
|
m_badgeGlyphIcon.Glyph = "";
|
|
return;
|
|
}
|
|
|
|
// should have badging Glyph to show if we are here
|
|
VisualStateManager.GoToState(this, "BadgeWithoutImageSource", false);
|
|
|
|
m_badgeGlyphIcon.Glyph = badgeGlyph;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates Badging Image element.
|
|
/// </summary>
|
|
void UpdateBadgeImageSource()
|
|
{
|
|
if (m_badgeImageBrush == null)
|
|
{
|
|
m_badgeImageBrush = (ImageBrush)GetTemplateChild("BadgeImageBrush");
|
|
}
|
|
|
|
if (m_badgingEllipse == null || m_badgeImageBrush == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_badgeImageBrush.ImageSource = BadgeImageSource;
|
|
|
|
if (BadgeImageSource != null)
|
|
{
|
|
VisualStateManager.GoToState(this, "BadgeWithImageSource", false);
|
|
}
|
|
else
|
|
{
|
|
VisualStateManager.GoToState(this, "NoBadge", false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the UI Automation name for the control based on contact name and badge state.
|
|
/// </summary>
|
|
void UpdateAutomationName()
|
|
{
|
|
string automationName;
|
|
string contactName;
|
|
|
|
// The AutomationName for the control is in the format: PersonName, BadgeInformation.
|
|
// PersonName is set based on the name / initial properties in the order below.
|
|
// if none exist, it defaults to "Person"
|
|
if (IsGroup)
|
|
{
|
|
contactName = "Group";
|
|
}
|
|
else if (!string.IsNullOrEmpty(DisplayName))
|
|
{
|
|
contactName = DisplayName;
|
|
}
|
|
else if (!string.IsNullOrEmpty(Initials))
|
|
{
|
|
contactName = Initials;
|
|
}
|
|
else
|
|
{
|
|
contactName = "Person";
|
|
}
|
|
|
|
// BadgeInformation portion of the AutomationName is set to 'n items' if there is a BadgeNumber,
|
|
// or 'icon' for BadgeGlyph or BadgeImageSource. If BadgeText is specified, it will override
|
|
// the string 'items' or 'icon'
|
|
if (BadgeNumber > 0)
|
|
{
|
|
if (!string.IsNullOrEmpty(BadgeText))
|
|
{
|
|
automationName = string.Format(
|
|
"{0}, {1}",
|
|
contactName,
|
|
BadgeNumber,
|
|
BadgeText);
|
|
}
|
|
else
|
|
{
|
|
automationName = string.Format(
|
|
GetLocalizedPluralBadgeItemStringResource(BadgeNumber),
|
|
contactName,
|
|
BadgeNumber);
|
|
}
|
|
}
|
|
else if (!string.IsNullOrEmpty(BadgeGlyph) || BadgeImageSource != null)
|
|
{
|
|
if (!string.IsNullOrEmpty(BadgeText))
|
|
{
|
|
automationName = string.Format(
|
|
"{0}, {1}",
|
|
contactName,
|
|
BadgeText);
|
|
}
|
|
else
|
|
{
|
|
automationName = string.Format(
|
|
"{0}, icon",
|
|
contactName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
automationName = contactName;
|
|
}
|
|
|
|
AutomationProperties.SetName(this, automationName);
|
|
}
|
|
|
|
// Helper functions
|
|
string GetLocalizedPluralBadgeItemStringResource(int numericValue)
|
|
{
|
|
int valueMod10 = numericValue % 10;
|
|
string value;
|
|
|
|
if (numericValue == 1) // Singular
|
|
{
|
|
value = "{0}, {1:d} item";
|
|
}
|
|
else if (numericValue == 2) // 2
|
|
{
|
|
value = "{0}, {1:d} items";
|
|
}
|
|
else if (numericValue == 3 || numericValue == 4) // 3,4
|
|
{
|
|
value = "{0}, {1:d} items";
|
|
}
|
|
else if (numericValue >= 5 && numericValue <= 10) // 5-10
|
|
{
|
|
value = "{0}, {1:d} items";
|
|
}
|
|
else if (numericValue >= 11 && numericValue <= 19) // 11-19
|
|
{
|
|
value = "{0}, {1:d} items";
|
|
}
|
|
else if (valueMod10 == 1) // 21, 31, 41, etc.
|
|
{
|
|
value = "{0}, {1:d} items";
|
|
}
|
|
else if (valueMod10 >= 2 && valueMod10 <= 4) // 22-24, 32-34, 42-44, etc.
|
|
{
|
|
value = "{0}, {1:d} items";
|
|
}
|
|
else // Everything else... 0, 20, 25-30, 35-40, etc.
|
|
{
|
|
value = "{0}, {1:d} items";
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
void PrivateOnPropertyChanged(DependencyPropertyChangedEventArgs args)
|
|
{
|
|
DependencyProperty property = args.Property;
|
|
|
|
if (property == BadgeNumberProperty ||
|
|
property == BadgeGlyphProperty ||
|
|
property == BadgeImageSourceProperty)
|
|
{
|
|
UpdateBadge();
|
|
}
|
|
else if (property == BadgeTextProperty)
|
|
{
|
|
UpdateAutomationName();
|
|
}
|
|
else if (property == DisplayNameProperty)
|
|
{
|
|
OnDisplayNameChanged(args);
|
|
}
|
|
else if (property == ProfilePictureProperty ||
|
|
property == InitialsProperty ||
|
|
property == IsGroupProperty)
|
|
{
|
|
UpdateIfReady();
|
|
}
|
|
// No additional action required for s_PreferSmallImageProperty
|
|
}
|
|
|
|
// DependencyProperty changed event handlers
|
|
void OnDisplayNameChanged(DependencyPropertyChangedEventArgs args)
|
|
{
|
|
m_displayNameInitials = InitialsGenerator.InitialsFromDisplayName(DisplayName);
|
|
|
|
UpdateIfReady();
|
|
}
|
|
|
|
// Event handlers
|
|
void OnSizeChanged(object? sender, SizeChangedEventArgs args)
|
|
{
|
|
{
|
|
bool widthChanged = args.NewSize.Width != args.PreviousSize.Width;
|
|
bool heightChanged = args.NewSize.Height != args.PreviousSize.Height;
|
|
double newSize;
|
|
|
|
if (widthChanged && heightChanged)
|
|
{
|
|
// Maintain circle by enforcing the new size on both Width and Height.
|
|
// To do so, we will use the minimum value.
|
|
newSize = (args.NewSize.Width < args.NewSize.Height) ? args.NewSize.Width : args.NewSize.Height;
|
|
}
|
|
else if (widthChanged)
|
|
{
|
|
newSize = args.NewSize.Width;
|
|
}
|
|
else if (heightChanged)
|
|
{
|
|
newSize = args.NewSize.Height;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
Height = newSize;
|
|
Width = newSize;
|
|
}
|
|
|
|
// Calculate the FontSize of the control's text. Design guidelines have specified the
|
|
// font size to be 42% of the container. Since it's circular, 42% of either Width or Height.
|
|
// Note that we cap it to a minimum of 1, since a font size of less than 1 is an invalid value
|
|
// that will result in a failure.
|
|
double fontSize = Math.Max(1.0, Width * .42);
|
|
|
|
var initialsTextBlock = m_initialsTextBlock;
|
|
if (initialsTextBlock != null)
|
|
{
|
|
initialsTextBlock.FontSize = fontSize;
|
|
}
|
|
|
|
if (m_badgingEllipse != null && m_badgingBackgroundEllipse != null && m_badgeNumberTextBlock != null && m_badgeGlyphIcon != null)
|
|
{
|
|
// Maintain badging circle and font size by enforcing the new size on both Width and Height.
|
|
// Design guidelines have specified the font size to be 60% of the badging plate, and we want to keep
|
|
// badging plate to be about 50% of the control so that don't block the initial/profile picture.
|
|
double newSize = (args.NewSize.Width < args.NewSize.Height) ? args.NewSize.Width : args.NewSize.Height;
|
|
m_badgingEllipse.Height = newSize * 0.5;
|
|
m_badgingEllipse.Width = newSize * 0.5;
|
|
m_badgingBackgroundEllipse.Height = newSize * 0.5;
|
|
m_badgingBackgroundEllipse.Width = newSize * 0.5;
|
|
m_badgeNumberTextBlock.FontSize = Math.Max(1.0, m_badgingEllipse.Height * 0.6);
|
|
m_badgeGlyphIcon.FontSize = Math.Max(1.0, m_badgingEllipse.Height * 0.6);
|
|
}
|
|
}
|
|
|
|
void OnUnloaded(object? sender, RoutedEventArgs e)
|
|
{
|
|
//if (auto profilePictureReadAsync = m_profilePictureReadAsync.get())
|
|
//{
|
|
// profilePictureReadAsync.Cancel();
|
|
//}
|
|
}
|
|
|
|
/// <summary>
|
|
/// XAML Element for the first TextBlock matching x:Name of InitialsTextBlock.
|
|
/// </summary>
|
|
private TextBlock m_initialsTextBlock = null!;
|
|
|
|
/// <summary>
|
|
/// XAML Element for the first TextBlock matching x:Name of BadgeNumberTextBlock.
|
|
/// </summary>
|
|
private TextBlock m_badgeNumberTextBlock = null!;
|
|
|
|
/// <summary>
|
|
/// XAML Element for the first TextBlock matching x:Name of BadgeGlyphIcon.
|
|
/// </summary>
|
|
private FontIcon m_badgeGlyphIcon = null!;
|
|
|
|
/// <summary>
|
|
/// XAML Element for the first ImageBrush matching x:Name of BadgeImageBrush.
|
|
/// </summary>
|
|
private ImageBrush m_badgeImageBrush = null!;
|
|
|
|
/// <summary>
|
|
/// XAML Element for the first Ellipse matching x:Name of BadgingBackgroundEllipse.
|
|
/// </summary>
|
|
private Ellipse m_badgingEllipse = null!;
|
|
|
|
/// <summary>
|
|
/// XAML Element for the first Ellipse matching x:Name of BadgingEllipse.
|
|
/// </summary>
|
|
private Ellipse m_badgingBackgroundEllipse = null!;
|
|
|
|
/// <summary>
|
|
/// The async operation object representing the loading and assignment of the Thumbnail.
|
|
/// </summary>
|
|
//tracker_ref<winrt::IAsyncOperation<winrt::IRandomAccessStreamWithContentType>> m_profilePictureReadAsync;
|
|
|
|
/// <summary>
|
|
/// The initials from the DisplayName property.
|
|
/// </summary>
|
|
private string m_displayNameInitials = null!;
|
|
|
|
/// <summary>
|
|
/// The initials from the Contact property.
|
|
/// </summary>
|
|
private string m_contactDisplayNameInitials = null!;
|
|
|
|
/// <summary>
|
|
/// The ImageSource from the Contact property.
|
|
/// </summary>
|
|
private ImageSource m_contactImageSource = null!;
|
|
}
|