Commit graph

5927 commits

Author SHA1 Message Date
c2bb45f2dd Timeline: manually extract construction coordinates from FreeCAD
Identify the elements of the construction geometry in the "Sketch"
object in the FreeCAD document and paste the corresponding coordinate
values into the SVG drawing prepared for documentation.

The arc segment parameters seemingly are given in radians;
and while FreeCAD uses the common mathematical right-handed orientation,
the orientation in SVG is applied clockwise rather.
2023-02-26 01:29:13 +01:00
ef4401a185 Timeline: rearrange drawing in accordance to construction
...since the construction is determined now (and was worked out in FreeCAD),
the SVG will serve to document the construction; thus the drawing
primitives are rearranged to use the unscaled reference coordinates
to be extracted from the FreeCAD document; all scaling and placement
in the SVG document will be applied through common groups.
2023-02-25 15:32:35 +01:00
3ad5eaa9ba Timeline: construct a design for track scope brackets
My idea was to use the brackets from musical notation as inspiration;
if you know some principles of typography, it is rather straight-forward
to come up with a pleasing design of such a bracket, using a
cascade of golden ratio relationships.

BUT ... all of this is geometry, and translating that into a symbolic
or numerical calculation is excessively complicated. Thus I looked
for ways to use some geometry or CAD software to build such a construction.

The geometry software I tired was woefully inadequate for this task.
Using the Constraint system in FreeCAD, building the construction went
smooth and straight forward, but then I was unable to export that drawing
in a way indicating the construction clearly.

So in the end, I'll have to hand-pick the resulting numerical coordinates
from the FreeCAD XML document and integrate them directly into Cairo
drawing code...
2023-02-25 01:49:58 +01:00
171c3e30d7 Timeline: fix horizontal allocation on leaf-tracks
...as it turned out, it suffices just to state desired behaviour explicitly
- make the StaveBracket expand=false
- declare the HeadControl expand=true

The reason lies in the fact that on leaf-tracks there are just two
adjacent cells in a single row, lacking any exterior source of layout guidance.
2023-02-11 04:30:26 +01:00
4444e2b0ec Timeline: setup framework for custom drawing for the track structure
...just drawing a marker cross for now to indicate allocated size;
speaking of size -- GTK sometimes expands allocation horizontally,
while we'd prefer an absolutely fixed size for the purpose at hand.
2023-02-10 17:51:02 +01:00
70b8b86182 Timeline: determine remaining work to wrap-up
Intention is to shift focus of development work down towards Player and Scheduler soon.
However, since the timeline display saw substantial improvement it seems prudent
to finish work on some open ends, notably

- the track head structure
- the drawing and styling code

While content rendering for Clip widgets can safely be postponed, regarding the TrackHeadWidget,
where custom drawing is planned to display a structural outline of the nested scopes, some
ground work should be completed to make those plannings explicit.
2023-02-09 01:30:32 +01:00
37543024fd Timeline: use ElementBoxWidget to display the track header
This both demonstrates the layout of the `TrackHeadWidget`
and puts `ElementBoxWidget` into intended use to indicate
the scope of a track and to provide a placement icon and
an expander/collapser button.

see #1018
see #1219
2023-02-03 04:38:56 +01:00
25be54ecbe Timeline: remove diagnostic code (closes: #1211)
Layout calculation and balancing between body and track header
now works reasonably well, labels are placed properly and the
calculated layout remains stable when changing window size,
connected panes and scrollbars behave as expected now.

Quite a feat!
2023-01-25 01:42:40 +01:00
e0e0192938 Timeline: fix height calculation
...to properly account for
- the "prelude" padding above the root track
- overview rulers located into a fixed separate canvas at top
- slope-down and slope-up borders around nested tracks.

After testing, results seem now to be accurate up to ± 1px

Finally (sigh)
2023-01-23 23:44:55 +01:00
9bfae3cee1 Timeline: control alignment to sub-tracks through additional padding
...a huge improvement;
however, it becomes obiouvs now that we'll have to deal specifically
with the root track ruler canvas and "prelude" padding...
2023-01-22 00:50:34 +01:00
b38481fa89 Timeline: solve basic layout issue by properly calculating the height
As it turns out, we need to take the actual allocation of the cells
within the grid explicitly into account: combined cells will report
their extension for each of the underlying cells, thus leading to
excessively overestimated measurements.

So we now calculate the overall height based on the actual structure
- first row holds the label
- left column below used as expander
- right column holds individual content

Remaining problems:
- height of ruler canvas at top not taken into account (11px in this example)
- start of sub-track headers not synchronised with start of sub-tracks
  in the body area
2023-01-20 23:57:21 +01:00
4887194589 Timeline: investigate vertical space allocation on nested tracks
...seemingly the allocation of grid cells in the `TrackHeadWidget`
is not quite correct yet: even when there are nested sub-tracks,
we always need another row to hold the controls corresponding to
the track itself and the whole scope. And this row is also what
should be adjusted to match the vertical extension of the content
area.

As it turns out, the whole topic how to handle collapsed tracks
was not even considered yet; the calculation of the "track profile"
would need to be reworked to accommodate collapsed tracks, see: #1265
2023-01-19 23:58:42 +01:00
a04d2b01c6 Timeline: hunt down and fix a numeric-wrap problem
Sometimes, fractional seconds in the ZoomWindow metric can build up
to numerator and denominator values in the 64-bit integer domain.
Thus the division and truncation of the Window pixel with value
must be done in int64_t, while the result value is then
guaranteed to be a small integer < 100000

This caused the canvas to flicker and jump in size and the
resulting scrollbar change caused various cascading effects
on the further layout calculations...
2023-01-17 02:16:30 +01:00
2fb182e7ff Timeline: analyse and fix re-entrance problem in DisplayEvaluation
Note: the actual root cause, why this re-entrance happens,
is due to another obvious numerics bug not yet solved.
Here, the canvas width was suddenly set to zero, causing
the scrollbar position to change and thus the ZoomWindow
to re-fire the structure change signal.

However, such invalidation of previously established baseline
values can never be totally excluded in advanced layout calculations,
and thus the evaluation mechanism must be prepared and re-triggered
to start over, until a stable layout is achieved.
2023-01-16 00:07:05 +01:00
0a99e5dfc8 Timeline: reorganise additional padding in TrackHeadWidget
- rearrange cell content and disable auto-expand to prevent
  the content area from becoming oversized initially

- fix autocompletion error in signal binding,
  causing segfault when moving the scrollbar
2023-01-15 02:51:00 +01:00
61f0e2f6a7 Timeline: further (unsuccessful) efforts towards layout sync
attempting to get the vertical space allocation in header and content area
synchronised; previously we conflated the content size and additional
padding, but even after distinguishing both, we still get a cyclic
dependency, leading to progressive increasing of allocated size...
2023-01-10 04:02:26 +01:00
832dc72652 fix indentation 2023-01-10 02:48:45 +01:00
09714cfe28 Timeline: draft solution to interconnect ZoomWindow and scrollbar
After quite some tinkering, instead of extending the DisplayManager interface,
I now prefer to treat this connection rather as an intricate implementation detail:

The TimelineLayout implementatino now provides two translation functions,
which are directly wired as slots from the Signals emitted by moving the
hand of the scrollbar; the idea is that these functions mutate the ZoomWindow,
which then triggers a DisplayEvaltuation, which in turn causes the
drawing code to pick up and translate back the new metric and position.

Results look promising, insofar the DisplayEvaluation is now triggered
repeatedly, and the actual window width in pixel is propagated;
however, the response of the layout code is seemingly random at times,
the allocated height grows monontonously and the code Segfaults when
moving the scrollbar...
2023-01-06 03:09:28 +01:00
941d3088d5 Timeline: rearrange ruler canvas into a slave scrolled pane
this makes the arrangement more symmetric and natural
and also makes the overview ruler scroll alongside the content pane,
thereby creating the (intended) impression of one uniform layout space
2023-01-05 02:45:11 +01:00
f3098daeb4 Timeline: investigate problem with horizontal scrollbar
As it turns out, this happens as side-effect from the workaround 2019-08-22

fc5eaf857c

Obviously, just set_size() on the canvas is not sufficient for
GTK actually to establish a size-request (seemingly the canvas counts
as /empty/ and only real widgets would make a difference).
However, since the ruler canvas is directly placed into the box,
and not adapted by a ScrolledWindow, explicitly set_size_request()
also causes the enclosing Box to "inherit" this minimal required size,
thereby also spreading out the BodyCanvasWidget beyond the size
actually available. GTK handles this situation by hard-clipping
on the right side, which causes the vertical scrollbar to disappear
and keeps the horizontal scrollbar disabled (since nominally it still
spans the whole size available, even while this size is then clipped
subsequently).

This changeset adds a lot of debug printing and demonstrates this
behaviour by setting only a minimal horizontal size_request, so that
the window is no longer expanded and clipped.
2023-01-04 23:55:21 +01:00
c22bbe5f93 Timeline: Refactor DisplayEvaluation to integrate zoom calibration
This does not really change the logic of the DisplayEvaluation mechanism,
but makes it much more flexible: instead of having two hard wired components,
the DisplayEvaluation visitor now holds a collection of LayoutElement pointers.
This way, the Layout manager itself (on behalf of the ZoomWindow) can
participate in the process, and on activation will now establish the
window width in pixel.

This works now insofar the drawing on the canvas is adapted coherently;
however something with the setup of the Scrollbar is still not quite right;
some time ago I recall the scrollbar worked, but now it is blocked
and the canvas just clips to the right side.
2023-01-04 03:36:12 +01:00
061b70c96f Timeline: integrate ZoomWindow change listener
- connect to TimelineLayout::signalStructureChange
 - this causes the trackProfile to be reset
 - next draw() call will then triger display-evaluation
2023-01-03 02:23:22 +01:00
62bad8720a Timeline: decide upon handling of the canvas origin
It is now tied to the start of ZoomWindow::overallSpan(),
thereby defining the (technical) pixel coordinates within the window
and for drawing on the canvas to be always positive. Whenever ZoomWindow
re-calibrates, it's change signal will trigger, causing the
TimelineLayout to perform a new DisplayEvaluationPass,
which in turn prompts all embedded widgets to readjust
their positions accordingly.
2023-01-03 00:20:09 +01:00
52d3231226 Timeline: finish ZoomWindow implementation and boundrary tests 2022-12-18 03:47:40 +01:00
e436023ef9 Timeline: properly handling maximal zoom-in and alignment to µ-ticks 2022-12-17 17:47:10 +01:00
b1514f6632 Timeline: properly handling extreme scroll-steps 2022-12-17 01:15:34 +01:00
3893968502 Timeline: improve handling of window size changes at extreme positions
...again and again surprising how much inconsitencies can hide in just some lines of code...
2022-12-16 19:59:47 +01:00
77bb156615 Timeline: verify handling of extreme time offsets 2022-12-16 02:23:20 +01:00
5e595c57ca Timeline: automatically orient and shift into allowed time domain
Note: changing behaviour of TimeSpan to possibly flip start and end,
and also to use Offset as Offset and then re-orient,
since this seems the least surprising behaviour.

These changes carry over into changed default and limiting
on ZoomWindow constructor and various mutators, and most
notably shifting the time span always into allowed domain.
2022-12-14 03:00:07 +01:00
777024ee40 Timeline: resolve yet another insidious corner case at maximum zoom
...the implementation was way too naive; in some cases we could go
into an infinite loop. In the end, using Newton approximation was not
necessary (and thus there is no loop anymore), but it helped me get
at a much better solution with very small error margin on average case.

All these corner cases are obviously "academic" to some degree,
but it turns out there is no clear-cut point where you'd be able
just so set a limit and be sure that fractional integer arithmetic
works flawless in all cases.

Thus the choice is
 - give up (fractional) integers and work with floats and have to
   deal with error accumulation
 - or do something as chosen here, namely add a boundary zone, where
   fractional integer arithmetic can be kept under control, while admitting
   small errors, and in turn get the absolutely precise integers in all
   everyday standard cases
2022-12-13 01:21:18 +01:00
c31522c236 Timeline: define better internal zoom-out limit
The value used previously was too conservative, and prevented ZommWindow
from zooming out to the complete Time domain. This was due to missing the
Time::SCALE denominator, which increaded the limit by factor 1e6

In fact the code is able to handle even this extremely reduced limit,
but doing so seems over the top, since now detox() kicks in on several
calculations, leading to rather coarse grained errors.

Thus I decided to use a compromise: lower the limit only by factor 1000;
with typical screen pixel widths, we can reach the full time domain,
while most scaling and zoom calculations can be performed precisely,
without detox() kicking in. Obviously this change requires adjusting
a lot of the test case expectations, since we can now zoom out maximally.
2022-12-10 04:26:22 +01:00
40f003a962 Timeline: stress-test with excessive zoom-out reveals further weakness
As it turns out, the calculation path initially choosen for the mutateScale(Rat)
was needlessly indirect, and also duplicated several of the safeguards,
meanwhile implemented way better in conformWindowToMetric(Rat)

Thus, instead of relatively re-scaling the window, now we just
limit the given zoomFactor and pass it to conformWindowToMetric()
2022-12-09 23:13:27 +01:00
ce3713d872 Timeline: now able to increase to maximum pixel count
There is a built-in limitation, which now is even
lowered to 100000 pixels horizontally.

With the techniques introduced in this changeset, it seems possible
to support more -- yet this would be a case of unnecessary genricity;
handling such large numbers will drive more computations into the
danger zone, and doing so incurs cost in terms of testing and debugging.

Placing that into context, contemporary displays are not even 4K on
average, and it does not look likely even for cinema display to go
way beyond 8k -- so yes, I want display hardware with 100000 pixels!!


The key takeaway of this changeset:
 - can calculate px = trunc(zoomFactor * duration) step wise,
   even when the direct calculation would lead to wrap-around
 - can safely adjust and fix the zoomFactor using Newton approximation
2022-12-09 19:37:35 +01:00
068549dace Timeline: now able to handle maximal zoom-out
...even zooming out to span the complete time domain (~19000 years).
But only under the condition that the display window is sufficiently
large in terms of pixels, so we can handle the computation without
glitches.

This should not be a relevant limitation in practice, since a window
size of some 100 pixels is enough to handle Duration::MAX. Needless to add
that it's hard to imagine a media timeline of such tremendous size...
building on these Library changes, plus the safe-add function
developed some days ago, it is now possible to mark a large displacement
as `time::Offset`, and apply this to yield any valid time position,
even extreme negative values
2022-12-08 18:06:37 +01:00
006758f349 Library/Timeline: now able to scroll to extreme positions (closes #1263)
...building on these Library changes, plus the safe-add function
developed some days ago, it is now possible to mark a large displacement
as `time::Offset`, and apply this to yield any valid time position,
even extreme negative values
2022-12-05 03:34:04 +01:00
13adc56f34 Library: rectify confusingly named function on the Grid API
The APIs for time quantisation were drafted in an early stage of the project
and then never followed-up. Especially Grid::gridAlign has no
real-world usage yet, and is only massaged in some tests.

When looking at QuantiserBasics_test, I was puzzled and led astray,
since this function suggests to materialise a continuous time into
a quantised time -- which it doesn't (there is another dedicated
function Quantiser::materialise() to that end); so, without engaging
into the discussion if this function is of any use, I'll hereby
choose a name better reflecting what it does.
2022-12-05 01:05:23 +01:00
50c602ec3f Library: rectify clipping of time::Duration (see #1263)
This is a deep refactoring to allow to represent the distance
between all valid time points as a time::Offset or time::Duration.

By design this is possible, since Time::MAX was defined as 1/30 of
the maximum value technically representable as int64_t. However,
introducing a different limiter for offsets and durations turns
out difficult, due to the inconsistencies in the exiting hierarchy
of temporal entities. Which in turn seems to stem from the unfortunate
decision to make time entities immutable, see #1261

Since the limiter is hard wired into the `time::TimeValue` constructor,
we are forced to create a "backdoor" of sorts, to pass up values
with different limiting from child classes. This would not be so
much of a problem if calculations weren't forced to go through `TimeVar`,
which does not distinguish between time points and time durations.

This solution rearranges all checks to be performed now by time::Offset,
while time::Duration will only take the absolute value at construction,
based on the fact that there is no valid construction path to yield
a duration which does not go through an offset first.

Later, when we're ready to sort out the implementation base of time values
(see #1258), this design issue should be revisited
- either we'll allow derived classes explicitly to invoke the limiter functions
- or we may be able to have an automatic conversion path from clearly
  marked base implementation types, in which case we wouldn't use the
  buildRaw_() and _raw() "backdoor" functions any more...
2022-12-05 00:58:32 +01:00
4d79bdce5f Timeline: sophisticated helper function to resolve problems with numeric precision
While the calculation was already basically under control, I just was not content
with the achieved numeric precision -- and the fact that the test case in fact
misses the bar, making it difficult do demonstrate that the calculation
is not derailed. I just had the gut feeling that it must be somehow possible
to achieve an absolute error level, not just a relative error level of 1e-6

Thus I reworked this part into a generic helper function, see #1262

The end result is:
 * partial failure. we can only ''guarantee'' the relative error margin of 1e-6
 * but in most cases not out to the most extreme numbers, the sophisticated
   solution achieves much better results way below the absolute error level of 1µ-Tick

Thus with using rational numbers, we have now a solution that is absolutely precise
in the regular case, and gradually introduces errors at the domain bound
but with an guaranteed relative error margin of 1e-6 (== Time::SCALE)
2022-12-02 22:23:14 +01:00
2ddbed693b Timeline: fix inconsistency in ZoomWindow normalisation numerics
...now able to achieve the expected error bound of 1e-6
...this seems to be the worst-case for ZoomWindow::setVisiblePos(factor)
   for extremely large timeline; seemingly not possible to achieve the
   goal set for this special test case
2022-12-02 03:48:36 +01:00
289f92da7e Timeline: safely calculate sum/difference of large fractional times
...in a similar vein as done for the product calculation.
In this case, we need to check the dimensions carefully and pick
the best calculation path, but as long as the overall result can
be represented, it should be possible to carry out the calculation
with fractional values, albeit introducing a small error.

As a follow-up, I have now also refactored the re-quantisation
functions, to be usable for general requantisation to another grid,
and I used these to replace the *naive* implementation of the
conversion FSecs -> µ-Grid, which caused a lot of integer-wrap-around

However, while the test now works basically without glitch or wrap,
the window position is still numerically of by 1e-6, which becomes
quite noticeably here due to the large overall span used for the test.
2022-12-01 23:23:50 +01:00
7007101357 Timeline: safely calculate the fraction of a very large timespan
...using a requantisation trick to cancel out some factors in the
product of two rational numbers, allowing to calculate the product
without actual multiplication of (dangerously large) numbers.

with these additional safeguards, the anchorWindowAtPosition()
succeeds without Integer-wrap, but the result is not fully correct
(some further calculation error hidden somewhere??)
2022-11-29 02:00:41 +01:00
b898f1514b Timeline: safeguard agains injecting a poisonous metric factor 2022-11-26 19:45:10 +01:00
90aba4df09 Timeline: demonstrate safeguards against reversed and toxic input 2022-11-18 02:55:28 +01:00
cfe3a6618f Lib: cover re-quantisation helper
...which I intend to use for sanitising poisonous rational numbers,
as prerequisite for handling divisor based time scales in the ZoomWindow
2022-11-15 02:13:57 +01:00
ce1220ee72 Lib: test coverage for rational-int corner cases and integer-log
- detailed documentation of known problematic behaviour
  when working with rational fractions
- demonstrate the heuristic predicate to detect dangerous numbers

- add extensive coverage and microbenchmarks for the integer-logarithm
  implementation, based on an example on Stackoverflow. Surprising result:
  The std::ilog(double) function is of comparable speed, at least for
  GCC-8 on Debian-Buster.
2022-11-14 05:20:37 +01:00
8ab0e1acb5 Lib: consider method to sanitise a poisonous rational
Especially rational numbers with large denominator can be insidious,
since they might cause numeric overflow on seemingly harmless operations,
like adding a small number.

A solution might be to *requantise* the number into a different,
way smaller denominator. Obviously this is a lossy operation;
yet a small and controlled numeric error is always better than
an uncontrolled numeric wrap-around.
2022-11-13 16:52:12 +01:00
d3fc6fd4de Timeline: Analysis of possibly toxic input values
- protection against negative numbers seems adequate
- a possible concern are handling of very large time spans
- definitively have to guard against "poisonous" fractions
  (e.g. n / INT_MAX)
2022-11-12 17:35:47 +01:00
cc16953fd8 Timeline: implement and verify ZoomWindow change notification 2022-11-11 16:30:27 +01:00
3f396ef3b2 Timeline: ZoomWindow now passes all tests according to spec
- some test definitions were simply numerically wrong
- changed some aspects of the specified behaviour, to be more consistent
  + scrolling is more liberal and always allows to extend canvas
  + setting window to a given duration expands around anchor point
2022-11-11 03:06:33 +01:00
93de063ae5 Timeline: fix next ZoomWindow_test failure 2022-11-10 21:00:00 +01:00