2018-02-01 23:08:43 +01:00
|
|
|
/*
|
|
|
|
|
UI-LOCATION-SOLVER.hpp - decide upon a possible location for some UI component
|
|
|
|
|
|
|
|
|
|
Copyright (C) Lumiera.org
|
|
|
|
|
2018, 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 ui-location-resolver.hpp
|
|
|
|
|
** A solver to match incomplete coordinate specifications against the actual UI topology.
|
|
|
|
|
** Within the Lumiera UI, a _component view_ is typically _created or retrieved_ to live at some position
|
|
|
|
|
** within the tree-like topology of the interface. Such happens as a consequence of interaction or other
|
|
|
|
|
** events, and the logic as to where and how to place a new UI element shall not be intermingled with the
|
|
|
|
|
** actual event handling code. Rather, the ViewLocator, as a service related to the InteractionDirector,
|
|
|
|
|
** can be invoked to draw on some default configuration plus the actual UI topology present at this time.
|
|
|
|
|
**
|
|
|
|
|
** @todo WIP 2/2018 early draft ////////////////////////////////////////////////////////////TICKET #1127
|
|
|
|
|
**
|
|
|
|
|
** @see UILocationResolver_test
|
|
|
|
|
** @see ViewSpecDSL_test
|
|
|
|
|
** @see UICoordResolver
|
|
|
|
|
** @see view-locator.hpp
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef GUI_INTERACT_UI_LOCATION_SOLVER_H
|
|
|
|
|
#define GUI_INTERACT_UI_LOCATION_SOLVER_H
|
|
|
|
|
|
|
|
|
|
#include "lib/error.hpp"
|
2018-02-08 00:37:02 +01:00
|
|
|
#include "lib/symbol.hpp"
|
2018-02-01 23:08:43 +01:00
|
|
|
//#include "lib/meta/function.hpp"
|
|
|
|
|
//#include "lib/meta/tuple-helper.hpp"
|
|
|
|
|
//#include "lib/meta/function-closure.hpp"
|
|
|
|
|
#include "gui/interact/ui-coord.hpp"
|
2018-02-08 00:37:02 +01:00
|
|
|
#include "gui/interact/ui-coord-resolver.hpp"
|
2018-02-01 23:08:43 +01:00
|
|
|
|
|
|
|
|
//#include <functional>
|
2018-02-07 03:11:12 +01:00
|
|
|
#include <boost/noncopyable.hpp>
|
2018-02-01 23:08:43 +01:00
|
|
|
#include <utility>
|
2018-02-07 03:11:12 +01:00
|
|
|
#include <vector>
|
2018-02-01 23:08:43 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace gui {
|
|
|
|
|
namespace interact {
|
|
|
|
|
|
|
|
|
|
// using std::forward;
|
2018-02-07 03:11:12 +01:00
|
|
|
using std::move;
|
2018-02-08 00:37:02 +01:00
|
|
|
using lib::Symbol;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LocationQuery;
|
|
|
|
|
using LocationQueryAccess = std::function<LocationQuery&()>;
|
|
|
|
|
|
|
|
|
|
/** @internal access UI service to query and discover locations within UI topology */
|
|
|
|
|
extern LocationQueryAccess loactionQuery;
|
|
|
|
|
|
|
|
|
|
|
2018-02-01 23:08:43 +01:00
|
|
|
|
2018-02-09 04:10:53 +01:00
|
|
|
struct LocationClause
|
2018-02-07 03:11:12 +01:00
|
|
|
: boost::noncopyable
|
|
|
|
|
{
|
2018-02-09 04:10:53 +01:00
|
|
|
UICoord pattern;
|
|
|
|
|
bool createParents;
|
2018-02-07 03:11:12 +01:00
|
|
|
|
2018-02-09 04:10:53 +01:00
|
|
|
|
|
|
|
|
LocationClause (UICoord && locationPattern, bool allowCreate =false)
|
|
|
|
|
: pattern{move (locationPattern)}
|
|
|
|
|
, createParents{allowCreate}
|
2018-02-07 03:11:12 +01:00
|
|
|
{ }
|
|
|
|
|
LocationClause (LocationClause && rr)
|
2018-02-09 04:10:53 +01:00
|
|
|
: pattern{move (rr.pattern)}
|
|
|
|
|
, createParents{rr.createParents}
|
2018-02-07 03:11:12 +01:00
|
|
|
{ }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LocationRule
|
|
|
|
|
: boost::noncopyable
|
|
|
|
|
{
|
|
|
|
|
using Clauses = std::vector<LocationClause>;
|
|
|
|
|
|
|
|
|
|
Clauses clauses_;
|
|
|
|
|
|
|
|
|
|
public:
|
2018-02-11 04:00:59 +01:00
|
|
|
LocationRule (LocationClause && firstRule)
|
2018-02-07 03:11:12 +01:00
|
|
|
: clauses_{}
|
2018-02-07 04:23:44 +01:00
|
|
|
{
|
|
|
|
|
this->append (move (firstRule));
|
|
|
|
|
}
|
|
|
|
|
LocationRule (LocationRule && rr)
|
|
|
|
|
: clauses_{move (rr.clauses_)}
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
LocationRule&&
|
2018-02-11 04:00:59 +01:00
|
|
|
append (LocationClause && furtherRule)
|
2018-02-07 04:23:44 +01:00
|
|
|
{
|
|
|
|
|
clauses_.emplace_back (move (furtherRule));
|
|
|
|
|
return move (*this);
|
2018-02-07 03:11:12 +01:00
|
|
|
}
|
2018-02-08 01:50:11 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
using iterator = lib::RangeIter<Clauses::const_iterator>;
|
|
|
|
|
iterator begin() const { return iterator{clauses_.begin(), clauses_.end()}; }
|
|
|
|
|
iterator end() const { return iterator(); }
|
2018-02-07 03:11:12 +01:00
|
|
|
};
|
2018-02-01 23:08:43 +01:00
|
|
|
|
|
|
|
|
|
2018-02-11 04:00:59 +01:00
|
|
|
/* ==== Support of UI-Coordinate notation within the ViewSpec-DSL ==== */
|
|
|
|
|
|
|
|
|
|
/** interprets the current (inline) contents of an UICoord builder expression
|
|
|
|
|
* as a standard LocationClause, which has the meaning of "when an element
|
|
|
|
|
* exists at the location XYZ in the real UI"
|
|
|
|
|
* @warning like all the UICoord::Builder functions, the contents are moved.
|
|
|
|
|
* Thus, after using this conversion path _once_, the Builder _is defunct_
|
|
|
|
|
*/
|
|
|
|
|
inline
|
|
|
|
|
UICoord::Builder::operator LocationClause()
|
|
|
|
|
{
|
|
|
|
|
return LocationClause{move(*this), false};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** interprets the current (inline) builder contents as _create clause_,
|
|
|
|
|
* which has the meaning "create a new element XYZ when possible" */
|
|
|
|
|
inline LocationClause
|
|
|
|
|
UICoord::Builder::create()
|
|
|
|
|
{
|
|
|
|
|
return LocationClause{move(*this), true};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2018-02-07 04:23:44 +01:00
|
|
|
/** DSL operator to assemble a sequence of clauses.
|
|
|
|
|
* Introduced solely for the purpose of writing location specifications within the
|
|
|
|
|
* [ViewSpec-DSL](\ref id-scheme.hpp), this operator acts on several UI-Coordinate specs
|
|
|
|
|
* to create a sequence of clauses, to be checked against the currently existing UI topology,
|
|
|
|
|
* in the given order, ranging from more specific to more general patterns.
|
|
|
|
|
*/
|
|
|
|
|
inline LocationRule
|
|
|
|
|
operator or (UICoord::Builder && firstRule, UICoord secondRule)
|
|
|
|
|
{
|
|
|
|
|
return LocationRule{move (firstRule)}
|
|
|
|
|
.append (move (secondRule));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline LocationRule&&
|
|
|
|
|
operator or (LocationRule && ruleSet, UICoord furtherRule)
|
|
|
|
|
{
|
|
|
|
|
ruleSet.append (move (furtherRule));
|
|
|
|
|
return move(ruleSet);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-02-01 23:08:43 +01:00
|
|
|
/**
|
|
|
|
|
* Access or allocate a UI component view
|
|
|
|
|
*
|
2018-02-08 01:50:11 +01:00
|
|
|
* @todo initial draft as of 2/2018 -- actual implementation need to be filled in
|
2018-02-01 23:08:43 +01:00
|
|
|
*/
|
|
|
|
|
class UILocationSolver
|
|
|
|
|
: boost::noncopyable
|
|
|
|
|
{
|
|
|
|
|
// ctrl::GlobalCtx& globals_;
|
2018-02-08 01:50:11 +01:00
|
|
|
LocationQueryAccess getLocationQuery;
|
2018-02-01 23:08:43 +01:00
|
|
|
|
|
|
|
|
public:
|
2018-02-08 00:37:02 +01:00
|
|
|
explicit
|
2018-02-08 01:50:11 +01:00
|
|
|
UILocationSolver (LocationQueryAccess accessor)
|
|
|
|
|
: getLocationQuery{accessor}
|
2018-02-08 00:37:02 +01:00
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
explicit
|
2018-02-08 01:50:11 +01:00
|
|
|
UILocationSolver (LocationQuery& locationQueryService)
|
|
|
|
|
: getLocationQuery{[&]() -> LocationQuery& { return locationQueryService; }}
|
2018-02-01 23:08:43 +01:00
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2018-02-08 01:50:11 +01:00
|
|
|
* Solve for a location according to the given location rule.
|
|
|
|
|
* @param depth desired kind of UI element (and thus the depth in the UI topology tree)
|
|
|
|
|
* @param elementType designator of the specific element to be created at that level
|
2018-02-09 04:10:53 +01:00
|
|
|
* @return an explicit location, resolved against the current UI topology. May be empty
|
2018-02-08 01:50:11 +01:00
|
|
|
* @remarks the returned path is either empty (no solution exists), or it is "partially covered"
|
|
|
|
|
* by the existing UI; here, the "covered" part are the already existing UI elements,
|
|
|
|
|
* while the remaining, uncovered extension describes additional elements to be created.
|
|
|
|
|
* When the resolution process found an already existing UI element, the returned path
|
|
|
|
|
* is completely covered. The degree of coverage of a path can be found out with the
|
2018-02-09 23:49:36 +01:00
|
|
|
* help of a UICoordResolver, which also needs a LocationQuery (service) to discover
|
|
|
|
|
* the currently existing UI topology.
|
2018-02-01 23:08:43 +01:00
|
|
|
*/
|
2018-02-08 00:37:02 +01:00
|
|
|
UICoord
|
2018-02-11 04:16:58 +01:00
|
|
|
solve (LocationRule const& rule, size_t depth, Symbol elementTypeID)
|
2018-02-08 00:37:02 +01:00
|
|
|
{
|
2018-02-08 01:50:11 +01:00
|
|
|
for (auto& clause : rule)
|
|
|
|
|
{
|
2018-02-09 23:49:36 +01:00
|
|
|
// Clauses which do not at least describe an element at parent level
|
|
|
|
|
// will never lead to a solution and can thus be skipped
|
|
|
|
|
if (depth+1 < clause.pattern.size()
|
|
|
|
|
or depth > clause.pattern.size())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// try to solve the current Clause by matching against real UI topology
|
2018-02-09 04:10:53 +01:00
|
|
|
UICoordResolver resolver{clause.pattern, getLocationQuery()};
|
2018-02-09 23:49:36 +01:00
|
|
|
resolver.coverPartially(); // now either holds a solution or is empty
|
|
|
|
|
|
|
|
|
|
if (not isnil(resolver) // Solution found!
|
|
|
|
|
and (clause.createParents // The "create" case requires only some part to exist,
|
|
|
|
|
or resolver.isCoveredTotally())) // while in the default case we demand complete coverage
|
2018-02-09 04:10:53 +01:00
|
|
|
{
|
2018-02-09 23:49:36 +01:00
|
|
|
if (depth == clause.pattern.size())
|
|
|
|
|
// append ID of the new element to be created
|
|
|
|
|
// unless it's already there (and thus exists)
|
|
|
|
|
resolver.append (elementTypeID);
|
|
|
|
|
return move (resolver);
|
|
|
|
|
// use the first suitable solution and exit
|
2018-02-09 04:10:53 +01:00
|
|
|
}
|
|
|
|
|
else
|
2018-02-09 23:49:36 +01:00
|
|
|
if (clause.createParents and clause.pattern.isExplicit())
|
|
|
|
|
// allow creation of a totally new path from scratch
|
|
|
|
|
// as long as it is complete and explicitly given
|
|
|
|
|
return clause.pattern;
|
2018-02-08 01:50:11 +01:00
|
|
|
}
|
2018-02-09 23:49:36 +01:00
|
|
|
//all clauses tried without success...
|
|
|
|
|
return UICoord();
|
2018-02-08 00:37:02 +01:00
|
|
|
}
|
2018-02-01 23:08:43 +01:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}}// namespace gui::interact
|
|
|
|
|
#endif /*GUI_INTERACT_UI_LOCATION_SOLVER_H*/
|