Lumiera 0.pre.04~rc.1
»edit your freedom«
Loading...
Searching...
No Matches
zoom-window.hpp
Go to the documentation of this file.
1/*
2 ZOOM-WINDOW.hpp - generic translation from domain to screen coordinates
3
4 Copyright (C)
5 2018, Hermann Vosseler <Ichthyostega@web.de>
6
7  **Lumiera** is free software; you can redistribute it and/or modify it
8  under the terms of the GNU General Public License as published by the
9  Free Software Foundation; either version 2 of the License, or (at your
10  option) any later version. See the file COPYING for further details.
11
12*/
13
14
81#ifndef STAGE_MODEL_ZOOM_WINDOW_H
82#define STAGE_MODEL_ZOOM_WINDOW_H
83
84
85#include "lib/error.hpp"
86#include "lib/rational.hpp"
88#include "lib/nocopy.hpp"
89#include "lib/util.hpp"
90
91#include <limits>
92#include <functional>
93#include <array>
94
95
96namespace stage {
97namespace model {
98
102 using lib::time::TimeVar;
103 using lib::time::Offset;
104 using lib::time::FSecs;
105 using lib::time::Time;
106
107 using util::Rat;
108 using util::rational_cast;
110 using util::reQuant;
111
112 using util::min;
113 using util::max;
114 using util::sgn;
115
116 namespace {
125 inline FSecs
126 _FSecs (TimeValue const& timeVal)
127 {
128 return FSecs{_raw(timeVal), TimeValue::SCALE};
129 }
130
137 inline bool
138 isMicroGridAligned (FSecs duration)
139 {
140 return 0 == Time::SCALE % duration.denominator();
141 }
142
143 inline double
144 approx (Rat r)
145 {
146 return util::rational_cast<double> (r);
147 }
148
150
154 inline TimeVar
155 operator+ (Time const& tval, TimeVar const& tvar)
156 {
157 return TimeVar(tval) += tvar;
158 }
159 inline TimeVar
160 operator- (Time const& tval, TimeVar const& tvar)
161 {
162 return TimeVar(tval) -= tvar;
163 }
164 }
165
166
169
170 namespace {// initial values (rather arbitrary)
171 const FSecs DEFAULT_CANVAS{23};
172 const Rat DEFAULT_METRIC{25};
173 const uint MAX_PX_WIDTH{100000};
175 const FSecs MICRO_TICK{1_r/Time::SCALE};
176
181 const int64_t LIM_HAZARD {int64_t{1} << 40 };
183 const int64_t MAXDIM {util::ilog2 (std::numeric_limits<int64_t>::max())};
184
185 inline int
186 toxicDegree (Rat poison, const int64_t THRESHOLD =HAZARD_DEGREE)
187 {
188 int64_t magNum = util::ilog2(abs(poison.numerator()));
189 int64_t magDen = util::ilog2(abs(poison.denominator()));
190 int64_t degree = max (magNum, magDen);
191 return max (0, degree - THRESHOLD);
192 }
193 }
194
195
196
197
198
199 /******************************************************/
215 {
219
220 std::function<void()> changeSignal_{};
221
222 public:
223 ZoomWindow (uint pxWidth, TimeSpan timeline =TimeSpan{Time::ZERO, DEFAULT_CANVAS})
224 : startAll_{ensureNonEmpty(timeline).start()}
225 , afterAll_{ensureNonEmpty(timeline).end()}
229 {
230 pxWidth = this->pxWidth();
231 ASSERT (0 < pxWidth);
234 }
235
236 ZoomWindow (TimeSpan timeline =TimeSpan{Time::ZERO, DEFAULT_CANVAS})
237 : ZoomWindow{0, timeline} //see establishMetric()
238 { }
239
240 TimeSpan
242 {
244 }
245
247 visible() const
248 {
250 }
251
252 Rat
254 {
255 return px_per_sec_;
256 }
257
258 uint
259 pxWidth() const
260 {
261 REQUIRE (startWin_ < afterWin_);
263 }
264
265
266
267 /* === Mutators === */
268
277 void
283
290 void
296
305 void
306 nudgeMetric (int steps)
307 {
308 setMetric(
309 steps > 0 ? Rat{px_per_sec_.numerator() << steps
310 ,px_per_sec_.denominator()}
311 : Rat{px_per_sec_.numerator()
312 ,px_per_sec_.denominator() << -steps});
313 }
314
325 void
327 {
328 mutateRanges (overall, visible);
330 }
331
339 void
341 {
342 mutateCanvas (range);
344 }
345
346 void
352
353 void
355 {
356 mutateCanvas (TimeSpan{startAll_, duration});
358 }
359
360 void
366
372 void
374 {
375 mutateWindow (newWindow);
377 }
378
386 void
388 {
389 // Formulation: Assuming the current window was generated from TimeSpan
390 // by applying an affine-linear transformation f = a·x + b
391 FSecs tarDur = _FSecs(target.end()-target.start());
392 Rat a = FSecs{afterWin_-startWin_};
393 Rat b = FSecs{startWin_}*_FSecs(target.end()) - FSecs{afterWin_}*_FSecs((target.start()));
394 a /= tarDur;
395 b /= tarDur;
396 Time startNew {a * FSecs{startWin_} + b};
397 Time afterNew {a * FSecs{afterWin_} + b};
398
399 mutateWindow(TimeSpan{startNew, afterNew});
401 }
402
410 void
412 {
413 mutateDuration (_FSecs(duration));
415 }
416
418 void
424
426 void
427 nudgeVisiblePos (int64_t steps)
428 {
429 FSecs dur{afterWin_-startWin_};
430 int64_t limPages = 2 * rational_cast<int64_t> (MAX_TIMESPAN/dur);
431 steps = util::limited(-limPages, steps, +limPages);
432 FSecs scroll = steps * dur/2; // move by half window sized steps
433 if (abs(scroll) < MICRO_TICK) scroll = sgn(steps) * MICRO_TICK;
435 }
436
441 void
442 setVisiblePos (Time posToShow)
443 {
444 FSecs canvasOffset{posToShow - startAll_};
445 anchorWindowAtPosition (canvasOffset);
447 }
448
450 void
451 setVisiblePos (Rat percentage)
452 {
453 FSecs canvasDuration{afterAll_-startAll_};
454 anchorWindowAtPosition (scaleSafe (canvasDuration, percentage));
456 }
457
458 void
459 setVisiblePos (double percentage)
460 { // use some arbitrary yet significantly large work scale
461 int64_t scale = max (_raw(afterAll_-startAll_), MAX_PX_WIDTH);
462 Rat factor{int64_t(scale*percentage), scale};
463 setVisiblePos (factor);
464 }
465
466 void
468 {
469 UNIMPLEMENTED ("navigate Zoom History");
470 }
471
472
474 template<class FUN>
475 void
477 {
478 changeSignal_ = std::forward<FUN> (trigger);
479 }
480
481 void
483 {
484 changeSignal_ = std::function<void()>();
485 }
486
487
488 private:
489 void
494
495
496 /* === utility functions to handle dangerous fractional values === */
497
519 static Rat
520 detox (Rat poison)
521 {
522 int toxicity = toxicDegree (poison);
523 return toxicity ? reQuant (poison, max (poison.denominator() >> toxicity, 64))
524 : poison;
525 }
526
537 static FSecs
538 scaleSafe (FSecs duration, Rat factor)
539 {
540 if (util::can_represent_Product(duration, factor))
541 // just calculate ordinary numbers...
542 return duration * factor;
543 else
544 {
545 auto guess{approx(duration) * approx (factor)};
546 if (approx(MAX_TIMESPAN) < abs(guess))
547 return MAX_TIMESPAN * sgn(guess); // exceeds limits of time representation => cap the result
548 if (0 == guess)
549 return 0;
556 struct ReductionStrategy
557 {
558 int64_t f1;
559 int64_t u;
560 int64_t q;
561 int64_t f2;
562 bool invert;
563
564 int64_t
565 determineLimit()
566 {
567 REQUIRE (u != 0);
568 return isFeasible()? u : 0;
569 }
570
571 Rat
572 calculateResult()
573 {
574 REQUIRE (isFeasible());
575 f2 = reQuant (f2, q, u);
576 return invert? Rat{f2, f1}
577 : Rat{f1, f2};
578 }
579
580 bool
581 isFeasible()
582 { // Note: factors are nonzero,
583 REQUIRE (u and q and f2);// otherwise exit after pre-check above
584 int dim_u = util::ilog2 (abs (u));
585 int dim_q = util::ilog2 (abs (q));
586 if (dim_q > dim_u) return true; // requantisation will reduce size and thus no danger
587 int dim_f = util::ilog2 (abs (f2));
588 int deltaQ = dim_u - dim_q; // how much q must be increased to match u
589 int headroom = MAXDIM - dim_f; // how much the counter factor f2 can be increased
590 return headroom > deltaQ;
591 }
592 };
593 using Cases = std::array<ReductionStrategy, 4>;
594 // There are four possible strategy configurations.
595 // One case stands out, insofar this factor is guaranteed to be present:
596 // because one of the numbers is a quantised Time, it has Time::SCALE as denominator,
597 // maybe after cancelling out some further common integral factors
598 auto [reduction,rem] = util::iDiv (Time::SCALE, duration.denominator());
599 if (rem != 0) reduction = 1; // when duration is not µ-Tick quantised
600 int64_t durationQuant = duration.denominator()*reduction;
601 int64_t durationTicks = duration.numerator()*reduction;
602
603 //-f1--------------------+-u-------------------+-q---------------------+-f2--------------------+-invert--
604 Cases cases{{{durationTicks , durationQuant , factor.numerator() , factor.denominator() , false}
605 ,{factor.numerator() , factor.denominator(), duration.numerator() , duration.denominator(), false}
606 ,{duration.denominator(), duration.numerator(), factor.denominator() , factor.numerator() , true}
607 ,{factor.denominator() , factor.numerator() , duration.denominator(), duration.numerator() , true}
608 }};
609 // However, some of the other cases may yield a larger denominator to be cancelled out,
610 // and thus lead to a smaller error margin. Attempt thus to find the best strategy...
611 ReductionStrategy* solution{nullptr};
612 int64_t maxLimit = 0;
613 for (auto& candidate: cases)
614 {
615 int64_t limit = candidate.determineLimit();
616 if (limit > maxLimit)
617 {
618 maxLimit = limit;
619 solution = &candidate;
620 }
621 }
622
623 ASSERT (solution and maxLimit > 0);
624 return detox (solution->calculateResult());
625 }
626 }
627
635 static FSecs
636 addSafe (FSecs t1, FSecs t2)
637 {
638 if (util::can_represent_Sum (t1,t2))
639 // directly calculate ordinary numbers...
640 return t1 + t2;
641 else
642 {
643 auto guess{approx(t1) + approx(t2)};
644 if (approx(MAX_TIMESPAN) < abs(guess))
645 return MAX_TIMESPAN * sgn(guess); // exceeds limits => cap the result
646
647 // re-Quantise numbers to achieve a common denominator,
648 // thus avoiding to multiply numerators for normalisation
649 int64_t n1 = t1.numerator();
650 int64_t d1 = t1.denominator();
651 int s1 = sgn(n1)*sgn(d1);
652 n1 = abs(n1); d1 = abs(d1);
653 int64_t n2 = t2.numerator();
654 int64_t d2 = t2.denominator();
655 int s2 = sgn(n2)*sgn(d2);
656 n2 = abs(n2); d2 = abs(d2);
657 // quantise to smaller denominator to avoid increasing any numerator
658 int64_t u = d1<d2? d1:d2;
659 if (u < Time::SCALE)
660 // regarding precision, quantising to µ-grid is the better solution
661 u = Time::SCALE;
662 else //re-quantise to common denominator more fine-grained than µ-grid
663 if (s1*s2 > 0 // check numerators to detect danger of wrap-around
664 and (MAXDIM<=util::ilog2(n1) or MAXDIM<=util::ilog2(n2)))
665 u >>= 1; // danger zone! wrap-around imminent
666
667 n1 = d1==u? n1 : reQuant (n1,d1, u);
668 n2 = d2==u? n2 : reQuant (n2,d2, u);
669 FSecs res{s1*n1 + s2*n2, u};
670
671 auto f128 = [](Rat n){ return rational_cast<long double>(n); }; // can't use the guess from above,
672 ENSURE (abs (f128(res) - (f128(t1)+f128(t2))) < 1.0/u); // double precision is not sufficient
673 return detox (res);
674 }
675 }
676
677
678
679
680 /* === establish and maintain invariants === */
681 /*
682 * - oriented and non-empty windows
683 * - never alter given pxWidth
684 * - zoom metric factor < max zoom
685 * - visibleWindow ⊂ Canvas
686 */
687
688 static TimeSpan
690 {
691 return TimeSpan{span.start()
692 ,util::isnil(span.duration())? Duration{DEFAULT_CANVAS}
693 : span.duration()
694 }.conform();
695 }
696
698 static void
699 ENSURE_matchesExpectedPixWidth (Rat zoomFactor, FSecs duration, uint pxWidth)
700 {
701 auto sizeAtRequestedScale = approx(zoomFactor) * approx(duration);
702 ENSURE (abs(pxWidth - sizeAtRequestedScale) <= 1
703 ,"ZoomWindow: established size or metric misses expectation "
704 "by more than 1px. %upx != %1.6f expected pixel."
705 , pxWidth, sizeAtRequestedScale);
706 }
707
711 static int64_t
712 calcPixelsForDurationAtScale (Rat zoomFactor, FSecs duration)
713 {// break down the integer division into several steps...
714 auto zn = zoomFactor.numerator();
715 auto zd = zoomFactor.denominator();
716 auto dn = duration.numerator();
717 auto dd = duration.denominator();
718 auto [secs,r] = util::iDiv (dn, dd); // split duration in full seconds and rest
719 auto [px1,r1] = util::iDiv (secs*zn, zd); // calc pixels required for full seconds
720 auto [px2,r2] = util::iDiv (r*zn, dd*zd); // calc pixels required for rest duration
721 auto pxr = (r1*dd +r2) /(dd*zd); // and calculate integer div for combined remainders
722 ENSURE (0 <= px1 and 0 <= px2 and 0<= pxr);
723 return px1 + px2 + pxr;
724 }
725
728 static FSecs
730 {
731 return min (FSecs{LIM_HAZARD * pxWidth, 1000}, MAX_TIMESPAN);
732 } // Note: denominator 1000 is additional safety margin
733 // wouldn't be necessary, but makes detox(largeTime) more precise
734
742 Rat
743 optimiseMetric (uint pxWidth, FSecs dur, Rat rawMetric)
744 {
745 using util::ilog2;
746 REQUIRE (0 < pxWidth and 0 < dur and 0 < rawMetric);
747 REQUIRE (isMicroGridAligned (dur));
748 // circumvent numeric problems due to excessive large factors
749 int64_t magDen = ilog2(rawMetric.denominator());
750 int reduction = toxicDegree (rawMetric);
751 int quant = max (magDen-reduction, 16);
752 // re-quantise metric into power of two <= 2^40 (headroom 22 bit)
753 // Known to work always, since 9e-10 < metric < 2e+6
754 Rat adjMetric = util::reQuant (rawMetric, int64_t(1) << quant);
755
756 // Correct that metric to reproduce expected pxWidth...
757 // Retain reduced denominator, but optimise the numerator
758 // pixel = trunc{ metric*duration }
759 double epsilon = std::numeric_limits<double>::epsilon()
760 , dn = dur.numerator()
761 , dd = dur.denominator()
762 , md = adjMetric.denominator()
763 , mn = (pxWidth+epsilon)*md*dd/dn;
764 // construct optimised zoom metric result
765 int64_t num = mn, den = adjMetric.denominator();
766 if (epsilon < mn - num)
767 {// optimisation found inter-grid result -- increase precision
768 int headroom = max (1, HAZARD_DEGREE - max (ilog2(num), ilog2(den)));
769 int64_t scale = int64_t(1) << headroom;
770 num = scale*mn; // quantise again with increased resolution
771 den = scale*den; // at least factor 2 to get some improvement
772 if (pxWidth > dn/dd*num/den) // If still some remaining error....
773 ++num; // round up to be sure to hit the next higher pixel count
774 }
775 adjMetric = Rat{num, den};
776 ENSURE (pxWidth == calcPixelsForDurationAtScale (adjMetric, dur));
777 double impliedDur = double(pxWidth)*den/num;
778 double relError = abs(dn/dd /impliedDur -1);
779 double quantErr = 1.0/(num-1);
780 ENSURE (quantErr > relError, "metric misses duration by "
781 "%3.2f%% > %3.2f%% (=relative quantisation error)"
782 ,100*relError, 100.0*quantErr);
783 return adjMetric;
784 }
785
786
787 static Rat
788 establishMetric (uint pxWidth, Time startWin, Time afterWin)
789 {
790 REQUIRE (startWin < afterWin);
791 FSecs dur = _FSecs(afterWin-startWin);
792 if (pxWidth == 0 or pxWidth > MAX_PX_WIDTH) // default to sane pixel width
793 pxWidth = max<uint> (1, rational_cast<uint> (DEFAULT_METRIC * dur));
794 Rat metric = Rat(pxWidth) / dur;
795 // rational arithmetic ensures we can always reproduce the pxWidth
796 ENSURE (pxWidth == calcPixelsForDurationAtScale (metric, dur));
797 ENSURE (0 < metric);
798 return metric;
799 }
800
803 void
804 conformWindowToMetric (Rat changedMetric)
805 {
806 REQUIRE (changedMetric > 0);
807 REQUIRE (afterWin_> startWin_);
808 FSecs dur{afterWin_-startWin_};
810 dur = Rat(pxWidth) / detox (changedMetric);
811 dur = min (dur, MAX_TIMESPAN);// limit maximum window size
812 dur = max (dur, MICRO_TICK); // prevent window going void
813 TimeVar timeDur{Duration{dur}};
814 // prefer bias towards increased window instead of increased metric
815 if (not isMicroGridAligned (dur))
816 timeDur = timeDur + TimeValue(1);
817 // resize window relative to anchor point
820 // re-check metric to maintain precise pxWidth
822 ENSURE (_FSecs(afterWin_-startWin_) <= MAX_TIMESPAN);
824 }
825
826 Rat
828 {
829 REQUIRE (pxWidth > 0);
830 REQUIRE (afterWin_> startWin_);
831 FSecs dur{afterWin_-startWin_};
832 Rat adjMetric = Rat(pxWidth) / dur;
833 if (not toxicDegree(adjMetric)
834 and pxWidth == calcPixelsForDurationAtScale (adjMetric, dur))
835 return adjMetric;
836 else
837 return optimiseMetric(pxWidth, dur, adjMetric);
838 }
839
846 void
848 {
849 REQUIRE (pxWidth > 0);
850 FSecs dur{afterWin_-startWin_};
851 if (dur > maxSaneWinExtension (pxWidth))
852 {
856 }
857 }
858
859 void
861 {
862 FSecs dur{afterWin_-startWin_};
863 REQUIRE (dur <= MAX_TIMESPAN);
866 if (dur <= _FSecs(afterAll_-startAll_))
867 {//possibly shift into current canvas
868 if (afterWin_ > afterAll_)
869 {
870 Offset shift{afterWin_ - afterAll_};
871 startWin_ -= shift;
872 afterWin_ -= shift;
873 }
874 else
875 if (startWin_ < startAll_)
876 {
877 Offset shift{startAll_ - startWin_};
878 startWin_ += shift;
879 afterWin_ += shift;
880 }
881 }
882 else
883 {//need to cap window to fit into canvas
886 }
887 ENSURE (startAll_ <= startWin_);
888 ENSURE (afterWin_ <= afterAll_);
889 ENSURE (Time::MIN <= startWin_);
890 ENSURE (afterWin_ <= Time::MAX);
891 }
892
893 void
894 conformToBounds (Rat changedMetric)
895 {
896 if (changedMetric > ZOOM_MAX_RESOLUTION)
897 {
898 changedMetric = ZOOM_MAX_RESOLUTION;
899 conformWindowToMetric (changedMetric);
900 }
903 ENSURE (Time::MIN <= startWin_);
904 ENSURE (afterWin_ <= Time::MAX);
905 ENSURE (startAll_ <= startWin_);
906 ENSURE (afterWin_ <= afterAll_);
908 ENSURE (px_per_sec_ <= changedMetric); // bias
909 }
910
921 void
923 {
924 if (px==0) px = pxWidth();
928 }
929
930
931
932 /* === adjust and coordinate window parameters === */
933
936 void
938 {
939 startAll_ = ensureNonEmpty(canvas).start();
940 afterAll_ = ensureNonEmpty(canvas).end();
942 }
943
947 void
949 {
950 uint px{pxWidth()};
951 startWin_ = ensureNonEmpty(window).start();
952 afterWin_ = ensureNonEmpty(window).end();
956 ensureInvariants (px);
957 }
958
961 void
963 {
964 uint px{pxWidth()};
965 startAll_ = ensureNonEmpty(canvas).start();
966 afterAll_ = ensureNonEmpty(canvas).end();
967 startWin_ = ensureNonEmpty(window).start();
968 afterWin_ = ensureNonEmpty(window).end();
970 ensureInvariants (px);
971 }
972
976 void
977 mutateScale (Rat changedMetric)
978 {
979 uint px{pxWidth()};
980 changedMetric = max (changedMetric, px / maxSaneWinExtension(px));
981 changedMetric = min (detox(changedMetric), ZOOM_MAX_RESOLUTION);
982 if (changedMetric == px_per_sec_) return;
983 conformWindowToMetric (changedMetric);
984 ensureInvariants (px);
985 }
986
989 void
990 mutateDuration (FSecs duration, uint px =0)
991 {
992 if (px==0)
993 px = pxWidth();
994 if (duration <= 0)
995 duration = DEFAULT_CANVAS;
996 else if (duration > maxSaneWinExtension (px))
997 duration = maxSaneWinExtension (px);
999 establishWindowDuration (duration);
1001 ensureInvariants (px);
1002 }
1003
1006 void
1008 {
1009 pxWidth = util::limited (1u, pxWidth, MAX_PX_WIDTH);
1010 FSecs adaptedWindow{Rat{pxWidth} / px_per_sec_};
1011 adaptedWindow = max (adaptedWindow, MICRO_TICK); // prevent void window
1012 adaptedWindow = min (adaptedWindow, maxSaneWinExtension (pxWidth));
1013 establishWindowDuration (adaptedWindow);
1015 }
1016
1022 void
1023 anchorWindowAtPosition (FSecs canvasOffset)
1024 {
1025 REQUIRE (afterWin_ > startWin_);
1026 REQUIRE (afterAll_ > startAll_);
1027 uint px{pxWidth()};
1028 FSecs duration{afterWin_-startWin_};
1029 Rat posFactor = canvasOffset / FSecs{afterAll_-startAll_};
1030 posFactor = parabolicAnchorRule (posFactor); // also limited 0...1
1031 FSecs partBeforeAnchor = scaleSafe (duration, posFactor);
1032 startWin_ = startAll_ + Offset{addSafe (canvasOffset, -partBeforeAnchor)};
1033 establishWindowDuration (duration);
1034 startAll_ = min (startAll_, startWin_);
1035 afterAll_ = max (afterAll_, afterWin_);
1036 ensureInvariants (px);
1037 }
1038
1039
1043 void
1045 {
1046 FSecs partBeforeAnchor = scaleSafe(duration, relativeAnchor());
1047 startWin_ = Time{anchorPoint()} - Time{partBeforeAnchor};
1048 }
1049
1050 void
1052 {
1053 if (startWin_<= Time::MAX - duration)
1054 afterWin_ = startWin_ + duration;
1055 else
1056 {
1057 startWin_ = Time::MAX - duration;
1059 }
1060 }
1061
1062
1063
1074 FSecs
1076 {
1078 }
1079
1087 Rat
1089 {
1090 // the visible window itself has to fit in, which reduces the action range
1091 FSecs possibleRange = (afterAll_-startAll_) - (afterWin_-startWin_);
1092 if (possibleRange <= 0) // if there is no room for scrolling...
1093 return 1_r/2; // then anchor zooming in the middle
1094
1095 // use a 3rd degree parabola to favour positions in the middle
1096 Rat posFactor = FSecs{startWin_-startAll_} / possibleRange;
1097 return parabolicAnchorRule (posFactor);
1098 }
1099
1109 static Rat
1110 parabolicAnchorRule (Rat posFactor)
1111 {
1112 posFactor = util::limited (0, posFactor, 1);
1113 if (toxicDegree(posFactor, 20)) // prevent integer wrap
1114 posFactor = util::reQuant(posFactor, 1 << 20);
1115 posFactor = (2*posFactor - 1); // -1 ... +1
1116 posFactor = posFactor*posFactor*posFactor; // -1 ... +1 but accelerating towards boundaries
1117 posFactor = (posFactor + 1) / 2; // 0 ... 1
1118 posFactor = util::limited (0, posFactor, 1);
1119 return detox (posFactor);
1120 }
1121 };
1122
1123
1124
1125}} // namespace stage::model
1126#endif /*STAGE_MODEL_ZOOM_WINDOW_H*/
Duration is the internal Lumiera time metric.
static const Duration MAX
maximum possible temporal extension
Offset measures a distance in time.
A time interval anchored at a specific point in time.
Time start() const
Duration & duration()
basic constant internal time value.
static const raw_time_64 SCALE
Number of micro ticks (µs) per second as basic time scale.
a mutable time value, behaving like a plain number, allowing copy and re-accessing
Lumiera's internal time value datatype.
static const Time MIN
static const Time ZERO
static const Time MAX
A component to ensure uniform handling of zoom scale and visible interval on the timeline.
void setVisiblePos(Rat percentage)
scroll to reveal position designated relative to overall canvas
TimeSpan overallSpan() const
void nudgeVisiblePos(int64_t steps)
scroll by increments of half window size, possibly expanding.
static FSecs scaleSafe(FSecs duration, Rat factor)
Scale a possibly large time duration by a rational factor, while attempting to avoid integer wrap-aro...
void setVisiblePos(Time posToShow)
scroll the window to bring the denoted position in sight, retaining the current zoom factor,...
void offsetVisiblePos(Offset offset)
scroll by arbitrary offset, possibly expanding canvas.
static Rat parabolicAnchorRule(Rat posFactor)
A counter movement rule to place an anchor point, based on a percentage factor.
ZoomWindow(uint pxWidth, TimeSpan timeline=TimeSpan{Time::ZERO, DEFAULT_CANVAS})
static Rat detox(Rat poison)
Check and possibly sanitise a rational number to avoid internal numeric overflow.
static void ENSURE_matchesExpectedPixWidth(Rat zoomFactor, FSecs duration, uint pxWidth)
Assertion helper: resulting pxWidth matches expectations.
void calibrateExtension(uint pxWidth)
Define the extension of the window in pixels.
void mutateCanvas(TimeSpan canvas)
void nudgeMetric(int steps)
scale up or down on a 2-logarithmic scale.
void setOverallDuration(Duration duration)
void setRanges(TimeSpan overall, TimeSpan visible)
Set both the overall canvas, as well as the visible part within that canvas.
void expandVisibleRange(TimeSpan target)
the »reverse zoom operation«: zoom out such as to bring the current window at the designated time spa...
Rat conformMetricToWindow(uint pxWidth)
void setOverallRange(TimeSpan range)
redefine the overall canvas range.
void attachChangeNotification(FUN &&trigger)
Attach a λ or functor to be triggered on each actual change.
void setOverallStart(TimeValue start)
void placeWindowRelativeToAnchor(FSecs duration)
void anchorWindowAtPosition(FSecs canvasOffset)
void mutateRanges(TimeSpan canvas, TimeSpan window)
FSecs anchorPoint() const
The anchor point or centre for zooming operations applied to the visible window.
static Rat establishMetric(uint pxWidth, Time startWin, Time afterWin)
static FSecs addSafe(FSecs t1, FSecs t2)
Calculate sum (or difference) of possibly large time durations, avoiding integer wrap-around.
void mutateScale(Rat changedMetric)
void conformWindowToMetric(Rat changedMetric)
this is the centrepiece of the whole zoom metric logic...
TimeSpan visible() const
static FSecs maxSaneWinExtension(uint pxWidth)
window size beyond that limit would lead to numerically dangerous zoom factors (pixel/duration)
void ensureInvariants(uint px=0)
Procedure to (re)establish the invariants.
void adaptWindowToPixels(uint pxWidth)
void setMetric(Rat px_per_sec)
explicitly set the zoom factor, defined as pixel per second
Rat relativeAnchor() const
define at which proportion to the visible window's duration the anchor should be placed
ZoomWindow(TimeSpan timeline=TimeSpan{Time::ZERO, DEFAULT_CANVAS})
void mutateWindow(TimeSpan window)
void setVisibleStart(TimeValue start)
static int64_t calcPixelsForDurationAtScale(Rat zoomFactor, FSecs duration)
calculate rational_cast<uint> (zoomFactor * duration)
void establishWindowDuration(Duration duration)
void setVisibleRange(TimeSpan newWindow)
explicitly set the visible window, possibly expanding the canvas to fit.
void setVisibleDuration(Duration duration)
explicitly set the duration of the visible window range, working around the relative anchor point; po...
static TimeSpan ensureNonEmpty(TimeSpan const &span)
Rat optimiseMetric(uint pxWidth, FSecs dur, Rat rawMetric)
Reform the effective metric in all dangerous corner cases.
std::function< void()> changeSignal_
void mutateDuration(FSecs duration, uint px=0)
void conformWindowToMetricLimits(uint pxWidth)
The zoom metric factor must not become "poisonous".
void setVisiblePos(double percentage)
void conformToBounds(Rat changedMetric)
Any copy and copy construction prohibited.
Definition nocopy.hpp:38
Lumiera error handling (C++ interface).
unsigned int uint
Definition integral.hpp:29
long double f128
Definition integral.hpp:36
boost::rational< int64_t > FSecs
rational representation of fractional seconds
const int64_t LIM_HAZARD
Maximum quantiser to be handled in fractional arithmetics without hazard.
int toxicDegree(Rat poison, const int64_t THRESHOLD=HAZARD_DEGREE)
const Rat ZOOM_MAX_RESOLUTION
the deepest zoom is to use 2px per micro-tick
Lumiera GTK UI implementation root.
Definition guifacade.cpp:37
constexpr int ilog2(I num)
Integral binary logarithm (disregarding fractional part)
IDiv< I > iDiv(I num, I den)
auto max(IT &&elms)
boost::rational< int64_t > Rat
Definition rational.hpp:73
bool can_represent_Product(int64_t a, int64_t b)
Definition rational.hpp:80
constexpr int sgn(NUM n)
Definition util.hpp:61
constexpr NUM limited(NB lowerBound, NUM val, NB upperBound)
force a numeric to be within bounds, inclusively
Definition util.hpp:91
int64_t reQuant(int64_t num, int64_t den, int64_t u)
Re-Quantise a number into a new grid, truncating to the next lower grid point.
Definition rational.hpp:120
auto min(IT &&elms)
bool isnil(lib::time::Duration const &dur)
bool can_represent_Sum(Rat a, Rat b)
Definition rational.hpp:95
Mix-Ins to allow or prohibit various degrees of copying and cloning.
Rational number support, based on boost::rational.
a family of time value like entities and their relationships.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...