Knob: instantiate SimpleKnobImageSource by default
[libprolooks.git] / prolooks / Knob.vala
blob81be589d68bbd06b7339e49c0ecdd57bfcfbe81f
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,
22 NUM_MODES
26 public class Knob : Range {
27 private Cairo.Surface? cache_surface = null;
29 public IKnobImageSource image_source { get; set; }
31 public KnobMode knob_mode { get; set; }
32 double start_value;
33 int start_x;
34 int start_y;
35 int last_y;
37 public override bool expose_event (Gdk.EventExpose event) {
38 if (image_source == null) {
39 return false;
42 // Create a Cairo context
43 var cr = Gdk.cairo_create (this.window);
45 // Set clipping area in order to avoid unnecessary drawing
46 cr.rectangle (event.area.x, event.area.y,
47 event.area.width, event.area.height);
48 cr.clip ();
50 Adjustment adj = get_adjustment ();
52 //debug ("adjustment = %p value = %f\n", adj, adj.value);
53 int ox = allocation.x, oy = allocation.y;
55 ox += (int)(allocation.width - image_source.get_knob_width ()) / 2;
56 oy += (int)(allocation.height - image_source.get_knob_height ()) / 2;
58 int phase = (int)((adj.value - adj.lower) * 64 / (adj.upper - adj.lower));
59 // skip middle phase except for true middle value
60 if (knob_mode == KnobMode.BIPOLAR && phase == 32) {
61 double pt = (adj.value - adj.lower) * 2.0 / (adj.upper - adj.lower) - 1.0;
62 if (pt < 0)
63 phase = 31;
64 if (pt > 0)
65 phase = 33;
68 // endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
69 if (knob_mode == KnobMode.ENDLESS && (phase % 16) == 0) {
70 if (phase == 64)
71 phase = 0;
72 double nom = adj.lower + phase * (adj.upper - adj.lower) / 64.0;
73 double diff = (adj.value - nom) / (adj.upper - adj.lower);
74 if (diff > 0.0001)
75 phase = (phase + 1) % 64;
76 if (diff < -0.0001)
77 phase = (phase + 63) % 64;
80 if (cache_surface == null) {
81 // looks like its either first call or the widget has been resized.
82 // create the cache_surface.
83 var cache_width = image_source.get_knob_width () * image_source.phases;
84 var cache_height = image_source.get_knob_height ();
85 cache_surface = new Cairo.Surface.similar (cr.get_target (),
86 Cairo.Content.COLOR_ALPHA,
87 (int)cache_width,
88 (int)cache_height);
90 Cairo.Context cache_cr = new Cairo.Context ( cache_surface );
91 cache_cr.set_source_rgba (0, 0, 0, 0);
92 cache_cr.rectangle (0, 0, cache_width, cache_height);
93 cache_cr.fill ();
95 image_source.paint_knobs (cache_cr,
96 knob_mode,
97 image_source.get_knob_width () / 2,
98 image_source.get_knob_height () / 2);
101 assert (cache_surface != null);
102 cr.set_source_surface (cache_surface, ox - phase * image_source.get_knob_width (), oy);
103 cr.rectangle (ox, oy, image_source.get_knob_width (), image_source.get_knob_height ());
104 cr.fill ();
106 // debug ("exposed %p %d+%d", window, allocation.x, allocation.y);
107 if (is_focus) {
108 Gtk.paint_focus (style, window, StateType.NORMAL, null, this, null, ox, oy,
109 (int)image_source.get_knob_width (), (int)image_source.get_knob_height ());
112 return true;
115 public override void size_request (out Gtk.Requisition requisition) {
116 requisition.width = (int)image_source.get_knob_width ();
117 requisition.height = (int)image_source.get_knob_height ();
120 void calf_knob_incr (int dir_down) {
121 Adjustment adj = get_adjustment ();
123 int oldstep = (int)(0.5f + (adj.value - adj.lower) / adj.step_increment);
124 int step;
125 int nsteps = (int)(0.5f + (adj.upper - adj.lower) / adj.step_increment); // less 1 actually
126 if (dir_down != 0)
127 step = oldstep - 1;
128 else
129 step = oldstep + 1;
130 if (knob_mode == KnobMode.ENDLESS && step >= nsteps)
131 step %= nsteps;
132 if (knob_mode == KnobMode.ENDLESS && step < 0)
133 step = nsteps - (nsteps - step) % nsteps;
135 // trying to reduce error cumulation here, by counting from lowest or from highest
136 float value = (float) (adj.lower + step * (double) (adj.upper - adj.lower) / nsteps);
137 set_value (value);
138 // debug ("step %d:%d nsteps %d value %f:%f\n", oldstep, step, nsteps, oldvalue, value);
142 public override bool key_press_event (Gdk.EventKey event) {
143 Adjustment adj = get_adjustment ();
145 switch (event.keyval)
147 case GDK_Home:
148 set_value (adj.lower);
149 return true;
151 case GDK_End:
152 set_value (adj.upper);
153 return true;
155 case GDK_Up:
156 calf_knob_incr (0);
157 return true;
159 case GDK_Down:
160 calf_knob_incr (1);
161 return true;
163 case GDK_Shift_L:
164 case GDK_Shift_R:
165 start_value = (int)get_value ();
166 start_y = last_y;
167 return true;
170 return false;
173 public override bool key_release_event (Gdk.EventKey event) {
174 if (event.keyval == GDK_Shift_L || event.keyval == GDK_Shift_R) {
175 start_value = (int)get_value ();
176 start_y = last_y;
177 return true;
180 return false;
183 public override bool button_press_event (Gdk.EventButton event)
185 grab_focus ();
186 grab_add (this);
187 start_x = (int)event.x;
188 start_y = (int)event.y;
189 start_value = get_value ();
190 //debug ("PRESS: start_value: %f, event.y: %d, start_y: %d", start_value, (int)event.y, (int)start_y);
192 return true;
195 public override bool button_release_event (Gdk.EventButton event)
197 if (has_grab ()) {
198 grab_remove (this);
200 return false;
203 inline float endless (float value)
205 if (value >= 0.0f)
206 return Math.fmodf (value, 1.0f);
207 else
208 return Math.fmodf (1.0f - Math.fmodf (1.0f - value, 1.0f), 1.0f);
211 inline float deadzone (float value, float incr, float scale)
213 float dzw = 10.0f / scale;
214 if (value >= 0.501f)
215 value += dzw;
216 if (value < 0.499f)
217 value -= dzw;
219 value += incr;
221 if (value >= (0.5f - dzw) && value <= (0.5f + dzw))
222 return 0.5f;
223 if (value < 0.5f)
224 return value + dzw;
225 return value - dzw;
228 public override bool motion_notify_event (Gdk.EventMotion event)
230 float scale = (event.state == Gdk.ModifierType.SHIFT_MASK) ? 1000.0f : 100.0f;
231 bool moved = false;
233 if (has_grab ())
235 //debug ("start_value: %d, event.y: %d, start_y: %d", (int)start_value, (int)event.y, (int)start_y);
236 if (knob_mode == KnobMode.ENDLESS) {
237 set_value (endless ((float)(start_value - (event.y - start_y) / scale)));
238 } else if (knob_mode == KnobMode.BIPOLAR) {
239 set_value (deadzone ((float)start_value, (float)(-(event.y - start_y) / scale), (float)scale));
240 } else {
241 set_value (start_value - (event.y - start_y) / scale);
243 moved = true;
245 last_y = (int)event.y;
246 return moved;
249 public override bool scroll_event (Gdk.EventScroll event) {
250 calf_knob_incr (event.direction);
251 return true;
254 public Knob () {
255 Adjustment adj = new Adjustment(0, 0, 1, 0.01, 0.5, 0);
256 init (adj);
259 private void init (Adjustment an_adjustment) {
260 image_source = new SimpleKnobImageSource ();
261 requisition.width = 40;
262 requisition.height = 40;
263 set_flags (WidgetFlags.CAN_FOCUS);
264 set_adjustment (an_adjustment);
265 this.value_changed += () => {
266 queue_draw ();
268 add_events (Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.KEY_RELEASE_MASK );
271 public Knob.with_adjustment (Adjustment an_adjustment) {
272 init (an_adjustment);
275 } // class Knob
277 } // namespace Prolooks