Tool System: experimental fallback tool support
authorCampbell Barton <ideasman42@gmail.com>
Fri, 6 Dec 2019 16:45:50 +0000 (03:45 +1100)
committerCampbell Barton <ideasman42@gmail.com>
Fri, 6 Dec 2019 19:03:00 +0000 (06:03 +1100)
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).

22 files changed:
release/scripts/presets/keyconfig/keymap_data/blender_default.py
release/scripts/startup/bl_operators/wm.py
release/scripts/startup/bl_ui/space_toolsystem_common.py
release/scripts/startup/bl_ui/space_toolsystem_toolbar.py
release/scripts/startup/bl_ui/space_topbar.py
release/scripts/startup/bl_ui/space_userpref.py
source/blender/editors/space_view3d/CMakeLists.txt
source/blender/editors/space_view3d/space_view3d.c
source/blender/editors/space_view3d/view3d_gizmo_tool_generic.c [new file with mode: 0644]
source/blender/editors/space_view3d/view3d_intern.h
source/blender/editors/transform/transform_gizmo_3d.c
source/blender/editors/transform/transform_gizmo_extrude_3d.c
source/blender/makesdna/DNA_scene_types.h
source/blender/makesdna/DNA_userdef_types.h
source/blender/makesdna/DNA_workspace_types.h
source/blender/makesrna/intern/rna_scene.c
source/blender/makesrna/intern/rna_userdef.c
source/blender/makesrna/intern/rna_workspace.c
source/blender/makesrna/intern/rna_workspace_api.c
source/blender/windowmanager/gizmo/WM_gizmo_types.h
source/blender/windowmanager/intern/wm_event_system.c
source/blender/windowmanager/intern/wm_toolsystem.c

index 06fac16292d234178c7dc21cda8224a60b5234cf..5f600520c12a14092e60ba4579f54989d8654712 100644 (file)
@@ -412,6 +412,8 @@ def km_window(params):
             ("wm.batch_rename", {"type": 'F2', "value": 'PRESS', "ctrl": True}, None),
             ("wm.search_menu", {"type": 'F3', "value": 'PRESS'}, None),
             op_menu("TOPBAR_MT_file_context_menu", {"type": 'F4', "value": 'PRESS'}),
+            # Pass through when when no tool-system exists or the fallback isn't available.
+            ("wm.toolbar_fallback_pie", {"type": 'W', "value": 'PRESS', "alt": True}, None),
             # Alt as "Leader-Key".
             ("wm.toolbar_prompt", {"type": 'LEFT_ALT', "value": 'CLICK'}, None),
             ("wm.toolbar_prompt", {"type": 'RIGHT_ALT', "value": 'CLICK'}, None),
index 24670b2a37d4977437aa4ef4b110c8c7f6e608c5..1452b6767b64218fe5d63177ceda0683517a9478 100644 (file)
@@ -1645,6 +1645,12 @@ class WM_OT_tool_set_by_id(Operator):
         default=False,
         options={'SKIP_SAVE'},
     )
+    as_fallback: BoolProperty(
+        name="Set Fallback",
+        description="Set the fallback tool instead of the primary tool",
+        default=False,
+        options={'SKIP_SAVE', 'HIDDEN'},
+    )
 
     space_type: rna_space_type_prop
 
@@ -1672,7 +1678,10 @@ class WM_OT_tool_set_by_id(Operator):
             space_type = context.space_data.type
 
         fn = activate_by_id_or_cycle if self.cycle else activate_by_id
-        if fn(context, space_type, self.name):
+        if fn(context, space_type, self.name, as_fallback=self.as_fallback):
+            if self.as_fallback:
+                tool_settings = context.tool_settings
+                tool_settings.workspace_tool_type = 'FALLBACK'
             return {'FINISHED'}
         else:
             self.report({'WARNING'}, f"Tool {self.name!r:s} not found for space {space_type!r:s}.")
@@ -1699,13 +1708,20 @@ class WM_OT_tool_set_by_index(Operator):
         default=True,
     )
 
+    as_fallback: BoolProperty(
+        name="Set Fallback",
+        description="Set the fallback tool instead of the primary",
+        default=False,
+        options={'SKIP_SAVE', 'HIDDEN'},
+    )
+
     space_type: rna_space_type_prop
 
     def execute(self, context):
         from bl_ui.space_toolsystem_common import (
             activate_by_id,
             activate_by_id_or_cycle,
-            item_from_index,
+            item_from_index_active,
             item_from_flat_index,
         )
 
@@ -1714,7 +1730,7 @@ class WM_OT_tool_set_by_index(Operator):
         else:
             space_type = context.space_data.type
 
-        fn = item_from_flat_index if self.expand else item_from_index
+        fn = item_from_flat_index if self.expand else item_from_index_active
         item = fn(context, space_type, self.index)
         if item is None:
             # Don't report, since the number of tools may change.
@@ -1722,7 +1738,10 @@ class WM_OT_tool_set_by_index(Operator):
 
         # Same as: WM_OT_tool_set_by_id
         fn = activate_by_id_or_cycle if self.cycle else activate_by_id
-        if fn(context, space_type, item.idname):
+        if fn(context, space_type, item.idname, as_fallback=self.as_fallback):
+            if self.as_fallback:
+                tool_settings = context.tool_settings
+                tool_settings.workspace_tool_type = 'FALLBACK'
             return {'FINISHED'}
         else:
             # Since we already have the tool, this can't happen.
@@ -1776,6 +1795,41 @@ class WM_OT_toolbar(Operator):
         return {'FINISHED'}
 
 
+class WM_OT_toolbar_fallback_pie(Operator):
+    bl_idname = "wm.toolbar_fallback_pie"
+    bl_label = "Fallback Tool Pie Menu"
+
+    @classmethod
+    def poll(cls, context):
+        return context.space_data is not None
+
+    def invoke(self, context, event):
+        if not context.preferences.experimental.use_tool_fallback:
+            return {'PASS_THROUGH'}
+
+        from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
+        space_type = context.space_data.type
+        cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type)
+        if cls is None:
+            return {'PASS_THROUGH'}
+
+        # It's possible we don't have the fallback tool available.
+        # This can happen in the image editor for example when there is no selection
+        # in painting modes.
+        item, _ = cls._tool_get_by_id(context, space_type, cls.tool_fallback_id)
+        if item is None:
+            print("Tool", cls.tool_fallback_id, "not active in", cls)
+            return {'PASS_THROUGH'}
+
+        def draw_cb(self, context):
+            from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
+            ToolSelectPanelHelper.draw_fallback_tool_items_for_pie_menu(self.layout, context)
+
+        wm = context.window_manager
+        wm.popup_menu_pie(draw_func=draw_cb, title="Fallback Tool", event=event)
+        return {'FINISHED'}
+
+
 class WM_OT_toolbar_prompt(Operator):
     """Leader key like functionality for accessing tools"""
     bl_idname = "wm.toolbar_prompt"
@@ -2563,6 +2617,7 @@ classes = (
     WM_OT_tool_set_by_id,
     WM_OT_tool_set_by_index,
     WM_OT_toolbar,
+    WM_OT_toolbar_fallback_pie,
     WM_OT_toolbar_prompt,
     BatchRenameAction,
     WM_OT_batch_rename,
index 7c7825403a944071cf4e6f90809a82a6dcd4b235..a550130101586df24ea6ecaa90034ba8fa8e8efc 100644 (file)
@@ -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
 
index 9a5df62da46faa52943611c55106c70630585971..68d39a3be6489967849dfab658540c113e291b03 100644 (file)
@@ -297,8 +297,15 @@ class _defs_transform:
             if layout.use_property_split:
                 layout.label(text="Gizmos:")
 
-            props = tool.gizmo_group_properties("VIEW3D_GGT_xform_gizmo")
-            layout.prop(props, "drag_action")
+            show_drag = True
+            if context.preferences.experimental.use_tool_fallback:
+                tool_settings = context.tool_settings
+                if tool_settings.workspace_tool_type == 'FALLBACK':
+                    show_drag = False
+
+            if show_drag:
+                props = tool.gizmo_group_properties("VIEW3D_GGT_xform_gizmo")
+                layout.prop(props, "drag_action")
 
             _template_widget.VIEW3D_GGT_xform_gizmo.draw_settings_with_index(context, layout, 1)
 
@@ -472,7 +479,7 @@ class _defs_edit_mesh:
             idname="builtin.rip_region",
             label="Rip Region",
             icon="ops.mesh.rip",
-            widget=None,
+            widget="VIEW3D_GGT_tool_generic_handle_free",
             keymap=(),
             draw_settings=draw_settings,
         )
@@ -512,7 +519,7 @@ class _defs_edit_mesh:
             idname="builtin.edge_slide",
             label="Edge Slide",
             icon="ops.transform.edge_slide",
-            widget=None,
+            widget="VIEW3D_GGT_tool_generic_handle_normal",
             keymap=(),
             draw_settings=draw_settings,
         )
@@ -527,7 +534,7 @@ class _defs_edit_mesh:
             idname="builtin.vertex_slide",
             label="Vertex Slide",
             icon="ops.transform.vert_slide",
-            widget=None,
+            widget="VIEW3D_GGT_tool_generic_handle_free",
             keymap=(),
             draw_settings=draw_settings,
         )
@@ -579,7 +586,7 @@ class _defs_edit_mesh:
             idname="builtin.inset_faces",
             label="Inset Faces",
             icon="ops.mesh.inset",
-            widget=None,
+            widget="VIEW3D_GGT_tool_generic_handle_normal",
             keymap=(),
             draw_settings=draw_settings,
         )
@@ -597,7 +604,7 @@ class _defs_edit_mesh:
             idname="builtin.bevel",
             label="Bevel",
             icon="ops.mesh.bevel",
-            widget=None,
+            widget="VIEW3D_GGT_tool_generic_handle_normal",
             keymap=(),
             draw_settings=draw_settings,
         )
@@ -629,7 +636,7 @@ class _defs_edit_mesh:
             idname="builtin.extrude_along_normals",
             label="Extrude Along Normals",
             icon="ops.mesh.extrude_region_shrink_fatten",
-            widget=None,
+            widget="VIEW3D_GGT_tool_generic_handle_normal",
             operator="mesh.extrude_region_shrink_fatten",
             keymap=(),
             draw_settings=draw_settings,
@@ -641,7 +648,7 @@ class _defs_edit_mesh:
             idname="builtin.extrude_individual",
             label="Extrude Individual",
             icon="ops.mesh.extrude_faces_move",
-            widget=None,
+            widget="VIEW3D_GGT_tool_generic_handle_normal",
             keymap=(),
         )
 
@@ -698,7 +705,7 @@ class _defs_edit_mesh:
             idname="builtin.smooth",
             label="Smooth",
             icon="ops.mesh.vertices_smooth",
-            widget=None,
+            widget="VIEW3D_GGT_tool_generic_handle_normal",
             keymap=(),
             draw_settings=draw_settings,
         )
@@ -714,7 +721,7 @@ class _defs_edit_mesh:
             idname="builtin.randomize",
             label="Randomize",
             icon="ops.transform.vertex_random",
-            widget=None,
+            widget="VIEW3D_GGT_tool_generic_handle_normal",
             keymap=(),
             draw_settings=draw_settings,
         )
@@ -753,7 +760,7 @@ class _defs_edit_mesh:
             idname="builtin.shrink_fatten",
             label="Shrink/Fatten",
             icon="ops.transform.shrink_fatten",
-            widget=None,
+            widget="VIEW3D_GGT_tool_generic_handle_normal",
             keymap=(),
             draw_settings=draw_settings,
         )
@@ -764,7 +771,7 @@ class _defs_edit_mesh:
             idname="builtin.push_pull",
             label="Push/Pull",
             icon="ops.transform.push_pull",
-            widget=None,
+            widget="VIEW3D_GGT_tool_generic_handle_normal",
             keymap=(),
         )
 
@@ -890,7 +897,7 @@ class _defs_edit_curve:
             idname="builtin.randomize",
             label="Randomize",
             icon="ops.curve.vertex_random",
-            widget=None,
+            widget="VIEW3D_GGT_tool_generic_handle_normal",
             keymap=(),
             draw_settings=draw_settings,
         )
@@ -1682,6 +1689,9 @@ class IMAGE_PT_tools_active(ToolSelectPanelHelper, Panel):
     # Satisfy the 'ToolSelectPanelHelper' API.
     keymap_prefix = "Image Editor Tool:"
 
+    # Default group to use as a fallback.
+    tool_fallback_id = "builtin.select"
+
     @classmethod
     def tools_from_context(cls, context, mode=None):
         if mode is None:
@@ -1766,6 +1776,9 @@ class NODE_PT_tools_active(ToolSelectPanelHelper, Panel):
     # Satisfy the 'ToolSelectPanelHelper' API.
     keymap_prefix = "Node Editor Tool:"
 
+    # Default group to use as a fallback.
+    tool_fallback_id = "builtin.select"
+
     @classmethod
     def tools_from_context(cls, context, mode=None):
         if mode is None:
@@ -1822,6 +1835,9 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
     # Satisfy the 'ToolSelectPanelHelper' API.
     keymap_prefix = "3D View Tool:"
 
+    # Default group to use as a fallback.
+    tool_fallback_id = "builtin.select"
+
     @classmethod
     def tools_from_context(cls, context, mode=None):
         if mode is None:
index dcfeb6a210f55755f2dc6f16ec10e7eba5b9036b..09531cb5ef66df5dfbecf369bc25fab6e39cd95f 100644 (file)
@@ -78,6 +78,23 @@ class TOPBAR_HT_upper_bar(Header):
             unlink="scene.view_layer_remove")
 
 
+class TOPBAR_PT_tool_fallback(Panel):
+    bl_space_type = 'VIEW_3D'
+    bl_region_type = 'HEADER'
+    bl_label = "Layers"
+    bl_ui_units_x = 8
+
+    def draw(self, context):
+        from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
+        layout = self.layout
+
+        tool_settings = context.tool_settings
+        ToolSelectPanelHelper.draw_fallback_tool_items(layout, context)
+        if tool_settings.workspace_tool_type == 'FALLBACK':
+            tool = context.tool
+            ToolSelectPanelHelper.draw_active_tool_fallback(context, layout, tool)
+
+
 class TOPBAR_PT_gpencil_layers(Panel):
     bl_space_type = 'VIEW_3D'
     bl_region_type = 'HEADER'
@@ -772,6 +789,7 @@ classes = (
     TOPBAR_MT_render,
     TOPBAR_MT_window,
     TOPBAR_MT_help,
+    TOPBAR_PT_tool_fallback,
     TOPBAR_PT_gpencil_layers,
     TOPBAR_PT_gpencil_primitive,
     TOPBAR_PT_gpencil_fill,
index e9ccbbabdd38000a77e70e1638bec2a8549ad865..130db518cb7b053f74dfa87d8d042260681079cd 100644 (file)
@@ -2188,6 +2188,12 @@ class USERPREF_PT_experimental_all(ExperimentalPanel, Panel):
         # For the other settings create new panels
         # and make sure they are disabled if use_experimental_all is True
 
+        url_prefix = "https://developer.blender.org/"
+
+        row = col.row()
+        row.prop(experimental, "use_tool_fallback")
+
+        row.operator("wm.url_open", text="", icon='URL').url = url_prefix + "T66304"
 
 """
 # Example panel, leave it here so we always have a template to follow even
index 7c75f0ea907d35f1fd1967fe30f19f56a32d4278..2feef9e0c9a7adc6ad2175e85907c147432b2a45 100644 (file)
@@ -61,6 +61,7 @@ set(SRC
   view3d_gizmo_preselect.c
   view3d_gizmo_preselect_type.c
   view3d_gizmo_ruler.c
+  view3d_gizmo_tool_generic.c
   view3d_header.c
   view3d_iterators.c
   view3d_ops.c
index 5c7263d458dc5a20b73f22441f4d59c7e03a7de3..7db1a6123e888becaecc4969618a8a79d0aebc7d 100644 (file)
@@ -626,6 +626,8 @@ static void view3d_widgets(void)
   WM_gizmogrouptype_append(VIEW3D_GGT_xform_extrude);
   WM_gizmogrouptype_append(VIEW3D_GGT_mesh_preselect_elem);
   WM_gizmogrouptype_append(VIEW3D_GGT_mesh_preselect_edgering);
+  WM_gizmogrouptype_append(VIEW3D_GGT_tool_generic_handle_normal);
+  WM_gizmogrouptype_append(VIEW3D_GGT_tool_generic_handle_free);
 
   WM_gizmogrouptype_append(VIEW3D_GGT_ruler);
   WM_gizmotype_append(VIEW3D_GT_ruler_item);
diff --git a/source/blender/editors/space_view3d/view3d_gizmo_tool_generic.c b/source/blender/editors/space_view3d/view3d_gizmo_tool_generic.c
new file mode 100644 (file)
index 0000000..838abe4
--- /dev/null
@@ -0,0 +1,236 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup spview3d
+ */
+
+#include "BLI_math.h"
+#include "BLI_utildefines.h"
+
+#include "BKE_context.h"
+
+#include "ED_screen.h"
+#include "ED_transform.h"
+#include "ED_gizmo_library.h"
+#include "ED_gizmo_utils.h"
+
+#include "UI_resources.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "WM_toolsystem.h"
+
+#include "RNA_access.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+#include "WM_toolsystem.h"
+#include "WM_message.h"
+
+#include "view3d_intern.h" /* own include */
+
+static const char *handle_normal_id;
+static const char *handle_free_id;
+
+/* -------------------------------------------------------------------- */
+/** \name Generic Tool
+ * \{ */
+
+static bool WIDGETGROUP_tool_generic_poll(const bContext *C, wmGizmoGroupType *gzgt)
+{
+  if (!U.experimental.use_tool_fallback) {
+    return false;
+  }
+
+  if (!ED_gizmo_poll_or_unlink_delayed_from_tool(C, gzgt)) {
+    return false;
+  }
+
+  View3D *v3d = CTX_wm_view3d(C);
+  if (v3d->gizmo_flag & (V3D_GIZMO_HIDE | V3D_GIZMO_HIDE_CONTEXT)) {
+    return false;
+  }
+
+  return true;
+}
+
+static wmGizmo *tool_generic_create_gizmo(const bContext *C, wmGizmoGroup *gzgroup)
+{
+  wmGizmo *gz;
+
+  if (gzgroup->type->idname == handle_normal_id) {
+    gz = WM_gizmo_new("GIZMO_GT_button_2d", gzgroup, NULL);
+
+    UI_GetThemeColor3fv(TH_GIZMO_PRIMARY, gz->color);
+    UI_GetThemeColor3fv(TH_GIZMO_HI, gz->color_hi);
+
+    unit_m4(gz->matrix_offset);
+
+    PropertyRNA *prop = RNA_struct_find_property(gz->ptr, "icon");
+    RNA_property_enum_set(gz->ptr, prop, ICON_NONE);
+
+    gz->scale_basis = 0.12f;
+    gz->matrix_offset[3][2] -= 12.0;
+    RNA_enum_set(gz->ptr,
+                 "draw_options",
+                 (ED_GIZMO_BUTTON_SHOW_BACKDROP | ED_GIZMO_BUTTON_SHOW_HELPLINE |
+                  ED_GIZMO_BUTTON_SHOW_OUTLINE));
+  }
+  else {
+    gz = WM_gizmo_new("GIZMO_GT_button_2d", gzgroup, NULL);
+
+    UI_GetThemeColor3fv(TH_GIZMO_PRIMARY, gz->color);
+    UI_GetThemeColor3fv(TH_GIZMO_HI, gz->color_hi);
+
+    unit_m4(gz->matrix_offset);
+    gz->scale_basis = 0.16f * 3;
+
+    PropertyRNA *prop = RNA_struct_find_property(gz->ptr, "icon");
+    RNA_property_enum_set(gz->ptr, prop, ICON_NONE);
+
+    RNA_enum_set(gz->ptr, "draw_options", ED_GIZMO_BUTTON_SHOW_BACKDROP);
+
+    /* Make the center low alpha. */
+    WM_gizmo_set_line_width(gz, 2.0f);
+    RNA_float_set(gz->ptr, "backdrop_fill_alpha", 0.125f);
+  }
+
+  bToolRef *tref = WM_toolsystem_ref_from_context((bContext *)C);
+  wmWindowManager *wm = CTX_wm_manager(C);
+  struct wmKeyConfig *kc = wm->defaultconf;
+
+  gz->keymap = WM_keymap_ensure(kc, tref->runtime->keymap, tref->space_type, RGN_TYPE_WINDOW);
+  return gz;
+}
+
+static void WIDGETGROUP_tool_generic_setup(const bContext *C, wmGizmoGroup *gzgroup)
+{
+  wmGizmoWrapper *wwrapper = MEM_mallocN(sizeof(wmGizmoWrapper), __func__);
+  wwrapper->gizmo = tool_generic_create_gizmo(C, gzgroup);
+  gzgroup->customdata = wwrapper;
+}
+
+static void WIDGETGROUP_tool_generic_refresh(const bContext *C, wmGizmoGroup *gzgroup)
+{
+  wmGizmoWrapper *wwrapper = gzgroup->customdata;
+  wmGizmo *gz = wwrapper->gizmo;
+
+  ToolSettings *ts = CTX_data_tool_settings(C);
+  if (ts->workspace_tool_type != SCE_WORKSPACE_TOOL_FALLBACK) {
+    gzgroup->use_fallback_keymap = false;
+    WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, true);
+    return;
+  }
+  else {
+    gzgroup->use_fallback_keymap = true;
+  }
+
+  /* skip, we don't draw anything anyway */
+  {
+    int orientation;
+    if (gzgroup->type->idname == handle_normal_id) {
+      orientation = V3D_ORIENT_NORMAL;
+    }
+    else {
+      orientation = V3D_ORIENT_GLOBAL; /* dummy, use view. */
+    }
+
+    struct TransformBounds tbounds;
+    const bool hide = ED_transform_calc_gizmo_stats(C,
+                                                    &(struct TransformCalcParams){
+                                                        .use_only_center = true,
+                                                        .orientation_type = orientation + 1,
+                                                    },
+                                                    &tbounds) == 0;
+
+    WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, hide);
+    if (hide) {
+      return;
+    }
+    copy_m4_m3(gz->matrix_basis, tbounds.axis);
+    copy_v3_v3(gz->matrix_basis[3], tbounds.center);
+    negate_v3(gz->matrix_basis[2]);
+  }
+
+  WM_gizmo_set_flag(gz, WM_GIZMO_DRAW_OFFSET_SCALE, true);
+}
+
+static void WIDGETGROUP_gizmo_message_subscribe(const bContext *C,
+                                                wmGizmoGroup *gzgroup,
+                                                struct wmMsgBus *mbus)
+{
+  ARegion *ar = CTX_wm_region(C);
+
+  wmMsgSubscribeValue msg_sub_value_gz_tag_refresh = {
+      .owner = ar,
+      .user_data = gzgroup->parent_gzmap,
+      .notify = WM_gizmo_do_msg_notify_tag_refresh,
+  };
+
+  {
+    extern PropertyRNA rna_ToolSettings_workspace_tool_type;
+    const PropertyRNA *props[] = {
+        &rna_ToolSettings_workspace_tool_type,
+    };
+
+    Scene *scene = CTX_data_scene(C);
+    PointerRNA toolsettings_ptr;
+    RNA_pointer_create(&scene->id, &RNA_ToolSettings, scene->toolsettings, &toolsettings_ptr);
+
+    for (int i = 0; i < ARRAY_SIZE(props); i++) {
+      WM_msg_subscribe_rna(
+          mbus, &toolsettings_ptr, props[i], &msg_sub_value_gz_tag_refresh, __func__);
+    }
+  }
+}
+
+static const char *handle_normal_id = "VIEW3D_GGT_tool_generic_handle_normal";
+static const char *handle_free_id = "VIEW3D_GGT_tool_generic_handle_free";
+
+void VIEW3D_GGT_tool_generic_handle_normal(wmGizmoGroupType *gzgt)
+{
+  gzgt->name = "Generic Tool Widget Normal";
+  gzgt->idname = handle_normal_id;
+
+  gzgt->flag |= (WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP);
+
+  gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
+  gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
+
+  gzgt->poll = WIDGETGROUP_tool_generic_poll;
+  gzgt->setup = WIDGETGROUP_tool_generic_setup;
+  gzgt->refresh = WIDGETGROUP_tool_generic_refresh;
+  gzgt->message_subscribe = WIDGETGROUP_gizmo_message_subscribe;
+}
+
+void VIEW3D_GGT_tool_generic_handle_free(wmGizmoGroupType *gzgt)
+{
+  gzgt->name = "Generic Tool Widget Free";
+  gzgt->idname = handle_free_id;
+
+  gzgt->flag |= (WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP);
+
+  gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
+  gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
+
+  gzgt->poll = WIDGETGROUP_tool_generic_poll;
+  gzgt->setup = WIDGETGROUP_tool_generic_setup;
+  gzgt->refresh = WIDGETGROUP_tool_generic_refresh;
+  gzgt->message_subscribe = WIDGETGROUP_gizmo_message_subscribe;
+}
+
+/** \} */
index 6b5c27b68f490056577633cfdf7e559f9ca0a871..ddbb64bf0671dad7babd7b61adfd4ebbe6394014 100644 (file)
@@ -261,6 +261,8 @@ void VIEW3D_GGT_armature_spline(struct wmGizmoGroupType *gzgt);
 void VIEW3D_GGT_navigate(struct wmGizmoGroupType *gzgt);
 void VIEW3D_GGT_mesh_preselect_elem(struct wmGizmoGroupType *gzgt);
 void VIEW3D_GGT_mesh_preselect_edgering(struct wmGizmoGroupType *gzgt);
+void VIEW3D_GGT_tool_generic_handle_normal(struct wmGizmoGroupType *gzgt);
+void VIEW3D_GGT_tool_generic_handle_free(struct wmGizmoGroupType *gzgt);
 
 void VIEW3D_GGT_ruler(struct wmGizmoGroupType *gzgt);
 void VIEW3D_GT_ruler_item(struct wmGizmoType *gzt);
index 237bf50be7cdb4d2576ba89b357754c8eceeadd3..bb47f40d84f7ae00cf6dd238256d94e65d47a538 100644 (file)
@@ -1322,6 +1322,17 @@ static void gizmo_xform_message_subscribe(wmGizmoGroup *gzgroup,
     }
   }
 
+  {
+    extern PropertyRNA rna_ToolSettings_workspace_tool_type;
+    const PropertyRNA *props[] = {
+        &rna_ToolSettings_workspace_tool_type,
+    };
+    for (int i = 0; i < ARRAY_SIZE(props); i++) {
+      WM_msg_subscribe_rna(
+          mbus, &toolsettings_ptr, props[i], &msg_sub_value_gz_tag_refresh, __func__);
+    }
+  }
+
   PointerRNA view3d_ptr;
   RNA_pointer_create(&screen->id, &RNA_SpaceView3D, sa->spacedata.first, &view3d_ptr);
 
@@ -1818,6 +1829,13 @@ static void WIDGETGROUP_gizmo_refresh(const bContext *C, wmGizmoGroup *gzgroup)
   for (int i = MAN_AXIS_RANGE_ROT_START; i < MAN_AXIS_RANGE_ROT_END; i++) {
     ggd->gizmos[i]->select_bias = rotate_select_bias;
   }
+
+  if (scene->toolsettings->workspace_tool_type == SCE_WORKSPACE_TOOL_FALLBACK) {
+    gzgroup->use_fallback_keymap = true;
+  }
+  else {
+    gzgroup->use_fallback_keymap = false;
+  }
 }
 
 static void WIDGETGROUP_gizmo_message_subscribe(const bContext *C,
@@ -2028,7 +2046,7 @@ void VIEW3D_GGT_xform_gizmo(wmGizmoGroupType *gzgt)
   gzgt->name = "3D View: Transform Gizmo";
   gzgt->idname = "VIEW3D_GGT_xform_gizmo";
 
-  gzgt->flag = WM_GIZMOGROUPTYPE_3D;
+  gzgt->flag = WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP;
 
   gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
   gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
@@ -2062,7 +2080,8 @@ void VIEW3D_GGT_xform_gizmo_context(wmGizmoGroupType *gzgt)
   gzgt->name = "3D View: Transform Gizmo Context";
   gzgt->idname = "VIEW3D_GGT_xform_gizmo_context";
 
-  gzgt->flag = WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_PERSISTENT;
+  gzgt->flag = WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_PERSISTENT |
+               WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP;
 
   gzgt->poll = WIDGETGROUP_gizmo_poll_context;
   gzgt->setup = WIDGETGROUP_gizmo_setup;
@@ -2212,6 +2231,13 @@ static void WIDGETGROUP_xform_cage_refresh(const bContext *C, wmGizmoGroup *gzgr
 
   /* Needed to test view orientation changes. */
   copy_m3_m4(xgzgroup->prev.viewinv_m3, rv3d->viewinv);
+
+  if (scene->toolsettings->workspace_tool_type == SCE_WORKSPACE_TOOL_FALLBACK) {
+    gzgroup->use_fallback_keymap = true;
+  }
+  else {
+    gzgroup->use_fallback_keymap = false;
+  }
 }
 
 static void WIDGETGROUP_xform_cage_message_subscribe(const bContext *C,
@@ -2263,7 +2289,7 @@ void VIEW3D_GGT_xform_cage(wmGizmoGroupType *gzgt)
   gzgt->name = "Transform Cage";
   gzgt->idname = "VIEW3D_GGT_xform_cage";
 
-  gzgt->flag |= WM_GIZMOGROUPTYPE_3D;
+  gzgt->flag |= WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP;
 
   gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
   gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
@@ -2385,6 +2411,13 @@ static void WIDGETGROUP_xform_shear_refresh(const bContext *C, wmGizmoGroup *gzg
 
   /* Needed to test view orientation changes. */
   copy_m3_m4(xgzgroup->prev.viewinv_m3, rv3d->viewinv);
+
+  if (scene->toolsettings->workspace_tool_type == SCE_WORKSPACE_TOOL_FALLBACK) {
+    gzgroup->use_fallback_keymap = true;
+  }
+  else {
+    gzgroup->use_fallback_keymap = false;
+  }
 }
 
 static void WIDGETGROUP_xform_shear_message_subscribe(const bContext *C,
@@ -2446,7 +2479,7 @@ void VIEW3D_GGT_xform_shear(wmGizmoGroupType *gzgt)
   gzgt->name = "Transform Shear";
   gzgt->idname = "VIEW3D_GGT_xform_shear";
 
-  gzgt->flag |= WM_GIZMOGROUPTYPE_3D;
+  gzgt->flag |= WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP;
 
   gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
   gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
index 513a8afa9b63ed282dff911176135b86d2eeed10..da6b0285a5ca101f103cbc62614b8e4c6ad65d33 100644 (file)
@@ -55,9 +55,10 @@ enum {
 
 static const float extrude_button_scale = 0.15f;
 static const float extrude_button_offset_scale = 1.5f;
-static const float extrude_arrow_scale = 1.0f;
-static const float extrude_arrow_xyz_axis_scale = 1.0f;
-static const float extrude_arrow_normal_axis_scale = 1.0f;
+static const float extrude_outer_scale = 1.2f;
+static const float extrude_arrow_scale = 0.7f;
+static const float extrude_arrow_xyz_axis_scale = 0.6666f;
+static const float extrude_arrow_normal_axis_scale = 0.6666f;
 static const float extrude_dial_scale = 0.2;
 
 static const uchar shape_plus[] = {
@@ -69,6 +70,8 @@ typedef struct GizmoExtrudeGroup {
 
   /* XYZ & normal. */
   wmGizmo *invoke_xyz_no[4];
+  /* Only visible when 'drag' tool option is disabled. */
+  wmGizmo *invoke_view;
   /* Constrained & unconstrained (arrow & circle). */
   wmGizmo *adjust[2];
   int adjust_axis;
@@ -126,11 +129,19 @@ static void gizmo_mesh_extrude_setup(const bContext *C, wmGizmoGroup *gzgroup)
 
   ggd->adjust[0] = WM_gizmo_new_ptr(gzt_arrow, gzgroup, NULL);
   ggd->adjust[1] = WM_gizmo_new_ptr(gzt_dial, gzgroup, NULL);
+  RNA_enum_set(ggd->adjust[1]->ptr, "draw_options", ED_GIZMO_DIAL_DRAW_FLAG_FILL_SELECT);
+
   for (int i = 0; i < 4; i++) {
     ggd->invoke_xyz_no[i] = WM_gizmo_new_ptr(gzt_move, gzgroup, NULL);
     ggd->invoke_xyz_no[i]->flag |= WM_GIZMO_DRAW_OFFSET_SCALE;
   }
 
+  {
+    ggd->invoke_view = WM_gizmo_new_ptr(gzt_dial, gzgroup, NULL);
+    ggd->invoke_view->select_bias = -2.0f;
+    RNA_enum_set(ggd->invoke_view->ptr, "draw_options", ED_GIZMO_DIAL_DRAW_FLAG_FILL_SELECT);
+  }
+
   {
     PropertyRNA *prop = RNA_struct_find_property(ggd->invoke_xyz_no[3]->ptr, "shape");
     for (int i = 0; i < 4; i++) {
@@ -170,6 +181,8 @@ static void gizmo_mesh_extrude_setup(const bContext *C, wmGizmoGroup *gzgroup)
     UI_GetThemeColor3fv(TH_AXIS_X + i, ggd->invoke_xyz_no[i]->color);
   }
   UI_GetThemeColor3fv(TH_GIZMO_PRIMARY, ggd->invoke_xyz_no[3]->color);
+  ggd->invoke_view->color[3] = 0.5f;
+
   for (int i = 0; i < 2; i++) {
     UI_GetThemeColor3fv(TH_GIZMO_PRIMARY, ggd->adjust[i]->color);
   }
@@ -177,6 +190,9 @@ static void gizmo_mesh_extrude_setup(const bContext *C, wmGizmoGroup *gzgroup)
   for (int i = 0; i < 4; i++) {
     WM_gizmo_set_scale(ggd->invoke_xyz_no[i], extrude_button_scale);
   }
+  WM_gizmo_set_scale(ggd->invoke_view, extrude_outer_scale);
+  ggd->invoke_view->line_width = 2.0f;
+
   WM_gizmo_set_scale(ggd->adjust[0], extrude_arrow_scale);
   WM_gizmo_set_scale(ggd->adjust[1], extrude_dial_scale);
   ggd->adjust[1]->line_width = 2.0f;
@@ -193,6 +209,15 @@ static void gizmo_mesh_extrude_setup(const bContext *C, wmGizmoGroup *gzgroup)
     }
   }
 
+  {
+    PointerRNA *ptr = WM_gizmo_operator_set(ggd->invoke_view, 0, ggd->ot_extrude, NULL);
+    PointerRNA macroptr = RNA_pointer_get(ptr, "TRANSFORM_OT_translate");
+    RNA_boolean_set(&macroptr, "release_confirm", true);
+
+    bool constraint[3] = {0, 0, 0};
+    RNA_boolean_set_array(&macroptr, "constraint_axis", constraint);
+  }
+
   /* Adjust extrude. */
   for (int i = 0; i < 2; i++) {
     wmGizmo *gz = ggd->adjust[i];
@@ -211,6 +236,7 @@ static void gizmo_mesh_extrude_refresh(const bContext *C, wmGizmoGroup *gzgroup)
   for (int i = 0; i < 4; i++) {
     WM_gizmo_set_flag(ggd->invoke_xyz_no[i], WM_GIZMO_HIDDEN, true);
   }
+  WM_gizmo_set_flag(ggd->invoke_view, WM_GIZMO_HIDDEN, true);
   for (int i = 0; i < 2; i++) {
     WM_gizmo_set_flag(ggd->adjust[i], WM_GIZMO_HIDDEN, true);
   }
@@ -303,6 +329,7 @@ static void gizmo_mesh_extrude_refresh(const bContext *C, wmGizmoGroup *gzgroup)
   for (int i = 0; i < axis_len_used; i++) {
     WM_gizmo_set_matrix_location(ggd->invoke_xyz_no[i], tbounds.center);
   }
+  WM_gizmo_set_matrix_location(ggd->invoke_view, tbounds.center);
   /* Un-hide. */
   for (int i = 0; i < axis_len_used; i++) {
     WM_gizmo_set_flag(ggd->invoke_xyz_no[i], WM_GIZMO_HIDDEN, false);
@@ -351,6 +378,15 @@ static void gizmo_mesh_extrude_refresh(const bContext *C, wmGizmoGroup *gzgroup)
       WM_gizmo_set_flag(ggd->invoke_xyz_no[3], WM_GIZMO_HIDDEN, true);
       break;
   }
+
+  if (scene->toolsettings->workspace_tool_type == SCE_WORKSPACE_TOOL_FALLBACK) {
+    WM_gizmo_set_flag(ggd->invoke_view, WM_GIZMO_HIDDEN, false);
+    gzgroup->use_fallback_keymap = true;
+  }
+  else {
+    WM_gizmo_set_flag(ggd->invoke_view, WM_GIZMO_HIDDEN, true);
+    gzgroup->use_fallback_keymap = false;
+  }
 }
 
 static void gizmo_mesh_extrude_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup)
@@ -380,6 +416,11 @@ static void gizmo_mesh_extrude_draw_prepare(const bContext *C, wmGizmoGroup *gzg
       copy_v3_v3(ggd->adjust[1]->matrix_basis[1], rv3d->viewinv[1]);
       copy_v3_v3(ggd->adjust[1]->matrix_basis[2], rv3d->viewinv[2]);
     }
+    if ((ggd->invoke_view->flag & WM_GIZMO_HIDDEN) == 0) {
+      copy_v3_v3(ggd->invoke_view->matrix_basis[0], rv3d->viewinv[0]);
+      copy_v3_v3(ggd->invoke_view->matrix_basis[1], rv3d->viewinv[1]);
+      copy_v3_v3(ggd->invoke_view->matrix_basis[2], rv3d->viewinv[2]);
+    }
   }
 }
 
@@ -400,6 +441,9 @@ static void gizmo_mesh_extrude_invoke_prepare(const bContext *UNUSED(C),
     }
     RNA_float_set_array(&macroptr, "value", ggd->redo_xform.value);
   }
+  else if (gz == ggd->invoke_view) {
+    /* pass */
+  }
   else {
     /* Workaround for extrude action modifying normals. */
     const int i = BLI_array_findindex(ggd->invoke_xyz_no, ARRAY_SIZE(ggd->invoke_xyz_no), &gz);
@@ -449,6 +493,20 @@ static void gizmo_mesh_extrude_message_subscribe(const bContext *C,
                               },
                               &msg_sub_value_gz_tag_refresh,
                               __func__);
+
+  {
+    Scene *scene = CTX_data_scene(C);
+    PointerRNA toolsettings_ptr;
+    RNA_pointer_create(&scene->id, &RNA_ToolSettings, scene->toolsettings, &toolsettings_ptr);
+    extern PropertyRNA rna_ToolSettings_workspace_tool_type;
+    const PropertyRNA *props[] = {
+        &rna_ToolSettings_workspace_tool_type,
+    };
+    for (int i = 0; i < ARRAY_SIZE(props); i++) {
+      WM_msg_subscribe_rna(
+          mbus, &toolsettings_ptr, props[i], &msg_sub_value_gz_tag_refresh, __func__);
+    }
+  }
 }
 
 void VIEW3D_GGT_xform_extrude(struct wmGizmoGroupType *gzgt)
@@ -456,7 +514,7 @@ void VIEW3D_GGT_xform_extrude(struct wmGizmoGroupType *gzgt)
   gzgt->name = "3D View Extrude";
   gzgt->idname = "VIEW3D_GGT_xform_extrude";
 
-  gzgt->flag = WM_GIZMOGROUPTYPE_3D;
+  gzgt->flag = WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP;
 
   gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
   gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
index 54ae38f608cbd9079aed05ad4881425fc1f38c72..5336ea381c367834c5e0c05967c72e7aa3379e90 100644 (file)
@@ -1509,7 +1509,10 @@ typedef struct ToolSettings {
   /* XXX: these sculpt_paint_* fields are deprecated, use the
    * unified_paint_settings field instead! */
   short sculpt_paint_settings DNA_DEPRECATED;
-  char _pad5[2];
+
+  char workspace_tool_type;
+
+  char _pad5[1];
   int sculpt_paint_unified_size DNA_DEPRECATED;
   float sculpt_paint_unified_unprojected_radius DNA_DEPRECATED;
   float sculpt_paint_unified_alpha DNA_DEPRECATED;
@@ -2042,6 +2045,12 @@ enum {
   SCE_OBJECT_MODE_LOCK = (1 << 0),
 };
 
+/* ToolSettings.workspace_tool_flag */
+enum {
+  SCE_WORKSPACE_TOOL_FALLBACK = 0,
+  SCE_WORKSPACE_TOOL_DEFAULT = 1,
+};
+
 /* ToolSettings.snap_flag */
 #define SCE_SNAP (1 << 0)
 #define SCE_SNAP_ROTATE (1 << 1)
index eb1b3036767c434f10bcabdfa38eaf91c102950c..c26eb58a8f92273ce35474b1afc033ede11443cf 100644 (file)
@@ -607,7 +607,10 @@ typedef struct UserDef_FileSpaceData {
 typedef struct UserDef_Experimental {
   /** #eUserPref_Experimental_Flag options. */
   int flag;
-  char _pad0[4];
+
+  char use_tool_fallback;
+
+  char _pad0[3];
 } UserDef_Experimental;
 
 typedef struct UserDef {
index dbfb0cc6346c981c92a4fb5f97a1aa98105c6754..573b076542e27a4828dafcfa1076f7f99dbeff9f 100644 (file)
@@ -35,6 +35,10 @@ typedef struct bToolRef_Runtime {
   char gizmo_group[64];
   char data_block[64];
 
+  /** Optionally use these when not interacting directly with the primary tools gizmo. */
+  char idname_fallback[64];
+  char keymap_fallback[64];
+
   /** Use to infer primary operator to use when setting accelerator keys. */
   char op[64];
 
index cca82abc9da9ea82a893bf94350c0e35a9f1e02d..708dfb6c73680a6de74e2a4c1f5bcdc5cd2725c8 100644 (file)
@@ -2867,6 +2867,17 @@ static void rna_def_tool_settings(BlenderRNA *brna)
   RNA_def_property_ui_text(prop, "Lock Object Modes", "Restrict select to the current mode");
   RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
 
+  static const EnumPropertyItem workspace_tool_items[] = {
+      {SCE_WORKSPACE_TOOL_DEFAULT, "DEFAULT", 0, "Active Tool", ""},
+      {SCE_WORKSPACE_TOOL_FALLBACK, "FALLBACK", 0, "Select", ""},
+      {0, NULL, 0, NULL, NULL},
+  };
+
+  prop = RNA_def_property(srna, "workspace_tool_type", PROP_ENUM, PROP_NONE);
+  RNA_def_property_enum_sdna(prop, NULL, "workspace_tool_type");
+  RNA_def_property_enum_items(prop, workspace_tool_items);
+  RNA_def_property_ui_text(prop, "Drag", "Action when dragging in the viewport");
+
   /* Transform */
   prop = RNA_def_property(srna, "use_proportional_edit", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "proportional_edit", PROP_EDIT_USE);
index 64bca38882f27ac47ab46ef12e5edfdb5732f797..aa69b49c39183c596066d406a9e6da40b66c2cbf 100644 (file)
@@ -5845,6 +5845,11 @@ static void rna_def_userdef_experimental(BlenderRNA *brna)
                            "All Experimental Features",
                            "Expose all the experimental features in the user interface");
   RNA_def_property_update(prop, 0, "rna_userdef_update");
+
+  prop = RNA_def_property(srna, "use_tool_fallback", PROP_BOOLEAN, PROP_NONE);
+  RNA_def_property_boolean_sdna(prop, NULL, "use_tool_fallback", 1);
+  RNA_def_property_ui_text(prop, "Fallback Tool Support", "Allow selection with an active tool");
+  RNA_def_property_update(prop, 0, "rna_userdef_update");
 }
 
 static void rna_def_userdef_addon_collection(BlenderRNA *brna, PropertyRNA *cprop)
index 47138653af1fe6d4f016fd18b718dbb576425e98..7c6e3c2730b00a458dc19be6ce2ebda7c70fbdb5 100644 (file)
@@ -192,6 +192,18 @@ static int rna_WorkSpaceTool_widget_length(PointerRNA *ptr)
   return tref->runtime ? strlen(tref->runtime->gizmo_group) : 0;
 }
 
+static void rna_WorkSpaceTool_tool_fallback_get(PointerRNA *ptr, char *value)
+{
+  bToolRef *tref = ptr->data;
+  strcpy(value, tref->runtime ? tref->runtime->idname_fallback : "");
+}
+
+static int rna_WorkSpaceTool_tool_fallback_length(PointerRNA *ptr)
+{
+  bToolRef *tref = ptr->data;
+  return tref->runtime ? strlen(tref->runtime->idname_fallback) : 0;
+}
+
 #else /* RNA_RUNTIME */
 
 static void rna_def_workspace_owner(BlenderRNA *brna)
@@ -290,6 +302,13 @@ static void rna_def_workspace_tool(BlenderRNA *brna)
       prop, "rna_WorkSpaceTool_widget_get", "rna_WorkSpaceTool_widget_length", NULL);
   RNA_define_verify_sdna(1);
 
+  prop = RNA_def_property(srna, "tool_fallback", PROP_STRING, PROP_NONE);
+  RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+  RNA_def_property_ui_text(prop, "Fallback", "");
+  RNA_def_property_string_funcs(
+      prop, "rna_WorkSpaceTool_tool_fallback_get", "rna_WorkSpaceTool_tool_fallback_length", NULL);
+  RNA_define_verify_sdna(1);
+
   RNA_api_workspace_tool(srna);
 }
 
index 2d3b1e76b71580dbcfc0bb46a5b34ecb5ce5dae8..f244a674e57dc44c7961e04d64f52ba72b13acfa 100644 (file)
 static void rna_WorkSpaceTool_setup(ID *id,
                                     bToolRef *tref,
                                     bContext *C,
-                                    const char *tool_idname,
+                                    const char *idname,
                                     /* Args for: 'bToolRef_Runtime'. */
                                     int cursor,
                                     const char *keymap,
                                     const char *gizmo_group,
                                     const char *data_block,
                                     const char *op_idname,
-                                    int index)
+                                    int index,
+                                    const char *idname_fallback,
+                                    const char *keymap_fallback)
 {
   bToolRef_Runtime tref_rt = {0};
 
@@ -61,7 +63,10 @@ static void rna_WorkSpaceTool_setup(ID *id,
   STRNCPY(tref_rt.op, op_idname);
   tref_rt.index = index;
 
-  WM_toolsystem_ref_set_from_runtime(C, (WorkSpace *)id, tref, &tref_rt, tool_idname);
+  STRNCPY(tref_rt.idname_fallback, idname_fallback ? idname_fallback : NULL);
+  STRNCPY(tref_rt.keymap_fallback, keymap_fallback ? keymap_fallback : NULL);
+
+  WM_toolsystem_ref_set_from_runtime(C, (WorkSpace *)id, tref, &tref_rt, idname);
 }
 
 static void rna_WorkSpaceTool_refresh_from_context(ID *id, bToolRef *tref, Main *bmain)
@@ -140,6 +145,9 @@ void RNA_api_workspace_tool(StructRNA *srna)
   RNA_def_string(func, "operator", NULL, MAX_NAME, "Operator", "");
   RNA_def_int(func, "index", 0, INT_MIN, INT_MAX, "Index", "", INT_MIN, INT_MAX);
 
+  RNA_def_string(func, "idname_fallback", NULL, MAX_NAME, "Fallback Identifier", "");
+  RNA_def_string(func, "keymap_fallback", NULL, KMAP_MAX_NAME, "Fallback Key Map", "");
+
   /* Access tool operator options (optionally create). */
   func = RNA_def_function(srna, "operator_properties", "rna_WorkSpaceTool_operator_properties");
   RNA_def_function_flag(func, FUNC_USE_REPORTS);
index 7bb77375934cf64f8d7d8adac5e9fa55a7fa22ed..d3aa333daea8e0670bbd6ed016fe902116d9a2ee 100644 (file)
@@ -120,6 +120,12 @@ typedef enum eWM_GizmoFlagGroupTypeFlag {
    * We could even move the options into the key-map item.
    * ~ campbell. */
   WM_GIZMOGROUPTYPE_TOOL_INIT = (1 << 6),
+
+  /**
+   * This gizmo type supports using the fallback tools keymap.
+   * #wmGizmoGroup.use_tool_fallback will need to be set too.
+   */
+  WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP = (1 << 7),
 } eWM_GizmoFlagGroupTypeFlag;
 
 /**
@@ -443,6 +449,8 @@ typedef struct wmGizmoGroup {
 
   bool tag_remove;
 
+  bool use_fallback_keymap;
+
   void *customdata;
   /** For freeing customdata from above. */
   void (*customdata_free)(void *);
index 180a518de2bc0bac1b41085a310bbb07979aaee1..d65cf2324a9028ff89f9ada6a4c52075baee15ed 100644 (file)
@@ -3728,8 +3728,40 @@ wmKeyMap *WM_event_get_keymap_from_toolsystem(wmWindowManager *wm, wmEventHandle
   handler->keymap_tool = NULL;
   bToolRef_Runtime *tref_rt = sa->runtime.tool ? sa->runtime.tool->runtime : NULL;
   if (tref_rt && tref_rt->keymap[0]) {
+    const char *keymap_id = tref_rt->keymap;
+
+    /* Support for the gizmo owning the tool keymap. */
+    if (U.experimental.use_tool_fallback) {
+      if (tref_rt->gizmo_group[0] != '\0') {
+        wmGizmoMap *gzmap = NULL;
+        wmGizmoGroup *gzgroup = NULL;
+        for (ARegion *ar = sa->regionbase.first; ar; ar = ar->next) {
+          if (ar->gizmo_map != NULL) {
+            gzmap = ar->gizmo_map;
+            gzgroup = WM_gizmomap_group_find(gzmap, tref_rt->gizmo_group);
+            if (gzgroup != NULL) {
+              break;
+            }
+          }
+        }
+        if (gzgroup != NULL) {
+          if (gzgroup->type->flag & WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP) {
+            /* If all are hidden, don't override. */
+            if (gzgroup->use_fallback_keymap) {
+              wmGizmo *highlight = wm_gizmomap_highlight_get(gzmap);
+              if (highlight == NULL) {
+                if (tref_rt->keymap_fallback[0]) {
+                  keymap_id = tref_rt->keymap_fallback;
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+
     wmKeyMap *km = WM_keymap_list_find_spaceid_or_empty(
-        &wm->userconf->keymaps, tref_rt->keymap, sa->spacetype, RGN_TYPE_WINDOW);
+        &wm->userconf->keymaps, keymap_id, sa->spacetype, RGN_TYPE_WINDOW);
     /* We shouldn't use keymaps from unrelated spaces. */
     if (km != NULL) {
       handler->keymap_tool = sa->runtime.tool;
index f64acf20581454429ae948580b402e8af3eb2867..d714fdaa19ea21906328c1df90c0570a4a5c15e9 100644 (file)
@@ -351,6 +351,25 @@ void WM_toolsystem_ref_set_from_runtime(struct bContext *C,
     *tref->runtime = *tref_rt;
   }
 
+  /* FIXME: ideally Python could check this gizmo group flag and not
+   * pass in the argument to begin with. */
+  bool use_fallback_keymap = false;
+
+  if (U.experimental.use_tool_fallback) {
+    if (tref_rt->gizmo_group[0]) {
+      wmGizmoGroupType *gzgt = WM_gizmogrouptype_find(tref_rt->gizmo_group, false);
+      if (gzgt) {
+        if (gzgt->flag & WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP) {
+          use_fallback_keymap = true;
+        }
+      }
+    }
+  }
+  if (use_fallback_keymap == false) {
+    tref->runtime->idname_fallback[0] = '\0';
+    tref->runtime->keymap_fallback[0] = '\0';
+  }
+
   toolsystem_ref_link(C, workspace, tref);
 
   toolsystem_refresh_screen_from_active_tool(bmain, workspace, tref);