* layers system (stored in comments of .map file)

menus	* View->Layers Browser (L)
	* Edit->Expand Selection To Layers (ctrl + q)
	* shift + q: move-to LayersMenu
closes #133
This commit is contained in:
Garux
2025-08-31 16:25:12 +05:00
parent 581dd79acb
commit 1599d0a64b
40 changed files with 1042 additions and 82 deletions
+1
View File
@@ -882,6 +882,7 @@ $(INSTALLDIR)/radiant.$(EXE): \
radiant/gtkmisc.o \
radiant/help.o \
radiant/image.o \
radiant/layerswindow.o \
radiant/mainframe.o \
radiant/main.o \
radiant/map.o \
+4
View File
@@ -369,6 +369,9 @@ menus...
* Modify->Repeat: saved transforms feedback and individual resets
* Modify->Repeat->Reset Transforms (alt + r)
* Entity->Reload Entity Definitions
* View->Layers Browser (L)
* Edit->Expand Selection To Layers (ctrl + q)
* shift + q: move-to LayersMenu
misc...
* removed note to save before going to preferences, project settings
@@ -1133,6 +1136,7 @@ misc...
* build menu.customize: drag and drop items; ctrl + drag = copy
* build menu.customize: remove command line item when set empty
* build menu.customize.build variables: are editable
* layers system (stored in comments of .map file)
+4
View File
@@ -55,6 +55,8 @@ const TypeId NODETYPEID_NONE = NODETYPEID_MAX;
const TypeId INSTANCETYPEID_MAX = 64;
const TypeId INSTANCETYPEID_NONE = INSTANCETYPEID_MAX;
class Layer;
namespace scene
{
/// \brief A unique key to an instance of a node in the scene-graph.
@@ -88,6 +90,8 @@ public:
virtual void insert_root( Node& root ) = 0;
/// \brief Clears the root-node of the graph.
virtual void erase_root() = 0;
/// \brief Returns current layer or 0 when no root is inserted.
virtual Layer* currentLayer() = 0;
/// \brief Traverses all nodes in the graph depth-first, starting from the root node.
virtual void traverse( const Walker& walker ) = 0;
/// \brief Traverses all nodes in the graph depth-first, starting from 'start'.
+1
View File
@@ -63,6 +63,7 @@ struct _QERScripLibTable
STRING_CONSTANT( Name, "scriptlib" );
Tokeniser& ( *m_pfnNewScriptTokeniser )( TextInputStream & istream );
Tokeniser& ( *m_pfnNewMapTokeniser )( TextInputStream & istream );
Tokeniser& ( *m_pfnNewSimpleTokeniser )( TextInputStream & istream );
TokenWriter& ( *m_pfnNewSimpleTokenWriter )( TextOutputStream & ostream );
};
+86
View File
@@ -0,0 +1,86 @@
/*
Copyright (C) 2001-2006, William Joseph.
All Rights Reserved.
This file is part of GtkRadiant.
GtkRadiant 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.
GtkRadiant 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 GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include "scenelib.h"
#include "string/string.h"
#include <concepts>
#define LAYERIDX0 0
#define LAYERIDXPARENT -1
template <typename F>
concept LayerFunctor = std::invocable<F, Layer&> && std::is_void_v<std::invoke_result_t<F, Layer&>>;
class Layer
{
public:
CopiedString m_name;
std::list<Layer> m_children;
Layer *m_parent; // must be valid besides 0 in the root 'Layers' container
int m_color[ 3 ]{}; // 0 0 0 = default gui color
int m_parentIndex = LAYERIDXPARENT; // parent index during map loading // -1 parent = root
int m_ownIndex = LAYERIDXPARENT; // own index for map writing and operator== set via update_ownIndices()
Layer( const char *name, Layer *parent ) : m_name( name ), m_parent( parent ){
}
Layer( Layer&& ) noexcept = default; // no copy
bool operator==( const Layer& other ) const {
return m_name == other.m_name
&& m_parent->m_ownIndex == const_cast<Layer*>( other.m_parent )->m_ownIndex
&& !memcmp( m_color, other.m_color, sizeof( m_color ) )
&& m_children == other.m_children;
}
private:
void forEach( std::list<Layer>& layers, LayerFunctor auto&& functor ){
for( Layer& layer : layers ){
functor( layer );
forEach( layer.m_children, functor );
}
}
public:
void forEach( LayerFunctor auto&& functor ){
forEach( m_children, functor );
}
using iterator = decltype( m_children )::iterator;
};
class Layers : public Layer
{
public:
STRING_CONSTANT( Name, "Layers" );
Layer *m_currentLayer;
Layers() : Layer( "", nullptr ){
m_children.emplace_back( "0", this ); // add default layer
m_currentLayer = &m_children.front();
}
Layers( Layers&& ) noexcept = default; // no copy
void update_ownIndices(){
forEach( [i = 0]( Layer& layer ) mutable { layer.m_ownIndex = i++; } );
}
};
inline Layers* Node_getLayers( scene::Node& node ){
return NodeTypeCast<Layers>::cast( node );
}
+11 -4
View File
@@ -23,6 +23,7 @@
#include "nameable.h"
#include "mapfile.h"
#include "layers.h"
#include "traverselib.h"
#include "transformlib.h"
@@ -136,6 +137,7 @@ class MapRoot : public scene::Node::Symbiot, public scene::Instantiable, public
NodeContainedCast<MapRoot, TransformNode>::install( m_casts );
NodeContainedCast<MapRoot, Nameable>::install( m_casts );
NodeContainedCast<MapRoot, MapFile>::install( m_casts );
NodeContainedCast<MapRoot, Layers>::install( m_casts );
}
NodeTypeCastTable& get(){
return m_casts;
@@ -149,6 +151,7 @@ class MapRoot : public scene::Node::Symbiot, public scene::Instantiable, public
typedef SelectableInstance Instance;
NameableString m_name;
UndoFileChangeTracker m_changeTracker;
Layers m_layers;
public:
typedef LazyStatic<TypeCasts> StaticTypeCasts;
@@ -164,8 +167,11 @@ public:
MapFile& get( NullType<MapFile>){
return m_changeTracker;
}
Layers& get( NullType<Layers>){
return m_layers;
}
MapRoot( const char* name ) : m_node( this, this, StaticTypeCasts::instance().get() ), m_name( name ){
MapRoot( const char* name ) : m_node( this, this, StaticTypeCasts::instance().get(), nullptr ), m_name( name ){
m_node.m_isRoot = true;
m_traverse.attach( this );
@@ -174,6 +180,7 @@ public:
}
~MapRoot(){
}
MapRoot( MapRoot&& ) noexcept = default; // no copy: Layers use m_parent pointer
void release(){
GlobalUndoSystem().trackerDetach( m_changeTracker );
@@ -203,9 +210,9 @@ public:
m_instances.erase( child );
}
scene::Node& clone() const {
return ( new MapRoot( *this ) )->node();
}
// scene::Node& clone() const {
// return ( new MapRoot( *this ) )->node();
// }
scene::Instance* create( const scene::Path& path, scene::Instance* parent ){
return new Instance( path, parent );
+49 -3
View File
@@ -146,6 +146,8 @@ class NodeIdentityCast :
{
};
class Layer;
namespace scene
{
class Node
@@ -156,6 +158,7 @@ public:
eHidden = 1 << 0,
eFiltered = 1 << 1,
eExcluded = 1 << 2,
eLayerHidden = 1 << 3,
};
class Symbiot
@@ -175,18 +178,20 @@ private:
public:
bool m_isRoot;
Layer *m_layer; // nullptr for group entity node; they are not finely manageable and affect children visibility
bool isRoot(){
return m_isRoot;
}
Node( Symbiot* symbiot, void* node, NodeTypeCastTable& casts ) :
Node( Symbiot* symbiot, void* node, NodeTypeCastTable& casts, Layer *layer ) :
m_state( eVisible ),
m_refcount( 0 ),
m_symbiot( symbiot ),
m_node( node ),
m_casts( casts ),
m_isRoot( false ){
m_isRoot( false ),
m_layer( layer ){
}
~Node(){
}
@@ -238,7 +243,7 @@ class NullNode : public Node::Symbiot
NodeTypeCastTable m_casts;
Node m_node;
public:
NullNode() : m_node( this, 0, m_casts ){
NullNode() : m_node( this, 0, m_casts, nullptr ){
}
void release(){
delete this;
@@ -921,6 +926,47 @@ inline bool Entity_isSelected( scene::Instance& entity ){
}
template<class Check>
bool Traversable_all_of_children( scene::Traversable* traversable, const Check&& check ){
class Check_all : public scene::Traversable::Walker
{
const Check m_check;
public:
mutable bool m_all = true; // true for empty container
Check_all( Check check ) : m_check( check ){
}
bool pre( scene::Node& node ) const override {
if( m_all && !m_check( node ) )
m_all = false;
return m_all;
}
} check_all( check );
traversable->traverse( check_all );
return check_all.m_all;
}
template<class Check>
bool Traversable_any_of_children( scene::Traversable* traversable, const Check&& check ){
class Check_all : public scene::Traversable::Walker
{
const Check m_check;
public:
mutable bool m_any = false; // false for empty container
Check_all( Check check ) : m_check( check ){
}
bool pre( scene::Node& node ) const override {
if( !m_any && m_check( node ) )
m_any = true;
return !m_any;
}
} check_all( check );
traversable->traverse( check_all );
return check_all.m_any;
}
class InstanceCounter
{
+35 -7
View File
@@ -53,7 +53,10 @@ class ScriptTokeniser final : public Tokeniser
bool m_unget;
bool m_emit;
bool m_special;
const bool m_special;
const bool m_specialComments;
const char m_specialCommentSig[4] = "@$&";
const char *m_specialCommentRead;
CharType charType( const char c ){
switch ( c )
@@ -144,7 +147,8 @@ class ScriptTokeniser final : public Tokeniser
m_emit = true; // emit token
break;
case eCharSolidus:
#if 0 //SPoG: ignore comments in the middle of tokens.
#define MID_TOKEN_COMMENTS 0
#if MID_TOKEN_COMMENTS //SPoG: ignore comments in the middle of tokens.
push( Tokenise( &ScriptTokeniser::tokeniseSolidus ) );
break;
#endif
@@ -200,7 +204,12 @@ class ScriptTokeniser final : public Tokeniser
break;
case eCharSolidus:
pop();
push( Tokenise( &ScriptTokeniser::tokeniseComment ) );
if( m_specialComments ){
push( Tokenise( &ScriptTokeniser::tokeniseCommentSpecialSig ) );
m_specialCommentRead = m_specialCommentSig;
}
else
push( Tokenise( &ScriptTokeniser::tokeniseComment ) );
break; // dont emit single slash
case eCharStar:
pop();
@@ -214,13 +223,25 @@ class ScriptTokeniser final : public Tokeniser
bool tokeniseComment( char c ){
if ( c == '\n' ) {
pop();
#if MID_TOKEN_COMMENTS
if ( state() == Tokenise( &ScriptTokeniser::tokeniseToken ) ) {
pop();
m_emit = true; // emit token immediately preceding comment
}
#endif
}
return true;
}
bool tokeniseCommentSpecialSig( char c ){ // test for '//@$&' signature
if( *m_specialCommentRead++ == c ){
if( *m_specialCommentRead == '\0' ) // match, do tokeniseDefault
pop(); // note no MID_TOKEN_COMMENTS support
return true;
}
pop();
push( Tokenise( &ScriptTokeniser::tokeniseComment ) );
return tokeniseComment( c );
}
bool tokeniseBlockComment( char c ){
if ( c == '*' ) {
pop();
@@ -233,10 +254,12 @@ class ScriptTokeniser final : public Tokeniser
{
case '/':
pop();
#if MID_TOKEN_COMMENTS
if ( state() == Tokenise( &ScriptTokeniser::tokeniseToken ) ) {
pop();
m_emit = true; // emit token immediately preceding comment
}
#endif
break; // dont emit comment
case '*':
break; // no state change
@@ -303,7 +326,7 @@ class ScriptTokeniser final : public Tokeniser
}
public:
ScriptTokeniser( TextInputStream& istream, bool special ) :
ScriptTokeniser( TextInputStream& istream, bool special, bool specialComments ) :
m_state( m_stack ),
m_istream( istream ),
m_scriptline( 1 ),
@@ -311,7 +334,8 @@ public:
m_crossline( false ),
m_unget( false ),
m_emit( false ),
m_special( special ){
m_special( special ),
m_specialComments( specialComments ){
m_stack[0] = Tokenise( &ScriptTokeniser::tokeniseDefault );
m_eof = !m_istream.readChar( m_current );
m_token[MAXTOKEN - 1] = '\0';
@@ -347,9 +371,13 @@ public:
inline Tokeniser& NewScriptTokeniser( TextInputStream& istream ){
return *( new ScriptTokeniser( istream, true ) );
return *( new ScriptTokeniser( istream, true, false ) );
}
inline Tokeniser& NewMapTokeniser( TextInputStream& istream ){
return *( new ScriptTokeniser( istream, false, true ) );
}
inline Tokeniser& NewSimpleTokeniser( TextInputStream& istream ){
return *( new ScriptTokeniser( istream, false ) );
return *( new ScriptTokeniser( istream, false, false ) );
}
+15
View File
@@ -287,6 +287,21 @@ inline bool Tokeniser_nextTokenIsDigit( Tokeniser& tokeniser ){
return std::isdigit( c ) != 0;
}
inline bool Tokeniser_inlineTokenAvailable( Tokeniser& tokeniser ){
const size_t line = tokeniser.getLine();
if ( tokeniser.getToken() ) {
tokeniser.ungetToken();
return line == tokeniser.getLine();
}
return false;
}
inline void Tokeniser_skipToNextLine( Tokeniser& tokeniser ){
const size_t line = tokeniser.getLine();
while ( tokeniser.getToken() && line == tokeniser.getLine() ) {}
tokeniser.ungetToken();
}
+2 -2
View File
@@ -634,9 +634,9 @@ class PicoModelNode : public scene::Node::Symbiot, public scene::Instantiable
public:
typedef LazyStatic<TypeCasts> StaticTypeCasts;
PicoModelNode() : m_node( this, this, StaticTypeCasts::instance().get() ){
PicoModelNode() : m_node( this, this, StaticTypeCasts::instance().get(), nullptr ){
}
PicoModelNode( const AssScene scene ) : m_node( this, this, StaticTypeCasts::instance().get() ), m_picomodel( scene ){
PicoModelNode( const AssScene scene ) : m_node( this, this, StaticTypeCasts::instance().get(), nullptr ), m_picomodel( scene ){
}
void release(){
+2 -2
View File
@@ -657,7 +657,7 @@ public:
}
Doom3GroupNode( EntityClass* eclass ) :
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), nullptr ),
m_contained( eclass, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSet::BoundsChangedCaller( m_instances ), InstanceSetEvaluateTransform<Doom3GroupInstance>::Caller( m_instances ) ){
construct();
}
@@ -666,7 +666,7 @@ public:
scene::Instantiable( other ),
scene::Cloneable( other ),
scene::Traversable::Observer( other ),
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), nullptr ),
m_contained( other.m_contained, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSet::BoundsChangedCaller( m_instances ), InstanceSetEvaluateTransform<Doom3GroupInstance>::Caller( m_instances ) ){
construct();
}
+2 -2
View File
@@ -437,7 +437,7 @@ public:
}
EclassModelNode( EntityClass* eclass ) :
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), GlobalSceneGraph().currentLayer() ),
m_contained( eclass, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSetEvaluateTransform<EclassModelInstance>::Caller( m_instances ) ){
construct();
}
@@ -446,7 +446,7 @@ public:
scene::Instantiable( other ),
scene::Cloneable( other ),
scene::Traversable::Observer( other ),
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), other.m_node.m_layer ),
m_contained( other.m_contained, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSetEvaluateTransform<EclassModelInstance>::Caller( m_instances ) ){
construct();
}
+2 -2
View File
@@ -388,14 +388,14 @@ public:
}
GenericEntityNode( EntityClass* eclass ) :
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), GlobalSceneGraph().currentLayer() ),
m_contained( eclass, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSetEvaluateTransform<GenericEntityInstance>::Caller( m_instances ) ){
}
GenericEntityNode( const GenericEntityNode& other ) :
scene::Node::Symbiot( other ),
scene::Instantiable( other ),
scene::Cloneable( other ),
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), other.m_node.m_layer ),
m_contained( other.m_contained, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSetEvaluateTransform<GenericEntityInstance>::Caller( m_instances ) ){
}
void release(){
+2 -2
View File
@@ -445,7 +445,7 @@ public:
}
GroupNode( EntityClass* eclass ) :
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), nullptr ),
m_contained( eclass, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSetEvaluateTransform<GroupInstance>::Caller( m_instances ) ){
construct();
}
@@ -454,7 +454,7 @@ public:
scene::Instantiable( other ),
scene::Cloneable( other ),
scene::Traversable::Observer( other ),
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), nullptr ),
m_contained( other.m_contained, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSetEvaluateTransform<GroupInstance>::Caller( m_instances ) ){
construct();
}
+2 -2
View File
@@ -2044,7 +2044,7 @@ public:
}
LightNode( EntityClass* eclass ) :
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), GlobalSceneGraph().currentLayer() ),
m_contained( eclass, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSet::BoundsChangedCaller( m_instances ), InstanceSetEvaluateTransform<LightInstance>::Caller( m_instances ) ){
construct();
}
@@ -2053,7 +2053,7 @@ public:
scene::Instantiable( other ),
scene::Cloneable( other ),
scene::Traversable::Observer( other ),
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), other.m_node.m_layer ),
m_contained( other.m_contained, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSet::BoundsChangedCaller( m_instances ), InstanceSetEvaluateTransform<LightInstance>::Caller( m_instances ) ){
construct();
}
+2 -2
View File
@@ -491,7 +491,7 @@ public:
}
MiscModelNode( EntityClass* eclass ) :
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), GlobalSceneGraph().currentLayer() ),
m_contained( eclass, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSetEvaluateTransform<MiscModelInstance>::Caller( m_instances ) ){
construct();
}
@@ -500,7 +500,7 @@ public:
scene::Instantiable( other ),
scene::Cloneable( other ),
scene::Traversable::Observer( other ),
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), other.m_node.m_layer ),
m_contained( other.m_contained, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSetEvaluateTransform<MiscModelInstance>::Caller( m_instances ) ){
construct();
}
+108 -5
View File
@@ -32,6 +32,98 @@
#include "string/string.h"
#include "stringio.h"
#include "eclasslib.h"
#include "layers.h"
class LayersParser
{
Layers& m_layers;
std::vector<Layer::iterator> m_layersVec; // vector to access layers by index
int m_currentLayer = LAYERIDX0;
public:
LayersParser( scene::Node& root ) : m_layers( *Node_getLayers( root ) ){
}
void construct_tree(){
{
auto& list = m_layers.m_children;
if( list.size() > 1 ) // something was parsed, remove default inserted layer
list.pop_front();
m_layersVec.reserve( list.size() );
for( auto it = list.begin(); it != list.end(); ++it )
m_layersVec.push_back( it );
// check safety // note possible circular reference is not checked
if( std::ranges::find( list, LAYERIDXPARENT, &Layer::m_parentIndex ) == list.cend() ){
globalErrorStream() << "No layers in the root\n";
list.front().m_parentIndex = LAYERIDXPARENT;
}
}
for( auto it : m_layersVec ){
// check safety
if( it->m_parentIndex < LAYERIDXPARENT || it->m_parentIndex >= static_cast<int>( m_layersVec.size() ) ){
globalErrorStream() << it->m_parentIndex << " parent layer index out of bounds\n";
it->m_parentIndex = LAYERIDXPARENT;
}
if( it->m_parentIndex > LAYERIDXPARENT && m_layersVec[ it->m_parentIndex ] == it ){
globalErrorStream() << it->m_parentIndex << " parent layer index self reference\n";
it->m_parentIndex = LAYERIDXPARENT;
}
// link & reference parent
Layer& parent = it->m_parentIndex == LAYERIDXPARENT? m_layers : *m_layersVec[ it->m_parentIndex ];
parent.m_children.splice( parent.m_children.cend(), m_layers.m_children, it );
it->m_parent = &parent;
}
m_layers.m_currentLayer = &m_layers.m_children.front();
}
bool read_layers( Tokeniser& tokeniser ){
for (;; )
{
tokeniser.nextLine();
const char *token;
if( !( token = tokeniser.getToken() ) )
return false;
if( !string_equal( token, "layerdef" ) ){
tokeniser.ungetToken();
return true;
}
if( !( token = tokeniser.getToken() ) ){
Tokeniser_unexpectedError( tokeniser, token, "#layer-name" );
return false;
}
auto& layer = m_layers.m_children.emplace_back( token, nullptr ); // initially construct plain list
if( !Tokeniser_getInteger( tokeniser, layer.m_parentIndex )
|| !Tokeniser_getInteger( tokeniser, layer.m_color[ 0 ] )
|| !Tokeniser_getInteger( tokeniser, layer.m_color[ 1 ] )
|| !Tokeniser_getInteger( tokeniser, layer.m_color[ 2 ] ) )
return false;
Tokeniser_skipToNextLine( tokeniser ); // skip possible format extension
}
}
bool read_layer( Tokeniser& tokeniser ){
const char *token = tokeniser.getToken();
if ( token == nullptr )
return false;
if( !string_equal( token, "layer" ) ){
tokeniser.ungetToken();
return true;
}
if( !Tokeniser_getInteger( tokeniser, m_currentLayer ) ){
return false;
}
else if( m_currentLayer <= LAYERIDXPARENT || m_currentLayer >= static_cast<int>( m_layersVec.size() ) ){
globalErrorStream() << m_currentLayer << " primitive layer index out of bounds\n";
m_currentLayer = LAYERIDX0;
}
Tokeniser_skipToNextLine( tokeniser ); // skip possible format extension
return true;
}
Layer* getCurrentLayer(){
return m_layersVec[ m_currentLayer ].operator->();
}
};
inline MapImporter* Node_getMapImporter( scene::Node& node ){
return NodeTypeCast<MapImporter>::cast( node );
@@ -52,7 +144,7 @@ NodeSmartReference Entity_create( EntityCreator& entityTable, EntityClass* entit
return NodeSmartReference( entity );
}
NodeSmartReference Entity_parseTokens( Tokeniser& tokeniser, EntityCreator& entityTable, const PrimitiveParser& parser, int index ){
NodeSmartReference Entity_parseTokens( Tokeniser& tokeniser, EntityCreator& entityTable, const PrimitiveParser& parser, int index, LayersParser& layersParser ){
NodeSmartReference entity( g_nullNode );
KeyValues keyValues;
const char* classname = "";
@@ -61,8 +153,8 @@ NodeSmartReference Entity_parseTokens( Tokeniser& tokeniser, EntityCreator& enti
while ( 1 )
{
tokeniser.nextLine();
const char* token = tokeniser.getToken();
if ( token == 0 ) {
const char* token;
if ( !layersParser.read_layer( tokeniser ) || !( token = tokeniser.getToken() ) ) {
Tokeniser_unexpectedError( tokeniser, token, "#entity-token" );
return g_nullNode;
}
@@ -86,6 +178,7 @@ NodeSmartReference Entity_parseTokens( Tokeniser& tokeniser, EntityCreator& enti
globalErrorStream() << "brush " << count_primitives << ": parse error\n";
return g_nullNode;
}
primitive.get().m_layer = layersParser.getCurrentLayer();
scene::Traversable* traversable = Node_getTraversable( entity );
if ( Node_getEntity( entity )->isContainer() && traversable != 0 ) {
@@ -116,15 +209,23 @@ NodeSmartReference Entity_parseTokens( Tokeniser& tokeniser, EntityCreator& enti
}
void Map_Read( scene::Node& root, Tokeniser& tokeniser, EntityCreator& entityTable, const PrimitiveParser& parser ){
LayersParser layersParser( root );
if( !layersParser.read_layers( tokeniser ) ){
layersParser.construct_tree(); // contruct anytime to have at least one layer, e.g. when empty .map
return;
}
layersParser.construct_tree();
int count_entities = 0;
for (;; )
{
tokeniser.nextLine();
if ( !tokeniser.getToken() ) { // { or 0
if ( !layersParser.read_layer( tokeniser ) || !tokeniser.getToken() ) { // { or 0
break;
}
Layer *entityLayer = layersParser.getCurrentLayer();
NodeSmartReference entity( Entity_parseTokens( tokeniser, entityTable, parser, count_entities ) );
NodeSmartReference entity( Entity_parseTokens( tokeniser, entityTable, parser, count_entities, layersParser ) );
if ( entity == g_nullNode ) {
globalErrorStream() << "entity " << count_entities << ": parse error\n";
@@ -132,6 +233,8 @@ void Map_Read( scene::Node& root, Tokeniser& tokeniser, EntityCreator& entityTab
}
Node_getTraversable( root )->insert( entity );
if( !Node_getEntity( entity )->isContainer() )
entity.get().m_layer = entityLayer;
++count_entities;
}
+6 -6
View File
@@ -101,7 +101,7 @@ public:
return g_nullNode;
}
void readGraph( scene::Node& root, TextInputStream& inputStream, EntityCreator& entityTable ) const {
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewSimpleTokeniser( inputStream );
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewMapTokeniser( inputStream );
tokeniser.nextLine();
if ( !Tokeniser_parseToken( tokeniser, "Version" ) ) {
return;
@@ -172,7 +172,7 @@ public:
return g_nullNode;
}
void readGraph( scene::Node& root, TextInputStream& inputStream, EntityCreator& entityTable ) const {
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewSimpleTokeniser( inputStream );
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewMapTokeniser( inputStream );
tokeniser.nextLine();
if ( !Tokeniser_parseToken( tokeniser, "Version" ) ) {
return;
@@ -291,7 +291,7 @@ public:
}
void readGraph( scene::Node& root, TextInputStream& inputStream, EntityCreator& entityTable ) const {
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewSimpleTokeniser( inputStream );
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewMapTokeniser( inputStream );
m_formatDetected = false;
Map_Read( root, tokeniser, entityTable, *this );
tokeniser.release();
@@ -368,7 +368,7 @@ public:
return g_nullNode;
}
void readGraph( scene::Node& root, TextInputStream& inputStream, EntityCreator& entityTable ) const {
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewSimpleTokeniser( inputStream );
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewMapTokeniser( inputStream );
m_formatDetected = false;
Map_Read( root, tokeniser, entityTable, *this );
tokeniser.release();
@@ -412,7 +412,7 @@ public:
return g_nullNode;
}
void readGraph( scene::Node& root, TextInputStream& inputStream, EntityCreator& entityTable ) const {
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewSimpleTokeniser( inputStream );
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewMapTokeniser( inputStream );
Map_Read( root, tokeniser, entityTable, *this );
tokeniser.release();
}
@@ -487,7 +487,7 @@ public:
return g_nullNode;
}
void readGraph( scene::Node& root, TextInputStream& inputStream, EntityCreator& entityTable ) const {
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewSimpleTokeniser( inputStream );
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewMapTokeniser( inputStream );
m_formatDetected = false;
Map_Read( root, tokeniser, entityTable, *this );
tokeniser.release();
+36 -3
View File
@@ -24,6 +24,31 @@
#include "ientity.h"
#include "iscriplib.h"
#include "scenelib.h"
#include "layers.h"
void Layers_Write( Layers& layers, TokenWriter& writer ){
layers.forEach( [&]( Layer& layer ){
writer.writeToken( "//@$&" );
writer.writeToken( "layerdef" );
writer.writeString( layer.m_name.c_str() );
writer.writeInteger( layer.m_parent->m_ownIndex );
writer.writeInteger( layer.m_color[ 0 ] );
writer.writeInteger( layer.m_color[ 1 ] );
writer.writeInteger( layer.m_color[ 2 ] );
writer.nextLine();
} );
}
void Layer_Write( const Layer *layer, int& currentLayer, TokenWriter& writer ){
if( layer != nullptr && currentLayer != layer->m_ownIndex ){
currentLayer = layer->m_ownIndex;
writer.writeToken( "//@$&" );
writer.writeToken( "layer" );
writer.writeInteger( currentLayer );
writer.nextLine();
}
}
inline MapExporter* Node_getMapExporter( scene::Node& node ){
return NodeTypeCast<MapExporter>::cast( node );
@@ -61,9 +86,13 @@ class WriteTokensWalker : public scene::Traversable::Walker
mutable Stack<bool> m_stack;
TokenWriter& m_writer;
bool m_ignorePatches;
mutable int m_currentLayer = LAYERIDX0;
public:
WriteTokensWalker( TokenWriter& writer, bool ignorePatches )
: m_writer( writer ), m_ignorePatches( ignorePatches ){
WriteTokensWalker( TokenWriter& writer, bool ignorePatches, Layers& layers )
: m_writer( writer ), m_ignorePatches( ignorePatches )
{
layers.update_ownIndices();
Layers_Write( layers, m_writer );
}
bool pre( scene::Node& node ) const {
m_stack.push( false );
@@ -80,6 +109,8 @@ public:
m_writer.writeUnsigned( g_count_entities++ );
m_writer.nextLine();
Layer_Write( node.m_layer, m_currentLayer, m_writer );
m_writer.writeToken( "{" );
m_writer.nextLine();
m_stack.top() = true;
@@ -96,6 +127,8 @@ public:
m_writer.writeUnsigned( g_count_brushes++ );
m_writer.nextLine();
Layer_Write( node.m_layer, m_currentLayer, m_writer );
exporter->exportTokens( m_writer );
}
}
@@ -113,5 +146,5 @@ public:
void Map_Write( scene::Node& root, GraphTraversalFunc traverse, TokenWriter& writer, bool ignorePatches ){
g_count_entities = 0;
traverse( root, WriteTokensWalker( writer, ignorePatches ) );
traverse( root, WriteTokensWalker( writer, ignorePatches, *Node_getLayers( root ) ) );
}
+1 -1
View File
@@ -456,7 +456,7 @@ public:
typedef LazyStatic<TypeCasts> StaticTypeCasts;
ModelNode() : m_node( this, this, StaticTypeCasts::instance().get() ){
ModelNode() : m_node( this, this, StaticTypeCasts::instance().get(), nullptr ){
}
Model& model(){
+2 -2
View File
@@ -585,9 +585,9 @@ class PicoModelNode : public scene::Node::Symbiot, public scene::Instantiable
public:
typedef LazyStatic<TypeCasts> StaticTypeCasts;
PicoModelNode() : m_node( this, this, StaticTypeCasts::instance().get() ){
PicoModelNode() : m_node( this, this, StaticTypeCasts::instance().get(), nullptr ){
}
PicoModelNode( picoModel_t* model ) : m_node( this, this, StaticTypeCasts::instance().get() ), m_picomodel( model ){
PicoModelNode( picoModel_t* model ) : m_node( this, this, StaticTypeCasts::instance().get(), nullptr ), m_picomodel( model ){
}
void release(){
+2 -2
View File
@@ -95,7 +95,7 @@ public:
}
BrushNode() :
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), GlobalSceneGraph().currentLayer() ),
m_brush( m_node, InstanceSetEvaluateTransform<BrushInstance>::Caller( m_instances ), InstanceSet::BoundsChangedCaller( m_instances ) ),
m_mapImporter( m_brush ),
m_mapExporter( m_brush ),
@@ -106,7 +106,7 @@ public:
scene::Node::Symbiot( other ),
scene::Instantiable( other ),
scene::Cloneable( other ),
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), other.m_node.m_layer ),
m_brush( other.m_brush, m_node, InstanceSetEvaluateTransform<BrushInstance>::Caller( m_instances ), InstanceSet::BoundsChangedCaller( m_instances ) ),
m_mapImporter( m_brush ),
m_mapExporter( m_brush ),
+5 -5
View File
@@ -354,7 +354,7 @@ public:
for( Brush* b : out ) {
b->removeEmptyFaces();
if( b->hasContributingFaces() ) {
NodeSmartReference node( ( new BrushNode() )->node() );
NodeSmartReference node( GlobalBrushCreator().createBrush() );
Node_getBrush( node )->copy( *b );
Node_getTraversable( path.parent() )->insert( node );
//path.push( makeReference( node.get() ) );
@@ -690,7 +690,7 @@ public:
++m_after;
brush->removeEmptyFaces();
if ( !brush->empty() ) {
NodeSmartReference node( ( new BrushNode() )->node() );
NodeSmartReference node( GlobalBrushCreator().createBrush() );
Node_getBrush( node )->copy( *brush );
delete brush;
Node_getTraversable( path.parent() )->insert( node );
@@ -766,7 +766,7 @@ public:
// the plane intersects this brush
m_gj = true;
if ( m_split ) {
NodeSmartReference node( ( new BrushNode() )->node() );
NodeSmartReference node( GlobalBrushCreator().createBrush() );
Brush* fragment = Node_getBrush( node );
fragment->copy( *brush );
fragment->addPlane( m_points[0], m_points[2], m_points[1], m_shader, m_projection );
@@ -960,7 +960,7 @@ void CSG_Merge(){
UndoableCommand undo( "brushMerge" );
NodeSmartReference node( ( new BrushNode() )->node() );
NodeSmartReference node( GlobalBrushCreator().createBrush() );
Brush* brush = Node_getBrush( node );
// if the new brush would not be convex
if ( !Brush_merge( *brush, selected_brushes, true ) ) {
@@ -1166,7 +1166,7 @@ void CSG_WrapMerge( const ClipperPoints& clipperPoints ){
return;
}
NodeSmartReference node( ( new BrushNode() )->node() );
NodeSmartReference node( GlobalBrushCreator().createBrush() );
Brush* brush = GlobalSelectionSystem().countSelected() > 0? Node_getBrush( GlobalSelectionSystem().ultimateSelected().path().top() ) : 0;
const bool oldbrush = brush && primit;
if( oldbrush )
+537
View File
@@ -0,0 +1,537 @@
/*
Copyright (C) 1999-2006 Id Software, Inc. and contributors.
For a list of contributors, see the accompanying CONTRIBUTORS file.
This file is part of GtkRadiant.
GtkRadiant 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.
GtkRadiant 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 GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "layerswindow.h"
#include "layers.h"
#include <QApplication>
#include <QTreeWidget>
#include <QHeaderView>
#include <QDropEvent>
#include <QMimeData>
#include <QIcon>
#include <QMenu>
#include <QInputDialog>
#include <QTimer>
#include "map.h"
#include "ientity.h"
#include "generic/callback.h"
#include "signal/signal.h"
#include "gtkutil/image.h"
#include "gtkmisc.h"
#include "windowobservers.h"
/*
?todo operator[] range check // checked during loading
layer color
restrict " in the name, empty name
'move to layer' context menu on keybind
? tree 'move to layer' menu
def line format extensible
? extensibility with new root keywords
handle paste, clone, map import
new layer = make current
hide recursively
always nullptr layer for group ents node
hide/show group entity nodes automatically
don't select hidden nodes
non locked 0 layer //may be confusing for user when falling back to default layer in operations, it's implicitly 1st in the list//?write 0 layer for future
? feedback which layers are selected //highlight tree items
? feedback items count in a layer
? undo
button to add a layer
buttons to hide/show all layers
*/
class LayerAssignVisitor : public SelectionSystem::Visitor
{
Layer *m_layer;
public:
LayerAssignVisitor( Layer *layer ) : m_layer( layer ){
}
void visit( scene::Instance& instance ) const override {
scene::Node& node = instance.path().top().get();
if( Entity *entity; !( ( entity = Node_getEntity( node ) ) && entity->isContainer() ) )
node.m_layer = m_layer;
}
};
class LayerCollectVisitor : public SelectionSystem::Visitor
{
std::vector<Layer*>& m_layers;
public:
LayerCollectVisitor( std::vector<Layer*>& layers ) : m_layers( layers ){
}
void visit( scene::Instance& instance ) const override {
Layer *layer = instance.path().top().get().m_layer;
if( layer != nullptr && std::ranges::find( m_layers, layer ) == m_layers.cend() )
m_layers.push_back( layer );
}
};
class LayerHideWalker : public scene::Graph::Walker
{
const std::vector<Layer*>& m_layers;
public:
LayerHideWalker( const std::vector<Layer*>& layers ) : m_layers( layers ){
}
bool pre( const scene::Path& path, scene::Instance& instance ) const override {
scene::Node& node = path.top().get();
if( std::ranges::find( m_layers, node.m_layer ) != m_layers.cend() ){
node.enable( scene::Node::eLayerHidden );
if( Selectable* selectable = Instance_getSelectable( instance ) )
selectable->setSelected( false );
}
return true;
}
void post( const scene::Path& path, scene::Instance& instance ) const override {
scene::Node& node = path.top().get();
if( Node_isEntity( node ) ) // hide group entity labels, when their content is entirely hidden
if( scene::Traversable* traversable = Node_getTraversable( node ) )
if( Traversable_all_of_children( traversable, []( const scene::Node& node ){ return node.excluded( scene::Node::eLayerHidden ); } ) )
node.enable( scene::Node::eLayerHidden );
}
};
class LayerShowWalker : public scene::Graph::Walker
{
const std::vector<Layer*>& m_layers;
public:
LayerShowWalker( const std::vector<Layer*>& layers ) : m_layers( layers ){
}
bool pre( const scene::Path& path, scene::Instance& instance ) const override {
scene::Node& node = path.top().get();
if( std::ranges::find( m_layers, node.m_layer ) != m_layers.cend() ){
node.disable( scene::Node::eLayerHidden );
}
return true;
}
void post( const scene::Path& path, scene::Instance& instance ) const override {
scene::Node& node = path.top().get();
if( Node_isEntity( node ) ) // unhide group entity labels, when their content is partially visible
if( scene::Traversable* traversable = Node_getTraversable( node ) )
if( Traversable_any_of_children( traversable, []( const scene::Node& node ){ return !node.excluded( scene::Node::eLayerHidden ); } ) )
node.disable( scene::Node::eLayerHidden );
}
};
class LayerSelectWalker : public scene::Graph::Walker
{
const std::vector<Layer*>& m_layers;
const bool m_select;
public:
LayerSelectWalker( const std::vector<Layer*>& layers, bool select ) : m_layers( layers ), m_select( select ){
}
bool pre( const scene::Path& path, scene::Instance& instance ) const override {
scene::Node& node = path.top().get();
if( node.visible() && std::ranges::find( m_layers, node.m_layer ) != m_layers.cend() ){
Instance_setSelected( instance, m_select );
}
return true;
}
};
class LayerDeletetWalker : public scene::Graph::Walker
{
const std::vector<Layer*>& m_layers;
Layer *m_currentLayer;
public:
LayerDeletetWalker( const std::vector<Layer*>& layers, Layer *currentLayer ) : m_layers( layers ), m_currentLayer( currentLayer ){
}
bool pre( const scene::Path& path, scene::Instance& instance ) const override {
scene::Node& node = path.top().get();
if( std::ranges::find( m_layers, node.m_layer ) != m_layers.cend() ){
node.m_layer = m_currentLayer;
}
return true;
}
};
class LayersBrowser
{
public:
QTreeWidget *m_tree = nullptr;
QMenu *m_movetoMenu = nullptr;
QIcon m_iconSelect;
QIcon m_iconSelectAdd;
QIcon m_iconSelectDeselect;
QIcon m_iconSelectMove;
QIcon m_iconVisibleOn;
QIcon m_iconVisibleOff;
};
LayersBrowser g_lbro;
namespace Column
{
enum
{
name,
current,
select,
visible,
};
};
template<class Functor>
requires( std::invocable<Functor, QTreeWidgetItem*> && std::is_void_v<std::invoke_result_t<Functor, QTreeWidgetItem*>> )
void items_iterate_recursively( QTreeWidgetItem *item, Functor&& functor ){
functor( item );
for( int i = 0; i < item->childCount(); ++i )
items_iterate_recursively( item->child( i ), functor );
}
Layer::iterator item_getLayerIterator( QTreeWidgetItem *item ){
static_assert( sizeof( Layer::iterator ) == sizeof( size_t ) );
size_t layer = item->data( Column::name, Qt::ItemDataRole::UserRole ).value<size_t>();
return *reinterpret_cast<Layer::iterator*>( &layer );
}
Layer* item_getLayer( QTreeWidgetItem *item ){
return item_getLayerIterator( item ).operator->();
}
std::vector<Layer*> item_getLayers( QTreeWidgetItem *item ){
std::vector<Layer*> layers;
items_iterate_recursively( item, [&]( QTreeWidgetItem *item ){ layers.push_back( item_getLayer( item ) ); } );
return layers;
}
std::vector<Layer::iterator> item_getLayersIterators( QTreeWidgetItem *item ){
std::vector<Layer::iterator> layers;
items_iterate_recursively( item, [&]( QTreeWidgetItem *item ){ layers.push_back( item_getLayerIterator( item ) ); } );
return layers;
}
void item_setCurrent( QTreeWidgetItem *item ){
for( QTreeWidgetItemIterator it( item->treeWidget() ); *it; ++it )
( *it )->setCheckState( Column::current, Qt::CheckState::Unchecked );
item->setCheckState( Column::current, Qt::CheckState::Checked );
Node_getLayers( GlobalSceneGraph().root() )->m_currentLayer = item_getLayer( item );
}
void item_setIcons( QTreeWidgetItem *item ){
item->setIcon( Column::select, g_lbro.m_iconSelect );
item->setIcon( Column::visible, item->data( Column::visible, Qt::ItemDataRole::UserRole ).toBool()? g_lbro.m_iconVisibleOn : g_lbro.m_iconVisibleOff );
}
void item_setColor( QTreeWidgetItem *item, const int ( &color )[ 3 ] ){
if( color[ 0 ] == 0 && color[ 1 ] == 0 && color[ 2 ] == 0 )
item->setBackground( Column::current, {} );
else
item->setBackground( Column::current, QColor( color[ 0 ], color[ 1 ], color[ 2 ] ) );
}
void item_construct( QTreeWidgetItem *item, Layer::iterator layer ){
item->setText( Column::name, layer->m_name.c_str() );
item->setData( Column::name, Qt::ItemDataRole::UserRole, QVariant::fromValue( *reinterpret_cast<size_t*>( &layer ) ) ); // Q_DECLARE_METATYPE( Layer* ) = crash during drag and drop
item->setData( Column::visible, Qt::ItemDataRole::UserRole, true ); // save toggle state
item->setToolTip( Column::current, "Set current layer" );
item->setToolTip( Column::select, "CLICK = Select\nCTRL = multiple\nALT = deselect\nSHIFT = Move to layer" );
item->setToolTip( Column::visible, "Toggle visibility" );
item_setIcons( item );
item_setColor( item, layer->m_color );
item->setCheckState( Column::current, Qt::CheckState::Unchecked ); // make checkable
}
void insert_children( QTreeWidgetItem *parentItem, std::list<Layer>& children ){
for( auto layer = children.begin(); layer != children.end(); ++layer )
{
auto *item = new QTreeWidgetItem( parentItem );
item_construct( item, layer );
insert_children( item, layer->m_children );
}
}
void LayersBrowser_constructTree( QTreeWidget *tree ){
if( Map_Valid( g_map ) ){
Layers& layers = *Node_getLayers( GlobalSceneGraph().root() );
tree->clear();
insert_children( tree->invisibleRootItem(), layers.m_children );
tree->topLevelItem( LAYERIDX0 )->setCheckState( Column::current, Qt::CheckState::Checked );
tree->expandAll();
}
}
class QTreeWidget_layers : public QTreeWidget, public WindowObserver
{
protected:
bool m_drop = false;
void rowsInserted( const QModelIndex& parent, int start, int end ) override {
if( std::exchange( m_drop, false ) ){ // insertion source is drop
QTreeWidgetItem *parentItem = parent.isValid()? this->itemFromIndex( parent ) : this->invisibleRootItem();
QTreeWidgetItem *item = parentItem->child( start );
Layer *parentLayer = parent.isValid()? item_getLayer( parentItem ) : Node_getLayers( GlobalSceneGraph().root() );
Layer::iterator layer = item_getLayerIterator( item );
auto& srcList = layer->m_parent->m_children;
// move to the end of list 1st, so that resulting 'start' index can be used correctly, when moving inside a list farther
srcList.splice( srcList.cend(), srcList, layer );
parentLayer->m_children.splice( std::next( parentLayer->m_children.cbegin(), start ), srcList, layer );
layer->m_parent = parentLayer;
}
QTreeWidget::rowsInserted( parent, start, end );
expandAll();
}
// bool positionAboveItem0( const QPoint& pos ) const {
// const QModelIndex index = indexAt( pos );
// return index.row() == LAYERIDX0
// && !index.parent().isValid() // in the root
// && pos.y() < visualRect( index ).center().y();
// }
void dragMoveEvent( QDragMoveEvent* event ) override {
// if( positionAboveItem0( event->pos() ) ){
// event->ignore();
// return;
// }
QTreeWidget::dragMoveEvent( event );
}
void dropEvent( QDropEvent* event ) override {
// if( positionAboveItem0( event->pos() ) ){
// event->ignore();
// return;
// }
ASSERT_MESSAGE( !m_drop, "dropEvent() without rowsInserted()" );
m_drop = true;
QTreeWidget::dropEvent( event );
}
private:
ModifierFlags m_modifiers;
void set_select_icons(){
if( this->isVisible() ){
QIcon& icon = m_modifiers == c_modifierControl? g_lbro.m_iconSelectAdd
: m_modifiers == c_modifierAlt? g_lbro.m_iconSelectDeselect
: m_modifiers == c_modifierShift? g_lbro.m_iconSelectMove
: g_lbro.m_iconSelect;
for( QTreeWidgetItemIterator it( this ); *it; ++it )
( *it )->setIcon( Column::select, icon );
}
}
public:
void release() override {};
void onSizeChanged( int width, int height ) override {};
void onMouseDown( const WindowVector& position, ButtonIdentifier button, ModifierFlags modifiers ) override {};
void onMouseUp( const WindowVector& position, ButtonIdentifier button, ModifierFlags modifiers ) override {};
void onMouseMotion( const WindowVector& position, ModifierFlags modifiers ) override {};
void onModifierDown( ModifierFlags modifier ) override {
m_modifiers = bitfield_enable( m_modifiers, modifier );
set_select_icons();
}
void onModifierUp( ModifierFlags modifier ) override {
m_modifiers = bitfield_disable( m_modifiers, modifier );
set_select_icons();
}
};
void context_menu( const QPoint& pos ){
auto *menu = new QMenu( g_lbro.m_tree );
menu->setAttribute( Qt::WA_DeleteOnClose );
QTreeWidgetItem *item = g_lbro.m_tree->itemAt( pos );
Layer *layer = item? item_getLayer( item ) : nullptr;
Layers& layers = *Node_getLayers( GlobalSceneGraph().root() );
menu->addAction( "Move Selection to This Layer", [&](){
GlobalSelectionSystem().foreachSelected( LayerAssignVisitor( layer ) );
} )->setDisabled( item == nullptr );
menu->addAction( g_lbro.m_iconSelectAdd, "New Layer", [&](){
bool ok;
QString text = QInputDialog::getText( g_lbro.m_tree, "Layer Name", "Enter layer name:", QLineEdit::EchoMode::Normal,
"UnnamedpLayer", &ok );
text.remove( '"' ); // remove token bounds symbol
if( ok && !text.isEmpty() ){
QTreeWidgetItem *parentItem = item? item : g_lbro.m_tree->invisibleRootItem();
Layer *parentLayer = layer? layer : &layers;
parentLayer->m_children.emplace_back( text.toLatin1().constData(), parentLayer );
auto *newItem = new QTreeWidgetItem( parentItem );
item_construct( newItem, --parentLayer->m_children.end() );
item_setCurrent( newItem );
}
} );
int layersCount = 0;
layers.forEach( [&]( Layer& ){ ++layersCount; } );
menu->addAction( QApplication::style()->standardIcon( QStyle::SP_DialogCloseButton ), "Delete", [&](){
auto dels = item_getLayers( item );
auto it = item_getLayerIterator( item );
GlobalSceneGraph().traverse( LayerDeletetWalker( dels, layers.m_currentLayer ) );
it->m_parent->m_children.erase( it );
delete item;
if( std::ranges::find( dels, layers.m_currentLayer ) != dels.cend() ){
item_setCurrent( g_lbro.m_tree->topLevelItem( LAYERIDX0 ) );
}
} )->setDisabled( item == nullptr || layersCount < 2 );
menu->addAction( "Rename", [&](){
bool ok;
QString text = QInputDialog::getText( g_lbro.m_tree, "Layer Name", "Enter layer name:", QLineEdit::EchoMode::Normal,
item->text( Column::name ), &ok );
text.remove( '"' ); // remove token bounds symbol
if( ok && !text.isEmpty() ){
item->setText( Column::name, text );
layer->m_name = text.toLatin1().constData();
}
} )->setDisabled( item == nullptr );
menu->addAction( "Color", [&](){
Vector3 color( layer->m_color[ 0 ] / 255.f, layer->m_color[ 1 ] / 255.f, layer->m_color[ 2 ] / 255.f );
if( color_dialog( g_lbro.m_tree, color ) ){
layer->m_color[ 0 ] = color[ 0 ] * 255;
layer->m_color[ 1 ] = color[ 1 ] * 255;
layer->m_color[ 2 ] = color[ 2 ] * 255;
item_setColor( item, layer->m_color );
}
} )->setDisabled( item == nullptr );
menu->exec( g_lbro.m_tree->mapToGlobal( pos ) );
}
void context_moveto_menu(){
if( !g_lbro.m_movetoMenu->isVisible() ){
g_lbro.m_movetoMenu->clear();
Node_getLayers( GlobalSceneGraph().root() )->forEach( []( Layer& layer ){
g_lbro.m_movetoMenu->addAction( QString( "-> " ) + layer.m_name.c_str(), [layer = &layer](){
GlobalSelectionSystem().foreachSelected( LayerAssignVisitor( layer ) );
} )->setDisabled( GlobalSelectionSystem().countSelected() == 0 );
} );
g_lbro.m_movetoMenu->exec( QCursor::pos() );
}
}
void itemClicked( QTreeWidgetItem *item, int column ){
if( item != nullptr ){
if( column == Column::select ){
const Qt::KeyboardModifiers kb = QApplication::keyboardModifiers();
if( kb == Qt::KeyboardModifier::NoModifier || kb == Qt::KeyboardModifier::ControlModifier || kb == Qt::KeyboardModifier::AltModifier ){
if( kb != Qt::KeyboardModifier::ControlModifier && kb != Qt::KeyboardModifier::AltModifier)
GlobalSelectionSystem().setSelectedAll( false );
GlobalSceneGraph().traverse( LayerSelectWalker( item_getLayers( item ), kb != Qt::KeyboardModifier::AltModifier ) );
}
else if( kb == Qt::KeyboardModifier::ShiftModifier ){
GlobalSelectionSystem().foreachSelected( LayerAssignVisitor( item_getLayer( item ) ) );
}
}
else if( column == Column::visible ){
const bool visible = item->data( Column::visible, Qt::ItemDataRole::UserRole ).toBool();
items_iterate_recursively( item, [&]( QTreeWidgetItem *item ){
item->setData( Column::visible, Qt::ItemDataRole::UserRole, !visible );
item_setIcons( item );
} );
if( visible ){
GlobalSceneGraph().traverse( LayerHideWalker( item_getLayers( item ) ) );
/* not hiding worldspawn node so that newly created brushes are visible */
if( scene::Node* w = Map_FindWorldspawn( g_map ) )
w->disable( scene::Node::eLayerHidden );
if( GlobalSelectionSystem().countSelectedComponents() != 0 )
GlobalSelectionSystem().setSelectedAllComponents( false );
}
else{
GlobalSceneGraph().traverse( LayerShowWalker( item_getLayers( item ) ) );
}
SceneChangeNotify();
}
else if( column == Column::current ){
item_setCurrent( item );
}
}
}
class QWidget* LayersBrowser_constructWindow( QWidget* toplevel ){
auto *tree = new QTreeWidget_layers;
g_lbro.m_tree = tree;
tree->setColumnCount( 4 );
tree->setUniformRowHeights( true ); // optimization
tree->setHorizontalScrollBarPolicy( Qt::ScrollBarPolicy::ScrollBarAlwaysOff );
tree->header()->setStretchLastSection( false ); // non greedy column sizing //makes it greedy for name section here
tree->header()->setSectionResizeMode( QHeaderView::ResizeMode::ResizeToContents );
tree->header()->setSectionResizeMode( Column::name, QHeaderView::ResizeMode::Stretch );
tree->setHeaderHidden( true );
tree->setAutoScroll( true );
tree->setAlternatingRowColors( true );
const int iconSize = tree->style()->pixelMetric( QStyle::PixelMetric::PM_SmallIconSize, nullptr, tree ) * 1.3;
tree->setIconSize( QSize( iconSize, iconSize) );
// tree->setStyleSheet( "QTreeWidget::item { border-bottom: 1px solid gray; }" );
tree->setDragDropMode( QAbstractItemView::DragDropMode::InternalMove );
tree->setContextMenuPolicy( Qt::ContextMenuPolicy::CustomContextMenu );
QObject::connect( tree, &QTreeWidget::customContextMenuRequested, context_menu );
tree->header()->swapSections( Column::current, Column::name ); // display current indicator 1st, tree second
QObject::connect( tree, &QTreeWidget::itemClicked, itemClicked );
QObject::connect( tree, &QTreeWidget::itemDoubleClicked, itemClicked ); // workaround fast clicks being not registered
// Connect to itemCollapsed signal to prevent collapsing
QObject::connect( tree, &QTreeWidget::itemCollapsed, []( QTreeWidgetItem *item ){ item->setExpanded( true ); });
g_lbro.m_movetoMenu = new QMenu( tree );
g_lbro.m_iconSelect = new_local_icon( "check_mark.png" );
g_lbro.m_iconSelectAdd = new_local_icon( "plus.png" );
g_lbro.m_iconSelectDeselect = new_local_icon( "minus.png" );
g_lbro.m_iconSelectMove = new_local_icon( "arrow_right.png" );
g_lbro.m_iconVisibleOn = new_local_icon( "eye_opened.png" );
g_lbro.m_iconVisibleOff = new_local_icon( "eye_closed.png" );
GlobalWindowObservers_add( tree ); // track modifiers for Column::select icons toggle
Map_addValidCallback( g_map, PointerCaller<QTreeWidget, void(), LayersBrowser_constructTree>( g_lbro.m_tree ) );
return g_lbro.m_tree;
}
void LayersBrowser_destroyWindow(){
g_lbro.m_tree = nullptr;
}
void Scene_ExpandSelectionToLayers(){
std::vector<Layer*> layers;
GlobalSelectionSystem().foreachSelected( LayerCollectVisitor( layers ) );
for( size_t i = 0, end = layers.size(); i < end; ++i ){ // vector may be changed during loop
layers[ i ]->forEach( [&]( Layer& layer ){
if( std::ranges::find( layers, &layer ) == layers.cend() )
layers.push_back( &layer );
} );
}
GlobalSceneGraph().traverse( LayerSelectWalker( layers, true ) );
}
#include "commands.h"
void Layers_registerCommands(){
GlobalCommands_insert( "ExpandSelectionToLayers", makeCallbackF( Scene_ExpandSelectionToLayers ), QKeySequence( "Ctrl+Q" ) );
GlobalCommands_insert( "LayersMenu", makeCallbackF( context_moveto_menu ), QKeySequence( "Shift+Q" ) );
}
void Layers_registerShortcuts(){
command_connect_accelerator( "LayersMenu" );
}
+28
View File
@@ -0,0 +1,28 @@
/*
Copyright (C) 1999-2006 Id Software, Inc. and contributors.
For a list of contributors, see the accompanying CONTRIBUTORS file.
This file is part of GtkRadiant.
GtkRadiant 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.
GtkRadiant 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 GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
class QWidget* LayersBrowser_constructWindow( QWidget* toplevel );
void LayersBrowser_destroyWindow();
void Layers_registerCommands();
void Layers_registerShortcuts();
+20 -2
View File
@@ -114,6 +114,7 @@
#include "textures.h"
#include "texwindow.h"
#include "modelwindow.h"
#include "layerswindow.h"
#include "url.h"
#include "xywindow.h"
#include "windowobservers.h"
@@ -727,6 +728,12 @@ void ModelBrowser_ToggleShow(){
GroupDialog_showPage( g_page_models );
}
QWidget* g_page_layers;
void LayersBrowser_ToggleShow(){
GroupDialog_showPage( g_page_layers);
}
static class EverySecondTimer
{
@@ -924,6 +931,7 @@ void create_edit_menu( QMenuBar *menubar ){
create_menu_item_with_mnemonic( menu, "Select Textured", "SelectTextured" );
create_menu_item_with_mnemonic( menu, "&Expand Selection To Primitives", "ExpandSelectionToPrimitives" );
create_menu_item_with_mnemonic( menu, "&Expand Selection To Entities", "ExpandSelectionToEntities" );
create_menu_item_with_mnemonic( menu, "&Expand Selection To Layers", "ExpandSelectionToLayers" );
create_menu_item_with_mnemonic( menu, "Select Connected Entities", "SelectConnectedEntities" );
menu->addSeparator();
@@ -951,8 +959,9 @@ void create_view_menu( QMenuBar *menubar, MainFrame::EViewStyle style ){
}
create_menu_item_with_mnemonic( menu, "Model Browser", "ToggleModelBrowser" );
create_menu_item_with_mnemonic( menu, "Entity Inspector", "ToggleEntityInspector" );
create_menu_item_with_mnemonic( menu, "Layers Browser", "ToggleLayersBrowser" );
create_menu_item_with_mnemonic( menu, "&Surface Inspector", "SurfaceInspector" );
create_menu_item_with_mnemonic( menu, "Entity List", "EntityList" );
create_menu_item_with_mnemonic( menu, "Entity List", "ToggleEntityList" );
menu->addSeparator();
{
@@ -1322,6 +1331,7 @@ void register_shortcuts(){
TexBro_registerShortcuts();
Misc_registerShortcuts();
Entity_registerShortcuts();
Layers_registerShortcuts();
}
void File_constructToolbar( QToolBar* toolbar ){
@@ -1693,6 +1703,8 @@ void MainFrame::Create(){
g_page_models = GroupDialog_addPage( "Models", ModelBrowser_constructWindow( GroupDialog_getWindow() ), RawStringExportCaller( "Models" ) );
g_page_layers = GroupDialog_addPage( "Layers", LayersBrowser_constructWindow( GroupDialog_getWindow() ), RawStringExportCaller( "Layers" ) );
window->show();
if ( CurrentStyle() == eRegular || CurrentStyle() == eRegularLeft ) {
@@ -1841,6 +1853,9 @@ void MainFrame::Create(){
m_hSplit->setStretchFactor( 1, 2222 );
m_hSplit->setStretchFactor( 2, 0 );
}
else{ // floating group dialog
GlobalWindowObservers_connectTopLevel( GroupDialog_getWindow() ); // for layers browser icons toggle
}
EntityList_constructWindow( window );
PreferencesDialog_constructWindow( window );
@@ -1888,6 +1903,7 @@ void MainFrame::Shutdown(){
delete std::exchange( m_pXZWnd, nullptr );
ModelBrowser_destroyWindow();
LayersBrowser_destroyWindow();
TextureBrowser_destroyWindow();
DeleteCamWnd( m_pCamWnd );
@@ -2055,9 +2071,11 @@ void MainFrame_Construct(){
GlobalCommands_insert( "ToggleConsole", makeCallbackF( Console_ToggleShow ), QKeySequence( "O" ) );
GlobalCommands_insert( "ToggleEntityInspector", makeCallbackF( EntityInspector_ToggleShow ), QKeySequence( "N" ) );
GlobalCommands_insert( "ToggleModelBrowser", makeCallbackF( ModelBrowser_ToggleShow ), QKeySequence( "/" ) );
GlobalCommands_insert( "EntityList", makeCallbackF( EntityList_toggleShown ), QKeySequence( "L" ) );
GlobalCommands_insert( "ToggleLayersBrowser", makeCallbackF( LayersBrowser_ToggleShow ), QKeySequence( "L" ) );
GlobalCommands_insert( "ToggleEntityList", makeCallbackF( EntityList_toggleShown ), QKeySequence( "Shift+L" ) );
Select_registerCommands();
Layers_registerCommands();
Tools_registerCommands();
+55 -1
View File
@@ -564,6 +564,46 @@ scene::Node& Map_FindOrInsertWorldspawn( Map& map ){
}
// assigns layers to ones from the other layers set of the same size
class LayersMergeWalker : public scene::Traversable::Walker
{
std::vector<Layer *> m_layersVec;
public:
LayersMergeWalker( Layers *currentLayers ){
currentLayers->forEach( [this]( Layer& layer ){ m_layersVec.push_back( &layer ); } );
}
bool pre( scene::Node& node ) const {
if( node.m_layer != nullptr )
node.m_layer = m_layersVec[ node.m_layer->m_ownIndex ];
return true;
}
};
class LayersAssignWalker : public scene::Traversable::Walker
{
Layer *m_current;
public:
LayersAssignWalker( Layer *current ) : m_current( current ){
}
bool pre( scene::Node& node ) const {
if( Entity *entity; !( ( entity = Node_getEntity( node ) ) && entity->isContainer() ) )
node.m_layer = m_current;
return true;
}
};
void Map_mergeLayers( scene::Node& newRoot, Layers *newLayers ){
Layers *currLayers = Node_getLayers( GlobalSceneGraph().root() );
newLayers->update_ownIndices();
currLayers->update_ownIndices();
if( newLayers->m_children == currLayers->m_children ){ // merge, likely same map copypaste or just one layer in total
Node_getTraversable( newRoot )->traverse( LayersMergeWalker( currLayers ) );
}
else{ // set current
Node_getTraversable( newRoot )->traverse( LayersAssignWalker( GlobalSceneGraph().currentLayer() ) );
}
}
class MapMergeAll : public scene::Traversable::Walker
{
mutable scene::Path m_path;
@@ -631,6 +671,7 @@ class BasicContainer : public scene::Node::Symbiot
public:
TypeCasts(){
NodeContainedCast<BasicContainer, scene::Traversable>::install( m_casts );
NodeContainedCast<BasicContainer, Layers>::install( m_casts );
}
NodeTypeCastTable& get(){
return m_casts;
@@ -639,6 +680,7 @@ class BasicContainer : public scene::Node::Symbiot
scene::Node m_node;
TraversableNodeSet m_traverse;
Layers m_layers;
public:
typedef LazyStatic<TypeCasts> StaticTypeCasts;
@@ -646,8 +688,11 @@ public:
scene::Traversable& get( NullType<scene::Traversable>){
return m_traverse;
}
Layers& get( NullType<Layers>){
return m_layers;
}
BasicContainer() : m_node( this, this, StaticTypeCasts::instance().get() ){
BasicContainer() : m_node( this, this, StaticTypeCasts::instance().get(), nullptr ){
}
void release(){
delete this;
@@ -703,6 +748,7 @@ void Map_ImportSelected( TextInputStream& in, const MapFormat& format ){
Node_getTraversable( node )->traverse( Convert_Brushes( BrushType_getTexdefType( GlobalBrushCreator().getFormat() ), BrushType_getTexdefType( brush_type ) ) );
GlobalBrushCreator().toggleFormat( brush_type );
}
Map_mergeLayers( node, Node_getLayers( node ) );
Map_gatherNamespaced( node );
Map_mergeClonedNames();
MergeMap( node );
@@ -1167,11 +1213,17 @@ void Map_Traverse_Region( scene::Node& root, const scene::Traversable::Walker& w
}
inline void swap_layers( Layers *from, Layers *to ){
std::swap( from->m_children, to->m_children );
to->forEach( [&]( Layer& layer ){ if( layer.m_parent == from ) layer.m_parent = to; } );
}
void Map_RenameAbsolute( const char* absolute ){
Resource* resource = GlobalReferenceCache().capture( absolute );
NodeSmartReference clone( NewMapRoot( path_make_relative( absolute, GlobalFileSystem().findRoot( absolute ) ) ) );
resource->setNode( clone.get_pointer() );
swap_layers( Node_getLayers( GlobalSceneGraph().root() ), Node_getLayers( clone ) );
{
//ScopeTimer timer( "clone subgraph" );
Node_getTraversable( GlobalSceneGraph().root() )->traverse( CloneAll( clone ) );
@@ -1523,6 +1575,7 @@ bool Map_ImportFile( const char* filename ){
}
NodeSmartReference clone( NewMapRoot( "" ) );
Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
Map_mergeLayers( clone, Node_getLayers( *resource->getNode() ) );
resource->flush(); /* wipe map from cache to not spoil namespace */
GlobalReferenceCache().release( filename );
Map_gatherNamespaced( clone );
@@ -1575,6 +1628,7 @@ tryDecompile:
}
NodeSmartReference clone( NewMapRoot( "" ) );
Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
Map_mergeLayers( clone, Node_getLayers( *resource->getNode() ) );
resource->flush(); /* wipe map from cache to not spoil namespace */
GlobalReferenceCache().release( filename );
Map_gatherNamespaced( clone );
+7 -3
View File
@@ -116,6 +116,10 @@ public:
root.DecRef();
}
class Layer* currentLayer(){
ASSERT_MESSAGE( 0, "Reached unreachable: currentLayer()" );
return nullptr;
}
void boundsChanged(){
ASSERT_MESSAGE( 0, "Reached unreachable: boundsChanged()" );
}
@@ -303,7 +307,7 @@ public:
return m_transform;
}
ModelGraphRoot() : m_node( this, this, StaticTypeCasts::instance().get() ){
ModelGraphRoot() : m_node( this, this, StaticTypeCasts::instance().get(), nullptr ){
m_node.m_isRoot = true;
m_traverse.attach( this );
@@ -392,14 +396,14 @@ public:
}
ModelNode() :
m_node( this, this, StaticTypeCasts::instance().get() ){
m_node( this, this, StaticTypeCasts::instance().get(), nullptr ){
construct();
}
ModelNode( const ModelNode& other ) :
scene::Node::Symbiot( other ),
scene::Instantiable( other ),
scene::Traversable::Observer( other ),
m_node( this, this, StaticTypeCasts::instance().get() ){
m_node( this, this, StaticTypeCasts::instance().get(), nullptr ){
construct();
}
~ModelNode(){
+1 -1
View File
@@ -150,7 +150,7 @@ public:
typedef LazyStatic<TypeCasts> StaticTypeCasts;
NullModelNode() : m_node( this, this, StaticTypeCasts::instance().get() ){
NullModelNode() : m_node( this, this, StaticTypeCasts::instance().get(), nullptr ){
m_node.m_isRoot = true;
}
+1
View File
@@ -33,6 +33,7 @@ public:
ScriptLibraryAPI(){
m_scriptlibrary.m_pfnNewScriptTokeniser = &NewScriptTokeniser;
m_scriptlibrary.m_pfnNewMapTokeniser = &NewMapTokeniser;
m_scriptlibrary.m_pfnNewSimpleTokeniser = &NewSimpleTokeniser;
m_scriptlibrary.m_pfnNewSimpleTokenWriter = &NewSimpleTokenWriter;
}
+2 -2
View File
@@ -1756,7 +1756,7 @@ public:
}
PatchNode( bool patchDef3 = false ) :
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), GlobalSceneGraph().currentLayer() ),
m_patch( m_node, InstanceSetEvaluateTransform<PatchInstance>::Caller( m_instances ), InstanceSet::BoundsChangedCaller( m_instances ) ),
m_importMap( m_patch ),
m_exportMap( m_patch ){
@@ -1766,7 +1766,7 @@ public:
scene::Node::Symbiot( other ),
scene::Instantiable( other ),
scene::Cloneable( other ),
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), other.m_node.m_layer ),
m_patch( other.m_patch, m_node, InstanceSetEvaluateTransform<PatchInstance>::Caller( m_instances ), InstanceSet::BoundsChangedCaller( m_instances ) ),
m_importMap( m_patch ),
m_exportMap( m_patch ){
+10
View File
@@ -32,6 +32,7 @@
#include "scenelib.h"
#include "instancelib.h"
#include "treemodel.h"
#include "layers.h"
template<std::size_t SIZE>
class TypeIdMap
@@ -63,6 +64,8 @@ class CompiledGraph final : public scene::Graph, public scene::Instantiable::Obs
Signal0 m_boundsChanged;
scene::Path m_rootpath;
Signal0 m_sceneChangedCallbacks;
Layer *m_currentLayer0 = nullptr;
Layer **m_currentLayer = &m_currentLayer0;
TypeIdMap<NODETYPEID_MAX> m_nodeTypeIds;
TypeIdMap<INSTANCETYPEID_MAX> m_instanceTypeIds;
@@ -94,6 +97,8 @@ public:
Node_traverseSubgraph( root, InstanceSubgraphWalker( this, scene::Path(), 0 ) );
m_rootpath.push( makeReference( root ) );
m_currentLayer = &Node_getLayers( root )->m_currentLayer;
}
void erase_root(){
//globalOutputStream() << "erase_root\n";
@@ -107,6 +112,11 @@ public:
Node_traverseSubgraph( root, UninstanceSubgraphWalker( this, scene::Path() ) );
root.DecRef();
m_currentLayer = &m_currentLayer0;
}
Layer* currentLayer(){
return *m_currentLayer;
}
void boundsChanged(){
m_boundsChanged();
-20
View File
@@ -941,26 +941,6 @@ void Select_FitTexture( float horizontal, float vertical, bool only_dimension ){
#include "commands.h"
#include "dialog.h"
template<class Check>
bool Traversable_all_of_children( scene::Traversable* traversable, const Check&& check ){
class Check_all : public scene::Traversable::Walker
{
const Check m_check;
public:
mutable bool m_all = true; // true for empty container
Check_all( Check check ) : m_check( check ){
}
bool pre( scene::Node& node ) const override {
if( !m_check( node ) )
m_all = false;
return m_all;
}
} check_all( check );
traversable->traverse( check_all );
return check_all.m_all;
}
inline void hide_node( scene::Node& node, bool hide ){
hide
? node.enable( scene::Node::eHidden )
+1 -1
View File
@@ -1401,7 +1401,7 @@ protected:
}
}
bool editorEvent( QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index ) override {
/* let's do some infamous juggling to track user unduced CheckState change */
/* let's do some infamous juggling to track user induced CheckState change */
if( event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::KeyPress ){
if( const QVariant value = index.data( Qt::ItemDataRole::CheckStateRole ); value.isValid() ){
const bool ret = QItemDelegate::editorEvent( event, model, option, index );
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB