UI: Unit completion hint when text editing #116103

Open
Falk David wants to merge 9 commits from filedescriptor/blender:ui-unit-text-completion into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
18 changed files with 249 additions and 47 deletions

View File

@ -24,7 +24,8 @@ size_t BKE_unit_value_as_string_adaptive(char *str,
int system,
int type,
bool split,
bool pad);
bool pad,
bool do_suffix);
/**
* Representation of a value in units. Negative precision is used to disable stripping of zeroes.
* This reduces text jumping when changing values.
@ -35,7 +36,8 @@ size_t BKE_unit_value_as_string(char *str,
int prec,
int type,
const UnitSettings *settings,
bool pad);
bool pad,
bool do_suffix);
/**
* Replace units with values, used before python button evaluation.
@ -95,8 +97,10 @@ bool BKE_unit_is_valid(int system, int type);
void BKE_unit_system_get(int system, int type, const void **r_usys_pt, int *r_len);
int BKE_unit_base_get(const void *usys_pt);
int BKE_unit_base_of_type_get(int system, int type);
int BKE_unit_of_type_or_default(const UnitSettings *settings, int type);
const char *BKE_unit_name_get(const void *usys_pt, int index);
const char *BKE_unit_display_name_get(const void *usys_pt, int index);
const char *BKE_unit_display_name_short_get(const void *usys_pt, int index);
const char *BKE_unit_identifier_get(const void *usys_pt, int index);
double BKE_unit_scalar_get(const void *usys_pt, int index);
bool BKE_unit_is_suppressed(const void *usys_pt, int index);

View File

@ -1520,7 +1520,8 @@ static size_t unit_as_string(char *str,
const bUnitCollection *usys,
/* Non exposed options. */
const bUnitDef *unit,
char pad)
char pad,
const bool do_suffix)
{
if (unit == nullptr) {
if (value == 0.0) {
@ -1572,16 +1573,18 @@ static size_t unit_as_string(char *str,
}
/* Now add a space for all units except foot, inch, degree, arcminute, arcsecond. */
if (!(unit->flag & B_UNIT_DEF_NO_SPACE)) {
if (!(unit->flag & B_UNIT_DEF_NO_SPACE) && do_suffix) {
str[++i] = ' ';
}
/* Now add the suffix. */
if (i < str_maxncpy) {
int j = 0;
i++;
while (unit->name_short[j] && (i < str_maxncpy)) {
str[i++] = unit->name_short[j++];
if (do_suffix) {
/* Now add the suffix. */
int j = 0;
while (unit->name_short[j] && (i < str_maxncpy)) {
str[i++] = unit->name_short[j++];
}
}
}
@ -1634,7 +1637,7 @@ static size_t unit_as_string_split_pair(char *str,
/* Check the 2 is a smaller unit. */
if (unit_b > unit_a) {
size_t i = unit_as_string(str, str_maxncpy, value_a, prec, usys, unit_a, '\0');
size_t i = unit_as_string(str, str_maxncpy, value_a, prec, usys, unit_a, '\0', true);
prec -= integer_digits_d(value_a / unit_b->scalar) -
integer_digits_d(value_b / unit_b->scalar);
@ -1645,7 +1648,7 @@ static size_t unit_as_string_split_pair(char *str,
str[i++] = ' ';
/* Use low precision since this is a smaller unit. */
i += unit_as_string(str + i, str_maxncpy - i, value_b, prec, usys, unit_b, '\0');
i += unit_as_string(str + i, str_maxncpy - i, value_b, prec, usys, unit_b, '\0', true);
}
return i;
}
@ -1658,11 +1661,11 @@ static bool is_valid_unit_collection(const bUnitCollection *usys)
return usys != nullptr && usys->units[0].name != nullptr;
}
static const bUnitDef *get_preferred_display_unit_if_used(int type, PreferredUnits units)
static int get_preferred_display_unit_index_if_used(int type, PreferredUnits units)
{
const bUnitCollection *usys = unit_get_system(units.system, type);
if (!is_valid_unit_collection(usys)) {
return nullptr;
return -1;
}
int max_offset = usys->length - 1;
@ -1672,36 +1675,46 @@ static const bUnitDef *get_preferred_display_unit_if_used(int type, PreferredUni
case B_UNIT_AREA:
case B_UNIT_VOLUME:
if (units.length == USER_UNIT_ADAPTIVE) {
return nullptr;
return -1;
}
return usys->units + std::min(units.length, max_offset);
return std::min(units.length, max_offset);
case B_UNIT_MASS:
if (units.mass == USER_UNIT_ADAPTIVE) {
return nullptr;
return -1;
}
return usys->units + std::min(units.mass, max_offset);
return std::min(units.mass, max_offset);
case B_UNIT_TIME:
if (units.time == USER_UNIT_ADAPTIVE) {
return nullptr;
return -1;
}
return usys->units + std::min(units.time, max_offset);
return std::min(units.time, max_offset);
case B_UNIT_ROTATION:
if (units.rotation == 0) {
return usys->units + 0;
return 0;
}
else if (units.rotation == USER_UNIT_ROT_RADIANS) {
return usys->units + 3;
return 3;
}
break;
case B_UNIT_TEMPERATURE:
if (units.temperature == USER_UNIT_ADAPTIVE) {
return nullptr;
return -1;
}
return usys->units + std::min(units.temperature, max_offset);
return std::min(units.temperature, max_offset);
default:
break;
}
return nullptr;
return -1;
}
static const bUnitDef *get_preferred_display_unit_if_used(int type, PreferredUnits units)
{
const bUnitCollection *usys = unit_get_system(units.system, type);
const int index = get_preferred_display_unit_index_if_used(type, units);
if (index == -1) {
return nullptr;
}
return usys->units + index;
}
/* Return the length of the generated string. */
@ -1712,6 +1725,7 @@ static size_t unit_as_string_main(char *str,
int type,
bool split,
bool pad,
const bool do_suffix,
PreferredUnits units)
{
const bUnitCollection *usys = unit_get_system(units.system, type);
@ -1732,11 +1746,19 @@ static size_t unit_as_string_main(char *str,
}
}
return unit_as_string(str, str_maxncpy, value, prec, usys, main_unit, pad ? ' ' : '\0');
return unit_as_string(
str, str_maxncpy, value, prec, usys, main_unit, pad ? ' ' : '\0', do_suffix);
}
size_t BKE_unit_value_as_string_adaptive(
char *str, int str_maxncpy, double value, int prec, int system, int type, bool split, bool pad)
size_t BKE_unit_value_as_string_adaptive(char *str,
int str_maxncpy,
double value,
int prec,
int system,
int type,
bool split,
bool pad,
const bool do_suffix)
{
PreferredUnits units;
units.system = system;
@ -1745,7 +1767,7 @@ size_t BKE_unit_value_as_string_adaptive(
units.mass = USER_UNIT_ADAPTIVE;
units.time = USER_UNIT_ADAPTIVE;
units.temperature = USER_UNIT_ADAPTIVE;
return unit_as_string_main(str, str_maxncpy, value, prec, type, split, pad, units);
return unit_as_string_main(str, str_maxncpy, value, prec, type, split, pad, do_suffix, units);
}
size_t BKE_unit_value_as_string(char *str,
@ -1754,11 +1776,12 @@ size_t BKE_unit_value_as_string(char *str,
int prec,
int type,
const UnitSettings *settings,
bool pad)
bool pad,
const bool do_suffix)
{
bool do_split = (settings->flag & USER_UNIT_OPT_SPLIT) != 0;
PreferredUnits units = preferred_units_from_UnitSettings(settings);
return unit_as_string_main(str, str_maxncpy, value, prec, type, do_split, pad, units);
return unit_as_string_main(str, str_maxncpy, value, prec, type, do_split, pad, do_suffix, units);
}
BLI_INLINE bool isalpha_or_utf8(const int ch)
@ -2347,6 +2370,16 @@ int BKE_unit_base_of_type_get(int system, int type)
return unit_get_system(system, type)->base_unit;
}
int BKE_unit_of_type_or_default(const UnitSettings *settings, int type)
{
PreferredUnits units = preferred_units_from_UnitSettings(settings);
int unit_index = get_preferred_display_unit_index_if_used(type, units);
if (unit_index == -1) {
return BKE_unit_base_of_type_get(units.system, type);
}
return unit_index;
}
const char *BKE_unit_name_get(const void *usys_pt, int index)
{
const bUnitCollection *usys = static_cast<const bUnitCollection *>(usys_pt);
@ -2359,6 +2392,12 @@ const char *BKE_unit_display_name_get(const void *usys_pt, int index)
BLI_assert(uint(index) < uint(usys->length));
return usys->units[index].name_display;
}
const char *BKE_unit_display_name_short_get(const void *usys_pt, int index)
{
const bUnitCollection *usys = static_cast<const bUnitCollection *>(usys_pt);
BLI_assert(uint(index) < uint(usys->length));
return usys->units[index].name_short;
}
const char *BKE_unit_identifier_get(const void *usys_pt, int index)
{
const bUnitCollection *usys = static_cast<const bUnitCollection *>(usys_pt);

View File

@ -327,7 +327,8 @@ void DRW_text_edit_mesh_measure_stats(ARegion *region,
3,
B_UNIT_LENGTH,
unit,
false);
false,
true);
}
else {
numstr_len = SNPRINTF_RLEN(numstr, conv_float, len_v3v3(v1, v2));
@ -469,7 +470,8 @@ void DRW_text_edit_mesh_measure_stats(ARegion *region,
3,
B_UNIT_AREA,
unit,
false);
false,
true);
}
else {
numstr_len = SNPRINTF_RLEN(numstr, conv_float, area);

View File

@ -945,6 +945,11 @@ void UI_but_type_set_menu_from_pulldown(uiBut *but);
*/
void UI_but_placeholder_set(uiBut *but, const char *placeholder_text) ATTR_NONNULL(1);
/**
* Set a completion text that will be displayed while editing.
*/
void UI_but_completion_set(uiBut *but, const char *completion_text) ATTR_NONNULL(1);
/**
* Special button case, only draw it when used actively, for outliner etc.
*

View File

@ -192,7 +192,8 @@ static void depthdropper_depth_sample_pt(bContext *C,
4,
B_UNIT_LENGTH,
&scene->unit,
false);
false,
true);
}
else {
STRNCPY(ddr->name, "Nothing under cursor");

View File

@ -2764,8 +2764,13 @@ void ui_but_convert_to_unit_alt_name(uiBut *but, char *str, size_t str_maxncpy)
/**
* \param float_precision: Override the button precision.
*/
static void ui_get_but_string_unit(
uiBut *but, char *str, int str_maxncpy, double value, bool pad, int float_precision)
static void ui_get_but_string_unit(uiBut *but,
char *str,
int str_maxncpy,
double value,
bool pad,
int float_precision,
const bool do_suffix)
{
UnitSettings *unit = but->block->unit;
const int unit_type = UI_but_unit_type_get(but);
@ -2796,7 +2801,8 @@ static void ui_get_but_string_unit(
precision,
RNA_SUBTYPE_UNIT_VALUE(unit_type),
unit,
pad);
pad,
do_suffix);
}
static float ui_get_but_step_unit(uiBut *but, float step_default)
@ -2928,7 +2934,7 @@ void ui_but_string_get_ex(uiBut *but,
}
if (ui_but_is_unit(but)) {
ui_get_but_string_unit(but, str, str_maxncpy, value, false, prec);
ui_get_but_string_unit(but, str, str_maxncpy, value, false, prec, false);
}
else if (subtype == PROP_FACTOR) {
if (U.factor_display_type == USER_FACTOR_AS_FACTOR) {
@ -3486,6 +3492,10 @@ static void ui_but_free(const bContext *C, uiBut *but)
MEM_freeN(but->placeholder);
}
if (but->completion) {
MEM_freeN(but->completion);
}
ui_but_free_type_specific(but);
if (but->active) {
@ -3764,7 +3774,7 @@ static void ui_but_build_drawstr_float(uiBut *but, double value)
}
else if (ui_but_is_unit(but)) {
char new_str[sizeof(but->drawstr)];
ui_get_but_string_unit(but, new_str, sizeof(new_str), value, true, -1);
ui_get_but_string_unit(but, new_str, sizeof(new_str), value, true, -1, true);
STR_CONCAT(but->drawstr, slen, new_str);
}
else {
@ -5967,6 +5977,17 @@ const char *ui_but_placeholder_get(uiBut *but)
return placeholder;
}
void UI_but_completion_set(uiBut *but, const char *completion_text)
{
MEM_SAFE_FREE(but->completion);
but->completion = BLI_strdup_null(completion_text);
}
const char *ui_but_completion_get(uiBut *but)
{
return but->completion;
}
void UI_but_type_set_menu_from_pulldown(uiBut *but)
{
BLI_assert(but->type == UI_BTYPE_PULLDOWN);

View File

@ -52,6 +52,7 @@
#include "IMB_colormanagement.h"
#include "ED_numinput.hh"
#include "ED_screen.hh"
#include "ED_undo.hh"
@ -61,6 +62,8 @@
#include "BLF_api.h"
#include "BPY_extern_run.h"
#include "interface_intern.hh"
#include "RNA_access.hh"
@ -3426,6 +3429,77 @@ const wmIMEData *ui_but_ime_data_get(uiBut *but)
}
#endif /* WITH_INPUT_IME */
static void ui_do_but_text_completion(bContext *C, uiBut *but, uiHandleButtonData *data)
{
/* Unit completion (hint) is only done for buttons with a unit or with a property of type
* PROP_PIXEL or PROP_PERCENTAGE. For everything else, we reset the completion to nullptr. */
if (!ui_but_is_unit(but) &&
(but->rnaprop && !ELEM(RNA_property_subtype(but->rnaprop), PROP_PIXEL, PROP_PERCENTAGE)))
{
UI_but_completion_set(but, nullptr);
return;
}
/* Set the completion text (hint) to the unit that is used by this value. */
std::string name_short;
const int unit_type = RNA_SUBTYPE_UNIT_VALUE(UI_but_unit_type_get(but));
if (unit_type != PROP_NONE) {
/* If the string contains the unit already, don't add it as a hint. */
if (BKE_unit_string_contains_unit(data->str, unit_type)) {
UI_but_completion_set(but, nullptr);
return;
}
/* If the number we're entering is not valid, don't show the hint. */
double value;
if (!BPY_run_string_as_number(C, nullptr, data->str, nullptr, &value)) {
UI_but_completion_set(but, nullptr);
return;
}
const void *usys;
int len;
UnitSettings &unit_settings = CTX_data_scene(C)->unit;
BKE_unit_system_get(unit_settings.system, unit_type, &usys, &len);
const int unit_index = BKE_unit_of_type_or_default(&unit_settings, unit_type);
name_short = BKE_unit_display_name_short_get(usys, unit_index);
}
else if (but->rnaprop) {
/* Special handling for PROP_PIXEL and PROP_PERCENTAGE (because they are not treated as units
* unfortunatly). */
const PropertySubType subtype = RNA_property_subtype(but->rnaprop);
if (ELEM(subtype, PROP_PIXEL, PROP_PERCENTAGE)) {
switch (subtype) {
case PROP_PIXEL:
name_short = "px";
break;
case PROP_PERCENTAGE:
name_short = "%";
default:
break;
}
/* If the string contains the unit already, don't add it as a hint. */
std::string str = data->str;
if (str.find(name_short) != std::string::npos) {
UI_but_completion_set(but, nullptr);
return;
}
/* If the number we're entering is not valid, don't show the hint. */
double value;
if (!BPY_run_string_as_number(C, nullptr, data->str, nullptr, &value)) {
UI_but_completion_set(but, nullptr);
return;
}
}
}
/* Add a space before the short unit name. */
std::string text_completion = " " + name_short;
UI_but_completion_set(but, text_completion.c_str());
}
static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
{
wmWindow *win = data->window;
@ -3516,6 +3590,9 @@ static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
ui_but_update(but);
/* Initialize completion. */
ui_do_but_text_completion(C, but, data);
/* Make sure the edited button is in view. */
if (data->searchbox) {
/* Popup blocks don't support moving after creation, so don't change the view for them. */
@ -4052,6 +4129,9 @@ static void ui_do_but_textedit(
ui_textedit_undo_push(data->undo_stack_text, data->str, but->pos);
}
/* Handle text completion. */
ui_do_but_text_completion(C, but, data);
/* only do live update when but flag request it (UI_BUT_TEXTEDIT_UPDATE). */
if (update && data->interactive) {
ui_apply_but(C, block, but, data, true);

View File

@ -181,6 +181,7 @@ struct uiBut {
char *str = nullptr;
char strdata[UI_MAX_NAME_STR] = "";
char drawstr[UI_MAX_DRAW_STR] = "";
char *completion = nullptr;
char *placeholder = nullptr;
@ -759,6 +760,11 @@ uiBut *ui_but_drag_multi_edit_get(uiBut *but);
*/
const char *ui_but_placeholder_get(uiBut *but);
/**
* Get the completion string while editing.
*/
const char *ui_but_completion_get(uiBut *but);
void ui_def_but_icon(uiBut *but, int icon, int flag);
/**
* Avoid using this where possible since it's better not to ask for an icon in the first place.

View File

@ -22,6 +22,7 @@
#include "BLI_utildefines.h"
#include "BKE_context.hh"
#include "BKE_unit.hh"
#include "RNA_access.hh"
@ -1849,6 +1850,7 @@ static void widget_draw_text(const uiFontStyle *fstyle,
const char *drawstr_right = nullptr;
bool use_right_only = false;
const char *indeterminate_str = UI_VALUE_INDETERMINATE_CHAR;
const char *completion = ui_but_completion_get(but);
#ifdef WITH_INPUT_IME
const wmIMEData *ime_data;
@ -1873,6 +1875,7 @@ static void widget_draw_text(const uiFontStyle *fstyle,
uiBut *but_edit = ui_but_drag_multi_edit_get(but);
if (but_edit) {
drawstr = but_edit->editstr;
completion = but_edit->completion;
align = UI_STYLE_TEXT_LEFT;
}
}
@ -2177,6 +2180,32 @@ static void widget_draw_text(const uiFontStyle *fstyle,
}
}
}
if (completion && completion[0] != '\0' && drawstr[0] != '\0') {
rcti text_bounds;
BLF_boundbox(fstyle->uifont_id, drawstr + but->ofs, drawlen, &text_bounds);
uiFontStyle style = *fstyle;
style.shadow = 0;
uchar col[4];
copy_v4_v4_uchar(col, wcol->text);
col[3] *= 0.33f;
rcti completion_rect;
completion_rect.xmin = rect->xmin + text_bounds.xmax;
completion_rect.ymin = rect->ymin;
completion_rect.xmax = rect->xmax;
completion_rect.ymax = rect->ymax;
UI_fontstyle_draw_ex(&style,
&completion_rect,
completion,
strlen(completion),
col,
&params,
nullptr,
nullptr,
nullptr);
}
}
}

View File

@ -145,6 +145,7 @@ static void edbm_bevel_update_status_text(bContext *C, wmOperator *op)
3,
B_UNIT_LENGTH,
&sce->unit,
true,
true);
}

View File

@ -92,6 +92,7 @@ static void edbm_inset_update_header(wmOperator *op, bContext *C)
4,
B_UNIT_LENGTH,
&sce->unit,
true,
true);
BKE_unit_value_as_string(flts_str + NUM_STR_REP_LEN,
NUM_STR_REP_LEN,
@ -99,6 +100,7 @@ static void edbm_inset_update_header(wmOperator *op, bContext *C)
4,
B_UNIT_LENGTH,
&sce->unit,
true,
true);
}
SNPRINTF(msg,

View File

@ -520,7 +520,8 @@ static void knifetool_draw_visible_distances(const KnifeTool_OpData *kcd)
distance_precision,
B_UNIT_LENGTH,
unit,
false);
false,
true);
}
BLF_enable(blf_mono_font, BLF_ROTATION);
@ -649,8 +650,14 @@ static void knifetool_draw_angle(const KnifeTool_OpData *kcd,
SNPRINTF(numstr, "%.*f" BLI_STR_UTF8_DEGREE_SIGN, angle_precision, RAD2DEGF(angle));
}
else {
BKE_unit_value_as_string(
numstr, sizeof(numstr), double(angle), angle_precision, B_UNIT_ROTATION, unit, false);
BKE_unit_value_as_string(numstr,
sizeof(numstr),
double(angle),
angle_precision,
B_UNIT_ROTATION,
unit,
false,
true);
}
BLF_enable(blf_mono_font, BLF_ROTATION);

View File

@ -338,6 +338,7 @@ static void voxel_size_edit_draw(const bContext *C, ARegion * /*region*/, void *
-3,
B_UNIT_LENGTH,
unit,
true,
true);
strdrawlen = BLI_strlen_utf8(str);

View File

@ -213,7 +213,7 @@ static void ruler_item_as_string(
}
else {
BKE_unit_value_as_string(
numstr, numstr_size, double(ruler_angle), prec, B_UNIT_ROTATION, unit, false);
numstr, numstr_size, double(ruler_angle), prec, B_UNIT_ROTATION, unit, false, true);
}
}
else {
@ -229,7 +229,8 @@ static void ruler_item_as_string(
prec,
B_UNIT_LENGTH,
unit,
false);
false,
true);
}
}
}

View File

@ -120,6 +120,7 @@ static void applyShrinkFatten(TransInfo *t)
4,
B_UNIT_LENGTH,
unit,
true,
true);
}
else {

View File

@ -191,7 +191,7 @@ static void translate_dist_to_str(char *r_str,
{
if (unit) {
BKE_unit_value_as_string(
r_str, r_str_maxncpy, val * unit->scale_length, 4, B_UNIT_LENGTH, unit, false);
r_str, r_str_maxncpy, val * unit->scale_length, 4, B_UNIT_LENGTH, unit, false, true);
}
else {
/* Check range to prevent string buffer overflow. */

View File

@ -130,7 +130,8 @@ void outputNumInput(NumInput *n, char *str, UnitSettings *unit_settings)
n->unit_sys,
n->unit_type[i],
true,
false);
false,
true);
}
/* +1 because of trailing '\0' */
@ -153,7 +154,7 @@ void outputNumInput(NumInput *n, char *str, UnitSettings *unit_settings)
else {
char tstr[NUM_STR_REP_LEN];
BKE_unit_value_as_string_adaptive(
tstr, ln, double(n->val[i]), prec, n->unit_sys, n->unit_type[i], true, false);
tstr, ln, double(n->val[i]), prec, n->unit_sys, n->unit_type[i], true, false, true);
BLI_snprintf(&str[j * ln], ln, "%s%s%s", cur, tstr, cur);
}
}
@ -243,7 +244,8 @@ static void value_to_editstr(NumInput *n, int idx)
n->unit_sys,
n->unit_type[idx],
true,
false);
false,
true);
}
static bool editstr_insert_at_cursor(NumInput *n, const char *buf, const int buf_len)

View File

@ -311,7 +311,7 @@ static PyObject *bpyunits_to_string(PyObject * /*self*/, PyObject *args, PyObjec
PyObject *result;
BKE_unit_value_as_string_adaptive(
buf1, sizeof(buf1), value, precision, usys, ucat, bool(split_unit), false);
buf1, sizeof(buf1), value, precision, usys, ucat, bool(split_unit), false, true);
if (compatible_unit) {
BKE_unit_name_to_alt(buf2, sizeof(buf2), buf1, usys, ucat);