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 }