Blender-Pipeline/studio/studio_gtk.py
Jeison Yehuda Amihud (Blender Dumbass) d067b6c64c Multiuser Alpha 1.0
These are the UI and studio system part of
The implementation
2021-01-02 23:33:34 +00:00

450 lines
16 KiB
Python

# THIS FILE IS A PART OF VCStudio
# PYTHON 3
import os
import datetime
# GTK module ( Graphical interface
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
import cairo
import datetime
import threading
# Own modules
from settings import settings
from settings import talk
from project_manager import pm_project
# Studio
from studio import analytics
from studio import story
from studio import studio_storyDeletionLayer
from studio import studio_storyLayer
from studio import studio_settingsLayer
from studio import studio_assetLayer
from studio import studio_analyticsLayer
from studio import studio_scriptLayer
from studio import studio_multiuserLayer
# UI modules
from UI import UI_testing
from UI import UI_color
from UI import UI_elements
# Network
from network import network_renders
from network import network_multiuser
from network import multiuser_terminal
def previous(win):
win.previous = {}
for i in win.current:
if type(win.current[i]) == list or type(win.current[i]) is dict:
win.previous[i] = win.current[i].copy()
else:
win.previous[i] = win.current[i]
# OK let's make a window
def run(project, win):
# In the Blender-Organizer I was putting the version into the title. Not cool.
# Because if you would snap it to the sidebar in Ubuntu. On mouse over it would
# show the first ever version. So there will be a better way to see version.
# I think let's do that like in Blender. Drawn with in the window somewhere.
try:
win.destroy()
except:
pass
# Setting up the window
win = Gtk.Window()
win.maximize()
win.connect("destroy", Gtk.main_quit)
win.set_title("VCStudio")
win.set_default_icon_from_file("tinyicon.png")
# Setting up the events ( mouse and keyboard handling )
win.connect("button-press-event", mouse_button_press, win)
win.connect("button-release-event", mouse_button_release, win)
win.connect("key-press-event", key_press, win)
win.connect("key-release-event", key_release, win)
# Guess what. The entire time on Blender-Organizer 4 ( 2018 -2020 ) and
# few days of trying connecting the scroll event directly to window or to
# the drawing area. And just now I finally made it work. BY DOING THIS
# Why scroll event is only on ScrolledWindow ? OMG !!!
scroll = Gtk.ScrolledWindow()
scroll.connect("scroll-event", scrolling, win)
# Setting up the global variables. (kinda)
win.animations = {}
win.previous = {}
win.current = {}
win.images = {}
win.imageload = 0
win.text = {}
win.textactive = ""
win.scroll = {}
win.FPS = 0
win.url = "story_editor"
win.cur = "" # This will be used to get precicelly what asset / scene / shot we are in
win.update = {"versions":{}}
win.project = project
win.out_dots = {}
win.assets = {}
win.szone = [[100,100],[100,100]] # Square drawn if selected more then one node.
win.surround = { # And this is the list of the squares. Because it's not
"frame":0, # as easy to do. See UI / UI_math / rectangle_surround()
"rects":[] # for details of this implementation.
}
win.calllayer = False
win.layercashe = {} # Here I gonna store layers that are inactive to speed up stuff
win.checklists = {}
win.blink = False # Cursor blinking thing.
win.renders = {} # List of current active renders.
win.undo_history = []
win.undo_index = 0
win.multiuser = {
"server":False, # Whether we are connected to the server. And the server ID
"userid":"", # The id of current user in the server
"last_request": "", # The last request send to the server. ( for stopping bloat )
"curs":{}, # The information about various items on other users machines.
"request":[["story"]], # Requests done in UI space
"users":{}, # List of users information Names, IPs etc.
"messages":[], # List of messages
"unread":0 , # Amount of unread messages
"terminal":[], # The outputs from the server
"asset_list_check":False, # Whether the initial update happened when connecting to the server
"story_check":False, # Whether the first story check was done.
"analytics_check":False
}
if pm_project.is_legacy(project):
win.story = story.get_legacy(project)
win.analytics = analytics.get_legacy(project)
else:
win.story = story.load(project)
win.analytics = analytics.load(project)
# Try to put a name of the project at the title
win.set_title("VCStudio : "+win.analytics["name"])
# Cashed tables
win.color = UI_color.get_table()
win.settings = settings.load_all()
# Default values
win.current["frame"] = 0
win.current["testing"] = False
win.current["LMB"] = False
win.current["MMB"] = False
win.current["RMB"] = False
win.current["keys"] = []
win.current["key_letter"] = ""
win.current["scroll"] = [0,0]
win.current["project"] = ""
win.current["tool"] = "selection"
win.current["draw_dot"] = "end"
win.current["calls"] = {} # Calls. See sutdio/studio_dialogs.py
win.current["script_find"] = [0,0]
if "pointers" not in win.story:
win.story["pointers"] = {} # List of text pointers per scene
new_date_format = "%Y/%m/%d"
today = datetime.datetime.strftime(datetime.datetime.today(), new_date_format)
win.current["date"] = today # Don't even ask. I'm litteraly tired already.
previous(win)
# Version of the software
win.version = 0.0
try:
vfile = open("settings/update.data")
vfile = vfile.read()
vfile = vfile.split("\n")
for line in vfile:
if line.startswith("VERSION "):
win.version = float(line.replace("VERSION ", ""))
break
except:
win.version = 0.0
# FPS
win.sFPS = datetime.datetime.now()
# Setting the drawable
pmdraw = Gtk.DrawingArea()
pmdraw.set_size_request(1040, 720)
scroll.set_size_request(1040, 720) # This step is because GTK developers are
win.add(scroll) # well. A good, nice, people who knows
scroll.add_with_viewport(pmdraw) # what they are doing. Really.
pmdraw.connect("draw", pmdrawing, win)
# Before we run the UI. Let's run the mutliuser client.
multiuser = threading.Thread(target=network_multiuser.client, args=(win,))
multiuser.setDaemon(True)
multiuser.start()
# And let's start also the multiuser_terminal
multiuser_term = threading.Thread(target=multiuser_terminal.listen, args=(win,))
multiuser_term.setDaemon(True)
multiuser_term.start()
#run
win.show_all()
Gtk.main()
def pmdrawing(pmdrawing, main_layer, win):
# This function draws the actuall image. I'm doing full frames redraws. It's
# a bit simpler then making some kind of dynamic draw call system that might
# be used in such an application. But to hell with it. I did the same on the
# Blender-Organizer altho with way less cairo. And it works well enought.
# FPS counter
win.fFPS = datetime.datetime.now()
win.tFPS = win.fFPS - win.sFPS
if win.current["frame"] % 5 == 0:
win.blink = not win.blink # Iterating the blink
if win.current["frame"] % 10 == 0:
win.FPS = int ( 1.0 / ( win.tFPS.microseconds /1000000))
if "Auto_De-Blur" not in win.settings:
win.settings["Auto_De-Blur"] = True
# Fail switch for Graphics.
if win.FPS < 10 and win.settings["Auto_De-Blur"]:
win.settings["Blur"] = False
win.sFPS = datetime.datetime.now()
# Current frame (for animations and things like this)
win.current["frame"] += 1
# Getting data about the frame
win.current['mx'] = win.get_pointer()[0]
win.current['my'] = win.get_pointer()[1]
win.current['w'] = win.get_size()[0]
win.current['h'] = win.get_size()[1]
# Attemt to make things straight when pressing Ctrl.
if 65507 in win.current["keys"] and win.current["LMB"]:
# Let's see what's got more distance. X or Y motion of the mouse.
dx = win.current["LMB"][0] - win.current["mx"]
dx = max(dx, dx*-1)
dy = win.current["LMB"][1] - win.current["my"]
dy = max(dy, dy*-1)
# If X has more ditance. Then Y should be the same as begining.
if dx > dy:
win.current["my"] = win.current["LMB"][1]
else:
win.current["mx"] = win.current["LMB"][0]
# Attempt at making the mouse cursor a bit more usefull for stuff.
if win.previous["LMB"] and len(win.previous["LMB"]) > 2 and win.current["LMB"]:
win.current["LMB"] = win.previous["LMB"]
#Background color
UI_color.set(main_layer, win, "background")
main_layer.rectangle(
0,
0,
win.current['w'],
win.current['h'])
main_layer.fill()
# Tooltips and other junk has to be defined here. And then drawn later to
# the screen. So here we get a special layer. That will be drawn to during
# the time of drawing. And later composeted over everything.
win.tooltip_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, win.current['w'],
win.current['h'])
win.tooltip = cairo.Context(win.tooltip_surface)
win.tooltip.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
# Layers. Order of them matter
Layers = []
if win.url == "story_editor":
Layers.append([studio_storyLayer.layer(win),"story_editor"])
if "story_editor" in win.layercashe:
del win.layercashe["story_editor"]
else:
if "story_editor" not in win.layercashe:
win.layercashe["story_editor" ] = studio_storyLayer.layer(win)
Layers.append([win.layercashe["story_editor" ],"story_editor"])
if win.url == "story_deletion_dialog":
Layers.append([studio_storyDeletionLayer.layer(win),"story_deletion_dialog"])
elif win.url == "settings_layer":
Layers.append([studio_settingsLayer.layer(win),"settings_layer"])
elif win.url == "analytics":
Layers.append([studio_analyticsLayer.layer(win),"analytics"])
elif win.url == "assets":
Layers.append([studio_assetLayer.layer(win),"assets"])
elif win.url == "script":
Layers.append([studio_scriptLayer.layer(win), "script"])
elif win.url == "multiuser":
Layers.append([studio_multiuserLayer.layer(win), "multiuser"])
# Call layers. See studio/studio_dialogs.py for explanation. It's wild.
win.calllayer = False
remlater = []
for call in list(win.current["calls"].keys()):
if win.current["calls"][call]["var"] == None:
Layers.append([win.current["calls"][call]["draw"](win, call)])
win.url = win.current["calls"][call]["url"]
win.calllayer = True
else:
win.url = win.current["calls"][call]["back"]
win.current["calls"][call]["call"](win, win.current["calls"][call]["var"])
win.textactive = ""
remlater.append(call)
for call in remlater:
del win.current["calls"][call]
Layers.append([UI_testing.layer(win)])
Layers.append([win.tooltip_surface])
# Combining layers
for layer in Layers:
if len(layer) > 1:
layer, url = layer
blur = UI_elements.animate(url+"_blur", win, 50)
if (win.url != url or win.calllayer):
if win.current["tool"] not in ["schedule", "grab"]:
blur = UI_elements.animate(url+"_blur", win, blur, 50, 2, True)
else:
blur = UI_elements.animate(url+"_blur", win, 50, 50, 0, True)
else:
if win.current["tool"] not in ["schedule", "grab"]:
blur = UI_elements.animate(url+"_blur", win, blur, 0, 2, True)
else:
blur = UI_elements.animate(url+"_blur", win, 0, 0, 0, True)
layer = UI_elements.blur(layer, win, blur)
else:
layer = layer[0]
main_layer.set_source_surface(layer, 0 , 0)
main_layer.paint()
# If you press ESC you get back from any window to the main menu.
if 65307 in win.current["keys"] and win.url != "install_updates":
win.url = "story_editor"
win.story["selected"] = []
win.current["tool"] = "selection"
win.current["keys"] = []
win.textactive = ""
# For the rendering I will need to read render info at each frame and parse
# it in various windows.
network_renders.read_renders(win)
# There is a but in the Gnome I guess. That autopresses the Shift and Ctrl
# keys when you scroll to different windows. So here is a little fix.
if not win.is_active():
win.current["keys"] = []
elif "is_active" in win.previous and not win.previous["is_active"]:
win.multiuser["last_request"] = ""
UI_elements.reload_images(win)
win.current["is_active"] = win.is_active()
# Saving data about this frame for the next one. A bit hard to get WTF am I
# doing here. Basically trying to avoid current and previous data to be links
# of the same data.
previous(win) # Moved it into a seprate function for obvoius reasons
# Refreshing those that need to be refrashed
win.current["scroll"] = [0,0]
# Refreshing the frame automatically
pmdrawing.queue_draw()
# This program will have things like mouse and keyboard input. And this setup
# Will be done in both PM and the actuall Project window. ( Also in the render
# Window. Basically all separate windows will have to have this setup separatelly.
# Mouse
def mouse_button_press(widget, event, win):
# This function marks activation of the button. Not it's deactivation.
# I'm going to attempt something quite disturbing. Basically I want to save
# the state of the mouse as the press begun untill it's released. And I'm
# going to do in a slightly weird way. Because I'm bored I guess. The prob-
# lem is that it will require to check whether the data even exists in the
# first place. If x. Before parsing it. Because it might be False.
for i, button in enumerate(["LMB", "MMB", "RMB"]):
if i+1 == int(event.get_button()[1]):
win.current[button] = [event.x, event.y]
# If you folowed the code. By checking for example if win.current["LMB"]
# You can know if it's even pressed to begin with. Because if it's not
# It's False.
def mouse_button_release(widget, event, win):
# This function reverses the effects of the mouse_button_press() function.
for i, button in enumerate(["LMB", "MMB", "RMB"]):
if i+1 == int(event.get_button()[1]):
win.current[button] = False
# I guess it's time to make something similar for the keyboard keys as well.
# I'm going to reuse the old system from the Blender-Organizer. Just a list of
# pressed keys. Maybe as well a strting thingy. Because I want to type in this
# app.
def key_press(widget, event, win):
if event.keyval not in win.current["keys"]:
win.current["keys"].append(event.keyval)
win.current["key_letter"] = event.string
def key_release(widget, event, win):
try:
win.current["keys"].remove(event.keyval)
except:
win.current["keys"] = []
# I also want to clean the key letter. Because other wise in the
# script writer I had weird key presses all the time.
if not win.current["keys"]:
win.current["key_letter"] = ""
def scrolling(widget, event, win):
e, x, y = event.get_scroll_deltas()
win.current["scroll"] = [x,y]