Skip to content
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ On the **Discord** page (laid out like Discord on Xbox: you and a menu on the le
The **Settings** page in the overlay has a list of sections on the left. Moving through the list shows each section on the right; A (or →) goes into it, B (or ←) comes back. On a choice, ← → changes it. Every change is saved right away.

- **Controls**: record a new controller combo (pick it, then hold the new buttons together for a second) or a new keyboard shortcut (pick it, then press the keys). Esc cancels.
- **Appearance**: accent color, the background behind the overlay (blurred game, sharp game, or dark), 12 or 24-hour clock.
- **Appearance**: size (Big, the default, fits the overlay to the screen like a console on a TV; Standard is for a desk; Huge is bigger still), accent color, the background behind the overlay (blurred game, sharp game, or dark), 12 or 24-hour clock.
- **Startup**: start with Windows, and whether to show a notification when it starts.
- **Games**: what to do with exclusive fullscreen games, and which games to skip. Open the overlay over a game and it offers to skip that one.
- **Discord**: enter your Discord app's Client ID and Client Secret (type them, or X to paste from the clipboard), open the setup guide, sign out.
Expand Down
89 changes: 89 additions & 0 deletions src/ConsoleOverlay/Overlay/HintChips.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace ConsoleOverlay.Overlay;

/// <summary>
/// Turns a hint like "A select · B back · LB / RB other pages" into console-style prompts: a green A, a red B,
/// a blue X, a yellow Y, grey pills for the bumpers and triggers, and key caps for the keyboard.
/// </summary>
internal static class HintChips
{
// Longest first, so "LB / RB" wins over "B".
private static readonly string[] Prefixes =
[
"LB / RB", "LT / RT", "Q / E", "Ctrl+V", "← →", "Enter", "Esc", "Tab", "Delete", "A", "B", "X", "Y",
];

private static readonly Color Green = Color.FromRgb(0x4C, 0xB8, 0x4A);
private static readonly Color Red = Color.FromRgb(0xE2, 0x4B, 0x4B);
private static readonly Color Blue = Color.FromRgb(0x3E, 0x8E, 0xF0);
private static readonly Color Yellow = Color.FromRgb(0xF2, 0xC1, 0x3A);

public static void Fill(WrapPanel panel, FrameworkElement resources, string hint)
{
panel.Children.Clear();
var labelBrush = (Brush)resources.FindResource("SoftTextBrush");

foreach (var part in hint.Split(" · ", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
var item = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 4, 22, 4) };
string label = part;
var prefix = Prefixes.FirstOrDefault(p => part.StartsWith(p + " ", StringComparison.Ordinal));
if (prefix is not null)
{
foreach (var button in prefix.Split(prefix == "← →" ? " " : " / ", StringSplitOptions.RemoveEmptyEntries))
item.Children.Add(Chip(button.Trim()));
label = part[(prefix.Length + 1)..];
}
item.Children.Add(new TextBlock
{
Text = label,
FontSize = 14,
Foreground = labelBrush,
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(prefix is null ? 0 : 4, 0, 0, 0),
});
panel.Children.Add(item);
}
}

private static FrameworkElement Chip(string button)
{
(Color? fill, Color ink, bool round) = button switch
{
"A" => ((Color?)Green, Colors.White, true),
"B" => ((Color?)Red, Colors.White, true),
"X" => ((Color?)Blue, Colors.White, true),
"Y" => ((Color?)Yellow, Color.FromRgb(0x1A, 0x1A, 0x1A), true),
"LB" or "RB" or "LT" or "RT" => ((Color?)Color.FromArgb(0x40, 0xFF, 0xFF, 0xFF), Colors.White, false),
_ => ((Color?)null, Color.FromRgb(0xE6, 0xE9, 0xF0), false), // a keyboard key: outlined key cap
};

var text = new TextBlock
{
Text = button,
FontSize = button.Length > 2 ? 11 : 12,
FontWeight = FontWeights.Bold,
Foreground = new SolidColorBrush(ink),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
};
return new Border
{
Height = 24,
MinWidth = 24,
Padding = round ? new Thickness(0) : new Thickness(7, 0, 7, 0),
CornerRadius = new CornerRadius(round ? 12 : 7),
Background = fill is { } f ? new SolidColorBrush(f) : Brushes.Transparent,
BorderBrush = fill is null ? new SolidColorBrush(Color.FromArgb(0x70, 0xFF, 0xFF, 0xFF)) : null,
BorderThickness = fill is null ? new Thickness(1.5) : new Thickness(0),
Margin = new Thickness(0, 0, 4, 0),
Child = text,
VerticalAlignment = VerticalAlignment.Center,
};
}
}
19 changes: 16 additions & 3 deletions src/ConsoleOverlay/Overlay/OverlayWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@
<Image x:Name="SnapshotBlurred" Stretch="Fill" Margin="-40" Opacity="0" RenderOptions.BitmapScalingMode="Linear" />
<Rectangle x:Name="Dim" Fill="{StaticResource DimBrush}" Opacity="0" MouseLeftButtonDown="Backdrop_MouseLeftButtonDown" />

<!-- Everything you read and press, scaled up as a whole for the TV-distance sizes (Settings › Appearance › Size).
No background, so clicks around the card still reach the dim layer and close the overlay. -->
<Grid x:Name="Chrome">
<Grid.LayoutTransform>
<ScaleTransform x:Name="ChromeScale" />
</Grid.LayoutTransform>

<!-- Page card: frosted glass. A soft shadow, a see-through tint over the blurred game, a light sheen at the
top and a hairline edge that is brighter on top, like light catching the rim of a pane. -->
<Grid x:Name="PageCard"
Expand Down Expand Up @@ -63,7 +70,8 @@
<Grid x:Name="PageHost" />
</ScrollViewer>

<TextBlock Grid.Row="2" x:Name="PageHint" Margin="2,16,0,0" Style="{StaticResource HintText}" />
<!-- Button prompts, drawn like a console's: a colored A, a red B, LB / RB pills... -->
<WrapPanel Grid.Row="2" x:Name="PageHint" Margin="0,16,0,0" />
</Grid>
</Grid>

Expand All @@ -82,10 +90,15 @@
<TranslateTransform x:Name="DockOffset" />
</Border.RenderTransform>
<StackPanel Orientation="Horizontal">
<TextBlock Text="LB" FontSize="10" FontWeight="Bold" Foreground="{StaticResource IconBrush}" VerticalAlignment="Center" Margin="8,0,6,0" />
<Border Background="#33FFFFFF" CornerRadius="7" Height="24" Padding="8,0" VerticalAlignment="Center" Margin="8,0,6,0">
<TextBlock Text="LB" FontSize="11" FontWeight="Bold" Foreground="White" VerticalAlignment="Center" />
</Border>
<StackPanel x:Name="DockItems" Orientation="Horizontal" />
<TextBlock Text="RB" FontSize="10" FontWeight="Bold" Foreground="{StaticResource IconBrush}" VerticalAlignment="Center" Margin="6,0,8,0" />
<Border Background="#33FFFFFF" CornerRadius="7" Height="24" Padding="8,0" VerticalAlignment="Center" Margin="6,0,8,0">
<TextBlock Text="RB" FontSize="11" FontWeight="Bold" Foreground="White" VerticalAlignment="Center" />
</Border>
</StackPanel>
</Border>
</Grid>
</Grid>
</Window>
37 changes: 34 additions & 3 deletions src/ConsoleOverlay/Overlay/OverlayWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ private sealed class Page
private int _selected;
private IntPtr _currentWindow;
private string _background = "Blur";
private string _uiSize = "Big";
private Native.RECT _monitor;
private string _clockFormat = "System";

public event Action? CloseRequested;
Expand Down Expand Up @@ -316,6 +318,8 @@ public void UpdateSettings(AppSettings settings)
foreach (var page in _pages)
AnimateDock(page, page == _pages[_selected]);

_uiSize = settings.UiSize;
if (IsVisible) ApplyScale();
_background = settings.Background;
_clockFormat = settings.ClockFormat;
if (IsVisible)
Expand Down Expand Up @@ -373,11 +377,14 @@ private void ShowHint()
hint = System.Text.RegularExpressions.Regex.Replace(hint, @"\bA\b", "Enter");
hint = System.Text.RegularExpressions.Regex.Replace(hint, @"\bB\b", "Esc");
}
if (PageHint.Text == hint) return;
PageHint.Text = hint;
if (hint == _shownHint) return;
_shownHint = hint;
HintChips.Fill(PageHint, this, hint);
PageHint.BeginAnimation(OpacityProperty, new DoubleAnimation(0.2, 1, new Duration(TimeSpan.FromMilliseconds(200))));
}

private string? _shownHint;

/// <summary>The keyboard does everything the controller does, through the same commands.</summary>
private static NavCommand? KeyToCommand(Key key) => key switch
{
Expand Down Expand Up @@ -504,6 +511,7 @@ internal void ShowOn(Native.RECT monitor, bool exclusiveMode, BitmapSource? snap
OpaqueBackdrop.Visibility = useSnapshot ? Visibility.Collapsed : Visibility.Visible;

_currentWindow = currentWindow;
_monitor = monitor;
DeactivateLogic(); // reopened during the close animation: start fresh
_tabs.SetWindows(windows, currentWindow);
_sound.Prefetch(currentWindow);
Expand All @@ -517,6 +525,7 @@ internal void ShowOn(Native.RECT monitor, bool exclusiveMode, BitmapSource? snap
Show();

Native.SetWindowPos(Handle, Native.HWND_TOPMOST, monitor.Left, monitor.Top, monitor.Width, monitor.Height, Native.SWP_SHOWWINDOW);
ApplyScale();
Activate();
_selected = 0;
SelectPage(0, focusFirst: true);
Expand Down Expand Up @@ -625,6 +634,27 @@ public void HideAnimated(Action? after)
Root.BeginAnimation(OpacityProperty, fade);
}

/// <summary>
/// Sizes everything for the screen, the way a console does for a TV: Big and Huge fit the overlay to the
/// screen's height, so it looks the same on a 1080p or 4K TV and is readable from the couch.
/// </summary>
private void ApplyScale()
{
double dpi = VisualTreeHelper.GetDpi(this).DpiScaleY;
double height = Math.Max(600, _monitor.Height / Math.Max(dpi, 0.5)); // in WPF units
double scale = _uiSize switch
{
"Standard" => 1.0,
"Huge" => height / 660,
_ => height / 760,
};
scale = Math.Clamp(scale, 1.0, 2.5);
ChromeScale.ScaleX = ChromeScale.ScaleY = scale;
_scale = scale * dpi;
}

private double _scale = 1;

private readonly Dictionary<UIElement, DispatcherTimer> _cacheTimers = [];

/// <summary>
Expand All @@ -634,7 +664,8 @@ public void HideAnimated(Action? after)
private void CacheWhileMoving(UIElement element, TimeSpan duration)
{
if (RenderCapability.Tier >> 16 == 0) return; // in software drawing a cache only adds work
element.CacheMode ??= new BitmapCache { SnapsToDevicePixels = true };
// Rendered at the final on-screen size, so the cached picture isn't blurry when the UI is scaled up.
element.CacheMode ??= new BitmapCache { SnapsToDevicePixels = true, RenderAtScale = _scale };
if (!_cacheTimers.TryGetValue(element, out var timer))
{
timer = new DispatcherTimer(DispatcherPriority.Background);
Expand Down
2 changes: 2 additions & 0 deletions src/ConsoleOverlay/Overlay/SettingsPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,8 @@ private void BuildLook()
Header("Look", first: true);
Choice("accent", "Accent color", "The focus ring and the current page in the dock",
Appearance.Accents.Select(a => (a.Value, a.Label)).ToArray(), () => Settings.AccentColor, v => Settings.AccentColor = v);
Choice("size", "Size", "Big fits the screen, for playing from the couch",
Appearance.Sizes, () => Settings.UiSize, v => Settings.UiSize = v);
Choice("background", "Background", "What you see behind the overlay",
Appearance.Backgrounds, () => Settings.Background, v => Settings.Background = v);
Choice("clock", "Clock", "The time at the top of every page",
Expand Down
3 changes: 3 additions & 0 deletions src/ConsoleOverlay/Settings/AppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public sealed class AppSettings
/// <summary>What shows behind the overlay: Blur (the game, blurred), Clear (the game, sharp) or Dark.</summary>
public string Background { get; set; } = "Blur";

/// <summary>How big everything is: Standard (desk), Big (fits the screen, good for a TV) or Huge.</summary>
public string UiSize { get; set; } = "Big";

/// <summary>Clock in the overlay: System (follows Windows), 24h or 12h.</summary>
public string ClockFormat { get; set; } = "System";

Expand Down
7 changes: 7 additions & 0 deletions src/ConsoleOverlay/Settings/Appearance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ public static readonly (string Value, string Label)[] Backgrounds =
("Dark", "Dark"),
];

public static readonly (string Value, string Label)[] Sizes =
[
("Standard", "Standard"),
("Big", "Big (TV)"),
("Huge", "Huge"),
];

public static readonly (string Value, string Label)[] ClockFormats =
[
("System", "Like Windows"),
Expand Down
Loading