diff --git a/intern/ghost/intern/GHOST_SystemX11.cpp b/intern/ghost/intern/GHOST_SystemX11.cpp index e347661ebc6..6d948b2135f 100644 --- a/intern/ghost/intern/GHOST_SystemX11.cpp +++ b/intern/ghost/intern/GHOST_SystemX11.cpp @@ -525,6 +525,16 @@ processEvents( continue; } #endif + /* when using autorepeat, some keypress events can actually come *after* the + * last keyrelease. The next code takes care of that */ + if (xevent.type == KeyRelease) { + m_last_release_keycode = xevent.xkey.keycode; + m_last_release_time = xevent.xkey.time; + } + else if (xevent.type == KeyPress) { + if ((xevent.xkey.keycode == m_last_release_keycode) && ((xevent.xkey.time <= m_last_release_time))) + continue; + } processEvent(&xevent); anyProcessed = true; diff --git a/intern/ghost/intern/GHOST_SystemX11.h b/intern/ghost/intern/GHOST_SystemX11.h index 1f95e4b1ce2..a21300e36f0 100644 --- a/intern/ghost/intern/GHOST_SystemX11.h +++ b/intern/ghost/intern/GHOST_SystemX11.h @@ -356,6 +356,10 @@ private: * and stop accumulating all events generated before that */ Time m_last_warp; + /* detect autorepeat glitch */ + unsigned int m_last_release_keycode; + Time m_last_release_time; + /** * Return the ghost window associated with the * X11 window xwind diff --git a/release/scripts/modules/bpy_types.py b/release/scripts/modules/bpy_types.py index d2683471915..bc2e9368b71 100644 --- a/release/scripts/modules/bpy_types.py +++ b/release/scripts/modules/bpy_types.py @@ -140,6 +140,15 @@ class WindowManager(bpy_types.ID): finally: self.pupmenu_end__internal(popup) + def popup_menu_pie(self, event, draw_func, title="", icon='NONE'): + import bpy + pie = self.piemenu_begin__internal(title, icon, event) + + try: + draw_func(pie, bpy.context) + finally: + self.piemenu_end__internal(pie) + class _GenericBone: """ diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py index 4281c908afd..15e482a5e63 100644 --- a/release/scripts/startup/bl_operators/wm.py +++ b/release/scripts/startup/bl_operators/wm.py @@ -527,7 +527,33 @@ class WM_OT_context_menu_enum(Operator): context.window_manager.popup_menu(draw_func=draw_cb, title=prop.name, icon=prop.icon) - return {'PASS_THROUGH'} + return {'FINISHED'} + + +class WM_OT_context_pie_enum(Operator): + bl_idname = "wm.context_pie_enum" + bl_label = "Context Enum Pie" + bl_options = {'UNDO', 'INTERNAL'} + data_path = rna_path_prop + + def invoke(self, context, event): + data_path = self.data_path + value = context_path_validate(context, data_path) + + if value is Ellipsis: + return {'PASS_THROUGH'} + + base_path, prop_string = data_path.rsplit(".", 1) + value_base = context_path_validate(context, base_path) + prop = value_base.bl_rna.properties[prop_string] + + def draw_cb(self, context): + layout = self.layout + layout.prop(value_base, prop_string, expand=True) + + context.window_manager.popup_menu_pie(draw_func=draw_cb, title=prop.name, icon=prop.icon, event=event) + + return {'FINISHED'} class WM_OT_context_set_id(Operator): diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index b325e8c9759..999e41e17f7 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -216,6 +216,13 @@ class USERPREF_PT_interface(Panel): sub.prop(view, "open_toplevel_delay", text="Top Level") sub.prop(view, "open_sublevel_delay", text="Sub Level") + col.separator() + col.label(text="Pie Menus:") + sub = col.column(align=True) + sub.prop(view, "pie_animation_timeout") + sub.prop(view, "pie_initial_timeout") + sub.prop(view, "pie_menu_radius") + sub.prop(view, "pie_menu_threshold") col.separator() col.separator() col.separator() @@ -681,6 +688,9 @@ class USERPREF_PT_theme(Panel): col.label(text="Menu:") self._theme_widget_style(col, ui.wcol_menu) + col.label(text="Pie Menu:") + self._theme_widget_style(col, ui.wcol_pie_menu) + col.label(text="Pulldown:") self._theme_widget_style(col, ui.wcol_pulldown) diff --git a/release/scripts/templates_py/ui_pie_menu.py b/release/scripts/templates_py/ui_pie_menu.py new file mode 100644 index 00000000000..87500b682d6 --- /dev/null +++ b/release/scripts/templates_py/ui_pie_menu.py @@ -0,0 +1,32 @@ +import bpy +from bpy.types import Menu + +# spawn an edit mode selection pie (run while object is in edit mode to get a valid output) + + +class VIEW3D_PIE_template(Menu): + # label is displayed at the center of the pie menu. + bl_label = "Select Mode" + + def draw(self, context): + layout = self.layout + + pie = layout.menu_pie() + # operator_enum will just spread all available options + # for the type enum of the operator on the pie + pie.operator_enum("mesh.select_mode", "type") + + +def register(): + bpy.utils.register_class(VIEW3D_PIE_template) + + +def unregister(): + bpy.utils.unregister_class(VIEW3D_PIE_template) + + +if __name__ == "__main__": + register() + + bpy.ops.wm.call_menu_pie(name="VIEW3D_PIE_template") + diff --git a/source/blender/blenkernel/BKE_blender.h b/source/blender/blenkernel/BKE_blender.h index 50639ba7ed7..ea904983eca 100644 --- a/source/blender/blenkernel/BKE_blender.h +++ b/source/blender/blenkernel/BKE_blender.h @@ -42,7 +42,7 @@ extern "C" { * and keep comment above the defines. * Use STRINGIFY() rather than defining with quotes */ #define BLENDER_VERSION 271 -#define BLENDER_SUBVERSION 3 +#define BLENDER_SUBVERSION 4 /* 262 was the last editmesh release but it has compatibility code for bmesh data */ #define BLENDER_MINVERSION 270 #define BLENDER_MINSUBVERSION 5 diff --git a/source/blender/editors/include/ED_space_api.h b/source/blender/editors/include/ED_space_api.h index 4fbe01a5fc7..d268c578cf2 100644 --- a/source/blender/editors/include/ED_space_api.h +++ b/source/blender/editors/include/ED_space_api.h @@ -35,6 +35,7 @@ struct ARegionType; struct bContext; void ED_spacetypes_init(void); +void ED_spacemacros_init(void); /* the pluginnable API for export to editors */ diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index d7b4f753810..c02ab1e4cac 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -102,6 +102,7 @@ typedef struct uiLayout uiLayout; #define UI_EMBOSSN 1 /* Nothing, only icon and/or text */ #define UI_EMBOSSP 2 /* Pulldown menu style */ #define UI_EMBOSST 3 /* Table */ +#define UI_EMBOSSR 4 /* Pie Menu */ /* uiBlock->direction */ #define UI_DIRECTION (UI_TOP | UI_DOWN | UI_LEFT | UI_RIGHT) @@ -137,6 +138,7 @@ typedef struct uiLayout uiLayout; /* block->flag bits 14-17 are identical to but->drawflag bits */ #define UI_BLOCK_LIST_ITEM (1 << 19) +#define UI_BLOCK_RADIAL (1 << 20) /* uiPopupBlockHandle->menuretval */ #define UI_RETURN_CANCEL (1 << 0) /* cancel all menus cascading */ @@ -360,6 +362,17 @@ struct uiLayout *uiPupMenuLayout(uiPopupMenu *head); void uiPupMenuReports(struct bContext *C, struct ReportList *reports) ATTR_NONNULL(); bool uiPupMenuInvoke(struct bContext *C, const char *idname, struct ReportList *reports) ATTR_NONNULL(1, 2); +/* Pie menus */ +typedef struct uiPieMenu uiPieMenu; + +void uiPieMenuInvoke(struct bContext *C, const char *idname, const struct wmEvent *event); +void uiPieOperatorEnumInvoke(struct bContext *C, const char *title, const char *opname, + const char *propname, const struct wmEvent *event); +void uiPieEnumInvoke(struct bContext *C, const char *title, const char *path, const struct wmEvent *event); + +struct uiPieMenu *uiPieMenuBegin(struct bContext *C, const char *title, int icon, const struct wmEvent *event) ATTR_NONNULL(); +void uiPieMenuEnd(struct bContext *C, uiPieMenu *pie); +struct uiLayout *uiPieMenuLayout(struct uiPieMenu *pie); /* Popup Blocks * * Functions used to create popup blocks. These are like popup menus @@ -417,7 +430,8 @@ typedef enum { UI_BLOCK_BOUNDS_TEXT, UI_BLOCK_BOUNDS_POPUP_MOUSE, UI_BLOCK_BOUNDS_POPUP_MENU, - UI_BLOCK_BOUNDS_POPUP_CENTER + UI_BLOCK_BOUNDS_POPUP_CENTER, + UI_BLOCK_BOUNDS_PIE_CENTER, } eBlockBoundsCalc; void uiBoundsBlock(struct uiBlock *block, int addval); @@ -705,7 +719,7 @@ void UI_panel_category_draw_all(struct ARegion *ar, const * as screen/ if ED_KEYMAP_UI is set, or internally in popup functions. */ void UI_add_region_handlers(struct ListBase *handlers); -void UI_add_popup_handlers(struct bContext *C, struct ListBase *handlers, uiPopupBlockHandle *popup); +void UI_add_popup_handlers(struct bContext *C, struct ListBase *handlers, uiPopupBlockHandle *popup, const bool accept_dbl_click); void UI_remove_popup_handlers(struct ListBase *handlers, uiPopupBlockHandle *popup); void UI_remove_popup_handlers_all(struct bContext *C, struct ListBase *handlers); @@ -737,6 +751,7 @@ void UI_exit(void); #define UI_LAYOUT_HEADER 1 #define UI_LAYOUT_MENU 2 #define UI_LAYOUT_TOOLBAR 3 +#define UI_LAYOUT_PIEMENU 4 #define UI_UNIT_X ((void)0, U.widget_unit) #define UI_UNIT_Y ((void)0, U.widget_unit) @@ -827,8 +842,8 @@ uiLayout *uiLayoutListBox(uiLayout *layout, struct uiList *ui_list, struct Point uiLayout *uiLayoutAbsolute(uiLayout *layout, int align); uiLayout *uiLayoutSplit(uiLayout *layout, float percentage, int align); uiLayout *uiLayoutOverlap(uiLayout *layout); - uiBlock *uiLayoutAbsoluteBlock(uiLayout *layout); +uiLayout *uiLayoutRadial(uiLayout *layout); /* templates */ void uiTemplateHeader(uiLayout *layout, struct bContext *C); diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c index 0a45ffc4c13..73eb5f63aea 100644 --- a/source/blender/editors/interface/interface.c +++ b/source/blender/editors/interface/interface.c @@ -321,6 +321,20 @@ static void ui_centered_bounds_block(wmWindow *window, uiBlock *block) ui_bounds_block(block); } + +static void ui_centered_pie_bounds_block(uiBlock *block) +{ + const int xy[2] = { + block->pie_data.pie_center_spawned[0], + block->pie_data.pie_center_spawned[1] + }; + + ui_block_translate(block, xy[0], xy[1]); + + /* now recompute bounds and safety */ + ui_bounds_block(block); +} + static void ui_popup_bounds_block(wmWindow *window, uiBlock *block, eBlockBoundsCalc bounds_calc, const int xy[2]) { @@ -1062,6 +1076,42 @@ static bool ui_but_event_property_operator_string(const bContext *C, uiBut *but, return found; } +/* this goes in a seemingly weird pattern: + * + * 4 + * 5 6 + * 1 2 + * 7 8 + * 3 + * + * but it's actually quite logical. It's designed to be 'upwards compatible' + * for muscle memory so that the menu item locations are fixed and don't move + * as new items are added to the menu later on. It also optimises efficiency - + * a radial menu is best kept symmetrical, with as large an angle between + * items as possible, so that the gestural mouse movements can be fast and inexact. + + * It starts off with two opposite sides for the first two items + * then joined by the one below for the third (this way, even with three items, + * the menu seems to still be 'in order' reading left to right). Then the fourth is + * added to complete the compass directions. From here, it's just a matter of + * subdividing the rest of the angles for the last 4 items. + * + * --Matt 07/2006 + */ +const char ui_radial_dir_order[8] = { + UI_RADIAL_W, UI_RADIAL_E, UI_RADIAL_S, UI_RADIAL_N, + UI_RADIAL_NW, UI_RADIAL_NE, UI_RADIAL_SW, UI_RADIAL_SE}; + +const char ui_radial_dir_to_numpad[8] = {8, 9, 6, 3, 2, 1, 4, 7}; +const short ui_radial_dir_to_angle_visual[8] = {90, 40, 0, 320, 270, 220, 180, 140}; +const short ui_radial_dir_to_angle[8] = {90, 45, 0, 315, 270, 225, 180, 135}; + +static void ui_but_pie_direction_string(uiBut *but, char *buf, int size) +{ + BLI_assert(but->pie_dir < ARRAY_SIZE(ui_radial_dir_to_numpad)); + BLI_snprintf(buf, size, "%d", ui_radial_dir_to_numpad[but->pie_dir]); +} + static void ui_menu_block_set_keymaps(const bContext *C, uiBlock *block) { uiBut *but; @@ -1071,13 +1121,23 @@ static void ui_menu_block_set_keymaps(const bContext *C, uiBlock *block) if (block->rect.xmin != block->rect.xmax) return; - for (but = block->buttons.first; but; but = but->next) { - - if (ui_but_event_operator_string(C, but, buf, sizeof(buf))) { - ui_but_add_shortcut(but, buf, false); + if (block->flag & UI_BLOCK_RADIAL) { + for (but = block->buttons.first; but; but = but->next) { + if (but->pie_dir != UI_RADIAL_NONE) { + ui_but_pie_direction_string(but, buf, sizeof(buf)); + ui_but_add_shortcut(but, buf, false); + } } - else if (ui_but_event_property_operator_string(C, but, buf, sizeof(buf))) { - ui_but_add_shortcut(but, buf, false); + } + else { + for (but = block->buttons.first; but; but = but->next) { + + if (ui_but_event_operator_string(C, but, buf, sizeof(buf))) { + ui_but_add_shortcut(but, buf, false); + } + else if (ui_but_event_property_operator_string(C, but, buf, sizeof(buf))) { + ui_but_add_shortcut(but, buf, false); + } } } } @@ -1173,6 +1233,9 @@ void uiEndBlock_ex(const bContext *C, uiBlock *block, const int xy[2]) case UI_BLOCK_BOUNDS_POPUP_CENTER: ui_centered_bounds_block(window, block); break; + case UI_BLOCK_BOUNDS_PIE_CENTER: + ui_centered_pie_bounds_block(block); + break; /* fallback */ case UI_BLOCK_BOUNDS_POPUP_MOUSE: @@ -1244,6 +1307,10 @@ void uiDrawBlock(const bContext *C, uiBlock *block) rcti rect; int multisample_enabled; + /* early exit if cancelled */ + if ((block->flag & UI_BLOCK_RADIAL) && (block->pie_data.flags & UI_PIE_FINISHED)) + return; + /* get menu region or area region */ ar = CTX_wm_menu(C); if (!ar) @@ -1279,7 +1346,9 @@ void uiDrawBlock(const bContext *C, uiBlock *block) wmOrtho2(-0.01f, ar->winx - 0.01f, -0.01f, ar->winy - 0.01f); /* back */ - if (block->flag & UI_BLOCK_LOOP) + if (block->flag & UI_BLOCK_RADIAL) + ui_draw_pie_center(block); + else if (block->flag & UI_BLOCK_LOOP) ui_draw_menu_back(&style, block, &rect); else if (block->panel) ui_draw_aligned_panel(&style, block, &rect, UI_panel_category_is_visible(ar)); @@ -3002,6 +3071,7 @@ static uiBut *ui_def_but(uiBlock *block, int type, int retval, const char *str, but->lock = block->lock; but->lockstr = block->lockstr; but->dt = block->dt; + but->pie_dir = UI_RADIAL_NONE; but->block = block; /* pointer back, used for frontbuffer status, and picker */ @@ -3028,8 +3098,11 @@ static uiBut *ui_def_but(uiBlock *block, int type, int retval, const char *str, } } - if ((block->flag & UI_BLOCK_LOOP) || - ELEM(but->type, MENU, TEX, LABEL, BLOCK, BUTM, SEARCH_MENU, PROGRESSBAR, SEARCH_MENU_UNLINK)) + if (block->flag & UI_BLOCK_RADIAL) { + but->drawflag |= (UI_BUT_TEXT_LEFT | UI_BUT_ICON_LEFT); + } + else if ((block->flag & UI_BLOCK_LOOP) || + ELEM(but->type, MENU, TEX, LABEL, BLOCK, BUTM, SEARCH_MENU, PROGRESSBAR, SEARCH_MENU_UNLINK)) { but->drawflag |= (UI_BUT_TEXT_LEFT | UI_BUT_ICON_LEFT); } diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 68148d30136..33938615d72 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -116,6 +116,7 @@ static bool ui_mouse_motion_keynav_test(struct uiKeyNavLock *keynav, const wmEve #define BUTTON_TOOLTIP_DELAY 0.500 #define BUTTON_FLASH_DELAY 0.020 #define MENU_SCROLL_INTERVAL 0.1 +#define PIE_MENU_INTERVAL 0.01 #define BUTTON_AUTO_OPEN_THRESH 0.3 #define BUTTON_MOUSE_TOWARDS_THRESH 1.0 /* pixels to move the cursor to get out of keyboard navigation */ @@ -1238,7 +1239,7 @@ static bool ui_but_start_drag(bContext *C, uiBut *but, uiHandleButtonData *data, WM_event_add_ui_handler(C, &data->window->modalhandlers, ui_handler_region_drag_toggle, ui_handler_region_drag_toggle_remove, - drag_info); + drag_info, false); CTX_wm_region_set(C, ar_prev); } @@ -6377,6 +6378,43 @@ static bool ui_but_contains_pt(uiBut *but, float mx, float my) return BLI_rctf_isect_pt(&but->rect, mx, my); } +static void ui_but_pie_dir__internal(RadialDirection dir, float vec[2], const short angles[8]) +{ + float angle; + + BLI_assert(dir != UI_RADIAL_NONE); + + angle = DEG2RADF((float)angles[dir]); + vec[0] = cosf(angle); + vec[1] = sinf(angle); +} + +void ui_but_pie_dir_visual(RadialDirection dir, float vec[2]) +{ + ui_but_pie_dir__internal(dir, vec, ui_radial_dir_to_angle_visual); +} + +void ui_but_pie_dir(RadialDirection dir, float vec[2]) +{ + ui_but_pie_dir__internal(dir, vec, ui_radial_dir_to_angle); +} + +static bool ui_but_isect_pie_seg(uiBlock *block, uiBut *but) +{ + const float angle_range = (block->pie_data.flags & UI_PIE_DEGREES_RANGE_LARGE) ? M_PI_4 : M_PI_4 / 2.0; + float vec[2]; + + if (block->pie_data.flags & UI_PIE_INVALID_DIR) + return false; + + ui_but_pie_dir(but->pie_dir, vec); + + if (saacos(dot_v2v2(vec, block->pie_data.pie_dir)) < angle_range) + return true; + + return false; +} + uiBut *ui_but_find_activated(ARegion *ar) { uiBlock *block; @@ -6495,6 +6533,7 @@ static bool ui_mouse_inside_region(ARegion *ar, int x, int y) static bool ui_mouse_inside_button(ARegion *ar, uiBut *but, int x, int y) { + uiBlock *block = but->block; float mx, my; if (!ui_mouse_inside_region(ar, x, y)) return false; @@ -6502,10 +6541,16 @@ static bool ui_mouse_inside_button(ARegion *ar, uiBut *but, int x, int y) mx = x; my = y; - ui_window_to_block_fl(ar, but->block, &mx, &my); + ui_window_to_block_fl(ar, block, &mx, &my); - if (!ui_but_contains_pt(but, mx, my)) + if (but->dt == UI_EMBOSSR) { + if (!ui_but_isect_pie_seg(block, but)) { + return false; + } + } + else if (!ui_but_contains_pt(but, mx, my)) { return false; + } return true; } @@ -6559,7 +6604,13 @@ static uiBut *ui_but_find_mouse_over_ex(ARegion *ar, const int x, const int y, c for (but = block->buttons.last; but; but = but->prev) { if (ui_is_but_interactive(but, labeledit)) { - if (ui_but_contains_pt(but, mx, my)) { + if (but->pie_dir != UI_RADIAL_NONE) { + if (ui_but_isect_pie_seg(block, but)) { + butover = but; + break; + } + } + else if (ui_but_contains_pt(but, mx, my)) { butover = but; break; } @@ -6764,7 +6815,7 @@ static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState s if (!(but->block->handle && but->block->handle->popup)) { if (button_modal_state(state)) { if (!button_modal_state(data->state)) - WM_event_add_ui_handler(C, &data->window->modalhandlers, ui_handler_region_menu, NULL, data); + WM_event_add_ui_handler(C, &data->window->modalhandlers, ui_handler_region_menu, NULL, data, false); } else { if (button_modal_state(data->state)) { @@ -7919,6 +7970,30 @@ static int ui_handle_menu_button(bContext *C, const wmEvent *event, uiPopupBlock return retval; } +void ui_block_calculate_pie_segment(uiBlock *block, const float event_xy[2]) +{ + float seg1[2]; + float seg2[2]; + float len; + + if (block->pie_data.flags & UI_PIE_INITIAL_DIRECTION) { + copy_v2_v2(seg1, block->pie_data.pie_center_init); + } + else { + copy_v2_v2(seg1, block->pie_data.pie_center_spawned); + } + + sub_v2_v2v2(seg2, event_xy, seg1); + + len = normalize_v2_v2(block->pie_data.pie_dir, seg2); + + /* ten pixels for now, a bit arbitrary */ + if (len < U.pie_menu_threshold * U.pixelsize) + block->pie_data.flags |= UI_PIE_INVALID_DIR; + else + block->pie_data.flags &= ~UI_PIE_INVALID_DIR; +} + static int ui_handle_menu_event( bContext *C, const wmEvent *event, uiPopupBlockHandle *menu, int level, const bool is_parent_inside, const bool is_parent_menu, const bool is_floating) @@ -8420,10 +8495,311 @@ static int ui_handle_menu_return_submenu(bContext *C, const wmEvent *event, uiPo ui_mouse_motion_towards_reinit(menu, &event->x); } - if (menu->menuretval) + if (menu->menuretval) { + /* pie menus should not close but wait for release instead */ + if ((block->flag & UI_BLOCK_RADIAL) && + !(block->pie_data.flags & UI_PIE_CLICK_STYLE)) + { + menu->menuretval = 0; + block->pie_data.flags |= UI_PIE_FINISHED; + } + return WM_UI_HANDLER_CONTINUE; - else + } + else { return WM_UI_HANDLER_BREAK; + } +} + +static bool ui_but_pie_menu_supported_apply(uiBut *but) +{ + return (but->type != NUMSLI); +} + +static int ui_but_pie_menu_apply(bContext *C, uiPopupBlockHandle *menu, uiBut *but, bool force_close, bool click_style) +{ + int retval = WM_UI_HANDLER_BREAK; + + if (but && ui_but_pie_menu_supported_apply(but)) { + if (but->type == MENU) { + /* forcing the pie menu to close will not handle menus */ + if (!force_close) { + uiBut *active_but = ui_but_find_activated(menu->region); + + if (active_but) { + button_activate_exit(C, active_but, active_but->active, false, false); + } + + button_activate_init(C, menu->region, but, BUTTON_ACTIVATE_OPEN); + return retval; + } + else { + menu->menuretval = UI_RETURN_CANCEL; + } + } + else { + ui_apply_button(C, but->block, but, but->active, false); + button_activate_exit((bContext *)C, but, but->active, false, true); + + if (!(click_style || force_close)) { + but->block->pie_data.flags |= UI_PIE_FINISHED; + menu->menuretval = 0; + } + else { + menu->menuretval = UI_RETURN_OK; + } + } + } + else { + uiBlock *block = menu->region->uiblocks.first; + + if (!(click_style || force_close)) { + block->pie_data.flags |= UI_PIE_FINISHED; + } + else { + menu->menuretval = UI_RETURN_CANCEL; + } + + ED_region_tag_redraw(menu->region); + } + + return retval; +} + +static uiBut *ui_block_pie_dir_activate(uiBlock *block, const wmEvent *event, RadialDirection dir) +{ + uiBut *but; + + if ((block->flag & UI_BLOCK_NUMSELECT) && event->val == KM_PRESS) { + for (but = block->buttons.first; but; but = but->next) { + if (but->pie_dir == dir && !ELEM(but->type, SEPR, SEPRLINE)) { + return but; + } + } + } + + return NULL; +} + +static int ui_but_pie_button_activate(bContext *C, uiBut *but, uiPopupBlockHandle *menu, bool is_click_style) +{ + uiBut *active_but; + + if (but == NULL) + return WM_UI_HANDLER_BREAK; + + active_but = ui_but_find_activated(menu->region); + + if (active_but) + button_activate_exit(C, active_but, active_but->active, false, false); + + button_activate_init(C, menu->region, but, BUTTON_ACTIVATE_OVER); + return ui_but_pie_menu_apply(C, menu, but, false, is_click_style); +} + +static int ui_handler_pie(bContext *C, const wmEvent *event, uiPopupBlockHandle *menu) +{ + ARegion *ar; + uiBlock *block; + uiBut *but; + float event_xy[2]; + double duration; + bool is_click_style; + + /* we block all events, this is modal interaction, except for drop events which is described below */ + int retval = WM_UI_HANDLER_BREAK; + + if (event->type == EVT_DROP) { + /* may want to leave this here for later if we support pie ovens */ + + retval = WM_UI_HANDLER_CONTINUE; + } + + ar = menu->region; + block = ar->uiblocks.first; + + is_click_style = (block->pie_data.flags & UI_PIE_CLICK_STYLE); + + if (menu->scrolltimer == NULL) { + menu->scrolltimer = + WM_event_add_timer(CTX_wm_manager(C), CTX_wm_window(C), TIMER, PIE_MENU_INTERVAL); + menu->scrolltimer->duration = 0.0; + } + + duration = menu->scrolltimer->duration; + + if (event->type == TIMER) { + if (event->customdata == menu->scrolltimer) { + /* deactivate initial direction after a while */ + if (duration > 0.01 * U.pie_initial_timeout) { + block->pie_data.flags &= ~UI_PIE_INITIAL_DIRECTION; + } + + /* handle animation */ + if (!(block->pie_data.flags & UI_PIE_ANIMATION_FINISHED)) { + uiBut *but; + double final_time = 0.01 * U.pie_animation_timeout; + float fac = duration / final_time; + float pie_radius = U.pie_menu_radius * UI_DPI_FAC; + + if (fac > 1.0f) { + fac = 1.0f; + block->pie_data.flags |= UI_PIE_ANIMATION_FINISHED; + } + + pie_radius *= fac; + + for (but = block->buttons.first; but; but = but->next) { + if (but->pie_dir != UI_RADIAL_NONE) { + float dir[2]; + + ui_but_pie_dir_visual(but->pie_dir, dir); + + mul_v2_fl(dir, pie_radius ); + add_v2_v2(dir, block->pie_data.pie_center_spawned); + BLI_rctf_recenter(&but->rect, dir[0], dir[1]); + } + } + block->pie_data.alphafac = fac; + + ED_region_tag_redraw(ar); + } + } + } + + event_xy[0] = event->x; + event_xy[1] = event->y; + + ui_window_to_block_fl(ar, block, &event_xy[0], &event_xy[1]); + + ui_block_calculate_pie_segment(block, event_xy); + + if (block->pie_data.flags & UI_PIE_FINISHED) { + if ((event->type == block->pie_data.event && event->val == KM_RELEASE) || + ((event->type == RIGHTMOUSE || event->type == ESCKEY) && (event->val == KM_PRESS))) + { + menu->menuretval = UI_RETURN_OK; + } + + ED_region_tag_redraw(ar); + return WM_UI_HANDLER_BREAK; + } + + if (event->type == block->pie_data.event) { + if (event->val != KM_RELEASE) { + ui_handle_menu_button(C, event, menu); + + /* why redraw here? It's simple, we are getting many double click events here. + * Those operate like mouse move events almost */ + ED_region_tag_redraw(ar); + } + else { + /* distance from initial point */ + if (len_squared_v2v2(event_xy, block->pie_data.pie_center_init) < PIE_CLICK_THRESHOLD_SQ) { + block->pie_data.flags |= UI_PIE_CLICK_STYLE; + } + else if (!is_click_style) { + uiBut *but = ui_but_find_activated(menu->region); + + retval = ui_but_pie_menu_apply(C, menu, but, true, is_click_style); + } + } + } + else { + /* direction from numpad */ + RadialDirection num_dir = UI_RADIAL_NONE; + + switch (event->type) { + case MOUSEMOVE: + /* mouse move should always refresh the area for pie menus */ + ui_handle_menu_button(C, event, menu); + ED_region_tag_redraw(ar); + break; + + case LEFTMOUSE: + if (event->val == KM_PRESS) { + uiBut *but = ui_but_find_activated(menu->region); + retval = ui_but_pie_menu_apply(C, menu, but, false, is_click_style); + } + break; + + case ESCKEY: + case RIGHTMOUSE: + if (!is_click_style) { + block->pie_data.flags |= UI_PIE_FINISHED; + menu->menuretval = 0; + ED_region_tag_redraw(ar); + } + else + menu->menuretval = UI_RETURN_CANCEL; + break; + + case AKEY: + case BKEY: + case CKEY: + case DKEY: + case EKEY: + case FKEY: + case GKEY: + case HKEY: + case IKEY: + case JKEY: + case KKEY: + case LKEY: + case MKEY: + case NKEY: + case OKEY: + case PKEY: + case QKEY: + case RKEY: + case SKEY: + case TKEY: + case UKEY: + case VKEY: + case WKEY: + case XKEY: + case YKEY: + case ZKEY: + { + if ((event->val == KM_PRESS || event->val == KM_DBL_CLICK) && + (event->shift == 0) && + (event->ctrl == 0) && + (event->oskey == 0)) + { + for (but = block->buttons.first; but; but = but->next) { + if (but->menu_key == event->type) { + ui_but_pie_button_activate(C, but, menu, is_click_style); + } + } + } + break; + } + +#define CASE_NUM_TO_DIR(n, d) \ + case (ZEROKEY + n): case (PAD0 + n): \ + { if (num_dir == UI_RADIAL_NONE) num_dir = d; } (void)0 + + CASE_NUM_TO_DIR(1, UI_RADIAL_SW); + CASE_NUM_TO_DIR(2, UI_RADIAL_S); + CASE_NUM_TO_DIR(3, UI_RADIAL_SE); + CASE_NUM_TO_DIR(4, UI_RADIAL_W); + CASE_NUM_TO_DIR(6, UI_RADIAL_E); + CASE_NUM_TO_DIR(7, UI_RADIAL_NW); + CASE_NUM_TO_DIR(8, UI_RADIAL_N); + CASE_NUM_TO_DIR(9, UI_RADIAL_NE); + { + but = ui_block_pie_dir_activate(block, event, num_dir); + retval = ui_but_pie_button_activate(C, but, menu, is_click_style); + break; + } +#undef CASE_NUM_TO_DIR + default: + retval = ui_handle_menu_button(C, event, menu); + break; + } + } + + return retval; } static int ui_handle_menus_recursive( @@ -8445,17 +8821,21 @@ static int ui_handle_menus_recursive( uiBlock *block = menu->region->uiblocks.first; const bool is_menu = ui_block_is_menu(block); bool inside = false; + /* root pie menus accept the key that spawned them as double click to improve responsiveness */ + bool do_recursion = (!(block->flag & UI_BLOCK_RADIAL) || event->type != block->pie_data.event); - if (is_parent_inside == false) { - int mx, my; + if (do_recursion) { + if (is_parent_inside == false) { + int mx, my; - mx = event->x; - my = event->y; - ui_window_to_block(menu->region, block, &mx, &my); - inside = BLI_rctf_isect_pt(&block->rect, mx, my); + mx = event->x; + my = event->y; + ui_window_to_block(menu->region, block, &mx, &my); + inside = BLI_rctf_isect_pt(&block->rect, mx, my); + } + + retval = ui_handle_menus_recursive(C, event, submenu, level + 1, is_parent_inside || inside, is_menu, false); } - - retval = ui_handle_menus_recursive(C, event, submenu, level + 1, is_parent_inside || inside, is_menu, false); } /* now handle events for our own menu */ @@ -8488,7 +8868,12 @@ static int ui_handle_menus_recursive( } } else { - retval = ui_handle_menu_event(C, event, menu, level, is_parent_inside, is_parent_menu, is_floating); + uiBlock *block = menu->region->uiblocks.first; + + if (block->flag & UI_BLOCK_RADIAL) + retval = ui_handler_pie(C, event, menu); + else if (event->type == LEFTMOUSE || event->val != KM_DBL_CLICK) + retval = ui_handle_menu_event(C, event, menu, level, is_parent_inside, is_parent_menu, is_floating); } } @@ -8703,12 +9088,12 @@ static void ui_handler_remove_popup(bContext *C, void *userdata) void UI_add_region_handlers(ListBase *handlers) { WM_event_remove_ui_handler(handlers, ui_handler_region, ui_handler_remove_region, NULL, false); - WM_event_add_ui_handler(NULL, handlers, ui_handler_region, ui_handler_remove_region, NULL); + WM_event_add_ui_handler(NULL, handlers, ui_handler_region, ui_handler_remove_region, NULL, false); } -void UI_add_popup_handlers(bContext *C, ListBase *handlers, uiPopupBlockHandle *popup) +void UI_add_popup_handlers(bContext *C, ListBase *handlers, uiPopupBlockHandle *popup, const bool accept_dbl_click) { - WM_event_add_ui_handler(C, handlers, ui_handler_popup, ui_handler_remove_popup, popup); + WM_event_add_ui_handler(C, handlers, ui_handler_popup, ui_handler_remove_popup, popup, accept_dbl_click); } void UI_remove_popup_handlers(ListBase *handlers, uiPopupBlockHandle *popup) diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index 7d03aaea6b3..d3ff1c7063f 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -88,6 +88,7 @@ typedef enum { UI_WTYPE_PULLDOWN, UI_WTYPE_MENU_ITEM, + UI_WTYPE_MENU_ITEM_RADIAL, UI_WTYPE_MENU_BACK, /* specials */ @@ -121,6 +122,24 @@ enum { /* warn: rest of uiBut->flag in UI_interface.h */ }; +/* but->pie_dir */ +typedef enum RadialDirection { + UI_RADIAL_NONE = -1, + UI_RADIAL_N = 0, + UI_RADIAL_NE = 1, + UI_RADIAL_E = 2, + UI_RADIAL_SE = 3, + UI_RADIAL_S = 4, + UI_RADIAL_SW = 5, + UI_RADIAL_W = 6, + UI_RADIAL_NW = 7, +} RadialDirection; + +extern const char ui_radial_dir_order[8]; +extern const char ui_radial_dir_to_numpad[8]; +extern const short ui_radial_dir_to_angle_visual[8]; +extern const short ui_radial_dir_to_angle[8]; + /* internal panel drawing defines */ #define PNL_GRID (UI_UNIT_Y / 5) /* 4 default */ #define PNL_HEADER (UI_UNIT_Y + 4) /* 24 default */ @@ -144,6 +163,19 @@ enum { /* split numbuts by ':' and align l/r */ #define USE_NUMBUTS_LR_ALIGN +/* PieMenuData->flags */ +enum { + UI_PIE_DEGREES_RANGE_LARGE = (1 << 0), /* pie menu item collision is detected at 90 degrees */ + UI_PIE_INITIAL_DIRECTION = (1 << 1), /* use initial center of pie menu to calculate direction */ + UI_PIE_3_ITEMS = (1 << 2), /* pie menu has only 3 items, careful when centering */ + UI_PIE_INVALID_DIR = (1 << 3), /* mouse not far enough from center position */ + UI_PIE_FINISHED = (1 << 4), /* pie menu finished but we still wait for a release event */ + UI_PIE_CLICK_STYLE = (1 << 5), /* pie menu changed to click style, click to confirm */ + UI_PIE_ANIMATION_FINISHED = (1 << 6), /* pie animation finished, do not calculate any more motio */ +}; + +#define PIE_CLICK_THRESHOLD_SQ 50.0f + typedef struct uiLinkLine { /* only for draw/edit */ struct uiLinkLine *next, *prev; struct uiBut *from, *to; @@ -227,6 +259,7 @@ struct uiBut { BIFIconID icon; bool lock; char dt; /* drawtype: UI_EMBOSS, UI_EMBOSSN ... etc, copied from the block */ + signed char pie_dir; /* direction in a pie menu, used for collision detection (RadialDirection) */ char changed; /* could be made into a single flag */ unsigned char unit_type; /* so buttons can support unit systems which are not RNA */ short modifier_key; @@ -274,6 +307,15 @@ struct uiBut { uiBlock *block; }; +struct PieMenuData { + float pie_dir[2]; + float pie_center_init[2]; + float pie_center_spawned[2]; + int flags; + int event; /* initial event used to fire the pie menu, store here so we can query for release */ + float alphafac; +}; + struct uiBlock { uiBlock *next, *prev; @@ -356,6 +398,7 @@ struct uiBlock { char display_device[64]; /* display device name used to display this block, * used by color widgets to transform colors from/to scene linear */ + struct PieMenuData pie_data; }; typedef struct uiSafetyRct { @@ -561,6 +604,9 @@ extern int ui_button_open_menu_direction(uiBut *but); extern void ui_button_text_password_hide(char password_str[UI_MAX_DRAW_STR], uiBut *but, const bool restore); extern uiBut *ui_but_find_activated(struct ARegion *ar); bool ui_but_is_editable(const uiBut *but); +void ui_but_pie_dir_visual(RadialDirection dir, float vec[2]); +void ui_but_pie_dir(RadialDirection dir, float vec[2]); +void ui_block_calculate_pie_segment(struct uiBlock *block, const float event_xy[2]); void ui_button_clipboard_free(void); void ui_panel_menu(struct bContext *C, ARegion *ar, Panel *pa); @@ -571,6 +617,7 @@ uiBut *ui_but_find_new(uiBlock *block_old, const uiBut *but_new); void ui_draw_anti_tria(float x1, float y1, float x2, float y2, float x3, float y3); void ui_draw_anti_roundbox(int mode, float minx, float miny, float maxx, float maxy, float rad, bool use_alpha); void ui_draw_menu_back(struct uiStyle *style, uiBlock *block, rcti *rect); +void ui_draw_pie_center(uiBlock *block); uiWidgetColors *ui_tooltip_get_theme(void); void ui_draw_tooltip_background(uiStyle *UNUSED(style), uiBlock *block, rcti *rect); void ui_draw_search_back(struct uiStyle *style, uiBlock *block, rcti *rect); diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c index 645eb607031..a2d8ce06e5f 100644 --- a/source/blender/editors/interface/interface_layout.c +++ b/source/blender/editors/interface/interface_layout.c @@ -106,6 +106,7 @@ typedef enum uiItemType { ITEM_LAYOUT_ABSOLUTE, ITEM_LAYOUT_SPLIT, ITEM_LAYOUT_OVERLAP, + ITEM_LAYOUT_RADIAL, ITEM_LAYOUT_ROOT #if 0 @@ -218,7 +219,9 @@ static int ui_item_fit(int item, int pos, int all, int available, int last, int static int ui_layout_vary_direction(uiLayout *layout) { - return (layout->root->type == UI_LAYOUT_HEADER || layout->alignment != UI_LAYOUT_ALIGN_EXPAND) ? UI_ITEM_VARY_X : UI_ITEM_VARY_Y; + return ((ELEM(layout->root->type, UI_LAYOUT_HEADER, UI_LAYOUT_PIEMENU) || + (layout->alignment != UI_LAYOUT_ALIGN_EXPAND)) ? + UI_ITEM_VARY_X : UI_ITEM_VARY_Y); } /* estimated size of text + icon */ @@ -553,15 +556,24 @@ static void ui_item_enum_expand(uiLayout *layout, uiBlock *block, PointerRNA *pt */ uiBut *but; + uiLayout *layout_radial = NULL; EnumPropertyItem *item, *item_array; const char *name; int itemw, icon, value; bool free; + bool radial = (layout->root->type == UI_LAYOUT_PIEMENU); - RNA_property_enum_items_gettexted(block->evil_C, ptr, prop, &item_array, NULL, &free); + if (radial) + RNA_property_enum_items_gettexted_all(block->evil_C, ptr, prop, &item_array, NULL, &free); + else + RNA_property_enum_items_gettexted(block->evil_C, ptr, prop, &item_array, NULL, &free); /* we dont want nested rows, cols in menus */ - if (layout->root->type != UI_LAYOUT_MENU) { + if (radial) { + layout_radial = uiLayoutRadial(layout); + uiBlockSetCurLayout(block, layout_radial); + } + else if (layout->root->type != UI_LAYOUT_MENU) { uiBlockSetCurLayout(block, ui_item_local_sublayout(layout, layout, 1)); } else { @@ -569,8 +581,11 @@ static void ui_item_enum_expand(uiLayout *layout, uiBlock *block, PointerRNA *pt } for (item = item_array; item->identifier; item++) { - if (!item->identifier[0]) + if (!item->identifier[0]) { + if (radial) + uiItemS(layout_radial); continue; + } name = (!uiname || uiname[0]) ? item->name : ""; icon = item->icon; @@ -869,6 +884,8 @@ void uiItemsFullEnumO(uiLayout *layout, const char *opname, const char *propname PointerRNA ptr; PropertyRNA *prop; uiBlock *block = layout->root->block; + const bool radial = (layout->item.type == ITEM_LAYOUT_RADIAL) || + ((layout->item.type == ITEM_LAYOUT_ROOT) && (layout->root->type == UI_LAYOUT_PIEMENU)); if (!ot || !ot->srna) { ui_item_disabled(layout, opname); @@ -887,10 +904,24 @@ void uiItemsFullEnumO(uiLayout *layout, const char *opname, const char *propname if (prop && RNA_property_type(prop) == PROP_ENUM) { EnumPropertyItem *item, *item_array = NULL; bool free; - uiLayout *split = uiLayoutSplit(layout, 0.0f, false); - uiLayout *column = uiLayoutColumn(split, layout->align); + uiLayout *split; + uiLayout *target; + + if (radial) { + target = uiLayoutRadial(layout); + } + else { + split = uiLayoutSplit(layout, 0.0f, false); + target = uiLayoutColumn(split, layout->align); + } + + if (radial) { + RNA_property_enum_items_gettexted_all(block->evil_C, &ptr, prop, &item_array, NULL, &free); + } + else { + RNA_property_enum_items_gettexted(block->evil_C, &ptr, prop, &item_array, NULL, &free); + } - RNA_property_enum_items_gettexted(block->evil_C, &ptr, prop, &item_array, NULL, &free); for (item = item_array; item->identifier; item++) { if (item->identifier[0]) { PointerRNA tptr; @@ -905,20 +936,24 @@ void uiItemsFullEnumO(uiLayout *layout, const char *opname, const char *propname } RNA_property_enum_set(&tptr, prop, item->value); - uiItemFullO_ptr(column, ot, item->name, item->icon, tptr.data, context, flag); + uiItemFullO_ptr(target, ot, item->name, item->icon, tptr.data, context, flag); + ui_but_tip_from_enum_item(block->buttons.last, item); } else { if (item->name) { uiBut *but; - if (item != item_array) { - column = uiLayoutColumn(split, layout->align); + + if (item != item_array && !radial) { + target = uiLayoutColumn(split, layout->align); + /* inconsistent, but menus with labels do not look good flipped */ block->flag |= UI_BLOCK_NO_FLIP; } - if (item->icon) { - uiItemL(column, item->name, item->icon); + if (item->icon || radial) { + uiItemL(target, item->name, item->icon); + but = block->buttons.last; } else { @@ -928,8 +963,14 @@ void uiItemsFullEnumO(uiLayout *layout, const char *opname, const char *propname } ui_but_tip_from_enum_item(but, item); } - else { /* XXX bug here, colums draw bottom item badly */ - uiItemS(column); + else { + if (radial) { + uiItemS(target); + } + else { + /* XXX bug here, colums draw bottom item badly */ + uiItemS(target); + } } } } @@ -2072,16 +2113,135 @@ static void ui_litem_layout_column(uiLayout *litem) litem->y = y; } +/* calculates the angle of a specified button in a radial menu, + * stores a float vector in unit circle */ +static RadialDirection ui_get_radialbut_vec(float vec[2], short itemnum) +{ + RadialDirection dir; + BLI_assert(itemnum < 8); + + dir = ui_radial_dir_order[itemnum]; + ui_but_pie_dir_visual(dir, vec); + + return dir; +} + +static bool ui_item_is_radial_displayable(uiItem *item) +{ + + if ((item->type == ITEM_BUTTON) && (((uiButtonItem *)item)->but->type == LABEL)) + return false; + + return true; +} + +static bool ui_item_is_radial_drawable(uiButtonItem *bitem) +{ + + if (ELEM(bitem->but->type, SEPR, SEPRLINE)) + return false; + + return true; +} + +static void ui_litem_layout_radial(uiLayout *litem) +{ + uiItem *item; + int itemh, itemw, x, y; + int itemnum = 0; + int totitems = 0; + + int minx, miny, maxx, maxy; + /* For the radial layout we will use Matt Ebb's design + * for radiation, see http://mattebb.com/weblog/radiation/ + * also the old code at http://developer.blender.org/T5103 + */ + + int pie_radius = U.pie_menu_radius * UI_DPI_FAC; + + x = litem->x; + y = litem->y; + + minx = x, miny = y, maxx = x, maxy = y; + + /* first count total items */ + for (item = litem->items.first; item; item = item->next) + totitems++; + + if (totitems < 5) + litem->root->block->pie_data.flags |= UI_PIE_DEGREES_RANGE_LARGE; + + if (totitems == 3) + litem->root->block->pie_data.flags |= UI_PIE_3_ITEMS; + + for (item = litem->items.first; item; item = item->next) { + /* not all button types are drawn in a radial menu, do filtering here */ + if (ui_item_is_radial_displayable(item)) { + RadialDirection dir; + float vec[2]; + + dir = ui_get_radialbut_vec(vec, itemnum); + + itemnum++; + + if (item->type == ITEM_BUTTON) { + uiButtonItem *bitem = (uiButtonItem *) item; + + bitem->but->pie_dir = dir; + /* scale the buttons */ + bitem->but->rect.ymax *= 1.5f; + /* add a little bit more here to include number */ + bitem->but->rect.xmax += 1.5f * UI_UNIT_X; + /* enable drawing as pie item if supported by widget */ + if (ui_item_is_radial_drawable(bitem)) + bitem->but->dt = UI_EMBOSSR; + } + + ui_item_size(item, &itemw, &itemh); + + ui_item_position(item, x + vec[0] * pie_radius - itemw / 2, y + vec[1] * pie_radius - itemh / 2, itemw, itemh); + + minx = min_ii(minx, x + vec[0] * pie_radius - itemw / 2); + maxx = max_ii(maxx, x + vec[0] * pie_radius + itemw / 2); + miny = min_ii(miny, y + vec[1] * pie_radius - itemh / 2); + maxy = max_ii(maxy, y + vec[1] * pie_radius + itemh / 2); + } + } + + litem->x = minx; + litem->y = miny; + litem->w = maxx - minx; + litem->h = maxy - miny; +} + /* root layout */ static void ui_litem_estimate_root(uiLayout *UNUSED(litem)) { /* nothing to do */ } +static void ui_litem_layout_root_radial(uiLayout *litem) +{ + /* first item is pie menu title, align on center of menu */ + uiItem *item = litem->items.first; + + if (item->type == ITEM_BUTTON) { + int itemh, itemw, x, y; + x = litem->x; + y = litem->y; + + ui_item_size(item, &itemw, &itemh); + + ui_item_position(item, x - itemw / 2, y + 2 * UI_UNIT_Y, itemw, itemh); + } +} + static void ui_litem_layout_root(uiLayout *litem) { if (litem->root->type == UI_LAYOUT_HEADER) ui_litem_layout_row(litem); + else if (litem->root->type == UI_LAYOUT_PIEMENU) + ui_litem_layout_root_radial(litem); else ui_litem_layout_column(litem); } @@ -2497,6 +2657,40 @@ static uiLayoutItemBx *ui_layout_box(uiLayout *layout, int type) return box; } +uiLayout *uiLayoutRadial(uiLayout *layout) +{ + uiLayout *litem; + uiItem *item; + + /* radial layouts are only valid for radial menus */ + if (layout->root->type != UI_LAYOUT_PIEMENU) + return ui_item_local_sublayout(layout, layout, 0); + + /* only one radial wheel per root layout is allowed, so check and return that, if it exists */ + for (item = layout->root->layout->items.first; item; item = item->next) { + litem = (uiLayout *)item; + if (litem->item.type == ITEM_LAYOUT_RADIAL) { + uiBlockSetCurLayout(layout->root->block, litem); + return litem; + } + } + + litem = MEM_callocN(sizeof(uiLayout), "uiLayoutRadial"); + litem->item.type = ITEM_LAYOUT_RADIAL; + litem->root = layout->root; + litem->active = true; + litem->enabled = true; + litem->context = layout->context; + litem->redalert = layout->redalert; + litem->w = layout->w; + BLI_addtail(&layout->root->layout->items, litem); + + uiBlockSetCurLayout(layout->root->block, litem); + + return litem; +} + + uiLayout *uiLayoutBox(uiLayout *layout) { return (uiLayout *)ui_layout_box(layout, ROUNDBOX); @@ -2843,6 +3037,9 @@ static void ui_item_layout(uiItem *item) case ITEM_LAYOUT_OVERLAP: ui_litem_layout_overlap(litem); break; + case ITEM_LAYOUT_RADIAL: + ui_litem_layout_radial(litem); + break; default: break; } @@ -2916,7 +3113,7 @@ uiLayout *uiBlockLayout(uiBlock *block, int dir, int type, int x, int y, int siz layout->enabled = 1; layout->context = NULL; - if (type == UI_LAYOUT_MENU) + if (type == UI_LAYOUT_MENU || type == UI_LAYOUT_PIEMENU) layout->space = 0; if (dir == UI_LAYOUT_HORIZONTAL) { diff --git a/source/blender/editors/interface/interface_panel.c b/source/blender/editors/interface/interface_panel.c index d0909e9413c..78f67beacff 100644 --- a/source/blender/editors/interface/interface_panel.c +++ b/source/blender/editors/interface/interface_panel.c @@ -1912,7 +1912,7 @@ static void panel_activate_state(const bContext *C, Panel *pa, uiHandlePanelStat data = MEM_callocN(sizeof(uiHandlePanelData), "uiHandlePanelData"); pa->activedata = data; - WM_event_add_ui_handler(C, &win->modalhandlers, ui_handler_panel, ui_handler_remove_panel, pa); + WM_event_add_ui_handler(C, &win->modalhandlers, ui_handler_panel, ui_handler_remove_panel, pa, false); } if (ELEM(state, PANEL_STATE_ANIMATION, PANEL_STATE_DRAG)) diff --git a/source/blender/editors/interface/interface_regions.c b/source/blender/editors/interface/interface_regions.c index 3629c72ce49..084b0c0ac67 100644 --- a/source/blender/editors/interface/interface_regions.c +++ b/source/blender/editors/interface/interface_regions.c @@ -43,6 +43,8 @@ #include "BLI_utildefines.h" #include "BLI_ghash.h" +#include "PIL_time.h" + #include "BKE_context.h" #include "BKE_screen.h" #include "BKE_report.h" @@ -1704,18 +1706,69 @@ uiBlock *ui_popup_block_refresh( BLI_addhead(&block->saferct, saferct); } - /* clip block with window boundary */ - ui_popup_block_clip(window, block); - - /* the block and buttons were positioned in window space as in 2.4x, now - * these menu blocks are regions so we bring it back to region space. - * additionally we add some padding for the menu shadow or rounded menus */ - ar->winrct.xmin = block->rect.xmin - width; - ar->winrct.xmax = block->rect.xmax + width; - ar->winrct.ymin = block->rect.ymin - width; - ar->winrct.ymax = block->rect.ymax + MENU_TOP; - - ui_block_translate(block, -ar->winrct.xmin, -ar->winrct.ymin); + if (block->flag & UI_BLOCK_RADIAL) { + uiBut *but; + int win_width = UI_SCREEN_MARGIN; + int winx, winy; + + int x_offset = 0, y_offset = 0; + + winx = WM_window_pixels_x(window); + winy = WM_window_pixels_y(window); + + copy_v2_v2(block->pie_data.pie_center_init, block->pie_data.pie_center_spawned); + + /* only try translation if area is large enough */ + if (BLI_rctf_size_x(&block->rect) < winx - (2.0f * win_width)) { + if (block->rect.xmin < win_width ) x_offset += win_width - block->rect.xmin; + if (block->rect.xmax > winx - win_width) x_offset += winx - win_width - block->rect.xmax; + } + + if (BLI_rctf_size_y(&block->rect) < winy - (2.0f * win_width)) { + if (block->rect.ymin < win_width ) y_offset += win_width - block->rect.ymin; + if (block->rect.ymax > winy - win_width) y_offset += winy - win_width - block->rect.ymax; + } + /* if we are offsetting set up initial data for timeout functionality */ + + if ((x_offset != 0) || (y_offset != 0)) { + block->pie_data.pie_center_spawned[0] += x_offset; + block->pie_data.pie_center_spawned[1] += y_offset; + + ui_block_translate(block, x_offset, y_offset); + + if (U.pie_initial_timeout > 0) + block->pie_data.flags |= UI_PIE_INITIAL_DIRECTION; + } + + ar->winrct.xmin = 0; + ar->winrct.xmax = winx; + ar->winrct.ymin = 0; + ar->winrct.ymax = winy; + + ui_block_calculate_pie_segment(block, block->pie_data.pie_center_init); + + /* lastly set the buttons at the center of the pie menu, ready for animation */ + if (U.pie_animation_timeout > 0) { + for (but = block->buttons.first; but; but = but->next) { + if (but->pie_dir != UI_RADIAL_NONE) { + BLI_rctf_recenter(&but->rect, UNPACK2(block->pie_data.pie_center_spawned)); + } + } + } + } + else { + /* clip block with window boundary */ + ui_popup_block_clip(window, block); + /* the block and buttons were positioned in window space as in 2.4x, now + * these menu blocks are regions so we bring it back to region space. + * additionally we add some padding for the menu shadow or rounded menus */ + ar->winrct.xmin = block->rect.xmin - width; + ar->winrct.xmax = block->rect.xmax + width; + ar->winrct.ymin = block->rect.ymin - width; + ar->winrct.ymax = block->rect.ymax + MENU_TOP; + + ui_block_translate(block, -ar->winrct.xmin, -ar->winrct.ymin); + } if (block_old) { block->oldblock = block_old; @@ -2353,6 +2406,12 @@ struct uiPopupMenu { void *menu_arg; }; +struct uiPieMenu { + uiBlock *block_radial; /* radial block of the pie menu (more could be added later) */ + uiLayout *layout; + int mx, my; +}; + static uiBlock *ui_block_func_POPUP(bContext *C, uiPopupBlockHandle *handle, void *arg_pup) { uiBlock *block; @@ -2526,7 +2585,7 @@ uiPopupBlockHandle *ui_popup_menu_create(bContext *C, ARegion *butregion, uiBut if (!but) { handle->popup = true; - UI_add_popup_handlers(C, &window->modalhandlers, handle); + UI_add_popup_handlers(C, &window->modalhandlers, handle, false); WM_event_add_mousemove(C); } @@ -2588,7 +2647,7 @@ void uiPupMenuEnd(bContext *C, uiPopupMenu *pup) menu = ui_popup_block_create(C, NULL, NULL, NULL, ui_block_func_POPUP, pup); menu->popup = true; - UI_add_popup_handlers(C, &window->modalhandlers, menu); + UI_add_popup_handlers(C, &window->modalhandlers, menu, false); WM_event_add_mousemove(C); MEM_freeN(pup); @@ -2599,6 +2658,175 @@ uiLayout *uiPupMenuLayout(uiPopupMenu *pup) return pup->layout; } +/*************************** Pie Menus ***************************************/ + +static uiBlock *ui_block_func_PIE(bContext *UNUSED(C), uiPopupBlockHandle *handle, void *arg_pie) +{ + uiBlock *block; + uiPieMenu *pie = arg_pie; + int minwidth, width, height; + + minwidth = 50; + block = pie->block_radial; + + /* in some cases we create the block before the region, + * so we set it delayed here if necessary */ + if (BLI_findindex(&handle->region->uiblocks, block) == -1) + uiBlockSetRegion(block, handle->region); + + uiBlockLayoutResolve(block, &width, &height); + + uiBlockSetFlag(block, UI_BLOCK_LOOP | UI_BLOCK_REDRAW | UI_BLOCK_NUMSELECT); + + block->minbounds = minwidth; + block->bounds = 1; + block->mx = 0; + block->my = 0; + block->bounds_type = UI_BLOCK_BOUNDS_PIE_CENTER; + + block->pie_data.pie_center_spawned[0] = pie->mx; + block->pie_data.pie_center_spawned[1] = pie->my; + + return pie->block_radial; +} + +static float uiPieTitleWidth(const char *name, int icon) +{ + return (UI_GetStringWidth(name) + + (UI_UNIT_X * (1.50f + (icon ? 0.25f : 0.0f)))); +} + +uiPieMenu *uiPieMenuBegin(struct bContext *C, const char *title, int icon, const wmEvent *event) +{ + uiStyle *style = UI_GetStyleDraw(); + uiPieMenu *pie = MEM_callocN(sizeof(uiPopupMenu), "pie menu"); + + pie->block_radial = uiBeginBlock(C, NULL, __func__, UI_EMBOSS); + /* may be useful later to allow spawning pies + * from old positions */ + /* pie->block_radial->flag |= UI_BLOCK_POPUP_MEMORY; */ + pie->block_radial->puphash = ui_popup_menu_hash(title); + pie->block_radial->flag |= UI_BLOCK_RADIAL; + pie->block_radial->pie_data.event = event->type; + + pie->layout = uiBlockLayout(pie->block_radial, UI_LAYOUT_VERTICAL, UI_LAYOUT_PIEMENU, 0, 0, 200, 0, 0, style); + pie->mx = event->x; + pie->my = event->y; + + /* create title button */ + if (title[0]) { + char titlestr[256]; + int w; + if (icon) { + BLI_snprintf(titlestr, sizeof(titlestr), " %s", title); + w = uiPieTitleWidth(titlestr, icon); + uiDefIconTextBut(pie->block_radial, LABEL, 0, icon, titlestr, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); + } + else { + w = uiPieTitleWidth(title, 0); + uiDefBut(pie->block_radial, LABEL, 0, title, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); + } + } + + return pie; +} + +void uiPieMenuEnd(bContext *C, uiPieMenu *pie) +{ + wmWindow *window = CTX_wm_window(C); + uiPopupBlockHandle *menu; + + menu = ui_popup_block_create(C, NULL, NULL, NULL, ui_block_func_PIE, pie); + menu->popup = true; + menu->towardstime = PIL_check_seconds_timer(); + + UI_add_popup_handlers(C, &window->modalhandlers, menu, true); + WM_event_add_mousemove(C); + + MEM_freeN(pie); +} + +uiLayout *uiPieMenuLayout(uiPieMenu *pie) +{ + return pie->layout; +} + +void uiPieMenuInvoke(struct bContext *C, const char *idname, const wmEvent *event) +{ + uiPieMenu *pie; + uiLayout *layout; + Menu menu; + MenuType *mt = WM_menutype_find(idname, true); + + if (mt == NULL) { + printf("%s: named menu \"%s\" not found\n", __func__, idname); + return; + } + + if (mt->poll && mt->poll(C, mt) == 0) + return; + + pie = uiPieMenuBegin(C, IFACE_(mt->label), ICON_NONE, event); + layout = uiPieMenuLayout(pie); + + menu.layout = layout; + menu.type = mt; + + if (G.debug & G_DEBUG_WM) { + printf("%s: opening menu \"%s\"\n", __func__, idname); + } + + mt->draw(C, &menu); + + uiPieMenuEnd(C, pie); +} + +void uiPieOperatorEnumInvoke(struct bContext *C, const char *title, const char *opname, + const char *propname, const wmEvent *event) +{ + uiPieMenu *pie; + uiLayout *layout; + + pie = uiPieMenuBegin(C, IFACE_(title), ICON_NONE, event); + layout = uiPieMenuLayout(pie); + + layout = uiLayoutRadial(layout); + uiItemsEnumO(layout, opname, propname); + + uiPieMenuEnd(C, pie); +} + +void uiPieEnumInvoke(struct bContext *C, const char *title, const char *path, + const wmEvent *event) +{ + PointerRNA ctx_ptr; + PointerRNA r_ptr; + PropertyRNA *r_prop; + uiPieMenu *pie; + uiLayout *layout; + + RNA_pointer_create(NULL, &RNA_Context, C, &ctx_ptr); + + if (!RNA_path_resolve(&ctx_ptr, path, &r_ptr, &r_prop)) { + return; + } + + /* invalid property, only accept enums */ + if (RNA_property_type(r_prop) != PROP_ENUM) { + BLI_assert(0); + return; + } + + pie = uiPieMenuBegin(C, IFACE_(title), ICON_NONE, event); + layout = uiPieMenuLayout(pie); + + layout = uiLayoutRadial(layout); + uiItemFullR(layout, &r_ptr, r_prop, RNA_NO_INDEX, 0, UI_ITEM_R_EXPAND, NULL, 0); + + uiPieMenuEnd(C, pie); +} + + /*************************** Standard Popup Menus ****************************/ void uiPupMenuReports(bContext *C, ReportList *reports) @@ -2695,7 +2923,7 @@ void uiPupBlockO(bContext *C, uiBlockCreateFunc func, void *arg, const char *opn handle->optype = (opname) ? WM_operatortype_find(opname, 0) : NULL; handle->opcontext = opcontext; - UI_add_popup_handlers(C, &window->modalhandlers, handle); + UI_add_popup_handlers(C, &window->modalhandlers, handle, false); WM_event_add_mousemove(C); } @@ -2718,7 +2946,7 @@ void uiPupBlockEx(bContext *C, uiBlockCreateFunc func, uiBlockHandleFunc popup_f handle->cancel_func = cancel_func; // handle->opcontext = opcontext; - UI_add_popup_handlers(C, &window->modalhandlers, handle); + UI_add_popup_handlers(C, &window->modalhandlers, handle, false); WM_event_add_mousemove(C); } diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c index 23f185befb9..db4ddeae659 100644 --- a/source/blender/editors/interface/interface_widgets.c +++ b/source/blender/editors/interface/interface_widgets.c @@ -1596,6 +1596,21 @@ static struct uiWidgetColors wcol_menu_back = { 25, -20 }; +/* pie menus */ +static struct uiWidgetColors wcol_pie_menu = { + {10, 10, 10, 200}, + {25, 25, 25, 230}, + {140, 140, 140, 255}, + {45, 45, 45, 230}, + + {160, 160, 160, 255}, + {255, 255, 255, 255}, + + 1, + 10, -10 +}; + + /* tooltip color */ static struct uiWidgetColors wcol_tooltip = { {0, 0, 0, 255}, @@ -1743,6 +1758,7 @@ void ui_widget_color_init(ThemeUI *tui) tui->wcol_menu = wcol_menu; tui->wcol_pulldown = wcol_pulldown; tui->wcol_menu_back = wcol_menu_back; + tui->wcol_pie_menu = wcol_pie_menu; tui->wcol_tooltip = wcol_tooltip; tui->wcol_menu_item = wcol_menu_item; tui->wcol_box = wcol_box; @@ -1891,6 +1907,34 @@ static void widget_state_pulldown(uiWidgetType *wt, int state) copy_v3_v3_char(wt->wcol.text, wt->wcol.text_sel); } +/* special case, pie menu items */ +static void widget_state_pie_menu_item(uiWidgetType *wt, int state) +{ + wt->wcol = *(wt->wcol_theme); + + /* active and disabled (not so common) */ + if ((state & UI_BUT_DISABLED) && (state & UI_ACTIVE)) { + widget_state_blend(wt->wcol.text, wt->wcol.text_sel, 0.5f); + /* draw the backdrop at low alpha, helps navigating with keys + * when disabled items are active */ + copy_v4_v4_char(wt->wcol.inner, wt->wcol.item); + wt->wcol.inner[3] = 64; + } + /* regular disabled */ + else if (state & (UI_BUT_DISABLED | UI_BUT_INACTIVE)) { + widget_state_blend(wt->wcol.text, wt->wcol.inner, 0.5f); + } + /* regular active */ + else if (state & UI_SELECT) { + copy_v4_v4_char(wt->wcol.outline, wt->wcol.inner_sel); + copy_v3_v3_char(wt->wcol.text, wt->wcol.text_sel); + } + else if (state & UI_ACTIVE) { + copy_v4_v4_char(wt->wcol.inner, wt->wcol.item); + copy_v3_v3_char(wt->wcol.text, wt->wcol.text_sel); + } +} + /* special case, menu items */ static void widget_state_menu_item(uiWidgetType *wt, int state) { @@ -2973,6 +3017,29 @@ static void widget_menu_itembut(uiWidgetColors *wcol, rcti *rect, int UNUSED(sta widgetbase_draw(&wtb, wcol); } +static void widget_menu_radial_itembut(uiBut *but, uiWidgetColors *wcol, rcti *rect, int UNUSED(state), int UNUSED(roundboxalign)) +{ + uiWidgetBase wtb; + float rad; + float fac = but->block->pie_data.alphafac; + + widget_init(&wtb); + + wtb.emboss = 0; + + rad = 0.5f * BLI_rcti_size_y(rect); + round_box_edges(&wtb, UI_CNR_ALL, rect, rad); + + wcol->inner[3] *= fac; + wcol->inner_sel[3] *= fac; + wcol->item[3] *= fac; + wcol->text[3] *= fac; + wcol->text_sel[3] *= fac; + wcol->outline[3] *= fac; + + widgetbase_draw(&wtb, wcol); +} + static void widget_list_itembut(uiWidgetColors *wcol, rcti *rect, int UNUSED(state), int UNUSED(roundboxalign)) { uiWidgetBase wtb; @@ -3291,6 +3358,12 @@ static uiWidgetType *widget_type(uiWidgetTypeEnum type) wt.wcol_theme = &btheme->tui.wcol_progress; wt.custom = widget_progressbar; break; + + case UI_WTYPE_MENU_ITEM_RADIAL: + wt.wcol_theme = &btheme->tui.wcol_pie_menu; + wt.custom = widget_menu_radial_itembut; + wt.state = widget_state_pie_menu_item; + break; } return &wt; @@ -3397,6 +3470,9 @@ void ui_draw_but(const bContext *C, ARegion *ar, uiStyle *style, uiBut *but, rct /* "nothing" */ wt = widget_type(UI_WTYPE_ICON); } + else if (but->dt == UI_EMBOSSR) { + wt = widget_type(UI_WTYPE_MENU_ITEM_RADIAL); + } else { switch (but->type) { @@ -3649,6 +3725,125 @@ void ui_draw_menu_back(uiStyle *UNUSED(style), uiBlock *block, rcti *rect) } } +static void draw_disk_shaded( + float start, float angle, + float radius_int, float radius_ext, int subd, + const char col1[4], const char col2[4], + bool shaded) +{ + const float radius_ext_scale = (0.5f / radius_ext); /* 1 / (2 * radius_ext) */ + int i; + + float s, c; + float y1, y2; + float fac; + unsigned char r_col[4]; + + glBegin(GL_TRIANGLE_STRIP); + + s = sinf(start); + c = cosf(start); + + y1 = s * radius_int; + y2 = s * radius_ext; + + if (shaded) { + fac = (y1 + radius_ext) * radius_ext_scale; + round_box_shade_col4_r(r_col, col1, col2, fac); + + glColor4ubv(r_col); + } + + glVertex2f(c * radius_int, s * radius_int); + + if (shaded) { + fac = (y2 + radius_ext) * radius_ext_scale; + round_box_shade_col4_r(r_col, col1, col2, fac); + + glColor4ubv(r_col); + } + glVertex2f(c * radius_ext, s * radius_ext); + + for (i = 1; i < subd; i++) { + float a; + + a = start + ((i) / (float)(subd - 1)) * angle; + s = sinf(a); + c = cosf(a); + y1 = s * radius_int; + y2 = s * radius_ext; + + if (shaded) { + fac = (y1 + radius_ext) * radius_ext_scale; + round_box_shade_col4_r(r_col, col1, col2, fac); + + glColor4ubv(r_col); + } + glVertex2f(c * radius_int, s * radius_int); + + if (shaded) { + fac = (y2 + radius_ext) * radius_ext_scale; + round_box_shade_col4_r(r_col, col1, col2, fac); + + glColor4ubv(r_col); + } + glVertex2f(c * radius_ext, s * radius_ext); + } + glEnd(); + +} + +void ui_draw_pie_center(uiBlock *block) +{ + bTheme *btheme = UI_GetTheme(); + float cx = block->pie_data.pie_center_spawned[0]; + float cy = block->pie_data.pie_center_spawned[1]; + + float *pie_dir = block->pie_data.pie_dir; + + float pie_radius_internal = U.pixelsize * U.pie_menu_threshold; + float pie_radius_external = U.pixelsize * (U.pie_menu_threshold + 7.0f); + + int subd = 40; + + float angle = atan2(pie_dir[1], pie_dir[0]); + float range = (block->pie_data.flags & UI_PIE_DEGREES_RANGE_LARGE) ? ((float)M_PI / 2.0f) : ((float)M_PI / 4.0f); + + glPushMatrix(); + glTranslatef(cx, cy, 0.0f); + + glEnable(GL_BLEND); + if (btheme->tui.wcol_pie_menu.shaded) { + char col1[4], col2[4]; + shadecolors4(col1, col2, btheme->tui.wcol_pie_menu.inner, btheme->tui.wcol_pie_menu.shadetop, btheme->tui.wcol_pie_menu.shadedown); + draw_disk_shaded(0.0f, (float)(M_PI * 2.0), pie_radius_internal, pie_radius_external, subd, col1, col2, true); + } + else { + glColor4ubv((GLubyte *)btheme->tui.wcol_pie_menu.inner); + draw_disk_shaded(0.0f, (float)(M_PI * 2.0), pie_radius_internal, pie_radius_external, subd, NULL, NULL, false); + } + + if (!(block->pie_data.flags & UI_PIE_INVALID_DIR)) { + if (btheme->tui.wcol_pie_menu.shaded) { + char col1[4], col2[4]; + shadecolors4(col1, col2, btheme->tui.wcol_pie_menu.inner_sel, btheme->tui.wcol_pie_menu.shadetop, btheme->tui.wcol_pie_menu.shadedown); + draw_disk_shaded(angle - range / 2.0f, range, pie_radius_internal, pie_radius_external, subd, col1, col2, true); + } + else { + glColor4ubv((GLubyte *)btheme->tui.wcol_pie_menu.inner_sel); + draw_disk_shaded(angle - range / 2.0f, range, pie_radius_internal, pie_radius_external, subd, NULL, NULL, false); + } + } + + glColor4ubv((GLubyte *)btheme->tui.wcol_pie_menu.outline); + glutil_draw_lined_arc(0.0f, (float)M_PI * 2.0f, pie_radius_internal, subd); + glutil_draw_lined_arc(0.0f, (float)M_PI * 2.0f, pie_radius_external, subd); + + glDisable(GL_BLEND); + glPopMatrix(); +} + + uiWidgetColors *ui_tooltip_get_theme(void) { uiWidgetType *wt = widget_type(UI_WTYPE_TOOLTIP); diff --git a/source/blender/editors/interface/resources.c b/source/blender/editors/interface/resources.c index dbb0235f40f..92277cc63ea 100644 --- a/source/blender/editors/interface/resources.c +++ b/source/blender/editors/interface/resources.c @@ -2454,6 +2454,31 @@ void init_userdef_do_versions(void) } } + if (U.versionfile < 271 || (U.versionfile == 271 && U.subversionfile < 4)) { + bTheme *btheme; + + struct uiWidgetColors wcol_pie_menu = { + {10, 10, 10, 200}, + {25, 25, 25, 230}, + {140, 140, 140, 255}, + {45, 45, 45, 230}, + + {160, 160, 160, 255}, + {255, 255, 255, 255}, + + 1, + 10, -10 + }; + + U.pie_menu_radius = 150; + U.pie_menu_threshold = 12; + U.pie_animation_timeout = 6; + + for (btheme = U.themes.first; btheme; btheme = btheme->next) { + btheme->tui.wcol_pie_menu = wcol_pie_menu; + } + } + if (U.pixelsize == 0.0f) U.pixelsize = 1.0f; diff --git a/source/blender/editors/space_api/spacetypes.c b/source/blender/editors/space_api/spacetypes.c index b171e7a5f88..c8431d58bf5 100644 --- a/source/blender/editors/space_api/spacetypes.c +++ b/source/blender/editors/space_api/spacetypes.c @@ -61,6 +61,7 @@ #include "ED_space_api.h" #include "ED_sound.h" #include "ED_uvedit.h" +#include "ED_view3d.h" #include "ED_mball.h" #include "ED_logic.h" #include "ED_clip.h" @@ -130,8 +131,17 @@ void ED_spacetypes_init(void) type->operatortypes(); } - /* Macros's must go last since they reference other operators - * maybe we'll need to have them go after python operators too? */ + /* register internal render callbacks */ + ED_render_internal_init(); +} + +void ED_spacemacros_init(void) +{ + const ListBase *spacetypes; + SpaceType *type; + + /* Macros's must go last since they reference other operators. + * We need to have them go after python operators too */ ED_operatormacros_armature(); ED_operatormacros_mesh(); ED_operatormacros_metaball(); @@ -152,9 +162,6 @@ void ED_spacetypes_init(void) if (type->dropboxes) type->dropboxes(); } - - /* register internal render callbacks */ - ED_render_internal_init(); } /* called in wm.c */ diff --git a/source/blender/editors/space_view3d/view3d_edit.c b/source/blender/editors/space_view3d/view3d_edit.c index 5e2346cecca..5988473588b 100644 --- a/source/blender/editors/space_view3d/view3d_edit.c +++ b/source/blender/editors/space_view3d/view3d_edit.c @@ -78,6 +78,7 @@ #include "ED_view3d.h" #include "ED_sculpt.h" +#include "UI_resources.h" #include "PIL_time.h" /* smoothview */ @@ -3583,13 +3584,13 @@ void VIEW3D_OT_zoom_camera_1_to_1(wmOperatorType *ot) /* ********************* Changing view operator ****************** */ static EnumPropertyItem prop_view_items[] = { + {RV3D_VIEW_LEFT, "LEFT", ICON_TRIA_LEFT, "Left", "View From the Left"}, + {RV3D_VIEW_RIGHT, "RIGHT", ICON_TRIA_RIGHT, "Right", "View From the Right"}, + {RV3D_VIEW_BOTTOM, "BOTTOM", ICON_TRIA_DOWN, "Bottom", "View From the Bottom"}, + {RV3D_VIEW_TOP, "TOP", ICON_TRIA_UP, "Top", "View From the Top"}, {RV3D_VIEW_FRONT, "FRONT", 0, "Front", "View From the Front"}, {RV3D_VIEW_BACK, "BACK", 0, "Back", "View From the Back"}, - {RV3D_VIEW_LEFT, "LEFT", 0, "Left", "View From the Left"}, - {RV3D_VIEW_RIGHT, "RIGHT", 0, "Right", "View From the Right"}, - {RV3D_VIEW_TOP, "TOP", 0, "Top", "View From the Top"}, - {RV3D_VIEW_BOTTOM, "BOTTOM", 0, "Bottom", "View From the Bottom"}, - {RV3D_VIEW_CAMERA, "CAMERA", 0, "Camera", "View From the Active Camera"}, + {RV3D_VIEW_CAMERA, "CAMERA", ICON_CAMERA_DATA, "Camera", "View From the Active Camera"}, {0, NULL, 0, NULL, NULL} }; diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 4240d85a74d..d1f5cc1eadd 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -162,7 +162,7 @@ typedef struct ThemeUI { uiWidgetColors wcol_radio, wcol_option, wcol_toggle; uiWidgetColors wcol_num, wcol_numslider; uiWidgetColors wcol_menu, wcol_pulldown, wcol_menu_back, wcol_menu_item, wcol_tooltip; - uiWidgetColors wcol_box, wcol_scroll, wcol_progress, wcol_list_item; + uiWidgetColors wcol_box, wcol_scroll, wcol_progress, wcol_list_item, wcol_pie_menu; uiWidgetStateColors wcol_state; @@ -534,6 +534,15 @@ typedef struct UserDef { float fcu_inactive_alpha; /* opacity of inactive F-Curves in F-Curve Editor */ float pixelsize; /* private, set by GHOST, to multiply DPI with */ + short pie_interaction_type; /* if keeping a pie menu spawn button pressed after this time, it turns into + * a drag/release pie menu */ + short pie_initial_timeout; /* direction in the pie menu will always be calculated from the initial position + * within this time limit */ + int pie_animation_timeout; + int pad2; + short pie_menu_radius; /* pie menu radius */ + short pie_menu_threshold; /* pie menu distance from center before a direction is set */ + struct WalkNavigation walk_navigation; } UserDef; diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index ba2dd8b5d69..0e091112d8f 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -633,6 +633,7 @@ extern StructRNA RNA_TransformConstraint; extern StructRNA RNA_TransformSequence; extern StructRNA RNA_UILayout; extern StructRNA RNA_UIList; +extern StructRNA RNA_UIPieMenu; extern StructRNA RNA_UIPopupMenu; extern StructRNA RNA_UVWarpModifier; extern StructRNA RNA_UVProjectModifier; @@ -794,6 +795,8 @@ void RNA_property_enum_items(struct bContext *C, PointerRNA *ptr, PropertyRNA *p EnumPropertyItem **item, int *r_totitem, bool *r_free); void RNA_property_enum_items_gettexted(struct bContext *C, PointerRNA *ptr, PropertyRNA *prop, EnumPropertyItem **r_item, int *r_totitem, bool *r_free); +void RNA_property_enum_items_gettexted_all(struct bContext *C, PointerRNA *ptr, PropertyRNA *prop, + EnumPropertyItem **r_item, int *r_totitem, bool *r_free); bool RNA_property_enum_value(struct bContext *C, PointerRNA *ptr, PropertyRNA *prop, const char *identifier, int *r_value); bool RNA_property_enum_identifier(struct bContext *C, PointerRNA *ptr, PropertyRNA *prop, const int value, const char **identifier); bool RNA_property_enum_name(struct bContext *C, PointerRNA *ptr, PropertyRNA *prop, const int value, const char **name); diff --git a/source/blender/makesrna/intern/rna_access.c b/source/blender/makesrna/intern/rna_access.c index 8c76edd2f17..079f71d7be3 100644 --- a/source/blender/makesrna/intern/rna_access.c +++ b/source/blender/makesrna/intern/rna_access.c @@ -1249,12 +1249,9 @@ void RNA_property_enum_items(bContext *C, PointerRNA *ptr, PropertyRNA *prop, En } } -void RNA_property_enum_items_gettexted(bContext *C, PointerRNA *ptr, PropertyRNA *prop, - EnumPropertyItem **r_item, int *r_totitem, bool *r_free) -{ - RNA_property_enum_items(C, ptr, prop, r_item, r_totitem, r_free); - #ifdef WITH_INTERNATIONAL +static void property_enum_translate(PropertyRNA *prop, EnumPropertyItem **r_item, int *r_totitem, bool *r_free) +{ if (!(prop->flag & PROP_ENUM_NO_TRANSLATE)) { int i; @@ -1300,9 +1297,71 @@ void RNA_property_enum_items_gettexted(bContext *C, PointerRNA *ptr, PropertyRNA *r_item = nitem; } +} +#endif + +void RNA_property_enum_items_gettexted(bContext *C, PointerRNA *ptr, PropertyRNA *prop, + EnumPropertyItem **r_item, int *r_totitem, bool *r_free) +{ + RNA_property_enum_items(C, ptr, prop, r_item, r_totitem, r_free); + +#ifdef WITH_INTERNATIONAL + property_enum_translate(prop, r_item, r_totitem, r_free); #endif } +void RNA_property_enum_items_gettexted_all(bContext *C, PointerRNA *ptr, PropertyRNA *prop, + EnumPropertyItem **r_item, int *r_totitem, bool *r_free) +{ + EnumPropertyRNA *eprop = (EnumPropertyRNA *)rna_ensure_property(prop); + int mem_size = sizeof(EnumPropertyItem) * (eprop->totitem + 1); + /* first return all items */ + *r_free = true; + *r_item = MEM_mallocN(mem_size, "enum_gettext_all"); + memcpy(*r_item, eprop->item, mem_size); + + if (r_totitem) + *r_totitem = eprop->totitem; + + if (eprop->itemf && (C != NULL || (prop->flag & PROP_ENUM_NO_CONTEXT))) { + EnumPropertyItem *item; + int i; + bool free = false; + + if (prop->flag & PROP_ENUM_NO_CONTEXT) + item = eprop->itemf(NULL, ptr, prop, &free); + else + item = eprop->itemf(C, ptr, prop, &free); + + /* any callbacks returning NULL should be fixed */ + BLI_assert(item != NULL); + + for (i = 0; i < eprop->totitem; i++) { + bool exists = false; + int i_fixed; + + /* items that do not exist on list are returned, but have their names/identifiers NULLed out */ + for (i_fixed = 0; item[i_fixed].identifier; i_fixed++) { + if (STREQ(item[i_fixed].identifier, (*r_item)[i].identifier)) { + exists = true; + break; + } + } + + if (!exists) { + (*r_item)[i].name = NULL; + (*r_item)[i].identifier = ""; + } + } + + if (free) + MEM_freeN(item); + } + +#ifdef WITH_INTERNATIONAL + property_enum_translate(prop, r_item, r_totitem, r_free); +#endif +} bool RNA_property_enum_value(bContext *C, PointerRNA *ptr, PropertyRNA *prop, const char *identifier, int *r_value) { diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c index 5d10a0a61c1..216a99866b3 100644 --- a/source/blender/makesrna/intern/rna_object.c +++ b/source/blender/makesrna/intern/rna_object.c @@ -61,12 +61,12 @@ EnumPropertyItem object_mode_items[] = { {OB_MODE_OBJECT, "OBJECT", ICON_OBJECT_DATAMODE, "Object Mode", ""}, {OB_MODE_EDIT, "EDIT", ICON_EDITMODE_HLT, "Edit Mode", ""}, + {OB_MODE_POSE, "POSE", ICON_POSE_HLT, "Pose Mode", ""}, {OB_MODE_SCULPT, "SCULPT", ICON_SCULPTMODE_HLT, "Sculpt Mode", ""}, {OB_MODE_VERTEX_PAINT, "VERTEX_PAINT", ICON_VPAINT_HLT, "Vertex Paint", ""}, {OB_MODE_WEIGHT_PAINT, "WEIGHT_PAINT", ICON_WPAINT_HLT, "Weight Paint", ""}, {OB_MODE_TEXTURE_PAINT, "TEXTURE_PAINT", ICON_TPAINT_HLT, "Texture Paint", ""}, {OB_MODE_PARTICLE_EDIT, "PARTICLE_EDIT", ICON_PARTICLEMODE, "Particle Edit", ""}, - {OB_MODE_POSE, "POSE", ICON_POSE_HLT, "Pose Mode", ""}, {0, NULL, 0, NULL, NULL} }; diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index 0ddc63e57cf..7dc77814ad5 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -1784,11 +1784,11 @@ static void rna_def_space_view3d(BlenderRNA *brna) PropertyRNA *prop; static EnumPropertyItem manipulators_items[] = { - {V3D_MANIP_TRANSLATE, "TRANSLATE", ICON_MAN_TRANS, "Manipulator Translate", + {V3D_MANIP_TRANSLATE, "TRANSLATE", ICON_MAN_TRANS, "Translate", "Use the manipulator for movement transformations"}, - {V3D_MANIP_ROTATE, "ROTATE", ICON_MAN_ROT, "Manipulator Rotate", + {V3D_MANIP_ROTATE, "ROTATE", ICON_MAN_ROT, "Rotate", "Use the manipulator for rotation transformations"}, - {V3D_MANIP_SCALE, "SCALE", ICON_MAN_SCALE, "Manipulator Scale", + {V3D_MANIP_SCALE, "SCALE", ICON_MAN_SCALE, "Scale", "Use the manipulator for scale transformations"}, {0, NULL, 0, NULL, NULL} }; diff --git a/source/blender/makesrna/intern/rna_ui_api.c b/source/blender/makesrna/intern/rna_ui_api.c index da3d7b029ed..b13bdedaffd 100644 --- a/source/blender/makesrna/intern/rna_ui_api.c +++ b/source/blender/makesrna/intern/rna_ui_api.c @@ -462,6 +462,13 @@ void RNA_api_ui_layout(StructRNA *srna) RNA_def_float(func, "percentage", 0.0f, 0.0f, 1.0f, "Percentage", "Percentage of width to split at", 0.0f, 1.0f); RNA_def_boolean(func, "align", false, "", "Align buttons to each other"); + /* radial/pie layout */ + func = RNA_def_function(srna, "menu_pie", "uiLayoutRadial"); + parm = RNA_def_pointer(func, "layout", "UILayout", "", "Sub-layout to put items in"); + RNA_def_function_return(func, parm); + RNA_def_function_ui_description(func, "Sublayout. Items placed in this sublayout are placed " + "in a radial fashion around the menu center)"); + /* Icon of a rna pointer */ func = RNA_def_function(srna, "icon", "rna_ui_get_rnaptr_icon"); parm = RNA_def_int(func, "icon_value", ICON_NONE, 0, INT_MAX, "", "Icon identifier", 0, INT_MAX); diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 12958297028..f11780768fd 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -953,6 +953,12 @@ static void rna_def_userdef_theme_ui(BlenderRNA *brna) RNA_def_property_flag(prop, PROP_NEVER_NULL); RNA_def_property_ui_text(prop, "Menu Backdrop Colors", ""); RNA_def_property_update(prop, 0, "rna_userdef_update"); + + prop = RNA_def_property(srna, "wcol_pie_menu", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_NEVER_NULL); + RNA_def_property_ui_text(prop, "Pie Menu Colors", ""); + RNA_def_property_update(prop, 0, "rna_userdef_update"); + prop = RNA_def_property(srna, "wcol_tooltip", PROP_POINTER, PROP_NONE); RNA_def_property_flag(prop, PROP_NEVER_NULL); RNA_def_property_ui_text(prop, "Tooltip Colors", ""); @@ -3208,6 +3214,26 @@ static void rna_def_userdef_view(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Sub Level Menu Open Delay", "Time delay in 1/10 seconds before automatically opening sub level menus"); + /* pie menus */ + prop = RNA_def_property(srna, "pie_initial_timeout", PROP_INT, PROP_NONE); + RNA_def_property_range(prop, 0, 1000); + RNA_def_property_ui_text(prop, "Recenter Timeout", + "Pie menus will use the initial mouse position as center for this amount of time " + "(in 1/100ths of sec)"); + + prop = RNA_def_property(srna, "pie_animation_timeout", PROP_INT, PROP_NONE); + RNA_def_property_range(prop, 0, 1000); + RNA_def_property_ui_text(prop, "Animation Timeout", + "Time needed to fully animate the pie to unfolded state (in 1/100ths of sec)"); + + prop = RNA_def_property(srna, "pie_menu_radius", PROP_INT, PROP_PIXEL); + RNA_def_property_range(prop, 0, 1000); + RNA_def_property_ui_text(prop, "Radius", "Pie menu size in pixels"); + + prop = RNA_def_property(srna, "pie_menu_threshold", PROP_INT, PROP_PIXEL); + RNA_def_property_range(prop, 0, 1000); + RNA_def_property_ui_text(prop, "Threshold", "Distance from center needed before a selection can be made"); + prop = RNA_def_property(srna, "use_quit_dialog", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "uiflag", USER_QUIT_PROMPT); RNA_def_property_ui_text(prop, "Prompt Quit", diff --git a/source/blender/makesrna/intern/rna_wm.c b/source/blender/makesrna/intern/rna_wm.c index 996d2d2882d..c88fa020ed6 100644 --- a/source/blender/makesrna/intern/rna_wm.c +++ b/source/blender/makesrna/intern/rna_wm.c @@ -594,6 +594,17 @@ static PointerRNA rna_PopupMenu_layout_get(PointerRNA *ptr) return rptr; } +static PointerRNA rna_PieMenu_layout_get(PointerRNA *ptr) +{ + struct uiPieMenu *pie = ptr->data; + uiLayout *layout = uiPieMenuLayout(pie); + + PointerRNA rptr; + RNA_pointer_create(ptr->id.data, &RNA_UILayout, layout, &rptr); + + return rptr; +} + static void rna_Window_screen_set(PointerRNA *ptr, PointerRNA value) { wmWindow *win = (wmWindow *)ptr->data; @@ -1716,6 +1727,26 @@ static void rna_def_popupmenu(BlenderRNA *brna) RNA_define_verify_sdna(1); /* not in sdna */ } +static void rna_def_piemenu(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "UIPieMenu", NULL); + RNA_def_struct_ui_text(srna, "PieMenu", ""); + RNA_def_struct_sdna(srna, "uiPieMenu"); + + RNA_define_verify_sdna(0); /* not in sdna */ + + /* could wrap more, for now this is enough */ + prop = RNA_def_property(srna, "layout", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "UILayout"); + RNA_def_property_pointer_funcs(prop, "rna_PieMenu_layout_get", + NULL, NULL, NULL); + + RNA_define_verify_sdna(1); /* not in sdna */ +} + static void rna_def_window(BlenderRNA *brna) { StructRNA *srna; @@ -2078,6 +2109,7 @@ void RNA_def_wm(BlenderRNA *brna) rna_def_event(brna); rna_def_timer(brna); rna_def_popupmenu(brna); + rna_def_piemenu(brna); rna_def_window(brna); rna_def_windowmanager(brna); rna_def_keyconfig(brna); diff --git a/source/blender/makesrna/intern/rna_wm_api.c b/source/blender/makesrna/intern/rna_wm_api.c index 9b288903aa2..7d499521a1a 100644 --- a/source/blender/makesrna/intern/rna_wm_api.c +++ b/source/blender/makesrna/intern/rna_wm_api.c @@ -310,6 +310,24 @@ static void rna_PupMenuEnd(bContext *C, PointerRNA *handle) uiPupMenuEnd(C, handle->data); } +/* pie menu wrapper */ +static PointerRNA rna_PieMenuBegin(bContext *C, const char *title, int icon, PointerRNA *event) +{ + PointerRNA r_ptr; + void *data; + + data = (void *)uiPieMenuBegin(C, title, icon, event->data); + + RNA_pointer_create(NULL, &RNA_UIPieMenu, data, &r_ptr); + + return r_ptr; +} + +static void rna_PieMenuEnd(bContext *C, PointerRNA *handle) +{ + uiPieMenuEnd(C, handle->data); +} + #else #define WM_GEN_INVOKE_EVENT (1 << 0) @@ -461,6 +479,26 @@ void RNA_api_wm(StructRNA *srna) RNA_def_function_flag(func, FUNC_NO_SELF | FUNC_USE_CONTEXT); parm = RNA_def_pointer(func, "menu", "UIPopupMenu", "", ""); RNA_def_property_flag(parm, PROP_RNAPTR | PROP_NEVER_NULL); + + /* wrap uiPieMenuBegin */ + func = RNA_def_function(srna, "piemenu_begin__internal", "rna_PieMenuBegin"); + RNA_def_function_flag(func, FUNC_NO_SELF | FUNC_USE_CONTEXT); + parm = RNA_def_string(func, "title", NULL, 0, "", ""); + RNA_def_property_flag(parm, PROP_REQUIRED); + parm = RNA_def_property(func, "icon", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(parm, icon_items); + parm = RNA_def_pointer(func, "event", "Event", "", ""); + RNA_def_property_flag(parm, PROP_RNAPTR | PROP_NEVER_NULL); + /* return */ + parm = RNA_def_pointer(func, "menu_pie", "UIPieMenu", "", ""); + RNA_def_property_flag(parm, PROP_RNAPTR | PROP_NEVER_NULL); + RNA_def_function_return(func, parm); + + /* wrap uiPieMenuEnd */ + func = RNA_def_function(srna, "piemenu_end__internal", "rna_PieMenuEnd"); + RNA_def_function_flag(func, FUNC_NO_SELF | FUNC_USE_CONTEXT); + parm = RNA_def_pointer(func, "menu", "UIPieMenu", "", ""); + RNA_def_property_flag(parm, PROP_RNAPTR | PROP_NEVER_NULL); } void RNA_api_operator(StructRNA *srna) diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index 253976052fd..35b7fb4b9c9 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -151,7 +151,7 @@ typedef void (*wmUIHandlerRemoveFunc)(struct bContext *C, void *userdata); struct wmEventHandler *WM_event_add_ui_handler( const struct bContext *C, ListBase *handlers, wmUIHandlerFunc ui_handle, wmUIHandlerRemoveFunc ui_remove, - void *userdata); + void *userdata, const bool accept_dbl_click); void WM_event_remove_ui_handler( ListBase *handlers, wmUIHandlerFunc ui_handle, wmUIHandlerRemoveFunc ui_remove, diff --git a/source/blender/windowmanager/WM_keymap.h b/source/blender/windowmanager/WM_keymap.h index 9645c95f62b..8f3b4dbb8e6 100644 --- a/source/blender/windowmanager/WM_keymap.h +++ b/source/blender/windowmanager/WM_keymap.h @@ -64,6 +64,8 @@ wmKeyMapItem *WM_keymap_add_item(struct wmKeyMap *keymap, const char *idname, in int val, int modifier, int keymodifier); wmKeyMapItem *WM_keymap_add_menu(struct wmKeyMap *keymap, const char *idname, int type, int val, int modifier, int keymodifier); +wmKeyMapItem *WM_keymap_add_menu_pie(struct wmKeyMap *keymap, const char *idname, int type, + int val, int modifier, int keymodifier, bool force_click); bool WM_keymap_remove_item(struct wmKeyMap *keymap, struct wmKeyMapItem *kmi); int WM_keymap_item_to_string(wmKeyMapItem *kmi, char *str, const int len); diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index 7e2b7f2eb65..ab4b21d5e33 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -401,8 +401,12 @@ static int wm_handler_ui_call(bContext *C, wmEventHandler *handler, wmEvent *eve /* UI code doesn't handle return values - it just always returns break. * to make the DBL_CLICK conversion work, we just don't send this to UI, except mouse clicks */ - if (event->type != LEFTMOUSE && event->val == KM_DBL_CLICK) + if (((handler->flag & WM_HANDLER_ACCEPT_DBL_CLICK) == 0) && + (event->type != LEFTMOUSE) && + (event->val == KM_DBL_CLICK)) + { return WM_HANDLER_CONTINUE; + } /* UI is quite aggressive with swallowing events, like scrollwheel */ /* I realize this is not extremely nice code... when UI gets keymaps it can be maybe smarter */ @@ -2563,7 +2567,7 @@ void WM_event_remove_keymap_handler(ListBase *handlers, wmKeyMap *keymap) wmEventHandler *WM_event_add_ui_handler( const bContext *C, ListBase *handlers, wmUIHandlerFunc ui_handle, wmUIHandlerRemoveFunc ui_remove, - void *userdata) + void *userdata, const bool accept_dbl_click) { wmEventHandler *handler = MEM_callocN(sizeof(wmEventHandler), "event ui handler"); handler->ui_handle = ui_handle; @@ -2580,6 +2584,9 @@ wmEventHandler *WM_event_add_ui_handler( handler->ui_menu = NULL; } + if (accept_dbl_click) { + handler->flag |= WM_HANDLER_ACCEPT_DBL_CLICK; + } BLI_addhead(handlers, handler); diff --git a/source/blender/windowmanager/intern/wm_init_exit.c b/source/blender/windowmanager/intern/wm_init_exit.c index 693c48cf8b9..da0ef2b0c2a 100644 --- a/source/blender/windowmanager/intern/wm_init_exit.c +++ b/source/blender/windowmanager/intern/wm_init_exit.c @@ -187,6 +187,8 @@ void WM_init(bContext *C, int argc, const char **argv) (void)argv; /* unused */ #endif + ED_spacemacros_init(); + if (!G.background && !wm_start_with_console) GHOST_toggleConsole(3); diff --git a/source/blender/windowmanager/intern/wm_keymap.c b/source/blender/windowmanager/intern/wm_keymap.c index a95f2af1a8f..e7bdce92122 100644 --- a/source/blender/windowmanager/intern/wm_keymap.c +++ b/source/blender/windowmanager/intern/wm_keymap.c @@ -464,6 +464,14 @@ wmKeyMapItem *WM_keymap_add_menu(wmKeyMap *keymap, const char *idname, int type, return kmi; } +wmKeyMapItem *WM_keymap_add_menu_pie(wmKeyMap *keymap, const char *idname, int type, int val, int modifier, int keymodifier, bool force_click) +{ + wmKeyMapItem *kmi = WM_keymap_add_item(keymap, "WM_OT_call_menu_pie", type, val, modifier, keymodifier); + RNA_string_set(kmi->ptr, "name", idname); + RNA_boolean_set(kmi->ptr, "force_click", force_click); + return kmi; +} + bool WM_keymap_remove_item(wmKeyMap *keymap, wmKeyMapItem *kmi) { if (BLI_findindex(&keymap->items, kmi) != -1) { diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c index 0239175835e..d71beed3602 100644 --- a/source/blender/windowmanager/intern/wm_operators.c +++ b/source/blender/windowmanager/intern/wm_operators.c @@ -2049,6 +2049,41 @@ static void WM_OT_call_menu(wmOperatorType *ot) RNA_def_string(ot->srna, "name", NULL, BKE_ST_MAXNAME, "Name", "Name of the menu"); } +static int wm_call_pie_menu_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + char idname[BKE_ST_MAXNAME]; + RNA_string_get(op->ptr, "name", idname); + + uiPieMenuInvoke(C, idname, event); + + return OPERATOR_CANCELLED; +} + +static int wm_call_pie_menu_exec(bContext *C, wmOperator *op) +{ + char idname[BKE_ST_MAXNAME]; + RNA_string_get(op->ptr, "name", idname); + + uiPieMenuInvoke(C, idname, CTX_wm_window(C)->eventstate); + + return OPERATOR_CANCELLED; +} + +static void WM_OT_call_menu_pie(wmOperatorType *ot) +{ + ot->name = "Call Pie Menu"; + ot->idname = "WM_OT_call_menu_pie"; + ot->description = "Call (draw) a pre-defined pie menu"; + + ot->invoke = wm_call_pie_menu_invoke; + ot->exec = wm_call_pie_menu_exec; + ot->poll = WM_operator_winactive; + + ot->flag = OPTYPE_INTERNAL; + + RNA_def_string(ot->srna, "name", NULL, BKE_ST_MAXNAME, "Name", "Name of the pie menu"); +} + /* ************ window / screen operator definitions ************** */ /* this poll functions is needed in place of WM_operator_winactive @@ -4427,6 +4462,7 @@ void wm_operatortype_init(void) WM_operatortype_append(WM_OT_splash); WM_operatortype_append(WM_OT_search_menu); WM_operatortype_append(WM_OT_call_menu); + WM_operatortype_append(WM_OT_call_menu_pie); WM_operatortype_append(WM_OT_radial_control); #if defined(WIN32) WM_operatortype_append(WM_OT_console_toggle); diff --git a/source/blender/windowmanager/wm_event_system.h b/source/blender/windowmanager/wm_event_system.h index df084554c54..d1a94194108 100644 --- a/source/blender/windowmanager/wm_event_system.h +++ b/source/blender/windowmanager/wm_event_system.h @@ -81,8 +81,9 @@ enum { /* handler flag */ enum { - WM_HANDLER_BLOCKING = 1, /* after this handler all others are ignored */ - WM_HANDLER_DO_FREE = 2 /* handler tagged to be freed in wm_handlers_do() */ + WM_HANDLER_BLOCKING = (1 << 0), /* after this handler all others are ignored */ + WM_HANDLER_DO_FREE = (1 << 1), /* handler tagged to be freed in wm_handlers_do() */ + WM_HANDLER_ACCEPT_DBL_CLICK = (1 << 2), /* handler accepts double key press events */ }; /* wm_event_system.c */ diff --git a/source/blenderplayer/bad_level_call_stubs/stubs.c b/source/blenderplayer/bad_level_call_stubs/stubs.c index 57e947ed0a8..87a8bbf3e02 100644 --- a/source/blenderplayer/bad_level_call_stubs/stubs.c +++ b/source/blenderplayer/bad_level_call_stubs/stubs.c @@ -622,6 +622,13 @@ struct wmKeyMap *WM_modalkeymap_add(struct wmKeyConfig *keyconf, const char *idn struct uiPopupMenu *uiPupMenuBegin(struct bContext *C, const char *title, int icon) RET_NULL void uiPupMenuEnd(struct bContext *C, struct uiPopupMenu *head) RET_NONE struct uiLayout *uiPupMenuLayout(struct uiPopupMenu *head) RET_NULL +struct uiLayout *uiPieMenuLayout(struct uiPieMenu *pie) RET_NULL +void uiPieMenuInvoke(struct bContext *C, const char *idname, const struct wmEvent *event) RET_NONE +struct uiPieMenu *uiPieMenuBegin(struct bContext *C, const char *title, int icon, const struct wmEvent *event) RET_NULL +void uiPieMenuEnd(struct bContext *C, uiPieMenu *pie) RET_NONE +struct uiLayout *uiLayoutRadial(struct uiLayout *layout) RET_NULL +void uiPieOperatorEnumInvoke(struct bContext *C, const char *title, const char *opname, + const char *propname, const struct wmEvent *event) RET_NONE /* RNA COLLADA dependency */ int collada_export(struct Scene *sce,