Asset Shelf: Transparent asset shelf header with background for buttons #112241

Merged
Julian Eisel merged 19 commits from JulianEisel/blender:temp-asset-shelf-button-sections into main 2023-09-26 17:12:45 +02:00
11 changed files with 398 additions and 10 deletions

View File

@ -492,6 +492,7 @@ void ED_asset_shelf_header_region_init(wmWindowManager * /*wm*/, ARegion *region
{
ED_region_header_init(region);
region->alignment |= RGN_SPLIT_SCALE_PREV;
region->flag |= RGN_FLAG_RESIZE_RESPECT_BUTTON_SECTIONS;
}
void ED_asset_shelf_header_region(const bContext *C, ARegion *region)
@ -505,14 +506,17 @@ void ED_asset_shelf_header_region(const bContext *C, ARegion *region)
*main_shelf_region);
update_active_shelf(*C, *space_type, *shelf_regiondata);
ED_region_header(C, region);
ED_region_header_with_button_sections(C, region, uiButtonSectionsAlign::Bottom);
JulianEisel marked this conversation as resolved Outdated

Might it make sense to add a ED_region_header_with_button_sections ? Since practically all other region functions use ED_region_header(C, region).

Might it make sense to add a `ED_region_header_with_button_sections` ? Since practically all other region functions use `ED_region_header(C, region)`.
}
int ED_asset_shelf_header_region_size()
{
/* The asset shelf tends to look like a separate area. Making the shelf header smaller than a
* normal header helps a bit. */
return ED_area_headersize() * 0.85f;
/* Use a height that lets widgets sit just on top of the separator line drawn at the lower edge
* of the region (widgets will be centered).
*
* Note that this is usually a bit less than the header size. The asset shelf tends to look like
* a separate area, so making the shelf header smaller than a header helps. */
return UI_UNIT_Y + (UI_BUTTON_SECTION_SEPERATOR_LINE_WITH * 2);
}
void ED_asset_shelf_region_blend_read_data(BlendDataReader *reader, ARegion *region)

View File

@ -118,6 +118,16 @@ void ED_region_header_init(ARegion *region);
void ED_region_header(const bContext *C, ARegion *region);
void ED_region_header_layout(const bContext *C, ARegion *region);
void ED_region_header_draw(const bContext *C, ARegion *region);
/* Forward declare enum. */
enum class uiButtonSectionsAlign : int8_t;
/** Version of #ED_region_header() that draws with button sections. */
void ED_region_header_with_button_sections(const bContext *C,
ARegion *region,
uiButtonSectionsAlign align);
/** Version of #ED_region_header_draw() that draws with button sections. */
void ED_region_header_draw_with_button_sections(const bContext *C,
const ARegion *region,
uiButtonSectionsAlign align);
void ED_region_cursor_set(wmWindow *win, ScrArea *area, ARegion *region);
/**

View File

@ -839,6 +839,25 @@ void UI_block_region_set(uiBlock *block, ARegion *region);
void UI_block_lock_set(uiBlock *block, bool val, const char *lockstr);
void UI_block_lock_clear(uiBlock *block);
#define UI_BUTTON_SECTION_MERGE_DISTANCE (UI_UNIT_X * 3)
/* Separator line between regions if the #uiButtonSectionsAlign is not #None. */
#define UI_BUTTON_SECTION_SEPERATOR_LINE_WITH (U.pixelsize * 2)
enum class uiButtonSectionsAlign : int8_t { None = 1, Top, Bottom };
JulianEisel marked this conversation as resolved Outdated

picky I have a slight preference to set the first value to 1, so it's clear when debugging what the values are and so a zeroed value isn't implicitly initializing in a non obvious way.

Also, why not uint8_t? (if the size is given at all, may as well not use a larger value then is needed.)

*picky* I have a slight preference to set the first value to `1`, so it's clear when debugging what the values are and so a zeroed value isn't implicitly initializing in a non obvious way. Also, why not `uint8_t`? (if the size is given at all, may as well not use a larger value then is needed.)
/**
* Draw a background with rounded corners behind each visual group of buttons. The visual groups
* are separated by spacer buttons (#uiItemSpacer()). Button groups that are closer than
* #UI_BUTTON_SECTION_MERGE_DISTANCE will be merged into one visual section. If the group is closer
* than that to a region edge, it will also be extended to that, and the rounded corners will be
* removed on that edge.
*
* \note This currently only works well for horizontal, header like regions.
*/
void UI_region_button_sections_draw(const ARegion *region,
int /*THemeColorID*/ colorid,
uiButtonSectionsAlign align);
bool UI_region_button_sections_is_inside_x(const ARegion *region, const int mval_x);
/**
* Automatic aligning, horizontal or vertical.
*/

View File

@ -42,6 +42,7 @@ set(SRC
interface_align.cc
interface_anim.cc
interface_button_group.cc
interface_button_sections.cc
interface_context_menu.cc
interface_context_path.cc
interface_drag.cc

View File

@ -0,0 +1,225 @@
/* 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) {
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();
for (rcti &bounds : section_bounds) {
BLI_rcti_pad(&bounds, style->buttonspacex, style->buttonspacey);
/* 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,
JulianEisel marked this conversation as resolved Outdated

Span should be passed by value, otherwise it acts like a pointer to a pointer.

`Span` should be passed by value, otherwise it acts like a pointer to a pointer.

Heh, rushing things before the meeting much? Thanks :)

Heh, rushing things before the meeting much? Thanks :)
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);
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;
}

View File

@ -149,6 +149,73 @@ void UI_draw_roundbox_4fv(const rctf *rect, bool filled, float rad, const float
UI_draw_roundbox_4fv_ex(rect, (filled) ? col : nullptr, nullptr, 1.0f, col, U.pixelsize, rad);
}
void ui_draw_rounded_corners_inverted(const rcti &rect,
const float rad,
const blender::float4 color)
{
GPUVertFormat *format = immVertexFormat();
const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
float vec[4][2] = {
{0.195, 0.02},
{0.55, 0.169},
{0.831, 0.45},
{0.98, 0.805},
};
for (int a = 0; a < 4; a++) {
mul_v2_fl(vec[a], rad);
}
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
immUniformColor4fv(color);
if (roundboxtype & UI_CNR_TOP_LEFT) {
immBegin(GPU_PRIM_TRI_FAN, 7);
immVertex2f(pos, rect.xmin, rect.ymax);
immVertex2f(pos, rect.xmin, rect.ymax - rad);
for (int a = 0; a < 4; a++) {
immVertex2f(pos, rect.xmin + vec[a][1], rect.ymax - rad + vec[a][0]);
}
immVertex2f(pos, rect.xmin + rad, rect.ymax);
immEnd();
}
if (roundboxtype & UI_CNR_TOP_RIGHT) {
immBegin(GPU_PRIM_TRI_FAN, 7);
immVertex2f(pos, rect.xmax, rect.ymax);
immVertex2f(pos, rect.xmax - rad, rect.ymax);
for (int a = 0; a < 4; a++) {
immVertex2f(pos, rect.xmax - rad + vec[a][0], rect.ymax - vec[a][1]);
}
immVertex2f(pos, rect.xmax, rect.ymax - rad);
immEnd();
}
if (roundboxtype & UI_CNR_BOTTOM_RIGHT) {
immBegin(GPU_PRIM_TRI_FAN, 7);
immVertex2f(pos, rect.xmax, rect.ymin);
immVertex2f(pos, rect.xmax, rect.ymin + rad);
for (int a = 0; a < 4; a++) {
immVertex2f(pos, rect.xmax - vec[a][1], rect.ymin + rad - vec[a][0]);
}
immVertex2f(pos, rect.xmax - rad, rect.ymin);
immEnd();
}
if (roundboxtype & UI_CNR_BOTTOM_LEFT) {
immBegin(GPU_PRIM_TRI_FAN, 7);
immVertex2f(pos, rect.xmin, rect.ymin);
immVertex2f(pos, rect.xmin + rad, rect.ymin);
for (int a = 0; a < 4; a++) {
immVertex2f(pos, rect.xmin + rad - vec[a][0], rect.ymin + vec[a][1]);
}
immVertex2f(pos, rect.xmin, rect.ymin + rad);
immEnd();
}
immUnbindProgram();
}
void UI_draw_text_underline(int pos_x, int pos_y, int len, int height, const float color[4])
{
const int ofs_y = 4 * U.pixelsize;

View File

@ -11,6 +11,7 @@
#include <functional>
#include "BLI_compiler_attrs.h"
#include "BLI_math_vector_types.hh"
#include "BLI_rect.h"
#include "BLI_vector.hh"
@ -1015,6 +1016,16 @@ void ui_draw_dropshadow(const rctf *rct, float radius, float aspect, float alpha
*/
void ui_draw_gradient(const rcti *rect, const float hsv[3], eButGradientType type, float alpha);
/**
* Draws rounded corner segments but inverted. Imagine each corner like a filled right triangle,
* just that the hypotenuse is nicely curved inwards (towards the right angle of the triangle).
*
* Useful for connecting orthogonal shapes with a rounded corner, which can look quite nice.
*/
void ui_draw_rounded_corners_inverted(const rcti &rect,
const float rad,
const blender::float4 color);
/* based on UI_draw_roundbox_gl_mode,
* check on making a version which allows us to skip some sides */
void ui_draw_but_TAB_outline(const rcti *rect,

View File

@ -2820,6 +2820,14 @@ void ED_region_clear(const bContext *C, const ARegion *region, const int /*Theme
}
}
static void region_clear_fully_transparent(const bContext *C)
{
/* view should be in pixelspace */
UI_view2d_view_restore(C);
GPU_clear_color(0, 0, 0, 0);
}
BLI_INLINE bool streq_array_any(const char *s, const char *arr[])
{
for (uint i = 0; arr[i]; i++) {
@ -3574,11 +3582,8 @@ void ED_region_header_layout(const bContext *C, ARegion *region)
UI_view2d_view_restore(C);
}
void ED_region_header_draw(const bContext *C, ARegion *region)
static void region_draw_blocks_in_view2d(const bContext *C, const ARegion *region)
{
/* clear */
ED_region_clear(C, region, region_background_color_id(C, region));
UI_view2d_view_ortho(&region->v2d);
/* View2D matrix might have changed due to dynamic sized regions. */
@ -3591,6 +3596,31 @@ void ED_region_header_draw(const bContext *C, ARegion *region)
UI_view2d_view_restore(C);
}
void ED_region_header_draw(const bContext *C, ARegion *region)
{
/* clear */
ED_region_clear(C, region, region_background_color_id(C, region));
region_draw_blocks_in_view2d(C, region);
}
void ED_region_header_draw_with_button_sections(const bContext *C,
JulianEisel marked this conversation as resolved Outdated

Worth mentioning this follows ED_region_header_draw closely, it might make sense to but this directly below that function too so changes to one aren't as likely to be missed in the other.

Worth mentioning this follows `ED_region_header_draw` closely, it might make sense to but this directly below that function too so changes to one aren't as likely to be missed in the other.
const ARegion *region,
const uiButtonSectionsAlign align)
{
JulianEisel marked this conversation as resolved Outdated

*picky *normally -> normal.

*picky *normally -> normal.
const ThemeColorID bgcolorid = region_background_color_id(C, region);
/* Clear and draw button sections background when using region overlap. Otherwise clear using the
* background color like normal. */
if (region->overlap) {
region_clear_fully_transparent(C);
UI_region_button_sections_draw(region, bgcolorid, align);
}
else {
ED_region_clear(C, region, bgcolorid);
}
region_draw_blocks_in_view2d(C, region);
}
void ED_region_header(const bContext *C, ARegion *region)
{
/* TODO: remove? */
@ -3598,6 +3628,14 @@ void ED_region_header(const bContext *C, ARegion *region)
ED_region_header_draw(C, region);
}
void ED_region_header_with_button_sections(const bContext *C,
ARegion *region,
const uiButtonSectionsAlign align)
{
ED_region_header_layout(C, region);
ED_region_header_draw_with_button_sections(C, region, align);
}
void ED_region_header_init(ARegion *region)
{
UI_view2d_region_reinit(&region->v2d, V2D_COMMONVIEW_HEADER, region->winx, region->winy);

View File

@ -856,6 +856,20 @@ static AZone *area_actionzone_refresh_xy(ScrArea *area, const int xy[2], const b
break;
}
if (az->type == AZONE_REGION) {
const ARegion *region = az->region;
const int local_xy[2] = {xy[0] - region->winrct.xmin, xy[1] - region->winrct.ymin};
/* Respect button sections: If the mouse is horizontally hovering empty space defined by a
* separator-spacer between buttons, don't allow scaling the region from there. Used for
* regions that have a transparent background between such button sections, users don't
* expect to be able to resize from there. */
if (region->visible && (region->flag & RGN_FLAG_RESIZE_RESPECT_BUTTON_SECTIONS) &&
!UI_region_button_sections_is_inside_x(az->region, local_xy[0]))
{
az = nullptr;
break;
}
break;
}
if (az->type == AZONE_FULLSCREEN) {

View File

@ -740,6 +740,7 @@ enum {
/** #ARegionType.poll() failed for the current context, and the region should be treated as if it
* wouldn't exist. Runtime only flag. */
RGN_FLAG_POLL_FAILED = (1 << 10),
RGN_FLAG_RESIZE_RESPECT_BUTTON_SECTIONS = (1 << 11),
};
/** #ARegion.do_draw */

View File

@ -865,8 +865,6 @@ static bool rna_Space_show_region_asset_shelf_get(PointerRNA *ptr)
static void rna_Space_show_region_asset_shelf_set(PointerRNA *ptr, bool value)
{
rna_Space_bool_from_region_flag_set_by_type(ptr, RGN_TYPE_ASSET_SHELF, RGN_FLAG_HIDDEN, !value);
rna_Space_bool_from_region_flag_set_by_type(
ptr, RGN_TYPE_ASSET_SHELF_HEADER, RGN_FLAG_HIDDEN, !value);
}
static void rna_Space_show_region_asset_shelf_update(bContext *C, PointerRNA *ptr)
{