├── Keyframer.toe ├── Keyframer.tox ├── .gitattributes ├── Keyframer.01122024.1.tox ├── modules ├── curvePixel.glsl ├── labelsPixel.glsl ├── labelsVertex.glsl ├── keysVertex.glsl ├── curveVert.glsl ├── keysPixel.glsl ├── handleKnobVertex.glsl ├── handleKnobPixel.glsl ├── channellist.py ├── keyframecontrols.py ├── vsu.py └── widgets.py ├── README.md └── .gitignore /Keyframer.toe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntentDev/Keyframer/HEAD/Keyframer.toe -------------------------------------------------------------------------------- /Keyframer.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntentDev/Keyframer/HEAD/Keyframer.tox -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /Keyframer.01122024.1.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntentDev/Keyframer/HEAD/Keyframer.01122024.1.tox -------------------------------------------------------------------------------- /modules/curvePixel.glsl: -------------------------------------------------------------------------------- 1 | uniform vec4 uColor; 2 | 3 | in Vertex 4 | { 5 | vec4 color; 6 | } iVert; 7 | 8 | // Output variable for the color 9 | layout(location = 0) out vec4 oFragColor[TD_NUM_COLOR_BUFFERS]; 10 | void main() 11 | { 12 | TDCheckDiscard(); 13 | vec4 outcol = vec4(0.0, 0.0, 0.0, 0.0); 14 | outcol.rgb += uColor.rgb * iVert.color.rgb; 15 | outcol.rgb *= uColor.a; 16 | float alpha = uColor.a * iVert.color.a ; 17 | outcol = TDDither(outcol); 18 | TDAlphaTest(alpha); 19 | outcol.a = alpha; 20 | oFragColor[0] = TDOutputSwizzle(outcol); 21 | } 22 | -------------------------------------------------------------------------------- /modules/labelsPixel.glsl: -------------------------------------------------------------------------------- 1 | uniform vec4 uColor; 2 | in Vertex 3 | { 4 | vec4 color; 5 | flat uint instanceTextureIndex; 6 | vec2 texCoord0; 7 | } iVert; 8 | 9 | // Output variable for the color 10 | layout(location = 0) out vec4 oFragColor[TD_NUM_COLOR_BUFFERS]; 11 | void main() 12 | { 13 | TDCheckDiscard(); 14 | vec4 outcol = vec4(0.0, 0.0, 0.0, 0.0); 15 | vec2 texCoord0 = iVert.texCoord0.st; 16 | vec4 emitMapColor = TDInstanceTexture(iVert.instanceTextureIndex, texCoord0.st); 17 | outcol.rgb += uColor.rgb * emitMapColor.rgb * iVert.color.rgb; 18 | outcol.rgb *= uColor.a; 19 | float alpha = uColor.a * emitMapColor.a * iVert.color.a ; 20 | outcol = TDDither(outcol); 21 | TDAlphaTest(alpha); 22 | outcol.a = alpha; 23 | oFragColor[0] = TDOutputSwizzle(outcol); 24 | } 25 | -------------------------------------------------------------------------------- /modules/labelsVertex.glsl: -------------------------------------------------------------------------------- 1 | 2 | out Vertex 3 | { 4 | vec4 color; 5 | flat uint instanceTextureIndex; 6 | vec2 texCoord0; 7 | } oVert; 8 | 9 | void main() 10 | { 11 | 12 | { // Avoid duplicate variable defs 13 | vec3 texcoord = TDInstanceTexCoord(uv[0]); 14 | oVert.texCoord0.st = texcoord.st; 15 | } 16 | vec4 p = vec4(P, 1.0); 17 | gl_Position = uTDMats[0].proj * uTDMats[0].world * TDInstanceMat() * p; 18 | 19 | #ifndef TD_PICKING_ACTIVE 20 | oVert.color = TDInstanceColor(Cd); 21 | oVert.instanceTextureIndex = uint(TDInstanceTextureIndex()); 22 | 23 | #else // TD_PICKING_ACTIVE 24 | 25 | // This will automatically write out the nessessary values 26 | // for this shader to work with picking. 27 | // See the documentation if you want to write custom values for picking. 28 | TDWritePickingValues(); 29 | 30 | #endif // TD_PICKING_ACTIVE 31 | } 32 | -------------------------------------------------------------------------------- /modules/keysVertex.glsl: -------------------------------------------------------------------------------- 1 | uniform float uPointSize; 2 | 3 | out Vertex 4 | { 5 | vec4 color; 6 | vec4 customAttrib0; 7 | } oVert; 8 | 9 | void main() 10 | { 11 | 12 | // First deform the vertex and normal 13 | // TDDeform always returns values in world space 14 | gl_PointSize = uPointSize; 15 | vec3 pos = P; 16 | //pos.xy *= scaleSize; 17 | vec4 worldSpacePos = TDDeform(pos); 18 | gl_Position = TDWorldToProj(worldSpacePos); 19 | 20 | oVert.customAttrib0 = TDInstanceCustomAttrib0(); 21 | 22 | // This is here to ensure we only execute lighting etc. code 23 | // when we need it. If picking is active we don't need lighting, so 24 | // this entire block of code will be ommited from the compile. 25 | // The TD_PICKING_ACTIVE define will be set automatically when 26 | // picking is active. 27 | #ifndef TD_PICKING_ACTIVE 28 | oVert.color = TDInstanceColor(Cd); 29 | 30 | #else // TD_PICKING_ACTIVE 31 | // This will automatically write out the nessessary values 32 | // for this shader to work with picking. 33 | // See the documentation if you want to write custom values for picking. 34 | TDWritePickingValues(); 35 | vTDCustomPickVert.indices = ivec4(TDInstanceCustomAttrib1()); 36 | 37 | #endif // TD_PICKING_ACTIVE 38 | } 39 | -------------------------------------------------------------------------------- /modules/curveVert.glsl: -------------------------------------------------------------------------------- 1 | uniform ivec2 uAnimRange; 2 | uniform float uPointYChop[512]; 3 | // use for chop array with more than 512 samples (large view...) 4 | // uniform samplerBuffer uPointYChop; 5 | 6 | out Vertex 7 | { 8 | vec4 color; 9 | } oVert; 10 | 11 | void main() 12 | { 13 | vec4 adjPos = vec4(P, 1.0); 14 | adjPos.y += uPointYChop[gl_VertexID]; 15 | // use for chop array with more than 512 samples (large view...) 16 | // adjPos.y += texelFetch(uPointYChop, index).x; 17 | vec4 worldCamPos = uTDMats[0].worldCam * adjPos; 18 | worldCamPos.x = P.x; 19 | vec4 projPos = uTDMats[0].proj * worldCamPos; 20 | // projPos.z += 1; 21 | projPos = TDPickAdjust(projPos, 0); 22 | gl_Position = projPos; 23 | 24 | #ifndef TD_PICKING_ACTIVE 25 | vec4 worldCamInvPos = uTDMats[0].worldCamInverse * adjPos; 26 | if (worldCamInvPos.x < uAnimRange.x || worldCamInvPos.x > uAnimRange.y) 27 | { 28 | oVert.color = mix(vec4(.5), vec4(.0), int(P.x * 2) % 2); 29 | } 30 | else 31 | { 32 | oVert.color = TDInstanceColor(Cd); 33 | } 34 | 35 | TDWritePickingValues(); 36 | #else // TD_PICKING_ACTIVE 37 | TDWritePickingValues(); 38 | // vTDPickVert.camSpacePosition = worldCamPos.xyz; 39 | // vTDPickVert.worldSpacePosition = adjPos.xyz; 40 | #endif // TD_PICKING_ACTIVE 41 | } 42 | -------------------------------------------------------------------------------- /modules/keysPixel.glsl: -------------------------------------------------------------------------------- 1 | uniform vec4 uColor; 2 | uniform vec4 uSelColor; 3 | uniform sampler2D sSpriteTex; 4 | 5 | vec4 cols[2]; 6 | 7 | 8 | in Vertex 9 | { 10 | vec4 color; 11 | vec4 customAttrib0; 12 | } iVert; 13 | 14 | // Output variable for the color 15 | layout(location = 0) out vec4 oFragColor[TD_NUM_COLOR_BUFFERS]; 16 | void main() 17 | { 18 | // This allows things such as order independent transparency 19 | // and Dual-Paraboloid rendering to work properly 20 | TDCheckDiscard(); 21 | int viewState = int(ceil(iVert.customAttrib0.y)); 22 | cols[0] = uColor; 23 | cols[1] = uSelColor; 24 | vec4 col = cols[viewState]; 25 | 26 | vec4 outcol = texture(sSpriteTex, gl_PointCoord.st); 27 | 28 | outcol.rgb *= col.rgb * iVert.color.rgb; 29 | 30 | // Alpha Calculation 31 | float alpha = outcol.a * uColor.a * iVert.color.a; 32 | 33 | // Dithering, does nothing if dithering is disabled 34 | outcol = TDDither(outcol); 35 | 36 | outcol.rgb *= alpha; 37 | 38 | // Modern GL removed the implicit alpha test, so we need to apply 39 | // it manually here. This function does nothing if alpha test is disabled. 40 | TDAlphaTest(alpha); 41 | outcol.a = alpha; 42 | oFragColor[0] = TDOutputSwizzle(outcol); 43 | 44 | // TD_NUM_COLOR_BUFFERS will be set to the number of color buffers 45 | // active in the render. By default we want to output zero to every 46 | // buffer except the first one. 47 | for (int i = 1; i < TD_NUM_COLOR_BUFFERS; i++) 48 | { 49 | oFragColor[i] = vec4(0.0); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /modules/handleKnobVertex.glsl: -------------------------------------------------------------------------------- 1 | 2 | uniform vec2 uPointSize; 3 | uniform vec3 uRenderSize; 4 | 5 | out Vertex 6 | { 7 | vec4 color; 8 | vec4 customAttrib0; 9 | mat4 worldTransform; 10 | flat bool locked; 11 | } oVert; 12 | 13 | void main() 14 | { 15 | 16 | // First deform the vertex and normal 17 | // TDDeform always returns values in world space 18 | vec3 pos = P; 19 | vec4 wPos0 = TDDeform(P); 20 | vec4 pPos0 = TDWorldToProj(wPos0); 21 | 22 | pos.x += TDInstanceCustomAttrib0().x; 23 | vec4 wPos1 = TDDeform(pos); 24 | vec4 pPos1 = TDWorldToProj(wPos1); 25 | 26 | gl_Position = pPos1; 27 | vec4 customAttrib0 = TDInstanceCustomAttrib0(); 28 | oVert.customAttrib0 = customAttrib0; 29 | oVert.locked = bool(customAttrib0.z); 30 | oVert.worldTransform = TDInstanceMat(); 31 | 32 | vec3 delta = pPos1.xyz - pPos0.xyz; 33 | float len = length(delta * uRenderSize); 34 | float sizeMix = len / uPointSize.x; 35 | 36 | gl_PointSize = mix(uPointSize.x, uPointSize.y, clamp(sizeMix, 0., 1.)); 37 | 38 | // This is here to ensure we only execute lighting etc. code 39 | // when we need it. If picking is active we don't need lighting, so 40 | // this entire block of code will be ommited from the compile. 41 | // The TD_PICKING_ACTIVE define will be set automatically when 42 | // picking is active. 43 | #ifndef TD_PICKING_ACTIVE 44 | oVert.color = TDInstanceColor(Cd); 45 | 46 | #else // TD_PICKING_ACTIVE 47 | // This will automatically write out the nessessary values 48 | // for this shader to work with picking. 49 | // See the documentation if you want to write custom values for picking. 50 | TDWritePickingValues(); 51 | // vTDPickVert.color = vec4(int(customAttrib0.w), 0, 0, 0); 52 | vTDCustomPickVert.indices = ivec4(TDInstanceCustomAttrib1()); 53 | 54 | #endif // TD_PICKING_ACTIVE 55 | } 56 | -------------------------------------------------------------------------------- /modules/handleKnobPixel.glsl: -------------------------------------------------------------------------------- 1 | uniform vec4 uColor; 2 | uniform vec4 uSelColor; 3 | uniform sampler2D sLocked; 4 | uniform sampler2D sUnlocked; 5 | 6 | vec4 cols[2]; 7 | 8 | 9 | in Vertex 10 | { 11 | vec4 color; 12 | vec4 customAttrib0; 13 | mat4 worldTransform; 14 | flat bool locked; 15 | } iVert; 16 | 17 | // Output variable for the color 18 | layout(location = 0) out vec4 oFragColor[TD_NUM_COLOR_BUFFERS]; 19 | void main() 20 | { 21 | // This allows things such as order independent transparency 22 | // and Dual-Paraboloid rendering to work properly 23 | TDCheckDiscard(); 24 | int viewState = int(ceil(iVert.customAttrib0.y)); 25 | cols[0] = uColor; 26 | cols[1] = uSelColor; 27 | vec4 col = cols[viewState]; 28 | 29 | vec4 coord = vec4(gl_PointCoord.st, 0.0, 0.0); 30 | coord.xy = 2.0 * coord.xy - 1.0; 31 | coord.xyz = mat3(iVert.worldTransform) * coord.xyz; 32 | coord.xy = .5 * coord.xy + .5; 33 | 34 | vec4 outcol = vec4(0.0); 35 | if (iVert.locked == true) 36 | { 37 | outcol = texture(sLocked, coord.xy); 38 | } 39 | else 40 | { 41 | outcol = texture(sUnlocked, coord.xy); 42 | } 43 | 44 | 45 | outcol.rgb *= col.rgb * iVert.color.rgb; 46 | 47 | // Alpha Calculation 48 | float alpha = outcol.a * uColor.a * iVert.color.a; 49 | 50 | // Dithering, does nothing if dithering is disabled 51 | outcol = TDDither(outcol); 52 | 53 | outcol.rgb *= alpha; 54 | 55 | // Modern GL removed the implicit alpha test, so we need to apply 56 | // it manually here. This function does nothing if alpha test is disabled. 57 | TDAlphaTest(alpha); 58 | outcol.a = alpha; 59 | oFragColor[0] = TDOutputSwizzle(outcol); 60 | 61 | // TD_NUM_COLOR_BUFFERS will be set to the number of color buffers 62 | // active in the render. By default we want to output zero to every 63 | // buffer except the first one. 64 | for (int i = 1; i < TD_NUM_COLOR_BUFFERS; i++) 65 | { 66 | oFragColor[i] = vec4(0.0); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /modules/channellist.py: -------------------------------------------------------------------------------- 1 | WIDGETS = iop.Widgets 2 | KEYFRAMER = parent.Keyframer 3 | 4 | class WidgetExt(WIDGETS.Widget): 5 | """ 6 | WidgetExt description 7 | """ 8 | def __init__(self, ownerComp): 9 | super().__init__(ownerComp) 10 | self.listComp = ownerComp.op('list') 11 | 12 | def SetValue(self, element, value): 13 | KEYFRAMER.OnChannelListSetValue(element, value) 14 | 15 | def Refresh(self, channels, channelNames): 16 | self.listComp.par.rows = len(channelNames) 17 | self.listComp.par.Labels.expr = channelNames 18 | run("args[0](args[1], args[2])", self.refresh, channels, channelNames, 19 | delayFrames=1) 20 | 21 | def refresh(self, channels, channelNames): 22 | for i, chan in enumerate(channels.values()): 23 | if i < len(self.listComp.Toggles): 24 | self.listComp.Toggles[i][0] = chan.display 25 | self.listComp.cellAttribs[i, 0].bgColor = [ 26 | self.listComp.Settings.Cellcolor, 27 | self.listComp.Settings.Cellselectcolor 28 | ][int(self.listComp.Toggles[i][0])] 29 | 30 | def OnRSelectOffToOn(self, startrow, startcol, startcoords, 31 | endrow, endcol, endcoords, start, end): 32 | chanName = self.listComp.cellAttribs[endrow, 0].text 33 | KEYFRAMER.OpenContextMenu(self.ownerComp, chanName) 34 | pass 35 | 36 | def StartEditCell(self, chanName): 37 | self.row = KEYFRAMER.ChannelNames.val.index(chanName) 38 | self.listComp.StartEditCell(self.row, 0) 39 | 40 | def OnEditCell(self, row, col, value): 41 | chanName = KEYFRAMER.ChannelNames.val[row] 42 | KEYFRAMER.RenameChannel(chanName, value) 43 | 44 | def OnMSelectOffToOn(self, startrow, startcol, startcoords, 45 | endrow, endcol, endcoords, start, end): 46 | self.mSelectStartRow = startrow 47 | 48 | def OnMSelectOnToOff(self, startrow, startcol, startcoords, 49 | endrow, endcol, endcoords, start, end): 50 | 51 | if endrow != None and endrow != -1: 52 | KEYFRAMER.ReorderChannels(self.mSelectStartRow, endrow) 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Keyframer 2 | 3 | Keyframer is a keyframe editor component for use with animationCOMP in TD. It's designed as a replacement for the existing animation editor in TD, offering enhanced functionality and user experience. 4 | 5 | ## Key Features 6 | 7 | - **Template Creation**: Utilize an internal template animationCOMP to generate multiple animationCOMPs with identical initial channels. 8 | - **Channel Creation**: Easily create new channels by dropping Pars, CHOP channels, CHOPs, and DATs into Keyframer. Includes a standard append channels function with custom names. 9 | - **AnimationCOMP Editing**: Directly edit any dropped animationCOMP within Keyframer. 10 | - **Undo/Redo**: Full support for undoing and redoing actions. 11 | - **Keyframe Management**: 12 | - Insert Keys using Alt-Left Click or Alt-Ctrl Left Click. 13 | - Delete Keys with the Delete button. 14 | - Translate Keys by selecting and dragging with the mouse. Use Shift for horizontal locking and Shift-Ctrl for vertical locking. 15 | - Scale Keys by selecting multiple keys, then pressing D, F, or both, and moving the mouse to scale along the X, Y, or XY axis. 16 | - **Clipboard Operations**: Copy, cut, and paste selected keys at the current mouse position (within the edit view). 17 | - **Bezier Handles**: Handles represent the actual length of acceleration for precise control. 18 | - **Handle Locking**: Toggle lock/unlock on handles with the T key. 19 | - **Channel Organization**: Reorder channels via middle-click and drag in the Channel List. 20 | - **Advanced Features**: 21 | - Auto limiting handle lengths. 22 | - New `EaseAdjust()` segment type with adjustable ease in/out handles. 23 | - Nudge selected items (keys or handles) using arrow keys (Shift, Ctrl, and Alt modify step size). 24 | - Tab selection of adjacent items - use Tab and Shift+Tab to navigate. 25 | - Marquee and Shift select for multi-item selection. 26 | - **View Controls**: 27 | - Translate View with a middle-click and drag. 28 | - Zoom View with Shift-Middle Click and drag. 29 | - Fit View to screen with the H key. 30 | - Auto Vert Slider Widgets for smooth, fine adjustments of values for selected items. 31 | 32 | ## Installation 33 | 34 | To use Keyframer, only the `Keyframer.*.tox` file is required (external modules and files do not need to be downloaded). 35 | -------------------------------------------------------------------------------- /modules/keyframecontrols.py: -------------------------------------------------------------------------------- 1 | WIDGETS = iop.Widgets 2 | KEYFRAMER = parent.Keyframer 3 | 4 | class WidgetVecExt(WIDGETS.Widget): 5 | """ 6 | WidgetVecExt description 7 | """ 8 | def __init__(self, ownerComp): 9 | super().__init__(ownerComp) 10 | 11 | def SetValue(self, element, value): 12 | KEYFRAMER.SetSelectedFuncs[element.name](value) 13 | 14 | def UpdateViews(self, elementsValues): 15 | for key,value in elementsValues.items(): 16 | if key != 'function': 17 | self.elementDict[key].UpdateView(None, value) 18 | else: 19 | self.elementDict[key].UpdateView(value) 20 | # print(key, value) 21 | 22 | def UpdateView(self, element, value): 23 | self.elementDict[element].UpdateView(None, value) 24 | 25 | def UpdateViewKey(self, frame, value): 26 | self.elementDict['frame'].UpdateView(None, frame) 27 | self.elementDict['value'].UpdateView(None, value) 28 | 29 | def UpdateViewInHandle(self, slope, accel): 30 | self.elementDict['inslope'].UpdateView(None, slope) 31 | self.elementDict['inaccel'].UpdateView(None, accel) 32 | 33 | def UpdateViewOutHandle(self, slope, accel): 34 | self.elementDict['outslope'].UpdateView(None, slope) 35 | self.elementDict['outaccel'].UpdateView(None, accel) 36 | 37 | def UpdateViewFunction(self, value): 38 | self.elementDict['function'].UpdateView(value) 39 | 40 | def GetState(self): 41 | fullState = {key:e.GetState() for key,e in self.elementDict.items() 42 | if e in self.elementsUpdate} 43 | state = {} 44 | for key, val in fullState.items(): 45 | if isinstance(val, dict): 46 | state[key] = val['Field'] 47 | else: 48 | state[key] = val 49 | return state 50 | 51 | def ActiveElement(self, name, value): 52 | self.elementDict[name].Active(value) 53 | 54 | def ActiveKey(self, active): 55 | if active: 56 | self.Active(active) 57 | else: 58 | self.elementDict['frame'].Active(active) 59 | self.elementDict['value'].Active(active) 60 | self.elementDict['function'].Active(active) 61 | 62 | def ActiveInHandle(self, active): 63 | self.elementDict['inslope'].Active(active) 64 | self.elementDict['inaccel'].Active(active) 65 | 66 | def ActiveOutHandle(self, active): 67 | self.elementDict['outslope'].Active(active) 68 | self.elementDict['outaccel'].Active(active) 69 | 70 | def ActiveAll(self, key, inHandle, outHandle): 71 | self.ActiveKey(key) 72 | self.ActiveInHandle(inHandle) 73 | self.ActiveOutHandle(outHandle) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #project specific 2 | 3 | /Backup 4 | 5 | config.ini 6 | CrashAutoSave.toe 7 | 8 | 9 | 10 | # Byte-compiled / optimized / DLL files 11 | __pycache__/ 12 | *.py[co] 13 | *$py.class 14 | 15 | # C extensions 16 | *.so 17 | 18 | # Distribution / packaging 19 | .Python 20 | build/ 21 | develop-eggs/ 22 | dist/ 23 | downloads/ 24 | eggs/ 25 | .eggs/ 26 | lib/ 27 | lib64/ 28 | parts/ 29 | sdist/ 30 | var/ 31 | wheels/ 32 | share/python-wheels/ 33 | *.egg-info/ 34 | .installed.cfg 35 | *.egg 36 | MANIFEST 37 | 38 | # PyInstaller 39 | # Usually these files are written by a python script from a template 40 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 41 | *.manifest 42 | *.spec 43 | 44 | # Installer logs 45 | pip-log.txt 46 | pip-delete-this-directory.txt 47 | 48 | # Unit test / coverage reports 49 | htmlcov/ 50 | .tox/ 51 | .nox/ 52 | .coverage 53 | .coverage.* 54 | .cache 55 | nosetests.xml 56 | coverage.xml 57 | *.cover 58 | *.py,cover 59 | .hypothesis/ 60 | .pytest_cache/ 61 | cover/ 62 | 63 | # Translations 64 | *.mo 65 | *.pot 66 | 67 | # Django stuff: 68 | *.log 69 | local_settings.py 70 | db.sqlite3 71 | db.sqlite3-journal 72 | 73 | # Flask stuff: 74 | instance/ 75 | .webassets-cache 76 | 77 | # Scrapy stuff: 78 | .scrapy 79 | 80 | # Sphinx documentation 81 | docs/_build/ 82 | 83 | # PyBuilder 84 | .pybuilder/ 85 | target/ 86 | 87 | # Jupyter Notebook 88 | .ipynb_checkpoints 89 | 90 | # IPython 91 | profile_default/ 92 | ipython_config.py 93 | 94 | # pyenv 95 | # For a library or package, you might want to ignore these files since the code is 96 | # intended to run in multiple environments; otherwise, check them in: 97 | # .python-version 98 | 99 | # pipenv 100 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 101 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 102 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 103 | # install all needed dependencies. 104 | #Pipfile.lock 105 | 106 | # poetry 107 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 108 | # This is especially recommended for binary packages to ensure reproducibility, and is more 109 | # commonly ignored for libraries. 110 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 111 | #poetry.lock 112 | 113 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 114 | __pypackages__/ 115 | 116 | # Celery stuff 117 | celerybeat-schedule 118 | celerybeat.pid 119 | 120 | # SageMath parsed files 121 | *.sage.py 122 | 123 | # Environments 124 | .env 125 | .venv 126 | env/ 127 | venv/ 128 | ENV/ 129 | env.bak/ 130 | venv.bak/ 131 | 132 | # Spyder project settings 133 | .spyderproject 134 | .spyproject 135 | 136 | # Rope project settings 137 | .ropeproject 138 | 139 | # mkdocs documentation 140 | /site 141 | 142 | # mypy 143 | .mypy_cache/ 144 | .dmypy.json 145 | dmypy.json 146 | 147 | # Pyre type checker 148 | .pyre/ 149 | 150 | # pytype static type analyzer 151 | .pytype/ 152 | 153 | # Cython debug symbols 154 | cython_debug/ 155 | 156 | # PyCharm 157 | # JetBrains specific template is maintainted in a separate JetBrains.gitignore that can 158 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 159 | # and can be added to the global gitignore or merged into this file. For a more nuclear 160 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 161 | #.idea/ 162 | 163 | 164 | 165 | plugins/Plugins.json 166 | -------------------------------------------------------------------------------- /modules/vsu.py: -------------------------------------------------------------------------------- 1 | # various utilities module 2 | # use module level function for standalone functions 3 | # use classMethod() or staticmethod in classes fi class 4 | # is meant to be used rather than object 5 | import re 6 | 7 | def ExpandValue(value, to1, to2): 8 | # expands normalized value 9 | value = ((to2 - to1) * (value)) / 1.0 + to1 10 | return value 11 | 12 | def NormalizeValue(value, from1, from2): 13 | return (value - from1) / (from2 - from1) 14 | 15 | def RangeValue(from1, from2, to1, to2, value): 16 | value = ((to2 - to1) * (value -from1)) / (from2 - from1) + to1 17 | return value 18 | 19 | def getOpDepth(operator, maxSearchDepth=100): 20 | isRoot = False 21 | i = 1 22 | depth = 0 23 | while isRoot is False and i < maxSearchDepth: 24 | if operator.parent(i) == root: 25 | isRoot = True 26 | depth = i 27 | i += 1 28 | return(depth) 29 | 30 | class TextportLogging: 31 | 32 | @staticmethod 33 | def printStatusMessage(message, error=False): 34 | if error: 35 | title= "ERROR" 36 | print(f"{title}:", message, error) 37 | else: 38 | title= "SUCCESS" 39 | print(f"{title}:", message) 40 | 41 | class VMath: 42 | # example (only really needed if needed access attributes between functions) 43 | @classmethod 44 | def sum2(cls, x, y): 45 | return x + y 46 | 47 | @staticmethod 48 | def expandValue(value, to1, to2): 49 | # expands normalized value 50 | value = ((to2 - to1) * (value)) / 1.0 + to1 51 | return value 52 | 53 | @staticmethod 54 | def normalizeValue(value, from1, from2): 55 | return (value - from1) / (from2 - from1) 56 | 57 | @staticmethod 58 | def rangeValue(value, from1, from2, to1, to2): 59 | value = ((to2 - to1) * (value -from1)) / (from2 - from1) + to1 60 | return value 61 | 62 | class Timecode(): 63 | def __init__(self, rate=None): 64 | if rate == None: 65 | self.rate = me.time.rate 66 | else: 67 | self.rate = rate 68 | 69 | def getSeconds(self, hours, mins , secs, frames, rate): 70 | return hours * 60 * 60 + mins * 60 + secs + frames / rate 71 | 72 | def TimecodeToSeconds(self, timecode): 73 | timecode = timecode.split(':') 74 | hours = int(timecode[0]) 75 | mins = int(timecode[1]) 76 | secs = int(timecode[2][:2]) 77 | frames = int(timecode[2][-2:]) 78 | return self.getSeconds(hours, mins, secs, frames, self.rate) 79 | 80 | def SecondsToTimecode(self, Seconds): 81 | frac, seconds = math.modf(Seconds) 82 | frames = round(self.rate * frac) 83 | minutes = math.floor(seconds / 60) 84 | hours = math.floor(minutes / 60) 85 | minutes = minutes % 60 86 | seconds = int(seconds) % 60 87 | hours = str(hours).zfill(2) 88 | minutes = str(minutes).zfill(2) 89 | seconds = str(seconds).zfill(2) 90 | frames = str(frames).zfill(2) 91 | return f"{hours}:{minutes}:{seconds}.{frames}" 92 | 93 | def TimecodeToFrames(self, timecode): 94 | timecode = re.split(r'[:.]', timecode) 95 | hours = int(timecode[0]) 96 | mins = int(timecode[1]) 97 | secs = int(timecode[2]) 98 | frames = int(timecode[3]) 99 | seconds = self.getSeconds(hours, mins, secs, frames, self.rate) 100 | frame = int(seconds * self.rate) 101 | return frame 102 | 103 | def ExtTimecodeToFrames(self, timecode, rate): 104 | timecode = re.split(r'[:.]', timecode) 105 | hours = int(timecode[0]) 106 | mins = int(timecode[1]) 107 | secs = int(timecode[2]) 108 | frames = int(timecode[3]) 109 | seconds = self.getSeconds(hours, mins, secs, frames, rate) 110 | frame = int(seconds * rate) 111 | return frame 112 | 113 | def FramesToTimecode(self, frame): 114 | frameRate = self.rate 115 | ff = int(frame % frameRate) 116 | s = int(frame // frameRate) 117 | l = (s // 3600, s // 60 % 60, s % 60, ff) 118 | hours = str(l[0]).zfill(2) 119 | minutes = str(l[1]).zfill(2) 120 | seconds = str(l[2]).zfill(2) 121 | frames = str(l[3]).zfill(2) 122 | return f"{hours}:{minutes}:{seconds}.{frames}" 123 | 124 | def ExtFramesToTimecode(self, frame, rate): 125 | frameRate = self.rate 126 | ff = int(frame % rate) 127 | s = int(frame // rate) 128 | l = (s // 3600, s // 60 % 60, s % 60, ff) 129 | hours = str(l[0]).zfill(2) 130 | minutes = str(l[1]).zfill(2) 131 | seconds = str(l[2]).zfill(2) 132 | frames = str(l[3]).zfill(2) 133 | return f"{hours}:{minutes}:{seconds}.{frames}" 134 | 135 | def CheckAllChar(self, string, search = re.compile(r'[^\d:.]').search): 136 | return not bool(search(string.replace(' ', ''))) 137 | 138 | def StringToTimecode(self, string, prevString): 139 | timecode = prevString 140 | setTimecode = self.CheckAllChar(string) 141 | if setTimecode: 142 | frameRate = self.rate 143 | tc = re.split(r'[:.]', string) 144 | i = 0 145 | frames = 0 146 | for val in reversed(tc): 147 | if val == '': val = 0 148 | val = int(val) 149 | if i == 0: frames += val 150 | elif i == 1: frames += val * frameRate 151 | elif i == 2: frames += val * 60 * frameRate 152 | elif i == 3: frames += val * 3600 * frameRate 153 | i += 1 154 | timecode = self.FramesToTimecode(frames) 155 | return timecode 156 | -------------------------------------------------------------------------------- /modules/widgets.py: -------------------------------------------------------------------------------- 1 | import colorsys 2 | import copy 3 | vsu = op('vsu').module 4 | from pprint import pprint 5 | if hasattr(parent, 'Vision'): 6 | VSN = parent.Vision 7 | else: 8 | VSN = None 9 | 10 | WIDGETS = iop.Widgets 11 | 12 | PI = 3.1415926535897932384626433832795 13 | PI2 = 2 * PI 14 | 15 | 16 | class Widget: 17 | def __init__(self, ownerComp): 18 | self.ownerComp = ownerComp 19 | self.parent = ownerComp.parent() 20 | self.name = ownerComp.name 21 | self.par = ownerComp.par 22 | self.panel = ownerComp.panel 23 | self.contextMenu = self.ownerComp.iop.ContextMenu 24 | if hasattr(ownerComp.parent, 'widgetView'): 25 | self.View = ownerComp.parent.widgetView 26 | else: 27 | self.View = ownerComp 28 | 29 | self.elements = ownerComp.findChildren(tags=['WidgetElement', 'Widget'], depth=1) 30 | self.elements.sort(key=lambda x: x.par.alignorder.eval()) 31 | self.elementDict = {element.name:element for element 32 | in self.elements} 33 | self.elementsUpdate = [element for element in self.elements 34 | if hasattr(element, 'UpdateView')] 35 | self.numElementsUpdate = len(self.elementsUpdate) 36 | self.contextMenuItems = [ 37 | {'label': 'Set Default', 'func': self.SetDefault}, 38 | {'label': 'Copy Value', 'func': self.CopyValue}, 39 | {'label': 'Paste Value', 'func': self.PasteValue}, 40 | ] 41 | 42 | def UpdateView(self, element, value): 43 | self.elementDict[element].UpdateView(value) 44 | 45 | def SetValue(self, element, value): 46 | """Override this function in derived class if needed.""" 47 | self.parent.SetValue(self.ownerComp, value) 48 | pass 49 | 50 | def SetupFromVar(self, var): 51 | # override in each extension if needed 52 | pass 53 | 54 | def SetDefault(self, *args, **kwargs): 55 | # override in each extension if needed 56 | pass 57 | 58 | def CopyValue(self, *args, **kwargs): 59 | # override in each extension if needed 60 | pass 61 | 62 | def PasteValue(self, *args, **kwargs): 63 | # override in each extension if needed 64 | pass 65 | 66 | def GetState(self): 67 | return {key:e.GetState() for key,e in self.elementDict.items() 68 | if e in self.elementsUpdate} 69 | 70 | def Active(self, active): 71 | for element in self.elementsUpdate: 72 | element.Active(active) 73 | 74 | class WidgetVar(Widget): 75 | """ 76 | WidgetButtonExt description 77 | """ 78 | def __init__(self, ownerComp): 79 | super().__init__(ownerComp) 80 | self.label = ownerComp.op('Label') 81 | self.varName = self.name 82 | self.varAttr = 'val' 83 | 84 | @property 85 | def plugin(self): 86 | return self.par.Plugin.eval() 87 | 88 | @property 89 | def var(self): 90 | return getattr(self.plugin.Var, self.varName) 91 | 92 | def UpdateView(self, element, value): 93 | for element in self.elementsUpdate: 94 | element.UpdateView(value) 95 | 96 | def SetupFromVar(self, var): 97 | md = var.metadata 98 | if md.get('label') != None: 99 | self.label.par.Labeltext = md['label'] 100 | else: 101 | self.label.par.Labeltext = var.name 102 | 103 | def OpenContextMenu(self, element): 104 | self.contextMenu.OpenMenu(self, self.contextMenuItems) 105 | 106 | def SetDefault(self, *args, **kwargs): 107 | value = self.var.metadata[self.varAttr]['default'] 108 | VSN.SetVarAttr(self.plugin, self.varName, self.varAttr, value) 109 | pass 110 | 111 | def CopyValue(self, *args, **kwargs): 112 | self.contextMenu.CopiedValue = [getattr(self.var, self.varAttr)] 113 | pass 114 | 115 | def PasteValue(self, *args, **kwargs): 116 | prevValue = getattr(self.var, self.varAttr) 117 | value = self.contextMenu.CopiedValue[0] 118 | if isinstance(value, (int, float)): 119 | VSN.SetVarAttr(self.plugin, self.varName, self.varAttr, value) 120 | ui.undo.startBlock('Paste Value') 121 | ui.undo.addCallback(self.undoPaste, [prevValue, value]) 122 | ui.undo.endBlock() 123 | 124 | def undoPaste(self, isUndo, info): 125 | if isUndo: 126 | value = info[0] 127 | else: 128 | value = info[1] 129 | if isinstance(value, (int, float)): 130 | VSN.SetVarAttr(self.plugin, self.varName, self.varAttr, value) 131 | 132 | class WidgetProperty: 133 | def __init__(self, element): 134 | self.element = element 135 | 136 | def __get__(self, obj, objType=None): 137 | return self.element 138 | 139 | def __set__(self, obj, value): 140 | self.element.UpdateView(value) 141 | 142 | class Element: 143 | def __init__(self, ownerComp, updateElements): 144 | self.ownerComp = ownerComp 145 | self.parent = ownerComp.parent() 146 | self.name = ownerComp.name 147 | self.par = ownerComp.par 148 | self.panel = ownerComp.panel 149 | self.panelexecDat = ownerComp.op('panelexec') 150 | self.updateElements = updateElements 151 | 152 | if hasattr(ownerComp.par, 'Doundo'): 153 | self.doUndo = ownerComp.par.Doundo.eval() 154 | else: 155 | for i in range(vsu.getOpDepth(ownerComp)): 156 | if hasattr(ownerComp.parent(i).par, 'Doundo'): 157 | self.doUndo = ownerComp.parent(i).par.Doundo.eval() 158 | break 159 | else: 160 | self.doUndo = True 161 | 162 | def ExpandVal(self, low, high, value, Type): 163 | value = ((high - low) * (value)) / 1 + low 164 | if Type == 'int': 165 | value = round(value) 166 | return value 167 | 168 | def NormalizeVal(self, low, high, value): 169 | return (value - low) / (high - low) 170 | 171 | def NormalizeValMod(self, low, high, value): 172 | low = -max(abs(low),abs(high)) 173 | high = max(abs(low),abs(high)) 174 | return (value - low) / (high - low) 175 | 176 | def RangeVal(self, from1, from2, to1, to2, value, Type): 177 | value = ((to2 - to1) * (value -from1)) / (from2 - from1) + to1 178 | if Type == 'int': 179 | value = math.ceil(value) 180 | return value 181 | 182 | def OnRollover(self, value): 183 | value = max(self.panel.rollover.val, self.panel.lselect.val) 184 | bgCol = self.bgCols[value] 185 | self.par.bgcolorr = bgCol[0] 186 | self.par.bgcolorg = bgCol[1] 187 | self.par.bgcolorb = bgCol[2] 188 | self.par.bgalpha = bgCol[3] 189 | 190 | def OnRSelectOffToOn(self): 191 | if hasattr(self.parent, 'OpenContextMenu'): 192 | self.parent.OpenContextMenu(self) 193 | 194 | def GetColors(self, *tupletNames): 195 | colors = [] 196 | for tupletName in tupletNames: 197 | tuplet = self.ownerComp.pars(f"{tupletName}*") 198 | colors.append([par.eval() for par in tuplet]) 199 | return colors 200 | 201 | def GetState(self): 202 | return self.curState 203 | 204 | def Active(self, active): 205 | self.panelexecDat.par.active = active 206 | self.ownerComp.par.enable = active 207 | 208 | @property 209 | def curState(self): 210 | return self.ownerComp.storage.get('curState') 211 | @curState.setter 212 | def curState(self, value): 213 | self.ownerComp.storage['curState'] = value 214 | 215 | @property 216 | def prevState(self): 217 | return self.ownerComp.storage.get('prevState') 218 | @prevState.setter 219 | def prevState(self, value): 220 | self.ownerComp.storage['prevState'] = value 221 | 222 | class WidgetSettings: 223 | def __init__(self): 224 | pass 225 | 226 | class Label(Element): 227 | def __init__(self, ownerComp, updateElements=None): 228 | self.Text = ownerComp.op('Text') 229 | self.textPar = self.Text.par 230 | super().__init__(ownerComp, updateElements) 231 | 232 | class SliderHorz(Element): 233 | def __init__(self, ownerComp, updateElements=None): 234 | super().__init__(ownerComp, updateElements) 235 | self.bgCols = self.GetColors('Slidercolbg', 'Slidercolroll') 236 | self.knobCols = self.GetColors('Knobcolbg', 'Knobcolsel') 237 | self.Knob = ownerComp.op('Knob') 238 | self.knobPar = self.Knob.par 239 | self.detailLevel = ownerComp.fetch('detailLevel', 240 | {'fine': 1.0, 'init': 1.0, 'prev': 1.0}, 241 | storeDefault=True, search=False) 242 | 243 | self.undoBlockName = f"SliderHorz Value" 244 | self.ownerComp.fetch('curState', 0.0, search=False, storeDefault=True) 245 | self.ownerComp.fetch('prevState', self.curState, 246 | search=False, storeDefault=True) 247 | self.prevState = self.curState 248 | 249 | def getValue(self, value): 250 | Type = self.par.Type 251 | range1 = self.par.Range1.eval() 252 | range2 = self.par.Range2.eval() 253 | value = self.ExpandVal(range1, range2, value, Type) 254 | if self.par.Clamp: 255 | value = min(range2, max(range1, value)) 256 | return value 257 | 258 | def undo(self, isUndo, info): 259 | if isUndo: 260 | state = info[0] 261 | else: 262 | state = info[1] 263 | self.UpdateView(state) 264 | if self.updateElements: 265 | [element.UpdateView(state) for element in self.updateElements] 266 | self.parent.SetValue(self.ownerComp, state) 267 | 268 | def getDetailValue(self, value): 269 | return (self.detailLevel['prev'] 270 | + (value - self.detailLevel['init']) 271 | * self.detailLevel['fine']) 272 | 273 | def OnLSelectOffToOn(self): 274 | self.detailLevel['init'] = self.panel.trueu.val 275 | fine = 1.0 276 | if self.panel.alt.val and self.panel.ctrl.val: 277 | self.par.mouserel = True 278 | fine = .001 279 | elif self.panel.alt.val: 280 | self.par.mouserel = True 281 | fine = .01 282 | elif self.panel.ctrl.val: 283 | self.par.mouserel = True 284 | fine = .1 285 | self.detailLevel['fine'] = fine 286 | self.OnKnobSelect(1) 287 | self.prevState = self.curState 288 | 289 | def OnLSelectOnToOff(self): 290 | self.par.mouserel = False 291 | u = self.panel.trueu.val 292 | if self.detailLevel['fine'] != 1.0: 293 | u = self.getDetailValue(u) 294 | self.detailLevel['prev'] = u 295 | self.detailLevel['fine'] = 1.0 296 | self.OnKnobSelect(0) 297 | if self.doUndo: 298 | ui.undo.startBlock(self.undoBlockName) 299 | ui.undo.addCallback(self.undo, [self.prevState, self.curState]) 300 | ui.undo.endBlock() 301 | 302 | def OnKnobSelect(self, value): 303 | knobCol = self.knobCols[value] 304 | self.knobPar.bgcolorr = knobCol[0] 305 | self.knobPar.bgcolorg = knobCol[1] 306 | self.knobPar.bgcolorb = knobCol[2] 307 | self.knobPar.bgalpha = knobCol[3] 308 | 309 | def OnTrueu(self, u): 310 | if self.detailLevel['fine'] != 1.0: 311 | u = self.getDetailValue(u) 312 | self.UpdateView(u, normalize=False) 313 | value = self.getValue(u) 314 | self.curState = value 315 | if self.updateElements: 316 | [element.UpdateView(value) for element in self.updateElements] 317 | self.parent.SetValue(self.ownerComp, value) 318 | 319 | def UpdateView(self, value, normalize=True): 320 | if normalize: 321 | self.curState = value 322 | range1 = self.par.Range1 323 | range2 = self.par.Range2 324 | value = self.ownerComp.NormalizeVal(range1, range2, value) 325 | self.detailLevel['prev'] = value 326 | knobWidth = self.Knob.par.w 327 | parentWidth = self.ownerComp.width 328 | self.Knob.par.x = max(min(value * parentWidth - knobWidth * .5, 329 | parentWidth - knobWidth - 1), 1,) 330 | 331 | class Field(Element): 332 | def __init__(self, ownerComp, updateElements=None): 333 | super().__init__(ownerComp, updateElements) 334 | self.bgCols = self.GetColors('Fieldcolbg', 'Fieldcolroll') 335 | self.fontCols = self.GetColors('Fontcolcolor', 'Fontcolroll', 336 | 'Fontcolinv', 'Fontcolbg') 337 | self.Text = ownerComp.op('Text') 338 | self.textPar = self.Text.par 339 | self.string = ownerComp.op('string') 340 | self.undoBlockName = "Field Value" 341 | self.ownerComp.fetch('curState', self.panel.field.val, 342 | search=False, storeDefault=True) 343 | self.ownerComp.fetch('prevState', self.curState, 344 | search=False, storeDefault=True) 345 | self.prevState = self.curState 346 | 347 | def OnRollover(self, value): 348 | self.bgCol = self.bgCols[value] 349 | self.par.bgcolorr = self.bgCol[0] 350 | self.par.bgcolorg = self.bgCol[1] 351 | self.par.bgcolorb = self.bgCol[2] 352 | self.par.bgalpha = self.bgCol[3] 353 | 354 | self.fontCol = self.fontCols[value] 355 | self.textPar.fontcolorr = self.fontCol[0] 356 | self.textPar.fontcolorg = self.fontCol[1] 357 | self.textPar.fontcolorb = self.fontCol[2] 358 | self.textPar.fontalpha = self.fontCol[3] 359 | 360 | def undo(self, isUndo, info): 361 | # print(info) 362 | if isUndo: 363 | state = info[0] 364 | else: 365 | state = info[1] 366 | self.setField(state) 367 | self.textPar.text = state 368 | 369 | def OnFocusOffToOn(self): 370 | pass 371 | 372 | def OnFocusOnToOff(self): 373 | if self.prevState != self.panel.field.val: 374 | value = self.panel.field.val 375 | self.setField(value) 376 | 377 | # in field undo is extremely buggy, need to create simple case 378 | # and report bug. 379 | def OnCharacter(self, value): 380 | # if value != 0: 381 | # self.prevState = self.curState 382 | # else: 383 | # print(self.prevState, self.panel.fieldediting.val) 384 | # self.curState = self.panel.fieldediting.val 385 | # ui.undo.startBlock(self.undoBlockName) 386 | # ui.undo.addCallback(self.undo,[self.prevState, self.curState]) 387 | # ui.undo.endBlock() 388 | pass 389 | 390 | def setField(self, value): 391 | if self.par.fieldtype.eval() != 'string': 392 | value = float(value) 393 | if self.par.Clamp: 394 | range1 = self.par.Range1.eval() 395 | range2 = self.par.Range2.eval() 396 | value = min(range2, max(range1, value)) 397 | self.UpdateView(value) 398 | self.curState = value 399 | if self.updateElements: 400 | [element.UpdateView(value) for element 401 | in self.updateElements] 402 | self.parent.SetValue(self.ownerComp, value) 403 | if self.doUndo: 404 | ui.undo.startBlock(self.undoBlockName) 405 | ui.undo.addCallback(self.undo, [self.prevState, value]) 406 | ui.undo.endBlock() 407 | self.prevState = self.curState 408 | 409 | 410 | def UpdateView(self, value): 411 | if self.ownerComp.par.enable.eval(): 412 | self.curState = value 413 | if self.par.fieldtype.eval() != 'string': 414 | self.textPar.text = f"{round(value, 5 - len(str(round(value))))}" 415 | else: 416 | self.textPar.text = value 417 | 418 | def OnRSelectOffToOn(self): 419 | self.panel.focus = False 420 | super().OnRSelectOffToOn() 421 | 422 | def OnMSelectOffToOn(self): 423 | self.panel.focus = False 424 | 425 | def OnMSelectOnToOff(self): 426 | self.panel.focus = False 427 | 428 | def Active(self, active): 429 | self.ownerComp.par.enable = active 430 | self.panelexecDat.par.active = active 431 | if not active: 432 | self.Text.par.field = '' 433 | self.Text.cook(force=True) 434 | self.Text.par.text = '' 435 | 436 | else: 437 | self.Text.par.field = '..' 438 | self.Text.cook(force=True) 439 | 440 | class Button(Element): 441 | def __init__(self, ownerComp, updateElements=None): 442 | super().__init__(ownerComp, updateElements) 443 | self.btnCols = self.GetColors('Buttoncolcolor', 'Buttoncolroll', 444 | 'Buttoncolsel', 'Buttoncolon') 445 | self.TextOff = ownerComp.op('TextOff') 446 | self.TextOn = ownerComp.op('TextOn') 447 | self.digits = self.ownerComp.digits 448 | self.toggleUndoBlockName = "Button Toggle Value" 449 | self.pulseUndoBlockName = "Button Pulse" 450 | self.ownerComp.fetch('curState', 0, 451 | search=False, storeDefault=True) 452 | self.ownerComp.fetch('prevState', self.curState, 453 | search=False, storeDefault=True) 454 | self.prevState = self.curState 455 | self.UndoCallback = None 456 | 457 | def undo(self, isUndo, info): 458 | if isUndo: 459 | state = info[0] 460 | else: 461 | state = info[1] 462 | self.UpdateView(state) 463 | self.parent.SetValue(self.ownerComp, state) 464 | 465 | def setColor(self): 466 | index = min(3, 467 | (self.panel.rollover + self.panel.state * 2) + self.panel.lselect) 468 | col = self.btnCols[index] 469 | self.par.bgcolorr = col[0] 470 | self.par.bgcolorg = col[1] 471 | self.par.bgcolorb = col[2] 472 | self.par.bgalpha = col[3] 473 | 474 | def OnRollOffToOn(self, value): 475 | self.setColor() 476 | 477 | def OnRollOnToOff(self, value): 478 | self.setColor() 479 | 480 | def OnLSelectOffToOn(self): 481 | self.prevState = self.curState 482 | self.curState = bool(self.panel.state.val) 483 | self.setColor() 484 | self.parent.SetValue(self.ownerComp, self.curState) 485 | 486 | def OnLSelectOnToOff(self): 487 | self.setColor() 488 | buttonType = self.par.Buttontype.eval() 489 | if buttonType == 'momentary': 490 | self.curState = bool(self.panel.state.val) 491 | self.parent.SetValue(self.ownerComp, self.curState) 492 | if self.par.Doundo: 493 | if buttonType == 'toggledown': 494 | ui.undo.startBlock(self.toggleUndoBlockName) 495 | ui.undo.addCallback(self.undo, [self.prevState, self.curState]) 496 | ui.undo.endBlock() 497 | elif self.UndoCallback != None: 498 | ui.undo.startBlock(self.pulseUndoBlockName) 499 | ui.undo.addCallback(self.UndoCallback, self.ownerComp) 500 | ui.undo.endBlock() 501 | 502 | def UpdateView(self, value): 503 | if self.par.Buttontype.eval() != 'pulse': 504 | self.curState = value 505 | self.panel.state = value 506 | self.setColor() 507 | 508 | def Active(self, active): 509 | if not active and self.panel.state.val != 2: 510 | self.panel.state.val = 2 511 | self.ownerComp.par.enable = active 512 | self.panelexecDat.par.active = active 513 | if active: 514 | self.panel.state.val = 0 515 | 516 | class SliderVert(Element): 517 | def __init__(self, ownerComp, updateElements=None): 518 | super().__init__(ownerComp, updateElements) 519 | self.bgCols = self.GetColors('Slidercolbg', 'Slidercolroll') 520 | self.knobCols = self.GetColors('Knobcolbg', 'Knobcolsel') 521 | self.Knob = ownerComp.op('Knob') 522 | self.knobPar = self.Knob.par 523 | self.panelexecAutoDat = ownerComp.op('panelexecAuto') 524 | self.keyboard = self.ownerComp.iop.MasterKeyboard 525 | self.ctrl = self.keyboard['kctrl'] 526 | self.alt = self.keyboard['kalt'] 527 | self.detailLevel = ownerComp.fetch('detailLevel', 528 | {'fine': 1.0, 'init': 1.0, 'prev': 1.0}, 529 | storeDefault=True, search=False,) 530 | 531 | self.undoBlockName = f"SliderVert Value" 532 | self.ownerComp.fetch('curState', 0.0, search=False, storeDefault=True) 533 | self.ownerComp.fetch('prevState', self.curState, 534 | search=False, storeDefault=True) 535 | self.prevState = self.curState 536 | 537 | def getValue(self, value): 538 | Type = self.par.Type 539 | range1 = self.par.Range1.eval() 540 | range2 = self.par.Range2.eval() 541 | value = self.ExpandVal(range1, range2, value, Type) 542 | if self.par.Clamp: 543 | value = min(range2, max(range1, value)) 544 | return value 545 | 546 | def undo(self, isUndo, info): 547 | if isUndo: 548 | state = info[0] 549 | else: 550 | state = info[1] 551 | if self.par.Slidermode != 'AUTO': 552 | self.UpdateView(state) 553 | if self.updateElements: 554 | [element.UpdateView(state) for element in self.updateElements] 555 | self.parent.SetValue(self.ownerComp, state) 556 | 557 | def OnRollover(self, value): 558 | value = max(self.panel.rollover.val, self.panel.lselect.val) 559 | bgCol = self.bgCols[value] 560 | self.par.bgcolorr = bgCol[0] 561 | self.par.bgcolorg = bgCol[1] 562 | self.par.bgcolorb = bgCol[2] 563 | self.par.bgalpha = bgCol[3] 564 | if self.updateElements: 565 | self.updateElements[0].panel.rollover = value 566 | 567 | def getDetailValue(self, value): 568 | return (self.detailLevel['prev'] 569 | + (value - self.detailLevel['init']) 570 | * self.detailLevel['fine']) 571 | 572 | def setFine(self): 573 | value = self.getFine() 574 | if value != 1.0: 575 | self.par.mouserel = True 576 | self.detailLevel['fine'] = value 577 | 578 | def getFine(self): 579 | fine = 1.0 580 | if self.alt and self.ctrl: 581 | fine = .001 582 | elif self.alt: 583 | fine = .01 584 | elif self.ctrl: 585 | fine = .1 586 | return fine 587 | 588 | def OnKnobSelect(self, value): 589 | knobCol = self.knobCols[value] 590 | self.knobPar.bgcolorr = knobCol[0] 591 | self.knobPar.bgcolorg = knobCol[1] 592 | self.knobPar.bgcolorb = knobCol[2] 593 | self.knobPar.bgalpha = knobCol[3] 594 | 595 | def OnLSelectOffToOn(self): 596 | if self.par.Slidermode == 'REL': 597 | self.par.mouserel = True 598 | self.detailLevel['init'] = self.panel.truev * self.par.Relposscale 599 | elif self.par.Slidermode == 'AUTO': 600 | self.UpdateKnob(.5) 601 | self.par.mouserel = True 602 | self.detailLevel['init'] = self.panel.truev.val 603 | else: 604 | self.detailLevel['init'] = self.panel.truev.val 605 | self.setFine() 606 | self.OnKnobSelect(1) 607 | self.prevState = self.curState 608 | 609 | def OnLSelectOnToOff(self): 610 | self.panel.ctrl = 0 611 | self.panel.alt = 0 612 | self.par.mouserel = False 613 | if self.detailLevel['fine'] != 1.0 or self.par.Slidermode != 'ABS': 614 | value = self.panel.truev.val * self.par.Relposscale 615 | value = self.getDetailValue(value) 616 | elif self.par.Slidermode == 'ABS': 617 | value = 0.0 618 | else: 619 | value = self.panel.truev.val 620 | if self.par.Clamp: 621 | value = min(1, max(0, value)) 622 | 623 | self.detailLevel['fine'] = 1.0 624 | if self.par.Slidermode == 'AUTO': 625 | self.UpdateKnob(.5) 626 | else: 627 | self.detailLevel['prev'] = value 628 | self.OnKnobSelect(0) 629 | if self.doUndo: 630 | ui.undo.startBlock(self.undoBlockName) 631 | ui.undo.addCallback(self.undo, [self.prevState, self.curState]) 632 | ui.undo.endBlock() 633 | 634 | def OnTruev(self, v): 635 | if self.par.Slidermode != 'AUTO': 636 | if self.detailLevel['fine'] != 1.0 or self.par.Slidermode != 'ABS': 637 | v *= self.par.Relposscale 638 | v = self.getDetailValue(v) 639 | self.UpdateView(v, normalize=False) 640 | value = self.getValue(v) 641 | self.curState = value 642 | if self.updateElements: 643 | [element.UpdateView(value) for element in self.updateElements] 644 | self.parent.SetValue(self.ownerComp, value) 645 | 646 | def OnWhileOn(self): 647 | range1 = self.par.Range1.eval() 648 | range2 = self.par.Range2.eval() 649 | Type = self.par.Type 650 | fine = self.getFine() 651 | v = self.panel.truev.val - self.detailLevel['init'] 652 | v *= self.par.Relposscale * fine * .1 * self.par.Autospeed 653 | self.UpdateKnob(v * 10 / self.par.Autospeed + .5) 654 | v = v + self.detailLevel['prev'] 655 | if self.par.Clamp: 656 | v = min(1, max(0, v)) 657 | if v != self.detailLevel['prev']: 658 | self.detailLevel['prev'] = v 659 | value = self.ExpandVal(range1, range2, v, Type) 660 | if self.updateElements: 661 | [element.UpdateView(value) for element in self.updateElements] 662 | self.parent.SetValue(self.ownerComp, value) 663 | 664 | self.curState = value 665 | 666 | def UpdateView(self, value, normalize=True): 667 | if normalize: 668 | self.curState = value 669 | range1 = self.par.Range1 670 | range2 = self.par.Range2 671 | value = self.ownerComp.NormalizeVal(range1, range2, value) 672 | self.detailLevel['prev'] = value 673 | if self.par.Slidermode != 'AUTO': 674 | self.UpdateKnob(value) 675 | 676 | def UpdateKnob(self, value): 677 | knobHeight = self.Knob.height 678 | ownerHeight = self.ownerComp.height 679 | self.Knob.par.y = max(min(value * ownerHeight - knobHeight * .5, 680 | ownerHeight - knobHeight - 2), 2,) 681 | 682 | def Active(self, active): 683 | self.ownerComp.par.enable = active 684 | self.panelexecDat.par.active = active 685 | self.panelexecAutoDat.par.whileon = active 686 | self.Knob.par.display = active 687 | 688 | class Droplist(Element): 689 | def __init__(self, ownerComp, updateElements=None): 690 | super().__init__(ownerComp, updateElements) 691 | self.DroplistListView = self.ownerComp.iop.DroplistListView 692 | self.ListItems = ownerComp.op('ListItems') 693 | self.button = ownerComp.op('Button') 694 | self.ItemHeight = self.button.par.h 695 | self.undoBlockName = "Droplist Value" 696 | self.ownerComp.fetch('curState', 0, 697 | search=False, storeDefault=True) 698 | self.ownerComp.fetch('prevState', self.curState, 699 | search=False, storeDefault=True) 700 | self.prevState = self.curState 701 | 702 | @property 703 | def ListWidth(self): 704 | return self.button.width 705 | 706 | def undo(self, isUndo, info): 707 | if isUndo: 708 | state = info[0] 709 | else: 710 | state = info[1] 711 | self.UpdateView(state) 712 | self.parent.SetValue(self.ownerComp, state) 713 | 714 | def OnSelectItem(self, itemIndex): 715 | self.prevState = self.curState 716 | self.UpdateView(itemIndex) 717 | self.parent.SetValue(self.ownerComp, itemIndex) 718 | if self.doUndo: 719 | ui.undo.startBlock(self.undoBlockName) 720 | ui.undo.addCallback(self.undo, [self.prevState, self.curState]) 721 | ui.undo.endBlock() 722 | 723 | def UpdateListItems(self, items): 724 | # TODO implement... 725 | pass 726 | 727 | def UpdateView(self, itemIndex): 728 | self.curState = itemIndex 729 | buttonLabel = self.ListItems[itemIndex, 0].val 730 | self.button.par.Offtext = buttonLabel 731 | self.button.par.Ontext = buttonLabel 732 | 733 | def SetValue(self, element, value): 734 | self.DroplistListView.OpenList(self.ownerComp) 735 | 736 | def Active(self, active): 737 | self.button.Active(active) 738 | 739 | class DroplistListView(Element): 740 | def __init__(self, ownerComp, updateElements=None): 741 | super().__init__(ownerComp, updateElements) 742 | self.droplist = self.ownerComp.op('dropContainer/list') 743 | self.selectListItems = ownerComp.op('selectListItems') 744 | self.window = self.ownerComp.op('window') 745 | self.listContainer = self.ownerComp.op('listContainer') 746 | self.list = self.listContainer.op('list') 747 | self.absMouse = ownerComp.par.Absolutemousechop.eval() 748 | 749 | self.DroplistWidget = None 750 | self.listItems = ownerComp.op('listItems') 751 | pass 752 | 753 | def OnLSelectOnToOff(self): 754 | itemIndex = int(self.list.panel.celloverid) 755 | if itemIndex != -1: 756 | self.DroplistWidget.OnSelectItem(itemIndex) 757 | self.window.par.winclose.pulse() 758 | 759 | def OpenList(self, droplistWidget): 760 | self.DroplistWidget = droplistWidget 761 | self.listItems = self.DroplistWidget.ListItems 762 | opened = self.window.isOpen 763 | self.selectListItems.par.dat = self.listItems 764 | 765 | maxHeight = int(self.DroplistWidget.par.Maxlistheight) 766 | compH = self.listItems.numRows * self.DroplistWidget.ItemHeight 767 | height = min(maxHeight, compH) 768 | width = self.DroplistWidget.ListWidth 769 | self.listContainer.par.w = width 770 | if self.listContainer.par.pvscrollbar: 771 | w2 = width - 12 772 | self.list.par.w = w2 773 | else: 774 | self.list.par.w = width 775 | self.listContainer.par.h = height 776 | 777 | if opened == False: 778 | absMouseY = self.absMouse['ty'].eval() 779 | mouseX = (self.DroplistWidget.panel.insideu 780 | * self.DroplistWidget.width) 781 | mouseY = (self.DroplistWidget.panel.insidev 782 | * self.DroplistWidget.height) 783 | 784 | x = width - mouseX - 8 785 | if absMouseY >= height + self.DroplistWidget.ItemHeight: 786 | y = - height * 0.5 - mouseY 787 | else: 788 | y = height * 0.5 + (self.DroplistWidget.par.h - mouseY) 789 | 790 | self.window.par.winoffsetx = x 791 | self.window.par.winoffsety = y 792 | self.window.par.winopen.pulse() 793 | self.listContainer.setFocus() 794 | else: 795 | self.window.par.winclose.pulse() 796 | 797 | class MultiButton(Element): 798 | def __init__(self, ownerComp, updateElements=None): 799 | super().__init__(ownerComp, updateElements) 800 | self.cellAttribs = ownerComp.cellAttribs 801 | self.CurItem = ownerComp.fetch('CurrentItem', [None, None, None], 802 | search=False, storeDefault=True) 803 | self.PrevItem = ownerComp.fetch('PrevItem', [None, None, None], 804 | search=False, storeDefault=True) 805 | self.parexec = self.ownerComp.op('parexec') 806 | self.getSettings() 807 | self.GetToggles() 808 | self.ownerComp.par.reset.pulse() 809 | self.undoBlockName = "RadioButton Value" 810 | self.ownerComp.fetch('curState', self.CurItem[2], 811 | search=False, storeDefault=True) 812 | self.ownerComp.fetch('prevState', self.curState, 813 | search=False, storeDefault=True) 814 | self.prevState = self.curState 815 | self.lselect = False 816 | self.rselect = False 817 | self.mselect = False 818 | 819 | self.textJustifyModes = [JustifyType.TOPLEFT, JustifyType.TOPCENTER, 820 | JustifyType.TOPRIGHT, JustifyType.CENTERLEFT, 821 | JustifyType.CENTER, JustifyType.CENTERRIGHT, 822 | JustifyType.BOTTOMLEFT, JustifyType.BOTTOMCENTER, 823 | JustifyType.BOTTOMRIGHT] 824 | 825 | @property 826 | def Toggles(self): 827 | return self.ownerComp.storage.get('Toggles', [[]]) 828 | @Toggles.setter 829 | def Toggles(self, value): 830 | self.ownerComp.storage['Toggles'] = value 831 | 832 | def undo(self, isUndo, info): 833 | if isUndo: 834 | state = info[0] 835 | else: 836 | state = info[1] 837 | if self.Settings.Type != 'toggledown': 838 | self.UpdateView(state) 839 | else: 840 | self.Toggles[state[0]][state[1]] = state[2] 841 | cell = state[0] * self.Settings.cols + state[1] 842 | self.UpdateView(cell) 843 | self.SetValue(state) 844 | 845 | def getSettings(self): 846 | self.Settings = WidgetSettings() 847 | parNames = self.parexec.par.pars.eval().replace(',', ' ') 848 | parNames = parNames.split() 849 | 850 | for name in parNames: 851 | pars = self.ownerComp.pars(name) 852 | lenPars = len(pars) 853 | if lenPars > 1: 854 | value = [par.eval() for par in pars] 855 | setattr(self.Settings, name.replace('*', ''), value) 856 | elif lenPars == 1: 857 | value = pars[0].eval() 858 | setattr(self.Settings, name, value) 859 | 860 | def GetToggles(self, init=False): 861 | prevToggles = self.ownerComp.storage.get('Toggles', [[]]) 862 | default = [] 863 | numRows = self.Settings.rows 864 | numCols = self.Settings.cols 865 | for i in range(numRows): 866 | row = [] 867 | for n in range(numCols): 868 | if i < len(prevToggles): 869 | if n < len(prevToggles[i]): 870 | row.append(prevToggles[i][n]) 871 | else: 872 | row.append(self.ownerComp.par.Defaulttogglevalue.eval()) 873 | else: 874 | row.append(self.ownerComp.par.Defaulttogglevalue.eval()) 875 | default.append(row) 876 | 877 | if not init and numRows != len(self.Toggles): 878 | init = True 879 | elif not init and len(self.Toggles) > 0: 880 | if numCols != len(self.Toggles[0]): 881 | init = True 882 | if init: 883 | self.Toggles = default 884 | 885 | def OnInitCell(self, row, col, attribs): 886 | numCells = self.Settings.rows * self.Settings.cols 887 | if isinstance(self.Settings.Labels, list): 888 | userLabels = self.Settings.Labels 889 | elif isinstance(self.Settings.Labels, str): 890 | userLabels = self.Settings.Labels.replace(',', ' ').replace('[', ' ').replace(']', ' ') 891 | userLabels = userLabels.split() 892 | else: 893 | userLabels = [] 894 | lenUserLabels = len(userLabels) 895 | indexLabels = [str(i) for i in range (lenUserLabels, 896 | lenUserLabels + numCells)] 897 | cell = row * self.Settings.cols + col 898 | source = userLabels + indexLabels 899 | if cell < len(source): 900 | cellContent = source[cell] 901 | attribs.text = cellContent 902 | attribs.textColor = self.Settings.Textcolor 903 | attribs.textJustify = getattr(JustifyType, 904 | self.Settings.Textjustify) 905 | attribs.textOffsetX = self.Settings.Textoffset[0] 906 | attribs.textOffsetY = self.Settings.Textoffset[1] 907 | attribs.fontSizeX = self.par.Fontsize 908 | attribs.fontFace = 'Verdana' 909 | # attribs.fontFace = "../vlib/widgets/fonts/Inter-3.13/"\ 910 | # "Inter Desktop/Inter-Regular.otf" 911 | 912 | if self.Settings.Type != 'toggledown': 913 | isSelected = int(self.CurItem[2] == cell) 914 | attribs.bgColor = [self.Settings.Cellcolor, 915 | self.Settings.Cellselectcolor][isSelected] 916 | else: 917 | if row < len(self.Toggles): 918 | if col < len(self.Toggles[row]): 919 | toggle = int(self.Toggles[row][col]) 920 | else: 921 | toggle = 0 922 | else: 923 | toggle = 0 924 | 925 | attribs.bgColor = [ 926 | self.Settings.Cellcolor, 927 | self.Settings.Cellselectcolor 928 | ][toggle] 929 | 930 | def OnSelect(self, startrow, startcol, startcoords, 931 | endrow, endcol, endcoords, start, end): 932 | # print('start', self.panel.mselect.val, startrow, endrow, start, end) 933 | lselect = self.panel.lselect.val 934 | if self.panel.lselect.val: 935 | if start: 936 | self.lselect = True 937 | self.OnLSelect(startrow, startcol, startcoords, 938 | endrow, endcol, endcoords, start, end) 939 | elif self.panel.rselect.val: 940 | if start: 941 | self.rselect = True 942 | self.OnRSelect(startrow, startcol, startcoords, 943 | endrow, endcol, endcoords, start, end) 944 | elif self.panel.mselect.val: 945 | if start: 946 | self.mselect = True 947 | self.OnMSelect(startrow, startcol, startcoords, 948 | endrow, endcol, endcoords, start, end) 949 | 950 | if end: 951 | if self.lselect: 952 | self.OnLSelect(startrow, startcol, startcoords, 953 | endrow, endcol, endcoords, start, end) 954 | self.lselect = False 955 | elif self.rselect: 956 | self.OnRSelect(startrow, startcol, startcoords, 957 | endrow, endcol, endcoords, start, end) 958 | self.rselect = False 959 | elif self.mselect: 960 | self.OnMSelect(startrow, startcol, startcoords, 961 | endrow, endcol, endcoords, start, end) 962 | self.mselect = False 963 | 964 | def OnLSelect(self, startrow, startcol, startcoords, 965 | endrow, endcol, endcoords, start, end): 966 | cell = startrow * self.Settings.cols + startcol 967 | if endrow != None and endcol != None and startrow != -1 and endrow != -1: 968 | endCell = endrow * self.Settings.cols + endcol 969 | 970 | lselect = self.panel.lselect.val 971 | if start and lselect: 972 | self.lselect = True 973 | 974 | if self.Settings.Type == 'radio': 975 | if start and self.lselect: 976 | self.prevState = self.curState 977 | self.UpdateView(cell) 978 | self.SetValue(cell) 979 | elif self.CurItem[2] != endCell and self.lselect: 980 | self.PrevItem[0:4] = self.CurItem 981 | prevItemAttribs = self.cellAttribs[self.PrevItem[0], 982 | self.PrevItem[1]] 983 | cellAttribs = self.cellAttribs[endrow, endcol] 984 | cellAttribs.bgColor = self.Settings.Cellselectcolor 985 | if prevItemAttribs: 986 | prevItemAttribs.bgColor = self.Settings.Cellcolor 987 | self.CurItem[0:4] = [endrow, endcol, endCell] 988 | self.SetValue(endCell) 989 | self.curState = endCell 990 | 991 | elif self.Settings.Type == 'exclusive': 992 | if start and self.lselect: 993 | self.prevState = self.curState 994 | if cell == self.CurItem[2]: 995 | cell = -1 996 | self.UpdateView(cell) 997 | self.SetValue(cell) 998 | elif self.CurItem[2] != endCell and self.lselect: 999 | if not end: 1000 | self.PrevItem[0:4] = self.CurItem 1001 | prevItemAttribs = self.cellAttribs[self.PrevItem[0], 1002 | self.PrevItem[1]] 1003 | cellAttribs = self.cellAttribs[endrow, endcol] 1004 | cellAttribs.bgColor = self.Settings.Cellselectcolor 1005 | if prevItemAttribs: 1006 | prevItemAttribs.bgColor = self.Settings.Cellcolor 1007 | self.CurItem[0:4] = [endrow, endcol, endCell] 1008 | self.SetValue(endCell) 1009 | self.curState = endCell 1010 | 1011 | elif self.Settings.Type == 'toggledown': 1012 | if start and self.lselect: 1013 | state = self.Toggles[startrow][startcol] 1014 | self.prevState = [startrow, startcol, state] 1015 | state = not state 1016 | self.Toggles[startrow][startcol] = state 1017 | self.UpdateView(cell) 1018 | value = [startrow, startcol, state] 1019 | self.SetValue(value) 1020 | self.curState = value 1021 | if self.doUndo: 1022 | ui.undo.startBlock(self.undoBlockName) 1023 | ui.undo.addCallback(self.undo, 1024 | [self.prevState, self.curState]) 1025 | ui.undo.endBlock() 1026 | 1027 | elif self.CurItem[2] != endCell and self.lselect: 1028 | self.PrevItem[0:4] = self.CurItem 1029 | state = self.Toggles[endrow][endcol] 1030 | self.prevState = [endrow, endcol, state] 1031 | state = not state 1032 | self.Toggles[endrow][endcol] = state 1033 | self.UpdateView(endCell) 1034 | value = [endrow, endcol, state] 1035 | self.SetValue(value) 1036 | self.curState = value 1037 | self.CurItem[0:4] = [endrow, endcol, endCell] 1038 | if self.doUndo: 1039 | ui.undo.startBlock(self.undoBlockName) 1040 | ui.undo.addCallback(self.undo, 1041 | [self.prevState, self.curState]) 1042 | ui.undo.endBlock() 1043 | 1044 | else: 1045 | if start and self.lselect: 1046 | self.prevState = self.curState 1047 | self.UpdateView(cell) 1048 | self.SetValue([cell, True]) 1049 | self.momentaryCell = cell 1050 | elif self.CurItem[2] != endCell and self.lselect: 1051 | self.PrevItem[0:4] = self.CurItem 1052 | prevItemAttribs = self.cellAttribs[self.PrevItem[0], 1053 | self.PrevItem[1]] 1054 | cellAttribs = self.cellAttribs[endrow, endcol] 1055 | cellAttribs.bgColor = self.Settings.Cellselectcolor 1056 | if prevItemAttribs: 1057 | prevItemAttribs.bgColor = self.Settings.Cellcolor 1058 | self.CurItem[0:4] = [endrow, endcol, endCell] 1059 | self.momentaryCell = endCell 1060 | self.SetValue([self.momentaryCell, True]) 1061 | if self.Settings.Type == 'momentary': 1062 | self.SetValue([self.PrevItem[2], False]) 1063 | elif end and self.lselect: 1064 | cell = -1 1065 | self.UpdateView(cell) 1066 | if self.Settings.Type == 'momentary': 1067 | self.SetValue([self.momentaryCell, False]) 1068 | 1069 | if self.doUndo and end and self.lselect and self.Settings.Type \ 1070 | not in ['pulse', 'momentary', 'toggledown']: 1071 | ui.undo.startBlock(self.undoBlockName) 1072 | ui.undo.addCallback(self.undo, [self.prevState, self.curState]) 1073 | ui.undo.endBlock() 1074 | self.lselect = False 1075 | 1076 | elif end and self.Settings.Type in ['pulse', 'momentary']: 1077 | cell = -1 1078 | self.UpdateView(cell) 1079 | if self.Settings.Type == 'momentary': 1080 | self.SetValue([self.momentaryCell, False]) 1081 | 1082 | return 1083 | 1084 | def OnRSelect(self, startrow, startcol, startcoords, 1085 | endrow, endcol, endcoords, start, end): 1086 | if hasattr(self.ownerComp.parent(), 'OnRSelectOffToOn') and start: 1087 | self.ownerComp.parent().OnRSelectOffToOn( 1088 | startrow, startcol, startcoords, 1089 | endrow, endcol, endcoords, start, end) 1090 | elif hasattr(self.ownerComp.parent(), 'OnRSelectOnToOff') and end: 1091 | self.ownerComp.parent().OnRSelectOnToOff( 1092 | startrow, startcol, startcoords, 1093 | endrow, endcol, endcoords, start, end) 1094 | 1095 | def OnMSelect(self, startrow, startcol, startcoords, 1096 | endrow, endcol, endcoords, start, end): 1097 | if hasattr(self.ownerComp.parent(), 'OnMSelectOffToOn') and start: 1098 | self.ownerComp.parent().OnMSelectOffToOn( 1099 | startrow, startcol, startcoords, 1100 | endrow, endcol, endcoords, start, end) 1101 | elif hasattr(self.ownerComp.parent(), 'OnMSelectOnToOff') and end: 1102 | self.ownerComp.parent().OnMSelectOnToOff( 1103 | startrow, startcol, startcoords, 1104 | endrow, endcol, endcoords, start, end) 1105 | if start: 1106 | self.prevrow = None 1107 | self.prevcol = None 1108 | self.row = endrow 1109 | self.col = endcol 1110 | 1111 | if self.prevrow != None and self.prevcol != None: 1112 | prevCell = self.prevrow * self.Settings.cols + self.prevcol 1113 | else: 1114 | prevCell = None 1115 | if self.row != None and self.col != None: 1116 | cell = self.row * self.Settings.cols + self.col 1117 | else: 1118 | cell = None 1119 | 1120 | if self.row != self.prevrow or self.col != self.prevcol: 1121 | if cell != None and cell >= 0: 1122 | if self.Settings.Type != 'toggledown': 1123 | state = int(cell == self.CurItem[2]) 1124 | else: 1125 | state = int(self.Toggles[self.row][self.col]) 1126 | if self.cellAttribs[self.row, self.col]: 1127 | cellAttribs = self.cellAttribs[self.row, self.col] 1128 | cellAttribs.bgColor = [self.Settings.Cellrollcolor, 1129 | self.Settings.Cellrollselectcolor][state] 1130 | 1131 | if prevCell != None and prevCell >= 0: 1132 | if self.Settings.Type != 'toggledown': 1133 | state = int(prevCell == self.CurItem[2]) 1134 | else: 1135 | state = int(self.Toggles[self.prevrow][self.prevcol]) 1136 | if self.cellAttribs[self.prevrow, self.prevcol]: 1137 | cellAttribs = self.cellAttribs[self.prevrow, self.prevcol] 1138 | cellAttribs.bgColor = [self.Settings.Cellcolor, 1139 | self.Settings.Cellselectcolor][state] 1140 | self.prevrow = self.row 1141 | self.prevcol = self.col 1142 | 1143 | def OnRollover(self, row, col, coords, prevrow, prevcol, prevcoords): 1144 | # print(row, col, coords, prevrow, prevcol, prevcoords) 1145 | if prevrow != None and prevcol != None: 1146 | prevCell = prevrow * self.Settings.cols + prevcol 1147 | else: 1148 | prevCell = None 1149 | if row != None and col != None: 1150 | cell = row * self.Settings.cols + col 1151 | else: 1152 | cell = None 1153 | 1154 | if row != prevrow or col != prevcol: 1155 | if cell != None and cell >= 0: 1156 | if self.Settings.Type != 'toggledown': 1157 | state = int(cell == self.CurItem[2]) 1158 | else: 1159 | state = int(self.Toggles[row][col]) 1160 | if self.cellAttribs[row, col]: 1161 | cellAttribs = self.cellAttribs[row, col] 1162 | cellAttribs.bgColor = [self.Settings.Cellrollcolor, 1163 | self.Settings.Cellrollselectcolor][state] 1164 | 1165 | if prevCell != None and prevCell >= 0 and len(self.Toggles) > 0: 1166 | if self.Settings.Type != 'toggledown': 1167 | state = int(prevCell == self.CurItem[2]) 1168 | else: 1169 | state = int(self.Toggles[prevrow][prevcol]) 1170 | if self.cellAttribs[prevrow, prevcol]: 1171 | cellAttribs = self.cellAttribs[prevrow, prevcol] 1172 | cellAttribs.bgColor = [self.Settings.Cellcolor, 1173 | self.Settings.Cellselectcolor][state] 1174 | 1175 | def StartEditCell(self, row, col): 1176 | self.ownerComp.setKeyboardFocus(row, col) 1177 | 1178 | def OnEditCell(self, row, col, value): 1179 | if hasattr(self.ownerComp.parent(), 'OnEditCell'): 1180 | self.ownerComp.parent().OnEditCell(row, col, value) 1181 | 1182 | def UpdateView(self, value): 1183 | self.curState = value 1184 | if self.Settings.Type != 'toggledown': 1185 | self.PrevItem[0:4] = self.CurItem 1186 | if self.PrevItem[0] != None and self.PrevItem[1] != None: 1187 | prevItemAttribs = self.ownerComp.cellAttribs[self.PrevItem[0], 1188 | self.PrevItem[1]] 1189 | if prevItemAttribs: 1190 | prevItemAttribs.bgColor = self.ownerComp.pars('Cellcolor*') 1191 | 1192 | numCols = self.ownerComp.par.cols.eval() 1193 | col = value % numCols 1194 | row = int((value - col) / numCols) 1195 | cellAttribs = self.ownerComp.cellAttribs[row, col] 1196 | if cellAttribs: 1197 | if self.Settings.Type != 'toggledown': 1198 | cellAttribs.bgColor = self.ownerComp.pars('Cellselectcolor*') 1199 | else: 1200 | state = self.Toggles[row][col] 1201 | cellAttribs.bgColor = [self.Settings.Cellcolor, 1202 | self.Settings.Cellselectcolor][int(state)] 1203 | self.CurItem[0:4] = [row, col, value] 1204 | 1205 | def SetValue(self, value): 1206 | self.parent.SetValue(self.ownerComp, value) 1207 | pass 1208 | 1209 | class List(Element): 1210 | def __init__(self, ownerComp, updateElements=None): 1211 | super().__init__(ownerComp, updateElements) 1212 | self.cellAttribs = ownerComp.cellAttribs 1213 | self.CurItem = ownerComp.fetch('CurrentItem', [None, None, None], 1214 | search=False, storeDefault=True) 1215 | self.PrevItem = ownerComp.fetch('PrevItem', [None, None, None], 1216 | search=False, storeDefault=True) 1217 | self.parexec = self.ownerComp.op('parexec') 1218 | self.getSettings() 1219 | self.GetToggles() 1220 | self.ownerComp.par.reset.pulse() 1221 | self.undoBlockName = "RadioButton Value" 1222 | self.ownerComp.fetch('curState', self.CurItem[2], 1223 | search=False, storeDefault=True) 1224 | self.ownerComp.fetch('prevState', self.curState, 1225 | search=False, storeDefault=True) 1226 | self.prevState = self.curState 1227 | self.lselect = False 1228 | self.rselect = False 1229 | self.mselect = False 1230 | 1231 | self.textJustifyModes = [JustifyType.TOPLEFT, JustifyType.TOPCENTER, 1232 | JustifyType.TOPRIGHT, JustifyType.CENTERLEFT, 1233 | JustifyType.CENTER, JustifyType.CENTERRIGHT, 1234 | JustifyType.BOTTOMLEFT, JustifyType.BOTTOMCENTER, 1235 | JustifyType.BOTTOMRIGHT] 1236 | 1237 | @property 1238 | def ListData(self): 1239 | return self.ownerComp.storage.get('ListData', [[]]) 1240 | @ListData.setter 1241 | def ListData(self, value): 1242 | self.ownerComp.storage['ListData'] = value 1243 | 1244 | @property 1245 | def Toggles(self): 1246 | return self.ownerComp.storage.get('Toggles', [[]]) 1247 | @Toggles.setter 1248 | def Toggles(self, value): 1249 | self.ownerComp.storage['Toggles'] = value 1250 | 1251 | 1252 | def undo(self, isUndo, info): 1253 | if isUndo: 1254 | state = info[0] 1255 | else: 1256 | state = info[1] 1257 | if self.Settings.Type != 'toggledown': 1258 | self.UpdateView(state) 1259 | else: 1260 | self.Toggles[state[0]][state[1]] = state[2] 1261 | cell = state[0] * self.Settings.cols + state[1] 1262 | self.UpdateView(cell) 1263 | self.SetValue(state) 1264 | 1265 | def getSettings(self): 1266 | self.Settings = WidgetSettings() 1267 | parNames = self.parexec.par.pars.eval().replace(',', ' ') 1268 | parNames = parNames.split() 1269 | 1270 | for name in parNames: 1271 | pars = self.ownerComp.pars(name) 1272 | lenPars = len(pars) 1273 | if lenPars > 1: 1274 | value = [par.eval() for par in pars] 1275 | setattr(self.Settings, name.replace('*', ''), value) 1276 | elif lenPars == 1: 1277 | value = pars[0].eval() 1278 | setattr(self.Settings, name, value) 1279 | 1280 | def GetToggles(self, init=False): 1281 | prevToggles = self.ownerComp.storage.get('Toggles', [[]]) 1282 | default = [] 1283 | numRows = self.Settings.rows 1284 | numCols = self.Settings.cols 1285 | for i in range(numCols): 1286 | col = [] 1287 | for n in range(numRows): 1288 | if i < len(prevToggles): 1289 | if n < len(prevToggles[i]): 1290 | col.append(prevToggles[i][n]) 1291 | else: 1292 | col.append(self.ownerComp.par.Defaulttogglevalue.eval()) 1293 | else: 1294 | col.append(self.ownerComp.par.Defaulttogglevalue.eval()) 1295 | default.append(col) 1296 | 1297 | if not init and numRows != len(self.Toggles): 1298 | init = True 1299 | elif not init and len(self.Toggles) > 0: 1300 | if numRows != len(self.Toggles[0]): 1301 | init = True 1302 | if init: 1303 | self.Toggles = default 1304 | 1305 | def OnInitCell(self, row, col, attribs): 1306 | numCells = self.Settings.rows * self.Settings.cols 1307 | if isinstance(self.Settings.Labels, list): 1308 | userLabels = self.Settings.Labels 1309 | elif isinstance(self.Settings.Labels, str): 1310 | userLabels = self.Settings.Labels.replace(',', ' ').replace('[', ' ').replace(']', ' ') 1311 | userLabels = userLabels.split() 1312 | else: 1313 | userLabels = [] 1314 | lenUserLabels = len(userLabels) 1315 | indexLabels = [str(i) for i in range (lenUserLabels, 1316 | lenUserLabels + numCells)] 1317 | cell = row * self.Settings.cols + col 1318 | source = userLabels + indexLabels 1319 | if cell < len(source): 1320 | cellContent = source[cell] 1321 | attribs.text = cellContent 1322 | attribs.textColor = self.Settings.Textcolor 1323 | attribs.textJustify = getattr(JustifyType, 1324 | self.Settings.Textjustify) 1325 | attribs.textOffsetX = self.Settings.Textoffset[0] 1326 | attribs.textOffsetY = self.Settings.Textoffset[1] 1327 | attribs.fontSizeX = self.par.Fontsize 1328 | attribs.fontFace = 'Verdana' 1329 | # attribs.fontFace = "../vlib/widgets/fonts/Inter-3.13/"\ 1330 | # "Inter Desktop/Inter-Regular.otf" 1331 | 1332 | if self.Settings.Type != 'toggledown': 1333 | isSelected = int(self.CurItem[2] == cell) 1334 | attribs.bgColor = [self.Settings.Cellcolor, 1335 | self.Settings.Cellselectcolor][isSelected] 1336 | else: 1337 | if col < len(self.Toggles): 1338 | if row < len(self.Toggles[col]): 1339 | toggle = int(self.Toggles[col][row]) 1340 | else: 1341 | toggle = 0 1342 | else: 1343 | toggle = 0 1344 | 1345 | attribs.bgColor = [ 1346 | self.Settings.Cellcolor, 1347 | self.Settings.Cellselectcolor 1348 | ][toggle] 1349 | 1350 | def OnInitRow(self, row, attribs): 1351 | attribs.rowHeight = self.ownerComp.par.Rowheight 1352 | if row != 0: 1353 | attribs.topBorderOutColor = self.ownerComp.Settings.Bordercolor 1354 | 1355 | def OnInitCol(self, col, attribs): 1356 | attribs.colWidth = self.ownerComp.par.Colwidth 1357 | if col != 0: 1358 | attribs.leftBorderOutColor = self.ownerComp.Settings.Bordercolor 1359 | 1360 | def OnSelect(self, startrow, startcol, startcoords, 1361 | endrow, endcol, endcoords, start, end): 1362 | # print('start', self.panel.mselect.val, startrow, endrow, start, end) 1363 | lselect = self.panel.lselect.val 1364 | if self.panel.lselect.val: 1365 | if start: 1366 | self.lselect = True 1367 | self.OnLSelect(startrow, startcol, startcoords, 1368 | endrow, endcol, endcoords, start, end) 1369 | elif self.panel.rselect.val: 1370 | if start: 1371 | self.rselect = True 1372 | self.OnRSelect(startrow, startcol, startcoords, 1373 | endrow, endcol, endcoords, start, end) 1374 | elif self.panel.mselect.val: 1375 | if start: 1376 | self.mselect = True 1377 | self.OnMSelect(startrow, startcol, startcoords, 1378 | endrow, endcol, endcoords, start, end) 1379 | 1380 | if end: 1381 | if self.lselect: 1382 | self.OnLSelect(startrow, startcol, startcoords, 1383 | endrow, endcol, endcoords, start, end) 1384 | self.lselect = False 1385 | elif self.rselect: 1386 | self.OnRSelect(startrow, startcol, startcoords, 1387 | endrow, endcol, endcoords, start, end) 1388 | self.rselect = False 1389 | elif self.mselect: 1390 | self.OnMSelect(startrow, startcol, startcoords, 1391 | endrow, endcol, endcoords, start, end) 1392 | self.mselect = False 1393 | 1394 | def OnLSelect(self, startrow, startcol, startcoords, 1395 | endrow, endcol, endcoords, start, end): 1396 | cell = startrow * self.Settings.cols + startcol 1397 | if endrow != None and endcol != None and startrow != -1 and endrow != -1: 1398 | endCell = endrow * self.Settings.cols + endcol 1399 | 1400 | lselect = self.panel.lselect.val 1401 | if start and lselect: 1402 | self.lselect = True 1403 | 1404 | if self.Settings.Type == 'radio': 1405 | if start and self.lselect: 1406 | self.prevState = self.curState 1407 | self.UpdateView(cell) 1408 | self.SetValue(cell) 1409 | elif self.CurItem[2] != endCell and self.lselect: 1410 | self.PrevItem[0:4] = self.CurItem 1411 | prevItemAttribs = self.cellAttribs[self.PrevItem[0], 1412 | self.PrevItem[1]] 1413 | cellAttribs = self.cellAttribs[endrow, endcol] 1414 | cellAttribs.bgColor = self.Settings.Cellselectcolor 1415 | if prevItemAttribs: 1416 | prevItemAttribs.bgColor = self.Settings.Cellcolor 1417 | self.CurItem[0:4] = [endrow, endcol, endCell] 1418 | self.SetValue(endCell) 1419 | self.curState = endCell 1420 | 1421 | elif self.Settings.Type == 'exclusive': 1422 | if start and self.lselect: 1423 | self.prevState = self.curState 1424 | if cell == self.CurItem[2]: 1425 | cell = -1 1426 | self.UpdateView(cell) 1427 | self.SetValue(cell) 1428 | elif self.CurItem[2] != endCell and self.lselect: 1429 | if not end: 1430 | self.PrevItem[0:4] = self.CurItem 1431 | prevItemAttribs = self.cellAttribs[self.PrevItem[0], 1432 | self.PrevItem[1]] 1433 | cellAttribs = self.cellAttribs[endrow, endcol] 1434 | cellAttribs.bgColor = self.Settings.Cellselectcolor 1435 | if prevItemAttribs: 1436 | prevItemAttribs.bgColor = self.Settings.Cellcolor 1437 | self.CurItem[0:4] = [endrow, endcol, endCell] 1438 | self.SetValue(endCell) 1439 | self.curState = endCell 1440 | 1441 | elif self.Settings.Type == 'toggledown': 1442 | if start and self.lselect: 1443 | state = self.Toggles[startcol][startrow] 1444 | self.prevState = [startcol, startrow, state] 1445 | state = not state 1446 | self.Toggles[startcol][startrow] = state 1447 | self.UpdateView(cell) 1448 | value = [startcol, startrow, state] 1449 | self.SetValue(value) 1450 | self.curState = value 1451 | if self.doUndo: 1452 | ui.undo.startBlock(self.undoBlockName) 1453 | ui.undo.addCallback(self.undo, 1454 | [self.prevState, self.curState]) 1455 | ui.undo.endBlock() 1456 | 1457 | elif self.CurItem[2] != endCell and self.lselect: 1458 | self.PrevItem[0:4] = self.CurItem 1459 | state = self.Toggles[endcol][endrow] 1460 | self.prevState = [endcol, endrow, state] 1461 | state = not state 1462 | self.Toggles[endcol][endrow] = state 1463 | self.UpdateView(endCell) 1464 | value = [endcol, endrow, state] 1465 | self.SetValue(value) 1466 | self.curState = value 1467 | self.CurItem[0:4] = [endcol, endrow, endCell] 1468 | if self.doUndo: 1469 | ui.undo.startBlock(self.undoBlockName) 1470 | ui.undo.addCallback(self.undo, 1471 | [self.prevState, self.curState]) 1472 | ui.undo.endBlock() 1473 | 1474 | else: 1475 | if start and self.lselect: 1476 | self.prevState = self.curState 1477 | self.UpdateView(cell) 1478 | self.SetValue([cell, True]) 1479 | self.momentaryCell = cell 1480 | elif self.CurItem[2] != endCell and self.lselect: 1481 | self.PrevItem[0:4] = self.CurItem 1482 | prevItemAttribs = self.cellAttribs[self.PrevItem[0], 1483 | self.PrevItem[1]] 1484 | cellAttribs = self.cellAttribs[endrow, endcol] 1485 | cellAttribs.bgColor = self.Settings.Cellselectcolor 1486 | if prevItemAttribs: 1487 | prevItemAttribs.bgColor = self.Settings.Cellcolor 1488 | self.CurItem[0:4] = [endrow, endcol, endCell] 1489 | self.momentaryCell = endCell 1490 | self.SetValue([self.momentaryCell, True]) 1491 | if self.Settings.Type == 'momentary': 1492 | self.SetValue([self.PrevItem[2], False]) 1493 | elif end and self.lselect: 1494 | cell = -1 1495 | self.UpdateView(cell) 1496 | if self.Settings.Type == 'momentary': 1497 | self.SetValue([self.momentaryCell, False]) 1498 | 1499 | if self.doUndo and end and self.lselect and self.Settings.Type \ 1500 | not in ['pulse', 'momentary', 'toggledown']: 1501 | ui.undo.startBlock(self.undoBlockName) 1502 | ui.undo.addCallback(self.undo, [self.prevState, self.curState]) 1503 | ui.undo.endBlock() 1504 | self.lselect = False 1505 | 1506 | elif end and self.Settings.Type in ['pulse', 'momentary']: 1507 | cell = -1 1508 | self.UpdateView(cell) 1509 | if self.Settings.Type == 'momentary': 1510 | self.SetValue([self.momentaryCell, False]) 1511 | 1512 | return 1513 | 1514 | def OnRSelect(self, startrow, startcol, startcoords, 1515 | endrow, endcol, endcoords, start, end): 1516 | if hasattr(self.ownerComp.parent(), 'OnRSelectOffToOn') and start: 1517 | self.ownerComp.parent().OnRSelectOffToOn( 1518 | startrow, startcol, startcoords, 1519 | endrow, endcol, endcoords, start, end) 1520 | elif hasattr(self.ownerComp.parent(), 'OnRSelectOnToOff') and end: 1521 | self.ownerComp.parent().OnRSelectOnToOff( 1522 | startrow, startcol, startcoords, 1523 | endrow, endcol, endcoords, start, end) 1524 | 1525 | def OnMSelect(self, startrow, startcol, startcoords, 1526 | endrow, endcol, endcoords, start, end): 1527 | if hasattr(self.ownerComp.parent(), 'OnMSelectOffToOn') and start: 1528 | self.ownerComp.parent().OnMSelectOffToOn( 1529 | startrow, startcol, startcoords, 1530 | endrow, endcol, endcoords, start, end) 1531 | elif hasattr(self.ownerComp.parent(), 'OnMSelectOnToOff') and end: 1532 | self.ownerComp.parent().OnMSelectOnToOff( 1533 | startrow, startcol, startcoords, 1534 | endrow, endcol, endcoords, start, end) 1535 | if start: 1536 | self.prevrow = None 1537 | self.prevcol = None 1538 | self.row = endrow 1539 | self.col = endcol 1540 | 1541 | if self.prevrow != None and self.prevcol != None: 1542 | prevCell = self.prevrow * self.Settings.cols + self.prevcol 1543 | else: 1544 | prevCell = None 1545 | if self.row != None and self.col != None: 1546 | cell = self.row * self.Settings.cols + self.col 1547 | else: 1548 | cell = None 1549 | 1550 | if self.row != self.prevrow or self.col != self.prevcol: 1551 | if cell != None and cell >= 0: 1552 | if self.Settings.Type != 'toggledown': 1553 | state = int(cell == self.CurItem[2]) 1554 | else: 1555 | state = int(self.Toggles[self.row][self.col]) 1556 | if self.cellAttribs[self.row, self.col]: 1557 | cellAttribs = self.cellAttribs[self.row, self.col] 1558 | cellAttribs.bgColor = [self.Settings.Cellrollcolor, 1559 | self.Settings.Cellrollselectcolor][state] 1560 | 1561 | if prevCell != None and prevCell >= 0: 1562 | if self.Settings.Type != 'toggledown': 1563 | state = int(prevCell == self.CurItem[2]) 1564 | else: 1565 | state = int(self.Toggles[self.prevrow][self.prevcol]) 1566 | if self.cellAttribs[self.prevrow, self.prevcol]: 1567 | cellAttribs = self.cellAttribs[self.prevrow, self.prevcol] 1568 | cellAttribs.bgColor = [self.Settings.Cellcolor, 1569 | self.Settings.Cellselectcolor][state] 1570 | self.prevrow = self.row 1571 | self.prevcol = self.col 1572 | 1573 | def OnRollover(self, row, col, coords, prevrow, prevcol, prevcoords): 1574 | # print(row, col, coords, prevrow, prevcol, prevcoords) 1575 | if prevrow != None and prevcol != None: 1576 | prevCell = prevrow * self.Settings.cols + prevcol 1577 | else: 1578 | prevCell = None 1579 | if row != None and col != None: 1580 | cell = row * self.Settings.cols + col 1581 | else: 1582 | cell = None 1583 | 1584 | if row != prevrow or col != prevcol: 1585 | if cell != None and cell >= 0: 1586 | if self.Settings.Type != 'toggledown': 1587 | state = int(cell == self.CurItem[2]) 1588 | else: 1589 | state = int(self.Toggles[col][row]) 1590 | if self.cellAttribs[row, col]: 1591 | cellAttribs = self.cellAttribs[row, col] 1592 | cellAttribs.bgColor = [self.Settings.Cellrollcolor, 1593 | self.Settings.Cellrollselectcolor][state] 1594 | 1595 | if prevCell != None and prevCell >= 0 and len(self.Toggles) > 0: 1596 | if self.Settings.Type != 'toggledown': 1597 | state = int(prevCell == self.CurItem[2]) 1598 | else: 1599 | state = int(self.Toggles[prevcol][prevrow]) 1600 | if self.cellAttribs[prevcol, prevrow]: 1601 | cellAttribs = self.cellAttribs[prevrow, prevcol] 1602 | cellAttribs.bgColor = [self.Settings.Cellcolor, 1603 | self.Settings.Cellselectcolor][state] 1604 | 1605 | def StartEditCell(self, row, col): 1606 | self.ownerComp.setKeyboardFocus(row, col) 1607 | 1608 | def OnEditCell(self, row, col, value): 1609 | if hasattr(self.ownerComp.parent(), 'OnEditCell'): 1610 | self.ownerComp.parent().OnEditCell(row, col, value) 1611 | 1612 | def UpdateView(self, value): 1613 | self.curState = value 1614 | if self.Settings.Type != 'toggledown': 1615 | self.PrevItem[0:4] = self.CurItem 1616 | if self.PrevItem[0] != None and self.PrevItem[1] != None: 1617 | prevItemAttribs = self.ownerComp.cellAttribs[self.PrevItem[0], 1618 | self.PrevItem[1]] 1619 | if prevItemAttribs: 1620 | prevItemAttribs.bgColor = self.ownerComp.pars('Cellcolor*') 1621 | 1622 | numCols = self.ownerComp.par.cols.eval() 1623 | col = value % numCols 1624 | row = int((value - col) / numCols) 1625 | cellAttribs = self.ownerComp.cellAttribs[row, col] 1626 | if cellAttribs: 1627 | if self.Settings.Type != 'toggledown': 1628 | cellAttribs.bgColor = self.ownerComp.pars('Cellselectcolor*') 1629 | else: 1630 | state = self.Toggles[col][row] 1631 | cellAttribs.bgColor = [self.Settings.Cellcolor, 1632 | self.Settings.Cellselectcolor][int(state)] 1633 | self.CurItem[0:4] = [row, col, value] 1634 | 1635 | def SetValue(self, value): 1636 | self.parent.SetValue(self.ownerComp, value) 1637 | pass 1638 | 1639 | class Timecode(Field): 1640 | def __init__(self, ownerComp, updateElements=None): 1641 | super().__init__(ownerComp, updateElements) 1642 | self.tc = vsu.Timecode() 1643 | self.undoBlockName = "Timecode Value" 1644 | self.ownerComp.fetch('curState', self.panel.field.val, 1645 | search=False, storeDefault=True) 1646 | self.ownerComp.fetch('prevState', self.curState, 1647 | search=False, storeDefault=True) 1648 | self.prevState = self.curState 1649 | 1650 | def undo(self, isUndo, info): 1651 | if isUndo: 1652 | state = info[0] 1653 | else: 1654 | state = info[1] 1655 | self.setTimecode(state) 1656 | 1657 | def setTimecode(self, value): 1658 | timecode = self.tc.StringToTimecode(value, self.prevState) 1659 | self.UpdateView(timecode) 1660 | if self.updateElements: 1661 | [element.UpdateView(value) for element in self.updateElements] 1662 | self.parent.SetValue(self.ownerComp, timecode) 1663 | 1664 | def OnFocusOffToON(self): 1665 | self.prevState = self.curState 1666 | 1667 | def OnFocusOnToOff(self): 1668 | value = self.panel.field.val 1669 | if self.prevState != value: 1670 | self.setTimecode(value) 1671 | ui.undo.startBlock(self.undoBlockName) 1672 | ui.undo.addCallback(self.undo, [self.prevState, value]) 1673 | ui.undo.endBlock() 1674 | 1675 | def UpdateView(self, value): 1676 | self.curState = value 1677 | self.textPar.text = value 1678 | 1679 | class Operator(Field): 1680 | def __init__(self, ownerComp, updateElements=None): 1681 | super().__init__(ownerComp, updateElements) 1682 | self.tc = vsu.Timecode() 1683 | self.undoBlockName = "Operator Value" 1684 | self.ownerComp.fetch('curState', self.panel.field.val, 1685 | search=False, storeDefault=True) 1686 | self.ownerComp.fetch('prevState', self.curState, 1687 | search=False, storeDefault=True) 1688 | self.prevState = self.curState 1689 | self.opStyles = {'CHOP': 'isCHOP', 'COMP': 'isCOMP', 'DAT': 'isDAT', 1690 | 'MAT': 'isMAT', 'object': 'isObject', 1691 | 'panel': 'isPanel', 'SOP': 'isSOP', 'TOP': 'isTOP'} 1692 | self.opTypes = [CHOP, COMP, DAT, MAT, SOP, TOP] 1693 | self.subTypes = ['panel', 'object'] 1694 | 1695 | @property 1696 | def Type(self): 1697 | return self.ownerComp.par.Type.eval() 1698 | 1699 | @property 1700 | def fromOP(self): 1701 | return self.ownerComp.par.Pathrelativeto.eval() 1702 | 1703 | def undo(self, isUndo, info): 1704 | if isUndo: 1705 | state = info[0] 1706 | else: 1707 | state = info[1] 1708 | if isinstance(state, OP): 1709 | self.setOP(state.path) 1710 | 1711 | def OnDrop(self, args): 1712 | style = args[5] 1713 | if style in self.opStyles.keys(): 1714 | path = f"{args[6]}/{args[0]}" 1715 | self.setOP(path) 1716 | 1717 | def setOP(self, path): 1718 | if op(path) != None: 1719 | _op = op(path) 1720 | elif self.fromOP != None: 1721 | if self.fromOP.op(path) != None: 1722 | _op = self.fromOP.op(path) 1723 | else: 1724 | _op = None 1725 | else: 1726 | _op = None 1727 | 1728 | if _op: 1729 | setValue = False 1730 | _type = self.Type 1731 | if _type == 'OP': 1732 | setValue = True 1733 | else: 1734 | setValue = getattr(_op, self.opStyles[_type]) 1735 | if setValue: 1736 | viewPath = _op.path 1737 | if self.fromOP: 1738 | viewPath = _op.path.replace(f"{self.fromOP.path}/", '') 1739 | self.UpdateView(viewPath, checkPath=False) 1740 | self.parent.SetValue(self.ownerComp, _op) 1741 | self.curState = _op 1742 | ui.undo.startBlock(self.undoBlockName) 1743 | ui.undo.addCallback(self.undo, [self.prevState, _op]) 1744 | ui.undo.endBlock() 1745 | self.prevState = self.curState 1746 | return True 1747 | else: 1748 | self.UpdateView('') 1749 | self.parent.SetValue(self.ownerComp, None) 1750 | return False 1751 | 1752 | def OnFocusOffToON(self): 1753 | pass 1754 | 1755 | def OnFocusOnToOff(self): 1756 | self.setOP(self.panel.field.val) 1757 | 1758 | def UpdateView(self, value, checkPath=True): 1759 | if checkPath and value != None and value != '': 1760 | if self.fromOP: 1761 | value = value.path.replace(f"{self.fromOP.path}/", '') 1762 | self.curState = value 1763 | self.textPar.text = value 1764 | 1765 | class ColorSwatch(Element): 1766 | def __init__(self, ownerComp, updateElements=None): 1767 | super().__init__(ownerComp, updateElements) 1768 | self.BtnCols = [self.ownerComp.pars('Buttoncolcolor*'), 1769 | self.ownerComp.pars('Buttoncolroll*'), 1770 | self.ownerComp.pars('Buttoncolsel*'), 1771 | self.ownerComp.pars('Buttoncolon*')] 1772 | self.BtnCol = tdu.Dependency(self.BtnCols[0]) 1773 | self.swatchColPars = self.ownerComp.pars('Swatchcolor*') 1774 | 1775 | @property 1776 | def swatchCol(self): 1777 | return [par.eval() for par in self.swatchColPars] 1778 | 1779 | def setColor(self): 1780 | index = min(3, 1781 | (self.panel.rollover + self.panel.state * 2) + self.panel.lselect) 1782 | self.BtnCol.val = self.BtnCols[index] 1783 | 1784 | def OnRollOffToOn(self, value): 1785 | self.setColor() 1786 | 1787 | def OnRollOnToOff(self, value): 1788 | self.setColor() 1789 | 1790 | def OnLSelect(self, value): 1791 | self.parent.OpenColorPicker(self.swatchCol) 1792 | pass 1793 | 1794 | # def UpdateView(self, value=None): 1795 | # self.setColor() 1796 | 1797 | class ColorSwatches(Element): 1798 | def __init__(self, ownerComp, updateElements=None): 1799 | super().__init__(ownerComp, updateElements) 1800 | self.cellAttribs = ownerComp.cellAttribs 1801 | self.CurItem = ownerComp.fetch('CurrentItem', [None, None, None], 1802 | search=False, storeDefault=True) 1803 | self.PrevItem = ownerComp.fetch('PrevItem', [None, None, None], 1804 | search=False, storeDefault=True) 1805 | self.parexec = self.ownerComp.op('parexec') 1806 | self.getSettings() 1807 | self.getSwatchesData() 1808 | self.ownerComp.par.reset.pulse() 1809 | self.undoStoreBlockName = "Store Swatch" 1810 | self.ownerComp.fetch('curState', [[0.0, 0.0, 0.0, 1.0], 0, 0], 1811 | search=False, storeDefault=True) 1812 | self.ownerComp.fetch('prevState', self.curState, 1813 | search=False, storeDefault=True) 1814 | self.prevState = self.curState 1815 | self.ctrl = False 1816 | 1817 | def undoStoreSwatch(self, isUndo, info): 1818 | if isUndo: 1819 | state = info[0] 1820 | else: 1821 | state = info[1] 1822 | self.curState = state 1823 | self.Swatches[state[1]][state[2]] = state[0][:5] 1824 | # self.cellAttribs[state[1], state[2]] 1825 | # if self.cellAttribs: 1826 | # self.cellAttribs = state[0] 1827 | self.ownerComp.par.reset.pulse() 1828 | 1829 | def getSwatchesData(self, init=False): 1830 | default = [] 1831 | for row in range(self.par.rows.eval()): 1832 | row = [] 1833 | for col in range(self.par.cols.eval()): 1834 | row.append([0.0, 0.0, 0.0, 1.0]) 1835 | default.append(row) 1836 | if not init: 1837 | self.Swatches = self.ownerComp.fetch('Swatches', default, 1838 | search=False, storeDefault=True) 1839 | else: 1840 | self.ownerComp.store('Swatches', default) 1841 | self.Swatches = self.ownerComp.fetch('Swatches', search=False) 1842 | 1843 | def getSettings(self): 1844 | self.Settings = WidgetSettings() 1845 | parNames = self.parexec.par.pars.eval().replace(',', ' ') 1846 | parNames = parNames.split() 1847 | 1848 | for name in parNames: 1849 | pars = self.ownerComp.pars(name) 1850 | lenPars = len(pars) 1851 | if lenPars > 1: 1852 | value = [par.eval() for par in pars] 1853 | setattr(self.Settings, name.replace('*', ''), value) 1854 | elif lenPars == 1: 1855 | value = pars[0].eval() 1856 | setattr(self.Settings, name, value) 1857 | 1858 | def UpdateView(self, value): 1859 | self.ownerComp.par.reset.pulse() 1860 | 1861 | def OnSelect(self, startrow, startcol, startcoords, 1862 | endrow, endcol, endcoords, start, end): 1863 | if start: 1864 | color = self.Swatches[startrow][startcol][:-1] + [1.0] 1865 | sel = [color[0] + 0.1, color[1] + 0.1, color[2] + 0.1, 1.0] 1866 | if startrow != None and startcol != None: 1867 | cellAttribs = self.cellAttribs[startrow, startcol] 1868 | cellAttribs.bgColor = sel 1869 | if self.panel.lselect and self.panel.ctrl: 1870 | self.ctrl = True 1871 | self.prevState = [self.Swatches[startrow][startcol][:5], 1872 | startrow, startcol] 1873 | self.OnCtrlLSelectSwatch(startrow, startcol) 1874 | elif self.panel.lselect: 1875 | self.parent.SetPrevState() 1876 | self.OnLSelectSwatch(startrow, startcol) 1877 | if end: 1878 | color = self.Swatches[startrow][startcol][:-1] + [1.0] 1879 | if startrow != None and startcol != None: 1880 | cellAttribs = self.cellAttribs[startrow, startcol] 1881 | roll = [color[0] + 0.05, color[1] + 0.05, color[2] + 0.05, 1.0] 1882 | cellAttribs.bgColor = roll 1883 | if self.ctrl: 1884 | self.ctrl = False 1885 | self.curState = [self.Swatches[startrow][startcol][:5], 1886 | startrow, startcol] 1887 | ui.undo.startBlock(self.undoStoreBlockName) 1888 | ui.undo.addCallback(self.undoStoreSwatch, 1889 | [self.prevState, self.curState]) 1890 | ui.undo.endBlock() 1891 | else: 1892 | self.parent.AddSelectSwatchCallback() 1893 | 1894 | def OnLSelectOffToOn(self): 1895 | # if self.panel.ctrl.val: 1896 | # self.ctrl = True 1897 | # else: 1898 | # self.parent.SetPrevState() 1899 | pass 1900 | 1901 | def OnLSelectOnToOff(self): 1902 | # if self.ctrl: 1903 | # self.ctrl = False 1904 | 1905 | # else: 1906 | # self.parent.AddSelectSwatchCallback() 1907 | pass 1908 | 1909 | def OnRSelectOffToOn(self): 1910 | pass 1911 | 1912 | def OnLSelectSwatch(self, row, col): 1913 | value = self.Swatches[row][col] 1914 | self.parent.OnLSelectSwatch(row, col) 1915 | pass 1916 | 1917 | def OnCtrlLSelectSwatch(self, row, col): 1918 | value = self.Swatches[row][col] 1919 | self.parent.OnCtrlLSelectSwatch(row, col) 1920 | pass 1921 | 1922 | class ColorWheel(Element): 1923 | def __init__(self, ownerComp, updateElements=None): 1924 | super().__init__(ownerComp, updateElements) 1925 | self.wheel = ownerComp.op('wheel') 1926 | self.getHS() 1927 | self.getUV(self.h, self.s) 1928 | # self.lSelect = False 1929 | self.undoBlockName = "ColorWheel Values" 1930 | self.ownerComp.fetch('curState', [self.h, self.s], 1931 | search=False, storeDefault=True) 1932 | self.ownerComp.fetch('prevState', self.curState, 1933 | search=False, storeDefault=True) 1934 | self.prevState = self.curState 1935 | 1936 | @property 1937 | def U(self): 1938 | return self.u.val 1939 | @U.setter 1940 | def U(self, value): 1941 | self.u = tdu.Dependency(value) 1942 | 1943 | @property 1944 | def V(self): 1945 | return self.v.val 1946 | @V.setter 1947 | def V(self, value): 1948 | self.v = tdu.Dependency(value) 1949 | 1950 | def undo(self, isUndo, info): 1951 | if isUndo: 1952 | state = info[0] 1953 | else: 1954 | state = info[1] 1955 | self.getUV(*state) 1956 | self.parent.OnPick(state) 1957 | self.curState = state 1958 | 1959 | def pickColor(self): 1960 | self.parent.OnPick(self.getHS()) 1961 | self.getUV(self.h, self.s) 1962 | self.curState = [self.h, self.s] 1963 | 1964 | def getHS(self): 1965 | u = self.panel.u 1966 | v = self.panel.v 1967 | x = 2.0 * u - 1.0 1968 | y = 2.0 * v - 1.0 1969 | self.theta = -math.atan2(-x, y) 1970 | self.r = math.sqrt(math.pow(x, 2.0) + math.pow(y, 2.0)) 1971 | self.h = (self.theta + PI) / (PI2) 1972 | self.s = min(1.0, self.r) 1973 | return [self.h, self.s] 1974 | 1975 | def getUV(self, h, s): 1976 | s = min(1.0, max(0.0, s)) 1977 | self.theta = -(h * PI2) - .5 * PI 1978 | self.r = s 1979 | x = self.r * math.cos(self.theta) 1980 | y = self.r * math.sin(self.theta) 1981 | self.U = x * .5 + .5 1982 | self.V = y * .5 + .5 1983 | 1984 | def OnLSelectOnToOff(self, value): 1985 | # self.lSelect = True 1986 | self.prevState = self.curState 1987 | self.pickColor() 1988 | 1989 | def OnLSelectOffToOn(self, value): 1990 | # self.lSelect = True 1991 | self.pickColor() 1992 | ui.undo.startBlock(self.undoBlockName) 1993 | ui.undo.addCallback(self.undo, [self.prevState, [self.h, self.s]]) 1994 | ui.undo.endBlock() 1995 | 1996 | def OnUV(self, value): 1997 | self.pickColor() 1998 | 1999 | def UpdateView(self, value): 2000 | self.curState = value 2001 | self.getUV(*value) 2002 | 2003 | class ColorPicker(Element): 2004 | def __init__(self, ownerComp, updateElements=None): 2005 | super().__init__(ownerComp, updateElements) 2006 | self.wheel = ownerComp.op('wheel') 2007 | self.rgba = ownerComp.op('rgba') 2008 | self.hsv = ownerComp.op('hsv') 2009 | self.value = ownerComp.op('value') 2010 | self.hex = ownerComp.op('hex') 2011 | self.swatch = ownerComp.op('swatch') 2012 | self.swatchColPars = self.swatch.pars('Swatchcolor*') 2013 | self.swatches = ownerComp.op('swatches') 2014 | self.swatchesColors = self.swatches.fetch('Swatches') 2015 | self.ColorWidget = None 2016 | self.color = self.swatchCol 2017 | self.hsva = [0.0, 0.0, 0.0, 1.0] 2018 | self.window = ownerComp.op('window') 2019 | self.mode = 'RGBA' 2020 | self.disableAlpha = ownerComp.op('disableAlpha') 2021 | self._rgbaMode = tdu.Dependency(ownerComp.op('rgbaMode/Button').panel.state.val) 2022 | 2023 | self.undoSwatchesSelectBlockName = "Select Swatch" 2024 | self.ownerComp.fetch('prevState', self.hsva[:5], 2025 | search=False, storeDefault=True) 2026 | self.prevState = self.hsva[:5] 2027 | 2028 | @property 2029 | def swatchCol(self): 2030 | return [par.eval() for par in self.swatchColPars] 2031 | 2032 | @property 2033 | def IsOpen(self): 2034 | return self.window.isOpen 2035 | def OpenColorPicker(self): 2036 | pass 2037 | 2038 | @property 2039 | def _rgba(self): 2040 | return list(colorsys.hsv_to_rgb(*self.hsva[:-1])) + self.hsva[3:4] 2041 | 2042 | @property 2043 | def RgbaMode(self): 2044 | return self._rgbaMode.val 2045 | 2046 | @RgbaMode.setter 2047 | def RgbaMode(self, value): 2048 | self._rgbaMode.val = value 2049 | self.UpdateViews() 2050 | 2051 | def undoSelectSwatch(self, isUndo, info): 2052 | if isUndo: 2053 | state = info[0] 2054 | else: 2055 | state = info[1] 2056 | self.hsva = state 2057 | self.UpdateViews() 2058 | 2059 | def SetPrevState(self): 2060 | self.prevState = self.hsva[:5] 2061 | 2062 | def AddSelectSwatchCallback(self): 2063 | ui.undo.startBlock(self.undoSwatchesSelectBlockName) 2064 | ui.undo.addCallback(self.undoSelectSwatch, 2065 | [self.prevState, self.hsva[:5]]) 2066 | ui.undo.endBlock() 2067 | 2068 | def setMode(self): 2069 | tags = self.ColorWidget.tags 2070 | if 'RGBA' in tags: 2071 | self.mode = 'RGBA' 2072 | self.disableAlpha.par.display = False 2073 | else: 2074 | self.mode = 'RGB' 2075 | self.disableAlpha.par.display = True 2076 | 2077 | def setHsva(self, rgba): 2078 | self.hsva = (list(colorsys.rgb_to_hsv(*rgba[:-1])) + rgba[3:4]) 2079 | 2080 | def undoLinkColorPicker(self, isUndo, info): 2081 | if isUndo: 2082 | state = info[0] 2083 | else: 2084 | state = info[1] 2085 | if state[0] != None: 2086 | self.linkColorPicker(state[0], state[1]) 2087 | 2088 | def linkColorPicker(self, colorWidget, color): 2089 | prevCol = self._rgba if self.mode == 'RGBA' else self._rgba[:-1] 2090 | self.prevColorWidgetInfo = [self.ColorWidget, prevCol] 2091 | self.ColorWidget = colorWidget 2092 | self.setMode() 2093 | if self.mode == 'RGBA': 2094 | rgba = color 2095 | else: 2096 | rgba = color + [1.0] 2097 | self.setHsva(rgba) 2098 | self.UpdateViews(fromView=self.ColorWidget) 2099 | 2100 | def Open(self, colorWidget, color): 2101 | self.linkColorPicker(colorWidget, color) 2102 | info = [self.prevColorWidgetInfo, [colorWidget, color]] 2103 | ui.undo.startBlock('_HISTORY_HIDE_ Link Color Picker') 2104 | ui.undo.addCallback(self.undoLinkColorPicker, info) 2105 | ui.undo.endBlock() 2106 | self.window.par.winopen.pulse() 2107 | 2108 | def UpdateColor(self, cp, value, fromView=None): 2109 | rgba = self._rgba 2110 | rgba['rgba'.index(cp)] = value 2111 | self.setHsva(rgba) 2112 | self.UpdateViews(fromView) 2113 | 2114 | def OnValue(self, value): 2115 | self.hsva[2] = value 2116 | self.UpdateViews(self.value) 2117 | 2118 | def OnHex(self, value): 2119 | rgba = self.hexToRgba(value) 2120 | self.setHsva(rgba) 2121 | self.UpdateViews(self.hex) 2122 | # print(value) 2123 | 2124 | def OnHsv(self, cp, value): 2125 | if cp == 'h': 2126 | value /= 360.0 2127 | self.hsva['hsv'.index(cp)] = value 2128 | self.UpdateViews(self.hsv) 2129 | 2130 | def OnRgba(self, cp, value): 2131 | if self.RgbaMode: 2132 | value = value / 255.0 2133 | rgba = self._rgba 2134 | rgba['rgba'.index(cp)] = value 2135 | self.setHsva(rgba) 2136 | self.UpdateViews(self.rgba) 2137 | 2138 | def OnPick(self, value): 2139 | self.hsva[0:-2] = value 2140 | self.UpdateViews(self.wheel) 2141 | 2142 | def OnLSelectSwatch(self, row, col): 2143 | rgba = self.swatchesColors[row][col] 2144 | self.setHsva(rgba) 2145 | self.UpdateViews() 2146 | 2147 | def OnCtrlLSelectSwatch(self, row, col): 2148 | self.swatchesColors[row][col] = self._rgba 2149 | 2150 | def UpdateViews(self, fromView=None): 2151 | rgba = self._rgba 2152 | for i,cp in enumerate(rgba): 2153 | self.swatchColPars[i].val = cp 2154 | if fromView != self.ColorWidget: 2155 | if self.ColorWidget: 2156 | if self.mode == 'RGBA': 2157 | color = rgba 2158 | else: 2159 | color = rgba[:-1] 2160 | self.setValue(color) 2161 | if fromView != self.rgba: 2162 | _rgba = list(rgba) 2163 | if self.RgbaMode: 2164 | _rgba[0] = int(rgba[0] * 255.0) 2165 | _rgba[1] = int(rgba[1] * 255.0) 2166 | _rgba[2] = int(rgba[2] * 255.0) 2167 | _rgba[3] = int(rgba[3] * 255.0) 2168 | self.rgba.UpdateView(None, _rgba) 2169 | if fromView != self.hsv: 2170 | hsv = list(self.hsva[:-1]) 2171 | hsv[0] *= 360 2172 | self.hsv.UpdateView(None, hsv) 2173 | if fromView != self.wheel: 2174 | self.wheel.UpdateView(self.hsva[:-2]) 2175 | if fromView != self.value: 2176 | self.value.UpdateView(self.hsva[2]) 2177 | if fromView != self.hex: 2178 | self.hex.UpdateView(self.rgbToHex(rgba[:-1])) 2179 | pass 2180 | 2181 | def setValue(self, color): 2182 | for i,col in enumerate(color): 2183 | VSN.SetVarAttr(self.ColorWidget.ext.WidgetRGBAExt.plugin, 2184 | self.ColorWidget.ext.WidgetRGBAExt.var.name, 2185 | 'rgba'[i], col) 2186 | 2187 | def rgbToHex(self, rgb): 2188 | hexR = "%0.2X" % int(rgb[0] * 255) 2189 | hexG = "%0.2X" % int(rgb[1] * 255) 2190 | hexB = "%0.2X" % int(rgb[2] * 255) 2191 | hexStr = hexR + hexG + hexB 2192 | # _hex& 0xFFFFFF 2193 | return hexStr 2194 | 2195 | def hexToRgba(self, _hex): 2196 | hexColors = [_hex[i:i+2] for i in range(0, len(_hex), 2)] 2197 | rgba = [0.0, 0.0, 0.0] + self.hsva[3:] 2198 | rgba[0] = float.fromhex(hexColors[0]) / 255.0 2199 | rgba[1] = float.fromhex(hexColors[1]) / 255.0 2200 | rgba[2] = float.fromhex(hexColors[2]) / 255.0 2201 | return rgba 2202 | 2203 | class ContextMenu(Element): 2204 | def __init__(self, ownerComp, updateElements=None): 2205 | super().__init__(ownerComp, updateElements) 2206 | self.view = ownerComp.op('view') 2207 | self.window = ownerComp.op('window') 2208 | self.list = ownerComp.op('list') 2209 | self.absMouse = ownerComp.par.Absolutemousechop.eval() 2210 | self.menuItems = [] 2211 | self.menuLabels = [] 2212 | self._CopiedValue = [] 2213 | self.obj = None 2214 | self.args = None 2215 | pass 2216 | 2217 | # TODO put this on Widgets or Vision? 2218 | @property 2219 | def CopiedValue(self): 2220 | return self._CopiedValue 2221 | @CopiedValue.setter 2222 | def CopiedValue(self, value): 2223 | self._CopiedValue = value 2224 | 2225 | def OnLSelectOnToOff(self): 2226 | itemIndex = int(self.list.panel.celloverid) 2227 | if itemIndex != -1: 2228 | self.DroplistWidget.OnSelectItem(itemIndex) 2229 | # crashing TD bug... 2230 | # self.window.par.winclose.pulse() 2231 | run("args[0].pulse()", self.window.par.winclose, delayFrames=3) 2232 | 2233 | def OpenMenu(self, obj, menuItems, *args): 2234 | self.obj = obj 2235 | self.args = args 2236 | self.menuItems = menuItems 2237 | self.menuLabels = [item['label'] for item in menuItems] 2238 | 2239 | self.list.Settings.Labels = self.menuLabels 2240 | self.list.par.rows = len(self.menuLabels) 2241 | self.list.par.reset.pulse() 2242 | self.view.setFocus() 2243 | self.window.par.winopen.pulse() 2244 | 2245 | def SetValue(self, element, value): 2246 | itemIndex = value[0] 2247 | self.menuItems[itemIndex]['func'](*self.args) 2248 | 2249 | # TODO add undo block 2250 | class Dial(Element): 2251 | def __init__(self, ownerComp, updateElements=None): 2252 | super().__init__(ownerComp, updateElements) 2253 | self.over = ownerComp.op('over1') 2254 | self.bgCols = self.GetColors('Dialcolbg', 'Dialcolroll') 2255 | self.knobSwitch = ownerComp.op('switch') 2256 | self.detailLevel = WIDGETS.fetch('detailLevel', 2257 | {'fine': 1.0, 'init': 1.0, 'prev': 1.0}, 2258 | storeDefault=True, search=False,) 2259 | 2260 | self.clampAngleLow = -50 2261 | self.clampAngleHigh = 230 2262 | 2263 | def getDetailValue(self, value): 2264 | return (self.detailLevel['prev'] 2265 | + (value - self.detailLevel['init']) 2266 | * self.detailLevel['fine']) 2267 | 2268 | def OnLSelectOffToOn(self): 2269 | self.detailLevel['init'] = self.getAngle() 2270 | fine = 1.0 2271 | if self.panel.alt.val and self.panel.ctrl.val: 2272 | fine = .001 2273 | elif self.panel.alt.val: 2274 | fine = .01 2275 | elif self.panel.ctrl.val: 2276 | fine = .1 2277 | self.detailLevel['fine'] = fine 2278 | self.par.mouserel = True 2279 | self.panelexecDat.par.valuechange = True 2280 | self.knobSwitch.par.index = 1 2281 | 2282 | def OnLSelectOnToOff(self): 2283 | self.panelexecDat.par.valuechange = False 2284 | self.par.mouserel = False 2285 | value = self.getAngle() 2286 | value = self.getDetailValue(value) 2287 | self.detailLevel['prev'] = value 2288 | self.detailLevel['fine'] = 1.0 2289 | self.knobSwitch.par.index = 0 2290 | 2291 | 2292 | def getAngle(self): 2293 | if self.par.Rotmode == 'RADIAL': 2294 | u = self.panel.trueu.val 2295 | v = self.panel.truev.val 2296 | angle = math.degrees(math.atan2(u,v)) + 100 2297 | angle = min(self.clampAngleHigh, max(self.clampAngleLow, angle)) 2298 | elif self.par.Rotmode == 'VERT': 2299 | angle = self.panel.truev.val * 40 2300 | else: 2301 | angle = self.panel.trueu.val * 40 2302 | return angle 2303 | 2304 | def OnTrueuv(self, value): 2305 | range1 = self.par.Range1.eval() 2306 | range2 = self.par.Range2.eval() 2307 | Type = self.par.Type 2308 | value = self.getAngle() 2309 | if self.par.Rotmode != 'RADIAL': 2310 | value = self.getDetailValue(value) 2311 | self.UpdateView(value, rangeValue=False) 2312 | value = self.RangeVal(self.clampAngleLow, self.clampAngleHigh, 2313 | range1, range2, value, Type) 2314 | if self.par.Clamp: 2315 | value = min(range2, max(range1, value)) 2316 | if self.updateElements: 2317 | [element.UpdateView(value) for element in self.updateElements] 2318 | self.parent.SetValue(self.ownerComp, value) 2319 | 2320 | def UpdateView(self, value, rangeValue=True): 2321 | if rangeValue: 2322 | range1 = self.par.Range1 2323 | range2 = self.par.Range2 2324 | value = self.RangeVal(range1, range2, self.clampAngleLow, 2325 | self.clampAngleHigh, value, 'Float') 2326 | self.detailLevel['prev'] = value 2327 | 2328 | if self.par.Clamp: 2329 | value = min(self.clampAngleHigh, max(self.clampAngleLow, value)) 2330 | self.over.par.self.r = value 2331 | 2332 | # TODO add undo block 2333 | class SpinBox(Element): 2334 | def __init__(self, ownerComp, updateElements=None): 2335 | super().__init__(ownerComp, updateElements) 2336 | self.spinSwitch = ownerComp.op('switch') 2337 | self.multOffset = ownerComp.op('multOffset') 2338 | self.bgCols = self.GetColors('Slidercolbg', 'Slidercolroll') 2339 | self.knobCols = self.GetColors('Knobcolbg', 'Knobcolsel') 2340 | self.detailLevel = ownerComp.fetch('detailLevel', 2341 | {'fine': 1.0, 'init': 1.0, 'prev': 1.0}, 2342 | storeDefault=True, search=False,) 2343 | self.prevValue = self.panel.truev.val 2344 | 2345 | def getDetailValue(self, value): 2346 | return (self.detailLevel['prev'] 2347 | + (value - self.detailLevel['init']) 2348 | * self.detailLevel['fine']) 2349 | 2350 | def OnLSelectOffToOn(self): 2351 | fine = 1.0 2352 | self.par.mouserel = True 2353 | self.detailLevel['init'] = self.panel.truev.val * self.par.Relposscale 2354 | if self.panel.alt.val and self.panel.ctrl.val: 2355 | self.par.mouserel = True 2356 | fine = .001 2357 | elif self.panel.alt.val: 2358 | self.par.mouserel = True 2359 | fine = .01 2360 | elif self.panel.ctrl.val: 2361 | self.par.mouserel = True 2362 | fine = .1 2363 | self.detailLevel['fine'] = fine 2364 | self.spinSwitch.par.index = 1 2365 | 2366 | def OnLSelectOnToOff(self): 2367 | self.par.mouserel = False 2368 | value = self.panel.truev.val * self.par.Relposscale 2369 | value = self.getDetailValue(value) 2370 | if self.par.Clamp: 2371 | value = min(1, max(0, value)) 2372 | self.detailLevel['prev'] = value 2373 | self.detailLevel['fine'] = 1.0 2374 | self.spinSwitch.par.index = 0 2375 | 2376 | def OnTruev(self, value): 2377 | range1 = self.par.Range1.eval() 2378 | range2 = self.par.Range2.eval() 2379 | Type = self.par.Type 2380 | value *= self.par.Relposscale 2381 | value = self.getDetailValue(value) 2382 | if self.par.Clamp: 2383 | value = min(1, max(0, value)) 2384 | self.UpdateView(value, rangeValue=False) 2385 | value = self.ExpandVal(range1, range2, value, Type) 2386 | if self.updateElements: 2387 | [element.UpdateView(value) for element in self.updateElements] 2388 | 2389 | if value != self.prevValue: 2390 | self.spinSwitch.par.index = int(value > self.prevValue) + 1 2391 | self.parent.SetValue(self.ownerComp, value) 2392 | self.prevValue = value 2393 | 2394 | def UpdateView(self, value, rangeValue=True): 2395 | if not rangeValue: 2396 | self.multOffset.par.ty = value 2397 | else: 2398 | range1 = self.par.Range1 2399 | range2 = self.par.Range2 2400 | value = self.ownerComp.NormalizeVal(range1, range2, value) 2401 | self.detailLevel['prev'] = value 2402 | 2403 | def SetValue(widget, value): 2404 | # placeholder function so widget inside of Widgets function have 2405 | # a function to call when testing them 2406 | # print(widget, value) 2407 | pass 2408 | --------------------------------------------------------------------------------