1 /++
2  + Authors: Avaxar <avaxar@nekkl.org>
3  + Copyright: Copyright © 2023, Avaxar
4  + License: $(LINK2 https://mit-license.org, MIT License)
5  +/
6 
7 module dsdl2.display;
8 @safe:
9 
10 import bindbc.sdl;
11 import dsdl2.sdl;
12 import dsdl2.pixels;
13 import dsdl2.rect;
14 
15 import std.array : uninitializedArray;
16 import std.conv : to;
17 import std.format : format;
18 import std.string : toStringz;
19 import std.typecons : Tuple;
20 
21 /++
22  + D struct that wraps `SDL_DisplayMode` containing display mode information
23  +/
24 struct DisplayMode {
25     PixelFormat pixelFormat; /// Pixel format used
26     uint[2] size; /// Size in pixels
27     uint refreshRate; /// Refresh rate per second
28     void* driverData; /// Internal driver data
29 
30     this() @disable;
31 
32     /++
33      + Contructs a `dsdl2.DisplayMode` from a vanilla `SDL_DisplayMode` from bindbc-sdl
34      +
35      + Params:
36      +   sdlDisplayMode = the `SDL_DisplayMode` struct
37      +/
38     this(SDL_DisplayMode sdlDisplayMode) {
39         this.pixelFormat = new PixelFormat(sdlDisplayMode.format);
40         this.size = [sdlDisplayMode.w.to!uint, sdlDisplayMode.h.to!uint];
41         this.refreshRate = sdlDisplayMode.refresh_rate.to!uint;
42         this.driverData = sdlDisplayMode.driverdata;
43     }
44 
45     /++
46      + Constructs a `dsdl2.DisplayMode` by feeding it its attributes
47      +
48      + Params:
49      +   pixelFormat = pixel format
50      +   size = size in pixels
51      +   refreshRate = refresh rate per second
52      +   driverData = internal driver data
53      +/
54     this(const PixelFormat pixelFormat, uint[2] size, uint refreshRate, void* driverData = null)
55     in {
56         assert(pixelFormat !is null);
57         assert(!pixelFormat.indexed);
58     }
59     do {
60         this.pixelFormat = new PixelFormat(pixelFormat.sdlPixelFormatEnum);
61         this.size = size;
62         this.refreshRate = refreshRate;
63         this.driverData = driverData;
64     }
65 
66     invariant {
67         assert(pixelFormat !is null);
68     }
69 
70     /++
71      + Formats the `dsdl2.DisplayMode` into its construction representation:
72      + `"dsdl2.DisplayMode(<pixelFormat>, <size>, <refreshRate>, <driverData>)"`
73      +
74      + Returns: the formatted `string`
75      +/
76     string toString() const {
77         return "dsdl2.DisplayMode(%s, %s, %d, %p)".format(this.pixelFormat, this.size,
78             this.refreshRate, this.driverData);
79     }
80 
81     /++
82      + Gets the internal `SDL_DisplayMode` representation
83      +
84      + Returns: `SDL_DisplayMode` with all of the attributes
85      +/
86     inout(SDL_DisplayMode) sdlDisplayMode() inout @property {
87         return inout SDL_DisplayMode(this.pixelFormat.sdlPixelFormatEnum, this.width.to!int, this.height.to!int,
88             this.refreshRate.to!int, this.driverData);
89     }
90 
91     /++
92      + Proxy to the width of the `dsdl2.DisplayMode`
93      +
94      + Returns: width of the `dsdl2.DisplayMode`
95      +/
96     ref inout(uint) width() return inout @property {
97         return this.size[0];
98     }
99 
100     /++
101      + Proxy to the height of the `dsdl2.DisplayMode`
102      +
103      + Returns: height of the `dsdl2.DisplayMode`
104      +/
105     ref inout(uint) height() return inout @property {
106         return this.size[1];
107     }
108 }
109 
110 static if (sdlSupport >= SDLSupport.v2_0_9) {
111     /++
112      + D enum that wraps `SDL_DisplayOrientation` (from SDL 2.0.9) defining orientation of displays
113      +/
114     enum DisplayOrientation {
115         /++
116          + Wraps `SDL_ORIENTATION_*` enumeration constants
117          +/
118         unknown = SDL_ORIENTATION_UNKNOWN,
119         landscape = SDL_ORIENTATION_LANDSCAPE, /// ditto
120         flippedLandscape = SDL_ORIENTATION_LANDSCAPE_FLIPPED, /// ditto
121         portrait = SDL_ORIENTATION_PORTRAIT, /// ditto
122         flippedPortrait = SDL_ORIENTATION_PORTRAIT_FLIPPED /// ditto
123     }
124 }
125 
126 /++
127  + D class that acts as a proxy for a display from a display index
128  +/
129 final class Display {
130     const uint sdlDisplayIndex; /// Display index from SDL
131 
132     this() @disable;
133 
134     private this(uint sdlDisplayIndex) {
135         this.sdlDisplayIndex = sdlDisplayIndex;
136     }
137 
138     /++
139      + Equality operator overload
140      +/
141     bool opEquals(const Display rhs) const {
142         return this.sdlDisplayIndex == rhs.sdlDisplayIndex;
143     }
144 
145     /++
146      + Gets the hash of the `dsdl2.Display`
147      +
148      + Returns: unique hash for the instance being the display index
149      +/
150     override hash_t toHash() const {
151         return cast(hash_t) this.sdlDisplayIndex;
152     }
153 
154     /++
155      + Formats the `dsdl2.Display` showing its internal information: `"dsdl2.PixelFormat(<sdlDisplayIndex>)"`
156      +
157      + Returns: the formatted `string`
158      +/
159     override string toString() const {
160         return "dsdl2.Display(%d)".format(this.sdlDisplayIndex);
161     }
162 
163     /++
164      + Wraps `SDL_GetDisplayName` which gets the display's name
165      +
166      + Returns: the display's name
167      + Throws: `dsdl2.SDLException` if failed to get the display name
168      +/
169     string name() const @property @trusted {
170         if (const(char)* name = SDL_GetDisplayName(this.sdlDisplayIndex)) {
171             return name.to!string;
172         }
173         else {
174             throw new SDLException;
175         }
176     }
177 
178     /++
179      + Wraps `SDL_GetDisplayBounds` which gets the display's bounding rectangle
180      +
181      + Returns: `dsdl2.Rect` of the display's bounding rectangle
182      + Throws: `dsdl2.SDLException` if failed to get the display bounds
183      +/
184     Rect bounds() const @property @trusted {
185         Rect rect = void;
186         if (SDL_GetDisplayBounds(this.sdlDisplayIndex, &rect.sdlRect) != 0) {
187             throw new SDLException;
188         }
189 
190         return rect;
191     }
192 
193     /++
194      + Gets the width of the display
195      + Wraps in pixels
196      +
197      + Returns: width of the display in pixels
198      +/
199     uint width() const @property @trusted {
200         return this.bounds.width;
201     }
202 
203     /++
204      + Gets the height of the display in pixels
205      +
206      + Returns: height of the display in pixels
207      +/
208     uint height() const @property @trusted {
209         return this.bounds.height;
210     }
211 
212     /++
213      + Wraps `SDL_GetNumDisplayModes` and `SDL_GetDisplayMode` which get return a list of the available
214      + display modes of the display
215      +
216      + Returns: array of the `dsdl2.DisplayMode`s
217      + Throws: `dsdl2.SDLException` if failed to get the display modes
218      +/
219     DisplayMode[] displayModes() const @property @trusted {
220         int numModes = SDL_GetNumDisplayModes(this.sdlDisplayIndex);
221         if (numModes <= 0) {
222             throw new SDLException;
223         }
224 
225         SDL_DisplayMode[] sdlModes = new SDL_DisplayMode[](numModes);
226         foreach (i; 0 .. numModes) {
227             if (SDL_GetDisplayMode(this.sdlDisplayIndex, i, &sdlModes[i]) != 0) {
228                 throw new SDLException;
229             }
230         }
231 
232         DisplayMode[] modes = uninitializedArray!(DisplayMode[])(numModes);
233         foreach (i, SDL_DisplayMode sdlMode; sdlModes) {
234             modes[i] = DisplayMode(sdlMode);
235         }
236 
237         return modes;
238     }
239 
240     /++
241      + Wraps `SDL_GetDesktopDisplayMode` which gets the desktop display mode of the display
242      +
243      + Returns: the desktop `dsdl2.DisplayMode`
244      + Throws: `dsdl2.SDLException` if failed to get the desktop display mode
245      +/
246     DisplayMode desktopDisplayMode() const @property @trusted {
247         SDL_DisplayMode sdlMode = void;
248         if (SDL_GetDesktopDisplayMode(this.sdlDisplayIndex, &sdlMode) != 0) {
249             throw new SDLException;
250         }
251 
252         return DisplayMode(sdlMode);
253     }
254 
255     /++
256      + Wraps `SDL_GetCurrentDisplayMode` which gets the current display mode for the display
257      +
258      + Returns: the current `dsdl2.DisplayMode`
259      + Throws: `dsdl2.SDLException` if failed to get the current display mode
260      +/
261     DisplayMode currentDisplayMode() const @property @trusted {
262         SDL_DisplayMode sdlMode = void;
263         if (SDL_GetCurrentDisplayMode(this.sdlDisplayIndex, &sdlMode) != 0) {
264             throw new SDLException;
265         }
266 
267         return DisplayMode(sdlMode);
268     }
269 
270     /++
271      + Wraps `SDL_GetClosestDisplayMode` which gets the closest display mode of the display to the desired mode
272      +
273      + Params:
274      +   desiredMode = the desired `dsdl2.DisplayMode`
275      + Returns: the closest available `dsdl2.DisplayMode` of the display
276      + Throws: `dsdl2.SDLException` if failed to get the closest display mode
277      +/
278     DisplayMode getClosestDisplayMode(DisplayMode desiredMode) const @trusted {
279         SDL_DisplayMode sdlDesiredMode = desiredMode.sdlDisplayMode;
280         SDL_DisplayMode sdlClosestMode = void;
281 
282         if (SDL_GetClosestDisplayMode(this.sdlDisplayIndex.to!int, &sdlDesiredMode, &sdlClosestMode) is null) {
283             throw new SDLException;
284         }
285 
286         return DisplayMode(sdlClosestMode);
287     }
288 
289     static if (sdlSupport >= SDLSupport.v2_0_4) {
290         private alias DisplayDPI = Tuple!(float, "ddpi", float, "hdpi", float, "vdpi");
291 
292         /++
293          + Wraps `SDL_GetDisplayDPI` (from SDL 2.0.4) which gets the display's DPI information
294          +
295          + Returns: named tuple of `ddpi`, `hdpi`, and `vdpi`
296          + Throws: `dsdl2.SDLException` if failed to get the display's DPI information
297          +/
298         DisplayDPI displayDPI() const @property @trusted
299         in {
300             assert(getVersion() >= Version(2, 0, 4));
301         }
302         do {
303             DisplayDPI dpi = void;
304             if (SDL_GetDisplayDPI(this.sdlDisplayIndex, &dpi.ddpi, &dpi.hdpi, &dpi.vdpi) != 0) {
305                 throw new SDLException;
306             }
307 
308             return dpi;
309         }
310     }
311 
312     static if (sdlSupport >= SDLSupport.v2_0_9) {
313         /++
314          + Wraps `SDL_GetDisplayOrientation` (from SDL 2.0.9) which gets the display's orientation
315          +
316          + Returns: `dsdl2.DisplayOrientation` of the display
317          +/
318         DisplayOrientation orientation() const @property @trusted
319         in {
320             assert(getVersion() >= Version(2, 0, 9));
321         }
322         do {
323             return cast(DisplayOrientation) SDL_GetDisplayOrientation(this.sdlDisplayIndex);
324         }
325     }
326 }
327 
328 /++
329  + Gets `dsdl2.Display` proxy instances of the available displays in the system
330  +
331  + Returns: array of proxies to the available `dsdl2.Display`s
332  + Throws: `dsdl2.SDLException` if failed to get the available displays
333  +/
334 const(Display[]) getDisplays() @trusted {
335     int numDisplays = SDL_GetNumVideoDisplays();
336     if (numDisplays <= 0) {
337         throw new SDLException;
338     }
339 
340     static Display[] displays;
341     if (displays !is null) {
342         size_t originalLength = displays.length;
343         displays.length = numDisplays;
344 
345         if (numDisplays > originalLength) {
346             foreach (i; originalLength .. numDisplays) {
347                 displays[i] = new Display(i.to!uint);
348             }
349         }
350     }
351     else {
352         displays = new Display[](numDisplays);
353         foreach (i; 0 .. numDisplays) {
354             displays[i] = new Display(i);
355         }
356     }
357 
358     return displays;
359 }