Add Chromium-only Blender WebEngine parity work
This commit is contained in:
623
blender-5.2.0/source/blender/windowmanager/intern/wm.cc
Normal file
623
blender-5.2.0/source/blender/windowmanager/intern/wm.cc
Normal 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
|
||||
1028
blender-5.2.0/source/blender/windowmanager/intern/wm_cursors.cc
Normal file
1028
blender-5.2.0/source/blender/windowmanager/intern/wm_cursors.cc
Normal file
File diff suppressed because it is too large
Load Diff
1443
blender-5.2.0/source/blender/windowmanager/intern/wm_dragdrop.cc
Normal file
1443
blender-5.2.0/source/blender/windowmanager/intern/wm_dragdrop.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
1787
blender-5.2.0/source/blender/windowmanager/intern/wm_draw.cc
Normal file
1787
blender-5.2.0/source/blender/windowmanager/intern/wm_draw.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
7022
blender-5.2.0/source/blender/windowmanager/intern/wm_event_system.cc
Normal file
7022
blender-5.2.0/source/blender/windowmanager/intern/wm_event_system.cc
Normal file
File diff suppressed because it is too large
Load Diff
5213
blender-5.2.0/source/blender/windowmanager/intern/wm_files.cc
Normal file
5213
blender-5.2.0/source/blender/windowmanager/intern/wm_files.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
1105
blender-5.2.0/source/blender/windowmanager/intern/wm_files_link.cc
Normal file
1105
blender-5.2.0/source/blender/windowmanager/intern/wm_files_link.cc
Normal file
File diff suppressed because it is too large
Load Diff
620
blender-5.2.0/source/blender/windowmanager/intern/wm_gesture.cc
Normal file
620
blender-5.2.0/source/blender/windowmanager/intern/wm_gesture.cc
Normal 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(>->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 >, 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, >->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(>->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
|
||||
1279
blender-5.2.0/source/blender/windowmanager/intern/wm_gesture_ops.cc
Normal file
1279
blender-5.2.0/source/blender/windowmanager/intern/wm_gesture_ops.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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, ¶ms_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
|
||||
830
blender-5.2.0/source/blender/windowmanager/intern/wm_jobs.cc
Normal file
830
blender-5.2.0/source/blender/windowmanager/intern/wm_jobs.cc
Normal 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
|
||||
2290
blender-5.2.0/source/blender/windowmanager/intern/wm_keymap.cc
Normal file
2290
blender-5.2.0/source/blender/windowmanager/intern/wm_keymap.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 = ®ion->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
|
||||
@@ -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
|
||||
4736
blender-5.2.0/source/blender/windowmanager/intern/wm_operators.cc
Normal file
4736
blender-5.2.0/source/blender/windowmanager/intern/wm_operators.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
100
blender-5.2.0/source/blender/windowmanager/intern/wm_platform.cc
Normal file
100
blender-5.2.0/source/blender/windowmanager/intern/wm_platform.cc
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
2381
blender-5.2.0/source/blender/windowmanager/intern/wm_playanim.cc
Normal file
2381
blender-5.2.0/source/blender/windowmanager/intern/wm_playanim.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
408
blender-5.2.0/source/blender/windowmanager/intern/wm_stereo.cc
Normal file
408
blender-5.2.0/source/blender/windowmanager/intern/wm_stereo.cc
Normal 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
|
||||
@@ -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
|
||||
145
blender-5.2.0/source/blender/windowmanager/intern/wm_surface.cc
Normal file
145
blender-5.2.0/source/blender/windowmanager/intern/wm_surface.cc
Normal 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
|
||||
1348
blender-5.2.0/source/blender/windowmanager/intern/wm_toolsystem.cc
Normal file
1348
blender-5.2.0/source/blender/windowmanager/intern/wm_toolsystem.cc
Normal file
File diff suppressed because it is too large
Load Diff
148
blender-5.2.0/source/blender/windowmanager/intern/wm_tooltip.cc
Normal file
148
blender-5.2.0/source/blender/windowmanager/intern/wm_tooltip.cc
Normal 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
|
||||
@@ -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 ®ion : *regionbase) {
|
||||
wm_uilisttype_unlink_from_region(ult, ®ion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ®ion : screen->regionbase) {
|
||||
wm_uilisttype_unlink_from_region(ult, ®ion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -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
|
||||
3532
blender-5.2.0/source/blender/windowmanager/intern/wm_window.cc
Normal file
3532
blender-5.2.0/source/blender/windowmanager/intern/wm_window.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user