sg_gui.nim 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. import
  2. sfml, sfml_colors,
  3. input_helpers, sg_packets
  4. from strutils import countlines
  5. type
  6. PGuiContainer* = ref TGuiContainer
  7. TGuiContainer* = object of RootObj
  8. position: TVector2f
  9. activeEntry: PTextEntry
  10. widgets: seq[PGuiObject]
  11. buttons: seq[PButton]
  12. PGuiObject* = ref TGuiObject
  13. TGuiObject* = object of RootObj
  14. PButton* = ref TButton
  15. TButton* = object of TGuiObject
  16. enabled: bool
  17. bg*: sfml.PRectangleShape
  18. text*: PText
  19. onClick*: TButtonClicked
  20. bounds: TFloatRect
  21. PButtonCollection* = ref TButtonCollection
  22. TButtonCollection* = object of TGuiContainer
  23. PTextEntry* = ref TTextEntry
  24. TTextEntry* = object of TButton
  25. inputClient: input_helpers.PTextInput
  26. PMessageArea* = ref TMessageArea
  27. TMessageArea* = object of TGuiObject
  28. pos: TVector2f
  29. messages: seq[TMessage]
  30. texts: seq[PText]
  31. scrollBack*: int
  32. sizeVisible*: int
  33. direction*: int
  34. TMessage = object
  35. color: TColor
  36. text: string
  37. lines: int
  38. TButtonClicked = proc(button: PButton)
  39. var
  40. guiFont* = newFont("data/fnt/LiberationMono-Regular.ttf")
  41. messageProto* = newText("", guiFont, 16)
  42. let
  43. vectorZeroF* = vec2f(0.0, 0.0)
  44. proc newGuiContainer*(): PGuiContainer
  45. proc newGuiContainer*(pos: TVector2f): PGuiContainer {.inline.}
  46. proc free*(container: PGuiContainer)
  47. proc add*(container: PGuiContainer; widget: PGuiObject)
  48. proc clearButtons*(container: PGuiContainer)
  49. proc click*(container: PGuiContainer; position: TVector2f)
  50. proc setActive*(container: PGuiContainer; entry: PTextEntry)
  51. proc setPosition*(container: PGuiContainer; position: TVector2f)
  52. proc update*(container: PGuiContainer; dt: float)
  53. proc draw*(window: PRenderWindow; container: PGuiContainer) {.inline.}
  54. proc newMessageArea*(container: PGuiContainer; position: TVector2f): PMessageArea {.discardable.}
  55. proc add*(m: PMessageArea; msg: ScChat)
  56. proc draw*(window: PRenderWindow; b: PButton) {.inline.}
  57. proc click*(b: PButton; p: TVector2f)
  58. proc setPosition*(b: PButton; p: TVector2f)
  59. proc setString*(b: PButton; s: string) {.inline.}
  60. proc newButton*(container: PGuiContainer; text: string; position: TVector2f;
  61. onClick: TButtonClicked; startEnabled: bool = true): PButton {.discardable.}
  62. proc init(b: PButton; text: string; position: TVector2f; onClick: TButtonClicked)
  63. proc setEnabled*(b: PButton; enabled: bool)
  64. proc disable*(b: PButton) {.inline.}
  65. proc enable*(b: PButton) {.inline.}
  66. proc newTextEntry*(container: PGuiContainer; text: string;
  67. position: TVector2f; onEnter: TInputFinishedProc = nil): PTextEntry {.discardable.}
  68. proc init(t: PTextEntry; text: string; onEnter: TInputFinishedProc)
  69. proc draw*(window: PRenderWindow, t: PTextEntry) {.inline.}
  70. proc setActive*(t: PTextEntry) {.inline.}
  71. proc clearText*(t: PTextEntry) {.inline.}
  72. proc getText*(t: PTextEntry): string {.inline.}
  73. proc update*(m: PMessageArea)
  74. if guiFont == nil:
  75. echo("Could not load font, crying softly to myself.")
  76. quit(1)
  77. proc newGuiContainer*(): PGuiContainer =
  78. new(result, free)
  79. result.widgets = @[]
  80. result.buttons = @[]
  81. proc newGuiContainer*(pos: TVector2f): PGuiContainer =
  82. result = newGuiContainer()
  83. result.setPosition pos
  84. proc free*(container: PGuiContainer) =
  85. container.widgets = @[]
  86. container.buttons = @[]
  87. proc add*(container: PGuiContainer; widget: PGuiObject) =
  88. container.widgets.add(widget)
  89. proc add*(container: PGuiContainer; button: PButton) =
  90. if container.isNil: return
  91. container.buttons.add(button)
  92. proc clearButtons*(container: PGuiContainer) =
  93. container.buttons.setLen 0
  94. proc click*(container: PGuiContainer; position: TVector2f) =
  95. for b in container.buttons:
  96. click(b, position)
  97. proc setActive*(container: PGuiContainer; entry: PTextEntry) =
  98. container.activeEntry = entry
  99. setActive(entry)
  100. proc setPosition*(container: PGuiContainer; position: TVector2f) =
  101. container.position = position
  102. proc update*(container: PGuiContainer; dt: float) =
  103. if not container.activeEntry.isNil:
  104. container.activeEntry.setString(container.activeEntry.getText())
  105. proc draw*(window: PRenderWindow; container: PGuiContainer) =
  106. for b in container.buttons:
  107. window.draw b
  108. proc free(c: PButton) =
  109. c.bg.destroy()
  110. c.text.destroy()
  111. c.bg = nil
  112. c.text = nil
  113. c.onClick = nil
  114. proc newButton*(container: PGuiContainer; text: string;
  115. position: TVector2f; onClick: TButtonClicked;
  116. startEnabled: bool = true): PButton =
  117. new(result, free)
  118. init(result,
  119. text,
  120. if not container.isNil: position + container.position else: position,
  121. onClick)
  122. container.add result
  123. if not startEnabled: disable(result)
  124. proc init(b: PButton; text: string; position: TVector2f; onClick: TButtonClicked) =
  125. b.bg = newRectangleShape()
  126. b.bg.setSize(vec2f(80.0, 16.0))
  127. b.bg.setFillColor(color(20, 30, 15))
  128. b.text = newText(text, guiFont, 16)
  129. b.onClick = onClick
  130. b.setPosition(position)
  131. b.enabled = true
  132. proc copy*(c: PButton): PButton =
  133. new(result, free)
  134. result.bg = c.bg.copy()
  135. result.text = c.text.copy()
  136. result.onClick = c.onClick
  137. result.setPosition(result.bg.getPosition())
  138. proc setEnabled*(b: PButton; enabled: bool) =
  139. b.enabled = enabled
  140. if enabled:
  141. b.text.setColor(White)
  142. else:
  143. b.text.setColor(Gray)
  144. proc enable*(b: PButton) = setEnabled(b, true)
  145. proc disable*(b: PButton) = setEnabled(b, false)
  146. proc draw*(window: PRenderWindow; b: PButton) =
  147. window.draw b.bg
  148. window.draw b.text
  149. proc setPosition*(b: PButton, p: TVector2f) =
  150. b.bg.setPosition(p)
  151. b.text.setPosition(p)
  152. b.bounds = b.text.getGlobalBounds()
  153. proc setString*(b: PButton; s: string) =
  154. b.text.setString(s)
  155. proc click*(b: PButton, p: TVector2f) =
  156. if b.enabled and (addr b.bounds).contains(p.x, p.y):
  157. b.onClick(b)
  158. proc free(obj: PTextEntry) =
  159. free(PButton(obj))
  160. proc newTextEntry*(container: PGuiContainer; text: string;
  161. position: TVector2F; onEnter: TInputFinishedProc = nil): PTextEntry =
  162. new(result, free)
  163. init(PButton(result), text, position + container.position, proc(b: PButton) =
  164. setActive(container, PTextEntry(b)))
  165. init(result, text, onEnter)
  166. container.add result
  167. proc init(t: PTextEntry; text: string; onEnter: TInputFinishedProc) =
  168. t.inputClient = newTextInput(text, text.len, onEnter)
  169. proc draw(window: PRenderWindow; t: PTextEntry) =
  170. window.draw PButton(t)
  171. proc clearText*(t: PTextEntry) =
  172. t.inputClient.clear()
  173. proc getText*(t: PTextEntry): string =
  174. return t.inputClient.text
  175. proc setActive*(t: PTextEntry) =
  176. if not t.isNil and not t.inputClient.isNil:
  177. input_helpers.setActive(t.inputClient)
  178. discard """proc newMessageArea*(container: PGuiContainer; position: TVector2f): PMessageArea =
  179. new(result)
  180. result.messages = @[]
  181. result.pos = position
  182. container.add(result)
  183. proc add*(m: PMessageArea, text: string): PText =
  184. result = messageProto.copy()
  185. result.setString(text)
  186. m.messages.add(result)
  187. let nmsgs = len(m.messages)
  188. var pos = vec2f(m.pos.x, m.pos.y)
  189. for i in countdown(nmsgs - 1, max(nmsgs - 30, 0)):
  190. setPosition(m.messages[i], pos)
  191. pos.y -= 16.0
  192. proc draw*(window: PRenderWindow; m: PMessageArea) =
  193. let nmsgs = len(m.messages)
  194. if nmsgs == 0: return
  195. for i in countdown(nmsgs - 1, max(nmsgs - 30, 0)):
  196. window.draw(m.messages[i])
  197. """
  198. proc newMessageArea*(container: PGuiContainer; position: TVector2f): PMessageArea =
  199. new(result)
  200. result.messages = @[]
  201. result.texts = @[]
  202. result.pos = position + container.position
  203. result.sizeVisible = 10
  204. result.scrollBack = 0
  205. result.direction = -1 ## to push old messages up
  206. container.add(result)
  207. proc add*(m: PMessageArea, msg: ScChat) =
  208. const prependName = {CPub, CPriv}
  209. var mmm: TMessage
  210. if msg.kind in prependName:
  211. mmm.text = "<"
  212. mmm.text.add msg.fromPlayer
  213. mmm.text.add "> "
  214. mmm.text.add msg.text
  215. else:
  216. mmm.text = msg.text
  217. case msg.kind
  218. of CPub: mmm.color = RoyalBlue
  219. of CPriv, CSystem: mmm.color = Green
  220. of CError: mmm.color = Red
  221. mmm.lines = countLines(mmm.text)
  222. m.messages.add mmm
  223. update m
  224. proc add*(m: PMessageArea, msg: string) {.inline.} =
  225. var chat = newScChat(kind = CSystem, text = msg)
  226. add(m, chat)
  227. proc proctor*(m: PText; msg: ptr TMessage; pos: ptr TVector2f) =
  228. m.setString msg.text
  229. m.setColor msg.color
  230. m.setPosition pos[]
  231. proc update*(m: PMessageArea) =
  232. if m.texts.len < m.sizeVisible:
  233. echo "adding ", m.sizeVisible - m.texts.len, " fields"
  234. for i in 1..m.sizeVisible - m.texts.len:
  235. var t = messageProto.copy()
  236. m.texts.add messageProto.copy()
  237. elif m.texts.len > m.sizeVisible:
  238. echo "cutting ", m.texts.len - m.sizeVisible, " fields"
  239. for i in m.sizeVisible ..< m.texts.len:
  240. m.texts.pop().destroy()
  241. let nmsgs = m.messages.len()
  242. if m.sizeVisible == 0 or nmsgs == 0:
  243. echo "no messages? ", m.sizeVisible, ", ", nmsgs
  244. return
  245. var pos = vec2f(m.pos.x, m.pos.y)
  246. for i in 0.. min(m.sizeVisible, nmsgs)-1:
  247. ##echo nmsgs - i - 1 - m.scrollBack
  248. let msg = addr m.messages[nmsgs - i - 1 - m.scrollBack]
  249. proctor(m.texts[i], msg, addr pos)
  250. pos.y += (16 * m.direction * msg.lines).cfloat
  251. proc draw*(window: PRenderWindow; m: PMessageArea) =
  252. let nmsgs = len(m.texts)
  253. if nmsgs == 0: return
  254. for i in countdown(nmsgs - 1, max(nmsgs - m.sizeVisible, 0)):
  255. window.draw m.texts[i]