Merge branch 'blender2.7'
[blender.git] / source / blender / editors / interface / interface.c
index 11c2161..1e07983 100644 (file)
@@ -17,8 +17,8 @@
  * All rights reserved.
  */
 
-/** \file blender/editors/interface/interface.c
- *  \ingroup edinterface
+/** \file
+ * \ingroup edinterface
  */
 
 
@@ -35,6 +35,7 @@
 #include "DNA_scene_types.h"
 #include "DNA_screen_types.h"
 #include "DNA_userdef_types.h"
+#include "DNA_workspace_types.h"
 
 #include "BLI_math.h"
 #include "BLI_listbase.h"
@@ -44,6 +45,7 @@
 
 #include "BLI_utildefines.h"
 
+#include "BKE_animsys.h"
 #include "BKE_context.h"
 #include "BKE_idprop.h"
 #include "BKE_main.h"
 #include "BKE_screen.h"
 #include "BKE_unit.h"
 
-#include "BIF_gl.h"
+#include "GPU_glew.h"
+#include "GPU_matrix.h"
+#include "GPU_state.h"
 
 #include "BLF_api.h"
 #include "BLT_translation.h"
 
 #include "UI_interface.h"
+#include "UI_interface_icons.h"
 
 #include "IMB_imbuf.h"
 
 #include "WM_api.h"
 #include "WM_types.h"
-#include "wm_subwindow.h"
+#include "WM_message.h"
 
 #include "RNA_access.h"
 
 #include "BPY_extern.h"
 
+#include "ED_screen.h"
+#include "ED_numinput.h"
+
 #include "IMB_colormanagement.h"
 
+#include "DEG_depsgraph_query.h"
+
 #include "interface_intern.h"
 
+/* prototypes. */
+static void ui_but_to_pixelrect(struct rcti *rect, const struct ARegion *ar, struct uiBlock *block, struct uiBut *but);
+static void ui_def_but_rna__menu(bContext *UNUSED(C), uiLayout *layout, void *but_p);
+
 /* avoid unneeded calls to ui_but_value_get */
 #define UI_BUT_VALUE_UNSET DBL_MAX
 #define UI_GET_BUT_VALUE_INIT(_but, _value) if (_value == DBL_MAX) {  (_value) = ui_but_value_get(_but); } (void)0
@@ -144,6 +158,18 @@ void ui_block_to_window_rctf(const ARegion *ar, uiBlock *block, rctf *rct_dst, c
        ui_block_to_window_fl(ar, block, &rct_dst->xmax, &rct_dst->ymax);
 }
 
+float ui_block_to_window_scale(const ARegion *ar, uiBlock *block)
+{
+       /* We could have function for this to avoid dummy arg. */
+       float dummy_x;
+       float min_y = 0, max_y = 1;
+       dummy_x = 0.0f;
+       ui_block_to_window_fl(ar, block, &dummy_x, &min_y);
+       dummy_x = 0.0f;
+       ui_block_to_window_fl(ar, block, &dummy_x, &max_y);
+       return max_y - min_y;
+}
+
 /* for mouse cursor */
 void ui_window_to_block_fl(const ARegion *ar, uiBlock *block, float *x, float *y)
 {
@@ -200,6 +226,84 @@ void ui_region_to_window(const ARegion *ar, int *x, int *y)
        *y += ar->winrct.ymin;
 }
 
+static void ui_update_flexible_spacing(const ARegion *region, uiBlock *block)
+{
+       int sepr_flex_len = 0;
+       for (uiBut *but = block->buttons.first; but; but = but->next) {
+               if (but->type == UI_BTYPE_SEPR_SPACER) {
+                       sepr_flex_len++;
+               }
+       }
+
+       if (sepr_flex_len == 0) {
+               return;
+       }
+
+       rcti rect;
+       ui_but_to_pixelrect(&rect, region, block, block->buttons.last);
+       const float buttons_width = (float)rect.xmax + UI_HEADER_OFFSET;
+       const float region_width = (float)region->sizex * U.dpi_fac;
+
+       if (region_width <= buttons_width) {
+               return;
+       }
+
+       /* We could get rid of this loop if we agree on a max number of spacer */
+       int *spacers_pos = alloca(sizeof(*spacers_pos) * (size_t)sepr_flex_len);
+       int i = 0;
+       for (uiBut *but = block->buttons.first; but; but = but->next) {
+               if (but->type == UI_BTYPE_SEPR_SPACER) {
+                       ui_but_to_pixelrect(&rect, region, block, but);
+                       spacers_pos[i] = rect.xmax + UI_HEADER_OFFSET;
+                       i++;
+               }
+       }
+
+       const float segment_width = region_width / (float)sepr_flex_len;
+       float offset = 0, remaining_space = region_width - buttons_width;
+       i = 0;
+       for (uiBut *but = block->buttons.first; but; but = but->next) {
+               BLI_rctf_translate(&but->rect, offset, 0);
+               if (but->type == UI_BTYPE_SEPR_SPACER) {
+                       /* How much the next block overlap with the current segment */
+                       int overlap = (
+                               (i == sepr_flex_len - 1) ?
+                               buttons_width - spacers_pos[i] :
+                               (spacers_pos[i + 1] - spacers_pos[i]) / 2);
+                       int segment_end = segment_width * (i + 1);
+                       int spacer_end = segment_end - overlap;
+                       int spacer_sta = spacers_pos[i] + offset;
+                       if (spacer_end > spacer_sta) {
+                               float step = min_ff(remaining_space, spacer_end - spacer_sta);
+                               remaining_space -= step;
+                               offset += step;
+                       }
+                       i++;
+               }
+       }
+       ui_block_bounds_calc(block);
+}
+
+static void ui_update_window_matrix(const wmWindow *window, const ARegion *region, uiBlock *block)
+{
+       /* window matrix and aspect */
+       if (region && region->visible) {
+               /* Get projection matrix which includes View2D translation and zoom. */
+               GPU_matrix_projection_get(block->winmat);
+               block->aspect = 2.0f / fabsf(region->winx * block->winmat[0][0]);
+       }
+       else {
+               /* No subwindow created yet, for menus for example, so we use the main
+                * window instead, since buttons are created there anyway. */
+               int width = WM_window_pixels_x(window);
+               int height = WM_window_pixels_y(window);
+               rcti winrct = {0, width - 1, 0, height - 1};
+
+               wmGetProjectionMatrix(block->winmat, &winrct);
+               block->aspect = 2.0f / fabsf(width * block->winmat[0][0]);
+       }
+}
+
 /**
  * Popups will add a margin to #ARegion.winrct for shadow,
  * for interactivity (point-inside tests for eg), we want the winrct without the margin added.
@@ -218,7 +322,7 @@ void ui_region_winrct_get_no_margin(const struct ARegion *ar, struct rcti *r_rec
 
 /* ******************* block calc ************************* */
 
-void ui_block_translate(uiBlock *block, int x, int y)
+void UI_block_translate(uiBlock *block, int x, int y)
 {
        uiBut *but;
 
@@ -238,7 +342,7 @@ static void ui_block_bounds_calc_text(uiBlock *block, float offset)
        UI_fontstyle_set(&style->widget);
 
        for (init_col_bt = bt = block->buttons.first; bt; bt = bt->next) {
-               if (!ELEM(bt->type, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE)) {
+               if (!ELEM(bt->type, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE, UI_BTYPE_SEPR_SPACER)) {
                        j = BLF_width(style->widget.uifont_id, bt->drawstr, sizeof(bt->drawstr));
 
                        if (j > i)
@@ -328,7 +432,7 @@ static void ui_block_bounds_calc_centered(wmWindow *window, uiBlock *block)
        startx = (xmax * 0.5f) - (width * 0.5f);
        starty = (ymax * 0.5f) - (height * 0.5f);
 
-       ui_block_translate(block, startx - block->rect.xmin, starty - block->rect.ymin);
+       UI_block_translate(block, startx - block->rect.xmin, starty - block->rect.ymin);
 
        /* now recompute bounds and safety */
        ui_block_bounds_calc(block);
@@ -339,10 +443,10 @@ static void ui_block_bounds_calc_centered_pie(uiBlock *block)
 {
        const int xy[2] = {
            block->pie_data.pie_center_spawned[0],
-           block->pie_data.pie_center_spawned[1]
+           block->pie_data.pie_center_spawned[1],
        };
 
-       ui_block_translate(block, xy[0], xy[1]);
+       UI_block_translate(block, xy[0], xy[1]);
 
        /* now recompute bounds and safety */
        ui_block_bounds_calc(block);
@@ -391,8 +495,8 @@ static void ui_block_bounds_calc_popup(
 
        /* offset block based on mouse position, user offset is scaled
         * along in case we resized the block in ui_block_bounds_calc_text */
-       raw_x = rect.xmin = xy[0] + block->rect.xmin + (block->mx * width) / oldwidth;
-       raw_y = rect.ymin = xy[1] + block->rect.ymin + (block->my * height) / oldheight;
+       raw_x = rect.xmin = xy[0] + block->rect.xmin + (block->bounds_offset[0] * width) / oldwidth;
+       raw_y = rect.ymin = xy[1] + block->rect.ymin + (block->bounds_offset[1] * height) / oldheight;
        rect.xmax = rect.xmin + width;
        rect.ymax = rect.ymin + height;
 
@@ -402,7 +506,7 @@ static void ui_block_bounds_calc_popup(
        rect_bounds.ymax = ymax - UI_POPUP_MENU_TOP;
 
        BLI_rcti_clamp(&rect, &rect_bounds, ofs_dummy);
-       ui_block_translate(block, rect.xmin - block->rect.xmin, rect.ymin - block->rect.ymin);
+       UI_block_translate(block, rect.xmin - block->rect.xmin, rect.ymin - block->rect.ymin);
 
        /* now recompute bounds and safety */
        ui_block_bounds_calc(block);
@@ -434,21 +538,33 @@ void UI_block_bounds_set_text(uiBlock *block, int addval)
 }
 
 /* used for block popups */
-void UI_block_bounds_set_popup(uiBlock *block, int addval, int mx, int my)
+void UI_block_bounds_set_popup(uiBlock *block, int addval, const int bounds_offset[2])
 {
        block->bounds = addval;
        block->bounds_type = UI_BLOCK_BOUNDS_POPUP_MOUSE;
-       block->mx = mx;
-       block->my = my;
+       if (bounds_offset != NULL) {
+               block->bounds_offset[0] = bounds_offset[0];
+               block->bounds_offset[1] = bounds_offset[1];
+       }
+       else {
+               block->bounds_offset[0] = 0;
+               block->bounds_offset[1] = 0;
+       }
 }
 
 /* used for menu popups */
-void UI_block_bounds_set_menu(uiBlock *block, int addval, int mx, int my)
+void UI_block_bounds_set_menu(uiBlock *block, int addval, const int bounds_offset[2])
 {
        block->bounds = addval;
        block->bounds_type = UI_BLOCK_BOUNDS_POPUP_MENU;
-       block->mx = mx;
-       block->my = my;
+       if (bounds_offset != NULL) {
+               block->bounds_offset[0] = bounds_offset[0];
+               block->bounds_offset[1] = bounds_offset[1];
+       }
+       else {
+               block->bounds_offset[0] = 0;
+               block->bounds_offset[1] = 0;
+       }
 }
 
 /* used for centered popups, i.e. splash */
@@ -487,86 +603,6 @@ static int ui_but_calc_float_precision(uiBut *but, double value)
        return UI_calc_float_precision(prec, value);
 }
 
-/* ************** LINK LINE DRAWING  ************* */
-
-/* link line drawing is not part of buttons or theme.. so we stick with it here */
-
-static void ui_draw_linkline(uiLinkLine *line, int highlightActiveLines, int dashInactiveLines)
-{
-       rcti rect;
-
-       if (line->from == NULL || line->to == NULL) return;
-
-       rect.xmin = BLI_rctf_cent_x(&line->from->rect);
-       rect.ymin = BLI_rctf_cent_y(&line->from->rect);
-       rect.xmax = BLI_rctf_cent_x(&line->to->rect);
-       rect.ymax = BLI_rctf_cent_y(&line->to->rect);
-
-       if (dashInactiveLines)
-               UI_ThemeColor(TH_GRID);
-       else if (line->flag & UI_SELECT)
-               glColor3ub(100, 100, 100);
-       else if (highlightActiveLines && ((line->from->flag & UI_ACTIVE) || (line->to->flag & UI_ACTIVE)))
-               UI_ThemeColor(TH_TEXT_HI);
-       else
-               glColor3ub(0, 0, 0);
-
-       ui_draw_link_bezier(&rect);
-}
-
-static void ui_draw_links(uiBlock *block)
-{
-       uiBut *but;
-       uiLinkLine *line;
-
-       /* Draw the gray out lines. Do this first so they appear at the
-        * bottom of inactive or active lines.
-        * As we go, remember if we see any active or selected lines. */
-       bool found_selectline = false;
-       bool found_activeline = false;
-
-       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 = line->next) {
-                               if (!(line->from->flag & UI_ACTIVE) && !(line->to->flag & UI_ACTIVE)) {
-                                       if (line->deactive)
-                                               ui_draw_linkline(line, 0, true);
-                               }
-                               else
-                                       found_activeline = true;
-
-                               if ((line->from->flag & UI_SELECT) || (line->to->flag & UI_SELECT))
-                                       found_selectline = true;
-                       }
-               }
-       }
-
-       /* Draw the inactive lines (lines with neither button being hovered over) */
-       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 = line->next) {
-                               if (!(line->from->flag & UI_ACTIVE) && !(line->to->flag & UI_ACTIVE)) {
-                                       if (!line->deactive)
-                                               ui_draw_linkline(line, 0, false);
-                               }
-                       }
-               }
-       }
-
-       /* Draw any active lines (lines with either button being hovered over).
-        * Do this last so they appear on top of inactive and gray out lines. */
-       if (found_activeline) {
-               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 = line->next) {
-                                       if ((line->from->flag & UI_ACTIVE) || (line->to->flag & UI_ACTIVE))
-                                               ui_draw_linkline(line, !found_selectline, false);
-                               }
-                       }
-               }
-       }
-}
-
 /* ************** BLOCK ENDING FUNCTION ************* */
 
 /* NOTE: if but->poin is allocated memory for every defbut, things fail... */
@@ -609,38 +645,6 @@ uiBut *ui_but_find_new(uiBlock *block_new, const uiBut *but_old)
        return but_new;
 }
 
-/* oldbut is being inserted in new block, so we use the lines from new button, and replace button pointers */
-static void ui_but_update_linklines(uiBlock *block, uiBut *oldbut, uiBut *newbut)
-{
-       uiLinkLine *line;
-       uiBut *but;
-
-       /* if active button is UI_BTYPE_LINK */
-       if (newbut->type == UI_BTYPE_LINK && newbut->link) {
-
-               SWAP(uiLink *, oldbut->link, newbut->link);
-
-               for (line = oldbut->link->lines.first; line; line = line->next) {
-                       if (line->to == newbut)
-                               line->to = oldbut;
-                       if (line->from == newbut)
-                               line->from = oldbut;
-               }
-       }
-
-       /* check all other button links */
-       for (but = block->buttons.first; but; but = but->next) {
-               if (but != newbut && but->type == UI_BTYPE_LINK && but->link) {
-                       for (line = but->link->lines.first; line; line = line->next) {
-                               if (line->to == newbut)
-                                       line->to = oldbut;
-                               if (line->from == newbut)
-                                       line->from = oldbut;
-                       }
-               }
-       }
-}
-
 /**
  * \return true when \a but_p is set (only done for active buttons).
  */
@@ -697,8 +701,6 @@ static bool ui_but_update_from_old_block(const bContext *C, uiBlock *block, uiBu
                but->selend = oldbut->selend;
                but->softmin = oldbut->softmin;
                but->softmax = oldbut->softmax;
-               but->linkto[0] = oldbut->linkto[0];
-               but->linkto[1] = oldbut->linkto[1];
                oldbut->active = NULL;
 #endif
 
@@ -740,8 +742,6 @@ static bool ui_but_update_from_old_block(const bContext *C, uiBlock *block, uiBu
                        oldbut->a1 = but->a1;
                }
 
-               ui_but_update_linklines(block, oldbut, but);
-
                if (!BLI_listbase_is_empty(&block->butstore)) {
                        UI_butstore_register_update(block, oldbut, but);
                }
@@ -964,6 +964,7 @@ void ui_but_add_shortcut(uiBut *but, const char *shortcut_str, const bool do_str
                MEM_freeN(butstr_orig);
                but->str = but->strdata;
                but->flag |= UI_BUT_HAS_SEP_CHAR;
+               but->drawflag |= UI_BUT_HAS_SHORTCUT;
                ui_but_update(but);
        }
 }
@@ -1008,8 +1009,8 @@ static bool ui_but_event_operator_string_from_menu(
        IDP_AddToGroup(prop_menu, IDP_NewString(mt->idname, "name", sizeof(mt->idname)));
 
        if (WM_key_event_operator_string(
-               C, "WM_OT_call_menu", WM_OP_INVOKE_REGION_WIN, prop_menu, true,
-               buf, buf_len))
+                   C, "WM_OT_call_menu", WM_OP_INVOKE_REGION_WIN, prop_menu, true,
+                   buf, buf_len))
        {
                found = true;
        }
@@ -1019,6 +1020,41 @@ static bool ui_but_event_operator_string_from_menu(
        return found;
 }
 
+static bool ui_but_event_operator_string_from_panel(
+        const bContext *C, uiBut *but,
+        char *buf, const size_t buf_len)
+{
+       /** Nearly exact copy of #ui_but_event_operator_string_from_menu */
+       PanelType *pt = UI_but_paneltype_get(but);
+       BLI_assert(pt != NULL);
+
+       bool found = false;
+       IDProperty *prop_panel;
+
+       /* annoying, create a property */
+       IDPropertyTemplate val = {0};
+       prop_panel = IDP_New(IDP_GROUP, &val, __func__); /* dummy, name is unimportant  */
+       IDP_AddToGroup(prop_panel, IDP_NewString(pt->idname, "name", sizeof(pt->idname)));
+       IDP_AddToGroup(prop_panel, IDP_New(IDP_INT, &(IDPropertyTemplate){ .i = pt->space_type, }, "space_type"));
+       IDP_AddToGroup(prop_panel, IDP_New(IDP_INT, &(IDPropertyTemplate){ .i = pt->region_type, }, "region_type"));
+
+       for (int i = 0; i < 2; i++) {
+               /* FIXME(campbell): We can't reasonably search all configurations - long term. */
+               IDP_ReplaceInGroup(prop_panel, IDP_New(IDP_INT, &(IDPropertyTemplate){ .i = i, }, "keep_open"));
+               if (WM_key_event_operator_string(
+                           C, "WM_OT_call_panel", WM_OP_INVOKE_REGION_WIN, prop_panel, true,
+                           buf, buf_len))
+               {
+                       found = true;
+                       break;
+               }
+       }
+
+       IDP_FreeProperty(prop_panel);
+       MEM_freeN(prop_panel);
+       return found;
+}
+
 static bool ui_but_event_operator_string(
         const bContext *C, uiBut *but,
         char *buf, const size_t buf_len)
@@ -1031,6 +1067,9 @@ static bool ui_but_event_operator_string(
        else if (UI_but_menutype_get(but) != NULL) {
                found = ui_but_event_operator_string_from_menu(C, but, buf, buf_len);
        }
+       else if (UI_but_paneltype_get(but) != NULL) {
+               found = ui_but_event_operator_string_from_panel(C, but, buf, buf_len);
+       }
 
        return found;
 }
@@ -1040,6 +1079,11 @@ static bool ui_but_event_property_operator_string(
         char *buf, const size_t buf_len)
 {
        /* context toggle operator names to check... */
+
+       /* This function could use a refactor to generalize button type to operator relationship
+        * as well as which operators use properties.
+        * - Campbell
+        * */
        const char *ctx_toggle_opnames[] = {
                "WM_OT_context_toggle",
                "WM_OT_context_toggle_enum",
@@ -1047,56 +1091,113 @@ static bool ui_but_event_property_operator_string(
                "WM_OT_context_cycle_enum",
                "WM_OT_context_cycle_array",
                "WM_OT_context_menu_enum",
-               NULL
+               NULL,
+       };
+
+       const char *ctx_enum_opnames[] = {
+               "WM_OT_context_set_enum",
+               NULL,
        };
-       const size_t num_ops = sizeof(ctx_toggle_opnames) / sizeof(const char *);
+
+       const char *ctx_enum_opnames_for_Area_ui_type[] = {
+               "SCREEN_OT_space_type_set_or_cycle",
+               NULL,
+       };
+
+       const char **opnames = ctx_toggle_opnames;
+       int          opnames_len = ARRAY_SIZE(ctx_toggle_opnames);
+
+
+       int  prop_enum_value = -1;
+       bool prop_enum_value_ok = false;
+       bool prop_enum_value_is_int = false;
+       const char *prop_enum_value_id = "value";
+       PointerRNA *ptr = &but->rnapoin;
+       PropertyRNA *prop = but->rnaprop;
+       if ((but->type == UI_BTYPE_BUT_MENU) && (but->block->handle != NULL)) {
+               uiBut *but_parent = but->block->handle->popup_create_vars.but;
+               if ((but->type == UI_BTYPE_BUT_MENU) &&
+                   (but_parent && but_parent->rnaprop) &&
+                   (RNA_property_type(but_parent->rnaprop) == PROP_ENUM) &&
+                   (but_parent->menu_create_func == ui_def_but_rna__menu))
+               {
+                       prop_enum_value = (int)but->hardmin;
+                       ptr = &but_parent->rnapoin;
+                       prop = but_parent->rnaprop;
+                       prop_enum_value_ok = true;
+
+                       opnames = ctx_enum_opnames;
+                       opnames_len = ARRAY_SIZE(ctx_enum_opnames);
+               }
+       }
 
        bool found = false;
 
-       /* this version is only for finding hotkeys for properties (which get set via context using operators) */
-       if (but->rnaprop) {
+       /* Don't use the button again. */
+       but = NULL;
+
+       /* this version is only for finding hotkeys for properties
+        * (which get set via context using operators) */
+       if (prop) {
                /* to avoid massive slowdowns on property panels, for now, we only check the
                 * hotkeys for Editor / Scene settings...
                 *
                 * TODO: userpref settings?
                 */
-               // TODO: value (for enum stuff)?
                char *data_path = NULL;
 
-               if (but->rnapoin.id.data) {
-                       ID *id = but->rnapoin.id.data;
+               if (ptr->id.data) {
+                       ID *id = ptr->id.data;
 
                        if (GS(id->name) == ID_SCR) {
                                /* screen/editor property
                                 * NOTE: in most cases, there is actually no info for backwards tracing
                                 * how to get back to ID from the editor data we may be dealing with
                                 */
-                               if (RNA_struct_is_a(but->rnapoin.type, &RNA_Space)) {
+                               if (RNA_struct_is_a(ptr->type, &RNA_Space)) {
                                        /* data should be directly on here... */
-                                       data_path = BLI_sprintfN("space_data.%s", RNA_property_identifier(but->rnaprop));
+                                       data_path = BLI_sprintfN("space_data.%s", RNA_property_identifier(prop));
+                               }
+                               else if (RNA_struct_is_a(ptr->type, &RNA_Area)) {
+                                       /* data should be directly on here... */
+                                       const char *prop_id = RNA_property_identifier(prop);
+                                       /* Hack since keys access 'type', UI shows 'ui_type'. */
+                                       if (STREQ(prop_id, "ui_type")) {
+                                               prop_id = "type";
+                                               prop_enum_value >>= 16;
+                                               prop = RNA_struct_find_property(ptr, prop_id);
+
+                                               opnames = ctx_enum_opnames_for_Area_ui_type;
+                                               opnames_len = ARRAY_SIZE(ctx_enum_opnames_for_Area_ui_type);
+                                               prop_enum_value_id = "space_type";
+                                               prop_enum_value_is_int = true;
+                                       }
+                                       else {
+                                               data_path = BLI_sprintfN("area.%s", prop_id);
+                                       }
                                }
                                else {
                                        /* special exceptions for common nested data in editors... */
-                                       if (RNA_struct_is_a(but->rnapoin.type, &RNA_DopeSheet)) {
+                                       if (RNA_struct_is_a(ptr->type, &RNA_DopeSheet)) {
                                                /* dopesheet filtering options... */
-                                               data_path = BLI_sprintfN("space_data.dopesheet.%s", RNA_property_identifier(but->rnaprop));
+                                               data_path = BLI_sprintfN("space_data.dopesheet.%s", RNA_property_identifier(prop));
                                        }
-                                       else if (RNA_struct_is_a(but->rnapoin.type, &RNA_FileSelectParams)) {
+                                       else if (RNA_struct_is_a(ptr->type, &RNA_FileSelectParams)) {
                                                /* Filebrowser options... */
-                                               data_path = BLI_sprintfN("space_data.params.%s", RNA_property_identifier(but->rnaprop));
+                                               data_path = BLI_sprintfN("space_data.params.%s", RNA_property_identifier(prop));
                                        }
                                }
                        }
                        else if (GS(id->name) == ID_SCE) {
-                               if (RNA_struct_is_a(but->rnapoin.type, &RNA_ToolSettings)) {
+                               if (RNA_struct_is_a(ptr->type, &RNA_ToolSettings)) {
                                        /* toolsettings property
                                         * NOTE: toolsettings is usually accessed directly (i.e. not through scene)
                                         */
-                                       data_path = RNA_path_from_ID_to_property(&but->rnapoin, but->rnaprop);
+                                       data_path = RNA_path_from_ID_to_property(ptr, prop);
                                }
                                else {
                                        /* scene property */
-                                       char *path = RNA_path_from_ID_to_property(&but->rnapoin, but->rnaprop);
+                                       char *path = RNA_path_from_ID_to_property(ptr, prop);
 
                                        if (path) {
                                                data_path = BLI_sprintfN("scene.%s", path);
@@ -1105,7 +1206,7 @@ static bool ui_but_event_property_operator_string(
 #if 0
                                        else {
                                                printf("ERROR in %s(): Couldn't get path for scene property - %s\n",
-                                                      __func__, RNA_property_identifier(but->rnaprop));
+                                                      __func__, RNA_property_identifier(prop));
                                        }
 #endif
                                }
@@ -1114,27 +1215,50 @@ static bool ui_but_event_property_operator_string(
                                //puts("other id");
                        }
 
-                       //printf("prop shortcut: '%s' (%s)\n", RNA_property_identifier(but->rnaprop), data_path);
+                       //printf("prop shortcut: '%s' (%s)\n", RNA_property_identifier(prop), data_path);
                }
 
                /* we have a datapath! */
-               if (data_path) {
-                       size_t i;
-
+               if (data_path || (prop_enum_value_ok && prop_enum_value_id)) {
                        /* create a property to host the "datapath" property we're sending to the operators */
                        IDProperty *prop_path;
-                       IDProperty *prop_path_value;
 
                        IDPropertyTemplate val = {0};
                        prop_path = IDP_New(IDP_GROUP, &val, __func__);
-                       prop_path_value = IDP_NewString(data_path, "data_path", strlen(data_path) + 1);
-                       IDP_AddToGroup(prop_path, prop_path_value);
+                       if (data_path) {
+                               IDP_AddToGroup(prop_path, IDP_NewString(data_path, "data_path", strlen(data_path) + 1));
+                       }
+                       if (prop_enum_value_ok) {
+                               const EnumPropertyItem *item;
+                               bool free;
+                               RNA_property_enum_items((bContext *)C, ptr, prop, &item, NULL, &free);
+                               int index = RNA_enum_from_value(item, prop_enum_value);
+                               if (index != -1) {
+                                       IDProperty *prop_value;
+                                       if (prop_enum_value_is_int) {
+                                               int value = item[index].value;
+                                               prop_value = IDP_New(IDP_INT, &(IDPropertyTemplate){ .i = value, }, prop_enum_value_id);
+                                       }
+                                       else {
+                                               const char *id = item[index].identifier;
+                                               prop_value = IDP_NewString(id, prop_enum_value_id, strlen(id) + 1);
+                                       }
+                                       IDP_AddToGroup(prop_path, prop_value);
+                               }
+                               else {
+                                       opnames_len = 0;  /* Do nothing. */
+                               }
+                               if (free) {
+                                       MEM_freeN((void *)item);
+                               }
+                       }
 
                        /* check each until one works... */
-                       for (i = 0; (i < num_ops) && (ctx_toggle_opnames[i]); i++) {
+
+                       for (int i = 0; (i < opnames_len) && (opnames[i]); i++) {
                                if (WM_key_event_operator_string(
-                                       C, ctx_toggle_opnames[i], WM_OP_INVOKE_REGION_WIN, prop_path, false,
-                                       buf, buf_len))
+                                           C, opnames[i], WM_OP_INVOKE_REGION_WIN, prop_path, false,
+                                           buf, buf_len))
                                {
                                        found = true;
                                        break;
@@ -1144,7 +1268,9 @@ static bool ui_but_event_property_operator_string(
                        /* cleanup */
                        IDP_FreeProperty(prop_path);
                        MEM_freeN(prop_path);
-                       MEM_freeN(data_path);
+                       if (data_path) {
+                               MEM_freeN(data_path);
+                       }
                }
        }
 
@@ -1196,11 +1322,15 @@ static void ui_menu_block_set_keymaps(const bContext *C, uiBlock *block)
        uiBut *but;
        char buf[128];
 
-       BLI_assert(block->flag & UI_BLOCK_LOOP);
+       BLI_assert(block->flag & (UI_BLOCK_LOOP | UI_BLOCK_SHOW_SHORTCUT_ALWAYS));
 
        /* only do it before bounding */
-       if (block->rect.xmin != block->rect.xmax)
+       if (block->rect.xmin != block->rect.xmax) {
                return;
+       }
+       if (STREQ(block->name, "splash")) {
+               return;
+       }
 
        if (block->flag & UI_BLOCK_RADIAL) {
                for (but = block->buttons.first; but; but = but->next) {
@@ -1212,7 +1342,18 @@ static void ui_menu_block_set_keymaps(const bContext *C, uiBlock *block)
        }
        else {
                for (but = block->buttons.first; but; but = but->next) {
-                       if (but->dt != UI_EMBOSS_PULLDOWN) {
+                       if (block->flag & UI_BLOCK_SHOW_SHORTCUT_ALWAYS) {
+                               /* Skip icon-only buttons (as used in the toolbar). */
+                               if (but->drawstr[0] == '\0') {
+                                       continue;
+                               }
+                               else if (((block->flag & UI_BLOCK_POPOVER) == 0) && UI_but_is_tool(but)) {
+                                       /* For non-popovers, shown in shortcut only
+                                        * (has special shortcut handling code). */
+                                       continue;
+                               }
+                       }
+                       else if (but->dt != UI_EMBOSS_PULLDOWN) {
                                continue;
                        }
 
@@ -1226,6 +1367,18 @@ static void ui_menu_block_set_keymaps(const bContext *C, uiBlock *block)
        }
 }
 
+void ui_but_override_flag(uiBut *but)
+{
+       const int override_status = RNA_property_static_override_status(&but->rnapoin, but->rnaprop, but->rnaindex);
+
+       if (override_status & RNA_OVERRIDE_STATUS_OVERRIDDEN) {
+               but->flag |= UI_BUT_OVERRIDEN;
+       }
+       else {
+               but->flag &= ~UI_BUT_OVERRIDEN;
+       }
+}
+
 void UI_block_update_from_old(const bContext *C, uiBlock *block)
 {
        uiBut *but_old;
@@ -1263,6 +1416,7 @@ void UI_block_end_ex(const bContext *C, uiBlock *block, const int xy[2], int r_x
 {
        wmWindow *window = CTX_wm_window(C);
        Scene *scene = CTX_data_scene(C);
+       ARegion *region = CTX_wm_region(C);
        uiBut *but;
 
        BLI_assert(block->active);
@@ -1290,6 +1444,10 @@ void UI_block_end_ex(const bContext *C, uiBlock *block, const int xy[2], int r_x
                }
 
                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);
+               }
        }
 
 
@@ -1298,12 +1456,12 @@ void UI_block_end_ex(const bContext *C, uiBlock *block, const int xy[2], int r_x
        if (block->layouts.first) {
                UI_block_layout_resolve(block, NULL, NULL);
        }
-       ui_block_align_calc(block);
+       ui_block_align_calc(block, CTX_wm_region(C));
        if ((block->flag & UI_BLOCK_LOOP) && (block->flag & UI_BLOCK_NUMSELECT)) {
                ui_menu_block_set_keyaccels(block); /* could use a different flag to check */
        }
 
-       if (block->flag & UI_BLOCK_LOOP) {
+       if (block->flag & (UI_BLOCK_LOOP | UI_BLOCK_SHOW_SHORTCUT_ALWAYS)) {
                ui_menu_block_set_keymaps(C, block);
        }
 
@@ -1338,6 +1496,8 @@ void UI_block_end_ex(const bContext *C, uiBlock *block, const int xy[2], int r_x
                UI_block_align_end(block);
        }
 
+       ui_update_flexible_spacing(region, block);
+
        block->endblock = 1;
 }
 
@@ -1384,7 +1544,6 @@ void UI_block_draw(const bContext *C, uiBlock *block)
        ARegion *ar;
        uiBut *but;
        rcti rect;
-       int multisample_enabled;
 
        /* get menu region or area region */
        ar = CTX_wm_menu(C);
@@ -1394,13 +1553,8 @@ void UI_block_draw(const bContext *C, uiBlock *block)
        if (!block->endblock)
                UI_block_end(C, block);
 
-       /* disable AA, makes widgets too blurry */
-       multisample_enabled = glIsEnabled(GL_MULTISAMPLE);
-       if (multisample_enabled)
-               glDisable(GL_MULTISAMPLE);
-
        /* we set this only once */
-       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+       GPU_blend_set_func_separate(GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA);
 
        /* scale fonts */
        ui_fontscale(&style.paneltitle.points, block->aspect);
@@ -1412,21 +1566,29 @@ void UI_block_draw(const bContext *C, uiBlock *block)
        ui_but_to_pixelrect(&rect, ar, block, NULL);
 
        /* pixel space for AA widgets */
-       glMatrixMode(GL_PROJECTION);
-       glPushMatrix();
-       glMatrixMode(GL_MODELVIEW);
-       glPushMatrix();
-       glLoadIdentity();
+       GPU_matrix_push_projection();
+       GPU_matrix_push();
+       GPU_matrix_identity_set();
 
        wmOrtho2_region_pixelspace(ar);
 
        /* back */
        if (block->flag & UI_BLOCK_RADIAL)
                ui_draw_pie_center(block);
+       else if (block->flag & UI_BLOCK_POPOVER)
+               ui_draw_popover_back(ar, &style, block, &rect);
        else if (block->flag & UI_BLOCK_LOOP)
                ui_draw_menu_back(&style, block, &rect);
-       else if (block->panel)
-               ui_draw_aligned_panel(&style, block, &rect, UI_panel_category_is_visible(ar));
+       else if (block->panel) {
+               bool show_background = ar->alignment != RGN_ALIGN_FLOAT;
+               ui_draw_aligned_panel(
+                       &style, block, &rect,
+                       UI_panel_category_is_visible(ar), show_background);
+       }
+
+       BLF_batch_draw_begin();
+       UI_icon_draw_cache_begin();
+       UI_widgetbase_draw_cache_begin();
 
        /* widgets */
        for (but = block->buttons.first; but; but = but->next) {
@@ -1440,16 +1602,47 @@ void UI_block_draw(const bContext *C, uiBlock *block)
                }
        }
 
-       /* restore matrix */
-       glMatrixMode(GL_PROJECTION);
-       glPopMatrix();
-       glMatrixMode(GL_MODELVIEW);
-       glPopMatrix();
+       UI_widgetbase_draw_cache_end();
+       UI_icon_draw_cache_end();
+       BLF_batch_draw_end();
 
-       if (multisample_enabled)
-               glEnable(GL_MULTISAMPLE);
+       /* restore matrix */
+       GPU_matrix_pop_projection();
+       GPU_matrix_pop();
+}
+
+static void ui_block_message_subscribe(ARegion *ar, struct wmMsgBus *mbus, uiBlock *block)
+{
+       uiBut *but_prev = NULL;
+       /* possibly we should keep the region this block is contained in? */
+       for (uiBut *but = block->buttons.first; but; but = but->next) {
+               if (but->rnapoin.type && but->rnaprop) {
+                       /* quick check to avoid adding buttons representing a vector, multiple times. */
+                       if ((but_prev &&
+                            (but_prev->rnaprop == but->rnaprop) &&
+                            (but_prev->rnapoin.type == but->rnapoin.type) &&
+                            (but_prev->rnapoin.data == but->rnapoin.data) &&
+                            (but_prev->rnapoin.id.data == but->rnapoin.id.data)) == false)
+                       {
+                               /* TODO: could make this into utility function. */
+                               WM_msg_subscribe_rna(
+                                       mbus, &but->rnapoin, but->rnaprop,
+                                       &(const wmMsgSubscribeValue){
+                                           .owner = ar,
+                                           .user_data = ar,
+                                           .notify = ED_region_do_msg_notify_tag_redraw,
+                                       }, __func__);
+                               but_prev = but;
+                       }
+               }
+       }
+}
 
-       ui_draw_links(block);
+void UI_region_message_subscribe(ARegion *ar, struct wmMsgBus *mbus)
+{
+       for (uiBlock *block = ar->uiblocks.first; block; block = block->next) {
+               ui_block_message_subscribe(ar, mbus, block);
+       }
 }
 
 /* ************* EVENTS ************* */
@@ -1498,6 +1691,22 @@ int ui_but_is_pushed_ex(uiBut *but, double *value)
                                break;
                        case UI_BTYPE_ROW:
                        case UI_BTYPE_LISTROW:
+                       case UI_BTYPE_TAB:
+                               if ((but->type == UI_BTYPE_TAB) && but->rnaprop && but->custom_data) {
+                                       /* uiBut.custom_data points to data this tab represents (e.g. workspace).
+                                        * uiBut.rnapoin/prop store an active value (e.g. active workspace). */
+                                       if (RNA_property_type(but->rnaprop) == PROP_POINTER) {
+                                               PointerRNA active_ptr = RNA_property_pointer_get(&but->rnapoin, but->rnaprop);
+                                               if (active_ptr.data == but->custom_data) {
+                                                       is_push = true;
+                                               }
+                                       }
+                                       break;
+                               }
+                               else if (but->optype) {
+                                       break;
+                               }
+
                                UI_GET_BUT_VALUE_INIT(but, *value);
                                /* support for rna enum buts */
                                if (but->rnaprop && (RNA_property_flag(but->rnaprop) & PROP_ENUM_FLAG)) {
@@ -1533,83 +1742,6 @@ static void ui_but_update_select_flag(uiBut *but, double *value)
        }
 }
 
-static uiBut *ui_linkline_find_inlink(uiBlock *block, void *poin)
-{
-       uiBut *but;
-
-       but = block->buttons.first;
-       while (but) {
-               if (but->type == UI_BTYPE_INLINK) {
-                       if (but->poin == poin) return but;
-               }
-               but = but->next;
-       }
-       return NULL;
-}
-
-static void ui_linkline_add(ListBase *listb, uiBut *but, uiBut *bt, short deactive)
-{
-       uiLinkLine *line;
-
-       line = MEM_callocN(sizeof(uiLinkLine), "linkline");
-       BLI_addtail(listb, line);
-       line->from = but;
-       line->to = bt;
-       line->deactive = deactive;
-}
-
-uiBut *UI_block_links_find_inlink(uiBlock *block, void *poin)
-{
-       return ui_linkline_find_inlink(block, poin);
-}
-
-void UI_block_links_compose(uiBlock *block)
-{
-       uiBut *but, *bt;
-       uiLink *link;
-       void ***ppoin;
-       int a;
-
-       but = block->buttons.first;
-       while (but) {
-               if (but->type == UI_BTYPE_LINK) {
-                       link = but->link;
-
-                       /* for all pointers in the array */
-                       if (link) {
-                               if (link->ppoin) {
-                                       ppoin = link->ppoin;
-                                       for (a = 0; a < *(link->totlink); a++) {
-                                               bt = ui_linkline_find_inlink(block, (*ppoin)[a]);
-                                               if (bt) {
-                                                       if ((but->flag & UI_BUT_SCA_LINK_GREY) || (bt->flag & UI_BUT_SCA_LINK_GREY)) {
-                                                               ui_linkline_add(&link->lines, but, bt, true);
-                                                       }
-                                                       else {
-                                                               ui_linkline_add(&link->lines, but, bt, false);
-                                                       }
-
-                                               }
-                                       }
-                               }
-                               else if (link->poin) {
-                                       bt = ui_linkline_find_inlink(block, *link->poin);
-                                       if (bt) {
-                                               if ((but->flag & UI_BUT_SCA_LINK_GREY) || (bt->flag & UI_BUT_SCA_LINK_GREY)) {
-                                                       ui_linkline_add(&link->lines, but, bt, true);
-                                               }
-                                               else {
-                                                       ui_linkline_add(&link->lines, but, bt, false);
-                                               }
-                                       }
-                               }
-                       }
-               }
-               but = but->next;
-       }
-}
-
-
 /* ************************************************ */
 
 void UI_block_lock_set(uiBlock *block, bool val, const char *lockstr)
@@ -1626,48 +1758,78 @@ void UI_block_lock_clear(uiBlock *block)
        block->lockstr = NULL;
 }
 
-/* *************************************************************** */
-
-void ui_linkline_remove(uiLinkLine *line, uiBut *but)
-{
-       uiLink *link;
-       int a, b;
-
-       BLI_remlink(&but->link->lines, line);
-
-       link = line->from->link;
-
-       /* are there more pointers allowed? */
-       if (link->ppoin) {
+/* *********************** data get/set ***********************
+ * this either works with the pointed to data, or can work with
+ * an edit override pointer while dragging for example */
 
-               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))--;
+/* Get PointerRNA which will point to a data inside of an evaluated
+ * ID datablock.
+ */
+static PointerRNA ui_but_evaluated_rnapoin_get(uiBut *but)
+{
+       BLI_assert(but->rnaprop != NULL);
+       /* TODO(sergey): evil_C sounds.. EVIL! Any clear way to avoid this? */
+       PointerRNA rnapoin_eval = but->rnapoin;
+       /* If there is no animation or drivers, it doesn't matter if we read value
+        * from evaluated datablock or from original one.
+        *
+        * Reading from original one is much faster, since we don't need to do any
+        * PointerRNA remapping or hash lookup.
+        */
+       if (BKE_animdata_from_id(but->rnapoin.id.data) == NULL) {
+               return rnapoin_eval;
+       }
+       /* Same goes for the properties which can not be animated. */
+       if (!RNA_property_animateable(&but->rnapoin, but->rnaprop)) {
+               return rnapoin_eval;
+       }
+       Depsgraph *depsgraph = CTX_data_depsgraph(but->block->evil_C);
+       /* ID pointer we can always remap, they are inside of depsgraph. */
+       rnapoin_eval.id.data =
+               DEG_get_evaluated_id(depsgraph, rnapoin_eval.id.data);
+       /* Some of ID datablocks do not have their evaluated copy inside
+        * of dependency graph. If it's such datablock, no need to worry about
+        * data pointer.
+        */
+       if (rnapoin_eval.id.data == but->rnapoin.id.data) {
+               return rnapoin_eval;
+       }
+       /* For the data pointer it's getting a bit more involved, since it can
+        * whether be and ID, or can be a property deep inside of ID.
+        *
+        * We start from checking if it's an ID, since that is the less involved
+        * code path, and probably is executed in most of the cases.
+        */
+       if (but->rnapoin.data == but->rnapoin.id.data) {
+               rnapoin_eval.data = DEG_get_evaluated_id(depsgraph, rnapoin_eval.data);
+               return rnapoin_eval;
+       }
+       /* We aren't as lucky as we thought we are :(
+        *
+        * Since we don't know what the property is, we get it's RNA path
+        * relative to the original ID, and then we decent down from evaluated
+        * ID to the same property.
+        *
+        * This seems to be most straightforward way to get sub-data pointers
+        * which can be buried deep inside of ID block.
+        */
+       const char *rna_path =
+              RNA_path_from_ID_to_property(&but->rnapoin, but->rnaprop);
+       if (rna_path != NULL) {
+               PointerRNA id_ptr;
+               RNA_id_pointer_create(rnapoin_eval.id.data, &id_ptr);
+               if (!RNA_path_resolve_full(&id_ptr,
+                                          rna_path,
+                                          &rnapoin_eval,
+                                          NULL, NULL))
+               {
+                       /* TODO(sergey): Anything to do here to recover? */
                }
+               MEM_freeN((void *)rna_path);
        }
-       else {
-               *(link->poin) = NULL;
-       }
-
-       MEM_freeN(line);
-       //REDRAW
+       return rnapoin_eval;
 }
 
-/* *********************** data get/set ***********************
- * this either works with the pointed to data, or can work with
- * an edit override pointer while dragging for example */
-
 /* for buttons pointing to color for example */
 void ui_but_v3_get(uiBut *but, float vec[3])
 {
@@ -1683,16 +1845,18 @@ void ui_but_v3_get(uiBut *but, float vec[3])
 
                zero_v3(vec);
 
+               PointerRNA rnapoin_eval = ui_but_evaluated_rnapoin_get(but);
+
                if (RNA_property_type(prop) == PROP_FLOAT) {
-                       int tot = RNA_property_array_length(&but->rnapoin, prop);
+                       int tot = RNA_property_array_length(&rnapoin_eval, prop);
                        BLI_assert(tot > 0);
                        if (tot == 3) {
-                               RNA_property_float_get_array(&but->rnapoin, prop, vec);
+                               RNA_property_float_get_array(&rnapoin_eval, prop, vec);
                        }
                        else {
                                tot = min_ii(tot, 3);
                                for (a = 0; a < tot; a++) {
-                                       vec[a] = RNA_property_float_get_index(&but->rnapoin, prop, a);
+                                       vec[a] = RNA_property_float_get_index(&rnapoin_eval, prop, a);
                                }
                        }
                }
@@ -1774,7 +1938,7 @@ bool ui_but_is_float(const uiBut *but)
 
 bool ui_but_is_bool(const uiBut *but)
 {
-       if (ELEM(but->type, UI_BTYPE_TOGGLE, UI_BTYPE_TOGGLE_N, UI_BTYPE_ICON_TOGGLE, UI_BTYPE_ICON_TOGGLE_N))
+       if (ELEM(but->type, UI_BTYPE_TOGGLE, UI_BTYPE_TOGGLE_N, UI_BTYPE_ICON_TOGGLE, UI_BTYPE_ICON_TOGGLE_N, UI_BTYPE_TAB))
                return true;
 
        if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_BOOLEAN)
@@ -1872,27 +2036,29 @@ double ui_but_value_get(uiBut *but)
 
                BLI_assert(but->rnaindex != -1);
 
+               PointerRNA rnapoin_eval = ui_but_evaluated_rnapoin_get(but);
+
                switch (RNA_property_type(prop)) {
                        case PROP_BOOLEAN:
                                if (RNA_property_array_check(prop))
-                                       value = RNA_property_boolean_get_index(&but->rnapoin, prop, but->rnaindex);
+                                       value = RNA_property_boolean_get_index(&rnapoin_eval, prop, but->rnaindex);
                                else
-                                       value = RNA_property_boolean_get(&but->rnapoin, prop);
+                                       value = RNA_property_boolean_get(&rnapoin_eval, prop);
                                break;
                        case PROP_INT:
                                if (RNA_property_array_check(prop))
-                                       value = RNA_property_int_get_index(&but->rnapoin, prop, but->rnaindex);
+                                       value = RNA_property_int_get_index(&rnapoin_eval, prop, but->rnaindex);
                                else
-                                       value = RNA_property_int_get(&but->rnapoin, prop);
+                                       value = RNA_property_int_get(&rnapoin_eval, prop);
                                break;
                        case PROP_FLOAT:
                                if (RNA_property_array_check(prop))
-                                       value = RNA_property_float_get_index(&but->rnapoin, prop, but->rnaindex);
+                                       value = RNA_property_float_get_index(&rnapoin_eval, prop, but->rnaindex);
                                else
-                                       value = RNA_property_float_get(&but->rnapoin, prop);
+                                       value = RNA_property_float_get(&rnapoin_eval, prop);
                                break;
                        case PROP_ENUM:
-                               value = RNA_property_enum_get(&but->rnapoin, prop);
+                               value = RNA_property_enum_get(&rnapoin_eval, prop);
                                break;
                        default:
                                value = 0.0;
@@ -2036,7 +2202,7 @@ static bool ui_but_icon_extra_is_visible_text_clear(const uiBut *but)
 
 static bool ui_but_icon_extra_is_visible_search_unlink(const uiBut *but)
 {
-       BLI_assert(but->type == UI_BTYPE_SEARCH_MENU);
+       BLI_assert(ELEM(but->type, UI_BTYPE_SEARCH_MENU));
        return ((but->editstr == NULL) &&
                (but->drawstr[0] != '\0') &&
                (but->flag & UI_BUT_VALUE_CLEAR));
@@ -2126,7 +2292,6 @@ void ui_but_convert_to_unit_alt_name(uiBut *but, char *str, size_t maxlen)
 static void ui_get_but_string_unit(uiBut *but, char *str, int len_max, double value, bool pad, int float_precision)
 {
        UnitSettings *unit = but->block->unit;
-       const bool do_split = (unit->flag & USER_UNIT_OPT_SPLIT) != 0;
        int unit_type = UI_but_unit_type_get(but);
        int precision;
 
@@ -2143,9 +2308,9 @@ static void ui_get_but_string_unit(uiBut *but, char *str, int len_max, double va
                precision = float_precision;
        }
 
-       bUnit_AsString(
+       bUnit_AsString2(
                str, len_max, ui_get_but_scale_unit(but, value), precision,
-               unit->system, RNA_SUBTYPE_UNIT_VALUE(unit_type), do_split, pad);
+               RNA_SUBTYPE_UNIT_VALUE(unit_type), unit, pad);
 }
 
 static float ui_get_but_step_unit(uiBut *but, float step_default)
@@ -2194,14 +2359,23 @@ void ui_but_string_get_ex(uiBut *but, char *str, const size_t maxlen, const int
                *r_use_exp_float = false;
        }
 
-       if (but->rnaprop && ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
+       if (but->rnaprop && ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU, UI_BTYPE_TAB)) {
                PropertyType type;
                const char *buf = NULL;
                int buf_len;
 
                type = RNA_property_type(but->rnaprop);
 
-               if (type == PROP_STRING) {
+               if ((but->type == UI_BTYPE_TAB) && (but->custom_data)) {
+                       StructRNA *ptr_type = RNA_property_pointer_type(&but->rnapoin, but->rnaprop);
+                       PointerRNA ptr;
+
+                       /* uiBut.custom_data points to data this tab represents (e.g. workspace).
+                        * uiBut.rnapoin/prop store an active value (e.g. active workspace). */
+                       RNA_pointer_create(but->rnapoin.id.data, ptr_type, but->custom_data, &ptr);
+                       buf = RNA_struct_name_get_alloc(&ptr, str, maxlen, &buf_len);
+               }
+               else if (type == PROP_STRING) {
                        /* RNA string */
                        buf = RNA_property_string_get_alloc(&but->rnapoin, but->rnaprop, str, maxlen, &buf_len);
                }
@@ -2237,12 +2411,7 @@ void ui_but_string_get_ex(uiBut *but, char *str, const size_t maxlen, const int
                        MEM_freeN((void *)buf);
                }
        }
-       else if (but->type == UI_BTYPE_TEXT) {
-               /* string */
-               BLI_strncpy(str, but->poin, maxlen);
-               return;
-       }
-       else if (but->type == UI_BTYPE_SEARCH_MENU) {
+       else if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
                /* string */
                BLI_strncpy(str, but->poin, maxlen);
                return;
@@ -2256,12 +2425,27 @@ void ui_but_string_get_ex(uiBut *but, char *str, const size_t maxlen, const int
 
                value = ui_but_value_get(but);
 
+               PropertySubType subtype = PROP_NONE;
+               if (but->rnaprop) {
+                       subtype = RNA_property_subtype(but->rnaprop);
+               }
+
                if (ui_but_is_float(but)) {
+                       int prec = (float_precision == -1) ? ui_but_calc_float_precision(but, value) : float_precision;
+
                        if (ui_but_is_unit(but)) {
-                               ui_get_but_string_unit(but, str, maxlen, value, false, float_precision);
+                               ui_get_but_string_unit(but, str, maxlen, value, false, prec);
+                       }
+                       else if (subtype == PROP_FACTOR) {
+                               if (U.factor_display_type == USER_FACTOR_AS_FACTOR) {
+                                       BLI_snprintf(str, maxlen, "%.*f", prec, value);
+                               }
+                               else {
+                                       BLI_snprintf(str, maxlen, "%.*f", MAX2(0, prec - 2), value * 100);
+                               }
+
                        }
                        else {
-                               int prec = (float_precision == -1) ? ui_but_calc_float_precision(but, value) : float_precision;
                                if (use_exp_float) {
                                        const int int_digits_num = integer_digits_f(value);
                                        if (int_digits_num < -6 || int_digits_num > 12) {
@@ -2352,65 +2536,87 @@ char *ui_but_string_get_dynamic(uiBut *but, int *r_str_size)
        return str;
 }
 
-#ifdef WITH_PYTHON
-
 static bool ui_set_but_string_eval_num_unit(bContext *C, uiBut *but, const char *str, double *r_value)
 {
-       char str_unit_convert[256];
-       const int unit_type = UI_but_unit_type_get(but);
-
-       BLI_strncpy(str_unit_convert, str, sizeof(str_unit_convert));
-
-       /* ugly, use the draw string to get the value,
-        * this could cause problems if it includes some text which resolves to a unit */
-       bUnit_ReplaceString(
-               str_unit_convert, sizeof(str_unit_convert), but->drawstr,
-               ui_get_but_scale_unit(but, 1.0), but->block->unit->system, RNA_SUBTYPE_UNIT_VALUE(unit_type));
-
-       return BPY_execute_string_as_number(C, NULL, str_unit_convert, true, r_value);
+       const UnitSettings *unit = but->block->unit;
+       int type = RNA_SUBTYPE_UNIT_VALUE(UI_but_unit_type_get(but));
+       return user_string_to_number(C, str, unit, type, r_value);
 }
 
-#endif /* WITH_PYTHON */
-
-
-bool ui_but_string_set_eval_num(bContext *C, uiBut *but, const char *str, double *r_value)
+static bool ui_number_from_string(bContext *C, const char *str, double *r_value)
 {
-       bool ok = false;
-
 #ifdef WITH_PYTHON
+       return BPY_execute_string_as_number(C, NULL, str, true, r_value);
+#else
+       *r_value = atof(str);
+       return true;
+#endif
+}
 
-       if (str[0] != '\0') {
-               bool is_unit_but = (ui_but_is_float(but) && ui_but_is_unit(but));
-               /* only enable verbose if we won't run again with units */
-               if (BPY_execute_string_as_number(C, NULL, str, is_unit_but == false, r_value)) {
-                       /* if the value parsed ok without unit conversion
-                        * this button may still need a unit multiplier */
-                       if (is_unit_but) {
-                               char str_new[128];
-
-                               BLI_snprintf(str_new, sizeof(str_new), "%f", *r_value);
-                               ok = ui_set_but_string_eval_num_unit(C, but, str_new, r_value);
-                       }
-                       else {
-                               ok = true; /* parse normal string via py (no unit conversion needed) */
-                       }
+static bool ui_number_from_string_factor(bContext *C, const char *str, double *r_value)
+{
+       int len = strlen(str);
+       if (BLI_strn_endswith(str, "%", len)) {
+               char *str_new = BLI_strdupn(str, len - 1);
+               bool success = ui_number_from_string(C, str_new, r_value);
+               MEM_freeN(str_new);
+               *r_value /= 100.0;
+               return success;
+       }
+       else {
+               if (!ui_number_from_string(C, str, r_value)) {
+                       return false;
                }
-               else if (is_unit_but) {
-                       /* parse failed, this is a unit but so run replacements and parse again */
-                       ok = ui_set_but_string_eval_num_unit(C, but, str, r_value);
+               if (U.factor_display_type == USER_FACTOR_AS_PERCENTAGE) {
+                       *r_value /= 100.0;
                }
+               return true;
        }
+}
 
-#else /* WITH_PYTHON */
-
-       *r_value = atof(str);
-       ok = true;
+static bool ui_number_from_string_percentage(bContext *C, const char *str, double *r_value)
+{
+       int len = strlen(str);
+       if (BLI_strn_endswith(str, "%", len)) {
+               char *str_new = BLI_strdupn(str, len - 1);
+               bool success = ui_number_from_string(C, str_new, r_value);
+               MEM_freeN(str_new);
+               return success;
+       }
+       else {
+               return ui_number_from_string(C, str, r_value);
+       }
+}
 
-       UNUSED_VARS(C, but);
+bool ui_but_string_set_eval_num(bContext *C, uiBut *but, const char *str, double *r_value)
+{
+       if (str[0] == '\0') {
+               *r_value = 0.0;
+               return true;
+       }
 
-#endif /* WITH_PYTHON */
+       PropertySubType subtype = PROP_NONE;
+       if (but->rnaprop) {
+               subtype = RNA_property_subtype(but->rnaprop);
+       }
 
-       return ok;
+       if (ui_but_is_float(but)) {
+               if (ui_but_is_unit(but)) {
+                       return ui_set_but_string_eval_num_unit(C, but, str, r_value);
+               }
+               else if (subtype == PROP_FACTOR) {
+                       return ui_number_from_string_factor(C, str, r_value);
+               }
+               else if (subtype == PROP_PERCENTAGE) {
+                       return ui_number_from_string_percentage(C, str, r_value);
+               }
+               else {
+                       return ui_number_from_string(C, str, r_value);
+               }
+       }
+       else {
+               return ui_number_from_string(C, str, r_value);
+       }
 }
 
 /* just the assignment/free part */
@@ -2442,7 +2648,7 @@ static void ui_but_string_free_internal(uiBut *but)
 
 bool ui_but_string_set(bContext *C, uiBut *but, const char *str)
 {
-       if (but->rnaprop && ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
+       if (but->rnaprop && but->rnapoin.data && ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
                if (RNA_property_editable(&but->rnapoin, but->rnaprop)) {
                        PropertyType type;
 
@@ -2454,20 +2660,29 @@ bool ui_but_string_set(bContext *C, uiBut *but, const char *str)
                                return true;
                        }
                        else if (type == PROP_POINTER) {
-                               /* RNA pointer */
-                               PointerRNA ptr, rptr;
-                               PropertyRNA *prop;
-
                                if (str[0] == '\0') {
                                        RNA_property_pointer_set(&but->rnapoin, but->rnaprop, PointerRNA_NULL);
                                        return true;
                                }
                                else {
-                                       ptr = but->rnasearchpoin;
-                                       prop = but->rnasearchprop;
-
-                                       if (prop && RNA_property_collection_lookup_string(&ptr, prop, str, &rptr))
+                                       /* RNA pointer */
+                                       PointerRNA rptr;
+                                       PointerRNA ptr = but->rnasearchpoin;
+                                       PropertyRNA *prop = but->rnasearchprop;
+
+                                       /* This is kind of hackish, in theory think we could only ever use the second member of
+                                        * this if/else, since ui_searchbox_apply() is supposed to always set that pointer when
+                                        * we are storing pointers... But keeping str search first for now, to try to break as little as
+                                        * possible existing code. All this is band-aids anyway.
+                                        * Fact remains, using editstr as main 'reference' over whole search button thingy is utterly weak
+                                        * and should be redesigned imho, but that's not a simple task. */
+                                       if (prop && RNA_property_collection_lookup_string(&ptr, prop, str, &rptr)) {
                                                RNA_property_pointer_set(&but->rnapoin, but->rnaprop, rptr);
+                                       }
+                                       else if (but->func_arg2 != NULL) {
+                                               RNA_pointer_create(NULL, RNA_property_pointer_type(&but->rnapoin, but->rnaprop), but->func_arg2, &rptr);
+                                               RNA_property_pointer_set(&but->rnapoin, but->rnaprop, rptr);
+                                       }
 
                                        return true;
                                }
@@ -2487,10 +2702,32 @@ bool ui_but_string_set(bContext *C, uiBut *but, const char *str)
                        }
                }
        }
+       else if (but->type == UI_BTYPE_TAB) {
+               if (but->rnaprop && but->custom_data) {
+                       StructRNA *ptr_type = RNA_property_pointer_type(&but->rnapoin, but->rnaprop);
+                       PointerRNA ptr;
+                       PropertyRNA *prop;
+
+                       /* uiBut.custom_data points to data this tab represents (e.g. workspace).
+                        * uiBut.rnapoin/prop store an active value (e.g. active workspace). */
+                       RNA_pointer_create(but->rnapoin.id.data, ptr_type, but->custom_data, &ptr);
+                       prop = RNA_struct_name_property(ptr_type);
+                       if (RNA_property_editable(&ptr, prop)) {
+                               RNA_property_string_set(&ptr, prop, str);
+                       }
+               }
+       }
        else if (but->type == UI_BTYPE_TEXT) {
                /* string */
-               if (ui_but_is_utf8(but)) BLI_strncpy_utf8(but->poin, str, but->hardmax);
-               else BLI_strncpy(but->poin, str, but->hardmax);
+               if (!but->poin) {
+                       str = "";
+               }
+               else if (ui_but_is_utf8(but)) {
+                       BLI_strncpy_utf8(but->poin, str, but->hardmax);
+               }
+               else {
+                       BLI_strncpy(but->poin, str, but->hardmax);
+               }
 
                return true;
        }
@@ -2674,14 +2911,6 @@ static void ui_set_but_soft_range(uiBut *but)
 
 /* ******************* Free ********************/
 
-static void ui_free_link(uiLink *link)
-{
-       if (link) {
-               BLI_freelistN(&link->lines);
-               MEM_freeN(link);
-       }
-}
-
 /* can be called with C==NULL */
 static void ui_but_free(const bContext *C, uiBut *but)
 {
@@ -2702,6 +2931,10 @@ static void ui_but_free(const bContext *C, uiBut *but)
                MEM_freeN(but->hold_argN);
        }
 
+       if (!but->editstr && but->free_search_arg) {
+               MEM_SAFE_FREE(but->search_arg);
+       }
+
        if (but->active) {
                /* XXX solve later, buttons should be free-able without context ideally,
                 * however they may have open tooltips or popup windows, which need to
@@ -2718,7 +2951,6 @@ static void ui_but_free(const bContext *C, uiBut *but)
        if (but->str && but->str != but->strdata) {
                MEM_freeN(but->str);
        }
-       ui_free_link(but->link);
 
        if ((but->type == UI_BTYPE_IMAGE) && but->poin) {
                IMB_freeImBuf((struct ImBuf *)but->poin);
@@ -2760,6 +2992,27 @@ void UI_block_free(const bContext *C, uiBlock *block)
        MEM_freeN(block);
 }
 
+void UI_blocklist_update_window_matrix(const bContext *C, const ListBase *lb)
+{
+       ARegion *region = CTX_wm_region(C);
+       wmWindow *window = CTX_wm_window(C);
+
+       for (uiBlock *block = lb->first; block; block = block->next) {
+               if (block->active) {
+                       ui_update_window_matrix(window, region, block);
+               }
+       }
+}
+
+void UI_blocklist_draw(const bContext *C, const ListBase *lb)
+{
+       for (uiBlock *block = lb->first; block; block = block->next) {
+               if (block->active) {
+                       UI_block_draw(C, block);
+               }
+       }
+}
+
 /* can be called with C==NULL */
 void UI_blocklist_free(const bContext *C, ListBase *lb)
 {
@@ -2816,7 +3069,6 @@ uiBlock *UI_block_begin(const bContext *C, ARegion *region, const char *name, sh
        uiBlock *block;
        wmWindow *window;
        Scene *scn;
-       int getsizex, getsizey;
 
        window = CTX_wm_window(C);
        scn = CTX_data_scene(C);
@@ -2827,52 +3079,38 @@ uiBlock *UI_block_begin(const bContext *C, ARegion *region, const char *name, sh
        block->evil_C = (void *)C;  /* XXX */
 
        if (scn) {
-               block->color_profile = true;
-
                /* store display device name, don't lookup for transformations yet
                 * block could be used for non-color displays where looking up for transformation
                 * would slow down redraw, so only lookup for actual transform when it's indeed
                 * needed
                 */
-               BLI_strncpy(block->display_device, scn->display_settings.display_device, sizeof(block->display_device));
+               STRNCPY(block->display_device, scn->display_settings.display_device);
 
                /* copy to avoid crash when scene gets deleted with ui still open */
                block->unit = MEM_mallocN(sizeof(scn->unit), "UI UnitSettings");
                memcpy(block->unit, &scn->unit, sizeof(scn->unit));
        }
+       else {
+               STRNCPY(block->display_device, IMB_colormanagement_display_get_default_name());
+       }
 
        BLI_strncpy(block->name, name, sizeof(block->name));
 
        if (region)
                UI_block_region_set(block, region);
 
-       /* window matrix and aspect */
-       if (region && region->swinid) {
-               wm_subwindow_matrix_get(window, region->swinid, block->winmat);
-               wm_subwindow_size_get(window, region->swinid, &getsizex, &getsizey);
-
-               block->aspect = 2.0f / fabsf(getsizex * block->winmat[0][0]);
-       }
-       else {
-               /* no subwindow created yet, for menus for example, so we
-                * use the main window instead, since buttons are created
-                * there anyway */
-               wm_subwindow_matrix_get(window, window->screen->mainwin, block->winmat);
-               wm_subwindow_size_get(window, window->screen->mainwin, &getsizex, &getsizey);
+       /* Set window matrix and aspect for region and OpenGL state. */
+       ui_update_window_matrix(window, region, block);
 
-               block->aspect = 2.0f / fabsf(getsizex * block->winmat[0][0]);
+       /* Tag as popup menu if not created within a region. */
+       if (!(region && region->visible)) {
                block->auto_open = true;
-               block->flag |= UI_BLOCK_LOOP; /* tag as menu */
+               block->flag |= UI_BLOCK_LOOP;
        }
 
        return block;
 }
 
-uiBlock *UI_block_find_in_region(const char *name, ARegion *ar)
-{
-       return BLI_findstring(&ar->uiblocks, name, offsetof(uiBlock, name));
-}
-
 void UI_block_emboss_set(uiBlock *block, char dt)
 {
        block->dt = dt;
@@ -2883,6 +3121,71 @@ void UI_block_theme_style_set(uiBlock *block, char theme_style)
        block->theme_style = theme_style;
 }
 
+static void ui_but_build_drawstr_float(uiBut *but, double value)
+{
+       size_t slen = 0;
+       STR_CONCAT(but->drawstr, slen, but->str);
+
+       PropertySubType subtype = PROP_NONE;
+       if (but->rnaprop) {
+               subtype = RNA_property_subtype(but->rnaprop);
+       }
+
+       if (value == (double)FLT_MAX) {
+               STR_CONCAT(but->drawstr, slen, "inf");
+       }
+       else if (value == (double)-FLT_MIN) {
+               STR_CONCAT(but->drawstr, slen, "-inf");
+       }
+       else if (subtype == PROP_PERCENTAGE) {
+               int prec = ui_but_calc_float_precision(but, value);
+               STR_CONCATF(but->drawstr, slen, "%.*f %%", prec, value);
+       }
+       else if (subtype == PROP_PIXEL) {
+               int prec = ui_but_calc_float_precision(but, value);
+               STR_CONCATF(but->drawstr, slen, "%.*f px", prec, value);
+       }
+       else if (subtype == PROP_FACTOR) {
+               int precision = ui_but_calc_float_precision(but, value);
+
+               if (U.factor_display_type == USER_FACTOR_AS_FACTOR) {
+                       STR_CONCATF(but->drawstr, slen, "%.*f", precision, value);
+               }
+               else {
+                       STR_CONCATF(but->drawstr, slen, "%.*f %%", MAX2(0, precision - 2), value * 100);
+               }
+       }
+       else if (ui_but_is_unit(but)) {
+               char new_str[sizeof(but->drawstr)];
+               ui_get_but_string_unit(but, new_str, sizeof(new_str), value, true, -1);
+               STR_CONCAT(but->drawstr, slen, new_str);
+       }
+       else {
+               int prec = ui_but_calc_float_precision(but, value);
+               STR_CONCATF(but->drawstr, slen, "%.*f", prec, value);
+       }
+}
+
+static void ui_but_build_drawstr_int(uiBut *but, int value)
+{
+       size_t slen = 0;
+       STR_CONCAT(but->drawstr, slen, but->str);
+
+       PropertySubType subtype = PROP_NONE;
+       if (but->rnaprop) {
+               subtype = RNA_property_subtype(but->rnaprop);
+       }
+
+       STR_CONCATF(but->drawstr, slen, "%d", value);
+
+       if (subtype == PROP_PERCENTAGE) {
+               STR_CONCAT(but->drawstr, slen, "%");
+       }
+       else if (subtype == PROP_PIXEL) {
+               STR_CONCAT(but->drawstr, slen, " px");
+       }
+}
+
 /**
  * \param but: Button to update.
  * \param validate: When set, this function may change the button value.
@@ -2892,7 +3195,6 @@ static void ui_but_update_ex(uiBut *but, const bool validate)
 {
        /* if something changed in the button */
        double value = UI_BUT_VALUE_UNSET;
-//     float okwidth; // UNUSED
 
        ui_but_update_select_flag(but, &value);
 
@@ -2927,9 +3229,12 @@ static void ui_but_update_ex(uiBut *but, const bool validate)
 
                case UI_BTYPE_ICON_TOGGLE:
                case UI_BTYPE_ICON_TOGGLE_N:
-                       if (!but->rnaprop || (RNA_property_flag(but->rnaprop) & PROP_ICONS_CONSECUTIVE)) {
-                               if (but->flag & UI_SELECT) but->iconadd = 1;
-                               else but->iconadd = 0;
+                       if ((but->rnaprop == NULL) || (RNA_property_flag(but->rnaprop) & PROP_ICONS_CONSECUTIVE)) {
+                               if (but->rnaprop && RNA_property_flag(but->rnaprop) & PROP_ICONS_REVERSE) {
+                                       but->drawflag |= UI_BUT_ICON_REVERSE;
+                               }
+
+                               but->iconadd = (but->flag & UI_SELECT) ? 1 : 0;
                        }
                        break;
 
@@ -2954,8 +3259,8 @@ static void ui_but_update_ex(uiBut *but, const bool validate)
 
                                                EnumPropertyItem item;
                                                if (RNA_property_enum_item_from_value_gettexted(
-                                                       but->block->evil_C,
-                                                       &but->rnapoin, but->rnaprop, value_enum, &item))
+                                                           but->block->evil_C,
+                                                           &but->rnapoin, but->rnaprop, value_enum, &item))
                                                {
                                                        size_t slen = strlen(item.name);
                                                        ui_but_string_free_internal(but);
@@ -2970,52 +3275,15 @@ static void ui_but_update_ex(uiBut *but, const bool validate)
 
                case UI_BTYPE_NUM:
                case UI_BTYPE_NUM_SLIDER:
-
-                       if (!but->editstr) {
-                               const char *drawstr_suffix = NULL;
-                               size_t slen;
-
-                               UI_GET_BUT_VALUE_INIT(but, value);
-
-                               slen = BLI_strncpy_rlen(but->drawstr, but->str, sizeof(but->drawstr));
-
-                               if (ui_but_is_float(but)) {
-                                       if (value == (double) FLT_MAX) {
-                                               slen += BLI_strncpy_rlen(but->drawstr + slen, "inf", sizeof(but->drawstr) - slen);
-                                       }
-                                       else if (value == (double) -FLT_MAX) {
-                                               slen += BLI_strncpy_rlen(but->drawstr + slen, "-inf", sizeof(but->drawstr) - slen);
-                                       }
-                                       /* support length type buttons */
-                                       else if (ui_but_is_unit(but)) {
-                                               char new_str[sizeof(but->drawstr)];
-                                               ui_get_but_string_unit(but, new_str, sizeof(new_str), value, true, -1);
-                                               slen += BLI_strncpy_rlen(but->drawstr + slen, new_str, sizeof(but->drawstr) - slen);
-                                       }
-                                       else {
-                                               const int prec = ui_but_calc_float_precision(but, value);
-                                               slen += BLI_snprintf_rlen(but->drawstr + slen, sizeof(but->drawstr) - slen, "%.*f", prec, value);
-                                       }
-                               }
-                               else {
-                                       slen += BLI_snprintf_rlen(but->drawstr + slen, sizeof(but->drawstr) - slen, "%d", (int)value);
-                               }
-
-                               if (but->rnaprop) {
-                                       PropertySubType pstype = RNA_property_subtype(but->rnaprop);
-
-                                       if (pstype == PROP_PERCENTAGE) {
-                                               drawstr_suffix = "%";
-                                       }
-                                       else if (pstype == PROP_PIXEL) {
-                                               drawstr_suffix = " px";
-                                       }
-                               }
-
-                               if (drawstr_suffix) {
-                                       BLI_strncpy(but->drawstr + slen, drawstr_suffix, sizeof(but->drawstr) - slen);
-                               }
-
+                       if (but->editstr) {
+                               break;
+                       }
+                       UI_GET_BUT_VALUE_INIT(but, value);
+                       if (ui_but_is_float(but)) {
+                               ui_but_build_drawstr_float(but, value);
+                       }
+                       else {
+                               ui_but_build_drawstr_int(but, (int)value);
                        }
                        break;
 
@@ -3136,25 +3404,14 @@ void ui_block_cm_to_display_space_v3(uiBlock *block, float pixel[3])
        IMB_colormanagement_scene_linear_to_display_v3(pixel, display);
 }
 
-void ui_block_cm_to_scene_linear_v3(uiBlock *block, float pixel[3])
+static uiBut *ui_but_alloc(const eButType type)
 {
-       struct ColorManagedDisplay *display = ui_block_cm_display_get(block);
-
-       IMB_colormanagement_display_to_scene_linear_v3(pixel, display);
-}
-
-void ui_block_cm_to_display_space_range(uiBlock *block, float *min, float *max)
-{
-       struct ColorManagedDisplay *display = ui_block_cm_display_get(block);
-       float pixel[3];
-
-       copy_v3_fl(pixel, *min);
-       IMB_colormanagement_scene_linear_to_display_v3(pixel, display);
-       *min = min_fff(UNPACK3(pixel));
-
-       copy_v3_fl(pixel, *max);
-       IMB_colormanagement_scene_linear_to_display_v3(pixel, display);
-       *max = max_fff(UNPACK3(pixel));
+       switch (type) {
+               case UI_BTYPE_TAB:
+                       return MEM_callocN(sizeof(uiButTab), "uiButTab");
+               default:
+                       return MEM_callocN(sizeof(uiBut), "uiBut");
+       }
 }
 
 /**
@@ -3190,7 +3447,7 @@ static uiBut *ui_def_but(
                }
        }
 
-       but = MEM_callocN(sizeof(uiBut), "uiBut");
+       but = ui_but_alloc(type & BUTTYPE);
 
        but->type = type & BUTTYPE;
        but->pointype = type & UI_BUT_POIN_TYPES;
@@ -3247,14 +3504,15 @@ static uiBut *ui_def_but(
 
        if (block->flag & UI_BLOCK_RADIAL) {
                but->drawflag |= UI_BUT_TEXT_LEFT;
-               if (but->str && but->str[0])
+               if (but->str && but->str[0]) {
                        but->drawflag |= UI_BUT_ICON_LEFT;
+               }
        }
-       else if ((block->flag & UI_BLOCK_LOOP) ||
+       else if (((block->flag & UI_BLOCK_LOOP) && !ui_block_is_popover(block)) ||
                 ELEM(but->type,
                      UI_BTYPE_MENU, UI_BTYPE_TEXT, UI_BTYPE_LABEL,
                      UI_BTYPE_BLOCK, UI_BTYPE_BUT_MENU, UI_BTYPE_SEARCH_MENU,
-                     UI_BTYPE_PROGRESS_BAR))
+                     UI_BTYPE_PROGRESS_BAR, UI_BTYPE_POPOVER))
        {
                but->drawflag |= (UI_BUT_TEXT_LEFT | UI_BUT_ICON_LEFT);
        }
@@ -3277,7 +3535,8 @@ static uiBut *ui_def_but(
                 UI_BTYPE_BLOCK, UI_BTYPE_BUT, UI_BTYPE_LABEL,
                 UI_BTYPE_PULLDOWN, UI_BTYPE_ROUNDBOX, UI_BTYPE_LISTBOX,
                 UI_BTYPE_BUT_MENU, UI_BTYPE_SCROLL, UI_BTYPE_GRIP,
-                UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE) ||
+                UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE,
+                UI_BTYPE_SEPR_SPACER) ||
            (but->type >= UI_BTYPE_SEARCH_MENU))
        {
                /* pass */
@@ -3334,7 +3593,7 @@ static void ui_def_but_rna__menu(bContext *UNUSED(C), uiLayout *layout, void *bu
 
        int totitems = 0;
        int columns, rows, a, b;
-       int column_start = 0, column_end = 0;
+       int column_end = 0;
        int nbr_entries_nosepr = 0;
 
        UI_block_flag_enable(block, UI_BLOCK_MOVEMOUSE_QUIT);
@@ -3347,7 +3606,7 @@ static void ui_def_but_rna__menu(bContext *UNUSED(C), uiLayout *layout, void *bu
 
        for (item = item_array; item->identifier; item++, totitems++) {
                if (!item->identifier[0]) {
-                       /* inconsistent, but menus with labels do not look good flipped */
+                       /* inconsistent, but menus with categories do not look good flipped */
                        if (item->name) {
                                block->flag |= UI_BLOCK_NO_FLIP;
                                nbr_entries_nosepr++;
@@ -3371,10 +3630,12 @@ static void ui_def_but_rna__menu(bContext *UNUSED(C), uiLayout *layout, void *bu
        while (rows * columns < totitems)
                rows++;
 
-       /* Title */
-       uiDefBut(block, UI_BTYPE_LABEL, 0, RNA_property_ui_name(but->rnaprop),
-                0, 0, UI_UNIT_X * 5, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
-       uiItemS(layout);
+       if (block->flag & UI_BLOCK_NO_FLIP) {
+               /* Title at the top for menus with categories. */
+               uiDefBut(block, UI_BTYPE_LABEL, 0, RNA_property_ui_name(but->rnaprop),
+                        0, 0, UI_UNIT_X * 5, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
+               uiItemS(layout);
+       }
 
        /* note, item_array[...] is reversed on access */
 
@@ -3385,7 +3646,6 @@ static void ui_def_but_rna__menu(bContext *UNUSED(C), uiLayout *layout, void *bu
                if (a == column_end) {
                        /* start new column, and find out where it ends in advance, so we
                         * can flip the order of items properly per column */
-                       column_start = a;
                        column_end = totitems;
 
                        for (b = a + 1; b < totitems; b++) {
@@ -3401,12 +3661,7 @@ static void ui_def_but_rna__menu(bContext *UNUSED(C), uiLayout *layout, void *bu
                        column = uiLayoutColumn(split, false);
                }
 
-               if (block->flag & UI_BLOCK_NO_FLIP) {
-                       item = &item_array[a];
-               }
-               else {
-                       item = &item_array[(column_start + column_end - 1 - a)];
-               }
+               item = &item_array[a];
 
                if (!item->identifier[0]) {
                        if (item->name) {
@@ -3437,6 +3692,13 @@ static void ui_def_but_rna__menu(bContext *UNUSED(C), uiLayout *layout, void *bu
                }
        }
 
+       if (!(block->flag & UI_BLOCK_NO_FLIP)) {
+               /* Title at the bottom for menus without categories. */
+               uiItemS(layout);
+               uiDefBut(block, UI_BTYPE_LABEL, 0, RNA_property_ui_name(but->rnaprop),
+                        0, 0, UI_UNIT_X * 5, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
+       }
+
        UI_block_layout_set_current(block, layout);
 
        if (free) {
@@ -3446,6 +3708,12 @@ static void ui_def_but_rna__menu(bContext *UNUSED(C), uiLayout *layout, void *bu
        block->flag |= UI_BLOCK_IS_FLIP;
 }
 
+static void ui_but_submenu_enable(uiBlock *block, uiBut *but)
+{
+       but->flag |= UI_BUT_ICON_SUBMENU;
+       block->content_hints |= UI_BLOCK_CONTAINS_SUBMENU_BUT;
+}
+
 /**
  * ui_def_but_rna_propname and ui_def_but_rna
  * both take the same args except for propname vs prop, this is done so we can
@@ -3582,7 +3850,7 @@ static uiBut *ui_def_but_rna(
 
        if (type == UI_BTYPE_MENU) {
                if (but->dt == UI_EMBOSS_PULLDOWN) {
-                       but->flag |= UI_BUT_ICON_SUBMENU;
+                       ui_but_submenu_enable(block, but);
                }
        }
        else if (type == UI_BTYPE_SEARCH_MENU) {
@@ -3593,7 +3861,7 @@ static uiBut *ui_def_but_rna(
        }
 
        const char *info;
-       if (!RNA_property_editable_info(&but->rnapoin, prop, &info)) {
+       if (but->rnapoin.data && !RNA_property_editable_info(&but->rnapoin, prop, &info)) {
                ui_def_but_rna__disable(but, info);
        }
 
@@ -4012,19 +4280,6 @@ uiBut *uiDefIconTextButO(uiBlock *block, int type, const char *opname, int opcon
 
 /* END Button containing both string label and icon */
 
-void UI_but_link_set(uiBut *but, void **poin, void ***ppoin, short *tot, int from, int to)
-{
-       uiLink *link;
-
-       link = but->link = MEM_callocN(sizeof(uiLink), "new uilink");
-
-       link->poin = poin;
-       link->ppoin = ppoin;
-       link->totlink = tot;
-       link->fromcode = from;
-       link->tocode = to;
-}
-
 /* cruft to make uiBlock and uiBut private */
 
 int UI_blocklist_min_y_get(ListBase *lb)
@@ -4297,6 +4552,12 @@ void UI_but_func_tooltip_set(uiBut *but, uiButToolTipFunc func, void *argN)
        but->tip_argN = argN;
 }
 
+void UI_but_func_pushed_state_set(uiBut *but, uiButPushedStateFunc func, void *arg)
+{
+       but->pushed_state_func = func;
+       but->pushed_state_arg = arg;
+}
+
 uiBut *uiDefBlockBut(uiBlock *block, uiBlockCreateFunc func, void *arg, const char *str, int x, int y, short width, short height, const char *tip)
 {
        uiBut *but = ui_def_but(block, UI_BTYPE_BLOCK, 0, str, x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip);
@@ -4341,7 +4602,7 @@ uiBut *uiDefIconTextMenuBut(uiBlock *block, uiMenuCreateFunc func, void *arg, in
        ui_def_but_icon(but, icon, UI_HAS_ICON);
 
        but->drawflag |= UI_BUT_ICON_LEFT;
-       but->flag |= UI_BUT_ICON_SUBMENU;
+       ui_but_submenu_enable(block, but);
 
        but->menu_create_func = func;
        ui_but_update(but);
@@ -4373,7 +4634,7 @@ uiBut *uiDefIconTextBlockBut(uiBlock *block, uiBlockCreateFunc func, void *arg,
                but->drawflag |= UI_BUT_ICON_LEFT;
        }
        but->flag |= UI_HAS_ICON;
-       but->flag |= UI_BUT_ICON_SUBMENU;
+       ui_but_submenu_enable(block, but);
 
        but->block_create_func = func;
        ui_but_update(but);
@@ -4494,7 +4755,7 @@ static void operator_enum_search_cb(const struct bContext *C, void *but, const c
                        /* note: need to give the index rather than the
                         * identifier because the enum can be freed */
                        if (BLI_strcasestr(item->name, str)) {
-                               if (false == UI_search_item_add(items, item->name, POINTER_FROM_INT(item->value), 0))
+                               if (false == UI_search_item_add(items, item->name, POINTER_FROM_INT(item->value), item->icon))
                                        break;
                        }
                }
@@ -4625,14 +4886,21 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...)
                                tmp = BLI_strdup(RNA_property_identifier(but->rnaprop));
                }
                else if (type == BUT_GET_RNASTRUCT_IDENTIFIER) {
-                       if (but->rnaprop)
+                       if (but->rnaprop && but->rnapoin.data)
                                tmp = BLI_strdup(RNA_struct_identifier(but->rnapoin.type));
                        else if (but->optype)
                                tmp = BLI_strdup(but->optype->idname);
                        else if (ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_PULLDOWN)) {
                                MenuType *mt = UI_but_menutype_get(but);
-                               if (mt)
+                               if (mt) {
                                        tmp = BLI_strdup(mt->idname);
+                               }
+                       }
+                       else if (but->type == UI_BTYPE_POPOVER) {
+                               PanelType *pt = UI_but_paneltype_get(but);
+                               if (pt) {
+                                       tmp = BLI_strdup(pt->idname);
+                               }
                        }
                }
                else if (ELEM(type, BUT_GET_RNA_LABEL, BUT_GET_RNA_TIP)) {
@@ -4696,12 +4964,15 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...)
                                /* enum property */
                                ptr = &but->rnapoin;
                                prop = but->rnaprop;
-                               value = (but->type == UI_BTYPE_ROW) ? (int)but->hardmax : (int)ui_but_value_get(but);
+                               value = (ELEM(but->type, UI_BTYPE_ROW, UI_BTYPE_TAB)) ? (int)but->hardmax : (int)ui_but_value_get(but);
                        }
                        else if (but->optype) {
                                PointerRNA *opptr = UI_but_operator_ptr_get(but);
                                wmOperatorType *ot = but->optype;
 
+                               /* so the context is passed to itemf functions */
+                               WM_operator_properties_sanitize(opptr, false);
+
                                /* if the default property of the operator is enum and it is set,
                                 * fetch the tooltip of the selected value so that "Snap" and "Mirror"
                                 * operator menus in the Anim Editors will show tooltips for the different