Source file b_avar.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
(** Animates variables *)
(* This is a special case of dynamic variables, where we know that the variable
   will be used (and thus, updated) at every iteration of the main loop *)
(* If there is an active Avar, the "anim" flag should be set so that the main
   loop does not wait for events *)

(* We could probably do the same thing using standard Dynvar, and emitting
   another event at each iteration. This would be less efficient (?) *)

open B_utils
module Time = B_time
module Mouse = B_mouse

type callback = unit -> unit

type 'a t = {
  mutable value : 'a; (* current value *)
  mutable starting_time : Time.t option;
  mutable finished : bool;
  (* this flag is set to true just *before* the computation of the last value *)
  (* one can create a var with finished = true to behave like a normal var *)
  mutable frame : int; (* the frame when the value was computed *)
  mutable progress : float;
  (* = float in [0,1] giving the percentage of the animation when the last
     v.value was computed. In case of inifinite animation, this is juste the
     elapsed Time (in ms). *)
  init : callback; (* function to be called before the animation starts *)
  ending : callback; (* function to be called when the animation is finished *)
  update : 'a t -> float -> 'a;
  (* update is a function v --> [0,1] --> 'a which gives the new value of the
     variable given the old value, where the interval [0,1] represents the whole
     duration of the animation. In case of inifinite animation, it is a function
     of the elapsed Time in ms. *)
  (* TODO: the first argument ('a t) is not currently used. Remove ? *)
  duration : Time.t; (* a negative duration means an infinite animation *)
}

(* This global variable keeps track of the number of animations that are not
finished. At this point, this is only for debugging. We cannot rely on it for
programming, because if an animation was started on a layout that is not used
anymore, it will never "finish".  Moreover some animations can belong to layouts
that are still alive but hidden (maybe clipped, maybe in a hidden window): in
this case they should not be considered "alive" by the renderer. *)
(* not used *)
let alive_animations = ref 0

(* For the moment, in order to indicate that a var is changed, in case it is not
   detected by Bogue.has_amin, one should use: Trigger.push_var_changed *)

(** this global variable counts the number of frames displayed *)
(* of course it should be increased by the main loop *)
let frame = ref 0

let new_frame () =
  incr frame

let nop () = ()

let fail _ _ = failwith "This variable does not know how to update itself"

let create ?(duration=1000) ?(init=nop) ?(ending=nop) ?(finished=false)
    ?(update=fail) value =
  { value;
    starting_time = None;
    finished;
    frame = !frame;
    progress = 0.;
    init;
    ending;
    update;
    duration
  }

let constant x _ _ = x

(** Simulate a mutable normal variable with a fixed value. The value can be
    changed by changing v.value.

    <OLD>But it cannot update itself: thus v.finished
    should never be set to false. </OLD> *)
(* in fact, one could use update v u = v.value *)
(* this would solve the problem that setting v.value directly doesn't trigger
   has_anim, and thus can become unnoticed by the main event loop *)
let var value =
  let update v _ = v.value in
  create ~finished:true ~duration:0 ~update value

(** create a fixed value. Behaves a bit like var, with important differences: it
    is declared as a new animation (and thus reports "has_anim"), it will always
    have this value when initialized (even if v.value was manually changed) and
    if v.finished is set to false, the initial value will be set again *)
let fixed value =
  create ~duration:0 ~update:(constant value) value

(* one could use this instead of the global variable
   alive_animations *)
let has_anim v = not v.finished

let finished v = v.finished

let started v = v.starting_time <> None

(* this should not be called directly (done by get v) *)
(* in particular, it assumes that v.finished = false *)
let start v =
  if !debug then assert (not v.finished);
  v.init ();
  let t = Time.now () in
  incr alive_animations;
  printd debug_event "New animation started. Total=%d" !alive_animations;
  v.starting_time <- Some t;
  t

let progress v = v.progress

let in_progress v = v.starting_time <> None && not v.finished

let elapsed v =
  if v.duration < 0
  then round v.progress
  else round (v.progress *. (float v.duration))

(** return the final value, or the current value if v was stopped. This does not
    stop the animation and does not trigger 'ending' *)
let final_value v =
  if v.finished then v.value
  else
  if v.duration < 0 then begin
    printd debug_error "Cannot compute the final value for an infinite animation !";
    v.value end
  else v.update v 1.

(** stop the animation, but doesn't change the value *)
(* can be called directly *)
let stop v =
  if v.finished then ()
  else begin
    v.ending ();
    v.finished <- true;
    decr alive_animations;
    printd debug_event "Animation finished. Total remaining=%d" !alive_animations
  end

(** finish the animation and set the value to the expected final value *)
let finish v =
  let final = final_value v in
  stop v;
  v.value <- final

(* reset so that the animation will start again *)
let reset v =
  if v.finished
  then printd debug_warning "Resetting animation."
  else if v.starting_time <> None
  then begin
    printd debug_warning "Animation was reset before ending.";
    decr alive_animations
  end;
  v.starting_time <- None;
  v.finished <- false;
  v.frame <- !frame;
  v.progress <- 0.


(** start the animation and compute the current value of the variable *)
let get v =
  if v.finished || (started v && v.frame = !frame) then v.value
  else
    let u = (* the rescaled time from 0. to 1. *)
      let t = Time.now () in
      let t0 = match v.starting_time with
        | Some t0 -> t0
        | None -> start v
      in
      if v.duration < 0 then float t (* no rescale in this case: infinite animation! *)
      else if Time.(t - t0 >= v.duration)
        then (stop v; 1.)
        else
        (* here v.duration should not be 0 *)
        Time.(float (t - t0) /. (float v.duration)) in

    (* we compute the new value: *)
    let x = v.update v u in
    v.value <- x;
    v.frame <- !frame;
    v.progress <- u;
    x

(** get the old value. This is the way to get the value if one doesn't want to
    start the animation, or if one doesn't want to make any calculation *)
let old v =
  v.value

(** sets the value *)
(* if there is an anim running, this has (almost) no effect, since the new value
   will be computed anyway. v.progress is *not* modified *)
let set v value =
  v.value <- value;
  v.frame <- !frame

(** create a new Avar by composing with f; the old Avar is still active *)
(* this doesn't start the animation *)
(* TODO if this one stop just a msec before the old one, and the old one is only
   active through the new one, then the old one will never "stop"... but maybe
   it is not a problem ? *)
let apply_old f v =
  let value = f (old v) in
  let update _ _ = f (get v) in
  let av = create ~duration:(v.duration-20) ~finished:v.finished ~update value in
  av.starting_time <- v.starting_time;
  av.progress <- v.progress;
  if not v.finished && started v then
    (incr alive_animations;
     printd debug_event "New composite animation started. Total=%d"
       !alive_animations;
    );
  av

let apply f v =
  let value = f (old v) in
  let update _ _ = f (get v) in
  let duration = v.duration - (elapsed v) in
  create ~duration ~finished:v.finished ~update value

type direction =
  | No
  | Left
  | Right
  | Top
  | Bottom
  | TopLeft
  | TopRight
  | BottomLeft
  | BottomRight
  | Random


let slowdown_old u =
  (* between 0 and 1, with speed from 1.8 to 0 *)
  2. *. (sin ((1. +. 2. *. u) *. pi /. 6.) -. 0.5)

let slowdown u =
  (* between 0 and 1, with speed from 2 to 0 *)
  u *. (2. -. u)

let fmin a b : float = min a b

(* for 0 to 1 with prescribed initial speed *)
let initial_slope ~slope =
  let u1 = 2. /. slope in
  if slope >= 2. then fun u -> (* this one is constant = 1 for u >= 2/slope *)
    if u < u1 then (slope *. u *. (1. -. slope *. u /. 4.)) else 1.
  else fun u ->
    u *. (slope +. (1. -. slope) *. u)

(* from x1 to x2 with given initial and final slopes *)
let interpol3 ~slope1 ~slope2 x1 x2 u =
  let dx = x2 -. x1 in
  x1 +. u *. (slope1
              +. u *. (3. *. dx -. 2. *. slope1 -. slope2
                       +. u *. (slope1 +. slope2 -. 2. *. dx)))

(* from x1 to x2 *)
let affine x1 x2 u =
  x1 *. (1. -. u) +. x2 *. u

(* from 0 to x *)
let linear x u =
  x *. u

let reverse u =
  1. -. u

let concat ?(weight=0.5) g1 g2 =
  assert (weight >= 0. && weight <= 1.);
  if weight = 0. then g2
  else if weight = 1. then g1
  else fun u ->
    if u < weight then g1 (u /. weight)
    else g2 ((u -. weight) /. (1. -. weight))

(******** examples of animated variables *********)


(** create a (slowdowned) integer Avar from x1 to x2 *)
let fromto_old ?(duration=300) x1 x2 =
  let update _ u =
    let t = slowdown u in
    round (float x1 *. (1. -. t) +. float x2 *. t) in
  create ~duration ~update x1

(** create a (slowdowned) integer Avar from x1 to x2 *)
let fromto ?(duration=300) ?ending x1 x2 =
  if x1 = x2 then fixed x1
  else let update _ u =
    initial_slope ~slope:1.2 u
    |> affine (float x1) (float x2)
    |> round in
    create ~duration ~update ?ending x1

let fromto_unif ?(duration=300) ?ending x1 x2 =
  if x1 = x2 then fixed x1
  else let update _ u =
         affine (float x1) (float x2) u
         |> round in
    create ~duration ~update ?ending x1

let fromto_float ?(duration=300) ?ending x1 x2 =
  if x1 = x2 then fixed x1
  else let update _ u =
         initial_slope ~slope:1.2 u
         |> affine x1 x2 in
    create ~duration ~update ?ending x1

(** piecevise linear, with 2 pieces *)
let pl2 ?(duration=300) ~via x1 x3 =
  let (weight, x2) = via in
  if x1 = x2 && x2 = x3
  then fixed x1
  else let g1 = affine (float x1) (float x2) in
    let g2 = affine (float x2) (float x3) in
    let update _ u =
      concat ~weight g1 g2 u
      |> round in
    create ~duration ~update x1

(** oscillate around the initial position *)
let oscillate ?(duration = 10000) ?(frequency=5.) amplitude x0 =
  let f = frequency *. 2. *. pi in
  let update _ u =
    x0 + round (float amplitude *. (sin (f *. u))) in
  create ~duration ~update 0

(** linear slide-in animation *)
let slide_in ?(from=Right) ?duration ~pos ~size () =
  let w,h = size in
  let x0, y0 = pos in
  let dx,dy = match from with
    | No -> 0, 0
    | Top -> 0, -h
    | Bottom -> 0, h
    | Right -> w, 0
    | Left -> -w, 0
    | TopLeft -> -w, -h
    | TopRight -> w, -h
    | BottomLeft -> -w, h
    | BottomRight -> w, h
    | Random -> let t = Random.float (2. *. pi) in
      round (float w *. cos t), round (float h *. sin t) in
  let x = fromto ?duration (x0 + dx) x0 in
  let y = fromto ?duration (y0 + dy) y0 in
  (x,y)

(** hoffset animation from h1 to h2 *)
(* for fun, one could use 'apply' instead to compose several Dynvar, but
   certainly less optimized... *)
let show ?(duration = 300) ?init ?ending h1 h2 =
  let update _ u =
    slowdown u
    |> affine (float h1) (float h2)
    |> round in
  create ~duration ~update ?init ?ending h1

let hide ?(duration = 300) ?init ?ending h1 h2 =
  let update _ u =
    reverse u
    |> slowdown
    |> affine (float h1) (float h2)
    |> round in
  create ~duration ~update ?init ?ending h1

(** fade_in animation *)
(* same as fade_out, but accell curve is reversed *)
let fade_in ?(duration = 300) ?(from_alpha = 0.) ?(to_alpha = 1.) () =
  let update _ u =
    reverse u
    |> slowdown
    |> affine to_alpha from_alpha in
  create ~duration ~update from_alpha

(** fade_out animation *)
let fade_out ?ending ?(duration = 300) ?(from_alpha = 1.) ?(to_alpha = 0.) () =
  let update _ u =
    slowdown u
    |> affine from_alpha to_alpha in
  create ~duration ~update ?ending from_alpha

(* (\** mouse position relative to starting position *\)
 * let mouse_motion_x_old ?(threshold=7) window =
 *   let resist = ref true in
 *   let x0 = ref 0 in
 *   let init () = x0 := fst (Mouse.window_pos window) in
 *   let update _ u =
 *     let x = fst (Mouse.window_pos window) in
 *     if !resist then begin
 *       if abs (x - !x0) > threshold then resist := false;
 *       0
 *     end
 *     else x - !x0 in
 *   create ~duration:(-1) ~init ~update 0;; *)

(* 'resist threshold' creates a function which, if x stays close to 0 then
   returns 0 otherwise returns x (even if later x come back close to 0 ) *)
let resist threshold =
  let resist = ref true in
  fun x ->
    if !resist then begin
      if abs x > threshold then resist := false;
      0 end
    else x

(* mouse x position relative to starting position *)
let mouse_motion_x_old ?(threshold=7) ?(dx = 0) window =
  let resist = resist threshold in
  let x0 = ref 0 in
  let init () = x0 := fst (Mouse.window_pos (Lazy.force window)) in
  let update _ _ =
    resist (fst (Mouse.window_pos (Lazy.force window)) - !x0)
    |> (+) dx in
  create ~duration:(-1) ~init ~update dx

(* mouse y position relative to starting position *)
let mouse_motion_y_old ?(threshold=7) ?(dy = 0) window =
  let resist = resist threshold in
  let y0 = ref 0 in
  let init () = y0 := snd (Mouse.window_pos (Lazy.force window)) in
  let update _ _ =
    resist (snd (Mouse.window_pos (Lazy.force window)) - !y0)
    |> (+) dy in
  create ~duration:(-1) ~init ~update dy

(** create a new avar from the current position to x2 with C^1 glue *)
(* warning: this is not guaranteed to stay between x1 and x2 *)
let extendto ~duration v x2 =
  let x1 = v.value in
  if v.finished || not (started v)
  then fromto ~duration x1 x2
  else let slope1 = (* we compute the slope at the current point of v *)
    (* if v has a different duration, the slope (in terms of u) has to be
       rescaled *)
    (* it is difficult to compute the slope this way, since v.update has
       integer values; this is why we take du=0.1 quite large. It would be
       better to have a 'float update' *)
    let du = 0.1 in
    let u1 = v.progress in
    let dx = if u1 < du
      then v.update v (u1 +. du) - x1
      else x1 - (v.update v (u1 -. du)) in
    if duration >= 0 && v.duration >= 0
    then (float (v.duration * dx)) /. (du *. float duration)
    else if duration < 0 && v.duration >= 0
    then (float dx) /. (du *. float duration)
    else if duration >= 0
    then (float (v.duration * dx)) /. du
    else (* both durations are negative *)
      (float dx) /. du
    in
    let update _ u =
      interpol3 ~slope1 ~slope2:(0.) (float x1) (float x2) u
      |> round  in
    (* print_endline ("SLOPE=" ^ (string_of_float slope1));  *)(* DEBUG *)
    create ~duration ~update x1