# 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 from gi.repository import Gdk 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 from troubleshooter import error_notify # 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.set_default_size(1200,720) win.set_position(Gtk.WindowPosition.CENTER) #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] win.current["camera_arrived"] = False 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. try: # 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] win.cursors = { "arrow":Gdk.Cursor.new(Gdk.CursorType.ARROW), "watch":Gdk.Cursor.new(Gdk.CursorType.WATCH), "text" :Gdk.Cursor.new(Gdk.CursorType.XTERM), "hand" :Gdk.Cursor.new(Gdk.CursorType.HAND1), "cross":Gdk.Cursor.new(Gdk.CursorType.CROSS) } win.current["cursor"] = win.cursors["arrow"] # 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() win.get_root_window().set_cursor(win.current["cursor"]) # 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() except: Gtk.main_quit() error_notify.show() # 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]