Files
test/source/blender/editors/interface/templates/interface_template_status.cc
Guillermo Venegas 3b1e123361 Refactor: UI: Replace uiItemS and uiItemS_ex with uiLayout::separator
This merges the public `uiItemS` and `uiItemS_ex` functions into an
object oriented API (`uiLayout::separator`), matching the python API.
This reduces the difference between the C++ API with the python version,
its also helps while converting code from python to C++ code
(or vice-versa), making it almost seamless.

Part of: #117604

Pull Request: https://projects.blender.org/blender/blender/pulls/138826
2025-05-13 17:54:26 +02:00

607 lines
18 KiB
C++

/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edinterface
*/
#include <fmt/format.h>
#include "BKE_blender_version.h"
#include "BKE_context.hh"
#include "BKE_global.hh"
#include "BKE_layer.hh"
#include "BKE_main.hh"
#include "BKE_report.hh"
#include "BKE_screen.hh"
#include "BKE_workspace.hh"
#include "BLI_listbase.h"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.h"
#include "BLI_rect.h"
#include "BLI_string.h"
#include "BLF_api.hh"
#include "BLT_translation.hh"
#include "DNA_space_types.h"
#include "DNA_workspace_types.h"
#include "ED_info.hh"
#include "ED_screen_types.hh"
#include "WM_api.hh"
#include "UI_interface.hh"
#include "interface_intern.hh"
/* Maximum width for a Status Bar report */
#define REPORT_BANNER_MAX_WIDTH (800.0f * UI_SCALE_FAC)
void uiTemplateReportsBanner(uiLayout *layout, bContext *C)
{
ReportList *reports = CTX_wm_reports(C);
Report *report = BKE_reports_last_displayable(reports);
const uiStyle *style = UI_style_get();
uiBut *but;
/* if the report display has timed out, don't show */
if (!reports->reporttimer) {
return;
}
ReportTimerInfo *rti = (ReportTimerInfo *)reports->reporttimer->customdata;
if (!rti || rti->widthfac == 0.0f || !report) {
return;
}
uiLayout *ui_abs = &layout->absolute(false);
uiBlock *block = uiLayoutGetBlock(ui_abs);
blender::ui::EmbossType previous_emboss = UI_block_emboss_get(block);
uchar report_icon_color[4];
uchar report_text_color[4];
UI_GetThemeColorType4ubv(
UI_icon_colorid_from_report_type(report->type), SPACE_INFO, report_icon_color);
UI_GetThemeColorType4ubv(
UI_text_colorid_from_report_type(report->type), SPACE_INFO, report_text_color);
report_text_color[3] = 255; /* This theme color is RGB only, so have to set alpha here. */
if (rti->flash_progress <= 1.0) {
/* Flash report briefly according to progress through fade-out duration. */
const int brighten_amount = int(32 * (1.0f - rti->flash_progress));
add_v3_uchar_clamped(report_icon_color, brighten_amount);
}
UI_fontstyle_set(&style->widget);
int width = BLF_width(style->widget.uifont_id, report->message, report->len);
width = min_ii(width, int(REPORT_BANNER_MAX_WIDTH));
width = min_ii(int(rti->widthfac * width), width);
width = max_ii(width, 10 * UI_SCALE_FAC);
UI_block_align_begin(block);
/* Background for icon. */
but = uiDefBut(block,
UI_BTYPE_ROUNDBOX,
0,
"",
0,
0,
UI_UNIT_X + (6 * UI_SCALE_FAC),
UI_UNIT_Y,
nullptr,
0.0f,
0.0f,
"");
/* #UI_BTYPE_ROUNDBOX's background color is set in `but->col`. */
copy_v4_v4_uchar(but->col, report_icon_color);
/* Background for the rest of the message. */
but = uiDefBut(block,
UI_BTYPE_ROUNDBOX,
0,
"",
UI_UNIT_X + (6 * UI_SCALE_FAC),
0,
UI_UNIT_X + width,
UI_UNIT_Y,
nullptr,
0.0f,
0.0f,
"");
/* Use icon background at low opacity to highlight, but still contrasting with area TH_TEXT. */
copy_v3_v3_uchar(but->col, report_icon_color);
but->col[3] = 64;
UI_block_align_end(block);
UI_block_emboss_set(block, blender::ui::EmbossType::None);
/* The report icon itself. */
but = uiDefIconButO(block,
UI_BTYPE_BUT,
"SCREEN_OT_info_log_show",
WM_OP_INVOKE_REGION_WIN,
UI_icon_from_report_type(report->type),
(3 * UI_SCALE_FAC),
0,
UI_UNIT_X,
UI_UNIT_Y,
TIP_("Click to open the info editor"));
copy_v4_v4_uchar(but->col, report_text_color);
/* The report message. */
but = uiDefButO(block,
UI_BTYPE_BUT,
"SCREEN_OT_info_log_show",
WM_OP_INVOKE_REGION_WIN,
report->message,
UI_UNIT_X,
0,
width + UI_UNIT_X,
UI_UNIT_Y,
TIP_("Show in Info Log"));
UI_block_emboss_set(block, previous_emboss);
}
static bool uiTemplateInputStatusAzone(uiLayout *layout, const AZone *az, const ARegion *region)
{
if (az->type == AZONE_AREA) {
layout->label(nullptr, ICON_MOUSE_LMB_DRAG);
layout->separator(-0.2f);
layout->label(IFACE_("Split/Dock"), ICON_NONE);
layout->separator(0.6f);
layout->label("", ICON_EVENT_SHIFT);
layout->separator(-0.4f);
layout->label(nullptr, ICON_MOUSE_LMB_DRAG);
layout->separator(-0.2f);
layout->label(IFACE_("Duplicate into Window"), ICON_NONE);
layout->separator(0.6f);
layout->label("", ICON_EVENT_CTRL);
layout->separator(ui_event_icon_offset(ICON_EVENT_CTRL));
layout->label(nullptr, ICON_MOUSE_LMB_DRAG);
layout->separator(-0.2f);
layout->label(IFACE_("Swap Areas"), ICON_NONE);
return true;
}
if (az->type == AZONE_REGION) {
layout->label(nullptr, ICON_MOUSE_LMB_DRAG);
layout->separator(-0.2f);
layout->label((region->runtime->visible) ? IFACE_("Resize Region") :
IFACE_("Show Hidden Region"),
ICON_NONE);
return true;
}
return false;
}
static bool uiTemplateInputStatusBorder(wmWindow *win, uiLayout *row)
{
/* On a gap between editors. */
rcti win_rect;
const int pad = int((3.0f * UI_SCALE_FAC) + U.pixelsize);
WM_window_screen_rect_calc(win, &win_rect);
BLI_rcti_pad(&win_rect, pad * -2, pad);
if (BLI_rcti_isect_pt_v(&win_rect, win->eventstate->xy)) {
/* Show options but not along left and right edges. */
BLI_rcti_pad(&win_rect, 0, pad * -3);
if (BLI_rcti_isect_pt_v(&win_rect, win->eventstate->xy)) {
/* No resize at top and bottom. */
row->label(nullptr, ICON_MOUSE_LMB_DRAG);
row->separator(-0.2f);
row->label(IFACE_("Resize"), ICON_NONE);
row->separator(0.6f);
}
row->label(nullptr, ICON_MOUSE_RMB);
row->separator(-0.9f);
row->label(IFACE_("Options"), ICON_NONE);
return true;
}
return false;
}
static bool uiTemplateInputStatusHeader(ARegion *region, uiLayout *row)
{
if (region->regiontype != RGN_TYPE_HEADER) {
return false;
}
/* Over a header region. */
row->label(nullptr, ICON_MOUSE_MMB_DRAG);
row->separator(-0.2f);
row->label(IFACE_("Pan"), ICON_NONE);
row->separator(0.6f);
row->label(nullptr, ICON_MOUSE_RMB);
row->separator(-0.9f);
row->label(IFACE_("Options"), ICON_NONE);
return true;
}
static bool uiTemplateInputStatus3DView(bContext *C, uiLayout *row)
{
const Object *ob = CTX_data_active_object(C);
if (!ob) {
return false;
}
if (is_negative_m4(ob->object_to_world().ptr())) {
row->separator(1.0f);
row->label("", ICON_ERROR);
row->separator(-0.2f);
row->label(IFACE_("Active object has negative scale"), ICON_NONE);
row->separator(0.5f, LayoutSeparatorType::Line);
row->separator(0.5f);
/* Return false to allow other items to be added after. */
return false;
}
if (!(fabsf(ob->scale[0] - ob->scale[1]) < 1e-4f && fabsf(ob->scale[1] - ob->scale[2]) < 1e-4f))
{
row->separator(1.0f);
row->label("", ICON_ERROR);
row->separator(-0.2f);
row->label(IFACE_("Active object has non-uniform scale"), ICON_NONE);
row->separator(0.5f, LayoutSeparatorType::Line);
row->separator(0.5f);
/* Return false to allow other items to be added after. */
return false;
}
return false;
}
void uiTemplateInputStatus(uiLayout *layout, bContext *C)
{
wmWindow *win = CTX_wm_window(C);
WorkSpace *workspace = CTX_wm_workspace(C);
/* Workspace status text has priority. */
if (!workspace->runtime->status.is_empty()) {
uiLayout *row = &layout->row(true);
for (const blender::bke::WorkSpaceStatusItem &item : workspace->runtime->status) {
if (item.space_factor != 0.0f) {
row->separator(item.space_factor);
}
else {
uiBut *but = uiItemL_ex(row, item.text, item.icon, false, false);
if (item.inverted) {
but->drawflag |= UI_BUT_ICON_INVERT;
}
const float offset = ui_event_icon_offset(item.icon);
if (offset != 0.0f) {
row->separator(offset);
}
}
}
return;
}
if (WM_window_modal_keymap_status_draw(C, win, layout)) {
return;
}
bScreen *screen = CTX_wm_screen(C);
ARegion *region = screen->active_region;
uiLayout *row = &layout->row(true);
if (region == nullptr) {
/* Check if over an action zone. */
LISTBASE_FOREACH (ScrArea *, area_iter, &screen->areabase) {
LISTBASE_FOREACH (AZone *, az, &area_iter->actionzones) {
if (BLI_rcti_isect_pt_v(&az->rect, win->eventstate->xy)) {
region = az->region;
if (uiTemplateInputStatusAzone(row, az, region)) {
return;
}
break;
}
}
}
}
ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, win->eventstate->xy);
if (!area) {
/* Are we in a global area? */
LISTBASE_FOREACH (ScrArea *, global_area, &win->global_areas.areabase) {
if (BLI_rcti_isect_pt_v(&global_area->totrct, win->eventstate->xy)) {
area = global_area;
break;
}
}
}
if (!area) {
/* Outside of all areas. */
return;
}
if (!region && win && uiTemplateInputStatusBorder(win, row)) {
/* On a gap between editors. */
return;
}
if (region && uiTemplateInputStatusHeader(region, row)) {
/* Over a header region. */
return;
}
if (area && area->spacetype == SPACE_VIEW3D && uiTemplateInputStatus3DView(C, row)) {
/* Specific to 3DView. */
return;
}
if (!area || !region) {
/* Keymap status only if over a region in an area. */
return;
}
/* Otherwise should cursor keymap status. */
for (int i = 0; i < 3; i++) {
uiLayoutSetAlignment(row, UI_LAYOUT_ALIGN_LEFT);
const char *msg = CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT,
WM_window_cursor_keymap_status_get(win, i, 0));
const char *msg_drag = CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT,
WM_window_cursor_keymap_status_get(win, i, 1));
if (msg) {
row->label("", (ICON_MOUSE_LMB + i));
row->separator(-0.9f);
row->label(msg, ICON_NONE);
row->separator(0.6f);
}
if (msg_drag) {
row->label("", (ICON_MOUSE_LMB_DRAG + i));
row->separator(-0.4f);
row->label(msg_drag, ICON_NONE);
row->separator(0.6f);
}
}
}
static std::string ui_template_status_tooltip(bContext *C,
void * /*argN*/,
const blender::StringRef /*tip*/)
{
Main *bmain = CTX_data_main(C);
std::string tooltip_message;
if (bmain->has_forward_compatibility_issues) {
char writer_ver_str[12];
BKE_blender_version_blendfile_string_from_values(
writer_ver_str, sizeof(writer_ver_str), bmain->versionfile, -1);
tooltip_message += fmt::format(
fmt::runtime(RPT_("File saved by newer Blender\n({}), expect loss of data")),
writer_ver_str);
}
if (bmain->is_asset_edit_file) {
if (!tooltip_message.empty()) {
tooltip_message += "\n\n";
}
tooltip_message += RPT_(
"This file is managed by the Blender asset system and cannot be overridden");
}
return tooltip_message;
}
void uiTemplateStatusInfo(uiLayout *layout, bContext *C)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
uiLayout *row = &layout->row(true);
const char *status_info_txt = ED_info_statusbar_string_ex(
bmain, scene, view_layer, (U.statusbar_flag & ~STATUSBAR_SHOW_VERSION));
/* True when the status is populated (delimiters required for following items). */
bool has_status_info = false;
if (status_info_txt[0]) {
row->label(status_info_txt, ICON_NONE);
has_status_info = true;
}
if (U.statusbar_flag & STATUSBAR_SHOW_EXTENSIONS_UPDATES) {
wmWindowManager *wm = CTX_wm_manager(C);
/* Special case, always show an alert for any blocked extensions. */
if (wm->extensions_blocked > 0) {
if (has_status_info) {
row->separator(-0.5f);
row->label("|", ICON_NONE);
row->separator(-0.5f);
}
uiLayoutSetEmboss(row, blender::ui::EmbossType::None);
/* This operator also works fine for blocked extensions. */
row->op("EXTENSIONS_OT_userpref_show_for_update", "", ICON_ERROR);
uiBut *but = uiLayoutGetBlock(layout)->buttons.last().get();
uchar color[4];
UI_GetThemeColor4ubv(TH_TEXT, color);
copy_v4_v4_uchar(but->col, color);
BLI_str_format_integer_unit(but->icon_overlay_text.text, wm->extensions_blocked);
UI_but_icon_indicator_color_set(but, color);
row->separator(1.0f);
has_status_info = true;
}
if ((G.f & G_FLAG_INTERNET_ALLOW) == 0) {
if (has_status_info) {
row->separator(-0.5f);
row->label("|", ICON_NONE);
row->separator(-0.5f);
}
if ((G.f & G_FLAG_INTERNET_OVERRIDE_PREF_OFFLINE) != 0) {
row->label("", ICON_INTERNET_OFFLINE);
}
else {
uiLayoutSetEmboss(row, blender::ui::EmbossType::None);
row->op("EXTENSIONS_OT_userpref_show_online", "", ICON_INTERNET_OFFLINE);
uiBut *but = uiLayoutGetBlock(layout)->buttons.last().get();
uchar color[4];
UI_GetThemeColor4ubv(TH_TEXT, color);
copy_v4_v4_uchar(but->col, color);
}
row->separator(1.0f);
has_status_info = true;
}
else if ((wm->extensions_updates > 0) ||
(wm->extensions_updates == WM_EXTENSIONS_UPDATE_CHECKING))
{
int icon = ICON_INTERNET;
if (wm->extensions_updates == WM_EXTENSIONS_UPDATE_CHECKING) {
icon = ICON_UV_SYNC_SELECT;
}
if (has_status_info) {
row->separator(-0.5f);
row->label("|", ICON_NONE);
row->separator(-0.5f);
}
uiLayoutSetEmboss(row, blender::ui::EmbossType::None);
row->op("EXTENSIONS_OT_userpref_show_for_update", "", icon);
uiBut *but = uiLayoutGetBlock(layout)->buttons.last().get();
uchar color[4];
UI_GetThemeColor4ubv(TH_TEXT, color);
copy_v4_v4_uchar(but->col, color);
if (wm->extensions_updates > 0) {
BLI_str_format_integer_unit(but->icon_overlay_text.text, wm->extensions_updates);
UI_but_icon_indicator_color_set(but, color);
}
row->separator(1.0f);
has_status_info = true;
}
}
if (!BKE_main_has_issues(bmain)) {
if (U.statusbar_flag & STATUSBAR_SHOW_VERSION) {
if (has_status_info) {
row->separator(-0.5f);
row->label("|", ICON_NONE);
row->separator(-0.5f);
}
const char *status_info_d_txt = ED_info_statusbar_string_ex(
bmain, scene, view_layer, STATUSBAR_SHOW_VERSION);
row->label(status_info_d_txt, ICON_NONE);
}
return;
}
blender::StringRefNull version_string = ED_info_statusbar_string_ex(
bmain, scene, view_layer, STATUSBAR_SHOW_VERSION);
blender::StringRefNull warning_message;
/* Blender version part is shown as warning area when there are forward compatibility issues with
* currently loaded .blend file. */
if (bmain->has_forward_compatibility_issues) {
warning_message = version_string;
}
else {
/* For other issues, still show the version if enabled. */
if (U.statusbar_flag & STATUSBAR_SHOW_VERSION) {
layout->label(version_string, ICON_NONE);
}
}
const uiStyle *style = UI_style_get();
uiLayout *ui_abs = &layout->absolute(false);
uiBlock *block = uiLayoutGetBlock(ui_abs);
blender::ui::EmbossType previous_emboss = UI_block_emboss_get(block);
UI_fontstyle_set(&style->widget);
const int width = max_ii(
int(BLF_width(style->widget.uifont_id, warning_message.c_str(), warning_message.size())),
int(10 * UI_SCALE_FAC));
UI_block_align_begin(block);
/* Background for icon. */
uiBut *but = uiDefBut(block,
UI_BTYPE_ROUNDBOX,
0,
"",
0,
0,
UI_UNIT_X + (6 * UI_SCALE_FAC),
UI_UNIT_Y,
nullptr,
0.0f,
0.0f,
"");
/*# UI_BTYPE_ROUNDBOX's background color is set in `but->col`. */
UI_GetThemeColor4ubv(TH_WARNING, but->col);
if (!warning_message.is_empty()) {
/* Background for the rest of the message. */
but = uiDefBut(block,
UI_BTYPE_ROUNDBOX,
0,
"",
UI_UNIT_X + (6 * UI_SCALE_FAC),
0,
UI_UNIT_X + width,
UI_UNIT_Y,
nullptr,
0.0f,
0.0f,
"");
/* Use icon background at low opacity to highlight, but still contrasting with area TH_TEXT. */
UI_GetThemeColor4ubv(TH_WARNING, but->col);
but->col[3] = 64;
}
UI_block_align_end(block);
UI_block_emboss_set(block, blender::ui::EmbossType::None);
/* The warning icon itself. */
but = uiDefIconBut(block,
UI_BTYPE_BUT,
0,
ICON_ERROR,
int(3 * UI_SCALE_FAC),
0,
UI_UNIT_X,
UI_UNIT_Y,
nullptr,
0.0f,
0.0f,
std::nullopt);
UI_but_func_tooltip_set(but, ui_template_status_tooltip, nullptr, nullptr);
UI_GetThemeColorType4ubv(TH_INFO_WARNING_TEXT, SPACE_INFO, but->col);
but->col[3] = 255; /* This theme color is RBG only, so have to set alpha here. */
/* The warning message, if any. */
if (!warning_message.is_empty()) {
but = uiDefBut(block,
UI_BTYPE_BUT,
0,
warning_message.c_str(),
UI_UNIT_X,
0,
short(width + UI_UNIT_X),
UI_UNIT_Y,
nullptr,
0.0f,
0.0f,
std::nullopt);
UI_but_func_tooltip_set(but, ui_template_status_tooltip, nullptr, nullptr);
}
UI_block_emboss_set(block, previous_emboss);
}