~gordallott/unity/unity-gtk3

« back to all changes in this revision

Viewing changes to unity-private/launcher/scroller-view.vala

  • Committer: Gord Allott
  • Date: 2011-06-16 14:09:48 UTC
  • mfrom: (1187.1.53 trunk)
  • Revision ID: gord.allott@canonical.com-20110616140948-wz5f46qpvwwg3ttl
save point

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* -*- Mode: vala; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */
2
 
/*
3
 
 * Copyright (C) 2010 Canonical Ltd
4
 
 *
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.
8
 
 *
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.
13
 
 *
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/>.
16
 
 *
17
 
 * Authored by Gordon Allott <gord.allott@canonical.com>
18
 
 *
19
 
 */
20
 
 
21
 
namespace Unity.Launcher
22
 
{
23
 
  /* describes the current phase of animation for the scroller */
24
 
  private enum ScrollerPhase
25
 
  {
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
31
 
    NONE
32
 
  }
33
 
 
34
 
  private enum ScrollerViewType
35
 
  {
36
 
    EXPANDED,
37
 
    CONTRACTED
38
 
  }
39
 
 
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
43
 
  {
44
 
    public float position;
45
 
    public float rotation;
46
 
  }
47
 
 
48
 
  class ScrollerView : Ctk.Actor
49
 
  {
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;}
54
 
 
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;
60
 
 
61
 
 
62
 
    /* our scroller constants */
63
 
    public int spacing = 6;
64
 
    public int drag_sensitivity = 7;
65
 
    public float friction = 0.9f;
66
 
 
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;
70
 
    /*
71
 
     * graphical images
72
 
     */
73
 
    private ThemeImage bgtex;
74
 
    private ThemeImage top_shadow;
75
 
    /*
76
 
     * state tracking variables
77
 
     */
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;
86
 
 
87
 
    private float last_known_x = 0;
88
 
    private float last_known_y = 0;
89
 
 
90
 
    /*
91
 
     * scrolling variables
92
 
     */
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
96
 
 
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;
100
 
 
101
 
    public Clutter.Timeline fling_timeline;
102
 
 
103
 
    public Clutter.Timeline autoscroll_timeline;
104
 
 
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
109
 
 
110
 
    private float contract_icon_degrees = 70.0f;
111
 
    private float contract_icon_partial_degrees = 30.0f;
112
 
    private int focused_launcher = 0;
113
 
 
114
 
    /* helps out with draw order */
115
 
    private Gee.ArrayList<ScrollerChild> draw_ftb;
116
 
    private Gee.ArrayList<ScrollerChild> draw_btf;
117
 
 
118
 
    /* Key binding indicators */
119
 
    private Gee.ArrayList<Clutter.CairoTexture> keyboard_indicators;
120
 
 
121
 
    /*
122
 
     * Refrence holders
123
 
     */
124
 
    private Gee.ArrayList<ScrollerChild> child_refs; // we sometimes need to hold a reference to a child
125
 
 
126
 
    public ScrollerView (ScrollerModel _model, Ctk.EffectCache _cache)
127
 
    {
128
 
      Object (model:_model, cache:_cache);
129
 
    }
130
 
 
131
 
    construct
132
 
    {
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);
136
 
 
137
 
      Unity.Testing.ObjectRegistry.get_default ().register ("LauncherScrollerView", this);
138
 
      var mypadding = this.padding;
139
 
 
140
 
      mypadding.left = 0.0f;
141
 
      mypadding.right = 0.0f;
142
 
      mypadding.top = 10.0f;
143
 
      mypadding.bottom = 5.0f;
144
 
 
145
 
      this.padding = mypadding;
146
 
 
147
 
      keyboard_indicators = new Gee.ArrayList <Clutter.CairoTexture> ();
148
 
      load_textures ();
149
 
      Clutter.Color color = Clutter.Color () {
150
 
        red = 0xff,
151
 
        green = 0xff,
152
 
        blue = 0xff,
153
 
        alpha = 0xff
154
 
      };
155
 
 
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);
159
 
 
160
 
      model.child_added.connect (model_child_added);
161
 
      model.child_removed.connect (model_child_removed);
162
 
      model.order_changed.connect (model_order_changed);
163
 
 
164
 
      // we need to go through our model and add all the items that are currently
165
 
      // in it
166
 
      foreach (ScrollerChild child in model)
167
 
        {
168
 
          model_child_added (child);
169
 
        }
170
 
 
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);
177
 
 
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);
181
 
 
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 ();
188
 
      });
189
 
      fling_timeline.completed.connect (() => {
190
 
        Timeout.add (0, () => {
191
 
        cache.update_texture_cache ();
192
 
        return false;
193
 
        });
194
 
      });
195
 
 
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 ();
202
 
      });
203
 
 
204
 
      autoscroll_timeline.completed.connect (() => {
205
 
        Timeout.add (0, () => {
206
 
          cache.update_texture_cache ();
207
 
          return false;
208
 
        });
209
 
      });
210
 
 
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;
216
 
        button_down = 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 ();
222
 
 
223
 
        animate (Clutter.AnimationMode.EASE_OUT_SINE, 150,
224
 
                 "drag-indicator-opacity", 1.0f);
225
 
      });
226
 
 
227
 
      drag_controller.drag_drop.connect ((drag_model, x, y) => {
228
 
        Unity.Launcher.disable_quicklists = false;
229
 
        foreach (Clutter.Actor child in model)
230
 
          {
231
 
            child.set_reactive (false);
232
 
          }
233
 
          if (x > get_width ()) contract_launcher ();
234
 
          else
235
 
            {
236
 
              Idle.add (() => {
237
 
                order_children (false);
238
 
              });
239
 
            }
240
 
          animate (Clutter.AnimationMode.EASE_OUT_SINE, 150,
241
 
                   "drag-indicator-opacity", 0.0f);
242
 
      });
243
 
 
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);
248
 
      });
249
 
 
250
 
      set_reactive (true);
251
 
 
252
 
      child_refs = new Gee.ArrayList <ScrollerChild> ();
253
 
      order_children (true);
254
 
      queue_relayout ();
255
 
      Idle.add (() => {
256
 
        order_children (true);
257
 
        queue_relayout ();
258
 
      });
259
 
 
260
 
      drag_indicator_space = false;
261
 
 
262
 
    }
263
 
 
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
268
 
     * is real... sheesh
269
 
     */
270
 
    private Clutter.Actor? last_picked_actor = null;
271
 
    private Clutter.Actor? handle_event (Clutter.Event event, bool assume_on_launcher=false)
272
 
    {
273
 
      if (disable_child_events)
274
 
        return null;
275
 
 
276
 
      if ((last_picked_actor is Clutter.Actor) == false)
277
 
        last_picked_actor = null;
278
 
 
279
 
 
280
 
      foreach (Clutter.Actor actor in model)
281
 
        {
282
 
          actor.set_reactive (true);
283
 
        }
284
 
 
285
 
 
286
 
      float x, y;
287
 
      event.get_coords (out x, out y);
288
 
      last_known_x = x;
289
 
      last_known_y = y;
290
 
      if (assume_on_launcher)
291
 
        x = 25;
292
 
 
293
 
      Clutter.Actor picked_actor = (get_stage () as Clutter.Stage).get_actor_at_pos (Clutter.PickMode.REACTIVE, (int)x, (int)y);
294
 
 
295
 
 
296
 
      foreach (Clutter.Actor actor in model)
297
 
        {
298
 
          actor.set_reactive (false);
299
 
        }
300
 
 
301
 
 
302
 
      if (picked_actor is Clutter.Actor)
303
 
        {
304
 
          // we now have a picked actor, figure out what event to send it
305
 
          if (last_picked_actor != picked_actor)
306
 
            {
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;
313
 
 
314
 
              if (last_picked_actor is Clutter.Actor)
315
 
                last_picked_actor.do_event (crossing_event, false);
316
 
 
317
 
              crossing_event.type = Clutter.EventType.ENTER;
318
 
              picked_actor.do_event (crossing_event, false);
319
 
            }
320
 
        }
321
 
      else if (last_picked_actor is Clutter.Actor)
322
 
        {
323
 
          // if picked_actor is null, then we want to send a leave event on the
324
 
          // previous actor
325
 
 
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;
332
 
 
333
 
          last_picked_actor.do_event (crossing_event, false);
334
 
        }
335
 
 
336
 
      last_picked_actor = picked_actor;
337
 
      return picked_actor;
338
 
    }
339
 
 
340
 
    private bool passthrough_motion_event (Clutter.Event event)
341
 
    {
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);
348
 
      if (is_scrolling)
349
 
        {
350
 
          get_stage ().motion_event.disconnect (on_motion_event);
351
 
        }
352
 
 
353
 
      Clutter.Actor picked_actor = handle_event (event, is_scrolling);
354
 
 
355
 
      if (picked_actor is Clutter.Actor)
356
 
          picked_actor.do_event (event, false);
357
 
 
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);
362
 
      if (is_scrolling)
363
 
        {
364
 
          get_stage ().motion_event.connect (on_motion_event);
365
 
        }
366
 
      return false;
367
 
    }
368
 
 
369
 
    private bool passthrough_button_press_event (Clutter.Event event)
370
 
    {
371
 
      var drag_controller = Drag.Controller.get_default ();
372
 
      if (drag_controller.is_dragging) return false;
373
 
 
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);
380
 
 
381
 
      enter_event.connect (on_enter_event);
382
 
      leave_event.connect (on_leave_event);
383
 
      button_press_event.connect (passthrough_button_press_event);
384
 
      return false;
385
 
    }
386
 
 
387
 
    private bool passthrough_button_release_event (Clutter.Event event)
388
 
    {
389
 
      var drag_controller = Drag.Controller.get_default ();
390
 
      if (drag_controller.is_dragging) return false;
391
 
 
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);
398
 
 
399
 
      enter_event.connect (on_enter_event);
400
 
      leave_event.connect (on_leave_event);
401
 
      button_release_event.connect (passthrough_button_release_event);
402
 
      return false;
403
 
    }
404
 
 
405
 
    private void on_drag_indicator_space_change ()
406
 
    {
407
 
      if (drag_indicator_active)
408
 
        {
409
 
          if (drag_indicator_space)
410
 
            {
411
 
              animate (Clutter.AnimationMode.EASE_OUT_SINE, 150,
412
 
                       "drag-indicator-opacity", 0.0f);
413
 
              order_children (false);
414
 
              return;
415
 
            }
416
 
          else
417
 
            {
418
 
              animate (Clutter.AnimationMode.EASE_OUT_SINE, 150,
419
 
                       "drag-indicator-opacity", 1.0f);
420
 
              order_children (false);
421
 
            }
422
 
        }
423
 
      else
424
 
        {
425
 
          animate (Clutter.AnimationMode.EASE_OUT_SINE, 150,
426
 
                   "drag-indicator-opacity", 1.0f);
427
 
          order_children (false);
428
 
        }
429
 
    }
430
 
 
431
 
    private void on_drag_indicator_active_change ()
432
 
    {
433
 
      on_drag_indicator_space_change ();
434
 
    }
435
 
 
436
 
    private void on_drag_indicator_index_change ()
437
 
    {
438
 
      order_children (false);
439
 
      queue_relayout ();
440
 
    }
441
 
 
442
 
    private float last_scroll_position = 0.0f;
443
 
    public void enable_keyboard_selection_mode (bool choice)
444
 
    {
445
 
      if (choice)
446
 
        last_scroll_position = scroll_position;
447
 
 
448
 
      uint8 new_opacity = (choice) ? 0xff : 0x00;
449
 
 
450
 
      int index = 1;
451
 
      foreach (Clutter.CairoTexture kb_ind in keyboard_indicators)
452
 
        {
453
 
          kb_ind.animate (Clutter.AnimationMode.EASE_OUT_SINE, 150,
454
 
                          "opacity", new_opacity);
455
 
          if (model.size <= index) new_opacity = 0x00;
456
 
          index++;
457
 
        }
458
 
 
459
 
 
460
 
      if (!choice)
461
 
        {
462
 
          if (view_type != ScrollerViewType.CONTRACTED &&
463
 
              last_known_pointer_x >= get_width ())
464
 
            {
465
 
              foreach (ScrollerChild child in model)
466
 
              {
467
 
                if (child.active)
468
 
                  {
469
 
                    focused_launcher = model.index_of (child);
470
 
                    break;
471
 
                  }
472
 
              }
473
 
              contract_launcher ();
474
 
            }
475
 
          else if (last_known_pointer_x < get_width ())
476
 
            {
477
 
              move_scroll_position (last_scroll_position - scroll_position);
478
 
            }
479
 
        }
480
 
      else
481
 
        {
482
 
          expand_launcher (0);
483
 
        }
484
 
 
485
 
 
486
 
    }
487
 
 
488
 
    public int get_model_index_at_y_pos_no_anim (float y, bool return_minus_if_fail=false)
489
 
    {
490
 
      SList<float?> positions = new SList<float?> ();
491
 
      foreach (ScrollerChild child in model)
492
 
        {
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)
497
 
            {
498
 
              Clutter.Interval interval = anim.get_interval ("position");
499
 
              if (interval is Clutter.Interval)
500
 
                {
501
 
                  interval.get_final_value (value);
502
 
                  child.position = value.get_float ();
503
 
                }
504
 
            }
505
 
        }
506
 
 
507
 
        int value = get_model_index_at_y_pos (y, return_minus_if_fail);
508
 
 
509
 
        unowned SList<float?> list = positions;
510
 
        foreach (ScrollerChild child in model)
511
 
          {
512
 
            child.position = (float)list.data;
513
 
            list = list.next;
514
 
          }
515
 
 
516
 
        ScrollerChild child = (Drag.Controller.get_default ().get_drag_model () as ScrollerChildController).child;
517
 
 
518
 
        //value = model.clamp (child, value);
519
 
 
520
 
        return value;
521
 
    }
522
 
 
523
 
    public int get_model_index_at_y_pos (float y, bool return_minus_if_fail=false)
524
 
    {
525
 
      if (!do_logic_pick)
526
 
        return get_model_index_at_y_pos_pick (y, return_minus_if_fail);
527
 
      else
528
 
        return get_model_index_at_y_pos_logic (y, return_minus_if_fail);
529
 
    }
530
 
 
531
 
    private int get_model_index_at_y_pos_logic (float y, bool return_minus_if_fail=false)
532
 
    {
533
 
      foreach (ScrollerChild child in model)
534
 
        {
535
 
          if (child.position + padding.top + child.get_height () > y)
536
 
            return model.index_of (child as ScrollerChild);
537
 
        }
538
 
 
539
 
      if (return_minus_if_fail)
540
 
        return -1;
541
 
 
542
 
      return (y < padding.top + model[0].get_height () + spacing) ? 0 : model.size -1;
543
 
 
544
 
    }
545
 
 
546
 
    private int get_model_index_at_y_pos_pick(float y, bool return_minus_if_fail=false)
547
 
    {
548
 
      // trying out a different method
549
 
      int iy = (int)y;
550
 
      foreach (ScrollerChild actor in model)
551
 
        {
552
 
          if (!actor.do_not_render)
553
 
            actor.set_reactive (true);
554
 
        }
555
 
 
556
 
      Clutter.Actor picked_actor = (get_stage () as Clutter.Stage).get_actor_at_pos (Clutter.PickMode.REACTIVE, 25, iy);
557
 
      int ret_val = -200;
558
 
 
559
 
      if (picked_actor is ScrollerChild == false)
560
 
        {
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);
563
 
 
564
 
          if (picked_actor is ScrollerChild == false)
565
 
            {
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)
569
 
                {
570
 
                  if (return_minus_if_fail)
571
 
                    ret_val = -1;
572
 
                  // couldn't pick a single actor, return 0
573
 
                  ret_val =  (y < padding.top + model[0].get_height () + spacing) ? 0 : model.size -1 ;
574
 
                }
575
 
            }
576
 
        }
577
 
 
578
 
      if (ret_val < -1)
579
 
        ret_val = model.index_of (picked_actor as ScrollerChild);
580
 
 
581
 
      foreach (Clutter.Actor actor in model)
582
 
        {
583
 
          actor.set_reactive (false);
584
 
        }
585
 
 
586
 
      return ret_val;
587
 
    }
588
 
 
589
 
    private void draw_keyboard_indicator_cairo (Cairo.Context cr, string text)
590
 
    {
591
 
      double x = 0;
592
 
      double y = 0;
593
 
      double w = 10;
594
 
      double h = 10;
595
 
      double r = Ctk.em_to_pixel (0.7f);
596
 
 
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 ();
611
 
      int text_width;
612
 
      int text_height;
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;
617
 
 
618
 
      w += text_width;
619
 
      h += text_height;
620
 
      cr.set_source_rgba (0.07, 0.07, 0.07, 0.8);
621
 
 
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
631
 
 
632
 
      cr.fill ();
633
 
 
634
 
      cr.set_source_rgba (1, 1, 1, 1);
635
 
 
636
 
      // draw text
637
 
      cr.move_to (x + (w - text_width) * 0.5,
638
 
                  y + (h - text_height) * 0.5);
639
 
      Pango.cairo_show_layout (cr, layout);
640
 
    }
641
 
 
642
 
    /*
643
 
     * private methods
644
 
     */
645
 
    private void load_textures ()
646
 
    {
647
 
      bgtex = new ThemeImage ("launcher_background_middle");
648
 
      bgtex.set_repeat (true, true);
649
 
      bgtex.set_parent (this);
650
 
 
651
 
      top_shadow = new ThemeImage ("overflow_top");
652
 
      top_shadow.set_repeat (true, false);
653
 
      top_shadow.set_parent (this);
654
 
 
655
 
      var color = Clutter.Color () {
656
 
        red = 0xff,
657
 
        green = 0xff,
658
 
        blue = 0xff,
659
 
        alpha = 0xff
660
 
      };
661
 
 
662
 
      int index = 1;
663
 
      // indicator size find out activate!
664
 
      int key_indicator_w, key_indicator_h;
665
 
      Gtk.Settings settings = Gtk.Settings.get_default ();
666
 
 
667
 
      Unity.QuicklistRendering.get_text_extents (settings.gtk_font_name, "2",
668
 
                                                 out key_indicator_w, out key_indicator_h);
669
 
 
670
 
      key_indicator_w += 10;
671
 
      key_indicator_h += 10;
672
 
 
673
 
      for (; index <= 10; index++)
674
 
        {
675
 
          var keyboard_indicator = new Clutter.CairoTexture (key_indicator_w, key_indicator_h);
676
 
          keyboard_indicator.set_parent (this);
677
 
          keyboard_indicator.opacity = 0x00;
678
 
 
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 ();
682
 
            {
683
 
              Cairo.Context cr = keyboard_indicator.create ();
684
 
 
685
 
              string ind_str = index.to_string ();
686
 
              if (index == 10)
687
 
                ind_str = "0";
688
 
 
689
 
              draw_keyboard_indicator_cairo (cr, ind_str);
690
 
            }
691
 
          keyboard_indicators.add (keyboard_indicator);
692
 
        }
693
 
    }
694
 
 
695
 
    // will move the scroller by the given pixels
696
 
    private float calculate_scroll_position (bool check_bounds=false, float limit = 160.0f)
697
 
    {
698
 
      float new_scroll_position = scroll_position;
699
 
      if (check_bounds)
700
 
        {
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 ()));
703
 
        }
704
 
      else if (new_scroll_position > 0)
705
 
        {
706
 
          new_scroll_position = limit * ( 1 - Math.powf ((limit - 1) / limit, new_scroll_position));
707
 
        }
708
 
      else if (get_total_children_height () < get_available_height () &&
709
 
               new_scroll_position < 0)
710
 
        {
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;
714
 
        }
715
 
      else if (get_total_children_height () >= get_available_height () &&
716
 
               new_scroll_position < -(get_total_children_height () - get_available_height ()))
717
 
        {
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;
721
 
        }
722
 
 
723
 
      return new_scroll_position;
724
 
    }
725
 
 
726
 
    private void move_scroll_position (float pixels, bool check_bounds=false, float limit = 160.0f)
727
 
    {
728
 
      scroll_position += pixels;
729
 
      float old_scroll_position = scroll_position;
730
 
 
731
 
      scroll_position = calculate_scroll_position (check_bounds, limit);
732
 
 
733
 
      order_children (true);
734
 
      queue_relayout ();
735
 
 
736
 
      scroll_position = old_scroll_position;
737
 
    }
738
 
 
739
 
    /* disables animations and events on children so that they don't
740
 
     * get in the way of our scroller interactions
741
 
     */
742
 
    private void disable_animations_on_children (Clutter.Event event)
743
 
    {
744
 
/*
745
 
      disable_child_events = true;
746
 
 
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;
754
 
 
755
 
      foreach (ScrollerChild child in model)
756
 
        {
757
 
          if (child is Clutter.Actor)
758
 
            {
759
 
              e.crossing.source = child;
760
 
              child.do_event (e, false);
761
 
            }
762
 
        }
763
 
*/
764
 
 
765
 
    }
766
 
 
767
 
    private void expand_launcher (float absolute_y)
768
 
    {
769
 
      if (view_type == ScrollerViewType.EXPANDED) return;
770
 
      view_type = ScrollerViewType.EXPANDED;
771
 
 
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 ())
775
 
        {
776
 
          do_logic_pick = false;
777
 
          int index = get_model_index_at_y_pos (absolute_y);
778
 
 
779
 
          // set our state to what we will end up being so we can find the correct
780
 
          //place to be.
781
 
          float contracted_position = model[index].position;
782
 
          var old_scroll_position = scroll_position;
783
 
          scroll_position = 0;
784
 
          order_children (true);
785
 
 
786
 
          float new_scroll_position = -(model[index].position - contracted_position);
787
 
 
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);
792
 
 
793
 
          // and finally animate to the new view
794
 
          view_type = ScrollerViewType.EXPANDED;
795
 
 
796
 
          scroll_position = new_scroll_position;
797
 
          order_children (false); // have to order twice, boo
798
 
 
799
 
          queue_relayout ();
800
 
          do_logic_pick = true;
801
 
        }
802
 
    }
803
 
 
804
 
 
805
 
    private void contract_launcher ()
806
 
    {
807
 
      if (view_type == ScrollerViewType.CONTRACTED) return;
808
 
 
809
 
      foreach (ScrollerChild child in model)
810
 
        {
811
 
          if (child.active)
812
 
            focused_launcher = model.index_of (child);
813
 
        }
814
 
 
815
 
      view_type = ScrollerViewType.CONTRACTED;
816
 
      order_children (false);
817
 
      queue_relayout ();
818
 
      is_autoscrolling = false;
819
 
    }
820
 
 
821
 
    /*
822
 
     * model signal connections
823
 
     */
824
 
    private void model_child_added (ScrollerChild child)
825
 
    {
826
 
      child.unparent ();
827
 
      child.set_parent (this);
828
 
 
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];
832
 
 
833
 
      foreach (ScrollerChild modelchild in model)
834
 
        {
835
 
          prev_positions += modelchild.position;
836
 
          prev_rotations += modelchild.rotation;
837
 
        }
838
 
      order_children (true);
839
 
 
840
 
      int index = 0;
841
 
      foreach (ScrollerChild modelchild in model)
842
 
        {
843
 
          if (child != modelchild)
844
 
            {
845
 
              change_child_position_rotation (modelchild,
846
 
                                              prev_positions[index],
847
 
                                              prev_rotations[index],
848
 
                                              true);
849
 
            }
850
 
          index++;
851
 
        }
852
 
 
853
 
      order_children (false);
854
 
      queue_relayout ();
855
 
      child.notify["position"].connect (() => {
856
 
        queue_relayout ();
857
 
      });
858
 
 
859
 
      child.set_reactive (false);
860
 
    }
861
 
 
862
 
    private void model_child_removed (ScrollerChild child)
863
 
    {
864
 
      if (child in draw_btf) draw_btf.remove (child);
865
 
      if (child in draw_ftb) draw_ftb.remove (child);
866
 
 
867
 
      var drag_controller = Drag.Controller.get_default ();
868
 
      if (drag_controller.is_dragging)
869
 
        {
870
 
          order_children (false);
871
 
          queue_relayout ();
872
 
        }
873
 
      else
874
 
        {
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,
877
 
                                    SHORT_DELAY,
878
 
                                    "opacity", 0);
879
 
 
880
 
          var icon_scale_anim = child.processed_icon.animate (Clutter.AnimationMode.EASE_OUT_QUAD,
881
 
                                                              SHORT_DELAY,
882
 
                                                              "scale-x", 0.0,
883
 
                                                              "scale-y", 0.0);
884
 
          anim.completed.connect (() => {
885
 
            child.unparent ();
886
 
            child_refs.remove (child);
887
 
            order_children (false);
888
 
            queue_relayout ();
889
 
          });
890
 
        }
891
 
    }
892
 
 
893
 
    private void model_order_changed ()
894
 
    {
895
 
      order_children (false);
896
 
      queue_relayout ();
897
 
    }
898
 
 
899
 
    private uint autoscroll_stored_delta = 0;
900
 
    private void on_autoscroll_frame (Clutter.Timeline timeline, int msecs)
901
 
    {
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
905
 
       */
906
 
      uint delta = timeline.get_delta ();
907
 
      delta += autoscroll_stored_delta;
908
 
      if (delta <= 16)
909
 
        {
910
 
          autoscroll_stored_delta = delta;
911
 
          return;
912
 
        }
913
 
 
914
 
      while (delta > 33)
915
 
        {
916
 
          delta -= 33;
917
 
          float speed = 0.0f;
918
 
          if (autoscroll_mouse_pos_cache < 0)
919
 
            speed = Math.fabsf (-12 - autoscroll_mouse_pos_cache);
920
 
          else
921
 
            speed = 12.0f - Math.fabsf (autoscroll_mouse_pos_cache);
922
 
 
923
 
          speed /= 12.0f;
924
 
          speed *= 30;
925
 
          speed *= autoscroll_direction;
926
 
          move_scroll_position (speed, true);
927
 
        }
928
 
 
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);
936
 
 
937
 
        autoscroll_stored_delta = delta;
938
 
    }
939
 
 
940
 
    private void on_auto_scrolling_state_change ()
941
 
    {
942
 
      if (autoscroll_timeline.is_playing () == false && is_autoscrolling)
943
 
        {
944
 
          autoscroll_timeline.start ();
945
 
        }
946
 
      else if (autoscroll_timeline.is_playing () && is_autoscrolling == false)
947
 
        {
948
 
          autoscroll_timeline.stop ();
949
 
        }
950
 
    }
951
 
 
952
 
    /*
953
 
     * Clutter signal connections
954
 
     */
955
 
    private bool on_button_press_event (Clutter.Event event)
956
 
    {
957
 
      if (event.button.button != 1)
958
 
        {
959
 
          // not a left click
960
 
          return false;
961
 
        }
962
 
 
963
 
      //Clutter.grab_pointer (this);
964
 
      if (is_scrolling)
965
 
        {
966
 
          passthrough_button_press_event (event);
967
 
        }
968
 
      button_down = true;
969
 
      if (get_model_index_at_y_pos (event.button.y, true) < 0)
970
 
        can_scroll = false;
971
 
      else
972
 
        can_scroll = true;
973
 
 
974
 
      previous_y_position = event.button.y;
975
 
      previous_y_time = event.button.time;
976
 
 
977
 
      this.get_stage ().button_release_event.connect (this.on_button_release_event);
978
 
 
979
 
      return false;
980
 
    }
981
 
 
982
 
    private bool on_button_release_event (Clutter.Event event)
983
 
    {
984
 
      if (event.button.button != 1)
985
 
        {
986
 
          // not a left click
987
 
          return false;
988
 
        }
989
 
 
990
 
      button_down = false;
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 ();
994
 
 
995
 
      if (is_scrolling)
996
 
        {
997
 
          //passthrough_button_release_event (event);
998
 
          foreach (ScrollerChild child in model)
999
 
            {
1000
 
              child.grabbed_push = 0;
1001
 
            }
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 ()))
1007
 
            {
1008
 
              current_phase = ScrollerPhase.SETTLING;
1009
 
              settle_position = get_aligned_settle_position ();
1010
 
            }
1011
 
          else
1012
 
            {
1013
 
              current_phase = ScrollerPhase.FLUNG;
1014
 
            }
1015
 
 
1016
 
          disable_child_events = true;
1017
 
          fling_timeline.start ();
1018
 
        }
1019
 
 
1020
 
      MenuManager manager = MenuManager.get_default ();
1021
 
      manager.popdown_current_menu ();
1022
 
 
1023
 
      return false;
1024
 
    }
1025
 
 
1026
 
 
1027
 
    uint queue_contract_launcher = 0;
1028
 
    private bool on_enter_event (Clutter.Event event)
1029
 
    {
1030
 
      if (queue_contract_launcher != 0)
1031
 
        {
1032
 
          Source.remove (queue_contract_launcher);
1033
 
          queue_contract_launcher = 0;
1034
 
        }
1035
 
 
1036
 
      if (attached_menu is QuicklistController)
1037
 
        {
1038
 
          attached_menu.notify["status"].disconnect (on_menu_close);
1039
 
          attached_menu = null;
1040
 
        }
1041
 
 
1042
 
      expand_launcher (event.crossing.y);
1043
 
      return false;
1044
 
    }
1045
 
 
1046
 
    private bool on_queue_contract_launcher ()
1047
 
    {
1048
 
      if (queue_contract_launcher != 0)
1049
 
        {
1050
 
          current_phase = ScrollerPhase.NONE;
1051
 
          contract_launcher ();
1052
 
        }
1053
 
      queue_contract_launcher = 0;
1054
 
      return false;
1055
 
    }
1056
 
 
1057
 
    private bool do_queue_contract_launcher ()
1058
 
    {
1059
 
      queue_contract_launcher = Timeout.add (250, on_queue_contract_launcher);
1060
 
      return false;
1061
 
    }
1062
 
 
1063
 
    QuicklistController? attached_menu = null;
1064
 
    private void on_menu_close ()
1065
 
    {
1066
 
      if (attached_menu is QuicklistController)
1067
 
        {
1068
 
          if (attached_menu.state != QuicklistControllerState.MENU)
1069
 
            {
1070
 
              if (last_known_pointer_x > get_width ())
1071
 
                do_queue_contract_launcher ();
1072
 
 
1073
 
              attached_menu.notify["status"].disconnect (on_menu_close);
1074
 
              attached_menu = null;
1075
 
            }
1076
 
        }
1077
 
    }
1078
 
 
1079
 
    private bool on_leave_event (Clutter.Event event)
1080
 
    {
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;
1085
 
 
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)
1089
 
        {
1090
 
          if (menu.state == QuicklistControllerState.MENU)
1091
 
            {
1092
 
              attached_menu = menu;
1093
 
              attached_menu.notify["state"].connect (on_menu_close);
1094
 
              return false;
1095
 
            }
1096
 
          }
1097
 
 
1098
 
      do_queue_contract_launcher ();
1099
 
 
1100
 
      if (last_picked_actor is Clutter.Actor &&
1101
 
          last_picked_actor != this)
1102
 
        {
1103
 
          last_picked_actor.do_event (event, false);
1104
 
          last_picked_actor = null;
1105
 
        }
1106
 
      return false;
1107
 
    }
1108
 
 
1109
 
    float autoscroll_mouse_pos_cache = 0.0f;
1110
 
    private bool on_autoscroll_motion_check (float y)
1111
 
    {
1112
 
      if (get_total_children_height () < get_available_height () || is_scrolling)
1113
 
        {
1114
 
          is_autoscrolling = false;
1115
 
        }
1116
 
      else
1117
 
        {
1118
 
          //check for autoscroll events
1119
 
          float pos_x, pos_y;
1120
 
          get_transformed_position (out pos_x, out pos_y);
1121
 
          float transformed_y = y - pos_y;
1122
 
 
1123
 
          autoscroll_mouse_pos_cache = transformed_y;
1124
 
          if (transformed_y > (get_height ()/2))
1125
 
            {
1126
 
              autoscroll_direction = -1;
1127
 
              autoscroll_mouse_pos_cache -= get_height ();
1128
 
            }
1129
 
          else
1130
 
            {
1131
 
              autoscroll_direction = 1;
1132
 
            }
1133
 
          if (transformed_y < 12 || transformed_y > (get_height () - 12))
1134
 
            is_autoscrolling = true;
1135
 
          else
1136
 
            is_autoscrolling = false;
1137
 
        }
1138
 
      return false;
1139
 
    }
1140
 
 
1141
 
    private void on_drag_motion_event (Unity.Drag.Model model, float x, float y)
1142
 
    {
1143
 
      on_autoscroll_motion_check (y);
1144
 
    }
1145
 
 
1146
 
    private bool on_motion_event (Clutter.Event event)
1147
 
    {
1148
 
      on_autoscroll_motion_check (event.motion.y);
1149
 
 
1150
 
      var drag_controller = Drag.Controller.get_default ();
1151
 
      if (drag_controller.is_dragging)
1152
 
      {
1153
 
        // we are dragging from somewhere else, ignore motion events
1154
 
        return false;
1155
 
      }
1156
 
      last_motion_event_time = event.motion.time;
1157
 
 
1158
 
      if (button_down && is_scrolling == false &&
1159
 
          view_type != ScrollerViewType.CONTRACTED &&
1160
 
          can_scroll)
1161
 
        {
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.
1165
 
           */
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);
1172
 
        }
1173
 
 
1174
 
      if (is_scrolling)
1175
 
        {
1176
 
          /* Disable any animations on the children */
1177
 
          //disable_animations_on_children (event);
1178
 
 
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
1182
 
           */
1183
 
          passthrough_motion_event (event);
1184
 
 
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);
1188
 
 
1189
 
          previous_y_position = event.motion.y;
1190
 
          previous_y_time = event.motion.time;
1191
 
 
1192
 
          // move the scroller by how far we dragged
1193
 
          move_scroll_position (pixel_diff);
1194
 
 
1195
 
          return true;
1196
 
        }
1197
 
 
1198
 
      return false;
1199
 
    }
1200
 
 
1201
 
    private bool on_scroll_event (Clutter.Event event)
1202
 
    {
1203
 
      // got a mouse wheel scroll
1204
 
      float modifier = 0.0f;
1205
 
      if (event.scroll.direction == Clutter.ScrollDirection.UP)
1206
 
        modifier = 1.0f;
1207
 
      else if (event.scroll.direction == Clutter.ScrollDirection.DOWN)
1208
 
        modifier = -1.0f;
1209
 
 
1210
 
      if (modifier != 0.0f)
1211
 
        {
1212
 
          float speed = ((48 + spacing) * 3) * 6.0f;
1213
 
          scroll_speed += speed * modifier;
1214
 
          current_phase = ScrollerPhase.FLUNG;
1215
 
          fling_timeline.start ();
1216
 
        }
1217
 
 
1218
 
      return false;
1219
 
    }
1220
 
 
1221
 
    /*
1222
 
     * Methods to handle our scroller animation, generally todo with the scrolling
1223
 
     */
1224
 
    private void on_scroller_frame (Clutter.Timeline timeline, int msecs)
1225
 
    {
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
1229
 
       */
1230
 
      // animate the scroller depeding on its phase
1231
 
      uint delta = timeline.get_delta ();
1232
 
      delta += stored_delta;
1233
 
      if (delta <= 16)
1234
 
        {
1235
 
          stored_delta = delta;
1236
 
          return;
1237
 
        }
1238
 
 
1239
 
      while (delta > 16)
1240
 
        {
1241
 
          is_animating = true;
1242
 
          delta -= 16;
1243
 
          if (fling_timeout_source != 0 && current_phase != ScrollerPhase.NONE)
1244
 
            Source.remove (fling_timeout_source);
1245
 
 
1246
 
          switch (current_phase) {
1247
 
            case (ScrollerPhase.SETTLING):
1248
 
              do_anim_settle (timeline, msecs);
1249
 
              break;
1250
 
            case (ScrollerPhase.FLUNG):
1251
 
              do_anim_fling (timeline, msecs);
1252
 
              break;
1253
 
            case (ScrollerPhase.BOUNCE):
1254
 
              do_anim_bounce (timeline, msecs);
1255
 
              break;
1256
 
            case (ScrollerPhase.NONE):
1257
 
              {
1258
 
                timeline.stop ();
1259
 
                scroll_speed = 0.0f;
1260
 
                is_animating = false;
1261
 
                disable_child_events = false;
1262
 
              }
1263
 
              break;
1264
 
            default:
1265
 
              assert_not_reached ();
1266
 
          }
1267
 
        }
1268
 
 
1269
 
      if (current_phase == ScrollerPhase.NONE)
1270
 
        cache.update_texture_cache ();
1271
 
      else
1272
 
        cache.invalidate_texture_cache ();
1273
 
 
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);
1281
 
 
1282
 
      stored_delta = delta;
1283
 
    }
1284
 
 
1285
 
    private void do_anim_settle (Clutter.Timeline timeline, int msecs)
1286
 
    {
1287
 
      var distance = settle_position - scroll_position;
1288
 
      move_scroll_position (distance * 0.2f, false, 60.0f);
1289
 
      if (Math.fabs (distance) < 1 )
1290
 
        {
1291
 
          move_scroll_position (distance);
1292
 
          current_phase = ScrollerPhase.NONE;
1293
 
        }
1294
 
 
1295
 
    }
1296
 
 
1297
 
    uint fling_timeout_source = 0;
1298
 
    private void do_anim_fling (Clutter.Timeline timeline, int msecs)
1299
 
    {
1300
 
      scroll_speed *= friction; // slow our speed
1301
 
 
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);
1305
 
 
1306
 
      //after a fling, we have to figure out if we want to change our
1307
 
      // scroller phase or not
1308
 
 
1309
 
 
1310
 
      if (Math.fabsf (scroll_move_amount) < 1.0)
1311
 
        {
1312
 
          current_phase = ScrollerPhase.NONE;
1313
 
          fling_timeout_source = GLib.Timeout.add (300, () =>
1314
 
            {
1315
 
              current_phase = ScrollerPhase.SETTLING;
1316
 
              settle_position = get_aligned_settle_position ();
1317
 
              fling_timeline.start ();
1318
 
              return false;
1319
 
            });
1320
 
        }
1321
 
    }
1322
 
 
1323
 
    private void do_anim_bounce (Clutter.Timeline timeline, int msecs)
1324
 
    {
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;
1329
 
    }
1330
 
 
1331
 
    private float get_aligned_settle_position ()
1332
 
    {
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)
1336
 
        {
1337
 
          // just move up to the top because we don't have enough items
1338
 
          final_position = 0;
1339
 
        }
1340
 
      else if (scroll_position > 0)
1341
 
        {
1342
 
          // we always position on the first child
1343
 
          final_position = 0;
1344
 
        }
1345
 
      else if (get_total_children_height () < get_available_height ())
1346
 
        {
1347
 
          final_position = 0;
1348
 
        }
1349
 
      else if (-scroll_position > total_child_height - height - padding.top - padding.bottom)
1350
 
        {
1351
 
          // position on the final child
1352
 
          final_position = -(get_total_children_height () - get_available_height ());
1353
 
        }
1354
 
 
1355
 
      return final_position;
1356
 
    }
1357
 
 
1358
 
 
1359
 
    /*
1360
 
     * Clutter overrides
1361
 
     */
1362
 
    public override void get_preferred_width (float for_height,
1363
 
                                              out float minimum_width,
1364
 
                                              out float natural_width)
1365
 
    {
1366
 
      minimum_width = 0;
1367
 
      natural_width = 0;
1368
 
 
1369
 
      float pmin_width = 0.0f;
1370
 
      float pnat_width = 0.0f;
1371
 
 
1372
 
      foreach (ScrollerChild child in model)
1373
 
      {
1374
 
        float cmin_width = 0.0f;
1375
 
        float cnat_width = 0.0f;
1376
 
 
1377
 
        child.get_preferred_width (for_height,
1378
 
                                   out cmin_width,
1379
 
                                   out cnat_width);
1380
 
 
1381
 
        pmin_width = pmin_width.max(pmin_width, cmin_width);
1382
 
        pnat_width = pnat_width.max(pnat_width, cnat_width);
1383
 
 
1384
 
      }
1385
 
 
1386
 
      pmin_width += padding.left + padding.right;
1387
 
      pnat_width += padding.left + padding.right;
1388
 
 
1389
 
      minimum_width = pmin_width;
1390
 
      natural_width = pnat_width;
1391
 
 
1392
 
    }
1393
 
 
1394
 
    public override void get_preferred_height (float for_width,
1395
 
                                      out float minimum_height,
1396
 
                                      out float natural_height)
1397
 
    {
1398
 
      minimum_height = 0.0f;
1399
 
      natural_height = 0.0f;
1400
 
 
1401
 
      float cnat_height = 0.0f;
1402
 
      float cmin_height = 0.0f;
1403
 
      total_child_height = 0.0f;
1404
 
 
1405
 
      foreach (ScrollerChild child in model)
1406
 
      {
1407
 
        cnat_height = 0.0f;
1408
 
        cmin_height = 0.0f;
1409
 
        child.get_preferred_height (for_width,
1410
 
                                    out cmin_height,
1411
 
                                    out cnat_height);
1412
 
        total_child_height += cnat_height + spacing;
1413
 
      }
1414
 
 
1415
 
      minimum_height = total_child_height + padding.top + padding.bottom;
1416
 
      natural_height = total_child_height + padding.top + padding.bottom;
1417
 
      return;
1418
 
    }
1419
 
 
1420
 
    private void order_children (bool immediate)
1421
 
    {
1422
 
      if (get_total_children_height () < get_available_height ())
1423
 
        {
1424
 
          order_children_expanded (immediate);
1425
 
        }
1426
 
      else
1427
 
        {
1428
 
          switch (view_type)
1429
 
            {
1430
 
              case ScrollerViewType.CONTRACTED:
1431
 
                order_children_contracted (immediate);
1432
 
                break;
1433
 
 
1434
 
              case ScrollerViewType.EXPANDED:
1435
 
                order_children_expanded (immediate);
1436
 
                break;
1437
 
 
1438
 
              default:
1439
 
                assert_not_reached ();
1440
 
            }
1441
 
        }
1442
 
      queue_relayout ();
1443
 
    }
1444
 
 
1445
 
    private void change_child_position_rotation (ScrollerChild child,
1446
 
                                                 float position, float rotation,
1447
 
                                                 bool immediate = false)
1448
 
    {
1449
 
      if (immediate || global_shell.is_starting)
1450
 
        {
1451
 
          child.position = position;
1452
 
          child.force_rotation_jump (rotation);
1453
 
        }
1454
 
      else
1455
 
        {
1456
 
          bool do_new_position = true;
1457
 
          if (child.get_animation () is Clutter.Animation)
1458
 
            {
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)
1465
 
                {
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;
1470
 
                }
1471
 
              else
1472
 
                {
1473
 
                  do_new_position = false;
1474
 
                }
1475
 
            }
1476
 
 
1477
 
          child.rotation = rotation;
1478
 
 
1479
 
          if (do_new_position)
1480
 
            {
1481
 
              if (view_type == ScrollerViewType.CONTRACTED)
1482
 
                child.animate (Clutter.AnimationMode.EASE_IN_OUT_QUAD, 300,
1483
 
                               "position", position);
1484
 
              else
1485
 
                child.animate (Clutter.AnimationMode.EASE_OUT_QUINT, 300,
1486
 
                               "position", position);
1487
 
            }
1488
 
        }
1489
 
    }
1490
 
 
1491
 
    private void order_children_expanded (bool immediate = false)
1492
 
    {
1493
 
      // figures out the position of each child based on its order in the model
1494
 
      float h = 0.0f;
1495
 
      float min_height, nat_height;
1496
 
      if (!(draw_ftb is Gee.ArrayList))
1497
 
        draw_ftb = new Gee.ArrayList<ScrollerChild> ();
1498
 
 
1499
 
      if (!(draw_ftb is Gee.ArrayList))
1500
 
        draw_btf = new Gee.ArrayList<ScrollerChild> ();
1501
 
 
1502
 
      int index = 0;
1503
 
      foreach (ScrollerChild child in model)
1504
 
      {
1505
 
 
1506
 
        if (index == drag_indicator_index && drag_indicator_active)
1507
 
          {
1508
 
            if (drag_indicator_space)
1509
 
              {
1510
 
                child.get_preferred_height (get_width (), out min_height, out nat_height);
1511
 
                h += nat_height + spacing;
1512
 
              }
1513
 
            else
1514
 
              {
1515
 
                h += 2 + spacing;
1516
 
              }
1517
 
          }
1518
 
        else
1519
 
          {
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);
1522
 
 
1523
 
            h += nat_height + spacing;
1524
 
          }
1525
 
        index += 1;
1526
 
      }
1527
 
    }
1528
 
 
1529
 
    private void order_children_contracted (bool immediate = false)
1530
 
    {
1531
 
      float h = 0.0f;
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;
1539
 
 
1540
 
      if (total_child_height > get_available_height ())
1541
 
        {
1542
 
          // we need to contract some icons
1543
 
 
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));
1546
 
 
1547
 
          for (; num_launchers >= 1; num_launchers--)
1548
 
            {
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;
1552
 
 
1553
 
              contracted_space = ((actual_model_size - num_launchers) * (8 + spacing));
1554
 
 
1555
 
              if (flat_space + spacing + contracted_space < (get_available_height () - (spacing * 2)))
1556
 
                {
1557
 
                  // everything fits in at this level, woo!
1558
 
                  break;
1559
 
                }
1560
 
            }
1561
 
          num_launchers = int.max (num_launchers, 1);
1562
 
          // num_launchers now contains how many launchers should be "flat"
1563
 
        }
1564
 
      else
1565
 
        {
1566
 
          num_launchers = actual_model_size;
1567
 
        }
1568
 
 
1569
 
      int num_children_handled = 0;
1570
 
      int index_start_flat, index_end_flat = 0;
1571
 
 
1572
 
      if (focused_launcher < actual_model_size - (num_launchers -(num_launchers / 2)))
1573
 
        {
1574
 
          index_start_flat = int.max (0, focused_launcher - (num_launchers / 2));
1575
 
          index_end_flat = index_start_flat + num_launchers;
1576
 
        }
1577
 
      else
1578
 
        {
1579
 
          index_end_flat = actual_model_size;
1580
 
          index_start_flat = index_end_flat - num_launchers;
1581
 
        }
1582
 
      draw_ftb = new Gee.ArrayList<ScrollerChild> ();
1583
 
      draw_btf = new Gee.ArrayList<ScrollerChild> ();
1584
 
 
1585
 
      for (int index = 0; index < model.size; index++)
1586
 
        {
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)
1590
 
            {
1591
 
              change_child_position_rotation (child, h, 0.0f, immediate);
1592
 
              h += 48 + spacing;
1593
 
              num_children_handled++;
1594
 
 
1595
 
              if (index == index_start_flat)
1596
 
                draw_ftb.add (child);
1597
 
              else
1598
 
                draw_btf.add (child);
1599
 
            }
1600
 
          else
1601
 
            {
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)
1607
 
                {
1608
 
                  if (num_children_handled == index_start_flat - 1)
1609
 
                    {
1610
 
                      rotation = -contract_icon_partial_degrees;
1611
 
                      h += spacing;
1612
 
                    }
1613
 
                  else
1614
 
                    {
1615
 
                      rotation = -contract_icon_degrees;
1616
 
                    }
1617
 
                  position = h;
1618
 
                  draw_ftb.add (child);
1619
 
                }
1620
 
              else
1621
 
                {
1622
 
                  position = h;
1623
 
                  if (index == index_end_flat)
1624
 
                    {
1625
 
                      rotation = contract_icon_partial_degrees;
1626
 
                      h += spacing;
1627
 
                    }
1628
 
                  else
1629
 
                    {
1630
 
                      rotation = contract_icon_degrees;
1631
 
                    }
1632
 
                  draw_btf.add (child);
1633
 
                }
1634
 
 
1635
 
              change_child_position_rotation (child, position, rotation, immediate);
1636
 
              h += 8 + spacing;
1637
 
              num_children_handled++;
1638
 
 
1639
 
              if (index +1 == index_start_flat) h += 30;
1640
 
            }
1641
 
        }
1642
 
    }
1643
 
 
1644
 
 
1645
 
    private float get_total_children_height ()
1646
 
    {
1647
 
      float h = 0.0f;
1648
 
      float min_height, nat_height;
1649
 
      foreach (ScrollerChild child in model)
1650
 
        {
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;
1654
 
        }
1655
 
      return h;
1656
 
    }
1657
 
 
1658
 
    private float get_available_height ()
1659
 
    {
1660
 
      Clutter.ActorBox box;
1661
 
      get_stored_allocation (out box);
1662
 
      return box.get_height () - padding.top - padding.bottom;
1663
 
    }
1664
 
 
1665
 
    public override void allocate (Clutter.ActorBox box,
1666
 
                                   Clutter.AllocationFlags flags)
1667
 
    {
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;
1674
 
 
1675
 
      total_child_height = 0.0f;
1676
 
      int index = 0;
1677
 
 
1678
 
      if (drag_indicator_active)
1679
 
        drag_indicator_position = model[drag_indicator_index].position + padding.top;
1680
 
 
1681
 
      foreach (ScrollerChild child in model)
1682
 
        {
1683
 
 
1684
 
          float child_height, child_width, natural, min;
1685
 
 
1686
 
          child.get_preferred_width (available_height, out min, out natural);
1687
 
          child_width = Math.fmaxf (min, Math.fminf (natural, available_width));
1688
 
 
1689
 
          child.get_preferred_height (child_width, out min, out natural);
1690
 
          child_height = Math.fmaxf (min, Math.fminf (natural, available_height));
1691
 
 
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;
1696
 
 
1697
 
          if (!child.do_not_render) ;
1698
 
            child.allocate (child_box, flags);
1699
 
 
1700
 
          total_child_height += child_height + spacing;
1701
 
 
1702
 
          if (index >= 0 && index <= 9)
1703
 
          {
1704
 
            Clutter.CairoTexture? keyboard_indicator = null;
1705
 
            keyboard_indicator = keyboard_indicators[(int)index];
1706
 
 
1707
 
            if (keyboard_indicator is Clutter.Actor)
1708
 
              {
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);
1716
 
              }
1717
 
 
1718
 
          index += 1;
1719
 
        }
1720
 
      }
1721
 
 
1722
 
      child_box.x1 = 0;
1723
 
      child_box.x2 = box.get_width ();
1724
 
 
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);
1729
 
 
1730
 
      child_box.y1 = bg_offset - (bg_height - 1);
1731
 
      child_box.y2 = bg_offset + (bg_height - 1) + box.get_height ();
1732
 
 
1733
 
      bgtex.allocate (child_box, flags);
1734
 
 
1735
 
      /* allocate the extra graphics */
1736
 
      top_shadow.get_base_size (out bg_width, out bg_height);
1737
 
      child_box.y1 = -1;
1738
 
      child_box.y2 = bg_height -1;
1739
 
      top_shadow.allocate (child_box, flags);
1740
 
    }
1741
 
 
1742
 
    public override void pick (Clutter.Color color)
1743
 
    {
1744
 
      base.pick (color);
1745
 
      foreach (ScrollerChild child in model)
1746
 
        {
1747
 
          if (child is ScrollerChild && child.opacity > 0 && !child.do_not_render)
1748
 
            {
1749
 
              (child as ScrollerChild).paint ();
1750
 
            }
1751
 
        }
1752
 
 
1753
 
      foreach (ScrollerChild child in child_refs)
1754
 
        {
1755
 
          if (child.do_not_render) continue;
1756
 
          child.paint ();
1757
 
        }
1758
 
    }
1759
 
 
1760
 
 
1761
 
    public override void paint ()
1762
 
    {
1763
 
      bgtex.paint ();
1764
 
 
1765
 
      if (drag_indicator_active)
1766
 
        {
1767
 
          Cogl.set_source_color4f (1.0f, 1.0f, 1.0f,
1768
 
                                   drag_indicator_opacity);
1769
 
 
1770
 
          Cogl.rectangle (0, drag_indicator_position,
1771
 
                          get_width (),
1772
 
                          drag_indicator_position + 2);
1773
 
 
1774
 
        }
1775
 
 
1776
 
      for (int index = draw_btf.size-1; index >= 0; index--)
1777
 
        {
1778
 
          ScrollerChild child = draw_btf[index];
1779
 
          if (child is ScrollerChild && child.opacity > 0 && !child.do_not_render)
1780
 
            {
1781
 
              (child as ScrollerChild).paint ();
1782
 
            }
1783
 
        }
1784
 
 
1785
 
      foreach (ScrollerChild child in draw_ftb)
1786
 
        {
1787
 
          if (child is ScrollerChild && child.opacity > 0 && !child.do_not_render)
1788
 
            {
1789
 
              (child as ScrollerChild).paint ();
1790
 
            }
1791
 
        }
1792
 
 
1793
 
      foreach (ScrollerChild child in model)
1794
 
        {
1795
 
          if ((child in draw_ftb) || (child in draw_btf))
1796
 
            continue;
1797
 
 
1798
 
          if (child is ScrollerChild && child.opacity > 0)
1799
 
            {
1800
 
              (child as ScrollerChild).paint ();
1801
 
            }
1802
 
        }
1803
 
 
1804
 
 
1805
 
      foreach (ScrollerChild child in child_refs)
1806
 
        {
1807
 
          if (child.do_not_render) continue;
1808
 
          child.paint ();
1809
 
        }
1810
 
 
1811
 
      foreach (Clutter.CairoTexture kb_ind in keyboard_indicators)
1812
 
        {
1813
 
          kb_ind.paint ();
1814
 
        }
1815
 
 
1816
 
      top_shadow.paint ();
1817
 
    }
1818
 
 
1819
 
    public override void map ()
1820
 
    {
1821
 
      base.map ();
1822
 
      bgtex.map ();
1823
 
      top_shadow.map ();
1824
 
 
1825
 
      foreach (Clutter.CairoTexture kb_ind in keyboard_indicators)
1826
 
        {
1827
 
          kb_ind.map ();
1828
 
        }
1829
 
 
1830
 
      foreach (ScrollerChild child in model)
1831
 
        {
1832
 
          child.map ();
1833
 
        }
1834
 
 
1835
 
    }
1836
 
 
1837
 
    public override void unmap ()
1838
 
    {
1839
 
      base.unmap ();
1840
 
      bgtex.map ();
1841
 
      top_shadow.map ();
1842
 
 
1843
 
      foreach (Clutter.CairoTexture kb_ind in keyboard_indicators)
1844
 
        {
1845
 
          kb_ind.paint ();
1846
 
        }
1847
 
 
1848
 
      foreach (ScrollerChild child in model)
1849
 
        {
1850
 
          child.unmap ();
1851
 
        }
1852
 
    }
1853
 
  }
1854
 
 
1855
 
}