Clone in a terminal:
SpinButton.vala
/*
    Copyright (C) 2012 Johan Mattsson

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

using Cairo;

namespace Supplement {

public class SpinButton : Tool {

	public signal void new_value_action (SpinButton selected);
	
	public int8 deka = 2;
	public int8 deci = 0;
	public int8 centi = 0;
	public int8 milli = 0;
	
	bool value_from_motion = false;
	double begin_x = 0;
	double begin_y = 0;
	double begin_value = 0;
	
	double max = 9.999;
	double min = 0;
	
	public SpinButton (string? name = null, string tip = "", unichar key = '\0', uint modifier_flag = 0) {
		base (null , tip, key, modifier_flag);
		
		if (name != null) {
			base.name = (!) name;
		}
		
		set_icon ("spin_button");
		
		panel_press_action.connect ((selected, button, tx, ty) => {
			double py = Math.fabs (y - ty);
			int n;
			
			if (is_selected ()) {
				n = (button == 3) ? 10 : 1;
					
				for (int i = 0; i < n; i++) {
					if (py < 28) increase ();
					if (py > 35) decrease ();
				}
			}
			
			value_from_motion = true;
			begin_x = tx;
			begin_y = ty;
			
			begin_value = double.parse (get_display_value ());
		});

		panel_move_action.connect ((selected, button, tx, ty) => {
			double d;
			if (value_from_motion) {
			 d = (begin_y - ty) / 200;
			 d = (d < 0) ? -Math.pow (d, 2) : Math.pow (d, 2);
			 set_value_round (begin_value + d);
			}
		});

		panel_release_action.connect ((selected, button, tx, ty) => {
			value_from_motion = false;
		});
	}
	
	public void set_max (double max) {
		this.max = max;
	}

	public void set_min (double min) {
		this.min = min;
	}
	
	public void increase () {	
		if (deka == 9 && deci == 9 && centi == 9 && milli == 9) {
			return;
		}
		
		milli++;

		if (milli == 10) {
			centi++;
			milli = 0;
		}
				
		if (centi == 10) {
			deci++;
			centi = 0;
		}

		if (deci == 10) {
			deka++;
			deci = 0;
		}
		
		if (get_value () > max) {
			set_value_round (max, false);
		}

		new_value_action (this);
	}

	public void decrease () {
		if (deka == 0 && deci == 0 && centi == 0) {
			return;
		}
		
		milli--;
		
		if (milli == -1) {
			centi--;
			milli = 9;
		}
		
		if (centi == -1) {
			deci--;
			centi = 9;
		}

		if (deci == -1) {
			deka--;
			deci = 9;
		}
		
		if (get_value () < 0.05) { // lower limit
			set_value_round (0.05);
		}

		if (get_value () < min) {
			set_value_round (min, false);
		}
				
		new_value_action (this);
	}

	public void set_value (string new_value, bool check_boundries = true, bool emit_signal = true) {
		string v = new_value;
		
		while (!(v.char_count () >= 5)) {
			if (v.index_of (".") == -1) {
				v += ".";
			} else {
				v += "0";
			}
			
			return;
		}
		
		deka = (int8) int.parse (v.substring (0, 1));
		deci = (int8) int.parse (v.substring (2, 1));
		centi = (int8) int.parse (v.substring (3, 1));
		milli = (int8) int.parse (v.substring (4, 1));
		
		if (emit_signal) {
			new_value_action (this);
		}
		
		if (check_boundries && get_value () > max) {
			set_value_round (max, false);
		}

		if (check_boundries && get_value () < min) {
			set_value_round (min, false);
		}
	}

	public void set_value_round (double v, bool check_boundries = true, bool emit_signal = true) {
		int8 m;
		
		v += 0.005;
		
		if (v > 10) v = 9.999;
		if (v < 0.001) v = 0.001;
				
		m = milli; // ignore milli value
		set_value (@"$v", check_boundries, emit_signal);
		milli = m;
	}
	
	public double get_value () {
		return deka + (deci / 10.0) + (centi / 100.0) + (milli / 1000.0);
	}

	public string get_display_value () {
		return @"$deka.$deci$centi$milli";
	}

	public override void draw (Context cr) {
		double xt = x + 3 + w;
		double yt = y + h + 17;

		double text_x = -4.5;
		double text_y = 11;

		base.draw (cr);
	
		cr.save ();
	
		cr.set_source_rgba (99/255.0, 99/255.0, 99/255.0, 1);
		
		cr.set_font_size (10);
		cr.select_font_face ("Cantarell", FontSlant.NORMAL, FontWeight.NORMAL);

		cr.move_to (xt + text_x, yt + text_y);
		
		cr.show_text (get_display_value ());
		
		cr.restore ();
	}
}

}

Contact

Email: johan.mattsson.m@gmail.com
Mastodon: typo.social/@birdfont
GitHub: johanmattssonm
LinkedIn: johan-mattsson-2ab324112