Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Place IMGUI UIs into a separate common module #25131

Merged
merged 14 commits into from
Feb 17, 2023
Merged
41 changes: 41 additions & 0 deletions examples/common/imgui_ui/BUILD.gn
Original file line number Diff line number Diff line change
@@ -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
]
}
219 changes: 219 additions & 0 deletions examples/common/imgui_ui/ui.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/*
*
* 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 <SDL.h>
#include <SDL_opengl.h>
#include <imgui.h>
#include <imgui_impl_opengl3.h>
#include <imgui_impl_sdl2.h>

#include <lib/support/logging/CHIPLogging.h>

#include <atomic>
#include <thread>

namespace example {
namespace Ui {
namespace {

// Controls running the UI event loop
std::atomic<bool> 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)
chip::DeviceLayer::PlatformMgr().ScheduleWork([](intptr_t) { chip::DeviceLayer::PlatformMgr().HandleServerShuttingDown(); });

// Stop the chip main loop as well. This is expected to
// wait for the task to finish.
chip::DeviceLayer::PlatformMgr().StopEventLoopTask();
andy31415 marked this conversation as resolved.
Show resolved Hide resolved
}

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++)
andy31415 marked this conversation as resolved.
Show resolved Hide resolved
{
(*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<ImguiUi *>(self);
_this->ChipLoopStateUpdate();
sem_post(&_this->mChipLoopWaitSemaphore); // notify complete
}

void ImguiUi::UpdateState()
{
CHIP_ERROR err = chip::DeviceLayer::PlatformMgr().ScheduleWork(&ChipLoopUpdateCallback, reinterpret_cast<intptr_t>(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
65 changes: 65 additions & 0 deletions examples/common/imgui_ui/ui.h
Original file line number Diff line number Diff line change
@@ -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 <AppMain.h>
#include <imgui_ui/windows/window.h>

#include <semaphore.h>

#include <list>
#include <memory>

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> 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<std::unique_ptr<Window>> mWindows;

static void ChipLoopUpdateCallback(intptr_t self);
};

} // namespace Ui
} // namespace example
55 changes: 55 additions & 0 deletions examples/common/imgui_ui/windows/BUILD.gn
Original file line number Diff line number Diff line change
@@ -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" ]
}
Loading