UI: add non-linear slider support

This patch introduces non linear sliders. That means, that the movement
of the mouse doesn't map linearly to the value of the slider.

The following changes have been made.

- Free logarithmic sliders with maximum range of (`0 <= x < inf`)
- Logarithmic sliders with correct value indication bar.
- Free cubic sliders with maximum range of (`-inf < x < inf`)
- Cubic sliders with correct value indication bar.

Cubic mapping has been added as well, because it's used for brush sizes
in other applications (Krita for e.g.).

To make a slider have a different scale type use following line in RNA:
 `RNA_def_property_ui_scale_type(prop, PROP_SCALE_LOGARITHMIC);`
or:
 `RNA_def_property_ui_scale_type(prop, PROP_SCALE_CUBIC);`

Test the precision, step size and soft-min if you change the scale type
of a property as it will feel very different and may need tweaking.

Ref D9074
This commit is contained in:
Henrik Dick
2021-05-17 17:28:12 +10:00
committed by Campbell Barton
parent 45863059f7
commit 0447aedb96
11 changed files with 351 additions and 49 deletions

View File

@@ -125,6 +125,24 @@
*/
#define UI_MAX_PASSWORD_STR 128
/**
* This is a lower limit on the soft minimum of the range.
* Usually the derived lower limit from the visible precision is higher,
* so this number is the backup minimum.
*
* Logarithmic scale does not work with a minimum value of zero,
* but we want to support it anyway. It is set to 0.5e... for
* correct rounding since when the tweaked value is lower than
* the log minimum (lower limit), it will snap to 0.
*/
#define UI_PROP_SCALE_LOG_MIN 0.5e-8f
/**
* This constant defines an offset for the precision change in
* snap rounding, when going to higher values. It is set to
* `0.5 - log10(3) = 0.03` to make the switch at `0.3` values.
*/
#define UI_PROP_SCALE_LOG_SNAP_OFFSET 0.03f
/**
* When #USER_CONTINUOUS_MOUSE is disabled or tablet input is used,
* Use this as a maximum soft range for mapping cursor motion to the value.
@@ -3938,9 +3956,16 @@ static void ui_numedit_begin(uiBut *but, uiHandleButtonData *data)
float softmin = but->softmin;
float softmax = but->softmax;
float softrange = softmax - softmin;
const PropertyScaleType scale_type = ui_but_scale_type(but);
float log_min = (scale_type == PROP_SCALE_LOG) ? max_ff(softmin, UI_PROP_SCALE_LOG_MIN) : 0.0f;
if ((but->type == UI_BTYPE_NUM) && (ui_but_is_cursor_warp(but) == false)) {
uiButNumber *number_but = (uiButNumber *)but;
if (scale_type == PROP_SCALE_LOG) {
log_min = max_ff(log_min, powf(10, -number_but->precision) * 0.5f);
}
/* Use a minimum so we have a predictable range,
* otherwise some float buttons get a large range. */
const float value_step_float_min = 0.1f;
@@ -3989,7 +4014,31 @@ static void ui_numedit_begin(uiBut *but, uiHandleButtonData *data)
}
}
data->dragfstart = (softrange == 0.0f) ? 0.0f : ((float)data->value - softmin) / softrange;
if (softrange == 0.0f) {
data->dragfstart = 0.0f;
}
else {
switch (scale_type) {
case PROP_SCALE_LINEAR: {
data->dragfstart = ((float)data->value - softmin) / softrange;
break;
}
case PROP_SCALE_LOG: {
BLI_assert(log_min != 0.0f);
const float base = softmax / log_min;
data->dragfstart = logf((float)data->value / log_min) / logf(base);
break;
}
case PROP_SCALE_CUBIC: {
const float cubic_min = cube_f(softmin);
const float cubic_max = cube_f(softmax);
const float cubic_range = cubic_max - cubic_min;
const float f = ((float)data->value - softmin) * cubic_range / softrange + cubic_min;
data->dragfstart = (cbrtf(f) - softmin) / softrange;
break;
}
}
}
data->dragf = data->dragfstart;
data->drag_map_soft_min = softmin;
@@ -4707,6 +4756,7 @@ static float ui_numedit_apply_snapf(
/* pass */
}
else {
const PropertyScaleType scale_type = ui_but_scale_type(but);
float softrange = softmax - softmin;
float fac = 1.0f;
@@ -4744,30 +4794,29 @@ static float ui_numedit_apply_snapf(
}
}
if (snap == SNAP_ON) {
if (softrange < 2.10f) {
tempf = roundf(tempf * 10.0f) * 0.1f;
BLI_assert(ELEM(snap, SNAP_ON, SNAP_ON_SMALL));
switch (scale_type) {
case PROP_SCALE_LINEAR:
case PROP_SCALE_CUBIC: {
const float snap_fac = (snap == SNAP_ON_SMALL ? 0.1f : 1.0f);
if (softrange < 2.10f) {
tempf = roundf(tempf * 10.0f / snap_fac) * 0.1f * snap_fac;
}
else if (softrange < 21.0f) {
tempf = roundf(tempf / snap_fac) * snap_fac;
}
else {
tempf = roundf(tempf * 0.1f / snap_fac) * 10.0f * snap_fac;
}
break;
}
else if (softrange < 21.0f) {
tempf = roundf(tempf);
case PROP_SCALE_LOG: {
const float snap_fac = powf(10.0f,
roundf(log10f(tempf) + UI_PROP_SCALE_LOG_SNAP_OFFSET) -
(snap == SNAP_ON_SMALL ? 2.0f : 1.0f));
tempf = roundf(tempf / snap_fac) * snap_fac;
break;
}
else {
tempf = roundf(tempf * 0.1f) * 10.0f;
}
}
else if (snap == SNAP_ON_SMALL) {
if (softrange < 2.10f) {
tempf = roundf(tempf * 100.0f) * 0.01f;
}
else if (softrange < 21.0f) {
tempf = roundf(tempf * 10.0f) * 0.1f;
}
else {
tempf = roundf(tempf);
}
}
else {
BLI_assert(0);
}
if (fac != 1.0f) {
@@ -4813,6 +4862,7 @@ static bool ui_numedit_but_NUM(uiButNumber *number_but,
int lvalue, temp;
bool changed = false;
const bool is_float = ui_but_is_float(but);
const PropertyScaleType scale_type = ui_but_scale_type(but);
/* prevent unwanted drag adjustments, test motion so modifier keys refresh. */
if ((is_motion || data->draglock) && (ui_but_dragedit_update_mval(data, mx) == false)) {
@@ -4824,21 +4874,74 @@ static bool ui_numedit_but_NUM(uiButNumber *number_but,
const float softmax = but->softmax;
const float softrange = softmax - softmin;
const float log_min = (scale_type == PROP_SCALE_LOG) ?
max_ff(max_ff(softmin, UI_PROP_SCALE_LOG_MIN),
powf(10, -number_but->precision) * 0.5f) :
0;
/* Mouse location isn't screen clamped to the screen so use a linear mapping
* 2px == 1-int, or 1px == 1-ClickStep */
if (is_float) {
fac *= 0.01f * number_but->step_size;
tempf = (float)data->startvalue + ((float)(mx - data->dragstartx) * fac);
switch (scale_type) {
case PROP_SCALE_LINEAR: {
tempf = (float)data->startvalue + (float)(mx - data->dragstartx) * fac;
break;
}
case PROP_SCALE_LOG: {
const float startvalue = max_ff((float)data->startvalue, log_min);
tempf = expf((float)(mx - data->dragstartx) * fac) * startvalue;
if (tempf <= log_min) {
tempf = 0.0f;
}
break;
}
case PROP_SCALE_CUBIC: {
tempf = cbrtf((float)data->startvalue) + (float)(mx - data->dragstartx) * fac;
tempf *= tempf * tempf;
break;
}
}
tempf = ui_numedit_apply_snapf(but, tempf, softmin, softmax, snap);
#if 1 /* fake moving the click start, nicer for dragging back after passing the limit */
if (tempf < softmin) {
data->dragstartx -= (softmin - tempf) / fac;
tempf = softmin;
}
else if (tempf > softmax) {
data->dragstartx += (tempf - softmax) / fac;
tempf = softmax;
switch (scale_type) {
case PROP_SCALE_LINEAR: {
if (tempf < softmin) {
data->dragstartx -= (softmin - tempf) / fac;
tempf = softmin;
}
else if (tempf > softmax) {
data->dragstartx -= (softmax - tempf) / fac;
tempf = softmax;
}
break;
}
case PROP_SCALE_LOG: {
if (tempf < log_min) {
data->dragstartx -= logf(log_min / (float)data->startvalue) / fac -
(float)(mx - data->dragstartx);
tempf = softmin;
}
else if (tempf > softmax) {
data->dragstartx -= logf(softmax / (float)data->startvalue) / fac -
(float)(mx - data->dragstartx);
tempf = softmax;
}
break;
}
case PROP_SCALE_CUBIC: {
if (tempf < softmin) {
data->dragstartx = mx - (int)((cbrtf(softmin) - cbrtf((float)data->startvalue)) / fac);
tempf = softmin;
}
else if (tempf > softmax) {
data->dragstartx = mx - (int)((cbrtf(softmax) - cbrtf((float)data->startvalue)) / fac);
tempf = softmax;
}
break;
}
}
#else
CLAMP(tempf, softmin, softmax);
@@ -4945,7 +5048,31 @@ static bool ui_numedit_but_NUM(uiButNumber *number_but,
}
data->draglastx = mx;
tempf = (softmin + data->dragf * softrange);
switch (scale_type) {
case PROP_SCALE_LINEAR: {
tempf = (softmin + data->dragf * softrange);
break;
}
case PROP_SCALE_LOG: {
const float log_min = max_ff(max_ff(softmin, UI_PROP_SCALE_LOG_MIN),
powf(10.0f, -number_but->precision) * 0.5f);
const float base = softmax / log_min;
tempf = powf(base, data->dragf) * log_min;
if (tempf <= log_min) {
tempf = 0.0f;
}
break;
}
case PROP_SCALE_CUBIC: {
tempf = (softmin + data->dragf * softrange);
tempf *= tempf * tempf;
float cubic_min = softmin * softmin * softmin;
float cubic_max = softmax * softmax * softmax;
tempf = (tempf - cubic_min) / (cubic_max - cubic_min) * softrange + softmin;
break;
}
}
if (!is_float) {
temp = round_fl_to_int(tempf);
@@ -5192,9 +5319,19 @@ static int ui_do_but_NUM(
else {
/* Float Value. */
if (but->drawflag & (UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT)) {
const PropertyScaleType scale_type = ui_but_scale_type(but);
button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
const double value_step = (double)number_but->step_size * UI_PRECISION_FLOAT_SCALE;
double value_step;
if (scale_type == PROP_SCALE_LOG) {
value_step = powf(10.0f,
(roundf(log10f(data->value) + UI_PROP_SCALE_LOG_SNAP_OFFSET) - 1.0f) +
log10f(number_but->step_size));
}
else {
value_step = (double)number_but->step_size * UI_PRECISION_FLOAT_SCALE;
}
BLI_assert(value_step > 0.0f);
const double value_test = (but->drawflag & UI_BUT_ACTIVE_LEFT) ?
(double)max_ff(but->softmin,
@@ -5242,6 +5379,8 @@ static bool ui_numedit_but_SLI(uiBut *but,
return changed;
}
const PropertyScaleType scale_type = ui_but_scale_type(but);
softmin = but->softmin;
softmax = but->softmax;
softrange = softmax - softmin;
@@ -5283,7 +5422,24 @@ static bool ui_numedit_but_SLI(uiBut *but,
#endif
/* done correcting mouse */
tempf = softmin + f * softrange;
switch (scale_type) {
case PROP_SCALE_LINEAR: {
tempf = softmin + f * softrange;
break;
}
case PROP_SCALE_LOG: {
tempf = powf(softmax / softmin, f) * softmin;
break;
}
case PROP_SCALE_CUBIC: {
const float cubicmin = cube_f(softmin);
const float cubicmax = cube_f(softmax);
const float cubicrange = cubicmax - cubicmin;
tempf = cube_f(softmin + f * softrange);
tempf = (tempf - cubicmin) / cubicrange * softrange + softmin;
break;
}
}
temp = round_fl_to_int(tempf);
if (snap) {
@@ -5477,6 +5633,8 @@ static int ui_do_but_SLI(
if (click) {
if (click == 2) {
const PropertyScaleType scale_type = ui_but_scale_type(but);
/* nudge slider to the left or right */
float f, tempf, softmin, softmax, softrange;
int temp;
@@ -5501,14 +5659,20 @@ static int ui_do_but_SLI(
f = (float)(mx - but->rect.xmin) / (BLI_rctf_size_x(&but->rect));
}
f = softmin + f * softrange;
if (scale_type == PROP_SCALE_LOG) {
f = powf(softmax / softmin, f) * softmin;
}
else {
f = softmin + f * softrange;
}
if (!ui_but_is_float(but)) {
int value_step = 1;
if (f < temp) {
temp--;
temp -= value_step;
}
else {
temp++;
temp += value_step;
}
if (temp >= softmin && temp <= softmax) {
@@ -5519,14 +5683,23 @@ static int ui_do_but_SLI(
}
}
else {
if (f < tempf) {
tempf -= 0.01f;
}
else {
tempf += 0.01f;
}
if (tempf >= softmin && tempf <= softmax) {
float value_step;
if (scale_type == PROP_SCALE_LOG) {
value_step = powf(10.0f, roundf(log10f(tempf) + UI_PROP_SCALE_LOG_SNAP_OFFSET) - 1.0f);
}
else {
value_step = 0.01f;
}
if (f < tempf) {
tempf -= value_step;
}
else {
tempf += value_step;
}
CLAMP(tempf, softmin, softmax);
data->value = tempf;
}
else {