From cf357edcb9fff57b14aafbd6374ea78828d90b01 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Fri, 3 Jun 2016 14:24:56 +0300 Subject: [PATCH 001/108] Added: Interface auto scaling to view renderer --HG-- branch : experimental-ui-scaling --- code/nel/include/nel/gui/view_renderer.h | 15 ++++- code/nel/src/gui/view_renderer.cpp | 77 +++++++++++++++++++----- 2 files changed, 76 insertions(+), 16 deletions(-) diff --git a/code/nel/include/nel/gui/view_renderer.h b/code/nel/include/nel/gui/view_renderer.h index a54708b04..c737037a1 100644 --- a/code/nel/include/nel/gui/view_renderer.h +++ b/code/nel/include/nel/gui/view_renderer.h @@ -176,6 +176,12 @@ namespace NLGUI */ void getScreenOOSize (float &oow, float &ooh); + /* + * UI scaling + */ + void setInterfaceScale(float scale, sint32 width = 0, sint32 height = 0); + float getInterfaceScale() const { return _InterfaceScale; } + /* * is the Screen minimized? */ @@ -526,6 +532,13 @@ namespace NLGUI float _OneOverScreenW, _OneOverScreenH; bool _IsMinimized; + // UI scaling + float _InterfaceScale; + float _InterfaceUserScale; + sint32 _InterfaceBaseW, _InterfaceBaseH; + sint32 _EffectiveScreenW, _EffectiveScreenH; + + void updateInterfaceScale(); //map linking a uint to a bitmap. Used to display figurs std::vector _IndexesToTextureIds; @@ -585,14 +598,12 @@ namespace NLGUI static CViewRenderer *instance; static NL3D::UDriver *driver; static NL3D::UTextContext *textcontext; - public: static NL3D::UTextContext* getTextContext(){ return textcontext; } /// Set of hw cursor images static std::set< std::string > *hwCursors; static float hwCursorScale; - }; diff --git a/code/nel/src/gui/view_renderer.cpp b/code/nel/src/gui/view_renderer.cpp index 0b4d837f3..b2d793e46 100644 --- a/code/nel/src/gui/view_renderer.cpp +++ b/code/nel/src/gui/view_renderer.cpp @@ -91,16 +91,13 @@ namespace NLGUI if(w!=0 && h!=0) { _IsMinimized= false; - _ScreenW = w; - _ScreenH = h; - if(_ScreenW>0) - _OneOverScreenW = 1.0f / (float)_ScreenW; - else - _OneOverScreenW = 1000; - if(_ScreenH>0) - _OneOverScreenH = 1.0f / (float)_ScreenH; - else - _OneOverScreenH = 1000; + if (w != _ScreenW || h != _ScreenH) + { + _ScreenW = w; + _ScreenH = h; + + updateInterfaceScale(); + } } else { @@ -109,14 +106,48 @@ namespace NLGUI } } + void CViewRenderer::updateInterfaceScale() + { + if(_ScreenW>0) + _OneOverScreenW = 1.0f / (float)_ScreenW; + else + _OneOverScreenW = 1000; + if(_ScreenH>0) + _OneOverScreenH = 1.0f / (float)_ScreenH; + else + _OneOverScreenH = 1000; + + _InterfaceScale = _InterfaceUserScale; + if (_InterfaceBaseW > 0 && _InterfaceBaseH > 0) + { + float wRatio = (float)_ScreenW / _InterfaceBaseW; + float rRatio = (float)_ScreenH / _InterfaceBaseH; + _InterfaceScale *= std::min(wRatio, rRatio); + } + + if (_InterfaceScale != 1.0f) + { + _OneOverScreenW *= _InterfaceScale; + _OneOverScreenH *= _InterfaceScale; + + _EffectiveScreenW = sint(_ScreenW / _InterfaceScale); + _EffectiveScreenH = sint(_ScreenH / _InterfaceScale); + } + else + { + _EffectiveScreenW = _ScreenW; + _EffectiveScreenH = _ScreenH; + } + } + /* * getScreenSize : get the screen window size */ void CViewRenderer::getScreenSize (uint32 &w, uint32 &h) { - w = _ScreenW; - h = _ScreenH; + w = _EffectiveScreenW; + h = _EffectiveScreenH; } /* @@ -128,6 +159,20 @@ namespace NLGUI ooh= _OneOverScreenH; } + void CViewRenderer::setInterfaceScale(float scale, sint32 width/*=0*/, sint32 height/*=0*/) + { + // prevent #div/0 + if (sint(scale*100) > 0) + _InterfaceUserScale = scale; + else + _InterfaceUserScale = 1.0f; + + _InterfaceBaseW = width; + _InterfaceBaseH = height; + + updateInterfaceScale(); + } + void CViewRenderer::setup() { _ClipX = _ClipY = 0; @@ -135,8 +180,10 @@ namespace NLGUI _ClipH = 600; _ScreenW = 800; _ScreenH = 600; - _OneOverScreenW= 1.0f / (float)_ScreenW; - _OneOverScreenH= 1.0f / (float)_ScreenH; + _InterfaceScale = 1.0f; + _InterfaceUserScale = 1.0f; + _InterfaceBaseW = 0; + _InterfaceBaseH = 0; _IsMinimized= false; _WFigurTexture= 0; _HFigurTexture= 0; @@ -152,6 +199,8 @@ namespace NLGUI _EmptyLayer[i]= true; } _BlankGlobalTexture = NULL; + + updateInterfaceScale(); } From b474f39970c7af55527f2638a2cbfe8b734bffee Mon Sep 17 00:00:00 2001 From: Nimetu Date: Fri, 3 Jun 2016 14:26:17 +0300 Subject: [PATCH 002/108] Added: Scaling options to client config --HG-- branch : experimental-ui-scaling --- .../data/gamedev/interfaces_v3/game_config.xml | 16 +++++++++++++++- code/ryzom/client/src/client_cfg.cpp | 6 ++++++ code/ryzom/client/src/client_cfg.h | 3 +++ code/ryzom/client/src/connection.cpp | 11 +++++++++++ code/ryzom/client/src/init.cpp | 1 + .../src/interface_v3/interface_manager.cpp | 10 ++++++++++ .../client/src/interface_v3/interface_manager.h | 3 +++ code/ryzom/client/src/main_loop_utilities.cpp | 6 +++++- 8 files changed, 54 insertions(+), 2 deletions(-) diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/game_config.xml b/code/ryzom/client/data/gamedev/interfaces_v3/game_config.xml index 38e63cb24..001d2be1d 100644 --- a/code/ryzom/client/data/gamedev/interfaces_v3/game_config.xml +++ b/code/ryzom/client/data/gamedev/interfaces_v3/game_config.xml @@ -878,10 +878,17 @@ posparent="lum" x="0" y="-2" /> + @@ -3086,6 +3093,13 @@ realtime="true" widget="sbfloat" link="Gamma" /> + setInterfaceScale(ClientCfg.InterfaceScale); } @@ -290,6 +293,8 @@ void setOutGameFullScreen() */ } + // Enable auto scaling in login window + CViewRenderer::getInstance()->setInterfaceScale(1.0f, 1024, 768); } @@ -454,6 +459,9 @@ bool connection (const string &cookie, const string &fsaddr) firstConnection = false; + // Restore user UI scaling + CViewRenderer::getInstance()->setInterfaceScale(ClientCfg.InterfaceScale); + // Disable inputs Actions.enable(false); EditActions.enable(false); @@ -586,6 +594,9 @@ bool reconnection() InterfaceState = globalMenu(); } + // Restore user UI scaling + CViewRenderer::getInstance()->setInterfaceScale(ClientCfg.InterfaceScale); + // Disable inputs Actions.enable(false); EditActions.enable(false); diff --git a/code/ryzom/client/src/init.cpp b/code/ryzom/client/src/init.cpp index 7b1ca728f..8105d9351 100644 --- a/code/ryzom/client/src/init.cpp +++ b/code/ryzom/client/src/init.cpp @@ -1316,6 +1316,7 @@ void prelogInit() CInterfaceManager::getInstance(); + CViewRenderer::getInstance()->setInterfaceScale(1.0f, 1024, 768); // Yoyo: initialize NOW the InputHandler for Event filtering. CInputHandlerManager *InputHandlerManager = CInputHandlerManager::getInstance(); diff --git a/code/ryzom/client/src/interface_v3/interface_manager.cpp b/code/ryzom/client/src/interface_v3/interface_manager.cpp index 3e2a0ba0e..802f98c70 100644 --- a/code/ryzom/client/src/interface_v3/interface_manager.cpp +++ b/code/ryzom/client/src/interface_v3/interface_manager.cpp @@ -475,6 +475,8 @@ CInterfaceManager::CInterfaceManager() interfaceLinkUpdater = new CInterfaceLink::CInterfaceLinkUpdater(); _ScreenW = _ScreenH = 0; _LastInGameScreenW = _LastInGameScreenH = 0; + _InterfaceScaleChanged = false; + _InterfaceScale = 1.0f; _DescTextTarget = NULL; _ConfigLoaded = false; _LogState = false; @@ -1984,6 +1986,14 @@ void CInterfaceManager::drawViews(NL3D::UCamera camera) _CurrentPlayerCharac[i] = node ? node->getValue32() : 0; } + // update value change from ingame config window + // must update it here, right before widget manager checks it + if (_InterfaceScaleChanged) + { + CViewRenderer::getInstance()->setInterfaceScale(_InterfaceScale); + _InterfaceScaleChanged = false; + } + CWidgetManager::getInstance()->drawViews( camera ); // flush obs diff --git a/code/ryzom/client/src/interface_v3/interface_manager.h b/code/ryzom/client/src/interface_v3/interface_manager.h index 6025868e5..c5c641033 100644 --- a/code/ryzom/client/src/interface_v3/interface_manager.h +++ b/code/ryzom/client/src/interface_v3/interface_manager.h @@ -543,6 +543,7 @@ public: NLMISC::CCDBNodeLeaf *_DB_UI_DUMMY_FACTION_TYPE; void updateDesktops( uint32 newScreenW, uint32 newScreenH ); + void setInterfaceScale( float scale ) { _InterfaceScaleChanged = true; _InterfaceScale = scale; } private: @@ -581,6 +582,8 @@ private: uint32 _ScreenW, _ScreenH; // Change res detection sint32 _LastInGameScreenW, _LastInGameScreenH; // Resolution used for last InGame interface + float _InterfaceScale; + bool _InterfaceScaleChanged; // Modes CInterfaceConfig::CDesktopImage _Modes[MAX_NUM_MODES]; diff --git a/code/ryzom/client/src/main_loop_utilities.cpp b/code/ryzom/client/src/main_loop_utilities.cpp index 415764bfa..e62c033e4 100644 --- a/code/ryzom/client/src/main_loop_utilities.cpp +++ b/code/ryzom/client/src/main_loop_utilities.cpp @@ -36,6 +36,7 @@ #include "input.h" #include "sound_manager.h" #include "camera.h" +#include "interface_v3/interface_manager.h" using namespace NLMISC; using namespace NL3D; @@ -100,6 +101,9 @@ void updateFromClientCfg() Driver->forceTextureResize(1); } + if (ClientCfg.InterfaceScale != LastClientCfg.InterfaceScale) + CInterfaceManager::getInstance()->setInterfaceScale(ClientCfg.InterfaceScale); + //--------------------------------------------------- if (ClientCfg.WaitVBL != LastClientCfg.WaitVBL) { @@ -395,4 +399,4 @@ void updateFromClientCfg() LastClientCfg = ClientCfg; } -/* end of file */ \ No newline at end of file +/* end of file */ From 8965ce50d5fa03d5b0923930874e033e60fa4131 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Fri, 3 Jun 2016 16:02:37 +0300 Subject: [PATCH 003/108] Changed: Font scaling --HG-- branch : experimental-ui-scaling --- code/nel/include/nel/gui/view_text.h | 12 ++- code/nel/src/gui/view_renderer.cpp | 19 ++++ code/nel/src/gui/view_text.cpp | 140 ++++++++++++++------------- 3 files changed, 100 insertions(+), 71 deletions(-) diff --git a/code/nel/include/nel/gui/view_text.h b/code/nel/include/nel/gui/view_text.h index 95507ffe3..c814bcb7b 100644 --- a/code/nel/include/nel/gui/view_text.h +++ b/code/nel/include/nel/gui/view_text.h @@ -232,12 +232,14 @@ namespace NLGUI bool _Embolden; bool _Oblique; // width of the font in pixel. Just a Hint for tabing format (computed with '_') - uint _FontWidth; + float _FontWidth; // height of the font in pixel. // use getFontHeight - uint _FontHeight; - uint _FontLegHeight; + float _FontHeight; + float _FontLegHeight; float _SpaceWidth; + /// last UI scale used to calculate font size + float _Scale; /// the text color NLMISC::CRGBA _Color; /// the shadow mode @@ -333,8 +335,8 @@ namespace NLGUI // Clear the line & remove text contexts void clear(); // Add a new word (and its context) in the line + a number of spaces to append at the end of the line - void addWord(const ucstring &word, uint numSpaces, const CFormatInfo &wordFormat, uint fontWidth); - void addWord(const CWord &word, uint fontWidth); + void addWord(const ucstring &word, uint numSpaces, const CFormatInfo &wordFormat, float fontWidth); + void addWord(const CWord &word, float fontWidth); uint getNumWords() const { return (uint)_Words.size(); } CWord &getWord(uint index) { return _Words[index]; } float getSpaceWidth() const { return _SpaceWidth; } diff --git a/code/nel/src/gui/view_renderer.cpp b/code/nel/src/gui/view_renderer.cpp index b2d793e46..f8a8152b9 100644 --- a/code/nel/src/gui/view_renderer.cpp +++ b/code/nel/src/gui/view_renderer.cpp @@ -1938,6 +1938,25 @@ namespace NLGUI void CViewRenderer::drawText (sint layerId, float x, float y, uint wordIndex, float xmin, float ymin, float xmax, float ymax, UTextContext &textContext) { + xmin = xmin * _OneOverScreenW; + ymin = ymin * _OneOverScreenH; + xmax = xmax * _OneOverScreenW; + ymax = ymax * _OneOverScreenH; + + if (_InterfaceScale != 1.0f && _InterfaceScale != 2.0f) + { + // align to screen pixel + x *= _OneOverScreenW * _ScreenW; + y *= _OneOverScreenH * _ScreenH; + x = floorf(x) * 1.f / (float) _ScreenW; + y = floorf(y) * 1.f / (float) _ScreenH; + } + else + { + x = floorf(x) * _OneOverScreenW; + y = floorf(y) * _OneOverScreenH; + } + if (_WorldSpaceTransformation) { textContext.printClipAtUnProjected(*getStringRenderBuffer(layerId), _CameraFrustum, _WorldSpaceMatrix, x, y, _CurrentZ, wordIndex, xmin, ymin, xmax, ymax); diff --git a/code/nel/src/gui/view_text.cpp b/code/nel/src/gui/view_text.cpp index 8c2ca96d3..6a66bdf74 100644 --- a/code/nel/src/gui/view_text.cpp +++ b/code/nel/src/gui/view_text.cpp @@ -79,6 +79,7 @@ namespace NLGUI _MultiMaxLine = 0; _Index = 0xFFFFFFFF; + _Scale = 1.0f; _FontWidth= 0; _FontHeight = 0; _FontLegHeight = 0; @@ -877,12 +878,13 @@ namespace NLGUI // *************************************************************************** sint CViewText::getCurrentMultiLineMaxW() const { + sint maxw = ceilf(_LineMaxW * _Scale); if(_MultiLineMaxWOnly) - return _LineMaxW; + return maxw; else { sint parentWidth = std::min(_Parent->getMaxWReal(), _Parent->getWReal()); - return std::min(parentWidth-(sint)(_XReal-_Parent->getXReal()), (sint)_LineMaxW); + return std::min(parentWidth-(sint)(_XReal-_Parent->getXReal()), maxw); } } @@ -890,6 +892,9 @@ namespace NLGUI // *************************************************************************** void CViewText::checkCoords () { + if (_Scale != CViewRenderer::getInstance()->getInterfaceScale()) + invalidateContent(); + if ((_MultiLine)&&(_Parent != NULL)) { // If never setuped, and if text is not empty @@ -943,6 +948,8 @@ namespace NLGUI CViewRenderer &rVR = *CViewRenderer::getInstance(); + //rVR.drawRotFlipBitmap (_RenderLayer, _XReal, _YReal, _WReal, _HReal, 0, false, rVR.getBlankTextureId(), CRGBA(64,64,64,255)); + // *** Out Of Clip? sint32 ClipX, ClipY, ClipW, ClipH; rVR.getClipWindow (ClipX, ClipY, ClipW, ClipH); @@ -951,12 +958,8 @@ namespace NLGUI return; // *** Screen Minimized? - uint32 w, h; - float oow, ooh; - rVR.getScreenSize (w, h); if (rVR.isMinimized()) return; - rVR.getScreenOOSize (oow, ooh); NL3D::UTextContext *TextContext = CViewRenderer::getTextContext(); @@ -989,22 +992,22 @@ namespace NLGUI TextContext->setShaded (_Shadow); TextContext->setShadeOutline (_ShadowOutline); TextContext->setShadeColor (shcol); - TextContext->setFontSize (_FontSize); + TextContext->setFontSize (_FontSize*_Scale); TextContext->setEmbolden (_Embolden); TextContext->setOblique (_Oblique); - float y = (float)(_YReal) * ooh; // y is expressed in scree, coordinates [0..1] + float y = _YReal; //y += _LinesInfos[_LinesInfos.size()-1].StringLine / h; // Y is the base line of the string, so it must be grown up. - y += (float)_FontLegHeight * ooh; + y += _FontLegHeight; - sint y_line = _YReal+_FontLegHeight-2; + sint y_line = _YReal+_FontLegHeight-2.0f*_Scale; if (_MultiMinLine > _Lines.size()) { uint dy = getMultiMinOffsetY(); - y += dy * ooh; + y += dy; y_line += dy; } @@ -1076,8 +1079,7 @@ namespace NLGUI px= max(px, (float)(_XReal + currWord.Format.TabX*_FontWidth)); // draw. We take floorf px to avoid filtering of letters that are not located on a pixel boundary - rVR.drawText (_RenderLayer, floorf(px) * oow, y, currWord.Index, (float)ClipX * oow, (float)ClipY * ooh, - (float)(ClipX+ClipW) * oow, (float)(ClipY+ClipH) * ooh, *TextContext); + rVR.drawText (_RenderLayer, px, y, currWord.Index, ClipX, ClipY, ClipX+ClipW, ClipY+ClipH, *TextContext); // Draw a line if (_Underlined) @@ -1090,7 +1092,7 @@ namespace NLGUI px += currWord.Info.StringWidth; } // go one line up - y += (_FontHeight + _MultiLineSpace) * ooh; + y += (_FontHeight + _MultiLineSpace); y_line += _FontHeight+_MultiLineSpace; } @@ -1116,22 +1118,19 @@ namespace NLGUI TextContext->setShaded (_Shadow); TextContext->setShadeOutline (_ShadowOutline); TextContext->setShadeColor (shcol); - TextContext->setFontSize (_FontSize); + TextContext->setFontSize (_FontSize*_Scale); TextContext->setEmbolden (_Embolden); TextContext->setOblique (_Oblique); - if(_LetterColors!=NULL && !TextContext->isSameLetterColors(_LetterColors, _Index)) { TextContext->setLetterColors(_LetterColors, _Index); } - - float x = (float)(_XReal) * oow; - float y = (float)(_YReal) * ooh; + float y = _YReal; // Y is the base line of the string, so it must be grown up. - y += (float)_FontLegHeight * ooh; + y += _FontLegHeight; // special selection code if(_TextSelection) @@ -1142,12 +1141,11 @@ namespace NLGUI TextContext->setStringColor(_Index, col); // draw - rVR.drawText (_RenderLayer, x, y, _Index, (float)ClipX * oow, (float)ClipY * ooh, - (float)(ClipX+ClipW) * oow, (float)(ClipY+ClipH) * ooh, *TextContext); + rVR.drawText (_RenderLayer, _XReal, y, _Index, ClipX, ClipY, ClipX+ClipW, ClipY+ClipH, *TextContext); // Draw a line if (_Underlined) - rVR.drawRotFlipBitmap (_RenderLayer, _XReal, _YReal+_FontLegHeight-2, _WReal, 1, 0, false, rVR.getBlankTextureId(), col); + rVR.drawRotFlipBitmap (_RenderLayer, _XReal, _YReal+_FontLegHeight-2.0f*_Scale, _WReal, 1, 0, false, rVR.getBlankTextureId(), col); if (_StrikeThrough) rVR.drawRotFlipBitmap (_RenderLayer, _XReal, _YReal+(_FontLegHeight/2), _WReal, 1, 0, false, rVR.getBlankTextureId(), col); @@ -1336,7 +1334,7 @@ namespace NLGUI sint32 value; if(CLuaIHM::popSINT32(ls, value)) { - setLineMaxW(value); + setLineMaxW(ceilf(value / _Scale)); } return 0; } @@ -1463,7 +1461,7 @@ namespace NLGUI ucstring ucStrLetter; ucStrLetter= ucLetter; si = TextContext->getStringInfo (ucStrLetter); - rWidthLetter = (si.StringWidth); + rWidthLetter = (si.StringWidth / _Scale); if ((rWidthCurrentLine + rWidthLetter) > nMaxWidth) { flushWordInLine(ucCurrentWord, linePushed, wordFormat); @@ -1538,7 +1536,7 @@ namespace NLGUI static const ucstring spaceStr(" "); // precLineWidth valid only id precedent line is part of same paragraph. float precLineWidth= 0; - float lineWidth = (float)_FirstLineX; // width of the current line + float lineWidth = (float)_FirstLineX * _Scale; // width of the current line uint numWordsInLine = 0; // number of words in the current line bool isParagraphStart = true; // A paragraph is a group of characters between 2 \n bool lineFeed; @@ -1554,7 +1552,7 @@ namespace NLGUI TCharPos spaceEnd; TCharPos wordEnd; uint numSpaces; - float newLineWidth; + float newLineWidth = 0; breakLine = false; // if (_Text[currPos] == (ucchar) '\n') @@ -1629,7 +1627,7 @@ namespace NLGUI // compute size of spaces/Tab + word newLineWidth = lineWidth + numSpaces * _SpaceWidth; newLineWidth = max(newLineWidth, (float)wordFormat.TabX*_FontWidth); - newLineWidth+= si.StringWidth; + newLineWidth+= si.StringWidth / _Scale; } // // Does the word go beyond the end of line ? @@ -1704,8 +1702,8 @@ namespace NLGUI { oneChar = wordValue[currChar]; si = CViewRenderer::getTextContext()->getStringInfo(oneChar); - if ((uint) (px + si.StringWidth) > nMaxWidth) break; - px += si.StringWidth; + if ((uint) (px + si.StringWidth / _Scale) > nMaxWidth) break; + px += si.StringWidth / _Scale; } currChar = std::max((uint) 1, currChar); // must fit at least one character otherwise there's an infinite loop wordValue = _Text.substr(spaceEnd, currChar); @@ -1822,12 +1820,15 @@ namespace NLGUI // *************************************************************************** void CViewText::updateTextContext () { + if (_Scale != CViewRenderer::getInstance()->getInterfaceScale()) + computeFontSize(); + NL3D::UTextContext *TextContext = CViewRenderer::getTextContext(); TextContext->setHotSpot (UTextContext::BottomLeft); TextContext->setShaded (_Shadow); TextContext->setShadeOutline (_ShadowOutline); - TextContext->setFontSize (_FontSize); + TextContext->setFontSize (_FontSize*_Scale); TextContext->setEmbolden (_Embolden); TextContext->setOblique (_Oblique); @@ -1836,7 +1837,7 @@ namespace NLGUI if ((_MultiLine)&&(_Parent != NULL)) { - sint nMaxWidth = getCurrentMultiLineMaxW(); + float nMaxWidth = getCurrentMultiLineMaxW(); _LastMultiLineMaxW = nMaxWidth; clearLines(); if (nMaxWidth <= 0) @@ -1873,8 +1874,8 @@ namespace NLGUI { rTotalW = std::max(_Lines[i]->getWidth() + ((i==0)?_FirstLineX:0), rTotalW); } - _W = (sint)rTotalW; - _H = std::max(_FontHeight, uint(_FontHeight * _Lines.size() + std::max(0, sint(_Lines.size()) - 1) * _MultiLineSpace)); + _W = (sint)ceilf(rTotalW); + _H = std::max(_FontHeight, ceilf(_FontHeight * _Lines.size() + std::max(0, sint(_Lines.size()) - 1) * _MultiLineSpace)); // See if we should pretend to have at least X lines if (_MultiMinLine > 1) @@ -1895,7 +1896,7 @@ namespace NLGUI { CCtrlToolTip *pTooltip = _Tooltips[word.Format.IndexTt]; - sint y = (sint) ((_FontHeight + _MultiLineSpace) * (_Lines.size() - i - 1)); + sint y = (sint) ceilf((_FontHeight + _MultiLineSpace) * (_Lines.size() - i - 1)); pTooltip->setX(0); pTooltip->setY(y); @@ -1914,7 +1915,8 @@ namespace NLGUI // Common case: no W clamp _Index = TextContext->textPush (_Text); _Info = TextContext->getStringInfo (_Index); - _W = (sint)(_Info.StringWidth); + _Info.StringWidth /= _Scale; + _W = (sint)ceilf(_Info.StringWidth); // Rare case: clamp W => recompute slowly, cut letters if(_W>_LineMaxW) @@ -1927,7 +1929,7 @@ namespace NLGUI ucCurrentLine.reserve(_Text.size()); // Append ... to the end of line si = TextContext->getStringInfo (ucstring("...")); - float dotWidth= si.StringWidth; + float dotWidth= si.StringWidth / _Scale; float rWidthCurrentLine = 0, rWidthLetter; // for all the text if (_ClampRight) @@ -1938,7 +1940,7 @@ namespace NLGUI ucstring ucStrLetter; ucStrLetter= ucLetter; si = TextContext->getStringInfo (ucStrLetter); - rWidthLetter = (si.StringWidth); + rWidthLetter = (si.StringWidth / _Scale); if ((rWidthCurrentLine + rWidthLetter + dotWidth) > _LineMaxW) { break; @@ -1962,7 +1964,7 @@ namespace NLGUI ucstring ucStrLetter; ucStrLetter= ucLetter; si = TextContext->getStringInfo (ucStrLetter); - rWidthLetter = (si.StringWidth); + rWidthLetter = (si.StringWidth / _Scale); if ((rWidthCurrentLine + rWidthLetter + dotWidth) > _LineMaxW) { break; @@ -1982,13 +1984,14 @@ namespace NLGUI // And so setup this trunc text _Index = TextContext->textPush (ucCurrentLine); _Info = TextContext->getStringInfo (_Index); - _W = (sint)(_Info.StringWidth); + _Info.StringWidth /= _Scale; + _W = (sint)ceilf(_Info.StringWidth); _SingleLineTextClamped= true; } // same height always - _H = _FontHeight; + _H = (sint)ceilf(_FontHeight); } _InvalidTextContext= false; @@ -2025,12 +2028,12 @@ namespace NLGUI if (_ClampRight) { sint32 parentRight = parent->getXReal() + parent->getWReal() - (sint32) _AutoClampOffset; - setLineMaxW(std::max((sint32) 0, parentRight - _XReal)); + setLineMaxW(ceilf(std::max((sint32) 0, parentRight - _XReal) / _Scale)); } else { sint32 parentLeft = parent->getXReal() + (sint32) _AutoClampOffset; - setLineMaxW(std::max((sint32) 0, _XReal + _WReal - parentLeft)); + setLineMaxW(ceilf(std::max((sint32) 0, _XReal + _WReal - parentLeft) / _Scale)); } } } @@ -2154,7 +2157,7 @@ namespace NLGUI TextContext->setHotSpot (UTextContext::BottomLeft); TextContext->setShaded (_Shadow); TextContext->setShadeOutline (_ShadowOutline); - TextContext->setFontSize (_FontSize); + TextContext->setFontSize (_FontSize*_Scale); TextContext->setEmbolden (_Embolden); TextContext->setOblique (_Oblique); // CViewRenderer &rVR = *CViewRenderer::getInstance(); @@ -2224,14 +2227,14 @@ namespace NLGUI ucstring subStr = currWord.Text.substr(0, index - charIndex - currWord.NumSpaces); // compute the size UTextContext::CStringInfo si = TextContext->getStringInfo(subStr); - x = (sint) (px + si.StringWidth + currWord.NumSpaces * currLine.getSpaceWidth()); + x = (sint) ceilf(px + si.StringWidth / _Scale + currWord.NumSpaces * currLine.getSpaceWidth()); height = getFontHeight(); return; } else { // character is in the spaces preceding the word - x = (sint) (px + currLine.getSpaceWidth() * (index - charIndex)); + x = (sint) ceilf(px + currLine.getSpaceWidth() * (index - charIndex)); height = getFontHeight(); return; } @@ -2251,7 +2254,7 @@ namespace NLGUI // compute the size UTextContext::CStringInfo si = TextContext->getStringInfo(subStr); y = 0; - x = (sint) si.StringWidth; + x = (sint) ceilf(si.StringWidth / _Scale); } } @@ -2260,6 +2263,7 @@ namespace NLGUI static uint getCharacterIndex(const ucstring &textValue, float x) { float px = 0.f; + float sw; UTextContext::CStringInfo si; ucstring singleChar(" "); uint i; @@ -2268,12 +2272,13 @@ namespace NLGUI // get character width singleChar[0] = textValue[i]; si = CViewRenderer::getTextContext()->getStringInfo(singleChar); - px += si.StringWidth; + sw = si.StringWidth / CViewRenderer::getInstance()->getInterfaceScale(); + px += sw / CViewRenderer::getInstance()->getInterfaceScale(); // the character is at the i - 1 position if (px > x) { // if the half of the character is after the cursor, then prefer select the next one (like in Word) - if(px-si.StringWidth/2 < x) + if(px-sw/2 < x) i++; break; } @@ -2290,7 +2295,7 @@ namespace NLGUI TextContext->setHotSpot (UTextContext::BottomLeft); TextContext->setShaded (_Shadow); TextContext->setShadeOutline (_ShadowOutline); - TextContext->setFontSize (_FontSize); + TextContext->setFontSize (_FontSize*_Scale); TextContext->setEmbolden (_Embolden); TextContext->setOblique (_Oblique); // find the line where the character is @@ -2490,7 +2495,7 @@ namespace NLGUI } // *************************************************************************** - void CViewText::CLine::addWord(const ucstring &text, uint numSpaces, const CFormatInfo &wordFormat, uint fontWidth) + void CViewText::CLine::addWord(const ucstring &text, uint numSpaces, const CFormatInfo &wordFormat, float fontWidth) { CWord word; word.build(text, numSpaces); @@ -2499,7 +2504,7 @@ namespace NLGUI } // *************************************************************************** - void CViewText::CLine::addWord(const CWord &word, uint fontWidth) + void CViewText::CLine::addWord(const CWord &word, float fontWidth) { _Words.push_back(word); _NumChars += word.NumSpaces + uint(word.Text.length()); @@ -2509,7 +2514,7 @@ namespace NLGUI _StringLine = word.Info.StringLine; } // the width of the line must reach at least the Tab - _WidthWithoutSpaces= max(_WidthWithoutSpaces, word.Format.TabX * float(fontWidth)); + _WidthWithoutSpaces= max(_WidthWithoutSpaces, word.Format.TabX * fontWidth); // append the text space _WidthWithoutSpaces += word.Info.StringWidth; } @@ -2544,6 +2549,7 @@ namespace NLGUI NL3D::UTextContext *TextContext = CViewRenderer::getTextContext(); Index = TextContext->textPush(text); Info = TextContext->getStringInfo(Index); + Info.StringWidth /= CViewRenderer::getInstance()->getInterfaceScale(); } // *************************************************************************** @@ -2568,7 +2574,7 @@ namespace NLGUI TextContext->setHotSpot (UTextContext::BottomLeft); TextContext->setShaded (_Shadow); TextContext->setShadeOutline (_ShadowOutline); - TextContext->setFontSize (_FontSize); + TextContext->setFontSize (_FontSize*_Scale); TextContext->setEmbolden (_Embolden); TextContext->setOblique (_Oblique); @@ -2617,7 +2623,7 @@ namespace NLGUI si = TextContext->getStringInfo(wordValue); // compute size of spaces + word - lineWidth += numSpaces * _SpaceWidth + si.StringWidth; + lineWidth += numSpaces * _SpaceWidth + si.StringWidth / _Scale; currPos = wordEnd; } @@ -2629,14 +2635,14 @@ namespace NLGUI linePos = lineEnd+1; } - return (sint32)maxWidth; + return (sint32)ceilf(maxWidth); } // *************************************************************************** sint32 CViewText::getMinUsedW() const { static const ucstring spaceOrLineFeedStr(" \n\t"); - sint32 maxWidth = 0; + float maxWidth = 0.0f; // Not multi line ? Same size than min if (!_MultiLine) @@ -2646,7 +2652,7 @@ namespace NLGUI if (_TextMode == ClipWord) { // No largest font parameter, return the font height - return _FontHeight; + return (sint32)ceilf(_FontHeight); } // If we can't clip the words, return the size of the largest word else if ((_TextMode == DontClipWord) || (_TextMode == Justified)) @@ -2655,7 +2661,7 @@ namespace NLGUI TextContext->setHotSpot (UTextContext::BottomLeft); TextContext->setShaded (_Shadow); TextContext->setShadeOutline (_ShadowOutline); - TextContext->setFontSize (_FontSize); + TextContext->setFontSize (_FontSize*_Scale); TextContext->setEmbolden (_Embolden); TextContext->setOblique (_Oblique); @@ -2683,7 +2689,7 @@ namespace NLGUI si = TextContext->getStringInfo(wordValue); // Larger ? - sint32 stringWidth = (sint32)si.StringWidth; + float stringWidth = (si.StringWidth / _Scale); if (stringWidth>maxWidth) maxWidth = stringWidth; @@ -2692,7 +2698,7 @@ namespace NLGUI } } - return maxWidth; + return ceilf(maxWidth); } // *************************************************************************** @@ -2709,11 +2715,13 @@ namespace NLGUI // *************************************************************************** void CViewText::computeFontSize () { + _Scale = CViewRenderer::getInstance()->getInterfaceScale(); + NL3D::UTextContext *TextContext = CViewRenderer::getTextContext(); TextContext->setHotSpot (UTextContext::BottomLeft); TextContext->setShaded (_Shadow); TextContext->setShadeOutline (_ShadowOutline); - TextContext->setFontSize (_FontSize); + TextContext->setFontSize (_FontSize * _Scale); TextContext->setEmbolden (_Embolden); TextContext->setOblique (_Oblique); @@ -2728,16 +2736,16 @@ namespace NLGUI // for now we can't know that directly from UTextContext UTextContext::CStringInfo si = TextContext->getStringInfo(chars); // add a padding of 1 pixel else the top will be truncated - _FontHeight = (uint) si.StringHeight+1; - _FontLegHeight = (uint) si.StringLine; + _FontHeight = (si.StringHeight / _Scale) + 1; + _FontLegHeight = si.StringLine / _Scale; // Space width si = TextContext->getStringInfo(ucstring(" ")); - _SpaceWidth = si.StringWidth; + _SpaceWidth = si.StringWidth / _Scale; // Font Width si = TextContext->getStringInfo(ucstring("_")); - _FontWidth = (uint)si.StringWidth; + _FontWidth = si.StringWidth / _Scale; } From 34fa04644e941fe1fb1f3cbd04dff31749707eb2 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Mon, 6 Jun 2016 19:48:08 +0300 Subject: [PATCH 004/108] Fixed: In scene text bubble position --HG-- branch : experimental-ui-scaling --- code/ryzom/client/src/interface_v3/group_in_scene.cpp | 6 ++++-- .../ryzom/client/src/interface_v3/group_in_scene_bubble.cpp | 5 ++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/code/ryzom/client/src/interface_v3/group_in_scene.cpp b/code/ryzom/client/src/interface_v3/group_in_scene.cpp index fccf2e9e6..b547f7fb0 100644 --- a/code/ryzom/client/src/interface_v3/group_in_scene.cpp +++ b/code/ryzom/client/src/interface_v3/group_in_scene.cpp @@ -86,8 +86,10 @@ void CGroupInScene::computeWindowPos(sint32 &newX, sint32 &newY, CVector &newPro tmp = pVR.getFrustum().projectZ (tmp); // Get the width and height - tmp.x *= (float)CViewRenderer::getInstance()->getDriver()->getWindowWidth(); - tmp.y *= (float)CViewRenderer::getInstance()->getDriver()->getWindowHeight(); + uint32 width, height; + CViewRenderer::getInstance()->getScreenSize(width, height); + tmp.x *= width; + tmp.y *= height; // position without offset, in float newProjCenter.x= tmp.x; diff --git a/code/ryzom/client/src/interface_v3/group_in_scene_bubble.cpp b/code/ryzom/client/src/interface_v3/group_in_scene_bubble.cpp index e964911c3..eb935bc40 100644 --- a/code/ryzom/client/src/interface_v3/group_in_scene_bubble.cpp +++ b/code/ryzom/client/src/interface_v3/group_in_scene_bubble.cpp @@ -668,9 +668,8 @@ CGroupInSceneBubbleManager::CPopupContext *CGroupInSceneBubbleManager::buildCont if (target) { // Find a position - NL3D::UDriver *Driver = CViewRenderer::getInstance()->getDriver(); - const uint width = Driver->getWindowWidth(); - const uint height = Driver->getWindowHeight(); + uint32 width, height; + CViewRenderer::getInstance()->getScreenSize(width, height); h = (target->getXReal() < ((sint)width-target->getXReal()-target->getWReal()))?"l":"r"; v = (target->getYReal() < ((sint)height-target->getYReal()-target->getHReal()))?"b":"t"; target->setActive(true); From 2cb71e654ae945a67cb9a306847e2b28de84220a Mon Sep 17 00:00:00 2001 From: Nimetu Date: Tue, 7 Jun 2016 17:27:00 +0300 Subject: [PATCH 005/108] Fixed: Update text widget parent size when interface scale changes --HG-- branch : experimental-ui-scaling --- code/nel/include/nel/gui/widget_manager.h | 1 + code/nel/src/gui/widget_manager.cpp | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/code/nel/include/nel/gui/widget_manager.h b/code/nel/include/nel/gui/widget_manager.h index cfb51af8e..7566a8ad4 100644 --- a/code/nel/include/nel/gui/widget_manager.h +++ b/code/nel/include/nel/gui/widget_manager.h @@ -614,6 +614,7 @@ namespace NLGUI uint32 _ScreenH; uint32 _ScreenW; + float _InterfaceScale; std::vector< CInterfaceAnim* > activeAnims; diff --git a/code/nel/src/gui/widget_manager.cpp b/code/nel/src/gui/widget_manager.cpp index f866594c9..0288bff12 100644 --- a/code/nel/src/gui/widget_manager.cpp +++ b/code/nel/src/gui/widget_manager.cpp @@ -1850,9 +1850,10 @@ namespace NLGUI class InvalidateTextVisitor : public CInterfaceElementVisitor { public: - InvalidateTextVisitor( bool reset ) + InvalidateTextVisitor( bool reset, bool invalidate ) { this->reset = reset; + this->invalidate = invalidate; } void visitGroup( CInterfaceGroup *group ) @@ -1865,13 +1866,17 @@ namespace NLGUI { if( reset ) vt->resetTextIndex(); - vt->updateTextContext(); + if( invalidate ) + vt->invalidateContent(); + else + vt->updateTextContext(); } } } private: bool reset; + bool invalidate; }; // ------------------------------------------------------------------------------------------------ @@ -1884,6 +1889,8 @@ namespace NLGUI CViewRenderer::getInstance()->checkNewScreenSize (); CViewRenderer::getInstance()->getScreenSize (w, h); + bool scaleChanged = _InterfaceScale != CViewRenderer::getInstance()->getInterfaceScale(); + // Update ui:* (limit the master containers to the height of the screen) for (nMasterGroup = 0; nMasterGroup < _MasterGroups.size(); nMasterGroup++) { @@ -1902,7 +1909,7 @@ namespace NLGUI { SMasterGroup &rMG = _MasterGroups[nMasterGroup]; - InvalidateTextVisitor inv( false ); + InvalidateTextVisitor inv( false, scaleChanged ); rMG.Group->visitGroupAndChildren( &inv ); rMG.Group->invalidateCoords (); @@ -3725,6 +3732,7 @@ namespace NLGUI inGame = false; setScreenWH(0, 0); + _InterfaceScale = 1.0f; _GroupSelection = false; multiSelection = false; From 6bfc777496fe5506575d516c3c55e49322504b3c Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 14 Jul 2016 20:20:55 +0300 Subject: [PATCH 006/108] Fixed: Restore scaled interface from icfg --HG-- branch : experimental-ui-scaling --- code/ryzom/client/src/interface_v3/interface_manager.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/code/ryzom/client/src/interface_v3/interface_manager.cpp b/code/ryzom/client/src/interface_v3/interface_manager.cpp index 802f98c70..c75d880b6 100644 --- a/code/ryzom/client/src/interface_v3/interface_manager.cpp +++ b/code/ryzom/client/src/interface_v3/interface_manager.cpp @@ -1787,8 +1787,12 @@ bool CInterfaceManager::loadConfig (const string &filename) CWidgetManager::getInstance()->setScreenWH(_LastInGameScreenW, _LastInGameScreenH); // NB: we are typically InGame here (even though the _InGame flag is not yet set) // Use the screen size of the config file. Don't update current UI, just _Modes - CWidgetManager::getInstance()->moveAllWindowsToNewScreenSize(ClientCfg.Width, ClientCfg.Height, false); - updateDesktops( ClientCfg.Width, ClientCfg.Height ); + // + // ClientCfg has W/H set to screen size, but interface expects scaled size + sint32 scaledW = ClientCfg.Width / ClientCfg.InterfaceScale; + sint32 scaledH = ClientCfg.Height / ClientCfg.InterfaceScale; + CWidgetManager::getInstance()->moveAllWindowsToNewScreenSize(scaledW, scaledH, false); + updateDesktops(scaledW, scaledH); } // *** apply the current mode From 87aa86e01687da4babd991e3f8c9418a7f9d5881 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 14 Jul 2016 20:20:55 +0300 Subject: [PATCH 007/108] Fixed: Mouse pointer location after exiting freelook --HG-- branch : experimental-ui-scaling --- code/ryzom/client/src/input.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/ryzom/client/src/input.cpp b/code/ryzom/client/src/input.cpp index 403947538..f0a3133b9 100644 --- a/code/ryzom/client/src/input.cpp +++ b/code/ryzom/client/src/input.cpp @@ -179,9 +179,9 @@ void SetMouseCursor (bool updatePos) // Get the last cursor float x = 0.5f, y = 0.5f; - // Window size - uint width = Driver->getWindowWidth(); - uint height = Driver->getWindowHeight(); + // Screen size + uint width, height; + CViewRenderer::getInstance()->getScreenSize(width, height); // Update the interface pointer CInterfaceManager *instance = CInterfaceManager::getInstance(); From 5e1c6cd287f4718efbfae34e34f62a47cbdd99da Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 14 Jul 2016 20:20:55 +0300 Subject: [PATCH 008/108] Added: Bilinear filtering option for texture atlas textures --HG-- branch : experimental-ui-scaling --- code/nel/include/nel/gui/view_renderer.h | 2 ++ code/nel/src/gui/view_renderer.cpp | 5 +++-- code/ryzom/client/src/client_cfg.cpp | 2 ++ code/ryzom/client/src/client_cfg.h | 1 + code/ryzom/client/src/init.cpp | 1 + code/ryzom/client/src/main_loop_utilities.cpp | 3 +++ 6 files changed, 12 insertions(+), 2 deletions(-) diff --git a/code/nel/include/nel/gui/view_renderer.h b/code/nel/include/nel/gui/view_renderer.h index c737037a1..64cbfc888 100644 --- a/code/nel/include/nel/gui/view_renderer.h +++ b/code/nel/include/nel/gui/view_renderer.h @@ -181,6 +181,7 @@ namespace NLGUI */ void setInterfaceScale(float scale, sint32 width = 0, sint32 height = 0); float getInterfaceScale() const { return _InterfaceScale; } + void setBilinearFiltering(bool b) { _Bilinear = b; } /* * is the Screen minimized? @@ -537,6 +538,7 @@ namespace NLGUI float _InterfaceUserScale; sint32 _InterfaceBaseW, _InterfaceBaseH; sint32 _EffectiveScreenW, _EffectiveScreenH; + bool _Bilinear; void updateInterfaceScale(); diff --git a/code/nel/src/gui/view_renderer.cpp b/code/nel/src/gui/view_renderer.cpp index f8a8152b9..fe86dce1f 100644 --- a/code/nel/src/gui/view_renderer.cpp +++ b/code/nel/src/gui/view_renderer.cpp @@ -199,6 +199,7 @@ namespace NLGUI _EmptyLayer[i]= true; } _BlankGlobalTexture = NULL; + _Bilinear = false; updateInterfaceScale(); } @@ -1279,7 +1280,7 @@ namespace NLGUI _Material.setTexture(0, ite->Texture); // Special Case if _WorldSpaceTransformation and _WorldSpaceScale, enable bilinear - if(_WorldSpaceTransformation && _WorldSpaceScale) + if(_Bilinear || (_WorldSpaceTransformation && _WorldSpaceScale)) ite->Texture->setFilterMode(UTexture::Linear, UTexture::LinearMipMapOff); // draw quads and empty list @@ -1296,7 +1297,7 @@ namespace NLGUI } // Special Case if _WorldSpaceTransformation and _WorldSpaceScale, reset - if(_WorldSpaceTransformation && _WorldSpaceScale) + if(_Bilinear || (_WorldSpaceTransformation && _WorldSpaceScale)) ite->Texture->setFilterMode(UTexture::Nearest, UTexture::NearestMipMapOff); } if (!layer.FilteredAlphaBlendedQuads.empty() || diff --git a/code/ryzom/client/src/client_cfg.cpp b/code/ryzom/client/src/client_cfg.cpp index e321fdd6a..237892dbf 100644 --- a/code/ryzom/client/src/client_cfg.cpp +++ b/code/ryzom/client/src/client_cfg.cpp @@ -301,6 +301,7 @@ CClientConfig::CClientConfig() Gamma = 0.f; // Default Monitor Gamma. InterfaceScale = 1.0f; // Resize UI + BilinearUI = false; VREnable = false; VRDisplayDevice = "Auto"; @@ -839,6 +840,7 @@ void CClientConfig::setValues() READ_FLOAT_FV(InterfaceScale); // 50% smaller / 2x bigger clamp(ClientCfg.InterfaceScale, 0.5f, 2.0f); + READ_BOOL_FV(BilinearUI); // 3D Driver varPtr = ClientCfg.ConfigFile.getVarPtr ("Driver3D"); if (varPtr) diff --git a/code/ryzom/client/src/client_cfg.h b/code/ryzom/client/src/client_cfg.h index 157503957..2fc561c4d 100644 --- a/code/ryzom/client/src/client_cfg.h +++ b/code/ryzom/client/src/client_cfg.h @@ -148,6 +148,7 @@ struct CClientConfig // UI scaling float InterfaceScale; + bool BilinearUI; // VR bool VREnable; diff --git a/code/ryzom/client/src/init.cpp b/code/ryzom/client/src/init.cpp index 8105d9351..d0f38f7b6 100644 --- a/code/ryzom/client/src/init.cpp +++ b/code/ryzom/client/src/init.cpp @@ -1317,6 +1317,7 @@ void prelogInit() CInterfaceManager::getInstance(); CViewRenderer::getInstance()->setInterfaceScale(1.0f, 1024, 768); + CViewRenderer::getInstance()->setBilinearFiltering(ClientCfg.BilinearUI); // Yoyo: initialize NOW the InputHandler for Event filtering. CInputHandlerManager *InputHandlerManager = CInputHandlerManager::getInstance(); diff --git a/code/ryzom/client/src/main_loop_utilities.cpp b/code/ryzom/client/src/main_loop_utilities.cpp index e62c033e4..37bfc3247 100644 --- a/code/ryzom/client/src/main_loop_utilities.cpp +++ b/code/ryzom/client/src/main_loop_utilities.cpp @@ -104,6 +104,9 @@ void updateFromClientCfg() if (ClientCfg.InterfaceScale != LastClientCfg.InterfaceScale) CInterfaceManager::getInstance()->setInterfaceScale(ClientCfg.InterfaceScale); + if (ClientCfg.BilinearUI != LastClientCfg.BilinearUI) + CViewRenderer::getInstance()->setBilinearFiltering(ClientCfg.BilinearUI); + //--------------------------------------------------- if (ClientCfg.WaitVBL != LastClientCfg.WaitVBL) { From d8e1d3b25487b4f3e1380fa8a369e85446db6cd3 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 14 Jul 2016 20:20:55 +0300 Subject: [PATCH 009/108] Added: Border duplication option to texture atlas builder. --HG-- branch : experimental-ui-scaling --- code/nel/tools/3d/build_interface/main.cpp | 30 ++++++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/code/nel/tools/3d/build_interface/main.cpp b/code/nel/tools/3d/build_interface/main.cpp index 1b13a0f71..f2d3aa49c 100644 --- a/code/nel/tools/3d/build_interface/main.cpp +++ b/code/nel/tools/3d/build_interface/main.cpp @@ -201,10 +201,13 @@ int main(int nNbArg, char **ppArgs) outString ("ERROR : Wrong number of arguments\n"); outString ("USAGE : build_interface [-s] [path_maps2] [path_maps3] ....\n"); outString (" -s : build a subset of an existing interface definition while preserving the existing texture ids,"); + outString (" -b : border duplication to enable bi-linear filtering"); outString (" to support freeing up VRAM by switching to the subset without rebuilding the entire interface\n"); return -1; } - + + uint borderSize = 0; + // build as a subset of existing interface bool buildSubset = false; string existingUVfilename; @@ -215,6 +218,10 @@ int main(int nNbArg, char **ppArgs) { switch ( ppArgs[i][1] ) { + case 'B': + case 'b': + borderSize = 1; + break; case 'S': case 's': buildSubset = true; @@ -274,6 +281,19 @@ int main(int nNbArg, char **ppArgs) pBtmp->convertToType(CBitmap::RGBA); } + // duplicate bitmap border to enable bilinear filtering + { + NLMISC::CBitmap *tmp = new NLMISC::CBitmap; + tmp->resize(pBtmp->getWidth(), pBtmp->getHeight()); + tmp->blit(pBtmp, 0, 0); + // upscale image to get borders + tmp->resample(tmp->getWidth()+borderSize*2, tmp->getHeight()+borderSize*2); + // copy original + tmp->blit(pBtmp, borderSize, borderSize); + delete pBtmp; + pBtmp = tmp; + } + AllMaps[i] = pBtmp; } catch (const NLMISC::Exception &e) @@ -325,10 +345,10 @@ int main(int nNbArg, char **ppArgs) } putIn (AllMaps[i], &GlobalTexture, x, y); putIn (AllMaps[i], &GlobalMask, x, y, false); - UVMin[i].U = (float)x; - UVMin[i].V = (float)y; - UVMax[i].U = (float)x+AllMaps[i]->getWidth(); - UVMax[i].V = (float)y+AllMaps[i]->getHeight(); + UVMin[i].U = (float)x+borderSize; + UVMin[i].V = (float)y+borderSize; + UVMax[i].U = (float)x+borderSize+AllMaps[i]->getWidth()-borderSize*2; + UVMax[i].V = (float)y+borderSize+AllMaps[i]->getHeight()-borderSize*2; /* // Do not remove this is useful for debugging { From 44208ba1ad7358917c03cf5ee83e16aca52626d7 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Tue, 19 Jul 2016 11:12:47 +0300 Subject: [PATCH 010/108] Changed: Use float for bitmap pos for better pixel align when scale is used --HG-- branch : experimental-ui-scaling --- code/nel/include/nel/gui/view_renderer.h | 2 +- code/nel/src/gui/view_renderer.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/nel/include/nel/gui/view_renderer.h b/code/nel/include/nel/gui/view_renderer.h index 64cbfc888..99d92a7cd 100644 --- a/code/nel/include/nel/gui/view_renderer.h +++ b/code/nel/include/nel/gui/view_renderer.h @@ -192,7 +192,7 @@ namespace NLGUI * drawBitmap : this is the interface with all the views * */ - void drawRotFlipBitmap (sint layerId, sint32 x, sint32 y, sint32 width, sint32 height, uint8 rot, bool flipv, + void drawRotFlipBitmap (sint layerId, float x, float y, float width, float height, uint8 rot, bool flipv, sint32 nTxId, const NLMISC::CRGBA &col = NLMISC::CRGBA(255,255,255,255)); /* diff --git a/code/nel/src/gui/view_renderer.cpp b/code/nel/src/gui/view_renderer.cpp index fe86dce1f..d0bb214b2 100644 --- a/code/nel/src/gui/view_renderer.cpp +++ b/code/nel/src/gui/view_renderer.cpp @@ -496,7 +496,7 @@ namespace NLGUI /* * drawBitmap */ - void CViewRenderer::drawRotFlipBitmap (sint layerId, sint32 x, sint32 y, sint32 width, sint32 height, + void CViewRenderer::drawRotFlipBitmap (sint layerId, float x, float y, float width, float height, uint8 rot, bool flipv, sint32 nTxId, const CRGBA &col) { if (width <= 0 || height <= 0) return; From 8d09471a6dda49912bbecdc35d3c4a629c0e568b Mon Sep 17 00:00:00 2001 From: Nimetu Date: Tue, 19 Jul 2016 11:12:51 +0300 Subject: [PATCH 011/108] Fixed: Pixel align underline/strikethrough line on text --HG-- branch : experimental-ui-scaling --- code/nel/src/gui/view_text.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/nel/src/gui/view_text.cpp b/code/nel/src/gui/view_text.cpp index 6a66bdf74..ea0c96ca5 100644 --- a/code/nel/src/gui/view_text.cpp +++ b/code/nel/src/gui/view_text.cpp @@ -1083,10 +1083,10 @@ namespace NLGUI // Draw a line if (_Underlined) - rVR.drawRotFlipBitmap (_RenderLayer, (sint)floorf(px), y_line, line_width, 1, 0, false, rVR.getBlankTextureId(), col); + rVR.drawRotFlipBitmap (_RenderLayer, (sint)floorf(px), y_line, line_width, 1.0f / rVR.getInterfaceScale(), 0, false, rVR.getBlankTextureId(), col); if (_StrikeThrough) - rVR.drawRotFlipBitmap (_RenderLayer, (sint)floorf(px), y_line + (_FontHeight / 2), line_width, 1, 0, false, rVR.getBlankTextureId(), col); + rVR.drawRotFlipBitmap (_RenderLayer, (sint)floorf(px), y_line + (_FontHeight / 2), line_width, 1.0f / rVR.getInterfaceScale(), 0, false, rVR.getBlankTextureId(), col); // skip word px += currWord.Info.StringWidth; From 7b0d1cd0563e757e7bdeb663fa9b6a571d3fb550 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sat, 13 Aug 2016 23:32:54 +0300 Subject: [PATCH 012/108] Fixed: Double font width scaling --HG-- branch : experimental-ui-scaling --- code/nel/src/gui/view_text.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/nel/src/gui/view_text.cpp b/code/nel/src/gui/view_text.cpp index 0ff74c963..2be91d0d0 100644 --- a/code/nel/src/gui/view_text.cpp +++ b/code/nel/src/gui/view_text.cpp @@ -2293,7 +2293,7 @@ namespace NLGUI singleChar[0] = textValue[i]; si = textContext.getStringInfo(singleChar); sw = si.StringWidth / CViewRenderer::getInstance()->getInterfaceScale(); - px += sw / CViewRenderer::getInstance()->getInterfaceScale(); + px += sw; // the character is at the i - 1 position if (px > x) { From 279f67fc2dc087e4d93b87dc2622bf18d4871309 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sat, 13 Aug 2016 23:34:49 +0300 Subject: [PATCH 013/108] Changed: Use current scale value instead of asking CViewRenderer --HG-- branch : experimental-ui-scaling --- code/nel/include/nel/gui/view_text.h | 4 ++-- code/nel/src/gui/view_text.cpp | 34 ++++++++++++++-------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/code/nel/include/nel/gui/view_text.h b/code/nel/include/nel/gui/view_text.h index 265935067..3c5b8622d 100644 --- a/code/nel/include/nel/gui/view_text.h +++ b/code/nel/include/nel/gui/view_text.h @@ -326,7 +326,7 @@ namespace NLGUI CFormatInfo Format; public: // build from a string, using the current text context - void build(const ucstring &text, NL3D::UTextContext &textContext, uint numSpaces= 0); + void build(const ucstring &text, NL3D::UTextContext &textContext, float scale, uint numSpaces= 0); }; typedef std::vector TWordVect; @@ -339,7 +339,7 @@ namespace NLGUI // Clear the line & remove text contexts void clear(NL3D::UTextContext &textContext); // Add a new word (and its context) in the line + a number of spaces to append at the end of the line - void addWord(const ucstring &word, uint numSpaces, const CFormatInfo &wordFormat, float fontWidth, NL3D::UTextContext &textContext); + void addWord(const ucstring &word, uint numSpaces, const CFormatInfo &wordFormat, float fontWidth, NL3D::UTextContext &textContext, float scale); void addWord(const CWord &word, float fontWidth); uint getNumWords() const { return (uint)_Words.size(); } CWord &getWord(uint index) { return _Words[index]; } diff --git a/code/nel/src/gui/view_text.cpp b/code/nel/src/gui/view_text.cpp index 2be91d0d0..f28f9c3ed 100644 --- a/code/nel/src/gui/view_text.cpp +++ b/code/nel/src/gui/view_text.cpp @@ -1082,10 +1082,10 @@ namespace NLGUI // Draw a line if (_Underlined) - rVR.drawRotFlipBitmap (_RenderLayer, (sint)floorf(px), y_line, line_width, 1.0f / rVR.getInterfaceScale(), 0, false, rVR.getBlankTextureId(), col); + rVR.drawRotFlipBitmap (_RenderLayer, (sint)floorf(px), y_line, line_width, 1.0f / _Scale, 0, false, rVR.getBlankTextureId(), col); if (_StrikeThrough) - rVR.drawRotFlipBitmap (_RenderLayer, (sint)floorf(px), y_line + (_FontHeight / 2), line_width, 1.0f / rVR.getInterfaceScale(), 0, false, rVR.getBlankTextureId(), col); + rVR.drawRotFlipBitmap (_RenderLayer, (sint)floorf(px), y_line + (_FontHeight / 2), line_width, 1.0f / _Scale, 0, false, rVR.getBlankTextureId(), col); // skip word px += currWord.Info.StringWidth; @@ -1426,7 +1426,7 @@ namespace NLGUI linePushed= true; } // Append to the last line - _Lines.back()->addWord(ucCurrentWord, 0, wordFormat, _FontWidth, *TextContext); + _Lines.back()->addWord(ucCurrentWord, 0, wordFormat, _FontWidth, *TextContext, _Scale); // reset the word ucCurrentWord = ucstring(""); } @@ -1523,7 +1523,7 @@ namespace NLGUI if(currLine[i].Format!=lineWordFormat) { // add the current lineWord to the line. - _Lines.back()->addWord(lineWord, 0, lineWordFormat, _FontWidth, *TextContext); + _Lines.back()->addWord(lineWord, 0, lineWordFormat, _FontWidth, *TextContext, _Scale); // get new lineWordFormat lineWordFormat= currLine[i].Format; // and clear @@ -1538,7 +1538,7 @@ namespace NLGUI } if(!lineWord.empty()) - _Lines.back()->addWord(lineWord, 0, lineWordFormat, _FontWidth, *TextContext); + _Lines.back()->addWord(lineWord, 0, lineWordFormat, _FontWidth, *TextContext, _Scale); // clear currLine.clear(); @@ -1699,7 +1699,7 @@ namespace NLGUI { uint maxNumSpaces = std::max(1U, (uint) (nMaxWidth / _SpaceWidth)); CWord spaceWord; // a word with only spaces in it - spaceWord.build (ucstring (""), *TextContext, maxNumSpaces); + spaceWord.build (ucstring (""), *TextContext, _Scale, maxNumSpaces); spaceWord.Format= wordFormat; _Lines.push_back(TLineSPtr(new CLine)); _Lines.back()->addWord(spaceWord, _FontWidth); @@ -1728,7 +1728,7 @@ namespace NLGUI currChar = std::max((uint) 1, currChar); // must fit at least one character otherwise there's an infinite loop wordValue = _Text.substr(spaceEnd, currChar); CWord word; - word.build(wordValue, *TextContext, numSpaces); + word.build(wordValue, *TextContext, _Scale, numSpaces); word.Format= wordFormat; _Lines.push_back(TLineSPtr(new CLine)); float roomForSpaces = (float) nMaxWidth - word.Info.StringWidth; @@ -1763,7 +1763,7 @@ namespace NLGUI if (!wordValue.empty() || numSpaces != 0) { CWord word; - word.build(wordValue, *TextContext, numSpaces); + word.build(wordValue, *TextContext, _Scale, numSpaces); word.Format= wordFormat; // update line width _Lines.back()->addWord(word, _FontWidth); @@ -1883,7 +1883,7 @@ namespace NLGUI _Lines.pop_back(); CViewText::CLine *endLine = new CViewText::CLine; CViewText::CWord w; - w.build(string("..."), *TextContext); + w.build(string("..."), *TextContext, _Scale); endLine->addWord(w, _FontWidth); _Lines.push_back(TLineSPtr(endLine)); } @@ -2280,7 +2280,7 @@ namespace NLGUI // *************************************************************************** // Tool fct : From a word and a x coordinate, give the matching character index - static uint getCharacterIndex(const ucstring &textValue, float x, NL3D::UTextContext &textContext) + static uint getCharacterIndex(const ucstring &textValue, float x, NL3D::UTextContext &textContext, float scale) { float px = 0.f; float sw; @@ -2292,7 +2292,7 @@ namespace NLGUI // get character width singleChar[0] = textValue[i]; si = textContext.getStringInfo(singleChar); - sw = si.StringWidth / CViewRenderer::getInstance()->getInterfaceScale(); + sw = si.StringWidth / scale; px += sw; // the character is at the i - 1 position if (px > x) @@ -2390,7 +2390,7 @@ namespace NLGUI else { // the coord is in the word itself - index = charPos + currWord.NumSpaces + getCharacterIndex(currWord.Text, (float) x - (px + spacesWidth), *TextContext); + index = charPos + currWord.NumSpaces + getCharacterIndex(currWord.Text, (float) x - (px + spacesWidth), *TextContext, _Scale); cursorAtPreviousLineEnd = false; return; } @@ -2415,7 +2415,7 @@ namespace NLGUI index = 0; return; } - index = getCharacterIndex(_Text, (float) x, *TextContext); + index = getCharacterIndex(_Text, (float) x, *TextContext, _Scale); return; } } @@ -2516,10 +2516,10 @@ namespace NLGUI } // *************************************************************************** - void CViewText::CLine::addWord(const ucstring &text, uint numSpaces, const CFormatInfo &wordFormat, float fontWidth, NL3D::UTextContext &textContext) + void CViewText::CLine::addWord(const ucstring &text, uint numSpaces, const CFormatInfo &wordFormat, float fontWidth, NL3D::UTextContext &textContext, float scale) { CWord word; - word.build(text, textContext, numSpaces); + word.build(text, textContext, scale, numSpaces); word.Format= wordFormat; addWord(word, fontWidth); } @@ -2563,13 +2563,13 @@ namespace NLGUI } // *************************************************************************** - void CViewText::CWord::build(const ucstring &text, NL3D::UTextContext &textContext, uint numSpaces) + void CViewText::CWord::build(const ucstring &text, NL3D::UTextContext &textContext, float scale, uint numSpaces) { Text = text; NumSpaces = numSpaces; Index = textContext.textPush(text); Info = textContext.getStringInfo(Index); - Info.StringWidth /= CViewRenderer::getInstance()->getInterfaceScale(); + Info.StringWidth /= scale; } // *************************************************************************** From 4a722c7783754c6fead120a6f63cb73d32caf03e Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sun, 14 Aug 2016 02:25:10 +0300 Subject: [PATCH 014/108] Changed: Use UI scale change event to change CViewText font scale --HG-- branch : experimental-ui-scaling --- code/nel/include/nel/gui/interface_element.h | 16 +++++- code/nel/include/nel/gui/view_text.h | 1 + code/nel/include/nel/gui/widget_manager.h | 7 +++ code/nel/src/gui/view_text.cpp | 27 ++++++---- code/nel/src/gui/widget_manager.cpp | 56 ++++++++++++++++---- 5 files changed, 87 insertions(+), 20 deletions(-) diff --git a/code/nel/include/nel/gui/interface_element.h b/code/nel/include/nel/gui/interface_element.h index 65a0fcaff..5177e322a 100644 --- a/code/nel/include/nel/gui/interface_element.h +++ b/code/nel/include/nel/gui/interface_element.h @@ -38,6 +38,16 @@ namespace NLGUI class IActionHandler; class CGroupParagraph; + /** + * Interface for UI scale change event + */ + class IInterfaceScaleWatcher + { + public: + virtual ~IInterfaceScaleWatcher(){} + virtual void onInterfaceScaleChanged()=0; + }; + /** * A visitor to walk a tree of interface elements and apply a teartment on them. * @@ -66,7 +76,7 @@ namespace NLGUI * \author Nevrax France * \date 2002 */ - class CInterfaceElement : public CReflectableRefPtrTarget, public NLMISC::IStreamable + class CInterfaceElement : public IInterfaceScaleWatcher, public CReflectableRefPtrTarget, public NLMISC::IStreamable { public: @@ -402,6 +412,10 @@ namespace NLGUI */ virtual void onInvalidateContent() {} + /* Element UI scale change event callback + */ + virtual void onInterfaceScaleChanged() {} + // called by interfaceManager for master window only void resetInvalidCoords(); diff --git a/code/nel/include/nel/gui/view_text.h b/code/nel/include/nel/gui/view_text.h index 3c5b8622d..95b8d2e4a 100644 --- a/code/nel/include/nel/gui/view_text.h +++ b/code/nel/include/nel/gui/view_text.h @@ -70,6 +70,7 @@ namespace NLGUI virtual void checkCoords(); virtual void updateCoords(); virtual void onAddToGroup(); + virtual void onInterfaceScaleChanged(); /// From CInterfaceElement sint32 getMaxUsedW() const; diff --git a/code/nel/include/nel/gui/widget_manager.h b/code/nel/include/nel/gui/widget_manager.h index 8cca08cc6..827e35224 100644 --- a/code/nel/include/nel/gui/widget_manager.h +++ b/code/nel/include/nel/gui/widget_manager.h @@ -49,6 +49,7 @@ namespace NLGUI class CProcedure; class IEditorSelectionWatcher; class IWidgetAdditionWatcher; + class IInterfaceScaleWatcher; /** GUI Widget Manager @@ -530,6 +531,11 @@ namespace NLGUI bool unGroupSelection(); void setMultiSelection( bool b ){ multiSelection = b; } + float getInterfaceScale() const { return _InterfaceScale; } + void notifyInterfaceScaleWatchers(); + void registerInterfaceScaleWatcher(IInterfaceScaleWatcher *watcher); + void unregisterInterfaceScaleWatcher(IInterfaceScaleWatcher *watcher); + bool createNewGUI( const std::string &project, const std::string &window ); private: @@ -623,6 +629,7 @@ namespace NLGUI std::vector< IOnWidgetsDrawnHandler* > onWidgetsDrawnHandlers; std::vector< IEditorSelectionWatcher* > selectionWatchers; std::vector< IWidgetWatcher* > widgetWatchers; + std::vector< IInterfaceScaleWatcher* > scaleWatchers; std::vector< std::string > editorSelection; bool _GroupSelection; diff --git a/code/nel/src/gui/view_text.cpp b/code/nel/src/gui/view_text.cpp index f28f9c3ed..3e649c1c3 100644 --- a/code/nel/src/gui/view_text.cpp +++ b/code/nel/src/gui/view_text.cpp @@ -80,7 +80,7 @@ namespace NLGUI _MultiMaxLine = 0; _Index = 0xFFFFFFFF; - _Scale = 1.0f; + _Scale = CWidgetManager::getInstance()->getInterfaceScale(); _FontWidth= 0; _FontHeight = 0; _FontLegHeight = 0; @@ -113,6 +113,8 @@ namespace NLGUI :CViewBase(param) { setupDefault (); + + CWidgetManager::getInstance()->registerInterfaceScaleWatcher(this); } ///constructor @@ -130,11 +132,15 @@ namespace NLGUI _ShadowOutline = ShadowOutline; setText(Text); computeFontSize (); + + CWidgetManager::getInstance()->registerInterfaceScaleWatcher(this); } // *************************************************************************** CViewText::~CViewText() { + CWidgetManager::getInstance()->unregisterInterfaceScaleWatcher(this); + if (_Index != 0xFFFFFFFF) CViewRenderer::getTextContext(_FontName)->erase (_Index); clearLines(); @@ -893,9 +899,6 @@ namespace NLGUI // *************************************************************************** void CViewText::checkCoords () { - if (_Scale != CViewRenderer::getInstance()->getInterfaceScale()) - invalidateContent(); - if ((_MultiLine)&&(_Parent != NULL)) { // If never setuped, and if text is not empty @@ -1840,9 +1843,6 @@ namespace NLGUI // *************************************************************************** void CViewText::updateTextContext () { - if (_Scale != CViewRenderer::getInstance()->getInterfaceScale()) - computeFontSize(); - NL3D::UTextContext *TextContext = CViewRenderer::getTextContext(_FontName); TextContext->setHotSpot (UTextContext::BottomLeft); @@ -2732,11 +2732,20 @@ namespace NLGUI invalidateCoords(); } + // *************************************************************************** + void CViewText::onInterfaceScaleChanged() + { + _Scale = CWidgetManager::getInstance()->getInterfaceScale(); + + computeFontSize (); + invalidateContent(); + + CViewBase::onInterfaceScaleChanged(); + } + // *************************************************************************** void CViewText::computeFontSize () { - _Scale = CViewRenderer::getInstance()->getInterfaceScale(); - NL3D::UTextContext *TextContext = CViewRenderer::getTextContext(_FontName); TextContext->setHotSpot (UTextContext::BottomLeft); TextContext->setShaded (_Shadow); diff --git a/code/nel/src/gui/widget_manager.cpp b/code/nel/src/gui/widget_manager.cpp index 5d278a29a..a215dbf08 100644 --- a/code/nel/src/gui/widget_manager.cpp +++ b/code/nel/src/gui/widget_manager.cpp @@ -1850,10 +1850,9 @@ namespace NLGUI class InvalidateTextVisitor : public CInterfaceElementVisitor { public: - InvalidateTextVisitor( bool reset, bool invalidate ) + InvalidateTextVisitor( bool reset) { this->reset = reset; - this->invalidate = invalidate; } void visitGroup( CInterfaceGroup *group ) @@ -1866,17 +1865,13 @@ namespace NLGUI { if( reset ) vt->resetTextIndex(); - if( invalidate ) - vt->invalidateContent(); - else - vt->updateTextContext(); + vt->updateTextContext(); } } } private: bool reset; - bool invalidate; }; // ------------------------------------------------------------------------------------------------ @@ -1889,8 +1884,6 @@ namespace NLGUI CViewRenderer::getInstance()->checkNewScreenSize (); CViewRenderer::getInstance()->getScreenSize (w, h); - bool scaleChanged = _InterfaceScale != CViewRenderer::getInstance()->getInterfaceScale(); - // Update ui:* (limit the master containers to the height of the screen) for (nMasterGroup = 0; nMasterGroup < _MasterGroups.size(); nMasterGroup++) { @@ -1900,6 +1893,13 @@ namespace NLGUI } CViewRenderer::getInstance()->setClipWindow(0, 0, w, h); + bool scaleChanged = _InterfaceScale != CViewRenderer::getInstance()->getInterfaceScale(); + if (scaleChanged) + { + _InterfaceScale = CViewRenderer::getInstance()->getInterfaceScale(); + notifyInterfaceScaleWatchers(); + } + // If all conditions are OK, move windows so they fit correctly with new screen size // Do this work only InGame when Config is loaded moveAllWindowsToNewScreenSize(w,h,true); @@ -1909,7 +1909,7 @@ namespace NLGUI { SMasterGroup &rMG = _MasterGroups[nMasterGroup]; - InvalidateTextVisitor inv( false, scaleChanged ); + InvalidateTextVisitor inv( false); rMG.Group->visitGroupAndChildren( &inv ); rMG.Group->invalidateCoords (); @@ -3697,6 +3697,42 @@ namespace NLGUI } + // ------------------------------------------------------------------------------------------------ + void CWidgetManager::notifyInterfaceScaleWatchers() + { + std::vector< IInterfaceScaleWatcher* >::iterator itr = scaleWatchers.begin(); + while( itr != scaleWatchers.end() ) + { + (*itr)->onInterfaceScaleChanged(); + ++itr; + } + } + + // ------------------------------------------------------------------------------------------------ + void CWidgetManager::registerInterfaceScaleWatcher( IInterfaceScaleWatcher *watcher ) + { + std::vector< IInterfaceScaleWatcher* >::const_iterator itr + = std::find( scaleWatchers.begin(), scaleWatchers.end(), watcher ); + + if( itr != scaleWatchers.end() ) + return; + + scaleWatchers.push_back( watcher ); + } + + // ------------------------------------------------------------------------------------------------ + void CWidgetManager::unregisterInterfaceScaleWatcher( IInterfaceScaleWatcher *watcher ) + { + std::vector< IInterfaceScaleWatcher* >::iterator itr + = std::find( scaleWatchers.begin(), scaleWatchers.end(), watcher ); + + if( itr == scaleWatchers.end() ) + return; + + scaleWatchers.erase( itr ); + } + + // ------------------------------------------------------------------------------------------------ CWidgetManager::CWidgetManager() { LinkHack(); From b5717de4fcc9657bc1f78bdb264cf6d2c4faf15a Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sun, 14 Aug 2016 02:25:19 +0300 Subject: [PATCH 015/108] Added: Remove hardcoded text overflow indicator --HG-- branch : experimental-ui-scaling --- code/nel/include/nel/gui/view_text.h | 3 ++ code/nel/src/gui/view_text.cpp | 42 ++++++++++++++++++++-------- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/code/nel/include/nel/gui/view_text.h b/code/nel/include/nel/gui/view_text.h index 95b8d2e4a..e0603895c 100644 --- a/code/nel/include/nel/gui/view_text.h +++ b/code/nel/include/nel/gui/view_text.h @@ -90,6 +90,7 @@ namespace NLGUI void setShadowOutline (bool bShadowOutline); void setShadowColor (const NLMISC::CRGBA &color); void setLineMaxW (sint nMaxW, bool invalidate=true); + void setOverflowText(const ucstring &text) { _OverflowText = text; } void setMultiLine (bool bMultiLine); void setMultiLineSpace (sint nMultiLineSpace); void setMultiLineMaxWOnly (bool state); @@ -114,6 +115,7 @@ namespace NLGUI bool getShadowOutline() { return _ShadowOutline; } NLMISC::CRGBA getShadowColor() { return _ShadowColor; } sint getLineMaxW() const { return _LineMaxW; } + ucstring getOverflowText() const { return _OverflowText; } bool getMultiLine() const { return _MultiLine; } sint getMultiLineSpace() const { return _MultiLineSpace; } bool getMultiLineMaxWOnly() const { return _MultiLineMaxWOnly; } @@ -259,6 +261,7 @@ namespace NLGUI sint32 _LineMaxW; /// For single line, true if the text is clamped (ie displayed with "...") bool _SingleLineTextClamped; + ucstring _OverflowText; /// Multiple lines handling bool _MultiLine; diff --git a/code/nel/src/gui/view_text.cpp b/code/nel/src/gui/view_text.cpp index 3e649c1c3..02b2600b4 100644 --- a/code/nel/src/gui/view_text.cpp +++ b/code/nel/src/gui/view_text.cpp @@ -99,6 +99,7 @@ namespace NLGUI _AutoClamp = false; _ClampRight = true; // clamp on the right of the text + _OverflowText = "..."; _LetterColors = NULL; _Setuped= false; @@ -1880,12 +1881,15 @@ namespace NLGUI _Lines.back()->clear(*TextContext); _Lines.pop_back(); } - _Lines.pop_back(); - CViewText::CLine *endLine = new CViewText::CLine; - CViewText::CWord w; - w.build(string("..."), *TextContext, _Scale); - endLine->addWord(w, _FontWidth); - _Lines.push_back(TLineSPtr(endLine)); + if (_OverflowText.size() > 0) + { + _Lines.pop_back(); + CViewText::CLine *endLine = new CViewText::CLine; + CViewText::CWord w; + w.build(_OverflowText, *TextContext, _Scale); + endLine->addWord(w, _FontWidth); + _Lines.push_back(TLineSPtr(endLine)); + } } // Calculate size @@ -1947,10 +1951,20 @@ namespace NLGUI UTextContext::CStringInfo si; ucstring ucCurrentLine; ucCurrentLine.reserve(_Text.size()); + // Append ... to the end of line - si = TextContext->getStringInfo (ucstring("...")); - float dotWidth= si.StringWidth / _Scale; - float rWidthCurrentLine = 0, rWidthLetter; + float dotWidth; + if (_OverflowText.size() > 0) + { + si = TextContext->getStringInfo (ucstring(_OverflowText)); + dotWidth = si.StringWidth / _Scale; + } + else + { + dotWidth = 0.0f; + } + + float rWidthCurrentLine = 0, rWidthLetter; // for all the text if (_ClampRight) { @@ -1974,7 +1988,10 @@ namespace NLGUI } // Add the dots - ucCurrentLine+= "..."; + if (_OverflowText.size() > 0) + { + ucCurrentLine += _OverflowText; + } } else { @@ -1998,7 +2015,10 @@ namespace NLGUI } // Add the dots - ucCurrentLine = "..." + ucCurrentLine; + if (_OverflowText.size() > 0) + { + ucCurrentLine = _OverflowText + ucCurrentLine; + } } // And so setup this trunc text From a6b9da78fc4ec51c6a1009d8846f9454930646b1 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sun, 14 Aug 2016 10:41:27 +0300 Subject: [PATCH 016/108] Changed: Replace icon bitmap text with a font --HG-- branch : experimental-ui-scaling --- .../client/src/interface_v3/dbctrl_sheet.cpp | 274 ++++++++++++------ .../client/src/interface_v3/dbctrl_sheet.h | 26 +- 2 files changed, 197 insertions(+), 103 deletions(-) diff --git a/code/ryzom/client/src/interface_v3/dbctrl_sheet.cpp b/code/ryzom/client/src/interface_v3/dbctrl_sheet.cpp index abd85de83..2b9594db4 100644 --- a/code/ryzom/client/src/interface_v3/dbctrl_sheet.cpp +++ b/code/ryzom/client/src/interface_v3/dbctrl_sheet.cpp @@ -542,6 +542,15 @@ CCtrlDraggable(param) _ItemRMClassType= NULL; _ItemRMFaberStatType= NULL; _NotifyAnimEndTime = 0; + + _SheetText = NULL; + _QualityText = NULL; + _QuantityText = NULL; + _SapText = NULL; + + _QualityTextValue = -1; + _QuantityTextValue = -1; + _SapTextValue = -1; } // ---------------------------------------------------------------------------- @@ -562,6 +571,30 @@ CDBCtrlSheet::~CDBCtrlSheet() _GuildSymb = NULL; } + if (_SheetText) + { + delete _SheetText; + _SheetText = NULL; + } + + if (_QualityText) + { + delete _QualityText; + _QualityText = NULL; + } + + if (_QuantityText) + { + delete _QuantityText; + _QuantityText = NULL; + } + + if (_SapText) + { + delete _SapText; + _SapText = NULL; + } + // ensure erase static if(this==_CurrMenuSheet) _CurrMenuSheet = NULL; if(this == dynamic_cast< CDBCtrlSheet* >( CCtrlDraggable::getDraggedSheet() ) ) @@ -1020,6 +1053,19 @@ void CDBCtrlSheet::updateCoords () } } CInterfaceElement::updateCoords(); + + if (getActive()) + { + // updateCoords() is called to apply possible UI scale change to font + if (_SheetText) + _SheetText->updateCoords(); + if (_QualityText) + _QualityText->updateCoords(); + if (_QuantityText) + _QuantityText->updateCoords(); + if (_SapText) + _SapText->updateCoords(); + } } // ---------------------------------------------------------------------------- @@ -1733,82 +1779,107 @@ sint32 CDBCtrlSheet::getSPhraseId() const // *************************************************************************** void CDBCtrlSheet::resetCharBitmaps() { - _CharBitmaps.clear(); + if (_SheetText) + _SheetText->setActive(false); + if (_QualityText) + _QualityText->setActive(false); + if (_QuantityText) + _QuantityText->setActive(false); + if (_SapText) + _SapText->setActive(false); +} + +CViewText* CDBCtrlSheet::createViewText(const std::string &id, sint fontSize) +{ + CViewText *text = new CViewText(CViewBase::TCtorParam()); + text->setActive(false); + text->setId(getId() + ":" + id); + // icon slot does not inherit from CInterfaceGroup and cannot be used as parent + text->setParent(_Parent); + text->setParentPos(_ParentPos); + text->setParentPosRef(_ParentPosRef); + text->setFontSize(fontSize); + text->setShadow(true); + text->setColor(CRGBA::White); + text->setOverflowText(ucstring("")); + text->setModulateGlobalColor(false); + text->setMultiLineSpace(0); + text->setMultiLineMaxWOnly(true); + text->setMultiLine(false); + + return text; } // *************************************************************************** void CDBCtrlSheet::setupCharBitmaps(sint32 maxW, sint32 maxLine, sint32 maxWChar, bool topDown) { // Use the optString for the Macro name - _OptString = toLower(_OptString); CInterfaceManager *pIM = CInterfaceManager::getInstance(); CViewRenderer &rVR = *CViewRenderer::getInstance(); - _CharBitmaps.clear(); - if(maxLine<=0) + { + if (_SheetText) + _SheetText->setActive(false); return; - - uint h = rVR.getTypoTextureH('a'); - sint lineNb = 0; - sint curLineSize = 0; - uint i; - uint xChar= 0; - for (i = 0; i < _OptString.size(); ++i) - { - char c = _OptString[i]; - sint32 w = rVR.getTypoTextureW(c); - if ((curLineSize + w) > maxW || (sint32)xChar>=maxWChar) - { - lineNb ++; - if (lineNb == maxLine) break; - curLineSize = 0; - xChar = 0; - } - sint32 id = rVR.getTypoTextureId(c); - if (id != -1) - { - CCharBitmap bmp; - bmp.X= curLineSize; - bmp.Y= lineNb; - bmp.Id= id; - _CharBitmaps.push_back(bmp); - } - curLineSize += w; - ++xChar; } - if (lineNb == maxLine) lineNb = maxLine-1; + if (!_SheetText) + _SheetText = createViewText("opt", 8); - for (i = 0; i < _CharBitmaps.size(); ++i) - { - _CharBitmaps[i].Y = (lineNb - _CharBitmaps[i].Y)*h; - } - - // if topDown, revert Y + _SheetText->setX(0); if (topDown) { - for (i = 0; i < _CharBitmaps.size(); ++i) - { - _CharBitmaps[i].Y = _IconH - _CharBitmaps[i].Y - h; - } + _SheetText->setY(_IconH); + _SheetText->setPosRef(Hotspot_TL); } + else + _SheetText->setPosRef(Hotspot_BL); + + _SheetText->setActive(true); + + ucstring txt; + txt.fromUtf8(_OptString); + _SheetText->setText(txt); + _SheetText->setLineMaxW(maxW); + _SheetText->setMultiLine(maxLine > 1); + + _SheetText->updateTextContext(); } // *************************************************************************** void CDBCtrlSheet::displayCharBitmaps(sint32 rdrLayer, sint32 x, sint32 y, CRGBA color) { - CInterfaceManager *pIM = CInterfaceManager::getInstance(); CViewRenderer &rVR = *CViewRenderer::getInstance(); - for (uint i = 0; i < _CharBitmaps.size(); ++i) + if (_SheetText) { - rVR.draw11RotFlipBitmap (rdrLayer, x+_CharBitmaps[i].X, y+_CharBitmaps[i].Y, 0, false, - _CharBitmaps[i].Id, color); + sint32 ClipX, ClipY, ClipW, ClipH; + rVR.getClipWindow (ClipX, ClipY, ClipW, ClipH); + + // clip region is bbox from parent + if (isIn(ClipX, ClipY, ClipW, ClipH)) + { + // position text over icon + sint32 sx = x + _SheetText->getX(); + sint32 sy = y + _SheetText->getY(); + if (_SheetText->getPosRef() & Hotspot_Tx) + sy -= _SheetText->getHReal(); + + _SheetText->setRenderLayer(rdrLayer); + _SheetText->setXReal(sx); + _SheetText->setYReal(sy); + + _SheetText->setColor(color); + + // set clip area over icon to hide oversize text + rVR.setClipWindow(sx, sy, _WReal-2, _HReal-2); + _SheetText->draw(); + rVR.setClipWindow (ClipX, ClipY, ClipW, ClipH); + } } } - // *************************************************************************** void CDBCtrlSheet::draw() { @@ -2128,7 +2199,7 @@ void CDBCtrlSheet::drawSheet (sint32 x, sint32 y, bool draging, bool showSelecti if (_DispQuality != -1) { // For pact sheets, the quality gives the level of the sheet - drawNumber(x+1, y+1, wSheet, hSheet, numberColor, _DispQuality+1); + drawQuality(x+1, y+1, wSheet, hSheet, numberColor, _DispQuality+1); } break; case CCtrlSheetInfo::SheetType_Skill: @@ -2183,7 +2254,7 @@ void CDBCtrlSheet::drawSheet (sint32 x, sint32 y, bool draging, bool showSelecti // Draw Quality. -1 for lookandfeel. Draw it with global color if (_DispQuality != -1) { - drawNumber(x-1,y+1,wSheet, hSheet, numberColor, _DispQuality); + drawQuality(x-1,y+1, wSheet, hSheet, numberColor, _DispQuality); } // Draw Quantity if (_UseQuantity && _DispQuantity>-1) @@ -2200,7 +2271,7 @@ void CDBCtrlSheet::drawSheet (sint32 x, sint32 y, bool draging, bool showSelecti { quantity -= getLockValuePtr()->getValue32(); } - drawNumber(x+1+crossW, y+1, wSheet, hSheet, curSheetColor, quantity, false); + drawQuantity(x+1+crossW, y+1, wSheet, hSheet, curSheetColor, quantity); } // Is the item enchanted ? sint32 enchant = _Enchant.getSInt32(); @@ -2209,7 +2280,7 @@ void CDBCtrlSheet::drawSheet (sint32 x, sint32 y, bool draging, bool showSelecti // Yes draw the additionnal bitmap and the charge (number of enchanted spell we can launch with the enchanted item) enchant--; rVR.draw11RotFlipBitmap (_RenderLayer+2, x, y, 0, false, rVR.getSystemTextureId(CViewRenderer::ItemEnchantedTexture), curSheetColor); - drawNumber(x+1, y-2+hSheet-rVR.getFigurTextureH(), wSheet, hSheet, numberColor, enchant, false); + drawSap(x+1, y+1, wSheet, hSheet, numberColor, enchant); } // if a raw material for example, must add special icon text. @@ -2472,7 +2543,10 @@ void CDBCtrlSheet::drawSheet (sint32 x, sint32 y, bool draging, bool showSelecti // Draw Quality. -1 for lookandfeel. if( _ActualType == CCtrlSheetInfo::SheetType_SBrick ) { - if (_UseQuality && _MustDisplayLevel) drawNumber(px-1,py+1,BrickSheetWidth, BrickSheetHeight, curSheetColor, _DispLevel); + if (_UseQuality && _MustDisplayLevel) + { + drawQuality(px-1, py+1, BrickSheetWidth, BrickSheetHeight, curSheetColor, _DispLevel); + } } else { @@ -2540,51 +2614,63 @@ void CDBCtrlSheet::drawSheet (sint32 x, sint32 y, bool draging, bool showSelecti } // ---------------------------------------------------------------------------- -sint32 CDBCtrlSheet::drawNumber(sint32 x, sint32 y, sint32 wSheet, sint32 /* hSheet */, CRGBA color, sint32 value, bool rightAlign) +sint32 CDBCtrlSheet::drawQuality(sint32 x, sint32 y, sint32 wSheet, sint32 /* hSheet */, CRGBA color, sint32 value) { - CInterfaceManager *pIM = CInterfaceManager::getInstance(); - CViewRenderer &rVR = *CViewRenderer::getInstance(); - sint32 wDigit= rVR.getFigurTextureW(); - sint32 hDigit= rVR.getFigurTextureH(); + if (!_QualityText) + _QualityText = createViewText("quality", 7); - sint32 totalWidth = 0; - - if (value > -1) + if (_QualityTextValue != value) { - // compute start pos - sint32 units = value; - sint32 pos; - if(rightAlign) - pos= wSheet-wDigit; - else - { - // compute number of digits to display - pos= 0; - uint numDigits= 0; - do - { - units = units / 10; - numDigits++; - } - while (units != 0); - // so pos is: - pos= numDigits*wDigit - wDigit; - } - // display digits - units = value; - do - { - sint32 unitsID = rVR.getFigurTextureId (units % 10); - // decal layer because must drawn after Items/Brick in DXTC - rVR.drawRotFlipBitmap (_RenderLayer+2, x+pos, y, wDigit, hDigit, 0, false, unitsID, color); - units = units / 10; - pos-= wDigit; - totalWidth += wDigit; - } - while (units != 0); - return totalWidth; + _QualityText->setText(toString(value)); + _QualityText->updateTextContext(); + _QualityTextValue = value; } - return -1; + _QualityText->setActive(true); + _QualityText->setRenderLayer(_RenderLayer + 2); + _QualityText->setXReal(x + wSheet - _QualityText->getW()); + _QualityText->setYReal(y); + _QualityText->setColor(color); + _QualityText->draw(); +} + +// ---------------------------------------------------------------------------- +sint32 CDBCtrlSheet::drawQuantity(sint32 x, sint32 y, sint32 wSheet, sint32 /* hSheet */, CRGBA color, sint32 value) +{ + if (!_QuantityText) + _QuantityText = createViewText(getId() + ":quantity", 7); + + if (_QuantityTextValue != value) + { + _QuantityText->setText(toString(value)); + _QuantityText->updateTextContext(); + _QuantityTextValue = value; + } + _QuantityText->setActive(true); + _QuantityText->setRenderLayer(_RenderLayer + 2); + _QuantityText->setXReal(x); + _QuantityText->setYReal(y); + _QuantityText->setColor(color); + _QuantityText->draw(); +} + +// ---------------------------------------------------------------------------- +sint32 CDBCtrlSheet::drawSap(sint32 x, sint32 y, sint32 wSheet, sint32 hSheet, CRGBA color, sint32 value) +{ + if (!_SapText) + _SapText = createViewText(getId() + ":sap", 8); + + if (_SapTextValue != value) + { + _SapText->setText(toString(value)); + _SapText->updateTextContext(); + _SapTextValue = value; + } + _SapText->setActive(true); + _SapText->setRenderLayer(_RenderLayer + 2); + _SapText->setXReal(x); + _SapText->setYReal(y + hSheet - _SapText->getH()); + _SapText->setColor(color); + _SapText->draw(); } // ---------------------------------------------------------------------------- diff --git a/code/ryzom/client/src/interface_v3/dbctrl_sheet.h b/code/ryzom/client/src/interface_v3/dbctrl_sheet.h index 8c697d660..880199188 100644 --- a/code/ryzom/client/src/interface_v3/dbctrl_sheet.h +++ b/code/ryzom/client/src/interface_v3/dbctrl_sheet.h @@ -27,6 +27,7 @@ #include "nel/gui/ctrl_draggable.h" #include "nel/gui/interface_expr.h" #include "nel/gui/action_handler.h" +#include "nel/gui/view_text.h" #include "sphrase_manager.h" // game share #include "game_share/brick_types.h" @@ -50,6 +51,7 @@ class COutpostBuildingSheet; namespace NLGUI { class CViewRenderer; + class CViewText; } @@ -589,8 +591,10 @@ protected: // setup icon from phrases void setupDisplayAsPhrase(const std::vector &bricks, const ucstring &phraseName); - // draw a number and returns the width of the drawn number - sint32 drawNumber(sint32 x, sint32 y, sint32 wSheet, sint32 hSheet, NLMISC::CRGBA color, sint32 value, bool rightAlign=true); + // + sint32 drawQuality(sint32 x, sint32 y, sint32 wSheet, sint32 hSheet, NLMISC::CRGBA color, sint32 value); + sint32 drawQuantity(sint32 x, sint32 y, sint32 wSheet, sint32 hSheet, NLMISC::CRGBA color, sint32 value); + sint32 drawSap(sint32 x, sint32 y, sint32 wSheet, sint32 hSheet, NLMISC::CRGBA color, sint32 value); protected: @@ -703,13 +707,15 @@ protected: NLMISC::CCDBNodeLeaf *_GrayedLink; - // Macro or sentence String compiled as texture Ids and positions, from the _OptString. - struct CCharBitmap - { - sint32 X,Y; - sint32 Id; - }; - std::vector _CharBitmaps; + // + CViewText *_SheetText; + CViewText *_QualityText; + CViewText *_QuantityText; + CViewText *_SapText; + // cached values for faster comparing + sint32 _QualityTextValue; + sint32 _QuantityTextValue; + sint32 _SapTextValue; // Macro Id sint32 _MacroID; @@ -753,6 +759,8 @@ private: void resetAllTexIDs(); void setupInit(); + CViewText *createViewText(const std::string &id, sint fontSize); + void setupCharBitmaps(sint32 maxW, sint32 maxLine, sint32 maxWChar= 1000, bool topDown= false); void resetCharBitmaps(); void displayCharBitmaps(sint32 rdrLayer, sint32 x, sint32 y, NLMISC::CRGBA color); From edf755188484693f48b7d5ee7363feb7a18261e7 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 11 May 2017 11:47:30 +0300 Subject: [PATCH 017/108] Backed out changeset: 85c435fe41bb --HG-- branch : experimental-ui-scaling --- code/nel/tools/3d/build_interface/main.cpp | 30 ++++------------------ 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/code/nel/tools/3d/build_interface/main.cpp b/code/nel/tools/3d/build_interface/main.cpp index f2d3aa49c..1b13a0f71 100644 --- a/code/nel/tools/3d/build_interface/main.cpp +++ b/code/nel/tools/3d/build_interface/main.cpp @@ -201,13 +201,10 @@ int main(int nNbArg, char **ppArgs) outString ("ERROR : Wrong number of arguments\n"); outString ("USAGE : build_interface [-s] [path_maps2] [path_maps3] ....\n"); outString (" -s : build a subset of an existing interface definition while preserving the existing texture ids,"); - outString (" -b : border duplication to enable bi-linear filtering"); outString (" to support freeing up VRAM by switching to the subset without rebuilding the entire interface\n"); return -1; } - - uint borderSize = 0; - + // build as a subset of existing interface bool buildSubset = false; string existingUVfilename; @@ -218,10 +215,6 @@ int main(int nNbArg, char **ppArgs) { switch ( ppArgs[i][1] ) { - case 'B': - case 'b': - borderSize = 1; - break; case 'S': case 's': buildSubset = true; @@ -281,19 +274,6 @@ int main(int nNbArg, char **ppArgs) pBtmp->convertToType(CBitmap::RGBA); } - // duplicate bitmap border to enable bilinear filtering - { - NLMISC::CBitmap *tmp = new NLMISC::CBitmap; - tmp->resize(pBtmp->getWidth(), pBtmp->getHeight()); - tmp->blit(pBtmp, 0, 0); - // upscale image to get borders - tmp->resample(tmp->getWidth()+borderSize*2, tmp->getHeight()+borderSize*2); - // copy original - tmp->blit(pBtmp, borderSize, borderSize); - delete pBtmp; - pBtmp = tmp; - } - AllMaps[i] = pBtmp; } catch (const NLMISC::Exception &e) @@ -345,10 +325,10 @@ int main(int nNbArg, char **ppArgs) } putIn (AllMaps[i], &GlobalTexture, x, y); putIn (AllMaps[i], &GlobalMask, x, y, false); - UVMin[i].U = (float)x+borderSize; - UVMin[i].V = (float)y+borderSize; - UVMax[i].U = (float)x+borderSize+AllMaps[i]->getWidth()-borderSize*2; - UVMax[i].V = (float)y+borderSize+AllMaps[i]->getHeight()-borderSize*2; + UVMin[i].U = (float)x; + UVMin[i].V = (float)y; + UVMax[i].U = (float)x+AllMaps[i]->getWidth(); + UVMax[i].V = (float)y+AllMaps[i]->getHeight(); /* // Do not remove this is useful for debugging { From 4c605fcfe0618bd19771b67c3ef42fa73dd017cf Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 11 May 2017 11:48:05 +0300 Subject: [PATCH 018/108] Backed out changeset: da24b99ddf3d --HG-- branch : experimental-ui-scaling --- .../client/src/interface_v3/dbctrl_sheet.cpp | 274 ++++++------------ .../client/src/interface_v3/dbctrl_sheet.h | 26 +- 2 files changed, 103 insertions(+), 197 deletions(-) diff --git a/code/ryzom/client/src/interface_v3/dbctrl_sheet.cpp b/code/ryzom/client/src/interface_v3/dbctrl_sheet.cpp index 2b9594db4..abd85de83 100644 --- a/code/ryzom/client/src/interface_v3/dbctrl_sheet.cpp +++ b/code/ryzom/client/src/interface_v3/dbctrl_sheet.cpp @@ -542,15 +542,6 @@ CCtrlDraggable(param) _ItemRMClassType= NULL; _ItemRMFaberStatType= NULL; _NotifyAnimEndTime = 0; - - _SheetText = NULL; - _QualityText = NULL; - _QuantityText = NULL; - _SapText = NULL; - - _QualityTextValue = -1; - _QuantityTextValue = -1; - _SapTextValue = -1; } // ---------------------------------------------------------------------------- @@ -571,30 +562,6 @@ CDBCtrlSheet::~CDBCtrlSheet() _GuildSymb = NULL; } - if (_SheetText) - { - delete _SheetText; - _SheetText = NULL; - } - - if (_QualityText) - { - delete _QualityText; - _QualityText = NULL; - } - - if (_QuantityText) - { - delete _QuantityText; - _QuantityText = NULL; - } - - if (_SapText) - { - delete _SapText; - _SapText = NULL; - } - // ensure erase static if(this==_CurrMenuSheet) _CurrMenuSheet = NULL; if(this == dynamic_cast< CDBCtrlSheet* >( CCtrlDraggable::getDraggedSheet() ) ) @@ -1053,19 +1020,6 @@ void CDBCtrlSheet::updateCoords () } } CInterfaceElement::updateCoords(); - - if (getActive()) - { - // updateCoords() is called to apply possible UI scale change to font - if (_SheetText) - _SheetText->updateCoords(); - if (_QualityText) - _QualityText->updateCoords(); - if (_QuantityText) - _QuantityText->updateCoords(); - if (_SapText) - _SapText->updateCoords(); - } } // ---------------------------------------------------------------------------- @@ -1779,107 +1733,82 @@ sint32 CDBCtrlSheet::getSPhraseId() const // *************************************************************************** void CDBCtrlSheet::resetCharBitmaps() { - if (_SheetText) - _SheetText->setActive(false); - if (_QualityText) - _QualityText->setActive(false); - if (_QuantityText) - _QuantityText->setActive(false); - if (_SapText) - _SapText->setActive(false); -} - -CViewText* CDBCtrlSheet::createViewText(const std::string &id, sint fontSize) -{ - CViewText *text = new CViewText(CViewBase::TCtorParam()); - text->setActive(false); - text->setId(getId() + ":" + id); - // icon slot does not inherit from CInterfaceGroup and cannot be used as parent - text->setParent(_Parent); - text->setParentPos(_ParentPos); - text->setParentPosRef(_ParentPosRef); - text->setFontSize(fontSize); - text->setShadow(true); - text->setColor(CRGBA::White); - text->setOverflowText(ucstring("")); - text->setModulateGlobalColor(false); - text->setMultiLineSpace(0); - text->setMultiLineMaxWOnly(true); - text->setMultiLine(false); - - return text; + _CharBitmaps.clear(); } // *************************************************************************** void CDBCtrlSheet::setupCharBitmaps(sint32 maxW, sint32 maxLine, sint32 maxWChar, bool topDown) { // Use the optString for the Macro name + _OptString = toLower(_OptString); CInterfaceManager *pIM = CInterfaceManager::getInstance(); CViewRenderer &rVR = *CViewRenderer::getInstance(); + _CharBitmaps.clear(); + if(maxLine<=0) - { - if (_SheetText) - _SheetText->setActive(false); return; + + uint h = rVR.getTypoTextureH('a'); + sint lineNb = 0; + sint curLineSize = 0; + uint i; + uint xChar= 0; + for (i = 0; i < _OptString.size(); ++i) + { + char c = _OptString[i]; + sint32 w = rVR.getTypoTextureW(c); + if ((curLineSize + w) > maxW || (sint32)xChar>=maxWChar) + { + lineNb ++; + if (lineNb == maxLine) break; + curLineSize = 0; + xChar = 0; + } + sint32 id = rVR.getTypoTextureId(c); + if (id != -1) + { + CCharBitmap bmp; + bmp.X= curLineSize; + bmp.Y= lineNb; + bmp.Id= id; + _CharBitmaps.push_back(bmp); + } + curLineSize += w; + ++xChar; } - if (!_SheetText) - _SheetText = createViewText("opt", 8); + if (lineNb == maxLine) lineNb = maxLine-1; - _SheetText->setX(0); + for (i = 0; i < _CharBitmaps.size(); ++i) + { + _CharBitmaps[i].Y = (lineNb - _CharBitmaps[i].Y)*h; + } + + // if topDown, revert Y if (topDown) { - _SheetText->setY(_IconH); - _SheetText->setPosRef(Hotspot_TL); + for (i = 0; i < _CharBitmaps.size(); ++i) + { + _CharBitmaps[i].Y = _IconH - _CharBitmaps[i].Y - h; + } } - else - _SheetText->setPosRef(Hotspot_BL); - - _SheetText->setActive(true); - - ucstring txt; - txt.fromUtf8(_OptString); - _SheetText->setText(txt); - _SheetText->setLineMaxW(maxW); - _SheetText->setMultiLine(maxLine > 1); - - _SheetText->updateTextContext(); } // *************************************************************************** void CDBCtrlSheet::displayCharBitmaps(sint32 rdrLayer, sint32 x, sint32 y, CRGBA color) { + CInterfaceManager *pIM = CInterfaceManager::getInstance(); CViewRenderer &rVR = *CViewRenderer::getInstance(); - if (_SheetText) + for (uint i = 0; i < _CharBitmaps.size(); ++i) { - sint32 ClipX, ClipY, ClipW, ClipH; - rVR.getClipWindow (ClipX, ClipY, ClipW, ClipH); - - // clip region is bbox from parent - if (isIn(ClipX, ClipY, ClipW, ClipH)) - { - // position text over icon - sint32 sx = x + _SheetText->getX(); - sint32 sy = y + _SheetText->getY(); - if (_SheetText->getPosRef() & Hotspot_Tx) - sy -= _SheetText->getHReal(); - - _SheetText->setRenderLayer(rdrLayer); - _SheetText->setXReal(sx); - _SheetText->setYReal(sy); - - _SheetText->setColor(color); - - // set clip area over icon to hide oversize text - rVR.setClipWindow(sx, sy, _WReal-2, _HReal-2); - _SheetText->draw(); - rVR.setClipWindow (ClipX, ClipY, ClipW, ClipH); - } + rVR.draw11RotFlipBitmap (rdrLayer, x+_CharBitmaps[i].X, y+_CharBitmaps[i].Y, 0, false, + _CharBitmaps[i].Id, color); } } + // *************************************************************************** void CDBCtrlSheet::draw() { @@ -2199,7 +2128,7 @@ void CDBCtrlSheet::drawSheet (sint32 x, sint32 y, bool draging, bool showSelecti if (_DispQuality != -1) { // For pact sheets, the quality gives the level of the sheet - drawQuality(x+1, y+1, wSheet, hSheet, numberColor, _DispQuality+1); + drawNumber(x+1, y+1, wSheet, hSheet, numberColor, _DispQuality+1); } break; case CCtrlSheetInfo::SheetType_Skill: @@ -2254,7 +2183,7 @@ void CDBCtrlSheet::drawSheet (sint32 x, sint32 y, bool draging, bool showSelecti // Draw Quality. -1 for lookandfeel. Draw it with global color if (_DispQuality != -1) { - drawQuality(x-1,y+1, wSheet, hSheet, numberColor, _DispQuality); + drawNumber(x-1,y+1,wSheet, hSheet, numberColor, _DispQuality); } // Draw Quantity if (_UseQuantity && _DispQuantity>-1) @@ -2271,7 +2200,7 @@ void CDBCtrlSheet::drawSheet (sint32 x, sint32 y, bool draging, bool showSelecti { quantity -= getLockValuePtr()->getValue32(); } - drawQuantity(x+1+crossW, y+1, wSheet, hSheet, curSheetColor, quantity); + drawNumber(x+1+crossW, y+1, wSheet, hSheet, curSheetColor, quantity, false); } // Is the item enchanted ? sint32 enchant = _Enchant.getSInt32(); @@ -2280,7 +2209,7 @@ void CDBCtrlSheet::drawSheet (sint32 x, sint32 y, bool draging, bool showSelecti // Yes draw the additionnal bitmap and the charge (number of enchanted spell we can launch with the enchanted item) enchant--; rVR.draw11RotFlipBitmap (_RenderLayer+2, x, y, 0, false, rVR.getSystemTextureId(CViewRenderer::ItemEnchantedTexture), curSheetColor); - drawSap(x+1, y+1, wSheet, hSheet, numberColor, enchant); + drawNumber(x+1, y-2+hSheet-rVR.getFigurTextureH(), wSheet, hSheet, numberColor, enchant, false); } // if a raw material for example, must add special icon text. @@ -2543,10 +2472,7 @@ void CDBCtrlSheet::drawSheet (sint32 x, sint32 y, bool draging, bool showSelecti // Draw Quality. -1 for lookandfeel. if( _ActualType == CCtrlSheetInfo::SheetType_SBrick ) { - if (_UseQuality && _MustDisplayLevel) - { - drawQuality(px-1, py+1, BrickSheetWidth, BrickSheetHeight, curSheetColor, _DispLevel); - } + if (_UseQuality && _MustDisplayLevel) drawNumber(px-1,py+1,BrickSheetWidth, BrickSheetHeight, curSheetColor, _DispLevel); } else { @@ -2614,63 +2540,51 @@ void CDBCtrlSheet::drawSheet (sint32 x, sint32 y, bool draging, bool showSelecti } // ---------------------------------------------------------------------------- -sint32 CDBCtrlSheet::drawQuality(sint32 x, sint32 y, sint32 wSheet, sint32 /* hSheet */, CRGBA color, sint32 value) +sint32 CDBCtrlSheet::drawNumber(sint32 x, sint32 y, sint32 wSheet, sint32 /* hSheet */, CRGBA color, sint32 value, bool rightAlign) { - if (!_QualityText) - _QualityText = createViewText("quality", 7); + CInterfaceManager *pIM = CInterfaceManager::getInstance(); + CViewRenderer &rVR = *CViewRenderer::getInstance(); + sint32 wDigit= rVR.getFigurTextureW(); + sint32 hDigit= rVR.getFigurTextureH(); - if (_QualityTextValue != value) + sint32 totalWidth = 0; + + if (value > -1) { - _QualityText->setText(toString(value)); - _QualityText->updateTextContext(); - _QualityTextValue = value; + // compute start pos + sint32 units = value; + sint32 pos; + if(rightAlign) + pos= wSheet-wDigit; + else + { + // compute number of digits to display + pos= 0; + uint numDigits= 0; + do + { + units = units / 10; + numDigits++; + } + while (units != 0); + // so pos is: + pos= numDigits*wDigit - wDigit; + } + // display digits + units = value; + do + { + sint32 unitsID = rVR.getFigurTextureId (units % 10); + // decal layer because must drawn after Items/Brick in DXTC + rVR.drawRotFlipBitmap (_RenderLayer+2, x+pos, y, wDigit, hDigit, 0, false, unitsID, color); + units = units / 10; + pos-= wDigit; + totalWidth += wDigit; + } + while (units != 0); + return totalWidth; } - _QualityText->setActive(true); - _QualityText->setRenderLayer(_RenderLayer + 2); - _QualityText->setXReal(x + wSheet - _QualityText->getW()); - _QualityText->setYReal(y); - _QualityText->setColor(color); - _QualityText->draw(); -} - -// ---------------------------------------------------------------------------- -sint32 CDBCtrlSheet::drawQuantity(sint32 x, sint32 y, sint32 wSheet, sint32 /* hSheet */, CRGBA color, sint32 value) -{ - if (!_QuantityText) - _QuantityText = createViewText(getId() + ":quantity", 7); - - if (_QuantityTextValue != value) - { - _QuantityText->setText(toString(value)); - _QuantityText->updateTextContext(); - _QuantityTextValue = value; - } - _QuantityText->setActive(true); - _QuantityText->setRenderLayer(_RenderLayer + 2); - _QuantityText->setXReal(x); - _QuantityText->setYReal(y); - _QuantityText->setColor(color); - _QuantityText->draw(); -} - -// ---------------------------------------------------------------------------- -sint32 CDBCtrlSheet::drawSap(sint32 x, sint32 y, sint32 wSheet, sint32 hSheet, CRGBA color, sint32 value) -{ - if (!_SapText) - _SapText = createViewText(getId() + ":sap", 8); - - if (_SapTextValue != value) - { - _SapText->setText(toString(value)); - _SapText->updateTextContext(); - _SapTextValue = value; - } - _SapText->setActive(true); - _SapText->setRenderLayer(_RenderLayer + 2); - _SapText->setXReal(x); - _SapText->setYReal(y + hSheet - _SapText->getH()); - _SapText->setColor(color); - _SapText->draw(); + return -1; } // ---------------------------------------------------------------------------- diff --git a/code/ryzom/client/src/interface_v3/dbctrl_sheet.h b/code/ryzom/client/src/interface_v3/dbctrl_sheet.h index 880199188..8c697d660 100644 --- a/code/ryzom/client/src/interface_v3/dbctrl_sheet.h +++ b/code/ryzom/client/src/interface_v3/dbctrl_sheet.h @@ -27,7 +27,6 @@ #include "nel/gui/ctrl_draggable.h" #include "nel/gui/interface_expr.h" #include "nel/gui/action_handler.h" -#include "nel/gui/view_text.h" #include "sphrase_manager.h" // game share #include "game_share/brick_types.h" @@ -51,7 +50,6 @@ class COutpostBuildingSheet; namespace NLGUI { class CViewRenderer; - class CViewText; } @@ -591,10 +589,8 @@ protected: // setup icon from phrases void setupDisplayAsPhrase(const std::vector &bricks, const ucstring &phraseName); - // - sint32 drawQuality(sint32 x, sint32 y, sint32 wSheet, sint32 hSheet, NLMISC::CRGBA color, sint32 value); - sint32 drawQuantity(sint32 x, sint32 y, sint32 wSheet, sint32 hSheet, NLMISC::CRGBA color, sint32 value); - sint32 drawSap(sint32 x, sint32 y, sint32 wSheet, sint32 hSheet, NLMISC::CRGBA color, sint32 value); + // draw a number and returns the width of the drawn number + sint32 drawNumber(sint32 x, sint32 y, sint32 wSheet, sint32 hSheet, NLMISC::CRGBA color, sint32 value, bool rightAlign=true); protected: @@ -707,15 +703,13 @@ protected: NLMISC::CCDBNodeLeaf *_GrayedLink; - // - CViewText *_SheetText; - CViewText *_QualityText; - CViewText *_QuantityText; - CViewText *_SapText; - // cached values for faster comparing - sint32 _QualityTextValue; - sint32 _QuantityTextValue; - sint32 _SapTextValue; + // Macro or sentence String compiled as texture Ids and positions, from the _OptString. + struct CCharBitmap + { + sint32 X,Y; + sint32 Id; + }; + std::vector _CharBitmaps; // Macro Id sint32 _MacroID; @@ -759,8 +753,6 @@ private: void resetAllTexIDs(); void setupInit(); - CViewText *createViewText(const std::string &id, sint fontSize); - void setupCharBitmaps(sint32 maxW, sint32 maxLine, sint32 maxWChar= 1000, bool topDown= false); void resetCharBitmaps(); void displayCharBitmaps(sint32 rdrLayer, sint32 x, sint32 y, NLMISC::CRGBA color); From ddcdc8b8fb03c704ee7d6416b5ced4a7f1958373 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Fri, 16 Dec 2016 00:03:30 +0200 Subject: [PATCH 019/108] Added: Border duplication option to texture atlas builder. --HG-- branch : experimental-ui-scaling --- code/nel/tools/3d/build_interface/main.cpp | 28 ++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/code/nel/tools/3d/build_interface/main.cpp b/code/nel/tools/3d/build_interface/main.cpp index a8f854721..58c97e513 100644 --- a/code/nel/tools/3d/build_interface/main.cpp +++ b/code/nel/tools/3d/build_interface/main.cpp @@ -214,6 +214,7 @@ int main(int argc, char **argv) args.addArg("x", "extract", "", "Extract all interface elements from to ."); args.addAdditionalArg("output_filename", "PNG or TGA file to generate", true); args.addAdditionalArg("input_path", "Path that containts interfaces elements", false); + args.addArg("b", "border", "", "Duplicate icon border to allow bilinear filtering"); if (!args.parse(argc, argv)) return 1; @@ -227,6 +228,13 @@ int main(int argc, char **argv) existingUVfilename = args.getArg("s").front(); } + // + uint borderSize = 0; + if (args.haveArg("b")) + { + borderSize = 1; + } + // extract all interface elements bool extractElements = args.haveArg("x"); @@ -407,6 +415,18 @@ int main(int argc, char **argv) pBtmp->convertToType(CBitmap::RGBA); } + // duplicate icon border + if (borderSize > 0) + { + NLMISC::CBitmap *tmp = new NLMISC::CBitmap; + tmp->resize(pBtmp->getWidth(), pBtmp->getHeight()); + tmp->blit(pBtmp, 0, 0); + tmp->resample(tmp->getWidth() + borderSize * 2, tmp->getHeight() + borderSize * 2); + tmp->blit(pBtmp, borderSize, borderSize); + delete pBtmp; + pBtmp = tmp; + } + AllMaps[i] = pBtmp; } catch (const NLMISC::Exception &e) @@ -461,10 +481,10 @@ int main(int argc, char **argv) putIn (AllMaps[i], &GlobalTexture, x, y); putIn (AllMaps[i], &GlobalMask, x, y, false); - UVMin[i].U = (float)x; - UVMin[i].V = (float)y; - UVMax[i].U = (float)x+AllMaps[i]->getWidth(); - UVMax[i].V = (float)y+AllMaps[i]->getHeight(); + UVMin[i].U = (float)x + borderSize; + UVMin[i].V = (float)y + borderSize; + UVMax[i].U = (float)x + AllMaps[i]->getWidth() - borderSize; + UVMax[i].V = (float)y + AllMaps[i]->getHeight() - borderSize; #if 0 // Do not remove this is useful for debugging From 4ba39732954b5c8a7cc941fb0c302e9ca4ecdfc1 Mon Sep 17 00:00:00 2001 From: "nimetu@gmail.com" Date: Fri, 16 Mar 2018 15:00:30 +0200 Subject: [PATCH 020/108] Changed: Remove ui scale option from ingame config --HG-- branch : experimental-ui-scaling --- .../data/gamedev/interfaces_v3/game_config.xml | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/game_config.xml b/code/ryzom/client/data/gamedev/interfaces_v3/game_config.xml index c3b904f01..2728e7854 100644 --- a/code/ryzom/client/data/gamedev/interfaces_v3/game_config.xml +++ b/code/ryzom/client/data/gamedev/interfaces_v3/game_config.xml @@ -876,17 +876,10 @@ posparent="lum" x="0" y="-2" /> - @@ -3098,13 +3091,6 @@ realtime="true" widget="sbfloat" link="Gamma" /> - Date: Fri, 16 Mar 2018 15:01:22 +0200 Subject: [PATCH 021/108] Added: Allow to set UI scale with /setuiscale command --HG-- branch : experimental-ui-scaling --- .../data/gamedev/interfaces_v3/commands.xml | 2 ++ code/ryzom/client/src/client_cfg.cpp | 3 +- code/ryzom/client/src/client_cfg.h | 3 ++ .../src/interface_v3/action_handler_game.cpp | 28 +++++++++++++++++++ .../src/interface_v3/interface_manager.cpp | 4 +-- 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/commands.xml b/code/ryzom/client/data/gamedev/interfaces_v3/commands.xml index 31a1a7099..6b45ec715 100644 --- a/code/ryzom/client/data/gamedev/interfaces_v3/commands.xml +++ b/code/ryzom/client/data/gamedev/interfaces_v3/commands.xml @@ -122,6 +122,8 @@ + + diff --git a/code/ryzom/client/src/client_cfg.cpp b/code/ryzom/client/src/client_cfg.cpp index c21797def..14741b7ad 100644 --- a/code/ryzom/client/src/client_cfg.cpp +++ b/code/ryzom/client/src/client_cfg.cpp @@ -839,8 +839,7 @@ void CClientConfig::setValues() READ_FLOAT_FV(Gamma) // UI scaling READ_FLOAT_FV(InterfaceScale); - // 50% smaller / 2x bigger - clamp(ClientCfg.InterfaceScale, 0.5f, 2.0f); + clamp(ClientCfg.InterfaceScale, MIN_INTERFACE_SCALE, MAX_INTERFACE_SCALE); READ_BOOL_FV(BilinearUI); // 3D Driver varPtr = ClientCfg.ConfigFile.getVarPtr ("Driver3D"); diff --git a/code/ryzom/client/src/client_cfg.h b/code/ryzom/client/src/client_cfg.h index accc5ae1e..9b24f3058 100644 --- a/code/ryzom/client/src/client_cfg.h +++ b/code/ryzom/client/src/client_cfg.h @@ -46,6 +46,9 @@ using NLMISC::CVector; using NLMISC::CRGBA; using std::string; +// limits for UI scale +const float MIN_INTERFACE_SCALE = 0.8; +const float MAX_INTERFACE_SCALE = 2.0; //--------------------------------------------------- // CClientConfig : diff --git a/code/ryzom/client/src/interface_v3/action_handler_game.cpp b/code/ryzom/client/src/interface_v3/action_handler_game.cpp index 196b0a45e..f1b27bb46 100644 --- a/code/ryzom/client/src/interface_v3/action_handler_game.cpp +++ b/code/ryzom/client/src/interface_v3/action_handler_game.cpp @@ -3732,6 +3732,34 @@ class CHandlerGameConfigChangeScreenRatioCustom : public IActionHandler }; REGISTER_ACTION_HANDLER (CHandlerGameConfigChangeScreenRatioCustom, "game_config_change_screen_ratio_custom"); +// *************************************************************************** +class CHandlerSetInterfaceScale : public IActionHandler +{ + virtual void execute (CCtrlBase *pCaller, const string &Params) + { + std::string s; + s = getParam(Params, "scale"); + if (!s.empty()) { + float scale; + if (fromString(s, scale)) + { + if (scale >= MIN_INTERFACE_SCALE && scale <= MAX_INTERFACE_SCALE) + { + ClientCfg.InterfaceScale = scale; + ClientCfg.writeDouble("InterfaceScale", ClientCfg.InterfaceScale); + + ClientCfg.IsInvalidated = true; + return; + } + } + } + + ucstring help("/setuiscale "+toString("%.1f .. %.1f", MIN_INTERFACE_SCALE, MAX_INTERFACE_SCALE)); + CInterfaceManager::getInstance()->displaySystemInfo(help); + } +}; +REGISTER_ACTION_HANDLER (CHandlerSetInterfaceScale, "set_ui_scale"); + // *************************************************************************** class CHandlerGameMissionAbandon : public IActionHandler diff --git a/code/ryzom/client/src/interface_v3/interface_manager.cpp b/code/ryzom/client/src/interface_v3/interface_manager.cpp index 221ae0671..a63d4b5ac 100644 --- a/code/ryzom/client/src/interface_v3/interface_manager.cpp +++ b/code/ryzom/client/src/interface_v3/interface_manager.cpp @@ -2038,8 +2038,7 @@ void CInterfaceManager::drawViews(NL3D::UCamera camera) _CurrentPlayerCharac[i] = node ? node->getValue32() : 0; } - // update value change from ingame config window - // must update it here, right before widget manager checks it + // scale must be updated right before widget manager checks it if (_InterfaceScaleChanged) { CViewRenderer::getInstance()->setInterfaceScale(_InterfaceScale); @@ -2916,7 +2915,6 @@ NLMISC_COMMAND(loadui, "Load an interface file", "") return result; } - // *************************************************************************** void CInterfaceManager::displayWebWindow(const string & name, const string & url) { From d7ee45021533c94ccb671e5655368201689699cb Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sat, 17 Mar 2018 13:59:42 +0200 Subject: [PATCH 022/108] Changed: Use user requested resolution for both char select and ingame --HG-- branch : experimental-ui-scaling --- code/ryzom/client/src/connection.cpp | 36 ++++------------------------ 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/code/ryzom/client/src/connection.cpp b/code/ryzom/client/src/connection.cpp index 20c424688..92fce64f8 100644 --- a/code/ryzom/client/src/connection.cpp +++ b/code/ryzom/client/src/connection.cpp @@ -256,41 +256,13 @@ REGISTER_ACTION_HANDLER (CAHOnReloadTestPage, "on_reload_test_page"); // ------------------------------------------------------------------------------------------------ void setOutGameFullScreen() { - // Setup full screen (special 1024x768 for outgame) if we have to. - // NB: don't setup fullscreen if player wants to play in window if (!ClientCfg.Local && ClientCfg.SelectCharacter == -1) { if (StereoDisplayAttached) StereoDisplay->detachFromDisplay(); StereoDisplayAttached = false; - UDriver::CMode currMode; - Driver->getCurrentScreenMode(currMode); - UDriver::CMode wantedMode; - wantedMode.Windowed = true; - wantedMode.Width = 1024; - wantedMode.Height = 768; - wantedMode.Depth = uint8(ClientCfg.Depth); - wantedMode.Frequency = ClientCfg.Frequency; - - // change mode only if necessary - if ((wantedMode.Windowed != currMode.Windowed) || - (wantedMode.Width != currMode.Width) || - (wantedMode.Height != currMode.Height)) - { - setVideoMode(wantedMode); - } - InitMouseWithCursor(ClientCfg.HardwareCursor && !StereoDisplayAttached); - /* - InitMouseWithCursor (true); - Driver->showCursor(false); - Driver->showCursor(true); - Driver->clearBuffers(CRGBA::Black); - Driver->swapBuffers(); - Driver->showCursor(false); - Driver->showCursor(true); - */ } // Enable auto scaling in login window @@ -312,8 +284,8 @@ bool connection (const string &cookie, const string &fsaddr) game_exit = false; - // Setup full screen (special 1024x768 for outgame) if we have to. - setOutGameFullScreen(); + // set resolution from cfg after login + connectionRestoreVideoMode (); // Preload continents { @@ -347,13 +319,14 @@ bool connection (const string &cookie, const string &fsaddr) // init the string manager cache. STRING_MANAGER::CStringManagerClient::instance()->initCache("", ClientCfg.LanguageCode); // VOIR BORIS #endif - connectionRestoreVideoMode (); return true; } ProgressBar.setFontFactor(1.0f); // Init out game + setOutGameFullScreen(); + ucstring nmsg("Initializing outgame..."); ProgressBar.newMessage (ClientCfg.buildLoadingString(nmsg) ); pIM->initOutGame(); @@ -492,7 +465,6 @@ bool reconnection() game_exit = false; - // Setup full screen (special 1024x768 for outgame) if we have to. setOutGameFullScreen(); // Preload continents From 1c142ef968800e27d1577f730f681c0f1bbe42ad Mon Sep 17 00:00:00 2001 From: Nimetu Date: Fri, 22 Jun 2018 19:44:12 +0300 Subject: [PATCH 023/108] Fixed: Font size calculations for scaled text. --HG-- branch : experimental-ui-scaling --- code/nel/include/nel/gui/view_text.h | 12 +- code/nel/src/gui/group_editbox.cpp | 12 +- code/nel/src/gui/view_text.cpp | 265 ++++++++++-------- .../src/interface_v3/action_handler_edit.cpp | 18 +- 4 files changed, 166 insertions(+), 141 deletions(-) diff --git a/code/nel/include/nel/gui/view_text.h b/code/nel/include/nel/gui/view_text.h index e5c5d7e10..ad5a30f2a 100644 --- a/code/nel/include/nel/gui/view_text.h +++ b/code/nel/include/nel/gui/view_text.h @@ -129,6 +129,8 @@ namespace NLGUI uint getFontHeight() const; // get current font leg height, in pixels uint getFontLegHeight() const; + // get current line height, in pixels + float getLineHeight() const; // Set the display mode (supported with multiline only for now) void setTextMode(TTextMode mode); TTextMode getTextMode() const { return _TextMode; } @@ -149,11 +151,11 @@ namespace NLGUI * When looking at standard edit box, we see that if a line is split accross to line with no * This also returns the height of the line */ - void getCharacterPositionFromIndex(sint index, bool lineEnd, sint &x, sint &y, sint &height) const; + void getCharacterPositionFromIndex(sint index, bool lineEnd, float &x, float &y, float &height) const; /** From a coordinate relative to the BR BR corner of the text, return the index of a character. * If no character is found at the given position, the closest character is returned (first or last character, for the line or the whole text) */ - void getCharacterIndexFromPosition(sint x, sint y, uint &index, bool &lineEnd) const; + void getCharacterIndexFromPosition(float x, float y, uint &index, bool &lineEnd) const; /** From a character index, get the index of the line it belongs to, or -1 if the index is invalid * \param cursorDisplayedAtEndOfPreviousLine true if the cursor is displayed at the end of the previous line that match its index */ @@ -330,7 +332,7 @@ namespace NLGUI CFormatInfo Format; public: // build from a string, using the current text context - void build(const ucstring &text, NL3D::UTextContext &textContext, float scale, uint numSpaces= 0); + void build(const ucstring &text, NL3D::UTextContext &textContext, uint numSpaces= 0); }; typedef std::vector TWordVect; @@ -343,7 +345,7 @@ namespace NLGUI // Clear the line & remove text contexts void clear(NL3D::UTextContext &textContext); // Add a new word (and its context) in the line + a number of spaces to append at the end of the line - void addWord(const ucstring &word, uint numSpaces, const CFormatInfo &wordFormat, float fontWidth, NL3D::UTextContext &textContext, float scale); + void addWord(const ucstring &word, uint numSpaces, const CFormatInfo &wordFormat, float fontWidth, NL3D::UTextContext &textContext); void addWord(const CWord &word, float fontWidth); uint getNumWords() const { return (uint)_Words.size(); } CWord &getWord(uint index) { return _Words[index]; } @@ -404,7 +406,7 @@ namespace NLGUI uint _TextSelectionEnd; // First line X coordinate - sint _FirstLineX; + float _FirstLineX; /// Dynamic tooltips std::vector _Tooltips; diff --git a/code/nel/src/gui/group_editbox.cpp b/code/nel/src/gui/group_editbox.cpp index 13979f4aa..1a800b23a 100644 --- a/code/nel/src/gui/group_editbox.cpp +++ b/code/nel/src/gui/group_editbox.cpp @@ -702,9 +702,9 @@ namespace NLGUI sint32 maxPos= max(_CursorPos, _SelectCursorPos) + (sint32)_Prompt.length(); // get its position on screen - sint cxMinPos, cyMinPos; - sint cxMaxPos, cyMaxPos; - sint height; + float cxMinPos, cyMinPos; + float cxMaxPos, cyMaxPos; + float height; _ViewText->getCharacterPositionFromIndex(minPos, false, cxMinPos, cyMinPos, height); _ViewText->getCharacterPositionFromIndex(maxPos, false, cxMaxPos, cyMaxPos, height); @@ -755,8 +755,8 @@ namespace NLGUI if (_BlinkState) // is the cursor shown ? { // get its position on screen - sint cx, cy; - sint height; + float cx, cy; + float height; _ViewText->getCharacterPositionFromIndex(_CursorPos + (sint)_Prompt.length(), _CursorAtPreviousLineEnd, cx, cy, height); // display the cursor // get the texture for the cursor @@ -1482,7 +1482,7 @@ namespace NLGUI if (_ViewText->getWReal() > _WReal) { // Check if cursor visible - sint xCursVT, xCurs, yTmp, hTmp; + float xCursVT, xCurs, yTmp, hTmp; // Get the cursor pos from the BL of the viewtext _ViewText->getCharacterPositionFromIndex(_CursorPos+(sint)_Prompt.size(), false, xCursVT, yTmp, hTmp); // Get the cursor pos from the BL of the edit box diff --git a/code/nel/src/gui/view_text.cpp b/code/nel/src/gui/view_text.cpp index 881b4fa5d..f9403014e 100644 --- a/code/nel/src/gui/view_text.cpp +++ b/code/nel/src/gui/view_text.cpp @@ -957,8 +957,31 @@ namespace NLGUI CViewRenderer &rVR = *CViewRenderer::getInstance(); +#if 0 //rVR.drawRotFlipBitmap (_RenderLayer, _XReal, _YReal, _WReal, _HReal, 0, false, rVR.getBlankTextureId(), CRGBA(64,64,64,255)); + // debug text with mouse hover + if(CWidgetManager::getInstance()->getPointer()) + { + // but must check first if mouse is over + sint32 x = CWidgetManager::getInstance()->getPointer()->getX(); + sint32 y = CWidgetManager::getInstance()->getPointer()->getY(); + bool mouseIn; + // use parent clip or self clip? + if(_OverExtendViewTextUseParentRect) + mouseIn= _Parent && _Parent->isIn(x,y); + else + mouseIn= isIn(x,y); + // if the mouse cursor is in the clip area + if(mouseIn) { + rVR.drawRotFlipBitmap (_RenderLayer, _XReal, _YReal, _WReal, 1, 0, false, rVR.getBlankTextureId(), CRGBA(200,200,200,255)); + rVR.drawRotFlipBitmap (_RenderLayer, _XReal, _YReal+_HReal, _WReal, 1, 0, false, rVR.getBlankTextureId(), CRGBA(200,200,200,255)); + rVR.drawRotFlipBitmap (_RenderLayer, _XReal, _YReal, 1, _HReal, 0, false, rVR.getBlankTextureId(), CRGBA(200,200,200,255)); + rVR.drawRotFlipBitmap (_RenderLayer, _XReal+_WReal, _YReal, 1, _HReal, 0, false, rVR.getBlankTextureId(), CRGBA(200,200,200,255)); + } + } +#endif + // *** Out Of Clip? sint32 ClipX, ClipY, ClipW, ClipH; rVR.getClipWindow (ClipX, ClipY, ClipW, ClipH); @@ -1003,19 +1026,12 @@ namespace NLGUI TextContext->setEmbolden (_Embolden); TextContext->setOblique (_Oblique); - float y = _YReal; - //y += _LinesInfos[_LinesInfos.size()-1].StringLine / h; - // Y is the base line of the string, so it must be grown up. - y += _FontLegHeight; - - sint y_line = _YReal+_FontLegHeight-2.0f*_Scale; + float y = _YReal * _Scale + _FontLegHeight; if (_MultiMinLine > _Lines.size()) { - uint dy = getMultiMinOffsetY(); - y += dy; - y_line += dy; + y += getMultiMinOffsetY() * _Scale; } // special selection code @@ -1056,7 +1072,7 @@ namespace NLGUI { CLine &currLine = *_Lines[i]; // current x position - float px = (float) (_XReal + ((i==0) ? (sint)_FirstLineX : 0)); + float px = (float) (_XReal * _Scale + ((i==0) ? (sint)_FirstLineX : 0)); // draw each words of the line for(uint k = 0; k < currLine.getNumWords(); ++k) { @@ -1083,24 +1099,25 @@ namespace NLGUI px += firstSpace; // skip tabulation before current word if(currWord.Format.TabX) - px= max(px, (float)(_XReal + currWord.Format.TabX*_FontWidth)); + px= max(px, (float)(_XReal * _Scale + currWord.Format.TabX*_FontWidth)); // draw. We take floorf px to avoid filtering of letters that are not located on a pixel boundary - rVR.drawText (_RenderLayer, px, y, currWord.Index, ClipX, ClipY, ClipX+ClipW, ClipY+ClipH, *TextContext); + float fx = px / _Scale; + float fy = y / _Scale; + rVR.drawText (_RenderLayer, fx, fy, currWord.Index, ClipX, ClipY, ClipX+ClipW, ClipY+ClipH, *TextContext); // Draw a line if (_Underlined) - rVR.drawRotFlipBitmap (_RenderLayer, (sint)floorf(px), y_line, line_width, 1.0f / _Scale, 0, false, rVR.getBlankTextureId(), col); + rVR.drawRotFlipBitmap (_RenderLayer, fx, fy - _FontLegHeight*0.3f / _Scale, line_width / _Scale, 1.0f / _Scale, 0, false, rVR.getBlankTextureId(), col); if (_StrikeThrough) - rVR.drawRotFlipBitmap (_RenderLayer, (sint)floorf(px), y_line + (_FontHeight / 2), line_width, 1.0f / _Scale, 0, false, rVR.getBlankTextureId(), col); + rVR.drawRotFlipBitmap (_RenderLayer, fx, fy + _FontHeight*0.2f / _Scale, line_width / _Scale, 1.0f / _Scale, 0, false, rVR.getBlankTextureId(), col); // skip word px += currWord.Info.StringWidth; } // go one line up - y += (_FontHeight + _MultiLineSpace); - y_line += _FontHeight+_MultiLineSpace; + y += (_FontHeight + _MultiLineSpace * _Scale); } // reset selection @@ -1134,10 +1151,8 @@ namespace NLGUI TextContext->setLetterColors(_LetterColors, _Index); } - float y = _YReal; - // Y is the base line of the string, so it must be grown up. - y += _FontLegHeight; + float y = _YReal * _Scale + _FontLegHeight; // special selection code if(_TextSelection) @@ -1148,14 +1163,14 @@ namespace NLGUI TextContext->setStringColor(_Index, col); // draw - rVR.drawText (_RenderLayer, _XReal, y, _Index, ClipX, ClipY, ClipX+ClipW, ClipY+ClipH, *TextContext); + rVR.drawText (_RenderLayer, _XReal, y / _Scale, _Index, ClipX, ClipY, ClipX+ClipW, ClipY+ClipH, *TextContext); // Draw a line if (_Underlined) - rVR.drawRotFlipBitmap (_RenderLayer, _XReal, _YReal+_FontLegHeight-2.0f*_Scale, _WReal, 1, 0, false, rVR.getBlankTextureId(), col); + rVR.drawRotFlipBitmap (_RenderLayer, _XReal, _YReal - _FontLegHeight*0.3f/_Scale, _WReal, 1, 0, false, rVR.getBlankTextureId(), col); if (_StrikeThrough) - rVR.drawRotFlipBitmap (_RenderLayer, _XReal, _YReal+(_FontLegHeight/2), _WReal, 1, 0, false, rVR.getBlankTextureId(), col); + rVR.drawRotFlipBitmap (_RenderLayer, _XReal, _YReal + _FontHeight*0.2f / _Scale, _WReal, 1, 0, false, rVR.getBlankTextureId(), col); // reset selection if(_TextSelection) @@ -1204,7 +1219,6 @@ namespace NLGUI } } } - } // *************************************************************************** @@ -1344,6 +1358,7 @@ namespace NLGUI // *************************************************************************** void CViewText::setLineMaxW (sint nMaxW, bool invalidate) { + nMaxW *= _Scale; if(_LineMaxW!=nMaxW) { _LineMaxW = nMaxW; @@ -1395,19 +1410,25 @@ namespace NLGUI // *************************************************************************** uint CViewText::getFontWidth() const { - return _FontWidth; + return _FontWidth / _Scale; } // *************************************************************************** uint CViewText::getFontHeight() const { - return _FontHeight; + return _FontHeight / _Scale; } // *************************************************************************** uint CViewText::getFontLegHeight() const { - return _FontLegHeight; + return _FontLegHeight / _Scale; + } + + // *************************************************************************** + float CViewText::getLineHeight() const + { + return _FontHeight / _Scale + _MultiLineSpace; } // *************************************************************************** @@ -1418,7 +1439,7 @@ namespace NLGUI { // first line is always present even if _Lines is empty uint nbLines = _MultiMinLine - std::max((sint)1, (sint)_Lines.size()); - dy = nbLines * _FontHeight + (nbLines - 1) * _MultiLineSpace; + dy = (nbLines * _FontHeight + (nbLines - 1) * _MultiLineSpace * _Scale) / _Scale; } return dy; } @@ -1434,7 +1455,7 @@ namespace NLGUI linePushed= true; } // Append to the last line - _Lines.back()->addWord(ucCurrentWord, 0, wordFormat, _FontWidth, *TextContext, _Scale); + _Lines.back()->addWord(ucCurrentWord, 0, wordFormat, _FontWidth, *TextContext); // reset the word ucCurrentWord.clear(); } @@ -1450,11 +1471,12 @@ namespace NLGUI ucstring ucCurrentWord; CFormatInfo wordFormat; // line state - float rWidthCurrentLine = 0, rWidthLetter; + float rWidthCurrentLine = 0; bool linePushed= false; // for all the text uint textSize= (uint)_Text.size(); uint formatTagIndex= 0; + nMaxWidth *= _Scale; for (i = 0; i < textSize; ++i) { if(isFormatTagChange(i, formatTagIndex)) @@ -1487,21 +1509,20 @@ namespace NLGUI ucstring ucStrLetter; ucStrLetter= ucLetter; si = TextContext->getStringInfo (ucStrLetter); - rWidthLetter = (si.StringWidth / _Scale); - if ((rWidthCurrentLine + rWidthLetter) > nMaxWidth) + if ((rWidthCurrentLine + si.StringWidth) > nMaxWidth) { flushWordInLine(ucCurrentWord, linePushed, wordFormat); // reset line state, and begin with the cut letter linePushed= false; - rWidthCurrentLine = rWidthLetter; + rWidthCurrentLine = si.StringWidth; ucCurrentWord = ucLetter; } else { // Grow the current word ucCurrentWord += ucLetter; - rWidthCurrentLine += rWidthLetter; + rWidthCurrentLine += si.StringWidth; } } } @@ -1531,7 +1552,7 @@ namespace NLGUI if(currLine[i].Format!=lineWordFormat) { // add the current lineWord to the line. - _Lines.back()->addWord(lineWord, 0, lineWordFormat, _FontWidth, *TextContext, _Scale); + _Lines.back()->addWord(lineWord, 0, lineWordFormat, _FontWidth, *TextContext); // get new lineWordFormat lineWordFormat= currLine[i].Format; // and clear @@ -1546,7 +1567,7 @@ namespace NLGUI } if(!lineWord.empty()) - _Lines.back()->addWord(lineWord, 0, lineWordFormat, _FontWidth, *TextContext, _Scale); + _Lines.back()->addWord(lineWord, 0, lineWordFormat, _FontWidth, *TextContext); // clear currLine.clear(); @@ -1564,7 +1585,7 @@ namespace NLGUI static const ucstring spaceStr(" "); // precLineWidth valid only id precedent line is part of same paragraph. float precLineWidth= 0; - float lineWidth = (float)_FirstLineX * _Scale; // width of the current line + float lineWidth = (float)_FirstLineX; // width of the current line uint numWordsInLine = 0; // number of words in the current line bool isParagraphStart = true; // A paragraph is a group of characters between 2 \n bool lineFeed; @@ -1575,6 +1596,7 @@ namespace NLGUI CFormatInfo wordFormat; uint formatTagIndex= 0; // + nMaxWidth *= _Scale; while (currPos != _Text.length()) { TCharPos spaceEnd; @@ -1655,7 +1677,7 @@ namespace NLGUI // compute size of spaces/Tab + word newLineWidth = lineWidth + numSpaces * _SpaceWidth; newLineWidth = max(newLineWidth, (float)wordFormat.TabX*_FontWidth); - newLineWidth+= si.StringWidth / _Scale; + newLineWidth+= si.StringWidth; } // // Does the word go beyond the end of line ? @@ -1707,7 +1729,7 @@ namespace NLGUI { uint maxNumSpaces = std::max(1U, (uint) (nMaxWidth / _SpaceWidth)); CWord spaceWord; // a word with only spaces in it - spaceWord.build (ucstring (""), *TextContext, _Scale, maxNumSpaces); + spaceWord.build (ucstring (""), *TextContext, maxNumSpaces); spaceWord.Format= wordFormat; _Lines.push_back(TLineSPtr(new CLine)); _Lines.back()->addWord(spaceWord, _FontWidth); @@ -1730,13 +1752,13 @@ namespace NLGUI { oneChar = wordValue[currChar]; si = TextContext->getStringInfo(oneChar); - if ((uint) (px + si.StringWidth / _Scale) > nMaxWidth) break; - px += si.StringWidth / _Scale; + if ((uint) (px + si.StringWidth) > nMaxWidth) break; + px += si.StringWidth; } currChar = std::max((uint) 1, currChar); // must fit at least one character otherwise there's an infinite loop wordValue = _Text.substr(spaceEnd, currChar); CWord word; - word.build(wordValue, *TextContext, _Scale, numSpaces); + word.build(wordValue, *TextContext, numSpaces); word.Format= wordFormat; _Lines.push_back(TLineSPtr(new CLine)); float roomForSpaces = (float) nMaxWidth - word.Info.StringWidth; @@ -1771,7 +1793,7 @@ namespace NLGUI if (!wordValue.empty() || numSpaces != 0) { CWord word; - word.build(wordValue, *TextContext, _Scale, numSpaces); + word.build(wordValue, *TextContext, numSpaces); word.Format= wordFormat; // update line width _Lines.back()->addWord(word, _FontWidth); @@ -1890,24 +1912,25 @@ namespace NLGUI _Lines.pop_back(); CViewText::CLine *endLine = new CViewText::CLine; CViewText::CWord w; - w.build(_OverflowText, *TextContext, _Scale); + w.build(_OverflowText, *TextContext); endLine->addWord(w, _FontWidth); _Lines.push_back(TLineSPtr(endLine)); } } // Calculate size + float rMultiLineSpace = _MultiLineSpace * _Scale; float rTotalW = 0; for (uint i = 0; i < _Lines.size(); ++i) { rTotalW = std::max(_Lines[i]->getWidth() + ((i==0)?_FirstLineX:0), rTotalW); } - _W = (sint)ceilf(rTotalW); - _H = std::max(_FontHeight, ceilf(_FontHeight * _Lines.size() + std::max(0, sint(_Lines.size()) - 1) * _MultiLineSpace)); + _W = (sint)ceilf(rTotalW / _Scale); + _H = std::max(_FontHeight / _Scale, ceilf((_FontHeight * _Lines.size() + std::max(0, sint(_Lines.size()) - 1) * rMultiLineSpace) / _Scale)); // See if we should pretend to have at least X lines if (_MultiMinLine > 1) - _H = std::max(_H, sint(_FontHeight * _MultiMinLine + (_MultiMinLine - 1) * _MultiLineSpace)); + _H = std::max(_H, sint((_FontHeight * _MultiMinLine + (_MultiMinLine - 1) * rMultiLineSpace)/_Scale)); // Compute tooltips size if (!_Tooltips.empty()) @@ -1916,7 +1939,6 @@ namespace NLGUI for (uint j=0 ; j<_Lines[i]->getNumWords() ; ++j) { CWord word = _Lines[i]->getWord(j); - // float w = _Lines[i]->getWidth(); if (word.Format.IndexTt != -1) { @@ -1924,7 +1946,7 @@ namespace NLGUI { CCtrlToolTip *pTooltip = _Tooltips[word.Format.IndexTt]; - sint y = (sint) ceilf((_FontHeight + _MultiLineSpace) * (_Lines.size() - i - 1)); + sint y = (sint) ceilf(((_FontHeight + rMultiLineSpace) * (_Lines.size() - i - 1))/_Scale); pTooltip->setX(0); pTooltip->setY(y); @@ -1943,11 +1965,10 @@ namespace NLGUI // Common case: no W clamp _Index = TextContext->textPush (_Text); _Info = TextContext->getStringInfo (_Index); - _Info.StringWidth /= _Scale; - _W = (sint)ceilf(_Info.StringWidth); + _W = (sint)ceilf(_Info.StringWidth / _Scale); // Rare case: clamp W => recompute slowly, cut letters - if(_W>_LineMaxW) + if(_Info.StringWidth > _LineMaxW) { TextContext->erase (_Index); @@ -1957,18 +1978,14 @@ namespace NLGUI ucCurrentLine.reserve(_Text.size()); // Append ... to the end of line - float dotWidth; + float dotWidth = 0.f; if (_OverflowText.size() > 0) { si = TextContext->getStringInfo (ucstring(_OverflowText)); - dotWidth = si.StringWidth / _Scale; - } - else - { - dotWidth = 0.0f; + dotWidth = si.StringWidth; } - float rWidthCurrentLine = 0, rWidthLetter; + float rWidthCurrentLine = 0; // for all the text if (_ClampRight) { @@ -1978,8 +1995,7 @@ namespace NLGUI ucstring ucStrLetter; ucStrLetter= ucLetter; si = TextContext->getStringInfo (ucStrLetter); - rWidthLetter = (si.StringWidth / _Scale); - if ((rWidthCurrentLine + rWidthLetter + dotWidth) > _LineMaxW) + if ((rWidthCurrentLine + si.StringWidth + dotWidth) > _LineMaxW) { break; } @@ -1987,7 +2003,7 @@ namespace NLGUI { // Grow the current line ucCurrentLine += ucLetter; - rWidthCurrentLine += rWidthLetter; + rWidthCurrentLine += si.StringWidth; } } @@ -2005,8 +2021,7 @@ namespace NLGUI ucstring ucStrLetter; ucStrLetter= ucLetter; si = TextContext->getStringInfo (ucStrLetter); - rWidthLetter = (si.StringWidth / _Scale); - if ((rWidthCurrentLine + rWidthLetter + dotWidth) > _LineMaxW) + if ((rWidthCurrentLine + si.StringWidth + dotWidth) > _LineMaxW) { break; } @@ -2014,7 +2029,7 @@ namespace NLGUI { // Grow the current line ucCurrentLine = ucLetter + ucCurrentLine; - rWidthCurrentLine += rWidthLetter; + rWidthCurrentLine += si.StringWidth; } } @@ -2028,14 +2043,13 @@ namespace NLGUI // And so setup this trunc text _Index = TextContext->textPush (ucCurrentLine); _Info = TextContext->getStringInfo (_Index); - _Info.StringWidth /= _Scale; - _W = (sint)ceilf(_Info.StringWidth); + _W = (sint)ceilf(_Info.StringWidth / _Scale); _SingleLineTextClamped= true; } // same height always - _H = (sint)ceilf(_FontHeight); + _H = (sint)ceilf(_FontHeight / _Scale); } _InvalidTextContext= false; @@ -2072,12 +2086,12 @@ namespace NLGUI if (_ClampRight) { sint32 parentRight = parent->getXReal() + parent->getWReal() - (sint32) _AutoClampOffset; - setLineMaxW(ceilf(std::max((sint32) 0, parentRight - _XReal) / _Scale)); + setLineMaxW(std::max((sint32) 0, parentRight - _XReal)); } else { sint32 parentLeft = parent->getXReal() + (sint32) _AutoClampOffset; - setLineMaxW(ceilf(std::max((sint32) 0, _XReal + _WReal - parentLeft) / _Scale)); + setLineMaxW(std::max((sint32) 0, _XReal + _WReal - parentLeft)); } } } @@ -2194,7 +2208,7 @@ namespace NLGUI } // *************************************************************************** - void CViewText::getCharacterPositionFromIndex(sint index, bool cursorAtPreviousLineEnd, sint &x, sint &y, sint &height) const + void CViewText::getCharacterPositionFromIndex(sint index, bool cursorAtPreviousLineEnd, float &x, float &y, float &height) const { NLMISC::clamp(index, 0, (sint) _Text.length()); NL3D::UTextContext *TextContext = CViewRenderer::getTextContext(_FontName); @@ -2204,18 +2218,19 @@ namespace NLGUI TextContext->setFontSize (_FontSize*_Scale); TextContext->setEmbolden (_Embolden); TextContext->setOblique (_Oblique); - // CViewRenderer &rVR = *CViewRenderer::getInstance(); - height = getFontHeight(); + height = getLineHeight(); // if (_MultiLine) { - uint dy = getMultiMinOffsetY(); + float fx, fy; + float dy = getMultiMinOffsetY() * _Scale; + float nMaxWidth = getCurrentMultiLineMaxW() * _Scale; uint charIndex = 0; // special case for end of text if (index == (sint) _Text.length()) { - y = dy; + fy = dy; if (_Lines.empty()) { x = 0; @@ -2223,10 +2238,12 @@ namespace NLGUI else { CLine &lastLine = *_Lines.back(); - x = (sint) (lastLine.getWidth() + lastLine.getEndSpaces() * lastLine.getSpaceWidth()); - sint nMaxWidth = getCurrentMultiLineMaxW(); - x = std::min(x, nMaxWidth); + fx = lastLine.getWidth() + lastLine.getEndSpaces() * lastLine.getSpaceWidth(); + fx = std::min(fx, nMaxWidth); } + + x = fx / _Scale; + y = fy / _Scale; return; } for(sint i = 0; i < (sint) _Lines.size(); ++i) @@ -2235,10 +2252,12 @@ namespace NLGUI { // should display the character at the end of previous line CLine &currLine = *_Lines[i - 1]; - y = (sint) ((_FontHeight + _MultiLineSpace) * (_Lines.size() - i) + dy); - x = (sint) (currLine.getWidth() + currLine.getEndSpaces() * currLine.getSpaceWidth()); - sint nMaxWidth = getCurrentMultiLineMaxW(); - x = std::min(x, nMaxWidth); + fy = (_FontHeight + _MultiLineSpace * _Scale) * (_Lines.size() - i) + dy; + fx = currLine.getWidth() + currLine.getEndSpaces() * currLine.getSpaceWidth(); + fx = std::min(fx, nMaxWidth); + + x = fx / _Scale; + y = fy / _Scale; return; } CLine &currLine = *_Lines[i]; @@ -2246,14 +2265,16 @@ namespace NLGUI if ((sint) newCharIndex > index) { // ok, this line contains the character, now, see which word contains it. - y = (sint) ((_FontHeight + _MultiLineSpace) * (_Lines.size() - 1 - i) + dy); + fy = (_FontHeight + _MultiLineSpace * _Scale) * (_Lines.size() - 1 - i) + dy; // see if the index is in the spaces at the end of line if (index - charIndex >= currLine.getNumChars()) { uint numSpaces = index - charIndex - currLine.getNumChars(); - x = (sint) (currLine.getWidth() + numSpaces * _SpaceWidth); - sint nMaxWidth = getCurrentMultiLineMaxW(); - x = std::min(x, nMaxWidth); + fx = currLine.getWidth() + numSpaces * _SpaceWidth; + fx = std::min(fx, nMaxWidth); + + x = fx / _Scale; + y = fy / _Scale; return; } // now, search containing word in current line @@ -2271,15 +2292,19 @@ namespace NLGUI ucstring subStr = currWord.Text.substr(0, index - charIndex - currWord.NumSpaces); // compute the size UTextContext::CStringInfo si = TextContext->getStringInfo(subStr); - x = (sint) ceilf(px + si.StringWidth / _Scale + currWord.NumSpaces * currLine.getSpaceWidth()); - height = getFontHeight(); + fx = px + si.StringWidth + currWord.NumSpaces * currLine.getSpaceWidth(); + + x = fx / _Scale; + y = fy / _Scale; return; } else { // character is in the spaces preceding the word - x = (sint) ceilf(px + currLine.getSpaceWidth() * (index - charIndex)); - height = getFontHeight(); + fx = px + currLine.getSpaceWidth() * (index - charIndex); + + x = fx / _Scale; + y = fy / _Scale; return; } } @@ -2303,11 +2328,11 @@ namespace NLGUI } // *************************************************************************** - // Tool fct : From a word and a x coordinate, give the matching character index - static uint getCharacterIndex(const ucstring &textValue, float x, NL3D::UTextContext &textContext, float scale) + // Tool fct : From a word and a x coordinate (font scale), give the matching character index + static uint getCharacterIndex(const ucstring &textValue, float x, NL3D::UTextContext &textContext) { float px = 0.f; - float sw; + UTextContext::CStringInfo si; ucstring singleChar(" "); uint i; @@ -2316,13 +2341,12 @@ namespace NLGUI // get character width singleChar[0] = textValue[i]; si = textContext.getStringInfo(singleChar); - sw = si.StringWidth / scale; - px += sw; + px += si.StringWidth; // the character is at the i - 1 position if (px > x) { // if the half of the character is after the cursor, then prefer select the next one (like in Word) - if(px-sw/2 < x) + if(px-si.StringWidth/2.f < x) i++; break; } @@ -2331,10 +2355,13 @@ namespace NLGUI } // *************************************************************************** - void CViewText::getCharacterIndexFromPosition(sint x, sint y, uint &index, bool &cursorAtPreviousLineEnd) const + void CViewText::getCharacterIndexFromPosition(float x, float y, uint &index, bool &cursorAtPreviousLineEnd) const { NL3D::UTextContext *TextContext = CViewRenderer::getTextContext(_FontName); + x *= _Scale; + y = roundf(y * _Scale); + // setup the text context TextContext->setHotSpot (UTextContext::BottomLeft); TextContext->setShaded (_Shadow); @@ -2347,7 +2374,7 @@ namespace NLGUI uint charPos = 0; if (_MultiLine) { - y -= getMultiMinOffsetY(); + y -= getMultiMinOffsetY() * _Scale; // seek the line float py = 0.f; if (py > y) @@ -2359,7 +2386,7 @@ namespace NLGUI sint line; for (line = (uint)_Lines.size() - 1; line >= 0; --line) { - float newPy = py + _FontHeight + _MultiLineSpace; + float newPy = py + _FontHeight + _MultiLineSpace * _Scale; if (newPy > y) { break; @@ -2392,6 +2419,7 @@ namespace NLGUI return; } + cursorAtPreviousLineEnd = false; float px = (float)_FirstLineX; for(uint k = 0; k < currLine.getNumWords(); ++k) { @@ -2408,14 +2436,12 @@ namespace NLGUI : 0; clamp(numSpaces, 0, (sint)currWord.NumSpaces); index = numSpaces + charPos; - cursorAtPreviousLineEnd = false; return; } else { // the coord is in the word itself - index = charPos + currWord.NumSpaces + getCharacterIndex(currWord.Text, (float) x - (px + spacesWidth), *TextContext, _Scale); - cursorAtPreviousLineEnd = false; + index = charPos + currWord.NumSpaces + getCharacterIndex(currWord.Text, x - (px + spacesWidth), *TextContext); return; } } @@ -2423,7 +2449,6 @@ namespace NLGUI charPos += (uint)currWord.Text.length() + currWord.NumSpaces; } index = charPos; - cursorAtPreviousLineEnd = false; return; } else @@ -2439,7 +2464,7 @@ namespace NLGUI index = 0; return; } - index = getCharacterIndex(_Text, (float) x, *TextContext, _Scale); + index = getCharacterIndex(_Text, x, *TextContext); return; } } @@ -2507,21 +2532,21 @@ namespace NLGUI // *************************************************************************** uint CViewText::getFirstLineX() const { - return _FirstLineX; + return _FirstLineX / _Scale; } // *************************************************************************** uint CViewText::getLastLineW () const { if (!_Lines.empty()) - return (uint)_Lines.back()->getWidth(); + return (uint)_Lines.back()->getWidth() / _Scale; return 0; } // *************************************************************************** void CViewText::setFirstLineX(sint firstLineX) { - _FirstLineX = firstLineX; + _FirstLineX = firstLineX * _Scale; } ///////////////////////////////////// @@ -2540,10 +2565,10 @@ namespace NLGUI } // *************************************************************************** - void CViewText::CLine::addWord(const ucstring &text, uint numSpaces, const CFormatInfo &wordFormat, float fontWidth, NL3D::UTextContext &textContext, float scale) + void CViewText::CLine::addWord(const ucstring &text, uint numSpaces, const CFormatInfo &wordFormat, float fontWidth, NL3D::UTextContext &textContext) { CWord word; - word.build(text, textContext, scale, numSpaces); + word.build(text, textContext, numSpaces); word.Format= wordFormat; addWord(word, fontWidth); } @@ -2587,13 +2612,12 @@ namespace NLGUI } // *************************************************************************** - void CViewText::CWord::build(const ucstring &text, NL3D::UTextContext &textContext, float scale, uint numSpaces) + void CViewText::CWord::build(const ucstring &text, NL3D::UTextContext &textContext, uint numSpaces) { Text = text; NumSpaces = numSpaces; Index = textContext.textPush(text); Info = textContext.getStringInfo(Index); - Info.StringWidth /= scale; } // *************************************************************************** @@ -2667,7 +2691,7 @@ namespace NLGUI si = TextContext->getStringInfo(wordValue); // compute size of spaces + word - lineWidth += numSpaces * _SpaceWidth + si.StringWidth / _Scale; + lineWidth += numSpaces * _SpaceWidth + si.StringWidth; currPos = wordEnd; } @@ -2679,7 +2703,7 @@ namespace NLGUI linePos = lineEnd+1; } - return (sint32)ceilf(maxWidth); + return (sint32)ceilf(maxWidth / _Scale); } // *************************************************************************** @@ -2696,7 +2720,7 @@ namespace NLGUI if (_TextMode == ClipWord) { // No largest font parameter, return the font height - return (sint32)ceilf(_FontHeight); + return (sint32)ceilf(_FontHeight / _Scale); } // If we can't clip the words, return the size of the largest word else if ((_TextMode == DontClipWord) || (_TextMode == Justified)) @@ -2733,16 +2757,15 @@ namespace NLGUI si = TextContext->getStringInfo(wordValue); // Larger ? - float stringWidth = (si.StringWidth / _Scale); - if (stringWidth>maxWidth) - maxWidth = stringWidth; + if (maxWidth < si.StringWidth) + maxWidth = si.StringWidth; // Next word currPos = wordEnd; } } - return ceilf(maxWidth); + return ceilf(maxWidth / _Scale); } // *************************************************************************** @@ -2796,16 +2819,16 @@ namespace NLGUI si = TextContext->getStringInfo(chars); } // add a padding of 1 pixel else the top will be truncated - _FontHeight = (si.StringHeight / _Scale) + 1; - _FontLegHeight = si.StringLine / _Scale; + _FontHeight = si.StringHeight + 1; + _FontLegHeight = si.StringLine; // Space width si = TextContext->getStringInfo(ucstring(" ")); - _SpaceWidth = si.StringWidth / _Scale; + _SpaceWidth = si.StringWidth; // Font Width si = TextContext->getStringInfo(ucstring("_")); - _FontWidth = si.StringWidth / _Scale; + _FontWidth = si.StringWidth; } diff --git a/code/ryzom/client/src/interface_v3/action_handler_edit.cpp b/code/ryzom/client/src/interface_v3/action_handler_edit.cpp index 2b3bb7c5e..9c44fa492 100644 --- a/code/ryzom/client/src/interface_v3/action_handler_edit.cpp +++ b/code/ryzom/client/src/interface_v3/action_handler_edit.cpp @@ -383,10 +383,10 @@ class CAHEditPreviousLine : public CAHEdit // .. so do nothing return; } - sint cx, cy; - sint height; + float cx, cy; + float height; _GroupEdit->getViewText()->getCharacterPositionFromIndex(cursorPosInText, _GroupEdit->isCursorAtPreviousLineEnd(), cx, cy, height); - cy += height + _GroupEdit->getViewText()->getMultiLineSpace(); + cy += _GroupEdit->getViewText()->getLineHeight(); uint newCharIndex; bool newLineEnd; _GroupEdit->getViewText()->getCharacterIndexFromPosition(cx, cy, newCharIndex, newLineEnd); @@ -401,8 +401,8 @@ class CAHEditPreviousLine : public CAHEdit } _GroupEdit->setCursorAtPreviousLineEnd(false); // takes character whose X is closer to the current X - sint cx0, cx1; - sint cy0, cy1; + float cx0, cx1; + float cy0, cy1; _GroupEdit->getViewText()->getCharacterPositionFromIndex(newCharIndex, _GroupEdit->isCursorAtPreviousLineEnd(), cx0, cy0, height); _GroupEdit->getViewText()->getCharacterPositionFromIndex(newCharIndex + 1, _GroupEdit->isCursorAtPreviousLineEnd(), cx1, cy1, height); if (abs(cx0 - cx) < abs(cx1 - cx) || cy0 != cy1) @@ -446,8 +446,8 @@ class CAHEditNextLine : public CAHEdit } else if (_GroupEdit->getViewText()->getMultiLine()) { - sint cx, cy; - sint height; + float cx, cy; + float height; _GroupEdit->getViewText()->getCharacterPositionFromIndex(_GroupEdit->getCursorPos() + (sint)_GroupEdit->getPrompt().length(), _GroupEdit->isCursorAtPreviousLineEnd(), cx, cy, height); if (cy != 0) { @@ -466,8 +466,8 @@ class CAHEditNextLine : public CAHEdit } _GroupEdit->setCursorAtPreviousLineEnd(false); // takes character whose X is closer to the current X - sint cx0, cx1; - sint cy0, cy1; + float cx0, cx1; + float cy0, cy1; _GroupEdit->getViewText()->getCharacterPositionFromIndex(newCharIndex, _GroupEdit->isCursorAtPreviousLineEnd(), cx0, cy0, height); _GroupEdit->getViewText()->getCharacterPositionFromIndex(newCharIndex + 1, _GroupEdit->isCursorAtPreviousLineEnd(), cx1, cy1, height); if (abs(cx0 - cx) < abs(cx1 - cx) || cy0 != cy1) From 7eb63bfb3f16771e5e37b2a992f7defbb087ae8a Mon Sep 17 00:00:00 2001 From: Nimetu Date: Mon, 25 Jun 2018 11:16:59 +0300 Subject: [PATCH 024/108] Fixed: Font underline position --HG-- branch : experimental-ui-scaling --- code/nel/src/gui/view_text.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/nel/src/gui/view_text.cpp b/code/nel/src/gui/view_text.cpp index f9403014e..1a175e224 100644 --- a/code/nel/src/gui/view_text.cpp +++ b/code/nel/src/gui/view_text.cpp @@ -1108,7 +1108,7 @@ namespace NLGUI // Draw a line if (_Underlined) - rVR.drawRotFlipBitmap (_RenderLayer, fx, fy - _FontLegHeight*0.3f / _Scale, line_width / _Scale, 1.0f / _Scale, 0, false, rVR.getBlankTextureId(), col); + rVR.drawRotFlipBitmap (_RenderLayer, fx, fy - _FontLegHeight*0.6f / _Scale, line_width / _Scale, 1.0f / _Scale, 0, false, rVR.getBlankTextureId(), col); if (_StrikeThrough) rVR.drawRotFlipBitmap (_RenderLayer, fx, fy + _FontHeight*0.2f / _Scale, line_width / _Scale, 1.0f / _Scale, 0, false, rVR.getBlankTextureId(), col); From 9aa52bc26753fbc073377c77c3b6f4bb512cfaaf Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sat, 20 Oct 2018 19:07:32 +0300 Subject: [PATCH 025/108] Fixed: Scale button height as needed based on text height --HG-- branch : develop --- code/nel/src/gui/ctrl_text_button.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/nel/src/gui/ctrl_text_button.cpp b/code/nel/src/gui/ctrl_text_button.cpp index 81dfcc953..6e1fab26e 100644 --- a/code/nel/src/gui/ctrl_text_button.cpp +++ b/code/nel/src/gui/ctrl_text_button.cpp @@ -899,7 +899,7 @@ namespace NLGUI } if (!(_SizeRef & 2)) { - _H= _BmpH; + _H= max(_BmpH, _ViewText->getH()); } CViewBase::updateCoords(); From 12ec528b5a92582efefbf30e8d8cff1d437261a9 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sat, 20 Oct 2018 19:07:32 +0300 Subject: [PATCH 026/108] Fixed: Mouse events on partially hidden elemnts --HG-- branch : develop --- code/nel/include/nel/gui/interface_group.h | 10 ++++++- code/nel/src/gui/interface_group.cpp | 32 ++++++++++++++++++---- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/code/nel/include/nel/gui/interface_group.h b/code/nel/include/nel/gui/interface_group.h index 01f2b9701..5f864e9e9 100644 --- a/code/nel/include/nel/gui/interface_group.h +++ b/code/nel/include/nel/gui/interface_group.h @@ -101,6 +101,9 @@ namespace NLGUI // test is a group is a direct child of this interface group bool isChildGroup(const CInterfaceGroup *group) const; + // test is x,y is inside last draw clip aread + bool isInViewport(sint32 x, sint32 y) const; + virtual bool isWindowUnder (sint32 x, sint32 y); // Virtual for menu that is not square CInterfaceGroup *getGroupUnder (sint32 x, sint32 y); virtual bool getViewsUnder (sint32 x, sint32 y, sint32 clipX, sint32 clipY, sint32 clipW, sint32 clipH, std::vector &vVB); // Return true if x,y under the group @@ -341,8 +344,13 @@ namespace NLGUI void alignElements(); protected: + /// Last clip area cached from draw call + sint32 _LastClipX; + sint32 _LastClipY; + sint32 _LastClipW; + sint32 _LastClipH; - void makeNewClip (sint32 &oldClipX, sint32 &oldClipY, sint32 &oldClipW, sint32 &oldClipH); + void makeNewClip (sint32 &oldClipX, sint32 &oldClipY, sint32 &oldClipW, sint32 &oldClipH, bool drawing = false); void restoreClip (sint32 oldSciX, sint32 oldSciY, sint32 oldSciW, sint32 oldSciH); // Compute clip contribution for current window, and a previous clipping rectangle. This doesn't change the clip window in the driver. diff --git a/code/nel/src/gui/interface_group.cpp b/code/nel/src/gui/interface_group.cpp index 789030a4a..3da60c9f2 100644 --- a/code/nel/src/gui/interface_group.cpp +++ b/code/nel/src/gui/interface_group.cpp @@ -77,6 +77,11 @@ namespace NLGUI _LUAEnvTableCreated= false; _DepthForZSort= 0.f; + _LastClipX = 0; + _LastClipY = 0; + _LastClipW = 0; + _LastClipH = 0; + #ifdef AJM_DEBUG_TRACK_INTERFACE_GROUPS CInterfaceManager::getInstance()->DebugTrackGroupsCreated( this ); #endif @@ -1254,7 +1259,8 @@ namespace NLGUI { const NLGUI::CEventDescriptorMouse &eventDesc = (const NLGUI::CEventDescriptorMouse &)event; - if (!isIn(eventDesc.getX(), eventDesc.getY())) + // group might be partially hidden (scolling) so test against last visible area + if (!isInViewport(eventDesc.getX(), eventDesc.getY())) return false; bool taken = false; @@ -1299,7 +1305,6 @@ namespace NLGUI } if (eventDesc.getEventTypeExtended() == NLGUI::CEventDescriptorMouse::mousewheel) { - // handle the Mouse Wheel only if interesting if (_H>_MaxH) { CInterfaceGroup *currParent = _Parent; @@ -1329,7 +1334,7 @@ namespace NLGUI void CInterfaceGroup::draw () { sint32 oldSciX, oldSciY, oldSciW, oldSciH; - makeNewClip (oldSciX, oldSciY, oldSciW, oldSciH); + makeNewClip (oldSciX, oldSciY, oldSciW, oldSciH, true); // Display sons only if not total clipped CViewRenderer &rVR = *CViewRenderer::getInstance(); @@ -1718,6 +1723,16 @@ namespace NLGUI (y <= (_YReal + _HReal))); } + // ------------------------------------------------------------------------------------------------ + bool CInterfaceGroup::isInViewport(sint32 x, sint32 y) const + { + return ( + (x > _LastClipX) && + (x < (_LastClipX + _LastClipW))&& + (y > _LastClipY) && + (y < (_LastClipY + _LastClipH))); + } + // ------------------------------------------------------------------------------------------------ CInterfaceGroup* CInterfaceGroup::getGroupUnder (sint32 x, sint32 y) { @@ -1976,11 +1991,10 @@ namespace NLGUI newSciYDest = newSciY; newSciWDest = newSciW/* - _MarginLeft*/; newSciHDest = newSciH; - } // ------------------------------------------------------------------------------------------------ - void CInterfaceGroup::makeNewClip (sint32 &oldSciX, sint32 &oldSciY, sint32 &oldSciW, sint32 &oldSciH) + void CInterfaceGroup::makeNewClip (sint32 &oldSciX, sint32 &oldSciY, sint32 &oldSciW, sint32 &oldSciH, bool drawing) { CViewRenderer &rVR = *CViewRenderer::getInstance(); rVR.getClipWindow (oldSciX, oldSciY, oldSciW, oldSciH); @@ -1988,6 +2002,14 @@ namespace NLGUI sint32 newSciX, newSciY, newSciW, newSciH; computeCurrentClipContribution(oldSciX, oldSciY, oldSciW, oldSciH, newSciX, newSciY, newSciW, newSciH); rVR.setClipWindow (newSciX, newSciY, newSciW, newSciH); + + if (drawing) + { + _LastClipX = newSciX; + _LastClipY = newSciY; + _LastClipW = newSciW; + _LastClipH = newSciH; + } } // ------------------------------------------------------------------------------------------------ From c74a56f3b961fe787c9a03d7bdb0821525319c33 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sat, 20 Oct 2018 19:07:32 +0300 Subject: [PATCH 027/108] Fixed: Streaming source did not report finished state correctly --HG-- branch : develop --- code/nel/include/nel/sound/stream_file_source.h | 3 ++- code/nel/src/sound/stream_file_source.cpp | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/code/nel/include/nel/sound/stream_file_source.h b/code/nel/include/nel/sound/stream_file_source.h index 79151f171..b7c48f96a 100644 --- a/code/nel/include/nel/sound/stream_file_source.h +++ b/code/nel/include/nel/sound/stream_file_source.h @@ -98,8 +98,9 @@ private: NLMISC::IThread *m_Thread; IAudioDecoder *m_AudioDecoder; - + bool m_Paused; + bool m_DecodingEnded; }; /* class CStreamFileSource */ diff --git a/code/nel/src/sound/stream_file_source.cpp b/code/nel/src/sound/stream_file_source.cpp index 185b7514f..9a03ba2d8 100644 --- a/code/nel/src/sound/stream_file_source.cpp +++ b/code/nel/src/sound/stream_file_source.cpp @@ -45,7 +45,7 @@ using namespace std; namespace NLSOUND { CStreamFileSource::CStreamFileSource(CStreamFileSound *streamFileSound, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster, CGroupController *groupController) -: CStreamSource(streamFileSound, spawn, cb, cbUserParam, cluster, groupController), m_AudioDecoder(NULL), m_Paused(false) +: CStreamSource(streamFileSound, spawn, cb, cbUserParam, cluster, groupController), m_AudioDecoder(NULL), m_Paused(false), m_DecodingEnded(false) { m_Thread = NLMISC::IThread::create(this); } @@ -244,7 +244,7 @@ void CStreamFileSource::resume() bool CStreamFileSource::isEnded() { - return (!m_Thread->isRunning() && !_Playing && !m_WaitingForPlay && !m_Paused); + return m_DecodingEnded || (!m_Thread->isRunning() && !_Playing && !m_WaitingForPlay && !m_Paused); } float CStreamFileSource::getLength() @@ -319,6 +319,7 @@ void CStreamFileSource::run() this->getRecommendedBufferSize(samples, bytes); uint32 recSleep = 40; uint32 doSleep = 10; + m_DecodingEnded = false; while (_Playing || m_WaitingForPlay) { if (!m_AudioDecoder->isMusicEnded()) @@ -369,6 +370,9 @@ void CStreamFileSource::run() { delete m_AudioDecoder; m_AudioDecoder = NULL; + // _Playing cannot be used to detect play state because its required in cleanup + // Using m_AudioDecoder in isEnded() may result race condition (decoder is only created after thread is started) + m_DecodingEnded = true; } // drop buffers m_FreeBuffers = 3; From c93d8ffd50b36c0d3f9d2ed15f76460ace9c53ce Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sat, 20 Oct 2018 19:07:47 +0300 Subject: [PATCH 028/108] Changed: Improve music player UI, add repeat, shuffle options --HG-- branch : develop --- .../client/src/interface_v3/music_player.cpp | 156 ++++++++++++++---- .../client/src/interface_v3/music_player.h | 16 +- 2 files changed, 141 insertions(+), 31 deletions(-) diff --git a/code/ryzom/client/src/interface_v3/music_player.cpp b/code/ryzom/client/src/interface_v3/music_player.cpp index b9cadad5c..358189d42 100644 --- a/code/ryzom/client/src/interface_v3/music_player.cpp +++ b/code/ryzom/client/src/interface_v3/music_player.cpp @@ -39,6 +39,9 @@ extern UDriver *Driver; #define TEMPLATE_PLAYLIST_SONG "playlist_song" #define TEMPLATE_PLAYLIST_SONG_TITLE "title" #define TEMPLATE_PLAYLIST_SONG_DURATION "duration" +// ui state +#define MP3_SAVE_SHUFFLE "UI:SAVE:MP3_SHUFFLE" +#define MP3_SAVE_REPEAT "UI:SAVE:MP3_REPEAT" static const std::string MediaPlayerDirectory("music/"); @@ -48,8 +51,20 @@ CMusicPlayer MusicPlayer; CMusicPlayer::CMusicPlayer () { - _CurrentSong = 0; + _CurrentSongIndex = 0; _State = Stopped; + _PlayStart = 0; + _PauseTime = 0; +} + +bool CMusicPlayer::isRepeatEnabled() const +{ + return (NLGUI::CDBManager::getInstance()->getDbProp(MP3_SAVE_REPEAT)->getValue32() == 1); +} + +bool CMusicPlayer::isShuffleEnabled() const +{ + return (NLGUI::CDBManager::getInstance()->getDbProp(MP3_SAVE_SHUFFLE)->getValue32() == 1); } @@ -59,16 +74,61 @@ void CMusicPlayer::playSongs (const std::vector &songs) _Songs = songs; // reset song index if out of bounds - if (_CurrentSong > _Songs.size()) - _CurrentSong = 0; + if (_CurrentSongIndex > _Songs.size()) + _CurrentSongIndex = 0; + if (isShuffleEnabled()) + shuffleAndRebuildPlaylist(); + else + rebuildPlaylist(); + + // If pause, stop, else play will resume + if (_State == Paused) + _State = Stopped; +} + +// *************************************************************************** +void CMusicPlayer::updatePlaylist(sint prevIndex) +{ + CInterfaceElement *pIE; + std::string rowId; + + if (prevIndex >= 0 && prevIndex < _Songs.size()) + { + rowId = toString("%s:s%d:bg", MP3_PLAYER_PLAYLIST_LIST, prevIndex); + pIE = dynamic_cast(CWidgetManager::getInstance()->getElementFromId(rowId)); + if (pIE) pIE->setActive(false); + } + + rowId = toString("%s:s%d:bg", MP3_PLAYER_PLAYLIST_LIST, _CurrentSongIndex); + pIE = dynamic_cast(CWidgetManager::getInstance()->getElementFromId(rowId)); + if (pIE) pIE->setActive(true); +} + +// *************************************************************************** +void CMusicPlayer::shuffleAndRebuildPlaylist() +{ + std::random_shuffle(_Songs.begin(), _Songs.end()); + rebuildPlaylist(); +} + +// *************************************************************************** +void CMusicPlayer::rebuildPlaylist() +{ CGroupList *pList = dynamic_cast(CWidgetManager::getInstance()->getElementFromId(MP3_PLAYER_PLAYLIST_LIST)); if (pList) { pList->clearGroups(); pList->setDynamicDisplaySize(true); + bool found = _CurrentSong.Filename.empty(); for (uint i=0; i < _Songs.size(); ++i) { + if (!found && _CurrentSong.Filename == _Songs[i].Filename) + { + found = true; + _CurrentSongIndex = i; + } + uint min = (sint32)(_Songs[i].Length / 60) % 60; uint sec = (sint32)(_Songs[i].Length) % 60; uint hour = _Songs[i].Length / 3600; @@ -103,9 +163,7 @@ void CMusicPlayer::playSongs (const std::vector &songs) pList->invalidateCoords(); } - // If pause, stop, else play will resume - if (_State == Paused) - _State = Stopped; + updatePlaylist(); } @@ -116,31 +174,40 @@ void CMusicPlayer::play (sint index) if(!SoundMngr) return; + sint prevSongIndex = _CurrentSongIndex; + if (index >= 0 && index < (sint)_Songs.size()) { if (_State == Paused) + { stop(); + } - _CurrentSong = index; + _CurrentSongIndex = index; + _PauseTime = 0; } if (!_Songs.empty()) { - nlassert (_CurrentSong<_Songs.size()); + nlassert (_CurrentSongIndex<_Songs.size()); /* If the player is paused, resume, else, play the current song */ if (_State == Paused) + { SoundMngr->resumeMusic(); + } else - SoundMngr->playMusic(_Songs[_CurrentSong].Filename, 0, true, false, false); + { + SoundMngr->playMusic(_Songs[_CurrentSongIndex].Filename, 0, true, false, false); + _PauseTime = 0; + } _State = Playing; + _PlayStart = CTime::getLocalTime() - _PauseTime; - /* Show the song title */ - CInterfaceManager *pIM = CInterfaceManager::getInstance(); - CViewText *pVT = dynamic_cast(CWidgetManager::getInstance()->getElementFromId("ui:interface:mp3_player:screen:text")); - if (pVT) - pVT->setText (ucstring::makeFromUtf8(_Songs[_CurrentSong].Title)); + _CurrentSong = _Songs[_CurrentSongIndex]; + + updatePlaylist(prevSongIndex); } } @@ -155,6 +222,9 @@ void CMusicPlayer::pause () { SoundMngr->pauseMusic(); _State = Paused; + + if (_PlayStart > 0) + _PauseTime = CTime::getLocalTime() - _PlayStart; } } @@ -167,6 +237,8 @@ void CMusicPlayer::stop () // stop the music only if we are really playing (else risk to stop a background music!) SoundMngr->stopMusic(0); _State = Stopped; + _PlayStart = 0; + _PauseTime = 0; } // *************************************************************************** @@ -176,12 +248,13 @@ void CMusicPlayer::previous () if (!_Songs.empty()) { // Point the previous song - if (_CurrentSong == 0) - _CurrentSong = (uint)_Songs.size()-1; + sint index; + if (_CurrentSongIndex == 0) + index = (uint)_Songs.size()-1; else - _CurrentSong--; + index = _CurrentSongIndex-1; - play (); + play(index); } } @@ -191,9 +264,11 @@ void CMusicPlayer::next () { if (!_Songs.empty()) { - _CurrentSong++; - _CurrentSong%=_Songs.size(); - play (); + sint index = _CurrentSongIndex+1; + if (index == _Songs.size()) + index = 0; + + play(index); } } @@ -205,17 +280,30 @@ void CMusicPlayer::update () return; if (_State == Playing) { + CViewText *pVT = dynamic_cast(CWidgetManager::getInstance()->getElementFromId("ui:interface:mp3_player:screen:text")); + if (pVT) + { + TTime dur = (CTime::getLocalTime() - _PlayStart) / 1000; + std::string title; + title = toString("%02d:%02d %s", dur / 60, dur % 60, _CurrentSong.Title.c_str()); + pVT->setText(ucstring::makeFromUtf8(title)); + } + if (SoundMngr->isMusicEnded ()) { - // Point the next song - _CurrentSong++; - _CurrentSong%=_Songs.size(); - - // End of the playlist ? - if (_CurrentSong != 0) + // select next song from playlist + sint index = _CurrentSongIndex + 1; + if (isRepeatEnabled() || index < _Songs.size()) { - // No, play the next song - play (); + if (index == _Songs.size()) + { + index = 0; + + if (isShuffleEnabled()) + shuffleAndRebuildPlaylist(); + } + + play(index); } else { @@ -361,11 +449,19 @@ public: CMusicPlayer::CSongs song; song.Filename = filenames[i]; SoundMngr->getMixer()->getSongTitle(filenames[i], song.Title, song.Length); - songs.push_back (song); + if (song.Length > 0) + songs.push_back (song); } MusicPlayer.playSongs(songs); } + else if (Params == "update_playlist") + { + if (MusicPlayer.isShuffleEnabled()) + MusicPlayer.shuffleAndRebuildPlaylist(); + + MusicPlayer.rebuildPlaylist(); + } else if (Params == "previous") MusicPlayer.previous(); else if (Params == "play") diff --git a/code/ryzom/client/src/interface_v3/music_player.h b/code/ryzom/client/src/interface_v3/music_player.h index 33de33c74..7170934d4 100644 --- a/code/ryzom/client/src/interface_v3/music_player.h +++ b/code/ryzom/client/src/interface_v3/music_player.h @@ -55,14 +55,28 @@ public: void update (); + bool isRepeatEnabled() const; + bool isShuffleEnabled() const; + + // Build playlist UI from songs + void rebuildPlaylist(); + // Randomize playlist and rebuild the ui + void shuffleAndRebuildPlaylist(); + // Update playlist active row + void updatePlaylist(sint prevIndex = -1); + private: // The playlist - uint _CurrentSong; // If (!_Songs.empty()) must always be <_Songs.size() + CSongs _CurrentSong; + uint _CurrentSongIndex; // If (!_Songs.empty()) must always be <_Songs.size() std::vector _Songs; // State enum TState { Stopped, Playing, Paused } _State; + + TTime _PlayStart; + TTime _PauseTime; }; extern CMusicPlayer MusicPlayer; From e994c1298347cb24002f4ea9c39f497ab622feed Mon Sep 17 00:00:00 2001 From: Nimetu Date: Tue, 23 Oct 2018 11:19:07 +0300 Subject: [PATCH 029/108] Added: FFmpeg based audio decoder --HG-- branch : develop --- code/CMakeModules/FindFFmpeg.cmake | 173 +++++++ code/nel/CMakeLists.txt | 1 + .../include/nel/sound/audio_decoder_ffmpeg.h | 108 +++++ code/nel/src/sound/CMakeLists.txt | 7 + code/nel/src/sound/audio_decoder.cpp | 42 +- code/nel/src/sound/audio_decoder_ffmpeg.cpp | 430 ++++++++++++++++++ .../client/src/interface_v3/music_player.cpp | 25 +- 7 files changed, 761 insertions(+), 25 deletions(-) create mode 100644 code/CMakeModules/FindFFmpeg.cmake create mode 100644 code/nel/include/nel/sound/audio_decoder_ffmpeg.h create mode 100644 code/nel/src/sound/audio_decoder_ffmpeg.cpp diff --git a/code/CMakeModules/FindFFmpeg.cmake b/code/CMakeModules/FindFFmpeg.cmake new file mode 100644 index 000000000..96cbb6ed0 --- /dev/null +++ b/code/CMakeModules/FindFFmpeg.cmake @@ -0,0 +1,173 @@ +# vim: ts=2 sw=2 +# - Try to find the required ffmpeg components(default: AVFORMAT, AVUTIL, AVCODEC) +# +# Once done this will define +# FFMPEG_FOUND - System has the all required components. +# FFMPEG_INCLUDE_DIRS - Include directory necessary for using the required components headers. +# FFMPEG_LIBRARIES - Link these to use the required ffmpeg components. +# FFMPEG_DEFINITIONS - Compiler switches required for using the required ffmpeg components. +# +# For each of the components it will additionaly set. +# - AVCODEC +# - AVDEVICE +# - AVFORMAT +# - AVUTIL +# - POSTPROC +# - SWSCALE +# - SWRESAMPLE +# the following variables will be defined +# _FOUND - System has +# _INCLUDE_DIRS - Include directory necessary for using the headers +# _LIBRARIES - Link these to use +# _DEFINITIONS - Compiler switches required for using +# _VERSION - The components version +# +# Copyright (c) 2006, Matthias Kretz, +# Copyright (c) 2008, Alexander Neundorf, +# Copyright (c) 2011, Michael Jansen, +# +# Redistribution and use is allowed according to the terms of the BSD license. + +include(FindPackageHandleStandardArgs) + +if(NOT FFmpeg_FIND_COMPONENTS) + set(FFmpeg_FIND_COMPONENTS AVFORMAT AVCODEC AVUTIL) +endif() + +# +### Macro: set_component_found +# +# Marks the given component as found if both *_LIBRARIES AND *_INCLUDE_DIRS is present. +# +macro(set_component_found _component) + if(${_component}_LIBRARIES AND ${_component}_INCLUDE_DIRS) + # message(STATUS " - ${_component} found.") + set(${_component}_FOUND TRUE) + else() + # message(STATUS " - ${_component} not found.") + endif() +endmacro() + +# +### Macro: find_component +# +# Checks for the given component by invoking pkgconfig and then looking up the libraries and +# include directories. +# +macro(find_component _component _pkgconfig _library _header) + if(NOT WIN32) + # use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_${_component} ${_pkgconfig}) + endif() + endif() + + find_path(${_component}_INCLUDE_DIRS ${_header} + HINTS + ${FFMPEGSDK_INC} + ${PC_LIB${_component}_INCLUDEDIR} + ${PC_LIB${_component}_INCLUDE_DIRS} + PATH_SUFFIXES + ffmpeg + ) + + find_library(${_component}_LIBRARIES NAMES ${_library} + HINTS + ${FFMPEGSDK_LIB} + ${PC_LIB${_component}_LIBDIR} + ${PC_LIB${_component}_LIBRARY_DIRS} + ) + + STRING(REGEX REPLACE "/.*" "/version.h" _ver_header ${_header}) + if(EXISTS "${${_component}_INCLUDE_DIRS}/${_ver_header}") + file(STRINGS "${${_component}_INCLUDE_DIRS}/${_ver_header}" version_str REGEX "^#define[\t ]+LIB${_component}_VERSION_M.*") + + foreach(_str "${version_str}") + if(NOT version_maj) + string(REGEX REPLACE "^.*LIB${_component}_VERSION_MAJOR[\t ]+([0-9]*).*$" "\\1" version_maj "${_str}") + endif() + if(NOT version_min) + string(REGEX REPLACE "^.*LIB${_component}_VERSION_MINOR[\t ]+([0-9]*).*$" "\\1" version_min "${_str}") + endif() + if(NOT version_mic) + string(REGEX REPLACE "^.*LIB${_component}_VERSION_MICRO[\t ]+([0-9]*).*$" "\\1" version_mic "${_str}") + endif() + endforeach() + unset(version_str) + + set(${_component}_VERSION "${version_maj}.${version_min}.${version_mic}" CACHE STRING "The ${_component} version number.") + unset(version_maj) + unset(version_min) + unset(version_mic) + endif(EXISTS "${${_component}_INCLUDE_DIRS}/${_ver_header}") + set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number.") + set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS.") + + set_component_found(${_component}) + + mark_as_advanced( + ${_component}_INCLUDE_DIRS + ${_component}_LIBRARIES + ${_component}_DEFINITIONS + ${_component}_VERSION) +endmacro() + + +set(FFMPEGSDK $ENV{FFMPEG_HOME}) +if(FFMPEGSDK) + set(FFMPEGSDK_INC "${FFMPEGSDK}/include") + set(FFMPEGSDK_LIB "${FFMPEGSDK}/lib") +endif() + +# Check for all possible components. +find_component(AVCODEC libavcodec avcodec libavcodec/avcodec.h) +find_component(AVFORMAT libavformat avformat libavformat/avformat.h) +find_component(AVDEVICE libavdevice avdevice libavdevice/avdevice.h) +find_component(AVUTIL libavutil avutil libavutil/avutil.h) +find_component(SWSCALE libswscale swscale libswscale/swscale.h) +find_component(SWRESAMPLE libswresample swresample libswresample/swresample.h) +find_component(POSTPROC libpostproc postproc libpostproc/postprocess.h) + +# Check if the required components were found and add their stuff to the FFMPEG_* vars. +foreach(_component ${FFmpeg_FIND_COMPONENTS}) + if(${_component}_FOUND) + # message(STATUS "Required component ${_component} present.") + set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${${_component}_LIBRARIES}) + set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} ${${_component}_DEFINITIONS}) + list(APPEND FFMPEG_INCLUDE_DIRS ${${_component}_INCLUDE_DIRS}) + else() + # message(STATUS "Required component ${_component} missing.") + endif() +endforeach() + +# Build the include path and library list with duplicates removed. +if(FFMPEG_INCLUDE_DIRS) + list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS) +endif() + +if(FFMPEG_LIBRARIES) + list(REMOVE_DUPLICATES FFMPEG_LIBRARIES) +endif() + +# cache the vars. +set(FFMPEG_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS} CACHE STRING "The FFmpeg include directories." FORCE) +set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} CACHE STRING "The FFmpeg libraries." FORCE) +set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} CACHE STRING "The FFmpeg cflags." FORCE) + +mark_as_advanced(FFMPEG_INCLUDE_DIRS FFMPEG_LIBRARIES FFMPEG_DEFINITIONS) + +# Now set the noncached _FOUND vars for the components. +foreach(_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWRESAMPLE SWSCALE) + set_component_found(${_component}) +endforeach () + +# Compile the list of required vars +set(_FFmpeg_REQUIRED_VARS FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS) +foreach(_component ${FFmpeg_FIND_COMPONENTS}) + list(APPEND _FFmpeg_REQUIRED_VARS ${_component}_LIBRARIES ${_component}_INCLUDE_DIRS) +endforeach() + +# Give a nice error message if some of the required vars are missing. +find_package_handle_standard_args(FFmpeg DEFAULT_MSG ${_FFmpeg_REQUIRED_VARS}) diff --git a/code/nel/CMakeLists.txt b/code/nel/CMakeLists.txt index 84b820d33..3470fcebb 100644 --- a/code/nel/CMakeLists.txt +++ b/code/nel/CMakeLists.txt @@ -20,6 +20,7 @@ ENDIF() IF(WITH_SOUND) FIND_PACKAGE(Ogg) FIND_PACKAGE(Vorbis) + FIND_PACKAGE(FFmpeg COMPONENTS AVCODEC AVFORMAT AVUTIL SWRESAMPLE) IF(WITH_DRIVER_OPENAL) FIND_PACKAGE(OpenAL) diff --git a/code/nel/include/nel/sound/audio_decoder_ffmpeg.h b/code/nel/include/nel/sound/audio_decoder_ffmpeg.h new file mode 100644 index 000000000..c12f52f58 --- /dev/null +++ b/code/nel/include/nel/sound/audio_decoder_ffmpeg.h @@ -0,0 +1,108 @@ +// NeL - MMORPG Framework +// Copyright (C) 2018 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef NLSOUND_AUDIO_DECODER_FFMPEG_H +#define NLSOUND_AUDIO_DECODER_FFMPEG_H +#include + +#include + +struct AVCodecContext; +struct AVFormatContext; +struct AVIOContext; +struct AVPacket; +struct SwrContext; + +namespace NLSOUND { + +/** + * \brief CAudioDecoderFfmpeg + * \date 2018-10-21 08:08GMT + * \author Meelis Mägi (Nimetu) + * CAudioDecoderFfmpeg + * Create trough IAudioDecoder + */ +class CAudioDecoderFfmpeg : public IAudioDecoder +{ +protected: + NLMISC::IStream *_Stream; + + bool _IsSupported; + bool _Loop; + bool _IsMusicEnded; + sint32 _StreamOffset; + sint32 _StreamSize; + + AVIOContext *_AvioContext; + AVFormatContext *_FormatContext; + AVCodecContext *_AudioContext; + SwrContext *_SwrContext; + + // selected stream + sint32 _AudioStreamIndex; + + // output buffer for decoded frame + SwrContext *_ConvertContext; + +private: + // called from constructor if ffmpeg fails to initialize + // or from destructor to cleanup ffmpeg pointers + void release(); + +public: + CAudioDecoderFfmpeg(NLMISC::IStream *stream, bool loop); + virtual ~CAudioDecoderFfmpeg(); + + inline NLMISC::IStream *getStream() { return _Stream; } + inline sint32 getStreamSize() { return _StreamSize; } + inline sint32 getStreamOffset() { return _StreamOffset; } + + // Return true if ffmpeg is able to decode the stream + bool isFormatSupported() const; + + /// Get information on a music file (only artist and title at the moment). + static bool getInfo(NLMISC::IStream *stream, std::string &artist, std::string &title, float &length); + + /// Get how many bytes the music buffer requires for output minimum. + virtual uint32 getRequiredBytes(); + + /// Get an amount of bytes between minimum and maximum (can be lower than minimum if at end). + virtual uint32 getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum); + + /// Get the amount of channels (2 is stereo) in output. + virtual uint8 getChannels(); + + /// Get the samples per second (often 44100) in output. + virtual uint getSamplesPerSec(); + + /// Get the bits per sample (often 16) in output. + virtual uint8 getBitsPerSample(); + + /// Get if the music has ended playing (never true if loop). + virtual bool isMusicEnded(); + + /// Get the total time in seconds. + virtual float getLength(); + + /// Set looping + virtual void setLooping(bool loop); +}; /* class CAudioDecoderFfmpeg */ + +} /* namespace NLSOUND */ + +#endif // NLSOUND_AUDIO_DECODER_FFMPEG_H + +/* end of file */ diff --git a/code/nel/src/sound/CMakeLists.txt b/code/nel/src/sound/CMakeLists.txt index e4831c643..bc1816a17 100644 --- a/code/nel/src/sound/CMakeLists.txt +++ b/code/nel/src/sound/CMakeLists.txt @@ -58,6 +58,7 @@ FILE(GLOB STREAM FILE(GLOB STREAM_FILE audio_decoder.cpp ../../include/nel/sound/audio_decoder.h audio_decoder_vorbis.cpp ../../include/nel/sound/audio_decoder_vorbis.h + audio_decoder_ffmpeg.cpp ../../include/nel/sound/audio_decoder_ffmpeg.h stream_file_sound.cpp ../../include/nel/sound/stream_file_sound.h stream_file_source.cpp ../../include/nel/sound/stream_file_source.h ) @@ -95,6 +96,12 @@ IF(WITH_STATIC) TARGET_LINK_LIBRARIES(nelsound ${OGG_LIBRARY}) ENDIF() +IF(FFMPEG_FOUND) + ADD_DEFINITIONS(-DFFMPEG_ENABLED) + INCLUDE_DIRECTORIES(${FFMPEG_INCLUDE_DIRS}) + TARGET_LINK_LIBRARIES(nelsound ${FFMPEG_LIBRARIES}) +ENDIF() + INCLUDE_DIRECTORIES(${LIBXML2_INCLUDE_DIR}) diff --git a/code/nel/src/sound/audio_decoder.cpp b/code/nel/src/sound/audio_decoder.cpp index 6e9e42b61..f0eb80efd 100644 --- a/code/nel/src/sound/audio_decoder.cpp +++ b/code/nel/src/sound/audio_decoder.cpp @@ -37,6 +37,10 @@ // Project includes #include +#ifdef FFMPEG_ENABLED +#include +#endif + using namespace std; using namespace NLMISC; @@ -82,6 +86,17 @@ IAudioDecoder *IAudioDecoder::createAudioDecoder(const std::string &type, NLMISC nlwarning("Stream is NULL"); return NULL; } +#ifdef FFMPEG_ENABLED + try { + CAudioDecoderFfmpeg *decoder = new CAudioDecoderFfmpeg(stream, loop); + return static_cast(decoder); + } + catch(const Exception &e) + { + nlwarning("Exception %s during ffmpeg setup", e.what()); + return NULL; + } +#else std::string type_lower = toLower(type); if (type_lower == "ogg") { @@ -92,23 +107,32 @@ IAudioDecoder *IAudioDecoder::createAudioDecoder(const std::string &type, NLMISC nlwarning("Music file type unknown: '%s'", type_lower.c_str()); return NULL; } +#endif } bool IAudioDecoder::getInfo(const std::string &filepath, std::string &artist, std::string &title, float &length) { std::string lookup = CPath::lookup(filepath, false); if (lookup.empty()) - { + { nlwarning("Music file %s does not exist!", filepath.c_str()); - return false; + return false; } + +#ifdef FFMPEG_ENABLED + CIFile ifile; + ifile.setCacheFileOnOpen(false); + ifile.allowBNPCacheFileOnOpen(false); + if (ifile.open(lookup)) + return CAudioDecoderFfmpeg::getInfo(&ifile, artist, title, length); +#else std::string type = CFile::getExtension(filepath); std::string type_lower = NLMISC::toLower(type); if (type_lower == "ogg") { - CIFile ifile; - ifile.setCacheFileOnOpen(false); + CIFile ifile; + ifile.setCacheFileOnOpen(false); ifile.allowBNPCacheFileOnOpen(false); if (ifile.open(lookup)) return CAudioDecoderVorbis::getInfo(&ifile, artist, title, length); @@ -119,6 +143,7 @@ bool IAudioDecoder::getInfo(const std::string &filepath, std::string &artist, st { nlwarning("Music file type unknown: '%s'", type_lower.c_str()); } +#endif artist.clear(); title.clear(); return false; @@ -132,6 +157,11 @@ void IAudioDecoder::getMusicExtensions(std::vector &extensions) { extensions.push_back("ogg"); } +#ifdef FFMPEG_ENABLED + extensions.push_back("mp3"); + extensions.push_back("flac"); + extensions.push_back("aac"); +#endif // extensions.push_back("wav"); // TODO: Easy. } @@ -139,7 +169,11 @@ void IAudioDecoder::getMusicExtensions(std::vector &extensions) /// Return if a music extension is supported by the nel sound library. bool IAudioDecoder::isMusicExtensionSupported(const std::string &extension) { +#ifdef FFMPEG_ENABLED + return (extension == "ogg" || extension == "mp3" || extension == "flac" || extension == "aac"); +#else return (extension == "ogg"); +#endif } } /* namespace NLSOUND */ diff --git a/code/nel/src/sound/audio_decoder_ffmpeg.cpp b/code/nel/src/sound/audio_decoder_ffmpeg.cpp new file mode 100644 index 000000000..50acb17cc --- /dev/null +++ b/code/nel/src/sound/audio_decoder_ffmpeg.cpp @@ -0,0 +1,430 @@ +// NeL - MMORPG Framework +// Copyright (C) 2018 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + + +#include "stdsound.h" + +#include + +#define __STDC_CONSTANT_MACROS +extern "C" +{ +#include +#include +#include +#include +}; + +using namespace std; +using namespace NLMISC; + +namespace { + +const std::string av_err2string(sint err) +{ + char buf[AV_ERROR_MAX_STRING_SIZE]; + av_strerror(err, buf, AV_ERROR_MAX_STRING_SIZE); + return (std::string)buf; +} + +void nel_logger(void *ptr, int level, const char *fmt, va_list vargs) +{ + static char msg[1024]; + + const char *module = NULL; + + // AV_LOG_DEBUG, AV_LOG_TRACE + if (level >= AV_LOG_DEBUG) return; + + if (ptr) + { + AVClass *avc = *(AVClass**) ptr; + module = avc->item_name(ptr); + } + + vsnprintf(msg, sizeof(msg), fmt, vargs); + msg[sizeof(msg)-1] = '\0'; + + switch(level) + { + case AV_LOG_PANIC: + // ffmpeg is about to crash so lets throw + nlerror("FFMPEG(P): (%s) %s", module, msg); + break; + case AV_LOG_FATAL: + // ffmpeg had unrecoverable error, corrupted stream or such + nlerrornoex("FFMPEG(F): (%s) %s", module, msg); + break; + case AV_LOG_ERROR: + nlwarning("FFMPEG(E): (%s) %s", module, msg); + break; + case AV_LOG_WARNING: + nlwarning("FFMPEG(W): (%s) %s", module, msg); + break; + case AV_LOG_INFO: + nlinfo("FFMPEG(I): (%s) %s", module, msg); + break; + case AV_LOG_VERBOSE: + nldebug("FFMPEG(V): (%s) %s", module, msg); + break; + case AV_LOG_DEBUG: + nldebug("FFMPEG(D): (%s) %s", module, msg); + break; + default: + nlinfo("FFMPEG: invalid log level:%d (%s) %s", level, module, msg); + break; + } +} + +class CFfmpegInstance +{ +public: + CFfmpegInstance() + { + av_log_set_level(AV_LOG_DEBUG); + av_log_set_callback(nel_logger); + + av_register_all(); + + //avformat_network_init(); + } + + virtual ~CFfmpegInstance() + { + //avformat_network_deinit(); + } +}; + +CFfmpegInstance ffmpeg; + +// Send bytes to ffmpeg +int avio_read_packet(void *opaque, uint8 *buf, int buf_size) +{ + NLSOUND::CAudioDecoderFfmpeg *decoder = static_cast(opaque); + NLMISC::IStream *stream = decoder->getStream(); + nlassert(stream->isReading()); + + uint32 available = decoder->getStreamSize() - stream->getPos(); + if (available == 0) return 0; + + buf_size = FFMIN(buf_size, available); + stream->serialBuffer((uint8 *)buf, buf_size); + return buf_size; +} + +sint64 avio_seek(void *opaque, sint64 offset, int whence) +{ + NLSOUND::CAudioDecoderFfmpeg *decoder = static_cast(opaque); + NLMISC::IStream *stream = decoder->getStream(); + nlassert(stream->isReading()); + + NLMISC::IStream::TSeekOrigin origin; + switch(whence) + { + case SEEK_SET: + origin = NLMISC::IStream::begin; + break; + case SEEK_CUR: + origin = NLMISC::IStream::current; + break; + case SEEK_END: + origin = NLMISC::IStream::end; + break; + case AVSEEK_SIZE: + return decoder->getStreamSize(); + default: + return -1; + } + + stream->seek((sint32) offset, origin); + return stream->getPos(); +} + +}//ns + +namespace NLSOUND { + +// swresample will convert audio to this format +#define FFMPEG_SAMPLE_RATE 44100 +#define FFMPEG_CHANNELS 2 +#define FFMPEG_CHANNEL_LAYOUT AV_CH_LAYOUT_STEREO +#define FFMPEG_BITS_PER_SAMPLE 16 +#define FFMPEG_SAMPLE_FORMAT AV_SAMPLE_FMT_S16 + +CAudioDecoderFfmpeg::CAudioDecoderFfmpeg(NLMISC::IStream *stream, bool loop) +: IAudioDecoder(), + _Stream(stream), _Loop(loop), _IsMusicEnded(false), _StreamSize(0), _IsSupported(false), + _AvioContext(NULL), _FormatContext(NULL), + _AudioContext(NULL), _AudioStreamIndex(0), + _SwrContext(NULL) +{ + _StreamOffset = stream->getPos(); + stream->seek(0, NLMISC::IStream::end); + _StreamSize = stream->getPos(); + stream->seek(_StreamOffset, NLMISC::IStream::begin); + + try { + _FormatContext = avformat_alloc_context(); + if (!_FormatContext) + throw Exception("Can't create AVFormatContext"); + + // avio_ctx_buffer can be reallocated by ffmpeg and assigned to avio_ctx->buffer + uint8 *avio_ctx_buffer = NULL; + size_t avio_ctx_buffer_size = 4096; + avio_ctx_buffer = static_cast(av_malloc(avio_ctx_buffer_size)); + if (!avio_ctx_buffer) + throw Exception("Can't allocate avio context buffer"); + + _AvioContext = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, this, &avio_read_packet, NULL, &avio_seek); + if (!_AvioContext) + throw Exception("Can't allocate avio context"); + + _FormatContext->pb = _AvioContext; + sint ret = avformat_open_input(&_FormatContext, NULL, NULL, NULL); + if (ret < 0) + throw Exception("avformat_open_input: %d", ret); + + // find stream and then audio codec to see if ffmpeg supports this + _IsSupported = false; + if (avformat_find_stream_info(_FormatContext, NULL) >= 0) + { + _AudioStreamIndex = av_find_best_stream(_FormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); + if (_AudioStreamIndex >= 0) + { + _AudioContext = _FormatContext->streams[_AudioStreamIndex]->codec; + av_opt_set_int(_AudioContext, "refcounted_frames", 1, 0); + + AVCodec *codec = avcodec_find_decoder(_AudioContext->codec_id); + if (codec != NULL && avcodec_open2(_AudioContext, codec, NULL) >= 0) + { + _IsSupported = true; + } + } + } + } + catch(...) + { + release(); + + throw; + } + + if (!_IsSupported) + { + nlwarning("FFMPEG: Decoder created, unknown stream format / codec"); + } +} + +CAudioDecoderFfmpeg::~CAudioDecoderFfmpeg() +{ + release(); +} + +void CAudioDecoderFfmpeg::release() +{ + if (_SwrContext) + swr_free(&_SwrContext); + + if (_AudioContext) + avcodec_close(_AudioContext); + + if (_FormatContext) + avformat_close_input(&_FormatContext); + + if (_AvioContext && _AvioContext->buffer) + av_freep(&_AvioContext->buffer); + + if (_AvioContext) + av_freep(&_AvioContext); +} + +bool CAudioDecoderFfmpeg::isFormatSupported() const +{ + return _IsSupported; +} + +/// Get information on a music file. +bool CAudioDecoderFfmpeg::getInfo(NLMISC::IStream *stream, std::string &artist, std::string &title, float &length) +{ + CAudioDecoderFfmpeg ffmpeg(stream, false); + if (!ffmpeg.isFormatSupported()) + { + title.clear(); + artist.clear(); + length = 0.f; + + return false; + } + + AVDictionaryEntry *tag = NULL; + while((tag = av_dict_get(ffmpeg._FormatContext->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) + { + if (!strcmp(tag->key, "artist")) + { + artist = tag->value; + } + else if (!strcmp(tag->key, "title")) + { + title = tag->value; + } + } + + if (ffmpeg._FormatContext->duration != AV_NOPTS_VALUE) + { + length = ffmpeg._FormatContext->duration * av_q2d(AV_TIME_BASE_Q); + } + else if (ffmpeg._FormatContext->streams[ffmpeg._AudioStreamIndex]->duration != AV_NOPTS_VALUE) + { + length = ffmpeg._FormatContext->streams[ffmpeg._AudioStreamIndex]->duration * av_q2d(ffmpeg._FormatContext->streams[ffmpeg._AudioStreamIndex]->time_base); + } + else + { + length = 0.f; + } + + return true; +} + +uint32 CAudioDecoderFfmpeg::getRequiredBytes() +{ + return 0; // no minimum requirement of bytes to buffer out +} + +uint32 CAudioDecoderFfmpeg::getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum) +{ + if (_IsMusicEnded) return 0; + nlassert(minimum <= maximum); // can't have this.. + + // TODO: CStreamFileSource::play() will stall when there is no frames on warmup + // supported can be set false if there is an issue creating converter + if (!_IsSupported) + { + _IsMusicEnded = true; + return 1; + } + + uint32 bytes_read = 0; + + AVFrame frame = {0}; + AVPacket packet = {0}; + + if (!_SwrContext) + { + sint64 in_channel_layout = av_get_default_channel_layout(_AudioContext->channels); + _SwrContext = swr_alloc_set_opts(NULL, + // output + FFMPEG_CHANNEL_LAYOUT, FFMPEG_SAMPLE_FORMAT, FFMPEG_SAMPLE_RATE, + // input + in_channel_layout, _AudioContext->sample_fmt, _AudioContext->sample_rate, + 0, NULL); + swr_init(_SwrContext); + } + + sint ret; + while(bytes_read < minimum) + { + // read packet from stream + if ((ret = av_read_frame(_FormatContext, &packet)) < 0) + { + _IsMusicEnded = true; + // TODO: looping + break; + } + + if (packet.stream_index == _AudioStreamIndex) + { + // packet can contain multiple frames + AVPacket first = packet; + int got_frame = 0; + do { + got_frame = 0; + ret = avcodec_decode_audio4(_AudioContext, &frame, &got_frame, &packet); + if (ret < 0) + { + nlwarning("FFMPEG: error decoding audio frame: %s", av_err2string(ret).c_str()); + break; + } + packet.size -= ret; + packet.data += ret; + + if (got_frame) + { + uint32 out_bps = av_get_bytes_per_sample(FFMPEG_SAMPLE_FORMAT) * FFMPEG_CHANNELS; + uint32 max_samples = (maximum - bytes_read) / out_bps; + + uint32 out_samples = av_rescale_rnd(swr_get_delay(_SwrContext, _AudioContext->sample_rate) + frame.nb_samples, + FFMPEG_SAMPLE_RATE, _AudioContext->sample_rate, AV_ROUND_UP); + + if (max_samples > out_samples) + max_samples = out_samples; + + uint32 converted = swr_convert(_SwrContext, &buffer, max_samples, (const uint8 **)frame.extended_data, frame.nb_samples); + uint32 size = out_bps * converted; + + bytes_read += size; + buffer += size; + + av_frame_unref(&frame); + } + } while (got_frame && packet.size > 0); + + av_packet_unref(&first); + } + else + { + ret = 0; + av_packet_unref(&packet); + } + } + + return bytes_read; +} + +uint8 CAudioDecoderFfmpeg::getChannels() +{ + return FFMPEG_CHANNELS; +} + +uint CAudioDecoderFfmpeg::getSamplesPerSec() +{ + return FFMPEG_SAMPLE_RATE; +} + +uint8 CAudioDecoderFfmpeg::getBitsPerSample() +{ + return FFMPEG_BITS_PER_SAMPLE; +} + +bool CAudioDecoderFfmpeg::isMusicEnded() +{ + return _IsMusicEnded; +} + +float CAudioDecoderFfmpeg::getLength() +{ + printf(">> CAudioDecoderFfmpeg::getLength\n"); + // TODO: return (float)ov_time_total(&_OggVorbisFile, -1); + return 0.f; +} + +void CAudioDecoderFfmpeg::setLooping(bool loop) +{ + _Loop = loop; +} + +} /* namespace NLSOUND */ + +/* end of file */ diff --git a/code/ryzom/client/src/interface_v3/music_player.cpp b/code/ryzom/client/src/interface_v3/music_player.cpp index 358189d42..55aa9e342 100644 --- a/code/ryzom/client/src/interface_v3/music_player.cpp +++ b/code/ryzom/client/src/interface_v3/music_player.cpp @@ -372,22 +372,10 @@ public: // no format supported if (extensions.empty()) return; - bool oggSupported = false; - bool mp3Supported = false; - std::string message; for(uint i = 0; i < extensions.size(); ++i) { - if (extensions[i] == "ogg") - { - oggSupported = true; - message += " ogg"; - } - else if (extensions[i] == "mp3") - { - mp3Supported = true; - message += " mp3"; - } + message += " " + extensions[i]; } message += " m3u m3u8"; nlinfo("Media player supports: '%s'", message.substr(1).c_str()); @@ -404,15 +392,9 @@ public: for (i = 0; i < filesToProcess.size(); ++i) { std::string ext = toLower(CFile::getExtension(filesToProcess[i])); - if (ext == "ogg") + if (std::find(extensions.begin(), extensions.end(), ext) != extensions.end()) { - if (oggSupported) - filenames.push_back(filesToProcess[i]); - } - else if (ext == "mp3" || ext == "mp2" || ext == "mp1") - { - if (mp3Supported) - filenames.push_back(filesToProcess[i]); + filenames.push_back(filesToProcess[i]); } else if (ext == "m3u" || ext == "m3u8") { @@ -448,6 +430,7 @@ public: CMusicPlayer::CSongs song; song.Filename = filenames[i]; + // TODO: cache the result for next refresh SoundMngr->getMixer()->getSongTitle(filenames[i], song.Title, song.Length); if (song.Length > 0) songs.push_back (song); From 9b9bd0eb8cd548cfa745a2b6ab1de59f8fb1604b Mon Sep 17 00:00:00 2001 From: Nimetu Date: Wed, 24 Oct 2018 08:38:47 +0300 Subject: [PATCH 030/108] Fixed: Compiling withoud ffmpeg --HG-- branch : develop --- code/nel/src/sound/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/code/nel/src/sound/CMakeLists.txt b/code/nel/src/sound/CMakeLists.txt index bc1816a17..f0417754c 100644 --- a/code/nel/src/sound/CMakeLists.txt +++ b/code/nel/src/sound/CMakeLists.txt @@ -1,6 +1,10 @@ FILE(GLOB SRC *.cpp *.h) FILE(GLOB HEADERS ../../include/nel/sound/*.h) +IF(NOT FFMPEG_FOUND) + LIST(REMOVE_ITEM SRC ${CMAKE_CURRENT_SOURCE_DIR}/audio_decoder_ffmpeg.cpp) + LIST(REMOVE_ITEM HEADERS ${CMAKE_CURRENT_SOURCE_DIR}../../include/nel/sound/audio_decoder_ffmpeg.h) +ENDIF() FILE(GLOB ANIMATION sound_anim_manager.cpp ../../include/nel/sound/sound_anim_manager.h From cea7a4c0cbd423aff0761efab0ad4b34f38ab1ea Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 1 Nov 2018 08:33:35 +0200 Subject: [PATCH 031/108] Backed out changeset: 68e5d9033466 --HG-- branch : develop --- code/nel/include/nel/gui/interface_group.h | 10 +------ code/nel/src/gui/interface_group.cpp | 32 ++++------------------ 2 files changed, 6 insertions(+), 36 deletions(-) diff --git a/code/nel/include/nel/gui/interface_group.h b/code/nel/include/nel/gui/interface_group.h index 5f864e9e9..01f2b9701 100644 --- a/code/nel/include/nel/gui/interface_group.h +++ b/code/nel/include/nel/gui/interface_group.h @@ -101,9 +101,6 @@ namespace NLGUI // test is a group is a direct child of this interface group bool isChildGroup(const CInterfaceGroup *group) const; - // test is x,y is inside last draw clip aread - bool isInViewport(sint32 x, sint32 y) const; - virtual bool isWindowUnder (sint32 x, sint32 y); // Virtual for menu that is not square CInterfaceGroup *getGroupUnder (sint32 x, sint32 y); virtual bool getViewsUnder (sint32 x, sint32 y, sint32 clipX, sint32 clipY, sint32 clipW, sint32 clipH, std::vector &vVB); // Return true if x,y under the group @@ -344,13 +341,8 @@ namespace NLGUI void alignElements(); protected: - /// Last clip area cached from draw call - sint32 _LastClipX; - sint32 _LastClipY; - sint32 _LastClipW; - sint32 _LastClipH; - void makeNewClip (sint32 &oldClipX, sint32 &oldClipY, sint32 &oldClipW, sint32 &oldClipH, bool drawing = false); + void makeNewClip (sint32 &oldClipX, sint32 &oldClipY, sint32 &oldClipW, sint32 &oldClipH); void restoreClip (sint32 oldSciX, sint32 oldSciY, sint32 oldSciW, sint32 oldSciH); // Compute clip contribution for current window, and a previous clipping rectangle. This doesn't change the clip window in the driver. diff --git a/code/nel/src/gui/interface_group.cpp b/code/nel/src/gui/interface_group.cpp index 3da60c9f2..789030a4a 100644 --- a/code/nel/src/gui/interface_group.cpp +++ b/code/nel/src/gui/interface_group.cpp @@ -77,11 +77,6 @@ namespace NLGUI _LUAEnvTableCreated= false; _DepthForZSort= 0.f; - _LastClipX = 0; - _LastClipY = 0; - _LastClipW = 0; - _LastClipH = 0; - #ifdef AJM_DEBUG_TRACK_INTERFACE_GROUPS CInterfaceManager::getInstance()->DebugTrackGroupsCreated( this ); #endif @@ -1259,8 +1254,7 @@ namespace NLGUI { const NLGUI::CEventDescriptorMouse &eventDesc = (const NLGUI::CEventDescriptorMouse &)event; - // group might be partially hidden (scolling) so test against last visible area - if (!isInViewport(eventDesc.getX(), eventDesc.getY())) + if (!isIn(eventDesc.getX(), eventDesc.getY())) return false; bool taken = false; @@ -1305,6 +1299,7 @@ namespace NLGUI } if (eventDesc.getEventTypeExtended() == NLGUI::CEventDescriptorMouse::mousewheel) { + // handle the Mouse Wheel only if interesting if (_H>_MaxH) { CInterfaceGroup *currParent = _Parent; @@ -1334,7 +1329,7 @@ namespace NLGUI void CInterfaceGroup::draw () { sint32 oldSciX, oldSciY, oldSciW, oldSciH; - makeNewClip (oldSciX, oldSciY, oldSciW, oldSciH, true); + makeNewClip (oldSciX, oldSciY, oldSciW, oldSciH); // Display sons only if not total clipped CViewRenderer &rVR = *CViewRenderer::getInstance(); @@ -1723,16 +1718,6 @@ namespace NLGUI (y <= (_YReal + _HReal))); } - // ------------------------------------------------------------------------------------------------ - bool CInterfaceGroup::isInViewport(sint32 x, sint32 y) const - { - return ( - (x > _LastClipX) && - (x < (_LastClipX + _LastClipW))&& - (y > _LastClipY) && - (y < (_LastClipY + _LastClipH))); - } - // ------------------------------------------------------------------------------------------------ CInterfaceGroup* CInterfaceGroup::getGroupUnder (sint32 x, sint32 y) { @@ -1991,10 +1976,11 @@ namespace NLGUI newSciYDest = newSciY; newSciWDest = newSciW/* - _MarginLeft*/; newSciHDest = newSciH; + } // ------------------------------------------------------------------------------------------------ - void CInterfaceGroup::makeNewClip (sint32 &oldSciX, sint32 &oldSciY, sint32 &oldSciW, sint32 &oldSciH, bool drawing) + void CInterfaceGroup::makeNewClip (sint32 &oldSciX, sint32 &oldSciY, sint32 &oldSciW, sint32 &oldSciH) { CViewRenderer &rVR = *CViewRenderer::getInstance(); rVR.getClipWindow (oldSciX, oldSciY, oldSciW, oldSciH); @@ -2002,14 +1988,6 @@ namespace NLGUI sint32 newSciX, newSciY, newSciW, newSciH; computeCurrentClipContribution(oldSciX, oldSciY, oldSciW, oldSciH, newSciX, newSciY, newSciW, newSciH); rVR.setClipWindow (newSciX, newSciY, newSciW, newSciH); - - if (drawing) - { - _LastClipX = newSciX; - _LastClipY = newSciY; - _LastClipW = newSciW; - _LastClipH = newSciH; - } } // ------------------------------------------------------------------------------------------------ From 19e1b47ddbf2cfefed9907e06c9454ea2187711d Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sun, 4 Nov 2018 15:59:42 +0200 Subject: [PATCH 032/108] Fixed: Compiling under windows --HG-- branch : develop --- code/nel/src/sound/CMakeLists.txt | 2 +- code/nel/src/sound/audio_decoder_ffmpeg.cpp | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/code/nel/src/sound/CMakeLists.txt b/code/nel/src/sound/CMakeLists.txt index f0417754c..7b9eebc9f 100644 --- a/code/nel/src/sound/CMakeLists.txt +++ b/code/nel/src/sound/CMakeLists.txt @@ -3,7 +3,7 @@ FILE(GLOB HEADERS ../../include/nel/sound/*.h) IF(NOT FFMPEG_FOUND) LIST(REMOVE_ITEM SRC ${CMAKE_CURRENT_SOURCE_DIR}/audio_decoder_ffmpeg.cpp) - LIST(REMOVE_ITEM HEADERS ${CMAKE_CURRENT_SOURCE_DIR}../../include/nel/sound/audio_decoder_ffmpeg.h) + LIST(REMOVE_ITEM HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/../../include/nel/sound/audio_decoder_ffmpeg.h) ENDIF() FILE(GLOB ANIMATION diff --git a/code/nel/src/sound/audio_decoder_ffmpeg.cpp b/code/nel/src/sound/audio_decoder_ffmpeg.cpp index 50acb17cc..dea8368d2 100644 --- a/code/nel/src/sound/audio_decoder_ffmpeg.cpp +++ b/code/nel/src/sound/audio_decoder_ffmpeg.cpp @@ -26,10 +26,15 @@ extern "C" #include #include #include -}; +} using namespace std; using namespace NLMISC; +using namespace NLSOUND; + +// Visual Studio does not support AV_TIME_BASE_Q macro in C++ +#undef AV_TIME_BASE_Q +static const AVRational AV_TIME_BASE_Q = {1, AV_TIME_BASE}; namespace { From edf1d12f65197ef665fb8c1d145789475e89da6f Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sun, 4 Nov 2018 18:14:52 +0200 Subject: [PATCH 033/108] Fixed: Opening local file with @ char in filename. --HG-- branch : develop --- code/nel/src/misc/file.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/nel/src/misc/file.cpp b/code/nel/src/misc/file.cpp index 414a10e5a..54ffdfbc6 100644 --- a/code/nel/src/misc/file.cpp +++ b/code/nel/src/misc/file.cpp @@ -185,7 +185,7 @@ bool CIFile::open(const std::string &path, bool text) // Bigfile or xml pack access requested ? string::size_type pos; - if ((pos = path.find('@')) != string::npos) + if (!CFile::fileExists(path) && (pos = path.find('@')) != string::npos) { // check for a double @ to identify XML pack file if (pos+1 < path.size() && path[pos+1] == '@') From 6c8e42361a8fa5513a41ad6d39a352498a782213 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sun, 4 Nov 2018 18:15:27 +0200 Subject: [PATCH 034/108] Fixed: Playing music files with @ in the filename. --HG-- branch : develop --- code/ryzom/client/src/interface_v3/music_player.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/code/ryzom/client/src/interface_v3/music_player.cpp b/code/ryzom/client/src/interface_v3/music_player.cpp index 55aa9e342..c49757681 100644 --- a/code/ryzom/client/src/interface_v3/music_player.cpp +++ b/code/ryzom/client/src/interface_v3/music_player.cpp @@ -415,14 +415,6 @@ public: std::vector songs; for (i=0; i Date: Sun, 4 Nov 2018 17:22:33 +0200 Subject: [PATCH 035/108] Fixed: Displaying song playtime --HG-- branch : develop --- code/ryzom/client/src/interface_v3/music_player.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/code/ryzom/client/src/interface_v3/music_player.cpp b/code/ryzom/client/src/interface_v3/music_player.cpp index c49757681..0670c2935 100644 --- a/code/ryzom/client/src/interface_v3/music_player.cpp +++ b/code/ryzom/client/src/interface_v3/music_player.cpp @@ -284,8 +284,13 @@ void CMusicPlayer::update () if (pVT) { TTime dur = (CTime::getLocalTime() - _PlayStart) / 1000; - std::string title; - title = toString("%02d:%02d %s", dur / 60, dur % 60, _CurrentSong.Title.c_str()); + uint min = (dur / 60) % 60; + uint sec = dur % 60; + uint hour = dur / 3600; + + std::string title(toString("%02d:%02d", min, sec)); + if (hour > 0) title = toString("%02d:", hour) + title; + title += " " + _CurrentSong.Title; pVT->setText(ucstring::makeFromUtf8(title)); } From 5b4b7893f8dcb13465f5f91dd6c3685cc9986c46 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Mon, 5 Nov 2018 13:11:43 +0200 Subject: [PATCH 036/108] Fixed: openURL crash under linux --HG-- branch : develop --- code/nel/src/misc/common.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/nel/src/misc/common.cpp b/code/nel/src/misc/common.cpp index 599f3cfb2..48e6e9845 100644 --- a/code/nel/src/misc/common.cpp +++ b/code/nel/src/misc/common.cpp @@ -1720,12 +1720,12 @@ static bool openDocWithExtension (const std::string &document, const std::string const char *previousEnv = getenv("LD_LIBRARY_PATH"); // clear LD_LIBRARY_PATH to avoid problems with Steam Runtime - setenv("LD_LIBRARY_PATH", "", 1); + if (previousEnv) setenv("LD_LIBRARY_PATH", "", 1); bool res = launchProgram(command, document); // restore previous LD_LIBRARY_PATH - setenv("LD_LIBRARY_PATH", previousEnv, 1); + if (previousEnv) setenv("LD_LIBRARY_PATH", previousEnv, 1); return res; #endif // NL_OS_WINDOWS From ab6467f64c36cc1cbfb7d2f614b66f88455756de Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sun, 11 Nov 2018 23:50:16 +0200 Subject: [PATCH 037/108] Fixed: Html P element not using style attribute --HG-- branch : develop --- code/nel/include/nel/gui/libwww.h | 1 + code/nel/src/gui/group_html.cpp | 4 ++-- code/nel/src/gui/libwww.cpp | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/code/nel/include/nel/gui/libwww.h b/code/nel/include/nel/gui/libwww.h index 64258608e..9c6579f1f 100644 --- a/code/nel/include/nel/gui/libwww.h +++ b/code/nel/include/nel/gui/libwww.h @@ -208,6 +208,7 @@ namespace NLGUI HTML_ATTR(P,QUICK_HELP_EVENTS), HTML_ATTR(P,QUICK_HELP_LINK), HTML_ATTR(P,NAME), + HTML_ATTR(P,STYLE), }; enum diff --git a/code/nel/src/gui/group_html.cpp b/code/nel/src/gui/group_html.cpp index b606a093d..efea31898 100644 --- a/code/nel/src/gui/group_html.cpp +++ b/code/nel/src/gui/group_html.cpp @@ -2281,8 +2281,8 @@ namespace NLGUI { newParagraph(PBeginSpace); pushStyle(); - if (present[HTML_BLOCK_STYLE] && value[HTML_BLOCK_STYLE]) - getStyleParams(value[HTML_BLOCK_STYLE], _Style); + if (present[MY_HTML_P_STYLE] && value[MY_HTML_P_STYLE]) + getStyleParams(value[MY_HTML_P_STYLE], _Style); } break; case HTML_PRE: diff --git a/code/nel/src/gui/libwww.cpp b/code/nel/src/gui/libwww.cpp index 2e9b4451a..f00a92749 100644 --- a/code/nel/src/gui/libwww.cpp +++ b/code/nel/src/gui/libwww.cpp @@ -213,6 +213,7 @@ namespace NLGUI HTML_ATTR(P,QUICK_HELP_EVENTS), HTML_ATTR(P,QUICK_HELP_LINK), HTML_ATTR(P,NAME), + HTML_ATTR(P,STYLE), { 0 } }; From e5b7064a4dc53bfc2194ca5aaaf784489abe1afe Mon Sep 17 00:00:00 2001 From: Nimetu Date: Wed, 24 Oct 2018 14:22:35 +0300 Subject: [PATCH 038/108] Changed: New font texture implementation --HG-- branch : develop --- code/nel/include/nel/3d/computed_string.h | 5 + code/nel/include/nel/3d/font_generator.h | 2 + code/nel/include/nel/3d/font_manager.h | 17 +- code/nel/include/nel/3d/text_context.h | 13 + code/nel/include/nel/3d/texture_font.h | 133 ++++- code/nel/src/3d/font_generator.cpp | 5 + code/nel/src/3d/font_manager.cpp | 55 +- code/nel/src/3d/text_context.cpp | 28 +- code/nel/src/3d/texture_font.cpp | 692 +++++++++++++--------- code/ryzom/client/src/main_loop.cpp | 16 + 10 files changed, 637 insertions(+), 329 deletions(-) diff --git a/code/nel/include/nel/3d/computed_string.h b/code/nel/include/nel/3d/computed_string.h index 517200383..25d12a3ac 100644 --- a/code/nel/include/nel/3d/computed_string.h +++ b/code/nel/include/nel/3d/computed_string.h @@ -178,6 +178,10 @@ public: CVertexBuffer Vertices; CMaterial *Material; CRGBA Color; + ucstring Text; + + uint32 CacheVersion; + /// The width of the string, in pixels (eg: 30) float StringWidth; /// The height of the string, in pixels (eg: 10) @@ -223,6 +227,7 @@ public: */ CComputedString (bool bSetupVB=true) { + CacheVersion = 0; StringWidth = 0; StringHeight = 0; if (bSetupVB) diff --git a/code/nel/include/nel/3d/font_generator.h b/code/nel/include/nel/3d/font_generator.h index 5a07733a0..e71551c43 100644 --- a/code/nel/include/nel/3d/font_generator.h +++ b/code/nel/include/nel/3d/font_generator.h @@ -74,6 +74,8 @@ public: uint32 getUID() { return _UID; } + std::string getFontFileName() const; + private: static uint32 _FontGeneratorCounterUID; diff --git a/code/nel/include/nel/3d/font_manager.h b/code/nel/include/nel/3d/font_manager.h index 26ea02ce0..663e9d23a 100644 --- a/code/nel/include/nel/3d/font_manager.h +++ b/code/nel/include/nel/3d/font_manager.h @@ -59,6 +59,9 @@ class CFontManager CSmartPtr _MatFont; CSmartPtr _TexFont; + // Keep track number of textures created to properly report cache version + uint32 _TexCacheNr; + public: /** @@ -71,6 +74,7 @@ public: _NbChar = 0; _MatFont = NULL; _TexFont = NULL; + _TexCacheNr = 0; } @@ -94,7 +98,6 @@ public: */ CMaterial* getFontMaterial(); - /** * Compute primitive blocks and materials of each character of * the string. @@ -152,7 +155,8 @@ public: void dumpCache (const char *filename) { - _TexFont->dumpTextureFont (filename); + if (_TexFont) + _TexFont->dumpTextureFont (filename); } /** @@ -160,6 +164,15 @@ public: */ void invalidate(); + // get font atlas rebuild count + uint32 getCacheVersion() const + { + if (_TexFont) + return (_TexFont->getCacheVersion() << 16) + _TexCacheNr; + + return 0; + } + }; diff --git a/code/nel/include/nel/3d/text_context.h b/code/nel/include/nel/3d/text_context.h index a95916da4..1f75e1184 100644 --- a/code/nel/include/nel/3d/text_context.h +++ b/code/nel/include/nel/3d/text_context.h @@ -150,6 +150,10 @@ public: { nlassert (index < _CacheStrings.size()); CComputedString &rCS = _CacheStrings[index]; + if (rCS.CacheVersion != _FontManager->getCacheVersion()) + { + computeString(rCS.Text, rCS); + } if (_Shaded) { CRGBA bkup = rCS.Color; @@ -184,6 +188,10 @@ public: { nlassert (index < _CacheStrings.size()); CComputedString &rCS = _CacheStrings[index]; + if (rCS.CacheVersion != _FontManager->getCacheVersion()) + { + computeString(rCS.Text, rCS); + } if(_Shaded) { CRGBA bkup = rCS.Color; @@ -218,6 +226,11 @@ public: { nlassert (index < _CacheStrings.size()); CComputedString &rCS = _CacheStrings[index]; + if (rCS.CacheVersion != _FontManager->getCacheVersion()) + { + computeString(rCS.Text, rCS); + } + if (_Shaded) { CRGBA bkup = rCS.Color; diff --git a/code/nel/include/nel/3d/texture_font.h b/code/nel/include/nel/3d/texture_font.h index e743bb137..865615b63 100644 --- a/code/nel/include/nel/3d/texture_font.h +++ b/code/nel/include/nel/3d/texture_font.h @@ -18,6 +18,7 @@ #define NL_TEXTURE_FONT_H #include "nel/misc/types_nl.h" +#include "nel/misc/rect.h" #include "nel/3d/texture.h" namespace NL3D @@ -25,9 +26,6 @@ namespace NL3D class CFontGenerator; -#define TEXTUREFONT_NBCATEGORY 5 // Config 1 -//#define TEXTUREFONT_NBCATEGORY 4 - // **************************************************************************** /** * CTextureFont @@ -37,32 +35,59 @@ class CTextureFont : public ITexture public: - struct SLetterInfo + // Holds info for glyphs rendered on atlas + struct SGlyphInfo { - // To generate the letter - ucchar Char; - CFontGenerator *FontGenerator; + // font atlas info + uint32 CacheVersion; + + // atlas region with padding + uint32 X, Y, W, H; + + // rendered glyph size without padding + uint32 CharWidth; + uint32 CharHeight; + + // UV coords for rendered glyph without padding + float U0, V0, U1, V1; + + uint32 GlyphIndex; sint Size; bool Embolden; bool Oblique; + CFontGenerator *FontGenerator; + SGlyphInfo() + : CacheVersion(0), + U0(0.f), V0(0.f), U1(0.f), V1(0.f), + X(0), Y(0), W(0), H(0), CharWidth(0), CharHeight(0), + GlyphIndex(0), Size(0), Embolden(false), Oblique(false), FontGenerator(NULL) + { + } + }; - // The less recently used infos - SLetterInfo *Next, *Prev; + // Holds info for glyphs displayed on screen + struct SLetterInfo + { + ucchar Char; + sint Size; + bool Embolden; + bool Oblique; + CFontGenerator *FontGenerator; - uint Cat; // 8x8, 16x16, 24x24, 32x32 - - ////////////////////////////////////////////////////////////////////// - - float U ,V; - uint32 CharWidth; - uint32 CharHeight; - uint32 GlyphIndex; // number of the character in the this font + uint32 GlyphIndex; + uint32 CharWidth; // Displayed glyph height + uint32 CharHeight; // Displayed glyph height sint32 Top; // Distance between origin and top of the texture sint32 Left; // Distance between origin and left of the texture sint32 AdvX; // Advance to the next caracter - SLetterInfo():Char(0), FontGenerator(NULL), Size(0), Embolden(false), Oblique(false), Next(NULL), Prev(NULL), Cat(0), CharWidth(0), CharHeight(0), GlyphIndex(0), Top(0), Left(0), AdvX(0) + SGlyphInfo* glyph; + + SLetterInfo() + : Char(0), Size(0), Embolden(false), Oblique(false), FontGenerator(NULL), + GlyphIndex(0), CharWidth(0), CharHeight(0), Top(0), Left(0), AdvX(0), + glyph(NULL) { } }; @@ -70,14 +95,13 @@ public: struct SLetterKey { ucchar Char; - CFontGenerator *FontGenerator; sint Size; bool Embolden; bool Oblique; + CFontGenerator *FontGenerator; + // Does not use FontGenerator in return value uint32 getVal(); - //bool operator < (const SLetterKey&k) const; - //bool operator == (const SLetterKey&k) const; SLetterKey():Char(0), FontGenerator(NULL), Size(0), Embolden(false), Oblique(false) { @@ -96,19 +120,76 @@ public: void doGenerate (bool async = false); // This function manage the cache if the letter wanted does not exist - SLetterInfo* getLetterInfo (SLetterKey& k); + // \param render Set to true if letter is currently visible on screen + SLetterInfo* getLetterInfo (SLetterKey& k, bool render); void dumpTextureFont (const char *filename); + // Version is increased with each rebuild of font atlas + uint32 getCacheVersion() const { return _CacheVersion; } + private: + uint32 _CacheVersion; + + // current texture size + uint32 _TextureSizeX; + uint32 _TextureSizeY; + + // maximum texture size allowed + uint32 _TextureMaxW; + uint32 _TextureMaxH; + + // padding around glyphs + uint8 _PaddingL, _PaddingT; + uint8 _PaddingR, _PaddingB; // To find a letter in the texture - std::map Accel; - std::vector Letters[TEXTUREFONT_NBCATEGORY]; - SLetterInfo *Front[TEXTUREFONT_NBCATEGORY], *Back[TEXTUREFONT_NBCATEGORY]; + // Keep track of available space in main texture + std::vector _AtlasNodes; - void rebuildLetter (sint cat, sint x, sint y); + std::vector _Letters; + + // lookup letter from letter cache or create new + SLetterInfo* findLetter(SLetterKey& k, bool insert); + + // lower/upper bound of glyphs to render, sizes outside are scaled bitmaps + uint _MinGlyphSize; + uint _MaxGlyphSize; + // start using size stem from this font size + uint _GlyphSizeStepMin; + // every n'th font size is rendered, intermediates are using bitmap scaling + uint _GlyphSizeStep; + + // rendered glyph cache + std::list _GlyphCache; + SGlyphInfo* findLetterGlyph(SLetterInfo *letter, bool insert); + + // render letter glyph into glyph cache + SGlyphInfo* renderLetterGlyph(SLetterInfo *letter, uint32 bitmapFontSize); + + // copy glyph bitmap into texture and invalidate that region + void copyGlyphBitmap(uint8* bitmap, uint32 bitmapW, uint32 bitmapH, uint32 atlasX, uint32 atlasY); + + // Find best fit for WxH rect in atlas + uint fitRegion(uint index, uint width, uint height); + + // Return top/left from font texture or false if there is no more room + bool reserveAtlas(const uint32 width, const uint32 height, uint32 &x, uint32 &y); + + // repack glyphs, resize texture, and invalidate unused glyphs. + void repackAtlas(); + void repackAtlas(uint32 width, uint32 height); + + // resize texture, + bool resizeAtlas(); + + // remove all glyphs from atlas, clear glyph cache, letter info is kept + void clearAtlas(); + + // if return true: newW, newH contains next size font atlas should be resized + // if return false: _TextureMaxW and _TextureMaxH is reached + bool getNextTextureSize(uint32 &newW, uint32 &newH) const; /// Todo: serialize a font texture. public: diff --git a/code/nel/src/3d/font_generator.cpp b/code/nel/src/3d/font_generator.cpp index f50b61c5b..b0e917b6f 100644 --- a/code/nel/src/3d/font_generator.cpp +++ b/code/nel/src/3d/font_generator.cpp @@ -81,6 +81,11 @@ const char *CFontGenerator::getFT2Error(FT_Error fte) return ukn; } +std::string CFontGenerator::getFontFileName() const +{ + return _FontFileName; +} + CFontGenerator *newCFontGenerator(const std::string &fontFileName) { return new CFontGenerator(fontFileName); diff --git a/code/nel/src/3d/font_manager.cpp b/code/nel/src/3d/font_manager.cpp index 3b77a2100..d00ebb8f5 100644 --- a/code/nel/src/3d/font_manager.cpp +++ b/code/nel/src/3d/font_manager.cpp @@ -46,6 +46,7 @@ CMaterial* CFontManager::getFontMaterial() if (_TexFont == NULL) { _TexFont = new CTextureFont; + _TexCacheNr++; } if (_MatFont == NULL) @@ -142,11 +143,17 @@ void CFontManager::computeString (const ucstring &s, sint32 nMaxZ = -1000000, nMinZ = 1000000; output.StringHeight = 0; + // save string info for later rebuild as needed + output.Text = s; + output.CacheVersion = getCacheVersion(); + uint j = 0; { CVertexBufferReadWrite vba; output.Vertices.lock (vba); + hlfPixScrW = 0.f; + hlfPixScrH = 0.f; // For all chars for (uint i = 0; i < s.size(); i++) @@ -157,38 +164,43 @@ void CFontManager::computeString (const ucstring &s, k.Size = fontSize; k.Embolden = embolden; k.Oblique = oblique; - CTextureFont::SLetterInfo *pLI = pTexFont->getLetterInfo (k); + // render letter + CTextureFont::SLetterInfo *pLI = pTexFont->getLetterInfo (k, true); if(pLI != NULL) { - if ((pLI->CharWidth > 0) && (pLI->CharHeight > 0)) + if (pLI->glyph) { + // If letter is heavily upscaled, then there is noticeable clipping on edges + // fixing UV will make it bit better + if ((pLI->Size >> 1) > pLI->glyph->Size) + { + hlfPixTexW = 0.5f * TexRatioW; + hlfPixTexH = 0.5f * TexRatioH; + } + // Creating vertices dx = pLI->Left; - dz = -((sint32)pLI->CharHeight-(sint32)(pLI->Top)); - u1 = pLI->U - hlfPixTexW; - v1 = pLI->V - hlfPixTexH; - u2 = pLI->U + ((float)pLI->CharWidth) * TexRatioW + hlfPixTexW; - v2 = pLI->V + ((float)pLI->CharHeight) * TexRatioH + hlfPixTexH; + dz = -((sint32)pLI->CharHeight - (sint32)(pLI->Top)); x1 = (penx + dx) - hlfPixScrW; z1 = (penz + dz) - hlfPixScrH; - x2 = (penx + dx + (sint32)pLI->CharWidth) + hlfPixScrW; + x2 = (penx + dx + (sint32)pLI->CharWidth) + hlfPixScrW; z2 = (penz + dz + (sint32)pLI->CharHeight) + hlfPixScrH; vba.setVertexCoord (j, x1, 0, z1); - vba.setTexCoord (j, 0, u1, v2); + vba.setTexCoord (j, 0, pLI->glyph->U0-hlfPixTexW, pLI->glyph->V1+hlfPixTexH); ++j; vba.setVertexCoord (j, x2, 0, z1); - vba.setTexCoord (j, 0, u2, v2); + vba.setTexCoord (j, 0, pLI->glyph->U1+hlfPixTexW, pLI->glyph->V1+hlfPixTexH); ++j; vba.setVertexCoord (j, x2, 0, z2); - vba.setTexCoord (j, 0, u2, v1); + vba.setTexCoord (j, 0, pLI->glyph->U1+hlfPixTexW, pLI->glyph->V0-hlfPixTexH); ++j; vba.setVertexCoord (j, x1, 0, z2); - vba.setTexCoord (j, 0, u1, v1); + vba.setTexCoord (j, 0, pLI->glyph->U0-hlfPixTexW, pLI->glyph->V0-hlfPixTexH); ++j; // String Bound @@ -245,6 +257,19 @@ void CFontManager::computeStringInfo ( const ucstring &s, { output.Color = color; + // save string info for later rebuild as needed + output.Text = s; + output.CacheVersion = 0; + + if (s.empty()) + { + output.StringWidth = 0.f; + output.StringHeight = 0; + output.StringLine = 0; + + return; + } + // resize fontSize if window not of 800x600. if (keep800x600Ratio) { @@ -273,7 +298,7 @@ void CFontManager::computeStringInfo ( const ucstring &s, k.Size = fontSize; k.Embolden = embolden; k.Oblique = oblique; - pLI = pTexFont->getLetterInfo (k); + pLI = pTexFont->getLetterInfo (k, false); if(pLI != NULL) { if ((pLI->CharWidth > 0) && (pLI->CharHeight > 0)) @@ -318,7 +343,11 @@ void CFontManager::invalidate() { if (_TexFont) _TexFont = NULL; + _TexFont = new CTextureFont; + _TexCacheNr++; + + getFontMaterial()->setTexture(0, _TexFont); } diff --git a/code/nel/src/3d/text_context.cpp b/code/nel/src/3d/text_context.cpp index 5fcc43daa..235ea9fcb 100644 --- a/code/nel/src/3d/text_context.cpp +++ b/code/nel/src/3d/text_context.cpp @@ -74,25 +74,9 @@ uint32 CTextContext::textPush (const char *format, ...) char *str; NLMISC_CONVERT_VARGS (str, format, NLMISC::MaxCStringSize); - if (_CacheNbFreePlaces == 0) - { - CComputedString csTmp; - - _CacheStrings.push_back (csTmp); - if (_CacheFreePlaces.empty()) - _CacheFreePlaces.resize (1); - _CacheFreePlaces[0] = (uint32)_CacheStrings.size()-1; - _CacheNbFreePlaces = 1; - } - - // compute the string. - uint32 index = _CacheFreePlaces[_CacheNbFreePlaces-1]; - CComputedString &strToFill = _CacheStrings[index]; - _FontManager->computeString (str, _FontGen, _Color, _FontSize, _Embolden, _Oblique, _Driver, strToFill, _Keep800x600Ratio); - - _CacheNbFreePlaces--; - - return index; + ucstring uc; + uc.fromUtf8((const char *)str); + return textPush(uc); } // ------------------------------------------------------------------------------------------------ @@ -115,8 +99,10 @@ uint32 CTextContext::textPush (const ucstring &str) uint32 index = _CacheFreePlaces[_CacheNbFreePlaces-1]; nlassert (index < _CacheStrings.size()); CComputedString &strToFill = _CacheStrings[index]; - _FontManager->computeString (str, _FontGen, _Color - , _FontSize, _Embolden, _Oblique, _Driver, strToFill, _Keep800x600Ratio); + + _FontManager->computeString (str, _FontGen, _Color, _FontSize, _Embolden, _Oblique, _Driver, strToFill, _Keep800x600Ratio); + // just compute letters, glyphs are rendered on demand before first draw + //_FontManager->computeStringInfo(str, _FontGen, _Color, _FontSize, _Embolden, _Oblique, _Driver, strToFill, _Keep800x600Ratio); _CacheNbFreePlaces--; diff --git a/code/nel/src/3d/texture_font.cpp b/code/nel/src/3d/texture_font.cpp index cf46d025d..eac8cb545 100644 --- a/code/nel/src/3d/texture_font.cpp +++ b/code/nel/src/3d/texture_font.cpp @@ -23,7 +23,7 @@ #include "nel/misc/common.h" #include "nel/misc/rect.h" #include "nel/misc/file.h" - +#include "nel/misc/path.h" using namespace std; using namespace NLMISC; @@ -35,37 +35,14 @@ using namespace NLMISC; namespace NL3D { -// Config 1 -const int TextureSizeX = 1024; -const int TextureSizeY = 1024; // If change this value -> change NbLine too -const int Categories[TEXTUREFONT_NBCATEGORY] = { 8, 16, 24, 32, 64 }; -const int NbLine[TEXTUREFONT_NBCATEGORY] = { 8, 24, 16, 4, 1 }; // Based on textsize - -/* -const int TextureSizeX = 256; -const int TextureSizeY = 256; -const int Categories[TEXTUREFONT_NBCATEGORY] = { 8, 16, 24, 32 }; -const int NbLine[TEXTUREFONT_NBCATEGORY] = { 4, 6, 4, 1 }; // Based on textsize -*/ - -// --------------------------------------------------------------------------- -inline uint32 CTextureFont::SLetterKey::getVal() -{ - // this limits Size to 6bits - // Large sizes already render wrong when many - // different glyphs are used due to limited texture atlas - uint8 eb = ((uint)Embolden) + ((uint)Oblique << 1); - if (FontGenerator == NULL) - return Char + ((Size&255)<<16) + (eb << 22); - else - return Char + ((Size&255)<<16) + (eb << 22) + ((FontGenerator->getUID()&0xFF)<<24); -} - // --------------------------------------------------------------------------- CTextureFont::CTextureFont() + : _CacheVersion(1), + _TextureSizeX(512), _TextureSizeY(512), _TextureMaxW(4096), _TextureMaxH(4096), + _PaddingL(0), _PaddingT(0), _PaddingR(1), _PaddingB(1), + _MinGlyphSize(5), _MaxGlyphSize(200), + _GlyphSizeStepMin(50), _GlyphSizeStep(5) { - uint i; - setFilterMode (ITexture::Linear, ITexture::LinearMipMapOff); setWrapS (ITexture::Repeat); @@ -75,53 +52,9 @@ CTextureFont::CTextureFont() setReleasable (false); - resize (TextureSizeX, TextureSizeY, CBitmap::Alpha); - for(i = 0; i < TextureSizeX*TextureSizeY; ++i) - getPixels()[i] = 0; - // convertToType (CBitmap::Alpha); + resize (_TextureSizeX, _TextureSizeY, CBitmap::Alpha, true); - sint posY = 0; - - for(i = 0; i < TEXTUREFONT_NBCATEGORY; ++i) - { - // Number of chars per cache - Letters[i].resize ((TextureSizeX/Categories[i])*NbLine[i]); - - for(uint32 j = 0; j < Letters[i].size(); ++j) - { - SLetterInfo &rLetter = Letters[i][j]; - rLetter.Char = 0xffff; - rLetter.FontGenerator = NULL; - rLetter.Size= 0; - rLetter.Embolden = false; - rLetter.Oblique = false; - - // The less recently used infos - if (j < Letters[i].size()-1) - rLetter.Next = &Letters[i][j+1]; - else - rLetter.Next = NULL; - - if (j > 0) - rLetter.Prev = &Letters[i][j-1]; - else - rLetter.Prev = NULL; - - rLetter.Cat = i; - - sint sizeX = TextureSizeX/Categories[i]; - rLetter.U = (Categories[i]*(j%sizeX)) / ((float)TextureSizeX); - rLetter.V = (posY + Categories[i]*((sint)(j/sizeX))) / ((float)TextureSizeY); - - ///////////////////////////////////////////////// - - rLetter.CharWidth = rLetter.CharHeight = 0; - rLetter.GlyphIndex = rLetter.Top = rLetter.Left = rLetter.AdvX = 0; - } - Front[i] = &Letters[i][0]; - Back[i] = &Letters[i][Letters[i].size()-1]; - posY += NbLine[i] * Categories[i]; - } + _AtlasNodes.push_back(CRect(0, 0, _TextureSizeX, _TextureSizeY)); } @@ -129,17 +62,16 @@ CTextureFont::~CTextureFont() { } - // --------------------------------------------------------------------------- void CTextureFont::dumpTextureFont(const char *filename) { CBitmap b; COFile f( filename ); - b.resize (TextureSizeX, TextureSizeY, CBitmap::RGBA); + b.resize (_TextureSizeX, _TextureSizeY, CBitmap::RGBA); CObjectVector&bits = b.getPixels(); CObjectVector&src = getPixels(); - for (uint i = 0; i < (TextureSizeX*TextureSizeY); ++i) + for (uint i = 0; i < (_TextureSizeX*_TextureSizeY); ++i) { bits[i*4+0] = bits[i*4+1] = bits[i*4+2] = bits[i*4+3] = src[i]; } @@ -147,242 +79,468 @@ void CTextureFont::dumpTextureFont(const char *filename) b.writeTGA (f, 32); } +// --------------------------------------------------------------------------- +bool CTextureFont::getNextTextureSize(uint32 &newW, uint32 &newH) const +{ + // width will be resized first (256x256 -> 512x256) + if (_TextureSizeX <= _TextureSizeY) + { + newW = _TextureSizeX * 2; + newH = _TextureSizeY; + } + else + { + newW = _TextureSizeX; + newH = _TextureSizeY * 2; + } + + // no more room + return newW <= _TextureMaxW && newH <= _TextureMaxH; +} // --------------------------------------------------------------------------- -// cat : categories where the letter is -// x : pos x of the letter -// y : pos y of the letter -void CTextureFont::rebuildLetter (sint cat, sint x, sint y) +// out of room, clear everything and rebuild glyphs on demand +// note: text will display wrong until glyphs get rendered again +void CTextureFont::clearAtlas() { - sint sizex = TextureSizeX / Categories[cat]; - sint index = x + y*sizex; - SLetterInfo &rLetter = Letters[cat][index]; + nlwarning("Glyph cache will be cleared."); - if (rLetter.FontGenerator == NULL) - return; + _AtlasNodes.clear(); + _AtlasNodes.push_back(CRect(0, 0, _TextureSizeX, _TextureSizeY)); - sint catTopY = 0; - sint c = 0; - while (c < cat) + // clear texture + _Data[0].fill(0); + + // clear glyph cache + for(uint i = 0; i< _Letters.size(); ++i) { - catTopY += NbLine[c] * Categories[c]; - ++c; + _Letters[i].glyph = NULL; } - // Destination position in pixel of the letter - sint posx = x * Categories[cat]; - sint posy = catTopY + y * Categories[cat]; + _GlyphCache.clear(); - uint32 pitch = 0; - uint8 *bitmap = rLetter.FontGenerator->getBitmap ( rLetter.Char, rLetter.Size, rLetter.Embolden, rLetter.Oblique, - rLetter.CharWidth, rLetter.CharHeight, - pitch, rLetter.Left, rLetter.Top, - rLetter.AdvX, rLetter.GlyphIndex ); + _CacheVersion++; - // Copy FreeType buffer - uint i; - for (i = 0; i < rLetter.CharHeight; ++i) + touch(); +} + +// --------------------------------------------------------------------------- +void CTextureFont::repackAtlas() +{ + repackAtlas(_TextureSizeX, _TextureSizeY); +} + +// --------------------------------------------------------------------------- +// backup old glyphs and move them to newly resized texture +// new atlas will be sorted if _GlyphCache is +void CTextureFont::repackAtlas(uint32 newW, uint32 newH) +{ + uint32 newCacheVersion = _CacheVersion+1; + + CBitmap btm; + uint32 oldW, oldH; + + oldW = _TextureSizeX; + oldH = _TextureSizeY; + btm.resize(oldW, oldH, CBitmap::Alpha, true); + btm.blit(this, 0, 0); + + // resize texture + if (_TextureSizeX != newW || _TextureSizeY != newH) { - uint8 *pDst = &_Data[0][posx + (posy+i)*TextureSizeY]; - uint8 *pSrc = &bitmap[i*pitch]; - for (uint j = 0; j < rLetter.CharWidth; ++j) + _TextureSizeX = newW; + _TextureSizeY = newH; + resize (_TextureSizeX, _TextureSizeY, CBitmap::Alpha, true); + } + else + { + _Data[0].fill(0); + } + + // release atlas and rebuild + _AtlasNodes.clear(); + _AtlasNodes.push_back(CRect(0, 0, _TextureSizeX, _TextureSizeY)); + + CObjectVector&src = btm.getPixels(); + for(std::list::iterator it = _GlyphCache.begin(); it != _GlyphCache.end(); ++it) + { + if (it->CacheVersion != _CacheVersion) { - *pDst = *pSrc; - ++pDst; - ++pSrc; + // TODO: must remove glyph from all letters before removing glyph from cache + //continue; + } + + SGlyphInfo &glyph = *it; + + glyph.CacheVersion = newCacheVersion; + + uint32 atlasX, atlasY; + if (reserveAtlas(glyph.W, glyph.H, atlasX, atlasY)) + { + for (uint y = 0; y < glyph.H; ++y) + { + uint8 *pDst = &_Data[0][(atlasY + y) * _TextureSizeX + atlasX]; + for (uint x = 0; x < glyph.W; ++x) + { + *pDst = src[(glyph.Y + y) * oldW + glyph.X + x]; + ++pDst; + } + } + + // TODO: dup code with renderGlyph + glyph.U0 = (atlasX+_PaddingL) / (float)_TextureSizeX; + glyph.V0 = (atlasY+_PaddingT) / (float)_TextureSizeY; + glyph.U1 = (atlasX+_PaddingL+glyph.CharWidth) / (float)_TextureSizeX; + glyph.V1 = (atlasY+_PaddingT+glyph.CharHeight) / (float)_TextureSizeY; + + glyph.X = atlasX; + glyph.Y = atlasY; } } - // Black border bottom and right - for (i = 0; i < rLetter.CharHeight+1; ++i) + _CacheVersion = newCacheVersion; + + // invalidate full texture + touch(); +} + +// --------------------------------------------------------------------------- +bool CTextureFont::resizeAtlas() +{ + uint32 newW, newH; + if (!getNextTextureSize(newW, newH)) { - _Data[0][posx + rLetter.CharWidth + (posy+i)*TextureSizeY] = 0; + nlwarning("Font texture at maximum (%d,%d). Resize failed.", _TextureSizeX, _TextureSizeY); + return false; } - for (i = 0; i < rLetter.CharWidth+1; ++i) - { - _Data[0][posx + i + (posy+rLetter.CharHeight)*TextureSizeY] = 0; - } - - /* - dumpTextureFont (this); - int a = 5; - a++; - */ + // resize and redraw + repackAtlas(newW, newH); + return true; } // --------------------------------------------------------------------------- void CTextureFont::doGenerate(bool async) { - // Rectangle invalidate ? - if (_ListInvalidRect.begin()!=_ListInvalidRect.end()) - { - // Yes, rebuild only those rectangles. - - // For each rectangle to compute - std::list::iterator ite=_ListInvalidRect.begin(); - while (ite!=_ListInvalidRect.end()) - { - // Compute rectangle coordinates - sint x = ite->left(); - sint y = ite->bottom(); - - // Look in which category is the rectangle - sint cat = 0; - sint catTopY = 0; - sint catBotY = NbLine[cat] * Categories[cat]; - while (y > catBotY) - { - if (y < catBotY) - break; - ++cat; - nlassert (cat < TEXTUREFONT_NBCATEGORY); - catTopY = catBotY; - catBotY += NbLine[cat] * Categories[cat]; - } - - x = x / Categories[cat]; - y = ite->top(); - y = y - catTopY; - y = y / Categories[cat]; - - rebuildLetter (cat, x, y); - - // Next rectangle - ite++; - } - } - else - { - for(int cat = 0; cat < TEXTUREFONT_NBCATEGORY; ++cat) - { - sint sizex = TextureSizeX / Categories[cat]; - sint sizey = NbLine[cat]; - for (sint y = 0; y < sizey; y++) - for (sint x = 0; x < sizex; x++) - { - rebuildLetter (cat, x, y); - } - } - } -/* - dumpTextureFont (this); - int a = 5; -*/ + /* + nlinfo("doGenerate: Letters(%d/%d), Glyphs(%d/%d)\n", _Letters.size(), _Letters.size() * sizeof(SLetterInfo), + _GlyphCache.size(), _GlyphCache.size() * sizeof(SGlyphInfo)); + //std::string fname = CFile::findNewFile("/tmp/font-texture.tga"); + std::string fname = toString("/tmp/font-texture-%p-%03d.tga", this, _CacheVersion); + dumpTextureFont (fname.c_str()); + */ } // --------------------------------------------------------------------------- -CTextureFont::SLetterInfo* CTextureFont::getLetterInfo (SLetterKey& k) +uint CTextureFont::fitRegion(uint index, uint width, uint height) { - sint cat; - uint32 nTmp = k.getVal(); - map::iterator itAccel = Accel.find (nTmp); - if (itAccel != Accel.end()) + if (_AtlasNodes[index].X + width > _TextureSizeX - 1) { - // Put it in the first place - SLetterInfo *pLetterToMove = itAccel->second; - cat = pLetterToMove->Cat; - if (pLetterToMove != Front[cat]) + return -1; + } + + uint x = _AtlasNodes[index].X; + uint y = _AtlasNodes[index].Y; + sint widthLeft = width; + + while(widthLeft > 0) + { + if (_AtlasNodes[index].Y > y) { - // unlink - nlassert(pLetterToMove->Prev); - pLetterToMove->Prev->Next = pLetterToMove->Next; - if (pLetterToMove == Back[cat]) - { - Back[cat] = pLetterToMove->Prev; - } - else - { - pLetterToMove->Next->Prev = pLetterToMove->Prev; - } - - // link to front - pLetterToMove->Prev = NULL; - pLetterToMove->Next = Front[cat]; - Front[cat]->Prev = pLetterToMove; - Front[cat] = pLetterToMove; + y = _AtlasNodes[index].Y; } - return pLetterToMove; + + // _AtlasNodes[0] for margin is not used here + if (_AtlasNodes[index].Y + height > _TextureSizeY - 1) + { + return -1; + } + + widthLeft -= _AtlasNodes[index].Width; + index++; } - // The letter is not already present - // Found the category of the new letter - uint32 width, height; + return y; +} - //k.FontGenerator->getSizes (k.Char, k.Size, width, height); - // \todo mat : Temp !!! Try to use freetype cache - uint32 nPitch, nGlyphIndex; - sint32 nLeft, nTop, nAdvX; - k.FontGenerator->getBitmap (k.Char, k.Size, k.Embolden, k.Oblique, width, height, nPitch, nLeft, nTop, - nAdvX, nGlyphIndex ); +bool CTextureFont::reserveAtlas(const uint32 width, const uint32 height, uint32 &x, uint32 &y) +{ + if (_AtlasNodes.empty()) + { + nlwarning("No available space in texture atlas (_AtlasNodes.empty() == true)"); + return false; + } - // Add 1 pixel space for black border to get correct category - cat = 0; - if (((sint)width+1 > Categories[TEXTUREFONT_NBCATEGORY-1]) || - ((sint)height+1 > Categories[TEXTUREFONT_NBCATEGORY-1])) + x = 0; + y = 0; + + sint bestIndex = -1; + sint bestWidth = _TextureSizeX; + sint bestHeight = _TextureSizeY; + + sint selY=0; + + for (uint i = 0; i < _AtlasNodes.size(); ++i) + { + selY = fitRegion(i, width, height); + if (selY >=0) + { + if (((selY + height) < bestHeight) || ((selY + height) == bestHeight && _AtlasNodes[i].Width > 0 && _AtlasNodes[i].Width < bestWidth)) + { + bestHeight = selY + height; + bestIndex = i; + bestWidth = _AtlasNodes[i].Width; + x = _AtlasNodes[i].X; + y = selY; + } + } + } + + if (bestIndex == -1) + { + x = 0; + y = 0; + return false; + } + + CRect r(x, y + height, width, 0); + _AtlasNodes.insert(_AtlasNodes.begin() + bestIndex, r); + + // shrink or remove nodes overlaping with newly inserted node + for(uint i = bestIndex+1; i< _AtlasNodes.size(); i++) + { + if (_AtlasNodes[i].X < (_AtlasNodes[i-1].X + _AtlasNodes[i-1].Width)) + { + sint shrink = _AtlasNodes[i-1].X + _AtlasNodes[i-1].Width - _AtlasNodes[i].X; + _AtlasNodes[i].X += shrink; + if (_AtlasNodes[i].Width > shrink) + { + _AtlasNodes[i].Width -= shrink; + break; + } + _AtlasNodes.erase(_AtlasNodes.begin() + i); + i--; + } + else break; + } + + // merge nearby nodes from same row + for(uint i = 0; i < _AtlasNodes.size() - 1; i++) + { + if (_AtlasNodes[i].Y == _AtlasNodes[i+1].Y) + { + _AtlasNodes[i].Width += _AtlasNodes[i+1].Width; + _AtlasNodes.erase(_AtlasNodes.begin() + i + 1); + i--; + } + } + + return true; +} + +// --------------------------------------------------------------------------- +// bitmap : texture data +// bitmapW : bitmap width +// bitmapH : bitmap height +// atlasX : pos x in font texture +// atlasY : pos y in font texture +void CTextureFont::copyGlyphBitmap(uint8* bitmap, uint32 bitmapW, uint32 bitmapH, uint32 atlasX, uint32 atlasY) +{ + for (uint bY = 0; bY < bitmapH; ++bY) + { + uint8 *pDst = &_Data[0][(atlasY+_PaddingT+bY) * _TextureSizeX+atlasX+_PaddingL]; + for (uint bX = 0; bX < bitmapW; ++bX) + { + *pDst = bitmap[bY * bitmapW+bX]; + ++pDst; + } + } + + if (_PaddingR > 0 || _PaddingB > 0 || _PaddingL > 0 || _PaddingT > 0) + { + for(uint i = 0; i<(bitmapH+_PaddingT+_PaddingB); ++i) + { + if (_PaddingT > 0) _Data[0][(atlasY + i) * _TextureSizeX + atlasX ] = 0; + if (_PaddingB > 0) _Data[0][(atlasY + i) * _TextureSizeX + atlasX + _PaddingL + bitmapW] = 0; + } + + for (uint i = 0; i<(bitmapW+_PaddingL+_PaddingR); ++i) + { + if (_PaddingL > 0) _Data[0][atlasY * _TextureSizeX + atlasX + i] = 0; + if (_PaddingB > 0) _Data[0][(atlasY + _PaddingT + bitmapH) * _TextureSizeX + atlasX + i] = 0; + } + } + + CRect r(atlasX, atlasY, bitmapW + _PaddingL + _PaddingR, bitmapH + _PaddingT + _PaddingB); + touchRect(r); +} + + +// --------------------------------------------------------------------------- +CTextureFont::SGlyphInfo* CTextureFont::renderLetterGlyph(SLetterInfo *letter, uint bitmapFontSize) +{ + uint32 nPitch; + sint32 left; + sint32 top; + sint32 advx; + uint32 charWidth; + uint32 charHeight; + uint32 glyphIndex; + + uint8 *bitmap = letter->FontGenerator->getBitmap (letter->Char, bitmapFontSize, letter->Embolden, letter->Oblique, + charWidth, charHeight, + nPitch, left, top, + advx, glyphIndex ); + + uint32 atlasX, atlasY; + uint32 rectW, rectH; + rectW = charWidth + _PaddingL + _PaddingR; + rectH = charHeight + _PaddingT + _PaddingB; + + if (!reserveAtlas(rectW, rectH, atlasX, atlasY)) + { + // no room return NULL; + } + copyGlyphBitmap(bitmap, charWidth, charHeight, atlasX, atlasY); - while (((sint)width+1 > Categories[cat]) || ((sint)height+1 > Categories[cat])) + SGlyphInfo* glyphInfo = NULL; { - ++cat; - nlassert (cat != TEXTUREFONT_NBCATEGORY); + // keep cache sorted by height (smaller first) + std::list::iterator it = _GlyphCache.begin(); + while(it != _GlyphCache.end() && it->CharHeight < charHeight) + { + ++it; + } + + it = _GlyphCache.insert(it, SGlyphInfo()); + glyphInfo = &(*it); } - // And replace the less recently used letter - SLetterKey k2; - k2.Char = Back[cat]->Char; - k2.FontGenerator = Back[cat]->FontGenerator; - k2.Size = Back[cat]->Size; - k2.Embolden = Back[cat]->Embolden; - k2.Oblique = Back[cat]->Oblique; + glyphInfo->GlyphIndex = glyphIndex; + glyphInfo->Size = bitmapFontSize; + glyphInfo->Embolden = letter->Embolden; + glyphInfo->Oblique = letter->Oblique; + glyphInfo->FontGenerator = letter->FontGenerator; + glyphInfo->CacheVersion = _CacheVersion; - itAccel = Accel.find (k2.getVal()); - if (itAccel != Accel.end()) + glyphInfo->U0 = (atlasX+_PaddingL) / (float)_TextureSizeX; + glyphInfo->V0 = (atlasY+_PaddingT) / (float)_TextureSizeY; + glyphInfo->U1 = (atlasX+_PaddingL+charWidth) / (float)_TextureSizeX; + glyphInfo->V1 = (atlasY+_PaddingT+charHeight) / (float)_TextureSizeY; + + glyphInfo->CharWidth = charWidth; + glyphInfo->CharHeight = charHeight; + + glyphInfo->X = atlasX; + glyphInfo->Y = atlasY; + glyphInfo->W = rectW; + glyphInfo->H = rectH; + + return glyphInfo; +} + + +// --------------------------------------------------------------------------- +CTextureFont::SGlyphInfo* CTextureFont::findLetterGlyph(SLetterInfo *letter, bool insert) +{ + uint bitmapFontSize = max((sint)_MinGlyphSize, min((sint)_MaxGlyphSize, letter->Size)); + if (_GlyphSizeStep > 1 && bitmapFontSize > _GlyphSizeStepMin) { - Accel.erase (itAccel); + uint size = (bitmapFontSize / _GlyphSizeStep) * _GlyphSizeStep; } - SLetterInfo *NewBack = Back[cat]->Prev; - NewBack->Next = NULL; - Back[cat]->Cat = cat; - Back[cat]->Char = k.Char; - Back[cat]->FontGenerator = k.FontGenerator; - Back[cat]->Size = k.Size; - Back[cat]->Embolden = k.Embolden; - Back[cat]->Oblique = k.Oblique; - Back[cat]->CharWidth = width; - Back[cat]->CharHeight = height; - Back[cat]->Top = nTop; - Back[cat]->Left = nLeft; - Back[cat]->AdvX = nAdvX; - Back[cat]->Prev = NULL; - Back[cat]->Next = Front[cat]; - Front[cat]->Prev = Back[cat]; - Front[cat] = Back[cat]; - Back[cat] = NewBack; - - Accel.insert (map::value_type(k.getVal(),Front[cat])); - - // Invalidate the zone - sint index = (sint)(Front[cat] - &Letters[cat][0]);// / sizeof (SLetterInfo); - sint sizex = TextureSizeX / Categories[cat]; - sint x = index % sizex; - sint y = index / sizex; - x = x * Categories[cat]; - y = y * Categories[cat]; - - sint c = 0; - while (c < cat) + // CacheVersion not checked, all glyphs in cache must be rendered on texture + for(std::list::iterator it = _GlyphCache.begin(); it != _GlyphCache.end(); ++it) { - y = y + NbLine[c] * Categories[c]; - ++c; + if (it->GlyphIndex == letter->GlyphIndex && + it->Size == bitmapFontSize && + it->Embolden == letter->Embolden && + it->Oblique == letter->Oblique && + it->FontGenerator == letter->FontGenerator) + { + return &(*it); + } } - // must update the char, WITH the black borders - CRect r (x, y, width+1, height+1); + if (insert) + { + return renderLetterGlyph(letter, bitmapFontSize); + } - touchRect (r); + return NULL; +} - return Front[cat]; +// --------------------------------------------------------------------------- +CTextureFont::SLetterInfo* CTextureFont::findLetter(SLetterKey &k, bool insert) +{ + // TODO: use std::map + for(uint i = 0; i < _Letters.size(); ++i) + { + if (_Letters[i].Char == k.Char && _Letters[i].Size == k.Size && + _Letters[i].Embolden == k.Embolden && _Letters[i].Oblique == k.Oblique && + _Letters[i].FontGenerator == k.FontGenerator) + { + return &_Letters[i]; + } + } + + if (insert) + { + _Letters.push_back(SLetterInfo()); + SLetterInfo* letter = &_Letters.back(); + + // get metrics for requested size + letter->Char = k.Char; + letter->Size = k.Size; + letter->Embolden = k.Embolden; + letter->Oblique = k.Oblique; + letter->FontGenerator = k.FontGenerator; + + uint32 nPitch; + letter->FontGenerator->getBitmap(letter->Char, letter->Size, letter->Embolden, letter->Oblique, + letter->CharWidth, letter->CharHeight, + nPitch, letter->Left, letter->Top, + letter->AdvX, letter->GlyphIndex ); + + return letter; + } + + return NULL; +} + +// --------------------------------------------------------------------------- +CTextureFont::SLetterInfo* CTextureFont::getLetterInfo (SLetterKey& k, bool render) +{ + // find already cached letter or create new one + SLetterInfo* letter = findLetter(k, true); + // letter not found (=NULL) or render not requested + if (!letter || !render) return letter; + + if (!letter->glyph || letter->glyph->CacheVersion != _CacheVersion) + { + // render glyph + letter->glyph = findLetterGlyph(letter, true); + if (letter->glyph == NULL) + { + // resize/repack and try again + if (!resizeAtlas()) repackAtlas(); + + letter->glyph = findLetterGlyph(letter, true); + if (letter->glyph == NULL) + { + // make room by clearing all glyphs and reduce max size for glyphs + clearAtlas(); + if (_MaxGlyphSize > _MinGlyphSize) + { + _MaxGlyphSize = max(_MinGlyphSize, _MaxGlyphSize - 10); + } + + letter->glyph = findLetterGlyph(letter, true); + } + } + } + + return letter; } } // NL3D diff --git a/code/ryzom/client/src/main_loop.cpp b/code/ryzom/client/src/main_loop.cpp index 4657f1dff..32b76391b 100644 --- a/code/ryzom/client/src/main_loop.cpp +++ b/code/ryzom/client/src/main_loop.cpp @@ -3419,6 +3419,22 @@ void displayDebugClusters() } +NLMISC_COMMAND(dumpFontTexture, "Write font texture to file", "") +{ + CInterfaceManager *im = CInterfaceManager::getInstance(); + if (TextContext) + { + std::string fname = CFile::findNewFile("font-texture.tga"); + TextContext->dumpCacheTexture(fname.c_str()); + im->displaySystemInfo(ucstring(fname + " created"), "SYS"); + } + else + { + im->displaySystemInfo(ucstring("Error: TextContext == NULL"), "SYS"); + } + return true; +} + // *************************************************************************** void inGamePatchUncompleteWarning() From 06ab5b5f16edf05a52f9c84c745391bb1ec54922 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sun, 18 Nov 2018 17:17:56 +0200 Subject: [PATCH 039/108] Fixed: Invalid LineMaxW with scaling --HG-- branch : experimental-ui-scaling --- code/nel/src/gui/view_text.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/code/nel/src/gui/view_text.cpp b/code/nel/src/gui/view_text.cpp index 4355eb647..7ac1bf8a4 100644 --- a/code/nel/src/gui/view_text.cpp +++ b/code/nel/src/gui/view_text.cpp @@ -1412,7 +1412,6 @@ namespace NLGUI // *************************************************************************** void CViewText::setLineMaxW (sint nMaxW, bool invalidate) { - nMaxW *= _Scale; if(_LineMaxW!=nMaxW) { _LineMaxW = nMaxW; From 2877ece3c67a43ac9d47135ad55a8685bd39d860 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sun, 18 Nov 2018 21:33:05 +0200 Subject: [PATCH 040/108] Changed: Add right click copy-to-clipboard action to chat --HG-- branch : develop --- code/ryzom/client/src/interface_v3/chat_text_manager.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/code/ryzom/client/src/interface_v3/chat_text_manager.cpp b/code/ryzom/client/src/interface_v3/chat_text_manager.cpp index a9a682bf1..ae76eefcb 100644 --- a/code/ryzom/client/src/interface_v3/chat_text_manager.cpp +++ b/code/ryzom/client/src/interface_v3/chat_text_manager.cpp @@ -402,6 +402,10 @@ CViewBase *CChatTextManager::createMsgTextComplex(const ucstring &msg, NLMISC::C para->setSizeRef("w"); para->setResizeFromChildH(true); + // use right click because left click might be used to activate chat window + para->setRightClickHandler("copy_to_clipboard"); + para->setRightClickHandlerParams(msg.toUtf8()); + if (plaintext) { CViewBase *vt = createMsgTextSimple(msg, col, justified, NULL); From 8e53cd87e5dfa68f5bee8b1363058b8fc1004488 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Mon, 19 Nov 2018 11:26:31 +0200 Subject: [PATCH 041/108] Fixed: Zero width/height letters (ie space) should not be included in atlas --HG-- branch : develop --- code/nel/src/3d/texture_font.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/code/nel/src/3d/texture_font.cpp b/code/nel/src/3d/texture_font.cpp index eac8cb545..5977a8e5a 100644 --- a/code/nel/src/3d/texture_font.cpp +++ b/code/nel/src/3d/texture_font.cpp @@ -516,6 +516,9 @@ CTextureFont::SLetterInfo* CTextureFont::getLetterInfo (SLetterKey& k, bool rend // letter not found (=NULL) or render not requested if (!letter || !render) return letter; + // nothing to render, ie space char + if (letter->CharWidth == 0 || letter->CharHeight == 0) return letter; + if (!letter->glyph || letter->glyph->CacheVersion != _CacheVersion) { // render glyph From fcac3986960a02eb2179f5e7f304676187d1925e Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 6 Dec 2018 15:19:54 +0200 Subject: [PATCH 042/108] Fixed: More fixes for LineMaxW with scaling --HG-- branch : experimental-ui-scaling --- code/nel/src/gui/view_text.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/code/nel/src/gui/view_text.cpp b/code/nel/src/gui/view_text.cpp index 7ac1bf8a4..8d36c046d 100644 --- a/code/nel/src/gui/view_text.cpp +++ b/code/nel/src/gui/view_text.cpp @@ -920,9 +920,8 @@ namespace NLGUI // *************************************************************************** sint CViewText::getCurrentMultiLineMaxW() const { - sint maxw = ceilf(_LineMaxW * _Scale); if(_MultiLineMaxWOnly) - return maxw; + return _LineMaxW; else { sint offset = (sint)_XReal - (sint)_Parent->getXReal(); @@ -2021,7 +2020,7 @@ namespace NLGUI _W = (sint)ceilf(_Info.StringWidth / _Scale); // Rare case: clamp W => recompute slowly, cut letters - if(_Info.StringWidth > _LineMaxW) + if(_W > _LineMaxW) { TextContext->erase (_Index); @@ -2038,6 +2037,8 @@ namespace NLGUI dotWidth = si.StringWidth; } + // scale LineMaxW to actual font size + float fLineMaxW = (float)_LineMaxW * _Scale; float rWidthCurrentLine = 0; // for all the text if (_ClampRight) @@ -2048,7 +2049,7 @@ namespace NLGUI ucstring ucStrLetter; ucStrLetter= ucLetter; si = TextContext->getStringInfo (ucStrLetter); - if ((rWidthCurrentLine + si.StringWidth + dotWidth) > _LineMaxW) + if ((rWidthCurrentLine + si.StringWidth + dotWidth) > fLineMaxW) { break; } @@ -2074,7 +2075,7 @@ namespace NLGUI ucstring ucStrLetter; ucStrLetter= ucLetter; si = TextContext->getStringInfo (ucStrLetter); - if ((rWidthCurrentLine + si.StringWidth + dotWidth) > _LineMaxW) + if ((rWidthCurrentLine + si.StringWidth + dotWidth) > fLineMaxW) { break; } From 2bbdbd82d531ca1c25f8b28234564482b38cd56f Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 20 Dec 2018 22:21:43 +0200 Subject: [PATCH 043/108] Changed: Set image and form input element ids so then can be accessed with lua --HG-- branch : develop --- code/nel/include/nel/gui/group_html.h | 2 +- code/nel/src/gui/group_html.cpp | 35 ++++++++++++++++++++------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/code/nel/include/nel/gui/group_html.h b/code/nel/include/nel/gui/group_html.h index 9c48149bf..62d8fb1a0 100644 --- a/code/nel/include/nel/gui/group_html.h +++ b/code/nel/include/nel/gui/group_html.h @@ -366,7 +366,7 @@ namespace NLGUI void addString(const ucstring &str); // Add an image in the current paragraph - void addImage(const char *image, bool reloadImg=false, const CStyleParams &style = CStyleParams()); + void addImage(const std::string &id, const char *image, bool reloadImg=false, const CStyleParams &style = CStyleParams()); // Add a text area in the current paragraph CInterfaceGroup *addTextArea (const std::string &templateName, const char *name, uint rows, uint cols, bool multiLine, const ucstring &content, uint maxlength); diff --git a/code/nel/src/gui/group_html.cpp b/code/nel/src/gui/group_html.cpp index efea31898..ce9a8489d 100644 --- a/code/nel/src/gui/group_html.cpp +++ b/code/nel/src/gui/group_html.cpp @@ -1846,7 +1846,11 @@ namespace NLGUI { CStyleParams style; float tmpf; - + + std::string id; + if (present[MY_HTML_IMG_ID] && value[MY_HTML_IMG_ID]) + id = value[MY_HTML_IMG_ID]; + if (present[MY_HTML_IMG_WIDTH] && value[MY_HTML_IMG_WIDTH]) getPercentage(style.Width, tmpf, value[MY_HTML_IMG_WIDTH]); if (present[MY_HTML_IMG_HEIGHT] && value[MY_HTML_IMG_HEIGHT]) @@ -1880,13 +1884,13 @@ namespace NLGUI if (getA() && getParent () && getParent ()->getParent()) { string params = "name=" + getId() + "|url=" + getLink (); - addButton(CCtrlButton::PushButton, value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC], + addButton(CCtrlButton::PushButton, id, value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC], overSrc, "browse", params.c_str(), tooltip, style); } else if (tooltip || !overSrc.empty()) { - addButton(CCtrlButton::PushButton, value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC], + addButton(CCtrlButton::PushButton, id, value[MY_HTML_IMG_SRC], value[MY_HTML_IMG_SRC], overSrc, "", "", tooltip, style); } else @@ -1908,7 +1912,7 @@ namespace NLGUI reloadImg = true; } - addImage (value[MY_HTML_IMG_SRC], reloadImg, style); + addImage(id, value[MY_HTML_IMG_SRC], reloadImg, style); } } } @@ -1920,6 +1924,10 @@ namespace NLGUI // read general property string templateName; string minWidth; + string id; + + if (present[MY_HTML_INPUT_ID] && value[MY_HTML_INPUT_ID]) + id = value[MY_HTML_INPUT_ID]; // Widget template name if (present[MY_HTML_INPUT_Z_BTN_TMPL] && value[MY_HTML_INPUT_Z_BTN_TMPL]) @@ -2114,6 +2122,10 @@ namespace NLGUI { if (btnType == CCtrlButton::RadioButton) { + // override with 'id' because radio buttons share same name + if (!id.empty()) + checkbox->setId(id); + // group together buttons with same name CForm &form = _Forms.back(); bool notfound = true; @@ -4528,7 +4540,7 @@ namespace NLGUI // *************************************************************************** - void CGroupHTML::addImage(const char *img, bool reloadImg, const CStyleParams &style) + void CGroupHTML::addImage(const std::string &id, const char *img, bool reloadImg, const CStyleParams &style) { // In a paragraph ? if (!_Paragraph) @@ -4544,6 +4556,7 @@ namespace NLGUI // Not added ? CViewBitmap *newImage = new CViewBitmap (TCtorParam()); + newImage->setId(id); // // 1/ try to load the image with the old system (local files in bnp) @@ -4746,7 +4759,7 @@ namespace NLGUI // *************************************************************************** - CCtrlButton *CGroupHTML::addButton(CCtrlButton::EType type, const std::string &/* name */, const std::string &normalBitmap, const std::string &pushedBitmap, + CCtrlButton *CGroupHTML::addButton(CCtrlButton::EType type, const std::string &name, const std::string &normalBitmap, const std::string &pushedBitmap, const std::string &overBitmap, const char *actionHandler, const char *actionHandlerParams, const char *tooltip, const CStyleParams &style) { @@ -4759,6 +4772,10 @@ namespace NLGUI // Add the ctrl button CCtrlButton *ctrlButton = new CCtrlButton(TCtorParam()); + if (!name.empty()) + { + ctrlButton->setId(name); + } // Load only tga files.. (conversion in dds filename is done in the lookup procedure) string normal = normalBitmap.empty()?"":CFile::getPath(normalBitmap) + CFile::getFilenameWithoutExtension(normalBitmap) + ".tga"; @@ -4855,7 +4872,7 @@ namespace NLGUI getParagraph()->addChild (ctrlButton); paragraphChange (); - + setImageSize(ctrlButton, style); return ctrlButton; @@ -6004,12 +6021,12 @@ namespace NLGUI if (!url.empty()) { string params = "name=" + getId() + "|url=" + getLink (); - addButton(CCtrlButton::PushButton, ls.toString(1), ls.toString(1), ls.toString(1), + addButton(CCtrlButton::PushButton, "", ls.toString(1), ls.toString(1), "", "browse", params.c_str(), "", style); } else { - addImage(ls.toString(1), false, style); + addImage("", ls.toString(1), false, style); } From e1be4260d5c8f93e96507128cf8422e5dacce149 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Wed, 26 Dec 2018 18:16:21 +0200 Subject: [PATCH 044/108] Fixed: Audio mixer isEventMusicEnded status was always true. --HG-- branch : develop --- code/nel/src/sound/audio_mixer_user.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/nel/src/sound/audio_mixer_user.cpp b/code/nel/src/sound/audio_mixer_user.cpp index 854a2c1dc..7ea0e5d6e 100644 --- a/code/nel/src/sound/audio_mixer_user.cpp +++ b/code/nel/src/sound/audio_mixer_user.cpp @@ -2759,7 +2759,7 @@ void CAudioMixerUser::setEventMusicVolume(float gain) bool CAudioMixerUser::isEventMusicEnded() { if (_MusicChannelFaders[EventMusicChannel].isInitOk()) - _MusicChannelFaders[EventMusicChannel].isEnded(); + return _MusicChannelFaders[EventMusicChannel].isEnded(); return true; } From 32109c7197f477b625c6e74bb0434ac56a75e215 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Wed, 26 Dec 2018 18:16:21 +0200 Subject: [PATCH 045/108] Changed: Wrong isEnded state for streaming file when still in waiting state. --HG-- branch : develop --- code/nel/src/sound/stream_file_source.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/nel/src/sound/stream_file_source.cpp b/code/nel/src/sound/stream_file_source.cpp index 9a03ba2d8..4d490cfac 100644 --- a/code/nel/src/sound/stream_file_source.cpp +++ b/code/nel/src/sound/stream_file_source.cpp @@ -372,7 +372,7 @@ void CStreamFileSource::run() m_AudioDecoder = NULL; // _Playing cannot be used to detect play state because its required in cleanup // Using m_AudioDecoder in isEnded() may result race condition (decoder is only created after thread is started) - m_DecodingEnded = true; + m_DecodingEnded = !m_WaitingForPlay; } // drop buffers m_FreeBuffers = 3; From 798494ffbfa9c1e41bf149f4e49426e9c9810bf3 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Wed, 26 Dec 2018 18:21:35 +0200 Subject: [PATCH 046/108] Changed: Repeat/shuffle buttons to music player --HG-- branch : develop --- .../data/gamedev/interfaces_v3/compass.xml | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/compass.xml b/code/ryzom/client/data/gamedev/interfaces_v3/compass.xml index a88f49f44..969ce332c 100644 --- a/code/ryzom/client/data/gamedev/interfaces_v3/compass.xml +++ b/code/ryzom/client/data/gamedev/interfaces_v3/compass.xml @@ -11,6 +11,12 @@ + + @@ -33,11 +39,17 @@ global_color_normal="true" global_color_pushed="true" global_color_over="true" ondblclick_l="music_player" params_dblclick_l="song=#index" /> + - + + + + + + + + + + + + From fa16ca8dd9358e90f823c40a39dd7d77b569b826 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Wed, 26 Dec 2018 18:21:42 +0200 Subject: [PATCH 047/108] Changed: Merge music player play/pause button into one, add stop button --HG-- branch : develop --- .../data/gamedev/interfaces_v3/compass.xml | 42 ++++++++++++++----- .../client/src/interface_v3/music_player.cpp | 11 +++++ 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/compass.xml b/code/ryzom/client/data/gamedev/interfaces_v3/compass.xml index 969ce332c..fbc8495d8 100644 --- a/code/ryzom/client/data/gamedev/interfaces_v3/compass.xml +++ b/code/ryzom/client/data/gamedev/interfaces_v3/compass.xml @@ -17,6 +17,9 @@ + @@ -37,11 +40,11 @@ + ondblclick_l="music_player" params_dblclick_l="song=#index" /> - + @@ -50,6 +53,17 @@ + + + + + + + + + + + + + params_l="stop" + tooltip="uiMP3Stop" /> + + = 0 && index < (sint)_Songs.size()) @@ -208,6 +209,8 @@ void CMusicPlayer::play (sint index) _CurrentSong = _Songs[_CurrentSongIndex]; updatePlaylist(prevSongIndex); + + NLGUI::CDBManager::getInstance()->getDbProp("UI:TEMP:MP3_PLAYING")->setValueBool(true); } } @@ -217,6 +220,7 @@ void CMusicPlayer::pause () { if(!SoundMngr) return; + // pause the music only if we are really playing (else risk to pause a background music!) if(_State==Playing) { @@ -225,6 +229,8 @@ void CMusicPlayer::pause () if (_PlayStart > 0) _PauseTime = CTime::getLocalTime() - _PlayStart; + + NLGUI::CDBManager::getInstance()->getDbProp("UI:TEMP:MP3_PLAYING")->setValueBool(false); } } @@ -234,11 +240,14 @@ void CMusicPlayer::stop () { if(!SoundMngr) return; + // stop the music only if we are really playing (else risk to stop a background music!) SoundMngr->stopMusic(0); _State = Stopped; _PlayStart = 0; _PauseTime = 0; + + NLGUI::CDBManager::getInstance()->getDbProp("UI:TEMP:MP3_PLAYING")->setValueBool(false); } // *************************************************************************** @@ -446,6 +455,8 @@ public: MusicPlayer.previous(); else if (Params == "play") MusicPlayer.play(); + else if (Params == "stop") + MusicPlayer.stop(); else if (Params == "pause") MusicPlayer.pause(); else if (Params == "next") From 2607db5b51174f3c5ffab1c90ef7f2b009c9281b Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 27 Dec 2018 09:02:07 +0200 Subject: [PATCH 048/108] Added: CGroupHTML::clearUndoRedo() lua function --HG-- branch : develop --- code/nel/include/nel/gui/group_html.h | 2 ++ code/nel/src/gui/group_html.cpp | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/code/nel/include/nel/gui/group_html.h b/code/nel/include/nel/gui/group_html.h index 62d8fb1a0..dfd80dba9 100644 --- a/code/nel/include/nel/gui/group_html.h +++ b/code/nel/include/nel/gui/group_html.h @@ -270,6 +270,7 @@ namespace NLGUI void setURL(const std::string &url); + int luaClearUndoRedo(CLuaState &ls); int luaBrowse(CLuaState &ls); int luaRefresh(CLuaState &ls); int luaRemoveContent(CLuaState &ls); @@ -285,6 +286,7 @@ namespace NLGUI REFLECT_EXPORT_START(CGroupHTML, CGroupScrollText) REFLECT_LUA_METHOD("browse", luaBrowse) REFLECT_LUA_METHOD("refresh", luaRefresh) + REFLECT_LUA_METHOD("clearUndoRedo", luaClearUndoRedo) REFLECT_LUA_METHOD("removeContent", luaRemoveContent) REFLECT_LUA_METHOD("insertText", luaInsertText) REFLECT_LUA_METHOD("addString", luaAddString) diff --git a/code/nel/src/gui/group_html.cpp b/code/nel/src/gui/group_html.cpp index ce9a8489d..032ffb666 100644 --- a/code/nel/src/gui/group_html.cpp +++ b/code/nel/src/gui/group_html.cpp @@ -5912,6 +5912,15 @@ namespace NLGUI return true; } + int CGroupHTML::luaClearUndoRedo(CLuaState &ls) + { + const char *funcName = "clearUndoRedo"; + CLuaIHM::checkArgCount(ls, funcName, 0); + + clearUndoRedo(); + return 0; + } + // *************************************************************************** int CGroupHTML::luaBrowse(CLuaState &ls) { From 06c0f6800c0c1b933b6b274da02227d9f0b7d226 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 27 Dec 2018 09:03:02 +0200 Subject: [PATCH 049/108] Added: CGroupHTML::clearRefresh() lua function --HG-- branch : develop --- code/nel/include/nel/gui/group_html.h | 4 ++++ code/nel/src/gui/group_html.cpp | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/code/nel/include/nel/gui/group_html.h b/code/nel/include/nel/gui/group_html.h index dfd80dba9..7820cbd6a 100644 --- a/code/nel/include/nel/gui/group_html.h +++ b/code/nel/include/nel/gui/group_html.h @@ -262,6 +262,8 @@ namespace NLGUI void browseUndo (); // Redo browse: Browse the precedent url undoed. no op if none void browseRedo (); + // disable refresh button + void clearRefresh(); // clear undo/redo void clearUndoRedo(); @@ -270,6 +272,7 @@ namespace NLGUI void setURL(const std::string &url); + int luaClearRefresh(CLuaState &ls); int luaClearUndoRedo(CLuaState &ls); int luaBrowse(CLuaState &ls); int luaRefresh(CLuaState &ls); @@ -287,6 +290,7 @@ namespace NLGUI REFLECT_LUA_METHOD("browse", luaBrowse) REFLECT_LUA_METHOD("refresh", luaRefresh) REFLECT_LUA_METHOD("clearUndoRedo", luaClearUndoRedo) + REFLECT_LUA_METHOD("clearRefresh", luaClearRefresh) REFLECT_LUA_METHOD("removeContent", luaRemoveContent) REFLECT_LUA_METHOD("insertText", luaInsertText) REFLECT_LUA_METHOD("addString", luaAddString) diff --git a/code/nel/src/gui/group_html.cpp b/code/nel/src/gui/group_html.cpp index 032ffb666..6c1444f5c 100644 --- a/code/nel/src/gui/group_html.cpp +++ b/code/nel/src/gui/group_html.cpp @@ -5787,6 +5787,13 @@ namespace NLGUI return false; } + // *************************************************************************** + void CGroupHTML::clearRefresh() + { + _URL.clear(); + updateRefreshButton(); + } + // *************************************************************************** void CGroupHTML::clearUndoRedo() { @@ -5873,7 +5880,7 @@ namespace NLGUI { CCtrlBaseButton *butRefresh = dynamic_cast(CWidgetManager::getInstance()->getElementFromId(_BrowseRefreshButton)); - bool enabled = !_Browsing && !_Connecting; + bool enabled = !_Browsing && !_Connecting && !_URL.empty(); if(butRefresh) butRefresh->setFrozen(!enabled); } @@ -5912,6 +5919,16 @@ namespace NLGUI return true; } + int CGroupHTML::luaClearRefresh(CLuaState &ls) + { + const char *funcName = "clearRefresh"; + CLuaIHM::checkArgCount(ls, funcName, 0); + + clearRefresh(); + + return 0; + } + int CGroupHTML::luaClearUndoRedo(CLuaState &ls) { const char *funcName = "clearUndoRedo"; From d8638fa42bce3a10e53f529739b89009d327c082 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 27 Dec 2018 09:03:29 +0200 Subject: [PATCH 050/108] Changed: For ingame help html files, use english files as fallback. --HG-- branch : develop --- .../src/interface_v3/group_quick_help.cpp | 54 +++++++++++++++---- .../src/interface_v3/group_quick_help.h | 5 ++ 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/code/ryzom/client/src/interface_v3/group_quick_help.cpp b/code/ryzom/client/src/interface_v3/group_quick_help.cpp index b3c2c1ecc..144dc9b9a 100644 --- a/code/ryzom/client/src/interface_v3/group_quick_help.cpp +++ b/code/ryzom/client/src/interface_v3/group_quick_help.cpp @@ -297,6 +297,49 @@ void CGroupQuickHelp::beginElement (uint element_number, const std::vector } } +// *************************************************************************** +std::string CGroupQuickHelp::getLanguageUrl(const std::string &href, std::string lang) const +{ + std::string uri = href; + + if (uri.size() < 5 || uri.substr(0, 5) == "http://" || uri.substr(0, 6) == "https://") + { + return uri; + } + + // modify uri such that '_??.html' ending contains current user language + if (uri.substr(uri.size()-5) == ".html") + { + if (uri.rfind("_") == uri.size() - 8) + { + uri = uri.substr(0, uri.size() - 8); + } + else + { + uri = uri.substr(0, uri.size() - 5); + } + uri += "_" + lang + ".html"; + + // files inside bnp (file:/gamedev.bnp@help_en.html) will always match with CPath::lookup() + std::string fname; + size_t pos = uri.find("@"); + if (pos != std::string::npos) + { + fname = uri.substr(pos+1); + } + else + { + fname = uri; + } + if (CPath::lookup(fname, false) == "" && lang != "en") + { + uri = getLanguageUrl(href, "en"); + } + } + + return uri; +} + // *************************************************************************** void CGroupQuickHelp::browse (const char *url) @@ -307,12 +350,7 @@ void CGroupQuickHelp::browse (const char *url) _IsQuickHelp = false; - string completeURL = url; - if (completeURL.substr(completeURL.size()-5, 5) == ".html") - { - completeURL = completeURL.substr(0, completeURL.size()-5); // Substract the ".html" - completeURL += "_" + ClientCfg.getHtmlLanguageCode() + ".html"; - } + string completeURL = getLanguageUrl(url, ClientCfg.getHtmlLanguageCode()); CGroupHTML::browse (completeURL.c_str()); } @@ -321,9 +359,7 @@ void CGroupQuickHelp::browse (const char *url) std::string CGroupQuickHelp::home() { - string completeURL = Home; - completeURL = completeURL.substr(0, completeURL.size()-5); // Substract the ".html" - completeURL += "_" + ClientCfg.getHtmlLanguageCode() + ".html"; + string completeURL = getLanguageUrl(Home, ClientCfg.getHtmlLanguageCode()); return completeURL; } diff --git a/code/ryzom/client/src/interface_v3/group_quick_help.h b/code/ryzom/client/src/interface_v3/group_quick_help.h index 76f863bf9..3dc1c8afa 100644 --- a/code/ryzom/client/src/interface_v3/group_quick_help.h +++ b/code/ryzom/client/src/interface_v3/group_quick_help.h @@ -53,6 +53,11 @@ private: virtual void browse (const char *url); virtual std::string home(); + // Modify uri with '.html' or '_??.html' ending to have current user language, + // If the uri is not found locally, then try "en" as fallback language + // ie. 'help_ru.html' does not exists, return 'help_en.html' + std::string getLanguageUrl(const std::string &href, std::string lang) const; + // Init parsing value void initParameters(); From 0099eb3ba4e8ec4420567eec04dc13fa4680baaf Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 27 Dec 2018 11:32:52 +0200 Subject: [PATCH 051/108] Changed: Do not restrict html font-family to inherit/monospace only. --HG-- branch : develop --- code/nel/src/gui/group_html.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/code/nel/src/gui/group_html.cpp b/code/nel/src/gui/group_html.cpp index 6c1444f5c..92431a3aa 100644 --- a/code/nel/src/gui/group_html.cpp +++ b/code/nel/src/gui/group_html.cpp @@ -6301,10 +6301,7 @@ namespace NLGUI if (it->second == "inherit") style.FontFamily = current.FontFamily; else - if (it->second == "monospace") - style.FontFamily = "monospace"; - else - style.FontFamily.clear(); + style.FontFamily = it->second; } else if (it->first == "font-weight") From c7d65c70637d667073e106bcdb6734d3a4b1c271 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 3 Jan 2019 20:05:00 +0200 Subject: [PATCH 052/108] Changed: Improve inventory search UI --HG-- branch : develop --- .../data/gamedev/interfaces_v3/widgets.xml | 137 ++++++++++++------ .../src/interface_v3/inventory_manager.cpp | 85 +++++++++-- 2 files changed, 167 insertions(+), 55 deletions(-) diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/widgets.xml b/code/ryzom/client/data/gamedev/interfaces_v3/widgets.xml index 62c202815..10c36d6a4 100644 --- a/code/ryzom/client/data/gamedev/interfaces_v3/widgets.xml +++ b/code/ryzom/client/data/gamedev/interfaces_v3/widgets.xml @@ -2625,6 +2625,7 @@ @@ -6337,6 +6352,7 @@ x="0" y="0" posref="BL BL" + posparent="" dblink="" texture="" tooltip="" @@ -6345,6 +6361,7 @@ id="but_#id" button_type="toggle_button" posref="#posref" + posparent="but_#posparent" x="#x" y="#y" tx_normal="w_button_filter_off.tga" @@ -6371,6 +6388,7 @@ x="0" y="0" posref="BL BL" + posparent="" dblink="" texture="" tooltip="" @@ -6379,6 +6397,7 @@ id="but_#id" button_type="toggle_button" posref="#posref" + posparent="but_#posparent" x="#x" y="#y" tx_normal="w_button_filter_off.tga" @@ -6553,83 +6572,109 @@ texture="W_line_hor.tga" /> + + + + + + + - - - + onchange="inv_set_search" + on_focus_lost="inv_search_unfocus" + on_focus_lost_params="but_inv_search" /> diff --git a/code/ryzom/client/src/interface_v3/inventory_manager.cpp b/code/ryzom/client/src/interface_v3/inventory_manager.cpp index ea233b530..0d23e19ec 100644 --- a/code/ryzom/client/src/interface_v3/inventory_manager.cpp +++ b/code/ryzom/client/src/interface_v3/inventory_manager.cpp @@ -2528,25 +2528,92 @@ class CHandlerInvDrag : public IActionHandler }; REGISTER_ACTION_HANDLER( CHandlerInvDrag, "inv_drag" ); -// ********************************************************************************************************** -class CHandlerInvSetSearch : public IActionHandler +// *************************************************************************** +// show/hide edit box, set keyboard focus if 'show' +class CHandlerInvSearchButton : public IActionHandler { - void execute (CCtrlBase *pCaller, const std::string &sParams) + virtual void execute (CCtrlBase *pCaller, const string &sParams) + { + if (sParams.empty()) + { + nlwarning("inv_search_button: missing edit box shortid"); + return; + } + + CCtrlBaseButton* btn = dynamic_cast(pCaller); + if (!btn) + { + nlwarning("inv_search_button pCaller == NULL, caller must be CCtrlBaseButton with 'toggle_button' type"); + return; + } + + ucstring filter; + std::string id = btn->getParent()->getId() + ":" + sParams + ":eb"; + CGroupEditBox *eb = dynamic_cast(CWidgetManager::getInstance()->getElementFromId(id)); + if (!eb) + { + nlwarning("inv_search_button: editbox (%s) not found\n", id.c_str()); + return; + } + + eb->getParent()->setActive(btn->getPushed()); + if (eb->getParent()->getActive()) + { + CWidgetManager::getInstance()->setCaptureKeyboard(eb); + eb->setSelectionAll(); + filter = eb->getInputString(); + } + + CDBGroupListSheetBag *pList = dynamic_cast(CWidgetManager::getInstance()->getElementFromId(btn->getParent()->getId() + ":bag_list")); + if (pList != NULL) pList->setSearchFilter(filter); + + CDBGroupIconListBag *pIcons = dynamic_cast(CWidgetManager::getInstance()->getElementFromId(btn->getParent()->getId() + ":bag_icons")); + if (pIcons != NULL) pIcons->setSearchFilter(filter); + } +}; +REGISTER_ACTION_HANDLER( CHandlerInvSearchButton, "inv_search_button" ); + +// *************************************************************************** +// if :eb is empty then hide edit box, unpush search button +class CHandlerInvSearchUnfocus : public IActionHandler +{ + virtual void execute (CCtrlBase *pCaller, const string &sParams) { if (!pCaller) return; CGroupEditBox *eb = dynamic_cast(pCaller); - if (!eb) return; - - CInterfaceManager *pIM = CInterfaceManager::getInstance(); + if (!eb || !eb->getInputString().empty()) return; // ui:interface:inventory:content:bag:iil:inv_query_eb:eb - string invId = pCaller->getParent()->getParent()->getId(); + std::string id = pCaller->getParent()->getParent()->getId() + ":" + sParams; + CCtrlBaseButton *btn = dynamic_cast(CWidgetManager::getInstance()->getElementFromId(id)); + if (btn) btn->setPushed(false); - CDBGroupListSheetBag *pList = dynamic_cast(CWidgetManager::getInstance()->getElementFromId(invId + ":bag_list")); + // hide :inv_query_eb + pCaller->getParent()->setActive(false); + + // clear filter + CAHManager::getInstance()->runActionHandler("inv_set_search", pCaller, ""); + } +}; +REGISTER_ACTION_HANDLER( CHandlerInvSearchUnfocus, "inv_search_unfocus" ); + +// ********************************************************************************************************** +// set inventory search string +class CHandlerInvSetSearch : public IActionHandler +{ + void execute (CCtrlBase *pCaller, const std::string &sParams) + { + CGroupEditBox *eb = dynamic_cast(pCaller); + if (!eb) return; + + // ui:interface:inventory:content:bag:iil:inv_query_eb:eb + std::string id = pCaller->getParent()->getParent()->getId(); + + CDBGroupListSheetBag *pList = dynamic_cast(CWidgetManager::getInstance()->getElementFromId(id + ":bag_list")); if (pList != NULL) pList->setSearchFilter(eb->getInputString()); - CDBGroupIconListBag *pIcons = dynamic_cast(CWidgetManager::getInstance()->getElementFromId(invId + ":bag_icons")); + CDBGroupIconListBag *pIcons = dynamic_cast(CWidgetManager::getInstance()->getElementFromId(id + ":bag_icons")); if (pIcons != NULL) pIcons->setSearchFilter(eb->getInputString()); } }; From ef1977330b46d946c1a036dd23e901384cb7ec33 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sat, 5 Jan 2019 21:10:14 +0200 Subject: [PATCH 053/108] Fixed: Glyph size step not taken into account. --HG-- branch : develop --- code/nel/src/3d/texture_font.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/nel/src/3d/texture_font.cpp b/code/nel/src/3d/texture_font.cpp index 5977a8e5a..b40083516 100644 --- a/code/nel/src/3d/texture_font.cpp +++ b/code/nel/src/3d/texture_font.cpp @@ -446,7 +446,7 @@ CTextureFont::SGlyphInfo* CTextureFont::findLetterGlyph(SLetterInfo *letter, boo uint bitmapFontSize = max((sint)_MinGlyphSize, min((sint)_MaxGlyphSize, letter->Size)); if (_GlyphSizeStep > 1 && bitmapFontSize > _GlyphSizeStepMin) { - uint size = (bitmapFontSize / _GlyphSizeStep) * _GlyphSizeStep; + bitmapFontSize = (bitmapFontSize / _GlyphSizeStep) * _GlyphSizeStep; } // CacheVersion not checked, all glyphs in cache must be rendered on texture From 09cb6f56dda4583411ea64eaa395c958175c3805 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sat, 5 Jan 2019 22:05:49 +0200 Subject: [PATCH 054/108] Changed: Move ui scale limits to client cfg --HG-- branch : develop --- code/ryzom/client/client_default.cfg | 6 ++++++ .../data/gamedev/interfaces_v3/game_config.xml | 16 +++++++++++++++- code/ryzom/client/src/client_cfg.cpp | 8 +++++++- code/ryzom/client/src/client_cfg.h | 7 +++---- .../src/interface_v3/action_handler_game.cpp | 4 ++-- 5 files changed, 33 insertions(+), 8 deletions(-) diff --git a/code/ryzom/client/client_default.cfg b/code/ryzom/client/client_default.cfg index 4ffddc935..950da08b6 100644 --- a/code/ryzom/client/client_default.cfg +++ b/code/ryzom/client/client_default.cfg @@ -320,6 +320,12 @@ CameraSpeedMin = 2.0; CameraSpeedMax = 100.0; CameraResetSpeed = 10.0; // Speed in radian/s +// Values for UI Scale +InterfaceScale = 1.0; +InterfaceScale_min = 0.8; +InterfaceScale_max = 2.0; +InterfaceScale_step = 0.05; + // Default values for map MaxMapScale = 2.0; R2EDMaxMapScale = 8.0; diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/game_config.xml b/code/ryzom/client/data/gamedev/interfaces_v3/game_config.xml index 2728e7854..c3b904f01 100644 --- a/code/ryzom/client/data/gamedev/interfaces_v3/game_config.xml +++ b/code/ryzom/client/data/gamedev/interfaces_v3/game_config.xml @@ -876,10 +876,17 @@ posparent="lum" x="0" y="-2" /> + @@ -3091,6 +3098,13 @@ realtime="true" widget="sbfloat" link="Gamma" /> + = MIN_INTERFACE_SCALE && scale <= MAX_INTERFACE_SCALE) + if (scale >= ClientCfg.InterfaceScale_min && scale <= ClientCfg.InterfaceScale_max) { ClientCfg.InterfaceScale = scale; ClientCfg.writeDouble("InterfaceScale", ClientCfg.InterfaceScale); @@ -3754,7 +3754,7 @@ class CHandlerSetInterfaceScale : public IActionHandler } } - ucstring help("/setuiscale "+toString("%.1f .. %.1f", MIN_INTERFACE_SCALE, MAX_INTERFACE_SCALE)); + ucstring help("/setuiscale "+toString("%.1f .. %.1f", ClientCfg.InterfaceScale_min, ClientCfg.InterfaceScale_max)); CInterfaceManager::getInstance()->displaySystemInfo(help); } }; From ef1a51d0e4e3541cf0461beecd015405ff3c82b5 Mon Sep 17 00:00:00 2001 From: inky Date: Sun, 6 Jan 2019 04:58:12 +0100 Subject: [PATCH 055/108] Fixed: Free animal in player trade. Player could free an animal already selected in trade. When abort, client was given a copy of the (ghost)item in bag. --HG-- branch : develop --- code/ryzom/client/src/interface_v3/action_handler_game.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/ryzom/client/src/interface_v3/action_handler_game.cpp b/code/ryzom/client/src/interface_v3/action_handler_game.cpp index 7120c3b9f..836a4df28 100644 --- a/code/ryzom/client/src/interface_v3/action_handler_game.cpp +++ b/code/ryzom/client/src/interface_v3/action_handler_game.cpp @@ -1532,7 +1532,8 @@ public: virtual void execute (CCtrlBase * /* pCaller */, const string &Params) { // free with no confirm - beastOrder ("free", Params, false); + if (!UserEntity->isBusy()) + beastOrder ("free", Params, false); } }; REGISTER_ACTION_HANDLER( CHandlerDoBeastFree, "do_beast_free") From ed3130216381e05627624206864eb867f390e49e Mon Sep 17 00:00:00 2001 From: inky Date: Mon, 7 Jan 2019 00:14:14 +0100 Subject: [PATCH 056/108] Changed: Random command has now private roll. --HG-- branch : develop --- code/ryzom/client/src/commands.cpp | 13 ++++++++----- code/ryzom/client/src/user_entity.cpp | 23 ++++++++++++++++++++++- code/ryzom/client/src/user_entity.h | 2 +- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/code/ryzom/client/src/commands.cpp b/code/ryzom/client/src/commands.cpp index 7c33edb90..e8c1178c8 100644 --- a/code/ryzom/client/src/commands.cpp +++ b/code/ryzom/client/src/commands.cpp @@ -475,14 +475,17 @@ bool randomFromString(std::string const& str, sint16& val, sint16 min = -32768, return false; } -NLMISC_COMMAND(random, "Roll a dice and say the result around","[] ") +NLMISC_COMMAND(random, "Roll a dice and say the result around","[] [h|ide]") { // Check parameters. - if (args.size()<1 || args.size()>2) + if (args.size() < 1 || args.size() > 3) return false; sint16 min = 1; sint16 max; + + bool hide = args[args.size()-1][0] == 'h'; + if (!randomFromString(args[0], max)) { CInterfaceManager *pIM = CInterfaceManager::getInstance(); @@ -491,13 +494,13 @@ NLMISC_COMMAND(random, "Roll a dice and say the result around","[] ") pIM->displaySystemInfo(msg); return false; } - if (args.size()==2) + if (args.size() > 1 && args[1][0] != 'h') { if (!randomFromString(args[1], min)) { CInterfaceManager *pIM = CInterfaceManager::getInstance(); ucstring msg = CI18N::get("uiRandomBadParameter"); - strFindReplace(msg, "%s", args[0] ); + strFindReplace(msg, "%s", args[1] ); pIM->displaySystemInfo(msg); return false; } @@ -506,7 +509,7 @@ NLMISC_COMMAND(random, "Roll a dice and say the result around","[] ") std::swap(min, max); if (UserEntity != NULL) - UserEntity->rollDice(min, max); + UserEntity->rollDice(min, max, hide); return true; } diff --git a/code/ryzom/client/src/user_entity.cpp b/code/ryzom/client/src/user_entity.cpp index 25096f304..81a9e5a3a 100644 --- a/code/ryzom/client/src/user_entity.cpp +++ b/code/ryzom/client/src/user_entity.cpp @@ -3091,8 +3091,29 @@ void CUserEntity::setAFK(bool b, string afkTxt) //----------------------------------------------- // rollDice //----------------------------------------------- -void CUserEntity::rollDice(sint16 min, sint16 max) +void CUserEntity::rollDice(sint16 min, sint16 max, bool local) { + if (local) + { + // no need to broadcast over network here + static NLMISC::CRandom* dice = (NLMISC::CRandom*)NULL; + if (!dice) + { + dice = new NLMISC::CRandom; + dice->srand(CTickEventHandler::getGameCycle()); + } + sint16 roll = min + (sint16)dice->rand(max-min); + + ucstring msg = CI18N::get("msgRollDiceLocal"); + strFindReplace(msg, "%min", std::to_string(min)); + strFindReplace(msg, "%max", std::to_string(max)); + strFindReplace(msg, "%roll", std::to_string(roll)); + + CInterfaceManager *pIM= CInterfaceManager::getInstance(); + + pIM->displaySystemInfo(msg, getStringCategory(msg, msg)); + return; + } const string msgName = "COMMAND:RANDOM"; CBitMemStream out; if (GenericMsgHeaderMngr.pushNameToStream(msgName, out)) diff --git a/code/ryzom/client/src/user_entity.h b/code/ryzom/client/src/user_entity.h index ae4730f32..8a4a27c16 100644 --- a/code/ryzom/client/src/user_entity.h +++ b/code/ryzom/client/src/user_entity.h @@ -225,7 +225,7 @@ public: void setAFK(bool b, std::string afkTxt=""); /// Roll a dice and tell the result around - void rollDice(sint16 min, sint16 max); + void rollDice(sint16 min, sint16 max, bool local); /// return true if user can engage melee combat, else return false and display system msg bool canEngageCombat(); From bc3572462299f72e92bb4ad08615846ab7ad9f23 Mon Sep 17 00:00:00 2001 From: Inky Date: Tue, 8 Jan 2019 02:33:18 +0100 Subject: [PATCH 057/108] Added: Inventory filter for animals --HG-- branch : develop --- .../src/interface_v3/inventory_manager.cpp | 18 ++++++++++++++++-- .../src/interface_v3/inventory_manager.h | 12 ++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/code/ryzom/client/src/interface_v3/inventory_manager.cpp b/code/ryzom/client/src/interface_v3/inventory_manager.cpp index 0d23e19ec..5f9ff2d3b 100644 --- a/code/ryzom/client/src/interface_v3/inventory_manager.cpp +++ b/code/ryzom/client/src/interface_v3/inventory_manager.cpp @@ -2003,6 +2003,9 @@ bool SBagOptions::parse(xmlNodePtr cur, CInterfaceGroup * /* parentGroup */) prop = xmlGetProp (cur, (xmlChar*)"filter_tool"); if (prop) DbFilterTool = NLGUI::CDBManager::getInstance()->getDbProp(prop.str()); + prop = xmlGetProp (cur, (xmlChar*)"filter_pet"); + if (prop) DbFilterPet = NLGUI::CDBManager::getInstance()->getDbProp(prop.str()); + prop = xmlGetProp (cur, (xmlChar*)"filter_mp"); if (prop) DbFilterMP = NLGUI::CDBManager::getInstance()->getDbProp(prop.str()); @@ -2079,6 +2082,13 @@ bool SBagOptions::isSomethingChanged() LastDbFilterTool = (DbFilterTool->getValue8() != 0); } + if (DbFilterPet != NULL) + if ((DbFilterPet->getValue8() != 0) != LastDbFilterPet) + { + bRet = true; + LastDbFilterPet = (DbFilterPet->getValue8() != 0); + } + if (DbFilterMP != NULL) if ((DbFilterMP->getValue8() != 0) != LastDbFilterMP) { @@ -2117,6 +2127,7 @@ bool SBagOptions::canDisplay(CDBCtrlSheet *pCS) const bool bFilterArmor = getFilterArmor(); bool bFilterWeapon = getFilterWeapon(); bool bFilterTool = getFilterTool(); + bool bFilterPet = getFilterPet(); bool bFilterMP = getFilterMP(); bool bFilterMissMP = getFilterMissMP(); bool bFilterTP = getFilterTP(); @@ -2168,10 +2179,13 @@ bool SBagOptions::canDisplay(CDBCtrlSheet *pCS) const (pIS->Family == ITEMFAMILY::HARVEST_TOOL) || (pIS->Family == ITEMFAMILY::TAMING_TOOL) || (pIS->Family == ITEMFAMILY::TRAINING_TOOL) || - (pIS->Family == ITEMFAMILY::BAG) || - (pIS->Family == ITEMFAMILY::PET_ANIMAL_TICKET) ) + (pIS->Family == ITEMFAMILY::BAG)) if (!bFilterTool) bDisplay = false; + // Pet + if (pIS->Family == ITEMFAMILY::PET_ANIMAL_TICKET) + if (!bFilterPet) bDisplay = false; + // MP if ((pIS->Family == ITEMFAMILY::RAW_MATERIAL) && pIS->canBuildSomeItemPart()) if (!bFilterMP) bDisplay = false; diff --git a/code/ryzom/client/src/interface_v3/inventory_manager.h b/code/ryzom/client/src/interface_v3/inventory_manager.h index d1c5dbe8c..364874f36 100644 --- a/code/ryzom/client/src/interface_v3/inventory_manager.h +++ b/code/ryzom/client/src/interface_v3/inventory_manager.h @@ -509,6 +509,7 @@ struct SBagOptions NLMISC::CCDBNodeLeaf *DbFilterArmor; NLMISC::CCDBNodeLeaf *DbFilterWeapon; NLMISC::CCDBNodeLeaf *DbFilterTool; + NLMISC::CCDBNodeLeaf *DbFilterPet; NLMISC::CCDBNodeLeaf *DbFilterMP; NLMISC::CCDBNodeLeaf *DbFilterMissMP; NLMISC::CCDBNodeLeaf *DbFilterTP; @@ -516,6 +517,7 @@ struct SBagOptions bool LastDbFilterArmor; bool LastDbFilterWeapon; bool LastDbFilterTool; + bool LastDbFilterPet; bool LastDbFilterMP; bool LastDbFilterMissMP; bool LastDbFilterTP; @@ -529,8 +531,8 @@ struct SBagOptions SBagOptions() { InvType = CInventoryManager::InvUnknown; - DbFilterArmor = DbFilterWeapon = DbFilterTool = DbFilterMP = DbFilterMissMP = DbFilterTP = NULL; - LastDbFilterArmor = LastDbFilterWeapon = LastDbFilterTool = LastDbFilterMP = LastDbFilterMissMP = LastDbFilterTP = false; + DbFilterArmor = DbFilterWeapon = DbFilterTool = DbFilterPet = DbFilterMP = DbFilterMissMP = DbFilterTP = NULL; + LastDbFilterArmor = LastDbFilterWeapon = LastDbFilterTool = LastDbFilterPet = LastDbFilterMP = LastDbFilterMissMP = LastDbFilterTP = false; SearchFilterChanged = false; SearchQualityMin = 0; SearchQualityMax = 999; @@ -561,6 +563,12 @@ struct SBagOptions return (DbFilterTool->getValue8()!=0); } + bool getFilterPet() const + { + if (DbFilterPet == NULL) return true; + return (DbFilterPet->getValue8()!=0); + } + bool getFilterMP() const { if (DbFilterMP == NULL) return true; From dda8a36ee275c91415f3b345a601cd3f535e97a8 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Tue, 8 Jan 2019 11:31:50 +0200 Subject: [PATCH 058/108] Changed: Allow to set custom sizing chars for text --HG-- branch : develop --- code/nel/include/nel/gui/view_text.h | 6 +++ code/nel/src/gui/view_text.cpp | 69 ++++++++++++++++++++++------ 2 files changed, 62 insertions(+), 13 deletions(-) diff --git a/code/nel/include/nel/gui/view_text.h b/code/nel/include/nel/gui/view_text.h index aa1d83330..b9ef7211c 100644 --- a/code/nel/include/nel/gui/view_text.h +++ b/code/nel/include/nel/gui/view_text.h @@ -100,6 +100,9 @@ namespace NLGUI void setMultiMaxLine(uint l) { _MultiMaxLine = l; } void setMultiMinLine(uint l) { _MultiMinLine = l; } + // Override chars used to compute font size + void setFontSizing(const std::string &chars, const std::string &fallback); + // Force only a subset of letter to be displayed. Default is 0/0xFFFFFFFF void enableStringSelection(uint start, uint end); void disableStringSelection(); @@ -244,6 +247,9 @@ namespace NLGUI bool _Oblique; // width of the font in pixel. Just a Hint for tabing format (computed with '_') float _FontWidth; + // strings to use when computing font size + ucstring _FontSizingChars; + ucstring _FontSizingFallback; // height of the font in pixel. // use getFontHeight float _FontHeight; diff --git a/code/nel/src/gui/view_text.cpp b/code/nel/src/gui/view_text.cpp index 8d36c046d..03dd52701 100644 --- a/code/nel/src/gui/view_text.cpp +++ b/code/nel/src/gui/view_text.cpp @@ -97,7 +97,6 @@ namespace NLGUI _InvalidTextContext= true; _FirstLineX = 0; - computeFontSize (); _SingleLineTextClamped= false; _OverExtendViewText= false; @@ -110,6 +109,14 @@ namespace NLGUI _LetterColors = NULL; _Setuped= false; _AutoClampOffset = 0; + + // Letter size + // - "_" that should be the character with the lowest part + // - A with an accent for the highest part + _FontSizingChars.fromUtf8("_\xc3\x84"); + // fallback if SizingChars are not supported by font + _FontSizingFallback.fromUtf8("|"); + computeFontSize (); } // *************************************************************************** @@ -370,6 +377,16 @@ namespace NLGUI { return toString( _ContinuousUpdate ); } + else + if ( name == "sizing_chars" ) + { + return _FontSizingChars.toUtf8(); + } + else + if ( name == "sizing_fallback" ) + { + return _FontSizingFallback.toUtf8(); + } else return ""; } @@ -615,6 +632,18 @@ namespace NLGUI return true; } + else + if( name == "sizing_chars" ) + { + _FontSizingChars.fromUtf8(value); + return true; + } + else + if( name == "sizing_fallback" ) + { + _FontSizingFallback.fromUtf8(value); + return true; + } else return false; } @@ -679,6 +708,8 @@ namespace NLGUI xmlSetProp( node, BAD_CAST "clamp_right", BAD_CAST toString( _ClampRight ).c_str() ); xmlSetProp( node, BAD_CAST "auto_clamp_offset", BAD_CAST toString( _AutoClampOffset ).c_str() ); xmlSetProp( node, BAD_CAST "continuous_update", BAD_CAST toString( _ContinuousUpdate ).c_str() ); + xmlSetProp( node, BAD_CAST "sizing_chars", BAD_CAST _FontSizingChars.toUtf8().c_str() ); + xmlSetProp( node, BAD_CAST "sizing_fallback", BAD_CAST _FontSizingFallback.toUtf8().c_str() ); return true; } @@ -854,6 +885,17 @@ namespace NLGUI _ContinuousUpdate = convertBool(prop); } + // "_Ä" lowest/highest chars (underscore, A+diaeresis) + _FontSizingChars.fromUtf8("_\xc3\x84"); + prop = (char*) xmlGetProp( cur, (xmlChar*)"sizing_chars" ); + if (prop) + _FontSizingChars.fromUtf8((const char*)prop); + + // fallback if SizingChars are not supported by font + _FontSizingFallback.fromUtf8("|"); + prop = (char*) xmlGetProp( cur, (xmlChar*)"sizing_fallback" ); + if (prop) + _FontSizingFallback.fromUtf8((const char*)prop); computeFontSize (); } @@ -1325,6 +1367,15 @@ namespace NLGUI _FormatTags.clear(); } + // *************************************************************************** + void CViewText::setFontSizing(const std::string &chars, const std::string &fallback) + { + _FontSizingChars.clear(); + _FontSizingChars.fromUtf8(chars); + _FontSizingFallback.clear(); + _FontSizingFallback.fromUtf8(fallback); + } + // *************************************************************************** void CViewText::setFontName(const std::string &name) { @@ -2856,22 +2907,14 @@ namespace NLGUI TextContext->setOblique (_Oblique); // Letter size - ucstring chars; - // instead of using the height of "|" that depends on font, - // we're using 2 characters: - // - "_" that should be the character with the lowest part - // - A with an accent for the highest part - chars.fromUtf8("_\xc3\x84"); - - // for now we can't know that directly from UTextContext - UTextContext::CStringInfo si = TextContext->getStringInfo(chars); + UTextContext::CStringInfo si = TextContext->getStringInfo(_FontSizingChars); // font generator changes unknown glyphs to dot '.'. use fallback if it looks odd if (_FontSize > (si.StringHeight + si.StringLine)) { - chars.fromUtf8("|"); - si = TextContext->getStringInfo(chars); + si = TextContext->getStringInfo(_FontSizingFallback); } + // add a padding of 1 pixel else the top will be truncated _FontHeight = si.StringHeight + 1; _FontLegHeight = si.StringLine; @@ -2880,7 +2923,7 @@ namespace NLGUI si = TextContext->getStringInfo(ucstring(" ")); _SpaceWidth = si.StringWidth; - // Font Width + // Font Width (used for ) si = TextContext->getStringInfo(ucstring("_")); _FontWidth = si.StringWidth; } From 8b69d673be7ff09d9ca21298ebe9a17585830b21 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Tue, 8 Jan 2019 11:32:01 +0200 Subject: [PATCH 059/108] Changed: Allow to set font size without adding global font size coef --HG-- branch : develop --- code/nel/include/nel/gui/view_text.h | 3 +- code/nel/src/gui/view_text.cpp | 61 +++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/code/nel/include/nel/gui/view_text.h b/code/nel/include/nel/gui/view_text.h index b9ef7211c..5fe86dee5 100644 --- a/code/nel/include/nel/gui/view_text.h +++ b/code/nel/include/nel/gui/view_text.h @@ -82,7 +82,7 @@ namespace NLGUI void setText (const ucstring &text); void setFontName (const std::string &name); - void setFontSize (sint nFontSize); + void setFontSize (sint nFontSize, bool coef = true); void setEmbolden (bool nEmbolden); void setOblique (bool nOblique); void setColor (const NLMISC::CRGBA &color); @@ -243,6 +243,7 @@ namespace NLGUI std::string _FontName; /// the font size sint _FontSize; + bool _FontSizeCoef; bool _Embolden; bool _Oblique; // width of the font in pixel. Just a Hint for tabing format (computed with '_') diff --git a/code/nel/src/gui/view_text.cpp b/code/nel/src/gui/view_text.cpp index 03dd52701..e991d8170 100644 --- a/code/nel/src/gui/view_text.cpp +++ b/code/nel/src/gui/view_text.cpp @@ -65,6 +65,7 @@ namespace NLGUI _FontSize = 12 + CWidgetManager::getInstance()->getSystemOption( CWidgetManager::OptionAddCoefFont ).getValSInt32(); + _FontSizeCoef = true; _FontName.clear(); _Embolden = false; _Oblique = false; @@ -141,6 +142,7 @@ namespace NLGUI setupDefault (); _FontSize = FontSize + CWidgetManager::getInstance()->getSystemOption( CWidgetManager::OptionAddCoefFont).getValSInt32(); + _FontSizeCoef = true; _Color = Color; _Shadow = Shadow; _ShadowOutline = ShadowOutline; @@ -185,6 +187,7 @@ namespace NLGUI _PosRef = vt._PosRef; _FontSize = vt._FontSize; + _FontSizeCoef = vt._FontSizeCoef; _Embolden = vt._Embolden; _Oblique = vt._Oblique; _Underlined = vt._Underlined; @@ -248,9 +251,15 @@ namespace NLGUI else if( name == "fontsize" ) { - return toString( - _FontSize - CWidgetManager::getInstance()->getSystemOption( CWidgetManager::OptionAddCoefFont ).getValSInt32() - ); + if (_FontSizeCoef) + return toString(_FontSize - CWidgetManager::getInstance()->getSystemOption( CWidgetManager::OptionAddCoefFont ).getValSInt32()); + + return toString(_FontSize); + } + else + if ( name == "fontsize_coef" ) + { + return toString(_FontSizeCoef); } else if( name == "fontweight" ) @@ -425,6 +434,22 @@ namespace NLGUI return true; } else + if( name == "fontsize_coef" ) + { + bool b; + bool oldValue = _FontSizeCoef; + if (fromString( value, b) ) + _FontSizeCoef = b; + // must only change font size when current state changes + if (_FontSizeCoef != oldValue) + { + if (_FontSizeCoef) + _FontSize += CWidgetManager::getInstance()->getSystemOption( CWidgetManager::OptionAddCoefFont ).getValSInt32(); + else + _FontSize -= CWidgetManager::getInstance()->getSystemOption( CWidgetManager::OptionAddCoefFont ).getValSInt32(); + } + } + else if( name == "fontweight" ) { if (value == "bold") @@ -653,10 +678,11 @@ namespace NLGUI { xmlSetProp( node, BAD_CAST "color", BAD_CAST toString( _Color ).c_str() ); xmlSetProp( node, BAD_CAST "global_color", BAD_CAST toString( _ModulateGlobalColor ).c_str() ); - xmlSetProp( node, BAD_CAST "fontsize", - BAD_CAST toString( - _FontSize - CWidgetManager::getInstance()->getSystemOption( CWidgetManager::OptionAddCoefFont ).getValSInt32() - ).c_str() ); + + sint32 fontSize = _FontSize; + if (_FontSizeCoef) fontSize -= CWidgetManager::getInstance()->getSystemOption( CWidgetManager::OptionAddCoefFont ).getValSInt32(); + xmlSetProp( node, BAD_CAST "fontsize", BAD_CAST toString(fontSize).c_str() ); + xmlSetProp( node, BAD_CAST "fontsize_coef", BAD_CAST toString(_FontSizeCoef).c_str() ); std::string fontweight("normal"); if (_Embolden) @@ -755,6 +781,16 @@ namespace NLGUI _FontSize += CWidgetManager::getInstance()->getSystemOption( CWidgetManager::OptionAddCoefFont).getValSInt32(); } + prop = (char*) xmlGetProp( cur, (xmlChar*)"fontsize_coef" ); + _FontSizeCoef = true; + if (prop) + { + _FontSizeCoef = convertBool(prop); + if (!_FontSizeCoef) + _FontSize -= CWidgetManager::getInstance()->getSystemOption( CWidgetManager::OptionAddCoefFont).getValSInt32(); + } + + prop = (char*) xmlGetProp( cur, (xmlChar*)"fontweight" ); _Embolden = false; if (prop) @@ -1395,9 +1431,11 @@ namespace NLGUI } // *************************************************************************** - void CViewText::setFontSize (sint nFontSize) + void CViewText::setFontSize (sint nFontSize, bool coef) { - _FontSize = nFontSize + CWidgetManager::getInstance()->getSystemOption( CWidgetManager::OptionAddCoefFont).getValSInt32(); + _FontSize = nFontSize; + if (coef) _FontSize += CWidgetManager::getInstance()->getSystemOption( CWidgetManager::OptionAddCoefFont).getValSInt32(); + _FontSizeCoef = coef; computeFontSize (); invalidateContent(); } @@ -1405,7 +1443,10 @@ namespace NLGUI // *************************************************************************** sint CViewText::getFontSize() const { - return _FontSize - CWidgetManager::getInstance()->getSystemOption( CWidgetManager::OptionAddCoefFont).getValSInt32(); + if (_FontSizeCoef) + return _FontSize - CWidgetManager::getInstance()->getSystemOption( CWidgetManager::OptionAddCoefFont).getValSInt32(); + + return _FontSize; } // *************************************************************************** From a7b931353c356bb09b597bd0bf5857a5fc37e43d Mon Sep 17 00:00:00 2001 From: Nimetu Date: Tue, 8 Jan 2019 11:39:02 +0200 Subject: [PATCH 060/108] Changed: Fixes for client local/debug mode --HG-- branch : develop --- .../ryzom/client/src/interface_v3/action_handler_game.cpp | 4 ++-- code/ryzom/client/src/interface_v3/group_html_webig.cpp | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/code/ryzom/client/src/interface_v3/action_handler_game.cpp b/code/ryzom/client/src/interface_v3/action_handler_game.cpp index 836a4df28..c5faa16d9 100644 --- a/code/ryzom/client/src/interface_v3/action_handler_game.cpp +++ b/code/ryzom/client/src/interface_v3/action_handler_game.cpp @@ -4238,7 +4238,7 @@ public: string fileName = getParam(sParams, "music"); // don't play if db is in init stage - if (IngameDbMngr.initInProgress()) return; + if (!ClientCfg.Local && IngameDbMngr.initInProgress()) return; if(SoundMngr) SoundMngr->playEventMusic(fileName, xFade, loop); @@ -4260,7 +4260,7 @@ public: string fileName= getParam(sParams, "music"); // don't play if db is in init stage - if (IngameDbMngr.initInProgress()) return; + if (!ClientCfg.Local && IngameDbMngr.initInProgress()) return; if(SoundMngr) SoundMngr->stopEventMusic(fileName, xFade); diff --git a/code/ryzom/client/src/interface_v3/group_html_webig.cpp b/code/ryzom/client/src/interface_v3/group_html_webig.cpp index fe2fa5229..66cfecdad 100644 --- a/code/ryzom/client/src/interface_v3/group_html_webig.cpp +++ b/code/ryzom/client/src/interface_v3/group_html_webig.cpp @@ -54,7 +54,7 @@ REGISTER_ACTION_HANDLER( CHandlerBrowseHome, "browse_home"); static string getWebAuthKey() { - if(!UserEntity) return ""; + if(!UserEntity || !NetMngr.getLoginCookie().isValid()) return ""; // authkey = uint32 cid = NetMngr.getLoginCookie().getUserId() * 16 + PlayerSelectedSlot; @@ -83,7 +83,7 @@ void addWebIGParams (string &url, bool trustedDomain) { url += string("&cid=") + toString(cid) + string("&authkey=") + getWebAuthKey(); - + if (url.find('$') != string::npos) { strFindReplace(url, "$gender$", GSGENDER::toString(UserEntity->getGender())); @@ -304,6 +304,8 @@ CGroupHTMLAuth::~CGroupHTMLAuth() void CGroupHTMLAuth::addHTTPGetParams (string &url, bool trustedDomain) { + if(!UserEntity || !NetMngr.getLoginCookie().isValid()) return; + addWebIGParams(url, trustedDomain); } @@ -311,7 +313,7 @@ void CGroupHTMLAuth::addHTTPGetParams (string &url, bool trustedDomain) void CGroupHTMLAuth::addHTTPPostParams (SFormFields &formfields, bool trustedDomain) { - if(!UserEntity) return; + if(!UserEntity || !NetMngr.getLoginCookie().isValid()) return; uint32 cid = NetMngr.getLoginCookie().getUserId() * 16 + PlayerSelectedSlot; formfields.add("shardid", toString(CharacterHomeSessionId)); From 6c1d44bc511ec470165ae196574bf5613a62c866 Mon Sep 17 00:00:00 2001 From: Inky Date: Tue, 8 Jan 2019 03:07:40 +0100 Subject: [PATCH 061/108] Added: sendMsgToServerUseItem lua bind --HG-- branch : develop --- code/ryzom/client/src/interface_v3/lua_ihm_ryzom.cpp | 11 +++++++++++ code/ryzom/client/src/interface_v3/lua_ihm_ryzom.h | 1 + 2 files changed, 12 insertions(+) diff --git a/code/ryzom/client/src/interface_v3/lua_ihm_ryzom.cpp b/code/ryzom/client/src/interface_v3/lua_ihm_ryzom.cpp index 36780f9e5..01b4dd786 100644 --- a/code/ryzom/client/src/interface_v3/lua_ihm_ryzom.cpp +++ b/code/ryzom/client/src/interface_v3/lua_ihm_ryzom.cpp @@ -540,6 +540,7 @@ void CLuaIHMRyzom::RegisterRyzomFunctions(NLGUI::CLuaState &ls) LUABIND_FUNC(getClientCfg), LUABIND_FUNC(sendMsgToServer), LUABIND_FUNC(sendMsgToServerPvpTag), + LUABIND_FUNC(sendMsgToServerUseItem), LUABIND_FUNC(isGuildQuitAvailable), LUABIND_FUNC(sortGuildMembers), LUABIND_FUNC(getNbGuildMembers), @@ -3326,6 +3327,16 @@ void CLuaIHMRyzom::sendMsgToServerPvpTag(bool pvpTag) ::sendMsgToServer("PVP:PVP_TAG", tag); } +// *************************************************************************** +void CLuaIHMRyzom::sendMsgToServerUseItem(sint32 slot) +{ + //H_AUTO(Lua_CLuaIHM_sendMsgToServerUseItem) + uint8 u8n1 = (uint8)((uint16)slot >> 8); + uint8 u8n2 = (uint8)((uint16)slot & 0x00FF); + + ::sendMsgToServer("ITEM:USE_ITEM", u8n1, u8n2); +} + // *************************************************************************** bool CLuaIHMRyzom::isGuildQuitAvailable() { diff --git a/code/ryzom/client/src/interface_v3/lua_ihm_ryzom.h b/code/ryzom/client/src/interface_v3/lua_ihm_ryzom.h index fa8e57c9c..7a733f7aa 100644 --- a/code/ryzom/client/src/interface_v3/lua_ihm_ryzom.h +++ b/code/ryzom/client/src/interface_v3/lua_ihm_ryzom.h @@ -198,6 +198,7 @@ private: static std::string getClientCfg(const std::string &varName); static void sendMsgToServer(const std::string &msgName); static void sendMsgToServerPvpTag(bool pvpTag); + static void sendMsgToServerUseItem(sint32 slot); static bool isGuildQuitAvailable(); static void sortGuildMembers(); static sint32 getNbGuildMembers(); From ec9a0e6d38585bedd89858ffe76e140e5521b8c5 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sun, 13 Jan 2019 09:08:08 +0200 Subject: [PATCH 062/108] Fixed: Crash in lua getWeatherValue() when continent was not loaded --HG-- branch : develop --- code/ryzom/client/src/interface_v3/lua_ihm_ryzom.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/code/ryzom/client/src/interface_v3/lua_ihm_ryzom.cpp b/code/ryzom/client/src/interface_v3/lua_ihm_ryzom.cpp index 01b4dd786..a599cd18b 100644 --- a/code/ryzom/client/src/interface_v3/lua_ihm_ryzom.cpp +++ b/code/ryzom/client/src/interface_v3/lua_ihm_ryzom.cpp @@ -1688,7 +1688,13 @@ int CLuaIHMRyzom::getWeatherValue(CLuaState &ls) CLuaIHM::checkArgCount(ls, funcName, 0); uint64 currDay = RT.getRyzomDay(); float currHour = (float) RT.getRyzomTime(); - ls.push(::getBlendedWeather(currDay, currHour, *WeatherFunctionParams, ContinentMngr.cur()->WeatherFunction)); + float weather = 0.f; + if (ContinentMngr.cur()) + { + weather = ::getBlendedWeather(currDay, currHour, *WeatherFunctionParams, ContinentMngr.cur()->WeatherFunction); + } + + ls.push(weather); return 1; } From f4ade875a3516eaa9f1a32e92fd18ef5b6cf9205 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sat, 19 Jan 2019 22:26:36 +0200 Subject: [PATCH 063/108] Added: CViewText centered text mode option. --HG-- branch : develop --- code/nel/include/nel/gui/view_text.h | 2 +- code/nel/src/gui/view_text.cpp | 27 +++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/code/nel/include/nel/gui/view_text.h b/code/nel/include/nel/gui/view_text.h index 5fe86dee5..195db5a62 100644 --- a/code/nel/include/nel/gui/view_text.h +++ b/code/nel/include/nel/gui/view_text.h @@ -37,7 +37,7 @@ namespace NLGUI class CViewText : public CViewBase { public: - enum TTextMode { ClipWord, DontClipWord, Justified }; + enum TTextMode { ClipWord, DontClipWord, Justified, Centered }; public: DECLARE_UI_CLASS(CViewText) diff --git a/code/nel/src/gui/view_text.cpp b/code/nel/src/gui/view_text.cpp index e991d8170..d04d3df2e 100644 --- a/code/nel/src/gui/view_text.cpp +++ b/code/nel/src/gui/view_text.cpp @@ -312,6 +312,9 @@ namespace NLGUI case Justified: return "justified"; break; + + case Centered: + return "centered"; } return ""; @@ -522,6 +525,9 @@ namespace NLGUI else if( value == "justified" ) _TextMode = Justified; + else + if( value == "centered" ) + _TextMode = Centered; return true; } @@ -716,6 +722,10 @@ namespace NLGUI case Justified: just = "justified"; break; + + case Centered: + just = "centered"; + break; } xmlSetProp( node, BAD_CAST "justification", BAD_CAST just.c_str() ); @@ -843,6 +853,7 @@ namespace NLGUI if (nlstricmp("clip_word", (const char *) prop) == 0) _TextMode = ClipWord; else if (nlstricmp("dont_clip_word", (const char *) prop) == 0) _TextMode = DontClipWord; else if (nlstricmp("justified", (const char *) prop) == 0) _TextMode = Justified; + else if (nlstricmp("centered", (const char *) prop) == 0) _TextMode = Centered; else nlwarning(" bad text mode"); } @@ -1195,7 +1206,18 @@ namespace NLGUI { CLine &currLine = *_Lines[i]; // current x position - float px = (float) (_XReal * _Scale + ((i==0) ? (sint)_FirstLineX : 0)); + float px = (float) (_XReal * _Scale + ((i==0) ? _FirstLineX : 0.f)); + + // Center line to computed maximum line width (_WReal) + // + // Does not give most accurate result when _WReal is much smaller than parent, + // but _WReal also defines mouseover hotspot/tooltip area. + // + // May not work correctly in CGroupParagraph (multiple text elements). + // + if (_TextMode == Centered) + px += (float)(_WReal * _Scale - (currLine.getWidth() + (i == 0 ? _FirstLineX : 0.f)) )/ 2.f; + // draw each words of the line for(uint k = 0; k < currLine.getNumWords(); ++k) { @@ -2039,6 +2061,7 @@ namespace NLGUI switch(_TextMode) { case ClipWord: updateTextContextMultiLine(nMaxWidth); break; + case Centered: // fallthru to DontClipWord case DontClipWord: updateTextContextMultiLineJustified(nMaxWidth, false); break; case Justified: updateTextContextMultiLineJustified(nMaxWidth, true); break; } @@ -2869,7 +2892,7 @@ namespace NLGUI return (sint32)ceilf(_FontHeight / _Scale); } // If we can't clip the words, return the size of the largest word - else if ((_TextMode == DontClipWord) || (_TextMode == Justified)) + else if ((_TextMode == DontClipWord) || (_TextMode == Justified) || (_TextMode == Centered)) { NL3D::UTextContext *TextContext = CViewRenderer::getTextContext(_FontName); TextContext->setHotSpot (UTextContext::BottomLeft); From 8c51bbc808bcb02de04a11f200dbb12a73bfbfbe Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sat, 19 Jan 2019 22:28:13 +0200 Subject: [PATCH 064/108] Fixed: typo --HG-- branch : develop --- code/nel/src/gui/view_text.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/nel/src/gui/view_text.cpp b/code/nel/src/gui/view_text.cpp index d04d3df2e..47f128d96 100644 --- a/code/nel/src/gui/view_text.cpp +++ b/code/nel/src/gui/view_text.cpp @@ -350,7 +350,7 @@ namespace NLGUI return toString( _Underlined ); } else - if( name == "strikthrough" ) + if( name == "strikethrough" ) { return toString( _StrikeThrough ); } From 4e515eedf000bd1e86ab669cde5e914eef081452 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Tue, 22 Jan 2019 20:00:06 +0200 Subject: [PATCH 065/108] Added: game:onLoadMap lua event to allow override map texture as needed --HG-- branch : develop --- .../client/data/gamedev/interfaces_v3/map.lua | 42 +++++++++ .../client/data/gamedev/interfaces_v3/map.xml | 4 + .../client/src/interface_v3/group_map.cpp | 88 ++++++++++++++++++- .../ryzom/client/src/interface_v3/group_map.h | 22 +++++ .../register_interface_elements.cpp | 1 + 5 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 code/ryzom/client/data/gamedev/interfaces_v3/map.lua diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/map.lua b/code/ryzom/client/data/gamedev/interfaces_v3/map.lua new file mode 100644 index 000000000..609d18b28 --- /dev/null +++ b/code/ryzom/client/data/gamedev/interfaces_v3/map.lua @@ -0,0 +1,42 @@ +-- +-- custom maps +-- + +if (game==nil) then + game= {}; +end + +-- alternative textures for maps +game.mapTextures = {} +-- game.mapTextures["zorai_map.tga"] = "tryker_map.tga" + +-- register alternative texture for map +function game:setAltMap(mapName, altMap) + self.mapTextures[mapName] = altMap +end + +-- remove alternative map texture +function game:removeAltMap(mapName) + self.mapTextures[mapName] = nil +end + +-- map = getUI("ui:interface:map:content:map_content:actual_map") +function game:onLoadMap(map) + -- debugInfo("onLoadMap(id=".. map.id ..", texture=".. map.texture ..")"); + + -- if alt view not enabled + if getDbProp("UI:VARIABLES:SHOW_ALT_MAP") == 0 or map:isIsland() then + return + end + + local texture = map.texture + if self.mapTextures[texture] ~= nil then + -- debugInfo("-- using ".. self.mapTextures[texture] .." for " .. texture) + return self.mapTextures[texture] + end +end + +-- register map overrride +-- game:setAltMap("fyros_map.tga", "fyros_map_sp.tga") + + diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/map.xml b/code/ryzom/client/data/gamedev/interfaces_v3/map.xml index 0f1ab111c..b93028b82 100644 --- a/code/ryzom/client/data/gamedev/interfaces_v3/map.xml +++ b/code/ryzom/client/data/gamedev/interfaces_v3/map.xml @@ -2,6 +2,10 @@ + + + + diff --git a/code/ryzom/client/src/interface_v3/group_map.cpp b/code/ryzom/client/src/interface_v3/group_map.cpp index e6cfcf824..9de74bcc7 100644 --- a/code/ryzom/client/src/interface_v3/group_map.cpp +++ b/code/ryzom/client/src/interface_v3/group_map.cpp @@ -38,6 +38,7 @@ #include "../sheet_manager.h" // for MaxNumPeopleInTeam #include "../global.h" #include "nel/gui/ctrl_quad.h" +#include "nel/gui/lua_ihm.h" // #include "nel/misc/xml_auto_ptr.h" #include "game_share/mission_desc.h" @@ -403,6 +404,7 @@ CGroupMap::CGroupMap(const TCtorParam ¶m) _MaxH = 2000; //_MinW = 50; _MapTF = NULL; + _MapTexture.clear(); _PlayerPosMaterial = NULL; _PlayerPosTF = NULL; _MapTexW = 0; @@ -462,6 +464,8 @@ CGroupMap::CGroupMap(const TCtorParam ¶m) _PanStartDateInMs = 0; _DeltaTimeBeforePanInMs = 0; _DeltaPosBeforePan = 0; + // + _LuaLoadMapEntered = false; } //============================================================================================================ @@ -2070,16 +2074,62 @@ void CGroupMap::loadPlayerPos() _PlayerPosMaterial.setTexture(_PlayerPosTF); } +//============================================================================================================ +void CGroupMap::reload() +{ + if (!_CurMap || !getActive()) return; + + SMap* current = _CurMap; + _CurMap = NULL; + + setMap(current); +} + //============================================================================================================ void CGroupMap::loadMap() { _MapLoadFailure = true; if (!_CurMap) return; - const std::string &mapName = _CurMap->BitmapName; - std::string fullName = NLMISC::CPath::lookup(mapName, false, false); + + _MapTexture = _CurMap->BitmapName; + + // call lua game:onLoadMap() function if present + // avoid deadlock if called recursively + if (!_LuaLoadMapEntered) + { + _LuaLoadMapEntered = true; + CLuaState *ls = CLuaManager::getInstance().getLuaState(); + + CLuaStackRestorer lsr(ls, ls->getTop()); + ls->pushGlobalTable(); + + CLuaObject game(*ls); + game = game["game"]; + if (!game["onLoadMap"].isNil()) + { + uint numArg = 1; + uint numResult = 1; + + CLuaIHM::pushReflectableOnStack(*ls, this); + if (game.callMethodByNameNoThrow("onLoadMap", numArg, numResult)) + { + if (ls->isString(1)) + { + if (!NLMISC::CPath::lookup(ls->toString(1), false, false).empty()) + _MapTexture = ls->toString(1); + else + nlwarning("Custom map texture not found '%s' for map '%s'", ls->toString(1), _MapTexture.c_str()); + } + } + } + + _LuaLoadMapEntered = false; + } + + std::string fullName = NLMISC::CPath::lookup(_MapTexture, false, false); if (fullName.empty()) { - nlwarning("Can't find map %s", mapName.c_str()); + nlwarning("Can't find map %s", _MapTexture.c_str()); return; } uint32 w, h; @@ -2098,7 +2148,7 @@ void CGroupMap::loadMap() } else { - nlwarning("Can't open map %s", mapName.c_str()); + nlwarning("Can't open map %s", _MapTexture.c_str()); return; } _MapTF = Driver->createTextureFile(fullName); @@ -3322,6 +3372,36 @@ SMap *CGroupMap::getParentMap(SMap *map) return NULL; } +//========================================================================================================= +std::string CGroupMap::getContinentName() const +{ + if (_CurMap == NULL) return ""; + + return toLower(_CurMap->ContinentName); +} + +//========================================================================================================= +std::string CGroupMap::getMapTexture() const +{ + return toLower(_MapTexture); +} + +//========================================================================================================= +int CGroupMap::luaReload(CLuaState &ls) +{ + CLuaIHM::checkArgCount(ls, "reload", 0); + reload(); + return 0; +} + +//========================================================================================================= +int CGroupMap::luaIsIsland(CLuaState &ls) +{ + CLuaIHM::checkArgCount(ls, "isIsland", 0); + ls.push(_IsIsland); + return 1; +} + ///////////////////// // ACTION HANDLERS // diff --git a/code/ryzom/client/src/interface_v3/group_map.h b/code/ryzom/client/src/interface_v3/group_map.h index 52980c0d8..baf273779 100644 --- a/code/ryzom/client/src/interface_v3/group_map.h +++ b/code/ryzom/client/src/interface_v3/group_map.h @@ -114,6 +114,17 @@ public: */ virtual void onUpdate(CGroupMap &/* owner */) {} }; + + REFLECT_EXPORT_START(CGroupMap, CInterfaceGroup) + REFLECT_STRING("continent", getContinentName, dummySet); + REFLECT_STRING("texture", getMapTexture, dummySet); + REFLECT_LUA_METHOD("isIsland", luaIsIsland); + REFLECT_LUA_METHOD("reload", luaReload); + REFLECT_EXPORT_END + + int luaReload(CLuaState &ls); + int luaIsIsland(CLuaState &ls); + public: CGroupMap(const TCtorParam ¶m); virtual ~CGroupMap(); @@ -134,6 +145,14 @@ public: void setMap(const std::string &mapName); void setMap(SMap *map); + // return current continent + std::string getContinentName() const; + // return currently displayed map texture + std::string getMapTexture() const; + + // reload current map texture + void reload(); + // pan the map of the given number of pixels void pan(sint32 dx, sint32 dy); @@ -323,6 +342,7 @@ private: CContinent *_CurContinent; // the last continent for which the map was displayed (can be NULL if world) NLMISC::CVector2f _MapMinCorner; // In world coordinates NLMISC::CVector2f _MapMaxCorner; + std::string _MapTexture; // currently displayed map texture bool _IsIsland; // true if current map is an island (island bitmap need not to be raised to the next // power of 2 @@ -499,6 +519,8 @@ private: // r2 islands std::vector _Islands; + // guard against recursive calls + bool _LuaLoadMapEntered; private: void loadPlayerPos(); diff --git a/code/ryzom/client/src/interface_v3/register_interface_elements.cpp b/code/ryzom/client/src/interface_v3/register_interface_elements.cpp index 6ce0029bf..203cac03a 100644 --- a/code/ryzom/client/src/interface_v3/register_interface_elements.cpp +++ b/code/ryzom/client/src/interface_v3/register_interface_elements.cpp @@ -34,6 +34,7 @@ void registerInterfaceElements() CViewPointerRyzom::forceLinking(); REGISTER_REFLECTABLE_CLASS(CViewRadar, CViewBase); + REGISTER_REFLECTABLE_CLASS(CGroupMap, CInterfaceGroup); REGISTER_REFLECTABLE_CLASS(CDBCtrlSheet, CCtrlDraggable); REGISTER_REFLECTABLE_CLASS(IListSheetBase, CInterfaceGroup); REGISTER_REFLECTABLE_CLASS(CInterface3DScene, CInterfaceGroup); From 2694cdb7890e11dfe40a627132e9f382f65063a4 Mon Sep 17 00:00:00 2001 From: Jan Boon Date: Fri, 25 Jan 2019 23:46:38 +0800 Subject: [PATCH 066/108] Set up CI --- azure-pipelines.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 000000000..39541fd7d --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,23 @@ +jobs: + - job: ubuntu16 + pool: + vmImage: 'Ubuntu-16.04' + steps: + - script: | + sudo apt-get update + sudo apt-get upgrade -y + sudo apt-get install liblua5.1-dev libluabind-dev libcpptest-dev -y + sudo apt-get install libogg-dev libvorbis-dev libopenal-dev -y + sudo apt-get install libgif-dev libfreetype6-dev -y + sudo apt-get install libxml2-dev -y + displayName: 'Dependencies' + - script: | + mkdir build + cmake --version + cd build + cmake -DWITH_NEL_TESTS=ON -DWITH_NEL_SAMPLES=ON -DWITH_LUA51=ON -DWITH_RYZOM_SERVER=ON -DWITH_RYZOM_TOOLS=ON -DWITH_NEL_TOOLS=ON ../code + cat build/CMakeCache.txt + displayName: 'CMake' + - script: | + make -j`nproc` + displayName: 'Make' \ No newline at end of file From 4c84748bace4a7dac3910b6b01633408f1bdeb29 Mon Sep 17 00:00:00 2001 From: Jan Boon Date: Fri, 25 Jan 2019 23:59:35 +0800 Subject: [PATCH 067/108] Update --- azure-pipelines.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 39541fd7d..1d200d1bc 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -5,7 +5,10 @@ jobs: steps: - script: | sudo apt-get update - sudo apt-get upgrade -y + sudo apt-get install cmake build-essential -y + sudo apt-get install libmysqlclient15-dev -y + sudo apt-get install bison autoconf automake -y + sudo apt-get install libpng12-dev libjpeg62-dev -y sudo apt-get install liblua5.1-dev libluabind-dev libcpptest-dev -y sudo apt-get install libogg-dev libvorbis-dev libopenal-dev -y sudo apt-get install libgif-dev libfreetype6-dev -y From 7661d49bf0722502bf903c91cd4032c36b9b3ebd Mon Sep 17 00:00:00 2001 From: Jan Boon Date: Sat, 26 Jan 2019 00:03:07 +0800 Subject: [PATCH 068/108] Update --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1d200d1bc..e100d14d4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -13,6 +13,7 @@ jobs: sudo apt-get install libogg-dev libvorbis-dev libopenal-dev -y sudo apt-get install libgif-dev libfreetype6-dev -y sudo apt-get install libxml2-dev -y + sudo apt-get install libcurl4-openssl-dev -y displayName: 'Dependencies' - script: | mkdir build From 397f5b15072fa34445fc120a1a9ab82762af713a Mon Sep 17 00:00:00 2001 From: Jan Boon Date: Sat, 26 Jan 2019 00:07:21 +0800 Subject: [PATCH 069/108] Update --- azure-pipelines.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e100d14d4..a4695291e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -6,7 +6,7 @@ jobs: - script: | sudo apt-get update sudo apt-get install cmake build-essential -y - sudo apt-get install libmysqlclient15-dev -y + sudo apt-get install libmysqlclient-dev -y sudo apt-get install bison autoconf automake -y sudo apt-get install libpng12-dev libjpeg62-dev -y sudo apt-get install liblua5.1-dev libluabind-dev libcpptest-dev -y @@ -14,6 +14,7 @@ jobs: sudo apt-get install libgif-dev libfreetype6-dev -y sudo apt-get install libxml2-dev -y sudo apt-get install libcurl4-openssl-dev -y + sudo apt-get install libsquish-dev -y displayName: 'Dependencies' - script: | mkdir build From bd274c5ad04081f663d233e56ca8f3041748afdd Mon Sep 17 00:00:00 2001 From: Jan Boon Date: Sat, 26 Jan 2019 00:11:23 +0800 Subject: [PATCH 070/108] Update --- azure-pipelines.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a4695291e..89562087c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -14,13 +14,12 @@ jobs: sudo apt-get install libgif-dev libfreetype6-dev -y sudo apt-get install libxml2-dev -y sudo apt-get install libcurl4-openssl-dev -y - sudo apt-get install libsquish-dev -y displayName: 'Dependencies' - script: | mkdir build cmake --version cd build - cmake -DWITH_NEL_TESTS=ON -DWITH_NEL_SAMPLES=ON -DWITH_LUA51=ON -DWITH_RYZOM_SERVER=ON -DWITH_RYZOM_TOOLS=ON -DWITH_NEL_TOOLS=ON ../code + cmake -DWITH_NEL_TESTS=OFF -DWITH_NEL_SAMPLES=ON -DWITH_LUA51=ON -DWITH_RYZOM_SERVER=ON -DWITH_RYZOM_TOOLS=OFF -DWITH_NEL_TOOLS=OFF ../code cat build/CMakeCache.txt displayName: 'CMake' - script: | From 61b1509048589ed1cdc696cd769376165904bbc0 Mon Sep 17 00:00:00 2001 From: Jan Boon Date: Sat, 26 Jan 2019 00:14:36 +0800 Subject: [PATCH 071/108] Update --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 89562087c..2ede2a7e8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -20,7 +20,7 @@ jobs: cmake --version cd build cmake -DWITH_NEL_TESTS=OFF -DWITH_NEL_SAMPLES=ON -DWITH_LUA51=ON -DWITH_RYZOM_SERVER=ON -DWITH_RYZOM_TOOLS=OFF -DWITH_NEL_TOOLS=OFF ../code - cat build/CMakeCache.txt + cat CMakeCache.txt displayName: 'CMake' - script: | make -j`nproc` From f4ff5e0d2567f3780f5b7c5e173c433bee90f7e0 Mon Sep 17 00:00:00 2001 From: Jan Boon Date: Sat, 26 Jan 2019 00:17:22 +0800 Subject: [PATCH 072/108] Update --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2ede2a7e8..6ad47eba4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -23,5 +23,6 @@ jobs: cat CMakeCache.txt displayName: 'CMake' - script: | + cd build make -j`nproc` displayName: 'Make' \ No newline at end of file From 86195393dbbc7e4d76bf12bb6c38991c6240d833 Mon Sep 17 00:00:00 2001 From: Jan Boon Date: Sat, 26 Jan 2019 00:41:31 +0800 Subject: [PATCH 073/108] Update: Pipeline script --- azure-pipelines.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6ad47eba4..6e0f2b2a7 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -4,8 +4,14 @@ jobs: vmImage: 'Ubuntu-16.04' steps: - script: | + sudo apt-get update + sudo apt-get install -y software-properties-common + sudo add-apt-repository ppa:ubuntu-toolchain-r/test sudo apt-get update sudo apt-get install cmake build-essential -y + sudo apt-get install gcc-8 g++-8 -y + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 60 + sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 60 sudo apt-get install libmysqlclient-dev -y sudo apt-get install bison autoconf automake -y sudo apt-get install libpng12-dev libjpeg62-dev -y From a888ce008050e1feba00b37b6f4a28b33d172cc0 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Mon, 28 Jan 2019 11:40:52 +0200 Subject: [PATCH 074/108] Changed: Make border duplication as default for build interface --HG-- branch : develop --- code/nel/tools/3d/build_interface/main.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/code/nel/tools/3d/build_interface/main.cpp b/code/nel/tools/3d/build_interface/main.cpp index 58c97e513..ed7f220b5 100644 --- a/code/nel/tools/3d/build_interface/main.cpp +++ b/code/nel/tools/3d/build_interface/main.cpp @@ -214,7 +214,7 @@ int main(int argc, char **argv) args.addArg("x", "extract", "", "Extract all interface elements from to ."); args.addAdditionalArg("output_filename", "PNG or TGA file to generate", true); args.addAdditionalArg("input_path", "Path that containts interfaces elements", false); - args.addArg("b", "border", "", "Duplicate icon border to allow bilinear filtering"); + args.addArg("", "no-border", "", "Disable border duplication. Enabled by default"); if (!args.parse(argc, argv)) return 1; @@ -229,10 +229,10 @@ int main(int argc, char **argv) } // - uint borderSize = 0; - if (args.haveArg("b")) + uint borderSize = 1; + if (args.haveLongArg("no-border")) { - borderSize = 1; + borderSize = 0; } // extract all interface elements From d97f5b1a8bad84b4fb5c238a7b1aa20487e5a620 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Mon, 28 Jan 2019 11:40:52 +0200 Subject: [PATCH 075/108] Changed: Enable UI bilinear filtering by default. Requires new atlases to be generated --HG-- branch : develop --- code/ryzom/client/client_default.cfg | 5 +++++ code/ryzom/client/src/client_cfg.cpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/code/ryzom/client/client_default.cfg b/code/ryzom/client/client_default.cfg index 950da08b6..11bd62b5b 100644 --- a/code/ryzom/client/client_default.cfg +++ b/code/ryzom/client/client_default.cfg @@ -326,6 +326,11 @@ InterfaceScale_min = 0.8; InterfaceScale_max = 2.0; InterfaceScale_step = 0.05; +// Enable biliner filtering for UI textures +// Texture atlas needs to be generated with border duplication +// or there will be visible texture bleeding +BilinearUI = 1; + // Default values for map MaxMapScale = 2.0; R2EDMaxMapScale = 8.0; diff --git a/code/ryzom/client/src/client_cfg.cpp b/code/ryzom/client/src/client_cfg.cpp index e1ab87c02..ea81cd980 100644 --- a/code/ryzom/client/src/client_cfg.cpp +++ b/code/ryzom/client/src/client_cfg.cpp @@ -304,7 +304,7 @@ CClientConfig::CClientConfig() InterfaceScale_min = 0.8f; InterfaceScale_max = 2.0f; InterfaceScale_step = 0.05; - BilinearUI = false; + BilinearUI = true; VREnable = false; VRDisplayDevice = "Auto"; From 1f1bd9fb8f5486a9295cc7b7e3529c934d1f88d3 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Tue, 29 Jan 2019 00:34:38 +0200 Subject: [PATCH 076/108] Changed: Allow to override scroll bar texture width/height --HG-- branch : develop --- code/nel/include/nel/gui/ctrl_scroll.h | 1 + code/nel/src/gui/ctrl_scroll.cpp | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/code/nel/include/nel/gui/ctrl_scroll.h b/code/nel/include/nel/gui/ctrl_scroll.h index cf01ada78..a22da2cbd 100644 --- a/code/nel/include/nel/gui/ctrl_scroll.h +++ b/code/nel/include/nel/gui/ctrl_scroll.h @@ -175,6 +175,7 @@ namespace NLGUI bool _CallingAH : 1; bool _Cancelable : 1; // true if the slider may be cancelled when pressed on the mouse right button bool _Frozen : 1; + bool _Scale : 1; // For Target Scroller only: the target offset step in pixel. sint32 _TargetStepX; diff --git a/code/nel/src/gui/ctrl_scroll.cpp b/code/nel/src/gui/ctrl_scroll.cpp index acdaa15f5..e97bd8cd4 100644 --- a/code/nel/src/gui/ctrl_scroll.cpp +++ b/code/nel/src/gui/ctrl_scroll.cpp @@ -68,6 +68,7 @@ namespace NLGUI _StepValue = 0; _TileM = false; _Frozen = false; + _Scale = false; } // ------------------------------------------------------------------------------------------------ @@ -108,6 +109,11 @@ namespace NLGUI return getTextureTopOrRight(); } else + if( name == "scale" ) + { + return toString( _Scale ); + } + else if( name == "vertical" ) { return toString( _Vertical ); @@ -244,6 +250,14 @@ namespace NLGUI return; } else + if( name =="scale" ) + { + bool b; + if (fromString( value, b ) ) + _Scale = b; + return; + } + else if( name == "vertical" ) { bool b; @@ -408,6 +422,7 @@ namespace NLGUI xmlSetProp( node, BAD_CAST "tx_bottomleft", BAD_CAST getTextureBottomOrLeft().c_str() ); xmlSetProp( node, BAD_CAST "tx_middle", BAD_CAST getTextureMiddle().c_str() ); xmlSetProp( node, BAD_CAST "tx_topright", BAD_CAST getTextureTopOrRight().c_str() ); + xmlSetProp( node, BAD_CAST "scale", BAD_CAST toString( _Scale ).c_str() ); xmlSetProp( node, BAD_CAST "vertical", BAD_CAST toString( _Vertical ).c_str() ); std::string align; @@ -480,6 +495,10 @@ namespace NLGUI if(prop) setTextureTopOrRight(string((const char*)prop)); else setTextureTopOrRight ("w_scroll_l0_t.tga"); + // Override texture size (w for vertical, h for horizontal) + prop = (char*) xmlGetProp( node, (xmlChar*)"scale" ); + if (prop) _Scale = convertBool((const char*)prop); + // Read properties prop = (char*) xmlGetProp( node, (xmlChar*)"vertical" ); if (prop) _Vertical = convertBool((const char*)prop); @@ -606,13 +625,13 @@ namespace NLGUI if (_Vertical) { - _W = w; + if (!_Scale) _W = w; _H = _Target->getMaxHReal(); } else { _W = _Target->getMaxWReal(); - _H = h; + if (!_Scale) _H = h; } CCtrlBase::updateCoords (); From 5a1a167de91056c074e73aff026a0847063b6e38 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Tue, 29 Jan 2019 00:37:34 +0200 Subject: [PATCH 077/108] Added: Scroll bar for main chat tabs --HG-- branch : develop --- .../gamedev/interfaces_v3/interaction.xml | 20 ++++++++++++++++++- .../client/src/interface_v3/chat_window.cpp | 9 +++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/interaction.xml b/code/ryzom/client/data/gamedev/interfaces_v3/interaction.xml index f4ab98453..90432bcff 100644 --- a/code/ryzom/client/data/gamedev/interfaces_v3/interaction.xml +++ b/code/ryzom/client/data/gamedev/interfaces_v3/interaction.xml @@ -1042,7 +1042,9 @@ h="24" posref="TL TL" x="0" - y="-4"> + y="-4" + max_w="0" + max_sizeref="w"> + + + + setHeaderColor(desc.HeaderColor); + + // because root group was created from template, element from scrollbar target attribute was not created yet + CInterfaceGroup *pIG = w->getContainer()->getGroup("header_opened:channel_select"); + if (pIG) + { + CCtrlScroll *sb = dynamic_cast(w->getContainer()->getCtrl("channel_scroll")); + if (sb) sb->setTarget(pIG); + } + return w; } else From 07d9107b6bc2dc2578dda299395344d82c6ffe7e Mon Sep 17 00:00:00 2001 From: Nimetu Date: Wed, 30 Jan 2019 23:54:18 +0200 Subject: [PATCH 078/108] Fixed: build interface border duplication --HG-- branch : develop --- code/nel/tools/3d/build_interface/main.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/code/nel/tools/3d/build_interface/main.cpp b/code/nel/tools/3d/build_interface/main.cpp index ed7f220b5..2f0cc88bd 100644 --- a/code/nel/tools/3d/build_interface/main.cpp +++ b/code/nel/tools/3d/build_interface/main.cpp @@ -421,8 +421,17 @@ int main(int argc, char **argv) NLMISC::CBitmap *tmp = new NLMISC::CBitmap; tmp->resize(pBtmp->getWidth(), pBtmp->getHeight()); tmp->blit(pBtmp, 0, 0); + // corners tmp->resample(tmp->getWidth() + borderSize * 2, tmp->getHeight() + borderSize * 2); + // top, bottom + tmp->blit(pBtmp, borderSize, 0); + tmp->blit(pBtmp, borderSize, borderSize*2); + // left, right + tmp->blit(pBtmp, 0, borderSize); + tmp->blit(pBtmp, borderSize*2, borderSize); + // center tmp->blit(pBtmp, borderSize, borderSize); + delete pBtmp; pBtmp = tmp; } From 5e68a4e293455f3cc647aaf3c2bd3e380be77c20 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Mon, 11 Feb 2019 12:45:15 +0200 Subject: [PATCH 079/108] Fixed: Loss of precision when calculating text line width --HG-- branch : develop --- code/nel/include/nel/gui/view_text.h | 4 ++-- code/nel/src/gui/view_text.cpp | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/code/nel/include/nel/gui/view_text.h b/code/nel/include/nel/gui/view_text.h index 195db5a62..fa5f74b84 100644 --- a/code/nel/include/nel/gui/view_text.h +++ b/code/nel/include/nel/gui/view_text.h @@ -442,9 +442,9 @@ namespace NLGUI // Clear all the lines and free their datas void clearLines(); // Update in the case of a multiline text - void updateTextContextMultiLine(uint nMaxWidth); + void updateTextContextMultiLine(float nMaxWidth); // Update in the case of a multiline text with justification - void updateTextContextMultiLineJustified(uint nMaxWidth, bool expandSpaces); + void updateTextContextMultiLineJustified(float nMaxWidth, bool expandSpaces); // Recompute font size info void computeFontSize (); diff --git a/code/nel/src/gui/view_text.cpp b/code/nel/src/gui/view_text.cpp index 47f128d96..45adbe245 100644 --- a/code/nel/src/gui/view_text.cpp +++ b/code/nel/src/gui/view_text.cpp @@ -1628,7 +1628,7 @@ namespace NLGUI // *************************************************************************** - void CViewText::updateTextContextMultiLine(uint nMaxWidth) + void CViewText::updateTextContextMultiLine(float nMaxWidth) { ucchar ucLetter; UTextContext::CStringInfo si; @@ -1741,7 +1741,7 @@ namespace NLGUI } // *************************************************************************** - void CViewText::updateTextContextMultiLineJustified(uint nMaxWidth, bool expandSpaces) + void CViewText::updateTextContextMultiLineJustified(float nMaxWidth, bool expandSpaces) { NL3D::UTextContext *TextContext = CViewRenderer::getTextContext(_FontName); UTextContext::CStringInfo si; @@ -1847,10 +1847,10 @@ namespace NLGUI } // // Does the word go beyond the end of line ? - if (!lineFeed && newLineWidth > (float) nMaxWidth) + if (!lineFeed && newLineWidth > nMaxWidth) { // Have we enough room for this word on a line ? - bool roomForThisWord = (numWordsInLine > 0) || ( (newLineWidth - lineWidth) < (float) nMaxWidth ); + bool roomForThisWord = (numWordsInLine > 0) || ( (newLineWidth - lineWidth) < nMaxWidth ); // not enough room for that word // If it is the only word of the line, just split it @@ -1927,7 +1927,7 @@ namespace NLGUI word.build(wordValue, *TextContext, numSpaces); word.Format= wordFormat; _Lines.push_back(TLineSPtr(new CLine)); - float roomForSpaces = (float) nMaxWidth - word.Info.StringWidth; + float roomForSpaces = nMaxWidth - word.Info.StringWidth; if (expandSpaces && numSpaces != 0) { _Lines.back()->setSpaceWidth(roomForSpaces / (float) numSpaces); From 0e4048661c0dbcce8744d6dcc7c5807d2606fec3 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Mon, 11 Feb 2019 12:45:37 +0200 Subject: [PATCH 080/108] Fixed: Copy to clipboard mangles utf8 chars --HG-- branch : develop --- code/nel/src/gui/action_handler.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/code/nel/src/gui/action_handler.cpp b/code/nel/src/gui/action_handler.cpp index 2d2246937..e4809c00f 100644 --- a/code/nel/src/gui/action_handler.cpp +++ b/code/nel/src/gui/action_handler.cpp @@ -748,8 +748,12 @@ namespace NLGUI { virtual void execute (CCtrlBase *pCaller, const std::string ¶ms) { - if (!CViewRenderer::getInstance()->getDriver()->copyTextToClipboard(params)) + ucstring s; + s.fromUtf8(params); + if (!CViewRenderer::getInstance()->getDriver()->copyTextToClipboard(s)) + { nlwarning("Copy to clipboard failed: '%s'", params.c_str()); + } } }; REGISTER_ACTION_HANDLER(CAHCopyToClipboard, "copy_to_clipboard"); From 838dd14d15fa8d2682da9b7c0daa8dac7ef4e243 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Mon, 11 Feb 2019 14:32:13 +0200 Subject: [PATCH 081/108] Changed: Use popup menu for chat-copy function --HG-- branch : develop --- code/nel/include/nel/gui/group_paragraph.h | 7 +++ code/nel/src/gui/group_paragraph.cpp | 6 ++- .../gamedev/interfaces_v3/interaction.xml | 9 ++++ .../src/interface_v3/chat_text_manager.cpp | 43 ++++++++++++++++++- 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/code/nel/include/nel/gui/group_paragraph.h b/code/nel/include/nel/gui/group_paragraph.h index 0004850fa..02f948582 100644 --- a/code/nel/include/nel/gui/group_paragraph.h +++ b/code/nel/include/nel/gui/group_paragraph.h @@ -206,6 +206,11 @@ namespace NLGUI invalidateContent(); } + /// temporarily enable mouse over effect + // will be automatically disabled when mouse leaves element + void enableTempOver() { _TempOver = true; } + void disableTempOver() { _TempOver = false; } + /// \from CInterfaceElement void onInvalidateContent(); sint32 getMaxUsedW() const; @@ -233,6 +238,8 @@ namespace NLGUI // Do we have a color under the element pointed by the mouse bool _Over; + // Temporarily force mouse over effect. Deactivated when mouse moves away + bool _TempOver; // If over is true so we have a color NLMISC::CRGBA _OverColor; diff --git a/code/nel/src/gui/group_paragraph.cpp b/code/nel/src/gui/group_paragraph.cpp index d7d50af2e..63ebc967d 100644 --- a/code/nel/src/gui/group_paragraph.cpp +++ b/code/nel/src/gui/group_paragraph.cpp @@ -52,6 +52,7 @@ namespace NLGUI _MinW= 0; _MinH= 0; _Over = false; + _TempOver = false; _OverColor = CRGBA(255,255,255,32); _OverElt = -1; _LastW = 0; @@ -967,7 +968,7 @@ namespace NLGUI // TEMP TEMP //CViewRenderer &rVR = *CViewRenderer::getInstance(); //rVR.drawRotFlipBitmap _RenderLayer, (_XReal, _YReal, _WReal, _HReal, 0, false, rVR.getBlankTextureId(), CRGBA(0,255,0,255) ); - if (_Over) + if (_Over || _TempOver) { CViewRenderer &rVR = *CViewRenderer::getInstance(); @@ -1052,7 +1053,10 @@ namespace NLGUI _OverElt = -1; if (!isIn(eventDesc.getX(), eventDesc.getY())) + { + _TempOver = false; return false; + } for (uint32 i = 0; i < _Elements.size(); ++i) if (_Elements[i].Element->getActive()) diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/interaction.xml b/code/ryzom/client/data/gamedev/interfaces_v3/interaction.xml index 90432bcff..ef30966ee 100644 --- a/code/ryzom/client/data/gamedev/interfaces_v3/interaction.xml +++ b/code/ryzom/client/data/gamedev/interfaces_v3/interaction.xml @@ -3173,4 +3173,13 @@ params="game:chatUrlBrowse()" /> + + + + diff --git a/code/ryzom/client/src/interface_v3/chat_text_manager.cpp b/code/ryzom/client/src/interface_v3/chat_text_manager.cpp index ae76eefcb..51179cf29 100644 --- a/code/ryzom/client/src/interface_v3/chat_text_manager.cpp +++ b/code/ryzom/client/src/interface_v3/chat_text_manager.cpp @@ -28,6 +28,9 @@ using namespace NLMISC; CChatTextManager* CChatTextManager::_Instance = NULL; +// last selected chat from 'copy_chat_popup' action handler +static std::string LastSelectedChat; + //================================================================================= CChatTextManager::CChatTextManager() : _TextFontSize(NULL), @@ -403,7 +406,7 @@ CViewBase *CChatTextManager::createMsgTextComplex(const ucstring &msg, NLMISC::C para->setResizeFromChildH(true); // use right click because left click might be used to activate chat window - para->setRightClickHandler("copy_to_clipboard"); + para->setRightClickHandler("copy_chat_popup"); para->setRightClickHandlerParams(msg.toUtf8()); if (plaintext) @@ -526,3 +529,41 @@ void CChatTextManager::reset () _TextShadowed = NULL; _ShowTimestamps = NULL; } + +// *************************************************************************** +// Called when we right click on a chat line +class CHandlerCopyChatPopup: public IActionHandler +{ +public: + virtual void execute(CCtrlBase *pCaller, const string ¶ms ) + { + if (pCaller == NULL) return; + + LastSelectedChat = params; + + CGroupParagraph *pGP = dynamic_cast(pCaller); + if (pGP) pGP->enableTempOver(); + + CWidgetManager::getInstance()->enableModalWindow (pCaller, "ui:interface:chat_copy_action_menu"); + } +}; +REGISTER_ACTION_HANDLER( CHandlerCopyChatPopup, "copy_chat_popup"); + +// *************************************************************************** +// Called when we right click on a chat line and choose 'copy' from context menu +class CHandlerCopyChat: public IActionHandler +{ +public: + virtual void execute(CCtrlBase *pCaller, const string ¶ms ) + { + if (pCaller == NULL) return; + + CGroupParagraph *pGP = dynamic_cast(pCaller); + if (pGP) pGP->disableTempOver(); + + CAHManager::getInstance()->runActionHandler("copy_to_clipboard", NULL, LastSelectedChat); + CWidgetManager::getInstance()->disableModalWindow(); + } +}; +REGISTER_ACTION_HANDLER( CHandlerCopyChat, "copy_chat"); + From 3a5bb966a85909418968f68473786cd9958e3b17 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Wed, 13 Feb 2019 14:52:07 +0200 Subject: [PATCH 082/108] Fixed: ffmpeg deocde getLength function was placeholder --HG-- branch : develop --- code/nel/src/sound/audio_decoder_ffmpeg.cpp | 26 +++++++++------------ 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/code/nel/src/sound/audio_decoder_ffmpeg.cpp b/code/nel/src/sound/audio_decoder_ffmpeg.cpp index dea8368d2..158c0ddc3 100644 --- a/code/nel/src/sound/audio_decoder_ffmpeg.cpp +++ b/code/nel/src/sound/audio_decoder_ffmpeg.cpp @@ -287,18 +287,7 @@ bool CAudioDecoderFfmpeg::getInfo(NLMISC::IStream *stream, std::string &artist, } } - if (ffmpeg._FormatContext->duration != AV_NOPTS_VALUE) - { - length = ffmpeg._FormatContext->duration * av_q2d(AV_TIME_BASE_Q); - } - else if (ffmpeg._FormatContext->streams[ffmpeg._AudioStreamIndex]->duration != AV_NOPTS_VALUE) - { - length = ffmpeg._FormatContext->streams[ffmpeg._AudioStreamIndex]->duration * av_q2d(ffmpeg._FormatContext->streams[ffmpeg._AudioStreamIndex]->time_base); - } - else - { - length = 0.f; - } + length = ffmpeg.getLength(); return true; } @@ -420,9 +409,16 @@ bool CAudioDecoderFfmpeg::isMusicEnded() float CAudioDecoderFfmpeg::getLength() { - printf(">> CAudioDecoderFfmpeg::getLength\n"); - // TODO: return (float)ov_time_total(&_OggVorbisFile, -1); - return 0.f; + float length = 0.f; + if (_FormatContext->duration != AV_NOPTS_VALUE) + { + length = _FormatContext->duration * av_q2d(AV_TIME_BASE_Q); + } + else if (_FormatContext->streams[_AudioStreamIndex]->duration != AV_NOPTS_VALUE) + { + length = _FormatContext->streams[_AudioStreamIndex]->duration * av_q2d(_FormatContext->streams[_AudioStreamIndex]->time_base); + } + return length; } void CAudioDecoderFfmpeg::setLooping(bool loop) From 4a31913c60eba1284e0aabd2b4ba4e0309136260 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 14 Feb 2019 16:04:07 +0200 Subject: [PATCH 083/108] Changed: Target command to support keyword search. --HG-- branch : develop --- .../data/gamedev/interfaces_v3/commands.xml | 14 +-- code/ryzom/client/src/entities.cpp | 56 +++++++++ code/ryzom/client/src/entities.h | 6 + .../src/interface_v3/action_handler_game.cpp | 112 +++++++++--------- 4 files changed, 120 insertions(+), 68 deletions(-) diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/commands.xml b/code/ryzom/client/data/gamedev/interfaces_v3/commands.xml index 13356083c..e236e43c8 100644 --- a/code/ryzom/client/data/gamedev/interfaces_v3/commands.xml +++ b/code/ryzom/client/data/gamedev/interfaces_v3/commands.xml @@ -78,17 +78,11 @@ - - + + - - - - - - - - + + diff --git a/code/ryzom/client/src/entities.cpp b/code/ryzom/client/src/entities.cpp index 7d16e51dc..495471da1 100644 --- a/code/ryzom/client/src/entities.cpp +++ b/code/ryzom/client/src/entities.cpp @@ -2334,7 +2334,63 @@ CEntityCL *CEntityManager::getEntityByName (uint32 stringId) const } //----------------------------------------------- +CEntityCL *CEntityManager::getEntityByKeywords (const std::vector &keywords, bool onlySelectable) const +{ + if (keywords.empty()) return NULL; + std::vector lcKeywords; + lcKeywords.resize(keywords.size()); + for(uint k = 0; k < keywords.size(); k++) + { + lcKeywords[k] = toLower(keywords[k]); + } + + const NLMISC::CVectorD &userPosD = UserEntity->pos(); + const uint count = (uint)_Entities.size(); + uint selectedEntityId = 0; + float selectedEntityDist = FLT_MAX; + for(uint i = 0; i < count; ++i) + { + if (!_Entities[i]) continue; + + if (onlySelectable && !_Entities[i]->properties().selectable()) continue; + + ucstring lcName; + lcName = toLower(_Entities[i]->getDisplayName()); + if (lcName.empty()) continue; + + bool match = true; + for (uint k = 0; k < lcKeywords.size(); ++k) + { + if (lcName.find(lcKeywords[k]) == ucstring::npos) + { + match = false; + break; + } + } + + if (match) + { + const NLMISC::CVectorD &targetPosD = _Entities[i]->pos(); + + float deltaX = (float) targetPosD.x - (float) userPosD.x; + float deltaY = (float) targetPosD.y - (float) userPosD.y; + float dist = (float)sqrt(deltaX * deltaX + deltaY * deltaY); + if (dist < selectedEntityDist) + { + selectedEntityDist = dist; + selectedEntityId = i; + } + } + } + + if (selectedEntityDist != FLT_MAX) + return _Entities[selectedEntityId]; + else + return NULL; +} + +//----------------------------------------------- CEntityCL *CEntityManager::getEntityByName (const ucstring &name, bool caseSensitive, bool complete) const { ucstring source = name; diff --git a/code/ryzom/client/src/entities.h b/code/ryzom/client/src/entities.h index fb8504fc1..1151a367d 100644 --- a/code/ryzom/client/src/entities.h +++ b/code/ryzom/client/src/entities.h @@ -302,6 +302,12 @@ public: * \param complete : if true, the name must match the full name of the entity. */ CEntityCL *getEntityByName (const ucstring &name, bool caseSensitive, bool complete) const; + /** + * Case insensitive match against entity name. All listed keywords must match. + * \param keywords to match + * \param onlySelectable : if true, match only entity that can be selected + */ + CEntityCL *getEntityByKeywords (const std::vector &keywords, bool onlySelectable) const; CEntityCL *getEntityBySheetName (const std::string &sheet) const; /// Get an entity by dataset index. Returns NULL if the entity is not found. CEntityCL *getEntityByCompressedIndex(TDataSetIndex compressedIndex) const; diff --git a/code/ryzom/client/src/interface_v3/action_handler_game.cpp b/code/ryzom/client/src/interface_v3/action_handler_game.cpp index c5faa16d9..2f91cb9a5 100644 --- a/code/ryzom/client/src/interface_v3/action_handler_game.cpp +++ b/code/ryzom/client/src/interface_v3/action_handler_game.cpp @@ -2416,70 +2416,66 @@ class CAHTarget : public IActionHandler { virtual void execute (CCtrlBase * /* pCaller */, const string &Params) { - // Get the entity name to target ucstring entityName; - entityName.fromUtf8 (getParam (Params, "entity")); - bool preferCompleteMatch = (getParam (Params, "prefer_complete_match") != "0"); + entityName.fromUtf8(getParam(Params, "entity")); + if (entityName.empty()) return; + + string completeMatch = getParam(Params, "prefer_complete_match"); bool quiet = (getParam (Params, "quiet") == "true"); - if (!entityName.empty()) + vector keywords; + NLMISC::splitUCString(entityName, ucstring(" "), keywords); + if (!keywords.empty() && keywords[0].size() > 0 && keywords[0][0] == (ucchar)'"') { - CEntityCL *entity = NULL; - if (preferCompleteMatch) - { - // Try to get the entity with complete match first - entity = EntitiesMngr.getEntityByName (entityName, false, true); - } - - if (entity == NULL) - { - // Get the entity with a partial match - entity = EntitiesMngr.getEntityByName (entityName, false, false); - } + // entity name is in quotes, do old style match with 'starts with' filter + // search for optional second parameter from old command for prefer_complete_match param + keywords.clear(); - if (entity == NULL) - { - //Get the entity with a sheetName - entity = EntitiesMngr.getEntityBySheetName(entityName.toUtf8()); - } - - if (entity) - { - CCharacterCL *character = dynamic_cast(entity); - if (character != NULL) - { - if(character->isSelectableBySpace()) - { - nldebug("isSelectableBySpace"); - } - else - { - nldebug("is not isSelectableBySpace"); - } - } - if(entity->properties().selectable()) - { - nldebug("is prop selectable"); - } - else - { - // to avoid campfire selection exploit #316 - nldebug("is not prop selectable"); - CInterfaceManager *pIM= CInterfaceManager::getInstance(); - if(!quiet) - pIM->displaySystemInfo(CI18N::get("uiTargetErrorCmd")); - return; - } + ucstring::size_type lastOf = entityName.rfind(ucstring("\"")); + if (lastOf == 0) + lastOf = ucstring::npos; - // Select the entity - UserEntity->selection(entity->slot()); - } - else - { - CInterfaceManager *pIM= CInterfaceManager::getInstance(); - if(!quiet) - pIM->displaySystemInfo(CI18N::get("uiTargetErrorCmd")); - } + // override the value only when there is no 'prefer_complete_match' parameter set + if (completeMatch.empty() && lastOf < entityName.size()) + completeMatch = trim(entityName.substr(lastOf+1).toUtf8()); + + entityName = entityName.substr(1, lastOf-1); + } + + // late check because only possible if doing 'starts-with' search + bool preferCompleteMatch = (completeMatch != "0"); + + CEntityCL *entity = NULL; + if (preferCompleteMatch) + { + // Try to get the entity with complete match first + entity = EntitiesMngr.getEntityByName (entityName, false, true); + } + + if (entity == NULL && !keywords.empty()) + { + entity = EntitiesMngr.getEntityByKeywords(keywords, true); + } + + if (entity == NULL) + { + // Get the entity with a partial match using 'starts with' search + entity = EntitiesMngr.getEntityByName(entityName, false, false); + } + + if (entity == NULL) + { + //Get the entity with a sheetName + entity = EntitiesMngr.getEntityBySheetName(entityName.toUtf8()); + } + + if (entity && entity->properties().selectable() && !entity->getDisplayName().empty()) + { + UserEntity->selection(entity->slot()); + } + else if (!quiet) + { + CInterfaceManager::getInstance()->displaySystemInfo(CI18N::get("uiTargetErrorCmd")); } } }; From 1e6027c9708a137d36142ae65164fbb9b4a5fd0a Mon Sep 17 00:00:00 2001 From: Nimetu Date: Mon, 18 Feb 2019 13:46:39 +0200 Subject: [PATCH 084/108] Added: mp3 decoder based dr_mp3 single file library --HG-- branch : develop --- .../nel/include/nel/sound/audio_decoder_mp3.h | 96 + code/nel/include/nel/sound/decoder/dr_mp3.h | 3566 +++++++++++++++++ code/nel/src/sound/CMakeLists.txt | 1 + code/nel/src/sound/audio_decoder.cpp | 19 + code/nel/src/sound/audio_decoder_mp3.cpp | 221 + 5 files changed, 3903 insertions(+) create mode 100644 code/nel/include/nel/sound/audio_decoder_mp3.h create mode 100644 code/nel/include/nel/sound/decoder/dr_mp3.h create mode 100644 code/nel/src/sound/audio_decoder_mp3.cpp diff --git a/code/nel/include/nel/sound/audio_decoder_mp3.h b/code/nel/include/nel/sound/audio_decoder_mp3.h new file mode 100644 index 000000000..fac2e2693 --- /dev/null +++ b/code/nel/include/nel/sound/audio_decoder_mp3.h @@ -0,0 +1,96 @@ +// NeL - MMORPG Framework +// Copyright (C) 2018 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef NLSOUND_AUDIO_DECODER_MP3_H +#define NLSOUND_AUDIO_DECODER_MP3_H +#include + +#include + +// disable drmp3_init_file() +#define DR_MP3_NO_STDIO +#include + +namespace NLSOUND { + +/** + * \brief CAudioDecoderMP3 + * \date 2019-01-13 12:39GMT + * \author Meelis Mägi (Nimetu) + * CAudioDecoderMP3 + * Create trough IAudioDecoder, type "mp3" + */ +class CAudioDecoderMP3 : public IAudioDecoder +{ +protected: + NLMISC::IStream *_Stream; + + bool _IsSupported; + bool _Loop; + bool _IsMusicEnded; + sint32 _StreamOffset; + sint32 _StreamSize; + + drmp3 _Decoder; + + // set to total pcm frames after getLength() is called + uint64 _PCMFrameCount; + +public: + CAudioDecoderMP3(NLMISC::IStream *stream, bool loop); + virtual ~CAudioDecoderMP3(); + + inline NLMISC::IStream *getStream() { return _Stream; } + inline sint32 getStreamSize() { return _StreamSize; } + inline sint32 getStreamOffset() { return _StreamOffset; } + + // Return true if mp3 is valid + bool isFormatSupported() const; + + /// Get information on a music file (only ID3v1 tag is read. + static bool getInfo(NLMISC::IStream *stream, std::string &artist, std::string &title, float &length); + + /// Get how many bytes the music buffer requires for output minimum. + virtual uint32 getRequiredBytes(); + + /// Get an amount of bytes between minimum and maximum (can be lower than minimum if at end). + virtual uint32 getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum); + + /// Get the amount of channels (2 is stereo) in output. + virtual uint8 getChannels(); + + /// Get the samples per second (often 44100) in output. + virtual uint getSamplesPerSec(); + + /// Get the bits per sample (often 16) in output. + virtual uint8 getBitsPerSample(); + + /// Get if the music has ended playing (never true if loop). + virtual bool isMusicEnded(); + + /// Get the total time in seconds. + virtual float getLength(); + + /// Set looping + virtual void setLooping(bool loop); + +}; /* class CAudioDecoderMP3 */ + +} /* namespace NLSOUND */ + +#endif // NLSOUND_AUDIO_DECODER_MP3_H + +/* end of file */ diff --git a/code/nel/include/nel/sound/decoder/dr_mp3.h b/code/nel/include/nel/sound/decoder/dr_mp3.h new file mode 100644 index 000000000..465438bf5 --- /dev/null +++ b/code/nel/include/nel/sound/decoder/dr_mp3.h @@ -0,0 +1,3566 @@ +// MP3 audio decoder. Public domain. See "unlicense" statement at the end of this file. +// dr_mp3 - v0.4.1 - 2018-12-30 +// +// David Reid - mackron@gmail.com +// +// Based off minimp3 (https://github.com/lieff/minimp3) which is where the real work was done. See the bottom of this file for +// differences between minimp3 and dr_mp3. + +// USAGE +// ===== +// dr_mp3 is a single-file library. To use it, do something like the following in one .c file. +// #define DR_MP3_IMPLEMENTATION +// #include "dr_mp3.h" +// +// You can then #include this file in other parts of the program as you would with any other header file. To decode audio data, +// do something like the following: +// +// drmp3 mp3; +// if (!drmp3_init_file(&mp3, "MySong.mp3", NULL)) { +// // Failed to open file +// } +// +// ... +// +// drmp3_uint64 framesRead = drmp3_read_pcm_frames_f32(pMP3, framesToRead, pFrames); +// +// The drmp3 object is transparent so you can get access to the channel count and sample rate like so: +// +// drmp3_uint32 channels = mp3.channels; +// drmp3_uint32 sampleRate = mp3.sampleRate; +// +// The third parameter of drmp3_init_file() in the example above allows you to control the output channel count and sample rate. It +// is a pointer to a drmp3_config object. Setting any of the variables of this object to 0 will cause dr_mp3 to use defaults. +// +// The example above initializes a decoder from a file, but you can also initialize it from a block of memory and read and seek +// callbacks with drmp3_init_memory() and drmp3_init() respectively. +// +// You do not need to do any annoying memory management when reading PCM frames - this is all managed internally. You can request +// any number of PCM frames in each call to drmp3_read_pcm_frames_f32() and it will return as many PCM frames as it can, up to the +// requested amount. +// +// You can also decode an entire file in one go with drmp3_open_and_read_f32(), drmp3_open_memory_and_read_f32() and +// drmp3_open_file_and_read_f32(). +// +// +// OPTIONS +// ======= +// #define these options before including this file. +// +// #define DR_MP3_NO_STDIO +// Disable drmp3_init_file(), etc. +// +// #define DR_MP3_NO_SIMD +// Disable SIMD optimizations. + +#ifndef dr_mp3_h +#define dr_mp3_h + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#if defined(_MSC_VER) && _MSC_VER < 1600 +typedef signed char drmp3_int8; +typedef unsigned char drmp3_uint8; +typedef signed short drmp3_int16; +typedef unsigned short drmp3_uint16; +typedef signed int drmp3_int32; +typedef unsigned int drmp3_uint32; +typedef signed __int64 drmp3_int64; +typedef unsigned __int64 drmp3_uint64; +#else +#include +typedef int8_t drmp3_int8; +typedef uint8_t drmp3_uint8; +typedef int16_t drmp3_int16; +typedef uint16_t drmp3_uint16; +typedef int32_t drmp3_int32; +typedef uint32_t drmp3_uint32; +typedef int64_t drmp3_int64; +typedef uint64_t drmp3_uint64; +#endif +typedef drmp3_uint8 drmp3_bool8; +typedef drmp3_uint32 drmp3_bool32; +#define DRMP3_TRUE 1 +#define DRMP3_FALSE 0 + +#define DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME 1152 +#define DRMP3_MAX_SAMPLES_PER_FRAME (DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME*2) + + +// Low Level Push API +// ================== +typedef struct +{ + int frame_bytes, channels, hz, layer, bitrate_kbps; +} drmp3dec_frame_info; + +typedef struct +{ + float mdct_overlap[2][9*32], qmf_state[15*2*32]; + int reserv, free_format_bytes; + unsigned char header[4], reserv_buf[511]; +} drmp3dec; + +// Initializes a low level decoder. +void drmp3dec_init(drmp3dec *dec); + +// Reads a frame from a low level decoder. +int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info); + +// Helper for converting between f32 and s16. +void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples); + + + + +// Main API (Pull API) +// =================== + +typedef struct drmp3_src drmp3_src; +typedef drmp3_uint64 (* drmp3_src_read_proc)(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, void* pUserData); // Returns the number of frames that were read. + +typedef enum +{ + drmp3_src_algorithm_none, + drmp3_src_algorithm_linear +} drmp3_src_algorithm; + +#define DRMP3_SRC_CACHE_SIZE_IN_FRAMES 512 +typedef struct +{ + drmp3_src* pSRC; + float pCachedFrames[2 * DRMP3_SRC_CACHE_SIZE_IN_FRAMES]; + drmp3_uint32 cachedFrameCount; + drmp3_uint32 iNextFrame; +} drmp3_src_cache; + +typedef struct +{ + drmp3_uint32 sampleRateIn; + drmp3_uint32 sampleRateOut; + drmp3_uint32 channels; + drmp3_src_algorithm algorithm; + drmp3_uint32 cacheSizeInFrames; // The number of frames to read from the client at a time. +} drmp3_src_config; + +struct drmp3_src +{ + drmp3_src_config config; + drmp3_src_read_proc onRead; + void* pUserData; + float bin[256]; + drmp3_src_cache cache; // <-- For simplifying and optimizing client -> memory reading. + union + { + struct + { + double alpha; + drmp3_bool32 isPrevFramesLoaded : 1; + drmp3_bool32 isNextFramesLoaded : 1; + } linear; + } algo; +}; + +typedef enum +{ + drmp3_seek_origin_start, + drmp3_seek_origin_current +} drmp3_seek_origin; + +typedef struct +{ + drmp3_uint64 seekPosInBytes; // Points to the first byte of an MP3 frame. + drmp3_uint64 pcmFrameIndex; // The index of the PCM frame this seek point targets. + drmp3_uint16 mp3FramesToDiscard; // The number of whole MP3 frames to be discarded before pcmFramesToDiscard. + drmp3_uint16 pcmFramesToDiscard; // The number of leading samples to read and discard. These are discarded after mp3FramesToDiscard. +} drmp3_seek_point; + +// Callback for when data is read. Return value is the number of bytes actually read. +// +// pUserData [in] The user data that was passed to drmp3_init(), drmp3_open() and family. +// pBufferOut [out] The output buffer. +// bytesToRead [in] The number of bytes to read. +// +// Returns the number of bytes actually read. +// +// A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback until +// either the entire bytesToRead is filled or you have reached the end of the stream. +typedef size_t (* drmp3_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); + +// Callback for when data needs to be seeked. +// +// pUserData [in] The user data that was passed to drmp3_init(), drmp3_open() and family. +// offset [in] The number of bytes to move, relative to the origin. Will never be negative. +// origin [in] The origin of the seek - the current position or the start of the stream. +// +// Returns whether or not the seek was successful. +// +// Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which +// will be either drmp3_seek_origin_start or drmp3_seek_origin_current. +typedef drmp3_bool32 (* drmp3_seek_proc)(void* pUserData, int offset, drmp3_seek_origin origin); + +typedef struct +{ + drmp3_uint32 outputChannels; + drmp3_uint32 outputSampleRate; +} drmp3_config; + +typedef struct +{ + drmp3dec decoder; + drmp3dec_frame_info frameInfo; + drmp3_uint32 channels; + drmp3_uint32 sampleRate; + drmp3_read_proc onRead; + drmp3_seek_proc onSeek; + void* pUserData; + drmp3_uint32 mp3FrameChannels; // The number of channels in the currently loaded MP3 frame. Internal use only. + drmp3_uint32 mp3FrameSampleRate; // The sample rate of the currently loaded MP3 frame. Internal use only. + drmp3_uint32 pcmFramesConsumedInMP3Frame; + drmp3_uint32 pcmFramesRemainingInMP3Frame; + drmp3_uint8 pcmFrames[sizeof(float)*DRMP3_MAX_SAMPLES_PER_FRAME]; // <-- Multipled by sizeof(float) to ensure there's enough room for DR_MP3_FLOAT_OUTPUT. + drmp3_uint64 currentPCMFrame; // The current PCM frame, globally, based on the output sample rate. Mainly used for seeking. + drmp3_uint64 streamCursor; // The current byte the decoder is sitting on in the raw stream. + drmp3_src src; + drmp3_seek_point* pSeekPoints; // NULL by default. Set with drmp3_bind_seek_table(). Memory is owned by the client. dr_mp3 will never attempt to free this pointer. + drmp3_uint32 seekPointCount; // The number of items in pSeekPoints. When set to 0 assumes to no seek table. Defaults to zero. + size_t dataSize; + size_t dataCapacity; + drmp3_uint8* pData; + drmp3_bool32 atEnd : 1; + struct + { + const drmp3_uint8* pData; + size_t dataSize; + size_t currentReadPos; + } memory; // Only used for decoders that were opened against a block of memory. +} drmp3; + +// Initializes an MP3 decoder. +// +// onRead [in] The function to call when data needs to be read from the client. +// onSeek [in] The function to call when the read position of the client data needs to move. +// pUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek. +// +// Returns true if successful; false otherwise. +// +// Close the loader with drmp3_uninit(). +// +// See also: drmp3_init_file(), drmp3_init_memory(), drmp3_uninit() +drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_config* pConfig); + +// Initializes an MP3 decoder from a block of memory. +// +// This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for +// the lifetime of the drmp3 object. +// +// The buffer should contain the contents of the entire MP3 file. +drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_config* pConfig); + +#ifndef DR_MP3_NO_STDIO +// Initializes an MP3 decoder from a file. +// +// This holds the internal FILE object until drmp3_uninit() is called. Keep this in mind if you're caching drmp3 +// objects because the operating system may restrict the number of file handles an application can have open at +// any given time. +drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* filePath, const drmp3_config* pConfig); +#endif + +// Uninitializes an MP3 decoder. +void drmp3_uninit(drmp3* pMP3); + +// Reads PCM frames as interleaved 32-bit IEEE floating point PCM. +// +// Note that framesToRead specifies the number of PCM frames to read, _not_ the number of MP3 frames. +drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut); + +// Seeks to a specific frame. +// +// Note that this is _not_ an MP3 frame, but rather a PCM frame. +drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex); + +// Calculates the total number of PCM frames in the MP3 stream. Cannot be used for infinite streams such as internet +// radio. Runs in linear time. Returns 0 on error. +drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3); + +// Calculates the total number of MP3 frames in the MP3 stream. Cannot be used for infinite streams such as internet +// radio. Runs in linear time. Returns 0 on error. +drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3); + +// Calculates the seekpoints based on PCM frames. This is slow. +// +// pSeekpoint count is a pointer to a uint32 containing the seekpoint count. On input it contains the desired count. +// On output it contains the actual count. The reason for this design is that the client may request too many +// seekpoints, in which case dr_mp3 will return a corrected count. +// +// Note that seektable seeking is not quite sample exact when the MP3 stream contains inconsistent sample rates. +drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCount, drmp3_seek_point* pSeekPoints); + +// Binds a seek table to the decoder. +// +// This does _not_ make a copy of pSeekPoints - it only references it. It is up to the application to ensure this +// remains valid while it is bound to the decoder. +// +// Use drmp3_calculate_seek_points() to calculate the seek points. +drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPointCount, drmp3_seek_point* pSeekPoints); + + + +// Opens an decodes an entire MP3 stream as a single operation. +// +// pConfig is both an input and output. On input it contains what you want. On output it contains what you got. +// +// Free the returned pointer with drmp3_free(). +float* drmp3_open_and_read_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount); +float* drmp3_open_memory_and_read_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount); +#ifndef DR_MP3_NO_STDIO +float* drmp3_open_file_and_read_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount); +#endif + +// Frees any memory that was allocated by a public drmp3 API. +void drmp3_free(void* p); + +#ifdef __cplusplus +} +#endif +#endif // dr_mp3_h + + +///////////////////////////////////////////////////// +// +// IMPLEMENTATION +// +///////////////////////////////////////////////////// +#ifdef DR_MP3_IMPLEMENTATION +#include +#include +#include +#include // For INT_MAX + +// Disable SIMD when compiling with TCC for now. +#if defined(__TINYC__) +#define DR_MP3_NO_SIMD +#endif + +#define DRMP3_OFFSET_PTR(p, offset) ((void*)((drmp3_uint8*)(p) + (offset))) + +#define DRMP3_MAX_FREE_FORMAT_FRAME_SIZE 2304 /* more than ISO spec's */ +#ifndef DRMP3_MAX_FRAME_SYNC_MATCHES +#define DRMP3_MAX_FRAME_SYNC_MATCHES 10 +#endif + +#define DRMP3_MAX_L3_FRAME_PAYLOAD_BYTES DRMP3_MAX_FREE_FORMAT_FRAME_SIZE /* MUST be >= 320000/8/32000*1152 = 1440 */ + +#define DRMP3_MAX_BITRESERVOIR_BYTES 511 +#define DRMP3_SHORT_BLOCK_TYPE 2 +#define DRMP3_STOP_BLOCK_TYPE 3 +#define DRMP3_MODE_MONO 3 +#define DRMP3_MODE_JOINT_STEREO 1 +#define DRMP3_HDR_SIZE 4 +#define DRMP3_HDR_IS_MONO(h) (((h[3]) & 0xC0) == 0xC0) +#define DRMP3_HDR_IS_MS_STEREO(h) (((h[3]) & 0xE0) == 0x60) +#define DRMP3_HDR_IS_FREE_FORMAT(h) (((h[2]) & 0xF0) == 0) +#define DRMP3_HDR_IS_CRC(h) (!((h[1]) & 1)) +#define DRMP3_HDR_TEST_PADDING(h) ((h[2]) & 0x2) +#define DRMP3_HDR_TEST_MPEG1(h) ((h[1]) & 0x8) +#define DRMP3_HDR_TEST_NOT_MPEG25(h) ((h[1]) & 0x10) +#define DRMP3_HDR_TEST_I_STEREO(h) ((h[3]) & 0x10) +#define DRMP3_HDR_TEST_MS_STEREO(h) ((h[3]) & 0x20) +#define DRMP3_HDR_GET_STEREO_MODE(h) (((h[3]) >> 6) & 3) +#define DRMP3_HDR_GET_STEREO_MODE_EXT(h) (((h[3]) >> 4) & 3) +#define DRMP3_HDR_GET_LAYER(h) (((h[1]) >> 1) & 3) +#define DRMP3_HDR_GET_BITRATE(h) ((h[2]) >> 4) +#define DRMP3_HDR_GET_SAMPLE_RATE(h) (((h[2]) >> 2) & 3) +#define DRMP3_HDR_GET_MY_SAMPLE_RATE(h) (DRMP3_HDR_GET_SAMPLE_RATE(h) + (((h[1] >> 3) & 1) + ((h[1] >> 4) & 1))*3) +#define DRMP3_HDR_IS_FRAME_576(h) ((h[1] & 14) == 2) +#define DRMP3_HDR_IS_LAYER_1(h) ((h[1] & 6) == 6) + +#define DRMP3_BITS_DEQUANTIZER_OUT -1 +#define DRMP3_MAX_SCF (255 + DRMP3_BITS_DEQUANTIZER_OUT*4 - 210) +#define DRMP3_MAX_SCFI ((DRMP3_MAX_SCF + 3) & ~3) + +#define DRMP3_MIN(a, b) ((a) > (b) ? (b) : (a)) +#define DRMP3_MAX(a, b) ((a) < (b) ? (b) : (a)) + +#if !defined(DR_MP3_NO_SIMD) + +#if !defined(DR_MP3_ONLY_SIMD) && (defined(_M_X64) || defined(_M_ARM64) || defined(__x86_64__) || defined(__aarch64__)) +/* x64 always have SSE2, arm64 always have neon, no need for generic code */ +#define DR_MP3_ONLY_SIMD +#endif + +#if (defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))) || ((defined(__i386__) || defined(__x86_64__)) && defined(__SSE2__)) +#if defined(_MSC_VER) +#include +#endif +#include +#define DRMP3_HAVE_SSE 1 +#define DRMP3_HAVE_SIMD 1 +#define DRMP3_VSTORE _mm_storeu_ps +#define DRMP3_VLD _mm_loadu_ps +#define DRMP3_VSET _mm_set1_ps +#define DRMP3_VADD _mm_add_ps +#define DRMP3_VSUB _mm_sub_ps +#define DRMP3_VMUL _mm_mul_ps +#define DRMP3_VMAC(a, x, y) _mm_add_ps(a, _mm_mul_ps(x, y)) +#define DRMP3_VMSB(a, x, y) _mm_sub_ps(a, _mm_mul_ps(x, y)) +#define DRMP3_VMUL_S(x, s) _mm_mul_ps(x, _mm_set1_ps(s)) +#define DRMP3_VREV(x) _mm_shuffle_ps(x, x, _MM_SHUFFLE(0, 1, 2, 3)) +typedef __m128 drmp3_f4; +#if defined(_MSC_VER) || defined(DR_MP3_ONLY_SIMD) +#define drmp3_cpuid __cpuid +#else +static __inline__ __attribute__((always_inline)) void drmp3_cpuid(int CPUInfo[], const int InfoType) +{ +#if defined(__PIC__) + __asm__ __volatile__( +#if defined(__x86_64__) + "push %%rbx\n" + "cpuid\n" + "xchgl %%ebx, %1\n" + "pop %%rbx\n" +#else + "xchgl %%ebx, %1\n" + "cpuid\n" + "xchgl %%ebx, %1\n" +#endif + : "=a" (CPUInfo[0]), "=r" (CPUInfo[1]), "=c" (CPUInfo[2]), "=d" (CPUInfo[3]) + : "a" (InfoType)); +#else + __asm__ __volatile__( + "cpuid" + : "=a" (CPUInfo[0]), "=b" (CPUInfo[1]), "=c" (CPUInfo[2]), "=d" (CPUInfo[3]) + : "a" (InfoType)); +#endif +} +#endif +static int drmp3_have_simd() +{ +#ifdef DR_MP3_ONLY_SIMD + return 1; +#else + static int g_have_simd; + int CPUInfo[4]; +#ifdef MINIMP3_TEST + static int g_counter; + if (g_counter++ > 100) + return 0; +#endif + if (g_have_simd) + goto end; + drmp3_cpuid(CPUInfo, 0); + if (CPUInfo[0] > 0) + { + drmp3_cpuid(CPUInfo, 1); + g_have_simd = (CPUInfo[3] & (1 << 26)) + 1; /* SSE2 */ + return g_have_simd - 1; + } + +end: + return g_have_simd - 1; +#endif +} +#elif defined(__ARM_NEON) || defined(__aarch64__) +#include +#define DRMP3_HAVE_SIMD 1 +#define DRMP3_VSTORE vst1q_f32 +#define DRMP3_VLD vld1q_f32 +#define DRMP3_VSET vmovq_n_f32 +#define DRMP3_VADD vaddq_f32 +#define DRMP3_VSUB vsubq_f32 +#define DRMP3_VMUL vmulq_f32 +#define DRMP3_VMAC(a, x, y) vmlaq_f32(a, x, y) +#define DRMP3_VMSB(a, x, y) vmlsq_f32(a, x, y) +#define DRMP3_VMUL_S(x, s) vmulq_f32(x, vmovq_n_f32(s)) +#define DRMP3_VREV(x) vcombine_f32(vget_high_f32(vrev64q_f32(x)), vget_low_f32(vrev64q_f32(x))) +typedef float32x4_t drmp3_f4; +static int drmp3_have_simd() +{ /* TODO: detect neon for !DR_MP3_ONLY_SIMD */ + return 1; +} +#else +#define DRMP3_HAVE_SIMD 0 +#ifdef DR_MP3_ONLY_SIMD +#error DR_MP3_ONLY_SIMD used, but SSE/NEON not enabled +#endif +#endif + +#else + +#define DRMP3_HAVE_SIMD 0 + +#endif + +typedef struct +{ + const drmp3_uint8 *buf; + int pos, limit; +} drmp3_bs; + +typedef struct +{ + float scf[3*64]; + drmp3_uint8 total_bands, stereo_bands, bitalloc[64], scfcod[64]; +} drmp3_L12_scale_info; + +typedef struct +{ + drmp3_uint8 tab_offset, code_tab_width, band_count; +} drmp3_L12_subband_alloc; + +typedef struct +{ + const drmp3_uint8 *sfbtab; + drmp3_uint16 part_23_length, big_values, scalefac_compress; + drmp3_uint8 global_gain, block_type, mixed_block_flag, n_long_sfb, n_short_sfb; + drmp3_uint8 table_select[3], region_count[3], subblock_gain[3]; + drmp3_uint8 preflag, scalefac_scale, count1_table, scfsi; +} drmp3_L3_gr_info; + +typedef struct +{ + drmp3_bs bs; + drmp3_uint8 maindata[DRMP3_MAX_BITRESERVOIR_BYTES + DRMP3_MAX_L3_FRAME_PAYLOAD_BYTES]; + drmp3_L3_gr_info gr_info[4]; + float grbuf[2][576], scf[40], syn[18 + 15][2*32]; + drmp3_uint8 ist_pos[2][39]; +} drmp3dec_scratch; + +static void drmp3_bs_init(drmp3_bs *bs, const drmp3_uint8 *data, int bytes) +{ + bs->buf = data; + bs->pos = 0; + bs->limit = bytes*8; +} + +static drmp3_uint32 drmp3_bs_get_bits(drmp3_bs *bs, int n) +{ + drmp3_uint32 next, cache = 0, s = bs->pos & 7; + int shl = n + s; + const drmp3_uint8 *p = bs->buf + (bs->pos >> 3); + if ((bs->pos += n) > bs->limit) + return 0; + next = *p++ & (255 >> s); + while ((shl -= 8) > 0) + { + cache |= next << shl; + next = *p++; + } + return cache | (next >> -shl); +} + +static int drmp3_hdr_valid(const drmp3_uint8 *h) +{ + return h[0] == 0xff && + ((h[1] & 0xF0) == 0xf0 || (h[1] & 0xFE) == 0xe2) && + (DRMP3_HDR_GET_LAYER(h) != 0) && + (DRMP3_HDR_GET_BITRATE(h) != 15) && + (DRMP3_HDR_GET_SAMPLE_RATE(h) != 3); +} + +static int drmp3_hdr_compare(const drmp3_uint8 *h1, const drmp3_uint8 *h2) +{ + return drmp3_hdr_valid(h2) && + ((h1[1] ^ h2[1]) & 0xFE) == 0 && + ((h1[2] ^ h2[2]) & 0x0C) == 0 && + !(DRMP3_HDR_IS_FREE_FORMAT(h1) ^ DRMP3_HDR_IS_FREE_FORMAT(h2)); +} + +static unsigned drmp3_hdr_bitrate_kbps(const drmp3_uint8 *h) +{ + static const drmp3_uint8 halfrate[2][3][15] = { + { { 0,4,8,12,16,20,24,28,32,40,48,56,64,72,80 }, { 0,4,8,12,16,20,24,28,32,40,48,56,64,72,80 }, { 0,16,24,28,32,40,48,56,64,72,80,88,96,112,128 } }, + { { 0,16,20,24,28,32,40,48,56,64,80,96,112,128,160 }, { 0,16,24,28,32,40,48,56,64,80,96,112,128,160,192 }, { 0,16,32,48,64,80,96,112,128,144,160,176,192,208,224 } }, + }; + return 2*halfrate[!!DRMP3_HDR_TEST_MPEG1(h)][DRMP3_HDR_GET_LAYER(h) - 1][DRMP3_HDR_GET_BITRATE(h)]; +} + +static unsigned drmp3_hdr_sample_rate_hz(const drmp3_uint8 *h) +{ + static const unsigned g_hz[3] = { 44100, 48000, 32000 }; + return g_hz[DRMP3_HDR_GET_SAMPLE_RATE(h)] >> (int)!DRMP3_HDR_TEST_MPEG1(h) >> (int)!DRMP3_HDR_TEST_NOT_MPEG25(h); +} + +static unsigned drmp3_hdr_frame_samples(const drmp3_uint8 *h) +{ + return DRMP3_HDR_IS_LAYER_1(h) ? 384 : (1152 >> (int)DRMP3_HDR_IS_FRAME_576(h)); +} + +static int drmp3_hdr_frame_bytes(const drmp3_uint8 *h, int free_format_size) +{ + int frame_bytes = drmp3_hdr_frame_samples(h)*drmp3_hdr_bitrate_kbps(h)*125/drmp3_hdr_sample_rate_hz(h); + if (DRMP3_HDR_IS_LAYER_1(h)) + { + frame_bytes &= ~3; /* slot align */ + } + return frame_bytes ? frame_bytes : free_format_size; +} + +static int drmp3_hdr_padding(const drmp3_uint8 *h) +{ + return DRMP3_HDR_TEST_PADDING(h) ? (DRMP3_HDR_IS_LAYER_1(h) ? 4 : 1) : 0; +} + +#ifndef DR_MP3_ONLY_MP3 +static const drmp3_L12_subband_alloc *drmp3_L12_subband_alloc_table(const drmp3_uint8 *hdr, drmp3_L12_scale_info *sci) +{ + const drmp3_L12_subband_alloc *alloc; + int mode = DRMP3_HDR_GET_STEREO_MODE(hdr); + int nbands, stereo_bands = (mode == DRMP3_MODE_MONO) ? 0 : (mode == DRMP3_MODE_JOINT_STEREO) ? (DRMP3_HDR_GET_STEREO_MODE_EXT(hdr) << 2) + 4 : 32; + + if (DRMP3_HDR_IS_LAYER_1(hdr)) + { + static const drmp3_L12_subband_alloc g_alloc_L1[] = { { 76, 4, 32 } }; + alloc = g_alloc_L1; + nbands = 32; + } else if (!DRMP3_HDR_TEST_MPEG1(hdr)) + { + static const drmp3_L12_subband_alloc g_alloc_L2M2[] = { { 60, 4, 4 }, { 44, 3, 7 }, { 44, 2, 19 } }; + alloc = g_alloc_L2M2; + nbands = 30; + } else + { + static const drmp3_L12_subband_alloc g_alloc_L2M1[] = { { 0, 4, 3 }, { 16, 4, 8 }, { 32, 3, 12 }, { 40, 2, 7 } }; + int sample_rate_idx = DRMP3_HDR_GET_SAMPLE_RATE(hdr); + unsigned kbps = drmp3_hdr_bitrate_kbps(hdr) >> (int)(mode != DRMP3_MODE_MONO); + if (!kbps) /* free-format */ + { + kbps = 192; + } + + alloc = g_alloc_L2M1; + nbands = 27; + if (kbps < 56) + { + static const drmp3_L12_subband_alloc g_alloc_L2M1_lowrate[] = { { 44, 4, 2 }, { 44, 3, 10 } }; + alloc = g_alloc_L2M1_lowrate; + nbands = sample_rate_idx == 2 ? 12 : 8; + } else if (kbps >= 96 && sample_rate_idx != 1) + { + nbands = 30; + } + } + + sci->total_bands = (drmp3_uint8)nbands; + sci->stereo_bands = (drmp3_uint8)DRMP3_MIN(stereo_bands, nbands); + + return alloc; +} + +static void drmp3_L12_read_scalefactors(drmp3_bs *bs, drmp3_uint8 *pba, drmp3_uint8 *scfcod, int bands, float *scf) +{ + static const float g_deq_L12[18*3] = { +#define DRMP3_DQ(x) 9.53674316e-07f/x, 7.56931807e-07f/x, 6.00777173e-07f/x + DRMP3_DQ(3),DRMP3_DQ(7),DRMP3_DQ(15),DRMP3_DQ(31),DRMP3_DQ(63),DRMP3_DQ(127),DRMP3_DQ(255),DRMP3_DQ(511),DRMP3_DQ(1023),DRMP3_DQ(2047),DRMP3_DQ(4095),DRMP3_DQ(8191),DRMP3_DQ(16383),DRMP3_DQ(32767),DRMP3_DQ(65535),DRMP3_DQ(3),DRMP3_DQ(5),DRMP3_DQ(9) + }; + int i, m; + for (i = 0; i < bands; i++) + { + float s = 0; + int ba = *pba++; + int mask = ba ? 4 + ((19 >> scfcod[i]) & 3) : 0; + for (m = 4; m; m >>= 1) + { + if (mask & m) + { + int b = drmp3_bs_get_bits(bs, 6); + s = g_deq_L12[ba*3 - 6 + b % 3]*(1 << 21 >> b/3); + } + *scf++ = s; + } + } +} + +static void drmp3_L12_read_scale_info(const drmp3_uint8 *hdr, drmp3_bs *bs, drmp3_L12_scale_info *sci) +{ + static const drmp3_uint8 g_bitalloc_code_tab[] = { + 0,17, 3, 4, 5,6,7, 8,9,10,11,12,13,14,15,16, + 0,17,18, 3,19,4,5, 6,7, 8, 9,10,11,12,13,16, + 0,17,18, 3,19,4,5,16, + 0,17,18,16, + 0,17,18,19, 4,5,6, 7,8, 9,10,11,12,13,14,15, + 0,17,18, 3,19,4,5, 6,7, 8, 9,10,11,12,13,14, + 0, 2, 3, 4, 5,6,7, 8,9,10,11,12,13,14,15,16 + }; + const drmp3_L12_subband_alloc *subband_alloc = drmp3_L12_subband_alloc_table(hdr, sci); + + int i, k = 0, ba_bits = 0; + const drmp3_uint8 *ba_code_tab = g_bitalloc_code_tab; + + for (i = 0; i < sci->total_bands; i++) + { + drmp3_uint8 ba; + if (i == k) + { + k += subband_alloc->band_count; + ba_bits = subband_alloc->code_tab_width; + ba_code_tab = g_bitalloc_code_tab + subband_alloc->tab_offset; + subband_alloc++; + } + ba = ba_code_tab[drmp3_bs_get_bits(bs, ba_bits)]; + sci->bitalloc[2*i] = ba; + if (i < sci->stereo_bands) + { + ba = ba_code_tab[drmp3_bs_get_bits(bs, ba_bits)]; + } + sci->bitalloc[2*i + 1] = sci->stereo_bands ? ba : 0; + } + + for (i = 0; i < 2*sci->total_bands; i++) + { + sci->scfcod[i] = (drmp3_uint8)(sci->bitalloc[i] ? DRMP3_HDR_IS_LAYER_1(hdr) ? 2 : drmp3_bs_get_bits(bs, 2) : 6); + } + + drmp3_L12_read_scalefactors(bs, sci->bitalloc, sci->scfcod, sci->total_bands*2, sci->scf); + + for (i = sci->stereo_bands; i < sci->total_bands; i++) + { + sci->bitalloc[2*i + 1] = 0; + } +} + +static int drmp3_L12_dequantize_granule(float *grbuf, drmp3_bs *bs, drmp3_L12_scale_info *sci, int group_size) +{ + int i, j, k, choff = 576; + for (j = 0; j < 4; j++) + { + float *dst = grbuf + group_size*j; + for (i = 0; i < 2*sci->total_bands; i++) + { + int ba = sci->bitalloc[i]; + if (ba != 0) + { + if (ba < 17) + { + int half = (1 << (ba - 1)) - 1; + for (k = 0; k < group_size; k++) + { + dst[k] = (float)((int)drmp3_bs_get_bits(bs, ba) - half); + } + } else + { + unsigned mod = (2 << (ba - 17)) + 1; /* 3, 5, 9 */ + unsigned code = drmp3_bs_get_bits(bs, mod + 2 - (mod >> 3)); /* 5, 7, 10 */ + for (k = 0; k < group_size; k++, code /= mod) + { + dst[k] = (float)((int)(code % mod - mod/2)); + } + } + } + dst += choff; + choff = 18 - choff; + } + } + return group_size*4; +} + +static void drmp3_L12_apply_scf_384(drmp3_L12_scale_info *sci, const float *scf, float *dst) +{ + int i, k; + memcpy(dst + 576 + sci->stereo_bands*18, dst + sci->stereo_bands*18, (sci->total_bands - sci->stereo_bands)*18*sizeof(float)); + for (i = 0; i < sci->total_bands; i++, dst += 18, scf += 6) + { + for (k = 0; k < 12; k++) + { + dst[k + 0] *= scf[0]; + dst[k + 576] *= scf[3]; + } + } +} +#endif + +static int drmp3_L3_read_side_info(drmp3_bs *bs, drmp3_L3_gr_info *gr, const drmp3_uint8 *hdr) +{ + static const drmp3_uint8 g_scf_long[8][23] = { + { 6,6,6,6,6,6,8,10,12,14,16,20,24,28,32,38,46,52,60,68,58,54,0 }, + { 12,12,12,12,12,12,16,20,24,28,32,40,48,56,64,76,90,2,2,2,2,2,0 }, + { 6,6,6,6,6,6,8,10,12,14,16,20,24,28,32,38,46,52,60,68,58,54,0 }, + { 6,6,6,6,6,6,8,10,12,14,16,18,22,26,32,38,46,54,62,70,76,36,0 }, + { 6,6,6,6,6,6,8,10,12,14,16,20,24,28,32,38,46,52,60,68,58,54,0 }, + { 4,4,4,4,4,4,6,6,8,8,10,12,16,20,24,28,34,42,50,54,76,158,0 }, + { 4,4,4,4,4,4,6,6,6,8,10,12,16,18,22,28,34,40,46,54,54,192,0 }, + { 4,4,4,4,4,4,6,6,8,10,12,16,20,24,30,38,46,56,68,84,102,26,0 } + }; + static const drmp3_uint8 g_scf_short[8][40] = { + { 4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, + { 8,8,8,8,8,8,8,8,8,12,12,12,16,16,16,20,20,20,24,24,24,28,28,28,36,36,36,2,2,2,2,2,2,2,2,2,26,26,26,0 }, + { 4,4,4,4,4,4,4,4,4,6,6,6,6,6,6,8,8,8,10,10,10,14,14,14,18,18,18,26,26,26,32,32,32,42,42,42,18,18,18,0 }, + { 4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,32,32,32,44,44,44,12,12,12,0 }, + { 4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, + { 4,4,4,4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,22,22,22,30,30,30,56,56,56,0 }, + { 4,4,4,4,4,4,4,4,4,4,4,4,6,6,6,6,6,6,10,10,10,12,12,12,14,14,14,16,16,16,20,20,20,26,26,26,66,66,66,0 }, + { 4,4,4,4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,12,12,12,16,16,16,20,20,20,26,26,26,34,34,34,42,42,42,12,12,12,0 } + }; + static const drmp3_uint8 g_scf_mixed[8][40] = { + { 6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, + { 12,12,12,4,4,4,8,8,8,12,12,12,16,16,16,20,20,20,24,24,24,28,28,28,36,36,36,2,2,2,2,2,2,2,2,2,26,26,26,0 }, + { 6,6,6,6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,14,14,14,18,18,18,26,26,26,32,32,32,42,42,42,18,18,18,0 }, + { 6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,32,32,32,44,44,44,12,12,12,0 }, + { 6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, + { 4,4,4,4,4,4,6,6,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,22,22,22,30,30,30,56,56,56,0 }, + { 4,4,4,4,4,4,6,6,4,4,4,6,6,6,6,6,6,10,10,10,12,12,12,14,14,14,16,16,16,20,20,20,26,26,26,66,66,66,0 }, + { 4,4,4,4,4,4,6,6,4,4,4,6,6,6,8,8,8,12,12,12,16,16,16,20,20,20,26,26,26,34,34,34,42,42,42,12,12,12,0 } + }; + + unsigned tables, scfsi = 0; + int main_data_begin, part_23_sum = 0; + int sr_idx = DRMP3_HDR_GET_MY_SAMPLE_RATE(hdr); sr_idx -= (sr_idx != 0); + int gr_count = DRMP3_HDR_IS_MONO(hdr) ? 1 : 2; + + if (DRMP3_HDR_TEST_MPEG1(hdr)) + { + gr_count *= 2; + main_data_begin = drmp3_bs_get_bits(bs, 9); + scfsi = drmp3_bs_get_bits(bs, 7 + gr_count); + } else + { + main_data_begin = drmp3_bs_get_bits(bs, 8 + gr_count) >> gr_count; + } + + do + { + if (DRMP3_HDR_IS_MONO(hdr)) + { + scfsi <<= 4; + } + gr->part_23_length = (drmp3_uint16)drmp3_bs_get_bits(bs, 12); + part_23_sum += gr->part_23_length; + gr->big_values = (drmp3_uint16)drmp3_bs_get_bits(bs, 9); + if (gr->big_values > 288) + { + return -1; + } + gr->global_gain = (drmp3_uint8)drmp3_bs_get_bits(bs, 8); + gr->scalefac_compress = (drmp3_uint16)drmp3_bs_get_bits(bs, DRMP3_HDR_TEST_MPEG1(hdr) ? 4 : 9); + gr->sfbtab = g_scf_long[sr_idx]; + gr->n_long_sfb = 22; + gr->n_short_sfb = 0; + if (drmp3_bs_get_bits(bs, 1)) + { + gr->block_type = (drmp3_uint8)drmp3_bs_get_bits(bs, 2); + if (!gr->block_type) + { + return -1; + } + gr->mixed_block_flag = (drmp3_uint8)drmp3_bs_get_bits(bs, 1); + gr->region_count[0] = 7; + gr->region_count[1] = 255; + if (gr->block_type == DRMP3_SHORT_BLOCK_TYPE) + { + scfsi &= 0x0F0F; + if (!gr->mixed_block_flag) + { + gr->region_count[0] = 8; + gr->sfbtab = g_scf_short[sr_idx]; + gr->n_long_sfb = 0; + gr->n_short_sfb = 39; + } else + { + gr->sfbtab = g_scf_mixed[sr_idx]; + gr->n_long_sfb = DRMP3_HDR_TEST_MPEG1(hdr) ? 8 : 6; + gr->n_short_sfb = 30; + } + } + tables = drmp3_bs_get_bits(bs, 10); + tables <<= 5; + gr->subblock_gain[0] = (drmp3_uint8)drmp3_bs_get_bits(bs, 3); + gr->subblock_gain[1] = (drmp3_uint8)drmp3_bs_get_bits(bs, 3); + gr->subblock_gain[2] = (drmp3_uint8)drmp3_bs_get_bits(bs, 3); + } else + { + gr->block_type = 0; + gr->mixed_block_flag = 0; + tables = drmp3_bs_get_bits(bs, 15); + gr->region_count[0] = (drmp3_uint8)drmp3_bs_get_bits(bs, 4); + gr->region_count[1] = (drmp3_uint8)drmp3_bs_get_bits(bs, 3); + gr->region_count[2] = 255; + } + gr->table_select[0] = (drmp3_uint8)(tables >> 10); + gr->table_select[1] = (drmp3_uint8)((tables >> 5) & 31); + gr->table_select[2] = (drmp3_uint8)((tables) & 31); + gr->preflag = (drmp3_uint8)(DRMP3_HDR_TEST_MPEG1(hdr) ? drmp3_bs_get_bits(bs, 1) : (gr->scalefac_compress >= 500)); + gr->scalefac_scale = (drmp3_uint8)drmp3_bs_get_bits(bs, 1); + gr->count1_table = (drmp3_uint8)drmp3_bs_get_bits(bs, 1); + gr->scfsi = (drmp3_uint8)((scfsi >> 12) & 15); + scfsi <<= 4; + gr++; + } while(--gr_count); + + if (part_23_sum + bs->pos > bs->limit + main_data_begin*8) + { + return -1; + } + + return main_data_begin; +} + +static void drmp3_L3_read_scalefactors(drmp3_uint8 *scf, drmp3_uint8 *ist_pos, const drmp3_uint8 *scf_size, const drmp3_uint8 *scf_count, drmp3_bs *bitbuf, int scfsi) +{ + int i, k; + for (i = 0; i < 4 && scf_count[i]; i++, scfsi *= 2) + { + int cnt = scf_count[i]; + if (scfsi & 8) + { + memcpy(scf, ist_pos, cnt); + } else + { + int bits = scf_size[i]; + if (!bits) + { + memset(scf, 0, cnt); + memset(ist_pos, 0, cnt); + } else + { + int max_scf = (scfsi < 0) ? (1 << bits) - 1 : -1; + for (k = 0; k < cnt; k++) + { + int s = drmp3_bs_get_bits(bitbuf, bits); + ist_pos[k] = (drmp3_uint8)(s == max_scf ? -1 : s); + scf[k] = (drmp3_uint8)s; + } + } + } + ist_pos += cnt; + scf += cnt; + } + scf[0] = scf[1] = scf[2] = 0; +} + +static float drmp3_L3_ldexp_q2(float y, int exp_q2) +{ + static const float g_expfrac[4] = { 9.31322575e-10f,7.83145814e-10f,6.58544508e-10f,5.53767716e-10f }; + int e; + do + { + e = DRMP3_MIN(30*4, exp_q2); + y *= g_expfrac[e & 3]*(1 << 30 >> (e >> 2)); + } while ((exp_q2 -= e) > 0); + return y; +} + +static void drmp3_L3_decode_scalefactors(const drmp3_uint8 *hdr, drmp3_uint8 *ist_pos, drmp3_bs *bs, const drmp3_L3_gr_info *gr, float *scf, int ch) +{ + static const drmp3_uint8 g_scf_partitions[3][28] = { + { 6,5,5, 5,6,5,5,5,6,5, 7,3,11,10,0,0, 7, 7, 7,0, 6, 6,6,3, 8, 8,5,0 }, + { 8,9,6,12,6,9,9,9,6,9,12,6,15,18,0,0, 6,15,12,0, 6,12,9,6, 6,18,9,0 }, + { 9,9,6,12,9,9,9,9,9,9,12,6,18,18,0,0,12,12,12,0,12, 9,9,6,15,12,9,0 } + }; + const drmp3_uint8 *scf_partition = g_scf_partitions[!!gr->n_short_sfb + !gr->n_long_sfb]; + drmp3_uint8 scf_size[4], iscf[40]; + int i, scf_shift = gr->scalefac_scale + 1, gain_exp, scfsi = gr->scfsi; + float gain; + + if (DRMP3_HDR_TEST_MPEG1(hdr)) + { + static const drmp3_uint8 g_scfc_decode[16] = { 0,1,2,3, 12,5,6,7, 9,10,11,13, 14,15,18,19 }; + int part = g_scfc_decode[gr->scalefac_compress]; + scf_size[1] = scf_size[0] = (drmp3_uint8)(part >> 2); + scf_size[3] = scf_size[2] = (drmp3_uint8)(part & 3); + } else + { + static const drmp3_uint8 g_mod[6*4] = { 5,5,4,4,5,5,4,1,4,3,1,1,5,6,6,1,4,4,4,1,4,3,1,1 }; + int k, modprod, sfc, ist = DRMP3_HDR_TEST_I_STEREO(hdr) && ch; + sfc = gr->scalefac_compress >> ist; + for (k = ist*3*4; sfc >= 0; sfc -= modprod, k += 4) + { + for (modprod = 1, i = 3; i >= 0; i--) + { + scf_size[i] = (drmp3_uint8)(sfc / modprod % g_mod[k + i]); + modprod *= g_mod[k + i]; + } + } + scf_partition += k; + scfsi = -16; + } + drmp3_L3_read_scalefactors(iscf, ist_pos, scf_size, scf_partition, bs, scfsi); + + if (gr->n_short_sfb) + { + int sh = 3 - scf_shift; + for (i = 0; i < gr->n_short_sfb; i += 3) + { + iscf[gr->n_long_sfb + i + 0] += gr->subblock_gain[0] << sh; + iscf[gr->n_long_sfb + i + 1] += gr->subblock_gain[1] << sh; + iscf[gr->n_long_sfb + i + 2] += gr->subblock_gain[2] << sh; + } + } else if (gr->preflag) + { + static const drmp3_uint8 g_preamp[10] = { 1,1,1,1,2,2,3,3,3,2 }; + for (i = 0; i < 10; i++) + { + iscf[11 + i] += g_preamp[i]; + } + } + + gain_exp = gr->global_gain + DRMP3_BITS_DEQUANTIZER_OUT*4 - 210 - (DRMP3_HDR_IS_MS_STEREO(hdr) ? 2 : 0); + gain = drmp3_L3_ldexp_q2(1 << (DRMP3_MAX_SCFI/4), DRMP3_MAX_SCFI - gain_exp); + for (i = 0; i < (int)(gr->n_long_sfb + gr->n_short_sfb); i++) + { + scf[i] = drmp3_L3_ldexp_q2(gain, iscf[i] << scf_shift); + } +} + +static const float g_drmp3_pow43[129 + 16] = { + 0,-1,-2.519842f,-4.326749f,-6.349604f,-8.549880f,-10.902724f,-13.390518f,-16.000000f,-18.720754f,-21.544347f,-24.463781f,-27.473142f,-30.567351f,-33.741992f,-36.993181f, + 0,1,2.519842f,4.326749f,6.349604f,8.549880f,10.902724f,13.390518f,16.000000f,18.720754f,21.544347f,24.463781f,27.473142f,30.567351f,33.741992f,36.993181f,40.317474f,43.711787f,47.173345f,50.699631f,54.288352f,57.937408f,61.644865f,65.408941f,69.227979f,73.100443f,77.024898f,81.000000f,85.024491f,89.097188f,93.216975f,97.382800f,101.593667f,105.848633f,110.146801f,114.487321f,118.869381f,123.292209f,127.755065f,132.257246f,136.798076f,141.376907f,145.993119f,150.646117f,155.335327f,160.060199f,164.820202f,169.614826f,174.443577f,179.305980f,184.201575f,189.129918f,194.090580f,199.083145f,204.107210f,209.162385f,214.248292f,219.364564f,224.510845f,229.686789f,234.892058f,240.126328f,245.389280f,250.680604f,256.000000f,261.347174f,266.721841f,272.123723f,277.552547f,283.008049f,288.489971f,293.998060f,299.532071f,305.091761f,310.676898f,316.287249f,321.922592f,327.582707f,333.267377f,338.976394f,344.709550f,350.466646f,356.247482f,362.051866f,367.879608f,373.730522f,379.604427f,385.501143f,391.420496f,397.362314f,403.326427f,409.312672f,415.320884f,421.350905f,427.402579f,433.475750f,439.570269f,445.685987f,451.822757f,457.980436f,464.158883f,470.357960f,476.577530f,482.817459f,489.077615f,495.357868f,501.658090f,507.978156f,514.317941f,520.677324f,527.056184f,533.454404f,539.871867f,546.308458f,552.764065f,559.238575f,565.731879f,572.243870f,578.774440f,585.323483f,591.890898f,598.476581f,605.080431f,611.702349f,618.342238f,625.000000f,631.675540f,638.368763f,645.079578f +}; + +static float drmp3_L3_pow_43(int x) +{ + float frac; + int sign, mult = 256; + + if (x < 129) + { + return g_drmp3_pow43[16 + x]; + } + + if (x < 1024) + { + mult = 16; + x <<= 3; + } + + sign = 2*x & 64; + frac = (float)((x & 63) - sign) / ((x & ~63) + sign); + return g_drmp3_pow43[16 + ((x + sign) >> 6)]*(1.f + frac*((4.f/3) + frac*(2.f/9)))*mult; +} + +static void drmp3_L3_huffman(float *dst, drmp3_bs *bs, const drmp3_L3_gr_info *gr_info, const float *scf, int layer3gr_limit) +{ + static const drmp3_int16 tabs[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 785,785,785,785,784,784,784,784,513,513,513,513,513,513,513,513,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256, + -255,1313,1298,1282,785,785,785,785,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,290,288, + -255,1313,1298,1282,769,769,769,769,529,529,529,529,529,529,529,529,528,528,528,528,528,528,528,528,512,512,512,512,512,512,512,512,290,288, + -253,-318,-351,-367,785,785,785,785,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,819,818,547,547,275,275,275,275,561,560,515,546,289,274,288,258, + -254,-287,1329,1299,1314,1312,1057,1057,1042,1042,1026,1026,784,784,784,784,529,529,529,529,529,529,529,529,769,769,769,769,768,768,768,768,563,560,306,306,291,259, + -252,-413,-477,-542,1298,-575,1041,1041,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-383,-399,1107,1092,1106,1061,849,849,789,789,1104,1091,773,773,1076,1075,341,340,325,309,834,804,577,577,532,532,516,516,832,818,803,816,561,561,531,531,515,546,289,289,288,258, + -252,-429,-493,-559,1057,1057,1042,1042,529,529,529,529,529,529,529,529,784,784,784,784,769,769,769,769,512,512,512,512,512,512,512,512,-382,1077,-415,1106,1061,1104,849,849,789,789,1091,1076,1029,1075,834,834,597,581,340,340,339,324,804,833,532,532,832,772,818,803,817,787,816,771,290,290,290,290,288,258, + -253,-349,-414,-447,-463,1329,1299,-479,1314,1312,1057,1057,1042,1042,1026,1026,785,785,785,785,784,784,784,784,769,769,769,769,768,768,768,768,-319,851,821,-335,836,850,805,849,341,340,325,336,533,533,579,579,564,564,773,832,578,548,563,516,321,276,306,291,304,259, + -251,-572,-733,-830,-863,-879,1041,1041,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-511,-527,-543,1396,1351,1381,1366,1395,1335,1380,-559,1334,1138,1138,1063,1063,1350,1392,1031,1031,1062,1062,1364,1363,1120,1120,1333,1348,881,881,881,881,375,374,359,373,343,358,341,325,791,791,1123,1122,-703,1105,1045,-719,865,865,790,790,774,774,1104,1029,338,293,323,308,-799,-815,833,788,772,818,803,816,322,292,307,320,561,531,515,546,289,274,288,258, + -251,-525,-605,-685,-765,-831,-846,1298,1057,1057,1312,1282,785,785,785,785,784,784,784,784,769,769,769,769,512,512,512,512,512,512,512,512,1399,1398,1383,1367,1382,1396,1351,-511,1381,1366,1139,1139,1079,1079,1124,1124,1364,1349,1363,1333,882,882,882,882,807,807,807,807,1094,1094,1136,1136,373,341,535,535,881,775,867,822,774,-591,324,338,-671,849,550,550,866,864,609,609,293,336,534,534,789,835,773,-751,834,804,308,307,833,788,832,772,562,562,547,547,305,275,560,515,290,290, + -252,-397,-477,-557,-622,-653,-719,-735,-750,1329,1299,1314,1057,1057,1042,1042,1312,1282,1024,1024,785,785,785,785,784,784,784,784,769,769,769,769,-383,1127,1141,1111,1126,1140,1095,1110,869,869,883,883,1079,1109,882,882,375,374,807,868,838,881,791,-463,867,822,368,263,852,837,836,-543,610,610,550,550,352,336,534,534,865,774,851,821,850,805,593,533,579,564,773,832,578,578,548,548,577,577,307,276,306,291,516,560,259,259, + -250,-2107,-2507,-2764,-2909,-2974,-3007,-3023,1041,1041,1040,1040,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-767,-1052,-1213,-1277,-1358,-1405,-1469,-1535,-1550,-1582,-1614,-1647,-1662,-1694,-1726,-1759,-1774,-1807,-1822,-1854,-1886,1565,-1919,-1935,-1951,-1967,1731,1730,1580,1717,-1983,1729,1564,-1999,1548,-2015,-2031,1715,1595,-2047,1714,-2063,1610,-2079,1609,-2095,1323,1323,1457,1457,1307,1307,1712,1547,1641,1700,1699,1594,1685,1625,1442,1442,1322,1322,-780,-973,-910,1279,1278,1277,1262,1276,1261,1275,1215,1260,1229,-959,974,974,989,989,-943,735,478,478,495,463,506,414,-1039,1003,958,1017,927,942,987,957,431,476,1272,1167,1228,-1183,1256,-1199,895,895,941,941,1242,1227,1212,1135,1014,1014,490,489,503,487,910,1013,985,925,863,894,970,955,1012,847,-1343,831,755,755,984,909,428,366,754,559,-1391,752,486,457,924,997,698,698,983,893,740,740,908,877,739,739,667,667,953,938,497,287,271,271,683,606,590,712,726,574,302,302,738,736,481,286,526,725,605,711,636,724,696,651,589,681,666,710,364,467,573,695,466,466,301,465,379,379,709,604,665,679,316,316,634,633,436,436,464,269,424,394,452,332,438,363,347,408,393,448,331,422,362,407,392,421,346,406,391,376,375,359,1441,1306,-2367,1290,-2383,1337,-2399,-2415,1426,1321,-2431,1411,1336,-2447,-2463,-2479,1169,1169,1049,1049,1424,1289,1412,1352,1319,-2495,1154,1154,1064,1064,1153,1153,416,390,360,404,403,389,344,374,373,343,358,372,327,357,342,311,356,326,1395,1394,1137,1137,1047,1047,1365,1392,1287,1379,1334,1364,1349,1378,1318,1363,792,792,792,792,1152,1152,1032,1032,1121,1121,1046,1046,1120,1120,1030,1030,-2895,1106,1061,1104,849,849,789,789,1091,1076,1029,1090,1060,1075,833,833,309,324,532,532,832,772,818,803,561,561,531,560,515,546,289,274,288,258, + -250,-1179,-1579,-1836,-1996,-2124,-2253,-2333,-2413,-2477,-2542,-2574,-2607,-2622,-2655,1314,1313,1298,1312,1282,785,785,785,785,1040,1040,1025,1025,768,768,768,768,-766,-798,-830,-862,-895,-911,-927,-943,-959,-975,-991,-1007,-1023,-1039,-1055,-1070,1724,1647,-1103,-1119,1631,1767,1662,1738,1708,1723,-1135,1780,1615,1779,1599,1677,1646,1778,1583,-1151,1777,1567,1737,1692,1765,1722,1707,1630,1751,1661,1764,1614,1736,1676,1763,1750,1645,1598,1721,1691,1762,1706,1582,1761,1566,-1167,1749,1629,767,766,751,765,494,494,735,764,719,749,734,763,447,447,748,718,477,506,431,491,446,476,461,505,415,430,475,445,504,399,460,489,414,503,383,474,429,459,502,502,746,752,488,398,501,473,413,472,486,271,480,270,-1439,-1455,1357,-1471,-1487,-1503,1341,1325,-1519,1489,1463,1403,1309,-1535,1372,1448,1418,1476,1356,1462,1387,-1551,1475,1340,1447,1402,1386,-1567,1068,1068,1474,1461,455,380,468,440,395,425,410,454,364,467,466,464,453,269,409,448,268,432,1371,1473,1432,1417,1308,1460,1355,1446,1459,1431,1083,1083,1401,1416,1458,1445,1067,1067,1370,1457,1051,1051,1291,1430,1385,1444,1354,1415,1400,1443,1082,1082,1173,1113,1186,1066,1185,1050,-1967,1158,1128,1172,1097,1171,1081,-1983,1157,1112,416,266,375,400,1170,1142,1127,1065,793,793,1169,1033,1156,1096,1141,1111,1155,1080,1126,1140,898,898,808,808,897,897,792,792,1095,1152,1032,1125,1110,1139,1079,1124,882,807,838,881,853,791,-2319,867,368,263,822,852,837,866,806,865,-2399,851,352,262,534,534,821,836,594,594,549,549,593,593,533,533,848,773,579,579,564,578,548,563,276,276,577,576,306,291,516,560,305,305,275,259, + -251,-892,-2058,-2620,-2828,-2957,-3023,-3039,1041,1041,1040,1040,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-511,-527,-543,-559,1530,-575,-591,1528,1527,1407,1526,1391,1023,1023,1023,1023,1525,1375,1268,1268,1103,1103,1087,1087,1039,1039,1523,-604,815,815,815,815,510,495,509,479,508,463,507,447,431,505,415,399,-734,-782,1262,-815,1259,1244,-831,1258,1228,-847,-863,1196,-879,1253,987,987,748,-767,493,493,462,477,414,414,686,669,478,446,461,445,474,429,487,458,412,471,1266,1264,1009,1009,799,799,-1019,-1276,-1452,-1581,-1677,-1757,-1821,-1886,-1933,-1997,1257,1257,1483,1468,1512,1422,1497,1406,1467,1496,1421,1510,1134,1134,1225,1225,1466,1451,1374,1405,1252,1252,1358,1480,1164,1164,1251,1251,1238,1238,1389,1465,-1407,1054,1101,-1423,1207,-1439,830,830,1248,1038,1237,1117,1223,1148,1236,1208,411,426,395,410,379,269,1193,1222,1132,1235,1221,1116,976,976,1192,1162,1177,1220,1131,1191,963,963,-1647,961,780,-1663,558,558,994,993,437,408,393,407,829,978,813,797,947,-1743,721,721,377,392,844,950,828,890,706,706,812,859,796,960,948,843,934,874,571,571,-1919,690,555,689,421,346,539,539,944,779,918,873,932,842,903,888,570,570,931,917,674,674,-2575,1562,-2591,1609,-2607,1654,1322,1322,1441,1441,1696,1546,1683,1593,1669,1624,1426,1426,1321,1321,1639,1680,1425,1425,1305,1305,1545,1668,1608,1623,1667,1592,1638,1666,1320,1320,1652,1607,1409,1409,1304,1304,1288,1288,1664,1637,1395,1395,1335,1335,1622,1636,1394,1394,1319,1319,1606,1621,1392,1392,1137,1137,1137,1137,345,390,360,375,404,373,1047,-2751,-2767,-2783,1062,1121,1046,-2799,1077,-2815,1106,1061,789,789,1105,1104,263,355,310,340,325,354,352,262,339,324,1091,1076,1029,1090,1060,1075,833,833,788,788,1088,1028,818,818,803,803,561,561,531,531,816,771,546,546,289,274,288,258, + -253,-317,-381,-446,-478,-509,1279,1279,-811,-1179,-1451,-1756,-1900,-2028,-2189,-2253,-2333,-2414,-2445,-2511,-2526,1313,1298,-2559,1041,1041,1040,1040,1025,1025,1024,1024,1022,1007,1021,991,1020,975,1019,959,687,687,1018,1017,671,671,655,655,1016,1015,639,639,758,758,623,623,757,607,756,591,755,575,754,559,543,543,1009,783,-575,-621,-685,-749,496,-590,750,749,734,748,974,989,1003,958,988,973,1002,942,987,957,972,1001,926,986,941,971,956,1000,910,985,925,999,894,970,-1071,-1087,-1102,1390,-1135,1436,1509,1451,1374,-1151,1405,1358,1480,1420,-1167,1507,1494,1389,1342,1465,1435,1450,1326,1505,1310,1493,1373,1479,1404,1492,1464,1419,428,443,472,397,736,526,464,464,486,457,442,471,484,482,1357,1449,1434,1478,1388,1491,1341,1490,1325,1489,1463,1403,1309,1477,1372,1448,1418,1433,1476,1356,1462,1387,-1439,1475,1340,1447,1402,1474,1324,1461,1371,1473,269,448,1432,1417,1308,1460,-1711,1459,-1727,1441,1099,1099,1446,1386,1431,1401,-1743,1289,1083,1083,1160,1160,1458,1445,1067,1067,1370,1457,1307,1430,1129,1129,1098,1098,268,432,267,416,266,400,-1887,1144,1187,1082,1173,1113,1186,1066,1050,1158,1128,1143,1172,1097,1171,1081,420,391,1157,1112,1170,1142,1127,1065,1169,1049,1156,1096,1141,1111,1155,1080,1126,1154,1064,1153,1140,1095,1048,-2159,1125,1110,1137,-2175,823,823,1139,1138,807,807,384,264,368,263,868,838,853,791,867,822,852,837,866,806,865,790,-2319,851,821,836,352,262,850,805,849,-2399,533,533,835,820,336,261,578,548,563,577,532,532,832,772,562,562,547,547,305,275,560,515,290,290,288,258 }; + static const drmp3_uint8 tab32[] = { 130,162,193,209,44,28,76,140,9,9,9,9,9,9,9,9,190,254,222,238,126,94,157,157,109,61,173,205}; + static const drmp3_uint8 tab33[] = { 252,236,220,204,188,172,156,140,124,108,92,76,60,44,28,12 }; + static const drmp3_int16 tabindex[2*16] = { 0,32,64,98,0,132,180,218,292,364,426,538,648,746,0,1126,1460,1460,1460,1460,1460,1460,1460,1460,1842,1842,1842,1842,1842,1842,1842,1842 }; + static const drmp3_uint8 g_linbits[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,6,8,10,13,4,5,6,7,8,9,11,13 }; + +#define DRMP3_PEEK_BITS(n) (bs_cache >> (32 - n)) +#define DRMP3_FLUSH_BITS(n) { bs_cache <<= (n); bs_sh += (n); } +#define DRMP3_CHECK_BITS while (bs_sh >= 0) { bs_cache |= (drmp3_uint32)*bs_next_ptr++ << bs_sh; bs_sh -= 8; } +#define DRMP3_BSPOS ((bs_next_ptr - bs->buf)*8 - 24 + bs_sh) + + float one = 0.0f; + int ireg = 0, big_val_cnt = gr_info->big_values; + const drmp3_uint8 *sfb = gr_info->sfbtab; + const drmp3_uint8 *bs_next_ptr = bs->buf + bs->pos/8; + drmp3_uint32 bs_cache = (((bs_next_ptr[0]*256u + bs_next_ptr[1])*256u + bs_next_ptr[2])*256u + bs_next_ptr[3]) << (bs->pos & 7); + int pairs_to_decode, np, bs_sh = (bs->pos & 7) - 8; + bs_next_ptr += 4; + + while (big_val_cnt > 0) + { + int tab_num = gr_info->table_select[ireg]; + int sfb_cnt = gr_info->region_count[ireg++]; + const drmp3_int16 *codebook = tabs + tabindex[tab_num]; + int linbits = g_linbits[tab_num]; + do + { + np = *sfb++ / 2; + pairs_to_decode = DRMP3_MIN(big_val_cnt, np); + one = *scf++; + do + { + int j, w = 5; + int leaf = codebook[DRMP3_PEEK_BITS(w)]; + while (leaf < 0) + { + DRMP3_FLUSH_BITS(w); + w = leaf & 7; + leaf = codebook[DRMP3_PEEK_BITS(w) - (leaf >> 3)]; + } + DRMP3_FLUSH_BITS(leaf >> 8); + + for (j = 0; j < 2; j++, dst++, leaf >>= 4) + { + int lsb = leaf & 0x0F; + if (lsb == 15 && linbits) + { + lsb += DRMP3_PEEK_BITS(linbits); + DRMP3_FLUSH_BITS(linbits); + DRMP3_CHECK_BITS; + *dst = one*drmp3_L3_pow_43(lsb)*((int32_t)bs_cache < 0 ? -1: 1); + } else + { + *dst = g_drmp3_pow43[16 + lsb - 16*(bs_cache >> 31)]*one; + } + DRMP3_FLUSH_BITS(lsb ? 1 : 0); + } + DRMP3_CHECK_BITS; + } while (--pairs_to_decode); + } while ((big_val_cnt -= np) > 0 && --sfb_cnt >= 0); + } + + for (np = 1 - big_val_cnt;; dst += 4) + { + const drmp3_uint8 *codebook_count1 = (gr_info->count1_table) ? tab33 : tab32; + int leaf = codebook_count1[DRMP3_PEEK_BITS(4)]; + if (!(leaf & 8)) + { + leaf = codebook_count1[(leaf >> 3) + (bs_cache << 4 >> (32 - (leaf & 3)))]; + } + DRMP3_FLUSH_BITS(leaf & 7); + if (DRMP3_BSPOS > layer3gr_limit) + { + break; + } +#define DRMP3_RELOAD_SCALEFACTOR if (!--np) { np = *sfb++/2; if (!np) break; one = *scf++; } +#define DRMP3_DEQ_COUNT1(s) if (leaf & (128 >> s)) { dst[s] = ((drmp3_int32)bs_cache < 0) ? -one : one; DRMP3_FLUSH_BITS(1) } + DRMP3_RELOAD_SCALEFACTOR; + DRMP3_DEQ_COUNT1(0); + DRMP3_DEQ_COUNT1(1); + DRMP3_RELOAD_SCALEFACTOR; + DRMP3_DEQ_COUNT1(2); + DRMP3_DEQ_COUNT1(3); + DRMP3_CHECK_BITS; + } + + bs->pos = layer3gr_limit; +} + +static void drmp3_L3_midside_stereo(float *left, int n) +{ + int i = 0; + float *right = left + 576; +#if DRMP3_HAVE_SIMD + if (drmp3_have_simd()) for (; i < n - 3; i += 4) + { + drmp3_f4 vl = DRMP3_VLD(left + i); + drmp3_f4 vr = DRMP3_VLD(right + i); + DRMP3_VSTORE(left + i, DRMP3_VADD(vl, vr)); + DRMP3_VSTORE(right + i, DRMP3_VSUB(vl, vr)); + } +#endif + for (; i < n; i++) + { + float a = left[i]; + float b = right[i]; + left[i] = a + b; + right[i] = a - b; + } +} + +static void drmp3_L3_intensity_stereo_band(float *left, int n, float kl, float kr) +{ + int i; + for (i = 0; i < n; i++) + { + left[i + 576] = left[i]*kr; + left[i] = left[i]*kl; + } +} + +static void drmp3_L3_stereo_top_band(const float *right, const drmp3_uint8 *sfb, int nbands, int max_band[3]) +{ + int i, k; + + max_band[0] = max_band[1] = max_band[2] = -1; + + for (i = 0; i < nbands; i++) + { + for (k = 0; k < sfb[i]; k += 2) + { + if (right[k] != 0 || right[k + 1] != 0) + { + max_band[i % 3] = i; + break; + } + } + right += sfb[i]; + } +} + +static void drmp3_L3_stereo_process(float *left, const drmp3_uint8 *ist_pos, const drmp3_uint8 *sfb, const drmp3_uint8 *hdr, int max_band[3], int mpeg2_sh) +{ + static const float g_pan[7*2] = { 0,1,0.21132487f,0.78867513f,0.36602540f,0.63397460f,0.5f,0.5f,0.63397460f,0.36602540f,0.78867513f,0.21132487f,1,0 }; + unsigned i, max_pos = DRMP3_HDR_TEST_MPEG1(hdr) ? 7 : 64; + + for (i = 0; sfb[i]; i++) + { + unsigned ipos = ist_pos[i]; + if ((int)i > max_band[i % 3] && ipos < max_pos) + { + float kl, kr, s = DRMP3_HDR_TEST_MS_STEREO(hdr) ? 1.41421356f : 1; + if (DRMP3_HDR_TEST_MPEG1(hdr)) + { + kl = g_pan[2*ipos]; + kr = g_pan[2*ipos + 1]; + } else + { + kl = 1; + kr = drmp3_L3_ldexp_q2(1, (ipos + 1) >> 1 << mpeg2_sh); + if (ipos & 1) + { + kl = kr; + kr = 1; + } + } + drmp3_L3_intensity_stereo_band(left, sfb[i], kl*s, kr*s); + } else if (DRMP3_HDR_TEST_MS_STEREO(hdr)) + { + drmp3_L3_midside_stereo(left, sfb[i]); + } + left += sfb[i]; + } +} + +static void drmp3_L3_intensity_stereo(float *left, drmp3_uint8 *ist_pos, const drmp3_L3_gr_info *gr, const drmp3_uint8 *hdr) +{ + int max_band[3], n_sfb = gr->n_long_sfb + gr->n_short_sfb; + int i, max_blocks = gr->n_short_sfb ? 3 : 1; + + drmp3_L3_stereo_top_band(left + 576, gr->sfbtab, n_sfb, max_band); + if (gr->n_long_sfb) + { + max_band[0] = max_band[1] = max_band[2] = DRMP3_MAX(DRMP3_MAX(max_band[0], max_band[1]), max_band[2]); + } + for (i = 0; i < max_blocks; i++) + { + int default_pos = DRMP3_HDR_TEST_MPEG1(hdr) ? 3 : 0; + int itop = n_sfb - max_blocks + i; + int prev = itop - max_blocks; + ist_pos[itop] = (drmp3_uint8)(max_band[i] >= prev ? default_pos : ist_pos[prev]); + } + drmp3_L3_stereo_process(left, ist_pos, gr->sfbtab, hdr, max_band, gr[1].scalefac_compress & 1); +} + +static void drmp3_L3_reorder(float *grbuf, float *scratch, const drmp3_uint8 *sfb) +{ + int i, len; + float *src = grbuf, *dst = scratch; + + for (;0 != (len = *sfb); sfb += 3, src += 2*len) + { + for (i = 0; i < len; i++, src++) + { + *dst++ = src[0*len]; + *dst++ = src[1*len]; + *dst++ = src[2*len]; + } + } + memcpy(grbuf, scratch, (dst - scratch)*sizeof(float)); +} + +static void drmp3_L3_antialias(float *grbuf, int nbands) +{ + static const float g_aa[2][8] = { + {0.85749293f,0.88174200f,0.94962865f,0.98331459f,0.99551782f,0.99916056f,0.99989920f,0.99999316f}, + {0.51449576f,0.47173197f,0.31337745f,0.18191320f,0.09457419f,0.04096558f,0.01419856f,0.00369997f} + }; + + for (; nbands > 0; nbands--, grbuf += 18) + { + int i = 0; +#if DRMP3_HAVE_SIMD + if (drmp3_have_simd()) for (; i < 8; i += 4) + { + drmp3_f4 vu = DRMP3_VLD(grbuf + 18 + i); + drmp3_f4 vd = DRMP3_VLD(grbuf + 14 - i); + drmp3_f4 vc0 = DRMP3_VLD(g_aa[0] + i); + drmp3_f4 vc1 = DRMP3_VLD(g_aa[1] + i); + vd = DRMP3_VREV(vd); + DRMP3_VSTORE(grbuf + 18 + i, DRMP3_VSUB(DRMP3_VMUL(vu, vc0), DRMP3_VMUL(vd, vc1))); + vd = DRMP3_VADD(DRMP3_VMUL(vu, vc1), DRMP3_VMUL(vd, vc0)); + DRMP3_VSTORE(grbuf + 14 - i, DRMP3_VREV(vd)); + } +#endif +#ifndef DR_MP3_ONLY_SIMD + for(; i < 8; i++) + { + float u = grbuf[18 + i]; + float d = grbuf[17 - i]; + grbuf[18 + i] = u*g_aa[0][i] - d*g_aa[1][i]; + grbuf[17 - i] = u*g_aa[1][i] + d*g_aa[0][i]; + } +#endif + } +} + +static void drmp3_L3_dct3_9(float *y) +{ + float s0, s1, s2, s3, s4, s5, s6, s7, s8, t0, t2, t4; + + s0 = y[0]; s2 = y[2]; s4 = y[4]; s6 = y[6]; s8 = y[8]; + t0 = s0 + s6*0.5f; + s0 -= s6; + t4 = (s4 + s2)*0.93969262f; + t2 = (s8 + s2)*0.76604444f; + s6 = (s4 - s8)*0.17364818f; + s4 += s8 - s2; + + s2 = s0 - s4*0.5f; + y[4] = s4 + s0; + s8 = t0 - t2 + s6; + s0 = t0 - t4 + t2; + s4 = t0 + t4 - s6; + + s1 = y[1]; s3 = y[3]; s5 = y[5]; s7 = y[7]; + + s3 *= 0.86602540f; + t0 = (s5 + s1)*0.98480775f; + t4 = (s5 - s7)*0.34202014f; + t2 = (s1 + s7)*0.64278761f; + s1 = (s1 - s5 - s7)*0.86602540f; + + s5 = t0 - s3 - t2; + s7 = t4 - s3 - t0; + s3 = t4 + s3 - t2; + + y[0] = s4 - s7; + y[1] = s2 + s1; + y[2] = s0 - s3; + y[3] = s8 + s5; + y[5] = s8 - s5; + y[6] = s0 + s3; + y[7] = s2 - s1; + y[8] = s4 + s7; +} + +static void drmp3_L3_imdct36(float *grbuf, float *overlap, const float *window, int nbands) +{ + int i, j; + static const float g_twid9[18] = { + 0.73727734f,0.79335334f,0.84339145f,0.88701083f,0.92387953f,0.95371695f,0.97629601f,0.99144486f,0.99904822f,0.67559021f,0.60876143f,0.53729961f,0.46174861f,0.38268343f,0.30070580f,0.21643961f,0.13052619f,0.04361938f + }; + + for (j = 0; j < nbands; j++, grbuf += 18, overlap += 9) + { + float co[9], si[9]; + co[0] = -grbuf[0]; + si[0] = grbuf[17]; + for (i = 0; i < 4; i++) + { + si[8 - 2*i] = grbuf[4*i + 1] - grbuf[4*i + 2]; + co[1 + 2*i] = grbuf[4*i + 1] + grbuf[4*i + 2]; + si[7 - 2*i] = grbuf[4*i + 4] - grbuf[4*i + 3]; + co[2 + 2*i] = -(grbuf[4*i + 3] + grbuf[4*i + 4]); + } + drmp3_L3_dct3_9(co); + drmp3_L3_dct3_9(si); + + si[1] = -si[1]; + si[3] = -si[3]; + si[5] = -si[5]; + si[7] = -si[7]; + + i = 0; + +#if DRMP3_HAVE_SIMD + if (drmp3_have_simd()) for (; i < 8; i += 4) + { + drmp3_f4 vovl = DRMP3_VLD(overlap + i); + drmp3_f4 vc = DRMP3_VLD(co + i); + drmp3_f4 vs = DRMP3_VLD(si + i); + drmp3_f4 vr0 = DRMP3_VLD(g_twid9 + i); + drmp3_f4 vr1 = DRMP3_VLD(g_twid9 + 9 + i); + drmp3_f4 vw0 = DRMP3_VLD(window + i); + drmp3_f4 vw1 = DRMP3_VLD(window + 9 + i); + drmp3_f4 vsum = DRMP3_VADD(DRMP3_VMUL(vc, vr1), DRMP3_VMUL(vs, vr0)); + DRMP3_VSTORE(overlap + i, DRMP3_VSUB(DRMP3_VMUL(vc, vr0), DRMP3_VMUL(vs, vr1))); + DRMP3_VSTORE(grbuf + i, DRMP3_VSUB(DRMP3_VMUL(vovl, vw0), DRMP3_VMUL(vsum, vw1))); + vsum = DRMP3_VADD(DRMP3_VMUL(vovl, vw1), DRMP3_VMUL(vsum, vw0)); + DRMP3_VSTORE(grbuf + 14 - i, DRMP3_VREV(vsum)); + } +#endif + for (; i < 9; i++) + { + float ovl = overlap[i]; + float sum = co[i]*g_twid9[9 + i] + si[i]*g_twid9[0 + i]; + overlap[i] = co[i]*g_twid9[0 + i] - si[i]*g_twid9[9 + i]; + grbuf[i] = ovl*window[0 + i] - sum*window[9 + i]; + grbuf[17 - i] = ovl*window[9 + i] + sum*window[0 + i]; + } + } +} + +static void drmp3_L3_idct3(float x0, float x1, float x2, float *dst) +{ + float m1 = x1*0.86602540f; + float a1 = x0 - x2*0.5f; + dst[1] = x0 + x2; + dst[0] = a1 + m1; + dst[2] = a1 - m1; +} + +static void drmp3_L3_imdct12(float *x, float *dst, float *overlap) +{ + static const float g_twid3[6] = { 0.79335334f,0.92387953f,0.99144486f, 0.60876143f,0.38268343f,0.13052619f }; + float co[3], si[3]; + int i; + + drmp3_L3_idct3(-x[0], x[6] + x[3], x[12] + x[9], co); + drmp3_L3_idct3(x[15], x[12] - x[9], x[6] - x[3], si); + si[1] = -si[1]; + + for (i = 0; i < 3; i++) + { + float ovl = overlap[i]; + float sum = co[i]*g_twid3[3 + i] + si[i]*g_twid3[0 + i]; + overlap[i] = co[i]*g_twid3[0 + i] - si[i]*g_twid3[3 + i]; + dst[i] = ovl*g_twid3[2 - i] - sum*g_twid3[5 - i]; + dst[5 - i] = ovl*g_twid3[5 - i] + sum*g_twid3[2 - i]; + } +} + +static void drmp3_L3_imdct_short(float *grbuf, float *overlap, int nbands) +{ + for (;nbands > 0; nbands--, overlap += 9, grbuf += 18) + { + float tmp[18]; + memcpy(tmp, grbuf, sizeof(tmp)); + memcpy(grbuf, overlap, 6*sizeof(float)); + drmp3_L3_imdct12(tmp, grbuf + 6, overlap + 6); + drmp3_L3_imdct12(tmp + 1, grbuf + 12, overlap + 6); + drmp3_L3_imdct12(tmp + 2, overlap, overlap + 6); + } +} + +static void drmp3_L3_change_sign(float *grbuf) +{ + int b, i; + for (b = 0, grbuf += 18; b < 32; b += 2, grbuf += 36) + for (i = 1; i < 18; i += 2) + grbuf[i] = -grbuf[i]; +} + +static void drmp3_L3_imdct_gr(float *grbuf, float *overlap, unsigned block_type, unsigned n_long_bands) +{ + static const float g_mdct_window[2][18] = { + { 0.99904822f,0.99144486f,0.97629601f,0.95371695f,0.92387953f,0.88701083f,0.84339145f,0.79335334f,0.73727734f,0.04361938f,0.13052619f,0.21643961f,0.30070580f,0.38268343f,0.46174861f,0.53729961f,0.60876143f,0.67559021f }, + { 1,1,1,1,1,1,0.99144486f,0.92387953f,0.79335334f,0,0,0,0,0,0,0.13052619f,0.38268343f,0.60876143f } + }; + if (n_long_bands) + { + drmp3_L3_imdct36(grbuf, overlap, g_mdct_window[0], n_long_bands); + grbuf += 18*n_long_bands; + overlap += 9*n_long_bands; + } + if (block_type == DRMP3_SHORT_BLOCK_TYPE) + drmp3_L3_imdct_short(grbuf, overlap, 32 - n_long_bands); + else + drmp3_L3_imdct36(grbuf, overlap, g_mdct_window[block_type == DRMP3_STOP_BLOCK_TYPE], 32 - n_long_bands); +} + +static void drmp3_L3_save_reservoir(drmp3dec *h, drmp3dec_scratch *s) +{ + int pos = (s->bs.pos + 7)/8u; + int remains = s->bs.limit/8u - pos; + if (remains > DRMP3_MAX_BITRESERVOIR_BYTES) + { + pos += remains - DRMP3_MAX_BITRESERVOIR_BYTES; + remains = DRMP3_MAX_BITRESERVOIR_BYTES; + } + if (remains > 0) + { + memmove(h->reserv_buf, s->maindata + pos, remains); + } + h->reserv = remains; +} + +static int drmp3_L3_restore_reservoir(drmp3dec *h, drmp3_bs *bs, drmp3dec_scratch *s, int main_data_begin) +{ + int frame_bytes = (bs->limit - bs->pos)/8; + int bytes_have = DRMP3_MIN(h->reserv, main_data_begin); + memcpy(s->maindata, h->reserv_buf + DRMP3_MAX(0, h->reserv - main_data_begin), DRMP3_MIN(h->reserv, main_data_begin)); + memcpy(s->maindata + bytes_have, bs->buf + bs->pos/8, frame_bytes); + drmp3_bs_init(&s->bs, s->maindata, bytes_have + frame_bytes); + return h->reserv >= main_data_begin; +} + +static void drmp3_L3_decode(drmp3dec *h, drmp3dec_scratch *s, drmp3_L3_gr_info *gr_info, int nch) +{ + int ch; + + for (ch = 0; ch < nch; ch++) + { + int layer3gr_limit = s->bs.pos + gr_info[ch].part_23_length; + drmp3_L3_decode_scalefactors(h->header, s->ist_pos[ch], &s->bs, gr_info + ch, s->scf, ch); + drmp3_L3_huffman(s->grbuf[ch], &s->bs, gr_info + ch, s->scf, layer3gr_limit); + } + + if (DRMP3_HDR_TEST_I_STEREO(h->header)) + { + drmp3_L3_intensity_stereo(s->grbuf[0], s->ist_pos[1], gr_info, h->header); + } else if (DRMP3_HDR_IS_MS_STEREO(h->header)) + { + drmp3_L3_midside_stereo(s->grbuf[0], 576); + } + + for (ch = 0; ch < nch; ch++, gr_info++) + { + int aa_bands = 31; + int n_long_bands = (gr_info->mixed_block_flag ? 2 : 0) << (int)(DRMP3_HDR_GET_MY_SAMPLE_RATE(h->header) == 2); + + if (gr_info->n_short_sfb) + { + aa_bands = n_long_bands - 1; + drmp3_L3_reorder(s->grbuf[ch] + n_long_bands*18, s->syn[0], gr_info->sfbtab + gr_info->n_long_sfb); + } + + drmp3_L3_antialias(s->grbuf[ch], aa_bands); + drmp3_L3_imdct_gr(s->grbuf[ch], h->mdct_overlap[ch], gr_info->block_type, n_long_bands); + drmp3_L3_change_sign(s->grbuf[ch]); + } +} + +static void drmp3d_DCT_II(float *grbuf, int n) +{ + static const float g_sec[24] = { + 10.19000816f,0.50060302f,0.50241929f,3.40760851f,0.50547093f,0.52249861f,2.05778098f,0.51544732f,0.56694406f,1.48416460f,0.53104258f,0.64682180f,1.16943991f,0.55310392f,0.78815460f,0.97256821f,0.58293498f,1.06067765f,0.83934963f,0.62250412f,1.72244716f,0.74453628f,0.67480832f,5.10114861f + }; + int i, k = 0; +#if DRMP3_HAVE_SIMD + if (drmp3_have_simd()) for (; k < n; k += 4) + { + drmp3_f4 t[4][8], *x; + float *y = grbuf + k; + + for (x = t[0], i = 0; i < 8; i++, x++) + { + drmp3_f4 x0 = DRMP3_VLD(&y[i*18]); + drmp3_f4 x1 = DRMP3_VLD(&y[(15 - i)*18]); + drmp3_f4 x2 = DRMP3_VLD(&y[(16 + i)*18]); + drmp3_f4 x3 = DRMP3_VLD(&y[(31 - i)*18]); + drmp3_f4 t0 = DRMP3_VADD(x0, x3); + drmp3_f4 t1 = DRMP3_VADD(x1, x2); + drmp3_f4 t2 = DRMP3_VMUL_S(DRMP3_VSUB(x1, x2), g_sec[3*i + 0]); + drmp3_f4 t3 = DRMP3_VMUL_S(DRMP3_VSUB(x0, x3), g_sec[3*i + 1]); + x[0] = DRMP3_VADD(t0, t1); + x[8] = DRMP3_VMUL_S(DRMP3_VSUB(t0, t1), g_sec[3*i + 2]); + x[16] = DRMP3_VADD(t3, t2); + x[24] = DRMP3_VMUL_S(DRMP3_VSUB(t3, t2), g_sec[3*i + 2]); + } + for (x = t[0], i = 0; i < 4; i++, x += 8) + { + drmp3_f4 x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3], x4 = x[4], x5 = x[5], x6 = x[6], x7 = x[7], xt; + xt = DRMP3_VSUB(x0, x7); x0 = DRMP3_VADD(x0, x7); + x7 = DRMP3_VSUB(x1, x6); x1 = DRMP3_VADD(x1, x6); + x6 = DRMP3_VSUB(x2, x5); x2 = DRMP3_VADD(x2, x5); + x5 = DRMP3_VSUB(x3, x4); x3 = DRMP3_VADD(x3, x4); + x4 = DRMP3_VSUB(x0, x3); x0 = DRMP3_VADD(x0, x3); + x3 = DRMP3_VSUB(x1, x2); x1 = DRMP3_VADD(x1, x2); + x[0] = DRMP3_VADD(x0, x1); + x[4] = DRMP3_VMUL_S(DRMP3_VSUB(x0, x1), 0.70710677f); + x5 = DRMP3_VADD(x5, x6); + x6 = DRMP3_VMUL_S(DRMP3_VADD(x6, x7), 0.70710677f); + x7 = DRMP3_VADD(x7, xt); + x3 = DRMP3_VMUL_S(DRMP3_VADD(x3, x4), 0.70710677f); + x5 = DRMP3_VSUB(x5, DRMP3_VMUL_S(x7, 0.198912367f)); /* rotate by PI/8 */ + x7 = DRMP3_VADD(x7, DRMP3_VMUL_S(x5, 0.382683432f)); + x5 = DRMP3_VSUB(x5, DRMP3_VMUL_S(x7, 0.198912367f)); + x0 = DRMP3_VSUB(xt, x6); xt = DRMP3_VADD(xt, x6); + x[1] = DRMP3_VMUL_S(DRMP3_VADD(xt, x7), 0.50979561f); + x[2] = DRMP3_VMUL_S(DRMP3_VADD(x4, x3), 0.54119611f); + x[3] = DRMP3_VMUL_S(DRMP3_VSUB(x0, x5), 0.60134488f); + x[5] = DRMP3_VMUL_S(DRMP3_VADD(x0, x5), 0.89997619f); + x[6] = DRMP3_VMUL_S(DRMP3_VSUB(x4, x3), 1.30656302f); + x[7] = DRMP3_VMUL_S(DRMP3_VSUB(xt, x7), 2.56291556f); + } + + if (k > n - 3) + { +#if DRMP3_HAVE_SSE +#define DRMP3_VSAVE2(i, v) _mm_storel_pi((__m64 *)(void*)&y[i*18], v) +#else +#define DRMP3_VSAVE2(i, v) vst1_f32((float32_t *)&y[i*18], vget_low_f32(v)) +#endif + for (i = 0; i < 7; i++, y += 4*18) + { + drmp3_f4 s = DRMP3_VADD(t[3][i], t[3][i + 1]); + DRMP3_VSAVE2(0, t[0][i]); + DRMP3_VSAVE2(1, DRMP3_VADD(t[2][i], s)); + DRMP3_VSAVE2(2, DRMP3_VADD(t[1][i], t[1][i + 1])); + DRMP3_VSAVE2(3, DRMP3_VADD(t[2][1 + i], s)); + } + DRMP3_VSAVE2(0, t[0][7]); + DRMP3_VSAVE2(1, DRMP3_VADD(t[2][7], t[3][7])); + DRMP3_VSAVE2(2, t[1][7]); + DRMP3_VSAVE2(3, t[3][7]); + } else + { +#define DRMP3_VSAVE4(i, v) DRMP3_VSTORE(&y[i*18], v) + for (i = 0; i < 7; i++, y += 4*18) + { + drmp3_f4 s = DRMP3_VADD(t[3][i], t[3][i + 1]); + DRMP3_VSAVE4(0, t[0][i]); + DRMP3_VSAVE4(1, DRMP3_VADD(t[2][i], s)); + DRMP3_VSAVE4(2, DRMP3_VADD(t[1][i], t[1][i + 1])); + DRMP3_VSAVE4(3, DRMP3_VADD(t[2][1 + i], s)); + } + DRMP3_VSAVE4(0, t[0][7]); + DRMP3_VSAVE4(1, DRMP3_VADD(t[2][7], t[3][7])); + DRMP3_VSAVE4(2, t[1][7]); + DRMP3_VSAVE4(3, t[3][7]); + } + } else +#endif +#ifdef DR_MP3_ONLY_SIMD + {} +#else + for (; k < n; k++) + { + float t[4][8], *x, *y = grbuf + k; + + for (x = t[0], i = 0; i < 8; i++, x++) + { + float x0 = y[i*18]; + float x1 = y[(15 - i)*18]; + float x2 = y[(16 + i)*18]; + float x3 = y[(31 - i)*18]; + float t0 = x0 + x3; + float t1 = x1 + x2; + float t2 = (x1 - x2)*g_sec[3*i + 0]; + float t3 = (x0 - x3)*g_sec[3*i + 1]; + x[0] = t0 + t1; + x[8] = (t0 - t1)*g_sec[3*i + 2]; + x[16] = t3 + t2; + x[24] = (t3 - t2)*g_sec[3*i + 2]; + } + for (x = t[0], i = 0; i < 4; i++, x += 8) + { + float x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3], x4 = x[4], x5 = x[5], x6 = x[6], x7 = x[7], xt; + xt = x0 - x7; x0 += x7; + x7 = x1 - x6; x1 += x6; + x6 = x2 - x5; x2 += x5; + x5 = x3 - x4; x3 += x4; + x4 = x0 - x3; x0 += x3; + x3 = x1 - x2; x1 += x2; + x[0] = x0 + x1; + x[4] = (x0 - x1)*0.70710677f; + x5 = x5 + x6; + x6 = (x6 + x7)*0.70710677f; + x7 = x7 + xt; + x3 = (x3 + x4)*0.70710677f; + x5 -= x7*0.198912367f; /* rotate by PI/8 */ + x7 += x5*0.382683432f; + x5 -= x7*0.198912367f; + x0 = xt - x6; xt += x6; + x[1] = (xt + x7)*0.50979561f; + x[2] = (x4 + x3)*0.54119611f; + x[3] = (x0 - x5)*0.60134488f; + x[5] = (x0 + x5)*0.89997619f; + x[6] = (x4 - x3)*1.30656302f; + x[7] = (xt - x7)*2.56291556f; + + } + for (i = 0; i < 7; i++, y += 4*18) + { + y[0*18] = t[0][i]; + y[1*18] = t[2][i] + t[3][i] + t[3][i + 1]; + y[2*18] = t[1][i] + t[1][i + 1]; + y[3*18] = t[2][i + 1] + t[3][i] + t[3][i + 1]; + } + y[0*18] = t[0][7]; + y[1*18] = t[2][7] + t[3][7]; + y[2*18] = t[1][7]; + y[3*18] = t[3][7]; + } +#endif +} + +#ifndef DR_MP3_FLOAT_OUTPUT +typedef drmp3_int16 drmp3d_sample_t; + +static drmp3_int16 drmp3d_scale_pcm(float sample) +{ + if (sample >= 32766.5) return (drmp3_int16) 32767; + if (sample <= -32767.5) return (drmp3_int16)-32768; + drmp3_int16 s = (drmp3_int16)(sample + .5f); + s -= (s < 0); /* away from zero, to be compliant */ + return (drmp3_int16)s; +} +#else +typedef float drmp3d_sample_t; + +static float drmp3d_scale_pcm(float sample) +{ + return sample*(1.f/32768.f); +} +#endif + +static void drmp3d_synth_pair(drmp3d_sample_t *pcm, int nch, const float *z) +{ + float a; + a = (z[14*64] - z[ 0]) * 29; + a += (z[ 1*64] + z[13*64]) * 213; + a += (z[12*64] - z[ 2*64]) * 459; + a += (z[ 3*64] + z[11*64]) * 2037; + a += (z[10*64] - z[ 4*64]) * 5153; + a += (z[ 5*64] + z[ 9*64]) * 6574; + a += (z[ 8*64] - z[ 6*64]) * 37489; + a += z[ 7*64] * 75038; + pcm[0] = drmp3d_scale_pcm(a); + + z += 2; + a = z[14*64] * 104; + a += z[12*64] * 1567; + a += z[10*64] * 9727; + a += z[ 8*64] * 64019; + a += z[ 6*64] * -9975; + a += z[ 4*64] * -45; + a += z[ 2*64] * 146; + a += z[ 0*64] * -5; + pcm[16*nch] = drmp3d_scale_pcm(a); +} + +static void drmp3d_synth(float *xl, drmp3d_sample_t *dstl, int nch, float *lins) +{ + int i; + float *xr = xl + 576*(nch - 1); + drmp3d_sample_t *dstr = dstl + (nch - 1); + + static const float g_win[] = { + -1,26,-31,208,218,401,-519,2063,2000,4788,-5517,7134,5959,35640,-39336,74992, + -1,24,-35,202,222,347,-581,2080,1952,4425,-5879,7640,5288,33791,-41176,74856, + -1,21,-38,196,225,294,-645,2087,1893,4063,-6237,8092,4561,31947,-43006,74630, + -1,19,-41,190,227,244,-711,2085,1822,3705,-6589,8492,3776,30112,-44821,74313, + -1,17,-45,183,228,197,-779,2075,1739,3351,-6935,8840,2935,28289,-46617,73908, + -1,16,-49,176,228,153,-848,2057,1644,3004,-7271,9139,2037,26482,-48390,73415, + -2,14,-53,169,227,111,-919,2032,1535,2663,-7597,9389,1082,24694,-50137,72835, + -2,13,-58,161,224,72,-991,2001,1414,2330,-7910,9592,70,22929,-51853,72169, + -2,11,-63,154,221,36,-1064,1962,1280,2006,-8209,9750,-998,21189,-53534,71420, + -2,10,-68,147,215,2,-1137,1919,1131,1692,-8491,9863,-2122,19478,-55178,70590, + -3,9,-73,139,208,-29,-1210,1870,970,1388,-8755,9935,-3300,17799,-56778,69679, + -3,8,-79,132,200,-57,-1283,1817,794,1095,-8998,9966,-4533,16155,-58333,68692, + -4,7,-85,125,189,-83,-1356,1759,605,814,-9219,9959,-5818,14548,-59838,67629, + -4,7,-91,117,177,-106,-1428,1698,402,545,-9416,9916,-7154,12980,-61289,66494, + -5,6,-97,111,163,-127,-1498,1634,185,288,-9585,9838,-8540,11455,-62684,65290 + }; + float *zlin = lins + 15*64; + const float *w = g_win; + + zlin[4*15] = xl[18*16]; + zlin[4*15 + 1] = xr[18*16]; + zlin[4*15 + 2] = xl[0]; + zlin[4*15 + 3] = xr[0]; + + zlin[4*31] = xl[1 + 18*16]; + zlin[4*31 + 1] = xr[1 + 18*16]; + zlin[4*31 + 2] = xl[1]; + zlin[4*31 + 3] = xr[1]; + + drmp3d_synth_pair(dstr, nch, lins + 4*15 + 1); + drmp3d_synth_pair(dstr + 32*nch, nch, lins + 4*15 + 64 + 1); + drmp3d_synth_pair(dstl, nch, lins + 4*15); + drmp3d_synth_pair(dstl + 32*nch, nch, lins + 4*15 + 64); + +#if DRMP3_HAVE_SIMD + if (drmp3_have_simd()) for (i = 14; i >= 0; i--) + { +#define DRMP3_VLOAD(k) drmp3_f4 w0 = DRMP3_VSET(*w++); drmp3_f4 w1 = DRMP3_VSET(*w++); drmp3_f4 vz = DRMP3_VLD(&zlin[4*i - 64*k]); drmp3_f4 vy = DRMP3_VLD(&zlin[4*i - 64*(15 - k)]); +#define DRMP3_V0(k) { DRMP3_VLOAD(k) b = DRMP3_VADD(DRMP3_VMUL(vz, w1), DRMP3_VMUL(vy, w0)) ; a = DRMP3_VSUB(DRMP3_VMUL(vz, w0), DRMP3_VMUL(vy, w1)); } +#define DRMP3_V1(k) { DRMP3_VLOAD(k) b = DRMP3_VADD(b, DRMP3_VADD(DRMP3_VMUL(vz, w1), DRMP3_VMUL(vy, w0))); a = DRMP3_VADD(a, DRMP3_VSUB(DRMP3_VMUL(vz, w0), DRMP3_VMUL(vy, w1))); } +#define DRMP3_V2(k) { DRMP3_VLOAD(k) b = DRMP3_VADD(b, DRMP3_VADD(DRMP3_VMUL(vz, w1), DRMP3_VMUL(vy, w0))); a = DRMP3_VADD(a, DRMP3_VSUB(DRMP3_VMUL(vy, w1), DRMP3_VMUL(vz, w0))); } + drmp3_f4 a, b; + zlin[4*i] = xl[18*(31 - i)]; + zlin[4*i + 1] = xr[18*(31 - i)]; + zlin[4*i + 2] = xl[1 + 18*(31 - i)]; + zlin[4*i + 3] = xr[1 + 18*(31 - i)]; + zlin[4*i + 64] = xl[1 + 18*(1 + i)]; + zlin[4*i + 64 + 1] = xr[1 + 18*(1 + i)]; + zlin[4*i - 64 + 2] = xl[18*(1 + i)]; + zlin[4*i - 64 + 3] = xr[18*(1 + i)]; + + DRMP3_V0(0) DRMP3_V2(1) DRMP3_V1(2) DRMP3_V2(3) DRMP3_V1(4) DRMP3_V2(5) DRMP3_V1(6) DRMP3_V2(7) + + { +#ifndef DR_MP3_FLOAT_OUTPUT +#if DRMP3_HAVE_SSE + static const drmp3_f4 g_max = { 32767.0f, 32767.0f, 32767.0f, 32767.0f }; + static const drmp3_f4 g_min = { -32768.0f, -32768.0f, -32768.0f, -32768.0f }; + __m128i pcm8 = _mm_packs_epi32(_mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(a, g_max), g_min)), + _mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(b, g_max), g_min))); + dstr[(15 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 1); + dstr[(17 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 5); + dstl[(15 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 0); + dstl[(17 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 4); + dstr[(47 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 3); + dstr[(49 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 7); + dstl[(47 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 2); + dstl[(49 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 6); +#else + int16x4_t pcma, pcmb; + a = DRMP3_VADD(a, DRMP3_VSET(0.5f)); + b = DRMP3_VADD(b, DRMP3_VSET(0.5f)); + pcma = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(a), vreinterpretq_s32_u32(vcltq_f32(a, DRMP3_VSET(0))))); + pcmb = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(b), vreinterpretq_s32_u32(vcltq_f32(b, DRMP3_VSET(0))))); + vst1_lane_s16(dstr + (15 - i)*nch, pcma, 1); + vst1_lane_s16(dstr + (17 + i)*nch, pcmb, 1); + vst1_lane_s16(dstl + (15 - i)*nch, pcma, 0); + vst1_lane_s16(dstl + (17 + i)*nch, pcmb, 0); + vst1_lane_s16(dstr + (47 - i)*nch, pcma, 3); + vst1_lane_s16(dstr + (49 + i)*nch, pcmb, 3); + vst1_lane_s16(dstl + (47 - i)*nch, pcma, 2); + vst1_lane_s16(dstl + (49 + i)*nch, pcmb, 2); +#endif +#else + static const drmp3_f4 g_scale = { 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f }; + a = DRMP3_VMUL(a, g_scale); + b = DRMP3_VMUL(b, g_scale); +#if DRMP3_HAVE_SSE + _mm_store_ss(dstr + (15 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(1, 1, 1, 1))); + _mm_store_ss(dstr + (17 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(1, 1, 1, 1))); + _mm_store_ss(dstl + (15 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(0, 0, 0, 0))); + _mm_store_ss(dstl + (17 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(0, 0, 0, 0))); + _mm_store_ss(dstr + (47 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(3, 3, 3, 3))); + _mm_store_ss(dstr + (49 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(3, 3, 3, 3))); + _mm_store_ss(dstl + (47 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(2, 2, 2, 2))); + _mm_store_ss(dstl + (49 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(2, 2, 2, 2))); +#else + vst1q_lane_f32(dstr + (15 - i)*nch, a, 1); + vst1q_lane_f32(dstr + (17 + i)*nch, b, 1); + vst1q_lane_f32(dstl + (15 - i)*nch, a, 0); + vst1q_lane_f32(dstl + (17 + i)*nch, b, 0); + vst1q_lane_f32(dstr + (47 - i)*nch, a, 3); + vst1q_lane_f32(dstr + (49 + i)*nch, b, 3); + vst1q_lane_f32(dstl + (47 - i)*nch, a, 2); + vst1q_lane_f32(dstl + (49 + i)*nch, b, 2); +#endif +#endif /* DR_MP3_FLOAT_OUTPUT */ + } + } else +#endif +#ifdef DR_MP3_ONLY_SIMD + {} +#else + for (i = 14; i >= 0; i--) + { +#define DRMP3_LOAD(k) float w0 = *w++; float w1 = *w++; float *vz = &zlin[4*i - k*64]; float *vy = &zlin[4*i - (15 - k)*64]; +#define DRMP3_S0(k) { int j; DRMP3_LOAD(k); for (j = 0; j < 4; j++) b[j] = vz[j]*w1 + vy[j]*w0, a[j] = vz[j]*w0 - vy[j]*w1; } +#define DRMP3_S1(k) { int j; DRMP3_LOAD(k); for (j = 0; j < 4; j++) b[j] += vz[j]*w1 + vy[j]*w0, a[j] += vz[j]*w0 - vy[j]*w1; } +#define DRMP3_S2(k) { int j; DRMP3_LOAD(k); for (j = 0; j < 4; j++) b[j] += vz[j]*w1 + vy[j]*w0, a[j] += vy[j]*w1 - vz[j]*w0; } + float a[4], b[4]; + + zlin[4*i] = xl[18*(31 - i)]; + zlin[4*i + 1] = xr[18*(31 - i)]; + zlin[4*i + 2] = xl[1 + 18*(31 - i)]; + zlin[4*i + 3] = xr[1 + 18*(31 - i)]; + zlin[4*(i + 16)] = xl[1 + 18*(1 + i)]; + zlin[4*(i + 16) + 1] = xr[1 + 18*(1 + i)]; + zlin[4*(i - 16) + 2] = xl[18*(1 + i)]; + zlin[4*(i - 16) + 3] = xr[18*(1 + i)]; + + DRMP3_S0(0) DRMP3_S2(1) DRMP3_S1(2) DRMP3_S2(3) DRMP3_S1(4) DRMP3_S2(5) DRMP3_S1(6) DRMP3_S2(7) + + dstr[(15 - i)*nch] = drmp3d_scale_pcm(a[1]); + dstr[(17 + i)*nch] = drmp3d_scale_pcm(b[1]); + dstl[(15 - i)*nch] = drmp3d_scale_pcm(a[0]); + dstl[(17 + i)*nch] = drmp3d_scale_pcm(b[0]); + dstr[(47 - i)*nch] = drmp3d_scale_pcm(a[3]); + dstr[(49 + i)*nch] = drmp3d_scale_pcm(b[3]); + dstl[(47 - i)*nch] = drmp3d_scale_pcm(a[2]); + dstl[(49 + i)*nch] = drmp3d_scale_pcm(b[2]); + } +#endif +} + +static void drmp3d_synth_granule(float *qmf_state, float *grbuf, int nbands, int nch, drmp3d_sample_t *pcm, float *lins) +{ + int i; + for (i = 0; i < nch; i++) + { + drmp3d_DCT_II(grbuf + 576*i, nbands); + } + + memcpy(lins, qmf_state, sizeof(float)*15*64); + + for (i = 0; i < nbands; i += 2) + { + drmp3d_synth(grbuf + i, pcm + 32*nch*i, nch, lins + i*64); + } +#ifndef DR_MP3_NONSTANDARD_BUT_LOGICAL + if (nch == 1) + { + for (i = 0; i < 15*64; i += 2) + { + qmf_state[i] = lins[nbands*64 + i]; + } + } else +#endif + { + memcpy(qmf_state, lins + nbands*64, sizeof(float)*15*64); + } +} + +static int drmp3d_match_frame(const drmp3_uint8 *hdr, int mp3_bytes, int frame_bytes) +{ + int i, nmatch; + for (i = 0, nmatch = 0; nmatch < DRMP3_MAX_FRAME_SYNC_MATCHES; nmatch++) + { + i += drmp3_hdr_frame_bytes(hdr + i, frame_bytes) + drmp3_hdr_padding(hdr + i); + if (i + DRMP3_HDR_SIZE > mp3_bytes) + return nmatch > 0; + if (!drmp3_hdr_compare(hdr, hdr + i)) + return 0; + } + return 1; +} + +static int drmp3d_find_frame(const drmp3_uint8 *mp3, int mp3_bytes, int *free_format_bytes, int *ptr_frame_bytes) +{ + int i, k; + for (i = 0; i < mp3_bytes - DRMP3_HDR_SIZE; i++, mp3++) + { + if (drmp3_hdr_valid(mp3)) + { + int frame_bytes = drmp3_hdr_frame_bytes(mp3, *free_format_bytes); + int frame_and_padding = frame_bytes + drmp3_hdr_padding(mp3); + + for (k = DRMP3_HDR_SIZE; !frame_bytes && k < DRMP3_MAX_FREE_FORMAT_FRAME_SIZE && i + 2*k < mp3_bytes - DRMP3_HDR_SIZE; k++) + { + if (drmp3_hdr_compare(mp3, mp3 + k)) + { + int fb = k - drmp3_hdr_padding(mp3); + int nextfb = fb + drmp3_hdr_padding(mp3 + k); + if (i + k + nextfb + DRMP3_HDR_SIZE > mp3_bytes || !drmp3_hdr_compare(mp3, mp3 + k + nextfb)) + continue; + frame_and_padding = k; + frame_bytes = fb; + *free_format_bytes = fb; + } + } + + if ((frame_bytes && i + frame_and_padding <= mp3_bytes && + drmp3d_match_frame(mp3, mp3_bytes - i, frame_bytes)) || + (!i && frame_and_padding == mp3_bytes)) + { + *ptr_frame_bytes = frame_and_padding; + return i; + } + *free_format_bytes = 0; + } + } + *ptr_frame_bytes = 0; + return i; +} + +void drmp3dec_init(drmp3dec *dec) +{ + dec->header[0] = 0; +} + +int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info) +{ + int i = 0, igr, frame_size = 0, success = 1; + const drmp3_uint8 *hdr; + drmp3_bs bs_frame[1]; + drmp3dec_scratch scratch; + + if (mp3_bytes > 4 && dec->header[0] == 0xff && drmp3_hdr_compare(dec->header, mp3)) + { + frame_size = drmp3_hdr_frame_bytes(mp3, dec->free_format_bytes) + drmp3_hdr_padding(mp3); + if (frame_size != mp3_bytes && (frame_size + DRMP3_HDR_SIZE > mp3_bytes || !drmp3_hdr_compare(mp3, mp3 + frame_size))) + { + frame_size = 0; + } + } + if (!frame_size) + { + memset(dec, 0, sizeof(drmp3dec)); + i = drmp3d_find_frame(mp3, mp3_bytes, &dec->free_format_bytes, &frame_size); + if (!frame_size || i + frame_size > mp3_bytes) + { + info->frame_bytes = i; + return 0; + } + } + + hdr = mp3 + i; + memcpy(dec->header, hdr, DRMP3_HDR_SIZE); + info->frame_bytes = i + frame_size; + info->channels = DRMP3_HDR_IS_MONO(hdr) ? 1 : 2; + info->hz = drmp3_hdr_sample_rate_hz(hdr); + info->layer = 4 - DRMP3_HDR_GET_LAYER(hdr); + info->bitrate_kbps = drmp3_hdr_bitrate_kbps(hdr); + + drmp3_bs_init(bs_frame, hdr + DRMP3_HDR_SIZE, frame_size - DRMP3_HDR_SIZE); + if (DRMP3_HDR_IS_CRC(hdr)) + { + drmp3_bs_get_bits(bs_frame, 16); + } + + if (info->layer == 3) + { + int main_data_begin = drmp3_L3_read_side_info(bs_frame, scratch.gr_info, hdr); + if (main_data_begin < 0 || bs_frame->pos > bs_frame->limit) + { + drmp3dec_init(dec); + return 0; + } + success = drmp3_L3_restore_reservoir(dec, bs_frame, &scratch, main_data_begin); + if (success && pcm != NULL) + { + for (igr = 0; igr < (DRMP3_HDR_TEST_MPEG1(hdr) ? 2 : 1); igr++, pcm = DRMP3_OFFSET_PTR(pcm, sizeof(drmp3d_sample_t)*576*info->channels)) + { + memset(scratch.grbuf[0], 0, 576*2*sizeof(float)); + drmp3_L3_decode(dec, &scratch, scratch.gr_info + igr*info->channels, info->channels); + drmp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 18, info->channels, (drmp3d_sample_t*)pcm, scratch.syn[0]); + } + } + drmp3_L3_save_reservoir(dec, &scratch); + } else + { +#ifdef DR_MP3_ONLY_MP3 + return 0; +#else + if (pcm == NULL) { + return drmp3_hdr_frame_samples(hdr); + } + + drmp3_L12_scale_info sci[1]; + drmp3_L12_read_scale_info(hdr, bs_frame, sci); + + memset(scratch.grbuf[0], 0, 576*2*sizeof(float)); + for (i = 0, igr = 0; igr < 3; igr++) + { + if (12 == (i += drmp3_L12_dequantize_granule(scratch.grbuf[0] + i, bs_frame, sci, info->layer | 1))) + { + i = 0; + drmp3_L12_apply_scf_384(sci, sci->scf + igr, scratch.grbuf[0]); + drmp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 12, info->channels, (drmp3d_sample_t*)pcm, scratch.syn[0]); + memset(scratch.grbuf[0], 0, 576*2*sizeof(float)); + pcm = DRMP3_OFFSET_PTR(pcm, sizeof(drmp3d_sample_t)*384*info->channels); + } + if (bs_frame->pos > bs_frame->limit) + { + drmp3dec_init(dec); + return 0; + } + } +#endif + } + + return success*drmp3_hdr_frame_samples(dec->header); +} + +void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples) +{ + if(num_samples > 0) + { + int i = 0; +#if DRMP3_HAVE_SIMD + int aligned_count = num_samples & ~7; + for(; i < aligned_count; i+=8) + { + static const drmp3_f4 g_scale = { 32768.0f, 32768.0f, 32768.0f, 32768.0f }; + drmp3_f4 a = DRMP3_VMUL(DRMP3_VLD(&in[i ]), g_scale); + drmp3_f4 b = DRMP3_VMUL(DRMP3_VLD(&in[i+4]), g_scale); +#if DRMP3_HAVE_SSE + static const drmp3_f4 g_max = { 32767.0f, 32767.0f, 32767.0f, 32767.0f }; + static const drmp3_f4 g_min = { -32768.0f, -32768.0f, -32768.0f, -32768.0f }; + __m128i pcm8 = _mm_packs_epi32(_mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(a, g_max), g_min)), + _mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(b, g_max), g_min))); + out[i ] = (drmp3_int16)_mm_extract_epi16(pcm8, 0); + out[i+1] = (drmp3_int16)_mm_extract_epi16(pcm8, 1); + out[i+2] = (drmp3_int16)_mm_extract_epi16(pcm8, 2); + out[i+3] = (drmp3_int16)_mm_extract_epi16(pcm8, 3); + out[i+4] = (drmp3_int16)_mm_extract_epi16(pcm8, 4); + out[i+5] = (drmp3_int16)_mm_extract_epi16(pcm8, 5); + out[i+6] = (drmp3_int16)_mm_extract_epi16(pcm8, 6); + out[i+7] = (drmp3_int16)_mm_extract_epi16(pcm8, 7); +#else + int16x4_t pcma, pcmb; + a = DRMP3_VADD(a, DRMP3_VSET(0.5f)); + b = DRMP3_VADD(b, DRMP3_VSET(0.5f)); + pcma = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(a), vreinterpretq_s32_u32(vcltq_f32(a, DRMP3_VSET(0))))); + pcmb = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(b), vreinterpretq_s32_u32(vcltq_f32(b, DRMP3_VSET(0))))); + vst1_lane_s16(out+i , pcma, 0); + vst1_lane_s16(out+i+1, pcma, 1); + vst1_lane_s16(out+i+2, pcma, 2); + vst1_lane_s16(out+i+3, pcma, 3); + vst1_lane_s16(out+i+4, pcmb, 0); + vst1_lane_s16(out+i+5, pcmb, 1); + vst1_lane_s16(out+i+6, pcmb, 2); + vst1_lane_s16(out+i+7, pcmb, 3); +#endif + } +#endif + for(; i < num_samples; i++) + { + float sample = in[i] * 32768.0f; + if (sample >= 32766.5) + out[i] = (drmp3_int16) 32767; + else if (sample <= -32767.5) + out[i] = (drmp3_int16)-32768; + else + { + short s = (drmp3_int16)(sample + .5f); + s -= (s < 0); /* away from zero, to be compliant */ + out[i] = s; + } + } + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// +// Main Public API +// +/////////////////////////////////////////////////////////////////////////////// + +#if defined(SIZE_MAX) + #define DRMP3_SIZE_MAX SIZE_MAX +#else + #if defined(_WIN64) || defined(_LP64) || defined(__LP64__) + #define DRMP3_SIZE_MAX ((drmp3_uint64)0xFFFFFFFFFFFFFFFF) + #else + #define DRMP3_SIZE_MAX 0xFFFFFFFF + #endif +#endif + +// Options. +#ifndef DR_MP3_DEFAULT_CHANNELS +#define DR_MP3_DEFAULT_CHANNELS 2 +#endif +#ifndef DR_MP3_DEFAULT_SAMPLE_RATE +#define DR_MP3_DEFAULT_SAMPLE_RATE 44100 +#endif +#ifndef DRMP3_SEEK_LEADING_MP3_FRAMES +#define DRMP3_SEEK_LEADING_MP3_FRAMES 2 +#endif + + +// Standard library stuff. +#ifndef DRMP3_ASSERT +#include +#define DRMP3_ASSERT(expression) assert(expression) +#endif +#ifndef DRMP3_COPY_MEMORY +#define DRMP3_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) +#endif +#ifndef DRMP3_ZERO_MEMORY +#define DRMP3_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) +#endif +#define DRMP3_ZERO_OBJECT(p) DRMP3_ZERO_MEMORY((p), sizeof(*(p))) +#ifndef DRMP3_MALLOC +#define DRMP3_MALLOC(sz) malloc((sz)) +#endif +#ifndef DRMP3_REALLOC +#define DRMP3_REALLOC(p, sz) realloc((p), (sz)) +#endif +#ifndef DRMP3_FREE +#define DRMP3_FREE(p) free((p)) +#endif + +#define drmp3_assert DRMP3_ASSERT +#define drmp3_copy_memory DRMP3_COPY_MEMORY +#define drmp3_zero_memory DRMP3_ZERO_MEMORY +#define drmp3_zero_object DRMP3_ZERO_OBJECT +#define drmp3_malloc DRMP3_MALLOC +#define drmp3_realloc DRMP3_REALLOC + +#define drmp3_countof(x) (sizeof(x) / sizeof(x[0])) +#define drmp3_max(x, y) (((x) > (y)) ? (x) : (y)) +#define drmp3_min(x, y) (((x) < (y)) ? (x) : (y)) + +#define DRMP3_DATA_CHUNK_SIZE 16384 // The size in bytes of each chunk of data to read from the MP3 stream. minimp3 recommends 16K. + +static inline float drmp3_mix_f32(float x, float y, float a) +{ + return x*(1-a) + y*a; +} + +static void drmp3_blend_f32(float* pOut, float* pInA, float* pInB, float factor, drmp3_uint32 channels) +{ + for (drmp3_uint32 i = 0; i < channels; ++i) { + pOut[i] = drmp3_mix_f32(pInA[i], pInB[i], factor); + } +} + +void drmp3_src_cache_init(drmp3_src* pSRC, drmp3_src_cache* pCache) +{ + drmp3_assert(pSRC != NULL); + drmp3_assert(pCache != NULL); + + pCache->pSRC = pSRC; + pCache->cachedFrameCount = 0; + pCache->iNextFrame = 0; +} + +drmp3_uint64 drmp3_src_cache_read_frames(drmp3_src_cache* pCache, drmp3_uint64 frameCount, float* pFramesOut) +{ + drmp3_assert(pCache != NULL); + drmp3_assert(pCache->pSRC != NULL); + drmp3_assert(pCache->pSRC->onRead != NULL); + drmp3_assert(frameCount > 0); + drmp3_assert(pFramesOut != NULL); + + drmp3_uint32 channels = pCache->pSRC->config.channels; + + drmp3_uint64 totalFramesRead = 0; + while (frameCount > 0) { + // If there's anything in memory go ahead and copy that over first. + drmp3_uint64 framesRemainingInMemory = pCache->cachedFrameCount - pCache->iNextFrame; + drmp3_uint64 framesToReadFromMemory = frameCount; + if (framesToReadFromMemory > framesRemainingInMemory) { + framesToReadFromMemory = framesRemainingInMemory; + } + + drmp3_copy_memory(pFramesOut, pCache->pCachedFrames + pCache->iNextFrame*channels, (drmp3_uint32)(framesToReadFromMemory * channels * sizeof(float))); + pCache->iNextFrame += (drmp3_uint32)framesToReadFromMemory; + + totalFramesRead += framesToReadFromMemory; + frameCount -= framesToReadFromMemory; + if (frameCount == 0) { + break; + } + + + // At this point there are still more frames to read from the client, so we'll need to reload the cache with fresh data. + drmp3_assert(frameCount > 0); + pFramesOut += framesToReadFromMemory * channels; + + pCache->iNextFrame = 0; + pCache->cachedFrameCount = 0; + + drmp3_uint32 framesToReadFromClient = drmp3_countof(pCache->pCachedFrames) / pCache->pSRC->config.channels; + if (framesToReadFromClient > pCache->pSRC->config.cacheSizeInFrames) { + framesToReadFromClient = pCache->pSRC->config.cacheSizeInFrames; + } + + pCache->cachedFrameCount = (drmp3_uint32)pCache->pSRC->onRead(pCache->pSRC, framesToReadFromClient, pCache->pCachedFrames, pCache->pSRC->pUserData); + + + // Get out of this loop if nothing was able to be retrieved. + if (pCache->cachedFrameCount == 0) { + break; + } + } + + return totalFramesRead; +} + + +drmp3_uint64 drmp3_src_read_frames_passthrough(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush); +drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush); + +drmp3_bool32 drmp3_src_init(const drmp3_src_config* pConfig, drmp3_src_read_proc onRead, void* pUserData, drmp3_src* pSRC) +{ + if (pSRC == NULL) return DRMP3_FALSE; + drmp3_zero_object(pSRC); + + if (pConfig == NULL || onRead == NULL) return DRMP3_FALSE; + if (pConfig->channels == 0 || pConfig->channels > 2) return DRMP3_FALSE; + + pSRC->config = *pConfig; + pSRC->onRead = onRead; + pSRC->pUserData = pUserData; + + if (pSRC->config.cacheSizeInFrames > DRMP3_SRC_CACHE_SIZE_IN_FRAMES || pSRC->config.cacheSizeInFrames == 0) { + pSRC->config.cacheSizeInFrames = DRMP3_SRC_CACHE_SIZE_IN_FRAMES; + } + + drmp3_src_cache_init(pSRC, &pSRC->cache); + return DRMP3_TRUE; +} + +drmp3_bool32 drmp3_src_set_input_sample_rate(drmp3_src* pSRC, drmp3_uint32 sampleRateIn) +{ + if (pSRC == NULL) return DRMP3_FALSE; + + // Must have a sample rate of > 0. + if (sampleRateIn == 0) { + return DRMP3_FALSE; + } + + pSRC->config.sampleRateIn = sampleRateIn; + return DRMP3_TRUE; +} + +drmp3_bool32 drmp3_src_set_output_sample_rate(drmp3_src* pSRC, drmp3_uint32 sampleRateOut) +{ + if (pSRC == NULL) return DRMP3_FALSE; + + // Must have a sample rate of > 0. + if (sampleRateOut == 0) { + return DRMP3_FALSE; + } + + pSRC->config.sampleRateOut = sampleRateOut; + return DRMP3_TRUE; +} + +drmp3_uint64 drmp3_src_read_frames_ex(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush) +{ + if (pSRC == NULL || frameCount == 0 || pFramesOut == NULL) return 0; + + drmp3_src_algorithm algorithm = pSRC->config.algorithm; + + // Always use passthrough if the sample rates are the same. + if (pSRC->config.sampleRateIn == pSRC->config.sampleRateOut) { + algorithm = drmp3_src_algorithm_none; + } + + // Could just use a function pointer instead of a switch for this... + switch (algorithm) + { + case drmp3_src_algorithm_none: return drmp3_src_read_frames_passthrough(pSRC, frameCount, pFramesOut, flush); + case drmp3_src_algorithm_linear: return drmp3_src_read_frames_linear(pSRC, frameCount, pFramesOut, flush); + default: return 0; + } +} + +drmp3_uint64 drmp3_src_read_frames(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut) +{ + return drmp3_src_read_frames_ex(pSRC, frameCount, pFramesOut, DRMP3_FALSE); +} + +drmp3_uint64 drmp3_src_read_frames_passthrough(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush) +{ + drmp3_assert(pSRC != NULL); + drmp3_assert(frameCount > 0); + drmp3_assert(pFramesOut != NULL); + + (void)flush; // Passthrough need not care about flushing. + return pSRC->onRead(pSRC, frameCount, pFramesOut, pSRC->pUserData); +} + +drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush) +{ + drmp3_assert(pSRC != NULL); + drmp3_assert(frameCount > 0); + drmp3_assert(pFramesOut != NULL); + + // For linear SRC, the bin is only 2 frames: 1 prior, 1 future. + + // Load the bin if necessary. + if (!pSRC->algo.linear.isPrevFramesLoaded) { + drmp3_uint64 framesRead = drmp3_src_cache_read_frames(&pSRC->cache, 1, pSRC->bin); + if (framesRead == 0) { + return 0; + } + pSRC->algo.linear.isPrevFramesLoaded = DRMP3_TRUE; + } + if (!pSRC->algo.linear.isNextFramesLoaded) { + drmp3_uint64 framesRead = drmp3_src_cache_read_frames(&pSRC->cache, 1, pSRC->bin + pSRC->config.channels); + if (framesRead == 0) { + return 0; + } + pSRC->algo.linear.isNextFramesLoaded = DRMP3_TRUE; + } + + double factor = (double)pSRC->config.sampleRateIn / pSRC->config.sampleRateOut; + + drmp3_uint64 totalFramesRead = 0; + while (frameCount > 0) { + // The bin is where the previous and next frames are located. + float* pPrevFrame = pSRC->bin; + float* pNextFrame = pSRC->bin + pSRC->config.channels; + + drmp3_blend_f32((float*)pFramesOut, pPrevFrame, pNextFrame, (float)pSRC->algo.linear.alpha, pSRC->config.channels); + + pSRC->algo.linear.alpha += factor; + + // The new alpha value is how we determine whether or not we need to read fresh frames. + drmp3_uint32 framesToReadFromClient = (drmp3_uint32)pSRC->algo.linear.alpha; + pSRC->algo.linear.alpha = pSRC->algo.linear.alpha - framesToReadFromClient; + + for (drmp3_uint32 i = 0; i < framesToReadFromClient; ++i) { + for (drmp3_uint32 j = 0; j < pSRC->config.channels; ++j) { + pPrevFrame[j] = pNextFrame[j]; + } + + drmp3_uint64 framesRead = drmp3_src_cache_read_frames(&pSRC->cache, 1, pNextFrame); + if (framesRead == 0) { + for (drmp3_uint32 j = 0; j < pSRC->config.channels; ++j) { + pNextFrame[j] = 0; + } + + if (pSRC->algo.linear.isNextFramesLoaded) { + pSRC->algo.linear.isNextFramesLoaded = DRMP3_FALSE; + } else { + if (flush) { + pSRC->algo.linear.isPrevFramesLoaded = DRMP3_FALSE; + } + } + + break; + } + } + + pFramesOut = (drmp3_uint8*)pFramesOut + (1 * pSRC->config.channels * sizeof(float)); + frameCount -= 1; + totalFramesRead += 1; + + // If there's no frames available we need to get out of this loop. + if (!pSRC->algo.linear.isNextFramesLoaded && (!flush || !pSRC->algo.linear.isPrevFramesLoaded)) { + break; + } + } + + return totalFramesRead; +} + + +static size_t drmp3__on_read(drmp3* pMP3, void* pBufferOut, size_t bytesToRead) +{ + size_t bytesRead = pMP3->onRead(pMP3->pUserData, pBufferOut, bytesToRead); + pMP3->streamCursor += bytesRead; + return bytesRead; +} + +static drmp3_bool32 drmp3__on_seek(drmp3* pMP3, int offset, drmp3_seek_origin origin) +{ + drmp3_assert(offset >= 0); + + if (!pMP3->onSeek(pMP3->pUserData, offset, origin)) { + return DRMP3_FALSE; + } + + if (origin == drmp3_seek_origin_start) { + pMP3->streamCursor = (drmp3_uint64)offset; + } else { + pMP3->streamCursor += offset; + } + + return DRMP3_TRUE; +} + +static drmp3_bool32 drmp3__on_seek_64(drmp3* pMP3, drmp3_uint64 offset, drmp3_seek_origin origin) +{ + if (offset <= 0x7FFFFFFF) { + return drmp3__on_seek(pMP3, (int)offset, origin); + } + + + // Getting here "offset" is too large for a 32-bit integer. We just keep seeking forward until we hit the offset. + if (!drmp3__on_seek(pMP3, 0x7FFFFFFF, drmp3_seek_origin_start)) { + return DRMP3_FALSE; + } + + offset -= 0x7FFFFFFF; + while (offset > 0) { + if (offset <= 0x7FFFFFFF) { + if (!drmp3__on_seek(pMP3, (int)offset, drmp3_seek_origin_current)) { + return DRMP3_FALSE; + } + offset = 0; + } else { + if (!drmp3__on_seek(pMP3, 0x7FFFFFFF, drmp3_seek_origin_current)) { + return DRMP3_FALSE; + } + offset -= 0x7FFFFFFF; + } + } + + return DRMP3_TRUE; +} + + + + +static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames, drmp3_bool32 discard) +{ + drmp3_assert(pMP3 != NULL); + drmp3_assert(pMP3->onRead != NULL); + + if (pMP3->atEnd) { + return 0; + } + + drmp3_uint32 pcmFramesRead = 0; + do { + // minimp3 recommends doing data submission in 16K chunks. If we don't have at least 16K bytes available, get more. + if (pMP3->dataSize < DRMP3_DATA_CHUNK_SIZE) { + if (pMP3->dataCapacity < DRMP3_DATA_CHUNK_SIZE) { + pMP3->dataCapacity = DRMP3_DATA_CHUNK_SIZE; + drmp3_uint8* pNewData = (drmp3_uint8*)drmp3_realloc(pMP3->pData, pMP3->dataCapacity); + if (pNewData == NULL) { + return 0; // Out of memory. + } + + pMP3->pData = pNewData; + } + + size_t bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); + if (bytesRead == 0) { + if (pMP3->dataSize == 0) { + pMP3->atEnd = DRMP3_TRUE; + return 0; // No data. + } + } + + pMP3->dataSize += bytesRead; + } + + if (pMP3->dataSize > INT_MAX) { + pMP3->atEnd = DRMP3_TRUE; + return 0; // File too big. + } + + drmp3dec_frame_info info; + pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData, (int)pMP3->dataSize, pPCMFrames, &info); // <-- Safe size_t -> int conversion thanks to the check above. + + // Consume the data. + size_t leftoverDataSize = (pMP3->dataSize - (size_t)info.frame_bytes); + if (info.frame_bytes > 0) { + memmove(pMP3->pData, pMP3->pData + info.frame_bytes, leftoverDataSize); + pMP3->dataSize = leftoverDataSize; + } + + // pcmFramesRead will be equal to 0 if decoding failed. If it is zero and info.frame_bytes > 0 then we have successfully + // decoded the frame. A special case is if we are wanting to discard the frame, in which case we return successfully. + if (pcmFramesRead > 0 || (info.frame_bytes > 0 && discard)) { + pcmFramesRead = drmp3_hdr_frame_samples(pMP3->decoder.header); + pMP3->pcmFramesConsumedInMP3Frame = 0; + pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead; + pMP3->mp3FrameChannels = info.channels; + pMP3->mp3FrameSampleRate = info.hz; + drmp3_src_set_input_sample_rate(&pMP3->src, pMP3->mp3FrameSampleRate); + break; + } else if (info.frame_bytes == 0) { + // Need more data. minimp3 recommends doing data submission in 16K chunks. + if (pMP3->dataCapacity == pMP3->dataSize) { + // No room. Expand. + pMP3->dataCapacity += DRMP3_DATA_CHUNK_SIZE; + drmp3_uint8* pNewData = (drmp3_uint8*)drmp3_realloc(pMP3->pData, pMP3->dataCapacity); + if (pNewData == NULL) { + return 0; // Out of memory. + } + + pMP3->pData = pNewData; + } + + // Fill in a chunk. + size_t bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); + if (bytesRead == 0) { + pMP3->atEnd = DRMP3_TRUE; + return 0; // Error reading more data. + } + + pMP3->dataSize += bytesRead; + } + } while (DRMP3_TRUE); + + return pcmFramesRead; +} + +static drmp3_uint32 drmp3_decode_next_frame(drmp3* pMP3) +{ + drmp3_assert(pMP3 != NULL); + return drmp3_decode_next_frame_ex(pMP3, (drmp3d_sample_t*)pMP3->pcmFrames, DRMP3_FALSE); +} + +#if 0 +static drmp3_uint32 drmp3_seek_next_frame(drmp3* pMP3) +{ + drmp3_assert(pMP3 != NULL); + + drmp3_uint32 pcmFrameCount = drmp3_decode_next_frame_ex(pMP3, NULL); + if (pcmFrameCount == 0) { + return 0; + } + + // We have essentially just skipped past the frame, so just set the remaining samples to 0. + pMP3->currentPCMFrame += pcmFrameCount; + pMP3->pcmFramesConsumedInMP3Frame = pcmFrameCount; + pMP3->pcmFramesRemainingInMP3Frame = 0; + + return pcmFrameCount; +} +#endif + +static drmp3_uint64 drmp3_read_src(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, void* pUserData) +{ + drmp3* pMP3 = (drmp3*)pUserData; + drmp3_assert(pMP3 != NULL); + drmp3_assert(pMP3->onRead != NULL); + + float* pFramesOutF = (float*)pFramesOut; + drmp3_uint64 totalFramesRead = 0; + + while (frameCount > 0) { + // Read from the in-memory buffer first. + while (pMP3->pcmFramesRemainingInMP3Frame > 0 && frameCount > 0) { + drmp3d_sample_t* frames = (drmp3d_sample_t*)pMP3->pcmFrames; +#ifndef DR_MP3_FLOAT_OUTPUT + if (pMP3->mp3FrameChannels == 1) { + if (pMP3->channels == 1) { + // Mono -> Mono. + pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; + } else { + // Mono -> Stereo. + pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; + pFramesOutF[1] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; + } + } else { + if (pMP3->channels == 1) { + // Stereo -> Mono + float sample = 0; + sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0] / 32768.0f; + sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1] / 32768.0f; + pFramesOutF[0] = sample * 0.5f; + } else { + // Stereo -> Stereo + pFramesOutF[0] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0] / 32768.0f; + pFramesOutF[1] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1] / 32768.0f; + } + } +#else + if (pMP3->mp3FrameChannels == 1) { + if (pMP3->channels == 1) { + // Mono -> Mono. + pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame]; + } else { + // Mono -> Stereo. + pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame]; + pFramesOutF[1] = frames[pMP3->pcmFramesConsumedInMP3Frame]; + } + } else { + if (pMP3->channels == 1) { + // Stereo -> Mono + float sample = 0; + sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0]; + sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1]; + pFramesOutF[0] = sample * 0.5f; + } else { + // Stereo -> Stereo + pFramesOutF[0] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0]; + pFramesOutF[1] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1]; + } + } +#endif + + pMP3->pcmFramesConsumedInMP3Frame += 1; + pMP3->pcmFramesRemainingInMP3Frame -= 1; + totalFramesRead += 1; + frameCount -= 1; + pFramesOutF += pSRC->config.channels; + } + + if (frameCount == 0) { + break; + } + + drmp3_assert(pMP3->pcmFramesRemainingInMP3Frame == 0); + + // At this point we have exhausted our in-memory buffer so we need to re-fill. Note that the sample rate may have changed + // at this point which means we'll also need to update our sample rate conversion pipeline. + if (drmp3_decode_next_frame(pMP3) == 0) { + break; + } + } + + return totalFramesRead; +} + +drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_config* pConfig) +{ + drmp3_assert(pMP3 != NULL); + drmp3_assert(onRead != NULL); + + // This function assumes the output object has already been reset to 0. Do not do that here, otherwise things will break. + drmp3dec_init(&pMP3->decoder); + + // The config can be null in which case we use defaults. + drmp3_config config; + if (pConfig != NULL) { + config = *pConfig; + } else { + drmp3_zero_object(&config); + } + + pMP3->channels = config.outputChannels; + if (pMP3->channels == 0) { + pMP3->channels = DR_MP3_DEFAULT_CHANNELS; + } + + // Cannot have more than 2 channels. + if (pMP3->channels > 2) { + pMP3->channels = 2; + } + + pMP3->sampleRate = config.outputSampleRate; + if (pMP3->sampleRate == 0) { + pMP3->sampleRate = DR_MP3_DEFAULT_SAMPLE_RATE; + } + + pMP3->onRead = onRead; + pMP3->onSeek = onSeek; + pMP3->pUserData = pUserData; + + // We need a sample rate converter for converting the sample rate from the MP3 frames to the requested output sample rate. + drmp3_src_config srcConfig; + drmp3_zero_object(&srcConfig); + srcConfig.sampleRateIn = DR_MP3_DEFAULT_SAMPLE_RATE; + srcConfig.sampleRateOut = pMP3->sampleRate; + srcConfig.channels = pMP3->channels; + srcConfig.algorithm = drmp3_src_algorithm_linear; + if (!drmp3_src_init(&srcConfig, drmp3_read_src, pMP3, &pMP3->src)) { + drmp3_uninit(pMP3); + return DRMP3_FALSE; + } + + // Decode the first frame to confirm that it is indeed a valid MP3 stream. + if (!drmp3_decode_next_frame(pMP3)) { + drmp3_uninit(pMP3); + return DRMP3_FALSE; // Not a valid MP3 stream. + } + + return DRMP3_TRUE; +} + +drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_config* pConfig) +{ + if (pMP3 == NULL || onRead == NULL) { + return DRMP3_FALSE; + } + + drmp3_zero_object(pMP3); + return drmp3_init_internal(pMP3, onRead, onSeek, pUserData, pConfig); +} + + +static size_t drmp3__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + drmp3* pMP3 = (drmp3*)pUserData; + drmp3_assert(pMP3 != NULL); + drmp3_assert(pMP3->memory.dataSize >= pMP3->memory.currentReadPos); + + size_t bytesRemaining = pMP3->memory.dataSize - pMP3->memory.currentReadPos; + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (bytesToRead > 0) { + drmp3_copy_memory(pBufferOut, pMP3->memory.pData + pMP3->memory.currentReadPos, bytesToRead); + pMP3->memory.currentReadPos += bytesToRead; + } + + return bytesToRead; +} + +static drmp3_bool32 drmp3__on_seek_memory(void* pUserData, int byteOffset, drmp3_seek_origin origin) +{ + drmp3* pMP3 = (drmp3*)pUserData; + drmp3_assert(pMP3 != NULL); + + if (origin == drmp3_seek_origin_current) { + if (byteOffset > 0) { + if (pMP3->memory.currentReadPos + byteOffset > pMP3->memory.dataSize) { + byteOffset = (int)(pMP3->memory.dataSize - pMP3->memory.currentReadPos); // Trying to seek too far forward. + } + } else { + if (pMP3->memory.currentReadPos < (size_t)-byteOffset) { + byteOffset = -(int)pMP3->memory.currentReadPos; // Trying to seek too far backwards. + } + } + + // This will never underflow thanks to the clamps above. + pMP3->memory.currentReadPos += byteOffset; + } else { + if ((drmp3_uint32)byteOffset <= pMP3->memory.dataSize) { + pMP3->memory.currentReadPos = byteOffset; + } else { + pMP3->memory.currentReadPos = pMP3->memory.dataSize; // Trying to seek too far forward. + } + } + + return DRMP3_TRUE; +} + +drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_config* pConfig) +{ + if (pMP3 == NULL) { + return DRMP3_FALSE; + } + + drmp3_zero_object(pMP3); + + if (pData == NULL || dataSize == 0) { + return DRMP3_FALSE; + } + + pMP3->memory.pData = (const drmp3_uint8*)pData; + pMP3->memory.dataSize = dataSize; + pMP3->memory.currentReadPos = 0; + + return drmp3_init_internal(pMP3, drmp3__on_read_memory, drmp3__on_seek_memory, pMP3, pConfig); +} + + +#ifndef DR_MP3_NO_STDIO +#include + +static size_t drmp3__on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + return fread(pBufferOut, 1, bytesToRead, (FILE*)pUserData); +} + +static drmp3_bool32 drmp3__on_seek_stdio(void* pUserData, int offset, drmp3_seek_origin origin) +{ + return fseek((FILE*)pUserData, offset, (origin == drmp3_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; +} + +drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* filePath, const drmp3_config* pConfig) +{ + FILE* pFile; +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (fopen_s(&pFile, filePath, "rb") != 0) { + return DRMP3_FALSE; + } +#else + pFile = fopen(filePath, "rb"); + if (pFile == NULL) { + return DRMP3_FALSE; + } +#endif + + return drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pConfig); +} +#endif + +void drmp3_uninit(drmp3* pMP3) +{ + if (pMP3 == NULL) { + return; + } + +#ifndef DR_MP3_NO_STDIO + if (pMP3->onRead == drmp3__on_read_stdio) { + fclose((FILE*)pMP3->pUserData); + } +#endif + + drmp3_free(pMP3->pData); +} + +drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut) +{ + if (pMP3 == NULL || pMP3->onRead == NULL) { + return 0; + } + + drmp3_uint64 totalFramesRead = 0; + + if (pBufferOut == NULL) { + float temp[4096]; + while (framesToRead > 0) { + drmp3_uint64 framesToReadRightNow = sizeof(temp)/sizeof(temp[0]) / pMP3->channels; + if (framesToReadRightNow > framesToRead) { + framesToReadRightNow = framesToRead; + } + + drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp); + if (framesJustRead == 0) { + break; + } + + framesToRead -= framesJustRead; + totalFramesRead += framesJustRead; + } + } else { + totalFramesRead = drmp3_src_read_frames_ex(&pMP3->src, framesToRead, pBufferOut, DRMP3_TRUE); + pMP3->currentPCMFrame += totalFramesRead; + } + + return totalFramesRead; +} + +void drmp3_reset(drmp3* pMP3) +{ + drmp3_assert(pMP3 != NULL); + + pMP3->pcmFramesConsumedInMP3Frame = 0; + pMP3->pcmFramesRemainingInMP3Frame = 0; + pMP3->currentPCMFrame = 0; + pMP3->dataSize = 0; + pMP3->atEnd = DRMP3_FALSE; + pMP3->src.bin[0] = 0; + pMP3->src.bin[1] = 0; + pMP3->src.bin[2] = 0; + pMP3->src.bin[3] = 0; + pMP3->src.cache.cachedFrameCount = 0; + pMP3->src.cache.iNextFrame = 0; + pMP3->src.algo.linear.alpha = 0; + pMP3->src.algo.linear.isNextFramesLoaded = 0; + pMP3->src.algo.linear.isPrevFramesLoaded = 0; + //drmp3_zero_object(&pMP3->decoder); + drmp3dec_init(&pMP3->decoder); +} + +drmp3_bool32 drmp3_seek_to_start_of_stream(drmp3* pMP3) +{ + drmp3_assert(pMP3 != NULL); + drmp3_assert(pMP3->onSeek != NULL); + + // Seek to the start of the stream to begin with. + if (!drmp3__on_seek(pMP3, 0, drmp3_seek_origin_start)) { + return DRMP3_FALSE; + } + + // Clear any cached data. + drmp3_reset(pMP3); + return DRMP3_TRUE; +} + +float drmp3_get_cached_pcm_frame_count_from_src(drmp3* pMP3) +{ + return (pMP3->src.cache.cachedFrameCount - pMP3->src.cache.iNextFrame) + (float)pMP3->src.algo.linear.alpha; +} + +float drmp3_get_pcm_frames_remaining_in_mp3_frame(drmp3* pMP3) +{ + float factor = (float)pMP3->src.config.sampleRateOut / (float)pMP3->src.config.sampleRateIn; + float frameCountPreSRC = drmp3_get_cached_pcm_frame_count_from_src(pMP3) + pMP3->pcmFramesRemainingInMP3Frame; + return frameCountPreSRC * factor; +} + +// NOTE ON SEEKING +// =============== +// The seeking code below is a complete mess and is broken for cases when the sample rate changes. The problem +// is with the resampling and the crappy resampler used by dr_mp3. What needs to happen is the following: +// +// 1) The resampler needs to be replaced. +// 2) The resampler has state which needs to be updated whenever an MP3 frame is decoded outside of +// drmp3_read_pcm_frames_f32(). The resampler needs an API to "flush" some imaginary input so that it's +// state is updated accordingly. + +drmp3_bool32 drmp3_seek_forward_by_pcm_frames__brute_force(drmp3* pMP3, drmp3_uint64 frameOffset) +{ +#if 0 + // MP3 is a bit annoying when it comes to seeking because of the bit reservoir. It basically means that an MP3 frame can possibly + // depend on some of the data of prior frames. This means it's not as simple as seeking to the first byte of the MP3 frame that + // contains the sample because that MP3 frame will need the data from the previous MP3 frame (which we just seeked past!). To + // resolve this we seek past a number of MP3 frames up to a point, and then read-and-discard the remainder. + drmp3_uint64 maxFramesToReadAndDiscard = (drmp3_uint64)(DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME * 3 * ((float)pMP3->src.config.sampleRateOut / (float)pMP3->src.config.sampleRateIn)); + + // Now get rid of leading whole frames. + while (frameOffset > maxFramesToReadAndDiscard) { + float pcmFramesRemainingInCurrentMP3FrameF = drmp3_get_pcm_frames_remaining_in_mp3_frame(pMP3); + drmp3_uint32 pcmFramesRemainingInCurrentMP3Frame = (drmp3_uint32)pcmFramesRemainingInCurrentMP3FrameF; + if (frameOffset > pcmFramesRemainingInCurrentMP3Frame) { + frameOffset -= pcmFramesRemainingInCurrentMP3Frame; + pMP3->currentPCMFrame += pcmFramesRemainingInCurrentMP3Frame; + pMP3->pcmFramesConsumedInMP3Frame += pMP3->pcmFramesRemainingInMP3Frame; + pMP3->pcmFramesRemainingInMP3Frame = 0; + } else { + break; + } + + drmp3_uint32 pcmFrameCount = drmp3_decode_next_frame_ex(pMP3, pMP3->pcmFrames, DRMP3_FALSE); + if (pcmFrameCount == 0) { + break; + } + } + + // The last step is to read-and-discard any remaining PCM frames to make it sample-exact. + drmp3_uint64 framesRead = drmp3_read_pcm_frames_f32(pMP3, frameOffset, NULL); + if (framesRead != frameOffset) { + return DRMP3_FALSE; + } +#else + // Just using a dumb read-and-discard for now pending updates to the resampler. + drmp3_uint64 framesRead = drmp3_read_pcm_frames_f32(pMP3, frameOffset, NULL); + if (framesRead != frameOffset) { + return DRMP3_FALSE; + } +#endif + + return DRMP3_TRUE; +} + +drmp3_bool32 drmp3_seek_to_pcm_frame__brute_force(drmp3* pMP3, drmp3_uint64 frameIndex) +{ + drmp3_assert(pMP3 != NULL); + + if (frameIndex == pMP3->currentPCMFrame) { + return DRMP3_TRUE; + } + + // If we're moving foward we just read from where we're at. Otherwise we need to move back to the start of + // the stream and read from the beginning. + //drmp3_uint64 framesToReadAndDiscard; + if (frameIndex < pMP3->currentPCMFrame) { + // Moving backward. Move to the start of the stream and then move forward. + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return DRMP3_FALSE; + } + } + + drmp3_assert(frameIndex >= pMP3->currentPCMFrame); + return drmp3_seek_forward_by_pcm_frames__brute_force(pMP3, (frameIndex - pMP3->currentPCMFrame)); +} + +drmp3_bool32 drmp3_find_closest_seek_point(drmp3* pMP3, drmp3_uint64 frameIndex, drmp3_uint32* pSeekPointIndex) +{ + drmp3_assert(pSeekPointIndex != NULL); + + if (frameIndex < pMP3->pSeekPoints[0].pcmFrameIndex) { + return DRMP3_FALSE; + } + + // Linear search for simplicity to begin with while I'm getting this thing working. Once it's all working change this to a binary search. + for (drmp3_uint32 iSeekPoint = 0; iSeekPoint < pMP3->seekPointCount; ++iSeekPoint) { + if (pMP3->pSeekPoints[iSeekPoint].pcmFrameIndex > frameIndex) { + break; // Found it. + } + + *pSeekPointIndex = iSeekPoint; + } + + return DRMP3_TRUE; +} + +drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint64 frameIndex) +{ + drmp3_assert(pMP3 != NULL); + drmp3_assert(pMP3->pSeekPoints != NULL); + drmp3_assert(pMP3->seekPointCount > 0); + + drmp3_seek_point seekPoint; + + // If there is no prior seekpoint it means the target PCM frame comes before the first seek point. Just assume a seekpoint at the start of the file in this case. + drmp3_uint32 priorSeekPointIndex; + if (drmp3_find_closest_seek_point(pMP3, frameIndex, &priorSeekPointIndex)) { + seekPoint = pMP3->pSeekPoints[priorSeekPointIndex]; + } else { + seekPoint.seekPosInBytes = 0; + seekPoint.pcmFrameIndex = 0; + seekPoint.mp3FramesToDiscard = 0; + seekPoint.pcmFramesToDiscard = 0; + } + + // First thing to do is seek to the first byte of the relevant MP3 frame. + if (!drmp3__on_seek_64(pMP3, seekPoint.seekPosInBytes, drmp3_seek_origin_start)) { + return DRMP3_FALSE; // Failed to seek. + } + + // Clear any cached data. + drmp3_reset(pMP3); + + // Whole MP3 frames need to be discarded first. + for (drmp3_uint16 iMP3Frame = 0; iMP3Frame < seekPoint.mp3FramesToDiscard; ++iMP3Frame) { + // Pass in non-null for the last frame because we want to ensure the sample rate converter is preloaded correctly. + drmp3d_sample_t* pPCMFrames = NULL; + if (iMP3Frame == seekPoint.mp3FramesToDiscard-1) { + pPCMFrames = (drmp3d_sample_t*)pMP3->pcmFrames; + } + + // We first need to decode the next frame, and then we need to flush the resampler. + drmp3_uint32 pcmFramesReadPreSRC = drmp3_decode_next_frame_ex(pMP3, pPCMFrames, DRMP3_TRUE); + if (pcmFramesReadPreSRC == 0) { + return DRMP3_FALSE; + } + } + + // We seeked to an MP3 frame in the raw stream so we need to make sure the current PCM frame is set correctly. + pMP3->currentPCMFrame = seekPoint.pcmFrameIndex - seekPoint.pcmFramesToDiscard; + + // Update resampler. This is wrong. Need to instead update it on a per MP3 frame basis. Also broken for cases when + // the sample rate is being reduced in my testing. Should work fine when the input and output sample rate is the same + // or a clean multiple. + pMP3->src.algo.linear.alpha = pMP3->currentPCMFrame * ((double)pMP3->src.config.sampleRateIn / pMP3->src.config.sampleRateOut); + pMP3->src.algo.linear.alpha = pMP3->src.algo.linear.alpha - (drmp3_uint32)(pMP3->src.algo.linear.alpha); + if (pMP3->src.algo.linear.alpha > 0) { + pMP3->src.algo.linear.isPrevFramesLoaded = 1; + } + + // Now at this point we can follow the same process as the brute force technique where we just skip over unnecessary MP3 frames and then + // read-and-discard at least 2 whole MP3 frames. + drmp3_uint64 leftoverFrames = frameIndex - pMP3->currentPCMFrame; + return drmp3_seek_forward_by_pcm_frames__brute_force(pMP3, leftoverFrames); +} + +drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex) +{ + if (pMP3 == NULL || pMP3->onSeek == NULL) { + return DRMP3_FALSE; + } + + if (frameIndex == 0) { + return drmp3_seek_to_start_of_stream(pMP3); + } + + // Use the seek table if we have one. + if (pMP3->pSeekPoints != NULL && pMP3->seekPointCount > 0) { + return drmp3_seek_to_pcm_frame__seek_table(pMP3, frameIndex); + } else { + return drmp3_seek_to_pcm_frame__brute_force(pMP3, frameIndex); + } +} + +drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint64* pMP3FrameCount, drmp3_uint64* pPCMFrameCount) +{ + if (pMP3 == NULL) { + return DRMP3_FALSE; + } + + // The way this works is we move back to the start of the stream, iterate over each MP3 frame and calculate the frame count based + // on our output sample rate, the seek back to the PCM frame we were sitting on before calling this function. + + // The stream must support seeking for this to work. + if (pMP3->onSeek == NULL) { + return DRMP3_FALSE; + } + + // We'll need to seek back to where we were, so grab the PCM frame we're currently sitting on so we can restore later. + drmp3_uint64 currentPCMFrame = pMP3->currentPCMFrame; + + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return DRMP3_FALSE; + } + + drmp3_uint64 totalPCMFrameCount = 0; + drmp3_uint64 totalMP3FrameCount = 0; + + float totalPCMFrameCountFractionalPart = 0; // <-- With resampling there will be a fractional part to each MP3 frame that we need to accumulate. + for (;;) { + drmp3_uint32 pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, DRMP3_FALSE); + if (pcmFramesInCurrentMP3FrameIn == 0) { + break; + } + + float srcRatio = (float)pMP3->mp3FrameSampleRate / (float)pMP3->sampleRate; + drmp3_assert(srcRatio > 0); + + float pcmFramesInCurrentMP3FrameOutF = totalPCMFrameCountFractionalPart + (pcmFramesInCurrentMP3FrameIn / srcRatio); + drmp3_uint32 pcmFramesInCurrentMP3FrameOut = (drmp3_uint32)pcmFramesInCurrentMP3FrameOutF; + totalPCMFrameCountFractionalPart = pcmFramesInCurrentMP3FrameOutF - pcmFramesInCurrentMP3FrameOut; + totalPCMFrameCount += pcmFramesInCurrentMP3FrameOut; + totalMP3FrameCount += 1; + } + + // Finally, we need to seek back to where we were. + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return DRMP3_FALSE; + } + + if (!drmp3_seek_to_pcm_frame(pMP3, currentPCMFrame)) { + return DRMP3_FALSE; + } + + if (pMP3FrameCount != NULL) { + *pMP3FrameCount = totalMP3FrameCount; + } + if (pPCMFrameCount != NULL) { + *pPCMFrameCount = totalPCMFrameCount; + } + + return DRMP3_TRUE; +} + +drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3) +{ + drmp3_uint64 totalPCMFrameCount; + if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, NULL, &totalPCMFrameCount)) { + return 0; + } + + return totalPCMFrameCount; +} + +drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3) +{ + drmp3_uint64 totalMP3FrameCount; + if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, &totalMP3FrameCount, NULL)) { + return 0; + } + + return totalMP3FrameCount; +} + +void drmp3__accumulate_running_pcm_frame_count(drmp3* pMP3, drmp3_uint32 pcmFrameCountIn, drmp3_uint64* pRunningPCMFrameCount, float* pRunningPCMFrameCountFractionalPart) +{ + float srcRatio = (float)pMP3->mp3FrameSampleRate / (float)pMP3->sampleRate; + drmp3_assert(srcRatio > 0); + + float pcmFrameCountOutF = *pRunningPCMFrameCountFractionalPart + (pcmFrameCountIn / srcRatio); + drmp3_uint32 pcmFrameCountOut = (drmp3_uint32)pcmFrameCountOutF; + *pRunningPCMFrameCountFractionalPart = pcmFrameCountOutF - pcmFrameCountOut; + *pRunningPCMFrameCount += pcmFrameCountOut; +} + +typedef struct +{ + drmp3_uint64 bytePos; + drmp3_uint64 pcmFrameIndex; // <-- After sample rate conversion. +} drmp3__seeking_mp3_frame_info; + +drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCount, drmp3_seek_point* pSeekPoints) +{ + if (pMP3 == NULL || pSeekPointCount == NULL || pSeekPoints == NULL) { + return DRMP3_FALSE; // Invalid args. + } + + drmp3_uint32 seekPointCount = *pSeekPointCount; + if (seekPointCount == 0) { + return DRMP3_FALSE; // The client has requested no seek points. Consider this to be invalid arguments since the client has probably not intended this. + } + + // We'll need to seek back to the current sample after calculating the seekpoints so we need to go ahead and grab the current location at the top. + drmp3_uint64 currentPCMFrame = pMP3->currentPCMFrame; + + // We never do more than the total number of MP3 frames and we limit it to 32-bits. + drmp3_uint64 totalMP3FrameCount; + drmp3_uint64 totalPCMFrameCount; + if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, &totalMP3FrameCount, &totalPCMFrameCount)) { + return DRMP3_FALSE; + } + + // If there's less than DRMP3_SEEK_LEADING_MP3_FRAMES+1 frames we just report 1 seek point which will be the very start of the stream. + if (totalMP3FrameCount < DRMP3_SEEK_LEADING_MP3_FRAMES+1) { + seekPointCount = 1; + pSeekPoints[0].seekPosInBytes = 0; + pSeekPoints[0].pcmFrameIndex = 0; + pSeekPoints[0].mp3FramesToDiscard = 0; + pSeekPoints[0].pcmFramesToDiscard = 0; + } else { + if (seekPointCount > totalMP3FrameCount-1) { + seekPointCount = (drmp3_uint32)totalMP3FrameCount-1; + } + + drmp3_uint64 pcmFramesBetweenSeekPoints = totalPCMFrameCount / (seekPointCount+1); + + // Here is where we actually calculate the seek points. We need to start by moving the start of the stream. We then enumerate over each + // MP3 frame. + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return DRMP3_FALSE; + } + + // We need to cache the byte positions of the previous MP3 frames. As a new MP3 frame is iterated, we cycle the byte positions in this + // array. The value in the first item in this array is the byte position that will be reported in the next seek point. + drmp3__seeking_mp3_frame_info mp3FrameInfo[DRMP3_SEEK_LEADING_MP3_FRAMES+1]; + + drmp3_uint64 runningPCMFrameCount = 0; + float runningPCMFrameCountFractionalPart = 0; + + // We need to initialize the array of MP3 byte positions for the leading MP3 frames. + for (int iMP3Frame = 0; iMP3Frame < DRMP3_SEEK_LEADING_MP3_FRAMES+1; ++iMP3Frame) { + // The byte position of the next frame will be the stream's cursor position, minus whatever is sitting in the buffer. + drmp3_assert(pMP3->streamCursor >= pMP3->dataSize); + mp3FrameInfo[iMP3Frame].bytePos = pMP3->streamCursor - pMP3->dataSize; + mp3FrameInfo[iMP3Frame].pcmFrameIndex = runningPCMFrameCount; + + // We need to get information about this frame so we can know how many samples it contained. + drmp3_uint32 pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, DRMP3_FALSE); + if (pcmFramesInCurrentMP3FrameIn == 0) { + return DRMP3_FALSE; // This should never happen. + } + + drmp3__accumulate_running_pcm_frame_count(pMP3, pcmFramesInCurrentMP3FrameIn, &runningPCMFrameCount, &runningPCMFrameCountFractionalPart); + } + + // At this point we will have extracted the byte positions of the leading MP3 frames. We can now start iterating over each seek point and + // calculate them. + drmp3_uint64 nextTargetPCMFrame = 0; + for (drmp3_uint32 iSeekPoint = 0; iSeekPoint < seekPointCount; ++iSeekPoint) { + nextTargetPCMFrame += pcmFramesBetweenSeekPoints; + + for (;;) { + if (nextTargetPCMFrame < runningPCMFrameCount) { + // The next seek point is in the current MP3 frame. + pSeekPoints[iSeekPoint].seekPosInBytes = mp3FrameInfo[0].bytePos; + pSeekPoints[iSeekPoint].pcmFrameIndex = nextTargetPCMFrame; + pSeekPoints[iSeekPoint].mp3FramesToDiscard = DRMP3_SEEK_LEADING_MP3_FRAMES; + pSeekPoints[iSeekPoint].pcmFramesToDiscard = (drmp3_uint16)(nextTargetPCMFrame - mp3FrameInfo[DRMP3_SEEK_LEADING_MP3_FRAMES-1].pcmFrameIndex); + break; + } else { + // The next seek point is not in the current MP3 frame, so continue on to the next one. The first thing to do is cycle the cached + // MP3 frame info. + for (size_t i = 0; i < drmp3_countof(mp3FrameInfo)-1; ++i) { + mp3FrameInfo[i] = mp3FrameInfo[i+1]; + } + + // Cache previous MP3 frame info. + mp3FrameInfo[drmp3_countof(mp3FrameInfo)-1].bytePos = pMP3->streamCursor - pMP3->dataSize; + mp3FrameInfo[drmp3_countof(mp3FrameInfo)-1].pcmFrameIndex = runningPCMFrameCount; + + // Go to the next MP3 frame. This shouldn't ever fail, but just in case it does we just set the seek point and break. If it happens, it + // should only ever do it for the last seek point. + drmp3_uint32 pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, DRMP3_TRUE); + if (pcmFramesInCurrentMP3FrameIn == 0) { + pSeekPoints[iSeekPoint].seekPosInBytes = mp3FrameInfo[0].bytePos; + pSeekPoints[iSeekPoint].pcmFrameIndex = nextTargetPCMFrame; + pSeekPoints[iSeekPoint].mp3FramesToDiscard = DRMP3_SEEK_LEADING_MP3_FRAMES; + pSeekPoints[iSeekPoint].pcmFramesToDiscard = (drmp3_uint16)(nextTargetPCMFrame - mp3FrameInfo[DRMP3_SEEK_LEADING_MP3_FRAMES-1].pcmFrameIndex); + break; + } + + drmp3__accumulate_running_pcm_frame_count(pMP3, pcmFramesInCurrentMP3FrameIn, &runningPCMFrameCount, &runningPCMFrameCountFractionalPart); + } + } + } + + // Finally, we need to seek back to where we were. + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return DRMP3_FALSE; + } + if (!drmp3_seek_to_pcm_frame(pMP3, currentPCMFrame)) { + return DRMP3_FALSE; + } + } + + *pSeekPointCount = seekPointCount; + return DRMP3_TRUE; +} + +drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPointCount, drmp3_seek_point* pSeekPoints) +{ + if (pMP3 == NULL) { + return DRMP3_FALSE; + } + + if (seekPointCount == 0 || pSeekPoints == NULL) { + // Unbinding. + pMP3->seekPointCount = 0; + pMP3->pSeekPoints = NULL; + } else { + // Binding. + pMP3->seekPointCount = seekPointCount; + pMP3->pSeekPoints = pSeekPoints; + } + + return DRMP3_TRUE; +} + + +float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +{ + drmp3_assert(pMP3 != NULL); + + drmp3_uint64 totalFramesRead = 0; + drmp3_uint64 framesCapacity = 0; + float* pFrames = NULL; + + float temp[4096]; + for (;;) { + drmp3_uint64 framesToReadRightNow = drmp3_countof(temp) / pMP3->channels; + drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp); + if (framesJustRead == 0) { + break; + } + + // Reallocate the output buffer if there's not enough room. + if (framesCapacity < totalFramesRead + framesJustRead) { + framesCapacity *= 2; + if (framesCapacity < totalFramesRead + framesJustRead) { + framesCapacity = totalFramesRead + framesJustRead; + } + + drmp3_uint64 newFramesBufferSize = framesCapacity*pMP3->channels*sizeof(float); + if (newFramesBufferSize > DRMP3_SIZE_MAX) { + break; + } + + float* pNewFrames = (float*)drmp3_realloc(pFrames, (size_t)newFramesBufferSize); + if (pNewFrames == NULL) { + drmp3_free(pFrames); + break; + } + + pFrames = pNewFrames; + } + + drmp3_copy_memory(pFrames + totalFramesRead*pMP3->channels, temp, (size_t)(framesJustRead*pMP3->channels*sizeof(float))); + totalFramesRead += framesJustRead; + + // If the number of frames we asked for is less that what we actually read it means we've reached the end. + if (framesJustRead != framesToReadRightNow) { + break; + } + } + + if (pConfig != NULL) { + pConfig->outputChannels = pMP3->channels; + pConfig->outputSampleRate = pMP3->sampleRate; + } + + drmp3_uninit(pMP3); + + if (pTotalFrameCount) *pTotalFrameCount = totalFramesRead; + return pFrames; +} + +float* drmp3_open_and_read_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +{ + drmp3 mp3; + if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pConfig)) { + return NULL; + } + + return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); +} + +float* drmp3_open_memory_and_read_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +{ + drmp3 mp3; + if (!drmp3_init_memory(&mp3, pData, dataSize, pConfig)) { + return NULL; + } + + return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); +} + +#ifndef DR_MP3_NO_STDIO +float* drmp3_open_file_and_read_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +{ + drmp3 mp3; + if (!drmp3_init_file(&mp3, filePath, pConfig)) { + return NULL; + } + + return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); +} +#endif + +void drmp3_free(void* p) +{ + DRMP3_FREE(p); +} + +#endif /*DR_MP3_IMPLEMENTATION*/ + + +// DIFFERENCES BETWEEN minimp3 AND dr_mp3 +// ====================================== +// - First, keep in mind that minimp3 (https://github.com/lieff/minimp3) is where all the real work was done. All of the +// code relating to the actual decoding remains mostly unmodified, apart from some namespacing changes. +// - dr_mp3 adds a pulling style API which allows you to deliver raw data via callbacks. So, rather than pushing data +// to the decoder, the decoder _pulls_ data from your callbacks. +// - In addition to callbacks, a decoder can be initialized from a block of memory and a file. +// - The dr_mp3 pull API reads PCM frames rather than whole MP3 frames. +// - dr_mp3 adds convenience APIs for opening and decoding entire files in one go. +// - dr_mp3 is fully namespaced, including the implementation section, which is more suitable when compiling projects +// as a single translation unit (aka unity builds). At the time of writing this, a unity build is not possible when +// using minimp3 in conjunction with stb_vorbis. dr_mp3 addresses this. + + +// REVISION HISTORY +// ================ +// +// v0.4.1 - 2018-12-30 +// - Fix a warning. +// +// v0.4.0 - 2018-12-16 +// - API CHANGE: Rename some APIs: +// - drmp3_read_f32 -> to drmp3_read_pcm_frames_f32 +// - drmp3_seek_to_frame -> drmp3_seek_to_pcm_frame +// - drmp3_open_and_decode_f32 -> drmp3_open_and_read_f32 +// - drmp3_open_and_decode_memory_f32 -> drmp3_open_memory_and_read_f32 +// - drmp3_open_and_decode_file_f32 -> drmp3_open_file_and_read_f32 +// - Add drmp3_get_pcm_frame_count(). +// - Add drmp3_get_mp3_frame_count(). +// - Improve seeking performance. +// +// v0.3.2 - 2018-09-11 +// - Fix a couple of memory leaks. +// - Bring up to date with minimp3. +// +// v0.3.1 - 2018-08-25 +// - Fix C++ build. +// +// v0.3.0 - 2018-08-25 +// - Bring up to date with minimp3. This has a minor API change: the "pcm" parameter of drmp3dec_decode_frame() has +// been changed from short* to void* because it can now output both s16 and f32 samples, depending on whether or +// not the DR_MP3_FLOAT_OUTPUT option is set. +// +// v0.2.11 - 2018-08-08 +// - Fix a bug where the last part of a file is not read. +// +// v0.2.10 - 2018-08-07 +// - Improve 64-bit detection. +// +// v0.2.9 - 2018-08-05 +// - Fix C++ build on older versions of GCC. +// - Bring up to date with minimp3. +// +// v0.2.8 - 2018-08-02 +// - Fix compilation errors with older versions of GCC. +// +// v0.2.7 - 2018-07-13 +// - Bring up to date with minimp3. +// +// v0.2.6 - 2018-07-12 +// - Bring up to date with minimp3. +// +// v0.2.5 - 2018-06-22 +// - Bring up to date with minimp3. +// +// v0.2.4 - 2018-05-12 +// - Bring up to date with minimp3. +// +// v0.2.3 - 2018-04-29 +// - Fix TCC build. +// +// v0.2.2 - 2018-04-28 +// - Fix bug when opening a decoder from memory. +// +// v0.2.1 - 2018-04-27 +// - Efficiency improvements when the decoder reaches the end of the stream. +// +// v0.2 - 2018-04-21 +// - Bring up to date with minimp3. +// - Start using major.minor.revision versioning. +// +// v0.1d - 2018-03-30 +// - Bring up to date with minimp3. +// +// v0.1c - 2018-03-11 +// - Fix C++ build error. +// +// v0.1b - 2018-03-07 +// - Bring up to date with minimp3. +// +// v0.1a - 2018-02-28 +// - Fix compilation error on GCC/Clang. +// - Fix some warnings. +// +// v0.1 - 2018-02-xx +// - Initial versioned release. + + +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +*/ + +/* + https://github.com/lieff/minimp3 + To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. + This software is distributed without any warranty. + See . +*/ diff --git a/code/nel/src/sound/CMakeLists.txt b/code/nel/src/sound/CMakeLists.txt index 7b9eebc9f..d2c47387e 100644 --- a/code/nel/src/sound/CMakeLists.txt +++ b/code/nel/src/sound/CMakeLists.txt @@ -62,6 +62,7 @@ FILE(GLOB STREAM FILE(GLOB STREAM_FILE audio_decoder.cpp ../../include/nel/sound/audio_decoder.h audio_decoder_vorbis.cpp ../../include/nel/sound/audio_decoder_vorbis.h + audio_decoder_mp3.cpp ../../include/nel/sound/audio_decoder_mp3.h audio_decoder_ffmpeg.cpp ../../include/nel/sound/audio_decoder_ffmpeg.h stream_file_sound.cpp ../../include/nel/sound/stream_file_sound.h stream_file_source.cpp ../../include/nel/sound/stream_file_source.h diff --git a/code/nel/src/sound/audio_decoder.cpp b/code/nel/src/sound/audio_decoder.cpp index f0eb80efd..d849ed770 100644 --- a/code/nel/src/sound/audio_decoder.cpp +++ b/code/nel/src/sound/audio_decoder.cpp @@ -36,6 +36,7 @@ // Project includes #include +#include #ifdef FFMPEG_ENABLED #include @@ -102,6 +103,10 @@ IAudioDecoder *IAudioDecoder::createAudioDecoder(const std::string &type, NLMISC { return new CAudioDecoderVorbis(stream, loop); } + else if (type_lower == "mp3") + { + return new CAudioDecoderMP3(stream, loop); + } else { nlwarning("Music file type unknown: '%s'", type_lower.c_str()); @@ -139,6 +144,16 @@ bool IAudioDecoder::getInfo(const std::string &filepath, std::string &artist, st nlwarning("Unable to open: '%s'", filepath.c_str()); } + else if (type_lower == "mp3") + { + CIFile ifile; + ifile.setCacheFileOnOpen(false); + ifile.allowBNPCacheFileOnOpen(false); + if (ifile.open(lookup)) + return CAudioDecoderMP3::getInfo(&ifile, artist, title, length); + + nlwarning("Unable to open: '%s'", filepath.c_str()); + } else { nlwarning("Music file type unknown: '%s'", type_lower.c_str()); @@ -157,6 +172,10 @@ void IAudioDecoder::getMusicExtensions(std::vector &extensions) { extensions.push_back("ogg"); } + if (std::find(extensions.begin(), extensions.end(), "mp3") == extensions.end()) + { + extensions.push_back("mp3"); + } #ifdef FFMPEG_ENABLED extensions.push_back("mp3"); extensions.push_back("flac"); diff --git a/code/nel/src/sound/audio_decoder_mp3.cpp b/code/nel/src/sound/audio_decoder_mp3.cpp new file mode 100644 index 000000000..bef0aad71 --- /dev/null +++ b/code/nel/src/sound/audio_decoder_mp3.cpp @@ -0,0 +1,221 @@ +// NeL - MMORPG Framework +// Copyright (C) 2018 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + + +#include "stdsound.h" + +#include + +#define DR_MP3_IMPLEMENTATION +#include + +using namespace std; +using namespace NLMISC; +using namespace NLSOUND; + +namespace NLSOUND { + +// callback for drmp3 +static size_t drmp3_read(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + NLSOUND::CAudioDecoderMP3 *decoder = static_cast(pUserData); + NLMISC::IStream *stream = decoder->getStream(); + nlassert(stream->isReading()); + + uint32 available = decoder->getStreamSize() - stream->getPos(); + if (available == 0) + return 0; + + if (bytesToRead > available) + bytesToRead = available; + + stream->serialBuffer((uint8 *)pBufferOut, bytesToRead); + return bytesToRead; +} + +// callback for drmp3 +static drmp3_bool32 drmp3_seek(void* pUserData, int offset, drmp3_seek_origin origin) +{ + NLSOUND::CAudioDecoderMP3 *decoder = static_cast(pUserData); + NLMISC::IStream *stream = decoder->getStream(); + nlassert(stream->isReading()); + + NLMISC::IStream::TSeekOrigin seekOrigin; + if (origin == drmp3_seek_origin_start) + seekOrigin = NLMISC::IStream::begin; + else if (origin == drmp3_seek_origin_current) + seekOrigin = NLMISC::IStream::current; + else + return false; + + stream->seek((sint32) offset, seekOrigin); + return true; +} + +// these should always be 44100Hz/16bit/2ch +#define MP3_SAMPLE_RATE 44100 +#define MP3_BITS_PER_SAMPLE 16 +#define MP3_CHANNELS 2 + +CAudioDecoderMP3::CAudioDecoderMP3(NLMISC::IStream *stream, bool loop) +: IAudioDecoder(), + _Stream(stream), _Loop(loop), _IsMusicEnded(false), _StreamSize(0), _IsSupported(false), _PCMFrameCount(0) +{ + _StreamOffset = stream->getPos(); + stream->seek(0, NLMISC::IStream::end); + _StreamSize = stream->getPos(); + stream->seek(_StreamOffset, NLMISC::IStream::begin); + + drmp3_config config; + config.outputChannels = MP3_CHANNELS; + config.outputSampleRate = MP3_SAMPLE_RATE; + + _IsSupported = drmp3_init(&_Decoder, &drmp3_read, &drmp3_seek, this, &config); + if (!_IsSupported) + { + nlwarning("MP3: Decoder failed to read stream"); + } +} + +CAudioDecoderMP3::~CAudioDecoderMP3() +{ + drmp3_uninit(&_Decoder); +} + +bool CAudioDecoderMP3::isFormatSupported() const +{ + return _IsSupported; +} + +/// Get information on a music file. +bool CAudioDecoderMP3::getInfo(NLMISC::IStream *stream, std::string &artist, std::string &title, float &length) +{ + CAudioDecoderMP3 mp3(stream, false); + if (!mp3.isFormatSupported()) + { + title.clear(); + artist.clear(); + length = 0.f; + + return false; + } + length = mp3.getLength(); + + // ID3v1 + stream->seek(-128, NLMISC::IStream::end); + { + uint8 buf[128]; + stream->serialBuffer(buf, 128); + + if(buf[0] == 'T' && buf[1] == 'A' && buf[2] == 'G') + { + uint i; + for(i = 0; i < 30; ++i) if (buf[3+i] == '\0') break; + artist.assign((char *)&buf[3], i); + + for(i = 0; i < 30; ++i) if (buf[33+i] == '\0') break; + title.assign((char *)&buf[33], i); + } + } + + return true; +} + +uint32 CAudioDecoderMP3::getRequiredBytes() +{ + return 0; // no minimum requirement of bytes to buffer out +} + +uint32 CAudioDecoderMP3::getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum) +{ + if (_IsMusicEnded) return 0; + nlassert(minimum <= maximum); // can't have this.. + + // TODO: CStreamFileSource::play() will stall when there is no frames on warmup + // supported can be set false if there is an issue creating converter + if (!_IsSupported) + { + _IsMusicEnded = true; + return 1; + } + + sint16 *pFrameBufferOut = (sint16 *)buffer; + uint32 bytesPerFrame = MP3_BITS_PER_SAMPLE / 8 * _Decoder.channels; + + uint32 totalFramesRead = 0; + uint32 framesToRead = minimum / bytesPerFrame; + while(framesToRead > 0) + { + float tempBuffer[4096]; + uint64 tempFrames = drmp3_countof(tempBuffer) / _Decoder.channels; + + if (tempFrames > framesToRead) + tempFrames = framesToRead; + + tempFrames = drmp3_read_pcm_frames_f32(&_Decoder, tempFrames, tempBuffer); + if (tempFrames == 0) + break; + + drmp3dec_f32_to_s16(tempBuffer, pFrameBufferOut, tempFrames * _Decoder.channels); + pFrameBufferOut += tempFrames * _Decoder.channels; + + framesToRead -= tempFrames; + totalFramesRead += tempFrames; + } + + _IsMusicEnded = (framesToRead > 0); + return totalFramesRead * bytesPerFrame; +} + +uint8 CAudioDecoderMP3::getChannels() +{ + return _Decoder.channels; +} + +uint CAudioDecoderMP3::getSamplesPerSec() +{ + return _Decoder.sampleRate; +} + +uint8 CAudioDecoderMP3::getBitsPerSample() +{ + return MP3_BITS_PER_SAMPLE; +} + +bool CAudioDecoderMP3::isMusicEnded() +{ + return _IsMusicEnded; +} + +float CAudioDecoderMP3::getLength() +{ + // cached because drmp3_get_pcm_frame_count is reading full file + if (_PCMFrameCount == 0) + { + _PCMFrameCount = drmp3_get_pcm_frame_count(&_Decoder); + } + + return _PCMFrameCount / (float) _Decoder.sampleRate; +} + +void CAudioDecoderMP3::setLooping(bool loop) +{ + _Loop = loop; +} + +} /* namespace NLSOUND */ + +/* end of file */ From 1635330a14f695770ac92aeae7585ed0580608c4 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Mon, 18 Feb 2019 22:22:25 +0200 Subject: [PATCH 085/108] Fixed: Possible crash when queued buffers are already empty --HG-- branch : develop --- code/nel/src/sound/driver/openal/source_al.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/nel/src/sound/driver/openal/source_al.cpp b/code/nel/src/sound/driver/openal/source_al.cpp index 825d9f1e9..b35dbe201 100644 --- a/code/nel/src/sound/driver/openal/source_al.cpp +++ b/code/nel/src/sound/driver/openal/source_al.cpp @@ -194,7 +194,7 @@ uint CSourceAL::countStreamingBuffers() const // a bit ugly here, but makes a much easier/simpler implementation on both drivers ALint buffersProcessed; alGetSourcei(_Source, AL_BUFFERS_PROCESSED, &buffersProcessed); - while (buffersProcessed) + while (buffersProcessed && !_QueuedBuffers.empty()) { ALuint bufferName = _QueuedBuffers.front()->bufferName(); alSourceUnqueueBuffers(_Source, 1, &bufferName); From 70e40339a1c7c211541bf0f39b131b1df3fe5647 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Fri, 22 Feb 2019 18:57:56 +0200 Subject: [PATCH 086/108] Fixed: Possible use after free and null pointer issues --HG-- branch : develop --- code/nel/src/sound/audio_decoder_mp3.cpp | 5 ++++- code/nel/src/sound/complex_source.cpp | 4 +++- code/nel/src/sound/driver/openal/source_al.cpp | 4 ++++ code/nel/src/sound/sample_bank_manager.cpp | 15 ++++++++------- code/nel/src/sound/stream_source.cpp | 2 +- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/code/nel/src/sound/audio_decoder_mp3.cpp b/code/nel/src/sound/audio_decoder_mp3.cpp index bef0aad71..dc740bec0 100644 --- a/code/nel/src/sound/audio_decoder_mp3.cpp +++ b/code/nel/src/sound/audio_decoder_mp3.cpp @@ -92,7 +92,10 @@ CAudioDecoderMP3::CAudioDecoderMP3(NLMISC::IStream *stream, bool loop) CAudioDecoderMP3::~CAudioDecoderMP3() { - drmp3_uninit(&_Decoder); + if (_IsSupported) + { + drmp3_uninit(&_Decoder); + } } bool CAudioDecoderMP3::isFormatSupported() const diff --git a/code/nel/src/sound/complex_source.cpp b/code/nel/src/sound/complex_source.cpp index 103c6cc60..3caee06f6 100644 --- a/code/nel/src/sound/complex_source.cpp +++ b/code/nel/src/sound/complex_source.cpp @@ -704,7 +704,9 @@ void CComplexSource::checkup() for (; first != last; ++first) { USource *source = *first; - if (source != NULL && source->getSound()->getLooping() && !source->isPlaying()) + if (source == NULL) + continue; + if (source->getSound()->getLooping() && !source->isPlaying()) source->play(); if (source->getSound()->getSoundType() != CSound::SOUND_SIMPLE) static_cast(source)->checkup(); diff --git a/code/nel/src/sound/driver/openal/source_al.cpp b/code/nel/src/sound/driver/openal/source_al.cpp index b35dbe201..439da980d 100644 --- a/code/nel/src/sound/driver/openal/source_al.cpp +++ b/code/nel/src/sound/driver/openal/source_al.cpp @@ -194,6 +194,10 @@ uint CSourceAL::countStreamingBuffers() const // a bit ugly here, but makes a much easier/simpler implementation on both drivers ALint buffersProcessed; alGetSourcei(_Source, AL_BUFFERS_PROCESSED, &buffersProcessed); + if (buffersProcessed && _QueuedBuffers.empty()) + { + nlwarning("AL: QueuedBuffers is empty, but OpenAL buffers processed > 0"); + } while (buffersProcessed && !_QueuedBuffers.empty()) { ALuint bufferName = _QueuedBuffers.front()->bufferName(); diff --git a/code/nel/src/sound/sample_bank_manager.cpp b/code/nel/src/sound/sample_bank_manager.cpp index 996c59f1e..6f6728bca 100644 --- a/code/nel/src/sound/sample_bank_manager.cpp +++ b/code/nel/src/sound/sample_bank_manager.cpp @@ -82,13 +82,14 @@ void CSampleBankManager::init(NLGEORGES::UFormElm *mixerConfig) TFilteredBank fb; std::string bankName; NLGEORGES::UFormElm *realBank = NULL; - realBank->getArrayNode(&realBank, j); - - realBank->getValueByName(bankName, ".SampleBank"); - fb.BankName = CStringMapper::map(bankName); - realBank->getValueByName(fb.Filter, ".Filter"); - - vfb.push_back(fb); + realBanks->getArrayNode(&realBank, j); + if (realBank != 0) + { + realBank->getValueByName(bankName, ".SampleBank"); + fb.BankName = CStringMapper::map(bankName); + realBank->getValueByName(fb.Filter, ".Filter"); + vfb.push_back(fb); + } } } diff --git a/code/nel/src/sound/stream_source.cpp b/code/nel/src/sound/stream_source.cpp index 9bd48ff25..537a4c24d 100644 --- a/code/nel/src/sound/stream_source.cpp +++ b/code/nel/src/sound/stream_source.cpp @@ -92,7 +92,7 @@ void CStreamSource::releasePhysicalSource() // free the track pSource->stop(); pSource->setStreaming(false); - mixer->freeTrack(m_Track); + if (mixer) mixer->freeTrack(m_Track); m_Track = NULL; } } From 4cbed9f9570f7beb059768e616b773d51789f918 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Fri, 22 Feb 2019 18:57:56 +0200 Subject: [PATCH 087/108] Fixed: Looping sound or crash during teleport with OpenAL driver --HG-- branch : develop --- code/nel/src/sound/driver/openal/source_al.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/code/nel/src/sound/driver/openal/source_al.cpp b/code/nel/src/sound/driver/openal/source_al.cpp index 439da980d..3cc0dd328 100644 --- a/code/nel/src/sound/driver/openal/source_al.cpp +++ b/code/nel/src/sound/driver/openal/source_al.cpp @@ -121,6 +121,11 @@ void CSourceAL::setStreaming(bool streaming) alTestError(); _Buffer = NULL; _IsStreaming = streaming; + if (_IsStreaming) + { + // make sure looping is disabled on OpenAL side + setLooping(false); + } } /* Set the buffer that will be played (no streaming) From ac68a37f1bfa349a63761c423b982882a225cfb8 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 28 Feb 2019 15:01:51 +0200 Subject: [PATCH 088/108] Changed: Move user landmarks from icfg to own xml file --HG-- branch : develop --- code/ryzom/client/src/continent_manager.cpp | 175 ++++++++++++++- code/ryzom/client/src/continent_manager.h | 7 +- code/ryzom/client/src/far_tp.cpp | 1 + .../client/src/interface_v3/group_map.cpp | 2 +- .../src/interface_v3/interface_manager.cpp | 210 ++++++++++++++++-- .../src/interface_v3/interface_manager.h | 13 ++ .../src/interface_v3/parser_modules.cpp | 21 ++ .../client/src/interface_v3/parser_modules.h | 9 + 8 files changed, 411 insertions(+), 27 deletions(-) diff --git a/code/ryzom/client/src/continent_manager.cpp b/code/ryzom/client/src/continent_manager.cpp index fe9067a4f..56a11cf63 100644 --- a/code/ryzom/client/src/continent_manager.cpp +++ b/code/ryzom/client/src/continent_manager.cpp @@ -488,8 +488,177 @@ CContinent *CContinentManager::get(const std::string &contName) return NULL; } -void CContinentManager::serialUserLandMarks(NLMISC::IStream &f) +void CContinentManager::writeTo(xmlNodePtr node) const { + // + // + // + // ... + // + for(TContinents::const_iterator it = _Continents.begin(); it != _Continents.end(); ++it) + { + std::string name = it->first; + xmlNodePtr contNode = xmlNewChild(node, NULL, (const xmlChar*)"landmarks", NULL); + xmlSetProp(contNode, (const xmlChar*)"continent", (const xmlChar*)name.c_str()); + xmlSetProp(contNode, (const xmlChar*)"type", (const xmlChar*)"user"); + + if (it->second && it->second->UserLandMarks.size() > 0) + { + for(uint i = 0; i< it->second->UserLandMarks.size(); ++i) + { + const CUserLandMark& lm = it->second->UserLandMarks[i]; + + xmlNodePtr lmNode = xmlNewChild(contNode, NULL, (const xmlChar*)"landmark", NULL); + xmlSetProp(lmNode, (const xmlChar*)"type", (const xmlChar*)toString("%d", (uint32)lm.Type).c_str()); + xmlSetProp(lmNode, (const xmlChar*)"x", (const xmlChar*)toString("%.2f", lm.Pos.x).c_str()); + xmlSetProp(lmNode, (const xmlChar*)"y", (const xmlChar*)toString("%.2f", lm.Pos.y).c_str()); + + // sanitize ascii control chars + // libxml will encode other special chars itself + std::string title = lm.Title.toUtf8(); + for(uint i = 0; i< title.size(); i++) + { + if (title[i] < ' ' && title[i] != '\n' && title[i] != '\t') + { + title[i] = '?'; + } + } + xmlSetProp(lmNode, (const xmlChar*)"title", (const xmlChar*)title.c_str()); + } + } + } +} + +void CContinentManager::readFrom(xmlNodePtr node) +{ + CXMLAutoPtr prop; + + // + // + // ... + // + std::string continent; + prop = xmlGetProp(node, (xmlChar*)"continent"); + if (!prop) + { + nlwarning("Ignore landmarks group 'continent' attribute."); + return; + } + continent = (const char*)prop; + + TContinents::iterator itContinent = _Continents.find(continent); + if (itContinent == _Continents.end() || !itContinent->second) + { + nlwarning("Ignore landmarks group with unknown 'continent' '%s'", continent.c_str()); + return; + } + + std::string lmtype; + prop = xmlGetProp(node, (xmlChar*)"type"); + if (!prop) + { + nlwarning("Ignore landmarks group without 'type' attribute."); + return; + } + lmtype = toLower((const char*)prop); + if (lmtype != "user") + { + nlwarning("Ignore landmarks group with type '%s', expected 'user'.", lmtype.c_str()); + return; + } + + node = node->children; + while(node) + { + if (stricmp((char*)node->name, "landmark") != 0) + { + nlwarning("Ignore invalid node '%s' under landmarks group", (const char*)node->name); + + node = node->next; + continue; + } + + bool add = true; + CUserLandMark lm; + + prop = xmlGetProp(node, (xmlChar*)"type"); + if (prop) + fromString((const char*)prop, lm.Type); + else + nlwarning("Using default value for landmark type"); + + prop = xmlGetProp(node, (xmlChar*)"x"); + if (prop) + { + fromString((const char*)prop, lm.Pos.x); + } + else + { + nlwarning("Landmark missing 'x' attribute"); + add = false; + } + + prop = xmlGetProp(node, (xmlChar*)"y"); + if (prop) + { + fromString((const char*)prop, lm.Pos.y); + } + else + { + nlwarning("Landmark missing 'y' attribute"); + add = false; + } + + prop = xmlGetProp(node, (xmlChar*)"title"); + if (prop) + { + lm.Title.fromUtf8((const char*)prop); + } + else + { + nlwarning("Landmark missing 'title' attribute"); + add = false; + } + + if (add) + { + // before adding, check for duplicate + // duplicates might be read from .icfg before .xml is read + add = true; + for(uint i = 0; i< itContinent->second->UserLandMarks.size(); ++i) + { + const CUserLandMark& test = itContinent->second->UserLandMarks[i]; + uint xdiff = abs(test.Pos.x - lm.Pos.x) * 100; + uint ydiff = abs(test.Pos.y - lm.Pos.y) * 100; + if (xdiff == 0 && ydiff == 0) + { + add = false; + break; + } + } + + if (add) + { + itContinent->second->UserLandMarks.push_back(lm); + } + else + { + nlwarning("Ignore landmark with duplicate pos (continent:'%s', x:%.2f, y:%.2f, type:%d, title:'%s')", continent.c_str(), lm.Pos.x, lm.Pos.y, (uint8)lm.Type, lm.Title.toUtf8().c_str()); + } + } + else + { + nlwarning("Landmark not added"); + } + + node = node->next; + } +} + +uint32 CContinentManager::serialUserLandMarks(NLMISC::IStream &f) +{ + uint32 totalLandmarks = 0; + f.serialVersion(1); if (!f.isReading()) { @@ -502,6 +671,7 @@ void CContinentManager::serialUserLandMarks(NLMISC::IStream &f) if (it->second) { f.serialCont(it->second->UserLandMarks); + totalLandmarks += it->second->UserLandMarks.size(); } else { @@ -522,6 +692,7 @@ void CContinentManager::serialUserLandMarks(NLMISC::IStream &f) if (it != _Continents.end() && it->second) { f.serialCont(it->second->UserLandMarks); + totalLandmarks += it->second->UserLandMarks.size(); } else { @@ -530,6 +701,8 @@ void CContinentManager::serialUserLandMarks(NLMISC::IStream &f) } } } + + return totalLandmarks; } diff --git a/code/ryzom/client/src/continent_manager.h b/code/ryzom/client/src/continent_manager.h index c51498967..1a0641627 100644 --- a/code/ryzom/client/src/continent_manager.h +++ b/code/ryzom/client/src/continent_manager.h @@ -109,8 +109,13 @@ public: const std::string &getCurrentContinentSelectName(); + // load / save all user landmarks in xml format + void writeTo(xmlNodePtr node) const; + void readFrom(xmlNodePtr node); + // load / saves all user landMarks - void serialUserLandMarks(NLMISC::IStream &f); + // \return number of landmarks loaded or saved + uint32 serialUserLandMarks(NLMISC::IStream &f); // rebuild visible landmarks on current map void updateUserLandMarks(); diff --git a/code/ryzom/client/src/far_tp.cpp b/code/ryzom/client/src/far_tp.cpp index 577477ea2..92a8bbe3a 100644 --- a/code/ryzom/client/src/far_tp.cpp +++ b/code/ryzom/client/src/far_tp.cpp @@ -1298,6 +1298,7 @@ void CFarTP::sendReady() pIM->loadKeys(); CWidgetManager::getInstance()->hideAllWindows(); pIM->loadInterfaceConfig(); + pIM->loadLandmarks(); } else { diff --git a/code/ryzom/client/src/interface_v3/group_map.cpp b/code/ryzom/client/src/interface_v3/group_map.cpp index 9de74bcc7..28eb5e1e3 100644 --- a/code/ryzom/client/src/interface_v3/group_map.cpp +++ b/code/ryzom/client/src/interface_v3/group_map.cpp @@ -2715,7 +2715,7 @@ CCtrlButton *CGroupMap::addUserLandMark(const NLMISC::CVector2f &pos, const ucst addLandMark(_UserLM, pos, title, getUserLandMarkOptions((uint32)_CurContinent->UserLandMarks.size() - 1)); // Save the config file each time a user landmark is created - CInterfaceManager::getInstance()->saveConfig(); + CInterfaceManager::getInstance()->saveLandmarks(); return _UserLM.back(); } diff --git a/code/ryzom/client/src/interface_v3/interface_manager.cpp b/code/ryzom/client/src/interface_v3/interface_manager.cpp index 7684accd8..bc606f939 100644 --- a/code/ryzom/client/src/interface_v3/interface_manager.cpp +++ b/code/ryzom/client/src/interface_v3/interface_manager.cpp @@ -458,6 +458,7 @@ CInterfaceManager::CInterfaceManager() parser->addModule( "command", new CCommandParser() ); parser->addModule( "key", new CKeyParser() ); parser->addModule( "macro", new CMacroParser() ); + parser->addModule( "landmarks", new CLandmarkParser() ); parser->setCacheUIParsing( ClientCfg.CacheUIParsing ); CViewRenderer::setDriver( Driver ); @@ -982,6 +983,9 @@ void CInterfaceManager::initInGame() // Interface config loadInterfaceConfig(); + //Load user landmarks + loadLandmarks(); + // Must do extra init for people interaction after load PeopleInterraction.initAfterLoad(); @@ -1217,6 +1221,22 @@ void CInterfaceManager::configureQuitDialogBox() } } +// ------------------------------------------------------------------------------------------------ +// +std::string CInterfaceManager::getSaveFileName(const std::string &module, const std::string &ext, bool useShared) const +{ + string filename = "save/" + module + "_" + PlayerSelectedFileName + "." + ext; + if (useShared && !CFile::fileExists(filename)) + { + string sharedFile = "save/shared_" + module + "." + ext; + if (CFile::fileExists(sharedFile)) + { + return sharedFile; + } + } + return filename; +} + // ------------------------------------------------------------------------------------------------ void CInterfaceManager::loadKeys() { @@ -1228,26 +1248,19 @@ void CInterfaceManager::loadKeys() vector xmlFilesToParse; // Does the keys file exist ? - string userKeyFileName = "save/keys_"+PlayerSelectedFileName+".xml"; + string userKeyFileName = getSaveFileName("keys", "xml"); if (CFile::fileExists(userKeyFileName) && CFile::getFileSize(userKeyFileName) > 0) { // Load the user key file xmlFilesToParse.push_back (userKeyFileName); } - else - { - string filename = "save/shared_keys.xml"; - if(CFile::fileExists(filename) && CFile::getFileSize(filename) > 0) - { - xmlFilesToParse.push_back(filename); - } - } + // Load the default key (but don't replace existings bounds, see keys.xml "key_def_no_replace") xmlFilesToParse.push_back ("keys.xml"); if (!parseInterface (xmlFilesToParse, true)) { - badXMLParseMessageBox(); + createFileBackup("Error loading keys", userKeyFileName); } _KeysLoaded = true; @@ -1260,10 +1273,7 @@ void CInterfaceManager::loadInterfaceConfig() if (ClientCfg.R2EDEnabled) // in R2ED mode the CEditor class deals with it return; - string filename = "save/interface_" + PlayerSelectedFileName + ".icfg"; - if (!CFile::fileExists(filename)) - filename = "save/shared_interface.icfg"; - + string filename = getSaveFileName("interface", "icfg"); loadConfig(filename); // Invalidate coords of changed groups _ConfigLoaded = true; @@ -1680,6 +1690,7 @@ bool CInterfaceManager::loadConfig (const string &filename) uint32 nNbMode; CInterfaceConfig ic; bool lastInGameScreenResLoaded= false; + uint32 nbLandmarks = 0; try { sint ver = f.serialVersion(ICFG_STREAM_VERSION); @@ -1775,7 +1786,7 @@ bool CInterfaceManager::loadConfig (const string &filename) } // Load user landmarks - ContinentMngr.serialUserLandMarks(f); + nbLandmarks = ContinentMngr.serialUserLandMarks(f); CCDBNodeLeaf *pNL = NLGUI::CDBManager::getInstance()->getDbProp( "SERVER:INTERFACES:NB_BONUS_LANDMARKS" ); if ( pNL ) @@ -1809,10 +1820,9 @@ bool CInterfaceManager::loadConfig (const string &filename) catch(const NLMISC::EStream &) { f.close(); - string sFileNameBackup = sFileName+"backup"; - if (CFile::fileExists(sFileNameBackup)) - CFile::deleteFile(sFileNameBackup); - CFile::moveFile(sFileNameBackup, sFileName); + + createFileBackup("Config loading failed", sFileName); + nlwarning("Config loading failed : restore default"); vector v; if (!ClientCfg.R2EDEnabled) @@ -1823,6 +1833,35 @@ bool CInterfaceManager::loadConfig (const string &filename) } f.close(); + if (nbLandmarks > 0) + { + // use copy for backup so on save proper shared/player icfg file is used + createFileBackup("Landmarks will be migrated to xml", sFileName, true); + + // if icfg is interface_player.icfg, then landmarks must also be loaded/saved to player file + if (nlstricmp(sFileName.substr(0, 12), "save/shared_") != 0) + { + string lmfile = getSaveFileName("landmarks", "xml", false); + if (!CFile::fileExists(lmfile)) + { + // create placeholder player landmarks file so saveLandmarks will use it + // even if shared landmarks file exists + COFile f; + if (f.open(lmfile, false, false, true)) + { + std::string xml; + xml = "\n"; + f.serialBuffer((uint8 *)xml.c_str(), xml.size()); + f.close(); + } + } + } + + // merge .icfg landmarks with landmarks.xml + loadLandmarks(); + saveLandmarks(); + } + // *** If saved resolution is different from the current one setuped, must fix positions in _Modes if(lastInGameScreenResLoaded) { @@ -1877,6 +1916,88 @@ public: } }; +bool CInterfaceManager::saveLandmarks(bool verbose) const +{ + bool ret = true; + + if (!ClientCfg.R2EDEnabled) + { + uint8 currMode = getMode(); + + string filename = getSaveFileName("landmarks", "xml"); + if (verbose) CInterfaceManager::getInstance()->displaySystemInfo("Saving " + filename); + ret = saveLandmarks(filename); + } + + return ret; +} + +bool CInterfaceManager::loadLandmarks() +{ + // Does the keys file exist ? + string filename = getSaveFileName("landmarks", "xml"); + + CIFile f; + string sFileName; + sFileName = NLMISC::CPath::lookup (filename, false); + if (sFileName.empty() || !f.open(sFileName)) + return false; + + bool ret = false; + vector xmlFilesToParse; + xmlFilesToParse.push_back (filename); + + //ContinentMngr.serialUserLandMarks(node); + if (!parseInterface (xmlFilesToParse, true)) + { + f.close(); + + createFileBackup("Error while loading landmarks", filename); + + ret = false; + } + + return ret; +} + +bool CInterfaceManager::saveLandmarks(const std::string &filename) const +{ + nlinfo( "Saving landmarks : %s", filename.c_str() ); + + bool ret = false; + try + { + COFile f; + + // using temporary file, so no f.close() unless its a success + if (f.open(filename, false, false, true)) + { + COXml xmlStream; + xmlStream.init (&f); + + xmlDocPtr doc = xmlStream.getDocument (); + xmlNodePtr node = xmlNewDocNode(doc, NULL, (const xmlChar*)"interface_config", NULL); + xmlDocSetRootElement (doc, node); + + ContinentMngr.writeTo(node); + + // Flush the stream + xmlStream.flush(); + + // Close the stream + f.close (); + + ret = true; + } + } + catch (const Exception &e) + { + nlwarning ("Error while writing the file %s : %s.", filename.c_str(), e.what ()); + } + + return ret; +} + // ------------------------------------------------------------------------------------------------ // bool CInterfaceManager::saveConfig (bool verbose) @@ -1887,9 +2008,7 @@ bool CInterfaceManager::saveConfig (bool verbose) { uint8 currMode = getMode(); - string filename = "save/interface_" + PlayerSelectedFileName + ".icfg"; - if (!CFile::fileExists(filename) && CFile::fileExists("save/shared_interface.icfg")) - filename = "save/shared_interface.icfg"; + string filename = getSaveFileName("interface", "icfg"); if (verbose) CInterfaceManager::getInstance()->displaySystemInfo("Saving " + filename); ret = saveConfig(filename); @@ -2004,8 +2123,13 @@ bool CInterfaceManager::saveConfig (const string &filename) CTaskBarManager *pTBM= CTaskBarManager::getInstance(); pTBM->serial(f); - // Save user landmarks - ContinentMngr.serialUserLandMarks(f); + //ContinentMngr.serialUserLandMarks(f); + // empty landmarks block for compatibility + { + f.serialVersion(1); + uint32 numCont = 0; + f.serial(numCont); + } // Info Windows position. CInterfaceHelp::serialInfoWindows(f); @@ -2972,6 +3096,7 @@ class CAHSaveUI : public IActionHandler { CInterfaceManager::getInstance()->saveKeys(true); CInterfaceManager::getInstance()->saveConfig(true); + CInterfaceManager::getInstance()->saveLandmarks(true); } }; REGISTER_ACTION_HANDLER (CAHSaveUI, "save_ui"); @@ -4135,3 +4260,40 @@ bool CInterfaceManager::parseTokens(ucstring& ucstr) ucstr = str; return true;; } + +std::string CInterfaceManager::getNextBackupName(std::string filename) +{ + std::string ts = getTimestampHuman("%Y-%m-%d"); + + if (!ts.empty()) + { + std::string::size_type pos = filename.find_last_of('.'); + if (pos == std::string::npos) + filename = filename + "_" + ts + "_"; + else + filename = filename.substr(0, pos) + "_" + ts + "_" + filename.substr(pos); + } + + // filename_YYYY-MM-DD_000.ext + return CFile::findNewFile(filename); +} + +void CInterfaceManager::createFileBackup(const std::string &message, const std::string &filename, bool useCopy) +{ + std::string backupName = getNextBackupName(filename); + nlwarning("%s: '%s'.", message.c_str(), filename.c_str()); + if (!backupName.empty()) + { + if (useCopy) + { + nlwarning("Backup copy saved as '%s'", backupName.c_str()); + CFile::copyFile(backupName, filename); + } + else + { + nlwarning("File renamed to '%s'", backupName.c_str()); + CFile::moveFile(backupName, filename); + } + } +} + diff --git a/code/ryzom/client/src/interface_v3/interface_manager.h b/code/ryzom/client/src/interface_v3/interface_manager.h index 652d692b3..488dbe0a5 100644 --- a/code/ryzom/client/src/interface_v3/interface_manager.h +++ b/code/ryzom/client/src/interface_v3/interface_manager.h @@ -203,6 +203,19 @@ public: /// Load a set of xml files bool parseInterface (const std::vector &xmlFileNames, bool reload, bool isFilename = true); + /// return new filename that can be used to backup original file + std::string getNextBackupName(std::string filename); + /// copy/rename filename for backup and show error in log + void createFileBackup(const std::string &message, const std::string &filename, bool useCopy = false); + + /// select player/shared file name from 'save' folder' + std::string getSaveFileName(const std::string &module, const std::string &ext, bool useShared = true) const; + + /// Load / save user landmarks in .xml format + bool loadLandmarks (); + bool saveLandmarks (bool verbose = false) const; + bool saveLandmarks (const std::string &filename) const; + // Load/Save position, size, etc.. of windows bool loadConfig (const std::string &filename); // Save config to default location, if verbose is true, display message in game sysinfo diff --git a/code/ryzom/client/src/interface_v3/parser_modules.cpp b/code/ryzom/client/src/interface_v3/parser_modules.cpp index b540900a4..2d2c7cb60 100644 --- a/code/ryzom/client/src/interface_v3/parser_modules.cpp +++ b/code/ryzom/client/src/interface_v3/parser_modules.cpp @@ -24,6 +24,7 @@ #include "../commands.h" #include "interface_3d_scene.h" #include "nel/misc/i_xml.h" +#include "../continent_manager.h" using namespace NLMISC; @@ -31,6 +32,8 @@ using namespace NLMISC; #include "../client_cfg.h" #endif +extern CContinentManager ContinentMngr; + CIF3DSceneParser::CIF3DSceneParser() { parsingStage |= ( Resolved | GroupChildren ); @@ -529,3 +532,21 @@ bool CMacroParser::parse( xmlNodePtr cur, NLGUI::CInterfaceGroup *parentGroup ) return true; } +CLandmarkParser::CLandmarkParser() +{ + parsingStage |= Unresolved; +} + +CLandmarkParser::~CLandmarkParser() +{ +} + +bool CLandmarkParser::parse( xmlNodePtr cur, NLGUI::CInterfaceGroup *parentGroup ) +{ + H_AUTO(parseLandmark) + + ContinentMngr.readFrom(cur); + + return true; +} + diff --git a/code/ryzom/client/src/interface_v3/parser_modules.h b/code/ryzom/client/src/interface_v3/parser_modules.h index 9a2bf76b9..f816f8d39 100644 --- a/code/ryzom/client/src/interface_v3/parser_modules.h +++ b/code/ryzom/client/src/interface_v3/parser_modules.h @@ -76,4 +76,13 @@ public: bool parse( xmlNodePtr cur, CInterfaceGroup *parentGroup ); }; +class CLandmarkParser : public CInterfaceParser::IParserModule +{ +public: + CLandmarkParser(); + ~CLandmarkParser(); + + bool parse( xmlNodePtr cur, CInterfaceGroup *parentGroup ); +}; + #endif From 2f0a831d528abe3f378b182c05dc87c41c5d0090 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 28 Feb 2019 17:26:51 +0200 Subject: [PATCH 089/108] Fixed: Invalid comparison of signed value --HG-- branch : develop --- code/ryzom/client/src/continent_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/ryzom/client/src/continent_manager.cpp b/code/ryzom/client/src/continent_manager.cpp index 56a11cf63..c4b5cc693 100644 --- a/code/ryzom/client/src/continent_manager.cpp +++ b/code/ryzom/client/src/continent_manager.cpp @@ -518,7 +518,7 @@ void CContinentManager::writeTo(xmlNodePtr node) const std::string title = lm.Title.toUtf8(); for(uint i = 0; i< title.size(); i++) { - if (title[i] < ' ' && title[i] != '\n' && title[i] != '\t') + if (title[i] >= '\0' && title[i] < ' ' && title[i] != '\n' && title[i] != '\t') { title[i] = '?'; } From ab7ad10796d102ba6320d9ccdc11d25fba0bddfa Mon Sep 17 00:00:00 2001 From: Nimetu Date: Fri, 1 Mar 2019 00:22:43 +0200 Subject: [PATCH 090/108] Fixed: Save landmarks immediately on change --HG-- branch : develop --- code/ryzom/client/src/interface_v3/group_map.cpp | 3 +++ code/ryzom/client/src/interface_v3/interface_manager.cpp | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/code/ryzom/client/src/interface_v3/group_map.cpp b/code/ryzom/client/src/interface_v3/group_map.cpp index 28eb5e1e3..e9864b61b 100644 --- a/code/ryzom/client/src/interface_v3/group_map.cpp +++ b/code/ryzom/client/src/interface_v3/group_map.cpp @@ -2739,6 +2739,7 @@ void CGroupMap::removeUserLandMark(CCtrlButton *button) updateUserLandMarks(); } + CInterfaceManager::getInstance()->saveLandmarks(); return; } } @@ -2758,6 +2759,8 @@ void CGroupMap::updateUserLandMark(CCtrlButton *button, const ucstring &newTitle updateLandMarkButton(_UserLM[k], getUserLandMarkOptions(k)); button->setDefaultContextHelp(newTitle); + + CInterfaceManager::getInstance()->saveLandmarks(); return; } } diff --git a/code/ryzom/client/src/interface_v3/interface_manager.cpp b/code/ryzom/client/src/interface_v3/interface_manager.cpp index bc606f939..80c6c552b 100644 --- a/code/ryzom/client/src/interface_v3/interface_manager.cpp +++ b/code/ryzom/client/src/interface_v3/interface_manager.cpp @@ -3096,7 +3096,6 @@ class CAHSaveUI : public IActionHandler { CInterfaceManager::getInstance()->saveKeys(true); CInterfaceManager::getInstance()->saveConfig(true); - CInterfaceManager::getInstance()->saveLandmarks(true); } }; REGISTER_ACTION_HANDLER (CAHSaveUI, "save_ui"); From 21f15a47bc59c7851e6331e0671c590f10c72316 Mon Sep 17 00:00:00 2001 From: kervala Date: Sat, 9 Mar 2019 12:59:58 +0100 Subject: [PATCH 091/108] Fixed: If no profile found, don't process the default one --HG-- branch : develop --- .../client/ryzom_installer/src/configfile.cpp | 4 ++-- .../client/ryzom_installer/src/mainwindow.cpp | 16 ++++++++++++++++ .../ryzom_installer/src/operationdialog.cpp | 6 ++++++ .../ryzom_installer/src/uninstalldialog.cpp | 3 +++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/code/ryzom/tools/client/ryzom_installer/src/configfile.cpp b/code/ryzom/tools/client/ryzom_installer/src/configfile.cpp index 889dec9cb..d23119b27 100644 --- a/code/ryzom/tools/client/ryzom_installer/src/configfile.cpp +++ b/code/ryzom/tools/client/ryzom_installer/src/configfile.cpp @@ -290,8 +290,8 @@ CProfile CConfigFile::getProfile(const QString &id) const if (m_profiles[i].id == id) return m_profiles[i]; } - // default profile - return getProfile(); + // no profile + return NoProfile; } void CConfigFile::setProfile(int i, const CProfile &profile) diff --git a/code/ryzom/tools/client/ryzom_installer/src/mainwindow.cpp b/code/ryzom/tools/client/ryzom_installer/src/mainwindow.cpp index e89a1f07d..16e0c8bad 100644 --- a/code/ryzom/tools/client/ryzom_installer/src/mainwindow.cpp +++ b/code/ryzom/tools/client/ryzom_installer/src/mainwindow.cpp @@ -103,6 +103,10 @@ void CMainWindow::updateButtons() CConfigFile *config = CConfigFile::getInstance(); const CProfile &profile = config->getProfile(profileIndex); + + // wrong profile + if (profile.id.isEmpty()) return; + const CServer &server = config->getServer(profile.server); // get full path of client executable @@ -125,6 +129,10 @@ void CMainWindow::onPlayClicked() CConfigFile *config = CConfigFile::getInstance(); const CProfile &profile = config->getProfile(profileIndex); + + // wrong profile + if (profile.id.isEmpty()) return; + const CServer &server = config->getServer(profile.server); // get full path of client executable @@ -162,6 +170,10 @@ void CMainWindow::onConfigureClicked() CConfigFile *config = CConfigFile::getInstance(); const CProfile &profile = config->getProfile(profileIndex); + + // wrong profile + if (profile.id.isEmpty()) return; + const CServer &server = config->getServer(profile.server); // get full path of configuration executable @@ -310,6 +322,10 @@ void CMainWindow::onProfileChanged(int profileIndex) CConfigFile *config = CConfigFile::getInstance(); CProfile profile = config->getProfile(profileIndex); + + // wrong profile + if (profile.id.isEmpty()) return; + CServer server = config->getServer(profile.server); // load changelog diff --git a/code/ryzom/tools/client/ryzom_installer/src/operationdialog.cpp b/code/ryzom/tools/client/ryzom_installer/src/operationdialog.cpp index ec0a21714..e9d6fdebd 100644 --- a/code/ryzom/tools/client/ryzom_installer/src/operationdialog.cpp +++ b/code/ryzom/tools/client/ryzom_installer/src/operationdialog.cpp @@ -926,6 +926,9 @@ bool COperationDialog::createProfileShortcuts(const QString &profileId) const CProfile &profile = config->getProfile(profileId); + // wrong profile + if (profile.id.isEmpty()) return false; + m_currentOperation = tr("Creating shortcuts for profile %1...").arg(profile.id); profile.createShortcuts(); @@ -1080,6 +1083,9 @@ void COperationDialog::addComponentsProfiles() { const CProfile &profile = config->getProfile(profileId); + // wrong profile + if (profile.id.isEmpty()) continue; + profile.createShortcuts(); profile.createClientConfig(); } diff --git a/code/ryzom/tools/client/ryzom_installer/src/uninstalldialog.cpp b/code/ryzom/tools/client/ryzom_installer/src/uninstalldialog.cpp index 1330a16bf..e346d7a00 100644 --- a/code/ryzom/tools/client/ryzom_installer/src/uninstalldialog.cpp +++ b/code/ryzom/tools/client/ryzom_installer/src/uninstalldialog.cpp @@ -257,6 +257,9 @@ void CUninstallDialog::updateSizes() { const CProfile &profile = config->getProfile(it.key()); + // wrong profile + if (profile.id.isEmpty()) continue; + qint64 bytes = getDirectorySize(profile.getDirectory(), true); emit updateSize(it.value(), qBytesToHumanReadable(bytes)); From 78c39850ae576188fb5d0329a1aa7a1cd14368b3 Mon Sep 17 00:00:00 2001 From: kervala Date: Sat, 9 Mar 2019 13:00:59 +0100 Subject: [PATCH 092/108] Fixed: Check profile details in backup profiles if deleted --HG-- branch : develop --- .../tools/client/ryzom_installer/src/configfile.cpp | 10 ++++++++++ .../tools/client/ryzom_installer/src/configfile.h | 1 + .../client/ryzom_installer/src/operationdialog.cpp | 13 ++++++++++--- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/code/ryzom/tools/client/ryzom_installer/src/configfile.cpp b/code/ryzom/tools/client/ryzom_installer/src/configfile.cpp index d23119b27..7b61bda7e 100644 --- a/code/ryzom/tools/client/ryzom_installer/src/configfile.cpp +++ b/code/ryzom/tools/client/ryzom_installer/src/configfile.cpp @@ -264,6 +264,16 @@ const CServer& CConfigFile::getServer(const QString &id) const return getServer(); } +CProfile CConfigFile::getBackupProfile(const QString &id) const +{ + for (int i = 0; i < m_backupProfiles.size(); ++i) + { + if (m_backupProfiles[i].id == id) return m_backupProfiles[i]; + } + + return NoProfile; +} + void CConfigFile::backupProfiles() { m_backupProfiles = m_profiles; diff --git a/code/ryzom/tools/client/ryzom_installer/src/configfile.h b/code/ryzom/tools/client/ryzom_installer/src/configfile.h index baa815bd8..6480c1f87 100644 --- a/code/ryzom/tools/client/ryzom_installer/src/configfile.h +++ b/code/ryzom/tools/client/ryzom_installer/src/configfile.h @@ -53,6 +53,7 @@ public: void setProfiles(const CProfiles &profiles) { m_profiles = profiles; } CProfiles getBackupProfiles() const { return m_backupProfiles; } + CProfile getBackupProfile(const QString &id) const; void backupProfiles(); QString getLanguage() const { return m_language; } diff --git a/code/ryzom/tools/client/ryzom_installer/src/operationdialog.cpp b/code/ryzom/tools/client/ryzom_installer/src/operationdialog.cpp index e9d6fdebd..fe01509ab 100644 --- a/code/ryzom/tools/client/ryzom_installer/src/operationdialog.cpp +++ b/code/ryzom/tools/client/ryzom_installer/src/operationdialog.cpp @@ -1105,6 +1105,9 @@ void COperationDialog::deleteComponentsProfiles() CConfigFile *config = CConfigFile::getInstance(); + // some profiles have been removed, use backup profiles + bool useBackup = !config->getBackupProfiles().isEmpty(); + int i = 0; foreach(const QString &profileId, m_removeComponents.profiles) @@ -1115,7 +1118,11 @@ void COperationDialog::deleteComponentsProfiles() return; } - const CProfile &profile = config->getProfile(profileId); + // only search in backup profiles, because they are already deleted in profiles + const CProfile &profile = useBackup ? config->getBackupProfile(profileId):config->getProfile(profileId); + + // already deleted profile + if (profile.id.isEmpty()) continue; emit progress(i++, profile.name); @@ -1134,8 +1141,8 @@ void COperationDialog::deleteComponentsProfiles() profile.deleteShortcuts(); - // delete profile - config->removeProfile(profileId); + // delete profile if still used + if (!useBackup) config->removeProfile(profileId); } emit success(m_removeComponents.profiles.size()); From d62c139ee0d0d7e9363392810329135a19c3cddb Mon Sep 17 00:00:00 2001 From: Nimetu Date: Tue, 12 Mar 2019 20:02:43 +0200 Subject: [PATCH 093/108] Changed: Parse html style attribute --HG-- branch : develop --- code/nel/include/nel/gui/group_html.h | 5 +++ code/nel/include/nel/gui/libwww.h | 8 ++++ code/nel/src/gui/group_html.cpp | 60 ++++++++++++++++++++------- code/nel/src/gui/libwww.cpp | 11 +++++ 4 files changed, 68 insertions(+), 16 deletions(-) diff --git a/code/nel/include/nel/gui/group_html.h b/code/nel/include/nel/gui/group_html.h index 7820cbd6a..8b296f114 100644 --- a/code/nel/include/nel/gui/group_html.h +++ b/code/nel/include/nel/gui/group_html.h @@ -105,6 +105,7 @@ namespace NLGUI Height=-1; MaxWidth=-1; MaxHeight=-1; + BackgroundColor=NLMISC::CRGBA::Black; } uint FontSize; uint FontWeight; @@ -119,6 +120,7 @@ namespace NLGUI sint32 Height; sint32 MaxWidth; sint32 MaxHeight; + NLMISC::CRGBA BackgroundColor; }; // ImageDownload system @@ -345,6 +347,9 @@ namespace NLGUI // Get Home URL virtual std::string home(); + // Clear style stack and restore default style + void resetCssStyle(); + // Parse style html tag TStyle parseStyle(const std::string &str_styles); diff --git a/code/nel/include/nel/gui/libwww.h b/code/nel/include/nel/gui/libwww.h index 9c6579f1f..112cae7d7 100644 --- a/code/nel/include/nel/gui/libwww.h +++ b/code/nel/include/nel/gui/libwww.h @@ -49,6 +49,14 @@ namespace NLGUI #undef HTML_ATTR #define HTML_ATTR(t,a) MY_HTML_##t##_##a + enum + { + HTML_ATTR(HTML,DIR) = 0, + HTML_ATTR(HTML,LANG), + HTML_ATTR(HTML,VERSION), + HTML_ATTR(HTML,STYLE), + }; + enum { HTML_ATTR(A,ACCESSKEY) = 0, diff --git a/code/nel/src/gui/group_html.cpp b/code/nel/src/gui/group_html.cpp index 92431a3aa..7b81ef746 100644 --- a/code/nel/src/gui/group_html.cpp +++ b/code/nel/src/gui/group_html.cpp @@ -1489,6 +1489,13 @@ namespace NLGUI // Paragraph ? switch(element_number) { + case HTML_HTML: + if (present[MY_HTML_HTML_STYLE] && value[MY_HTML_HTML_STYLE]) + getStyleParams(value[MY_HTML_HTML_STYLE], _StyleDefault); + + _Style = _StyleDefault; + setBackgroundColor(_Style.BackgroundColor); + break; case HTML_HEAD: _ReadingHeadTag = !_IgnoreHeadTag; _IgnoreHeadTag = true; @@ -1708,18 +1715,22 @@ namespace NLGUI break; case HTML_BODY: { - if (present[HTML_BODY_BGCOLOR] && value[HTML_BODY_BGCOLOR]) - { - CRGBA bgColor; - if (scanHTMLColor(value[HTML_BODY_BGCOLOR], bgColor)) - setBackgroundColor (bgColor); - } - + pushStyle(); + string style; if (present[HTML_BODY_STYLE] && value[HTML_BODY_STYLE]) style = value[HTML_BODY_STYLE]; - - + + if (!style.empty()) + getStyleParams(style, _Style); + + CRGBA bgColor = _Style.BackgroundColor; + if (present[HTML_BODY_BGCOLOR] && value[HTML_BODY_BGCOLOR]) + scanHTMLColor(value[HTML_BODY_BGCOLOR], bgColor); + + if (bgColor != _Style.BackgroundColor) + setBackgroundColor(bgColor); + if (!style.empty()) { TStyle styles = parseStyle(style); @@ -1743,10 +1754,6 @@ namespace NLGUI image = image.substr(4, image.size()-5); setBackground (image, scale, repeat); } - - // set default text style from - getStyleParams(style, _StyleDefault); - _Style = _StyleDefault; } } break; @@ -2737,6 +2744,9 @@ namespace NLGUI case HTML_HEAD: _ReadingHeadTag = false; break; + case HTML_BODY: + popStyle(); + break; case HTML_FONT: popStyle(); break; @@ -4915,9 +4925,7 @@ namespace NLGUI _IgnoreBaseUrlTag = false; // reset style - _StyleDefault = CStyleParams(); - _Style = _StyleDefault; - _StyleParams.clear(); + resetCssStyle(); // TR @@ -6252,6 +6260,18 @@ namespace NLGUI return uri.toString(); } + // *************************************************************************** + void CGroupHTML::resetCssStyle() + { + _StyleDefault = CStyleParams(); + _StyleDefault.TextColor = TextColor; + _StyleDefault.FontSize = TextFontSize; + _StyleDefault.BackgroundColor = BgColor; + + _Style = _StyleDefault; + _StyleParams.clear(); + } + // *************************************************************************** // CGroupHTML::CStyleParams style; // style.FontSize; // font-size: 10px; @@ -6485,6 +6505,14 @@ namespace NLGUI if (fromString(it->second, b)) style.GlobalColor = b; } + else + if (it->first == "background-color") + { + if (it->second == "inherit") + style.BackgroundColor = current.backgroundColor; + else + scanHTMLColor(it->second.c_str(), style.BackgroundColor); + } } } diff --git a/code/nel/src/gui/libwww.cpp b/code/nel/src/gui/libwww.cpp index f00a92749..bb32fd5b6 100644 --- a/code/nel/src/gui/libwww.cpp +++ b/code/nel/src/gui/libwww.cpp @@ -47,6 +47,15 @@ namespace NLGUI #undef HTML_ATTR #define HTML_ATTR(a,b) { (char*) #b } + HTAttr html_attr[] = + { + HTML_ATTR(HTML,DIR), + HTML_ATTR(HTML,LANG), + HTML_ATTR(HTML,VERSION), + HTML_ATTR(HTML,STYLE), + { 0 } + }; + HTAttr a_attr[] = { HTML_ATTR(A,ACCESSKEY), @@ -469,6 +478,8 @@ namespace NLGUI // Change the HTML DTD SGML_dtd *HTML_DTD = HTML_dtd (); + HTML_DTD->tags[HTML_HTML].attributes = html_attr; + HTML_DTD->tags[HTML_HTML].number_of_attributes = sizeof(html_attr) / sizeof(HTAttr) - 1; HTML_DTD->tags[HTML_TABLE].attributes = table_attr; HTML_DTD->tags[HTML_TABLE].number_of_attributes = sizeof(table_attr) / sizeof(HTAttr) - 1; HTML_DTD->tags[HTML_TR].attributes = tr_attr; From b6d3ff8323c17a5c7b382fad601ccd75e244552a Mon Sep 17 00:00:00 2001 From: Nimetu Date: Tue, 12 Mar 2019 20:03:34 +0200 Subject: [PATCH 094/108] Changed: Parse css pt/em/rem size values --HG-- branch : develop --- code/nel/include/nel/gui/libwww.h | 3 + code/nel/src/gui/group_html.cpp | 96 +++++++++++++++++++++++++++---- code/nel/src/gui/libwww.cpp | 31 ++++++++++ 3 files changed, 118 insertions(+), 12 deletions(-) diff --git a/code/nel/include/nel/gui/libwww.h b/code/nel/include/nel/gui/libwww.h index 112cae7d7..84eeb01e9 100644 --- a/code/nel/include/nel/gui/libwww.h +++ b/code/nel/include/nel/gui/libwww.h @@ -280,6 +280,9 @@ namespace NLGUI #undef HTML_ATTR // *************************************************************************** + // Read a CSS length value, return true if one of supported units '%, rem, em, px, pt' + // On failure: 'value' and 'unit' values are undefined + bool getCssLength (float &value, std::string &unit, const std::string &str); // Read a width HTML parameter. "100" or "100%". Returns true if percent (0 ~ 1) else false bool getPercentage (sint32 &width, float &percent, const char *str); diff --git a/code/nel/src/gui/group_html.cpp b/code/nel/src/gui/group_html.cpp index 7b81ef746..514bccf15 100644 --- a/code/nel/src/gui/group_html.cpp +++ b/code/nel/src/gui/group_html.cpp @@ -1851,10 +1851,11 @@ namespace NLGUI // Get the string name if (present[MY_HTML_IMG_SRC] && value[MY_HTML_IMG_SRC]) { - CStyleParams style; float tmpf; - std::string id; + CStyleParams style; + style.FontSize = _Style.FontSize; + if (present[MY_HTML_IMG_ID] && value[MY_HTML_IMG_ID]) id = value[MY_HTML_IMG_ID]; @@ -2190,6 +2191,7 @@ namespace NLGUI if (!(_Forms.empty())) { CStyleParams style; + style.FontSize = _Style.FontSize; // A select box string name; @@ -2699,6 +2701,7 @@ namespace NLGUI if (sep) { CStyleParams style; + style.FontSize = _Style.FontSize; style.TextColor = CRGBA(120, 120, 120, 255); style.Height = 0; style.Width = 0; @@ -6291,22 +6294,39 @@ namespace NLGUI float tmpf; TStyle styles = parseStyle(styleString); TStyle::iterator it; + + // first pass: get font-size for 'em' sizes for (it=styles.begin(); it != styles.end(); ++it) { if (it->first == "font-size") { if (it->second == "inherit") + { style.FontSize = current.FontSize; + } else { - float tmp; - sint size = 0; - getPercentage (size, tmp, it->second.c_str()); - if (size > 0) - style.FontSize = size; + std::string unit; + if (getCssLength(tmpf, unit, it->second.c_str())) + { + if (unit == "rem") + style.FontSize = _StyleDefault.FontSize * tmpf; + else if (unit == "em") + style.FontSize = current.FontSize * tmpf; + else if (unit == "pt") + style.FontSize = tmpf / 0.75f; + else if (unit == "%") + style.FontSize = current.FontSize * tmpf / 100.f; + else + style.FontSize = tmpf; + } } } - else + } + + // second pass: rest of style + for (it=styles.begin(); it != styles.end(); ++it) + { if (it->first == "font-style") { if (it->second == "inherit") @@ -6485,16 +6505,68 @@ namespace NLGUI } else if (it->first == "width") - getPercentage(style.Width, tmpf, it->second.c_str()); + { + std::string unit; + if (getCssLength(tmpf, unit, it->second.c_str())) + { + if (unit == "rem") + style.Width = tmpf * _StyleDefault.FontSize; + else if (unit == "em") + style.Width = tmpf * style.FontSize; + else if (unit == "pt") + style.FontSize = tmpf / 0.75f; + else + style.Width = tmpf; + } + } else if (it->first == "height") - getPercentage(style.Height, tmpf, it->second.c_str()); + { + std::string unit; + if (getCssLength(tmpf, unit, it->second.c_str())) + { + if (unit == "rem") + style.Height = tmpf * _StyleDefault.FontSize; + else if (unit == "em") + style.Height = tmpf * style.FontSize; + else if (unit == "pt") + style.FontSize = tmpf / 0.75f; + else + style.Height = tmpf; + } + } else if (it->first == "max-width") - getPercentage(style.MaxWidth, tmpf, it->second.c_str()); + { + std::string unit; + if (getCssLength(tmpf, unit, it->second.c_str())) + { + if (unit == "rem") + style.MaxWidth = tmpf * _StyleDefault.FontSize; + else if (unit == "em") + style.MaxWidth = tmpf * style.FontSize; + else if (unit == "pt") + style.FontSize = tmpf / 0.75f; + else + style.MaxWidth = tmpf; + } + } else if (it->first == "max-height") - getPercentage(style.MaxHeight, tmpf, it->second.c_str()); + { + std::string unit; + if (getCssLength(tmpf, unit, it->second.c_str())) + { + if (unit == "rem") + style.MaxHeight = tmpf * _StyleDefault.FontSize; + else if (unit == "em") + style.MaxHeight = tmpf * style.FontSize; + else if (unit == "pt") + style.FontSize = tmpf / 0.75f; + else + style.MaxHeight = tmpf; + } + } else if (it->first == "-ryzom-modulate-color") { diff --git a/code/nel/src/gui/libwww.cpp b/code/nel/src/gui/libwww.cpp index bb32fd5b6..ca8920b14 100644 --- a/code/nel/src/gui/libwww.cpp +++ b/code/nel/src/gui/libwww.cpp @@ -293,6 +293,37 @@ namespace NLGUI }; // *************************************************************************** + bool getCssLength (float &value, std::string &unit, const std::string &str) + { + std::string::size_type pos = 0; + std::string::size_type len = str.size(); + if (len == 1 && str[0] == '.') + { + return false; + } + + while(pos < len) + { + bool isNumeric = (str[pos] >= '0' && str[pos] <= '9') + || (pos == 0 && str[pos] == '.') + || (pos > 0 && str[pos] == '.' && str[pos-1] >= '0' && str[pos-1] <= '9'); + if (!isNumeric) + { + break; + } + + pos++; + } + + unit = toLower(str.substr(pos)); + if (unit == "%" || unit == "rem" || unit == "em" || unit == "px" || unit == "pt") + { + std::string tmpstr = str.substr(0, pos); + return fromString(tmpstr, value); + } + + return false; + } // Read a width HTML parameter. "100" or "100%". Returns true if percent (0 ~ 1) else false bool getPercentage (sint32 &width, float &percent, const char *str) From 69f73ba28ba18ccfbdcca954fd06c9988c20f13b Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 14 Mar 2019 14:57:55 +0200 Subject: [PATCH 095/108] Changed: Add hmin attribute to text button --HG-- branch : develop --- code/nel/include/nel/gui/ctrl_text_button.h | 8 +++++-- code/nel/src/gui/ctrl_text_button.cpp | 26 ++++++++++++++++++++- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/code/nel/include/nel/gui/ctrl_text_button.h b/code/nel/include/nel/gui/ctrl_text_button.h index 69898639d..367e527f8 100644 --- a/code/nel/include/nel/gui/ctrl_text_button.h +++ b/code/nel/include/nel/gui/ctrl_text_button.h @@ -111,6 +111,9 @@ namespace NLGUI sint32 getWMin() const { return _WMin; } void setWMin( sint32 wmin ) { _WMin = wmin; } + sint32 getHMin() const { return _HMin; } + void setHMin( sint32 hmin ) { _HMin = hmin; } + // Compute Size according to bitmap and Text (Ensure as big as possible button) sint32 getWMax() const; @@ -122,6 +125,7 @@ namespace NLGUI REFLECT_SINT32("text_x", getTextX, setTextX) REFLECT_SINT32("wmargin", getWMargin, setWMargin) REFLECT_SINT32("wmin", getWMin, setWMin) + REFLECT_SINT32("hmin", getHMin, setHMin) REFLECT_LUA_METHOD("getViewText", luaGetViewText) REFLECT_EXPORT_END @@ -151,8 +155,8 @@ namespace NLGUI sint32 _BmpLeftW, _BmpMiddleW, _BmpRightW, _BmpH; // Value to add to TextW to get button W. sint32 _WMargin; - // Min W Value - sint32 _WMin; + // Min W, H Value + sint32 _WMin, _HMin; sint32 _TextY; sint32 _TextX; THotSpot _TextPosRef; diff --git a/code/nel/src/gui/ctrl_text_button.cpp b/code/nel/src/gui/ctrl_text_button.cpp index 6e1fab26e..9775b1b07 100644 --- a/code/nel/src/gui/ctrl_text_button.cpp +++ b/code/nel/src/gui/ctrl_text_button.cpp @@ -46,6 +46,7 @@ namespace NLGUI _BmpLeftW= _BmpMiddleW= _BmpRightW= _BmpH= 0; _WMargin= 0; _WMin= 0; + _HMin= 0; _TextX= 0; _TextY= 0; _Setuped= false; @@ -124,6 +125,11 @@ namespace NLGUI return toString( _WMin ); } else + if( name == "hmin" ) + { + return toString( _HMin ); + } + else if( name == "hardtext" ) { if( _ViewText != NULL ) @@ -296,6 +302,14 @@ namespace NLGUI return; } else + if( name == "hmin" ) + { + sint32 i; + if( fromString( value, i ) ) + _HMin = i; + return; + } + else if( name == "hardtext" ) { if( _ViewText != NULL ) @@ -469,6 +483,7 @@ namespace NLGUI xmlNewProp( node, BAD_CAST "wmargin", BAD_CAST toString( _WMargin ).c_str() ); xmlNewProp( node, BAD_CAST "wmin", BAD_CAST toString( _WMin ).c_str() ); + xmlNewProp( node, BAD_CAST "hmin", BAD_CAST toString( _HMin ).c_str() ); xmlNewProp( node, BAD_CAST "hardtext", BAD_CAST _ViewText->getText().toString().c_str() ); xmlNewProp( node, BAD_CAST "text_y", BAD_CAST toString( _TextY ).c_str() ); xmlNewProp( node, BAD_CAST "text_x", BAD_CAST toString( _TextX ).c_str() ); @@ -519,7 +534,6 @@ namespace NLGUI return false; } - // *** Read Textures. prop = (char*) xmlGetProp( cur, (xmlChar*)"tx_normal" ); if (prop) @@ -604,6 +618,15 @@ namespace NLGUI // _WMin is at least the size of All W Bitmaps _WMin= max(_WMin, _BmpLeftW + _BmpMiddleW + _BmpRightW); + // hmin + _HMin= 0; + prop = (char*) xmlGetProp( cur, (xmlChar*)"hmin" ); + if (prop) + { + fromString((const char *) prop, _HMin); + } + _HMin= max(_HMin, _BmpH); + // TextY _TextY= 0; prop = (char*) xmlGetProp( cur, (xmlChar*)"text_y" ); @@ -900,6 +923,7 @@ namespace NLGUI if (!(_SizeRef & 2)) { _H= max(_BmpH, _ViewText->getH()); + _H= max(_H, _HMin); } CViewBase::updateCoords(); From 64e54f24c1d923c074e1cc4f3442a82543827c63 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 14 Mar 2019 22:59:33 +0200 Subject: [PATCH 096/108] Changed: Form element styling --HG-- branch : develop --- code/nel/include/nel/gui/ctrl_text_button.h | 7 + code/nel/include/nel/gui/dbgroup_combo_box.h | 1 + code/nel/include/nel/gui/group_html.h | 8 ++ code/nel/include/nel/gui/group_menu.h | 3 + code/nel/src/gui/ctrl_text_button.cpp | 37 ++++++ code/nel/src/gui/dbgroup_combo_box.cpp | 9 ++ code/nel/src/gui/group_html.cpp | 133 ++++++++++++++++--- code/nel/src/gui/group_menu.cpp | 6 + 8 files changed, 188 insertions(+), 16 deletions(-) diff --git a/code/nel/include/nel/gui/ctrl_text_button.h b/code/nel/include/nel/gui/ctrl_text_button.h index 367e527f8..e88ec81e1 100644 --- a/code/nel/include/nel/gui/ctrl_text_button.h +++ b/code/nel/include/nel/gui/ctrl_text_button.h @@ -117,6 +117,13 @@ namespace NLGUI // Compute Size according to bitmap and Text (Ensure as big as possible button) sint32 getWMax() const; + // Set texture directly without _l.tga, _m.tga, _r.tga convention + // Texture size is only read from normal textures + // If updateHeight == false, then _BmpH will keep its value + void setTexture(const std::string &l, const std::string &m, const std::string &r, bool updateHeight = true); + void setTexturePushed(const std::string &l, const std::string &m, const std::string &r); + void setTextureOver(const std::string &l, const std::string &m, const std::string &r); + int luaGetViewText(CLuaState &ls); REFLECT_EXPORT_START(CCtrlTextButton, CCtrlBaseButton) diff --git a/code/nel/include/nel/gui/dbgroup_combo_box.h b/code/nel/include/nel/gui/dbgroup_combo_box.h index 87e6d758a..723afbdc0 100644 --- a/code/nel/include/nel/gui/dbgroup_combo_box.h +++ b/code/nel/include/nel/gui/dbgroup_combo_box.h @@ -85,6 +85,7 @@ namespace NLGUI // view text void setViewText(const ucstring & text); ucstring getViewText() const; + CViewText *getViewText(); void setTexture(uint i, const ucstring &texture); diff --git a/code/nel/include/nel/gui/group_html.h b/code/nel/include/nel/gui/group_html.h index 8b296f114..71d7b9ff0 100644 --- a/code/nel/include/nel/gui/group_html.h +++ b/code/nel/include/nel/gui/group_html.h @@ -33,6 +33,7 @@ typedef std::map TStyle; namespace NLGUI { class CCtrlButton; + class CCtrlTextButton; class CCtrlScroll; class CGroupList; class CGroupMenu; @@ -105,7 +106,9 @@ namespace NLGUI Height=-1; MaxWidth=-1; MaxHeight=-1; + BorderWidth=1; BackgroundColor=NLMISC::CRGBA::Black; + BackgroundColorOver=NLMISC::CRGBA::Black; } uint FontSize; uint FontWeight; @@ -120,7 +123,9 @@ namespace NLGUI sint32 Height; sint32 MaxWidth; sint32 MaxHeight; + sint32 BorderWidth; NLMISC::CRGBA BackgroundColor; + NLMISC::CRGBA BackgroundColorOver; }; // ImageDownload system @@ -857,6 +862,9 @@ namespace NLGUI void setImage(CViewBase *view, const std::string &file, const TImageType type); void setImageSize(CViewBase *view, const CStyleParams &style = CStyleParams()); + void setTextButtonStyle(CCtrlTextButton *ctrlButton, const CStyleParams &style); + void setTextStyle(CViewText *pVT, const CStyleParams &style); + // BnpDownload system void initBnpDownload(); void checkBnpDownload(); diff --git a/code/nel/include/nel/gui/group_menu.h b/code/nel/include/nel/gui/group_menu.h index d4f940088..cfe91f7f9 100644 --- a/code/nel/include/nel/gui/group_menu.h +++ b/code/nel/include/nel/gui/group_menu.h @@ -351,6 +351,9 @@ namespace NLGUI void setMinW(sint32 minW); void setMinH(sint32 minH); + // change fontsize for new menu items + void setFontSize(uint32 fontSize); + // Gray a line on the RootMenu void setGrayedLine(uint line, bool g); diff --git a/code/nel/src/gui/ctrl_text_button.cpp b/code/nel/src/gui/ctrl_text_button.cpp index 9775b1b07..d10dfa0c9 100644 --- a/code/nel/src/gui/ctrl_text_button.cpp +++ b/code/nel/src/gui/ctrl_text_button.cpp @@ -728,6 +728,43 @@ namespace NLGUI return true; } + // *************************************************************************** + void CCtrlTextButton::setTexture(const std::string &l, const std::string &m, const std::string &r, bool updateHeight) + { + nlctassert(NumTexture==3); + _TextureIdNormal[0].setTexture(l.c_str()); + _TextureIdNormal[1].setTexture(m.c_str()); + _TextureIdNormal[2].setTexture(r.c_str()); + + sint32 newH; + + // Compute Bmp Sizes + CViewRenderer &rVR = *CViewRenderer::getInstance(); + rVR.getTextureSizeFromId(_TextureIdNormal[0], _BmpLeftW, newH); + rVR.getTextureSizeFromId(_TextureIdNormal[1], _BmpMiddleW, newH); + rVR.getTextureSizeFromId(_TextureIdNormal[2], _BmpRightW, newH); + + if (updateHeight) _BmpH = newH; + } + + // *************************************************************************** + void CCtrlTextButton::setTexturePushed(const std::string &l, const std::string &m, const std::string &r) + { + nlctassert(NumTexture==3); + _TextureIdPushed[0].setTexture(l.c_str()); + _TextureIdPushed[1].setTexture(m.c_str()); + _TextureIdPushed[2].setTexture(r.c_str()); + } + + // *************************************************************************** + void CCtrlTextButton::setTextureOver(const std::string &l, const std::string &m, const std::string &r) + { + nlctassert(NumTexture==3); + _TextureIdOver[0].setTexture(l.c_str()); + _TextureIdOver[1].setTexture(m.c_str()); + _TextureIdOver[2].setTexture(r.c_str()); + } + // *************************************************************************** void CCtrlTextButton::draw () { diff --git a/code/nel/src/gui/dbgroup_combo_box.cpp b/code/nel/src/gui/dbgroup_combo_box.cpp index 2afd8c570..5893c0b72 100644 --- a/code/nel/src/gui/dbgroup_combo_box.cpp +++ b/code/nel/src/gui/dbgroup_combo_box.cpp @@ -473,6 +473,12 @@ namespace NLGUI return _ViewText->getText(); } + // *************************************************************************** + CViewText *CDBGroupComboBox::getViewText() + { + return _ViewText; + } + // *************************************************************************** std::string CDBGroupComboBox::getSelectionText() const { @@ -633,6 +639,9 @@ namespace NLGUI { nlassert(groupMenu); + if (_ViewText) + groupMenu->setFontSize(_ViewText->getFontSize()); + // Setup the menu with combo action. groupMenu->reset(); for(uint i=0; i treating it like "display: inline-block;" + if (style.Width > 0) ctrlButton->setWMin(_Style.Width); + if (style.Height > 0) ctrlButton->setHMin(_Style.Height); + + CViewText *pVT = ctrlButton->getViewText(); + if (pVT) + { + setTextStyle(pVT, _Style); + } + + if (_Style.BackgroundColor.A > 0) + { + if (_Style.BackgroundColorOver.A == 0) + _Style.BackgroundColorOver = _Style.BackgroundColor; + + ctrlButton->setColor(_Style.BackgroundColor); + ctrlButton->setColorOver(_Style.BackgroundColorOver); + ctrlButton->setTexture("", "blank.tga", "", false); + ctrlButton->setTextureOver("", "blank.tga", ""); + ctrlButton->setProperty("force_text_over", "true"); + } + else if (_Style.BackgroundColorOver.A > 0) + { + ctrlButton->setColorOver(_Style.BackgroundColorOver); + ctrlButton->setProperty("force_text_over", "true"); + ctrlButton->setTextureOver("blank.tga", "blank.tga", "blank.tga"); + } + } + + void CGroupHTML::setTextStyle(CViewText *pVT, const CStyleParams &style) + { + if (pVT) + { + pVT->setFontSize(style.FontSize); + pVT->setColor(style.TextColor); + pVT->setColor(style.TextColor); + pVT->setFontName(style.FontFamily); + pVT->setFontSize(style.FontSize); + pVT->setEmbolden(style.FontWeight >= FONT_WEIGHT_BOLD); + pVT->setOblique(style.FontOblique); + pVT->setUnderlined(style.Underlined); + pVT->setStrikeThrough(style.StrikeThrough); + if (style.TextShadow.Enabled) + { + pVT->setShadow(true); + pVT->setShadowColor(style.TextShadow.Color); + pVT->setShadowOutline(style.TextShadow.Outline); + pVT->setShadowOffset(style.TextShadow.X, style.TextShadow.Y); + } + } + } + // Get an url and return the local filename with the path where the url image should be string CGroupHTML::localImageName(const string &url) { @@ -1556,6 +1610,10 @@ namespace NLGUI _Style.TextColor = LinkColor; _Style.Underlined = true; _Style.GlobalColor = LinkColorGlobalColor; + _Style.BackgroundColor.A = 0; + _Style.BackgroundColorOver.A = 0; + _Style.Width = -1; + _Style.Height = -1; if (present[HTML_A_STYLE] && value[HTML_A_STYLE]) getStyleParams(value[HTML_A_STYLE], _Style); @@ -1956,6 +2014,13 @@ namespace NLGUI _Style.FontSize = TextFontSize; _Style.FontWeight = FONT_WEIGHT_NORMAL; _Style.FontOblique = false; + _Style.TextShadow = STextShadow(true); + _Style.Width = -1; + _Style.Height = -1; + // by default background texture is transparent, + // using alpha value to decide if to change it to 'blank.tga' for coloring + _Style.BackgroundColor.A = 0; + _Style.BackgroundColorOver.A = 0; // Global color flag if (present[MY_HTML_INPUT_GLOBAL_COLOR]) @@ -2058,6 +2123,8 @@ namespace NLGUI } ctrlButton->setText(ucstring::makeFromUtf8(text)); + + setTextButtonStyle(ctrlButton, _Style); } getParagraph()->addChild (buttonGroup); paragraphChange (); @@ -2226,6 +2293,7 @@ namespace NLGUI sb->setMinH(style.Height); sb->setMaxVisibleLine(size); + sb->setFontSize(style.FontSize); } entry.SelectBox = sb; @@ -2234,6 +2302,14 @@ namespace NLGUI { CDBGroupComboBox *cb = addComboBox(DefaultFormSelectGroup, name.c_str()); entry.ComboBox = cb; + + if (cb) + { + // create view text + cb->updateCoords(); + if (cb->getViewText()) + setTextStyle(cb->getViewText(), style); + } } _Forms.back().Entries.push_back (entry); } @@ -2460,6 +2536,9 @@ namespace NLGUI _Style.FontOblique = false; _Style.FontSize = TextFontSize; _Style.TextShadow = STextShadow(true); + _Style.Width = -1; + _Style.Height = -1; + _Style.BackgroundColor.A = 0; if (present[MY_HTML_TEXTAREA_STYLE] && value[MY_HTML_TEXTAREA_STYLE]) getStyleParams(value[MY_HTML_TEXTAREA_STYLE], _Style); @@ -4494,6 +4573,8 @@ namespace NLGUI // Translate the tooltip ctrlButton->setDefaultContextHelp(ucstring::makeFromUtf8(getLinkTitle())); ctrlButton->setText(tmpStr); + + setTextButtonStyle(ctrlButton, _Style); } getParagraph()->addChild (buttonGroup); paragraphChange (); @@ -4516,23 +4597,10 @@ namespace NLGUI } } newLink->setText(tmpStr); - newLink->setColor(_Style.TextColor); - newLink->setFontName(_Style.FontFamily); - newLink->setFontSize(_Style.FontSize); - newLink->setEmbolden(embolden); - newLink->setOblique(_Style.FontOblique); - newLink->setUnderlined(_Style.Underlined); - newLink->setStrikeThrough(_Style.StrikeThrough); newLink->setMultiLineSpace((uint)((float)(_Style.FontSize)*LineSpaceFontFactor)); newLink->setMultiLine(true); newLink->setModulateGlobalColor(_Style.GlobalColor); - if (_Style.TextShadow.Enabled) - { - newLink->setShadow(true); - newLink->setShadowColor(_Style.TextShadow.Color); - newLink->setShadowOutline(_Style.TextShadow.Outline); - newLink->setShadowOffset(_Style.TextShadow.X, _Style.TextShadow.Y); - } + setTextStyle(newLink, _Style); // newLink->setLineAtBottom (true); registerAnchor(newLink); @@ -4638,6 +4706,10 @@ namespace NLGUI _CurrentViewLink = NULL; { + // override cols/rows values from style + if (_Style.Width > 0) cols = _Style.Width / _Style.FontSize; + if (_Style.Height > 0) rows = _Style.Height / _Style.FontSize; + // Not added ? std::vector > templateParams; templateParams.push_back (std::pair ("w", toString (cols*_Style.FontSize))); @@ -4675,7 +4747,18 @@ namespace NLGUI // Set the content CGroupEditBox *eb = dynamic_cast(textArea->getGroup("eb")); if (eb) + { eb->setInputString(decodeHTMLEntities(content)); + if (_Style.BackgroundColor.A > 0) + { + CViewBitmap *bg = dynamic_cast(eb->getView("bg")); + if (bg) + { + bg->setTexture("blank.tga"); + bg->setColor(_Style.BackgroundColor); + } + } + } textArea->invalidateCoords(); getParagraph()->addChild (textArea); @@ -6327,6 +6410,16 @@ namespace NLGUI // second pass: rest of style for (it=styles.begin(); it != styles.end(); ++it) { + if (it->first == "border") + { + sint32 b; + if (it->second == "none") + style.BorderWidth = 0; + else + if (fromString(it->second, b)) + style.BorderWidth = b; + } + else if (it->first == "font-style") { if (it->second == "inherit") @@ -6581,10 +6674,18 @@ namespace NLGUI if (it->first == "background-color") { if (it->second == "inherit") - style.BackgroundColor = current.backgroundColor; + style.BackgroundColor = current.BackgroundColor; else scanHTMLColor(it->second.c_str(), style.BackgroundColor); } + else + if (it->first == "-ryzom-background-color-over") + { + if (it->second == "inherit") + style.BackgroundColorOver = current.BackgroundColorOver; + else + scanHTMLColor(it->second.c_str(), style.BackgroundColorOver); + } } } diff --git a/code/nel/src/gui/group_menu.cpp b/code/nel/src/gui/group_menu.cpp index 7d002cfd6..d8b3452ed 100644 --- a/code/nel/src/gui/group_menu.cpp +++ b/code/nel/src/gui/group_menu.cpp @@ -2561,6 +2561,12 @@ namespace NLGUI } } + // ------------------------------------------------------------------------------------------------ + void CGroupMenu::setFontSize(uint fontSize) + { + _FontSize = fontSize; + } + // ------------------------------------------------------------------------------------------------ uint CGroupMenu::getNumLine() const { From dafd8cf7f7beceab7cce63a389c40b4d4fe1ab60 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Fri, 15 Mar 2019 15:41:02 +0200 Subject: [PATCH 097/108] Changed: Add font inherit style rule --HG-- branch : develop --- code/nel/src/gui/group_html.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/code/nel/src/gui/group_html.cpp b/code/nel/src/gui/group_html.cpp index d36747a8d..783861f45 100644 --- a/code/nel/src/gui/group_html.cpp +++ b/code/nel/src/gui/group_html.cpp @@ -6381,6 +6381,17 @@ namespace NLGUI // first pass: get font-size for 'em' sizes for (it=styles.begin(); it != styles.end(); ++it) { + if (it->first == "font") + { + if (it->second == "inherit") + { + style.FontSize = current.FontSize; + style.FontFamily = current.FontFamily; + style.FontWeight = current.FontWeight; + style.FontOblique = current.FontOblique; + } + } + else if (it->first == "font-size") { if (it->second == "inherit") From 24767a86dfc26ed4e55e3b7f2cf4f91a58c7508e Mon Sep 17 00:00:00 2001 From: Nimetu Date: Fri, 15 Mar 2019 15:41:02 +0200 Subject: [PATCH 098/108] Fixed: CSS inherit rules --HG-- branch : develop --- code/nel/include/nel/gui/group_html.h | 2 +- code/nel/src/gui/group_html.cpp | 73 +++++++++++++-------------- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/code/nel/include/nel/gui/group_html.h b/code/nel/include/nel/gui/group_html.h index 71d7b9ff0..20584b04e 100644 --- a/code/nel/include/nel/gui/group_html.h +++ b/code/nel/include/nel/gui/group_html.h @@ -796,7 +796,7 @@ namespace NLGUI static TGroupHtmlByUIDMap _GroupHtmlByUID; // read style attribute - void getStyleParams(const std::string &styleString, CStyleParams &style, bool inherit = true); + void getStyleParams(const std::string &styleString, CStyleParams &style, const CStyleParams ¤t); void applyCssMinMax(sint32 &width, sint32 &height, sint32 minw=0, sint32 minh=0, sint32 maxw=0, sint32 maxh=0); // load and render local html file (from bnp for example) diff --git a/code/nel/src/gui/group_html.cpp b/code/nel/src/gui/group_html.cpp index 783861f45..2b4786a79 100644 --- a/code/nel/src/gui/group_html.cpp +++ b/code/nel/src/gui/group_html.cpp @@ -1545,7 +1545,7 @@ namespace NLGUI { case HTML_HTML: if (present[MY_HTML_HTML_STYLE] && value[MY_HTML_HTML_STYLE]) - getStyleParams(value[MY_HTML_HTML_STYLE], _StyleDefault); + getStyleParams(value[MY_HTML_HTML_STYLE], _StyleDefault, _StyleDefault); _Style = _StyleDefault; setBackgroundColor(_Style.BackgroundColor); @@ -1616,7 +1616,7 @@ namespace NLGUI _Style.Height = -1; if (present[HTML_A_STYLE] && value[HTML_A_STYLE]) - getStyleParams(value[HTML_A_STYLE], _Style); + getStyleParams(value[HTML_A_STYLE], _Style, _StyleParams.back()); _A.push_back(true); _Link.push_back (""); @@ -1669,7 +1669,7 @@ namespace NLGUI style = value[MY_HTML_DIV_STYLE]; if (!style.empty()) - getStyleParams(style, _Style); + getStyleParams(style, _Style, _StyleParams.back()); // use generic template system if (_TrustedDomain && !instClass.empty() && instClass == "ryzom-ui-grouptemplate") @@ -1780,7 +1780,7 @@ namespace NLGUI style = value[HTML_BODY_STYLE]; if (!style.empty()) - getStyleParams(style, _Style); + getStyleParams(style, _Style, _StyleParams.back()); CRGBA bgColor = _Style.BackgroundColor; if (present[HTML_BODY_BGCOLOR] && value[HTML_BODY_BGCOLOR]) @@ -1841,7 +1841,7 @@ namespace NLGUI _Style.TextColor = H1Color; _Style.GlobalColor = H1ColorGlobalColor; if (present[MY_HTML_H1_STYLE] && value[MY_HTML_H1_STYLE]) - getStyleParams(value[MY_HTML_H1_STYLE], _Style); + getStyleParams(value[MY_HTML_H1_STYLE], _Style, _StyleParams.back()); } break; case HTML_H2: @@ -1853,7 +1853,7 @@ namespace NLGUI _Style.TextColor = H2Color; _Style.GlobalColor = H2ColorGlobalColor; if (present[MY_HTML_H2_STYLE] && value[MY_HTML_H2_STYLE]) - getStyleParams(value[MY_HTML_H2_STYLE], _Style); + getStyleParams(value[MY_HTML_H2_STYLE], _Style, _StyleParams.back()); } break; case HTML_H3: @@ -1865,7 +1865,7 @@ namespace NLGUI _Style.TextColor = H3Color; _Style.GlobalColor = H3ColorGlobalColor; if (present[MY_HTML_H3_STYLE] && value[MY_HTML_H3_STYLE]) - getStyleParams(value[MY_HTML_H3_STYLE], _Style); + getStyleParams(value[MY_HTML_H3_STYLE], _Style, _StyleParams.back()); } break; case HTML_H4: @@ -1877,7 +1877,7 @@ namespace NLGUI _Style.TextColor = H4Color; _Style.GlobalColor = H4ColorGlobalColor; if (present[MY_HTML_H4_STYLE] && value[MY_HTML_H4_STYLE]) - getStyleParams(value[MY_HTML_H4_STYLE], _Style); + getStyleParams(value[MY_HTML_H4_STYLE], _Style, _StyleParams.back()); } break; case HTML_H5: @@ -1889,7 +1889,7 @@ namespace NLGUI _Style.TextColor = H5Color; _Style.GlobalColor = H5ColorGlobalColor; if (present[MY_HTML_H5_STYLE] && value[MY_HTML_H5_STYLE]) - getStyleParams(value[MY_HTML_H5_STYLE], _Style); + getStyleParams(value[MY_HTML_H5_STYLE], _Style, _StyleParams.back()); } break; case HTML_H6: @@ -1901,7 +1901,7 @@ namespace NLGUI _Style.TextColor = H6Color; _Style.GlobalColor = H6ColorGlobalColor; if (present[MY_HTML_H6_STYLE] && value[MY_HTML_H6_STYLE]) - getStyleParams(value[MY_HTML_H6_STYLE], _Style); + getStyleParams(value[MY_HTML_H6_STYLE], _Style, _StyleParams.back()); } break; case HTML_IMG: @@ -1928,7 +1928,7 @@ namespace NLGUI // width, height from inline css if (present[MY_HTML_IMG_STYLE] && value[MY_HTML_IMG_STYLE]) - getStyleParams(value[MY_HTML_IMG_STYLE], style); + getStyleParams(value[MY_HTML_IMG_STYLE], style, _Style); // Tooltip const char *tooltip = NULL; @@ -2032,7 +2032,7 @@ namespace NLGUI tooltip = value[MY_HTML_INPUT_ALT]; if (present[MY_HTML_INPUT_STYLE] && value[MY_HTML_INPUT_STYLE]) - getStyleParams(value[MY_HTML_INPUT_STYLE], _Style); + getStyleParams(value[MY_HTML_INPUT_STYLE], _Style, _StyleParams.back()); string type = toLower(value[MY_HTML_INPUT_TYPE]); if (type == "image") @@ -2258,7 +2258,6 @@ namespace NLGUI if (!(_Forms.empty())) { CStyleParams style; - style.FontSize = _Style.FontSize; // A select box string name; @@ -2272,7 +2271,7 @@ namespace NLGUI if (present[HTML_SELECT_MULTIPLE] && value[HTML_SELECT_MULTIPLE]) multiple = true; if (present[HTML_SELECT_STYLE] && value[HTML_SELECT_STYLE]) - getStyleParams(value[HTML_SELECT_STYLE], style); + getStyleParams(value[HTML_SELECT_STYLE], style, _Style); CGroupHTML::CForm::CEntry entry; entry.Name = name; @@ -2356,7 +2355,7 @@ namespace NLGUI pushStyle(); if (present[HTML_LI_STYLE] && value[HTML_LI_STYLE]) - getStyleParams(value[HTML_LI_STYLE], _Style); + getStyleParams(value[HTML_LI_STYLE], _Style, _StyleParams.back()); ucstring str; str.fromUtf8(_UL.back().getListMarkerText()); @@ -2379,7 +2378,7 @@ namespace NLGUI newParagraph(PBeginSpace); pushStyle(); if (present[MY_HTML_P_STYLE] && value[MY_HTML_P_STYLE]) - getStyleParams(value[MY_HTML_P_STYLE], _Style); + getStyleParams(value[MY_HTML_P_STYLE], _Style, _StyleParams.back()); } break; case HTML_PRE: @@ -2388,7 +2387,7 @@ namespace NLGUI _Style.FontFamily = "monospace"; if (present[HTML_PRE_STYLE] && value[HTML_PRE_STYLE]) - getStyleParams(value[HTML_PRE_STYLE], _Style); + getStyleParams(value[HTML_PRE_STYLE], _Style, _StyleParams.back()); _PRE.push_back(true); @@ -2416,7 +2415,7 @@ namespace NLGUI if (present[MY_HTML_TABLE_CELLPADDING] && value[MY_HTML_TABLE_CELLPADDING]) fromString(value[MY_HTML_TABLE_CELLPADDING], table->CellPadding); if (present[MY_HTML_TABLE_STYLE] && value[MY_HTML_TABLE_STYLE]) - getStyleParams(value[MY_HTML_TABLE_STYLE], _Style); + getStyleParams(value[MY_HTML_TABLE_STYLE], _Style, _StyleParams.back()); table->setMarginLeft(getIndent()); addHtmlGroup (table, 0); @@ -2446,7 +2445,7 @@ namespace NLGUI } if (present[MY_HTML_TD_STYLE] && value[MY_HTML_TD_STYLE]) - getStyleParams(value[MY_HTML_TD_STYLE], _Style); + getStyleParams(value[MY_HTML_TD_STYLE], _Style, _StyleParams.back()); CGroupTable *table = getTable(); if (table) @@ -2541,7 +2540,7 @@ namespace NLGUI _Style.BackgroundColor.A = 0; if (present[MY_HTML_TEXTAREA_STYLE] && value[MY_HTML_TEXTAREA_STYLE]) - getStyleParams(value[MY_HTML_TEXTAREA_STYLE], _Style); + getStyleParams(value[MY_HTML_TEXTAREA_STYLE], _Style, _StyleParams.back()); // Got one form ? if (!(_Forms.empty())) @@ -2597,7 +2596,7 @@ namespace NLGUI pushStyle(); if (present[MY_HTML_TR_STYLE] && value[MY_HTML_TR_STYLE]) - getStyleParams(value[MY_HTML_TR_STYLE], _Style); + getStyleParams(value[MY_HTML_TR_STYLE], _Style, _StyleParams.back()); } break; case HTML_UL: @@ -2614,7 +2613,7 @@ namespace NLGUI pushStyle(); if (present[HTML_UL_STYLE] && value[HTML_UL_STYLE]) - getStyleParams(value[HTML_UL_STYLE], _Style); + getStyleParams(value[HTML_UL_STYLE], _Style, _StyleParams.back()); break; case HTML_OBJECT: _ObjectType.clear(); @@ -2637,7 +2636,7 @@ namespace NLGUI pushStyle(); if (present[MY_HTML_SPAN_STYLE] && value[MY_HTML_SPAN_STYLE]) - getStyleParams(value[MY_HTML_SPAN_STYLE], _Style); + getStyleParams(value[MY_HTML_SPAN_STYLE], _Style, _StyleParams.back()); } break; case HTML_DEL: @@ -2681,7 +2680,7 @@ namespace NLGUI endParagraph(); pushStyle(); if (present[HTML_GEN_STYLE] && value[HTML_GEN_STYLE]) - getStyleParams(value[HTML_GEN_STYLE], _Style); + getStyleParams(value[HTML_GEN_STYLE], _Style, _StyleParams.back()); } break; case HTML_DT: @@ -2704,7 +2703,7 @@ namespace NLGUI pushStyle(); _Style.FontWeight = FONT_WEIGHT_BOLD; if (present[HTML_GEN_STYLE] && value[HTML_GEN_STYLE]) - getStyleParams(value[HTML_GEN_STYLE], _Style); + getStyleParams(value[HTML_GEN_STYLE], _Style, _StyleParams.back()); if (!_LI) { @@ -2739,7 +2738,7 @@ namespace NLGUI pushStyle(); if (present[HTML_GEN_STYLE] && value[HTML_GEN_STYLE]) - getStyleParams(value[HTML_GEN_STYLE], _Style); + getStyleParams(value[HTML_GEN_STYLE], _Style, _StyleParams.back()); if (!_LI) { @@ -2763,7 +2762,7 @@ namespace NLGUI if (present[HTML_OL_TYPE] && value[HTML_OL_TYPE]) type = value[HTML_OL_TYPE]; if (present[HTML_OL_STYLE] && value[HTML_OL_STYLE]) - getStyleParams(value[HTML_OL_STYLE], _Style); + getStyleParams(value[HTML_OL_STYLE], _Style, _StyleParams.back()); _UL.push_back(HTMLOListElement(start, type)); // if LI is already present @@ -2786,7 +2785,7 @@ namespace NLGUI style.Width = 0; if (present[HTML_HR_STYLE] && value[HTML_HR_STYLE]) - getStyleParams(value[HTML_HR_STYLE], style); + getStyleParams(value[HTML_HR_STYLE], style, _Style); CViewBitmap *bitmap = dynamic_cast(sep->getView("hr")); if (bitmap) @@ -6364,16 +6363,8 @@ namespace NLGUI // style.TextColor; // color: #ABCDEF; // style.Underlined; // text-decoration: underline; text-decoration-line: underline; // style.StrikeThrough; // text-decoration: line-through; text-decoration-line: line-through; - void CGroupHTML::getStyleParams(const std::string &styleString, CStyleParams &style, bool inherit) + void CGroupHTML::getStyleParams(const std::string &styleString, CStyleParams &style, const CStyleParams ¤t) { - const CStyleParams current = _Style; - - if (inherit) - { - style.Underlined = current.Underlined; - style.StrikeThrough = current.StrikeThrough; - } - float tmpf; TStyle styles = parseStyle(styleString); TStyle::iterator it; @@ -6698,6 +6689,14 @@ namespace NLGUI scanHTMLColor(it->second.c_str(), style.BackgroundColorOver); } } + + // if outer element has underline set, then inner element cannot remove it + if (current.Underlined) + style.Underlined = current.Underlined; + + // if outer element has line-through set, then inner element cannot remove it + if (current.StrikeThrough) + style.StrikeThrough = current.StrikeThrough; } // *************************************************************************** From eee4684b0c7db1beb204eb50abce27d5412763a7 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Fri, 15 Mar 2019 15:41:02 +0200 Subject: [PATCH 099/108] Fixed: Invalid window mode switch in some cases. --HG-- branch : develop --- code/ryzom/client/src/init.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/code/ryzom/client/src/init.cpp b/code/ryzom/client/src/init.cpp index b653127f4..d80cfda00 100644 --- a/code/ryzom/client/src/init.cpp +++ b/code/ryzom/client/src/init.cpp @@ -1096,23 +1096,21 @@ void prelogInit() UDriver::CMode mode; - bool forceWindowed1024x768 = true; - if (Driver->getCurrentScreenMode(mode)) { - // if screen mode lower than 1024x768, use same mode in fullscreen - if (mode.Width <= 1024 && mode.Height <= 768) + // use current mode if its smaller than 1024x768 + // mode should be windowed already, but incase its not, use the mode as is + if (mode.Windowed && (mode.Width > 1024 && mode.Height > 768)) { - mode.Windowed = false; - forceWindowed1024x768 = false; + mode.Width = 1024; + mode.Height = 768; } } - - if (forceWindowed1024x768) + else { - mode.Width = 1024; - mode.Height = 768; - mode.Windowed = true; + mode.Width = 1024; + mode.Height = 768; + mode.Windowed = true; } // Disable Hardware Vertex Program. From 4e7d33729742d43c906a72f72d5264ab0b2e8c3c Mon Sep 17 00:00:00 2001 From: Nimetu Date: Wed, 20 Mar 2019 12:25:15 +0200 Subject: [PATCH 100/108] Changed: Only capture mouse for freeview or when button is held and mouse is moving --HG-- branch : develop --- code/ryzom/client/src/input.cpp | 36 +++++++++++++++------------------ code/ryzom/client/src/input.h | 3 --- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/code/ryzom/client/src/input.cpp b/code/ryzom/client/src/input.cpp index bf90d92bd..d554002ed 100644 --- a/code/ryzom/client/src/input.cpp +++ b/code/ryzom/client/src/input.cpp @@ -84,9 +84,6 @@ bool InitMouseWithCursor (bool hardware) MouseHardware = hardware; CViewPointer::setHWMouse( hardware ); - // Update mouse information - UpdateMouse (); - if (InitMouseFirstTime) { InitMouseFirstTime = false; @@ -129,16 +126,6 @@ bool IsMouseCursorHardware () return MouseHardware; } -// ********************************************************************************* -// Set the mouse mode. Call this method once per frame to update window size -void UpdateMouse () -{ - if (!Driver->isSystemCursorCaptured()) - { - DownMouseButtons = 0; - } -} - // ********************************************************************************* // Use this method to toggle the mouse (freelook <- cursor) void SetMouseFreeLook () @@ -160,7 +147,6 @@ void SetMouseFreeLook () pointer->show (false); } } - UpdateMouse (); } } @@ -202,7 +188,6 @@ void SetMouseCursor (bool updatePos) } MouseFreeLook = false; - UpdateMouse (); // Integer coordinates sint ix = (sint)(x*(float)width+0.5f); @@ -253,7 +238,6 @@ void SetMouseCursor (bool updatePos) void SetMouseSpeed (float speed) { MouseCursorSpeed = speed; - UpdateMouse (); } // ********************************************************************************* @@ -261,12 +245,27 @@ void SetMouseSpeed (float speed) void SetMouseAcceleration (uint accel) { MouseCursorAcceleration = accel; - UpdateMouse (); } // ********************************************************************************* void HandleSystemCursorCapture(const CEvent &event) { + static bool mouseCaptured = false; + + // capture on first move event after button is held down or free look is activated + if (event == EventMouseMoveId && !mouseCaptured && (MouseFreeLook || DownMouseButtons != 0)) + { + mouseCaptured = true; + Driver->setCapture(true); + } + + // release when button is released and not in free look + if (mouseCaptured && !MouseFreeLook && DownMouseButtons == 0) + { + mouseCaptured = false; + Driver->setCapture(false); + } + if (event == EventMouseDownId) { CEventMouseDown &em = (CEventMouseDown &) event; @@ -279,8 +278,6 @@ void HandleSystemCursorCapture(const CEvent &event) cursor->setPointerMiddleDown(em.Button == middleButton); cursor->setPointerRightDown(em.Button == rightButton); } - - Driver->setCapture(true); } if (event == EventMouseUpId) @@ -297,7 +294,6 @@ void HandleSystemCursorCapture(const CEvent &event) cursor->setPointerMiddleDown(false); cursor->setPointerRightDown(false); } - Driver->setCapture(false); } } diff --git a/code/ryzom/client/src/input.h b/code/ryzom/client/src/input.h index e7579d375..60288416d 100644 --- a/code/ryzom/client/src/input.h +++ b/code/ryzom/client/src/input.h @@ -44,9 +44,6 @@ bool InitMouseWithCursor (bool hardware); // Is mouse cursor hardware ? bool IsMouseCursorHardware (); -// Set the mouse mode. Call this method once per frame to update window size -void UpdateMouse (); - // Use this method to toggle the mouse (freelook <- cursor) void SetMouseFreeLook (); From f23fa7b0fe287ef7b1b900fee6663f57b8d9c51f Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 28 Mar 2019 14:44:58 +0200 Subject: [PATCH 101/108] Added: const char* version of toLower() --HG-- branch : develop --- code/nel/include/nel/misc/common.h | 1 + code/nel/src/misc/common.cpp | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/code/nel/include/nel/misc/common.h b/code/nel/include/nel/misc/common.h index eb70fce9e..d5d59b130 100644 --- a/code/nel/include/nel/misc/common.h +++ b/code/nel/include/nel/misc/common.h @@ -222,6 +222,7 @@ inline double isValidDouble (double v) * \param str a string to transform to lower case */ +std::string toLower ( const char *str ); std::string toLower ( const std::string &str ); void toLower ( char *str ); char toLower ( const char ch ); // convert only one character diff --git a/code/nel/src/misc/common.cpp b/code/nel/src/misc/common.cpp index 48e6e9845..72f29eb82 100644 --- a/code/nel/src/misc/common.cpp +++ b/code/nel/src/misc/common.cpp @@ -591,6 +591,22 @@ NLMISC_CATEGORISED_COMMAND(nel,stohr, "Convert a second number into an human rea return true; } +std::string toLower(const char *str) +{ + if (!str) return ""; + + uint len = strlen(str); + string res; + res.reserve(len); + for(uint i = 0; i < len; i++) + { + if( (str[i] >= 'A') && (str[i] <= 'Z') ) + res += str[i] - 'A' + 'a'; + else + res += str[i]; + } + return res; +} std::string toLower(const std::string &str) { From 35f5c1b77fe2ddbada642ec01cea9d4aeb870803 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Wed, 10 Apr 2019 00:31:23 +0300 Subject: [PATCH 102/108] Fixed: Mount stays invisible when unmounting while in first person view. --HG-- branch : develop --- code/ryzom/client/src/user_entity.cpp | 31 ++++++++++++++++++++++----- code/ryzom/client/src/user_entity.h | 2 ++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/code/ryzom/client/src/user_entity.cpp b/code/ryzom/client/src/user_entity.cpp index 81a9e5a3a..6d905521c 100644 --- a/code/ryzom/client/src/user_entity.cpp +++ b/code/ryzom/client/src/user_entity.cpp @@ -163,6 +163,7 @@ CUserEntity::CUserEntity() // Your are not on a mount at the beginning. _OnMount = false; + _HiddenMount = CLFECOMMON::INVALID_SLOT; _AnimAttackOn = false; @@ -3186,6 +3187,8 @@ void CUserEntity::viewMode(CUserEntity::TView viewMode, bool changeView) CEntityCL *mount = EntitiesMngr.entity(_Mount); if(mount) mount->displayable(false); + + _HiddenMount = _Mount; } // Change Controls. if( isRiding() ) @@ -3203,11 +3206,14 @@ void CUserEntity::viewMode(CUserEntity::TView viewMode, bool changeView) case ThirdPV: if(changeView) ClientCfg.FPV = false; - if(_Mount != CLFECOMMON::INVALID_SLOT) + + if(_HiddenMount != CLFECOMMON::INVALID_SLOT) { - CEntityCL *mount = EntitiesMngr.entity(_Mount); + CEntityCL *mount = EntitiesMngr.entity(_HiddenMount); if(mount) mount->displayable(true); + + _HiddenMount == CLFECOMMON::INVALID_SLOT; } // Change Controls. UserControls.mode(CUserControls::ThirdMode); @@ -3391,9 +3397,24 @@ void CUserEntity::updateVisualDisplay() if(UserControls.isInternalView() || View.forceFirstPersonView()) { // Hide the mount - CCharacterCL *mount = dynamic_cast(EntitiesMngr.entity(_Mount)); - if(mount) - mount->displayable(false); + if (_Mount != CLFECOMMON::INVALID_SLOT) + { + CCharacterCL *mount = dynamic_cast(EntitiesMngr.entity(_Mount)); + if(mount) + mount->displayable(false); + + _HiddenMount = _Mount; + } + else if (_HiddenMount != CLFECOMMON::INVALID_SLOT) + { + // not on mount anymore, but still in FPV + CCharacterCL *mount = dynamic_cast(EntitiesMngr.entity(_HiddenMount)); + if(mount) + mount->displayable(true); + + _HiddenMount = CLFECOMMON::INVALID_SLOT; + } + // Hide all user body parts. for(uint i=0; i<_Instances.size(); ++i) if(!_Instances[i].Current.empty()) diff --git a/code/ryzom/client/src/user_entity.h b/code/ryzom/client/src/user_entity.h index 8a4a27c16..5149b46e8 100644 --- a/code/ryzom/client/src/user_entity.h +++ b/code/ryzom/client/src/user_entity.h @@ -701,6 +701,8 @@ protected: private: /// TO know if the user is on a mount at the moment. bool _OnMount; + /// Keep track of last hidden mount when switching to FPV mode + CLFECOMMON::TCLEntityId _HiddenMount; /// Is the attack animation is currently playing. bool _AnimAttackOn; /// Current View Mode (First/Third Person View). From ac4fd3f8b23769a4e3e66c84d89712c3477b08de Mon Sep 17 00:00:00 2001 From: Nimetu Date: Wed, 10 Apr 2019 00:31:37 +0300 Subject: [PATCH 103/108] Fixed: Changing font name for already rendered text leading to crash. --HG-- branch : develop --- code/nel/src/gui/view_text.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/code/nel/src/gui/view_text.cpp b/code/nel/src/gui/view_text.cpp index 45adbe245..cf3857102 100644 --- a/code/nel/src/gui/view_text.cpp +++ b/code/nel/src/gui/view_text.cpp @@ -1440,12 +1440,11 @@ namespace NLGUI if (_FontName == name) return; - if (_FontName.length() > 0) - { - if (_Index != 0xFFFFFFFF) - CViewRenderer::getTextContext(_FontName)->erase (_Index); - clearLines(); - } + if (_Index != 0xFFFFFFFF) + CViewRenderer::getTextContext(_FontName)->erase (_Index); + clearLines(); + + resetTextIndex(); _FontName = name; computeFontSize (); From ae019d8cadcc9439a6645317ceec002cdc986af2 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Thu, 11 Apr 2019 19:50:22 +0300 Subject: [PATCH 104/108] Fixed: Memory leak in html parser --HG-- branch : develop --- code/nel/src/gui/group_html_parser.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/code/nel/src/gui/group_html_parser.cpp b/code/nel/src/gui/group_html_parser.cpp index b2a61c4f9..0069fd77d 100644 --- a/code/nel/src/gui/group_html_parser.cpp +++ b/code/nel/src/gui/group_html_parser.cpp @@ -327,6 +327,7 @@ namespace NLGUI nlwarning("html root node failed"); success = false; } + xmlFreeDoc(parser->myDoc); } else { From f997e7396105a02a5dc945ebfbb8fe7be2565757 Mon Sep 17 00:00:00 2001 From: kervala Date: Sun, 14 Apr 2019 11:31:39 +0200 Subject: [PATCH 105/108] Fixed: Pass more system variables to PCH compilation --HG-- branch : develop --- code/CMakeModules/PCHSupport.cmake | 250 +++++++++++++++-------------- 1 file changed, 133 insertions(+), 117 deletions(-) diff --git a/code/CMakeModules/PCHSupport.cmake b/code/CMakeModules/PCHSupport.cmake index 24b4d5a85..e0794ad9d 100644 --- a/code/CMakeModules/PCHSupport.cmake +++ b/code/CMakeModules/PCHSupport.cmake @@ -41,31 +41,49 @@ ENDMACRO() MACRO(PCH_SET_COMPILE_FLAGS _target) SET(PCH_FLAGS) SET(PCH_ARCHS) + SET(PCH_INCLUDES) - SET(_FLAGS) + # Append target for clang if defined + IF(CMAKE_CXX_COMPILER_TARGET) + LIST(APPEND PCH_FLAGS "--target=${CMAKE_CXX_COMPILER_TARGET}") + ENDIF() + + IF(CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN) + LIST(APPEND PCH_FLAGS "--gcc-toolchain=${CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN}") + ENDIF() + + IF(CMAKE_SYSROOT) + LIST(APPEND PCH_FLAGS "--sysroot=${CMAKE_SYSROOT}") + ENDIF() + + IF(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES) + FOREACH(item ${CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES}) + LIST(APPEND PCH_FLAGS "-isystem ${item}") + ENDFOREACH() + ENDIF() # C++ flags - SET(_FLAG ${CMAKE_CXX_FLAGS}) - SEPARATE_ARGUMENTS(_FLAG) + SET(_FLAGS ${CMAKE_CXX_FLAGS}) + SEPARATE_ARGUMENTS(_FLAGS) - LIST(APPEND _FLAGS ${_FLAG}) + LIST(APPEND PCH_FLAGS ${_FLAGS}) # C++ config flags STRING(TOUPPER "${CMAKE_BUILD_TYPE}" _UPPER_BUILD) - SET(_FLAG ${CMAKE_CXX_FLAGS_${_UPPER_BUILD}}) - SEPARATE_ARGUMENTS(_FLAG) + SET(_FLAGS ${CMAKE_CXX_FLAGS_${_UPPER_BUILD}}) + SEPARATE_ARGUMENTS(_FLAGS) - LIST(APPEND _FLAGS ${_FLAG}) + LIST(APPEND PCH_FLAGS ${_FLAGS}) GET_TARGET_PROPERTY(_targetType ${_target} TYPE) SET(_USE_PIC OFF) IF(${_targetType} STREQUAL "SHARED_LIBRARY" OR ${_targetType} STREQUAL "MODULE_LIBRARY") - SET(_FLAG ${CMAKE_SHARED_LIBRARY_CXX_FLAGS}) - SEPARATE_ARGUMENTS(_FLAG) - LIST(APPEND _FLAGS ${_FLAG}) + SET(_FLAGS ${CMAKE_SHARED_LIBRARY_CXX_FLAGS}) + SEPARATE_ARGUMENTS(_FLAGS) + LIST(APPEND PCH_FLAGS ${_FLAGS}) ELSE() GET_TARGET_PROPERTY(_pic ${_target} POSITION_INDEPENDENT_CODE) IF(_pic) @@ -75,7 +93,7 @@ MACRO(PCH_SET_COMPILE_FLAGS _target) GET_DIRECTORY_PROPERTY(DIRINC INCLUDE_DIRECTORIES) FOREACH(item ${DIRINC}) - LIST(APPEND _FLAGS -I"${item}") + LIST(APPEND PCH_INCLUDES "${item}") ENDFOREACH() # NOTE: As cmake files (eg FindQT4) may now use generator expressions around their defines that evaluate @@ -98,14 +116,14 @@ MACRO(PCH_SET_COMPILE_FLAGS _target) ENDFOREACH() ENDIF() - GET_DIRECTORY_PROPERTY(DEFINITIONS DIRECTORY ${CMAKE_SOURCE_DIR} COMPILE_DEFINITIONS) + GET_DIRECTORY_PROPERTY(DEFINITIONS DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMPILE_DEFINITIONS) IF(DEFINITIONS) FOREACH(item ${DEFINITIONS}) APPEND_DEFINITION(GLOBAL_DEFINITIONS ${item}) ENDFOREACH() ENDIF() - GET_DIRECTORY_PROPERTY(DEFINITIONS DIRECTORY ${CMAKE_SOURCE_DIR} COMPILE_DEFINITIONS_${_UPPER_BUILD}) + GET_DIRECTORY_PROPERTY(DEFINITIONS DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMPILE_DEFINITIONS_${_UPPER_BUILD}) IF(DEFINITIONS) FOREACH(item ${DEFINITIONS}) APPEND_DEFINITION(GLOBAL_DEFINITIONS ${item}) @@ -114,22 +132,22 @@ MACRO(PCH_SET_COMPILE_FLAGS _target) GET_TARGET_PROPERTY(oldProps ${_target} COMPILE_FLAGS) IF(oldProps) - SET(_FLAG ${oldProps}) - SEPARATE_ARGUMENTS(_FLAG) - LIST(APPEND _FLAGS ${_FLAG}) + SET(_FLAGS ${oldProps}) + SEPARATE_ARGUMENTS(_FLAGS) + LIST(APPEND PCH_FLAGS ${_FLAGS}) ENDIF() GET_TARGET_PROPERTY(oldPropsBuild ${_target} COMPILE_FLAGS_${_UPPER_BUILD}) IF(oldPropsBuild) - SET(_FLAG ${oldPropsBuild}) - SEPARATE_ARGUMENTS(_FLAG) - LIST(APPEND _FLAGS ${_FLAG}) + SET(_FLAGS ${oldPropsBuild}) + SEPARATE_ARGUMENTS(_FLAGS) + LIST(APPEND PCH_FLAGS ${_FLAGS}) ENDIF() GET_TARGET_PROPERTY(DIRINC ${_target} INCLUDE_DIRECTORIES) IF(DIRINC) FOREACH(item ${DIRINC}) - LIST(APPEND _FLAGS -I"${item}") + LIST(APPEND PCH_INCLUDES "${item}") ENDFOREACH() ENDIF() @@ -147,6 +165,18 @@ MACRO(PCH_SET_COMPILE_FLAGS _target) ENDFOREACH() ENDIF() + GET_TARGET_PROPERTY(OPTIONS ${_target} COMPILE_OPTIONS) + IF(OPTIONS) + SEPARATE_ARGUMENTS(OPTIONS) + LIST(APPEND PCH_FLAGS ${OPTIONS}) + ENDIF() + + GET_TARGET_PROPERTY(OPTIONS ${_target} COMPILE_OPTIONS_${_UPPER_BUILD}) + IF(OPTIONS) + SEPARATE_ARGUMENTS(OPTIONS) + LIST(APPEND PCH_FLAGS ${OPTIONS}) + ENDIF() + GET_TARGET_PROPERTY(_LIBS ${_target} INTERFACE_LINK_LIBRARIES) IF(_LIBS) FOREACH(_LIB ${_LIBS}) @@ -156,7 +186,7 @@ MACRO(PCH_SET_COMPILE_FLAGS _target) IF(_DIRS) FOREACH(item ${_DIRS}) - LIST(APPEND GLOBAL_DEFINITIONS -I"${item}") + LIST(APPEND PCH_INCLUDES "${item}") ENDFOREACH() ENDIF() @@ -185,7 +215,7 @@ MACRO(PCH_SET_COMPILE_FLAGS _target) ENDIF() IF(_USE_PIC) - LIST(APPEND _FLAGS ${CMAKE_CXX_COMPILE_OPTIONS_PIC}) + LIST(APPEND PCH_FLAGS ${CMAKE_CXX_COMPILE_OPTIONS_PIC}) ENDIF() ENDIF() @@ -195,116 +225,81 @@ MACRO(PCH_SET_COMPILE_FLAGS _target) IF(_DIRECTORY_FLAGS) SEPARATE_ARGUMENTS(_DIRECTORY_FLAGS) FOREACH(item ${_DIRECTORY_FLAGS}) - LIST(APPEND _FLAGS "${item}") + LIST(APPEND PCH_FLAGS "${item}") ENDFOREACH() ENDIF() - GET_DIRECTORY_PROPERTY(_DIRECTORY_DEFINITIONS DIRECTORY ${CMAKE_SOURCE_DIR} DEFINITIONS) + GET_DIRECTORY_PROPERTY(_DIRECTORY_DEFINITIONS DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEFINITIONS) IF(_DIRECTORY_DEFINITIONS) SEPARATE_ARGUMENTS(_DIRECTORY_DEFINITIONS) FOREACH(item ${_DIRECTORY_DEFINITIONS}) - LIST(APPEND _FLAGS "${item}") + LIST(APPEND PCH_FLAGS "${item}") ENDFOREACH() ENDIF() ENDIF() IF(CMAKE_CXX11_EXTENSION_COMPILE_OPTION) - LIST(APPEND _FLAGS ${CMAKE_CXX11_EXTENSION_COMPILE_OPTION}) - ENDIF() - - # Format definitions - IF(MSVC) - # Fix path with space - SEPARATE_ARGUMENTS(_FLAGS UNIX_COMMAND "${_FLAGS}") + LIST(APPEND PCH_FLAGS "${CMAKE_CXX11_EXTENSION_COMPILE_OPTION}") ENDIF() # Already in list form and items may contain non-leading spaces that should not be split on - LIST(INSERT _FLAGS 0 "${GLOBAL_DEFINITIONS}") + LIST(APPEND PCH_FLAGS "${GLOBAL_DEFINITIONS}") - IF(CLANG) - # Determining all architectures and get common flags - SET(_ARCH_NEXT) - SET(_XARCH_NEXT) - FOREACH(item ${_FLAGS}) - IF(_ARCH_NEXT) - LIST(FIND PCH_ARCHS ${item} ITEM_FOUND) - IF(ITEM_FOUND EQUAL -1) - LIST(APPEND PCH_ARCHS ${item}) - STRING(TOUPPER "${item}" _UPPER_ARCH) - SET(PCH_ARCH_${_UPPER_ARCH}_FLAGS "-arch" ${item}) - ENDIF() - SET(_ARCH_NEXT OFF) - ELSEIF(_XARCH_NEXT) - SET(_XARCH_NEXT OFF) - ELSE() - IF(item MATCHES "^-arch") - SET(_ARCH_NEXT ON) - ELSEIF(item MATCHES "^-Xarch_") - STRING(REGEX REPLACE "-Xarch_([a-z0-9_]+)" "\\1" item ${item}) - LIST(FIND PCH_ARCHS ${item} ITEM_FOUND) - IF(ITEM_FOUND EQUAL -1) - LIST(APPEND PCH_ARCHS ${item}) - STRING(TOUPPER "${item}" _UPPER_ARCH) - SET(PCH_ARCH_${_UPPER_ARCH}_FLAGS "-arch" ${item}) - ENDIF() - SET(_XARCH_NEXT ON) - ELSE() - LIST(APPEND PCH_FLAGS ${item}) - ENDIF() - ENDIF() - ENDFOREACH() - - # Get architcture specific flags - SET(_XARCH_NEXT) - FOREACH(item ${_FLAGS}) - IF(_XARCH_NEXT) - STRING(TOUPPER "${_XARCH_NEXT}" _UPPER_XARCH) - LIST(APPEND PCH_ARCH_${_UPPER_XARCH}_FLAGS ${item}) - SET(_XARCH_NEXT OFF) - ELSE() - IF(item MATCHES "^-Xarch_") - STRING(SUBSTRING "${item}" 7 -1 _XARCH_NEXT) - ENDIF() - ENDIF() - ENDFOREACH() - - # Remove duplicated architectures - IF(_ARCHS AND PCH_ARCHS) - LIST(REMOVE_DUPLICATES PCH_ARCHS) - ENDIF() + IF(WIN32) + SET(SYSTEM_FLAG "[-/$]") ELSE() - SET(PCH_FLAGS ${_FLAGS}) + SET(SYSTEM_FLAG "[-$]") ENDIF() - IF(PCH_FLAGS) - LIST(REMOVE_ITEM PCH_FLAGS "") - LIST(REMOVE_DUPLICATES PCH_FLAGS) + SET(_FINAL_FLAGS) + SET(_PREVIOUS_FLAG) + + FOREACH(_FLAG ${PCH_FLAGS}) + # If parameter is really a flag (starts with -) + IF(_FLAG MATCHES "^${SYSTEM_FLAG}") + IF(_PREVIOUS_FLAG) + # Append previous flag + LIST(APPEND _FINAL_FLAGS ${_PREVIOUS_FLAG}) + ENDIF() + + SET(_PREVIOUS_FLAG ${_FLAG}) + ELSE() + IF(_PREVIOUS_FLAG) + # Append previous flag and its parameter + # TODO: escape them only if there is an space + LIST(APPEND _FINAL_FLAGS "${_PREVIOUS_FLAG} \"${_FLAG}\"") + SET(_PREVIOUS_FLAG) + ELSE() + # Shouldn't happen + MESSAGE(FATAL_ERROR "No previous flag before ${_FLAG}") + ENDIF() + ENDIF() + ENDFOREACH() + + IF(_PREVIOUS_FLAG) + LIST(APPEND _FINAL_FLAGS ${_PREVIOUS_FLAG}) ENDIF() + + SET(PCH_FLAGS ${_FINAL_FLAGS}) + + # Remove flags that don't work with PCH + LIST(REMOVE_ITEM PCH_FLAGS "-Wa,--noexecstack") + + # Remove all empty parameters + LIST(REMOVE_ITEM PCH_FLAGS "") + + # Remove duplicate parameters + LIST(REMOVE_DUPLICATES PCH_FLAGS) + + # create a command-line string + STRING(REGEX REPLACE ";" " " PCH_FLAGS "${PCH_FLAGS}") + + # and separate arguments + SEPARATE_ARGUMENTS(PCH_FLAGS) ENDMACRO() -MACRO(GET_PDB_FILENAME _out_filename _target) - # determine output directory based on target type - GET_TARGET_PROPERTY(_targetType ${_target} TYPE) - IF(${_targetType} STREQUAL EXECUTABLE) - SET(_targetOutput ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) - ELSEIF(${_targetType} STREQUAL STATIC_LIBRARY) - SET(_targetOutput ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}) - ELSE() - SET(_targetOutput ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) - ENDIF() - - # determine target postfix - STRING(TOUPPER "${CMAKE_BUILD_TYPE}_POSTFIX" _postfix_var_name) - GET_TARGET_PROPERTY(_targetPostfix ${_target} ${_postfix_var_name}) - IF(${_targetPostfix} MATCHES NOTFOUND) - SET(_targetPostfix "") - ENDIF() - - SET(${_out_filename} "${_targetOutput}/${_target}${_targetPostfix}.pdb") -ENDMACRO(GET_PDB_FILENAME) - -MACRO(PCH_SET_COMPILE_COMMAND _inputcpp _compile_FLAGS) +MACRO(PCH_SET_COMPILE_COMMAND _inputcpp _compile_FLAGS _includes) IF(CMAKE_CXX_COMPILER_ARG1) # remove leading space in compiler argument STRING(REGEX REPLACE "^ +" "" pchsupport_compiler_cxx_arg1 ${CMAKE_CXX_COMPILER_ARG1}) @@ -313,19 +308,34 @@ MACRO(PCH_SET_COMPILE_COMMAND _inputcpp _compile_FLAGS) ENDIF() IF(MSVC) - GET_PDB_FILENAME(_PDB_FILE ${_PCH_current_target}) - SET(PCH_COMMAND ${CMAKE_CXX_COMPILER} ${pchsupport_compiler_cxx_arg1} ${_compile_FLAGS} /Yc /Fp"${PCH_OUTPUT}" ${_inputcpp} /Fd"${_PDB_FILE}" /c /Fo"${PCH_OUTPUT}.obj") + GET_INTERMEDIATE_PDB_FULLPATH(${_PCH_current_target} _PDB_FILE) + + SET(PCH_TEMP_CONTENT) + + FOREACH(_include ${_includes}) + SET(PCH_TEMP_CONTENT "${PCH_TEMP_CONTENT} -I \"${_include}\"") + ENDFOREACH() + + SET(PCH_TEMP_FILE ${CMAKE_CURRENT_BINARY_DIR}/pch_command.txt) + FILE(WRITE ${PCH_TEMP_FILE} "${PCH_TEMP_CONTENT}") + + SET(PCH_COMMAND ${CMAKE_CXX_COMPILER} /nologo @${PCH_TEMP_FILE} ${pchsupport_compiler_cxx_arg1} ${_compile_FLAGS} /Yc /Fp"${PCH_OUTPUT}" ${_inputcpp} /Fd"${_PDB_FILE}" /c /Fo"${PCH_OUTPUT}.obj") # Ninja PCH Support # http://public.kitware.com/pipermail/cmake-developers/2012-March/003653.html SET_SOURCE_FILES_PROPERTIES(${_inputcpp} PROPERTIES OBJECT_OUTPUTS "${PCH_OUTPUT}.obj") ELSE() SET(HEADER_FORMAT "c++-header") - SET(_FLAGS "") + SET(_FLAGS) IF(APPLE) SET(HEADER_FORMAT "objective-${HEADER_FORMAT}") - SET(_FLAGS ${OBJC_FLAGS}) + LIST(APPEND _FLAGS ${OBJC_FLAGS}) ENDIF() + + FOREACH(_include ${_includes}) + LIST(APPEND _FLAGS -I "${_include}") + ENDFOREACH() + SET(PCH_COMMAND ${CMAKE_CXX_COMPILER} ${pchsupport_compiler_cxx_arg1} ${_compile_FLAGS} ${_FLAGS} -x ${HEADER_FORMAT} -o ${PCH_OUTPUT} -c ${PCH_INPUT}) ENDIF() ENDMACRO() @@ -467,7 +477,7 @@ MACRO(ADD_PRECOMPILED_HEADER _targetName _inputh _inputcpp) PCH_SET_PRECOMPILED_HEADER_OUTPUT(${_targetName} ${_inputh} ${_ARCH} "") LIST(APPEND PCH_OUTPUTS ${PCH_OUTPUT}) - PCH_SET_COMPILE_COMMAND(${_inputcpp} "${PCH_ARCH_${_UPPER_ARCH}_FLAGS};${PCH_FLAGS}") + PCH_SET_COMPILE_COMMAND(${_inputcpp} "${PCH_ARCH_${_UPPER_ARCH}_FLAGS};${PCH_FLAGS}" "${PCH_INCLUDES}") PCH_CREATE_TARGET(${_targetName} ${_targetName}_pch_${_ARCH}) ADD_PRECOMPILED_HEADER_TO_TARGET_ARCH(${_targetName} ${_ARCH}) @@ -476,7 +486,7 @@ MACRO(ADD_PRECOMPILED_HEADER _targetName _inputh _inputcpp) PCH_SET_PRECOMPILED_HEADER_OUTPUT(${_targetName} ${_inputh} "" "") LIST(APPEND PCH_OUTPUTS ${PCH_OUTPUT}) - PCH_SET_COMPILE_COMMAND(${_inputcpp} "${PCH_FLAGS}") + PCH_SET_COMPILE_COMMAND(${_inputcpp} "${PCH_FLAGS}" "${PCH_INCLUDES}") PCH_CREATE_TARGET(${_targetName} ${_targetName}_pch) ENDIF() @@ -525,7 +535,13 @@ MACRO(ADD_NATIVE_PRECOMPILED_HEADER _targetName _inputh _inputcpp) SET_TARGET_PROPERTIES(${_targetName} PROPERTIES XCODE_ATTRIBUTE_GCC_PRECOMPILE_PREFIX_HEADER "YES") ELSE() #Fallback to the "old" precompiled suppport - ADD_PRECOMPILED_HEADER(${_targetName} ${_inputh} ${_inputcpp}) + IF(CMAKE_OSX_ARCHITECTURES AND TARGETS_COUNT GREATER 1) + FOREACH(_ARCH ${CMAKE_OSX_ARCHITECTURES}) + ADD_PRECOMPILED_HEADER(${_targetName}_${_ARCH} ${_inputh} ${_inputcpp}) + ENDFOREACH() + ELSE() + ADD_PRECOMPILED_HEADER(${_targetName} ${_inputh} ${_inputcpp}) + ENDIF() ENDIF() IF(TARGET ${_targetName}_static) From 86db1f29aca6087f1d9f82086f90b80d3d3787f3 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sun, 14 Apr 2019 15:01:12 +0300 Subject: [PATCH 106/108] Changed: add text/lua content type for CGroupHTML and Webig notif thread --HG-- branch : develop --- code/nel/src/gui/group_html.cpp | 20 +- code/ryzom/client/client_default.cfg | 2 + code/ryzom/client/src/client_cfg.cpp | 2 + code/ryzom/client/src/client_cfg.h | 1 + .../src/interface_v3/group_html_webig.cpp | 178 +++++++++++------- .../src/interface_v3/group_html_webig.h | 3 + .../src/interface_v3/interface_manager.cpp | 26 +++ .../src/interface_v3/interface_manager.h | 13 ++ 8 files changed, 175 insertions(+), 70 deletions(-) diff --git a/code/nel/src/gui/group_html.cpp b/code/nel/src/gui/group_html.cpp index 2b4786a79..5e0812aaa 100644 --- a/code/nel/src/gui/group_html.cpp +++ b/code/nel/src/gui/group_html.cpp @@ -5682,6 +5682,22 @@ namespace NLGUI // create html code with image url inside and do the request again renderHtmlString(""+_URL+""); } + else if (_TrustedDomain && type.find("text/lua") == 0) + { + setTitle(_TitleString); + + _LuaScript = "\nlocal __CURRENT_WINDOW__=\""+this->_Id+"\" \n"+content; + CLuaManager::getInstance().executeLuaScript(_LuaScript, true); + _LuaScript.clear(); + + _Browsing = false; + _Connecting = false; + + // disable refresh button + clearRefresh(); + // disable redo into this url + _AskedUrl.clear(); + } else { renderHtmlString(content); @@ -5926,7 +5942,9 @@ namespace NLGUI return; // push to redo, pop undo, and set current - _BrowseRedo.push_front(_AskedUrl); + if (!_AskedUrl.empty()) + _BrowseRedo.push_front(_AskedUrl); + _AskedUrl= _BrowseUndo.back(); _BrowseUndo.pop_back(); diff --git a/code/ryzom/client/client_default.cfg b/code/ryzom/client/client_default.cfg index 11bd62b5b..3516e2a2c 100644 --- a/code/ryzom/client/client_default.cfg +++ b/code/ryzom/client/client_default.cfg @@ -595,6 +595,8 @@ HelpPages = "ru=http://forums.ryzom.com/forum/showthread.php?t=29129" }; +// interval in minutes for webig notify thread to run +WebIgNotifInterval = 10; WebIgMainDomain = "app.ryzom.com"; WebIgTrustedDomains = { "api.ryzom.com", "app.ryzom.com" diff --git a/code/ryzom/client/src/client_cfg.cpp b/code/ryzom/client/src/client_cfg.cpp index ea81cd980..2e7531d07 100644 --- a/code/ryzom/client/src/client_cfg.cpp +++ b/code/ryzom/client/src/client_cfg.cpp @@ -434,6 +434,7 @@ CClientConfig::CClientConfig() WebIgMainDomain = "shard.ryzomcore.org"; WebIgTrustedDomains.push_back(WebIgMainDomain); + WebIgNotifInterval = 10; // time in minutes CurlMaxConnections = 2; CurlCABundle.clear(); @@ -1097,6 +1098,7 @@ void CClientConfig::setValues() // WEBIG // READ_STRING_FV(WebIgMainDomain); READ_STRINGVECTOR_FV(WebIgTrustedDomains); + READ_INT_FV(WebIgNotifInterval); READ_INT_FV(CurlMaxConnections); if (ClientCfg.CurlMaxConnections < 0) ClientCfg.CurlMaxConnections = 2; diff --git a/code/ryzom/client/src/client_cfg.h b/code/ryzom/client/src/client_cfg.h index de930a63b..c9c75a152 100644 --- a/code/ryzom/client/src/client_cfg.h +++ b/code/ryzom/client/src/client_cfg.h @@ -312,6 +312,7 @@ struct CClientConfig std::string WebIgMainDomain; std::vector WebIgTrustedDomains; + uint WebIgNotifInterval; // value in minutes for notification thread sint32 CurlMaxConnections; string CurlCABundle; diff --git a/code/ryzom/client/src/interface_v3/group_html_webig.cpp b/code/ryzom/client/src/interface_v3/group_html_webig.cpp index 66cfecdad..ea885f17b 100644 --- a/code/ryzom/client/src/interface_v3/group_html_webig.cpp +++ b/code/ryzom/client/src/interface_v3/group_html_webig.cpp @@ -18,6 +18,7 @@ #include "group_html_webig.h" #include "nel/misc/xml_auto_ptr.h" +#include "nel/gui/lua_manager.h" #include "../client_cfg.h" #include "../user_entity.h" #include "../entities.h" @@ -149,13 +150,21 @@ size_t writeDataFromCurl(void *buffer, size_t size, size_t nmemb, void *pcl) return size*nmemb; } - -struct CWebigNotificationThread : public NLMISC::IRunnable +class CWebigNotificationThread : public NLMISC::IRunnable { +private: CURL *Curl; + bool _Running; + IThread *_Thread; + +public: CWebigNotificationThread() { + _Running = false; + _Thread = NULL; + curl_global_init(CURL_GLOBAL_ALL); + Curl = curl_easy_init(); if(!Curl) return; curl_easy_setopt(Curl, CURLOPT_COOKIEFILE, ""); @@ -173,6 +182,12 @@ struct CWebigNotificationThread : public NLMISC::IRunnable curl_easy_cleanup(Curl); Curl = 0; } + if (_Thread) + { + _Thread->terminate(); + delete _Thread; + _Thread = NULL; + } } void get(const std::string &url) @@ -186,61 +201,24 @@ struct CWebigNotificationThread : public NLMISC::IRunnable curl_easy_getinfo(Curl, CURLINFO_RESPONSE_CODE, &r); //nlwarning("result : '%s'", curlresult.c_str()); - vector notifs; - explode(curlresult, string("|"), notifs); - - // Update the mail notification icon - - uint32 nbmail = 0; - if(!notifs.empty() && fromString(notifs[0], nbmail)) + char *ch; + std::string contentType; + res = curl_easy_getinfo(Curl, CURLINFO_CONTENT_TYPE, &ch); + if (res == CURLE_OK && ch != NULL) { - //nlinfo("nb mail is a number %d", nbmail); - CInterfaceManager *pIM = CInterfaceManager::getInstance(); - if(pIM) - { - CCDBNodeLeaf *_CheckMailNode = NLGUI::CDBManager::getInstance()->getDbProp("UI:VARIABLES:MAIL_WAITING"); - if(_CheckMailNode) - { - _CheckMailNode->setValue32(nbmail==0?0:1); - CInterfaceElement *elm = CWidgetManager::getInstance()->getElementFromId("ui:interface:compass:mail:mail_nb"); - if (elm) - { - CViewText *vt = dynamic_cast(elm); - vt->setText(toString("%d", nbmail)); - } - } - } + contentType = ch; + } + + // "text/lua; charset=utf8" + if (contentType.find("text/lua") == 0) + { + std::string script; + script = "\nlocal __WEBIG_NOTIF__= true\n" + curlresult; + CInterfaceManager::getInstance()->queueLuaScript(script); } else { - nlwarning("this is not a number '%s'", curlresult.c_str()); - } - - // Update the forum notification icon - - uint32 nbforum = 0; - if(notifs.size() > 1 && fromString(notifs[1], nbforum)) - { - //nlinfo("nb forum this is a number %d", nbforum); - CInterfaceManager *pIM = CInterfaceManager::getInstance(); - if(pIM) - { - CCDBNodeLeaf *_CheckForumNode = NLGUI::CDBManager::getInstance()->getDbProp("UI:VARIABLES:FORUM_UPDATED"); - if(_CheckForumNode) - { - _CheckForumNode->setValue32(nbforum==0?0:1); - CInterfaceElement *elm = CWidgetManager::getInstance()->getElementFromId("ui:interface:compass:forum:forum_nb"); - if (elm) - { - CViewText *vt = dynamic_cast(elm); - vt->setText(toString("%d", nbforum)); - } - } - } - } - else - { - nlwarning("this is not a number '%s'", curlresult.c_str()); + nlwarning("Invalid content-type '%s', expected 'text/lua'", contentType.c_str()); } } @@ -257,30 +235,93 @@ struct CWebigNotificationThread : public NLMISC::IRunnable void run() { - // first time, we wait a small amount of time to be sure everything is initialized - nlSleep(1*60*1000); - while (true) + if (ClientCfg.WebIgNotifInterval == 0) { - string url = "http://"+ClientCfg.WebIgMainDomain+"/index.php?app=notif&rnd="+randomString(); + _Running = false; + nlwarning("ClientCfg.WebIgNotifInterval == 0, notification thread not running"); + return; + } + + std::string domain = ClientCfg.WebIgMainDomain; + uint32 ms = ClientCfg.WebIgNotifInterval*60*1000; + + _Running = true; + // first time, we wait a small amount of time to be sure everything is initialized + nlSleep(30*1000); + uint c = 0; + while (_Running) + { + string url = "https://"+domain+"/index.php?app=notif&format=lua&rnd="+randomString(); addWebIGParams(url, true); get(url); - nlSleep(10*60*1000); + + sleepLoop(ms); } } + + void sleepLoop(uint ms) + { + // use smaller sleep time so stopThread() will not block too long + // tick == 100ms + uint32 ticks = ms / 100; + while (_Running && ticks > 0) { + nlSleep(100); + ticks--; + } + } + + void startThread() + { + if (!_Thread) + { + _Thread = IThread::create(this); + nlassert(_Thread != NULL); + _Thread->start(); + nlwarning("WebIgNotification thread started"); + } + else + { + nlwarning("WebIgNotification thread already started"); + } + + } + + void stopThread() + { + _Running = false; + if (_Thread) + { + _Thread->wait(); + delete _Thread; + _Thread = NULL; + nlwarning("WebIgNotification thread stopped"); + } + else + { + nlwarning("WebIgNotification thread already stopped"); + } + } + + bool isRunning() const + { + return _Running; + } }; -void startWebigNotificationThread() +static CWebigNotificationThread webigThread; +void startWebIgNotificationThread() { - static bool startedWebigNotificationThread = false; - if(!startedWebigNotificationThread) + if (!webigThread.isRunning()) { - curl_global_init(CURL_GLOBAL_ALL); - //nlinfo("startStatThread"); - CWebigNotificationThread *webigThread = new CWebigNotificationThread(); - IThread *thread = IThread::create (webigThread); - nlassert (thread != NULL); - thread->start (); - startedWebigNotificationThread = true; + webigThread.startThread(); + } +} + +void stopWebIgNotificationThread() +{ + if (webigThread.isRunning()) + { + webigThread.stopThread(); } } @@ -351,7 +392,6 @@ NLMISC_REGISTER_OBJECT(CViewBase, CGroupHTMLWebIG, std::string, "webig_html"); CGroupHTMLWebIG::CGroupHTMLWebIG(const TCtorParam ¶m) : CGroupHTMLAuth(param) { - startWebigNotificationThread(); } // *************************************************************************** diff --git a/code/ryzom/client/src/interface_v3/group_html_webig.h b/code/ryzom/client/src/interface_v3/group_html_webig.h index 49aac7153..62e1cfedd 100644 --- a/code/ryzom/client/src/interface_v3/group_html_webig.h +++ b/code/ryzom/client/src/interface_v3/group_html_webig.h @@ -20,6 +20,9 @@ #include "nel/misc/types_nl.h" #include "nel/gui/group_html.h" +void startWebIgNotificationThread(); +void stopWebIgNotificationThread(); + /** * Auth HTML group */ diff --git a/code/ryzom/client/src/interface_v3/interface_manager.cpp b/code/ryzom/client/src/interface_v3/interface_manager.cpp index 80c6c552b..6ab3ebe5f 100644 --- a/code/ryzom/client/src/interface_v3/interface_manager.cpp +++ b/code/ryzom/client/src/interface_v3/interface_manager.cpp @@ -134,6 +134,8 @@ using namespace NLGUI; #include "user_agent.h" #include "../item_group_manager.h" +#include "group_html_webig.h" + using namespace NLMISC; namespace NLGUI @@ -1050,6 +1052,8 @@ void CInterfaceManager::initInGame() { displaySystemInfo(CI18N::get("uiLogTurnedOff")); } + + startWebIgNotificationThread(); } // ------------------------------------------------------------------------------------------------ @@ -1303,6 +1307,7 @@ void CInterfaceManager::uninitInGame0 () // ------------------------------------------------------------------------------------------------ void CInterfaceManager::uninitInGame1 () { + stopWebIgNotificationThread(); // release Bar Manager (HP, SAP etc... Bars) CBarManager::getInstance()->releaseInGame(); @@ -1464,6 +1469,9 @@ void CInterfaceManager::updateFrameEvents() H_AUTO_USE ( RZ_Client_Update_Frame_Events ) + // lua scripts from different thread + flushScriptQueue(); + flushDebugWindow(); // Handle anims done in 2 times because some AH can add or remove anims @@ -3483,6 +3491,24 @@ void CInterfaceManager::notifyForumUpdated() _CheckForumNode->setValue32(1); } +void CInterfaceManager::queueLuaScript(const std::string &script) +{ + CAutoMutex autoMutex(_ScriptQueueMutex); + + _ScriptQueue.push(script); +} + +void CInterfaceManager::flushScriptQueue() +{ + CAutoMutex autoMutex(_ScriptQueueMutex); + + while(!_ScriptQueue.empty()) + { + CLuaManager::getInstance().executeLuaScript(_ScriptQueue.front()); + _ScriptQueue.pop(); + } +} + // *************************************************************************** void CInterfaceManager::resetTextIndex() diff --git a/code/ryzom/client/src/interface_v3/interface_manager.h b/code/ryzom/client/src/interface_v3/interface_manager.h index 488dbe0a5..c6fda4380 100644 --- a/code/ryzom/client/src/interface_v3/interface_manager.h +++ b/code/ryzom/client/src/interface_v3/interface_manager.h @@ -19,8 +19,11 @@ #ifndef NL_INTERFACE_MANAGER_H #define NL_INTERFACE_MANAGER_H +#include + #include "nel/misc/types_nl.h" #include "nel/misc/cdb_manager.h" +#include "nel/misc/mutex.h" #include "nel/3d/u_texture.h" #include "nel/3d/u_text_context.h" #include "nel/gui/interface_group.h" @@ -427,6 +430,10 @@ public: void notifyMailAvailable(); void notifyForumUpdated(); + /** Queue up lua script to be run on next frame update + */ + void queueLuaScript(const std::string &script); + /** Return true if 12-hour clock should be used */ static bool use12hClock(); @@ -570,6 +577,12 @@ private: NLMISC::CCDBNodeLeaf *_CheckForumNode; sint64 _UpdateWeatherTime; + // WebIG notify thread is pushing lua code here + std::queue _ScriptQueue; + NLMISC::CMutex _ScriptQueueMutex; + + void flushScriptQueue(); + // @} /** This is the GLOBAL Action counter used to synchronize some systems (including INVENTORY) with the server. From 1fde33cf8fcd7c716b97439f193cc4eb9efb1680 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sun, 14 Apr 2019 15:01:18 +0300 Subject: [PATCH 107/108] Added: Allow to set webig window title from lua --HG-- branch : develop --- code/nel/include/nel/gui/group_html.h | 3 +++ code/nel/src/gui/group_html.cpp | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/code/nel/include/nel/gui/group_html.h b/code/nel/include/nel/gui/group_html.h index 20584b04e..55daa3c52 100644 --- a/code/nel/include/nel/gui/group_html.h +++ b/code/nel/include/nel/gui/group_html.h @@ -309,6 +309,7 @@ namespace NLGUI REFLECT_LUA_METHOD("renderHtml", luaRenderHtml) REFLECT_STRING("url", getURL, setURL) REFLECT_FLOAT("timeout", getTimeout, setTimeout) + REFLECT_STRING("title", getTitle, setTitle) REFLECT_EXPORT_END protected : @@ -407,6 +408,8 @@ namespace NLGUI // Set the title void setTitle (const ucstring &title); + void setTitle (const std::string &title); + std::string getTitle() const; // Lookup a url in local file system bool lookupLocalFile (std::string &result, const char *url, bool isUrl); diff --git a/code/nel/src/gui/group_html.cpp b/code/nel/src/gui/group_html.cpp index 5e0812aaa..0bdebf865 100644 --- a/code/nel/src/gui/group_html.cpp +++ b/code/nel/src/gui/group_html.cpp @@ -5153,6 +5153,25 @@ namespace NLGUI } } + void CGroupHTML::setTitle(const std::string &title) + { + ucstring uctitle; + uctitle.fromUtf8(title); + + _TitleString.clear(); + if(!_TitlePrefix.empty()) + { + _TitleString = _TitlePrefix + " - "; + } + _TitleString += uctitle; + + setTitle(_TitleString); + } + + std::string CGroupHTML::getTitle() const { + return _TitleString.toUtf8(); + }; + // *************************************************************************** bool CGroupHTML::lookupLocalFile (string &result, const char *url, bool isUrl) From 9254e794e1ee3fd2687f1c2e207e24e227babb1e Mon Sep 17 00:00:00 2001 From: Nimetu Date: Sun, 14 Apr 2019 15:01:18 +0300 Subject: [PATCH 108/108] Fixed: use-after-free on deleted mouse pointer --HG-- branch : develop --- code/ryzom/client/src/interface_v3/interface_manager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/ryzom/client/src/interface_v3/interface_manager.cpp b/code/ryzom/client/src/interface_v3/interface_manager.cpp index 6ab3ebe5f..5593a365f 100644 --- a/code/ryzom/client/src/interface_v3/interface_manager.cpp +++ b/code/ryzom/client/src/interface_v3/interface_manager.cpp @@ -1381,6 +1381,8 @@ void CInterfaceManager::uninitInGame1 () reset(); CInterfaceLink::removeAllLinks(); + CWidgetManager::getInstance()->setPointer( NULL ); + // Release DDX manager, before DB remove CDDXManager::getInstance()->release();