Timeline: ZoomWindow implementation draft
implement the first test case: nudge the zoom factor ⟹ scale factor doubled ⟹ visible window reduced to half size ⟹ visible window placed in the middle of the overall range
This commit is contained in:
parent
b3fe6e16c6
commit
7145d0d9ce
6 changed files with 255 additions and 92 deletions
|
|
@ -23,14 +23,47 @@
|
||||||
/** @file timevalue.hpp
|
/** @file timevalue.hpp
|
||||||
** a family of time value like entities and their relationships.
|
** a family of time value like entities and their relationships.
|
||||||
** This is the foundation for the Lumiera time handling framework. On the implementation
|
** This is the foundation for the Lumiera time handling framework. On the implementation
|
||||||
** level, time values are represented as 64bit integer values \c gavl_time_t. But for the
|
** level, time values are represented as 64bit integer values `gavl_time_t`. But for the
|
||||||
** actual use, we create several kinds of time "values", based on their logical properties.
|
** actual use, we create several kinds of time "values", based on their logical properties.
|
||||||
** These time values are considered to be fixed (immutable) values, which may only be
|
** These time values are considered to be fixed (immutable) values, which may only be
|
||||||
** created through some well defined construction paths, and any time based calculation
|
** created through some limited construction paths, and any time based calculation
|
||||||
** is forced to go through our time calculation library. This is prerequisite for
|
** is forced to go through our time calculation library. This is prerequisite for
|
||||||
** the definition of <i>frame aligned</i> time values and time code representation
|
** the definition of _frame aligned_ time values and time code representation
|
||||||
** implemented as display format based on these frame quantised time values.
|
** implemented as display format based on these frame quantised time values.
|
||||||
**
|
**
|
||||||
|
** # Time entities
|
||||||
|
**
|
||||||
|
** The value types defined in this header represent time points and time intervals
|
||||||
|
** based on an internal time scale (µs ticks) and not related to any known fixed time
|
||||||
|
** zone or time base; rather they are interpreted in usage context, and the only way
|
||||||
|
** to retrieve such a value is by formatting it into a time code format.
|
||||||
|
**
|
||||||
|
** The lib::time::TimeValue serves as foundation for all further time calculations;
|
||||||
|
** in fact it is implemented as a single 64bit µ-tick value (`gavl_time_t`). The
|
||||||
|
** further time entities are implemented as value objects (without virtual functions):
|
||||||
|
** - lib::time::Time represents a time instant and is the reference for any usage
|
||||||
|
** - lib::time::TimeVar is a mutable time variable and can be used for calculations
|
||||||
|
** - lib::time::Offset can be used to express a positive or negative shift on time scale
|
||||||
|
** - lib::time::Duration represents the extension or an amount of time
|
||||||
|
** - lib::time::TimeSpan represents a distinct interval, with start time and duration
|
||||||
|
** - lib::time::FrameRate can be used to mark a number to denote a frames-per-second spec
|
||||||
|
** - lib::time::FSecs is a rational number to represent seconds or fractions thereof
|
||||||
|
**
|
||||||
|
** # Manipulating time values
|
||||||
|
**
|
||||||
|
** Time values are conceived as fixed, immutable entities, similar to numbers; you can't
|
||||||
|
** just change the number two, and likewise, two seconds are two seconds. However, for
|
||||||
|
** many use cases we have to combine time values to perform calculations
|
||||||
|
** - Time entities can be combined with operators, to form new time entities
|
||||||
|
** - the TimeVar can be used as accumulator or variable for ongoing calculations
|
||||||
|
** - since TimeSpan, Duration (and the grid-aligned, "quantised" flavours) will often
|
||||||
|
** represent some time-like property or entity, e.g. the temporal specification of
|
||||||
|
** a media Clip with start and duration, there is the concept of an explicit *mutation*,
|
||||||
|
** which is _accepted_ by these entities. Notably the lib::time::Control can be attached
|
||||||
|
** to these entities, and can then receive manipulations (nudging, offset); moreover it
|
||||||
|
** is possible to attach as listener to such a "controller" and be notified by any
|
||||||
|
** manipulation; this setup is the base for running time display, playback cursors etc.
|
||||||
|
**
|
||||||
** @see time.h basic time calculation library functions
|
** @see time.h basic time calculation library functions
|
||||||
** @see timequant.hpp
|
** @see timequant.hpp
|
||||||
** @see TimeValue_test
|
** @see TimeValue_test
|
||||||
|
|
@ -134,58 +167,6 @@ namespace time {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** a mutable time value,
|
|
||||||
* behaving like a plain number,
|
|
||||||
* allowing copy and re-accessing
|
|
||||||
* @note supports scaling by a factor,
|
|
||||||
* which \em deliberately is chosen
|
|
||||||
* as int, not gavl_time_t, because the
|
|
||||||
* multiplying of times is meaningless.
|
|
||||||
*/
|
|
||||||
class TimeVar
|
|
||||||
: public TimeValue
|
|
||||||
, boost::additive<TimeVar,
|
|
||||||
boost::additive<TimeVar, TimeValue,
|
|
||||||
boost::multipliable<TimeVar, int>
|
|
||||||
> >
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
TimeVar (TimeValue const& time = TimeValue())
|
|
||||||
: TimeValue(time)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
// Allowing copy and assignment
|
|
||||||
TimeVar (TimeVar const& o)
|
|
||||||
: TimeValue(o)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
TimeVar&
|
|
||||||
operator= (TimeValue const& o)
|
|
||||||
{
|
|
||||||
t_ = TimeVar(o);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Support mixing with plain long int arithmetics
|
|
||||||
operator gavl_time_t () const { return t_; }
|
|
||||||
|
|
||||||
// Supporting additive
|
|
||||||
TimeVar& operator+= (TimeVar const& tx) { t_ += tx.t_; return *this; }
|
|
||||||
TimeVar& operator-= (TimeVar const& tx) { t_ -= tx.t_; return *this; }
|
|
||||||
|
|
||||||
// Supporting multiplication with integral factor
|
|
||||||
TimeVar& operator*= (int64_t fact) { t_ *= fact; return *this; }
|
|
||||||
|
|
||||||
// Supporting sign flip
|
|
||||||
TimeVar operator- () const { return TimeVar(*this)*=-1; }
|
|
||||||
|
|
||||||
// baseclass TimeValue is already totally_ordered
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -208,7 +189,65 @@ namespace time {
|
||||||
typedef boost::rational<int64_t> FSecs;
|
typedef boost::rational<int64_t> FSecs;
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
|
/** a mutable time value,
|
||||||
|
* behaving like a plain number,
|
||||||
|
* allowing copy and re-accessing
|
||||||
|
* @note supports scaling by a factor,
|
||||||
|
* which _deliberately_ is chosen
|
||||||
|
* as int, not gavl_time_t, because the
|
||||||
|
* multiplying of times is meaningless.
|
||||||
|
*/
|
||||||
|
class TimeVar
|
||||||
|
: public TimeValue
|
||||||
|
, boost::additive<TimeVar,
|
||||||
|
boost::additive<TimeVar, TimeValue,
|
||||||
|
boost::multipliable<TimeVar, int>
|
||||||
|
> >
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TimeVar (TimeValue const& time = TimeValue())
|
||||||
|
: TimeValue(time)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/** Allow to pick up precise fractional seconds
|
||||||
|
* @warning truncating fractional µ-ticks */
|
||||||
|
TimeVar (FSecs const&);
|
||||||
|
|
||||||
|
/// Allowing copy and assignment
|
||||||
|
TimeVar (TimeVar const& o)
|
||||||
|
: TimeValue(o)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
TimeVar&
|
||||||
|
operator= (TimeValue const& o)
|
||||||
|
{
|
||||||
|
t_ = TimeVar(o);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Support mixing with plain long int arithmetics
|
||||||
|
operator gavl_time_t() const { return t_; }
|
||||||
|
/// Support for micro-tick precise time arithmetics
|
||||||
|
operator FSecs() const { return FSecs{t_, TimeValue::SCALE}; }
|
||||||
|
|
||||||
|
/// Supporting additive
|
||||||
|
TimeVar& operator+= (TimeVar const& tx) { t_ += tx.t_; return *this; }
|
||||||
|
TimeVar& operator-= (TimeVar const& tx) { t_ -= tx.t_; return *this; }
|
||||||
|
|
||||||
|
/// Supporting multiplication with integral factor
|
||||||
|
TimeVar& operator*= (int64_t fact) { t_ *= fact; return *this; }
|
||||||
|
|
||||||
|
/// Supporting sign flip
|
||||||
|
TimeVar operator- () const { return TimeVar(*this)*=-1; }
|
||||||
|
|
||||||
|
// baseclass TimeValue is already totally_ordered
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************//**
|
||||||
* Lumiera's internal time value datatype.
|
* Lumiera's internal time value datatype.
|
||||||
* This is a TimeValue, but now more specifically denoting
|
* This is a TimeValue, but now more specifically denoting
|
||||||
* a point in time, measured in reference to an internal
|
* a point in time, measured in reference to an internal
|
||||||
|
|
@ -216,7 +255,7 @@ namespace time {
|
||||||
*
|
*
|
||||||
* Lumiera Time provides some limited capabilities for
|
* Lumiera Time provides some limited capabilities for
|
||||||
* direct manipulation; Time values can be created directly
|
* direct manipulation; Time values can be created directly
|
||||||
* from \c (ms,sec,min,hour) specification and there is an
|
* from `(ms,sec,min,hour)` specification and there is an
|
||||||
* string representation intended for internal use (reporting
|
* string representation intended for internal use (reporting
|
||||||
* and debugging). Any real output, formatting and persistent
|
* and debugging). Any real output, formatting and persistent
|
||||||
* storage should be based on the (quantised) timecode
|
* storage should be based on the (quantised) timecode
|
||||||
|
|
@ -378,7 +417,7 @@ namespace time {
|
||||||
* It is an absolute (positive) value, but can be
|
* It is an absolute (positive) value, but can be
|
||||||
* promoted from an offset. While Duration generally
|
* promoted from an offset. While Duration generally
|
||||||
* is treated as immutable value, there is the
|
* is treated as immutable value, there is the
|
||||||
* possibility to send a \em Mutation message.
|
* possibility to send a _Mutation message_.
|
||||||
*/
|
*/
|
||||||
class Duration
|
class Duration
|
||||||
: public TimeValue
|
: public TimeValue
|
||||||
|
|
@ -612,6 +651,11 @@ namespace time {
|
||||||
: raw;
|
: raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
TimeVar::TimeVar (FSecs const& fractionalSeconds)
|
||||||
|
: TimeVar{Time(fractionalSeconds)}
|
||||||
|
{ }
|
||||||
|
|
||||||
inline
|
inline
|
||||||
Duration::Duration (TimeSpan const& interval)
|
Duration::Duration (TimeSpan const& interval)
|
||||||
: TimeValue(interval.duration())
|
: TimeValue(interval.duration())
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,17 @@
|
||||||
|
|
||||||
|
|
||||||
/** @file zoom-window.hpp
|
/** @file zoom-window.hpp
|
||||||
** Abstraction: a multi-dimensional extract from model space into screen coordinates.
|
** Abstraction: the current zoom- and navigation state of a view, possibly in multiple
|
||||||
** This is a generic component to represent and handle the zooming and positioning of
|
** dimensions. This is a generic component to represent and handle the zooming and
|
||||||
** views within an underlying model space. This model space is conceived to be two fold:
|
** positioning of views within an underlying model space. This model space is conceived
|
||||||
|
** to be two fold:
|
||||||
** - it is a place or excerpt within the model topology (e.g. the n-th track in the fork)
|
** - it is a place or excerpt within the model topology (e.g. the n-th track in the fork)
|
||||||
** - it has a temporal extension within a larger temporal frame (e.g. some seconds within
|
** - it has a temporal extension within a larger temporal frame (e.g. some seconds within
|
||||||
** the timeline)
|
** the timeline)
|
||||||
** This component is called »Zoom Window«, since it represents a window-like local visible
|
** This component is called »Zoom Window«, since it represents a window-like local visible
|
||||||
** interval, embedded into a larger time span covering the whole timeline.
|
** interval, embedded into a larger time span covering a complete timeline.
|
||||||
|
** @note as of 10/2022 this component is in an early stage of development and just used
|
||||||
|
** to coordinate the horizontal extension of the timeline view.
|
||||||
**
|
**
|
||||||
** # Rationale
|
** # Rationale
|
||||||
**
|
**
|
||||||
|
|
@ -98,6 +101,10 @@ namespace model {
|
||||||
using lib::time::FSecs;
|
using lib::time::FSecs;
|
||||||
using lib::time::Time;
|
using lib::time::Time;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
/** the deepest zoom is to use 2px per micro-tick */
|
||||||
|
const uint ZOOM_MAX_RESOLUTION = 2 * TimeValue::SCALE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A component to ensure uniform handling of zoom scale
|
* A component to ensure uniform handling of zoom scale
|
||||||
|
|
@ -105,6 +112,13 @@ namespace model {
|
||||||
* the mutator functions are validated and harmonised to
|
* the mutator functions are validated and harmonised to
|
||||||
* meet the internal invariants; a change listener is
|
* meet the internal invariants; a change listener is
|
||||||
* possibly notified to pick up the new settings.
|
* possibly notified to pick up the new settings.
|
||||||
|
*
|
||||||
|
* A ZoomWindow...
|
||||||
|
* - is a #visible TimeSpan
|
||||||
|
* - which is completely inside an #overalSpan
|
||||||
|
* - and is rendered at a scale factor #px_per_sec
|
||||||
|
* - 0 < px_per_sec <= ZOOM_MAX_RESOLUTION
|
||||||
|
* - zoom operations are applied around an #anchorPoint
|
||||||
*/
|
*/
|
||||||
class ZoomWindow
|
class ZoomWindow
|
||||||
: util::NonCopyable
|
: util::NonCopyable
|
||||||
|
|
@ -152,7 +166,12 @@ namespace model {
|
||||||
void
|
void
|
||||||
nudgeMetric (int steps)
|
nudgeMetric (int steps)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED ("nudgeMetric");
|
uint changedScale =
|
||||||
|
steps > 0 ? px_per_sec_ << steps
|
||||||
|
: px_per_sec_ >> -steps;
|
||||||
|
if (0 < changedScale
|
||||||
|
and changedScale <= ZOOM_MAX_RESOLUTION)
|
||||||
|
mutateScale (changedScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
@ -239,7 +258,39 @@ namespace model {
|
||||||
void
|
void
|
||||||
mutateScale (uint px_per_sec)
|
mutateScale (uint px_per_sec)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED ("change scale factor, validate and adjust all params");
|
if (px_per_sec == 0) px_per_sec = 1;
|
||||||
|
if (px_per_sec == px_per_sec_) return;
|
||||||
|
|
||||||
|
FSecs changeFactor{px_per_sec, px_per_sec_};
|
||||||
|
FSecs dur{afterWin_ - startWin_};
|
||||||
|
dur /= changeFactor;
|
||||||
|
if (dur > FSecs{afterAll_ - startAll_})
|
||||||
|
{// limit to the overall timespan...
|
||||||
|
px_per_sec_ = adjustedScale (startAll_,afterAll_, startWin_,afterWin_);
|
||||||
|
startWin_ = startAll_;
|
||||||
|
afterWin_ = afterAll_;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TimeVar start{anchorPoint() - dur*relativeAnchor()};
|
||||||
|
if (start < startAll_)
|
||||||
|
start = startAll_;
|
||||||
|
TimeVar after{start + dur};
|
||||||
|
if (after > afterAll_)
|
||||||
|
{
|
||||||
|
after = afterAll_;
|
||||||
|
start = afterAll_ - dur;
|
||||||
|
}
|
||||||
|
ASSERT (after-start <= afterAll_-startAll_);
|
||||||
|
|
||||||
|
if (start == startWin_ and after == afterWin_)
|
||||||
|
return; // nothing changed effectively
|
||||||
|
|
||||||
|
px_per_sec_ = adjustedScale (start,after, startWin_,afterWin_);
|
||||||
|
startWin_ = start;
|
||||||
|
afterWin_ = after;
|
||||||
|
}
|
||||||
|
fireChangeNotification();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
@ -247,6 +298,68 @@ namespace model {
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED ("change visible duration, validate and adjust all params");
|
UNIMPLEMENTED ("change visible duration, validate and adjust all params");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjust the display scale such as to match the given changed time interval
|
||||||
|
* @param startNew changed start point
|
||||||
|
* @param afterNew changed end point
|
||||||
|
* @param startOld previous start point
|
||||||
|
* @param afterOld previous end point
|
||||||
|
* @return adapted scale factor in pixel per second, rounded half up to the next pixel.
|
||||||
|
*/
|
||||||
|
uint
|
||||||
|
adjustedScale (TimeVar startNew, TimeVar afterNew, TimeVar startOld, TimeVar afterOld)
|
||||||
|
{
|
||||||
|
REQUIRE (startOld < afterOld);
|
||||||
|
FSecs factor = FSecs{afterNew - startNew} / FSecs{afterOld - startOld};
|
||||||
|
return boost::rational_cast<uint>(px_per_sec_ / factor + 1/2); // rounding half pixels
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The anchor point or centre for zooming operations applied to the visible window
|
||||||
|
* @return where the visible window should currently be anchored
|
||||||
|
* @remark this point can sometimes be outside the current visible window,
|
||||||
|
* but any further zooming/scaling/scrolling operation should bring it back
|
||||||
|
* into sight. Moreover, the function #relativeAnchor() defines the position
|
||||||
|
* where this anchor point _should_ be placed relative to the visible window.
|
||||||
|
* @todo 10/2022 we use a numerical rule currently, but that could be contextual state,
|
||||||
|
* like e.g. the current position of the play head or edit cursor or mouse.
|
||||||
|
*/
|
||||||
|
FSecs
|
||||||
|
anchorPoint() const
|
||||||
|
{
|
||||||
|
return startWin_ + FSecs{afterWin_-startWin_} * relativeAnchor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* define at which proportion to the visible window's duration the anchor should be placed
|
||||||
|
* @return a fraction 0 ... 1, where 0 means at start and 1 means after end.
|
||||||
|
* @note as of 10/2022 we use a numerical rule to place the anchor point in accordance
|
||||||
|
* to the current visible window's position within the overall timeline; if it's
|
||||||
|
* close to the beginning, the anchor point is also rather to the beginning...
|
||||||
|
*/
|
||||||
|
FSecs
|
||||||
|
relativeAnchor() const
|
||||||
|
{
|
||||||
|
// the visible window itself has to fit in, which reduces the action range
|
||||||
|
FSecs possibleRange = (afterAll_-startAll_) - (afterWin_-startWin_);
|
||||||
|
if (possibleRange == 0) // if there is no room for scrolling...
|
||||||
|
return FSecs{1,2}; // then anchor zooming in the middle
|
||||||
|
|
||||||
|
// use a 3rd degree parabola to favour positions in the middle
|
||||||
|
FSecs posFactor = FSecs{startWin_-startAll_} / possibleRange;
|
||||||
|
posFactor = (2*posFactor - 1); // -1 ... +1
|
||||||
|
posFactor = posFactor*posFactor*posFactor; // -1 ... +1 but accelerating towards boundraries
|
||||||
|
posFactor = (posFactor + 1) / 2; // 0 ... 1
|
||||||
|
return posFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
fireChangeNotification()
|
||||||
|
{
|
||||||
|
TODO("really fire...");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ namespace test {
|
||||||
|
|
||||||
zoomWin.nudgeMetric(+1);
|
zoomWin.nudgeMetric(+1);
|
||||||
CHECK (zoomWin.px_per_sec() == 50);
|
CHECK (zoomWin.px_per_sec() == 50);
|
||||||
CHECK (zoomWin.visible() == TimeSpan(Time(FSecs(23,4)), Time(FSecs(23,2))));
|
CHECK (zoomWin.visible() == TimeSpan(Time(FSecs(23,4)), FSecs(23,2)));
|
||||||
CHECK (zoomWin.overallSpan() == TimeSpan(Time::ZERO, Time(FSecs(23))));
|
CHECK (zoomWin.overallSpan() == TimeSpan(Time::ZERO, Time(FSecs(23))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38379,8 +38379,14 @@
|
||||||
</html></richcontent>
|
</html></richcontent>
|
||||||
</node>
|
</node>
|
||||||
</node>
|
</node>
|
||||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1666966669487" ID="ID_1477573565" MODIFIED="1666967820569" TEXT="testgetrieben entwickelt">
|
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1666966669487" ID="ID_1477573565" MODIFIED="1667093086439" TEXT="testgetrieben entwickelt">
|
||||||
<icon BUILTIN="flag-yellow"/>
|
<icon BUILTIN="pencil"/>
|
||||||
|
<node COLOR="#338800" CREATED="1667093097050" ID="ID_1662709264" MODIFIED="1667093103756" TEXT="verify_simpleUsage">
|
||||||
|
<icon BUILTIN="button_ok"/>
|
||||||
|
<node CREATED="1667093106907" ID="ID_1255657275" MODIFIED="1667093118644" TEXT="nudge Zoom-Faktor"/>
|
||||||
|
<node CREATED="1667093120846" ID="ID_1405458968" MODIFIED="1667093134771" TEXT="⟹ doppelte Auflösung"/>
|
||||||
|
<node CREATED="1667093136007" ID="ID_1309995367" MODIFIED="1667093153233" TEXT="⟹ visible Window liegt in der Mitte und hat hable Länge"/>
|
||||||
|
</node>
|
||||||
</node>
|
</node>
|
||||||
</node>
|
</node>
|
||||||
<node BACKGROUND_COLOR="#d2beaf" COLOR="#5c4d6e" CREATED="1666913274942" ID="ID_4743528" MODIFIED="1666913286194" TEXT="Design überprüfen">
|
<node BACKGROUND_COLOR="#d2beaf" COLOR="#5c4d6e" CREATED="1666913274942" ID="ID_4743528" MODIFIED="1666913286194" TEXT="Design überprüfen">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue