diff options
-rw-r--r-- | source/blender/blenkernel/BKE_screen.h | 19 | ||||
-rw-r--r-- | source/blender/editors/include/UI_interface.h | 21 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_align.c | 4 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_intern.h | 1 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_panel.c | 497 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_widgets.c | 26 | ||||
-rw-r--r-- | source/blender/editors/screen/area.c | 106 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_screen_types.h | 20 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_ui.c | 17 |
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( + ®ion_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, ®ion->uiblocks) { + if (STREQ(block->name, block_name)) { + BLI_remlink(®ion->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, ®ion->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, ®ion->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, ®ion->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, ®ion->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, ®ion->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, ®ion->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(®ion->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(®ion->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, ®ion->panels, pt, panel, w, em, vertical); + ed_panel_draw(C, + area, + region, + ®ion->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, ®ion->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, + ®ion->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"); |