Files
test/source/blender/windowmanager/intern/wm_toolsystem.cc
Julian Eisel 892ba12bc9 Fix #128027: Last used brush not remembered correctly
Order of operations was wrong: First, the new tool has to be activated
(or at least identified) before we can tell which tool (or more precise,
which brush type) to update the brush binding for.
2024-09-24 15:31:10 +02:00

1130 lines
37 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* Experimental tool-system>
*/
#include <cstring>
#include "CLG_log.h"
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "DNA_ID.h"
#include "DNA_brush_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_space_types.h"
#include "DNA_windowmanager_types.h"
#include "DNA_workspace_types.h"
#include "BKE_asset_edit.hh"
#include "BKE_brush.hh"
#include "BKE_context.hh"
#include "BKE_idprop.hh"
#include "BKE_layer.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_paint.hh"
#include "BKE_workspace.hh"
#include "RNA_access.hh"
#include "RNA_enum_types.hh"
#include "WM_api.hh"
#include "WM_message.hh"
#include "WM_toolsystem.hh" /* Own include. */
#include "WM_types.hh"
static void toolsystem_reinit_with_toolref(bContext *C, WorkSpace * /*workspace*/, bToolRef *tref);
static bToolRef *toolsystem_reinit_ensure_toolref(bContext *C,
WorkSpace *workspace,
const bToolKey *tkey,
const char *default_tool);
static void toolsystem_refresh_screen_from_active_tool(Main *bmain,
WorkSpace *workspace,
bToolRef *tref);
/* -------------------------------------------------------------------- */
/** \name Tool Reference API
* \{ */
bToolRef *WM_toolsystem_ref_from_context(const bContext *C)
{
WorkSpace *workspace = CTX_wm_workspace(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
ScrArea *area = CTX_wm_area(C);
if ((area == nullptr) || ((1 << area->spacetype) & WM_TOOLSYSTEM_SPACE_MASK) == 0) {
return nullptr;
}
bToolKey tkey{};
tkey.space_type = area->spacetype;
tkey.mode = WM_toolsystem_mode_from_spacetype(scene, view_layer, area, area->spacetype);
bToolRef *tref = WM_toolsystem_ref_find(workspace, &tkey);
/* We could return 'area->runtime.tool' in this case. */
if (area->runtime.is_tool_set) {
BLI_assert(tref == area->runtime.tool);
}
return tref;
}
bToolRef_Runtime *WM_toolsystem_runtime_from_context(const bContext *C)
{
bToolRef *tref = WM_toolsystem_ref_from_context(C);
return tref ? tref->runtime : nullptr;
}
bToolRef *WM_toolsystem_ref_find(WorkSpace *workspace, const bToolKey *tkey)
{
BLI_assert((1 << tkey->space_type) & WM_TOOLSYSTEM_SPACE_MASK);
LISTBASE_FOREACH (bToolRef *, tref, &workspace->tools) {
if ((tref->space_type == tkey->space_type) && (tref->mode == tkey->mode)) {
return tref;
}
}
return nullptr;
}
bToolRef_Runtime *WM_toolsystem_runtime_find(WorkSpace *workspace, const bToolKey *tkey)
{
bToolRef *tref = WM_toolsystem_ref_find(workspace, tkey);
return tref ? tref->runtime : nullptr;
}
bool WM_toolsystem_ref_ensure(WorkSpace *workspace, const bToolKey *tkey, bToolRef **r_tref)
{
bToolRef *tref = WM_toolsystem_ref_find(workspace, tkey);
if (tref) {
*r_tref = tref;
return false;
}
tref = static_cast<bToolRef *>(MEM_callocN(sizeof(*tref), __func__));
BLI_addhead(&workspace->tools, tref);
tref->space_type = tkey->space_type;
tref->mode = tkey->mode;
*r_tref = tref;
return true;
}
/** \} */
static void toolsystem_unlink_ref(bContext *C, WorkSpace * /*workspace*/, bToolRef *tref)
{
bToolRef_Runtime *tref_rt = tref->runtime;
if (tref_rt->gizmo_group[0]) {
wmGizmoGroupType *gzgt = WM_gizmogrouptype_find(tref_rt->gizmo_group, false);
if (gzgt != nullptr) {
Main *bmain = CTX_data_main(C);
WM_gizmo_group_remove_by_tool(C, bmain, gzgt, tref);
}
}
}
void WM_toolsystem_unlink(bContext *C, WorkSpace *workspace, const bToolKey *tkey)
{
bToolRef *tref = WM_toolsystem_ref_find(workspace, tkey);
if (tref && tref->runtime) {
toolsystem_unlink_ref(C, workspace, tref);
}
}
/* -------------------------------------------------------------------- */
/** \name Brush Tools
* \{ */
static std::optional<blender::StringRefNull> find_tool_id_from_brush_type_id(const bContext *C,
const int brush_type)
{
const WorkSpace *workspace = CTX_wm_workspace(C);
LISTBASE_FOREACH (const bToolRef *, tref, &workspace->tools) {
if (tref->runtime && (tref->runtime->brush_type != -1) &&
tref->runtime->brush_type == brush_type)
{
return tref->idname;
}
}
return {};
}
static const char *brush_type_identifier_get(const int brush_type, const PaintMode paint_mode)
{
const EnumPropertyItem *type_enum = BKE_paint_get_tool_enum_from_paintmode(paint_mode);
const int item_idx = RNA_enum_from_value(type_enum, brush_type);
if (item_idx == -1) {
return "";
}
return type_enum[item_idx].identifier;
}
static bool brush_type_is_compatible_with_active_tool(bContext *C, const int brush_type)
{
if (!WM_toolsystem_active_tool_is_brush(C)) {
return false;
}
const bToolRef *active_tool = WM_toolsystem_ref_from_context(C);
BLI_assert(BKE_paintmode_get_active_from_context(C) == BKE_paintmode_get_from_tool(active_tool));
/* Tool supports any brush type, no need to check further. */
if (active_tool->runtime->brush_type == -1) {
return true;
}
return active_tool->runtime->brush_type == brush_type;
}
static NamedBrushAssetReference *toolsystem_brush_type_binding_lookup(const Paint *paint,
const char *brush_type_name)
{
return static_cast<NamedBrushAssetReference *>(
BLI_findstring_ptr(&paint->tool_brush_bindings.active_brush_per_brush_type,
brush_type_name,
offsetof(NamedBrushAssetReference, name)));
}
/**
* Update the bindings so the main brush reference matches the currently active brush.
*/
static void toolsystem_main_brush_binding_update_from_active(Paint *paint)
{
MEM_delete(paint->tool_brush_bindings.main_brush_asset_reference);
paint->tool_brush_bindings.main_brush_asset_reference = nullptr;
if (paint->brush != nullptr) {
if (std::optional<AssetWeakReference> brush_asset_reference =
blender::bke::asset_edit_weak_reference_from_id(paint->brush->id))
{
paint->tool_brush_bindings.main_brush_asset_reference = MEM_new<AssetWeakReference>(
__func__, *brush_asset_reference);
}
}
}
static void toolsystem_brush_type_binding_update(Paint *paint,
const PaintMode paint_mode,
const int brush_type)
{
if (paint->brush == nullptr) {
return;
}
const char *brush_type_name = brush_type_identifier_get(brush_type, paint_mode);
if (!brush_type_name || !brush_type_name[0]) {
return;
}
/* Update existing reference. */
if (NamedBrushAssetReference *existing_brush_ref = toolsystem_brush_type_binding_lookup(
paint, brush_type_name))
{
MEM_delete(existing_brush_ref->brush_asset_reference);
existing_brush_ref->brush_asset_reference = MEM_new<AssetWeakReference>(
__func__, *paint->brush_asset_reference);
}
/* Add new reference. */
else {
NamedBrushAssetReference *new_brush_ref = MEM_cnew<NamedBrushAssetReference>(__func__);
new_brush_ref->name = BLI_strdup(brush_type_name);
new_brush_ref->brush_asset_reference = MEM_new<AssetWeakReference>(
__func__, *paint->brush_asset_reference);
BLI_addhead(&paint->tool_brush_bindings.active_brush_per_brush_type, new_brush_ref);
}
}
bool WM_toolsystem_activate_brush_and_tool(bContext *C, Paint *paint, Brush *brush)
{
const bToolRef *active_tool = WM_toolsystem_ref_from_context(C);
const PaintMode paint_mode = BKE_paintmode_get_active_from_context(C);
if (!BKE_paint_brush_set(paint, brush)) {
return false;
}
/* If necessary, find a compatible tool to switch to. */
{
std::optional<int> brush_type = BKE_paint_get_brush_type_from_paintmode(brush, paint_mode);
if (!brush_type) {
BLI_assert_unreachable();
WM_toolsystem_ref_set_by_id(C, "builtin.brush");
}
else if (!brush_type_is_compatible_with_active_tool(C, *brush_type)) {
std::optional<blender::StringRefNull> compatible_tool = find_tool_id_from_brush_type_id(
C, *brush_type);
WM_toolsystem_ref_set_by_id(C, compatible_tool.value_or("builtin.brush").c_str());
}
}
if (active_tool->runtime->brush_type == -1) {
/* Only update the main brush binding to reference the newly active brush. */
toolsystem_main_brush_binding_update_from_active(paint);
}
else {
toolsystem_brush_type_binding_update(paint, paint_mode, active_tool->runtime->brush_type);
}
return true;
}
static void toolsystem_brush_activate_from_toolref_for_object_particle(const bContext *C,
const WorkSpace *workspace,
const bToolRef *tref)
{
const Main *bmain = CTX_data_main(C);
const bToolRef_Runtime *tref_rt = tref->runtime;
if (!tref_rt->data_block[0]) {
return;
}
const EnumPropertyItem *items = rna_enum_particle_edit_hair_brush_items;
const int i = RNA_enum_from_identifier(items, tref_rt->data_block);
if (i == -1) {
return;
}
const wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
if (workspace == WM_window_get_active_workspace(win)) {
Scene *scene = WM_window_get_active_scene(win);
ToolSettings *ts = scene->toolsettings;
ts->particle.brushtype = items[i].value;
}
}
}
static void toolsystem_brush_activate_from_toolref_for_object_paint(const bContext *C,
const WorkSpace *workspace,
const bToolRef *tref)
{
Main *bmain = CTX_data_main(C);
bToolRef_Runtime *tref_rt = tref->runtime;
const PaintMode paint_mode = BKE_paintmode_get_from_tool(tref);
BLI_assert(paint_mode != PaintMode::Invalid);
wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
if (workspace != WM_window_get_active_workspace(win)) {
continue;
}
Scene *scene = WM_window_get_active_scene(win);
BKE_paint_ensure_from_paintmode(bmain, scene, paint_mode);
Paint *paint = BKE_paint_get_active_from_paintmode(scene, paint_mode);
/* Attempt to re-activate a brush remembered for this brush type, as stored in a brush
* binding. */
if (tref_rt->brush_type != -1) {
std::optional<AssetWeakReference> brush_asset_reference =
[&]() -> std::optional<AssetWeakReference> {
const char *brush_type_name = brush_type_identifier_get(tref_rt->brush_type, paint_mode);
const NamedBrushAssetReference *brush_ref = toolsystem_brush_type_binding_lookup(
paint, brush_type_name);
if (brush_ref && brush_ref->brush_asset_reference) {
return *brush_ref->brush_asset_reference;
}
/* No remembered brush found for this type, use a default for the type. */
return BKE_paint_brush_type_default_reference(eObjectMode(paint->runtime.ob_mode),
tref_rt->brush_type);
}();
if (brush_asset_reference) {
BKE_paint_brush_set(bmain, paint, &*brush_asset_reference);
}
}
/* Re-activate the main brush, regardless of the brush type. */
else {
if (paint->tool_brush_bindings.main_brush_asset_reference) {
BKE_paint_brush_set(bmain, paint, paint->tool_brush_bindings.main_brush_asset_reference);
toolsystem_main_brush_binding_update_from_active(paint);
}
else {
std::optional<AssetWeakReference> main_brush_asset_reference =
[&]() -> std::optional<AssetWeakReference> {
if (paint->tool_brush_bindings.main_brush_asset_reference) {
return *paint->tool_brush_bindings.main_brush_asset_reference;
}
return BKE_paint_brush_type_default_reference(eObjectMode(paint->runtime.ob_mode),
std::nullopt);
}();
if (main_brush_asset_reference) {
BKE_paint_brush_set(bmain, paint, &*main_brush_asset_reference);
toolsystem_main_brush_binding_update_from_active(paint);
}
}
}
}
}
/**
* Activate a brush compatible with \a tref, call when the active tool changes.
*/
static void toolsystem_brush_activate_from_toolref(const bContext *C,
const WorkSpace *workspace,
const bToolRef *tref)
{
BLI_assert(tref->runtime->flag & TOOLREF_FLAG_USE_BRUSHES);
if (tref->space_type == SPACE_VIEW3D) {
if (tref->mode == CTX_MODE_PARTICLE) {
toolsystem_brush_activate_from_toolref_for_object_particle(C, workspace, tref);
}
else {
toolsystem_brush_activate_from_toolref_for_object_paint(C, workspace, tref);
}
}
}
/** \} */
static void toolsystem_ref_link(const bContext *C, WorkSpace *workspace, bToolRef *tref)
{
bToolRef_Runtime *tref_rt = tref->runtime;
if (tref_rt->gizmo_group[0]) {
const char *idname = tref_rt->gizmo_group;
wmGizmoGroupType *gzgt = WM_gizmogrouptype_find(idname, false);
if (gzgt != nullptr) {
if ((gzgt->flag & WM_GIZMOGROUPTYPE_TOOL_INIT) == 0) {
if (!WM_gizmo_group_type_ensure_ptr(gzgt)) {
/* Even if the group-type was has been linked, it's possible the space types
* were not previously using it. (happens with multiple windows). */
wmGizmoMapType *gzmap_type = WM_gizmomaptype_ensure(&gzgt->gzmap_params);
WM_gizmoconfig_update_tag_group_type_init(gzmap_type, gzgt);
}
}
}
else {
CLOG_WARN(WM_LOG_TOOLS, "'%s' widget not found", idname);
}
}
if (tref_rt->flag & TOOLREF_FLAG_USE_BRUSHES) {
toolsystem_brush_activate_from_toolref(C, workspace, tref);
}
}
static void toolsystem_refresh_ref(const bContext *C, WorkSpace *workspace, bToolRef *tref)
{
if (tref->runtime == nullptr) {
return;
}
/* Currently same operation. */
toolsystem_ref_link(C, workspace, tref);
}
void WM_toolsystem_refresh(const bContext *C, WorkSpace *workspace, const bToolKey *tkey)
{
bToolRef *tref = WM_toolsystem_ref_find(workspace, tkey);
if (tref) {
toolsystem_refresh_ref(C, workspace, tref);
}
}
static void toolsystem_reinit_ref(bContext *C, WorkSpace *workspace, bToolRef *tref)
{
toolsystem_reinit_with_toolref(C, workspace, tref);
}
void WM_toolsystem_reinit(bContext *C, WorkSpace *workspace, const bToolKey *tkey)
{
bToolRef *tref = WM_toolsystem_ref_find(workspace, tkey);
if (tref) {
toolsystem_reinit_ref(C, workspace, tref);
}
}
void WM_toolsystem_unlink_all(bContext *C, WorkSpace *workspace)
{
LISTBASE_FOREACH (bToolRef *, tref, &workspace->tools) {
tref->tag = 0;
}
LISTBASE_FOREACH (bToolRef *, tref, &workspace->tools) {
if (tref->runtime) {
if (tref->tag == 0) {
toolsystem_unlink_ref(C, workspace, tref);
tref->tag = 1;
}
}
}
}
void WM_toolsystem_refresh_all(const bContext *C, WorkSpace *workspace)
{
BLI_assert(0);
LISTBASE_FOREACH (bToolRef *, tref, &workspace->tools) {
toolsystem_refresh_ref(C, workspace, tref);
}
}
void WM_toolsystem_reinit_all(bContext *C, wmWindow *win)
{
bScreen *screen = WM_window_get_active_screen(win);
const Scene *scene = WM_window_get_active_scene(win);
ViewLayer *view_layer = WM_window_get_active_view_layer(win);
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
if (((1 << area->spacetype) & WM_TOOLSYSTEM_SPACE_MASK) == 0) {
continue;
}
WorkSpace *workspace = WM_window_get_active_workspace(win);
bToolKey tkey{};
tkey.space_type = area->spacetype;
tkey.mode = WM_toolsystem_mode_from_spacetype(scene, view_layer, area, area->spacetype);
bToolRef *tref = WM_toolsystem_ref_find(workspace, &tkey);
if (tref) {
if (tref->tag == 0) {
toolsystem_reinit_ref(C, workspace, tref);
tref->tag = 1;
}
}
}
}
void WM_toolsystem_ref_set_from_runtime(bContext *C,
WorkSpace *workspace,
bToolRef *tref,
const bToolRef_Runtime *tref_rt,
const char *idname)
{
Main *bmain = CTX_data_main(C);
if (tref->runtime) {
toolsystem_unlink_ref(C, workspace, tref);
}
STRNCPY(tref->idname, idname);
if (tref->runtime == nullptr) {
tref->runtime = static_cast<bToolRef_Runtime *>(MEM_callocN(sizeof(*tref->runtime), __func__));
}
if (tref_rt != tref->runtime) {
*tref->runtime = *tref_rt;
}
/* Ideally Python could check this gizmo group flag and not
* pass in the argument to begin with. */
bool use_fallback_keymap = false;
if (tref->idname_fallback[0] || tref->runtime->keymap_fallback[0]) {
if (tref_rt->flag & TOOLREF_FLAG_FALLBACK_KEYMAP) {
use_fallback_keymap = true;
}
else 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->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);
/* Set the cursor if possible, if not - it's fine as entering the region will refresh it. */
{
wmWindow *win = CTX_wm_window(C);
if (win != nullptr) {
win->addmousemove = true;
win->tag_cursor_refresh = true;
}
}
{
wmMsgBus *mbus = CTX_wm_message_bus(C);
WM_msg_publish_rna_prop(mbus, &workspace->id, workspace, WorkSpace, tools);
}
}
void WM_toolsystem_ref_sync_from_context(Main *bmain, WorkSpace *workspace, bToolRef *tref)
{
bToolRef_Runtime *tref_rt = tref->runtime;
if ((tref_rt == nullptr) || (tref_rt->data_block[0] == '\0')) {
return;
}
wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
if (workspace != WM_window_get_active_workspace(win)) {
continue;
}
Scene *scene = WM_window_get_active_scene(win);
ToolSettings *ts = scene->toolsettings;
ViewLayer *view_layer = WM_window_get_active_view_layer(win);
BKE_view_layer_synced_ensure(scene, view_layer);
const Object *ob = BKE_view_layer_active_object_get(view_layer);
if (ob == nullptr) {
/* Pass. */
}
if ((tref->space_type == SPACE_VIEW3D) && (tref->mode == CTX_MODE_PARTICLE)) {
if (ob->mode & OB_MODE_PARTICLE_EDIT) {
const EnumPropertyItem *items = rna_enum_particle_edit_hair_brush_items;
const int i = RNA_enum_from_value(items, ts->particle.brushtype);
const EnumPropertyItem *item = &items[i];
if (!STREQ(tref_rt->data_block, item->identifier)) {
STRNCPY(tref_rt->data_block, item->identifier);
SNPRINTF(tref->idname, "builtin_brush.%s", item->name);
}
}
}
}
}
void WM_toolsystem_init(const bContext *C)
{
Main *bmain = CTX_data_main(C);
BLI_assert(CTX_wm_window(C) == nullptr);
LISTBASE_FOREACH (WorkSpace *, workspace, &bmain->workspaces) {
LISTBASE_FOREACH (bToolRef *, tref, &workspace->tools) {
MEM_SAFE_FREE(tref->runtime);
}
}
/* Rely on screen initialization for gizmos. */
}
static bool toolsystem_key_ensure_check(const bToolKey *tkey)
{
switch (tkey->space_type) {
case SPACE_VIEW3D:
return true;
case SPACE_IMAGE:
if (ELEM(tkey->mode, SI_MODE_PAINT, SI_MODE_UV)) {
return true;
}
break;
case SPACE_NODE:
return true;
case SPACE_SEQ:
return true;
}
return false;
}
int WM_toolsystem_mode_from_spacetype(const Scene *scene,
ViewLayer *view_layer,
ScrArea *area,
int space_type)
{
int mode = -1;
switch (space_type) {
case SPACE_VIEW3D: {
/* 'area' may be nullptr in this case. */
BKE_view_layer_synced_ensure(scene, view_layer);
Object *obact = BKE_view_layer_active_object_get(view_layer);
if (obact != nullptr) {
Object *obedit = OBEDIT_FROM_OBACT(obact);
mode = CTX_data_mode_enum_ex(obedit, obact, eObjectMode(obact->mode));
}
else {
mode = CTX_MODE_OBJECT;
}
break;
}
case SPACE_IMAGE: {
SpaceImage *sima = static_cast<SpaceImage *>(area->spacedata.first);
mode = sima->mode;
break;
}
case SPACE_NODE: {
mode = 0;
break;
}
case SPACE_SEQ: {
SpaceSeq *sseq = static_cast<SpaceSeq *>(area->spacedata.first);
mode = sseq->view;
break;
}
}
return mode;
}
bool WM_toolsystem_key_from_context(const Scene *scene,
ViewLayer *view_layer,
ScrArea *area,
bToolKey *tkey)
{
int space_type = SPACE_EMPTY;
int mode = -1;
if (area != nullptr) {
space_type = area->spacetype;
mode = WM_toolsystem_mode_from_spacetype(scene, view_layer, area, space_type);
}
if (mode != -1) {
tkey->space_type = space_type;
tkey->mode = mode;
return true;
}
return false;
}
void WM_toolsystem_refresh_active(bContext *C)
{
Main *bmain = CTX_data_main(C);
struct {
wmWindow *win;
ScrArea *area;
ARegion *region;
bool is_set;
} context_prev = {nullptr};
for (wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first); wm;
wm = static_cast<wmWindowManager *>(wm->id.next))
{
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
WorkSpace *workspace = WM_window_get_active_workspace(win);
bScreen *screen = WM_window_get_active_screen(win);
const Scene *scene = WM_window_get_active_scene(win);
ViewLayer *view_layer = WM_window_get_active_view_layer(win);
/* Could skip loop for modes that don't depend on space type. */
int space_type_mask_handled = 0;
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
/* Don't change the space type of the active tool, only update its mode. */
const int space_type_mask = (1 << area->spacetype);
if ((space_type_mask & WM_TOOLSYSTEM_SPACE_MASK) &&
((space_type_mask_handled & space_type_mask) == 0))
{
space_type_mask_handled |= space_type_mask;
bToolKey tkey{};
tkey.space_type = area->spacetype;
tkey.mode = WM_toolsystem_mode_from_spacetype(scene, view_layer, area, area->spacetype);
bToolRef *tref = WM_toolsystem_ref_find(workspace, &tkey);
if (tref != area->runtime.tool) {
if (context_prev.is_set == false) {
context_prev.win = CTX_wm_window(C);
context_prev.area = CTX_wm_area(C);
context_prev.region = CTX_wm_region(C);
context_prev.is_set = true;
}
CTX_wm_window_set(C, win);
CTX_wm_area_set(C, area);
toolsystem_reinit_ensure_toolref(C, workspace, &tkey, nullptr);
}
}
}
}
}
if (context_prev.is_set) {
CTX_wm_window_set(C, context_prev.win);
CTX_wm_area_set(C, context_prev.area);
CTX_wm_region_set(C, context_prev.region);
}
BKE_workspace_id_tag_all_visible(bmain, ID_TAG_DOIT);
LISTBASE_FOREACH (WorkSpace *, workspace, &bmain->workspaces) {
if (workspace->id.tag & ID_TAG_DOIT) {
workspace->id.tag &= ~ID_TAG_DOIT;
/* Refresh to ensure data is initialized.
* This is needed because undo can load a state which no longer has the underlying DNA data
* needed for the tool (un-initialized paint-slots for eg), see: #64339. */
LISTBASE_FOREACH (bToolRef *, tref, &workspace->tools) {
toolsystem_refresh_ref(C, workspace, tref);
}
}
}
}
bool WM_toolsystem_refresh_screen_area(WorkSpace *workspace,
const Scene *scene,
ViewLayer *view_layer,
ScrArea *area)
{
const bool is_tool_set_prev = area->runtime.is_tool_set;
const bToolRef *tref_prev = area->runtime.tool;
area->runtime.tool = nullptr;
area->runtime.is_tool_set = true;
const int mode = WM_toolsystem_mode_from_spacetype(scene, view_layer, area, area->spacetype);
LISTBASE_FOREACH (bToolRef *, tref, &workspace->tools) {
if (tref->space_type == area->spacetype) {
if (tref->mode == mode) {
area->runtime.tool = tref;
break;
}
}
}
return !(is_tool_set_prev && (tref_prev == area->runtime.tool));
}
void WM_toolsystem_refresh_screen_window(wmWindow *win)
{
WorkSpace *workspace = WM_window_get_active_workspace(win);
bool space_type_has_tools[SPACE_TYPE_NUM] = {false};
LISTBASE_FOREACH (bToolRef *, tref, &workspace->tools) {
space_type_has_tools[tref->space_type] = true;
}
bScreen *screen = WM_window_get_active_screen(win);
const Scene *scene = WM_window_get_active_scene(win);
ViewLayer *view_layer = WM_window_get_active_view_layer(win);
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
area->runtime.tool = nullptr;
area->runtime.is_tool_set = true;
if (space_type_has_tools[area->spacetype]) {
WM_toolsystem_refresh_screen_area(workspace, scene, view_layer, area);
}
}
}
void WM_toolsystem_refresh_screen_all(Main *bmain)
{
/* Update all ScrArea's tools. */
for (wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first); wm;
wm = static_cast<wmWindowManager *>(wm->id.next))
{
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
WM_toolsystem_refresh_screen_window(win);
}
}
}
static void toolsystem_refresh_screen_from_active_tool(Main *bmain,
WorkSpace *workspace,
bToolRef *tref)
{
/* Update all ScrArea's tools. */
for (wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first); wm;
wm = static_cast<wmWindowManager *>(wm->id.next))
{
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
if (workspace == WM_window_get_active_workspace(win)) {
bScreen *screen = WM_window_get_active_screen(win);
const Scene *scene = WM_window_get_active_scene(win);
ViewLayer *view_layer = WM_window_get_active_view_layer(win);
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
if (area->spacetype == tref->space_type) {
int mode = WM_toolsystem_mode_from_spacetype(scene, view_layer, area, area->spacetype);
if (mode == tref->mode) {
area->runtime.tool = tref;
area->runtime.is_tool_set = true;
}
}
}
}
}
}
}
bToolRef *WM_toolsystem_ref_set_by_id_ex(
bContext *C, WorkSpace *workspace, const bToolKey *tkey, const char *name, bool cycle)
{
wmOperatorType *ot = WM_operatortype_find("WM_OT_tool_set_by_id", false);
/* On startup, Python operators are not yet loaded. */
if (ot == nullptr) {
return nullptr;
}
/* Some contexts use the current space type (image editor for e.g.),
* ensure this is set correctly or there is no area. */
#ifndef NDEBUG
/* Exclude this check for some space types where the space type isn't used. */
if ((1 << tkey->space_type) & WM_TOOLSYSTEM_SPACE_MASK_MODE_FROM_SPACE) {
ScrArea *area = CTX_wm_area(C);
BLI_assert(area == nullptr || area->spacetype == tkey->space_type);
}
#endif
PointerRNA op_props;
WM_operator_properties_create_ptr(&op_props, ot);
RNA_string_set(&op_props, "name", name);
BLI_assert((1 << tkey->space_type) & WM_TOOLSYSTEM_SPACE_MASK);
RNA_enum_set(&op_props, "space_type", tkey->space_type);
RNA_boolean_set(&op_props, "cycle", cycle);
WM_operator_name_call_ptr(C, ot, WM_OP_EXEC_DEFAULT, &op_props, nullptr);
WM_operator_properties_free(&op_props);
bToolRef *tref = WM_toolsystem_ref_find(workspace, tkey);
if (tref) {
Main *bmain = CTX_data_main(C);
toolsystem_refresh_screen_from_active_tool(bmain, workspace, tref);
}
return (tref && STREQ(tref->idname, name)) ? tref : nullptr;
}
bToolRef *WM_toolsystem_ref_set_by_id(bContext *C, const char *name)
{
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
ScrArea *area = CTX_wm_area(C);
bToolKey tkey;
if (WM_toolsystem_key_from_context(scene, view_layer, area, &tkey)) {
WorkSpace *workspace = CTX_wm_workspace(C);
return WM_toolsystem_ref_set_by_id_ex(C, workspace, &tkey, name, false);
}
return nullptr;
}
static void toolsystem_reinit_with_toolref(bContext *C, WorkSpace *workspace, bToolRef *tref)
{
bToolKey tkey{};
tkey.space_type = tref->space_type;
tkey.mode = tref->mode;
WM_toolsystem_ref_set_by_id_ex(C, workspace, &tkey, tref->idname, false);
}
static const char *toolsystem_default_tool(const bToolKey *tkey)
{
switch (tkey->space_type) {
case SPACE_VIEW3D:
switch (tkey->mode) {
case CTX_MODE_SCULPT:
case CTX_MODE_PAINT_VERTEX:
case CTX_MODE_PAINT_WEIGHT:
case CTX_MODE_PAINT_TEXTURE:
case CTX_MODE_PAINT_GPENCIL_LEGACY:
case CTX_MODE_PAINT_GREASE_PENCIL:
case CTX_MODE_SCULPT_GPENCIL_LEGACY:
case CTX_MODE_SCULPT_GREASE_PENCIL:
case CTX_MODE_WEIGHT_GPENCIL_LEGACY:
case CTX_MODE_WEIGHT_GREASE_PENCIL:
case CTX_MODE_VERTEX_GPENCIL_LEGACY:
case CTX_MODE_VERTEX_GREASE_PENCIL:
case CTX_MODE_SCULPT_CURVES:
return "builtin.brush";
case CTX_MODE_PARTICLE:
return "builtin_brush.Comb";
case CTX_MODE_EDIT_TEXT:
return "builtin.select_text";
}
break;
case SPACE_IMAGE:
switch (tkey->mode) {
case SI_MODE_PAINT:
return "builtin.brush";
}
break;
case SPACE_NODE: {
return "builtin.select_box";
}
case SPACE_SEQ: {
switch (tkey->mode) {
case SEQ_VIEW_SEQUENCE:
return "builtin.select";
case SEQ_VIEW_PREVIEW:
return "builtin.select_box";
case SEQ_VIEW_SEQUENCE_PREVIEW:
return "builtin.select";
}
return "builtin.select_box";
}
}
return "builtin.select_box";
}
/**
* Run after changing modes.
*/
static bToolRef *toolsystem_reinit_ensure_toolref(bContext *C,
WorkSpace *workspace,
const bToolKey *tkey,
const char *default_tool)
{
bToolRef *tref;
if (WM_toolsystem_ref_ensure(workspace, tkey, &tref)) {
if (default_tool == nullptr) {
default_tool = toolsystem_default_tool(tkey);
}
STRNCPY(tref->idname, default_tool);
}
toolsystem_reinit_with_toolref(C, workspace, tref);
return tref;
}
static void wm_toolsystem_update_from_context_view3d_impl(bContext *C, WorkSpace *workspace)
{
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
int space_type = SPACE_VIEW3D;
bToolKey tkey{};
tkey.space_type = space_type;
tkey.mode = WM_toolsystem_mode_from_spacetype(scene, view_layer, nullptr, space_type);
toolsystem_reinit_ensure_toolref(C, workspace, &tkey, nullptr);
}
void WM_toolsystem_update_from_context_view3d(bContext *C)
{
WorkSpace *workspace = CTX_wm_workspace(C);
wm_toolsystem_update_from_context_view3d_impl(C, workspace);
/* Multi window support. */
Main *bmain = CTX_data_main(C);
wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
if (!BLI_listbase_is_single(&wm->windows)) {
wmWindow *win_prev = CTX_wm_window(C);
ScrArea *area_prev = CTX_wm_area(C);
ARegion *region_prev = CTX_wm_region(C);
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
if (win != win_prev) {
WorkSpace *workspace_iter = WM_window_get_active_workspace(win);
if (workspace_iter != workspace) {
CTX_wm_window_set(C, win);
wm_toolsystem_update_from_context_view3d_impl(C, workspace_iter);
CTX_wm_window_set(C, win_prev);
CTX_wm_area_set(C, area_prev);
CTX_wm_region_set(C, region_prev);
}
}
}
}
}
void WM_toolsystem_update_from_context(
bContext *C, WorkSpace *workspace, const Scene *scene, ViewLayer *view_layer, ScrArea *area)
{
bToolKey tkey{};
tkey.space_type = area->spacetype;
tkey.mode = WM_toolsystem_mode_from_spacetype(scene, view_layer, area, area->spacetype);
if (toolsystem_key_ensure_check(&tkey)) {
toolsystem_reinit_ensure_toolref(C, workspace, &tkey, nullptr);
}
}
bool WM_toolsystem_active_tool_is_brush(const bContext *C)
{
const bToolRef_Runtime *tref_rt = WM_toolsystem_runtime_from_context((bContext *)C);
return tref_rt && (tref_rt->flag & TOOLREF_FLAG_USE_BRUSHES);
}
bool WM_toolsystem_active_tool_has_custom_cursor(const bContext *C)
{
const bToolRef_Runtime *tref_rt = WM_toolsystem_runtime_from_context((bContext *)C);
return tref_rt && (tref_rt->cursor != WM_CURSOR_DEFAULT);
}
void WM_toolsystem_do_msg_notify_tag_refresh(bContext *C,
wmMsgSubscribeKey * /*msg_key*/,
wmMsgSubscribeValue *msg_val)
{
ScrArea *area = static_cast<ScrArea *>(msg_val->user_data);
Main *bmain = CTX_data_main(C);
wmWindow *win = static_cast<wmWindow *>(((wmWindowManager *)bmain->wm.first)->windows.first);
if (win->next != nullptr) {
do {
bScreen *screen = WM_window_get_active_screen(win);
if (BLI_findindex(&screen->areabase, area) != -1) {
break;
}
} while ((win = win->next));
}
WorkSpace *workspace = WM_window_get_active_workspace(win);
const Scene *scene = WM_window_get_active_scene(win);
ViewLayer *view_layer = WM_window_get_active_view_layer(win);
bToolKey tkey{};
tkey.space_type = area->spacetype;
tkey.mode = WM_toolsystem_mode_from_spacetype(scene, view_layer, area, area->spacetype);
WM_toolsystem_refresh(C, workspace, &tkey);
WM_toolsystem_refresh_screen_area(workspace, scene, view_layer, area);
}
static IDProperty *idprops_ensure_named_group(IDProperty *group, const char *idname)
{
IDProperty *prop = IDP_GetPropertyFromGroup(group, idname);
if ((prop == nullptr) || (prop->type != IDP_GROUP)) {
prop = blender::bke::idprop::create_group(__func__).release();
STRNCPY(prop->name, idname);
IDP_ReplaceInGroup_ex(group, prop, nullptr);
}
return prop;
}
IDProperty *WM_toolsystem_ref_properties_get_idprops(bToolRef *tref)
{
IDProperty *group = tref->properties;
if (group == nullptr) {
return nullptr;
}
return IDP_GetPropertyFromGroup(group, tref->idname);
}
IDProperty *WM_toolsystem_ref_properties_ensure_idprops(bToolRef *tref)
{
if (tref->properties == nullptr) {
tref->properties = blender::bke::idprop::create_group(__func__).release();
}
return idprops_ensure_named_group(tref->properties, tref->idname);
}
bool WM_toolsystem_ref_properties_get_ex(bToolRef *tref,
const char *idname,
StructRNA *type,
PointerRNA *r_ptr)
{
IDProperty *group = WM_toolsystem_ref_properties_get_idprops(tref);
IDProperty *prop = group ? IDP_GetPropertyFromGroup(group, idname) : nullptr;
*r_ptr = RNA_pointer_create(nullptr, type, prop);
return (prop != nullptr);
}
void WM_toolsystem_ref_properties_ensure_ex(bToolRef *tref,
const char *idname,
StructRNA *type,
PointerRNA *r_ptr)
{
IDProperty *group = WM_toolsystem_ref_properties_ensure_idprops(tref);
IDProperty *prop = idprops_ensure_named_group(group, idname);
*r_ptr = RNA_pointer_create(nullptr, type, prop);
}
void WM_toolsystem_ref_properties_init_for_keymap(bToolRef *tref,
PointerRNA *dst_ptr,
PointerRNA *src_ptr,
wmOperatorType *ot)
{
*dst_ptr = *src_ptr;
if (dst_ptr->data) {
dst_ptr->data = IDP_CopyProperty(static_cast<const IDProperty *>(dst_ptr->data));
}
else {
dst_ptr->data = blender::bke::idprop::create_group("wmOpItemProp").release();
}
IDProperty *group = WM_toolsystem_ref_properties_get_idprops(tref);
if (group != nullptr) {
IDProperty *prop = IDP_GetPropertyFromGroup(group, ot->idname);
if (prop) {
/* Important key-map items properties don't get overwritten by the tools.
* - When a key-map item doesn't set a property, the tool-systems is used.
* - When it does, it overrides the tool-system.
*
* This way the default action can be to follow the top-bar tool-settings &
* modifier keys can be used to perform different actions that aren't clobbered here.
*/
IDP_MergeGroup(static_cast<IDProperty *>(dst_ptr->data), prop, false);
}
}
}