diff options
author | Campbell Barton <ideasman42@gmail.com> | 2019-12-06 19:45:50 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2019-12-06 22:03:00 +0300 |
commit | 6ffcddc10afa07b073ce01c25884e23fc2b661df (patch) | |
tree | 86e7ddb017474083890bf16fac4876175a923e57 /release/scripts/startup/bl_ui/space_toolsystem_common.py | |
parent | 7465078e637b729e8b36a4f4b73a50a347052df4 (diff) |
Tool System: experimental fallback tool support
Implement T66304 as an experimental option,
available under the preferences "Experimental" section.
- When enabled most tools in the 3D view have a gizmo.
- Dragging outside the gizmo uses the 'fallback' tool.
- The fallback tool can be changed or disabled in the tool options
or from a pie menu (Alt-W).
Diffstat (limited to 'release/scripts/startup/bl_ui/space_toolsystem_common.py')
-rw-r--r-- | release/scripts/startup/bl_ui/space_toolsystem_common.py | 364 |
1 files changed, 301 insertions, 63 deletions
diff --git a/release/scripts/startup/bl_ui/space_toolsystem_common.py b/release/scripts/startup/bl_ui/space_toolsystem_common.py index 7c7825403a9..a5501301015 100644 --- a/release/scripts/startup/bl_ui/space_toolsystem_common.py +++ b/release/scripts/startup/bl_ui/space_toolsystem_common.py @@ -162,7 +162,7 @@ class ToolActivePanelHelper: layout.use_property_decorate = False ToolSelectPanelHelper.draw_active_tool_header( context, - layout, + layout.column(), show_tool_name=True, tool_key=ToolSelectPanelHelper._tool_key_from_context(context, space_type=self.bl_space_type), ) @@ -262,76 +262,100 @@ class ToolSelectPanelHelper: else: yield item, -1 - @staticmethod - def _tool_get_active(context, space_type, mode, with_icon=False): + @classmethod + def _tool_get_active(cls, context, space_type, mode, with_icon=False): """ Return the active Python tool definition and icon name. """ - cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) - if cls is not None: - tool_active = ToolSelectPanelHelper._tool_active_from_context(context, space_type, mode) - tool_active_id = getattr(tool_active, "idname", None) - for item in ToolSelectPanelHelper._tools_flatten(cls.tools_from_context(context, mode)): - if item is not None: - if item.idname == tool_active_id: - if with_icon: - icon_value = ToolSelectPanelHelper._icon_value_from_icon_handle(item.icon) - else: - icon_value = 0 - return (item, tool_active, icon_value) + tool_active = ToolSelectPanelHelper._tool_active_from_context(context, space_type, mode) + tool_active_id = getattr(tool_active, "idname", None) + for item in ToolSelectPanelHelper._tools_flatten(cls.tools_from_context(context, mode)): + if item is not None: + if item.idname == tool_active_id: + if with_icon: + icon_value = ToolSelectPanelHelper._icon_value_from_icon_handle(item.icon) + else: + icon_value = 0 + return (item, tool_active, icon_value) return None, None, 0 - @staticmethod - def _tool_get_by_id(context, space_type, idname): + @classmethod + def _tool_get_by_id(cls, context, space_type, idname): """ Return the active Python tool definition and index (if in sub-group, else -1). """ - cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) - if cls is not None: - for item, index in ToolSelectPanelHelper._tools_flatten_with_tool_index(cls.tools_from_context(context)): - if item is not None: + for item, index in ToolSelectPanelHelper._tools_flatten_with_tool_index(cls.tools_from_context(context)): + if item is not None: + if item.idname == idname: + return (item, index) + return None, -1 + + @classmethod + def _tool_get_by_id_active(cls, context, space_type, idname): + """ + Return the active Python tool definition and index (if in sub-group, else -1). + """ + for item in cls.tools_from_context(context): + if item is not None: + if type(item) is tuple: + if item[0].idname == idname: + index = cls._tool_group_active.get(item[0].idname, 0) + return (item[index], index) + else: if item.idname == idname: - return (cls, item, index) - return None, None, -1 + return (item, -1) + return None, -1 - @staticmethod - def _tool_get_by_flat_index(context, space_type, tool_index): + @classmethod + def _tool_get_by_id_active_with_group(cls, context, space_type, idname): + """ + Return the active Python tool definition and index (if in sub-group, else -1). + """ + for item in cls.tools_from_context(context): + if item is not None: + if type(item) is tuple: + if item[0].idname == idname: + index = cls._tool_group_active.get(item[0].idname, 0) + return (item[index], index, item) + else: + if item.idname == idname: + return (item, -1, None) + return None, -1, None + + @classmethod + def _tool_get_by_flat_index(cls, context, space_type, tool_index): """ Return the active Python tool definition and index (if in sub-group, else -1). Return the index of the expanded list. """ - cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) - if cls is not None: - i = 0 - for item, index in ToolSelectPanelHelper._tools_flatten_with_tool_index(cls.tools_from_context(context)): - if item is not None: - if i == tool_index: - return (cls, item, index) - i += 1 - return None, None, -1 + i = 0 + for item, index in ToolSelectPanelHelper._tools_flatten_with_tool_index(cls.tools_from_context(context)): + if item is not None: + if i == tool_index: + return (item, index) + i += 1 + return None, -1 - @staticmethod - def _tool_get_by_index(context, space_type, tool_index): + @classmethod + def _tool_get_active_by_index(cls, context, space_type, tool_index): """ Return the active Python tool definition and index (if in sub-group, else -1). Return the index of the list without expanding. """ - cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) - if cls is not None: - i = 0 - for item in cls.tools_from_context(context): - if item is not None: - if i == tool_index: - if type(item) is tuple: - index = cls._tool_group_active.get(item[0].idname, 0) - item = item[index] - else: - index = -1 - return (cls, item, index) - i += 1 - return None, None, -1 + i = 0 + for item in cls.tools_from_context(context): + if item is not None: + if i == tool_index: + if type(item) is tuple: + index = cls._tool_group_active.get(item[0].idname, 0) + item = item[index] + else: + index = -1 + return (item, index) + i += 1 + return None, -1 @staticmethod def _tool_active_from_context(context, space_type, mode=None, create=False): @@ -633,6 +657,24 @@ class ToolSelectPanelHelper: space_type = context.space_data.type return ToolSelectPanelHelper._tool_active_from_context(context, space_type) + + @staticmethod + def draw_active_tool_fallback( + context, layout, tool, + *, + is_horizontal_layout=False, + ): + tool_fallback = tool.tool_fallback + space_type = tool.space_type + cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) + item_fallback, _index = cls._tool_get_by_id(context, space_type, tool_fallback) + if item_fallback is not None: + draw_settings = item_fallback.draw_settings + if draw_settings is not None: + if not is_horizontal_layout: + layout.separator() + draw_settings(context, layout, tool) + @staticmethod def draw_active_tool_header( context, layout, @@ -640,6 +682,7 @@ class ToolSelectPanelHelper: show_tool_name=False, tool_key=None, ): + is_horizontal_layout = layout.direction != 'VERTICAL' if tool_key is None: space_type, mode = ToolSelectPanelHelper._tool_key_from_context(context) else: @@ -647,7 +690,9 @@ class ToolSelectPanelHelper: if space_type is None: return None - item, tool, icon_value = ToolSelectPanelHelper._tool_get_active(context, space_type, mode, with_icon=True) + + cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) + item, tool, icon_value = cls._tool_get_active(context, space_type, mode, with_icon=True) if item is None: return None # Note: we could show 'item.text' here but it makes the layout jitter when switching tools. @@ -658,8 +703,128 @@ class ToolSelectPanelHelper: draw_settings = item.draw_settings if draw_settings is not None: draw_settings(context, layout, tool) + + if context.preferences.experimental.use_tool_fallback: + tool_fallback = tool.tool_fallback + else: + tool_fallback = None + + if tool_fallback and tool_fallback != item.idname: + tool_settings = context.tool_settings + + # Show popover which looks like an enum but isn't one. + if tool_settings.workspace_tool_type == 'FALLBACK': + tool_fallback_id = cls.tool_fallback_id + item, _select_index = cls._tool_get_by_id_active(context, space_type, tool_fallback_id) + label = item.label + else: + label = "Active Tool" + + split = layout.split(factor=0.5) + row = split.row() + row.alignment = 'RIGHT' + row.label(text="Drag") + row = split.row() + row.context_pointer_set("tool", tool) + row.popover(panel="TOPBAR_PT_tool_fallback", text=label) + return tool + # Show a list of tools in the popover. + @staticmethod + def draw_fallback_tool_items(layout, context): + + space_type = context.space_data.type + if space_type == 'PROPERTIES': + space_type = 'VIEW_3D' + + cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) + tool_fallback_id = cls.tool_fallback_id + + _item, _select_index, item_group = cls._tool_get_by_id_active_with_group(context, space_type, tool_fallback_id) + + if item_group is None: + # Could print comprehensive message - listing available items. + raise Exception("Fallback tool doesn't exist") + + col = layout.column(align=True) + tool_settings = context.tool_settings + col.prop_enum( + tool_settings, + "workspace_tool_type", + value='DEFAULT', + text="Active Tool", + ) + is_active_tool = (tool_settings.workspace_tool_type == 'DEFAULT') + + col = layout.column(align=True) + if is_active_tool: + index_current = -1 + else: + index_current = cls._tool_group_active.get(item_group[0].idname, 0) + for i, sub_item in enumerate(item_group): + is_active = (i == index_current) + + props = col.operator( + "wm.tool_set_by_id", + text=sub_item.label, + depress=is_active, + ) + props.name = sub_item.idname + props.as_fallback = True + props.space_type = space_type + + @staticmethod + def draw_fallback_tool_items_for_pie_menu(layout, context): + space_type = context.space_data.type + if space_type == 'PROPERTIES': + space_type = 'VIEW_3D' + + cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) + tool_fallback_id = cls.tool_fallback_id + + _item, _select_index, item_group = cls._tool_get_by_id_active_with_group(context, space_type, tool_fallback_id) + + if item_group is None: + # Could print comprehensive message - listing available items. + raise Exception("Fallback tool doesn't exist") + + # Allow changing the active tool, + # even though this isn't the purpose of the pie menu + # it's confusing from a user perspective if we don't allow it. + is_fallback_group_active = getattr( + ToolSelectPanelHelper._tool_active_from_context(context, space_type), + "idname", None, + ) in (item.idname for item in item_group) + + pie = layout.menu_pie() + tool_settings = context.tool_settings + pie.prop_enum( + tool_settings, + "workspace_tool_type", + value='DEFAULT', + text="Active Tool", + icon='TOOL_SETTINGS', # Could use a less generic icon. + ) + is_active_tool = (tool_settings.workspace_tool_type == 'DEFAULT') + + if is_active_tool: + index_current = -1 + else: + index_current = cls._tool_group_active.get(item_group[0].idname, 0) + for i, sub_item in enumerate(item_group): + is_active = (i == index_current) + props = pie.operator( + "wm.tool_set_by_id", + text=sub_item.label, + depress=is_active, + icon_value=ToolSelectPanelHelper._icon_value_from_icon_handle(sub_item.icon), + ) + props.name = sub_item.idname + props.space_type = space_type + if not is_fallback_group_active: + props.as_fallback = True + # The purpose of this menu is to be a generic popup to select between tools # in cases when a single tool allows to select alternative tools. @@ -701,7 +866,43 @@ class WM_MT_toolsystem_submenu(Menu): ).name = item.idname -def _activate_by_item(context, space_type, item, index): +def _activate_by_item(context, space_type, item, index, *, as_fallback=False): + cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) + tool_fallback_id = cls.tool_fallback_id + + if as_fallback: + # To avoid complicating logic too much, isolate all fallback logic to this block. + # This will set the tool again, using the item for the fallback instead of the primary tool. + # + # If this ends up needing to be more complicated, + # it would be better to split it into a separate function. + + _item, _select_index, item_group = cls._tool_get_by_id_active_with_group(context, space_type, tool_fallback_id) + + if item_group is None: + # Could print comprehensive message - listing available items. + raise Exception("Fallback tool doesn't exist") + index_new = -1 + for i, sub_item in enumerate(item_group): + if sub_item.idname == item.idname: + index_new = i + break + if index_new == -1: + raise Exception("Fallback tool not found in group") + + cls._tool_group_active[tool_fallback_id] = index_new + + # Done, now get the current tool to replace the item & index. + tool_active = ToolSelectPanelHelper._tool_active_from_context(context, space_type) + item, index = cls._tool_get_by_id(context, space_type, getattr(tool_active, "idname", None)) + + # Find fallback keymap. + item_fallback = None + _item, select_index = cls._tool_get_by_id(context, space_type, tool_fallback_id) + if select_index != -1: + item_fallback, _index = cls._tool_get_active_by_index(context, space_type, select_index) + # End calculating fallback. + tool = ToolSelectPanelHelper._tool_active_from_context(context, space_type, create=True) tool.setup( idname=item.idname, @@ -711,6 +912,9 @@ def _activate_by_item(context, space_type, item, index): data_block=item.data_block or "", operator=item.operator or "", index=index, + + idname_fallback=item_fallback.idname if item_fallback else "", + keymap_fallback=(item_fallback.keymap[0] or "") if item_fallback else "", ) WindowManager = bpy.types.WindowManager @@ -729,18 +933,22 @@ def _activate_by_item(context, space_type, item, index): _activate_by_item._cursor_draw_handle = {} -def activate_by_id(context, space_type, text): - _cls, item, index = ToolSelectPanelHelper._tool_get_by_id(context, space_type, text) +def activate_by_id(context, space_type, idname, *, as_fallback=False): + cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) + if cls is None: + return False + item, index = cls._tool_get_by_id(context, space_type, idname) if item is None: return False - _activate_by_item(context, space_type, item, index) + _activate_by_item(context, space_type, item, index, as_fallback=as_fallback) return True -def activate_by_id_or_cycle(context, space_type, idname, offset=1): +def activate_by_id_or_cycle(context, space_type, idname, *, offset=1, as_fallback=False): # Only cycle when the active tool is activated again. - cls, item, _index = ToolSelectPanelHelper._tool_get_by_id(context, space_type, idname) + cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) + item, _index = cls._tool_get_by_id(context, space_type, idname) if item is None: return False @@ -774,7 +982,8 @@ def activate_by_id_or_cycle(context, space_type, idname, offset=1): def description_from_id(context, space_type, idname, *, use_operator=True): # Used directly for tooltips. - _cls, item, _index = ToolSelectPanelHelper._tool_get_by_id(context, space_type, idname) + cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) + item, _index = cls._tool_get_by_id(context, space_type, idname) if item is None: return False @@ -806,23 +1015,52 @@ def description_from_id(context, space_type, idname, *, use_operator=True): def item_from_id(context, space_type, idname): # Used directly for tooltips. - _cls, item, _index = ToolSelectPanelHelper._tool_get_by_id(context, space_type, idname) + cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) + if cls is None: + return None + item, _index = cls._tool_get_by_id(context, space_type, idname) + return item + + +def item_from_id_active(context, space_type, idname): + # Used directly for tooltips. + cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) + if cls is None: + return None + item, _index = cls._tool_get_by_id_active(context, space_type, idname) + return item + + +def item_from_id_active_with_group(context, space_type, idname): + cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) + if cls is None: + return None + cls, item, _index = cls._tool_get_by_id_active_with_group(context, space_type, idname) return item def item_from_flat_index(context, space_type, index): - _cls, item, _index = ToolSelectPanelHelper._tool_get_by_flat_index(context, space_type, index) + cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) + if cls is None: + return None + item, _index = cls._tool_get_by_flat_index(context, space_type, index) return item -def item_from_index(context, space_type, index): - _cls, item, _index = ToolSelectPanelHelper._tool_get_by_index(context, space_type, index) +def item_from_index_active(context, space_type, index): + cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) + if cls is None: + return None + item, _index = cls._tool_get_active_by_index(context, space_type, index) return item def keymap_from_id(context, space_type, idname): # Used directly for tooltips. - _cls, item, _index = ToolSelectPanelHelper._tool_get_by_id(context, space_type, idname) + cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) + if cls is None: + return None + item, _index = cls._tool_get_by_id(context, space_type, idname) if item is None: return False |