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
1 change: 1 addition & 0 deletions ImGui.App/ImGui.App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
<Compile Remove="ForceDpiAware.cs" />
<Compile Remove="GdiPlusHelper.cs" />
<Compile Remove="NativeMethods.cs" />
<Compile Remove="OverlayWindow.cs" />
<!-- Reflection-heavy; plan §2.7 explicitly skips extension auto-discovery on iOS
because AOT-trim analysers reject Assembly.GetType(string). All call sites for
this type live in ImGuiController/, which is also removed below. -->
Expand Down
60 changes: 60 additions & 0 deletions ImGui.App/ImGuiApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ public static ImGuiAppWindowState WindowState
internal static readonly PidFrameLimiter frameLimiter = new();
internal static double previousTargetFrameTimeMs = 1000.0 / 30.0;

/// <summary>Drives the native window styling that backs overlay mode (see <see cref="EnableOverlay"/>).</summary>
internal static readonly OverlayChrome overlayChrome = new();

/// <summary>
/// Gets a value indicating whether the window is currently in overlay mode (borderless,
/// always-on-top, and translucent). Toggled via <see cref="EnableOverlay"/> and
/// <see cref="DisableOverlay"/>.
/// </summary>
public static bool IsOverlayActive => overlayChrome.IsActive;

/// <summary>
/// Updates the last input time to the current time. Called by the input system when user input is detected.
/// </summary>
Expand Down Expand Up @@ -372,6 +382,43 @@ public static void Hide()
ApplyNativeVisibility(false);
}

/// <summary>
/// Switches the window into overlay mode: borderless, always-on-top, and whole-window
/// translucent, with optional click-through. Safe to call every frame to keep the opacity
/// and click-through in sync with live settings; the underlying window styles are only
/// touched when something actually changes.
/// <para>
/// Overlay styling is implemented on Windows. On other platforms <see cref="IsOverlayActive"/>
/// still reflects the request (so overlay frame-rate throttling via
/// <see cref="ImGuiAppPerformanceSettings.OverlayFps"/> still applies) but the window is not
/// restyled. Call from the thread that owns the render window (e.g. from <see cref="ImGuiAppConfig.OnRender"/>).
/// </para>
/// </summary>
/// <param name="opacity">Whole-window opacity, clamped to 0.2 (faint) – 1.0 (opaque).</param>
/// <param name="clickThrough">When true, mouse input passes through to whatever is behind the overlay.</param>
public static void EnableOverlay(float opacity = 1.0f, bool clickThrough = false) =>
overlayChrome.Enable(TryGetWindowHandle(), opacity, clickThrough);

/// <summary>
/// Locks the overlay to a corner of the active monitor's work area at the given offset and
/// size. No-op unless overlay mode is active (see <see cref="EnableOverlay"/>). Re-applies
/// only when the geometry changes, so it is cheap to call every frame. Windows-only;
/// elsewhere the consumer is responsible for positioning.
/// </summary>
/// <param name="corner">Which work-area corner to anchor the overlay to.</param>
/// <param name="offsetX">Horizontal inset (px) from the anchored corner.</param>
/// <param name="offsetY">Vertical inset (px) from the anchored corner.</param>
/// <param name="width">Overlay width in pixels (minimum 200).</param>
/// <param name="height">Overlay height in pixels (minimum 140).</param>
public static void SetOverlayGeometry(OverlayCorner corner, int offsetX, int offsetY, int width, int height) =>
overlayChrome.SetGeometry(TryGetWindowHandle(), corner, offsetX, offsetY, width, height);

/// <summary>
/// Leaves overlay mode and restores the decorated, non-topmost, opaque window. Safe to call
/// repeatedly (including when overlay mode was never entered).
/// </summary>
public static void DisableOverlay() => overlayChrome.Disable();

internal static void SetupWindowResizeHandler(ImGuiAppConfig config)
{
window!.FramebufferResize += s =>
Expand Down Expand Up @@ -419,6 +466,18 @@ internal static void UpdateWindowPerformance()
return;
}

// Overlay mode runs at its own dedicated rate. An always-on-top overlay is usually
// unfocused and may report as "not visible" to some backends, which would otherwise
// throttle it to a crawl — yet it typically shows live, continuously-updating data.
// So while overlay mode is active we bypass the focus/idle/visibility reductions and
// use OverlayFps directly.
if (IsOverlayActive)
{
IsIdle = false;
targetFrameTimeMs = settings.OverlayFps > 0 ? (1000.0 / settings.OverlayFps) : 10000.0;
return;
}

// Update idle state if idle detection is enabled
if (settings.EnableIdleDetection)
{
Expand Down Expand Up @@ -1733,6 +1792,7 @@ internal static void Reset()
ScaleFactor = 1;
GlobalScale = 1.0f;
Textures.Clear();
overlayChrome.ResetState();
Config = new();
}

Expand Down
9 changes: 9 additions & 0 deletions ImGui.App/ImGuiAppPerformanceSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ public class ImGuiAppPerformanceSettings
/// </summary>
public double NotVisibleFps { get; init; } = 2.0;

/// <summary>
/// Gets or sets the target frame rate (FPS) used while the window is in overlay mode
/// (entered via <c>ImGuiApp.EnableOverlay</c>). Because an always-on-top overlay is usually
/// unfocused — and may even report as not visible — the normal focus/idle/visibility
/// throttling would make it sluggish despite showing live data. While overlay mode is
/// active this rate is used instead, bypassing those reductions. Defaults to 30 FPS.
/// </summary>
public double OverlayFps { get; init; } = 30.0;

/// <summary>
/// Gets or sets a value indicating whether idle detection is enabled.
/// When true, the application will detect when there's no user input and reduce frame rate further.
Expand Down
48 changes: 48 additions & 0 deletions ImGui.App/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,54 @@ internal static partial class NativeMethods
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
internal static partial nint SetWindowLongPtr(nint hWnd, int nIndex, nint dwNewLong);

/// <summary>Reads a window's style/extended-style word (used to enter and restore overlay mode).</summary>
[LibraryImport("user32.dll", EntryPoint = "GetWindowLongPtrW")]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
internal static partial nint GetWindowLongPtr(nint hWnd, int nIndex);

/// <summary>Sets the per-pixel/whole-window alpha for a layered window (overlay translucency).</summary>
[LibraryImport("user32.dll")]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool SetLayeredWindowAttributes(nint hWnd, uint crKey, byte bAlpha, uint dwFlags);

/// <summary>Changes a window's size, position, and Z-order (used to make the overlay topmost and corner-locked).</summary>
[LibraryImport("user32.dll")]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool SetWindowPos(nint hWnd, nint hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);

/// <summary>Returns the monitor that contains (or is nearest to) the given window.</summary>
[LibraryImport("user32.dll")]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
internal static partial nint MonitorFromWindow(nint hWnd, uint dwFlags);

/// <summary>Retrieves monitor geometry, including the work area excluding the taskbar.</summary>
[LibraryImport("user32.dll", EntryPoint = "GetMonitorInfoW")]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool GetMonitorInfo(nint hMonitor, ref MONITORINFO lpmi);

/// <summary>A rectangle defined by its edges, matching the Win32 RECT structure.</summary>
[StructLayout(LayoutKind.Sequential)]
internal struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}

/// <summary>Monitor geometry returned by <see cref="GetMonitorInfo"/>, matching the Win32 MONITORINFO structure.</summary>
[StructLayout(LayoutKind.Sequential)]
internal struct MONITORINFO
{
public int cbSize;
public RECT rcMonitor;
public RECT rcWork;
public uint dwFlags;
}

/// <summary>Forwards a message to the previously installed window procedure.</summary>
[LibraryImport("user32.dll", EntryPoint = "CallWindowProcW")]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
Expand Down
Loading
Loading