This commit is contained in:
Ilusha 2024-01-18 09:15:22 +03:00 committed by IlyaShurupov
parent f7f0d6e5be
commit 83d51d3e83
30 changed files with 1186 additions and 208 deletions

View 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&);
};
};

View 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;
};
}

View 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";
};
}

View 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;
};
}

View 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;
};
}

View 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;
};
}

View 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;
};
}

View 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;
}

View 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"