| | 1 | | using System; |
| | 2 | | using System.Collections.Generic; |
| | 3 | | using System.Linq; |
| | 4 | |
|
| | 5 | | using static Imagini.ErrorHandler; |
| | 6 | | using static SDL2.SDL_hints; |
| | 7 | | using static SDL2.SDL_video; |
| | 8 | |
|
| | 9 | | namespace Imagini |
| | 10 | | { |
| | 11 | | /// <summary> |
| | 12 | | /// Defines window display mode. |
| | 13 | | /// </summary> |
| | 14 | | public enum WindowMode |
| | 15 | | { |
| | 16 | | Windowed, |
| | 17 | | Borderless, |
| | 18 | | Fullscreen, |
| | 19 | | BorderlessFullscreen |
| | 20 | | } |
| | 21 | |
|
| | 22 | | /// <summary> |
| | 23 | | /// Defines app window settings. |
| | 24 | | /// </summary> |
| | 25 | | public class WindowSettings : ICloneable |
| | 26 | | { |
| | 27 | | /// <summary> |
| | 28 | | /// Width of the window in pixels. |
| | 29 | | /// </summary> |
| | 30 | | /// <remarks>Default is 800.</remarks> |
| 298 | 31 | | public int WindowWidth { get; set; } = 800; |
| | 32 | | /// <summary> |
| | 33 | | /// Height of the window in pixels. |
| | 34 | | /// </summary> |
| | 35 | | /// <remarks>Default is 600.</remarks> |
| 298 | 36 | | public int WindowHeight { get; set; } = 600; |
| | 37 | | /// <summary> |
| | 38 | | /// Flag indicating if the window should be fullscreen. |
| | 39 | | /// </summary> |
| | 40 | | /// <remarks>Default is false.</remarks> |
| 234 | 41 | | public bool IsFullscreen { get; set; } = false; |
| | 42 | | /// <summary> |
| | 43 | | /// Indicates if the vertical sync should be enabled (if supported). |
| | 44 | | /// </summary> |
| | 45 | | /// <remarks>Default is true.</remarks> |
| 233 | 46 | | public bool VSync { get; set; } = true; |
| | 47 | | /// <summary> |
| | 48 | | /// Flag indicating if the window should be visible. |
| | 49 | | /// </summary> |
| | 50 | | /// <remarks> |
| | 51 | | /// Used only on window creation, to change window visibility after |
| | 52 | | /// creation use <see cref="Window.Show" /> and <see cref="Window.Hide" />. |
| | 53 | | /// Default is true. |
| | 54 | | /// </remarks> |
| 221 | 55 | | public bool IsVisible { get; set; } = true; |
| | 56 | | /// <summary> |
| | 57 | | /// Flag indicating if the window should be borderless. |
| | 58 | | /// </summary> |
| | 59 | | /// <remarks>Default is false.</remarks> |
| 231 | 60 | | public bool IsBorderless { get; set; } = false; |
| | 61 | | /// <summary> |
| | 62 | | /// Flag indicating if the window should be resizable. |
| | 63 | | /// </summary> |
| | 64 | | /// <remarks> |
| | 65 | | /// Should be specified at window creation, cannot be changed later. |
| | 66 | | /// Default is false. |
| | 67 | | /// </remarks> |
| 139 | 68 | | public bool IsResizable { get; set; } = false; |
| | 69 | | /// <summary> |
| | 70 | | /// Flag indicating if the OS should treat the window as high-DPI aware. |
| | 71 | | /// </summary> |
| | 72 | | /// <remarks> |
| | 73 | | /// Should be specified at window creation, cannot be changed later. |
| | 74 | | /// Default is false. |
| | 75 | | /// </remarks> |
| 76 | 76 | | public bool AllowHighDpi { get; set; } = false; |
| | 77 | |
|
| | 78 | | /// <summary> |
| | 79 | | /// Specifies a display index on which the window should be positioned. |
| | 80 | | /// </summary> |
| | 81 | | /// <remarks>Default is 0 (primary display).</remarks> |
| 229 | 82 | | public int DisplayIndex { get; set; } = 0; |
| | 83 | | /// <summary> |
| | 84 | | /// Specifies the window title. |
| | 85 | | /// </summary> |
| | 86 | | /// <remarks> |
| | 87 | | /// Used only on window creation, to change window title after |
| | 88 | | /// creation use <see cref="Window.Title" />. |
| | 89 | | /// </remarks> |
| 305 | 90 | | public string Title { get; set; } = "Imagini"; |
| | 91 | |
|
| | 92 | | /// <summary> |
| | 93 | | /// Gets or sets the window mode. |
| | 94 | | /// </summary> |
| | 95 | | public WindowMode WindowMode |
| | 96 | | { |
| 155 | 97 | | get => GetWindowMode(IsFullscreen, IsBorderless); |
| | 98 | | set |
| | 99 | | { |
| 0 | 100 | | IsFullscreen = |
| 0 | 101 | | value == WindowMode.Fullscreen || |
| 0 | 102 | | value == WindowMode.BorderlessFullscreen; |
| 0 | 103 | | IsBorderless = |
| 0 | 104 | | value == WindowMode.Borderless || |
| 0 | 105 | | value == WindowMode.BorderlessFullscreen; |
| 0 | 106 | | } |
| | 107 | | } |
| | 108 | |
|
| | 109 | | /// <summary> |
| | 110 | | /// Gets or sets the display mode used when the app is fullscreen. |
| | 111 | | /// </summary> |
| 78 | 112 | | public DisplayMode FullscreenDisplayMode { get; set; } = DisplayMode.GetCurrent(0); |
| | 113 | |
|
| | 114 | | private WindowMode GetWindowMode(bool isFullscreen, bool isBorderless) |
| | 115 | | { |
| 462 | 116 | | if (!isFullscreen && !isBorderless) return WindowMode.Windowed; |
| 2 | 117 | | else if (!isFullscreen && isBorderless) return WindowMode.Borderless; |
| 4 | 118 | | else if (isFullscreen && !isBorderless) return WindowMode.Fullscreen; |
| 0 | 119 | | else return WindowMode.BorderlessFullscreen; |
| | 120 | | } |
| | 121 | |
|
| | 122 | | /// <summary> |
| | 123 | | /// Returns SDL window creation flags. |
| | 124 | | /// </summary> |
| | 125 | | public uint GetFlags() |
| | 126 | | { |
| 76 | 127 | | var result = 0u; |
| | 128 | | // result |= SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayIndex); |
| 76 | 129 | | if (IsFullscreen) result |= (uint)SDL_WindowFlags.SDL_WINDOW_FULLSCREEN; |
| 76 | 130 | | if (IsVisible) |
| 56 | 131 | | result |= (uint)SDL_WindowFlags.SDL_WINDOW_SHOWN; |
| | 132 | | else |
| 20 | 133 | | result |= (uint)SDL_WindowFlags.SDL_WINDOW_HIDDEN; |
| 76 | 134 | | if (IsBorderless) result |= (uint)SDL_WindowFlags.SDL_WINDOW_BORDERLESS; |
| 139 | 135 | | if (IsResizable) result |= (uint)SDL_WindowFlags.SDL_WINDOW_RESIZABLE; |
| 76 | 136 | | if (AllowHighDpi) result |= (uint)SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI; |
| 76 | 137 | | return result; |
| | 138 | | } |
| | 139 | |
|
| | 140 | | internal void Apply(IntPtr window) |
| | 141 | | { |
| 77 | 142 | | var flags = SDL_GetWindowFlags(window); |
| 77 | 143 | | bool alreadyFullscreen = HasFlag(flags, SDL_WindowFlags.SDL_WINDOW_FULLSCREEN); |
| 77 | 144 | | bool alreadyBorderless = HasFlag(flags, SDL_WindowFlags.SDL_WINDOW_BORDERLESS); |
| | 145 | |
|
| 77 | 146 | | var curMode = GetWindowMode(alreadyFullscreen, alreadyBorderless); |
| 77 | 147 | | var curIndex = Display.GetCurrentDisplayIndexForWindow(window); |
| 77 | 148 | | var targetMode = WindowMode; |
| 77 | 149 | | var targetIndex = DisplayIndex; |
| | 150 | |
|
| | 151 | | // If the WindowMode or target display have changed, do some preparation |
| 77 | 152 | | ChangeVideoMode(window, curMode, targetMode, curIndex, targetIndex); |
| 0 | 153 | | Check(() => SDL_SetHintWithPriority(SDL_HINT_RENDER_VSYNC, |
| 0 | 154 | | VSync ? "1" : "0", SDL_HintPriority.SDL_HINT_OVERRIDE), |
| 0 | 155 | | "SDL_SetHintWithPriority(SDL_HINT_RENDER_VSYNC, OVERRIDE)"); |
| 77 | 156 | | } |
| | 157 | |
|
| | 158 | | private void ChangeVideoMode(IntPtr window, WindowMode curMode, WindowMode targetMode, |
| | 159 | | int curIndex, int targetIndex) |
| | 160 | | { |
| | 161 | | // Set target window size for Windowed and Borderless modes |
| 77 | 162 | | SDL_SetWindowSize(window, WindowWidth, WindowHeight); |
| 77 | 163 | | var flags = SDL_GetWindowFlags(window); |
| 0 | 164 | | var shouldBeFullscreen = |
| 0 | 165 | | (targetMode == WindowMode.Fullscreen) || |
| 0 | 166 | | (targetMode == WindowMode.BorderlessFullscreen); |
| | 167 | |
|
| | 168 | | // switch to windowed mode and move to corresponding display |
| | 169 | | // if the target display for fullscreen mode have changed |
| 77 | 170 | | if (curIndex != targetIndex && shouldBeFullscreen) |
| | 171 | | { |
| 0 | 172 | | Try(() => SDL_SetWindowFullscreen(window, 0), "SDL_SetWindowFullscreen"); |
| 0 | 173 | | MoveToDisplay(window, targetIndex); |
| 0 | 174 | | curMode = HasFlag(flags, SDL_WindowFlags.SDL_WINDOW_BORDERLESS) ? |
| 0 | 175 | | WindowMode.Borderless : WindowMode.Windowed; |
| 0 | 176 | | curIndex = targetIndex; |
| | 177 | | } |
| 0 | 178 | | var alreadyFullscreen = |
| 0 | 179 | | (curMode == WindowMode.Fullscreen) || |
| 0 | 180 | | (curMode == WindowMode.BorderlessFullscreen); |
| | 181 | | // Set the target params for the Fullscreen mode |
| 77 | 182 | | if (shouldBeFullscreen) |
| | 183 | | { |
| 1 | 184 | | var targetDM = FullscreenDisplayMode; |
| 0 | 185 | | Try(() => |
| 1 | 186 | | SDL_SetWindowDisplayMode(window, ref targetDM._mode), |
| 0 | 187 | | "SDL_SetWindowDisplayMode"); |
| | 188 | | } |
| 153 | 189 | | if (curMode == targetMode) return; |
| | 190 | | // Set the fullscreen mode flag |
| 1 | 191 | | var fullscreenOpt = 0u; |
| | 192 | | switch (targetMode) |
| | 193 | | { |
| | 194 | | case WindowMode.BorderlessFullscreen: |
| 0 | 195 | | fullscreenOpt = (uint)SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP; |
| 0 | 196 | | break; |
| | 197 | | case WindowMode.Fullscreen: |
| 1 | 198 | | fullscreenOpt = (uint)SDL_WindowFlags.SDL_WINDOW_FULLSCREEN; |
| | 199 | | break; |
| | 200 | | } |
| 0 | 201 | | Try(() => |
| 1 | 202 | | SDL_SetWindowFullscreen(window, fullscreenOpt), |
| 0 | 203 | | "SDL_SetWindowFullscreen"); |
| | 204 | | // Set the window border flag if it doesn't occupy the whole screen |
| | 205 | | // because SDL_WINDOW_FULLSCREEN_DESKTOP sets it automatically |
| 1 | 206 | | if (targetMode == WindowMode.Borderless) |
| 0 | 207 | | SDL_SetWindowBordered(window, 0); |
| 1 | 208 | | else if (targetMode == WindowMode.Windowed) |
| 0 | 209 | | SDL_SetWindowBordered(window, 1); |
| 1 | 210 | | } |
| | 211 | |
|
| | 212 | | private void MoveToDisplay(IntPtr window, int displayIndex, int x = 0, int y = 0) |
| | 213 | | { |
| 0 | 214 | | var display = Display.All[displayIndex]; |
| 0 | 215 | | var bounds = display.Bounds; |
| 0 | 216 | | var targetX = bounds.x + x; |
| 0 | 217 | | var targetY = bounds.y + y; |
| 0 | 218 | | SDL_SetWindowPosition(window, targetX, targetY); |
| 0 | 219 | | } |
| | 220 | |
|
| | 221 | | private bool HasFlag(uint flags, SDL_WindowFlags flag) => |
| 154 | 222 | | (flags & (uint)flag) == (uint)flag; |
| | 223 | |
|
| | 224 | | /// <summary> |
| | 225 | | /// Creates a shallow copy of this object. |
| | 226 | | /// </summary> |
| 77 | 227 | | public object Clone() => this.MemberwiseClone(); |
| | 228 | |
|
| 1 | 229 | | static WindowSettings() => Lifecycle.TryInitialize(); |
| | 230 | | } |
| | 231 | | } |