956 lines
36 KiB
Python
956 lines
36 KiB
Python
# THIS FILE IS A PART OF VCStudio
|
|
# PYTHON 3
|
|
|
|
# This a console project manager.
|
|
|
|
import os
|
|
import math
|
|
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]):
|
|
|
|
# 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:
|
|
# 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"]:
|
|
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
|
|
|
|
# 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 image(layer, win ,path, x, y, width=0, height=0, fit="crop", cell=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), 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. Basically for each image I see it's any image is loading
|
|
# at the moment. If yes, wait. If not loading. Meaning it can take the turn
|
|
# I create a thread using GLib. Because I'm lazy. Which is loading the image.
|
|
# Basically the UI keeps working before all images are loaded.
|
|
|
|
if path not in win.images[cell] or win.images[cell][path] == "LOADING-IMAGE":
|
|
|
|
win.images[cell][path] = "LOADING-IMAGE"
|
|
|
|
if win.imageload < 1: # This needs to be fixed. Because multithreading
|
|
win.imageload += 1 # gives tons of errors when loading single images.
|
|
def loadimage(layer, win ,path, x, y, width, height, fit):
|
|
|
|
# Loading the image into the cairo.
|
|
try:
|
|
# It could be not PNG
|
|
try:
|
|
loadimage = cairo.ImageSurface.create_from_png(path)
|
|
except:
|
|
# If it's not png it's gonna take few steps
|
|
|
|
# Let's first of all see if it's a blend file.
|
|
|
|
part = path.replace("/", "_").replace(" ", "_")
|
|
|
|
if path.endswith(".blend") or path.endswith(".blend1"):
|
|
|
|
IF = os.system("python3 "+os.getcwd()+"/UI/blender-thumbnailer.py "\
|
|
+path+" /tmp/vcstudio_blender_thumbnail"+part+".png")
|
|
if not IF:
|
|
load1 = GdkPixbuf.Pixbuf.new_from_file("/tmp/vcstudio_blender_thumbnail"+part+".png")
|
|
else:
|
|
load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/"\
|
|
+win.settings["Theme"]+"/icons/blender.png")
|
|
|
|
os.remove("/tmp/vcstudio_blender_thumbnail"+part+".png")
|
|
|
|
|
|
else:
|
|
|
|
# if video
|
|
video = False
|
|
for f in fileformats.videos:
|
|
if path.endswith(f):
|
|
video = True
|
|
|
|
if video:
|
|
IF = os.system("totem-video-thumbnailer -s "+str(width)+" "+path\
|
|
+" /tmp/vcstudio_video_thumbnail"+part+".png")
|
|
if not IF:
|
|
load1 = GdkPixbuf.Pixbuf.new_from_file("/tmp/vcstudio_video_thumbnail"+part+".png")
|
|
else:
|
|
load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/"\
|
|
+win.settings["Theme"]+"/icons/video.png")
|
|
os.remove("/tmp/vcstudio_video_thumbnail"+part+".png")
|
|
else:
|
|
# if image
|
|
image = False
|
|
for f in fileformats.images:
|
|
if path.endswith(f):
|
|
image = True
|
|
|
|
|
|
if image:
|
|
try:
|
|
load1 = GdkPixbuf.Pixbuf.new_from_file(path)
|
|
except:
|
|
load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/"\
|
|
+win.settings["Theme"]+"/icons/image.png")
|
|
|
|
else:
|
|
|
|
if path.endswith(".progress"):
|
|
load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/"\
|
|
+win.settings["Theme"]+"/icons/checklist.png")
|
|
else:
|
|
load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/"\
|
|
+win.settings["Theme"]+"/icons/file.png")
|
|
|
|
# First we need to make a pixbuf.
|
|
|
|
Px = load1.get_width()
|
|
Py = load1.get_height()
|
|
|
|
# Then to convert the pixbuf to a cairo surface
|
|
loadimage = cairo.ImageSurface(cairo.FORMAT_ARGB32, Px, Py)
|
|
imagedraw = cairo.Context(loadimage)
|
|
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 > loadimage.get_height()\
|
|
or width > loadimage.get_width():
|
|
|
|
dx = (width/2) -(loadimage.get_width() /2)
|
|
dy = (height/2)-(loadimage.get_height()/2)
|
|
|
|
else:
|
|
factor = 1
|
|
if (loadimage.get_height()*(width/loadimage.get_width()))\
|
|
< height:
|
|
factor = height / loadimage.get_height()
|
|
else:
|
|
factor = width / loadimage.get_width()
|
|
#factor = 0.1
|
|
imagedraw.scale(factor, factor)
|
|
dx = (width/2)/factor -(loadimage.get_width() /2)
|
|
dy = (height/2)/factor -(loadimage.get_height()/2)
|
|
|
|
|
|
|
|
imagedraw.set_source_surface(loadimage, dx, dy)
|
|
imagedraw.paint()
|
|
|
|
|
|
else:
|
|
imagesurface = loadimage
|
|
except Exception as e:
|
|
print(e)
|
|
imagesurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10)
|
|
|
|
# Saving it into the win.images
|
|
win.images[cell][path] = imagesurface
|
|
win.imageload -= 1
|
|
|
|
#GLib.timeout_add(1, loadimage, layer, win ,path, x, y, width, height, fit)
|
|
t = threading.Thread(target=loadimage, args=(layer, win ,path, x, y, width, height, fit))
|
|
t.start()
|
|
|
|
#loading it back
|
|
else:
|
|
if win.images[cell][path] != "LOADING-IMAGE":
|
|
imagesurface = win.images[cell][path]
|
|
|
|
# Writting the image to the screen
|
|
layer.set_source_surface(imagesurface, x, y)
|
|
layer.paint()
|
|
|
|
# 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()
|
|
|
|
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)
|
|
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()
|
|
|