Merge branch 'blender2.7'
[blender.git] / source / blender / editors / interface / interface_handlers.c
index 3ebbf3e..a52a963 100644 (file)
@@ -17,8 +17,8 @@
  * All rights reserved.
  */
 
-/** \file blender/editors/interface/interface_handlers.c
- *  \ingroup edinterface
+/** \file
+ * \ingroup edinterface
  */
 
 #include <float.h>
 #include "MEM_guardedalloc.h"
 
 #include "DNA_brush_types.h"
-#include "DNA_sensor_types.h"
-#include "DNA_controller_types.h"
-#include "DNA_actuator_types.h"
 
-#include "DNA_object_types.h"
 #include "DNA_scene_types.h"
 #include "DNA_screen_types.h"
 
 #include "BKE_unit.h"
 #include "BKE_paint.h"
 
+#include "IMB_colormanagement.h"
+
 #include "ED_screen.h"
 #include "ED_undo.h"
 
 #include "UI_interface.h"
+#include "UI_view2d.h"
 
 #include "BLF_api.h"
 
 #define UI_MAX_PASSWORD_STR 128
 
 /* proto */
-static void ui_but_smart_controller_add(bContext *C, uiBut *from, uiBut *to);
-static void ui_but_link_add(bContext *C, uiBut *from, uiBut *to);
 static int ui_do_but_EXIT(bContext *C, uiBut *but, struct uiHandleButtonData *data, const wmEvent *event);
 static bool ui_but_find_select_in_enum__cmp(const uiBut *but_a, const uiBut *but_b);
 static void ui_textedit_string_set(uiBut *but, struct uiHandleButtonData *data, const char *str);
@@ -119,7 +116,7 @@ static bool ui_mouse_motion_keynav_test(struct uiKeyNavLock *keynav, const wmEve
 #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_AUTO_OPEN_THRESH     0.2
 #define BUTTON_MOUSE_TOWARDS_THRESH 1.0
 /* pixels to move the cursor to get out of keyboard navigation */
 #define BUTTON_KEYNAV_PX_LIMIT      8
@@ -275,6 +272,7 @@ typedef struct uiHandleButtonData {
        /* booleans (could be made into flags) */
        bool cancel, escapecancel;
        bool applied, applied_interactive;
+       bool changed_cursor;
        wmTimer *flashtimer;
 
        /* edited value */
@@ -620,11 +618,8 @@ PointerRNA *ui_handle_afterfunc_add_operator(wmOperatorType *ot, int opcontext,
 
 static void popup_check(bContext *C, wmOperator *op)
 {
-       if (op && op->type->check && op->type->check(C, op)) {
-               /* check for popup and re-layout buttons */
-               ARegion *ar_menu = CTX_wm_menu(C);
-               if (ar_menu)
-                       ED_region_tag_refresh_ui(ar_menu);
+       if (op && op->type->check) {
+               op->type->check(C, op);
        }
 }
 
@@ -705,8 +700,7 @@ static void ui_apply_but_undo(uiBut *but)
                const char *str = NULL;
 
                /* define which string to use for undo */
-               if (ELEM(but->type, UI_BTYPE_LINK, UI_BTYPE_INLINK)) str = "Add button link";
-               else if (but->type == UI_BTYPE_MENU) str = but->drawstr;
+               if (but->type == UI_BTYPE_MENU) str = but->drawstr;
                else if (but->drawstr[0]) str = but->drawstr;
                else str = but->tip;
 
@@ -918,6 +912,21 @@ static void ui_apply_but_TEX(bContext *C, uiBut *but, uiHandleButtonData *data)
        data->applied = true;
 }
 
+static void ui_apply_but_TAB(bContext *C, uiBut *but, uiHandleButtonData *data)
+{
+       if (data->str) {
+               ui_but_string_set(C, but, data->str);
+               ui_but_update_edited(but);
+       }
+       else {
+               ui_but_value_set(but, but->hardmax);
+               ui_apply_but_func(C, but);
+       }
+
+       data->retval = but->retval;
+       data->applied = true;
+}
+
 static void ui_apply_but_NUM(bContext *C, uiBut *but, uiHandleButtonData *data)
 {
        if (data->str) {
@@ -1117,14 +1126,12 @@ static bool ui_multibut_states_tag(
 
 static void ui_multibut_states_create(uiBut *but_active, uiHandleButtonData *data)
 {
-       uiBut *but;
-
        BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_SETUP);
        BLI_assert(data->multi_data.has_mbuts);
 
        data->multi_data.bs_mbuts = UI_butstore_create(but_active->block);
 
-       for (but = but_active->block->buttons.first; but; but = but->next) {
+       for (uiBut *but = but_active->block->buttons.first; but; but = but->next) {
                if (but->flag & UI_BUT_DRAG_MULTI) {
                        ui_multibut_add(data, but);
                }
@@ -1133,11 +1140,14 @@ static void ui_multibut_states_create(uiBut *but_active, uiHandleButtonData *dat
        /* edit buttons proportionally to eachother
         * note: if we mix buttons which are proportional and others which are not,
         * this may work a bit strangely */
-       if (but_active->rnaprop) {
-               if ((data->origvalue != 0.0) && (RNA_property_flag(but_active->rnaprop) & PROP_PROPORTIONAL)) {
+       if ((but_active->rnaprop && (RNA_property_flag(but_active->rnaprop) & PROP_PROPORTIONAL)) ||
+           ELEM(but_active->unit_type, PROP_UNIT_LENGTH))
+       {
+               if (data->origvalue != 0.0) {
                        data->multi_data.is_proportional = true;
                }
        }
+
 }
 
 static void ui_multibut_states_apply(bContext *C, uiHandleButtonData *data, uiBlock *block)
@@ -1220,35 +1230,51 @@ static bool ui_drag_toggle_but_is_supported(const uiBut *but)
        if (ui_but_is_bool(but)) {
                return true;
        }
+       else if (UI_but_is_decorator(but)) {
+               return ELEM(but->icon, ICON_DECORATE, ICON_DECORATE_KEYFRAME, ICON_DECORATE_ANIMATE, ICON_DECORATE_OVERRIDE);
+       }
        else {
                return false;
        }
 }
 
-static bool ui_drag_toggle_but_is_pushed(uiBut *but)
+/* Button pushed state to compare if other buttons match. Can be more
+ * then just true or false for toggle buttons with more than 2 states. */
+static int ui_drag_toggle_but_pushed_state(bContext *C, uiBut *but)
 {
-       if (ui_but_is_bool(but)) {
+       if (but->rnapoin.data == NULL && but->poin == NULL && but->icon) {
+               if (but->pushed_state_func) {
+                       return but->pushed_state_func(C, but->pushed_state_arg);
+               }
+               else {
+                       /* Assume icon identifies a unique state, for buttons that
+                       * work though functions callbacks and don't have an boolean
+                       * value that indicates the state. */
+                       return but->icon + but->iconadd;
+               }
+       }
+       else if (ui_but_is_bool(but)) {
                return ui_but_is_pushed(but);
        }
        else {
-               return false;
+               return 0;
        }
 }
 
 typedef struct uiDragToggleHandle {
        /* init */
-       bool is_init;
-       bool is_set;
+       int pushed_state;
        float but_cent_start[2];
-       eButType but_type_start;
 
-       bool xy_lock[2];
+       bool is_xy_lock_init;
+       bool    xy_lock[2];
+
        int  xy_init[2];
        int  xy_last[2];
 } uiDragToggleHandle;
 
 static bool ui_drag_toggle_set_xy_xy(
-        bContext *C, ARegion *ar, const bool is_set, const eButType but_type_start,
+        bContext *C, ARegion *ar, const int pushed_state,
         const int xy_src[2], const int xy_dst[2])
 {
        /* popups such as layers won't re-evaluate on redraw */
@@ -1272,10 +1298,10 @@ static bool ui_drag_toggle_set_xy_xy(
                                if (BLI_rctf_isect_segment(&but->rect, xy_a_block, xy_b_block)) {
 
                                        /* execute the button */
-                                       if (ui_drag_toggle_but_is_supported(but) && but->type == but_type_start) {
+                                       if (ui_drag_toggle_but_is_supported(but)) {
                                                /* is it pressed? */
-                                               bool is_set_but = ui_drag_toggle_but_is_pushed(but);
-                                               if (is_set_but != is_set) {
+                                               int pushed_state_but = ui_drag_toggle_but_pushed_state(C, but);
+                                               if (pushed_state_but != pushed_state) {
                                                        UI_but_execute(C, but);
                                                        if (do_check) {
                                                                ui_but_update_edited(but);
@@ -1309,7 +1335,7 @@ static void ui_drag_toggle_set(bContext *C, uiDragToggleHandle *drag_info, const
         * Check if we need to initialize the lock axis by finding if the first
         * button we mouse over is X or Y aligned, then lock the mouse to that axis after.
         */
-       if (drag_info->is_init == false) {
+       if (drag_info->is_xy_lock_init == false) {
                /* first store the buttons original coords */
                uiBut *but = ui_but_find_mouse_over_ex(ar, xy_input[0], xy_input[1], true);
 
@@ -1331,11 +1357,11 @@ static void ui_drag_toggle_set(bContext *C, uiDragToggleHandle *drag_info, const
                                        else {
                                                drag_info->xy_lock[1] = true;
                                        }
-                                       drag_info->is_init = true;
+                                       drag_info->is_xy_lock_init = true;
                                }
                        }
                        else {
-                               drag_info->is_init = true;
+                               drag_info->is_xy_lock_init = true;
                        }
                }
        }
@@ -1347,7 +1373,7 @@ static void ui_drag_toggle_set(bContext *C, uiDragToggleHandle *drag_info, const
 
 
        /* touch all buttons between last mouse coord and this one */
-       do_draw = ui_drag_toggle_set_xy_xy(C, ar, drag_info->is_set, drag_info->but_type_start, drag_info->xy_last, xy);
+       do_draw = ui_drag_toggle_set_xy_xy(C, ar, drag_info->pushed_state, drag_info->xy_last, xy);
 
        if (do_draw) {
                ED_region_tag_redraw(ar);
@@ -1715,8 +1741,13 @@ static bool ui_but_drag_init(
        /* prevent other WM gestures to start while we try to drag */
        WM_gestures_remove(C);
 
-       if (ABS(data->dragstartx - event->x) + ABS(data->dragstarty - event->y) > U.dragthreshold) {
+       /* Clamp the maximum to half the UI unit size so a high user preference
+        * doesn't require the user to drag more then half the default button height. */
+       const int drag_threshold = min_ii(
+               U.tweak_threshold * U.dpi_fac,
+               (int)((UI_UNIT_Y / 2) * ui_block_to_window_scale(data->region, but->block)));
 
+       if (ABS(data->dragstartx - event->x) + ABS(data->dragstarty - event->y) > drag_threshold) {
                button_activate_state(C, but, BUTTON_STATE_EXIT);
                data->cancel = true;
 #ifdef USE_DRAG_TOGGLE
@@ -1728,10 +1759,9 @@ static bool ui_but_drag_init(
                         * typically 'button_activate_exit()' handles this */
                        ui_apply_but_autokey(C, but);
 
-                       drag_info->is_set = ui_drag_toggle_but_is_pushed(but);
+                       drag_info->pushed_state = ui_drag_toggle_but_pushed_state(C, but);
                        drag_info->but_cent_start[0] = BLI_rctf_cent_x(&but->rect);
                        drag_info->but_cent_start[1] = BLI_rctf_cent_y(&but->rect);
-                       drag_info->but_type_start = but->type;
                        copy_v2_v2_int(drag_info->xy_init, &event->x);
                        copy_v2_v2_int(drag_info->xy_last, &event->x);
 
@@ -1746,6 +1776,22 @@ static bool ui_but_drag_init(
                                drag_info, WM_HANDLER_BLOCKING);
 
                        CTX_wm_region_set(C, ar_prev);
+
+                       /* Initialize alignment for single row/column regions,
+                        * otherwise we use the relative position of the first other button dragged over. */
+                       if (ELEM(data->region->regiontype, RGN_TYPE_NAV_BAR, RGN_TYPE_HEADER)) {
+                               int lock_axis = -1;
+                               if (ELEM(data->region->alignment, RGN_ALIGN_LEFT, RGN_ALIGN_RIGHT)) {
+                                       lock_axis = 0;
+                               }
+                               else if (ELEM(data->region->alignment, RGN_ALIGN_TOP, RGN_ALIGN_BOTTOM)) {
+                                       lock_axis = 1;
+                               }
+                               if (lock_axis != -1) {
+                                       drag_info->xy_lock[lock_axis] = true;
+                                       drag_info->is_xy_lock_init = true;
+                               }
+                       }
                }
                else
 #endif
@@ -1798,223 +1844,6 @@ static bool ui_but_drag_init(
 
 /* ********************** linklines *********************** */
 
-static void ui_linkline_remove_active(uiBlock *block)
-{
-       uiBut *but;
-       uiLink *link;
-       uiLinkLine *line, *nline;
-       int a, b;
-
-       for (but = block->buttons.first; but; but = but->next) {
-               if (but->type == UI_BTYPE_LINK && but->link) {
-                       for (line = but->link->lines.first; line; line = nline) {
-                               nline = line->next;
-
-                               if (line->flag & UI_SELECT) {
-                                       BLI_remlink(&but->link->lines, line);
-
-                                       link = line->from->link;
-
-                                       /* are there more pointers allowed? */
-                                       if (link->ppoin) {
-
-                                               if (*(link->totlink) == 1) {
-                                                       *(link->totlink) = 0;
-                                                       MEM_freeN(*(link->ppoin));
-                                                       *(link->ppoin) = NULL;
-                                               }
-                                               else {
-                                                       b = 0;
-                                                       for (a = 0; a < (*(link->totlink)); a++) {
-
-                                                               if ((*(link->ppoin))[a] != line->to->poin) {
-                                                                       (*(link->ppoin))[b] = (*(link->ppoin))[a];
-                                                                       b++;
-                                                               }
-                                                       }
-                                                       (*(link->totlink))--;
-                                               }
-                                       }
-                                       else {
-                                               *(link->poin) = NULL;
-                                       }
-
-                                       MEM_freeN(line);
-                               }
-                       }
-               }
-       }
-}
-
-
-static uiLinkLine *ui_but_find_link(uiBut *from, uiBut *to)
-{
-       uiLinkLine *line;
-       uiLink *link;
-
-       link = from->link;
-       if (link) {
-               for (line = link->lines.first; line; line = line->next) {
-                       if (line->from == from && line->to == to) {
-                               return line;
-                       }
-               }
-       }
-       return NULL;
-}
-
-/* XXX BAD BAD HACK, fixme later **************** */
-/* Try to add an AND Controller between the sensor and the actuator logic bricks and to connect them all */
-static void ui_but_smart_controller_add(bContext *C, uiBut *from, uiBut *to)
-{
-       Object *ob = NULL;
-       bSensor *sens_iter;
-       bActuator *act_to, *act_iter;
-       bController *cont;
-       bController ***sens_from_links;
-       uiBut *tmp_but;
-
-       uiLink *link = from->link;
-
-       PointerRNA props_ptr, object_ptr;
-
-       if (link->ppoin)
-               sens_from_links = (bController ***)(link->ppoin);
-       else return;
-
-       act_to = (bActuator *)(to->poin);
-
-       /* (1) get the object */
-       CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects)
-       {
-               for (sens_iter = ob_iter->sensors.first; sens_iter; sens_iter = sens_iter->next) {
-                       if (&(sens_iter->links) == sens_from_links) {
-                               ob = ob_iter;
-                               break;
-                       }
-               }
-               if (ob) break;
-       } CTX_DATA_END;
-
-       if (!ob) return;
-
-       /* (2) check if the sensor and the actuator are from the same object */
-       for (act_iter = ob->actuators.first; act_iter; act_iter = (bActuator *)act_iter->next) {
-               if (act_iter == act_to)
-                       break;
-       }
-
-       /* only works if the sensor and the actuator are from the same object */
-       if (!act_iter) return;
-
-       /* in case the linked controller is not the active one */
-       RNA_pointer_create((ID *)ob, &RNA_Object, ob, &object_ptr);
-
-       WM_operator_properties_create(&props_ptr, "LOGIC_OT_controller_add");
-       RNA_string_set(&props_ptr, "object", ob->id.name + 2);
-
-       /* (3) add a new controller */
-       if (WM_operator_name_call(C, "LOGIC_OT_controller_add", WM_OP_EXEC_DEFAULT, &props_ptr) & OPERATOR_FINISHED) {
-               cont = (bController *)ob->controllers.last;
-               /* Quick fix to make sure we always have an AND controller.
-                * It might be nicer to make sure the operator gives us the right one though... */
-               cont->type = CONT_LOGIC_AND;
-
-               /* (4) link the sensor->controller->actuator */
-               tmp_but = MEM_callocN(sizeof(uiBut), "uiBut");
-               UI_but_link_set(
-                       tmp_but, (void **)&cont, (void ***)&(cont->links),
-                       &cont->totlinks, from->link->tocode, (int)to->hardmin);
-               tmp_but->hardmin = from->link->tocode;
-               tmp_but->poin = (char *)cont;
-
-               tmp_but->type = UI_BTYPE_INLINK;
-               ui_but_link_add(C, from, tmp_but);
-
-               tmp_but->type = UI_BTYPE_LINK;
-               ui_but_link_add(C, tmp_but, to);
-
-               /* (5) garbage collection */
-               MEM_freeN(tmp_but->link);
-               MEM_freeN(tmp_but);
-       }
-       WM_operator_properties_free(&props_ptr);
-}
-
-static void ui_but_link_add(bContext *C, uiBut *from, uiBut *to)
-{
-       /* in 'from' we have to add a link to 'to' */
-       uiLink *link;
-       uiLinkLine *line;
-       void **oldppoin;
-       int a;
-
-       if ((line = ui_but_find_link(from, to))) {
-               line->flag |= UI_SELECT;
-               ui_linkline_remove_active(from->block);
-               return;
-       }
-
-       if (from->type == UI_BTYPE_INLINK && to->type == UI_BTYPE_INLINK) {
-               return;
-       }
-       else if (from->type == UI_BTYPE_LINK && to->type == UI_BTYPE_INLINK) {
-               if (from->link->tocode != (int)to->hardmin) {
-                       ui_but_smart_controller_add(C, from, to);
-                       return;
-               }
-       }
-       else if (from->type == UI_BTYPE_INLINK && to->type == UI_BTYPE_LINK) {
-               if (to->link->tocode == (int)from->hardmin) {
-                       return;
-               }
-       }
-
-       link = from->link;
-
-       /* are there more pointers allowed? */
-       if (link->ppoin) {
-               oldppoin = *(link->ppoin);
-
-               (*(link->totlink))++;
-               *(link->ppoin) = MEM_callocN(*(link->totlink) * sizeof(void *), "new link");
-
-               for (a = 0; a < (*(link->totlink)) - 1; a++) {
-                       (*(link->ppoin))[a] = oldppoin[a];
-               }
-               (*(link->ppoin))[a] = to->poin;
-
-               if (oldppoin) MEM_freeN(oldppoin);
-       }
-       else {
-               *(link->poin) = to->poin;
-       }
-
-}
-
-
-static void ui_apply_but_LINK(bContext *C, uiBut *but, uiHandleButtonData *data)
-{
-       ARegion *ar = CTX_wm_region(C);
-       uiBut *bt;
-
-       for (bt = but->block->buttons.first; bt; bt = bt->next) {
-               if (ui_but_contains_point_px(ar, bt, but->linkto[0] + ar->winrct.xmin, but->linkto[1] + ar->winrct.ymin) )
-                       break;
-       }
-       if (bt && bt != but) {
-               if (!ELEM(bt->type, UI_BTYPE_LINK, UI_BTYPE_INLINK) || !ELEM(but->type, UI_BTYPE_LINK, UI_BTYPE_INLINK))
-                       return;
-
-               if (but->type == UI_BTYPE_LINK) ui_but_link_add(C, but, bt);
-               else ui_but_link_add(C, bt, but);
-
-               ui_apply_but_func(C, but);
-               data->retval = but->retval;
-       }
-       data->applied = true;
-}
-
 static void ui_apply_but_IMAGE(bContext *C, uiBut *but, uiHandleButtonData *data)
 {
        ui_apply_but_func(C, but);
@@ -2133,6 +1962,9 @@ static void ui_apply_but(bContext *C, uiBlock *block, uiBut *but, uiHandleButton
                case UI_BTYPE_LISTROW:
                        ui_apply_but_ROW(C, block, but, data);
                        break;
+               case UI_BTYPE_TAB:
+                       ui_apply_but_TAB(C, but, data);
+                       break;
                case UI_BTYPE_SCROLL:
                case UI_BTYPE_GRIP:
                case UI_BTYPE_NUM:
@@ -2168,10 +2000,6 @@ static void ui_apply_but(bContext *C, uiBlock *block, uiBut *but, uiHandleButton
                case UI_BTYPE_HOTKEY_EVENT:
                        ui_apply_but_BUT(C, but, data);
                        break;
-               case UI_BTYPE_LINK:
-               case UI_BTYPE_INLINK:
-                       ui_apply_but_LINK(C, but, data);
-                       break;
                case UI_BTYPE_IMAGE:
                        ui_apply_but_IMAGE(C, but, data);
                        break;
@@ -2231,7 +2059,7 @@ static void ui_but_drop(bContext *C, const wmEvent *event, uiBut *but, uiHandleB
                if (wmd->type == WM_DRAG_ID) {
                        /* align these types with UI_but_active_drop_name */
                        if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
-                               ID *id = (ID *)wmd->poin;
+                               ID *id = WM_drag_ID(wmd, 0);
 
                                button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
 
@@ -2251,278 +2079,404 @@ static void ui_but_drop(bContext *C, const wmEvent *event, uiBut *but, uiHandleB
 
 /* ******************* copy and paste ********************  */
 
-/* c = copy, v = paste */
-static void ui_but_copy_paste(bContext *C, uiBut *but, uiHandleButtonData *data, const char mode, const bool copy_array)
+static bool ui_but_contains_password(uiBut *but)
 {
-       int buf_paste_len = 0;
-       const char *buf_paste = "";
-       bool buf_paste_alloc = false;
-       bool show_report = false;  /* use to display errors parsing paste input */
+       return but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD);
+}
 
-       BLI_assert((but->flag & UI_BUT_DISABLED) == 0); /* caller should check */
+static void ui_but_get_pasted_text_from_clipboard(char **buf_paste, int *buf_len)
+{
+       char *text;
+       int length;
+       /* get only first line even if the clipboard contains multiple lines */
+       text = WM_clipboard_text_get_firstline(false, &length);
 
-       if (mode == 'c') {
-               /* disallow copying from any passwords */
-               if (but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD)) {
-                       return;
-               }
+       if (text) {
+               *buf_paste = text;
+               *buf_len = length;
+       }
+       else {
+               *buf_paste = MEM_callocN(sizeof(char), __func__);
+               *buf_len = 0;
        }
+}
 
-       if (mode == 'v') {
-               /* extract first line from clipboard in case of multi-line copies */
-               const char *buf_paste_test;
+static int get_but_property_array_length(uiBut *but)
+{
+       return RNA_property_array_length(&but->rnapoin, but->rnaprop);
+}
 
-               buf_paste_test = WM_clipboard_text_get_firstline(false, &buf_paste_len);
-               if (buf_paste_test) {
-                       buf_paste = buf_paste_test;
-                       buf_paste_alloc = true;
-               }
+static void ui_but_set_float_array(bContext *C, uiBut *but, uiHandleButtonData *data, float *values, int array_length)
+{
+       button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
+
+       for (int i = 0; i < array_length; i++) {
+               RNA_property_float_set_index(&but->rnapoin, but->rnaprop, i, values[i]);
        }
+       if (data) data->value = values[but->rnaindex];
 
-       /* No return from here down */
+       button_activate_state(C, but, BUTTON_STATE_EXIT);
+}
 
+static void float_array_to_string(float *values, int array_length, char *output, int output_len_max)
+{
+       /* to avoid buffer overflow attacks; numbers are quite arbitrary */
+       BLI_assert(output_len_max > 15);
+       output_len_max -= 10;
 
-       /* numeric value */
-       if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER)) {
+       int current_index = 0;
+       output[current_index] = '[';
+       current_index++;
 
-               if (but->poin == NULL && but->rnapoin.data == NULL) {
-                       /* pass */
+       for (int i = 0; i < array_length; i++) {
+               int length = BLI_snprintf(output + current_index, output_len_max - current_index, "%f", values[i]);
+               current_index += length;
+
+               if (i < array_length - 1) {
+                       if (current_index < output_len_max) {
+                               output[current_index + 0] = ',';
+                               output[current_index + 1] = ' ';
+                               current_index += 2;
+                       }
                }
-               else if (copy_array && but->rnapoin.data && but->rnaprop &&
-                        ELEM(RNA_property_subtype(but->rnaprop), PROP_COLOR, PROP_TRANSLATION, PROP_DIRECTION,
-                             PROP_VELOCITY, PROP_ACCELERATION, PROP_MATRIX, PROP_EULER, PROP_QUATERNION, PROP_AXISANGLE,
-                             PROP_XYZ, PROP_XYZ_LENGTH, PROP_COLOR_GAMMA, PROP_COORDS))
-               {
-                       float values[4];
-                       int array_length = RNA_property_array_length(&but->rnapoin, but->rnaprop);
+       }
 
-                       if (mode == 'c') {
-                               char buf_copy[UI_MAX_DRAW_STR];
+       output[current_index + 0] = ']';
+       output[current_index + 1] = '\0';
+}
 
-                               if (array_length == 4) {
-                                       values[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3);
-                               }
-                               else {
-                                       values[3] = 0.0f;
-                               }
-                               ui_but_v3_get(but, values);
+static void ui_but_copy_numeric_array(uiBut *but, char *output, int output_len_max)
+{
+       int array_length = get_but_property_array_length(but);
+       float *values = alloca(array_length * sizeof(float));
+       RNA_property_float_get_array(&but->rnapoin, but->rnaprop, values);
+       float_array_to_string(values, array_length, output, output_len_max);
+}
 
-                               BLI_snprintf(buf_copy, sizeof(buf_copy), "[%f, %f, %f, %f]", values[0], values[1], values[2], values[3]);
-                               WM_clipboard_text_set(buf_copy, 0);
-                       }
-                       else {
-                               if (sscanf(buf_paste, "[%f, %f, %f, %f]", &values[0], &values[1], &values[2], &values[3]) >= array_length) {
-                                       button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
+static bool parse_float_array(char *text, float *values, int expected_length)
+{
+       /* can parse max 4 floats for now */
+       BLI_assert(0 <= expected_length && expected_length <= 4);
 
-                                       ui_but_v3_set(but, values);
-                                       if (but->rnaprop && array_length == 4) {
-                                               RNA_property_float_set_index(&but->rnapoin, but->rnaprop, 3, values[3]);
-                                       }
-                                       data->value = values[but->rnaindex];
+       float v[5];
+       int actual_length = sscanf(text, "[%f, %f, %f, %f, %f]", &v[0], &v[1], &v[2], &v[3], &v[4]);
 
-                                       button_activate_state(C, but, BUTTON_STATE_EXIT);
-                               }
-                               else {
-                                       WM_report(RPT_ERROR, "Paste expected 4 numbers, formatted: '[n, n, n, n]'");
-                                       show_report = true;
-                               }
-                       }
-               }
-               else if (mode == 'c') {
-                       /* Get many decimal places, then strip trailing zeros.
-                        * note: too high values start to give strange results */
-                       char buf_copy[UI_MAX_DRAW_STR];
-                       ui_but_string_get_ex(but, buf_copy, sizeof(buf_copy), UI_PRECISION_FLOAT_MAX, false, NULL);
-                       BLI_str_rstrip_float_zero(buf_copy, '\0');
+       if (actual_length == expected_length) {
+               memcpy(values, v, sizeof(float) * expected_length);
+               return true;
+       }
+       else {
+               return false;
+       }
+}
 
-                       WM_clipboard_text_set(buf_copy, 0);
-               }
-               else {
-                       double val;
+static void ui_but_paste_numeric_array(bContext *C, uiBut *but, uiHandleButtonData *data, char *buf_paste)
+{
+       int array_length = get_but_property_array_length(but);
+       if (array_length > 4) {
+               // not supported for now
+               return;
+       }
 
-                       if (ui_but_string_set_eval_num(C, but, buf_paste, &val)) {
-                               button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
-                               data->value = val;
-                               ui_but_string_set(C, but, buf_paste);
-                               button_activate_state(C, but, BUTTON_STATE_EXIT);
-                       }
-                       else {
-                               /* evaluating will report errors */
-                               show_report = true;
-                       }
-               }
+       float *values = alloca(sizeof(float) * array_length);
+
+       if (parse_float_array(buf_paste, values, array_length)) {
+               ui_but_set_float_array(C, but, data, values, array_length);
        }
+       else {
+               WM_report(RPT_ERROR, "Expected an array of numbers: [n, n, ...]");
+       }
+}
 
-       /* NORMAL button */
-       else if (but->type == UI_BTYPE_UNITVEC) {
-               float xyz[3];
+static void ui_but_copy_numeric_value(uiBut *but, char *output, int output_len_max)
+{
+       /* Get many decimal places, then strip trailing zeros.
+        * note: too high values start to give strange results */
+       ui_but_string_get_ex(but, output, output_len_max, UI_PRECISION_FLOAT_MAX, false, NULL);
+       BLI_str_rstrip_float_zero(output, '\0');
+}
 
-               if (but->poin == NULL && but->rnapoin.data == NULL) {
-                       /* pass */
-               }
-               else if (mode == 'c') {
-                       char buf_copy[UI_MAX_DRAW_STR];
-                       ui_but_v3_get(but, xyz);
-                       BLI_snprintf(buf_copy, sizeof(buf_copy), "[%f, %f, %f]", xyz[0], xyz[1], xyz[2]);
-                       WM_clipboard_text_set(buf_copy, 0);
+static void ui_but_paste_numeric_value(bContext *C, uiBut *but, uiHandleButtonData *data, char *buf_paste)
+{
+       double value;
+
+       if (ui_but_string_set_eval_num(C, but, buf_paste, &value)) {
+               button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
+               data->value = value;
+               ui_but_string_set(C, but, buf_paste);
+               button_activate_state(C, but, BUTTON_STATE_EXIT);
+       }
+       else {
+               WM_report(RPT_ERROR, "Expected a number");
+       }
+}
+
+static bool ui_but_has_array_value(uiBut *but)
+{
+       return (but->rnapoin.data && but->rnaprop &&
+               ELEM(RNA_property_subtype(but->rnaprop), PROP_COLOR, PROP_TRANSLATION, PROP_DIRECTION,
+                    PROP_VELOCITY, PROP_ACCELERATION, PROP_MATRIX, PROP_EULER, PROP_QUATERNION, PROP_AXISANGLE,
+                    PROP_XYZ, PROP_XYZ_LENGTH, PROP_COLOR_GAMMA, PROP_COORDS));
+}
+
+static void ui_but_paste_normalized_vector(bContext *C, uiBut *but, char *buf_paste)
+{
+       float xyz[3];
+       if (parse_float_array(buf_paste, xyz, 3)) {
+               if (normalize_v3(xyz) == 0.0f) {
+                       /* better set Z up then have a zero vector */
+                       xyz[2] = 1.0;
                }
-               else {
-                       if (sscanf(buf_paste, "[%f, %f, %f]", &xyz[0], &xyz[1], &xyz[2]) == 3) {
-                               if (normalize_v3(xyz) == 0.0f) {
-                                       /* better set Z up then have a zero vector */
-                                       xyz[2] = 1.0;
-                               }
-                               button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
-                               ui_but_v3_set(but, xyz);
-                               button_activate_state(C, but, BUTTON_STATE_EXIT);
-                       }
-                       else {
-                               WM_report(RPT_ERROR, "Paste expected 3 numbers, formatted: '[n, n, n]'");
-                               show_report = true;
+               ui_but_set_float_array(C, but, NULL, xyz, 3);
+       }
+       else {
+               WM_report(RPT_ERROR, "Paste expected 3 numbers, formatted: '[n, n, n]'");
+       }
+}
+
+static void ui_but_copy_color(uiBut *but, char *output, int output_len_max)
+{
+       float rgba[4];
+
+       if (but->rnaprop && get_but_property_array_length(but) == 4)
+               rgba[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3);
+       else
+               rgba[3] = 1.0f;
+
+       ui_but_v3_get(but, rgba);
+
+       /* convert to linear color to do compatible copy between gamma and non-gamma */
+       if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA)
+               srgb_to_linearrgb_v3_v3(rgba, rgba);
+
+       float_array_to_string(rgba, 4, output, output_len_max);
+}
+
+static void ui_but_paste_color(bContext *C, uiBut *but, char *buf_paste)
+{
+       float rgba[4];
+       if (parse_float_array(buf_paste, rgba, 4)) {
+               if (but->rnaprop) {
+                       /* Assume linear colors in buffer. */
+                       if (RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
+                               linearrgb_to_srgb_v3_v3(rgba, rgba);
                        }
+
+                       /* Some color properties are RGB, not RGBA. */
+                       int array_len = get_but_property_array_length(but);
+                       BLI_assert(ELEM(array_len, 3, 4));
+                       ui_but_set_float_array(C, but, NULL, rgba, array_len);
                }
        }
+       else {
+               WM_report(RPT_ERROR, "Paste expected 4 numbers, formatted: '[n, n, n, n]'");
+       }
+}
 
+static void ui_but_copy_text(uiBut *but, char *output, int output_len_max)
+{
+       ui_but_string_get(but, output, output_len_max);
+}
 
-       /* RGB triple */
-       else if (but->type == UI_BTYPE_COLOR) {
-               float rgba[4];
+static void ui_but_paste_text(bContext *C, uiBut *but, uiHandleButtonData *data, char *buf_paste)
+{
+       button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
+       ui_textedit_string_set(but, but->active, buf_paste);
 
-               if (but->poin == NULL && but->rnapoin.data == NULL) {
-                       /* pass */
-               }
-               else if (mode == 'c') {
-                       char buf_copy[UI_MAX_DRAW_STR];
+       if (but->type == UI_BTYPE_SEARCH_MENU) {
+               but->changed = true;
+               ui_searchbox_update(C, data->searchbox, but, true);
+       }
 
-                       if (but->rnaprop && RNA_property_array_length(&but->rnapoin, but->rnaprop) == 4)
-                               rgba[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3);
-                       else
-                               rgba[3] = 1.0f;
+       button_activate_state(C, but, BUTTON_STATE_EXIT);
+}
 
-                       ui_but_v3_get(but, rgba);
-                       /* convert to linear color to do compatible copy between gamma and non-gamma */
-                       if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA)
-                               srgb_to_linearrgb_v3_v3(rgba, rgba);
+static void ui_but_copy_colorband(uiBut *but)
+{
+       if (but->poin != NULL) {
+               memcpy(&but_copypaste_coba, but->poin, sizeof(ColorBand));
+       }
+}
 
-                       BLI_snprintf(buf_copy, sizeof(buf_copy), "[%f, %f, %f, %f]", rgba[0], rgba[1], rgba[2], rgba[3]);
-                       WM_clipboard_text_set(buf_copy, 0);
+static void ui_but_paste_colorband(bContext *C, uiBut *but, uiHandleButtonData *data)
+{
+       if (but_copypaste_coba.tot != 0) {
+               if (!but->poin)
+                       but->poin = MEM_callocN(sizeof(ColorBand), "colorband");
 
-               }
-               else {
-                       if (sscanf(buf_paste, "[%f, %f, %f, %f]", &rgba[0], &rgba[1], &rgba[2], &rgba[3]) == 4) {
-                               /* assume linear colors in buffer */
-                               if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA)
-                                       linearrgb_to_srgb_v3_v3(rgba, rgba);
+               button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
+               memcpy(data->coba, &but_copypaste_coba, sizeof(ColorBand));
+               button_activate_state(C, but, BUTTON_STATE_EXIT);
+       }
+}
 
-                               button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
-                               ui_but_v3_set(but, rgba);
-                               if (but->rnaprop && RNA_property_array_length(&but->rnapoin, but->rnaprop) == 4)
-                                       RNA_property_float_set_index(&but->rnapoin, but->rnaprop, 3, rgba[3]);
+static void ui_but_copy_curvemapping(uiBut *but)
+{
+       if (but->poin != NULL) {
+               but_copypaste_curve_alive = true;
+               curvemapping_free_data(&but_copypaste_curve);
+               curvemapping_copy_data(&but_copypaste_curve, (CurveMapping *) but->poin);
+       }
+}
 
-                               button_activate_state(C, but, BUTTON_STATE_EXIT);
+static void ui_but_paste_curvemapping(bContext *C, uiBut *but)
+{
+       if (but_copypaste_curve_alive) {
+               if (!but->poin)
+                       but->poin = MEM_callocN(sizeof(CurveMapping), "curvemapping");
+
+               button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
+               curvemapping_free_data((CurveMapping *) but->poin);
+               curvemapping_copy_data((CurveMapping *) but->poin, &but_copypaste_curve);
+               button_activate_state(C, but, BUTTON_STATE_EXIT);
+       }
+}
+
+static void ui_but_copy_operator(bContext *C, uiBut *but, char *output, int output_len_max)
+{
+       PointerRNA *opptr;
+       opptr = UI_but_operator_ptr_get(but);
+
+       char *str;
+       str = WM_operator_pystring_ex(C, NULL, false, true, but->optype, opptr);
+       BLI_strncpy(output, str, output_len_max);
+       MEM_freeN(str);
+}
+
+static void ui_but_copy_menu(uiBut *but, char *output, int output_len_max)
+{
+       MenuType *mt = UI_but_menutype_get(but);
+       if (mt) {
+               BLI_snprintf(output, output_len_max, "bpy.ops.wm.call_menu(name=\"%s\")", mt->idname);
+       }
+}
+
+static void ui_but_copy(bContext *C, uiBut *but, const bool copy_array)
+{
+       if (ui_but_contains_password(but)) {
+               return;
+       }
+
+       /* Arbitrary large value (allow for paths: 'PATH_MAX') */
+       char buf[4096] = {0};
+       const int buf_max_len = sizeof(buf);
+
+       /* Left false for copying internal data (color-band for eg). */
+       bool is_buf_set = false;
+
+       bool has_required_data = !(but->poin == NULL && but->rnapoin.data == NULL);
+
+       switch (but->type) {
+               case UI_BTYPE_NUM:
+               case UI_BTYPE_NUM_SLIDER:
+                       if (!has_required_data) break;
+                       if (copy_array && ui_but_has_array_value(but)) {
+                               ui_but_copy_numeric_array(but, buf, buf_max_len);
                        }
                        else {
-                               WM_report(RPT_ERROR, "Paste expected 4 numbers, formatted: '[n, n, n, n]'");
-                               show_report = true;
+                               ui_but_copy_numeric_value(but, buf, buf_max_len);
                        }
-               }
-       }
+                       is_buf_set = true;
+                       break;
 
-       /* text/string and ID data */
-       else if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
-               uiHandleButtonData *active_data = but->active;
+               case UI_BTYPE_UNITVEC:
+                       if (!has_required_data) break;
+                       ui_but_copy_numeric_array(but, buf, buf_max_len);
+                       is_buf_set = true;
+                       break;
 
-               if (but->poin == NULL && but->rnapoin.data == NULL) {
-                       /* pass */
-               }
-               else if (mode == 'c') {
-                       button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
-                       WM_clipboard_text_set(active_data->str, 0);
-                       active_data->cancel = true;
-                       button_activate_state(C, but, BUTTON_STATE_EXIT);
-               }
-               else {
-                       button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
+               case UI_BTYPE_COLOR:
+                       if (!has_required_data) break;
+                       ui_but_copy_color(but, buf, buf_max_len);
+                       is_buf_set = true;
+                       break;
+
+               case UI_BTYPE_TEXT:
+               case UI_BTYPE_SEARCH_MENU:
+                       if (!has_required_data) break;
+                       ui_but_copy_text(but, buf, buf_max_len);
+                       is_buf_set = true;
+                       break;
 
-                       ui_textedit_string_set(but, active_data, buf_paste);
+               case UI_BTYPE_COLORBAND:
+                       ui_but_copy_colorband(but);
+                       break;
 
-                       if (but->type == UI_BTYPE_SEARCH_MENU) {
-                               /* else uiSearchboxData.active member is not updated [#26856] */
-                               but->changed = true;
-                               ui_searchbox_update(C, data->searchbox, but, true);
-                       }
-                       button_activate_state(C, but, BUTTON_STATE_EXIT);
-               }
+               case UI_BTYPE_CURVE:
+                       ui_but_copy_curvemapping(but);
+                       break;
+
+               case UI_BTYPE_BUT:
+                       ui_but_copy_operator(C, but, buf, buf_max_len);
+                       is_buf_set = true;
+                       break;
+
+               case UI_BTYPE_MENU:
+               case UI_BTYPE_PULLDOWN:
+                       ui_but_copy_menu(but, buf, buf_max_len);
+                       is_buf_set = true;
+                       break;
+
+               default:
+                       break;
        }
-       /* colorband (not supported by system clipboard) */
-       else if (but->type == UI_BTYPE_COLORBAND) {
-               if (mode == 'c') {
-                       if (but->poin != NULL) {
-                               memcpy(&but_copypaste_coba, but->poin, sizeof(ColorBand));
-                       }
-               }
-               else {
-                       if (but_copypaste_coba.tot != 0) {
-                               if (!but->poin)
-                                       but->poin = MEM_callocN(sizeof(ColorBand), "colorband");
 
-                               button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
-                               memcpy(data->coba, &but_copypaste_coba, sizeof(ColorBand));
-                               button_activate_state(C, but, BUTTON_STATE_EXIT);
+       if (is_buf_set) {
+               WM_clipboard_text_set(buf, 0);
+       }
+}
+
+static void ui_but_paste(bContext *C, uiBut *but, uiHandleButtonData *data, const bool paste_array)
+{
+       BLI_assert((but->flag & UI_BUT_DISABLED) == 0); /* caller should check */
+
+       int buf_paste_len = 0;
+       char *buf_paste;
+       ui_but_get_pasted_text_from_clipboard(&buf_paste, &buf_paste_len);
+
+       bool has_required_data = !(but->poin == NULL && but->rnapoin.data == NULL);
+
+       switch (but->type) {
+               case UI_BTYPE_NUM:
+               case UI_BTYPE_NUM_SLIDER:
+                       if (!has_required_data) break;
+                       if (paste_array && ui_but_has_array_value(but)) {
+                               ui_but_paste_numeric_array(C, but, data, buf_paste);
                        }
-               }
-       }
-       else if (but->type == UI_BTYPE_CURVE) {
-               if (mode == 'c') {
-                       if (but->poin != NULL) {
-                               but_copypaste_curve_alive = true;
-                               curvemapping_free_data(&but_copypaste_curve);
-                               curvemapping_copy_data(&but_copypaste_curve, (CurveMapping *) but->poin);
+                       else {
+                               ui_but_paste_numeric_value(C, but, data, buf_paste);
                        }
-               }
-               else {
-                       if (but_copypaste_curve_alive) {
-                               if (!but->poin)
-                                       but->poin = MEM_callocN(sizeof(CurveMapping), "curvemapping");
+                       break;
 
-                               button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
-                               curvemapping_free_data((CurveMapping *) but->poin);
-                               curvemapping_copy_data((CurveMapping *) but->poin, &but_copypaste_curve);
-                               button_activate_state(C, but, BUTTON_STATE_EXIT);
-                       }
-               }
-       }
-       /* operator button (any type) */
-       else if (but->optype) {
-               if (mode == 'c') {
-                       PointerRNA *opptr;
-                       char *str;
-                       opptr = UI_but_operator_ptr_get(but); /* allocated when needed, the button owns it */
+               case UI_BTYPE_UNITVEC:
+                       if (!has_required_data) break;
+                       ui_but_paste_normalized_vector(C, but, buf_paste);
+                       break;
 
-                       str = WM_operator_pystring_ex(C, NULL, false, true, but->optype, opptr);
+               case UI_BTYPE_COLOR:
+                       if (!has_required_data) break;
+                       ui_but_paste_color(C, but, buf_paste);
+                       break;
 
-                       WM_clipboard_text_set(str, 0);
+               case UI_BTYPE_TEXT:
+               case UI_BTYPE_SEARCH_MENU:
+                       if (!has_required_data) break;
+                       ui_but_paste_text(C, but, data, buf_paste);
+                       break;
 
-                       MEM_freeN(str);
-               }
-       }
-       /* menu (any type) */
-       else if (ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_PULLDOWN)) {
-               MenuType *mt = UI_but_menutype_get(but);
-               if (mt) {
-                       char str[32 + sizeof(mt->idname)];
-                       BLI_snprintf(str, sizeof(str), "bpy.ops.wm.call_menu(name=\"%s\")", mt->idname);
-                       WM_clipboard_text_set(str, 0);
-               }
-       }
+               case UI_BTYPE_COLORBAND:
+                       ui_but_paste_colorband(C, but, data);
+                       break;
 
-       if (buf_paste_alloc) {
-               MEM_freeN((void *)buf_paste);
-       }
+               case UI_BTYPE_CURVE:
+                       ui_but_paste_curvemapping(C, but);
+                       break;
 
-       if (show_report) {
-               WM_report_banner_show();
+               default:
+                       break;
        }
+
+       MEM_freeN((void *)buf_paste);
 }
 
 /**
@@ -2978,7 +2932,7 @@ static int ui_textedit_autocomplete(bContext *C, uiBut *but, uiHandleButtonData
 enum {
        UI_TEXTEDIT_PASTE = 1,
        UI_TEXTEDIT_COPY,
-       UI_TEXTEDIT_CUT
+       UI_TEXTEDIT_CUT,
 };
 
 static bool ui_textedit_copypaste(uiBut *but, uiHandleButtonData *data, const int mode)
@@ -3184,6 +3138,9 @@ static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data)
 
                        ui_searchbox_free(C, data->searchbox);
                        data->searchbox = NULL;
+                       if (but->free_search_arg) {
+                               MEM_SAFE_FREE(but->search_arg);
+                       }
                }
 
                but->editstr = NULL;
@@ -3665,6 +3622,7 @@ static void ui_block_open_begin(bContext *C, uiBut *but, uiHandleButtonData *dat
        uiBlockCreateFunc func = NULL;
        uiBlockHandleCreateFunc handlefunc = NULL;
        uiMenuCreateFunc menufunc = NULL;
+       uiMenuCreateFunc popoverfunc = NULL;
        void *arg = NULL;
 
        switch (but->type) {
@@ -3684,6 +3642,11 @@ static void ui_block_open_begin(bContext *C, uiBut *but, uiHandleButtonData *dat
                        menufunc = but->menu_create_func;
                        arg = but->poin;
                        break;
+               case UI_BTYPE_POPOVER:
+                       BLI_assert(but->menu_create_func);
+                       popoverfunc = but->menu_create_func;
+                       arg = but->poin;
+                       break;
                case UI_BTYPE_COLOR:
                        ui_but_v3_get(but, data->origvec);
                        copy_v3_v3(data->vec, data->origvec);
@@ -3708,6 +3671,11 @@ static void ui_block_open_begin(bContext *C, uiBut *but, uiHandleButtonData *dat
                if (but->block->handle)
                        data->menu->popup = but->block->handle->popup;
        }
+       else if (popoverfunc) {
+               data->menu = ui_popover_panel_create(C, data->region, but, popoverfunc, arg);
+               if (but->block->handle)
+                       data->menu->popup = but->block->handle->popup;
+       }
 
 #ifdef USE_ALLSELECT
        {
@@ -3888,8 +3856,7 @@ static int ui_do_but_HOTKEYEVT(
                ED_region_tag_redraw(data->region);
 
                if (event->val == KM_PRESS) {
-                       if (ISHOTKEY(event->type)) {
-
+                       if (ISHOTKEY(event->type) && (event->type != ESCKEY)) {
                                if (WM_key_event_string(event->type, false)[0])
                                        ui_but_value_set(but, event->type);
                                else
@@ -3955,6 +3922,51 @@ static bool ui_but_is_mouse_over_icon_extra(const ARegion *region, uiBut *but, c
        return BLI_rcti_isect_pt(&icon_rect, x, y);
 }
 
+static int ui_do_but_TAB(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
+{
+       const bool is_property = (but->rnaprop != NULL);
+
+#ifdef USE_DRAG_TOGGLE
+       if (is_property) {
+               int retval;
+               if (ui_do_but_ANY_drag_toggle(C, but, data, event, &retval)) {
+                       return retval;
+               }
+       }
+#endif
+
+       if (data->state == BUTTON_STATE_HIGHLIGHT) {
+               const int rna_type = but->rnaprop ? RNA_property_type(but->rnaprop) : 0;
+
+               if (is_property &&
+                   ELEM(rna_type, PROP_POINTER, PROP_STRING) &&
+                   (but->custom_data != NULL) &&
+                   (event->type == LEFTMOUSE) &&
+                   ((event->val == KM_DBL_CLICK) || event->ctrl))
+               {
+                       button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
+                       return WM_UI_HANDLER_BREAK;
+               }
+               else if (ELEM(event->type, LEFTMOUSE, PADENTER, RETKEY)) {
+                       int event_val = (is_property) ? KM_PRESS : KM_CLICK;
+                       if (event->val == event_val) {
+                               button_activate_state(C, but, BUTTON_STATE_EXIT);
+                               return WM_UI_HANDLER_BREAK;
+                       }
+               }
+       }
+       else if (data->state == BUTTON_STATE_TEXT_EDITING) {
+               ui_do_but_textedit(C, block, but, data, event);
+               return WM_UI_HANDLER_BREAK;
+       }
+       else if (data->state == BUTTON_STATE_TEXT_SELECTING) {
+               ui_do_but_textedit_select(C, block, but, data, event);
+               return WM_UI_HANDLER_BREAK;
+       }
+
+       return WM_UI_HANDLER_CONTINUE;
+}
+
 static int ui_do_but_TEX(
         bContext *C, uiBlock *block, uiBut *but,
         uiHandleButtonData *data, const wmEvent *event)
@@ -4037,7 +4049,23 @@ static int ui_do_but_TOG(
 #endif
 
        if (data->state == BUTTON_STATE_HIGHLIGHT) {
-               if (ELEM(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->val == KM_PRESS) {
+               bool do_activate = false;
+               if (ELEM(event->type, PADENTER, RETKEY)) {
+                       if (event->val == KM_PRESS) {
+                               do_activate = true;
+                       }
+               }
+               else if (event->type == LEFTMOUSE) {
+                       if (ui_block_is_menu(but->block)) {
+                               /* Behave like other menu items. */
+                               do_activate = (event->val == KM_RELEASE);
+                       }
+                       else {
+                               do_activate = (event->val == KM_PRESS);
+                       }
+               }
+
+               if (do_activate) {
 #if 0          /* UNUSED */
                        data->togdual = event->ctrl;
                        data->togonly = !event->shift;
@@ -4384,6 +4412,54 @@ static bool ui_numedit_but_NUM(
        return changed;
 }
 
+static void ui_numedit_set_active(uiBut *but)
+{
+       int oldflag = but->drawflag;
+       but->drawflag &= ~(UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT);
+
+       uiHandleButtonData *data = but->active;
+       if (!data) {
+               return;
+       }
+
+       /* Ignore once we start dragging. */
+       if (data->dragchange == false) {
+               const  float handle_width = min_ff(BLI_rctf_size_x(&but->rect) / 3, BLI_rctf_size_y(&but->rect) * 0.7f);
+               /* we can click on the side arrows to increment/decrement,
+                * or click inside to edit the value directly */
+               int mx = data->window->eventstate->x;
+               int my = data->window->eventstate->y;
+               ui_window_to_block(data->region, but->block, &mx, &my);
+
+               if (mx < (but->rect.xmin + handle_width)) {
+                       but->drawflag |= UI_BUT_ACTIVE_LEFT;
+               }
+               else if (mx > (but->rect.xmax - handle_width)) {
+                       but->drawflag |= UI_BUT_ACTIVE_RIGHT;
+               }
+       }
+
+       /* Don't change the cursor once pressed. */
+       if ((but->flag & UI_SELECT) == 0) {
+               if ((but->drawflag & (UI_BUT_ACTIVE_LEFT)) || (but->drawflag & (UI_BUT_ACTIVE_RIGHT))) {
+                       if (data->changed_cursor) {
+                               WM_cursor_modal_restore(data->window);
+                               data->changed_cursor = false;
+                       }
+               }
+               else {
+                       if (data->changed_cursor == false) {
+                               WM_cursor_modal_set(data->window, CURSOR_X_MOVE);
+                               data->changed_cursor = true;
+                       }
+               }
+       }
+
+       if (but->drawflag != oldflag) {
+               ED_region_tag_redraw(data->region);
+       }
+}
+
 static int ui_do_but_NUM(
         bContext *C, uiBlock *block, uiBut *but,
         uiHandleButtonData *data, const wmEvent *event)
@@ -4397,6 +4473,7 @@ static int ui_do_but_NUM(
        my = screen_my = event->y;
 
        ui_window_to_block(data->region, block, &mx, &my);
+       ui_numedit_set_active(but);
 
        if (data->state == BUTTON_STATE_HIGHLIGHT) {
                int type = event->type, val = event->val;
@@ -4412,10 +4489,14 @@ static int ui_do_but_NUM(
                }
                else if (type == WHEELDOWNMOUSE && event->ctrl) {
                        mx = but->rect.xmin;
+                       but->drawflag &= ~UI_BUT_ACTIVE_RIGHT;
+                       but->drawflag |= UI_BUT_ACTIVE_LEFT;
                        click = 1;
                }
                else if (type == WHEELUPMOUSE && event->ctrl) {
                        mx = but->rect.xmax;
+                       but->drawflag &= ~UI_BUT_ACTIVE_LEFT;
+                       but->drawflag |= UI_BUT_ACTIVE_RIGHT;
                        click = 1;
                }
                else if (event->val == KM_PRESS) {
@@ -4508,16 +4589,13 @@ static int ui_do_but_NUM(
                /* we can click on the side arrows to increment/decrement,
                 * or click inside to edit the value directly */
                float tempf, softmin, softmax;
-               float handlewidth;
                int temp;
 
                softmin = but->softmin;
                softmax = but->softmax;
 
-               handlewidth = min_ff(BLI_rctf_size_x(&but->rect) / 3, BLI_rctf_size_y(&but->rect));
-
                if (!ui_but_is_float(but)) {
-                       if (mx < (but->rect.xmin + handlewidth)) {
+                       if (but->drawflag & UI_BUT_ACTIVE_LEFT) {
                                button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
 
                                temp = (int)data->value - 1;
@@ -4528,7 +4606,7 @@ static int ui_do_but_NUM(
 
                                button_activate_state(C, but, BUTTON_STATE_EXIT);
                        }
-                       else if (mx > (but->rect.xmax - handlewidth)) {
+                       else if (but->drawflag & UI_BUT_ACTIVE_RIGHT) {
                                button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
 
                                temp = (int)data->value + 1;
@@ -4544,7 +4622,7 @@ static int ui_do_but_NUM(
                        }
                }
                else {
-                       if (mx < (but->rect.xmin + handlewidth)) {
+                       if (but->drawflag & UI_BUT_ACTIVE_LEFT) {
                                button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
 
                                tempf = (float)data->value - (UI_PRECISION_FLOAT_SCALE * but->a1);
@@ -4553,7 +4631,7 @@ static int ui_do_but_NUM(
 
                                button_activate_state(C, but, BUTTON_STATE_EXIT);
                        }
-                       else if (mx > but->rect.xmax - handlewidth) {
+                       else if (but->drawflag & UI_BUT_ACTIVE_RIGHT) {
                                button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
 
                                tempf = (float)data->value + (UI_PRECISION_FLOAT_SCALE * but->a1);
@@ -5280,7 +5358,8 @@ static int ui_do_but_COLOR(
                                if (!event->ctrl) {
                                        float color[3];
                                        Scene *scene = CTX_data_scene(C);
-                                       Paint *paint = BKE_paint_get_active(scene);
+                                       ViewLayer *view_layer = CTX_data_view_layer(C);
+                                       Paint *paint = BKE_paint_get_active(scene, view_layer);
                                        Brush *brush = BKE_paint_brush(paint);
 
                                        if (brush->flag & BRUSH_USE_GRADIENT) {
@@ -5288,7 +5367,7 @@ static int ui_do_but_COLOR(
 
                                                if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
                                                        RNA_property_float_get_array(&but->rnapoin, but->rnaprop, target);
-                                                       ui_block_cm_to_scene_linear_v3(but->block, target);
+                                                       IMB_colormanagement_srgb_to_scene_linear_v3(target);
                                                }
                                                else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
                                                        RNA_property_float_get_array(&but->rnapoin, but->rnaprop, target);
@@ -5301,7 +5380,7 @@ static int ui_do_but_COLOR(
                                                }
                                                else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
                                                        RNA_property_float_get_array(&but->rnapoin, but->rnaprop, color);
-                                                       ui_block_cm_to_display_space_v3(but->block, color);
+                                                       IMB_colormanagement_scene_linear_to_srgb_v3(color);
                                                        BKE_brush_color_set(scene, brush, color);
                                                }
                                        }
@@ -5415,7 +5494,6 @@ static bool ui_numedit_but_HSVCUBE(
        float x, y;
        float mx_fl, my_fl;
        bool changed = true;
-       bool use_display_colorspace = ui_but_is_colorpicker_display_space(but);
 
        ui_mouse_scale_warp(data, mx, my, &mx_fl, &my_fl, shift);
 
@@ -5429,9 +5507,7 @@ static bool ui_numedit_but_HSVCUBE(
 #endif
 
        ui_but_v3_get(but, rgb);
-
-       if (use_display_colorspace)
-               ui_block_cm_to_display_space_v3(but->block, rgb);
+       ui_scene_linear_to_color_picker_space(but, rgb);
 
        ui_rgb_to_color_picker_HSVCUBE_compat_v(but, rgb, hsv);
 
@@ -5444,8 +5520,7 @@ static bool ui_numedit_but_HSVCUBE(
 
                /* calculate original hsv again */
                copy_v3_v3(rgb, data->origvec);
-               if (use_display_colorspace)
-                       ui_block_cm_to_display_space_v3(but->block, rgb);
+               ui_scene_linear_to_color_picker_space(but, rgb);
 
                copy_v3_v3(hsvo, hsv);
 
@@ -5493,9 +5568,6 @@ static bool ui_numedit_but_HSVCUBE(
                {
                        /* vertical 'value' strip */
                        float min = but->softmin, max = but->softmax;
-                       if (use_display_colorspace) {
-                               ui_block_cm_to_display_space_range(but->block, &min, &max);
-                       }
                        /* exception only for value strip - use the range set in but->min/max */
                        hsv[2] = y * (max - min) + min;
                        break;
@@ -5512,9 +5584,7 @@ static bool ui_numedit_but_HSVCUBE(
        }
 
        ui_color_picker_to_rgb_HSVCUBE_v(but, hsv, rgb);
-
-       if (use_display_colorspace)
-               ui_block_cm_to_scene_linear_v3(but->block, rgb);
+       ui_color_picker_to_scene_linear_space(but, rgb);
 
        /* clamp because with color conversion we can exceed range [#34295] */
        if (but->a1 == UI_GRAD_V_ALT) {
@@ -5540,13 +5610,9 @@ static void ui_ndofedit_but_HSVCUBE(
        const float hsv_v_max = max_ff(hsv[2], but->softmax);
        float rgb[3];
        float sensitivity = (shift ? 0.15f : 0.3f) * ndof->dt;
-       bool use_display_colorspace = ui_but_is_colorpicker_display_space(but);
 
        ui_but_v3_get(but, rgb);
-
-       if (use_display_colorspace)
-               ui_block_cm_to_display_space_v3(but->block, rgb);
-
+       ui_scene_linear_to_color_picker_space(but, rgb);
        ui_rgb_to_color_picker_HSVCUBE_compat_v(but, rgb, hsv);
 
        switch ((int)but->a1) {
@@ -5595,9 +5661,7 @@ static void ui_ndofedit_but_HSVCUBE(
        hsv_clamp_v(hsv, hsv_v_max);
 
        ui_color_picker_to_rgb_HSVCUBE_v(but, hsv, rgb);
-
-       if (use_display_colorspace)
-               ui_block_cm_to_scene_linear_v3(but->block, rgb);
+       ui_color_picker_to_scene_linear_space(but, rgb);
 
        copy_v3_v3(data->vec, rgb);
        ui_but_v3_set(but, data->vec);
@@ -5712,7 +5776,6 @@ static bool ui_numedit_but_HSVCIRCLE(
        float rgb[3];
        ColorPicker *cpicker = but->custom_data;
        float *hsv = cpicker->color_data;
-       bool use_display_colorspace = ui_but_is_colorpicker_display_space(but);
 
        ui_mouse_scale_warp(data, mx, my, &mx_fl, &my_fl, shift);
 
@@ -5735,9 +5798,7 @@ static bool ui_numedit_but_HSVCIRCLE(
        BLI_rcti_rctf_copy(&rect, &but->rect);
 
        ui_but_v3_get(but, rgb);
-       if (use_display_colorspace)
-               ui_block_cm_to_display_space_v3(but->block, rgb);
-
+       ui_scene_linear_to_color_picker_space(but, rgb);
        ui_rgb_to_color_picker_compat_v(rgb, hsv);
 
        /* exception, when using color wheel in 'locked' value state:
@@ -5759,9 +5820,7 @@ static bool ui_numedit_but_HSVCIRCLE(
                /* calculate original hsv again */
                copy_v3_v3(hsvo, hsv);
                copy_v3_v3(rgbo, data->origvec);
-               if (use_display_colorspace)
-                       ui_block_cm_to_display_space_v3(but->block, rgbo);
-
+               ui_scene_linear_to_color_picker_space(but, rgbo);
                ui_rgb_to_color_picker_compat_v(rgbo, hsvo);
 
                /* and original position */
@@ -5787,9 +5846,7 @@ static bool ui_numedit_but_HSVCIRCLE(
                normalize_v3_length(rgb, but->a2);
        }
 
-       if (use_display_colorspace)
-               ui_block_cm_to_scene_linear_v3(but->block, rgb);
-
+       ui_color_picker_to_scene_linear_space(but, rgb);
        ui_but_v3_set(but, rgb);
 
        data->draglastx = mx;
@@ -5806,14 +5863,12 @@ static void ui_ndofedit_but_HSVCIRCLE(
 {
        ColorPicker *cpicker = but->custom_data;
        float *hsv = cpicker->color_data;
-       bool use_display_colorspace = ui_but_is_colorpicker_display_space(but);
        float rgb[3];
        float phi, r /*, sqr */ /* UNUSED */, v[2];
        float sensitivity = (shift ? 0.06f : 0.3f) * ndof->dt;
 
        ui_but_v3_get(but, rgb);
-       if (use_display_colorspace)
-               ui_block_cm_to_display_space_v3(but->block, rgb);
+       ui_scene_linear_to_color_picker_space(but, rgb);
        ui_rgb_to_color_picker_compat_v(rgb, hsv);
 
        /* Convert current color on hue/sat disc to circular coordinates phi, r */
@@ -5864,9 +5919,7 @@ static void ui_ndofedit_but_HSVCIRCLE(
                normalize_v3_length(data->vec, but->a2);
        }
 
-       if (use_display_colorspace)
-               ui_block_cm_to_scene_linear_v3(but->block, data->vec);
-
+       ui_color_picker_to_scene_linear_space(but, data->vec);
        ui_but_v3_set(but, data->vec);
 }
 #endif /* WITH_INPUT_NDOF */
@@ -6198,6 +6251,7 @@ static int ui_do_but_CURVE(
        int mx, my, a;
        bool changed = false;
        Scene *scene = CTX_data_scene(C);
+       ViewLayer *view_layer = CTX_data_view_layer(C);
 
        mx = event->x;
        my = event->y;
@@ -6327,7 +6381,7 @@ static int ui_do_but_CURVE(
                                }
                                else {
                                        curvemapping_changed(cumap, true);  /* remove doubles */
-                                       BKE_paint_invalidate_cursor_overlay(scene, cumap);
+                                       BKE_paint_invalidate_cursor_overlay(scene, view_layer, cumap);
                                }
                        }
 
@@ -6493,35 +6547,6 @@ static int ui_do_but_WAVEFORM(
        return WM_UI_HANDLER_CONTINUE;
 }
 
-static int ui_do_but_LINK(
-        bContext *C, uiBut *but,
-        uiHandleButtonData *data, const wmEvent *event)
-{
-       VECCOPY2D(but->linkto, event->mval);
-
-       if (data->state == BUTTON_STATE_HIGHLIGHT) {
-               if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
-                       button_activate_state(C, but, BUTTON_STATE_WAIT_RELEASE);
-                       return WM_UI_HANDLER_BREAK;
-               }
-               else if (event->type == LEFTMOUSE && but->block->handle) {
-                       button_activate_state(C, but, BUTTON_STATE_EXIT);
-                       return WM_UI_HANDLER_BREAK;
-               }
-       }
-       else if (data->state == BUTTON_STATE_WAIT_RELEASE) {
-
-               if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
-                       if (!(but->flag & UI_SELECT))
-                               data->cancel = true;
-                       button_activate_state(C, but, BUTTON_STATE_EXIT);
-                       return WM_UI_HANDLER_BREAK;
-               }
-       }
-
-       return WM_UI_HANDLER_CONTINUE;
-}
-
 static bool ui_numedit_but_TRACKPREVIEW(
         bContext *C, uiBut *but, uiHandleButtonData *data,
         int mx, int my,
@@ -6614,8 +6639,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
        data = but->active;
        retval = WM_UI_HANDLER_CONTINUE;
 
-       if (but->flag & UI_BUT_DISABLED)
-               return WM_UI_HANDLER_CONTINUE;
+       bool is_disabled = but->flag & UI_BUT_DISABLED;
 
        /* if but->pointype is set, but->poin should be too */
        BLI_assert(!but->pointype || but->poin);
@@ -6624,35 +6648,55 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
         * keymaps are handled using operators (see #ED_keymap_ui). */
 
        if ((data->state == BUTTON_STATE_HIGHLIGHT) || (event->type == EVT_DROP)) {
-               /* handle copy-paste */
-               if (ELEM(event->type, CKEY, VKEY) && event->val == KM_PRESS &&
-                   IS_EVENT_MOD(event, ctrl, oskey) && !event->shift)
-               {
-                       /* Specific handling for listrows, we try to find their overlapping tex button. */
-                       if (but->type == UI_BTYPE_LISTROW) {
-                               uiBut *labelbut = ui_but_list_row_text_activate(C, but, data, event, BUTTON_ACTIVATE_OVER);
-                               if (labelbut) {
-                                       but = labelbut;
-                                       data = but->active;
-                               }
+
+               /* handle copy and paste */
+               bool is_press_ctrl_but_no_shift = event->val == KM_PRESS && IS_EVENT_MOD(event, ctrl, oskey) && !event->shift;
+               bool do_copy = event->type == CKEY && is_press_ctrl_but_no_shift;
+               bool do_paste = event->type == VKEY && is_press_ctrl_but_no_shift;
+
+               /* Specific handling for listrows, we try to find their overlapping tex button. */
+               if ((do_copy || do_paste) && but->type == UI_BTYPE_LISTROW) {
+                       uiBut *labelbut = ui_but_list_row_text_activate(C, but, data, event, BUTTON_ACTIVATE_OVER);
+                       if (labelbut) {
+                               but = labelbut;
+                               data = but->active;
                        }
-                       ui_but_copy_paste(C, but, data, (event->type == CKEY) ? 'c' : 'v', event->alt);
-                       return WM_UI_HANDLER_BREAK;
                }
-               /* handle drop */
-               else if (event->type == EVT_DROP) {
-                       ui_but_drop(C, event, but, data);
+
+               /* do copy first, because it is the only allowed operator when disabled */
+               if (do_copy) {
+                       ui_but_copy(C, but, event->alt);
+                       return WM_UI_HANDLER_BREAK;
                }
+
                /* handle menu */
-               else if ((event->type == RIGHTMOUSE) &&
-                        !IS_EVENT_MOD(event, shift, ctrl, alt, oskey) &&
-                        (event->val == KM_PRESS))
+               if ((event->type == RIGHTMOUSE) &&
+                   !IS_EVENT_MOD(event, shift, ctrl, alt, oskey) &&
+                   (event->val == KM_PRESS))
                {
                        /* RMB has two options now */
                        if (ui_popup_context_menu_for_button(C, but)) {
                                return WM_UI_HANDLER_BREAK;
                        }
                }
+
+               if (is_disabled) {
+                       return WM_UI_HANDLER_CONTINUE;
+               }
+
+               if (do_paste) {
+                       ui_but_paste(C, but, data, event->alt);
+                       return WM_UI_HANDLER_BREAK;
+               }
+
+               /* handle drop */
+               if (event->type == EVT_DROP) {
+                       ui_but_drop(C, event, but, data);
+               }
+       }
+
+       if (but->flag & UI_BUT_DISABLED) {
+               return WM_UI_HANDLER_CONTINUE;
        }
 
        switch (but->type) {
@@ -6665,6 +6709,9 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
                case UI_BTYPE_HOTKEY_EVENT:
                        retval = ui_do_but_HOTKEYEVT(C, but, data, event);
                        break;
+               case UI_BTYPE_TAB:
+                       retval = ui_do_but_TAB(C, block, but, data, event);
+                       break;
                case UI_BTYPE_BUT_TOGGLE:
                case UI_BTYPE_TOGGLE:
                case UI_BTYPE_ICON_TOGGLE:
@@ -6722,6 +6769,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
                        retval = ui_do_but_TEX(C, block, but, data, event);
                        break;
                case UI_BTYPE_MENU:
+               case UI_BTYPE_POPOVER:
                case UI_BTYPE_BLOCK:
                case UI_BTYPE_PULLDOWN:
                        retval = ui_do_but_BLOCK(C, but, data, event);
@@ -6750,10 +6798,6 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
                case UI_BTYPE_HSVCIRCLE:
                        retval = ui_do_but_HSVCIRCLE(C, block, but, data, event);
                        break;
-               case UI_BTYPE_LINK:
-               case UI_BTYPE_INLINK:
-                       retval = ui_do_but_LINK(C, but, data, event);
-                       break;
                case UI_BTYPE_TRACK_PREVIEW:
                        retval = ui_do_but_TRACKPREVIEW(C, block, but, data, event);
                        break;
@@ -6761,6 +6805,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
                        /* quiet warnings for unhandled types */
                case UI_BTYPE_SEPR:
                case UI_BTYPE_SEPR_LINE:
+               case UI_BTYPE_SEPR_SPACER:
                case UI_BTYPE_EXTRA:
                        break;
        }
@@ -6943,12 +6988,11 @@ bool ui_but_is_editing(uiBut *but)
 /* is called by notifier */
 void UI_screen_free_active_but(const bContext *C, bScreen *screen)
 {
-       ScrArea *sa = screen->areabase.first;
+       wmWindow *win = CTX_wm_window(C);
 
-       for (; sa; sa = sa->next) {
-               ARegion *ar = sa->regionbase.first;
-               for (; ar; ar = ar->next) {
-                       uiBut *but = ui_but_find_active_in_region(ar);
+       ED_screen_areas_iter(win, screen, area) {
+               for (ARegion *region = area->regionbase.first; region; region = region->next) {
+                       uiBut *but = ui_but_find_active_in_region(region);
                        if (but) {
                                uiHandleButtonData *data = but->active;
 
@@ -7034,7 +7078,7 @@ static bool ui_region_contains_point_px(ARegion *ar, int x, int y)
                ui_window_to_region(ar, &mx, &my);
 
                /* check if in the rect */
-               if (!BLI_rcti_isect_pt(&v2d->mask, mx, my)) {
+               if (!BLI_rcti_isect_pt(&v2d->mask, mx, my) || UI_view2d_mouse_in_scrollers(ar, &ar->v2d, x, y)) {
                        return false;
                }
        }
@@ -7189,7 +7233,7 @@ void UI_but_tooltip_refresh(bContext *C, uiBut *but)
 {
        uiHandleButtonData *data = but->active;
        if (data) {
-               bScreen *sc = data->window->screen;
+               bScreen *sc = WM_window_get_active_screen(data->window);
                if (sc->tool_tip && sc->tool_tip->region) {
                        WM_tooltip_refresh(C, data->window);
                }
@@ -7215,12 +7259,21 @@ void UI_but_tooltip_timer_remove(bContext *C, uiBut *but)
        }
 }
 
-static ARegion *ui_but_tooltip_init(bContext *C, ARegion *ar, bool *r_exit_on_event)
+static ARegion *ui_but_tooltip_init(
+        bContext *C, ARegion *ar,
+        int *pass, double *r_pass_delay, bool *r_exit_on_event)
 {
+       bool is_label = false;
+       if (*pass == 1) {
+               is_label = true;
+               (*pass)--;
+               (*r_pass_delay) = UI_TOOLTIP_DELAY - UI_TOOLTIP_DELAY_LABEL;
+       }
+
        uiBut *but = UI_region_active_but_get(ar);
        *r_exit_on_event = false;
        if (but) {
-               return UI_tooltip_create_from_button(C, ar, but);
+               return UI_tooltip_create_from_button(C, ar, but, is_label);
        }
        return NULL;
 }
@@ -7235,7 +7288,15 @@ static void button_tooltip_timer_reset(bContext *C, uiBut *but)
        if ((U.flag & USER_TOOLTIPS) || (data->tooltip_force)) {
                if (!but->block->tooltipdisabled) {
                        if (!wm->drags.first) {
-                               WM_tooltip_timer_init(C, data->window, data->region, ui_but_tooltip_init);
+                               bool is_label = UI_but_has_tooltip_label(but);
+                               double delay = is_label ? UI_TOOLTIP_DELAY_LABEL : UI_TOOLTIP_DELAY;
+                               WM_tooltip_timer_init_ex(C, data->window, data->region, ui_but_tooltip_init, delay);
+                               if (is_label) {
+                                       bScreen *sc = WM_window_get_active_screen(data->window);
+                                       if (sc->tool_tip) {
+                                               sc->tool_tip->pass = 1;
+                                       }
+                               }
                        }
                }
        }
@@ -7256,7 +7317,7 @@ static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState s
                button_tooltip_timer_reset(C, but);
 
                /* automatic open pulldown block timer */
-               if (ELEM(but->type, UI_BTYPE_BLOCK, UI_BTYPE_PULLDOWN)) {
+               if (ELEM(but->type, UI_BTYPE_BLOCK, UI_BTYPE_PULLDOWN, UI_BTYPE_POPOVER)) {
                        if (data->used_mouse && !data->autoopentimer) {
                                int time;
 
@@ -7419,6 +7480,7 @@ static void button_activate_init(bContext *C, ARegion *ar, uiBut *but, uiButtonA
 
        /* activate button */
        but->flag |= UI_ACTIVE;
+
        but->active = data;
 
        /* we disable auto_open in the block after a threshold, because we still
@@ -7466,6 +7528,22 @@ static void button_activate_init(bContext *C, ARegion *ar, uiBut *but, uiButtonA
                const bool horizontal = (BLI_rctf_size_x(&but->rect) < BLI_rctf_size_y(&but->rect));
                WM_cursor_modal_set(data->window, horizontal ? CURSOR_X_MOVE : CURSOR_Y_MOVE);
        }
+       else if (but->type == UI_BTYPE_NUM) {
+               ui_numedit_set_active(but);
+       }
+
+       if (UI_but_has_tooltip_label(but)) {
+               /* Show a label for this button. */
+               bScreen *sc = WM_window_get_active_screen(data->window);
+               if ((PIL_check_seconds_timer() - WM_tooltip_time_closed()) < 0.1) {
+                       WM_tooltip_immediate_init(
+                               C, CTX_wm_window(C), ar,
+                               ui_but_tooltip_init);
+                       if (sc->tool_tip) {
+                               sc->tool_tip->pass = 1;
+                       }
+               }
+       }
 }
 
 static void button_activate_exit(
@@ -7559,8 +7637,13 @@ static void button_activate_exit(
        ui_selectcontext_end(but, &data->select_others);
 #endif
 
-       /* redraw (data is but->active!) */
+       if (data->changed_cursor) {
+               WM_cursor_modal_restore(data->window);
+       }
+
+       /* redraw and refresh (for popups) */
        ED_region_tag_redraw(data->region);
+       ED_region_tag_refresh_ui(data->region);
 
        /* clean up button */
        if (but->active) {
@@ -7746,6 +7829,11 @@ void UI_context_update_anim_flag(const bContext *C)
                for (block = ar->uiblocks.first; block; block = block->next) {
                        for (but = block->buttons.first; but; but = but->next) {
                                ui_but_anim_flag(but, (scene) ? scene->r.cfra : 0.0f);
+                               ui_but_override_flag(but);
+                               if (UI_but_is_decorator(but)) {
+                                       ui_but_anim_decorate_update_from_flag(but);
+                               }
+
                                ED_region_tag_redraw(ar);
 
                                if (but->active) {
@@ -7933,8 +8021,29 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but)
                        case EVT_BUT_CANCEL:
                                data->cancel = true;
                                button_activate_state(C, but, BUTTON_STATE_EXIT);
-                               retval = WM_UI_HANDLER_CONTINUE;
                                break;
+#ifdef USE_UI_POPOVER_ONCE
+                       case LEFTMOUSE:
+                       {
+                               if (event->val == KM_RELEASE) {
+                                       if (block->flag & UI_BLOCK_POPOVER_ONCE) {
+                                               if (!(but->flag & UI_BUT_DISABLED)) {
+                                                       if (ui_but_is_popover_once_compat(but)) {
+                                                               data->cancel = false;
+                                                               button_activate_state(C, but, BUTTON_STATE_EXIT);
+                                                               retval = WM_UI_HANDLER_BREAK;
+                                                               block->handle->menuretval = UI_RETURN_OK;
+                                                       }
+                                                       else if (ui_but_is_editable_as_text(but)) {
+                                                               ui_handle_button_activate(C, ar, but, BUTTON_ACTIVATE_TEXT_EDITING);
+                                                               retval = WM_UI_HANDLER_BREAK;
+                                                       }
+                                               }
+                                       }
+                               }
+                               break;
+                       }
+#endif
                        case MOUSEMOVE:
                        {
                                uiBut *but_other = ui_but_find_mouse_over(ar, event);
@@ -7965,16 +8074,16 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but)
                        }
                        case TIMER:
                        {
-                               /* handle menu auto open timer */
+                               /* Handle menu auto open timer. */
                                if (event->customdata == data->autoopentimer) {
                                        WM_event_remove_timer(data->wm, data->window, data->autoopentimer);
                                        data->autoopentimer = NULL;
 
-                                       if (ui_but_contains_point_px(ar, but, event->x, event->y))
+                                       if (ui_but_contains_point_px(ar, but, event->x, event->y) || but->active) {
                                                button_activate_state(C, but, BUTTON_STATE_MENU_OPEN);
+                                       }
                                }
 
-                               retval = WM_UI_HANDLER_CONTINUE;
                                break;
                        }
                        /* XXX hardcoded keymap check... but anyway,
@@ -7986,10 +8095,11 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but)
                                UI_but_tooltip_timer_remove(C, but);
                                ATTR_FALLTHROUGH;
                        default:
-                               /* handle button type specific events */
-                               retval = ui_do_button(C, block, but, event);
                                break;
                }
+
+               /* handle button type specific events */
+               retval = ui_do_button(C, block, but, event);
        }
        else if (data->state == BUTTON_STATE_WAIT_RELEASE) {
                switch (event->type) {
@@ -8018,43 +8128,38 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but)
                                break;
                        }
                        case MOUSEMOVE:
-                               if (ELEM(but->type, UI_BTYPE_LINK, UI_BTYPE_INLINK)) {
-                                       but->flag |= UI_SELECT;
-                                       ui_do_button(C, block, but, event);
-                                       ED_region_tag_redraw(ar);
-                               }
-                               else {
-                                       /* deselect the button when moving the mouse away */
-                                       /* also de-activate for buttons that only show higlights */
-                                       if (ui_but_contains_point_px(ar, but, event->x, event->y)) {
-
-                                               /* Drag on a hold button (used in the toolbar) now opens it immediately. */
-                                               if (data->hold_action_timer) {
-                                                       if (but->flag & UI_SELECT) {
-                                                               if ((abs(event->x - event->prevx)) > 2 ||
-                                                                   (abs(event->y - event->prevy)) > 2)
-                                                               {
-                                                                       WM_event_remove_timer(data->wm, data->window, data->hold_action_timer);
-                                                                       data->hold_action_timer = WM_event_add_timer(data->wm, data->window, TIMER, 0.0f);
-                                                               }
+                       {
+                               /* deselect the button when moving the mouse away */
+                               /* also de-activate for buttons that only show highlights */
+                               if (ui_but_contains_point_px(ar, but, event->x, event->y)) {
+
+                                       /* Drag on a hold button (used in the toolbar) now opens it immediately. */
+                                       if (data->hold_action_timer) {
+                                               if (but->flag & UI_SELECT) {
+                                                       if ((abs(event->x - event->prevx)) > 2 ||
+                                                           (abs(event->y - event->prevy)) > 2)
+                                                       {
+                                                               WM_event_remove_timer(data->wm, data->window, data->hold_action_timer);
+                                                               data->hold_action_timer = WM_event_add_timer(data->wm, data->window, TIMER, 0.0f);
                                                        }
                                                }
+                                       }
 
-                                               if (!(but->flag & UI_SELECT)) {
-                                                       but->flag |= (UI_SELECT | UI_ACTIVE);
-                                                       data->cancel = false;
-                                                       ED_region_tag_redraw(data->region);
-                                               }
+                                       if (!(but->flag & UI_SELECT)) {
+                                               but->flag |= (UI_SELECT | UI_ACTIVE);
+                                               data->cancel = false;
+                                               ED_region_tag_redraw(data->region);
                                        }
-                                       else {
-                                               if (but->flag & UI_SELECT) {
-                                                       but->flag &= ~(UI_SELECT | UI_ACTIVE);
-                                                       data->cancel = true;
-                                                       ED_region_tag_redraw(data->region);
-                                               }
+                               }
+                               else {
+                                       if (but->flag & UI_SELECT) {
+                                               but->flag &= ~(UI_SELECT | UI_ACTIVE);
+                                               data->cancel = true;
+                                               ED_region_tag_redraw(data->region);
                                        }
                                }
                                break;
+                       }
                        default:
                                /* otherwise catch mouse release event */
                                ui_do_button(C, block, but, event);
@@ -8099,6 +8204,17 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but)
                                }
                                break;
                        }
+                       case RIGHTMOUSE:
+                       {
+                               if (event->val == KM_PRESS) {
+                                       uiBut *bt = ui_but_find_mouse_over(ar, event);
+                                       if (bt && bt->active == data) {
+                                               button_activate_state(C, bt, BUTTON_STATE_HIGHLIGHT);
+                                       }
+                               }
+                               break;
+                       }
+
                }
 
                ui_do_button(C, block, but, event);
@@ -8287,13 +8403,8 @@ static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *ar,
        }
 
        if (redraw) {
-               if (listbox->block->flag & UI_BLOCK_POPUP) {
-                       /* popups need special refreshing */
-                       ED_region_tag_refresh_ui(ar);
-               }
-               else {
-                       ED_region_tag_redraw(ar);
-               }
+               ED_region_tag_redraw(ar);
+               ED_region_tag_refresh_ui(ar);
        }
 
        return retval;
@@ -8354,7 +8465,7 @@ static void ui_handle_button_return_submenu(bContext *C, const wmEvent *event, u
 /* ************************* menu handling *******************************/
 
 /**
- * Function used to prevent losing the open menu when using nested pulldowns,
+ * Function used to prevent losing the open menu when using nested pull-downs,
  * when moving mouse towards the pulldown menu over other buttons that could
  * steal the highlight from the current button, only checks:
  *
@@ -8365,7 +8476,7 @@ static void ui_handle_button_return_submenu(bContext *C, const wmEvent *event, u
 
 static void ui_mouse_motion_towards_init_ex(uiPopupBlockHandle *menu, const int xy[2], const bool force)
 {
-       BLI_assert(((uiBlock *)menu->region->uiblocks.first)->flag & UI_BLOCK_MOVEMOUSE_QUIT);
+       BLI_assert(((uiBlock *)menu->region->uiblocks.first)->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER));
 
        if (!menu->dotowards || force) {
                menu->dotowards = true;
@@ -8400,7 +8511,7 @@ static bool ui_mouse_motion_towards_check(
        const float margin = MENU_TOWARDS_MARGIN;
        rctf rect_px;
 
-       BLI_assert(block->flag & UI_BLOCK_MOVEMOUSE_QUIT);
+       BLI_assert(block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER));
 
 
        /* annoying fix for [#36269], this is a bit odd but in fact works quite well
@@ -8552,6 +8663,9 @@ static int ui_menu_scroll(ARegion *ar, uiBlock *block, int my, uiBut *to_bt)
                                dy = block->rect.ymin - ymin + UI_MENU_SCROLL_PAD;
                }
 
+               /* remember scroll offset for refreshes */
+               block->handle->scrolloffset += dy;
+
                /* apply scroll offset */
                for (bt = block->buttons.first; bt; bt = bt->next) {
                        bt->rect.ymin += dy;
@@ -8654,7 +8768,7 @@ float ui_block_calc_pie_segment(uiBlock *block, const float event_xy[2])
 
        len = normalize_v2_v2(block->pie_data.pie_dir, seg2);
 
-       if (len < U.pie_menu_threshold * U.pixelsize)
+       if (len < U.pie_menu_threshold * U.dpi_fac)
                block->pie_data.flags |= UI_PIE_INVALID_DIR;
        else
                block->pie_data.flags &= ~UI_PIE_INVALID_DIR;
@@ -8704,7 +8818,7 @@ static int ui_handle_menu_event(
 
                                add_v2_v2v2_int(menu->popup_create_vars.event_xy, menu->popup_create_vars.event_xy, mdiff);
 
-                               ui_popup_translate(C, ar, mdiff);
+                               ui_popup_translate(ar, mdiff);
                        }
 
                        return retval;
@@ -8713,9 +8827,9 @@ static int ui_handle_menu_event(
 #endif
 
        if (but && button_modal_state(but->active->state)) {
-               if (block->flag & UI_BLOCK_MOVEMOUSE_QUIT) {
+               if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) {
                        /* if a button is activated modal, always reset the start mouse
-                        * position of the towards mechanism to avoid loosing focus,
+                        * position of the towards mechanism to avoid losing focus,
                         * and don't handle events */
                        ui_mouse_motion_towards_reinit(menu, &event->x);
                }
@@ -8727,7 +8841,7 @@ static int ui_handle_menu_event(
        else {
                /* for ui_mouse_motion_towards_block */
                if (event->type == MOUSEMOVE) {
-                       if (block->flag & UI_BLOCK_MOVEMOUSE_QUIT) {
+                       if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) {
                                ui_mouse_motion_towards_init(menu, &event->x);
                        }
 
@@ -8747,7 +8861,29 @@ static int ui_handle_menu_event(
 
                        switch (event->type) {
 
-                               /* closing sublevels of pulldowns */
+                               /* Closing sub-levels of pull-downs.
+                                *
+                                * The actual event is handled by the button under the cursor.
+                                * This is done so we can right click on menu items even when they have sub-menus open. */
+                               case RIGHTMOUSE:
+                                       if (inside == false) {
+                                               if (event->val == KM_PRESS && (block->flag & UI_BLOCK_LOOP)) {
+                                                       if (block->saferct.first) {
+                                                               /* Currently right clicking on a top level pull-down (typically in the header)
+                                                                * just closes the menu and doesn't support immediately handling the RMB event.
+                                                                *
+                                                                * To support we would need UI_RETURN_OUT_PARENT to be handled by
+                                                                * top-level buttons, not just menus. Note that this isn't very important
+                                                                * since it's easy to manually close these menus by clicking on them. */
+                                                               menu->menuretval = (level > 0) ? UI_RETURN_OUT_PARENT : UI_RETURN_OUT;
+
+                                                       }
+                                               }
+                                               retval = WM_UI_HANDLER_BREAK;
+                                       }
+                                       break;
+
+                               /* Closing sub-levels of pull-downs. */
                                case LEFTARROWKEY:
                                        if (event->val == KM_PRESS && (block->flag & UI_BLOCK_LOOP))
                                                if (block->saferct.first)
@@ -8756,7 +8892,7 @@ static int ui_handle_menu_event(
                                        retval = WM_UI_HANDLER_BREAK;
                                        break;
 
-                               /* opening sublevels of pulldowns */
+                               /* Opening sub-levels of pull-downs. */
                                case RIGHTARROWKEY:
                                        if (event->val == KM_PRESS && (block->flag & UI_BLOCK_LOOP)) {
 
@@ -8947,7 +9083,7 @@ static int ui_handle_menu_event(
                                                                        ui_handle_button_activate(C, ar, but, BUTTON_ACTIVATE_APPLY);
                                                                }
                                                                else if (ELEM(but->type, UI_BTYPE_BLOCK, UI_BTYPE_PULLDOWN)) {
-                                                                       /* open submenus (like right arrow key) */
+                                                                       /* open sub-menus (like right arrow key) */
                                                                        ui_handle_button_activate(C, ar, but, BUTTON_ACTIVATE_OPEN);
                                                                }
                                                                else if (but->type == UI_BTYPE_MENU) {
@@ -8971,13 +9107,16 @@ static int ui_handle_menu_event(
 
                /* here we check return conditions for menus */
                if (block->flag & UI_BLOCK_LOOP) {
-                       /* if we click outside the block, verify if we clicked on the
+                       /* If we click outside the block, verify if we clicked on the
                         * button that opened us, otherwise we need to close,
                         *
                         * note that there is an exception for root level menus and
                         * popups which you can click again to close.
+                        *
+                        * Events handled above may have already set the return value,
+                        * don't overwrite them, see: T61015.
                         */
-                       if (inside == 0) {
+                       if ((inside == 0) && (menu->menuretval == 0)) {
                                uiSafetyRct *saferct = block->saferct.first;
 
                                if (ELEM(event->type, LEFTMOUSE, MIDDLEMOUSE, RIGHTMOUSE)) {
@@ -9013,12 +9152,12 @@ static int ui_handle_menu_event(
                        }
 #ifdef USE_KEYNAV_LIMIT
                        else if ((event->type == MOUSEMOVE) && ui_mouse_motion_keynav_test(&menu->keynav_state, event)) {
-                               /* don't handle the mousemove if we're using key-navigation */
+                               /* Don't handle the mouse-move if we're using key-navigation. */
                                retval = WM_UI_HANDLER_BREAK;
                        }
 #endif
                        else if (event->type == ESCKEY && event->val == KM_PRESS) {
-                               /* esc cancels this and all preceding menus */
+                               /* Escape cancels this and all preceding menus. */
                                menu->menuretval = UI_RETURN_CANCEL;
                        }
                        else if (ELEM(event->type, RETKEY, PADENTER) && event->val == KM_PRESS) {
@@ -9045,7 +9184,7 @@ static int ui_handle_menu_event(
                        else {
 
                                /* check mouse moving outside of the menu */
-                               if (inside == 0 && (block->flag & UI_BLOCK_MOVEMOUSE_QUIT)) {
+                               if (inside == 0 && (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER))) {
                                        uiSafetyRct *saferct;
 
                                        ui_mouse_motion_towards_check(block, menu, &event->x, is_parent_inside == false);
@@ -9089,6 +9228,15 @@ static int ui_handle_menu_event(
                retval = ui_handle_menu_button(C, event, menu);
        }
 
+#ifdef USE_UI_POPOVER_ONCE
+       if (block->flag & UI_BLOCK_POPOVER_ONCE) {
+               if ((event->type == LEFTMOUSE) && (event->val == KM_RELEASE)) {
+                       UI_popover_once_clear(menu->popup_create_vars.arg);
+                       block->flag &= ~UI_BLOCK_POPOVER_ONCE;
+               }
+       }
+#endif
+
        /* Don't handle double click events, rehandle as regular press/release. */
        if (retval == WM_UI_HANDLER_CONTINUE && event->val == KM_DBL_CLICK) {
                return retval;
@@ -9145,7 +9293,7 @@ static int ui_handle_menu_return_submenu(bContext *C, const wmEvent *event, uiPo
                        submenu->menuretval = 0;
        }
 
-       if (block->flag & UI_BLOCK_MOVEMOUSE_QUIT) {
+       if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) {
                /* for cases where close does not cascade, allow the user to
                 * move the mouse back towards the menu without closing */
                ui_mouse_motion_towards_reinit(menu, &event->x);
@@ -9273,6 +9421,7 @@ static int ui_pie_handler(bContext *C, const wmEvent *event, uiPopupBlockHandle
 
        ui_window_to_block_fl(ar, block, &event_xy[0], &event_xy[1]);
 
+       /* Distance from initial point. */
        dist = ui_block_calc_pie_segment(block, event_xy);
 
        if (but && button_modal_state(but->active->state)) {
@@ -9357,15 +9506,16 @@ static int ui_pie_handler(bContext *C, const wmEvent *event, uiPopupBlockHandle
                                ED_region_tag_redraw(ar);
                        }
                        else {
-                               /* distance from initial point */
-                               if (!(block->pie_data.flags & UI_PIE_DRAG_STYLE)) {
+                               if ((duration < 0.01 * U.pie_tap_timeout) &&
+                                   !(block->pie_data.flags & UI_PIE_DRAG_STYLE))
+                               {
                                        block->pie_data.flags |= UI_PIE_CLICK_STYLE;
                                }
                                else {
                                        but = ui_but_find_active_in_region(menu->region);
 
                                        if (but && (U.pie_menu_confirm > 0) &&
-                                           (dist >= U.pie_menu_threshold + U.pie_menu_confirm))
+                                           (dist >= U.dpi_fac * (U.pie_menu_threshold + U.pie_menu_confirm)))
                                        {
                                                if (but)
                                                        return ui_but_pie_menu_apply(C, menu, but, true);
@@ -9393,7 +9543,7 @@ static int ui_pie_handler(bContext *C, const wmEvent *event, uiPopupBlockHandle
                                                /* here instead, we use the offset location to account for the initial
                                                 * direction timeout */
                                                if ((U.pie_menu_confirm > 0) &&
-                                                   (dist >= U.pie_menu_threshold + U.pie_menu_confirm))
+                                                   (dist >= U.dpi_fac * (U.pie_menu_threshold + U.pie_menu_confirm)))
                                                {
                                                        block->pie_data.flags |= UI_PIE_GESTURE_END_WAIT;
                                                        copy_v2_v2(block->pie_data.last_pos, event_xy);
@@ -9548,7 +9698,7 @@ static int ui_handle_menus_recursive(
 
                        retval = ui_handle_menu_button(C, event, menu);
 
-                       if (block->flag & UI_BLOCK_MOVEMOUSE_QUIT) {
+                       if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) {
                                /* when there is a active search button and we close it,
                                 * we need to reinit the mouse coords [#35346] */
                                if (ui_but_find_active_in_region(menu->region) != but) {
@@ -9710,11 +9860,12 @@ static int ui_handler_region_menu(bContext *C, const wmEvent *event, void *UNUSE
                data = but->active;
 
                if ((data->state == BUTTON_STATE_MENU_OPEN) &&
-                   (is_inside_menu == false) && /* make sure mouse isn't inside another menu (see T43247) */
-                   (but->type == UI_BTYPE_PULLDOWN) &&
+                   /* make sure mouse isn't inside another menu (see T43247) */
+                   (is_inside_menu == false) &&
+                   (ELEM(but->type, UI_BTYPE_PULLDOWN, UI_BTYPE_POPOVER)) &&
                    (but_other = ui_but_find_mouse_over(ar, event)) &&
                    (but != but_other) &&
-                   (but->type == but_other->type))
+                   (ELEM(but_other->type, UI_BTYPE_PULLDOWN, UI_BTYPE_POPOVER)))
                {
                        /* if mouse moves to a different root-level menu button,
                         * open it to replace the current menu */
@@ -9887,22 +10038,26 @@ void UI_popup_handlers_add(bContext *C, ListBase *handlers, uiPopupBlockHandle *
 
 void UI_popup_handlers_remove(ListBase *handlers, uiPopupBlockHandle *popup)
 {
-       wmEventHandler *handler;
+       LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers) {
+               if (handler_base->type == WM_HANDLER_TYPE_UI) {
+                       wmEventHandler_UI *handler = (wmEventHandler_UI *)handler_base;
 
-       for (handler = handlers->first; handler; handler = handler->next) {
-               if (handler->ui_handle == ui_popup_handler &&
-                   handler->ui_remove == ui_popup_handler_remove &&
-                   handler->ui_userdata == popup)
-               {
-                       /* tag refresh parent popup */
-                       if (handler->next &&
-                           handler->next->ui_handle == ui_popup_handler &&
-                           handler->next->ui_remove == ui_popup_handler_remove)
+                       if (handler->handle_fn == ui_popup_handler &&
+                           handler->remove_fn == ui_popup_handler_remove &&
+                           handler->user_data == popup)
                        {
-                               uiPopupBlockHandle *parent_popup = handler->next->ui_userdata;
-                               ED_region_tag_refresh_ui(parent_popup->region);
+                               /* tag refresh parent popup */
+                               wmEventHandler_UI *handler_next = (wmEventHandler_UI *)handler->head.next;
+                               if (handler_next &&
+                                   handler_next->head.type == WM_HANDLER_TYPE_UI &&
+                                   handler_next->handle_fn == ui_popup_handler &&
+                                   handler_next->remove_fn == ui_popup_handler_remove)
+                               {
+                                       uiPopupBlockHandle *parent_popup = handler_next->user_data;
+                                       ED_region_tag_refresh_ui(parent_popup->region);
+                               }
+                               break;
                        }
-                       break;
                }
        }