Subversion Repositories DIN Is Noise

Rev

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