1 /*
2     Copyright © 2020, Inochi2D Project
3     Distributed under the 2-Clause BSD License, see LICENSE file.
4     
5     Authors: Luna Nielsen
6 */
7 module creator.widgets.dialog;
8 import creator.widgets.dummy;
9 import creator.widgets.label;
10 import creator.core.font;
11 import bindbc.imgui;
12 import inochi2d;
13 import i18n;
14 
15 enum DialogLevel : size_t {
16     Info = 0,
17     Warning = 1,
18     Error = 2
19 }
20 
21 enum DialogButtons {
22     NONE = 0,
23     OK = 1,
24     Cancel = 2,
25     Yes = 4,
26     No = 8
27 }
28 
29 void incInitDialogs() {
30     // Only load Ada in official builds
31     version(InBranding) {
32         auto infoTex = ShallowTexture(cast(ubyte[])import("ui/ui-info.png"));
33         inTexPremultiply(infoTex.data);
34         auto warnTex = ShallowTexture(cast(ubyte[])import("ui/ui-warning.png"));
35         inTexPremultiply(warnTex.data);
36         auto errTex = ShallowTexture(cast(ubyte[])import("ui/ui-error.png"));
37         inTexPremultiply(errTex.data);
38 
39         adaTextures = [
40             new Texture(infoTex),
41             new Texture(warnTex),
42             new Texture(errTex),
43         ];
44     }
45 }
46 
47 /**
48     Render dialogs
49 */
50 void incRenderDialogs() {
51     if (entries.length > 0) {
52         auto entry = &entries[0];
53 
54         if (!igIsPopupOpen(entry.title)) {
55             igOpenPopup(entry.title);
56         }
57 
58         auto flags = 
59             ImGuiWindowFlags.NoSavedSettings | 
60             ImGuiWindowFlags.NoResize | 
61             ImGuiWindowFlags.AlwaysAutoResize;
62             
63         ImVec2 wpos = ImVec2(
64             igGetMainViewport().Pos.x+(igGetMainViewport().Size.x/2),
65             igGetMainViewport().Pos.y+(igGetMainViewport().Size.y/2),
66         );
67         igSetNextWindowPos(wpos, ImGuiCond.Appearing, ImVec2(0.5, 0.5));
68         if (igBeginPopupModal(entry.title, null, flags)) {
69             float errImgScale = 112;
70             float msgEndPadding = 4;
71 
72 
73             igBeginGroup();
74 
75                 if (igBeginChild("ErrorMainBoxLogo", ImVec2(errImgScale, errImgScale))) {
76                     version (InBranding) {
77                         import creator.core : incGetLogo;
78                         igImage(cast(void*)adaTextures[cast(size_t)entry.level].getTextureId(), ImVec2(errImgScale, errImgScale));
79                     }
80                 }
81                 igEndChild();
82 
83                 igSameLine(0, 0);
84                 igPushTextWrapPos(512);
85                     incText(entry.text);
86                 igPopTextWrapPos();
87 
88                 igSameLine(0, 0);
89                 incDummy(ImVec2(msgEndPadding, 1));
90             igEndGroup();
91 
92 
93             //
94             // BUTTONS
95             //
96             auto avail = incAvailableSpace();
97             float btnHeight = 24;
98             float btnSize = 80;
99             float totalBtnSize = btnSize*entry.btncount;
100             float msgAreaWidth = errImgScale+incMeasureString(entry.text).x+msgEndPadding;
101             float requestedMinimumSize = 256;
102 
103             if ((msgAreaWidth < requestedMinimumSize) && totalBtnSize < requestedMinimumSize) {
104 
105                 // Handle very short dialog messages.
106                 igDummy(ImVec2(requestedMinimumSize-(totalBtnSize+1), btnHeight));
107                 igSameLine(0, 0);
108             } else if (avail.x > totalBtnSize) {
109                 
110                 // Add pre-padding to buttons
111                 igDummy(ImVec2(avail.x-(totalBtnSize+1), btnHeight));
112                 igSameLine(0, 0);
113             }
114             
115             if ((entry.btns & DialogButtons.Cancel) == 2) {
116                 if (igButton(__("Cancel"), ImVec2(btnSize, btnHeight))) {
117                     entry.selected = DialogButtons.Cancel;
118                     igCloseCurrentPopup();
119                 }
120                 igSameLine(0, 0);
121             }
122 
123             if ((entry.btns & DialogButtons.OK) == 1) {
124                 if (igButton(__("OK"), ImVec2(btnSize, btnHeight))) {
125                     entry.selected = DialogButtons.OK;
126                     igCloseCurrentPopup();
127                 }
128                 igSameLine(0, 0);
129             }
130             
131             if ((entry.btns & DialogButtons.Yes) == 4) {
132                 if (igButton(__("Yes"), ImVec2(btnSize, btnHeight))) {
133                     entry.selected = DialogButtons.Yes;
134                     igCloseCurrentPopup();
135                 }
136                 igSameLine(0, 0);
137             }
138             
139             if ((entry.btns & DialogButtons.No) == 8) {
140                 if (igButton(__("No"), ImVec2(btnSize, btnHeight))) {
141                     entry.selected = DialogButtons.No;
142                     igCloseCurrentPopup();
143                 }
144             }
145 
146             igEndPopup();
147         }
148     }
149 }
150 
151 /**
152     Clean up dialogs
153 */
154 void incCleanupDialogs() {
155     if (entries.length > 0 && entries[0].selected > 0) {
156         entries = entries[1..$];
157     }
158 }
159 
160 /**
161     Creates a dialog with the tag set to the title
162 
163     Only use this if you don't need to query the exit state of the dialog
164 */
165 void incDialog(const(char)* title, string body_, DialogLevel level = DialogLevel.Error, DialogButtons btns = DialogButtons.OK, void* userData = null) {
166     incDialog(title, title, body_, level, btns, userData);
167 }
168 
169 /**
170     Creates a dialog
171 */
172 void incDialog(const(char)* tag, const(char)* title, string body_, DialogLevel level = DialogLevel.Error, DialogButtons btns = DialogButtons.OK, void* userData = null) {
173     import std.string : toStringz;
174     int btncount = 0;
175     if ((btns & DialogButtons.OK) == 1) btncount++;
176     if ((btns & DialogButtons.Cancel) == 2) btncount++;
177     if ((btns & DialogButtons.Yes) == 4) btncount++;
178     if ((btns & DialogButtons.No) == 8) btncount++;
179 
180     entries ~= DialogEntry(
181         tag,
182         title,
183         body_,
184         level,
185         btns,
186         DialogButtons.NONE,
187         btncount,
188         userData
189     );
190 }
191 
192 /**
193     Gets which button the user selected in the last dialog box with the selected tag.
194     Returns NONE if the last dialog was *not* the looked for tag or if there's no dialogs open
195 */
196 DialogButtons incDialogButtonSelected(const(char)* tag) {
197     if (entries.length == 0) return DialogButtons.NONE;
198     if (entries[0].tag != tag) return DialogButtons.NONE;
199     return entries[0].selected;
200 }
201 
202 /**
203     Returns the user data bound to the dialog
204 */
205 void* incDialogButtonUserData(const(char)* tag) {
206     if (entries.length == 0) return null;
207     if (entries[0].tag != tag) return null;
208     return entries[0].userData;
209 }
210 
211 private {
212     Texture[] adaTextures;
213 
214     DialogEntry[] entries;
215 
216     DialogEntry* findDialogEntry(const(char)* tag) {
217         foreach(i; 0..entries.length) {
218             if (entries[i].tag == tag) return &entries[i];
219         }
220         return null;
221     }
222 
223     struct DialogEntry {
224         const(char)* tag;
225         const(char)* title;
226         string text;
227         DialogLevel level;
228         DialogButtons btns;
229         DialogButtons selected;
230         int btncount;
231         void* userData;
232     }
233 }