diff --git a/data/fonts/Inconsolata/Inconsolata-Regular.ttf b/data/fonts/Inconsolata/Inconsolata-Regular.ttf new file mode 100644 index 0000000000..592ccd2007 Binary files /dev/null and b/data/fonts/Inconsolata/Inconsolata-Regular.ttf differ diff --git a/include/openspace/interaction/luaconsole.h b/include/openspace/interaction/luaconsole.h index 77beaf3109..09ff1c1bf8 100644 --- a/include/openspace/interaction/luaconsole.h +++ b/include/openspace/interaction/luaconsole.h @@ -28,12 +28,19 @@ #include #include #include +#include #include #include #include #include +namespace ghoul { +namespace opengl { + class ProgramObject; +} // namespace opengl +} // namespace ghoul + namespace openspace { class LuaConsole : public properties::PropertyOwner { @@ -46,7 +53,9 @@ public: bool keyboardCallback(Key key, KeyModifier modifier, KeyAction action); void charCallback(unsigned int codepoint, KeyModifier modifier); + void update(); void render(); + float currentHeight() const; private: void parallelConnectionChanged(const ParallelConnection::Status& status); @@ -55,6 +64,14 @@ private: properties::BoolProperty _isVisible; properties::BoolProperty _remoteScripting; + properties::Vec4Property _backgroundColor; + properties::Vec4Property _highlightColor; + properties::Vec4Property _separatorColor; + properties::Vec4Property _entryTextColor; + properties::Vec4Property _historyTextColor; + properties::IntProperty _historyLength; + + size_t _inputPosition; std::vector _commandsHistory; size_t _activeCommand; @@ -65,6 +82,17 @@ private: bool hasInitialValue; std::string initialValue; } _autoCompleteInfo; + + float _currentHeight; + float _targetHeight; + float _fullHeight; + + std::shared_ptr _font; + std::shared_ptr _historyFont; + + std::unique_ptr _program; + GLuint _vao; + GLuint _vbo; }; } // namespace openspace diff --git a/include/openspace/properties/property.h b/include/openspace/properties/property.h index 54e7a07532..709656fc6b 100644 --- a/include/openspace/properties/property.h +++ b/include/openspace/properties/property.h @@ -292,6 +292,18 @@ public: */ void setReadOnly(bool state); + /** + * Default view options that can be used in the Property::setViewOption method. The + * values are: Property::ViewOptions::Color = color, + * Property::ViewOptions::LightPosition = lightPosition, + * Property::ViewOptions::PowerScaledScalar = powerScaledScalar, and + * Property::ViewOptions::PowerScaledCoordinate = powerScaledCoordinate. + */ + struct ViewOptions { + static const char* Color; + static const char* LightPosition; + }; + /** * This method allows the developer to give hints to the GUI about different * representations for the GUI. The same Property (for example Vec4Property) can be @@ -307,18 +319,14 @@ public: void setViewOption(std::string option, bool value = true); /** - * Default view options that can be used in the Property::setViewOption method. The - * values are: Property::ViewOptions::Color = color, - * Property::ViewOptions::LightPosition = lightPosition, - * Property::ViewOptions::PowerScaledScalar = powerScaledScalar, and - * Property::ViewOptions::PowerScaledCoordinate = powerScaledCoordinate. + * This method returns the state of a \p option hint. See Property::ViewOptions for a + * default list of possible options. As these are only hints, the GUI is free to + * ignore any suggestion by the developer. + * \param option The view option that should be retrieved + * \param defaultValue The value that is returned if the \p option was not set + * \return The view option's value */ - struct ViewOptions { - static const char* Color; - static const char* LightPosition; - static const char* PowerScaledScalar; - static const char* PowerScaledCoordinate; - }; + bool viewOption(const std::string& option, bool defaultValue = false) const; /** * Returns the metaData that contains all information for external applications to diff --git a/modules/onscreengui/onscreenguimodule.cpp b/modules/onscreengui/onscreenguimodule.cpp index 051801b4b6..a06c05eb59 100644 --- a/modules/onscreengui/onscreenguimodule.cpp +++ b/modules/onscreengui/onscreenguimodule.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -61,7 +62,8 @@ OnScreenGUIModule::OnScreenGUIModule() &(OsEng.settingsEngine()), &(OsEng.interactionHandler()), &(OsEng.renderEngine()), - &(OsEng.parallelConnection()) + &(OsEng.parallelConnection()), + &(OsEng.console()) }; return res; } diff --git a/modules/onscreengui/src/renderproperties.cpp b/modules/onscreengui/src/renderproperties.cpp index 0e4ad7b3e3..0c7855e279 100644 --- a/modules/onscreengui/src/renderproperties.cpp +++ b/modules/onscreengui/src/renderproperties.cpp @@ -407,13 +407,22 @@ void renderVec3Property(Property* prop, const std::string& ownerName, float max = std::max(std::max(p->maxValue().x, p->maxValue().y), p->maxValue().z); - ImGui::SliderFloat3( - name.c_str(), - glm::value_ptr(value), - min, - max, - "%.5f" - ); + + if (prop->viewOption(Property::ViewOptions::Color)) { + ImGui::ColorEdit3( + name.c_str(), + glm::value_ptr(value) + ); + } + else { + ImGui::SliderFloat3( + name.c_str(), + glm::value_ptr(value), + min, + max, + "%.5f" + ); + } renderTooltip(prop); if (value != p->value()) { @@ -443,13 +452,22 @@ void renderVec4Property(Property* prop, const std::string& ownerName, float max = std::max(std::max(std::max( p->maxValue().x, p->maxValue().y), p->maxValue().z), p->maxValue().w); - ImGui::SliderFloat4( - name.c_str(), - &value.x, - min, - max, - "%.5f" - ); + if (prop->viewOption(Property::ViewOptions::Color)) { + ImGui::ColorEdit4( + name.c_str(), + glm::value_ptr(value) + ); + } + else { + ImGui::SliderFloat4( + name.c_str(), + glm::value_ptr(value), + min, + max, + "%.5f" + ); + } + renderTooltip(prop); if (value != p->value()) { diff --git a/openspace.cfg b/openspace.cfg index 5bba443950..16ee725a07 100644 --- a/openspace.cfg +++ b/openspace.cfg @@ -51,7 +51,8 @@ return { }, Fonts = { Mono = "${FONTS}/Droid_Sans_Mono/DroidSansMono.ttf", - Light = "${FONTS}/Roboto/Roboto-Regular.ttf" + Light = "${FONTS}/Roboto/Roboto-Regular.ttf", + Console = "${FONTS}/Inconsolata/Inconsolata-Regular.ttf" }, Logging = { -- LogLevel = "Trace", diff --git a/shaders/luaconsole.frag b/shaders/luaconsole.frag new file mode 100644 index 0000000000..4389d09921 --- /dev/null +++ b/shaders/luaconsole.frag @@ -0,0 +1,9 @@ +#version __CONTEXT__ + +out vec4 FragColor; + +uniform vec4 color; + +void main() { + FragColor = color; +} diff --git a/shaders/luaconsole.vert b/shaders/luaconsole.vert new file mode 100644 index 0000000000..fcb1bc6710 --- /dev/null +++ b/shaders/luaconsole.vert @@ -0,0 +1,14 @@ +#version __CONTEXT__ + +in vec2 in_position; + +uniform float height; +uniform ivec2 res; + +uniform mat4 ortho; + +void main() { + float y = float(res.y) - in_position.y * height * res.y; + + gl_Position = ortho * vec4(in_position.x * res.x, y, 0.0, 1.0); +} diff --git a/src/engine/openspaceengine.cpp b/src/engine/openspaceengine.cpp index 9a96c49899..118d2f7ea7 100644 --- a/src/engine/openspaceengine.cpp +++ b/src/engine/openspaceengine.cpp @@ -152,6 +152,7 @@ OpenSpaceEngine::OpenSpaceEngine(std::string programName, _globalPropertyNamespace->addPropertySubOwner(_renderEngine.get()); _globalPropertyNamespace->addPropertySubOwner(_windowWrapper.get()); _globalPropertyNamespace->addPropertySubOwner(_parallelConnection.get()); + _globalPropertyNamespace->addPropertySubOwner(_console.get()); FactoryManager::initialize(); FactoryManager::ref().addFactory( @@ -346,7 +347,6 @@ void OpenSpaceEngine::create(int argc, char** argv, FileSys.createCacheManager( absPath("${" + ConfigurationManager::KeyCache + "}"), CacheVersion ); - _engine->_console->initialize(); // Register the provided shader directories ghoul::opengl::ShaderPreprocessor::addIncludePath(absPath("${SHADERS}")); @@ -843,6 +843,8 @@ void OpenSpaceEngine::configureLogging() { void OpenSpaceEngine::initializeGL() { LTRACE("OpenSpaceEngine::initializeGL(begin)"); + _engine->_console->initialize(); + const std::string key = ConfigurationManager::KeyOpenGLDebugContext; if (_configurationManager->hasKey(key)) { ghoul::Dictionary dict = _configurationManager->value(key); @@ -1073,17 +1075,24 @@ void OpenSpaceEngine::render(const glm::mat4& sceneMatrix, const glm::mat4& viewMatrix, const glm::mat4& projectionMatrix) { + + bool isGuiWindow = _windowWrapper->hasGuiWindow() ? _windowWrapper->isGuiWindow() : true; + bool showOverlay = isGuiWindow && _windowWrapper->isMaster() && _windowWrapper->isRegularRendering(); + // @CLEANUP: Replace the two windows by a single call to whether a gui should be + // rendered ---abock + + if (showOverlay) { + _console->update(); + } + LTRACE("OpenSpaceEngine::render(begin)"); _renderEngine->render(sceneMatrix, viewMatrix, projectionMatrix); for (const auto& func : _moduleCallbacks.render) { func(); } - - // @CLEANUP: Replace the two windows by a single call to whether a gui should be - // rendered ---abock - bool showGui = _windowWrapper->hasGuiWindow() ? _windowWrapper->isGuiWindow() : true; - if (showGui && _windowWrapper->isMaster() && _windowWrapper->isRegularRendering()) { + + if (showOverlay) { _renderEngine->renderScreenLog(); _console->render(); } diff --git a/src/interaction/luaconsole.cpp b/src/interaction/luaconsole.cpp index 37ea62df08..a095b7e78d 100644 --- a/src/interaction/luaconsole.cpp +++ b/src/interaction/luaconsole.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -34,6 +35,9 @@ #include #include #include +#include + +#include #include #include @@ -43,6 +47,8 @@ namespace { const int NoAutoComplete = -1; + const int MaximumHistoryLength = 1000; + // A high number is chosen since we didn't have a version number before // any small number might also be equal to the console history length @@ -51,6 +57,17 @@ namespace { const uint64_t CurrentVersion = 0xFEEEFEEE00000001; const openspace::Key CommandInputButton = openspace::Key::GraveAccent; + + const char* FontName = "Console"; + const float EntryFontSize = 14.0f; + const float HistoryFontSize = 11.0f; + + // Additional space between the entry text and the history (in pixels) + const float SeparatorSpace = 30.f; + + // Determines at which speed the console opens. + const float ConsoleOpenSpeed = 2.5; + } // namespace namespace openspace { @@ -59,27 +76,77 @@ LuaConsole::LuaConsole() : properties::PropertyOwner("LuaConsole") , _isVisible("isVisible", "Is Visible", false) , _remoteScripting("remoteScripting", "Remote scripting", false) + , _backgroundColor( + "backgroundColor", + "Background Color", + glm::vec4(21.f / 255.f, 23.f / 255.f, 28.f / 255.f, 0.8f), + glm::vec4(0.f), + glm::vec4(1.f) + ) + , _highlightColor( + "highlightColor", + "Highlight Color", + glm::vec4(1.f, 1.f, 1.f, 0.f), + glm::vec4(0.f), + glm::vec4(1.f) + ) + , _separatorColor( + "separatorColor", + "Separator Color", + glm::vec4(0.4f, 0.4f, 0.4f, 0.f), + glm::vec4(0.f), + glm::vec4(1.f) + ) + , _entryTextColor( + "entryTextColor", + "Entry Text Color", + glm::vec4(1.f, 1.f, 1.f, 1.f), + glm::vec4(0.f), + glm::vec4(1.f) + ) + , _historyTextColor( + "historyTextColor", + "History Text Color", + glm::vec4(1.0f, 1.0f, 1.0f, 0.65f), + glm::vec4(0.f), + glm::vec4(1.f) + ) + , _historyLength("historyLength", "History Length", 13, 0, 100) , _inputPosition(0) , _activeCommand(0) , _autoCompleteInfo({NoAutoComplete, false, ""}) + , _currentHeight(0.f) + , _targetHeight(0.f) + , _fullHeight(0.f) { addProperty(_isVisible); addProperty(_remoteScripting); + + addProperty(_historyLength); + + _backgroundColor.setViewOption(properties::Property::ViewOptions::Color); + addProperty(_backgroundColor); + _highlightColor.setViewOption(properties::Property::ViewOptions::Color); + addProperty(_highlightColor); + _separatorColor.setViewOption(properties::Property::ViewOptions::Color); + addProperty(_separatorColor); + _entryTextColor.setViewOption(properties::Property::ViewOptions::Color); + addProperty(_entryTextColor); + _historyTextColor.setViewOption(properties::Property::ViewOptions::Color); + addProperty(_historyTextColor); } void LuaConsole::initialize() { - std::string filename = FileSys.cacheManager()->cachedFilename( + const std::string filename = FileSys.cacheManager()->cachedFilename( HistoryFile, "", ghoul::filesystem::CacheManager::Persistent::Yes ); - - try { - if (FileSys.fileExists(filename)) { - std::ifstream file; - file.exceptions(std::ofstream::badbit); - file.open(filename, std::ios::binary | std::ios::in); + if (FileSys.fileExists(filename)) { + std::ifstream file(filename, std::ios::binary | std::ios::in); + + if (file.good()) { // Read the number of commands from the history uint64_t version; file.read(reinterpret_cast(&version), sizeof(uint64_t)); @@ -94,8 +161,6 @@ void LuaConsole::initialize() { int64_t nCommands; file.read(reinterpret_cast(&nCommands), sizeof(int64_t)); - // @TODO: Add an upper limit on the number of commands so that the history - // can't grow without bounds for (int64_t i = 0; i < nCommands; ++i) { int64_t length; file.read(reinterpret_cast(&length), sizeof(int64_t)); @@ -105,17 +170,52 @@ void LuaConsole::initialize() { _commandsHistory.emplace_back(std::string(tmp.begin(), tmp.end())); } } - file.close(); } } - catch (std::exception& e) { - LERRORC("LuaConsole", e.what()); - } _commands = _commandsHistory; _commands.push_back(""); _activeCommand = _commands.size() - 1; + _program = ghoul::opengl::ProgramObject::Build( + "Console", + "${SHADERS}/luaconsole.vert", + "${SHADERS}/luaconsole.frag" + ); + + GLfloat data[] = { + 0.f, 0.f, + 1.f, 1.f, + 0.f, 1.f, + + 0.f, 0.f, + 1.f, 0.f, + 1.f, 1.f + }; + + glGenVertexArrays(1, &_vao); + glBindVertexArray(_vao); + glGenBuffers(1, &_vbo); + glBindBuffer(GL_ARRAY_BUFFER, _vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW); + + glEnableVertexAttribArray(0); + glVertexAttribPointer( + 0, + 2, + GL_FLOAT, + GL_FALSE, + 2 * sizeof(GLfloat), + reinterpret_cast(0) + ); + + glBindVertexArray(0); + + _font = OsEng.fontManager().font(FontName, EntryFontSize); + + _historyFont = OsEng.fontManager().font(FontName, HistoryFontSize); + + OsEng.parallelConnection().connectionEvent()->subscribe( "luaConsole", "statusChanged", @@ -127,27 +227,39 @@ void LuaConsole::initialize() { } void LuaConsole::deinitialize() { - std::string filename = FileSys.cacheManager()->cachedFilename( + const std::string filename = FileSys.cacheManager()->cachedFilename( HistoryFile, "", ghoul::filesystem::CacheManager::Persistent::Yes ); - std::ofstream file(filename); - - uint64_t version = CurrentVersion; - file.write(reinterpret_cast(&version), sizeof(uint64_t)); - - int64_t nCommands = _commandsHistory.size(); - file.write(reinterpret_cast(&nCommands), sizeof(int64_t)); - - for (const std::string& s : _commandsHistory) { - int64_t length = s.length(); - file.write(reinterpret_cast(&length), sizeof(int64_t)); - // We don't write the \0 at the end on purpose - file.write(s.c_str(), length); + // We want to limit the command history to a realistic value, so that it doesn't + // grow without bounds + if (_commandsHistory.size() > MaximumHistoryLength) { + _commandsHistory = std::vector( + _commandsHistory.end() - MaximumHistoryLength, + _commandsHistory.end() + ); } + std::ofstream file(filename, std::ios::binary); + if (file.good()) { + uint64_t version = CurrentVersion; + file.write(reinterpret_cast(&version), sizeof(uint64_t)); + + int64_t nCommands = _commandsHistory.size(); + file.write(reinterpret_cast(&nCommands), sizeof(int64_t)); + + for (const std::string& s : _commandsHistory) { + int64_t length = s.length(); + file.write(reinterpret_cast(&length), sizeof(int64_t)); + // We don't write the \0 at the end on purpose + file.write(s.c_str(), length); + } + } + + _program = nullptr; + OsEng.parallelConnection().connectionEvent()->unsubscribe("luaConsole"); } @@ -162,16 +274,20 @@ bool LuaConsole::keyboardCallback(Key key, KeyModifier modifier, KeyAction actio if (_isVisible) { if (_remoteScripting) { _remoteScripting = false; - } else { - _isVisible = false; } - } else { + else { + _isVisible = false; + _commands.back() = ""; + _inputPosition = 0; + } + } + else { _isVisible = true; if (OsEng.parallelConnection().status() == ParallelConnection::Status::Host) { _remoteScripting = true; } } - + return true; } @@ -179,31 +295,52 @@ bool LuaConsole::keyboardCallback(Key key, KeyModifier modifier, KeyAction actio return false; } + if (key == Key::Escape) { + _isVisible = false; + return true; + } + + const bool modifierControl = (modifier == KeyModifier::Control); const bool modifierShift = (modifier == KeyModifier::Shift); // Paste from clipboard - if (modifierControl && (key == Key::V)) { + if (modifierControl && (key == Key::V || key == Key::Y)) { addToCommand(ghoul::clipboardText()); return true; } // Copy to clipboard - if (modifierControl && (key == Key::C)) { + if (modifierControl && key == Key::C) { ghoul::setClipboardText(_commands.at(_activeCommand)); return true; } + // Cut to clipboard + if (modifierControl && key == Key::X) { + ghoul::setClipboardText(_commands.at(_activeCommand)); + _commands.at(_activeCommand).clear(); + _inputPosition = 0; + } + + // Cut part after cursor to clipboard ("Kill") + if (modifierControl && key == Key::K) { + auto here = _commands.at(_activeCommand).begin() + _inputPosition; + auto end = _commands.at(_activeCommand).end(); + ghoul::setClipboardText(std::string(here, end)); + _commands.at(_activeCommand).erase(here, end); + } + // Go to the previous character - if ((key == Key::Left) && (_inputPosition > 0)) { - --_inputPosition; + if (key == Key::Left || (modifierControl && key == Key::B)) { + if (_inputPosition > 0) { + --_inputPosition; + } return true; } // Go to the next character - if (key == Key::Right) { - //&& _inputPosition < _commands.at(_activeCommand).length()) - //++_inputPosition; + if (key == Key::Right || (modifierControl && key == Key::F)) { _inputPosition = std::min( _inputPosition + 1, _commands.at(_activeCommand).length() @@ -245,48 +382,40 @@ bool LuaConsole::keyboardCallback(Key key, KeyModifier modifier, KeyAction actio } return true; } - + // Go to the beginning of command string - if (key == Key::Home) { + if (key == Key::Home || (modifierControl && key == Key::A)) { _inputPosition = 0; return true; } // Go to the end of command string - if (key == Key::End) { + if (key == Key::End || (modifierControl && key == Key::E)) { _inputPosition = _commands.at(_activeCommand).size(); return true; } - if (key == Key::Enter) { - // SHIFT+ENTER == new line - if (modifierShift) { - addToCommand("\n"); - } - // ENTER == run lua script - else { - std::string cmd = _commands.at(_activeCommand); - if (cmd != "") { - using RemoteScripting = scripting::ScriptEngine::RemoteScripting; - OsEng.scriptEngine().queueScript( - cmd, - _remoteScripting ? RemoteScripting::Yes : RemoteScripting::No - ); - - // Only add the current command to the history if it hasn't been - // executed before. We don't want two of the same commands in a row - if (_commandsHistory.empty() || (cmd != _commandsHistory.back())) { - _commandsHistory.push_back(_commands.at(_activeCommand)); - } + if (key == Key::Enter || key == Key::KeypadEnter) { + std::string cmd = _commands.at(_activeCommand); + if (cmd != "") { + using RemoteScripting = scripting::ScriptEngine::RemoteScripting; + OsEng.scriptEngine().queueScript( + cmd, + _remoteScripting ? RemoteScripting::Yes : RemoteScripting::No + ); + + // Only add the current command to the history if it hasn't been + // executed before. We don't want two of the same commands in a row + if (_commandsHistory.empty() || (cmd != _commandsHistory.back())) { + _commandsHistory.push_back(_commands.at(_activeCommand)); } - - // Some clean up after the execution of the command - _commands = _commandsHistory; - _commands.push_back(""); - _activeCommand = _commands.size() - 1; - _inputPosition = 0; - _isVisible = false; } + + // Some clean up after the execution of the command + _commands = _commandsHistory; + _commands.push_back(""); + _activeCommand = _commands.size() - 1; + _inputPosition = 0; return true; } @@ -306,7 +435,7 @@ bool LuaConsole::keyboardCallback(Key key, KeyModifier modifier, KeyAction actio std::sort(allCommands.begin(), allCommands.end()); std::string currentCommand = _commands.at(_activeCommand); - + // Check if it is the first time the tab has been pressed. If so, we need to // store the already entered command so that we can later start the search // from there. We will overwrite the 'currentCommand' thus making the storage @@ -332,7 +461,7 @@ bool LuaConsole::keyboardCallback(Key key, KeyModifier modifier, KeyAction actio std::back_inserter(commandLowerCase), [](char v) { return static_cast(tolower(v)); } ); - + std::string initialValueLowerCase; std::transform( _autoCompleteInfo.initialValue.begin(), @@ -340,11 +469,11 @@ bool LuaConsole::keyboardCallback(Key key, KeyModifier modifier, KeyAction actio std::back_inserter(initialValueLowerCase), [](char v) { return static_cast(tolower(v)); } ); - + bool correctCommand = commandLowerCase.substr(0, fullLength) == initialValueLowerCase; - - if (correctLength && correctCommand && (i > _autoCompleteInfo.lastIndex)){ + + if (correctLength && correctCommand && (i > _autoCompleteInfo.lastIndex)) { // We found our index, so store it _autoCompleteInfo.lastIndex = i; @@ -390,6 +519,11 @@ bool LuaConsole::keyboardCallback(Key key, KeyModifier modifier, KeyAction actio } } + // We want to ignore the function keys as they don't translate to text anyway + if (key >= Key::F1 && key <= Key::F25) { + return false; + } + return true; } @@ -420,98 +554,187 @@ void LuaConsole::charCallback(unsigned int codepoint, KeyModifier modifier) { addToCommand(std::string(1, static_cast(codepoint))); } -void LuaConsole::render() { - const float FontSize = 10.0f; - - if (!_isVisible) { - return; - } - - const int ySize = OsEng.renderEngine().fontResolution().y; - - const float startY = - static_cast(ySize) - 2.0f * FontSize - FontSize * 15.0f * 2.0f;; - - const glm::vec4 red(1, 0, 0, 1); - const glm::vec4 lightBlue(0.4, 0.4, 1, 1); - const glm::vec4 green(0, 1, 0, 1); - const glm::vec4 white(1, 1, 1, 1); - std::shared_ptr font = OsEng.fontManager().font( - "Mono", FontSize +void LuaConsole::update() { + // Compute the height by simulating _historyFont number of lines and checking + // what the bounding box for that text would be. + using namespace ghoul::fontrendering; + size_t nLines = std::min(static_cast(_historyLength), _commandsHistory.size()); + const auto bbox = FontRenderer::defaultRenderer().boundingBox( + *_historyFont, + std::string(nLines, '\n').c_str() ); - using ghoul::fontrendering::RenderFont; + // Update the full height and the target height. + // Add the height of the entry line and space for a separator. + _fullHeight = (bbox.boundingBox.y + EntryFontSize + SeparatorSpace); + _targetHeight = _isVisible ? _fullHeight : 0; - if (_remoteScripting) { - int nClients = OsEng.parallelConnection().nConnections() - 1; - if (nClients == 1) { - RenderFont( - *font, - glm::vec2(15.f, startY + 20.0f), - red, - "Broadcasting script to 1 client" - ); - } else { - RenderFont( - *font, - glm::vec2(15.f, startY + 20.0f), - red, - ("Broadcasting script to " + std::to_string(nClients) + " clients").c_str() - ); - } - RenderFont(*font, glm::vec2(15.f, startY), red, "$"); + const float frametime = static_cast(OsEng.windowWrapper().deltaTime()); + + // Update the current height. + // The current height is the offset that is used to slide + // the console in from the top. + const glm::ivec2 res = OsEng.windowWrapper().currentWindowResolution(); + const glm::vec2 dpiScaling = OsEng.windowWrapper().dpiScaling(); + _currentHeight += (_targetHeight - _currentHeight) * + std::pow(0.98, 1.0 / (ConsoleOpenSpeed / dpiScaling.y * frametime)); + + _currentHeight = std::max(0.0f, _currentHeight); + _currentHeight = std::min(static_cast(res.y), _currentHeight); +} + + +void LuaConsole::render() { + using namespace ghoul::fontrendering; + + // Don't render the console if it's collapsed. + if (_currentHeight < 1.0f) { + return; } - else { - if (OsEng.parallelConnection().isHost()) { - RenderFont( - *font, - glm::vec2(15.f, startY + 20.0f), - lightBlue, - "Local script execution" - ); - } - RenderFont(*font, glm::vec2(15.f, startY), lightBlue, "$"); + + if (_program->isDirty()) { + _program->rebuildFromFile(); } - RenderFont( - *font, - glm::vec2(15.f + FontSize, startY), - white, - "%s", + + const glm::vec2 dpiScaling = OsEng.windowWrapper().dpiScaling(); + const glm::ivec2 res = + glm::vec2(OsEng.windowWrapper().currentWindowResolution()) / dpiScaling; + + + // Render background + glDisable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_DEPTH_TEST); + + _program->activate(); + + _program->setUniform("res", res); + _program->setUniform("color", _backgroundColor); + _program->setUniform("height", _currentHeight / res.y); + _program->setUniform( + "ortho", + glm::ortho( + 0.f, static_cast(res.x), 0.f, static_cast(res.y) + ) + ); + + // Draw the background color + glBindVertexArray(_vao); + glDrawArrays(GL_TRIANGLES, 0, 6); + + // Draw the highlight lines above and below the background + _program->setUniform("color", _highlightColor); + glDrawArrays(GL_LINES, 1, 4); + + // Draw the separator between the current entry box and the history + _program->setUniform("color", _separatorColor); + _program->setUniform("height", _currentHeight / res.y - 2.5f * EntryFontSize / res.y); + glDrawArrays(GL_LINES, 1, 2); + + _program->deactivate(); + + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + + // Render text on top of the background + glm::vec2 inputLocation = glm::vec2( + EntryFontSize / 2.f, + res.y - _currentHeight + EntryFontSize + ); + + // Render the current command + RenderFontCr( + *_font, + inputLocation, + _entryTextColor, + "> %s", _commands.at(_activeCommand).c_str() ); - const size_t n = std::count( - _commands.at(_activeCommand).begin(), - _commands.at(_activeCommand).begin() + _inputPosition, - '\n' - ); - size_t p = _commands.at(_activeCommand).find_last_of('\n', _inputPosition); - size_t linepos = _inputPosition; + // Just offset the ^ marker slightly for a nicer look + inputLocation.y += 3 * dpiScaling.y; - if (n > 0) { - if (p == _inputPosition) { - p = _commands.at(_activeCommand).find_last_of('\n', _inputPosition - 1); - if (p != std::string::npos) { - linepos -= p + 1; - } - else { - linepos = _inputPosition - 1; - } - } - else { - linepos -= p + 1; - } + // Render the ^ marker below the text to show where the current entry point is + RenderFont( + *_font, + inputLocation, + _entryTextColor, + (std::string(_inputPosition + 2, ' ') + "^").c_str() + ); + + glm::vec2 historyInputLocation = glm::vec2( + HistoryFontSize / 2.f, + res.y - HistoryFontSize * 1.5f + _fullHeight - _currentHeight + ); + + // @CPP17: Replace with array_view + std::vector commandSubset; + if (_commandsHistory.size() < static_cast(_historyLength)) { + commandSubset = _commandsHistory; + } + else { + commandSubset = std::vector( + _commandsHistory.end() - _historyLength, + _commandsHistory.end() + ); } - std::stringstream ss; - ss << "%" << linepos + 1 << "s"; - RenderFont( - *font, - glm::vec2(15.f + FontSize * 0.5f, startY - (FontSize) * (n + 1) * 3.0f / 2.0f), - green, - ss.str().c_str(), - "^" - ); + for (const std::string& cmd : commandSubset) { + RenderFontCr( + *_historyFont, + historyInputLocation, + _historyTextColor, + "%s", + cmd.c_str() + ); + } + + // Computes the location for right justified text on the same y height as the entry + auto locationForRightJustifiedText = [&](const std::string& text) { + using namespace ghoul::fontrendering; + + const glm::vec2 loc = glm::vec2( + EntryFontSize / 2.f, + res.y - _currentHeight + EntryFontSize + ); + + const auto bbox = FontRenderer::defaultRenderer().boundingBox( + *_font, text.c_str() + ); + return glm::vec2( + loc.x + res.x - bbox.boundingBox.x - 10.f, + loc.y + ); + }; + + if (_remoteScripting) { + const glm::vec4 red(1, 0, 0, 1); + + ParallelConnection::Status status = OsEng.parallelConnection().status(); + const int nClients = + status != ParallelConnection::Status::Disconnected ? + OsEng.parallelConnection().nConnections() - 1 : + 0; + + const std::string nClientsText = + nClients == 1 ? + "Broadcasting script to 1 client" : + "Broadcasting script to " + std::to_string(nClients) + " clients"; + + const glm::vec2 loc = locationForRightJustifiedText(nClientsText); + RenderFont(*_font, loc, red, nClientsText.c_str()); + } else if (OsEng.parallelConnection().isHost()) { + const glm::vec4 lightBlue(0.4, 0.4, 1, 1); + + const std::string localExecutionText = "Local script execution"; + const glm::vec2 loc = locationForRightJustifiedText(localExecutionText); + RenderFont(*_font, loc, lightBlue, localExecutionText.c_str()); + } +} + +float LuaConsole::currentHeight() const { + return _currentHeight; } void LuaConsole::addToCommand(std::string c) { diff --git a/src/properties/property.cpp b/src/properties/property.cpp index a06c186588..a71331a745 100644 --- a/src/properties/property.cpp +++ b/src/properties/property.cpp @@ -42,8 +42,6 @@ namespace { const char* Property::ViewOptions::Color = "color"; const char* Property::ViewOptions::LightPosition = "lightPosition"; -const char* Property::ViewOptions::PowerScaledCoordinate = "powerScaledCoordinate"; -const char* Property::ViewOptions::PowerScaledScalar = "powerScaledScalar"; const char* Property::IdentifierKey = "Identifier"; const char* Property::NameKey = "Name"; @@ -156,6 +154,12 @@ void Property::setViewOption(std::string option, bool value) { ); } +bool Property::viewOption(const std::string& option, bool defaultValue) const { + bool v = defaultValue; + _metaData.getValue(_metaDataKeyViewPrefix + option, v); + return v; +} + const ghoul::Dictionary& Property::metaData() const { return _metaData; } diff --git a/src/rendering/renderengine.cpp b/src/rendering/renderengine.cpp index c8c17668a5..a742cf1ad3 100644 --- a/src/rendering/renderengine.cpp +++ b/src/rendering/renderengine.cpp @@ -42,6 +42,7 @@ #include #include +#include #include #include #include @@ -831,6 +832,8 @@ void RenderEngine::renderInformation() { //OsEng.windowWrapper().viewportPixelCoordinates().w ); + penPosition.y -= OsEng.console().currentHeight(); + if (_showDate && _fontDate) { penPosition.y -= _fontDate->height(); RenderFontCr(