diff options
Diffstat (limited to 'source/blender/editors')
-rw-r--r-- | source/blender/editors/include/ED_space_api.h | 1 | ||||
-rw-r--r-- | source/blender/editors/include/UI_interface.h | 21 | ||||
-rw-r--r-- | source/blender/editors/interface/interface.c | 91 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_handlers.c | 423 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_intern.h | 47 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_layout.c | 227 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_panel.c | 2 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_regions.c | 260 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_widgets.c | 195 | ||||
-rw-r--r-- | source/blender/editors/interface/resources.c | 25 | ||||
-rw-r--r-- | source/blender/editors/space_api/spacetypes.c | 17 | ||||
-rw-r--r-- | source/blender/editors/space_view3d/view3d_edit.c | 11 |
12 files changed, 1247 insertions, 73 deletions
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} }; |