1-- Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
2-- Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
3
4-- Convenience wrappers for the C++ UI functions and general functions
5local Engine = require 'Engine'
6local Game = require 'Game'
7local utils = require 'utils'
8local pigui = Engine.pigui
9local ui = require 'pigui.libs.forwarded'
10
11--
12-- Function: ui.rescaleUI
13--
14-- ui.rescaleUI(val, baseResolution, rescaleToScreenAspect, targetResolution)
15--
16-- Scales a set of values (normally a size or a position) based on a base
17-- resolution and the current or target resultion.
18--
19--
20-- Example:
21--
22-- > size = ui.rescaleUI(Vector2(96, 96), Vector2(1600, 900))
23--
24-- Parameters:
25--
26--   val                   - number|Vector2|Table, the values to scale
27--   baseResolution        - Vector2, the resolution at which val is valid
28--   rescaleToScreenAspect - (Optional) number, when scaling a Vector2, scale x and y
29--                           appropriately to match the given aspect ratio
30--   targetResolution      - (Optional) Vector2, the target resolution to scale
31--                           the value to. Default: current screen resolution.
32--
33-- Returns:
34--
35--   number|Vector2|Table - the scaled value
36--
37function ui.rescaleUI(val, baseResolution, rescaleToScreenAspect, targetResolution)
38	if not targetResolution then
39		targetResolution = Vector2(pigui.screen_width, pigui.screen_height)
40	end
41
42	local rescaleVector = Vector2(targetResolution.x / baseResolution.x, targetResolution.y / baseResolution.y)
43	local rescaleFactor = math.min(rescaleVector.x, rescaleVector.y)
44	local type = type(val)
45
46	if type == 'table' then
47		local result = {}
48		for k, v in pairs(val) do
49			result[k] = ui.rescaleUI(v, baseResolution, rescaleToScreenAspect, targetResolution)
50		end
51
52		return result
53	elseif type == 'userdata' and val.x and val.y then
54		return Vector2(val.x * ((rescaleToScreenAspect and rescaleVector.x) or rescaleFactor), val.y * ((rescaleToScreenAspect and rescaleVector.y) or rescaleFactor))
55	elseif type == 'number' then
56		return val * rescaleFactor
57	end
58end
59
60--
61-- Function: ui.pcall
62--
63-- ui.pcall(fun, ...)
64--
65-- Clean up the ImGui stack in case of an error
66--
67--
68-- Example:
69--
70-- >
71--
72-- Parameters:
73--
74--   fun -
75--   ... -
76--
77-- Returns:
78--
79--   nil
80--
81function ui.pcall(fun, ...)
82	local stack = pigui.GetImguiStack()
83	return xpcall(fun, function(msg)
84		return debug.traceback(msg, 2) .. "\n" .. pigui.CleanupImguiStack(stack)
85	end, ...)
86end
87
88--
89-- Function: ui.window
90--
91-- ui.window(name, params, fun)
92--
93-- Display a window
94--
95--
96-- Example:
97--
98-- >
99--
100-- Parameters:
101--
102--   name   - String, a unique name for the window,
103--            used to group its children
104--   params - Table, window options:
105--              - NoTitleBar                : Disable title-bar
106--              - NoResize                  : Disable user resizing with the lower-right grip
107--              - NoMove                    : Disable user moving the window
108--              - NoScrollbar               : Disable scrollbars (window can still scroll with
109--                                            mouse or programmatically)
110--              - NoScrollWithMouse         : Disable user vertically scrolling with mouse wheel.
111--                                            On child window, mouse wheel will be forwarded to
112--                                            the parent unless NoScrollbar is also set.
113--              - NoCollapse                : Disable user collapsing window by double-clicking
114--                                            on it
115--              - AlwaysAutoResize          : Resize every window to its content every frame
116--              - NoSavedSettings           : Never load/save settings in .ini file
117--              - NoInputs                  :
118--              - MenuBar                   : Has a menu-bar
119--              - HorizontalScrollbar       : Allow horizontal scrollbar to appear (off by default).
120--              - NoFocusOnAppearing        : Disable taking focus when transitioning from hidden to
121--                                            visible state
122--              - NoBringToFrontOnFocus     : Disable bringing window to front when taking focus
123--                                            (e.g. clicking on it or programmatically giving it
124--                                            focus)
125--              - AlwaysVerticalScrollbar   : Always show vertical scrollbar
126--              - AlwaysHorizontalScrollbar : Always show horizontal scrollbar
127--              - AlwaysUseWindowPadding    : Ensure child windows without border uses
128--                                            style.WindowPadding (ignored by default for
129--                                            non-bordered child windows, because more convenient)
130--   fun    - Function, a function that is called to define the window contents
131--
132-- Returns:
133--
134--   nil
135--
136function ui.window(name, params, fun)
137	local ok = pigui.Begin(name, params)
138	if ok then fun() end
139	pigui.End()
140end
141
142--
143-- Function: ui.group
144--
145-- ui.group(fun)
146--
147-- Display items in a group
148--
149--
150-- Example:
151--
152-- >
153--
154-- Parameters:
155--
156--   fun - Function, a function that is called to define the group contents
157--
158-- Returns:
159--
160--   nil
161--
162function ui.group(fun)
163	pigui.BeginGroup()
164	fun()
165	pigui.EndGroup()
166end
167
168--
169-- Function: ui.popup
170--
171-- ui.popup(name, params, fun)
172--
173-- Display a popup window
174--
175--
176-- Example:
177--
178-- >
179--
180-- Parameters:
181--
182--   name - String, a unique name for the window,
183--          used to group its children
184--   fun  - Function, a function that is called to define the popup contents
185--
186-- Returns:
187--
188--   nil
189--
190function ui.popup(name, fun)
191	if pigui.BeginPopup(name) then
192		fun()
193		pigui.EndPopup()
194	end
195end
196
197--
198-- Function: ui.customTooltip
199--
200-- ui.customTooltip(fun)
201--
202-- Display a tooltip window
203--
204--
205-- Example:
206--
207-- >
208--
209-- Parameters:
210--
211--   fun  - Function, a function that is called to define the tooltip contents
212--
213-- Returns:
214--
215--   nil
216--
217function ui.customTooltip(fun)
218	pigui.BeginTooltip()
219	fun()
220	pigui.EndTooltip()
221end
222
223--
224-- Function: ui.child
225--
226-- ui.child(id, size, flags, fun)
227--
228-- Define a child window
229--
230--
231-- Example:
232--
233-- >
234--
235-- Parameters:
236--
237--   id    - String, a unique name for the window,
238--           used to group its children
239--   size  - (Optional)Vector2
240--   flags - (Optional)Table, options:
241--              - NoTitleBar                : Disable title-bar
242--              - NoResize                  : Disable user resizing with the lower-right grip
243--              - NoMove                    : Disable user moving the window
244--              - NoScrollbar               : Disable scrollbars (window can still scroll with
245--                                            mouse or programmatically)
246--              - NoScrollWithMouse         : Disable user vertically scrolling with mouse wheel.
247--                                            On child window, mouse wheel will be forwarded to
248--                                            the parent unless NoScrollbar is also set.
249--              - NoCollapse                : Disable user collapsing window by double-clicking
250--                                            on it
251--              - AlwaysAutoResize          : Resize every window to its content every frame
252--              - NoSavedSettings           : Never load/save settings in .ini file
253--              - NoInputs                  :
254--              - MenuBar                   : Has a menu-bar
255--              - HorizontalScrollbar       : Allow horizontal scrollbar to appear (off by default).
256--              - NoFocusOnAppearing        : Disable taking focus when transitioning from hidden to
257--                                            visible state
258--              - NoBringToFrontOnFocus     : Disable bringing window to front when taking focus
259--                                            (e.g. clicking on it or programmatically giving it
260--                                            focus)
261--              - AlwaysVerticalScrollbar   : Always show vertical scrollbar
262--              - AlwaysHorizontalScrollbar : Always show horizontal scrollbar
263--              - AlwaysUseWindowPadding    : Ensure child windows without border uses
264--                                            style.WindowPadding (ignored by default for
265--                                            non-bordered child windows, because more convenient)
266--   fun   - Function, a function that is called to define the popup contents
267--
268-- Returns:
269--
270--   nil
271--
272function ui.child(id, size, flags, fun)
273	if flags == nil and fun == nil then -- size is optional
274		fun = size
275		size = Vector2(-1,-1)
276		flags = {}
277	elseif fun == nil then
278		fun = flags
279		flags = {}
280	end
281
282	pigui.BeginChild(id, size, flags)
283	fun()
284	pigui.EndChild()
285end
286
287--
288-- Function: ui.withTooltip
289--
290-- ui.withTooltip(tooltip, fun)
291--
292-- Display something, but with a tooltip shown on mouseover
293--
294--
295-- Example:
296--
297-- >
298--
299-- Parameters:
300--
301--   tooltip - String, the tooltip to display
302--   fun     - Function, a function that is called to display the contents
303--             that will have the tooltip
304--
305-- Returns:
306--
307--   nil
308--
309function ui.withTooltip(tooltip, fun)
310	local startPos = pigui.GetCursorPos()
311	pigui.BeginGroup()
312	fun()
313	pigui.EndGroup()
314	if string.len(tooltip) > 0 and pigui.IsItemHovered() then
315		pigui.SetTooltip(tooltip)
316	end
317end
318
319--
320-- Function: ui.playBoinkNoise
321--
322-- ui.playBoinkNoise()
323--
324-- Boink!
325--
326-- Example:
327--
328-- > ui.playBoinkNoise()
329--
330-- Parameters:
331--
332-- Returns:
333--
334--   nil
335--
336function ui.playBoinkNoise()
337	ui.playSfx("Click", 0.3)
338end
339
340--
341-- Function: ui.isMouseHoveringWindow
342--
343-- ui.isMouseHoveringWindow()
344--
345--
346-- Example:
347--
348-- >
349--
350-- Parameters:
351--
352-- Returns:
353--
354--   boolean - true if the mouse is currently within the current
355--             window, false otherwise
356--
357function ui.isMouseHoveringWindow()
358	return ui.isWindowHovered({"AllowWhenBlockedByPopup", "AllowWhenBlockedByActiveItem"})
359end
360
361--
362-- Function: ui.isAnyWindowHovered
363--
364-- ui.isAnyWindowHovered()
365--
366--
367-- Example:
368--
369-- >
370--
371-- Parameters:
372--
373-- Returns:
374--
375--   boolean - true if the mouse is currently within any window,
376--             false otherwise
377--
378function ui.isAnyWindowHovered()
379	return ui.isWindowHovered({"AnyWindow"})
380end
381
382--
383-- Function: ui.ctrlHeld
384--
385-- ui.ctrlHeld()
386--
387--
388-- Example:
389--
390-- >
391--
392-- Parameters:
393--
394-- Returns:
395--
396--   boolean - true if a ctrl key is being held
397--
398function ui.ctrlHeld() return pigui.key_ctrl end
399
400--
401-- Function: ui.altHeld
402--
403-- ui.altHeld()
404--
405--
406-- Example:
407--
408-- >
409--
410-- Parameters:
411--
412-- Returns:
413--
414--   boolean - true if an alt key is being held
415--
416function ui.altHeld() return pigui.key_alt end
417
418--
419-- Function: ui.shiftHeld
420--
421-- ui.shiftHeld()
422--
423--
424-- Example:
425--
426-- >
427--
428-- Parameters:
429--
430-- Returns:
431--
432--   boolean - true if a shift key is being held
433--
434function ui.shiftHeld() return pigui.key_shift end
435
436--
437-- Function: ui.noModifierHeld
438--
439-- ui.noModifierHeld()
440--
441--
442-- Example:
443--
444-- >
445--
446-- Parameters:
447--
448-- Returns:
449--
450--   boolean - true if no modifier (alt, shift, ctrl) keys are being held
451--
452function ui.noModifierHeld() return pigui.key_none end
453
454--
455-- Function: ui.escapeKeyReleased
456--
457-- Performs some sanity checks and returns true if the user has pressed escape
458-- and the escape key is not currently being consumed.
459--
460--
461-- Parameters:
462--
463--   ignorePopup - if true, skip checking for open popups.
464--
465-- Returns:
466--
467--   boolean - true if the escape key is pressed and not being consumed
468--
469function ui.escapeKeyReleased(ignorePopup)
470	return (ignorePopup or not ui.isAnyPopupOpen()) and ui.noModifierHeld() and ui.isKeyReleased(ui.keys.escape)
471end
472
473--
474-- Function: ui.tabBar
475--
476-- ui.tabBar(id, items)
477--
478--
479-- Example:
480--
481-- >
482--
483-- Parameters:
484--   id    - String, unique id to identify the group of tabs by
485--   items - Table, a list of contents. Each item should be a
486--           table containing a table of tab options and a function
487--           to call that displays the tabs contents
488--
489-- Returns:
490--
491--   boolean - true if the tab bar is open, false otherwise
492--
493function ui.tabBar(id, items)
494	local open = pigui.BeginTabBar(id)
495	if not open then return false end
496
497	for i, v in ipairs(items) do
498		if type(v) == "table" and v[1] and type(v[2]) == "function" then
499			if pigui.BeginTabItem(tostring(v[1]) .. "##" .. tostring(i)) then
500				v[2](v)
501			end
502		end
503	end
504
505	pigui.EndTabBar()
506	return true
507end
508
509--
510-- Function: ui.withFont
511--
512-- ui.withFont(name, size, fun)
513--
514--
515-- Example:
516--
517-- >
518--
519-- Parameters:
520--   name    - Table|String, a table defining the font name and size or
521--             a string containing the name of the font
522--   size    - (Optional) number, font size. Optional if name is a table and defines size
523--   fun     - function, a function to call that shows the contents with the defined font
524--
525-- Returns:
526--
527--   any - the value returned from fun
528--
529function ui.withFont(name, size, fun)
530	-- allow `withFont(fontObj, fun)`
531	if type(name) == "table" and type(size) == "function" then
532		name, size, fun = table.unpack{name.name, name.size, size}
533	end
534
535	local font = pigui:PushFont(name, size)
536	local res = fun()
537	if font then
538		pigui.PopFont()
539	end
540	return res
541end
542
543--
544-- Function: ui.withStyleColors
545--
546-- ui.withStyleColors(styles, fun)
547--
548-- Display UI content with defined colors
549--
550-- Example:
551--
552-- >
553--
554-- Parameters:
555--   styles - table, table of style elements with the desired colors:
556--              Text, TextDisabled, WindowBg, ChildWindowBg, PopupBg, Border,
557--              BorderShadow, FrameBg, FrameBgHovered, FrameBgActive,TitleBg,
558--              TitleBgCollapsed, TitleBgActive, MenuBarBg, ScrollbarBg,
559--              ScrollbarGrab, ScrollbarGrabHovered, ScrollbarGrabActive,
560--              CheckMark, SliderGrab, SliderGrabActive, Button,
561--              ButtonHovered, ButtonActive, Header, HeaderHovered,
562--              HeaderActive, Separator, SeparatorHovered, SeparatorActive,
563--              ResizeGrip, ResizeGripHovered, ResizeGripActive, PlotLines,
564--              PlotLinesHovered, PlotHistogram, PlotHistogramHovered,
565--              TextSelectedBg, ModalWindowDarkening
566--   fun    - function, a function to call that shows the contents with the defined font
567--
568-- Returns:
569--
570--   any - the value returned from fun
571--
572function ui.withStyleColors(styles, fun)
573	for k,v in pairs(styles) do
574		pigui.PushStyleColor(k, v)
575	end
576	local res = fun()
577	pigui.PopStyleColor(utils.count(styles))
578	return res
579end
580
581--
582-- Function: ui.withStyleVars
583--
584-- ui.withStyleVars(styles, fun)
585--
586-- Display UI content with defined styles
587--
588-- Example:
589--
590-- >
591--
592-- Parameters:
593--   vars - table, table of style elements with the desired values:
594--            Alpha, WindowPadding, WindowRounding, WindowBorderSize,
595--            WindowMinSize, ChildRounding, ChildBorderSize, FramePadding,
596--            FrameRounding, FrameBorderSize, ItemSpacing,
597--            ItemInnerSpacing, IndentSpacing, GrabMinSize,
598--            ButtonTextAlign
599--   fun  - function, a function to call that shows the contents with the defined font
600--
601-- Returns:
602--
603--   any - the value returned from fun
604--
605function ui.withStyleVars(vars, fun)
606	for k,v in pairs(vars) do
607		pigui.PushStyleVar(k, v)
608	end
609	local res = fun()
610	pigui.PopStyleVar(utils.count(vars))
611	return res
612end
613
614--
615-- Function: ui.withStyleColorsAndVars
616--
617-- ui.withStyleColorsAndVars(styles, vars, fun)
618--
619-- Display UI content with defined styles and colors
620--
621-- Example:
622--
623-- >
624--
625-- Parameters:
626--   styles - table, table of style elements with the desired colors (see ui.withStyleColors)
627--   vars   - table, table of style elements with the desired values (see ui.withStyleVars)
628--   fun    - function, a function to call that shows the contents with the defined font
629--
630-- Returns:
631--
632--   any - the value returned from fun
633--
634function ui.withStyleColorsAndVars(styles, vars, fun)
635	for k,v in pairs(styles) do
636		pigui.PushStyleColor(k, v)
637	end
638	for k,v in pairs(vars) do
639		pigui.PushStyleVar(k, v)
640	end
641	local res = fun()
642	pigui.PopStyleVar(utils.count(vars))
643	pigui.PopStyleColor(utils.count(styles))
644	return res
645end
646
647--
648-- Function: ui.screenSize
649--
650-- ui.screenSize()
651--
652-- Return the current screen resolution as a Vector2
653--
654-- Example:
655--
656-- >
657--
658-- Parameters:
659--
660-- Returns:
661--
662--   Vector2 - screen width and height
663--
664function ui.screenSize()
665	return Vector2(ui.screenWidth, ui.screenHeight)
666end
667
668--
669-- Function: ui.setNextWindowPosCenter
670--
671-- ui.setNextWindowPosCenter(cond)
672--
673-- Set the next window position to be centered on screen
674--
675-- Example:
676--
677-- >
678--
679-- Parameters:
680--   cond - table, condition flags: Always, Once, FirstUseEver, Appearing
681--
682-- Returns:
683--
684--   nil
685--
686function ui.setNextWindowPosCenter(cond)
687	ui.setNextWindowPos(ui.screenSize() / 2, cond, Vector2(0.5, 0.5))
688end
689
690--
691-- Function: ui.sameLine
692--
693-- ui.sameLine(pos_x, spacing_w)
694--
695-- Draw the next command on the same line as the previous
696--
697-- Example:
698--
699-- >
700--
701-- Parameters:
702--   pos_x     - (Optional) number, X position for next draw command, default 0
703--   spacing_w - (Optional) number, draw with spacing relative to previous, default -1
704--
705-- Returns:
706--
707--   nil
708--
709function ui.sameLine(pos_x, spacing_w)
710	local px = pos_x or 0.0
711	local sw = spacing_w or -1.0
712	pigui.SameLine(px, sw)
713end
714
715--
716-- Function: ui.withID
717--
718-- ui.withID(id, fun)
719--
720-- Display content with a specified ID
721--
722-- Example:
723--
724-- >
725--
726-- Parameters:
727--   id  - string, the desired ID
728--   fun - function, function called to display content
729--
730-- Returns:
731--
732--   nil
733--
734function ui.withID(id, fun)
735	pigui.PushID(id)
736	fun()
737	pigui.PopID()
738end
739
740--
741-- Function: ui.loadTextureFromSVG
742--
743-- ui.loadTextureFromSVG(filename, width, height)
744--
745-- Create a texture from an SVG
746--
747-- Example:
748--
749-- >
750--
751-- Parameters:
752--   filename - string, svg path
753--   width    - number, width of texture to create
754--   height   - number, height of texture to create
755--
756-- Returns:
757--
758--   userdata - the texture
759--
760function ui.loadTextureFromSVG(filename, width, height)
761	return pigui:LoadTextureFromSVG(filename, width, height)
762end
763
764--
765-- Function: ui.loadTexture
766--
767-- ui.loadTexture(filename)
768--
769-- Load a texture from file
770--
771-- Example:
772--
773-- >
774--
775-- Parameters:
776--   filename - string, texture file path
777--
778-- Returns:
779--
780--   userdata - the texture
781--
782function ui.loadTexture(filename)
783	return pigui:LoadTexture(filename)
784end
785
786function ui.maybeSetTooltip(tooltip)
787	if not Game.player:IsMouseActive() then
788		pigui.SetTooltip(tooltip)
789	end
790end
791
792ui.setTooltip = ui.maybeSetTooltip
793