The previous limit of 63 bytes for the panel name was an issue for languages where characters use multiple bytes. The UI would show panel names truncated that had reasonably long/short names. It's easy to support dynamically sized strings here, so do that instead of using fixed size buffers. Fixes #111927. Pull Request: https://projects.blender.org/blender/blender/pulls/111979
402 lines
11 KiB
C++
402 lines
11 KiB
C++
/* SPDX-FileCopyrightText: 2008 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup edinterface
|
|
*
|
|
* Floating Persistent Region
|
|
*/
|
|
|
|
#include <cstring>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "DNA_screen_types.h"
|
|
#include "DNA_userdef_types.h"
|
|
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_rect.h"
|
|
#include "BLI_string.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "BKE_context.h"
|
|
#include "BKE_screen.h"
|
|
|
|
#include "WM_api.hh"
|
|
#include "WM_types.hh"
|
|
|
|
#include "RNA_access.hh"
|
|
|
|
#include "UI_interface.hh"
|
|
#include "UI_view2d.hh"
|
|
|
|
#include "BLT_translation.h"
|
|
|
|
#include "ED_screen.hh"
|
|
#include "ED_undo.hh"
|
|
|
|
#include "GPU_framebuffer.h"
|
|
#include "interface_intern.hh"
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Utilities
|
|
* \{ */
|
|
|
|
struct HudRegionData {
|
|
short regionid;
|
|
};
|
|
|
|
static bool last_redo_poll(const bContext *C, short region_type)
|
|
{
|
|
wmOperator *op = WM_operator_last_redo(C);
|
|
if (op == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
bool success = false;
|
|
{
|
|
/* Make sure that we are using the same region type as the original
|
|
* operator call. Otherwise we would be polling the operator with the
|
|
* wrong context.
|
|
*/
|
|
ScrArea *area = CTX_wm_area(C);
|
|
ARegion *region_op = (region_type != -1) ? BKE_area_find_region_type(area, region_type) :
|
|
nullptr;
|
|
ARegion *region_prev = CTX_wm_region(C);
|
|
CTX_wm_region_set((bContext *)C, region_op);
|
|
|
|
if (WM_operator_repeat_check(C, op) && WM_operator_ui_poll(op->type, op->ptr)) {
|
|
success = WM_operator_poll((bContext *)C, op->type);
|
|
}
|
|
CTX_wm_region_set((bContext *)C, region_prev);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
static void hud_region_hide(ARegion *region)
|
|
{
|
|
region->flag |= RGN_FLAG_HIDDEN;
|
|
/* Avoids setting 'AREA_FLAG_REGION_SIZE_UPDATE'
|
|
* since other regions don't depend on this. */
|
|
BLI_rcti_init(®ion->winrct, 0, 0, 0, 0);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Redo Panel
|
|
* \{ */
|
|
|
|
static bool hud_panel_operator_redo_poll(const bContext *C, PanelType * /*pt*/)
|
|
{
|
|
ScrArea *area = CTX_wm_area(C);
|
|
ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_HUD);
|
|
if (region != nullptr) {
|
|
HudRegionData *hrd = static_cast<HudRegionData *>(region->regiondata);
|
|
if (hrd != nullptr) {
|
|
return last_redo_poll(C, hrd->regionid);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void hud_panel_operator_redo_draw_header(const bContext *C, Panel *panel)
|
|
{
|
|
wmOperator *op = WM_operator_last_redo(C);
|
|
const std::string opname = WM_operatortype_name(op->type, op->ptr);
|
|
UI_panel_drawname_set(panel, opname);
|
|
}
|
|
|
|
static void hud_panel_operator_redo_draw(const bContext *C, Panel *panel)
|
|
{
|
|
wmOperator *op = WM_operator_last_redo(C);
|
|
if (op == nullptr) {
|
|
return;
|
|
}
|
|
if (!WM_operator_check_ui_enabled(C, op->type->name)) {
|
|
uiLayoutSetEnabled(panel->layout, false);
|
|
}
|
|
uiLayout *col = uiLayoutColumn(panel->layout, false);
|
|
uiTemplateOperatorRedoProperties(col, C);
|
|
}
|
|
|
|
static void hud_panels_register(ARegionType *art, int space_type, int region_type)
|
|
{
|
|
PanelType *pt = MEM_cnew<PanelType>(__func__);
|
|
STRNCPY(pt->idname, "OPERATOR_PT_redo");
|
|
STRNCPY(pt->label, N_("Redo"));
|
|
STRNCPY(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
|
|
pt->draw_header = hud_panel_operator_redo_draw_header;
|
|
pt->draw = hud_panel_operator_redo_draw;
|
|
pt->poll = hud_panel_operator_redo_poll;
|
|
pt->space_type = space_type;
|
|
pt->region_type = region_type;
|
|
pt->flag |= PANEL_TYPE_DEFAULT_CLOSED;
|
|
BLI_addtail(&art->paneltypes, pt);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Callbacks for Floating Region
|
|
* \{ */
|
|
|
|
static void hud_region_init(wmWindowManager *wm, ARegion *region)
|
|
{
|
|
ED_region_panels_init(wm, region);
|
|
|
|
/* Reset zoom from panels init because we don't want zoom allowed for redo panel. */
|
|
region->v2d.maxzoom = 1.0f;
|
|
region->v2d.minzoom = 1.0f;
|
|
|
|
UI_region_handlers_add(®ion->handlers);
|
|
region->flag |= RGN_FLAG_TEMP_REGIONDATA;
|
|
}
|
|
|
|
static void hud_region_free(ARegion *region)
|
|
{
|
|
MEM_SAFE_FREE(region->regiondata);
|
|
}
|
|
|
|
static void hud_region_layout(const bContext *C, ARegion *region)
|
|
{
|
|
HudRegionData *hrd = static_cast<HudRegionData *>(region->regiondata);
|
|
if (hrd == nullptr || !last_redo_poll(C, hrd->regionid)) {
|
|
ED_region_tag_redraw(region);
|
|
hud_region_hide(region);
|
|
return;
|
|
}
|
|
|
|
ScrArea *area = CTX_wm_area(C);
|
|
const int size_y = region->sizey;
|
|
|
|
ED_region_panels_layout(C, region);
|
|
|
|
if (region->panels.first &&
|
|
((area->flag & AREA_FLAG_REGION_SIZE_UPDATE) || (region->sizey != size_y)))
|
|
{
|
|
int winx_new = UI_SCALE_FAC * (region->sizex + 0.5f);
|
|
int winy_new = UI_SCALE_FAC * (region->sizey + 0.5f);
|
|
View2D *v2d = ®ion->v2d;
|
|
|
|
if (region->flag & RGN_FLAG_SIZE_CLAMP_X) {
|
|
CLAMP_MAX(winx_new, region->winx);
|
|
}
|
|
if (region->flag & RGN_FLAG_SIZE_CLAMP_Y) {
|
|
CLAMP_MAX(winy_new, region->winy);
|
|
}
|
|
|
|
region->winx = winx_new;
|
|
region->winy = winy_new;
|
|
|
|
region->winrct.xmax = (region->winrct.xmin + region->winx) - 1;
|
|
region->winrct.ymax = (region->winrct.ymin + region->winy) - 1;
|
|
|
|
UI_view2d_region_reinit(v2d, V2D_COMMONVIEW_LIST, region->winx, region->winy);
|
|
|
|
/* Weak, but needed to avoid glitches, especially with hi-dpi
|
|
* (where resizing the view glitches often).
|
|
* Fortunately this only happens occasionally. */
|
|
ED_region_panels_layout(C, region);
|
|
}
|
|
|
|
/* restore view matrix */
|
|
UI_view2d_view_restore(C);
|
|
}
|
|
|
|
static void hud_region_draw(const bContext *C, ARegion *region)
|
|
{
|
|
UI_view2d_view_ortho(®ion->v2d);
|
|
wmOrtho2_region_pixelspace(region);
|
|
GPU_clear_color(0.0f, 0.0f, 0.0f, 0.0f);
|
|
|
|
if ((region->flag & RGN_FLAG_HIDDEN) == 0) {
|
|
rcti reset_rect = {};
|
|
reset_rect.xmax = region->winx;
|
|
reset_rect.ymax = region->winy;
|
|
ui_draw_menu_back(nullptr, nullptr, &reset_rect);
|
|
ED_region_panels_draw(C, region);
|
|
}
|
|
}
|
|
|
|
ARegionType *ED_area_type_hud(int space_type)
|
|
{
|
|
ARegionType *art = MEM_cnew<ARegionType>(__func__);
|
|
art->regionid = RGN_TYPE_HUD;
|
|
art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_VIEW2D;
|
|
art->layout = hud_region_layout;
|
|
art->draw = hud_region_draw;
|
|
art->init = hud_region_init;
|
|
art->free = hud_region_free;
|
|
|
|
/* We need to indicate a preferred size to avoid false `RGN_FLAG_TOO_SMALL`
|
|
* the first time the region is created. */
|
|
art->prefsizex = AREAMINX;
|
|
art->prefsizey = HEADERY;
|
|
|
|
hud_panels_register(art, space_type, art->regionid);
|
|
|
|
art->lock = 1; /* can become flag, see BKE_spacedata_draw_locks */
|
|
return art;
|
|
}
|
|
|
|
static ARegion *hud_region_add(ScrArea *area)
|
|
{
|
|
ARegion *region = MEM_cnew<ARegion>(__func__);
|
|
ARegion *region_win = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
|
|
if (region_win) {
|
|
BLI_insertlinkbefore(&area->regionbase, region_win, region);
|
|
}
|
|
else {
|
|
BLI_addtail(&area->regionbase, region);
|
|
}
|
|
region->regiontype = RGN_TYPE_HUD;
|
|
region->alignment = RGN_ALIGN_FLOAT;
|
|
region->overlap = true;
|
|
region->flag |= RGN_FLAG_DYNAMIC_SIZE;
|
|
|
|
if (region_win) {
|
|
float x, y;
|
|
|
|
UI_view2d_scroller_size_get(®ion_win->v2d, true, &x, &y);
|
|
region->runtime.offset_x = x;
|
|
region->runtime.offset_y = y;
|
|
}
|
|
|
|
return region;
|
|
}
|
|
|
|
void ED_area_type_hud_clear(wmWindowManager *wm, ScrArea *area_keep)
|
|
{
|
|
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
|
bScreen *screen = WM_window_get_active_screen(win);
|
|
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
|
|
if (area != area_keep) {
|
|
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
|
|
if (region->regiontype == RGN_TYPE_HUD) {
|
|
if ((region->flag & RGN_FLAG_HIDDEN) == 0) {
|
|
hud_region_hide(region);
|
|
ED_region_tag_redraw(region);
|
|
ED_area_tag_redraw(area);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ED_area_type_hud_ensure(bContext *C, ScrArea *area)
|
|
{
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
ED_area_type_hud_clear(wm, area);
|
|
|
|
ARegionType *art = BKE_regiontype_from_id(area->type, RGN_TYPE_HUD);
|
|
if (art == nullptr) {
|
|
return;
|
|
}
|
|
|
|
ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_HUD);
|
|
|
|
if (region && (region->flag & RGN_FLAG_HIDDEN_BY_USER)) {
|
|
/* The region is intentionally hidden by the user, don't show it. */
|
|
hud_region_hide(region);
|
|
return;
|
|
}
|
|
|
|
bool init = false;
|
|
const bool was_hidden = region == nullptr || region->visible == false;
|
|
ARegion *region_op = CTX_wm_region(C);
|
|
BLI_assert((region_op == nullptr) || (region_op->regiontype != RGN_TYPE_HUD));
|
|
if (!last_redo_poll(C, region_op ? region_op->regiontype : -1)) {
|
|
if (region) {
|
|
ED_region_tag_redraw(region);
|
|
hud_region_hide(region);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (region == nullptr) {
|
|
init = true;
|
|
region = hud_region_add(area);
|
|
region->type = art;
|
|
}
|
|
|
|
/* Let 'ED_area_update_region_sizes' do the work of placing the region.
|
|
* Otherwise we could set the 'region->winrct' & 'region->winx/winy' here. */
|
|
if (init) {
|
|
ED_area_tag_region_size_update(area, region);
|
|
}
|
|
else {
|
|
if (region->flag & RGN_FLAG_HIDDEN) {
|
|
/* Also forces recalculating HUD size in hud_region_layout(). */
|
|
ED_area_tag_region_size_update(area, region);
|
|
}
|
|
region->flag &= ~RGN_FLAG_HIDDEN;
|
|
}
|
|
|
|
{
|
|
HudRegionData *hrd = static_cast<HudRegionData *>(region->regiondata);
|
|
if (hrd == nullptr) {
|
|
hrd = MEM_cnew<HudRegionData>(__func__);
|
|
region->regiondata = hrd;
|
|
}
|
|
if (region_op) {
|
|
hrd->regionid = region_op->regiontype;
|
|
}
|
|
else {
|
|
hrd->regionid = -1;
|
|
}
|
|
}
|
|
|
|
if (init) {
|
|
/* This is needed or 'winrct' will be invalid. */
|
|
wmWindow *win = CTX_wm_window(C);
|
|
ED_area_update_region_sizes(wm, win, area);
|
|
}
|
|
|
|
ED_region_floating_init(region);
|
|
ED_region_tag_redraw(region);
|
|
|
|
/* Reset zoom level (not well supported). */
|
|
rctf reset_rect = {};
|
|
reset_rect.xmax = region->winx;
|
|
reset_rect.ymax = region->winy;
|
|
region->v2d.cur = region->v2d.tot = reset_rect;
|
|
region->v2d.minzoom = 1.0f;
|
|
region->v2d.maxzoom = 1.0f;
|
|
|
|
region->visible = !(region->flag & RGN_FLAG_HIDDEN);
|
|
|
|
/* We shouldn't need to do this every time :S */
|
|
/* XXX, this is evil! - it also makes the menu show on first draw. :( */
|
|
if (region->visible) {
|
|
ARegion *region_prev = CTX_wm_region(C);
|
|
CTX_wm_region_set((bContext *)C, region);
|
|
hud_region_layout(C, region);
|
|
if (was_hidden) {
|
|
region->winx = region->v2d.winx;
|
|
region->winy = region->v2d.winy;
|
|
region->v2d.cur = region->v2d.tot = reset_rect;
|
|
}
|
|
CTX_wm_region_set((bContext *)C, region_prev);
|
|
}
|
|
|
|
region->visible = !((region->flag & RGN_FLAG_HIDDEN) || (region->flag & RGN_FLAG_TOO_SMALL));
|
|
}
|
|
|
|
ARegion *ED_area_type_hud_redo_region_find(const ScrArea *area, const ARegion *hud_region)
|
|
{
|
|
BLI_assert(hud_region->regiontype == RGN_TYPE_HUD);
|
|
HudRegionData *hrd = static_cast<HudRegionData *>(hud_region->regiondata);
|
|
|
|
if (hrd->regionid == -1) {
|
|
return nullptr;
|
|
}
|
|
|
|
return BKE_area_find_region_type(area, hrd->regionid);
|
|
}
|
|
|
|
/** \} */
|