diff options
author | Hans Goudey <h.goudey@me.com> | 2020-05-26 22:39:49 +0300 |
---|---|---|
committer | Hans Goudey <h.goudey@me.com> | 2020-05-26 22:39:49 +0300 |
commit | 5171d86806a338aa885cdddf3ad83dfcf15c40aa (patch) | |
tree | 00fd7c3fc9c245485f742c73a175cf6c9a0adb30 /source/blender/editors | |
parent | de257b6366455ba6a3604c0830a92245df11f7bc (diff) |
UI: List Panel System
This implements a general system to implement drag and drop, subpanels,
and UI animation for the stack UIs in Blender. There are NO functional
changes in this patch, but it makes it relatively trivial to implement
these features for stacks.
The biggest complication to using panels to implement the UI for lists
is that there can be multiple modifiers of the same type. Currently there
is an assumed 1 to 1 relationship between every panel and its type, but
there can be multiple list items of the same type, so we have to break
this relationship. The mapping between panels and their data is stored
with an index in the panel's runtime struct.
To make use the system for a list like modifiers, four components
must be added:
1. A panel type defined and registered for each list data type, with a
known mapping between list data types and panel idnames.
1. A function called by interface code to build the add the panel
layouts with the provided helper functions.
- UI_panel_list_matches_data will check if the panel list needs to
be rebuilt.
- UI_panels_free_instanced will remove the existing list panels
- UI_panel_add_instanced adds a list panel of a given type.
3. An expand flag for the list data and implementations of
get_list_data_expand_flag and set_list_data_expand_flag.
4. For reordering, the panel type's reorder callback. This is called
when the instanced panels are drag-dropped. This requires
implementing a "move to index" operator for the list data.
Reviewed By: Severin, brecht
Differential Revision: https://developer.blender.org/D7490
Diffstat (limited to 'source/blender/editors')
-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 |
6 files changed, 606 insertions, 49 deletions
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 */ |