From 3dba30f322c9ccc851a41712e35e64a26932525c Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 17 Feb 2023 12:34:52 -0500 Subject: [PATCH] Place IMGUI UIs into a separate common module (#25131) * Modularize imgui - split windows and ready to add into any app * Comment update * Added extra assert * Switch to using a work scheduler instead of a stack lock for state updates * Restyle * typo fix * Update to sem_wait * Restyle * Generate shutdown event before stoppign the event loop task * Restyle * Update the code to not reference Server (would not compile otherwise) * Use a semaphore to wait for UI to send the shutdown. Also update comments * Restyle --------- Co-authored-by: Andrei Litvin --- examples/common/imgui_ui/BUILD.gn | 41 ++ examples/common/imgui_ui/ui.cpp | 230 ++++++++ examples/common/imgui_ui/ui.h | 65 +++ examples/common/imgui_ui/windows/BUILD.gn | 55 ++ examples/common/imgui_ui/windows/light.cpp | 161 ++++++ examples/common/imgui_ui/windows/light.h | 66 +++ examples/common/imgui_ui/windows/qrcode.cpp | 146 +++++ .../ui.h => common/imgui_ui/windows/qrcode.h} | 30 +- examples/common/imgui_ui/windows/window.h | 47 ++ examples/lighting-app/linux/BUILD.gn | 10 +- examples/lighting-app/linux/main.cpp | 49 +- examples/lighting-app/linux/ui.cpp | 502 ------------------ 12 files changed, 844 insertions(+), 558 deletions(-) create mode 100644 examples/common/imgui_ui/BUILD.gn create mode 100644 examples/common/imgui_ui/ui.cpp create mode 100644 examples/common/imgui_ui/ui.h create mode 100644 examples/common/imgui_ui/windows/BUILD.gn create mode 100644 examples/common/imgui_ui/windows/light.cpp create mode 100644 examples/common/imgui_ui/windows/light.h create mode 100644 examples/common/imgui_ui/windows/qrcode.cpp rename examples/{lighting-app/linux/ui.h => common/imgui_ui/windows/qrcode.h} (60%) create mode 100644 examples/common/imgui_ui/windows/window.h delete mode 100644 examples/lighting-app/linux/ui.cpp diff --git a/examples/common/imgui_ui/BUILD.gn b/examples/common/imgui_ui/BUILD.gn new file mode 100644 index 00000000000000..7818a6012a875d --- /dev/null +++ b/examples/common/imgui_ui/BUILD.gn @@ -0,0 +1,41 @@ +# Copyright (c) 2023 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import("//build_overrides/chip.gni") + +import("${chip_root}/build/chip/tools.gni") +import("${chip_root}/third_party/imgui/imgui.gni") + +config("imgui_ui_config") { + # allow including via 'imgui_ui/....' + include_dirs = [ "../" ] +} + +static_library("imgui_ui") { + sources = [ + "ui.cpp", + "ui.h", + ] + + deps = [ + "${chip_root}/examples/common/imgui_ui/windows", + "${chip_root}/examples/platform/linux:app-main", + "${chip_root}/src/lib/support", + "${chip_root}/third_party/imgui", + ] + + public_configs = [ + ":imgui_ui_config", + "${chip_root}/third_party/imgui:imgui_config", # propagate enabling flag + ] +} diff --git a/examples/common/imgui_ui/ui.cpp b/examples/common/imgui_ui/ui.cpp new file mode 100644 index 00000000000000..c0c04ffb4a9efb --- /dev/null +++ b/examples/common/imgui_ui/ui.cpp @@ -0,0 +1,230 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "ui.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace example { +namespace Ui { +namespace { + +// Controls running the UI event loop +std::atomic gUiRunning{ false }; + +void UiInit(SDL_GLContext * gl_context, SDL_Window ** window) +{ + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) + { + ChipLogError(AppServer, "SDL Init Error: %s\n", SDL_GetError()); + return; + } + +#if defined(__APPLE__) + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); +#else + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); +#endif + +#ifdef SDL_HINT_IME_SHOW_UI + SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); +#endif + + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + *window = SDL_CreateWindow("Light UI", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags); + *gl_context = SDL_GL_CreateContext(*window); + SDL_GL_MakeCurrent(*window, *gl_context); + SDL_GL_SetSwapInterval(1); // Enable vsync + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO & io = ImGui::GetIO(); + (void) io; + // io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + // io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + + ImGui::StyleColorsDark(); + + // Setup Platform/Renderer backends + ImGui_ImplSDL2_InitForOpenGL(*window, *gl_context); + ImGui_ImplOpenGL3_Init(); +} + +void UiShutdown(SDL_GLContext * gl_context, SDL_Window ** window) +{ + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + + SDL_GL_DeleteContext(*gl_context); + SDL_DestroyWindow(*window); + SDL_Quit(); +} + +void EventLoop(ImguiUi * ui) +{ + gUiRunning = true; + SDL_GLContext gl_context; + SDL_Window * window = nullptr; + + UiInit(&gl_context, &window); + + ImGuiIO & io = ImGui::GetIO(); + + while (gUiRunning.load()) + { + SDL_Event event; + while (SDL_PollEvent(&event)) + { + ImGui_ImplSDL2_ProcessEvent(&event); + if ((event.type == SDL_QUIT) || + (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && + event.window.windowID == SDL_GetWindowID(window))) + { + gUiRunning = false; + } + } + + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); + + ui->UpdateState(); + ui->Render(); + + // rendering + ImGui::Render(); + glViewport(0, 0, (int) io.DisplaySize.x, (int) io.DisplaySize.y); + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + SDL_GL_SwapWindow(window); + } + + UiShutdown(&gl_context, &window); + + ChipLogProgress(AppServer, "UI thread Stopped..."); +} + +} // namespace + +void ImguiUi::RunMainLoop() +{ + // Guaranteed to be on the main task (no chip event loop started yet) + ChipLoopLoadInitialState(); + + // Platform event loop will be on a separate thread, + // while the event UI loop will be on the main thread. + chip::DeviceLayer::PlatformMgr().StartEventLoopTask(); + + // SignalSafeStopMainLoop will stop this loop below + // or the loop exits by itself when processing a SDL + // exit (generally by clicking the window close icon). + EventLoop(this); + + // ensure shutdown events are generated (generally basic cluster + // will send a shutdown event to subscribers). + // + // We attempt to wait for finish as the event will be sent sync. + // Since the Main loop is stopped, there will be no MRP, however at least + // one event is attempted to be sent. + chip::DeviceLayer::PlatformMgr().ScheduleWork( + [](intptr_t arg) { + chip::DeviceLayer::PlatformMgr().HandleServerShuttingDown(); + sem_t * semaphore = reinterpret_cast(arg); + sem_post(semaphore); // notify complete + }, + reinterpret_cast(&mChipLoopWaitSemaphore)); + sem_wait(&mChipLoopWaitSemaphore); + + // Stop the chip main loop as well. This is expected to + // wait for the task to finish. + chip::DeviceLayer::PlatformMgr().StopEventLoopTask(); +} + +void ImguiUi::SignalSafeStopMainLoop() +{ + gUiRunning = false; +} + +void ImguiUi::ChipLoopStateUpdate() +{ + assertChipStackLockedByCurrentThread(); + for (auto it = mWindows.begin(); it != mWindows.end(); it++) + { + (*it)->UpdateState(); + } +} + +void ImguiUi::ChipLoopLoadInitialState() +{ + assertChipStackLockedByCurrentThread(); + for (auto it = mWindows.begin(); it != mWindows.end(); it++) + { + (*it)->LoadInitialState(); + } +} + +void ImguiUi::Render() +{ + for (auto it = mWindows.begin(); it != mWindows.end(); it++) + { + (*it)->Render(); + } +} + +void ImguiUi::ChipLoopUpdateCallback(intptr_t self) +{ + ImguiUi * _this = reinterpret_cast(self); + _this->ChipLoopStateUpdate(); + sem_post(&_this->mChipLoopWaitSemaphore); // notify complete +} + +void ImguiUi::UpdateState() +{ + CHIP_ERROR err = chip::DeviceLayer::PlatformMgr().ScheduleWork(&ChipLoopUpdateCallback, reinterpret_cast(this)); + + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "Failed to schedule state update: %" CHIP_ERROR_FORMAT, err.Format()); + return; + } + + // ensure update is done when exiting + sem_wait(&mChipLoopWaitSemaphore); +} + +} // namespace Ui +} // namespace example diff --git a/examples/common/imgui_ui/ui.h b/examples/common/imgui_ui/ui.h new file mode 100644 index 00000000000000..0c365542c11053 --- /dev/null +++ b/examples/common/imgui_ui/ui.h @@ -0,0 +1,65 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +#include + +#include +#include + +namespace example { +namespace Ui { + +/** + * Supports showing a UI using ImGUI + * + * The UI supports several windows, such as QR codes or device control. + */ +class ImguiUi : public AppMainLoopImplementation +{ +public: + ImguiUi() { sem_init(&mChipLoopWaitSemaphore, 0 /* shared */, 0); } + virtual ~ImguiUi() { sem_destroy(&mChipLoopWaitSemaphore); } + + void AddWindow(std::unique_ptr window) { mWindows.push_back(std::move(window)); } + + void UpdateState(); // runs a state update from ember/app + void Render(); // render windows to screen + + // AppMainLoopImplementation + void RunMainLoop() override; + void SignalSafeStopMainLoop() override; + +private: + // First initial state load + void ChipLoopLoadInitialState(); + + // Updates the window states. Run in the CHIP main loop (has access + // to CHIP API calls) + void ChipLoopStateUpdate(); + + sem_t mChipLoopWaitSemaphore; + std::list> mWindows; + + static void ChipLoopUpdateCallback(intptr_t self); +}; + +} // namespace Ui +} // namespace example diff --git a/examples/common/imgui_ui/windows/BUILD.gn b/examples/common/imgui_ui/windows/BUILD.gn new file mode 100644 index 00000000000000..925c29865ca23b --- /dev/null +++ b/examples/common/imgui_ui/windows/BUILD.gn @@ -0,0 +1,55 @@ +# Copyright (c) 2023 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import("//build_overrides/chip.gni") + +import("${chip_root}/build/chip/tools.gni") +import("${chip_root}/third_party/imgui/imgui.gni") + +source_set("windows") { + sources = [ "window.h" ] +} + +static_library("qrcode") { + sources = [ + "qrcode.cpp", + "qrcode.h", + ] + + deps = [ + ":windows", + "${chip_root}/examples/common/QRCode", + "${chip_root}/examples/platform/linux:app-main", + "${chip_root}/src/app/server", + "${chip_root}/src/setup_payload", + "${chip_root}/third_party/imgui", + ] + + public_configs = + [ "${chip_root}/examples/common/QRCode:qrcode-common_config" ] +} + +static_library("light") { + sources = [ + "light.cpp", + "light.h", + ] + + deps = [ + ":windows", + "${chip_root}/src/app/common:cluster-objects", + "${chip_root}/third_party/imgui", + ] + + public_configs = [ "${chip_root}/src:includes" ] +} diff --git a/examples/common/imgui_ui/windows/light.cpp b/examples/common/imgui_ui/windows/light.cpp new file mode 100644 index 00000000000000..f16f2ff9de4150 --- /dev/null +++ b/examples/common/imgui_ui/windows/light.cpp @@ -0,0 +1,161 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "light.h" + +#include + +#include + +#include +#include + +namespace example { +namespace Ui { +namespace Windows { +namespace { + +using namespace chip::app::Clusters; +using chip::app::DataModel::Nullable; + +ImVec4 HueSaturationToColor(float hueDegrees, float saturationPercent) +{ + saturationPercent /= 100.0f; + + float x = saturationPercent * static_cast(1 - fabs(fmod(hueDegrees / 60, 2) - 1)); + + if (hueDegrees < 60) + { + return ImVec4(saturationPercent, x, 0, 1.0f); + } + if (hueDegrees < 120) + { + return ImVec4(x, saturationPercent, 0, 1.0f); + } + if (hueDegrees < 180) + { + return ImVec4(0, saturationPercent, x, 1.0f); + } + if (hueDegrees < 240) + { + return ImVec4(0, x, saturationPercent, 1.0f); + } + if (hueDegrees < 300) + { + return ImVec4(x, 0, saturationPercent, 1.0f); + } + + return ImVec4(saturationPercent, 0, x, 1.0f); +} + +} // namespace + +void Light::UpdateState() +{ + OnOff::Attributes::OnOff::Get(mEndpointId, &mLightIsOn); + + // Level Control + LevelControl::Attributes::CurrentLevel::Get(mEndpointId, mCurrentLevel); + LevelControl::Attributes::MinLevel::Get(mEndpointId, &mMinLevel); + LevelControl::Attributes::MaxLevel::Get(mEndpointId, &mMaxLevel); + LevelControl::Attributes::RemainingTime::Get(mEndpointId, &mLevelRemainingTime10sOfSec); + + // Color control + ColorControl::Attributes::ColorMode::Get(mEndpointId, &mColorMode); + + ColorControl::Attributes::CurrentHue::Get(mEndpointId, &mColorHue); + ColorControl::Attributes::CurrentSaturation::Get(mEndpointId, &mColorSaturation); + ColorControl::Attributes::CurrentX::Get(mEndpointId, &mColorX); + ColorControl::Attributes::CurrentY::Get(mEndpointId, &mColorY); + ColorControl::Attributes::ColorTemperatureMireds::Get(mEndpointId, &mColorTemperatureMireds); +} + +void Light::Render() +{ + ImGui::Begin("Light state"); + ImGui::Text("Light on endpoint %d", mEndpointId); + + ImGui::Text("On-Off:"); + ImGui::Indent(); + if (mLightIsOn) + { + ImGui::Text("Light is ON"); + } + else + { + ImGui::Text("Light is OFF"); + } + + // bright yellow vs dark yellow on/off view + ImGui::ColorButton("LightIsOn", mLightIsOn ? ImVec4(1.0f, 1.0f, 0.0f, 1.0f) : ImVec4(0.3f, 0.3f, 0.0f, 1.0f), + 0 /* ImGuiColorEditFlags_* */, ImVec2(80, 80)); + ImGui::Unindent(); + + ImGui::Text("Level Control:"); + ImGui::Indent(); + ImGui::Text("Remaining Time (1/10s): %d", mLevelRemainingTime10sOfSec); + ImGui::Text("MIN Level: %d", mMinLevel); + ImGui::Text("MAX Level: %d", mMaxLevel); + if (mCurrentLevel.IsNull()) + { + ImGui::Text("Current Level: NULL"); + } + else + { + int levelValue = mCurrentLevel.Value(); + ImGui::SliderInt("Current Level", &levelValue, mMinLevel, mMaxLevel); + } + ImGui::Unindent(); + + ImGui::Text("Color Control:"); + ImGui::Indent(); + const char * mode = // based on ColorMode attribute: spec 3.2.7.9 + (mColorMode == EMBER_ZCL_COLOR_MODE_CURRENT_HUE_AND_CURRENT_SATURATION) + ? "Hue/Saturation" + : (mColorMode == EMBER_ZCL_COLOR_MODE_CURRENT_X_AND_CURRENT_Y) + ? "X/Y" + : (mColorMode == EMBER_ZCL_COLOR_MODE_COLOR_TEMPERATURE) ? "Temperature/Mireds" : "UNKNOWN"; + + ImGui::Text("Mode: %s", mode); + + if (mColorMode == EMBER_ZCL_COLOR_MODE_CURRENT_HUE_AND_CURRENT_SATURATION) + { + const float hueDegrees = (mColorHue * 360.0f) / 254.0f; + const float saturationPercent = 100.0f * (mColorSaturation / 254.0f); + + ImGui::Text("Current Hue: %d (%f deg)", mColorHue, hueDegrees); + ImGui::Text("Current Saturation: %d (%f %%)", mColorSaturation, saturationPercent); + + ImGui::ColorButton("LightColor", HueSaturationToColor(hueDegrees, saturationPercent), 0 /* ImGuiColorEditFlags_* */, + ImVec2(80, 80)); + } + else if (mColorMode == EMBER_ZCL_COLOR_MODE_CURRENT_X_AND_CURRENT_Y) + { + ImGui::Text("Current X: %d", mColorX); + ImGui::Text("Current Y: %d", mColorY); + } + else if (mColorMode == EMBER_ZCL_COLOR_MODE_COLOR_TEMPERATURE) + { + ImGui::Text("Color Temperature Mireds: %d", mColorTemperatureMireds); + } + ImGui::Unindent(); + + ImGui::End(); +} + +} // namespace Windows +} // namespace Ui +} // namespace example diff --git a/examples/common/imgui_ui/windows/light.h b/examples/common/imgui_ui/windows/light.h new file mode 100644 index 00000000000000..3636af5c6c1bd3 --- /dev/null +++ b/examples/common/imgui_ui/windows/light.h @@ -0,0 +1,66 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "window.h" + +#include + +#include +#include + +#include + +namespace example { +namespace Ui { +namespace Windows { + +/** + * Assumes light on a given endpoint with FULL support (on/off, level and color) + */ +class Light : public Window +{ +public: + Light(chip::EndpointId endpointId) : mEndpointId(endpointId) {} + + void UpdateState() override; + void Render() override; + +private: + const chip::EndpointId mEndpointId; + + // OnOff + bool mLightIsOn = false; + + // Level + uint8_t mMinLevel = 0; + uint8_t mMaxLevel = 0; + chip::app::DataModel::Nullable mCurrentLevel; + uint16_t mLevelRemainingTime10sOfSec = 0; + + // Color control + uint8_t mColorMode = EMBER_ZCL_COLOR_MODE_CURRENT_HUE_AND_CURRENT_SATURATION; + uint8_t mColorHue = 0; + uint8_t mColorSaturation = 0; + uint16_t mColorX = 0; + uint16_t mColorY = 0; + uint16_t mColorTemperatureMireds = 0; +}; + +} // namespace Windows +} // namespace Ui +} // namespace example diff --git a/examples/common/imgui_ui/windows/qrcode.cpp b/examples/common/imgui_ui/windows/qrcode.cpp new file mode 100644 index 00000000000000..163110ed254769 --- /dev/null +++ b/examples/common/imgui_ui/windows/qrcode.cpp @@ -0,0 +1,146 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "qrcode.h" + +#include + +#include // examples/platform/linux/Options.h +#include +#include +#include + +namespace example { +namespace Ui { +namespace Windows { +namespace { + +inline ImVec2 operator+(const ImVec2 & a, const ImVec2 & b) +{ + return ImVec2(a.x + b.x, a.y + b.y); +} + +} // namespace + +void QRCode::LoadInitialState() +{ + chip::PayloadContents payload = LinuxDeviceOptions::GetInstance().payload; + if (!payload.isValidQRCodePayload()) + { + return; + } + + char payloadBuffer[chip::QRCodeBasicSetupPayloadGenerator::kMaxQRCodeBase38RepresentationLength + 1]; + chip::MutableCharSpan qrCode(payloadBuffer); + + CHIP_ERROR err = GetQRCode(qrCode, payload); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "Failed to load QR code: %" CHIP_ERROR_FORMAT, err.Format()); + return; + } + + if (qrCode.size() > kMaxQRBufferSize) + { + ChipLogError(AppServer, "Insufficient qr code buffer size to encode"); + return; + } + + uint8_t tempAndData[kMaxQRBufferSize]; + memcpy(tempAndData, qrCode.data(), qrCode.size()); + + mHasQRCode = qrcodegen_encodeBinary(tempAndData, qrCode.size(), mQRData, qrcodegen_Ecc_MEDIUM, qrcodegen_VERSION_MIN, + qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true); + + if (!mHasQRCode) + { + ChipLogError(AppServer, "Failed to encode QR code"); + return; + } +} + +void QRCode::Render() +{ + ImGui::Begin("QR Code."); + if (!mHasQRCode) + { + ImGui::Text("MISSING/ERROR!"); + ImGui::End(); + return; + } + + ImDrawList * drawList = ImGui::GetWindowDrawList(); + + constexpr int kBorderSize = 35; + constexpr int kMinWindowSize = 200; + const int kQRCodeSize = qrcodegen_getSize(mQRData); + + ImVec2 pos = ImGui::GetWindowPos(); + ImVec2 size = ImGui::GetWindowSize(); + + if (size.y < kMinWindowSize) + { + size = ImVec2(kMinWindowSize, kMinWindowSize); + ImGui::SetWindowSize(size); + } + + // Fill the entire window white, then figure out borders + drawList->AddRectFilled(pos, pos + size, IM_COL32_WHITE); + + // add a border + if (size.x >= 2 * kBorderSize && size.y >= 2 * kBorderSize) + { + size.x -= 2 * kBorderSize; + size.y -= 2 * kBorderSize; + pos.x += kBorderSize; + pos.y += kBorderSize; + } + + // create a square rectangle: keep only the smaller side and adjust the + // other + if (size.x > size.y) + { + pos.x += (size.x - size.y) / 2; + size.x = size.y; + } + else if (size.y > size.x) + { + pos.y += (size.y - size.x) / 2; + size.y = size.x; + } + + const ImVec2 squareSize = ImVec2(size.x / static_cast(kQRCodeSize), size.y / static_cast(kQRCodeSize)); + + for (int y = 0; y < kQRCodeSize; ++y) + { + for (int x = 0; x < kQRCodeSize; ++x) + { + if (qrcodegen_getModule(mQRData, x, y)) + { + ImVec2 placement = + ImVec2(pos.x + static_cast(x) * squareSize.x, pos.y + static_cast(y) * squareSize.y); + drawList->AddRectFilled(placement, placement + squareSize, IM_COL32_BLACK); + } + } + } + + ImGui::End(); +} + +} // namespace Windows +} // namespace Ui +} // namespace example diff --git a/examples/lighting-app/linux/ui.h b/examples/common/imgui_ui/windows/qrcode.h similarity index 60% rename from examples/lighting-app/linux/ui.h rename to examples/common/imgui_ui/windows/qrcode.h index d1344201a453a8..ac2cc21bab818c 100644 --- a/examples/lighting-app/linux/ui.h +++ b/examples/common/imgui_ui/windows/qrcode.h @@ -1,6 +1,6 @@ /* + * * Copyright (c) 2023 Project CHIP Authors - * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,22 +14,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - #pragma once +#include "window.h" + +#include + +#include + namespace example { namespace Ui { +namespace Windows { -// MUST be called from within the main thread, to access CHIP global -// data -void Init(); +class QRCode : public Window +{ +public: + void LoadInitialState() override; + void Render() override; -// MAC requires this to be run from the main event loop. Linux supports -// running this from a thread -void EventLoop(); +private: + static constexpr int kQRCodeVersion = qrcodegen_VERSION_MAX; + static constexpr int kMaxQRBufferSize = qrcodegen_BUFFER_LEN_FOR_VERSION(kQRCodeVersion); -// Flags the UI Event loop to stop -void StopEventLoop(); + bool mHasQRCode = false; + uint8_t mQRData[kMaxQRBufferSize] = { 0 }; +}; +} // namespace Windows } // namespace Ui } // namespace example diff --git a/examples/common/imgui_ui/windows/window.h b/examples/common/imgui_ui/windows/window.h new file mode 100644 index 00000000000000..2c4f2598016f29 --- /dev/null +++ b/examples/common/imgui_ui/windows/window.h @@ -0,0 +1,47 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +namespace example { +namespace Ui { + +/** + * Represents a generic UI window for a ImGUI application. + * + * Provides callbacks of loading state, updating state and rendering the + * actual UI. + * + * UI rendering is expected to be done using imgui. + */ +class Window +{ +public: + virtual ~Window() = default; + + // State updates will run in the chip main loop + + virtual void LoadInitialState() {} + virtual void UpdateState() {} + + // Render the UI + // MUST use Imgui rendering, generally within a Begin/End block to + // create a window. + virtual void Render() = 0; +}; + +} // namespace Ui +} // namespace example diff --git a/examples/lighting-app/linux/BUILD.gn b/examples/lighting-app/linux/BUILD.gn index 3b20243631b6de..1ca00e2f431381 100644 --- a/examples/lighting-app/linux/BUILD.gn +++ b/examples/lighting-app/linux/BUILD.gn @@ -49,13 +49,9 @@ executable("chip-lighting-app") { if (chip_examples_enable_imgui_ui) { deps += [ - "${chip_root}/examples/common/QRCode", - "${chip_root}/third_party/imgui", - ] - - sources += [ - "ui.cpp", - "ui.h", + "${chip_root}/examples/common/imgui_ui", + "${chip_root}/examples/common/imgui_ui/windows:light", + "${chip_root}/examples/common/imgui_ui/windows:qrcode", ] } diff --git a/examples/lighting-app/linux/main.cpp b/examples/lighting-app/linux/main.cpp index 5af91faac1227f..1a69bd92a930e4 100644 --- a/examples/lighting-app/linux/main.cpp +++ b/examples/lighting-app/linux/main.cpp @@ -28,7 +28,10 @@ #include #if defined(CHIP_IMGUI_ENABLED) && CHIP_IMGUI_ENABLED -#include "ui.h" +#include +#include +#include + #endif using namespace chip; @@ -78,42 +81,6 @@ void ApplicationInit() #endif } -#if defined(CHIP_IMGUI_ENABLED) && CHIP_IMGUI_ENABLED - -class UiAppMainLoopImplementation : public AppMainLoopImplementation -{ -public: - void RunMainLoop() override; - void SignalSafeStopMainLoop() override; -}; - -void UiAppMainLoopImplementation::RunMainLoop() -{ - // Guaranteed to be on the main task (no chip event loop started yet) - example::Ui::Init(); - - // Platform event loop will be on a separate thread, - // while the event UI loop will be on the main thread. - chip::DeviceLayer::PlatformMgr().StartEventLoopTask(); - - // StopEventLoop will stop the loop below. It is called - // from within SignalSafeStopMainLoop below and - // UI knows how to stop itself if windows are closed. - example::Ui::EventLoop(); - - // Stop the chip main loop as well. This is expected to - // wait for the task to finish. - chip::DeviceLayer::PlatformMgr().StopEventLoopTask(); -} - -void UiAppMainLoopImplementation::SignalSafeStopMainLoop() -{ - Server::GetInstance().GenerateShutDownEvent(); - example::Ui::StopEventLoop(); -} - -#endif - int main(int argc, char * argv[]) { if (ChipLinuxAppInit(argc, argv) != 0) @@ -124,8 +91,12 @@ int main(int argc, char * argv[]) LightingMgr().Init(); #if defined(CHIP_IMGUI_ENABLED) && CHIP_IMGUI_ENABLED - UiAppMainLoopImplementation loop; - ChipLinuxAppMainLoop(&loop); + example::Ui::ImguiUi ui; + + ui.AddWindow(std::make_unique()); + ui.AddWindow(std::make_unique(chip::EndpointId(1))); + + ChipLinuxAppMainLoop(&ui); #else ChipLinuxAppMainLoop(); #endif diff --git a/examples/lighting-app/linux/ui.cpp b/examples/lighting-app/linux/ui.cpp deleted file mode 100644 index a5c03f917e4eb9..00000000000000 --- a/examples/lighting-app/linux/ui.cpp +++ /dev/null @@ -1,502 +0,0 @@ -/* - * Copyright (c) 2023 Project CHIP Authors - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "ui.h" - -#include // examples/platform/linux/Options.h -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace example { -namespace Ui { - -namespace { - -using namespace chip::app::Clusters; -using chip::app::DataModel::Nullable; - -std::atomic gUiRunning{ false }; - -class DeviceState -{ -public: - DeviceState() { sem_init(&mChipLoopWaitSemaphore, 0 /* shared */, 0); } - ~DeviceState() { sem_destroy(&mChipLoopWaitSemaphore); } - - // Initialize. MUST be called within the CHIP main loop as it - // loads startup data. - void Init(); - - // Use ImgUI to show the current state - void ShowUi(); - - // Fetches the current state from Ember - void UpdateState(); - -private: - static constexpr int kQRCodeVersion = qrcodegen_VERSION_MAX; - static constexpr int kMaxQRBufferSize = qrcodegen_BUFFER_LEN_FOR_VERSION(kQRCodeVersion); - static constexpr int kMaxColors = 6; // spec defined maximum - - sem_t mChipLoopWaitSemaphore; - - bool mHasQRCode = false; - uint8_t mQRData[kMaxQRBufferSize] = { 0 }; - - // OnOff - bool mLightIsOn = false; - - // Level - uint8_t mMinLevel = 0; - uint8_t mMaxLevel = 0; - Nullable mCurrentLevel; - uint16_t mLevelRemainingTime10sOfSec = 0; - - // Color control - uint8_t mColorMode = EMBER_ZCL_COLOR_MODE_CURRENT_HUE_AND_CURRENT_SATURATION; - uint8_t mColorHue = 0; - uint8_t mColorSaturation = 0; - uint16_t mColorX = 0; - uint16_t mColorY = 0; - uint16_t mColorTemperatureMireds = 0; - - // Updates the data (run in the chip event loop) - void ChipLoopUpdate(); - - void InitQRCode(); - - // displays a window with the QR Code. - void DrawQRCode(); - - // Run in CHIPMainLoop to access ember in a single threaded - // fashion - static void ChipLoopUpdateCallback(intptr_t self); -}; - -DeviceState gDeviceState; - -/* - * Converts HSV with assumption that V == 100% into a IMGui color vector - */ -ImVec4 HueSaturationToColor(float hueDegrees, float saturationPercent) -{ - saturationPercent /= 100.0f; - - float x = saturationPercent * static_cast(1 - fabs(fmod(hueDegrees / 60, 2) - 1)); - - if (hueDegrees < 60) - { - return ImVec4(saturationPercent, x, 0, 1.0f); - } - if (hueDegrees < 120) - { - return ImVec4(x, saturationPercent, 0, 1.0f); - } - if (hueDegrees < 180) - { - return ImVec4(0, saturationPercent, x, 1.0f); - } - if (hueDegrees < 240) - { - return ImVec4(0, x, saturationPercent, 1.0f); - } - if (hueDegrees < 300) - { - return ImVec4(x, 0, saturationPercent, 1.0f); - } - - return ImVec4(saturationPercent, 0, x, 1.0f); -} - -void DeviceState::Init() -{ - InitQRCode(); -} - -void DeviceState::InitQRCode() -{ - - chip::PayloadContents payload = LinuxDeviceOptions::GetInstance().payload; - if (!payload.isValidQRCodePayload()) - { - return; - } - - char payloadBuffer[chip::QRCodeBasicSetupPayloadGenerator::kMaxQRCodeBase38RepresentationLength + 1]; - chip::MutableCharSpan qrCode(payloadBuffer); - - CHIP_ERROR err = GetQRCode(qrCode, payload); - if (err != CHIP_NO_ERROR) - { - ChipLogError(AppServer, "Failed to load QR code: %" CHIP_ERROR_FORMAT, err.Format()); - return; - } - - if (qrCode.size() > kMaxQRBufferSize) - { - ChipLogError(AppServer, "Insufficient qr code buffer size to encode"); - return; - } - - uint8_t tempAndData[kMaxQRBufferSize]; - memcpy(tempAndData, qrCode.data(), qrCode.size()); - - mHasQRCode = qrcodegen_encodeBinary(tempAndData, qrCode.size(), mQRData, qrcodegen_Ecc_MEDIUM, qrcodegen_VERSION_MIN, - qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true); - - if (!mHasQRCode) - { - ChipLogError(AppServer, "Failed to encode QR code"); - return; - } -} - -inline ImVec2 operator+(const ImVec2 & a, const ImVec2 & b) -{ - return ImVec2(a.x + b.x, a.y + b.y); -} - -void DeviceState::ShowUi() -{ - ImGui::Begin("Light app state"); - - ImGui::Text("On-Off:"); - ImGui::Indent(); - if (mLightIsOn) - { - ImGui::Text("Light is ON"); - } - else - { - ImGui::Text("Light is OFF"); - } - - // bright yellow vs dark yellow on/off view - ImGui::ColorButton("LightIsOn", mLightIsOn ? ImVec4(1.0f, 1.0f, 0.0f, 1.0f) : ImVec4(0.3f, 0.3f, 0.0f, 1.0f), - 0 /* ImGuiColorEditFlags_* */, ImVec2(80, 80)); - ImGui::Unindent(); - - ImGui::Text("Level Control:"); - ImGui::Indent(); - ImGui::Text("Remaining Time (1/10s): %d", mLevelRemainingTime10sOfSec); - ImGui::Text("MIN Level: %d", mMinLevel); - ImGui::Text("MAX Level: %d", mMaxLevel); - if (mCurrentLevel.IsNull()) - { - ImGui::Text("Current Level: NULL"); - } - else - { - int levelValue = mCurrentLevel.Value(); - ImGui::SliderInt("Current Level", &levelValue, mMinLevel, mMaxLevel); - } - ImGui::Unindent(); - - ImGui::Text("Color Control:"); - ImGui::Indent(); - const char * mode = // based on ColorMode attribute: spec 3.2.7.9 - (mColorMode == EMBER_ZCL_COLOR_MODE_CURRENT_HUE_AND_CURRENT_SATURATION) - ? "Hue/Saturation" - : (mColorMode == EMBER_ZCL_COLOR_MODE_CURRENT_X_AND_CURRENT_Y) - ? "X/Y" - : (mColorMode == EMBER_ZCL_COLOR_MODE_COLOR_TEMPERATURE) ? "Temperature/Mireds" : "UNKNOWN"; - - ImGui::Text("Mode: %s", mode); - - if (mColorMode == EMBER_ZCL_COLOR_MODE_CURRENT_HUE_AND_CURRENT_SATURATION) - { - const float hueDegrees = (mColorHue * 360.0f) / 254.0f; - const float saturationPercent = 100.0f * (mColorSaturation / 254.0f); - - ImGui::Text("Current Hue: %d (%f deg)", mColorHue, hueDegrees); - ImGui::Text("Current Saturation: %d (%f %%)", mColorSaturation, saturationPercent); - - ImGui::ColorButton("LightColor", HueSaturationToColor(hueDegrees, saturationPercent), 0 /* ImGuiColorEditFlags_* */, - ImVec2(80, 80)); - } - else if (mColorMode == EMBER_ZCL_COLOR_MODE_CURRENT_X_AND_CURRENT_Y) - { - ImGui::Text("Current X: %d", mColorX); - ImGui::Text("Current Y: %d", mColorY); - } - else if (mColorMode == EMBER_ZCL_COLOR_MODE_COLOR_TEMPERATURE) - { - ImGui::Text("Color Temperature Mireds: %d", mColorTemperatureMireds); - } - ImGui::Unindent(); - - ImGui::End(); - - DrawQRCode(); -} - -void DeviceState::DrawQRCode() -{ - if (!mHasQRCode) - { - return; - } - - ImGui::Begin("QR Code."); - - ImDrawList * drawList = ImGui::GetWindowDrawList(); - - constexpr int kBorderSize = 35; - constexpr int kMinWindowSize = 200; - const int kQRCodeSize = qrcodegen_getSize(mQRData); - - ImVec2 pos = ImGui::GetWindowPos(); - ImVec2 size = ImGui::GetWindowSize(); - - if (size.y < kMinWindowSize) - { - size = ImVec2(kMinWindowSize, kMinWindowSize); - ImGui::SetWindowSize(size); - } - - // Fill the entire window white, then figure out borders - drawList->AddRectFilled(pos, pos + size, IM_COL32_WHITE); - - // add a border - if (size.x >= 2 * kBorderSize && size.y >= 2 * kBorderSize) - { - size.x -= 2 * kBorderSize; - size.y -= 2 * kBorderSize; - pos.x += kBorderSize; - pos.y += kBorderSize; - } - - // create a square rectangle: keep only the smaller side and adjust the - // other - if (size.x > size.y) - { - pos.x += (size.x - size.y) / 2; - size.x = size.y; - } - else if (size.y > size.x) - { - pos.y += (size.y - size.x) / 2; - size.y = size.x; - } - - const ImVec2 squareSize = ImVec2(size.x / static_cast(kQRCodeSize), size.y / static_cast(kQRCodeSize)); - - for (int y = 0; y < kQRCodeSize; ++y) - { - for (int x = 0; x < kQRCodeSize; ++x) - { - if (qrcodegen_getModule(mQRData, x, y)) - { - ImVec2 placement = - ImVec2(pos.x + static_cast(x) * squareSize.x, pos.y + static_cast(y) * squareSize.y); - drawList->AddRectFilled(placement, placement + squareSize, IM_COL32_BLACK); - } - } - } - - ImGui::End(); -} - -void DeviceState::ChipLoopUpdate() -{ - // This will contain a dimmable light - static constexpr chip::EndpointId kLightEndpointId = 1; - - // TODO: - // - consider error checking - { - OnOff::Attributes::OnOff::Get(kLightEndpointId, &mLightIsOn); - - // Level Control - LevelControl::Attributes::CurrentLevel::Get(kLightEndpointId, mCurrentLevel); - LevelControl::Attributes::MinLevel::Get(kLightEndpointId, &mMinLevel); - LevelControl::Attributes::MaxLevel::Get(kLightEndpointId, &mMaxLevel); - LevelControl::Attributes::RemainingTime::Get(kLightEndpointId, &mLevelRemainingTime10sOfSec); - - // Color control - ColorControl::Attributes::ColorMode::Get(kLightEndpointId, &mColorMode); - - ColorControl::Attributes::CurrentHue::Get(kLightEndpointId, &mColorHue); - ColorControl::Attributes::CurrentSaturation::Get(kLightEndpointId, &mColorSaturation); - ColorControl::Attributes::CurrentX::Get(kLightEndpointId, &mColorX); - ColorControl::Attributes::CurrentY::Get(kLightEndpointId, &mColorY); - ColorControl::Attributes::ColorTemperatureMireds::Get(kLightEndpointId, &mColorTemperatureMireds); - } -} - -void DeviceState::ChipLoopUpdateCallback(intptr_t self) -{ - DeviceState * _this = reinterpret_cast(self); - _this->ChipLoopUpdate(); - sem_post(&_this->mChipLoopWaitSemaphore); // notify complete -} - -void DeviceState::UpdateState() -{ - chip::DeviceLayer::PlatformMgr().ScheduleWork(&ChipLoopUpdateCallback, reinterpret_cast(this)); - // ensure update is done when existing - if (sem_trywait(&mChipLoopWaitSemaphore) != 0) - { - if (!gUiRunning.load()) - { - // UI should stop, no need to wait, probably chip main loop is stopped - return; - } - std::this_thread::yield(); - } -} - -void UiInit(SDL_GLContext * gl_context, SDL_Window ** window) -{ - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) - { - ChipLogError(AppServer, "SDL Init Error: %s\n", SDL_GetError()); - return; - } - -#if defined(__APPLE__) - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); -#else - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); -#endif - -#ifdef SDL_HINT_IME_SHOW_UI - SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); -#endif - - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); - SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); - *window = SDL_CreateWindow("Light UI", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags); - *gl_context = SDL_GL_CreateContext(*window); - SDL_GL_MakeCurrent(*window, *gl_context); - SDL_GL_SetSwapInterval(1); // Enable vsync - - // Setup Dear ImGui context - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO & io = ImGui::GetIO(); - (void) io; - // io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls - // io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls - - ImGui::StyleColorsDark(); - - // Setup Platform/Renderer backends - ImGui_ImplSDL2_InitForOpenGL(*window, *gl_context); - ImGui_ImplOpenGL3_Init(); -} - -void UiShutdown(SDL_GLContext * gl_context, SDL_Window ** window) -{ - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplSDL2_Shutdown(); - ImGui::DestroyContext(); - - SDL_GL_DeleteContext(*gl_context); - SDL_DestroyWindow(*window); - SDL_Quit(); -} - -} // namespace - -void Init() -{ - // Init inside the "main" thread, so that it can access globals - // properly (for QR code and such) - gDeviceState.Init(); -} - -void EventLoop() -{ - gUiRunning = true; - SDL_GLContext gl_context; - SDL_Window * window = nullptr; - - UiInit(&gl_context, &window); - - ImGuiIO & io = ImGui::GetIO(); - - while (gUiRunning.load()) - { - SDL_Event event; - while (SDL_PollEvent(&event)) - { - ImGui_ImplSDL2_ProcessEvent(&event); - if ((event.type == SDL_QUIT) || - (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && - event.window.windowID == SDL_GetWindowID(window))) - { - StopEventLoop(); - } - } - - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplSDL2_NewFrame(); - ImGui::NewFrame(); - - gDeviceState.UpdateState(); - gDeviceState.ShowUi(); - - // rendering - ImGui::Render(); - glViewport(0, 0, (int) io.DisplaySize.x, (int) io.DisplaySize.y); - glClearColor(0, 0, 0, 0); - glClear(GL_COLOR_BUFFER_BIT); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - SDL_GL_SwapWindow(window); - } - - UiShutdown(&gl_context, &window); - - ChipLogProgress(AppServer, "UI thread Stopped..."); -} - -void StopEventLoop() -{ - gUiRunning = false; -} - -} // namespace Ui -} // namespace example