Make an Elementary slider behave like a knob (work in progress)

Here is an example for an edje theme which makes an Elementary slider widget look and behave like a knob.

It will look like this and works by displaying one of 60 images, depending on how much you drag the knob (up and down). Each of the images corresponds to a different rotation of the knob.

The important parts of the edc file for this theme will be described below. In the group block we define the name we will later use to load the theme from within a C program.

collections{
	group{
		name: "elm/slider/horizontal/knob";
		alias: "elm/slider/horizontal/default";
		min: 64 64;

Next we list the images we will be using

		images{
			image: "0000.png" COMP;
			image: "0001.png" COMP;
			image: "0002.png" COMP

                        //Similar for 0003.png to 0059.png.
 
			image: "0060.png" COMP;
			image: "knobbg.png" COMP;
		}

The script block contains global variables and functions. The function update_knob_state is used to load new images when the knob is turned. The variables are used to store the state of the knob.

                script{ 
		     	public knob_pos;
		     	public knob_ref;
		     	public knob_last;
		     	public knob_move;
		     	public signal_from_dragger;


		     	public update_knob_state(Float:frac){

                        new px, py, pw, ph;
                        get_geometry(PART:"knob", px, py, pw, ph);
			new Float:step = ph/60;
				if(frac > ph)     { set_state(PART:"knob", "60", 0.0); return;} 
				if(frac <= 0)     { set_state(PART:"knob", "default", 0.0); return;}
				if(frac < step)   { set_state(PART:"knob", "1", 0.0); return;}
				if(frac < 2*step) { set_state(PART:"knob", "2", 0.0); return;}

                                //Similar "if" statments for frac < 3*step to 59*step.
 
				if(frac < 60*step) { set_state(PART:"knob", "60", 0.0); return;}

		     	}

		     	public reset_dragger(){
		     	        set_drag(PART:"dragger", 0.0, 0.0);
		     	        set_int(signal_from_dragger, 0);
		     	}
     		}

The parts block contains the functional parts of the knob. These are mostly geometric objects that may be moveable and/or visible. They may also emit signals which will be important in the programs block below.

    		parts{
		     	part{
				name: "knobbase";
				scale: 1;
				description{
					state: "default" 0.0;
					min: 64 64;
				     	image.normal: "knobbg.png";
				}
		     	}

Each part has a one or more descriptions, which represent states of the part. Here each state is associated with one image (position) of the knob.

			part{
				name: "knob";
				mouse_events: 0;
				scale: 1;
				description{
					state: "default" 0.0;
					min: 64 64;
					rel.to: "knobbase";
					image.normal: "0000.png";
				}
				description{ 
					state: "1" 0.0;
					inherit: "default" 0.0;
					image.normal: "0001.png";
				}
				description{ 
					state: "2" 0.0;
					inherit: "default" 0.0;
					image.normal: "0002.png";
				}

                                //Descriptions for states "3" to "59" similar.
 
				description { 
					state: "60" 0.0;
					inherit: "default" 0.0;
					image.normal: "0060.png";
				}
			}

The following part is needed so we can set the value of the knob from the C source via elm_slider_value_set.

		        part{
				//The real slider for elm_slider
				name: "elm.dragable.slider";
				scale: 1;
				dragable.x: 1 1 0;
				dragable.y: 1 1 0;
				type: RECT;
				description{
					state: "default" 0.0;
					visible: 0;
				}
				dragable{
					x: 1 1 0;
					y: 1 1 0;
				}
		        }

This part named “dragger” is the actually draggable part of the slider. It's invisible but will measure how much the knob has been turned (or dragged).

			part{ 
				name: "dragger";
				type: RECT;
				description{
					state: "default" 0.0;
				       	rel1.to: "knob";
				       	rel2.to: "knob";
					color: 0 0 0 0;
				}
				description{
					state: "hoki" 0.0;
				       	rel1.to: "knob";
				       	rel2.to: "knob";
					color: 0 0 0 0;
				}
				dragable{
					x: 0 0 0;
					y: 1 1 0;
				}
			}
		}

Now the parts block is finished and we continue with the programs block which contains small scripts which control the behaviour of our knob. The programs are called when a certain signal from a certain source is received. Signals can be emitted from inside this edje or from outside (C source).

     		programs{

The script part of the program named “on_drag_move” runs when the signal “drag” from source “dragger” (the part defined above) is received.

		     	program{
				name: "on_drag_move";
				signal: "drag";
				source: "dragger";
				script{
					new Float:p1;
					new Float:p2;

                                        //The drag value is subtracted from the last knob position
                                        //because a drag upwards yields negative values.

					get_drag(PART:"dragger", p1, p2);
					set_float(knob_pos, get_float(knob_last) - p2);
				
					new px, py, pw, ph;
					get_geometry(PART:"knob", px, py, pw, ph);
					if(get_float(knob_pos) > ph) set_float(knob_pos, ph);
					if(get_float(knob_pos) < 0) set_float(knob_pos, 0);
				
					update_knob_state(get_float(knob_pos));
					new Float:sl_val = get_float(knob_pos)/ph;

					set_int(signal_from_dragger, 1);
					set_drag(PART:"elm.dragable.slider", sl_val , sl_val);
				}
			}

The variable “signal_from_dragger” is used as a guard to distinguish if calls of set_drag for “elm.dragable.slider” are made from within the edje theme (then it is 1), ore from outside, e.g. C source, (then it is 0).

Next are some more programs

			program{
				name: "on_drag_stop";
				signal: "drag,stop";
				source: "dragger";
				script{
					set_float(knob_last, get_float(knob_pos)); 
					reset_dragger();
				}
			}
			program{
				name: "on_wheel";
				signal: "mouse,wheel*";
				source: "dragger";
				//after: "on_slider_set";
   			}
			program{
				signal: "elm,state,enabled"; 
				source: "elm";
					script {
						set_int(signal_from_dragger, 0);
					}
			}

The following program is called when set_drag for elm.dragable.slider is called.

			program{

				signal: "drag,set";
				source: "elm.dragable.slider";
				script {

If we were called because the part “dragger” was dragged, “signal_from_dragger” is 1 and nothing else will happen here except that “signal_from_dragger” will be set to 0.

					if(get_int(signal_from_dragger) == 0){
						new Float:p1;
						new Float:p2;
						get_drag(PART:"elm.dragable.slider", p1, p2);
				
						new px, py, pw, ph;
						get_geometry(PART:"knob", px, py, pw, ph);
						set_float(knob_pos, ph*p1);
				
						if(get_float(knob_pos) > ph) set_float(knob_pos, ph);
						if(get_float(knob_pos) < 0) set_float(knob_pos, 0);
				
						set_float(knob_last, get_float(knob_pos)); 
				
						update_knob_state(get_float(knob_pos));
				
						new Float:sl_val = get_float(knob_pos)/ph;
						set_drag(PART:"elm.dragable.slider", sl_val , sl_val);
				
					}else{
						set_int(signal_from_dragger, 0);
					}
				}
			}
		}