2 Copyright 2009 by Hans Baier, Krzysztof Foltman
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
{
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; }
37 public override bool expose_event (Gdk
.EventExpose event
) {
38 if (image_source
== null) {
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
);
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;
68 // endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
69 if (knob_mode
== KnobMode
.ENDLESS
&& (phase
% 16) == 0) {
72 double nom
= adj
.lower
+ phase
* (adj
.upper
- adj
.lower
) / 64.0;
73 double diff
= (adj
.value
- nom
) / (adj
.upper
- adj
.lower
);
75 phase
= (phase
+ 1) % 64;
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
,
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
);
95 image_source
.paint_knobs (cache_cr
,
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 ());
106 // debug ("exposed %p %d+%d", window, allocation.x, allocation.y);
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 ());
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
);
125 int nsteps
= (int)(0.5f
+ (adj
.upper
- adj
.lower
) / adj
.step_increment
); // less 1 actually
130 if (knob_mode
== KnobMode
.ENDLESS
&& 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
);
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
)
148 set_value (adj
.lower
);
152 set_value (adj
.upper
);
165 start_value
= (int)get_value ();
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 ();
183 public override bool button_press_event (Gdk
.EventButton event
)
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);
195 public override bool button_release_event (Gdk
.EventButton event
)
203 inline
float endless (float value
)
206 return Math
.fmodf (value
, 1.0f
);
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
;
221 if (value
>= (0.5f
- dzw
) && value
<= (0.5f
+ dzw
))
228 public override bool motion_notify_event (Gdk
.EventMotion event
)
230 float scale
= (event
.state
== Gdk
.ModifierType
.SHIFT_MASK
) ?
1000.0f
: 100.0f
;
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
));
241 set_value (start_value
- (event
.y
- start_y
) / scale
);
245 last_y
= (int)event
.y
;
249 public override bool scroll_event (Gdk
.EventScroll event
) {
250 calf_knob_incr (event
.direction
);
255 Adjustment adj
= new
Adjustment(0, 0, 1, 0.01, 0.5, 0);
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
+= () => {
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
);
277 } // namespace Prolooks