# THIS FILE IS A PART OF VCStudio # PYTHON 3 import os import datetime import re import json # GTK module ( Graphical interface import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from gi.repository import GLib from gi.repository import Gdk import cairo # Own modules from settings import settings from settings import talk from settings import fileformats from settings import oscalls from project_manager import pm_project #UI modules from UI import UI_elements from UI import UI_color from UI import UI_math # story from studio import story from studio import checklist from studio import analytics from studio import studio_dialogs from studio import schedule from studio import history def layer(win): # Making the layer surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, win.current['w'], win.current['h']) layer = cairo.Context(surface) #text setting layer.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) UI_color.set(layer, win, "dark_overdrop") layer.rectangle( 0, 0, win.current["w"], win.current["h"], ) layer.fill() ############################################################################ # This is a file that will draw the text editor of VCStudio. The idea is that # you not only work on making the movie in VCStudio, but the script to the # movie is also written in VCStudio. This was already implemented in Blender- # Organizer legacy. And already implemented partially in VCStudio. As there # is story editor in place where you can add empty scenes. Or deal with # scenes that are converted from legacy. # But as you may guess by the time I'm writting it there is no way of editing # those scene's contents. Or read them in full. ( You can see peaces in the # assets ) # The implementation of the editor will be different from Blender-Organizer's # where I used an off the shelf GTK text writting widget. # The problem was that I could not really do much with that widget appart from # text. Don't get me wrong it was good for text. But I could not make for # example custom ways of drawing particular parts. Like what if I want a # round-rectangle arround each frase that the crachater's say. Or what if I # want to draw some custom UI buttons somewhere in the text. Stuff like this. # Which resulted in a weird workflow. I had a pretty script preview thing. # and another non-pretty script preview thing. And a script writer thing where # you type in code to mark thing. Very non pretty. Non user-friendly. And # you had to learn the code. And stuff. Yeah... # So here I want to make more of traditional editor. Where you see what you are # editing as you editing it. Hopefully it's going to work. ############################################################################ ####### TOP PANEL ######### UI_color.set(layer, win, "node_background") UI_elements.roundrect(layer, win, win.current["w"]/4, 10, win.current["w"]/2, 50, 10) # Let's add the toolbar. There is not a lot of tools we need. Since it's # a program only for writting a script. # Mark Shot # Mark Asset # Start Dialogue # Link Image # MARK SHOT def do(): win.current["key_letter"] = chr(19) UI_elements.roundrect(layer, win, win.current["w"]/4+5, 15, 40, 40, 10, do, "shot_new", talk.text("add_shot_tooltip")+"\n\n[Ctrl - S]") # MARK ASSET def do(): win.current["key_letter"] = chr(12) UI_elements.roundrect(layer, win, win.current["w"]/4+55, 15, 40, 40, 10, do, "obj_link", talk.text("add_asset_tooltip")+"\n\n[Ctrl - L]") # Do DIALOGUE def do(): # Later on I'm doing the Ctrl - D combination to add a frase part # and this gives me a character of 00000011 or in other words a 4. # So i guess making it thing that I pressed Ctrl-D is fine. win.current["key_letter"] = chr(4) UI_elements.roundrect(layer, win, win.current["w"]/4+105, 15, 40, 40, 10, do, "frase_new", talk.text("add_phrase_tooltip")+"\n\n[Ctrl - D]") # ADD IMAGE def do(): win.current["key_letter"] = chr(9) UI_elements.roundrect(layer, win, win.current["w"]/4+155, 15, 40, 40, 10, do, "image_link", talk.text("add_image_script_tooltip")+"\n\n[Ctrl - I]") # Next part is to add the scene name editor. It's not as easy as you might # thing. Since every scene's shot might contain an actuall folder on the # operating system after you create a first blend file to animate the shot. # So there will be a folder for the scene with many folders for shots inside. # And if you change the name of the scene in the story you probably want to # change the name of the scene in the folder as well. BUT. Since there could # be countless blend files inside. And some names just could be already taken # and stuff like that, I will block the editing of the scene if a folder exists. # And will give a folder icon. # Technically it's going to be possible to rename the scene. By renaming the # folder first. But this is a kind of breaking everything operation i don't # want to implement inside the VCStudio it self. I give constant folder links # so editing of files could be done by the user at any time. # But I don't want it to feel easy. Since stuff like deleting assets or # editing names could break things in a different place in the program. That's # why I don't provide easy to use functions for it. The user should really # decide to make a change like this. To the point of breaking the convinience # of VCStudio. # Parsing the url if win.cur.count("/") > 1: tmp = win.cur.replace("/","",1).split("/") scene, shot = tmp[0], tmp[1] else: scene = win.cur[win.cur.rfind("/")+1:] shot = "" # Let's find out if a folder exists. if os.path.exists(win.project+"/rnd/"+scene): editable = False minus = 50 # The minus is the width of the folder button def do(): oscalls.Open(win.project+"/rnd/"+scene) UI_elements.roundrect(layer, win, win.current["w"]/4*3-45, 15, 40, 40, 10, do, "folder", tip="/rnd/"+scene) else: editable = True minus = 0 UI_elements.text(layer, win, "scene_name", win.current["w"]/4+205, 15, win.current["w"]/2-210-minus, 40, set_text=scene, editable=editable, fill=False) # Now let's do the actual edititing part. What we want to look for is whether # the key already exists in the scenes, if not allow it to be applied. Now # you are probably asking yourself. But the current scene exists there? So # how could you apply it. Easy. If you change anything. It already doesn't # exist there. And if you didn't change anything. What's the point of applying # anyway? So here what we are going to do. newname = win.text["scene_name"]["text"].replace("/","_").replace(" ", "_")\ .replace('"',"_").replace("(","_").replace(")","_").replace("'","_")\ .replace("[","_").replace("]","_").replace("{","_").replace("}","_") if newname not in win.story["scenes"]: def do(): win.story["scenes"][newname] = win.story["scenes"].pop(scene) win.text["scene_name"]["text"] = newname win.cur = newname # There is one more rub. Scenes could be connected together. # so now we need to preserve the connections as well. This is # not that hard. for arrow in win.story["arrows"]: if arrow[0][1] == scene: arrow[0][1] = newname if arrow[1][1] == scene: arrow[1][1] = newname # Easy. I like how if it's a list. it's a link to the list. # so there will not be memory overdrives. So I can itterate # over a list of lists and assing values of to the itteration # and not trying to get the main list. LOVELY. # Saving to the file story.save(win.project, win.story) UI_elements.roundrect(layer, win, win.current["w"]/4*3-45, 15, 40, 40, 10, do, "ok", tip=talk.text("checked")) # Just in case parsing the url again if win.cur.count("/") > 1: tmp = win.cur.replace("/","",1).split("/") scene, shot = tmp[0], tmp[1] else: scene = win.cur[win.cur.rfind("/")+1:] shot = "" ######### MAIN PART ########### # Oh dear. I have no idea how hard it's going to be. But let's try. I don't # thing it's going to be that insane. But I need to take in consideration # a couple of things. Mainly text warping. Meaning I'm probably rendering # each word as an individual object. Now this creates a challenge. # How do I mark parts of the text having more then 1 word it it? I probably # will need to draw a couple layers at ones. # TEXT LAYER # SHOTS MARKING LAYER # ASSET MARKING LAYER # And then combine them in a different order # SHOT MARKING LAYER # ASSET MARKING LAYER (on top) # TEXT LAYER (on top of everything) # So let's make those 3 layers. x = win.current["w"]/4 y = 70 width = win.current["w"]/2 -30 height = win.current["h"]-130 # Good that at least all layers have the same size. textsurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(height)) text = cairo.Context(textsurface) text.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) shotsurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(height)) shots = cairo.Context(shotsurface) shots.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) assetsurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(height)) assets = cairo.Context(assetsurface) assets.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) frasesurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(height)) frases = cairo.Context(frasesurface) frases.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ########################### # Oh dear # Clips: UI_elements.roundrect(text, win, 0,0,width, height, 10,fill=False) text.clip() UI_elements.roundrect(shots, win, 0,0,width, height, 10,fill=False) shots.clip() UI_elements.roundrect(assets, win, 0,0,width, height, 10,fill=False) assets.clip() UI_elements.roundrect(frases, win, 0,0,width, height, 10,fill=False) frases.clip() # Background UI_color.set(frases, win, "node_background") frases.rectangle(0,0,width, height) frases.fill() # Scroll stuff if "script" not in win.scroll: win.scroll["script"] = 0 tileX = 12 current_Y = 0 # For highlights of assets and shots. Basically when mouse is over a given # asset it will record itself into the higlight. And be read on the next # frame by those above it which are links to the same asset / shot. # This is done like so, so I could make multiline drawing of markings. if "script_asset_higlight" not in win.current: win.current["script_asset_higlight"] = "" if "script_shot_higlight" not in win.current: win.current["script_shot_higlight"] = "" # Next. For editing the text we need a cursor on the screen. A kind of pointer # that will be our selection. The only problem is that we are trying to make # usefull text editor where you can select a part of text. And do things to it # like let's say mark a shot or an asset. This is why there are actually 2 # points which are souce of quite heavy head auks later on. pointer = [0,0] # The editing cursor point = 0 # The current place in the text of the scene. if scene in win.story["pointers"]: # Also I thought sinec we are pointer = win.story["pointers"][scene] # saving selection in story # editor. Why not save also # The text pointers. # Coming back to the previous thing. If I mouse over the part the part get's # highlighted. But simply removing the mouse is not enough. Because I'm # writting that it's higlighted into a global dictionary. So I want to clean. # it ones in a while. And for now ( It does cause some visual noise ) I chose # to remove the selection if the mouse cursor moved over 1 pixel from it's # previous frame. So if you move the cursor very slowly, there will be no # change. if int(win.current["mx"]) not in range(int(win.previous["mx"])-1, int(win.previous["mx"])+1)\ and int(win.current["my"]) not in range(int(win.previous["my"])-1, int(win.previous["my"])+1): win.current["script_asset_higlight"] = "" win.current["script_shot_higlight"] = "" # This 2 fellas will be responsible for drawing the shot marking. shotpart = 0 # This is a pixel where to start the rounded rectangle. shotis = "" # this is a current drawing shot's name. # Same idea for the selection cursor. ss = 0 # The pixel where to start drawing the selection cursor. smx = ss # Another one of these for the rectangles under frases. This one is Y location # instead. frasepart = 0 # This is the scroll when typing. putscroll = 0 # Before we start. Let's bullet proof this. So if the scene does not exist # we will be transferred back to story editor and not stuck. try: win.story["scenes"][scene]["shots"] except: win.url = "story_editor" win.cur = "" return surface # Okay if nothing is in the scene we want to create a text block of 1 character # into the scene. Because if it will have 0 characters it will be automatically # deleted. if not win.story["scenes"][scene]["shots"]\ or win.story["scenes"][scene]["shots"][-1][0] == "shot_block": win.story["scenes"][scene]["shots"].append([ "text_block",[["text", ' ']] ]) # This is a list of parts that are inside the selection. I will populate it # as I itterate trough text. The problem with this text is that it's not a # long string. It's a list of blocks. Each of them has a string. But also # each of them has some aditional metadata. Like is it a shot, or not. Is it # a frase. Is it a link to an asset. This kind a thing. So in order to edit it # I need to get all selected peaces into a big thing. selecwords = [] # And a little thing for the left panel. Since we already itterating through. # the shots. Why not get a quick list of them shot_list = {} # Okay so let's begin itterating the scene blocks. There are 2 types of scene # blocks. (At least for now.) Those are shot_block and text_block. Text is a # normal text parts. Shot is everything with in a shot. Shot has one more item # in the list. It's the name of the shot. ['shot_block', 'shot_name', []] # while text has just 2. ['text_block', []]. So you can see me refencing # the block[-1] a lot. To get the text parts of the block. for num, block in enumerate(win.story["scenes"][scene]["shots"]): # If shot block is empty Let's get rig of it, so not to collect garbage in # our story file. Each empty srting is still a string. That requires some # characters to write it to file. So let's get rid of it here. if not block[-1]: del win.story["scenes"][scene]["shots"][num] # Here I want to make another type of filtering. So there will not be # 1 million blocks each 1 character long. I want to make it next. # If the previous block has the same metadata as this one. Join them. if num > 0 and win.story["scenes"][scene]["shots"][num-1][0] == "text_block" and block[0] == "text_block": win.story["scenes"][scene]["shots"][num-1][-1] = win.story["scenes"][scene]["shots"][num-1][-1] + block[-1] del win.story["scenes"][scene]["shots"][num] # THIS ONE IS NOT TESTED YET, BUT I MIGHT ALSO FORGET TO DELETE THE COMMENT. elif num > 0 and win.story["scenes"][scene]["shots"][num-1][0] == "shot_block" and block[0] == "shot_block"\ and win.story["scenes"][scene]["shots"][num-1][1] == block[1]: win.story["scenes"][scene]["shots"][num-1][-1] = win.story["scenes"][scene]["shots"][num-1][-1] + block[-1] del win.story["scenes"][scene]["shots"][num] if block[0] == "shot_block" and block[1] not in shot_list: shot_list[block[1]] = block # Now other windows might want to reference the script. Like assets for # example. So this script_find is for other windows to set. So while in # the script it would automatically scroll to that part. And so if set # only shot. Here is out shot finder, auto-scroller thingy. if win.current["script_find"][0] and win.current["script_find"][0] == num: win.scroll["script"] = 0-current_Y+height/2 win.current["script_find"][0] = 0 # Now let's itterate over parts of the text. Now you probably asking wasn't # blocks just strings with some metadata? Yes and know. Every text block has # blocks with in it. So I could mark items and images and frases with in text # across or without a shot. Basically there is our simple Hello-World thing # written inside a scene data. ['text_block', ['text','Hello-World']] # This are similar. The actual sting is always in the very end. So to find it # I'm using part[-1] for n, part in enumerate(block[-1]): # If you remember me deleting every empty block. Not this is the same # but with empty parts. So you could delete a frase for example. While # just backspacing. if not part[-1]: del block[-1][n] # Here I want to make another type of filtering. So there will not be # 1 million blocks each 1 character long. I want to make it next. # If the previous block has the same metadata as this one. Join them. if n > 0 and block[-1][n-1][0] == "text" and part[0] == "text": block[-1][n-1][-1] = block[-1][n-1][-1]+part[-1] part[-1] = "" # Now this is our search finder auto-scroller again. But if a part # also specified. if win.current["script_find"][1] and win.current["script_find"][1] == n: win.scroll["script"] = 0-current_Y+height/2 win.current["script_find"][1] = 0 # Now t and p. Well t is simply a shortcut to the string it self. Tho # I found it not really usefull since it's not a link to the data. Like # in the case of part[-1]. But for rendering it will be okay. t = part[-1] # Now p is a bit more complicated to explain. Basically I always need # an index number from all character in the whole scene in relation to # this particular part. See i will have a selection across the blocks # and across the parts. And in order to edit them at the right place # I need a reference point of where in the whole, this part starts. p = point # Now xb and mt you could see as margins of the text. Sometimes you # want the whole width to be text. And sometimes. Like in frases you # will want the text closer to the center. xb = 12 # Where is the start of the line mt = width # Where is the maximum width ##### FRASE ##### # Now let's make an example of xb and mt. Here is a frase. So when # it's a frase. You want the text be closer to the center. if part[0] == "frase": # Give it some space. It's a rendering of a frase. You don't # want to be a part of the rest of the text. current_Y = current_Y + 30 # Now frase metadata has another part. Which could be either # text or link. This is the name of our character. The little # part at the top, in the center. If it's a link we want to # draw it accordingly if part[1][0] == "link": # First we draw a little purple rectangle. To show us that # it's a link to an item. UI_color.set(assets, win, "node_asset") UI_elements.roundrect(assets, win, width/2-len(part[1][-1])*6-6, win.scroll["script"] + current_Y+6, len(part[1][-1])*12+12, 28, 10) # Then we make a selection by hovering with the mouse. So if # you click later. You will be transferred to the thing. if int(win.current['mx']) in range(int(x+width/2-len(part[1][-1])*6-6), int(x+width/2-len(part[1][-1])*6-6+len(part[1][-1])*12+12)) \ and int(win.current['my']) in range(int(y+win.scroll["script"] + current_Y+8), int(y+win.scroll["script"] + current_Y+8+22)) : win.current["script_asset_higlight"] = part[1][1] # And if the mouse is over the item's link. No matter if the # link is this one. But if mouse is over any other mention of # the same asset. We want to draw another rectangle. To give # the user a feedback. if part[1][1] == win.current["script_asset_higlight"]: UI_color.set(assets, win, "text_normal") UI_elements.roundrect(assets, win, width/2-len(part[1][-1])*6-6, win.scroll["script"] + current_Y+30, len(part[1][-1])*12+12, 0, 3) # Next we will want to do it make a little button to edit the # name of the character. Because I guess I'm not smart enough # to figure out how to make this a part of the script. And this # implementation is not that terrible. I have a chance to put a # single line entry into a multiline text. This is something. if "current_name_frase_editing" not in win.current: win.current["current_name_frase_editing"] = False win.previous["current_name_frase_editing"] = False # So we put a selection variable. And set up the button. if not win.current["current_name_frase_editing"]: def do(): win.current["LMB"] = False win.previous["LMB"] = False if not win.previous["current_name_frase_editing"]: print(part[1][-1]) win.current["current_name_frase_editing"] = [num, n] if "current_name_frase_editing" in win.text: win.text["current_name_frase_editing"]["text"] = part[1][-1] win.textactive = "current_name_frase_editing" UI_elements.roundrect(frases, win, 0, win.scroll["script"] + current_Y+6, width, 28, 10, button=do, offset=[x,y]) # Now let's draw a little text editor with in a text editor. IK wild. # but since everything is custom I can do wild things. if win.current["current_name_frase_editing"] == [num, n]: UI_elements.text(text, win, "current_name_frase_editing", 200, win.scroll["script"] + current_Y-5, width-400, 40, set_text=part[1][-1], offset=[x,y]) # Here I will add some logic to this little editor with in # editor. OMG WHAT AM I DOING? # First let's make the apply button. And I guess the enter key. def linking(): print("linking") if "linking_asset_operation_metadata" not in win.current: win.current["linking_asset_operation_metadata"] = [] win.current["linking_asset_operation_metadata"] = part def after(win, var): print(var) if var: part = win.current["linking_asset_operation_metadata"] print(part) part[1] = ["link", var, part[1][-1]] studio_dialogs.asset_select(win, "link_asset_script_frase", after, SEARCH=win.text["current_name_frase_editing"]["text"]) UI_elements.roundrect(text, win, width-240, win.scroll["script"] + current_Y-5, 40, 40, 10, button=linking, icon="obj_link", offset=[x,y]) def do(): part[1][-1] = win.text["current_name_frase_editing"]["text"] win.current["current_name_frase_editing"] = False win.current["LMB"] = False win.previous["LMB"] = False win.textactive = False win.current["key_letter"] = "" UI_elements.roundrect(text, win, width-280, win.scroll["script"] + current_Y-5, 40, 40, 10, button=do, icon="ok", offset=[x,y]) # Enter key. if 65293 in win.current["keys"]: do() # If you press Ctrl-Enter instead of Enter. I want it # to apply and also give you automatically to select # the asset to link in the frase header. if 65507 in win.current["keys"]: linking() win.current["keys"] = [] # Canceling the editing. If we don't do that the entry thing # will still be on the screen. And it's not something that # I want to have there. if not win.textactive: win.current["current_name_frase_editing"] = False win.current["key_letter"] = "" else: # And if we are not editing the name currently # let's just draw the name in the center of the # screen. UI_color.set(text, win, "text_normal") text.set_font_size(20) text.move_to( width/2-len(part[1][-1])*6, win.scroll["script"] + current_Y+25, ) text.show_text(part[1][-1]) # Giving it a new line. current_Y = current_Y + 35 # Putting the line into the frasepart so to draw a rectangle # later under the whole thing. frasepart = current_Y # And here we are editing the margins. Because actuall text will # be done in a different place. xb = 212 mt = width - 200 tileX = xb # And passing the current starting position to selection drawing # so it too will start where it should. if ss: ss = xb # Now if we are not in the frase. Let's draw the damn rectangle under # it finally. # THIS IS STILL BUGGY. if frasepart and part[0] != "frase": UI_color.set(frases, win, "dark_overdrop") UI_elements.roundrect(frases, win, 200, win.scroll["script"] + frasepart, width-400, current_Y - frasepart+40, 10) frasepart = 0 # MAKING A NEW LINE smx = tileX tileX = xb if assetpart: assetpart = xb if shotpart: shotpart = xb if ss: UI_color.set(assets, win, "node_blendfile") UI_elements.roundrect(assets, win, ss, win.scroll["script"] + current_Y+5, smx-ss, 26, 10) smx = ss ss = xb current_Y = current_Y + 30 # Now let's draw our images. If they are in the scene. # THIS IS STILL BUGGY if part[0] == "image": if UI_math.line_overlap([pointer[1], pointer[0]],[point, point+1]): if win.current["key_letter"] and not win.textactive: # Let's make it possible to delete images by just # deleting near them. part[-1] = "" current_Y = current_Y + 30 if os.path.exists(win.project+t): UI_elements.image(text, win, win.project+t, 100, win.scroll["script"] + current_Y, int(width)-200, 380, cell="script_images") else: UI_elements.image(text, win, t, 100, win.scroll["script"] + current_Y, int(width)-200, 380, cell="script_images") # Now I guess to simplify the matters let's create a selection # button. Double clicking which will open the file. # So we need a selection dialog. I think I'm going to hack into # my own program right now because I'm lazy to implement anything # normal. This image thing. Will need more love later on. Now it's # thrown together quite in a rush. I would say. def do(): if t != win.textactive: win.textactive = t else: oscalls.file_open(win, t) UI_elements.roundrect(text, win, 100, win.scroll["script"] + current_Y, int(width)-200, 380, 10, button=do, fill=False, offset=[x,y]) text.stroke() # Selection drawing if t == win.textactive: UI_color.set(text, win, "text_normal") UI_elements.roundrect(text, win, 100, win.scroll["script"] + current_Y, int(width)-200, 380, 10, fill=False) text.stroke() # Removing it from the selection using ESC if 65307 in win.current["keys"]: win.textactive = "" win.current["keys"] = [] win.current["key_letter"] = "" # Deleting the image if pressing Delete or Backspace if 65288 in win.current["keys"] or 65535 in win.current["keys"]: win.textactive = "" part[-1] = "" win.current["keys"] = [] win.current["key_letter"] = "" current_Y = current_Y + 400 continue # we don't want to image url in the text it self. # Do you remember I was doing the starting positions of shots and # selection? These 2 are the same concept but to links to assets. assetpart = 0 assetis = "" ############## TEXT IT SELF ############### # Here we go and render the text. Now you can see I iterate over # lines using re.split rather then built in split(). It's because # I need all characters still be present. Since I'm counting every # each character. for line in re.split("(\n)",t): # Now if the line says '\n' (new_line) then I just want to # draw the next line. Note that I'm also drawing the selection # box here. And moving all assetpart, shotpart and ss. So on # the next line those rectangles would start their drawing from # the begining of the line. # Also not the large comment with a POINT in it. It's I'm counting # this character in for to know where I'm editing. if line == "\n": smx = tileX tileX = xb #point = point + 1 #################### <<<<<< POINT if assetpart: assetpart = xb if shotpart: shotpart = xb if ss: UI_color.set(assets, win, "node_blendfile") UI_elements.roundrect(assets, win, ss, win.scroll["script"] + current_Y+5, smx-ss, 26, 10) text.stroke() smx = ss ss = xb current_Y = current_Y + 30 line = " " #continue # And this is since I don't want weird rectangles # on the screen. And just new lines. # Now let's itterate over each and every word. Since we want # text wrapping. So if a certain word goes off screen I want # to declare a next line. NOTE: I'm still using re.split() # becuase we are counting charaters still. for word in re.split("( )", line): # Now let's do the actuall text wrapping. For size 20 which # is what I'm using. With monospace font. Width of each # character is 12 pixels. ( More or less. Good enough for # what we are doing ). So if lenght of word times 12, plus # current place on X is more then the width of our screen, # with all the margins. We want to start a new line. if tileX + len(word)*12 > mt: smx = tileX tileX = xb # And do not forget about all the asset, shot and selection # rendering too. if assetpart: assetpart = xb if shotpart: shotpart = xb if ss: UI_color.set(assets, win, "node_blendfile") UI_elements.roundrect(assets, win, ss, win.scroll["script"] + current_Y+5, smx-ss, 26, 10) text.stroke() ss = xb current_Y = current_Y + 30 # This is logic to draw ASSETS higlights. if part[0] == "link" and part[1] != assetis and not assetpart: assetpart = tileX assetis = part[1] if assetpart: if word: UI_color.set(assets, win, "node_asset") UI_elements.roundrect(assets, win, assetpart-5, win.scroll["script"] + current_Y+8, tileX - assetpart + len(word)*12+12, 22, 10) # Selectiong asset. ( Not actually.) But making a # highlight of the asset. And if clicked. Going # to the asset. if int(win.current['mx']) in range(int(x+assetpart-5), int(x+tileX + len(word)*12+12)) \ and int(win.current['my']) in range(int(y+win.scroll["script"] + current_Y+8), int(y+win.scroll["script"] + current_Y+8+22)) : win.current["script_asset_higlight"] = assetis if assetis == win.current["script_asset_higlight"]: UI_color.set(assets, win, "text_normal") UI_elements.roundrect(assets, win, assetpart-6, win.scroll["script"] + current_Y+26, tileX - assetpart + len(word)*12+12, 0, 3) assetpart = tileX assetis = part[1] if part[0] != "link": assetpart = 0 assetis = "" # And this is logic to draw SHOTS higlights. if block[0] == "shot_block" and block[1] != shotis and not shotpart: shotpart = tileX shotis = block[1] if shotpart: if word: # I want to randomize the shots colors. But I don't it to be an epileptic # show. But not too much. Let's actually write the colors into the story # data. Why not. if "shot_colors" not in win.story: win.story["shot_colors"] = {} surl = "/"+scene+"/"+shotis # Making it scroll to the selected shot in the story itself. # Thankyou very much... if "scroll_shot_to_in_script" not in win.current: win.current["scroll_shot_to_in_script"] = True if surl == win.cur and win.current["scroll_shot_to_in_script"]: win.scroll["script"] = (0 - current_Y) + (win.current["h"]/2) win.current["scroll_shot_to_in_script"] = False if surl not in win.story["shot_colors"]: rcolors = [ "shot_1", "shot_2", "shot_3", "shot_4", "shot_5" ] win.story["shot_colors"][surl] = rcolors[len(win.story["shot_colors"]) % len(rcolors)] col = win.story["shot_colors"][surl] if int(win.current['mx']) in range(int(x+shotpart-5), int(x+tileX + len(word)*12+12)) \ and int(win.current['my']) in range(int(y+win.scroll["script"] + current_Y+8), int(y+win.scroll["script"] + current_Y+8+22)) : win.current["script_shot_higlight"] = shotis UI_color.set(shots, win, col) UI_elements.roundrect(shots, win, shotpart-6, win.scroll["script"] + current_Y+5, tileX - shotpart + len(word)*12+12, 28, 10) # This is a bit of a hack because I was lazy to # figure out how to make shot markings as good # as I made the selection. So drawing a thing around it # would not really work that well. But a line under the # text looks quite alright. I like the look of it. # Maybe I will polish this in future and give people # an option to draw it differently. I don't know yet. if shotis == win.current["script_shot_higlight"] or shot == shotis: UI_color.set(shots, win, "text_normal") UI_elements.roundrect(shots, win, shotpart-6, win.scroll["script"] + current_Y+30, tileX - shotpart + len(word)*12+12, 0, 3) shotpart = tileX shotis = block[1] if block[0] != "shot_block": shotpart = 0 shotis = "" # Now let's draw our word. Simple really. What's so special # about it? Why is this comment 2 lines high? UI_color.set(text, win, "text_normal") #if win.current["script_shot_higlight"] == block[1] or shot == block[1]: # UI_color.set(text, win, "darker_parts") text.set_font_size(20) text.move_to( tileX, win.scroll["script"] + current_Y+25, ) text.show_text(word) # Here I want to make a selection using a mouse. I think it's quite important # since using a Shift key and pressing sideways is not cool # and I want it to be cool. if win.current["LMB"]: if int(win.current["LMB"][1]) in range(int(y+win.scroll["script"] + current_Y), int(y+win.scroll["script"] + current_Y)+25)\ and int(win.current["LMB"][0]-x) in range(tileX, tileX+len(word)*12+12): pointer[0] = int(point - (tileX+len(word)*12 - int(win.current["LMB"][0]-x))/12 + len(word)) if int(win.current["my"]) in range(int(y+win.scroll["script"] + current_Y), int(y+win.scroll["script"] + current_Y)+25)\ and int(win.current["mx"]-x) in range(tileX, tileX+len(word)*12+12): pointer[1] = int(point - (tileX+len(word)*12 - int(win.current["mx"]-x))/12 + len(word)) win.cur = "/"+scene win.textactive = "" if int(win.current["my"]) in range(int(y+win.scroll["script"] + current_Y), int(y+win.scroll["script"] + current_Y)+25)\ and int(win.current["mx"]-x) in range(tileX, tileX+len(word)*12+12): win.current["cursor"] = win.cursors["text"] # This is the logic to draw our selection and logic that # come with the selection. if UI_math.line_overlap([pointer[1], pointer[0]],[point, point+len(word)+1]) and not ss: ss = tileX+(min(pointer[1], pointer[0])-point)*12 smx = ss # If current word is inside the selection. It means that # current text part is also inside the selection. And this # means this part should be edited if I press a button. if UI_math.line_overlap([pointer[1], pointer[0]],[point, point+len(word)+1]): # So we need to add this part and some data about this # part like amount of characters it's away from the # begining of the scene into a list that I could reference # later in the writting logic. if [part, p, num, n] not in selecwords: selecwords.append([part, p, num, n]) # Let's make something for me to understand what is going # on. If I will enter the testing mode. Let it draw the # current number of characters. And the current characrter. if win.current["testing"]: # This is our character. It's not always available. # sometimes you migt be on the edge of something and # something else. So !! will mean seomthing is wrong. try: c = part[-1][pointer[0]-p] except: c = "!!" UI_color.set(text, win, "text_normal") text.set_font_size(10) text.move_to( tileX+(pointer[0]-point)*12-26, win.scroll["script"] + current_Y+35, ) text.show_text(str(pointer[0])+"["+c+"]") # Now doing it again for the second pointer. If you # remeber, We have 2. try: c = part[-1][pointer[1]-p] except: c = "!!" UI_color.set(text, win, "text_normal") text.set_font_size(10) text.move_to( tileX+(pointer[1]-point)*12-26, win.scroll["script"] + current_Y+35, ) text.show_text(str(pointer[1])+"["+c+"]") if max(pointer[1], pointer[0]) in range(point, point+len(word)+1): # This is some more drawing of the selection. This is # if the selection is ending mid way through the line. if ss: if pointer[0] != pointer[1] : UI_color.set(assets, win, "node_blendfile") UI_elements.roundrect(assets, win, max(ss, xb), win.scroll["script"] + current_Y+5, min(tileX+(max(pointer[1], pointer[0])-point)*12-max(ss, xb), mt-max(ss, xb)), 26, 10) elif not win.textactive and win.blink: UI_color.set(text, win, "node_blendfile") text.rectangle( max(ss, xb), win.scroll["script"] + current_Y+5, min(tileX+(max(pointer[1], pointer[0])-point)*12-max(ss, xb), mt-max(ss, xb)), 26) text.stroke() # win.scroll["script"] + current_Y, # min(mt-max(ss,xb), mt-xb), ss = 0 #smx = 0 if win.current["keys"] and not win.textactive and not putscroll: putscroll = min(0 - current_Y + width/2, win.scroll["script"]) # Now in the end of our word. We are counting it's lenght # and moving the tileX so the next word draws after this # one and not on top of this one. I think it's important. point = point + len(word) #################### <<<<<< POINT tileX = tileX + len(word)*12 # Also what you probably want to happen is when you type the script # will scroll down. Yeah. It's not coming by default. Even on standart # GTK text parts. So here is a little thing. if putscroll: win.scroll["script"] = putscroll ############# LOGIC ############### # So I'm doing the logic of typing separatelly from rendering of the text. # because it kind a seems right to do so. I'm still figuring it out at this # point. I have no idea what could go wrong. Maybe I will reddo the whole # thing. Maybe few times. IDK. if 65363 in win.current["keys"]: # RIGHT pointer[0] = pointer[0]+1 if not 65506 in win.current["keys"]: pointer[1] = pointer[0] #win.current["keys"].remove(65363) if 65361 in win.current["keys"]: # LEFT pointer[0] = pointer[0]-1 if not 65506 in win.current["keys"]: pointer[1] = pointer[0] #win.current["keys"].remove(65361) if 65362 in win.current["keys"]: # UP pointer[0] = pointer[0]-min(len(line), int((mt-12)/12)) # STILL BUGGY if not 65506 in win.current["keys"]: pointer[1] = pointer[0] #win.current["keys"].remove(65362) if 65364 in win.current["keys"]: # DOWN pointer[0] = pointer[0]+min(len(line), int((mt-12)/12)) # STILL BUGGY if not 65506 in win.current["keys"]: pointer[1] = pointer[0] #win.current["keys"].remove(65364) # In order for history to work properly I don't want to save history on # each button press. But rather ones every time the scene is edited. So if "scene_edited_already" not in win.current: win.current["scene_edited_already"] = False # I'm going to implement some basic clipboard. It will need love in future. # I just need for now something that will "work" so to speak. See where I # use the clipboard variable later to learn how it all works. clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) # This is a clipboard object # If there is a key press that contains a string. Like anything on a keyboard # where you can actually type. Let's do something with it. def clean(): # This is an empty function. Placeholder for a clean operation after # a given key press. Because we are dealing with multiple blocks of # text that look like 1 block for the user. pass # To make shot I want to write a little list of text parts that will come # into that shot. new_shot_list = [] after_shot_list = [] new_shot = [] if win.current["key_letter"] and not win.textactive: # Here I'm going to implement history if not win.current["scene_edited_already"]: win.current["scene_edited_already"] = True history.record(win, "/rnd"+win.cur, "[Edited]") #print("------{ BEFORE }--------") #print(win.story["scenes"][scene]["shots"]) #print("------{ }------") #print() #print() # For testing for u, stuff in enumerate(selecwords): part, p, num, n = stuff try: block = win.story["scenes"][scene]["shots"][num] except: break try: ORD = ord(win.current["key_letter"]) except: ORD = 0 print(ORD, "ORD") # Output the value of the button. # Now some of the ORDs I found to be weird characters. Like on # CTRL - C or CTRL - V. So I want to filter them out. So there # wont be a bunch of rectangles every time you type. # Now I need the minimum and maximum parts. So there will not be # scips of content I guess. MIN = min(len(part[-1]), max(0, min(pointer[1], pointer[0])-p)) MAX = max(0, min(len(part[-1]), max(pointer[1], pointer[0])-p)) #print(pointer,"POINTER") #print(p, "P") #print(len(part[-1]), "LENGHT", [part[-1]]) #print(MIN, "MIN") #print(MAX, "MAX") #print() # Here I want to record undo history for the script writer. I am going # to ignore the letters. And only forcus on other simbols such as # spacebars and various commands. nonhistory = "1234567890qwertyuiopasdfghjklzxcvbnnmQWERTYUIOPASDFGHJKLZXCVBNM" if win.current["key_letter"] not in nonhistory and ORD not in [26, 25]: story.undo_record(win) # Multiuser sycning win.multiuser["request"] = "story" # Now let's retrive out data. # UNDO if ORD == 26: story.undo(win) # REDO if ORD == 25: story.redo(win) if u == 0: if ORD not in range(32) or ORD in range(127, 160): # Typing logic part[-1] = \ part[-1][:MIN]+\ win.current["key_letter"]+\ part[-1][MAX:] def clean(): pointer[0] = pointer[0] + 1 pointer[1] = pointer[0] point = point + 1 if ORD == 22: # Paste ( Ctrl - V ) cliptext = str(clipboard.wait_for_text()) part[-1] = \ part[-1][:MIN]+\ cliptext+\ part[-1][MAX:] def clean(): pointer[0] = pointer[0] + len(cliptext) pointer[1] = pointer[0] point = point + len(cliptext) if ORD == 3: # Copy ( Ctrl - C ) clipboard.set_text( part[-1][MIN:MAX] , -1) # VERY BASIC # If ord 13 it means you pressed enter. And it should be a new line. # but for some reason it's ord 13. Maybe it is a new line. Who knows # Anyway. It does not return \n but returns a weird rectangle. So # here is a little solution. if ORD == 13: # ENTER # Only I want to use new line to exit from all kind of things. # Like double enter will exit the frase. Ans stuff like this. if part[-1][MIN-1] == "\n" and part[0] != "text": block[-1].insert(n+1, ["text", "\n"] ) block[-1].insert(n+2, ["text", part[-1][MAX:]+" "] ) part[-1] = \ part[-1][:MIN-1] else: part[-1] = \ part[-1][:MIN]+\ "\n"+\ part[-1][MAX:] def clean(): pointer[0] = pointer[0] + 1 pointer[1] = pointer[0] point = point + 1 if ORD == 4: # CTRL D ( FOR SOME REASON) block[-1].insert(n+1, ["frase", ["text", " "], " "] ) block[-1].insert(n+2, ["text", part[-1][MAX:]] ) part[-1] = \ part[-1][:MIN] def clean(): pointer[0] = pointer[0] + 1 pointer[1] = pointer[0] point = point + 1 # Activating the editing of the name automatically if "current_name_frase_editing" in win.text: win.text["current_name_frase_editing"]["text"] = "" if "current_name_frase_editing" not in win.current: win.current["current_name_frase_editing"] = False win.current["current_name_frase_editing"] = [num, n+1] win.textactive = "current_name_frase_editing" if ORD == 8: # BACKSPACE if pointer[1] == pointer[0]: part[-1] = \ part[-1][:MIN-1]+\ part[-1][MAX:] def clean(): pointer[0] = pointer[0] - 1 pointer[1] = pointer[0] point = point - 1 else: part[-1] = \ part[-1][:MIN]+\ part[-1][MAX:] def clean(): pointer[0] = min(pointer[0], pointer[1]) pointer[1] = pointer[0] if ORD == 127: # DELETE (FORWARD) part[-1] = \ part[-1][:MIN]+\ part[-1][MAX+1:] def clean(): pointer[0] = pointer[0] pointer[1] = pointer[0] if ORD == 19: # NEW SHOT ( CTRL - S ) app = part.copy() app[-1] = part[-1][MIN:MAX] new_shot_list.append(app) new_shot = [num , n] after_shot_list = block.copy() after_shot_list[-1] = [] app2 = part.copy() app2[-1] = part[-1][MAX:] after_shot_list[-1].append(app2) part[-1] = part[-1][:MIN] if ORD == 12 and part[0] == "text" and pointer[1] != pointer[0]: # Asset higlight if "linking_asset_operation_metadata" not in win.current: win.current["linking_asset_operation_metadata"] = [] win.current["linking_asset_operation_metadata"] = [part , block, MIN, MAX] def after(win, var): print(var) if var: part , block, MIN, MAX = win.current["linking_asset_operation_metadata"] block[-1].insert(n+1, ["link", var, part[-1][MIN:MAX]] ) block[-1].insert(n+2, ["text", part[-1][MAX:]] ) part[-1] = \ part[-1][:MIN] win.current["key_letter"] = "" studio_dialogs.asset_select(win, "link_asset_script_frase", after, SEARCH=part[-1][MIN:MAX]) if ORD == 9 and part[0] == "text" and pointer[1] == pointer[0]: # Insert image if "linking_asset_operation_metadata" not in win.current: win.current["linking_asset_operation_metadata"] = [] win.current["linking_asset_operation_metadata"] = [part , block, MIN, MAX] def after(win, var): print(var) if var: part , block, MIN, MAX = win.current["linking_asset_operation_metadata"] block[-1].insert(n+1, ["image", var] ) block[-1].insert(n+2, ["text", part[-1][MAX:]] ) part[-1] = \ part[-1][:MIN] win.current["key_letter"] = "" studio_dialogs.file_select(win, "link_asset_script_frase", after, SEARCH=part[-1][MIN:MAX]) # Making stuff happen elif u < len(selecwords)-1: if ORD not in [12, 19, 3, 22, 26, 25]: # 3 = Crtl - C, 22 = Ctrl - V part[-1] = "" if ORD == 19: app = part.copy() new_shot_list.append(app) part[-1] = "" else: MIN = min(len(part[-1])-1, max(0, min(pointer[1], pointer[0])-p)) MAX = max(0, min(len(part[-1])-1, max(pointer[1], pointer[0])-p)) if ORD not in [12, 19, 3, 22, 26, 25]: part[-1] = part[-1][MAX:] if ORD == 19: app = part.copy() app[-1] = part[-1][MIN:] new_shot_list.append(app) part[-1] = "" # Saving to the file story.save(win.project, win.story) win.current["key_letter"] = "" # Let's get all the rest of the parts from that block so I could insert them # as a separate thing later. Because we basically splitting 1 block into 3 # making the selection. Middle one our new shot_block. And the rest. Should # stay the same. if new_shot: num , n = new_shot for l, part in enumerate(win.story["scenes"][scene]["shots"][num][-1]): if l > n: app = part.copy() after_shot_list[-1].append(app) part[-1] = "" if after_shot_list[0] == "shot_block"\ and win.story["scenes"][scene]["shots"][num][0] == "shot_block": after_shot_list[1] = win.story["scenes"][scene]["shots"][num][1] if new_shot_list: #print("NEW: ", new_shot_list) #print("KEEP: ", after_shot_list) nename = "Shot_1" count = 1 while nename in shot_list: count = count + 1 nename = "Shot_"+str(count) num , n = new_shot win.story["scenes"][scene]["shots"].insert(num+1, ["shot_block", nename, new_shot_list]) win.story["scenes"][scene]["shots"].insert(num+2, after_shot_list) # Saving to the file story.save(win.project, win.story) #print() #print("------{ AFTER }--------") #print(win.story["scenes"][scene]["shots"]) #print("------{ }------") clean() current_Y = current_Y + 30 if scene not in win.story["pointers"]: pointer = [point, point] win.story["pointers"][scene] = pointer else: pointer[0] = min(pointer[0], point) pointer[1] = min(pointer[1], point) pointer[0] = max(pointer[0], 1) pointer[1] = max(pointer[1], 1) win.story["pointers"][scene] = pointer # Selecting the shot if win.current["script_shot_higlight"] and win.previous["LMB"] and not win.current["LMB"]: win.cur = "/"+scene+"/"+win.current["script_shot_higlight"] # Going to that asset if win.current["script_asset_higlight"] and win.previous["LMB"] and not win.current["LMB"]: try: del win.text["scene_name"] win.previous["script_asset_higlight"] win.url = "assets" win.cur = win.current["script_asset_higlight"] win.current["asset_scene_selected"] = scene win.current["asset_left_panel"] = "scene" del win.current["script_asset_higlight"] # Saving to the file story.save(win.project, win.story) except: pass ########################### # And finally combine the layers # Outputting the layer layer.set_source_surface(frasesurface, x, y) layer.paint() # Outputting the layer layer.set_source_surface(shotsurface, x, y) layer.paint() # Outputting the layer layer.set_source_surface(assetsurface, x, y) layer.paint() # Outputting the layer layer.set_source_surface(textsurface, x, y) layer.paint() # Scroll UI_elements.scroll_area(layer, win, "script", x+0, y+0, width+30, height-0, current_Y, bar=True, mmb=True, bar_always=True) ####### BOTTOM PANEL ######### UI_color.set(layer, win, "node_background") UI_elements.roundrect(layer, win, win.current["w"]/4, win.current["h"]-50, win.current["w"]/2, 40, 10) # Documentation entry def do(): def after(win, var): pass studio_dialogs.help(win, "help", after, SEARCH=talk.text("documentation_script_writer")) UI_elements.roundrect(layer, win, win.current["w"]/4, win.current["h"]-50, 40, 40, 10, do, "question") # Here in the bottom I want to introduce little things to go between scenes # back and forth. left = "" right = "" for arrow in win.story["arrows"]: if arrow[0][1] == scene and arrow[1] != "end": right = arrow[1][1] if arrow[1][1] == scene and arrow[0] != "start": left = arrow[0][1] if left: def do(): win.scroll["script"] = 0 win.cur = "/"+left win.url = "script" try: del win.text["scene_name"] except: pass UI_elements.roundrect(layer, win, win.current["w"]/2-45, win.current["h"]-50, 40, 40, 10, button=do, icon="left") if right: def do(): win.scroll["script"] = 0 win.cur = "/"+right win.url = "script" try: del win.text["scene_name"] except: pass UI_elements.roundrect(layer, win, win.current["w"]/2+5, win.current["h"]-50, 40, 40, 10, button=do, icon="right") # CANCEl def do(): win.url = "story_editor" win.assets = {} try: del win.text["scene_name"] except: pass win.story["selected"] = [] win.scroll["script"] = 0 win.current["scene_edited_already"] = False UI_elements.roundrect(layer, win, win.current["w"]-40-win.current["w"]/4, win.current["h"]-50, 40, 40, 10, button=do, icon="cancel", tip=talk.text("cancel")) # Short cut ESC if 65307 in win.current["keys"] and not win.textactive: do() ############# CHECKLIST ################# # Here I want to have some checklists. Now... I have 4 types of new check- # lists prepared for shots and scene. There are 3 types of checklists for # a shot. So I can't just simply generate one on the fly. Since I don't # know whether this shot is a simple shot, animated or VFX. And also I # don't want to generate folders when they are not nessesary. So I want to # implement a kind of selection thingy when if a checklists does not exist. # Now let's first of all display our checklists properly. if os.path.exists(win.project+"/rnd"+win.cur+"/shot.progress"): checklist.draw(layer, win, win.project+"/rnd"+win.cur+"/shot.progress", back=win.url) elif os.path.exists(win.project+"/rnd"+win.cur+"/scene.progress") and not shot: checklist.draw(layer, win, win.project+"/rnd"+win.cur+"/scene.progress", back=win.url) else: # Now if we have no checklist what so ever. We want to give user a way # to choose to generate one. x = win.current["w"] / 4 * 3 + 10 y = 10 width = win.current["w"] / 4 - 20 height = win.current["h"] - 20 if shot: # If we are in a shot I want to give different option to choose from # then if we are just simply in a scene. shot_options = { "shot":"add_shot_live_checklist", "shot_anim":"add_shot_anim_checklist", # First name of new_file then text on screen "shot_vfx":"add_shot_vfx_checklist" } for num, option in enumerate(shot_options): # We want to draw a button, a rectangle arround it and the text # tooltip. def do(): # First let's make sure that the folder exists. Because # it might not exists. I think doing it automatically # makes sense. try: os.makedirs(win.project+"/rnd"+win.cur) except: pass # Then we copy the file. oscalls.copy_file( win, os.getcwd()+"/new_file/"+option+".progress", "/rnd"+win.cur+"/", "shot.progress") UI_elements.roundrect(layer, win, x, y+(50*num), width, 40, 10, button=do, icon="checklist_new") UI_color.set(layer, win, "progress_background") UI_elements.roundrect(layer, win, x, y+(50*num), width, 40, 10, fill=False) layer.stroke() UI_color.set(layer, win, "text_normal") layer.set_font_size(20) layer.move_to( x+50, y+(50*num)+25) layer.show_text(talk.text(shot_options[option])) else: # Now... If it's not a shot. We can do it in one line. # It's pretty simple. def do(): # First let's make sure that the folder exists. Because # it might not exists. I think doing it automatically # makes sense. try: os.makedirs(win.project+"/rnd"+win.cur) except: pass # Then we copy the file. oscalls.copy_file( win, os.getcwd()+"/new_file/scene.progress", "/rnd"+win.cur+"/", "scene.progress") UI_elements.roundrect(layer, win, x, y, width, 40, 10, button=do, icon="checklist_new") UI_color.set(layer, win, "progress_background") UI_elements.roundrect(layer, win, x, y, width, 40, 10, fill=False) layer.stroke() UI_color.set(layer, win, "text_normal") layer.set_font_size(20) layer.move_to( x+50, y+25) layer.show_text(talk.text("add_scene_checklist")) ############## LEFT PANEL ##################### leftpanellist = ["shot", "schedule", "history"] # Using the names of the icons. # We need to choose the correct category based smartly on the project's # current progress. Or at least on the current progress of this asset. if "script_left_panel" not in win.current: win.current["script_left_panel"] = "shot" # A little banner. UI_color.set(layer, win, "node_background") UI_elements.roundrect(layer, win, 10, 10, win.current["w"]/4-20, 50, 10) for num, thing in enumerate(leftpanellist): if win.current["script_left_panel"] == thing: UI_color.set(layer, win, "progress_time") UI_elements.roundrect(layer, win, 20+(40*num), 15, 40, 40, 10) def do(): win.current["script_left_panel"] = thing UI_elements.roundrect(layer, win, 20+(40*num), 15, 40, 40, 10, do, thing) ### SCHEDULES ### if win.current["script_left_panel"] == "schedule": schedule.draw(layer, win) ### HISTORY ### if win.current["script_left_panel"] == "history": history.draw(layer, win) #### SHOTS #### if win.current["script_left_panel"] == "shot": # Here I want to draw the left panel full of shots and stuff. So you # could access the animation Blend-Files, render them and see analytics # or rendering. # Since it's the last thing I'm drawing to this layer. I guess we can clip it. x = 10 y = 70 width = win.current["w"] / 4 - 20 height = win.current["h"] - 80 UI_elements.roundrect(layer, win, x, y, width, height, 10, fill=False) layer.clip() if "script_shots" not in win.scroll: win.scroll["script_shots"] = 0 current_Y_shots = 0 rcolors = [ "shot_1", "shot_2", "shot_3", "shot_4", "shot_5" ] # So let's itterate over a lit of shots we've got from the textview. for shotis in shot_list: # Getting the color. It's not always works. if "shot_colors" not in win.story: win.story["shot_colors"] = {} surl = "/"+scene+"/"+shotis if surl not in win.story["shot_colors"]: win.story["shot_colors"][surl] = rcolors[len(win.story["shot_colors"]) % len(rcolors)] col = win.story["shot_colors"][surl] UI_color.set(layer, win, "node_background") UI_elements.roundrect(layer, win, x, y+win.scroll["script_shots"]+current_Y_shots, width, 50, 10) UI_color.set(layer, win, col) UI_elements.roundrect(layer, win, x+60, y+win.scroll["script_shots"]+current_Y_shots+15, 10, 10, 10) # SHOT NAME UI_color.set(layer, win, "text_normal") layer.set_font_size(20) layer.move_to( x+90, y+win.scroll["script_shots"] + current_Y_shots+30) layer.show_text(shotis) # ICON UI_elements.image(layer, win, "settings/themes/"+win.settings["Theme"]+"/icons/shot.png", 20, y+win.scroll["script_shots"] + current_Y_shots+5, 40, 40) # The selection will be badically the current win.cur value. SO... if surl == win.cur: UI_color.set(layer, win, "text_normal") UI_elements.roundrect(layer, win, x, y+win.scroll["script_shots"]+current_Y_shots, width, 50, 10, fill=False) layer.stroke() # If this shot has a folder already. Unfortunatly it's not wise # to make it editable. But I already talk about it in the # beggining of this file. # But a folder button could be cool. if os.path.exists(win.project+"/rnd"+win.cur): def do(): oscalls.Open(win.project+"/rnd"+win.cur) UI_elements.roundrect(layer, win, width-45, y+win.scroll["script_shots"]+current_Y_shots+5, 40, 40, 10, do, "folder", tip="/rnd"+win.cur ) else: # If there is no folder I want to give people an ability # to edit it. I think let's do it like with checklists. # like a little second button. That if you click it. You # create a text entry. I know. Quite a lot of text entries. # There will be more. if "current_shot_name_editor" not in win.current: win.current["current_shot_name_editor"] = "" # Now if the button is clicked. There will be an editor # let's make one. if win.current["current_shot_name_editor"] == shotis and win.textactive == "shot_name": UI_elements.text(layer, win, "shot_name", x+85, y+win.scroll["script_shots"]+current_Y_shots+5, width-90, 40, set_text=shotis) # Now let's make it applyable. But first we need to filter # the name. So there will not be any weird stuff. So the # folder when it's created will not make problems. newname = win.text["shot_name"]["text"].replace("/","_").replace(" ", "_")\ .replace('"',"_").replace("(","_").replace(")","_").replace("'","_")\ .replace("[","_").replace("]","_").replace("{","_").replace("}","_") if newname not in shot_list: def do(): win.current["key_letter"] = "" shot_list[shotis][1] = newname win.cur = "/"+scene+"/"+newname # Saving to the file story.save(win.project, win.story) UI_elements.roundrect(layer, win, width-45, y+win.scroll["script_shots"]+current_Y_shots+5, 40, 40, 10, button=do, icon="ok") else: # A button to activate the editing I guess. def do(): win.current["current_shot_name_editor"] = shotis try: del win.text["shot_name"] except: pass win.textactive = "shot_name" UI_elements.roundrect(layer, win, x+85, y+win.scroll["script_shots"]+current_Y_shots+5, width-90, 40, 10, button=do, fill=False) layer.stroke() current_Y_shots = current_Y_shots + 60 # Let's make the color selectiong thing. Because I know people # gonna eat me alive if the software does everything for them. # Especially when it's something that customazible. for num, col in enumerate(rcolors): if win.story["shot_colors"][surl] == col: # If the current color is the color of the shot then # let's draw a little white thing around it. UI_color.set(layer, win, "text_normal") UI_elements.roundrect(layer, win, x+(width/len(rcolors)*num), y+win.scroll["script_shots"]+current_Y_shots-2, width/len(rcolors), 10, 12) UI_color.set(layer, win, col) UI_elements.roundrect(layer, win, x+(width/len(rcolors)*num)+2, y+win.scroll["script_shots"]+current_Y_shots, width/len(rcolors)-4, 10, 10) # Also let's make a button that the user can click to change # the color. def do(): win.story["shot_colors"][surl] = col UI_elements.roundrect(layer, win, x+(width/len(rcolors)*num), y+win.scroll["script_shots"]+current_Y_shots-2, width/len(rcolors), 20, 12, button=do, fill=False) layer.stroke() current_Y_shots = current_Y_shots + 30 # Now let's draw 4 main folders of the shot. # storyboard # opengl # test_rnd # rendered # There is also an extra folder created always. # but since it's very technical let's not include it here. # let's focus on those 4 because for the user these 4 will # be our progress bar. ##### ##### ##### ##### ########## ######### ########## ######## ########## ##### ##### ##### ##### # Like 4 links in a chain or something. I think it's a good # representation. fouricons = { "storyboard":[], "opengl":[], "test_rnd":[], "rendered":[] } fraction = 0.0 # Let's create a variable for the current selected folder. if shotis+"_active_folder" not in win.current: win.current[shotis+"_active_folder"] = "Pending" for numb, icon in enumerate(fouricons): # While we are drawing them. Let's find out whether there # are any images inside. If yes. We add this to the over # all percentage. files = [] try: files = sorted(os.listdir(win.project+"/rnd"+win.cur+"/"+icon)) except: pass fouricons[icon] = files if files: fraction = 0.25*(numb+1) # If the icon is currently selected. if win.current[shotis+"_active_folder"] == icon: UI_color.set(layer, win, "progress_time") UI_elements.roundrect(layer, win, x+(width/5)*numb+(width/10)+20, y+win.scroll["script_shots"]+current_Y_shots-2, 40, 40, 20) def do(): win.current[shotis+"_active_folder"] = icon UI_elements.roundrect(layer, win, x+(width/5)*numb+(width/10)+20, y+win.scroll["script_shots"]+current_Y_shots-2, 40, 40, 20, button=do, icon=icon, tip=icon) current_Y_shots = current_Y_shots + 50 # Let's find out which one is going to be selected. if win.current[shotis+"_active_folder"] == "Pending": if fouricons["rendered"]: win.current[shotis+"_active_folder"] = "rendered" elif fouricons["test_rnd"]: win.current[shotis+"_active_folder"] = "test_rnd" elif fouricons["opengl"]: win.current[shotis+"_active_folder"] = "opengl" else: win.current[shotis+"_active_folder"] = "storyboard" # Now let's draw a line that will act almost like a progress # bar of a kind. # It will be made of 4 circles stading on a line. UI_color.set(layer, win, "progress_background") UI_elements.roundrect(layer, win, x+20, y+win.scroll["script_shots"]+current_Y_shots-2, width-40, 0, 5) UI_color.set(layer, win, "progress_active") UI_elements.roundrect(layer, win, x+20, y+win.scroll["script_shots"]+current_Y_shots-2, (width-40)*fraction, 0, 5) # Now the 4 circles. for numb, icon in enumerate(fouricons): UI_color.set(layer, win, "progress_background") if fouricons[icon]: # If this folder has any files. UI_color.set(layer, win, "progress_active") UI_elements.roundrect(layer, win, x+(width/5)*numb+(width/10)+30, y+win.scroll["script_shots"]+current_Y_shots-7, 0, 0, 10) current_Y_shots = current_Y_shots + 50 # The user might want to add the subfolders if they do not # exist already. at_least_one_missing = False list_of_folders_to_generate = ["storyboard", "opengl", "test_rnd", "rendered", "extra"] files_there = [] try: files_there = os.listdir(win.project+"/rnd"+win.cur) except: pass for i in list_of_folders_to_generate: if i not in files_there: at_least_one_missing = True if at_least_one_missing: def do(): for i in list_of_folders_to_generate: try: os.mkdir(win.project+"/rnd"+win.cur+"/"+i) except: pass UI_elements.roundrect(layer, win, 10, y+win.scroll["script_shots"]+current_Y_shots-10, width-30, 40, 10, button=do, icon="folder", tip=talk.text("GenerateSubfoldersTip")) UI_color.set(layer, win, "text_normal") layer.move_to(60, y+win.scroll["script_shots"]+current_Y_shots+15) layer.show_text(talk.text("GenerateSubfolders")) current_Y_shots = current_Y_shots + 50 # Now after we have the progress bar / selection type thingy. # let's do a preview of the render. Usually. When rendering # using Blender-Organizer legacy ( or blender console ) it makes # files like 0001.png, 0002.png, 0003.png and so on. Now # user might any type of stuff in that folder. And I don't want # a broken program if stuff like this happens. So. if shotis+"_active_folder_item" not in win.current: win.current[shotis+"_active_folder_item"] = 0 # Making sure that we never going to select something beyond selectable. if win.current[shotis+"_active_folder_item"] > len(fouricons[win.current[shotis+"_active_folder"]])-1: win.current[shotis+"_active_folder_item"] = len(fouricons[win.current[shotis+"_active_folder"]])-1 # This will be the user selected file inside the folder. # The frame. In case image fails to load. UI_color.set(layer, win, "progress_background") UI_elements.roundrect(layer, win, x+5, y+win.scroll["script_shots"]+current_Y_shots-7, width-10, 200, 10, fill=False) layer.stroke() # Image it self. if fouricons[win.current[shotis+"_active_folder"]]: imageurl = win.project+"/rnd"+win.cur+"/"+win.current[shotis+"_active_folder"]+"/"+fouricons[win.current[shotis+"_active_folder"]][win.current[shotis+"_active_folder_item"]] UI_elements.image(layer, win, imageurl, x+10, y+win.scroll["script_shots"]+current_Y_shots-2, int(width-20), int(190), cell="shot_renders") # Well how to live without a clickable openable thingy. def do(): oscalls.file_open(win, imageurl) UI_elements.roundrect(layer, win, x+5, y+win.scroll["script_shots"]+current_Y_shots-7, width-10, 200, 10, button=do, fill=False) layer.stroke() current_Y_shots = current_Y_shots + 210 # After we drew our image. Let's draw a little timeline thing. # so the user could select which frame from the animation they # want to see. Basically a little bar. I guess. IDK. Like # Blender's timeline I guess. But later I will draw a time # graph on it. So it's not as simple as that. UI_color.set(layer, win, "dark_overdrop") layer.rectangle( x+5, y+win.scroll["script_shots"]+current_Y_shots, width-10, 100) layer.fill() numofis = len(fouricons[win.current[shotis+"_active_folder"]]) # Here as you can see I made the the rectangle a 100 pixels high # this is for 1 simple reason. To be able to draw analytics. # But. # There are like one million problems with it. Let's go over them. # * There are multiple blend files each one with analytics data. # * Reading JSON files and drawing graphs is not a trivial thing # requiring cashing of the image. Such as in Analytics window. # Bull will see. # * During rendeing the DATA changes. And I want to see the up # to date data at all times. # Let's try to implement it the simple way. Reading everything # live. I've done that in Blender-Organizer. But the data was # way simpler. alldata = {} largest = 1 # WAIT extra might not exists. try: for filename in os.listdir(win.project+"/rnd"+win.cur+"/extra"): if filename.endswith(".blend.json"): # I want to now open JSON FILE for each one and parse # it. Actually when I think about it. It's not supposed # to be that bad. But this computer has an SSD. LOL. data = {} try: with open(win.project+"/rnd"+win.cur+"/extra/"+filename) as json_file: data = json.load(json_file) # Since I'm inside try I can do some wild stuff here. data = data["analytics"][win.current[shotis+"_active_folder"]] alldata[filename.replace(".json", "")] = data for i in data: if data[i] > largest: largest = data[i] except: pass except: pass # So let's now read through all the data that we have for n, blend in enumerate(alldata): data = alldata[blend] # I want each new file to have it's own color. So UI_color.set(layer, win, rcolors[n % len(rcolors)]) layer.move_to( x+5, y+win.scroll["script_shots"]+current_Y_shots+100 ) # Now let's draw everything. for numb, fil in enumerate(fouricons[win.current[shotis+"_active_folder"]]): if str(numb+1) in data: layer.line_to( x+5+(width-10)/numofis*(numb+0.5), (y+win.scroll["script_shots"]+current_Y_shots+100)-(100/largest*data[str(numb+1)]) ) layer.stroke() # Funny that it didn't really stuck too much like this. Please # let me know if on your machine this way of doing it is hell # a slow. for numb, fil in enumerate(fouricons[win.current[shotis+"_active_folder"]]): # Here I'm going to put some logic. That will enable me to # scroll trough the images. In a way that allows me to # preview the animation. Simple button wouldn't work since # it's waiting for the user to release the button to be # activated. This one will be activated by just dragging # across. # Also my roundrect buttons are not ideal when you have little # space and 10000 frames. fill = False if int(win.current["mx"]) in range(int(x+5+(width-10)/numofis*numb),int(x+5+(width-10)/numofis*numb+(width-10)/numofis))\ and int(win.current["my"]) in range(int(y+win.scroll["script_shots"]+current_Y_shots), int(y+win.scroll["script_shots"]+current_Y_shots+100)): # Before we are doing the logic. I want to give user a # tooltip about render analytics. So let's do this. tip = "" for n, blend in enumerate(alldata): data = alldata[blend] if str(numb+1) in data: tip = tip + blend+" "+UI_math.timestring(data[str(numb+1)]/1000000)+"\n" if tip: tip = tip[:-1] # Removing the last \n UI_elements.tooltip(win, tip) # Now let's do the fill and other stuff fill = True if win.current["LMB"]: win.current[shotis+"_active_folder_item"] = numb if fill or win.current[shotis+"_active_folder_item"] == numb: UI_color.set(layer, win, "progress_time") layer.rectangle( x+5+(width-10)/numofis*numb, y+win.scroll["script_shots"]+current_Y_shots, (width-10)/numofis, 100) layer.fill() current_Y_shots = current_Y_shots + 110 # Okay let's put blend file from the shot. So you could actually # do work on it. # But first. I want to add a seach promt. So I could add new ones # the same way I do it in any other window. By searching. I think # it's important to keep the workflow similar. # I think that if we have no blend files at all. It might give you # to make a new one with the name of the shot. And all the rest will # be done by searching. Yeah. Makes sense. Wait. I can put the name of # the shot in the search. Hm... Okay. Let's make the seach icon first. UI_elements.image(layer, win, "settings/themes/"+win.settings["Theme"]+"/icons/search.png", x+10, y+win.scroll["script_shots"]+current_Y_shots, 40, 40) # Now beside it will be out text entry. UI_elements.text(layer, win, "shot_search", x+50, y+win.scroll["script_shots"]+current_Y_shots, width-55, 40) current_Y_shots = current_Y_shots + 50 # Now let's get the blend file list. It's going to take a few # stept. First we are going to try getting all the files in the # folder. Then filter them by .blend in the end. We do not want # to show you the .blend1 files. We don't want to show you # anything. blendfiles = [] try: for blend in os.listdir(win.project+"/rnd"+win.cur): if blend.endswith(".blend"): blendfiles.append(blend) except: pass # Now since we've got our blendfiles list. We can now draw them. # But I want to preserve the countinuity with the rest of the # software. So I will need to draw a little Blendfile node each # time. For which I'm going to create a node. But this time it's # going to have 2 icons at the bottom. ############## ############## ############## # 01.blend # # 02.blend # # 03.blend # ############## ############## ############## # ____ # # ____ # # ____ # # ( 0 0 ) # # ( 0 0 ) # # ( 0 0 ) # # ) ( # # ) ( # # ) ( # # \__/ # # \__/ # # \__/ # # # # # # # ############## ############## ############## # 0 # 0 # # 0 # 0 # # 0 # 0 # ############## ############## ############## # Those 2 icons in the bottom of each will be buttons for render # and linking. The linking is what you do to put assets inside # animation files. A new dialog should be developped for this. # The rendering will resemble rendering of Blender-Organizer with # render times analytics and stuff. But will be way more extended. # For example with an ability to open a render task on another # computer. So groups of people could work together. tileX = 5 searchis = win.text["shot_search"]["text"].replace("/","_").replace(" ", "_")\ .replace('"',"_").replace("(","_").replace(")","_").replace("'","_")\ .replace("[","_").replace("]","_").replace("{","_").replace("}","_") for bn, blend in enumerate(blendfiles): # Each one will be drawn in it's own little layer. Because # I want the top bar to have a nice rounded corner and also # the image to be cut. # Each one here will 128 pixels wide. ( Because this is the # resolution of blender-thumbnailer.py thumbnails. And I # want to save horizontal resolution as much as possible # But before I render a Blend. I want to exclude it if it's # not in a search. if searchis and searchis.lower() not in blend.lower(): continue # Making the layer nodesurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 128, 198) node = cairo.Context(nodesurface) node.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) UI_elements.roundrect(node, win, 0, 0, 128, 198, 10, fill=False) node.clip() # Background UI_color.set(node, win, "dark_overdrop") node.rectangle(0,0,128, 198) node.fill() # Blender icon UI_elements.image(node, win, win.project+"/rnd"+win.cur+"/"+blend, 0, 20, 128, 128, cell="shot_blends") # Banner UI_color.set(node, win, "node_blendfile") node.rectangle(0,0,128, 20) node.fill() # Filename UI_color.set(node, win, "text_normal") node.set_font_size(12) node.move_to(5, 15) node.show_text(blend) # Outputting the layer layer.set_source_surface(nodesurface, x+tileX, y+win.scroll["script_shots"]+current_Y_shots) layer.paint() # Here we gonna out 3 buttons: # 1. Launch the Blend-File # 2. Link into Blend-File # 3. Render Blend-File # Launch the Blend file: def do(): oscalls.file_open(win, win.project+"/rnd"+win.cur+"/"+blend) UI_elements.roundrect(layer, win, x+tileX, y+win.scroll["script_shots"]+current_Y_shots, 128, 148, 10, button=do, fill=False, tip=blend) layer.stroke() # Link things into Blend-File def do(): # I'm using a dialog here. Because I want to return back # to the shot we were in. But I don't really want to do # anything after the dialog is closed. So there is this # empty function. def after(win, var): pass studio_dialogs.asset_link(win, "asset_linking", after, "/rnd"+win.cur+"/"+blend) UI_elements.roundrect(layer, win, x+tileX+20, y+win.scroll["script_shots"]+current_Y_shots+152, 40, 40, 10, icon="obj_link", button=do, tip=talk.text("link_shot_blend_tooltip")+blend) # Render def do(): def after(win, var): pass studio_dialogs.render(win, "render_setup", after, "/rnd"+win.cur+"/"+blend) UI_elements.roundrect(layer, win, x+tileX+65, y+win.scroll["script_shots"]+current_Y_shots+152, 40, 40, 10, icon="render", button=do, tip=talk.text("render_shot_blend_tooltip")+blend) tileX = tileX + 150 if tileX + 128 > width - 10 and bn != len(blendfiles)-1: tileX = 5 current_Y_shots = current_Y_shots + 205 # Now before we are finished. We want to able to add new files # by typing the names in the search. if not blendfiles and not searchis: searchis = shotis if not searchis.endswith(".blend"): searchis = searchis + ".blend" if searchis != ".blend" and searchis not in blendfiles: # There will be 2 buttons. To add a brand new file and to # copy one from a different shot. In Blender-Oraniger it # had a copy-paste kind of system for this. But since we # have a file selector. I'm going to use it instead. Tho # tell me if a copy-paste for files is a cool idea. if tileX + 128 > width - 10: tileX = 5 current_Y_shots = current_Y_shots + 205 # ADD NEW BLEND def do(): # First let's make sure that the folder exists. Because # it might not exists. I think doing it automatically # makes sense. try: os.makedirs(win.project+"/rnd"+win.cur) except: pass # Then we copy the file. oscalls.copy_file( win, os.getcwd()+"/new_file/rnd.blend", "/rnd"+win.cur+"/", searchis) # And clear the search win.text["shot_search"]["text"] = "" win.images = {} UI_elements.roundrect(layer, win, x+tileX, y+win.scroll["script_shots"]+current_Y_shots, 128, 198, 10, button=do) # A little surrounding thing. UI_color.set(layer, win, "progress_background") UI_elements.roundrect(layer, win, x+tileX, y+win.scroll["script_shots"]+current_Y_shots, 128, 198, 10, fill=False) layer.stroke() # Icon UI_elements.image(layer, win, "settings/themes/"+win.settings["Theme"]+"/icons/new_file.png", x+tileX+44, y+win.scroll["script_shots"]+current_Y_shots + 70, 40, 40) # preiew UI_color.set(layer, win, "text_normal") layer.set_font_size(12) layer.move_to(x+tileX+64-len(searchis)*4, y+win.scroll["script_shots"]+current_Y_shots+150) layer.show_text(searchis) tileX = tileX + 150 if tileX + 128 > width - 10: tileX = 5 current_Y_shots = current_Y_shots + 205 # And a copy file here too # COPY BLEND def do(): # Now the copy function going to be a tiny bit harder # since we will need to launch a dialog for you to # select the file. I think you gonna look for files # from this scene more often. So let's do that. def after(win, var): if var: try: os.makedirs(win.project+"/rnd"+win.cur) except: pass # Then we copy the file. oscalls.copy_file( win,var, "/rnd"+win.cur+"/", searchis) # And clear the search win.text["shot_search"]["text"] = "" win.images = {} # Running the dialog starter studio_dialogs.file_select(win, shotis+"_blends", after, force=True, IMAGE=False, BLEND=True, VIDEO=False, FILE=False, CHR=False, VEH=False, LOC=False, OBJ=False, RND=True, FOLDER=False, SEARCH=scene) UI_elements.roundrect(layer, win, x+tileX, y+win.scroll["script_shots"]+current_Y_shots, 128, 198, 10, button=do) # A little surrounding thing. UI_color.set(layer, win, "progress_background") UI_elements.roundrect(layer, win, x+tileX, y+win.scroll["script_shots"]+current_Y_shots, 128, 198, 10, fill=False) layer.stroke() # Icon UI_elements.image(layer, win, "settings/themes/"+win.settings["Theme"]+"/icons/copy_file.png", x+tileX+44, y+win.scroll["script_shots"]+current_Y_shots + 70, 40, 40) # preiew UI_color.set(layer, win, "text_normal") layer.set_font_size(12) layer.move_to(x+tileX+64-len(searchis)*4, y+win.scroll["script_shots"]+current_Y_shots+150) layer.show_text(searchis) current_Y_shots = current_Y_shots + 205 else: # If the shot is not selected I want to be able to select it # even if I can't find it in the text of the script. def do(): win.cur = surl win.current["scroll_shot_to_in_script"] = True UI_elements.roundrect(layer, win, x, y+win.scroll["script_shots"]+current_Y_shots, width, 50, 10, button=do, fill=False) layer.stroke() current_Y_shots = current_Y_shots + 60 # Scroll UI_elements.scroll_area(layer, win, "script_shots", x+0, y+0, width, height, current_Y_shots, bar=True, mmb=True) return surface