mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-01 18:50:16 +03:00
ecode: add customizable debugger panel layouts
- add a reusable right-side panel managed at the application level
- migrate debugger tabs to UITabWidgetSplitter instances
- support moving and splitting debugger tabs across bottom and right panels
- restrict debugger tab drops to debugger-owned panel targets
- serialize debugger tab layouts, panel state, and table column widths
- add pixel and percentage column-width modes to UIAbstractTableView
- support XML/CSS configuration and an optional width-mode context menu
- preserve percentage totals across resizing, fit-to-content, minimum widths,
scrollbar changes, and pixel-density rounding
- add splitter edge hiding while preserving splitter-always-show precedence
- use EventConnection ownership for safe widget and controller lifetimes
- validate serialized splitter layouts with structural limits
- display project-relative source paths in debugger stack frames
- add ecode localization strings, CSS documentation, tests, and agent rules
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
#include <eepp/ui/uiscrollbar.hpp>
|
||||
#include <eepp/window/engine.hpp>
|
||||
#include <eepp/window/input.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace EE { namespace UI { namespace Abstract {
|
||||
|
||||
@@ -104,6 +105,166 @@ const Float& UIAbstractTableView::getColumnWidth( const size_t& colIndex ) const
|
||||
return columnData( colIndex ).width;
|
||||
}
|
||||
|
||||
Float UIAbstractTableView::getColumnWidthPercentage( const size_t& colIndex ) const {
|
||||
return columnData( colIndex ).percentage;
|
||||
}
|
||||
|
||||
std::vector<Float> UIAbstractTableView::getColumnsWidthPercentage() const {
|
||||
std::vector<Float> percentages;
|
||||
size_t count = getModel() ? getModel()->columnCount() : mColumn.size();
|
||||
percentages.reserve( count );
|
||||
for ( size_t i = 0; i < count; ++i )
|
||||
percentages.emplace_back( columnData( i ).percentage );
|
||||
return percentages;
|
||||
}
|
||||
|
||||
UIAbstractTableView::ColumnWidthMode UIAbstractTableView::getColumnWidthMode() const {
|
||||
return mColumnWidthMode;
|
||||
}
|
||||
|
||||
void UIAbstractTableView::setColumnWidthMode( ColumnWidthMode mode, bool convertCurrentWidths ) {
|
||||
if ( mode == mColumnWidthMode )
|
||||
return;
|
||||
if ( mode == ColumnWidthMode::Percentage && getModel() ) {
|
||||
setAutoColumnsWidth( false );
|
||||
if ( convertCurrentWidths ) {
|
||||
Float totalWidth = 0;
|
||||
for ( size_t i = 0; i < getModel()->columnCount(); ++i )
|
||||
if ( !isColumnHidden( i ) )
|
||||
totalWidth += columnData( i ).width;
|
||||
if ( totalWidth > 0 )
|
||||
for ( size_t i = 0; i < getModel()->columnCount(); ++i )
|
||||
if ( !isColumnHidden( i ) )
|
||||
columnData( i ).percentage = columnData( i ).width / totalWidth * 100.f;
|
||||
}
|
||||
}
|
||||
mColumnWidthMode = mode;
|
||||
createOrUpdateColumns( false );
|
||||
}
|
||||
|
||||
bool UIAbstractTableView::isColumnWidthModeMenuEnabled() const {
|
||||
return mColumnWidthModeMenuEnabled;
|
||||
}
|
||||
|
||||
void UIAbstractTableView::setColumnWidthModeMenuEnabled( bool enabled ) {
|
||||
mColumnWidthModeMenuEnabled = enabled;
|
||||
}
|
||||
|
||||
void UIAbstractTableView::setColumnWidthPercentage( const size_t& colIndex, Float percentage ) {
|
||||
if ( mColumnWidthMode != ColumnWidthMode::Percentage )
|
||||
setColumnWidthMode( ColumnWidthMode::Percentage );
|
||||
if ( !getModel() || colIndex >= getModel()->columnCount() )
|
||||
return;
|
||||
auto& column = columnData( colIndex );
|
||||
int adjacent = adjacentVisibleColumn( colIndex );
|
||||
if ( adjacent >= 0 ) {
|
||||
auto& sibling = columnData( adjacent );
|
||||
Float combined = column.percentage + sibling.percentage;
|
||||
column.percentage = eeclamp( percentage, 0.f, combined );
|
||||
sibling.percentage = combined - column.percentage;
|
||||
} else {
|
||||
column.percentage = 100.f;
|
||||
}
|
||||
createOrUpdateColumns( false );
|
||||
}
|
||||
|
||||
void UIAbstractTableView::setColumnsWidthPercentage( const std::vector<Float>& percentages ) {
|
||||
const size_t columnCount = getModel() ? getModel()->columnCount() : percentages.size();
|
||||
if ( mColumn.size() < columnCount )
|
||||
mColumn.resize( columnCount );
|
||||
const size_t suppliedColumnCount = eemin( percentages.size(), columnCount );
|
||||
Float total = 0;
|
||||
for ( size_t i = 0; i < suppliedColumnCount; ++i ) {
|
||||
columnData( i ).percentage = eemax( 0.f, percentages[i] );
|
||||
if ( !getModel() || !isColumnHidden( i ) )
|
||||
total += columnData( i ).percentage;
|
||||
}
|
||||
size_t missingVisibleColumns = 0;
|
||||
for ( size_t i = suppliedColumnCount; i < columnCount; ++i )
|
||||
if ( !getModel() || !isColumnHidden( i ) )
|
||||
++missingVisibleColumns;
|
||||
const Float missingPercentage =
|
||||
missingVisibleColumns > 0 ? eemax( 0.f, 100.f - total ) / missingVisibleColumns : 0.f;
|
||||
for ( size_t i = suppliedColumnCount; i < columnCount; ++i ) {
|
||||
columnData( i ).percentage = !getModel() || !isColumnHidden( i ) ? missingPercentage : 0.f;
|
||||
total += columnData( i ).percentage;
|
||||
}
|
||||
if ( total > 0 )
|
||||
for ( size_t i = 0; i < columnCount; ++i )
|
||||
if ( !getModel() || !isColumnHidden( i ) )
|
||||
columnData( i ).percentage = columnData( i ).percentage / total * 100.f;
|
||||
setAutoColumnsWidth( false );
|
||||
mColumnWidthMode = ColumnWidthMode::Percentage;
|
||||
if ( getModel() )
|
||||
createOrUpdateColumns( false );
|
||||
}
|
||||
|
||||
nlohmann::json UIAbstractTableView::serializeColumnWidths() const {
|
||||
if ( !getModel() && !mPendingSerializedColumnWidths.empty() )
|
||||
return nlohmann::json::parse( mPendingSerializedColumnWidths, nullptr, false, true );
|
||||
nlohmann::json saved;
|
||||
const bool percentage = mColumnWidthMode == ColumnWidthMode::Percentage;
|
||||
saved["mode"] = percentage ? "percentage" : "pixels";
|
||||
if ( percentage ) {
|
||||
saved["widths"] = getColumnsWidthPercentage();
|
||||
return saved;
|
||||
}
|
||||
std::vector<Float> widths;
|
||||
if ( !getModel() )
|
||||
return saved;
|
||||
widths.reserve( getModel()->columnCount() );
|
||||
for ( size_t i = 0; i < getModel()->columnCount(); ++i )
|
||||
widths.emplace_back( PixelDensity::pxToDp( getColumnWidth( i ) ) );
|
||||
saved["widths"] = std::move( widths );
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool UIAbstractTableView::unserializeColumnWidths( const nlohmann::json& saved ) {
|
||||
const nlohmann::json* widths = &saved;
|
||||
ColumnWidthMode mode = ColumnWidthMode::Percentage;
|
||||
if ( saved.is_object() ) {
|
||||
if ( !saved.contains( "widths" ) || !saved["widths"].is_array() )
|
||||
return false;
|
||||
widths = &saved["widths"];
|
||||
if ( saved.value( "mode", "percentage" ) == "pixels" )
|
||||
mode = ColumnWidthMode::Pixels;
|
||||
} else if ( !saved.is_array() ) {
|
||||
return false;
|
||||
}
|
||||
std::vector<Float> values;
|
||||
values.reserve( widths->size() );
|
||||
for ( const auto& value : *widths ) {
|
||||
if ( !value.is_number() )
|
||||
return false;
|
||||
values.emplace_back( value.get<Float>() );
|
||||
}
|
||||
if ( !getModel() ) {
|
||||
mPendingSerializedColumnWidths = saved.dump();
|
||||
return true;
|
||||
}
|
||||
mPendingSerializedColumnWidths.clear();
|
||||
if ( mode == ColumnWidthMode::Percentage ) {
|
||||
setColumnsWidthPercentage( values );
|
||||
} else {
|
||||
if ( values.size() != getModel()->columnCount() )
|
||||
return false;
|
||||
setColumnWidthMode( mode, false );
|
||||
for ( size_t i = 0; i < values.size(); ++i )
|
||||
setColumnWidth( i, PixelDensity::dpToPx( values[i] ) );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void UIAbstractTableView::restorePendingColumnWidths() {
|
||||
if ( !getModel() || mPendingSerializedColumnWidths.empty() )
|
||||
return;
|
||||
std::string serialized( std::move( mPendingSerializedColumnWidths ) );
|
||||
mPendingSerializedColumnWidths.clear();
|
||||
auto widths = nlohmann::json::parse( serialized, nullptr, false, true );
|
||||
if ( !widths.is_discarded() )
|
||||
unserializeColumnWidths( widths );
|
||||
}
|
||||
|
||||
void UIAbstractTableView::selectAll() {
|
||||
getSelection().clear();
|
||||
for ( size_t itemIndex = 0; itemIndex < getItemCount(); ++itemIndex ) {
|
||||
@@ -139,11 +300,13 @@ void UIAbstractTableView::onModelUpdate( unsigned flags ) {
|
||||
[this] {
|
||||
modelUpdate( mPendingUpdateFlags.exchange( 0 ) );
|
||||
createOrUpdateColumns( true );
|
||||
restorePendingColumnWidths();
|
||||
},
|
||||
Time::Zero, onModelUpdateTag );
|
||||
} else {
|
||||
UIAbstractView::onModelUpdate( flags );
|
||||
createOrUpdateColumns( true );
|
||||
restorePendingColumnWidths();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,6 +359,9 @@ void UIAbstractTableView::createOrUpdateColumns( bool resetColumnData ) {
|
||||
col.widget->setPixelsSize( col.width, getHeaderHeight() );
|
||||
}
|
||||
|
||||
if ( mColumnWidthMode == ColumnWidthMode::Percentage )
|
||||
updatePercentageColumnWidths();
|
||||
|
||||
if ( mAutoColumnsWidth && visibleColCount > 1 ) {
|
||||
Float contentWidth = getContentSpaceWidth();
|
||||
bool shouldVScrollBeVisible = shouldVerticalScrollBeVisible();
|
||||
@@ -311,9 +477,92 @@ void UIAbstractTableView::onSizeChange() {
|
||||
createOrUpdateColumns( false );
|
||||
}
|
||||
|
||||
void UIAbstractTableView::onColumnSizeChange( const size_t&, bool fromUserInteraction ) {
|
||||
void UIAbstractTableView::onColumnSizeChange( const size_t& colIndex, bool fromUserInteraction ) {
|
||||
if ( fromUserInteraction && mAutoColumnsWidth )
|
||||
mAutoColumnsWidth = false;
|
||||
if ( fromUserInteraction && mColumnWidthMode == ColumnWidthMode::Percentage && getModel() ) {
|
||||
int adjacent = adjacentVisibleColumn( colIndex );
|
||||
Float contentWidth = getContentSpaceWidth();
|
||||
if ( adjacent >= 0 && contentWidth > 0 ) {
|
||||
auto& column = columnData( colIndex );
|
||||
auto& sibling = columnData( adjacent );
|
||||
Float combined = column.percentage + sibling.percentage;
|
||||
Float minPercentage = column.minWidth / contentWidth * 100.f;
|
||||
Float siblingMinPercentage = sibling.minWidth / contentWidth * 100.f;
|
||||
column.percentage = eeclamp( column.width / contentWidth * 100.f, minPercentage,
|
||||
combined - siblingMinPercentage );
|
||||
sibling.percentage = combined - column.percentage;
|
||||
updatePercentageColumnWidths();
|
||||
updateHeaderSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UIAbstractTableView::updatePercentageColumnWidths() {
|
||||
if ( !getModel() )
|
||||
return;
|
||||
Float contentWidth = getContentSpaceWidth();
|
||||
Float totalPercentage = 0;
|
||||
int visibleColumns = 0;
|
||||
for ( size_t i = 0; i < getModel()->columnCount(); ++i ) {
|
||||
if ( !isColumnHidden( i ) ) {
|
||||
totalPercentage += columnData( i ).percentage;
|
||||
visibleColumns++;
|
||||
}
|
||||
}
|
||||
if ( totalPercentage <= 0 && visibleColumns > 0 ) {
|
||||
for ( size_t i = 0; i < getModel()->columnCount(); ++i )
|
||||
if ( !isColumnHidden( i ) )
|
||||
columnData( i ).percentage = 100.f / visibleColumns;
|
||||
totalPercentage = 100.f;
|
||||
}
|
||||
contentWidth = eefloor( contentWidth );
|
||||
Float cumulativePercentage = 0;
|
||||
Float previousBoundary = 0;
|
||||
Float assignedWidth = 0;
|
||||
for ( size_t i = 0; i < getModel()->columnCount(); ++i ) {
|
||||
if ( isColumnHidden( i ) )
|
||||
continue;
|
||||
auto& column = columnData( i );
|
||||
cumulativePercentage += column.percentage;
|
||||
Float boundary = eefloor( contentWidth * cumulativePercentage / totalPercentage + 0.5f );
|
||||
Float width = boundary - previousBoundary;
|
||||
Float minWidth = eeceil( column.minWidth );
|
||||
width = column.maxWidth != 0
|
||||
? eeclamp( width, minWidth, eemax( minWidth, eefloor( column.maxWidth ) ) )
|
||||
: eemax( width, minWidth );
|
||||
column.setWidth( width, true );
|
||||
assignedWidth += width;
|
||||
previousBoundary = boundary;
|
||||
}
|
||||
Float overflow = assignedWidth - contentWidth;
|
||||
for ( size_t i = getModel()->columnCount(); overflow > 0 && i > 0; --i ) {
|
||||
if ( isColumnHidden( i - 1 ) )
|
||||
continue;
|
||||
auto& column = columnData( i - 1 );
|
||||
Float shrink = eemin( overflow, column.width - eeceil( column.minWidth ) );
|
||||
if ( shrink > 0 ) {
|
||||
column.setWidth( column.width - shrink, true );
|
||||
overflow -= shrink;
|
||||
}
|
||||
}
|
||||
for ( size_t i = 0; i < getModel()->columnCount(); ++i ) {
|
||||
auto& column = columnData( i );
|
||||
if ( column.widget && !isColumnHidden( i ) )
|
||||
column.widget->setPixelsSize( column.width, getHeaderHeight() );
|
||||
}
|
||||
}
|
||||
|
||||
int UIAbstractTableView::adjacentVisibleColumn( size_t column ) const {
|
||||
if ( !getModel() )
|
||||
return -1;
|
||||
for ( size_t i = column + 1; i < getModel()->columnCount(); ++i )
|
||||
if ( !isColumnHidden( i ) )
|
||||
return i;
|
||||
for ( size_t i = column; i > 0; --i )
|
||||
if ( !isColumnHidden( i - 1 ) )
|
||||
return i - 1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
Float UIAbstractTableView::getMaxColumnContentWidth( const size_t&, bool ) {
|
||||
@@ -322,6 +571,10 @@ Float UIAbstractTableView::getMaxColumnContentWidth( const size_t&, bool ) {
|
||||
|
||||
void UIAbstractTableView::onColumnResizeToContent( const size_t& colIndex ) {
|
||||
columnData( colIndex ).setWidth( getMaxColumnContentWidth( colIndex, true ) );
|
||||
if ( mColumnWidthMode == ColumnWidthMode::Percentage ) {
|
||||
onColumnSizeChange( colIndex, true );
|
||||
return;
|
||||
}
|
||||
createOrUpdateColumns( false );
|
||||
}
|
||||
|
||||
@@ -582,6 +835,31 @@ void UIAbstractTableView::onScrollChange() {
|
||||
mHeader->setPixelsPosition( mRowHeaderWidth + -mScrollOffset.x, 0 );
|
||||
}
|
||||
|
||||
void UIAbstractTableView::onContentSizeChange() {
|
||||
if ( mUpdatingColumnsForScrollbars ) {
|
||||
UIScrollableWidget::onContentSizeChange();
|
||||
return;
|
||||
}
|
||||
|
||||
bool verticalScrollWasVisible = mVScroll->isVisible();
|
||||
UIScrollableWidget::onContentSizeChange();
|
||||
const bool columnsDependOnContentWidth =
|
||||
mColumnWidthMode == ColumnWidthMode::Percentage || mAutoColumnsWidth ||
|
||||
( mAutoExpandOnSingleColumn && visibleColumnCount() == 1 );
|
||||
if ( !columnsDependOnContentWidth || verticalScrollWasVisible == mVScroll->isVisible() )
|
||||
return;
|
||||
|
||||
mUpdatingColumnsForScrollbars = true;
|
||||
for ( int iteration = 0; iteration < 2; ++iteration ) {
|
||||
bool visibilityUsedForColumns = mVScroll->isVisible();
|
||||
createOrUpdateColumns( false );
|
||||
UIScrollableWidget::onContentSizeChange();
|
||||
if ( visibilityUsedForColumns == mVScroll->isVisible() )
|
||||
break;
|
||||
}
|
||||
mUpdatingColumnsForScrollbars = false;
|
||||
}
|
||||
|
||||
void UIAbstractTableView::bindNavigationClick( UIWidget* widget ) {
|
||||
mWidgetsClickCbId[widget].push_back(
|
||||
widget->on( Event::MouseDoubleClick, [this]( const Event* event ) {
|
||||
@@ -1012,6 +1290,14 @@ bool UIAbstractTableView::applyProperty( const StyleSheetProperty& attribute ) {
|
||||
case PropertyId::MainColumn:
|
||||
setMainColumn( attribute.asInt() );
|
||||
break;
|
||||
case PropertyId::ColumnWidthMode:
|
||||
setColumnWidthMode( String::iequals( attribute.getValue(), "percentage" )
|
||||
? ColumnWidthMode::Percentage
|
||||
: ColumnWidthMode::Pixels );
|
||||
break;
|
||||
case PropertyId::ColumnWidthModeMenu:
|
||||
setColumnWidthModeMenuEnabled( attribute.asBool() );
|
||||
break;
|
||||
case PropertyId::RowHeaderWidth:
|
||||
setRowHeaderWidth(
|
||||
lengthFromValue( attribute.getValue(), PropertyRelativeTarget::None ) );
|
||||
@@ -1063,6 +1349,10 @@ std::string UIAbstractTableView::getPropertyString( const PropertyDefinition* pr
|
||||
return String::fromFloat( (Float)getSortIconSize(), "px" );
|
||||
case PropertyId::MainColumn:
|
||||
return String::toString( (Int64)getMainColumn() );
|
||||
case PropertyId::ColumnWidthMode:
|
||||
return getColumnWidthMode() == ColumnWidthMode::Percentage ? "percentage" : "pixels";
|
||||
case PropertyId::ColumnWidthModeMenu:
|
||||
return isColumnWidthModeMenuEnabled() ? "true" : "false";
|
||||
case PropertyId::RowHeaderWidth:
|
||||
return String::fromFloat( getRowHeaderWidth(), "px" );
|
||||
case PropertyId::TableFlags: {
|
||||
@@ -1099,9 +1389,10 @@ std::string UIAbstractTableView::getPropertyString( const PropertyDefinition* pr
|
||||
|
||||
std::vector<PropertyId> UIAbstractTableView::getPropertiesImplemented() const {
|
||||
auto props = UIAbstractView::getPropertiesImplemented();
|
||||
props.insert( props.end(),
|
||||
{ PropertyId::RowHeight, PropertyId::IconSize, PropertyId::SortIconSize,
|
||||
PropertyId::MainColumn, PropertyId::RowHeaderWidth, PropertyId::TableFlags } );
|
||||
props.insert( props.end(), { PropertyId::RowHeight, PropertyId::IconSize,
|
||||
PropertyId::SortIconSize, PropertyId::MainColumn,
|
||||
PropertyId::ColumnWidthMode, PropertyId::ColumnWidthModeMenu,
|
||||
PropertyId::RowHeaderWidth, PropertyId::TableFlags } );
|
||||
return props;
|
||||
}
|
||||
|
||||
|
||||
@@ -336,6 +336,10 @@ void StyleSheetSpecification::registerDefaultProperties() {
|
||||
.setType( PropertyType::NumberLength );
|
||||
registerProperty( PropertyId::MainColumn, "main-column", "0" )
|
||||
.setType( PropertyType::NumberInt );
|
||||
registerProperty( PropertyId::ColumnWidthMode, "column-width-mode", "pixels" )
|
||||
.setType( PropertyType::String );
|
||||
registerProperty( PropertyId::ColumnWidthModeMenu, "column-width-mode-menu", "false" )
|
||||
.setType( PropertyType::Bool );
|
||||
registerProperty( PropertyId::RowHeaderWidth, "row-header-width", "" )
|
||||
.setType( PropertyType::NumberLength );
|
||||
registerProperty( PropertyId::TableFlags, "table-flags", "" )
|
||||
@@ -590,6 +594,8 @@ void StyleSheetSpecification::registerDefaultProperties() {
|
||||
.setRelativeTarget( PropertyRelativeTarget::LocalBlockWidth );
|
||||
registerProperty( PropertyId::SplitterAlwaysShow, "splitter-always-show", "true" )
|
||||
.setType( PropertyType::Bool );
|
||||
registerProperty( PropertyId::SplitterHideOnEdge, "splitter-hide-on-edge", "false" )
|
||||
.setType( PropertyType::Bool );
|
||||
|
||||
registerProperty( PropertyId::DroppableHoveringColor, "droppable-hovering-color", "#FFFFFF20" )
|
||||
.setType( PropertyType::Color );
|
||||
|
||||
@@ -1926,6 +1926,8 @@ void UICodeEditorSplitter::onTabClosed( const TabEvent* tabEvent ) {
|
||||
Node* remainingNode = tabWidget == splitter->getFirstWidget()
|
||||
? splitter->getLastWidget()
|
||||
: splitter->getFirstWidget();
|
||||
// Keep the surviving subtree out of closeSplitter()'s recursive bookkeeping.
|
||||
remainingNode->detach();
|
||||
closeSplitter( splitter );
|
||||
eeASSERT( parent->getChildCount() == 0 );
|
||||
remainingNode->setParent( parent );
|
||||
|
||||
@@ -17,7 +17,13 @@ UITabWidgetSplitter::UITabWidgetSplitter( UITabWidgetSplitter::Client* client,
|
||||
UISceneNode* sceneNode ) :
|
||||
mUISceneNode( sceneNode ), mClient( client ) {}
|
||||
|
||||
UITabWidgetSplitter::~UITabWidgetSplitter() {}
|
||||
UITabWidgetSplitter::~UITabWidgetSplitter() {
|
||||
mTabWidgetEventConnections.clear();
|
||||
mWidgetEventConnections.clear();
|
||||
mTabWidgets.clear();
|
||||
mCurWidget = nullptr;
|
||||
mClient = nullptr;
|
||||
}
|
||||
|
||||
UITabWidget* UITabWidgetSplitter::tabWidgetFromWidget( UIWidget* widget ) const {
|
||||
if ( widget )
|
||||
@@ -49,15 +55,15 @@ void UITabWidgetSplitter::setCurrentWidget( UIWidget* curWidget ) {
|
||||
std::pair<UITab*, UIWidget*>
|
||||
UITabWidgetSplitter::createWidgetInTabWidget( UITabWidget* tabWidget, UIWidget* widget,
|
||||
const std::string& tabName, bool focus ) {
|
||||
eeASSERT( curWidgetExists() );
|
||||
if ( nullptr == tabWidget )
|
||||
return std::make_pair( (UITab*)nullptr, (UIWidget*)nullptr );
|
||||
UITab* tab = tabWidget->add( tabName, widget );
|
||||
widget->setData( (UintPtr)tab );
|
||||
widget->on( Event::OnFocusWithin, [this]( const Event* event ) {
|
||||
auto& connections = mWidgetEventConnections[widget];
|
||||
connections += widget->connect( Event::OnFocusWithin, [this]( const Event* event ) {
|
||||
setCurrentWidget( event->getNode()->asType<UIWidget>() );
|
||||
} );
|
||||
widget->on( Event::OnTitleChange, [this]( const Event* event ) {
|
||||
connections += widget->connect( Event::OnTitleChange, [this]( const Event* event ) {
|
||||
const TextEvent* tevent = static_cast<const TextEvent*>( event );
|
||||
UIWidget* widget = event->getNode()->asType<UIWidget>();
|
||||
UITabWidget* tabWidget = tabWidgetFromWidget( widget );
|
||||
@@ -157,7 +163,8 @@ UITabWidget* UITabWidgetSplitter::createTabWidget( Node* parent ) {
|
||||
},
|
||||
mVisualSplitEdgePercent );
|
||||
}
|
||||
tabWidget->on( Event::OnTabSelected, [this]( const Event* event ) {
|
||||
auto& connections = mTabWidgetEventConnections[tabWidget];
|
||||
connections += tabWidget->connect( Event::OnTabSelected, [this]( const Event* event ) {
|
||||
UITabWidget* tabWidget = event->getNode()->asType<UITabWidget>();
|
||||
eeASSERT( nullptr != tabWidget && nullptr != tabWidget->getTabSelected() &&
|
||||
nullptr != tabWidget->getTabSelected()->getOwnedWidget() );
|
||||
@@ -175,7 +182,7 @@ UITabWidget* UITabWidgetSplitter::createTabWidget( Node* parent ) {
|
||||
}
|
||||
return false;
|
||||
} );
|
||||
tabWidget->on( Event::OnTabClosed, [this]( const Event* event ) {
|
||||
connections += tabWidget->connect( Event::OnTabClosed, [this]( const Event* event ) {
|
||||
onTabClosed( static_cast<const TabEvent*>( event ) );
|
||||
} );
|
||||
if ( mOnTabWidgetCreateCb )
|
||||
@@ -285,6 +292,7 @@ void UITabWidgetSplitter::closeTab( UIWidget* widget,
|
||||
UITabWidget::FocusTabBehavior focusTabBehavior ) {
|
||||
if ( widget ) {
|
||||
UITabWidget* tabWidget = tabWidgetFromWidget( widget );
|
||||
mWidgetEventConnections.erase( widget );
|
||||
if ( tabWidget )
|
||||
tabWidget->removeTab( (UITab*)widget->getData(), true, false, focusTabBehavior );
|
||||
if ( mCurWidget == widget )
|
||||
@@ -497,9 +505,12 @@ void UITabWidgetSplitter::closeTabWidgets( UISplitter* splitter ) {
|
||||
Node* node = splitter->getFirstChild();
|
||||
while ( node ) {
|
||||
if ( node->isType( UI_TYPE_TABWIDGET ) ) {
|
||||
auto it =
|
||||
std::find( mTabWidgets.begin(), mTabWidgets.end(), node->asType<UITabWidget>() );
|
||||
auto tabWidget = node->asType<UITabWidget>();
|
||||
auto it = std::find( mTabWidgets.begin(), mTabWidgets.end(), tabWidget );
|
||||
if ( it != mTabWidgets.end() ) {
|
||||
if ( mOnTabWidgetCloseCb )
|
||||
mOnTabWidgetCloseCb( tabWidget );
|
||||
mTabWidgetEventConnections.erase( tabWidget );
|
||||
Lock l( mTabWidgetMutex );
|
||||
mTabWidgets.erase( it );
|
||||
}
|
||||
@@ -622,6 +633,9 @@ void UITabWidgetSplitter::onTabClosed( const TabEvent* tabEvent ) {
|
||||
if ( tabWidget->getTabCount() == 0 ) {
|
||||
UISplitter* splitter = splitterFromWidget( widget );
|
||||
if ( splitter && splitter->isFull() ) {
|
||||
if ( mOnTabWidgetCloseCb )
|
||||
mOnTabWidgetCloseCb( tabWidget );
|
||||
mTabWidgetEventConnections.erase( tabWidget );
|
||||
tabWidget->close();
|
||||
auto itWidget = std::find( mTabWidgets.begin(), mTabWidgets.end(), tabWidget );
|
||||
if ( itWidget != mTabWidgets.end() ) {
|
||||
@@ -647,6 +661,7 @@ void UITabWidgetSplitter::onTabClosed( const TabEvent* tabEvent ) {
|
||||
Node* remainingNode = tabWidget == splitter->getFirstWidget()
|
||||
? splitter->getLastWidget()
|
||||
: splitter->getFirstWidget();
|
||||
remainingNode->detach();
|
||||
closeSplitter( splitter );
|
||||
eeASSERT( parent->getChildCount() == 0 );
|
||||
remainingNode->setParent( parent );
|
||||
@@ -676,6 +691,10 @@ void UITabWidgetSplitter::setOnTabWidgetCreateCb( std::function<void( UITabWidge
|
||||
mOnTabWidgetCreateCb = std::move( cb );
|
||||
}
|
||||
|
||||
void UITabWidgetSplitter::setOnTabWidgetCloseCb( std::function<void( UITabWidget* )> cb ) {
|
||||
mOnTabWidgetCloseCb = std::move( cb );
|
||||
}
|
||||
|
||||
bool UITabWidgetSplitter::getVisualSplitting() const {
|
||||
return mVisualSplitting;
|
||||
}
|
||||
|
||||
+40
-12
@@ -12,29 +12,33 @@ UISplitter::UISplitter() :
|
||||
UILayout( "splitter" ),
|
||||
mOrientation( UIOrientation::Horizontal ),
|
||||
mAlwaysShowSplitter( true ),
|
||||
mHideSplitterOnEdge( false ),
|
||||
mSplitPartition( StyleSheetLength( "50%" ) ),
|
||||
mFirstWidget( NULL ),
|
||||
mLastWidget( NULL ) {
|
||||
mFlags |= UI_OWNS_CHILDREN_POSITION;
|
||||
mSplitter = UIWidget::NewWithTag( "splitter::separator" );
|
||||
mSplitter->setDragEnabled( true );
|
||||
mSplitter->on( Event::OnDragStart,
|
||||
[this]( const Event* ) { mSplitter->pushState( UIState::StateSelected ); } );
|
||||
mSplitter->on( Event::OnDragStop,
|
||||
[this]( const Event* ) { mSplitter->popState( UIState::StateSelected ); } );
|
||||
mEventConnections += mSplitter->connect( Event::OnDragStart, [this]( const Event* ) {
|
||||
mSplitter->pushState( UIState::StateSelected );
|
||||
} );
|
||||
mEventConnections += mSplitter->connect( Event::OnDragStop, [this]( const Event* ) {
|
||||
mSplitter->popState( UIState::StateSelected );
|
||||
} );
|
||||
mSplitter->setParent( this );
|
||||
mSplitter->setMinWidth( 4 );
|
||||
mSplitter->setMinHeight( 4 );
|
||||
mSplitter->on( Event::OnSizeChange, [this]( const Event* ) {
|
||||
mEventConnections += mSplitter->connect( Event::OnSizeChange, [this]( const Event* ) {
|
||||
setLayoutDirty( LayoutInvalidation::ContainerLayout );
|
||||
} );
|
||||
mSplitter->on( Event::MouseEnter, [this]( const Event* ) {
|
||||
mEventConnections += mSplitter->connect( Event::MouseEnter, [this]( const Event* ) {
|
||||
getUISceneNode()->setCursor( mOrientation == UIOrientation::Horizontal ? Cursor::SizeWE
|
||||
: Cursor::SizeNS );
|
||||
} );
|
||||
mSplitter->on( Event::MouseLeave,
|
||||
[this]( const Event* ) { getUISceneNode()->setCursor( Cursor::Arrow ); } );
|
||||
mSplitter->on( Event::OnPositionChange, [this]( const Event* ) {
|
||||
mEventConnections += mSplitter->connect( Event::MouseLeave, [this]( const Event* ) {
|
||||
getUISceneNode()->setCursor( Cursor::Arrow );
|
||||
} );
|
||||
mEventConnections += mSplitter->connect( Event::OnPositionChange, [this]( const Event* ) {
|
||||
if ( mSplitter->isDragging() && !mDirtyLayout )
|
||||
updateFromDrag();
|
||||
} );
|
||||
@@ -77,6 +81,17 @@ void UISplitter::setAlwaysShowSplitter( bool alwaysShowSplitter ) {
|
||||
}
|
||||
}
|
||||
|
||||
const bool& UISplitter::hideSplitterOnEdge() const {
|
||||
return mHideSplitterOnEdge;
|
||||
}
|
||||
|
||||
void UISplitter::setHideSplitterOnEdge( bool hideSplitterOnEdge ) {
|
||||
if ( hideSplitterOnEdge != mHideSplitterOnEdge ) {
|
||||
mHideSplitterOnEdge = hideSplitterOnEdge;
|
||||
setLayoutDirty( LayoutInvalidation::ContainerLayout );
|
||||
}
|
||||
}
|
||||
|
||||
const StyleSheetLength& UISplitter::getSplitPartition() const {
|
||||
return mSplitPartition;
|
||||
}
|
||||
@@ -127,6 +142,9 @@ bool UISplitter::applyProperty( const StyleSheetProperty& attribute ) {
|
||||
case PropertyId::SplitterAlwaysShow:
|
||||
setAlwaysShowSplitter( attribute.asBool() );
|
||||
break;
|
||||
case PropertyId::SplitterHideOnEdge:
|
||||
setHideSplitterOnEdge( attribute.asBool() );
|
||||
break;
|
||||
case PropertyId::Orientation:
|
||||
setOrientation( String::iequals( attribute.getValue(), "horizontal" )
|
||||
? UIOrientation::Horizontal
|
||||
@@ -149,6 +167,8 @@ std::string UISplitter::getPropertyString( const PropertyDefinition* propertyDef
|
||||
return getSplitPartition().toString();
|
||||
case PropertyId::SplitterAlwaysShow:
|
||||
return alwaysShowSplitter() ? "true" : "false";
|
||||
case PropertyId::SplitterHideOnEdge:
|
||||
return hideSplitterOnEdge() ? "true" : "false";
|
||||
case PropertyId::Orientation:
|
||||
return getOrientation() == UIOrientation::Horizontal ? "horizontal" : "vertical";
|
||||
default:
|
||||
@@ -159,7 +179,7 @@ std::string UISplitter::getPropertyString( const PropertyDefinition* propertyDef
|
||||
std::vector<PropertyId> UISplitter::getPropertiesImplemented() const {
|
||||
auto props = UIWidget::getPropertiesImplemented();
|
||||
auto local = { PropertyId::SplitterPartition, PropertyId::SplitterAlwaysShow,
|
||||
PropertyId::Orientation };
|
||||
PropertyId::SplitterHideOnEdge, PropertyId::Orientation };
|
||||
props.insert( props.end(), local.begin(), local.end() );
|
||||
return props;
|
||||
}
|
||||
@@ -204,7 +224,7 @@ void UISplitter::onChildCountChange( Node* child, const bool& removed ) {
|
||||
|
||||
void UISplitter::updateFromDrag() {
|
||||
mDirtyLayout = true;
|
||||
mSplitter->setVisible( !mAlwaysShowSplitter && !mLastWidget ? false : true );
|
||||
mSplitter->setVisible( shouldShowSplitter() );
|
||||
mSplitter->setEnabled( mSplitter->isVisible() );
|
||||
|
||||
if ( UIOrientation::Horizontal == mOrientation ) {
|
||||
@@ -351,7 +371,7 @@ void UISplitter::updateLayout() {
|
||||
return;
|
||||
}
|
||||
|
||||
mSplitter->setVisible( !mAlwaysShowSplitter && !mLastWidget ? false : true );
|
||||
mSplitter->setVisible( shouldShowSplitter() );
|
||||
mSplitter->setEnabled( mSplitter->isVisible() );
|
||||
Float totalSpace = mOrientation == UIOrientation::Horizontal
|
||||
? mSize.getWidth() - mPaddingPx.Left - mPaddingPx.Right
|
||||
@@ -426,6 +446,14 @@ void UISplitter::updateSplitterDragFlags() {
|
||||
: UI_DRAG_HORIZONTAL );
|
||||
}
|
||||
|
||||
bool UISplitter::shouldShowSplitter() const {
|
||||
if ( !mLastWidget )
|
||||
return false;
|
||||
return mAlwaysShowSplitter || !mHideSplitterOnEdge ||
|
||||
mSplitPartition.getUnit() != StyleSheetLength::Percentage ||
|
||||
( mSplitPartition.getValue() > 0.f && mSplitPartition.getValue() < 100.f );
|
||||
}
|
||||
|
||||
Uint32 UISplitter::onMessage( const NodeMessage* Msg ) {
|
||||
switch ( Msg->getMsg() ) {
|
||||
case NodeMessage::LayoutAttributeChange: {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include <eepp/ui/abstract/uiabstracttableview.hpp>
|
||||
#include <eepp/ui/uimenuitem.hpp>
|
||||
#include <eepp/ui/uipopupmenu.hpp>
|
||||
#include <eepp/ui/uiscenenode.hpp>
|
||||
#include <eepp/ui/uitableheadercolumn.hpp>
|
||||
|
||||
@@ -31,7 +33,7 @@ Uint32 UITableHeaderColumn::onCalculateDrag( const Vector2f& position, const Uin
|
||||
}
|
||||
Vector2f pos( eefloor( position.x ), eefloor( position.y ) );
|
||||
if ( mDragPoint != pos && std::abs( mDragPoint.x - pos.x ) > 1.f ) {
|
||||
Sizef dragDiff( ( Float )( mDragPoint.x - pos.x ), 0 );
|
||||
Sizef dragDiff( (Float)( mDragPoint.x - pos.x ), 0 );
|
||||
if ( onDrag( pos, flags, dragDiff ) ) {
|
||||
mDragPoint = pos;
|
||||
eventDispatcher->setNodeDragging( this );
|
||||
@@ -77,6 +79,30 @@ Uint32 UITableHeaderColumn::onMouseClick( const Vector2i& position, const Uint32
|
||||
return UIPushButton::onMouseClick( position, flags );
|
||||
}
|
||||
|
||||
Uint32 UITableHeaderColumn::onMouseUp( const Vector2i& position, const Uint32& flags ) {
|
||||
if ( ( flags & EE_BUTTON_RMASK ) && mView->isColumnWidthModeMenuEnabled() ) {
|
||||
auto* menu = UIPopUpMenu::New();
|
||||
menu->addRadioButton( i18n( "uitable_fit_columns_to_view", "Fit Columns to View" ),
|
||||
mView->getColumnWidthMode() ==
|
||||
UIAbstractTableView::ColumnWidthMode::Percentage )
|
||||
->setId( "percentage" );
|
||||
menu->addRadioButton( i18n( "uitable_free_column_widths", "Free Column Widths" ),
|
||||
mView->getColumnWidthMode() ==
|
||||
UIAbstractTableView::ColumnWidthMode::Pixels )
|
||||
->setId( "pixels" );
|
||||
menu->on( Event::OnItemClicked, [view = mView]( const Event* event ) {
|
||||
if ( !event->getNode()->isType( UI_TYPE_MENUITEM ) )
|
||||
return;
|
||||
view->setColumnWidthMode( event->getNode()->getId() == "percentage"
|
||||
? UIAbstractTableView::ColumnWidthMode::Percentage
|
||||
: UIAbstractTableView::ColumnWidthMode::Pixels );
|
||||
} );
|
||||
menu->setCloseOnHide( true );
|
||||
menu->showAtScreenPosition( position.asFloat() );
|
||||
}
|
||||
return UIPushButton::onMouseUp( position, flags );
|
||||
}
|
||||
|
||||
Uint32 UITableHeaderColumn::onDrag( const Vector2f& position, const Uint32&,
|
||||
const Sizef& dragDiff ) {
|
||||
Vector2f localPos( convertToNodeSpace( position ) );
|
||||
|
||||
@@ -87,8 +87,10 @@ UITabWidget::UITabWidget() :
|
||||
mTabScroll = UIScrollBar::NewHorizontalWithTag( "scrollbarmini" );
|
||||
mTabScroll->setParent( mTabBar );
|
||||
mTabScroll->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent );
|
||||
mTabScroll->on( Event::OnSizeChange, [this]( const Event* ) { updateScrollBar(); } );
|
||||
mTabScroll->on( Event::OnValueChange, [this]( const Event* ) { updateScroll(); } );
|
||||
mEventConnections +=
|
||||
mTabScroll->connect( Event::OnSizeChange, [this]( const Event* ) { updateScrollBar(); } );
|
||||
mEventConnections +=
|
||||
mTabScroll->connect( Event::OnValueChange, [this]( const Event* ) { updateScroll(); } );
|
||||
|
||||
onSizeChange();
|
||||
|
||||
@@ -560,6 +562,10 @@ UITab* UITabWidget::add( const String& text, UINode* nodeOwned, DrawablePtr icon
|
||||
|
||||
UITabWidget* UITabWidget::add( UITab* tab ) {
|
||||
tab->setParent( mTabBar );
|
||||
if ( tab->getOwnedWidget() && tab->getOwnedWidget()->isWidget() )
|
||||
tab->getOwnedWidget()->asType<UIWidget>()->setLayoutSizePolicy( SizePolicy::Fixed,
|
||||
SizePolicy::Fixed );
|
||||
refreshOwnedWidget( tab );
|
||||
|
||||
mTabs.push_back( tab );
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <eepp/system/sys.hpp>
|
||||
#include <eepp/ui/tools/uitabwidgetsplitter.hpp>
|
||||
#include <eepp/ui/uiapplication.hpp>
|
||||
#include <eepp/ui/uiscrollbar.hpp>
|
||||
#include <eepp/ui/uitableview.hpp>
|
||||
#include <eepp/ui/uiwidget.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
@@ -21,6 +23,415 @@ class TestClient : public UITabWidgetSplitter::Client {
|
||||
void onWidgetFocusChange( UIWidget* ) override { focusChangeCount++; }
|
||||
};
|
||||
|
||||
class TransferTestTabWidget : public UITabWidget {
|
||||
public:
|
||||
static TransferTestTabWidget* New() { return eeNew( TransferTestTabWidget, () ); }
|
||||
|
||||
void detachTab( UITab* tab ) { removeTab( tab, false, false, false ); }
|
||||
|
||||
const Sizef& getContainerPixelsSize() const { return mNodeContainer->getPixelsSize(); }
|
||||
};
|
||||
|
||||
class ThreeColumnModel : public Model {
|
||||
public:
|
||||
explicit ThreeColumnModel( size_t rows = 1 ) : mRows( rows ) {}
|
||||
|
||||
size_t rowCount( const ModelIndex& = ModelIndex() ) const override { return mRows; }
|
||||
size_t columnCount( const ModelIndex& = ModelIndex() ) const override { return 3; }
|
||||
std::string columnName( const size_t& column ) const override {
|
||||
return String::format( "Column %zu", column );
|
||||
}
|
||||
ModelIndex index( int row, int column,
|
||||
const ModelIndex& parent = ModelIndex() ) const override {
|
||||
return row == 0 && column >= 0 && column < 3 && !parent.isValid()
|
||||
? createIndex( row, column )
|
||||
: ModelIndex{};
|
||||
}
|
||||
Variant data( const ModelIndex&, ModelRole = ModelRole::Display ) const override {
|
||||
return "Value";
|
||||
}
|
||||
|
||||
protected:
|
||||
size_t mRows;
|
||||
};
|
||||
|
||||
class PercentageTestTable : public UITableView {
|
||||
public:
|
||||
static PercentageTestTable* New() { return eeNew( PercentageTestTable, () ); }
|
||||
|
||||
void userResizeColumn( size_t column, Float width ) {
|
||||
columnData( column ).setWidth( width, true );
|
||||
onColumnSizeChange( column, true );
|
||||
}
|
||||
|
||||
void setColumnMinimumWidth( size_t column, Float width ) {
|
||||
columnData( column ).minWidth = width;
|
||||
createOrUpdateColumns( false );
|
||||
}
|
||||
|
||||
void resizeColumnToContent( size_t column, Float width ) {
|
||||
mContentWidth = width;
|
||||
onColumnResizeToContent( column );
|
||||
}
|
||||
|
||||
Float getMaxColumnContentWidth( const size_t&, bool ) override { return mContentWidth; }
|
||||
|
||||
protected:
|
||||
Float mContentWidth{ 0 };
|
||||
};
|
||||
|
||||
UTEST( UISplitter, HideSplitterOnEdge ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||
|
||||
auto* splitter = UISplitter::New();
|
||||
splitter->setParent( app.getUI() );
|
||||
splitter->setPixelsSize( 800, 600 );
|
||||
UIWidget::New()->setParent( splitter );
|
||||
UIWidget::New()->setParent( splitter );
|
||||
auto* separator = splitter->findByTag( "splitter::separator" );
|
||||
ASSERT_TRUE( separator != nullptr );
|
||||
|
||||
splitter->setHideSplitterOnEdge( true );
|
||||
splitter->setAlwaysShowSplitter( false );
|
||||
splitter->setSplitPartition( StyleSheetLength( "100%" ) );
|
||||
splitter->updateLayout();
|
||||
EXPECT_FALSE( separator->isVisible() );
|
||||
EXPECT_EQ( splitter->getFirstWidget()->getPixelsSize().getWidth(), 800.f );
|
||||
EXPECT_EQ( splitter->getLastWidget()->getPixelsSize().getWidth(), 0.f );
|
||||
|
||||
splitter->setSplitPartition( StyleSheetLength( "75%" ) );
|
||||
splitter->updateLayout();
|
||||
app.getUI()->update( Milliseconds( 16 ) );
|
||||
EXPECT_TRUE( separator->isVisible() );
|
||||
|
||||
splitter->setSplitPartition( StyleSheetLength( "0%" ) );
|
||||
splitter->updateLayout();
|
||||
EXPECT_FALSE( separator->isVisible() );
|
||||
EXPECT_EQ( splitter->getFirstWidget()->getPixelsSize().getWidth(), 0.f );
|
||||
EXPECT_EQ( splitter->getLastWidget()->getPixelsSize().getWidth(), 800.f );
|
||||
|
||||
splitter->setAlwaysShowSplitter( true );
|
||||
splitter->updateLayout();
|
||||
EXPECT_TRUE( separator->isVisible() );
|
||||
}
|
||||
|
||||
UTEST( UISplitter, ControlsMatchParentChildWidth ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||
|
||||
auto* splitter = UISplitter::New();
|
||||
splitter->setParent( app.getUI() );
|
||||
splitter->setPixelsSize( 800, 600 );
|
||||
auto* first = UIWidget::New();
|
||||
first->setParent( splitter );
|
||||
auto* last = UIWidget::New();
|
||||
last->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent );
|
||||
last->setParent( splitter );
|
||||
splitter->setSplitPartition( StyleSheetLength( "75%" ) );
|
||||
splitter->updateLayout();
|
||||
|
||||
EXPECT_TRUE( first->getPixelsSize().getWidth() > 590.f &&
|
||||
first->getPixelsSize().getWidth() < 600.f );
|
||||
EXPECT_TRUE( last->getPixelsSize().getWidth() > 190.f &&
|
||||
last->getPixelsSize().getWidth() < 200.f );
|
||||
}
|
||||
|
||||
UTEST( UIAbstractTableView, PercentageColumnWidthsScaleAndPreserveAdjacentTotal ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||
auto* table = PercentageTestTable::New();
|
||||
table->setParent( app.getUI() );
|
||||
table->setPixelsSize( 400, 200 );
|
||||
table->setModel( std::make_shared<ThreeColumnModel>() );
|
||||
table->setColumnWidth( 0, 100 );
|
||||
table->setColumnWidth( 1, 200 );
|
||||
table->setColumnWidth( 2, 100 );
|
||||
table->setColumnWidthMode( UIAbstractTableView::ColumnWidthMode::Percentage );
|
||||
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 0 ) - 25.f ) < 0.01f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 1 ) - 50.f ) < 0.01f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 2 ) - 25.f ) < 0.01f );
|
||||
|
||||
table->setPixelsSize( 800, 200 );
|
||||
Float contentWidth = table->getContentSpaceWidth();
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidth( 0 ) - contentWidth * 0.25f ) < 1.f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidth( 1 ) - contentWidth * 0.5f ) < 1.f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidth( 2 ) - contentWidth * 0.25f ) < 1.f );
|
||||
|
||||
table->userResizeColumn( 0, contentWidth * 0.4f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 0 ) - 40.f ) < 0.01f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 1 ) - 35.f ) < 0.01f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 2 ) - 25.f ) < 0.01f );
|
||||
|
||||
table->setColumnWidthPercentage( 0, 70.f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 0 ) - 70.f ) < 0.01f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 1 ) - 5.f ) < 0.01f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 2 ) - 25.f ) < 0.01f );
|
||||
EXPECT_TRUE( table->getColumnWidth( 0 ) >= 0 && table->getColumnWidth( 1 ) >= 0 &&
|
||||
table->getColumnWidth( 2 ) >= 0 );
|
||||
}
|
||||
|
||||
UTEST( UIAbstractTableView, PercentageColumnWidthsPreserveMinimumWidths ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||
auto* table = PercentageTestTable::New();
|
||||
table->setParent( app.getUI() );
|
||||
table->setPixelsSize( 150, 200 );
|
||||
table->setModel( std::make_shared<ThreeColumnModel>() );
|
||||
table->setColumnsWidthPercentage( { 25.f, 50.f, 25.f } );
|
||||
table->setColumnMinimumWidth( 0, 80.f );
|
||||
table->setColumnMinimumWidth( 1, 80.f );
|
||||
table->setColumnMinimumWidth( 2, 80.f );
|
||||
app.getUI()->update( Milliseconds( 16 ) );
|
||||
|
||||
EXPECT_TRUE( table->getColumnWidth( 0 ) >= 80.f );
|
||||
EXPECT_TRUE( table->getColumnWidth( 1 ) >= 80.f );
|
||||
EXPECT_TRUE( table->getColumnWidth( 2 ) >= 80.f );
|
||||
EXPECT_TRUE( table->getColumnWidth( 0 ) + table->getColumnWidth( 1 ) +
|
||||
table->getColumnWidth( 2 ) >
|
||||
table->getContentSpaceWidth() );
|
||||
EXPECT_TRUE( table->getHorizontalScrollBar()->isVisible() );
|
||||
}
|
||||
|
||||
UTEST( UIAbstractTableView, PercentageColumnWidthsAcceptPartialAndSurplusValues ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||
auto* table = PercentageTestTable::New();
|
||||
table->setParent( app.getUI() );
|
||||
table->setPixelsSize( 400, 200 );
|
||||
table->setModel( std::make_shared<ThreeColumnModel>() );
|
||||
|
||||
table->setColumnsWidthPercentage( { 20.f, 30.f } );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 0 ) - 20.f ) < 0.01f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 1 ) - 30.f ) < 0.01f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 2 ) - 50.f ) < 0.01f );
|
||||
|
||||
table->setColumnsWidthPercentage( { 10.f, 20.f, 70.f, 500.f } );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 0 ) - 10.f ) < 0.01f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 1 ) - 20.f ) < 0.01f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 2 ) - 70.f ) < 0.01f );
|
||||
|
||||
EXPECT_TRUE( table->unserializeColumnWidths( nlohmann::json{ 25.f, 25.f } ) );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 0 ) - 25.f ) < 0.01f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 1 ) - 25.f ) < 0.01f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 2 ) - 50.f ) < 0.01f );
|
||||
}
|
||||
|
||||
UTEST( UIAbstractTableView, PercentageColumnResizeToContentPreservesAdjacentTotal ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||
auto* table = PercentageTestTable::New();
|
||||
table->setParent( app.getUI() );
|
||||
table->setPixelsSize( 400, 200 );
|
||||
table->setModel( std::make_shared<ThreeColumnModel>() );
|
||||
table->setColumnsWidthPercentage( { 25.f, 50.f, 25.f } );
|
||||
const Float contentWidth = table->getContentSpaceWidth();
|
||||
|
||||
table->resizeColumnToContent( 0, contentWidth * 0.4f );
|
||||
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 0 ) - 40.f ) < 0.01f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 1 ) - 35.f ) < 0.01f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 2 ) - 25.f ) < 0.01f );
|
||||
|
||||
table->setColumnsWidthPercentage( { 25.f, 50.f, 25.f } );
|
||||
table->setColumnMinimumWidth( 1, contentWidth * 0.3f );
|
||||
table->resizeColumnToContent( 0, contentWidth * 0.6f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 0 ) - 45.f ) < 0.01f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 1 ) - 30.f ) < 0.01f );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 2 ) - 25.f ) < 0.01f );
|
||||
}
|
||||
|
||||
UTEST( UIAbstractTableView, ColumnWidthModeAndMenuCanBeConfiguredFromXML ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||
auto* defaultTable =
|
||||
app.getUI()->loadLayoutFromString( R"xml(<TableView />)xml" )->asType<UITableView>();
|
||||
EXPECT_FALSE( defaultTable->isColumnWidthModeMenuEnabled() );
|
||||
auto* table = app.getUI()
|
||||
->loadLayoutFromString( R"xml(<TableView column-width-mode="percentage"
|
||||
column-width-mode-menu="true" />)xml" )
|
||||
->asType<UITableView>();
|
||||
EXPECT_TRUE( table->getColumnWidthMode() == UIAbstractTableView::ColumnWidthMode::Percentage );
|
||||
EXPECT_TRUE( table->isColumnWidthModeMenuEnabled() );
|
||||
auto* cssRoot = app.getUI()->loadLayoutFromString( R"xml(
|
||||
<vbox>
|
||||
<style>#css_table { column-width-mode-menu: true; }</style>
|
||||
<TableView id="css_table" />
|
||||
</vbox>)xml" );
|
||||
auto* cssTable = cssRoot->querySelector( "#css_table" )->asType<UITableView>();
|
||||
EXPECT_TRUE( cssTable->isColumnWidthModeMenuEnabled() );
|
||||
}
|
||||
|
||||
UTEST( UIAbstractTableView, PercentageRoundingDoesNotCreatePhantomHorizontalScroll ) {
|
||||
Float previousDensity = PixelDensity::getPixelDensity();
|
||||
PixelDensity::setPixelDensity( 1.5f );
|
||||
UIApplication app(
|
||||
WindowSettings( 828, 300, "eepp - unit tests" ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||
auto* table = PercentageTestTable::New();
|
||||
table->setParent( app.getUI() );
|
||||
table->setPixelsSize( 828, 300 );
|
||||
table->setModel( std::make_shared<ThreeColumnModel>() );
|
||||
table->setColumnsWidthPercentage( { 100.f / 3.f, 100.f / 3.f, 100.f / 3.f } );
|
||||
app.getUI()->update( Milliseconds( 16 ) );
|
||||
|
||||
EXPECT_EQ( table->getColumnWidth( 0 ) + table->getColumnWidth( 1 ) + table->getColumnWidth( 2 ),
|
||||
table->getContentSpaceWidth() );
|
||||
EXPECT_FALSE( table->getHorizontalScrollBar()->isVisible() );
|
||||
|
||||
table->setColumnsWidthPercentage( { 25.f, 50.f, 25.f } );
|
||||
table->setColumnMinimumWidth( 2, 208.f );
|
||||
app.getUI()->update( Milliseconds( 16 ) );
|
||||
EXPECT_EQ( table->getColumnWidth( 0 ) + table->getColumnWidth( 1 ) + table->getColumnWidth( 2 ),
|
||||
table->getContentSpaceWidth() );
|
||||
EXPECT_TRUE( table->getColumnWidth( 2 ) >= 208.f );
|
||||
EXPECT_FALSE( table->getHorizontalScrollBar()->isVisible() );
|
||||
PixelDensity::setPixelDensity( previousDensity );
|
||||
}
|
||||
|
||||
UTEST( UIAbstractTableView, PercentageColumnsRecomputeWhenVerticalScrollbarAppears ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||
auto* table = PercentageTestTable::New();
|
||||
table->setParent( app.getUI() );
|
||||
table->setPixelsSize( 400, 100 );
|
||||
table->setModel( std::make_shared<ThreeColumnModel>( 100 ) );
|
||||
table->setColumnsWidthPercentage( { 25.f, 50.f, 25.f } );
|
||||
app.getUI()->update( Milliseconds( 16 ) );
|
||||
|
||||
EXPECT_TRUE( table->getVerticalScrollBar()->isVisible() );
|
||||
EXPECT_EQ( table->getColumnWidth( 0 ) + table->getColumnWidth( 1 ) + table->getColumnWidth( 2 ),
|
||||
table->getContentSpaceWidth() );
|
||||
EXPECT_FALSE( table->getHorizontalScrollBar()->isVisible() );
|
||||
}
|
||||
|
||||
UTEST( UIAbstractTableView, AutoPixelColumnsRecomputeWhenVerticalScrollbarAppears ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||
auto* table = PercentageTestTable::New();
|
||||
table->setParent( app.getUI() );
|
||||
table->setPixelsSize( 400, 100 );
|
||||
table->setModel( std::make_shared<ThreeColumnModel>( 100 ) );
|
||||
table->setAutoColumnsWidth( true );
|
||||
app.getUI()->update( Milliseconds( 16 ) );
|
||||
|
||||
EXPECT_TRUE( table->getColumnWidthMode() == UIAbstractTableView::ColumnWidthMode::Pixels );
|
||||
EXPECT_TRUE( table->getVerticalScrollBar()->isVisible() );
|
||||
EXPECT_FALSE( table->getHorizontalScrollBar()->isVisible() );
|
||||
}
|
||||
|
||||
UTEST( UIAbstractTableView, ColumnWidthsSerializationRoundTripsBothModes ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||
auto* table = PercentageTestTable::New();
|
||||
table->setParent( app.getUI() );
|
||||
table->setPixelsSize( 400, 200 );
|
||||
table->setModel( std::make_shared<ThreeColumnModel>() );
|
||||
table->setColumnsWidthPercentage( { 20.f, 30.f, 50.f } );
|
||||
auto percentageWidths = table->serializeColumnWidths();
|
||||
table->setColumnWidthMode( UIAbstractTableView::ColumnWidthMode::Pixels );
|
||||
EXPECT_TRUE( table->unserializeColumnWidths( percentageWidths ) );
|
||||
EXPECT_TRUE( table->getColumnWidthMode() == UIAbstractTableView::ColumnWidthMode::Percentage );
|
||||
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 0 ) - 20.f ) < 0.01f );
|
||||
|
||||
table->setColumnWidthMode( UIAbstractTableView::ColumnWidthMode::Pixels );
|
||||
table->setColumnWidth( 0, PixelDensity::dpToPx( 80.f ) );
|
||||
table->setColumnWidth( 1, PixelDensity::dpToPx( 120.f ) );
|
||||
table->setColumnWidth( 2, PixelDensity::dpToPx( 160.f ) );
|
||||
auto pixelWidths = table->serializeColumnWidths();
|
||||
table->setColumnsWidthPercentage( { 25.f, 50.f, 25.f } );
|
||||
EXPECT_TRUE( table->unserializeColumnWidths( pixelWidths ) );
|
||||
EXPECT_TRUE( table->getColumnWidthMode() == UIAbstractTableView::ColumnWidthMode::Pixels );
|
||||
EXPECT_EQ( table->getColumnWidth( 0 ), PixelDensity::dpToPx( 80.f ) );
|
||||
EXPECT_EQ( table->getColumnWidth( 1 ), PixelDensity::dpToPx( 120.f ) );
|
||||
EXPECT_EQ( table->getColumnWidth( 2 ), PixelDensity::dpToPx( 160.f ) );
|
||||
|
||||
EXPECT_TRUE( table->unserializeColumnWidths( nlohmann::json{ 25.f, 50.f, 25.f } ) );
|
||||
EXPECT_TRUE( table->getColumnWidthMode() == UIAbstractTableView::ColumnWidthMode::Percentage );
|
||||
|
||||
auto* deferredTable = PercentageTestTable::New();
|
||||
deferredTable->setParent( app.getUI() );
|
||||
deferredTable->setPixelsSize( 400, 200 );
|
||||
EXPECT_TRUE( deferredTable->unserializeColumnWidths( percentageWidths ) );
|
||||
EXPECT_TRUE( deferredTable->serializeColumnWidths() == percentageWidths );
|
||||
deferredTable->setModel( std::make_shared<ThreeColumnModel>() );
|
||||
EXPECT_TRUE( deferredTable->getColumnWidthMode() ==
|
||||
UIAbstractTableView::ColumnWidthMode::Percentage );
|
||||
EXPECT_TRUE( std::abs( deferredTable->getColumnWidthPercentage( 0 ) - 20.f ) < 0.01f );
|
||||
}
|
||||
|
||||
UTEST( UITabWidget, TransferTabPreservesDestinationTabsAndOwnedWidgets ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||
|
||||
auto* source = TransferTestTabWidget::New();
|
||||
source->setParent( app.getUI() );
|
||||
auto* destination = TransferTestTabWidget::New();
|
||||
destination->setParent( app.getUI() );
|
||||
destination->setPixelsSize( 240, 180 );
|
||||
auto* sourceWidget = UIWidget::New();
|
||||
auto* destinationWidgetA = UIWidget::New();
|
||||
auto* destinationWidgetB = UIWidget::New();
|
||||
auto* movedTab = source->add( "Moved", sourceWidget );
|
||||
destination->add( "Existing A", destinationWidgetA );
|
||||
destination->add( "Existing B", destinationWidgetB );
|
||||
|
||||
source->detachTab( movedTab );
|
||||
destination->add( movedTab );
|
||||
destination->setTabSelected( movedTab );
|
||||
|
||||
EXPECT_EQ( source->getTabCount(), Uint32{ 0 } );
|
||||
EXPECT_EQ( destination->getTabCount(), Uint32{ 3 } );
|
||||
EXPECT_TRUE( destination->getTab( 0 )->getOwnedWidget() == destinationWidgetA );
|
||||
EXPECT_TRUE( destination->getTab( 1 )->getOwnedWidget() == destinationWidgetB );
|
||||
EXPECT_TRUE( destination->getTab( 2 )->getOwnedWidget() == sourceWidget );
|
||||
EXPECT_TRUE( destination->isParentOf( sourceWidget ) );
|
||||
EXPECT_TRUE( destination->isParentOf( destinationWidgetA ) );
|
||||
EXPECT_TRUE( destination->isParentOf( destinationWidgetB ) );
|
||||
EXPECT_TRUE( sourceWidget->getPixelsSize() == destination->getContainerPixelsSize() );
|
||||
EXPECT_TRUE( sourceWidget->isVisible() );
|
||||
EXPECT_FALSE( destinationWidgetA->isVisible() );
|
||||
EXPECT_FALSE( destinationWidgetB->isVisible() );
|
||||
}
|
||||
|
||||
UTEST( UITabWidgetSplitter, ClosingSplitNotifiesBeforeTabWidgetDestruction ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||
TestClient client;
|
||||
auto* splitter = UITabWidgetSplitter::New( &client, app.getUI() );
|
||||
auto* container = UILayout::New();
|
||||
container->setParent( app.getUI() );
|
||||
auto* firstTabWidget = splitter->createTabWidget( container );
|
||||
auto* firstWidget = UIWidget::New();
|
||||
splitter->createWidgetInTabWidget( firstTabWidget, firstWidget, "First" );
|
||||
auto* secondTabWidget = splitter->splitTabWidget( SplitDirection::Right, firstTabWidget );
|
||||
auto* secondWidget = UIWidget::New();
|
||||
splitter->createWidgetInTabWidget( secondTabWidget, secondWidget, "Second" );
|
||||
UITabWidget* closedTabWidget = nullptr;
|
||||
splitter->setOnTabWidgetCloseCb(
|
||||
[&closedTabWidget]( UITabWidget* tabWidget ) { closedTabWidget = tabWidget; } );
|
||||
|
||||
splitter->closeTab( secondWidget, UITabWidget::FocusTabBehavior::Default );
|
||||
app.getUI()->update( Milliseconds( 16 ) );
|
||||
|
||||
EXPECT_EQ( closedTabWidget, secondTabWidget );
|
||||
EXPECT_EQ( splitter->getTabWidgets().size(), 1UL );
|
||||
eeDelete( splitter );
|
||||
}
|
||||
|
||||
UTEST( UITabWidgetSplitter, Serialization ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||
@@ -34,15 +445,14 @@ UTEST( UITabWidgetSplitter, Serialization ) {
|
||||
auto* tabWidget = splitter->createTabWidget( (Node*)app.getUI() );
|
||||
|
||||
bool saved = false;
|
||||
splitter->registerWidgetType(
|
||||
"testwidget",
|
||||
{ [&]( UIWidget* ) {
|
||||
saved = true;
|
||||
return nlohmann::json{ { "custom_key", 42 } };
|
||||
},
|
||||
[]( const nlohmann::json& ) -> WidgetLoadResult {
|
||||
return { nullptr, nullptr, "" };
|
||||
} } );
|
||||
splitter->registerWidgetType( "testwidget",
|
||||
{ [&]( UIWidget* ) {
|
||||
saved = true;
|
||||
return nlohmann::json{ { "custom_key", 42 } };
|
||||
},
|
||||
[]( const nlohmann::json& ) -> WidgetLoadResult {
|
||||
return { nullptr, nullptr, "" };
|
||||
} } );
|
||||
|
||||
auto* w = UIWidget::New();
|
||||
w->addClass( "testwidget" );
|
||||
@@ -93,9 +503,8 @@ UTEST( UITabWidgetSplitter, Serialization ) {
|
||||
splitter->createWidgetInTabWidget( tabWidget, ghost, "Ghost" );
|
||||
|
||||
splitter->registerWidgetType(
|
||||
"known",
|
||||
{ []( UIWidget* ) { return nlohmann::json{ { "val", 1 } }; },
|
||||
[]( const nlohmann::json& ) -> WidgetLoadResult { return {}; } } );
|
||||
"known", { []( UIWidget* ) { return nlohmann::json{ { "val", 1 } }; },
|
||||
[]( const nlohmann::json& ) -> WidgetLoadResult { return {}; } } );
|
||||
|
||||
auto* reg = UIWidget::New();
|
||||
reg->addClass( "known" );
|
||||
@@ -117,13 +526,12 @@ UTEST( UITabWidgetSplitter, Serialization ) {
|
||||
auto* tabWidget = splitter->createTabWidget( (Node*)app.getUI() );
|
||||
|
||||
splitter->registerWidgetType(
|
||||
"panewidget",
|
||||
{ []( UIWidget* ) { return nlohmann::json{ { "pane", true } }; },
|
||||
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
||||
auto* w = UIWidget::New();
|
||||
w->addClass( "panewidget" );
|
||||
return { w, nullptr, j.value( "title", "" ) };
|
||||
} } );
|
||||
"panewidget", { []( UIWidget* ) { return nlohmann::json{ { "pane", true } }; },
|
||||
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
||||
auto* w = UIWidget::New();
|
||||
w->addClass( "panewidget" );
|
||||
return { w, nullptr, j.value( "title", "" ) };
|
||||
} } );
|
||||
|
||||
auto* w1 = UIWidget::New();
|
||||
w1->addClass( "panewidget" );
|
||||
@@ -163,13 +571,12 @@ UTEST( UITabWidgetSplitter, Serialization ) {
|
||||
auto* tab1 = splitter1->createTabWidget( (Node*)app.getUI() );
|
||||
|
||||
splitter1->registerWidgetType(
|
||||
"mywidget",
|
||||
{ []( UIWidget* ) { return nlohmann::json{ { "data", "hello" } }; },
|
||||
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
||||
auto* w = UIWidget::New();
|
||||
w->addClass( "mywidget" );
|
||||
return { w, nullptr, j.value( "title", "" ) };
|
||||
} } );
|
||||
"mywidget", { []( UIWidget* ) { return nlohmann::json{ { "data", "hello" } }; },
|
||||
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
||||
auto* w = UIWidget::New();
|
||||
w->addClass( "mywidget" );
|
||||
return { w, nullptr, j.value( "title", "" ) };
|
||||
} } );
|
||||
|
||||
auto* wa = UIWidget::New();
|
||||
wa->addClass( "mywidget" );
|
||||
@@ -186,14 +593,13 @@ UTEST( UITabWidgetSplitter, Serialization ) {
|
||||
auto* splitter2 = UITabWidgetSplitter::New( &client2, app.getUI() );
|
||||
auto* tab2 = splitter2->createTabWidget( (Node*)app.getUI() );
|
||||
|
||||
splitter2->registerWidgetType(
|
||||
"mywidget",
|
||||
{ []( UIWidget* ) { return nlohmann::json::object(); },
|
||||
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
||||
auto* w = UIWidget::New();
|
||||
w->addClass( "mywidget" );
|
||||
return { w, nullptr, j.value( "title", "" ) };
|
||||
} } );
|
||||
splitter2->registerWidgetType( "mywidget",
|
||||
{ []( UIWidget* ) { return nlohmann::json::object(); },
|
||||
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
||||
auto* w = UIWidget::New();
|
||||
w->addClass( "mywidget" );
|
||||
return { w, nullptr, j.value( "title", "" ) };
|
||||
} } );
|
||||
|
||||
splitter2->fromJSON( saved );
|
||||
|
||||
@@ -256,14 +662,13 @@ UTEST( UITabWidgetSplitter, Serialization ) {
|
||||
auto* splitter1 = UITabWidgetSplitter::New( &client1, app.getUI() );
|
||||
auto* tab1 = splitter1->createTabWidget( (Node*)app.getUI() );
|
||||
|
||||
splitter1->registerWidgetType(
|
||||
"partwidget",
|
||||
{ []( UIWidget* ) { return nlohmann::json::object(); },
|
||||
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
||||
auto* w = UIWidget::New();
|
||||
w->addClass( "partwidget" );
|
||||
return { w, nullptr, j.value( "title", "" ) };
|
||||
} } );
|
||||
splitter1->registerWidgetType( "partwidget",
|
||||
{ []( UIWidget* ) { return nlohmann::json::object(); },
|
||||
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
||||
auto* w = UIWidget::New();
|
||||
w->addClass( "partwidget" );
|
||||
return { w, nullptr, j.value( "title", "" ) };
|
||||
} } );
|
||||
|
||||
auto* w1 = UIWidget::New();
|
||||
w1->addClass( "partwidget" );
|
||||
@@ -287,14 +692,13 @@ UTEST( UITabWidgetSplitter, Serialization ) {
|
||||
auto* splitter2 = UITabWidgetSplitter::New( &client2, app.getUI() );
|
||||
splitter2->createTabWidget( (Node*)app.getUI() );
|
||||
|
||||
splitter2->registerWidgetType(
|
||||
"partwidget",
|
||||
{ []( UIWidget* ) { return nlohmann::json::object(); },
|
||||
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
||||
auto* w = UIWidget::New();
|
||||
w->addClass( "partwidget" );
|
||||
return { w, nullptr, j.value( "title", "" ) };
|
||||
} } );
|
||||
splitter2->registerWidgetType( "partwidget",
|
||||
{ []( UIWidget* ) { return nlohmann::json::object(); },
|
||||
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
||||
auto* w = UIWidget::New();
|
||||
w->addClass( "partwidget" );
|
||||
return { w, nullptr, j.value( "title", "" ) };
|
||||
} } );
|
||||
|
||||
splitter2->fromJSON( saved );
|
||||
|
||||
|
||||
@@ -121,6 +121,7 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath,
|
||||
windowState.winIcon = ini.getValue( "window", "winicon", resPath + "icon/ecode.png" );
|
||||
windowState.panelPartition = iniState.getValue( "window", "panel_partition", "15%" );
|
||||
windowState.statusBarPartition = iniState.getValue( "window", "status_bar_partition", "85%" );
|
||||
windowState.rightPanelPartition = iniState.getValue( "window", "right_panel_partition", "75%" );
|
||||
windowState.displayIndex = iniState.getValueI( "window", "display_index", 0 );
|
||||
windowState.position.x = iniState.getValueI( "window", "x", -1 );
|
||||
windowState.position.y = iniState.getValueI( "window", "y", -1 );
|
||||
@@ -331,6 +332,7 @@ void AppConfig::save( const std::vector<std::string>& recentFiles,
|
||||
iniState.setValueF( "window", "pixeldensity", windowState.pixelDensity );
|
||||
iniState.setValue( "window", "panel_partition", panelPartition );
|
||||
iniState.setValue( "window", "status_bar_partition", statusBarPartition );
|
||||
iniState.setValue( "window", "right_panel_partition", windowState.rightPanelPartition );
|
||||
iniState.setValueI( "window", "display_index", windowState.displayIndex );
|
||||
iniState.setValueI( "window", "x", windowState.position.x );
|
||||
iniState.setValueI( "window", "y", windowState.position.y );
|
||||
|
||||
@@ -90,6 +90,7 @@ struct WindowStateConfig {
|
||||
bool maximized{ false };
|
||||
std::string panelPartition;
|
||||
std::string statusBarPartition;
|
||||
std::string rightPanelPartition{ "75%" };
|
||||
int displayIndex{ 0 };
|
||||
Vector2i position{ -1, -1 };
|
||||
Uint32 lastRunVersion{ 0 };
|
||||
|
||||
@@ -615,6 +615,7 @@ TabWidget::container > ImageViewer > TextView {
|
||||
</RelativeLayout>
|
||||
<Tab id="treeview_tab" text='@string("project", "Project")' owns="project_view_cont" text-as-fallback="true" icon="icon(folder-open, 12dp)" />
|
||||
</TabWidget>
|
||||
<Splitter id="right_panel_splitter" splitter-partition="100%" splitter-always-show="false" splitter-hide-on-edge="true">
|
||||
<vbox>
|
||||
<Splitter id="main_splitter" lw="mp" lh="0" lw8="1" orientation="vertical">
|
||||
<RelativeLayout id="main_splitter_cont">
|
||||
@@ -706,6 +707,8 @@ TabWidget::container > ImageViewer > TextView {
|
||||
<Widget class="status_sep" lw="0" lw8="1" lh="1dp" />
|
||||
</statusbar>
|
||||
</vbox>
|
||||
<RelativeLayout id="right_panel_container"></RelativeLayout>
|
||||
</Splitter>
|
||||
</Splitter>
|
||||
<Image id="settings" lw="16dp" lh="16dp" lg="top|right" gravity="center" />
|
||||
<TextView id="menu_hint" class="app_hint" lw="wc" lh="wc" lg="bottom|right" visible="false"
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
#include "fontpickercontroller.hpp"
|
||||
#include "keybindingshelper.hpp"
|
||||
#include "pathhelper.hpp"
|
||||
#include "plugins/debugger/statusdebuggercontroller.hpp"
|
||||
#include "settingsactions.hpp"
|
||||
#include "settingsmenu.hpp"
|
||||
#include "uibuildsettings.hpp"
|
||||
#include "uidownloadwindow.hpp"
|
||||
#include "uirightpanel.hpp"
|
||||
#include "uitreeviewfs.hpp"
|
||||
#include "uiwelcomescreen.hpp"
|
||||
#include "version.hpp"
|
||||
@@ -860,6 +862,8 @@ bool App::loadConfig( const LogLevel& logLevel, const Sizeu& displaySize, bool s
|
||||
void App::saveConfig() {
|
||||
if ( !mCurrentProject.empty() )
|
||||
saveSidePanelTabsOrder();
|
||||
if ( mRightPanel )
|
||||
mRightPanel->saveState();
|
||||
|
||||
mConfig.save(
|
||||
mRecentFiles, mRecentFolders,
|
||||
@@ -3810,6 +3814,17 @@ UISplitter* App::getMainSplitter() const {
|
||||
return mMainSplitter;
|
||||
}
|
||||
|
||||
UIRightPanel* App::getRightPanel() const {
|
||||
return mRightPanel.get();
|
||||
}
|
||||
|
||||
StatusDebuggerController* App::getStatusDebuggerController() const {
|
||||
if ( !mStatusBar )
|
||||
return nullptr;
|
||||
auto element = mStatusBar->getStatusBarElement( "status_app_debugger" );
|
||||
return static_cast<StatusDebuggerController*>( element.get() );
|
||||
}
|
||||
|
||||
StatusTerminalController* App::getStatusTerminalController() const {
|
||||
return mStatusTerminalController.get();
|
||||
}
|
||||
@@ -4979,6 +4994,8 @@ void App::init( InitParameters& params ) {
|
||||
mSplitter->setOpenDocumentsInMainSplit( mConfig.editor.openDocumentsInMainSplit );
|
||||
mSplitter->setRestoreEditorSelectionOnFocus( mConfig.editor.restoreEditorSelectionOnFocus );
|
||||
mSplitter->setOnTabWidgetCreateCb( [this]( UITabWidget* tabWidget ) {
|
||||
tabWidget->setAcceptsDropOfWidgetFn(
|
||||
[]( const UIWidget* widget ) { return !widget->hasClass( "debugger-tab" ); } );
|
||||
tabWidget->getTabBar()->onDoubleClick(
|
||||
[this]( const MouseEvent* ) { mSplitter->createEditorInNewTab(); } );
|
||||
} );
|
||||
@@ -5032,6 +5049,10 @@ void App::init( InitParameters& params ) {
|
||||
mMainSplitter = mUISceneNode->find<UISplitter>( "main_splitter" );
|
||||
mMainSplitter->setSplitPartition(
|
||||
StyleSheetLength( mConfig.windowState.statusBarPartition ) );
|
||||
auto rightPanelSplitter = mUISceneNode->find<UISplitter>( "right_panel_splitter" );
|
||||
auto rightPanelContainer = mUISceneNode->find<UILayout>( "right_panel_container" );
|
||||
mRightPanel =
|
||||
std::make_unique<UIRightPanel>( rightPanelSplitter, rightPanelContainer, &mConfig );
|
||||
mStatusBar = mUISceneNode->find<UIStatusBar>( "status_bar" );
|
||||
mPluginManager->setMainSplitter( mMainSplitter );
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ class DateTimeController;
|
||||
class FontPickerController;
|
||||
class SettingsMenu;
|
||||
class UITreeViewFS;
|
||||
class StatusDebuggerController;
|
||||
class UIRightPanel;
|
||||
|
||||
class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
|
||||
public:
|
||||
@@ -599,6 +601,10 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
|
||||
|
||||
UISplitter* getMainSplitter() const;
|
||||
|
||||
UIRightPanel* getRightPanel() const;
|
||||
|
||||
StatusDebuggerController* getStatusDebuggerController() const;
|
||||
|
||||
StatusTerminalController* getStatusTerminalController() const;
|
||||
|
||||
void hideStatusTerminal();
|
||||
@@ -777,6 +783,7 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
|
||||
UIThemePtr mTheme;
|
||||
UIStatusBar* mStatusBar{ nullptr };
|
||||
UISplitter* mMainSplitter{ nullptr };
|
||||
std::unique_ptr<UIRightPanel> mRightPanel;
|
||||
UIMessageBox* mCloseMsgBox{ nullptr };
|
||||
UIMenuBar* mMenuBar{ nullptr };
|
||||
std::unique_ptr<SettingsActions> mSettingsActions;
|
||||
|
||||
@@ -140,8 +140,10 @@ void DebuggerClientListener::initUI() {
|
||||
}
|
||||
|
||||
if ( !mStackModel ) {
|
||||
mStackModel = std::make_shared<StackModel>( StackTraceInfo{}, sceneNode );
|
||||
}
|
||||
mStackModel =
|
||||
std::make_shared<StackModel>( StackTraceInfo{}, sceneNode, mPlugin->mProjectPath );
|
||||
} else
|
||||
mStackModel->setProjectPath( mPlugin->mProjectPath );
|
||||
|
||||
UITableView* uiThreads = sdc->getUIThreads();
|
||||
uiThreads->setModel( mThreadsModel );
|
||||
|
||||
@@ -183,6 +183,11 @@ DebuggerPlugin::~DebuggerPlugin() {
|
||||
}
|
||||
}
|
||||
|
||||
void DebuggerPlugin::onSaveState( IniFile* state ) {
|
||||
if ( auto controller = getStatusDebuggerController() )
|
||||
state->setValue( "debugger", "panel_layout", controller->saveLayout() );
|
||||
}
|
||||
|
||||
void DebuggerPlugin::onSaveProject( const std::string& /*projectFolder*/,
|
||||
const std::string& projectStatePath,
|
||||
bool rewriteStateOnlyIfNeeded ) {
|
||||
@@ -579,11 +584,11 @@ void DebuggerPlugin::loadDAPConfig( const std::string& path, bool updateConfigFi
|
||||
mKeyBindings["debugger-step-over"] = "f10";
|
||||
mKeyBindings["debugger-step-into"] = "f11";
|
||||
mKeyBindings["debugger-step-out"] = "shift+f11";
|
||||
#if EE_PLATFORM == EE_PLATFORM_MACOS
|
||||
#if EE_PLATFORM == EE_PLATFORM_MACOS
|
||||
mKeyBindings["toggle-status-app-debugger"] = "mod+6";
|
||||
#else
|
||||
#else
|
||||
mKeyBindings["toggle-status-app-debugger"] = "alt+6";
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
if ( j.contains( "keybindings" ) ) {
|
||||
@@ -768,8 +773,9 @@ void DebuggerPlugin::buildSidePanelTab() {
|
||||
if ( mProjectPath.empty() )
|
||||
return;
|
||||
UIIcon* icon = findIcon( "debug" );
|
||||
mTab = mSidePanel->add( i18n( "debugger", "Debugger" ), mTabContents,
|
||||
icon ? icon->createDrawable( PixelDensity::dpToPx( 12 ) ) : nullptr );
|
||||
mTab =
|
||||
mSidePanel->add( i18n( "debugger", "Debugger" ), mTabContents,
|
||||
icon ? icon->createDrawable( PixelDensity::dpToPx( 12 ) ) : nullptr );
|
||||
mTab->setId( "debugger_tab" );
|
||||
mTab->setTextAsFallback( true );
|
||||
|
||||
@@ -1217,8 +1223,7 @@ bool DebuggerPlugin::replaceInVal( std::string& val,
|
||||
String::replaceAll( val, KEY_PATH_SEPARATOR, FileSystem::getOSSlash() );
|
||||
String::replaceAll( val, KEY_PATH_SEPARATOR_ABBR, FileSystem::getOSSlash() );
|
||||
String::replaceAll( val, KEY_UUID, UUID().toString() );
|
||||
String::replaceAll( val, KEY_TIMESTAMP,
|
||||
std::to_string( Sys::getUnixTimestamp() ) );
|
||||
String::replaceAll( val, KEY_TIMESTAMP, std::to_string( Sys::getUnixTimestamp() ) );
|
||||
|
||||
auto* editor = getPluginContext()->getSplitter()->getCurEditor();
|
||||
if ( getPluginContext()->getSplitter()->getCurEditor() ) {
|
||||
|
||||
@@ -67,6 +67,8 @@ class DebuggerPlugin : public PluginBase {
|
||||
|
||||
std::string getDescription() override { return Definition().description; }
|
||||
|
||||
void onSaveState( IniFile* state ) override;
|
||||
|
||||
void onSaveProject( const std::string& projectFolder, const std::string& projectStatePath,
|
||||
bool rewriteStateOnlyIfNeeded ) override;
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
#include "stackmodel.hpp"
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
#include <eepp/ui/uiscenenode.hpp>
|
||||
|
||||
namespace ecode {
|
||||
|
||||
StackModel::StackModel( StackTraceInfo&& stack, UISceneNode* sceneNode ) :
|
||||
mStack( std::move( stack ) ), mSceneNode( sceneNode ) {}
|
||||
StackModel::StackModel( StackTraceInfo&& stack, UISceneNode* sceneNode, std::string projectPath ) :
|
||||
mStack( std::move( stack ) ),
|
||||
mProjectPath( std::move( projectPath ) ),
|
||||
mSceneNode( sceneNode ) {
|
||||
FileSystem::dirAddSlashAtEnd( mProjectPath );
|
||||
}
|
||||
|
||||
size_t StackModel::rowCount( const ModelIndex& ) const {
|
||||
Lock l( mResourceLock );
|
||||
@@ -45,7 +50,8 @@ Variant StackModel::data( const ModelIndex& modelIndex, ModelRole role ) const {
|
||||
: Variant();
|
||||
case Columns::SourcePath:
|
||||
return mStack.stackFrames[modelIndex.row()].source
|
||||
? Variant( mStack.stackFrames[modelIndex.row()].source->path )
|
||||
? Variant( displaySourcePath(
|
||||
mStack.stackFrames[modelIndex.row()].source->path ) )
|
||||
: Variant();
|
||||
case Columns::Line:
|
||||
return Variant( String::toString( mStack.stackFrames[modelIndex.row()].line ) );
|
||||
@@ -90,4 +96,23 @@ void StackModel::setCurrentScopeId( int scope ) {
|
||||
}
|
||||
}
|
||||
|
||||
void StackModel::setProjectPath( const std::string& projectPath ) {
|
||||
std::string normalizedProjectPath( projectPath );
|
||||
FileSystem::dirAddSlashAtEnd( normalizedProjectPath );
|
||||
{
|
||||
Lock l( mResourceLock );
|
||||
if ( mProjectPath == normalizedProjectPath )
|
||||
return;
|
||||
mProjectPath = std::move( normalizedProjectPath );
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
const char* StackModel::displaySourcePath( const std::string& sourcePath ) const {
|
||||
if ( mProjectPath.empty() || sourcePath.size() < mProjectPath.size() ||
|
||||
0 != sourcePath.compare( 0, mProjectPath.size(), mProjectPath ) )
|
||||
return sourcePath.c_str();
|
||||
return sourcePath.c_str() + mProjectPath.size();
|
||||
}
|
||||
|
||||
} // namespace ecode
|
||||
|
||||
@@ -19,7 +19,7 @@ class StackModel : public Model {
|
||||
public:
|
||||
enum Columns { ID, Name, SourceName, SourcePath, Line, Column };
|
||||
|
||||
StackModel( StackTraceInfo&& stack, UISceneNode* sceneNode );
|
||||
StackModel( StackTraceInfo&& stack, UISceneNode* sceneNode, std::string projectPath = {} );
|
||||
|
||||
virtual size_t rowCount( const ModelIndex& ) const;
|
||||
|
||||
@@ -37,10 +37,15 @@ class StackModel : public Model {
|
||||
|
||||
void setCurrentScopeId( int scope );
|
||||
|
||||
void setProjectPath( const std::string& projectPath );
|
||||
|
||||
protected:
|
||||
StackTraceInfo mStack;
|
||||
std::string mProjectPath;
|
||||
UISceneNode* mSceneNode{ nullptr };
|
||||
int mCurrentScopeId{ 0 };
|
||||
|
||||
const char* displaySourcePath( const std::string& sourcePath ) const;
|
||||
};
|
||||
|
||||
} // namespace ecode
|
||||
|
||||
@@ -1,11 +1,45 @@
|
||||
#include "statusdebuggercontroller.hpp"
|
||||
#include "../../appconfig.hpp"
|
||||
#include "../../uirightpanel.hpp"
|
||||
#include "../../widgetcommandexecuter.hpp"
|
||||
#include "../plugincontextprovider.hpp"
|
||||
#include "eepp/ui/uiwidgetcreator.hpp"
|
||||
#include <eepp/ui/uicheckbox.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace ecode {
|
||||
|
||||
static constexpr size_t DEBUGGER_LAYOUT_MAX_DEPTH = 16;
|
||||
static constexpr size_t DEBUGGER_LAYOUT_MAX_NODES = 64;
|
||||
static constexpr size_t DEBUGGER_WIDGET_ID_PREFIX_LENGTH = 9;
|
||||
|
||||
static bool isValidDebuggerLayoutNode( const nlohmann::json& node,
|
||||
UnorderedSet<std::string>& widgetTypes, size_t depth,
|
||||
size_t& nodeCount ) {
|
||||
if ( depth > DEBUGGER_LAYOUT_MAX_DEPTH || ++nodeCount > DEBUGGER_LAYOUT_MAX_NODES )
|
||||
return false;
|
||||
if ( !node.is_object() || !node.contains( "type" ) || !node["type"].is_string() )
|
||||
return false;
|
||||
if ( node["type"] == "splitter" )
|
||||
return node.contains( "first" ) && node.contains( "last" ) &&
|
||||
isValidDebuggerLayoutNode( node["first"], widgetTypes, depth + 1, nodeCount ) &&
|
||||
isValidDebuggerLayoutNode( node["last"], widgetTypes, depth + 1, nodeCount );
|
||||
if ( node["type"] != "tabwidget" || !node.contains( "files" ) || !node["files"].is_array() )
|
||||
return false;
|
||||
for ( const auto& file : node["files"] ) {
|
||||
if ( !file.is_object() || !file.contains( "type" ) || !file["type"].is_string() ||
|
||||
!widgetTypes.emplace( file["type"].get<std::string>() ).second )
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void restoreColumnWidths( UIAbstractTableView* view, const nlohmann::json& columns,
|
||||
const char* key ) {
|
||||
if ( view && columns.contains( key ) )
|
||||
view->unserializeColumnWidths( columns[key] );
|
||||
}
|
||||
|
||||
class UIBreakpointsTableCell : public UITableCell {
|
||||
public:
|
||||
static UIBreakpointsTableCell* New( const std::string& tag, const BreakpointsModel* model,
|
||||
@@ -109,6 +143,37 @@ StatusDebuggerController::StatusDebuggerController( UISplitter* mainSplitter,
|
||||
PluginContextProvider* pluginContext ) :
|
||||
StatusBarElement( mainSplitter, uiSceneNode, pluginContext ) {}
|
||||
|
||||
StatusDebuggerController::~StatusDebuggerController() {
|
||||
mEventConnections.clear();
|
||||
mTabWidgetEventConnections.clear();
|
||||
if ( mContainer )
|
||||
mContainer->removeActionsByTag( reinterpret_cast<UintPtr>( this ) );
|
||||
if ( mTabWidgetSplitter ) {
|
||||
mTabWidgetSplitter->setOnTabWidgetCreateCb( nullptr );
|
||||
mTabWidgetSplitter->setOnTabWidgetCloseCb( nullptr );
|
||||
mTabWidgetSplitter->forEachTabWidget( []( UITabWidget* tabWidget ) {
|
||||
tabWidget->setSplitFunction( nullptr );
|
||||
tabWidget->setTabTryCloseCallback( nullptr );
|
||||
tabWidget->setAcceptsDropOfWidgetFn( nullptr );
|
||||
} );
|
||||
eeDelete( mTabWidgetSplitter );
|
||||
}
|
||||
if ( mContext && mContext->getRightPanel() )
|
||||
mContext->getRightPanel()->unregisterPanel( "debugger" );
|
||||
if ( mContainer )
|
||||
mContainer->close();
|
||||
}
|
||||
|
||||
void StatusDebuggerController::show() {
|
||||
StatusBarElement::show();
|
||||
updateRightPanel();
|
||||
}
|
||||
|
||||
void StatusDebuggerController::hide() {
|
||||
StatusBarElement::hide();
|
||||
updateRightPanel();
|
||||
}
|
||||
|
||||
UIWidget* StatusDebuggerController::getWidget() {
|
||||
return mContainer;
|
||||
}
|
||||
@@ -193,6 +258,207 @@ void StatusDebuggerController::clearConsoleBuffer() {
|
||||
} );
|
||||
}
|
||||
|
||||
const std::string& StatusDebuggerController::saveLayout() {
|
||||
saveTabLayout();
|
||||
return mSerializedLayout;
|
||||
}
|
||||
|
||||
void StatusDebuggerController::onTabCreated( UITab* tab, UIWidget* widget ) {
|
||||
tab->setId( "debugger_tab_" + widget->getId().substr( DEBUGGER_WIDGET_ID_PREFIX_LENGTH ) );
|
||||
tab->addClass( "debugger-tab" );
|
||||
tab->setTextAsFallback( true );
|
||||
mEventConnections += tab->connect( Event::OnDragStart,
|
||||
[this]( const Event* ) { beginRightPanelDropPreview(); } );
|
||||
mEventConnections +=
|
||||
tab->connect( Event::OnDragStop, [this]( const Event* ) { endRightPanelDropPreview(); } );
|
||||
}
|
||||
|
||||
void StatusDebuggerController::onWidgetFocusChange( UIWidget* ) {}
|
||||
|
||||
void StatusDebuggerController::createTabWidgets() {
|
||||
mRightPanelContainer = mContext->getRightPanel()
|
||||
? mContext->getRightPanel()->registerPanel( "debugger" )
|
||||
: nullptr;
|
||||
if ( !mDebuggerTabsContainer || !mRightPanelContainer )
|
||||
return;
|
||||
|
||||
mTabWidgetSplitter = UITabWidgetSplitter::New( this, mUISceneNode );
|
||||
mTabWidgetSplitter->setHideTabBarOnSingleTab( false );
|
||||
mTabWidgetSplitter->setOnTabWidgetCreateCb( [this]( UITabWidget* tabWidget ) {
|
||||
tabWidget->setTabsClosable( false );
|
||||
tabWidget->setAcceptsDropOfWidgetFn(
|
||||
[]( const UIWidget* widget ) { return widget->hasClass( "debugger-tab" ); } );
|
||||
tabWidget->setSplitFunction(
|
||||
[this]( SplitDirection direction, UITabWidget* target ) -> UITabWidget* {
|
||||
Node* dragging = mUISceneNode->getEventDispatcher()->getNodeDragging();
|
||||
if ( dragging && dragging->isType( UI_TYPE_TAB ) ) {
|
||||
auto source = dragging->asType<UITab>()->getTabWidget();
|
||||
bool sourceInRight = source && mRightPanelContainer->isParentOf( source );
|
||||
bool targetInRight = mRightPanelContainer->isParentOf( target );
|
||||
if ( source && sourceInRight != targetInRight )
|
||||
return target;
|
||||
}
|
||||
return mTabWidgetSplitter->splitTabWidget( direction, target );
|
||||
},
|
||||
mTabWidgetSplitter->getVisualSplitEdgePercent() );
|
||||
auto& connections = mTabWidgetEventConnections[tabWidget];
|
||||
connections += tabWidget->connect( Event::OnTabAdded, [this]( const Event* ) {
|
||||
if ( !mRestoringLayout ) {
|
||||
saveTabLayout();
|
||||
updateRightPanel();
|
||||
}
|
||||
} );
|
||||
} );
|
||||
mTabWidgetSplitter->setOnTabWidgetCloseCb(
|
||||
[this]( UITabWidget* tabWidget ) { mTabWidgetEventConnections.erase( tabWidget ); } );
|
||||
|
||||
mUITabWidget = mTabWidgetSplitter->createTabWidget( mDebuggerTabsContainer );
|
||||
mUITabWidget->setId( "app_debugger_tab_widget" );
|
||||
mUIRightTabWidget = mTabWidgetSplitter->createTabWidget( mRightPanelContainer );
|
||||
mUIRightTabWidget->setId( "app_debugger_right_tab_widget" );
|
||||
|
||||
const auto registerWidget = [this]( const std::string& type, UIWidget* widget,
|
||||
const std::string& title ) {
|
||||
widget->addClass( type );
|
||||
mTabWidgetSplitter->registerWidgetType(
|
||||
type, { []( UIWidget* ) { return nlohmann::json::object(); },
|
||||
[widget, title]( const nlohmann::json& ) -> WidgetLoadResult {
|
||||
return { widget, nullptr, title };
|
||||
} } );
|
||||
};
|
||||
registerWidget( "debugger-threads-and-stack", mUIThreadsSplitter,
|
||||
mContext->i18n( "threads_and_stack", "Threads & Stack" ).toUtf8() );
|
||||
registerWidget( "debugger-variables", mUIVariables,
|
||||
mContext->i18n( "variables", "Variables" ).toUtf8() );
|
||||
registerWidget( "debugger-expressions", mUIExpressions,
|
||||
mContext->i18n( "expressions", "Expressions" ).toUtf8() );
|
||||
registerWidget( "debugger-breakpoints", mUIBreakpoints,
|
||||
mContext->i18n( "breakpoints", "Breakpoints" ).toUtf8() );
|
||||
registerWidget( "debugger-console", mUIConsole,
|
||||
mContext->i18n( "console_output", "Console Output" ).toUtf8() );
|
||||
|
||||
restoreTabLayout();
|
||||
}
|
||||
|
||||
void StatusDebuggerController::restoreTabLayout() {
|
||||
static const UnorderedSet<std::string> expectedWidgetTypes{
|
||||
"debugger-threads-and-stack", "debugger-variables", "debugger-expressions",
|
||||
"debugger-breakpoints", "debugger-console" };
|
||||
mRestoringLayout = true;
|
||||
bool restored = false;
|
||||
mSerializedLayout = mContext->getConfig().iniState.getValue(
|
||||
"debugger", "panel_layout",
|
||||
mContext->getConfig().iniState.getValue( "ui", "debugger_panel_layout", "" ) );
|
||||
const std::string& saved = mSerializedLayout;
|
||||
if ( !saved.empty() ) {
|
||||
auto layout = nlohmann::json::parse( saved, nullptr, false, true );
|
||||
UnorderedSet<std::string> widgetTypes;
|
||||
size_t nodeCount = 0;
|
||||
const int version = layout.is_discarded() ? 0 : layout.value( "version", 0 );
|
||||
if ( !layout.is_discarded() && ( version == 2 || version == 3 ) &&
|
||||
layout.contains( "bottom" ) && layout.contains( "right" ) &&
|
||||
isValidDebuggerLayoutNode( layout["bottom"], widgetTypes, 0, nodeCount ) &&
|
||||
isValidDebuggerLayoutNode( layout["right"], widgetTypes, 0, nodeCount ) &&
|
||||
widgetTypes == expectedWidgetTypes ) {
|
||||
mTabWidgetSplitter->unserializeNode( layout["bottom"], mUITabWidget );
|
||||
mTabWidgetSplitter->unserializeNode( layout["right"], mUIRightTabWidget );
|
||||
if ( layout.contains( "columns" ) && layout["columns"].is_object() ) {
|
||||
restoreColumnWidths( mUIThreads, layout["columns"], "threads" );
|
||||
restoreColumnWidths( mUIStack, layout["columns"], "stack" );
|
||||
restoreColumnWidths( mUIVariables, layout["columns"], "variables" );
|
||||
restoreColumnWidths( mUIExpressions, layout["columns"], "expressions" );
|
||||
restoreColumnWidths( mUIBreakpoints, layout["columns"], "breakpoints" );
|
||||
}
|
||||
restored = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !restored ) {
|
||||
mTabWidgetSplitter->createWidgetInTabWidget(
|
||||
mUITabWidget, mUIThreadsSplitter,
|
||||
mContext->i18n( "threads_and_stack", "Threads & Stack" ).toUtf8(), false );
|
||||
mTabWidgetSplitter->createWidgetInTabWidget(
|
||||
mUITabWidget, mUIBreakpoints, mContext->i18n( "breakpoints", "Breakpoints" ).toUtf8(),
|
||||
false );
|
||||
mTabWidgetSplitter->createWidgetInTabWidget(
|
||||
mUITabWidget, mUIConsole, mContext->i18n( "console_output", "Console Output" ).toUtf8(),
|
||||
false );
|
||||
mTabWidgetSplitter->createWidgetInTabWidget(
|
||||
mUIRightTabWidget, mUIVariables, mContext->i18n( "variables", "Variables" ).toUtf8(),
|
||||
false );
|
||||
mTabWidgetSplitter->createWidgetInTabWidget(
|
||||
mUIRightTabWidget, mUIExpressions,
|
||||
mContext->i18n( "expressions", "Expressions" ).toUtf8(), false );
|
||||
mUITabWidget->setTabSelected( Uint32{ 0 } );
|
||||
mUIRightTabWidget->setTabSelected( Uint32{ 0 } );
|
||||
}
|
||||
if ( mUITabWidget->getTabCount() )
|
||||
mTabWidgetSplitter->setCurrentWidget(
|
||||
mUITabWidget->getTabSelected()->getOwnedWidget()->asType<UIWidget>() );
|
||||
mRestoringLayout = false;
|
||||
saveTabLayout();
|
||||
updateRightPanel();
|
||||
}
|
||||
|
||||
void StatusDebuggerController::saveTabLayout() {
|
||||
if ( mRestoringLayout || !mTabWidgetSplitter || !mDebuggerTabsContainer ||
|
||||
!mRightPanelContainer || !mDebuggerTabsContainer->getFirstChild() ||
|
||||
!mRightPanelContainer->getFirstChild() )
|
||||
return;
|
||||
nlohmann::json layout;
|
||||
layout["version"] = 3;
|
||||
layout["bottom"] = mTabWidgetSplitter->serializeNode( mDebuggerTabsContainer->getFirstChild() );
|
||||
layout["right"] = mTabWidgetSplitter->serializeNode( mRightPanelContainer->getFirstChild() );
|
||||
layout["columns"]["threads"] = mUIThreads->serializeColumnWidths();
|
||||
layout["columns"]["stack"] = mUIStack->serializeColumnWidths();
|
||||
layout["columns"]["variables"] = mUIVariables->serializeColumnWidths();
|
||||
layout["columns"]["expressions"] = mUIExpressions->serializeColumnWidths();
|
||||
layout["columns"]["breakpoints"] = mUIBreakpoints->serializeColumnWidths();
|
||||
mSerializedLayout = layout.dump();
|
||||
}
|
||||
|
||||
bool StatusDebuggerController::rightPanelHasTabs() const {
|
||||
bool hasTabs = false;
|
||||
if ( !mTabWidgetSplitter || !mRightPanelContainer )
|
||||
return false;
|
||||
mTabWidgetSplitter->forEachTabWidgetStoppable( [this, &hasTabs]( UITabWidget* tabWidget ) {
|
||||
hasTabs = mRightPanelContainer->isParentOf( tabWidget ) && tabWidget->getTabCount() > 0;
|
||||
return hasTabs;
|
||||
} );
|
||||
return hasTabs;
|
||||
}
|
||||
|
||||
void StatusDebuggerController::beginRightPanelDropPreview() {
|
||||
if ( !mRightPanelDropPreview && mContainer && mContainer->isVisible() &&
|
||||
!rightPanelHasTabs() ) {
|
||||
mRightPanelDropPreview = true;
|
||||
updateRightPanel();
|
||||
}
|
||||
}
|
||||
|
||||
void StatusDebuggerController::endRightPanelDropPreview() {
|
||||
if ( !mRightPanelDropPreview || !mContainer )
|
||||
return;
|
||||
// The drop target is resolved after OnDragStop. Keep the empty panel alive until the next
|
||||
// update so it can receive the drop, then collapse it if the drag was canceled or rejected.
|
||||
mContainer->removeActionsByTag( reinterpret_cast<UintPtr>( this ) );
|
||||
mContainer->runOnMainThread(
|
||||
[this] {
|
||||
mRightPanelDropPreview = false;
|
||||
updateRightPanel();
|
||||
},
|
||||
Time::Zero, reinterpret_cast<UintPtr>( this ) );
|
||||
}
|
||||
|
||||
void StatusDebuggerController::updateRightPanel() {
|
||||
auto rightPanel = mContext->getRightPanel();
|
||||
if ( !rightPanel || !mRightPanelContainer || !mTabWidgetSplitter )
|
||||
return;
|
||||
rightPanel->setPanelVisible( "debugger",
|
||||
mContainer && mContainer->isVisible() &&
|
||||
( mRightPanelDropPreview || rightPanelHasTabs() ) );
|
||||
}
|
||||
|
||||
void StatusDebuggerController::createContainer() {
|
||||
if ( mContainer )
|
||||
return;
|
||||
@@ -206,21 +472,16 @@ void StatusDebuggerController::createContainer() {
|
||||
}
|
||||
</style>
|
||||
<hboxce id="app_debugger" lw="mp" lh="mp" visible="false">
|
||||
<TabWidget id="app_debugger_tab_widget" lw="0" lw8="1" lh="mp">
|
||||
<vbox id="app_debugger_tabs" lw="0" lw8="1" lh="mp">
|
||||
<Splitter id="debugger_threads_and_stack" layout_width="mp" lh="mp" splitter-partition="15%">
|
||||
<TableView id="debugger_threads" layout_width="mp" layout_height="mp" />
|
||||
<TableView id="debugger_stack" layout_width="mp" layout_height="mp" />
|
||||
</Splitter>
|
||||
<TreeView id="debugger_variables" layout_width="mp" layout_height="mp" />
|
||||
<TreeView id="debugger_expressions" layout_width="mp" layout_height="mp" />
|
||||
<BreakpointsTableView id="debugger_breakpoints" layout_width="mp" layout_height="mp" />
|
||||
<TableView id="debugger_threads" layout_width="mp" layout_height="mp" column-width-mode="percentage" column-width-mode-menu="true" />
|
||||
<TableView id="debugger_stack" layout_width="mp" layout_height="mp" column-width-mode="percentage" column-width-mode-menu="true" />
|
||||
</Splitter>
|
||||
<TreeView id="debugger_variables" layout_width="mp" layout_height="mp" column-width-mode="percentage" column-width-mode-menu="true" />
|
||||
<TreeView id="debugger_expressions" layout_width="mp" layout_height="mp" column-width-mode="percentage" column-width-mode-menu="true" />
|
||||
<BreakpointsTableView id="debugger_breakpoints" layout_width="mp" layout_height="mp" column-width-mode="percentage" column-width-mode-menu="true" />
|
||||
<CodeEditor id="debugger_console" layout_width="mp" layout_height="mp" />
|
||||
<Tab id="debugger_tab_threads_and_stack" text="@string(threads_and_stack, Threads & Stack)" owns="debugger_threads_and_stack" />
|
||||
<Tab id="debugger_tab_variables" text="@string(variables, Variables)" owns="debugger_variables" />
|
||||
<Tab id="debugger_tab_expressions" text="@string(expressions, Expressions)" owns="debugger_expressions" />
|
||||
<Tab id="debugger_tab_breakpoints" text="@string(breakpoints, Breakpoints)" owns="debugger_breakpoints" />
|
||||
<Tab id="debugger_tab_console" text="@string(console_output, Console Output)" owns="debugger_console" />
|
||||
</TabWidget>
|
||||
</vbox>
|
||||
<vbox id="app_debugger_buttons" class="vertical_bar" lw="16dp" lh="mp">
|
||||
<PushButton class="expand_status_bar_panel" lw="mp" tooltip="@string(expand_panel, Expand Panel)" />
|
||||
<PushButton id="app_debugger_start" class="debugger_start" lw="mp" icon="icon(debug-start, 12dp)" tooltip="@string(start, Start)" />
|
||||
@@ -248,7 +509,7 @@ void StatusDebuggerController::createContainer() {
|
||||
|
||||
mContext->getStatusBar()->registerStatusBarPanel( mContainer, mContainer );
|
||||
|
||||
mContainer->bind( "app_debugger_tab_widget", mUITabWidget );
|
||||
mContainer->bind( "app_debugger_tabs", mDebuggerTabsContainer );
|
||||
mContainer->bind( "debugger_threads_and_stack", mUIThreadsSplitter );
|
||||
mContainer->bind( "debugger_threads", mUIThreads );
|
||||
mContainer->bind( "debugger_stack", mUIStack );
|
||||
@@ -256,6 +517,8 @@ void StatusDebuggerController::createContainer() {
|
||||
mContainer->bind( "debugger_variables", mUIVariables );
|
||||
mContainer->bind( "debugger_expressions", mUIExpressions );
|
||||
mContainer->bind( "debugger_console", mUIConsole );
|
||||
mEventConnections += mContainer->connect( Event::OnVisibleChange,
|
||||
[this]( const Event* ) { updateRightPanel(); } );
|
||||
mContainer->bind( "app_debugger_start", mUIButStart );
|
||||
mContainer->bind( "app_debugger_stop", mUIButStop );
|
||||
mContainer->bind( "app_debugger_pause", mUIButPause );
|
||||
@@ -264,25 +527,34 @@ void StatusDebuggerController::createContainer() {
|
||||
mContainer->bind( "app_debugger_step_into", mUIButStepInto );
|
||||
mContainer->bind( "app_debugger_step_out", mUIButStepOut );
|
||||
|
||||
createTabWidgets();
|
||||
|
||||
mContainer->setCommand( "next-tab", [this] {
|
||||
if ( mUITabWidget )
|
||||
mUITabWidget->focusNextTab();
|
||||
if ( mTabWidgetSplitter && mTabWidgetSplitter->getCurWidget() ) {
|
||||
auto tabWidget =
|
||||
mTabWidgetSplitter->tabWidgetFromWidget( mTabWidgetSplitter->getCurWidget() );
|
||||
if ( tabWidget )
|
||||
tabWidget->focusNextTab();
|
||||
}
|
||||
} );
|
||||
|
||||
mContainer->setCommand( "previous-tab", [this] {
|
||||
if ( mUITabWidget )
|
||||
mUITabWidget->focusPreviousTab();
|
||||
if ( mTabWidgetSplitter && mTabWidgetSplitter->getCurWidget() ) {
|
||||
auto tabWidget =
|
||||
mTabWidgetSplitter->tabWidgetFromWidget( mTabWidgetSplitter->getCurWidget() );
|
||||
if ( tabWidget )
|
||||
tabWidget->focusPreviousTab();
|
||||
}
|
||||
} );
|
||||
|
||||
for ( int i = 1; i <= 5; i++ ) {
|
||||
mContainer->setCommand( String::format( "switch-to-tab-%d", i ), [this, i] {
|
||||
if ( mUITabWidget )
|
||||
mUITabWidget->setTabSelected(
|
||||
eeclamp<Int32>( i - 1, 0, mUITabWidget->getTabCount() - 1 ) );
|
||||
if ( mTabWidgetSplitter )
|
||||
mTabWidgetSplitter->switchToTab( i - 1 );
|
||||
} );
|
||||
}
|
||||
|
||||
mContainer->on( Event::KeyDown, [this]( const Event* event ) {
|
||||
mEventConnections += mContainer->connect( Event::KeyDown, [this]( const Event* event ) {
|
||||
auto ke = event->asKeyEvent();
|
||||
if ( ke->getSanitizedMod() == 0 && ke->getKeyCode() == EE::Window::KEY_ESCAPE &&
|
||||
mSplitter->getCurEditor() ) {
|
||||
@@ -313,19 +585,12 @@ void StatusDebuggerController::createContainer() {
|
||||
|
||||
mUIThreads->setAutoExpandOnSingleColumn( true );
|
||||
|
||||
mUIStack->setAutoColumnsWidth( true );
|
||||
mUIStack->setFitAllColumnsToWidget( true );
|
||||
mUIStack->setMainColumn( 1 );
|
||||
|
||||
mUIVariables->setAutoColumnsWidth( true );
|
||||
mUIVariables->setFitAllColumnsToWidget( true );
|
||||
mUIVariables->setMainColumn( 1 );
|
||||
|
||||
mUIBreakpoints->setAutoColumnsWidth( true );
|
||||
mUIBreakpoints->setFitAllColumnsToWidget( true );
|
||||
mUIBreakpoints->setMainColumn( 1 );
|
||||
|
||||
mUIExpressions->setAutoColumnsWidth( true );
|
||||
mUIExpressions->setFitAllColumnsToWidget( true );
|
||||
mUIExpressions->setMainColumn( 1 );
|
||||
|
||||
mUIConsole->setLocked( true );
|
||||
@@ -333,7 +598,7 @@ void StatusDebuggerController::createContainer() {
|
||||
mUIConsole->setShowLineNumber( false );
|
||||
mUIConsole->getDocument().reset();
|
||||
mUIConsole->setScrollY( mUIConsole->getMaxScroll().y );
|
||||
mUIConsole->on( Event::OnScrollChange, [this]( auto ) {
|
||||
mEventConnections += mUIConsole->connect( Event::OnScrollChange, [this]( const Event* ) {
|
||||
mScrollLocked = mUIConsole->getMaxScroll().y == mUIConsole->getScroll().y;
|
||||
} );
|
||||
}
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
|
||||
#include "../../uistatusbar.hpp"
|
||||
#include "models/breakpointsmodel.hpp"
|
||||
#include <eepp/core/containers.hpp>
|
||||
#include <eepp/scene/eventconnection.hpp>
|
||||
#include <eepp/system/luapattern.hpp>
|
||||
#include <eepp/ui/tools/uicodeeditorsplitter.hpp>
|
||||
#include <eepp/ui/tools/uitabwidgetsplitter.hpp>
|
||||
#include <eepp/ui/uicodeeditor.hpp>
|
||||
#include <eepp/ui/uirelativelayout.hpp>
|
||||
#include <eepp/ui/uiscenenode.hpp>
|
||||
@@ -34,7 +37,7 @@ class UIBreakpointsTableView : public UITableView {
|
||||
UIWidget* createCell( UIWidget* rowWidget, const ModelIndex& index );
|
||||
};
|
||||
|
||||
class StatusDebuggerController : public StatusBarElement {
|
||||
class StatusDebuggerController : public StatusBarElement, public UITabWidgetSplitter::Client {
|
||||
public:
|
||||
enum class State { NotStarted, Running, Paused };
|
||||
|
||||
@@ -43,7 +46,11 @@ class StatusDebuggerController : public StatusBarElement {
|
||||
StatusDebuggerController( UISplitter* mainSplitter, UISceneNode* uiSceneNode,
|
||||
PluginContextProvider* pluginContext );
|
||||
|
||||
virtual ~StatusDebuggerController() {};
|
||||
virtual ~StatusDebuggerController();
|
||||
|
||||
virtual void show();
|
||||
|
||||
virtual void hide();
|
||||
|
||||
UIWidget* getWidget();
|
||||
|
||||
@@ -71,8 +78,14 @@ class StatusDebuggerController : public StatusBarElement {
|
||||
|
||||
void setDebuggingState( State state );
|
||||
|
||||
const std::string& saveLayout();
|
||||
|
||||
std::function<void( StatusDebuggerController*, UIWidget* )> onWidgetCreated{ nullptr };
|
||||
|
||||
void onTabCreated( UITab* tab, UIWidget* widget );
|
||||
|
||||
void onWidgetFocusChange( UIWidget* widget );
|
||||
|
||||
protected:
|
||||
UIHLinearLayoutCommandExecuter* mContainer{ nullptr };
|
||||
UITableView* mUIThreads{ nullptr };
|
||||
@@ -90,9 +103,32 @@ class StatusDebuggerController : public StatusBarElement {
|
||||
UIPushButton* mUIButStepOver{ nullptr };
|
||||
UIPushButton* mUIButStepOut{ nullptr };
|
||||
UITabWidget* mUITabWidget{ nullptr };
|
||||
UITabWidget* mUIRightTabWidget{ nullptr };
|
||||
UITabWidgetSplitter* mTabWidgetSplitter{ nullptr };
|
||||
UILayout* mDebuggerTabsContainer{ nullptr };
|
||||
UILayout* mRightPanelContainer{ nullptr };
|
||||
Scene::EventConnectionList mEventConnections;
|
||||
UnorderedMap<UITabWidget*, Scene::EventConnectionList> mTabWidgetEventConnections;
|
||||
bool mScrollLocked{ true };
|
||||
bool mRestoringLayout{ false };
|
||||
bool mRightPanelDropPreview{ false };
|
||||
std::string mSerializedLayout;
|
||||
|
||||
void createContainer();
|
||||
|
||||
void createTabWidgets();
|
||||
|
||||
void restoreTabLayout();
|
||||
|
||||
void saveTabLayout();
|
||||
|
||||
bool rightPanelHasTabs() const;
|
||||
|
||||
void beginRightPanelDropPreview();
|
||||
|
||||
void endRightPanelDropPreview();
|
||||
|
||||
void updateRightPanel();
|
||||
};
|
||||
|
||||
} // namespace ecode
|
||||
|
||||
@@ -19,6 +19,7 @@ class Font;
|
||||
namespace UI {
|
||||
|
||||
class UISplitter;
|
||||
class UILayout;
|
||||
class UITabWidget;
|
||||
class UISceneNode;
|
||||
class UICodeEditor;
|
||||
@@ -58,6 +59,8 @@ class ProjectDirectoryTree;
|
||||
struct TerminalConfig;
|
||||
class UIMainLayout;
|
||||
class UITreeViewFS;
|
||||
class StatusDebuggerController;
|
||||
class UIRightPanel;
|
||||
|
||||
class PluginContextProvider {
|
||||
public:
|
||||
@@ -65,6 +68,10 @@ class PluginContextProvider {
|
||||
|
||||
virtual UISplitter* getMainSplitter() const = 0;
|
||||
|
||||
virtual UIRightPanel* getRightPanel() const = 0;
|
||||
|
||||
virtual StatusDebuggerController* getStatusDebuggerController() const = 0;
|
||||
|
||||
virtual UITreeViewFS* getProjectTreeView() const = 0;
|
||||
|
||||
virtual void hideGlobalSearchBar() = 0;
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
#include "uirightpanel.hpp"
|
||||
#include "appconfig.hpp"
|
||||
|
||||
namespace ecode {
|
||||
|
||||
using namespace EE;
|
||||
using namespace EE::UI;
|
||||
|
||||
UIRightPanel::UIRightPanel( UISplitter* splitter, UILayout* container, AppConfig* config ) :
|
||||
mSplitter( splitter ), mContainer( container ), mConfig( config ) {}
|
||||
|
||||
UILayout* UIRightPanel::registerPanel( const std::string& id ) {
|
||||
auto found = mPanels.find( id );
|
||||
if ( found != mPanels.end() )
|
||||
return found->second.layout;
|
||||
if ( !mContainer )
|
||||
return nullptr;
|
||||
auto layout = UIRelativeLayout::New();
|
||||
layout->setId( id );
|
||||
layout->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent );
|
||||
layout->setVisible( false )->setEnabled( false )->setParent( mContainer );
|
||||
mPanels.emplace( id, Panel{ layout, false } );
|
||||
return layout;
|
||||
}
|
||||
|
||||
void UIRightPanel::unregisterPanel( const std::string& id ) {
|
||||
auto found = mPanels.find( id );
|
||||
if ( found == mPanels.end() )
|
||||
return;
|
||||
if ( found->second.layout )
|
||||
found->second.layout->close();
|
||||
mPanels.erase( found );
|
||||
updateVisibility();
|
||||
}
|
||||
|
||||
void UIRightPanel::setPanelVisible( const std::string& id, bool visible ) {
|
||||
auto found = mPanels.find( id );
|
||||
if ( found == mPanels.end() )
|
||||
return;
|
||||
if ( visible ) {
|
||||
for ( auto& panel : mPanels ) {
|
||||
if ( panel.first != id && panel.second.visible ) {
|
||||
panel.second.visible = false;
|
||||
if ( panel.second.layout )
|
||||
panel.second.layout->setVisible( false )->setEnabled( false );
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( found->second.visible == visible )
|
||||
return;
|
||||
found->second.visible = visible;
|
||||
if ( found->second.layout )
|
||||
found->second.layout->setVisible( visible )->setEnabled( visible );
|
||||
updateVisibility();
|
||||
}
|
||||
|
||||
bool UIRightPanel::isPanelVisible( const std::string& id ) const {
|
||||
auto found = mPanels.find( id );
|
||||
return found != mPanels.end() && found->second.visible;
|
||||
}
|
||||
|
||||
void UIRightPanel::saveState() {
|
||||
if ( mSplitter && mConfig &&
|
||||
mSplitter->getSplitPartition().getUnit() == StyleSheetLength::Percentage &&
|
||||
mSplitter->getSplitPartition().getValue() < 100.f )
|
||||
mConfig->windowState.rightPanelPartition = mSplitter->getSplitPartition().toString();
|
||||
}
|
||||
|
||||
UISplitter* UIRightPanel::getSplitter() const {
|
||||
return mSplitter;
|
||||
}
|
||||
|
||||
UILayout* UIRightPanel::getContainer() const {
|
||||
return mContainer;
|
||||
}
|
||||
|
||||
void UIRightPanel::updateVisibility() {
|
||||
if ( !mSplitter || !mConfig )
|
||||
return;
|
||||
bool visible = false;
|
||||
for ( const auto& panel : mPanels ) {
|
||||
if ( panel.second.visible ) {
|
||||
visible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( visible == mVisible )
|
||||
return;
|
||||
mVisible = visible;
|
||||
if ( visible ) {
|
||||
auto partition = mConfig->windowState.rightPanelPartition;
|
||||
if ( partition.empty() || partition == "100%" )
|
||||
partition = "75%";
|
||||
mSplitter->setSplitPartition( StyleSheetLength( partition ) );
|
||||
} else {
|
||||
saveState();
|
||||
mSplitter->setSplitPartition( StyleSheetLength( "100%" ) );
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ecode
|
||||
@@ -0,0 +1,50 @@
|
||||
#ifndef ECODE_UIRIGHTPANEL_HPP
|
||||
#define ECODE_UIRIGHTPANEL_HPP
|
||||
|
||||
#include <eepp/core/containers.hpp>
|
||||
#include <eepp/ui/uirelativelayout.hpp>
|
||||
#include <eepp/ui/uisplitter.hpp>
|
||||
|
||||
using namespace EE;
|
||||
using namespace EE::UI;
|
||||
|
||||
namespace ecode {
|
||||
|
||||
class AppConfig;
|
||||
|
||||
class UIRightPanel {
|
||||
public:
|
||||
UIRightPanel( UISplitter* splitter, UILayout* container, AppConfig* config );
|
||||
|
||||
UILayout* registerPanel( const std::string& id );
|
||||
|
||||
void unregisterPanel( const std::string& id );
|
||||
|
||||
void setPanelVisible( const std::string& id, bool visible );
|
||||
|
||||
bool isPanelVisible( const std::string& id ) const;
|
||||
|
||||
void saveState();
|
||||
|
||||
UISplitter* getSplitter() const;
|
||||
|
||||
UILayout* getContainer() const;
|
||||
|
||||
protected:
|
||||
struct Panel {
|
||||
UILayout* layout{ nullptr };
|
||||
bool visible{ false };
|
||||
};
|
||||
|
||||
UISplitter* mSplitter{ nullptr };
|
||||
UILayout* mContainer{ nullptr };
|
||||
AppConfig* mConfig{ nullptr };
|
||||
UnorderedMap<std::string, Panel> mPanels;
|
||||
bool mVisible{ false };
|
||||
|
||||
void updateVisibility();
|
||||
};
|
||||
|
||||
} // namespace ecode
|
||||
|
||||
#endif // ECODE_UIRIGHTPANEL_HPP
|
||||
Reference in New Issue
Block a user