boxes.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. from colorama import Fore, Back, Style
  2. # see http://xahlee.info/comp/unicode_drawing_shapes.html
  3. def merge(color_string):
  4. """ Given a string of colorama ANSI, merge them if you can. """
  5. return color_string.replace("m\x1b[", ";")
  6. class Boxes(object):
  7. """ Boxes makes nice ASCII graphic menus
  8. Example:
  9. c1 = merge(Style.BRIGHT + Fore.CYAN)
  10. c2 = merge(Style.NORMAL + Fore.CYAN)
  11. # Set the size of the box to 40 (Important!)
  12. box = Boxes(40, color=c1)
  13. self.queue_game.put(box.top())
  14. # format string "{0:^40}".format("Text Centered")
  15. # This centers the text into the size of the box.
  16. self.queue_game.put(box.row(c1 + "{0:^40}".format("Scripts")))
  17. # this draws the middle line. (The line that goes across, but is
  18. # connected on both sides to the one above it).
  19. self.queue_game.put(box.middle())
  20. def menu_item(ch, desc):
  21. # Again we want the final string to be 40!
  22. # c1 and c2 (below) take 0 space.
  23. # We have " " + ch + " " + "-" + " " .. which equals 5
  24. # So desc needs {:35} to pad out string out to 40 print chars.
  25. row = " {0}{1} {2}-{3} {4:35}".format(c1, ch, c2, c1, desc)
  26. # self.queue_game.put(
  27. # " " + c1 + ch + c2 + " - " + c1 + desc + self.nl
  28. # )
  29. self.queue_game.put(box.row(row))
  30. menu_item("1", "Ports (Trades between two sectors)")
  31. menu_item("2", "Explore (Strange new sectors)")
  32. menu_item("3", "Space... the final frontier...")
  33. menu_item("X", "eXit")
  34. self.queue_game.put(box.bottom())
  35. """
  36. box_styles = (
  37. # ┌──┐
  38. # │ │
  39. # ├──┤
  40. # └──┘
  41. {
  42. "tl": "\xda",
  43. "tr": "\xbf",
  44. "top": "\xc4",
  45. "side": "\xb3",
  46. "bl": "\xc0",
  47. "br": "\xd9",
  48. "ml": "\xc3",
  49. "mr": "\xb4",
  50. },
  51. # ╔══╗
  52. # ║ ║
  53. # ╠══╣
  54. # ╚══╝
  55. {
  56. "tl": "\xc9",
  57. "tr": "\xbb",
  58. "top": "\xcd",
  59. "side": "\xba",
  60. "bl": "\xc8",
  61. "br": "\xbc",
  62. "ml": "\xcc",
  63. "mr": "\xb9",
  64. },
  65. # ╒══╕
  66. # │ │
  67. # ╞══╡
  68. # ╘══╛
  69. {
  70. "tl": "\xd5",
  71. "tr": "\xb8",
  72. "top": "\xcd",
  73. "side": "\xb3",
  74. "bl": "\xd4",
  75. "br": "\xbe",
  76. "ml": "\xc6",
  77. "mr": "\xb5",
  78. },
  79. # ╓──╖
  80. # ║ ║
  81. # ╟──╢
  82. # ╙──╜
  83. {
  84. "tl": "\xd6",
  85. "tr": "\xb7",
  86. "top": "\xc4",
  87. "side": "\xba",
  88. "bl": "\xd3",
  89. "br": "\xbd",
  90. "ml": "\xc7",
  91. "mr": "\xb6",
  92. },
  93. )
  94. def __init__(self, size: int, color="", style=1, start_nl=True):
  95. """ Construct box
  96. size is width of text inside the box.
  97. (so actual width is size + 2).
  98. color = Style of color to use.
  99. If color is blank, we don't use the RESET_ALL.
  100. style=0 Is double lines.
  101. start_nl Should we start the top with a newline?
  102. """
  103. self.size = size
  104. self.style = style
  105. self.color = color
  106. self.start_nl = start_nl
  107. # default colors
  108. # self.c = merge(Style.BRIGHT + Fore.WHITE + Back.BLUE)
  109. # self.cp = merge(Style.BRIGHT + Fore.YELLOW + Back.BLUE)
  110. # useful consts
  111. self.r = Style.RESET_ALL
  112. self.nl = "\n\r"
  113. # self.bsb = "\b \b"
  114. def top(self):
  115. """ Output the TOP line. """
  116. if self.color == "":
  117. c = ""
  118. r = ""
  119. else:
  120. c = self.color
  121. r = self.r
  122. s = self.box_styles[self.style]
  123. if self.start_nl:
  124. n = self.nl
  125. else:
  126. n = ""
  127. return n + c + s["tl"] + s["top"] * self.size + s["tr"] + r + self.nl
  128. def middle(self):
  129. """ Output MIDDLE line.
  130. This solid line goes completely across.
  131. The edges connect to the top and bottom.
  132. """
  133. if self.color == "":
  134. c = ""
  135. r = ""
  136. else:
  137. c = self.color
  138. r = self.r
  139. s = self.box_styles[self.style]
  140. return c + s["ml"] + s["top"] * self.size + s["mr"] + r + self.nl
  141. def row(self, line):
  142. """ Output content line.
  143. NOTE: the line length must match the box size.
  144. Use "{0:30}".format(content) to match size of 30.
  145. """
  146. if self.color == "":
  147. c = ""
  148. r = ""
  149. else:
  150. c = self.color
  151. r = self.r
  152. s = self.box_styles[self.style]
  153. return c + "{0}{1}{2}".format(s["side"], r + line + c, s["side"]) + r + self.nl
  154. def bottom(self):
  155. """ Output BOTTOM line. """
  156. if self.color == "":
  157. c = ""
  158. r = ""
  159. else:
  160. c = self.color
  161. r = self.r
  162. s = self.box_styles[self.style]
  163. return c + s["bl"] + s["top"] * self.size + s["br"] + r + self.nl
  164. @staticmethod
  165. def alert(message, length=0, pad=2, width=78, base="blue", style=1):
  166. """
  167. Display alert message
  168. Example:
  169. self.queue_game.put(Boxes.alert("Proxy done.", base="green"))
  170. length: the printable character length.
  171. If the string contains color codes, you'll need
  172. to use this so it will be centered properly.
  173. If not given, len(message) is used.
  174. pad: The spaces at the front and back of the message.
  175. width: Defaults to 78 chars wide.
  176. base: The background color to use for the alert.
  177. """
  178. if base == "red":
  179. color = merge(Style.BRIGHT + Fore.YELLOW + Back.RED)
  180. elif base == "blue":
  181. color = merge(Style.BRIGHT + Fore.YELLOW + Back.BLUE)
  182. elif base == "green":
  183. color = merge(Style.BRIGHT + Fore.YELLOW + Back.GREEN)
  184. elif base == "cyan":
  185. color = merge(Style.BRIGHT + Fore.YELLOW + Back.CYAN)
  186. elif base == "magenta":
  187. color = merge(Style.BRIGHT + Fore.YELLOW + Back.MAGENTA)
  188. else:
  189. # I don't know.
  190. color = Style.RESET_ALL
  191. if length == 0:
  192. length = len(message)
  193. box_size = length + pad * 2
  194. boxpad = " " * pad
  195. box = Boxes(box_size, color=color, style=style, start_nl=False)
  196. msg = "{0}{1}{2}{3}".format(color, boxpad, message, boxpad)
  197. # How much pad to add to the left side?
  198. left_pad = (width - box_size + 2) // 2
  199. pad = " " * left_pad
  200. return "{0}{1}{2}{3}{4}{5}".format(
  201. pad, box.top(), pad, box.row(msg), pad, box.bottom()
  202. )