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 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; public bool HasIcon(bool light) => IconForTheme(light).HasIcon;

View File

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

View File

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

View File

@@ -57,7 +57,10 @@ public sealed partial class TimeDateCommandsProvider : CommandProvider
var wrappedBand = new WrappedDockItem( var wrappedBand = new WrappedDockItem(
[_bandItem], [_bandItem],
"com.microsoft.cmdpal.timedate.dockBand", "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 }; return new ICommandItem[] { wrappedBand };
} }