1
/* -*- Mode: vala; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */
3
* Copyright (C) 2010 Canonical Ltd
5
* This program is free software: you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License version 3 as
7
* published by the Free Software Foundation.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
14
* You should have received a copy of the GNU General Public License
15
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17
* Authored by Gordon Allott <gord.allott@canonical.com>
21
namespace Unity.Launcher
23
/* describes the current phase of animation for the scroller */
24
private enum ScrollerPhase
26
PANNING, // normal moving ar ound
27
SETTLING, // slow settling from the current position to the next one
28
REORDERING, // reordering items
29
FLUNG, // flying around uncontrolled
30
BOUNCE, // bouncing back to a position
34
private enum ScrollerViewType
40
// sucks that we have to use a class, obviously slower than we need it to be
41
// but vala doesn't work with structs+gee right now.
42
private class ChildTransition
44
public float position;
45
public float rotation;
48
class ScrollerView : Ctk.Actor
50
private bool disable_child_events = false;
51
// please don't reference this outside of this view, its only public for construct
52
public ScrollerModel model {get; construct;}
53
public Ctk.EffectCache cache {get; construct;}
55
public bool drag_indicator_active {get; set;}
56
public bool drag_indicator_space {get; set;}
57
public int drag_indicator_index {get; set;}
58
public float drag_indicator_opacity {get; set;}
59
private float drag_indicator_position = 0.0f;
62
/* our scroller constants */
63
public int spacing = 6;
64
public int drag_sensitivity = 7;
65
public float friction = 0.9f;
67
// if this is true, we don't want to be doing any gpu intensive stuff
68
// until the scroller is not animating again
69
public bool is_animating = false;
73
private ThemeImage bgtex;
74
private ThemeImage top_shadow;
76
* state tracking variables
78
private bool button_down = false;
79
private float total_child_height = 0.0f;
80
private ScrollerPhase current_phase = ScrollerPhase.SETTLING;
81
private uint last_motion_event_time = 0;
82
private ScrollerViewType view_type = ScrollerViewType.CONTRACTED;
83
private bool do_logic_pick = true;
84
private float last_known_pointer_x = 0.0f;
85
private bool can_scroll = false;
87
private float last_known_x = 0;
88
private float last_known_y = 0;
93
private bool is_scrolling; //set to true when the user is physically scrolling
94
private float scroll_position = 0.0f;
95
private float settle_position = 0.0f; // when we calculate the settle position for animation, we store it here
97
public bool is_autoscrolling {get; set;} //set to true when the mouse is at the top/bottom
98
private bool autoscroll_anim_active = false;
99
private int autoscroll_direction = 0;
101
public Clutter.Timeline fling_timeline;
103
public Clutter.Timeline autoscroll_timeline;
105
private float previous_y_position = 0.0f; // the last known y position of the pointer
106
private uint previous_y_time = 0; // the time (ms) that previous_y_position was set
107
private uint stored_delta = 0;
108
private float scroll_speed = 0.0f; // the current speed (pixels/per second) that we are scrolling
110
private float contract_icon_degrees = 70.0f;
111
private float contract_icon_partial_degrees = 30.0f;
112
private int focused_launcher = 0;
114
/* helps out with draw order */
115
private Gee.ArrayList<ScrollerChild> draw_ftb;
116
private Gee.ArrayList<ScrollerChild> draw_btf;
118
/* Key binding indicators */
119
private Gee.ArrayList<Clutter.CairoTexture> keyboard_indicators;
124
private Gee.ArrayList<ScrollerChild> child_refs; // we sometimes need to hold a reference to a child
126
public ScrollerView (ScrollerModel _model, Ctk.EffectCache _cache)
128
Object (model:_model, cache:_cache);
133
motion_event.connect (passthrough_motion_event);
134
button_press_event.connect (passthrough_button_press_event);
135
button_release_event.connect (passthrough_button_release_event);
137
Unity.Testing.ObjectRegistry.get_default ().register ("LauncherScrollerView", this);
138
var mypadding = this.padding;
140
mypadding.left = 0.0f;
141
mypadding.right = 0.0f;
142
mypadding.top = 10.0f;
143
mypadding.bottom = 5.0f;
145
this.padding = mypadding;
147
keyboard_indicators = new Gee.ArrayList <Clutter.CairoTexture> ();
149
Clutter.Color color = Clutter.Color () {
156
notify["drag-indicator-active"].connect (on_drag_indicator_active_change);
157
notify["drag-indicator-index"].connect (on_drag_indicator_index_change);
158
notify["drag-indicator-space"].connect (on_drag_indicator_space_change);
160
model.child_added.connect (model_child_added);
161
model.child_removed.connect (model_child_removed);
162
model.order_changed.connect (model_order_changed);
164
// we need to go through our model and add all the items that are currently
166
foreach (ScrollerChild child in model)
168
model_child_added (child);
171
//connect up our clutter signals
172
button_press_event.connect (on_button_press_event);
173
button_release_event.connect (on_button_release_event);
174
motion_event.connect (on_motion_event);
175
enter_event.connect (on_enter_event);
176
scroll_event.connect (on_scroll_event);
178
leave_event.connect (on_leave_event);
179
notify["is-autoscrolling"].connect (on_auto_scrolling_state_change);
180
Unity.Drag.Controller.get_default ().drag_motion.connect (on_drag_motion_event);
182
// set a timeline for our fling animation
183
fling_timeline = new Clutter.Timeline (1000);
184
fling_timeline.loop = true;
185
fling_timeline.new_frame.connect (this.on_scroller_frame);
186
fling_timeline.started.connect (() => {
187
cache.invalidate_texture_cache ();
189
fling_timeline.completed.connect (() => {
190
Timeout.add (0, () => {
191
cache.update_texture_cache ();
196
// set a timeline for our auto-scroll animation
197
autoscroll_timeline = new Clutter.Timeline (1000);
198
autoscroll_timeline.loop = true;
199
autoscroll_timeline.new_frame.connect (this.on_autoscroll_frame);
200
autoscroll_timeline.started.connect (() => {
201
cache.invalidate_texture_cache ();
204
autoscroll_timeline.completed.connect (() => {
205
Timeout.add (0, () => {
206
cache.update_texture_cache ();
211
//on drag start we need to disengage our own drag attempts
212
var drag_controller = Drag.Controller.get_default ();
213
drag_controller.drag_start.connect (() => {
214
Unity.global_shell.remove_fullscreen_request (this);
215
is_scrolling = false;
217
Unity.Launcher.disable_quicklists = true;
218
Clutter.ungrab_pointer ();
219
get_stage ().motion_event.disconnect (on_motion_event);
220
current_phase = ScrollerPhase.FLUNG;
221
fling_timeline.start ();
223
animate (Clutter.AnimationMode.EASE_OUT_SINE, 150,
224
"drag-indicator-opacity", 1.0f);
227
drag_controller.drag_drop.connect ((drag_model, x, y) => {
228
Unity.Launcher.disable_quicklists = false;
229
foreach (Clutter.Actor child in model)
231
child.set_reactive (false);
233
if (x > get_width ()) contract_launcher ();
237
order_children (false);
240
animate (Clutter.AnimationMode.EASE_OUT_SINE, 150,
241
"drag-indicator-opacity", 0.0f);
244
drag_controller.drag_motion.connect ((model, x, y) => {
245
last_known_pointer_x = x;
246
if (x > 200 + get_width ()) contract_launcher ();
247
else expand_launcher (y);
252
child_refs = new Gee.ArrayList <ScrollerChild> ();
253
order_children (true);
256
order_children (true);
260
drag_indicator_space = false;
264
/* hoo-boy this sucks. because of mutter and clutter issues, i have to set
265
* all my children to be non reactive. then i need to go through and
266
* send events to the correct children myself, by-passing clutter
267
* all this so i can accurately know if a mouse-leave event on the launcher
270
private Clutter.Actor? last_picked_actor = null;
271
private Clutter.Actor? handle_event (Clutter.Event event, bool assume_on_launcher=false)
273
if (disable_child_events)
276
if ((last_picked_actor is Clutter.Actor) == false)
277
last_picked_actor = null;
280
foreach (Clutter.Actor actor in model)
282
actor.set_reactive (true);
287
event.get_coords (out x, out y);
290
if (assume_on_launcher)
293
Clutter.Actor picked_actor = (get_stage () as Clutter.Stage).get_actor_at_pos (Clutter.PickMode.REACTIVE, (int)x, (int)y);
296
foreach (Clutter.Actor actor in model)
298
actor.set_reactive (false);
302
if (picked_actor is Clutter.Actor)
304
// we now have a picked actor, figure out what event to send it
305
if (last_picked_actor != picked_actor)
307
Clutter.Event crossing_event = { 0 };
308
crossing_event.type = Clutter.EventType.LEAVE;
309
crossing_event.crossing.x = x;
310
crossing_event.crossing.y = y;
311
crossing_event.crossing.stage = get_stage () as Clutter.Stage;
312
crossing_event.crossing.flags = Clutter.EventFlags.FLAG_SYNTHETIC;
314
if (last_picked_actor is Clutter.Actor)
315
last_picked_actor.do_event (crossing_event, false);
317
crossing_event.type = Clutter.EventType.ENTER;
318
picked_actor.do_event (crossing_event, false);
321
else if (last_picked_actor is Clutter.Actor)
323
// if picked_actor is null, then we want to send a leave event on the
326
Clutter.Event crossing_event = { 0 };
327
crossing_event.type = Clutter.EventType.LEAVE;
328
crossing_event.crossing.x = x;
329
crossing_event.crossing.y = y;
330
crossing_event.crossing.stage = get_stage () as Clutter.Stage;
331
crossing_event.crossing.flags = Clutter.EventFlags.FLAG_SYNTHETIC;
333
last_picked_actor.do_event (crossing_event, false);
336
last_picked_actor = picked_actor;
340
private bool passthrough_motion_event (Clutter.Event event)
342
var drag_controller = Drag.Controller.get_default ();
343
if (drag_controller.is_dragging) return false;
344
enter_event.disconnect (on_enter_event);
345
leave_event.disconnect (on_leave_event);
346
motion_event.disconnect (on_motion_event);
347
motion_event.disconnect (passthrough_motion_event);
350
get_stage ().motion_event.disconnect (on_motion_event);
353
Clutter.Actor picked_actor = handle_event (event, is_scrolling);
355
if (picked_actor is Clutter.Actor)
356
picked_actor.do_event (event, false);
358
enter_event.connect (on_enter_event);
359
leave_event.connect (on_leave_event);
360
motion_event.connect (on_motion_event);
361
motion_event.connect (passthrough_motion_event);
364
get_stage ().motion_event.connect (on_motion_event);
369
private bool passthrough_button_press_event (Clutter.Event event)
371
var drag_controller = Drag.Controller.get_default ();
372
if (drag_controller.is_dragging) return false;
374
enter_event.disconnect (on_enter_event);
375
leave_event.disconnect (on_leave_event);
376
button_press_event.disconnect (passthrough_button_press_event);
377
Clutter.Actor picked_actor = handle_event (event, is_scrolling);
378
if (picked_actor is Clutter.Actor)
379
picked_actor.do_event (event, false);
381
enter_event.connect (on_enter_event);
382
leave_event.connect (on_leave_event);
383
button_press_event.connect (passthrough_button_press_event);
387
private bool passthrough_button_release_event (Clutter.Event event)
389
var drag_controller = Drag.Controller.get_default ();
390
if (drag_controller.is_dragging) return false;
392
enter_event.disconnect (on_enter_event);
393
leave_event.disconnect (on_leave_event);
394
button_release_event.disconnect (passthrough_button_release_event);
395
Clutter.Actor picked_actor = handle_event (event, is_scrolling);
396
if (picked_actor is Clutter.Actor)
397
picked_actor.do_event (event, false);
399
enter_event.connect (on_enter_event);
400
leave_event.connect (on_leave_event);
401
button_release_event.connect (passthrough_button_release_event);
405
private void on_drag_indicator_space_change ()
407
if (drag_indicator_active)
409
if (drag_indicator_space)
411
animate (Clutter.AnimationMode.EASE_OUT_SINE, 150,
412
"drag-indicator-opacity", 0.0f);
413
order_children (false);
418
animate (Clutter.AnimationMode.EASE_OUT_SINE, 150,
419
"drag-indicator-opacity", 1.0f);
420
order_children (false);
425
animate (Clutter.AnimationMode.EASE_OUT_SINE, 150,
426
"drag-indicator-opacity", 1.0f);
427
order_children (false);
431
private void on_drag_indicator_active_change ()
433
on_drag_indicator_space_change ();
436
private void on_drag_indicator_index_change ()
438
order_children (false);
442
private float last_scroll_position = 0.0f;
443
public void enable_keyboard_selection_mode (bool choice)
446
last_scroll_position = scroll_position;
448
uint8 new_opacity = (choice) ? 0xff : 0x00;
451
foreach (Clutter.CairoTexture kb_ind in keyboard_indicators)
453
kb_ind.animate (Clutter.AnimationMode.EASE_OUT_SINE, 150,
454
"opacity", new_opacity);
455
if (model.size <= index) new_opacity = 0x00;
462
if (view_type != ScrollerViewType.CONTRACTED &&
463
last_known_pointer_x >= get_width ())
465
foreach (ScrollerChild child in model)
469
focused_launcher = model.index_of (child);
473
contract_launcher ();
475
else if (last_known_pointer_x < get_width ())
477
move_scroll_position (last_scroll_position - scroll_position);
488
public int get_model_index_at_y_pos_no_anim (float y, bool return_minus_if_fail=false)
490
SList<float?> positions = new SList<float?> ();
491
foreach (ScrollerChild child in model)
493
positions.append (child.position);
494
GLib.Value value = Value (typeof (float));
495
Clutter.Animation anim = child.get_animation ();
496
if (anim is Clutter.Animation)
498
Clutter.Interval interval = anim.get_interval ("position");
499
if (interval is Clutter.Interval)
501
interval.get_final_value (value);
502
child.position = value.get_float ();
507
int value = get_model_index_at_y_pos (y, return_minus_if_fail);
509
unowned SList<float?> list = positions;
510
foreach (ScrollerChild child in model)
512
child.position = (float)list.data;
516
ScrollerChild child = (Drag.Controller.get_default ().get_drag_model () as ScrollerChildController).child;
518
//value = model.clamp (child, value);
523
public int get_model_index_at_y_pos (float y, bool return_minus_if_fail=false)
526
return get_model_index_at_y_pos_pick (y, return_minus_if_fail);
528
return get_model_index_at_y_pos_logic (y, return_minus_if_fail);
531
private int get_model_index_at_y_pos_logic (float y, bool return_minus_if_fail=false)
533
foreach (ScrollerChild child in model)
535
if (child.position + padding.top + child.get_height () > y)
536
return model.index_of (child as ScrollerChild);
539
if (return_minus_if_fail)
542
return (y < padding.top + model[0].get_height () + spacing) ? 0 : model.size -1;
546
private int get_model_index_at_y_pos_pick(float y, bool return_minus_if_fail=false)
548
// trying out a different method
550
foreach (ScrollerChild actor in model)
552
if (!actor.do_not_render)
553
actor.set_reactive (true);
556
Clutter.Actor picked_actor = (get_stage () as Clutter.Stage).get_actor_at_pos (Clutter.PickMode.REACTIVE, 25, iy);
559
if (picked_actor is ScrollerChild == false)
561
// we didn't pick a scroller child. lets pick below us
562
picked_actor = (get_stage () as Clutter.Stage).get_actor_at_pos (Clutter.PickMode.REACTIVE, 25, iy - 24);
564
if (picked_actor is ScrollerChild == false)
566
// again nothing good! lets try again above us
567
picked_actor = (get_stage () as Clutter.Stage).get_actor_at_pos (Clutter.PickMode.REACTIVE, 25, iy + 24);
568
if (picked_actor is ScrollerChild == false)
570
if (return_minus_if_fail)
572
// couldn't pick a single actor, return 0
573
ret_val = (y < padding.top + model[0].get_height () + spacing) ? 0 : model.size -1 ;
579
ret_val = model.index_of (picked_actor as ScrollerChild);
581
foreach (Clutter.Actor actor in model)
583
actor.set_reactive (false);
589
private void draw_keyboard_indicator_cairo (Cairo.Context cr, string text)
595
double r = Ctk.em_to_pixel (0.7f);
597
Pango.Layout layout = Pango.cairo_create_layout (cr);
598
Gtk.Settings settings = Gtk.Settings.get_default ();
599
Pango.FontDescription desc = Pango.FontDescription.from_string (settings.gtk_font_name);
600
desc.set_weight (Pango.Weight.NORMAL);
601
layout.set_font_description (desc);
602
layout.set_text (text, -1);
603
Pango.Context pango_context = layout.get_context ();
604
Gdk.Screen screen = Gdk.Screen.get_default ();
605
Pango.cairo_context_set_font_options (pango_context,
606
screen.get_font_options ());
607
Pango.cairo_context_set_resolution (pango_context,
608
(float) settings.gtk_xft_dpi /
609
(float) Pango.SCALE);
610
layout.context_changed ();
613
Pango.Rectangle log_rect;
614
layout.get_extents (null, out log_rect);
615
text_width = log_rect.width / Pango.SCALE;
616
text_height = log_rect.height / Pango.SCALE;
620
cr.set_source_rgba (0.07, 0.07, 0.07, 0.8);
622
cr.move_to(x+r,y); // Move to A
623
cr.line_to(x+w-r,y); // Straight line to B
624
cr.curve_to(x+w,y,x+w,y,x+w,y+r); // Curve to C, Control points are both at Q
625
cr.line_to(x+w,y+h-r); // Move to D
626
cr.curve_to(x+w,y+h,x+w,y+h,x+w-r,y+h); // Curve to E
627
cr.line_to(x+r,y+h); // Line to F
628
cr.curve_to(x,y+h,x,y+h,x,y+h-r); // Curve to G
629
cr.line_to(x,y+r); // Line to H
630
cr.curve_to(x,y,x,y,x+r,y); // Curve to
634
cr.set_source_rgba (1, 1, 1, 1);
637
cr.move_to (x + (w - text_width) * 0.5,
638
y + (h - text_height) * 0.5);
639
Pango.cairo_show_layout (cr, layout);
645
private void load_textures ()
647
bgtex = new ThemeImage ("launcher_background_middle");
648
bgtex.set_repeat (true, true);
649
bgtex.set_parent (this);
651
top_shadow = new ThemeImage ("overflow_top");
652
top_shadow.set_repeat (true, false);
653
top_shadow.set_parent (this);
655
var color = Clutter.Color () {
663
// indicator size find out activate!
664
int key_indicator_w, key_indicator_h;
665
Gtk.Settings settings = Gtk.Settings.get_default ();
667
Unity.QuicklistRendering.get_text_extents (settings.gtk_font_name, "2",
668
out key_indicator_w, out key_indicator_h);
670
key_indicator_w += 10;
671
key_indicator_h += 10;
673
for (; index <= 10; index++)
675
var keyboard_indicator = new Clutter.CairoTexture (key_indicator_w, key_indicator_h);
676
keyboard_indicator.set_parent (this);
677
keyboard_indicator.opacity = 0x00;
679
keyboard_indicator.set_surface_size (key_indicator_w, key_indicator_h);
680
keyboard_indicator.set_size (key_indicator_w, key_indicator_h);
681
keyboard_indicator.clear ();
683
Cairo.Context cr = keyboard_indicator.create ();
685
string ind_str = index.to_string ();
689
draw_keyboard_indicator_cairo (cr, ind_str);
691
keyboard_indicators.add (keyboard_indicator);
695
// will move the scroller by the given pixels
696
private float calculate_scroll_position (bool check_bounds=false, float limit = 160.0f)
698
float new_scroll_position = scroll_position;
701
new_scroll_position = Math.fminf (new_scroll_position, 0);
702
new_scroll_position = Math.fmaxf (new_scroll_position, - (get_total_children_height () - get_available_height ()));
704
else if (new_scroll_position > 0)
706
new_scroll_position = limit * ( 1 - Math.powf ((limit - 1) / limit, new_scroll_position));
708
else if (get_total_children_height () < get_available_height () &&
709
new_scroll_position < 0)
711
new_scroll_position = -new_scroll_position;
712
new_scroll_position = limit * ( 1 - Math.powf ((limit - 1) / limit, new_scroll_position));
713
new_scroll_position = -new_scroll_position;
715
else if (get_total_children_height () >= get_available_height () &&
716
new_scroll_position < -(get_total_children_height () - get_available_height ()))
718
float diff = new_scroll_position + (get_total_children_height () - get_available_height ());
719
new_scroll_position = limit * ( 1 - Math.powf ((limit - 1) / limit, Math.fabsf (diff)));
720
new_scroll_position = -(get_total_children_height () - get_available_height ()) - new_scroll_position;
723
return new_scroll_position;
726
private void move_scroll_position (float pixels, bool check_bounds=false, float limit = 160.0f)
728
scroll_position += pixels;
729
float old_scroll_position = scroll_position;
731
scroll_position = calculate_scroll_position (check_bounds, limit);
733
order_children (true);
736
scroll_position = old_scroll_position;
739
/* disables animations and events on children so that they don't
740
* get in the way of our scroller interactions
742
private void disable_animations_on_children (Clutter.Event event)
745
disable_child_events = true;
747
Clutter.Event e = { 0 };
748
e.type = Clutter.EventType.LEAVE;
749
e.crossing.time = event.motion.time;
750
e.crossing.flags = event.motion.flags;
751
e.crossing.stage = event.motion.stage;
752
e.crossing.x = event.motion.x;
753
e.crossing.y = event.motion.y;
755
foreach (ScrollerChild child in model)
757
if (child is Clutter.Actor)
759
e.crossing.source = child;
760
child.do_event (e, false);
767
private void expand_launcher (float absolute_y)
769
if (view_type == ScrollerViewType.EXPANDED) return;
770
view_type = ScrollerViewType.EXPANDED;
772
// we need to set a new scroll position
773
// get the index of the icon we are hovering over
774
if (get_total_children_height () > get_available_height ())
776
do_logic_pick = false;
777
int index = get_model_index_at_y_pos (absolute_y);
779
// set our state to what we will end up being so we can find the correct
781
float contracted_position = model[index].position;
782
var old_scroll_position = scroll_position;
784
order_children (true);
786
float new_scroll_position = -(model[index].position - contracted_position);
788
//reset our view so that we animate cleanly to the new view
789
view_type = ScrollerViewType.CONTRACTED;
790
scroll_position = old_scroll_position;
791
order_children (true);
793
// and finally animate to the new view
794
view_type = ScrollerViewType.EXPANDED;
796
scroll_position = new_scroll_position;
797
order_children (false); // have to order twice, boo
800
do_logic_pick = true;
805
private void contract_launcher ()
807
if (view_type == ScrollerViewType.CONTRACTED) return;
809
foreach (ScrollerChild child in model)
812
focused_launcher = model.index_of (child);
815
view_type = ScrollerViewType.CONTRACTED;
816
order_children (false);
818
is_autoscrolling = false;
822
* model signal connections
824
private void model_child_added (ScrollerChild child)
827
child.set_parent (this);
829
// we only animate if the added child is not at the end
830
float[] prev_positions = {};//new float [model.size];
831
float[] prev_rotations = {};//new float [model.size];
833
foreach (ScrollerChild modelchild in model)
835
prev_positions += modelchild.position;
836
prev_rotations += modelchild.rotation;
838
order_children (true);
841
foreach (ScrollerChild modelchild in model)
843
if (child != modelchild)
845
change_child_position_rotation (modelchild,
846
prev_positions[index],
847
prev_rotations[index],
853
order_children (false);
855
child.notify["position"].connect (() => {
859
child.set_reactive (false);
862
private void model_child_removed (ScrollerChild child)
864
if (child in draw_btf) draw_btf.remove (child);
865
if (child in draw_ftb) draw_ftb.remove (child);
867
var drag_controller = Drag.Controller.get_default ();
868
if (drag_controller.is_dragging)
870
order_children (false);
875
child_refs.add (child); // we need to keep a reference on it for now
876
var anim = child.animate (Clutter.AnimationMode.EASE_OUT_QUAD,
880
var icon_scale_anim = child.processed_icon.animate (Clutter.AnimationMode.EASE_OUT_QUAD,
884
anim.completed.connect (() => {
886
child_refs.remove (child);
887
order_children (false);
893
private void model_order_changed ()
895
order_children (false);
899
private uint autoscroll_stored_delta = 0;
900
private void on_autoscroll_frame (Clutter.Timeline timeline, int msecs)
902
/* using a clutter animation to handle auto-scroll now
903
* doesn't fit the formula from design properly but thats just a fact
904
* of the animation systems being different
906
uint delta = timeline.get_delta ();
907
delta += autoscroll_stored_delta;
910
autoscroll_stored_delta = delta;
918
if (autoscroll_mouse_pos_cache < 0)
919
speed = Math.fabsf (-12 - autoscroll_mouse_pos_cache);
921
speed = 12.0f - Math.fabsf (autoscroll_mouse_pos_cache);
925
speed *= autoscroll_direction;
926
move_scroll_position (speed, true);
929
Clutter.Event motion_event = { 0 };
930
motion_event.type = Clutter.EventType.MOTION;
931
motion_event.motion.x = last_known_x;
932
motion_event.motion.y = last_known_y;
933
motion_event.motion.stage = get_stage () as Clutter.Stage;
934
motion_event.motion.flags = Clutter.EventFlags.FLAG_SYNTHETIC;
935
passthrough_motion_event (motion_event);
937
autoscroll_stored_delta = delta;
940
private void on_auto_scrolling_state_change ()
942
if (autoscroll_timeline.is_playing () == false && is_autoscrolling)
944
autoscroll_timeline.start ();
946
else if (autoscroll_timeline.is_playing () && is_autoscrolling == false)
948
autoscroll_timeline.stop ();
953
* Clutter signal connections
955
private bool on_button_press_event (Clutter.Event event)
957
if (event.button.button != 1)
963
//Clutter.grab_pointer (this);
966
passthrough_button_press_event (event);
969
if (get_model_index_at_y_pos (event.button.y, true) < 0)
974
previous_y_position = event.button.y;
975
previous_y_time = event.button.time;
977
this.get_stage ().button_release_event.connect (this.on_button_release_event);
982
private bool on_button_release_event (Clutter.Event event)
984
if (event.button.button != 1)
991
this.get_stage ().button_release_event.disconnect (this.on_button_release_event);
992
Unity.global_shell.remove_fullscreen_request (this);
993
Clutter.ungrab_pointer ();
997
//passthrough_button_release_event (event);
998
foreach (ScrollerChild child in model)
1000
child.grabbed_push = 0;
1002
is_scrolling = false;
1003
Unity.Launcher.disable_quicklists = false;
1004
Clutter.ungrab_pointer ();
1005
get_stage ().motion_event.disconnect (on_motion_event);
1006
if (scroll_position > 0 || scroll_position < -(get_total_children_height () - get_available_height ()))
1008
current_phase = ScrollerPhase.SETTLING;
1009
settle_position = get_aligned_settle_position ();
1013
current_phase = ScrollerPhase.FLUNG;
1016
disable_child_events = true;
1017
fling_timeline.start ();
1020
MenuManager manager = MenuManager.get_default ();
1021
manager.popdown_current_menu ();
1027
uint queue_contract_launcher = 0;
1028
private bool on_enter_event (Clutter.Event event)
1030
if (queue_contract_launcher != 0)
1032
Source.remove (queue_contract_launcher);
1033
queue_contract_launcher = 0;
1036
if (attached_menu is QuicklistController)
1038
attached_menu.notify["status"].disconnect (on_menu_close);
1039
attached_menu = null;
1042
expand_launcher (event.crossing.y);
1046
private bool on_queue_contract_launcher ()
1048
if (queue_contract_launcher != 0)
1050
current_phase = ScrollerPhase.NONE;
1051
contract_launcher ();
1053
queue_contract_launcher = 0;
1057
private bool do_queue_contract_launcher ()
1059
queue_contract_launcher = Timeout.add (250, on_queue_contract_launcher);
1063
QuicklistController? attached_menu = null;
1064
private void on_menu_close ()
1066
if (attached_menu is QuicklistController)
1068
if (attached_menu.state != QuicklistControllerState.MENU)
1070
if (last_known_pointer_x > get_width ())
1071
do_queue_contract_launcher ();
1073
attached_menu.notify["status"].disconnect (on_menu_close);
1074
attached_menu = null;
1079
private bool on_leave_event (Clutter.Event event)
1081
last_known_pointer_x = 200;
1082
var drag_controller = Drag.Controller.get_default ();
1083
if (drag_controller.is_dragging) return false;
1084
if (is_scrolling) return false;
1086
// if a menu is open, don't fold the launcher, wait until its closed (if ever)
1087
QuicklistController? menu = QuicklistController.get_current_menu ();
1088
if (menu is QuicklistController)
1090
if (menu.state == QuicklistControllerState.MENU)
1092
attached_menu = menu;
1093
attached_menu.notify["state"].connect (on_menu_close);
1098
do_queue_contract_launcher ();
1100
if (last_picked_actor is Clutter.Actor &&
1101
last_picked_actor != this)
1103
last_picked_actor.do_event (event, false);
1104
last_picked_actor = null;
1109
float autoscroll_mouse_pos_cache = 0.0f;
1110
private bool on_autoscroll_motion_check (float y)
1112
if (get_total_children_height () < get_available_height () || is_scrolling)
1114
is_autoscrolling = false;
1118
//check for autoscroll events
1120
get_transformed_position (out pos_x, out pos_y);
1121
float transformed_y = y - pos_y;
1123
autoscroll_mouse_pos_cache = transformed_y;
1124
if (transformed_y > (get_height ()/2))
1126
autoscroll_direction = -1;
1127
autoscroll_mouse_pos_cache -= get_height ();
1131
autoscroll_direction = 1;
1133
if (transformed_y < 12 || transformed_y > (get_height () - 12))
1134
is_autoscrolling = true;
1136
is_autoscrolling = false;
1141
private void on_drag_motion_event (Unity.Drag.Model model, float x, float y)
1143
on_autoscroll_motion_check (y);
1146
private bool on_motion_event (Clutter.Event event)
1148
on_autoscroll_motion_check (event.motion.y);
1150
var drag_controller = Drag.Controller.get_default ();
1151
if (drag_controller.is_dragging)
1153
// we are dragging from somewhere else, ignore motion events
1156
last_motion_event_time = event.motion.time;
1158
if (button_down && is_scrolling == false &&
1159
view_type != ScrollerViewType.CONTRACTED &&
1162
/* we have a left button down, but we aren't dragging yet, we need to
1163
* monitor how far away we have dragged from the original click, once
1164
* we get far enough away we can start scrolling.
1166
//var diff = event.motion.y - previous_y_position;
1167
is_scrolling = true;
1168
Unity.Launcher.disable_quicklists = true;
1169
Unity.global_shell.add_fullscreen_request (this);
1170
Clutter.grab_pointer (this);
1171
get_stage ().motion_event.connect (on_motion_event);
1176
/* Disable any animations on the children */
1177
//disable_animations_on_children (event);
1179
/* we need to compare the event y position from this event to the
1180
* previous event. once we have that we can compute a velocity based
1181
* on how long it was since the previous event
1183
passthrough_motion_event (event);
1185
float pixel_diff = event.motion.y - previous_y_position;
1186
uint time_diff = event.motion.time - previous_y_time;
1187
scroll_speed = pixel_diff / (time_diff / 1000.0f);
1189
previous_y_position = event.motion.y;
1190
previous_y_time = event.motion.time;
1192
// move the scroller by how far we dragged
1193
move_scroll_position (pixel_diff);
1201
private bool on_scroll_event (Clutter.Event event)
1203
// got a mouse wheel scroll
1204
float modifier = 0.0f;
1205
if (event.scroll.direction == Clutter.ScrollDirection.UP)
1207
else if (event.scroll.direction == Clutter.ScrollDirection.DOWN)
1210
if (modifier != 0.0f)
1212
float speed = ((48 + spacing) * 3) * 6.0f;
1213
scroll_speed += speed * modifier;
1214
current_phase = ScrollerPhase.FLUNG;
1215
fling_timeline.start ();
1222
* Methods to handle our scroller animation, generally todo with the scrolling
1224
private void on_scroller_frame (Clutter.Timeline timeline, int msecs)
1226
/* one of the things about our scroller is that we really need to step the
1227
* dynamically simulated parts of the animation at a fixed framerate. but
1228
* clutter does not allow that. so we fake it :) this code aims for 60/fps
1230
// animate the scroller depeding on its phase
1231
uint delta = timeline.get_delta ();
1232
delta += stored_delta;
1235
stored_delta = delta;
1241
is_animating = true;
1243
if (fling_timeout_source != 0 && current_phase != ScrollerPhase.NONE)
1244
Source.remove (fling_timeout_source);
1246
switch (current_phase) {
1247
case (ScrollerPhase.SETTLING):
1248
do_anim_settle (timeline, msecs);
1250
case (ScrollerPhase.FLUNG):
1251
do_anim_fling (timeline, msecs);
1253
case (ScrollerPhase.BOUNCE):
1254
do_anim_bounce (timeline, msecs);
1256
case (ScrollerPhase.NONE):
1259
scroll_speed = 0.0f;
1260
is_animating = false;
1261
disable_child_events = false;
1265
assert_not_reached ();
1269
if (current_phase == ScrollerPhase.NONE)
1270
cache.update_texture_cache ();
1272
cache.invalidate_texture_cache ();
1274
Clutter.Event motion_event = { 0 };
1275
motion_event.type = Clutter.EventType.MOTION;
1276
motion_event.motion.x = last_known_x;
1277
motion_event.motion.y = last_known_y;
1278
motion_event.motion.stage = get_stage () as Clutter.Stage;
1279
motion_event.motion.flags = Clutter.EventFlags.FLAG_SYNTHETIC;
1280
passthrough_motion_event (motion_event);
1282
stored_delta = delta;
1285
private void do_anim_settle (Clutter.Timeline timeline, int msecs)
1287
var distance = settle_position - scroll_position;
1288
move_scroll_position (distance * 0.2f, false, 60.0f);
1289
if (Math.fabs (distance) < 1 )
1291
move_scroll_position (distance);
1292
current_phase = ScrollerPhase.NONE;
1297
uint fling_timeout_source = 0;
1298
private void do_anim_fling (Clutter.Timeline timeline, int msecs)
1300
scroll_speed *= friction; // slow our speed
1302
// we devide by 60 because get 60 ticks a second
1303
float scroll_move_amount = scroll_speed / 60.0f;
1304
move_scroll_position (scroll_move_amount, false, 60.0f);
1306
//after a fling, we have to figure out if we want to change our
1307
// scroller phase or not
1310
if (Math.fabsf (scroll_move_amount) < 1.0)
1312
current_phase = ScrollerPhase.NONE;
1313
fling_timeout_source = GLib.Timeout.add (300, () =>
1315
current_phase = ScrollerPhase.SETTLING;
1316
settle_position = get_aligned_settle_position ();
1317
fling_timeline.start ();
1323
private void do_anim_bounce (Clutter.Timeline timeline, int msecs)
1325
scroll_speed *= 0.5f;
1326
move_scroll_position (scroll_speed / 60.0f);
1327
settle_position = -get_aligned_settle_position ();
1328
current_phase = ScrollerPhase.SETTLING;
1331
private float get_aligned_settle_position ()
1333
/* attempts to integligently find the correct settle position */
1334
float final_position = scroll_position;
1335
if (total_child_height < height - padding.top - padding.bottom)
1337
// just move up to the top because we don't have enough items
1340
else if (scroll_position > 0)
1342
// we always position on the first child
1345
else if (get_total_children_height () < get_available_height ())
1349
else if (-scroll_position > total_child_height - height - padding.top - padding.bottom)
1351
// position on the final child
1352
final_position = -(get_total_children_height () - get_available_height ());
1355
return final_position;
1362
public override void get_preferred_width (float for_height,
1363
out float minimum_width,
1364
out float natural_width)
1369
float pmin_width = 0.0f;
1370
float pnat_width = 0.0f;
1372
foreach (ScrollerChild child in model)
1374
float cmin_width = 0.0f;
1375
float cnat_width = 0.0f;
1377
child.get_preferred_width (for_height,
1381
pmin_width = pmin_width.max(pmin_width, cmin_width);
1382
pnat_width = pnat_width.max(pnat_width, cnat_width);
1386
pmin_width += padding.left + padding.right;
1387
pnat_width += padding.left + padding.right;
1389
minimum_width = pmin_width;
1390
natural_width = pnat_width;
1394
public override void get_preferred_height (float for_width,
1395
out float minimum_height,
1396
out float natural_height)
1398
minimum_height = 0.0f;
1399
natural_height = 0.0f;
1401
float cnat_height = 0.0f;
1402
float cmin_height = 0.0f;
1403
total_child_height = 0.0f;
1405
foreach (ScrollerChild child in model)
1409
child.get_preferred_height (for_width,
1412
total_child_height += cnat_height + spacing;
1415
minimum_height = total_child_height + padding.top + padding.bottom;
1416
natural_height = total_child_height + padding.top + padding.bottom;
1420
private void order_children (bool immediate)
1422
if (get_total_children_height () < get_available_height ())
1424
order_children_expanded (immediate);
1430
case ScrollerViewType.CONTRACTED:
1431
order_children_contracted (immediate);
1434
case ScrollerViewType.EXPANDED:
1435
order_children_expanded (immediate);
1439
assert_not_reached ();
1445
private void change_child_position_rotation (ScrollerChild child,
1446
float position, float rotation,
1447
bool immediate = false)
1449
if (immediate || global_shell.is_starting)
1451
child.position = position;
1452
child.force_rotation_jump (rotation);
1456
bool do_new_position = true;
1457
if (child.get_animation () is Clutter.Animation)
1459
//GLib.Value value = GLib.Value (GLib.Type.from_name ("string"));
1460
GLib.Value value = Value (typeof (float));
1461
Clutter.Interval interval = child.get_animation ().get_interval ("position");
1462
if (interval is Clutter.Interval)
1463
interval.get_final_value (value);
1464
if (value.get_float () != position)
1466
// disable the current animation before starting a new one
1467
float current_pos = child.position;
1468
child.get_animation ().completed ();
1469
child.position = current_pos;
1473
do_new_position = false;
1477
child.rotation = rotation;
1479
if (do_new_position)
1481
if (view_type == ScrollerViewType.CONTRACTED)
1482
child.animate (Clutter.AnimationMode.EASE_IN_OUT_QUAD, 300,
1483
"position", position);
1485
child.animate (Clutter.AnimationMode.EASE_OUT_QUINT, 300,
1486
"position", position);
1491
private void order_children_expanded (bool immediate = false)
1493
// figures out the position of each child based on its order in the model
1495
float min_height, nat_height;
1496
if (!(draw_ftb is Gee.ArrayList))
1497
draw_ftb = new Gee.ArrayList<ScrollerChild> ();
1499
if (!(draw_ftb is Gee.ArrayList))
1500
draw_btf = new Gee.ArrayList<ScrollerChild> ();
1503
foreach (ScrollerChild child in model)
1506
if (index == drag_indicator_index && drag_indicator_active)
1508
if (drag_indicator_space)
1510
child.get_preferred_height (get_width (), out min_height, out nat_height);
1511
h += nat_height + spacing;
1520
child.get_preferred_height (get_width (), out min_height, out nat_height);
1521
change_child_position_rotation (child, h + scroll_position, 0.0f, immediate);
1523
h += nat_height + spacing;
1529
private void order_children_contracted (bool immediate = false)
1532
float min_height, nat_height;
1533
int num_launchers = 0;
1534
//get the total size of the children in a flat state
1535
float total_child_height = get_total_children_height ();
1536
int actual_model_size = model.size;
1537
foreach (ScrollerChild child in model)
1538
if (child.do_not_render) actual_model_size -= 1;
1540
if (total_child_height > get_available_height ())
1542
// we need to contract some icons
1544
// we need to calculate how many launchers fit on our launcher
1545
num_launchers = (int)Math.floorf ((get_available_height () - (spacing * 2)) / (48.0f + spacing));
1547
for (; num_launchers >= 1; num_launchers--)
1549
//check to see if we can fit everything in
1550
float flat_space = num_launchers * (48.0f + spacing);
1551
float contracted_space = 0.0f;
1553
contracted_space = ((actual_model_size - num_launchers) * (8 + spacing));
1555
if (flat_space + spacing + contracted_space < (get_available_height () - (spacing * 2)))
1557
// everything fits in at this level, woo!
1561
num_launchers = int.max (num_launchers, 1);
1562
// num_launchers now contains how many launchers should be "flat"
1566
num_launchers = actual_model_size;
1569
int num_children_handled = 0;
1570
int index_start_flat, index_end_flat = 0;
1572
if (focused_launcher < actual_model_size - (num_launchers -(num_launchers / 2)))
1574
index_start_flat = int.max (0, focused_launcher - (num_launchers / 2));
1575
index_end_flat = index_start_flat + num_launchers;
1579
index_end_flat = actual_model_size;
1580
index_start_flat = index_end_flat - num_launchers;
1582
draw_ftb = new Gee.ArrayList<ScrollerChild> ();
1583
draw_btf = new Gee.ArrayList<ScrollerChild> ();
1585
for (int index = 0; index < model.size; index++)
1587
ScrollerChild child = model[index];
1588
child.get_preferred_height (get_width (), out min_height, out nat_height);
1589
if (index >= index_start_flat && index < index_end_flat)
1591
change_child_position_rotation (child, h, 0.0f, immediate);
1593
num_children_handled++;
1595
if (index == index_start_flat)
1596
draw_ftb.add (child);
1598
draw_btf.add (child);
1602
// contracted launcher
1603
if (index == index_end_flat) h -= nat_height * 0.3333f - spacing;//spacing * 2;
1604
float rotation = 0.0f;
1605
float position = 0.0f;
1606
if (num_children_handled < index_start_flat)
1608
if (num_children_handled == index_start_flat - 1)
1610
rotation = -contract_icon_partial_degrees;
1615
rotation = -contract_icon_degrees;
1618
draw_ftb.add (child);
1623
if (index == index_end_flat)
1625
rotation = contract_icon_partial_degrees;
1630
rotation = contract_icon_degrees;
1632
draw_btf.add (child);
1635
change_child_position_rotation (child, position, rotation, immediate);
1637
num_children_handled++;
1639
if (index +1 == index_start_flat) h += 30;
1645
private float get_total_children_height ()
1648
float min_height, nat_height;
1649
foreach (ScrollerChild child in model)
1651
if (child.do_not_render) continue;
1652
child.get_preferred_height (get_width (), out min_height, out nat_height);
1653
h += nat_height + spacing;
1658
private float get_available_height ()
1660
Clutter.ActorBox box;
1661
get_stored_allocation (out box);
1662
return box.get_height () - padding.top - padding.bottom;
1665
public override void allocate (Clutter.ActorBox box,
1666
Clutter.AllocationFlags flags)
1668
base.allocate (box, flags);
1669
Clutter.ActorBox child_box = Clutter.ActorBox ();
1670
Clutter.ActorBox temp_child_box = Clutter.ActorBox ();
1671
float current_width = padding.left;
1672
float available_height = box.get_height () - padding.bottom;
1673
float available_width = box.get_width () - padding.right;
1675
total_child_height = 0.0f;
1678
if (drag_indicator_active)
1679
drag_indicator_position = model[drag_indicator_index].position + padding.top;
1681
foreach (ScrollerChild child in model)
1684
float child_height, child_width, natural, min;
1686
child.get_preferred_width (available_height, out min, out natural);
1687
child_width = Math.fmaxf (min, Math.fminf (natural, available_width));
1689
child.get_preferred_height (child_width, out min, out natural);
1690
child_height = Math.fmaxf (min, Math.fminf (natural, available_height));
1692
child_box.x1 = current_width;
1693
child_box.x2 = child_box.x1 + child_width;//box.get_width () - padding.right;
1694
child_box.y1 = child.position + padding.top;
1695
child_box.y2 = child_box.y1 + child_height;
1697
if (!child.do_not_render) ;
1698
child.allocate (child_box, flags);
1700
total_child_height += child_height + spacing;
1702
if (index >= 0 && index <= 9)
1704
Clutter.CairoTexture? keyboard_indicator = null;
1705
keyboard_indicator = keyboard_indicators[(int)index];
1707
if (keyboard_indicator is Clutter.Actor)
1709
uint surface_width, surface_height;
1710
keyboard_indicator.get_surface_size (out surface_width, out surface_height);
1711
child_box.x1 = box.get_width () - padding.right - surface_width - 6;
1712
child_box.x2 = child_box.x1 + keyboard_indicator.get_width ();
1713
child_box.y1 = child.position + padding.top + ((child_box.get_height ()*0.5f) - (surface_height*0.5f));
1714
child_box.y2 = child_box.y1 + keyboard_indicator.get_height ();
1715
keyboard_indicator.allocate (child_box, flags);
1723
child_box.x2 = box.get_width ();
1725
/* allocate the background graphic */
1726
int bg_height, bg_width;
1727
bgtex.get_base_size (out bg_width, out bg_height);
1728
float bg_offset = Math.fmodf (scroll_position + 1000000, bg_height);
1730
child_box.y1 = bg_offset - (bg_height - 1);
1731
child_box.y2 = bg_offset + (bg_height - 1) + box.get_height ();
1733
bgtex.allocate (child_box, flags);
1735
/* allocate the extra graphics */
1736
top_shadow.get_base_size (out bg_width, out bg_height);
1738
child_box.y2 = bg_height -1;
1739
top_shadow.allocate (child_box, flags);
1742
public override void pick (Clutter.Color color)
1745
foreach (ScrollerChild child in model)
1747
if (child is ScrollerChild && child.opacity > 0 && !child.do_not_render)
1749
(child as ScrollerChild).paint ();
1753
foreach (ScrollerChild child in child_refs)
1755
if (child.do_not_render) continue;
1761
public override void paint ()
1765
if (drag_indicator_active)
1767
Cogl.set_source_color4f (1.0f, 1.0f, 1.0f,
1768
drag_indicator_opacity);
1770
Cogl.rectangle (0, drag_indicator_position,
1772
drag_indicator_position + 2);
1776
for (int index = draw_btf.size-1; index >= 0; index--)
1778
ScrollerChild child = draw_btf[index];
1779
if (child is ScrollerChild && child.opacity > 0 && !child.do_not_render)
1781
(child as ScrollerChild).paint ();
1785
foreach (ScrollerChild child in draw_ftb)
1787
if (child is ScrollerChild && child.opacity > 0 && !child.do_not_render)
1789
(child as ScrollerChild).paint ();
1793
foreach (ScrollerChild child in model)
1795
if ((child in draw_ftb) || (child in draw_btf))
1798
if (child is ScrollerChild && child.opacity > 0)
1800
(child as ScrollerChild).paint ();
1805
foreach (ScrollerChild child in child_refs)
1807
if (child.do_not_render) continue;
1811
foreach (Clutter.CairoTexture kb_ind in keyboard_indicators)
1816
top_shadow.paint ();
1819
public override void map ()
1825
foreach (Clutter.CairoTexture kb_ind in keyboard_indicators)
1830
foreach (ScrollerChild child in model)
1837
public override void unmap ()
1843
foreach (Clutter.CairoTexture kb_ind in keyboard_indicators)
1848
foreach (ScrollerChild child in model)