diff options
Diffstat (limited to 'source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c')
-rw-r--r-- | source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c | 1209 |
1 files changed, 1209 insertions, 0 deletions
diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c new file mode 100644 index 00000000000..94db6b45887 --- /dev/null +++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c @@ -0,0 +1,1209 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2014 Blender Foundation. + * All rights reserved. + * + * Contributor(s): Blender Foundation + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/windowmanager/gizmo/intern/wm_gizmo_map.c + * \ingroup wm + */ + +#include <string.h> + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_rect.h" +#include "BLI_string.h" +#include "BLI_ghash.h" + +#include "BKE_context.h" +#include "BKE_global.h" + +#include "ED_screen.h" +#include "ED_view3d.h" + +#include "GPU_glew.h" +#include "GPU_matrix.h" +#include "GPU_select.h" + +#include "MEM_guardedalloc.h" + +#include "WM_api.h" +#include "WM_types.h" +#include "wm_event_system.h" + +/* for tool-tips */ +#include "UI_interface.h" + +#include "DEG_depsgraph.h" + +/* own includes */ +#include "wm_gizmo_wmapi.h" +#include "wm_gizmo_intern.h" + +/** + * Store all manipulator-maps here. Anyone who wants to register a manipulator for a certain + * area type can query the manipulator-map to do so. + */ +static ListBase manipulatormaptypes = {NULL, NULL}; + +/** + * Update when manipulator-map types change. + */ +/* so operator removal can trigger update */ +typedef enum eWM_ManipulatorGroupTypeGlobalFlag { + WM_MANIPULATORMAPTYPE_GLOBAL_UPDATE_INIT = (1 << 0), + WM_MANIPULATORMAPTYPE_GLOBAL_UPDATE_REMOVE = (1 << 1), +} eWM_ManipulatorGroupTypeGlobalFlag; + +static eWM_ManipulatorGroupTypeGlobalFlag wm_mmap_type_update_flag = 0; + +/** + * Manipulator-map update tagging. + */ +enum { + /** #manipulatormap_prepare_drawing has run */ + MANIPULATORMAP_IS_PREPARE_DRAW = (1 << 0), + MANIPULATORMAP_IS_REFRESH_CALLBACK = (1 << 1), +}; + + +/* -------------------------------------------------------------------- */ +/** \name wmManipulatorMap Selection Array API + * + * Just handle ``wm_manipulatormap_select_array_*``, not flags or callbacks. + * + * \{ */ + +static void wm_manipulatormap_select_array_ensure_len_alloc(wmManipulatorMap *mmap, int len) +{ + wmManipulatorMapSelectState *msel = &mmap->mmap_context.select; + if (len <= msel->len_alloc) { + return; + } + msel->items = MEM_reallocN(msel->items, sizeof(*msel->items) * len); + msel->len_alloc = len; +} + +void wm_manipulatormap_select_array_clear(wmManipulatorMap *mmap) +{ + wmManipulatorMapSelectState *msel = &mmap->mmap_context.select; + MEM_SAFE_FREE(msel->items); + msel->len = 0; + msel->len_alloc = 0; +} + +void wm_manipulatormap_select_array_shrink(wmManipulatorMap *mmap, int len_subtract) +{ + wmManipulatorMapSelectState *msel = &mmap->mmap_context.select; + msel->len -= len_subtract; + if (msel->len <= 0) { + wm_manipulatormap_select_array_clear(mmap); + } + else { + if (msel->len < msel->len_alloc / 2) { + msel->items = MEM_reallocN(msel->items, sizeof(*msel->items) * msel->len); + msel->len_alloc = msel->len; + } + } +} + +void wm_manipulatormap_select_array_push_back(wmManipulatorMap *mmap, wmManipulator *mpr) +{ + wmManipulatorMapSelectState *msel = &mmap->mmap_context.select; + BLI_assert(msel->len <= msel->len_alloc); + if (msel->len == msel->len_alloc) { + msel->len_alloc = (msel->len + 1) * 2; + msel->items = MEM_reallocN(msel->items, sizeof(*msel->items) * msel->len_alloc); + } + msel->items[msel->len++] = mpr; +} + +void wm_manipulatormap_select_array_remove(wmManipulatorMap *mmap, wmManipulator *mpr) +{ + wmManipulatorMapSelectState *msel = &mmap->mmap_context.select; + /* remove manipulator from selected_manipulators array */ + for (int i = 0; i < msel->len; i++) { + if (msel->items[i] == mpr) { + for (int j = i; j < (msel->len - 1); j++) { + msel->items[j] = msel->items[j + 1]; + } + wm_manipulatormap_select_array_shrink(mmap, 1); + break; + } + } + +} + +/** \} */ + + +/* -------------------------------------------------------------------- */ +/** \name wmManipulatorMap + * + * \{ */ + +/** + * Creates a manipulator-map with all registered manipulators for that type + */ +wmManipulatorMap *WM_manipulatormap_new_from_type( + const struct wmManipulatorMapType_Params *mmap_params) +{ + wmManipulatorMapType *mmap_type = WM_manipulatormaptype_ensure(mmap_params); + wmManipulatorMap *mmap; + + mmap = MEM_callocN(sizeof(wmManipulatorMap), "ManipulatorMap"); + mmap->type = mmap_type; + WM_manipulatormap_tag_refresh(mmap); + + /* create all manipulator-groups for this manipulator-map. We may create an empty one + * too in anticipation of manipulators from operators etc */ + for (wmManipulatorGroupTypeRef *wgt_ref = mmap_type->grouptype_refs.first; wgt_ref; wgt_ref = wgt_ref->next) { + wm_manipulatorgroup_new_from_type(mmap, wgt_ref->type); + } + + return mmap; +} + +void wm_manipulatormap_remove(wmManipulatorMap *mmap) +{ + /* Clear first so further calls don't waste time trying to maintain correct array state. */ + wm_manipulatormap_select_array_clear(mmap); + + for (wmManipulatorGroup *mgroup = mmap->groups.first, *mgroup_next; mgroup; mgroup = mgroup_next) { + mgroup_next = mgroup->next; + BLI_assert(mgroup->parent_mmap == mmap); + wm_manipulatorgroup_free(NULL, mgroup); + } + BLI_assert(BLI_listbase_is_empty(&mmap->groups)); + + MEM_freeN(mmap); +} + + +wmManipulatorGroup *WM_manipulatormap_group_find( + struct wmManipulatorMap *mmap, + const char *idname) +{ + wmManipulatorGroupType *wgt = WM_manipulatorgrouptype_find(idname, false); + if (wgt) { + return WM_manipulatormap_group_find_ptr(mmap, wgt); + } + return NULL; +} + +wmManipulatorGroup *WM_manipulatormap_group_find_ptr( + struct wmManipulatorMap *mmap, + const struct wmManipulatorGroupType *wgt) +{ + for (wmManipulatorGroup *mgroup = mmap->groups.first; mgroup; mgroup = mgroup->next) { + if (mgroup->type == wgt) { + return mgroup; + } + } + return NULL; +} + +const ListBase *WM_manipulatormap_group_list(wmManipulatorMap *mmap) +{ + return &mmap->groups; +} + +bool WM_manipulatormap_is_any_selected(const wmManipulatorMap *mmap) +{ + return mmap->mmap_context.select.len != 0; +} + +/** + * \note We could use a callback to define bounds, for now just use matrix location. + */ +bool WM_manipulatormap_minmax( + const wmManipulatorMap *mmap, bool UNUSED(use_hidden), bool use_select, + float r_min[3], float r_max[3]) +{ + if (use_select) { + int i; + for (i = 0; i < mmap->mmap_context.select.len; i++) { + minmax_v3v3_v3(r_min, r_max, mmap->mmap_context.select.items[i]->matrix_basis[3]); + } + return i != 0; + } + else { + bool ok = false; + BLI_assert(!"TODO"); + return ok; + } +} + +/** + * Creates and returns idname hash table for (visible) manipulators in \a mmap + * + * \param poll Polling function for excluding manipulators. + * \param data Custom data passed to \a poll + * + * TODO(campbell): this uses unreliable order, + * best we use an iterator function instead of a hash. + */ +static GHash *WM_manipulatormap_manipulator_hash_new( + const bContext *C, wmManipulatorMap *mmap, + bool (*poll)(const wmManipulator *, void *), + void *data, const bool include_hidden) +{ + GHash *hash = BLI_ghash_ptr_new(__func__); + + /* collect manipulators */ + for (wmManipulatorGroup *mgroup = mmap->groups.first; mgroup; mgroup = mgroup->next) { + if (WM_manipulator_group_type_poll(C, mgroup->type)) { + for (wmManipulator *mpr = mgroup->manipulators.first; mpr; mpr = mpr->next) { + if ((include_hidden || (mpr->flag & WM_MANIPULATOR_HIDDEN) == 0) && + (!poll || poll(mpr, data))) + { + BLI_ghash_insert(hash, mpr, mpr); + } + } + } + } + + return hash; +} + +void WM_manipulatormap_tag_refresh(wmManipulatorMap *mmap) +{ + if (mmap) { + /* We might want only to refresh some, for tag all steps. */ + for (int i = 0; i < WM_MANIPULATORMAP_DRAWSTEP_MAX; i++) { + mmap->update_flag[i] |= ( + MANIPULATORMAP_IS_PREPARE_DRAW | + MANIPULATORMAP_IS_REFRESH_CALLBACK); + } + } +} + +static bool manipulator_prepare_drawing( + wmManipulatorMap *mmap, wmManipulator *mpr, + const bContext *C, ListBase *draw_manipulators, + const eWM_ManipulatorMapDrawStep drawstep) +{ + int do_draw = wm_manipulator_is_visible(mpr); + if (do_draw == 0) { + /* skip */ + } + else { + /* Ensure we get RNA updates */ + if (do_draw & WM_MANIPULATOR_IS_VISIBLE_UPDATE) { + /* hover manipulators need updating, even if we don't draw them */ + wm_manipulator_update(mpr, C, (mmap->update_flag[drawstep] & MANIPULATORMAP_IS_PREPARE_DRAW) != 0); + } + if (do_draw & WM_MANIPULATOR_IS_VISIBLE_DRAW) { + BLI_addhead(draw_manipulators, BLI_genericNodeN(mpr)); + } + return true; + } + + return false; +} + +/** + * Update manipulators of \a mmap to prepare for drawing. Adds all manipulators that + * should be drawn to list \a draw_manipulators, note that added items need freeing. + */ +static void manipulatormap_prepare_drawing( + wmManipulatorMap *mmap, const bContext *C, ListBase *draw_manipulators, + const eWM_ManipulatorMapDrawStep drawstep) +{ + if (!mmap || BLI_listbase_is_empty(&mmap->groups)) + return; + wmManipulator *mpr_modal = mmap->mmap_context.modal; + + /* only active manipulator needs updating */ + if (mpr_modal) { + if ((mpr_modal->parent_mgroup->type->flag & WM_MANIPULATORGROUPTYPE_DRAW_MODAL_ALL) == 0) { + if (wm_manipulatorgroup_is_visible_in_drawstep(mpr_modal->parent_mgroup, drawstep)) { + if (manipulator_prepare_drawing(mmap, mpr_modal, C, draw_manipulators, drawstep)) { + mmap->update_flag[drawstep] &= ~MANIPULATORMAP_IS_PREPARE_DRAW; + } + } + /* don't draw any other manipulators */ + return; + } + } + + for (wmManipulatorGroup *mgroup = mmap->groups.first; mgroup; mgroup = mgroup->next) { + /* check group visibility - drawstep first to avoid unnecessary call of group poll callback */ + if (!wm_manipulatorgroup_is_visible_in_drawstep(mgroup, drawstep) || + !WM_manipulator_group_type_poll(C, mgroup->type)) + { + continue; + } + + /* needs to be initialized on first draw */ + /* XXX weak: Manipulator-group may skip refreshing if it's invisible (map gets untagged nevertheless) */ + if (mmap->update_flag[drawstep] & MANIPULATORMAP_IS_REFRESH_CALLBACK) { + /* force refresh again. */ + mgroup->init_flag &= ~WM_MANIPULATORGROUP_INIT_REFRESH; + } + /* Calls `setup`, `setup_keymap` and `refresh` if they're defined. */ + wm_manipulatorgroup_ensure_initialized(mgroup, C); + + /* prepare drawing */ + if (mgroup->type->draw_prepare) { + mgroup->type->draw_prepare(C, mgroup); + } + + for (wmManipulator *mpr = mgroup->manipulators.first; mpr; mpr = mpr->next) { + manipulator_prepare_drawing(mmap, mpr, C, draw_manipulators, drawstep); + } + } + + mmap->update_flag[drawstep] &= + ~(MANIPULATORMAP_IS_REFRESH_CALLBACK | + MANIPULATORMAP_IS_PREPARE_DRAW); +} + +/** + * Draw all visible manipulators in \a mmap. + * Uses global draw_manipulators listbase. + */ +static void manipulators_draw_list(const wmManipulatorMap *mmap, const bContext *C, ListBase *draw_manipulators) +{ + /* Can be empty if we're dynamically added and removed. */ + if ((mmap == NULL) || BLI_listbase_is_empty(&mmap->groups)) { + return; + } + + /* TODO this will need it own shader probably? don't think it can be handled from that point though. */ +/* const bool use_lighting = (U.manipulator_flag & V3D_MANIPULATOR_SHADED) != 0; */ + + bool is_depth_prev = false; + + /* draw_manipulators contains all visible manipulators - draw them */ + for (LinkData *link = draw_manipulators->first, *link_next; link; link = link_next) { + wmManipulator *mpr = link->data; + link_next = link->next; + + bool is_depth = (mpr->parent_mgroup->type->flag & WM_MANIPULATORGROUPTYPE_DEPTH_3D) != 0; + + /* Weak! since we don't 100% support depth yet (select ignores depth) always show highlighted */ + if (is_depth && (mpr->state & WM_MANIPULATOR_STATE_HIGHLIGHT)) { + is_depth = false; + } + + if (is_depth == is_depth_prev) { + /* pass */ + } + else { + if (is_depth) { + glEnable(GL_DEPTH_TEST); + } + else { + glDisable(GL_DEPTH_TEST); + } + is_depth_prev = is_depth; + } + + /* XXX force AntiAlias Manipulators. */ + glEnable(GL_LINE_SMOOTH); + glEnable(GL_POLYGON_SMOOTH); + + mpr->type->draw(C, mpr); + + glDisable(GL_LINE_SMOOTH); + glDisable(GL_POLYGON_SMOOTH); + + /* free/remove manipulator link after drawing */ + BLI_freelinkN(draw_manipulators, link); + } + + if (is_depth_prev) { + glDisable(GL_DEPTH_TEST); + } +} + +void WM_manipulatormap_draw( + wmManipulatorMap *mmap, const bContext *C, + const eWM_ManipulatorMapDrawStep drawstep) +{ + if (!WM_manipulator_context_check_drawstep(C, drawstep)) { + return; + } + + ListBase draw_manipulators = {NULL}; + + manipulatormap_prepare_drawing(mmap, C, &draw_manipulators, drawstep); + manipulators_draw_list(mmap, C, &draw_manipulators); + BLI_assert(BLI_listbase_is_empty(&draw_manipulators)); +} + +static void manipulator_draw_select_3D_loop(const bContext *C, ListBase *visible_manipulators) +{ + int select_id = 0; + wmManipulator *mpr; + + /* TODO(campbell): this depends on depth buffer being written to, currently broken for the 3D view. */ + bool is_depth_prev = false; + bool is_depth_skip_prev = false; + + for (LinkData *link = visible_manipulators->first; link; link = link->next) { + mpr = link->data; + + bool is_depth = (mpr->parent_mgroup->type->flag & WM_MANIPULATORGROUPTYPE_DEPTH_3D) != 0; + if (is_depth == is_depth_prev) { + /* pass */ + } + else { + if (is_depth) { + glEnable(GL_DEPTH_TEST); + } + else { + glDisable(GL_DEPTH_TEST); + } + is_depth_prev = is_depth; + } + bool is_depth_skip = (mpr->flag & WM_MANIPULATOR_SELECT_BACKGROUND) != 0; + if (is_depth_skip == is_depth_skip_prev) { + /* pass */ + } + else { + glDepthMask(!is_depth_skip); + is_depth_skip_prev = is_depth_skip; + } + + /* pass the selection id shifted by 8 bits. Last 8 bits are used for selected manipulator part id */ + + mpr->type->draw_select(C, mpr, select_id << 8); + + + select_id++; + } + + if (is_depth_prev) { + glDisable(GL_DEPTH_TEST); + } + if (is_depth_skip_prev) { + glDepthMask(true); + } +} + +static int manipulator_find_intersected_3d_intern( + ListBase *visible_manipulators, const bContext *C, const int co[2], + const int hotspot) +{ + ScrArea *sa = CTX_wm_area(C); + ARegion *ar = CTX_wm_region(C); + View3D *v3d = sa->spacedata.first; + rcti rect; + /* Almost certainly overkill, but allow for many custom manipulators. */ + GLuint buffer[MAXPICKBUF]; + short hits; + const bool do_passes = GPU_select_query_check_active(); + + BLI_rcti_init_pt_radius(&rect, co, hotspot); + + ED_view3d_draw_setup_view(CTX_wm_window(C), CTX_data_depsgraph(C), CTX_data_scene(C), ar, v3d, NULL, NULL, &rect); + + if (do_passes) + GPU_select_begin(buffer, ARRAY_SIZE(buffer), &rect, GPU_SELECT_NEAREST_FIRST_PASS, 0); + else + GPU_select_begin(buffer, ARRAY_SIZE(buffer), &rect, GPU_SELECT_ALL, 0); + /* do the drawing */ + manipulator_draw_select_3D_loop(C, visible_manipulators); + + hits = GPU_select_end(); + + if (do_passes && (hits > 0)) { + GPU_select_begin(buffer, ARRAY_SIZE(buffer), &rect, GPU_SELECT_NEAREST_SECOND_PASS, hits); + manipulator_draw_select_3D_loop(C, visible_manipulators); + GPU_select_end(); + } + + ED_view3d_draw_setup_view(CTX_wm_window(C), CTX_data_depsgraph(C), CTX_data_scene(C), ar, v3d, NULL, NULL, NULL); + + const GLuint *hit_near = GPU_select_buffer_near(buffer, hits); + + return hit_near ? hit_near[3] : -1; +} + +/** + * Try to find a 3D manipulator at screen-space coordinate \a co. Uses OpenGL picking. + */ +static wmManipulator *manipulator_find_intersected_3d( + bContext *C, const int co[2], ListBase *visible_manipulators, + int *r_part) +{ + wmManipulator *result = NULL; + int hit = -1; + + int hotspot_radii[] = { + 3 * U.pixelsize, + /* This runs on mouse move, careful doing too many tests! */ + 10 * U.pixelsize, + }; + + *r_part = 0; + + /* set up view matrices */ + view3d_operator_needs_opengl(C); + + hit = -1; + + for (int i = 0; i < ARRAY_SIZE(hotspot_radii); i++) { + hit = manipulator_find_intersected_3d_intern(visible_manipulators, C, co, hotspot_radii[i]); + if (hit != -1) { + break; + } + } + + if (hit != -1) { + LinkData *link = BLI_findlink(visible_manipulators, hit >> 8); + if (link != NULL) { + *r_part = hit & 255; + result = link->data; + } + else { + /* All manipulators should use selection ID they're given as part of the callback, + * if they don't it will attempt tp lookup non-existing index. */ + BLI_assert(0); + } + } + + return result; +} + +/** + * Try to find a manipulator under the mouse position. 2D intersections have priority over + * 3D ones (could check for smallest screen-space distance but not needed right now). + */ +wmManipulator *wm_manipulatormap_highlight_find( + wmManipulatorMap *mmap, bContext *C, const wmEvent *event, + int *r_part) +{ + wmManipulator *mpr = NULL; + ListBase visible_3d_manipulators = {NULL}; + bool do_step[WM_MANIPULATORMAP_DRAWSTEP_MAX]; + + for (int i = 0; i < ARRAY_SIZE(do_step); i++) { + do_step[i] = WM_manipulator_context_check_drawstep(C, i); + } + + for (wmManipulatorGroup *mgroup = mmap->groups.first; mgroup; mgroup = mgroup->next) { + + /* If it were important we could initialize here, + * but this only happens when events are handled before drawing, + * just skip to keep code-path for initializing manipulators simple. */ + if ((mgroup->init_flag & WM_MANIPULATORGROUP_INIT_SETUP) == 0) { + continue; + } + + if (WM_manipulator_group_type_poll(C, mgroup->type)) { + eWM_ManipulatorMapDrawStep step; + if (mgroup->type->flag & WM_MANIPULATORGROUPTYPE_3D) { + step = WM_MANIPULATORMAP_DRAWSTEP_3D; + } + else { + step = WM_MANIPULATORMAP_DRAWSTEP_2D; + } + + if (do_step[step]) { + if ((mmap->update_flag[step] & MANIPULATORMAP_IS_REFRESH_CALLBACK) && + (mgroup->type->refresh != NULL)) + { + mgroup->type->refresh(C, mgroup); + /* cleared below */ + } + if (step == WM_MANIPULATORMAP_DRAWSTEP_3D) { + wm_manipulatorgroup_intersectable_manipulators_to_list(mgroup, &visible_3d_manipulators); + } + else if (step == WM_MANIPULATORMAP_DRAWSTEP_2D) { + if ((mpr = wm_manipulatorgroup_find_intersected_manipulator(mgroup, C, event, r_part))) { + break; + } + } + } + } + } + + if (!BLI_listbase_is_empty(&visible_3d_manipulators)) { + /* 2D manipulators get priority. */ + if (mpr == NULL) { + mpr = manipulator_find_intersected_3d(C, event->mval, &visible_3d_manipulators, r_part); + } + BLI_freelistN(&visible_3d_manipulators); + } + + mmap->update_flag[WM_MANIPULATORMAP_DRAWSTEP_3D] &= ~MANIPULATORMAP_IS_REFRESH_CALLBACK; + mmap->update_flag[WM_MANIPULATORMAP_DRAWSTEP_2D] &= ~MANIPULATORMAP_IS_REFRESH_CALLBACK; + + return mpr; +} + +void WM_manipulatormap_add_handlers(ARegion *ar, wmManipulatorMap *mmap) +{ + wmEventHandler *handler; + + for (handler = ar->handlers.first; handler; handler = handler->next) { + if (handler->manipulator_map == mmap) { + return; + } + } + + handler = MEM_callocN(sizeof(wmEventHandler), "manipulator handler"); + + BLI_assert(mmap == ar->manipulator_map); + handler->manipulator_map = mmap; + BLI_addtail(&ar->handlers, handler); +} + +void wm_manipulatormaps_handled_modal_update( + bContext *C, wmEvent *event, wmEventHandler *handler) +{ + const bool modal_running = (handler->op != NULL); + + /* happens on render or when joining areas */ + if (!handler->op_region || !handler->op_region->manipulator_map) { + return; + } + + wmManipulatorMap *mmap = handler->op_region->manipulator_map; + wmManipulator *mpr = wm_manipulatormap_modal_get(mmap); + ScrArea *area = CTX_wm_area(C); + ARegion *region = CTX_wm_region(C); + + wm_manipulatormap_handler_context(C, handler); + + /* regular update for running operator */ + if (modal_running) { + wmManipulatorOpElem *mpop = mpr ? WM_manipulator_operator_get(mpr, mpr->highlight_part) : NULL; + if (mpr && mpop && (mpop->type != NULL) && (mpop->type == handler->op->type)) { + wmManipulatorFnModal modal_fn = mpr->custom_modal ? mpr->custom_modal : mpr->type->modal; + if (modal_fn != NULL) { + int retval = modal_fn(C, mpr, event, 0); + /* The manipulator is tried to the operator, we can't choose when to exit. */ + BLI_assert(retval & OPERATOR_RUNNING_MODAL); + UNUSED_VARS_NDEBUG(retval); + } + } + } + /* operator not running anymore */ + else { + wm_manipulatormap_highlight_set(mmap, C, NULL, 0); + if (mpr) { + /* This isn't defined if it ends because of success of cancel, we may want to change. */ + bool cancel = true; + if (mpr->type->exit) { + mpr->type->exit(C, mpr, cancel); + } + wm_manipulatormap_modal_set(mmap, C, mpr, NULL, false); + } + } + + /* restore the area */ + CTX_wm_area_set(C, area); + CTX_wm_region_set(C, region); +} + +/** + * Deselect all selected manipulators in \a mmap. + * \return if selection has changed. + */ +bool wm_manipulatormap_deselect_all(wmManipulatorMap *mmap) +{ + wmManipulatorMapSelectState *msel = &mmap->mmap_context.select; + + if (msel->items == NULL || msel->len == 0) { + return false; + } + + for (int i = 0; i < msel->len; i++) { + wm_manipulator_select_set_ex(mmap, msel->items[i], false, false, true); + } + + wm_manipulatormap_select_array_clear(mmap); + + /* always return true, we already checked + * if there's anything to deselect */ + return true; +} + +BLI_INLINE bool manipulator_selectable_poll(const wmManipulator *mpr, void *UNUSED(data)) +{ + return (mpr->parent_mgroup->type->flag & WM_MANIPULATORGROUPTYPE_SELECT); +} + +/** + * Select all selectable manipulators in \a mmap. + * \return if selection has changed. + */ +static bool wm_manipulatormap_select_all_intern( + bContext *C, wmManipulatorMap *mmap) +{ + wmManipulatorMapSelectState *msel = &mmap->mmap_context.select; + /* GHash is used here to avoid having to loop over all manipulators twice (once to + * get tot_sel for allocating, once for actually selecting). Instead we collect + * selectable manipulators in hash table and use this to get tot_sel and do selection */ + + GHash *hash = WM_manipulatormap_manipulator_hash_new(C, mmap, manipulator_selectable_poll, NULL, true); + GHashIterator gh_iter; + int i; + bool changed = false; + + wm_manipulatormap_select_array_ensure_len_alloc(mmap, BLI_ghash_len(hash)); + + GHASH_ITER_INDEX (gh_iter, hash, i) { + wmManipulator *mpr_iter = BLI_ghashIterator_getValue(&gh_iter); + WM_manipulator_select_set(mmap, mpr_iter, true); + } + /* highlight first manipulator */ + wm_manipulatormap_highlight_set(mmap, C, msel->items[0], msel->items[0]->highlight_part); + + BLI_assert(BLI_ghash_len(hash) == msel->len); + + BLI_ghash_free(hash, NULL, NULL); + return changed; +} + +/** + * Select/Deselect all selectable manipulators in \a mmap. + * \return if selection has changed. + * + * TODO select all by type + */ +bool WM_manipulatormap_select_all(bContext *C, wmManipulatorMap *mmap, const int action) +{ + bool changed = false; + + switch (action) { + case SEL_SELECT: + changed = wm_manipulatormap_select_all_intern(C, mmap); + break; + case SEL_DESELECT: + changed = wm_manipulatormap_deselect_all(mmap); + break; + default: + BLI_assert(0); + break; + } + + if (changed) + WM_event_add_mousemove(C); + + return changed; +} + +/** + * Prepare context for manipulator handling (but only if area/region is + * part of screen). Version of #wm_handler_op_context for manipulators. + */ +void wm_manipulatormap_handler_context(bContext *C, wmEventHandler *handler) +{ + bScreen *screen = CTX_wm_screen(C); + + if (screen) { + if (handler->op_area == NULL) { + /* do nothing in this context */ + } + else { + ScrArea *sa; + + for (sa = screen->areabase.first; sa; sa = sa->next) + if (sa == handler->op_area) + break; + if (sa == NULL) { + /* when changing screen layouts with running modal handlers (like render display), this + * is not an error to print */ + if (handler->manipulator_map == NULL) + printf("internal error: modal manipulator-map handler has invalid area\n"); + } + else { + ARegion *ar; + CTX_wm_area_set(C, sa); + for (ar = sa->regionbase.first; ar; ar = ar->next) + if (ar == handler->op_region) + break; + /* XXX no warning print here, after full-area and back regions are remade */ + if (ar) + CTX_wm_region_set(C, ar); + } + } + } +} + +bool WM_manipulatormap_cursor_set(const wmManipulatorMap *mmap, wmWindow *win) +{ + wmManipulator *mpr = mmap->mmap_context.highlight; + if (mpr && mpr->type->cursor_get) { + WM_cursor_set(win, mpr->type->cursor_get(mpr)); + return true; + } + + return false; +} + +bool wm_manipulatormap_highlight_set( + wmManipulatorMap *mmap, const bContext *C, wmManipulator *mpr, int part) +{ + if ((mpr != mmap->mmap_context.highlight) || + (mpr && part != mpr->highlight_part)) + { + if (mmap->mmap_context.highlight) { + mmap->mmap_context.highlight->state &= ~WM_MANIPULATOR_STATE_HIGHLIGHT; + mmap->mmap_context.highlight->highlight_part = -1; + } + + mmap->mmap_context.highlight = mpr; + + if (mpr) { + mpr->state |= WM_MANIPULATOR_STATE_HIGHLIGHT; + mpr->highlight_part = part; + mmap->mmap_context.last_cursor = -1; + + if (C && mpr->type->cursor_get) { + wmWindow *win = CTX_wm_window(C); + mmap->mmap_context.last_cursor = win->cursor; + WM_cursor_set(win, mpr->type->cursor_get(mpr)); + } + } + else { + if (C && mmap->mmap_context.last_cursor != -1) { + wmWindow *win = CTX_wm_window(C); + WM_cursor_set(win, mmap->mmap_context.last_cursor); + } + } + + /* tag the region for redraw */ + if (C) { + ARegion *ar = CTX_wm_region(C); + ED_region_tag_redraw(ar); + } + + return true; + } + + return false; +} + +wmManipulator *wm_manipulatormap_highlight_get(wmManipulatorMap *mmap) +{ + return mmap->mmap_context.highlight; +} + +/** + * Caller should call exit when (enable == False). + */ +void wm_manipulatormap_modal_set( + wmManipulatorMap *mmap, bContext *C, wmManipulator *mpr, const wmEvent *event, bool enable) +{ + if (enable) { + BLI_assert(mmap->mmap_context.modal == NULL); + wmWindow *win = CTX_wm_window(C); + + WM_tooltip_clear(C, win); + + if (mpr->type->invoke && + (mpr->type->modal || mpr->custom_modal)) + { + const int retval = mpr->type->invoke(C, mpr, event); + if ((retval & OPERATOR_RUNNING_MODAL) == 0) { + return; + } + } + + mpr->state |= WM_MANIPULATOR_STATE_MODAL; + mmap->mmap_context.modal = mpr; + + if ((mpr->flag & WM_MANIPULATOR_GRAB_CURSOR) && + (event->is_motion_absolute == false)) + { + WM_cursor_grab_enable(win, true, true, NULL); + copy_v2_v2_int(mmap->mmap_context.event_xy, &event->x); + mmap->mmap_context.event_grabcursor = win->grabcursor; + } + else { + mmap->mmap_context.event_xy[0] = INT_MAX; + } + + struct wmManipulatorOpElem *mpop = WM_manipulator_operator_get(mpr, mpr->highlight_part); + if (mpop && mpop->type) { + const int retval = WM_operator_name_call_ptr(C, mpop->type, WM_OP_INVOKE_DEFAULT, &mpop->ptr); + if ((retval & OPERATOR_RUNNING_MODAL) == 0) { + wm_manipulatormap_modal_set(mmap, C, mpr, event, false); + } + + /* we failed to hook the manipulator to the operator handler or operator was cancelled, return */ + if (!mmap->mmap_context.modal) { + mpr->state &= ~WM_MANIPULATOR_STATE_MODAL; + MEM_SAFE_FREE(mpr->interaction_data); + } + return; + } + } + else { + BLI_assert(ELEM(mmap->mmap_context.modal, NULL, mpr)); + + /* deactivate, manipulator but first take care of some stuff */ + if (mpr) { + mpr->state &= ~WM_MANIPULATOR_STATE_MODAL; + MEM_SAFE_FREE(mpr->interaction_data); + } + mmap->mmap_context.modal = NULL; + + if (C) { + wmWindow *win = CTX_wm_window(C); + if (mmap->mmap_context.event_xy[0] != INT_MAX) { + /* Check if some other part of Blender (typically operators) + * have adjusted the grab mode since it was set. + * If so: warp, so we have a predictable outcome. */ + if (mmap->mmap_context.event_grabcursor == win->grabcursor) { + WM_cursor_grab_disable(win, mmap->mmap_context.event_xy); + } + else { + WM_cursor_warp(win, UNPACK2(mmap->mmap_context.event_xy)); + } + } + ED_region_tag_redraw(CTX_wm_region(C)); + WM_event_add_mousemove(C); + } + + mmap->mmap_context.event_xy[0] = INT_MAX; + } +} + +wmManipulator *wm_manipulatormap_modal_get(wmManipulatorMap *mmap) +{ + return mmap->mmap_context.modal; +} + +wmManipulator **wm_manipulatormap_selected_get(wmManipulatorMap *mmap, int *r_selected_len) +{ + *r_selected_len = mmap->mmap_context.select.len; + return mmap->mmap_context.select.items; +} + +ListBase *wm_manipulatormap_groups_get(wmManipulatorMap *mmap) +{ + return &mmap->groups; +} + +void WM_manipulatormap_message_subscribe( + bContext *C, wmManipulatorMap *mmap, ARegion *ar, struct wmMsgBus *mbus) +{ + for (wmManipulatorGroup *mgroup = mmap->groups.first; mgroup; mgroup = mgroup->next) { + if (!WM_manipulator_group_type_poll(C, mgroup->type)) { + continue; + } + for (wmManipulator *mpr = mgroup->manipulators.first; mpr; mpr = mpr->next) { + if (mpr->flag & WM_MANIPULATOR_HIDDEN) { + continue; + } + WM_manipulator_target_property_subscribe_all(mpr, mbus, ar); + } + if (mgroup->type->message_subscribe != NULL) { + mgroup->type->message_subscribe(C, mgroup, mbus); + } + } +} + +/** \} */ /* wmManipulatorMap */ + + +/* -------------------------------------------------------------------- */ +/** \name Tooltip Handling + * + * \{ */ + +struct ARegion *WM_manipulatormap_tooltip_init( + struct bContext *C, struct ARegion *ar, bool *r_exit_on_event) +{ + wmManipulatorMap *mmap = ar->manipulator_map; + *r_exit_on_event = true; + if (mmap) { + wmManipulator *mpr = mmap->mmap_context.highlight; + if (mpr) { + return UI_tooltip_create_from_manipulator(C, mpr); + } + } + return NULL; +} + +/** \} */ /* wmManipulatorMapType */ + +/* -------------------------------------------------------------------- */ +/** \name wmManipulatorMapType + * + * \{ */ + +wmManipulatorMapType *WM_manipulatormaptype_find( + const struct wmManipulatorMapType_Params *mmap_params) +{ + for (wmManipulatorMapType *mmap_type = manipulatormaptypes.first; mmap_type; mmap_type = mmap_type->next) { + if (mmap_type->spaceid == mmap_params->spaceid && + mmap_type->regionid == mmap_params->regionid) + { + return mmap_type; + } + } + + return NULL; +} + +wmManipulatorMapType *WM_manipulatormaptype_ensure( + const struct wmManipulatorMapType_Params *mmap_params) +{ + wmManipulatorMapType *mmap_type = WM_manipulatormaptype_find(mmap_params); + + if (mmap_type) { + return mmap_type; + } + + mmap_type = MEM_callocN(sizeof(wmManipulatorMapType), "manipulatortype list"); + mmap_type->spaceid = mmap_params->spaceid; + mmap_type->regionid = mmap_params->regionid; + BLI_addhead(&manipulatormaptypes, mmap_type); + + return mmap_type; +} + +void wm_manipulatormaptypes_free(void) +{ + for (wmManipulatorMapType *mmap_type = manipulatormaptypes.first, *mmap_type_next; + mmap_type; + mmap_type = mmap_type_next) + { + mmap_type_next = mmap_type->next; + for (wmManipulatorGroupTypeRef *wgt_ref = mmap_type->grouptype_refs.first, *wgt_next; + wgt_ref; + wgt_ref = wgt_next) + { + wgt_next = wgt_ref->next; + WM_manipulatormaptype_group_free(wgt_ref); + } + MEM_freeN(mmap_type); + } +} + +/** + * Initialize keymaps for all existing manipulator-groups + */ +void wm_manipulators_keymap(wmKeyConfig *keyconf) +{ + /* we add this item-less keymap once and use it to group manipulator-group keymaps into it */ + WM_keymap_find(keyconf, "Manipulators", 0, 0); + + for (wmManipulatorMapType *mmap_type = manipulatormaptypes.first; mmap_type; mmap_type = mmap_type->next) { + for (wmManipulatorGroupTypeRef *wgt_ref = mmap_type->grouptype_refs.first; wgt_ref; wgt_ref = wgt_ref->next) { + wm_manipulatorgrouptype_setup_keymap(wgt_ref->type, keyconf); + } + } +} + +/** \} */ /* wmManipulatorMapType */ + +/* -------------------------------------------------------------------- */ +/** \name Updates for Dynamic Type Registraion + * + * \{ */ + + +void WM_manipulatorconfig_update_tag_init( + wmManipulatorMapType *mmap_type, wmManipulatorGroupType *wgt) +{ + /* tag for update on next use */ + mmap_type->type_update_flag |= (WM_MANIPULATORMAPTYPE_UPDATE_INIT | WM_MANIPULATORMAPTYPE_KEYMAP_INIT); + wgt->type_update_flag |= (WM_MANIPULATORMAPTYPE_UPDATE_INIT | WM_MANIPULATORMAPTYPE_KEYMAP_INIT); + + wm_mmap_type_update_flag |= WM_MANIPULATORMAPTYPE_GLOBAL_UPDATE_INIT; +} + +void WM_manipulatorconfig_update_tag_remove( + wmManipulatorMapType *mmap_type, wmManipulatorGroupType *wgt) +{ + /* tag for update on next use */ + mmap_type->type_update_flag |= WM_MANIPULATORMAPTYPE_UPDATE_REMOVE; + wgt->type_update_flag |= WM_MANIPULATORMAPTYPE_UPDATE_REMOVE; + + wm_mmap_type_update_flag |= WM_MANIPULATORMAPTYPE_GLOBAL_UPDATE_REMOVE; +} + +/** + * Run incase new types have been added (runs often, early exit where possible). + * Follows #WM_keyconfig_update concentions. + */ +void WM_manipulatorconfig_update(struct Main *bmain) +{ + if (G.background) + return; + + if (wm_mmap_type_update_flag == 0) + return; + + if (wm_mmap_type_update_flag & WM_MANIPULATORMAPTYPE_GLOBAL_UPDATE_REMOVE) { + for (wmManipulatorMapType *mmap_type = manipulatormaptypes.first; + mmap_type; + mmap_type = mmap_type->next) + { + if (mmap_type->type_update_flag & WM_MANIPULATORMAPTYPE_GLOBAL_UPDATE_REMOVE) { + mmap_type->type_update_flag &= ~WM_MANIPULATORMAPTYPE_UPDATE_REMOVE; + for (wmManipulatorGroupTypeRef *wgt_ref = mmap_type->grouptype_refs.first, *wgt_ref_next; + wgt_ref; + wgt_ref = wgt_ref_next) + { + wgt_ref_next = wgt_ref->next; + if (wgt_ref->type->type_update_flag & WM_MANIPULATORMAPTYPE_UPDATE_REMOVE) { + wgt_ref->type->type_update_flag &= ~WM_MANIPULATORMAPTYPE_UPDATE_REMOVE; + WM_manipulatormaptype_group_unlink(NULL, bmain, mmap_type, wgt_ref->type); + } + } + } + } + + wm_mmap_type_update_flag &= ~WM_MANIPULATORMAPTYPE_GLOBAL_UPDATE_REMOVE; + } + + if (wm_mmap_type_update_flag & WM_MANIPULATORMAPTYPE_GLOBAL_UPDATE_INIT) { + for (wmManipulatorMapType *mmap_type = manipulatormaptypes.first; + mmap_type; + mmap_type = mmap_type->next) + { + const uchar type_update_all = WM_MANIPULATORMAPTYPE_UPDATE_INIT | WM_MANIPULATORMAPTYPE_KEYMAP_INIT; + if (mmap_type->type_update_flag & type_update_all) { + mmap_type->type_update_flag &= ~type_update_all; + for (wmManipulatorGroupTypeRef *wgt_ref = mmap_type->grouptype_refs.first; + wgt_ref; + wgt_ref = wgt_ref->next) + { + if (wgt_ref->type->type_update_flag & WM_MANIPULATORMAPTYPE_KEYMAP_INIT) { + WM_manipulatormaptype_group_init_runtime_keymap(bmain, wgt_ref->type); + wgt_ref->type->type_update_flag &= ~WM_MANIPULATORMAPTYPE_KEYMAP_INIT; + } + + if (wgt_ref->type->type_update_flag & WM_MANIPULATORMAPTYPE_UPDATE_INIT) { + WM_manipulatormaptype_group_init_runtime(bmain, mmap_type, wgt_ref->type); + wgt_ref->type->type_update_flag &= ~WM_MANIPULATORMAPTYPE_UPDATE_INIT; + } + } + } + } + + wm_mmap_type_update_flag &= ~WM_MANIPULATORMAPTYPE_GLOBAL_UPDATE_INIT; + } +} + +/** \} */ |