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;
Harley marked this conversation as resolved Outdated

Remove this, only needed for listbase.

Remove this, only needed for listbase.
std::string text = {};
float space_factor = 0.0f;
bool inverted = false;
};
brecht marked this conversation as resolved Outdated

Conceptually, it would be nice if this could directly edit a uiLayout rather than this intermediate data structure. But that is tied closely to a uiBlock the creation of which is tied to the drawing code, and it doesn't seem practical. So this seems like a reasonable solution to me.

Conceptually, it would be nice if this could directly edit a `uiLayout` rather than this intermediate data structure. But that is tied closely to a `uiBlock` the creation of which is tied to the drawing code, and it doesn't seem practical. So this seems like a reasonable solution to me.

If you're going to initialize some members by default, might as well do all of them.

If you're going to initialize some members by default, might as well do all of them.
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:
Harley marked this conversation as resolved Outdated

Not sure what the purpose of is of giving the text a default value, I would rather expected ED_workspace_status_space to be used for that case.

Not sure what the purpose of is of giving the text a default value, I would rather expected `ED_workspace_status_space` to be used for that case.

Indeed, looking at this it showed times when ED_workspace_status_icons should have been used.

Indeed, looking at this it showed times when `ED_workspace_status_icons` should have been used.
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);
Harley marked this conversation as resolved Outdated

I'm not sure about overloading a function like this where one is a float and the other a boolean, it seems error prone. I would rather expect this to be a single function with 5 parameters, or two functions with different names.

I'm not sure about overloading a function like this where one is a float and the other a boolean, it seems error prone. I would rather expect this to be a single function with 5 parameters, or two functions with different names.

Yes, I think the larger error here is that the base function that creates a new WorkSpaceStatusItem should probably be a local static function. The others are more like formatted output instead. This overload confuses these.

Yes, I think the larger error here is that the base function that creates a new WorkSpaceStatusItem should probably be a local static function. The others are more like formatted output instead. This overload confuses these.
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)));
Harley marked this conversation as resolved Outdated

Don't add semicolon here.

Don't add semicolon here.
}
/* Private helper functions to help ensure consistant spacing. */
#define STATUS_AFTER_TEXT 0.7f
Harley marked this conversation as resolved Outdated

Don't add semicolon here.

Don't add semicolon here.
#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);
}
Harley marked this conversation as resolved Outdated

I'm not sure about using the 🚫 symbol here. I think has meaning "prohibited" rather than "off". I think "✗" maybe be the better opposite of "✔"?

I'm not sure about using the `🚫` symbol here. I think has meaning "prohibited" rather than "off". I think "✗" maybe be the better opposite of "✔"?

It does look "prohibited". But the "✗" (in the various versions we have of it in Unicode) looks a lot like a keypress X. For next update I'll probably make it ✔ & ✖ so we can look at it. Thinner version of these are too small. One matching pair that looks okay is & , but the "x" one can look "on"? Eh, this editor makes these look unlike how we see them in place, will make some captures later. Don't want to spend too much time on this yet as we can always make and use icons for this too.

It does look "prohibited". But the "✗" (in the various versions we have of it in Unicode) looks a lot like a keypress X. For next update I'll probably make it ✔ & ✖ so we can look at it. Thinner version of these are too small. One matching pair that looks okay is ✅ & ❎, but the "x" one can look "on"? Eh, this editor makes these look unlike how we see them in place, will make some captures later. Don't want to spend too much time on this yet as we can always make and use icons for this too.
/* ************************************************************ */
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. */