Files
test2/source/blender/editors/interface/interface_button_sections.cc
Julian Eisel 31e1a32378 Fix 1 pixel overlap in tool or asset shelf header background drawing
When the tool settings or asset shelf header would draw with some
transparency, the separator line between regions would visibly overlap
the background behind buttons, which looked unpolished. Ensure there is
no overlap (but also no gap, which became visible after accounting for
the separator line in the background drawing).
2023-09-28 19:30:05 +02:00

243 lines
8.0 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_math_vector_types.hh"
#include "BLI_span.hh"
#include "BLI_vector.hh"
#include "DNA_screen_types.h"
#include "GPU_immediate.h"
#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);
LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
if (!block->active) {
continue;
}
LISTBASE_FOREACH (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);
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;
}