ini
This commit is contained in:
parent
466c0c36bd
commit
5106cc3b71
30 changed files with 1186 additions and 208 deletions
25
Widgets/CMakeLists.txt
Normal file
25
Widgets/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
project(Widgets)
|
||||
|
||||
### ---------------------- Static Library --------------------- ###
|
||||
file(GLOB SOURCES "./private/*.cpp" "./private/*/*.cpp")
|
||||
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 Strings)
|
||||
|
||||
### -------------------------- Tests -------------------------- ###
|
||||
enable_testing()
|
||||
file(GLOB TEST_SOURCES "./tests/*.cpp")
|
||||
add_executable(${PROJECT_NAME}Tests ${TEST_SOURCES})
|
||||
target_link_libraries(${PROJECT_NAME}Tests ${PROJECT_NAME} Utils)
|
||||
add_test(NAME ${PROJECT_NAME}Tests COMMAND ${PROJECT_NAME}Tests)
|
||||
|
||||
### -------------------------- Applications -------------------------- ###
|
||||
|
||||
add_executable(ExampleGui examples/Entry.cpp)
|
||||
|
||||
target_link_libraries(ExampleGui ${PROJECT_NAME} Graphics Imgui Nanovg glfw ${GLEW_LIB})
|
||||
target_include_directories(ExampleGui PUBLIC ../Externals/glfw/include ${GLEW_INCLUDE_DIR})
|
||||
|
||||
file(COPY "examples/Font.ttf" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/")
|
||||
46
Widgets/examples/Entry.cpp
Normal file
46
Widgets/examples/Entry.cpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
#include "ExampleGUI.hpp"
|
||||
#include "Window.hpp"
|
||||
|
||||
using namespace tp;
|
||||
|
||||
void runApp() {
|
||||
auto window = tp::Window::createWindow(800, 600, "Window 1");
|
||||
|
||||
tp::ComplexWidget<tp::Window::Events, tp::Graphics::Canvas> gui;
|
||||
|
||||
if (window) {
|
||||
while (!window->shouldClose()) {
|
||||
window->processEvents();
|
||||
|
||||
auto area = window->getCanvas().getAvaliableArea();
|
||||
|
||||
gui.proc(window->getEvents(), {}, { area.x, area.y, area.z, area.w });
|
||||
gui.draw(window->getCanvas());
|
||||
|
||||
tp::sleep(20);
|
||||
|
||||
window->draw();
|
||||
}
|
||||
}
|
||||
|
||||
tp::Window::destroyWindow(window);
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
||||
tp::ModuleManifest* deps[] = { &tp::gModuleWidgets, nullptr };
|
||||
tp::ModuleManifest binModule("Chat", nullptr, nullptr, deps);
|
||||
|
||||
if (!binModule.initialize()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
{
|
||||
tp::GlobalGUIConfig config;
|
||||
tp::gGlobalGUIConfig = &config;
|
||||
runApp();
|
||||
}
|
||||
|
||||
binModule.deinitialize();
|
||||
}
|
||||
281
Widgets/examples/ExampleGUI.hpp
Normal file
281
Widgets/examples/ExampleGUI.hpp
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
#pragma once
|
||||
|
||||
#include "Widgets.hpp"
|
||||
|
||||
namespace tp {
|
||||
|
||||
template <typename Events, typename Canvas>
|
||||
class UserWidget : public Widget<Events, Canvas> {
|
||||
public:
|
||||
UserWidget() {
|
||||
this->createConfig("User");
|
||||
this->addColor("Base", "Base");
|
||||
this->addValue("Size", "FontSize");
|
||||
this->addValue("Padding", "Padding");
|
||||
this->addColor("ColUser", "Front");
|
||||
this->addColor("Accent", "Accent");
|
||||
this->addValue("Rounding", "Rounding");
|
||||
}
|
||||
|
||||
void proc(const Events& events, const RectF& areaParent, const RectF& aArea) override {
|
||||
this->mArea = aArea;
|
||||
this->mArea.w = 30;
|
||||
this->mVisible = areaParent.isOverlap(aArea);
|
||||
if (!this->mVisible) return;
|
||||
mIsHover = aArea.isInside(events.getPos());
|
||||
}
|
||||
|
||||
void draw(Canvas& canvas) const override {
|
||||
if (!this->mVisible) return;
|
||||
|
||||
if (mIsHover) canvas.rect(this->mArea, this->getColor("Accent"), this->getValue("Rounding"));
|
||||
else canvas.rect(this->mArea, this->getColor("Base"), this->getValue("Rounding"));
|
||||
|
||||
const auto padding = this->getValue("Padding");
|
||||
const auto size = this->getValue("Size");
|
||||
const auto colUser = this->getColor("ColUser");
|
||||
|
||||
canvas.text(mUser.read(), this->mArea, size, Canvas::CC, padding, colUser);
|
||||
}
|
||||
|
||||
public:
|
||||
String mUser = "UserName";
|
||||
bool mIsHover = false;
|
||||
};
|
||||
|
||||
template <typename Events, typename Canvas>
|
||||
class MessageWidget : public Widget<Events, Canvas> {
|
||||
public:
|
||||
MessageWidget() {
|
||||
this->createConfig("Message");
|
||||
this->addColor("Base", "Base");
|
||||
this->addValue("Size", "FontSize");
|
||||
this->addValue("SizeUser", "FontSizeDim");
|
||||
this->addValue("Padding", "Padding");
|
||||
this->addColor("ColUser", "Front");
|
||||
this->addColor("Col", "FrontDim");
|
||||
this->addValue("Rounding", "Rounding");
|
||||
}
|
||||
|
||||
void proc(const Events& events, const RectF& areaParent, const RectF& aArea) override {
|
||||
this->mArea = aArea;
|
||||
this->mArea.w = 50;
|
||||
this->mVisible = areaParent.isOverlap(aArea);
|
||||
if (!this->mVisible) return;
|
||||
mIsHover = aArea.isInside(events.getPos());
|
||||
}
|
||||
|
||||
void draw(Canvas& canvas) const override {
|
||||
if (!this->mVisible) return;
|
||||
|
||||
if (mIsHover) canvas.rect(this->mArea, this->getColor("Base"), this->getValue("Rounding"));
|
||||
|
||||
const auto padding = this->getValue("Padding");
|
||||
const auto size = this->getValue("Size");
|
||||
const auto sizeUser = this->getValue("SizeUser");
|
||||
const auto col = this->getColor("Col");
|
||||
const auto colUser = this->getColor("ColUser");
|
||||
|
||||
auto userName = this->mArea;
|
||||
userName.w = 25;
|
||||
|
||||
auto content = this->mArea;
|
||||
content.y = userName.y + userName.w;
|
||||
content.w = this->mArea.w - userName.w;
|
||||
|
||||
canvas.text(mContent.read(), content, size, Canvas::LC, padding, col);
|
||||
canvas.text(mUser.read(), userName, sizeUser, Canvas::LC, padding, colUser);
|
||||
}
|
||||
|
||||
public:
|
||||
String mContent = "Message Content";
|
||||
String mUser = "UserName";
|
||||
bool mIsHover = false;
|
||||
};
|
||||
|
||||
template <typename Events, typename Canvas>
|
||||
class LoginWidget : public Widget<Events, Canvas> {
|
||||
public:
|
||||
explicit LoginWidget() {
|
||||
this->createConfig("Login");
|
||||
this->addColor("Back", "Background");
|
||||
mPass.mId = "pass";
|
||||
mUser.mId = "user";
|
||||
mButton.mLabel.mLabel = "Login";
|
||||
}
|
||||
|
||||
void proc(const Events& events, const RectF& areaParent, const RectF& aArea) override {
|
||||
this->mArea = aArea;
|
||||
mLogged = false;
|
||||
|
||||
const auto xval = aArea.z / 2 - 100;
|
||||
mUser.proc(events, aArea, { xval, 10, 200, 30 });
|
||||
mPass.proc(events, aArea, { xval, 50, 200, 30 });
|
||||
mButton.proc(events, aArea, { xval, 90, 200, 30 });
|
||||
|
||||
if (mButton.mIsReleased) {
|
||||
mButton.mIsReleased = false;
|
||||
mLogged = true;
|
||||
}
|
||||
}
|
||||
|
||||
void draw(Canvas& canvas) const override {
|
||||
canvas.rect(this->mArea, this->getColor("Back"));
|
||||
mButton.draw(canvas);
|
||||
mUser.draw(canvas);
|
||||
mPass.draw(canvas);
|
||||
}
|
||||
|
||||
public:
|
||||
TextInputWidget<Events, Canvas> mUser;
|
||||
TextInputWidget<Events, Canvas> mPass;
|
||||
ButtonWidget<Events, Canvas> mButton;
|
||||
bool mLogged = false;
|
||||
};
|
||||
|
||||
template <typename Events, typename Canvas>
|
||||
class ActiveChatWidget : public Widget<Events, Canvas> {
|
||||
public:
|
||||
ActiveChatWidget() {
|
||||
this->createConfig("ActiveChat");
|
||||
this->addColor("Back", "Background");
|
||||
this->addValue("Padding", "Padding");
|
||||
mSend.mLabel.mLabel = "Send";
|
||||
mMessage.mId = "Message";
|
||||
}
|
||||
|
||||
void proc(const Events& events, const RectF& areaParent, const RectF& aArea) override {
|
||||
this->mArea = aArea;
|
||||
|
||||
auto history = this->mArea;
|
||||
history.w -= 50;
|
||||
|
||||
auto input = this->mArea;
|
||||
input.y = history.w + 10;
|
||||
input.w = 40 - this->getValue("Padding");
|
||||
input.x += this->getValue("Padding");
|
||||
input.z -= this->getValue("Padding");
|
||||
|
||||
auto inputMessage = input;
|
||||
inputMessage.z -= 100;
|
||||
|
||||
auto inputSend = input;
|
||||
inputSend.x = inputMessage.x + inputMessage.z + this->getValue("Padding");
|
||||
inputSend.z = 100 - this->getValue("Padding") * 2;
|
||||
|
||||
mSend.proc(events, this->mArea, inputSend);
|
||||
mMessage.proc(events, this->mArea, inputMessage);
|
||||
|
||||
if (mSend.mIsReleased) {
|
||||
mSend.mIsReleased = false;
|
||||
}
|
||||
|
||||
mHistoryView.proc(events, this->mArea, history);
|
||||
}
|
||||
|
||||
void draw(Canvas& canvas) const override {
|
||||
canvas.rect(this->mArea, this->getColor("Back"));
|
||||
mHistoryView.draw(canvas);
|
||||
mMessage.draw(canvas);
|
||||
mSend.draw(canvas);
|
||||
}
|
||||
|
||||
public:
|
||||
Buffer<MessageWidget<Events, Canvas>> mMessages;
|
||||
ScrollableWindow<Events, Canvas> mHistoryView;
|
||||
TextInputWidget<Events, Canvas> mMessage;
|
||||
ButtonWidget<Events, Canvas> mSend;
|
||||
};
|
||||
|
||||
template <typename Events, typename Canvas>
|
||||
class ChattingWidget : public Widget<Events, Canvas> {
|
||||
public:
|
||||
explicit ChattingWidget() {
|
||||
this->createConfig("Chatting");
|
||||
this->addColor("Back", "Background");
|
||||
this->addValue("Padding", "Padding");
|
||||
|
||||
// todo : fetch code
|
||||
mUsers.append(UserWidget<Events, Canvas>());
|
||||
mUsers.append(UserWidget<Events, Canvas>());
|
||||
mUsers.append(UserWidget<Events, Canvas>());
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
mActive.mMessages.append(MessageWidget<Events, Canvas>());
|
||||
mActive.mMessages.append(MessageWidget<Events, Canvas>());
|
||||
mActive.mMessages.append(MessageWidget<Events, Canvas>());
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
void proc(const Events& events, const RectF& areaParent, const RectF& aArea) override {
|
||||
this->mArea = aArea;
|
||||
|
||||
mSplitView.proc(events, aArea, aArea);
|
||||
|
||||
const auto padding = this->getValue("Padding");
|
||||
mSideView.proc(events, this->mArea, mSplitView.getSecond());
|
||||
|
||||
mActive.proc(events, aArea, mSplitView.getFirst());
|
||||
}
|
||||
|
||||
void draw(Canvas& canvas) const override {
|
||||
canvas.rect(this->mArea, this->getColor("Back"));
|
||||
mSplitView.draw(canvas);
|
||||
mSideView.draw(canvas);
|
||||
mActive.draw(canvas);
|
||||
}
|
||||
|
||||
public:
|
||||
Buffer<UserWidget<Events, Canvas>> mUsers;
|
||||
ScrollableWindow<Events, Canvas> mSideView;
|
||||
ActiveChatWidget<Events, Canvas> mActive;
|
||||
SplitView<Events, Canvas> mSplitView;
|
||||
};
|
||||
|
||||
template <typename Events, typename Canvas>
|
||||
class ComplexWidget : public Widget<Events, Canvas> {
|
||||
public:
|
||||
ComplexWidget() {
|
||||
this->createConfig("chat");
|
||||
this->addColor("Back", "Background");
|
||||
}
|
||||
|
||||
void proc(const Events& events, const RectF& areaParent, const RectF& aArea) override {
|
||||
this->mArea = aArea;
|
||||
if (mLogged) {
|
||||
mChatting.proc(events, aArea, aArea);
|
||||
} else {
|
||||
mLogin.proc(events, aArea, aArea);
|
||||
mLogged = mLogin.mLogged;
|
||||
if (mLogged) {
|
||||
mChatting.proc(events, aArea, aArea);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void draw(Canvas& canvas) const override {
|
||||
canvas.rect(this->mArea, this->getColor("Back"));
|
||||
if (mLogged) mChatting.draw(canvas);
|
||||
else mLogin.draw(canvas);
|
||||
}
|
||||
|
||||
private:
|
||||
bool mLogged = false;
|
||||
LoginWidget<Events, Canvas> mLogin;
|
||||
ChattingWidget<Events, Canvas> mChatting;
|
||||
};
|
||||
}
|
||||
BIN
Widgets/examples/Font.ttf
Normal file
BIN
Widgets/examples/Font.ttf
Normal file
Binary file not shown.
103
Widgets/private/Animations.cpp
Normal file
103
Widgets/private/Animations.cpp
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
|
||||
#include "Animations.hpp"
|
||||
|
||||
using namespace tp;
|
||||
|
||||
bool AnimValue::gInTransition = false;
|
||||
|
||||
halnf AnimValue::interpolate() const {
|
||||
if (!mTimeAnim) {
|
||||
return mVal;
|
||||
}
|
||||
|
||||
auto dt = gCurrentTime - mTimeStart;
|
||||
if (dt > mTimeAnim) dt = mTimeAnim;
|
||||
auto t = (halnf) dt / (halnf) mTimeAnim;
|
||||
t = (halnf) (0.511 + atan((t - 0.36) * 28) / 2.9);
|
||||
t = clamp(t, 0.f, 1.f);
|
||||
auto out = mValPrev + (mVal - mValPrev) * t;
|
||||
return out;
|
||||
}
|
||||
|
||||
AnimValue::AnimValue() {
|
||||
mTimeStart = gCurrentTime;
|
||||
}
|
||||
|
||||
void AnimValue::setAnimTime(halni time) { mTimeAnim = time; }
|
||||
|
||||
AnimValue::AnimValue(halnf val) {
|
||||
mVal = val;
|
||||
mValPrev = mVal;
|
||||
mTimeStart = gCurrentTime;
|
||||
}
|
||||
|
||||
bool AnimValue::inTransition() const {
|
||||
auto timePassed = gCurrentTime - mTimeStart >= mTimeAnim;
|
||||
return !timePassed;
|
||||
}
|
||||
|
||||
void AnimValue::set(halnf val) {
|
||||
if (!inTransition()) mValPrev = mVal;
|
||||
if (val == mVal) return;
|
||||
mValPrev = get();
|
||||
mVal = val;
|
||||
if (!inTransition()) {
|
||||
mTimeStart = gCurrentTime;
|
||||
}
|
||||
}
|
||||
|
||||
halnf AnimValue::getTarget() const { return mVal; }
|
||||
|
||||
void AnimValue::setNoTransition(halnf val) {
|
||||
mValPrev = val;
|
||||
mVal = val;
|
||||
mTimeStart = gCurrentTime - mTimeAnim;
|
||||
}
|
||||
|
||||
halnf AnimValue::get() const {
|
||||
if (inTransition()) {
|
||||
gInTransition = true;
|
||||
return interpolate();
|
||||
}
|
||||
return mVal;
|
||||
}
|
||||
|
||||
void AnimValue::operator=(halnf val) {
|
||||
setNoTransition(val);
|
||||
}
|
||||
|
||||
AnimValue::operator halnf() const { return get(); }
|
||||
|
||||
RectF AnimRect::get() const { return { x.get(), y.get(), z.get(), w.get() }; }
|
||||
|
||||
RectF AnimRect::getTarget() const { return { x.getTarget(), y.getTarget(), z.getTarget(), w.getTarget() }; }
|
||||
|
||||
void AnimRect::setNoTransition(RectF rec) {
|
||||
x.setNoTransition(rec.x);
|
||||
y.setNoTransition(rec.y);
|
||||
z.setNoTransition(rec.z);
|
||||
w.setNoTransition(rec.w);
|
||||
}
|
||||
|
||||
void AnimRect::setAnimTime(const halni time) {
|
||||
x.setAnimTime(time);
|
||||
y.setAnimTime(time);
|
||||
z.setAnimTime(time);
|
||||
w.setAnimTime(time);
|
||||
}
|
||||
|
||||
void AnimRect::set(const RectF& in) {
|
||||
x.set(in.x);
|
||||
y.set(in.y);
|
||||
z.set(in.z);
|
||||
w.set(in.w);
|
||||
}
|
||||
|
||||
AnimColor::AnimColor() : mColor() {}
|
||||
|
||||
RGBA AnimColor::get() const {
|
||||
auto col = mColor.get();
|
||||
return { col.x, col.y, col.z, col.w };
|
||||
}
|
||||
|
||||
void AnimColor::set(const RGBA& col) { mColor.set(RectF(col.r, col.g, col.b, col.a)); }
|
||||
9
Widgets/private/WidgetConfig.cpp
Normal file
9
Widgets/private/WidgetConfig.cpp
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
#include "WidgetBase.hpp"
|
||||
|
||||
namespace tp {
|
||||
GlobalGUIConfig* gGlobalGUIConfig = nullptr;
|
||||
|
||||
ModuleManifest* deps[] = { &gModuleMath, &gModuleStrings, nullptr };
|
||||
ModuleManifest gModuleWidgets = ModuleManifest("Widgets", nullptr, nullptr, deps);
|
||||
}
|
||||
57
Widgets/public/Animations.hpp
Normal file
57
Widgets/public/Animations.hpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
|
||||
#include "Color.hpp"
|
||||
#include "Rect.hpp"
|
||||
#include "Timing.hpp"
|
||||
|
||||
namespace tp {
|
||||
|
||||
class AnimValue {
|
||||
halnf mValPrev = 0;
|
||||
halnf mVal = 0;
|
||||
time_ms mTimeStart = 0;
|
||||
halni mTimeAnim = 250;
|
||||
|
||||
static bool gInTransition;
|
||||
|
||||
private:
|
||||
[[nodiscard]] halnf interpolate() const;
|
||||
|
||||
public:
|
||||
AnimValue();
|
||||
explicit AnimValue(halnf val);
|
||||
|
||||
[[nodiscard]] halnf prev() const { return mValPrev; }
|
||||
void set(halnf val);
|
||||
void setNoTransition(halnf);
|
||||
void setAnimTime(halni time);
|
||||
[[nodiscard]] halnf get() const;
|
||||
[[nodiscard]] halnf getTarget() const;
|
||||
[[nodiscard]] bool inTransition() const;
|
||||
explicit operator halnf() const;
|
||||
void operator=(halnf val);
|
||||
};
|
||||
|
||||
class AnimRect : Rect<AnimValue> {
|
||||
public:
|
||||
AnimRect() {
|
||||
setAnimTime(450);
|
||||
setNoTransition({ 0, 0, 0, 0 });
|
||||
}
|
||||
|
||||
[[nodiscard]] RectF get() const;
|
||||
[[nodiscard]] RectF getTarget() const;
|
||||
void setNoTransition(RectF);
|
||||
void set(const RectF&);
|
||||
void setAnimTime(halni time_ms);
|
||||
};
|
||||
|
||||
class AnimColor {
|
||||
public:
|
||||
AnimColor();
|
||||
AnimRect mColor;
|
||||
|
||||
[[nodiscard]] RGBA get() const;
|
||||
void set(const RGBA&);
|
||||
};
|
||||
};
|
||||
61
Widgets/public/ButtonWidget.hpp
Normal file
61
Widgets/public/ButtonWidget.hpp
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
#include "LabelWidget.hpp"
|
||||
|
||||
namespace tp {
|
||||
|
||||
template <typename Events, typename Canvas>
|
||||
class ButtonWidget : public Widget<Events, Canvas> {
|
||||
public:
|
||||
ButtonWidget() {
|
||||
this->createConfig("Button");
|
||||
this->addColor("Pressed", "Action");
|
||||
this->addColor("Hovered", "Interaction");
|
||||
this->addColor("Default", "Accent");
|
||||
this->addValue("Rounding", "Rounding");
|
||||
}
|
||||
|
||||
void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) override {
|
||||
this->mArea = aArea;
|
||||
mIsHover = false;
|
||||
|
||||
if (!areaParent.isOverlap(aArea)) {
|
||||
mIsReleased = false;
|
||||
mIsPressed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mIsHover = aArea.isInside(events.getPos());
|
||||
|
||||
if (events.isPressed() && mIsHover) {
|
||||
mIsPressed = true;
|
||||
}
|
||||
|
||||
if (mIsPressed && mIsHover && events.isReleased()) {
|
||||
mIsReleased = true;
|
||||
mIsPressed = false;
|
||||
}
|
||||
|
||||
if (!mIsHover) mIsPressed = false;
|
||||
|
||||
mLabel.proc(events, aArea, aArea);
|
||||
}
|
||||
|
||||
void draw(Canvas& canvas) const override {
|
||||
if (mIsPressed) {
|
||||
canvas.rect(this->mArea, this->getColor("Pressed"), this->getValue("Rounding"));
|
||||
} else if (mIsHover) {
|
||||
canvas.rect(this->mArea, this->getColor("Hovered"), this->getValue("Rounding"));
|
||||
} else {
|
||||
canvas.rect(this->mArea, this->getColor("Default"), this->getValue("Rounding"));
|
||||
}
|
||||
mLabel.draw(canvas);
|
||||
}
|
||||
|
||||
public:
|
||||
LabelWidget<Events, Canvas> mLabel;
|
||||
bool mIsHover = false;
|
||||
bool mIsPressed = false;
|
||||
bool mIsReleased = false;
|
||||
};
|
||||
}
|
||||
34
Widgets/public/LabelWidget.hpp
Normal file
34
Widgets/public/LabelWidget.hpp
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include "WidgetBase.hpp"
|
||||
|
||||
namespace tp {
|
||||
template <typename Events, typename Canvas>
|
||||
class LabelWidget : public Widget<Events, Canvas> {
|
||||
public:
|
||||
LabelWidget() {
|
||||
this->createConfig("Label");
|
||||
this->addValue("Size", "FontSize");
|
||||
this->addValue("Padding", "Padding");
|
||||
this->addColor("Default", "Front");
|
||||
}
|
||||
|
||||
void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) override {
|
||||
this->mArea = aArea;
|
||||
}
|
||||
|
||||
void draw(Canvas& canvas) const override {
|
||||
canvas.text(
|
||||
mLabel.read(),
|
||||
this->mArea,
|
||||
this->getValue("Size"),
|
||||
Canvas::CC,
|
||||
this->getValue("Padding"),
|
||||
this->getColor("Default")
|
||||
);
|
||||
}
|
||||
|
||||
public:
|
||||
String mLabel = "Label";
|
||||
};
|
||||
}
|
||||
218
Widgets/public/ScrollableWidget.hpp
Normal file
218
Widgets/public/ScrollableWidget.hpp
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
#pragma once
|
||||
|
||||
#include "WidgetBase.hpp"
|
||||
|
||||
namespace tp {
|
||||
|
||||
template <typename Events, typename Canvas>
|
||||
class ScrollBarWidget : public Widget<Events, Canvas> {
|
||||
public:
|
||||
ScrollBarWidget() {
|
||||
this->createConfig("ScrollBar");
|
||||
this->addColor("Default", "Base");
|
||||
this->addColor("Handle", "Accent");
|
||||
this->addColor("Hovered", "Interaction");
|
||||
this->addColor("Scrolling", "Action");
|
||||
this->addValue("Padding", "Padding");
|
||||
this->addValue("HandleSize", 20.f);
|
||||
this->addValue("MinSize", 20.f);
|
||||
this->addValue("Rounding", "Rounding");
|
||||
}
|
||||
|
||||
// takes whole area
|
||||
void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) override {
|
||||
this->mArea = aArea;
|
||||
|
||||
auto area = getHandle();
|
||||
mHovered = getHandleHandle().isInside(events.getPos());
|
||||
|
||||
if (mSizeFraction > 1.f) {
|
||||
mPositionFraction = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!areaParent.isOverlap(area)) {
|
||||
mIsScrolling = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (events.getScrollY() != 0 && areaParent.isInside(events.getPos())) {
|
||||
auto offset = events.getScrollY() < 0 ? 1.0f : -1.0f;
|
||||
if (scrollInertia * offset > 0) {
|
||||
scrollInertia += offset;
|
||||
} else {
|
||||
scrollInertia = -scrollInertia + offset;
|
||||
}
|
||||
}
|
||||
|
||||
if (tp::abs(scrollInertia) > 0.1f) {
|
||||
auto offset = scrollInertia * mScrollFactor;
|
||||
mPositionFraction += offset;
|
||||
mPositionFraction = tp::clamp(mPositionFraction, 0.f, 1.f - mSizeFraction);
|
||||
scrollInertia *= 0.f;
|
||||
return;
|
||||
}
|
||||
|
||||
if (events.isPressed() && area.isInside(events.getPos())) {
|
||||
mIsScrolling = true;
|
||||
} else if (!events.isDown()) {
|
||||
mIsScrolling = false;
|
||||
}
|
||||
|
||||
if (mIsScrolling) {
|
||||
tp::halnf pos = events.getPos().y;
|
||||
pos = (pos - area.y - mSizeFraction * area.w / 2.f) / area.w;
|
||||
mPositionFraction = tp::clamp(pos, 0.f, 1.f - mSizeFraction);
|
||||
}
|
||||
|
||||
mPositionFraction = tp::clamp(mPositionFraction, 0.f, 1.f - mSizeFraction);
|
||||
}
|
||||
|
||||
void draw(Canvas& canvas) const override {
|
||||
auto area = getHandle();
|
||||
|
||||
if (mSizeFraction > 1.f) return;
|
||||
// if (!areaParent.isOverlap(getHandle())) return;
|
||||
|
||||
tp::RGBA col = this->getColor("Handle");
|
||||
|
||||
if (mIsScrolling) {
|
||||
col = this->getColor("Scrolling");
|
||||
} else if (mHovered) {
|
||||
col = this->getColor("Hovered");
|
||||
}
|
||||
|
||||
canvas.rect(area, this->getColor("Default"), this->getValue("Rounding"));
|
||||
|
||||
canvas.rect(getHandleHandle(), col, this->getValue("Rounding"));
|
||||
}
|
||||
|
||||
RectF getHandleHandle() const {
|
||||
auto minSize = this->getValue("MinSize");
|
||||
if (mIsScrolling) {
|
||||
minSize = this->getValue("MinSize") * 2;
|
||||
} else if (mHovered) {
|
||||
minSize = this->getValue("MinSize") * 2;
|
||||
}
|
||||
auto area = getHandle();
|
||||
auto sliderSize = tp::clamp(area.w * mSizeFraction, minSize, area.w);
|
||||
auto diffSize = sliderSize - area.w * mSizeFraction;
|
||||
return { area.x, area.y + (area.w - diffSize) * mPositionFraction, area.z, sliderSize };
|
||||
}
|
||||
|
||||
RectF getViewport() const {
|
||||
if (mSizeFraction > 1.f) {
|
||||
return this->mArea;
|
||||
}
|
||||
return { this->mArea.x, this->mArea.y, this->mArea.z - this->getValue("HandleSize"), this->mArea.w };
|
||||
}
|
||||
|
||||
RectF getHandle() const {
|
||||
return { this->mArea.x + this->mArea.z - this->getValue("HandleSize") + this->getValue("Padding"),
|
||||
this->mArea.y + this->getValue("Padding"),
|
||||
this->getValue("HandleSize") - this->getValue("Padding") * 2,
|
||||
this->mArea.w - this->getValue("Padding") * 2 };
|
||||
}
|
||||
|
||||
public:
|
||||
halnf mScrollFactor = 0.f;
|
||||
halnf scrollInertia = 0.f;
|
||||
bool mIsScrolling = false;
|
||||
halnf mSizeFraction = 1.f;
|
||||
halnf mPositionFraction = 0.f;
|
||||
bool mHovered = false;
|
||||
};
|
||||
|
||||
template <typename Events, typename Canvas>
|
||||
class ScrollableWindow : public Widget<Events, Canvas> {
|
||||
public:
|
||||
ScrollableWindow() {
|
||||
this->createConfig("ScrollableWindow");
|
||||
this->addColor("Default", "Base");
|
||||
this->addValue("Padding", "Padding");
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
updateContents();
|
||||
updateContentSize();
|
||||
|
||||
const auto padding = this->getValue("Padding");
|
||||
|
||||
mScroller.mSizeFraction = this->mArea.w / mContentSize;
|
||||
mScroller.proc(events, this->mArea, this->mArea);
|
||||
|
||||
if (mScroller.mSizeFraction > 1.f) {
|
||||
setOffset(0);
|
||||
} else {
|
||||
setOffset((-mScroller.mPositionFraction) * mContentSize);
|
||||
}
|
||||
|
||||
for (auto widget : mContents) {
|
||||
widget->proc(
|
||||
events,
|
||||
this->mArea,
|
||||
{ this->mArea.x + padding, widget->mArea.y, mScroller.getViewport().z - padding * 2, widget->mArea.w }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void draw(Canvas& canvas) const override {
|
||||
if (!this->mVisible) return;
|
||||
mScroller.draw(canvas);
|
||||
|
||||
canvas.pushClamp(this->mArea);
|
||||
for (auto widget : mContents) {
|
||||
widget->draw(canvas);
|
||||
}
|
||||
canvas.popClamp();
|
||||
}
|
||||
|
||||
private:
|
||||
void updateContents() {
|
||||
if (mContents.size()) {
|
||||
const auto padding = this->getValue("Padding");
|
||||
const halnf offset = mContents.first()->mArea.y + padding;
|
||||
|
||||
halnf start = 0;
|
||||
for (auto widget : mContents) {
|
||||
widget->mArea.y = start;
|
||||
start += widget->mArea.w + padding;
|
||||
}
|
||||
|
||||
for (auto widget : mContents) {
|
||||
widget->mArea.y += offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateContentSize() {
|
||||
mContentSize = 0;
|
||||
if (mContents.size()) {
|
||||
const auto padding = this->getValue("Padding");
|
||||
mContentSize = mContents.last()->mArea.y - mContents.first()->mArea.y;
|
||||
mContentSize += mContents.last()->mArea.w;
|
||||
mContentSize += (mContents.size()) * padding;
|
||||
}
|
||||
}
|
||||
|
||||
void setOffset(const halnf offset) {
|
||||
if (!mContents.size()) return;
|
||||
const auto padding = this->getValue("Padding");
|
||||
auto newOffset = offset - mContents.first()->mArea.y + padding;
|
||||
for (auto widget : mContents) {
|
||||
widget->mArea.y += newOffset;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
halnf mContentSize = 0;
|
||||
Buffer<Widget<Events, Canvas>*> mContents;
|
||||
ScrollBarWidget<Events, Canvas> mScroller;
|
||||
};
|
||||
}
|
||||
79
Widgets/public/SplitViewWidget.hpp
Normal file
79
Widgets/public/SplitViewWidget.hpp
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
#pragma once
|
||||
|
||||
#include "WidgetBase.hpp"
|
||||
|
||||
namespace tp {
|
||||
template <typename Events, typename Canvas>
|
||||
class SplitView : public Widget<Events, Canvas> {
|
||||
public:
|
||||
SplitView() {
|
||||
this->createConfig("SplitView");
|
||||
this->addColor("Handle", "Accent");
|
||||
this->addColor("Hovered", "Interaction");
|
||||
this->addColor("Resizing", "Action");
|
||||
this->addValue("Min", 200.f);
|
||||
this->addValue("HandleSize", 7.f);
|
||||
}
|
||||
|
||||
void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) override {
|
||||
this->mArea = aArea;
|
||||
|
||||
if (!areaParent.isOverlap(aArea)) {
|
||||
mResizeInProcess = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mIsHover = getHandle().isInside(events.getPos());
|
||||
|
||||
if (events.isPressed() && mIsHover) {
|
||||
mResizeInProcess = true;
|
||||
} else if (!events.isDown()) {
|
||||
mResizeInProcess = false;
|
||||
}
|
||||
|
||||
if (mResizeInProcess) {
|
||||
halnf pos = events.getPos().x;
|
||||
auto diff = pos - (this->mArea.x + mFactor * this->mArea.z);
|
||||
mFactor += diff / this->mArea.z;
|
||||
}
|
||||
|
||||
mFactor = tp::clamp(mFactor, this->getValue("Min") / this->mArea.z, 1 - this->getValue("Min") / this->mArea.z);
|
||||
|
||||
if (this->getValue("Min") * 2.f > this->mArea.z) {
|
||||
mFactor = 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
// takes whole area
|
||||
void draw(Canvas& canvas) const override {
|
||||
if (mResizeInProcess) canvas.rect(getHandle(), this->getColor("Resizing"));
|
||||
else if (mIsHover) canvas.rect(getHandle(), this->getColor("Hovered"));
|
||||
else canvas.rect(getHandle(), this->getColor("Handle"));
|
||||
}
|
||||
|
||||
RectF getFirst() const {
|
||||
return {
|
||||
this->mArea.x, this->mArea.y, mFactor * this->mArea.z - this->getValue("HandleSize") / 2.f, this->mArea.w
|
||||
};
|
||||
}
|
||||
|
||||
RectF getSecond() const {
|
||||
return { this->mArea.x + mFactor * this->mArea.z + this->getValue("HandleSize") / 2.f,
|
||||
this->mArea.y,
|
||||
(1.f - mFactor) * this->mArea.z - this->getValue("HandleSize") / 2.f,
|
||||
this->mArea.w };
|
||||
}
|
||||
|
||||
RectF getHandle() const {
|
||||
return { this->mArea.x + mFactor * this->mArea.z - this->getValue("HandleSize") / 2.f,
|
||||
this->mArea.y,
|
||||
this->getValue("HandleSize"),
|
||||
this->mArea.w };
|
||||
}
|
||||
|
||||
public:
|
||||
halnf mFactor = 0.7f;
|
||||
bool mResizeInProcess = false;
|
||||
bool mIsHover = false;
|
||||
};
|
||||
}
|
||||
72
Widgets/public/TextInputWidget.hpp
Normal file
72
Widgets/public/TextInputWidget.hpp
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#pragma once
|
||||
|
||||
#include "WidgetBase.hpp"
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_internal.h"
|
||||
|
||||
namespace tp {
|
||||
template <typename Events, typename Canvas>
|
||||
class TextInputWidget : public Widget<Events, Canvas> {
|
||||
public:
|
||||
TextInputWidget() {
|
||||
this->createConfig("TextInput");
|
||||
this->addColor("Accent", "Accent");
|
||||
this->addColor("Base", "Base");
|
||||
this->addValue("Rounding", "Rounding");
|
||||
this->addColor("Hovered", "Interaction");
|
||||
this->addValue("Padding", "Padding");
|
||||
}
|
||||
|
||||
void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) override {
|
||||
this->mArea = aArea;
|
||||
nChanged = false;
|
||||
|
||||
const auto col = this->getColor("Accent");
|
||||
const auto colSel = this->getColor("Hovered");
|
||||
|
||||
ImGui::GetStyle().Colors[ImGuiCol_FrameBg] = { col.r, col.g, col.b, col.a };
|
||||
ImGui::GetStyle().Colors[ImGuiCol_TextSelectedBg] = { colSel.r, colSel.g, colSel.b, colSel.a };
|
||||
|
||||
ImGui::SetNextWindowPos({ this->mArea.x, this->mArea.y });
|
||||
ImGui::SetNextWindowSize({ this->mArea.z, this->mArea.w });
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 0, 0 });
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, this->getValue("Rounding") * 1.5f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { this->getValue("Padding"), this->getValue("Padding") });
|
||||
|
||||
// ImGui::PushID((int) alni(this));
|
||||
ImGui::Begin(
|
||||
mId.read(),
|
||||
0,
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBackground |
|
||||
ImGuiWindowFlags_NoResize
|
||||
);
|
||||
|
||||
if (mMultiline) {
|
||||
if (ImGui::InputTextMultiline("input", mBuff, mMaxBufferSize, { this->mArea.z, this->mArea.w })) {
|
||||
mValue = mBuff;
|
||||
nChanged = true;
|
||||
}
|
||||
} else {
|
||||
if (ImGui::InputTextEx("input", mId.read(), mBuff, mMaxBufferSize, { this->mArea.z, this->mArea.w }, 0)) {
|
||||
mValue = mBuff;
|
||||
nChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar(3);
|
||||
}
|
||||
|
||||
void draw(Canvas& canvas) const override {
|
||||
// canvas.rect(this->mArea, this->getColor("Base"));
|
||||
}
|
||||
|
||||
enum { mMaxBufferSize = 512 };
|
||||
char mBuff[mMaxBufferSize] = "";
|
||||
bool nChanged = false;
|
||||
String mValue;
|
||||
String mId = "id";
|
||||
bool mMultiline = false;
|
||||
};
|
||||
}
|
||||
64
Widgets/public/WidgetBase.hpp
Normal file
64
Widgets/public/WidgetBase.hpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#pragma once
|
||||
|
||||
#include "WidgetConfig.hpp"
|
||||
|
||||
namespace tp {
|
||||
|
||||
template <typename Events, typename Canvas>
|
||||
class Widget {
|
||||
public:
|
||||
Widget() = default;
|
||||
|
||||
[[nodiscard]] const RGBA& getColor(const String& name) const {
|
||||
const auto& node = cfg->values.get(name);
|
||||
if (node.type == WidgetConfig::Node::REF) {
|
||||
return globalCfg->configs.get(node.refGroup).values.get(node.refName).color;
|
||||
}
|
||||
return cfg->values.get(name).color;
|
||||
}
|
||||
|
||||
[[nodiscard]] halnf getValue(const String& name) const {
|
||||
const auto& node = cfg->values.get(name);
|
||||
if (node.type == WidgetConfig::Node::REF) {
|
||||
return globalCfg->configs.get(node.refGroup).values.get(node.refName).value;
|
||||
}
|
||||
return cfg->values.get(name).value;
|
||||
}
|
||||
|
||||
void addColor(const String& name, const RGBA& val) { cfg->values.put(name, WidgetConfig::Node(val)); }
|
||||
void addValue(const String& name, halnf val) { cfg->values.put(name, WidgetConfig::Node(val)); }
|
||||
|
||||
void addColor(const String& name, const String& presetName) {
|
||||
cfg->values.put(name, WidgetConfig::Node(presetName));
|
||||
}
|
||||
void addValue(const String& name, const String& presetName) {
|
||||
cfg->values.put(name, WidgetConfig::Node(presetName));
|
||||
}
|
||||
|
||||
void createConfig(const String& name) {
|
||||
globalCfg->configs.put(name, {});
|
||||
cfg = &globalCfg->configs.get(name);
|
||||
}
|
||||
|
||||
virtual void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) {
|
||||
mVisible = areaParent.isOverlap(aArea);
|
||||
if (!mVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->mArea = aArea;
|
||||
}
|
||||
|
||||
virtual void draw(Canvas& canvas) const {
|
||||
if (!mVisible) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
GlobalGUIConfig* globalCfg = gGlobalGUIConfig;
|
||||
WidgetConfig* cfg = nullptr;
|
||||
RectF mArea;
|
||||
bool mVisible = false;
|
||||
};
|
||||
}
|
||||
70
Widgets/public/WidgetConfig.hpp
Normal file
70
Widgets/public/WidgetConfig.hpp
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
#pragma once
|
||||
|
||||
#include "Animations.hpp"
|
||||
#include "Map.hpp"
|
||||
#include "Rect.hpp"
|
||||
#include "Strings.hpp"
|
||||
|
||||
namespace tp {
|
||||
|
||||
extern ModuleManifest gModuleWidgets;
|
||||
|
||||
struct WidgetConfig {
|
||||
struct Node {
|
||||
enum Type { NONE, VAL, COL, REF };
|
||||
|
||||
halnf value = 0.f;
|
||||
RGBA color = {};
|
||||
|
||||
Type type = NONE;
|
||||
|
||||
String refGroup;
|
||||
String refName;
|
||||
|
||||
Node() = default;
|
||||
|
||||
explicit Node(halnf val) {
|
||||
type = VAL;
|
||||
value = val;
|
||||
}
|
||||
|
||||
explicit Node(const RGBA& val) {
|
||||
type = COL;
|
||||
color = val;
|
||||
}
|
||||
|
||||
explicit Node(const String& val) {
|
||||
refGroup = "Presets";
|
||||
refName = val;
|
||||
type = REF;
|
||||
}
|
||||
};
|
||||
|
||||
Map<String, Node> values;
|
||||
};
|
||||
|
||||
struct GlobalGUIConfig {
|
||||
GlobalGUIConfig() {
|
||||
configs.put("Presets", {});
|
||||
auto& presets = configs.get("Presets");
|
||||
|
||||
presets.values.put("FontSize", WidgetConfig::Node(15.f));
|
||||
presets.values.put("FontSizeDim", WidgetConfig::Node(12.f));
|
||||
presets.values.put("Rounding", WidgetConfig::Node(5.f));
|
||||
presets.values.put("Padding", WidgetConfig::Node(5.f));
|
||||
presets.values.put("HandleSize", WidgetConfig::Node(5.f));
|
||||
|
||||
presets.values.put("Background", WidgetConfig::Node({ 0.03f, 0.03f, 0.03f, 1.f }));
|
||||
presets.values.put("Base", WidgetConfig::Node({ 0.07f, 0.07f, 0.07f, 1.f }));
|
||||
presets.values.put("Accent", WidgetConfig::Node({ 0.13f, 0.13f, 0.13f, 1.f }));
|
||||
presets.values.put("Interaction", WidgetConfig::Node({ 0.33f, 0.33f, 0.3f, 1.f }));
|
||||
presets.values.put("Action", WidgetConfig::Node({ 0.44f, 0.44f, 0.4f, 1.f }));
|
||||
presets.values.put("Front", WidgetConfig::Node({ 1.f, 1.f, 1.f, 1.f }));
|
||||
presets.values.put("FrontDim", WidgetConfig::Node({ 0.7f, 0.7f, 0.7f, 1.f }));
|
||||
}
|
||||
|
||||
Map<String, WidgetConfig> configs;
|
||||
};
|
||||
|
||||
extern GlobalGUIConfig* gGlobalGUIConfig;
|
||||
}
|
||||
8
Widgets/public/Widgets.hpp
Normal file
8
Widgets/public/Widgets.hpp
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "ButtonWidget.hpp"
|
||||
#include "LabelWidget.hpp"
|
||||
#include "ScrollableWidget.hpp"
|
||||
#include "SplitViewWidget.hpp"
|
||||
#include "TextInputWidget.hpp"
|
||||
#include "Animations.hpp"
|
||||
1
Widgets/tests/Tests.cpp
Normal file
1
Widgets/tests/Tests.cpp
Normal file
|
|
@ -0,0 +1 @@
|
|||
int main() {}
|
||||
Loading…
Add table
Add a link
Reference in a new issue