Rev 2110 |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/*
* spinner.h
* DIN Is Noise is copyright (c) 2006-2025 Jagannathan Sampath
* For more information, please visit http://dinisnoise.org/
*/
#ifndef __spinner
#define __spinner
#include "input.h"
#include "font.h"
#include "widget.h"
#include "label.h"
#include "arrow_button.h"
#include "checkbutton.h"
#include "field.h"
#include "utils.h"
#include "mouse_slider.h"
#include "tokenizer.h"
#include <string>
#include <typeinfo>
extern void abort_selectors ();
extern int VAR_MIN, VAR_MAX;
extern std::string VARSTR;
extern int SPACING;
extern button& detach_from_menu (widget** wa, int n, int posx, int posy);
extern void attach_to_menu (widget** wa, int n);
template <typename T> struct spinner : widget, state_listener, change_listener<field>, typing_listener, mouse_slider_listener, nullt {
template <typename Q> struct re_pre_lis : click_listener {
spinner<Q>& sp;
click_listener* pre;
re_pre_lis (spinner<Q>& _sp, click_listener* _pre) : sp (_sp), pre (_pre) {}
void clicked (button& b) {
widget* w[] = {&sp};
attach_to_menu (w, 1);
LISTEN (sp.dec, pre)
LISTEN (sp.inc, pre)
if (sp.null) sp.set_value (0);
}
};
template <typename Q> struct pre_lis : click_listener {
spinner<Q>& sp;
click_listener *decl, *incl;
pre_lis (spinner<Q>& _sp, click_listener* _decl, click_listener* _incl) : sp (_sp), decl (_decl), incl (_incl) {}
void clicked (button& b) {
sp.dec.set_listener (decl);
sp.inc.set_listener (incl);
widget* wa [] = {&sp};
button& b_close = detach_from_menu (wa, 1, sp.dec.posx, sp.lbl.posy);
LISTEN (b_close, &sp.reprel);
b.call_listener ();
}
};
template <typename Q> struct dec_lis : click_listener {
spinner<Q>& sp;
dec_lis (spinner<Q>& _sp) : sp (_sp) {}
void clicked (button& b) {sp.decrease ();}
};
template <typename Q> struct inc_lis : click_listener {
spinner<Q>& sp;
inc_lis (spinner<Q>& _sp) : sp (_sp) {}
void clicked (button& b) {sp.increase ();}
};
template <typename Q> struct more_lis : click_listener {
spinner<Q>& sp;
more_lis (spinner<Q>& _sp) : sp (_sp) {}
void clicked (button& b) {sp.toggle_delta();}
};
template <typename Q> struct delta_lis : change_listener<field> {
spinner<Q>* sp;
delta_lis (spinner<Q>* _sp = 0) : sp (_sp) {}
void changed (field& f) {
sp->delta0 = sp->delta = f;
sp->set_pos (sp->posx, sp->posy);
sp->call_listener (1, f);
}
};
template <typename Q> struct variance_lis : change_listener<field> {
spinner<Q>* sp;
variance_lis (spinner<Q>* _sp = 0) : sp (_sp) {}
void changed (field& f) {
tokenizer tz (f.text);
float mn, mx; tz >> mn >> mx;
sp->variance.setrd (mn, mx);
sp->call_listener (2, f);
}
};
checkbutton lbl; // main label
label backlbl; // back label, after field
int draw_backlbl;
// increase/decrease buttons
arrow_button dec, inc;
void updowndecinc () {
dec.dir = arrow_button::down;
inc.dir = arrow_button::up;
}
dec_lis<T> decl;
inc_lis<T> incl;
int dir; // < 0 = decrease, > 0 = increase
// pre listeners so can detach from menu
int pre;
pre_lis<T> prel;
// to attach back to menu
re_pre_lis<T> reprel;
// value
field f_value;
T value;
T lastv;
// more ui
int draw_more;
arrow_button more;
more_lis<T> mol;
// delta for decrease, increase
label l_delta;
field f_delta;
delta_lis<T> dell;
// variance
struct variancet {
checkbutton cb; // at front of spinner
label lbl; // the ~
field fld;
variance_lis<T> lis;
rnd<float> rd;
int ui;
variancet (spinner<T>* sp = 0) : lis (sp) {
cb.set_text ("~");
lbl.set_text (" ~ ");
fld.set_text (VARSTR);
setrd (VAR_MIN, VAR_MAX);
fld.change_lsnr = &lis;
fld.expr = 0;
ui = 1;
}
void setrd (float s, float t) {
// assume s,t in %
s /= 100.0f;
t /= 100.0f;
rd.set (s, t);
}
} variance;
// value >= lo and <= hi?
int limits;
T lo, hi;
void set_limits (T _lo, T _hi) {
limits = 1;
lo = _lo;
hi = _hi;
}
change_listener<field> *lis[3]; // 0 - f_value, 1 - f_delta, 2 - variance.fld = null
spinner (const std::string& _name = "unknown") :
decl(*this), incl(*this),
pre(1),
prel (*this, &decl, &incl),
reprel (*this, &prel),
mol(*this),
l_delta ("+-"), dell(this), variance(this) {
#ifndef __WIDGET_MOVE__
lbl.set_listener (this);
#endif
widget* chld [] = {
this,
&dec,
&inc,
&f_value,
&more,
&l_delta,
&f_delta,
&variance.cb,
&variance.lbl,
&variance.fld
};
for (int i = 0; i < 10; ++i) lbl.add_child (chld[i]);
dec.set_dir (arrow_button::left);
inc.set_dir (arrow_button::right);
inc.click_repeat = 1;
dec.click_repeat = 1;
dir = 1;
draw_more = 1;
more.set_dir (arrow_button::right);
set_pre (pre);
LISTEN (more,&mol)
l_delta.hide ();
f_delta.hide ();
lastv = value = 0;
f_value.change_lsnr = this;
f_delta.change_lsnr = &dell;
f_value.typing_lsnr = f_delta.typing_lsnr = this;
lis[0]=lis[1]=lis[2]=0;
limits = 0;
lo = hi = 0;
// see field::call_listener ()
const std::type_info& ti = typeid (T);
std::string tn (ti.name());
if (tn == "i")
f_value.type = f_delta.type = "int";
else if (tn == "f")
f_value.type = f_delta.type = "double";
draw_backlbl = 0;
vary = &variance.cb;
}
void set_pos (int x, int y) {
widget::set_pos (x, y);
int i = 0;
if (draw_more == 0 || variance.ui == 0) i = 1;
widget* w [] = {&variance.cb, &lbl, &dec, &inc, &f_value, &more, &l_delta, &f_delta, &variance.lbl, &variance.fld};
int xshift [] = {0, 0, 0, -1, 0, 0, SPACING, 1, 0, 0};
int fl = fnt.lift + 1;
int lft [] = {0, 0, fl, fl, 0, fl, 0, 0, 0, 0};
for (; i < 10; ++i) {
x += xshift [i];
widget* wi = w[i];
wi->set_pos (x, y + lft[i]);
advance_right (x, *wi, SPACING);
}
set_pos_backlbl ();
}
void set_pos_backlbl () {
if (draw_backlbl) {
widget* w = 0;
if (f_delta.visible) w = &variance.fld;
else if (draw_more) w = &more;
else w = &f_value;
backlbl.set_pos (w->extents.right + SPACING, w->extents.bottom - fnt.lift);
}
}
void update () {
lbl.update ();
backlbl.update ();
l_delta.update ();
f_value.update ();
f_delta.update ();
variance.cb.update ();
variance.lbl.update ();
variance.fld.update ();
set_pos (posx, posy);
inc.update ();
dec.update ();
more.update ();
}
void toggle_delta () {
if (more.dir == arrow_button::right) {
l_delta.show ();
f_delta.show ();
if (variance.ui) {
variance.lbl.show ();
variance.fld.show ();
}
more.set_dir (arrow_button::left);
} else {
l_delta.hide ();
f_delta.hide ();
if (variance.ui) {
variance.lbl.hide ();
variance.fld.hide ();
}
more.set_dir (arrow_button::right);
}
set_pos_backlbl ();
abort_selectors ();
}
void change_value (int _dir, double scl = 1.0, int upd = 0) {
dir = _dir;
delta = scl * delta0;
value += operator()();
if (limits) clamp<T>(lo, value, hi);
if (pre == 0 || upd) {
f_value = value;
set_pos (posx, posy);
}
call_listener (0, f_value);
}
spinner<T>& operator++ () {
change_value (+1, 1, 1);
return *this;
}
spinner<T>& operator-- () {
change_value (-1, 1, 1);
return *this;
}
void increase () {
change_value (+1, 1, 1);
}
void decrease () {
change_value (-1, 1, 1);
}
void changed (field& f) {
if (limits) {
T v = f;
if (clamp<T>(lo, v, hi)) f = v;
value = v;
} else
value = f;
delta = value - lastv;
if (delta > 0) {
dir = 1;
} else {
dir = -1;
delta = -delta;
}
lastv = value;
set_pos (posx, posy);
call_listener (0, f);
}
void changed (checkbutton& cb) {
if (orient == NONE) {
cb.turn_off (0);
if (mouse_slider0.active) cant_mouse_slide ();
} else {
if (cb.state) mouse_slider0.add (this); else mouse_slider0.remove (this);
if (SHIFT == 0) activate_mouse_slider ();
}
}
void call_listener (int i, field& f) {
change_listener<field>* lisi = lis[i];
if (lisi) lisi->changed (f);
}
void typing (field& f) {
f.update ();
set_pos (posx, posy);
}
void moused (int _dir, double scl) {
change_value (_dir, scl);
}
void after_slide () {
lbl.turn_off (DONT_CALL_LISTENER);
set_delta (delta0 * mouse_slider0.scale.value);
if (null)
set_value (0);
else {
if (pre) set_value (value);
}
}
void draw () {
glColor3f (clr.r, clr.g, clr.b);
widget* w [] = {&lbl, &dec, &inc, &f_value};
for (int i = 0, j = 4; i < j; ++i) w[i]->draw ();
if (variance.ui) variance.cb.draw ();
if (draw_more) {
more.draw ();
if (more.dir == arrow_button::left) {
l_delta.draw ();
f_delta.draw ();
if (variance.ui) {
variance.lbl.draw ();
variance.fld.draw ();
}
}
}
if (draw_backlbl) backlbl.draw ();
}
void set_value (T t) {
value = t;
lastv = value;
f_value = value;
set_pos (posx, posy);
}
void set_delta (T t) {
delta0 = delta = t;
f_delta = (T) delta0;
set_pos (posx, posy);
}
void set_listener (change_listener<field>* _lis, int id = 0) {
lis [id] = _lis;
}
void set_text (const std::string& l, const std::string& bl = "") {
lbl.set_text (l);
set_name (l);
mouse_slider_listener::name = l;
if (bl != "") {
backlbl.set_text (bl);
draw_backlbl = 1;
}
}
void set_moveable (int m, int mc = 0, int* pmb = &lmb) {lbl.set_moveable (m, mc, pmb);}
int handle_input () {
int r = lbl.handle_input ();
if (r) return r;
if (variance.ui && variance.cb.handle_input()) return 1;
int d1 = 0, i1 = 0, m1 = 0;
d1 = dec.handle_input ();
if (d1 == 0) {
i1 = inc.handle_input ();
if (i1 == 0)
m1 = more.handle_input ();
}
int c = d1 | i1 | m1;
if (c) return c;
int s = f_value.handle_input ();
if (s) return s;
if (more.dir == arrow_button::left) {
int fd = f_delta.handle_input ();
if (fd) return fd;
if (variance.ui) {
if (variance.fld.handle_input()) return 1;
}
}
return 0;
}
inline T dir_delta () {
return dir * delta;
}
inline T operator() () {
return operator()(dir_delta());
}
T operator() (float dirdelta) {
float var = 1.0f;
if (variance.cb.state) var += variance.rd ();
return dirdelta * var;
}
T variedval () {
if (variance.cb.state)
return value + variance.rd() * delta0;
else
return value;
}
void set (const std::string& t, T d, T lmin, T lmax, change_listener<field>* l = 0, int _pre = 1) {
set_text (t);
set_delta (d);
set_limits (lmin, lmax);
set_listener (l);
set_pre (_pre);
}
void set (const std::string& t, T d, change_listener<field>* l) {
set_text (t);
set_delta (d);
set_listener (l);
}
void set (T d, T lmin, T lmax, change_listener<field>* l) {
set_delta (d);
set_limits (lmin, lmax);
set_listener (l);
}
void set_pre (int _pre) {
pre = _pre;
if (pre) {
LISTEN (dec,&prel)
LISTEN (inc,&prel)
} else {
LISTEN (dec,&decl)
LISTEN (inc,&incl)
}
}
};
#endif