UI: Optional Complex Layout for Workspace Status #120595

Merged
Harley Acheson merged 51 commits from Harley/blender:WorkspaceStatus into main 2024-05-06 23:52:47 +02:00
13 changed files with 248 additions and 40 deletions

View File

@ -17,6 +17,21 @@ struct WorkSpace;
struct WorkSpaceInstanceHook;
struct WorkSpaceLayout;
struct WorkSpaceStatusItem {
int icon = 0;
std::string text = {};
float space_factor = 0.0f;
bool inverted = false;
};
namespace blender::bke {
struct WorkSpaceRuntime {
blender::Vector<WorkSpaceStatusItem> status;
};
Harley marked this conversation as resolved

Don't use pointers, use blender::Vector<WorkSpaceStatusItem>.

Don't do unnecessary = {}, that's automatic.

Don't use pointers, use `blender::Vector<WorkSpaceStatusItem>`. Don't do unnecessary `= {}`, that's automatic.
} // namespace blender::bke
/* -------------------------------------------------------------------- */
/** \name Create, Delete, Initialize
* \{ */
@ -153,6 +168,11 @@ bool BKE_workspace_owner_id_check(const WorkSpace *workspace, const char *owner_
void BKE_workspace_id_tag_all_visible(Main *bmain, int tag) ATTR_NONNULL();
/**
* Empty the Workspace status items to clear the status bar.
*/
void BKE_workspace_status_clear(struct WorkSpace *workspace);
#undef GETTER_ATTRS
#undef SETTER_ATTRS

View File

@ -42,6 +42,8 @@ static void workspace_init_data(ID *id)
{
WorkSpace *workspace = (WorkSpace *)id;
workspace->runtime = MEM_new<blender::bke::WorkSpaceRuntime>(__func__);
BKE_asset_library_reference_init_default(&workspace->asset_library_ref);
}
@ -58,7 +60,9 @@ static void workspace_free_data(ID *id)
BKE_workspace_tool_remove(workspace, static_cast<bToolRef *>(workspace->tools.first));
}
MEM_SAFE_FREE(workspace->status_text);
BKE_workspace_status_clear(workspace);
MEM_delete(workspace->runtime);
BKE_viewer_path_clear(&workspace->viewer_path);
}
@ -115,7 +119,7 @@ static void workspace_blend_read_data(BlendDataReader *reader, ID *id)
IDP_BlendDataRead(reader, &tref->properties);
}
workspace->status_text = nullptr;
workspace->runtime = MEM_new<blender::bke::WorkSpaceRuntime>(__func__);
/* Do not keep the scene reference when appending a workspace. Setting a scene for a workspace is
* a convenience feature, but the workspace should never truly depend on scene data. */
@ -633,3 +637,14 @@ bScreen *BKE_workspace_layout_screen_get(const WorkSpaceLayout *layout)
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Status
* \{ */
void BKE_workspace_status_clear(WorkSpace *workspace)
{
workspace->runtime->status.clear_and_shrink();
}
/** \} */

View File

@ -450,6 +450,19 @@ bool ED_workspace_layout_cycle(WorkSpace *workspace, short direction, bContext *
void ED_workspace_status_text(bContext *C, const char *str);
class WorkspaceStatus {
private:
WorkSpace *workspace_;
wmWindowManager *wm_;
public:
WorkspaceStatus(bContext *C);
void item(const std::string text, int icon1, int icon2 = 0);
void item_bool(const std::string text, bool inverted, int icon1, int icon2 = 0);
void range(const std::string text, int icon1, int icon2);
void opmodal(const std::string text, wmOperatorType *ot, int propvalue, bool inverted = false);
};
void ED_workspace_do_listen(bContext *C, const wmNotifier *note);
/* anim */

View File

@ -362,6 +362,9 @@ enum {
/** Drawn in a way that indicates that the state/value is unknown. */
UI_BUT_INDETERMINATE = 1 << 26,
/** Draw icon inverted to indicate a special state. */
UI_BUT_ICON_INVERT = 1 << 27,
};
/**

View File

@ -111,7 +111,8 @@ void UI_icon_draw_ex(float x,
float desaturate,
const uchar mono_color[4],
bool mono_border,
const IconTextOverlay *text_overlay);
const IconTextOverlay *text_overlay,
const bool inverted = false);
/**
* Draw an monochrome icon into a given coordinate rectangle. The rectangle is used as-is,

View File

@ -4067,7 +4067,8 @@ static uiBut *ui_def_but(uiBlock *block,
float max,
const char *tip)
{
BLI_assert(width >= 0 && height >= 0);
/* Allow negative separators. */
BLI_assert(width >= 0 && height >= 0 || (type == UI_BTYPE_SEPR));
if (type & UI_BUT_POIN_TYPES) { /* a pointer is required */
if (poin == nullptr) {

View File

@ -651,13 +651,13 @@ int UI_icon_from_event_type(short event_type, short event_value)
} while ((di = di->data.input.next));
if (event_type == LEFTMOUSE) {
return ELEM(event_value, KM_CLICK, KM_PRESS) ? ICON_MOUSE_LMB : ICON_MOUSE_LMB_DRAG;
return (event_value == KM_CLICK_DRAG) ? ICON_MOUSE_LMB_DRAG : ICON_MOUSE_LMB;
}
if (event_type == MIDDLEMOUSE) {
return ELEM(event_value, KM_CLICK, KM_PRESS) ? ICON_MOUSE_MMB : ICON_MOUSE_MMB_DRAG;
return (event_value == KM_CLICK_DRAG) ? ICON_MOUSE_MMB_DRAG : ICON_MOUSE_MMB;
}
if (event_type == RIGHTMOUSE) {
return ELEM(event_value, KM_CLICK, KM_PRESS) ? ICON_MOUSE_RMB : ICON_MOUSE_RMB_DRAG;
return (event_value == KM_CLICK_DRAG) ? ICON_MOUSE_MMB_DRAG : ICON_MOUSE_RMB;
}
return ICON_NONE;
@ -1985,7 +1985,8 @@ static void icon_draw_size(float x,
const float desaturate,
const uchar mono_rgba[4],
const bool mono_border,
const IconTextOverlay *text_overlay)
const IconTextOverlay *text_overlay,
const bool inverted = false)
{
bTheme *btheme = UI_GetTheme();
const float fdraw_size = float(draw_size);
@ -2059,7 +2060,7 @@ static void icon_draw_size(float x,
else if (di->type == ICON_TYPE_EVENT) {
const short event_type = di->data.input.event_type;
const short event_value = di->data.input.event_value;
icon_draw_rect_input(x, y, w, h, alpha, event_type, event_value);
icon_draw_rect_input(x, y, w, h, alpha, event_type, event_value, inverted);
}
else if (di->type == ICON_TYPE_COLOR_TEXTURE) {
/* texture image use premul alpha for correct scaling */
@ -2676,7 +2677,8 @@ void UI_icon_draw_ex(float x,
float desaturate,
const uchar mono_color[4],
const bool mono_border,
const IconTextOverlay *text_overlay)
const IconTextOverlay *text_overlay,
const bool inverted)
{
const int draw_size = get_draw_size(ICON_SIZE_ICON);
icon_draw_size(x,
@ -2689,7 +2691,8 @@ void UI_icon_draw_ex(float x,
desaturate,
mono_color,
mono_border,
text_overlay);
text_overlay,
inverted);
}
void UI_icon_draw_mono_rect(

View File

@ -38,8 +38,14 @@ static void icon_draw_rect_input_text(
BLF_batch_draw_flush();
}
void icon_draw_rect_input(
float x, float y, int w, int h, float /*alpha*/, short event_type, short /*event_value*/)
void icon_draw_rect_input(float x,
float y,
int w,
int h,
float /*alpha*/,
short event_type,
short /*event_value*/,
bool inverted)
{
rctf rect{};
rect.xmin = int(x) - U.pixelsize;
@ -49,9 +55,17 @@ void icon_draw_rect_input(
float color[4];
GPU_line_width(1.0f);
UI_GetThemeColor4fv(TH_TEXT, color);
UI_draw_roundbox_corner_set(UI_CNR_ALL);
UI_draw_roundbox_aa(&rect, false, 3.0f * U.pixelsize, color);
if (inverted) {
UI_GetThemeColor4fv(TH_TEXT, color);
UI_draw_roundbox_aa(&rect, true, 3.0f * U.pixelsize, color);
UI_GetThemeColor4fv(TH_BACK, color);
}
else {
UI_GetThemeColor4fv(TH_TEXT, color);
UI_draw_roundbox_aa(&rect, false, 3.0f * U.pixelsize, color);
}
const enum {
UNIX,

View File

@ -1298,8 +1298,14 @@ int ui_id_icon_get(const bContext *C, ID *id, bool big);
/* interface_icons_event.cc */
void icon_draw_rect_input(
float x, float y, int w, int h, float alpha, short event_type, short event_value);
void icon_draw_rect_input(float x,
float y,
int w,
int h,
float alpha,
short event_type,
short event_value,
bool inverted = false);
/* resources.cc */

View File

@ -1367,8 +1367,16 @@ static void widget_draw_icon(
if (has_theme) {
alpha *= 0.8f;
}
UI_icon_draw_ex(
xs, ys, icon, aspect, alpha, 0.0f, color, has_theme, &but->icon_overlay_text);
UI_icon_draw_ex(xs,
ys,
icon,
aspect,
alpha,
0.0f,
color,
has_theme,
&but->icon_overlay_text,
but->drawflag & UI_BUT_ICON_INVERT);
}
else {
const bTheme *btheme = UI_GetTheme();

View File

@ -67,6 +67,7 @@
#include "BKE_scene.hh"
#include "BKE_screen.hh"
#include "BKE_shader_fx.h"
#include "BKE_workspace.hh"
#include "BLO_readfile.hh"
@ -6313,8 +6314,19 @@ void uiTemplateInputStatus(uiLayout *layout, bContext *C)
WorkSpace *workspace = CTX_wm_workspace(C);
/* Workspace status text has priority. */
if (workspace->status_text) {
uiItemL(layout, workspace->status_text, ICON_NONE);
if (!workspace->runtime->status.is_empty()) {
uiLayout *row = uiLayoutRow(layout, true);
for (WorkSpaceStatusItem item : workspace->runtime->status) {
if (item.space_factor != 0.0f) {
uiItemS_ex(row, item.space_factor);
}
else {
uiBut *but = uiItemL_ex(row, item.text.c_str(), item.icon, false, false);
if (item.inverted) {
but->drawflag |= UI_BUT_ICON_INVERT;
}
}
}
return;
}

View File

@ -834,35 +834,138 @@ void ED_area_status_text(ScrArea *area, const char *str)
}
}
void ED_workspace_status_text(bContext *C, const char *str)
{
wmWindow *win = CTX_wm_window(C);
WorkSpace *workspace = CTX_wm_workspace(C);
/* *************************************************************** */
static void ed_workspace_status_item(WorkSpace *workspace,
const std::string text,
const int icon,
const float space_factor = 0.0f,
const bool inverted = false)
{
/* Can be nullptr when running operators in background mode. */
if (workspace == nullptr) {
return;
}
if (str) {
if (workspace->status_text == nullptr) {
workspace->status_text = static_cast<char *>(MEM_mallocN(UI_MAX_DRAW_STR, "headerprint"));
}
BLI_strncpy(workspace->status_text, str, UI_MAX_DRAW_STR);
}
else {
MEM_SAFE_FREE(workspace->status_text);
}
WorkSpaceStatusItem item;
item.text = text;
item.icon = icon;
item.space_factor = space_factor;
item.inverted = inverted;
workspace->runtime->status.append(item);
}
/* Redraw status bar. */
LISTBASE_FOREACH (ScrArea *, area, &win->global_areas.areabase) {
if (area->spacetype == SPACE_STATUSBAR) {
ED_area_tag_redraw(area);
break;
static void ed_workspace_status_space(WorkSpace *workspace, float space_factor)
{
ed_workspace_status_item(workspace, {}, ICON_NONE, space_factor);
}
WorkspaceStatus::WorkspaceStatus(bContext *C)
{
workspace_ = CTX_wm_workspace(C);
Harley marked this conversation as resolved

This is a bit bitpicky, but I think it's better not to store pointers to context like this. Because it doesn't represent a frozen context.

I suggest to put ED_area_tag_redraw in the constructor, and then store a pointer to the window manager along with the workspace.

This is a bit bitpicky, but I think it's better not to store pointers to context like this. Because it doesn't represent a frozen context. I suggest to put `ED_area_tag_redraw` in the constructor, and then store a pointer to the window manager along with the workspace.
wm_ = CTX_wm_manager(C);
if (workspace_) {
BKE_workspace_status_clear(workspace_);
}
ED_area_tag_redraw(WM_window_status_area_find(CTX_wm_window(C), CTX_wm_screen(C)));
}
/* Private helper functions to help ensure consistant spacing. */
#define STATUS_AFTER_TEXT 0.7f
#define STATUS_BEFORE_TEXT 0.3f
#define STATUS_MOUSE_ICON_BEFORE -0.5f
#define STATUS_MOUSE_ICON_AFTER -0.7f
static void ed_workspace_status_text_item(WorkSpace *workspace, const std::string text)
{
if (!text.empty()) {
ed_workspace_status_space(workspace, STATUS_BEFORE_TEXT);
ed_workspace_status_item(workspace, text, ICON_NONE);
ed_workspace_status_space(workspace, STATUS_AFTER_TEXT);
}
}
static void ed_workspace_status_mouse_item(WorkSpace *workspace, int icon, bool inverted = false)
{
if (icon) {
if (icon >= ICON_MOUSE_LMB && icon <= ICON_MOUSE_RMB_DRAG) {
/* Negative space before all narrow mice icons. */
ed_workspace_status_space(workspace, STATUS_MOUSE_ICON_BEFORE);
}
ed_workspace_status_item(workspace, {}, icon, 0.0f, inverted);
if (icon >= ICON_MOUSE_LMB && icon <= ICON_MOUSE_RMB) {
/* Negative space after non-drag mice icons. */
ed_workspace_status_space(workspace, STATUS_MOUSE_ICON_AFTER);
}
}
}
#undef STATUS_AFTER_TEXT
#undef STATUS_BEFORE_TEXT
#undef STATUS_MOUSE_ICON_BEFORE
#undef STATUS_MOUSE_ICON_AFTER
/* Public functions. */
void WorkspaceStatus::item(std::string text, int icon1, int icon2)
{
ed_workspace_status_mouse_item(workspace_, icon1);
ed_workspace_status_mouse_item(workspace_, icon2);
ed_workspace_status_text_item(workspace_, text);
}
void WorkspaceStatus::range(std::string text, int icon1, int icon2)
{
ed_workspace_status_item(workspace_, {}, icon1);
ed_workspace_status_item(workspace_, "-", ICON_NONE);
ed_workspace_status_space(workspace_, -0.5f);
ed_workspace_status_item(workspace_, {}, icon2);
ed_workspace_status_text_item(workspace_, text);
}
void WorkspaceStatus::item_bool(std::string text, bool interted, int icon1, int icon2)
{
ed_workspace_status_mouse_item(workspace_, icon1, interted);
ed_workspace_status_mouse_item(workspace_, icon2, interted);
ed_workspace_status_text_item(workspace_, text);
}
void WorkspaceStatus::opmodal(std::string text, wmOperatorType *ot, int propvalue, bool inverted)
{
wmKeyMap *keymap = WM_keymap_active(wm_, ot->modalkeymap);
if (keymap) {
const wmKeyMapItem *kmi = WM_modalkeymap_find_propvalue(keymap, propvalue);
if (kmi) {
int icon = UI_icon_from_event_type(kmi->type, kmi->val);
if (!ELEM(kmi->shift, KM_NOTHING, KM_ANY)) {
ed_workspace_status_item(workspace_, {}, ICON_EVENT_SHIFT, 0.0f, inverted);
}
if (!ELEM(kmi->ctrl, KM_NOTHING, KM_ANY)) {
ed_workspace_status_item(workspace_, {}, ICON_EVENT_CTRL, 0.0f, inverted);
}
if (!ELEM(kmi->alt, KM_NOTHING, KM_ANY)) {
ed_workspace_status_item(workspace_, {}, ICON_EVENT_ALT, 0.0f, inverted);
}
if (!ELEM(kmi->oskey, KM_NOTHING, KM_ANY)) {
ed_workspace_status_item(workspace_, {}, ICON_EVENT_OS, 0.0f, inverted);
}
if (kmi->val == KM_DBL_CLICK) {
ed_workspace_status_item(workspace_, "2" BLI_STR_UTF8_MULTIPLICATION_SIGN, ICON_NONE);
ed_workspace_status_space(workspace_, -0.7f);
}
ed_workspace_status_mouse_item(workspace_, icon, inverted);
ed_workspace_status_text_item(workspace_, text);
}
}
}
void ED_workspace_status_text(bContext *C, const char *str)
{
WorkspaceStatus status(C);
status.item(str ? str : "", ICON_NONE);
}
/* ************************************************************ */
static void area_azone_init(wmWindow *win, const bScreen *screen, ScrArea *area)

View File

@ -14,6 +14,15 @@
#include "DNA_asset_types.h"
#include "DNA_viewer_path_types.h"
#ifdef __cplusplus
namespace blender::bke {
struct WorkSpaceRuntime;
}
using WorkSpaceRuntimeHandle = blender::bke::WorkSpaceRuntime;
#else
typedef struct WorkSpaceRuntimeHandle WorkSpaceRuntimeHandle;
#endif
/** #bToolRef_Runtime.flag */
enum {
/**
@ -137,7 +146,7 @@ typedef struct WorkSpace {
int order;
/** Info text from modal operators (runtime). */
char *status_text;
WorkSpaceRuntimeHandle *runtime;
/** Workspace-wide active asset library, for asset UIs to use (e.g. asset view UI template). The
* Asset Browser has its own and doesn't use this. */