dialogbox.d 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. module dialogs.dialogbox;
  2. import raylib;
  3. import std.string;
  4. import std.uni;
  5. import std.algorithm;
  6. import variables;
  7. // Dialog state variables
  8. int currentPage = 0;
  9. float textDisplayProgress = 0.0f;
  10. bool textFullyDisplayed = false;
  11. // Texture variables
  12. float circleRotationAngle = 0.0f;
  13. // Border sizes for 9-slice scaling
  14. immutable int DIALOG_BORDER = 32; // Border that won't be stretched
  15. immutable int CHOICE_BORDER = 32; // Choice window border
  16. // Draws a texture with 9-slice scaling
  17. void draw9SliceTexture(Texture2D tex, Rectangle dest, int borderSize, Color tint) {
  18. Rectangle src = Rectangle(0, 0, tex.width, tex.height);
  19. // Prevent invalid border sizes
  20. borderSize = max(1, min(borderSize, tex.width/3, tex.height/3));
  21. Rectangle innerSrc = Rectangle(
  22. borderSize, borderSize,
  23. src.width - borderSize*2, src.height - borderSize*2
  24. );
  25. Rectangle innerDest = Rectangle(
  26. dest.x + borderSize, dest.y + borderSize,
  27. dest.width - borderSize*2, dest.height - borderSize*2
  28. );
  29. // Draw all 9 parts
  30. void drawPart(Rectangle s, Rectangle d) {
  31. DrawTexturePro(tex, s, d, Vector2(0, 0), 0, tint);
  32. }
  33. // Corners
  34. drawPart(Rectangle(src.x, src.y, borderSize, borderSize),
  35. Rectangle(dest.x, dest.y, borderSize, borderSize));
  36. drawPart(Rectangle(src.x + src.width - borderSize, src.y, borderSize, borderSize),
  37. Rectangle(dest.x + dest.width - borderSize, dest.y, borderSize, borderSize));
  38. drawPart(Rectangle(src.x, src.y + src.height - borderSize, borderSize, borderSize),
  39. Rectangle(dest.x, dest.y + dest.height - borderSize, borderSize, borderSize));
  40. drawPart(Rectangle(src.x + src.width - borderSize, src.y + src.height - borderSize, borderSize, borderSize),
  41. Rectangle(dest.x + dest.width - borderSize, dest.y + dest.height - borderSize, borderSize, borderSize));
  42. // Edges
  43. drawPart(Rectangle(src.x + borderSize, src.y, innerSrc.width, borderSize),
  44. Rectangle(dest.x + borderSize, dest.y, innerDest.width, borderSize));
  45. drawPart(Rectangle(src.x, src.y + borderSize, borderSize, innerSrc.height),
  46. Rectangle(dest.x, dest.y + borderSize, borderSize, innerDest.height));
  47. drawPart(Rectangle(src.x + src.width - borderSize, src.y + borderSize, borderSize, innerSrc.height),
  48. Rectangle(dest.x + dest.width - borderSize, dest.y + borderSize, borderSize, innerDest.height));
  49. drawPart(Rectangle(src.x + borderSize, src.y + src.height - borderSize, innerSrc.width, borderSize),
  50. Rectangle(dest.x + borderSize, dest.y + dest.height - borderSize, innerDest.width, borderSize));
  51. // Center
  52. drawPart(innerSrc, innerDest);
  53. }
  54. // Main dialog display function
  55. void displayDialog(string[] pages, string[] choices, ref int selectedChoice,
  56. int choicePage, Font dialogFont, bool* showDialog,
  57. ref float textSpeed, Texture2D circle,
  58. Texture2D dialogBackgroundTex, Texture2D choiceWindowTex) {
  59. immutable int screenWidth = systemSettings.defaultScreenWidth;
  60. immutable int screenHeight = systemSettings.defaultScreenHeight;
  61. immutable int screenPadding = 10;
  62. // Dialog background rectangle with padding
  63. Rectangle dialogRect = Rectangle(
  64. screenPadding,
  65. screenHeight - screenHeight/3 - screenPadding,
  66. screenWidth - 2*screenPadding,
  67. screenHeight/3
  68. );
  69. // Draw dialog background
  70. draw9SliceTexture(dialogBackgroundTex, dialogRect, DIALOG_BORDER, Colors.WHITE);
  71. // Text layout parameters
  72. immutable float textLeftMargin = 33.0f;
  73. immutable float textTopMargin = 20.0f;
  74. immutable float textRightMargin = 33.0f;
  75. immutable float textWidth = dialogRect.width - textLeftMargin - textRightMargin;
  76. immutable float fontSize = 40.0f;
  77. immutable float spacing = 1.0f;
  78. string name;
  79. // Text display logic
  80. string currentText = pages[currentPage];
  81. string displayText = currentText;
  82. if (currentText[0] == '[') {
  83. size_t start = currentText.indexOf('[') + 1;
  84. size_t end = currentText.lastIndexOf(']');
  85. name = currentText[start..end];
  86. size_t nameStart = currentText.indexOf('[');
  87. size_t nameEnd = currentText.lastIndexOf(']') + 1;
  88. displayText = currentText[0..nameStart] ~ currentText[nameEnd..$];
  89. Rectangle nameRect = Rectangle(
  90. screenPadding,
  91. dialogRect.y - screenHeight/12,
  92. screenWidth / 12 + MeasureTextEx(dialogFont, name.toStringz(), fontSize, spacing).x,
  93. screenHeight / 12
  94. );
  95. draw9SliceTexture(choiceWindowTex, nameRect, DIALOG_BORDER, Colors.WHITE);
  96. Vector2 nameSize = MeasureTextEx(dialogFont, name.toStringz(), fontSize, spacing);
  97. Vector2 namePos = Vector2(
  98. nameRect.x + (nameRect.width - nameSize.x) / 2,
  99. nameRect.y + (nameRect.height - nameSize.y) / 2
  100. );
  101. DrawTextEx(dialogFont, name.toStringz(), namePos + Vector2(3, 3), fontSize, spacing, Colors.BLACK);
  102. DrawTextEx(dialogFont, name.toStringz(), namePos, fontSize, spacing, Colors.WHITE);
  103. }
  104. if (IsKeyPressed(KeyboardKey.KEY_ENTER) || IsMouseButtonPressed(MouseButton.MOUSE_BUTTON_LEFT)) {
  105. if (!textFullyDisplayed) {
  106. textDisplayProgress = displayText.length;
  107. textFullyDisplayed = true;
  108. } else {
  109. currentPage++;
  110. textDisplayProgress = 0.0f;
  111. textFullyDisplayed = false;
  112. }
  113. } else if (!textFullyDisplayed) {
  114. textDisplayProgress = min(textDisplayProgress + textSpeed, cast(float)displayText.length);
  115. textFullyDisplayed = textDisplayProgress >= displayText.length;
  116. }
  117. // Split text into lines that fit the dialog width
  118. string[] lines;
  119. string remaining = displayText[0..min(cast(int)textDisplayProgress, displayText.length)];
  120. while (!remaining.empty) {
  121. int fitChars = 0;
  122. float lineWidth = 0.0f;
  123. while (fitChars < remaining.length) {
  124. int wordEnd = fitChars;
  125. while (wordEnd < remaining.length && !remaining[wordEnd].isWhite) wordEnd++;
  126. string word = remaining[fitChars..wordEnd];
  127. float wordWidth = MeasureTextEx(dialogFont, word.toStringz(), fontSize, spacing).x;
  128. if (lineWidth > 0 && lineWidth + wordWidth > textWidth) break;
  129. lineWidth += wordWidth;
  130. fitChars = wordEnd;
  131. // Add whitespace
  132. while (fitChars < remaining.length && remaining[fitChars].isWhite) {
  133. lineWidth += MeasureTextEx(dialogFont, " ".toStringz(), fontSize, spacing).x;
  134. fitChars++;
  135. }
  136. }
  137. if (fitChars == 0) fitChars = 1;
  138. lines ~= remaining[0..fitChars];
  139. remaining = remaining[fitChars..$];
  140. }
  141. // Draw text lines with shadow effect
  142. immutable float lineHeight = MeasureTextEx(dialogFont, "A", fontSize, spacing).y * 1.4;
  143. foreach(i, line; lines) {
  144. Vector2 pos = Vector2(dialogRect.x + textLeftMargin, dialogRect.y + textTopMargin + i*lineHeight);
  145. DrawTextEx(dialogFont, line.toStringz(), pos + Vector2(3, 3), fontSize, spacing, Colors.BLACK);
  146. DrawTextEx(dialogFont, line.toStringz(), pos, fontSize, spacing, Colors.WHITE);
  147. }
  148. // Draw continue indicator when text is fully displayed
  149. if (textFullyDisplayed && !lines.empty) {
  150. circleRotationAngle = (circleRotationAngle + 45*GetFrameTime()) % 360;
  151. float lastLineWidth = MeasureTextEx(dialogFont, lines[$-1].toStringz(), fontSize, spacing).x;
  152. Vector2 circlePos = Vector2(
  153. dialogRect.x + textLeftMargin + lastLineWidth + 20,
  154. dialogRect.y + textTopMargin + (lines.length-1)*lineHeight
  155. );
  156. DrawTexturePro(
  157. circle,
  158. Rectangle(0, 0, circle.width, circle.height),
  159. Rectangle(circlePos.x, circlePos.y+20, 30, 30),
  160. Vector2(15, 15),
  161. circleRotationAngle,
  162. Colors.WHITE
  163. );
  164. }
  165. // Choice selection logic
  166. if (textFullyDisplayed && (currentPage == choicePage)) {
  167. immutable choiceHeight = 50;
  168. immutable choiceSpacing = 10;
  169. immutable verticalPadding = 30;
  170. Rectangle choiceWindow = Rectangle(
  171. (screenWidth - screenWidth/2)/2,
  172. (screenHeight - (verticalPadding*2 + choices.length*(choiceHeight + choiceSpacing)))/2,
  173. screenWidth/2,
  174. verticalPadding*2 + choices.length*(choiceHeight + choiceSpacing)
  175. );
  176. draw9SliceTexture(choiceWindowTex, choiceWindow, CHOICE_BORDER, Colors.WHITE);
  177. Vector2 mousePos = GetMousePosition();
  178. bool mouseClicked = IsMouseButtonPressed(MouseButton.MOUSE_BUTTON_LEFT);
  179. foreach(i, choice; choices) {
  180. Rectangle choiceRect = Rectangle(
  181. choiceWindow.x + 40,
  182. choiceWindow.y + verticalPadding + i*(choiceHeight + choiceSpacing),
  183. choiceWindow.width - 80,
  184. choiceHeight
  185. );
  186. bool hovered = CheckCollisionPointRec(mousePos, choiceRect);
  187. if (hovered) selectedChoice = cast(int)i;
  188. Vector2 textSize = MeasureTextEx(dialogFont, choice.toStringz(), 39, spacing);
  189. Vector2 textPos = Vector2(
  190. choiceRect.x + (choiceRect.width - textSize.x)/2,
  191. choiceRect.y + (choiceRect.height - textSize.y)/2
  192. );
  193. DrawTextEx(dialogFont, choice.toStringz(), textPos + Vector2(3, 3), 39, spacing, Colors.BLACK);
  194. DrawTextEx(dialogFont, choice.toStringz(), textPos, 39, spacing,
  195. (i == selectedChoice) ? Colors.YELLOW : (hovered ? Colors.LIGHTGRAY : Colors.WHITE));
  196. if (hovered && mouseClicked) {
  197. currentPage++;
  198. textDisplayProgress = 0.0f;
  199. textFullyDisplayed = false;
  200. }
  201. }
  202. // Keyboard navigation
  203. if (IsKeyPressed(KeyboardKey.KEY_DOWN)) selectedChoice = cast(int)((selectedChoice + 1) % choices.length);
  204. if (IsKeyPressed(KeyboardKey.KEY_UP)) selectedChoice = cast(int)((selectedChoice - 1 + choices.length) % choices.length);
  205. }
  206. // Fast-forward with Ctrl
  207. if ((IsKeyDown(KeyboardKey.KEY_LEFT_CONTROL) || IsKeyDown(KeyboardKey.KEY_RIGHT_CONTROL))
  208. && currentPage != choicePage && currentPage < pages.length) {
  209. textFullyDisplayed = true;
  210. currentPage++;
  211. }
  212. // Close dialog when all pages are shown
  213. if (currentPage >= pages.length) {
  214. currentPage = 0;
  215. textDisplayProgress = 0.0f;
  216. textFullyDisplayed = false;
  217. *showDialog = false;
  218. }
  219. }