diff options
Diffstat (limited to 'rigify/operators/generic_ui_list.py')
-rw-r--r-- | rigify/operators/generic_ui_list.py | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/rigify/operators/generic_ui_list.py b/rigify/operators/generic_ui_list.py new file mode 100644 index 00000000..46f093b3 --- /dev/null +++ b/rigify/operators/generic_ui_list.py @@ -0,0 +1,187 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +from bpy.types import Operator, UILayout, Context +from bpy.props import EnumProperty, StringProperty + + +def get_context_attr(context: Context, data_path): + return context.path_resolve(data_path) + + +def set_context_attr(context: Context, data_path, value): + items = data_path.split('.') + setattr(context.path_resolve('.'.join(items[:-1])), items[-1], value) + + +class GenericUIListOperator(Operator): + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + + list_context_path: StringProperty() + active_idx_context_path: StringProperty() + + def get_list(self, context): + return get_context_attr(context, self.list_context_path) + + def get_active_index(self, context): + return get_context_attr(context, self.active_idx_context_path) + + def set_active_index(self, context, index): + set_context_attr(context, self.active_idx_context_path, index) + + +class UILIST_OT_entry_remove(GenericUIListOperator): + """Remove the selected entry from the list""" + + bl_idname = "ui.rigify_list_entry_remove" + bl_label = "Remove Selected Entry" + + def execute(self, context): + my_list = self.get_list(context) + active_index = self.get_active_index(context) + + my_list.remove(active_index) + + to_index = min(active_index, len(my_list) - 1) + + self.set_active_index(context, to_index) + + return {'FINISHED'} + + +class UILIST_OT_entry_add(GenericUIListOperator): + """Add an entry to the list""" + + bl_idname = "ui.rigify_list_entry_add" + bl_label = "Add Entry" + + def execute(self, context): + my_list = self.get_list(context) + active_index = self.get_active_index(context) + + to_index = min(len(my_list), active_index + 1) + + my_list.add() + my_list.move(len(my_list) - 1, to_index) + self.set_active_index(context, to_index) + + return {'FINISHED'} + + +class UILIST_OT_entry_move(GenericUIListOperator): + """Move an entry in the list up or down""" + + bl_idname = "ui.rigify_list_entry_move" + bl_label = "Move Entry" + + direction: EnumProperty( + name="Direction", + items=[('UP', 'UP', 'UP'), + ('DOWN', 'DOWN', 'DOWN')], + default='UP' + ) + + def execute(self, context): + my_list = self.get_list(context) + active_index = self.get_active_index(context) + + to_index = active_index + (1 if self.direction == 'DOWN' else -1) + + if to_index > len(my_list) - 1: + to_index = 0 + elif to_index < 0: + to_index = len(my_list) - 1 + + my_list.move(active_index, to_index) + self.set_active_index(context, to_index) + + return {'FINISHED'} + + +def draw_ui_list( + layout, context, class_name="UI_UL_list", *, + list_context_path: str, # Eg. "object.vertex_groups". + active_idx_context_path: str, # Eg., "object.vertex_groups.active_index". + insertion_operators=True, + move_operators=True, + menu_class_name="", + **kwargs) -> UILayout: + """ + This is intended as a replacement for row.template_list(). + By changing the requirements of the parameters, we can provide the Add, Remove and Move Up/Down + operators without the person implementing the UIList having to worry about that stuff. + """ + row = layout.row() + + list_owner = get_context_attr(context, ".".join(list_context_path.split(".")[:-1])) + list_prop_name = list_context_path.split(".")[-1] + idx_owner = get_context_attr(context, ".".join(active_idx_context_path.split(".")[:-1])) + idx_prop_name = active_idx_context_path.split(".")[-1] + + my_list = get_context_attr(context, list_context_path) + + row.template_list( + class_name, + list_context_path if class_name == 'UI_UL_list' else "", + list_owner, list_prop_name, + idx_owner, idx_prop_name, + rows=4 if len(my_list) > 0 else 1, + **kwargs + ) + + col = row.column() + + if insertion_operators: + add_op = col.operator(UILIST_OT_entry_add.bl_idname, text="", icon='ADD') + add_op.list_context_path = list_context_path + add_op.active_idx_context_path = active_idx_context_path + + row = col.row() + row.enabled = len(my_list) > 0 + remove_op = row.operator(UILIST_OT_entry_remove.bl_idname, text="", icon='REMOVE') + remove_op.list_context_path = list_context_path + remove_op.active_idx_context_path = active_idx_context_path + + col.separator() + + if menu_class_name != '': + # noinspection SpellCheckingInspection + col.menu(menu_class_name, icon='DOWNARROW_HLT', text="") + col.separator() + + if move_operators and len(my_list) > 0: + col = col.column() + col.enabled = len(my_list) > 1 + move_up_op = col.operator(UILIST_OT_entry_move.bl_idname, text="", icon='TRIA_UP') + move_up_op.direction = 'UP' + move_up_op.list_context_path = list_context_path + move_up_op.active_idx_context_path = active_idx_context_path + + move_down_op = col.operator(UILIST_OT_entry_move.bl_idname, text="", icon='TRIA_DOWN') + move_down_op.direction = 'DOWN' + move_down_op.list_context_path = list_context_path + move_down_op.active_idx_context_path = active_idx_context_path + + # Return the right-side column. + return col + + +# ============================================= +# Registration + +classes = ( + UILIST_OT_entry_remove, + UILIST_OT_entry_add, + UILIST_OT_entry_move, +) + + +def register(): + from bpy.utils import register_class + for cls in classes: + register_class(cls) + + +def unregister(): + from bpy.utils import unregister_class + for cls in classes: + unregister_class(cls) |