diff --git a/.gitignore b/.gitignore index 55fa27b..f91b804 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ *.sln.docstates # Build results - **/vc201[35]/**/[Dd]ebug/ **/vc201[35]/**/[Rr]elease/ **/vc201[35]/**/[Pp]roduction/ @@ -15,6 +14,9 @@ **/vc201[35]/**/[Bb]in/ **/vc201[35]/**/[Oo]bj/ +# VS +.vs/ + # Xcode (https://github.com/github/gitignore/blob/master/Global/Xcode.gitignore) ## Build generated diff --git a/assets/settings.json b/assets/settings.json index 49b926b..e66a815 100644 --- a/assets/settings.json +++ b/assets/settings.json @@ -16,6 +16,7 @@ "fullscreen": true, "borderless": false, "size": {"x": 1280, "y": 720}, + "pos": {"x": 64, "y": 64}, "cameraOffset": {"x": 0, "y": 0}, "clearColor": {"r": 0, "g": 0, "b": 0, "a": 1.0} }, @@ -27,6 +28,7 @@ }, "debug": { "debugEnabled": true, + "logToStdOut": true, "showStats": false, "showMinimap": false, "showTouches": false, diff --git a/src/bluecadet/core/BaseApp.cpp b/src/bluecadet/core/BaseApp.cpp index 1a14aff..130974d 100644 --- a/src/bluecadet/core/BaseApp.cpp +++ b/src/bluecadet/core/BaseApp.cpp @@ -19,313 +19,339 @@ using namespace bluecadet::touch; using namespace bluecadet::touch::drivers; #endif -namespace bluecadet { -namespace core { +//! Prints all messages to std::cout/std::cerr instead of ci::app::console() +class StdOutLogger : public ci::log::Logger { +public: + StdOutLogger(bool timestampEnabled = true) : Logger() { setTimestampEnabled(timestampEnabled); }; + void write(const ci::log::Metadata & meta, const std::string & text) override { + std::ostream * stream = &std::cout; + switch (meta.mLevel) { + case ci::log::LEVEL_ERROR: + case ci::log::LEVEL_FATAL: { + stream = &std::cerr; + break; + } + default: + break; + } + writeDefault((*stream), meta, text); + }; +}; -BaseApp::BaseApp() - : ci::app::App(), - mLastUpdateTime(0), - mDebugUiPadding(16.0f), - mRootView(new BaseView()), - mMiniMap(new MiniMapView(0.025f)), - mStats(new GraphView(ivec2(128, 48))), - mIsLateSetupCompleted(false) { -} +namespace bluecadet { + namespace core { + + BaseApp::BaseApp() + : ci::app::App(), + mLastUpdateTime(0), + mDebugUiPadding(16.0f), + mRootView(new BaseView()), + mMiniMap(new MiniMapView(0.025f)), + mStats(new GraphView(ivec2(128, 48))), + mIsLateSetupCompleted(false) { + } -BaseApp::~BaseApp() { -} + BaseApp::~BaseApp() { + } -void BaseApp::setup() { - SettingsManager::get()->getSignalSettingsLoaded().connect(bind(&BaseApp::handleSettingsLoaded, this)); - ScreenLayout::get()->getAppSizeChangedSignal().connect(bind(&BaseApp::handleAppSizeChange, this, placeholders::_1)); - ScreenCamera::get()->getViewportChangedSignal().connect(bind(&BaseApp::handleViewportChange, this, placeholders::_1)); + void BaseApp::setup() { + SettingsManager::get()->getSignalSettingsLoaded().connect(bind(&BaseApp::handleSettingsLoaded, this)); + ScreenLayout::get()->getAppSizeChangedSignal().connect(bind(&BaseApp::handleAppSizeChange, this, placeholders::_1)); + ScreenCamera::get()->getViewportChangedSignal().connect(bind(&BaseApp::handleViewportChange, this, placeholders::_1)); - handleSettingsLoaded(); + handleSettingsLoaded(); #ifndef NO_TOUCH - mSimulatedTouchDriver.setup(Rectf(vec2(0), getWindowSize()), 60); + mSimulatedTouchDriver.setup(Rectf(vec2(0), getWindowSize()), 60); #endif #if defined(CINDER_MSW) - HWND nativeWindow = (HWND)getWindow()->getNative(); - // This ensures that this app can be run in windowed mode when launched from PM2 - ::ShowWindow(nativeWindow, SW_SHOW); - // Move window to foreground so that console output doesn't obstruct it - ::SetForegroundWindow(nativeWindow); - ::SetFocus(nativeWindow); + HWND nativeWindow = (HWND)getWindow()->getNative(); + // This ensures that this app can be run in windowed mode when launched from PM2 + ::ShowWindow(nativeWindow, SW_SHOW); + // Move window to foreground so that console output doesn't obstruct it + ::SetForegroundWindow(nativeWindow); + ::SetFocus(nativeWindow); #endif - // Debugging - mStats->setBackgroundColor(ColorA(0, 0, 0, 0.1f)); - mStats->addGraph("FPS", 0, getFrameRate(), ColorA(0, 1.0f, 0, 1.0f)); + // Debugging + mStats->setBackgroundColor(ColorA(0, 0, 0, 0.1f)); + mStats->addGraph("FPS", 0, getFrameRate(), ColorA(0, 1.0f, 0, 1.0f)); - if (SettingsManager::get()->mTouchSimEnabled) { - addTouchSimulatorParams(SettingsManager::get()->mSimulatedTouchesPerSecond); - } -} + if (SettingsManager::get()->mTouchSimEnabled) { + addTouchSimulatorParams(SettingsManager::get()->mSimulatedTouchesPerSecond); + } -void BaseApp::update() { - if (!mIsLateSetupCompleted && getElapsedFrames() > 1) { - lateSetup(); - mIsLateSetupCompleted = true; - } + if (SettingsManager::get()->mLogToStdOut) { + mLogger = make_shared(); + } else { + mLogger = make_shared(); + } + + ci::log::LogManager::instance()->addLogger(mLogger); + } + void BaseApp::update() { + if (!mIsLateSetupCompleted && getElapsedFrames() > 1) { + lateSetup(); + mIsLateSetupCompleted = true; + } - const double absoluteTime = getElapsedSeconds(); - const double deltaTime = mLastUpdateTime == 0 ? 0 : absoluteTime - mLastUpdateTime; + const double absoluteTime = getElapsedSeconds(); + const double deltaTime = mLastUpdateTime == 0 ? 0 : absoluteTime - mLastUpdateTime; - BaseView::FrameInfo frameInfo(absoluteTime, deltaTime); - mLastUpdateTime = absoluteTime; + BaseView::FrameInfo frameInfo(absoluteTime, deltaTime); + mLastUpdateTime = absoluteTime; - // get the screen layout's transform and apply it to all - // touch events to convert touches from window into app space - const auto appTransform = glm::inverse(ScreenCamera::get()->getTransform()); - const auto appSize = ScreenLayout::get()->getAppSize(); + // get the screen layout's transform and apply it to all + // touch events to convert touches from window into app space + const auto appTransform = glm::inverse(ScreenCamera::get()->getTransform()); + const auto appSize = ScreenLayout::get()->getAppSize(); #ifndef NO_TOUCH - touch::TouchManager::get()->update(mRootView, appSize, appTransform); + touch::TouchManager::get()->update(mRootView, appSize, appTransform); #endif - mRootView->updateScene(frameInfo); + mRootView->updateScene(frameInfo); - mStats->addValue("FPS", 1.0f / (float)frameInfo.deltaTime); -} + mStats->addValue("FPS", 1.0f / (float)frameInfo.deltaTime); + } -void BaseApp::draw(const bool clear) { - auto settings = SettingsManager::get(); + void BaseApp::draw(const bool clear) { + auto settings = SettingsManager::get(); - if (clear) { - gl::clear(settings->mClearColor); - } + if (clear) { + gl::clear(settings->mClearColor); + } - { - gl::ScopedModelMatrix scopedMatrix; - // apply screen layout transform to root view - gl::multModelMatrix(ScreenCamera::get()->getTransform()); - mRootView->drawScene(); + { + gl::ScopedModelMatrix scopedMatrix; + // apply screen layout transform to root view + gl::multModelMatrix(ScreenCamera::get()->getTransform()); + mRootView->drawScene(); #ifndef NO_TOUCH - // draw debug touches in app coordinate space - if (settings->mDebugEnabled && settings->mShowTouches) { - touch::TouchManager::get()->debugDrawTouches(); - } + // draw debug touches in app coordinate space + if (settings->mDebugEnabled && settings->mShowTouches) { + touch::TouchManager::get()->debugDrawTouches(); + } #endif - } - - if (settings->mDebugEnabled) { - // draw params and debug layers in window coordinate space - if (settings->mShowScreenLayout) { - gl::ScopedModelMatrix scopedMatrix; - gl::multModelMatrix(ScreenCamera::get()->getTransform()); - ScreenLayout::get()->draw(); - } - if (settings->mShowMinimap) { - mMiniMap->drawScene(); - } - if (settings->mShowStats) { - mStats->drawScene(); - } - if (settings->getParams()->isVisible()) { - settings->getParams()->draw(); + } + + if (settings->mDebugEnabled) { + // draw params and debug layers in window coordinate space + if (settings->mShowScreenLayout) { + gl::ScopedModelMatrix scopedMatrix; + gl::multModelMatrix(ScreenCamera::get()->getTransform()); + ScreenLayout::get()->draw(); + } + if (settings->mShowMinimap) { + mMiniMap->drawScene(); + } + if (settings->mShowStats) { + mStats->drawScene(); + } + if (settings->getParams()->isVisible()) { + settings->getParams()->draw(); + } + } } - } -} - -void BaseApp::keyDown(KeyEvent event) { - if (event.isHandled()) { - // don't do anything on previously handled events - return; - } - - switch (event.getCode()) { - case KeyEvent::KEY_q: quit(); break; - case KeyEvent::KEY_c: - SettingsManager::get()->mShowCursor = !SettingsManager::get()->mShowCursor; - SettingsManager::get()->mShowCursor ? showCursor() : hideCursor(); - break; - case KeyEvent::KEY_f: - SettingsManager::get()->mFullscreen = !isFullScreen(); - setFullScreen(SettingsManager::get()->mFullscreen); - ScreenCamera::get()->zoomToFitWindow(); - break; - case KeyEvent::KEY_F1: - if (!SettingsManager::get()->getParams()->isVisible()) { - SettingsManager::get()->getParams()->show(); - SettingsManager::get()->getParams()->maximize(); - - } else if (SettingsManager::get()->getParams()->isMaximized()) { - if (event.isShiftDown()) { - SettingsManager::get()->getParams()->hide(); + + void BaseApp::keyDown(KeyEvent event) { + if (event.isHandled()) { + // don't do anything on previously handled events + return; + } + + switch (event.getCode()) { + case KeyEvent::KEY_q: quit(); break; + case KeyEvent::KEY_c: + SettingsManager::get()->mShowCursor = !SettingsManager::get()->mShowCursor; + SettingsManager::get()->mShowCursor ? showCursor() : hideCursor(); + break; + case KeyEvent::KEY_f: + SettingsManager::get()->mFullscreen = !isFullScreen(); + setFullScreen(SettingsManager::get()->mFullscreen); + ScreenCamera::get()->zoomToFitWindow(); + break; + case KeyEvent::KEY_F1: + if (!SettingsManager::get()->getParams()->isVisible()) { + SettingsManager::get()->getParams()->show(); + SettingsManager::get()->getParams()->maximize(); + + } else if (SettingsManager::get()->getParams()->isMaximized()) { + if (event.isShiftDown()) { + SettingsManager::get()->getParams()->hide(); + } else { + SettingsManager::get()->getParams()->minimize(); + } + } else { - SettingsManager::get()->getParams()->minimize(); + SettingsManager::get()->getParams()->show(); + SettingsManager::get()->getParams()->maximize(); } - - } else { - SettingsManager::get()->getParams()->show(); - SettingsManager::get()->getParams()->maximize(); + break; } - break; - } -} + } -void BaseApp::findAssetDir(const std::string & subPath, bool stopAtFirst) { - fs::path basePath = getAppPath(); + void BaseApp::findAssetDir(const std::string & subPath, bool stopAtFirst) { + fs::path basePath = getAppPath(); - while (basePath.has_parent_path()) { - basePath = basePath.parent_path(); + while (basePath.has_parent_path()) { + basePath = basePath.parent_path(); - auto possiblePath = basePath; - possiblePath = possiblePath.append(subPath); + auto possiblePath = basePath; + possiblePath = possiblePath.append(subPath); - if (fs::exists(possiblePath)) { - CI_LOG_I("Adding asset dir '" << possiblePath << "'"); - addAssetDirectory(possiblePath); + if (fs::exists(possiblePath)) { + CI_LOG_I("Adding asset dir '" << possiblePath << "'"); + addAssetDirectory(possiblePath); - if (stopAtFirst) { - return; + if (stopAtFirst) { + return; + } + } } } - } -} - -void BaseApp::handleAppSizeChange(const ci::ivec2 & appSize) { - mRootView->setSize(vec2(appSize)); - mMiniMap->setLayout(ScreenLayout::get()->getNumColumns(), ScreenLayout::get()->getNumRows(), - ScreenLayout::get()->getDisplaySize(), ScreenLayout::get()->getBezelDims()); -} - -void BaseApp::handleViewportChange(const ci::Area & viewport) { - mMiniMap->setViewport(viewport); - mMiniMap->setPosition(vec2(getWindowSize()) - mMiniMap->getSize() - vec2(mDebugUiPadding)); - mStats->setPosition(vec2(mDebugUiPadding, (float)getWindowHeight() - mStats->getHeight() - mDebugUiPadding)); - SettingsManager::get()->getParams()->setPosition(vec2(mDebugUiPadding)); -} -void BaseApp::handleSettingsLoaded() { - auto settings = SettingsManager::get(); - auto layout = ScreenLayout::get(); - auto camera = ScreenCamera::get(); - - if (settings->mDisplaySize.x <= 0) settings->mDisplaySize.x = getWindowWidth(); - if (settings->mDisplaySize.y <= 0) settings->mDisplaySize.y = getWindowHeight(); - - layout->setup(settings->mDisplaySize, settings->mDisplayRows, settings->mDisplayColumns, settings->mBezelDims); - - camera->setup(layout); - camera->setZoomToggleHotkeyEnabled(settings->mZoomToggleHotkeyEnabled); - camera->setDisplayIdHotkeysEnabled(settings->mDisplayIdHotkeysEnabled); - camera->zoomToFitWindow(); - - if (settings->mCameraOffset != vec2(0)) { - vec2 globalOffset = settings->mCameraOffset; - vec2 localOffset = globalOffset * camera->getScale(); - vec2 currentOffset = camera->getTranslation(); - camera->setTranslation(currentOffset + localOffset); - } - - if (settings->mCameraZoom != 1.0f) { - float zoom = camera->getScale().x; - camera->zoomAtCurrentLocation(zoom * settings->mCameraZoom); - } - - // Apply run-time settings - if (settings->mShowCursor) { - showCursor(); - } else { - hideCursor(); - } - - // Set up graphics - gl::enableVerticalSync(settings->mVerticalSync); - gl::enableAlphaBlending(); -#ifndef NO_TOUCH - // Set up touches - if (settings->mMouseEnabled) { - mMouseDriver.connect(); - mMouseDriver.setVirtualMultiTouchEnabled(true); - } else { - mMouseDriver.disconnect(); - } - if (settings->mTuioTouchEnabled) { - mTuioDriver.connect(); - } else { - mTuioDriver.disconnect(); - } - if (settings->mNativeTouchEnabled) { - if (settings->mSupportMultipleNativeTouchScreens) { - mMultiNativeTouchDriver.connect(); - } else { - mNativeTouchDriver.connect(); + void BaseApp::handleAppSizeChange(const ci::ivec2 & appSize) { + mRootView->setSize(vec2(appSize)); + mMiniMap->setLayout(ScreenLayout::get()->getNumColumns(), ScreenLayout::get()->getNumRows(), + ScreenLayout::get()->getDisplaySize(), ScreenLayout::get()->getBezelDims()); + } + + void BaseApp::handleViewportChange(const ci::Area & viewport) { + mMiniMap->setViewport(viewport); + mMiniMap->setPosition(vec2(getWindowSize()) - mMiniMap->getSize() - vec2(mDebugUiPadding)); + mStats->setPosition(vec2(mDebugUiPadding, (float)getWindowHeight() - mStats->getHeight() - mDebugUiPadding)); + SettingsManager::get()->getParams()->setPosition(vec2(mDebugUiPadding)); } - } else { - mMultiNativeTouchDriver.disconnect(); - mNativeTouchDriver.disconnect(); - } + void BaseApp::handleSettingsLoaded() { + auto settings = SettingsManager::get(); + auto layout = ScreenLayout::get(); + auto camera = ScreenCamera::get(); + + if (settings->mDisplaySize.x <= 0) settings->mDisplaySize.x = getWindowWidth(); + if (settings->mDisplaySize.y <= 0) settings->mDisplaySize.y = getWindowHeight(); + + layout->setup(settings->mDisplaySize, settings->mDisplayRows, settings->mDisplayColumns, settings->mBezelDims); + + camera->setup(layout); + camera->setZoomToggleHotkeyEnabled(settings->mZoomToggleHotkeyEnabled); + camera->setDisplayIdHotkeysEnabled(settings->mDisplayIdHotkeysEnabled); + camera->zoomToFitWindow(); + + if (settings->mCameraOffset != vec2(0)) { + vec2 globalOffset = settings->mCameraOffset; + vec2 localOffset = globalOffset * camera->getScale(); + vec2 currentOffset = camera->getTranslation(); + camera->setTranslation(currentOffset + localOffset); + } + + if (settings->mCameraZoom != 1.0f) { + float zoom = camera->getScale().x; + camera->zoomAtCurrentLocation(zoom * settings->mCameraZoom); + } + + // Apply run-time settings + if (settings->mShowCursor) { + showCursor(); + } else { + hideCursor(); + } + + // Set up graphics + gl::enableVerticalSync(settings->mVerticalSync); + gl::enableAlphaBlending(); + +#ifndef NO_TOUCH + // Set up touches + if (settings->mMouseEnabled) { + mMouseDriver.connect(); + mMouseDriver.setVirtualMultiTouchEnabled(true); + } else { + mMouseDriver.disconnect(); + } + if (settings->mTuioTouchEnabled) { + mTuioDriver.connect(); + } else { + mTuioDriver.disconnect(); + } + if (settings->mNativeTouchEnabled) { + if (settings->mSupportMultipleNativeTouchScreens) { + mMultiNativeTouchDriver.connect(); + } else { + mNativeTouchDriver.connect(); + } + } else { + mMultiNativeTouchDriver.disconnect(); + mNativeTouchDriver.disconnect(); + } #endif // !NO_TOUCH -} + } #ifndef NO_TOUCH -void BaseApp::addTouchSimulatorParams(float touchesPerSecond) { - - mSimulatedTouchDriver.setTouchesPerSecond(touchesPerSecond); - - const string groupName = "Touch Sim"; - auto params = SettingsManager::get()->getParams(); - - params - ->addParam( - "Enabled", - [&](bool v) { - if (!mSimulatedTouchDriver.isRunning()) { - mSimulatedTouchDriver.setBounds(Rectf(vec2(0), getWindowSize())); - mSimulatedTouchDriver.start(); - } else { - mSimulatedTouchDriver.stop(); - } - }, - [&] { return SettingsManager::get()->mShowTouches && mSimulatedTouchDriver.isRunning(); }) - .group(groupName) - .key("d"); - - static int stressTestMode = 0; - static vector stressTestModes = {"Tap & Drag", "Slow Drag", "Tap"}; - - params->addParam("Mode", stressTestModes, &stressTestMode) - .updateFn([&] { - if (stressTestMode == 0) { - mSimulatedTouchDriver.setMinTouchDuration(0); - mSimulatedTouchDriver.setMaxTouchDuration(1.f); - mSimulatedTouchDriver.setMaxDragDistance(200.f); - } else if (stressTestMode == 1) { - mSimulatedTouchDriver.setMinTouchDuration(4.f); - mSimulatedTouchDriver.setMaxTouchDuration(8.f); - mSimulatedTouchDriver.setMaxDragDistance(200.f); - } else if (stressTestMode == 2) { - mSimulatedTouchDriver.setMinTouchDuration(0); - mSimulatedTouchDriver.setMaxTouchDuration(0.1f); - mSimulatedTouchDriver.setMaxDragDistance(0.f); - } - }) - .group(groupName); - - params - ->addParam( - "Touches/s", [&](float v) { mSimulatedTouchDriver.setTouchesPerSecond(v); }, - [&]() { return mSimulatedTouchDriver.getTouchesPerSecond(); }) - .group(groupName); - - params - ->addParam( - "Show Missed Touches", [&](bool v) { TouchManager::get()->setDiscardMissedTouches(!v); }, - [&]() { return !TouchManager::get()->getDiscardMissedTouches(); }) - .group(groupName); - - if (SettingsManager::get()->mCollapseParams) { - params->setOptions(groupName, "opened=false"); - } -} + void BaseApp::addTouchSimulatorParams(float touchesPerSecond) { + + mSimulatedTouchDriver.setTouchesPerSecond(touchesPerSecond); + + const string groupName = "Touch Sim"; + auto params = SettingsManager::get()->getParams(); + + params + ->addParam( + "Enabled", + [&](bool v) { + if (!mSimulatedTouchDriver.isRunning()) { + mSimulatedTouchDriver.setBounds(Rectf(vec2(0), getWindowSize())); + mSimulatedTouchDriver.start(); + } else { + mSimulatedTouchDriver.stop(); + } + }, + [&] { return SettingsManager::get()->mShowTouches && mSimulatedTouchDriver.isRunning(); }) + .group(groupName) + .key("d"); + + static int stressTestMode = 0; + static vector stressTestModes = {"Tap & Drag", "Slow Drag", "Tap"}; + + params->addParam("Mode", stressTestModes, &stressTestMode) + .updateFn([&] { + if (stressTestMode == 0) { + mSimulatedTouchDriver.setMinTouchDuration(0); + mSimulatedTouchDriver.setMaxTouchDuration(1.f); + mSimulatedTouchDriver.setMaxDragDistance(200.f); + } else if (stressTestMode == 1) { + mSimulatedTouchDriver.setMinTouchDuration(4.f); + mSimulatedTouchDriver.setMaxTouchDuration(8.f); + mSimulatedTouchDriver.setMaxDragDistance(200.f); + } else if (stressTestMode == 2) { + mSimulatedTouchDriver.setMinTouchDuration(0); + mSimulatedTouchDriver.setMaxTouchDuration(0.1f); + mSimulatedTouchDriver.setMaxDragDistance(0.f); + } + }) + .group(groupName); + + params + ->addParam( + "Touches/s", [&](float v) { mSimulatedTouchDriver.setTouchesPerSecond(v); }, + [&]() { return mSimulatedTouchDriver.getTouchesPerSecond(); }) + .group(groupName); + + params + ->addParam( + "Show Missed Touches", [&](bool v) { TouchManager::get()->setDiscardMissedTouches(!v); }, + [&]() { return !TouchManager::get()->getDiscardMissedTouches(); }) + .group(groupName); + + if (SettingsManager::get()->mCollapseParams) { + params->setOptions(groupName, "opened=false"); + } + } #endif -} // namespace core + } // namespace core } // namespace bluecadet \ No newline at end of file diff --git a/src/bluecadet/core/BaseApp.h b/src/bluecadet/core/BaseApp.h index 30625eb..7878ddf 100644 --- a/src/bluecadet/core/BaseApp.h +++ b/src/bluecadet/core/BaseApp.h @@ -82,6 +82,7 @@ class BaseApp : public ci::app::App { views::BaseViewRef mRootView; views::MiniMapViewRef mMiniMap; views::GraphViewRef mStats; + ci::log::LoggerRef mLogger; double mLastUpdateTime; float mDebugUiPadding; bool mIsLateSetupCompleted; diff --git a/src/bluecadet/core/SettingsManager.cpp b/src/bluecadet/core/SettingsManager.cpp index 8996901..f7b7698 100644 --- a/src/bluecadet/core/SettingsManager.cpp +++ b/src/bluecadet/core/SettingsManager.cpp @@ -10,337 +10,347 @@ using namespace ci::app; using namespace std; namespace bluecadet { -namespace core { - -SettingsManagerRef SettingsManager::sInstance = nullptr; - -SettingsManager::SettingsManager() {} -SettingsManager::~SettingsManager() {} - -void SettingsManager::setup(ci::app::App::Settings * appSettings, ci::fs::path jsonPath, bool autoCreateJson, - std::function callback) { - mJsonPath = jsonPath; - - if (mJsonPath.empty()) { - CI_LOG_I("Setting json path to default: '" << getDefaulJsonPath() << "'"); - mJsonPath = getDefaulJsonPath(); - } - - mapFields(); - - load(jsonPath, autoCreateJson); - - if (callback) { - callback(this); - } - - setupCommandArgs(); - parseCommandArgs(appSettings->getCommandLineArgs()); - applyToAppSettings(appSettings); -} - -void SettingsManager::mapFields() { - // General - mapField("settings.general.console", &mConsole).commandArgs({"console"}); - mapField("settings.general.version", &mAppVersion); - - // Display - mapField("settings.display.index", &mDisplayIndex).commandArgs({"display", "display_index", "displayIndex"}); - mapField("settings.display.size", &mDisplaySize); - mapField("settings.display.columns", &mDisplayColumns); - mapField("settings.display.rows", &mDisplayRows); - mapField("settings.display.bezel", &mBezelDims).commandArgs({"bezel", "display_bezel", "displayBezel"}); - - // Window - mapField("settings.window.fps", &mFps).commandArgs({"fps"}); - mapField("settings.window.vsync", &mVerticalSync).commandArgs({"vsync"}); - mapField("settings.window.fullscreen", &mFullscreen).commandArgs({"fullscreen"});; - mapField("settings.window.borderless", &mBorderless).commandArgs({"borderless"});; - mapField("settings.window.pos", &mWindowPos).commandArgs({"pos", "window_pos", "windowPos"}); - mapField("settings.window.size", &mWindowSize).commandArgs({"size", "window_size", "windowSize"}); - mapField("settings.window.clearColor", &mClearColor); - - // Camera - mapField("settings.camera.offset", &mCameraOffset).commandArgs({"offset", "camera_offset", "cameraOffset"}); - mapField("settings.camera.zoom", &mCameraZoom).commandArgs({"zoom", "camera_zoom", "cameraZoom"}); - - // Touch - mapField("settings.touch.mouse", &mMouseEnabled).commandArgs({"mouse"}); - mapField("settings.touch.tuio", &mTuioTouchEnabled).commandArgs({"tuio"}); - mapField("settings.touch.native", &mNativeTouchEnabled).commandArgs({"native", "touch"}); - mapField("settings.touch.supportMultipleNativeTouchScreens", &mSupportMultipleNativeTouchScreens) - .commandArgs({"supportMultipleNativeTouchScreens", "support_multiple_native_touch_screens"});; - - // Debug - mapField("settings.debug.debugEnabled", &mDebugEnabled).commandArgs({"debug"}); - mapField("settings.debug.showStats", &mShowStats).commandArgs({"stats"}); - mapField("settings.debug.showMinimap", &mShowMinimap).commandArgs({"minimap"}); - mapField("settings.debug.showTouches", &mShowTouches).commandArgs({"show_touches", "showTouches"}); - mapField("settings.debug.showScreenLayout", &mShowScreenLayout).commandArgs({"show_layout", "showLayout"}); - mapField("settings.debug.showCursor", &mShowCursor).commandArgs({"cursor", "show_cursor", "showCursor"}); - mapField("settings.debug.minimizeParams", &mMinimizeParams).commandArgs({"minimize_params", "minimizeParams"}); - mapField("settings.debug.collapseParams", &mCollapseParams).commandArgs({"collapse_params", "collapseParams"}); - mapField("settings.debug.zoomToggleHotkey", &mZoomToggleHotkeyEnabled).commandArgs({"zoom_toggle_hotkey", "zoomToggleHotkey"}); - mapField("settings.debug.displayIdHotkeys", &mDisplayIdHotkeysEnabled).commandArgs({"display_id_hotkey", "displayIdHotkey"}); - mapField("settings.debug.touchSimulator.enabled", &mTouchSimEnabled).commandArgs({"touch_sim", "touchSim"}); -} - -void SettingsManager::setupCommandArgs() { - for (auto & mapping : mFieldMappings) { - for (auto & arg : mapping.second.commandArgNames) { - addCommandArg(arg, [&] (const std::string & arg) { - mapping.second.readCommandArg(arg); - }); - } - } -} + namespace core { -void SettingsManager::applyToAppSettings(ci::app::App::Settings * settings) { - const float pixelScale = Display::getMainDisplay()->getContentScale(); + SettingsManagerRef SettingsManager::sInstance = nullptr; - // Default window size to main display size if no custom size has been determined - if (mWindowSize == ivec2(INT_MIN)) { - mWindowSize = vec2(Display::getMainDisplay()->getSize()) * pixelScale; - } + SettingsManager::SettingsManager() {} + SettingsManager::~SettingsManager() {} - // Apply pre-launch settings -#ifdef CINDER_MSW - settings->setConsoleWindowEnabled(mConsole); -#endif + void SettingsManager::setup(ci::app::App::Settings* appSettings, ci::fs::path jsonPath, bool autoCreateJson, + std::function callback) { + mJsonPath = jsonPath; + + if (mJsonPath.empty()) { + CI_LOG_I("Setting json path to default: '" << getDefaulJsonPath() << "'"); + mJsonPath = getDefaulJsonPath(); + } - if (mFps > 0) { - settings->setFrameRate(mFps); - } else { - settings->disableFrameRate(); - } + mapFields(); - settings->setWindowSize(mWindowSize); - settings->setBorderless(mBorderless); - settings->setFullScreen(mFullscreen); + load(jsonPath, autoCreateJson); - if (mNativeTouchEnabled) { - settings->setMultiTouchEnabled(true); - } + if (callback) { + callback(this); + } - if (mDisplayIndex != 0) { - const auto displays = Display::getDisplays(); - if (mDisplayIndex >= 0 && mDisplayIndex < displays.size()) { - settings->setDisplay(displays.at(mDisplayIndex)); + setupCommandArgs(); + parseCommandArgs(appSettings->getCommandLineArgs()); + applyToAppSettings(appSettings); } - } - - // Default window position to centered in display if no custom pos has been set - if (mWindowPos == ivec2(INT_MIN)) { - ivec2 windowSizeInPx = vec2(settings->getWindowSize()) * pixelScale; - ivec2 windowPos = (Display::getMainDisplay()->getSize() - windowSizeInPx) / 2; - windowPos = glm::max(windowPos, ivec2(0)); - settings->setWindowPos(windowPos); - } else { - settings->setWindowPos(vec2(mWindowPos) * pixelScale); - } -} - -void SettingsManager::addCommandArg(const std::string & key, CommandArgParserFn callback) { - string lowercaseKey = key; - std::transform(lowercaseKey.begin(), lowercaseKey.end(), lowercaseKey.begin(), ::tolower); - - auto callbackListIt = mCommandArgsHandlers.find(lowercaseKey); - - if (callbackListIt == mCommandArgsHandlers.end()) { - mCommandArgsHandlers[lowercaseKey] = vector(); - callbackListIt = mCommandArgsHandlers.find(lowercaseKey); - } - - callbackListIt->second.push_back(callback); -} - -void SettingsManager::addCommandArgs(const std::set & keys, CommandArgParserFn callback) { - for (const auto key : keys) { - addCommandArg(key, callback); - } -} - -void SettingsManager::load(ci::fs::path jsonPath, bool autoCreateJson) { - if (jsonPath.empty()) { - jsonPath = mJsonPath; - } - - if (jsonPath.empty()) { - CI_LOG_D("No settings file specified. Using defaults."); - } else if (!fs::exists(jsonPath)) { - if (autoCreateJson) { - CI_LOG_I("Creating settings json at '" << jsonPath << "'"); - save(jsonPath); - } else { - CI_LOG_E("No settings file found at '" << jsonPath << "'"); + + void SettingsManager::mapFields() { + // General + mapField("settings.general.console", &mConsole).commandArgs({ "console" }); + mapField("settings.general.version", &mAppVersion); + + // Display + mapField("settings.display.index", &mDisplayIndex).commandArgs({ "display", "display_index", "displayIndex" }); + mapField("settings.display.size", &mDisplaySize); + mapField("settings.display.columns", &mDisplayColumns); + mapField("settings.display.rows", &mDisplayRows); + mapField("settings.display.bezel", &mBezelDims).commandArgs({ "bezel", "display_bezel", "displayBezel" }); + + // Window + mapField("settings.window.fps", &mFps).commandArgs({ "fps" }); + mapField("settings.window.vsync", &mVerticalSync).commandArgs({ "vsync" }); + mapField("settings.window.fullscreen", &mFullscreen).commandArgs({ "fullscreen" });; + mapField("settings.window.borderless", &mBorderless).commandArgs({ "borderless" });; + mapField("settings.window.pos", &mWindowPos).commandArgs({ "pos", "window_pos", "windowPos" }); + mapField("settings.window.size", &mWindowSize).commandArgs({ "size", "window_size", "windowSize" }); + mapField("settings.window.clearColor", &mClearColor); + + // Camera + mapField("settings.camera.offset", &mCameraOffset).commandArgs({ "offset", "camera_offset", "cameraOffset" }); + mapField("settings.camera.zoom", &mCameraZoom).commandArgs({ "zoom", "camera_zoom", "cameraZoom" }); + + // Touch + mapField("settings.touch.mouse", &mMouseEnabled).commandArgs({ "mouse" }); + mapField("settings.touch.tuio", &mTuioTouchEnabled).commandArgs({ "tuio" }); + mapField("settings.touch.native", &mNativeTouchEnabled).commandArgs({ "native", "touch" }); + mapField("settings.touch.supportMultipleNativeTouchScreens", &mSupportMultipleNativeTouchScreens) + .commandArgs({ "supportMultipleNativeTouchScreens", "support_multiple_native_touch_screens" });; + + // Debug + mapField("settings.debug.debugEnabled", &mDebugEnabled).commandArgs({ "debug" }); + mapField("settings.debug.logToStdOut", &mLogToStdOut).commandArgs({ "logToStdOut", "log_to_std_out" }); + mapField("settings.debug.showStats", &mShowStats).commandArgs({ "stats" }); + mapField("settings.debug.showMinimap", &mShowMinimap).commandArgs({ "minimap" }); + mapField("settings.debug.showTouches", &mShowTouches).commandArgs({ "show_touches", "showTouches" }); + mapField("settings.debug.showScreenLayout", &mShowScreenLayout).commandArgs({ "show_layout", "showLayout" }); + mapField("settings.debug.showCursor", &mShowCursor).commandArgs({ "cursor", "show_cursor", "showCursor" }); + mapField("settings.debug.minimizeParams", &mMinimizeParams).commandArgs({ "minimize_params", "minimizeParams" }); + mapField("settings.debug.collapseParams", &mCollapseParams).commandArgs({ "collapse_params", "collapseParams" }); + mapField("settings.debug.zoomToggleHotkey", &mZoomToggleHotkeyEnabled).commandArgs({ "zoom_toggle_hotkey", "zoomToggleHotkey" }); + mapField("settings.debug.displayIdHotkeys", &mDisplayIdHotkeysEnabled).commandArgs({ "display_id_hotkey", "displayIdHotkey" }); + mapField("settings.debug.touchSimulator.enabled", &mTouchSimEnabled).commandArgs({ "touch_sim", "touchSim" }); } - } else { - CI_LOG_I("Loading settings from '" << jsonPath << "'"); + void SettingsManager::setupCommandArgs() { + for (auto& mapping : mFieldMappings) { + for (auto& arg : mapping.second.commandArgNames) { + addCommandArg(arg, [&](const std::string& arg) { + mapping.second.readCommandArg(arg); + }); + } + } + } + + void SettingsManager::applyToAppSettings(ci::app::App::Settings* settings) { + const float pixelScale = Display::getMainDisplay()->getContentScale(); - try { - mJson = JsonTree(loadFile(jsonPath)); - deserialize(mJson); + // Default window size to main display size if no custom size has been determined + if (mWindowSize == ivec2(INT_MIN)) { + mWindowSize = vec2(Display::getMainDisplay()->getSize()) * pixelScale; + } - } catch (Exception e) { - CI_LOG_EXCEPTION("Could not parse json", e); + // Apply pre-launch settings +#ifdef CINDER_MSW + settings->setConsoleWindowEnabled(mConsole); +#endif + + if (mFps > 0) { + settings->setFrameRate(mFps); + } + else { + settings->disableFrameRate(); + } + + settings->setWindowSize(mWindowSize); + settings->setBorderless(mBorderless); + settings->setFullScreen(mFullscreen); + + if (mNativeTouchEnabled) { + settings->setMultiTouchEnabled(true); + } + + if (mDisplayIndex != 0) { + const auto displays = Display::getDisplays(); + if (mDisplayIndex >= 0 && mDisplayIndex < displays.size()) { + settings->setDisplay(displays.at(mDisplayIndex)); + } + } + + // Default window position to centered in display if no custom pos has been set + if (mWindowPos == ivec2(INT_MIN)) { + ivec2 windowSizeInPx = vec2(settings->getWindowSize()) * pixelScale; + ivec2 windowPos = (Display::getMainDisplay()->getSize() - windowSizeInPx) / 2; + windowPos = glm::max(windowPos, ivec2(0)); + settings->setWindowPos(windowPos); + } + else { + settings->setWindowPos(vec2(mWindowPos) * pixelScale); + } } - } -} -void SettingsManager::save(ci::fs::path jsonPath) { - if (jsonPath.empty()) { - jsonPath = mJsonPath; - } + void SettingsManager::addCommandArg(const std::string& key, CommandArgParserFn callback) { + string lowercaseKey = key; + std::transform(lowercaseKey.begin(), lowercaseKey.end(), lowercaseKey.begin(), ::tolower); - if (!jsonPath.empty()) { - CI_LOG_I("Saving settings to '" << jsonPath << "'"); + auto callbackListIt = mCommandArgsHandlers.find(lowercaseKey); - try { - auto json = serialize(); - json.write(jsonPath, JsonTree::WriteOptions().indented(true)); + if (callbackListIt == mCommandArgsHandlers.end()) { + mCommandArgsHandlers[lowercaseKey] = vector(); + callbackListIt = mCommandArgsHandlers.find(lowercaseKey); + } - } catch (Exception e) { - CI_LOG_EXCEPTION("Could not save json", e); + callbackListIt->second.push_back(callback); } - } else { - CI_LOG_D("No settings file specified. Using defaults."); - } -} - -void SettingsManager::parseCommandArgs(const std::vector & args) { - string allArgsStr; - - for (auto arg : args) { - int splitIndex = (int)arg.find_first_of("="); - if (splitIndex != std::string::npos) { - allArgsStr += " " + arg; - - std::transform(arg.begin(), arg.end(), arg.begin(), ::tolower); - std::string key = arg.substr(0, splitIndex); - std::string value = arg.substr(splitIndex + 1, arg.size() - splitIndex - 1); - auto callbackListIt = mCommandArgsHandlers.find(key); - if (callbackListIt == mCommandArgsHandlers.end()) continue; - - for (auto callback : callbackListIt->second) { - callback(value); + + void SettingsManager::addCommandArgs(const std::set& keys, CommandArgParserFn callback) { + for (const auto key : keys) { + addCommandArg(key, callback); } } - } - if (!allArgsStr.empty()) { - CI_LOG_I("Launching with command line args: '" + allArgsStr + "'"); - } -} + void SettingsManager::load(ci::fs::path jsonPath, bool autoCreateJson) { + if (jsonPath.empty()) { + jsonPath = mJsonPath; + } -void SettingsManager::deserialize(ci::JsonTree & json) { - for (const auto & fieldName : mFieldMappingNames) { - mergeField(fieldName, json); - } - mSignalSettingsLoaded.emit(); -} + if (jsonPath.empty()) { + CI_LOG_D("No settings file specified. Using defaults."); + } + else if (!fs::exists(jsonPath)) { + if (autoCreateJson) { + CI_LOG_I("Creating settings json at '" << jsonPath << "'"); + save(jsonPath); + } + else { + CI_LOG_E("No settings file found at '" << jsonPath << "'"); + } -ci::JsonTree SettingsManager::serialize() { - ci::JsonTree json; + } + else { + CI_LOG_I("Loading settings from '" << jsonPath << "'"); - for (auto & fieldMapping : mFieldMappings) { - auto fields = text::split>(fieldMapping.first, '.'); + try { + mJson = JsonTree(loadFile(jsonPath)); + deserialize(mJson); - // grab value and pop from fields - const string & key = fields.back(); - JsonTree value = fieldMapping.second.toJson(key); - fields.pop_back(); + } + catch (Exception e) { + CI_LOG_EXCEPTION("Could not parse json", e); + } + } + } + + void SettingsManager::save(ci::fs::path jsonPath) { + if (jsonPath.empty()) { + jsonPath = mJsonPath; + } - ci::JsonTree * parent = &json; + if (!jsonPath.empty()) { + CI_LOG_I("Saving settings to '" << jsonPath << "'"); - // create parent hierarchy - while (!fields.empty()) { - const auto field = fields.front(); + try { + auto json = serialize(); + json.write(jsonPath, JsonTree::WriteOptions().indented(true)); - if (!parent->hasChild(field)) { - parent->addChild(JsonTree::makeObject(field)); + } + catch (Exception e) { + CI_LOG_EXCEPTION("Could not save json", e); + } } + else { + CI_LOG_D("No settings file specified. Using defaults."); + } + } - parent = &(parent->getChild(field)); + void SettingsManager::parseCommandArgs(const std::vector& args) { + string allArgsStr; - fields.pop_front(); - } + for (auto arg : args) { + int splitIndex = (int)arg.find_first_of("="); + if (splitIndex != std::string::npos) { + allArgsStr += " " + arg; - // add child - parent->addChild(value); - } + std::transform(arg.begin(), arg.end(), arg.begin(), ::tolower); + std::string key = arg.substr(0, splitIndex); + std::string value = arg.substr(splitIndex + 1, arg.size() - splitIndex - 1); + auto callbackListIt = mCommandArgsHandlers.find(key); + if (callbackListIt == mCommandArgsHandlers.end()) continue; - return json; -} + for (auto callback : callbackListIt->second) { + callback(value); + } + } + } -void SettingsManager::mergeField(const std::string & fieldName, const ci::JsonTree & json) { - try { - if (!hasJsonValue(fieldName)) { - CI_LOG_W("Could not find settings value for field name '" << fieldName << "' in json file"); - return; + if (!allArgsStr.empty()) { + CI_LOG_I("Launching with command line args: '" + allArgsStr + "'"); + } } - auto it = mFieldMappings.find(fieldName); - if (it == mFieldMappings.end()) { - CI_LOG_E("No mapping exists for field name '" << fieldName << "'"); - return; + + void SettingsManager::deserialize(ci::JsonTree& json) { + for (const auto& fieldName : mFieldMappingNames) { + mergeField(fieldName, json); + } + mSignalSettingsLoaded.emit(); } - auto & mapping = it->second; - - mapping.readJson(json); - - } catch (cinder::Exception e) { - CI_LOG_EXCEPTION("Could not set '" << fieldName << "' in json file", e); - } -} - -ci::params::InterfaceGlRef SettingsManager::createParams() { - auto params = ci::params::InterfaceGl::create("Settings", toPixels(ci::ivec2(250, 250))); - params->addParam("Show Layout", &mShowScreenLayout).group("App").key("l"); - params->addParam("Show Minimap", &mShowMinimap).group("App").key("m"); - params->addParam("Show Stats", &mShowStats).group("App").key("s"); - - params->addParam("Show Touches", &mShowTouches).group("App").key("t"); - params->addParam("Show Cursor", &mShowCursor) - .updateFn([&] { mShowCursor ? ci::app::AppBase::get()->showCursor() : ci::app::AppBase::get()->hideCursor(); }) - .key("c") - .group("App"); - - static int boundIndex = 0; - params - ->addParam("View Bounds", {"None", "Visible", "All"}, - [&](int i) { - boundIndex = i; - bluecadet::views::BaseView::sDrawDebugInfo = boundIndex >= 1; - bluecadet::views::BaseView::sDrawDebugInfoWhenInvisible = boundIndex >= 2; - }, - [&] { return boundIndex; }) - .key("b") - .group("App"); - - if (mMinimizeParams) { - params->minimize(); - } - - params->addSeparator(); - params->addButton("Load", [=]() { load(); }); - params->addButton("Save", [=]() { save(); }); - params->addText("Version " + mAppVersion); - - for (const auto & fieldName : mFieldMappingNames) { - auto it = mFieldMappings.find(fieldName); - if (it != mFieldMappings.end()) { - auto & fieldMapping = it->second; - fieldMapping.attachToParams(params); + ci::JsonTree SettingsManager::serialize() { + ci::JsonTree json; + + for (auto& fieldMapping : mFieldMappings) { + auto fields = text::split>(fieldMapping.first, '.'); + + // grab value and pop from fields + const string& key = fields.back(); + JsonTree value = fieldMapping.second.toJson(key); + fields.pop_back(); + + ci::JsonTree* parent = &json; + + // create parent hierarchy + while (!fields.empty()) { + const auto field = fields.front(); + + if (!parent->hasChild(field)) { + parent->addChild(JsonTree::makeObject(field)); + } + + parent = &(parent->getChild(field)); + + fields.pop_front(); + } + + // add child + parent->addChild(value); + } + + return json; } - } - if (mCollapseParams) { - params->setOptions("App", "opened=false"); - } + void SettingsManager::mergeField(const std::string& fieldName, const ci::JsonTree& json) { + try { + if (!hasJsonValue(fieldName)) { + CI_LOG_W("Could not find settings value for field name '" << fieldName << "' in json file"); + return; + } + auto it = mFieldMappings.find(fieldName); + if (it == mFieldMappings.end()) { + CI_LOG_E("No mapping exists for field name '" << fieldName << "'"); + return; + } - return params; -} + auto& mapping = it->second; + + mapping.readJson(json); + + } + catch (cinder::Exception e) { + CI_LOG_EXCEPTION("Could not set '" << fieldName << "' in json file", e); + } + } + + ci::params::InterfaceGlRef SettingsManager::createParams() { + auto params = ci::params::InterfaceGl::create("Settings", toPixels(ci::ivec2(250, 250))); + params->addParam("Show Layout", &mShowScreenLayout).group("App").key("l"); + params->addParam("Show Minimap", &mShowMinimap).group("App").key("m"); + params->addParam("Show Stats", &mShowStats).group("App").key("s"); + + params->addParam("Show Touches", &mShowTouches).group("App").key("t"); + params->addParam("Show Cursor", &mShowCursor) + .updateFn([&] { mShowCursor ? ci::app::AppBase::get()->showCursor() : ci::app::AppBase::get()->hideCursor(); }) + .key("c") + .group("App"); + + static int boundIndex = 0; + params + ->addParam("View Bounds", { "None", "Visible", "All" }, + [&](int i) { + boundIndex = i; + bluecadet::views::BaseView::sDrawDebugInfo = boundIndex >= 1; + bluecadet::views::BaseView::sDrawDebugInfoWhenInvisible = boundIndex >= 2; + }, + [&] { return boundIndex; }) + .key("b") + .group("App"); + + if (mMinimizeParams) { + params->minimize(); + } + + params->addSeparator(); + params->addButton("Load", [=]() { load(); }); + params->addButton("Save", [=]() { save(); }); + params->addText("Version " + mAppVersion); + + for (const auto& fieldName : mFieldMappingNames) { + auto it = mFieldMappings.find(fieldName); + if (it != mFieldMappings.end()) { + auto& fieldMapping = it->second; + fieldMapping.attachToParams(params); + } + } + + if (mCollapseParams) { + params->setOptions("App", "opened=false"); + } + + return params; + } -} // namespace core + } // namespace core } // namespace bluecadet diff --git a/src/bluecadet/core/SettingsManager.h b/src/bluecadet/core/SettingsManager.h index 30b6f71..03e170b 100644 --- a/src/bluecadet/core/SettingsManager.h +++ b/src/bluecadet/core/SettingsManager.h @@ -1,227 +1,226 @@ #pragma once +#include + +#include "ValueMapping.h" +#include "bluecadet/text/Text.h" #include "cinder/Json.h" #include "cinder/Log.h" #include "cinder/app/App.h" #include "cinder/params/Params.h" -#include "ValueMapping.h" -#include "bluecadet/text/Text.h" +namespace bluecadet { + namespace core { -#include + typedef std::shared_ptr SettingsManagerRef; -namespace bluecadet { -namespace core { + class SettingsManager { + public: + typedef std::function CommandArgParserFn; -typedef std::shared_ptr SettingsManagerRef; + typedef ci::signals::Signal SettingsLoadedSignal; -class SettingsManager { -public: - typedef std::function CommandArgParserFn; + SettingsManager(); + virtual ~SettingsManager(); - typedef ci::signals::Signal SettingsLoadedSignal; + // Overrides the shared instance. Use this method if you'd like to use a sub-class of SettingsManager. + static void setInstance(SettingsManagerRef instance) { sInstance = instance; } - SettingsManager(); - virtual ~SettingsManager(); + // Singleton; Instance can be changed by calling setInstance() (e.g. to use subclasses) + static SettingsManagerRef get() { + if (!sInstance) { + sInstance = std::make_shared(); + } + return sInstance; + } - // Overrides the shared instance. Use this method if you'd like to use a sub-class of SettingsManager. - static void setInstance(SettingsManagerRef instance) { sInstance = instance; } + static ci::fs::path getDefaulJsonPath() { + const auto filename = "settings.json"; + ci::fs::path path; + if (ci::app::getAssetDirectories().empty()) { + path = ci::app::getAppPath(); + } else { + path = ci::app::getAssetDirectories().front(); + } + path.append(ci::fs::path(filename)); + return path; + } + + //================================================== + // Setup + + // Set up the settings manager with the path to an optional settings json. Should be called from prepareSettings() + // and before setup(). Callback can be used to override arguments from the json file. + virtual void setup(ci::app::App::Settings * appSettings, ci::fs::path jsonPath = getDefaulJsonPath(), + bool autoCreateJson = true, std::function callback = nullptr); + + SettingsLoadedSignal & getSignalSettingsLoaded() { return mSignalSettingsLoaded; }; + + // Loads settings from jsonPath (or mJsonPath if previously defined via setup()). + // If autoCreateJson is true, then a new file will be created at jsonPath or mJsonPath. + virtual void load(ci::fs::path jsonPath = "", bool autoCreateJson = false); + + // Saves all mapped settings to jsonPath (or mJsonPath if previously defined via setup()). + virtual void save(ci::fs::path jsonPath = ""); + + + // Parses all mapped settings and applies them. This only changes internal values, + // but doesn't apply any settings like the current app's window size. + virtual void deserialize(ci::JsonTree & json); + + // Serializes all mapped settings into a JsonTree. + virtual ci::JsonTree serialize(); + + + // Adds a callback to parse a command line argument by key. Keys are converted to lowercase and values are always + // passed as strings. Multiple parsers for the same key can be added and they are called in the order they were + // added in. + virtual void addCommandArg(const std::string & key, CommandArgParserFn callback); + + // Shortcut to add multiple command line args with different keys but the same parser. + void addCommandArgs(const std::set & keys, CommandArgParserFn callback); - // Singleton; Instance can be changed by calling setInstance() (e.g. to use subclasses) - static SettingsManagerRef get() { - if (!sInstance) { - sInstance = std::make_shared(); - } - return sInstance; - } - - static ci::fs::path getDefaulJsonPath() { - const auto filename = "settings.json"; - ci::fs::path path; - if (ci::app::getAssetDirectories().empty()) { - path = ci::app::getAppPath(); - } else { - path = ci::app::getAssetDirectories().front(); - } - path.append(ci::fs::path(filename)); - return path; - } - //================================================== - // Setup + // Checks if the field exists in the loaded settings json. + inline bool hasJsonValue(const std::string & field) { return mJson.hasChild(field); }; - // Set up the settings manager with the path to an optional settings json. Should be called from prepareSettings() - // and before setup(). Callback can be used to override arguments from the json file. - virtual void setup(ci::app::App::Settings * appSettings, ci::fs::path jsonPath = getDefaulJsonPath(), - bool autoCreateJson = true, std::function callback = nullptr); + // Get the value of field from within the settings json. Will return an empty default value of + // type T if the field was not found. + template T getJsonValue(const std::string & field); - SettingsLoadedSignal & getSignalSettingsLoaded() { return mSignalSettingsLoaded; }; - // Loads settings from jsonPath (or mJsonPath if previously defined via setup()). - // If autoCreateJson is true, then a new file will be created at jsonPath or mJsonPath. - virtual void load(ci::fs::path jsonPath = "", bool autoCreateJson = false); + // Returns the app params, creates new params if necessary. + inline ci::params::InterfaceGlRef getParams() { + if (!mParams) { + mParams = createParams(); + } + return mParams; + }; - // Saves all mapped settings to jsonPath (or mJsonPath if previously defined via setup()). - virtual void save(ci::fs::path jsonPath = ""); + //================================================== + // Default Properties + // General + bool mConsole = true; // Use external console window + std::string mAppVersion = "1.0.0"; // Displays app version in the params - // Parses all mapped settings and applies them. This only changes internal values, - // but doesn't apply any settings like the current app's window size. - virtual void deserialize(ci::JsonTree & json); + // Graphics + float mFps = 60.0f; + bool mFullscreen = true; + bool mBorderless = false; + bool mVerticalSync = true; - // Serializes all mapped settings into a JsonTree. - virtual ci::JsonTree serialize(); + // Display + int mDisplayIndex = 0; // The index of the display to launch this app on. Reverts to main display if out of bounds. + ci::ivec2 mDisplaySize = ci::ivec2(-1); // The size of one display. Defaults to getWindowSize() + ci::ivec2 mBezelDims = ci::ivec2(0); // The amount of bezel correction to add in both X and Y + int mDisplayColumns = 1; // The number of display columns in a display matrix. Used by ScreenLayout. + int mDisplayRows = 1; // The number of display rows in a display matrix. Used by ScreenLayout. + // Window + ci::ivec2 mWindowSize = ci::ivec2(INT_MIN); // The window size on launch + ci::ivec2 mWindowPos = ci::ivec2(INT_MIN); // The window position on launch + ci::ColorA mClearColor = ci::ColorA::black(); // The color used when clearing the screen before draw() - // Adds a callback to parse a command line argument by key. Keys are converted to lowercase and values are always - // passed as strings. Multiple parsers for the same key can be added and they are called in the order they were - // added in. - virtual void addCommandArg(const std::string & key, CommandArgParserFn callback); + // Camera + ci::vec2 mCameraOffset = ci::ivec2(0); // The offset of the camera on launch + float mCameraZoom = 1.0f; // The zoom of the camera at launch - // Shortcut to add multiple command line args with different keys but the same parser. - void addCommandArgs(const std::set & keys, CommandArgParserFn callback); + // Touches + bool mMouseEnabled = true; // Treat mouse events as touch events. + bool mNativeTouchEnabled = true; // Native touch coming from the OS + bool mSupportMultipleNativeTouchScreens = true; // Native touch with multi-touchscreen support + bool mTuioTouchEnabled = false; // TUIO touch events; It's recommended to disable native touch + // when TUIO is enabled to prevent duplicate events. + // Debugging + bool mDebugEnabled = true; // Enable/disable all of the below debug features (except for hotkeys) + bool mLogToStdOut = true; // Logs to std::cout and std::cerr if enabled + bool mShowTouches = false; // Visualizes all current touch data + bool mShowScreenLayout = false; // Visualizes the current screen layout and bezels + bool mShowCursor = true; // Show or hide the mouse cursor (toggle with C) + bool mShowMinimap = true; // Show or hide the mini-map (toggle with M) + bool mShowStats = true; // Show or hide the frame-rate graph/plot (toggle with S) + bool mMinimizeParams = false; // Minimizes the params window (toggle with F1) + bool mCollapseParams = true; // Collapses all the default parameter groups like "App" + bool mZoomToggleHotkeyEnabled = true; // When true, will bind 0 to toggle zoom to 100%/fit + bool mDisplayIdHotkeysEnabled = false; // When true, will bind 1-9 to zoom directly to displays 1-9 + bool mTouchSimEnabled = false; // When true, will add touch simulator controls to params + float mSimulatedTouchesPerSecond = 50.0f; // Used only if mEnableTouchSim is true - // Checks if the field exists in the loaded settings json. - inline bool hasJsonValue(const std::string & field) { return mJson.hasChild(field); }; + protected: + static SettingsManagerRef sInstance; - // Get the value of field from within the settings json. Will return an empty default value of - // type T if the field was not found. - template T getJsonValue(const std::string & field); + // Registers a field that will be mapped from the JSON to a property. Call this method from a subclass if you intend + // to add custom JSON fields and map them to class members. + template ValueMapping & mapField(const std::string & fieldName, T * target); + // Checks if a field exists in the JSON and copies its value to this class in that value if it does. + void mergeField(const std::string & fieldName, const ci::JsonTree & json); - // Returns the app params, creates new params if necessary. - inline ci::params::InterfaceGlRef getParams() { - if (!mParams) { - mParams = createParams(); + // Map JSON field names to class members. Called once at the beginning setup(). + virtual void mapFields(); + + // Adds all default command line args and those created by all mapped fields. + virtual void setupCommandArgs(); + + // Parses command line arguments, which can override json settings + virtual void parseCommandArgs(const std::vector & args); + + // Applies parsed settings to ci::app::App::Settings + virtual void applyToAppSettings(ci::app::App::Settings * settings); + + // Helpers to get string from primitive types and strings since we can't call to_string on strings + template inline std::string toString(T * target) { return std::to_string(*target); } + + + // Creates the params object + ci::params::InterfaceGlRef createParams(); + + // Key-based callbacks that are called when a command line argument with that key is passed in + std::map> mCommandArgsHandlers; + + // Base settings json + ci::JsonTree mJson; + ci::fs::path mJsonPath; + + // Mappings of JSON fields to variables + std::map mFieldMappings; + + // Ordered list of field mapping names to preserve params sequence + std::vector mFieldMappingNames; + + // Default params + ci::params::InterfaceGlRef mParams = nullptr; + + // Signals + SettingsLoadedSignal mSignalSettingsLoaded; + }; + + //================================================== + // Template and inline implementations + + template ValueMapping & SettingsManager::mapField(const std::string & fieldName, T * property) { + mFieldMappingNames.push_back(fieldName); + return mFieldMappings.insert({fieldName, ValueMapping(fieldName, property)}).first->second; } - return mParams; - }; - //================================================== - // Default Properties + template T SettingsManager::getJsonValue(const std::string & field) { + try { + if (!hasJsonValue(field)) { + CI_LOG_W("Field '" << field << "' could not be found"); + return T(); + } + return mJson.getValueForKey(field); + } catch (cinder::Exception e) { + CI_LOG_EXCEPTION("Could not read '" << field << "' from json file", e); + return T(); + } + } - // General - bool mConsole = true; // Use external console window - std::string mAppVersion = "1.0.0"; // Displays app version in the params - - // Graphics - float mFps = 60.0f; - bool mFullscreen = true; - bool mBorderless = false; - bool mVerticalSync = true; - - // Display - int mDisplayIndex = 0; // The index of the display to launch this app on. Reverts to main display if out of bounds. - ci::ivec2 mDisplaySize = ci::ivec2(-1); // The size of one display. Defaults to getWindowSize() - ci::ivec2 mBezelDims = ci::ivec2(0); // The amount of bezel correction to add in both X and Y - int mDisplayColumns = 1; // The number of display columns in a display matrix. Used by ScreenLayout. - int mDisplayRows = 1; // The number of display rows in a display matrix. Used by ScreenLayout. - - // Window - ci::ivec2 mWindowSize = ci::ivec2(INT_MIN); // The window size on launch - ci::ivec2 mWindowPos = ci::ivec2(INT_MIN); // The window position on launch - ci::ColorA mClearColor = ci::ColorA::black(); // The color used when clearing the screen before draw() - - // Camera - ci::vec2 mCameraOffset = ci::ivec2(0); // The offset of the camera on launch - float mCameraZoom = 1.0f; // The zoom of the camera at launch - - // Touches - bool mMouseEnabled = true; // Treat mouse events as touch events. - bool mNativeTouchEnabled = true; // Native touch coming from the OS - bool mSupportMultipleNativeTouchScreens = true; // Native touch with multi-touchscreen support - bool mTuioTouchEnabled = false; // TUIO touch events; It's recommended to disable native touch - // when TUIO is enabled to prevent duplicate events. - - // Debugging - bool mDebugEnabled = true; // Enable/disable all of the below debug features (except for - // hotkeys) - bool mShowTouches = false; // Visualizes all current touch data - bool mShowScreenLayout = false; // Visualizes the current screen layout and bezels - bool mShowCursor = true; // Show or hide the mouse cursor (toggle with C) - bool mShowMinimap = true; // Show or hide the mini-map (toggle with M) - bool mShowStats = true; // Show or hide the frame-rate graph/plot (toggle with S) - bool mMinimizeParams = false; // Minimizes the params window (toggle with F1) - bool mCollapseParams = true; // Collapses all the default parameter groups like "App" - bool mZoomToggleHotkeyEnabled = true; // When true, will bind 0 to toggle zoom to 100%/fit - bool mDisplayIdHotkeysEnabled = false; // When true, will bind 1-9 to zoom directly to displays 1-9 - bool mTouchSimEnabled = false; // When true, will add touch simulator controls to params - float mSimulatedTouchesPerSecond = 50.0f; // Used only if mEnableTouchSim is true - -protected: - static SettingsManagerRef sInstance; - - // Registers a field that will be mapped from the JSON to a property. Call this method from a subclass if you intend - // to add custom JSON fields and map them to class members. - template ValueMapping & mapField(const std::string & fieldName, T * target); - - // Checks if a field exists in the JSON and copies its value to this class in that value if it does. - void mergeField(const std::string & fieldName, const ci::JsonTree & json); - - // Map JSON field names to class members. Called once at the beginning setup(). - virtual void mapFields(); - - // Adds all default command line args and those created by all mapped fields. - virtual void setupCommandArgs(); - - // Parses command line arguments, which can override json settings - virtual void parseCommandArgs(const std::vector & args); - - // Applies parsed settings to ci::app::App::Settings - virtual void applyToAppSettings(ci::app::App::Settings * settings); - - // Helpers to get string from primitive types and strings since we can't call to_string on strings - template inline std::string toString(T * target) { return std::to_string(*target); } - - - // Creates the params object - ci::params::InterfaceGlRef createParams(); - - // Key-based callbacks that are called when a command line argument with that key is passed in - std::map> mCommandArgsHandlers; - - // Base settings json - ci::JsonTree mJson; - ci::fs::path mJsonPath; - - // Mappings of JSON fields to variables - std::map mFieldMappings; - - // Ordered list of field mapping names to preserve params sequence - std::vector mFieldMappingNames; - - // Default params - ci::params::InterfaceGlRef mParams = nullptr; - - // Signals - SettingsLoadedSignal mSignalSettingsLoaded; -}; - -//================================================== -// Template and inline implementations - -template ValueMapping & SettingsManager::mapField(const std::string & fieldName, T * property) { - mFieldMappingNames.push_back(fieldName); - return mFieldMappings.insert({fieldName, ValueMapping(fieldName, property)}).first->second; -} - -template T SettingsManager::getJsonValue(const std::string & field) { - try { - if (!hasJsonValue(field)) { - CI_LOG_W("Field '" << field << "' could not be found"); - return T(); + template <> std::string inline SettingsManager::toString(std::string * target) { + return *target; } - return mJson.getValueForKey(field); - } catch (cinder::Exception e) { - CI_LOG_EXCEPTION("Could not read '" << field << "' from json file", e); - return T(); - } -} - -template <> std::string inline SettingsManager::toString(std::string * target) { - return *target; -} - -} // namespace core + + } // namespace core } // namespace bluecadet