CmdPal: Handle an empty icon in dock items (#45968)

## Summary of the Pull Request

This PR allows dock to handle items without an icon better:

- Hides the icon element when a dock item is not visible and center text
labels in vertical dock modes.
- Adds an icon to clock dock wrapper, so it appears in settings.
- Stretches buttons with icon and labels in the vertical docks to the
full width.
- Fixes `IconInfoViewModel.IconForTheme` method implementation.

## Pictures? Pictures!

Horizontal Dock:

<img width="393" height="84" alt="image"
src="https://github.com/user-attachments/assets/d12aa406-da9d-4bd2-b464-595deab41d2e"
/>

<img width="390" height="105" alt="image"
src="https://github.com/user-attachments/assets/c28d65c0-1023-47d0-9ff5-85c74a18c342"
/>


Vertical Dock:

<img width="153" height="258" alt="image"
src="https://github.com/user-attachments/assets/e1be59d9-fa1f-4a24-b0c1-e8cff4211906"
/>

Settings:

<img width="1266" height="713" alt="image"
src="https://github.com/user-attachments/assets/722d47da-c668-4df2-9f1d-bf7808333be4"
/>



<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Closes: #45955
This commit is contained in:
Jiří Polášek
2026-03-09 12:17:56 +01:00
committed by GitHub
parent b72224ea0b
commit c6f1a09fa2
4 changed files with 59 additions and 34 deletions

View File

@@ -22,7 +22,7 @@ public partial class IconInfoViewModel : ObservableObject, IIconInfo
public IconDataViewModel Dark { get; private set; }
public IconDataViewModel IconForTheme(bool light) => Light = light ? Light : Dark;
public IconDataViewModel IconForTheme(bool light) => light ? Light : Dark;
public bool HasIcon(bool light) => IconForTheme(light).HasIcon;

View File

@@ -92,13 +92,14 @@
<StackPanel
x:Name="TextPanel"
Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Visibility="{TemplateBinding TextVisibility}">
<TextBlock
x:Name="TitleText"
MinWidth="24"
MaxWidth="100"
HorizontalAlignment="Left"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
FontFamily="Segoe UI"
FontSize="12"
@@ -110,13 +111,13 @@
x:Name="SubtitleText"
MaxWidth="100"
Margin="0,-2,0,0"
HorizontalAlignment="Left"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
FontFamily="Segoe UI"
FontSize="10"
Foreground="{ThemeResource TextFillColorTertiary}"
Text="{TemplateBinding Subtitle}"
TextAlignment="Center"
TextAlignment="Left"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap" />
</StackPanel>
@@ -173,6 +174,21 @@
<VisualState x:Name="IconHidden">
<VisualState.Setters>
<Setter Target="ContentGrid.ColumnSpacing" Value="0" />
<Setter Target="IconPresenter.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="TextAlignmentStates">
<VisualState x:Name="TextLeftAligned">
<VisualState.Setters>
<Setter Target="TitleText.TextAlignment" Value="Left" />
<Setter Target="SubtitleText.TextAlignment" Value="Left" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="TextCentered">
<VisualState.Setters>
<Setter Target="TitleText.TextAlignment" Value="Center" />
<Setter Target="SubtitleText.TextAlignment" Value="Center" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>

View File

@@ -138,50 +138,46 @@ public sealed partial class DockItemControl : Control
private void UpdateIconVisibility()
{
if (Icon is IconBox icon)
var shouldShowIcon = ShouldShowIcon();
if (_iconPresenter is not null)
{
var dt = icon.DataContext;
var src = icon.Source;
if (_iconPresenter is not null)
{
// n.b. this might be wrong - I think we always have an Icon (an IconBox),
// we need to check if the box has an icon
_iconPresenter.Visibility = Icon is null ? Visibility.Collapsed : Visibility.Visible;
}
UpdateIconVisibilityState();
_iconPresenter.Visibility = shouldShowIcon ? Visibility.Visible : Visibility.Collapsed;
}
UpdateIconVisibilityState();
}
private void UpdateIconVisibilityState()
{
var hasIcon = Icon is not null;
VisualStateManager.GoToState(this, hasIcon ? "IconVisible" : "IconHidden", true);
VisualStateManager.GoToState(this, ShouldShowIcon() ? "IconVisible" : "IconHidden", true);
}
private void UpdateAlignment()
{
// If this item has both an icon and a label, left align so that the
// icons don't wobble if the text changes.
//
// Otherwise, center align.
var requestedTheme = ActualTheme;
var isLight = requestedTheme == ElementTheme.Light;
var showText = HasText;
if (Icon is IconBox icoBox &&
icoBox.DataContext is DockItemViewModel item &&
item.Icon is IconInfoViewModel icon)
HorizontalAlignment = HorizontalAlignment.Stretch;
UpdateTextAlignmentState();
}
private bool ShouldShowIcon()
{
if (Icon is IconBox icoBox)
{
var showIcon = icon is not null && icon.HasIcon(isLight);
if (showText && showIcon)
if (icoBox.SourceKey is IconInfoViewModel icon)
{
HorizontalAlignment = HorizontalAlignment.Left;
return;
return icon.HasIcon(ActualTheme == ElementTheme.Light);
}
return icoBox.Source is not null;
}
HorizontalAlignment = HorizontalAlignment.Stretch;
return Icon is not null;
}
private void UpdateTextAlignmentState()
{
var verticalDock = _parentDock?.DockSide is DockSide.Left or DockSide.Right;
var shouldCenterText = verticalDock && !ShouldShowIcon();
VisualStateManager.GoToState(this, shouldCenterText ? "TextCentered" : "TextLeftAligned", true);
}
private void UpdateAllVisibility()
@@ -195,12 +191,14 @@ public sealed partial class DockItemControl : Control
{
base.OnApplyTemplate();
IsEnabledChanged -= OnIsEnabledChanged;
ActualThemeChanged -= DockItemControl_ActualThemeChanged;
PointerEntered -= Control_PointerEntered;
PointerExited -= Control_PointerExited;
Loaded -= DockItemControl_Loaded;
Unloaded -= DockItemControl_Unloaded;
ActualThemeChanged += DockItemControl_ActualThemeChanged;
PointerEntered += Control_PointerEntered;
PointerExited += Control_PointerExited;
Loaded += DockItemControl_Loaded;
@@ -229,12 +227,19 @@ public sealed partial class DockItemControl : Control
{
_parentDock = dock;
UpdateInnerMarginForDockSide(dock.DockSide);
UpdateAllVisibility();
_dockSideCallbackToken = dock.RegisterPropertyChangedCallback(
DockControl.DockSideProperty,
OnParentDockSideChanged);
}
}
private void DockItemControl_ActualThemeChanged(FrameworkElement sender, object args)
{
UpdateIconVisibility();
UpdateAlignment();
}
private void DockItemControl_Unloaded(object sender, RoutedEventArgs e)
{
if (_parentDock is not null && _dockSideCallbackToken >= 0)
@@ -252,6 +257,7 @@ public sealed partial class DockItemControl : Control
if (sender is DockControl dock)
{
UpdateInnerMarginForDockSide(dock.DockSide);
UpdateAlignment();
}
}

View File

@@ -57,7 +57,10 @@ public sealed partial class TimeDateCommandsProvider : CommandProvider
var wrappedBand = new WrappedDockItem(
[_bandItem],
"com.microsoft.cmdpal.timedate.dockBand",
Resources.Microsoft_plugin_timedate_dock_band_title);
Resources.Microsoft_plugin_timedate_dock_band_title)
{
Icon = Icons.TimeDateExtIcon,
};
return new ICommandItem[] { wrappedBand };
}