/* * ***** 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/manipulators/intern/wm_manipulatormap.c * \ingroup wm */ #include #include "BKE_context.h" #include "BLI_listbase.h" #include "BLI_math.h" #include "BLI_string.h" #include "BLI_ghash.h" #include "DNA_manipulator_types.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" /* own includes */ #include "wm_manipulator_wmapi.h" #include "wm_manipulator_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}; /** * Manipulator-map update tagging. */ enum eManipulatorMapUpdateFlags { /* Tag manipulator-map for refresh. */ MANIPULATORMAP_REFRESH = (1 << 0), }; /* -------------------------------------------------------------------- */ /** \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 *mmaptype = WM_manipulatormaptype_ensure(mmap_params); wmManipulatorMap *mmap; mmap = MEM_callocN(sizeof(wmManipulatorMap), "ManipulatorMap"); mmap->type = mmaptype; mmap->update_flag = MANIPULATORMAP_REFRESH; /* create all manipulator-groups for this manipulator-map. We may create an empty one * too in anticipation of manipulators from operators etc */ for (wmManipulatorGroupType *mgrouptype = mmaptype->manipulator_grouptypes.first; mgrouptype; mgrouptype = mgrouptype->next) { wm_manipulatorgroup_new_from_type(mmap, mgrouptype); } return mmap; } void wm_manipulatormap_selected_delete(wmManipulatorMap *mmap) { MEM_SAFE_FREE(mmap->mmap_context.selected_manipulator); mmap->mmap_context.tot_selected = 0; } void wm_manipulatormap_delete(wmManipulatorMap *mmap) { if (!mmap) return; for (wmManipulatorGroup *mgroup = mmap->manipulator_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->manipulator_groups)); wm_manipulatormap_selected_delete(mmap); MEM_freeN(mmap); } /** * 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 */ 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_str_new(__func__); /* collect manipulators */ for (wmManipulatorGroup *mgroup = mmap->manipulator_groups.first; mgroup; mgroup = mgroup->next) { if (!mgroup->type->poll || mgroup->type->poll(C, mgroup->type)) { for (wmManipulator *manipulator = mgroup->manipulators.first; manipulator; manipulator = manipulator->next) { if ((include_hidden || (manipulator->flag & WM_MANIPULATOR_HIDDEN) == 0) && (!poll || poll(manipulator, data))) { BLI_ghash_insert(hash, manipulator->idname, manipulator); } } } } return hash; } void WM_manipulatormap_tag_refresh(wmManipulatorMap *mmap) { if (mmap) { mmap->update_flag |= MANIPULATORMAP_REFRESH; } } static void manipulatormap_tag_updated(wmManipulatorMap *mmap) { mmap->update_flag = 0; } static bool manipulator_prepare_drawing( wmManipulatorMap *mmap, wmManipulator *manipulator, const bContext *C, ListBase *draw_manipulators) { if (!wm_manipulator_is_visible(manipulator)) { /* skip */ } else { wm_manipulator_update(manipulator, C, (mmap->update_flag & MANIPULATORMAP_REFRESH) != 0); BLI_addhead(draw_manipulators, BLI_genericNodeN(manipulator)); 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 int drawstep) { if (!mmap || BLI_listbase_is_empty(&mmap->manipulator_groups)) return; wmManipulator *active_manipulator = mmap->mmap_context.active_manipulator; /* only active manipulator needs updating */ if (active_manipulator) { if (manipulator_prepare_drawing(mmap, active_manipulator, C, draw_manipulators)) { manipulatormap_tag_updated(mmap); } /* don't draw any other manipulators */ return; } for (wmManipulatorGroup *mgroup = mmap->manipulator_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_manipulatorgroup_is_visible(mgroup, C)) { continue; } /* needs to be initialized on first draw */ wm_manipulatorgroup_ensure_initialized(mgroup, C); /* update data if needed */ /* XXX weak: Manipulator-group may skip refreshing if it's invisible (map gets untagged nevertheless) */ if (mmap->update_flag & MANIPULATORMAP_REFRESH && mgroup->type->refresh) { mgroup->type->refresh(C, mgroup); } /* prepare drawing */ if (mgroup->type->draw_prepare) { mgroup->type->draw_prepare(C, mgroup); } for (wmManipulator *manipulator = mgroup->manipulators.first; manipulator; manipulator = manipulator->next) { manipulator_prepare_drawing(mmap, manipulator, C, draw_manipulators); } } manipulatormap_tag_updated(mmap); } /** * 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) { if (!mmap) return; BLI_assert(!BLI_listbase_is_empty(&mmap->manipulator_groups)); const bool draw_multisample = (U.ogl_multisamples != USER_MULTISAMPLE_NONE); /* 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_SHADED_MANIPULATORS) != 0; */ /* enable multisampling */ if (draw_multisample) { glEnable(GL_MULTISAMPLE); } /* draw_manipulators contains all visible manipulators - draw them */ for (LinkData *link = draw_manipulators->first, *link_next; link; link = link_next) { wmManipulator *manipulator = link->data; link_next = link->next; manipulator->type.draw(C, manipulator); /* free/remove manipulator link after drawing */ BLI_freelinkN(draw_manipulators, link); } if (draw_multisample) { glDisable(GL_MULTISAMPLE); } } void WM_manipulatormap_draw(wmManipulatorMap *mmap, const bContext *C, const int drawstep) { 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_find_active_3D_loop(const bContext *C, ListBase *visible_manipulators) { int selectionbase = 0; wmManipulator *manipulator; for (LinkData *link = visible_manipulators->first; link; link = link->next) { manipulator = link->data; /* pass the selection id shifted by 8 bits. Last 8 bits are used for selected manipulator part id */ manipulator->type.draw_select(C, manipulator, selectionbase << 8); selectionbase++; } } static int manipulator_find_intersected_3D_intern( ListBase *visible_manipulators, const bContext *C, const int co[2], const float hotspot) { ScrArea *sa = CTX_wm_area(C); ARegion *ar = CTX_wm_region(C); View3D *v3d = sa->spacedata.first; rcti rect; GLuint buffer[64]; // max 4 items per select, so large enuf short hits; const bool do_passes = GPU_select_query_check_active(); rect.xmin = co[0] - hotspot; rect.xmax = co[0] + hotspot; rect.ymin = co[1] - hotspot; rect.ymax = co[1] + hotspot; ED_view3d_draw_setup_view(CTX_wm_window(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_find_active_3D_loop(C, visible_manipulators); hits = GPU_select_end(); if (do_passes) { GPU_select_begin(buffer, ARRAY_SIZE(buffer), &rect, GPU_SELECT_NEAREST_SECOND_PASS, hits); manipulator_find_active_3D_loop(C, visible_manipulators); GPU_select_end(); } ED_view3d_draw_setup_view(CTX_wm_window(C), CTX_data_scene(C), ar, v3d, NULL, NULL, NULL); return hits > 0 ? buffer[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, unsigned char *part) { wmManipulator *result = NULL; const float hotspot = 14.0f; int ret; *part = 0; /* set up view matrices */ view3d_operator_needs_opengl(C); ret = manipulator_find_intersected_3D_intern(visible_manipulators, C, co, 0.5f * hotspot); if (ret != -1) { LinkData *link; int retsec; retsec = manipulator_find_intersected_3D_intern(visible_manipulators, C, co, 0.2f * hotspot); if (retsec != -1) ret = retsec; link = BLI_findlink(visible_manipulators, ret >> 8); *part = ret & 255; result = link->data; } 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_find_highlighted_manipulator( wmManipulatorMap *mmap, bContext *C, const wmEvent *event, unsigned char *part) { wmManipulator *manipulator = NULL; ListBase visible_3d_manipulators = {NULL}; for (wmManipulatorGroup *mgroup = mmap->manipulator_groups.first; mgroup; mgroup = mgroup->next) { if (wm_manipulatorgroup_is_visible(mgroup, C)) { if (mgroup->type->flag & WM_MANIPULATORGROUPTYPE_IS_3D) { wm_manipulatorgroup_intersectable_manipulators_to_list(mgroup, &visible_3d_manipulators); } else if ((manipulator = wm_manipulatorgroup_find_intersected_mainpulator(mgroup, C, event, part))) { break; } } } if (!BLI_listbase_is_empty(&visible_3d_manipulators)) { manipulator = manipulator_find_intersected_3D(C, event->mval, &visible_3d_manipulators, part); BLI_freelistN(&visible_3d_manipulators); } return manipulator; } void WM_manipulatormap_add_handlers(ARegion *ar, wmManipulatorMap *mmap) { wmEventHandler *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 wmOperatorType *ot) { const bool modal_running = (handler->op != NULL); /* happens on render or when joining areas */ if (!handler->op_region || !handler->op_region->manipulator_map) return; /* hide operator manipulators */ if (!modal_running && ot->mgrouptype) { ot->mgrouptype->op = NULL; } wmManipulatorMap *mmap = handler->op_region->manipulator_map; wmManipulator *manipulator = wm_manipulatormap_get_active_manipulator(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) { if (manipulator && manipulator->type.handler && manipulator->opname && STREQ(manipulator->opname, handler->op->idname)) { manipulator->type.handler(C, manipulator, event, 0); } } /* operator not running anymore */ else { wm_manipulatormap_set_highlighted_manipulator(mmap, C, NULL, 0); wm_manipulatormap_set_active_manipulator(mmap, C, event, NULL); } /* 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, wmManipulator ***sel) { if (*sel == NULL || mmap->mmap_context.tot_selected == 0) return false; for (int i = 0; i < mmap->mmap_context.tot_selected; i++) { (*sel)[i]->state &= ~WM_MANIPULATOR_SELECTED; (*sel)[i] = NULL; } wm_manipulatormap_selected_delete(mmap); /* always return true, we already checked * if there's anything to deselect */ return true; } BLI_INLINE bool manipulator_selectable_poll(const wmManipulator *manipulator, void *UNUSED(data)) { return (manipulator->parent_mgroup->type->flag & WM_MANIPULATORGROUPTYPE_SELECTABLE); } /** * Select all selectable manipulators in \a mmap. * \return if selection has changed. */ static bool wm_manipulatormap_select_all_intern( bContext *C, wmManipulatorMap *mmap, wmManipulator ***sel, const int action) { /* 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, *tot_sel = &mmap->mmap_context.tot_selected; bool changed = false; *tot_sel = BLI_ghash_size(hash); *sel = MEM_reallocN(*sel, sizeof(**sel) * (*tot_sel)); GHASH_ITER_INDEX (gh_iter, hash, i) { wmManipulator *manipulator_iter = BLI_ghashIterator_getValue(&gh_iter); if ((manipulator_iter->state & WM_MANIPULATOR_SELECTED) == 0) { changed = true; } manipulator_iter->state |= WM_MANIPULATOR_SELECTED; if (manipulator_iter->type.select) { manipulator_iter->type.select(C, manipulator_iter, action); } (*sel)[i] = manipulator_iter; BLI_assert(i < (*tot_sel)); } /* highlight first manipulator */ wm_manipulatormap_set_highlighted_manipulator(mmap, C, (*sel)[0], (*sel)[0]->highlighted_part); 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) { wmManipulator ***sel = &mmap->mmap_context.selected_manipulator; bool changed = false; switch (action) { case SEL_SELECT: changed = wm_manipulatormap_select_all_intern(C, mmap, sel, action); break; case SEL_DESELECT: changed = wm_manipulatormap_deselect_all(mmap, sel); 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) { for (; mmap; mmap = mmap->next) { wmManipulator *manipulator = mmap->mmap_context.highlighted_manipulator; if (manipulator && manipulator->type.cursor_get) { WM_cursor_set(win, manipulator->type.cursor_get(manipulator)); return true; } } return false; } void wm_manipulatormap_set_highlighted_manipulator( wmManipulatorMap *mmap, const bContext *C, wmManipulator *manipulator, unsigned char part) { if ((manipulator != mmap->mmap_context.highlighted_manipulator) || (manipulator && part != manipulator->highlighted_part)) { if (mmap->mmap_context.highlighted_manipulator) { mmap->mmap_context.highlighted_manipulator->state &= ~WM_MANIPULATOR_HIGHLIGHT; mmap->mmap_context.highlighted_manipulator->highlighted_part = 0; } mmap->mmap_context.highlighted_manipulator = manipulator; if (manipulator) { manipulator->state |= WM_MANIPULATOR_HIGHLIGHT; manipulator->highlighted_part = part; if (C && manipulator->type.cursor_get) { wmWindow *win = CTX_wm_window(C); WM_cursor_set(win, manipulator->type.cursor_get(manipulator)); } } else { if (C) { wmWindow *win = CTX_wm_window(C); WM_cursor_set(win, CURSOR_STD); } } /* tag the region for redraw */ if (C) { ARegion *ar = CTX_wm_region(C); ED_region_tag_redraw(ar); } } } wmManipulator *wm_manipulatormap_get_highlighted_manipulator(wmManipulatorMap *mmap) { return mmap->mmap_context.highlighted_manipulator; } void wm_manipulatormap_set_active_manipulator( wmManipulatorMap *mmap, bContext *C, const wmEvent *event, wmManipulator *manipulator) { if (manipulator && C) { manipulator->state |= WM_MANIPULATOR_ACTIVE; mmap->mmap_context.active_manipulator = manipulator; if (manipulator->opname) { wmOperatorType *ot = WM_operatortype_find(manipulator->opname, 0); if (ot) { /* first activate the manipulator itself */ if (manipulator->type.invoke && manipulator->type.handler) { manipulator->type.invoke(C, manipulator, event); } WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &manipulator->opptr); /* we failed to hook the manipulator to the operator handler or operator was cancelled, return */ if (!mmap->mmap_context.active_manipulator) { manipulator->state &= ~WM_MANIPULATOR_ACTIVE; /* first activate the manipulator itself */ if (manipulator->interaction_data) { MEM_freeN(manipulator->interaction_data); manipulator->interaction_data = NULL; } } return; } else { printf("Manipulator error: operator not found"); mmap->mmap_context.active_manipulator = NULL; return; } } else { if (manipulator->type.invoke && manipulator->type.handler) { manipulator->type.invoke(C, manipulator, event); } } WM_cursor_grab_enable(CTX_wm_window(C), true, true, NULL); } else { manipulator = mmap->mmap_context.active_manipulator; /* deactivate, manipulator but first take care of some stuff */ if (manipulator) { manipulator->state &= ~WM_MANIPULATOR_ACTIVE; /* first activate the manipulator itself */ if (manipulator->interaction_data) { MEM_freeN(manipulator->interaction_data); manipulator->interaction_data = NULL; } } mmap->mmap_context.active_manipulator = NULL; if (C) { WM_cursor_grab_disable(CTX_wm_window(C), NULL); ED_region_tag_redraw(CTX_wm_region(C)); WM_event_add_mousemove(C); } } } wmManipulator *wm_manipulatormap_get_active_manipulator(wmManipulatorMap *mmap) { return mmap->mmap_context.active_manipulator; } /** \} */ /* wmManipulatorMap */ /* -------------------------------------------------------------------- */ /** \name wmManipulatorMapType * * \{ */ wmManipulatorMapType *WM_manipulatormaptype_find( const struct wmManipulatorMapType_Params *mmap_params) { for (wmManipulatorMapType *mmaptype = manipulatormaptypes.first; mmaptype; mmaptype = mmaptype->next) { if (mmaptype->spaceid == mmap_params->spaceid && mmaptype->regionid == mmap_params->regionid && STREQ(mmaptype->idname, mmap_params->idname)) { return mmaptype; } } return NULL; } wmManipulatorMapType *WM_manipulatormaptype_ensure( const struct wmManipulatorMapType_Params *mmap_params) { wmManipulatorMapType *mmaptype = WM_manipulatormaptype_find(mmap_params); if (mmaptype) { return mmaptype; } mmaptype = MEM_callocN(sizeof(wmManipulatorMapType), "manipulatortype list"); mmaptype->spaceid = mmap_params->spaceid; mmaptype->regionid = mmap_params->regionid; BLI_strncpy(mmaptype->idname, mmap_params->idname, sizeof(mmaptype->idname)); BLI_addhead(&manipulatormaptypes, mmaptype); return mmaptype; } void wm_manipulatormaptypes_free(void) { for (wmManipulatorMapType *mmaptype = manipulatormaptypes.first; mmaptype; mmaptype = mmaptype->next) { BLI_freelistN(&mmaptype->manipulator_grouptypes); } BLI_freelistN(&manipulatormaptypes); } /** * Initialize keymaps for all existing manipulator-groups */ void wm_manipulators_keymap(wmKeyConfig *keyconf) { wmManipulatorMapType *mmaptype; wmManipulatorGroupType *mgrouptype; /* 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 (mmaptype = manipulatormaptypes.first; mmaptype; mmaptype = mmaptype->next) { for (mgrouptype = mmaptype->manipulator_grouptypes.first; mgrouptype; mgrouptype = mgrouptype->next) { wm_manipulatorgrouptype_keymap_init(mgrouptype, keyconf); } } } /** \} */ /* wmManipulatorMapType */