diff --git a/Graphics/private/DebugGUI.cpp b/Graphics/private/DebugGUI.cpp index 5066fa3..df779c0 100644 --- a/Graphics/private/DebugGUI.cpp +++ b/Graphics/private/DebugGUI.cpp @@ -37,6 +37,8 @@ DebugGUI::DebugGUI(Window* window) { ImGuiIO& io = ImGui::GetIO(); io.Fonts->AddFontFromFileTTF("Font.ttf", 20.f); + + io.ConfigInputTrickleEventQueue = false; } DebugGUI::~DebugGUI() { diff --git a/Graphics/private/EventHandler.cpp b/Graphics/private/EventHandler.cpp index 502895c..c8fc0d4 100644 --- a/Graphics/private/EventHandler.cpp +++ b/Graphics/private/EventHandler.cpp @@ -4,4 +4,87 @@ using namespace tp; EventHandler::EventHandler() = default; -EventHandler::~EventHandler() = default; \ No newline at end of file +EventHandler::~EventHandler() = default; + +void EventHandler::postEvent(InputID inputID, InputEvent inputEvent) { + mMutex.lock(); + mEventQueue.pushBack({ inputID, inputEvent }); + mMutex.unlock(); +} + +bool EventHandler::isEvents() { + mMutex.lock(); + auto res = mEventQueue.length(); + mMutex.unlock(); + return res; +} + +InputState::State transitions[4][4] = { + { InputState::State::NONE, InputState::State::PRESSED, InputState::State::PRESSED, InputState::State::NONE }, + { InputState::State::PRESSED, InputState::State::PRESSED, InputState::State::HOLD, InputState::State::PRESSED }, + { InputState::State::HOLD, InputState::State::RELEASED, InputState::State::RELEASED, InputState::State::HOLD }, + { InputState::State::RELEASED, InputState::State::NONE, InputState::State::RELEASED, InputState::State::RELEASED }, +}; + +bool transitionsReduce[4][4] = { + { true, true, false, true }, + { true, true, false, true }, + { true, false, true, true }, + { true, false, true, true }, +}; + + +void EventHandler::processEvent() { + mMutex.lock(); + auto lastEvent = mEventQueue.last(); + + const auto& eventData = lastEvent->data.second; + const auto& inputId = lastEvent->data.first; + + switch (eventData.type) { + case InputEvent::Type::MOUSE_POS: + { + mPointer = eventData.mouseEvent; + mEventQueue.popFront(); + break; + } + + case InputEvent::Type::BUTTON_ACTION: + { + auto currentState = (int) mInputStates[(int) inputId].mCurrentState; + auto reportedEvent = (int) eventData.buttonAction; + + mInputStates[(int) inputId].mCurrentState = transitions[currentState][reportedEvent]; + + if (transitionsReduce[currentState][reportedEvent]) { + mEventQueue.popFront(); + } + + break; + } + + default: + { + mEventQueue.popFront(); + } + } + + mMutex.unlock(); +} + +const Vec2F& EventHandler::getPointer() const { return mPointer; } + +bool EventHandler::isPressed(InputID id) const { + return mInputStates[(int) id].mCurrentState == InputState::State::PRESSED; +} + +bool EventHandler::isReleased(InputID id) const { + return mInputStates[(int) id].mCurrentState == InputState::State::RELEASED; +} + +bool EventHandler::isDown(InputID id) const { + return mInputStates[(int) id].mCurrentState == InputState::State::PRESSED || + mInputStates[(int) id].mCurrentState == InputState::State::HOLD; +} + +halnf EventHandler::getScrollY() const { return 0; } \ No newline at end of file diff --git a/Graphics/private/Window.cpp b/Graphics/private/Window.cpp index baea6d9..00ad986 100644 --- a/Graphics/private/Window.cpp +++ b/Graphics/private/Window.cpp @@ -96,6 +96,8 @@ bool Window::shouldClose() const { return glfwWindowShouldClose(mContext->window void Window::processEvents() { glfwWaitEvents(); checkAxisUpdates(); + + glViewport(0, 0, mSize.x, mSize.y); } void Window::draw() { @@ -134,7 +136,7 @@ void Window::checkAxisUpdates() { RectF windowRec = { { 0, 0 }, mSize }; if (windowRec.isInside(mPointerPos)) { - mEventHandler->postEvent(InputID::MOUSE1, { {}, {}, mPointerPos }); + mEventHandler->postEvent(InputID::MOUSE1, { InputEvent::Type::MOUSE_POS, {}, mPointerPos }); } } } @@ -149,15 +151,11 @@ static void keyCallback(GLFWwindow* window, int key, int scancode, int action, i self->checkAxisUpdates(); - // post key event - /* if (action == GLFW_PRESS) { - inputManager->keyStates[key] = GLFW_PRESS; + eventHandler->postEvent((InputID) key, { InputEvent::Type::BUTTON_ACTION, InputEvent::ButtonAction::PRESS, {} }); } else if (action == GLFW_RELEASE) { - inputManager->keyStates[key] = GLFW_RELEASE; + eventHandler->postEvent((InputID) key, { InputEvent::Type::BUTTON_ACTION, InputEvent::ButtonAction::RELEASE, {} }); } - inputManager->isEvent = true; - */ } static void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) { @@ -169,9 +167,14 @@ static void mouseButtonCallback(GLFWwindow* window, int button, int action, int self->checkAxisUpdates(); - // post mouse button event - // inputManager->mouseButtonStates[button] = action; - // inputManager->isEvent = true; + auto id = (InputID) ((int) InputID::MOUSE1 + button); + + if (action == GLFW_PRESS) { + eventHandler->postEvent(id, { InputEvent::Type::BUTTON_ACTION, InputEvent::ButtonAction::PRESS, {} } + ); + } else if (action == GLFW_RELEASE) { + eventHandler->postEvent(id, { InputEvent::Type::BUTTON_ACTION, InputEvent::ButtonAction::RELEASE, {} }); + } } static void scrollCallback(GLFWwindow* window, double xOffset, double yOffset) { diff --git a/Graphics/public/EventHandler.hpp b/Graphics/public/EventHandler.hpp index a0468f4..0f4c3a1 100644 --- a/Graphics/public/EventHandler.hpp +++ b/Graphics/public/EventHandler.hpp @@ -11,14 +11,14 @@ namespace tp { struct InputEvent { enum class Type { - NONE, + NONE = 0, BUTTON_ACTION, MOUSE_DELTA, MOUSE_POS, } type = Type::NONE; enum class ButtonAction { - NONE, + NONE = 0, PRESS, RELEASE, REPEAT, @@ -28,18 +28,6 @@ namespace tp { }; class InputState { - // Transitions from state to state is strict and defined by state order in the enum - // If posted event conflicts with that automata, we need to emulate intermediate states - enum State { - NONE, // Button is inactive - PRESSED, // Button is pressed - HOLD, // Button is still pressed - RELEASED, // Button is released - }; - - State mCurrentState; - State mPreviousState; - public: InputState() = default; @@ -49,6 +37,19 @@ namespace tp { // true if event is no longer needed and false if we need to emulate some transitions further bool isHandled(); + + public: + // Transitions from state to state is strict and defined by state order in the enum + // If posted event conflicts with that automata, we need to emulate intermediate states + enum State { + NONE = 0, // Button is inactive + PRESSED, // Button is pressed + HOLD, // Button is still pressed + RELEASED, // Button is released + }; + + State mCurrentState; + State mPreviousState; }; // Assumes that user and event poster use different threads @@ -62,32 +63,16 @@ namespace tp { public: // Event Poster Interface // Record event - void postEvent(InputID inputID, InputEvent inputEvent) { - mMutex.lock(); - mEventQueue.pushBack({ inputID, inputEvent }); - mMutex.unlock(); - } + void postEvent(InputID inputID, InputEvent inputEvent); public: // User interface - bool isEvents() { - mMutex.lock(); - auto res = mEventQueue.length(); - mMutex.unlock(); - return res; - } - - void processEvent() { - mMutex.lock(); - mEventQueue.popFront(); - mMutex.unlock(); - } + bool isEvents(); + void processEvent(); const Vec2F& getPointer() const; - bool isPressed(InputID id) const; bool isReleased(InputID id) const; bool isDown(InputID id) const; - halnf getScrollY() const; private: @@ -100,7 +85,7 @@ namespace tp { Vec2F mPointer; halnf pressure = 0; - InputState mInputStates[(int) InputID::LAST_KEY_CODE]; + InputState mInputStates[(int) InputID::LAST_KEY_CODE]{}; }; } \ No newline at end of file diff --git a/Graphics/public/InputCodes.hpp b/Graphics/public/InputCodes.hpp index 8bab778..cae8e99 100644 --- a/Graphics/public/InputCodes.hpp +++ b/Graphics/public/InputCodes.hpp @@ -142,8 +142,6 @@ namespace tp { MOUSE3 = 503, MOUSE4 = 504, MOUSE5 = 505, - MOUSE_UP = 506, - MOUSE_DOWN = 507, LAST_KEY_CODE = 508, diff --git a/Widgets/CMakeLists.txt b/Widgets/CMakeLists.txt index 8a66ffa..a045b9a 100644 --- a/Widgets/CMakeLists.txt +++ b/Widgets/CMakeLists.txt @@ -6,7 +6,7 @@ file(GLOB HEADERS "./public/*.hpp") add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS}) target_include_directories(${PROJECT_NAME} PUBLIC ./public/) -target_link_libraries(${PROJECT_NAME} PUBLIC Math Graphics Strings) +target_link_libraries(${PROJECT_NAME} PUBLIC Math Graphics Imgui Strings) ### -------------------------- Tests -------------------------- ### enable_testing() @@ -18,6 +18,6 @@ add_test(NAME Test${PROJECT_NAME} COMMAND Test${PROJECT_NAME}) ### -------------------------- Applications -------------------------- ### add_executable(ExampleGui examples/Entry.cpp) -target_link_libraries(ExampleGui ${PROJECT_NAME} Graphics Imgui Nanovg glfw ${GLEW_LIB}) +target_link_libraries(ExampleGui ${PROJECT_NAME} ${GLEW_LIB}) target_include_directories(ExampleGui PUBLIC ../Externals/glfw/include ${GLEW_INCLUDE_DIR}) file(COPY "examples/Font.ttf" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") \ No newline at end of file diff --git a/Widgets/examples/Entry.cpp b/Widgets/examples/Entry.cpp index 96c0b5c..861b0be 100644 --- a/Widgets/examples/Entry.cpp +++ b/Widgets/examples/Entry.cpp @@ -7,9 +7,7 @@ using namespace tp; class ExampleGUI : public Application { public: - ExampleGUI() { - gGlobalGUIConfig = &mConfig; - } + ExampleGUI() = default; void processFrame(EventHandler* eventHandler) override { auto rec = RectF( { 0, 0 }, mWindow->getSize() ); @@ -21,7 +19,6 @@ public: } private: - GlobalGUIConfig mConfig; ComplexWidget mGui; }; @@ -35,8 +32,13 @@ int main() { } { - ExampleGUI gui; - gui.run(); + GlobalGUIConfig mConfig; + gGlobalGUIConfig = &mConfig; + + { + ExampleGUI gui; + gui.run(); + } } binModule.deinitialize(); diff --git a/Widgets/examples/ExampleGUI.hpp b/Widgets/examples/ExampleGUI.hpp index 57213cb..9a208c8 100644 --- a/Widgets/examples/ExampleGUI.hpp +++ b/Widgets/examples/ExampleGUI.hpp @@ -22,7 +22,7 @@ namespace tp { this->mArea.w = 30; this->mVisible = areaParent.isOverlap(aArea); if (!this->mVisible) return; - mIsHover = aArea.isInside(events.getPos()); + mIsHover = aArea.isInside(events.getPointer()); } void draw(Canvas& canvas) override { @@ -62,7 +62,7 @@ namespace tp { this->mArea.w = 50; this->mVisible = areaParent.isOverlap(aArea); if (!this->mVisible) return; - mIsHover = aArea.isInside(events.getPos()); + mIsHover = aArea.isInside(events.getPointer()); } void draw(Canvas& canvas) override { @@ -181,7 +181,7 @@ namespace tp { } public: - Buffer> mMessages; + Buffer*> mMessages; ScrollableWindow mHistoryView; TextInputWidget mMessage; ButtonWidget mSend; @@ -190,34 +190,34 @@ namespace tp { template class ChattingWidget : public Widget { public: - explicit ChattingWidget() { + ChattingWidget() { this->createConfig("Chatting"); this->addColor("Back", "Background"); this->addValue("Padding", "Padding"); // todo : fetch code - mUsers.append(UserWidget()); - mUsers.append(UserWidget()); - mUsers.append(UserWidget()); + mUsers.append(new UserWidget()); + mUsers.append(new UserWidget()); + mUsers.append(new UserWidget()); - mUsers[0].mArea = { 0, 0, 100, 100 }; - mUsers[1].mArea = { 0, 0, 100, 100 }; - mUsers[2].mArea = { 0, 0, 100, 100 }; + mUsers[0]->mArea = { 0, 0, 100, 100 }; + mUsers[1]->mArea = { 0, 0, 100, 100 }; + mUsers[2]->mArea = { 0, 0, 100, 100 }; for (auto message : mUsers) { - mSideView.mContents.append(&message.data()); + mSideView.mContents.append(message.data()); } - mActive.mMessages.append(MessageWidget()); - mActive.mMessages.append(MessageWidget()); - mActive.mMessages.append(MessageWidget()); + mActive.mMessages.append(new MessageWidget()); + mActive.mMessages.append(new MessageWidget()); + mActive.mMessages.append(new MessageWidget()); - mActive.mMessages[0].mArea = { 0, 0, 100, 100 }; - mActive.mMessages[1].mArea = { 0, 0, 100, 100 }; - mActive.mMessages[2].mArea = { 0, 0, 100, 100 }; + mActive.mMessages[0]->mArea = { 0, 0, 100, 100 }; + mActive.mMessages[1]->mArea = { 0, 0, 100, 100 }; + mActive.mMessages[2]->mArea = { 0, 0, 100, 100 }; for (auto message : mActive.mMessages) { - mActive.mHistoryView.mContents.append(&message.data()); + mActive.mHistoryView.mContents.append(message.data()); } } @@ -240,7 +240,7 @@ namespace tp { } public: - Buffer> mUsers; + Buffer*> mUsers; ScrollableWindow mSideView; ActiveChatWidget mActive; SplitView mSplitView; diff --git a/Widgets/public/ButtonWidget.hpp b/Widgets/public/ButtonWidget.hpp index 7811572..a29db2c 100644 --- a/Widgets/public/ButtonWidget.hpp +++ b/Widgets/public/ButtonWidget.hpp @@ -29,6 +29,9 @@ namespace tp { void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) override { this->mArea = aArea; + this->mVisible = areaParent.isOverlap(aArea); + if (!this->mVisible) return; + mIsHover = false; if (!areaParent.isOverlap(aArea)) { @@ -37,13 +40,13 @@ namespace tp { return; } - mIsHover = aArea.isInside(events.getPos()); + mIsHover = aArea.isInside(events.getPointer()); - if (events.isPressed() && mIsHover) { + if (events.isPressed(InputID::MOUSE1) && mIsHover) { mIsPressed = true; } - if (mIsPressed && mIsHover && events.isReleased()) { + if (mIsPressed && mIsHover && events.isReleased(InputID::MOUSE1)) { mIsReleased = true; mIsPressed = false; } @@ -54,6 +57,8 @@ namespace tp { } void draw(Canvas& canvas) override { + if (!this->mVisible) return; + if (mIsPressed) { canvas.rect(this->mArea, this->getColor("Pressed"), this->getValue("Rounding")); } else if (mIsHover) { diff --git a/Widgets/public/LabelWidget.hpp b/Widgets/public/LabelWidget.hpp index a81d2da..20da9df 100644 --- a/Widgets/public/LabelWidget.hpp +++ b/Widgets/public/LabelWidget.hpp @@ -15,9 +15,13 @@ namespace tp { void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) override { this->mArea = aArea; + this->mVisible = areaParent.isOverlap(aArea); + if (!this->mVisible) return; } void draw(Canvas& canvas) override { + if (!this->mVisible) return; + canvas.text( mLabel.read(), this->mArea, diff --git a/Widgets/public/ScrollableWidget.hpp b/Widgets/public/ScrollableWidget.hpp index 3f38fdd..c60a1ca 100644 --- a/Widgets/public/ScrollableWidget.hpp +++ b/Widgets/public/ScrollableWidget.hpp @@ -22,9 +22,11 @@ namespace tp { // takes whole area void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) override { this->mArea = aArea; + this->mVisible = areaParent.isOverlap(aArea); + if (!this->mVisible) return; auto area = getHandle(); - mHovered = getHandleHandle().isInside(events.getPos()); + mHovered = getHandleHandle().isInside(events.getPointer()); if (mSizeFraction > 1.f) { mPositionFraction = 0; @@ -36,7 +38,7 @@ namespace tp { return; } - if (events.getScrollY() != 0 && areaParent.isInside(events.getPos())) { + if (events.getScrollY() != 0 && areaParent.isInside(events.getPointer())) { auto offset = events.getScrollY() < 0 ? 1.0f : -1.0f; if (scrollInertia * offset > 0) { scrollInertia += offset; @@ -53,14 +55,14 @@ namespace tp { return; } - if (events.isPressed() && area.isInside(events.getPos())) { + if (events.isPressed(InputID::MOUSE1) && area.isInside(events.getPointer())) { mIsScrolling = true; - } else if (!events.isDown()) { + } else if (events.isReleased(InputID::MOUSE1)) { mIsScrolling = false; } if (mIsScrolling) { - tp::halnf pos = events.getPos().y; + tp::halnf pos = events.getPointer().y; pos = (pos - area.y - mSizeFraction * area.w / 2.f) / area.w; mPositionFraction = tp::clamp(pos, 0.f, 1.f - mSizeFraction); } @@ -69,6 +71,8 @@ namespace tp { } void draw(Canvas& canvas) override { + if (!this->mVisible) return; + auto area = getHandle(); if (mSizeFraction > 1.f) return; @@ -142,7 +146,6 @@ namespace tp { void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) override { this->mArea = aArea; this->mVisible = areaParent.isOverlap(aArea); - if (!this->mVisible) return; updateContents(); @@ -170,6 +173,7 @@ namespace tp { void draw(Canvas& canvas) override { if (!this->mVisible) return; + mScroller.draw(canvas); canvas.pushClamp(this->mArea); diff --git a/Widgets/public/SplitViewWidget.hpp b/Widgets/public/SplitViewWidget.hpp index 4fb0f30..2ee1ad8 100644 --- a/Widgets/public/SplitViewWidget.hpp +++ b/Widgets/public/SplitViewWidget.hpp @@ -17,8 +17,9 @@ namespace tp { void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) override { this->mArea = aArea; + this->mVisible = areaParent.isOverlap(aArea); - if (!areaParent.isOverlap(aArea)) { + if (!this->mVisible) { mResizeInProcess = false; return; } @@ -27,12 +28,12 @@ namespace tp { if (events.isPressed(InputID::MOUSE1) && mIsHover) { mResizeInProcess = true; - } else if (!events.isDown()) { + } else if (events.isReleased(InputID::MOUSE1)) { mResizeInProcess = false; } if (mResizeInProcess) { - halnf pos = events.getPos().x; + halnf pos = events.getPointer().x; auto diff = pos - (this->mArea.x + mFactor * this->mArea.z); mFactor += diff / this->mArea.z; } @@ -46,6 +47,8 @@ namespace tp { // takes whole area void draw(Canvas& canvas) override { + if (!this->mVisible) return; + if (mResizeInProcess) canvas.rect(getHandle(), this->getColor("Resizing")); else if (mIsHover) canvas.rect(getHandle(), this->getColor("Hovered")); else canvas.rect(getHandle(), this->getColor("Handle")); diff --git a/Widgets/public/TextInputWidget.hpp b/Widgets/public/TextInputWidget.hpp index 02fcbe4..3960c5c 100644 --- a/Widgets/public/TextInputWidget.hpp +++ b/Widgets/public/TextInputWidget.hpp @@ -20,6 +20,13 @@ namespace tp { void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) override { this->mArea = aArea; + this->mVisible = areaParent.isOverlap(aArea); + if (!this->mVisible) return; + } + + void draw(Canvas& canvas) override { + if (!this->mVisible) return; + nChanged = false; const auto col = this->getColor("Accent"); @@ -58,10 +65,6 @@ namespace tp { ImGui::PopStyleVar(3); } - void draw(Canvas& canvas) override { - // canvas.rect(this->mArea, this->getColor("Base")); - } - enum { mMaxBufferSize = 512 }; char mBuff[mMaxBufferSize] = ""; bool nChanged = false;