#include #include #include #include #include #include #include #include #include #include #include namespace EE { namespace UI { UITreeView* UITreeView::New() { return eeNew( UITreeView, () ); } UITreeView::UITreeView() : UIAbstractTableView( "treeview" ), mIndentWidth( PixelDensity::dpToPx( 6 ) ), mExpanderIconSize( PixelDensity::dpToPxI( 12 ) ) { setClipType( ClipType::ContentBox ); mExpandIcon = getUISceneNode()->findIcon( "tree-expanded" ); mContractIcon = getUISceneNode()->findIcon( "tree-contracted" ); } Uint32 UITreeView::getType() const { return UI_TYPE_TREEVIEW; } bool UITreeView::isType( const Uint32& type ) const { return UITreeView::getType() == type ? true : UIAbstractTableView::isType( type ); } UITreeView::MetadataForIndex& UITreeView::getIndexMetadata( const ModelIndex& index ) const { eeASSERT( index.isValid() ); auto it = mViewMetadata.find( index.internalData() ); if ( it != mViewMetadata.end() ) return it->second; auto newMetadata = MetadataForIndex(); mViewMetadata.insert( { index.internalData(), std::move( newMetadata ) } ); return mViewMetadata[index.internalData()]; } void UITreeView::traverseTree( TreeViewCallback callback ) const { if ( !getModel() ) return; Lock l( const_cast( getModel() )->resourceMutex() ); auto& model = *getModel(); int indentLevel = 0; Float yOffset = getHeaderHeight(); int rowIndex = -1; Float rowHeight = getRowHeight(); std::function traverseIndex = [&]( const ModelIndex& index ) { if ( index.isValid() ) { const auto& metadata = getIndexMetadata( index ); rowIndex++; IterationDecision decision = callback( rowIndex, index, indentLevel, yOffset ); if ( decision == IterationDecision::Break || decision == IterationDecision::Stop ) return decision; yOffset += rowHeight; if ( !metadata.open ) { return IterationDecision::Continue; } } if ( indentLevel >= 0 && !index.isValid() ) return IterationDecision::Continue; ++indentLevel; int rowCount = model.rowCount( index ); for ( int i = 0; i < rowCount; ++i ) { IterationDecision decision = traverseIndex( model.index( i, model.treeColumn(), index ) ); if ( decision == IterationDecision::Break || decision == IterationDecision::Stop ) return decision; } --indentLevel; return IterationDecision::Continue; }; int rootCount = model.rowCount(); for ( int rootIndex = 0; rootIndex < rootCount; ++rootIndex ) { IterationDecision decision = traverseIndex( model.index( rootIndex, model.treeColumn(), ModelIndex() ) ); if ( decision == IterationDecision::Break || decision == IterationDecision::Stop ) break; } } void UITreeView::createOrUpdateColumns( bool resetColumnData ) { updateContentSize(); if ( !getModel() ) return; UIAbstractTableView::createOrUpdateColumns( resetColumnData ); } size_t UITreeView::getItemCount() const { size_t count = 0; traverseTree( [&count]( const int&, const ModelIndex&, const size_t&, const Float& ) { count++; return IterationDecision::Continue; } ); return count; } void UITreeView::onColumnSizeChange( const size_t& colIndex, bool fromUserInteraction ) { UIAbstractTableView::onColumnSizeChange( colIndex, fromUserInteraction ); updateContentSize(); } void UITreeView::updateContentSize() { Sizef oldSize( mContentSize ); mContentSize = UIAbstractTableView::getContentSize(); if ( oldSize != mContentSize ) onContentSizeChange(); } void UITreeView::bindNavigationClick( UIWidget* widget ) { auto openTree = [this]( const ModelIndex& idx, const Event* event ) { ConditionalLock l( getModel() != nullptr, getModel() ? &getModel()->resourceMutex() : nullptr ); if ( getModel()->rowCount( idx ) ) { auto& data = getIndexMetadata( idx ); data.open = !data.open; createOrUpdateColumns( false ); onOpenTreeModelIndex( idx, data.open ); } else { onOpenModelIndex( idx, event ); } }; mWidgetsClickCbId[widget].push_back( widget->addEventListener( Event::MouseDoubleClick, [this, openTree]( const Event* event ) { auto mouseEvent = static_cast( event ); auto cellIdx = mouseEvent->getNode()->asType()->getCurIndex(); auto idx = mouseEvent->getNode()->getParent()->asType()->getCurIndex(); if ( isEditable() && ( mEditTriggers & EditTrigger::DoubleClicked ) && getModel() && getModel()->isEditable( cellIdx ) ) { beginEditing( cellIdx, mouseEvent->getNode()->asType() ); } else if ( ( mouseEvent->getFlags() & EE_BUTTON_LMASK ) && !mSingleClickNavigation ) { openTree( idx, event ); } } ) ); mWidgetsClickCbId[widget].push_back( widget->addEventListener( Event::MouseClick, [this, openTree]( const Event* event ) { auto mouseEvent = static_cast( event ); auto idx = mouseEvent->getNode()->getParent()->asType()->getCurIndex(); if ( mouseEvent->getFlags() & EE_BUTTON_RMASK ) { onOpenMenuModelIndex( idx, event ); } else if ( ( mouseEvent->getFlags() & EE_BUTTON_LMASK ) && mSingleClickNavigation ) { openTree( idx, event ); } } ) ); } bool UITreeView::tryOpenModelIndex( const ModelIndex& index, bool forceUpdate ) { size_t rowCount = 0; { ConditionalLock l( getModel() != nullptr, getModel() ? &getModel()->resourceMutex() : nullptr ); rowCount = getModel()->rowCount( index ); } if ( rowCount ) { auto& data = getIndexMetadata( index ); if ( !data.open ) { data.open = true; if ( forceUpdate ) createOrUpdateColumns( false ); onOpenTreeModelIndex( index, data.open ); } return true; } return false; } UIWidget* UITreeView::setupCell( UITableCell* widget, UIWidget* rowWidget, const ModelIndex& index ) { widget->setParent( rowWidget ); widget->unsetFlags( UI_AUTO_SIZE ); widget->setClipType( ClipType::ContentBox ); widget->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); widget->setTextAlign( UI_HALIGN_LEFT ); widget->setCurIndex( index ); if ( index.column() == (Int64)getModel()->treeColumn() ) { bindNavigationClick( widget ); widget->onClick( [this]( const MouseEvent* mouseEvent ) { if ( mSingleClickNavigation ) return; UIWidget* icon = mouseEvent->getNode()->asType()->getExtraInnerWidget(); if ( nullptr == icon ) return; Vector2f pos( icon->convertToNodeSpace( mouseEvent->getPosition().asFloat() ) ); if ( pos >= Vector2f::Zero && pos <= icon->getPixelsSize() ) { ConditionalLock l( getModel() != nullptr, getModel() ? &getModel()->resourceMutex() : nullptr ); auto idx = mouseEvent->getNode()->getParent()->asType()->getCurIndex(); if ( getModel()->rowCount( idx ) ) { auto& data = getIndexMetadata( idx ); data.open = !data.open; createOrUpdateColumns( false ); onOpenTreeModelIndex( idx, data.open ); } } } ); } return widget; } UIWidget* UITreeView::createCell( UIWidget* rowWidget, const ModelIndex& index ) { UITableCell* widget = index.column() == (Int64)getModel()->treeColumn() ? UITreeViewCell::New() : UITableCell::New(); return setupCell( widget, rowWidget, index ); } UIWidget* UITreeView::updateCell( const Vector2& posIndex, const ModelIndex& index, const size_t& indentLevel, const Float& yOffset ) { if ( posIndex.y >= (int)mWidgets.size() ) mWidgets.resize( posIndex.y + 1 ); auto* widget = mWidgets[posIndex.y][index.column()]; if ( !widget ) { UIWidget* rowWidget = updateRow( posIndex.y, index, yOffset ); widget = createCell( rowWidget, index ); mWidgets[posIndex.y][index.column()] = widget; widget->reloadStyle( true, true, true ); } const auto& colData = columnData( index.column() ); if ( !colData.visible ) { widget->setVisible( false, false ); return widget; } else { widget->setVisible( true, false ); } widget->setPixelsSize( colData.width, getRowHeight() ); widget->setPixelsPosition( { getColumnPosition( index.column() ).x, 0 } ); if ( widget->isType( UI_TYPE_TABLECELL ) ) { UITableCell* cell = widget->asType(); cell->setCurIndex( index ); if ( getModel()->classModelRoleEnabled() ) { bool needsReloadStyle = false; Variant cls( getModel()->data( index, ModelRole::Class ) ); cell->setLoadingState( true ); if ( cls.isValid() ) { std::string clsStr( cls.toString() ); needsReloadStyle = cell->getClasses().empty() || cell->getClasses().size() != 1 || clsStr != cell->getClasses()[0]; cell->setClass( clsStr ); } else { needsReloadStyle = !cell->getClasses().empty(); cell->resetClass(); } cell->setLoadingState( false ); if ( needsReloadStyle ) cell->reportStyleStateChangeRecursive(); } Variant txt( getModel()->data( index, ModelRole::Display ) ); if ( txt.isValid() ) cell->setText( txt.toString() ); bool hasChilds = false; if ( widget->isType( UI_TYPE_TREEVIEW_CELL ) && index.column() == (Int64)getModel()->treeColumn() ) { UITreeViewCell* tcell = widget->asType(); UIImage* image = widget->asType()->getImage(); Float minIndent = 0; if ( !mExpandersAsIcons && mExpandIcon && mContractIcon ) { minIndent = eemax( mExpandIcon->getSize( mExpanderIconSize )->getPixelsSize().getWidth(), mContractIcon->getSize( mExpanderIconSize )->getPixelsSize().getWidth() ) + image->getLayoutPixelsMargin().Right; } Float indentation = minIndent + getIndentWidth() * indentLevel; hasChilds = getModel()->rowCount( index ) > 0; if ( hasChilds ) { UIIcon* icon = getIndexMetadata( index ).open ? mExpandIcon : mContractIcon; Drawable* drawable = icon ? icon->getSize( mExpanderIconSize ) : nullptr; if ( drawable == nullptr ) { image->setVisible( false ); } else { image->setVisible( true ); image->setPixelsSize( drawable ? drawable->getPixelsSize() : Sizef( 0, 0 ) ); image->setDrawable( drawable ); if ( !mExpandersAsIcons ) indentation = indentation - image->getPixelsSize().getWidth(); } } else { image->setVisible( false ); } tcell->setIndentation( indentation ); } if ( hasChilds && mExpandersAsIcons && cell->hasIcon() ) { cell->getIcon()->setVisible( false ); return widget; } bool isVisible = false; Variant icon( getModel()->data( index, ModelRole::Icon ) ); if ( icon.is( Variant::Type::Drawable ) && icon.asDrawable() ) { isVisible = true; cell->setIcon( icon.asDrawable() ); } else if ( icon.is( Variant::Type::Icon ) && icon.asIcon() ) { isVisible = true; cell->setIcon( icon.asIcon()->getSize( mIconSize ) ); } if ( cell->hasIcon() ) cell->getIcon()->setVisible( isVisible ); cell->updateCell( getModel() ); } if ( isCellSelection() ) { if ( getSelection().contains( index ) ) { widget->pushState( UIState::StateSelected ); } else { widget->popState( UIState::StateSelected ); } } return widget; } const Float& UITreeView::getIndentWidth() const { return mIndentWidth; } void UITreeView::setIndentWidth( const Float& indentWidth ) { if ( mIndentWidth != indentWidth ) { mIndentWidth = indentWidth; createOrUpdateColumns( false ); } } Sizef UITreeView::getContentSize() const { return mContentSize; } void UITreeView::drawChilds() { int realRowIndex = 0; int realColIndex = 0; Float rowHeight = getRowHeight(); traverseTree( [this, &realRowIndex, &realColIndex, rowHeight]( const int&, const ModelIndex& index, const size_t& indentLevel, const Float& yOffset ) { if ( yOffset - mScrollOffset.y > mSize.getHeight() ) return IterationDecision::Stop; if ( yOffset - mScrollOffset.y + rowHeight < 0 ) return IterationDecision::Continue; Float xOffset = 0; UITableRow* rowNode = updateRow( realRowIndex, index, yOffset ); rowNode->setChildsVisibility( false, false ); realColIndex = 0; for ( size_t colIndex = 0; colIndex < getModel()->columnCount(); colIndex++ ) { auto& colData = columnData( colIndex ); if ( !colData.visible || ( xOffset + colData.width ) - mScrollOffset.x < 0 ) { if ( colData.visible ) xOffset += colData.width; continue; } if ( xOffset - mScrollOffset.x > mSize.getWidth() ) break; xOffset += colData.width; if ( (Int64)colIndex != index.column() ) { updateCell( { realColIndex, realRowIndex }, getModel()->index( index.row(), colIndex, index.parent() ), indentLevel, yOffset ); } else { auto* cell = updateCell( { realColIndex, realRowIndex }, index, indentLevel, yOffset ); if ( mFocusSelectionDirty && index == getSelection().first() ) { cell->setFocus(); mFocusSelectionDirty = false; } } realColIndex++; } rowNode->nodeDraw(); realRowIndex++; return IterationDecision::Continue; } ); if ( mHeader && mHeader->isVisible() ) mHeader->nodeDraw(); if ( mHScroll->isVisible() ) mHScroll->nodeDraw(); if ( mVScroll->isVisible() ) mVScroll->nodeDraw(); } Node* UITreeView::overFind( const Vector2f& point ) { ScopedOp op( [this] { mUISceneNode->setIsLoading( true ); }, [this] { mUISceneNode->setIsLoading( false ); } ); Node* pOver = NULL; if ( mEnabled && mVisible ) { updateWorldPolygon(); if ( mWorldBounds.contains( point ) && mPoly.pointInside( point ) ) { writeNodeFlag( NODE_FLAG_MOUSEOVER_ME_OR_CHILD, 1 ); mSceneNode->addMouseOverNode( this ); if ( mHScroll->isVisible() && ( pOver = mHScroll->overFind( point ) ) ) return pOver; if ( mVScroll->isVisible() && ( pOver = mVScroll->overFind( point ) ) ) return pOver; if ( mHeader && ( pOver = mHeader->overFind( point ) ) ) return pOver; int realIndex = 0; Float rowHeight = getRowHeight(); traverseTree( [this, &pOver, &realIndex, point, rowHeight]( int, const ModelIndex& index, const size_t&, const Float& yOffset ) { if ( yOffset - mScrollOffset.y > mSize.getHeight() ) return IterationDecision::Stop; if ( yOffset - mScrollOffset.y + rowHeight < 0 ) return IterationDecision::Continue; pOver = updateRow( realIndex, index, yOffset )->overFind( point ); realIndex++; if ( pOver ) return IterationDecision::Stop; return IterationDecision::Continue; } ); if ( !pOver ) pOver = this; } } return pOver; } bool UITreeView::isExpanded( const ModelIndex& index ) const { return getIndexMetadata( index ).open; } void UITreeView::setExpanded( const std::vector& indexes, bool expanded ) { if ( !getModel() ) return; Model& model = *getModel(); for ( const auto& index : indexes ) { if ( !index.isValid() ) continue; size_t count = model.rowCount( index ); if ( count ) getIndexMetadata( index ).open = expanded; } createOrUpdateColumns( false ); } void UITreeView::setExpanded( const ModelIndex& index, bool expanded ) { setExpanded( std::vector{ index }, expanded ); } void UITreeView::setAllExpanded( const ModelIndex& index, bool expanded ) { Model& model = *getModel(); size_t count = model.rowCount( index ); for ( size_t i = 0; i < count; i++ ) { auto curIndex = model.index( i, model.treeColumn(), index ); getIndexMetadata( curIndex ).open = expanded; if ( model.rowCount( curIndex ) > 0 ) setAllExpanded( curIndex, expanded ); } } void UITreeView::expandAll( const ModelIndex& index ) { if ( !getModel() ) return; setAllExpanded( index, true ); createOrUpdateColumns( false ); } void UITreeView::collapseAll( const ModelIndex& index ) { if ( !getModel() ) return; setAllExpanded( index, false ); createOrUpdateColumns( false ); } UIIcon* UITreeView::getExpandIcon() const { return mExpandIcon; } void UITreeView::setExpandedIcon( UIIcon* expandIcon ) { if ( mExpandIcon != expandIcon ) { mExpandIcon = expandIcon; createOrUpdateColumns( false ); } } void UITreeView::setExpandedIcon( const std::string& expandIcon ) { setExpandedIcon( mUISceneNode->findIcon( expandIcon ) ); } UIIcon* UITreeView::getContractIcon() const { return mContractIcon; } void UITreeView::setContractedIcon( UIIcon* contractIcon ) { if ( mContractIcon != contractIcon ) { mContractIcon = contractIcon; createOrUpdateColumns( false ); } } void UITreeView::setContractedIcon( const std::string& contractIcon ) { setContractedIcon( mUISceneNode->findIcon( contractIcon ) ); } bool UITreeView::getExpandersAsIcons() const { return mExpandersAsIcons; } void UITreeView::setExpandersAsIcons( bool expandersAsIcons ) { mExpandersAsIcons = expandersAsIcons; } Float UITreeView::getMaxColumnContentWidth( const size_t& colIndex, bool ) { Float lWidth = 0; ScopedOp op( [this] { mUISceneNode->setIsLoading( true ); }, [this] { mUISceneNode->setIsLoading( false ); } ); traverseTree( [this, &lWidth, colIndex]( const int&, const ModelIndex& index, const size_t& indentLevel, const Float& yOffset ) { UIWidget* widget = updateCell( { (Int64)0, (Int64)0 }, getModel()->index( index.row(), colIndex, index.parent() ), indentLevel, yOffset ); if ( widget->isType( UI_TYPE_PUSHBUTTON ) ) { Float w = widget->asType()->getContentSize().getWidth(); if ( w > lWidth ) lWidth = w; } return IterationDecision::Continue; } ); return lWidth; } const size_t& UITreeView::getExpanderIconSize() const { return mExpanderIconSize; } void UITreeView::setExpanderIconSize( const size_t& expanderSize ) { mExpanderIconSize = expanderSize; } Uint32 UITreeView::onKeyDown( const KeyEvent& event ) { if ( event.getMod() & KEYMOD_CTRL_SHIFT_ALT_META ) return UIAbstractTableView::onKeyDown( event ); auto curIndex = getSelection().first(); switch ( event.getKeyCode() ) { case KEY_PAGEUP: { int pageSize = eefloor( getVisibleArea().getHeight() / getRowHeight() ) - 1; std::deque> deque; Float curY; traverseTree( [&]( const int&, const ModelIndex& index, const size_t&, const Float& offsetY ) { deque.push_back( { index, offsetY } ); if ( (int)deque.size() > pageSize ) deque.pop_front(); if ( index == curIndex ) return IterationDecision::Break; return IterationDecision::Continue; } ); curY = deque.front().second - getHeaderHeight(); getSelection().set( deque.front().first ); scrollToPosition( { { mScrollOffset.x, curY }, { columnData( deque.front().first.column() ).width, getRowHeight() } } ); return 1; } case KEY_PAGEDOWN: { int pageSize = eefloor( getVisibleArea().getHeight() / getRowHeight() ) - 1; int counted = 0; bool foundStart = false; bool resultFound = false; ModelIndex foundIndex; Float curY = 0; Float lastOffsetY = 0; ModelIndex lastIndex; traverseTree( [&]( const int&, const ModelIndex& index, const size_t&, const Float& offsetY ) { if ( index == curIndex ) { foundStart = true; } else if ( foundStart ) { counted++; if ( counted == pageSize ) { foundIndex = index; curY = offsetY; resultFound = true; return IterationDecision::Break; } } lastOffsetY = offsetY; lastIndex = index; return IterationDecision::Continue; } ); if ( !resultFound ) { foundIndex = lastIndex; curY = lastOffsetY; } curY += getRowHeight(); getSelection().set( foundIndex ); scrollToPosition( { { mScrollOffset.x, curY }, { columnData( foundIndex.column() ).width, getRowHeight() } } ); return 1; } case KEY_UP: { ModelIndex prevIndex; ModelIndex foundIndex; Float curY = 0; traverseTree( [&]( const int&, const ModelIndex& index, const size_t&, const Float& offsetY ) { if ( index == curIndex ) { foundIndex = prevIndex; curY = offsetY; return IterationDecision::Break; } prevIndex = index; return IterationDecision::Continue; } ); if ( foundIndex.isValid() ) { getSelection().set( foundIndex ); if ( curY < mScrollOffset.y + getHeaderHeight() + getRowHeight() || curY > mScrollOffset.y + getPixelsSize().getHeight() - mPaddingPx.Top - mPaddingPx.Bottom - getRowHeight() ) { curY -= getHeaderHeight() + getRowHeight(); mVScroll->setValue( eemin( 1.f, eemax( 0.f, curY / getScrollableArea().getHeight() ) ) ); } } return 1; } case KEY_DOWN: { ModelIndex prevIndex; ModelIndex foundIndex; Float curY = 0; traverseTree( [&]( const int&, const ModelIndex& index, const size_t&, const Float& offsetY ) { if ( prevIndex == curIndex ) { foundIndex = index; curY = offsetY; return IterationDecision::Break; } prevIndex = index; return IterationDecision::Continue; } ); if ( foundIndex.isValid() ) { getSelection().set( foundIndex ); if ( curY < mScrollOffset.y || curY > mScrollOffset.y + getPixelsSize().getHeight() - mPaddingPx.Top - mPaddingPx.Bottom - getRowHeight() ) { curY -= eefloor( getVisibleArea().getHeight() / getRowHeight() ) * getRowHeight() - getRowHeight(); mVScroll->setValue( eemin( 1.f, curY / getScrollableArea().getHeight() ) ); } } return 1; } case KEY_END: { scrollToBottom(); ModelIndex lastIndex; traverseTree( [&]( const int&, const ModelIndex& index, const size_t&, const Float& ) { lastIndex = index; return IterationDecision::Continue; } ); getSelection().set( lastIndex ); return 1; } case KEY_HOME: { scrollToTop(); getSelection().set( getModel()->index( 0, 0 ) ); return 1; } case KEY_RIGHT: { if ( curIndex.isValid() && getModel()->rowCount( curIndex ) ) { auto& metadata = getIndexMetadata( curIndex ); if ( !metadata.open ) { metadata.open = true; createOrUpdateColumns( false ); return 0; } getSelection().set( getModel()->index( 0, getModel()->treeColumn(), curIndex ) ); } return 1; } case KEY_LEFT: { if ( curIndex.isValid() && getModel()->rowCount( curIndex ) ) { auto& metadata = getIndexMetadata( curIndex ); if ( metadata.open ) { metadata.open = false; createOrUpdateColumns( false ); return 0; } } if ( curIndex.isValid() && curIndex.parent().isValid() ) { getSelection().set( curIndex.parent() ); return 0; } return 1; } case KEY_RETURN: case KEY_KP_ENTER: { if ( curIndex.isValid() ) { if ( getModel()->rowCount( curIndex ) ) { auto& metadata = getIndexMetadata( curIndex ); metadata.open = !metadata.open; createOrUpdateColumns( false ); } else { onOpenModelIndex( curIndex, &event ); } } return 1; } case KEY_MENU: case KEY_APPLICATION: { if ( curIndex.isValid() ) onOpenMenuModelIndex( curIndex, &event ); return 1; } default: break; } return UIAbstractTableView::onKeyDown( event ); } void UITreeView::onOpenTreeModelIndex( const ModelIndex& index, bool open ) { ModelEvent event( getModel(), index, this, open ? ModelEventType::OpenTree : ModelEventType::CloseTree ); sendEvent( &event ); } void UITreeView::onSortColumn( const size_t& ) { // Do nothing. return; } ModelIndex UITreeView::findRowWithText( const std::string& text, const bool& caseSensitive, const bool& exactMatch ) const { const Model* model = getModel(); ConditionalLock l( getModel() != nullptr, getModel() ? &const_cast( getModel() )->resourceMutex() : nullptr ); if ( !model || model->rowCount() == 0 ) return {}; ModelIndex foundIndex = {}; traverseTree( [&]( const int&, const ModelIndex& index, const size_t&, const Float& ) { Variant var = model->data( index ); if ( var.isValid() && ( exactMatch ? var.toString() == text : String::startsWith( caseSensitive ? var.toString() : String::toLower( var.toString() ), caseSensitive ? text : String::toLower( text ) ) ) ) { foundIndex = index; return IterationDecision::Stop; } return IterationDecision::Continue; } ); return foundIndex; } ModelIndex UITreeView::selectRowWithPath( std::string path ) { const Model* model = getModel(); if ( !model || model->rowCount() == 0 ) return {}; String::replaceAll( path, "\\", "/" ); auto pathPart = String::split( path, "/" ); if ( pathPart.empty() ) return {}; ModelIndex parentIndex = {}; for ( size_t i = 0; i < pathPart.size(); i++ ) { ModelIndex foundIndex = {}; const auto& part = pathPart[i]; traverseTree( [&model, &foundIndex, &part, &parentIndex, i]( const int&, const ModelIndex& index, const size_t& indentLevel, const Float& ) { Variant var = model->data( index ); if ( i == indentLevel && var.isValid() && var.toString() == part ) { if ( !parentIndex.isValid() || parentIndex == index.parent() ) { foundIndex = index; return IterationDecision::Stop; } } return IterationDecision::Continue; } ); if ( foundIndex == ModelIndex() ) break; tryOpenModelIndex( foundIndex ); parentIndex = foundIndex; if ( i == pathPart.size() - 1 ) { setSelection( foundIndex ); return foundIndex; } } return {}; } void UITreeView::setSelection( const ModelIndex& index, bool scrollToSelection, bool openModelIndexTree ) { if ( !getModel() ) return; auto& model = *this->getModel(); if ( model.isValid( index ) ) { if ( openModelIndexTree ) openModelIndexParentTree( index ); getSelection().set( index ); if ( !scrollToSelection ) return; ModelIndex prevIndex; ModelIndex foundIndex; Float curY = 0; traverseTree( [&]( const int&, const ModelIndex& _index, const size_t&, const Float& offsetY ) { if ( index == _index ) { foundIndex = prevIndex; curY = offsetY; return IterationDecision::Break; } prevIndex = index; return IterationDecision::Continue; } ); if ( foundIndex.isValid() ) { if ( curY < mScrollOffset.y + getHeaderHeight() + getRowHeight() || curY > mScrollOffset.y + getPixelsSize().getHeight() - mPaddingPx.Top - mPaddingPx.Bottom - getRowHeight() ) { curY -= getHeaderHeight() + getRowHeight(); mVScroll->setValue( eemin( 1.f, eemax( 0.f, curY / getScrollableArea().getHeight() ) ) ); } } } } void UITreeView::openModelIndexParentTree( const ModelIndex& index ) { if ( !index.hasParent() ) return; std::stack indexes; ModelIndex parent = index; while ( parent.hasParent() ) { indexes.push( parent ); parent = parent.parent(); } while ( !indexes.empty() ) { if ( !tryOpenModelIndex( indexes.top() ) ) return; indexes.pop(); } createOrUpdateColumns( false ); } bool UITreeView::getFocusOnSelection() const { return mFocusOnSelection; } void UITreeView::setFocusOnSelection( bool focusOnSelection ) { mFocusOnSelection = focusOnSelection; } void UITreeView::onModelSelectionChange() { UIAbstractTableView::onModelSelectionChange(); if ( mFocusOnSelection ) mFocusSelectionDirty = true; } bool UITreeViewCell::isType( const Uint32& type ) const { return UITreeViewCell::getType() == type ? true : UITableCell::isType( type ); } Rectf UITreeViewCell::calculatePadding() const { Sizef size; Rectf autoPadding; if ( mFlags & UI_AUTO_PADDING ) { autoPadding = makePadding( true, true, true, true ); if ( autoPadding != Rectf() ) autoPadding = PixelDensity::dpToPx( autoPadding ); } if ( mPaddingPx.Top > autoPadding.Top ) autoPadding.Top = mPaddingPx.Top; if ( mPaddingPx.Bottom > autoPadding.Bottom ) autoPadding.Bottom = mPaddingPx.Bottom; if ( mPaddingPx.Left > autoPadding.Left ) autoPadding.Left = mPaddingPx.Left; if ( mPaddingPx.Right > autoPadding.Right ) autoPadding.Right = mPaddingPx.Right; autoPadding.Left += mIndent; return autoPadding; } void UITreeViewCell::setIndentation( const Float& indent ) { if ( mIndent != indent ) { mIndent = indent; updateLayout(); } } UITreeViewCell::UITreeViewCell( const std::function& newTextViewCb ) : UITableCell( "treeview::cell", newTextViewCb ) { mTextBox->setElementTag( mTag + "::text" ); mInnerWidgetOrientation = InnerWidgetOrientation::WidgetIconTextBox; auto cb = [this]( const Event* ) { updateLayout(); }; mImage = UIImage::NewWithTag( mTag + "::expander" ); mImage->setScaleType( UIScaleType::FitInside ) ->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ) ->setFlags( UI_VALIGN_CENTER | UI_HALIGN_CENTER ) ->setParent( const_cast( this ) ) ->setVisible( false ) ->setEnabled( false ); mImage->addEventListener( Event::OnPaddingChange, cb ); mImage->addEventListener( Event::OnMarginChange, cb ); mImage->addEventListener( Event::OnSizeChange, cb ); mImage->addEventListener( Event::OnVisibleChange, cb ); } UIWidget* UITreeViewCell::getExtraInnerWidget() const { return mImage; } }} // namespace EE::UI