added glade icons
[libprolooks.git] / prolooks / Knob.vala
blobf4c9dcab7782dcf74caccef6715e76977cef6228
1 /*
2 Copyright 2009 by Hans Baier, Krzysztof Foltman
3 License: LGPLv2+
4 */
6 using Gtk;
8 namespace Prolooks {
10 static const uint16 GDK_Home = 0xff50;
11 static const uint16 GDK_End = 0xff57;
12 static const uint16 GDK_Up = 0xff52;
13 static const uint16 GDK_Down = 0xff54;
14 static const uint16 GDK_Shift_L = 0xffe1;
15 static const uint16 GDK_Shift_R = 0xffe2;
17 public enum KnobMode {
18 POSITIVE,
19 BIPOLAR,
20 NEGATIVE,
21 ENDLESS
25 public class Knob : Range {
26 private Cairo.Surface? cache_surface = null;
28 private IKnobImageSource _image_source;
29 public IKnobImageSource image_source {
30 get {
31 if (_image_source == null) {
32 image_source = new SimpleKnobImageSource ();
34 return _image_source;
37 set {
38 if (value != null && value != _image_source) {
39 _image_source = value;
40 cache_surface = null;
41 queue_draw ();
46 private KnobMode _knob_mode;
47 public KnobMode knob_mode {
48 get {
49 return _knob_mode;
51 set {
52 if (value != _knob_mode) {
53 _knob_mode = value;
54 cache_surface = null;
55 queue_draw ();
60 double start_value;
61 int start_x;
62 int start_y;
63 int last_y;
65 public Knob () {
66 Adjustment adj = new Adjustment(0, 0, 1, 0.01, 0.5, 0);
67 init (adj);
70 private void init (Adjustment an_adjustment) {
71 requisition.width = (int)image_source.get_knob_width ();
72 requisition.height = (int)image_source.get_knob_height ();
73 set_flags (WidgetFlags.CAN_FOCUS);
74 set_adjustment (an_adjustment);
75 this.value_changed += () => {
76 queue_draw ();
78 add_events (Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.KEY_RELEASE_MASK );
81 public Knob.with_adjustment (Adjustment an_adjustment) {
82 init (an_adjustment);
85 public override bool expose_event (Gdk.EventExpose event) {
86 // Create a Cairo context
87 var cr = Gdk.cairo_create (this.window);
89 // Set clipping area in order to avoid unnecessary drawing
90 cr.rectangle (event.area.x, event.area.y,
91 event.area.width, event.area.height);
92 cr.clip ();
94 Adjustment adj = get_adjustment ();
96 //debug ("adjustment = %p value = %f\n", adj, adj.value);
97 int ox = allocation.x, oy = allocation.y;
99 ox += (int)(allocation.width - image_source.get_knob_width ()) / 2;
100 oy += (int)(allocation.height - image_source.get_knob_height ()) / 2;
102 int phase = (int)((adj.value - adj.lower) * 64 / (adj.upper - adj.lower));
103 // skip middle phase except for true middle value
104 if (knob_mode == KnobMode.BIPOLAR && phase == 32) {
105 double pt = (adj.value - adj.lower) * 2.0 / (adj.upper - adj.lower) - 1.0;
106 if (pt < 0)
107 phase = 31;
108 if (pt > 0)
109 phase = 33;
112 // endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
113 if (knob_mode == KnobMode.ENDLESS && (phase % 16) == 0) {
114 if (phase == 64)
115 phase = 0;
116 double nom = adj.lower + phase * (adj.upper - adj.lower) / 64.0;
117 double diff = (adj.value - nom) / (adj.upper - adj.lower);
118 if (diff > 0.0001)
119 phase = (phase + 1) % 64;
120 if (diff < -0.0001)
121 phase = (phase + 63) % 64;
124 if (cache_surface == null) {
125 // looks like its either first call or the widget has been resized.
126 // create the cache_surface.
127 var cache_width = image_source.get_knob_width () * image_source.phases;
128 var cache_height = image_source.get_knob_height ();
129 cache_surface = new Cairo.Surface.similar (cr.get_target (),
130 Cairo.Content.COLOR_ALPHA,
131 (int)cache_width,
132 (int)cache_height);
134 Cairo.Context cache_cr = new Cairo.Context ( cache_surface );
135 cache_cr.set_source_rgba (0, 0, 0, 0);
136 cache_cr.rectangle (0, 0, cache_width, cache_height);
137 cache_cr.fill ();
139 image_source.paint_knobs (cache_cr,
140 knob_mode,
141 image_source.get_knob_width () / 2,
142 image_source.get_knob_height () / 2);
145 assert (cache_surface != null);
146 cr.set_source_surface (cache_surface, ox - phase * image_source.get_knob_width (), oy);
147 cr.rectangle (ox, oy, image_source.get_knob_width (), image_source.get_knob_height ());
148 cr.fill ();
150 // debug ("exposed %p %d+%d", window, allocation.x, allocation.y);
151 if (is_focus) {
152 Gtk.paint_focus (style, window, StateType.NORMAL, null, this, null, ox, oy,
153 (int)image_source.get_knob_width (), (int)image_source.get_knob_height ());
156 return true;
159 public override void size_request (out Gtk.Requisition requisition) {
160 requisition.width = (int)image_source.get_knob_width ();
161 requisition.height = (int)image_source.get_knob_height ();
164 void calf_knob_incr (int dir_down) {
165 Adjustment adj = get_adjustment ();
167 int oldstep = (int)(0.5f + (adj.value - adj.lower) / adj.step_increment);
168 int step;
169 int nsteps = (int)(0.5f + (adj.upper - adj.lower) / adj.step_increment); // less 1 actually
170 if (dir_down != 0)
171 step = oldstep - 1;
172 else
173 step = oldstep + 1;
174 if (knob_mode == KnobMode.ENDLESS && step >= nsteps)
175 step %= nsteps;
176 if (knob_mode == KnobMode.ENDLESS && step < 0)
177 step = nsteps - (nsteps - step) % nsteps;
179 // trying to reduce error cumulation here, by counting from lowest or from highest
180 float value = (float) (adj.lower + step * (double) (adj.upper - adj.lower) / nsteps);
181 set_value (value);
182 // debug ("step %d:%d nsteps %d value %f:%f\n", oldstep, step, nsteps, oldvalue, value);
186 public override bool key_press_event (Gdk.EventKey event) {
187 Adjustment adj = get_adjustment ();
189 switch (event.keyval)
191 case GDK_Home:
192 set_value (adj.lower);
193 return true;
195 case GDK_End:
196 set_value (adj.upper);
197 return true;
199 case GDK_Up:
200 calf_knob_incr (0);
201 return true;
203 case GDK_Down:
204 calf_knob_incr (1);
205 return true;
207 case GDK_Shift_L:
208 case GDK_Shift_R:
209 start_value = (int)get_value ();
210 start_y = last_y;
211 return true;
214 return false;
217 public override bool key_release_event (Gdk.EventKey event) {
218 if (event.keyval == GDK_Shift_L || event.keyval == GDK_Shift_R) {
219 start_value = (int)get_value ();
220 start_y = last_y;
221 return true;
224 return false;
227 public override bool button_press_event (Gdk.EventButton event)
229 grab_focus ();
230 grab_add (this);
231 start_x = (int)event.x;
232 start_y = (int)event.y;
233 start_value = get_value ();
234 //debug ("PRESS: start_value: %f, event.y: %d, start_y: %d", start_value, (int)event.y, (int)start_y);
236 return true;
239 public override bool button_release_event (Gdk.EventButton event)
241 if (has_grab ()) {
242 grab_remove (this);
244 return false;
247 inline float endless (float value)
249 if (value >= 0.0f)
250 return Math.fmodf (value, 1.0f);
251 else
252 return Math.fmodf (1.0f - Math.fmodf (1.0f - value, 1.0f), 1.0f);
255 inline float deadzone (float value, float incr, float scale)
257 float dzw = 10.0f / scale;
258 if (value >= 0.501f)
259 value += dzw;
260 if (value < 0.499f)
261 value -= dzw;
263 value += incr;
265 if (value >= (0.5f - dzw) && value <= (0.5f + dzw))
266 return 0.5f;
267 if (value < 0.5f)
268 return value + dzw;
269 return value - dzw;
272 public override bool motion_notify_event (Gdk.EventMotion event)
274 float scale = (event.state == Gdk.ModifierType.SHIFT_MASK) ? 1000.0f : 100.0f;
275 bool moved = false;
277 if (has_grab ())
279 //debug ("start_value: %d, event.y: %d, start_y: %d", (int)start_value, (int)event.y, (int)start_y);
280 if (knob_mode == KnobMode.ENDLESS) {
281 set_value (endless ((float)(start_value - (event.y - start_y) / scale)));
282 } else if (knob_mode == KnobMode.BIPOLAR) {
283 set_value (deadzone ((float)start_value, (float)(-(event.y - start_y) / scale), (float)scale));
284 } else {
285 set_value (start_value - (event.y - start_y) / scale);
287 moved = true;
289 last_y = (int)event.y;
290 return moved;
293 public override bool scroll_event (Gdk.EventScroll event) {
294 calf_knob_incr (event.direction);
295 return true;
298 } // class Knob
300 } // namespace Prolooks