UI_elements.py 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235
  1. # THIS FILE IS A PART OF VCStudio
  2. # PYTHON 3
  3. # This a console project manager.
  4. import os
  5. import math
  6. import urllib3
  7. import hashlib
  8. import threading
  9. # GTK module ( Graphical interface
  10. import gi
  11. gi.require_version('Gtk', '3.0')
  12. from gi.repository import Gtk
  13. from gi.repository import Gdk
  14. from gi.repository import GLib
  15. from gi.repository import GdkPixbuf
  16. import cairo
  17. # Own modules
  18. from settings import settings
  19. from settings import talk
  20. from settings import fileformats
  21. # UI
  22. from UI import UI_color
  23. def roundrect(layer, win, x, y, width, height, r, button=False, icon=False,
  24. tip="", fill=True, url="", clip=False, offset=[0,0], text=False):
  25. # This function draws a rectangle with rounded edges.
  26. # A button variable is a calable for the button action. Basically it's a
  27. # function. Roundrect will act as a button.
  28. if button:
  29. #if not current url in the software
  30. if url and url != win.url:
  31. # So icons would not disappear LOL
  32. if icon:
  33. image(layer, win, "settings/themes/"\
  34. +win.settings["Theme"]+"/icons/"+icon+".png", x, y)
  35. return
  36. # If UI testing is on preview. Buttons.
  37. if win.current["testing"]:
  38. UI_color.set(layer, win, "testing_banner")
  39. layer.rectangle(x,y,width,height)
  40. layer.stroke()
  41. if win.current['mx'] in range(int(x+offset[0]), int(x+width+offset[0])) \
  42. and win.current['my'] in range(int(y+offset[1]), int(y+height+offset[1])) :
  43. do = True
  44. if clip:
  45. if win.current['mx'] in range(int(clip[0]), int(clip[0]+clip[2])) \
  46. and win.current['my'] in range(int(clip[1]), int(clip[1]+clip[3])) :
  47. do = True
  48. else:
  49. do = False
  50. else:
  51. do = False
  52. if do:
  53. # cursor
  54. if text:
  55. win.current["cursor"] = win.cursors["text"]
  56. else:
  57. win.current["cursor"] = win.cursors["hand"]
  58. # If holding click
  59. if win.current["LMB"]:
  60. UI_color.set(layer, win, "button_clicked")
  61. else:
  62. UI_color.set(layer, win, "button_active")
  63. # If clicked
  64. if win.previous["LMB"] and not win.current["LMB"]:
  65. win.current["cursor"] = win.cursors["watch"]
  66. button()
  67. # Button might have a tooltip as well
  68. if tip:
  69. tooltip(win, tip)
  70. else:
  71. do = True
  72. if do:
  73. # Making sure that round rectangle will not be smaller then it's round-
  74. # ness. Also with width and height zero, it's going to draw a circle.
  75. if width < r*2:
  76. width = r*2
  77. if height < r*2:
  78. height = r*2
  79. # I just out of blue decided that I want to have a setting to restrict
  80. # the amount of roundness for every roundrect. If the user want let him
  81. # or her have ugly squares everywhere.
  82. if "Roundness" not in win.settings:
  83. win.settings["Roundness"] = 1.0
  84. settings.write("Roundness", 1.0)
  85. r = r*win.settings["Roundness"]
  86. # actuall drawing
  87. layer.move_to(x,y+r)
  88. layer.arc(x+r, y+r, r, math.pi, 3*math.pi/2)
  89. layer.arc(x+width-r, y+r, r, 3*math.pi/2, 0)
  90. layer.arc(x+width-r, y+height-r, r, 0, math.pi/2)
  91. layer.arc(x+r, y+height-r, r, math.pi/2, math.pi)
  92. layer.close_path()
  93. if fill:
  94. layer.fill()
  95. # Icon is a continuation of the button part. Because you need a way to see
  96. # that that the button is even there to begin with.
  97. if icon:
  98. image(layer, win, "settings/themes/"\
  99. +win.settings["Theme"]+"/icons/"+icon+".png", x, y)
  100. def animate(name, win, v1=0, v2=None, time=10, force=False):
  101. # This function will make animating values over time possible. For smooth
  102. # Transisions and things like this it's going to be very usefull.
  103. # Let's clear mess in case they I'm lazy to make all the things
  104. if v2 == None:
  105. v2 = v1
  106. # Set up the animation into the animations. If it's not there yet.
  107. if name not in win.animations or force:
  108. win.animations[name] = [
  109. v1,
  110. v2,
  111. time,
  112. win.current["frame"]
  113. ]
  114. # Let's get data out of the win.animation[name]
  115. v1 = win.animations[name][0]
  116. v2 = win.animations[name][1]
  117. time = win.animations[name][2]
  118. start = win.animations[name][3]
  119. frame = win.current["frame"]
  120. if time == 0:
  121. return v2
  122. # If animation is over.
  123. if start + time < frame:
  124. return v2
  125. # If v1 and v2 are the same. I'm doing it here. In case the value would be
  126. # Animated later. So it will create the animation instance.
  127. if v1 == v2:
  128. return v2
  129. if v1 < v2:
  130. vN = v1 + ((v2 - v1)/time*(frame-start))
  131. else:
  132. vN = v1 - ((v1 - v2)/time*(frame-start))
  133. return vN
  134. def blur(surface, win, amount):
  135. # This function will blur a given layer by scaling it down and scaling it
  136. # back up. It will be doing it only when a given blur setting it active.
  137. # To avoid all kinds of Zero devision problems. And speed up the draw if
  138. # using animated blur values.
  139. if amount < 3: # When Blue value was less then 3 it felt sharp but not enough
  140. return surface # Which caused sense of uneasiness.
  141. # Setting up initial Blur
  142. if not "Blur" in win.settings:
  143. settings.write("Blur", True) # Writing to file
  144. win.settings = settings.load_all() # Loading file back to RAM
  145. # If to active blur. Will be changed in the graphics settings.
  146. if win.settings["Blur"]:
  147. # scaling down
  148. surface1 = cairo.ImageSurface(cairo.FORMAT_ARGB32, win.current['w'],
  149. win.current['h'])
  150. slacedownlayer = cairo.Context(surface1)
  151. slacedownlayer.scale(1/amount,
  152. 1/amount)
  153. slacedownlayer.set_source_surface(surface, 0 , 0)
  154. slacedownlayer.paint()
  155. #scaling back up
  156. surface2 = cairo.ImageSurface(cairo.FORMAT_ARGB32, win.current['w'],
  157. win.current['h'])
  158. slaceuplayer = cairo.Context(surface2)
  159. slaceuplayer.scale(amount,
  160. amount)
  161. slaceuplayer.set_source_surface(surface1, 0 , 0)
  162. # Pixelized Blur
  163. if not "PixelBlur" in win.settings:
  164. settings.write("PixelBlur", False) # Writing to file
  165. win.settings = settings.load_all()
  166. if win.settings["PixelBlur"]:
  167. p = slaceuplayer.get_source()
  168. p.set_filter(cairo.FILTER_NEAREST)
  169. slaceuplayer.paint()
  170. return surface2
  171. else:
  172. return surface
  173. def hash_file(f):
  174. # This function will give me MD5 hashes of various files
  175. try:
  176. BLOCKSIZE = 65536
  177. hasher = hashlib.md5()
  178. with open(f, 'rb') as afile:
  179. buf = afile.read(BLOCKSIZE)
  180. while len(buf) > 0:
  181. hasher.update(buf)
  182. buf = afile.read(BLOCKSIZE)
  183. return str(hasher.hexdigest())
  184. except:
  185. return "FOLDER"
  186. def loadimage(layer, win ,path, x, y, width, height, fit, cell=0):
  187. # Before we load an image we have to know whether the image is local
  188. # in the Internet. Markdown files for example can have links to a
  189. # picture in the internet. And so if a given Image URL is not local
  190. # we have to be able to download it.
  191. # For reasons we all can relate to. I don't want such downloading to
  192. # happen automatically. Unless the user is going to enable auto
  193. # download in the settings.
  194. # Let's set up a setting. I'm doing it here because Images drawn
  195. # first. On the main screen. And the setting does not exists in
  196. # the earlier settings file from the earlier version.
  197. if "auto_download_images" not in win.settings:
  198. win.settings["auto_download_images"] = False
  199. settings.write("auto_download_images", win.settings["auto_download_images"])
  200. filename = "/tmp/"+path.replace("/","_")
  201. # It could an image data without an image .png thing
  202. found = False
  203. for f in fileformats.images:
  204. if filename.endswith(f):
  205. found = True
  206. if not found:
  207. filename = filename+".png"
  208. tmppath = ""
  209. if path.startswith("http") and not os.path.exists(filename):
  210. if win.images[cell][path]["image"] != "downloading":
  211. # If the button is not pressed yet. Then we need to
  212. # put a downlod button. Look down where implement
  213. # the drawing of the image for the button it self.
  214. win.images[cell][path]["loading"] = False
  215. win.images[cell][path]["image"] = "download_button"
  216. return
  217. else:
  218. # Now somebody either pressed the button. Or it set to
  219. # automatic downloads. Then, let's get the file.
  220. win.images[cell][path]["image"] = None
  221. http = urllib3.PoolManager()
  222. r = http.request('GET', path, preload_content=False)
  223. with open(filename, 'wb') as out:
  224. while True:
  225. data = r.read(1024)
  226. if not data:
  227. break
  228. out.write(data)
  229. r.release_conn()
  230. elif path.startswith("http"):
  231. tmppath = path
  232. path = filename
  233. # Also I don't want downloading to happen here. Since if it's not
  234. # downloaded
  235. # So multiple types of the image could be loaded. It could be either
  236. # an image file. Like png or jpeg. Either a video file. Or a blend file
  237. # each with it's own loading problems.
  238. # While cairo can read pngs directly. It's not the best way of doing it
  239. # since it's not capable of reading jpegs and other files. So let's do
  240. # something about it.
  241. foundformat = False
  242. # IMAGEFILES
  243. for f in fileformats.images:
  244. if path.endswith(f):
  245. foundformat = True
  246. # So this parth of the code is for loading all simple images. From
  247. # pngs Jpegs to anything else.
  248. # We are going to use Gtk's GdkPixbuf for this. We will need to
  249. # convert it to cairo surface later
  250. try:
  251. load1 = GdkPixbuf.Pixbuf.new_from_file(path)
  252. except:
  253. try:
  254. load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/"+win.settings["Theme"]+"/icons/image.png")
  255. except:
  256. load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/Default/icons/image.png")
  257. # VIDEOFILES
  258. for f in fileformats.videos:
  259. if path.endswith(f):
  260. foundformat = True
  261. # Now if it's a video. It's going to be a little harder. We will need
  262. # to use totem. It's a video player that exists in many GNU/Linux
  263. # systems. Unfortunatly not everybody could see video previews.
  264. try:
  265. # This is going to require a few steps.
  266. # 1. Making a filename for our totem to output.
  267. part = path.replace("/", "_").replace(" ", "_")
  268. # 2. Calling totem. And telling him that we want a thumbnail.
  269. os.system("totem-video-thumbnailer -s "+str(width)+" "+path\
  270. +" /tmp/vcstudio_thumbnail"+part+".png")
  271. # 3. Loading this thumbnail.
  272. load1 = GdkPixbuf.Pixbuf.new_from_file("/tmp/vcstudio_thumbnail"+part+".png")
  273. # 4. Cleaning the thumbnail from the OS.
  274. try:
  275. os.remove("/tmp/vcstudio_thumbnail"+part+".png")
  276. except:
  277. pass
  278. except:
  279. try:
  280. load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/"+win.settings["Theme"]+"/icons/video.png")
  281. except:
  282. load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/Default/icons/video.png")
  283. # BLEND FILES
  284. for f in ["blend", "blend1"]:
  285. if path.endswith(f):
  286. foundformat = True
  287. # Similarly to the Video. Blends files has their own thumbnailer. This
  288. # time it's inside of the code of VCStudio. But I've copied it from
  289. # the blender's installation folder. So you can find it there too
  290. # it's called blender-thumbnailer.py. Which is a python script. Which
  291. # is cool.
  292. # Because thumbnailer is developed to work regardless of whether blender
  293. # is installed or not. As it's reading the blend file directly. And
  294. # not using some bpy script. We can copy that over to VCStudio and use
  295. # it to give previews to users who do not have blender installed.
  296. try:
  297. # This is going to require a few steps.
  298. # 1. Making a filename for our thumbnailer to output.
  299. part = path.replace("/", "_").replace(" ", "_")
  300. # 2. Calling thumbnailer. And telling him that we want a thumbnail.
  301. os.system("python3 "+os.getcwd()+"/UI/blender-thumbnailer.py "\
  302. +path+" /tmp/vcstudio_thumbnail"+part+".png")
  303. # 3. Loading this thumbnail.
  304. load1 = GdkPixbuf.Pixbuf.new_from_file("/tmp/vcstudio_thumbnail"+part+".png")
  305. # 4. Cleaning the thumbnail from the OS.
  306. try:
  307. os.remove("/tmp/vcstudio_thumbnail"+part+".png")
  308. except:
  309. pass
  310. except:
  311. try:
  312. load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/"+win.settings["Theme"]+"/icons/blender.png")
  313. except:
  314. load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/Default/icons/blender.png")
  315. if not foundformat:
  316. # If you can't find any format. Just use the file icon then
  317. try:
  318. load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/"+win.settings["Theme"]+"/icons/file.png")
  319. except:
  320. load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/Default/icons/file.png")
  321. # Then to convert the pixbuf to a cairo surface
  322. Px = load1.get_width()
  323. Py = load1.get_height()
  324. load2 = cairo.ImageSurface(cairo.FORMAT_ARGB32, Px, Py)
  325. imagedraw = cairo.Context(load2)
  326. Gdk.cairo_set_source_pixbuf( imagedraw, load1, 0, 0)
  327. imagedraw.paint()
  328. # If I want to resize the image for an icon or something. There is
  329. # gonna be the folowing algorythm.
  330. if width or height:
  331. dx = 0
  332. dy = 0
  333. imagesurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
  334. imagedraw = cairo.Context(imagesurface)
  335. # Crop effect. Really hard on my brains.
  336. if fit == 'crop':
  337. if height > load2.get_height()\
  338. or width > load2.get_width():
  339. dx = (width/2) -(load2.get_width() /2)
  340. dy = (height/2)-(load2.get_height()/2)
  341. else:
  342. factor = 1
  343. if (load2.get_height()*(width/load2.get_width()))\
  344. < height:
  345. factor = height / load2.get_height()
  346. else:
  347. factor = width / load2.get_width()
  348. #factor = 0.1
  349. imagedraw.scale(factor, factor)
  350. dx = (width/2)/factor -(load2.get_width() /2)
  351. dy = (height/2)/factor -(load2.get_height()/2)
  352. # Finally I need to implement something but the Crop.
  353. # Fit Width I want to use in MarkDowns. Maybe also in the
  354. # text of the script. Makes sense.
  355. elif fit == "fit_width" and load2.get_width() > width:
  356. factor = width / load2.get_width()
  357. dx = 0
  358. dy = 0
  359. imagesurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(load2.get_height()*factor))
  360. imagedraw = cairo.Context(imagesurface)
  361. imagedraw.scale(factor, factor)
  362. elif fit == "fit_width":
  363. dx = 0
  364. dy = 0
  365. imagesurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(load2.get_width()), int(load2.get_height()))
  366. imagedraw = cairo.Context(imagesurface)
  367. # Let's make an ability for theme makers to simply color the standard
  368. # icons into any shade.
  369. imagedraw.set_source_surface(load2, dx, dy)
  370. imagedraw.paint()
  371. else:
  372. imagesurface = load2
  373. # Saving it into the win.images
  374. if tmppath:
  375. path = tmppath
  376. win.images[cell][path]["loading"] = False
  377. win.images[cell][path]["image"] = imagesurface
  378. win.images[cell][path]["hash"] = hash_file(path)
  379. win.imageload -= 1
  380. def reload_images(win):
  381. # This function will force the images to refrash. For things like sync
  382. # or simply where a file is changed. Or for any other reason. Now we
  383. # probably don't need to do this on every image. So instead we are going
  384. # to compare the hashes of the current images to the ones of the files
  385. # and if they changed. We are going to update those.
  386. for cell in list(win.images.keys()):
  387. for image in list(win.images[cell].keys()):
  388. if hash_file(image) != win.images[cell][image]["hash"]:
  389. win.images[cell][image]["loading"] = True
  390. pass
  391. def image(layer, win ,path, x, y, width=0, height=0, fit="crop", cell=0, offset=[0,0]):
  392. # This module will handle drawing images to the layers. It's not that hard
  393. # to do in cairo by default. But i'm doing it at every frame. And so it
  394. # means a system of images should exist to take out the load. Basically
  395. # it will make sure the images is loaded only ones. And for the next draw
  396. # calls it will forward the old image.
  397. # Attempt of optimization. Keeping in mind the nature of the programm. Basi-
  398. # cally I will not load image unless x and y are in frame. Kind a ugly. I
  399. # know. But I don't want to even bother checking the resolution of the image
  400. # if it's not in a frame. Roughly speaking. Will see maybe I will make do
  401. # something to make it better.
  402. if cell not in win.images:
  403. win.images[cell] = {}
  404. if int(x) not in range(int(0-width ), int(win.current["w"])) or \
  405. int(y) not in range(int(0-height-win.current["h"]), int(win.current["h"])) :
  406. return
  407. # If you ran this software you probably noticed that images are loading
  408. # dynamically. I did it using this following crazy algorythm borowed from
  409. # the old organizer.
  410. # Here I want to load images. But I also want to give myself an ability to
  411. # load refrash images. Without refrashing all the images. For this I'm going
  412. # to use hashes. Specificly MD5 but it doesn't matter really. The idea is
  413. # that on a special redraw call. We are going to check hashes storred with
  414. # those from the file. And only if a file really changed. Then update the image.
  415. # First the icon might not exist in the icon pack. Since I want to make
  416. # packs using
  417. if win.settings["Theme"] in path and not os.path.exists(path):
  418. path = path.replace(win.settings["Theme"], "Default")
  419. if path not in win.images[cell] or win.images[cell][path]["loading"]:
  420. # If this is the first time this image is draw we want to create it a data
  421. # structure. So we could load the image.
  422. win.images[cell][path] = {
  423. "loading": True, # Whether the image is currently loading.
  424. "image" : None, # The image data it self.
  425. "hash" : "" # The hash of the image file.
  426. }
  427. # Then we want to insure to load only a given amount of images in the same
  428. # time. For this we are going to use the next variable.
  429. MAXLOAD = 10
  430. if win.imageload < MAXLOAD:
  431. win.imageload += 1
  432. # Now we want to load the image. But we are going to do that in a separate thread
  433. # not to freeze the UI while the image is loading.
  434. # See: loadimage() function for the continuation of this.
  435. t = threading.Thread(target=loadimage, args=(layer, win ,path, x, y, width, height, fit, cell))
  436. t.start()
  437. #loading it back
  438. else:
  439. if win.images[cell][path]["image"] and win.images[cell][path]["image"] != "download_button":
  440. imagesurface = win.images[cell][path]["image"]
  441. # Writting the image to the screen
  442. try:
  443. if "icons" in win.color and "settings/themes/" in path:
  444. UI_color.set(layer, win, "icons")
  445. layer.rectangle(x,y,0,0)
  446. layer.fill()
  447. layer.mask_surface(imagesurface, x, y)
  448. UI_color.set(layer, win, "icons")
  449. layer.fill()
  450. else:
  451. layer.set_source_surface(imagesurface, x, y)
  452. layer.paint()
  453. except:
  454. pass
  455. # And if testing
  456. if win.current["testing"]:
  457. UI_color.set(layer, win, "testing_image")
  458. layer.rectangle(x,y,imagesurface.get_width(),imagesurface.get_height())
  459. layer.stroke()
  460. elif win.images[cell][path]["image"] == "download_button":
  461. # If the image is online. By default it will not load it unless the
  462. # user presses a download button. So here it is.
  463. def do():
  464. win.images[cell][path]["image"] = "downloading"
  465. loadimage(layer, win ,path, x, y, width, height, fit, cell)
  466. win.images[cell][path]["loading"] = True
  467. # Some link image will be automatically clicked. This
  468. # fixes the problem.
  469. win.current["LMB"] = False
  470. win.previous["LMB"] = False
  471. if win.settings["auto_download_images"]:
  472. t = threading.Thread(target=do)
  473. t.start()
  474. else:
  475. roundrect(layer, win, x,y,40,40,10,
  476. icon="image_link",
  477. button=do,
  478. offset=offset,
  479. tip=path)
  480. def tooltip(win, text):
  481. layer = win.tooltip
  482. # This function draws a tooltip helper window.
  483. # Just in case
  484. text = str(text)
  485. # Let's get dimantions of the cube first.
  486. lines = 0
  487. maxletters = 0
  488. for line in text.split("\n"):
  489. lines += 1
  490. if len(line) > maxletters:
  491. maxletters = len(line)
  492. # Now when we now the mount of lines and the max lenght of a line. We can
  493. # start actually drawing something.
  494. # Let's try to make so it's not out of the frame.
  495. sx = win.current["mx"]
  496. sy = win.current["my"]
  497. if sx+(maxletters*9)+40 > win.current["w"]:
  498. sx = win.current["w"] - ((maxletters*9)+40)
  499. if sy+(lines*20)+10 > win.current["h"]:
  500. sy = win.current["h"] - ((lines*20)+10)
  501. # Rectangle
  502. UI_color.set(layer, win, "node_background")
  503. roundrect(layer, win,
  504. sx,
  505. sy,
  506. (maxletters*9)+40,
  507. (lines*20)+10,
  508. 10)
  509. # Text it self
  510. layer.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL,
  511. cairo.FONT_WEIGHT_NORMAL)
  512. layer.set_font_size(15)
  513. UI_color.set(layer, win, "text_normal")
  514. for num, line in enumerate(text.split("\n")):
  515. layer.move_to(sx+20,
  516. sy+20+(20*num) )
  517. layer.show_text(line)
  518. def scroll_area(layer, win, name, x, y, width, height, maxlength,
  519. bar=False,sideways=False, mmb=False, mmb_only=False,
  520. url="", strenght=50, bar_always=False):
  521. # This function going to handle all the scrolling windows. Making it so it's
  522. # relativelly simple to set up big widgets with in small areas.
  523. # It will handle only the value of the scroll stored in win.scroll[name]
  524. if maxlength == 0:
  525. maxlength = 1
  526. x = int(x)
  527. y = int(y)
  528. width = int(width)
  529. height = int(height)
  530. # First let's set one up if it's not setup
  531. if name not in win.scroll:
  532. win.scroll[name] = 0
  533. return
  534. # Getting scroll amount based on all kinds of settings. AT THIS MOMENT
  535. # IT'S IN AN ALPHA BECAUSE I'M LAZY. I GONNA IMPLEMENT THING AS I NEED THEM
  536. # Or you can do that. IDK...
  537. amount = 0.0
  538. if win.current['mx'] in range(x, x+width) \
  539. and win.current['my'] in range(y, y+height) :
  540. if not mmb_only:
  541. amount = win.current["scroll"][1]*strenght
  542. if mmb_only:
  543. mmb = True
  544. # Middle mouse button scroll, or using a graphical tablet.
  545. if mmb:
  546. if win.current["MMB"]:
  547. if not sideways:
  548. amount = 0 - ( win.current["my"] - win.previous["my"] )
  549. else:
  550. amount = 0 - ( win.current["mx"] - win.previous["mx"] )
  551. # I guess I need to separate the logic into a separate minifunction.
  552. # I will use later for the scroll bar thingy. So not to rewrite the code
  553. # Here is a function thingy.
  554. def logic():
  555. if url and url != win.url:
  556. return
  557. # Scroll logic
  558. win.scroll[name] -= amount
  559. if not sideways:
  560. # If too low
  561. if win.scroll[name] < (1-maxlength+height):
  562. win.scroll[name] = (1-maxlength+height)
  563. # If too high
  564. if win.scroll[name] > 0:
  565. win.scroll[name] = 0
  566. else:
  567. # If too low
  568. if win.scroll[name] < (1-maxlength+width):
  569. win.scroll[name] = (1-maxlength+width)
  570. # If too high
  571. if win.scroll[name] > 0:
  572. win.scroll[name] = 0
  573. logic()
  574. # Not BAR. Which is going to be drawn at a side of what ever content there
  575. # Basically a scrollbar. But implemented from scratch. Because I'm crazy.
  576. # and have nothing better to do now.
  577. if bar:
  578. # For now I'm going to implement only vertical bar. I gonna implement
  579. # the horisontal one when the time comes.
  580. if not sideways:
  581. # Fist let's make some math in front. Because things like this
  582. # require ton of maths. And it's good to have some predone.
  583. tobreak = False # Also if we can abort the operation early with it.
  584. fraction = height / maxlength # Similar to progress bar for now
  585. if fraction > 1:
  586. tobreak = True
  587. fraction = 1
  588. # To break parameter basically says. To draw it the bar only when
  589. # it's actully needed. When content aka maxlength is bigger then
  590. # our viewport.
  591. if not tobreak or bar_always:
  592. # Now the offset value. That will move our progress bar with
  593. # the scroll value.
  594. offset = (height-60)*( (1-win.scroll[name]) / maxlength )
  595. # Background bar
  596. UI_color.set(layer, win, "background")
  597. roundrect(layer, win,
  598. (x+width)-20,
  599. y+30,
  600. 10,
  601. height-60,
  602. 5
  603. )
  604. # Active bar
  605. UI_color.set(layer, win, "button_active")
  606. # Let's save a little bit more math because it's going to be
  607. # vild. And I love it.
  608. Lx = (x+width)-20
  609. LSx = 10
  610. Ly = y+30+offset
  611. LSy = (height-60)*fraction
  612. # Mouse over thingy. To make the bat a tiny bit brighter.
  613. # indicating to the user that it's now could be used.
  614. if win.current['mx'] in range(int(Lx), int(Lx+LSx)) \
  615. and win.current['my'] in range(int(Ly), int(Lx+LSy)) :
  616. UI_color.set(layer, win, "button_clicked")
  617. # Now separatelly we gonna check for if the mouse pressed.
  618. # Remember if it's not pressed it's False. It's written in one
  619. # of the files. Basically I want to be able to move the mouse
  620. # outside the bar while moving. And so it won't cancel the motion.
  621. # It seems like I did something wrong. Anyways it works.
  622. if win.current["LMB"]:
  623. if int(win.current['LMB'][0]) in range(int(Lx), int(Lx+LSx)) \
  624. and int(win.current['LMB'][1]) in range(int(y), int(y+(height-60))) :
  625. UI_color.set(layer, win, "button_clicked")
  626. # A bit more math to set the value back.
  627. amount = ( win.current["my"] - win.previous["my"] ) / \
  628. (height-60) * maxlength
  629. logic() # Yeah. Look a few lines back.
  630. # And only after all of this nonsense we can draw the cube. Or
  631. # should I say roundrect? A button? Aaaaaa....
  632. roundrect(layer, win,
  633. Lx,
  634. Ly,
  635. LSx,
  636. LSy,
  637. 5
  638. )
  639. else:
  640. # Fist let's make some math in front. Because things like this
  641. # require ton of maths. And it's good to have some predone.
  642. tobreak = False # Also if we can abort the operation early with it.
  643. fraction = width / maxlength # Similar to progress bar for now
  644. if fraction > 1:
  645. tobreak = True
  646. fraction = 1
  647. # To break parameter basically says. To draw it the bar only when
  648. # it's actully needed. When content aka maxlength is bigger then
  649. # our viewport.
  650. if not tobreak or bar_always:
  651. # Now the offset value. That will move our progress bar with
  652. # the scroll value.
  653. offset = (width-60)*( (1-win.scroll[name]) / maxlength )
  654. # Background bar
  655. UI_color.set(layer, win, "background")
  656. roundrect(layer, win,
  657. x+30,
  658. (y+height)-20,
  659. width-60,
  660. 10,
  661. 5
  662. )
  663. # Active bar
  664. UI_color.set(layer, win, "button_active")
  665. # Let's save a little bit more math because it's going to be
  666. # vild. And I love it.
  667. Lx = (y+height)-20
  668. LSx = 10
  669. Ly = x+30+offset
  670. LSy = (width-60)*fraction
  671. # Mouse over thingy. To make the bat a tiny bit brighter.
  672. # indicating to the user that it's now could be used.
  673. if win.current['my'] in range(int(Lx), int(Lx+LSx)) \
  674. and win.current['mx'] in range(int(Ly), int(Lx+LSy)) :
  675. UI_color.set(layer, win, "button_clicked")
  676. # Now separatelly we gonna check for if the mouse pressed.
  677. # Remember if it's not pressed it's False. It's written in one
  678. # of the files. Basically I want to be able to move the mouse
  679. # outside the bar while moving. And so it won't cancel the motion.
  680. # It seems like I did something wrong. Anyways it works.
  681. if win.current["LMB"]:
  682. if int(win.current['LMB'][1]) in range(int(Lx), int(Lx+LSx)) \
  683. and int(win.current['LMB'][0]) in range(int(x), int(x+(width-60))) :
  684. UI_color.set(layer, win, "button_clicked")
  685. # A bit more math to set the value back.
  686. amount = ( win.current["mx"] - win.previous["mx"] ) / \
  687. (width-60) * maxlength
  688. logic() # Yeah. Look a few lines back.
  689. # And only after all of this nonsense we can draw the cube. Or
  690. # should I say roundrect? A button? Aaaaaa....
  691. roundrect(layer, win,
  692. Ly,
  693. Lx,
  694. LSy,
  695. LSx,
  696. 5
  697. )
  698. def text(outlayer, win, name, x, y, width, height, set_text="", parse=False, fill=True,
  699. editable=True, multiline=False , linebreak=False, centered=False, tip="",
  700. offset=[0,0]):
  701. # This function will handle all the text writting in the software.
  702. # I'm not sure about how parsing going to work for script files later.
  703. # But if it's currently works, means that I already implemented it into
  704. # the program.
  705. # Making the layer
  706. surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(height))
  707. layer = cairo.Context(surface)
  708. layer.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
  709. layer.set_font_size(20)
  710. # Some challenges that it will have is how to correctly store data about
  711. # the text in the system. I think we can use the win.text variable to store
  712. # directories of the data.
  713. if name not in win.text:
  714. # I need to get something done before I can pu scroll in.
  715. scrollname = name
  716. while scrollname in win.scroll:
  717. scrollname = scrollname+"_text"
  718. win.text[name] = {
  719. "text" :set_text, # Actuall text you are editing.
  720. "cursor":[len(str(set_text)),len(str(set_text))], # Cursor
  721. "insert":False, # Whether the insert mode is on
  722. "scroll":scrollname # If multiline. The pointer for the scroll value.
  723. }
  724. # Background
  725. if fill:
  726. UI_color.set(layer, win, "darker_parts")
  727. roundrect(layer, win,
  728. 0,
  729. 0,
  730. width,
  731. height,
  732. 10)
  733. layer.fill()
  734. # Now after filling it up. I want to clip everything. SO no text will get
  735. # out of a given area.
  736. roundrect(layer, win,
  737. 0,
  738. 0,
  739. width,
  740. height,
  741. 10,
  742. fill=False)
  743. layer.clip()
  744. # Now I want to give a preview of the text it self. BUT. I need to be sure
  745. # that if the text longer then a given width and there is no multiline or a
  746. # linebreak. Then it scrolls sideways to the cursor.
  747. # Automatic scroll system: Based on the position of the cursor.
  748. offsetX = 0
  749. cursor2location = win.text[name]["cursor"][1]*12 + offsetX
  750. while cursor2location > width - 5:
  751. offsetX -= 1
  752. cursor2location = win.text[name]["cursor"][1]*12 + offsetX
  753. # Text selection. AKA cursor
  754. # So here we draw the cursor
  755. if editable:
  756. UI_color.set(layer, win, "node_blendfile")
  757. if win.text[name]["cursor"][0] == win.text[name]["cursor"][1]:
  758. if win.blink:
  759. layer.rectangle(
  760. win.text[name]["cursor"][0]*12+5 +offsetX,
  761. 5,
  762. (win.text[name]["cursor"][1]*12)-(win.text[name]["cursor"][0]*12)+2,
  763. 30
  764. )
  765. else:
  766. roundrect(layer, win,
  767. win.text[name]["cursor"][0]*12+5 +offsetX,
  768. 5,
  769. (win.text[name]["cursor"][1]*12)-(win.text[name]["cursor"][0]*12)+2,
  770. 30,
  771. 5,
  772. fill=False
  773. )
  774. if win.textactive == name:
  775. layer.fill()
  776. elif win.text[name]["cursor"][0] != win.text[name]["cursor"][1]:
  777. layer.stroke()
  778. # Making sure that cursor is correct. Because a lot of bugs are happening
  779. # with it and it's not cool.
  780. # Mouse select
  781. if win.current["LMB"]:
  782. if int(win.current["mx"]-offset[0]) in range(int(x), int(x+width))\
  783. and int(win.current["my"]-offset[1]) in range(int(y), int(y+height)):
  784. win.text[name]["cursor"][0] = int((win.current["LMB"][0]-x-offsetX-offset[0])/12)
  785. win.text[name]["cursor"][1] = int((win.current["mx"]-x-offsetX-offset[0])/12)
  786. # If second part of selection ends up bigger then the first. Reverse them.
  787. if win.text[name]["cursor"][0] > win.text[name]["cursor"][1]:
  788. win.text[name]["cursor"] = [
  789. win.text[name]["cursor"][1],
  790. win.text[name]["cursor"][0]]
  791. # If any part ends up beyond the text. Clip them in.
  792. if win.text[name]["cursor"][0] < 0:
  793. win.text[name]["cursor"][0] = 0
  794. if win.text[name]["cursor"][1] < 0:
  795. win.text[name]["cursor"][1] = 0
  796. if win.text[name]["cursor"][0] > len(str(win.text[name]["text"])):
  797. win.text[name]["cursor"][0] = len(str(win.text[name]["text"]))
  798. if win.text[name]["cursor"][1] > len(str(win.text[name]["text"])):
  799. win.text[name]["cursor"][1] = len(str(win.text[name]["text"]))
  800. # Drawing the text
  801. UI_color.set(layer, win, "text_normal")
  802. layer.move_to(5+offsetX, height/2+5)
  803. if centered:
  804. layer.move_to(width/2-len(str(win.text[name]["text"]))*12/2, height/2+5)
  805. layer.show_text(str(win.text[name]["text"]))
  806. # Editing the text
  807. if win.current["keys"] and editable and name == win.textactive:
  808. # Let's filter the input first.
  809. # For example
  810. if not multiline: #Removing enter key press
  811. if 65293 in win.current["keys"] or 65421 in win.current["keys"]\
  812. or 65289 in win.current["keys"]: # TAB
  813. win.current["key_letter"] = ""
  814. prevlen = len(win.text[name]["text"])
  815. clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
  816. regularclean = True
  817. ORD = 0
  818. try:
  819. ORD = ord(win.current["key_letter"])
  820. except:
  821. pass
  822. backremove = False # Whether to make selection go to 0 width thing
  823. #print(ORD, win.text[name]["cursor"][0])
  824. # Backspace
  825. if 65288 in win.current["keys"]:
  826. if win.text[name]["cursor"][0] != 0 and win.text[name]["cursor"][0]\
  827. == win.text[name]["cursor"][1]:
  828. win.text[name]["text"] = win.text[name]["text"]\
  829. [:win.text[name]["cursor"][0]-1]+\
  830. win.text[name]["text"]\
  831. [win.text[name]["cursor"][1]:]
  832. elif win.text[name]["cursor"][1] != 0 and win.text[name]["cursor"][0]\
  833. != win.text[name]["cursor"][1]:
  834. win.text[name]["text"] = win.text[name]["text"]\
  835. [:win.text[name]["cursor"][0]]+\
  836. win.text[name]["text"]\
  837. [win.text[name]["cursor"][1]:]
  838. backremove = True
  839. # Ctrl - C
  840. elif ORD == 3:
  841. clipboard.set_text( win.text[name]["text"]\
  842. [win.text[name]["cursor"][0]:win.text[name]["cursor"][1]], -1)
  843. # Ctrl - V
  844. elif ORD == 22:
  845. cliptext = str(clipboard.wait_for_text())
  846. win.text[name]["text"] = win.text[name]["text"]\
  847. [:win.text[name]["cursor"][0]]\
  848. + cliptext +\
  849. win.text[name]["text"]\
  850. [win.text[name]["cursor"][1]:]
  851. win.text[name]["cursor"][0] = win.text[name]["cursor"][1]
  852. # Ctrl - A
  853. elif ORD == 1:
  854. win.text[name]["cursor"][0] = 0
  855. win.text[name]["cursor"][1] = len(win.text[name]["text"])
  856. # To clear up the Controll
  857. elif 65507 in win.current["keys"]:
  858. pass
  859. # Shift
  860. elif 65506 in win.current["keys"]:
  861. # Right
  862. if 65363 in win.current["keys"]:
  863. win.text[name]["cursor"][1] = win.text[name]["cursor"][1] + 1
  864. #win.current["keys"].remove(65363)
  865. # Left
  866. elif 65361 in win.current["keys"]:
  867. if win.text[name]["cursor"][1] > win.text[name]["cursor"][0]:
  868. win.text[name]["cursor"][1] = win.text[name]["cursor"][1] - 1
  869. #win.current["keys"].remove(65361)
  870. # Right button
  871. elif 65363 in win.current["keys"]:
  872. win.text[name]["cursor"][0] = win.text[name]["cursor"][0] + 1
  873. win.text[name]["cursor"][1] = win.text[name]["cursor"][0]
  874. win.current["keys"].remove(65363)
  875. # Left button
  876. elif 65361 in win.current["keys"]:
  877. win.text[name]["cursor"][0] = win.text[name]["cursor"][0] - 1
  878. win.text[name]["cursor"][1] = win.text[name]["cursor"][0]
  879. win.current["keys"].remove(65361)
  880. # Escape
  881. elif 65307 in win.current["keys"]:
  882. win.textactive = ""
  883. win.current["keys"].remove(65307)
  884. else:
  885. win.text[name]["text"] = win.text[name]["text"]\
  886. [:win.text[name]["cursor"][0]]\
  887. + win.current["key_letter"]+\
  888. win.text[name]["text"]\
  889. [win.text[name]["cursor"][1]:]
  890. # Auto moving the cursor
  891. nowlen = len(win.text[name]["text"])
  892. if win.text[name]["cursor"][0] == win.text[name]["cursor"][1]:
  893. win.text[name]["cursor"][0] = win.text[name]["cursor"][0] + (nowlen - prevlen)
  894. win.text[name]["cursor"][1] = win.text[name]["cursor"][0]
  895. elif backremove:
  896. win.text[name]["cursor"][1] = win.text[name]["cursor"][0]
  897. if nowlen != prevlen and regularclean:
  898. # Deleting all the keys from the keys. So yeah.
  899. win.current["keys"] = []
  900. # Outputing to the outlayer.
  901. outlayer.set_source_surface(surface, x, y)
  902. outlayer.paint()
  903. # Button if editable.
  904. if editable:
  905. def do():
  906. win.textactive = name
  907. roundrect(outlayer, win,
  908. x,
  909. y,
  910. width,
  911. height,
  912. 10,
  913. fill=False,
  914. button=do,
  915. tip=tip,
  916. offset=offset,
  917. text=True)
  918. outlayer.stroke()
  919. if win.textactive == name:
  920. UI_color.set(outlayer, win, "button_active")
  921. roundrect(outlayer, win,
  922. x,
  923. y,
  924. width,
  925. height,
  926. 10,
  927. fill=False)
  928. outlayer.stroke()