From 10e783a47230f7b00bd31c168e92dfee8a9c7ac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tur=C3=A1nszki=20J=C3=A1nos?= Date: Mon, 14 Nov 2022 15:09:51 +0100 Subject: [PATCH] gui and editor updates: - play button: execute last script - stop button: kill background script processes - improved multiple scene switching --- .../ScriptingAPI-Documentation.md | 20 +- Editor/ComponentsWindow.cpp | 13 +- Editor/Editor.cpp | 356 ++++++++++++++---- Editor/Editor.h | 58 +-- Editor/OptionsWindow.cpp | 85 +++-- Editor/OptionsWindow.h | 1 - WickedEngine/wiConfig.cpp | 3 + WickedEngine/wiGUI.cpp | 104 ++++- WickedEngine/wiGUI.h | 6 +- WickedEngine/wiVersion.cpp | 2 +- 10 files changed, 481 insertions(+), 167 deletions(-) diff --git a/Content/Documentation/ScriptingAPI-Documentation.md b/Content/Documentation/ScriptingAPI-Documentation.md index 81c5ca380..3ee295e2c 100644 --- a/Content/Documentation/ScriptingAPI-Documentation.md +++ b/Content/Documentation/ScriptingAPI-Documentation.md @@ -678,19 +678,19 @@ Describes an orientation in 3D space.
- UpdateCamera() -- update the camera matrices -- TransformCamera(TransformComponent transform) -- copies the transform's orientation to the camera. Camera matrices are not updated immediately. They will be updated by the Scene::Update() (if the camera is part of the scene), or by manually calling UpdateCamera() +- TransformCamera(TransformComponent transform) -- copies the transform's orientation to the camera, and sets the camera position, look direction and up direction. Camera matrices are not updated immediately. They will be updated by the Scene::Update() (if the camera is part of the scene), or by manually calling UpdateCamera() - GetFOV() : float result -- SetFOV(float value) +- SetFOV(float value) -- Sets the vertical field of view for the camera (value is an angle in radians) - GetNearPlane() : float result -- SetNearPlane(float value) +- SetNearPlane(float value) -- Sets the near plane of the camera, which specifies the rendering cut off near the viewer. Must be a value greater than zero - GetFarPlane() : float result -- SetFarPlane(float value) +- SetFarPlane(float value) -- Sets the far plane (view distance) of the camera - GetFocalDistance() : float result -- SetFocalDistance(float value) +- SetFocalDistance(float value) -- Sets the focal distance (focus distance) of the camera. This is used by depth of field. - GetApertureSize() : float result -- SetApertureSize(float value) +- SetApertureSize(float value) -- Sets the aperture size of the camera. Larger values will make the depth of field effect stronger. - GetApertureShape() : float result -- SetApertureShape(Vector value) +- SetApertureShape(Vector value) -- Sets the aperture shape of camera, used for depth of field effect. The value's `.X` element specifies the horizontal, the `.Y` element specifies the vertical shape. - GetView() : Matrix result - GetProjection() : Matrix result - GetViewProjection() : Matrix result @@ -700,9 +700,9 @@ Describes an orientation in 3D space. - GetPosition() : Vector result - GetLookDirection() : Vector result - GetUpDirection() : Vector result -- SetPosition(Vector value) -- SetLookDirection(Vector value) -- SetUpDirection(Vector value) +- SetPosition(Vector value) -- Sets the position of the camera. `UpdateCamera()` should be used after this to apply the value. +- SetLookDirection(Vector value) -- Sets the look direction of the camera. The value must be a normalized direction `Vector`, relative to the camera position, and also perpendicular to the up direction. `UpdateCamera()` should be used after this to apply the value. This value will also be set if using the `TransformCamera()` function, from the transform's rotation. +- SetUpDirection(Vector value) -- Sets the up direction of the camera. This must be a normalized direction `Vector`, relative to the camera position, and also perpendicular to the look direction. `UpdateCamera()` should be used after this to apply the value. This value will also be set if using the `TransformCamera()` function, from the transform's rotation. #### AnimationComponent - Timer : float diff --git a/Editor/ComponentsWindow.cpp b/Editor/ComponentsWindow.cpp index 3a75a2f19..659dff07e 100644 --- a/Editor/ComponentsWindow.cpp +++ b/Editor/ComponentsWindow.cpp @@ -348,7 +348,16 @@ void ComponentsWindow::Create(EditorComponent* _editor) humanoidWnd.SetVisible(false); terrainWnd.SetVisible(false); - SetSize(editor->optionsWnd.GetSize()); + XMFLOAT2 size = XMFLOAT2(338, 500); + if (editor->main->config.GetSection("layout").Has("components.width")) + { + size.x = editor->main->config.GetSection("layout").GetFloat("components.width"); + } + if (editor->main->config.GetSection("layout").Has("components.height")) + { + size.y = editor->main->config.GetSection("layout").GetFloat("components.height"); + } + SetSize(size); } void ComponentsWindow::Update(float dt) { @@ -363,6 +372,8 @@ void ComponentsWindow::ResizeLayout() const float padding = 4; XMFLOAT2 pos = XMFLOAT2(padding, padding); const float width = GetWidgetAreaSize().x - padding * 2; + editor->main->config.GetSection("layout").Set("components.width", GetSize().x); + editor->main->config.GetSection("layout").Set("components.height", GetSize().y); if (!editor->translator.selected.empty()) { diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp index 03848bf5d..146789155 100644 --- a/Editor/Editor.cpp +++ b/Editor/Editor.cpp @@ -198,8 +198,10 @@ void EditorComponent::ResizeLayout() float screenH = GetLogicalHeight(); optionsWnd.SetPos(XMFLOAT2(0, screenH - optionsWnd.GetScale().y)); + optionsWnd.scale_local = wi::math::Clamp(optionsWnd.scale_local, XMFLOAT3(1, 1, 1), XMFLOAT3(screenW, screenH, 1)); componentsWnd.SetPos(XMFLOAT2(screenW - componentsWnd.GetScale().x, screenH - componentsWnd.GetScale().y)); + componentsWnd.scale_local = wi::math::Clamp(componentsWnd.scale_local, XMFLOAT3(1, 1, 1), XMFLOAT3(screenW, screenH, 1)); aboutLabel.SetSize(XMFLOAT2(screenW / 2.0f, screenH / 1.5f)); aboutLabel.SetPos(XMFLOAT2(screenW / 2.0f - aboutLabel.scale.x / 2.0f, screenH / 2.0f - aboutLabel.scale.y / 2.0f)); @@ -212,11 +214,72 @@ void EditorComponent::Load() // when an icon character is not found in the default font. wi::font::AddFontStyle("FontAwesomeV6", font_awesome_v6, sizeof(font_awesome_v6)); + newSceneButton.Create("+"); + newSceneButton.SetTooltip("New scene"); + newSceneButton.OnClick([&](wi::gui::EventArgs args) { + NewScene(); + }); + GetGUI().AddWidget(&newSceneButton); + + + playButton.Create(ICON_PLAY); + playButton.font.params.shadowColor = wi::Color::Transparent(); + playButton.SetShadowRadius(2); + playButton.SetTooltip("Execute the last used (standalone) script.\nTo use a new script, use the Open button."); + playButton.OnClick([&](wi::gui::EventArgs args) { + if (last_script_path.empty() || !wi::helper::FileExists(last_script_path)) + { + wi::helper::FileDialogParams params; + params.type = wi::helper::FileDialogParams::OPEN; + params.description = ".lua"; + params.extensions.push_back("lua"); + wi::helper::FileDialog(params, [&](std::string fileName) { + wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { + + std::string extension = wi::helper::toUpper(wi::helper::GetExtensionFromFileName(fileName)); + if (!extension.compare("LUA")) + { + last_script_path = fileName; + main->config.Set("last_script_path", last_script_path); + main->config.Commit(); + playButton.SetScriptTip("dofile(\"" + last_script_path + "\")"); + wi::lua::RunFile(fileName); + } + }); + }); + } + else + { + wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { + wi::lua::RunFile(last_script_path); + }); + } + }); + GetGUI().AddWidget(&playButton); + + if (main->config.Has("last_script_path")) + { + last_script_path = main->config.GetText("last_script_path"); + } + playButton.SetScriptTip("dofile(\"" + last_script_path + "\")"); + + + stopButton.Create(ICON_STOP); + stopButton.font.params.shadowColor = wi::Color::Transparent(); + stopButton.SetShadowRadius(2); + stopButton.SetTooltip("Stops every script background processes that are still running."); + stopButton.SetScriptTip("killProcesses()"); + stopButton.OnClick([&](wi::gui::EventArgs args) { + wi::lua::KillProcesses(); + }); + GetGUI().AddWidget(&stopButton); + + saveButton.Create(""); saveButton.font.params.shadowColor = wi::Color::Transparent(); saveButton.SetShadowRadius(2); - saveButton.SetTooltip("Save the current scene to a new file (Ctrl + Shift + S)"); + saveButton.SetTooltip("Save the current scene to a new file (Ctrl + Shift + S)\nYou can also use Ctrl + S to quicksave, without browsing."); saveButton.SetColor(wi::Color(50, 180, 100, 180), wi::gui::WIDGETSTATE::IDLE); saveButton.SetColor(wi::Color(50, 220, 140, 255), wi::gui::WIDGETSTATE::FOCUS); saveButton.OnClick([&](wi::gui::EventArgs args) { @@ -247,6 +310,10 @@ void EditorComponent::Load() std::string extension = wi::helper::toUpper(wi::helper::GetExtensionFromFileName(fileName)); if (!extension.compare("LUA")) { + last_script_path = fileName; + main->config.Set("last_script_path", last_script_path); + main->config.Commit(); + playButton.SetScriptTip("dofile(\"" + last_script_path + "\")"); wi::lua::RunFile(fileName); return; } @@ -323,61 +390,61 @@ void EditorComponent::Load() GetGUI().AddWidget(&openButton); - closeButton.Create(""); - closeButton.SetShadowRadius(2); - closeButton.font.params.shadowColor = wi::Color::Transparent(); - closeButton.SetTooltip("Close the current scene.\nThis will clear everything from the currently selected scene, delete the scene and kill all script processes.\nThis operation cannot be undone!"); - closeButton.SetColor(wi::Color(255, 130, 100, 180), wi::gui::WIDGETSTATE::IDLE); - closeButton.SetColor(wi::Color(255, 200, 150, 255), wi::gui::WIDGETSTATE::FOCUS); - closeButton.OnClick([&](wi::gui::EventArgs args) { + //closeButton.Create(""); + //closeButton.SetShadowRadius(2); + //closeButton.font.params.shadowColor = wi::Color::Transparent(); + //closeButton.SetTooltip("Close the current scene.\nThis will clear everything from the currently selected scene, delete the scene and kill all script processes.\nThis operation cannot be undone!"); + //closeButton.SetColor(wi::Color(255, 130, 100, 180), wi::gui::WIDGETSTATE::IDLE); + //closeButton.SetColor(wi::Color(255, 200, 150, 255), wi::gui::WIDGETSTATE::FOCUS); + //closeButton.OnClick([&](wi::gui::EventArgs args) { - wi::lua::KillProcesses(); - componentsWnd.terrainWnd.terrain_preset = {}; + // wi::lua::KillProcesses(); + // componentsWnd.terrainWnd.terrain_preset = {}; - translator.selected.clear(); - wi::scene::Scene& scene = GetCurrentScene(); - wi::renderer::ClearWorld(scene); - optionsWnd.cameraWnd.SetEntity(INVALID_ENTITY); - componentsWnd.objectWnd.SetEntity(INVALID_ENTITY); - componentsWnd.meshWnd.SetEntity(INVALID_ENTITY, -1); - componentsWnd.lightWnd.SetEntity(INVALID_ENTITY); - componentsWnd.soundWnd.SetEntity(INVALID_ENTITY); - componentsWnd.decalWnd.SetEntity(INVALID_ENTITY); - componentsWnd.envProbeWnd.SetEntity(INVALID_ENTITY); - componentsWnd.materialWnd.SetEntity(INVALID_ENTITY); - componentsWnd.emitterWnd.SetEntity(INVALID_ENTITY); - componentsWnd.hairWnd.SetEntity(INVALID_ENTITY); - componentsWnd.forceFieldWnd.SetEntity(INVALID_ENTITY); - componentsWnd.springWnd.SetEntity(INVALID_ENTITY); - componentsWnd.ikWnd.SetEntity(INVALID_ENTITY); - componentsWnd.transformWnd.SetEntity(INVALID_ENTITY); - componentsWnd.layerWnd.SetEntity(INVALID_ENTITY); - componentsWnd.nameWnd.SetEntity(INVALID_ENTITY); - componentsWnd.animWnd.SetEntity(INVALID_ENTITY); - componentsWnd.scriptWnd.SetEntity(INVALID_ENTITY); - componentsWnd.rigidWnd.SetEntity(INVALID_ENTITY); - componentsWnd.softWnd.SetEntity(INVALID_ENTITY); - componentsWnd.colliderWnd.SetEntity(INVALID_ENTITY); - componentsWnd.hierarchyWnd.SetEntity(INVALID_ENTITY); - componentsWnd.cameraComponentWnd.SetEntity(INVALID_ENTITY); - componentsWnd.expressionWnd.SetEntity(INVALID_ENTITY); - componentsWnd.armatureWnd.SetEntity(INVALID_ENTITY); - componentsWnd.humanoidWnd.SetEntity(INVALID_ENTITY); - componentsWnd.terrainWnd.SetEntity(INVALID_ENTITY); + // translator.selected.clear(); + // wi::scene::Scene& scene = GetCurrentScene(); + // wi::renderer::ClearWorld(scene); + // optionsWnd.cameraWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.objectWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.meshWnd.SetEntity(INVALID_ENTITY, -1); + // componentsWnd.lightWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.soundWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.decalWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.envProbeWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.materialWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.emitterWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.hairWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.forceFieldWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.springWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.ikWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.transformWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.layerWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.nameWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.animWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.scriptWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.rigidWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.softWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.colliderWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.hierarchyWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.cameraComponentWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.expressionWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.armatureWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.humanoidWnd.SetEntity(INVALID_ENTITY); + // componentsWnd.terrainWnd.SetEntity(INVALID_ENTITY); - optionsWnd.RefreshEntityTree(); - ResetHistory(); - GetCurrentEditorScene().path.clear(); + // optionsWnd.RefreshEntityTree(); + // ResetHistory(); + // GetCurrentEditorScene().path.clear(); - wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { - if (scenes.size() > 1) - { - scenes.erase(scenes.begin() + current_scene); - } - SetCurrentScene(std::max(0, current_scene - 1)); - }); - }); - GetGUI().AddWidget(&closeButton); + // wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { + // if (scenes.size() > 1) + // { + // scenes.erase(scenes.begin() + current_scene); + // } + // SetCurrentScene(std::max(0, current_scene - 1)); + // }); + // }); + //GetGUI().AddWidget(&closeButton); logButton.Create(""); @@ -1179,7 +1246,7 @@ void EditorComponent::Update(float dt) main->infoDisplay.colorgrading_helper = false; // Control operations... - if (wi::input::Down(wi::input::KEYBOARD_BUTTON_LCONTROL) || wi::input::Down(wi::input::KEYBOARD_BUTTON_RCONTROL)) + if (!GetGUI().IsTyping() && wi::input::Down(wi::input::KEYBOARD_BUTTON_LCONTROL) || wi::input::Down(wi::input::KEYBOARD_BUTTON_RCONTROL)) { // Color Grading helper if (wi::input::Down((wi::input::BUTTON)'G')) @@ -1357,32 +1424,35 @@ void EditorComponent::Update(float dt) } } - if (wi::input::Press(wi::input::BUTTON('1'))) + if (!GetGUI().IsTyping()) { - translator.isTranslator = !translator.isTranslator; - translator.isScalator = false; - translator.isRotator = false; - optionsWnd.isTranslatorCheckBox.SetCheck(translator.isTranslator); - optionsWnd.isScalatorCheckBox.SetCheck(false); - optionsWnd.isRotatorCheckBox.SetCheck(false); - } - else if (wi::input::Press(wi::input::BUTTON('2'))) - { - translator.isRotator = !translator.isRotator; - translator.isScalator = false; - translator.isTranslator = false; - optionsWnd.isRotatorCheckBox.SetCheck(translator.isRotator); - optionsWnd.isScalatorCheckBox.SetCheck(false); - optionsWnd.isTranslatorCheckBox.SetCheck(false); - } - else if (wi::input::Press(wi::input::BUTTON('3'))) - { - translator.isScalator = !translator.isScalator; - translator.isTranslator = false; - translator.isRotator = false; - optionsWnd.isScalatorCheckBox.SetCheck(translator.isScalator); - optionsWnd.isTranslatorCheckBox.SetCheck(false); - optionsWnd.isRotatorCheckBox.SetCheck(false); + if (wi::input::Press(wi::input::BUTTON('1'))) + { + translator.isTranslator = !translator.isTranslator; + translator.isScalator = false; + translator.isRotator = false; + optionsWnd.isTranslatorCheckBox.SetCheck(translator.isTranslator); + optionsWnd.isScalatorCheckBox.SetCheck(false); + optionsWnd.isRotatorCheckBox.SetCheck(false); + } + else if (wi::input::Press(wi::input::BUTTON('2'))) + { + translator.isRotator = !translator.isRotator; + translator.isScalator = false; + translator.isTranslator = false; + optionsWnd.isRotatorCheckBox.SetCheck(translator.isRotator); + optionsWnd.isScalatorCheckBox.SetCheck(false); + optionsWnd.isTranslatorCheckBox.SetCheck(false); + } + else if (wi::input::Press(wi::input::BUTTON('3'))) + { + translator.isScalator = !translator.isScalator; + translator.isTranslator = false; + translator.isRotator = false; + optionsWnd.isScalatorCheckBox.SetCheck(translator.isScalator); + optionsWnd.isTranslatorCheckBox.SetCheck(false); + optionsWnd.isRotatorCheckBox.SetCheck(false); + } } // Delete @@ -2937,7 +3007,7 @@ void EditorComponent::UpdateTopMenuAnimation() float wid_idle = 40; float wid_focus = wid_idle * 2.5f; float padding = 4; - float lerp = 0.3f; + float lerp = 0.4f; bool fullscreen = main->config.GetBool("fullscreen"); const char* fullscreen_text = fullscreen ? ICON_FA_COMPRESS " Windowed" : ICON_FULLSCREEN " Full screen"; @@ -2947,7 +3017,6 @@ void EditorComponent::UpdateTopMenuAnimation() fullscreenButton.SetText(fullscreenButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? fullscreen_text : fullscreen ? ICON_FA_COMPRESS : ICON_FULLSCREEN); bugButton.SetText(bugButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_BUG " Bug report" : ICON_BUG); logButton.SetText(logButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_BACKLOG " Backlog" : ICON_BACKLOG); - closeButton.SetText(closeButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_CLOSE " Close" : ICON_CLOSE); openButton.SetText(openButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_OPEN " Open" : ICON_OPEN); saveButton.SetText(saveButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_SAVE " Save" : ICON_SAVE); @@ -2956,7 +3025,6 @@ void EditorComponent::UpdateTopMenuAnimation() fullscreenButton.SetSize(XMFLOAT2(wi::math::Lerp(fullscreenButton.GetSize().x, fullscreenButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); bugButton.SetSize(XMFLOAT2(wi::math::Lerp(bugButton.GetSize().x, bugButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); logButton.SetSize(XMFLOAT2(wi::math::Lerp(logButton.GetSize().x, logButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); - closeButton.SetSize(XMFLOAT2(wi::math::Lerp(closeButton.GetSize().x, closeButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); openButton.SetSize(XMFLOAT2(wi::math::Lerp(openButton.GetSize().x, openButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); saveButton.SetSize(XMFLOAT2(wi::math::Lerp(saveButton.GetSize().x, saveButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); @@ -2965,7 +3033,129 @@ void EditorComponent::UpdateTopMenuAnimation() bugButton.SetPos(XMFLOAT2(aboutButton.GetPos().x - bugButton.GetSize().x - padding, 0)); fullscreenButton.SetPos(XMFLOAT2(bugButton.GetPos().x - fullscreenButton.GetSize().x - padding, 0)); logButton.SetPos(XMFLOAT2(fullscreenButton.GetPos().x - logButton.GetSize().x - padding, 0)); - closeButton.SetPos(XMFLOAT2(logButton.GetPos().x - closeButton.GetSize().x - padding, 0)); - openButton.SetPos(XMFLOAT2(closeButton.GetPos().x - openButton.GetSize().x - padding, 0)); + openButton.SetPos(XMFLOAT2(logButton.GetPos().x - openButton.GetSize().x - padding, 0)); saveButton.SetPos(XMFLOAT2(openButton.GetPos().x - saveButton.GetSize().x - padding, 0)); + + + stopButton.SetSize(XMFLOAT2(wid_idle * 0.75f, hei)); + stopButton.SetPos(XMFLOAT2(saveButton.GetPos().x - stopButton.GetSize().x - 20, 0)); + playButton.SetSize(XMFLOAT2(wid_idle * 0.75f, hei)); + playButton.SetPos(XMFLOAT2(stopButton.GetPos().x - playButton.GetSize().x - padding, 0)); + + + float ofs = screenW - 2; + float y = exitButton.GetPos().y + exitButton.GetSize().y + 5; + hei = 18; + wid_idle = 105; + for (int i = 0; i < int(scenes.size()); ++i) + { + auto& editorscene = scenes[i]; + editorscene->tabSelectButton.SetSize(XMFLOAT2(wid_idle, hei)); + editorscene->tabCloseButton.SetSize(XMFLOAT2(hei, hei)); + ofs -= editorscene->tabCloseButton.GetSize().x; + editorscene->tabCloseButton.SetPos(XMFLOAT2(ofs, y)); + ofs -= editorscene->tabSelectButton.GetSize().x; + editorscene->tabSelectButton.SetPos(XMFLOAT2(ofs, y)); + ofs -= 4; + } + newSceneButton.SetSize(XMFLOAT2(hei, hei)); + ofs -= newSceneButton.GetSize().x; + newSceneButton.SetPos(XMFLOAT2(ofs, y)); +} + +void EditorComponent::SetCurrentScene(int index) +{ + current_scene = index; + this->renderPath->scene = &scenes[current_scene].get()->scene; + this->renderPath->camera = &scenes[current_scene].get()->camera; + wi::lua::scene::SetGlobalScene(renderPath->scene); + wi::lua::scene::SetGlobalCamera(renderPath->camera); + optionsWnd.RefreshEntityTree(); + RefreshSceneList(); +} +void EditorComponent::RefreshSceneList() +{ + optionsWnd.themeCombo.SetSelected(optionsWnd.themeCombo.GetSelected()); + for (int i = 0; i < int(scenes.size()); ++i) + { + auto& editorscene = scenes[i]; + if (editorscene->path.empty()) + { + editorscene->tabSelectButton.SetText("Untitled scene"); + editorscene->tabSelectButton.SetTooltip(""); + } + else + { + editorscene->tabSelectButton.SetText(wi::helper::RemoveExtension(wi::helper::GetFileNameFromPath(editorscene->path))); + editorscene->tabSelectButton.SetTooltip(editorscene->path); + } + + editorscene->tabSelectButton.OnClick([this, i](wi::gui::EventArgs args) { + SetCurrentScene(i); + }); + editorscene->tabCloseButton.OnClick([this, i](wi::gui::EventArgs args) { + wi::lua::KillProcesses(); + componentsWnd.terrainWnd.terrain_preset = {}; + + translator.selected.clear(); + wi::scene::Scene& scene = GetCurrentScene(); + wi::renderer::ClearWorld(scene); + optionsWnd.cameraWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.objectWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.meshWnd.SetEntity(wi::ecs::INVALID_ENTITY, -1); + componentsWnd.lightWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.soundWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.decalWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.envProbeWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.materialWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.emitterWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.hairWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.forceFieldWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.springWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.ikWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.transformWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.layerWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.nameWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.animWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.scriptWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.rigidWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.softWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.colliderWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.hierarchyWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.cameraComponentWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.expressionWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.armatureWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.humanoidWnd.SetEntity(wi::ecs::INVALID_ENTITY); + componentsWnd.terrainWnd.SetEntity(wi::ecs::INVALID_ENTITY); + + optionsWnd.RefreshEntityTree(); + ResetHistory(); + scenes[i]->path.clear(); + + wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { + if (scenes.size() > 1) + { + GetGUI().RemoveWidget(&scenes[i]->tabSelectButton); + GetGUI().RemoveWidget(&scenes[i]->tabCloseButton); + scenes.erase(scenes.begin() + i); + } + SetCurrentScene(std::max(0, i - 1)); + }); + }); + } +} +void EditorComponent::NewScene() +{ + int scene_id = int(scenes.size()); + scenes.push_back(std::make_unique()); + auto& editorscene = scenes.back(); + editorscene->tabSelectButton.Create(""); + editorscene->tabCloseButton.Create("X"); + editorscene->tabCloseButton.SetTooltip("Close scene. This operation cannot be undone!"); + GetGUI().AddWidget(&editorscene->tabSelectButton); + GetGUI().AddWidget(&editorscene->tabCloseButton); + SetCurrentScene(scene_id); + RefreshSceneList(); + UpdateTopMenuAnimation(); + optionsWnd.cameraWnd.ResetCam(); } diff --git a/Editor/Editor.h b/Editor/Editor.h index 46140389e..8b0bfe6ea 100644 --- a/Editor/Editor.h +++ b/Editor/Editor.h @@ -23,9 +23,13 @@ class EditorComponent : public wi::RenderPath2D public: Editor* main = nullptr; + wi::gui::Button newSceneButton; + + wi::gui::Button playButton; + wi::gui::Button stopButton; + wi::gui::Button saveButton; wi::gui::Button openButton; - wi::gui::Button closeButton; wi::gui::Button logButton; wi::gui::Button fullscreenButton; wi::gui::Button bugButton; @@ -115,6 +119,8 @@ public: float save_text_alpha = 0; // seconds until save text disappears wi::Color save_text_color = wi::Color::White(); + std::string last_script_path; + struct EditorScene { std::string path; @@ -125,6 +131,8 @@ public: wi::scene::TransformComponent camera_target; wi::vector history; int historyPos = -1; + wi::gui::Button tabSelectButton; + wi::gui::Button tabCloseButton; }; wi::vector> scenes; int current_scene = 0; @@ -132,46 +140,9 @@ public: const EditorScene& GetCurrentEditorScene() const { return *scenes[current_scene].get(); } wi::scene::Scene& GetCurrentScene() { return scenes[current_scene].get()->scene; } const wi::scene::Scene& GetCurrentScene() const { return scenes[current_scene].get()->scene; } - void SetCurrentScene(int index) - { - current_scene = index; - this->renderPath->scene = &scenes[current_scene].get()->scene; - this->renderPath->camera = &scenes[current_scene].get()->camera; - wi::lua::scene::SetGlobalScene(this->renderPath->scene); - wi::lua::scene::SetGlobalCamera(this->renderPath->camera); - optionsWnd.RefreshEntityTree(); - RefreshSceneList(); - } - void RefreshSceneList() - { - optionsWnd.sceneComboBox.ClearItems(); - for (int i = 0; i < int(scenes.size()); ++i) - { - if (scenes[i]->path.empty()) - { - optionsWnd.sceneComboBox.AddItem("Untitled"); - } - else - { - optionsWnd.sceneComboBox.AddItem(wi::helper::RemoveExtension(wi::helper::GetFileNameFromPath(scenes[i]->path))); - } - } - optionsWnd.sceneComboBox.AddItem("[New]"); - optionsWnd.sceneComboBox.SetSelectedWithoutCallback(current_scene); - std::string tooltip = "Choose a scene"; - if (!GetCurrentEditorScene().path.empty()) - { - tooltip += "\nCurrent path: " + GetCurrentEditorScene().path; - } - optionsWnd.sceneComboBox.SetTooltip(tooltip); - } - void NewScene() - { - scenes.push_back(std::make_unique()); - SetCurrentScene(int(scenes.size()) - 1); - RefreshSceneList(); - optionsWnd.cameraWnd.ResetCam(); - } + void SetCurrentScene(int index); + void RefreshSceneList(); + void NewScene(); }; class Editor : public wi::Application @@ -182,4 +153,9 @@ public: wi::config::File config; void Initialize() override; + + ~Editor() + { + config.Commit(); + } }; diff --git a/Editor/OptionsWindow.cpp b/Editor/OptionsWindow.cpp index 0bf0b664e..f24180aec 100644 --- a/Editor/OptionsWindow.cpp +++ b/Editor/OptionsWindow.cpp @@ -392,20 +392,6 @@ void OptionsWindow::Create(EditorComponent* _editor) AddWidget(&materialPickerWnd); - sceneComboBox.Create("Scene: "); - sceneComboBox.OnSelect([&](wi::gui::EventArgs args) { - if (args.iValue >= int(editor->scenes.size())) - { - editor->NewScene(); - } - editor->SetCurrentScene(args.iValue); - }); - sceneComboBox.SetEnabled(true); - sceneComboBox.SetColor(wi::Color(50, 100, 255, 180), wi::gui::IDLE); - sceneComboBox.SetColor(wi::Color(120, 160, 255, 255), wi::gui::FOCUS); - AddWidget(&sceneComboBox); - - saveModeComboBox.Create("Save Mode: "); saveModeComboBox.AddItem("Embed resources " ICON_SAVE_EMBED, (uint64_t)wi::resourcemanager::Mode::ALLOW_RETAIN_FILEDATA); saveModeComboBox.AddItem("No embedding " ICON_SAVE_NO_EMBED, (uint64_t)wi::resourcemanager::Mode::ALLOW_RETAIN_FILEDATA_BUT_DISABLE_EMBEDDING); @@ -473,10 +459,10 @@ void OptionsWindow::Create(EditorComponent* _editor) case Theme::Hacking: editor->main->config.GetSection("options").Set("theme", "Hacking"); theme_color_idle = wi::Color(0, 0, 0, 255); - theme_color_focus = wi::Color(10, 230, 30, 255); + theme_color_focus = wi::Color(0, 160, 60, 255); dark_point = wi::Color(0, 0, 0, 255); - theme.shadow_color = wi::Color(0, 250, 0, 200); - theme.font.color = wi::Color(100, 250, 100, 255); + theme.shadow_color = wi::Color(0, 200, 90, 200); + theme.font.color = wi::Color(0, 200, 90, 255); theme.font.shadow_color = wi::Color::Shadow(); break; } @@ -525,8 +511,8 @@ void OptionsWindow::Create(EditorComponent* _editor) if ((Theme)args.userdata == Theme::Hacking) { - gui.SetColor(wi::Color(0, 200, 0, 255), wi::gui::WIDGET_ID_SLIDER_KNOB_IDLE); - gui.SetColor(wi::Color(0, 200, 0, 255), wi::gui::WIDGET_ID_SCROLLBAR_KNOB_INACTIVE); + gui.SetColor(wi::Color(0, 200, 90, 255), wi::gui::WIDGET_ID_SLIDER_KNOB_IDLE); + gui.SetColor(wi::Color(0, 200, 90, 255), wi::gui::WIDGET_ID_SCROLLBAR_KNOB_INACTIVE); } // customize individual elements: @@ -553,8 +539,51 @@ void OptionsWindow::Create(EditorComponent* _editor) editor->saveButton.sprites[i].params.enableCornerRounding(); editor->saveButton.sprites[i].params.corners_rounding[2].radius = 10; } + for (int i = 0; i < arraysize(editor->playButton.sprites); ++i) + { + editor->playButton.sprites[i].params.enableCornerRounding(); + editor->playButton.sprites[i].params.corners_rounding[2].radius = 40; + editor->stopButton.sprites[i].params.enableCornerRounding(); + editor->stopButton.sprites[i].params.corners_rounding[3].radius = 40; + } + int scene_id = 0; + for (auto& editorscene : editor->scenes) + { + for (int i = 0; i < arraysize(editorscene->tabSelectButton.sprites); ++i) + { + editorscene->tabSelectButton.sprites[i].params.enableCornerRounding(); + editorscene->tabSelectButton.sprites[i].params.corners_rounding[0].radius = 10; + editorscene->tabSelectButton.sprites[i].params.corners_rounding[2].radius = 10; + } + for (int i = 0; i < arraysize(editorscene->tabCloseButton.sprites); ++i) + { + editorscene->tabCloseButton.sprites[i].params.enableCornerRounding(); + editorscene->tabCloseButton.sprites[i].params.corners_rounding[1].radius = 10; + editorscene->tabCloseButton.sprites[i].params.corners_rounding[3].radius = 10; + } + + if (editor->current_scene == scene_id) + { + editorscene->tabSelectButton.sprites[wi::gui::IDLE].params.color = editor->newSceneButton.sprites[wi::gui::FOCUS].params.color; + } + else + { + editorscene->tabSelectButton.sprites[wi::gui::IDLE].params.color = editor->newSceneButton.sprites[wi::gui::IDLE].params.color; + } + editorscene->tabCloseButton.SetColor(wi::Color::Error(), wi::gui::WIDGET_ID_FOCUS); + scene_id++; + } + for (int i = 0; i < arraysize(editor->newSceneButton.sprites); ++i) + { + editor->newSceneButton.sprites[i].params.enableCornerRounding(); + editor->newSceneButton.sprites[i].params.corners_rounding[0].radius = 10; + editor->newSceneButton.sprites[i].params.corners_rounding[1].radius = 10; + editor->newSceneButton.sprites[i].params.corners_rounding[2].radius = 10; + editor->newSceneButton.sprites[i].params.corners_rounding[3].radius = 10; + } editor->componentsWnd.weatherWnd.default_sky_horizon = dark_point; editor->componentsWnd.weatherWnd.default_sky_zenith = theme_color_idle; + editor->componentsWnd.weatherWnd.Update(); if ((Theme)args.userdata == Theme::Bright) { @@ -574,7 +603,16 @@ void OptionsWindow::Create(EditorComponent* _editor) AddWidget(&themeCombo); - SetSize(XMFLOAT2(338, 500)); + XMFLOAT2 size = XMFLOAT2(338, 500); + if (editor->main->config.GetSection("layout").Has("options.width")) + { + size.x = editor->main->config.GetSection("layout").GetFloat("options.width"); + } + if (editor->main->config.GetSection("layout").Has("options.height")) + { + size.y = editor->main->config.GetSection("layout").GetFloat("options.height"); + } + SetSize(size); } void OptionsWindow::Update(float dt) { @@ -590,6 +628,8 @@ void OptionsWindow::ResizeLayout() XMFLOAT2 pos = XMFLOAT2(padding, padding); const float width = GetWidgetAreaSize().x - padding * 2; float x_off = 100; + editor->main->config.GetSection("layout").Set("options.width", GetSize().x); + editor->main->config.GetSection("layout").Set("options.height", GetSize().y); isScalatorCheckBox.SetPos(XMFLOAT2(pos.x + width - isScalatorCheckBox.GetSize().x, pos.y)); isRotatorCheckBox.SetPos(XMFLOAT2(isScalatorCheckBox.GetPos().x - isRotatorCheckBox.GetSize().x - 80, pos.y)); @@ -609,11 +649,6 @@ void OptionsWindow::ResizeLayout() pos.y += cinemaModeCheckBox.GetSize().y; pos.y += padding; - sceneComboBox.SetPos(XMFLOAT2(pos.x + x_off, pos.y)); - sceneComboBox.SetSize(XMFLOAT2(width - x_off - sceneComboBox.GetScale().y - 1, sceneComboBox.GetScale().y)); - pos.y += sceneComboBox.GetSize().y; - pos.y += padding; - saveModeComboBox.SetPos(XMFLOAT2(pos.x + x_off, pos.y)); saveModeComboBox.SetSize(XMFLOAT2(width - x_off - saveModeComboBox.GetScale().y - 1, saveModeComboBox.GetScale().y)); pos.y += saveModeComboBox.GetSize().y; diff --git a/Editor/OptionsWindow.h b/Editor/OptionsWindow.h index 858f4c0ce..760aea775 100644 --- a/Editor/OptionsWindow.h +++ b/Editor/OptionsWindow.h @@ -27,7 +27,6 @@ public: wi::gui::CheckBox otherinfoCheckBox; wi::gui::ComboBox themeCombo; wi::gui::ComboBox saveModeComboBox; - wi::gui::ComboBox sceneComboBox; GraphicsWindow graphicsWnd; CameraWindow cameraWnd; MaterialPickerWindow materialPickerWnd; diff --git a/WickedEngine/wiConfig.cpp b/WickedEngine/wiConfig.cpp index 1061d9b15..1968ba0ca 100644 --- a/WickedEngine/wiConfig.cpp +++ b/WickedEngine/wiConfig.cpp @@ -252,6 +252,7 @@ namespace wi::config if (committed_values[section].count(it.first) == 0) { text += it.first + " = " + it.second + "\n"; + committed_values[section].insert(it.first); } } // Begin new section: @@ -273,6 +274,7 @@ namespace wi::config if (committed_values[this].count(it.first) == 0) { text += it.first + " = " + it.second + "\n"; + committed_values[this].insert(it.first); } } for (auto& it : sections) @@ -289,6 +291,7 @@ namespace wi::config if (committed_values[&it.second].count(it2.first) == 0) { text += it2.first + " = " + it2.second + "\n"; + committed_values[&it.second].insert(it2.first); } } } diff --git a/WickedEngine/wiGUI.cpp b/WickedEngine/wiGUI.cpp index 977319fb3..15a361466 100644 --- a/WickedEngine/wiGUI.cpp +++ b/WickedEngine/wiGUI.cpp @@ -55,6 +55,7 @@ namespace wi::gui // As opposed to click and other interaction types, we don't want to disable scroll on every focused widget // because that would block scrolling the parent if a child element is hovered static bool scroll_allowed = true; + static bool typing_active = false; void GUI::Update(const wi::Canvas& canvas, float dt) { @@ -180,14 +181,27 @@ namespace wi::gui } return nullptr; } - bool GUI::HasFocus() + bool GUI::HasFocus() const + { + if (!visible) + { + return false; + } + if (IsTyping()) + { + return true; + } + + return focus; + } + bool GUI::IsTyping() const { if (!visible) { return false; } - return focus; + return typing_active; } void GUI::SetColor(wi::Color color, int id) { @@ -370,7 +384,8 @@ namespace wi::gui { scripttipFont.params = tooltipFont.params; scripttipFont.params.posY += textHeightWithoutScriptTip; - scripttipFont.params.color = wi::Color::lerp(tooltipFont.params.color, wi::Color::Transparent(), 0.25f); + scripttipFont.params.color = tooltipFont.params.color; + scripttipFont.params.color.setA(uint8_t(float(scripttipFont.params.color.getA()) / 255.0f * 0.6f * 255)); scripttipFont.Draw(cmd); } } @@ -830,6 +845,16 @@ namespace wi::gui fx.siz.x += shadow * 2; fx.siz.y += shadow * 2; fx.color = shadow_color; + if (fx.isCornerRoundingEnabled()) + { + for (auto& corner_rounding : fx.corners_rounding) + { + if (corner_rounding.radius > 0) + { + corner_rounding.radius += shadow; + } + } + } wi::image::Draw(wi::texturehelper::getWhite(), fx, cmd); } @@ -1184,6 +1209,16 @@ namespace wi::gui fx.siz.x += shadow * 2; fx.siz.y += shadow * 2; fx.color = shadow_color; + if (fx.isCornerRoundingEnabled()) + { + for (auto& corner_rounding : fx.corners_rounding) + { + if (corner_rounding.radius > 0) + { + corner_rounding.radius += shadow; + } + } + } wi::image::Draw(wi::texturehelper::getWhite(), fx, cmd); } @@ -1303,6 +1338,7 @@ namespace wi::gui args.iValue = atoi(args.sValue.c_str()); args.fValue = (float)atof(args.sValue.c_str()); onInputAccepted(args); + typing_active = false; } Deactivate(); @@ -1342,6 +1378,7 @@ namespace wi::gui // cancel input font_input.text.clear(); Deactivate(); + typing_active = false; } else if (wi::input::Down(wi::input::MOUSE_BUTTON_LEFT)) { @@ -1432,6 +1469,16 @@ namespace wi::gui fx.siz.x += shadow * 2; fx.siz.y += shadow * 2; fx.color = shadow_color; + if (fx.isCornerRoundingEnabled()) + { + for (auto& corner_rounding : fx.corners_rounding) + { + if (corner_rounding.radius > 0) + { + corner_rounding.radius += shadow; + } + } + } wi::image::Draw(wi::texturehelper::getWhite(), fx, cmd); } @@ -1579,6 +1626,7 @@ namespace wi::gui caret_pos = (int)font_input.GetText().size(); caret_begin = caret_pos; caret_delay = 0; + typing_active = true; } @@ -1770,6 +1818,16 @@ namespace wi::gui fx.siz.x += shadow * 2 + 1 + valueInputField.GetSize().x; fx.siz.y += shadow * 2; fx.color = shadow_color; + if (fx.isCornerRoundingEnabled()) + { + for (auto& corner_rounding : fx.corners_rounding) + { + if (corner_rounding.radius > 0) + { + corner_rounding.radius += shadow; + } + } + } wi::image::Draw(wi::texturehelper::getWhite(), fx, cmd); } @@ -1930,6 +1988,16 @@ namespace wi::gui fx.siz.x += shadow * 2; fx.siz.y += shadow * 2; fx.color = shadow_color; + if (fx.isCornerRoundingEnabled()) + { + for (auto& corner_rounding : fx.corners_rounding) + { + if (corner_rounding.radius > 0) + { + corner_rounding.radius += shadow; + } + } + } wi::image::Draw(wi::texturehelper::getWhite(), fx, cmd); } @@ -2228,6 +2296,16 @@ namespace wi::gui fx.siz.x += shadow * 2 + 1 + scale.y; fx.siz.y += shadow * 2; fx.color = shadow_color; + if (fx.isCornerRoundingEnabled()) + { + for (auto& corner_rounding : fx.corners_rounding) + { + if (corner_rounding.radius > 0) + { + corner_rounding.radius += shadow; + } + } + } wi::image::Draw(wi::texturehelper::getWhite(), fx, cmd); } @@ -2939,6 +3017,16 @@ namespace wi::gui { fx.siz.y = control_size + shadow * 2; } + if (fx.isCornerRoundingEnabled()) + { + for (auto& corner_rounding : fx.corners_rounding) + { + if (corner_rounding.radius > 0) + { + corner_rounding.radius += shadow; + } + } + } wi::image::Draw(wi::texturehelper::getWhite(), fx, cmd); } @@ -4318,6 +4406,16 @@ namespace wi::gui fx.siz.x += shadow * 2; fx.siz.y = scale.y + shadow * 2; fx.color = shadow_color; + if (fx.isCornerRoundingEnabled()) + { + for (auto& corner_rounding : fx.corners_rounding) + { + if (corner_rounding.radius > 0) + { + corner_rounding.radius += shadow; + } + } + } wi::image::Draw(wi::texturehelper::getWhite(), fx, cmd); } diff --git a/WickedEngine/wiGUI.h b/WickedEngine/wiGUI.h index 3bb663ad7..09e91fd1a 100644 --- a/WickedEngine/wiGUI.h +++ b/WickedEngine/wiGUI.h @@ -218,10 +218,12 @@ namespace wi::gui Widget* GetWidget(const std::string& name); // returns true if any gui element has the focus - bool HasFocus(); + bool HasFocus() const; + // returns true if text input is happening + bool IsTyping() const; void SetVisible(bool value) { visible = value; } - bool IsVisible() { return visible; } + bool IsVisible() const { return visible; } void SetColor(wi::Color color, int id = -1); void SetShadowColor(wi::Color color); diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index 52dcf0a10..fc3027d1e 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -9,7 +9,7 @@ namespace wi::version // minor features, major updates, breaking compatibility changes const int minor = 71; // minor bug fixes, alterations, refactors, updates - const int revision = 92; + const int revision = 93; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);