/*
* slider.h
* DIN Is Noise is copyright (c) 2006-2024 Jagannathan Sampath
* For more information, please visit http://dinisnoise.org/
*/


#ifndef __slider
#define __slider

#include "widget.h"
#include "viewwin.h"
#include "utils.h"
#include "filled_button.h"

#include <algorithm>
#include <vector>

extern int mousex;
extern viewport view;
extern int lmb;

template <typename S> struct after_slide_lis {
	virtual void slided (S& s) = 0;
};

template <typename S> struct start_slide_lis {
	virtual void start_slide (S& s) = 0;
};

template <typename T> struct values {
	
	T low, high, delta, val;
	float amount;

	values (T l = 0, T h = 1, T v = 0) {
		set_limits (l, h);
		*this = v;
	}
	
	operator T() {
		return ((T) val);
	}

	void set_limits (T l, T h) {
		low = l;
		high = h;
		delta = high - low;
	}
	
	void get_limits (T& l, T& h) {
		l = low;
		h = high;
	}
			
	T operator() () const { return val; }
	values& operator= (const T& v) {
		val = v;
		clamp<T> (low, val, high);
		if (delta) amount = (val - low) * 1.0f / delta; else amount = 0;
		return *this;
	}
	
	void set_amount (float a) {
		amount = a;
		clamp<float> (0.0f, amount, 1.0f);
		val = (T) (low + delta * amount);
	}
	
	float get_amount () const { return amount; }
	
};

template <typename T> struct slider : widget, move_listener {
	
	values<T> vx;
	int dx;
	
	int sliding;
	int lmb_clicked;

	change_listener<slider> *chgl;
	after_slide_lis<slider> *asl;
	start_slide_lis<slider> *ssl;

	filled_button sizer;
	static const int spc = 10;
			
	slider (int sv = 0, int w = 64, int h = 16) : widget (0, 0, w, h), vx (0, 0, 0), sliding(0), lmb_clicked(0) { 
		chgl = 0;
		asl = 0;
		set_sizer_visible (sv);
	}

	void set_sizer_visible (int sv) {
		sizer.visible = sv;
		if (sizer.visible) {
			sizer.set_moveable (1);
			sizer.movlis = this;
		} else {
			sizer.set_moveable (0);
			sizer.movlis = 0;
		}
	}

	void set_pos (int x, int y) {
		widget::set_pos (x, y);
		sizer.set_pos (extents.right + spc, extents.bottom);
	}

	void set_right (int r) {
		set_extents (extents.left, extents.bottom, r, extents.top);
		dx = vx.get_amount () * extents.width;
	}

  void set_size (int w) {
		set_extents (extents.left, extents.bottom, extents.left + w, extents.top);
    dx = vx.get_amount () * extents.width;
  }

	void moved () {
		int mpx = mousex - movr.prevx;
		if (mpx) {
			sizer.set_pos (mousex, extents.bottom);
			int msx = mousex - spc;
			if (msx > extents.left) set_right (msx);
		}
	}

	int handle_input () {
		
		widget::handle_input ();

		if (sizer.visible && sizer.handle_input ()) return 1;
		
		int ret = 0;

		if (lmb) {
			if (lmb_clicked == 0) {
				if (sliding) {
					sliding = 0;
					if (asl) asl->slided (*this);
					defocus (this);
					ret = 1;
				} else {
					if (hover) {
						sliding = 1;
						if (ssl) ssl->start_slide (*this);
						widget::focus = this;
						ret = 1;
					}
				}
				lmb_clicked = 1;
			}
		} else {
			if (sliding) {
				int nu_dx = mousex - extents.left;
				clamp (0, nu_dx, extents.width);
				if (nu_dx != dx) {
					dx = nu_dx;
					vx.set_amount (dx * extents.width_1);
					if (chgl) chgl->changed (*this);
				}
			} else 
        ret = 0;
			lmb_clicked = 0;
		}
		
		return ret;
		
	}

	void draw () {
		
		widget::draw ();

		glEnable (GL_BLEND);
		glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		
		const color& c = clr;
		
		glColor4f (c.r, c.g, c.b, (GLfloat) 0.25);
		
		const box<int>& e = extents;
			glRecti (e.left, e.bottom, e.right, e.top);
		
		glColor4f (c.r, c.g, c.b, GLfloat (1));
			glRecti (e.left, e.bottom, e.left + dx, e.top);

		glDisable (GL_BLEND);

		if (sizer.visible) sizer.draw ();

	}

	void update () {
		float amount = vx.get_amount ();
		const box<int>& e = extents;
		dx = (int) (amount * e.width);  
	}
	
	void set_listener (change_listener<slider>* _chgl, start_slide_lis<slider>* _ssl = 0, after_slide_lis<slider>* _asl = 0) {
		chgl = _chgl;
		ssl = _ssl;
		asl = _asl;
	}
	
	T operator() () const {return vx (); }
	
	void set_val (const T& t) { 
		vx = t;
		update ();
	}
	
	void set_limits (T l, T h) {
		vx = values<T> (l, h, vx());
		update ();
	}
	
	void get_limits (T& l, T& h) {
		vx.get_limits (l, h);
	}
};

#define MAKE_SLIDER_LISTENER(name,var) struct name : change_listener< slider<float> > { void changed (slider<float>& s); }; name var;
#define SLIDER_CHANGED(scope,name) void scope::name::changed (slider<float>& s)
#define MAKE_AFTER_SLIDE_LISTENER(name,type,var) struct name : after_slide_lis< type > { void slided (type& s); }; name var;
#define SLIDED(scope,name,type) void scope::name::slided (type& s)
#define MAKE_START_SLIDE_LISTENER(name,type,var) struct name : start_slide_lis< type > { void start_slide (type& s); }; name var;
#define START_SLIDE(scope,name,type) void scope::name::start_slide (type& s)

#endif
