From c4e5a70e07b1176547651cadcaf77ca36cb27dfd Mon Sep 17 00:00:00 2001 From: Harley Acheson Date: Mon, 6 May 2024 23:52:37 +0200 Subject: [PATCH] UI: Optional Complex Layout for Workspace Status Optionally allow complex layout instead of just plain text when using ED_workspace_status_text. Pull Request: https://projects.blender.org/blender/blender/pulls/120595 --- source/blender/blenkernel/BKE_workspace.hh | 20 +++ source/blender/blenkernel/intern/workspace.cc | 19 ++- source/blender/editors/include/ED_screen.hh | 13 ++ .../blender/editors/include/UI_interface_c.hh | 3 + .../editors/include/UI_interface_icons.hh | 3 +- source/blender/editors/interface/interface.cc | 3 +- .../editors/interface/interface_icons.cc | 17 ++- .../interface/interface_icons_event.cc | 22 ++- .../editors/interface/interface_intern.hh | 10 +- .../editors/interface/interface_widgets.cc | 12 +- .../templates/interface_templates.cc | 16 +- source/blender/editors/screen/area.cc | 139 +++++++++++++++--- source/blender/makesdna/DNA_workspace_types.h | 11 +- 13 files changed, 248 insertions(+), 40 deletions(-) diff --git a/source/blender/blenkernel/BKE_workspace.hh b/source/blender/blenkernel/BKE_workspace.hh index 6d947f53f7d..0052ca178a5 100644 --- a/source/blender/blenkernel/BKE_workspace.hh +++ b/source/blender/blenkernel/BKE_workspace.hh @@ -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 status; +}; + +} // 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 diff --git a/source/blender/blenkernel/intern/workspace.cc b/source/blender/blenkernel/intern/workspace.cc index 98f6fa4d377..ea4d07825c8 100644 --- a/source/blender/blenkernel/intern/workspace.cc +++ b/source/blender/blenkernel/intern/workspace.cc @@ -42,6 +42,8 @@ static void workspace_init_data(ID *id) { WorkSpace *workspace = (WorkSpace *)id; + workspace->runtime = MEM_new(__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(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(__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(); +} + +/** \} */ diff --git a/source/blender/editors/include/ED_screen.hh b/source/blender/editors/include/ED_screen.hh index d36ca708c85..ba75591d24b 100644 --- a/source/blender/editors/include/ED_screen.hh +++ b/source/blender/editors/include/ED_screen.hh @@ -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 */ diff --git a/source/blender/editors/include/UI_interface_c.hh b/source/blender/editors/include/UI_interface_c.hh index 7b87a672968..41e2f5ebdc8 100644 --- a/source/blender/editors/include/UI_interface_c.hh +++ b/source/blender/editors/include/UI_interface_c.hh @@ -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, }; /** diff --git a/source/blender/editors/include/UI_interface_icons.hh b/source/blender/editors/include/UI_interface_icons.hh index 2b59d602ab1..93747fef2c3 100644 --- a/source/blender/editors/include/UI_interface_icons.hh +++ b/source/blender/editors/include/UI_interface_icons.hh @@ -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, diff --git a/source/blender/editors/interface/interface.cc b/source/blender/editors/interface/interface.cc index 46e876a5f72..2ccc492f828 100644 --- a/source/blender/editors/interface/interface.cc +++ b/source/blender/editors/interface/interface.cc @@ -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) { diff --git a/source/blender/editors/interface/interface_icons.cc b/source/blender/editors/interface/interface_icons.cc index fc6112d42eb..89a3dc7c6fd 100644 --- a/source/blender/editors/interface/interface_icons.cc +++ b/source/blender/editors/interface/interface_icons.cc @@ -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( diff --git a/source/blender/editors/interface/interface_icons_event.cc b/source/blender/editors/interface/interface_icons_event.cc index 6f174eb86ec..8cec56fc04f 100644 --- a/source/blender/editors/interface/interface_icons_event.cc +++ b/source/blender/editors/interface/interface_icons_event.cc @@ -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, diff --git a/source/blender/editors/interface/interface_intern.hh b/source/blender/editors/interface/interface_intern.hh index 61b42dd481f..f45e2b43177 100644 --- a/source/blender/editors/interface/interface_intern.hh +++ b/source/blender/editors/interface/interface_intern.hh @@ -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 */ diff --git a/source/blender/editors/interface/interface_widgets.cc b/source/blender/editors/interface/interface_widgets.cc index b9cbaaf502a..329d3ae3abf 100644 --- a/source/blender/editors/interface/interface_widgets.cc +++ b/source/blender/editors/interface/interface_widgets.cc @@ -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(); diff --git a/source/blender/editors/interface/templates/interface_templates.cc b/source/blender/editors/interface/templates/interface_templates.cc index d6c911020d9..87462a198ca 100644 --- a/source/blender/editors/interface/templates/interface_templates.cc +++ b/source/blender/editors/interface/templates/interface_templates.cc @@ -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; } diff --git a/source/blender/editors/screen/area.cc b/source/blender/editors/screen/area.cc index 09f4c2fb1d7..3d5a5186836 100644 --- a/source/blender/editors/screen/area.cc +++ b/source/blender/editors/screen/area.cc @@ -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(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); + 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) diff --git a/source/blender/makesdna/DNA_workspace_types.h b/source/blender/makesdna/DNA_workspace_types.h index df7454cffa8..f3910822c5d 100644 --- a/source/blender/makesdna/DNA_workspace_types.h +++ b/source/blender/makesdna/DNA_workspace_types.h @@ -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. */