Blender-Pipeline/UI/UI_elements.py

1220 lines
44 KiB
Python

# THIS FILE IS A PART OF VCStudio
# PYTHON 3
# This a console project manager.
import os
import math
import urllib3
import hashlib
import threading
# GTK module ( Graphical interface
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GLib
from gi.repository import GdkPixbuf
import cairo
# Own modules
from settings import settings
from settings import talk
from settings import fileformats
# UI
from UI import UI_color
def roundrect(layer, win, x, y, width, height, r, button=False, icon=False,
tip="", fill=True, url="", clip=False, offset=[0,0], text=False):
# This function draws a rectangle with rounded edges.
# A button variable is a calable for the button action. Basically it's a
# function. Roundrect will act as a button.
if button:
#if not current url in the software
if url and url != win.url:
# So icons would not disappear LOL
if icon:
image(layer, win, "settings/themes/"\
+win.settings["Theme"]+"/icons/"+icon+".png", x, y)
return
# If UI testing is on preview. Buttons.
if win.current["testing"]:
UI_color.set(layer, win, "testing_banner")
layer.rectangle(x,y,width,height)
layer.stroke()
if win.current['mx'] in range(int(x+offset[0]), int(x+width+offset[0])) \
and win.current['my'] in range(int(y+offset[1]), int(y+height+offset[1])) :
do = True
if clip:
if win.current['mx'] in range(int(clip[0]), int(clip[0]+clip[2])) \
and win.current['my'] in range(int(clip[1]), int(clip[1]+clip[3])) :
do = True
else:
do = False
else:
do = False
if do:
# cursor
if text:
win.current["cursor"] = win.cursors["text"]
else:
win.current["cursor"] = win.cursors["hand"]
# If holding click
if win.current["LMB"]:
UI_color.set(layer, win, "button_clicked")
else:
UI_color.set(layer, win, "button_active")
# If clicked
if win.previous["LMB"] and not win.current["LMB"]:
win.current["cursor"] = win.cursors["watch"]
button()
# Button might have a tooltip as well
if tip:
tooltip(win, tip)
else:
do = True
if do:
# Making sure that round rectangle will not be smaller then it's round-
# ness. Also with width and height zero, it's going to draw a circle.
if width < r*2:
width = r*2
if height < r*2:
height = r*2
# I just out of blue decided that I want to have a setting to restrict
# the amount of roundness for every roundrect. If the user want let him
# or her have ugly squares everywhere.
if "Roundness" not in win.settings:
win.settings["Roundness"] = 1.0
settings.write("Roundness", 1.0)
r = r*win.settings["Roundness"]
# actuall drawing
layer.move_to(x,y+r)
layer.arc(x+r, y+r, r, math.pi, 3*math.pi/2)
layer.arc(x+width-r, y+r, r, 3*math.pi/2, 0)
layer.arc(x+width-r, y+height-r, r, 0, math.pi/2)
layer.arc(x+r, y+height-r, r, math.pi/2, math.pi)
layer.close_path()
if fill:
layer.fill()
# Icon is a continuation of the button part. Because you need a way to see
# that that the button is even there to begin with.
if icon:
image(layer, win, "settings/themes/"\
+win.settings["Theme"]+"/icons/"+icon+".png", x, y)
def animate(name, win, v1=0, v2=None, time=10, force=False):
# This function will make animating values over time possible. For smooth
# Transisions and things like this it's going to be very usefull.
# Let's clear mess in case they I'm lazy to make all the things
if v2 == None:
v2 = v1
# Set up the animation into the animations. If it's not there yet.
if name not in win.animations or force:
win.animations[name] = [
v1,
v2,
time,
win.current["frame"]
]
# Let's get data out of the win.animation[name]
v1 = win.animations[name][0]
v2 = win.animations[name][1]
time = win.animations[name][2]
start = win.animations[name][3]
frame = win.current["frame"]
if time == 0:
return v2
# If animation is over.
if start + time < frame:
return v2
# If v1 and v2 are the same. I'm doing it here. In case the value would be
# Animated later. So it will create the animation instance.
if v1 == v2:
return v2
if v1 < v2:
vN = v1 + ((v2 - v1)/time*(frame-start))
else:
vN = v1 - ((v1 - v2)/time*(frame-start))
return vN
def blur(surface, win, amount):
# This function will blur a given layer by scaling it down and scaling it
# back up. It will be doing it only when a given blur setting it active.
# To avoid all kinds of Zero devision problems. And speed up the draw if
# using animated blur values.
if amount < 3: # When Blue value was less then 3 it felt sharp but not enough
return surface # Which caused sense of uneasiness.
# Setting up initial Blur
if not "Blur" in win.settings:
settings.write("Blur", True) # Writing to file
win.settings = settings.load_all() # Loading file back to RAM
# If to active blur. Will be changed in the graphics settings.
if win.settings["Blur"]:
# scaling down
surface1 = cairo.ImageSurface(cairo.FORMAT_ARGB32, win.current['w'],
win.current['h'])
slacedownlayer = cairo.Context(surface1)
slacedownlayer.scale(1/amount,
1/amount)
slacedownlayer.set_source_surface(surface, 0 , 0)
slacedownlayer.paint()
#scaling back up
surface2 = cairo.ImageSurface(cairo.FORMAT_ARGB32, win.current['w'],
win.current['h'])
slaceuplayer = cairo.Context(surface2)
slaceuplayer.scale(amount,
amount)
slaceuplayer.set_source_surface(surface1, 0 , 0)
slaceuplayer.paint()
return surface2
else:
return surface
def hash_file(f):
# This function will give me MD5 hashes of various files
try:
BLOCKSIZE = 65536
hasher = hashlib.md5()
with open(f, 'rb') as afile:
buf = afile.read(BLOCKSIZE)
while len(buf) > 0:
hasher.update(buf)
buf = afile.read(BLOCKSIZE)
return str(hasher.hexdigest())
except:
return "FOLDER"
def loadimage(layer, win ,path, x, y, width, height, fit, cell=0):
# Before we load an image we have to know whether the image is local
# in the Internet. Markdown files for example can have links to a
# picture in the internet. And so if a given Image URL is not local
# we have to be able to download it.
# For reasons we all can relate to. I don't want such downloading to
# happen automatically. Unless the user is going to enable auto
# download in the settings.
# Let's set up a setting. I'm doing it here because Images drawn
# first. On the main screen. And the setting does not exists in
# the earlier settings file from the earlier version.
if "auto_download_images" not in win.settings:
win.settings["auto_download_images"] = False
settings.write("auto_download_images", win.settings["auto_download_images"])
filename = "/tmp/"+path.replace("/","_")
# It could an image data without an image .png thing
found = False
for f in fileformats.images:
if filename.endswith(f):
found = True
if not found:
filename = filename+".png"
tmppath = ""
if path.startswith("http") and not os.path.exists(filename):
if win.images[cell][path]["image"] != "downloading":
# If the button is not pressed yet. Then we need to
# put a downlod button. Look down where implement
# the drawing of the image for the button it self.
win.images[cell][path]["loading"] = False
win.images[cell][path]["image"] = "download_button"
return
else:
# Now somebody either pressed the button. Or it set to
# automatic downloads. Then, let's get the file.
win.images[cell][path]["image"] = None
http = urllib3.PoolManager()
r = http.request('GET', path, preload_content=False)
with open(filename, 'wb') as out:
while True:
data = r.read(1024)
if not data:
break
out.write(data)
r.release_conn()
elif path.startswith("http"):
tmppath = path
path = filename
# Also I don't want downloading to happen here. Since if it's not
# downloaded
# So multiple types of the image could be loaded. It could be either
# an image file. Like png or jpeg. Either a video file. Or a blend file
# each with it's own loading problems.
# While cairo can read pngs directly. It's not the best way of doing it
# since it's not capable of reading jpegs and other files. So let's do
# something about it.
foundformat = False
# IMAGEFILES
for f in fileformats.images:
if path.endswith(f):
foundformat = True
# So this parth of the code is for loading all simple images. From
# pngs Jpegs to anything else.
# We are going to use Gtk's GdkPixbuf for this. We will need to
# convert it to cairo surface later
try:
load1 = GdkPixbuf.Pixbuf.new_from_file(path)
except:
try:
load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/"+win.settings["Theme"]+"/icons/image.png")
except:
load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/Default/icons/image.png")
# VIDEOFILES
for f in fileformats.videos:
if path.endswith(f):
foundformat = True
# Now if it's a video. It's going to be a little harder. We will need
# to use totem. It's a video player that exists in many GNU/Linux
# systems. Unfortunatly not everybody could see video previews.
try:
# This is going to require a few steps.
# 1. Making a filename for our totem to output.
part = path.replace("/", "_").replace(" ", "_")
# 2. Calling totem. And telling him that we want a thumbnail.
os.system("totem-video-thumbnailer -s "+str(width)+" "+path\
+" /tmp/vcstudio_thumbnail"+part+".png")
# 3. Loading this thumbnail.
load1 = GdkPixbuf.Pixbuf.new_from_file("/tmp/vcstudio_thumbnail"+part+".png")
# 4. Cleaning the thumbnail from the OS.
try:
os.remove("/tmp/vcstudio_thumbnail"+part+".png")
except:
pass
except:
try:
load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/"+win.settings["Theme"]+"/icons/video.png")
except:
load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/Default/icons/video.png")
# BLEND FILES
for f in ["blend", "blend1"]:
if path.endswith(f):
foundformat = True
# Similarly to the Video. Blends files has their own thumbnailer. This
# time it's inside of the code of VCStudio. But I've copied it from
# the blender's installation folder. So you can find it there too
# it's called blender-thumbnailer.py. Which is a python script. Which
# is cool.
# Because thumbnailer is developed to work regardless of whether blender
# is installed or not. As it's reading the blend file directly. And
# not using some bpy script. We can copy that over to VCStudio and use
# it to give previews to users who do not have blender installed.
try:
# This is going to require a few steps.
# 1. Making a filename for our thumbnailer to output.
part = path.replace("/", "_").replace(" ", "_")
# 2. Calling thumbnailer. And telling him that we want a thumbnail.
os.system("python3 "+os.getcwd()+"/UI/blender-thumbnailer.py "\
+path+" /tmp/vcstudio_thumbnail"+part+".png")
# 3. Loading this thumbnail.
load1 = GdkPixbuf.Pixbuf.new_from_file("/tmp/vcstudio_thumbnail"+part+".png")
# 4. Cleaning the thumbnail from the OS.
try:
os.remove("/tmp/vcstudio_thumbnail"+part+".png")
except:
pass
except:
try:
load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/"+win.settings["Theme"]+"/icons/blender.png")
except:
load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/Default/icons/blender.png")
if not foundformat:
# If you can't find any format. Just use the file icon then
try:
load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/"+win.settings["Theme"]+"/icons/file.png")
except:
load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/Default/icons/file.png")
# Then to convert the pixbuf to a cairo surface
Px = load1.get_width()
Py = load1.get_height()
load2 = cairo.ImageSurface(cairo.FORMAT_ARGB32, Px, Py)
imagedraw = cairo.Context(load2)
Gdk.cairo_set_source_pixbuf( imagedraw, load1, 0, 0)
imagedraw.paint()
# If I want to resize the image for an icon or something. There is
# gonna be the folowing algorythm.
if width or height:
dx = 0
dy = 0
imagesurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
imagedraw = cairo.Context(imagesurface)
# Crop effect. Really hard on my brains.
if fit == 'crop':
if height > load2.get_height()\
or width > load2.get_width():
dx = (width/2) -(load2.get_width() /2)
dy = (height/2)-(load2.get_height()/2)
else:
factor = 1
if (load2.get_height()*(width/load2.get_width()))\
< height:
factor = height / load2.get_height()
else:
factor = width / load2.get_width()
#factor = 0.1
imagedraw.scale(factor, factor)
dx = (width/2)/factor -(load2.get_width() /2)
dy = (height/2)/factor -(load2.get_height()/2)
# Finally I need to implement something but the Crop.
# Fit Width I want to use in MarkDowns. Maybe also in the
# text of the script. Makes sense.
elif fit == "fit_width" and load2.get_width() > width:
factor = width / load2.get_width()
dx = 0
dy = 0
imagesurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(load2.get_height()*factor))
imagedraw = cairo.Context(imagesurface)
imagedraw.scale(factor, factor)
elif fit == "fit_width":
dx = 0
dy = 0
imagesurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(load2.get_width()), int(load2.get_height()))
imagedraw = cairo.Context(imagesurface)
# Let's make an ability for theme makers to simply color the standard
# icons into any shade.
imagedraw.set_source_surface(load2, dx, dy)
imagedraw.paint()
else:
imagesurface = load2
# Saving it into the win.images
if tmppath:
path = tmppath
win.images[cell][path]["loading"] = False
win.images[cell][path]["image"] = imagesurface
win.images[cell][path]["hash"] = hash_file(path)
win.imageload -= 1
def reload_images(win):
# This function will force the images to refrash. For things like sync
# or simply where a file is changed. Or for any other reason. Now we
# probably don't need to do this on every image. So instead we are going
# to compare the hashes of the current images to the ones of the files
# and if they changed. We are going to update those.
for cell in list(win.images.keys()):
for image in list(win.images[cell].keys()):
if hash_file(image) != win.images[cell][image]["hash"]:
win.images[cell][image]["loading"] = True
pass
def image(layer, win ,path, x, y, width=0, height=0, fit="crop", cell=0, offset=[0,0]):
# This module will handle drawing images to the layers. It's not that hard
# to do in cairo by default. But i'm doing it at every frame. And so it
# means a system of images should exist to take out the load. Basically
# it will make sure the images is loaded only ones. And for the next draw
# calls it will forward the old image.
# Attempt of optimization. Keeping in mind the nature of the programm. Basi-
# cally I will not load image unless x and y are in frame. Kind a ugly. I
# know. But I don't want to even bother checking the resolution of the image
# if it's not in a frame. Roughly speaking. Will see maybe I will make do
# something to make it better.
if cell not in win.images:
win.images[cell] = {}
if int(x) not in range(int(0-width ), int(win.current["w"])) or \
int(y) not in range(int(0-height-win.current["h"]), int(win.current["h"])) :
return
# If you ran this software you probably noticed that images are loading
# dynamically. I did it using this following crazy algorythm borowed from
# the old organizer.
# Here I want to load images. But I also want to give myself an ability to
# load refrash images. Without refrashing all the images. For this I'm going
# to use hashes. Specificly MD5 but it doesn't matter really. The idea is
# that on a special redraw call. We are going to check hashes storred with
# those from the file. And only if a file really changed. Then update the image.
# First the icon might not exist in the icon pack. Since I want to make
# packs using
if win.settings["Theme"] in path and not os.path.exists(path):
path = path.replace(win.settings["Theme"], "Default")
if path not in win.images[cell] or win.images[cell][path]["loading"]:
# If this is the first time this image is draw we want to create it a data
# structure. So we could load the image.
win.images[cell][path] = {
"loading": True, # Whether the image is currently loading.
"image" : None, # The image data it self.
"hash" : "" # The hash of the image file.
}
# Then we want to insure to load only a given amount of images in the same
# time. For this we are going to use the next variable.
MAXLOAD = 10
if win.imageload < MAXLOAD:
win.imageload += 1
# Now we want to load the image. But we are going to do that in a separate thread
# not to freeze the UI while the image is loading.
# See: loadimage() function for the continuation of this.
t = threading.Thread(target=loadimage, args=(layer, win ,path, x, y, width, height, fit, cell))
t.start()
#loading it back
else:
if win.images[cell][path]["image"] and win.images[cell][path]["image"] != "download_button":
imagesurface = win.images[cell][path]["image"]
# Writting the image to the screen
try:
if "icons" in win.color and "settings/themes/" in path:
UI_color.set(layer, win, "icons")
layer.rectangle(x,y,0,0)
layer.fill()
layer.mask_surface(imagesurface, x, y)
UI_color.set(layer, win, "icons")
layer.fill()
else:
layer.set_source_surface(imagesurface, x, y)
layer.paint()
except:
pass
# And if testing
if win.current["testing"]:
UI_color.set(layer, win, "testing_image")
layer.rectangle(x,y,imagesurface.get_width(),imagesurface.get_height())
layer.stroke()
elif win.images[cell][path]["image"] == "download_button":
# If the image is online. By default it will not load it unless the
# user presses a download button. So here it is.
def do():
win.images[cell][path]["image"] = "downloading"
loadimage(layer, win ,path, x, y, width, height, fit, cell)
win.images[cell][path]["loading"] = True
# Some link image will be automatically clicked. This
# fixes the problem.
win.current["LMB"] = False
win.previous["LMB"] = False
if win.settings["auto_download_images"]:
t = threading.Thread(target=do)
t.start()
else:
roundrect(layer, win, x,y,40,40,10,
icon="image_link",
button=do,
offset=offset,
tip=path)
def tooltip(win, text):
layer = win.tooltip
# This function draws a tooltip helper window.
# Just in case
text = str(text)
# Let's get dimantions of the cube first.
lines = 0
maxletters = 0
for line in text.split("\n"):
lines += 1
if len(line) > maxletters:
maxletters = len(line)
# Now when we now the mount of lines and the max lenght of a line. We can
# start actually drawing something.
# Let's try to make so it's not out of the frame.
sx = win.current["mx"]
sy = win.current["my"]
if sx+(maxletters*9)+40 > win.current["w"]:
sx = win.current["w"] - ((maxletters*9)+40)
if sy+(lines*20)+10 > win.current["h"]:
sy = win.current["h"] - ((lines*20)+10)
# Rectangle
UI_color.set(layer, win, "node_background")
roundrect(layer, win,
sx,
sy,
(maxletters*9)+40,
(lines*20)+10,
10)
# Text it self
layer.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL,
cairo.FONT_WEIGHT_NORMAL)
layer.set_font_size(15)
UI_color.set(layer, win, "text_normal")
for num, line in enumerate(text.split("\n")):
layer.move_to(sx+20,
sy+20+(20*num) )
layer.show_text(line)
def scroll_area(layer, win, name, x, y, width, height, maxlength,
bar=False,sideways=False, mmb=False, mmb_only=False,
url="", strenght=50, bar_always=False):
# This function going to handle all the scrolling windows. Making it so it's
# relativelly simple to set up big widgets with in small areas.
# It will handle only the value of the scroll stored in win.scroll[name]
if maxlength == 0:
maxlength = 1
x = int(x)
y = int(y)
width = int(width)
height = int(height)
# First let's set one up if it's not setup
if name not in win.scroll:
win.scroll[name] = 0
return
# Getting scroll amount based on all kinds of settings. AT THIS MOMENT
# IT'S IN AN ALPHA BECAUSE I'M LAZY. I GONNA IMPLEMENT THING AS I NEED THEM
# Or you can do that. IDK...
amount = 0.0
if win.current['mx'] in range(x, x+width) \
and win.current['my'] in range(y, y+height) :
if not mmb_only:
amount = win.current["scroll"][1]*strenght
if mmb_only:
mmb = True
# Middle mouse button scroll, or using a graphical tablet.
if mmb:
if win.current["MMB"]:
if not sideways:
amount = 0 - ( win.current["my"] - win.previous["my"] )
else:
amount = 0 - ( win.current["mx"] - win.previous["mx"] )
# I guess I need to separate the logic into a separate minifunction.
# I will use later for the scroll bar thingy. So not to rewrite the code
# Here is a function thingy.
def logic():
if url and url != win.url:
return
# Scroll logic
win.scroll[name] -= amount
if not sideways:
# If too low
if win.scroll[name] < (1-maxlength+height):
win.scroll[name] = (1-maxlength+height)
# If too high
if win.scroll[name] > 0:
win.scroll[name] = 0
else:
# If too low
if win.scroll[name] < (1-maxlength+width):
win.scroll[name] = (1-maxlength+width)
# If too high
if win.scroll[name] > 0:
win.scroll[name] = 0
logic()
# Not BAR. Which is going to be drawn at a side of what ever content there
# Basically a scrollbar. But implemented from scratch. Because I'm crazy.
# and have nothing better to do now.
if bar:
# For now I'm going to implement only vertical bar. I gonna implement
# the horisontal one when the time comes.
if not sideways:
# Fist let's make some math in front. Because things like this
# require ton of maths. And it's good to have some predone.
tobreak = False # Also if we can abort the operation early with it.
fraction = height / maxlength # Similar to progress bar for now
if fraction > 1:
tobreak = True
fraction = 1
# To break parameter basically says. To draw it the bar only when
# it's actully needed. When content aka maxlength is bigger then
# our viewport.
if not tobreak or bar_always:
# Now the offset value. That will move our progress bar with
# the scroll value.
offset = (height-60)*( (1-win.scroll[name]) / maxlength )
# Background bar
UI_color.set(layer, win, "background")
roundrect(layer, win,
(x+width)-20,
y+30,
10,
height-60,
5
)
# Active bar
UI_color.set(layer, win, "button_active")
# Let's save a little bit more math because it's going to be
# vild. And I love it.
Lx = (x+width)-20
LSx = 10
Ly = y+30+offset
LSy = (height-60)*fraction
# Mouse over thingy. To make the bat a tiny bit brighter.
# indicating to the user that it's now could be used.
if win.current['mx'] in range(int(Lx), int(Lx+LSx)) \
and win.current['my'] in range(int(Ly), int(Lx+LSy)) :
UI_color.set(layer, win, "button_clicked")
# Now separatelly we gonna check for if the mouse pressed.
# Remember if it's not pressed it's False. It's written in one
# of the files. Basically I want to be able to move the mouse
# outside the bar while moving. And so it won't cancel the motion.
# It seems like I did something wrong. Anyways it works.
if win.current["LMB"]:
if int(win.current['LMB'][0]) in range(int(Lx), int(Lx+LSx)) \
and int(win.current['LMB'][1]) in range(int(y), int(y+(height-60))) :
UI_color.set(layer, win, "button_clicked")
# A bit more math to set the value back.
amount = ( win.current["my"] - win.previous["my"] ) / \
(height-60) * maxlength
logic() # Yeah. Look a few lines back.
# And only after all of this nonsense we can draw the cube. Or
# should I say roundrect? A button? Aaaaaa....
roundrect(layer, win,
Lx,
Ly,
LSx,
LSy,
5
)
else:
# Fist let's make some math in front. Because things like this
# require ton of maths. And it's good to have some predone.
tobreak = False # Also if we can abort the operation early with it.
fraction = width / maxlength # Similar to progress bar for now
if fraction > 1:
tobreak = True
fraction = 1
# To break parameter basically says. To draw it the bar only when
# it's actully needed. When content aka maxlength is bigger then
# our viewport.
if not tobreak or bar_always:
# Now the offset value. That will move our progress bar with
# the scroll value.
offset = (width-60)*( (1-win.scroll[name]) / maxlength )
# Background bar
UI_color.set(layer, win, "background")
roundrect(layer, win,
x+30,
(y+height)-20,
width-60,
10,
5
)
# Active bar
UI_color.set(layer, win, "button_active")
# Let's save a little bit more math because it's going to be
# vild. And I love it.
Lx = (y+height)-20
LSx = 10
Ly = x+30+offset
LSy = (width-60)*fraction
# Mouse over thingy. To make the bat a tiny bit brighter.
# indicating to the user that it's now could be used.
if win.current['my'] in range(int(Lx), int(Lx+LSx)) \
and win.current['mx'] in range(int(Ly), int(Lx+LSy)) :
UI_color.set(layer, win, "button_clicked")
# Now separatelly we gonna check for if the mouse pressed.
# Remember if it's not pressed it's False. It's written in one
# of the files. Basically I want to be able to move the mouse
# outside the bar while moving. And so it won't cancel the motion.
# It seems like I did something wrong. Anyways it works.
if win.current["LMB"]:
if int(win.current['LMB'][1]) in range(int(Lx), int(Lx+LSx)) \
and int(win.current['LMB'][0]) in range(int(x), int(x+(width-60))) :
UI_color.set(layer, win, "button_clicked")
# A bit more math to set the value back.
amount = ( win.current["mx"] - win.previous["mx"] ) / \
(width-60) * maxlength
logic() # Yeah. Look a few lines back.
# And only after all of this nonsense we can draw the cube. Or
# should I say roundrect? A button? Aaaaaa....
roundrect(layer, win,
Ly,
Lx,
LSy,
LSx,
5
)
def text(outlayer, win, name, x, y, width, height, set_text="", parse=False, fill=True,
editable=True, multiline=False , linebreak=False, centered=False, tip="",
offset=[0,0]):
# This function will handle all the text writting in the software.
# I'm not sure about how parsing going to work for script files later.
# But if it's currently works, means that I already implemented it into
# the program.
# Making the layer
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(height))
layer = cairo.Context(surface)
layer.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
layer.set_font_size(20)
# Some challenges that it will have is how to correctly store data about
# the text in the system. I think we can use the win.text variable to store
# directories of the data.
if name not in win.text:
# I need to get something done before I can pu scroll in.
scrollname = name
while scrollname in win.scroll:
scrollname = scrollname+"_text"
win.text[name] = {
"text" :set_text, # Actuall text you are editing.
"cursor":[len(str(set_text)),len(str(set_text))], # Cursor
"insert":False, # Whether the insert mode is on
"scroll":scrollname # If multiline. The pointer for the scroll value.
}
# Background
if fill:
UI_color.set(layer, win, "darker_parts")
roundrect(layer, win,
0,
0,
width,
height,
10)
layer.fill()
# Now after filling it up. I want to clip everything. SO no text will get
# out of a given area.
roundrect(layer, win,
0,
0,
width,
height,
10,
fill=False)
layer.clip()
# Now I want to give a preview of the text it self. BUT. I need to be sure
# that if the text longer then a given width and there is no multiline or a
# linebreak. Then it scrolls sideways to the cursor.
# Automatic scroll system: Based on the position of the cursor.
offsetX = 0
cursor2location = win.text[name]["cursor"][1]*12 + offsetX
while cursor2location > width - 5:
offsetX -= 1
cursor2location = win.text[name]["cursor"][1]*12 + offsetX
# Text selection. AKA cursor
# So here we draw the cursor
if editable:
UI_color.set(layer, win, "node_blendfile")
if win.text[name]["cursor"][0] == win.text[name]["cursor"][1]:
if win.blink:
layer.rectangle(
win.text[name]["cursor"][0]*12+5 +offsetX,
5,
(win.text[name]["cursor"][1]*12)-(win.text[name]["cursor"][0]*12)+2,
30
)
else:
roundrect(layer, win,
win.text[name]["cursor"][0]*12+5 +offsetX,
5,
(win.text[name]["cursor"][1]*12)-(win.text[name]["cursor"][0]*12)+2,
30,
5,
fill=False
)
if win.textactive == name:
layer.fill()
elif win.text[name]["cursor"][0] != win.text[name]["cursor"][1]:
layer.stroke()
# Making sure that cursor is correct. Because a lot of bugs are happening
# with it and it's not cool.
# Mouse select
if win.current["LMB"]:
if int(win.current["mx"]-offset[0]) in range(int(x), int(x+width))\
and int(win.current["my"]-offset[1]) in range(int(y), int(y+height)):
win.text[name]["cursor"][0] = int((win.current["LMB"][0]-x-offsetX-offset[0])/12)
win.text[name]["cursor"][1] = int((win.current["mx"]-x-offsetX-offset[0])/12)
# If second part of selection ends up bigger then the first. Reverse them.
if win.text[name]["cursor"][0] > win.text[name]["cursor"][1]:
win.text[name]["cursor"] = [
win.text[name]["cursor"][1],
win.text[name]["cursor"][0]]
# If any part ends up beyond the text. Clip them in.
if win.text[name]["cursor"][0] < 0:
win.text[name]["cursor"][0] = 0
if win.text[name]["cursor"][1] < 0:
win.text[name]["cursor"][1] = 0
if win.text[name]["cursor"][0] > len(str(win.text[name]["text"])):
win.text[name]["cursor"][0] = len(str(win.text[name]["text"]))
if win.text[name]["cursor"][1] > len(str(win.text[name]["text"])):
win.text[name]["cursor"][1] = len(str(win.text[name]["text"]))
# Drawing the text
UI_color.set(layer, win, "text_normal")
layer.move_to(5+offsetX, height/2+5)
if centered:
layer.move_to(width/2-len(str(win.text[name]["text"]))*12/2, height/2+5)
layer.show_text(str(win.text[name]["text"]))
# Editing the text
if win.current["keys"] and editable and name == win.textactive:
# Let's filter the input first.
# For example
if not multiline: #Removing enter key press
if 65293 in win.current["keys"] or 65421 in win.current["keys"]\
or 65289 in win.current["keys"]: # TAB
win.current["key_letter"] = ""
prevlen = len(win.text[name]["text"])
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
regularclean = True
ORD = 0
try:
ORD = ord(win.current["key_letter"])
except:
pass
backremove = False # Whether to make selection go to 0 width thing
#print(ORD, win.text[name]["cursor"][0])
# Backspace
if 65288 in win.current["keys"]:
if win.text[name]["cursor"][0] != 0 and win.text[name]["cursor"][0]\
== win.text[name]["cursor"][1]:
win.text[name]["text"] = win.text[name]["text"]\
[:win.text[name]["cursor"][0]-1]+\
win.text[name]["text"]\
[win.text[name]["cursor"][1]:]
elif win.text[name]["cursor"][1] != 0 and win.text[name]["cursor"][0]\
!= win.text[name]["cursor"][1]:
win.text[name]["text"] = win.text[name]["text"]\
[:win.text[name]["cursor"][0]]+\
win.text[name]["text"]\
[win.text[name]["cursor"][1]:]
backremove = True
# Ctrl - C
elif ORD == 3:
clipboard.set_text( win.text[name]["text"]\
[win.text[name]["cursor"][0]:win.text[name]["cursor"][1]], -1)
# Ctrl - V
elif ORD == 22:
cliptext = str(clipboard.wait_for_text())
win.text[name]["text"] = win.text[name]["text"]\
[:win.text[name]["cursor"][0]]\
+ cliptext +\
win.text[name]["text"]\
[win.text[name]["cursor"][1]:]
win.text[name]["cursor"][0] = win.text[name]["cursor"][1]
# Ctrl - A
elif ORD == 1:
win.text[name]["cursor"][0] = 0
win.text[name]["cursor"][1] = len(win.text[name]["text"])
# To clear up the Controll
elif 65507 in win.current["keys"]:
pass
# Shift
elif 65506 in win.current["keys"]:
# Right
if 65363 in win.current["keys"]:
win.text[name]["cursor"][1] = win.text[name]["cursor"][1] + 1
#win.current["keys"].remove(65363)
# Left
elif 65361 in win.current["keys"]:
if win.text[name]["cursor"][1] > win.text[name]["cursor"][0]:
win.text[name]["cursor"][1] = win.text[name]["cursor"][1] - 1
#win.current["keys"].remove(65361)
# Right button
elif 65363 in win.current["keys"]:
win.text[name]["cursor"][0] = win.text[name]["cursor"][0] + 1
win.text[name]["cursor"][1] = win.text[name]["cursor"][0]
win.current["keys"].remove(65363)
# Left button
elif 65361 in win.current["keys"]:
win.text[name]["cursor"][0] = win.text[name]["cursor"][0] - 1
win.text[name]["cursor"][1] = win.text[name]["cursor"][0]
win.current["keys"].remove(65361)
# Escape
elif 65307 in win.current["keys"]:
win.textactive = ""
win.current["keys"].remove(65307)
else:
win.text[name]["text"] = win.text[name]["text"]\
[:win.text[name]["cursor"][0]]\
+ win.current["key_letter"]+\
win.text[name]["text"]\
[win.text[name]["cursor"][1]:]
# Auto moving the cursor
nowlen = len(win.text[name]["text"])
if win.text[name]["cursor"][0] == win.text[name]["cursor"][1]:
win.text[name]["cursor"][0] = win.text[name]["cursor"][0] + (nowlen - prevlen)
win.text[name]["cursor"][1] = win.text[name]["cursor"][0]
elif backremove:
win.text[name]["cursor"][1] = win.text[name]["cursor"][0]
if nowlen != prevlen and regularclean:
# Deleting all the keys from the keys. So yeah.
win.current["keys"] = []
# Outputing to the outlayer.
outlayer.set_source_surface(surface, x, y)
outlayer.paint()
# Button if editable.
if editable:
def do():
win.textactive = name
roundrect(outlayer, win,
x,
y,
width,
height,
10,
fill=False,
button=do,
tip=tip,
offset=offset,
text=True)
outlayer.stroke()
if win.textactive == name:
UI_color.set(outlayer, win, "button_active")
roundrect(outlayer, win,
x,
y,
width,
height,
10,
fill=False)
outlayer.stroke()