Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--source/blender/blenkernel/BKE_screen.h19
-rw-r--r--source/blender/editors/include/UI_interface.h21
-rw-r--r--source/blender/editors/interface/interface_align.c4
-rw-r--r--source/blender/editors/interface/interface_intern.h1
-rw-r--r--source/blender/editors/interface/interface_panel.c497
-rw-r--r--source/blender/editors/interface/interface_widgets.c26
-rw-r--r--source/blender/editors/screen/area.c106
-rw-r--r--source/blender/makesdna/DNA_screen_types.h20
-rw-r--r--source/blender/makesrna/intern/rna_ui.c17
9 files changed, 658 insertions, 53 deletions
diff --git a/source/blender/blenkernel/BKE_screen.h b/source/blender/blenkernel/BKE_screen.h
index 0d8f5ed550c..8794558b81f 100644
--- a/source/blender/blenkernel/BKE_screen.h
+++ b/source/blender/blenkernel/BKE_screen.h
@@ -229,6 +229,25 @@ typedef struct PanelType {
/* draw entirely, view changes should be handled here */
void (*draw)(const struct bContext *C, struct Panel *panel);
+ /* For instanced panels corresponding to a list: */
+
+ /** Reorder function, called when drag and drop finishes. */
+ void (*reorder)(struct bContext *C, struct Panel *pa, int new_index);
+ /**
+ * Get the panel and subpanel's expansion state from the expansion flag in the corresponding data
+ * item. Called on draw updates.
+ * \note Subpanels are indexed in depth first order, the visualorder you would see if all panels
+ * were expanded.
+ */
+ short (*get_list_data_expand_flag)(const struct bContext *C, struct Panel *pa);
+ /**
+ * Set the expansion bitfield from the closed / open state of this panel and its subpanels.
+ * Called when the expansion state of the panel changes with user input.
+ * \note Subpanels are indexed in depth first order, the visual order you would see if all panels
+ * were expanded.
+ */
+ void (*set_list_data_expand_flag)(const struct bContext *C, struct Panel *pa, short expand_flag);
+
/* sub panels */
struct PanelType *parent;
ListBase children;
diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h
index 3ad1608b47b..c95f517b155 100644
--- a/source/blender/editors/include/UI_interface.h
+++ b/source/blender/editors/include/UI_interface.h
@@ -239,6 +239,8 @@ enum {
#define UI_PANEL_CATEGORY_MARGIN_WIDTH (U.widget_unit * 1.0f)
+#define UI_PANEL_BOX_STYLE_MARGIN (U.widget_unit * 0.2f)
+
/* but->drawflag - these flags should only affect how the button is drawn. */
/* Note: currently, these flags _are not passed_ to the widget's state() or draw() functions
* (except for the 'align' ones)!
@@ -1679,6 +1681,7 @@ void UI_panel_end(const struct ScrArea *area,
int width,
int height,
bool open);
+
void UI_panels_scale(struct ARegion *region, float new_width);
void UI_panel_label_offset(struct uiBlock *block, int *r_x, int *r_y);
int UI_panel_size_y(const struct Panel *panel);
@@ -1702,6 +1705,24 @@ void UI_panel_category_draw_all(struct ARegion *region, const char *category_id_
struct PanelType *UI_paneltype_find(int space_id, int region_id, const char *idname);
+/* Polyinstantiated panels for representing a list of data. */
+struct Panel *UI_panel_add_instanced(struct ScrArea *area,
+ struct ARegion *region,
+ struct ListBase *panels,
+ char *panel_idname,
+ int list_index);
+void UI_panels_free_instanced(struct bContext *C, struct ARegion *region);
+
+#define LIST_PANEL_UNIQUE_STR_LEN 4
+void UI_list_panel_unique_str(struct Panel *panel, char *r_name);
+
+void UI_panel_set_expand_from_list_data(const struct bContext *C, struct Panel *panel);
+
+typedef void (*uiListPanelIDFromDataFunc)(void *data_link, char *r_idname);
+bool UI_panel_list_matches_data(struct ARegion *region,
+ struct ListBase *data,
+ uiListPanelIDFromDataFunc panel_idname_func);
+
/* Handlers
*
* Handlers that can be registered in regions, areas and windows for
diff --git a/source/blender/editors/interface/interface_align.c b/source/blender/editors/interface/interface_align.c
index acbdf564054..32cae609395 100644
--- a/source/blender/editors/interface/interface_align.c
+++ b/source/blender/editors/interface/interface_align.c
@@ -506,7 +506,7 @@ void ui_block_align_calc(uiBlock *block, const ARegion *region)
butal->but->drawflag |= align;
butal_other->but->drawflag |= align_opp;
- if (butal->dists[side]) {
+ if (!IS_EQF(butal->dists[side], 0.0f)) {
float *delta = &butal->dists[side];
if (*butal->borders[side] < *butal_other->borders[side_opp]) {
@@ -517,7 +517,7 @@ void ui_block_align_calc(uiBlock *block, const ARegion *region)
}
co = (*butal->borders[side] += *delta);
- if (butal_other->dists[side_opp]) {
+ if (!IS_EQF(butal_other->dists[side_opp], 0.0f)) {
BLI_assert(butal_other->dists[side_opp] * 0.5f == fabsf(*delta));
*butal_other->borders[side_opp] = co;
butal_other->dists[side_opp] = 0.0f;
diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h
index 5b68ccf9e7c..6cd990ec2b0 100644
--- a/source/blender/editors/interface/interface_intern.h
+++ b/source/blender/editors/interface/interface_intern.h
@@ -867,6 +867,7 @@ struct GPUBatch *ui_batch_roundbox_shadow_get(void);
void ui_draw_anti_tria_rect(const rctf *rect, char dir, const float color[4]);
void ui_draw_menu_back(struct uiStyle *style, uiBlock *block, rcti *rect);
+void ui_draw_box_opaque(rcti *rect, int roundboxalign);
void ui_draw_popover_back(struct ARegion *region,
struct uiStyle *style,
uiBlock *block,
diff --git a/source/blender/editors/interface/interface_panel.c b/source/blender/editors/interface/interface_panel.c
index c5f67e63bd3..f6838002ea5 100644
--- a/source/blender/editors/interface/interface_panel.c
+++ b/source/blender/editors/interface/interface_panel.c
@@ -39,6 +39,7 @@
#include "BLT_translation.h"
+#include "DNA_screen_types.h"
#include "DNA_userdef_types.h"
#include "BKE_context.h"
@@ -108,8 +109,14 @@ typedef struct uiHandlePanelData {
int startsizex, startsizey;
} uiHandlePanelData;
+typedef struct PanelSort {
+ Panel *panel, *orig;
+} PanelSort;
+
static int get_panel_real_size_y(const Panel *panel);
static void panel_activate_state(const bContext *C, Panel *panel, uiHandlePanelState state);
+static int compare_panel(const void *a1, const void *a2);
+static bool panel_type_context_poll(PanelType *panel_type, const char *context);
static void panel_title_color_get(bool show_background, uchar color[4])
{
@@ -235,9 +242,335 @@ static bool panels_need_realign(ScrArea *area, ARegion *region, Panel **r_panel_
return false;
}
+/********* Functions for instanced panels. ***********/
+
+static Panel *UI_panel_add_instanced_ex(
+ ScrArea *area, ARegion *region, ListBase *panels, PanelType *panel_type, int list_index)
+{
+ Panel *panel = MEM_callocN(sizeof(Panel), "instanced panel");
+ panel->type = panel_type;
+ BLI_strncpy(panel->panelname, panel_type->idname, sizeof(panel->panelname));
+
+ panel->runtime.list_index = list_index;
+
+ /* Add the panel's children too. Although they aren't instanced panels, we can still use this
+ * function to create them, as UI_panel_begin does other things we don't need to do. */
+ LISTBASE_FOREACH (LinkData *, child, &panel_type->children) {
+ PanelType *child_type = child->data;
+ UI_panel_add_instanced_ex(area, region, &panel->children, child_type, list_index);
+ }
+
+ /* Make sure the panel is added to the end of the display-order as well. This is needed for
+ * loading existing files.
+ *
+ * Note: We could use special behavior to place it after the panel that starts the list of
+ * instanced panels, but that would add complexity that isn't needed for now. */
+ int max_sortorder = 0;
+ LISTBASE_FOREACH (Panel *, existing_panel, panels) {
+ if (existing_panel->sortorder > max_sortorder) {
+ max_sortorder = existing_panel->sortorder;
+ }
+ }
+ panel->sortorder = max_sortorder + 1;
+
+ BLI_addtail(panels, panel);
+
+ return panel;
+}
+
+/**
+ * Called in situations where panels need to be added dynamically rather than having only one panel
+ * corresponding to each PanelType.
+ */
+Panel *UI_panel_add_instanced(
+ ScrArea *area, ARegion *region, ListBase *panels, char *panel_idname, int list_index)
+{
+ ARegionType *region_type = region->type;
+
+ PanelType *panel_type = BLI_findstring(
+ &region_type->paneltypes, panel_idname, offsetof(PanelType, idname));
+
+ if (panel_type == NULL) {
+ printf("Panel type '%s' not found.\n", panel_idname);
+ return NULL;
+ }
+
+ return UI_panel_add_instanced_ex(area, region, panels, panel_type, list_index);
+}
+
+/**
+ * Find a unique key to append to the idname for the lookup to the panel's #uiBlock. Needed for
+ * instanced panels, where there can be multiple with the same type and idname.
+ */
+void UI_list_panel_unique_str(Panel *panel, char *r_name)
+{
+ snprintf(r_name, LIST_PANEL_UNIQUE_STR_LEN, "%d", panel->runtime.list_index);
+}
+
+/**
+ * Remove the #uiBlock corresponding to a panel. The lookup is needed because panels don't store
+ * a reference to their corresponding #uiBlock.
+ */
+static void panel_free_block(ARegion *region, Panel *panel)
+{
+ BLI_assert(panel->type);
+
+ char block_name[BKE_ST_MAXNAME + LIST_PANEL_UNIQUE_STR_LEN];
+ strncpy(block_name, panel->type->idname, BKE_ST_MAXNAME);
+ char unique_panel_str[LIST_PANEL_UNIQUE_STR_LEN];
+ UI_list_panel_unique_str(panel, unique_panel_str);
+ strncat(block_name, unique_panel_str, LIST_PANEL_UNIQUE_STR_LEN);
+
+ LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
+ if (STREQ(block->name, block_name)) {
+ BLI_remlink(&region->uiblocks, block);
+ UI_block_free(NULL, block);
+ break; /* Only delete one block for this panel. */
+ }
+ }
+}
+
+/**
+ * Free a panel and it's children.
+ *
+ * \note The only panels that should need to be deleted at runtime are panels with the
+ * #PNL_INSTANCED flag set.
+ */
+static void panel_delete(ARegion *region, ListBase *panels, Panel *panel)
+{
+ /* Recursively delete children. */
+ LISTBASE_FOREACH_MUTABLE (Panel *, child, &panel->children) {
+ panel_delete(region, &panel->children, child);
+ }
+ BLI_freelistN(&panel->children);
+
+ panel_free_block(region, panel);
+
+ BLI_remlink(panels, panel);
+ if (panel->activedata) {
+ MEM_freeN(panel->activedata);
+ }
+ MEM_freeN(panel);
+}
+
+void UI_panels_free_instanced(bContext *C, ARegion *region)
+{
+ /* Delete panels with the instanced flag. */
+ LISTBASE_FOREACH_MUTABLE (Panel *, panel, &region->panels) {
+ if ((panel->type != NULL) && (panel->type->flag & PNL_INSTANCED)) {
+ /* Make sure the panel's handler is removed before deleting it. */
+ if (panel->activedata != NULL) {
+ panel_activate_state(C, panel, PANEL_STATE_EXIT);
+ }
+ panel_delete(region, &region->panels, panel);
+ }
+ }
+}
+
+/**
+ * Check if the instanced panels in the region's panels correspond to the list of data the panels
+ * represent. Returns false if the panels have been reordered or if the types from the list data
+ * don't match in any way.
+ *
+ * \param data: The list of data to check against the instanced panels.
+ * \param panel_type_func: Function to find the panel type idname for each item in the data list.
+ * For a readabilty and generality, this lookup happens separately for each type of panel list.
+ */
+bool UI_panel_list_matches_data(ARegion *region,
+ ListBase *data,
+ uiListPanelIDFromDataFunc panel_idname_func)
+{
+ int data_len = BLI_listbase_count(data);
+ int i = 0;
+ Link *data_link = data->first;
+ LISTBASE_FOREACH (Panel *, panel, &region->panels) {
+ if (panel->type != NULL && panel->type->flag & PNL_INSTANCED) {
+ /* The panels were reordered by drag and drop. */
+ if (panel->flag & PNL_INSTANCED_LIST_ORDER_CHANGED) {
+ return false;
+ }
+
+ /* We reached the last data item before the last instanced panel. */
+ if (data_link == NULL) {
+ return false;
+ }
+
+ /* Check if the panel type matches the panel type from the data item. */
+ char panel_idname[MAX_NAME];
+ panel_idname_func(data_link, panel_idname);
+ if (!STREQ(panel_idname, panel->type->idname)) {
+ return false;
+ }
+
+ data_link = data_link->next;
+ i++;
+ }
+ }
+
+ /* If we didn't make it to the last list item, the panel list isn't complete. */
+ if (i != data_len) {
+ return false;
+ }
+
+ return true;
+}
+
+static void reorder_instanced_panel_list(bContext *C, ARegion *region, Panel *drag_panel)
+{
+ /* Without a type we cannot access the reorder callback. */
+ if (drag_panel->type == NULL) {
+ return;
+ }
+ /* Don't reorder if this instanced panel doesn't support drag and drop reordering. */
+ if (drag_panel->type->reorder == NULL) {
+ return;
+ }
+
+ char *context = drag_panel->type->context;
+
+ /* Find how many instanced panels with this context string. */
+ int list_panels_len = 0;
+ LISTBASE_FOREACH (Panel *, panel, &region->panels) {
+ if (panel->type) {
+ if (panel_type_context_poll(panel->type, context)) {
+ if (panel->type->flag & PNL_INSTANCED) {
+ list_panels_len++;
+ }
+ }
+ }
+ }
+
+ /* Sort the matching instanced panels by their display order. */
+ PanelSort *panel_sort = MEM_callocN(list_panels_len * sizeof(*panel_sort), "instancedpanelsort");
+ PanelSort *sort_index = panel_sort;
+ LISTBASE_FOREACH (Panel *, panel, &region->panels) {
+ if (panel->type) {
+ if (panel_type_context_poll(panel->type, context)) {
+ if (panel->type->flag & PNL_INSTANCED) {
+ sort_index->panel = MEM_dupallocN(panel);
+ sort_index->orig = panel;
+ sort_index++;
+ }
+ }
+ }
+ }
+ qsort(panel_sort, list_panels_len, sizeof(*panel_sort), compare_panel);
+
+ /* Find how many of those panels are above this panel. */
+ int move_to_index = 0;
+ for (; move_to_index < list_panels_len; move_to_index++) {
+ if (panel_sort[move_to_index].orig == drag_panel) {
+ break;
+ }
+ }
+
+ /* Free panel sort array. */
+ int i = 0;
+ for (sort_index = panel_sort; i < list_panels_len; i++, sort_index++) {
+ MEM_freeN(sort_index->panel);
+ }
+ MEM_freeN(panel_sort);
+
+ /* Don't reorder the panel didn't change order after being dropped. */
+ if (move_to_index == drag_panel->runtime.list_index) {
+ return;
+ }
+
+ /* Set the bit to tell the interface to instanced the list. */
+ drag_panel->flag |= PNL_INSTANCED_LIST_ORDER_CHANGED;
+
+ /* Finally, move this panel's list item to the new index in its list. */
+ drag_panel->type->reorder(C, drag_panel, move_to_index);
+}
+
+/**
+ * Recursive implementation for #UI_panel_set_expand_from_list_data.
+ */
+static void panel_set_expand_from_list_data_recursive(Panel *panel, short flag, short *flag_index)
+{
+ bool open = (flag & (1 << *flag_index));
+ if (open) {
+ panel->flag &= ~PNL_CLOSEDY;
+ }
+ else {
+ panel->flag |= PNL_CLOSEDY;
+ }
+ LISTBASE_FOREACH (Panel *, child, &panel->children) {
+ *flag_index = *flag_index + 1;
+ panel_set_expand_from_list_data_recursive(child, flag, flag_index);
+ }
+}
+
+/**
+ * Set the expansion of the panel and its subpanels from the flag stored by the list data
+ * corresponding to this panel. The flag has expansion stored in each bit in depth first
+ * order.
+ */
+void UI_panel_set_expand_from_list_data(const bContext *C, Panel *panel)
+{
+ BLI_assert(panel->type != NULL);
+ BLI_assert(panel->type->flag & PNL_INSTANCED);
+ if (panel->type->get_list_data_expand_flag == NULL) {
+ /* Instanced panel doesn't support loading expansion. */
+ return;
+ }
+
+ short expand_flag = panel->type->get_list_data_expand_flag(C, panel);
+ short flag_index = 0;
+ panel_set_expand_from_list_data_recursive(panel, expand_flag, &flag_index);
+}
+
+/**
+ * Recursive implementation for #set_panels_list_data_expand_flag.
+ */
+static void get_panel_expand_flag(Panel *panel, short *flag, short *flag_index)
+{
+ bool open = !(panel->flag & PNL_CLOSEDY);
+ if (open) {
+ *flag |= (1 << *flag_index);
+ }
+ else {
+ *flag &= ~(1 << *flag_index);
+ }
+ LISTBASE_FOREACH (Panel *, child, &panel->children) {
+ *flag_index = *flag_index + 1;
+ get_panel_expand_flag(child, flag, flag_index);
+ }
+}
+
+/**
+ * Call the callback to store the panel and subpanel expansion settings in the list item that
+ * corresponds to this panel.
+ *
+ * \note This needs to iterate through all of the regions panels because the panel with changed
+ * expansion could have been the subpanel of a instanced panel, meaning it might not know
+ * which list item it corresponds to.
+ */
+static void set_panels_list_data_expand_flag(const bContext *C, ARegion *region)
+{
+ LISTBASE_FOREACH (Panel *, panel, &region->panels) {
+ PanelType *panel_type = panel->type;
+ if (panel_type == NULL) {
+ continue;
+ }
+
+ if (panel->type->flag & PNL_INSTANCED) {
+ short expand_flag = 0; /* Initialize to quite complaining compiler, value not used. */
+ short flag_index = 0;
+ get_panel_expand_flag(panel, &expand_flag, &flag_index);
+ if (panel->type->set_list_data_expand_flag) {
+ panel->type->set_list_data_expand_flag(C, panel, expand_flag);
+ }
+ }
+ }
+}
+
/****************************** panels ******************************/
-static void panels_collapse_all(ScrArea *area, ARegion *region, const Panel *from_panel)
+static void panels_collapse_all(const bContext *C,
+ ScrArea *area,
+ ARegion *region,
+ const Panel *from_panel)
{
const bool has_category_tabs = UI_panel_category_is_visible(region);
const char *category = has_category_tabs ? UI_panel_category_active_get(region, false) : NULL;
@@ -259,6 +592,15 @@ static void panels_collapse_all(ScrArea *area, ARegion *region, const Panel *fro
}
}
}
+ set_panels_list_data_expand_flag(C, region);
+}
+
+static bool panel_type_context_poll(PanelType *panel_type, const char *context)
+{
+ if (panel_type->context[0] && STREQ(panel_type->context, context)) {
+ return true;
+ }
+ return false;
}
Panel *UI_panel_find_by_type(ListBase *lb, PanelType *pt)
@@ -568,7 +910,7 @@ static void ui_draw_aligned_panel_header(
Panel *panel = block->panel;
rcti hrect;
int pnl_icons;
- const char *activename = panel->drawname[0] ? panel->drawname : panel->panelname;
+ const char *activename = panel->drawname;
const bool is_subpanel = (panel->type && panel->type->parent);
uiFontStyle *fontstyle = (is_subpanel) ? &style->widgetlabel : &style->paneltitle;
uchar col_title[4];
@@ -614,7 +956,6 @@ void ui_draw_aligned_panel(uiStyle *style,
const bool show_background)
{
Panel *panel = block->panel;
- rcti headrect;
rctf itemrect;
float color[4];
const bool is_closed_x = (panel->flag & PNL_CLOSEDX) ? true : false;
@@ -625,11 +966,19 @@ void ui_draw_aligned_panel(uiStyle *style,
* can't be dragged. This may be changed in future. */
show_background);
const int panel_col = is_subpanel ? TH_PANEL_SUB_BACK : TH_PANEL_BACK;
+ const bool draw_box_style = (panel->type && panel->type->flag & (PNL_DRAW_BOX));
+
+ /* Use the theme for box widgets for box-style panels. */
+ uiWidgetColors *box_wcol = NULL;
+ if (draw_box_style) {
+ bTheme *btheme = UI_GetTheme();
+ box_wcol = &btheme->tui.wcol_box;
+ }
+
+ uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
if (panel->type && (panel->type->flag & PNL_NO_HEADER)) {
if (show_background) {
- uint pos = GPU_vertformat_attr_add(
- immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
immUniformThemeColor(panel_col);
immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax);
@@ -638,25 +987,47 @@ void ui_draw_aligned_panel(uiStyle *style,
return;
}
- /* calculate header rect */
- /* + 0.001f to prevent flicker due to float inaccuracy */
- headrect = *rect;
- headrect.ymin = headrect.ymax;
- headrect.ymax = headrect.ymin + floor(PNL_HEADER / block->aspect + 0.001f);
+ /* Calculate header rect with + 0.001f to prevent flicker due to float inaccuracy */
+ rcti headrect = {
+ rect->xmin, rect->xmax, rect->ymax, rect->ymax + floor(PNL_HEADER / block->aspect + 0.001f)};
- rcti titlerect = headrect;
- if (is_subpanel) {
- titlerect.xmin += (0.7f * UI_UNIT_X) / block->aspect + 0.001f;
- }
+ /* Draw a panel and header backdrops with an opaque box backdrop for box style panels. */
+ if (draw_box_style && !is_subpanel) {
+ /* Expand the top a tiny bit to give header buttons equal size above and below. */
+ rcti box_rect = {rect->xmin,
+ rect->xmax,
+ (is_closed_x || is_closed_y) ? headrect.ymin : rect->ymin,
+ headrect.ymax + U.pixelsize};
+ ui_draw_box_opaque(&box_rect, UI_CNR_ALL);
- uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
- immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
+ /* Mimick the border between aligned box widgets for the bottom of the header. */
+ if (!(is_closed_x || is_closed_y)) {
+ immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
+ GPU_blend(true);
+
+ immUniformColor4ubv(box_wcol->outline);
+ immRectf(pos, rect->xmin, headrect.ymin - U.pixelsize, rect->xmax, headrect.ymin);
+ uchar emboss_col[4];
+ UI_GetThemeColor4ubv(TH_WIDGET_EMBOSS, emboss_col);
+ immUniformColor4ubv(emboss_col);
+ immRectf(pos,
+ rect->xmin,
+ headrect.ymin - U.pixelsize,
+ rect->xmax,
+ headrect.ymin - U.pixelsize - 1);
+
+ GPU_blend(false);
+ immUnbindProgram();
+ }
+ }
- if (show_background && !is_subpanel) {
+ /* Draw the header backdrop. */
+ if (show_background && !is_subpanel && !draw_box_style) {
float minx = rect->xmin;
float maxx = is_closed_x ? (minx + PNL_HEADER / block->aspect) : rect->xmax;
float y = headrect.ymax;
+ immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
GPU_blend(true);
/* draw with background color */
@@ -674,12 +1045,10 @@ void ui_draw_aligned_panel(uiStyle *style,
immEnd();
GPU_blend(false);
+ immUnbindProgram();
}
- immUnbindProgram();
-
- /* draw optional pin icon */
-
+/* draw optional pin icon */
#ifdef USE_PIN_HIDDEN
if (show_pin && (block->panel->flag & PNL_PIN))
#else
@@ -702,6 +1071,10 @@ void ui_draw_aligned_panel(uiStyle *style,
}
/* horizontal title */
+ rcti titlerect = headrect;
+ if (is_subpanel) {
+ titlerect.xmin += (0.7f * UI_UNIT_X) / block->aspect + 0.001f;
+ }
if (is_closed_x == false) {
ui_draw_aligned_panel_header(style, block, &titlerect, 'h', show_background);
@@ -730,9 +1103,7 @@ void ui_draw_aligned_panel(uiStyle *style,
}
}
- /* if the panel is minimized vertically:
- * (------)
- */
+ /* Draw panel backdrop. */
if (is_closed_y) {
/* skip */
}
@@ -745,11 +1116,18 @@ void ui_draw_aligned_panel(uiStyle *style,
else {
/* in some occasions, draw a border */
if (panel->flag & PNL_SELECT && !is_subpanel) {
+ float radius;
if (panel->control & UI_PNL_SOLID) {
UI_draw_roundbox_corner_set(UI_CNR_ALL);
+ radius = 8.0f;
+ }
+ else if (draw_box_style) {
+ UI_draw_roundbox_corner_set(UI_CNR_ALL);
+ radius = box_wcol->roundness * U.widget_unit;
}
else {
UI_draw_roundbox_corner_set(UI_CNR_NONE);
+ radius = 0.0f;
}
UI_GetThemeColorShade4fv(TH_BACK, -120, color);
@@ -758,18 +1136,40 @@ void ui_draw_aligned_panel(uiStyle *style,
0.5f + rect->ymin,
0.5f + rect->xmax,
0.5f + headrect.ymax + 1,
- 8,
+ radius,
color);
}
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
-
GPU_blend(true);
- if (show_background) {
- /* panel backdrop */
- immUniformThemeColor(panel_col);
- immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax);
+ /* Draw panel backdrop if it wasn't aleady been drawn by the single opauque round box earlier.
+ * Note: Subpanels blend with panels, so they can't be opaque. */
+ if (show_background && !(draw_box_style && !is_subpanel)) {
+ /* Draw the bottom subpanels . */
+ if (draw_box_style) {
+ if (panel->next) {
+ immUniformThemeColor(panel_col);
+ immRectf(
+ pos, rect->xmin + U.pixelsize, rect->ymin, rect->xmax - U.pixelsize, rect->ymax);
+ }
+ else {
+ /* Change the width a little bit to line up with sides. */
+ UI_draw_roundbox_corner_set(UI_CNR_BOTTOM_RIGHT | UI_CNR_BOTTOM_LEFT);
+ UI_GetThemeColor4fv(panel_col, color);
+ UI_draw_roundbox_aa(true,
+ rect->xmin + U.pixelsize,
+ rect->ymin + U.pixelsize,
+ rect->xmax - U.pixelsize,
+ rect->ymax,
+ box_wcol->roundness * U.widget_unit,
+ color);
+ }
+ }
+ else {
+ immUniformThemeColor(panel_col);
+ immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax);
+ }
}
if (panel->control & UI_PNL_SCALE) {
@@ -887,10 +1287,6 @@ bool UI_panel_is_dragging(const struct Panel *panel)
return data->is_drag_drop;
}
-typedef struct PanelSort {
- Panel *panel, *orig;
-} PanelSort;
-
/**
* \note about sorting;
* the sortorder has a lower value for new panels being added.
@@ -1052,13 +1448,30 @@ static bool uiAlignPanelStep(ScrArea *area, ARegion *region, const float fac, co
ps->panel->ofsx = 0;
ps->panel->ofsy = -get_panel_size_y(ps->panel);
ps->panel->ofsx += ps->panel->runtime.region_ofsx;
+ /* Extra margin if the panel is a box style panel. */
+ if (ps->panel->type && ps->panel->type->flag & PNL_DRAW_BOX) {
+ ps->panel->ofsx += UI_PANEL_BOX_STYLE_MARGIN;
+ ps->panel->ofsy -= UI_PANEL_BOX_STYLE_MARGIN;
+ }
for (a = 0; a < tot - 1; a++, ps++) {
psnext = ps + 1;
if (align == BUT_VERTICAL) {
+ bool use_box = ps->panel->type && ps->panel->type->flag & PNL_DRAW_BOX;
+ bool use_box_next = psnext->panel->type && psnext->panel->type->flag & PNL_DRAW_BOX;
psnext->panel->ofsx = ps->panel->ofsx;
psnext->panel->ofsy = get_panel_real_ofsy(ps->panel) - get_panel_size_y(psnext->panel);
+ /* Extra margin for box style panels. */
+ if (use_box || use_box_next) {
+ psnext->panel->ofsy -= UI_PANEL_BOX_STYLE_MARGIN;
+ }
+ if (use_box && !use_box_next) {
+ psnext->panel->ofsx -= UI_PANEL_BOX_STYLE_MARGIN;
+ }
+ else if (!use_box && use_box_next) {
+ psnext->panel->ofsx += UI_PANEL_BOX_STYLE_MARGIN;
+ }
}
else {
psnext->panel->ofsx = get_panel_real_ofsx(ps->panel);
@@ -1137,7 +1550,7 @@ static void ui_panels_size(ScrArea *area, ARegion *region, int *r_x, int *r_y)
*r_y = sizey;
}
-static void ui_do_animate(const bContext *C, Panel *panel)
+static void ui_do_animate(bContext *C, Panel *panel)
{
uiHandlePanelData *data = panel->activedata;
ScrArea *area = CTX_wm_area(C);
@@ -1156,7 +1569,15 @@ static void ui_do_animate(const bContext *C, Panel *panel)
}
if (fac >= 1.0f) {
+ /* Store before data is freed. */
+ const bool is_drag_drop = data->is_drag_drop;
+
panel_activate_state(C, panel, PANEL_STATE_EXIT);
+ if (is_drag_drop) {
+ /* Note: doing this in #panel_activate_state would require removing const for context in many
+ * other places. */
+ reorder_instanced_panel_list(C, region, panel);
+ }
return;
}
}
@@ -1460,6 +1881,8 @@ static void ui_panel_drag_collapse(bContext *C,
}
}
}
+ /* Update the instanced panel data expand flags with the changes made here. */
+ set_panels_list_data_expand_flag(C, region);
}
/**
@@ -1588,7 +2011,7 @@ static void ui_handle_panel_header(
if (ctrl) {
/* Only collapse all for parent panels. */
if (block->panel->type != NULL && block->panel->type->parent == NULL) {
- panels_collapse_all(area, region, block->panel);
+ panels_collapse_all(C, area, region, block->panel);
/* reset the view - we don't want to display a view without content */
UI_view2d_offset(&region->v2d, 0.0f, 1.0f);
@@ -1624,6 +2047,8 @@ static void ui_handle_panel_header(
ui_panel_drag_collapse_handler_add(C, true);
}
}
+
+ set_panels_list_data_expand_flag(C, region);
}
if (align) {
diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c
index 4706be205e1..0498b312618 100644
--- a/source/blender/editors/interface/interface_widgets.c
+++ b/source/blender/editors/interface/interface_widgets.c
@@ -4265,7 +4265,7 @@ static void widget_box(
copy_v3_v3_uchar(old_col, wcol->inner);
/* abuse but->hsv - if it's non-zero, use this color as the box's background */
- if (but->col[3]) {
+ if (but != NULL && but->col[3]) {
wcol->inner[0] = but->col[0];
wcol->inner[1] = but->col[1];
wcol->inner[2] = but->col[2];
@@ -5021,6 +5021,30 @@ void ui_draw_menu_back(uiStyle *UNUSED(style), uiBlock *block, rcti *rect)
}
/**
+ * Uses the widget base drawing and colors from from the box widget, but ensures an opaque
+ * inner color.
+ */
+void ui_draw_box_opaque(rcti *rect, int roundboxalign)
+{
+ uiWidgetType *wt = widget_type(UI_WTYPE_BOX);
+
+ /* Alpha blend with the region's background color to force an opaque background. */
+ uiWidgetColors *wcol = &wt->wcol;
+ wt->state(wt, 0, 0);
+ float background[4];
+ UI_GetThemeColor4fv(TH_BACK, background);
+ float new_inner[4];
+ rgba_uchar_to_float(new_inner, wcol->inner);
+ new_inner[0] = (new_inner[0] * new_inner[3]) + (background[0] * (1.0f - new_inner[3]));
+ new_inner[1] = (new_inner[1] * new_inner[3]) + (background[1] * (1.0f - new_inner[3]));
+ new_inner[2] = (new_inner[2] * new_inner[3]) + (background[2] * (1.0f - new_inner[3]));
+ new_inner[3] = 1.0f;
+ rgba_float_to_uchar(wcol->inner, new_inner);
+
+ wt->custom(NULL, wcol, rect, 0, roundboxalign);
+}
+
+/**
* Similar to 'widget_menu_back', however we can't use the widget preset system
* because we need to pass in the original location so we know where to show the arrow.
*/
diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c
index 48b54ddcea3..c04122edd36 100644
--- a/source/blender/editors/screen/area.c
+++ b/source/blender/editors/screen/area.c
@@ -2345,6 +2345,15 @@ BLI_INLINE bool streq_array_any(const char *s, const char *arr[])
return false;
}
+/**
+ * Builds the panel layout for the input \a panel or type \a pt.
+ *
+ * \param panel The panel to draw. Can be null, in which case a panel with the type of \a pt will
+ * be created.
+ * \param unique_panel_str A unique identifier for the name of the \a uiBlock associated with the
+ * panel. Used when the panel is an instanced panel so a unique identifier is needed to find the
+ * correct old \a uiBlock, and NULL otherwise.
+ */
static void ed_panel_draw(const bContext *C,
ScrArea *area,
ARegion *region,
@@ -2353,18 +2362,27 @@ static void ed_panel_draw(const bContext *C,
Panel *panel,
int w,
int em,
- bool vertical)
+ bool vertical,
+ char *unique_panel_str)
{
const uiStyle *style = UI_style_get_dpi();
- /* draw panel */
- uiBlock *block = UI_block_begin(C, region, pt->idname, UI_EMBOSS);
+ /* Draw panel. */
+
+ char block_name[BKE_ST_MAXNAME + LIST_PANEL_UNIQUE_STR_LEN];
+ strncpy(block_name, pt->idname, BKE_ST_MAXNAME);
+ if (unique_panel_str != NULL) {
+ /* Instanced panels should have already been added at this point. */
+ strncat(block_name, unique_panel_str, LIST_PANEL_UNIQUE_STR_LEN);
+ }
+ uiBlock *block = UI_block_begin(C, region, block_name, UI_EMBOSS);
bool open;
panel = UI_panel_begin(area, region, lb, block, pt, panel, &open);
/* bad fixed values */
int xco, yco, h = 0;
+ int headerend = w - UI_UNIT_X;
if (pt->draw_header_preset && !(pt->flag & PNL_NO_HEADER) && (open || vertical)) {
/* for preset menu */
@@ -2380,8 +2398,6 @@ static void ed_panel_draw(const bContext *C,
pt->draw_header_preset(C, panel);
- int headerend = w - UI_UNIT_X;
-
UI_block_layout_resolve(block, &xco, &yco);
UI_block_translate(block, headerend - xco, 0);
panel->layout = NULL;
@@ -2391,9 +2407,24 @@ static void ed_panel_draw(const bContext *C,
int labelx, labely;
UI_panel_label_offset(block, &labelx, &labely);
- /* for enabled buttons */
- panel->layout = UI_block_layout(
- block, UI_LAYOUT_HORIZONTAL, UI_LAYOUT_HEADER, labelx, labely, UI_UNIT_Y, 1, 0, style);
+ /* Unusual case: Use expanding layout (buttons stretch to available width). */
+ if (pt->flag & PNL_LAYOUT_HEADER_EXPAND) {
+ uiLayout *layout = UI_block_layout(block,
+ UI_LAYOUT_VERTICAL,
+ UI_LAYOUT_PANEL,
+ labelx,
+ labely,
+ headerend - 2 * style->panelspace,
+ 1,
+ 0,
+ style);
+ panel->layout = uiLayoutRow(layout, false);
+ }
+ /* Regular case: Normal panel with fixed size buttons. */
+ else {
+ panel->layout = UI_block_layout(
+ block, UI_LAYOUT_HORIZONTAL, UI_LAYOUT_HEADER, labelx, labely, UI_UNIT_Y, 1, 0, style);
+ }
pt->draw_header(C, panel);
@@ -2449,7 +2480,16 @@ static void ed_panel_draw(const bContext *C,
Panel *child_panel = UI_panel_find_by_type(&panel->children, child_pt);
if (child_pt->draw && (!child_pt->poll || child_pt->poll(C, child_pt))) {
- ed_panel_draw(C, area, region, &panel->children, child_pt, child_panel, w, em, vertical);
+ ed_panel_draw(C,
+ area,
+ region,
+ &panel->children,
+ child_pt,
+ child_panel,
+ w,
+ em,
+ vertical,
+ unique_panel_str);
}
}
}
@@ -2571,6 +2611,7 @@ void ED_region_panels_layout_ex(const bContext *C,
}
w -= margin_x;
+ int w_box_panel = w - UI_PANEL_BOX_STYLE_MARGIN * 2.0f;
/* create panels */
UI_panels_begin(C, region);
@@ -2578,8 +2619,14 @@ void ED_region_panels_layout_ex(const bContext *C,
/* set view2d view matrix - UI_block_begin() stores it */
UI_view2d_view_ortho(v2d);
+ bool has_instanced_panel = false;
for (LinkNode *pt_link = panel_types_stack; pt_link; pt_link = pt_link->next) {
PanelType *pt = pt_link->link;
+
+ if (pt->flag & PNL_INSTANCED) {
+ has_instanced_panel = true;
+ continue;
+ }
Panel *panel = UI_panel_find_by_type(&region->panels, pt);
if (use_category_tabs && pt->category[0] && !STREQ(category, pt->category)) {
@@ -2593,7 +2640,46 @@ void ED_region_panels_layout_ex(const bContext *C,
update_tot_size = false;
}
- ed_panel_draw(C, area, region, &region->panels, pt, panel, w, em, vertical);
+ ed_panel_draw(C,
+ area,
+ region,
+ &region->panels,
+ pt,
+ panel,
+ (pt->flag & PNL_DRAW_BOX) ? w_box_panel : w,
+ em,
+ vertical,
+ NULL);
+ }
+
+ /* Draw "polyinstantaited" panels that don't have a 1 to 1 correspondence with their types. */
+ if (has_instanced_panel) {
+ LISTBASE_FOREACH (Panel *, panel, &region->panels) {
+ if (panel->type == NULL) {
+ continue; /* Some panels don't have a type.. */
+ }
+ if (panel->type->flag & PNL_INSTANCED) {
+ if (panel && UI_panel_is_dragging(panel)) {
+ /* Prevent View2d.tot rectangle size changes while dragging panels. */
+ update_tot_size = false;
+ }
+
+ /* Use a unique identifier for instanced panels, otherwise an old block for a different
+ * panel of the same type might be found. */
+ char unique_panel_str[8];
+ UI_list_panel_unique_str(panel, unique_panel_str);
+ ed_panel_draw(C,
+ area,
+ region,
+ &region->panels,
+ panel->type,
+ panel,
+ (panel->type->flag & PNL_DRAW_BOX) ? w_box_panel : w,
+ em,
+ vertical,
+ unique_panel_str);
+ }
+ }
}
/* align panels and return size */
diff --git a/source/blender/makesdna/DNA_screen_types.h b/source/blender/makesdna/DNA_screen_types.h
index 8497d363179..07cba2bad1c 100644
--- a/source/blender/makesdna/DNA_screen_types.h
+++ b/source/blender/makesdna/DNA_screen_types.h
@@ -132,7 +132,9 @@ typedef struct ScrAreaMap {
typedef struct Panel_Runtime {
/* Applied to Panel.ofsx, but saved separately so we can track changes between redraws. */
int region_ofsx;
- char _pad[4];
+
+ /* For instanced panels: Index of the list item the panel corresponds to. */
+ int list_index;
} Panel_Runtime;
/** The part from uiBlock that needs saved in file. */
@@ -531,6 +533,8 @@ enum {
PNL_OVERLAP = (1 << 4),
PNL_PIN = (1 << 5),
PNL_POPOVER = (1 << 6),
+ /** The panel has been drag-drop reordered and the instanced panel list needs to be rebuilt. */
+ PNL_INSTANCED_LIST_ORDER_CHANGED = (1 << 7),
};
/** #Panel.snap - for snapping to screen edges */
@@ -543,9 +547,17 @@ enum {
/* #define PNL_SNAP_DIST 9.0 */
/* paneltype flag */
-#define PNL_DEFAULT_CLOSED 1
-#define PNL_NO_HEADER 2
-#define PNL_LAYOUT_VERT_BAR 4
+enum {
+ PNL_DEFAULT_CLOSED = (1 << 0),
+ PNL_NO_HEADER = (1 << 1),
+ /** Makes buttons in the header shrink/stretch to fill full layout width. */
+ PNL_LAYOUT_HEADER_EXPAND = (1 << 2),
+ PNL_LAYOUT_VERT_BAR = (1 << 3),
+ /** This panel type represents data external to the UI. */
+ PNL_INSTANCED = (1 << 4),
+ /** Draw panel like a box widget. */
+ PNL_DRAW_BOX = (1 << 6),
+};
/* Fallback panel category (only for old scripts which need updating) */
#define PNL_CATEGORY_FALLBACK "Misc"
diff --git a/source/blender/makesrna/intern/rna_ui.c b/source/blender/makesrna/intern/rna_ui.c
index ca466ce2821..1449c410d18 100644
--- a/source/blender/makesrna/intern/rna_ui.c
+++ b/source/blender/makesrna/intern/rna_ui.c
@@ -1285,6 +1285,18 @@ static void rna_def_panel(BlenderRNA *brna)
"Hide Header",
"If set to False, the panel shows a header, which contains a clickable "
"arrow to collapse the panel and the label (see bl_label)"},
+ {PNL_INSTANCED,
+ "INSTANCED",
+ 0,
+ "Instanced Panel",
+ "Multiple panels with this type can be used as part of a list depending on data external "
+ "to the UI. Used to create panels for the modifiers and other stacks."},
+ {PNL_LAYOUT_HEADER_EXPAND,
+ "HEADER_LAYOUT_EXPAND",
+ 0,
+ "Expand Header Layout",
+ "Allow buttons in the header to stretch and shrink to fill the entire layout width"},
+ {PNL_DRAW_BOX, "DRAW_BOX", 0, "Box Style", "Draw panel with the box widget theme"},
{0, NULL, 0, NULL, NULL},
};
@@ -1332,6 +1344,11 @@ static void rna_def_panel(BlenderRNA *brna)
RNA_def_property_string_sdna(prop, NULL, "drawname");
RNA_def_property_ui_text(prop, "Text", "XXX todo");
+ prop = RNA_def_int(
+ srna, "list_panel_index", 0, 0, INT_MAX, "Instanced Panel Data Index", "", 0, INT_MAX);
+ RNA_def_property_int_sdna(prop, NULL, "runtime.list_index");
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+
/* registration */
prop = RNA_def_property(srna, "bl_idname", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "type->idname");