Files
test/source/blender/editors/interface/interface_button_sections.cc
Guillermo Venegas 7682258ff9 Refactor: UI: Use a Vector to store buttons in UI blocks
This changes the ui-blocks buttons storage from Listbase to Vector.

Major changes that might cause a performance considerations are
in `ui_but_update_from_old_block` that requires to track buttons when restoring
button state between block redraws or in  `uiItemFullR` that may needs to insert
uiButs in the middle of the vector to add decorators. This might not be as fast as
removing or inserting elements in the middle of a listbase container. Also buttons currently
don't know its position in the container, so to get the previous and next
button its required to make a lookup of the button in the container.

`UI_block_update_from_old> ui_but_update_from_old_block` restores the state
of buttons between frames, this is done by sequentially testing if a button is the
same as an old button, however since UI can be created procedurally some old buttons
may not be drawn while editing other button data, this requires an extra track of what
buttons may not match to a new button while comparing for restoring state, but still
this buttons may be candidates to match to an new button.

Not functional changes expected.
Ref: #117604

Co-authored-by: Julian Eisel <julian@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/127128
2025-02-14 15:29:26 +01:00

253 lines
8.5 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edinterface
*
* Calculating and drawing of bounding boxes for "button sections". That is, each group of buttons
* separated by a separator spacer button.
*/
#include "BLI_listbase.h"
#include "BLI_math_vector_types.hh"
#include "BLI_rect.h"
#include "BLI_span.hh"
#include "BLI_vector.hh"
#include "BKE_screen.hh"
#include "DNA_screen_types.h"
#include "GPU_immediate.hh"
#include "GPU_state.hh"
#include "interface_intern.hh"
using namespace blender;
/**
* Calculate a bounding box for each section. Sections will be merged if they are closer than
* #UI_BUTTON_SECTION_MERGE_DISTANCE.
*
* If a section is closer than #UI_BUTTON_SECTION_MERGE_DISTANCE to a region edge, it will be
* extended to the edge.
*
* \return the bounding boxes in region space.
*/
static Vector<rcti> button_section_bounds_calc(const ARegion *region, const bool add_padding)
{
Vector<rcti> section_bounds;
const auto finish_section_fn = [&](const rcti cur_section_bounds) {
if (!section_bounds.is_empty() &&
std::abs(section_bounds.last().xmax - cur_section_bounds.xmin) <
UI_BUTTON_SECTION_MERGE_DISTANCE)
{
section_bounds.last().xmax = cur_section_bounds.xmax;
}
else {
section_bounds.append(cur_section_bounds);
}
rcti &last_bounds = section_bounds.last();
/* Extend to region edge if close enough. */
if (last_bounds.xmin <= UI_BUTTON_SECTION_MERGE_DISTANCE) {
last_bounds.xmin = 0;
}
if (last_bounds.xmax >= (region->winx - UI_BUTTON_SECTION_MERGE_DISTANCE)) {
last_bounds.xmax = region->winx;
}
};
{
bool has_section_content = false;
rcti cur_section_bounds;
BLI_rcti_init_minmax(&cur_section_bounds);
/* A bit annoying, but this function is called for both drawing and event handling. When
* drawing, we need to exclude inactive blocks since they mess with the result. However, this
* active state is only useful during drawing and must be ignored for handling (at which point
* #uiBlock::active is false for all blocks). */
const bool is_drawing = region->runtime->do_draw & RGN_DRAWING;
LISTBASE_FOREACH (uiBlock *, block, &region->runtime->uiblocks) {
if (is_drawing && !block->active) {
continue;
}
for (const std::unique_ptr<uiBut> &but : block->buttons) {
if (but->type == UI_BTYPE_SEPR_SPACER) {
/* Start a new section. */
if (has_section_content) {
finish_section_fn(cur_section_bounds);
/* Reset for next section. */
BLI_rcti_init_minmax(&cur_section_bounds);
has_section_content = false;
}
continue;
}
rcti but_pixelrect;
ui_but_to_pixelrect(&but_pixelrect, region, block, but.get());
BLI_rcti_do_minmax_rcti(&cur_section_bounds, &but_pixelrect);
has_section_content = true;
}
}
/* Finish last section in case the last button is not a spacer. */
if (has_section_content) {
finish_section_fn(cur_section_bounds);
}
}
if (add_padding) {
const uiStyle *style = UI_style_get_dpi();
const int pad_x = style->buttonspacex;
/* Making this based on the header size since this feature is typically used in headers, and
* this way we are more likely to pad the bounds all the way to the region edge. */
const int pad_y = ceil((HEADER_PADDING_Y * UI_SCALE_FAC) / 2.0f);
for (rcti &bounds : section_bounds) {
BLI_rcti_pad(&bounds, pad_x, pad_y);
/* Clamp, important for the rounded-corners to draw correct. */
CLAMP_MIN(bounds.xmin, 0);
CLAMP_MAX(bounds.xmax, region->winx);
CLAMP_MIN(bounds.ymin, 0);
CLAMP_MAX(bounds.ymax, region->winy);
}
}
return section_bounds;
}
static void ui_draw_button_sections_background(const ARegion *region,
const Span<rcti> section_bounds,
const ThemeColorID colorid,
const uiButtonSectionsAlign align,
const float corner_radius)
{
float bg_color[4];
UI_GetThemeColor4fv(colorid, bg_color);
for (const rcti &bounds : section_bounds) {
int roundbox_corners = [align]() -> int {
switch (align) {
case uiButtonSectionsAlign::Top:
return UI_CNR_BOTTOM_LEFT | UI_CNR_BOTTOM_RIGHT;
case uiButtonSectionsAlign::Bottom:
return UI_CNR_TOP_LEFT | UI_CNR_TOP_RIGHT;
case uiButtonSectionsAlign::None:
return UI_CNR_ALL;
}
return UI_CNR_ALL;
}();
/* No rounded corners at the region edge. */
if (bounds.xmin == 0) {
roundbox_corners &= ~(UI_CNR_TOP_LEFT | UI_CNR_BOTTOM_LEFT);
}
if (bounds.xmax >= region->winx) {
roundbox_corners &= ~(UI_CNR_TOP_RIGHT | UI_CNR_BOTTOM_RIGHT);
}
rctf bounds_float;
BLI_rctf_rcti_copy(&bounds_float, &bounds);
/* Make space for the separator line. */
if (align == uiButtonSectionsAlign::Top) {
bounds_float.ymax -= UI_BUTTON_SECTION_SEPERATOR_LINE_WITH;
}
else if (align == uiButtonSectionsAlign::Bottom) {
bounds_float.ymin += UI_BUTTON_SECTION_SEPERATOR_LINE_WITH;
}
UI_draw_roundbox_corner_set(roundbox_corners);
UI_draw_roundbox_4fv(&bounds_float, true, corner_radius, bg_color);
}
}
static void ui_draw_button_sections_alignment_separator(const ARegion *region,
const Span<rcti> section_bounds,
const ThemeColorID colorid,
const uiButtonSectionsAlign align,
const float corner_radius)
{
const int separator_line_width = UI_BUTTON_SECTION_SEPERATOR_LINE_WITH;
float bg_color[4];
UI_GetThemeColor4fv(colorid, bg_color);
GPU_blend(GPU_BLEND_ALPHA);
/* Separator line. */
{
GPUVertFormat *format = immVertexFormat();
const uint pos = GPU_vertformat_attr_add(
format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
immUniformColor4fv(bg_color);
if (align == uiButtonSectionsAlign::Top) {
immRecti(pos, 0, region->winy - separator_line_width, region->winx, region->winy);
}
else if (align == uiButtonSectionsAlign::Bottom) {
immRecti(pos, 0, 0, region->winx, separator_line_width);
}
else {
BLI_assert_unreachable();
}
immUnbindProgram();
}
int prev_xmax = 0;
for (const rcti &bounds : section_bounds) {
if (prev_xmax != 0) {
const rcti rounded_corner_rect = {
prev_xmax, bounds.xmin, separator_line_width, region->winy - separator_line_width};
UI_draw_roundbox_corner_set(align == uiButtonSectionsAlign::Top ?
(UI_CNR_TOP_LEFT | UI_CNR_TOP_RIGHT) :
(UI_CNR_BOTTOM_LEFT | UI_CNR_BOTTOM_RIGHT));
ui_draw_rounded_corners_inverted(rounded_corner_rect, corner_radius, bg_color);
}
prev_xmax = bounds.xmax;
}
GPU_blend(GPU_BLEND_NONE);
}
void UI_region_button_sections_draw(const ARegion *region,
const int /*ThemeColorID*/ colorid,
const uiButtonSectionsAlign align)
{
const float aspect = BLI_rctf_size_x(&region->v2d.cur) /
(BLI_rcti_size_x(&region->v2d.mask) + 1);
const float corner_radius = 4.0f * UI_SCALE_FAC / aspect;
const Vector<rcti> section_bounds = button_section_bounds_calc(region, true);
ui_draw_button_sections_background(
region, section_bounds, ThemeColorID(colorid), align, corner_radius);
if (align != uiButtonSectionsAlign::None) {
ui_draw_button_sections_alignment_separator(region,
section_bounds,
ThemeColorID(colorid),
align,
/* Slightly bigger corner radius, looks better. */
corner_radius + 1);
}
}
bool UI_region_button_sections_is_inside_x(const ARegion *region, const int mval_x)
{
const Vector<rcti> section_bounds = button_section_bounds_calc(region, true);
for (const rcti &bounds : section_bounds) {
if (BLI_rcti_isect_x(&bounds, mval_x)) {
return true;
}
}
return false;
}