diff --git a/BMPUtils.h b/BMPUtils.h index db610312..dc00c9f8 100644 --- a/BMPUtils.h +++ b/BMPUtils.h @@ -172,7 +172,7 @@ class CBMP data = newData; } - void RemapLogo( int stripes, const byte *rgb ) + void RemapLogo( int stripes, const byte *rgb, bool horizontal = false ) { // palette is always right after header rgbquad_t *palette = GetPaletteData(); @@ -203,19 +203,19 @@ class CBMP return; const bmp_t *hdr = GetBitmapHdr(); - double lines_per_stripe = hdr->height / (double)stripes; + const double cells_per_stripe = ( horizontal ? hdr->width : hdr->height ) / (double)stripes; byte *data = GetTextureData(); for( int i = 0; i < hdr->height; i++ ) { - int stripe = (int)(( hdr->height - i - 1 ) / lines_per_stripe ); - for( int j = 0; j < hdr->width; j++ ) { byte c = data[i * hdr->width + j]; if( c == 0 ) continue; + int stripe = horizontal ? j / cells_per_stripe : ( hdr->height - i - 1 ) / cells_per_stripe; + // remap to the palette int idx = ( c / 256.0f ) * max_palette_slots; // remap to limited palette idx = (int)((double)( idx ) / stripes) * stripes; // now remap per palette diff --git a/BaseMenu.cpp b/BaseMenu.cpp index 290012ae..47ab997b 100755 --- a/BaseMenu.cpp +++ b/BaseMenu.cpp @@ -37,6 +37,7 @@ cvar_t *ui_show_window_stack; cvar_t *ui_borderclip; cvar_t *ui_prefer_won_background; cvar_t *ui_background_stretch; +cvar_t *ui_logohorizontal; uiStatic_t uiStatic; static CMenuEntry *s_pEntries = NULL; @@ -1157,6 +1158,7 @@ void UI_Init( void ) ui_borderclip = EngFuncs::CvarRegister( "ui_borderclip", "0", FCVAR_ARCHIVE ); ui_prefer_won_background = EngFuncs::CvarRegister( "ui_prefer_won_background", "0", FCVAR_ARCHIVE ); ui_background_stretch = EngFuncs::CvarRegister( "ui_background_stretch", "0", FCVAR_ARCHIVE ); + ui_logohorizontal = EngFuncs::CvarRegister( "ui_logohorizontal", "0", FCVAR_ARCHIVE ); // show cl_predict dialog EngFuncs::CvarRegister( "menu_mp_firsttime2", "1", FCVAR_ARCHIVE ); diff --git a/BaseMenu.h b/BaseMenu.h index 2b442b85..c037e684 100755 --- a/BaseMenu.h +++ b/BaseMenu.h @@ -65,6 +65,7 @@ extern cvar_t *ui_show_window_stack; extern cvar_t *ui_borderclip; extern cvar_t *ui_prefer_won_background; extern cvar_t *ui_background_stretch; +extern cvar_t *ui_logohorizontal; enum EUISounds { diff --git a/controls/ColorPicker.cpp b/controls/ColorPicker.cpp index e1322c89..0832a7fa 100644 --- a/controls/ColorPicker.cpp +++ b/controls/ColorPicker.cpp @@ -39,8 +39,8 @@ void CMenuColorPicker::VidInit() { CMenuBaseItem::VidInit(); - const int hueW = 20; - const int gap = 6; + const int hueW = 20 * uiStatic.scaleX; + const int gap = 6 * uiStatic.scaleX; int side = Q_min( m_scSize.w - hueW - gap, m_scSize.h ); diff --git a/controls/Field.cpp b/controls/Field.cpp index 78913649..1c981d32 100644 --- a/controls/Field.cpp +++ b/controls/Field.cpp @@ -252,6 +252,7 @@ bool CMenuField::KeyDown( int key ) if( iCursor > len ) iCursor = len; } + else handled = false; } else handled = false; } diff --git a/dictgen.py b/dictgen.py index bf76da07..e18a7e77 100755 --- a/dictgen.py +++ b/dictgen.py @@ -48,7 +48,7 @@ def process_file(name): trans = [] # TODO: dumb! It can't find multilines - with open(name, "r") as f: + with open(name, "r", encoding='utf-8') as f: for line in f.readlines(): trans += re.findall(TRANSLATABLE_PATTERN, line) diff --git a/menus/ColorPickerDialog.cpp b/menus/ColorPickerDialog.cpp index 03de8087..9924c888 100644 --- a/menus/ColorPickerDialog.cpp +++ b/menus/ColorPickerDialog.cpp @@ -17,14 +17,15 @@ GNU General Public License for more details. #include "BaseMenu.h" #include "extdll_menu.h" #include "fmtstr.h" +#include "IntegerRangeModel.h" namespace Layout { - static const int DLG_W = 600; - static const int DLG_H = 430; + static const int DLG_W = 720; + static const int DLG_H = 480; static const int PAD = 20; static const int TITLE_H = 28; - static const int PICKER_W = 300; + static const int PICKER_W = 246; static const int PICKER_H = 220; static const int LOGO_W = 180; static const int LOGO_H = 180; @@ -34,7 +35,7 @@ namespace Layout static const int FIELD_H = 28; static const int LABEL_W = 18; static const int MODE_W = 140; - static const int INPUT_X = 102; + static const int INPUT_X = 130; static const int UI_VIRTUAL_HEIGHT = 768; static const int CONTENT_GAP = 16; @@ -44,12 +45,185 @@ namespace Layout static const int LABEL_GAP = 4; static const int LABEL_Y_OFFSET = 4; static const int BTN_GAP = 16; + + static const int SWATCH_ROW_H = 44; + static const int SWATCH_GAP = 8; + + static const int MID_LABEL_AREA = 32; + static const int MID_GAP = 14; } +struct logo_preset_t +{ + const char *name; + int stripes; + byte rgb[MAX_LOGO_STRIPES * 3]; +}; + +static const logo_preset_t g_LogoPresets[] = +{ + { + "Custom", + 1, + { 255, 255, 255 }, + }, + { + "Pride", + 6, + { + 0xE4, 0x03, 0x03, + 0xFF, 0x8C, 0x00, + 0xFF, 0xED, 0x00, + 0x00, 0x80, 0x26, + 0x24, 0x40, 0x8E, + 0x73, 0x29, 0x82, + }, + }, + { + "Lesbian", + 7, + { + 0xD5, 0x2D, 0x00, + 0xEF, 0x76, 0x27, + 0xFF, 0x9A, 0x56, + 0xFF, 0xFF, 0xFF, + 0xD1, 0x62, 0xA4, + 0xB5, 0x56, 0x90, + 0xA3, 0x02, 0x62, + }, + }, + { + "Gay", + 7, + { + 0x07, 0x8D, 0x70, + 0x26, 0xCE, 0xAA, + 0x98, 0xE8, 0xC1, + 0xFF, 0xFF, 0xFF, + 0x7B, 0xAD, 0xE2, + 0x50, 0x49, 0xCC, + 0x3D, 0x1A, 0x78, + }, + }, + { + "Bi", + 5, + { + 0xD6, 0x02, 0x70, + 0xD6, 0x02, 0x70, + 0x9B, 0x4F, 0x96, + 0x00, 0x38, 0xA8, + 0x00, 0x38, 0xA8, + }, + }, + { + "Trans", + 5, + { + 0x5B, 0xCE, 0xFA, + 0xF5, 0xA9, 0xB8, + 0xFF, 0xFF, 0xFF, + 0xF5, 0xA9, 0xB8, + 0x5B, 0xCE, 0xFA, + }, + }, + { + "Pan", + 3, + { + 0xFF, 0x21, 0x8C, + 0xFF, 0xD8, 0x00, + 0x21, 0xB1, 0xFF, + }, + }, + { + "Nonbinary", + 4, + { + 0xFC, 0xF4, 0x34, + 0xFF, 0xFF, 0xFF, + 0x9C, 0x59, 0xD1, + 0x2C, 0x2C, 0x2C, + }, + }, +}; + CMenuColorPickerDialog::CMenuColorPickerDialog() : CMenuBaseWindow( "CMenuColorPickerDialog" ), - m_modeRGB( true ), m_updatingFields( false ) + m_modeRGB( true ), m_updatingFields( false ), + m_detectPresetPending( false ), m_horizontal( false ), + m_stripeCount( 1 ), m_selectedStripe( 0 ) +{ + memset( m_stripes, 255, sizeof( m_stripes )); +} + +CMenuColorPickerDialog::CSwatchRow::CSwatchRow() +{ + size.w = 0; + size.h = Layout::SWATCH_ROW_H; +} + +void CMenuColorPickerDialog::CSwatchRow::GetSwatchRect( int idx, Point &p, Size &s ) const +{ + const int count = *stripeCount; + const int gap = Layout::SWATCH_GAP * uiStatic.scaleX; + int sw = ( m_scSize.w - gap * ( count - 1 )) / count; + if( sw < 1 ) sw = 1; + + p.x = m_scPos.x + idx * ( sw + gap ); + p.y = m_scPos.y; + s.w = sw; + s.h = m_scSize.h; +} + +void CMenuColorPickerDialog::CSwatchRow::Draw() +{ + const int count = *stripeCount; + const int sel = *selected; + + for( int i = 0; i < count; i++ ) + { + Point p; Size s; + GetSwatchRect( i, p, s ); + + EngFuncs::FillRGBA( p.x, p.y, s.w, s.h, stripes[i][0], stripes[i][1], stripes[i][2], 255 ); + + if( i == sel ) + { + UI_DrawRectangle( p, s, PackRGBA( 255, 255, 255, 255 )); + Point p2 = { p.x + 1, p.y + 1 }; + Size s2 = { s.w - 2, s.h - 2 }; + UI_DrawRectangle( p2, s2, PackRGBA( 0, 0, 0, 255 )); + } + else + { + UI_DrawRectangle( p, s, PackRGBA( 0, 0, 0, 200 )); + } + } +} + +bool CMenuColorPickerDialog::CSwatchRow::KeyDown( int key ) { + if( key != K_MOUSE1 ) + return false; + + const int count = *stripeCount; + for( int i = 0; i < count; i++ ) + { + Point p; Size s; + GetSwatchRect( i, p, s ); + if( !UI_CursorInRect( p, s )) + continue; + + if( *selected != i ) + { + *selected = i; + if( onSwatchSelected ) + onSwatchSelected( this ); + } + return true; + } + return false; } void CMenuColorPickerDialog::_Init() @@ -61,17 +235,70 @@ void CMenuColorPickerDialog::_Init() m_picker.SetRect( Layout::PAD, currentY, Layout::PICKER_W, Layout::PICKER_H ); SET_EVENT_MULTI( m_picker.onChanged, { - CMenuColorPickerDialog *dlg = (CMenuColorPickerDialog *)pSelf->Parent(); - dlg->UpdateLogoPreview(); - dlg->UpdateFieldsFromPicker(); + ((CMenuColorPickerDialog *)pSelf->Parent())->OnPickerChanged(); }); int logoX = Layout::DLG_W - Layout::PAD - Layout::LOGO_W; int logoY = currentY + ( Layout::PICKER_H - Layout::LOGO_H ) / 2; m_logoPreviewItem.SetRect( logoX, logoY, Layout::LOGO_W, Layout::LOGO_H ); + m_logoPreviewItem.stripes = m_stripes; + m_logoPreviewItem.stripeCount = m_stripeCount; + m_logoPreviewItem.horizontal = &m_horizontal; + + const int midX = Layout::PAD + Layout::PICKER_W + Layout::MID_GAP; + const int midW = Layout::DLG_W - 2 * Layout::PAD - Layout::LOGO_W - 2 * Layout::MID_GAP - Layout::PICKER_W; + const int rowStep = Layout::MID_LABEL_AREA + Layout::FIELD_H + Layout::MID_GAP; + int rowY = currentY; + + static CIntegerRangeModel countModel( 1, MAX_LOGO_STRIPES ); + m_stripeCountSpin.Setup( &countModel ); + m_stripeCountSpin.SetCurrentValue( 0.f ); + m_stripeCountSpin.SetRect( midX, rowY + Layout::MID_LABEL_AREA, midW, Layout::FIELD_H ); + SET_EVENT_MULTI( m_stripeCountSpin.onChanged, + { + ((CMenuColorPickerDialog *)pSelf->Parent())->OnStripeCountChanged(); + }); + + rowY += rowStep; + + static const char *presetItems[V_ARRAYSIZE( g_LogoPresets )]; + for( size_t i = 0; i < V_ARRAYSIZE( g_LogoPresets ); i++ ) + presetItems[i] = L( g_LogoPresets[i].name ); + static CStringArrayModel presetModel( presetItems, V_ARRAYSIZE( g_LogoPresets )); + + m_presetSpin.Setup( &presetModel ); + m_presetSpin.SetCurrentValue( 0.f ); + m_presetSpin.SetRect( midX, rowY + Layout::MID_LABEL_AREA, midW, Layout::FIELD_H ); + SET_EVENT_MULTI( m_presetSpin.onChanged, + { + ((CMenuColorPickerDialog *)pSelf->Parent())->OnPresetSelected(); + }); + + rowY += rowStep; + + static const char *orientationItems[] = { L( "Vertical" ), L( "Horizontal" ) }; + static CStringArrayModel orientationModel( orientationItems, 2 ); + m_orientationSpin.Setup( &orientationModel ); + m_orientationSpin.SetCurrentValue( 0.f ); + m_orientationSpin.SetRect( midX, rowY + Layout::MID_LABEL_AREA, midW, Layout::FIELD_H ); + SET_EVENT_MULTI( m_orientationSpin.onChanged, + { + ((CMenuColorPickerDialog *)pSelf->Parent())->OnOrientationChanged(); + }); currentY += Layout::PICKER_H + Layout::INPUT_GAP; + m_swatchRow.SetRect( Layout::PAD, currentY, Layout::DLG_W - Layout::PAD * 2, Layout::SWATCH_ROW_H ); + m_swatchRow.stripes = m_stripes; + m_swatchRow.stripeCount = &m_stripeCount; + m_swatchRow.selected = &m_selectedStripe; + SET_EVENT_MULTI( m_swatchRow.onSwatchSelected, + { + ((CMenuColorPickerDialog *)pSelf->Parent())->LoadSelectedStripeIntoPicker(); + }); + + currentY += Layout::SWATCH_ROW_H + Layout::INPUT_GAP; + static const char *modeItems[] = { "RGB", "HSV" }; static CStringArrayModel modeModel( modeItems, 2 ); @@ -80,13 +307,12 @@ void CMenuColorPickerDialog::_Init() m_modeSwitch.SetRect( Layout::INPUT_X, currentY, Layout::MODE_W, Layout::FIELD_H ); SET_EVENT_MULTI( m_modeSwitch.onChanged, { - CMenuColorPickerDialog *dlg = (CMenuColorPickerDialog *)pSelf->Parent(); - bool toRGB = ( ( (CMenuSpinControl *)pSelf )->GetCurrentValue() < 0.5f ); - dlg->SwitchMode( toRGB ); + CMenuColorPickerDialog *dlg = (CMenuColorPickerDialog *)pSelf->Parent(); + dlg->SwitchMode( ((CMenuSpinControl *)pSelf)->GetCurrentValue() < 0.5f ); }); static const char *rgbLabels[3] = { "R", "G", "B" }; - int fieldX = Layout::INPUT_X + Layout::MODE_W + Layout::INPUT_GROUP_GAP; + int fieldX = Layout::INPUT_X + Layout::MODE_W + Layout::INPUT_GROUP_GAP; for( int i = 0; i < 3; i++ ) { @@ -103,17 +329,15 @@ void CMenuColorPickerDialog::_Init() SET_EVENT_MULTI( m_fields[i].onChanged, { - CMenuColorPickerDialog *dlg = (CMenuColorPickerDialog *)pSelf->Parent(); - dlg->UpdatePickerFromFields(); + ((CMenuColorPickerDialog *)pSelf->Parent())->UpdatePickerFromFields(); }); } - const int buttonY = Layout::DLG_H - Layout::PAD - Layout::BTN_H; - // Center the gap between OK and Cancel buttons exactly on the right edge of the color picker column (PAD + PICKER_W) - const int buttonsX = ( Layout::PAD + Layout::PICKER_W ) - Layout::BTN_W - ( Layout::BTN_GAP / 2 ); + const int buttonY = Layout::DLG_H - Layout::PAD - Layout::BTN_H; + const int buttonsX = ( Layout::DLG_W - ( Layout::BTN_W * 2 + Layout::BTN_GAP )) / 2; m_btnOk.SetPicture( PC_OK ); - m_btnOk.szName = L( "OK" ); + m_btnOk.szName = L( "GameUI_OK" ); m_btnOk.eTextAlignment = QM_CENTER; m_btnOk.SetRect( buttonsX, buttonY, Layout::BTN_W, Layout::BTN_H ); SET_EVENT_MULTI( m_btnOk.onReleased, @@ -125,17 +349,20 @@ void CMenuColorPickerDialog::_Init() }); m_btnCancel.SetPicture( PC_CANCEL ); - m_btnCancel.szName = L( "Cancel" ); + m_btnCancel.szName = L( "GameUI_Cancel" ); m_btnCancel.eTextAlignment = QM_CENTER; m_btnCancel.SetRect( buttonsX + Layout::BTN_W + Layout::BTN_GAP, buttonY, Layout::BTN_W, Layout::BTN_H ); SET_EVENT_MULTI( m_btnCancel.onReleased, { - CMenuColorPickerDialog *dlg = (CMenuColorPickerDialog *)pSelf->Parent(); - dlg->Hide(); + ((CMenuColorPickerDialog *)pSelf->Parent())->Hide(); }); AddItem( m_picker ); AddItem( m_logoPreviewItem ); + AddItem( m_stripeCountSpin ); + AddItem( m_presetSpin ); + AddItem( m_orientationSpin ); + AddItem( m_swatchRow ); AddItem( m_modeSwitch ); for( int i = 0; i < 3; i++ ) AddItem( m_fields[i] ); @@ -162,6 +389,17 @@ void CMenuColorPickerDialog::Draw() EngFuncs::FillRGBA( m_scPos.x + Layout::PAD * uiStatic.scaleX, m_scPos.y + ( Layout::PAD + Layout::TITLE_H ) * uiStatic.scaleY, m_scSize.w - Layout::PAD * 2 * uiStatic.scaleX, 1, 255, 255, 255, 40 ); + CMenuSpinControl *const midSpins[3] = { &m_stripeCountSpin, &m_presetSpin, &m_orientationSpin }; + const char *const midLabels[3] = { L( "Stripes:" ), L( "Preset:" ), L( "Direction:" ) }; + const int fontH = g_FontMgr->GetFontTall( font ); + const int labelGap = fontH / 3; + for( int i = 0; i < 3; i++ ) + { + Point sp = midSpins[i]->GetRenderPosition(); + Size sz = midSpins[i]->GetRenderSize(); + UI_DrawString( font, sp.x, sp.y - fontH - labelGap, sz.w, fontH, midLabels[i], uiColorHelp, m_scChSize, QM_LEFT, ETF_SHADOW ); + } + for( int i = 0; i < 3; i++ ) { int lx = m_scPos.x + m_labels[i].pos.x * uiStatic.scaleX; @@ -182,21 +420,55 @@ void CMenuColorPickerDialog::CLogoPreview::Draw() UI_FillRect( m_scPos, m_scSize, uiPromptBgColor ); UI_DrawString( font, m_scPos, m_scSize, L( "No logo" ), colorBase, m_scChSize, QM_CENTER, ETF_SHADOW ); } - else + else if( stripeCount <= 1 ) { - EngFuncs::PIC_Set( hImage, r, g, b ); + EngFuncs::PIC_Set( hImage, stripes[0][0], stripes[0][1], stripes[0][2] ); EngFuncs::PIC_DrawTrans( m_scPos, m_scSize ); } + else + { + const Size img_sz = EngFuncs::PIC_Size( hImage ); + const bool hz = horizontal && *horizontal; + + const double tex_per_stripe = ( hz ? img_sz.w : img_sz.h ) / (double)stripeCount; + const double scr_per_stripe = ( hz ? m_scSize.w : m_scSize.h ) / (double)stripeCount; + + for( int i = 0; i < stripeCount; i++ ) + { + // derive each stripe's bounds from the next stripe's edge so consecutive stripes touch exactly + int scr_start = (int)( i * scr_per_stripe + 0.5 ); + int scr_next = (int)(( i + 1 ) * scr_per_stripe + 0.5 ); + int tex_start = (int)( i * tex_per_stripe + 0.5 ); + int tex_next = (int)(( i + 1 ) * tex_per_stripe + 0.5 ); + + wrect_t rc = {}; + if( hz ) + { + rc.left = tex_start; + rc.right = tex_next; + rc.bottom = img_sz.h; + } + else + { + rc.right = img_sz.w; + rc.top = tex_start; + rc.bottom = tex_next; + } + + Point ui_pt = hz ? Point( m_scPos.x + scr_start, m_scPos.y ) : Point( m_scPos.x, m_scPos.y + scr_start ); + Size ui_sz = hz ? Size( scr_next - scr_start, m_scSize.h ) : Size( m_scSize.w, scr_next - scr_start ); + + EngFuncs::PIC_Set( hImage, stripes[i][0], stripes[i][1], stripes[i][2] ); + EngFuncs::PIC_DrawTrans( ui_pt, ui_sz, &rc ); + } + } UI_DrawRectangle( m_scPos, m_scSize, uiInputFgColor ); } void CMenuColorPickerDialog::UpdateLogoPreview() { - byte r, g, b; - m_picker.GetRGB( r, g, b ); - m_logoPreviewItem.r = r; - m_logoPreviewItem.g = g; - m_logoPreviewItem.b = b; + m_logoPreviewItem.stripes = m_stripes; + m_logoPreviewItem.stripeCount = m_stripeCount; } void CMenuColorPickerDialog::UpdateFieldsFromPicker() @@ -209,15 +481,15 @@ void CMenuColorPickerDialog::UpdateFieldsFromPicker() { byte r, g, b; m_picker.GetRGB( r, g, b ); - m_fields[0].SetBuffer( CNumStr( (int)r ) ); - m_fields[1].SetBuffer( CNumStr( (int)g ) ); - m_fields[2].SetBuffer( CNumStr( (int)b ) ); + m_fields[0].SetBuffer( CNumStr( r )); + m_fields[1].SetBuffer( CNumStr( g )); + m_fields[2].SetBuffer( CNumStr( b )); } else { - m_fields[0].SetBuffer( CNumStr( (int)( m_picker.GetHue() + 0.5f ) ) ); - m_fields[1].SetBuffer( CNumStr( (int)( m_picker.GetSat() * 100.f + 0.5f ) ) ); - m_fields[2].SetBuffer( CNumStr( (int)( m_picker.GetVal() * 100.f + 0.5f ) ) ); + m_fields[0].SetBuffer( CNumStr( (int)( m_picker.GetHue() + 0.5f ))); + m_fields[1].SetBuffer( CNumStr( (int)( m_picker.GetSat() * 100.f + 0.5f ))); + m_fields[2].SetBuffer( CNumStr( (int)( m_picker.GetVal() * 100.f + 0.5f ))); } m_updatingFields = false; @@ -231,21 +503,22 @@ void CMenuColorPickerDialog::UpdatePickerFromFields() if( m_modeRGB ) { - int r = Q_max( 0, Q_min( 255, atoi( m_fields[0].GetBuffer() ) ) ); - int g = Q_max( 0, Q_min( 255, atoi( m_fields[1].GetBuffer() ) ) ); - int b = Q_max( 0, Q_min( 255, atoi( m_fields[2].GetBuffer() ) ) ); + int r = Q_max( 0, Q_min( 255, atoi( m_fields[0].GetBuffer() ))); + int g = Q_max( 0, Q_min( 255, atoi( m_fields[1].GetBuffer() ))); + int b = Q_max( 0, Q_min( 255, atoi( m_fields[2].GetBuffer() ))); m_picker.SetRGB( (byte)r, (byte)g, (byte)b ); } else { - float h = Q_max( 0.f, Q_min( 360.f, (float)atoi( m_fields[0].GetBuffer() ) ) ); - float s = Q_max( 0.f, Q_min( 1.f, atoi( m_fields[1].GetBuffer() ) / 100.f ) ); - float v = Q_max( 0.f, Q_min( 1.f, atoi( m_fields[2].GetBuffer() ) / 100.f ) ); + float h = Q_max( 0.f, Q_min( 360.f, (float)atoi( m_fields[0].GetBuffer() ))); + float s = Q_max( 0.f, Q_min( 1.f, atoi( m_fields[1].GetBuffer() ) / 100.f )); + float v = Q_max( 0.f, Q_min( 1.f, atoi( m_fields[2].GetBuffer() ) / 100.f )); m_picker.SetHSV( h, s, v ); } - UpdateLogoPreview(); m_updatingFields = false; + + OnPickerChanged(); } void CMenuColorPickerDialog::SwitchMode( bool toRGB ) @@ -259,17 +532,147 @@ void CMenuColorPickerDialog::SwitchMode( bool toRGB ) UpdateFieldsFromPicker(); } -void CMenuColorPickerDialog::Show( byte r, byte g, byte b, HIMAGE logoImage ) +void CMenuColorPickerDialog::OnPickerChanged() +{ + byte r, g, b; + m_picker.GetRGB( r, g, b ); + + m_stripes[m_selectedStripe][0] = r; + m_stripes[m_selectedStripe][1] = g; + m_stripes[m_selectedStripe][2] = b; + + UpdateLogoPreview(); + UpdateFieldsFromPicker(); + + // defer preset detection until drag ends so the spinner doesn't thrash + if( m_picker.IsDragging()) + m_detectPresetPending = true; + else + DetectAndSyncPreset(); +} + +void CMenuColorPickerDialog::Think() +{ + BaseClass::Think(); + + if( m_detectPresetPending && !m_picker.IsDragging()) + { + m_detectPresetPending = false; + DetectAndSyncPreset(); + } +} + +void CMenuColorPickerDialog::LoadSelectedStripeIntoPicker() +{ + m_picker.SetRGB( m_stripes[m_selectedStripe][0], m_stripes[m_selectedStripe][1], m_stripes[m_selectedStripe][2] ); + UpdateFieldsFromPicker(); +} + +void CMenuColorPickerDialog::OnStripeCountChanged() +{ + int newCount = (int)( m_stripeCountSpin.GetCurrentValue() + 0.5f ) + 1; + + if( newCount == m_stripeCount ) + return; + + for( int i = m_stripeCount; i < newCount; i++ ) + { + m_stripes[i][0] = m_stripes[m_stripeCount - 1][0]; + m_stripes[i][1] = m_stripes[m_stripeCount - 1][1]; + m_stripes[i][2] = m_stripes[m_stripeCount - 1][2]; + } + + m_stripeCount = newCount; + if( m_selectedStripe >= m_stripeCount ) + m_selectedStripe = m_stripeCount - 1; + + UpdateLogoPreview(); + LoadSelectedStripeIntoPicker(); + DetectAndSyncPreset(); +} + +void CMenuColorPickerDialog::OnPresetSelected() { - m_picker.SetRGB( r, g, b, false ); - m_logoPreviewItem.hImage = logoImage; - m_logoPreviewItem.r = r; - m_logoPreviewItem.g = g; - m_logoPreviewItem.b = b; + int idx = (int)( m_presetSpin.GetCurrentValue() + 0.5f ); + if( idx <= 0 || idx >= (int)V_ARRAYSIZE( g_LogoPresets )) + return; + + const logo_preset_t &p = g_LogoPresets[idx]; + + m_stripeCount = p.stripes; + for( int i = 0; i < m_stripeCount; i++ ) + { + m_stripes[i][0] = p.rgb[i * 3 + 0]; + m_stripes[i][1] = p.rgb[i * 3 + 1]; + m_stripes[i][2] = p.rgb[i * 3 + 2]; + } + + m_stripeCountSpin.SetCurrentValue( m_stripeCount - 1 ); + + if( m_selectedStripe >= m_stripeCount ) + m_selectedStripe = 0; + + UpdateLogoPreview(); + LoadSelectedStripeIntoPicker(); +} + +void CMenuColorPickerDialog::DetectAndSyncPreset() +{ + int matchIdx = 0; + for( size_t i = 1; i < V_ARRAYSIZE( g_LogoPresets ); i++ ) + { + const logo_preset_t &p = g_LogoPresets[i]; + if( p.stripes != m_stripeCount ) + continue; + + bool match = true; + for( int j = 0; j < m_stripeCount && match; j++ ) + { + if( m_stripes[j][0] != p.rgb[j * 3 + 0] + || m_stripes[j][1] != p.rgb[j * 3 + 1] + || m_stripes[j][2] != p.rgb[j * 3 + 2] ) + match = false; + } + if( match ) + { + matchIdx = (int)i; + break; + } + } + + m_presetSpin.SetCurrentValue( matchIdx ); +} + +void CMenuColorPickerDialog::OnOrientationChanged() +{ + m_horizontal = m_orientationSpin.GetCurrentValue() >= 0.5f; +} + +void CMenuColorPickerDialog::Show( const byte ( *stripes )[3], int stripeCount, bool horizontal, HIMAGE logoImage ) +{ + m_stripeCount = Q_max( 1, Q_min( MAX_LOGO_STRIPES, stripeCount )); + for( int i = 0; i < m_stripeCount; i++ ) + { + m_stripes[i][0] = stripes[i][0]; + m_stripes[i][1] = stripes[i][1]; + m_stripes[i][2] = stripes[i][2]; + } + m_selectedStripe = 0; + m_horizontal = horizontal; + + m_logoPreviewItem.hImage = logoImage; + m_logoPreviewItem.stripes = m_stripes; + m_logoPreviewItem.stripeCount = m_stripeCount; + + m_stripeCountSpin.SetCurrentValue( m_stripeCount - 1 ); + m_orientationSpin.SetCurrentValue( m_horizontal ? 1.f : 0.f ); m_modeRGB = true; m_modeSwitch.SetCurrentValue( 0.f ); + m_picker.SetRGB( m_stripes[0][0], m_stripes[0][1], m_stripes[0][2], false ); SwitchMode( true ); + DetectAndSyncPreset(); + BaseClass::Show(); } diff --git a/menus/ColorPickerDialog.h b/menus/ColorPickerDialog.h index e1793a67..afff7415 100644 --- a/menus/ColorPickerDialog.h +++ b/menus/ColorPickerDialog.h @@ -24,7 +24,7 @@ GNU General Public License for more details. #include "ColorPicker.h" #include "StringArrayModel.h" -class CMenuPlayerSetup; +#define MAX_LOGO_STRIPES 8 class CMenuColorPickerDialog : public CMenuBaseWindow { @@ -32,31 +32,56 @@ class CMenuColorPickerDialog : public CMenuBaseWindow typedef CMenuBaseWindow BaseClass; CMenuColorPickerDialog(); - void Show( byte r, byte g, byte b, HIMAGE logoImage ); - CEventCallback onOk; + void Show( const byte ( *stripes )[3], int stripeCount, bool horizontal, HIMAGE logoImage ); - void GetRGB( byte &r, byte &g, byte &b ) const + void GetStripes( byte ( *out )[3], int &count, bool &horizontal ) const { - m_picker.GetRGB( r, g, b ); + count = m_stripeCount; + horizontal = m_horizontal; + for( int i = 0; i < m_stripeCount; i++ ) + { + out[i][0] = m_stripes[i][0]; + out[i][1] = m_stripes[i][1]; + out[i][2] = m_stripes[i][2]; + } } + CEventCallback onOk; + private: void _Init() override; void _VidInit() override; void Draw() override; + void Think() override; void UpdateLogoPreview(); void UpdateFieldsFromPicker(); void UpdatePickerFromFields(); void SwitchMode( bool toRGB ); + void LoadSelectedStripeIntoPicker(); + void OnPickerChanged(); + void OnStripeCountChanged(); + void OnPresetSelected(); + void OnOrientationChanged(); + void DetectAndSyncPreset(); + CMenuColorPicker m_picker; CMenuPicButton m_btnOk; CMenuPicButton m_btnCancel; CMenuSpinControl m_modeSwitch; CMenuField m_fields[3]; + CMenuSpinControl m_stripeCountSpin; + CMenuSpinControl m_presetSpin; + CMenuSpinControl m_orientationSpin; bool m_modeRGB; bool m_updatingFields; + bool m_detectPresetPending; + bool m_horizontal; + + byte m_stripes[MAX_LOGO_STRIPES][3]; + int m_stripeCount; + int m_selectedStripe; struct FieldLabel { Point pos; Size size; const char *text; } m_labels[3]; @@ -65,8 +90,26 @@ class CMenuColorPickerDialog : public CMenuBaseWindow public: void Draw() override; HIMAGE hImage = 0; - byte r = 255, g = 255, b = 255; + const byte ( *stripes )[3] = nullptr; + int stripeCount = 1; + const bool *horizontal = nullptr; } m_logoPreviewItem; + + class CSwatchRow : public CMenuBaseItem + { + public: + CSwatchRow(); + void Draw() override; + bool KeyDown( int key ) override; + + const byte ( *stripes )[3] = nullptr; + const int *stripeCount = nullptr; + int *selected = nullptr; + CEventCallback onSwatchSelected; + + private: + void GetSwatchRect( int idx, Point &p, Size &s ) const; + } m_swatchRow; }; #endif // COLORPICKERDIALOG_H diff --git a/menus/Gamepad.cpp b/menus/Gamepad.cpp index e002f5a2..0ad3289f 100644 --- a/menus/Gamepad.cpp +++ b/menus/Gamepad.cpp @@ -216,7 +216,7 @@ void CMenuGamePad::_Init( void ) } side.Setup( 0.0f, 1.0f, 0.1f ); - side.SetNameAndStatus( L( "Side" ), L( "Side movement sensitity" ) ); + side.SetNameAndStatus( L( "Side" ), L( "Side movement sensitivity" ) ); invSide.SetNameAndStatus( L( "Invert" ), L( "Invert side movement axis" ) ); forward.Setup( 0.0f, 1.0f, 0.1f ); diff --git a/menus/PlayerSetup.cpp b/menus/PlayerSetup.cpp index 01b0919d..616065d6 100644 --- a/menus/PlayerSetup.cpp +++ b/menus/PlayerSetup.cpp @@ -101,22 +101,23 @@ class CMenuPlayerSetup : public CMenuFramework public: virtual void Draw(); HIMAGE hImage; - byte previewR; - byte previewG; - byte previewB; + const byte ( *stripes )[3]; + int stripeCount; + bool colorable; + const bool *horizontal; } logoImage; CMenuSpinControl logo; CMenuPicButton btnChooseColor; CMenuColorPickerDialog colorPickerDlg; - byte m_logoR; - byte m_logoG; - byte m_logoB; - byte m_lastCvarR; - byte m_lastCvarG; - byte m_lastCvarB; + byte m_stripes[MAX_LOGO_STRIPES][3]; + int m_stripeCount; + bool m_horizontal; bool m_logoColorable; + void ParseLogoColorCvar(); + void WriteLogoColorCvar(); + CMenuYesNoMessageBox msgBox; bool hideModels, hideLogos; @@ -131,15 +132,54 @@ void CMenuPlayerSetup::CMenuLogoPreview::Draw() UI_DrawString( font, m_scPos, m_scSize, L( "No logo" ), colorBase, m_scChSize, QM_CENTER, ETF_SHADOW ); } - else + else if( !colorable || stripeCount <= 1 ) { - EngFuncs::PIC_Set( hImage, previewR, previewG, previewB ); + byte r = colorable ? stripes[0][0] : 255; + byte g = colorable ? stripes[0][1] : 255; + byte b = colorable ? stripes[0][2] : 255; + EngFuncs::PIC_Set( hImage, r, g, b ); EngFuncs::PIC_DrawTrans( m_scPos, m_scSize ); } + else + { + const Size img_sz = EngFuncs::PIC_Size( hImage ); + const bool hz = horizontal && *horizontal; + + const double tex_per_stripe = ( hz ? img_sz.w : img_sz.h ) / (double)stripeCount; + const double scr_per_stripe = ( hz ? m_scSize.w : m_scSize.h ) / (double)stripeCount; + + for( int i = 0; i < stripeCount; i++ ) + { + int scr_start = (int)( i * scr_per_stripe + 0.5 ); + int scr_next = (int)(( i + 1 ) * scr_per_stripe + 0.5 ); + int tex_start = (int)( i * tex_per_stripe + 0.5 ); + int tex_next = (int)(( i + 1 ) * tex_per_stripe + 0.5 ); + + wrect_t rc = {}; + if( hz ) + { + rc.left = tex_start; + rc.right = tex_next; + rc.bottom = img_sz.h; + } + else + { + rc.right = img_sz.w; + rc.top = tex_start; + rc.bottom = tex_next; + } + + Point ui_pt = hz ? Point( m_scPos.x + scr_start, m_scPos.y ) : Point( m_scPos.x, m_scPos.y + scr_start ); + Size ui_sz = hz ? Size( scr_next - scr_start, m_scSize.h ) : Size( m_scSize.w, scr_next - scr_start ); + + EngFuncs::PIC_Set( hImage, stripes[i][0], stripes[i][1], stripes[i][2] ); + EngFuncs::PIC_DrawTrans( ui_pt, ui_sz, &rc ); + } + } int textHeight = m_scPos.y - (m_scChSize * 1.5f); uint textflags = ( iFlags & QMF_DROPSHADOW ) ? ETF_SHADOW : 0; - UI_DrawString( font, m_scPos.x, textHeight, m_scSize.w, m_scChSize, szName, uiColorHelp, m_scChSize, QM_LEFT, textflags | ETF_FORCECOL ); + UI_DrawString( font, m_scPos.x, textHeight, m_scSize.w, m_scChSize, szName, uiColorHelp, m_scChSize, QM_LEFT, textflags | ETF_FORCECOL | ETF_NOSIZELIMIT ); // draw the rectangle if( eFocusAnimation == QM_HIGHLIGHTIFFOCUS && IsCurrentSelected() ) @@ -303,22 +343,13 @@ void CMenuPlayerSetup::UpdateLogo() } } - m_logoColorable = colorable; - if( m_logoColorable ) - { - logoImage.previewR = m_logoR; - logoImage.previewG = m_logoG; - logoImage.previewB = m_logoB; - } - else - { - logoImage.previewR = 255; - logoImage.previewG = 255; - logoImage.previewB = 255; - } + m_logoColorable = colorable; + logoImage.colorable = m_logoColorable; + logoImage.stripes = m_stripes; + logoImage.stripeCount = m_stripeCount; + logoImage.horizontal = &m_horizontal; btnChooseColor.SetGrayed( !m_logoColorable ); - EngFuncs::CvarSetString( "cl_logofile", logo.GetCurrentString() ); } void CMenuPlayerSetup::ShowColorPicker() @@ -326,12 +357,13 @@ void CMenuPlayerSetup::ShowColorPicker() if( !m_logoColorable ) return; - colorPickerDlg.Show( m_logoR, m_logoG, m_logoB, logoImage.hImage ); + colorPickerDlg.Show( m_stripes, m_stripeCount, m_horizontal, logoImage.hImage ); } void CMenuPlayerSetup::OnColorPickerOk() { - colorPickerDlg.GetRGB( m_logoR, m_logoG, m_logoB ); + colorPickerDlg.GetStripes( m_stripes, m_stripeCount, m_horizontal ); + EngFuncs::CvarSetValue( "ui_logohorizontal", m_horizontal ? 1.f : 0.f ); ApplyColorToLogoPreview(); } @@ -343,9 +375,55 @@ void CMenuPlayerSetup::ApplyColorToImagePreview() void CMenuPlayerSetup::ApplyColorToLogoPreview() { - logoImage.previewR = m_logoColorable ? m_logoR : 255; - logoImage.previewG = m_logoColorable ? m_logoG : 255; - logoImage.previewB = m_logoColorable ? m_logoB : 255; + logoImage.colorable = m_logoColorable; + logoImage.stripes = m_stripes; + logoImage.stripeCount = m_stripeCount; + logoImage.horizontal = &m_horizontal; +} + +void CMenuPlayerSetup::ParseLogoColorCvar() +{ + m_stripeCount = 1; + m_stripes[0][0] = m_stripes[0][1] = m_stripes[0][2] = 255; + + const char *logoColor = EngFuncs::GetCvarString( "cl_logocolor" ); + if( !logoColor || !*logoColor ) + return; + + int parsed = 0; + const char *p = logoColor; + while( parsed < MAX_LOGO_STRIPES ) + { + int r, g, b, n = 0; + while( *p == ' ' || *p == '\t' || *p == ',' ) + p++; + if( !*p ) + break; + if( sscanf( p, "%d %d %d%n", &r, &g, &b, &n ) != 3 || n <= 0 ) + break; + m_stripes[parsed][0] = (byte)Q_max( 0, Q_min( 255, r )); + m_stripes[parsed][1] = (byte)Q_max( 0, Q_min( 255, g )); + m_stripes[parsed][2] = (byte)Q_max( 0, Q_min( 255, b )); + parsed++; + p += n; + } + + if( parsed > 0 ) + m_stripeCount = parsed; +} + +void CMenuPlayerSetup::WriteLogoColorCvar() +{ + CUtlString s; + for( int i = 0; i < m_stripeCount; i++ ) + { + if( i > 0 ) + s += " "; + CUtlString triple; + triple.Format( "%d %d %d", m_stripes[i][0], m_stripes[i][1], m_stripes[i][2] ); + s += triple; + } + EngFuncs::CvarSetString( "cl_logocolor", s.String() ); } void CMenuPlayerSetup::WriteNewLogo( void ) @@ -384,10 +462,7 @@ void CMenuPlayerSetup::WriteNewLogo( void ) // remap logo if needed if( m_logoColorable ) - { - byte rgb[3] = { m_logoR, m_logoG, m_logoB }; - bmpFile->RemapLogo( 1, rgb ); - } + bmpFile->RemapLogo( m_stripeCount, &m_stripes[0][0], m_horizontal ); bmpFile->Save( "logos/remapped.bmp" ); EngFuncs::CvarSetString( "cl_logoext", "bmp" ); @@ -396,16 +471,11 @@ void CMenuPlayerSetup::WriteNewLogo( void ) } if( m_logoColorable ) - { - CUtlString rgbColor; - rgbColor.Format( "%d %d %d", m_logoR, m_logoG, m_logoB ); - EngFuncs::CvarSetString( "cl_logocolor", rgbColor.String() ); - m_lastCvarR = m_logoR; - m_lastCvarG = m_logoG; - m_lastCvarB = m_logoB; - } + WriteLogoColorCvar(); logo.WriteCvar(); + + EngFuncs::CvarSetValue( "@cl_logoupdate", !EngFuncs::GetCvarFloat( "@cl_logoupdate" )); } /* @@ -416,28 +486,16 @@ UI_PlayerSetup_Init void CMenuPlayerSetup::_Init( void ) { int addFlags = 0; - const char *logoColor = EngFuncs::GetCvarString( "cl_logocolor" ); hideModels = hideLogos = false; - m_logoR = 255; - m_logoG = 255; - m_logoB = 255; - - if ( logoColor ) - { - int r, g, b; - if ( sscanf( logoColor, "%d %d %d", &r, &g, &b ) == 3 ) - { - m_logoR = r; - m_logoG = g; - m_logoB = b; - } - } + ParseLogoColorCvar(); + m_horizontal = EngFuncs::GetCvarFloat( "ui_logohorizontal" ) != 0.f; m_logoColorable = false; - m_lastCvarR = m_logoR; - m_lastCvarG = m_logoG; - m_lastCvarB = m_logoB; + logoImage.stripes = m_stripes; + logoImage.stripeCount = m_stripeCount; + logoImage.horizontal = &m_horizontal; + logoImage.colorable = false; // disable playermodel preview for HLRally to prevent crash if( !stricmp( gMenu.m_gameinfo.gamefolder, "hlrally" )) @@ -601,17 +659,8 @@ void CMenuPlayerSetup::Reload() { if( !hideLogos ) { - const char *logoColor = EngFuncs::GetCvarString( "cl_logocolor" ); - if( logoColor ) - { - int r, g, b; - if( sscanf( logoColor, "%d %d %d", &r, &g, &b ) == 3 ) - { - m_logoR = r; - m_logoG = g; - m_logoB = b; - } - } + ParseLogoColorCvar(); + m_horizontal = EngFuncs::GetCvarFloat( "ui_logohorizontal" ) != 0.f; UpdateLogo(); } if( !hideModels ) UpdateModel(); diff --git a/menus/TouchOptions.cpp b/menus/TouchOptions.cpp index 4fdefad4..804317f0 100644 --- a/menus/TouchOptions.cpp +++ b/menus/TouchOptions.cpp @@ -317,7 +317,7 @@ void CMenuTouchOptions::_Init( void ) lookY.Setup( 50, 500, 5 ); lookY.LinkCvar( "touch_pitch" ); - moveX.SetNameAndStatus( L( "Side" ), L( "Side movement sensitity" ) ); + moveX.SetNameAndStatus( L( "Side" ), L( "Side movement sensitivity" ) ); moveX.Setup( 0.02, 1.0, 0.05 ); moveX.LinkCvar( "touch_sidezone" ); @@ -349,7 +349,7 @@ void CMenuTouchOptions::_Init( void ) multiplier.Setup( 100, 1000, 1 ); multiplier.LinkCvar( "touch_pow_mult" ); - exponent.SetNameAndStatus( L( "Exponent" ), L( "Exponent factor, more agressive (touch_exp_mult)" ) ); + exponent.SetNameAndStatus( L( "Exponent" ), L( "Exponent factor, more aggressive (touch_exp_mult)" ) ); exponent.Setup( 0, 100, 1 ); exponent.LinkCvar( "touch_exp_mult" ); diff --git a/model/IntegerRangeModel.h b/model/IntegerRangeModel.h new file mode 100644 index 00000000..df7ad752 --- /dev/null +++ b/model/IntegerRangeModel.h @@ -0,0 +1,26 @@ +#pragma once +#ifndef INTEGERRANGEMODEL_H +#define INTEGERRANGEMODEL_H + +#include "BaseArrayModel.h" + +class CIntegerRangeModel : public CMenuBaseArrayModel +{ +public: + CIntegerRangeModel( int start, int count ) : m_iStart( start ), m_iCount( count ) { } + + void Update() override { } + int GetRows() const override { return m_iCount; } + const char *GetText( int line ) override + { + static char buf[12]; + snprintf( buf, sizeof( buf ), "%d", m_iStart + line ); + return buf; + } + +private: + int m_iStart; + int m_iCount; +}; + +#endif // INTEGERRANGEMODEL_H