Timeline: make TrackPresenter header-only
...there is no need for yet another indirection here, since TrackPresenter is not much of an interface and only included at into two other translation units. Moreover, header-only code simplifies the use of templated lambdas, which come in handy when dealing with the various nested sub-collections.
This commit is contained in:
parent
5ddf426add
commit
8d87029fd5
2 changed files with 171 additions and 215 deletions
|
|
@ -1,212 +0,0 @@
|
|||
/*
|
||||
TrackPresenter - presentation control element for a track within the timeline
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
* *****************************************************/
|
||||
|
||||
|
||||
/** @file track-presenter.cpp
|
||||
** Implementation details of track presentation management.
|
||||
** Especially here we define the model binding of all sub-elements belonging
|
||||
** to a given track. The TrackPresenter::buildMutator() implementation hooks up
|
||||
** the necessary callbacks, to allow adding and removing of sub elements and properties
|
||||
** of a track, by sending appropriate _mutation messages_ over the stage::UiBus.
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 12/2016
|
||||
** @todo as of 10/2018 timeline display in the UI is rebuilt to match the architecture
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#include "stage/gtk-base.hpp"
|
||||
#include "include/ui-protocol.hpp"
|
||||
#include "stage/timeline/track-presenter.hpp"
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1201 : test/code... remove this
|
||||
#include "lib/format-cout.hpp"
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1201 : test/code... remove this
|
||||
|
||||
//#include "stage/ui-bus.hpp"
|
||||
//#include "lib/format-string.hpp"
|
||||
//#include "lib/format-cout.hpp"
|
||||
|
||||
//#include "lib/util.hpp"
|
||||
|
||||
//#include <algorithm>
|
||||
//#include <vector>
|
||||
|
||||
|
||||
|
||||
//using util::_Fmt;
|
||||
using lib::diff::TreeMutator;
|
||||
using lib::diff::collection;
|
||||
using std::make_unique;
|
||||
//using util::contains;
|
||||
//using Gtk::Widget;
|
||||
//using sigc::mem_fun;
|
||||
//using sigc::ptr_fun;
|
||||
//using std::cout;
|
||||
//using std::endl;
|
||||
|
||||
|
||||
namespace stage {
|
||||
namespace timeline {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
TrackPresenter::~TrackPresenter()
|
||||
{
|
||||
TODO ("find a way how to detach from parent tracks"); ////////////////////////////////////////////TICKET #1199 : how to deal with re-ordering of tracks?
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1201 : test/code... remove this
|
||||
void
|
||||
TrackPresenter::injectDebugTrackLabels()
|
||||
{
|
||||
uint x = rand() % 50;
|
||||
uint y = 0;
|
||||
Gtk::Button* butt = Gtk::manage (new ViewHooked<Gtk::Button, Gtk::Widget>{display_.hookedAt(x,y), TODO_trackName_});
|
||||
butt->signal_clicked().connect(
|
||||
[butt]{ cout << "|=="<<butt->get_label()<<endl; });
|
||||
butt->show();
|
||||
|
||||
for (auto& subTrack : subFork_)
|
||||
subTrack->injectDebugTrackLabels();
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1201 : test/code... remove this
|
||||
/**
|
||||
* @note we distinguish between the contents of our four nested child collections
|
||||
* based on the symbolic type field sent in the Record type within the diff representation
|
||||
* - "Marker" designates a Marker object
|
||||
* - "Clip" designates a Clip placed on this track
|
||||
* - "Fork" designates a nested sub-track
|
||||
* - "Ruler" designates a nested ruler (timescale, overview,....) belonging to this track
|
||||
* @see TimelineController::buildMutator() for a basic explanation of the data binding mechanism
|
||||
*/
|
||||
void
|
||||
TrackPresenter::buildMutator (TreeMutator::Handle buffer)
|
||||
{
|
||||
using PFork = unique_ptr<TrackPresenter>;
|
||||
using PClip = unique_ptr<ClipPresenter>;
|
||||
using PMarker = unique_ptr<MarkerWidget>;
|
||||
using PRuler = unique_ptr<RulerTrack>;
|
||||
|
||||
buffer.create (
|
||||
TreeMutator::build()
|
||||
.attach (collection(display_.bindRulers())
|
||||
.isApplicableIf ([&](GenNode const& spec) -> bool
|
||||
{ // »Selector« : require object-like sub scope with type-field "Ruler"
|
||||
return TYPE_Ruler == spec.data.recordType();
|
||||
})
|
||||
.matchElement ([&](GenNode const& spec, PRuler const& elm) -> bool
|
||||
{
|
||||
return spec.idi == elm->getID();
|
||||
})
|
||||
.constructFrom ([&](GenNode const& spec) -> PRuler
|
||||
{ // »Constructor« : how to attach a new ruler track
|
||||
return make_unique<RulerTrack> (spec.idi, this->uiBus_, *this);
|
||||
})
|
||||
.buildChildMutator ([&](PRuler& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
|
||||
{
|
||||
if (subID != target->getID()) return false;
|
||||
target->buildMutator (buff);
|
||||
return true;
|
||||
}))
|
||||
.attach (collection(markers_)
|
||||
.isApplicableIf ([&](GenNode const& spec) -> bool
|
||||
{ // »Selector« : require object-like sub scope with type-field "Marker"
|
||||
return TYPE_Marker == spec.data.recordType();
|
||||
})
|
||||
.matchElement ([&](GenNode const& spec, PMarker const& elm) -> bool
|
||||
{
|
||||
return spec.idi == elm->getID();
|
||||
})
|
||||
.constructFrom ([&](GenNode const& spec) -> PMarker
|
||||
{
|
||||
return make_unique<MarkerWidget> (spec.idi, this->uiBus_);
|
||||
})
|
||||
.buildChildMutator ([&](PMarker& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
|
||||
{
|
||||
if (subID != target->getID()) return false;
|
||||
target->buildMutator (buff);
|
||||
return true;
|
||||
}))
|
||||
.attach (collection(clips_)
|
||||
.isApplicableIf ([&](GenNode const& spec) -> bool
|
||||
{ // »Selector« : require object-like sub scope with type-field "Clip"
|
||||
return TYPE_Clip == spec.data.recordType();
|
||||
})
|
||||
.matchElement ([&](GenNode const& spec, PClip const& elm) -> bool
|
||||
{
|
||||
return spec.idi == elm->getID();
|
||||
})
|
||||
.constructFrom ([&](GenNode const& spec) -> PClip
|
||||
{
|
||||
return make_unique<ClipPresenter> (spec.idi, this->uiBus_);
|
||||
})
|
||||
.buildChildMutator ([&](PClip& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
|
||||
{
|
||||
if (subID != target->getID()) return false;
|
||||
target->buildMutator (buff);
|
||||
return true;
|
||||
}))
|
||||
.attach (collection(subFork_)
|
||||
.isApplicableIf ([&](GenNode const& spec) -> bool
|
||||
{ // »Selector« : require object-like sub scope with type-field "Fork"
|
||||
return TYPE_Fork == spec.data.recordType();
|
||||
})
|
||||
.matchElement ([&](GenNode const& spec, PFork const& elm) -> bool
|
||||
{
|
||||
return spec.idi == elm->getID();
|
||||
})
|
||||
.constructFrom ([&](GenNode const& spec) -> PFork
|
||||
{
|
||||
return make_unique<TrackPresenter> (spec.idi, uiBus_, this->display_);
|
||||
})
|
||||
.buildChildMutator ([&](PFork& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
|
||||
{
|
||||
if (subID != target->getID()) return false;
|
||||
target->buildMutator (buff);
|
||||
return true;
|
||||
}))
|
||||
.change(ATTR_name, [&](string val)
|
||||
{ // »Attribute Setter« : receive a new value for the track name field
|
||||
this->setTrackName (val);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/** @todo 2/2020 */
|
||||
void
|
||||
TrackPresenter::establishLayout (DisplayEvaluation& displayEvaluation)
|
||||
{
|
||||
UNIMPLEMENTED ("respond to the DisplayEvaluation-Pass and pass on evaluation recursively");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}}// namespace stage::timeline
|
||||
|
|
@ -38,8 +38,43 @@
|
|||
** represent potentially several thousand individual elements as GTK entities, while at any time
|
||||
** only a small number of elements can be visible and active as far as user interaction is concerned.
|
||||
**
|
||||
** @todo WIP-WIP-WIP as of 12/2016
|
||||
** # Structure of the TrackPresenter
|
||||
**
|
||||
** Each TrackPresenter corresponds to a "sub-Fork" of timeline tracks. Since Lumiera always arranges
|
||||
** tracks as nested scopes into a tree, there is one root fork, recursively holding several sub forks.
|
||||
** - thus each TrackPresenter holds a collection #subFor_ -- possibly empty.
|
||||
** - moreover, it holds a collection #clips_, which represent the actual content of this track itself,
|
||||
** as opposed to content on some sub-track. These clips are to be arranged within the _content area_
|
||||
** of the track display, in the track body area (at the right side of the timeline). Actually, this
|
||||
** collection holds timeline::ClipPresenter objects, thus repeating the same design pattern.
|
||||
** - in addition, there can be a collection of #markers_, to be translated into various kinds of
|
||||
** region or point/location markup, typically shown in the (optional) _overview ruler,_ running
|
||||
** along the top-side of this track's display area.
|
||||
**
|
||||
** Since TrackPresenter is a model::Tangible, a central concern is the ability to respond to
|
||||
** _diff messages._ In fact, any actual content, including all the nested sub-structures, is
|
||||
** _populated_ through such _mutation messages_ sent from the session up via the stage::UiBus.
|
||||
** Thus, the TrackPresenter::buildMutator() implementation hooks up the necessary callbacks,
|
||||
** to allow adding and removing of sub elements and properties of a track.
|
||||
**
|
||||
** Another concern handled here is the coordination of layout and display activities.
|
||||
** A special twist arises here: The track header ("patchbay") display can be designed as a
|
||||
** classical tree / grid control, while the actual timeline body contents require us to perform
|
||||
** custom drawing activities. Which leads to the necessity to coordinate and connect two distinct
|
||||
** presentation schemes to form a coherent layout. We solve this challenge by introducing a helper
|
||||
** entity, the DisplayFrame. These act as a bridge to hook into both display hierarchies (the nested
|
||||
** TrackHeaderWidget and the TrackBody record managed by the BodyCanvasWidget). Display frames are
|
||||
** hooked down from their respective parent frame, thereby creating a properly interwoven fabric.
|
||||
**
|
||||
** After assembling the necessary GTK widgets, typically our custom drawing code will be invoked
|
||||
** at some point, thereby triggering BodyCanvasWidget::maybeRebuildLayout(). At this point the
|
||||
** timeline::TrackProfile needs to be established, so to reflect the succession and extension
|
||||
** of actual track spaces running alongside the time axis. This is accomplished through a global
|
||||
** timeline::DisplayEvaluation pass, recursively visiting all the involved parts to perform
|
||||
** size adjustments, until the layout is globally balanced.
|
||||
**
|
||||
** @todo as of 10/2018 timeline display in the UI is rebuilt to match the architecture
|
||||
** @todo still WIP as of 3/2020 -- yet the basic structure is settled by now.
|
||||
**
|
||||
*/
|
||||
|
||||
|
|
@ -48,12 +83,16 @@
|
|||
#define STAGE_TIMELINE_TRACK_PRESENTER_H
|
||||
|
||||
#include "stage/gtk-base.hpp"
|
||||
#include "include/ui-protocol.hpp"
|
||||
#include "stage/model/controller.hpp"
|
||||
#include "stage/timeline/display-evaluation.hpp"
|
||||
#include "stage/timeline/marker-widget.hpp"
|
||||
#include "stage/timeline/clip-presenter.hpp"
|
||||
#include "stage/timeline/track-head-widget.hpp"
|
||||
#include "stage/timeline/track-body.hpp"
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1201 : test/code... remove this
|
||||
#include "lib/format-cout.hpp"
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1201 : test/code... remove this
|
||||
|
||||
#include "lib/nocopy.hpp"
|
||||
//#include "lib/util.hpp"
|
||||
|
|
@ -70,6 +109,10 @@ namespace timeline {
|
|||
using std::vector;
|
||||
using std::unique_ptr;
|
||||
|
||||
using lib::diff::TreeMutator;
|
||||
using lib::diff::collection;
|
||||
using std::make_unique;
|
||||
|
||||
|
||||
/**
|
||||
* Reference frame to organise the presentation related to a specific Track in the Timeline-GUI.
|
||||
|
|
@ -134,8 +177,6 @@ namespace timeline {
|
|||
|
||||
|
||||
public:
|
||||
~TrackPresenter();
|
||||
|
||||
/**
|
||||
* @param id identity used to refer to a corresponding session::Fork
|
||||
* @param nexus a way to connect this Controller to the UI-Bus.
|
||||
|
|
@ -179,5 +220,132 @@ namespace timeline {
|
|||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1201 : test/code... remove this
|
||||
inline void
|
||||
TrackPresenter::injectDebugTrackLabels()
|
||||
{
|
||||
uint x = rand() % 50;
|
||||
uint y = 0;
|
||||
Gtk::Button* butt = Gtk::manage (new ViewHooked<Gtk::Button, Gtk::Widget>{display_.hookedAt(x,y), TODO_trackName_});
|
||||
butt->signal_clicked().connect(
|
||||
[butt]{ cout << "|=="<<butt->get_label()<<endl; });
|
||||
butt->show();
|
||||
|
||||
for (auto& subTrack : subFork_)
|
||||
subTrack->injectDebugTrackLabels();
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1201 : test/code... remove this
|
||||
/**
|
||||
* @note we distinguish between the contents of our four nested child collections
|
||||
* based on the symbolic type field sent in the Record type within the diff representation
|
||||
* - "Marker" designates a Marker object
|
||||
* - "Clip" designates a Clip placed on this track
|
||||
* - "Fork" designates a nested sub-track
|
||||
* - "Ruler" designates a nested ruler (timescale, overview,....) belonging to this track
|
||||
* @see TimelineController::buildMutator() for a basic explanation of the data binding mechanism
|
||||
*/
|
||||
inline void
|
||||
TrackPresenter::buildMutator (TreeMutator::Handle buffer)
|
||||
{
|
||||
using PFork = unique_ptr<TrackPresenter>;
|
||||
using PClip = unique_ptr<ClipPresenter>;
|
||||
using PMarker = unique_ptr<MarkerWidget>;
|
||||
using PRuler = unique_ptr<RulerTrack>;
|
||||
|
||||
buffer.create (
|
||||
TreeMutator::build()
|
||||
.attach (collection(display_.bindRulers())
|
||||
.isApplicableIf ([&](GenNode const& spec) -> bool
|
||||
{ // »Selector« : require object-like sub scope with type-field "Ruler"
|
||||
return TYPE_Ruler == spec.data.recordType();
|
||||
})
|
||||
.matchElement ([&](GenNode const& spec, PRuler const& elm) -> bool
|
||||
{
|
||||
return spec.idi == elm->getID();
|
||||
})
|
||||
.constructFrom ([&](GenNode const& spec) -> PRuler
|
||||
{ // »Constructor« : how to attach a new ruler track
|
||||
return make_unique<RulerTrack> (spec.idi, this->uiBus_, *this);
|
||||
})
|
||||
.buildChildMutator ([&](PRuler& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
|
||||
{
|
||||
if (subID != target->getID()) return false;
|
||||
target->buildMutator (buff);
|
||||
return true;
|
||||
}))
|
||||
.attach (collection(markers_)
|
||||
.isApplicableIf ([&](GenNode const& spec) -> bool
|
||||
{ // »Selector« : require object-like sub scope with type-field "Marker"
|
||||
return TYPE_Marker == spec.data.recordType();
|
||||
})
|
||||
.matchElement ([&](GenNode const& spec, PMarker const& elm) -> bool
|
||||
{
|
||||
return spec.idi == elm->getID();
|
||||
})
|
||||
.constructFrom ([&](GenNode const& spec) -> PMarker
|
||||
{
|
||||
return make_unique<MarkerWidget> (spec.idi, this->uiBus_);
|
||||
})
|
||||
.buildChildMutator ([&](PMarker& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
|
||||
{
|
||||
if (subID != target->getID()) return false;
|
||||
target->buildMutator (buff);
|
||||
return true;
|
||||
}))
|
||||
.attach (collection(clips_)
|
||||
.isApplicableIf ([&](GenNode const& spec) -> bool
|
||||
{ // »Selector« : require object-like sub scope with type-field "Clip"
|
||||
return TYPE_Clip == spec.data.recordType();
|
||||
})
|
||||
.matchElement ([&](GenNode const& spec, PClip const& elm) -> bool
|
||||
{
|
||||
return spec.idi == elm->getID();
|
||||
})
|
||||
.constructFrom ([&](GenNode const& spec) -> PClip
|
||||
{
|
||||
return make_unique<ClipPresenter> (spec.idi, this->uiBus_);
|
||||
})
|
||||
.buildChildMutator ([&](PClip& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
|
||||
{
|
||||
if (subID != target->getID()) return false;
|
||||
target->buildMutator (buff);
|
||||
return true;
|
||||
}))
|
||||
.attach (collection(subFork_)
|
||||
.isApplicableIf ([&](GenNode const& spec) -> bool
|
||||
{ // »Selector« : require object-like sub scope with type-field "Fork"
|
||||
return TYPE_Fork == spec.data.recordType();
|
||||
})
|
||||
.matchElement ([&](GenNode const& spec, PFork const& elm) -> bool
|
||||
{
|
||||
return spec.idi == elm->getID();
|
||||
})
|
||||
.constructFrom ([&](GenNode const& spec) -> PFork
|
||||
{
|
||||
return make_unique<TrackPresenter> (spec.idi, uiBus_, this->display_);
|
||||
})
|
||||
.buildChildMutator ([&](PFork& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
|
||||
{
|
||||
if (subID != target->getID()) return false;
|
||||
target->buildMutator (buff);
|
||||
return true;
|
||||
}))
|
||||
.change(ATTR_name, [&](string val)
|
||||
{ // »Attribute Setter« : receive a new value for the track name field
|
||||
this->setTrackName (val);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/** @todo 2/2020 */
|
||||
inline void
|
||||
TrackPresenter::establishLayout (DisplayEvaluation& displayEvaluation)
|
||||
{
|
||||
UNIMPLEMENTED ("respond to the DisplayEvaluation-Pass and pass on evaluation recursively");
|
||||
}
|
||||
|
||||
|
||||
}}// namespace stage::timeline
|
||||
#endif /*STAGE_TIMELINE_TRACK_PRESENTER_H*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue