224 lines
9.9 KiB
C++
224 lines
9.9 KiB
C++
/*
|
|
VIEW-SPEC-DSL.hpp - configuration of view allocation patterns
|
|
|
|
Copyright (C) Lumiera.org
|
|
2017, 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 view-spec-dsl.hpp
|
|
** A framework for configuration of view access and allocation patterns.
|
|
** Component views are building blocks of the Lumiera UI, and may, depending on their
|
|
** type, be instantiated or allocated according to specific rules and patterns. And these
|
|
** might vary in accordance to the desired working style. To give a typical example, at times
|
|
** it might be mandatory to use a single, external output for all kind of media playback, while
|
|
** other users prefer the classical editing application layout with two media viewers side by
|
|
** side. And yet another working style would be to use a stack of media viewers allocated on
|
|
** demand in MRU-fashion.
|
|
**
|
|
** To specify those standard behaviour patterns, we provide a small internal DSL to spell out
|
|
** the default configuration in a (hopefully) self explanatory way.
|
|
** @todo as of 9/2017, we confirm the intention to open this configuration DSL for some kind
|
|
** of user provided flexible configuration of screen layouts eventually; yet right now,
|
|
** only the foundation for this flexible configuration is provided while the defaults
|
|
** are to be compiled into the UI as hard wired constants.
|
|
**
|
|
**
|
|
** # Allocation of UI component views
|
|
**
|
|
** Within this context, _Allocation_ means...
|
|
** - to constitute the desired element's identity
|
|
** - to consider multiplicity and possibly retrieve an existing instance
|
|
** - to determine the hosting location
|
|
** - possibly to instantiate and register a new instance
|
|
** - and finally to configure that instance for the desired role
|
|
**
|
|
** ## View access
|
|
**
|
|
** The global access point to component views is the ViewLocator within InteractionDirector,
|
|
** which exposes a generic access- and management API to
|
|
** - get (possibly create) some view of given type
|
|
** - get (possibly create) a view with specific identity
|
|
** - destroy a specific view
|
|
** Together these operations expose a strictly typed direct reference to the respective view component
|
|
** and thus allow to manage them like child objects. Operating on top of these primitive operations,
|
|
** the _configuration of view access patterns_ creates a flexible binding layer, which isolates the
|
|
** users of component views (typically other parts of the UI) from the actual mechanics of locating.
|
|
** While the client just retrieves a view instance, a dedicated _allocation logic_ ensures this view
|
|
** instance is actually placed at the desired place within the UI, and manages active view instances.
|
|
**
|
|
** ## Configuration DSL
|
|
**
|
|
** Access to component views typically happens through the ViewLocator provided by the InteractionDirector.
|
|
** The implementation of view access in turn redirects access through the configured patterns.
|
|
** - at [definition site](\ref id-scheme.hpp), explicit specialisations are given for the relevant types
|
|
** of component view
|
|
** - each of those general view configurations
|
|
** * defines the multiplicity allowed for this kind of view
|
|
** * defines how to locate this view
|
|
** ... and that location definition is given as a list of alternatives in order of precedence.
|
|
** That is, the system tries each pattern of location and uses the first one applicable.
|
|
**
|
|
** Actually, the definitions and tokens used within the configuration DSL are implemented as _functors._
|
|
** At implementation level, the basic specification tokens are bound to the corresponding operations
|
|
** within ViewLocator and PanelLocator. Thus, the "value" retrieved from the configuration is actually
|
|
** a functor, which provides the desired behaviour pattern
|
|
** - the *locate* spec is evaluated to yield [user interface coordinates](\ref UICoord) representing the
|
|
** desired location of the view
|
|
** - the *allocate* spec is then evaluated as a second step, while providing the coordinates from first step.
|
|
** This results in calling a dedicated _view allocator_ to create a new view or re-use an existing one
|
|
** as appropriate.
|
|
**
|
|
** @note the actual binding for the DSL tokens can be found in the [ViewLocator implementation](view-locator.cpp)
|
|
**
|
|
** @see ViewSpecDSL_test
|
|
** @see id-scheme.hpp
|
|
** @see view-locator.hpp
|
|
** @see view-locator.cpp
|
|
** @see interaction-director.hpp
|
|
*/
|
|
|
|
|
|
#ifndef GUI_INTERACT_VIEW_SPEC_DSL_H
|
|
#define GUI_INTERACT_VIEW_SPEC_DSL_H
|
|
|
|
#include "lib/error.hpp"
|
|
#include "lib/symbol.hpp"
|
|
#include "lib/meta/function.hpp"
|
|
#include "lib/meta/tuple-helper.hpp"
|
|
#include "lib/meta/function-closure.hpp"
|
|
#include "gui/interact/ui-location-solver.hpp"
|
|
#include "gui/interact/ui-coord.hpp"
|
|
|
|
#include <functional>
|
|
#include <utility>
|
|
|
|
|
|
namespace gui {
|
|
namespace interact {
|
|
|
|
using std::forward;
|
|
|
|
|
|
|
|
/**
|
|
* A specification to describe the desired location of a component view within the Lumiera UI.
|
|
* ViewSpec is basically a set of [UI coordinates](\ref UICoord), with the additional possibility
|
|
* of specifying several alternatives, with the intention to pick the first applicable one.
|
|
*
|
|
* @todo initial draft as of 9/2017
|
|
*/
|
|
class ViewSpec
|
|
{
|
|
|
|
public:
|
|
ViewSpec(UICoord coordinates)
|
|
{
|
|
UNIMPLEMENTED ("build a view spec from explicitly given UI coordinates");
|
|
}
|
|
|
|
/** shortcut to allow initialisation from UI-Coordinate builder expression */
|
|
ViewSpec(UICoord::Builder&& coordinates)
|
|
: ViewSpec{UICoord(std::move (coordinates))}
|
|
{ }
|
|
|
|
operator UICoord()
|
|
{
|
|
UNIMPLEMENTED ("resolve a view spec to yield explicit UI coordinates");
|
|
}
|
|
private:
|
|
|
|
};
|
|
|
|
|
|
/**
|
|
* Allocator is a functor to resolve a given, desired location of a view within the UI, resulting
|
|
* in creation or allocation of the view. The result of this invocation are the UI coordinates
|
|
* of an existing or newly created view.
|
|
*/
|
|
using Allocator = std::function<UICoord(UICoord)>;
|
|
|
|
|
|
/**
|
|
* A specification to describe the strategy for allocating (placing, retrieving) a component view.
|
|
* On a DSL-technical level, AllocSpec is a _function generator_: it produces Allocator entities,
|
|
* which in turn are functions to perform the actual allocation.
|
|
* @note AllocSpec relies on a specific *convention* how to specify the actual allocation operation:
|
|
* - the operation takes one main argument and additional parameters
|
|
* - its first argument is the _work triggering argument_, namely the concrete UI coordinates
|
|
* passed to the Allocator, requesting to create or retrieve or claim the view at that location
|
|
* - the additional argument(s) serves for parametrisation or specialisation of the strategy;
|
|
* they will be "baked" into the generated allocator.
|
|
* @remark to give a typical example: an allocation operation might want to limit the number of
|
|
* generated views per window and take this instance limit as additional parameter. But for the DSL
|
|
* we want a allocation spec of the form `limitPerWindow(cnt)` -- which is a function accepting the
|
|
* concrete limit and producing the actual allocator function `UICoord -> UICoord`, which has this
|
|
* actual limit "baked in".
|
|
*/
|
|
template<typename... ARGS>
|
|
class AllocSpec
|
|
: public std::function<Allocator(ARGS&&...)>
|
|
{
|
|
/** @internal analyse and dissect the given allocation operation
|
|
* and fabricate a builder to accept the additional arguments
|
|
* and produce a partially closed allocation functor
|
|
* @note the generated lambda captures by value */
|
|
template<class FUN>
|
|
static auto
|
|
buildPartialApplicator (FUN&& fun)
|
|
{
|
|
using lib::meta::_Fun;
|
|
using lib::meta::Split;
|
|
using lib::meta::Tuple;
|
|
using lib::meta::Types;
|
|
using lib::meta::func::_Sig;
|
|
using lib::meta::func::PApply;
|
|
|
|
typedef typename _Fun<FUN>::Ret Ret;
|
|
typedef typename _Fun<FUN>::Args Args;
|
|
typedef typename Split<Args>::Head Arg1;
|
|
typedef typename Split<Args>::Tail FurtherArgs;
|
|
|
|
static_assert (std::is_convertible<UICoord, Arg1>::value,
|
|
"Allocator function must accept UICoordinates (where to create/locate) as first argument");
|
|
static_assert (std::is_convertible<Ret, UICoord>::value,
|
|
"Allocator function must produce UICoordinates (of the actually allocated UI element)");
|
|
static_assert (std::is_convertible<FurtherArgs, Types<ARGS...>>::value,
|
|
"Additional parameters of the allocator function must match the AllocSpec<ARGS> template parameters");
|
|
|
|
|
|
typedef Tuple<FurtherArgs> ArgTuple;
|
|
|
|
return [=](auto&&... args) -> Allocator
|
|
{
|
|
ArgTuple params {forward<decltype(args)> (args)...};
|
|
return PApply<FUN,FurtherArgs>::bindBack (fun, params);
|
|
};
|
|
}
|
|
|
|
|
|
public:
|
|
template<class FUN>
|
|
AllocSpec(FUN&& fun)
|
|
: std::function<Allocator(ARGS&&...)> {buildPartialApplicator (forward<FUN> (fun))}
|
|
{ }
|
|
};
|
|
|
|
|
|
}}// namespace gui::interact
|
|
#endif /*GUI_INTERACT_VIEW_SPEC_DSL_H*/
|