UI: Icon number indicator for data-blocks

Adds the possibility of having a little number on top of icons.

At the moment this is used for:
* Outliner
* Node Editor bread-crumb
* Node Group node header

For the outliner there is almost no functional change. It is mostly a refactor
to handle the indicators as part of the icon shader instead of the outliner
draw code. (note that this was already recently changed in a5d3b648e3).

The difference is that now we use rounded border rectangle instead of
circles, and we can go up to 999 elements.

So for the outliner this shows the number of collapsed elements of a
certain type (e.g., mesh objects inside a collapsed collection).

For the node editors is being used to show the use count for the data-block.
This is important for the node editor, so users know whether the node-group
they are editing (or are about to edit) is used elsewhere. This is
particularly important when the Node Options are hidden, which is the
default for node groups appended from the asset libraries.

---

Note: This can be easily enabled for ID templates which can then be part
of T84669. It just need to call UI_but_icon_indicator_number_set in the
function template_add_button_search_menu.

---

Special thanks Clément Foucault for the help figuring out the shader,
Julian Eisel for the help navigating the UI code, and Pablo Vazquez for
the collaboration in this design solution.

For images showing the result check the Differential Revision.
Differential Revision: https://developer.blender.org/D16284
This commit is contained in:
2022-10-20 16:37:07 +02:00
parent 58e25f11ae
commit 84825e4ed2
27 changed files with 498 additions and 161 deletions

View File

@@ -308,6 +308,28 @@ void BLI_str_format_byte_unit(char dst[15], long long int bytes, bool base_10) A
* Length of 7 is the maximum of the resulting string, for example, `-15.5K\0`.
*/
void BLI_str_format_decimal_unit(char dst[7], int number_to_format) ATTR_NONNULL();
/**
* Format a count to up to 3 places (plus minus sign, plus '\0' terminator) string using long
* number names abbreviations. Used to produce a compact representation of large numbers as
* integers.
*
* It shows a lower bound instead of rounding the number.
*
* 1 -> 1
* 15 -> 15
* 155 -> 155
* 1555 -> 1K
* 15555 -> 15K
* 155555 -> .1M
* 1555555 -> 1M
* 15555555 -> 15M
* 155555555 -> .1B
* 1000000000 -> 1B
* ...
*
* Length of 5 is the maximum of the resulting string, for example, `-15K\0`.
*/
void BLI_str_format_integer_unit(char dst[5], int number_to_format) ATTR_NONNULL();
/**
* Compare two strings without regard to case.
*

View File

@@ -1176,4 +1176,34 @@ void BLI_str_format_decimal_unit(char dst[7], int number_to_format)
BLI_snprintf(dst, dst_len, "%.*f%s", decimals, number_to_format_converted, units[order]);
}
void BLI_str_format_integer_unit(char dst[5], const int number_to_format)
{
float number_to_format_converted = number_to_format;
int order = 0;
const float base = 1000;
const char *units[] = {"", "K", "M", "B"};
const int units_num = ARRAY_SIZE(units);
while ((fabsf(number_to_format_converted) >= base) && ((order + 1) < units_num)) {
number_to_format_converted /= base;
order++;
}
const bool add_dot = (abs(number_to_format) > 99999) && fabsf(number_to_format_converted) > 99;
if (add_dot) {
number_to_format_converted /= 100;
order++;
}
const size_t dst_len = 5;
BLI_snprintf(dst,
dst_len,
"%s%s%d%s",
number_to_format < 0 ? "-" : "",
add_dot ? "." : "",
(int)floorf(fabsf(number_to_format_converted)),
units[order]);
}
/** \} */

View File

@@ -515,6 +515,103 @@ TEST(string, StrFormatDecimalUnits)
EXPECT_STREQ("-2.1B", size_str);
}
/* BLI_str_format_integer_unit */
TEST(string, StrFormatIntegerUnits)
{
char size_str[7];
int size;
BLI_str_format_integer_unit(size_str, size = 0);
EXPECT_STREQ("0", size_str);
BLI_str_format_integer_unit(size_str, size = 1);
EXPECT_STREQ("1", size_str);
BLI_str_format_integer_unit(size_str, size = 10);
EXPECT_STREQ("10", size_str);
BLI_str_format_integer_unit(size_str, size = 15);
EXPECT_STREQ("15", size_str);
BLI_str_format_integer_unit(size_str, size = 100);
EXPECT_STREQ("100", size_str);
BLI_str_format_integer_unit(size_str, size = 155);
EXPECT_STREQ("155", size_str);
BLI_str_format_integer_unit(size_str, size = 1000);
EXPECT_STREQ("1K", size_str);
BLI_str_format_integer_unit(size_str, size = 1555);
EXPECT_STREQ("1K", size_str);
BLI_str_format_integer_unit(size_str, size = 10000);
EXPECT_STREQ("10K", size_str);
BLI_str_format_integer_unit(size_str, size = 15555);
EXPECT_STREQ("15K", size_str);
BLI_str_format_integer_unit(size_str, size = 100000);
EXPECT_STREQ(".1M", size_str);
BLI_str_format_integer_unit(size_str, size = 155555);
EXPECT_STREQ(".1M", size_str);
BLI_str_format_integer_unit(size_str, size = 1000000);
EXPECT_STREQ("1M", size_str);
BLI_str_format_integer_unit(size_str, size = 1555555);
EXPECT_STREQ("1M", size_str);
BLI_str_format_integer_unit(size_str, size = 2555555);
EXPECT_STREQ("2M", size_str);
BLI_str_format_integer_unit(size_str, size = 10000000);
EXPECT_STREQ("10M", size_str);
BLI_str_format_integer_unit(size_str, size = 15555555);
EXPECT_STREQ("15M", size_str);
BLI_str_format_integer_unit(size_str, size = 100000000);
EXPECT_STREQ(".1B", size_str);
BLI_str_format_integer_unit(size_str, size = 155555555);
EXPECT_STREQ(".1B", size_str);
BLI_str_format_integer_unit(size_str, size = 255555555);
EXPECT_STREQ(".2B", size_str);
BLI_str_format_integer_unit(size_str, size = 1000000000);
EXPECT_STREQ("1B", size_str);
/* Largest possible value. */
BLI_str_format_integer_unit(size_str, size = INT32_MAX);
EXPECT_STREQ("2B", size_str);
BLI_str_format_integer_unit(size_str, size = -0);
EXPECT_STREQ("0", size_str);
BLI_str_format_integer_unit(size_str, size = -1);
EXPECT_STREQ("-1", size_str);
BLI_str_format_integer_unit(size_str, size = -10);
EXPECT_STREQ("-10", size_str);
BLI_str_format_integer_unit(size_str, size = -15);
EXPECT_STREQ("-15", size_str);
BLI_str_format_integer_unit(size_str, size = -100);
EXPECT_STREQ("-100", size_str);
BLI_str_format_integer_unit(size_str, size = -155);
EXPECT_STREQ("-155", size_str);
BLI_str_format_integer_unit(size_str, size = -1000);
EXPECT_STREQ("-1K", size_str);
BLI_str_format_integer_unit(size_str, size = -1555);
EXPECT_STREQ("-1K", size_str);
BLI_str_format_integer_unit(size_str, size = -10000);
EXPECT_STREQ("-10K", size_str);
BLI_str_format_integer_unit(size_str, size = -15555);
EXPECT_STREQ("-15K", size_str);
BLI_str_format_integer_unit(size_str, size = -100000);
EXPECT_STREQ("-.1M", size_str);
BLI_str_format_integer_unit(size_str, size = -155555);
EXPECT_STREQ("-.1M", size_str);
BLI_str_format_integer_unit(size_str, size = -1000000);
EXPECT_STREQ("-1M", size_str);
BLI_str_format_integer_unit(size_str, size = -1555555);
EXPECT_STREQ("-1M", size_str);
BLI_str_format_integer_unit(size_str, size = -10000000);
EXPECT_STREQ("-10M", size_str);
BLI_str_format_integer_unit(size_str, size = -15555555);
EXPECT_STREQ("-15M", size_str);
BLI_str_format_integer_unit(size_str, size = -100000000);
EXPECT_STREQ("-.1B", size_str);
BLI_str_format_integer_unit(size_str, size = -155555555);
EXPECT_STREQ("-.1B", size_str);
BLI_str_format_integer_unit(size_str, size = -1000000000);
EXPECT_STREQ("-1B", size_str);
/* Smallest possible value. */
BLI_str_format_integer_unit(size_str, size = -INT32_MAX);
EXPECT_STREQ("-2B", size_str);
}
struct WordInfo {
WordInfo() = default;
WordInfo(int start, int end) : start(start), end(end)

View File

@@ -1683,6 +1683,7 @@ int UI_search_items_find_index(uiSearchItems *items, const char *name);
* Adds a hint to the button which draws right aligned, grayed out and never clipped.
*/
void UI_but_hint_drawstr_set(uiBut *but, const char *string);
void UI_but_icon_indicator_number_set(uiBut *but, const int indicator_number);
void UI_but_node_link_set(uiBut *but, struct bNodeSocket *socket, const float draw_color[4]);
@@ -2788,7 +2789,8 @@ typedef struct uiPropertySplitWrapper {
uiPropertySplitWrapper uiItemPropertySplitWrapperCreate(uiLayout *parent_layout);
void uiItemL(uiLayout *layout, const char *name, int icon); /* label */
void uiItemL_ex(uiLayout *layout, const char *name, int icon, bool highlight, bool redalert);
struct uiBut *uiItemL_ex(
uiLayout *layout, const char *name, int icon, bool highlight, bool redalert);
/**
* Helper to add a label and creates a property split layout if needed.
*/

View File

@@ -35,6 +35,7 @@ struct ContextPathItem {
std::string name;
/* #BIFIconID */
int icon;
int icon_indicator_number;
};
void context_path_add_generic(Vector<ContextPathItem> &path,

View File

@@ -27,6 +27,12 @@ typedef struct IconFile {
int index;
} IconFile;
typedef struct IconTextOverlay {
char text[5];
} IconTextOverlay;
#define UI_NO_ICON_OVERLAY_TEXT NULL
#define ICON_DEFAULT_HEIGHT 16
#define ICON_DEFAULT_WIDTH 16
@@ -105,7 +111,8 @@ void UI_icon_draw_ex(float x,
float alpha,
float desaturate,
const uchar mono_color[4],
bool mono_border);
bool mono_border,
const struct IconTextOverlay *text_overlay);
void UI_icons_free(void);
void UI_icons_free_drawinfo(void *drawinfo);
@@ -124,6 +131,9 @@ int UI_icon_from_library(const struct ID *id);
int UI_icon_from_object_mode(int mode);
int UI_icon_color_from_collection(const struct Collection *collection);
void UI_icon_text_overlay_init_from_count(struct IconTextOverlay *text_overlay,
const int icon_indicator_number);
#ifdef __cplusplus
}
#endif

View File

@@ -6452,6 +6452,11 @@ void UI_but_hint_drawstr_set(uiBut *but, const char *string)
ui_but_add_shortcut(but, string, false);
}
void UI_but_icon_indicator_number_set(uiBut *but, const int indicator_number)
{
UI_icon_text_overlay_init_from_count(&but->icon_overlay_text, indicator_number);
}
void UI_but_node_link_set(uiBut *but, bNodeSocket *socket, const float draw_color[4])
{
but->flag |= UI_BUT_NODE_LINK;

View File

@@ -17,6 +17,8 @@
#include "UI_interface.hh"
#include "UI_resources.h"
#include "RNA_prototypes.h"
#include "WM_api.h"
namespace blender::ui {
@@ -41,7 +43,13 @@ void context_path_add_generic(Vector<ContextPathItem> &path,
static_cast<BIFIconID>(RNA_struct_ui_icon(rna_ptr.type)) :
icon_override;
path.append({name, int(icon)});
if (&rna_type == &RNA_NodeTree) {
ID *id = (ID *)ptr;
path.append({name, int(icon), id->us});
}
else {
path.append({name, int(icon), 1});
}
}
/* -------------------------------------------------------------------- */
@@ -60,7 +68,9 @@ void template_breadcrumbs(uiLayout &layout, Span<ContextPathItem> context_path)
if (i > 0) {
uiItemL(sub_row, "", ICON_RIGHTARROW_THIN);
}
uiItemL(sub_row, context_path[i].name.c_str(), context_path[i].icon);
uiBut *but = uiItemL_ex(
sub_row, context_path[i].name.c_str(), context_path[i].icon, false, false);
UI_but_icon_indicator_number_set(but, context_path[i].icon_indicator_number);
}
}

View File

@@ -415,8 +415,15 @@ static void vicon_collection_color_draw(
const float aspect = (float)ICON_DEFAULT_WIDTH / (float)w;
UI_icon_draw_ex(
x, y, ICON_OUTLINER_COLLECTION, aspect, 1.0f, 0.0f, collection_color->color, true);
UI_icon_draw_ex(x,
y,
ICON_OUTLINER_COLLECTION,
aspect,
1.0f,
0.0f,
collection_color->color,
true,
UI_NO_ICON_OVERLAY_TEXT);
}
# define DEF_ICON_COLLECTION_COLOR_DRAW(index, color) \
@@ -444,7 +451,8 @@ static void vicon_strip_color_draw(
const float aspect = (float)ICON_DEFAULT_WIDTH / (float)w;
UI_icon_draw_ex(x, y, ICON_SNAP_FACE, aspect, 1.0f, 0.0f, strip_color->color, true);
UI_icon_draw_ex(
x, y, ICON_SNAP_FACE, aspect, 1.0f, 0.0f, strip_color->color, true, UI_NO_ICON_OVERLAY_TEXT);
}
# define DEF_ICON_STRIP_COLOR_DRAW(index, color) \
@@ -472,8 +480,15 @@ static void vicon_strip_color_draw_library_data_indirect(
{
const float aspect = (float)ICON_DEFAULT_WIDTH / (float)w;
UI_icon_draw_ex(
x, y, ICON_LIBRARY_DATA_DIRECT, aspect, ICON_INDIRECT_DATA_ALPHA * alpha, 0.0f, NULL, false);
UI_icon_draw_ex(x,
y,
ICON_LIBRARY_DATA_DIRECT,
aspect,
ICON_INDIRECT_DATA_ALPHA * alpha,
0.0f,
NULL,
false,
UI_NO_ICON_OVERLAY_TEXT);
}
static void vicon_strip_color_draw_library_data_override_noneditable(
@@ -488,7 +503,8 @@ static void vicon_strip_color_draw_library_data_override_noneditable(
ICON_INDIRECT_DATA_ALPHA * alpha * 0.75f,
0.0f,
NULL,
false);
false,
UI_NO_ICON_OVERLAY_TEXT);
}
/* Dynamically render icon instead of rendering a plain color to a texture/buffer
@@ -1716,9 +1732,47 @@ static void icon_draw_texture(float x,
int ih,
float alpha,
const float rgb[3],
bool with_border)
bool with_border,
const IconTextOverlay *text_overlay)
{
if (g_icon_draw_cache.enabled) {
const float zoom_factor = w / UI_DPI_ICON_SIZE;
float text_width = 0.0f;
/* No need to show if too zoomed out, otherwise it just adds noise. */
const bool show_indicator = (text_overlay && text_overlay->text[0] != '\0') &&
(zoom_factor > 0.7f);
if (show_indicator) {
/* Handle the little numbers on top of the icon. */
uchar text_color[4];
UI_GetThemeColor3ubv(TH_TEXT, text_color);
text_color[3] = 255;
uiFontStyle fstyle_small = *UI_FSTYLE_WIDGET;
fstyle_small.points *= zoom_factor;
fstyle_small.points *= 0.8f;
rcti text_rect = {
.xmax = x + UI_UNIT_X * zoom_factor,
.xmin = x,
.ymax = y,
.ymin = y,
};
UI_fontstyle_draw(&fstyle_small,
&text_rect,
text_overlay->text,
sizeof(text_overlay->text),
text_color,
&(struct uiFontStyleDraw_Params){
.align = UI_STYLE_TEXT_RIGHT,
});
text_width = (float)UI_fontstyle_string_width(&fstyle_small, text_overlay->text) / UI_UNIT_X /
zoom_factor;
}
/* Draw the actual icon. */
if (!show_indicator && g_icon_draw_cache.enabled) {
icon_draw_texture_cached(x, y, w, h, ix, iy, iw, ih, alpha, rgb, with_border);
return;
}
@@ -1735,7 +1789,7 @@ static void icon_draw_texture(float x,
GPUTexture *texture = with_border ? icongltex.tex[1] : icongltex.tex[0];
GPUShader *shader = GPU_shader_get_builtin_shader(GPU_SHADER_2D_IMAGE_RECT_COLOR);
GPUShader *shader = GPU_shader_get_builtin_shader(GPU_SHADER_ICON);
GPU_shader_bind(shader);
const int img_binding = GPU_shader_get_texture_binding(shader, "image");
@@ -1752,6 +1806,7 @@ static void icon_draw_texture(float x,
GPU_shader_uniform_vector(shader, rect_tex_loc, 4, 1, (float[4]){x1, y1, x2, y2});
GPU_shader_uniform_vector(shader, rect_geom_loc, 4, 1, (float[4]){x, y, x + w, y + h});
GPU_shader_uniform_1f(shader, "text_width", text_width);
GPU_texture_bind_ex(texture, GPU_SAMPLER_ICON, img_binding, false);
@@ -1786,7 +1841,8 @@ static void icon_draw_size(float x,
int draw_size,
const float desaturate,
const uchar mono_rgba[4],
const bool mono_border)
const bool mono_border,
const IconTextOverlay *text_overlay)
{
bTheme *btheme = UI_GetTheme();
const float fdraw_size = (float)draw_size;
@@ -1874,7 +1930,8 @@ static void icon_draw_size(float x,
di->data.texture.h,
alpha,
NULL,
false);
false,
text_overlay);
}
else if (di->type == ICON_TYPE_MONO_TEXTURE) {
/* Monochrome icon that uses text or theme color. */
@@ -1908,7 +1965,8 @@ static void icon_draw_size(float x,
di->data.texture.h + 2 * border_texel,
color[3],
color,
with_border);
with_border,
text_overlay);
}
else if (di->type == ICON_TYPE_BUFFER) {
@@ -2425,17 +2483,27 @@ int UI_icon_color_from_collection(const Collection *collection)
void UI_icon_draw(float x, float y, int icon_id)
{
UI_icon_draw_ex(x, y, icon_id, U.inv_dpi_fac, 1.0f, 0.0f, NULL, false);
UI_icon_draw_ex(x, y, icon_id, U.inv_dpi_fac, 1.0f, 0.0f, NULL, false, UI_NO_ICON_OVERLAY_TEXT);
}
void UI_icon_draw_alpha(float x, float y, int icon_id, float alpha)
{
UI_icon_draw_ex(x, y, icon_id, U.inv_dpi_fac, alpha, 0.0f, NULL, false);
UI_icon_draw_ex(x, y, icon_id, U.inv_dpi_fac, alpha, 0.0f, NULL, false, UI_NO_ICON_OVERLAY_TEXT);
}
void UI_icon_draw_preview(float x, float y, int icon_id, float aspect, float alpha, int size)
{
icon_draw_size(x, y, icon_id, aspect, alpha, ICON_SIZE_PREVIEW, size, false, NULL, false);
icon_draw_size(x,
y,
icon_id,
aspect,
alpha,
ICON_SIZE_PREVIEW,
size,
false,
NULL,
false,
UI_NO_ICON_OVERLAY_TEXT);
}
void UI_icon_draw_ex(float x,
@@ -2445,7 +2513,8 @@ void UI_icon_draw_ex(float x,
float alpha,
float desaturate,
const uchar mono_color[4],
const bool mono_border)
const bool mono_border,
const IconTextOverlay *text_overlay)
{
const int draw_size = get_draw_size(ICON_SIZE_ICON);
icon_draw_size(x,
@@ -2457,7 +2526,19 @@ void UI_icon_draw_ex(float x,
draw_size,
desaturate,
mono_color,
mono_border);
mono_border,
text_overlay);
}
void UI_icon_text_overlay_init_from_count(IconTextOverlay *text_overlay,
const int icon_indicator_number)
{
/* The icon indicator is used as an aggregator, no need to show if it is 1. */
if (icon_indicator_number < 2) {
text_overlay->text[0] = '\0';
return;
}
BLI_str_format_integer_unit(text_overlay->text, icon_indicator_number);
}
/* ********** Alert Icons ********** */

View File

@@ -23,6 +23,7 @@ struct ARegion;
struct AnimationEvalContext;
struct CurveMapping;
struct CurveProfile;
struct IconTextOverlay;
struct ID;
struct ImBuf;
struct Main;
@@ -275,6 +276,9 @@ struct uiBut {
uiButPushedStateFunc pushed_state_func;
const void *pushed_state_arg;
/** Little indicator (e.g., counter) displayed on top of some icons. */
struct IconTextOverlay icon_overlay_text;
/* pointer back */
uiBlock *block;
};

View File

@@ -3235,7 +3235,7 @@ static uiBut *uiItemL_(uiLayout *layout, const char *name, int icon)
return but;
}
void uiItemL_ex(
uiBut *uiItemL_ex(
uiLayout *layout, const char *name, int icon, const bool highlight, const bool redalert)
{
uiBut *but = uiItemL_(layout, name, icon);
@@ -3248,6 +3248,8 @@ void uiItemL_ex(
if (redalert) {
UI_but_flag_enable(but, UI_BUT_REDALERT);
}
return but;
}
void uiItemL(uiLayout *layout, const char *name, int icon)

View File

@@ -1112,7 +1112,8 @@ static void panel_draw_aligned_widgets(const uiStyle *style,
0.7f,
0.0f,
title_color,
false);
false,
UI_NO_ICON_OVERLAY_TEXT);
GPU_blend(GPU_BLEND_NONE);
}
@@ -1140,7 +1141,8 @@ static void panel_draw_aligned_widgets(const uiStyle *style,
1.0f,
0.0f,
title_color,
false);
false,
UI_NO_ICON_OVERLAY_TEXT);
GPU_blend(GPU_BLEND_NONE);
}

View File

@@ -1420,21 +1420,25 @@ static void widget_draw_icon(
/* to indicate draggable */
if (ui_but_drag_is_draggable(but) && (but->flag & UI_ACTIVE)) {
UI_icon_draw_ex(xs, ys, icon, aspect, 1.25f, 0.0f, color, has_theme);
UI_icon_draw_ex(
xs, ys, icon, aspect, 1.25f, 0.0f, color, has_theme, &but->icon_overlay_text);
}
else if (but->flag & (UI_ACTIVE | UI_SELECT | UI_SELECT_DRAW)) {
UI_icon_draw_ex(xs, ys, icon, aspect, alpha, 0.0f, color, has_theme);
UI_icon_draw_ex(
xs, ys, icon, aspect, alpha, 0.0f, color, has_theme, &but->icon_overlay_text);
}
else if (!((but->icon != ICON_NONE) && UI_but_is_tool(but))) {
if (has_theme) {
alpha *= 0.8f;
}
UI_icon_draw_ex(xs, ys, icon, aspect, alpha, 0.0f, color, has_theme);
UI_icon_draw_ex(
xs, ys, icon, aspect, alpha, 0.0f, color, has_theme, &but->icon_overlay_text);
}
else {
const bTheme *btheme = UI_GetTheme();
const float desaturate = 1.0 - btheme->tui.icon_saturation;
UI_icon_draw_ex(xs, ys, icon, aspect, alpha, desaturate, color, has_theme);
UI_icon_draw_ex(
xs, ys, icon, aspect, alpha, desaturate, color, has_theme, &but->icon_overlay_text);
}
}
@@ -5426,7 +5430,8 @@ void ui_draw_menu_item(const uiFontStyle *fstyle,
GPU_blend(GPU_BLEND_ALPHA);
/* XXX scale weak get from fstyle? */
UI_icon_draw_ex(xs, ys, iconid, aspect, 1.0f, 0.0f, wt->wcol.text, false);
UI_icon_draw_ex(
xs, ys, iconid, aspect, 1.0f, 0.0f, wt->wcol.text, false, UI_NO_ICON_OVERLAY_TEXT);
GPU_blend(GPU_BLEND_NONE);
}

View File

@@ -187,7 +187,8 @@ static void area_draw_azone_fullscreen(
min_ff(alpha, 0.75f),
0.0f,
NULL,
false);
false,
UI_NO_ICON_OVERLAY_TEXT);
}
/**

View File

@@ -410,8 +410,15 @@ static void file_draw_preview(const SpaceFile *sfile,
}
icon_x = xco + (ex / 2.0f) - (icon_size / 2.0f);
icon_y = yco + (ey / 2.0f) - (icon_size * ((file->typeflag & FILE_TYPE_DIR) ? 0.78f : 0.75f));
UI_icon_draw_ex(
icon_x, icon_y, icon, icon_aspect / U.dpi_fac, icon_opacity, 0.0f, icon_color, false);
UI_icon_draw_ex(icon_x,
icon_y,
icon,
icon_aspect / U.dpi_fac,
icon_opacity,
0.0f,
icon_color,
false,
UI_NO_ICON_OVERLAY_TEXT);
}
if (is_link || is_offline) {
@@ -424,8 +431,24 @@ static void file_draw_preview(const SpaceFile *sfile,
/* At very bottom-left if preview style. */
const uchar dark[4] = {0, 0, 0, 255};
const uchar light[4] = {255, 255, 255, 255};
UI_icon_draw_ex(icon_x + 1, icon_y - 1, arrow, 1.0f / U.dpi_fac, 0.2f, 0.0f, dark, false);
UI_icon_draw_ex(icon_x, icon_y, arrow, 1.0f / U.dpi_fac, 0.6f, 0.0f, light, false);
UI_icon_draw_ex(icon_x + 1,
icon_y - 1,
arrow,
1.0f / U.dpi_fac,
0.2f,
0.0f,
dark,
false,
UI_NO_ICON_OVERLAY_TEXT);
UI_icon_draw_ex(icon_x,
icon_y,
arrow,
1.0f / U.dpi_fac,
0.6f,
0.0f,
light,
false,
UI_NO_ICON_OVERLAY_TEXT);
}
else {
/* Link to folder or non-previewed file. */
@@ -433,8 +456,15 @@ static void file_draw_preview(const SpaceFile *sfile,
UI_GetThemeColor4ubv(TH_BACK, icon_color);
icon_x = xco + ((file->typeflag & FILE_TYPE_DIR) ? 0.14f : 0.23f) * scaledx;
icon_y = yco + ((file->typeflag & FILE_TYPE_DIR) ? 0.24f : 0.14f) * scaledy;
UI_icon_draw_ex(
icon_x, icon_y, arrow, icon_aspect / U.dpi_fac * 1.8, 0.3f, 0.0f, icon_color, false);
UI_icon_draw_ex(icon_x,
icon_y,
arrow,
icon_aspect / U.dpi_fac * 1.8,
0.3f,
0.0f,
icon_color,
false,
UI_NO_ICON_OVERLAY_TEXT);
}
}
else if (icon && !is_icon && !(file->typeflag & FILE_TYPE_FTFONT)) {
@@ -444,8 +474,17 @@ static void file_draw_preview(const SpaceFile *sfile,
const uchar light[4] = {255, 255, 255, 255};
icon_x = xco + (2.0f * UI_DPI_FAC);
icon_y = yco + (2.0f * UI_DPI_FAC);
UI_icon_draw_ex(icon_x + 1, icon_y - 1, icon, 1.0f / U.dpi_fac, 0.2f, 0.0f, dark, false);
UI_icon_draw_ex(icon_x, icon_y, icon, 1.0f / U.dpi_fac, 0.6f, 0.0f, light, false);
UI_icon_draw_ex(icon_x + 1,
icon_y - 1,
icon,
1.0f / U.dpi_fac,
0.2f,
0.0f,
dark,
false,
UI_NO_ICON_OVERLAY_TEXT);
UI_icon_draw_ex(
icon_x, icon_y, icon, 1.0f / U.dpi_fac, 0.6f, 0.0f, light, false, UI_NO_ICON_OVERLAY_TEXT);
}
const bool is_current_main_data = filelist_file_get_id(file) != NULL;
@@ -456,7 +495,15 @@ static void file_draw_preview(const SpaceFile *sfile,
const uchar light[4] = {255, 255, 255, 255};
icon_x = xco + ex - UI_UNIT_X;
icon_y = yco + ey - UI_UNIT_Y;
UI_icon_draw_ex(icon_x, icon_y, ICON_CURRENT_FILE, 1.0f / U.dpi_fac, 0.6f, 0.0f, light, false);
UI_icon_draw_ex(icon_x,
icon_y,
ICON_CURRENT_FILE,
1.0f / U.dpi_fac,
0.6f,
0.0f,
light,
false,
UI_NO_ICON_OVERLAY_TEXT);
}
/* Contrasting outline around some preview types. */

View File

@@ -235,7 +235,8 @@ static bool textview_draw_string(TextViewDrawState *tds,
1.0f,
0.0f,
icon_fg,
false);
false,
UI_NO_ICON_OVERLAY_TEXT);
GPU_blend(GPU_BLEND_NONE);
}

View File

@@ -2145,6 +2145,9 @@ static void node_draw_basis(const bContext &C,
0,
"");
UI_but_func_set(but, node_toggle_button_cb, &node, (void *)"NODE_OT_group_edit");
if (node.id) {
UI_but_icon_indicator_number_set(but, node.id->us);
}
UI_block_emboss_set(&block, UI_EMBOSS);
}
if (node.type == NODE_CUSTOM && node.typeinfo->ui_icon != ICON_NONE) {

View File

@@ -815,7 +815,8 @@ static void draw_draglink_tooltip(const bContext * /*C*/, ARegion * /*region*/,
nldrag->cursor[0];
const float y = nldrag->cursor[1] - 2.0f * UI_DPI_FAC;
UI_icon_draw_ex(x, y, ICON_ADD, U.inv_dpi_fac, 1.0f, 0.0f, text_col, false);
UI_icon_draw_ex(
x, y, ICON_ADD, U.inv_dpi_fac, 1.0f, 0.0f, text_col, false, UI_NO_ICON_OVERLAY_TEXT);
}
static void draw_draglink_tooltip_activate(const ARegion &region, bNodeLinkDrag &nldrag)

View File

@@ -2877,7 +2877,8 @@ static bool tselem_draw_icon(uiBlock *block,
TreeStoreElem *tselem,
TreeElement *te,
float alpha,
const bool is_clickable)
const bool is_clickable,
const int num_elements)
{
TreeElementIcon data = tree_element_get_icon(tselem, te);
if (data.icon == 0) {
@@ -2885,6 +2886,8 @@ static bool tselem_draw_icon(uiBlock *block,
}
const bool is_collection = outliner_is_collection_tree_element(te);
IconTextOverlay text_overlay;
UI_icon_text_overlay_init_from_count(&text_overlay, num_elements);
/* Collection colors and icons covered by restrict buttons. */
if (!is_clickable || x >= xmax || is_collection) {
@@ -2904,7 +2907,8 @@ static bool tselem_draw_icon(uiBlock *block,
alpha,
0.0f,
btheme->collection_color[collection->color_tag].color,
true);
true,
&text_overlay);
return true;
}
}
@@ -2915,10 +2919,10 @@ static bool tselem_draw_icon(uiBlock *block,
/* Restrict column clip. it has been coded by simply overdrawing, doesn't work for buttons. */
uchar color[4];
if (UI_icon_get_theme_color(data.icon, color)) {
UI_icon_draw_ex(x, y, data.icon, U.inv_dpi_fac, alpha, 0.0f, color, true);
UI_icon_draw_ex(x, y, data.icon, U.inv_dpi_fac, alpha, 0.0f, color, true, &text_overlay);
}
else {
UI_icon_draw_ex(x, y, data.icon, U.inv_dpi_fac, alpha, 0.0f, nullptr, false);
UI_icon_draw_ex(x, y, data.icon, U.inv_dpi_fac, alpha, 0.0f, nullptr, false, &text_overlay);
}
}
else {
@@ -2941,104 +2945,6 @@ static bool tselem_draw_icon(uiBlock *block,
return true;
}
static bool outliner_is_main_row(const ARegion *region, const int ys)
{
int ystart;
ystart = int(region->v2d.tot.ymax);
ystart = UI_UNIT_Y * (ystart / (UI_UNIT_Y)) - OL_Y_OFFSET;
return ((ys - ystart) / UI_UNIT_Y) % 2;
}
/**
* Get the expected row background color to use for the data-block counter
*
* This reproduces some of the logic of outliner_draw_highlights.
* At the moment it doesn't implement the search match color since
* we don't draw the data-block counter in those cases.
*/
static void outliner_get_row_color(const ARegion *region,
const TreeElement *te,
int ys,
float r_color[4])
{
const TreeStoreElem *tselem = TREESTORE(te);
if ((tselem->flag & TSE_ACTIVE) && (tselem->flag & TSE_SELECTED)) {
UI_GetThemeColor3fv(TH_ACTIVE, r_color);
}
else if (tselem->flag & TSE_SELECTED) {
UI_GetThemeColor3fv(TH_SELECT_HIGHLIGHT, r_color);
}
else if (outliner_is_main_row(region, ys)) {
UI_GetThemeColor3fv(TH_BACK, r_color);
}
else {
float color_alternating[4];
UI_GetThemeColor4fv(TH_ROW_ALTERNATE, color_alternating);
UI_GetThemeColorBlend3f(TH_BACK, TH_ROW_ALTERNATE, color_alternating[3], r_color);
}
if (tselem->flag & TSE_HIGHLIGHTED) {
const float color_highlight[4] = {1.0f, 1.0f, 1.0f, 0.13f};
interp_v3_v3v3(r_color, r_color, color_highlight, color_highlight[3]);
}
r_color[3] = 1.0f;
}
/**
* For icon-only children of a collapsed tree,
* Draw small number over the icon to show how many items of this type are displayed.
*/
static void outliner_draw_iconrow_number(const ARegion *region,
const uiFontStyle *fstyle,
int offsx,
int ys,
const TreeElement *te_visible,
const int num_elements)
{
float color[4];
outliner_get_row_color(region, te_visible, ys, color);
float ufac = 0.25f * UI_UNIT_X;
float offset_x = float(offsx) + UI_UNIT_X * 0.35f;
rctf rect{};
BLI_rctf_init(&rect,
offset_x + ufac,
offset_x + UI_UNIT_X - ufac,
float(ys) - UI_UNIT_Y * 0.2f + ufac,
float(ys) - UI_UNIT_Y * 0.2f + UI_UNIT_Y - ufac);
UI_draw_roundbox_corner_set(UI_CNR_ALL);
UI_draw_roundbox_4fv_ex(
&rect, color, NULL, 1.0f, color, U.pixelsize, float(UI_UNIT_Y) / 2.0f - ufac);
/* Now the numbers. */
uchar text_col[4];
UI_GetThemeColor3ubv(TH_TEXT, text_col);
text_col[3] = 255;
uiFontStyle fstyle_small = *fstyle;
fstyle_small.points *= 0.8f;
/* We treat +99 as 4 digits to make sure the (eyeballed) alignment looks nice. */
int num_digits = 4;
char number_text[4] = "+99";
if (num_elements < 100) {
BLI_snprintf(number_text, sizeof(number_text), "%d", num_elements);
num_digits = num_elements < 10 ? 1 : 2;
}
UI_fontstyle_draw_simple(&fstyle_small,
(offset_x + ufac + UI_UNIT_X * (2 - num_digits) * 0.12f),
float(ys) - UI_UNIT_Y * 0.095f + ufac,
number_text,
text_col);
UI_fontstyle_set(fstyle);
GPU_blend(GPU_BLEND_ALPHA); /* Round-box and text drawing disables. */
}
static void outliner_icon_background_colors(float icon_color[4], float icon_border[4])
{
float text[4];
@@ -3069,11 +2975,8 @@ static void outliner_draw_active_indicator(const float minx,
GPU_blend(GPU_BLEND_ALPHA); /* Round-box disables. */
}
static void outliner_draw_iconrow_doit(const ARegion *region,
uiBlock *block,
TreeElement *te_visible,
static void outliner_draw_iconrow_doit(uiBlock *block,
TreeElement *te,
const uiFontStyle *fstyle,
int xmax,
int *offsx,
int ys,
@@ -3102,13 +3005,13 @@ static void outliner_draw_iconrow_doit(const ARegion *region,
if (tselem->flag & TSE_HIGHLIGHTED_ICON) {
alpha_fac += 0.5;
}
tselem_draw_icon(block, xmax, float(*offsx), float(ys), tselem, te, alpha_fac, false);
tselem_draw_icon(
block, xmax, float(*offsx), float(ys), tselem, te, alpha_fac, false, num_elements);
te->xs = *offsx;
te->ys = ys;
te->xend = short(*offsx) + UI_UNIT_X;
if (num_elements > 1) {
outliner_draw_iconrow_number(region, fstyle, *offsx, ys, te_visible, num_elements);
te->flag |= TE_ICONROW_MERGED;
}
else {
@@ -3145,7 +3048,6 @@ static void outliner_draw_iconrow(bContext *C,
const uiFontStyle *fstyle,
const TreeViewContext *tvc,
SpaceOutliner *space_outliner,
TreeElement *te_visible,
ListBase *lb,
int level,
int xmax,
@@ -3154,7 +3056,6 @@ static void outliner_draw_iconrow(bContext *C,
float alpha_fac,
MergedIconRow *merged)
{
const ARegion *region = CTX_wm_region(C);
eOLDrawState active = OL_DRAWSEL_NONE;
LISTBASE_FOREACH (TreeElement *, te, lb) {
@@ -3194,8 +3095,7 @@ static void outliner_draw_iconrow(bContext *C,
TSE_POSE_CHANNEL,
TSE_POSEGRP,
TSE_DEFGROUP)) {
outliner_draw_iconrow_doit(
region, block, te_visible, te, fstyle, xmax, offsx, ys, alpha_fac, active, 1);
outliner_draw_iconrow_doit(block, te, xmax, offsx, ys, alpha_fac, active, 1);
}
else {
const int index = tree_element_id_type_to_index(te);
@@ -3214,7 +3114,6 @@ static void outliner_draw_iconrow(bContext *C,
fstyle,
tvc,
space_outliner,
te,
&te->subtree,
level + 1,
xmax,
@@ -3236,11 +3135,8 @@ static void outliner_draw_iconrow(bContext *C,
for (int j = 0; j < num_subtypes; j++) {
const int index = index_base + j;
if (merged->num_elements[index] != 0) {
outliner_draw_iconrow_doit(region,
block,
te_visible,
outliner_draw_iconrow_doit(block,
merged->tree_element[index],
fstyle,
xmax,
offsx,
ys,
@@ -3429,7 +3325,8 @@ static void outliner_draw_tree_element(bContext *C,
tselem,
te,
(tselem->flag & TSE_HIGHLIGHTED_ICON) ? alpha_fac + 0.5f : alpha_fac,
true)) {
true,
1)) {
offsx += UI_UNIT_X + 4 * ufac;
}
else {
@@ -3478,7 +3375,6 @@ static void outliner_draw_tree_element(bContext *C,
fstyle,
tvc,
space_outliner,
te,
&te->subtree,
0,
xmax,

View File

@@ -282,6 +282,8 @@ set(GLSL_SRC
shaders/gpu_shader_2D_image_vert.glsl
shaders/gpu_shader_2D_image_rect_vert.glsl
shaders/gpu_shader_2D_image_multi_rect_vert.glsl
shaders/gpu_shader_icon_frag.glsl
shaders/gpu_shader_icon_vert.glsl
shaders/gpu_shader_image_frag.glsl
shaders/gpu_shader_image_desaturate_frag.glsl
shaders/gpu_shader_image_overlays_merge_frag.glsl
@@ -609,6 +611,7 @@ set(SRC_SHADER_CREATE_INFOS
shaders/infos/gpu_shader_3D_smooth_color_info.hh
shaders/infos/gpu_shader_3D_uniform_color_info.hh
shaders/infos/gpu_shader_gpencil_stroke_info.hh
shaders/infos/gpu_shader_icon_info.hh
shaders/infos/gpu_shader_instance_varying_color_varying_size_info.hh
shaders/infos/gpu_shader_keyframe_shape_info.hh
shaders/infos/gpu_shader_line_dashed_uniform_color_info.hh

View File

@@ -208,6 +208,10 @@ typedef enum eGPUBuiltinShader {
GPU_SHADER_TEXT,
GPU_SHADER_KEYFRAME_SHAPE,
GPU_SHADER_SIMPLE_LIGHTING,
/**
* Draw an icon, leaving a semi-transparent rectangle on top of the icon.
*/
GPU_SHADER_ICON,
/**
* Take a 2D position and color for each vertex with linear interpolation in window space.
*

View File

@@ -153,6 +153,11 @@ static const GPUShaderStages builtin_shader_stages[GPU_SHADER_BUILTIN_LEN] = {
.create_info = "gpu_shader_2D_diag_stripes",
},
[GPU_SHADER_ICON] =
{
.name = "GPU_SHADER_ICON",
.create_info = "gpu_shader_icon",
},
[GPU_SHADER_2D_IMAGE_OVERLAYS_MERGE] =
{
.name = "GPU_SHADER_2D_IMAGE_OVERLAYS_MERGE",

View File

@@ -0,0 +1,42 @@
/**
* Draw the icons, leaving a semi-transparent rectangle on top of the icon.
*
* The top-left corner of the rectangle is rounded and drawned with anti-alias.
* The anti-alias is done by transitioning from the outer to the inner radius of
* the rounded corner, and the rectangle sides.
*/
void main()
{
/* Top-left rounded corner parameters. */
const float circle_radius_outer = 0.1;
const float circle_radius_inner = 0.075;
/**
* Add a bit transparency to see a bit of the icon, without
* getting on the way of readability. */
const float mask_transparency = 0.25;
vec2 circle_center = vec2(circle_radius_outer - text_width, 0.5);
fragColor = texture(image, texCoord_interp) * color;
/* radius in icon space (1 is the icon width). */
float radius = length(mask_coord_interp - circle_center);
float mask = smoothstep(circle_radius_inner, circle_radius_outer, radius);
bool lower_half = mask_coord_interp.y < circle_center.y;
bool right_half = mask_coord_interp.x > circle_center.x;
if (right_half && mask_coord_interp.t < circle_center.y + circle_radius_outer) {
mask = smoothstep(circle_center.y + circle_radius_inner,
circle_center.y + circle_radius_outer,
mask_coord_interp.t);
}
if (lower_half && mask_coord_interp.s > circle_center.x - circle_radius_outer) {
mask = smoothstep(circle_center.x - circle_radius_inner,
circle_center.x - circle_radius_outer,
mask_coord_interp.s);
}
fragColor = mix(vec4(0.0), fragColor, max(mask_transparency, mask));
}

View File

@@ -0,0 +1,37 @@
/**
* Simple shader that just draw one icon at the specified location
* does not need any vertex input (producing less call to immBegin/End)
*/
void main()
{
vec2 uv;
vec2 co;
if (gl_VertexID == 0) {
co = rect_geom.xw;
uv = rect_icon.xw;
mask_coord_interp = vec2(0, 1);
}
else if (gl_VertexID == 1) {
co = rect_geom.xy;
uv = rect_icon.xy;
mask_coord_interp = vec2(0, 0);
}
else if (gl_VertexID == 2) {
co = rect_geom.zw;
uv = rect_icon.zw;
mask_coord_interp = vec2(1, 1);
}
else {
co = rect_geom.zy;
uv = rect_icon.zy;
mask_coord_interp = vec2(1, 0);
}
/* Put origin in lower right corner. */
mask_coord_interp.x -= 1;
gl_Position = ModelViewProjectionMatrix * vec4(co, 0.0f, 1.0f);
texCoord_interp = uv;
}

View File

@@ -18,3 +18,6 @@ GPU_SHADER_INTERFACE_INFO(smooth_radii_outline_iface, "").smooth(Type::VEC4, "ra
GPU_SHADER_INTERFACE_INFO(flat_color_smooth_tex_coord_interp_iface, "")
.flat(Type::VEC4, "finalColor")
.smooth(Type::VEC2, "texCoord_interp");
GPU_SHADER_INTERFACE_INFO(smooth_icon_interp_iface, "")
.smooth(Type::VEC2, "texCoord_interp")
.smooth(Type::VEC2, "mask_coord_interp");

View File

@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#include "gpu_interface_info.hh"
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(gpu_shader_icon)
.vertex_out(smooth_icon_interp_iface)
.fragment_out(0, Type::VEC4, "fragColor")
.push_constant(Type::MAT4, "ModelViewProjectionMatrix")
.push_constant(Type::VEC4, "color")
.push_constant(Type::VEC4, "rect_icon")
.push_constant(Type::VEC4, "rect_geom")
.push_constant(Type::FLOAT, "text_width")
.sampler(0, ImageType::FLOAT_2D, "image")
.vertex_source("gpu_shader_icon_vert.glsl")
.fragment_source("gpu_shader_icon_frag.glsl")
.do_static_compilation(true);

View File

@@ -853,7 +853,8 @@ static void wm_drag_draw_icon(bContext * /*C*/, wmWindow * /*win*/, wmDrag *drag
y = xy[1] - 2 * UI_DPI_FAC;
const uchar text_col[] = {255, 255, 255, 255};
UI_icon_draw_ex(x, y, drag->icon, U.inv_dpi_fac, 0.8, 0.0f, text_col, false);
UI_icon_draw_ex(
x, y, drag->icon, U.inv_dpi_fac, 0.8, 0.0f, text_col, false, UI_NO_ICON_OVERLAY_TEXT);
}
}