Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,623 @@
/* SPDX-FileCopyrightText: 2007 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* Internal functions for managing UI registerable types (operator, UI and menu types).
*
* Also Blender's main event loop (WM_main).
*/
/* Allow using deprecated functionality for .blend file I/O. */
#define DNA_DEPRECATED_ALLOW
#include <cstring>
#include "DNA_ID_enums.h"
#include "DNA_layer_types.h"
#include "DNA_windowmanager_types.h"
#include "MEM_guardedalloc.h"
#include "BLI_ghash.h"
#include "BLI_listbase.h"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
#include "BLT_translation.hh"
#include "BKE_context.hh"
#include "BKE_global.hh"
#include "BKE_idprop.hh"
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_lib_query.hh"
#include "BKE_main.hh"
#include "BKE_report.hh"
#include "BKE_screen.hh"
#include "BKE_workspace.hh"
#include "PRF_profile.hh"
#include "WM_api.hh"
#include "WM_keymap.hh"
#include "WM_message.hh"
#include "WM_types.hh"
#include "wm.hh"
#include "wm_draw.hh"
#include "wm_event_system.hh"
#include "wm_window.hh"
#ifdef WITH_XR_OPENXR
# include "wm_xr.hh"
#endif
#include "BKE_undo_system.hh"
#include "ED_screen.hh"
#ifdef WITH_PYTHON
# include "BPY_extern.hh"
# include "BPY_extern_run.hh"
#endif
#include "BLO_read_write.hh"
namespace blender {
/* ****************************************************** */
static void window_manager_free_data(ID *id)
{
wm_close_and_free(nullptr, id_cast<wmWindowManager *>(id));
}
static void window_manager_foreach_id(ID *id, LibraryForeachIDData *data)
{
wmWindowManager *wm = reinterpret_cast<wmWindowManager *>(id);
const int flag = BKE_lib_query_foreachid_process_flags_get(data);
for (wmWindow &win : wm->windows) {
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, win.scene, IDWALK_CB_USER_ONE);
/* This pointer can be nullptr during old files reading. */
if (win.workspace_hook != nullptr) {
ID *workspace = id_cast<ID *>(BKE_workspace_active_get(win.workspace_hook));
BKE_lib_query_foreachid_process(data, &workspace, IDWALK_CB_USER);
/* Allow callback to set a different workspace. */
BKE_workspace_active_set(win.workspace_hook, id_cast<WorkSpace *>(workspace));
if (BKE_lib_query_foreachid_iter_stop(data)) {
return;
}
}
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, win.unpinned_scene, IDWALK_CB_NOP);
if (flag & IDWALK_INCLUDE_UI) {
for (ScrArea &area : win.global_areas.areabase) {
BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data,
BKE_screen_foreach_id_screen_area(data, &area));
}
}
if (flag & IDWALK_DO_DEPRECATED_POINTERS) {
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, win.screen, IDWALK_CB_NOP);
}
}
BKE_LIB_FOREACHID_PROCESS_IDSUPER(
data, wm->xr.session_settings.base_pose_object, IDWALK_CB_USER_ONE);
}
static void write_wm_xr_data(BlendWriter *writer, wmXrData *xr_data)
{
BKE_screen_view3d_shading_blend_write(writer, &xr_data->session_settings.shading);
}
static void window_manager_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
wmWindowManager *wm = id_cast<wmWindowManager *>(id);
wm->runtime = nullptr;
writer->write_id_struct(id_address, wm);
BKE_id_blend_write(writer, &wm->id);
write_wm_xr_data(writer, &wm->xr);
for (wmWindow &win : wm->windows) {
/* Update deprecated screen member (for so loading in 2.7x uses the correct screen). */
win.screen = BKE_workspace_active_screen_get(win.workspace_hook);
writer->write_struct(&win);
writer->write_struct(win.workspace_hook);
writer->write_struct(win.stereo3d_format);
BKE_screen_area_map_blend_write(writer, &win.global_areas);
/* Data is written, clear deprecated data again. */
win.screen = nullptr;
}
}
static void direct_link_wm_xr_data(BlendDataReader *reader, wmXrData *xr_data)
{
BKE_screen_view3d_shading_blend_read_data(reader, &xr_data->session_settings.shading);
}
static void window_manager_blend_read_data(BlendDataReader *reader, ID *id)
{
wmWindowManager *wm = id_cast<wmWindowManager *>(id);
id_us_ensure_real(&wm->id);
BLO_read_struct_list(reader, wmWindow, &wm->windows);
for (wmWindow &win : wm->windows) {
BLO_read_struct(reader, wmWindow, &win.parent);
WorkSpaceInstanceHook *hook = win.workspace_hook;
BLO_read_struct(reader, WorkSpaceInstanceHook, &win.workspace_hook);
/* This will be nullptr for any pre-2.80 blend file. */
if (win.workspace_hook != nullptr) {
/* We need to restore a pointer to this later when reading workspaces,
* so store in global oldnew-map.
* Note that this is only needed for versioning of older .blend files now. */
BLO_read_data_globmap_add(reader, hook, win.workspace_hook);
/* Cleanup pointers to data outside of this data-block scope. */
win.workspace_hook->act_layout = nullptr;
win.workspace_hook->temp_workspace_store = nullptr;
win.workspace_hook->temp_layout_store = nullptr;
}
BKE_screen_area_map_blend_read_data(reader, &win.global_areas);
win.active = 0;
win.cursor = 0;
win.lastcursor = 0;
win.modalcursor = 0;
win.grabcursor = 0;
win.addmousemove = true;
win.event_queue_check_click = 0;
win.event_queue_check_drag = 0;
win.event_queue_check_drag_handled = 0;
win.event_queue_consecutive_gesture_type = EVENT_NONE;
win.event_queue_consecutive_gesture_data = nullptr;
BLO_read_struct(reader, Stereo3dFormat, &win.stereo3d_format);
/* Multi-view always falls back to anaglyph at file opening
* otherwise quad-buffer saved files can break Blender. */
if (win.stereo3d_format && win.stereo3d_format->display_mode == S3D_DISPLAY_PAGEFLIP) {
win.stereo3d_format->display_mode = S3D_DISPLAY_ANAGLYPH;
}
win.runtime = MEM_new<bke::WindowRuntime>(__func__);
}
direct_link_wm_xr_data(reader, &wm->xr);
wm->xr.runtime = nullptr;
wm->init_flag = eWM_InitFlag{};
wm->op_undo_depth = 0;
wm->extensions_updates = WM_EXTENSIONS_UPDATE_UNSET;
wm->extensions_blocked = 0;
BLI_assert(wm->runtime == nullptr);
wm->runtime = MEM_new<bke::WindowManagerRuntime>(__func__);
}
static void window_manager_blend_read_after_liblink(BlendLibReader *reader, ID *id)
{
wmWindowManager *wm = reinterpret_cast<wmWindowManager *>(id);
for (wmWindow &win : wm->windows) {
for (ScrArea &area : win.global_areas.areabase) {
BKE_screen_area_blend_read_after_liblink(reader, id, &area);
}
}
}
IDTypeInfo IDType_ID_WM = {
.id_code = wmWindowManager::id_type,
.id_filter = FILTER_ID_WM,
.dependencies_id_types = FILTER_ID_SCE | FILTER_ID_WS,
.main_listbase_index = INDEX_ID_WM,
.struct_size = sizeof(wmWindowManager),
.name = "WindowManager",
.name_plural = N_("window_managers"),
.translation_context = BLT_I18NCONTEXT_ID_WINDOWMANAGER,
.flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_ANIMDATA |
IDTYPE_FLAGS_NO_MEMFILE_UNDO | IDTYPE_FLAGS_NEVER_UNUSED,
.asset_type_info = nullptr,
.init_data = nullptr,
.copy_data = nullptr,
.free_data = window_manager_free_data,
.make_local = nullptr,
.foreach_id = window_manager_foreach_id,
.foreach_cache = nullptr,
.foreach_path = nullptr,
.foreach_working_space_color = nullptr,
.owner_pointer_get = nullptr,
.blend_write = window_manager_blend_write,
.blend_read_data = window_manager_blend_read_data,
.blend_read_after_liblink = window_manager_blend_read_after_liblink,
.blend_read_undo_preserve = nullptr,
.lib_override_apply_post = nullptr,
};
#define MAX_OP_REGISTERED 32
void WM_operator_free(wmOperator *op)
{
#ifdef WITH_PYTHON
if (op->py_instance) {
/* Do this first in case there are any __del__ functions or similar that use properties. */
BPY_DECREF_RNA_INVALIDATE(op->py_instance);
}
#endif
if (op->ptr) {
op->properties = static_cast<IDProperty *>(op->ptr->data);
MEM_delete(op->ptr);
}
if (op->properties) {
IDP_FreeProperty(op->properties);
}
if (op->reports && (op->reports->flag & RPT_FREE)) {
BKE_reports_free(op->reports);
MEM_delete(op->reports);
}
if (op->macro.first) {
wmOperator *opm, *opmnext;
for (opm = static_cast<wmOperator *>(op->macro.first); opm; opm = opmnext) {
opmnext = opm->next;
WM_operator_free(opm);
}
}
MEM_delete(op);
}
void WM_operator_free_all_after(wmWindowManager *wm, wmOperator *op)
{
op = op->next;
while (op != nullptr) {
wmOperator *op_next = op->next;
BLI_remlink(&wm->runtime->operators, op);
WM_operator_free(op);
op = op_next;
}
}
void WM_operator_type_set(wmOperator *op, wmOperatorType *ot)
{
/* Not supported for Python. */
BLI_assert(op->py_instance == nullptr);
op->type = ot;
op->ptr->type = ot->srna;
/* Ensure compatible properties. */
if (op->properties) {
PointerRNA ptr = WM_operator_properties_create_ptr(ot);
WM_operator_properties_default(&ptr, false);
if (ptr.data) {
IDP_SyncGroupTypes(op->properties, static_cast<const IDProperty *>(ptr.data), true);
}
WM_operator_properties_free(&ptr);
}
}
static void wm_reports_free(wmWindowManager *wm)
{
WM_event_timer_remove(wm, nullptr, wm->runtime->reports.reporttimer);
}
void wm_operator_register(bContext *C, wmOperator *op)
{
wmWindowManager *wm = CTX_wm_manager(C);
int tot = 0;
BLI_addtail(&wm->runtime->operators, op);
/* Only count registered operators. */
while (op) {
wmOperator *op_prev = op->prev;
if (op->type->flag & OPTYPE_REGISTER) {
tot += 1;
}
if (tot > MAX_OP_REGISTERED) {
BLI_remlink(&wm->runtime->operators, op);
WM_operator_free(op);
}
op = op_prev;
}
/* So the console is redrawn. */
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_INFO_REPORT, nullptr);
WM_event_add_notifier(C, NC_WM | ND_HISTORY, nullptr);
}
void WM_operator_stack_clear(wmWindowManager *wm)
{
while (wmOperator *op = static_cast<wmOperator *>(BLI_pophead(&wm->runtime->operators))) {
WM_operator_free(op);
}
WM_main_add_notifier(NC_WM | ND_HISTORY, nullptr);
}
void WM_operator_stack_clear(wmWindowManager *wm, const Set<wmOperatorType *> &types)
{
bool any_removed = false;
for (wmOperator &op : wm->runtime->operators.items_mutable()) {
if (types.contains(op.type)) {
BLI_remlink(&wm->runtime->operators, &op);
WM_operator_free(&op);
any_removed = true;
}
}
if (any_removed) {
WM_main_add_notifier(NC_WM | ND_HISTORY, nullptr);
}
}
void WM_operator_handlers_clear(wmWindowManager *wm, const Set<wmOperatorType *> &types)
{
for (wmWindow &win : wm->windows) {
bScreen *screen = WM_window_get_active_screen(&win);
for (ScrArea &area : screen->areabase) {
switch (area.spacetype) {
case SPACE_FILE: {
SpaceFile *sfile = static_cast<SpaceFile *>(area.spacedata.first);
if (sfile->op && types.contains(sfile->op->type)) {
/* Freed as part of the handler. */
sfile->op = nullptr;
}
break;
}
}
}
}
for (wmWindow &win : wm->windows) {
ListBaseT<wmEventHandler> *lb[2] = {&win.runtime->handlers, &win.runtime->modalhandlers};
for (int i = 0; i < ARRAY_SIZE(lb); i++) {
for (wmEventHandler &handler_base : *lb[i]) {
if (handler_base.type == WM_HANDLER_TYPE_OP) {
wmEventHandler_Op *handler = reinterpret_cast<wmEventHandler_Op *>(&handler_base);
if (handler->op && types.contains(handler->op->type)) {
/* Don't run op->cancel because it needs the context,
* assume whoever unregisters the operator will cleanup. */
handler->head.flag |= WM_HANDLER_DO_FREE;
WM_operator_free(handler->op);
handler->op = nullptr;
}
}
}
}
}
}
void WM_operator_handlers_clear(wmWindowManager *wm, wmOperatorType *ot)
{
WM_operator_handlers_clear(wm, Set<wmOperatorType *>{ot});
}
/* ****************************************** */
void WM_keyconfig_reload(bContext *C)
{
if (CTX_py_init_get(C) && !G.background) {
#ifdef WITH_PYTHON
const char *imports[] = {"bpy", nullptr};
BPY_run_string_eval(C, imports, "bpy.utils.keyconfig_init()");
#endif
}
}
void WM_keyconfig_init(bContext *C)
{
wmWindowManager *wm = CTX_wm_manager(C);
/* Create standard key configuration. */
if (wm->runtime->defaultconf == nullptr) {
/* Keep lowercase to match the preset filename. */
wm->runtime->defaultconf = WM_keyconfig_new(wm, WM_KEYCONFIG_STR_DEFAULT, false);
}
if (wm->runtime->addonconf == nullptr) {
wm->runtime->addonconf = WM_keyconfig_new(wm, WM_KEYCONFIG_STR_DEFAULT " addon", false);
}
if (wm->runtime->userconf == nullptr) {
wm->runtime->userconf = WM_keyconfig_new(wm, WM_KEYCONFIG_STR_DEFAULT " user", false);
}
/* Initialize only after python init is done, for keymaps that use python operators. */
if (CTX_py_init_get(C) && (wm->init_flag & WM_INIT_FLAG_KEYCONFIG) == 0) {
/* Create default key config, only initialize once,
* it's persistent across sessions. */
if (!(wm->runtime->defaultconf->flag & KEYCONF_INIT_DEFAULT)) {
wm_window_keymap(wm->runtime->defaultconf);
ED_spacetypes_keymap(wm->runtime->defaultconf);
WM_keyconfig_reload(C);
wm->runtime->defaultconf->flag |= KEYCONF_INIT_DEFAULT;
}
/* Harmless, but no need to update in background mode. */
if (!G.background) {
WM_keyconfig_update_tag(nullptr, nullptr);
}
/* Don't call #WM_keyconfig_update here because add-ons have not yet been registered yet. */
wm->init_flag |= WM_INIT_FLAG_KEYCONFIG;
}
}
void WM_check(bContext *C)
{
Main *bmain = CTX_data_main(C);
wmWindowManager *wm = CTX_wm_manager(C);
/* WM context. */
if (wm == nullptr) {
wm = static_cast<wmWindowManager *>(bmain->wm.first);
CTX_wm_manager_set(C, wm);
}
if (wm == nullptr || wm->windows.is_empty()) {
return;
}
/* Run before loading the keyconfig. */
if (wm->runtime->message_bus == nullptr) {
wm->runtime->message_bus = WM_msgbus_create();
}
if (!G.background) {
/* Case: file-read. */
if ((wm->init_flag & WM_INIT_FLAG_WINDOW) == 0) {
WM_keyconfig_init(C);
WM_file_autosave_init(wm);
}
/* Case: no open windows at all, for old file reads. */
wm_window_ghostwindows_ensure(wm);
}
/* Case: file-read. */
/* NOTE: this runs in background mode to set the screen context cb. */
if ((wm->init_flag & WM_INIT_FLAG_WINDOW) == 0) {
ED_screens_init(C, bmain, wm);
wm->init_flag |= WM_INIT_FLAG_WINDOW;
}
}
void wm_clear_default_size(bContext *C)
{
wmWindowManager *wm = CTX_wm_manager(C);
/* WM context. */
if (wm == nullptr) {
wm = static_cast<wmWindowManager *>(CTX_data_main(C)->wm.first);
CTX_wm_manager_set(C, wm);
}
if (wm == nullptr || wm->windows.is_empty()) {
return;
}
for (wmWindow &win : wm->windows) {
win.sizex = 0;
win.sizey = 0;
win.posx = 0;
win.posy = 0;
}
}
void wm_add_default(Main *bmain, bContext *C)
{
wmWindowManager *wm = static_cast<wmWindowManager *>(
BKE_libblock_alloc(bmain, ID_WM, "WinMan", 0));
wmWindow *win;
bScreen *screen = CTX_wm_screen(C); /* XXX: from file read hrmf. */
WorkSpace *workspace;
WorkSpaceLayout *layout = BKE_workspace_layout_find_global(bmain, screen, &workspace);
CTX_wm_manager_set(C, wm);
win = wm_window_new(bmain, wm, nullptr, false);
win->scene = CTX_data_scene(C);
STRNCPY_UTF8(win->view_layer_name, CTX_data_view_layer(C)->name);
BKE_workspace_active_set(win->workspace_hook, workspace);
BKE_workspace_active_layout_set(win->workspace_hook, win->winid, workspace, layout);
screen->winid = win->winid;
wm->runtime = MEM_new<bke::WindowManagerRuntime>(__func__);
wm->runtime->winactive = win;
wm->file_saved = 1;
wm_window_make_drawable(wm, win);
}
static void wm_xr_data_free(wmWindowManager *wm)
{
/* NOTE: this also runs when built without `WITH_XR_OPENXR`.
* It's necessary to prevent leaks when XR data is created or loaded into non XR builds.
* This can occur when Python reads all properties (see the `bl_rna_paths` test). */
/* Note that non-runtime data in `wm->xr` is freed as part of freeing the window manager. */
if (wm->xr.session_settings.shading.prop) {
IDP_FreeProperty(wm->xr.session_settings.shading.prop);
wm->xr.session_settings.shading.prop = nullptr;
}
}
void wm_close_and_free(bContext *C, wmWindowManager *wm)
{
if (wm->autosavetimer) {
wm_autosave_timer_end(wm);
}
#ifdef WITH_XR_OPENXR
/* May send notifier, so do before freeing notifier queue. */
wm_xr_exit(wm);
#endif
wm_xr_data_free(wm);
while (wmWindow *win = static_cast<wmWindow *>(BLI_pophead(&wm->windows))) {
/* Prevent draw clear to use screen. */
BKE_workspace_active_set(win->workspace_hook, nullptr);
wm_window_free(C, wm, win);
}
#ifdef WITH_PYTHON
BPY_callback_wm_free(wm);
#endif
wm_reports_free(wm);
if (C && CTX_wm_manager(C) == wm) {
CTX_wm_manager_set(C, nullptr);
}
MEM_delete(wm->runtime);
}
void WM_main(bContext *C)
{
PRF_scope(ProfileCategory::Core);
/* Single refresh before handling events.
* This ensures we don't run operators before the depsgraph has been evaluated. */
wm_event_do_refresh_wm_and_depsgraph(C);
while (true) {
/* Get events from ghost, handle window events, add to window queues. */
wm_window_events_process(C);
/* Per window, all events to the window, screen, area and region handlers. */
wm_event_do_handlers(C);
/* Events have left notes about changes, we handle and cache it. */
wm_event_do_notifiers(C);
/* Execute cached changes draw. */
wm_draw_update(C);
PRF_frame_mark;
}
}
} // namespace blender

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,92 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#include "testing/testing.h"
/* #eFileSel_File_Types. */
#include "DNA_space_types.h"
#include "WM_api.hh"
#include "WM_types.hh"
namespace blender::tests {
TEST(wm_drag, wmDragPath)
{
{
/**
* NOTE: `WM_drag_create_path_data` gets the `file_type` from the first path in `paths` and
* only needs its extension, so there is no need to describe a full path here that can have a
* different format on Windows or Linux. However callers must ensure that they are valid paths.
*/
Vector<const char *> paths{"text_file.txt"};
wmDragPath *path_data = WM_drag_create_path_data(paths);
Vector<std::string> expected_file_paths{"text_file.txt"};
EXPECT_EQ(path_data->paths.size(), 1);
EXPECT_EQ(path_data->tooltip, "text_file.txt");
EXPECT_EQ(path_data->paths, expected_file_paths);
/** Test `wmDrag` path data getters. */
wmDrag drag;
drag.type = WM_DRAG_PATH;
drag.poin = path_data;
EXPECT_STREQ(WM_drag_get_single_path(&drag), "text_file.txt");
EXPECT_EQ(WM_drag_get_path_file_type(&drag), FILE_TYPE_TEXT);
EXPECT_EQ(WM_drag_get_paths(&drag), expected_file_paths.as_span());
EXPECT_STREQ(WM_drag_get_single_path(&drag, FILE_TYPE_TEXT), "text_file.txt");
EXPECT_EQ(WM_drag_get_single_path(&drag, FILE_TYPE_BLENDER), nullptr);
EXPECT_TRUE(
WM_drag_has_path_file_type(&drag, FILE_TYPE_BLENDER | FILE_TYPE_TEXT | FILE_TYPE_IMAGE));
EXPECT_FALSE(WM_drag_has_path_file_type(&drag, FILE_TYPE_BLENDER | FILE_TYPE_IMAGE));
MEM_delete(path_data);
}
{
Vector<const char *> paths = {"blender.blend", "text_file.txt", "image.png"};
wmDragPath *path_data = WM_drag_create_path_data(paths);
Vector<std::string> expected_file_paths = {"blender.blend", "text_file.txt", "image.png"};
EXPECT_EQ(path_data->paths.size(), 3);
EXPECT_EQ(path_data->tooltip, "Dragging 3 files");
EXPECT_EQ(path_data->paths, expected_file_paths);
/** Test `wmDrag` path data getters. */
wmDrag drag;
drag.type = WM_DRAG_PATH;
drag.poin = path_data;
EXPECT_STREQ(WM_drag_get_single_path(&drag), "blender.blend");
EXPECT_EQ(WM_drag_get_path_file_type(&drag), FILE_TYPE_BLENDER);
EXPECT_EQ(WM_drag_get_paths(&drag), expected_file_paths.as_span());
EXPECT_STREQ(WM_drag_get_single_path(&drag, FILE_TYPE_BLENDER), "blender.blend");
EXPECT_STREQ(WM_drag_get_single_path(&drag, FILE_TYPE_IMAGE), "image.png");
EXPECT_STREQ(WM_drag_get_single_path(&drag, FILE_TYPE_TEXT), "text_file.txt");
EXPECT_STREQ(
WM_drag_get_single_path(&drag, FILE_TYPE_BLENDER | FILE_TYPE_TEXT | FILE_TYPE_IMAGE),
"blender.blend");
EXPECT_STREQ(WM_drag_get_single_path(&drag, FILE_TYPE_TEXT | FILE_TYPE_IMAGE),
"text_file.txt");
EXPECT_EQ(WM_drag_get_single_path(&drag, FILE_TYPE_ASSET), nullptr);
EXPECT_TRUE(
WM_drag_has_path_file_type(&drag, FILE_TYPE_BLENDER | FILE_TYPE_TEXT | FILE_TYPE_IMAGE));
EXPECT_TRUE(WM_drag_has_path_file_type(&drag, FILE_TYPE_BLENDER | FILE_TYPE_IMAGE));
EXPECT_TRUE(WM_drag_has_path_file_type(&drag, FILE_TYPE_IMAGE));
EXPECT_FALSE(WM_drag_has_path_file_type(&drag, FILE_TYPE_ASSET));
MEM_delete(path_data);
}
{
/** Test `wmDrag` path data getters when the drag type is different to `WM_DRAG_PATH`. */
wmDrag drag;
drag.type = WM_DRAG_COLOR;
EXPECT_EQ(WM_drag_get_single_path(&drag), nullptr);
EXPECT_EQ(WM_drag_get_path_file_type(&drag), 0);
EXPECT_EQ(WM_drag_get_paths(&drag).size(), 0);
EXPECT_EQ(WM_drag_get_single_path(
&drag, FILE_TYPE_BLENDER | FILE_TYPE_IMAGE | FILE_TYPE_TEXT | FILE_TYPE_ASSET),
nullptr);
EXPECT_FALSE(WM_drag_has_path_file_type(
&drag, FILE_TYPE_BLENDER | FILE_TYPE_IMAGE | FILE_TYPE_TEXT | FILE_TYPE_ASSET));
}
}
} // namespace blender::tests

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,698 @@
/* SPDX-FileCopyrightText: 2007 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* Read-only queries utility functions for the event system.
*/
#include <cstdlib>
#include <cstring>
#include "DNA_screen_types.h"
#include "DNA_userdef_types.h"
#include "DNA_windowmanager_types.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
#include "RNA_access.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "wm_event_system.hh"
#include "wm_event_types.hh"
#include "RNA_enum_types.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Event Printing
* \{ */
struct FlagIdentifierPair {
const char *id;
uint flag;
};
static void event_ids_from_flag(char *str,
const int str_maxncpy,
const FlagIdentifierPair *flag_data,
const int flag_data_len,
const uint flag)
{
int ofs = 0;
ofs += BLI_strncpy_rlen(str + ofs, "{", str_maxncpy - ofs);
for (int i = 0; i < flag_data_len; i++) {
if (flag & flag_data[i].flag) {
if (ofs != 1) {
ofs += BLI_strncpy_rlen(str + ofs, "|", str_maxncpy - ofs);
}
ofs += BLI_strncpy_rlen(str + ofs, flag_data[i].id, str_maxncpy - ofs);
}
}
ofs += BLI_strncpy_rlen(str + ofs, "}", str_maxncpy - ofs);
UNUSED_VARS(ofs); /* Quiet warning. */
}
static void event_ids_from_type_and_value(const short type,
const short val,
const char **r_type_id,
const char **r_val_id)
{
/* Type. */
RNA_enum_identifier(rna_enum_event_type_items, type, r_type_id);
/* Value. */
RNA_enum_identifier(rna_enum_event_value_items, val, r_val_id);
}
void WM_event_print(const wmEvent *event)
{
if (event) {
const char *unknown = "UNKNOWN";
const char *type_id = unknown;
const char *val_id = unknown;
const char *prev_type_id = unknown;
const char *prev_val_id = unknown;
event_ids_from_type_and_value(event->type, event->val, &type_id, &val_id);
event_ids_from_type_and_value(event->prev_type, event->prev_val, &prev_type_id, &prev_val_id);
char modifier_id[128];
{
FlagIdentifierPair flag_data[] = {
{"SHIFT", KM_SHIFT},
{"CTRL", KM_CTRL},
{"ALT", KM_ALT},
{"OS", KM_OSKEY},
{"HYPER", KM_HYPER},
};
event_ids_from_flag(
modifier_id, sizeof(modifier_id), flag_data, ARRAY_SIZE(flag_data), event->modifier);
}
char flag_id[128];
{
FlagIdentifierPair flag_data[] = {
{"SCROLL_INVERT", WM_EVENT_SCROLL_INVERT},
{"IS_REPEAT", WM_EVENT_IS_REPEAT},
{"IS_CONSECUTIVE", WM_EVENT_IS_CONSECUTIVE},
{"FORCE_DRAG_THRESHOLD", WM_EVENT_FORCE_DRAG_THRESHOLD},
};
event_ids_from_flag(flag_id, sizeof(flag_id), flag_data, ARRAY_SIZE(flag_data), event->flag);
}
printf(
"wmEvent type:%d/%s, val:%d/%s, "
"prev_type:%d/%s, prev_val:%d/%s, "
"modifier=%s, keymodifier:%d, flag:%s, "
"mouse:(%d,%d), utf8:'%.*s', pointer:%p",
event->type,
type_id,
event->val,
val_id,
event->prev_type,
prev_type_id,
event->prev_val,
prev_val_id,
modifier_id,
event->keymodifier,
flag_id,
event->xy[0],
event->xy[1],
BLI_str_utf8_size_or_error(event->utf8_buf),
event->utf8_buf,
static_cast<const void *>(event));
#ifdef WITH_INPUT_NDOF
if (ISNDOF(event->type)) {
const wmNDOFMotionData &ndof = *static_cast<const wmNDOFMotionData *>(event->customdata);
if (event->type == NDOF_MOTION) {
const char *ndof_progress = unknown;
# define CASE_NDOF_PROGRESS(id) \
case P_##id: { \
ndof_progress = STRINGIFY(id); \
break; \
}
switch (ndof.progress) {
CASE_NDOF_PROGRESS(NOT_STARTED);
CASE_NDOF_PROGRESS(STARTING);
CASE_NDOF_PROGRESS(IN_PROGRESS);
CASE_NDOF_PROGRESS(FINISHING);
CASE_NDOF_PROGRESS(FINISHED);
}
# undef CASE_NDOF_PROGRESS
printf(
", ndof: "
"rot: (%.4f %.4f %.4f), "
"tx: (%.4f %.4f %.4f), "
"time_delta: %.4f, "
"progress: %s",
UNPACK3(ndof.rvec),
UNPACK3(ndof.tvec),
ndof.time_delta,
ndof_progress);
}
else {
/* NDOF buttons printed already. */
}
}
#endif /* WITH_INPUT_NDOF */
if (event->tablet.active != EVT_TABLET_NONE) {
const wmTabletData *wmtab = &event->tablet;
printf(", tablet: active: %d, pressure %.4f, tilt: (%.4f %.4f)",
wmtab->active,
wmtab->pressure,
wmtab->tilt.x,
wmtab->tilt.y);
}
printf("\n");
}
else {
printf("wmEvent - nullptr\n");
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Event Modifier/Type Queries
* \{ */
bool WM_event_type_mask_test(const int event_type, const enum eEventType_Mask mask)
{
/* Keyboard. */
if (mask & EVT_TYPE_MASK_KEYBOARD) {
if (ISKEYBOARD(event_type)) {
return true;
}
}
else if (mask & EVT_TYPE_MASK_KEYBOARD_MODIFIER) {
if (ISKEYMODIFIER(event_type)) {
return true;
}
}
/* Mouse. */
if (mask & EVT_TYPE_MASK_MOUSE) {
if (ISMOUSE(event_type)) {
return true;
}
}
else if (mask & EVT_TYPE_MASK_MOUSE_WHEEL) {
if (ISMOUSE_WHEEL(event_type)) {
return true;
}
}
else if (mask & EVT_TYPE_MASK_MOUSE_GESTURE) {
if (ISMOUSE_GESTURE(event_type)) {
return true;
}
}
/* NDOF. */
if (mask & EVT_TYPE_MASK_NDOF) {
if (ISNDOF(event_type)) {
return true;
}
}
/* Action Zone. */
if (mask & EVT_TYPE_MASK_ACTIONZONE) {
if (IS_EVENT_ACTIONZONE(event_type)) {
return true;
}
}
return false;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Event Motion Queries
* \{ */
bool WM_event_is_modal_drag_exit(const wmEvent *event,
const short init_event_type,
const short init_event_val)
{
/* If the release-confirm preference setting is enabled,
* drag events can be canceled when mouse is released. */
if (U.flag & USER_RELEASECONFIRM) {
/* Option on, so can exit with km-release. */
if (event->val == KM_RELEASE) {
if ((init_event_val == KM_PRESS_DRAG) && (event->type == init_event_type)) {
return true;
}
}
else {
/* If the initial event wasn't a drag event then
* ignore #USER_RELEASECONFIRM setting: see #26756. */
if (init_event_val != KM_PRESS_DRAG) {
return true;
}
}
}
else {
/* This is fine as long as not doing km-release, otherwise some items (i.e. markers)
* being tweaked may end up getting dropped all over. */
if (event->val != KM_RELEASE) {
return true;
}
}
return false;
}
bool WM_event_is_mouse_drag(const wmEvent *event)
{
return (ISMOUSE_BUTTON(event->type) && (event->val == KM_PRESS_DRAG));
}
bool WM_event_is_mouse_drag_or_press(const wmEvent *event)
{
return WM_event_is_mouse_drag(event) ||
(ISMOUSE_BUTTON(event->type) && (event->val == KM_PRESS));
}
int WM_event_drag_direction(const wmEvent *event)
{
const int delta[2] = {
event->xy[0] - event->prev_press_xy[0],
event->xy[1] - event->prev_press_xy[1],
};
int theta = round_fl_to_int(4.0f * atan2f(float(delta[1]), float(delta[0])) / float(M_PI));
int val = KM_DIRECTION_W;
if (theta == 0) {
val = KM_DIRECTION_E;
}
else if (theta == 1) {
val = KM_DIRECTION_NE;
}
else if (theta == 2) {
val = KM_DIRECTION_N;
}
else if (theta == 3) {
val = KM_DIRECTION_NW;
}
else if (theta == -1) {
val = KM_DIRECTION_SE;
}
else if (theta == -2) {
val = KM_DIRECTION_S;
}
else if (theta == -3) {
val = KM_DIRECTION_SW;
}
#if 0
/* Debug. */
if (val == 1) {
printf("tweak north\n");
}
if (val == 2) {
printf("tweak north-east\n");
}
if (val == 3) {
printf("tweak east\n");
}
if (val == 4) {
printf("tweak south-east\n");
}
if (val == 5) {
printf("tweak south\n");
}
if (val == 6) {
printf("tweak south-west\n");
}
if (val == 7) {
printf("tweak west\n");
}
if (val == 8) {
printf("tweak north-west\n");
}
#endif
return val;
}
bool WM_cursor_test_motion_and_update(const int mval[2])
{
static int mval_prev[2] = {-1, -1};
bool use_cycle = (len_manhattan_v2v2_int(mval, mval_prev) <= WM_EVENT_CURSOR_MOTION_THRESHOLD);
copy_v2_v2_int(mval_prev, mval);
return !use_cycle;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Event Consecutive Checks
* \{ */
bool WM_event_consecutive_gesture_test(const wmEvent *event)
{
return ISMOUSE_GESTURE(event->type) || (event->type == NDOF_MOTION);
}
bool WM_event_consecutive_gesture_test_break(const wmWindow *win, const wmEvent *event)
{
/* Cursor motion breaks the chain. */
if (ISMOUSE_MOTION(event->type)) {
/* Mouse motion is checked because the user may navigate to a new area
* and perform the same gesture - logically it's best to view this as two separate gestures. */
if (len_manhattan_v2v2_int(event->xy, win->event_queue_consecutive_gesture_xy) >
WM_EVENT_CURSOR_MOTION_THRESHOLD)
{
return true;
}
}
else if (ISKEYBOARD_OR_BUTTON(event->type)) {
/* Modifiers are excluded because from a user perspective.
* For example, releasing a modifier should not begin a new action. */
if (!ISKEYMODIFIER(event->type)) {
return true;
}
}
else if (event->type == WINDEACTIVATE) {
return true;
}
return false;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Event Click/Drag Checks
*
* Values under this limit are detected as clicks.
*
* \{ */
int WM_event_drag_threshold(const wmEvent *event)
{
int drag_threshold;
BLI_assert(event->prev_press_type != MOUSEMOVE);
if (ISMOUSE_BUTTON(event->prev_press_type)) {
/* Using the previous type is important is we want to check the last pressed/released button,
* The `event->type` would include #MOUSEMOVE which is always the case when dragging
* and does not help us know which threshold to use. */
if (WM_event_is_tablet(event)) {
drag_threshold = U.drag_threshold_tablet;
}
else {
drag_threshold = U.drag_threshold_mouse;
}
}
else {
/* Typically keyboard, could be NDOF button or other less common types. */
drag_threshold = U.drag_threshold;
}
return drag_threshold * UI_SCALE_FAC;
}
bool WM_event_drag_test_with_delta(const wmEvent *event, const int drag_delta[2])
{
const int drag_threshold = WM_event_drag_threshold(event);
return abs(drag_delta[0]) > drag_threshold || abs(drag_delta[1]) > drag_threshold;
}
bool WM_event_drag_test(const wmEvent *event, const int prev_xy[2])
{
int drag_delta[2];
sub_v2_v2v2_int(drag_delta, prev_xy, event->xy);
return WM_event_drag_test_with_delta(event, drag_delta);
}
void WM_event_drag_start_mval(const wmEvent *event, const ARegion *region, int r_mval[2])
{
const int *xy = (event->val == KM_PRESS_DRAG) ? event->prev_press_xy : event->xy;
r_mval[0] = xy[0] - region->winrct.xmin;
r_mval[1] = xy[1] - region->winrct.ymin;
}
void WM_event_drag_start_mval_fl(const wmEvent *event, const ARegion *region, float r_mval[2])
{
const int *xy = (event->val == KM_PRESS_DRAG) ? event->prev_press_xy : event->xy;
r_mval[0] = xy[0] - region->winrct.xmin;
r_mval[1] = xy[1] - region->winrct.ymin;
}
void WM_event_drag_start_xy(const wmEvent *event, int r_xy[2])
{
copy_v2_v2_int(r_xy, (event->val == KM_PRESS_DRAG) ? event->prev_press_xy : event->xy);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Event Text Queries
* \{ */
char WM_event_utf8_to_ascii(const wmEvent *event)
{
if (BLI_str_utf8_size_or_error(event->utf8_buf) == 1) {
return event->utf8_buf[0];
}
return '\0';
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Event Preference Mapping
* \{ */
int WM_userdef_event_map(int kmitype)
{
switch (kmitype) {
case WHEELOUTMOUSE:
return (U.uiflag & USER_WHEELZOOMDIR) ? WHEELUPMOUSE : WHEELDOWNMOUSE;
case WHEELINMOUSE:
return (U.uiflag & USER_WHEELZOOMDIR) ? WHEELDOWNMOUSE : WHEELUPMOUSE;
}
return kmitype;
}
int WM_userdef_event_type_from_keymap_type(int kmitype)
{
switch (kmitype) {
case WHEELOUTMOUSE:
return (U.uiflag & USER_WHEELZOOMDIR) ? WHEELUPMOUSE : WHEELDOWNMOUSE;
case WHEELINMOUSE:
return (U.uiflag & USER_WHEELZOOMDIR) ? WHEELDOWNMOUSE : WHEELUPMOUSE;
}
return kmitype;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Event NDOF Input Access
* \{ */
#ifdef WITH_INPUT_NDOF
static float3 event_ndof_translation_get_with_sign(const wmNDOFMotionData &ndof, const float sign)
{
int ndof_flag = U.ndof_flag;
int x = 0, y = 1, z = 2;
if (ndof_flag & NDOF_SWAP_YZ_AXIS) {
/* Map `{x, y, z}` -> `{x, -z, y}`. */
std::swap(y, z);
ndof_flag ^= NDOF_PANY_INVERT_AXIS;
}
return {
ndof.tvec[x] * ((ndof_flag & NDOF_PANX_INVERT_AXIS) ? -sign : sign),
ndof.tvec[y] * ((ndof_flag & NDOF_PANY_INVERT_AXIS) ? -sign : sign),
ndof.tvec[z] * ((ndof_flag & NDOF_PANZ_INVERT_AXIS) ? -sign : sign),
};
}
static float3 event_ndof_rotation_get_with_sign(const wmNDOFMotionData &ndof, const float sign)
{
int ndof_flag = U.ndof_flag;
int x = 0, y = 1, z = 2;
if (ndof_flag & NDOF_SWAP_YZ_AXIS) {
/* Map `{x, y, z}` -> `{x, -z, y}`. */
std::swap(y, z);
ndof_flag ^= NDOF_ROTY_INVERT_AXIS;
}
return {
ndof.rvec[x] * ((ndof_flag & NDOF_ROTX_INVERT_AXIS) ? -sign : sign),
ndof.rvec[y] * ((ndof_flag & NDOF_ROTY_INVERT_AXIS) ? -sign : sign),
ndof.rvec[z] * ((ndof_flag & NDOF_ROTZ_INVERT_AXIS) ? -sign : sign),
};
}
float3 WM_event_ndof_translation_get_for_navigation(const wmNDOFMotionData &ndof)
{
const float sign = (U.ndof_navigation_mode == NDOF_NAVIGATION_MODE_OBJECT) ? -1.0f : 1.0f;
return event_ndof_translation_get_with_sign(ndof, sign);
}
float3 WM_event_ndof_rotation_get_for_navigation(const wmNDOFMotionData &ndof)
{
const float sign = (U.ndof_navigation_mode == NDOF_NAVIGATION_MODE_OBJECT) ? -1.0f : 1.0f;
return event_ndof_rotation_get_with_sign(ndof, sign);
}
float3 WM_event_ndof_translation_get(const wmNDOFMotionData &ndof)
{
return event_ndof_translation_get_with_sign(ndof, 1.0f);
}
float3 WM_event_ndof_rotation_get(const wmNDOFMotionData &ndof)
{
return event_ndof_rotation_get_with_sign(ndof, 1.0f);
}
float WM_event_ndof_rotation_get_axis_angle_for_navigation(const wmNDOFMotionData &ndof,
float axis[3])
{
const float3 rvec = WM_event_ndof_rotation_get_for_navigation(ndof);
return normalize_v3_v3(axis, rvec);
}
float WM_event_ndof_rotation_get_axis_angle(const wmNDOFMotionData &ndof, float axis[3])
{
const float3 rvec = WM_event_ndof_rotation_get(ndof);
return normalize_v3_v3(axis, rvec);
}
bool WM_event_ndof_translation_has_pan(const wmNDOFMotionData &ndof)
{
return (U.ndof_flag & NDOF_SWAP_YZ_AXIS) ? ((ndof.tvec[0] != 0.0f) || (ndof.tvec[2] != 0.0f)) :
((ndof.tvec[0] != 0.0f) || (ndof.tvec[1] != 0.0f));
}
bool WM_event_ndof_translation_has_zoom(const wmNDOFMotionData &ndof)
{
return ndof.tvec[(U.ndof_flag & NDOF_SWAP_YZ_AXIS) ? 1 : 2] != 0.0f;
}
#endif /* WITH_INPUT_NDOF */
/** \} */
/* -------------------------------------------------------------------- */
/** \name Event XR Input Access
* \{ */
#ifdef WITH_XR_OPENXR
bool WM_event_is_xr(const wmEvent *event)
{
return (event->type == EVT_XR_ACTION && event->custom == EVT_DATA_XR);
}
#endif
/** \} */
/* -------------------------------------------------------------------- */
/** \name Event Tablet Input Access
* \{ */
float wm_pressure_curve(float raw_pressure)
{
if (U.pressure_threshold_max != 0.0f) {
raw_pressure /= U.pressure_threshold_max;
}
CLAMP(raw_pressure, 0.0f, 1.0f);
if (U.pressure_softness != 0.0f) {
raw_pressure = powf(raw_pressure, powf(4.0f, -U.pressure_softness));
}
return raw_pressure;
}
float WM_event_tablet_data(const wmEvent *event, bool *r_pen_flip, float r_tilt[2])
{
if (r_tilt) {
copy_v2_v2(r_tilt, event->tablet.tilt);
}
if (r_pen_flip) {
(*r_pen_flip) = (event->tablet.active == EVT_TABLET_ERASER);
}
return event->tablet.pressure;
}
bool WM_event_is_tablet(const wmEvent *event)
{
return (event->tablet.active != EVT_TABLET_NONE);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Event Scroll's Absolute Deltas
*
* User may change the scroll behavior, and the deltas are automatically inverted.
* These functions return the absolute direction, swipe up/right gives positive values.
*
* \{ */
int WM_event_absolute_delta_x(const wmEvent *event)
{
int dx = event->xy[0] - event->prev_xy[0];
if ((event->flag & WM_EVENT_SCROLL_INVERT) == 0) {
dx = -dx;
}
return dx;
}
int WM_event_absolute_delta_y(const wmEvent *event)
{
int dy = event->xy[1] - event->prev_xy[1];
if ((event->flag & WM_EVENT_SCROLL_INVERT) == 0) {
dy = -dy;
}
return dy;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Event IME Input Access
* \{ */
#ifdef WITH_INPUT_IME
bool WM_event_is_ime_switch(const wmEvent *event)
{
/* Most OS's use `Ctrl+Space` / `OsKey+Space` to switch IME,
* so don't type in the space character.
*
* NOTE: Shift is excluded from this check since it prevented typing `Shift+Space`, see: #85517.
*/
return (event->val == KM_PRESS) && (event->type == EVT_SPACEKEY) &&
(event->modifier & (KM_CTRL | KM_OSKEY | KM_ALT));
}
#endif
/** \} */
} // namespace blender

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,215 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* Functions for handling file colorspaces.
*/
#include "BLI_colorspace.hh"
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BKE_context.hh"
#include "BKE_image.hh"
#include "BKE_main.hh"
#include "BKE_movieclip.hh"
#include "BKE_report.hh"
#include "DNA_windowmanager_enums.h"
#include "DNA_windowmanager_types.h"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_enum_types.hh"
#include "IMB_colormanagement.hh"
#include "DEG_depsgraph.hh"
#include "UI_interface_c.hh"
#include "BLT_translation.hh"
#include "ED_image.hh"
#include "ED_render.hh"
#include "RE_pipeline.h"
#include "SEQ_prefetch.hh"
#include "SEQ_relations.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "wm_files.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Set Working Color Space Operator
* \{ */
static const EnumPropertyItem *working_space_itemf(bContext * /*C*/,
PointerRNA * /*ptr*/,
PropertyRNA * /*prop*/,
bool *r_free)
{
EnumPropertyItem *item = nullptr;
int totitem = 0;
IMB_colormanagement_working_space_items_add(&item, &totitem);
RNA_enum_item_end(&item, &totitem);
*r_free = true;
return item;
}
static bool wm_set_working_space_check_safe(bContext *C, wmOperator *op)
{
const wmWindowManager *wm = CTX_wm_manager(C);
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
if (WM_jobs_test(wm, scene, WM_JOB_TYPE_ANY)) {
BKE_report(
op->reports, RPT_WARNING, RPT_("Can't change working space while jobs are running"));
return false;
}
if (ED_image_should_save_modified(bmain)) {
BKE_report(op->reports,
RPT_WARNING,
RPT_("Can't change working space with modified images, save them first"));
return false;
}
return true;
}
static wmOperatorStatus wm_set_working_color_space_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
const bool convert_colors = RNA_boolean_get(op->ptr, "convert_colors");
const int working_space_index = RNA_enum_get(op->ptr, "working_space");
const char *working_space = IMB_colormanagement_working_space_get_indexed_name(
working_space_index);
if (!wm_set_working_space_check_safe(C, op)) {
return OPERATOR_CANCELLED;
}
if (working_space[0] == '\0' || STREQ(working_space, bmain->colorspace.scene_linear_name)) {
return OPERATOR_CANCELLED;
}
/* Stop all viewport renders. */
ED_render_engine_changed(bmain, true);
RE_FreeAllPersistentData();
/* Change working space. */
IMB_colormanagement_working_space_set_from_name(working_space);
if (convert_colors) {
const bool depsgraph_tag = true;
IMB_colormanagement_working_space_convert(bmain,
bmain->colorspace.scene_linear_to_xyz,
colorspace::xyz_to_scene_linear,
depsgraph_tag);
}
STRNCPY(bmain->colorspace.scene_linear_name, working_space);
bmain->colorspace.scene_linear_to_xyz = colorspace::scene_linear_to_xyz;
/* Free all render, compositor and sequencer caches. */
RE_FreeAllRenderResults();
RE_FreeInteractiveCompositorRenders();
seq::prefetch_stop_all();
for (Scene &scene : bmain->scenes) {
seq::cache_cleanup(&scene, seq::CacheCleanup::All);
}
/* Free all images, they may have scene linear float buffers. */
for (Image &image : bmain->images) {
DEG_id_tag_update(&image.id, ID_RECALC_SOURCE);
BKE_image_signal(bmain, &image, nullptr, IMA_SIGNAL_COLORMANAGE);
BKE_image_partial_update_mark_full_update(&image);
}
for (MovieClip &clip : bmain->movieclips) {
BKE_movieclip_clear_cache(&clip);
BKE_movieclip_free_gputexture(&clip);
DEG_id_tag_update(&clip.id, ID_RECALC_SOURCE);
}
/* Redraw everything. */
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, nullptr);
WM_main_add_notifier(NC_SCENE | ND_RENDER_OPTIONS, nullptr);
WM_main_add_notifier(NC_SCENE | ND_NODES, nullptr);
WM_main_add_notifier(NC_WINDOW, nullptr);
return OPERATOR_FINISHED;
}
static wmOperatorStatus wm_set_working_color_space_invoke(bContext *C,
wmOperator *op,
const wmEvent *event)
{
if (!wm_set_working_space_check_safe(C, op)) {
return OPERATOR_CANCELLED;
}
if (RNA_enum_get(op->ptr, "working_space") == -1) {
RNA_enum_set(op->ptr,
"working_space",
IMB_colormanagement_working_space_get_named_index(
IMB_colormanagement_working_space_get_default()));
}
const Main *bmain = CTX_data_main(C);
const char *working_space = IMB_colormanagement_working_space_get_indexed_name(
RNA_enum_get(op->ptr, "working_space"));
if (STREQ(working_space, bmain->colorspace.scene_linear_name)) {
return OPERATOR_CANCELLED;
}
return WM_operator_props_popup_confirm_ex(
C,
op,
event,
std::nullopt,
IFACE_("Apply"),
false,
IFACE_("To match renders with the previous working space as closely as possible,\n"
"colors in all materials, lights and geometry must be converted.\n\n"
"Some nodes graphs cannot be converted accurately and may need manual fix-ups."));
}
void WM_OT_set_working_color_space(wmOperatorType *ot)
{
ot->name = "Set Blend File Working Color Space";
ot->idname = "WM_OT_set_working_color_space";
ot->description = "Change the working color space of all colors in this blend file";
ot->exec = wm_set_working_color_space_exec;
ot->invoke = wm_set_working_color_space_invoke;
ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
RNA_def_boolean(ot->srna,
"convert_colors",
true,
"Convert Colors in All Data-blocks",
"Change colors in all data-blocks to the new working space");
PropertyRNA *prop = RNA_def_enum(ot->srna,
"working_space",
rna_enum_dummy_NULL_items,
-1,
"Working Space",
"Color space to set");
RNA_def_enum_funcs(prop, working_space_itemf);
ot->prop = prop;
}
/** \} */
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,620 @@
/* SPDX-FileCopyrightText: 2008 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* Gestures (cursor motions) creating, evaluating and drawing, shared between operators.
*/
#include "DNA_screen_types.h"
#include "DNA_userdef_types.h"
#include "DNA_vec_types.h"
#include "DNA_windowmanager_types.h"
#include "MEM_guardedalloc.h"
#include "BLI_bitmap_draw_2d.h"
#include "BLI_lasso_2d.hh"
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_rect.h"
#include "BLI_utildefines.h"
#include "WM_api.hh"
#include "WM_types.hh"
#include "wm.hh"
#include "GPU_immediate.hh"
#include "GPU_immediate_util.hh"
#include "GPU_state.hh"
#include "BIF_glutil.hh"
namespace blender {
wmGesture *WM_gesture_new(wmWindow *window, const ARegion *region, const wmEvent *event, int type)
{
wmGesture *gesture = MEM_new<wmGesture>("new gesture");
BLI_addtail(&window->runtime->gesture, gesture);
gesture->type = type;
gesture->event_type = event->type;
gesture->event_modifier = event->modifier;
gesture->event_keymodifier = event->keymodifier;
gesture->winrct = region->winrct;
gesture->user_data.use_free = true; /* Free if user-data is set. */
gesture->modal_state = GESTURE_MODAL_NOP;
gesture->move = false;
int xy[2];
WM_event_drag_start_xy(event, xy);
if (ELEM(type,
WM_GESTURE_RECT,
WM_GESTURE_CROSS_RECT,
WM_GESTURE_CIRCLE,
WM_GESTURE_STRAIGHTLINE))
{
rcti *rect = MEM_new_zeroed<rcti>("gesture rect new");
gesture->customdata = rect;
rect->xmin = xy[0] - gesture->winrct.xmin;
rect->ymin = xy[1] - gesture->winrct.ymin;
if (type == WM_GESTURE_CIRCLE) {
/* Caller is responsible for initializing 'xmax' to radius. */
}
else {
rect->xmax = xy[0] - gesture->winrct.xmin;
rect->ymax = xy[1] - gesture->winrct.ymin;
}
}
else if (ELEM(type, WM_GESTURE_LINES, WM_GESTURE_LASSO)) {
float *lasso;
gesture->points_alloc = 1024;
gesture->customdata = lasso = MEM_new_array_uninitialized<float>(
size_t(2 * gesture->points_alloc), "lasso points");
lasso[0] = xy[0] - gesture->winrct.xmin;
lasso[1] = xy[1] - gesture->winrct.ymin;
gesture->points = 1;
}
else if (ELEM(type, WM_GESTURE_POLYLINE)) {
gesture->points_alloc = 64;
short *border = MEM_new_array_uninitialized<short>(size_t(2 * gesture->points_alloc),
"polyline points");
gesture->customdata = border;
border[0] = xy[0] - gesture->winrct.xmin;
border[1] = xy[1] - gesture->winrct.ymin;
gesture->mval.x = border[0];
gesture->mval.y = border[1];
gesture->points = 1;
}
return gesture;
}
void WM_gesture_end(wmWindow *win, wmGesture *gesture)
{
BLI_remlink(&win->runtime->gesture, gesture);
MEM_delete_void(gesture->customdata);
WM_generic_user_data_free(&gesture->user_data);
MEM_delete(gesture);
}
void WM_gestures_free_all(wmWindow *win)
{
while (win->runtime->gesture.first) {
WM_gesture_end(win, static_cast<wmGesture *>(win->runtime->gesture.first));
}
}
void WM_gestures_remove(wmWindow *win)
{
while (win->runtime->gesture.first) {
WM_gesture_end(win, static_cast<wmGesture *>(win->runtime->gesture.first));
}
}
bool WM_gesture_is_modal_first(const wmGesture *gesture)
{
if (gesture == nullptr) {
return true;
}
return (gesture->is_active_prev == false);
}
/* ******************* gesture draw ******************* */
static void wm_gesture_draw_line_active_side(const rcti *rect, const bool flip)
{
GPUVertFormat *format = immVertexFormat();
uint shdr_pos = GPU_vertformat_attr_add(format, "pos", gpu::VertAttrType::SFLOAT_32_32);
uint shdr_col = GPU_vertformat_attr_add(format, "color", gpu::VertAttrType::SFLOAT_32_32_32_32);
GPU_blend(GPU_BLEND_ALPHA);
immBindBuiltinProgram(GPU_SHADER_3D_SMOOTH_COLOR);
const float gradient_length = 150.0f * UI_SCALE_FAC;
float line_dir[2];
float gradient_dir[2];
float gradient_point[2][2];
const float line_start[2] = {float(rect->xmin), float(rect->ymin)};
const float line_end[2] = {float(rect->xmax), float(rect->ymax)};
const float color_line_gradient_start[4] = {0.2f, 0.2f, 0.2f, 0.4f};
const float color_line_gradient_end[4] = {0.0f, 0.0f, 0.0f, 0.0f};
sub_v2_v2v2(line_dir, line_end, line_start);
normalize_v2(line_dir);
ortho_v2_v2(gradient_dir, line_dir);
if (!flip) {
mul_v2_fl(gradient_dir, -1.0f);
}
mul_v2_fl(gradient_dir, gradient_length);
add_v2_v2v2(gradient_point[0], line_start, gradient_dir);
add_v2_v2v2(gradient_point[1], line_end, gradient_dir);
immBegin(GPU_PRIM_TRIS, 6);
immAttr4f(shdr_col, UNPACK4(color_line_gradient_start));
immVertex2f(shdr_pos, line_start[0], line_start[1]);
immAttr4f(shdr_col, UNPACK4(color_line_gradient_start));
immVertex2f(shdr_pos, line_end[0], line_end[1]);
immAttr4f(shdr_col, UNPACK4(color_line_gradient_end));
immVertex2f(shdr_pos, gradient_point[1][0], gradient_point[1][1]);
immAttr4f(shdr_col, UNPACK4(color_line_gradient_start));
immVertex2f(shdr_pos, line_start[0], line_start[1]);
immAttr4f(shdr_col, UNPACK4(color_line_gradient_end));
immVertex2f(shdr_pos, gradient_point[1][0], gradient_point[1][1]);
immAttr4f(shdr_col, UNPACK4(color_line_gradient_end));
immVertex2f(shdr_pos, gradient_point[0][0], gradient_point[0][1]);
immEnd();
immUnbindProgram();
GPU_blend(GPU_BLEND_NONE);
}
static void wm_gesture_draw_line(wmGesture *gt)
{
const rcti *rect = static_cast<rcti *>(gt->customdata);
if (gt->draw_active_side) {
wm_gesture_draw_line_active_side(rect, gt->use_flip);
}
uint shdr_pos = GPU_vertformat_attr_add(
immVertexFormat(), "pos", gpu::VertAttrType::SFLOAT_32_32);
immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR);
float viewport_size[4];
GPU_viewport_size_get_f(viewport_size);
immUniform2f("viewport_size", viewport_size[2], viewport_size[3]);
immUniform1i("colors_len", 2); /* "advanced" mode. */
immUniform4f("color", 0.4f, 0.4f, 0.4f, 1.0f);
immUniform4f("color2", 1.0f, 1.0f, 1.0f, 1.0f);
immUniform1f("dash_width", 8.0f);
immUniform1f("udash_factor", 0.5f);
float xmin = float(rect->xmin);
float ymin = float(rect->ymin);
immBegin(GPU_PRIM_LINES, 2);
immVertex2f(shdr_pos, xmin, ymin);
immVertex2f(shdr_pos, float(rect->xmax), float(rect->ymax));
immEnd();
immUnbindProgram();
}
static void wm_gesture_draw_rect(wmGesture *gt)
{
const rcti *rect = static_cast<const rcti *>(gt->customdata);
uint shdr_pos = GPU_vertformat_attr_add(
immVertexFormat(), "pos", gpu::VertAttrType::SFLOAT_32_32);
GPU_blend(GPU_BLEND_ALPHA);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
immUniformColor4f(1.0f, 1.0f, 1.0f, 0.05f);
immRectf(shdr_pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax);
immUnbindProgram();
GPU_blend(GPU_BLEND_NONE);
shdr_pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", gpu::VertAttrType::SFLOAT_32_32);
immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR);
float viewport_size[4];
GPU_viewport_size_get_f(viewport_size);
immUniform2f("viewport_size", viewport_size[2], viewport_size[3]);
immUniform1i("colors_len", 2); /* "advanced" mode. */
immUniform4f("color", 0.4f, 0.4f, 0.4f, 1.0f);
immUniform4f("color2", 1.0f, 1.0f, 1.0f, 1.0f);
immUniform1f("dash_width", 8.0f);
immUniform1f("udash_factor", 0.5f);
imm_draw_box_wire_2d(
shdr_pos, float(rect->xmin), float(rect->ymin), float(rect->xmax), float(rect->ymax));
immUnbindProgram();
/* Draws a diagonal line in the lined box to test #wm_gesture_draw_line. */
// wm_gesture_draw_line(gt);
}
static void wm_gesture_draw_circle(wmGesture *gt)
{
const rcti *rect = static_cast<const rcti *>(gt->customdata);
GPU_blend(GPU_BLEND_ALPHA);
const uint shdr_pos = GPU_vertformat_attr_add(
immVertexFormat(), "pos", gpu::VertAttrType::SFLOAT_32_32);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
immUniformColor4f(1.0f, 1.0f, 1.0f, 0.05f);
imm_draw_circle_fill_2d(shdr_pos, float(rect->xmin), float(rect->ymin), float(rect->xmax), 40);
immUnbindProgram();
GPU_blend(GPU_BLEND_NONE);
immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR);
float viewport_size[4];
GPU_viewport_size_get_f(viewport_size);
immUniform2f("viewport_size", viewport_size[2], viewport_size[3]);
immUniform1i("colors_len", 2); /* "advanced" mode. */
immUniform4f("color", 0.4f, 0.4f, 0.4f, 1.0f);
immUniform4f("color2", 1.0f, 1.0f, 1.0f, 1.0f);
immUniform1f("dash_width", 4.0f);
immUniform1f("udash_factor", 0.5f);
imm_draw_circle_wire_2d(shdr_pos, float(rect->xmin), float(rect->ymin), float(rect->xmax), 40);
immUnbindProgram();
}
struct LassoFillData {
uchar *px;
int width;
};
static void draw_filled_lasso_px_cb(int x, int x_end, int y, void *user_data)
{
LassoFillData *data = static_cast<LassoFillData *>(user_data);
uchar *col = &(data->px[(y * data->width) + x]);
memset(col, 0x10, x_end - x);
}
static void draw_filled_lasso(wmGesture *gt, const int2 *lasso_pt_extra)
{
const int mcoords_len = gt->points + (lasso_pt_extra ? 1 : 0);
Array<int2> mcoords(mcoords_len);
int i;
rcti rect;
const float red[4] = {1.0f, 0.0f, 0.0f, 0.0f};
if (gt->type == WM_GESTURE_POLYLINE) {
const short *lasso = static_cast<const short *>(gt->customdata);
for (i = 0; i < mcoords_len; i++, lasso += 2) {
mcoords[i][0] = lasso[0];
mcoords[i][1] = lasso[1];
}
}
else {
const float *lasso = static_cast<const float *>(gt->customdata);
for (i = 0; i < mcoords_len; i++, lasso += 2) {
mcoords[i][0] = lasso[0];
mcoords[i][1] = lasso[1];
}
}
if (lasso_pt_extra) {
mcoords[mcoords_len - 1][0] = lasso_pt_extra->x;
mcoords[mcoords_len - 1][1] = lasso_pt_extra->y;
}
BLI_lasso_boundbox(&rect, mcoords);
BLI_rcti_translate(&rect, gt->winrct.xmin, gt->winrct.ymin);
BLI_rcti_isect(&gt->winrct, &rect, &rect);
BLI_rcti_translate(&rect, -gt->winrct.xmin, -gt->winrct.ymin);
/* Highly unlikely this will fail, but could crash if (mcoords_len == 0). */
if (BLI_rcti_is_empty(&rect) == false) {
const int w = BLI_rcti_size_x(&rect);
const int h = BLI_rcti_size_y(&rect);
uchar *pixel_buf = MEM_new_array_zeroed<uchar>(size_t(w) * size_t(h), __func__);
LassoFillData lasso_fill_data = {pixel_buf, w};
BLI_bitmap_draw_2d_poly_v2i_n(rect.xmin,
rect.ymin,
rect.xmax,
rect.ymax,
mcoords,
draw_filled_lasso_px_cb,
&lasso_fill_data);
GPU_blend(GPU_BLEND_ADDITIVE_PREMULT);
PixelBitmapDrawer drawer(GPU_SHADER_2D_IMAGE_SHUFFLE_COLOR);
GPU_shader_uniform_float_ex(
drawer.shader_get(), GPU_shader_get_uniform(drawer.shader_get(), "shuffle"), 4, 1, red);
drawer.draw(rect.xmin,
rect.ymin,
w,
h,
gpu::TextureFormat::UNORM_8,
false,
pixel_buf,
1.0f,
1.0f,
nullptr);
MEM_delete(pixel_buf);
GPU_blend(GPU_BLEND_NONE);
}
}
/* TODO: Extract this common functionality so it can be shared between Sculpt brushes, the annotate
* tool, and this common logic. */
static void draw_lasso_smooth_stroke_indicator(wmGesture *gt, const uint shdr_pos)
{
float (*lasso)[2] = static_cast<float (*)[2]>(gt->customdata);
float last_x = lasso[gt->points - 1][0];
float last_y = lasso[gt->points - 1][1];
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
GPU_line_smooth(true);
GPU_blend(GPU_BLEND_ALPHA);
GPU_line_width(1.25f);
const float color[3] = {1.0f, 0.39f, 0.39f};
const float radius = 4.0f;
/* Draw Inner Ring */
immUniformColor4f(color[0], color[1], color[2], 0.8f);
imm_draw_circle_wire_2d(shdr_pos, gt->mval.x, gt->mval.y, radius, 40);
/* Draw Outer Ring: Dark color for contrast on light backgrounds (e.g. gray on white) */
float darkcolor[3];
mul_v3_v3fl(darkcolor, color, 0.40f);
immUniformColor4f(darkcolor[0], darkcolor[1], darkcolor[2], 0.8f);
imm_draw_circle_wire_2d(shdr_pos, gt->mval.x, gt->mval.y, radius + 1, 40);
/* Draw line from the last saved position to the current mouse position. */
immUniformColor4f(color[0], color[1], color[2], 0.8f);
immBegin(GPU_PRIM_LINES, 2);
immVertex2f(shdr_pos, gt->mval.x, gt->mval.y);
immVertex2f(shdr_pos, last_x, last_y);
immEnd();
GPU_blend(GPU_BLEND_NONE);
GPU_line_smooth(false);
immUnbindProgram();
}
static void wm_gesture_draw_lasso(wmGesture *gt, bool filled)
{
const float *lasso = static_cast<float *>(gt->customdata);
int i;
if (filled) {
draw_filled_lasso(gt, nullptr);
}
const int numverts = gt->points;
/* Nothing to draw, do early output. */
if (numverts < 2) {
return;
}
const uint shdr_pos = GPU_vertformat_attr_add(
immVertexFormat(), "pos", gpu::VertAttrType::SFLOAT_32_32);
immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR);
float viewport_size[4];
GPU_viewport_size_get_f(viewport_size);
immUniform2f("viewport_size", viewport_size[2], viewport_size[3]);
immUniform1i("colors_len", 2); /* "advanced" mode. */
immUniform4f("color", 0.4f, 0.4f, 0.4f, 1.0f);
immUniform4f("color2", 1.0f, 1.0f, 1.0f, 1.0f);
immUniform1f("dash_width", 2.0f);
immUniform1f("udash_factor", 0.5f);
immBegin((gt->type == WM_GESTURE_LASSO) ? GPU_PRIM_LINE_LOOP : GPU_PRIM_LINE_STRIP, numverts);
for (i = 0; i < gt->points; i++, lasso += 2) {
immVertex2f(shdr_pos, lasso[0], lasso[1]);
}
immEnd();
immUnbindProgram();
if (gt->use_smooth) {
draw_lasso_smooth_stroke_indicator(gt, shdr_pos);
}
}
static void draw_start_vertex_circle(const wmGesture &gt, const uint shdr_pos)
{
const int numverts = gt.points;
/* Draw the circle around the starting vertex. */
const short (*border)[2] = static_cast<short int (*)[2]>(gt.customdata);
const float start_pos[2] = {float(border[0][0]), float(border[0][1])};
const float current_pos[2] = {float(gt.mval.x), float(gt.mval.y)};
const float dist = len_v2v2(start_pos, current_pos);
const float limit = pow2f(wm::gesture::POLYLINE_CLICK_RADIUS * UI_SCALE_FAC);
if (dist < limit && numverts > 2) {
const float u = smoothstep(0.0f, limit, dist);
const float radius = interpf(
1.0f * UI_SCALE_FAC, wm::gesture::POLYLINE_CLICK_RADIUS * UI_SCALE_FAC, u);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
const float3 color = {1.0f, 1.0f, 1.0f};
immUniformColor4f(color.x, color.y, color.z, 0.8f);
imm_draw_circle_wire_2d(shdr_pos, start_pos[0], start_pos[1], radius, 15.0f);
const float3 darker_color = color * 0.4f;
immUniformColor4f(darker_color.x, darker_color.y, darker_color.z, 0.8f);
imm_draw_circle_wire_2d(shdr_pos, start_pos[0], start_pos[1], radius + 1, 15.0f);
immUnbindProgram();
}
}
static void wm_gesture_draw_polyline(wmGesture *gt)
{
draw_filled_lasso(gt, &gt->mval);
const int numverts = gt->points + 1;
if (numverts < 2) {
return;
}
const uint shdr_pos = GPU_vertformat_attr_add(
immVertexFormat(), "pos", gpu::VertAttrType::SFLOAT_32_32);
immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR);
float viewport_size[4];
GPU_viewport_size_get_f(viewport_size);
immUniform2f("viewport_size", viewport_size[2], viewport_size[3]);
immUniform1i("colors_len", 2); /* "advanced" mode */
immUniform4f("color", 0.4f, 0.4f, 0.4f, 1.0f);
immUniform4f("color2", 1.0f, 1.0f, 1.0f, 1.0f);
immUniform1f("dash_width", 2.0f);
immUniform1f("udash_factor", 0.5f);
immBegin(GPU_PRIM_LINE_LOOP, numverts);
const short *border = static_cast<short *>(gt->customdata);
for (int i = 0; i < gt->points; i++, border += 2) {
immVertex2f(shdr_pos, float(border[0]), float(border[1]));
}
immVertex2f(shdr_pos, float(gt->mval.x), float(gt->mval.y));
immEnd();
immUnbindProgram();
draw_start_vertex_circle(*gt, shdr_pos);
}
static void wm_gesture_draw_cross(const wmWindow *win, const wmGesture *gt)
{
const rcti *rect = static_cast<const rcti *>(gt->customdata);
const int2 win_size = WM_window_native_pixel_size(win);
float x1, x2, y1, y2;
const uint shdr_pos = GPU_vertformat_attr_add(
immVertexFormat(), "pos", gpu::VertAttrType::SFLOAT_32_32);
immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR);
float viewport_size[4];
GPU_viewport_size_get_f(viewport_size);
immUniform2f("viewport_size", viewport_size[2], viewport_size[3]);
immUniform1i("colors_len", 2); /* "advanced" mode. */
immUniform4f("color", 0.4f, 0.4f, 0.4f, 1.0f);
immUniform4f("color2", 1.0f, 1.0f, 1.0f, 1.0f);
immUniform1f("dash_width", 8.0f);
immUniform1f("udash_factor", 0.5f);
immBegin(GPU_PRIM_LINES, 4);
x1 = float(rect->xmin - win_size[0]);
y1 = float(rect->ymin);
x2 = float(rect->xmin + win_size[0]);
y2 = y1;
immVertex2f(shdr_pos, x1, y1);
immVertex2f(shdr_pos, x2, y2);
x1 = float(rect->xmin);
y1 = float(rect->ymin - win_size[1]);
x2 = x1;
y2 = float(rect->ymin + win_size[1]);
immVertex2f(shdr_pos, x1, y1);
immVertex2f(shdr_pos, x2, y2);
immEnd();
immUnbindProgram();
}
void wm_gesture_draw(wmWindow *win)
{
wmGesture *gt = static_cast<wmGesture *>(win->runtime->gesture.first);
GPU_line_width(1.0f);
for (; gt; gt = gt->next) {
/* All in sub-window space. */
wmViewport(&gt->winrct);
if (gt->type == WM_GESTURE_RECT) {
wm_gesture_draw_rect(gt);
}
else if (gt->type == WM_GESTURE_CIRCLE) {
wm_gesture_draw_circle(gt);
}
else if (gt->type == WM_GESTURE_CROSS_RECT) {
if (gt->is_active) {
wm_gesture_draw_rect(gt);
}
else {
wm_gesture_draw_cross(win, gt);
}
}
else if (gt->type == WM_GESTURE_LINES) {
wm_gesture_draw_lasso(gt, false);
}
else if (gt->type == WM_GESTURE_LASSO) {
wm_gesture_draw_lasso(gt, true);
}
else if (gt->type == WM_GESTURE_STRAIGHTLINE) {
wm_gesture_draw_line(gt);
}
else if (gt->type == WM_GESTURE_POLYLINE) {
wm_gesture_draw_polyline(gt);
}
}
}
void wm_gesture_tag_redraw(wmWindow *win)
{
bScreen *screen = WM_window_get_active_screen(win);
if (screen) {
screen->do_draw_gesture = true;
}
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,742 @@
/* SPDX-FileCopyrightText: 2007 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* Manage initializing resources and correctly shutting down.
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "MEM_guardedalloc.h"
#include "CLG_log.h"
#include "DNA_genfile.h"
#include "DNA_scene_types.h"
#include "DNA_userdef_types.h"
#include "DNA_windowmanager_types.h"
#include "BLI_listbase.h"
#include "BLI_memory_cache.hh"
#include "BLI_path_utils.hh"
#include "BLI_string.h"
#include "BLI_task.h"
#include "BLI_threads.h"
#include "BLI_timer.h"
#include "BLI_utildefines.h"
#include "BLO_undofile.hh"
#include "BLO_writefile.hh"
#include "BKE_blender.hh"
#include "BKE_blendfile.hh"
#include "BKE_callbacks.hh"
#include "BKE_context.hh"
#include "BKE_global.hh"
#include "BKE_icons.hh"
#include "BKE_image.hh"
#include "BKE_keyconfig.h"
#include "BKE_lib_remap.hh"
#include "BKE_main.hh"
#include "BKE_mball_tessellate.hh"
#include "BKE_preferences.h"
#include "BKE_preview_image.hh"
#include "BKE_scene.hh"
#include "BKE_screen.hh"
#include "BKE_sound.hh"
#include "BKE_vfont.hh"
#include "BKE_addon.h"
#include "BKE_appdir.hh"
#include "BKE_blender_cli_command.hh"
#include "BKE_mask.hh" /* Free mask clipboard. */
#include "BKE_material.hh" /* #BKE_material_copybuf_clear. */
#include "BKE_studiolight.h"
#include "BKE_subdiv.hh"
#include "BKE_tracking.hh" /* Free tracking clipboard. */
#include "RE_engine.h"
#include "RE_pipeline.h" /* `RE_` free stuff. */
#ifdef WITH_PYTHON
# include "BPY_extern_python.hh"
# include "BPY_extern_run.hh"
#endif
#include "GHOST_ISystem.hh"
#include "RNA_define.hh"
#include "WM_api.hh"
#include "WM_keymap.hh"
#include "WM_message.hh"
#include "WM_types.hh"
#include "wm.hh"
#include "wm_cursors.hh"
#include "wm_event_system.hh"
#include "wm_files.hh"
#include "wm_platform_support.hh"
#include "wm_surface.hh"
#include "wm_window.hh"
#include "ED_anim_api.hh"
#include "ED_asset.hh"
#include "ED_gpencil_legacy.hh"
#include "ED_grease_pencil.hh"
#include "ED_image.hh"
#include "ED_keyframes_edit.hh"
#include "ED_keyframing.hh"
#include "ED_node.hh"
#include "ED_render.hh"
#include "ED_screen.hh"
#include "ED_space_api.hh"
#include "ED_undo.hh"
#include "ED_util.hh"
#include "BLF_api.hh"
#include "BLT_lang.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "UI_string_search.hh"
#include "GPU_context.hh"
#include "GPU_init_exit.hh"
#include "GPU_shader.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
#include "ANIM_keyingsets.hh"
#include "DRW_engine.hh"
namespace blender {
CLG_LOGREF_DECLARE_GLOBAL(WM_LOG_OPERATORS, "operator");
CLG_LOGREF_DECLARE_GLOBAL(WM_LOG_EVENTS, "event");
CLG_LOGREF_DECLARE_GLOBAL(WM_LOG_TOOL_GIZMO, "tool.gizmo");
CLG_LOGREF_DECLARE_GLOBAL(WM_LOG_MSGBUS_PUB, "msgbus.pub");
CLG_LOGREF_DECLARE_GLOBAL(WM_LOG_MSGBUS_SUB, "msgbus.sub");
static CLG_LogRef LOG_BLEND = {"blend"};
static void wm_init_scripts_extensions_once(bContext *C);
static bool wm_start_with_console = false;
void WM_init_state_start_with_console_set(bool value)
{
wm_start_with_console = value;
}
/**
* Since we cannot know in advance if we will require the draw manager
* context when starting blender in background mode (specially true with
* scripts) we defer the ghost initialization the most as possible
* so that it does not break anything that can run in headless mode (as in
* without display server attached).
*/
static bool gpu_is_init = false;
void WM_init_gpu()
{
/* Must be called only once. */
BLI_assert(gpu_is_init == false);
if (G.background) {
/* Ghost is still not initialized elsewhere in background mode. */
wm_ghost_init_background();
}
if (!GPU_backend_supported()) {
return;
}
/* Needs to be first to have an OpenGL context bound. */
DRW_gpu_context_create();
GPU_init();
if (G.debug & G_DEBUG_GPU_COMPILE_SHADERS) {
GPU_shader_compile_static();
}
/* Some part of the code assumes no context is left bound. */
DRW_gpu_context_disable_ex(true);
gpu_is_init = true;
}
static void sound_jack_sync_callback(Main *bmain, int mode, double time)
{
/* Ugly: Blender doesn't like it when the animation is played back during rendering. */
if (G.is_rendering) {
return;
}
wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
for (wmWindow &window : wm->windows) {
Scene *scene = WM_window_get_active_scene(&window);
if ((scene->audio.flag & AUDIO_SYNC) == 0) {
continue;
}
ViewLayer *view_layer = WM_window_get_active_view_layer(&window);
Depsgraph *depsgraph = BKE_scene_get_depsgraph(scene, view_layer);
if (depsgraph == nullptr) {
continue;
}
Scene *scene_eval = DEG_get_evaluated_scene(depsgraph);
BKE_sound_jack_scene_update(scene_eval, mode, time);
}
}
void WM_init(bContext *C, int argc, const char **argv)
{
if (!G.background) {
wm_ghost_init(C); /* NOTE: it assigns C to ghost! */
wm_init_cursor_data();
BKE_sound_jack_sync_callback_set(sound_jack_sync_callback);
}
BKE_addon_pref_type_init();
BKE_keyconfig_pref_type_init();
wm_operatortypes_register();
WM_paneltype_init(); /* Lookup table only. */
WM_menutype_init();
WM_uilisttype_init();
wm_gizmotype_init();
wm_gizmogrouptype_init();
ED_undosys_type_init();
BKE_library_callback_free_notifier_reference_set(WM_main_remove_notifier_reference);
BKE_region_callback_free_gizmomap_set(wm_gizmomap_remove);
BKE_region_callback_refresh_tag_gizmomap_set(WM_gizmomap_tag_refresh);
BKE_library_callback_remap_editor_id_reference_set(WM_main_remap_editor_id_reference);
BKE_spacedata_callback_id_remap_set(ED_spacedata_id_remap_single);
DEG_editors_set_update_cb(ED_render_id_flush_update, ED_render_scene_update);
ED_spacetypes_init();
ED_node_init_butfuncs();
BLF_init();
BLT_lang_init();
/* Must call first before doing any `.blend` file reading,
* since versioning code may create new IDs. See #57066. */
BLT_lang_set(nullptr);
/* Init icons & previews before reading .blend files for preview icons, which can
* get triggered by the depsgraph. This is also done in background mode
* for scripts that do background processing with preview icons. */
BKE_icons_init(BIFICONID_LAST_STATIC);
BKE_preview_images_init();
WM_msgbus_types_init();
/* Studio-lights needs to be init before we read the home-file,
* otherwise the versioning cannot find the default studio-light. */
BKE_studiolight_init();
BLI_assert((G.fileflags & G_FILE_NO_UI) == 0);
/**
* NOTE(@ideasman42): Startup file and order of initialization.
*
* Loading #BLENDER_STARTUP_FILE, #BLENDER_USERPREF_FILE, starting Python and other sub-systems,
* have inter-dependencies, for example.
*
* - Some sub-systems depend on the preferences (initializing icons depend on the theme).
* - Add-ons depends on the preferences to know what has been enabled.
* - Add-ons depends on the window-manger to register their key-maps.
* - Evaluating the startup file depends on Python for animation-drivers (see #89046).
* - Starting Python depends on the startup file so key-maps can be added in the window-manger.
*
* Loading preferences early, then application subsystems and finally the startup data would
* simplify things if it weren't for key-maps being part of the window-manager
* which is blend file data.
* Creating a dummy window-manager early, or moving the key-maps into the preferences
* would resolve this and may be worth looking into long-term, see: D12184 for details.
*/
wmFileReadPost_Params *params_file_read_post = nullptr;
wmHomeFileRead_Params read_homefile_params{};
read_homefile_params.use_data = true;
read_homefile_params.use_userdef = true;
read_homefile_params.use_factory_settings = G.factory_startup;
read_homefile_params.use_empty_data = false;
read_homefile_params.filepath_startup_override = nullptr;
read_homefile_params.app_template_override = WM_init_state_app_template_get();
read_homefile_params.is_first_time = true;
wm_homefile_read_ex(C, &read_homefile_params, nullptr, &params_file_read_post);
/* NOTE: leave `G_MAIN->filepath` set to an empty string since this
* matches behavior after loading a new file. */
BLI_assert(G_MAIN->filepath[0] == '\0');
/* Call again to set from preferences. */
BLT_lang_set(nullptr);
/* For file-system. Called here so can include user preference paths if needed. */
ED_file_init();
if (!G.background) {
wmWindowManager *wm = CTX_wm_manager(C);
if (wm != nullptr) {
wm_window_ghostwindows_remove_invalid(C, wm);
}
if (wm == nullptr || wm->windows.is_empty()) {
if (params_file_read_post != nullptr) {
MEM_delete_void(static_cast<void *>(params_file_read_post));
params_file_read_post = nullptr;
}
WM_exit(C, EXIT_FAILURE);
}
GPU_render_begin();
#ifdef WITH_INPUT_NDOF
/* Sets 3D mouse dead-zone. */
WM_ndof_deadzone_set(U.ndof_deadzone);
#endif
WM_init_gpu();
if (!WM_platform_support_perform_checks()) {
WM_exit(C, -1);
}
GPU_context_begin_frame(GPU_context_active_get());
ui::init();
GPU_context_end_frame(GPU_context_active_get());
GPU_render_end();
}
bke::subdiv::init();
ED_spacemacros_init();
#ifdef WITH_PYTHON
BPY_python_start(C, argc, argv);
BPY_python_reset(C);
#else
UNUSED_VARS(argc, argv);
#endif
if (!G.background) {
GHOST_ISystem *ghost_system = GHOST_ISystem::getSystem();
if (wm_start_with_console) {
ghost_system->setConsoleWindowState(GHOST_kConsoleWindowStateShow);
}
else {
ghost_system->setConsoleWindowState(GHOST_kConsoleWindowStateHideForNonConsoleLaunch);
}
}
ED_render_clear_mtex_copybuf();
wm_history_file_read();
if (!G.background) {
ui::string_search::read_recent_searches_file();
}
STRNCPY(G.filepath_last_library, BKE_main_blendfile_path_from_global());
CTX_py_init_set(C, true);
/* Postpone updating the key-configuration until after add-ons have been registered,
* needed to properly load user-configured add-on key-maps, see: #113603. */
WM_keyconfig_update_postpone_begin();
WM_keyconfig_init(C);
/* Load add-ons after key-maps have been initialized (but before the blend file has been read),
* important to guarantee default key-maps have been declared & before post-read handlers run. */
wm_init_scripts_extensions_once(C);
WM_keyconfig_update_postpone_end();
WM_keyconfig_update_on_startup(static_cast<wmWindowManager *>(G_MAIN->wm.first));
wm_homefile_read_post(C, params_file_read_post);
}
static bool wm_init_splash_show_on_startup_check()
{
if (U.uiflag & USER_SPLASH_DISABLE) {
return false;
}
bool use_splash = false;
const char *blendfile_path = BKE_main_blendfile_path_from_global();
if (blendfile_path[0] == '\0') {
/* Common case, no file is loaded, show the splash. */
use_splash = true;
}
else {
/* A less common case, if there is no user preferences, show the splash screen
* so the user has the opportunity to restore settings from a previous version. */
use_splash = !bke::preferences::exists();
}
return use_splash;
}
void WM_init_splash_on_startup(bContext *C)
{
if (!wm_init_splash_show_on_startup_check()) {
return;
}
WM_init_splash(C);
}
void WM_init_splash(bContext *C)
{
wmWindowManager *wm = CTX_wm_manager(C);
/* NOTE(@ideasman42): this should practically never happen. */
if (UNLIKELY(wm->windows.is_empty())) {
return;
}
wmWindow *prevwin = CTX_wm_window(C);
CTX_wm_window_set(C, static_cast<wmWindow *>(wm->windows.first));
WM_operator_name_call(C, "WM_OT_splash", wm::OpCallContext::InvokeDefault, nullptr, nullptr);
CTX_wm_window_set(C, prevwin);
}
/** Load add-ons & app-templates once on startup. */
static void wm_init_scripts_extensions_once(bContext *C)
{
#ifdef WITH_PYTHON
const char *imports[] = {"bpy", nullptr};
BPY_run_string_eval(C, imports, "bpy.utils.load_scripts_extensions()");
#else
UNUSED_VARS(C);
#endif
}
/* Free strings of open recent files. */
static void free_openrecent()
{
for (RecentFile &recent : G.recent_files) {
MEM_delete(recent.filepath);
}
BLI_freelistN(&(G.recent_files));
}
static int wm_exit_handler(bContext *C, const wmEvent *event, void *userdata)
{
WM_exit(C, EXIT_SUCCESS);
UNUSED_VARS(event, userdata);
return WM_UI_HANDLER_BREAK;
}
static void wm_exit_schedule_delayed_for_window(const bContext *C, wmWindow &win)
{
/* Use modal UI handler for now.
* Could add separate WM handlers or so, but probably not worth it. */
WM_event_add_ui_handler(
C, &win.runtime->modalhandlers, wm_exit_handler, nullptr, nullptr, eWM_EventHandlerFlag(0));
WM_event_add_mousemove(&win); /* Ensure handler actually gets called. */
}
void wm_exit_schedule_delayed(const bContext *C)
{
/* What we do here is a little bit hacky, but quite simple and doesn't require bigger
* changes: Add a handler wrapping WM_exit() to cause a delayed call of it. */
if (wmWindow *win = CTX_wm_window(C)) {
wm_exit_schedule_delayed_for_window(C, *win);
}
else {
/* Unlikely but possible, in this case just ensure exit runs as it's not interactive. */
wmWindowManager *wm = static_cast<wmWindowManager *>(G_MAIN->wm.first);
for (wmWindow &win : wm->windows) {
wm_exit_schedule_delayed_for_window(C, win);
}
}
}
void UV_clipboard_free();
void WM_exit_ex(bContext *C, const bool do_python_exit, const bool do_user_exit_actions)
{
wmWindowManager *wm = C ? CTX_wm_manager(C) : nullptr;
/* While nothing technically prevents saving user data in background mode,
* don't do this as not typically useful and more likely to cause problems
* if automated scripts happen to write changes to the preferences for example.
* Saving #BLENDER_QUIT_FILE is also not likely to be desired either. */
BLI_assert(G.background ? (do_user_exit_actions == false) : true);
if (C) {
/* Run `exit_pre` Python handlers. */
BKE_callback_exec_boolean(CTX_data_main(C), do_user_exit_actions, BKE_CB_EVT_EXIT_PRE);
}
/* First wrap up running stuff, we assume only the active WM is running. */
/* Modal handlers are on window level freed, others too? */
/* NOTE: same code copied in `wm_files.cc`. */
if (C && wm) {
if (do_user_exit_actions) {
/* Save quit.blend. */
Main *bmain = CTX_data_main(C);
char filepath[FILE_MAX];
const int fileflags = G.fileflags | G_FILE_COMPRESS | G_FILE_RECOVER_WRITE;
BLI_path_join(filepath, sizeof(filepath), BKE_tempdir_base(), BLENDER_QUIT_FILE);
ED_editors_flush_edits(bmain);
ED_image_internal_autosave_flush(bmain);
BlendFileWriteParams blend_file_write_params{};
if (BLO_write_file(bmain, filepath, fileflags, &blend_file_write_params, nullptr)) {
CLOG_INFO_NOCHECK(&LOG_BLEND, "Saved session recovery to \"%s\"", filepath);
}
}
WM_jobs_kill_all(wm);
for (wmWindow &win : wm->windows) {
CTX_wm_window_set(C, &win); /* Needed by operator close callbacks. */
WM_event_remove_handlers(C, &win.runtime->handlers);
WM_event_remove_handlers(C, &win.runtime->modalhandlers);
ED_screen_exit(C, &win, WM_window_get_active_screen(&win));
}
if (!G.background) {
ui::string_search::write_recent_searches_file();
}
if (do_user_exit_actions) {
if ((U.pref_flag & USER_PREF_FLAG_SAVE) && ((G.f & G_FLAG_USERPREF_NO_SAVE_ON_EXIT) == 0)) {
if (U.runtime.is_dirty) {
BKE_blendfile_userdef_write_all(nullptr);
}
}
/* Free the callback data used on file-open
* (will be set when a recover operation has run). */
wm_test_autorun_revert_action_set(nullptr, nullptr);
}
}
#if defined(WITH_PYTHON) && !defined(WITH_PYTHON_MODULE)
/* Without this, we there isn't a good way to manage false-positive resource leaks
* where a #PyObject references memory allocated with guarded-alloc, #71362.
*
* This allows add-ons to free resources when unregistered (which is good practice anyway).
*
* Don't run this code when built as a Python module as this runs when Python is in the
* process of shutting down, where running a snippet like this will crash, see #82675.
* Instead use the `atexit` module, installed by #BPY_python_start.
*
* Don't run this code when `C` is null because #pyrna_unregister_class
* passes in `CTX_data_main(C)` to un-registration functions.
* Further: `addon_utils.disable_all()` may call into functions that expect a valid context,
* supporting all these code-paths with a null context is quite involved for such a corner-case.
*
* Check `CTX_py_init_get(C)` in case this function runs before Python has been initialized.
* Which can happen when the GPU backend fails to initialize.
*/
if (C && CTX_py_init_get(C)) {
/* Calls `addon_utils.disable_all()` as well as unregistering all "startup" modules. */
const char *imports[] = {"bpy", "bpy.utils", nullptr};
BPY_run_string_eval(C, imports, "bpy.utils._on_exit()");
}
#endif
/* Perform this early in case commands reference other data freed later in this function.
* This most run:
* - After add-ons are disabled because they may unregister commands.
* - Before Python exits so Python objects can be de-referenced.
* - Before #BKE_blender_atexit runs they free the `argv` on WIN32.
*/
BKE_blender_cli_command_free_all();
BLI_timer_free();
WM_paneltype_clear();
BKE_addon_pref_type_free();
BKE_keyconfig_pref_type_free();
BKE_materials_exit();
wm_operatortype_free();
wm_surfaces_free();
wm_dropbox_free();
WM_menutype_free();
/* All non-screen and non-space stuff editors did, like edit-mode. */
if (C) {
Main *bmain = CTX_data_main(C);
ED_editors_exit(bmain, true);
}
free_openrecent();
BKE_mball_cubeTable_free();
/* Clear the cache which may (indirectly) contain e.g. GPU resources which need to be freed
* before the GPU backend is destroyed. */
memory_cache::clear();
/* Render code might still access databases. */
RE_FreeAllRender();
RE_engines_exit();
ED_preview_free_dbase(); /* Frees a Main dbase, before #BKE_blender_free! */
ed::asset::list::storage_exit();
BKE_tracking_clipboard_free();
BKE_mask_clipboard_free();
BKE_vfont_clipboard_free();
ed::greasepencil::clipboard_free();
UV_clipboard_free();
wm_clipboard_free();
bke::subdiv::exit();
if (gpu_is_init) {
BKE_image_free_unused_gpu_textures();
}
/* Frees the entire library (#G_MAIN) and space-types. */
BKE_blender_free();
/* Important this runs after #BKE_blender_free because the window manager may be allocated
* when `C` is null, holding references to undo steps which will fail to free if their types
* have been freed first. */
ED_undosys_type_free();
/* Free the GPU subdivision data after the database to ensure that subdivision structs used by
* the modifiers were garbage collected. */
if (gpu_is_init) {
draw::DRW_cache_free_old_subdiv();
}
ANIM_fcurves_copybuf_free();
ANIM_drivers_copybuf_free();
ANIM_driver_vars_copybuf_free();
ANIM_fmodifiers_copybuf_free();
ED_gpencil_anim_copybuf_free();
/* Free gizmo-maps after freeing blender,
* so no deleted data get accessed during cleaning up of areas. */
wm_gizmomaptypes_free();
wm_gizmogrouptype_free();
wm_gizmotype_free();
/* Same for UI-list types. */
WM_uilisttype_free();
BLF_exit();
BLT_lang_free();
animrig::keyingset_infos_exit();
// free_txt_data();
#ifdef WITH_PYTHON
/* Option not to exit Python so this function can be called from 'atexit'. */
if ((C == nullptr) || CTX_py_init_get(C)) {
/* NOTE: (old note)
* before BKE_blender_free so Python's garbage-collection happens while library still exists.
* Needed at least for a rare crash that can happen in python-drivers.
*
* Update for Blender 2.5, move after #BKE_blender_free because Blender now holds references
* to #PyObject's so #Py_DECREF'ing them after Python ends causes bad problems every time
* the python-driver bug can be fixed if it happens again we can deal with it then. */
BPY_python_end(do_python_exit);
}
#else
(void)do_python_exit;
#endif
ED_file_exit(); /* For file-selector menu data. */
/* Delete GPU resources and context. The UI also uses GPU resources and so
* is also deleted with the context active. */
if (gpu_is_init) {
DRW_gpu_context_enable_ex(false);
ui::exit();
GPU_shader_cache_dir_clear_old();
GPU_exit();
DRW_gpu_context_disable_ex(false);
DRW_gpu_context_destroy();
}
else {
ui::exit();
}
BKE_blender_userdef_data_free(&U, false);
RNA_exit(); /* Should be after #BPY_python_end so struct python slots are cleared. */
wm_ghost_exit();
if (C) {
CTX_free(C);
}
DNA_sdna_current_free();
BLI_threadapi_exit();
BLI_task_scheduler_exit();
/* No need to call this early, rather do it late so that other
* pieces of Blender using sound may exit cleanly, see also #50676. */
BKE_sound_exit_once();
BKE_appdir_exit();
BKE_blender_atexit();
wm_autosave_delete();
BKE_tempdir_session_purge();
/* Logging cannot be called after exiting (#CLOG_INFO, #CLOG_WARN etc will crash).
* So postpone exiting until other sub-systems that may use logging have shut down. */
CLG_exit();
}
void WM_exit(bContext *C, const int exit_code)
{
const bool do_user_exit_actions = G.background ? false : (exit_code == EXIT_SUCCESS);
WM_exit_ex(C, true, do_user_exit_actions);
if (!CLG_quiet_get()) {
printf("\nBlender quit\n");
}
exit(exit_code);
}
void WM_script_tag_reload()
{
ui::interface_tag_script_reload();
/* Any operators referenced by gizmos may now be a dangling pointer.
*
* While it is possible to inspect the gizmos it's simpler to re-create them,
* especially for script reloading - where we can accept slower logic
* for the sake of simplicity, see #126852. */
WM_gizmoconfig_update_tag_reinit_all();
}
} // namespace blender

View File

@@ -0,0 +1,830 @@
/* SPDX-FileCopyrightText: 2009 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* Threaded job manager (high level job access).
*/
#include <cstring>
#include "DNA_windowmanager_types.h"
#include "MEM_guardedalloc.h"
#include "BLI_build_config.h"
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BLI_threads.h"
#include "BLI_time.h"
#include "BLI_utildefines.h"
#if OS_WINDOWS
# include "BLI_winstuff.h"
#endif
#include "BKE_context.hh"
#include "BKE_global.hh"
#include "BKE_report.hh"
#include "SEQ_prefetch.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "wm.hh"
#include "wm_event_types.hh"
namespace blender {
/*
* Add new job
* - register in WM
* - configure callbacks
*
* Start or re-run job
* - if job running
* - signal job to end
* - add timer notifier to verify when it has ended, to start it
* - else
* - start job
* - add timer notifier to handle progress
*
* Stop job
* - signal job to end
* on end, job will tag itself as sleeping
*
* Remove job
* - signal job to end
* on end, job will remove itself
*
* When job is done:
* - it puts timer to sleep (or removes?)
*/
struct ThreadSlot;
struct wmJob {
wmJob *next, *prev;
/** Job originating from, keep track of this when deleting windows. */
wmWindow *win;
/** Should store entirely owned context, for start, update, free. */
void *customdata;
/**
* To prevent cpu overhead, use this one which only gets called when job really starts.
* Executed in main thread.
*/
void (*initjob)(void *);
/**
* This performs the actual parallel work.
* Executed in worker thread(s).
*/
wm_jobs_start_callback startjob;
/**
* Called if thread defines so (see `do_update` flag), and max once per timer step.
* Executed in main thread.
*/
void (*update)(void *);
/**
* Optional, called for each timer step while the job is running. Can be used to send messages to
* the running job. For example, online asset library loading uses this to get status updates
* from the downloader to the loading job, like that it's done downloading some files that are
* now ready to be processed.
*
* Should be used for messaging to the job only, must _not_ be used to modify the running job,
* like changing the timer, replacing the custom data pointer, etc.
*
* Executed in the main thread.
*/
void (*timer_step)(void *);
/**
* Free callback (typically for customdata).
* Executed in main thread.
*/
void (*free)(void *);
/**
* Called when job is stopped.
* Executed in main thread.
*/
void (*endjob)(void *);
/**
* Called when job is stopped normally, i.e. by simply completing the startjob function.
* Executed in main thread.
*/
void (*completed)(void *);
/**
* Called when job is stopped abnormally, i.e. when stop=true but ready=false.
* Executed in main thread.
*/
void (*canceled)(void *);
/** Running jobs each have their own timer. */
double time_step;
wmTimer *wt;
/** Only start job after specified time delay. */
double start_delay_time;
/** The notifier event timers should send. */
uint note, endnote;
/* Internal. */
const void *owner;
eWM_JobFlag flag;
bool suspended, running, ready;
eWM_JobType job_type;
/** Data shared with the worker code, so can be accessed and edited from several threads. */
wmJobWorkerStatus worker_status;
/** For display in header, identification. */
char name[128];
/** Once running, we store this separately. */
void *run_customdata;
void (*run_free)(void *);
/** We use BLI_threads api, but per job only 1 thread runs. */
ListBaseT<ThreadSlot> threads;
double start_time;
/**
* Ticket mutex for main thread locking while some job accesses
* data that the main thread might modify at the same time.
*/
TicketMutex *main_thread_mutex;
};
/* Main thread locking. */
void WM_job_main_thread_lock_acquire(wmJob *wm_job)
{
BLI_ticket_mutex_lock(wm_job->main_thread_mutex);
}
void WM_job_main_thread_lock_release(wmJob *wm_job)
{
BLI_ticket_mutex_unlock(wm_job->main_thread_mutex);
}
static void wm_job_main_thread_yield(wmJob *wm_job)
{
/* Unlock and lock the ticket mutex. because it's a fair mutex any job that
* is waiting to acquire the lock will get it first, before we can lock. */
BLI_ticket_mutex_unlock(wm_job->main_thread_mutex);
BLI_ticket_mutex_lock(wm_job->main_thread_mutex);
}
static void wm_jobs_update_qos(const wmWindowManager *wm)
{
/* A QoS API is currently only available for Windows. */
#if OS_WINDOWS
for (wmJob &wm_job : wm->runtime->jobs) {
if (wm_job.flag & WM_JOB_PRIORITY) {
BLI_windows_process_set_qos(QoSMode::HIGH, QoSPrecedence::JOB);
return;
}
}
BLI_windows_process_set_qos(QoSMode::DEFAULT, QoSPrecedence::JOB);
#else
UNUSED_VARS(wm);
#endif
}
/**
* Finds if type or owner, compare for it, otherwise any matching job.
*/
static wmJob *wm_job_find(const wmWindowManager *wm, const void *owner, const eWM_JobType job_type)
{
if (owner && (job_type != WM_JOB_TYPE_ANY)) {
for (wmJob &wm_job : wm->runtime->jobs) {
if (wm_job.owner == owner && wm_job.job_type == job_type) {
return &wm_job;
}
}
}
else if (owner) {
for (wmJob &wm_job : wm->runtime->jobs) {
if (wm_job.owner == owner) {
return &wm_job;
}
}
}
else if (job_type != WM_JOB_TYPE_ANY) {
for (wmJob &wm_job : wm->runtime->jobs) {
if (wm_job.job_type == job_type) {
return &wm_job;
}
}
}
return nullptr;
}
/* ******************* public API ***************** */
wmJob *WM_jobs_get(wmWindowManager *wm,
wmWindow *win,
const void *owner,
const char *name,
const eWM_JobFlag flag,
const eWM_JobType job_type)
{
wmJob *wm_job = wm_job_find(wm, owner, job_type);
if (wm_job == nullptr) {
wm_job = MEM_new_zeroed<wmJob>("new job");
BLI_addtail(&wm->runtime->jobs, wm_job);
wm_job->win = win;
wm_job->owner = owner;
wm_job->flag = flag;
wm_job->job_type = job_type;
STRNCPY(wm_job->name, name);
wm_job->main_thread_mutex = BLI_ticket_mutex_alloc();
WM_job_main_thread_lock_acquire(wm_job);
wm_job->worker_status.reports = MEM_new<ReportList>(__func__);
BKE_reports_init(wm_job->worker_status.reports, RPT_STORE | RPT_PRINT);
BKE_report_print_level_set(wm_job->worker_status.reports, RPT_WARNING);
wm_jobs_update_qos(wm);
}
/* Else: a running job, be careful. */
/* Prevent creating a job with an invalid type. */
BLI_assert(wm_job->job_type != WM_JOB_TYPE_ANY);
return wm_job;
}
bool WM_jobs_test(const wmWindowManager *wm, const void *owner, int job_type)
{
/* Job can be running or about to run (suspended). */
for (wmJob &wm_job : wm->runtime->jobs) {
if (wm_job.owner != owner) {
continue;
}
if (!ELEM(job_type, WM_JOB_TYPE_ANY, wm_job.job_type)) {
continue;
}
if ((wm_job.flag & WM_JOB_PROGRESS) && (wm_job.running || wm_job.suspended)) {
return true;
}
}
return false;
}
float WM_jobs_progress(const wmWindowManager *wm, const void *owner)
{
const wmJob *wm_job = wm_job_find(wm, owner, WM_JOB_TYPE_ANY);
if (wm_job && wm_job->flag & WM_JOB_PROGRESS) {
return wm_job->worker_status.progress;
}
return 0.0;
}
static void wm_jobs_update_progress_bars(wmWindowManager *wm)
{
float total_progress = 0.0f;
float jobs_progress = 0;
for (wmJob &wm_job : wm->runtime->jobs) {
if (wm_job.threads.first && !wm_job.ready) {
if (wm_job.flag & WM_JOB_PROGRESS) {
/* Accumulate global progress for running jobs. */
jobs_progress++;
total_progress += wm_job.worker_status.progress;
}
}
}
/* If there are running jobs, set the global progress indicator. */
if (jobs_progress > 0) {
float progress = total_progress / jobs_progress;
for (wmWindow &win : wm->windows) {
WM_progress_set(&win, progress);
}
}
else {
for (wmWindow &win : wm->windows) {
WM_progress_clear(&win);
}
}
}
double WM_jobs_starttime(const wmWindowManager *wm, const void *owner)
{
const wmJob *wm_job = wm_job_find(wm, owner, WM_JOB_TYPE_ANY);
if (wm_job && wm_job->flag & WM_JOB_PROGRESS) {
return wm_job->start_time;
}
return 0;
}
const char *WM_jobs_name(const wmWindowManager *wm, const void *owner)
{
wmJob *wm_job = wm_job_find(wm, owner, WM_JOB_TYPE_ANY);
if (wm_job) {
return wm_job->name;
}
return nullptr;
}
void *WM_jobs_customdata_from_type(wmWindowManager *wm, const void *owner, int job_type)
{
wmJob *wm_job = wm_job_find(wm, owner, eWM_JobType(job_type));
if (wm_job) {
return WM_jobs_customdata_get(wm_job);
}
return nullptr;
}
bool WM_jobs_is_running(const wmJob *wm_job)
{
return wm_job->running;
}
bool WM_jobs_is_stopped(const wmWindowManager *wm, const void *owner)
{
wmJob *wm_job = wm_job_find(wm, owner, WM_JOB_TYPE_ANY);
return wm_job ? wm_job->worker_status.stop : true; /* XXX to be redesigned properly. */
}
void *WM_jobs_customdata_get(wmJob *wm_job)
{
if (!wm_job->customdata) {
return wm_job->run_customdata;
}
return wm_job->customdata;
}
void WM_jobs_customdata_set(wmJob *wm_job, void *customdata, void (*free)(void *customdata))
{
/* Pending job? just free. */
if (wm_job->customdata) {
wm_job->free(wm_job->customdata);
}
wm_job->customdata = customdata;
wm_job->free = free;
if (wm_job->running) {
/* Signal job to end. */
wm_job->worker_status.stop = true;
}
}
void WM_jobs_timer(
wmJob *wm_job, double time_step, uint note, uint endnote, void (*timer_step)(void *))
{
wm_job->time_step = time_step;
wm_job->note = note;
wm_job->endnote = endnote;
wm_job->timer_step = timer_step;
}
void WM_jobs_delay_start(wmJob *wm_job, double delay_time)
{
wm_job->start_delay_time = delay_time;
}
void WM_jobs_callbacks(wmJob *wm_job,
wm_jobs_start_callback startjob,
void (*initjob)(void *),
void (*update)(void *),
void (*endjob)(void *))
{
WM_jobs_callbacks_ex(wm_job, startjob, initjob, update, endjob, nullptr, nullptr);
}
void WM_jobs_callbacks_ex(wmJob *wm_job,
wm_jobs_start_callback startjob,
void (*initjob)(void *),
void (*update)(void *),
void (*endjob)(void *),
void (*completed)(void *),
void (*canceled)(void *))
{
wm_job->startjob = startjob;
wm_job->initjob = initjob;
wm_job->update = update;
wm_job->endjob = endjob;
wm_job->completed = completed;
wm_job->canceled = canceled;
}
static void wm_jobs_reports_update(wmWindowManager *wm, wmJob *wm_job)
{
WM_reports_from_reports_move(wm, wm_job->worker_status.reports);
}
static void *do_job_thread(void *job_v)
{
wmJob *wm_job = static_cast<wmJob *>(job_v);
wm_job->startjob(wm_job->run_customdata, &wm_job->worker_status);
wm_job->ready = true;
return nullptr;
}
/* Don't allow same startjob to be executed twice. */
static void wm_jobs_test_suspend_stop(wmWindowManager *wm, wmJob *test)
{
bool suspend = false;
/* Job added with suspend flag, we wait 1 timer step before activating it. */
if (test->start_delay_time > 0.0) {
suspend = true;
test->start_delay_time = 0.0;
}
else {
/* Check other jobs. */
for (wmJob &wm_job : wm->runtime->jobs) {
/* Obvious case, no test needed. */
if (&wm_job == test || !wm_job.running) {
continue;
}
/* If new job is not render, then check for same job type. */
if (0 == (test->flag & WM_JOB_EXCL_RENDER)) {
if (wm_job.job_type != test->job_type) {
continue;
}
}
/* If new job is render, any render job should be stopped. */
if (test->flag & WM_JOB_EXCL_RENDER) {
if (0 == (wm_job.flag & WM_JOB_EXCL_RENDER)) {
continue;
}
}
suspend = true;
/* If this job has higher priority, stop others. */
if (test->flag & WM_JOB_PRIORITY) {
wm_job.worker_status.stop = true;
// printf("job stopped: %s\n", wm_job->name);
}
}
}
/* Possible suspend ourselves, waiting for other jobs, or de-suspend. */
test->suspended = suspend;
#if 0
if (suspend) {
printf("job suspended: %s\n", test->name);
}
#endif
}
void WM_jobs_start(wmWindowManager *wm, wmJob *wm_job)
{
if (wm_job->running) {
/* Signal job to end and restart. */
wm_job->worker_status.stop = true;
// printf("job started a running job, ending... %s\n", wm_job->name);
}
else {
if (wm_job->customdata && wm_job->startjob) {
const double time_step = (wm_job->start_delay_time > 0.0) ? wm_job->start_delay_time :
wm_job->time_step;
wm_jobs_test_suspend_stop(wm, wm_job);
if (wm_job->suspended == false) {
/* Copy to ensure proper free in end. */
wm_job->run_customdata = wm_job->customdata;
wm_job->run_free = wm_job->free;
wm_job->free = nullptr;
wm_job->customdata = nullptr;
wm_job->running = true;
if (wm_job->initjob) {
wm_job->initjob(wm_job->run_customdata);
}
wm_job->worker_status.stop = false;
wm_job->ready = false;
wm_job->worker_status.progress = 0.0;
// printf("job started: %s\n", wm_job->name);
BLI_threadpool_init(&wm_job->threads, do_job_thread, 1);
BLI_threadpool_insert(&wm_job->threads, wm_job);
}
/* Restarted job has timer already. */
if (wm_job->wt && (wm_job->wt->time_step > time_step)) {
WM_event_timer_remove(wm, wm_job->win, wm_job->wt);
wm_job->wt = WM_event_timer_add(wm, wm_job->win, TIMERJOBS, time_step);
}
if (wm_job->wt == nullptr) {
wm_job->wt = WM_event_timer_add(wm, wm_job->win, TIMERJOBS, time_step);
}
wm_job->start_time = BLI_time_now_seconds();
}
else {
printf("job fails, not initialized\n");
}
}
}
static void wm_job_end(wmWindowManager *wm, wmJob *wm_job)
{
BLI_assert_msg(BLI_thread_is_main(), "wm_job_end should only be called from the main thread");
if (wm_job->endjob) {
wm_job->endjob(wm_job->run_customdata);
}
/* Do the final callback based on whether the job was run to completion or not.
* Not all jobs have the same way of signaling cancellation (i.e. rendering stops when
* `G.is_break == true`, but doesn't set any wm_job properties to cancel the WM job). */
const bool was_canceled = wm_job->worker_status.stop || G.is_break;
void (*final_callback)(void *) = (wm_job->ready && !was_canceled) ? wm_job->completed :
wm_job->canceled;
if (final_callback) {
final_callback(wm_job->run_customdata);
}
/* Ensure all reports have been moved to WM. */
wm_jobs_reports_update(wm, wm_job);
}
static void wm_job_free(wmWindowManager *wm, wmJob *wm_job)
{
BLI_remlink(&wm->runtime->jobs, wm_job);
WM_job_main_thread_lock_release(wm_job);
BLI_ticket_mutex_free(wm_job->main_thread_mutex);
BLI_assert(wm_job->worker_status.reports->list.is_empty());
BKE_reports_free(wm_job->worker_status.reports);
MEM_delete(wm_job->worker_status.reports);
MEM_delete(wm_job);
wm_jobs_update_qos(wm);
}
/* Stop job, end thread, free data completely. */
static void wm_jobs_kill_job(wmWindowManager *wm, wmJob *wm_job)
{
bool update_progress = (wm_job->flag & WM_JOB_PROGRESS) != 0;
if (wm_job->running) {
/* Signal job to end. */
wm_job->worker_status.stop = true;
WM_job_main_thread_lock_release(wm_job);
BLI_threadpool_end(&wm_job->threads);
WM_job_main_thread_lock_acquire(wm_job);
wm_job_end(wm, wm_job);
}
if (wm_job->wt) {
WM_event_timer_remove(wm, wm_job->win, wm_job->wt);
}
if (wm_job->customdata) {
wm_job->free(wm_job->customdata);
}
if (wm_job->run_customdata) {
wm_job->run_free(wm_job->run_customdata);
}
/* Remove wm_job. */
wm_job_free(wm, wm_job);
/* Update progress bars in windows. */
if (update_progress) {
wm_jobs_update_progress_bars(wm);
}
}
void WM_jobs_kill_all(wmWindowManager *wm)
{
wmJob *wm_job;
while ((wm_job = static_cast<wmJob *>(wm->runtime->jobs.first))) {
wm_jobs_kill_job(wm, wm_job);
}
/* This job will be automatically restarted. */
seq::prefetch_stop_all();
}
void WM_jobs_kill_all_except(wmWindowManager *wm, const void *owner)
{
for (wmJob &wm_job : wm->runtime->jobs.items_mutable()) {
if (wm_job.owner != owner) {
wm_jobs_kill_job(wm, &wm_job);
}
}
}
void WM_jobs_kill_type(wmWindowManager *wm, const void *owner, int job_type)
{
BLI_assert(job_type != WM_JOB_TYPE_ANY);
for (wmJob &wm_job : wm->runtime->jobs.items_mutable()) {
if (owner && wm_job.owner != owner) {
continue;
}
if (wm_job.job_type == job_type) {
wm_jobs_kill_job(wm, &wm_job);
}
}
}
void WM_jobs_kill_all_from_owner(wmWindowManager *wm, const void *owner)
{
for (wmJob &wm_job : wm->runtime->jobs.items_mutable()) {
if (wm_job.owner == owner) {
wm_jobs_kill_job(wm, &wm_job);
}
}
}
void WM_jobs_stop_type(wmWindowManager *wm, const void *owner, eWM_JobType job_type)
{
BLI_assert(job_type != WM_JOB_TYPE_ANY);
for (wmJob &wm_job : wm->runtime->jobs) {
if (owner && wm_job.owner != owner) {
continue;
}
if (wm_job.job_type == job_type) {
if (wm_job.running) {
wm_job.worker_status.stop = true;
}
}
}
}
void WM_jobs_stop_all_from_owner(wmWindowManager *wm, const void *owner)
{
for (wmJob &wm_job : wm->runtime->jobs) {
if (wm_job.owner == owner) {
if (wm_job.running) {
wm_job.worker_status.stop = true;
}
}
}
}
void wm_jobs_timer_end(wmWindowManager *wm, wmTimer *wt)
{
wmJob *wm_job = static_cast<wmJob *>(BLI_findptr(&wm->runtime->jobs, wt, offsetof(wmJob, wt)));
if (wm_job) {
wm_jobs_kill_job(wm, wm_job);
}
}
static void wm_job_update(wmWindowManager *wm, wmJob &job)
{
if (job.update) {
job.update(job.run_customdata);
}
if (job.note) {
WM_event_add_notifier_ex(wm, job.win, job.note, nullptr);
}
if (job.flag & WM_JOB_PROGRESS) {
WM_event_add_notifier_ex(wm, job.win, NC_WM | ND_JOB, nullptr);
}
job.worker_status.do_update = false;
}
void wm_jobs_timer(wmWindowManager *wm, wmTimer *wt)
{
wmJob *wm_job = static_cast<wmJob *>(BLI_findptr(&wm->runtime->jobs, wt, offsetof(wmJob, wt)));
if (wm_job) {
/* Running threads. */
if (wm_job->threads.first) {
/* Let threads get temporary lock over main thread if needed. */
wm_job_main_thread_yield(wm_job);
if (wm_job->timer_step) {
wm_job->timer_step(wm_job->run_customdata);
}
if (wm_job->worker_status.do_update) {
wm_job_update(wm, *wm_job);
}
}
else if (wm_job->suspended) {
WM_jobs_start(wm, wm_job);
}
/* Move pending reports generated by the worker thread to the WM main list. */
if (wm_job) {
wm_jobs_reports_update(wm, wm_job);
}
}
/* Update progress bars in windows. */
wm_jobs_update_progress_bars(wm);
}
void wm_jobs_handle_finished(const bContext *C)
{
wmWindowManager *wm = CTX_wm_manager(C);
for (wmJob &job : wm->runtime->jobs.items_reversed_mutable()) {
if (!job.threads.first) {
continue;
}
/* Let threads get temporary lock over main thread if needed. */
wm_job_main_thread_yield(&job);
/* Job not yet finished. */
if (!job.ready) {
continue;
}
wm_job_update(wm, job);
wm_job_end(wm, &job);
/* Free owned data. */
job.run_free(job.run_customdata);
job.run_customdata = nullptr;
job.run_free = nullptr;
if (G.debug & G_DEBUG_JOBS) {
printf(
"Job '%s' finished in %f seconds\n", job.name, BLI_time_now_seconds() - job.start_time);
}
job.running = false;
WM_job_main_thread_lock_release(&job);
BLI_threadpool_end(&job.threads);
WM_job_main_thread_lock_acquire(&job);
if (job.endnote) {
WM_event_add_notifier_ex(wm, job.win, job.endnote, nullptr);
}
WM_event_add_notifier_ex(wm, job.win, NC_WM | ND_JOB, nullptr);
/* New job added for wm_job? */
if (job.customdata) {
// printf("job restarted with new data %s\n", job.name);
WM_jobs_start(wm, &job);
}
else {
WM_event_timer_remove(wm, job.win, job.wt);
job.wt = nullptr;
/* Remove wm_job. */
wm_job_free(wm, &job);
}
}
/* Update progress bars in windows. */
wm_jobs_update_progress_bars(wm);
}
bool WM_jobs_has_running(const wmWindowManager *wm)
{
for (const wmJob &wm_job : wm->runtime->jobs) {
if (wm_job.running) {
return true;
}
}
return false;
}
bool WM_jobs_has_running_type(const wmWindowManager *wm, int job_type)
{
for (wmJob &wm_job : wm->runtime->jobs) {
if (wm_job.running && wm_job.job_type == job_type) {
return true;
}
}
return false;
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,597 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* Utilities to help define keymaps.
*/
#include <cstring>
#include "DNA_space_types.h"
#include "DNA_windowmanager_types.h"
#include "BLI_listbase.h"
#include "BLI_utildefines.h"
#include "BKE_context.hh"
#include "RNA_access.hh"
#include "WM_api.hh"
#include "WM_keymap.hh"
#include "WM_types.hh"
namespace blender {
/* Menu wrapper for #WM_keymap_add_item. */
/* -------------------------------------------------------------------- */
/** \name Wrappers for #WM_keymap_add_item
* \{ */
wmKeyMapItem *WM_keymap_add_menu(wmKeyMap *keymap,
const char *idname,
const KeyMapItem_Params *params)
{
wmKeyMapItem *kmi = WM_keymap_add_item(keymap, "WM_OT_call_menu", params);
RNA_string_set(kmi->ptr, "name", idname);
return kmi;
}
wmKeyMapItem *WM_keymap_add_menu_pie(wmKeyMap *keymap,
const char *idname,
const KeyMapItem_Params *params)
{
wmKeyMapItem *kmi = WM_keymap_add_item(keymap, "WM_OT_call_menu_pie", params);
RNA_string_set(kmi->ptr, "name", idname);
return kmi;
}
wmKeyMapItem *WM_keymap_add_panel(wmKeyMap *keymap,
const char *idname,
const KeyMapItem_Params *params)
{
wmKeyMapItem *kmi = WM_keymap_add_item(keymap, "WM_OT_call_panel", params);
RNA_string_set(kmi->ptr, "name", idname);
/* TODO: we might want to disable this. */
RNA_boolean_set(kmi->ptr, "keep_open", false);
return kmi;
}
wmKeyMapItem *WM_keymap_add_tool(wmKeyMap *keymap,
const char *idname,
const KeyMapItem_Params *params)
{
wmKeyMapItem *kmi = WM_keymap_add_item(keymap, "WM_OT_tool_set_by_id", params);
RNA_string_set(kmi->ptr, "name", idname);
return kmi;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Introspection
* \{ */
wmKeyMap *WM_keymap_guess_from_context(const bContext *C)
{
eSpace_Type space_type = SPACE_EMPTY;
eRegion_Type region_type = RGN_TYPE_WINDOW;
SpaceLink *sl = CTX_wm_space_data(C);
/* Tool property tab is a special case where 3d tool properties are shown in the properties
* editor. This would allow assigning tool shortcut keys from properties editor. */
bool allow_properties_keymap = false;
if (sl->spacetype == SPACE_PROPERTIES) {
SpaceProperties *sp = reinterpret_cast<SpaceProperties *>(sl);
if (sp->mainb == BCONTEXT_TOOL) {
allow_properties_keymap = true;
}
}
const char *km_id = nullptr;
if (sl->spacetype == SPACE_VIEW3D || allow_properties_keymap) {
const enum eContextObjectMode mode = CTX_data_mode_enum(C);
switch (mode) {
case CTX_MODE_EDIT_MESH:
km_id = "Mesh";
break;
case CTX_MODE_EDIT_CURVE:
km_id = "Curve";
break;
case CTX_MODE_EDIT_CURVES:
km_id = "Curves";
break;
case CTX_MODE_EDIT_SURFACE:
km_id = "Curve";
break;
case CTX_MODE_EDIT_TEXT:
km_id = "Font";
break;
case CTX_MODE_EDIT_ARMATURE:
km_id = "Armature";
break;
case CTX_MODE_EDIT_METABALL:
km_id = "Metaball";
break;
case CTX_MODE_EDIT_LATTICE:
km_id = "Lattice";
break;
case CTX_MODE_EDIT_GREASE_PENCIL:
km_id = "Grease Pencil Edit Mode";
break;
case CTX_MODE_EDIT_POINTCLOUD:
km_id = "Point Cloud";
break;
case CTX_MODE_POSE:
km_id = "Pose";
break;
case CTX_MODE_SCULPT:
km_id = "Sculpt";
break;
case CTX_MODE_PAINT_WEIGHT:
km_id = "Weight Paint";
break;
case CTX_MODE_PAINT_VERTEX:
km_id = "Vertex Paint";
break;
case CTX_MODE_PAINT_TEXTURE:
km_id = "Image Paint";
break;
case CTX_MODE_PARTICLE:
km_id = "Particle";
break;
case CTX_MODE_OBJECT:
km_id = "Object Mode";
break;
case CTX_MODE_PAINT_GPENCIL_LEGACY:
km_id = "Grease Pencil Stroke Paint Mode";
break;
case CTX_MODE_EDIT_GPENCIL_LEGACY:
km_id = "Grease Pencil Stroke Edit Mode";
break;
case CTX_MODE_SCULPT_GPENCIL_LEGACY:
km_id = "Grease Pencil Stroke Sculpt Mode";
break;
case CTX_MODE_WEIGHT_GPENCIL_LEGACY:
km_id = "Grease Pencil Stroke Weight Mode";
break;
case CTX_MODE_VERTEX_GPENCIL_LEGACY:
km_id = "Grease Pencil Stroke Vertex Mode";
break;
case CTX_MODE_SCULPT_CURVES:
km_id = "Sculpt Curves";
break;
case CTX_MODE_PAINT_GREASE_PENCIL:
km_id = "Grease Pencil Draw Mode";
break;
case CTX_MODE_SCULPT_GREASE_PENCIL:
km_id = "Grease Pencil Sculpt Mode";
break;
case CTX_MODE_WEIGHT_GREASE_PENCIL:
km_id = "Grease Pencil Weight Mode";
break;
case CTX_MODE_VERTEX_GREASE_PENCIL:
km_id = "Grease Pencil Vertex Mode";
break;
}
}
else if (sl->spacetype == SPACE_IMAGE) {
const SpaceImage *sima = reinterpret_cast<SpaceImage *>(sl);
const eSpaceImage_Mode mode = eSpaceImage_Mode(sima->mode);
switch (mode) {
case SI_MODE_VIEW:
km_id = "Image";
break;
case SI_MODE_PAINT:
km_id = "Image Paint";
break;
case SI_MODE_MASK:
km_id = "Mask Editing";
break;
case SI_MODE_UV:
km_id = "UV Editor";
break;
}
}
else if (sl->spacetype == SPACE_SEQ) {
const SpaceSeq *sseq = reinterpret_cast<SpaceSeq *>(sl);
const enum eSpaceSeq_Displays view = eSpaceSeq_Displays(sseq->view);
space_type = SPACE_SEQ;
switch (view) {
case SEQ_VIEW_SEQUENCE:
km_id = "Sequencer";
break;
case SEQ_VIEW_PREVIEW:
km_id = "Preview";
break;
case SEQ_VIEW_SEQUENCE_PREVIEW:
km_id = "Video Sequence Editor";
break;
}
}
else {
return nullptr;
}
wmKeyMap *km = WM_keymap_find_all(CTX_wm_manager(C), km_id, space_type, region_type);
BLI_assert(km);
return km;
}
wmKeyMap *WM_keymap_guess_opname(const bContext *C, const char *opname)
{
/* Op types purposely skipped for now:
* BOID_OT
* BUTTONS_OT
* CONSTRAINT_OT
* ED_OT
* FLUID_OT
* TEXTURE_OT
* WORLD_OT
*/
wmKeyMap *km = nullptr;
wmWindowManager *wm = CTX_wm_manager(C);
SpaceLink *sl = CTX_wm_space_data(C);
/* Window. */
if (STRPREFIX(opname, "WM_OT") || STRPREFIX(opname, "ED_OT_undo")) {
if (STREQ(opname, "WM_OT_tool_set_by_id") || STREQ(opname, "WM_OT_call_asset_shelf_popover")) {
km = WM_keymap_guess_from_context(C);
}
if (km == nullptr) {
km = WM_keymap_find_all(wm, "Window", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
}
/* Screen & Render. */
else if (STRPREFIX(opname, "SCREEN_OT") || STRPREFIX(opname, "RENDER_OT") ||
STRPREFIX(opname, "SOUND_OT") || STRPREFIX(opname, "SCENE_OT"))
{
km = WM_keymap_find_all(wm, "Screen", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
/* Grease Pencil. */
else if (STRPREFIX(opname, "GPENCIL_OT")) {
km = WM_keymap_find_all(wm, "Grease Pencil", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
else if (STRPREFIX(opname, "GREASE_PENCIL_OT")) {
km = WM_keymap_find_all(wm, "Grease Pencil", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
/* Markers. */
else if (STRPREFIX(opname, "MARKER_OT")) {
km = WM_keymap_find_all(wm, "Markers", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
/* Import/Export. */
else if (STRPREFIX(opname, "IMPORT_") || STRPREFIX(opname, "EXPORT_")) {
km = WM_keymap_find_all(wm, "Window", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
/* 3D View. */
else if (STRPREFIX(opname, "VIEW3D_OT")) {
km = WM_keymap_find_all(wm, "3D View", sl->spacetype, RGN_TYPE_WINDOW);
}
else if (STRPREFIX(opname, "OBJECT_OT")) {
/* Exception, this needs to work outside object mode too. */
if (STRPREFIX(opname, "OBJECT_OT_mode_set") || STRPREFIX(opname, "OBJECT_OT_transfer_mode")) {
km = WM_keymap_find_all(wm, "Object Non-modal", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
else {
km = WM_keymap_guess_from_context(C);
}
if (km == nullptr) {
km = WM_keymap_find_all(wm, "Object Mode", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
}
/* Object mode related. */
else if (STRPREFIX(opname, "GROUP_OT") || STRPREFIX(opname, "MATERIAL_OT") ||
STRPREFIX(opname, "PTCACHE_OT") || STRPREFIX(opname, "RIGIDBODY_OT"))
{
km = WM_keymap_find_all(wm, "Object Mode", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
/* Editing Modes. */
else if (STRPREFIX(opname, "MESH_OT")) {
km = WM_keymap_find_all(wm, "Mesh", SPACE_EMPTY, RGN_TYPE_WINDOW);
/* Some mesh operators are active in object mode too, like add-prim. */
if (km && !WM_keymap_poll(const_cast<bContext *>(C), km)) {
km = WM_keymap_find_all(wm, "Object Mode", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
}
else if (STRPREFIX(opname, "CURVE_OT") || STRPREFIX(opname, "SURFACE_OT")) {
km = WM_keymap_find_all(wm, "Curve", SPACE_EMPTY, RGN_TYPE_WINDOW);
/* Some curve operators are active in object mode too, like add-prim. */
if (km && !WM_keymap_poll(const_cast<bContext *>(C), km)) {
km = WM_keymap_find_all(wm, "Object Mode", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
}
else if (STRPREFIX(opname, "ARMATURE_OT") || STRPREFIX(opname, "SKETCH_OT")) {
km = WM_keymap_find_all(wm, "Armature", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
else if (STRPREFIX(opname, "POSE_OT") || STRPREFIX(opname, "POSELIB_OT")) {
switch (CTX_data_mode_enum(C)) {
case CTX_MODE_OBJECT:
/* Some POSE operators are now working in object mode. See #159734. */
km = WM_keymap_find_all(wm, "Object Mode", SPACE_EMPTY, RGN_TYPE_WINDOW);
break;
default:
km = WM_keymap_find_all(wm, "Pose", SPACE_EMPTY, RGN_TYPE_WINDOW);
break;
}
}
else if (STRPREFIX(opname, "SCULPT_OT")) {
switch (CTX_data_mode_enum(C)) {
case CTX_MODE_SCULPT:
km = WM_keymap_find_all(wm, "Sculpt", SPACE_EMPTY, RGN_TYPE_WINDOW);
break;
default:
break;
}
}
else if (STRPREFIX(opname, "CURVES_OT")) {
km = WM_keymap_find_all(wm, "Curves", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
else if (STRPREFIX(opname, "SCULPT_CURVES_OT")) {
km = WM_keymap_find_all(wm, "Sculpt Curves", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
else if (STRPREFIX(opname, "MBALL_OT")) {
km = WM_keymap_find_all(wm, "Metaball", SPACE_EMPTY, RGN_TYPE_WINDOW);
/* Some meta-ball operators are active in object mode too, like add-primitive. */
if (km && !WM_keymap_poll(const_cast<bContext *>(C), km)) {
km = WM_keymap_find_all(wm, "Object Mode", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
}
else if (STRPREFIX(opname, "LATTICE_OT")) {
km = WM_keymap_find_all(wm, "Lattice", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
else if (STRPREFIX(opname, "PARTICLE_OT")) {
km = WM_keymap_find_all(wm, "Particle", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
else if (STRPREFIX(opname, "POINTCLOUD_OT")) {
km = WM_keymap_find_all(wm, "Point Cloud", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
else if (STRPREFIX(opname, "FONT_OT")) {
km = WM_keymap_find_all(wm, "Font", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
/* Paint Face Mask. */
else if (STRPREFIX(opname, "PAINT_OT_face_select")) {
km = WM_keymap_find_all(
wm, "Paint Face Mask (Weight, Vertex, Texture)", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
else if (STRPREFIX(opname, "PAINT_OT") || STRPREFIX(opname, "BRUSH_OT")) {
/* Check for relevant mode. */
km = WM_keymap_guess_from_context(C);
}
/* General 2D View, not bound to a specific spacetype. */
else if (STRPREFIX(opname, "VIEW2D_OT")) {
km = WM_keymap_find_all(wm, "View2D", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
/* Image Editor. */
else if (STRPREFIX(opname, "IMAGE_OT")) {
km = WM_keymap_find_all(wm, "Image", sl->spacetype, RGN_TYPE_WINDOW);
}
/* Clip Editor. */
else if (STRPREFIX(opname, "CLIP_OT")) {
km = WM_keymap_find_all(wm, "Clip", sl->spacetype, RGN_TYPE_WINDOW);
}
else if (STRPREFIX(opname, "MASK_OT")) {
km = WM_keymap_find_all(wm, "Mask Editing", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
/* UV Editor. */
else if (STRPREFIX(opname, "UV_OT")) {
/* Hack to allow using UV unwrapping ops from 3DView/editmode.
* Mesh keymap is probably not ideal, but best place I could find to put those. */
if (sl->spacetype == SPACE_VIEW3D) {
km = WM_keymap_find_all(wm, "Mesh", SPACE_EMPTY, RGN_TYPE_WINDOW);
if (km && !WM_keymap_poll(const_cast<bContext *>(C), km)) {
km = nullptr;
}
}
if (!km) {
km = WM_keymap_find_all(wm, "UV Editor", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
}
/* Node Editor. */
else if (STRPREFIX(opname, "NODE_OT")) {
km = WM_keymap_find_all(wm, "Node Editor", sl->spacetype, RGN_TYPE_WINDOW);
}
/* Animation Editor Channels. */
else if (STRPREFIX(opname, "ANIM_OT_channels")) {
km = WM_keymap_find_all(wm, "Animation Channels", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
/* Animation Generic - after channels. */
else if (STRPREFIX(opname, "ANIM_OT")) {
if (sl->spacetype == SPACE_VIEW3D) {
switch (CTX_data_mode_enum(C)) {
case CTX_MODE_OBJECT:
km = WM_keymap_find_all(wm, "Object Mode", SPACE_EMPTY, RGN_TYPE_WINDOW);
break;
case CTX_MODE_POSE:
km = WM_keymap_find_all(wm, "Pose", SPACE_EMPTY, RGN_TYPE_WINDOW);
break;
default:
break;
}
if (ARegion *region = CTX_wm_region(C)) {
/* When property is in side panel, add shortcut key to User interface Keymap, see: #136998.
*/
if (region->regiontype == RGN_TYPE_UI) {
km = WM_keymap_find_all(wm, "User Interface", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
}
if (km && !WM_keymap_poll(const_cast<bContext *>(C), km)) {
km = nullptr;
}
}
else if (sl->spacetype == SPACE_PROPERTIES) {
km = WM_keymap_find_all(wm, "User Interface", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
if (!km) {
km = WM_keymap_find_all(wm, "Animation", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
}
/* Graph Editor. */
else if (STRPREFIX(opname, "GRAPH_OT")) {
km = WM_keymap_find_all(wm, "Graph Editor", sl->spacetype, RGN_TYPE_WINDOW);
}
/* Dopesheet Editor. */
else if (STRPREFIX(opname, "ACTION_OT")) {
km = WM_keymap_find_all(wm, "Dopesheet", sl->spacetype, RGN_TYPE_WINDOW);
}
/* NLA Editor. */
else if (STRPREFIX(opname, "NLA_OT")) {
km = WM_keymap_find_all(wm, "NLA Editor", sl->spacetype, RGN_TYPE_WINDOW);
}
/* Script. */
else if (STRPREFIX(opname, "SCRIPT_OT")) {
km = WM_keymap_find_all(wm, "Script", sl->spacetype, RGN_TYPE_WINDOW);
}
/* Text. */
else if (STRPREFIX(opname, "TEXT_OT")) {
km = WM_keymap_find_all(wm, "Text", sl->spacetype, RGN_TYPE_WINDOW);
}
/* Sequencer. */
else if (STRPREFIX(opname, "SEQUENCER_OT")) {
km = WM_keymap_find_all(wm, "Sequencer", sl->spacetype, RGN_TYPE_WINDOW);
}
/* Console. */
else if (STRPREFIX(opname, "CONSOLE_OT")) {
km = WM_keymap_find_all(wm, "Console", sl->spacetype, RGN_TYPE_WINDOW);
}
/* Console. */
else if (STRPREFIX(opname, "INFO_OT")) {
km = WM_keymap_find_all(wm, "Info", sl->spacetype, RGN_TYPE_WINDOW);
}
/* File browser. */
else if (STRPREFIX(opname, "FILE_OT")) {
km = WM_keymap_find_all(wm, "File Browser", sl->spacetype, RGN_TYPE_WINDOW);
}
/* Logic Editor. */
else if (STRPREFIX(opname, "LOGIC_OT")) {
km = WM_keymap_find_all(wm, "Logic Editor", sl->spacetype, RGN_TYPE_WINDOW);
}
/* Outliner. */
else if (STRPREFIX(opname, "OUTLINER_OT")) {
km = WM_keymap_find_all(wm, "Outliner", sl->spacetype, RGN_TYPE_WINDOW);
}
/* Transform. */
else if (STRPREFIX(opname, "TRANSFORM_OT")) {
/* Check for relevant editor. */
switch (sl->spacetype) {
case SPACE_VIEW3D:
km = WM_keymap_find_all(wm, "3D View", sl->spacetype, RGN_TYPE_WINDOW);
break;
case SPACE_GRAPH:
km = WM_keymap_find_all(wm, "Graph Editor", sl->spacetype, RGN_TYPE_WINDOW);
break;
case SPACE_ACTION:
km = WM_keymap_find_all(wm, "Dopesheet", sl->spacetype, RGN_TYPE_WINDOW);
break;
case SPACE_NLA:
km = WM_keymap_find_all(wm, "NLA Editor", sl->spacetype, RGN_TYPE_WINDOW);
break;
case SPACE_IMAGE:
km = WM_keymap_find_all(wm, "UV Editor", SPACE_EMPTY, RGN_TYPE_WINDOW);
break;
case SPACE_NODE:
km = WM_keymap_find_all(wm, "Node Editor", sl->spacetype, RGN_TYPE_WINDOW);
break;
case SPACE_SEQ:
km = WM_keymap_find_all(wm, "Sequencer", sl->spacetype, RGN_TYPE_WINDOW);
break;
}
}
/* User Interface. */
else if (STRPREFIX(opname, "UI_OT")) {
km = WM_keymap_find_all(wm, "User Interface", SPACE_EMPTY, RGN_TYPE_WINDOW);
}
/* Geometry. */
else if (STRPREFIX(opname, "GEOMETRY_OT")) {
switch (sl->spacetype) {
case SPACE_VIEW3D:
switch (CTX_data_mode_enum(C)) {
case CTX_MODE_EDIT_MESH:
km = WM_keymap_find_all(wm, "Mesh", SPACE_EMPTY, RGN_TYPE_WINDOW);
break;
case CTX_MODE_EDIT_CURVES:
km = WM_keymap_find_all(wm, "Curves", SPACE_EMPTY, RGN_TYPE_WINDOW);
break;
case CTX_MODE_EDIT_POINTCLOUD:
km = WM_keymap_find_all(wm, "Point Cloud", SPACE_EMPTY, RGN_TYPE_WINDOW);
break;
case CTX_MODE_SCULPT:
km = WM_keymap_find_all(wm, "Sculpt", SPACE_EMPTY, RGN_TYPE_WINDOW);
break;
case CTX_MODE_SCULPT_CURVES:
km = WM_keymap_find_all(wm, "Sculpt Curves", SPACE_EMPTY, RGN_TYPE_WINDOW);
break;
default:
break;
}
break;
}
}
return km;
}
static bool wm_keymap_item_uses_modifier(const wmKeyMapItem *kmi, const int event_modifier)
{
if (kmi->ctrl != KM_ANY) {
if ((kmi->ctrl == KM_NOTHING) != ((event_modifier & KM_CTRL) == 0)) {
return false;
}
}
if (kmi->alt != KM_ANY) {
if ((kmi->alt == KM_NOTHING) != ((event_modifier & KM_ALT) == 0)) {
return false;
}
}
if (kmi->shift != KM_ANY) {
if ((kmi->shift == KM_NOTHING) != ((event_modifier & KM_SHIFT) == 0)) {
return false;
}
}
if (kmi->oskey != KM_ANY) {
if ((kmi->oskey == KM_NOTHING) != ((event_modifier & KM_OSKEY) == 0)) {
return false;
}
}
if (kmi->hyper != KM_ANY) {
if ((kmi->hyper == KM_NOTHING) != ((event_modifier & KM_HYPER) == 0)) {
return false;
}
}
return true;
}
bool WM_keymap_uses_event_modifier(const wmKeyMap *keymap, const int event_modifier)
{
for (const wmKeyMapItem &kmi : keymap->items) {
if ((kmi.flag & KMI_INACTIVE) == 0) {
if (wm_keymap_item_uses_modifier(&kmi, event_modifier)) {
return true;
}
}
}
return false;
}
void WM_keymap_fix_linking() {}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,125 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* Menu Registry.
*/
#include <cstdio>
#include "DNA_windowmanager_types.h"
#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
#include "BLI_vector_set.hh"
#include "BKE_context.hh"
#include "BKE_screen.hh"
#include "BKE_workspace.hh"
#include "WM_api.hh"
#include "WM_types.hh"
namespace blender {
static auto &get_menu_type_map()
{
struct IDNameGetter {
StringRef operator()(const MenuType *value) const
{
return StringRef(value->idname);
}
};
static CustomIDVectorSet<MenuType *, IDNameGetter> map;
return map;
}
MenuType *WM_menutype_find(const StringRef idname, bool quiet)
{
if (!idname.is_empty()) {
if (MenuType *const *mt = get_menu_type_map().lookup_key_ptr_as(idname)) {
return *mt;
}
}
if (!quiet) {
printf("search for unknown menutype %s\n", std::string(idname).c_str());
}
return nullptr;
}
Span<MenuType *> WM_menutypes_registered_get()
{
return get_menu_type_map();
}
bool WM_menutype_add(MenuType *mt)
{
BLI_assert((mt->description == nullptr) || (mt->description[0]));
get_menu_type_map().add(mt);
return true;
}
void WM_menutype_freelink(MenuType *mt)
{
bool ok = get_menu_type_map().remove(mt);
MEM_delete(mt);
BLI_assert(ok);
UNUSED_VARS_NDEBUG(ok);
}
void WM_menutype_init()
{
/* Reserve size is set based on blender default setup. */
get_menu_type_map().reserve(512);
}
void WM_menutype_free()
{
for (MenuType *mt : get_menu_type_map()) {
if (mt->rna_ext.free) {
mt->rna_ext.free(mt->rna_ext.data);
}
MEM_delete(mt);
}
get_menu_type_map().clear();
}
bool WM_menutype_poll(bContext *C, MenuType *mt)
{
/* If we're tagged, only use compatible. */
if (mt->owner_id[0] != '\0') {
const WorkSpace *workspace = CTX_wm_workspace(C);
if (BKE_workspace_owner_id_check(workspace, mt->owner_id) == false) {
return false;
}
}
if (mt->poll != nullptr) {
return mt->poll(C, mt);
}
return true;
}
void WM_menutype_idname_visit_for_search(
const bContext * /*C*/,
PointerRNA * /*ptr*/,
PropertyRNA * /*prop*/,
const char * /*edit_text*/,
FunctionRef<void(StringPropertySearchVisitParams)> visit_fn)
{
for (MenuType *mt : get_menu_type_map()) {
StringPropertySearchVisitParams visit_params{};
visit_params.text = mt->idname;
visit_params.info = mt->label;
visit_fn(visit_params);
}
}
} // namespace blender

View File

@@ -0,0 +1,705 @@
/* SPDX-FileCopyrightText: 2007 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* Generic re-usable property definitions and accessors for operators to share.
* (`WM_operator_properties_*` functions).
*/
#include "DNA_ID_enums.h"
#include "DNA_space_types.h"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BLI_math_base.h"
#include "BLI_rect.h"
#include "UI_resources.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_enum_types.hh"
#include "RNA_prototypes.hh"
#include "ED_select_utils.hh"
#include "WM_api.hh"
#include "WM_types.hh"
namespace blender {
void WM_operator_properties_confirm_or_exec(wmOperatorType *ot)
{
PropertyRNA *prop;
prop = RNA_def_boolean(ot->srna, "confirm", true, "Confirm", "Prompt for confirmation");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
/**
* Extends rna_enum_fileselect_params_sort_items with a default item for operators to use.
*/
static const EnumPropertyItem *wm_operator_properties_filesel_sort_items_itemf(
bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free)
{
EnumPropertyItem *items;
const EnumPropertyItem default_item = {
FILE_SORT_DEFAULT,
"DEFAULT",
0,
"Default",
"Automatically determine sort method for files",
};
int totitem = 0;
RNA_enum_item_add(&items, &totitem, &default_item);
RNA_enum_items_add(&items, &totitem, rna_enum_fileselect_params_sort_items);
RNA_enum_item_end(&items, &totitem);
*r_free = true;
return items;
}
void WM_operator_properties_filesel(wmOperatorType *ot,
const int filter,
const short type,
const eFileSel_Action action,
const eFileSel_Flag flag,
const short display,
const short sort)
{
PropertyRNA *prop;
static const EnumPropertyItem file_display_items[] = {
{FILE_DEFAULTDISPLAY,
"DEFAULT",
0,
"Default",
"Automatically determine display type for files"},
{FILE_VERTICALDISPLAY,
"LIST_VERTICAL",
ICON_SHORTDISPLAY, /* Name of deprecated short list. */
"Short List",
"Display files as short list"},
{FILE_HORIZONTALDISPLAY,
"LIST_HORIZONTAL",
ICON_LONGDISPLAY, /* Name of deprecated long list. */
"Long List",
"Display files as a detailed list"},
{FILE_IMGDISPLAY, "THUMBNAIL", ICON_IMGDISPLAY, "Thumbnails", "Display files as thumbnails"},
{0, nullptr, 0, nullptr, nullptr},
};
if (flag & WM_FILESEL_FILEPATH) {
prop = RNA_def_string_file_path(
ot->srna, "filepath", nullptr, FILE_MAX, "File Path", "Path to file");
RNA_def_property_flag(prop, PROP_SKIP_PRESET);
}
if (flag & WM_FILESEL_DIRECTORY) {
prop = RNA_def_string_dir_path(
ot->srna, "directory", nullptr, FILE_MAX, "Directory", "Directory of the file");
RNA_def_property_flag(prop, PROP_SKIP_PRESET);
}
if (flag & WM_FILESEL_FILENAME) {
prop = RNA_def_string_file_name(
ot->srna, "filename", nullptr, FILE_MAX, "File Name", "Name of the file");
RNA_def_property_flag(prop, PROP_SKIP_PRESET);
}
if (flag & WM_FILESEL_FILES) {
prop = RNA_def_collection_runtime(ot->srna, "files", RNA_OperatorFileListElement, "Files", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE | PROP_SKIP_PRESET);
}
if ((flag & WM_FILESEL_SHOW_PROPS) == 0) {
prop = RNA_def_boolean(ot->srna,
"hide_props_region",
true,
"Hide Operator Properties",
"Collapse the region displaying the operator settings");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
/* NOTE: this is only used to check if we should highlight the filename area red when the
* filepath is an existing file. */
prop = RNA_def_boolean(ot->srna,
"check_existing",
action == FILE_SAVE,
"Check Existing",
"Check and warn on overwriting existing files");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "filter_blender", (filter & FILE_TYPE_BLENDER) != 0, "Filter .blend files", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna,
"filter_backup",
(filter & FILE_TYPE_BLENDER_BACKUP) != 0,
"Filter backup .blend files",
"");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "filter_image", (filter & FILE_TYPE_IMAGE) != 0, "Filter image files", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "filter_movie", (filter & FILE_TYPE_MOVIE) != 0, "Filter movie files", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "filter_python", (filter & FILE_TYPE_PYSCRIPT) != 0, "Filter Python files", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "filter_font", (filter & FILE_TYPE_FTFONT) != 0, "Filter font files", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "filter_sound", (filter & FILE_TYPE_SOUND) != 0, "Filter sound files", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "filter_text", (filter & FILE_TYPE_TEXT) != 0, "Filter text files", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "filter_archive", (filter & FILE_TYPE_ARCHIVE) != 0, "Filter archive files", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "filter_btx", (filter & FILE_TYPE_BTX) != 0, "Filter btx files", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "filter_alembic", (filter & FILE_TYPE_ALEMBIC) != 0, "Filter Alembic files", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "filter_usd", (filter & FILE_TYPE_USD) != 0, "Filter USD files", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "filter_obj", (filter & FILE_TYPE_OBJECT_IO) != 0, "Filter OBJ files", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna,
"filter_volume",
(filter & FILE_TYPE_VOLUME) != 0,
"Filter OpenVDB volume files",
"");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "filter_folder", (filter & FILE_TYPE_FOLDER) != 0, "Filter folders", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "filter_blenlib", (filter & FILE_TYPE_BLENDERLIB) != 0, "Filter Blender IDs", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
/* TODO: asset only filter? */
prop = RNA_def_int(
ot->srna,
"filemode",
type,
FILE_LOADLIB,
FILE_SPECIAL,
"File Browser Mode",
"The setting for the file browser mode to load a .blend file, a library or a special file",
FILE_LOADLIB,
FILE_SPECIAL);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
if (flag & WM_FILESEL_RELPATH) {
RNA_def_boolean(ot->srna,
"relative_path",
true,
"Relative Path",
"Select the file relative to the blend file");
}
if ((filter & FILE_TYPE_IMAGE) || (filter & FILE_TYPE_MOVIE)) {
prop = RNA_def_boolean(ot->srna, "show_multiview", false, "Enable Multi-View", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna, "use_multiview", false, "Use Multi-View", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
prop = RNA_def_enum(ot->srna, "display_type", file_display_items, display, "Display Type", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_enum(
ot->srna, "sort_method", rna_enum_dummy_NULL_items, sort, "File sorting mode", "");
RNA_def_enum_funcs(prop, wm_operator_properties_filesel_sort_items_itemf);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
void WM_operator_properties_id_lookup_set_from_id(PointerRNA *ptr, const ID *id)
{
PropertyRNA *prop_session_uid = RNA_struct_find_property(ptr, "session_uid");
PropertyRNA *prop_name = RNA_struct_find_property(ptr, "name");
if (prop_session_uid) {
RNA_int_set(ptr, "session_uid", int(id->session_uid));
}
else if (prop_name) {
RNA_string_set(ptr, "name", id->name + 2);
}
else {
BLI_assert_unreachable();
}
}
ID *WM_operator_properties_id_lookup_from_name_or_session_uid(Main *bmain,
PointerRNA *ptr,
const ID_Type type)
{
PropertyRNA *prop_session_uid = RNA_struct_find_property(ptr, "session_uid");
if (prop_session_uid && RNA_property_is_set(ptr, prop_session_uid)) {
const uint32_t session_uid = uint32_t(RNA_property_int_get(ptr, prop_session_uid));
return BKE_libblock_find_session_uid(bmain, type, session_uid);
}
PropertyRNA *prop_name = RNA_struct_find_property(ptr, "name");
if (prop_name && RNA_property_is_set(ptr, prop_name)) {
char name[MAX_ID_NAME - 2];
RNA_property_string_get(ptr, prop_name, name);
return BKE_libblock_find_name(bmain, type, name);
}
return nullptr;
}
bool WM_operator_properties_id_lookup_is_set(PointerRNA *ptr)
{
return RNA_struct_property_is_set(ptr, "session_uid") || RNA_struct_property_is_set(ptr, "name");
}
void WM_operator_properties_id_lookup(wmOperatorType *ot, const bool add_name_prop)
{
PropertyRNA *prop;
if (add_name_prop) {
prop = RNA_def_string(ot->srna,
"name",
nullptr,
MAX_ID_NAME - 2,
"Name",
"Name of the data-block to use by the operator");
RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
}
prop = RNA_def_int(ot->srna,
"session_uid",
0,
INT32_MIN,
INT32_MAX,
"Session UID",
"Session UID of the data-block to use by the operator",
INT32_MIN,
INT32_MAX);
RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
}
static void wm_operator_properties_select_action_ex(wmOperatorType *ot,
int default_action,
const EnumPropertyItem *select_actions,
bool hide_gui)
{
PropertyRNA *prop;
prop = RNA_def_enum(
ot->srna, "action", select_actions, default_action, "Action", "Selection action to execute");
if (hide_gui) {
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
}
void WM_operator_properties_select_action(wmOperatorType *ot, int default_action, bool hide_gui)
{
static const EnumPropertyItem select_actions[] = {
{SEL_TOGGLE, "TOGGLE", 0, "Toggle", "Toggle selection for all elements"},
{SEL_SELECT, "SELECT", 0, "Select", "Select all elements"},
{SEL_DESELECT, "DESELECT", 0, "Deselect", "Deselect all elements"},
{SEL_INVERT, "INVERT", 0, "Invert", "Invert selection of all elements"},
{0, nullptr, 0, nullptr, nullptr},
};
wm_operator_properties_select_action_ex(ot, default_action, select_actions, hide_gui);
}
void WM_operator_properties_select_action_simple(wmOperatorType *ot,
int default_action,
bool hide_gui)
{
static const EnumPropertyItem select_actions[] = {
{SEL_SELECT, "SELECT", 0, "Select", "Select all elements"},
{SEL_DESELECT, "DESELECT", 0, "Deselect", "Deselect all elements"},
{0, nullptr, 0, nullptr, nullptr},
};
wm_operator_properties_select_action_ex(ot, default_action, select_actions, hide_gui);
}
void WM_operator_properties_select_random(wmOperatorType *ot)
{
RNA_def_float_factor(ot->srna,
"ratio",
0.5f,
0.0f,
1.0f,
"Ratio",
"Portion of items to select randomly",
0.0f,
1.0f);
RNA_def_int(ot->srna,
"seed",
0,
0,
INT_MAX,
"Random Seed",
"Seed for the random number generator",
0,
255);
WM_operator_properties_select_action_simple(ot, SEL_SELECT, false);
}
int WM_operator_properties_select_random_seed_increment_get(wmOperator *op)
{
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "seed");
int value = RNA_property_int_get(op->ptr, prop);
if (op->flag & OP_IS_INVOKE) {
if (!RNA_property_is_set(op->ptr, prop)) {
value += 1;
RNA_property_int_set(op->ptr, prop, value);
}
}
return value;
}
void WM_operator_properties_select_all(wmOperatorType *ot)
{
WM_operator_properties_select_action(ot, SEL_TOGGLE, true);
}
void WM_operator_properties_border(wmOperatorType *ot)
{
PropertyRNA *prop;
prop = RNA_def_int(ot->srna, "xmin", 0, INT_MIN, INT_MAX, "X Min", "", INT_MIN, INT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_int(ot->srna, "xmax", 0, INT_MIN, INT_MAX, "X Max", "", INT_MIN, INT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_int(ot->srna, "ymin", 0, INT_MIN, INT_MAX, "Y Min", "", INT_MIN, INT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_int(ot->srna, "ymax", 0, INT_MIN, INT_MAX, "Y Max", "", INT_MIN, INT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna, "wait_for_input", true, "Wait for Input", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
void WM_operator_properties_border_to_rcti(wmOperator *op, rcti *r_rect)
{
r_rect->xmin = RNA_int_get(op->ptr, "xmin");
r_rect->ymin = RNA_int_get(op->ptr, "ymin");
r_rect->xmax = RNA_int_get(op->ptr, "xmax");
r_rect->ymax = RNA_int_get(op->ptr, "ymax");
}
void WM_operator_properties_border_to_rctf(wmOperator *op, rctf *r_rect)
{
rcti rect_i;
WM_operator_properties_border_to_rcti(op, &rect_i);
BLI_rctf_rcti_copy(r_rect, &rect_i);
}
Bounds<int2> WM_operator_properties_border_to_bounds(wmOperator *op)
{
return Bounds<int2>({RNA_int_get(op->ptr, "xmin"), RNA_int_get(op->ptr, "ymin")},
{RNA_int_get(op->ptr, "xmax"), RNA_int_get(op->ptr, "ymax")});
}
void WM_operator_properties_gesture_box_ex(wmOperatorType *ot, bool deselect, bool extend)
{
PropertyRNA *prop;
WM_operator_properties_border(ot);
if (deselect) {
prop = RNA_def_boolean(
ot->srna, "deselect", false, "Deselect", "Deselect rather than select items");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
if (extend) {
prop = RNA_def_boolean(ot->srna,
"extend",
true,
"Extend",
"Extend selection instead of deselecting everything first");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
}
void WM_operator_properties_use_cursor_init(wmOperatorType *ot)
{
PropertyRNA *prop = RNA_def_boolean(ot->srna,
"use_cursor_init",
true,
"Use Mouse Position",
"Allow the initial mouse position to be used");
RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
}
void WM_operator_properties_gesture_box_select(wmOperatorType *ot)
{
WM_operator_properties_gesture_box_ex(ot, true, true);
}
void WM_operator_properties_gesture_box(wmOperatorType *ot)
{
WM_operator_properties_gesture_box_ex(ot, false, false);
}
void WM_operator_properties_select_operation(wmOperatorType *ot)
{
static const EnumPropertyItem select_mode_items[] = {
{SEL_OP_SET, "SET", ICON_SELECT_SET, "Set", "Set a new selection"},
{SEL_OP_ADD, "ADD", ICON_SELECT_EXTEND, "Extend", "Extend existing selection"},
{SEL_OP_SUB, "SUB", ICON_SELECT_SUBTRACT, "Subtract", "Subtract existing selection"},
{SEL_OP_XOR, "XOR", ICON_SELECT_DIFFERENCE, "Difference", "Invert existing selection"},
{SEL_OP_AND, "AND", ICON_SELECT_INTERSECT, "Intersect", "Intersect existing selection"},
{0, nullptr, 0, nullptr, nullptr},
};
PropertyRNA *prop = RNA_def_enum(ot->srna, "mode", select_mode_items, SEL_OP_SET, "Mode", "");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
void WM_operator_properties_select_operation_simple(wmOperatorType *ot)
{
static const EnumPropertyItem select_mode_items[] = {
{SEL_OP_SET, "SET", ICON_SELECT_SET, "Set", "Set a new selection"},
{SEL_OP_ADD, "ADD", ICON_SELECT_EXTEND, "Extend", "Extend existing selection"},
{SEL_OP_SUB, "SUB", ICON_SELECT_SUBTRACT, "Subtract", "Subtract existing selection"},
{0, nullptr, 0, nullptr, nullptr},
};
PropertyRNA *prop = RNA_def_enum(ot->srna, "mode", select_mode_items, SEL_OP_SET, "Mode", "");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
void WM_operator_properties_select_walk_direction(wmOperatorType *ot)
{
static const EnumPropertyItem direction_items[] = {
{UI_SELECT_WALK_UP, "UP", 0, "Previous", ""},
{UI_SELECT_WALK_DOWN, "DOWN", 0, "Next", ""},
{UI_SELECT_WALK_LEFT, "LEFT", 0, "Left", ""},
{UI_SELECT_WALK_RIGHT, "RIGHT", 0, "Right", ""},
{0, nullptr, 0, nullptr, nullptr},
};
PropertyRNA *prop;
prop = RNA_def_enum(ot->srna,
"direction",
direction_items,
0,
"Walk Direction",
"Select/Deselect element in this direction");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
void WM_operator_properties_generic_select(wmOperatorType *ot)
{
/* On the initial mouse press, this is set by #WM_generic_select_modal() to let the select
* operator exec callback know that it should not __yet__ deselect other items when clicking on
* an already selected one. Instead should make sure the operator executes modal then (see
* #WM_generic_select_modal()), so that the exec callback can be called a second time on the
* mouse release event to do this part. */
PropertyRNA *prop = RNA_def_boolean(
ot->srna, "wait_to_deselect_others", false, "Wait to Deselect Others", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
/* Force the selection to act on mouse click, not press.
* Necessary for some cases, but isn't used much. */
prop = RNA_def_boolean(ot->srna,
"use_select_on_click",
false,
"Act on Click",
"Instead of selecting on mouse press, wait to see if there's drag event. "
"Otherwise select on mouse release");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
RNA_def_int(ot->srna, "mouse_x", 0, INT_MIN, INT_MAX, "Mouse X", "", INT_MIN, INT_MAX);
RNA_def_int(ot->srna, "mouse_y", 0, INT_MIN, INT_MAX, "Mouse Y", "", INT_MIN, INT_MAX);
}
void WM_operator_properties_gesture_box_zoom(wmOperatorType *ot)
{
WM_operator_properties_border(ot);
PropertyRNA *prop;
prop = RNA_def_boolean(ot->srna, "zoom_out", false, "Zoom Out", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
void WM_operator_properties_gesture_lasso(wmOperatorType *ot)
{
PropertyRNA *prop;
prop = RNA_def_collection_runtime(ot->srna, "path", RNA_OperatorMousePath, "Path", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna,
"use_smooth_stroke",
false,
"Stabilize Stroke",
"Selection lags behind mouse and follows a smoother path");
prop = RNA_def_float(ot->srna,
"smooth_stroke_factor",
0.75f,
0.5f,
0.99f,
"Smooth Stroke Factor",
"Higher values give a smoother stroke",
0.5f,
0.99f);
prop = RNA_def_int(ot->srna,
"smooth_stroke_radius",
35,
10,
200,
"Smooth Stroke Radius",
"Minimum distance from last point before selection continues",
10,
200);
RNA_def_property_subtype(prop, PROP_PIXEL);
}
void WM_operator_properties_gesture_polyline(wmOperatorType *ot)
{
PropertyRNA *prop;
prop = RNA_def_collection_runtime(ot->srna, "path", RNA_OperatorMousePath, "Path", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
void WM_operator_properties_gesture_straightline(wmOperatorType *ot, int cursor)
{
PropertyRNA *prop;
prop = RNA_def_int(ot->srna, "xstart", 0, INT_MIN, INT_MAX, "X Start", "", INT_MIN, INT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_int(ot->srna, "xend", 0, INT_MIN, INT_MAX, "X End", "", INT_MIN, INT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_int(ot->srna, "ystart", 0, INT_MIN, INT_MAX, "Y Start", "", INT_MIN, INT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_int(ot->srna, "yend", 0, INT_MIN, INT_MAX, "Y End", "", INT_MIN, INT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna, "flip", false, "Flip", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
if (cursor) {
prop = RNA_def_int(ot->srna,
"cursor",
cursor,
0,
INT_MAX,
"Cursor",
"Mouse cursor style to use during the modal operator",
0,
INT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN);
}
}
void WM_operator_properties_gesture_circle(wmOperatorType *ot)
{
PropertyRNA *prop;
const int radius_default = 25;
prop = RNA_def_int(ot->srna, "x", 0, INT_MIN, INT_MAX, "X", "", INT_MIN, INT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_int(ot->srna, "y", 0, INT_MIN, INT_MAX, "Y", "", INT_MIN, INT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
RNA_def_int(ot->srna, "radius", radius_default, 1, INT_MAX, "Radius", "", 1, INT_MAX);
prop = RNA_def_boolean(ot->srna, "wait_for_input", true, "Wait for Input", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
void WM_operator_properties_mouse_select(wmOperatorType *ot)
{
PropertyRNA *prop;
prop = RNA_def_boolean(ot->srna,
"extend",
false,
"Extend",
"Extend selection instead of deselecting everything first");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna, "deselect", false, "Deselect", "Remove from selection");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna, "toggle", false, "Toggle Selection", "Toggle the selection");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna,
"deselect_all",
false,
"Deselect On Nothing",
"Deselect all when nothing under the cursor");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
/* TODO: currently only used for the 3D viewport. */
prop = RNA_def_boolean(ot->srna,
"select_passthrough",
false,
"Only Select Unselected",
"Ignore the select action when the element is already selected");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
void WM_operator_properties_checker_interval(wmOperatorType *ot, bool nth_can_disable)
{
const int nth_default = nth_can_disable ? 0 : 1;
const int nth_min = min_ii(nth_default, 1);
RNA_def_int(ot->srna,
"skip",
nth_default,
nth_min,
INT_MAX,
"Deselected",
"Number of deselected elements in the repetitive sequence",
nth_min,
100);
RNA_def_int(ot->srna,
"nth",
1,
1,
INT_MAX,
"Selected",
"Number of selected elements in the repetitive sequence",
1,
100);
RNA_def_int(ot->srna,
"offset",
0,
INT_MIN,
INT_MAX,
"Offset",
"Offset from the starting point",
-100,
100);
}
void WM_operator_properties_checker_interval_from_op(wmOperator *op,
CheckerIntervalParams *op_params)
{
const int nth = RNA_int_get(op->ptr, "nth");
const int skip = RNA_int_get(op->ptr, "skip");
int offset = RNA_int_get(op->ptr, "offset");
op_params->nth = nth;
op_params->skip = skip;
/* So input of offset zero ends up being (nth - 1). */
op_params->offset = mod_i(offset, nth + skip);
}
bool WM_operator_properties_checker_interval_test(const CheckerIntervalParams *op_params,
int depth)
{
return ((op_params->skip == 0) ||
((op_params->offset + depth) % (op_params->skip + op_params->nth) >= op_params->skip));
}
} // namespace blender

View File

@@ -0,0 +1,651 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* Operator Registry.
*/
#include "MEM_guardedalloc.h"
#include "CLG_log.h"
#include "DNA_screen_types.h"
#include "DNA_windowmanager_types.h"
#include "BLT_translation.hh"
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BLI_vector_set.hh"
#include "BKE_context.hh"
#include "BKE_idprop.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_enum_types.hh"
#include "RNA_prototypes.hh"
#ifdef WITH_PYTHON
# include "BPY_extern.hh"
#endif
#include "WM_api.hh"
#include "WM_keymap.hh"
#include "WM_types.hh"
#include "wm.hh"
#include "wm_event_system.hh"
namespace blender {
#define UNDOCUMENTED_OPERATOR_TIP N_("(undocumented operator)")
static void wm_operatortype_free_macro(wmOperatorType *ot);
/* -------------------------------------------------------------------- */
/** \name Operator Type Registry
* \{ */
static auto &get_operators_map()
{
struct OperatorNameGetter {
StringRef operator()(const wmOperatorType *value) const
{
return StringRef(value->idname);
}
};
static auto map = []() {
CustomIDVectorSet<wmOperatorType *, OperatorNameGetter> map;
/* Reserve size is set based on blender default setup. */
map.reserve(2048);
return map;
}();
return map;
}
Span<wmOperatorType *> WM_operatortypes_registered_get()
{
return get_operators_map();
}
/** Counter for operator-properties that should not be tagged with #OP_PROP_TAG_ADVANCED. */
static int ot_prop_basic_count = -1;
wmOperatorType *WM_operatortype_find(const char *idname, bool quiet)
{
if (idname[0]) {
/* Needed to support python style names without the `_OT_` syntax. */
char idname_bl[OP_MAX_TYPENAME];
WM_operator_bl_idname(idname_bl, idname);
if (wmOperatorType *const *ot = get_operators_map().lookup_key_ptr_as(StringRef(idname_bl))) {
return *ot;
}
if (!quiet) {
CLOG_INFO(WM_LOG_OPERATORS, "Search for unknown operator '%s', '%s'", idname_bl, idname);
}
}
else {
if (!quiet) {
CLOG_INFO(WM_LOG_OPERATORS, "Search for empty operator");
}
}
return nullptr;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Operator Type Append
* \{ */
static wmOperatorType *wm_operatortype_append__begin()
{
wmOperatorType *ot = MEM_new<wmOperatorType>(__func__);
BLI_assert(ot_prop_basic_count == -1);
ot->srna = RNA_def_struct_ptr(&RNA_blender_rna_get(), "", RNA_OperatorProperties);
RNA_def_struct_property_tags(ot->srna, rna_enum_operator_property_tag_items);
/* Set the default i18n context now, so that opfunc can redefine it if needed! */
RNA_def_struct_translation_context(ot->srna, BLT_I18NCONTEXT_OPERATOR_DEFAULT);
ot->translation_context = BLT_I18NCONTEXT_OPERATOR_DEFAULT;
ot->cursor_pending = WM_CURSOR_PICK_AREA;
return ot;
}
static void wm_operatortype_append__end(wmOperatorType *ot)
{
if (ot->name == nullptr) {
CLOG_ERROR(WM_LOG_OPERATORS, "Operator '%s' has no name property", ot->idname);
}
BLI_assert((ot->description == nullptr) || (ot->description[0]));
/* Allow calling _begin without _end in operatortype creation. */
WM_operatortype_props_advanced_end(ot);
/* XXX All ops should have a description but for now allow them not to. */
RNA_def_struct_ui_text(
ot->srna, ot->name, ot->description ? ot->description : UNDOCUMENTED_OPERATOR_TIP);
RNA_def_struct_identifier(&RNA_blender_rna_get(), ot->srna, ot->idname);
BLI_assert(WM_operator_bl_idname_is_valid(ot->idname));
get_operators_map().add_new(ot);
/* Needed so any operators registered after startup will have their shortcuts set,
* in "register" scripts for example, see: #143838.
*
* This only has run-time implications when run after startup,
* it's a no-op when run beforehand, see: #WM_keyconfig_update_on_startup. */
WM_keyconfig_update_operatortype_tag();
}
/* All ops in 1 list (for time being... needs evaluation later). */
void WM_operatortype_append(void (*opfunc)(wmOperatorType *))
{
wmOperatorType *ot = wm_operatortype_append__begin();
opfunc(ot);
wm_operatortype_append__end(ot);
}
void WM_operatortype_append_ptr(void (*opfunc)(wmOperatorType *, void *), void *userdata)
{
wmOperatorType *ot = wm_operatortype_append__begin();
opfunc(ot, userdata);
wm_operatortype_append__end(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Operator Type Removal & Property Search
* \{ */
void WM_operatortype_remove_ptr(wmOperatorType *ot)
{
BLI_assert(ot == WM_operatortype_find(ot->idname, false));
#ifdef WITH_PYTHON
/* The 'unexposed' type (inherited from #RNA_OperatorProperties) created for this operator type's
* properties may have had a python type representation created. This needs to be dereferenced
* manually here, as other #bpy_class_free (which is part of the unregistering code for runtime
* operators) will not be able to handle it. */
BPY_free_srna_pytype(ot->srna);
#endif
RNA_struct_free(&RNA_blender_rna_get(), ot->srna);
if (ot->last_properties) {
IDP_FreeProperty(ot->last_properties);
}
if (ot->macro.first) {
wm_operatortype_free_macro(ot);
}
get_operators_map().remove(ot);
WM_keyconfig_update_operatortype_tag();
MEM_delete(ot);
}
bool WM_operatortype_remove(const char *idname)
{
wmOperatorType *ot = WM_operatortype_find(idname, false);
if (ot == nullptr) {
return false;
}
WM_operatortype_remove_ptr(ot);
return true;
}
static void operatortype_ghash_free_cb(wmOperatorType *ot)
{
if (ot->last_properties) {
IDP_FreeProperty(ot->last_properties);
}
if (ot->macro.first) {
wm_operatortype_free_macro(ot);
}
if (ot->rna_ext.srna) {
/* A Python operator, allocates its own string. */
MEM_delete(ot->idname);
}
MEM_delete(ot);
}
void wm_operatortype_free()
{
for (wmOperatorType *ot : get_operators_map()) {
operatortype_ghash_free_cb(ot);
}
get_operators_map().clear();
}
void WM_operatortype_props_advanced_begin(wmOperatorType *ot)
{
if (ot_prop_basic_count == -1) {
/* Don't do anything if _begin was called before, but not _end. */
ot_prop_basic_count = RNA_struct_count_properties(ot->srna);
}
}
void WM_operatortype_props_advanced_end(wmOperatorType *ot)
{
int counter = 0;
if (ot_prop_basic_count == -1) {
/* WM_operatortype_props_advanced_begin was not called. Don't do anything. */
return;
}
PointerRNA struct_ptr = WM_operator_properties_create_ptr(ot);
RNA_STRUCT_BEGIN (&struct_ptr, prop) {
counter++;
if (counter > ot_prop_basic_count) {
WM_operatortype_prop_tag(prop, OP_PROP_TAG_ADVANCED);
}
}
RNA_STRUCT_END;
ot_prop_basic_count = -1;
}
void WM_operatortype_last_properties_clear_all()
{
for (wmOperatorType *ot : get_operators_map()) {
if (ot->last_properties) {
IDP_FreeProperty(ot->last_properties);
ot->last_properties = nullptr;
}
}
}
void WM_operatortype_idname_visit_for_search(
const bContext * /*C*/,
PointerRNA * /*ptr*/,
PropertyRNA * /*prop*/,
const char * /*edit_text*/,
FunctionRef<void(StringPropertySearchVisitParams)> visit_fn)
{
for (wmOperatorType *ot : get_operators_map()) {
char idname_py[OP_MAX_TYPENAME];
WM_operator_py_idname(idname_py, ot->idname);
StringPropertySearchVisitParams visit_params{};
visit_params.text = idname_py;
visit_params.info = ot->name;
visit_fn(visit_params);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Operator Macro Type
* \{ */
struct MacroData {
wmOperatorStatus retval;
};
static void wm_macro_start(wmOperator *op)
{
if (op->customdata == nullptr) {
op->customdata = MEM_new_zeroed<MacroData>("MacroData");
}
}
static wmOperatorStatus wm_macro_end(wmOperator *op, wmOperatorStatus retval)
{
MacroData *md = static_cast<MacroData *>(op->customdata);
if (retval & (OPERATOR_CANCELLED | OPERATOR_INTERFACE)) {
if (md && (md->retval & OPERATOR_FINISHED)) {
retval |= OPERATOR_FINISHED;
retval &= ~(OPERATOR_CANCELLED | OPERATOR_INTERFACE);
}
}
/* If modal is ending, free custom data. */
if (retval & (OPERATOR_FINISHED | OPERATOR_CANCELLED)) {
if (md) {
MEM_delete(md);
op->customdata = nullptr;
}
}
return retval;
}
/* Macro exec only runs exec calls. */
static wmOperatorStatus wm_macro_exec(bContext *C, wmOperator *op)
{
wmOperatorStatus retval = OPERATOR_FINISHED;
const eOperator_Flag op_inherited_flag = op->flag & (OP_IS_REPEAT | OP_IS_REPEAT_LAST);
wm_macro_start(op);
for (wmOperator &opm : op->macro) {
if (opm.type->exec == nullptr) {
CLOG_WARN(WM_LOG_OPERATORS, "'%s' can't exec macro", opm.type->idname);
continue;
}
opm.flag |= op_inherited_flag;
retval = opm.type->exec(C, &opm);
opm.flag &= ~op_inherited_flag;
OPERATOR_RETVAL_CHECK(retval);
if (retval & OPERATOR_FINISHED) {
MacroData *md = static_cast<MacroData *>(op->customdata);
md->retval = OPERATOR_FINISHED; /* Keep in mind that at least one operator finished. */
}
else {
break; /* Operator didn't finish, end macro. */
}
}
return wm_macro_end(op, retval);
}
static wmOperatorStatus wm_macro_invoke_internal(bContext *C,
wmOperator *op,
const wmEvent *event,
wmOperator *opm)
{
wmOperatorStatus retval = OPERATOR_FINISHED;
const eOperator_Flag op_inherited_flag = op->flag & (OP_IS_REPEAT | OP_IS_REPEAT_LAST);
/* Start from operator received as argument. */
for (; opm; opm = opm->next) {
opm->flag |= op_inherited_flag;
if (opm->type->invoke) {
retval = opm->type->invoke(C, opm, event);
}
else if (opm->type->exec) {
retval = opm->type->exec(C, opm);
}
opm->flag &= ~op_inherited_flag;
OPERATOR_RETVAL_CHECK(retval);
BLI_movelisttolist(&op->reports->list, &opm->reports->list);
if (retval & OPERATOR_FINISHED) {
MacroData *md = static_cast<MacroData *>(op->customdata);
md->retval = OPERATOR_FINISHED; /* Keep in mind that at least one operator finished. */
}
else {
break; /* Operator didn't finish, end macro. */
}
}
return wm_macro_end(op, retval);
}
static wmOperatorStatus wm_macro_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
wm_macro_start(op);
return wm_macro_invoke_internal(C, op, event, static_cast<wmOperator *>(op->macro.first));
}
static wmOperatorStatus wm_macro_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
wmOperator *opm = op->opm;
wmOperatorStatus retval = OPERATOR_FINISHED;
if (opm == nullptr) {
CLOG_ERROR(WM_LOG_OPERATORS, "macro error, calling nullptr modal()");
}
else {
retval = opm->type->modal(C, opm, event);
OPERATOR_RETVAL_CHECK(retval);
/* If we're halfway through using a tool and cancel it, clear the options, see: #37149. */
if (retval & OPERATOR_CANCELLED) {
WM_operator_properties_clear(opm->ptr);
}
/* If this one is done but it's not the last operator in the macro. */
if ((retval & OPERATOR_FINISHED) && opm->next) {
MacroData *md = static_cast<MacroData *>(op->customdata);
md->retval = OPERATOR_FINISHED; /* Keep in mind that at least one operator finished. */
retval = wm_macro_invoke_internal(C, op, event, opm->next);
/* If new operator is modal and also added its own handler. */
if (retval & OPERATOR_RUNNING_MODAL && op->opm != opm) {
wmWindow *win = CTX_wm_window(C);
wmEventHandler_Op *handler;
handler = static_cast<wmEventHandler_Op *>(
BLI_findptr(&win->runtime->modalhandlers, op, offsetof(wmEventHandler_Op, op)));
if (handler) {
BLI_remlink(&win->runtime->modalhandlers, handler);
wm_event_free_handler(&handler->head);
}
/* If operator is blocking, grab cursor.
* This may end up grabbing twice, but we don't care. */
if (op->opm->type->flag & OPTYPE_BLOCKING) {
int wrap = WM_CURSOR_WRAP_NONE;
const rcti *wrap_region = nullptr;
if ((op->opm->flag & OP_IS_MODAL_GRAB_CURSOR) ||
(op->opm->type->flag & OPTYPE_GRAB_CURSOR_XY))
{
wrap = WM_CURSOR_WRAP_XY;
}
else if (op->opm->type->flag & OPTYPE_GRAB_CURSOR_X) {
wrap = WM_CURSOR_WRAP_X;
}
else if (op->opm->type->flag & OPTYPE_GRAB_CURSOR_Y) {
wrap = WM_CURSOR_WRAP_Y;
}
if (wrap) {
ARegion *region = CTX_wm_region(C);
if (region) {
wrap_region = &region->winrct;
}
}
WM_cursor_grab_enable(win, eWM_CursorWrapAxis(wrap), wrap_region, false);
}
}
}
}
return wm_macro_end(op, retval);
}
static void wm_macro_cancel(bContext *C, wmOperator *op)
{
/* Call cancel on the current modal operator, if any. */
if (op->opm && op->opm->type->cancel) {
op->opm->type->cancel(C, op->opm);
}
wm_macro_end(op, OPERATOR_CANCELLED);
}
wmOperatorType *WM_operatortype_append_macro(const char *idname,
const char *name,
const char *description,
int flag)
{
wmOperatorType *ot;
const char *i18n_context;
if (WM_operatortype_find(idname, true)) {
CLOG_ERROR(WM_LOG_OPERATORS, "operator %s exists, cannot create macro", idname);
return nullptr;
}
ot = MEM_new<wmOperatorType>(__func__);
ot->srna = RNA_def_struct_ptr(&RNA_blender_rna_get(), "", RNA_OperatorProperties);
ot->idname = idname;
ot->name = name;
ot->description = description;
ot->flag = OPTYPE_MACRO | flag;
ot->exec = wm_macro_exec;
ot->invoke = wm_macro_invoke;
ot->modal = wm_macro_modal;
ot->cancel = wm_macro_cancel;
ot->poll = nullptr;
/* XXX All ops should have a description but for now allow them not to. */
BLI_assert((ot->description == nullptr) || (ot->description[0]));
RNA_def_struct_ui_text(
ot->srna, ot->name, ot->description ? ot->description : UNDOCUMENTED_OPERATOR_TIP);
RNA_def_struct_identifier(&RNA_blender_rna_get(), ot->srna, ot->idname);
/* Use i18n context from rna_ext.srna if possible (py operators). */
i18n_context = ot->rna_ext.srna ? RNA_struct_translation_context(ot->rna_ext.srna) :
BLT_I18NCONTEXT_OPERATOR_DEFAULT;
RNA_def_struct_translation_context(ot->srna, i18n_context);
ot->translation_context = i18n_context;
BLI_assert(WM_operator_bl_idname_is_valid(ot->idname));
get_operators_map().add_new(ot);
return ot;
}
void WM_operatortype_append_macro_ptr(void (*opfunc)(wmOperatorType *ot, void *userdata),
void *userdata)
{
wmOperatorType *ot;
ot = MEM_new<wmOperatorType>(__func__);
ot->srna = RNA_def_struct_ptr(&RNA_blender_rna_get(), "", RNA_OperatorProperties);
ot->flag = OPTYPE_MACRO;
ot->exec = wm_macro_exec;
ot->invoke = wm_macro_invoke;
ot->modal = wm_macro_modal;
ot->cancel = wm_macro_cancel;
ot->poll = nullptr;
/* XXX All ops should have a description but for now allow them not to. */
BLI_assert((ot->description == nullptr) || (ot->description[0]));
/* Set the default i18n context now, so that opfunc can redefine it if needed! */
RNA_def_struct_translation_context(ot->srna, BLT_I18NCONTEXT_OPERATOR_DEFAULT);
ot->translation_context = BLT_I18NCONTEXT_OPERATOR_DEFAULT;
opfunc(ot, userdata);
RNA_def_struct_ui_text(
ot->srna, ot->name, ot->description ? ot->description : UNDOCUMENTED_OPERATOR_TIP);
RNA_def_struct_identifier(&RNA_blender_rna_get(), ot->srna, ot->idname);
BLI_assert(WM_operator_bl_idname_is_valid(ot->idname));
get_operators_map().add_new(ot);
}
wmOperatorTypeMacro *WM_operatortype_macro_define(wmOperatorType *ot, const char *idname)
{
wmOperatorTypeMacro *otmacro = MEM_new<wmOperatorTypeMacro>("wmOperatorTypeMacro");
STRNCPY(otmacro->idname, idname);
/* Do this on first use, since operator definitions might have been not done yet. */
WM_operator_properties_alloc(&(otmacro->ptr), &(otmacro->properties), idname);
WM_operator_properties_sanitize(otmacro->ptr, true);
BLI_addtail(&ot->macro, otmacro);
/* Operator should always be found but in the event its not. don't segfault. */
if (wmOperatorType *otsub = WM_operatortype_find(idname, false)) {
RNA_def_pointer_runtime(ot->srna, otsub->idname, otsub->srna, otsub->name, otsub->description);
}
return otmacro;
}
static void wm_operatortype_free_macro(wmOperatorType *ot)
{
for (wmOperatorTypeMacro &otmacro : ot->macro) {
if (otmacro.ptr) {
WM_operator_properties_free(otmacro.ptr);
MEM_delete(otmacro.ptr);
}
}
ot->macro.free_no_destruct();
}
std::string WM_operatortype_name(wmOperatorType *ot, PointerRNA *properties)
{
std::string name;
if (ot->get_name && properties) {
name = ot->get_name(ot, properties);
}
return name.empty() ? std::string(RNA_struct_ui_name(ot->srna)) : name;
}
std::string WM_operatortype_description(bContext *C, wmOperatorType *ot, PointerRNA *properties)
{
if (ot->get_description && properties) {
std::string description = ot->get_description(C, ot, properties);
if (!description.empty()) {
return description;
}
}
const char *info = RNA_struct_ui_description(ot->srna);
if (info && info[0]) {
return info;
}
return "";
}
std::string WM_operatortype_description_or_name(bContext *C,
wmOperatorType *ot,
PointerRNA *properties)
{
std::string text = WM_operatortype_description(C, ot, properties);
if (text.empty()) {
std::string text_orig = WM_operatortype_name(ot, properties);
if (!text_orig.empty()) {
return text_orig;
}
}
return text;
}
bool WM_operator_depends_on_cursor(bContext &C, wmOperatorType &ot, PointerRNA *properties)
{
if (ot.flag & OPTYPE_DEPENDS_ON_CURSOR) {
return true;
}
if (ot.depends_on_cursor) {
return ot.depends_on_cursor(C, ot, properties);
}
return false;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,342 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* Utilities for Implementing Operators
*/
#include <cmath>
#include "BLI_array.hh"
#include "BLI_string.h"
#include "BKE_context.hh"
#include "BKE_global.hh"
#include "BKE_layer.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "WM_api.hh" /* Own include. */
#include "WM_types.hh"
#include "MEM_guardedalloc.h"
#include "ED_object.hh"
#include "ED_screen.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Generic Utilities
* \{ */
wmOperatorStatus WM_operator_flag_only_pass_through_on_press(wmOperatorStatus retval,
const wmEvent *event)
{
if (event->val != KM_PRESS) {
if (retval & OPERATOR_PASS_THROUGH) {
/* Operators that use this function should either finish or cancel,
* otherwise non-press events will be passed through to other key-map items. */
BLI_assert((retval & ~OPERATOR_PASS_THROUGH) != 0);
if (retval & (OPERATOR_FINISHED | OPERATOR_CANCELLED)) {
retval &= ~OPERATOR_PASS_THROUGH;
}
}
}
return retval;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Value Interaction Helper
*
* Possible additions (add as needed).
* - Int support.
* - Configurable motion (x/y).
*
* \{ */
struct ValueInteraction {
struct {
float mval[2];
float prop_value;
} init;
struct {
float prop_value;
bool is_snap;
bool is_precise;
} prev;
float range[2];
struct {
ScrArea *area;
ARegion *region;
} context_vars;
};
static void interactive_value_init(bContext *C,
ValueInteraction *inter,
const wmEvent *event,
const float value_final,
const float range[2])
{
inter->context_vars.area = CTX_wm_area(C);
inter->context_vars.region = CTX_wm_region(C);
inter->init.mval[0] = event->mval[0];
inter->init.mval[1] = event->mval[1];
inter->init.prop_value = value_final;
inter->prev.prop_value = value_final;
inter->range[0] = range[0];
inter->range[1] = range[1];
}
static void interactive_value_init_from_property(
bContext *C, ValueInteraction *inter, const wmEvent *event, PointerRNA *ptr, PropertyRNA *prop)
{
float range[2];
float step, precision;
RNA_property_float_ui_range(ptr, prop, &range[0], &range[1], &step, &precision);
const float value_final = RNA_property_float_get(ptr, prop);
interactive_value_init(C, inter, event, value_final, range);
}
static void interactive_value_exit(ValueInteraction *inter)
{
ED_area_status_text(inter->context_vars.area, nullptr);
}
static bool interactive_value_update(ValueInteraction *inter,
const wmEvent *event,
float *r_value_final)
{
const int mval_axis = 0;
const float value_scale = 4.0f; /* Could be option. */
const float value_range = inter->range[1] - inter->range[0];
const int mval_curr = event->mval[mval_axis];
const int mval_init = inter->init.mval[mval_axis];
float value_delta = (inter->init.prop_value +
((float(mval_curr - mval_init) / inter->context_vars.region->winx) *
value_range)) *
value_scale;
if (event->modifier & KM_CTRL) {
const double snap = 0.1;
value_delta = roundf(double(value_delta) / snap) * snap;
}
if (event->modifier & KM_SHIFT) {
value_delta *= 0.1f;
}
const float value_final = inter->init.prop_value + value_delta;
const bool changed = value_final != inter->prev.prop_value;
if (changed) {
/* Set the property for the operator and call its modal function. */
char str[64];
SNPRINTF(str, "%.4f", value_final);
ED_area_status_text(inter->context_vars.area, str);
}
inter->prev.prop_value = value_final;
inter->prev.is_snap = (event->modifier & KM_CTRL) != 0;
inter->prev.is_precise = (event->modifier & KM_SHIFT) != 0;
*r_value_final = value_final;
return changed;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Object Edit Mode Coords (Modal Callbacks)
*
* \note We could support object mode coords too, it's just not needed at the moment.
* \{ */
struct ObCustomData_ForEditMode {
int launch_event;
bool wait_for_input;
bool is_active;
bool is_first;
ValueInteraction inter;
/** This could be split into a sub-type if we support different kinds of data. */
Array<std::unique_ptr<ed::object::XFormObjectData>> objects_xform;
};
/* Internal callback to free. */
static void op_generic_value_exit(wmOperator *op)
{
ObCustomData_ForEditMode *cd = static_cast<ObCustomData_ForEditMode *>(op->customdata);
if (cd) {
interactive_value_exit(&cd->inter);
MEM_delete(cd);
}
G.moving &= ~G_TRANSFORM_EDIT;
}
static void op_generic_value_restore(wmOperator *op)
{
ObCustomData_ForEditMode *cd = static_cast<ObCustomData_ForEditMode *>(op->customdata);
for (std::unique_ptr<ed::object::XFormObjectData> &xod : cd->objects_xform) {
ed::object::data_xform_restore(*xod);
ed::object::data_xform_tag_update(*xod);
}
}
static void op_generic_value_cancel(bContext * /*C*/, wmOperator *op)
{
op_generic_value_exit(op);
}
static wmOperatorStatus op_generic_value_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
if (RNA_property_is_set(op->ptr, op->type->prop)) {
return WM_operator_call_notest(C, op);
}
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, view_layer, CTX_wm_view3d(C));
if (objects.is_empty()) {
return OPERATOR_CANCELLED;
}
ObCustomData_ForEditMode *cd = MEM_new<ObCustomData_ForEditMode>(__func__);
cd->launch_event = WM_userdef_event_type_from_keymap_type(event->type);
cd->wait_for_input = RNA_boolean_get(op->ptr, "wait_for_input");
cd->is_active = !cd->wait_for_input;
cd->is_first = true;
if (cd->wait_for_input == false) {
interactive_value_init_from_property(C, &cd->inter, event, op->ptr, op->type->prop);
}
cd->objects_xform.reinitialize(objects.size());
for (const int i : objects.index_range()) {
Object *obedit = objects[i];
cd->objects_xform[i] = ed::object::data_xform_create_from_edit_mode(obedit->data);
}
op->customdata = cd;
WM_event_add_modal_handler(C, op);
G.moving |= G_TRANSFORM_EDIT;
return OPERATOR_RUNNING_MODAL;
}
static wmOperatorStatus op_generic_value_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
ObCustomData_ForEditMode *cd = static_cast<ObCustomData_ForEditMode *>(op->customdata);
/* Special case, check if we release the event that activated this operator. */
if ((event->type == cd->launch_event) && (event->val == KM_RELEASE)) {
if (cd->wait_for_input == false) {
op_generic_value_exit(op);
return OPERATOR_FINISHED;
}
}
switch (event->type) {
case MOUSEMOVE:
case EVT_LEFTCTRLKEY:
case EVT_RIGHTCTRLKEY:
case EVT_LEFTSHIFTKEY:
case EVT_RIGHTSHIFTKEY: {
float value_final;
if (cd->is_active && interactive_value_update(&cd->inter, event, &value_final)) {
wmWindowManager *wm = CTX_wm_manager(C);
RNA_property_float_set(op->ptr, op->type->prop, value_final);
if (cd->is_first == false) {
op_generic_value_restore(op);
}
wm->op_undo_depth++;
const wmOperatorStatus retval = op->type->exec(C, op);
OPERATOR_RETVAL_CHECK(retval);
wm->op_undo_depth--;
cd->is_first = false;
if ((retval & OPERATOR_FINISHED) == 0) {
op_generic_value_exit(op);
return OPERATOR_CANCELLED;
}
}
break;
}
case EVT_RETKEY:
case EVT_PADENTER:
case LEFTMOUSE: {
if (cd->wait_for_input) {
if (event->val == KM_PRESS) {
if (cd->is_active == false) {
cd->is_active = true;
interactive_value_init_from_property(C, &cd->inter, event, op->ptr, op->type->prop);
}
}
else if (event->val == KM_RELEASE) {
if (cd->is_active == true) {
op_generic_value_exit(op);
return OPERATOR_FINISHED;
}
}
}
else {
if (event->val == KM_RELEASE) {
op_generic_value_exit(op);
return OPERATOR_FINISHED;
}
}
break;
}
case EVT_ESCKEY:
case RIGHTMOUSE: {
if (event->val == KM_PRESS) {
if (cd->is_active == true) {
op_generic_value_restore(op);
}
op_generic_value_exit(op);
return OPERATOR_CANCELLED;
}
break;
}
default: {
break;
}
}
return OPERATOR_RUNNING_MODAL;
}
void WM_operator_type_modal_from_exec_for_object_edit_coords(wmOperatorType *ot)
{
PropertyRNA *prop;
BLI_assert(ot->modal == nullptr);
BLI_assert(ot->invoke == nullptr);
BLI_assert(ot->cancel == nullptr);
BLI_assert(ot->prop != nullptr);
ot->invoke = op_generic_value_invoke;
ot->modal = op_generic_value_modal;
ot->cancel = op_generic_value_cancel;
prop = RNA_def_boolean(ot->srna, "wait_for_input", true, "Wait for Input", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
/** \} */
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,94 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* Panel Registry.
*
* \note Unlike menu, and other registries, this doesn't *own* the PanelType.
*
* For popups/popovers only, regions handle panel types by including them in local lists.
*/
#include <cstdio>
#include "DNA_windowmanager_types.h"
#include "BLI_utildefines.h"
#include "BLI_vector_set.hh"
#include "BKE_screen.hh"
#include "WM_api.hh"
namespace blender {
static auto &get_panel_type_map()
{
struct IDNameGetter {
StringRef operator()(const PanelType *value) const
{
return StringRef(value->idname);
}
};
static CustomIDVectorSet<PanelType *, IDNameGetter> map;
return map;
}
PanelType *WM_paneltype_find(const StringRef idname, bool quiet)
{
if (!idname.is_empty()) {
if (PanelType *const *pt = get_panel_type_map().lookup_key_ptr_as(idname)) {
return *pt;
}
}
if (!quiet) {
printf("search for unknown paneltype %s\n", std::string(idname).c_str());
}
return nullptr;
}
bool WM_paneltype_add(PanelType *pt)
{
get_panel_type_map().add(pt);
return true;
}
void WM_paneltype_remove(PanelType *pt)
{
const bool ok = get_panel_type_map().remove(pt);
BLI_assert(ok);
UNUSED_VARS_NDEBUG(ok);
}
void WM_paneltype_init()
{
/* Reserve size is set based on blender default setup. */
get_panel_type_map().reserve(512);
}
void WM_paneltype_clear()
{
get_panel_type_map().clear();
}
void WM_paneltype_idname_visit_for_search(
const bContext * /*C*/,
PointerRNA * /*ptr*/,
PropertyRNA * /*prop*/,
const char * /*edit_text*/,
FunctionRef<void(StringPropertySearchVisitParams)> visit_fn)
{
for (PanelType *pt : get_panel_type_map()) {
StringPropertySearchVisitParams visit_params{};
visit_params.text = pt->idname;
visit_params.info = pt->label;
visit_fn(visit_params);
}
}
} // namespace blender

View File

@@ -0,0 +1,100 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* Interactions with the underlying platform.
*/
#include "WM_api.hh" /* Own include. */
#ifdef WIN32
# include "BLI_winstuff.h"
#elif defined(__APPLE__)
/* Pass. */
#else
# ifdef WITH_PYTHON
# include "BLI_string.h"
# include "BKE_context.hh"
# include "BPY_extern_run.hh"
# endif
#endif
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Register File Association
* \{ */
bool WM_platform_associate_set(bool do_register, bool all_users, char **r_error_msg)
{
bool result = false;
*r_error_msg = nullptr;
#ifdef WIN32
{
if (all_users) {
if (do_register) {
result = BLI_windows_execute_self("--register-allusers", true, true, true);
}
else {
result = BLI_windows_execute_self("--unregister-allusers", true, true, true);
}
}
else {
if (do_register) {
result = BLI_windows_register_blend_extension(false);
}
else {
result = BLI_windows_unregister_blend_extension(false);
}
}
}
#elif defined(__APPLE__)
/* Pass. */
UNUSED_VARS(do_register, all_users);
#else
{
# ifdef WITH_PYTHON
BPy_RunErrInfo err_info = {};
err_info.use_single_line_error = true;
err_info.r_string = r_error_msg;
const char *imports[] = {
"_bpy_internal",
"_bpy_internal.platform.freedesktop",
nullptr,
};
char expr_buf[128];
SNPRINTF(expr_buf,
"_bpy_internal.platform.freedesktop.%s(all_users=%d)",
do_register ? "register" : "unregister",
int(all_users));
/* NOTE: this could be null, however the running a script without `bpy.context` access
* is a rare enough situation that it's better to keep this a requirement of the API and
* pass in a temporary context instead of making an exception for this one case. */
bContext *C_temp = CTX_create();
char *value = nullptr;
if (BPY_run_string_as_string_or_none(C_temp, imports, expr_buf, &err_info, &value)) {
result = (value == nullptr);
*r_error_msg = value;
}
/* Else `r_error_msg` will be set to a single line exception. */
CTX_free(C_temp);
# else
/* Pass. */
UNUSED_VARS(do_register, all_users);
# endif
}
#endif
return result;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,258 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*/
#include "wm_platform_support.hh"
#include "wm_window_private.hh"
#include <cstring>
#include "BLI_dynstr.h"
#include "BLI_fileops.h"
#include "BLI_linklist.h"
#include "BLI_path_utils.hh"
#include "BLI_string.h"
#include "BLT_translation.hh"
#include "BKE_appdir.hh"
#include "BKE_global.hh"
#include "GPU_context.hh"
#include "GPU_platform.hh"
#include "CLG_log.h"
namespace blender {
#define WM_PLATFORM_SUPPORT_TEXT_SIZE 1024
static CLG_LogRef LOG = {"gpu.platform"};
/**
* Check if user has already approved the given `platform_support_key`.
*/
static bool wm_platform_support_check_approval(const char *platform_support_key, bool update)
{
if (G.factory_startup) {
return false;
}
const std::optional<std::string> cfgdir = BKE_appdir_folder_id(BLENDER_USER_CONFIG, nullptr);
if (!cfgdir.has_value()) {
return false;
}
bool result = false;
char filepath[FILE_MAX];
BLI_path_join(filepath, sizeof(filepath), cfgdir->c_str(), BLENDER_PLATFORM_SUPPORT_FILE);
LinkNode *lines = BLI_file_read_as_lines(filepath);
for (LinkNode *line_node = lines; line_node; line_node = line_node->next) {
const char *line = static_cast<char *>(line_node->link);
if (STREQ(line, platform_support_key)) {
result = true;
break;
}
}
if (!result && update) {
FILE *fp = BLI_fopen(filepath, "a");
if (fp) {
fprintf(fp, "%s\n", platform_support_key);
fclose(fp);
}
}
BLI_file_free_lines(lines);
return result;
}
static void wm_platform_support_create_link(char *link)
{
DynStr *ds = BLI_dynstr_new();
BLI_dynstr_append(ds, "https://docs.blender.org/manual/en/dev/troubleshooting/gpu/");
#if defined(_WIN32)
BLI_dynstr_append(ds, "windows/");
#elif defined(__APPLE__)
BLI_dynstr_append(ds, "apple/");
#else /* UNIX. */
BLI_dynstr_append(ds, "linux/");
#endif
if (GPU_type_matches(GPU_DEVICE_INTEL, GPU_OS_ANY, GPU_DRIVER_ANY)) {
BLI_dynstr_append(ds, "intel.html");
}
else if (GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_ANY, GPU_DRIVER_ANY)) {
BLI_dynstr_append(ds, "nvidia.html");
}
else if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_ANY)) {
BLI_dynstr_append(ds, "amd.html");
}
else {
BLI_dynstr_append(ds, "unknown.html");
}
BLI_assert(BLI_dynstr_get_len(ds) < WM_PLATFORM_SUPPORT_TEXT_SIZE);
BLI_dynstr_get_cstring_ex(ds, link);
BLI_dynstr_free(ds);
}
bool WM_platform_support_perform_checks()
{
char title[WM_PLATFORM_SUPPORT_TEXT_SIZE];
char message[WM_PLATFORM_SUPPORT_TEXT_SIZE];
char link[WM_PLATFORM_SUPPORT_TEXT_SIZE];
bool result = true;
GPUSupportLevel support_level = GPU_platform_support_level();
const char *platform_key = GPU_platform_support_level_key();
CLOG_INFO(&LOG, "Using GPU \"%s\"", GPU_platform_gpu_name());
CLOG_INFO(&LOG, "Using Backend \"%s\"", GPU_backend_get_name());
/* Check if previous check matches the current check. Don't update the approval when running in
* `background`. this could have been triggered by installing add-ons via installers. */
if (support_level != GPU_SUPPORT_LEVEL_UNSUPPORTED && !G.factory_startup &&
wm_platform_support_check_approval(platform_key, !G.background))
{
/* If it matches the user has confirmed and wishes to use it. */
return result;
}
bool backend_detected = GPU_backend_get_type() != GPU_BACKEND_NONE;
bool show_message = ELEM(
support_level, GPU_SUPPORT_LEVEL_LIMITED, GPU_SUPPORT_LEVEL_UNSUPPORTED);
bool show_continue = backend_detected && support_level != GPU_SUPPORT_LEVEL_UNSUPPORTED;
bool show_link = backend_detected;
link[0] = '\0';
if (show_link) {
wm_platform_support_create_link(link);
}
/* Update the message and link based on the found support level. */
GHOST_DialogOptions dialog_options = GHOST_DialogOptions(0);
switch (support_level) {
default:
case GPU_SUPPORT_LEVEL_SUPPORTED:
break;
case GPU_SUPPORT_LEVEL_LIMITED: {
size_t slen = 0;
STR_CONCAT(title, slen, "Blender - ");
STR_CONCAT(
title, slen, CTX_IFACE_(BLT_I18NCONTEXT_ID_WINDOWMANAGER, "Limited Platform Support"));
slen = 0;
STR_CONCAT(
message,
slen,
CTX_IFACE_(
BLT_I18NCONTEXT_ID_WINDOWMANAGER,
"Your graphics card or driver version has limited support. It may work, but with "
"issues."));
/* TODO: Extra space is needed for the split function in GHOST_SystemX11. We should change
* the behavior in GHOST_SystemX11. */
STR_CONCAT(message, slen, "\n \n");
STR_CONCAT(
message,
slen,
CTX_IFACE_(
BLT_I18NCONTEXT_ID_WINDOWMANAGER,
"Newer graphics drivers might be available with better Blender compatibility."));
STR_CONCAT(message, slen, "\n \n");
STR_CONCAT(message, slen, CTX_IFACE_(BLT_I18NCONTEXT_ID_WINDOWMANAGER, "Graphics card:\n"));
STR_CONCAT(message, slen, GPU_platform_gpu_name());
dialog_options = GHOST_DialogWarning;
break;
}
case GPU_SUPPORT_LEVEL_UNSUPPORTED: {
size_t slen = 0;
STR_CONCAT(title, slen, "Blender - ");
STR_CONCAT(
title, slen, CTX_IFACE_(BLT_I18NCONTEXT_ID_WINDOWMANAGER, "Platform Unsupported"));
slen = 0;
#ifdef __APPLE__
if (GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_ANY, GPU_DRIVER_ANY)) {
STR_CONCAT(
message,
slen,
CTX_IFACE_(BLT_I18NCONTEXT_ID_WINDOWMANAGER, "Your graphics card is not supported"));
}
else {
STR_CONCAT(message,
slen,
CTX_IFACE_(BLT_I18NCONTEXT_ID_WINDOWMANAGER,
"Your graphics card or macOS version is not supported"));
STR_CONCAT(message, slen, "\n \n");
STR_CONCAT(
message,
slen,
CTX_IFACE_(BLT_I18NCONTEXT_ID_WINDOWMANAGER,
"Upgrading to the latest macOS version may improve Blender support"));
}
#else
STR_CONCAT(message,
slen,
CTX_IFACE_(BLT_I18NCONTEXT_ID_WINDOWMANAGER,
"Your graphics card or driver version is not supported."));
STR_CONCAT(message, slen, "\n \n");
STR_CONCAT(
message,
slen,
CTX_IFACE_(
BLT_I18NCONTEXT_ID_WINDOWMANAGER,
"Newer graphics drivers might be available with better Blender compatibility."));
STR_CONCAT(message, slen, "\n \n");
STR_CONCAT(message, slen, CTX_IFACE_(BLT_I18NCONTEXT_ID_WINDOWMANAGER, "Graphics card:\n"));
STR_CONCAT(message, slen, GPU_platform_gpu_name());
#endif
STR_CONCAT(message, slen, "\n \n");
if (!show_continue) {
STR_CONCAT(message,
slen,
CTX_IFACE_(BLT_I18NCONTEXT_ID_WINDOWMANAGER, "Blender will now close."));
dialog_options = GHOST_DialogError;
result = false;
}
break;
}
}
if (show_message) {
/* Always print when in background mode or using debug argument. */
if (G.background || G.debug & G_DEBUG) {
CLOG_INFO_NOCHECK(&LOG, "%s\n\n%s\n%s\n", title, message, link);
}
else {
CLOG_INFO(&LOG, "%s\n\n%s\n%s\n", title, message, link);
}
}
if (G.background) {
/* Don't show the message-box when running in background mode.
* Printing to console is enough. */
result = true;
}
else if (show_message) {
WM_ghost_show_message_box(title,
message,
"Find Latest Drivers",
show_continue ? "Continue Anyway" : "Exit",
link,
dialog_options);
}
return result;
}
} // namespace blender

View File

@@ -0,0 +1,14 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*/
#pragma once
namespace blender {
bool WM_platform_support_perform_checks();
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,513 @@
/* SPDX-FileCopyrightText: 2007 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* This file contains the splash screen logic (the `WM_OT_splash` operator).
*
* - Loads the splash image.
* - Displaying version information.
* - Lists New Files (application templates).
* - Lists Recent files.
* - Links to web sites.
*/
#include <cstring>
#include "DNA_screen_types.h"
#include "DNA_userdef_types.h"
#include "DNA_windowmanager_types.h"
#include "BLI_listbase.h"
#include "BLI_math_base.h"
#include "BLI_path_utils.hh"
#include "BLI_utildefines.h"
#include "BKE_appdir.hh"
#include "BKE_blender_version.h"
#include "BKE_context.hh"
#include "BKE_preferences.h"
#include "BLT_translation.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "ED_datafiles.h"
#include "ED_screen.hh"
#include "RNA_access.hh"
#include "UI_interface.hh"
#include "UI_interface_icons.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "wm.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Splash Screen
* \{ */
static void wm_block_splash_close(bContext *C, ui::Block *block)
{
wmWindow *win = CTX_wm_window(C);
popup_block_close(C, win, block);
}
static void wm_block_splash_add_label(ui::Block *block, const char *label, int x, int y)
{
if (!(label && label[0])) {
return;
}
block_emboss_set(block, ui::EmbossType::None);
ui::Button *but = uiDefBut(
block, ui::ButtonType::Label, label, 0, y, x, UI_UNIT_Y, nullptr, 0, 0, std::nullopt);
button_drawflag_disable(but, ui::BUT_TEXT_LEFT);
button_drawflag_enable(but, ui::BUT_TEXT_RIGHT);
/* Regardless of theme, this text should always be bright white. */
uchar color[4] = {255, 255, 255, 255};
button_color_set(but, color);
block_emboss_set(block, ui::EmbossType::Emboss);
}
#ifndef WITH_HEADLESS
static void wm_block_splash_image_roundcorners_add(ImBuf *ibuf)
{
uchar *rct = ibuf->byte_data_for_write();
if (!rct) {
return;
}
bTheme *btheme = ui::theme::theme_get();
const float roundness = btheme->tui.wcol_menu_back.roundness * UI_SCALE_FAC;
const int size = roundness * 20;
if (size < ibuf->x && size < ibuf->y) {
/* Y-axis initial offset. */
rct += 4 * (ibuf->y - size) * ibuf->x;
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++, rct += 4) {
const float pixel = 1.0 / size;
const float u = pixel * x;
const float v = pixel * y;
const float distance = sqrt(u * u + v * v);
/* Pointer offset to the alpha value of pixel. */
/* NOTE: the left corner is flipped in the X-axis. */
const int offset_l = 4 * (size - x - x - 1) + 3;
const int offset_r = 4 * (ibuf->x - size) + 3;
if (distance > 1.0) {
rct[offset_l] = 0;
rct[offset_r] = 0;
}
else {
/* Create a single pixel wide transition for anti-aliasing.
* Invert the distance and map its range [0, 1] to [0, pixel]. */
const float fac = (1.0 - distance) * size;
if (fac > 1.0) {
continue;
}
const uchar alpha = unit_float_to_uchar_clamp(fac);
rct[offset_l] = alpha;
rct[offset_r] = alpha;
}
}
/* X-axis offset to the next row. */
rct += 4 * (ibuf->x - size);
}
}
}
#endif /* !WITH_HEADLESS */
static ImBuf *wm_block_splash_image(int width, int *r_height)
{
ImBuf *ibuf = nullptr;
int height = 0;
#ifndef WITH_HEADLESS
if (U.app_template[0] != '\0') {
char splash_filepath[FILE_MAX];
char template_directory[FILE_MAX];
if (BKE_appdir_app_template_id_search(
U.app_template, template_directory, sizeof(template_directory)))
{
BLI_path_join(splash_filepath, sizeof(splash_filepath), template_directory, "splash.png");
ibuf = IMB_load_image_from_filepath(splash_filepath, ImBufFlags::ByteData);
}
}
if (ibuf == nullptr) {
const char *custom_splash_path = BLI_getenv("BLENDER_CUSTOM_SPLASH");
if (custom_splash_path) {
ibuf = IMB_load_image_from_filepath(custom_splash_path, ImBufFlags::ByteData);
}
}
if (ibuf == nullptr) {
const uchar *splash_data = reinterpret_cast<const uchar *>(datatoc_splash_png);
size_t splash_data_size = datatoc_splash_png_size;
ibuf = IMB_load_image_from_memory(
splash_data, splash_data_size, ImBufFlags::ByteData, "<splash screen>");
}
if (ibuf) {
ibuf->color_mode = ImColorMode::RGBA; /* The image might not have an alpha channel. */
height = (width * ibuf->y) / ibuf->x;
if (width != ibuf->x || height != ibuf->y) {
IMB_scale(ibuf, width, height, IMBScaleFilter::Box, false);
}
wm_block_splash_image_roundcorners_add(ibuf);
IMB_premultiply_alpha(ibuf);
}
#else
UNUSED_VARS(width);
#endif
*r_height = height;
return ibuf;
}
static ImBuf *wm_block_splash_banner_image(int *r_width,
int *r_height,
int max_width,
int max_height)
{
ImBuf *ibuf = nullptr;
int height = 0;
int width = max_width;
#ifndef WITH_HEADLESS
const char *custom_splash_path = BLI_getenv("BLENDER_CUSTOM_SPLASH_BANNER");
if (custom_splash_path) {
ibuf = IMB_load_image_from_filepath(custom_splash_path, ImBufFlags::ByteData);
}
if (!ibuf) {
return nullptr;
}
ibuf->color_mode = ImColorMode::RGBA; /* The image might not have an alpha channel. */
width = ibuf->x;
height = ibuf->y;
if (width > 0 && height > 0 && (width > max_width || height > max_height)) {
const float splash_ratio = max_width / float(max_height);
const float banner_ratio = ibuf->x / float(ibuf->y);
if (banner_ratio > splash_ratio) {
/* The banner is wider than the splash image. */
width = max_width;
height = max_width / banner_ratio;
}
else if (banner_ratio < splash_ratio) {
/* The banner is taller than the splash image. */
height = max_height;
width = max_height * banner_ratio;
}
else {
width = max_width;
height = max_height;
}
if (width != ibuf->x || height != ibuf->y) {
IMB_scale(ibuf, width, height, IMBScaleFilter::Box, false);
}
}
IMB_premultiply_alpha(ibuf);
#else
UNUSED_VARS(max_height);
#endif
*r_height = height;
*r_width = width;
return ibuf;
}
/**
* Close the splash when opening a file-selector.
*/
static void wm_block_splash_close_on_fileselect(bContext *C, void *arg1, void * /*arg2*/)
{
wmWindow *win = CTX_wm_window(C);
if (!win) {
return;
}
/* Check for the event as this will run before the new window/area has been created. */
bool has_fileselect = false;
for (const wmEvent &event : win->runtime->event_queue) {
if (event.type == EVT_FILESELECT) {
has_fileselect = true;
break;
}
}
if (has_fileselect) {
wm_block_splash_close(C, static_cast<ui::Block *>(arg1));
}
}
#if defined(__APPLE__)
/* Check if Blender is running under Rosetta for the purpose of displaying a splash screen warning.
* From Apple's WWDC 2020 Session - Explore the new system architecture of Apple Silicon Macs.
* Time code: 14:31 - https://developer.apple.com/videos/play/wwdc2020/10686/ */
# include <sys/sysctl.h>
static int is_using_macos_rosetta()
{
int ret = 0;
size_t size = sizeof(ret);
if (sysctlbyname("sysctl.proc_translated", &ret, &size, nullptr, 0) != -1) {
return ret;
}
/* If "sysctl.proc_translated" is not present then must be native. */
if (errno == ENOENT) {
return 0;
}
return -1;
}
#endif /* __APPLE__ */
static ui::Block *wm_block_splash_create(bContext *C, ARegion *region, void * /*arg*/)
{
const uiStyle *style = ui::style_get_dpi();
ui::Block *block = block_begin(C, region, "splash", ui::EmbossType::Emboss);
/* Note on #BLOCK_NO_WIN_CLIP, the window size is not always synchronized
* with the OS when the splash shows, window clipping in this case gives
* ugly results and clipping the splash isn't useful anyway, just disable it #32938. */
block_flag_enable(block, ui::BLOCK_LOOP | ui::BLOCK_KEEP_OPEN | ui::BLOCK_NO_WIN_CLIP);
block_theme_style_set(block, ui::BLOCK_THEME_STYLE_POPUP);
int splash_width = style->widget.points * 45 * UI_SCALE_FAC;
CLAMP_MAX(splash_width, WM_window_native_pixel_x(CTX_wm_window(C)) * 0.7f);
int splash_height;
/* Would be nice to support caching this, so it only has to be re-read (and likely resized) on
* first draw or if the image changed. */
ImBuf *ibuf = wm_block_splash_image(splash_width, &splash_height);
/* This should never happen, if it does - don't crash. */
if (LIKELY(ibuf)) {
ui::Button *but = uiDefButImage(
block, ibuf, 0, 0.5f * U.widget_unit, splash_width, splash_height, nullptr);
button_func_set(but, [block](bContext &C) { wm_block_splash_close(&C, block); });
wm_block_splash_add_label(block,
BKE_blender_version_string(),
splash_width - 8.0 * UI_SCALE_FAC,
splash_height - 13.0 * UI_SCALE_FAC);
}
/* Banner image passed through the environment, to overlay on the splash and
* indicate a custom Blender version. Transparency can be used. To replace the
* full splash screen, see BLENDER_CUSTOM_SPLASH. */
int banner_width = 0;
int banner_height = 0;
ImBuf *bannerbuf = wm_block_splash_banner_image(
&banner_width, &banner_height, splash_width, splash_height);
if (bannerbuf) {
ui::Button *banner_but = uiDefButImage(
block, bannerbuf, 0, 0.5f * U.widget_unit, banner_width, banner_height, nullptr);
button_func_set(banner_but, [block](bContext &C) { wm_block_splash_close(&C, block); });
}
const int layout_margin_x = UI_SCALE_FAC * 26;
ui::Layout &layout = ui::block_layout(block,
ui::LayoutDirection::Vertical,
ui::LayoutType::Panel,
layout_margin_x,
0,
splash_width - (layout_margin_x * 2),
UI_SCALE_FAC * 110,
0,
style);
MenuType *mt;
/* Draw setup screen if no preferences have been saved yet. */
if (!bke::preferences::exists()) {
mt = WM_menutype_find("WM_MT_splash_quick_setup", true);
/* The #BLOCK_QUICK_SETUP flag prevents the button text from being left-aligned,
* as it is for all menus due to the #BLOCK_LOOP flag, see in #ui_def_but. */
block_flag_enable(block, ui::BLOCK_QUICK_SETUP);
}
else {
mt = WM_menutype_find("WM_MT_splash", true);
}
block_func_set(block, wm_block_splash_close_on_fileselect, block, nullptr);
if (mt) {
ui::menutype_draw(C, mt, &layout);
}
/* Displays a warning if blender is being emulated via Rosetta (macOS) or XTA (Windows) */
#if defined(__APPLE__) || defined(_M_X64)
# if defined(__APPLE__)
if (is_using_macos_rosetta() > 0)
# elif defined(_M_X64)
const char *proc_id = BLI_getenv("PROCESSOR_IDENTIFIER");
if (proc_id && strncmp(proc_id, "ARM", 3) == 0)
# endif
{
layout.separator(2.0f, ui::LayoutSeparatorType::Line);
ui::Layout &split = layout.split(0.725, true);
ui::Layout &row1 = split.row(true);
ui::Layout &row2 = split.row(true);
row1.label(RPT_("Intel binary detected. Expect reduced performance."), ICON_ERROR);
PointerRNA op_ptr = row2.op("WM_OT_url_open",
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Learn More"),
ICON_URL,
wm::OpCallContext::InvokeDefault,
UI_ITEM_NONE);
# if defined(__APPLE__)
RNA_string_set(
&op_ptr,
"url",
"https://docs.blender.org/manual/en/latest/getting_started/installing/macos.html");
# elif defined(_M_X64)
RNA_string_set(
&op_ptr,
"url",
"https://docs.blender.org/manual/en/latest/getting_started/installing/windows.html");
# endif
layout.separator();
}
#endif
block_bounds_set_centered(block, 0);
return block;
}
static wmOperatorStatus wm_splash_invoke(bContext *C,
wmOperator * /*op*/,
const wmEvent * /*event*/)
{
ui::popup_block_invoke(C, wm_block_splash_create, nullptr, nullptr);
return OPERATOR_FINISHED;
}
void WM_OT_splash(wmOperatorType *ot)
{
ot->name = "Splash Screen";
ot->idname = "WM_OT_splash";
ot->description = "Open the splash screen with release info";
ot->invoke = wm_splash_invoke;
ot->poll = WM_operator_winactive;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Splash Screen: About
* \{ */
static ui::Block *wm_block_about_create(bContext *C, ARegion *region, void * /*arg*/)
{
const uiStyle *style = ui::style_get_dpi();
const int dialog_width = style->widget.points * 42 * UI_SCALE_FAC;
ui::Block *block = block_begin(C, region, "about", ui::EmbossType::Emboss);
block_flag_enable(block, ui::BLOCK_KEEP_OPEN | ui::BLOCK_LOOP | ui::BLOCK_NO_WIN_CLIP);
block_theme_style_set(block, ui::BLOCK_THEME_STYLE_POPUP);
ui::Layout &layout = ui::block_layout(block,
ui::LayoutDirection::Vertical,
ui::LayoutType::Panel,
0,
0,
dialog_width,
0,
0,
style);
/* Blender logo. */
#ifndef WITH_HEADLESS
constexpr bool show_color = false;
const float size = 0.2f * dialog_width;
ImBuf *ibuf = ui::svg_icon_bitmap(ICON_BLENDER_LOGO_LARGE, size, show_color);
if (ibuf) {
bTheme *btheme = ui::theme::theme_get();
const uchar *color = btheme->tui.wcol_menu_back.text_sel;
/* The top margin. */
layout.row(false).separator(0.2f);
/* The logo image. */
layout.row(false).alignment_set(ui::LayoutAlign::Left);
uiDefButImage(block, ibuf, 0, U.widget_unit, ibuf->x, ibuf->y, show_color ? nullptr : color);
/* Padding below the logo. */
layout.row(false).separator(2.7f);
}
#endif /* !WITH_HEADLESS */
ui::Layout &col = layout.column(true);
uiItemL_ex(&col, IFACE_("Blender"), ICON_NONE, true, false);
MenuType *mt = WM_menutype_find("WM_MT_splash_about", true);
if (mt) {
ui::menutype_draw(C, mt, &col);
}
block_bounds_set_centered(block, 22 * UI_SCALE_FAC);
return block;
}
static wmOperatorStatus wm_splash_about_invoke(bContext *C,
wmOperator * /*op*/,
const wmEvent * /*event*/)
{
ui::popup_block_invoke(C, wm_block_about_create, nullptr, nullptr);
return OPERATOR_FINISHED;
}
void WM_OT_splash_about(wmOperatorType *ot)
{
ot->name = "About Blender";
ot->idname = "WM_OT_splash_about";
ot->description = "Open a window with information about Blender";
ot->invoke = wm_splash_about_invoke;
ot->poll = WM_operator_winactive;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,408 @@
/* SPDX-FileCopyrightText: 2015 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*/
#include <cstdlib>
#include <cstring>
#include "RNA_access.hh"
#include "RNA_prototypes.hh"
#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
#include "BKE_context.hh"
#include "BKE_global.hh"
#include "BKE_report.hh"
#include "BLT_translation.hh"
#include "GHOST_IWindow.hh"
#include "GHOST_Types.hh"
#include "ED_screen.hh"
#include "GPU_capabilities.hh"
#include "GPU_immediate.hh"
#include "GPU_viewport.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "wm.hh"
#include "wm_window.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
namespace blender {
void wm_stereo3d_draw_sidebyside(wmWindow *win, int view)
{
bool cross_eyed = (win->stereo3d_format->flag & S3D_SIDEBYSIDE_CROSSEYED) != 0;
GPUVertFormat *format = immVertexFormat();
uint texcoord = GPU_vertformat_attr_add(format, "texCoord", gpu::VertAttrType::SFLOAT_32_32);
uint pos = GPU_vertformat_attr_add(format, "pos", gpu::VertAttrType::SFLOAT_32_32);
immBindBuiltinProgram(GPU_SHADER_3D_IMAGE);
const int2 win_size = WM_window_native_pixel_size(win);
int soffx = win_size[0] / 2;
if (view == STEREO_LEFT_ID) {
if (!cross_eyed) {
soffx = 0;
}
}
else { /* #RIGHT_LEFT_ID. */
if (cross_eyed) {
soffx = 0;
}
}
/* `wmOrtho` for the screen has this same offset. */
const float halfx = GLA_PIXEL_OFS / win_size[0];
const float halfy = GLA_PIXEL_OFS / win_size[1];
/* Texture is already bound to GL_TEXTURE0 unit. */
immBegin(GPU_PRIM_TRI_FAN, 4);
immAttr2f(texcoord, halfx, halfy);
immVertex2f(pos, soffx, 0.0f);
immAttr2f(texcoord, 1.0f + halfx, halfy);
immVertex2f(pos, soffx + (win_size[0] * 0.5f), 0.0f);
immAttr2f(texcoord, 1.0f + halfx, 1.0f + halfy);
immVertex2f(pos, soffx + (win_size[0] * 0.5f), win_size[1]);
immAttr2f(texcoord, halfx, 1.0f + halfy);
immVertex2f(pos, soffx, win_size[1]);
immEnd();
immUnbindProgram();
}
void wm_stereo3d_draw_topbottom(wmWindow *win, int view)
{
GPUVertFormat *format = immVertexFormat();
uint texcoord = GPU_vertformat_attr_add(format, "texCoord", gpu::VertAttrType::SFLOAT_32_32);
uint pos = GPU_vertformat_attr_add(format, "pos", gpu::VertAttrType::SFLOAT_32_32);
immBindBuiltinProgram(GPU_SHADER_3D_IMAGE);
const int2 win_size = WM_window_native_pixel_size(win);
int soffy;
if (view == STEREO_LEFT_ID) {
soffy = win_size[1] * 0.5f;
}
else { /* #STEREO_RIGHT_ID. */
soffy = 0;
}
/* `wmOrtho` for the screen has this same offset. */
const float halfx = GLA_PIXEL_OFS / win_size[0];
const float halfy = GLA_PIXEL_OFS / win_size[1];
/* Texture is already bound to GL_TEXTURE0 unit. */
immBegin(GPU_PRIM_TRI_FAN, 4);
immAttr2f(texcoord, halfx, halfy);
immVertex2f(pos, 0.0f, soffy);
immAttr2f(texcoord, 1.0f + halfx, halfy);
immVertex2f(pos, win_size[0], soffy);
immAttr2f(texcoord, 1.0f + halfx, 1.0f + halfy);
immVertex2f(pos, win_size[0], soffy + (win_size[1] * 0.5f));
immAttr2f(texcoord, halfx, 1.0f + halfy);
immVertex2f(pos, 0.0f, soffy + (win_size[1] * 0.5f));
immEnd();
immUnbindProgram();
}
static bool wm_stereo3d_is_fullscreen_required(eStereoDisplayMode stereo_display)
{
return ELEM(stereo_display, S3D_DISPLAY_SIDEBYSIDE, S3D_DISPLAY_TOPBOTTOM);
}
bool WM_stereo3d_enabled(wmWindow *win, bool skip_stereo3d_check)
{
const bScreen *screen = WM_window_get_active_screen(win);
const Scene *scene = WM_window_get_active_scene(win);
const GHOST_IWindow *ghost_window = static_cast<GHOST_IWindow *>(win->runtime->ghostwin);
/* Some 3d methods change the window arrangement, thus they shouldn't
* toggle on/off just because there is no 3d elements being drawn. */
if (wm_stereo3d_is_fullscreen_required(eStereoDisplayMode(win->stereo3d_format->display_mode))) {
return ghost_window->getState() == GHOST_kWindowStateFullScreen;
}
if ((skip_stereo3d_check == false) && (ED_screen_stereo3d_required(screen, scene) == false)) {
return false;
}
/* Some 3d methods change the window arrangement, thus they shouldn't
* toggle on/off just because there is no 3d elements being drawn. */
if (wm_stereo3d_is_fullscreen_required(eStereoDisplayMode(win->stereo3d_format->display_mode))) {
return ghost_window->getState() == GHOST_kWindowStateFullScreen;
}
return true;
}
void wm_stereo3d_mouse_offset_apply(wmWindow *win, int r_mouse_xy[2])
{
if (!WM_stereo3d_enabled(win, false)) {
return;
}
if (win->stereo3d_format->display_mode == S3D_DISPLAY_SIDEBYSIDE) {
const int half_x = WM_window_native_pixel_x(win) / 2;
/* Right half of the screen. */
if (r_mouse_xy[0] > half_x) {
r_mouse_xy[0] -= half_x;
}
r_mouse_xy[0] *= 2;
}
else if (win->stereo3d_format->display_mode == S3D_DISPLAY_TOPBOTTOM) {
const int half_y = WM_window_native_pixel_y(win) / 2;
/* Upper half of the screen. */
if (r_mouse_xy[1] > half_y) {
r_mouse_xy[1] -= half_y;
}
r_mouse_xy[1] *= 2;
}
}
/************************** Stereo 3D operator **********************************/
struct Stereo3dData {
Stereo3dFormat stereo3d_format;
};
static bool wm_stereo3d_set_properties(bContext * /*C*/, wmOperator *op)
{
Stereo3dData *s3dd = static_cast<Stereo3dData *>(op->customdata);
Stereo3dFormat *s3d = &s3dd->stereo3d_format;
PropertyRNA *prop;
bool is_set = false;
prop = RNA_struct_find_property(op->ptr, "display_mode");
if (RNA_property_is_set(op->ptr, prop)) {
s3d->display_mode = eStereoDisplayMode(RNA_property_enum_get(op->ptr, prop));
is_set = true;
}
prop = RNA_struct_find_property(op->ptr, "anaglyph_type");
if (RNA_property_is_set(op->ptr, prop)) {
s3d->anaglyph_type = eStereo3dAnaglyphType(RNA_property_enum_get(op->ptr, prop));
is_set = true;
}
prop = RNA_struct_find_property(op->ptr, "interlace_type");
if (RNA_property_is_set(op->ptr, prop)) {
s3d->interlace_type = eStereo3dInterlaceType(RNA_property_enum_get(op->ptr, prop));
is_set = true;
}
prop = RNA_struct_find_property(op->ptr, "use_interlace_swap");
if (RNA_property_is_set(op->ptr, prop)) {
if (RNA_property_boolean_get(op->ptr, prop)) {
s3d->flag |= S3D_INTERLACE_SWAP;
}
else {
s3d->flag &= ~S3D_INTERLACE_SWAP;
}
is_set = true;
}
prop = RNA_struct_find_property(op->ptr, "use_sidebyside_crosseyed");
if (RNA_property_is_set(op->ptr, prop)) {
if (RNA_property_boolean_get(op->ptr, prop)) {
s3d->flag |= S3D_SIDEBYSIDE_CROSSEYED;
}
else {
s3d->flag &= ~S3D_SIDEBYSIDE_CROSSEYED;
}
is_set = true;
}
return is_set;
}
static void wm_stereo3d_set_init(bContext *C, wmOperator *op)
{
wmWindow *win = CTX_wm_window(C);
Stereo3dData *s3dd = MEM_new<Stereo3dData>(__func__);
op->customdata = s3dd;
/* Store the original win stereo 3d settings in case of cancel. */
s3dd->stereo3d_format = *win->stereo3d_format;
}
wmOperatorStatus wm_stereo3d_set_exec(bContext *C, wmOperator *op)
{
wmWindowManager *wm = CTX_wm_manager(C);
wmWindow *win_src = CTX_wm_window(C);
wmWindow *win_dst = nullptr;
const bool is_fullscreen = WM_window_is_fullscreen(win_src);
eStereoDisplayMode prev_display_mode = win_src->stereo3d_format->display_mode;
bool ok = true;
if (G.background) {
return OPERATOR_CANCELLED;
}
if (op->customdata == nullptr) {
/* No invoke means we need to set the operator properties here. */
wm_stereo3d_set_init(C, op);
wm_stereo3d_set_properties(C, op);
}
Stereo3dData *s3dd = static_cast<Stereo3dData *>(op->customdata);
*win_src->stereo3d_format = s3dd->stereo3d_format;
if (prev_display_mode == S3D_DISPLAY_PAGEFLIP &&
prev_display_mode != win_src->stereo3d_format->display_mode)
{
/* In case the hardware supports page-flip but not the display. */
if ((win_dst = wm_window_copy_test(C, win_src, false, false))) {
/* Pass. */
}
else {
BKE_report(
op->reports,
RPT_ERROR,
"Failed to create a window without quad-buffer support, you may experience flickering");
ok = false;
}
}
else if (win_src->stereo3d_format->display_mode == S3D_DISPLAY_PAGEFLIP) {
const bScreen *screen = WM_window_get_active_screen(win_src);
/* #ED_workspace_layout_duplicate() can't handle other cases yet #44688 */
if (screen->state != SCREENNORMAL) {
BKE_report(
op->reports, RPT_ERROR, "Failed to switch to Time Sequential mode when in fullscreen");
ok = false;
}
/* Page-flip requires a new window to be created with the proper OS flags. */
else if ((win_dst = wm_window_copy_test(C, win_src, false, false))) {
if (GPU_stereo_quadbuffer_support()) {
BKE_report(op->reports, RPT_INFO, "Quad-buffer window successfully created");
}
else {
wm_window_close(C, wm, win_dst);
win_dst = nullptr;
BKE_report(op->reports, RPT_ERROR, "Quad-buffer not supported by the system");
ok = false;
}
}
else {
BKE_report(op->reports,
RPT_ERROR,
"Failed to create a window compatible with the time sequential display method");
ok = false;
}
}
if (wm_stereo3d_is_fullscreen_required(eStereoDisplayMode(s3dd->stereo3d_format.display_mode))) {
if (!is_fullscreen) {
BKE_report(op->reports, RPT_INFO, "Stereo 3D Mode requires the window to be fullscreen");
}
}
MEM_delete(s3dd);
op->customdata = nullptr;
if (ok) {
if (win_dst) {
wm_window_close(C, wm, win_src);
}
WM_event_add_notifier(C, NC_WINDOW, nullptr);
return OPERATOR_FINISHED;
}
/* Without this, the popup won't be freed properly, see #44688. */
CTX_wm_window_set(C, win_src);
win_src->stereo3d_format->display_mode = prev_display_mode;
return OPERATOR_CANCELLED;
}
wmOperatorStatus wm_stereo3d_set_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
wm_stereo3d_set_init(C, op);
if (wm_stereo3d_set_properties(C, op)) {
return wm_stereo3d_set_exec(C, op);
}
return WM_operator_props_dialog_popup(C, op, 300, IFACE_("Set Stereo 3D"), IFACE_("Set"));
}
void wm_stereo3d_set_draw(bContext * /*C*/, wmOperator *op)
{
Stereo3dData *s3dd = static_cast<Stereo3dData *>(op->customdata);
ui::Layout &layout = *op->layout;
PointerRNA stereo3d_format_ptr = RNA_pointer_create_discrete(
nullptr, RNA_Stereo3dDisplay, &s3dd->stereo3d_format);
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
ui::Layout &col = layout.column(false);
col.prop(&stereo3d_format_ptr, "display_mode", UI_ITEM_NONE, std::nullopt, ICON_NONE);
switch (s3dd->stereo3d_format.display_mode) {
case S3D_DISPLAY_ANAGLYPH: {
col.prop(&stereo3d_format_ptr, "anaglyph_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
break;
}
case S3D_DISPLAY_INTERLACE: {
col.prop(&stereo3d_format_ptr, "interlace_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(&stereo3d_format_ptr, "use_interlace_swap", UI_ITEM_NONE, std::nullopt, ICON_NONE);
break;
}
case S3D_DISPLAY_SIDEBYSIDE: {
col.prop(
&stereo3d_format_ptr, "use_sidebyside_crosseyed", UI_ITEM_NONE, std::nullopt, ICON_NONE);
/* Fall-through. */
}
case S3D_DISPLAY_PAGEFLIP:
case S3D_DISPLAY_TOPBOTTOM:
default: {
break;
}
}
}
bool wm_stereo3d_set_check(bContext * /*C*/, wmOperator * /*op*/)
{
/* The check function guarantees that the menu is updated to show the sub-options when an enum
* changes (e.g. it shows the anaglyph options when anaglyph is on,
* and the interlace options when this is on). */
return true;
}
void wm_stereo3d_set_cancel(bContext * /*C*/, wmOperator *op)
{
Stereo3dData *s3dd = static_cast<Stereo3dData *>(op->customdata);
MEM_delete(s3dd);
op->customdata = nullptr;
}
} // namespace blender

View File

@@ -0,0 +1,150 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* OpenGL utilities for setting up 2D viewport for window and regions.
*/
#include "BLI_math_geom.h"
#include "BLI_rect.h"
#include "DNA_screen_types.h"
#include "DNA_windowmanager_types.h"
#include "GPU_matrix.hh"
#include "GPU_state.hh"
#include "GPU_viewport.hh"
#include "WM_api.hh"
namespace blender {
void wmViewport(const rcti *winrct)
{
int width = BLI_rcti_size_x(winrct) + 1;
int height = BLI_rcti_size_y(winrct) + 1;
GPU_viewport(winrct->xmin, winrct->ymin, width, height);
GPU_scissor(winrct->xmin, winrct->ymin, width, height);
wmOrtho2_pixelspace(width, height);
GPU_matrix_identity_set();
}
void wmPartialViewport(rcti *drawrct, const rcti *winrct, const rcti *partialrct)
{
/* Setup part of the viewport for partial redraw. */
bool scissor_pad;
if (partialrct->xmin == partialrct->xmax) {
/* Full region. */
*drawrct = *winrct;
scissor_pad = true;
}
else {
/* Partial redraw, clipped to region. */
BLI_rcti_isect(winrct, partialrct, drawrct);
scissor_pad = false;
}
int x = drawrct->xmin - winrct->xmin;
int y = drawrct->ymin - winrct->ymin;
int width = BLI_rcti_size_x(winrct) + 1;
int height = BLI_rcti_size_y(winrct) + 1;
int scissor_width = BLI_rcti_size_x(drawrct);
int scissor_height = BLI_rcti_size_y(drawrct);
/* Partial redraw rect uses different convention than region rect,
* so compensate for that here. One pixel offset is noticeable with
* viewport border render. */
if (scissor_pad) {
scissor_width += 1;
scissor_height += 1;
}
GPU_viewport(0, 0, width, height);
GPU_scissor(x, y, scissor_width, scissor_height);
wmOrtho2_pixelspace(width, height);
GPU_matrix_identity_set();
}
static void wmOrtho2_offset(const float x, const float y, const float ofs);
void wmWindowViewport_ex(const wmWindow *win, float offset)
{
const int2 win_size = WM_window_native_pixel_size(win);
GPU_viewport(0, 0, win_size[0], win_size[1]);
GPU_scissor(0, 0, win_size[0], win_size[1]);
wmOrtho2_offset(win_size[0], win_size[1], offset);
GPU_matrix_identity_set();
}
void wmWindowViewport(const wmWindow *win)
{
wmWindowViewport_ex(win, -GLA_PIXEL_OFS);
}
void wmWindowViewportTitle_ex(const rcti &rect, float offset)
{
GPU_viewport(rect.xmin, rect.ymin, rect.xmax, rect.ymax);
GPU_scissor(rect.xmin, rect.ymin, rect.xmax, rect.ymax);
wmOrtho2_offset(rect.xmax, rect.ymax, offset);
GPU_matrix_identity_set();
}
void wmWindowViewportTitle(const rcti &rect)
{
wmWindowViewportTitle_ex(rect, -GLA_PIXEL_OFS);
}
void wmOrtho2(float x1, float x2, float y1, float y2)
{
/* Prevent opengl from generating errors. */
if (x2 == x1) {
x2 += 1.0f;
}
if (y2 == y1) {
y2 += 1.0f;
}
GPU_matrix_ortho_set(
x1, x2, y1, y2, GPU_MATRIX_ORTHO_CLIP_NEAR_DEFAULT, GPU_MATRIX_ORTHO_CLIP_FAR_DEFAULT);
}
static void wmOrtho2_offset(const float x, const float y, const float ofs)
{
wmOrtho2(ofs, x + ofs, ofs, y + ofs);
}
void wmOrtho2_region_pixelspace(const ARegion *region)
{
wmOrtho2_offset(region->winx, region->winy, -0.01f);
}
void wmOrtho2_pixelspace(const float x, const float y)
{
wmOrtho2_offset(x, y, -GLA_PIXEL_OFS);
}
void wmGetProjectionMatrix(float mat[4][4], const rcti *winrct)
{
int width = BLI_rcti_size_x(winrct) + 1;
int height = BLI_rcti_size_y(winrct) + 1;
orthographic_m4(mat,
-GLA_PIXEL_OFS,
float(width) - GLA_PIXEL_OFS,
-GLA_PIXEL_OFS,
float(height) - GLA_PIXEL_OFS,
GPU_MATRIX_ORTHO_CLIP_NEAR_DEFAULT,
GPU_MATRIX_ORTHO_CLIP_FAR_DEFAULT);
}
} // namespace blender

View File

@@ -0,0 +1,145 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*/
#include "BLI_listbase.h"
#ifndef NDEBUG
# include "BLI_threads.h"
#endif
#include "BKE_global.hh"
#include "BKE_main.hh"
#include "GPU_context.hh"
#include "GPU_framebuffer.hh"
#include "MEM_guardedalloc.h"
#include "WM_api.hh"
#include "WM_types.hh"
#include "wm_surface.hh"
namespace blender {
static ListBaseT<wmSurface> global_surface_list = {nullptr, nullptr};
static wmSurface *g_drawable = nullptr;
static void wm_surface_constant_dpi_set_userpref()
{
/* Ensure WM surfaces are always drawn at the same base constant pixel size. No matter the host
* operating system, monitor, or parent Blender window.
* NOTE: This function is analogous to #WM_window_dpi_set_userdef. Changes made in this
* function might need to be reproduced here. */
U.dpi = 72.0f;
U.pixelsize = 1.0f;
U.virtual_pixel = VIRTUAL_PIXEL_NATIVE;
U.scale_factor = 1.0f;
U.inv_scale_factor = 1.0f;
U.widget_unit = int(roundf(18.0f * U.scale_factor)) + (2 * U.pixelsize);
}
void wm_surfaces_iter(bContext *C, void (*cb)(bContext *C, wmSurface *))
{
/* Mutable iterator in case a surface is freed. */
for (wmSurface &surf : global_surface_list.items_mutable()) {
cb(C, &surf);
}
}
static void wm_surface_do_depsgraph_fn(bContext *C, wmSurface *surface)
{
if (surface->do_depsgraph) {
surface->do_depsgraph(C);
}
}
void wm_surfaces_do_depsgraph(bContext *C)
{
wm_surfaces_iter(C, wm_surface_do_depsgraph_fn);
}
void wm_surface_clear_drawable()
{
if (g_drawable) {
WM_system_gpu_context_release(g_drawable->system_gpu_context);
GPU_context_active_set(nullptr);
if (g_drawable->deactivate) {
g_drawable->deactivate();
}
g_drawable = nullptr;
}
}
void wm_surface_set_drawable(wmSurface *surface, bool activate)
{
BLI_assert(ELEM(g_drawable, nullptr, surface));
g_drawable = surface;
if (activate) {
if (surface->activate) {
surface->activate();
}
WM_system_gpu_context_activate(surface->system_gpu_context);
}
GPU_context_active_set(surface->blender_gpu_context);
}
void wm_surface_make_drawable(wmSurface *surface)
{
BLI_assert(GPU_framebuffer_active_get() == GPU_framebuffer_back_get());
if (surface != g_drawable) {
wm_surface_clear_drawable();
wm_surface_set_drawable(surface, true);
wm_surface_constant_dpi_set_userpref();
}
}
void wm_surface_reset_drawable()
{
BLI_assert(BLI_thread_is_main());
BLI_assert(GPU_framebuffer_active_get() == GPU_framebuffer_back_get());
if (g_drawable) {
wm_surface_clear_drawable();
wm_surface_set_drawable(g_drawable, true);
}
}
void wm_surface_add(wmSurface *surface)
{
BLI_addtail(&global_surface_list, surface);
}
void wm_surface_remove(wmSurface *surface)
{
BLI_remlink(&global_surface_list, surface);
/* Ensure GPU context is bound to free GPU resources. */
wm_surface_make_drawable(surface);
surface->free_data(surface);
wm_surface_clear_drawable();
MEM_delete(surface);
}
void wm_surfaces_free()
{
for (wmSurface &surf : global_surface_list.items_mutable()) {
wm_surface_remove(&surf);
}
BLI_assert(global_surface_list.is_empty());
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,148 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* Manages a per-window tool-tip.
*/
#include "MEM_guardedalloc.h"
#include "BLI_math_vector.h"
#include "BLI_time.h"
#include "BKE_context.hh"
#include "UI_interface.hh"
#include "WM_api.hh"
#include "WM_types.hh"
namespace blender {
static double g_tooltip_time_closed;
double WM_tooltip_time_closed()
{
return g_tooltip_time_closed;
}
void WM_tooltip_immediate_init(
bContext *C, wmWindow *win, ScrArea *area, ARegion *region, wmTooltipInitFn init)
{
WM_tooltip_timer_clear(C, win);
bScreen *screen = WM_window_get_active_screen(win);
if (screen->tool_tip == nullptr) {
screen->tool_tip = MEM_new_zeroed<wmTooltipState>(__func__);
}
screen->tool_tip->area_from = area;
screen->tool_tip->region_from = region;
screen->tool_tip->init = init;
WM_tooltip_init(C, win);
}
void WM_tooltip_timer_init_ex(
bContext *C, wmWindow *win, ScrArea *area, ARegion *region, wmTooltipInitFn init, double delay)
{
WM_tooltip_timer_clear(C, win);
bScreen *screen = WM_window_get_active_screen(win);
wmWindowManager *wm = CTX_wm_manager(C);
if (screen->tool_tip == nullptr) {
screen->tool_tip = MEM_new_zeroed<wmTooltipState>(__func__);
}
screen->tool_tip->area_from = area;
screen->tool_tip->region_from = region;
screen->tool_tip->timer = WM_event_timer_add(wm, win, TIMER, delay);
screen->tool_tip->init = init;
/* Mouse position will be updated when the tooltip is shown, but save now
* because we cancel the showing if there is movement before timer expiry. */
copy_v2_v2_int(screen->tool_tip->event_xy, win->runtime->eventstate->xy);
}
void WM_tooltip_timer_init(
bContext *C, wmWindow *win, ScrArea *area, ARegion *region, wmTooltipInitFn init)
{
WM_tooltip_timer_init_ex(C, win, area, region, init, UI_TOOLTIP_DELAY);
}
void WM_tooltip_timer_clear(bContext *C, wmWindow *win)
{
wmWindowManager *wm = CTX_wm_manager(C);
bScreen *screen = WM_window_get_active_screen(win);
if (screen->tool_tip != nullptr) {
if (screen->tool_tip->timer != nullptr) {
WM_event_timer_remove(wm, win, screen->tool_tip->timer);
screen->tool_tip->timer = nullptr;
}
}
}
void WM_tooltip_clear(bContext *C, wmWindow *win)
{
WM_tooltip_timer_clear(C, win);
bScreen *screen = WM_window_get_active_screen(win);
if (screen->tool_tip != nullptr) {
if (screen->tool_tip->region) {
ui::tooltip_free(C, screen, screen->tool_tip->region);
screen->tool_tip->region = nullptr;
g_tooltip_time_closed = BLI_time_now_seconds();
}
MEM_delete(screen->tool_tip);
screen->tool_tip = nullptr;
}
}
void WM_tooltip_init(bContext *C, wmWindow *win)
{
WM_tooltip_timer_clear(C, win);
bScreen *screen = WM_window_get_active_screen(win);
if (screen->tool_tip->region) {
ui::tooltip_free(C, screen, screen->tool_tip->region);
screen->tool_tip->region = nullptr;
}
const int pass_prev = screen->tool_tip->pass;
double pass_delay = 0.0;
{
ScrArea *area_prev = CTX_wm_area(C);
ARegion *region_prev = CTX_wm_region(C);
CTX_wm_area_set(C, screen->tool_tip->area_from);
CTX_wm_region_set(C, screen->tool_tip->region_from);
screen->tool_tip->region = screen->tool_tip->init(C,
screen->tool_tip->region_from,
&screen->tool_tip->pass,
&pass_delay,
&screen->tool_tip->exit_on_event);
CTX_wm_area_set(C, area_prev);
CTX_wm_region_set(C, region_prev);
}
copy_v2_v2_int(screen->tool_tip->event_xy, win->runtime->eventstate->xy);
if (pass_prev != screen->tool_tip->pass) {
/* The pass changed, add timer for next pass. */
wmWindowManager *wm = CTX_wm_manager(C);
screen->tool_tip->timer = WM_event_timer_add(wm, win, TIMER, pass_delay);
}
if (screen->tool_tip->region == nullptr) {
WM_tooltip_clear(C, win);
}
}
void WM_tooltip_refresh(bContext *C, wmWindow *win)
{
WM_tooltip_timer_clear(C, win);
bScreen *screen = WM_window_get_active_screen(win);
if (screen->tool_tip != nullptr) {
if (screen->tool_tip->region) {
ui::tooltip_free(C, screen, screen->tool_tip->region);
screen->tool_tip->region = nullptr;
}
WM_tooltip_init(C, win);
}
}
} // namespace blender

View File

@@ -0,0 +1,170 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* UI List Registry.
*/
#include <cstdio>
#include <cstring>
#include "BLI_listbase.h"
#include "DNA_space_types.h"
#include "DNA_windowmanager_types.h"
#include "MEM_guardedalloc.h"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
#include "BLI_vector_set.hh"
#include "BKE_main.hh"
#include "BKE_screen.hh"
#include "UI_interface_types.hh"
#include "WM_api.hh"
#include "WM_types.hh"
namespace blender {
static auto &get_list_type_map()
{
struct IDNameGetter {
StringRef operator()(const uiListType *value) const
{
return StringRef(value->idname);
}
};
static CustomIDVectorSet<uiListType *, IDNameGetter> map;
return map;
}
uiListType *WM_uilisttype_find(const StringRef idname, bool quiet)
{
if (!idname.is_empty()) {
if (uiListType *const *ult = get_list_type_map().lookup_key_ptr_as(idname)) {
return *ult;
}
}
if (!quiet) {
printf("search for unknown uilisttype %s\n", std::string(idname).c_str());
}
return nullptr;
}
bool WM_uilisttype_add(uiListType *ult)
{
get_list_type_map().add(ult);
return true;
}
static void wm_uilisttype_unlink_from_region(const uiListType *ult, ARegion *region)
{
for (uiList &list : region->ui_lists) {
if (list.type == ult) {
/* Don't delete the list, it's not just runtime data but stored in files. Freeing would make
* that data get lost. */
list.type = nullptr;
}
}
}
static void wm_uilisttype_unlink_from_area(const uiListType *ult, ScrArea *area)
{
for (SpaceLink &space_link : area->spacedata) {
ListBaseT<ARegion> *regionbase = (&space_link == area->spacedata.first) ?
&area->regionbase :
&space_link.regionbase;
for (ARegion &region : *regionbase) {
wm_uilisttype_unlink_from_region(ult, &region);
}
}
}
/**
* For all lists representing \a ult, clear their `uiListType` pointer. Use when a list-type is
* deleted, so that the UI doesn't keep references to it.
*
* This is a common pattern for unregistering (usually `.py` defined) types at runtime, e.g.
* see #WM_gizmomaptype_group_unlink().
* Note that unlike in some other cases using this pattern, we don't actually free the lists with
* type \a ult, we just clear the reference to the type. That's because UI-Lists are written to
* files and we don't want them to get lost together with their (user visible) settings.
*/
static void wm_uilisttype_unlink(Main *bmain, const uiListType *ult)
{
for (wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first); wm != nullptr;
wm = static_cast<wmWindowManager *>(wm->id.next))
{
for (wmWindow &win : wm->windows) {
for (ScrArea &global_area : win.global_areas.areabase) {
wm_uilisttype_unlink_from_area(ult, &global_area);
}
}
}
for (bScreen *screen = static_cast<bScreen *>(bmain->screens.first); screen != nullptr;
screen = static_cast<bScreen *>(screen->id.next))
{
for (ScrArea &area : screen->areabase) {
wm_uilisttype_unlink_from_area(ult, &area);
}
for (ARegion &region : screen->regionbase) {
wm_uilisttype_unlink_from_region(ult, &region);
}
}
}
void WM_uilisttype_remove_ptr(Main *bmain, uiListType *ult)
{
wm_uilisttype_unlink(bmain, ult);
bool ok = get_list_type_map().remove(ult);
MEM_delete(ult);
BLI_assert(ok);
UNUSED_VARS_NDEBUG(ok);
}
void WM_uilisttype_init()
{
get_list_type_map().reserve(16);
}
void WM_uilisttype_free()
{
for (uiListType *ult : get_list_type_map()) {
if (ult->rna_ext.free) {
ult->rna_ext.free(ult->rna_ext.data);
}
MEM_delete(ult);
}
get_list_type_map().clear();
}
void WM_uilisttype_to_full_list_id(const uiListType *ult,
const char *list_id,
char r_full_list_id[/*UI_MAX_NAME_STR*/])
{
/* We tag the list id with the list type... */
BLI_snprintf_utf8(r_full_list_id, UI_MAX_NAME_STR, "%s_%s", ult->idname, list_id ? list_id : "");
}
const char *WM_uilisttype_list_id_get(const uiListType *ult, uiList *list)
{
/* Some sanity check for the assumed behavior of #WM_uilisttype_to_full_list_id(). */
BLI_assert((list->list_id + strlen(ult->idname))[0] == '_');
/* +1 to skip the '_'. */
return list->list_id + strlen(ult->idname) + 1;
}
} // namespace blender

View File

@@ -0,0 +1,61 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* Generic helper utilities that aren't associated with a particular area.
*/
#include "WM_api.hh"
#include "WM_types.hh"
#include "MEM_guardedalloc.h"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Generic Callback
* \{ */
void WM_generic_callback_free(wmGenericCallback *callback)
{
if (callback->free_user_data) {
callback->free_user_data(callback->user_data);
}
MEM_delete(callback);
}
static void do_nothing(bContext * /*C*/, void * /*user_data*/) {}
wmGenericCallback *WM_generic_callback_steal(wmGenericCallback *callback)
{
wmGenericCallback *new_callback = MEM_dupalloc(callback);
callback->exec = do_nothing;
callback->free_user_data = nullptr;
callback->user_data = nullptr;
return new_callback;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Generic User Data
* \{ */
void WM_generic_user_data_free(wmGenericUserData *wm_userdata)
{
if (wm_userdata->data && wm_userdata->use_free) {
if (wm_userdata->free_fn) {
wm_userdata->free_fn(wm_userdata->data);
}
else {
MEM_delete_void(wm_userdata->data);
}
}
}
/** \} */
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,322 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* Window client-side-decorations (CSD) drawing.
*/
#include "DNA_vec_types.h"
#include "DNA_windowmanager_types.h"
#include "BLI_math_vector.h"
#include "BLI_rect.h"
#include "GHOST_IWindow.hh"
#include "GPU_immediate.hh"
#include "GPU_state.hh"
#include "GPU_viewport.hh" /* #GLA_PIXEL_OFS */
#include "WM_api.hh"
#include "wm_window.hh"
#include "wm_window_private.hh" /* Own include. */
#include "UI_interface_c.hh"
#include "UI_resources.hh"
#include "BLF_api.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Window Title Bar Drawing
*
* For systems with client-side-decorations (CSD).
* \{ */
void WM_window_csd_draw_titlebar_ex(const int win_size[2],
const char win_state,
const GHOST_CSD_Layout *csd_layout,
const bool is_active,
const uint16_t dpi,
const char *title,
const int font_id,
const int font_size,
const uchar border_color[3],
const uchar text_color[3],
const float alpha)
{
GHOST_CSD_Elem csd_elems_orig[GHOST_kCSDType_NUM];
const int fractional_scale[2] = {
GHOST_CSD_DPI_FRACTIONAL_BASE,
dpi,
};
const int csd_elems_num = WM_window_csd_layout_callback(
win_size, fractional_scale, win_state, csd_layout, csd_elems_orig);
if (csd_elems_num <= 0) {
return;
}
if (border_color) {
GPU_clear_color(
border_color[0] / 255.0f, border_color[1] / 255.0f, border_color[2] / 255.0f, 1.0f);
/* Window border, if needed. */
if (win_state == GHOST_kWindowStateNormal) {
const uchar border_outline_color[4] = {
uchar(border_color[0] / 2),
uchar(border_color[1] / 2),
uchar(border_color[2] / 2),
255,
};
const int border_outline_width = std::max<int>(
1, WM_window_csd_fracitonal_scale_apply(2, fractional_scale));
const rcti window_rect = {
/*xmin*/ 0,
/*xmax*/ win_size[0],
/*ymin*/ 0,
/*ymax*/ win_size[1],
};
wmWindowViewportTitle_ex(window_rect, 0);
const uint shdr_pos = GPU_vertformat_attr_add(
immVertexFormat(), "pos", gpu::VertAttrType::SFLOAT_32_32);
immBindBuiltinProgram(GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR);
immUniformColor4ubv(border_outline_color);
float viewport[4];
GPU_viewport_size_get_f(viewport);
immUniform2fv("viewportSize", &viewport[2]);
immUniform1f("lineWidth", border_outline_width);
/* Pixel offsets are needed for the lines to display evenly. */
immBegin(GPU_PRIM_LINES, 8);
/* Left. */
immVertex2f(shdr_pos, window_rect.xmin + 1, window_rect.ymin);
immVertex2f(shdr_pos, window_rect.xmin + 1, window_rect.ymax);
/* Top. */
immVertex2f(shdr_pos, window_rect.xmin, window_rect.ymax - 1);
immVertex2f(shdr_pos, window_rect.xmax, window_rect.ymax - 1);
/* Right. */
immVertex2f(shdr_pos, window_rect.xmax, window_rect.ymax);
immVertex2f(shdr_pos, window_rect.xmax, window_rect.ymin);
/* Bottom. */
immVertex2f(shdr_pos, window_rect.xmax, window_rect.ymin);
immVertex2f(shdr_pos, window_rect.xmin, window_rect.ymin);
immEnd();
immUnbindProgram();
}
}
/* Flip the Y axis. */
GHOST_CSD_Elem csd_elems[GHOST_kCSDType_NUM];
for (int i = 0; i < GHOST_kCSDType_NUM; i++) {
csd_elems[i].type = GHOST_kCSDTypeBody;
csd_elems[i].bounds[0][0] = 0;
csd_elems[i].bounds[0][1] = 0;
csd_elems[i].bounds[1][0] = 0;
csd_elems[i].bounds[1][1] = 0;
}
for (int i = 0; i < csd_elems_num; i++) {
GHOST_CSD_Elem *elem = &csd_elems[csd_elems_orig[i].type];
*elem = csd_elems_orig[i];
elem->bounds[1][0] = win_size[1] - elem->bounds[1][0];
elem->bounds[1][1] = win_size[1] - elem->bounds[1][1];
std::swap(elem->bounds[1][0], elem->bounds[1][1]);
}
BLI_assert(csd_elems[GHOST_kCSDTypeTitlebar].type == GHOST_kCSDTypeTitlebar);
const rcti title_rect = {
/*xmin*/ csd_elems[GHOST_kCSDTypeTitlebar].bounds[0][0],
/*xmax*/ csd_elems[GHOST_kCSDTypeTitlebar].bounds[0][1],
/*ymin*/ csd_elems[GHOST_kCSDTypeTitlebar].bounds[1][0],
/*ymax*/ csd_elems[GHOST_kCSDTypeTitlebar].bounds[1][1],
};
const int rect_size_y = BLI_rcti_size_y(&title_rect);
wmWindowViewportTitle_ex(title_rect, 0.0f);
if (title) {
const float px_offset = -GLA_PIXEL_OFS;
const size_t title_len = strlen(title);
uchar color[4];
if (!is_active) {
if (border_color) {
color[0] = uchar((int(border_color[0]) + int(text_color[0])) / 2);
color[1] = uchar((int(border_color[1]) + int(text_color[1])) / 2);
color[2] = uchar((int(border_color[2]) + int(text_color[2])) / 2);
}
else {
color[0] = text_color[0] / 2;
color[1] = text_color[1] / 2;
color[2] = text_color[2] / 2;
}
}
else {
ARRAY_SET_ITEMS(color, UNPACK3(text_color), uchar(255 * alpha));
}
BLF_color4ubv(font_id, color);
if (border_color == nullptr) {
const float shadow_color[4] = {0.0f, 0.0f, 0.0f, alpha};
BLF_enable(font_id, BLF_SHADOW);
BLF_shadow(font_id, FontShadowType::Outline, shadow_color);
BLF_shadow_offset(font_id, 0, 0);
}
BLF_enable(font_id, BLF_BOLD);
BLF_size(font_id, WM_window_csd_fracitonal_scale_apply(int(font_size), fractional_scale));
const int title_width = BLF_width(font_id, title, title_len);
const int title_decender = -BLF_descender(font_id);
const int title_height_max = BLF_height_max(font_id);
const int offset_y = rect_size_y > title_height_max ? (rect_size_y - title_height_max) / 2 : 0;
BLF_position(font_id,
float(title_rect.xmin + (BLI_rcti_cent_x(&title_rect) - (title_width / 2))) +
px_offset,
float(title_decender + offset_y) + px_offset,
0);
BLF_draw(font_id, title, title_len);
BLF_disable(font_id, BLF_BOLD);
if (border_color == nullptr) {
BLF_disable(font_id, BLF_SHADOW);
}
}
/* Draw buttons (starting at the title region offset). */
{
constexpr int circle_segments = 16;
GPUVertFormat *format = immVertexFormat();
const uint shdr_pos = GPU_vertformat_attr_add(format, "pos", gpu::VertAttrType::SFLOAT_32_32);
GPU_blend(GPU_BLEND_ALPHA);
GPU_polygon_smooth(true);
const GHOST_TCSD_Type button_types[] = {
GHOST_kCSDTypeButtonClose,
GHOST_kCSDTypeButtonMaximize,
GHOST_kCSDTypeButtonMinimize,
GHOST_kCSDTypeButtonMenu,
};
const int button_icons[] = {
ICON_X,
(win_state == GHOST_kWindowStateMaximized) ? ICON_AREA_DOCK : ICON_CHECKBOX_DEHLT,
ICON_DOT,
ICON_BLENDER,
};
{
const int button_margin = rect_size_y / 12;
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
if (border_color) {
immUniformColor4f(1.0f, 1.0f, 1.0f, 0.15f);
}
else {
immUniformColor4f(0.25f, 0.25f, 0.25f, 0.5f * alpha);
}
for (int i = 0; i < ARRAY_SIZE(button_types); i++) {
const GHOST_TCSD_Type ty = button_types[i];
if (UNLIKELY(csd_elems[ty].type == GHOST_kCSDTypeBody)) {
continue;
}
const rcti butrect = {
/*xmin*/ csd_elems[ty].bounds[0][0] - title_rect.xmin,
/*xmax*/ csd_elems[ty].bounds[0][1] - title_rect.xmin,
/*ymin*/ csd_elems[ty].bounds[1][0] - title_rect.ymin,
/*ymax*/ csd_elems[ty].bounds[1][1] - title_rect.ymin,
};
const int but_radius = (BLI_rcti_size_x(&butrect) / 2) - button_margin;
const int center[2] = {
BLI_rcti_cent_x(&butrect),
BLI_rcti_cent_y(&butrect),
};
imm_draw_circle_fill_2d(shdr_pos, UNPACK2(center), but_radius, circle_segments);
}
immUnbindProgram();
}
const float button_color[4] = {1.0f, 1.0f, 1.0f, alpha};
const int icon_size = WM_window_csd_fracitonal_scale_apply(ICON_DEFAULT_HEIGHT,
fractional_scale);
for (int i = 0; i < ARRAY_SIZE(button_types); i++) {
const GHOST_TCSD_Type ty = button_types[i];
if (UNLIKELY(csd_elems[ty].type == GHOST_kCSDTypeBody)) {
continue;
}
const rcti butrect = {
/*xmin*/ csd_elems[ty].bounds[0][0] - title_rect.xmin,
/*xmax*/ csd_elems[ty].bounds[0][1] - title_rect.xmin,
/*ymin*/ csd_elems[ty].bounds[1][0] - title_rect.ymin,
/*ymax*/ csd_elems[ty].bounds[1][1] - title_rect.ymin,
};
const int xy[2] = {
BLI_rcti_cent_x(&butrect) - (icon_size / 2),
BLI_rcti_cent_y(&butrect) - (icon_size / 2),
};
BLF_draw_svg_icon(button_icons[i], UNPACK2(xy), icon_size, button_color, 0, false, nullptr);
}
GPU_polygon_smooth(false);
GPU_blend(GPU_BLEND_NONE);
}
}
void WM_window_csd_draw_titlebar(const wmWindow *win)
{
BLI_assert(WM_window_is_csd(win));
const int2 win_size = WM_window_native_pixel_size(win);
const GHOST_CSD_Layout *csd_layout = WM_window_csd_layout_get();
GHOST_IWindow *ghost_window = static_cast<GHOST_IWindow *>(win->runtime->ghostwin);
const uint16_t dpi = ghost_window->getDPIHint();
const char win_state = GHOST_TWindowState(win->windowstate);
const std::string window_title = ghost_window->getTitle();
const char *title = window_title.c_str();
const bool is_active = (win->active != 0);
uchar border_color[3], text_color[3];
/* NOTE(@ideasman42): avoid theme functions as #blender::ui::theme::theme_set
* won't have run after loading factory settings, see: #152138.
* Access the theme directly as this will probably be replaced by something else,
* it's better other parts of Blender don't unintentionally rely on the theme being set here. */
const bTheme &theme = *static_cast<const bTheme *>(U.themes.first);
copy_v3_v3_uchar(border_color, theme.space_view3d.header);
copy_v3_v3_uchar(text_color, theme.space_view3d.text_hi);
const uiStyle *style = ui::style_get_dpi();
const uiFontStyle &fstyle = style->paneltitle;
const int font_id = fstyle.uifont_id;
const int font_size = fstyle.points;
const float alpha = 1.0f;
WM_window_csd_draw_titlebar_ex(win_size,
win_state,
csd_layout,
is_active,
dpi,
title,
font_id,
font_size,
border_color,
text_color,
alpha);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,212 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* Window client-side-decorations (CSD) layout.
*/
#include "GHOST_IWindow.hh"
#include "BLI_rect.h"
#include "WM_api.hh"
#include "wm_window.hh"
#include "wm_window_private.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Window Title Bar Layout
*
* Generate a client-side-decorations (CSD).
* \{ */
int WM_window_csd_fracitonal_scale_apply(int value, const int fractional_scale[2])
{
return (value * fractional_scale[1]) / fractional_scale[0];
}
int WM_window_csd_layout_callback(const int window_size[2],
const int fractional_scale[2],
const char window_state,
const GHOST_CSD_Layout *csd_layout,
GHOST_CSD_Elem *csd_elems)
{
constexpr int csd_title_height = 25;
constexpr int csd_border_size = 5;
constexpr int csd_border_corner_size = csd_title_height + csd_border_size;
const int title = WM_window_csd_fracitonal_scale_apply(csd_title_height, fractional_scale);
/* The caller is expected not to run the callback for full screen windows. */
BLI_assert(window_state != GHOST_kWindowStateFullScreen);
int decor_num = 0;
const int32_t border = (window_state == GHOST_kWindowStateMaximized) ?
0 :
WM_window_csd_fracitonal_scale_apply(csd_border_size,
fractional_scale);
GHOST_CSD_Elem *elem;
/* Window contents. */
elem = &csd_elems[decor_num++];
elem->type = GHOST_kCSDTypeBody;
elem->bounds[0][0] = border;
elem->bounds[0][1] = window_size[0] - border;
elem->bounds[1][0] = border + title;
elem->bounds[1][1] = window_size[1] - border;
/* Allow this to be null for callers that only need to know about
* the "title" & "body" regions. */
if (csd_layout != nullptr) {
int button_layout_title_index = 0;
/* Buttons on the left. */
{
int button_index = 0;
for (int i = 0; i < csd_layout->buttons_num; i++) {
if (csd_layout->buttons[i] == GHOST_kCSDTypeTitlebar) {
button_layout_title_index = i;
break;
}
GHOST_TCSD_Type type = GHOST_TCSD_Type(csd_layout->buttons[i]);
elem = &csd_elems[decor_num++];
elem->type = type;
elem->bounds[0][0] = border + (title * button_index);
elem->bounds[0][1] = border + title + (title * button_index);
elem->bounds[1][0] = border;
elem->bounds[1][1] = border + title;
button_index++;
}
}
/* Buttons on the right. */
{
int button_index = 0;
for (int i = csd_layout->buttons_num - 1; i > button_layout_title_index; i--) {
GHOST_TCSD_Type type = csd_layout->buttons[i];
elem = &csd_elems[decor_num++];
elem->type = type;
elem->bounds[0][0] = (window_size[0] - (border + title)) - (title * button_index);
elem->bounds[0][1] = (window_size[0] - (border)) - (title * button_index);
elem->bounds[1][0] = border;
elem->bounds[1][1] = border + title;
button_index++;
}
}
}
/* Title bar. */
elem = &csd_elems[decor_num++];
elem->type = GHOST_kCSDTypeTitlebar;
elem->bounds[0][0] = border;
elem->bounds[0][1] = window_size[0] - border;
elem->bounds[1][0] = border;
elem->bounds[1][1] = border + title;
if (window_state != GHOST_kWindowStateMaximized) {
const int32_t border_corner = WM_window_csd_fracitonal_scale_apply(csd_border_corner_size,
fractional_scale);
/* Border: corners. */
elem = &csd_elems[decor_num++];
elem->type = GHOST_kCSDTypeBorderTopLeft;
elem->bounds[0][0] = 0;
elem->bounds[0][1] = border_corner;
elem->bounds[1][0] = 0;
elem->bounds[1][1] = border_corner;
elem = &csd_elems[decor_num++];
elem->type = GHOST_kCSDTypeBorderTopRight;
elem->bounds[0][0] = window_size[0] - border_corner;
elem->bounds[0][1] = window_size[0];
elem->bounds[1][0] = 0;
elem->bounds[1][1] = border_corner;
elem = &csd_elems[decor_num++];
elem->type = GHOST_kCSDTypeBorderBottomLeft;
elem->bounds[0][0] = 0;
elem->bounds[0][1] = border_corner;
elem->bounds[1][0] = window_size[1] - border_corner;
elem->bounds[1][1] = window_size[1];
elem = &csd_elems[decor_num++];
elem->type = GHOST_kCSDTypeBorderBottomRight;
elem->bounds[0][0] = window_size[0] - border_corner;
elem->bounds[0][1] = window_size[0];
elem->bounds[1][0] = window_size[1] - border_corner;
elem->bounds[1][1] = window_size[1];
/* Border: axis aligned. */
elem = &csd_elems[decor_num++];
elem->type = GHOST_kCSDTypeBorderTop;
elem->bounds[0][0] = 0;
elem->bounds[0][1] = window_size[0];
elem->bounds[1][0] = 0;
elem->bounds[1][1] = border;
elem = &csd_elems[decor_num++];
elem->type = GHOST_kCSDTypeBorderBottom;
elem->bounds[0][0] = 0;
elem->bounds[0][1] = window_size[0];
elem->bounds[1][0] = window_size[1] - border;
elem->bounds[1][1] = window_size[1];
elem = &csd_elems[decor_num++];
elem->type = GHOST_kCSDTypeBorderLeft;
elem->bounds[0][0] = 0;
elem->bounds[0][1] = border;
elem->bounds[1][0] = 0;
elem->bounds[1][1] = window_size[1];
elem = &csd_elems[decor_num++];
elem->type = GHOST_kCSDTypeBorderRight;
elem->bounds[0][0] = window_size[0] - border;
elem->bounds[0][1] = window_size[0];
elem->bounds[1][0] = 0;
elem->bounds[1][1] = window_size[1];
}
return decor_num;
}
void WM_window_csd_rect_calc(const wmWindow *win, rcti *r_rect)
{
const GHOST_CSD_Layout *csd_layout = WM_window_csd_layout_get();
GHOST_IWindow *ghost_window = static_cast<GHOST_IWindow *>(win->runtime->ghostwin);
const int fractional_scale[2] = {GHOST_CSD_DPI_FRACTIONAL_BASE, ghost_window->getDPIHint()};
GHOST_CSD_Elem csd_elems[GHOST_kCSDType_NUM];
const int2 win_size = WM_window_native_pixel_size(win);
const int decor_num = WM_window_csd_layout_callback(
win_size, fractional_scale, GHOST_TWindowState(win->windowstate), csd_layout, csd_elems);
const GHOST_CSD_Elem *elem = nullptr;
for (int i = 0; i < decor_num; i++) {
/* Typically the first. */
if (csd_elems[i].type == GHOST_kCSDTypeBody) {
elem = &csd_elems[i];
break;
}
}
if (elem == nullptr) {
BLI_assert_msg(0, "unexpected, no window contents");
BLI_rcti_init(r_rect, 0, win_size[0], 0, win_size[1]);
}
/* Flip the Y. */
r_rect->xmin = elem->bounds[0][0];
r_rect->xmax = elem->bounds[0][1];
r_rect->ymin = win_size.y - elem->bounds[1][1];
r_rect->ymax = win_size.y - elem->bounds[1][0];
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,70 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*
* Window icon generation for Wayland.
* Rasterizes the full-color application SVG for a window icon.
*/
#include <algorithm>
#include <cstring>
#include <string>
#include "GHOST_Types.hh"
#include "nanosvgrast.h"
#include "wm_window_icon.hh"
extern "C" const char datatoc_blender_app_icon_svg[];
static void wm_ghost_icon_generate(const GHOST_IconGenerator * /*icon_generator*/,
GHOST_IWindow * /*window*/,
uint8_t *pixels,
int icon_size)
{
const size_t buffer_size = size_t(icon_size) * icon_size * 4;
const int stride = icon_size * 4;
/* Rasterize the Blender logo SVG into the icon buffer.
* `nsvgParse` modifies the source string, so make a copy. */
std::string svg_source = datatoc_blender_app_icon_svg;
NSVGimage *image = nsvgParse(svg_source.data(), "px", 96.0f);
/* The bundled SVG is known to be valid. */
if (image == nullptr || image->width == 0 || image->height == 0) [[unlikely]] {
if (image) {
nsvgDelete(image);
}
memset(pixels, 0, buffer_size);
return;
}
NSVGrasterizer *rast = nsvgCreateRasterizer();
/* Rasterizer allocation should not fail for a small buffer. */
if (rast == nullptr) [[unlikely]] {
nsvgDelete(image);
memset(pixels, 0, buffer_size);
return;
}
/* Scale to fit the icon, centering the shorter axis. */
const float scale = float(icon_size) / std::max(image->width, image->height);
const float offset_x = (icon_size - image->width * scale) * 0.5f;
const float offset_y = (icon_size - image->height * scale) * 0.5f;
/* Clear the buffer first since the SVG may not fill the entire square. */
memset(pixels, 0, buffer_size);
nsvgRasterize(rast, image, offset_x, offset_y, scale, pixels, icon_size, icon_size, stride);
nsvgDeleteRasterizer(rast);
nsvgDelete(image);
}
const GHOST_IconGenerator wm_ghost_icon_generator = {
/*generate_fn*/ wm_ghost_icon_generate,
/*user_data*/ nullptr,
};

View File

@@ -0,0 +1,13 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*/
#pragma once
struct GHOST_IconGenerator;
extern const GHOST_IconGenerator wm_ghost_icon_generator;

View File

@@ -0,0 +1,87 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup wm
*/
#pragma once
#include "GHOST_Types.hh"
#include "GPU_platform_backend_enum.h"
struct GHOST_CSD_Layout;
namespace blender {
struct bContext;
struct wmWindow;
/* *************** Message box *************** */
/* `WM_ghost_show_message_box` is implemented in `wm_windows.c` it is
* defined here as it was implemented to be used for showing
* a message to the user when the platform is not (fully) supported.
*
* In all other cases this message box should not be used. */
void WM_ghost_show_message_box(const char *title,
const char *message,
const char *help_label,
const char *continue_label,
const char *link,
GHOST_DialogOptions dialog_options);
GHOST_TDrawingContextType wm_ghost_drawing_context_type(const GPUBackendType gpu_backend);
void wm_test_gpu_backend_fallback(bContext *C);
/* wm_window_csd_draw.cc */
/**
* \param win_size: The window size from GHOST, un-scaled.
* \param win_state: The window state (normal, maximized etc).
* \param is_active: The active state of the window.
* \param dpi: The DPI returned by GHOST (no UI scale preferences).
* \param title: The window title or null to display no title.
* \param font_id: The font to display the title.
* \param font_size: The font size to display the title
* \param border_color: The border color or null of the CSD to display as an overlay
* (used by the animation player).
* \param alpha: Transparency so decorations can be an overlay that is "faded" out
* (used by the animation player).
*/
void WM_window_csd_draw_titlebar_ex(const int win_size[2],
char win_state,
const GHOST_CSD_Layout *csd_layout,
bool is_active,
const uint16_t dpi,
const char *title,
int font_id,
int font_size,
const uchar border_color[3],
const uchar text_color[3],
float alpha);
void WM_window_csd_draw_titlebar(const wmWindow *win);
/* wm_window_csd_layout.cc */
/**
* Apply fractional scale for client side decorations.
*/
int WM_window_csd_fracitonal_scale_apply(int value, const int fractional_scale[2]);
/**
* Callback for GHOST that defines the layout of client side decorations.
*
* Also used to calculate the visible area of a window when #WM_window_is_csd returns true.
*
* \param csd_layout: When null, buttons won't be included.
*/
int WM_window_csd_layout_callback(const int window_size[2],
const int fractional_scale[2],
char window_state,
const GHOST_CSD_Layout *csd_layout,
GHOST_CSD_Elem *csd_elems);
const GHOST_CSD_Layout *WM_window_csd_layout_get();
} // namespace blender