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
13 changes: 13 additions & 0 deletions src/OpenClaw.Tray.WinUI/Controls/BrandMark.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<UserControl
x:Class="OpenClawTray.Controls.BrandMark"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
AutomationProperties.AccessibilityView="Raw"
IsHitTestVisible="False"
IsTabStop="False">

<Image x:Name="MarkImage"
Stretch="Uniform"
AutomationProperties.AccessibilityView="Raw"/>
</UserControl>
47 changes: 47 additions & 0 deletions src/OpenClaw.Tray.WinUI/Controls/BrandMark.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using OpenClawTray.Helpers;

namespace OpenClawTray.Controls;

public sealed partial class BrandMark : UserControl
{
private const double DefaultMarkSize = 20d;

public static readonly DependencyProperty MarkSizeProperty =
DependencyProperty.Register(
nameof(MarkSize),
typeof(double),
typeof(BrandMark),
new PropertyMetadata(DefaultMarkSize, OnMarkSizeChanged));

public BrandMark()
{
InitializeComponent();
MarkImage.Source = BrandAssets.CreateRedBotMarkSource();
ApplySize(MarkSize);
}

public double MarkSize
{
get => (double)GetValue(MarkSizeProperty);
set => SetValue(MarkSizeProperty, value);
}

private static void OnMarkSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is BrandMark mark && e.NewValue is double size)
mark.ApplySize(size);
}

private void ApplySize(double size)
{
if (!double.IsFinite(size) || size < 0)
size = DefaultMarkSize;

Width = size;
Height = size;
MarkImage.Width = size;
MarkImage.Height = size;
}
}
3 changes: 2 additions & 1 deletion src/OpenClaw.Tray.WinUI/Dialogs/PairingApprovalDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.UI.Xaml.Media;
using OpenClaw.Connection;
using OpenClaw.Shared;
using OpenClawTray.Controls;
using OpenClawTray.Helpers;
using OpenClawTray.Services;
using System;
Expand Down Expand Up @@ -65,7 +66,7 @@ public PairingApprovalDialog(PairingApprovalCoordinator coordinator)
var titleBar = new Grid { Height = 48, Padding = new Thickness(16, 0, 140, 0) };
titleBar.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
titleBar.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
var titleIcon = new TextBlock { Text = "🦞", FontSize = 16, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 8, 0) };
var titleIcon = new BrandMark { MarkSize = 16, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 8, 0) };
Grid.SetColumn(titleIcon, 0);
titleBar.Children.Add(titleIcon);
var titleText = new TextBlock
Expand Down
6 changes: 3 additions & 3 deletions src/OpenClaw.Tray.WinUI/Dialogs/RecordingConsentDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using OpenClaw.Shared.Capabilities;
using OpenClawTray.Controls;
using OpenClawTray.Helpers;
using OpenClawTray.Services;
using System;
Expand Down Expand Up @@ -55,10 +56,9 @@ public RecordingConsentDialog(RecordingType type)
titleBar.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
titleBar.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });

var titleIcon = new TextBlock
var titleIcon = new BrandMark
{
Text = "🦞",
FontSize = 16,
MarkSize = 16,
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(0, 0, 8, 0)
};
Expand Down
11 changes: 5 additions & 6 deletions src/OpenClaw.Tray.WinUI/Dialogs/WelcomeDialog.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using OpenClawTray.Controls;
using OpenClawTray.Helpers;
using OpenClawTray.Services;
using System;
Expand Down Expand Up @@ -45,10 +46,9 @@ public WelcomeDialog()
Spacing = 12,
HorizontalAlignment = HorizontalAlignment.Center
};
headerPanel.Children.Add(new TextBlock
headerPanel.Children.Add(new BrandMark
{
Text = "🦞",
FontSize = 48
MarkSize = 48
});
headerPanel.Children.Add(new TextBlock
{
Expand Down Expand Up @@ -130,10 +130,9 @@ public WelcomeDialog()
outerGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });

var titleBar = new Grid { Padding = new Thickness(16, 0, 140, 0) };
var titleIcon = new TextBlock
var titleIcon = new BrandMark
{
Text = "🦞",
FontSize = 20,
MarkSize = 20,
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(0, 0, 10, 0)
};
Expand Down
10 changes: 10 additions & 0 deletions src/OpenClaw.Tray.WinUI/Helpers/BrandAssets.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Microsoft.UI.Xaml.Media.Imaging;

namespace OpenClawTray.Helpers;

public static class BrandAssets
{
public const string RedBotMarkUri = "ms-appx:///Assets/Square44x44Logo.targetsize-256_altform-unplated.png";

public static BitmapImage CreateRedBotMarkSource() => new(new Uri(RedBotMarkUri));
}
3 changes: 0 additions & 3 deletions src/OpenClaw.Tray.WinUI/Helpers/FluentIconCatalog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,6 @@ public static class FluentIconCatalog
public const string ChevronR = "\uE76C"; // ChevronRight
public const string Check = "\uE73E"; // CheckMark

// ── Brand placeholder ──────────────────────────────────────────
public const string Brand = "🦞";

// ── Diagnostics page glyphs ────────────────────────────────────
// Added 2026-05 for the Diagnostics surface
// (src/OpenClaw.Tray.WinUI/Pages/DebugPage.xaml). Keep these here
Expand Down
3 changes: 2 additions & 1 deletion src/OpenClaw.Tray.WinUI/Pages/AgentEventsPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
x:Class="OpenClawTray.Pages.AgentEventsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:OpenClawTray.Controls"
xmlns:helpers="using:OpenClawTray.Helpers">

<Grid Padding="24,16,24,16" MaxWidth="900" HorizontalAlignment="Stretch">
Expand Down Expand Up @@ -140,7 +141,7 @@
<!-- Empty state -->
<StackPanel x:Name="EmptyState" Grid.Row="3"
VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="8">
<TextBlock Text="🤖" FontSize="48" HorizontalAlignment="Center"/>
<controls:BrandMark MarkSize="48" HorizontalAlignment="Center"/>
<TextBlock x:Uid="AgentEventsPage_TextBlock_96" Text="No events in this session"
Style="{StaticResource SubtitleTextBlockStyle}"
HorizontalAlignment="Center"/>
Expand Down
6 changes: 5 additions & 1 deletion src/OpenClaw.Tray.WinUI/Windows/HubWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
x:Class="OpenClawTray.Windows.HubWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:OpenClawTray.Controls"
xmlns:winex="using:WinUIEx"
Title="OpenClaw"
MinWidth="1000" MinHeight="400">
Expand Down Expand Up @@ -60,7 +61,10 @@
</StackPanel>

<!-- Icon + Title -->
<TextBlock Grid.Column="1" Text="🦞" FontSize="18" VerticalAlignment="Center" Margin="8,0,8,0" Translation="0,-1,0"/>
<controls:BrandMark Grid.Column="1"
MarkSize="18"
VerticalAlignment="Center"
Margin="8,0,8,0"/>
<TextBlock x:Uid="TitleText" Grid.Column="2" x:Name="TitleText" Text="OpenClaw Windows Companion"
FontSize="13" VerticalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}"/>
Expand Down
85 changes: 85 additions & 0 deletions tests/OpenClaw.Tray.Tests/BrandMarkContractTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
namespace OpenClaw.Tray.Tests;

public sealed class BrandMarkContractTests
{
private const string LobsterEmoji = "\ud83e\udd9e";
private const string RobotEmoji = "\ud83e\udd16";
private const string RedBotAssetName = "Square44x44Logo.targetsize-256_altform-unplated.png";
private static string RepoRoot => TestRepositoryPaths.GetRepositoryRoot();

private static string ReadTrayFile(params string[] pathParts)
=> File.ReadAllText(Path.Combine(new[] { RepoRoot, "src", "OpenClaw.Tray.WinUI" }.Concat(pathParts).ToArray()));

[Fact]
public void BrandMark_UsesSharedRedBotImageAsset()
{
var xaml = ReadTrayFile("Controls", "BrandMark.xaml");
var code = ReadTrayFile("Controls", "BrandMark.xaml.cs");
var assets = ReadTrayFile("Helpers", "BrandAssets.cs");
var redBotAssetPath = Path.Combine(
RepoRoot,
"src",
"OpenClaw.Tray.WinUI",
"Assets",
RedBotAssetName);

Assert.Contains("<Image", xaml);
Assert.Contains("AutomationProperties.AccessibilityView=\"Raw\"", xaml);
Assert.Contains("IsHitTestVisible=\"False\"", xaml);
Assert.Contains("IsTabStop=\"False\"", xaml);
Assert.Contains("BrandAssets.CreateRedBotMarkSource()", code);
Assert.Contains(RedBotAssetName, assets);
Assert.True(File.Exists(redBotAssetPath), $"BrandMark asset is missing: {redBotAssetPath}");
Assert.DoesNotContain("FontIcon", xaml);
Assert.DoesNotContain($"Text=\"{LobsterEmoji}\"", xaml);
}

[Fact]
public void KnownBrandSurfaces_UseBrandMark()
{
Assert.Contains("controls:BrandMark", ReadTrayFile("Windows", "HubWindow.xaml"));
Assert.Contains("controls:BrandMark", ReadTrayFile("Pages", "AgentEventsPage.xaml"));

var expectedCodeFiles = new[]
{
Path.Combine("Dialogs", "WelcomeDialog.cs"),
Path.Combine("Dialogs", "PairingApprovalDialog.cs"),
Path.Combine("Dialogs", "RecordingConsentDialog.cs"),
};

foreach (var file in expectedCodeFiles)
{
Assert.Contains("new BrandMark", ReadTrayFile(file.Split(Path.DirectorySeparatorChar)));
}
}

[Fact]
public void TrayWinUiSources_DoNotRenderEmojiBrandMarks()
{
var sourceRoot = Path.Combine(RepoRoot, "src", "OpenClaw.Tray.WinUI");
var offenders = Directory
.EnumerateFiles(sourceRoot, "*", SearchOption.AllDirectories)
.Where(path => path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase)
|| path.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase))
.Where(path => !IsBuildArtifact(sourceRoot, path))
.SelectMany(path => File.ReadLines(path)
.Select((line, index) => (path, line, lineNumber: index + 1)))
.Where(item => item.line.Contains(LobsterEmoji, StringComparison.Ordinal)
|| item.line.Contains(RobotEmoji, StringComparison.Ordinal))
.Select(item => $"{Path.GetRelativePath(RepoRoot, item.path)}:{item.lineNumber}")
.ToArray();

Assert.True(
offenders.Length == 0,
"Use the shared BrandMark control instead of emoji brand marks:"
+ Environment.NewLine
+ string.Join(Environment.NewLine, offenders));

static bool IsBuildArtifact(string sourceRoot, string path)
{
var relative = Path.GetRelativePath(sourceRoot, path);
return relative.StartsWith($"bin{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase)
|| relative.StartsWith($"obj{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase);
}
}
}
8 changes: 5 additions & 3 deletions tests/OpenClaw.Tray.Tests/DiagnosticsPageContractTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -439,8 +439,10 @@ public void HubWindow_NavPaneToggle_LivesInTitleBarAndHidesBuiltInToggle()
Assert.Contains("BorderBrush=\"Transparent\"", xaml);
Assert.Contains("BorderThickness=\"0\"", xaml);
Assert.Contains("FontSize=\"16\"", xaml);
Assert.Contains("Text=\"\ud83e\udd9e\" FontSize=\"18\"", xaml);
Assert.Contains("Translation=\"0,-1,0\"", xaml);
Assert.Contains("xmlns:controls=\"using:OpenClawTray.Controls\"", xaml);
Assert.Contains("<controls:BrandMark Grid.Column=\"1\"", xaml);
Assert.Contains("MarkSize=\"18\"", xaml);
Assert.DoesNotContain("Translation=\"0,-1,0\"", xaml);
Assert.Contains("IsPaneToggleButtonVisible=\"False\"", xaml);
Assert.Contains("x:Name=\"NavContentHost\"", xaml);
Assert.Contains("x:Name=\"NavContentClip\"", xaml);
Expand All @@ -449,7 +451,7 @@ public void HubWindow_NavPaneToggle_LivesInTitleBarAndHidesBuiltInToggle()

var titleBarIndex = xaml.IndexOf("x:Name=\"AppTitleBar\"", StringComparison.Ordinal);
var toggleIndex = xaml.IndexOf("x:Name=\"NavPaneToggleButton\"", StringComparison.Ordinal);
var iconIndex = xaml.IndexOf("Text=\"\ud83e\udd9e\"", StringComparison.Ordinal);
var iconIndex = xaml.IndexOf("<controls:BrandMark Grid.Column=\"1\"", StringComparison.Ordinal);
var navViewIndex = xaml.IndexOf("x:Name=\"NavView\"", StringComparison.Ordinal);
Assert.True(titleBarIndex >= 0, "The hub title bar must exist.");
Assert.True(toggleIndex > titleBarIndex, "The nav pane toggle must live inside the title bar block.");
Expand Down
11 changes: 3 additions & 8 deletions tests/OpenClaw.Tray.Tests/FluentIconCatalogTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ namespace OpenClaw.Tray.Tests;

/// <summary>
/// Pins the <c>FluentIconCatalog</c> contract: every advertised glyph is a
/// single Unicode Private Use Area character (or the documented Brand
/// emoji), and the helper builder uses the SymbolThemeFontFamily resource.
/// single Unicode Private Use Area character, and the helper builder uses
/// the SymbolThemeFontFamily resource.
///
/// We parse the source rather than reflect on the assembly because
/// <c>OpenClaw.Tray.Tests</c> is a pure net10.0 project that doesn't
Expand All @@ -24,7 +24,6 @@ public sealed class FluentIconCatalogTests
"Add", "Back", "Sync", "Lock", "Plug", "MoreOverflow",
"People", "Money", "ServerEnvironment", "CapabilityOff", "Channels",
"ChevronR", "Check",
"Brand",
// Diagnostics surface (see src/OpenClaw.Tray.WinUI/Pages/DebugPage.xaml).
"Bug", "Briefcase", "Folder", "Copy", "Document", "Refresh", "Reset", "Clear", "Develop",
// Workspace surface (see src/OpenClaw.Tray.WinUI/Pages/WorkspacePage.xaml).
Expand All @@ -41,8 +40,7 @@ private static string ReadCatalogSource()

private static IDictionary<string, string> ParseConstants(string source)
{
// Matches: public const string Name = "\uXXXX"; or
// public const string Name = "🦞";
// Matches: public const string Name = "\uXXXX";
var rx = new Regex(
@"public\s+const\s+string\s+(?<name>\w+)\s*=\s*""(?<value>(?:\\u[0-9A-Fa-f]{4}|[^""\\]|\\.)*)"";",
RegexOptions.Compiled);
Expand Down Expand Up @@ -74,9 +72,6 @@ public void PuaConstants_AreSingleCharacterInPrivateUseArea()
var map = ParseConstants(src);
foreach (var name in ExpectedConstants)
{
if (name == "Brand")
continue; // Brand is an emoji surrogate pair by design.

Assert.True(map.TryGetValue(name, out var value),
$"FluentIconCatalog.{name} not found in source");
Assert.True(value!.Length == 1,
Expand Down