UI_elements.py 45 KB

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