Timeline: implement rule for relative ZoomWindow positioning
This function allows to move the visible range such that it contains a given time position; the relative location of this point within the visible range however is in turn determined by relating it to the current overall canvas: if we are close to the beginning, the position is also located rather to the left side, and if we're approaching the canvas end, the position tends to the right side... (and yes, I am aware that the variant taking a rational number can be derailed by causing internal numeric overflow, when passing a maliciously crafted rational number, like INT_MAX-1 / INT_MAX )
This commit is contained in:
parent
a9df13f078
commit
29f030d2a1
2 changed files with 138 additions and 25 deletions
|
|
@ -318,19 +318,35 @@ namespace model {
|
|||
fireChangeNotification();
|
||||
}
|
||||
|
||||
/**
|
||||
* explicitly set the visible window,
|
||||
* possibly expanding the canvas to fit.
|
||||
* Typically used to zoom into a user selected range.
|
||||
*/
|
||||
void
|
||||
setVisibleRange (TimeSpan newWindow)
|
||||
{
|
||||
mutateWindow (newWindow);
|
||||
fireChangeNotification();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* the »reverse zoom operation«: zoom out such as to
|
||||
* bring the current window at the designated time span.
|
||||
* Typically the user selects a sub-range, and the current
|
||||
* view is then collapsed accordingly to fit into that range.
|
||||
* As a side effect, the canvas may be expanded significantly.
|
||||
*/
|
||||
void
|
||||
expandVisibleRange (TimeSpan target)
|
||||
{
|
||||
UNIMPLEMENTED ("zoom out to bring the current window at the designated time span");
|
||||
}
|
||||
|
||||
/**
|
||||
* explicitly set the duration of the visible window range,
|
||||
* while keeping the start position; possibly expand canvas.
|
||||
*/
|
||||
void
|
||||
setVisibleDuration (Duration duration)
|
||||
{
|
||||
|
|
@ -338,24 +354,15 @@ namespace model {
|
|||
fireChangeNotification();
|
||||
}
|
||||
|
||||
void
|
||||
setVisiblePos (TimeValue posToShow)
|
||||
{
|
||||
UNIMPLEMENTED ("setVisiblePos");
|
||||
}
|
||||
|
||||
void
|
||||
setVisiblePos (float percentage)
|
||||
{
|
||||
UNIMPLEMENTED ("setVisiblePos");
|
||||
}
|
||||
|
||||
/** scroll by arbitrary offset, possibly expanding canvas. */
|
||||
void
|
||||
offsetVisiblePos (Offset offset)
|
||||
{
|
||||
UNIMPLEMENTED ("offsetVisiblePos");
|
||||
mutateWindow (TimeSpan{startWin_+offset, Duration{afterWin_-startWin_}});
|
||||
fireChangeNotification();
|
||||
}
|
||||
|
||||
/** scroll by increments of half window size, possibly expanding. */
|
||||
void
|
||||
nudgeVisiblePos (int steps)
|
||||
{
|
||||
|
|
@ -364,6 +371,35 @@ namespace model {
|
|||
, dur});
|
||||
}
|
||||
|
||||
/**
|
||||
* scroll the window to bring the denoted position in sight,
|
||||
* retaining the current zoom factor, possibly expanding canvas.
|
||||
*/
|
||||
void
|
||||
setVisiblePos (Time posToShow)
|
||||
{
|
||||
FSecs canvasOffset{posToShow - startAll_};
|
||||
anchorWindowAtPosition (canvasOffset);
|
||||
fireChangeNotification();
|
||||
}
|
||||
|
||||
/** scroll to reveal position designated relative to overall canvas */
|
||||
void
|
||||
setVisiblePos (Rat percentage)
|
||||
{
|
||||
FSecs canvasDuration{afterAll_-startAll_};
|
||||
anchorWindowAtPosition (canvasDuration*percentage);
|
||||
fireChangeNotification();
|
||||
}
|
||||
|
||||
void
|
||||
setVisiblePos (double percentage)
|
||||
{ // use some arbitrary yet significantly large work scale
|
||||
int64_t scale = max (_raw(afterAll_-startAll_), MAX_PX_WIDTH);
|
||||
Rat factor{int64_t(scale*percentage), scale};
|
||||
setVisiblePos (factor);
|
||||
}
|
||||
|
||||
void
|
||||
navHistory()
|
||||
{
|
||||
|
|
@ -432,7 +468,7 @@ namespace model {
|
|||
timeDur = timeDur + TimeValue(1);
|
||||
// resize window relative to anchor point
|
||||
placeWindowRelativeToAnchor (dur);
|
||||
changeWindowDuration (timeDur);
|
||||
establishWindowDuration (timeDur);
|
||||
// re-check metric to maintain precise pxWidth
|
||||
px_per_sec_ = conformMetricToWindow (pxWidth);
|
||||
ENSURE (_FSecs(afterWin_-startWin_) < MAX_TIMESPAN);
|
||||
|
|
@ -522,13 +558,17 @@ namespace model {
|
|||
ensureInvariants();
|
||||
}
|
||||
|
||||
/** @internal change Window TimeSpan, validate and adjust all params */
|
||||
/** @internal change Window TimeSpan, possibly also outside
|
||||
* of the current canvas, which is then expanded;
|
||||
* validate and adjust all params accordingly */
|
||||
void
|
||||
mutateWindow (TimeSpan window)
|
||||
{
|
||||
uint px{pxWidth()};
|
||||
startWin_ = window.start();
|
||||
afterWin_ = ensureNonEmpty (startWin_, window.end());
|
||||
startAll_ = min (startAll_, startWin_);
|
||||
afterAll_ = max (afterAll_, afterWin_);
|
||||
px_per_sec_ = conformMetricToWindow (px);
|
||||
ensureInvariants (px);
|
||||
}
|
||||
|
|
@ -592,11 +632,38 @@ namespace model {
|
|||
{
|
||||
pxWidth = util::limited (1u, pxWidth, MAX_PX_WIDTH);
|
||||
FSecs adaptedWindow{Rat{pxWidth} / px_per_sec_};
|
||||
changeWindowDuration (adaptedWindow);
|
||||
establishWindowDuration (adaptedWindow);
|
||||
px_per_sec_ = conformMetricToWindow (pxWidth);
|
||||
ensureInvariants (pxWidth);
|
||||
}
|
||||
|
||||
/** @internal relocate window anchored at a position relative to canvas,
|
||||
* also placing the anchor position relative within the window
|
||||
* in accordance with the position relative to canvas.
|
||||
* Window will enclose the given position, possibly extending
|
||||
* canvas to fit, afterwards reestablishing all invariants. */
|
||||
void
|
||||
anchorWindowAtPosition (FSecs canvasOffset)
|
||||
{
|
||||
REQUIRE (afterWin_ > startWin_);
|
||||
REQUIRE (afterAll_ > startAll_);
|
||||
uint px{pxWidth()};
|
||||
FSecs duration{afterWin_-startWin_};
|
||||
Rat posFactor = canvasOffset / FSecs{afterAll_-startAll_};
|
||||
posFactor = parabolicAnchorRule (posFactor); // also limited 0...1
|
||||
FSecs partBeforeAnchor = posFactor * duration;
|
||||
startWin_ = startAll_ + (canvasOffset - partBeforeAnchor);
|
||||
establishWindowDuration (duration);
|
||||
startAll_ = min (startAll_, startWin_);
|
||||
afterAll_ = max (afterAll_, afterWin_);
|
||||
px_per_sec_ = conformMetricToWindow (px);
|
||||
ensureInvariants (px);
|
||||
}
|
||||
|
||||
|
||||
/** @internal similar operation as #anchorWindowAtPosition(),
|
||||
* but based on the current window position and without
|
||||
* relocation, rather intended for changing the scale */
|
||||
void
|
||||
placeWindowRelativeToAnchor (FSecs duration)
|
||||
{
|
||||
|
|
@ -605,7 +672,7 @@ namespace model {
|
|||
}
|
||||
|
||||
void
|
||||
changeWindowDuration (TimeVar duration)
|
||||
establishWindowDuration (TimeVar duration)
|
||||
{
|
||||
if (startWin_<= Time::MAX - duration)
|
||||
afterWin_ = startWin_ + duration;
|
||||
|
|
@ -651,12 +718,28 @@ namespace model {
|
|||
|
||||
// use a 3rd degree parabola to favour positions in the middle
|
||||
Rat posFactor = FSecs{startWin_-startAll_} / possibleRange;
|
||||
return parabolicAnchorRule (posFactor);
|
||||
}
|
||||
|
||||
/**
|
||||
* A counter movement rule to place an anchor point, based on a percentage factor.
|
||||
* Used to define the anchor point within the window, depending on the window's position
|
||||
* relative to the overall canvas. Implemented using a cubic parabola, which moves quick
|
||||
* away from the boundaries, while hovering most of the time in the middle area.
|
||||
* @return factor effectively between 0 ... 1 (inclusive)
|
||||
*/
|
||||
static Rat
|
||||
parabolicAnchorRule (Rat posFactor)
|
||||
{
|
||||
posFactor = util::limited (0, posFactor, 1);
|
||||
posFactor = (2*posFactor - 1); // -1 ... +1
|
||||
posFactor = posFactor*posFactor*posFactor; // -1 ... +1 but accelerating towards boundaries
|
||||
posFactor = (posFactor + 1) / 2; // 0 ... 1
|
||||
return posFactor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
fireChangeNotification()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -39389,16 +39389,46 @@
|
|||
<icon BUILTIN="flag-yellow"/>
|
||||
<node CREATED="1667528185832" ID="ID_1091652690" MODIFIED="1667528199122" TEXT="schrittweise incl Canvas erweitern"/>
|
||||
<node CREATED="1667528199785" ID="ID_1692959674" MODIFIED="1667528207840" TEXT="limits bei relativem positionieren"/>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1667787116332" ID="ID_538942533" MODIFIED="1667787124519" TEXT="relativ nach Position und Prozentsatz">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
<node COLOR="#338800" CREATED="1667787116332" ID="ID_538942533" MODIFIED="1667871911986" TEXT="relativ nach Position und Prozentsatz">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node COLOR="#435e98" CREATED="1667871912848" ID="ID_28548966" MODIFIED="1667871963481" TEXT="das läuft auf eine Variange des relativen-Ankers hinaus">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1667528209948" ID="ID_914020436" MODIFIED="1667787027641" TEXT="explizit setzen und Canvas erweitern">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1667787036706" ID="ID_192598582" MODIFIED="1667787077769" TEXT="muß hierfür die Limitierung explizit aufheben">
|
||||
<node CREATED="1667871926941" ID="ID_56646265" MODIFIED="1667872010814" TEXT="kann aber nur die kubische Positions-Regel wiederverwenden...">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
alles Andere ist in diesem Fall eben doch speziell; wir kommen bereits mit einer relativen Angabe, und die aktuelle Fensterposition spielt keine Rolle
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1667528209948" ID="ID_914020436" MODIFIED="1667871908657" TEXT="explizit setzen und Canvas erweitern">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node COLOR="#435e98" CREATED="1667787036706" ID="ID_192598582" MODIFIED="1667871690775" TEXT="muß hierfür die Limitierung explizit aufheben">
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1667787054759" ID="ID_280714694" MODIFIED="1667787063999" TEXT="auf welcher Ebene am Besten einbauen?">
|
||||
<icon BUILTIN="help"/>
|
||||
<node CREATED="1667787054759" ID="ID_280714694" MODIFIED="1667871751969" TEXT="neue Festlegung: alle mutateWindow()-Aufrufe können Canvas erweitern">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
<node CREATED="1667871761244" ID="ID_550353046" MODIFIED="1667871901718" TEXT="hierzu ändere ich die Spezifikation: auch relatives Zoomen kann nun Canvas erweitern">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
warum auch nicht?
|
||||
</p>
|
||||
<p>
|
||||
Das ist dann so viel konsistenter, insofern nun nämlich genau die Funktionen, die die visible-Window-Position setzen, auch den Canvas erweitern dürfen — aber alle anderen Funktionen stoßen an der Canvas-Grenze an
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
|
|
|
|||
Loading…
Reference in a new issue