// Copyright (C) 2007 Id Software, Inc. // #include "../precompiled.h" #pragma hdrstop #if defined( _DEBUG ) && !defined( ID_REDIRECT_NEWDELETE ) #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #include "UIWindow.h" #include "UserInterfaceLocal.h" #include "UIList.h" #include "UserInterfaceManager.h" #include "../../sys/sys_local.h" #include "../../idlib/Sort.h" #include "../../decllib/declTypeHolder.h" SD_UI_IMPLEMENT_CLASS( sdUIList, sdUIWindow ) idHashMap< sdUITemplateFunction< sdUIList >* > sdUIList::listFunctions; const char sdUITemplateFunctionInstance_IdentifierList[] = "sdUIListFunction"; const char sdUIListItem_Identifier[] = "sdUIListItem"; const char sdUIListColumn_Identifier[] = "sdUIListColumn"; idBlockAlloc< sdUIList::vec4EvaluatorList_t, 32 > sdUIList::vec4EvaluatorListAllocator; SD_UI_PUSH_CLASS_TAG( sdUIList ) const char* sdUIList::eventNames[ LE_NUM_EVENTS - WE_NUM_EVENTS ] = { SD_UI_EVENT_TAG( "onSelectItem", "", "Called when a list item is selected." ), SD_UI_EVENT_TAG( "onItemAdded", "", "Called when an item is inserted into the list." ), SD_UI_EVENT_TAG( "onItemRemoved", "", "Called when an item is deleted from the list." ), SD_UI_EVENT_PARM_TAG( "onDrawSelectedBackground", "", "If this event is valid in the GUI it will be called instead of onDrawItemBackGround for the currently selected item." ), SD_UI_EVENT_PARM( float, "index", "Row index." ) SD_UI_EVENT_PARM( rect, "rectangle", "Item rectangle." ) SD_UI_END_EVENT_TAG SD_UI_EVENT_PARM_TAG( "onDrawItemBackGround", "", "Called when drawing an item background." ), SD_UI_EVENT_PARM( float, "index", "Row index." ) SD_UI_EVENT_PARM( color, "itemColor", "Item color." ) SD_UI_EVENT_PARM( rect, "rectangle", "Item rectangle." ) SD_UI_END_EVENT_TAG SD_UI_EVENT_PARM_TAG( "onDrawItem", "", "Called when drawing an item. \"customDraw\" flag must be specified for the item for this event to be called for the item." ), SD_UI_EVENT_PARM( float, "itemRow", "Row index." ) SD_UI_EVENT_PARM( float, "itemColumn", "Item column." ) SD_UI_EVENT_RETURN_PARM( float, "Return false if the window should not do drawing itself." ) SD_UI_END_EVENT_TAG SD_UI_EVENT_PARM_TAG( "onDrawColumn", "", "Called when drawing a column. \"customDraw\" flag must be specified for the column for event to be called." ), SD_UI_EVENT_PARM( rect, "rectangle", "Column rectangle." ) SD_UI_EVENT_PARM( float, "column", "Column to draw." ) SD_UI_EVENT_RETURN_PARM( float, "Return false if the window should not do drawing itself." ) SD_UI_END_EVENT_TAG SD_UI_EVENT_PARM_TAG( "onEnterColumnHeader", "", "Called when the mouse enters a column header." ), SD_UI_EVENT_PARM( float, "column", "Column index." ) SD_UI_END_EVENT_TAG SD_UI_EVENT_PARM_TAG( "onExitColumnHeader", "", "Called when the mouse exits a column header." ), SD_UI_EVENT_PARM( float, "column", "Column index." ) SD_UI_END_EVENT_TAG SD_UI_EVENT_TAG( "onClickColumnHeader", "", "Called when there's a mouse click on the column header." ), SD_UI_EVENT_PARM_TAG( "onEnterItem", "", "Called when the mouse enters the list item." ), SD_UI_EVENT_PARM( float, "row", "The row index." ) SD_UI_EVENT_PARM( float, "column", "Column index." ) SD_UI_END_EVENT_TAG SD_UI_EVENT_PARM_TAG( "onExitItem", "", "Called when the mouse exits the list item." ), SD_UI_EVENT_PARM( float, "row", "Row index." ) SD_UI_EVENT_PARM( float, "column", "Column index." ) SD_UI_END_EVENT_TAG }; SD_UI_POP_CLASS_TAG /* ============ sdUIList::sdUIList ============ */ sdUIList::sdUIList() : scrollAmount( 0.0f ), scrollAmountMax( 0.0f ), columnHeight( 0.0f ), activeColumn( -1.0f ), activeColumnSort( 1.0f ), pageSize( 0.0f ), storedScrollAmount( -1.0f ), numItems( 0.0f ) { inBatchOperation = false; sizeLastColumnDelta = 0.0f; scrollToItem = -1; scriptState.GetProperties().RegisterProperty( "selectedItemForeColor", selectedItemForeColor ); scriptState.GetProperties().RegisterProperty( "columnFontSize", columnFontSize ); scriptState.GetProperties().RegisterProperty( "columnBorder", columnBorder ); scriptState.GetProperties().RegisterProperty( "rowSpacing", rowSpacing ); scriptState.GetProperties().RegisterProperty( "fixedRowHeight", fixedRowHeight ); scriptState.GetProperties().RegisterProperty( "currentSelection", currentSelection ); scriptState.GetProperties().RegisterProperty( "activeColumn", activeColumn ); scriptState.GetProperties().RegisterProperty( "activeColumnSort", activeColumnSort ); scriptState.GetProperties().RegisterProperty( "columnHeight", columnHeight ); scriptState.GetProperties().RegisterProperty( "scrollAmount", scrollAmount ); scriptState.GetProperties().RegisterProperty( "scrollAmountMax", scrollAmountMax ); scriptState.GetProperties().RegisterProperty( "pageSize", pageSize ); scriptState.GetProperties().RegisterProperty( "internalBorderWidth", internalBorderWidth ); scriptState.GetProperties().RegisterProperty( "numItems", numItems ); scrollAmountMax.SetReadOnly( true ); pageSize.SetReadOnly( true ); numItems.SetReadOnly( true ); columnHeight.SetReadOnly( true ); columnFontSize = 48.0f; columnBorder = 0.0f; internalBorderWidth = 0.0f; fixedRowHeight = 0.0f; rowSpacing = 0.0f; textAlignment = idVec2( TA_LEFT, TA_VCENTER ); selectedItemForeColor = colorWhite; currentSelection = -1.0f; dropShadowOffsetImage = 0.0f; UI_ADD_FLOAT_CALLBACK( fixedRowHeight, sdUIList, OnFixedRowHeightChanged ) UI_ADD_FLOAT_CALLBACK( currentSelection, sdUIList, OnCurrentItemChanged ) UI_ADD_FLOAT_CALLBACK( activeColumn, sdUIList, OnActiveColumnChanged ) UI_ADD_FLOAT_CALLBACK( internalBorderWidth, sdUIList, OnInternalBorderChanged ) SetWindowFlag( WF_ALLOW_FOCUS ); SetWindowFlag( WF_CLIP_TO_RECT ); SetWindowFlag( LF_AUTO_SCROLL_TO_SELECTION ); } /* ============ sdUIList::InitProperties ============ */ void sdUIList::InitProperties() { sdUIWindow::InitProperties(); } /* ============ sdUIList::~sdUIList ============ */ sdUIList::~sdUIList() { columns.DeleteContents( true ); DisconnectGlobalCallbacks(); } /* ============ sdUIList::FindFunction ============ */ const sdUIList::ListTemplateFunction* sdUIList::FindFunction( const char* name ) { sdUIList::ListTemplateFunction** ptr; return listFunctions.Get( name, &ptr ) ? *ptr : NULL; } /* ============ sdUIList::GetFunction ============ */ sdUIFunctionInstance* sdUIList::GetFunction( const char* name ) { const ListTemplateFunction* function = sdUIList::FindFunction( name ); if ( !function ) { return sdUIWindow::GetFunction( name ); } return new sdUITemplateFunctionInstance< sdUIList, sdUITemplateFunctionInstance_IdentifierList >( this, function ); } /* ============ sdUIList::RunNamedMethod ============ */ bool sdUIList::RunNamedMethod( const char* name, sdUIFunctionStack& stack ) { const sdUIList::ListTemplateFunction* func = sdUIList::FindFunction( name ); if ( !func ) { return sdUIWindow::RunNamedMethod( name, stack ); } CALL_MEMBER_FN_PTR( this, func->GetFunction() )( stack ); return true; } /* ============ sdUIList::CacheEvents ============ */ void sdUIList::CacheEvents() { sdUIWindow::CacheEvents(); drawItemHandle = events.GetEvent( sdUIEventInfo( LE_DRAW_ITEM, 0 ) ); drawItemBackHandle = events.GetEvent( sdUIEventInfo( LE_DRAW_ITEM_BACKGROUND, 0 ) ); drawColumnHandle = events.GetEvent( sdUIEventInfo( LE_DRAW_COLUMN, 0 ) ); drawSelectedItemBackHandle = events.GetEvent( sdUIEventInfo( LE_DRAW_SELECTED_BACKGROUND, 0 ) ); } /* ============ sdUIList::DrawLocal ============ */ void sdUIList::DrawLocal() { if( !PreDraw() ) { return; } const float border = ( borderWidth + internalBorderWidth ); const float headerHeight = GetHeaderHeight(); const int selectedItem = idMath::Ftoi( currentSelection ); float xCoord = cachedClientRect.x + border; idVec4 borderRect( cachedClientRect.x, cachedClientRect.y + headerHeight, cachedClientRect.z, cachedClientRect.w - headerHeight ); DrawBackground( borderRect ); bool selected = false; int active = idMath::Ftoi( activeColumn ); int now = GetUI()->GetCurrentTime(); deviceContext->SetFont( cachedFontHandle ); bool showHeadings = TestFlag( LF_SHOW_HEADINGS ); bool clip = TestFlag( WF_CLIP_TO_RECT ); bool truncateText = TestFlag( WF_TRUNCATE_TEXT ); bool truncateColumns = TestFlag( LF_TRUNCATE_COLUMNS ); int extraTextFlags = TestFlag( LF_VARIABLE_HEIGHT_ROWS ) ? DTF_WORDWRAP : 0; int removeFlags = extraTextFlags & DTF_WORDWRAP ? DTF_SINGLELINE : 0; if( TestFlag( WF_DROPSHADOW ) ) { extraTextFlags |= DTF_DROPSHADOW; } const wchar_t* truncationText = L"..."; int truncationWidth; int truncationHeight; if( truncateText || truncateColumns ) { deviceContext->GetTextDimensions( truncationText, sdBounds2D( 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT ), DTF_SINGLELINE | DTF_VCENTER | DTF_CENTER, cachedFontHandle, fontSize, truncationWidth, truncationHeight ); } sdWStringBuilder_Heap builder; bool firstColumn = true; // draw each column's row elements sdBounds2D clipRect( cachedClientRect.x, cachedClientRect.y + border + headerHeight, cachedClientRect.z, cachedClientRect.w - ( border + internalBorderWidth + headerHeight ) ); float* columnWidths = static_cast< float* >( _alloca( columns.Num() * sizeof( float ) ) ); CalcColumnWidths( columnWidths ); for ( int i = 0; i < columns.Num(); i++ ) { if ( columnWidths[ i ] <= 0.0f ) { continue; } sdUIListColumn* column = columns[ i ]; // column headings if ( showHeadings ) { sdBounds2D headerRect( xCoord, cachedClientRect.y, columnWidths[ i ], headerHeight ); bool continueDrawing = true; if( column->flags.customDraw ) { GetUI()->PushScriptVar( i ); GetUI()->PushScriptVar( headerRect.ToVec4() ); if( drawColumnHandle.IsValid() && RunEventHandle( drawColumnHandle ) ) { GetUI()->PopScriptVar( continueDrawing ); } GetUI()->ClearScriptStack(); } if( continueDrawing ) { const wchar_t* title = column->text.c_str(); if( title[ 0 ] == L'\0' && column->textHandle != -1 ) { const sdDeclLocStr* locStr = declHolder.declLocStrType.LocalFindByIndex( column->textHandle ); title = locStr->GetText(); } if( title[ 0 ] != '\0' ) { if( truncateText || truncateColumns ) { ShortenText( title, headerRect, cachedFontHandle, column->textFlags, columnFontSize, truncationText, truncationWidth, builder ); title = builder.c_str(); } int flags = column->textFlags; if( TestFlag( WF_DROPSHADOW ) ) { flags |= DTF_DROPSHADOW; } DrawText( title, column->transition.foreColor.Evaluate( now ), columnFontSize, headerRect, cachedFontHandle, flags ); } } } int firstVisibleItem = GetFirstVisibleItem(); if( clip ) { deviceContext->PushClipRect( clipRect ); } if ( firstVisibleItem >= 0 ) { int lastVisibleItem = GetLastVisibleItem( firstVisibleItem ); for( int j = firstVisibleItem; j <= lastVisibleItem; j++ ) { sdUIListItem* item = column->items[ indices[ j ] ]; sdTransition& transition = column->itemTransitions[ indices[ j ] ]; selected = ( j == selectedItem ); idVec4 itemRect; GetItemRect( j, i, itemRect, columnWidths ); if( i >= 1 ) { itemRect.x += columnBorder; itemRect.z -= columnBorder; } // draw selection highlight background if ( firstColumn ) { idVec4 rect = itemRect; for( int col = i + 1; col < columns.Num(); col++ ) { rect.z += columnWidths[ col ]; } GetUI()->PushScriptVar( rect ); if( selected && drawSelectedItemBackHandle.IsValid() ) { GetUI()->PushScriptVar( j ); RunEventHandle( drawSelectedItemBackHandle ); } else if( drawItemBackHandle.IsValid() ) { GetUI()->PushScriptVar( transition.backColor.Evaluate( now ) ); GetUI()->PushScriptVar( j ); RunEventHandle( drawItemBackHandle ); } GetUI()->ClearScriptStack(); } // script-controlled drawing if( item->flags.customDraw ) { GetUI()->PushScriptVar( i ); GetUI()->PushScriptVar( j ); if( RunEventHandle( drawItemHandle ) ) { bool continueDrawing; GetUI()->PopScriptVar( continueDrawing ); if( !continueDrawing ) { continue; } } } // draw icon if( item->part.mi.material != NULL ) { DrawItemMaterial( j, i, itemRect, transition.foreColor.Evaluate( now ), false ); continue; } const wchar_t* text = item->text.c_str(); if( text[ 0 ] == L'\0' && item->textHandle != -1 ) { const sdDeclLocStr* locStr = declHolder.declLocStrType.LocalFindByIndex( item->textHandle ); text = locStr->GetText(); } if ( text[ 0 ] != L'\0' ) { sdBounds2D itemBounds( itemRect.x, itemRect.y, itemRect.z, itemRect.w ); if( truncateText ) { ShortenText( text, itemBounds, cachedFontHandle, item->textFlags, fontSize, truncationText, truncationWidth, builder ); text = builder.c_str(); } DrawText( text, selected ? selectedItemForeColor.GetValue() : transition.foreColor.Evaluate( now ), fontSize, itemBounds, cachedFontHandle, ( item->textFlags | extraTextFlags ) & ~removeFlags ); } } firstColumn = false; } if( clip ) { deviceContext->PopClipRect(); } xCoord += columnWidths[ i ]; } PostDraw(); if ( borderWidth > 0.0f ) { deviceContext->DrawClippedBox( cachedClientRect.x, cachedClientRect.y, cachedClientRect.z, cachedClientRect.w, borderWidth, borderColor ); } } /* ============ sdUIList::ApplyLayout ============ */ void sdUIList::ApplyLayout() { if( windowState.recalculateLayout ) { BeginLayout(); if( cachedFontHandle == -1 ) { gameLocal.Warning( "%s: '%s' has no font set", GetUI()->GetName(), name.GetValue().c_str() ); } columnHeight.SetReadOnly( false ); columnHeight = 0.0f; int numItems = 0; for( int i = 0; i < columns.Num(); i++ ) { sdUIListColumn* column = columns[ i ]; numItems = Max( numItems, column->items.Num() ); } if ( TestFlag( LF_SHOW_HEADINGS ) ) { columnHeight = deviceContext->GetFontHeight( cachedFontHandle, columnFontSize ) + 2.0f; } columnHeight.SetReadOnly( true ); cachedClientRect = clientRect; cachedClientRect.ToVec2() += CalcWorldOffset(); if( TestFlag( LF_VARIABLE_HEIGHT_ROWS ) ) { float* columnWidths = static_cast< float* >( _alloca( columns.Num() * sizeof( float ) ) ); CalcColumnWidths( columnWidths ); idVec4 rect; int height; if( fixedRowHeight > 0.0f ) { height = fixedRowHeight; } else { height = deviceContext->GetFontHeight( cachedFontHandle, fontSize ); } for( int i = 0; i < columns.Num(); i++ ) { sdUIListColumn* c = columns[ i ]; for( int j = 0; j < c->items.Num(); j++ ) { const sdUIListItem& item = *c->items[ j ]; // reset to default height first if( i == 0 ) { rowHeights[ j ] = height; } GetItemRect( j, i, rect, columnWidths ); rect.w = height; int w, h; const wchar_t* text = item.textHandle != -1 ? declHolder.declLocStrType.LocalFindByIndex( item.textHandle )->GetText() : item.text.c_str(); if( text[ 0 ] != L'\0' ) { deviceContext->GetTextDimensions( text, sdBounds2D( rect ), DTF_LEFT | DTF_WORDWRAP | DTF_VCENTER, cachedFontHandle, fontSize, w, h ); // keep the element as a multiple of the fixed row height if( fixedRowHeight > 0.0f ) { float num = idMath::Floor( ( h / fixedRowHeight ) + 0.5f ); h = idMath::Ftoi( num * fixedRowHeight ); } } else { h = height; } rowHeights[ j ] = Max( rowHeights[ j ], h ); } } } else { if( fixedRowHeight > 0.0f ) { for( int i = 0; i < rowHeights.Num(); i++ ) { rowHeights[ i ] = fixedRowHeight; } } else { int fontHeight = Max( 2, deviceContext->GetFontHeight( cachedFontHandle, fontSize ) ); for( int i = 0; i < rowHeights.Num(); i++ ) { rowHeights[ i ] = fontHeight; } } } bool updateChildren = false; if ( TestFlag( WF_AUTO_SIZE_HEIGHT ) ) { cachedClientRect.w = idMath::ClampFloat( 2.0f, cachedClientRect.w, ( 2.0f * internalBorderWidth ) + columnHeight + GetTotalHeight() ); } scrollAmountMax.SetReadOnly( false ); scrollAmountMax = GetMaxScrollAmount(); scrollAmountMax.SetReadOnly( true ); pageSize.SetReadOnly( false ); pageSize = GetVisibleHeight(); pageSize.SetReadOnly( true ); if( idMath::Fabs( sizeLastColumnDelta ) > 0.0f ) { float* columnWidths = static_cast< float* >( _alloca( columns.Num() * sizeof( float ) ) ); CalcColumnWidths( columnWidths ); int lastVisible = columns.Num() - 1; while( lastVisible >= 0 ){ if( columnWidths[ lastVisible ] > 0.0f ) { break; } lastVisible--; } float totalWidth = 0.0f; int i; for( i = 0; i < lastVisible; i++ ) { totalWidth += columnWidths[ i ]; } // resize the last visible column float newWidth = cachedClientRect.z - totalWidth - sizeLastColumnDelta; if( lastVisible >= 0 && lastVisible < columns.Num() ) { if( columns[ lastVisible ]->flags.widthPercent ) { float constantColumnWidths = 0.0f; int i; for ( i = 0; i < columns.Num(); i++ ) { sdUIListColumn* column = columns[ i ]; if( !column->flags.widthPercent ) { constantColumnWidths += column->baseWidth; } } float percent = newWidth / ( cachedClientRect.z - constantColumnWidths ); columns[ lastVisible ]->baseWidth = percent; } else { columns[ lastVisible ]->baseWidth = newWidth; } } sizeLastColumnDelta = 0.0f; } if( scrollToItem != -1 ) { ScrollToItem( scrollToItem ); scrollToItem = -1; } if( storedScrollAmount >= 0.0f ) { scrollAmount = idMath::ClampFloat( 0.0f, GetMaxScrollAmount(), storedScrollAmount ); storedScrollAmount = -1.0f; } EndLayout(); } sdUIObject::ApplyLayout(); } /* ============ sdUIList::CheckHeaderClick ============ */ bool sdUIList::CheckHeaderClick( const sdSysEvent* event, const idVec2& point ) { if( !TestFlag( LF_SHOW_HEADINGS ) ) { return false; } idVec4 rect; int selectedColumn = -1; float* columnWidths = static_cast< float* >( _alloca( columns.Num() * sizeof( float ) ) ); CalcColumnWidths( columnWidths ); for( int i = 0; i < columns.Num(); i++ ) { GetHeaderColumnRect( i, rect, columnWidths ); sdBounds2D bounds( rect.x, rect.y, rect.z, rect.w ); if( bounds.ContainsPoint( point )) { selectedColumn = i; break; } } if( selectedColumn > -1 && TestFlag( LF_COLUMN_SORT ) ) { if( columns[ selectedColumn ]->flags.allowSort ) { int active = idMath::Ftoi( activeColumn ); if( active == selectedColumn ) { columns[ active ]->flags.sortDescending ^= true; activeColumnSort = columns[ active ]->flags.sortDescending ? -1.0f : 1.0f; } activeColumn = selectedColumn; Sort(); } } if( selectedColumn > -1 ) { RunEvent( sdUIEventInfo( LE_CLICK_COLUMN, 0 ) ); } return selectedColumn != -1; } /* ============ sdUIList::CheckItemClick ============ */ bool sdUIList::CheckItemClick( const sdSysEvent* event, const idVec2& point ) { idVec4 rect; for( int j = 0; j < GetNumItems(); j++ ) { GetItemRect( j, rect ); sdBounds2D bounds( rect.x, rect.y, rect.z, rect.w ); if( bounds.ContainsPoint( point )) { currentSelection = j; return true; } } return false; } /* ============ sdUIList::HandleKeyInput ============ */ bool sdUIList::HandleKeyInput( const sdSysEvent* event ) { if ( TestFlag( LF_NO_KEY_EVENTS ) ) { return false; } bool retVal = false; if ( event->IsMouseButtonEvent() ) { mouseButton_t mb = event->GetMouseButton(); if( GetNumItems() > 0 ) { int index; switch( mb ) { case M_MWHEELUP: index = idMath::ClampInt( 0, GetNumItems() - 1, GetFirstVisibleItem() ); Scroll( -rowHeights[ index ] ); retVal = true; break; case M_MWHEELDOWN: index = idMath::ClampInt( 0, GetNumItems() - 1, GetLastVisibleItem( GetFirstVisibleItem() ) ); Scroll( rowHeights[ index ] ); retVal = true; break; } } } else if ( event->IsKeyEvent() ) { keyNum_t key = event->GetKey(); switch( key ) { case K_UPARROW: case K_KP_UPARROW: if( currentSelection > 0 ) { currentSelection = Max( 0, idMath::Ftoi( currentSelection - 1.0f ) ); } retVal = true; break; case K_DOWNARROW: case K_KP_DOWNARROW: currentSelection = Min( GetNumItems() - 1, idMath::Ftoi( currentSelection + 1.0f )); retVal = true; break; case K_HOME: case K_KP_HOME: currentSelection = GetNumItems() ? 0 : -1; retVal = true; break; case K_END: case K_KP_END: currentSelection = GetNumItems() - 1; retVal = true; break; case K_PGUP: case K_KP_PGUP: scrollAmount = idMath::ClampFloat( 0.0f, GetMaxScrollAmount(), scrollAmount - pageSize ); retVal = true; break; case K_PGDN: case K_KP_PGDN: scrollAmount = idMath::ClampFloat( 0.0f, GetMaxScrollAmount(), scrollAmount + pageSize ); retVal = true; break; } } return retVal; } /* ============ sdUIList::PostEvent ============ */ bool sdUIList::PostEvent( const sdSysEvent* event ) { if( windowState.fullyClipped || !IsVisible() || !ParentsAllowEventPosting() ) { return false; } idVec2 point( ui->cursorPos ); bool retVal = false; bool hitItem = false; if ( IsMouseClick( event ) && !cachedClippedRect.ContainsPoint( point ) ) { return false; } if( columns.Num() > 0 ) { if( event->IsMouseEvent() ) { if( TestFlag( LF_HOT_TRACK ) ) { retVal |= CheckItemClick( event, point ); } bool hitColumn = false; // check for mouse enter/exit of column headings if( TestFlag( LF_SHOW_HEADINGS ) ) { hitColumn = CheckHeaderMouseOver( cachedClippedRect, point ); } if( !hitColumn ) { CheckItemMouseOver( cachedClippedRect, point ); } } else if ( event->IsMouseButtonEvent() ) { if ( event->IsButtonDown() ) { mouseButton_t mb = event->GetMouseButton(); // handle item selection if ( mb >= M_MOUSE1 && mb <= M_MOUSE12 ) { bool result = CheckItemClick( event, point ); retVal |= result; retVal |= CheckHeaderClick( event, point ); if ( !result && !TestFlag( LF_NO_NULL_SELECTION )) { currentSelection = -1; } } else if ( GetUI()->IsFocused( this ) ) { retVal |= HandleKeyInput( event ); } } } else if ( event->IsKeyEvent() ) { if ( event->IsKeyDown() ) { if ( GetUI()->IsFocused( this ) ) { retVal |= HandleKeyInput( event ); } } } } retVal |= sdUIWindow::PostEvent( event ); return retVal; } /* ============ sdUIList::CheckHeaderMouseOver ============ */ bool sdUIList::CheckHeaderMouseOver( const sdBounds2D& windowBounds, const idVec2& point ) { bool retVal = false; if( windowBounds.ContainsPoint( point ) ) { float* columnWidths = static_cast< float* >( _alloca( columns.Num() * sizeof( float ) ) ); CalcColumnWidths( columnWidths ); idVec4 rect; for( int i = 0; i < columns.Num(); i++ ) { GetHeaderColumnRect( i, rect, columnWidths ); sdBounds2D bounds( rect.x, rect.y, rect.z, rect.w ); if( bounds.ContainsPoint( point ) ) { if( !columns[ i ]->flags.mouseFocused ) { GetUI()->PushScriptVar( i ); RunEvent( sdUIEventInfo( LE_ENTER_COLUMN, 0 ) ); columns[ i ]->flags.mouseFocused = true; retVal = true; } } else if( columns[ i ]->flags.mouseFocused ) { GetUI()->PushScriptVar( i ); RunEvent( sdUIEventInfo( LE_EXIT_COLUMN, 0 ) ); columns[ i ]->flags.mouseFocused = false; } } } else { for( int i = 0; i < columns.Num(); i++ ) { if( columns[ i ]->flags.mouseFocused ) { GetUI()->PushScriptVar( i ); RunEvent( sdUIEventInfo( LE_EXIT_COLUMN, 0 ) ); columns[ i ]->flags.mouseFocused = false; } } } return retVal; } /* ============ sdUIList::CheckItemMouseOver ============ */ bool sdUIList::CheckItemMouseOver( const sdBounds2D& windowBounds, const idVec2& point ) { bool retVal = false; if( windowBounds.ContainsPoint( point ) ) { idVec4 rect; int firstVisible = GetFirstVisibleItem(); int lastVisibleItem = firstVisible; if( firstVisible >=0 ) { lastVisibleItem = GetLastVisibleItem( firstVisible ); } float* columnWidths = static_cast< float* >( _alloca( columns.Num() * sizeof( float ) ) ); CalcColumnWidths( columnWidths ); float baseX = cachedClientRect.x + ( 2.0f * ( borderWidth + internalBorderWidth ) ) - columnBorder; for( int i = 0; i < columns.Num(); baseX += columnWidths[ i ], i++ ) { sdUIListColumn* col = columns[ i ]; for( int j = firstVisible; j <= lastVisibleItem; j++ ) { sdUIListItem* item = col->items[ j ]; if( point.x < baseX || point.x > ( baseX + columnWidths[ i ] ) ) { if( item->flags.mouseFocused ) { GetUI()->PushScriptVar( i ); GetUI()->PushScriptVar( j ); RunEvent( sdUIEventInfo( LE_EXIT_ITEM, 0 ) ); GetUI()->ClearScriptStack(); item->flags.mouseFocused = false; } continue; } GetItemRect( j, i, rect, columnWidths ); sdBounds2D bounds( rect.x, rect.y, rect.z, rect.w ); if( bounds.ContainsPoint( point ) ) { if( !item->flags.mouseFocused ) { GetUI()->PushScriptVar( i ); GetUI()->PushScriptVar( j ); RunEvent( sdUIEventInfo( LE_ENTER_ITEM, 0 ) ); GetUI()->ClearScriptStack(); item->flags.mouseFocused = true; retVal = true; } } else if( item->flags.mouseFocused ) { GetUI()->PushScriptVar( i ); GetUI()->PushScriptVar( j ); RunEvent( sdUIEventInfo( LE_EXIT_ITEM, 0 ) ); GetUI()->ClearScriptStack(); item->flags.mouseFocused = false; } } } } else { for( int i = 0; i < columns.Num(); i++ ) { sdUIListColumn* col = columns[ i ]; for( int j = 0; j < col->items.Num(); j++ ) { sdUIListItem* item = col->items[ j ]; if( item->flags.mouseFocused ) { GetUI()->PushScriptVar( i ); GetUI()->PushScriptVar( j ); RunEvent( sdUIEventInfo( LE_EXIT_ITEM, 0 ) ); GetUI()->ClearScriptStack(); item->flags.mouseFocused = false; } } } } return retVal; } /* ============ sdUIList::InsertItem ============ */ int sdUIList::InsertItem( sdUIWindow* list, const wchar_t* name, int item, int column ) { assert( list ); sdUIFunctionStack stack; stack.Push( column ); // column stack.Push( item ); // index stack.Push( name ); if ( !list->RunNamedMethod( "insertItem", stack ) ) { return -1; } float retVal; stack.Pop( retVal ); return idMath::Ftoi( retVal ); } /* ============ sdUIList::DeleteItem ============ */ void sdUIList::DeleteItem( sdUIWindow* list, int item ) { assert( list ); sdUIFunctionStack stack; stack.Push( item ); // index list->RunNamedMethod( "deleteItem", stack ); } /* ============ sdUIList::SetItemText ============ */ void sdUIList::SetItemText( sdUIWindow* list, const wchar_t* name, int item, int column ) { assert( list ); sdUIFunctionStack stack; stack.Push( column ); // column stack.Push( item ); // index stack.Push( name ); list->RunNamedMethod( "setItemText", stack ); } /* ============ sdUIList::SetItemForeColor ============ */ void sdUIList::SetItemForeColor( sdUIWindow* list, const idVec4& color, int item, int column ) { assert( list ); sdUIFunctionStack stack; stack.Push( column ); // column stack.Push( item ); // index stack.Push( color ); list->RunNamedMethod( "setItemForeColor", stack ); } /* ============ sdUIList::SetItemBackColor ============ */ void sdUIList::SetItemBackColor( sdUIWindow* list, const idVec4& color, int item, int column ) { assert( list ); sdUIFunctionStack stack; stack.Push( column ); // column stack.Push( item ); // index stack.Push( color ); list->RunNamedMethod( "setItemBackColor", stack ); } /* ============ sdUIList::ClearItems ============ */ void sdUIList::ClearItems( sdUIWindow* list ) { assert( list ); sdUIFunctionStack s; list->RunNamedMethod( "clearItems", s ); } /* ============ sdUIList::ClearColumns ============ */ void sdUIList::ClearColumns( sdUIWindow* list ) { assert( list ); sdUIFunctionStack s; list->RunNamedMethod( "clearColumns", s ); } /* ============ sdUIList::SetItemIcon ============ */ void sdUIList::SetItemIcon( sdUIWindow* list, const char* iconMaterial, int item, int column ) { assert( list ); sdUIFunctionStack stack; stack.Push( column ); stack.Push( item ); stack.Push( iconMaterial ); list->RunNamedMethod( "setItemIcon", stack ); } /* ============ sdUIList::EnumerateEvents ============ */ void sdUIList::EnumerateEvents( const char* name, const idList& flags, idList< sdUIEventInfo >& events, const idTokenCache& tokenCache ) { if ( !idStr::Icmp( name, "onSelectItem" ) ) { events.Append( sdUIEventInfo( LE_SELECT_ITEM, 0 ) ); return; } if ( !idStr::Icmp( name, "onItemAdded" ) ) { events.Append( sdUIEventInfo( LE_ITEM_ADDED, 0 ) ); return; } if ( !idStr::Icmp( name, "onItemRemoved" ) ) { events.Append( sdUIEventInfo( LE_ITEM_REMOVED, 0 ) ); return; } if ( !idStr::Icmp( name, "onDrawSelectedBackground" ) ) { events.Append( sdUIEventInfo( LE_DRAW_SELECTED_BACKGROUND, 0 ) ); return; } if ( !idStr::Icmp( name, "onDrawItemBackground" ) ) { events.Append( sdUIEventInfo( LE_DRAW_ITEM_BACKGROUND, 0 ) ); return; } if ( !idStr::Icmp( name, "onDrawItem" ) ) { events.Append( sdUIEventInfo( LE_DRAW_ITEM, 0 ) ); return; } if ( !idStr::Icmp( name, "onDrawColumn" ) ) { events.Append( sdUIEventInfo( LE_DRAW_COLUMN, 0 ) ); return; } if ( !idStr::Icmp( name, "onEnterColumnHeader" ) ) { events.Append( sdUIEventInfo( LE_ENTER_COLUMN, 0 ) ); return; } if ( !idStr::Icmp( name, "onExitColumnHeader" ) ) { events.Append( sdUIEventInfo( LE_EXIT_COLUMN, 0 ) ); return; } if ( !idStr::Icmp( name, "onClickColumnHeader" ) ) { events.Append( sdUIEventInfo( LE_CLICK_COLUMN, 0 ) ); return; } if ( !idStr::Icmp( name, "onEnterItem" ) ) { events.Append( sdUIEventInfo( LE_ENTER_ITEM, 0 ) ); return; } if ( !idStr::Icmp( name, "onExitItem" ) ) { events.Append( sdUIEventInfo( LE_EXIT_ITEM, 0 ) ); return; } sdUIWindow::EnumerateEvents( name, flags, events, tokenCache ); } /* ============ sdUIList::GetVisibleItemOffset ============ */ float sdUIList::GetVisibleItemOffset( int item ) const { if( item >= GetNumItems() || item < 0 ) { return 0.0f; } const float headerHeight = GetHeaderHeight(); idVec4 rect; GetItemRect( item, rect ); float rectBottom = rect.y + rect.w; float clientRectBottom = cachedClientRect.y + cachedClientRect.w - ( borderWidth + internalBorderWidth ); // item is above the top of the list if( rect.y <= cachedClientRect.y + headerHeight + ( borderWidth + internalBorderWidth ) ) { return rect.y - ( headerHeight + cachedClientRect.y + ( borderWidth + internalBorderWidth ) ); } // item is below the bottom of the list if( rectBottom >= clientRectBottom ) { return rectBottom - clientRectBottom; } return 0.0f; } /* ============ sdUIList::OnFixedRowHeightChanged ============ */ void sdUIList::OnFixedRowHeightChanged( const float oldValue, const float newValue ) { MakeLayoutDirty(); } /* ============ sdUIList::OnInternalBorderChanged ============ */ void sdUIList::OnInternalBorderChanged( const float oldValue, const float newValue ) { MakeLayoutDirty(); } /* ============ sdUIList::OnActiveColumnChanged ============ */ void sdUIList::OnActiveColumnChanged( const float oldValue, const float newValue ) { if( TestFlag( LF_COLUMN_SORT ) ) { Sort(); } } /* ============ sdUIList::Sort ============ */ void sdUIList::Sort() { if( columns.Num() == 0 || TestFlag( LF_DIRECT_UPDATES ) ) { return; } int numRows = columns[ 0 ]->items.Num(); // this needs to be setup regardless of whether we sort or not int index; indices.AssureSize( numRows ); for( index = 0; index < numRows; index++ ) { indices[ index ] = index; } // nothing to do... if( inBatchOperation || numRows < 2 || !TestFlag( LF_COLUMN_SORT ) ) { return; } int active = idMath::Ftoi( activeColumn ); sdUIListColumn* column = active == -1 ? columns[ 0 ] : columns[ active ]; // quick-sort the indices based on the active column, then fix up all of the columns if( column->flags.dataSort ) { Sort( sdColumnSortData( *column ) ); } else if( column->flags.numericSort ) { Sort( sdColumnSortNumeric( *column ) ); } else { Sort( sdColumnSort( *column ) ); } if( column->flags.sortDescending ) { for( index = 0; index < numRows / 2; index++ ) { idSwap( indices[ index ], indices[ numRows - index - 1 ] ); } } } /* ============ sdUIList::ExtractTextFormatFromString ============ */ void sdUIList::ExtractTextFormatFromString( const wchar_t* format, const wchar_t* indicator, int& flags ) { flags = DTF_SINGLELINE; bool horizontalSet = false; bool verticalSet = false; int length = idWStr::Length( format ); int offset = 0; while( offset <= length ) { if( format[ offset ] != L'<' ) { offset++; continue; } idWLexer src( format + offset, idWStr::Length( format + offset ), "ExtractTextFormatFromString", LEXFL_NOERRORS ); while( true ) { if( !src.ReadToken( &token ) ) { break; } if( token.Cmp( L">" ) == 0 ) { break; } if( token.Cmp( L"<" ) != 0 ) { continue; } if( !src.ReadToken( &token ) ) { src.Warning( "'%s' bad text format string", name.GetValue().c_str() ); break; } if( token.Icmp( indicator ) != 0 ) { continue; } if( !src.ExpectTokenString( L"=" )) { break; } if( !src.ReadToken( &token ) ) { src.Warning( "'%s' bad text format string", name.GetValue().c_str() ); break; } while( src.ReadToken( &token ) ) { if( token.Cmp( L"," ) == 0 ) { continue; } if( token.Icmp( L"right" ) == 0 ) { flags |= DTF_RIGHT; horizontalSet = true; continue; } if( token.Icmp( L"left" ) == 0 ) { flags |= DTF_LEFT; horizontalSet = true; continue; } if( token.Icmp( L"center" ) == 0 ) { flags |= DTF_CENTER; horizontalSet = true; continue; } if( token.Icmp( L"top" ) == 0 ) { flags |= DTF_TOP; verticalSet = true; continue; } if( token.Icmp( L"bottom" ) == 0 ) { flags |= DTF_BOTTOM; verticalSet = true; continue; } if( token.Icmp( L"vcenter" ) == 0 ) { flags |= DTF_VCENTER; verticalSet = true; continue; } if( token.Cmp( L">" ) == 0 ) { break; } } break; } offset += src.GetFileOffset(); } if( !horizontalSet ) { flags |= DTF_LEFT; } if( !verticalSet ) { flags |= DTF_BOTTOM; } } /* ============ sdUIList::ExtractColumnWidthFromString ============ */ void sdUIList::ExtractColumnWidthFromString( const wchar_t* format, const wchar_t* indicator, sdUIListColumn& column ) { column.flags.widthPercent = false; int length = idWStr::Length( format ); int offset = 0; while( offset <= length ) { if( format[ offset ] != L'<' ) { offset++; continue; } idWLexer src( format + offset, idWStr::Length( format + offset ), "ExtractColumnWidthFromString", LEXFL_NOERRORS ); while( true ) { if( !src.ReadToken( &token ) ) { break; } if( token.Cmp( L">" ) == 0 ) { break; } if( token.Cmp( L"<" ) != 0 ) { continue; } if( !src.ReadToken( &token ) ) { src.Warning( "'%s' bad format string '%ls'", name.GetValue().c_str(), format ); break; } if( token.Icmp( indicator ) != 0 ) { continue; } if( !src.ExpectTokenString( L"=" )) { break; } if( !src.ReadToken( &token ) ) { src.Warning( "'%s' bad format string '%ls'", name.GetValue().c_str(), format ); break; } if( token.type != TT_NUMBER ) { src.Warning( "'%s' bad format string, expected number but found '%ls'", name.GetValue().c_str(), format ); break; } column.baseWidth = token.GetFloatValue(); if( src.ReadToken( &token ) ) { if( token.Cmp( L"%" ) == 0 ) { column.flags.widthPercent = true; column.baseWidth /= 100.0f; } else { src.Warning( "'%s' bad format string, unknown token '%ls'", name.GetValue().c_str(), format ); break; } } if( !src.ExpectTokenString( L">" )) { src.Warning( "'%s' bad text format string", name.GetValue().c_str() ); } break; } offset += src.GetFileOffset(); } } /* ============ sdUIList::ExtractLocalizedTextFromString ============ */ void sdUIList::ExtractLocalizedTextFromString( const wchar_t* format, const wchar_t* indicator, int& handle ) { handle = -1; int length = idWStr::Length( format ); int offset = 0; while( offset <= length ) { if( format[ offset ] != L'<' ) { offset++; continue; } idWLexer src( format + offset, idWStr::Length( format + offset ), "ExtractLocalizedTextFromString", LEXFL_NOERRORS ); while( true ) { if( !src.ReadToken( &token ) ) { break; } if( token.Cmp( L">" ) == 0 ) { break; } if( token.Cmp( L"<" ) != 0 ) { continue; } if( !src.ReadToken( &token ) ) { src.Warning( "'%s' bad format string '%ls'", name.GetValue().c_str(), format ); break; } if( token.Icmp( indicator ) != 0 ) { continue; } if( !src.ExpectTokenString( L"=" )) { break; } if( !src.ReadToken( &token ) ) { src.Warning( "'%s' bad format string '%ls'", name.GetValue().c_str(), format ); break; } const sdDeclLocStr* loc = declHolder.declLocStrType.LocalFind( va( "%ls", token.c_str() ) ); handle = loc->Index(); if( !src.ExpectTokenString( L">" )) { src.Warning( "'%s' bad format string '%ls'", name.GetValue().c_str(), format ); } break; } offset += src.GetFileOffset(); } } /* ============ sdUIList::ExtractColorFromString ============ */ bool sdUIList::ExtractColorFromString( const wchar_t* format, const wchar_t* indicator, idVec4& color ) { int length = idWStr::Length( format ); int offset = 0; bool set = false; while( offset <= length ) { if( format[ offset ] != L'<' ) { offset++; continue; } idWLexer src( format + offset, idWStr::Length( format + offset ), "ExtractColorFromString", LEXFL_NOERRORS ); while( true ) { if( !src.ReadToken( &token ) ) { break; } if( token.Cmp( L">" ) == 0 ) { break; } if( token.Cmp( L"<" ) != 0 ) { continue; } if( !src.ReadToken( &token ) ) { src.Warning( "'%s' bad format string '%ls'", name.GetValue().c_str(), format ); break; } if( token.Icmp( indicator ) != 0 ) { continue; } if( !src.ExpectTokenString( L"=" )) { break; } idVec4 temp; int i = 0; while( true ) { if( i >= 4 ) { break; } if( !src.ReadToken( &token )) { break; } if( !token.Cmp( L"," )) { continue; } if( !token.Cmp( L">" )) { break; } temp[ i ] = token.GetFloatValue(); i++; } color = temp; set = true; } offset += src.GetFileOffset(); } return set; } /* ============ sdUIList::ExtractMaterialFromString ============ */ void sdUIList::ExtractMaterialFromString( const wchar_t* format, const wchar_t* indicator, uiDrawPart_t& part ) { part.mi.Clear(); part.height = part.width = 0; int length = idWStr::Length( format ); int offset = 0; while( offset <= length ) { if( format[ offset ] != L'<' ) { offset++; continue; } idWLexer src( format + offset, idWStr::Length( format + offset ), "ExtractMaterialFromString", LEXFL_NOERRORS ); while( true ) { if( !src.ReadToken( &token ) ) { break; } if( token.Cmp( L">" ) == 0 ) { break; } if( token.Cmp( L"<" ) != 0 ) { continue; } if( !src.ReadToken( &token ) ) { src.Warning( "'%s' bad format string '%ls'", name.GetValue().c_str(), format ); break; } if( token.Icmp( indicator ) != 0 ) { continue; } if( !src.ReadToken( &token ) ) { src.Warning( "'%s' bad format string '%ls'", name.GetValue().c_str(), format ); break; } if ( token.Icmp( L"=" ) != 0 ) { break; } if( !src.ReadToken( &token ) ) { src.Warning( "'%s' bad format string '%ls'", name.GetValue().c_str(), format ); break; } if( token.Length() ) { idStr material; bool globalLookup; bool literal; uiDrawMode_e mode; // FIXME: support this at some point int offset = sdUserInterfaceLocal::ParseMaterial( va( "%ls", token.c_str() ), material, globalLookup, literal, mode ); if( globalLookup ) { material = "::" + material; } if( literal ) { GetUI()->LookupMaterial( va( "literal: %ls", token.c_str() + offset ), part.mi ); } else if( ( material.Length() && !globalLookup ) || ( material.Length() > 2 && globalLookup ) ) { GetUI()->LookupMaterial( material, part.mi ); } } if( !src.ExpectTokenString( L">" )) { src.Warning( "'%s' bad material format string", name.GetValue().c_str() ); break; } } offset += src.GetFileOffset(); } } /* ============ sdUIList::StripFormatting ============ */ void sdUIList::StripFormatting( idWStr& string ) { int start = string.Find( L"<" ); while( start != idWStr::INVALID_POSITION ) { int end = string.Find( L">", false, start + 1 ); if( end == idWStr::INVALID_POSITION ) { break; } string.EraseRange( start, end - start + 1 ); start = string.Find( L"<" ); } string.Replace( L"<", L"<" ); string.Replace( L">", L">" ); } /* ============ sdUIList::OnCurrentItemChanged ============ */ void sdUIList::OnCurrentItemChanged( const float oldValue, const float newValue ) { if ( newValue >= 0.0f ) { if( TestFlag( LF_AUTO_SCROLL_TO_SELECTION )) { scrollToItem = idMath::FtoiFast( newValue ); MakeLayoutDirty(); } RunEvent( sdUIEventInfo( LE_SELECT_ITEM, 0 ) ); } if ( sdUserInterfaceLocal::g_debugGUIEvents.GetInteger() > 2 && ui ) { gameLocal.Printf( "GUI '%s', window '%s',selected item '%i'\n", ui->GetName(), name.GetValue().c_str(), idMath::Ftoi( currentSelection )); } lastClickTime = -1; } /* ============ sdUIList::SelectItem ============ */ void sdUIList::SelectItem( int item ) { if( columns.Num() == 0 || item >= columns[ 0 ]->items.Num() ) { gameLocal.Warning( "SelectItem: '%s' %i out of range", name.GetValue().c_str(), item ); return; } currentSelection = item; } /* ============ sdUIList::SetItemDataInt ============ */ void sdUIList::SetItemDataInt( int integer, int item, int column, bool direct ) { sdUIListItem* listItem = GetItem( item, column, "SetItemDataInt", direct ); if( listItem != NULL ) { listItem->data.integer = integer; } } /* ============ sdUIList::SetItemDataPtr ============ */ void sdUIList::SetItemDataPtr( void* ptr, int item, int column, bool direct ) { sdUIListItem* listItem = GetItem( item, column, "SetItemDataPtr", direct ); if( listItem != NULL ) { listItem->data.ptr = ptr; } } /* ============ sdUIList::GetItemDataInt ============ */ int sdUIList::GetItemDataInt( int item, int column, bool direct ) const { const sdUIListItem* listItem = GetItem( item, column, "GetItemDataInt", direct ); if( listItem != NULL ) { return listItem->data.integer; } return 0; } /* ============ sdUIList::GetItemDataPtr ============ */ void* sdUIList::GetItemDataPtr( int item, int column, bool direct ) const { const sdUIListItem* listItem = GetItem( item, column, "GetItemDataPtr", direct ); if( listItem != NULL ) { return listItem->data.ptr; } return NULL; } /* ============ sdUIList::GetItemText ============ */ const wchar_t* sdUIList::GetItemText( int item, int column, bool direct ) const { // return header text if( item == -1 ) { if( column < 0 || column >= columns.Num() ) { gameLocal.Warning( "GetItemText: %s column %i out of range", name.GetValue().c_str(), column ); return L""; } if( columns[ column ]->textHandle != -1 ) { const sdDeclLocStr* locStr = declHolder.declLocStrType.LocalFindByIndex( columns[ column ]->textHandle, false ); if( locStr != NULL ) { return locStr->GetText(); } } return columns[ column ]->text.c_str(); } const sdUIListItem* listItem = GetItem( item, column, "GetItemText", direct ); if( listItem != NULL ) { if( listItem->textHandle != -1 ) { const sdDeclLocStr* locStr = declHolder.declLocStrType.LocalFindByIndex( listItem->textHandle, false ); if( locStr != NULL ) { return locStr->GetText(); } } return listItem->text.c_str(); } return L""; } /* ============ sdUIList::EndLevelLoad ============ */ void sdUIList::EndLevelLoad() { sdUIWindow::EndLevelLoad(); for( int c = 0; c < columns.Num(); c++ ) { sdUIListColumn* col = columns[ c ]; for( int r = 0; r < col->items.Num(); r++ ) { sdUIListItem* item = col->items[ r ]; sdUserInterfaceLocal::LookupPartSizes( &item->part, 1 ); } } } /* ============ sdUIList::InitVec4Transition ============ */ void sdUIList::InitVec4Transition( const idVec4& from, const idVec4& to, const int time, const idStr& accel, vec4Evaluator_t& evaluator ) { evaluator.InitLerp( GetUI()->GetCurrentTime(), GetUI()->GetCurrentTime() + idMath::Ftoi( time ), from, to ); if( !accel.IsEmpty() ) { idLexer src( accel.c_str(), accel.Length(), "TransitionItemVec4", LEXFL_NOERRORS ); idToken token; if( !src.ReadToken( &token ) ) { gameLocal.Error( "TransitionItemVec4: '%s' invalid acceleration", name.GetValue().c_str() ); return; } bool isTable = idStr::Icmpn( token, "table://", 8 ) == 0; if( !isTable ) { idVec2 accelTimes; accelTimes.x = src.ParseFloat(); src.ExpectTokenString( "," ); accelTimes.y = src.ParseFloat(); evaluator.InitAccelDecelEvaluation( accelTimes.x, accelTimes.y ); } else if( token.Length() > 8 ) { evaluator.InitTableEvaluation( token.c_str() + 8 ); } } } /* ============ sdUIList::ClearTransition ============ */ void sdUIList::ClearTransition( sdTransition& transition ) { transition.backColor.InitConstant( backColor ); transition.foreColor.InitConstant( foreColor ); transition.FreeEvaluators(); } /* ============ sdUIListItem::sdUIListItem ============ */ sdUIList::sdUIListItem::sdUIListItem() : textFlags( DTF_LEFT ), textHandle( -1 ) { data.ptr = NULL; flags.customDraw = false; flags.mouseFocused = false; } /* ============ sdUIListItem::~sdUIListItem ============ */ sdUIList::sdUIListItem::~sdUIListItem() { } /* ============ sdUIListItem::GetEvaluator ============ */ sdUIList::vec4Evaluator_t* sdUIList::sdTransition::GetEvaluator( int index, bool allowCreate ) { switch( index ) { case LTP_FORECOLOR: return &foreColor; case LTP_BACKCOLOR: return &backColor; default: if( evaluators == NULL ) { if( !allowCreate ) { return NULL; } evaluators = vec4EvaluatorListAllocator.Alloc(); evaluators->SetNum( MAX_VEC4_EVALUATORS ); } return &(*evaluators)[ index ]; } } /* ============ sdUIListColumn::sdUIListColumn ============ */ sdUIList::sdUIListColumn::sdUIListColumn() : baseWidth( 0.0f ), textHandle( -1 ), textFlags( DTF_LEFT | DTF_BOTTOM | DTF_SINGLELINE ) { flags.sortDescending = false; flags.widthPercent = false; flags.mouseFocused = false; ResetUserFlags(); } /* ============ sdUIList::SetItemGranularity ============ */ void sdUIList::SetItemGranularity( int granularity ) { for( int i = 0; i < columns.Num(); i++ ) { columns[ i ]->items.SetGranularity( granularity ); columns[ i ]->itemTransitions.SetGranularity( granularity ); } } /* ============ sdUIList::FormatItemText ============ */ void sdUIList::FormatItemText( sdUIListItem& item, sdTransition& transition, const wchar_t* text ) { item.text = text; int now = GetUI()->GetCurrentTime(); idVec4 localForeColor = transition.foreColor.Evaluate( now ); idVec4 localBackColor = transition.backColor.Evaluate( now ); if( ExtractColorFromString( text, L"fore", localForeColor ) ) { transition.foreColor.InitConstant( localForeColor ); } if( ExtractColorFromString( text, L"back", localBackColor ) ) { transition.backColor.InitConstant( localBackColor ); } ExtractMaterialFromString( text, L"material", item.part ); ExtractTextFormatFromString( text, L"align", item.textFlags ); ExtractLocalizedTextFromString( text, L"loc", item.textHandle ); ExtractItemFlagsFromString( text, L"flags", item ); StripFormatting( item.text ); } /* ============ sdUIList::ExtractItemFlagsFromString ============ */ void sdUIList::ExtractItemFlagsFromString( const wchar_t* format, const wchar_t* indicator, sdUIListItem& item ) { item.flags.customDraw = false; int length = idWStr::Length( format ); int offset = 0; while( offset <= length ) { if( format[ offset ] != L'<' ) { offset++; continue; } idWLexer src( format + offset, idWStr::Length( format + offset ), "ExtractItemFlagsFromString", LEXFL_NOERRORS ); while( true ) { if( !src.ReadToken( &token ) ) { break; } if( token.Cmp( L">" ) == 0 ) { break; } if( token.Cmp( L"<" ) != 0 ) { continue; } if( !src.ReadToken( &token ) ) { src.Warning( "'%s' bad text format string", name.GetValue().c_str() ); break; } if( token.Icmp( indicator ) != 0 ) { continue; } if( !src.ReadToken( &token ) ) { src.Warning( "'%s' bad text format string", name.GetValue().c_str() ); break; } if( token.Icmp( L"customDraw" ) == 0 ) { item.flags.customDraw = true; } if( !src.ReadToken( &token ) ) { src.Warning( "'%s' bad text format string", name.GetValue().c_str() ); break; } if( token.Cmp( L"," ) == 0 ) { continue; } if( token.Cmp( L">" ) != 0 ) { src.Warning( "'%s' bad text format string", name.GetValue().c_str() ); } break; } offset += src.GetFileOffset(); } } /* ============ sdUIList::DrawItemMaterial ============ */ void sdUIList::DrawItemMaterial( int row, int col, const idVec4& rect, const idVec4& color, bool directIndex ) { if( col < 0 || col >= columns.Num() ) { return; } sdUIListColumn* column = columns[ col ]; float* columnWidths = static_cast< float* >( _alloca( columns.Num() * sizeof( float ) ) ); CalcColumnWidths( columnWidths ); if( row < 0 || row >= column->items.Num() ) { return; } sdUIListItem* item; if( directIndex ) { item = column->items[ row ]; } else { item = column->items[ indices[ row ] ]; } int width = item->part.width > 0 ? item->part.width : idMath::Ftoi( columnWidths[ col ] ); int height = item->part.height > 0 ? item->part.height : rowHeights[ row ]; idVec4 rectLocal = rect; if( columnWidths[ col ] > item->part.width ) { if( item->textFlags & DTF_CENTER ) { rectLocal.x += ( columnWidths[ col ] - item->part.width ) * 0.5f; } else if( item->textFlags & DTF_RIGHT ) { rectLocal.x += ( columnWidths[ col ] - item->part.width ); } } float drawHeight = rowHeights[ row ] - rowSpacing; if( drawHeight > item->part.height ) { if( item->textFlags & DTF_VCENTER ) { rectLocal.y += ( drawHeight - item->part.height ) * 0.5f; } else if( item->textFlags & DTF_BOTTOM ) { rectLocal.y += ( drawHeight - item->part.height ); } } if ( dropShadowOffsetImage != 0.0f ) { DrawMaterial( item->part.mi, rectLocal.x + dropShadowOffsetImage, rectLocal.y + dropShadowOffsetImage, width, height, idVec4( 0.0f, 0.0f, 0.0f, color.w ) ); } DrawMaterial( item->part.mi, rectLocal.x, rectLocal.y, width, height, color ); } /* ============ sdUIList::CleanUserInput ============ */ void sdUIList::CleanUserInput( idWStr& input ) { input.Replace( L"<" , L"<" ); input.Replace( L">" , L">" ); } /* ============ sdUIList::ExtractColumnFlagsFromString ============ */ void sdUIList::ExtractColumnFlagsFromString( const wchar_t* format, const wchar_t* indicator, sdUIListColumn& column ) { column.ResetUserFlags(); int length = idWStr::Length( format ); int offset = 0; while( offset <= length ) { if( format[ offset ] != L'<' ) { offset++; continue; } idWLexer src( format + offset, idWStr::Length( format + offset ), "ExtractColumnFlagsFromString", LEXFL_NOERRORS ); while( true ) { if( !src.ReadToken( &token ) ) { break; } if( token.Cmp( L">" ) == 0 ) { break; } if( token.Cmp( L"<" ) != 0 ) { continue; } if( !src.ReadToken( &token ) ) { src.Warning( "'%s' bad text format string", name.GetValue().c_str() ); break; } if( token.Icmp( indicator ) != 0 ) { continue; } while( src.ReadToken( &token ) ) { if( token.Cmp( L">" ) == 0 ) { break; } if( token.Cmp( L"," ) == 0 ) { continue; } if( token.Icmp( L"customDraw" ) == 0 ) { column.flags.customDraw = true; continue; } if( token.Icmp( L"numeric" ) == 0 ) { column.flags.numericSort = true; continue; } if( token.Icmp( L"noSort" ) == 0 ) { column.flags.allowSort = false; continue; } if( token.Icmp( L"dataSort" ) == 0 ) { column.flags.dataSort = true; continue; } src.Warning( "'%s' bad text format string, unknown toekn '%s'", name.GetValue().c_str(), token.c_str() ); break; } if( token.Cmp( L">" ) != 0 ) { src.Warning( "'%s' bad text format string", name.GetValue().c_str() ); } break; } offset += src.GetFileOffset(); } } /* ============ sdUIList::CalcColumnWidths ============ */ void sdUIList::CalcColumnWidths( float* columnWidths ) const { float constantColumnWidths = 0.0f; int i; for ( i = 0; i < columns.Num(); i++ ) { sdUIListColumn* column = columns[ i ]; if( !column->flags.widthPercent ) { constantColumnWidths += column->baseWidth; } } for ( i = 0; i < columns.Num(); i++ ) { sdUIListColumn* column = columns[ i ]; if( column->flags.widthPercent ) { assert( column->baseWidth >= 0.0f && column->baseWidth <= 1.0f ); columnWidths[ i ] = ( cachedClientRect.z - constantColumnWidths ) * column->baseWidth; } else { columnWidths[ i ] = column->baseWidth; } } }