diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 06fac16292d..5f600520c12 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -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), diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py index 24670b2a37d..1452b6767b6 100644 --- a/release/scripts/startup/bl_operators/wm.py +++ b/release/scripts/startup/bl_operators/wm.py @@ -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, 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: - if item.idname == idname: - return (cls, item, index) - return None, None, -1 + 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 - @staticmethod - def _tool_get_by_flat_index(context, space_type, tool_index): + @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 (item, -1) + return None, -1 + + @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 diff --git a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py index 9a5df62da46..68d39a3be64 100644 --- a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -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: diff --git a/release/scripts/startup/bl_ui/space_topbar.py b/release/scripts/startup/bl_ui/space_topbar.py index dcfeb6a210f..09531cb5ef6 100644 --- a/release/scripts/startup/bl_ui/space_topbar.py +++ b/release/scripts/startup/bl_ui/space_topbar.py @@ -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, diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index e9ccbbabdd3..130db518cb7 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -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 diff --git a/source/blender/editors/space_view3d/CMakeLists.txt b/source/blender/editors/space_view3d/CMakeLists.txt index 7c75f0ea907..2feef9e0c9a 100644 --- a/source/blender/editors/space_view3d/CMakeLists.txt +++ b/source/blender/editors/space_view3d/CMakeLists.txt @@ -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 diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c index 5c7263d458d..7db1a6123e8 100644 --- a/source/blender/editors/space_view3d/space_view3d.c +++ b/source/blender/editors/space_view3d/space_view3d.c @@ -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 index 00000000000..838abe46616 --- /dev/null +++ b/source/blender/editors/space_view3d/view3d_gizmo_tool_generic.c @@ -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; +} + +/** \} */ diff --git a/source/blender/editors/space_view3d/view3d_intern.h b/source/blender/editors/space_view3d/view3d_intern.h index 6b5c27b68f4..ddbb64bf067 100644 --- a/source/blender/editors/space_view3d/view3d_intern.h +++ b/source/blender/editors/space_view3d/view3d_intern.h @@ -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); diff --git a/source/blender/editors/transform/transform_gizmo_3d.c b/source/blender/editors/transform/transform_gizmo_3d.c index 237bf50be7c..bb47f40d84f 100644 --- a/source/blender/editors/transform/transform_gizmo_3d.c +++ b/source/blender/editors/transform/transform_gizmo_3d.c @@ -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; diff --git a/source/blender/editors/transform/transform_gizmo_extrude_3d.c b/source/blender/editors/transform/transform_gizmo_extrude_3d.c index 513a8afa9b6..da6b0285a5c 100644 --- a/source/blender/editors/transform/transform_gizmo_extrude_3d.c +++ b/source/blender/editors/transform/transform_gizmo_extrude_3d.c @@ -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(¯optr, "release_confirm", true); + + bool constraint[3] = {0, 0, 0}; + RNA_boolean_set_array(¯optr, "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(¯optr, "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; diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 54ae38f608c..5336ea381c3 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -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) diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index eb1b3036767..c26eb58a8f9 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -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 { diff --git a/source/blender/makesdna/DNA_workspace_types.h b/source/blender/makesdna/DNA_workspace_types.h index dbfb0cc6346..573b076542e 100644 --- a/source/blender/makesdna/DNA_workspace_types.h +++ b/source/blender/makesdna/DNA_workspace_types.h @@ -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]; diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index cca82abc9da..708dfb6c736 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -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); diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 64bca38882f..aa69b49c391 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -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) diff --git a/source/blender/makesrna/intern/rna_workspace.c b/source/blender/makesrna/intern/rna_workspace.c index 47138653af1..7c6e3c2730b 100644 --- a/source/blender/makesrna/intern/rna_workspace.c +++ b/source/blender/makesrna/intern/rna_workspace.c @@ -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); } diff --git a/source/blender/makesrna/intern/rna_workspace_api.c b/source/blender/makesrna/intern/rna_workspace_api.c index 2d3b1e76b71..f244a674e57 100644 --- a/source/blender/makesrna/intern/rna_workspace_api.c +++ b/source/blender/makesrna/intern/rna_workspace_api.c @@ -43,14 +43,16 @@ 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); diff --git a/source/blender/windowmanager/gizmo/WM_gizmo_types.h b/source/blender/windowmanager/gizmo/WM_gizmo_types.h index 7bb77375934..d3aa333daea 100644 --- a/source/blender/windowmanager/gizmo/WM_gizmo_types.h +++ b/source/blender/windowmanager/gizmo/WM_gizmo_types.h @@ -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 *); diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index 180a518de2b..d65cf2324a9 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -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; diff --git a/source/blender/windowmanager/intern/wm_toolsystem.c b/source/blender/windowmanager/intern/wm_toolsystem.c index f64acf20581..d714fdaa19e 100644 --- a/source/blender/windowmanager/intern/wm_toolsystem.c +++ b/source/blender/windowmanager/intern/wm_toolsystem.c @@ -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);