├── hotbox_designer
├── designer
│ ├── __init__.py
│ ├── highlighter.py
│ ├── menu.py
│ ├── editarea.py
│ └── application.py
├── vendor
│ └── __init__.py
├── resources
│ ├── icons
│ │ ├── new.png
│ │ ├── addbg.png
│ │ ├── copy.png
│ │ ├── edit.png
│ │ ├── link.png
│ │ ├── ontop.png
│ │ ├── open.png
│ │ ├── paste.png
│ │ ├── play.png
│ │ ├── redo.png
│ │ ├── save.png
│ │ ├── snap.png
│ │ ├── touch.png
│ │ ├── undo.png
│ │ ├── addtext.png
│ │ ├── center.png
│ │ ├── delete.png
│ │ ├── movedown.png
│ │ ├── moveup.png
│ │ ├── onbottom.png
│ │ ├── picker.png
│ │ ├── reload.png
│ │ ├── unlink.png
│ │ ├── addbutton.png
│ │ ├── manager-edit.png
│ │ ├── manager-new.png
│ │ ├── manager-delete.png
│ │ ├── manager-export.png
│ │ └── manager-import.png
│ └── templates
│ │ ├── marking1.json
│ │ ├── marking2.json
│ │ ├── actions.json
│ │ ├── context.json
│ │ └── circles.json
├── __init__.py
├── commands.py
├── qtutils.py
├── arrayutils.py
├── languages.py
├── data.py
├── templates.py
├── painting.py
├── interactive.py
├── widgets.py
├── dialog.py
├── colorwheel.py
├── reader.py
├── applications.py
└── geometry.py
├── .gitignore
├── documentation
├── hotbox.gif
├── heditor.jpg
└── manager2.jpg
├── LICENSE
├── TODO
└── readme.md
/hotbox_designer/designer/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/hotbox_designer/vendor/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | __pycache__
3 | .vscode/.ropeproject/config.py
4 | .vscode/
5 |
--------------------------------------------------------------------------------
/documentation/hotbox.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/documentation/hotbox.gif
--------------------------------------------------------------------------------
/documentation/heditor.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/documentation/heditor.jpg
--------------------------------------------------------------------------------
/documentation/manager2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/documentation/manager2.jpg
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/new.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/addbg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/addbg.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/copy.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/edit.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/link.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/ontop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/ontop.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/open.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/paste.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/paste.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/play.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/redo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/redo.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/save.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/snap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/snap.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/touch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/touch.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/undo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/undo.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/addtext.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/addtext.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/center.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/center.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/delete.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/movedown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/movedown.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/moveup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/moveup.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/onbottom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/onbottom.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/picker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/picker.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/reload.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/reload.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/unlink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/unlink.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/addbutton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/addbutton.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/manager-edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/manager-edit.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/manager-new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/manager-new.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/manager-delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/manager-delete.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/manager-export.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/manager-export.png
--------------------------------------------------------------------------------
/hotbox_designer/resources/icons/manager-import.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckylyk/hotbox_designer/HEAD/hotbox_designer/resources/icons/manager-import.png
--------------------------------------------------------------------------------
/hotbox_designer/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | from hotbox_designer.reader import HotboxWidget
4 | from hotbox_designer.data import load_templates, load_json
5 | from hotbox_designer.manager import (
6 | launch_manager, initialize, show, hide, switch, load_hotboxes)
--------------------------------------------------------------------------------
/hotbox_designer/commands.py:
--------------------------------------------------------------------------------
1 |
2 | OPEN_COMMAND = """\
3 | import hotbox_designer
4 | from hotbox_designer import applications
5 | hotbox_designer.initialize(applications.{application}())
6 | hotbox_designer.show('{name}')
7 | """
8 |
9 | CLOSE_COMMAND = """\
10 | import hotbox_designer
11 | hotbox_designer.hide('{name}')
12 | """
13 |
14 | SWITCH_COMMAND = """\
15 | import hotbox_designer
16 | from hotbox_designer import applications
17 | hotbox_designer.initialize(applications.{application}())
18 | hotbox_designer.switch('{name}')
19 | """
20 |
--------------------------------------------------------------------------------
/hotbox_designer/qtutils.py:
--------------------------------------------------------------------------------
1 | import os
2 | from hotbox_designer.vendor.Qt import QtGui, QtWidgets, QtCore
3 |
4 |
5 | VALIGNS = {
6 | 'top': QtCore.Qt.AlignTop,
7 | 'center': QtCore.Qt.AlignVCenter,
8 | 'bottom': QtCore.Qt.AlignBottom}
9 | HALIGNS = {
10 | 'left': QtCore.Qt.AlignLeft,
11 | 'center': QtCore.Qt.AlignHCenter,
12 | 'right': QtCore.Qt.AlignRight}
13 | ICONDIR = os.path.dirname(__file__)
14 |
15 |
16 | def icon(filename):
17 | return QtGui.QIcon(os.path.join(ICONDIR, 'resources', 'icons', filename))
18 |
19 |
20 | def get_cursor(widget):
21 | return widget.mapFromGlobal(QtGui.QCursor.pos())
22 |
23 |
24 | def set_shortcut(keysequence, parent, method):
25 | shortcut = QtWidgets.QShortcut(QtGui.QKeySequence(keysequence), parent)
26 | shortcut.activated.connect(method)
27 |
--------------------------------------------------------------------------------
/hotbox_designer/arrayutils.py:
--------------------------------------------------------------------------------
1 |
2 | def move_elements_to_array_end(array, elements):
3 | return [e for e in array if e not in elements] + [e for e in elements]
4 |
5 |
6 | def move_elements_to_array_begin(array, elements):
7 | return [e for e in elements] + [e for e in array if e not in elements]
8 |
9 |
10 | def move_up_array_elements(array, elements):
11 | for element in reversed(array):
12 | if element not in elements:
13 | continue
14 | index = array.index(element)
15 | if index == len(array):
16 | continue
17 | array.insert(index + 2, element)
18 | array.pop(index)
19 |
20 |
21 | def move_down_array_elements(array, elements):
22 | for shape in array:
23 | if shape not in elements:
24 | continue
25 | index = array.index(shape)
26 | if index == 0:
27 | continue
28 | array.pop(index)
29 | array.insert(index - 1, shape)
30 |
--------------------------------------------------------------------------------
/hotbox_designer/languages.py:
--------------------------------------------------------------------------------
1 |
2 | PYTHON = 'python'
3 | MEL = 'mel'
4 | NUKE_TCL = 'nuke tcl'
5 | NUKE_EXPRESSION = 'nuke expression'
6 | HSCRIPT = 'houdini script'
7 | RUMBA_SCRIPT = 'rumba script'
8 |
9 |
10 | def execute_code(language, code):
11 | return EXECUTORS[language](code)
12 |
13 |
14 | def execute_python(code):
15 | exec(code, globals())
16 |
17 |
18 | def execute_mel(code):
19 | from maya import mel
20 | mel.eval(code.replace(u'\u2029', '\n'))
21 |
22 |
23 | def execute_nuke_tcl(code):
24 | import nuke
25 | nuke.tcl(code)
26 |
27 |
28 | def execute_nuke_expression(code):
29 | import nuke
30 | nuke.expression(code)
31 |
32 |
33 | def execute_hscript(code):
34 | import hou
35 | hou.hscript(code)
36 |
37 | def execute_rumba_script(code):
38 | import script
39 | script.script_interpreter.exec_script(code, globals())
40 |
41 |
42 | EXECUTORS = {
43 | PYTHON: execute_python,
44 | MEL: execute_mel,
45 | NUKE_TCL: execute_nuke_tcl,
46 | NUKE_EXPRESSION: execute_nuke_expression,
47 | HSCRIPT: execute_hscript,
48 | RUMBA_SCRIPT: execute_rumba_script
49 | }
50 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The Clear BSD License
2 |
3 | Copyright (c) 2018 Lionel Brouyere
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted (subject to the limitations in the disclaimer
8 | below) provided that the following conditions are met:
9 |
10 | * Redistributions of source code must retain the above copyright notice,
11 | this list of conditions and the following disclaimer.
12 |
13 | * Redistributions in binary form must reproduce the above copyright
14 | notice, this list of conditions and the following disclaimer in the
15 | documentation and/or other materials provided with the distribution.
16 |
17 | * Neither the name of the copyright holder nor the names of its
18 | contributors may be used to endorse or promote products derived from this
19 | software without specific prior written permission.
20 |
21 | NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
22 | THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
23 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
25 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
26 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
27 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
28 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
29 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
30 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 | POSSIBILITY OF SUCH DAMAGE.
33 |
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 |
2 | - Write documentation in modules and Api examples and TD Documentation
3 |
4 | FEATURES TODO
5 | - editor: set attribute editor disabled when no item is selected
6 | - manager: think about a solution for name clashes issues
7 | - manager: manage relative image path or mass repath image on import
8 | - misc: compatible with PySide 1 for old maya and natron
9 | - editor: put edit area in a qscrollarea
10 | - editor: Make color wheel bigger
11 | - editor: Add shape outliner (as listview)
12 | - misc: Put transparency value in % or between 0 and 1 instead of a 255 values
13 | - misc: Tool tip for buttons
14 | - editor: Switchable auto-save/manual-save
15 | - misc: Create better icons
16 | - misc: Allow python command for the button dynamic label
17 | - misc: Create software context for Blender, 3dsMax and Houdini
18 |
19 | FEATURES DONE
20 | - api: add a reader widget for API usages
21 | - reader: close on leave option added
22 | - reader: close with esc button
23 | - manager: play button added to command button
24 | - editor: basic higlighting added for python and mel
25 | - manager: add a "no hotbox selected" dialog
26 | - manager: add a shortcut setter
27 | - editor: add ctrl + a to select all
28 | - editor: add ctrl + i to invert selection
29 | - editor: invert Ctrl and Shift for selection behavior
30 | - manager: implement a hotbox referencing system for studio environment
31 | - misc: Retro compatibility data implemented
32 | - manager: Submenu management
33 | - editor: Copy Paste in editor
34 | - manager: Export / Import System
35 | - reader: Improve aiming system
36 | - misc: Create software context for Nuke
37 | - editor: Ctrl + D in editor
38 | - editor: Improve item selection ergonomy in editor
39 |
40 | TO FIX
41 | - nothing currently
42 |
43 | FIXED
44 | - Aiming system improved and fixed (aiming pop too)
45 | - Undo stack
46 | - allow multiple key shortcut for maya
47 | - Debug delete
48 | - Fixe right clic issue
49 | - Fixe language selection
50 | - Debug auto-save issue
51 | - color wheel
52 | - undo stack
53 | - close exec_
54 | - remove 'on close' and rename 'both' as triggering method
55 | - mel exec_
56 | - Remove the editor window modality
57 | - Image not shown
58 |
--------------------------------------------------------------------------------
/hotbox_designer/data.py:
--------------------------------------------------------------------------------
1 |
2 | import os
3 | import json
4 | from hotbox_designer.templates import HOTBOX
5 |
6 |
7 | DEFAULT_NAME = 'MyHotbox_{}'
8 | TRIGGERING_TYPES = 'click only', 'click or close'
9 | HOTBOX_REPRESENTATION = """\
10 | Name {name}
11 | Submenu {submenu}
12 | Triggering {triggering}
13 | Aiming {aiming}
14 | Close on leave {leaveclose}
15 | """
16 |
17 |
18 | def get_new_hotbox(hotboxes):
19 | options = HOTBOX.copy()
20 | options.update({'name': get_valid_name(hotboxes)})
21 | return {
22 | 'general': options,
23 | 'shapes': []}
24 |
25 |
26 | def get_valid_name(hotboxes, proposal=None):
27 | names = [hotbox['general']['name'] for hotbox in hotboxes]
28 | index = 0
29 | name = proposal or DEFAULT_NAME.format(str(index).zfill(2))
30 | while name in names:
31 | if proposal:
32 | name = proposal + "_" + str(index).zfill(2)
33 | else:
34 | name = DEFAULT_NAME.format(str(index).zfill(2))
35 | index += 1
36 | return name
37 |
38 |
39 | def load_hotboxes_datas(filename):
40 | datas = load_json(filename, default=[])
41 | return [ensure_old_data_compatible(data) for data in datas]
42 |
43 |
44 | def load_json(filename, default=None):
45 | if not os.path.exists(filename):
46 | return default
47 | with open(filename, 'r') as f:
48 | return json.load(f)
49 |
50 |
51 | def save_datas(filename, hotboxes_data):
52 | with open(filename, 'w') as f:
53 | json.dump(hotboxes_data, f, indent=2)
54 |
55 |
56 | def copy_hotbox_data(data):
57 | copied = {}
58 | copied['general'] = data['general'].copy()
59 | copied['shapes'] = [shape.copy() for shape in data['shapes']]
60 | return copied
61 |
62 |
63 | def ensure_old_data_compatible(data):
64 | """
65 | Tests and update datas done with old version of the script
66 | This function contain all the data structure history to convertion
67 | """
68 | try:
69 | del data['submenu']
70 | except:
71 | pass
72 | try:
73 | data['general']['submenu']
74 | except KeyError:
75 | data['general']['submenu'] = False
76 | try:
77 | data['general']['leaveclose']
78 | except KeyError:
79 | data['general']['leaveclose'] = False
80 |
81 | return data
82 |
83 |
84 | def load_templates():
85 | path = os.path.join(os.path.dirname(__file__), 'resources', 'templates')
86 | files = os.listdir(path)
87 | templates = []
88 | for file_ in files:
89 | filepath = os.path.join(path, file_)
90 | with open(filepath, 'r') as f:
91 | templates.append(json.load(f))
92 | return templates
93 |
94 |
95 | def hotbox_data_to_html(data):
96 | return HOTBOX_REPRESENTATION.format(
97 | name=data['general']['name'],
98 | submenu=data['general']['submenu'],
99 | triggering=data['general']['triggering'],
100 | aiming=data['general']['aiming'],
101 | leaveclose=data['general']['leaveclose'])
102 |
--------------------------------------------------------------------------------
/hotbox_designer/templates.py:
--------------------------------------------------------------------------------
1 | SQUARE_BUTTON = {
2 | 'shape': 'square', # or round
3 | 'shape.left': 0.0,
4 | 'shape.top': 0.0,
5 | 'shape.width': 120.0,
6 | 'shape.height': 25.0,
7 | 'border': True,
8 | 'borderwidth.normal': 1.0,
9 | 'borderwidth.hovered': 1.25,
10 | 'borderwidth.clicked': 2,
11 | 'bordercolor.normal': '#000000',
12 | 'bordercolor.hovered': '#393939',
13 | 'bordercolor.clicked': '#FFFFFF',
14 | 'bordercolor.transparency': 0,
15 | 'bgcolor.normal': '#888888',
16 | 'bgcolor.hovered': '#AAAAAA',
17 | 'bgcolor.clicked': '#DDDDDD',
18 | 'bgcolor.transparency': 0,
19 | 'text.content': 'Button',
20 | 'text.size': 12,
21 | 'text.bold': False,
22 | 'text.italic': False,
23 | 'text.color': '#FFFFFF',
24 | 'text.valign': 'center', # or 'top' or bottom
25 | 'text.halign': 'center', # or 'left' or 'right'
26 | 'action.left': True,
27 | 'action.left.close': False,
28 | 'action.left.language': 'python', # or mel
29 | 'action.left.command': '',
30 | 'action.right': False,
31 | 'action.right.close': False,
32 | 'action.right.language': 'python', # or mel
33 | 'action.right.command': '',
34 | 'image.path': '',
35 | 'image.fit': True,
36 | 'image.height': 32,
37 | 'image.width': 32}
38 |
39 |
40 | TEXT = {
41 | 'shape': 'square', # or round
42 | 'shape.left': 0.0,
43 | 'shape.top': 0.0,
44 | 'shape.width': 200.0,
45 | 'shape.height': 50.0,
46 | 'border': False,
47 | 'borderwidth.normal': 0,
48 | 'borderwidth.hovered': 0,
49 | 'borderwidth.clicked': 0,
50 | 'bordercolor.normal': '#000000',
51 | 'bordercolor.hovered': '#393939',
52 | 'bordercolor.clicked': '#FFFFFF',
53 | 'bordercolor.transparency': 0,
54 | 'bgcolor.normal': '#888888',
55 | 'bgcolor.hovered': '#AAAAAA',
56 | 'bgcolor.clicked': '#DDDDDD',
57 | 'bgcolor.transparency': 255,
58 | 'text.content': 'Text',
59 | 'text.size': 16,
60 | 'text.bold': True,
61 | 'text.italic': False,
62 | 'text.color': '#FFFFFF',
63 | 'text.valign': 'top', # or 'top' or bottom
64 | 'text.halign': 'left', # or 'left' or 'right'
65 | 'action.left': False,
66 | 'action.left.close': False,
67 | 'action.left.language': 'python', # or mel
68 | 'action.left.command': '',
69 | 'action.right': False,
70 | 'action.right.close': False,
71 | 'action.right.language': 'python', # or mel
72 | 'action.right.command': '',
73 | 'image.path': '',
74 | 'image.fit': False,
75 | 'image.height': 32,
76 | 'image.width': 32}
77 |
78 |
79 | BACKGROUND = {
80 | 'shape': 'square', # or round
81 | 'shape.left': 0.0,
82 | 'shape.top': 0.0,
83 | 'shape.width': 400.0,
84 | 'shape.height': 400.0,
85 | 'border': False,
86 | 'borderwidth.normal': 0,
87 | 'borderwidth.hovered': 0,
88 | 'borderwidth.clicked': 0,
89 | 'bordercolor.normal': '#888888',
90 | 'bordercolor.hovered': '#888888',
91 | 'bordercolor.clicked': '#888888',
92 | 'bordercolor.transparency': 0,
93 | 'bgcolor.normal': '#888888',
94 | 'bgcolor.hovered': '#888888',
95 | 'bgcolor.clicked': '#888888',
96 | 'bgcolor.transparency': 0,
97 | 'text.content': '',
98 | 'text.size': 12,
99 | 'text.bold': False,
100 | 'text.italic': False,
101 | 'text.color': '#FFFFFF',
102 | 'text.valign': 'center', # or 'top' or bottom
103 | 'text.halign': 'center', # or 'left' or 'right'
104 | 'action.left': False,
105 | 'action.left.close': False,
106 | 'action.left.language': 'python', # or mel
107 | 'action.left.command': '',
108 | 'action.right': False,
109 | 'action.right.close': False,
110 | 'action.right.language': 'python', # or mel
111 | 'action.right.command': '',
112 | 'image.path': '',
113 | 'image.fit': False,
114 | 'image.height': 32,
115 | 'image.width': 32}
116 |
117 |
118 | HOTBOX = {
119 | 'name': '',
120 | 'triggering': 'click only', # or 'click or close',
121 | 'aiming': False,
122 | 'centerx': 450,
123 | 'centery': 300,
124 | 'width': 900,
125 | 'height': 600,
126 | 'submenu': False,
127 | 'leaveclose': False
128 | }
129 |
130 |
--------------------------------------------------------------------------------
/hotbox_designer/designer/highlighter.py:
--------------------------------------------------------------------------------
1 | import keyword
2 | from hotbox_designer.vendor.Qt import QtGui, QtCore
3 | from hotbox_designer.languages import PYTHON, MEL, RUMBA_SCRIPT
4 |
5 |
6 |
7 | MELKEYWORDS = [
8 | 'if', 'else', 'int', 'float', 'double', 'string', 'array'
9 | 'var', 'return', 'case', 'then', 'continue', 'break', 'global', 'proc']
10 |
11 | TEXT_STYLES = {
12 | 'keyword': {
13 | 'color': 'white',
14 | 'bold': True,
15 | 'italic': False},
16 | 'number': {
17 | 'color': 'cyan',
18 | 'bold': False,
19 | 'italic': False},
20 | 'comment': {
21 | 'color': (0.7, 0.5, 0.5),
22 | 'bold': False,
23 | 'italic': False},
24 | 'function': {
25 | 'color': '#ff0571',
26 | 'bold': False,
27 | 'italic': True},
28 | 'string': {
29 | 'color': 'yellow',
30 | 'bold': False,
31 | 'italic': False},
32 | 'boolean': {
33 | 'color': '#a18852',
34 | 'bold': True,
35 | 'italic': False}}
36 |
37 |
38 | PATTERNS = {
39 | PYTHON: {
40 | 'keyword': r'\b|'.join(keyword.kwlist),
41 | 'number': r'\b[+-]?[0-9]+[lL]?\b',
42 | 'comment': r'#[^\n]*',
43 | 'function': r'\b[A-Za-z0-9_]+(?=\()',
44 | 'string': r'".*"|\'.*\'',
45 | 'boolean': r'\bTrue\b|\bFalse\b'},
46 | MEL: {
47 | 'keyword': r'\b|'.join(MELKEYWORDS),
48 | 'number': r'\b[+-]?[0-9]+[lL]?\b',
49 | 'comment': r'//[^\n]*',
50 | 'function': r'\b[A-Za-z0-9_]+(?=\()',
51 | 'string': r'".*"|\'.*\'',
52 | 'boolean': r'\btrue\b|\bfalse\b'},
53 | RUMBA_SCRIPT: {
54 | 'keyword': r'\b|'.join(keyword.kwlist),
55 | 'number': r'\b[+-]?[0-9]+[lL]?\b',
56 | 'comment': r'#[^\n]*',
57 | 'function': r'\b[A-Za-z0-9_]+(?=\()',
58 | 'string': r'".*"|\'.*\'',
59 | 'boolean': r'\bTrue\b|\bFalse\b'},
60 | }
61 |
62 |
63 | class Highlighter(QtGui.QSyntaxHighlighter):
64 | PATTERNS = []
65 |
66 | def __init__(self, parent=None):
67 | super(Highlighter, self).__init__(parent)
68 | self.rules = []
69 | for name, properties in TEXT_STYLES.items():
70 | if name not in self.PATTERNS:
71 | continue
72 | text_format = create_textcharformat(
73 | color=properties['color'],
74 | bold=properties['bold'],
75 | italic=properties['italic'])
76 | self.rules.append(
77 | (QtCore.QRegExp(self.PATTERNS[name]), text_format))
78 |
79 | def highlightBlock(self, text):
80 | for pattern, format_ in self.rules:
81 | if hasattr(pattern, 'globalMatch'):
82 | # PySide6 with QRegularExpression
83 | match_iter = pattern.globalMatch(text)
84 | while match_iter.hasNext():
85 | match = match_iter.next()
86 | start = match.capturedStart()
87 | length = match.capturedLength()
88 | self.setFormat(start, length, format_)
89 | else:
90 | # PySide2 with QRegExp
91 | index = pattern.indexIn(text)
92 | while index >= 0:
93 | length = pattern.matchedLength()
94 | self.setFormat(index, length, format_)
95 | index = pattern.indexIn(text, index + length)
96 |
97 |
98 | class PythonHighlighter(Highlighter):
99 | PATTERNS = PATTERNS[PYTHON]
100 |
101 |
102 | class MelHighlighter(Highlighter):
103 | PATTERNS = PATTERNS[MEL]
104 |
105 | class RumbaScriptHighlighter(Highlighter):
106 | PATTERNS = PATTERNS[RUMBA_SCRIPT]
107 |
108 | HIGHLIGHTERS = {
109 | PYTHON: PythonHighlighter,
110 | MEL: MelHighlighter,
111 | RUMBA_SCRIPT: RumbaScriptHighlighter
112 | }
113 |
114 |
115 | def get_highlighter(language):
116 | return HIGHLIGHTERS.get(language, Highlighter)
117 |
118 |
119 | def create_textcharformat(color, bold=False, italic=False):
120 | char_format = QtGui.QTextCharFormat()
121 | qcolor = QtGui.QColor()
122 | if isinstance(color, str):
123 | qcolor.setNamedColor(color)
124 | else:
125 | r, g, b = color
126 | qcolor.setRgbF(r, g, b)
127 | char_format.setForeground(qcolor)
128 | if bold:
129 | char_format.setFontWeight(QtGui.QFont.Bold)
130 | if italic:
131 | char_format.setFontItalic(True)
132 | return char_format
133 |
--------------------------------------------------------------------------------
/hotbox_designer/resources/templates/marking1.json:
--------------------------------------------------------------------------------
1 | {
2 | "shapes": [
3 | {
4 | "bgcolor.normal": "#424242",
5 | "borderwidth.hovered": 1.25,
6 | "borderwidth.normal": 1.0,
7 | "image.width": 32,
8 | "bordercolor.transparency": 0,
9 | "shape": "square",
10 | "borderwidth.clicked": 2,
11 | "action.right.close": false,
12 | "border": true,
13 | "action.right": false,
14 | "text.valign": "center",
15 | "bordercolor.hovered": "#000000",
16 | "image.path": "",
17 | "action.left": true,
18 | "image.height": 32,
19 | "action.left.command": "",
20 | "text.content": "Action 2",
21 | "bordercolor.normal": "#000000",
22 | "action.left.close": true,
23 | "shape.height": 21.5,
24 | "text.bold": false,
25 | "bgcolor.clicked": "#65aae6",
26 | "action.left.language": "python",
27 | "shape.width": 170.0,
28 | "action.right.command": "",
29 | "bordercolor.clicked": "#000000",
30 | "image.fit": true,
31 | "shape.top": 303.0,
32 | "bgcolor.hovered": "#65aae6",
33 | "text.italic": false,
34 | "bgcolor.transparency": 0,
35 | "action.right.language": "python",
36 | "text.color": "#FFFFFF",
37 | "text.size": 12,
38 | "text.halign": "center",
39 | "shape.left": 228.0
40 | },
41 | {
42 | "bgcolor.normal": "#424242",
43 | "borderwidth.hovered": 1.25,
44 | "borderwidth.normal": 1.0,
45 | "image.width": 32,
46 | "bordercolor.transparency": 0,
47 | "shape": "square",
48 | "borderwidth.clicked": 2,
49 | "action.right.close": false,
50 | "border": true,
51 | "action.right": false,
52 | "text.valign": "center",
53 | "bordercolor.hovered": "#000000",
54 | "image.path": "",
55 | "action.left": true,
56 | "image.height": 32,
57 | "action.left.command": "",
58 | "text.content": "Action 1",
59 | "bordercolor.normal": "#000000",
60 | "action.left.close": true,
61 | "shape.height": 21.5,
62 | "text.bold": false,
63 | "bgcolor.clicked": "#65aae6",
64 | "action.left.language": "python",
65 | "shape.width": 170.0,
66 | "action.right.command": "",
67 | "bordercolor.clicked": "#000000",
68 | "image.fit": true,
69 | "shape.top": 245.0,
70 | "bgcolor.hovered": "#65aae6",
71 | "text.italic": false,
72 | "bgcolor.transparency": 0,
73 | "action.right.language": "python",
74 | "text.color": "#FFFFFF",
75 | "text.size": 12,
76 | "text.halign": "center",
77 | "shape.left": 364.0
78 | },
79 | {
80 | "bgcolor.normal": "#424242",
81 | "borderwidth.hovered": 1.25,
82 | "borderwidth.normal": 1.0,
83 | "image.width": 32,
84 | "bordercolor.transparency": 0,
85 | "shape": "square",
86 | "borderwidth.clicked": 2,
87 | "action.right.close": false,
88 | "border": true,
89 | "action.right": false,
90 | "text.valign": "center",
91 | "bordercolor.hovered": "#000000",
92 | "image.path": "",
93 | "action.left": true,
94 | "image.height": 32,
95 | "action.left.command": "",
96 | "text.content": "Action 3",
97 | "bordercolor.normal": "#000000",
98 | "action.left.close": true,
99 | "shape.height": 21.5,
100 | "text.bold": false,
101 | "bgcolor.clicked": "#65aae6",
102 | "action.left.language": "python",
103 | "shape.width": 170.0,
104 | "action.right.command": "",
105 | "bordercolor.clicked": "#000000",
106 | "image.fit": true,
107 | "shape.top": 301.0,
108 | "bgcolor.hovered": "#65aae6",
109 | "text.italic": false,
110 | "bgcolor.transparency": 0,
111 | "action.right.language": "python",
112 | "text.color": "#FFFFFF",
113 | "text.size": 12,
114 | "text.halign": "center",
115 | "shape.left": 503.0
116 | },
117 | {
118 | "bgcolor.normal": "#424242",
119 | "borderwidth.hovered": 1.25,
120 | "borderwidth.normal": 1.0,
121 | "image.width": 32,
122 | "bordercolor.transparency": 0,
123 | "shape": "square",
124 | "borderwidth.clicked": 2,
125 | "action.right.close": false,
126 | "border": true,
127 | "action.right": false,
128 | "text.valign": "center",
129 | "bordercolor.hovered": "#000000",
130 | "image.path": "",
131 | "action.left": true,
132 | "image.height": 32,
133 | "action.left.command": "",
134 | "text.content": "Action 4",
135 | "bordercolor.normal": "#000000",
136 | "action.left.close": true,
137 | "shape.height": 21.5,
138 | "text.bold": false,
139 | "bgcolor.clicked": "#65aae6",
140 | "action.left.language": "python",
141 | "shape.width": 170.0,
142 | "action.right.command": "",
143 | "bordercolor.clicked": "#000000",
144 | "image.fit": true,
145 | "shape.top": 367.0,
146 | "bgcolor.hovered": "#65aae6",
147 | "text.italic": false,
148 | "bgcolor.transparency": 0,
149 | "action.right.language": "python",
150 | "text.color": "#FFFFFF",
151 | "text.size": 12,
152 | "text.halign": "center",
153 | "shape.left": 363.0
154 | }
155 | ],
156 | "general": {
157 | "submenu": false,
158 | "name": "Marking_Menu_4_Actions",
159 | "height": 600,
160 | "width": 900,
161 | "centerx": 451,
162 | "centery": 314,
163 | "aiming": true,
164 | "leaveclose": true,
165 | "triggering": "click only",
166 | "leaveclose": false
167 | }
168 | }
--------------------------------------------------------------------------------
/hotbox_designer/painting.py:
--------------------------------------------------------------------------------
1 | from hotbox_designer.vendor.Qt import QtCore, QtGui
2 | from hotbox_designer.qtutils import VALIGNS, HALIGNS
3 | from hotbox_designer.geometry import grow_rect
4 |
5 |
6 | MANIPULATOR_BORDER = 5
7 | SELECTION_COLOR = '#3388FF'
8 |
9 |
10 | def draw_editor(painter, rect, snap=None):
11 | # draw border
12 | pen = QtGui.QPen(QtGui.QColor('#333333'))
13 | pen.setStyle(QtCore.Qt.DashDotLine)
14 | pen.setWidth(3)
15 | brush = QtGui.QBrush(QtGui.QColor(255, 255, 255, 25))
16 | painter.setPen(pen)
17 | painter.setBrush(brush)
18 | painter.drawRect(rect)
19 |
20 | if snap is None:
21 | return
22 | # draw snap grid
23 | pen = QtGui.QPen(QtGui.QColor('red'))
24 | painter.setPen(pen)
25 | x = 0
26 | y = 0
27 | while y < rect.bottom():
28 | painter.drawPoint(x, y)
29 | x += snap[0]
30 | if x > rect.right():
31 | x = 0
32 | y += snap[1]
33 |
34 |
35 | def draw_editor_center(painter, rect, point):
36 | color = QtGui.QColor(200, 200, 200, 125)
37 | painter.setPen(QtGui.QPen(color))
38 | painter.setBrush(QtGui.QBrush(color))
39 | painter.drawRect(rect)
40 |
41 | path = get_center_path(QtCore.QPoint(*point))
42 | pen = QtGui.QPen(QtGui.QColor(50, 125, 255))
43 | pen.setWidth(2)
44 | painter.setPen(pen)
45 | painter.drawPath(path)
46 |
47 |
48 | def get_center_path(point):
49 | ext = 12
50 | int_ = 5
51 | path = QtGui.QPainterPath(point)
52 | path.moveTo(QtCore.QPoint(point.x() - ext, point.y()))
53 | path.lineTo(QtCore.QPoint(point.x() - int_, point.y()))
54 | path.moveTo(QtCore.QPoint(point.x() + int_, point.y()))
55 | path.lineTo(QtCore.QPoint(point.x() + ext, point.y()))
56 | path.moveTo(QtCore.QPoint(point.x(), point.y() - ext))
57 | path.lineTo(QtCore.QPoint(point.x(), point.y() - int_))
58 | path.moveTo(QtCore.QPoint(point.x(), point.y() + int_))
59 | path.lineTo(QtCore.QPoint(point.x(), point.y() + ext))
60 | path.addEllipse(point, 1, 1)
61 | return path
62 |
63 |
64 | def draw_shape(painter, shape):
65 | options = shape.options
66 | content_rect = shape.content_rect()
67 | if shape.clicked:
68 | bordercolor = QtGui.QColor(options['bordercolor.clicked'])
69 | backgroundcolor = QtGui.QColor(options['bgcolor.clicked'])
70 | bordersize = options['borderwidth.clicked']
71 | elif shape.hovered:
72 | bordercolor = QtGui.QColor(options['bordercolor.hovered'])
73 | backgroundcolor = QtGui.QColor(options['bgcolor.hovered'])
74 | bordersize = options['borderwidth.hovered']
75 | else:
76 | bordercolor = QtGui.QColor(options['bordercolor.normal'])
77 | backgroundcolor = QtGui.QColor(options['bgcolor.normal'])
78 | bordersize = options['borderwidth.normal']
79 | textcolor = QtGui.QColor(options['text.color'])
80 | alpha = options['bordercolor.transparency'] if options['border'] else 255
81 | bordercolor.setAlpha(255 - alpha)
82 | backgroundcolor.setAlpha(255 - options['bgcolor.transparency'])
83 |
84 | pen = QtGui.QPen(bordercolor)
85 | pen.setStyle(QtCore.Qt.SolidLine)
86 | pen.setWidthF(bordersize)
87 | painter.setPen(pen)
88 | painter.setBrush(QtGui.QBrush(backgroundcolor))
89 | if options['shape'] == 'square':
90 | painter.drawRect(shape.rect)
91 | else:
92 | painter.drawEllipse(shape.rect)
93 |
94 | if shape.pixmap is not None:
95 | rect = shape.image_rect or content_rect
96 | painter.drawPixmap(rect, shape.pixmap)
97 |
98 | painter.setPen(QtGui.QPen(textcolor))
99 | painter.setBrush(QtGui.QBrush(textcolor))
100 | option = QtGui.QTextOption()
101 | flags = VALIGNS[options['text.valign']] | HALIGNS[options['text.halign']]
102 | option.setAlignment(flags)
103 | font = QtGui.QFont()
104 | font.setBold(options['text.bold'])
105 | font.setItalic(options['text.italic'])
106 | font.setPixelSize(options['text.size'])
107 | painter.setFont(font)
108 | text = options['text.content']
109 | painter.drawText(QtCore.QRectF(content_rect), flags, text)
110 |
111 |
112 | def draw_selection_square(painter, rect):
113 | bordercolor = QtGui.QColor(SELECTION_COLOR)
114 | backgroundcolor = QtGui.QColor(SELECTION_COLOR)
115 | backgroundcolor.setAlpha(85)
116 | painter.setPen(QtGui.QPen(bordercolor))
117 | painter.setBrush(QtGui.QBrush(backgroundcolor))
118 | painter.drawRect(rect)
119 |
120 |
121 | def draw_manipulator(painter, manipulator, cursor):
122 | hovered = manipulator.hovered_rects(cursor)
123 |
124 | if manipulator.rect in hovered:
125 | pen = QtGui.QPen(QtGui.QColor(0, 0, 0, 0))
126 | brush = QtGui.QBrush(QtGui.QColor(125, 125, 125))
127 | brush.setStyle(QtCore.Qt.FDiagPattern)
128 | painter.setPen(pen)
129 | painter.setBrush(brush)
130 | painter.drawPath(manipulator.hovered_path)
131 |
132 | pen = QtGui.QPen(QtGui.QColor('black'))
133 | brush = QtGui.QBrush(QtGui.QColor('white'))
134 | painter.setBrush(brush)
135 | for rect in manipulator.handler_rects():
136 | pen.setWidth(3 if rect in hovered else 1)
137 | painter.setPen(pen)
138 | painter.drawEllipse(rect)
139 |
140 | pen.setWidth(1)
141 | pen.setStyle(QtCore.Qt.DashLine) # if not moving else QtCore.Qt.SolidLine)
142 | painter.setPen(pen)
143 | painter.setBrush(QtGui.QBrush(QtGui.QColor(0, 0, 0, 0)))
144 | painter.drawRect(manipulator.rect)
145 |
146 |
147 | def draw_aiming_background(painter, rect):
148 | pen = QtGui.QPen(QtGui.QColor(0, 0, 0, 0))
149 | brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 1))
150 | painter.setPen(pen)
151 | painter.setBrush(brush)
152 | painter.drawRect(rect)
153 |
154 |
155 | def draw_aiming(painter, center, target):
156 | pen = QtGui.QPen(QtGui.QColor(35, 35, 35))
157 | pen.setWidth(3)
158 | painter.setPen(pen)
159 | painter.setBrush(QtGui.QColor(0, 0, 0, 0))
160 | painter.drawLine(center, target)
161 |
162 |
163 | def get_hovered_path(rect):
164 | path = QtGui.QPainterPath()
165 | path.addRect(rect)
166 | path.addRect(grow_rect(rect, MANIPULATOR_BORDER))
167 | return path
--------------------------------------------------------------------------------
/hotbox_designer/interactive.py:
--------------------------------------------------------------------------------
1 | from hotbox_designer.vendor.Qt import QtCore, QtGui
2 |
3 | from hotbox_designer.geometry import (
4 | DIRECTIONS, get_topleft_rect, get_bottomleft_rect, get_topright_rect,
5 | get_bottomright_rect, get_left_side_rect, get_right_side_rect,
6 | get_top_side_rect, get_bottom_side_rect, proportional_rect)
7 | from hotbox_designer.painting import (
8 | draw_selection_square, draw_manipulator, get_hovered_path, draw_shape)
9 | from hotbox_designer.languages import execute_code
10 |
11 |
12 | class SelectionSquare():
13 | def __init__(self):
14 | self.rect = None
15 | self.handeling = False
16 |
17 | def clicked(self, cursor):
18 | self.handeling = True
19 | self.rect = QtCore.QRectF(cursor, cursor)
20 |
21 | def handle(self, cursor):
22 | self.rect.setBottomRight(cursor)
23 |
24 | def release(self):
25 | self.handeling = False
26 | self.rect = None
27 |
28 | def draw(self, painter):
29 | if self.rect is None:
30 | return
31 | draw_selection_square(painter, self.rect)
32 |
33 |
34 | class Manipulator():
35 | def __init__(self):
36 | self.rect = None
37 | self._is_hovered = False
38 |
39 | self._tl_corner_rect = None
40 | self._bl_corner_rect = None
41 | self._tr_corner_rect = None
42 | self._br_corner_rect = None
43 | self._l_side_rect = None
44 | self._r_side_rect = None
45 | self._t_side_rect = None
46 | self._b_side_rect = None
47 |
48 | self.hovered_path = None
49 |
50 | def handler_rects(self):
51 | return [
52 | self._tl_corner_rect, self._bl_corner_rect, self._tr_corner_rect,
53 | self._br_corner_rect, self._l_side_rect, self._r_side_rect,
54 | self._t_side_rect, self._b_side_rect]
55 |
56 | def get_direction(self, cursor):
57 | if self.rect is None:
58 | return None
59 | for i, rect in enumerate(self.handler_rects()):
60 | if rect.contains(cursor):
61 | return DIRECTIONS[i]
62 |
63 | def hovered_rects(self, cursor):
64 | rects = []
65 | for rect in self.handler_rects() + [self.rect]:
66 | if not rect:
67 | continue
68 | if rect.contains(cursor):
69 | rects.append(rect)
70 | return rects
71 |
72 | def set_rect(self, rect):
73 | self.rect = rect
74 | self.update_geometries()
75 |
76 | def update_geometries(self):
77 | rect = self.rect
78 | self._tl_corner_rect = get_topleft_rect(rect) if rect else None
79 | self._bl_corner_rect = get_bottomleft_rect(rect) if rect else None
80 | self._tr_corner_rect = get_topright_rect(rect) if rect else None
81 | self._br_corner_rect = get_bottomright_rect(rect) if rect else None
82 | self._l_side_rect = get_left_side_rect(rect) if rect else None
83 | self._r_side_rect = get_right_side_rect(rect) if rect else None
84 | self._t_side_rect = get_top_side_rect(rect) if rect else None
85 | self._b_side_rect = get_bottom_side_rect(rect) if rect else None
86 | self.hovered_path = get_hovered_path(rect) if rect else None
87 |
88 | def draw(self, painter, cursor):
89 | if self.rect is not None and all(self.handler_rects()):
90 | draw_manipulator(painter, self, cursor)
91 |
92 |
93 | def get_shape_rect_from_options(options):
94 | return QtCore.QRectF(
95 | options['shape.left'],
96 | options['shape.top'],
97 | options['shape.width'],
98 | options['shape.height'])
99 |
100 |
101 | class Shape():
102 | def __init__(self, options):
103 | self.hovered = False
104 | self.clicked = False
105 | self.options = options
106 | self.rect = get_shape_rect_from_options(options)
107 | self.pixmap = None
108 | self.image_rect = None
109 | self.synchronize_image()
110 |
111 | def set_hovered(self, cursor):
112 | self.hovered = self.rect.contains(cursor)
113 |
114 | def set_clicked(self, cursor):
115 | self.clicked = self.rect.contains(cursor)
116 |
117 | def release(self, cursor):
118 | self.clicked = False
119 | self.hovered = self.rect.contains(cursor)
120 |
121 | def draw(self, painter):
122 | draw_shape(painter, self)
123 |
124 | def synchronize_rect(self):
125 | self.options['shape.left'] = self.rect.left()
126 | self.options['shape.top'] = self.rect.top()
127 | self.options['shape.width'] = self.rect.width()
128 | self.options['shape.height'] = self.rect.height()
129 |
130 | def content_rect(self):
131 | if self.options['shape'] == 'round':
132 | return proportional_rect(self.rect.toRect(), 70)
133 | return self.rect.toRect()
134 |
135 | def execute(self, left=False, right=False):
136 | side = 'left' if left else 'right' if right else None
137 | if not side or not self.options['action.' + side]:
138 | return
139 | code = self.options['action.{}.command'.format(side)]
140 | language = self.options['action.{}.language'.format(side)]
141 | execute_code(language, code)
142 |
143 | def is_interactive(self):
144 | return any([self.options['action.right'], self.options['action.left']])
145 |
146 | def autoclose(self, left=False, right=False):
147 | if left is True and right is False:
148 | return self.options['action.left.close']
149 | elif left is False and right is True:
150 | return self.options['action.right.close']
151 | elif left is True and right is True:
152 | r_close = self.options['action.right.close']
153 | l_close = self.options['action.left.close']
154 | return r_close or l_close
155 | return False
156 |
157 | def synchronize_image(self):
158 | self.pixmap = QtGui.QPixmap(self.options['image.path'])
159 | if self.options['image.fit'] is True:
160 | self.image_rect = None
161 | return
162 | self.image_rect = QtCore.QRect(
163 | self.rect.left(),
164 | self.rect.top(),
165 | self.options['image.width'],
166 | self.options['image.height'])
167 | self.image_rect.moveCenter(self.rect.center().toPoint())
168 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 |
2 | # Hotbox Designer
3 | Python plug-in for CGI Softwares.
4 | It provide simple tools to create visually a hotbox menus, simply manage them and use them in the main software.
5 |
6 |
7 |
8 | ### Table of contents
9 | * [Credits](#credits)
10 | * [Implementation](#implementation)
11 | * [Installation](#installation)
12 | * [Maya](#autodesk-maya)
13 | * [Nuke](#nuke)
14 | * [Houdini](#houdini)
15 | * [Rumba](#rumba)
16 | * [Tutorials](#tutorials)
17 | * [Code samples](#code-samples)
18 | * [Launch manager](#launch-manager)
19 | * [Create custom widget](#create-custom-widget)
20 | * [Basic widget](#basic-widget)
21 | * [Advanced widget](#advanced-widget)
22 | * [Tools](#tools)
23 | * [Designer](#designer)
24 | * [Manager](#manager)
25 | * [Reader](#reader)
26 |
27 | ### Credits
28 | main coder: Lionel Brouyère, Herizo Ran
29 | contributor: Vincent Girès
30 | tester: David Vincze, Vincent Girès
31 | ### Implementation
32 | | Software | Implementation state | Application as string | Hotkey setter |
33 | | ------ | ------ | ------ | ----- |
34 | | Autodesk Maya | done | 'maya' | available |
35 | | Foundry Nuke | done | 'nuke' | available |
36 | | Autodesk 3dsMax| planned | undefined | Not available |
37 | | SideFX Houdini | done | 'houdini' | Not available |
38 | | Rumba Animation| done | 'rumba' | available |
39 |
40 | For each software who provide python and support PySide2/PyQt5, the implementation should be easy.
41 |
42 | ### Installation
43 | #### Autodesk Maya
44 |
45 | place the "hotbox_designer" folder into the maya script folder
46 |
47 | | os | path |
48 | | ------ | ------ |
49 | | linux | ~/< username >/maya |
50 | | windows | \Users\\Documents\maya |
51 | | mac os x | ~/Library/Preferences/Autodesk/maya |
52 |
53 | #### Nuke
54 | Place the _"hotbox_designer"_ folder into _~/.nuke_ or make it available in PYTHONPATH
55 | Add this script to menu.py or make it available in NUKE_PATH:
56 | ```python
57 | import nuke
58 | import hotbox_designer
59 | from hotbox_designer.applications import Nuke
60 | from hotbox_designer.manager import HotboxManager
61 |
62 | nuke_app = Nuke()
63 | hotbox_manager = HotboxManager(nuke_app)
64 |
65 | nuke_menu = nuke.menu('Nuke')
66 | menu = nuke_menu.addMenu('Hotbox Designer')
67 | menu.addCommand(
68 | name='Manager',
69 | command=hotbox_manager.show)
70 | nuke_app.create_menus()
71 | ```
72 | Hotkeys are saved in _~/.nuke/hotbox_hotkey.json_.
73 | To delete it, right now, the only way is to delete it in the file.
74 |
75 | Important:
76 | A bug appear on some windows version. Nuke execute the init file before to create his guy, which fail the hotbox designer execution. This initialization has to be delayed later. Then read this page to find the actual solution found:
77 | https://github.com/luckylyk/hotbox_designer/issues/10
78 |
79 | #### Houdini
80 | soon
81 | #### Rumba
82 | Locate the _"hotbox_designer"_ folder and make it available in
83 | RUMBA_USER_PLUGINS environment variable.
84 | Add this script in a python file _menu.py_ and make it available in
85 | RUMBA_USER_PLUGINS environment variable.
86 | ```python
87 | """Hotbox Designer integration for Rumba."""
88 |
89 | import rumbapy
90 | from PySide2 import QtCore
91 | from rumbapy.WidgetPlugins import register_ui, UIPluginOptions
92 |
93 |
94 | class HotboxDesigner(QtCore.QObject):
95 |
96 | def __init__(self, parent=None):
97 | super(HotboxDesigner, self).__init__(parent)
98 | from hotbox_designer.applications import Rumba
99 | rumba_app = Rumba()
100 | rumba_app.create_menus()
101 |
102 |
103 | register_ui(HotboxDesigner, "Hotbox Designer", UIPluginOptions(
104 | auto_create=True))
105 | ```
106 | Hotkeys are saved in ~/.rumba/hotbox_hotkey.json.
107 |
108 | ### Tutorials
109 | * [My first hotbox](https://vimeo.com/304248049)
110 | * [Create a submenu](https://vimeo.com/304252379)
111 | ### Code Samples
112 | #### Launch manager
113 | ```python
114 | import hotbox_designer
115 | hotbox_designer.launch_manager('maya') # or any other available application name as string
116 | ```
117 | #### Create custom widget
118 | * ##### Basic widget
119 | ```python
120 | from hotbox_designer import load_json, HotboxWidget
121 | # it can be integrated in a layout of an parent widget
122 | widget = HotboxWidget()
123 | # That can be changed interactively
124 | hotbox_data = load_json(r"your exported hotbox as json filepath")
125 | widget.set_hotbox_data(hotbox_data)
126 | ```
127 | * ##### Advanced widget
128 | Example of an template explorer
129 |
130 | ```python
131 | from hotbox_designer import HotboxWidget, load_templates
132 | from PySide2 import QtWidgets, QtCore
133 |
134 | class HotboxTemplateNavigator(QtWidgets.QWidget):
135 | def __init__(self, *args, **kwargs):
136 | super(HotboxTemplateNavigator, self).__init__(*args, **kwargs)
137 | self.templates = load_templates()
138 | items = [d['general']['name'] for d in self.templates]
139 | self.combo = QtWidgets.QComboBox()
140 | self.combo.addItems(items)
141 | self.combo.currentIndexChanged.connect(self.combo_index_changed)
142 | self.hotbox_widget = HotboxWidget()
143 |
144 | self.layout = QtWidgets.QVBoxLayout(self)
145 | self.layout.addWidget(self.combo)
146 | self.layout.addWidget(self.hotbox_widget)
147 | self.layout.addStretch(1)
148 |
149 | def combo_index_changed(self):
150 | index = self.combo.currentIndex()
151 | data = self.templates[index]
152 | self.hotbox_widget.set_hotbox_data(data)
153 | size = QtCore.QSize(data["general"]["width"], data["general"]["height"])
154 | self.hotbox_widget.setFixedSize(size)
155 | self.adjustSize()
156 |
157 |
158 | hotbox_template_navigator = HotboxTemplateNavigator(None, QtCore.Qt.Window)
159 | hotbox_template_navigator.show()
160 | ```
161 |
162 | ### Tools
163 | The application is separated in three parts:
164 | - ##### Designer
165 | this is the hotbox design part. It look like a simple version of QtDesigner
166 | 
167 | - ##### Manager
168 | its a simple ui who let manage multiple hotboxes and save them
169 | 
170 | - ##### Reader
171 | this contains the final hotbox widget. That's what is called when you use your menu as final product.
172 |
--------------------------------------------------------------------------------
/hotbox_designer/widgets.py:
--------------------------------------------------------------------------------
1 | from hotbox_designer.vendor.Qt import QtGui, QtCore, QtWidgets
2 | from hotbox_designer.qtutils import icon
3 | from hotbox_designer.colorwheel import ColorDialog
4 |
5 |
6 | # don't use style sheet like that, find better design
7 | TOGGLER_STYLESHEET = (
8 | 'background: rgb(0, 0, 0, 75); text-align: left; font: bold')
9 |
10 |
11 | class BoolCombo(QtWidgets.QComboBox):
12 | valueSet = QtCore.Signal(bool)
13 |
14 | def __init__(self, state=True, parent=None):
15 | super(BoolCombo, self).__init__(parent)
16 | self.addItem('True')
17 | self.addItem('False')
18 | self.setCurrentText(str(state))
19 | self.currentIndexChanged.connect(self.current_index_changed)
20 |
21 | def state(self):
22 | return self.currentText() == 'True'
23 |
24 | def current_index_changed(self):
25 | self.valueSet.emit(self.state())
26 |
27 |
28 | class BrowseEdit(QtWidgets.QWidget):
29 | valueSet = QtCore.Signal(str)
30 |
31 | def __init__(self, parent=None):
32 | super(BrowseEdit, self).__init__(parent)
33 |
34 | self.text = QtWidgets.QLineEdit()
35 | self.text.returnPressed.connect(self.apply)
36 | self.button = QtWidgets.QPushButton('B')
37 | self.button.setFixedSize(21, 21)
38 | self.button.released.connect(self.browse)
39 |
40 | self.layout = QtWidgets.QHBoxLayout(self)
41 | self.layout.setContentsMargins(0, 0, 0, 0)
42 | self.layout.setSpacing(0)
43 | self.layout.addWidget(self.text)
44 | self.layout.addWidget(self.button)
45 |
46 | self._value = self.value()
47 |
48 | def browse(self):
49 | dialog = QtWidgets.QFileDialog.getOpenFileName(self, 'select image')
50 | self.text.setText(dialog[0])
51 | self.apply()
52 |
53 | def apply(self):
54 | self.valueSet.emit(self.text.text())
55 |
56 | def value(self):
57 | value = self.text.text()
58 | return value if value != '' else None
59 |
60 | def set_value(self, value):
61 | self.text.setText(value)
62 |
63 |
64 | class WidgetToggler(QtWidgets.QPushButton):
65 | def __init__(self, label, widget, parent=None):
66 | super(WidgetToggler, self).__init__(parent)
67 | self.setStyleSheet(TOGGLER_STYLESHEET)
68 | self.setText(' v ' + label)
69 | self.widget = widget
70 | self.setCheckable(True)
71 | self.setChecked(True)
72 | self.toggled.connect(self._call_toggled)
73 |
74 | def _call_toggled(self, state):
75 | if state is True:
76 | self.widget.show()
77 | self.setText(self.text().replace('>', 'v'))
78 | else:
79 | self.widget.hide()
80 | self.setText(self.text().replace('v', '>'))
81 |
82 |
83 | class ColorEdit(QtWidgets.QWidget):
84 | valueSet = QtCore.Signal(str)
85 |
86 | def __init__(self, parent=None):
87 | super(ColorEdit, self).__init__(parent)
88 |
89 | self.text = QtWidgets.QLineEdit()
90 | self.text.returnPressed.connect(self.apply)
91 | self.button = QtWidgets.QPushButton(icon('picker.png'), '')
92 | self.button.setFixedSize(21, 21)
93 | self.button.released.connect(self.pick_color)
94 |
95 | self.layout = QtWidgets.QHBoxLayout(self)
96 | self.layout.setContentsMargins(0, 0, 0, 0)
97 | self.layout.setSpacing(0)
98 | self.layout.addWidget(self.text)
99 | self.layout.addWidget(self.button)
100 |
101 | self._value = self.value()
102 |
103 | def focusInEvent(self, event):
104 | self._value = self.value()
105 | return super(ColorEdit, self).focusInEvent(event)
106 |
107 | def focusOutEvent(self, event):
108 | self.apply()
109 | return super(ColorEdit, self).focusOutEvent(event)
110 |
111 | def pick_color(self):
112 | color = self.text.text() if self.text.text() else None
113 | dialog = ColorDialog(color)
114 | result = dialog.exec_()
115 | if result == QtWidgets.QDialog.Accepted:
116 | self.text.setText(dialog.colorname())
117 | self.apply()
118 |
119 | def apply(self):
120 | if self._value != self.value():
121 | self.valueSet.emit(self.value())
122 | self._value = self.value()
123 |
124 | def value(self):
125 | value = self.text.text()
126 | return value if value != '' else None
127 |
128 | def set_color(self, color):
129 | self.text.setText(color)
130 |
131 |
132 | class FloatEdit(QtWidgets.QLineEdit):
133 | valueSet = QtCore.Signal(float)
134 |
135 | def __init__(self, minimum=None, maximum=None, parent=None):
136 | super(FloatEdit, self).__init__(parent)
137 | self.validator = QtGui.QDoubleValidator()
138 | if minimum is not None:
139 | self.validator.setBottom(minimum)
140 | if maximum is not None:
141 | self.validator.setTop(maximum)
142 | self.setValidator(self.validator)
143 | self._value = self.value()
144 | self.returnPressed.connect(self.apply)
145 |
146 | def focusInEvent(self, event):
147 | self._value = self.value()
148 | return super(FloatEdit, self).focusInEvent(event)
149 |
150 | def focusOutEvent(self, event):
151 | self.apply()
152 | return super(FloatEdit, self).focusOutEvent(event)
153 |
154 | def apply(self):
155 | if self._value != self.value():
156 | self.valueSet.emit(self.value())
157 | self._value = self.value()
158 |
159 | def value(self):
160 | if self.text() == '':
161 | return None
162 | return float(self.text().replace(',', '.'))
163 |
164 |
165 | class Title(QtWidgets.QLabel):
166 | def __init__(self, title, parent=None):
167 | super(Title, self).__init__(parent)
168 | self.setFixedHeight(20)
169 | self.setStyleSheet('background: rgb(0, 0, 0, 25)')
170 | self.setText(' ' + title)
171 |
172 |
173 | class TouchEdit(QtWidgets.QLineEdit):
174 | def keyPressEvent(self, event):
175 | self.setText(QtGui.QKeySequence(event.key()).toString().lower())
176 | self.textEdited.emit(self.text())
177 |
178 |
179 | class CommandButton(QtWidgets.QWidget):
180 | released = QtCore.Signal()
181 | playReleased = QtCore.Signal()
182 | def __init__(self, label, parent=None):
183 | super(CommandButton, self).__init__(parent)
184 | self.mainbutton = QtWidgets.QPushButton(label)
185 | self.mainbutton.released.connect(self.released.emit)
186 | self.playbutton = QtWidgets.QPushButton(icon('play.png'), '')
187 | self.playbutton.released.connect(self.playReleased.emit)
188 | self.playbutton.setFixedSize(22, 22)
189 | self.layout = QtWidgets.QHBoxLayout(self)
190 | self.layout.setContentsMargins(0, 0, 0, 0)
191 | self.layout.setSpacing(2)
192 | self.layout.addWidget(self.mainbutton)
193 | self.layout.addWidget(self.playbutton)
--------------------------------------------------------------------------------
/hotbox_designer/dialog.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | from hotbox_designer.vendor.Qt import QtWidgets
4 | from hotbox_designer.data import (
5 | get_new_hotbox, get_valid_name, copy_hotbox_data, load_templates,
6 | ensure_old_data_compatible)
7 | from hotbox_designer.widgets import TouchEdit, BoolCombo
8 |
9 |
10 | def warning(title, message, parent=None):
11 | return QtWidgets.QMessageBox.warning(
12 | parent,
13 | title,
14 | message,
15 | QtWidgets.QMessageBox.Ok,
16 | QtWidgets.QMessageBox.Ok)
17 |
18 |
19 | def import_hotbox():
20 | try:
21 | filenames = QtWidgets.QFileDialog.getOpenFileName(
22 | None, caption='Import hotbox', directory=os.path.expanduser("~"),
23 | filter='*.json')
24 | except AttributeError:
25 | # 'dir' argument is for PySide6 module
26 | filenames = QtWidgets.QFileDialog.getOpenFileName(
27 | None, caption='Import hotbox', dir=os.path.expanduser("~"),
28 | filter='*.json')
29 | if not filenames[0]:
30 | return
31 | with open(filenames[0], 'r') as f:
32 | return ensure_old_data_compatible(json.load(f))
33 |
34 |
35 | def import_hotbox_link():
36 | filenames = QtWidgets.QFileDialog.getOpenFileName(
37 | None, caption='Import hotbox', directory=os.path.expanduser("~"),
38 | filter='*.json')
39 | if filenames:
40 | return filenames[0]
41 | return None
42 |
43 |
44 | def export_hotbox(hotbox):
45 | filenames = QtWidgets.QFileDialog.getSaveFileName(
46 | None, 'Export hotbox', os.path.expanduser("~"),
47 | '*.json')
48 | filename = filenames[0]
49 | if not filename:
50 | return
51 | if not filename.lower().endswith('.json'):
52 | filename += '.json'
53 | with open(filename, 'w') as f:
54 | json.dump(hotbox, f, indent=2)
55 |
56 |
57 | class CreateHotboxDialog(QtWidgets.QDialog):
58 | def __init__(self, hotboxes, parent=None):
59 | super(CreateHotboxDialog, self).__init__(parent)
60 | self.setWindowTitle("Create new hotbox")
61 | self.hotboxes = hotboxes
62 |
63 | self.new = QtWidgets.QRadioButton("empty hotbox")
64 | self.duplicate = QtWidgets.QRadioButton("duplicate existing hotbox")
65 | self.duplicate.setEnabled(bool(self.hotboxes))
66 | self.template = QtWidgets.QRadioButton("from template")
67 | self.groupbutton = QtWidgets.QButtonGroup()
68 | self.groupbutton.addButton(self.new, 0)
69 | self.groupbutton.addButton(self.duplicate, 1)
70 | self.groupbutton.addButton(self.template, 2)
71 | self.new.setChecked(True)
72 |
73 | self.existing = QtWidgets.QComboBox()
74 | self.existing.addItems([hb['general']['name'] for hb in self.hotboxes])
75 | self.template_combo = QtWidgets.QComboBox()
76 | items = [hb['general']['name'] for hb in load_templates()]
77 | self.template_combo.addItems(items)
78 |
79 | self.up_layout = QtWidgets.QGridLayout()
80 | self.up_layout.setContentsMargins(0, 0, 0, 0)
81 | self.up_layout.setSpacing(0)
82 | self.up_layout.addWidget(self.new, 0, 0)
83 | self.up_layout.addWidget(self.duplicate, 1, 0)
84 | self.up_layout.addWidget(self.existing, 1, 1)
85 | self.up_layout.addWidget(self.template, 2, 0)
86 | self.up_layout.addWidget(self.template_combo, 2, 1)
87 |
88 | self.ok = QtWidgets.QPushButton('ok')
89 | self.ok.released.connect(self.accept)
90 | self.cancel = QtWidgets.QPushButton('cancel')
91 | self.cancel.released.connect(self.reject)
92 |
93 | self.down_layout = QtWidgets.QHBoxLayout()
94 | self.down_layout.setContentsMargins(0, 0, 0, 0)
95 | self.down_layout.addStretch(1)
96 | self.down_layout.addWidget(self.ok)
97 | self.down_layout.addWidget(self.cancel)
98 |
99 | self.layout = QtWidgets.QVBoxLayout(self)
100 | self.layout.setSpacing(12)
101 | self.layout.addLayout(self.up_layout)
102 | self.layout.addLayout(self.down_layout)
103 |
104 | def hotbox(self):
105 | if self.groupbutton.checkedId() == 0:
106 | return get_new_hotbox(self.hotboxes)
107 | elif self.groupbutton.checkedId() == 1:
108 | name = self.existing.currentText()
109 | hotboxes = self.hotboxes
110 | elif self.groupbutton.checkedId() == 2:
111 | name = self.template_combo.currentText()
112 | hotboxes = load_templates()
113 | hotbox = [hb for hb in hotboxes if hb['general']['name'] == name][0]
114 | hotbox = copy_hotbox_data(hotbox)
115 | name = get_valid_name(hotboxes, hotbox['general']['name'])
116 | hotbox['general']['name'] = name
117 | return hotbox
118 |
119 |
120 | class CommandDisplayDialog(QtWidgets.QDialog):
121 | def __init__(self, command, parent=None):
122 | super(CommandDisplayDialog, self).__init__(parent)
123 | self.setWindowTitle("Command")
124 | self.text = QtWidgets.QTextEdit()
125 | self.text.setReadOnly(True)
126 | self.text.setPlainText(command)
127 | self.ok = QtWidgets.QPushButton('ok')
128 | self.ok.released.connect(self.accept)
129 |
130 | self.button_layout = QtWidgets.QHBoxLayout()
131 | self.button_layout.setContentsMargins(0, 0, 0, 0)
132 | self.button_layout.addStretch(1)
133 | self.button_layout.addWidget(self.ok)
134 |
135 | self.layout = QtWidgets.QVBoxLayout(self)
136 | self.layout.addWidget(self.text)
137 | self.layout.addLayout(self.button_layout)
138 |
139 |
140 | class HotkeySetter(QtWidgets.QDialog):
141 | def __init__(self, modes, parent=None):
142 | super(HotkeySetter, self).__init__(parent)
143 | self.setWindowTitle("Set hotkey")
144 | self.ctrl = BoolCombo(False)
145 | self.alt = BoolCombo(False)
146 | self.shift = BoolCombo(False)
147 | self.touch = TouchEdit()
148 | self.hotkeytype = QtWidgets.QComboBox()
149 | self.hotkeytype.addItems(modes)
150 |
151 | self.options_layout = QtWidgets.QFormLayout()
152 | self.options_layout.setContentsMargins(0, 0, 0, 0)
153 | self.options_layout.setVerticalSpacing(0)
154 | self.options_layout.addRow("Ctrl", self.ctrl)
155 | self.options_layout.addRow("Alt", self.alt)
156 | self.options_layout.addRow("Shift", self.shift)
157 | self.options_layout.addRow("Touch", self.touch)
158 | self.options_layout.addRow("Hotkey event", self.hotkeytype)
159 |
160 | self.ok = QtWidgets.QPushButton("ok")
161 | self.ok.released.connect(self.accept)
162 | self.cancel = QtWidgets.QPushButton("cancel")
163 | self.cancel.released.connect(self.reject)
164 |
165 | self.button_layout = QtWidgets.QHBoxLayout()
166 | self.button_layout.setContentsMargins(0, 0, 0, 0)
167 | self.button_layout.addStretch(1)
168 | self.button_layout.addWidget(self.ok)
169 | self.button_layout.addWidget(self.cancel)
170 |
171 | self.layout = QtWidgets.QVBoxLayout(self)
172 | self.layout.addLayout(self.options_layout)
173 | self.layout.addLayout(self.button_layout)
174 |
175 | def get_key_sequence(self):
176 | sequence = ''
177 | if self.ctrl.state() is True:
178 | sequence += "Ctrl+"
179 | if self.alt.state() is True:
180 | sequence += "Alt+"
181 | if self.shift.state() is True:
182 | sequence += "Shift+"
183 | sequence += self.touch.text()
184 | return sequence
185 |
186 | def mode(self):
187 | return self.hotkeytype.currentText()
188 |
--------------------------------------------------------------------------------
/hotbox_designer/designer/menu.py:
--------------------------------------------------------------------------------
1 | from hotbox_designer.vendor.Qt import QtGui, QtWidgets, QtCore
2 | from hotbox_designer.qtutils import icon
3 |
4 |
5 | class MenuWidget(QtWidgets.QWidget):
6 | deleteRequested = QtCore.Signal()
7 | copyRequested = QtCore.Signal()
8 | pasteRequested = QtCore.Signal()
9 | undoRequested = QtCore.Signal()
10 | redoRequested = QtCore.Signal()
11 | sizeChanged = QtCore.Signal()
12 | useSnapToggled = QtCore.Signal(bool)
13 | snapValuesChanged = QtCore.Signal()
14 | editCenterToggled = QtCore.Signal(bool)
15 | centerValuesChanged = QtCore.Signal(int, int)
16 | addButtonRequested = QtCore.Signal()
17 | addTextRequested = QtCore.Signal()
18 | addBackgroundRequested = QtCore.Signal()
19 | onBottomRequested = QtCore.Signal()
20 | moveDownRequested = QtCore.Signal()
21 | moveUpRequested = QtCore.Signal()
22 | onTopRequested = QtCore.Signal()
23 |
24 | def __init__(self, parent=None):
25 | super(MenuWidget, self).__init__(parent=parent)
26 | self.delete = QtWidgets.QAction(icon('delete.png'), '', self)
27 | self.delete.setToolTip('Delete selection')
28 | self.delete.triggered.connect(self.deleteRequested.emit)
29 | self.copy = QtWidgets.QAction(icon('copy.png'), '', self)
30 | self.copy.setToolTip('Copy selection')
31 | self.copy.triggered.connect(self.copyRequested.emit)
32 | self.paste = QtWidgets.QAction(icon('paste.png'), '', self)
33 | self.paste.setToolTip('Paste')
34 | self.paste.triggered.connect(self.pasteRequested.emit)
35 |
36 | self.undo = QtWidgets.QAction(icon('undo.png'), '', self)
37 | self.undo.setToolTip('Undo')
38 | self.undo.triggered.connect(self.undoRequested.emit)
39 | self.redo = QtWidgets.QAction(icon('redo.png'), '', self)
40 | self.redo.setToolTip('Redo')
41 | self.redo.triggered.connect(self.redoRequested.emit)
42 |
43 | validator = QtGui.QIntValidator()
44 | self.hbwidth = QtWidgets.QLineEdit('600')
45 | self.hbwidth.setFixedWidth(35)
46 | self.hbwidth.setValidator(validator)
47 | self.hbwidth.textEdited.connect(self.size_changed)
48 | self.hbheight = QtWidgets.QLineEdit('300')
49 | self.hbheight.setFixedWidth(35)
50 | self.hbheight.setValidator(validator)
51 | self.hbheight.textEdited.connect(self.size_changed)
52 |
53 | icon_ = icon('center.png')
54 | self.editcenter = QtWidgets.QAction(icon_, '', self)
55 | self.editcenter.setToolTip('Edit center')
56 | self.editcenter.setCheckable(True)
57 | self.editcenter.triggered.connect(self.edit_center_toggled)
58 | validator = QtGui.QIntValidator()
59 | self.editcenterx = QtWidgets.QLineEdit('10')
60 | self.editcenterx.setFixedWidth(35)
61 | self.editcenterx.setValidator(validator)
62 | self.editcenterx.textEdited.connect(self.center_values_changed)
63 | self.editcentery = QtWidgets.QLineEdit('10')
64 | self.editcentery.setFixedWidth(35)
65 | self.editcentery.setValidator(validator)
66 | self.editcentery.textEdited.connect(self.center_values_changed)
67 |
68 | self.snap = QtWidgets.QAction(icon('snap.png'), '', self)
69 | self.snap.setToolTip('Snap grid enable')
70 | self.snap.setCheckable(True)
71 | self.snap.triggered.connect(self.snap_toggled)
72 | validator = QtGui.QIntValidator(5, 150)
73 | self.snapx = QtWidgets.QLineEdit('10')
74 | self.snapx.setFixedWidth(35)
75 | self.snapx.setValidator(validator)
76 | self.snapx.setEnabled(False)
77 | self.snapx.textEdited.connect(self.snap_value_changed)
78 | self.snapy = QtWidgets.QLineEdit('10')
79 | self.snapy.setFixedWidth(35)
80 | self.snapy.setValidator(validator)
81 | self.snapy.setEnabled(False)
82 | self.snapy.textEdited.connect(self.snap_value_changed)
83 | self.snap.toggled.connect(self.snapx.setEnabled)
84 | self.snap.toggled.connect(self.snapy.setEnabled)
85 |
86 | icon_ = icon('addbutton.png')
87 | self.addbutton = QtWidgets.QAction(icon_, '', self)
88 | self.addbutton.setToolTip('Add button')
89 | self.addbutton.triggered.connect(self.addButtonRequested.emit)
90 | self.addtext = QtWidgets.QAction(icon('addtext.png'), '', self)
91 | self.addtext.setToolTip('Add text')
92 | self.addtext.triggered.connect(self.addTextRequested.emit)
93 | self.addbg = QtWidgets.QAction(icon('addbg.png'), '', self)
94 | self.addbg.setToolTip('Add background shape')
95 | self.addbg.triggered.connect(self.addBackgroundRequested.emit)
96 |
97 | icon_ = icon('onbottom.png')
98 | self.onbottom = QtWidgets.QAction(icon_, '', self)
99 | self.onbottom.setToolTip('Set selected shapes on bottom')
100 | self.onbottom.triggered.connect(self.onBottomRequested.emit)
101 | icon_ = icon('movedown.png')
102 | self.movedown = QtWidgets.QAction(icon_, '', self)
103 | self.movedown.setToolTip('Move down selected shapes')
104 | self.movedown.triggered.connect(self.moveDownRequested.emit)
105 | self.moveup = QtWidgets.QAction(icon('moveup.png'), '', self)
106 | self.moveup.setToolTip('Move up selected shapes')
107 | self.moveup.triggered.connect(self.moveUpRequested.emit)
108 | self.ontop = QtWidgets.QAction(icon('ontop.png'), '', self)
109 | self.ontop.setToolTip('Set selected shapes on top')
110 | self.ontop.triggered.connect(self.onTopRequested.emit)
111 |
112 | self.toolbar = QtWidgets.QToolBar()
113 | self.toolbar.addAction(self.delete)
114 | self.toolbar.addAction(self.copy)
115 | self.toolbar.addAction(self.paste)
116 | self.toolbar.addSeparator()
117 | self.toolbar.addAction(self.undo)
118 | self.toolbar.addAction(self.redo)
119 | self.toolbar.addSeparator()
120 | self.toolbar.addAction(self.snap)
121 | self.toolbar.addWidget(self.snapx)
122 | self.toolbar.addWidget(self.snapy)
123 | self.toolbar.addSeparator()
124 | self.toolbar.addWidget(QtWidgets.QLabel('size'))
125 | self.toolbar.addWidget(self.hbwidth)
126 | self.toolbar.addWidget(self.hbheight)
127 | self.toolbar.addSeparator()
128 | self.toolbar.addAction(self.editcenter)
129 | self.toolbar.addWidget(self.editcenterx)
130 | self.toolbar.addWidget(self.editcentery)
131 | self.toolbar.addSeparator()
132 | self.toolbar.addAction(self.addbutton)
133 | self.toolbar.addAction(self.addtext)
134 | self.toolbar.addAction(self.addbg)
135 | self.toolbar.addSeparator()
136 | self.toolbar.addAction(self.onbottom)
137 | self.toolbar.addAction(self.movedown)
138 | self.toolbar.addAction(self.moveup)
139 | self.toolbar.addAction(self.ontop)
140 |
141 | self.layout = QtWidgets.QVBoxLayout(self)
142 | self.layout.setContentsMargins(0, 0, 10, 0)
143 | self.layout.addWidget(self.toolbar)
144 |
145 | def size_changed(self, *_):
146 | self.sizeChanged.emit()
147 |
148 | def edit_center_toggled(self):
149 | self.editCenterToggled.emit(self.editcenter.isChecked())
150 |
151 | def snap_toggled(self):
152 | self.useSnapToggled.emit(self.snap.isChecked())
153 |
154 | def snap_values(self):
155 | x = int(self.snapx.text()) if self.snapx.text() else 1
156 | y = int(self.snapy.text()) if self.snapy.text() else 1
157 | x = x if x > 0 else 1
158 | y = y if y > 0 else 1
159 | return x, y
160 |
161 | def snap_value_changed(self, _):
162 | self.snapValuesChanged.emit()
163 |
164 | def set_center_values(self, x, y):
165 | self.editcenterx.setText(str(x))
166 | self.editcentery.setText(str(y))
167 |
168 | def center_values_changed(self, _):
169 | x = int(self.editcenterx.text()) if self.editcenterx.text() else 0
170 | y = int(self.editcentery.text()) if self.editcentery.text() else 0
171 | self.centerValuesChanged.emit(x, y)
172 |
173 | def set_size_values(self, width, height):
174 | self.hbwidth.setText(str(width))
175 | self.hbheight.setText(str(height))
176 | self.sizeChanged.emit()
177 |
178 | def get_size(self):
179 | width = int(self.hbwidth.text()) if self.hbwidth.text() else 1
180 | height = int(self.hbheight.text()) if self.hbheight.text() else 1
181 | return QtCore.QSize(width, height)
182 |
--------------------------------------------------------------------------------
/hotbox_designer/designer/editarea.py:
--------------------------------------------------------------------------------
1 |
2 | from hotbox_designer.vendor.Qt import QtCore, QtGui, QtWidgets
3 |
4 | from hotbox_designer.interactive import Manipulator, SelectionSquare
5 | from hotbox_designer.geometry import Transform, snap, get_combined_rects
6 | from hotbox_designer.painting import draw_editor, draw_editor_center
7 | from hotbox_designer.qtutils import get_cursor
8 |
9 |
10 | class ShapeEditArea(QtWidgets.QWidget):
11 | selectedShapesChanged = QtCore.Signal()
12 | increaseUndoStackRequested = QtCore.Signal()
13 | centerMoved = QtCore.Signal(int, int)
14 |
15 | def __init__(self, options, parent=None):
16 | super(ShapeEditArea, self).__init__(parent)
17 | self.setFixedSize(750, 550)
18 | self.setMouseTracking(True)
19 | self.options = options
20 |
21 | self.selection = Selection()
22 | self.selection_square = SelectionSquare()
23 | self.manipulator = Manipulator()
24 | self.transform = Transform()
25 |
26 | self.shapes = []
27 | self.clicked_shape = None
28 | self.clicked = False
29 | self.handeling = False
30 | self.manipulator_moved = False
31 | self.edit_center_mode = False
32 | self.increase_undo_on_release = False
33 |
34 | self.ctrl_pressed = False
35 | self.shit_pressed = False
36 |
37 | def mouseMoveEvent(self, _):
38 | cursor = get_cursor(self)
39 | if self.edit_center_mode is True:
40 | if self.clicked is False:
41 | return
42 | if self.transform.snap:
43 | x, y = snap(cursor.x(), cursor.y(), self.transform.snap)
44 | else:
45 | x, y = cursor.x(), cursor.y()
46 | self.centerMoved.emit(x, y)
47 | self.increase_undo_on_release = True
48 | self.repaint()
49 | return
50 |
51 | for shape in self.shapes:
52 | shape.set_hovered(cursor)
53 |
54 | if self.selection_square.handeling:
55 | self.selection_square.handle(cursor)
56 |
57 | if self.handeling is False:
58 | return self.repaint()
59 |
60 | self.manipulator_moved = True
61 | rect = self.manipulator.rect
62 | if self.transform.direction:
63 | self.transform.resize([s.rect for s in self.selection], cursor)
64 | self.manipulator.update_geometries()
65 | elif rect is not None and rect.contains(cursor):
66 | self.transform.move([s.rect for s in self.selection], cursor)
67 | self.manipulator.update_geometries()
68 | for shape in self.shapes:
69 | shape.synchronize_rect()
70 | shape.synchronize_image()
71 | self.increase_undo_on_release = True
72 | self.selectedShapesChanged.emit()
73 | self.repaint()
74 |
75 | def mousePressEvent(self, _):
76 | self.setFocus(QtCore.Qt.MouseFocusReason)
77 | cursor = get_cursor(self)
78 | direction = self.manipulator.get_direction(cursor)
79 | self.clicked = True
80 | self.transform.direction = direction
81 |
82 | self.manipulator_moved = False
83 | rect = self.manipulator.rect
84 | if rect is not None:
85 | self.transform.set_rect(rect)
86 | self.transform.reference_rect = QtCore.QRectF(rect)
87 |
88 | self.clicked_shape = None
89 | for shape in reversed(self.shapes):
90 | if shape.rect.contains(cursor):
91 | self.clicked_shape = shape
92 | break
93 |
94 | if rect and rect.contains(cursor):
95 | self.transform.set_reference_point(cursor)
96 | handeling = bool(direction or rect.contains(cursor) if rect else False)
97 |
98 | self.handeling = handeling
99 | if not self.handeling:
100 | self.selection_square.clicked(cursor)
101 |
102 | self.repaint()
103 |
104 | def mouseReleaseEvent(self, _):
105 | if self.edit_center_mode is True:
106 | self.clicked = False
107 | return
108 |
109 | shape = self.clicked_shape
110 | selection_update_conditions = (
111 | self.handeling is False
112 | or shape not in self.selection
113 | and self.manipulator_moved is False)
114 | if selection_update_conditions:
115 | self.selection.set([shape] if shape else None)
116 | self.update_selection()
117 |
118 | if self.selection_square.handeling:
119 | shapes = [
120 | s for s in self.shapes
121 | if s.rect.intersects(self.selection_square.rect)]
122 | if shapes:
123 | self.selection.set(shapes)
124 | rects = [shape.rect for shape in self.selection]
125 | self.manipulator.set_rect(get_combined_rects(rects))
126 | self.selectedShapesChanged.emit()
127 | self.selection_square.release()
128 |
129 | if self.increase_undo_on_release:
130 | self.increaseUndoStackRequested.emit()
131 | self.increase_undo_on_release = False
132 |
133 | self.clicked = False
134 | self.handeling = False
135 | self.repaint()
136 |
137 | def keyPressEvent(self, event):
138 | if event.key() == QtCore.Qt.Key_Shift:
139 | self.transform.square = True
140 | self.shit_pressed = True
141 |
142 | if event.key() == QtCore.Qt.Key_Control:
143 | self.ctrl_pressed = True
144 |
145 | self.selection.mode = get_selection_mode(
146 | shift=self.shit_pressed,
147 | ctrl=self.ctrl_pressed)
148 |
149 | self.repaint()
150 |
151 | def keyReleaseEvent(self, event):
152 | if event.key() == QtCore.Qt.Key_Shift:
153 | self.transform.square = False
154 | self.shit_pressed = False
155 |
156 | if event.key() == QtCore.Qt.Key_Control:
157 | self.ctrl_pressed = False
158 |
159 | self.selection.mode = get_selection_mode(
160 | shift=self.shit_pressed,
161 | ctrl=self.ctrl_pressed)
162 |
163 | self.repaint()
164 |
165 | def update_selection(self):
166 | rects = [shape.rect for shape in self.selection]
167 | self.manipulator.set_rect(get_combined_rects(rects))
168 | self.selectedShapesChanged.emit()
169 |
170 | def paintEvent(self, _):
171 | painter = QtGui.QPainter()
172 | painter.begin(self)
173 | self.paint(painter)
174 | painter.end()
175 |
176 | def paint(self, painter):
177 | painter.setRenderHint(QtGui.QPainter.Antialiasing)
178 | draw_editor(painter, self.rect(), snap=self.transform.snap)
179 | for shape in self.shapes:
180 | shape.draw(painter)
181 | self.manipulator.draw(painter, get_cursor(self))
182 | self.selection_square.draw(painter)
183 | if self.edit_center_mode is True:
184 | point = self.options['centerx'], self.options['centery']
185 | draw_editor_center(painter, self.rect(), point)
186 |
187 |
188 | class Selection():
189 | def __init__(self):
190 | self.shapes = []
191 | self.mode = 'replace'
192 |
193 | def set(self, shapes):
194 | if self.mode == 'add':
195 | if shapes is None:
196 | return
197 | return self.add(shapes)
198 | elif self.mode == 'replace':
199 | if shapes is None:
200 | return self.clear()
201 | return self.replace(shapes)
202 | elif self.mode == 'invert':
203 | if shapes is None:
204 | return
205 | return self.invert(shapes)
206 | elif self.mode == 'remove':
207 | if shapes is None:
208 | return
209 | for shape in shapes:
210 | if shape in self.shapes:
211 | self.remove(shape)
212 |
213 | def replace(self, shapes):
214 | self.shapes = shapes
215 |
216 | def add(self, shapes):
217 | self.shapes.extend([s for s in shapes if s not in self])
218 |
219 | def remove(self, shape):
220 | self.shapes.remove(shape)
221 |
222 | def invert(self, shapes):
223 | for shape in shapes:
224 | if shape not in self.shapes:
225 | self.add([shape])
226 | else:
227 | self.remove(shape)
228 |
229 | def clear(self):
230 | self.shapes = []
231 |
232 | def __iter__(self):
233 | return self.shapes.__iter__()
234 |
235 |
236 | def get_selection_mode(ctrl, shift):
237 | if not ctrl and not shift:
238 | return 'replace'
239 | elif ctrl and shift:
240 | return 'invert'
241 | elif shift and not ctrl:
242 | return 'add'
243 | elif ctrl and not shift:
244 | return 'remove'
245 |
--------------------------------------------------------------------------------
/hotbox_designer/resources/templates/marking2.json:
--------------------------------------------------------------------------------
1 | {
2 | "shapes": [
3 | {
4 | "bgcolor.normal": "#424242",
5 | "borderwidth.hovered": 1.25,
6 | "borderwidth.normal": 1.0,
7 | "image.width": 32,
8 | "bordercolor.transparency": 0,
9 | "shape": "square",
10 | "borderwidth.clicked": 2,
11 | "action.right.close": false,
12 | "border": true,
13 | "action.right": false,
14 | "text.valign": "center",
15 | "bordercolor.hovered": "#000000",
16 | "image.path": "",
17 | "action.left": true,
18 | "image.height": 32,
19 | "bordercolor.clicked": "#000000",
20 | "text.content": "Action 1",
21 | "action.left.close": true,
22 | "bordercolor.normal": "#000000",
23 | "shape.height": 21.5,
24 | "text.bold": false,
25 | "bgcolor.clicked": "#65aae6",
26 | "action.left.language": "python",
27 | "shape.width": 170.0,
28 | "action.right.command": "",
29 | "action.left.command": "",
30 | "image.fit": true,
31 | "shape.top": 320.0,
32 | "bgcolor.hovered": "#65aae6",
33 | "text.italic": false,
34 | "bgcolor.transparency": 0,
35 | "action.right.language": "python",
36 | "text.color": "#FFFFFF",
37 | "text.size": 12,
38 | "text.halign": "center",
39 | "shape.left": 150.0
40 | },
41 | {
42 | "bgcolor.normal": "#424242",
43 | "borderwidth.hovered": 1.25,
44 | "borderwidth.normal": 1.0,
45 | "image.width": 32,
46 | "bordercolor.transparency": 0,
47 | "shape": "square",
48 | "borderwidth.clicked": 2,
49 | "action.right.close": false,
50 | "border": true,
51 | "action.right": false,
52 | "text.valign": "center",
53 | "bordercolor.hovered": "#000000",
54 | "image.path": "",
55 | "action.left": true,
56 | "image.height": 32,
57 | "bordercolor.clicked": "#000000",
58 | "text.content": "Action 2",
59 | "action.left.close": true,
60 | "bordercolor.normal": "#000000",
61 | "shape.height": 21.5,
62 | "text.bold": false,
63 | "bgcolor.clicked": "#65aae6",
64 | "action.left.language": "python",
65 | "shape.width": 170.0,
66 | "action.right.command": "",
67 | "action.left.command": "",
68 | "image.fit": true,
69 | "shape.top": 240.0,
70 | "bgcolor.hovered": "#65aae6",
71 | "text.italic": false,
72 | "bgcolor.transparency": 0,
73 | "action.right.language": "python",
74 | "text.color": "#FFFFFF",
75 | "text.size": 12,
76 | "text.halign": "center",
77 | "shape.left": 190.0
78 | },
79 | {
80 | "bgcolor.normal": "#424242",
81 | "borderwidth.hovered": 1.25,
82 | "borderwidth.normal": 1.0,
83 | "image.width": 32,
84 | "bordercolor.transparency": 0,
85 | "shape": "square",
86 | "borderwidth.clicked": 2,
87 | "action.right.close": false,
88 | "border": true,
89 | "action.right": false,
90 | "text.valign": "center",
91 | "bordercolor.hovered": "#000000",
92 | "image.path": "",
93 | "action.left": true,
94 | "image.height": 32,
95 | "bordercolor.clicked": "#000000",
96 | "text.content": "Action 3",
97 | "action.left.close": true,
98 | "bordercolor.normal": "#000000",
99 | "shape.height": 21.5,
100 | "text.bold": false,
101 | "bgcolor.clicked": "#65aae6",
102 | "action.left.language": "python",
103 | "shape.width": 170.0,
104 | "action.right.command": "",
105 | "action.left.command": "",
106 | "image.fit": true,
107 | "shape.top": 160.0,
108 | "bgcolor.hovered": "#65aae6",
109 | "text.italic": false,
110 | "bgcolor.transparency": 0,
111 | "action.right.language": "python",
112 | "text.color": "#FFFFFF",
113 | "text.size": 12,
114 | "text.halign": "center",
115 | "shape.left": 340.0
116 | },
117 | {
118 | "bgcolor.normal": "#424242",
119 | "borderwidth.hovered": 1.25,
120 | "borderwidth.normal": 1.0,
121 | "image.width": 32,
122 | "bordercolor.transparency": 0,
123 | "shape": "square",
124 | "borderwidth.clicked": 2,
125 | "action.right.close": false,
126 | "border": true,
127 | "action.right": false,
128 | "text.valign": "center",
129 | "bordercolor.hovered": "#000000",
130 | "image.path": "",
131 | "action.left": true,
132 | "image.height": 32,
133 | "bordercolor.clicked": "#000000",
134 | "text.content": "Action 7",
135 | "action.left.close": true,
136 | "bordercolor.normal": "#000000",
137 | "shape.height": 21.5,
138 | "text.bold": false,
139 | "bgcolor.clicked": "#65aae6",
140 | "action.left.language": "python",
141 | "shape.width": 170.0,
142 | "action.right.command": "",
143 | "action.left.command": "",
144 | "image.fit": true,
145 | "shape.top": 417.0,
146 | "bgcolor.hovered": "#65aae6",
147 | "text.italic": false,
148 | "bgcolor.transparency": 0,
149 | "action.right.language": "python",
150 | "text.color": "#FFFFFF",
151 | "text.size": 12,
152 | "text.halign": "center",
153 | "shape.left": 240.0
154 | },
155 | {
156 | "bgcolor.normal": "#424242",
157 | "borderwidth.hovered": 1.25,
158 | "borderwidth.normal": 1.0,
159 | "image.width": 32,
160 | "bordercolor.transparency": 0,
161 | "shape": "square",
162 | "borderwidth.clicked": 2,
163 | "action.right.close": false,
164 | "border": true,
165 | "action.right": false,
166 | "text.valign": "center",
167 | "bordercolor.hovered": "#000000",
168 | "image.path": "",
169 | "action.left": true,
170 | "image.height": 32,
171 | "bordercolor.clicked": "#000000",
172 | "text.content": "Action 4",
173 | "action.left.close": true,
174 | "bordercolor.normal": "#000000",
175 | "shape.height": 21.5,
176 | "text.bold": false,
177 | "bgcolor.clicked": "#65aae6",
178 | "action.left.language": "python",
179 | "shape.width": 170.0,
180 | "action.right.command": "",
181 | "action.left.command": "",
182 | "image.fit": true,
183 | "shape.top": 240.0,
184 | "bgcolor.hovered": "#65aae6",
185 | "text.italic": false,
186 | "bgcolor.transparency": 0,
187 | "action.right.language": "python",
188 | "text.color": "#FFFFFF",
189 | "text.size": 12,
190 | "text.halign": "center",
191 | "shape.left": 480.0
192 | },
193 | {
194 | "bgcolor.normal": "#424242",
195 | "borderwidth.hovered": 1.25,
196 | "borderwidth.normal": 1.0,
197 | "image.width": 32,
198 | "bordercolor.transparency": 0,
199 | "shape": "square",
200 | "borderwidth.clicked": 2,
201 | "action.right.close": false,
202 | "border": true,
203 | "action.right": false,
204 | "text.valign": "center",
205 | "bordercolor.hovered": "#000000",
206 | "image.path": "",
207 | "action.left": true,
208 | "image.height": 32,
209 | "bordercolor.clicked": "#000000",
210 | "text.content": "Action 5",
211 | "action.left.close": true,
212 | "bordercolor.normal": "#000000",
213 | "shape.height": 21.5,
214 | "text.bold": false,
215 | "bgcolor.clicked": "#65aae6",
216 | "action.left.language": "python",
217 | "shape.width": 170.0,
218 | "action.right.command": "",
219 | "action.left.command": "",
220 | "image.fit": true,
221 | "shape.top": 320.0,
222 | "bgcolor.hovered": "#65aae6",
223 | "text.italic": false,
224 | "bgcolor.transparency": 0,
225 | "action.right.language": "python",
226 | "text.color": "#FFFFFF",
227 | "text.size": 12,
228 | "text.halign": "center",
229 | "shape.left": 530.0
230 | },
231 | {
232 | "bgcolor.normal": "#424242",
233 | "borderwidth.hovered": 1.25,
234 | "borderwidth.normal": 1.0,
235 | "image.width": 32,
236 | "bordercolor.transparency": 0,
237 | "shape": "square",
238 | "borderwidth.clicked": 2,
239 | "action.right.close": false,
240 | "border": true,
241 | "action.right": false,
242 | "text.valign": "center",
243 | "bordercolor.hovered": "#000000",
244 | "image.path": "",
245 | "action.left": true,
246 | "image.height": 32,
247 | "bordercolor.clicked": "#000000",
248 | "text.content": "Action 6",
249 | "action.left.close": true,
250 | "bordercolor.normal": "#000000",
251 | "shape.height": 21.5,
252 | "text.bold": false,
253 | "bgcolor.clicked": "#65aae6",
254 | "action.left.language": "python",
255 | "shape.width": 170.0,
256 | "action.right.command": "",
257 | "action.left.command": "",
258 | "image.fit": true,
259 | "shape.top": 417.0,
260 | "bgcolor.hovered": "#65aae6",
261 | "text.italic": false,
262 | "bgcolor.transparency": 0,
263 | "action.right.language": "python",
264 | "text.color": "#FFFFFF",
265 | "text.size": 12,
266 | "text.halign": "center",
267 | "shape.left": 443.0
268 | }
269 | ],
270 | "general": {
271 | "submenu": false,
272 | "name": "Markin_Menu_7_Actions",
273 | "height": 600,
274 | "width": 900,
275 | "centerx": 430,
276 | "centery": 310,
277 | "aiming": true,
278 | "leaveclose": true,
279 | "triggering": "click only",
280 | "leaveclose": false
281 | }
282 | }
--------------------------------------------------------------------------------
/hotbox_designer/colorwheel.py:
--------------------------------------------------------------------------------
1 | import math
2 | from hotbox_designer.vendor.Qt import QtWidgets, QtGui, QtCore
3 | from hotbox_designer.qtutils import get_cursor
4 | from hotbox_designer.geometry import (
5 | get_relative_point, get_point_on_line, get_absolute_angle_c)
6 |
7 |
8 | CONICAL_GRADIENT = (
9 | (0.0, (0, 255, 255)),
10 | (0.16, (0, 0, 255)),
11 | (0.33, (255, 0, 255)),
12 | (0.5, (255, 0, 0)),
13 | (0.66, (255, 255, 0)),
14 | (0.83, (0, 255, 0)),
15 | (1.0, (0, 255, 255)))
16 | TRANSPARENT = 0, 0, 0, 0
17 | BLACK = 'black'
18 | WHITE = 'white'
19 |
20 |
21 | class ColorDialog(QtWidgets.QDialog):
22 | def __init__(self, hexacolor, parent=None):
23 | super(ColorDialog, self).__init__(parent)
24 | self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
25 | self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
26 | self.colorwheel = ColorWheel()
27 | self.colorwheel.set_current_color(QtGui.QColor(hexacolor))
28 | self.ok = QtWidgets.QPushButton('ok')
29 | self.ok.released.connect(self.accept)
30 |
31 | self.layout = QtWidgets.QVBoxLayout(self)
32 | self.layout.setContentsMargins(0, 0, 0, 0)
33 | self.layout.setSpacing(0)
34 | self.layout.addWidget(self.colorwheel)
35 | self.layout.addWidget(self.ok)
36 |
37 | def colorname(self):
38 | return self.colorwheel.current_color().name()
39 |
40 | def exec_(self):
41 | point = get_cursor(self)
42 | point.setX(point.x() - 50)
43 | point.setY(point.y() - 75)
44 | self.move(point)
45 | result = super(ColorDialog, self).exec_()
46 | return result
47 |
48 |
49 | class ColorWheel(QtWidgets.QWidget):
50 | currentColorChanged = QtCore.Signal(QtGui.QColor)
51 |
52 | def __init__(self, parent=None):
53 | super(ColorWheel, self).__init__(parent)
54 | self._is_clicked = False
55 | self._rect = QtCore.QRect(25, 25, 50, 50)
56 | self._current_color = QtGui.QColor(WHITE)
57 | self._color_point = QtCore.QPoint(150, 50)
58 | self._current_tool = None
59 | self._angle = 180
60 | self.setFixedSize(100, 100)
61 | self.initUI()
62 |
63 | def initUI(self):
64 | self._conicalGradient = QtGui.QConicalGradient(
65 | self.width() / 2, self.height() / 2, 180)
66 | for pos, (r, g, b) in CONICAL_GRADIENT:
67 | self._conicalGradient.setColorAt(pos, QtGui.QColor(r, g, b))
68 |
69 | top = self._rect.top()
70 | bottom = self._rect.top() + self._rect.height()
71 | self._vertical_gradient = QtGui.QLinearGradient(0, top, 0, bottom)
72 | self._vertical_gradient.setColorAt(0.0, QtGui.QColor(*TRANSPARENT))
73 | self._vertical_gradient.setColorAt(1.0, QtGui.QColor(BLACK))
74 |
75 | left = self._rect.left()
76 | right = self._rect.left() + self._rect.width()
77 | self._horizontal_gradient = QtGui.QLinearGradient(left, 0, right, 0)
78 | self._horizontal_gradient.setColorAt(0.0, QtGui.QColor(WHITE))
79 |
80 | def paintEvent(self, _):
81 | painter = QtGui.QPainter()
82 | painter.begin(self)
83 | self.paint(painter)
84 | painter.end()
85 |
86 | def mousePressEvent(self, event):
87 | if self._rect.contains(event.pos()):
88 | self._current_tool = 'rect'
89 | else:
90 | self._current_tool = 'wheel'
91 | self.mouse_update(event)
92 |
93 | def mouseMoveEvent(self, event):
94 | self._is_clicked = True
95 | self.mouse_update(event)
96 |
97 | def mouse_update(self, event):
98 | if self._current_tool == 'rect':
99 | self.color_point = event.pos()
100 | else:
101 | center = self._get_center()
102 | a = QtCore.QPoint(event.pos().x(), center.y())
103 | self._angle = get_absolute_angle_c(a=a, b=event.pos(), c=center)
104 |
105 | self.repaint()
106 | self.currentColorChanged.emit(self.current_color())
107 |
108 | def mouseReleaseEvent(self, event):
109 | self._is_clicked = False
110 |
111 | def paint(self, painter):
112 | painter.setRenderHint(QtGui.QPainter.Antialiasing)
113 |
114 | pen = QtGui.QPen(QtGui.QColor(0, 0, 0, 0))
115 | pen.setWidth(0)
116 | pen.setJoinStyle(QtCore.Qt.MiterJoin)
117 |
118 | painter.setBrush(self._conicalGradient)
119 | painter.setPen(pen)
120 | painter.drawRoundedRect(
121 | 6, 6, (self.width() - 12), (self.height() - 12),
122 | self.width(), self.height())
123 |
124 | painter.setBrush(self.palette().color(QtGui.QPalette.Background))
125 | painter.drawRoundedRect(
126 | 12.5, 12.5, (self.width() - 25), (self.height() - 25),
127 | self.width(), self.height())
128 |
129 | self._horizontal_gradient.setColorAt(
130 | 1.0, self._get_current_wheel_color())
131 | painter.setBrush(self._horizontal_gradient)
132 | painter.drawRect(self._rect)
133 |
134 | painter.setBrush(self._vertical_gradient)
135 | painter.drawRect(self._rect)
136 |
137 | pen.setColor(QtGui.QColor(BLACK))
138 | pen.setWidth(3)
139 | painter.setPen(pen)
140 |
141 | angle = math.radians(self._angle)
142 | painter.drawLine(
143 | get_point_on_line(angle, 37),
144 | get_point_on_line(angle, 46))
145 |
146 | pen.setWidth(5)
147 | pen.setCapStyle(QtCore.Qt.RoundCap)
148 | painter.setPen(pen)
149 | painter.drawPoint(self._color_point)
150 |
151 | @property
152 | def color_point(self):
153 | return self._color_point
154 |
155 | @color_point.setter
156 | def color_point(self, point):
157 | if point.x() < self._rect.left():
158 | x = self._rect.left()
159 | elif point.x() > self._rect.left() + self._rect.width():
160 | x = self._rect.left() + self._rect.width()
161 | else:
162 | x = point.x()
163 |
164 | if point.y() < self._rect.top():
165 | y = self._rect.top()
166 | elif point.y() > self._rect.top() + self._rect.height():
167 | y = self._rect.top() + self._rect.height()
168 | else:
169 | y = point.y()
170 |
171 | self._color_point = QtCore.QPoint(x, y)
172 |
173 | def _get_current_wheel_color(self):
174 | degree = 360 - self._angle
175 | return QtGui.QColor(*degree_to_color(degree))
176 |
177 | def _get_center(self):
178 | return QtCore.QPoint(self.width() / 2, self.height() / 2)
179 |
180 | def current_color(self):
181 | point = get_relative_point(self._rect, self.color_point)
182 | x_factor = 1.0 - (float(point.x()) / self._rect.width())
183 | y_factor = 1.0 - (float(point.y()) / self._rect.height())
184 | r, g, b, _ = self._get_current_wheel_color().getRgb()
185 |
186 | # fade to white
187 | differences = 255.0 - r, 255.0 - g, 255.0 - b
188 | r += round(differences[0] * x_factor)
189 | g += round(differences[1] * x_factor)
190 | b += round(differences[2] * x_factor)
191 |
192 | # fade to black
193 | r = round(r * y_factor)
194 | g = round(g * y_factor)
195 | b = round(b * y_factor)
196 |
197 | return QtGui.QColor(r, g, b)
198 |
199 | def set_current_color(self, color):
200 | [r, g, b] = color.getRgb()[:3]
201 | self._angle = 360.0 - (QtGui.QColor(r, g, b).getHslF()[0] * 360.0)
202 | self._angle = self._angle if self._angle != 720.0 else 0
203 |
204 | x = ((((
205 | sorted([r, g, b], reverse=True)[0] -
206 | sorted([r, g, b])[0]) / 255.0) * self._rect.width()) +
207 | self._rect.left())
208 |
209 | y = ((((
210 | 255 - (sorted([r, g, b], reverse=True)[0])) / 255.0) *
211 | self._rect.height()) + self._rect.top())
212 |
213 | self._current_color = color
214 | self._color_point = QtCore.QPoint(x, y)
215 | self.repaint()
216 |
217 |
218 | def degree_to_color(degree):
219 | if degree is None:
220 | return None
221 | degree = degree / 360.0
222 |
223 | r, g, b = 255.0, 255.0, 255.0
224 | contain_red = (
225 | (degree >= 0.0 and degree <= 0.33)
226 | or (degree >= 0.66 and degree <= 1.0))
227 |
228 | if contain_red:
229 | if degree >= 0.66 and degree <= 0.83:
230 | factor = degree - 0.66
231 | r = round(255 * (factor / .16))
232 | if (degree > 0.0 and degree < 0.16) or (degree > 0.83 and degree < 1.0):
233 | r = 255
234 | elif degree >= 0.16 and degree <= 0.33:
235 | factor = degree - 0.16
236 | r = 255 - round(255 * (factor / .16))
237 | else:
238 | r = 0
239 | r = r if r <= 255 else 255
240 | r = r if r >= 0 else 0
241 |
242 | # GREEN
243 | if degree >= 0.0 and degree <= 0.66:
244 | if degree >= 0.0 and degree <= 0.16:
245 | g = round(255.0 * (degree / .16))
246 | elif degree > 0.16 and degree < 0.5:
247 | g = 255
248 | if degree >= 0.5 and degree <= 0.66:
249 | factor = degree - 0.5
250 | g = 255 - round(255.0 * (factor / .16))
251 | else:
252 | g = 0
253 | g = g if g <= 255.0 else 255.0
254 | g = g if g >= 0 else 0
255 |
256 | # BLUE
257 | if degree >= 0.33 and degree <= 1.0:
258 | if degree >= 0.33 and degree <= 0.5:
259 | factor = degree - 0.33
260 | b = round(255 * (factor / .16))
261 | elif degree > 0.5 and degree < 0.83:
262 | b = 255.0
263 | if degree >= 0.83 and degree <= 1.0:
264 | factor = degree - 0.83
265 | b = 255.0 - round(255.0 * (factor / .16))
266 | else:
267 | b = 0
268 | b = b if b <= 255 else 255
269 | b = b if b >= 0 else 0
270 | return r, g, b
271 |
--------------------------------------------------------------------------------
/hotbox_designer/reader.py:
--------------------------------------------------------------------------------
1 | from hotbox_designer.vendor.Qt import QtWidgets, QtCore, QtGui
2 | from hotbox_designer.interactive import Shape
3 | from hotbox_designer.qtutils import get_cursor
4 | from hotbox_designer.painting import draw_aiming, draw_aiming_background
5 | from hotbox_designer.geometry import distance, segment_cross_rect
6 |
7 |
8 | class HotboxWidget(QtWidgets.QWidget):
9 | def __init__(self, *args, **kwargs):
10 | super(HotboxWidget, self).__init__(*args, **kwargs)
11 | self.setMouseTracking(True)
12 | self.shapes = []
13 | self.interactive_shapes = []
14 | self.left_clicked = False
15 | self.right_clicked = False
16 |
17 | def set_hotbox_data(self, hotbox_data):
18 | self.shapes = [Shape(shape) for shape in hotbox_data['shapes']]
19 | self.interactive_shapes = [
20 | s for s in self.shapes if s.is_interactive()]
21 | self.repaint()
22 |
23 | def clear(self):
24 | self.shapes = []
25 | self.interactive_shapes = []
26 | self.repaint()
27 |
28 | @property
29 | def clicked(self):
30 | return self.right_clicked or self.left_clicked
31 |
32 | def mouseMoveEvent(self, _):
33 | shapes = self.interactive_shapes
34 | set_shapes_hovered(shapes, get_cursor(self), self.clicked)
35 | self.repaint()
36 |
37 | def leaveEvent(self, _):
38 | shapes = self.interactive_shapes
39 | set_shapes_hovered(shapes, get_cursor(self), self.clicked)
40 | self.repaint()
41 |
42 | def mousePressEvent(self, event):
43 | if event.button() == QtCore.Qt.RightButton:
44 | self.right_clicked = True
45 | elif event.button() == QtCore.Qt.LeftButton:
46 | self.left_clicked = True
47 | for shape in self.shapes:
48 | if shape.is_interactive():
49 | if shape.hovered and self.clicked:
50 | shape.clicked = True
51 | else:
52 | shape.clicked = False
53 | self.repaint()
54 |
55 | def mouseReleaseEvent(self, event):
56 | execute_hovered_shape(
57 | self.shapes, self.left_clicked, self.right_clicked)
58 |
59 | if event.button() == QtCore.Qt.RightButton:
60 | self.right_clicked = False
61 | elif event.button() == QtCore.Qt.LeftButton:
62 | self.left_clicked = False
63 |
64 | for shape in self.shapes:
65 | if shape.is_interactive():
66 | shape.clicked = bool(shape.hovered and self.clicked)
67 | self.repaint()
68 |
69 | def paintEvent(self, _):
70 | painter = QtGui.QPainter()
71 | painter.begin(self)
72 | painter.setRenderHint(QtGui.QPainter.Antialiasing)
73 | for shape in self.shapes:
74 | shape.draw(painter)
75 | painter.end()
76 |
77 |
78 | class HotboxReader(QtWidgets.QWidget):
79 | hideSubmenusRequested = QtCore.Signal()
80 |
81 | def __init__(self, hotbox_data, parent=None):
82 | super(HotboxReader, self).__init__(parent)
83 | f = (QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.FramelessWindowHint)
84 | self.setWindowFlags(f)
85 | self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
86 | self.setMouseTracking(True)
87 |
88 | settings = hotbox_data['general']
89 | self.triggering = settings['triggering']
90 | self.aiming = settings['aiming']
91 | self.is_submenu = settings['submenu']
92 | self.center = QtCore.QPoint(settings['centerx'], settings['centery'])
93 | self.setFixedSize(settings['width'], settings['height'])
94 | self.shapes = [Shape(data) for data in hotbox_data['shapes']]
95 | self.close_on_leave = settings['leaveclose']
96 | self.interactive_shapes = [
97 | s for s in self.shapes if s.is_interactive()]
98 |
99 | self.left_clicked = False
100 | self.right_clicked = False
101 |
102 | def mouseMoveEvent(self, _):
103 | self.set_hovered_shapes()
104 |
105 | def leaveEvent(self, _):
106 | shapes = self.interactive_shapes
107 | if self.aiming is True:
108 | set_crossed_shapes_hovered(
109 | self.center, get_cursor(self), shapes, get_cursor(self))
110 | else:
111 | set_shapes_hovered(shapes, get_cursor(self), self.clicked)
112 | if self.close_on_leave is True:
113 | self.hide()
114 | self.repaint()
115 |
116 | @property
117 | def clicked(self):
118 | return self.right_clicked or self.left_clicked
119 |
120 | def keyPressEvent(self, event):
121 | if event.key() == QtCore.Qt.Key_Escape:
122 | self.hide()
123 |
124 | parent = self.parent()
125 | if parent is not None:
126 | return parent.keyPressEvent(event)
127 |
128 | def mousePressEvent(self, event):
129 | if event.button() == QtCore.Qt.RightButton:
130 | self.right_clicked = True
131 | elif event.button() == QtCore.Qt.LeftButton:
132 | self.left_clicked = True
133 | for shape in self.shapes:
134 | if shape.is_interactive():
135 | if shape.hovered and self.clicked:
136 | shape.clicked = True
137 | else:
138 | shape.clicked = False
139 | self.repaint()
140 |
141 | def mouseReleaseEvent(self, event):
142 | close = execute_hovered_shape(
143 | self.shapes, self.left_clicked, self.right_clicked)
144 |
145 | if event.button() == QtCore.Qt.RightButton:
146 | self.right_clicked = False
147 | elif event.button() == QtCore.Qt.LeftButton:
148 | self.left_clicked = False
149 |
150 | for shape in self.shapes:
151 | if shape.is_interactive():
152 | shape.clicked = bool(shape.hovered and self.clicked)
153 |
154 | if close is True:
155 | self.hide()
156 | self.repaint()
157 |
158 | def paintEvent(self, _):
159 | painter = QtGui.QPainter()
160 | painter.begin(self)
161 | painter.setRenderHint(QtGui.QPainter.Antialiasing)
162 | # this is a workaround because a fully transparent widget doesn't
163 | # execute the mouseMove event when the cursor is hover a
164 | # transparent of the widget. This draw the reader rect has black
165 | # rect with a 1/255 transparency value
166 | draw_aiming_background(painter, self.rect())
167 |
168 | for shape in self.shapes:
169 | shape.draw(painter)
170 | if self.aiming:
171 | draw_aiming(painter, self.center, get_cursor(self))
172 | painter.end()
173 |
174 | def show(self):
175 | self.move(QtGui.QCursor.pos() - self.center)
176 | super(HotboxReader, self).show()
177 | self.set_hovered_shapes()
178 | self.setFocus()
179 |
180 | def hide(self):
181 | if not self.isVisible():
182 | return
183 |
184 | if self.triggering == 'click or close':
185 | execute_hovered_shape(self.shapes, left=True)
186 | if self.is_submenu is False:
187 | self.hideSubmenusRequested.emit()
188 |
189 | # the shape states for the next hotbox appearance
190 | for shape in self.interactive_shapes:
191 | shape.hovered = False
192 | shape.clicked = False
193 | # clean the aiming shape before close
194 | self.clear_aiming()
195 | super(HotboxReader, self).hide()
196 |
197 | def set_hovered_shapes(self):
198 | shapes = self.interactive_shapes
199 | if self.aiming is True:
200 | set_crossed_shapes_hovered(
201 | self.center, get_cursor(self), shapes, get_cursor(self))
202 | else:
203 | set_shapes_hovered(shapes, get_cursor(self), self.clicked)
204 | self.repaint()
205 |
206 | def clear_aiming(self):
207 | '''
208 | this method is a workaround because Qt seem optimize to keep a paint
209 | when a widget is hidden. The aiming shape have to been cleaned before
210 | the widget is hidden. In case of it's cleaned after, the shape can pop
211 | on the next hotbox opening.
212 | '''
213 | if self.aiming is False:
214 | return
215 | self.aiming = False
216 | self.repaint()
217 | self.aiming = True
218 |
219 | def set_shapes_hovered(shapes, cursor, clicked):
220 | """
221 | this function all the given shapes.
222 | It set hovered the shape if his rect contains the cursor.
223 | """
224 | for shape in shapes:
225 | if shape.is_interactive():
226 | shape.set_hovered(cursor)
227 | shape.clicked = shape.hovered and clicked
228 |
229 |
230 | def set_crossed_shapes_hovered(point1, point2, shapes, cursor):
231 | """
232 | this is the function to set the hovered shape using the aiming system.
233 | It filter all shapes crossed by the given line and set the closest to the.
234 | cursor hovered.
235 | """
236 | # reset hovered shape
237 | for shape in shapes:
238 | shape.hovered = False
239 | # check first if a shape rect contain the cursor
240 | for shape in shapes:
241 | if shape.rect.contains(cursor):
242 | shape.hovered = True
243 | return
244 | # filter all shapes crossed by a virtual line who joins the
245 | # hotspot and the cursor
246 | cshapes = [s for s in shapes if segment_cross_rect(point1, point2, s.rect)]
247 | if not cshapes:
248 | return
249 | # process distance between all shape crossed and
250 | # set the closest to the cursor hovered
251 | shapedistances = {
252 | distance(shape.rect.center(), cursor): shape
253 | for shape in cshapes}
254 | shapedistances[min(shapedistances.keys())].hovered = True
255 |
256 |
257 | def execute_hovered_shape(shapes, left=False, right=False):
258 | for shape in shapes:
259 | if shape.is_interactive() and shape.hovered:
260 | shape.execute(left=left, right=right)
261 | return shape.autoclose(left=left, right=right)
262 | return False
263 |
--------------------------------------------------------------------------------
/hotbox_designer/applications.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | from hotbox_designer.vendor.Qt import QtWidgets
4 | from hotbox_designer.dialog import warning
5 | from hotbox_designer.languages import (
6 | MEL, PYTHON, NUKE_TCL, NUKE_EXPRESSION, HSCRIPT, RUMBA_SCRIPT)
7 |
8 |
9 | HOTBOXES_FILENAME = 'hotboxes.json'
10 | SHARED_HOTBOXES_FILENAME = 'shared_hotboxes.json'
11 | SETMODE_PRESS_RELEASE = 'open on press | close on release'
12 | SETMODE_SWITCH_ON_PRESS = 'switch on press'
13 |
14 |
15 | def execute(command):
16 | exec(command)
17 |
18 |
19 | class AbstractApplication(object):
20 |
21 | def __init__(self):
22 | self.name = type(self).__name__
23 | folder = self.get_data_folder()
24 | self.local_file = os.path.join(folder, HOTBOXES_FILENAME)
25 | self.shared_file = os.path.join(folder, SHARED_HOTBOXES_FILENAME)
26 | self.main_window = self.get_main_window()
27 | self.reader_parent = self.get_reader_parent()
28 | self.available_languages = self.get_available_languages()
29 | self.available_set_hotkey_modes = self.get_available_set_hotkey_modes()
30 |
31 | @staticmethod
32 | def get_data_folder():
33 | raise NotImplementedError
34 |
35 | @staticmethod
36 | def get_reader_parent():
37 | raise NotImplementedError
38 |
39 | @staticmethod
40 | def get_main_window():
41 | raise NotImplementedError
42 |
43 | @staticmethod
44 | def get_available_languages():
45 | raise NotImplementedError
46 |
47 | @staticmethod
48 | def get_available_set_hotkey_modes():
49 | raise NotImplementedError
50 |
51 | @staticmethod
52 | def update_hotkeys():
53 | # Do not use 'raise NotImplementedError' in case other DCCs don't
54 | # have that feature
55 | pass
56 |
57 | def set_hotkey(self, mode, sequence, open_cmd, close_cmd, switch_cmd):
58 | raise NotImplementedError
59 |
60 |
61 | class Maya(AbstractApplication):
62 |
63 | @staticmethod
64 | def get_data_folder():
65 | from maya import cmds
66 | return cmds.internalVar(userPrefDir=True)
67 |
68 | @staticmethod
69 | def get_main_window():
70 | """Get the main window for maya.
71 |
72 | Returns:
73 | shiboken2.wrapInstance: The pointer to the maya main window.
74 | """
75 | import maya.OpenMayaUI as omui
76 | try:
77 | import shiboken2 as shiboken
78 | except ImportError:
79 | import shiboken6 as shiboken
80 | if os.name == 'posix':
81 | return None
82 | ptr = omui.MQtUtil.mainWindow()
83 | if ptr is not None:
84 | return shiboken.wrapInstance(int(ptr), QtWidgets.QWidget)
85 |
86 | @staticmethod
87 | def get_reader_parent():
88 | return None
89 |
90 | @staticmethod
91 | def get_available_languages():
92 | return [MEL, PYTHON]
93 |
94 | @staticmethod
95 | def get_available_set_hotkey_modes():
96 | return [SETMODE_PRESS_RELEASE, SETMODE_SWITCH_ON_PRESS]
97 |
98 | def set_hotkey(
99 | self, name, mode, sequence, open_cmd, close_cmd, switch_cmd):
100 | from maya import cmds, mel
101 | current_hotkey_set = cmds.hotkeySet(current=True, query=True)
102 | if current_hotkey_set == 'Maya_Default':
103 | msg = (
104 | 'The current hotkey set is locked,'
105 | 'change in the hotkey editor')
106 | warning('Hotbox designer', msg)
107 | return mel.eval("hotkeyEditorWindow;")
108 |
109 | use_alt = 'Alt' in sequence
110 | use_ctrl = 'Ctrl' in sequence
111 | use_shift = 'Shift' in sequence
112 | touch = sequence.split("+")[-1]
113 | show_name = 'showHotbox_' + name
114 | hide_name = 'hideHotbox_' + name
115 | switch_name = 'switchHotbox_' + name
116 | if mode == SETMODE_PRESS_RELEASE:
117 | cmds.nameCommand(
118 | show_name,
119 | annotation='show ' + name + ' hotbox',
120 | command=format_command_for_mel(open_cmd),
121 | sourceType="python")
122 | cmds.nameCommand(
123 | hide_name,
124 | annotation='hide ' + name + ' hotbox',
125 | command=format_command_for_mel(close_cmd),
126 | sourceType="python")
127 | cmds.hotkey(
128 | keyShortcut=touch,
129 | altModifier=use_alt,
130 | ctrlModifier=use_ctrl,
131 | shiftModifier=use_shift,
132 | name=show_name,
133 | releaseName=hide_name)
134 | else:
135 | cmds.nameCommand(
136 | switch_name,
137 | annotation='switch ' + name + ' hotbox',
138 | command=format_command_for_mel(switch_cmd),
139 | sourceType="python")
140 | cmds.hotkey(
141 | keyShortcut=touch,
142 | altModifier=use_alt,
143 | ctrlModifier=use_ctrl,
144 | shiftModifier=use_shift,
145 | name=switch_name)
146 |
147 |
148 | def format_command_for_mel(command):
149 | '''
150 | cause cmds.nameCommand fail to set python command, this method
151 | embed the given command to a mel command callin "python" function.
152 | It put everylines in a single one cause mel is not supporting multi-lines
153 | strings. Hopefully Autodesk gonna fixe this soon.
154 | '''
155 | command = command.replace("\n", ";")
156 | command = 'python("{}")'.format(command)
157 | return command
158 |
159 |
160 | class Nuke(AbstractApplication):
161 |
162 | @staticmethod
163 | def get_data_folder():
164 | return os.path.expanduser('~/.nuke')
165 |
166 | @staticmethod
167 | def get_main_window():
168 | for widget in QtWidgets.QApplication.instance().topLevelWidgets():
169 | if widget.inherits('QMainWindow'):
170 | return widget
171 |
172 | @staticmethod
173 | def get_reader_parent():
174 | return None
175 |
176 | @staticmethod
177 | def get_available_languages():
178 | return PYTHON, NUKE_TCL, NUKE_EXPRESSION
179 |
180 | @staticmethod
181 | def get_available_set_hotkey_modes():
182 | return [SETMODE_SWITCH_ON_PRESS]
183 |
184 | def set_hotkey(
185 | self, name, mode, sequence, open_cmd, close_cmd, switch_cmd):
186 | self.save_hotkey(name, sequence, switch_cmd)
187 | self.create_menus()
188 |
189 | def get_hotkey_file(self):
190 | hotkey_file = os.path.join(
191 | self.get_data_folder(), 'hotbox_hotkey.json')
192 | return hotkey_file
193 |
194 | def load_hotkey(self):
195 | hotkey_file = self.get_hotkey_file()
196 | if not os.path.exists(hotkey_file):
197 | return {}
198 | with open(hotkey_file, 'r') as f:
199 | return json.load(f)
200 |
201 | def save_hotkey(self, name, sequence, command):
202 | data = self.load_hotkey()
203 | data[name] = {
204 | 'sequence': sequence,
205 | 'command': command}
206 | with open(str(self.get_hotkey_file()), 'w+') as f:
207 | json.dump(data, f, indent=2)
208 |
209 | def create_menus(self):
210 | import nuke
211 | nuke_menu = nuke.menu('Nuke')
212 | menu = nuke_menu.addMenu('Hotbox Designer')
213 | hotkey_data = self.load_hotkey()
214 | for name, value in hotkey_data.items():
215 | menu.addCommand(
216 | name='Hotboxes/{name}'.format(name=name),
217 | command=str(value['command']), shortcut=value['sequence'])
218 |
219 |
220 | class Houdini(AbstractApplication):
221 |
222 | @staticmethod
223 | def get_data_folder():
224 | return os.path.expanduser('~/houdini17.0')
225 |
226 | @staticmethod
227 | def get_main_window():
228 | import hou
229 | return hou.qt.mainWindow()
230 |
231 | @staticmethod
232 | def get_reader_parent():
233 | return None
234 |
235 | @staticmethod
236 | def get_available_languages():
237 | return [PYTHON, HSCRIPT]
238 |
239 | @staticmethod
240 | def get_available_set_hotkey_modes():
241 | return [SETMODE_SWITCH_ON_PRESS]
242 |
243 | def set_hotkey(
244 | self, name, mode, sequence, open_cmd, close_cmd, switch_cmd):
245 | from hotbox_designer.qtutils import set_shortcut
246 | from functools import partial
247 | set_shortcut(sequence, self.main_window, partial(execute, switch_cmd))
248 |
249 | class Rumba(AbstractApplication):
250 |
251 | @staticmethod
252 | def get_data_folder():
253 | return os.path.expanduser('~/.rumba')
254 |
255 | @staticmethod
256 | def get_main_window():
257 | import rumbapy
258 | return rumbapy.widget("MainWindow")
259 |
260 | @staticmethod
261 | def get_reader_parent():
262 | return None
263 |
264 | @staticmethod
265 | def get_available_languages():
266 | return [RUMBA_SCRIPT, PYTHON]
267 |
268 | @staticmethod
269 | def get_available_set_hotkey_modes():
270 | return [SETMODE_SWITCH_ON_PRESS]
271 |
272 | def set_hotkey(
273 | self, name, mode, sequence, open_cmd, close_cmd, switch_cmd):
274 | self.save_hotkey(name, sequence, switch_cmd)
275 | self.create_menus(reload=True)
276 |
277 | def update_hotkeys(self):
278 | hotkey_file = self.get_hotkey_file()
279 | updated_hotkeys = self.remove_hotbox_item(
280 | self.load_hotboxes(), self.load_hotkey()
281 | )
282 | with open(hotkey_file, 'w') as f:
283 | json.dump(updated_hotkeys, f, indent=2)
284 |
285 | def get_hotboxes_file(self):
286 | hotboxes_file = os.path.join(
287 | self.get_data_folder(), HOTBOXES_FILENAME)
288 | return hotboxes_file
289 |
290 | def get_hotkey_file(self):
291 | hotkey_file = os.path.join(
292 | self.get_data_folder(), 'hotbox_hotkey.json')
293 | return hotkey_file
294 |
295 | def load_hotboxes(self):
296 | hotboxes_file = self.get_hotboxes_file()
297 | if not os.path.exists(hotboxes_file):
298 | return []
299 | with open(hotboxes_file, 'r') as f:
300 | return json.load(f)
301 |
302 | def load_hotkey(self):
303 | hotkey_file = self.get_hotkey_file()
304 | if not os.path.exists(hotkey_file):
305 | return {}
306 | with open(hotkey_file, 'r') as f:
307 | return json.load(f)
308 |
309 | def save_hotkey(self, name, sequence, command):
310 | hotkey_data = self.load_hotkey()
311 | updated_hotkey_data = self.remove_hotbox_item(self.load_hotboxes(), hotkey_data)
312 | updated_hotkey_data[name] = {
313 | 'sequence': sequence,
314 | 'command': command}
315 | with open(str(self.get_hotkey_file()), 'w') as f:
316 | json.dump(updated_hotkey_data, f, indent=2)
317 |
318 | def delete_menu(self, menu_bar: QtWidgets.QMenuBar, menu_title: str):
319 | """Find and delete a menu with a specific title from the menu bar."""
320 | menus = menu_bar.actions()
321 | for menu in menus:
322 | if menu.menu() and menu.text() == menu_title:
323 | menu_bar.removeAction(menu)
324 |
325 | def create_menus(self, reload=False):
326 | """Create the Hotbox Designer menu in Rumba's menu bar."""
327 | import rumbapy
328 | from functools import partial
329 |
330 | main_window = rumbapy.widget("MainWindow")
331 | menu_bar = main_window.menubar
332 | menu_title = "&Hotbox Designer"
333 |
334 | if reload:
335 | self.delete_menu(menu_bar, menu_title)
336 |
337 | hotbox_menu = menu_bar.addMenu(menu_title)
338 |
339 | hotkey_data = self.load_hotkey()
340 |
341 | for name, value in hotkey_data.items():
342 | action = rumbapy.action.new(
343 | name=name,
344 | widget=main_window,
345 | trigger=partial(lambda cmd: exec(cmd), value['command']),
346 | icon=None,
347 | shortcut=value["sequence"]
348 | )
349 | hotbox_menu.addAction(action)
350 |
351 | def remove_hotbox_item(self, hotboxes, hotbox_hotkey):
352 | """
353 | Remove hotbox items that are not present in the current hotboxes
354 | """
355 | hotbox_items = {item.get("general", {}).get("name") for item in hotboxes}
356 |
357 | updated_hotkey = {
358 | key: value for key, value in hotbox_hotkey.items()
359 | if key in hotbox_items
360 | }
361 |
362 | return updated_hotkey
363 |
--------------------------------------------------------------------------------
/hotbox_designer/designer/application.py:
--------------------------------------------------------------------------------
1 |
2 | from functools import partial
3 | from hotbox_designer.vendor.Qt import QtWidgets, QtCore
4 |
5 | from hotbox_designer.templates import SQUARE_BUTTON, TEXT, BACKGROUND
6 | from hotbox_designer.interactive import Shape
7 | from hotbox_designer.geometry import get_combined_rects
8 | from hotbox_designer.qtutils import set_shortcut
9 | from hotbox_designer.data import copy_hotbox_data
10 | from hotbox_designer.arrayutils import (
11 | move_elements_to_array_end, move_elements_to_array_begin,
12 | move_up_array_elements, move_down_array_elements)
13 |
14 | from .editarea import ShapeEditArea
15 | from .menu import MenuWidget
16 | from .attributes import AttributeEditor
17 |
18 |
19 | class HotboxEditor(QtWidgets.QWidget):
20 | hotboxDataModified = QtCore.Signal(object)
21 |
22 | def __init__(self, hotbox_data, application, parent=None):
23 | super(HotboxEditor, self).__init__(parent, QtCore.Qt.Window)
24 | self.setWindowTitle("Hotbox editor")
25 | self.options = hotbox_data['general']
26 | self.application = application
27 | self.clipboard = []
28 | self.undo_manager = UndoManager(hotbox_data)
29 |
30 | self.shape_editor = ShapeEditArea(self.options)
31 | self.set_hotbox_data(hotbox_data)
32 | self.shape_editor.selectedShapesChanged.connect(self.selection_changed)
33 | self.shape_editor.centerMoved.connect(self.move_center)
34 | method = self.set_data_modified
35 | self.shape_editor.increaseUndoStackRequested.connect(method)
36 |
37 | self.menu = MenuWidget()
38 | self.menu.copyRequested.connect(self.copy)
39 | self.menu.pasteRequested.connect(self.paste)
40 | self.menu.deleteRequested.connect(self.delete_selection)
41 | self.menu.sizeChanged.connect(self.editor_size_changed)
42 | self.menu.editCenterToggled.connect(self.edit_center_mode_changed)
43 | self.menu.useSnapToggled.connect(self.use_snap)
44 | self.menu.snapValuesChanged.connect(self.snap_value_changed)
45 | self.menu.centerValuesChanged.connect(self.move_center)
46 | width, height = self.options['width'], self.options['height']
47 | self.menu.set_size_values(width, height)
48 | x, y = self.options['centerx'], self.options['centery']
49 | self.menu.set_center_values(x, y)
50 | self.menu.undoRequested.connect(self.undo)
51 | self.menu.redoRequested.connect(self.redo)
52 | method = partial(self.create_shape, SQUARE_BUTTON)
53 | self.menu.addButtonRequested.connect(method)
54 | method = partial(self.create_shape, TEXT)
55 | self.menu.addTextRequested.connect(method)
56 | method = partial(self.create_shape, BACKGROUND, before=True)
57 | self.menu.addBackgroundRequested.connect(method)
58 | method = self.set_selection_move_down
59 | self.menu.moveDownRequested.connect(method)
60 | method = self.set_selection_move_up
61 | self.menu.moveUpRequested.connect(method)
62 | method = self.set_selection_on_top
63 | self.menu.onTopRequested.connect(method)
64 | method = self.set_selection_on_bottom
65 | self.menu.onBottomRequested.connect(method)
66 |
67 | set_shortcut("Ctrl+Z", self.shape_editor, self.undo)
68 | set_shortcut("Ctrl+Y", self.shape_editor, self.redo)
69 | set_shortcut("Ctrl+C", self.shape_editor, self.copy)
70 | set_shortcut("Ctrl+V", self.shape_editor, self.paste)
71 | set_shortcut("del", self.shape_editor, self.delete_selection)
72 | set_shortcut("Ctrl+D", self.shape_editor, self.deselect_all)
73 | set_shortcut("Ctrl+A", self.shape_editor, self.select_all)
74 | set_shortcut("Ctrl+I", self.shape_editor, self.invert_selection)
75 |
76 | self.attribute_editor = AttributeEditor(self.application)
77 | self.attribute_editor.optionSet.connect(self.option_set)
78 | self.attribute_editor.rectModified.connect(self.rect_modified)
79 | self.attribute_editor.imageModified.connect(self.image_modified)
80 |
81 | self.hlayout = QtWidgets.QHBoxLayout()
82 | self.hlayout.setContentsMargins(0, 0, 0, 0)
83 | self.hlayout.addStretch(1)
84 | self.hlayout.addWidget(self.shape_editor)
85 | self.hlayout.addStretch(1)
86 | self.hlayout.addWidget(self.attribute_editor)
87 |
88 | self.vlayout = QtWidgets.QVBoxLayout(self)
89 | self.vlayout.setContentsMargins(0, 0, 0, 0)
90 | self.vlayout.setSpacing(0)
91 | self.vlayout.addWidget(self.menu)
92 | self.vlayout.addLayout(self.hlayout)
93 |
94 | def copy(self):
95 | self.clipboard = [
96 | s.options.copy() for s in self.shape_editor.selection]
97 |
98 | def paste(self):
99 | clipboad_copy = [s.copy() for s in self.clipboard]
100 | shape_datas = self.hotbox_data()['shapes'][:] + clipboad_copy
101 | hotbox_data = {
102 | 'general': self.options,
103 | 'shapes': shape_datas}
104 | self.set_hotbox_data(hotbox_data)
105 | self.undo_manager.set_data_modified(hotbox_data)
106 | self.hotboxDataModified.emit(hotbox_data)
107 | # select new shapes
108 | shapes = self.shape_editor.shapes [-len(self.clipboard):]
109 | self.shape_editor.selection.replace(shapes)
110 | self.shape_editor.update_selection()
111 | self.shape_editor.repaint()
112 |
113 | def undo(self):
114 | result = self.undo_manager.undo()
115 | if result is False:
116 | return
117 | data = self.undo_manager.data
118 | self.set_hotbox_data(data)
119 | self.hotboxDataModified.emit(self.hotbox_data())
120 |
121 | def redo(self):
122 | self.undo_manager.redo()
123 | data = self.undo_manager.data
124 | self.set_hotbox_data(data)
125 | self.hotboxDataModified.emit(self.hotbox_data())
126 |
127 | def deselect_all(self):
128 | self.shape_editor.selection.clear()
129 | self.shape_editor.update_selection()
130 | self.shape_editor.repaint()
131 |
132 | def select_all(self):
133 | self.shape_editor.selection.add(self.shape_editor.shapes)
134 | self.shape_editor.update_selection()
135 | self.shape_editor.repaint()
136 |
137 | def invert_selection(self):
138 | self.shape_editor.selection.invert(self.shape_editor.shapes)
139 | self.shape_editor.update_selection()
140 | self.shape_editor.repaint()
141 |
142 | def set_data_modified(self):
143 | self.undo_manager.set_data_modified(self.hotbox_data())
144 | self.hotboxDataModified.emit(self.hotbox_data())
145 |
146 | def use_snap(self, state):
147 | snap = self.menu.snap_values() if state else None
148 | self.shape_editor.transform.snap = snap
149 | self.shape_editor.repaint()
150 |
151 | def snap_value_changed(self):
152 | self.shape_editor.transform.snap = self.menu.snap_values()
153 | self.set_data_modified()
154 | self.shape_editor.repaint()
155 |
156 | def edit_center_mode_changed(self, state):
157 | self.shape_editor.edit_center_mode = state
158 | self.shape_editor.repaint()
159 |
160 | def option_set(self, option, value):
161 | for shape in self.shape_editor.selection:
162 | shape.options[option] = value
163 | self.shape_editor.repaint()
164 | self.set_data_modified()
165 |
166 | def editor_size_changed(self):
167 | size = self.menu.get_size()
168 | self.shape_editor.setFixedSize(size)
169 | self.options['width'] = size.width()
170 | self.options['height'] = size.height()
171 | self.set_data_modified()
172 |
173 | def move_center(self, x, y):
174 | self.options['centerx'] = x
175 | self.options['centery'] = y
176 | self.menu.set_center_values(x, y)
177 | self.shape_editor.repaint()
178 | self.set_data_modified()
179 |
180 | def rect_modified(self, option, value):
181 | shapes = self.shape_editor.selection
182 | for shape in shapes:
183 | shape.options[option] = value
184 | if option == 'shape.height':
185 | shape.rect.setHeight(value)
186 | continue
187 | elif option == 'shape.width':
188 | shape.rect.setWidth(value)
189 | continue
190 |
191 | width = shape.rect.width()
192 | height = shape.rect.height()
193 | if option == 'shape.left':
194 | shape.rect.setLeft(value)
195 | else:
196 | shape.rect.setTop(value)
197 | shape.rect.setWidth(width)
198 | shape.rect.setHeight(height)
199 |
200 | rects = [shape.rect for shape in self.shape_editor.selection]
201 | rect = get_combined_rects(rects)
202 | self.shape_editor.manipulator.set_rect(rect)
203 | self.shape_editor.repaint()
204 |
205 | def selection_changed(self):
206 | shapes = self.shape_editor.selection
207 | options = [shape.options for shape in shapes]
208 | self.attribute_editor.set_options(options)
209 |
210 | def create_shape(self, template, before=False):
211 | options = template.copy()
212 | shape = Shape(options)
213 | shape.rect.moveCenter(self.shape_editor.rect().center())
214 | shape.synchronize_rect()
215 | if before is True:
216 | self.shape_editor.shapes.insert(0, shape)
217 | else:
218 | self.shape_editor.shapes.append(shape)
219 | self.shape_editor.repaint()
220 | self.set_data_modified()
221 |
222 | def image_modified(self):
223 | for shape in self.shape_editor.selection:
224 | shape.synchronize_image()
225 | self.shape_editor.repaint()
226 |
227 | def set_selection_move_down(self):
228 | array = self.shape_editor.shapes
229 | elements = self.shape_editor.selection
230 | move_down_array_elements(array, elements)
231 | self.shape_editor.repaint()
232 | self.set_data_modified()
233 |
234 | def set_selection_move_up(self):
235 | array = self.shape_editor.shapes
236 | elements = self.shape_editor.selection
237 | move_up_array_elements(array, elements)
238 | self.shape_editor.repaint()
239 | self.set_data_modified()
240 |
241 | def set_selection_on_top(self):
242 | array = self.shape_editor.shapes
243 | elements = self.shape_editor.selection
244 | self.shape_editor.shapes = move_elements_to_array_end(array, elements)
245 | self.shape_editor.repaint()
246 | self.set_data_modified()
247 |
248 | def set_selection_on_bottom(self):
249 | array = self.shape_editor.shapes
250 | elements = self.shape_editor.selection
251 | shapes = move_elements_to_array_begin(array, elements)
252 | self.shape_editor.shapes = shapes
253 | self.shape_editor.repaint()
254 | self.set_data_modified()
255 |
256 | def delete_selection(self):
257 | for shape in reversed(self.shape_editor.selection.shapes):
258 | self.shape_editor.shapes.remove(shape)
259 | self.shape_editor.selection.remove(shape)
260 | rects = [shape.rect for shape in self.shape_editor.selection]
261 | rect = get_combined_rects(rects)
262 | self.shape_editor.manipulator.set_rect(rect)
263 | self.shape_editor.repaint()
264 | self.set_data_modified()
265 |
266 | def hotbox_data(self):
267 | return {
268 | 'general': self.options,
269 | 'shapes': [shape.options for shape in self.shape_editor.shapes]}
270 |
271 | def set_hotbox_data(self, hotbox_data, reset_stacks=False):
272 | self.options = hotbox_data['general']
273 | self.shape_editor.options = self.options
274 | shapes = [Shape(options) for options in hotbox_data['shapes']]
275 | self.shape_editor.shapes = shapes
276 | self.shape_editor.manipulator.rect = None
277 | self.shape_editor.repaint()
278 | if reset_stacks is True:
279 | self.undo_manager.reset_stacks()
280 |
281 |
282 | class UndoManager():
283 | def __init__(self, data):
284 | self._current_state = data
285 | self._modified = False
286 | self._undo_stack = []
287 | self._redo_stack = []
288 |
289 | @property
290 | def data(self):
291 | return copy_hotbox_data(self._current_state)
292 |
293 | def undo(self):
294 | if not self._undo_stack:
295 | print ('no undostack')
296 | return False
297 | self._redo_stack.append(copy_hotbox_data(self._current_state))
298 | self._current_state = copy_hotbox_data(self._undo_stack[-1])
299 | del self._undo_stack[-1]
300 | return True
301 |
302 | def redo(self):
303 | if not self._redo_stack:
304 | return False
305 |
306 | self._undo_stack.append(copy_hotbox_data(self._current_state))
307 | self._current_state = copy_hotbox_data(self._redo_stack[-1])
308 | del self._redo_stack[-1]
309 | return True
310 |
311 | def set_data_modified(self, data):
312 | self._redo_stack = []
313 | self._undo_stack.append(copy_hotbox_data(self._current_state))
314 | self._current_state = copy_hotbox_data(data)
315 | self._modified = True
316 |
317 | def set_data_saved(self):
318 | self._modified = False
319 |
320 | @property
321 | def data_saved(self):
322 | return not self._modified
323 |
324 | def reset_stacks(self):
325 | self._undo_stack = []
326 | self._redo_stack = []
327 |
--------------------------------------------------------------------------------
/hotbox_designer/geometry.py:
--------------------------------------------------------------------------------
1 | import math
2 | from hotbox_designer.vendor.Qt import QtCore
3 |
4 | POINT_RADIUS = 8
5 | POINT_OFFSET = 4
6 | DIRECTIONS = [
7 | 'top_left',
8 | 'bottom_left',
9 | 'top_right',
10 | 'bottom_right',
11 | 'left',
12 | 'right',
13 | 'top',
14 | 'bottom']
15 |
16 |
17 | def get_topleft_rect(rect):
18 | """
19 | this function return a manipulator rect for the transform
20 | handler.
21 | *__________________________
22 | | |
23 | | |
24 | |________________________|
25 | """
26 | if not rect:
27 | return None
28 | point = rect.topLeft()
29 | return QtCore.QRectF(
30 | point.x() - (POINT_RADIUS / 2.0) - POINT_OFFSET,
31 | point.y() - (POINT_RADIUS / 2.0) - POINT_OFFSET,
32 | POINT_RADIUS, POINT_RADIUS)
33 |
34 |
35 | def get_bottomleft_rect(rect):
36 | """
37 | this function return a manipulator rect for the transform
38 | handler.
39 | __________________________
40 | | |
41 | | |
42 | |________________________|
43 | *
44 | """
45 | if not rect:
46 | return None
47 | point = rect.bottomLeft()
48 | return QtCore.QRectF(
49 | point.x() - (POINT_RADIUS / 2.0) - POINT_OFFSET,
50 | point.y() + (POINT_RADIUS / 2.0) - POINT_OFFSET,
51 | POINT_RADIUS, POINT_RADIUS)
52 |
53 |
54 | def get_topright_rect(rect):
55 | """
56 | this function return a manipulator rect for the transform
57 | handler.
58 | __________________________*
59 | | |
60 | | |
61 | |________________________|
62 | """
63 | if not rect:
64 | return None
65 | point = rect.topRight()
66 | return QtCore.QRectF(
67 | point.x() + (POINT_RADIUS / 2.0) - POINT_OFFSET,
68 | point.y() - (POINT_RADIUS / 2.0) - POINT_OFFSET,
69 | POINT_RADIUS, POINT_RADIUS)
70 |
71 |
72 | def get_bottomright_rect(rect):
73 | """
74 | this function return a manipulator rect for the transform
75 | handler.
76 | __________________________
77 | | |
78 | | |
79 | |________________________|
80 | *
81 | """
82 | if not rect:
83 | return None
84 | point = rect.bottomRight()
85 | return QtCore.QRectF(
86 | point.x() + (POINT_RADIUS / 2.0) - POINT_OFFSET,
87 | point.y() + (POINT_RADIUS / 2.0) - POINT_OFFSET,
88 | POINT_RADIUS, POINT_RADIUS)
89 |
90 |
91 | def get_left_side_rect(rect):
92 | """
93 | this function return a manipulator rect for the transform
94 | handler.
95 | __________________________
96 | | |
97 | *| |
98 | |________________________|
99 | """
100 | if not rect:
101 | return None
102 | top = rect.top() + (rect.height() / 2.0)
103 | return QtCore.QRectF(
104 | rect.left() - (POINT_RADIUS / 2.0) - POINT_OFFSET,
105 | top - (POINT_RADIUS / 2.0),
106 | POINT_RADIUS, POINT_RADIUS)
107 |
108 |
109 | def get_right_side_rect(rect):
110 | """
111 | this function return a manipulator rect for the transform
112 | handler.
113 | __________________________
114 | | |
115 | | |*
116 | |________________________|
117 | """
118 | if not rect:
119 | return None
120 | top = rect.top() + (rect.height() / 2.0)
121 | return QtCore.QRectF(
122 | rect.right() + (POINT_RADIUS / 2.0) - POINT_OFFSET,
123 | top - (POINT_RADIUS / 2.0) ,
124 | POINT_RADIUS, POINT_RADIUS)
125 |
126 |
127 | def get_top_side_rect(rect):
128 | """
129 | this function return a manipulator rect for the transform
130 | handler.
131 | _____________*____________
132 | | |
133 | | |
134 | |________________________|
135 | """
136 | if not rect:
137 | return None
138 | return QtCore.QRectF(
139 | rect.left() + (rect.width() / 2.0) - (POINT_RADIUS / 2.0),
140 | rect.top() - (POINT_RADIUS / 2.0) - POINT_OFFSET,
141 | POINT_RADIUS, POINT_RADIUS)
142 |
143 |
144 | def get_bottom_side_rect(rect):
145 | """
146 | this function return a manipulator rect for the transform
147 | handler.
148 | __________________________
149 | | |
150 | | |
151 | |________________________|
152 | *
153 | """
154 | if not rect:
155 | return None
156 | return QtCore.QRectF(
157 | rect.left() + (rect.width() / 2.0) - (POINT_RADIUS / 2.0),
158 | rect.bottom() + (POINT_RADIUS / 2.0) - POINT_OFFSET,
159 | POINT_RADIUS, POINT_RADIUS)
160 |
161 |
162 | def grow_rect(rect, value):
163 | if not rect:
164 | return None
165 | return QtCore.QRectF(
166 | rect.left() - value,
167 | rect.top() - value,
168 | rect.width() + (value * 2),
169 | rect.height() + (value * 2))
170 |
171 |
172 | def relative(value, in_min, in_max, out_min, out_max):
173 | """
174 | this function resolve simple equation and return the unknown value
175 | in between two values.
176 | a, a" = in_min, out_min
177 | b, b " = out_max, out_max
178 | c = value
179 | ? is the unknown processed by function.
180 | a --------- c --------- b
181 | a" --------------- ? ---------------- b"
182 | """
183 | factor = (value - in_min) / (in_max - in_min)
184 | width = out_max - out_min
185 | return out_min + (width * (factor))
186 |
187 |
188 | def distance(a, b):
189 | """ return distance between two points """
190 | x = (b.x() - a.x())**2
191 | y = (b.y() - a.y())**2
192 | return math.sqrt(abs(x + y))
193 |
194 |
195 | def get_relative_point(rect, point):
196 | x = point.x() - rect.left()
197 | y = point.y() - rect.top()
198 | return QtCore.QPoint(x, y)
199 |
200 |
201 | def get_quarter(a, b, c):
202 | quarter = None
203 | if b.y() <= a.y() and b.x() < c.x():
204 | quarter = 0
205 | elif b.y() < a.y() and b.x() >= c.x():
206 | quarter = 1
207 | elif b.y() >= a.y() and b.x() > c.x():
208 | quarter = 2
209 | elif b.y() >= a.y() and b.x() <= c.x():
210 | quarter = 3
211 | return quarter
212 |
213 |
214 | def get_point_on_line(angle, ray):
215 | x = 50 + ray * math.cos(float(angle))
216 | y = 50 + ray * math.sin(float(angle))
217 | return QtCore.QPoint(x, y)
218 |
219 |
220 | def get_angle_c(a, b, c):
221 | return math.degrees(math.atan(distance(a, b) / distance(a, c)))
222 |
223 |
224 | def get_absolute_angle_c(a, b, c):
225 | quarter = get_quarter(a, b, c)
226 | try:
227 | angle_c = get_angle_c(a, b, c)
228 | except ZeroDivisionError:
229 | return 360 - (90 * quarter)
230 |
231 | if quarter == 0:
232 | return round(180.0 + angle_c, 1)
233 | elif quarter == 1:
234 | return round(270.0 + (90 - angle_c), 1)
235 | elif quarter == 2:
236 | return round(angle_c, 1)
237 | elif quarter == 3:
238 | return math.fabs(round(90.0 + (90 - angle_c), 1))
239 |
240 |
241 | def segment_cross_rect(p1, p2, rect):
242 | return (
243 | segment_cross_segment(p1, p2, rect.topLeft(), rect.topRight()) or
244 | segment_cross_segment(p1, p2, rect.topRight(), rect.bottomRight()) or
245 | segment_cross_segment(p1, p2, rect.bottomRight(), rect.bottomLeft()) or
246 | segment_cross_segment(p1, p2, rect.bottomLeft(), rect.topLeft()))
247 |
248 |
249 | def segment_cross_segment(p1, p2, p3, p4):
250 | """ first is True and second is False
251 | \ p1 \ p1
252 | \ \
253 | p3-----------p4 p3 ------- p4 \
254 | \ \
255 | \ p2 \ p2
256 | """
257 | dx1, dy1 = p2.x() - p1.x(), p2.y() - p1.y()
258 | dx2, dy2 = p4.x() - p3.x(), p4.y() - p3.y()
259 | dx3, dy3 = p1.x() - p3.x(), p1.y() - p3.y()
260 | d = dx1 * dy2 - dy1 * dx2
261 | if d == 0:
262 | return False
263 |
264 | t1 = (dx2 * dy3 - dy2 * dx3) / d
265 | if t1 < 0 or t1 > 1:
266 | return False
267 |
268 | t2 = (dx1 * dy3 - dy1 * dx3) /d
269 | if t2 < 0 or t2 > 1:
270 | return False
271 | return True
272 |
273 |
274 | def proportional_rect(rect, percent=None):
275 | """ return a scaled rect with a percentage """
276 | factor = float(percent) / 100
277 | width = rect.width() * factor
278 | height = rect.height() * factor
279 | left = rect.left() + round((rect.width() - width) / 2)
280 | top = rect.top() + round((rect.height() - height) / 2)
281 | return QtCore.QRect(left, top, width, height)
282 |
283 |
284 | def resize_rect_with_reference(rect, in_reference_rect, out_reference_rect):
285 | """
286 | __________________________________ B
287 | | ________________ A |
288 | | | | |
289 | | |_______________| |
290 | | |
291 | |________________________________|
292 | __________________________ C
293 | | ? |
294 | | |
295 | |________________________|
296 | A = rect given
297 | B = in_reference_rect
298 | C = out_reference_rect
299 | the function process the fourth rect,
300 | it scale the A rect using the B, C scales as reference
301 | """
302 |
303 | left = relative(
304 | value=rect.left(),
305 | in_min=in_reference_rect.left(),
306 | in_max=in_reference_rect.right(),
307 | out_min=out_reference_rect.left(),
308 | out_max=out_reference_rect.right())
309 | top = relative(
310 | value=rect.top(),
311 | in_min=in_reference_rect.top(),
312 | in_max=in_reference_rect.bottom(),
313 | out_min=out_reference_rect.top(),
314 | out_max=out_reference_rect.bottom())
315 | right = relative(
316 | value=rect.right(),
317 | in_min=in_reference_rect.left(),
318 | in_max=in_reference_rect.right(),
319 | out_min=out_reference_rect.left(),
320 | out_max=out_reference_rect.right())
321 | bottom = relative(
322 | value=rect.bottom(),
323 | in_min=in_reference_rect.top(),
324 | in_max=in_reference_rect.bottom(),
325 | out_min=out_reference_rect.top(),
326 | out_max=out_reference_rect.bottom())
327 | rect.setCoords(left, top, right, bottom)
328 |
329 |
330 | def resize_rect_with_direction(rect, cursor, direction, force_square=False):
331 | if direction == 'top_left':
332 | if cursor.x() < rect.right():
333 | if cursor.y() < rect.bottom():
334 | rect.setTopLeft(cursor)
335 | if force_square:
336 | left = rect.right() - rect.height()
337 | rect.setLeft(left)
338 |
339 | elif direction == 'bottom_left':
340 | if cursor.x() < rect.right():
341 | if cursor.y() > rect.top():
342 | rect.setBottomLeft(cursor)
343 | if force_square:
344 | rect.setHeight(rect.width())
345 |
346 | elif direction == 'top_right':
347 | if cursor.x() > rect.left():
348 | if cursor.y() < rect.bottom():
349 | rect.setTopRight(cursor)
350 | if force_square:
351 | rect.setWidth(rect.height())
352 |
353 | elif direction == 'bottom_right':
354 | if cursor.x() > rect.left():
355 | if cursor.y() > rect.top():
356 | rect.setBottomRight(cursor)
357 | if force_square:
358 | rect.setHeight(rect.width())
359 |
360 | elif direction == 'left':
361 | if cursor.x() < rect.right():
362 | rect.setLeft(cursor.x())
363 | if force_square:
364 | rect.setHeight(rect.width())
365 |
366 | elif direction == 'right':
367 | if cursor.x() > rect.left():
368 | rect.setRight(cursor.x())
369 | if force_square:
370 | rect.setHeight(rect.width())
371 |
372 | elif direction == 'top':
373 | if cursor.y() < rect.bottom():
374 | rect.setTop(cursor.y())
375 | if force_square:
376 | rect.setWidth(rect.height())
377 |
378 | elif direction == 'bottom':
379 | if cursor.y() > rect.top():
380 | rect.setBottom(cursor.y())
381 | if force_square:
382 | rect.setWidth(rect.height())
383 |
384 |
385 | class Transform():
386 | def __init__(self):
387 | self.snap = None
388 | self.direction = None
389 | self.rect = None
390 | self.mode = None
391 | self.square = False
392 | self.reference_x = None
393 | self.reference_y = None
394 | self.reference_rect = None
395 |
396 | def set_rect(self, rect):
397 | self.rect = rect
398 | if rect is None:
399 | self.reference_x = None
400 | self.reference_y = None
401 | return
402 |
403 | def set_reference_point(self, cursor):
404 | self.reference_x = cursor.x() - self.rect.left()
405 | self.reference_y = cursor.y() - self.rect.top()
406 |
407 | def resize(self, rects, cursor):
408 | if self.snap is not None:
409 | x, y = snap(cursor.x(), cursor.y(), self.snap)
410 | cursor.setX(x)
411 | cursor.setY(y)
412 | resize_rect_with_direction(
413 | self.rect, cursor, self.direction, force_square=self.square)
414 | self.apply_relative_transformation(rects)
415 |
416 | def apply_relative_transformation(self, rects):
417 | for rect in rects:
418 | resize_rect_with_reference(
419 | rect, self.reference_rect, self.rect)
420 |
421 | self.reference_rect = QtCore.QRectF(
422 | self.rect.topLeft(), self.rect.bottomRight())
423 |
424 | def move(self, rects, cursor):
425 | x = cursor.x() - self.reference_x
426 | y = cursor.y() - self.reference_y
427 | if self.snap is not None:
428 | x, y = snap(x, y, self.snap)
429 | width = self.rect.width()
430 | height = self.rect.height()
431 | self.rect.setTopLeft(QtCore.QPointF(x, y))
432 | self.rect.setWidth(width)
433 | self.rect.setHeight(height)
434 | self.apply_relative_transformation(rects)
435 |
436 |
437 | def snap(x, y, snap):
438 | x = snap[0] * round(x / snap[0])
439 | y = snap[1] * round(y / snap[1])
440 | return x, y
441 |
442 |
443 | def get_combined_rects(rects):
444 | """
445 | this function analyse list of rects and return
446 | a rect with the smaller top and left and highest right and bottom
447 | __________________________________ ?
448 | | | A |
449 | | | |
450 | |______________| ___________| B
451 | | | |
452 | |_____________________|__________|
453 | """
454 | if not rects:
455 | return None
456 | left = min([rect.left() for rect in rects])
457 | right = max([rect.right() for rect in rects])
458 | top = min([rect.top() for rect in rects])
459 | bottom = max([rect.bottom() for rect in rects])
460 | return QtCore.QRectF(left, top, right - left, bottom - top)
461 |
--------------------------------------------------------------------------------
/hotbox_designer/resources/templates/actions.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "shapes": [
4 | {
5 | "bgcolor.normal": "#888888",
6 | "borderwidth.hovered": 0,
7 | "borderwidth.normal": 0,
8 | "image.width": 32,
9 | "bordercolor.transparency": 125.0,
10 | "shape": "square",
11 | "borderwidth.clicked": 0,
12 | "action.right.close": false,
13 | "border": false,
14 | "action.right": false,
15 | "text.valign": "center",
16 | "bordercolor.hovered": "#888888",
17 | "image.path": "",
18 | "action.left": false,
19 | "image.height": 32,
20 | "action.left.command": "",
21 | "text.content": "",
22 | "bordercolor.normal": "#888888",
23 | "action.left.close": false,
24 | "shape.height": 380.0,
25 | "text.bold": false,
26 | "bgcolor.clicked": "#888888",
27 | "action.left.language": "python",
28 | "shape.width": 150.0,
29 | "action.right.command": "",
30 | "bordercolor.clicked": "#888888",
31 | "image.fit": false,
32 | "shape.top": 20.0,
33 | "bgcolor.hovered": "#888888",
34 | "text.italic": false,
35 | "bgcolor.transparency": 0,
36 | "action.right.language": "python",
37 | "text.color": "#FFFFFF",
38 | "text.size": 12,
39 | "text.halign": "center",
40 | "shape.left": 0.0
41 | },
42 | {
43 | "bgcolor.normal": "#575757",
44 | "borderwidth.hovered": 0,
45 | "borderwidth.normal": 0,
46 | "image.width": 32,
47 | "bordercolor.transparency": 0,
48 | "shape": "square",
49 | "borderwidth.clicked": 0,
50 | "action.right.close": false,
51 | "border": false,
52 | "action.right": false,
53 | "text.valign": "center",
54 | "bordercolor.hovered": "#888888",
55 | "image.path": "",
56 | "action.left": false,
57 | "image.height": 32,
58 | "action.left.command": "",
59 | "text.content": "",
60 | "bordercolor.normal": "#888888",
61 | "action.left.close": false,
62 | "shape.height": 20.0,
63 | "text.bold": false,
64 | "bgcolor.clicked": "#575757",
65 | "action.left.language": "python",
66 | "shape.width": 370.0,
67 | "action.right.command": "",
68 | "bordercolor.clicked": "#888888",
69 | "image.fit": false,
70 | "shape.top": 0.0,
71 | "bgcolor.hovered": "#575757",
72 | "text.italic": false,
73 | "bgcolor.transparency": 0,
74 | "action.right.language": "python",
75 | "text.color": "#FFFFFF",
76 | "text.size": 12,
77 | "text.halign": "center",
78 | "shape.left": 0.0
79 | },
80 | {
81 | "bgcolor.normal": "#888888",
82 | "borderwidth.hovered": 0,
83 | "borderwidth.normal": 0,
84 | "image.width": 32,
85 | "bordercolor.transparency": 0,
86 | "shape": "square",
87 | "borderwidth.clicked": 0,
88 | "action.right.close": false,
89 | "border": false,
90 | "action.right": false,
91 | "text.valign": "top",
92 | "bordercolor.hovered": "#393939",
93 | "image.path": "",
94 | "action.left": false,
95 | "image.height": 32,
96 | "action.left.command": "",
97 | "text.content": "Title",
98 | "bordercolor.normal": "#000000",
99 | "action.left.close": false,
100 | "shape.height": 20.0,
101 | "text.bold": true,
102 | "bgcolor.clicked": "#DDDDDD",
103 | "action.left.language": "python",
104 | "shape.width": 65.0,
105 | "action.right.command": "",
106 | "bordercolor.clicked": "#FFFFFF",
107 | "image.fit": false,
108 | "shape.top": 0.0,
109 | "bgcolor.hovered": "#AAAAAA",
110 | "text.italic": false,
111 | "bgcolor.transparency": 255,
112 | "action.right.language": "python",
113 | "text.color": "#FFFFFF",
114 | "text.size": 16,
115 | "text.halign": "left",
116 | "shape.left": 5.0
117 | },
118 | {
119 | "bgcolor.normal": "#b34444",
120 | "borderwidth.hovered": 1.25,
121 | "borderwidth.normal": 1.0,
122 | "image.width": 32,
123 | "bordercolor.transparency": 0,
124 | "shape": "square",
125 | "borderwidth.clicked": 2,
126 | "action.right.close": false,
127 | "border": false,
128 | "action.right": false,
129 | "text.valign": "center",
130 | "bordercolor.hovered": "#393939",
131 | "image.path": "",
132 | "action.left": true,
133 | "image.height": 32,
134 | "action.left.command": "",
135 | "text.content": "x",
136 | "bordercolor.normal": "#000000",
137 | "action.left.close": true,
138 | "shape.height": 20.0,
139 | "text.bold": true,
140 | "bgcolor.clicked": "#DDDDDD",
141 | "action.left.language": "python",
142 | "shape.width": 20.0,
143 | "action.right.command": "",
144 | "bordercolor.clicked": "#FFFFFF",
145 | "image.fit": true,
146 | "shape.top": 0.0,
147 | "bgcolor.hovered": "#045dc2",
148 | "text.italic": false,
149 | "bgcolor.transparency": 0,
150 | "action.right.language": "python",
151 | "text.color": "#FFFFFF",
152 | "text.size": 12,
153 | "text.halign": "center",
154 | "shape.left": 130.0
155 | },
156 | {
157 | "bgcolor.normal": "#888888",
158 | "borderwidth.hovered": 1.25,
159 | "borderwidth.normal": 1.0,
160 | "image.width": 32,
161 | "bordercolor.transparency": 0,
162 | "shape": "square",
163 | "borderwidth.clicked": 2,
164 | "action.right.close": false,
165 | "border": true,
166 | "action.right": false,
167 | "text.valign": "center",
168 | "bordercolor.hovered": "#393939",
169 | "image.path": "",
170 | "action.left": true,
171 | "image.height": 32,
172 | "action.left.command": "",
173 | "text.content": "Button",
174 | "bordercolor.normal": "#000000",
175 | "action.left.close": false,
176 | "shape.height": 20.0,
177 | "text.bold": false,
178 | "bgcolor.clicked": "#DDDDDD",
179 | "action.left.language": "python",
180 | "shape.width": 140.0,
181 | "action.right.command": "",
182 | "bordercolor.clicked": "#FFFFFF",
183 | "image.fit": true,
184 | "shape.top": 25.0,
185 | "bgcolor.hovered": "#AAAAAA",
186 | "text.italic": false,
187 | "bgcolor.transparency": 0,
188 | "action.right.language": "python",
189 | "text.color": "#FFFFFF",
190 | "text.size": 12,
191 | "text.halign": "center",
192 | "shape.left": 5.0
193 | },
194 | {
195 | "bgcolor.normal": "#888888",
196 | "borderwidth.hovered": 1.25,
197 | "borderwidth.normal": 1.0,
198 | "image.width": 32,
199 | "bordercolor.transparency": 0,
200 | "shape": "square",
201 | "borderwidth.clicked": 2,
202 | "action.right.close": false,
203 | "border": true,
204 | "action.right": false,
205 | "text.valign": "center",
206 | "bordercolor.hovered": "#393939",
207 | "image.path": "",
208 | "action.left": true,
209 | "image.height": 32,
210 | "action.left.command": "",
211 | "text.content": "Button",
212 | "bordercolor.normal": "#000000",
213 | "action.left.close": false,
214 | "shape.height": 20.0,
215 | "text.bold": false,
216 | "bgcolor.clicked": "#DDDDDD",
217 | "action.left.language": "python",
218 | "shape.width": 140.0,
219 | "action.right.command": "",
220 | "bordercolor.clicked": "#FFFFFF",
221 | "image.fit": true,
222 | "shape.top": 50.0,
223 | "bgcolor.hovered": "#AAAAAA",
224 | "text.italic": false,
225 | "bgcolor.transparency": 0,
226 | "action.right.language": "python",
227 | "text.color": "#FFFFFF",
228 | "text.size": 12,
229 | "text.halign": "center",
230 | "shape.left": 5.0
231 | },
232 | {
233 | "bgcolor.normal": "#888888",
234 | "borderwidth.hovered": 1.25,
235 | "borderwidth.normal": 1.0,
236 | "image.width": 32,
237 | "bordercolor.transparency": 0,
238 | "shape": "square",
239 | "borderwidth.clicked": 2,
240 | "action.right.close": false,
241 | "border": true,
242 | "action.right": false,
243 | "text.valign": "center",
244 | "bordercolor.hovered": "#393939",
245 | "image.path": "",
246 | "action.left": true,
247 | "image.height": 32,
248 | "action.left.command": "",
249 | "text.content": "Button",
250 | "bordercolor.normal": "#000000",
251 | "action.left.close": false,
252 | "shape.height": 20.0,
253 | "text.bold": false,
254 | "bgcolor.clicked": "#DDDDDD",
255 | "action.left.language": "python",
256 | "shape.width": 140.0,
257 | "action.right.command": "",
258 | "bordercolor.clicked": "#FFFFFF",
259 | "image.fit": true,
260 | "shape.top": 75.0,
261 | "bgcolor.hovered": "#AAAAAA",
262 | "text.italic": false,
263 | "bgcolor.transparency": 0,
264 | "action.right.language": "python",
265 | "text.color": "#FFFFFF",
266 | "text.size": 12,
267 | "text.halign": "center",
268 | "shape.left": 5.0
269 | },
270 | {
271 | "bgcolor.normal": "#888888",
272 | "borderwidth.hovered": 1.25,
273 | "borderwidth.normal": 1.0,
274 | "image.width": 32,
275 | "bordercolor.transparency": 0,
276 | "shape": "square",
277 | "borderwidth.clicked": 2,
278 | "action.right.close": false,
279 | "border": true,
280 | "action.right": false,
281 | "text.valign": "center",
282 | "bordercolor.hovered": "#393939",
283 | "image.path": "",
284 | "action.left": true,
285 | "image.height": 32,
286 | "action.left.command": "",
287 | "text.content": "Button",
288 | "bordercolor.normal": "#000000",
289 | "action.left.close": false,
290 | "shape.height": 20.0,
291 | "text.bold": false,
292 | "bgcolor.clicked": "#DDDDDD",
293 | "action.left.language": "python",
294 | "shape.width": 140.0,
295 | "action.right.command": "",
296 | "bordercolor.clicked": "#FFFFFF",
297 | "image.fit": true,
298 | "shape.top": 150.0,
299 | "bgcolor.hovered": "#AAAAAA",
300 | "text.italic": false,
301 | "bgcolor.transparency": 0,
302 | "action.right.language": "python",
303 | "text.color": "#FFFFFF",
304 | "text.size": 12,
305 | "text.halign": "center",
306 | "shape.left": 5.0
307 | },
308 | {
309 | "bgcolor.normal": "#888888",
310 | "borderwidth.hovered": 1.25,
311 | "borderwidth.normal": 1.0,
312 | "image.width": 32,
313 | "bordercolor.transparency": 0,
314 | "shape": "square",
315 | "borderwidth.clicked": 2,
316 | "action.right.close": false,
317 | "border": true,
318 | "action.right": false,
319 | "text.valign": "center",
320 | "bordercolor.hovered": "#393939",
321 | "image.path": "",
322 | "action.left": true,
323 | "image.height": 32,
324 | "action.left.command": "",
325 | "text.content": "Button",
326 | "bordercolor.normal": "#000000",
327 | "action.left.close": false,
328 | "shape.height": 20.0,
329 | "text.bold": false,
330 | "bgcolor.clicked": "#DDDDDD",
331 | "action.left.language": "python",
332 | "shape.width": 140.0,
333 | "action.right.command": "",
334 | "bordercolor.clicked": "#FFFFFF",
335 | "image.fit": true,
336 | "shape.top": 125.0,
337 | "bgcolor.hovered": "#AAAAAA",
338 | "text.italic": false,
339 | "bgcolor.transparency": 0,
340 | "action.right.language": "python",
341 | "text.color": "#FFFFFF",
342 | "text.size": 12,
343 | "text.halign": "center",
344 | "shape.left": 5.0
345 | },
346 | {
347 | "bgcolor.normal": "#888888",
348 | "borderwidth.hovered": 1.25,
349 | "borderwidth.normal": 1.0,
350 | "image.width": 32,
351 | "bordercolor.transparency": 0,
352 | "shape": "square",
353 | "borderwidth.clicked": 2,
354 | "action.right.close": false,
355 | "border": true,
356 | "action.right": false,
357 | "text.valign": "center",
358 | "bordercolor.hovered": "#393939",
359 | "image.path": "",
360 | "action.left": true,
361 | "image.height": 32,
362 | "action.left.command": "",
363 | "text.content": "Button",
364 | "bordercolor.normal": "#000000",
365 | "action.left.close": false,
366 | "shape.height": 20.0,
367 | "text.bold": false,
368 | "bgcolor.clicked": "#DDDDDD",
369 | "action.left.language": "python",
370 | "shape.width": 140.0,
371 | "action.right.command": "",
372 | "bordercolor.clicked": "#FFFFFF",
373 | "image.fit": true,
374 | "shape.top": 100.0,
375 | "bgcolor.hovered": "#AAAAAA",
376 | "text.italic": false,
377 | "bgcolor.transparency": 0,
378 | "action.right.language": "python",
379 | "text.color": "#FFFFFF",
380 | "text.size": 12,
381 | "text.halign": "center",
382 | "shape.left": 5.0
383 | },
384 | {
385 | "bgcolor.normal": "#888888",
386 | "borderwidth.hovered": 1.25,
387 | "borderwidth.normal": 1.0,
388 | "image.width": 32,
389 | "bordercolor.transparency": 0,
390 | "shape": "square",
391 | "borderwidth.clicked": 2,
392 | "action.right.close": false,
393 | "border": true,
394 | "action.right": false,
395 | "text.valign": "center",
396 | "bordercolor.hovered": "#393939",
397 | "image.path": "",
398 | "action.left": true,
399 | "image.height": 32,
400 | "action.left.command": "",
401 | "text.content": "Button",
402 | "bordercolor.normal": "#000000",
403 | "action.left.close": false,
404 | "shape.height": 20.0,
405 | "text.bold": false,
406 | "bgcolor.clicked": "#DDDDDD",
407 | "action.left.language": "python",
408 | "shape.width": 140.0,
409 | "action.right.command": "",
410 | "bordercolor.clicked": "#FFFFFF",
411 | "image.fit": true,
412 | "shape.top": 225.0,
413 | "bgcolor.hovered": "#AAAAAA",
414 | "text.italic": false,
415 | "bgcolor.transparency": 0,
416 | "action.right.language": "python",
417 | "text.color": "#FFFFFF",
418 | "text.size": 12,
419 | "text.halign": "center",
420 | "shape.left": 5.0
421 | },
422 | {
423 | "bgcolor.normal": "#888888",
424 | "borderwidth.hovered": 1.25,
425 | "borderwidth.normal": 1.0,
426 | "image.width": 32,
427 | "bordercolor.transparency": 0,
428 | "shape": "square",
429 | "borderwidth.clicked": 2,
430 | "action.right.close": false,
431 | "border": true,
432 | "action.right": false,
433 | "text.valign": "center",
434 | "bordercolor.hovered": "#393939",
435 | "image.path": "",
436 | "action.left": true,
437 | "image.height": 32,
438 | "action.left.command": "",
439 | "text.content": "Button",
440 | "bordercolor.normal": "#000000",
441 | "action.left.close": false,
442 | "shape.height": 20.0,
443 | "text.bold": false,
444 | "bgcolor.clicked": "#DDDDDD",
445 | "action.left.language": "python",
446 | "shape.width": 140.0,
447 | "action.right.command": "",
448 | "bordercolor.clicked": "#FFFFFF",
449 | "image.fit": true,
450 | "shape.top": 200.0,
451 | "bgcolor.hovered": "#AAAAAA",
452 | "text.italic": false,
453 | "bgcolor.transparency": 0,
454 | "action.right.language": "python",
455 | "text.color": "#FFFFFF",
456 | "text.size": 12,
457 | "text.halign": "center",
458 | "shape.left": 5.0
459 | },
460 | {
461 | "bgcolor.normal": "#888888",
462 | "borderwidth.hovered": 1.25,
463 | "borderwidth.normal": 1.0,
464 | "image.width": 32,
465 | "bordercolor.transparency": 0,
466 | "shape": "square",
467 | "borderwidth.clicked": 2,
468 | "action.right.close": false,
469 | "border": true,
470 | "action.right": false,
471 | "text.valign": "center",
472 | "bordercolor.hovered": "#393939",
473 | "image.path": "",
474 | "action.left": true,
475 | "image.height": 32,
476 | "action.left.command": "",
477 | "text.content": "Button",
478 | "bordercolor.normal": "#000000",
479 | "action.left.close": false,
480 | "shape.height": 20.0,
481 | "text.bold": false,
482 | "bgcolor.clicked": "#DDDDDD",
483 | "action.left.language": "python",
484 | "shape.width": 140.0,
485 | "action.right.command": "",
486 | "bordercolor.clicked": "#FFFFFF",
487 | "image.fit": true,
488 | "shape.top": 175.0,
489 | "bgcolor.hovered": "#AAAAAA",
490 | "text.italic": false,
491 | "bgcolor.transparency": 0,
492 | "action.right.language": "python",
493 | "text.color": "#FFFFFF",
494 | "text.size": 12,
495 | "text.halign": "center",
496 | "shape.left": 5.0
497 | },
498 | {
499 | "bgcolor.normal": "#888888",
500 | "borderwidth.hovered": 1.25,
501 | "borderwidth.normal": 1.0,
502 | "image.width": 32,
503 | "bordercolor.transparency": 0,
504 | "shape": "square",
505 | "borderwidth.clicked": 2,
506 | "action.right.close": false,
507 | "border": true,
508 | "action.right": false,
509 | "text.valign": "center",
510 | "bordercolor.hovered": "#393939",
511 | "image.path": "",
512 | "action.left": true,
513 | "image.height": 32,
514 | "action.left.command": "",
515 | "text.content": "Button",
516 | "bordercolor.normal": "#000000",
517 | "action.left.close": false,
518 | "shape.height": 20.0,
519 | "text.bold": false,
520 | "bgcolor.clicked": "#DDDDDD",
521 | "action.left.language": "python",
522 | "shape.width": 140.0,
523 | "action.right.command": "",
524 | "bordercolor.clicked": "#FFFFFF",
525 | "image.fit": true,
526 | "shape.top": 250.0,
527 | "bgcolor.hovered": "#AAAAAA",
528 | "text.italic": false,
529 | "bgcolor.transparency": 0,
530 | "action.right.language": "python",
531 | "text.color": "#FFFFFF",
532 | "text.size": 12,
533 | "text.halign": "center",
534 | "shape.left": 5.0
535 | }
536 | ],
537 | "general": {
538 | "control": false,
539 | "name": "Action_Menu",
540 | "height": 275,
541 | "width": 150,
542 | "centerx": 80,
543 | "centery": 60,
544 | "touch": "",
545 | "alt": true,
546 | "aiming": false,
547 | "triggering": "click",
548 | "submenu": true,
549 | "leaveclose": false
550 | }
551 | }
--------------------------------------------------------------------------------
/hotbox_designer/resources/templates/context.json:
--------------------------------------------------------------------------------
1 | {
2 | "shapes": [
3 | {
4 | "bgcolor.normal": "#888888",
5 | "borderwidth.hovered": 0,
6 | "borderwidth.normal": 0,
7 | "image.width": 32,
8 | "bordercolor.transparency": 0,
9 | "shape": "square",
10 | "borderwidth.clicked": 0,
11 | "action.right.close": false,
12 | "border": true,
13 | "action.right": false,
14 | "text.valign": "center",
15 | "bordercolor.hovered": "#000000",
16 | "image.path": "",
17 | "action.left": false,
18 | "image.height": 32,
19 | "action.left.command": "",
20 | "text.content": "",
21 | "bordercolor.normal": "#000000",
22 | "action.left.close": false,
23 | "shape.height": 260.0,
24 | "text.bold": false,
25 | "bgcolor.clicked": "#888888",
26 | "action.left.language": "python",
27 | "shape.width": 190.0,
28 | "action.right.command": "",
29 | "bordercolor.clicked": "#000000",
30 | "image.fit": false,
31 | "shape.top": 90.0,
32 | "bgcolor.hovered": "#888888",
33 | "text.italic": false,
34 | "bgcolor.transparency": 0,
35 | "action.right.language": "python",
36 | "text.color": "#FFFFFF",
37 | "text.size": 12,
38 | "text.halign": "center",
39 | "shape.left": 100.0
40 | },
41 | {
42 | "bgcolor.normal": "#424242",
43 | "borderwidth.hovered": 1.25,
44 | "borderwidth.normal": 1.0,
45 | "image.width": 32,
46 | "bordercolor.transparency": 0,
47 | "shape": "square",
48 | "borderwidth.clicked": 2,
49 | "action.right.close": false,
50 | "border": false,
51 | "action.right": false,
52 | "text.valign": "center",
53 | "bordercolor.hovered": "#4384a8",
54 | "image.path": "",
55 | "action.left": true,
56 | "image.height": 32,
57 | "action.left.command": "",
58 | "text.content": " Action 1",
59 | "bordercolor.normal": "#000000",
60 | "action.left.close": true,
61 | "shape.height": 20.5,
62 | "text.bold": false,
63 | "bgcolor.clicked": "#65aae6",
64 | "action.left.language": "python",
65 | "shape.width": 190.0,
66 | "action.right.command": "",
67 | "bordercolor.clicked": "#FFFFFF",
68 | "image.fit": true,
69 | "shape.top": 90.0,
70 | "bgcolor.hovered": "#65aae6",
71 | "text.italic": false,
72 | "bgcolor.transparency": 0,
73 | "action.right.language": "python",
74 | "text.color": "#FFFFFF",
75 | "text.size": 12,
76 | "text.halign": "left",
77 | "shape.left": 100.0
78 | },
79 | {
80 | "bgcolor.normal": "#424242",
81 | "borderwidth.hovered": 1.25,
82 | "borderwidth.normal": 1.0,
83 | "image.width": 32,
84 | "bordercolor.transparency": 0,
85 | "shape": "square",
86 | "borderwidth.clicked": 2,
87 | "action.right.close": false,
88 | "border": false,
89 | "action.right": false,
90 | "text.valign": "center",
91 | "bordercolor.hovered": "#4384a8",
92 | "image.path": "",
93 | "action.left": true,
94 | "image.height": 32,
95 | "action.left.command": "",
96 | "text.content": " Action 2",
97 | "bordercolor.normal": "#000000",
98 | "action.left.close": true,
99 | "shape.height": 20.5,
100 | "text.bold": false,
101 | "bgcolor.clicked": "#65aae6",
102 | "action.left.language": "python",
103 | "shape.width": 190.0,
104 | "action.right.command": "",
105 | "bordercolor.clicked": "#FFFFFF",
106 | "image.fit": true,
107 | "shape.top": 110.0,
108 | "bgcolor.hovered": "#65aae6",
109 | "text.italic": false,
110 | "bgcolor.transparency": 0,
111 | "action.right.language": "python",
112 | "text.color": "#FFFFFF",
113 | "text.size": 12,
114 | "text.halign": "left",
115 | "shape.left": 100.0
116 | },
117 | {
118 | "bgcolor.normal": "#424242",
119 | "borderwidth.hovered": 1.25,
120 | "borderwidth.normal": 1.0,
121 | "image.width": 32,
122 | "bordercolor.transparency": 0,
123 | "shape": "square",
124 | "borderwidth.clicked": 2,
125 | "action.right.close": false,
126 | "border": false,
127 | "action.right": false,
128 | "text.valign": "center",
129 | "bordercolor.hovered": "#4384a8",
130 | "image.path": "",
131 | "action.left": true,
132 | "image.height": 32,
133 | "action.left.command": "",
134 | "text.content": " Action 3",
135 | "bordercolor.normal": "#000000",
136 | "action.left.close": true,
137 | "shape.height": 20.5,
138 | "text.bold": false,
139 | "bgcolor.clicked": "#65aae6",
140 | "action.left.language": "python",
141 | "shape.width": 190.0,
142 | "action.right.command": "",
143 | "bordercolor.clicked": "#FFFFFF",
144 | "image.fit": true,
145 | "shape.top": 130.0,
146 | "bgcolor.hovered": "#65aae6",
147 | "text.italic": false,
148 | "bgcolor.transparency": 0,
149 | "action.right.language": "python",
150 | "text.color": "#FFFFFF",
151 | "text.size": 12,
152 | "text.halign": "left",
153 | "shape.left": 100.0
154 | },
155 | {
156 | "bgcolor.normal": "#424242",
157 | "borderwidth.hovered": 1.25,
158 | "borderwidth.normal": 1.0,
159 | "image.width": 32,
160 | "bordercolor.transparency": 0,
161 | "shape": "square",
162 | "borderwidth.clicked": 2,
163 | "action.right.close": false,
164 | "border": false,
165 | "action.right": false,
166 | "text.valign": "center",
167 | "bordercolor.hovered": "#4384a8",
168 | "image.path": "",
169 | "action.left": true,
170 | "image.height": 32,
171 | "action.left.command": "",
172 | "text.content": " Action 4",
173 | "bordercolor.normal": "#000000",
174 | "action.left.close": true,
175 | "shape.height": 20.5,
176 | "text.bold": false,
177 | "bgcolor.clicked": "#65aae6",
178 | "action.left.language": "python",
179 | "shape.width": 190.0,
180 | "action.right.command": "",
181 | "bordercolor.clicked": "#FFFFFF",
182 | "image.fit": true,
183 | "shape.top": 150.0,
184 | "bgcolor.hovered": "#65aae6",
185 | "text.italic": false,
186 | "bgcolor.transparency": 0,
187 | "action.right.language": "python",
188 | "text.color": "#FFFFFF",
189 | "text.size": 12,
190 | "text.halign": "left",
191 | "shape.left": 100.0
192 | },
193 | {
194 | "bgcolor.normal": "#424242",
195 | "borderwidth.hovered": 1.25,
196 | "borderwidth.normal": 1.0,
197 | "image.width": 32,
198 | "bordercolor.transparency": 0,
199 | "shape": "square",
200 | "borderwidth.clicked": 2,
201 | "action.right.close": false,
202 | "border": false,
203 | "action.right": false,
204 | "text.valign": "center",
205 | "bordercolor.hovered": "#4384a8",
206 | "image.path": "",
207 | "action.left": true,
208 | "image.height": 32,
209 | "action.left.command": "",
210 | "text.content": " Action 5",
211 | "bordercolor.normal": "#000000",
212 | "action.left.close": true,
213 | "shape.height": 20.5,
214 | "text.bold": false,
215 | "bgcolor.clicked": "#65aae6",
216 | "action.left.language": "python",
217 | "shape.width": 190.0,
218 | "action.right.command": "",
219 | "bordercolor.clicked": "#FFFFFF",
220 | "image.fit": true,
221 | "shape.top": 170.0,
222 | "bgcolor.hovered": "#65aae6",
223 | "text.italic": false,
224 | "bgcolor.transparency": 0,
225 | "action.right.language": "python",
226 | "text.color": "#FFFFFF",
227 | "text.size": 12,
228 | "text.halign": "left",
229 | "shape.left": 100.0
230 | },
231 | {
232 | "bgcolor.normal": "#424242",
233 | "borderwidth.hovered": 1.25,
234 | "borderwidth.normal": 1.0,
235 | "image.width": 32,
236 | "bordercolor.transparency": 0,
237 | "shape": "square",
238 | "borderwidth.clicked": 2,
239 | "action.right.close": false,
240 | "border": false,
241 | "action.right": false,
242 | "text.valign": "center",
243 | "bordercolor.hovered": "#4384a8",
244 | "image.path": "",
245 | "action.left": true,
246 | "image.height": 32,
247 | "action.left.command": "",
248 | "text.content": " Action 6",
249 | "bordercolor.normal": "#000000",
250 | "action.left.close": true,
251 | "shape.height": 20.5,
252 | "text.bold": false,
253 | "bgcolor.clicked": "#65aae6",
254 | "action.left.language": "python",
255 | "shape.width": 190.0,
256 | "action.right.command": "",
257 | "bordercolor.clicked": "#FFFFFF",
258 | "image.fit": true,
259 | "shape.top": 190.0,
260 | "bgcolor.hovered": "#65aae6",
261 | "text.italic": false,
262 | "bgcolor.transparency": 0,
263 | "action.right.language": "python",
264 | "text.color": "#FFFFFF",
265 | "text.size": 12,
266 | "text.halign": "left",
267 | "shape.left": 100.0
268 | },
269 | {
270 | "bgcolor.normal": "#424242",
271 | "borderwidth.hovered": 1.25,
272 | "borderwidth.normal": 1.0,
273 | "image.width": 32,
274 | "bordercolor.transparency": 0,
275 | "shape": "square",
276 | "borderwidth.clicked": 2,
277 | "action.right.close": false,
278 | "border": false,
279 | "action.right": false,
280 | "text.valign": "center",
281 | "bordercolor.hovered": "#4384a8",
282 | "image.path": "",
283 | "action.left": true,
284 | "image.height": 32,
285 | "action.left.command": "",
286 | "text.content": " Action 7",
287 | "bordercolor.normal": "#000000",
288 | "action.left.close": true,
289 | "shape.height": 20.5,
290 | "text.bold": false,
291 | "bgcolor.clicked": "#65aae6",
292 | "action.left.language": "python",
293 | "shape.width": 190.0,
294 | "action.right.command": "",
295 | "bordercolor.clicked": "#FFFFFF",
296 | "image.fit": true,
297 | "shape.top": 210.0,
298 | "bgcolor.hovered": "#65aae6",
299 | "text.italic": false,
300 | "bgcolor.transparency": 0,
301 | "action.right.language": "python",
302 | "text.color": "#FFFFFF",
303 | "text.size": 12,
304 | "text.halign": "left",
305 | "shape.left": 100.0
306 | },
307 | {
308 | "bgcolor.normal": "#424242",
309 | "borderwidth.hovered": 1.25,
310 | "borderwidth.normal": 1.0,
311 | "image.width": 32,
312 | "bordercolor.transparency": 0,
313 | "shape": "square",
314 | "borderwidth.clicked": 2,
315 | "action.right.close": false,
316 | "border": false,
317 | "action.right": false,
318 | "text.valign": "center",
319 | "bordercolor.hovered": "#4384a8",
320 | "image.path": "",
321 | "action.left": true,
322 | "image.height": 32,
323 | "action.left.command": "",
324 | "text.content": " Action 8",
325 | "bordercolor.normal": "#000000",
326 | "action.left.close": true,
327 | "shape.height": 20.5,
328 | "text.bold": false,
329 | "bgcolor.clicked": "#65aae6",
330 | "action.left.language": "python",
331 | "shape.width": 190.0,
332 | "action.right.command": "",
333 | "bordercolor.clicked": "#FFFFFF",
334 | "image.fit": true,
335 | "shape.top": 230.0,
336 | "bgcolor.hovered": "#65aae6",
337 | "text.italic": false,
338 | "bgcolor.transparency": 0,
339 | "action.right.language": "python",
340 | "text.color": "#FFFFFF",
341 | "text.size": 12,
342 | "text.halign": "left",
343 | "shape.left": 100.0
344 | },
345 | {
346 | "bgcolor.normal": "#424242",
347 | "borderwidth.hovered": 1.25,
348 | "borderwidth.normal": 1.0,
349 | "image.width": 32,
350 | "bordercolor.transparency": 0,
351 | "shape": "square",
352 | "borderwidth.clicked": 2,
353 | "action.right.close": false,
354 | "border": false,
355 | "action.right": false,
356 | "text.valign": "center",
357 | "bordercolor.hovered": "#4384a8",
358 | "image.path": "",
359 | "action.left": true,
360 | "image.height": 32,
361 | "action.left.command": "",
362 | "text.content": " Action 9",
363 | "bordercolor.normal": "#000000",
364 | "action.left.close": true,
365 | "shape.height": 20.5,
366 | "text.bold": false,
367 | "bgcolor.clicked": "#65aae6",
368 | "action.left.language": "python",
369 | "shape.width": 190.0,
370 | "action.right.command": "",
371 | "bordercolor.clicked": "#FFFFFF",
372 | "image.fit": true,
373 | "shape.top": 250.0,
374 | "bgcolor.hovered": "#65aae6",
375 | "text.italic": false,
376 | "bgcolor.transparency": 0,
377 | "action.right.language": "python",
378 | "text.color": "#FFFFFF",
379 | "text.size": 12,
380 | "text.halign": "left",
381 | "shape.left": 100.0
382 | },
383 | {
384 | "bgcolor.normal": "#424242",
385 | "borderwidth.hovered": 1.25,
386 | "borderwidth.normal": 1.0,
387 | "image.width": 32,
388 | "bordercolor.transparency": 0,
389 | "shape": "square",
390 | "borderwidth.clicked": 2,
391 | "action.right.close": false,
392 | "border": false,
393 | "action.right": false,
394 | "text.valign": "center",
395 | "bordercolor.hovered": "#4384a8",
396 | "image.path": "",
397 | "action.left": true,
398 | "image.height": 32,
399 | "action.left.command": "",
400 | "text.content": " Action 10",
401 | "bordercolor.normal": "#000000",
402 | "action.left.close": true,
403 | "shape.height": 20.5,
404 | "text.bold": false,
405 | "bgcolor.clicked": "#65aae6",
406 | "action.left.language": "python",
407 | "shape.width": 190.0,
408 | "action.right.command": "",
409 | "bordercolor.clicked": "#FFFFFF",
410 | "image.fit": true,
411 | "shape.top": 270.0,
412 | "bgcolor.hovered": "#65aae6",
413 | "text.italic": false,
414 | "bgcolor.transparency": 0,
415 | "action.right.language": "python",
416 | "text.color": "#FFFFFF",
417 | "text.size": 12,
418 | "text.halign": "left",
419 | "shape.left": 100.0
420 | },
421 | {
422 | "bgcolor.normal": "#424242",
423 | "borderwidth.hovered": 1.25,
424 | "borderwidth.normal": 1.0,
425 | "image.width": 32,
426 | "bordercolor.transparency": 0,
427 | "shape": "square",
428 | "borderwidth.clicked": 2,
429 | "action.right.close": false,
430 | "border": false,
431 | "action.right": false,
432 | "text.valign": "center",
433 | "bordercolor.hovered": "#4384a8",
434 | "image.path": "",
435 | "action.left": true,
436 | "image.height": 32,
437 | "action.left.command": "",
438 | "text.content": " Action 11",
439 | "bordercolor.normal": "#000000",
440 | "action.left.close": true,
441 | "shape.height": 20.5,
442 | "text.bold": false,
443 | "bgcolor.clicked": "#65aae6",
444 | "action.left.language": "python",
445 | "shape.width": 190.0,
446 | "action.right.command": "",
447 | "bordercolor.clicked": "#FFFFFF",
448 | "image.fit": true,
449 | "shape.top": 290.0,
450 | "bgcolor.hovered": "#65aae6",
451 | "text.italic": false,
452 | "bgcolor.transparency": 0,
453 | "action.right.language": "python",
454 | "text.color": "#FFFFFF",
455 | "text.size": 12,
456 | "text.halign": "left",
457 | "shape.left": 100.0
458 | },
459 | {
460 | "bgcolor.normal": "#424242",
461 | "borderwidth.hovered": 1.25,
462 | "borderwidth.normal": 1.0,
463 | "image.width": 32,
464 | "bordercolor.transparency": 0,
465 | "shape": "square",
466 | "borderwidth.clicked": 2,
467 | "action.right.close": false,
468 | "border": false,
469 | "action.right": false,
470 | "text.valign": "center",
471 | "bordercolor.hovered": "#4384a8",
472 | "image.path": "",
473 | "action.left": true,
474 | "image.height": 32,
475 | "action.left.command": "",
476 | "text.content": " Action 12",
477 | "bordercolor.normal": "#000000",
478 | "action.left.close": true,
479 | "shape.height": 20.5,
480 | "text.bold": false,
481 | "bgcolor.clicked": "#65aae6",
482 | "action.left.language": "python",
483 | "shape.width": 190.0,
484 | "action.right.command": "",
485 | "bordercolor.clicked": "#FFFFFF",
486 | "image.fit": true,
487 | "shape.top": 310.0,
488 | "bgcolor.hovered": "#65aae6",
489 | "text.italic": false,
490 | "bgcolor.transparency": 0,
491 | "action.right.language": "python",
492 | "text.color": "#FFFFFF",
493 | "text.size": 12,
494 | "text.halign": "left",
495 | "shape.left": 100.0
496 | },
497 | {
498 | "bgcolor.normal": "#424242",
499 | "borderwidth.hovered": 1.25,
500 | "borderwidth.normal": 1.0,
501 | "image.width": 32,
502 | "bordercolor.transparency": 0,
503 | "shape": "square",
504 | "borderwidth.clicked": 2,
505 | "action.right.close": false,
506 | "border": false,
507 | "action.right": false,
508 | "text.valign": "center",
509 | "bordercolor.hovered": "#4384a8",
510 | "image.path": "",
511 | "action.left": true,
512 | "image.height": 32,
513 | "action.left.command": "",
514 | "text.content": " Action 13 (the lucky one)",
515 | "bordercolor.normal": "#000000",
516 | "action.left.close": true,
517 | "shape.height": 20.5,
518 | "text.bold": false,
519 | "bgcolor.clicked": "#65aae6",
520 | "action.left.language": "python",
521 | "shape.width": 190.0,
522 | "action.right.command": "",
523 | "bordercolor.clicked": "#FFFFFF",
524 | "image.fit": true,
525 | "shape.top": 330.0,
526 | "bgcolor.hovered": "#65aae6",
527 | "text.italic": false,
528 | "bgcolor.transparency": 0,
529 | "action.right.language": "python",
530 | "text.color": "#FFFFFF",
531 | "text.size": 12,
532 | "text.halign": "left",
533 | "shape.left": 100.0
534 | },
535 | {
536 | "bgcolor.normal": "black",
537 | "borderwidth.hovered": 0,
538 | "borderwidth.normal": 0,
539 | "image.width": 32,
540 | "bordercolor.transparency": 0,
541 | "shape": "square",
542 | "borderwidth.clicked": 0,
543 | "action.right.close": false,
544 | "border": false,
545 | "action.right": false,
546 | "text.valign": "center",
547 | "bordercolor.hovered": "#888888",
548 | "image.path": "",
549 | "action.left": false,
550 | "image.height": 32,
551 | "action.left.command": "",
552 | "text.content": "",
553 | "bordercolor.normal": "black",
554 | "action.left.close": false,
555 | "shape.height": 260.0,
556 | "text.bold": false,
557 | "bgcolor.clicked": "black",
558 | "action.left.language": "python",
559 | "shape.width": 20.0,
560 | "action.right.command": "",
561 | "bordercolor.clicked": "#888888",
562 | "image.fit": false,
563 | "shape.top": 90.0,
564 | "bgcolor.hovered": "black",
565 | "text.italic": false,
566 | "bgcolor.transparency": 200.0,
567 | "action.right.language": "python",
568 | "text.color": "#FFFFFF",
569 | "text.size": 12,
570 | "text.halign": "center",
571 | "shape.left": 100.0
572 | }
573 | ],
574 | "general": {
575 | "submenu": true,
576 | "name": "Context_Menu",
577 | "height": 450,
578 | "width": 400,
579 | "centerx": 100,
580 | "centery": 90,
581 | "aiming": false,
582 | "leaveclose": false,
583 | "triggering": "click only",
584 | "leaveclose": false
585 | }
586 | }
--------------------------------------------------------------------------------
/hotbox_designer/resources/templates/circles.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "shapes": [
4 | {
5 | "bgcolor.normal": "#888888",
6 | "borderwidth.hovered": 1.25,
7 | "borderwidth.normal": 1.0,
8 | "image.width": 32,
9 | "bordercolor.transparency": 0,
10 | "shape": "round",
11 | "borderwidth.clicked": 2,
12 | "action.right.close": false,
13 | "border": false,
14 | "action.right": false,
15 | "text.valign": "center",
16 | "bordercolor.hovered": "#393939",
17 | "image.path": "",
18 | "action.left": true,
19 | "image.height": 32,
20 | "action.left.command": "",
21 | "text.content": "",
22 | "bordercolor.normal": "#000000",
23 | "action.left.close": false,
24 | "shape.height": 101.0,
25 | "text.bold": false,
26 | "bgcolor.clicked": "#DDDDDD",
27 | "action.left.language": "python",
28 | "shape.width": 101.0,
29 | "action.right.command": "",
30 | "bordercolor.clicked": "#FFFFFF",
31 | "image.fit": true,
32 | "shape.top": 180.0,
33 | "bgcolor.hovered": "#AAAAAA",
34 | "text.italic": false,
35 | "bgcolor.transparency": 0,
36 | "action.right.language": "python",
37 | "text.color": "#FFFFFF",
38 | "text.size": 12,
39 | "text.halign": "center",
40 | "shape.left": 180.0
41 | },
42 | {
43 | "bgcolor.normal": "#888888",
44 | "borderwidth.hovered": 1.25,
45 | "borderwidth.normal": 1.0,
46 | "image.width": 32,
47 | "bordercolor.transparency": 0,
48 | "shape": "round",
49 | "borderwidth.clicked": 2,
50 | "action.right.close": false,
51 | "border": false,
52 | "action.right": false,
53 | "text.valign": "center",
54 | "bordercolor.hovered": "#393939",
55 | "image.path": "",
56 | "action.left": true,
57 | "image.height": 32,
58 | "action.left.command": "",
59 | "text.content": "",
60 | "bordercolor.normal": "#000000",
61 | "action.left.close": false,
62 | "shape.height": 40.0,
63 | "text.bold": false,
64 | "bgcolor.clicked": "#DDDDDD",
65 | "action.left.language": "python",
66 | "shape.width": 40.0,
67 | "action.right.command": "",
68 | "bordercolor.clicked": "#FFFFFF",
69 | "image.fit": true,
70 | "shape.top": 210.0,
71 | "bgcolor.hovered": "#AAAAAA",
72 | "text.italic": false,
73 | "bgcolor.transparency": 0,
74 | "action.right.language": "python",
75 | "text.color": "#FFFFFF",
76 | "text.size": 12,
77 | "text.halign": "center",
78 | "shape.left": 120.0
79 | },
80 | {
81 | "bgcolor.normal": "#888888",
82 | "borderwidth.hovered": 1.25,
83 | "borderwidth.normal": 1.0,
84 | "image.width": 32,
85 | "bordercolor.transparency": 0,
86 | "shape": "round",
87 | "borderwidth.clicked": 2,
88 | "action.right.close": false,
89 | "border": false,
90 | "action.right": false,
91 | "text.valign": "center",
92 | "bordercolor.hovered": "#393939",
93 | "image.path": "",
94 | "action.left": true,
95 | "image.height": 32,
96 | "action.left.command": "",
97 | "text.content": "",
98 | "bordercolor.normal": "#000000",
99 | "action.left.close": false,
100 | "shape.height": 40.0,
101 | "text.bold": false,
102 | "bgcolor.clicked": "#DDDDDD",
103 | "action.left.language": "python",
104 | "shape.width": 40.0,
105 | "action.right.command": "",
106 | "bordercolor.clicked": "#FFFFFF",
107 | "image.fit": true,
108 | "shape.top": 120.0,
109 | "bgcolor.hovered": "#AAAAAA",
110 | "text.italic": false,
111 | "bgcolor.transparency": 0,
112 | "action.right.language": "python",
113 | "text.color": "#FFFFFF",
114 | "text.size": 12,
115 | "text.halign": "center",
116 | "shape.left": 210.0
117 | },
118 | {
119 | "bgcolor.normal": "#888888",
120 | "borderwidth.hovered": 1.25,
121 | "borderwidth.normal": 1.0,
122 | "image.width": 32,
123 | "bordercolor.transparency": 0,
124 | "shape": "round",
125 | "borderwidth.clicked": 2,
126 | "action.right.close": false,
127 | "border": false,
128 | "action.right": false,
129 | "text.valign": "center",
130 | "bordercolor.hovered": "#393939",
131 | "image.path": "",
132 | "action.left": true,
133 | "image.height": 32,
134 | "action.left.command": "",
135 | "text.content": "",
136 | "bordercolor.normal": "#000000",
137 | "action.left.close": false,
138 | "shape.height": 40.0,
139 | "text.bold": false,
140 | "bgcolor.clicked": "#DDDDDD",
141 | "action.left.language": "python",
142 | "shape.width": 40.0,
143 | "action.right.command": "",
144 | "bordercolor.clicked": "#FFFFFF",
145 | "image.fit": true,
146 | "shape.top": 210.0,
147 | "bgcolor.hovered": "#AAAAAA",
148 | "text.italic": false,
149 | "bgcolor.transparency": 0,
150 | "action.right.language": "python",
151 | "text.color": "#FFFFFF",
152 | "text.size": 12,
153 | "text.halign": "center",
154 | "shape.left": 300.0
155 | },
156 | {
157 | "bgcolor.normal": "#888888",
158 | "borderwidth.hovered": 1.25,
159 | "borderwidth.normal": 1.0,
160 | "image.width": 32,
161 | "bordercolor.transparency": 0,
162 | "shape": "round",
163 | "borderwidth.clicked": 2,
164 | "action.right.close": false,
165 | "border": false,
166 | "action.right": false,
167 | "text.valign": "center",
168 | "bordercolor.hovered": "#393939",
169 | "image.path": "",
170 | "action.left": true,
171 | "image.height": 32,
172 | "action.left.command": "",
173 | "text.content": "",
174 | "bordercolor.normal": "#000000",
175 | "action.left.close": false,
176 | "shape.height": 40.0,
177 | "text.bold": false,
178 | "bgcolor.clicked": "#DDDDDD",
179 | "action.left.language": "python",
180 | "shape.width": 40.0,
181 | "action.right.command": "",
182 | "bordercolor.clicked": "#FFFFFF",
183 | "image.fit": true,
184 | "shape.top": 300.0,
185 | "bgcolor.hovered": "#AAAAAA",
186 | "text.italic": false,
187 | "bgcolor.transparency": 0,
188 | "action.right.language": "python",
189 | "text.color": "#FFFFFF",
190 | "text.size": 12,
191 | "text.halign": "center",
192 | "shape.left": 210.0
193 | },
194 | {
195 | "bgcolor.normal": "#888888",
196 | "borderwidth.hovered": 1.25,
197 | "borderwidth.normal": 1.0,
198 | "image.width": 32,
199 | "bordercolor.transparency": 0,
200 | "shape": "round",
201 | "borderwidth.clicked": 2,
202 | "action.right.close": false,
203 | "border": false,
204 | "action.right": false,
205 | "text.valign": "center",
206 | "bordercolor.hovered": "#393939",
207 | "image.path": "",
208 | "action.left": true,
209 | "image.height": 32,
210 | "action.left.command": "",
211 | "text.content": "",
212 | "bordercolor.normal": "#000000",
213 | "action.left.close": false,
214 | "shape.height": 40.0,
215 | "text.bold": false,
216 | "bgcolor.clicked": "#DDDDDD",
217 | "action.left.language": "python",
218 | "shape.width": 40.0,
219 | "action.right.command": "",
220 | "bordercolor.clicked": "#FFFFFF",
221 | "image.fit": true,
222 | "shape.top": 280.0,
223 | "bgcolor.hovered": "#AAAAAA",
224 | "text.italic": false,
225 | "bgcolor.transparency": 0,
226 | "action.right.language": "python",
227 | "text.color": "#FFFFFF",
228 | "text.size": 12,
229 | "text.halign": "center",
230 | "shape.left": 140.0
231 | },
232 | {
233 | "bgcolor.normal": "#888888",
234 | "borderwidth.hovered": 1.25,
235 | "borderwidth.normal": 1.0,
236 | "image.width": 32,
237 | "bordercolor.transparency": 0,
238 | "shape": "round",
239 | "borderwidth.clicked": 2,
240 | "action.right.close": false,
241 | "border": false,
242 | "action.right": false,
243 | "text.valign": "center",
244 | "bordercolor.hovered": "#393939",
245 | "image.path": "",
246 | "action.left": true,
247 | "image.height": 32,
248 | "action.left.command": "",
249 | "text.content": "",
250 | "bordercolor.normal": "#000000",
251 | "action.left.close": false,
252 | "shape.height": 40.0,
253 | "text.bold": false,
254 | "bgcolor.clicked": "#DDDDDD",
255 | "action.left.language": "python",
256 | "shape.width": 40.0,
257 | "action.right.command": "",
258 | "bordercolor.clicked": "#FFFFFF",
259 | "image.fit": true,
260 | "shape.top": 140.0,
261 | "bgcolor.hovered": "#AAAAAA",
262 | "text.italic": false,
263 | "bgcolor.transparency": 0,
264 | "action.right.language": "python",
265 | "text.color": "#FFFFFF",
266 | "text.size": 12,
267 | "text.halign": "center",
268 | "shape.left": 140.0
269 | },
270 | {
271 | "bgcolor.normal": "#888888",
272 | "borderwidth.hovered": 1.25,
273 | "borderwidth.normal": 1.0,
274 | "image.width": 32,
275 | "bordercolor.transparency": 0,
276 | "shape": "round",
277 | "borderwidth.clicked": 2,
278 | "action.right.close": false,
279 | "border": false,
280 | "action.right": false,
281 | "text.valign": "center",
282 | "bordercolor.hovered": "#393939",
283 | "image.path": "",
284 | "action.left": true,
285 | "image.height": 32,
286 | "action.left.command": "",
287 | "text.content": "",
288 | "bordercolor.normal": "#000000",
289 | "action.left.close": false,
290 | "shape.height": 40.0,
291 | "text.bold": false,
292 | "bgcolor.clicked": "#DDDDDD",
293 | "action.left.language": "python",
294 | "shape.width": 40.0,
295 | "action.right.command": "",
296 | "bordercolor.clicked": "#FFFFFF",
297 | "image.fit": true,
298 | "shape.top": 140.0,
299 | "bgcolor.hovered": "#AAAAAA",
300 | "text.italic": false,
301 | "bgcolor.transparency": 0,
302 | "action.right.language": "python",
303 | "text.color": "#FFFFFF",
304 | "text.size": 12,
305 | "text.halign": "center",
306 | "shape.left": 280.0
307 | },
308 | {
309 | "bgcolor.normal": "#888888",
310 | "borderwidth.hovered": 1.25,
311 | "borderwidth.normal": 1.0,
312 | "image.width": 32,
313 | "bordercolor.transparency": 0,
314 | "shape": "round",
315 | "borderwidth.clicked": 2,
316 | "action.right.close": false,
317 | "border": false,
318 | "action.right": false,
319 | "text.valign": "center",
320 | "bordercolor.hovered": "#393939",
321 | "image.path": "",
322 | "action.left": true,
323 | "image.height": 32,
324 | "action.left.command": "",
325 | "text.content": "",
326 | "bordercolor.normal": "#000000",
327 | "action.left.close": false,
328 | "shape.height": 40.0,
329 | "text.bold": false,
330 | "bgcolor.clicked": "#DDDDDD",
331 | "action.left.language": "python",
332 | "shape.width": 40.0,
333 | "action.right.command": "",
334 | "bordercolor.clicked": "#FFFFFF",
335 | "image.fit": true,
336 | "shape.top": 280.0,
337 | "bgcolor.hovered": "#AAAAAA",
338 | "text.italic": false,
339 | "bgcolor.transparency": 0,
340 | "action.right.language": "python",
341 | "text.color": "#FFFFFF",
342 | "text.size": 12,
343 | "text.halign": "center",
344 | "shape.left": 280.0
345 | },
346 | {
347 | "bgcolor.normal": "#888888",
348 | "borderwidth.hovered": 1.25,
349 | "borderwidth.normal": 1.0,
350 | "image.width": 32,
351 | "bordercolor.transparency": 0,
352 | "shape": "round",
353 | "borderwidth.clicked": 2,
354 | "action.right.close": false,
355 | "border": false,
356 | "action.right": false,
357 | "text.valign": "center",
358 | "bordercolor.hovered": "#393939",
359 | "image.path": "",
360 | "action.left": true,
361 | "image.height": 32,
362 | "action.left.command": "",
363 | "text.content": "",
364 | "bordercolor.normal": "#000000",
365 | "action.left.close": false,
366 | "shape.height": 40.0,
367 | "text.bold": false,
368 | "bgcolor.clicked": "#DDDDDD",
369 | "action.left.language": "python",
370 | "shape.width": 40.0,
371 | "action.right.command": "",
372 | "bordercolor.clicked": "#FFFFFF",
373 | "image.fit": true,
374 | "shape.top": 90.0,
375 | "bgcolor.hovered": "#AAAAAA",
376 | "text.italic": false,
377 | "bgcolor.transparency": 0,
378 | "action.right.language": "python",
379 | "text.color": "#FFFFFF",
380 | "text.size": 12,
381 | "text.halign": "center",
382 | "shape.left": 160.0
383 | },
384 | {
385 | "bgcolor.normal": "#888888",
386 | "borderwidth.hovered": 1.25,
387 | "borderwidth.normal": 1.0,
388 | "image.width": 32,
389 | "bordercolor.transparency": 0,
390 | "shape": "round",
391 | "borderwidth.clicked": 2,
392 | "action.right.close": false,
393 | "border": false,
394 | "action.right": false,
395 | "text.valign": "center",
396 | "bordercolor.hovered": "#393939",
397 | "image.path": "",
398 | "action.left": true,
399 | "image.height": 32,
400 | "action.left.command": "",
401 | "text.content": "",
402 | "bordercolor.normal": "#000000",
403 | "action.left.close": false,
404 | "shape.height": 40.0,
405 | "text.bold": false,
406 | "bgcolor.clicked": "#DDDDDD",
407 | "action.left.language": "python",
408 | "shape.width": 40.0,
409 | "action.right.command": "",
410 | "bordercolor.clicked": "#FFFFFF",
411 | "image.fit": true,
412 | "shape.top": 90.0,
413 | "bgcolor.hovered": "#AAAAAA",
414 | "text.italic": false,
415 | "bgcolor.transparency": 0,
416 | "action.right.language": "python",
417 | "text.color": "#FFFFFF",
418 | "text.size": 12,
419 | "text.halign": "center",
420 | "shape.left": 260.0
421 | },
422 | {
423 | "bgcolor.normal": "#888888",
424 | "borderwidth.hovered": 1.25,
425 | "borderwidth.normal": 1.0,
426 | "image.width": 32,
427 | "bordercolor.transparency": 0,
428 | "shape": "round",
429 | "borderwidth.clicked": 2,
430 | "action.right.close": false,
431 | "border": false,
432 | "action.right": false,
433 | "text.valign": "center",
434 | "bordercolor.hovered": "#393939",
435 | "image.path": "",
436 | "action.left": true,
437 | "image.height": 32,
438 | "action.left.command": "",
439 | "text.content": "",
440 | "bordercolor.normal": "#000000",
441 | "action.left.close": false,
442 | "shape.height": 40.0,
443 | "text.bold": false,
444 | "bgcolor.clicked": "#DDDDDD",
445 | "action.left.language": "python",
446 | "shape.width": 40.0,
447 | "action.right.command": "",
448 | "bordercolor.clicked": "#FFFFFF",
449 | "image.fit": true,
450 | "shape.top": 330.0,
451 | "bgcolor.hovered": "#AAAAAA",
452 | "text.italic": false,
453 | "bgcolor.transparency": 0,
454 | "action.right.language": "python",
455 | "text.color": "#FFFFFF",
456 | "text.size": 12,
457 | "text.halign": "center",
458 | "shape.left": 160.0
459 | },
460 | {
461 | "bgcolor.normal": "#888888",
462 | "borderwidth.hovered": 1.25,
463 | "borderwidth.normal": 1.0,
464 | "image.width": 32,
465 | "bordercolor.transparency": 0,
466 | "shape": "round",
467 | "borderwidth.clicked": 2,
468 | "action.right.close": false,
469 | "border": false,
470 | "action.right": false,
471 | "text.valign": "center",
472 | "bordercolor.hovered": "#393939",
473 | "image.path": "",
474 | "action.left": true,
475 | "image.height": 32,
476 | "action.left.command": "",
477 | "text.content": "",
478 | "bordercolor.normal": "#000000",
479 | "action.left.close": false,
480 | "shape.height": 40.0,
481 | "text.bold": false,
482 | "bgcolor.clicked": "#DDDDDD",
483 | "action.left.language": "python",
484 | "shape.width": 40.0,
485 | "action.right.command": "",
486 | "bordercolor.clicked": "#FFFFFF",
487 | "image.fit": true,
488 | "shape.top": 330.0,
489 | "bgcolor.hovered": "#AAAAAA",
490 | "text.italic": false,
491 | "bgcolor.transparency": 0,
492 | "action.right.language": "python",
493 | "text.color": "#FFFFFF",
494 | "text.size": 12,
495 | "text.halign": "center",
496 | "shape.left": 260.0
497 | },
498 | {
499 | "bgcolor.normal": "#888888",
500 | "borderwidth.hovered": 1.25,
501 | "borderwidth.normal": 1.0,
502 | "image.width": 32,
503 | "bordercolor.transparency": 0,
504 | "shape": "round",
505 | "borderwidth.clicked": 2,
506 | "action.right.close": false,
507 | "border": false,
508 | "action.right": false,
509 | "text.valign": "center",
510 | "bordercolor.hovered": "#393939",
511 | "image.path": "",
512 | "action.left": true,
513 | "image.height": 32,
514 | "action.left.command": "",
515 | "text.content": "",
516 | "bordercolor.normal": "#000000",
517 | "action.left.close": false,
518 | "shape.height": 40.0,
519 | "text.bold": false,
520 | "bgcolor.clicked": "#DDDDDD",
521 | "action.left.language": "python",
522 | "shape.width": 40.0,
523 | "action.right.command": "",
524 | "bordercolor.clicked": "#FFFFFF",
525 | "image.fit": true,
526 | "shape.top": 160.0,
527 | "bgcolor.hovered": "#AAAAAA",
528 | "text.italic": false,
529 | "bgcolor.transparency": 0,
530 | "action.right.language": "python",
531 | "text.color": "#FFFFFF",
532 | "text.size": 12,
533 | "text.halign": "center",
534 | "shape.left": 90.0
535 | },
536 | {
537 | "bgcolor.normal": "#888888",
538 | "borderwidth.hovered": 1.25,
539 | "borderwidth.normal": 1.0,
540 | "image.width": 32,
541 | "bordercolor.transparency": 0,
542 | "shape": "round",
543 | "borderwidth.clicked": 2,
544 | "action.right.close": false,
545 | "border": false,
546 | "action.right": false,
547 | "text.valign": "center",
548 | "bordercolor.hovered": "#393939",
549 | "image.path": "",
550 | "action.left": true,
551 | "image.height": 32,
552 | "action.left.command": "",
553 | "text.content": "",
554 | "bordercolor.normal": "#000000",
555 | "action.left.close": false,
556 | "shape.height": 40.0,
557 | "text.bold": false,
558 | "bgcolor.clicked": "#DDDDDD",
559 | "action.left.language": "python",
560 | "shape.width": 40.0,
561 | "action.right.command": "",
562 | "bordercolor.clicked": "#FFFFFF",
563 | "image.fit": true,
564 | "shape.top": 260.0,
565 | "bgcolor.hovered": "#AAAAAA",
566 | "text.italic": false,
567 | "bgcolor.transparency": 0,
568 | "action.right.language": "python",
569 | "text.color": "#FFFFFF",
570 | "text.size": 12,
571 | "text.halign": "center",
572 | "shape.left": 90.0
573 | },
574 | {
575 | "bgcolor.normal": "#888888",
576 | "borderwidth.hovered": 1.25,
577 | "borderwidth.normal": 1.0,
578 | "image.width": 32,
579 | "bordercolor.transparency": 0,
580 | "shape": "round",
581 | "borderwidth.clicked": 2,
582 | "action.right.close": false,
583 | "border": false,
584 | "action.right": false,
585 | "text.valign": "center",
586 | "bordercolor.hovered": "#393939",
587 | "image.path": "",
588 | "action.left": true,
589 | "image.height": 32,
590 | "action.left.command": "",
591 | "text.content": "",
592 | "bordercolor.normal": "#000000",
593 | "action.left.close": false,
594 | "shape.height": 40.0,
595 | "text.bold": false,
596 | "bgcolor.clicked": "#DDDDDD",
597 | "action.left.language": "python",
598 | "shape.width": 40.0,
599 | "action.right.command": "",
600 | "bordercolor.clicked": "#FFFFFF",
601 | "image.fit": true,
602 | "shape.top": 160.0,
603 | "bgcolor.hovered": "#AAAAAA",
604 | "text.italic": false,
605 | "bgcolor.transparency": 0,
606 | "action.right.language": "python",
607 | "text.color": "#FFFFFF",
608 | "text.size": 12,
609 | "text.halign": "center",
610 | "shape.left": 330.0
611 | },
612 | {
613 | "bgcolor.normal": "#888888",
614 | "borderwidth.hovered": 1.25,
615 | "borderwidth.normal": 1.0,
616 | "image.width": 32,
617 | "bordercolor.transparency": 0,
618 | "shape": "round",
619 | "borderwidth.clicked": 2,
620 | "action.right.close": false,
621 | "border": false,
622 | "action.right": false,
623 | "text.valign": "center",
624 | "bordercolor.hovered": "#393939",
625 | "image.path": "",
626 | "action.left": true,
627 | "image.height": 32,
628 | "action.left.command": "",
629 | "text.content": "",
630 | "bordercolor.normal": "#000000",
631 | "action.left.close": false,
632 | "shape.height": 40.0,
633 | "text.bold": false,
634 | "bgcolor.clicked": "#DDDDDD",
635 | "action.left.language": "python",
636 | "shape.width": 40.0,
637 | "action.right.command": "",
638 | "bordercolor.clicked": "#FFFFFF",
639 | "image.fit": true,
640 | "shape.top": 260.0,
641 | "bgcolor.hovered": "#AAAAAA",
642 | "text.italic": false,
643 | "bgcolor.transparency": 0,
644 | "action.right.language": "python",
645 | "text.color": "#FFFFFF",
646 | "text.size": 12,
647 | "text.halign": "center",
648 | "shape.left": 330.0
649 | }
650 | ],
651 | "general": {
652 | "control": true,
653 | "name": "Circles",
654 | "height": 460,
655 | "width": 460,
656 | "centerx": 230,
657 | "centery": 230,
658 | "touch": "e",
659 | "alt": true,
660 | "aiming": false,
661 | "triggering": "on click",
662 | "submenu": false,
663 | "leaveclose": false
664 | }
665 | }
--------------------------------------------------------------------------------