ui.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. # THIS IS A SOURCE CODE FILE FROM I'M NOT EVEN HUMAN THE GAME.
  2. # IT COULD BE USED IN A DIFFERENT PIECE OF SOFTWARE ( LIKE A
  3. # DIFFERENT GAME ), BUT IT WAS ORIGINALLY WRITTEN FOR I'M NOT
  4. # EVEN HUMAN THE GAME.
  5. # THE DEVELOPERS OF THE GAME ARE : (C) J.Y.AMIHUD, AYYZEE AND
  6. # OTHER CONTRIBUTORS. THIS AND OTHER FILES IN THIS GAME,
  7. # UNLESS SPECIFICALLY NOTED, COULD BE USED UNDER THE TERMS OF
  8. # GNU GENERAL PUBLIC LICENSE VERSION 3 OR ANY LATER VERSION.
  9. import os
  10. import json
  11. import cairo
  12. def load_palete(game):
  13. """Loads colors from assets/palete.json"""
  14. with open("assets/palete.json") as f:
  15. game.palete = json.load(f)
  16. def distance(list1, list2):
  17. """Calculates distnaces between two vectors"""
  18. d = []
  19. for n, i in enumerate(list1):
  20. try:
  21. d.append(max((i-list2[n]),(list2[n]-i)))
  22. except:
  23. d.append(0)
  24. return sum(d) / len(d)
  25. def color(game, layer, code, alpha=1):
  26. """Sets a cairo brush color from a palete by looking for the
  27. closest match."""
  28. # If it's a code in hex like #FF0000 so we need to convert it
  29. # to numbers first
  30. if type(code) == str and code.startswith("#") and (len(code) -1) %3 == 0:
  31. new_c = []
  32. for c in range(3):
  33. ind = int((len(code)-1)/3)
  34. new_c.append( int(code[ind*c+1:ind*c+ind+1], 16)/255 )
  35. code = new_c
  36. # If it's a code in RGB, we look for the nearest
  37. # color on the palete.
  38. if type(code) == list or type(code) == tuple:
  39. name = "black" # Place holder for a palete entry
  40. dist = 10000000000 # Imaginary large number
  41. # Chekcing for the lowest possible deviation of color
  42. for color in game.palete:
  43. new_dist = distance(game.palete[color], code)
  44. if dist > new_dist:
  45. name = color
  46. dist = new_dist
  47. r, g, b = game.palete[name]
  48. elif type(code) == str:
  49. try:
  50. r, g, b = game.palete[code]
  51. except:
  52. r, g, b = 0, 0, 0
  53. else:
  54. r, g, b = 0, 0, 0
  55. layer.set_source_rgba(r,g,b,alpha)
  56. def cache_sprite_sheet(game, sheet, asset=False):
  57. """This function will cache sprite sheets into game.images"""
  58. if sheet not in game.images:
  59. # Loading the sheet image
  60. grid = cairo.ImageSurface.create_from_png(sheet)
  61. # Loading metadata
  62. try:
  63. with open(sheet.replace(".png", ".json")) as f:
  64. metadata = json.load(f)
  65. except:
  66. metadata = {}
  67. width = 1
  68. height = 1
  69. try:
  70. width = metadata["resolution"][0]
  71. height = metadata["resolution"][1]
  72. except:
  73. pass
  74. # Getting the resolution of the cell
  75. cellx = int(grid.get_width()/width)
  76. celly = int(grid.get_height()/height)
  77. data = {}
  78. if asset:
  79. game.elements[asset[0]][asset[1]] = []
  80. for x in range(width):
  81. for y in range(height):
  82. # Creating individual cells
  83. cell = cairo.ImageSurface(cairo.FORMAT_ARGB32,
  84. cellx,
  85. celly)
  86. drawcell = cairo.Context(cell)
  87. drawcell.set_antialias(cairo.ANTIALIAS_NONE)
  88. drawcell.set_source_surface(grid,
  89. 1-(cellx*x+1),
  90. 1-(celly*y+1))
  91. drawcell.paint()
  92. # Putting the cell into the data as in "0:0" or "43:69".
  93. cellname = str(x)+":"+str(y)
  94. cellnameindata = cellname
  95. celldata = {}
  96. try:
  97. if asset:
  98. print(asset)
  99. game.elements[asset[0]][asset[1]].append(metadata[cellname])
  100. cellnameindata = metadata[cellname]["title"]
  101. celldata = metadata[cellname]
  102. except:
  103. pass
  104. data[cellnameindata] = [cell, celldata]
  105. try:
  106. data["title"] = metadata["title"]
  107. except:
  108. data["title"] = sheet
  109. game.images[sheet] = data
  110. def image(game, layer, x, y, name, code="0:0", color=False, offset=False, alpha=1):
  111. x = int(x)
  112. y = int(y)
  113. """This function draws an image into a canvas. And load it if
  114. it's not loaded"""
  115. # Load the whole image as a sprite ( if it's not loaded yet )
  116. cache_sprite_sheet(game, name)
  117. # Drawing the image
  118. source = game.images[name].get(code,
  119. list(game.images[name].values())[-2])
  120. if offset:
  121. off = source[1].get("offset", [0,0])
  122. x += off[0]
  123. y += off[1]
  124. if color: #If we forcing color
  125. layer.set_antialias(cairo.ANTIALIAS_NONE)
  126. layer.mask_surface(source[0],x, y)
  127. layer.fill()
  128. else: #If we just using the image
  129. layer.set_source_surface(source[0], x, y)
  130. if alpha == 1:
  131. layer.paint()
  132. else:
  133. layer.paint_with_alpha(alpha)
  134. def text(game, layer, string, x, y, color=True, align="left"):
  135. """This function will draw text using an assets/font.png file."""
  136. for n, l in enumerate(string):
  137. ax = x+(8*n)
  138. if align == "center":
  139. ax = ax - int(len(string)*4)
  140. elif align == "right":
  141. ax = ax - int(len(string)*8)
  142. image(game, layer, ax, y, "assets/font.png", l, color=color)
  143. def button(game, layer, x, y, w, h,
  144. menu="", icon="", string="", func=False,
  145. scroll="", borders=True):
  146. """Draws a button"""
  147. # Let's add this button into a menu.
  148. menu_selected = False
  149. select_number = 0
  150. if menu:
  151. if menu not in game.menus :
  152. game.menus[menu] = {"selected":0,
  153. "buttons":[]}
  154. bmenudata = [x,y]
  155. if bmenudata not in game.menus[menu]["buttons"]:
  156. game.menus[menu]["buttons"].append(bmenudata)
  157. for n, i in enumerate(game.menus[menu]["buttons"]):
  158. if i == bmenudata:
  159. select_number = n
  160. if n == game.menus[menu]["selected"]:
  161. menu_selected = True
  162. if scroll and scroll in game.scroll:
  163. # Making sure that while I press either UP or DOWN it will
  164. # scroll to the correct spot.
  165. if (65362 in game.current["keys"]\
  166. or 65364 in game.current["keys"])\
  167. and menu_selected:
  168. game.scroll[scroll] = 0 - y + int(game.current["h"] / 2)
  169. y = y + int(game.scroll[scroll])
  170. x = int(round(x))
  171. y = int(round(y))
  172. w = int(round(w))
  173. h = int(round(h))
  174. color(game, layer, "#00FF00")
  175. layer.set_line_width(1)
  176. if borders:
  177. layer.rectangle(x,y,w,h)
  178. layer.stroke()
  179. mo = False
  180. if int(game.current["mx"]) in range(x, x+w) \
  181. and int(game.current["my"]) in range(y, y+h):
  182. mo = True
  183. if mo or menu_selected:
  184. if menu:
  185. game.menus[menu]["selected"] = select_number
  186. if mo:
  187. layer.rectangle( x+1,y+1, w-3, h-3 )
  188. layer.fill()
  189. else:
  190. layer.rectangle( x+1,y+1, w-2, h-2 )
  191. layer.stroke()
  192. # If you clicked the button
  193. if ( game.previous["LMB"] \
  194. and int(game.previous["LMB"][0]) in range(x, x+w) \
  195. and int(game.previous["LMB"][1]) in range(y, y+h) ) \
  196. or ( menu_selected and 65293 in game.previous["keys"]):
  197. color(game, layer, "yellow")
  198. layer.rectangle( x+1,y+1, w-3, h-3 )
  199. layer.fill()
  200. # If the is doing something
  201. if (( not game.current["LMB"] and game.previous["LMB"] and mo) \
  202. or (65293 not in game.current["keys"] \
  203. and 65293 in game.previous["keys"])) \
  204. and func:
  205. func()
  206. game.previous["LMB"] = False
  207. game.current["LMB"] = False
  208. game.current["keys"] = []
  209. game.previous["keys"] = []
  210. if icon:
  211. if mo:
  212. color(game, layer, "black")
  213. image(game, layer, x+2,y+1,"assets/menu_icons.png",
  214. icon, True)
  215. if string:
  216. if mo:
  217. color(game, layer, "black")
  218. widthleft = w - 4
  219. xd = x
  220. if icon:
  221. widthleft -= 12
  222. xd += 12
  223. sl = int(widthleft / 8)
  224. if len(string)*8 > widthleft:
  225. fp = int(game.current["frame"]/10) % (len(string)+7)
  226. text(game, layer, (string+" "+string)[fp:fp+sl], xd+2, y-int(h/5)+3 )
  227. #elif len(string)*8 > widthleft:
  228. # text(game, layer, string[:sl-3]+"...", xd+2, y-int(h/5)+3 )
  229. else:
  230. text(game, layer, string, x+int(w/2), y-int(h/5)+3, align="center")
  231. def button_navigate(game, menu):
  232. """This function will run in each layer to provide keyboard navigation."""
  233. # We don't want to run it if non of the arrow keys are pressed.
  234. # Thus we check for either of the arrow keys is pressed.
  235. if 65361 in game.current["keys"]\
  236. or 65362 in game.current["keys"]\
  237. or 65363 in game.current["keys"]\
  238. or 65364 in game.current["keys"]:
  239. # We want to know things about the currently selected button
  240. select = game.menus[menu]["selected"] # It's number in the list
  241. cur = game.menus[menu]["buttons"][select] # It's coordinates x, y
  242. prevdistance = 100000000000 # Arbitrary huge number
  243. # For all buttons in the menu list, we are going to see if it's
  244. # in a correct direction from the currently selected button, based on
  245. # the arrow key:
  246. # 65361 - RIGHT
  247. # 65362 - UP
  248. # 65363 - LEFT
  249. # 65364 - DOWN
  250. # But since there could be more then one button towards the correct
  251. # side, we are also checking for the closest one ( by comparing the
  252. # lowest possible distance to the currently selected button ).
  253. for n, i in enumerate( game.menus[menu]["buttons"] ):
  254. curdistance = distance(i, cur)
  255. if ((65361 in game.current["keys"] and i[0] < cur[0] )\
  256. or (65362 in game.current["keys"] and i[1] < cur[1] )\
  257. or (65363 in game.current["keys"] and i[0] > cur[0] )\
  258. or (65364 in game.current["keys"] and i[1] > cur[1] ))\
  259. and (curdistance < prevdistance)\
  260. and i != cur:
  261. select = n
  262. prevdistance = curdistance
  263. # And we restart keys, so it will not do this on each frame
  264. game.current["keys"] = []
  265. # And we write the number of the selected key
  266. game.menus[menu]["selected"] = select
  267. def scroll_area(game, layer, menu, x, y, width, height, max_value,
  268. strenght=6):
  269. """This function makes an invisible area in the UI, where there
  270. could be scroolling."""
  271. if max_value == 0:
  272. max_value = 1
  273. x = int(x)
  274. y = int(y)
  275. width = int(width)
  276. height = int(height)
  277. max_value += 5
  278. amount = 0.0
  279. if int(game.current['mx']) in range(x, x+width) \
  280. and int(game.current['my']) in range(y, y+height):
  281. amount = game.current["scroll"][1] * strenght
  282. game.current["scroll"] = [0, 0]
  283. # Middle mouse drag ( for those who have no working wheel
  284. # like myself, lol )
  285. if game.current["MMB"]\
  286. and int(game.current["mx"]) in range(x, x+width)\
  287. and int(game.current["my"]) in range(y, y+height):
  288. amount = 0- ( game.current["my"] - game.previous["my"] )
  289. def logic():
  290. # Scroll logic
  291. game.scroll[menu] -= amount
  292. # If too low
  293. if game.scroll[menu] < (1-max_value+height):
  294. game.scroll[menu] = (1-max_value+height)
  295. # If too high
  296. if game.scroll[menu] > 0:
  297. game.scroll[menu] = 0
  298. logic()
  299. if game.current["testing"]:
  300. color(game, layer, "red")
  301. layer.rectangle(x,y+1,width-1,height-1)
  302. layer.stroke()