initial layout stuff

This commit is contained in:
HJfod 2022-10-10 20:58:47 +03:00
parent e6065b210f
commit 3f7a9ed8d3
12 changed files with 498 additions and 86 deletions

View file

@ -14,6 +14,7 @@ file(GLOB CORE_SOURCES
src/cocos2d-ext/*.cpp
src/core/*.cpp
src/hooks/*.cpp
src/ids/*.cpp
src/internal/*.cpp
src/internal/windows/*.cpp
src/internal/mac/*.cpp

View file

@ -2,13 +2,17 @@
#include "ui/BasedButtonSprite.hpp"
#include "ui/BasedButton.hpp"
#include "ui/ColorPickPopup.hpp"
#include "ui/EnterLayerEvent.hpp"
#include "ui/IconButtonSprite.hpp"
#include "ui/InputNode.hpp"
#include "ui/ListView.hpp"
#include "ui/MDPopup.hpp"
#include "ui/MDTextArea.hpp"
#include "ui/Notification.hpp"
#include "ui/Popup.hpp"
#include "ui/SceneManager.hpp"
#include "ui/Scrollbar.hpp"
#include "ui/ScrollLayer.hpp"
#include "ui/SelectList.hpp"
#include "ui/TextRenderer.hpp"

View file

@ -37,6 +37,7 @@
#include "kazmath/kazmath.h"
#include "script_support/CCScriptSupport.h"
#include "CCProtocols.h"
#include "Layout.hpp"
NS_CC_BEGIN
@ -875,6 +876,13 @@ public:
* @returns The child, or nullptr if none was found
*/
CCNode* getChildByIDRecursive(std::string const& id);
void setLayout(Layout* layout, bool apply = true);
Layout* getLayout();
void updateLayout();
void setPositionHint(PositionHint hint);
PositionHint getPositionHint();
);
/// @{

View file

@ -0,0 +1,153 @@
#pragma once
#include "ccMacros.h"
#include "cocoa/CCAffineTransform.h"
#include "cocoa/CCArray.h"
#include <Geode/platform/platform.hpp>
#include <optional>
#include <unordered_map>
NS_CC_BEGIN
class CCNode;
/**
* Layouts automatically handle the positioning of nodes. Use CCNode::setLayout
* to apply a layout to a node, and then use CCNode::updateLayout to apply
* the layout's positioning. Geode comes with a few default layouts like
* RowLayout, ColumnLayout, and GridLayout, but if you need a different kind
* of layout you can inherit from the Layout class.
*/
class Layout {
public:
/**
* Automatically apply the layout's positioning on a set of nodes
* @param nodes Nodes to position
* @param availableSize Give hints to the layout about how much space is
* available. Note that the layout may still overflow
*/
virtual void apply(CCArray* nodes, CCSize const& availableSize) = 0;
};
/**
* Determines how a node should be positioned within its parent, if that
* parent has an automatically positioning layout
*/
enum class PositionHint {
// The container can determine the best position
// for this node
Default,
// The container's layout should not affect the
// position of this node
Absolute,
};
/**
* Specifies the alignment of something
*/
enum class Alignment {
Begin,
Center,
End,
};
/**
* Simple layout for arranging nodes in a row (horizontal line)
*/
class GEODE_DLL RowLayout : public Layout {
protected:
Alignment m_alignment = Alignment::Center;
std::optional<float> m_alignVertically;
float m_gap;
public:
void apply(CCArray* nodes, CCSize const& availableSize) override;
/**
* Create a new RowLayout. Note that this class is not automatically
* managed by default, so you must assign it to a CCNode or manually
* manage the memory yourself.
* @param gap Space between nodes
* @param alignVertically Whether to align the nodes vertically, and if so,
* what Y position to align them at
* @returns Created RowLayout
*/
static RowLayout* create(
float gap = 5.f,
std::optional<float> alignVertically = std::nullopt
);
RowLayout* setAlignment(Alignment align);
RowLayout* setGap(float gap);
RowLayout* setAlignVertically(std::optional<float> align);
};
/**
* Simple layout for arranging nodes in a column (vertical line)
*/
class GEODE_DLL ColumnLayout : public Layout {
protected:
Alignment m_alignment = Alignment::Center;
std::optional<float> m_alignHorizontally;
float m_gap;
public:
void apply(CCArray* nodes, CCSize const& availableSize) override;
static ColumnLayout* create(
float gap = 5.f,
std::optional<float> alignHorizontally = std::nullopt
);
ColumnLayout* setAlignment(Alignment align);
ColumnLayout* setGap(float gap);
ColumnLayout* setAlignHorizontally(std::optional<float> align);
};
/**
* Grid direction; which direction the grid should add its next row to if the
* current row is full
*/
enum class GridDirection {
// Downward
Column,
// Upward
ReverseColumn,
// Right
Row,
// Left
ReverseRow,
};
/**
* Grid alignment; same as normal Alignment but also features the "Stretch"
* option which will stretch the row out to be the same size as the others
*/
enum class GridAlignment {
Begin,
Center,
Stretch,
End,
};
class GEODE_DLL GridLayout : public Layout {
protected:
GridDirection m_direction = GridDirection::Column;
GridAlignment m_alignment = GridAlignment::Center;
std::optional<size_t> m_rowSize;
public:
void apply(CCArray* nodes, CCSize const& availableSize) override;
static GridLayout* create(
std::optional<size_t> rowSize,
GridAlignment alignment = GridAlignment::Center,
GridDirection direction = GridDirection::Column
);
GridLayout* setDirection(GridDirection direction);
GridLayout* setAlignment(GridAlignment alignment);
GridLayout* setRowSize(std::optional<size_t> rowSize);
};
NS_CC_END

View file

@ -173,14 +173,14 @@ typedef void (CCObject::*SEL_MenuHandler)(CCObject*);
typedef void (CCObject::*SEL_EventHandler)(CCEvent*);
typedef int (CCObject::*SEL_Compare)(CCObject*);
#define schedule_selector(_SELECTOR) (SEL_SCHEDULE)(&_SELECTOR)
#define callfunc_selector(_SELECTOR) (SEL_CallFunc)(&_SELECTOR)
#define callfuncN_selector(_SELECTOR) (SEL_CallFuncN)(&_SELECTOR)
#define callfuncND_selector(_SELECTOR) (SEL_CallFuncND)(&_SELECTOR)
#define callfuncO_selector(_SELECTOR) (SEL_CallFuncO)(&_SELECTOR)
#define menu_selector(_SELECTOR) (SEL_MenuHandler)(&_SELECTOR)
#define event_selector(_SELECTOR) (SEL_EventHandler)(&_SELECTOR)
#define compare_selector(_SELECTOR) (SEL_Compare)(&_SELECTOR)
#define schedule_selector(_SELECTOR) (cocos2d::SEL_SCHEDULE)(&_SELECTOR)
#define callfunc_selector(_SELECTOR) (cocos2d::SEL_CallFunc)(&_SELECTOR)
#define callfuncN_selector(_SELECTOR) (cocos2d::SEL_CallFuncN)(&_SELECTOR)
#define callfuncND_selector(_SELECTOR) (cocos2d::SEL_CallFuncND)(&_SELECTOR)
#define callfuncO_selector(_SELECTOR) (cocos2d::SEL_CallFuncO)(&_SELECTOR)
#define menu_selector(_SELECTOR) (cocos2d::SEL_MenuHandler)(&_SELECTOR)
#define event_selector(_SELECTOR) (cocos2d::SEL_EventHandler)(&_SELECTOR)
#define compare_selector(_SELECTOR) (cocos2d::SEL_Compare)(&_SELECTOR)
// end of base_nodes group
/// @}

View file

@ -19,7 +19,6 @@ if constexpr (derived##index::uuid != nullptr && (void*)base##index::uuid != (vo
namespace geode::modifier {
template <class Derived, class Base>
class Modify;

View file

@ -1,3 +1,5 @@
#pragma once
#include "Popup.hpp"
#include "InputNode.hpp"

View file

@ -0,0 +1,61 @@
#pragma once
#include "../loader/Event.hpp"
namespace cocos2d {
class CCNode;
}
namespace geode {
template<class T>
concept InheritsCCNode = std::is_base_of_v<cocos2d::CCNode, T>;
template<InheritsCCNode T>
class EnterLayerEvent : public Event {
protected:
std::string m_layerID;
T* m_layer;
public:
EnterLayerEvent(
std::string const& layerID,
T* layer
) : m_layerID(layerID),
m_layer(layer) {}
std::string getID() const {
return m_layerID;
}
T* getLayer() const {
return m_layer;
}
};
template<class T, class N>
concept InheritsEnterLayer = std::is_base_of_v<EnterLayerEvent<N>, T>;
template<class N, InheritsEnterLayer<N> T>
class EnterLayerEventHandler : public EventHandler<EnterLayerEvent<N>> {
public:
using Consumer = void(*)(T*);
protected:
Consumer m_consumer;
std::optional<std::string> m_targetID;
public:
PassThrough handle(EnterLayerEvent<N>* event) override {
if (m_targetID == event->getID()) {
m_consumer(static_cast<T*>(event));
}
return PassThrough::Propagate;
}
EnterLayerEventHandler(
std::optional<std::string> const& id,
Consumer handler
) : m_targetID(id),
m_consumer(handler) {}
};
}

View file

@ -0,0 +1,138 @@
#include <cocos2d.h>
#include <Geode/utils/WackyGeodeMacros.hpp>
using namespace cocos2d;
void RowLayout::apply(CCArray* nodes, CCSize const& availableSize) {
float totalWidth = .0f;
CCARRAY_FOREACH_B_BASE(nodes, node, CCNode*, ix) {
totalWidth += node->getScaledContentSize().width;
if (ix) {
totalWidth += m_gap;
}
}
float pos;
switch (m_alignment) {
default:
case Alignment::Center: pos = -totalWidth / 2; break;
case Alignment::Begin: pos = -totalWidth; break;
case Alignment::End: pos = 0.f; break;
}
CCARRAY_FOREACH_B_TYPE(nodes, node, CCNode) {
auto sw = node->getScaledContentSize().width;
node->setPositionX(pos + sw / 2);
if (m_alignVertically) {
node->setPositionY(m_alignVertically.value());
}
pos += sw + m_gap;
}
}
RowLayout* RowLayout::create(
float gap,
std::optional<float> alignVertically
) {
auto ret = new RowLayout;
ret->m_gap = gap;
ret->m_alignVertically = alignVertically;
return ret;
}
RowLayout* RowLayout::setAlignment(Alignment align) {
m_alignment = align;
return this;
}
RowLayout* RowLayout::setGap(float gap) {
m_gap = gap;
return this;
}
RowLayout* RowLayout::setAlignVertically(std::optional<float> align) {
m_alignVertically = align;
return this;
}
void ColumnLayout::apply(CCArray* nodes, CCSize const& availableSize) {
float totalHeight = .0f;
CCARRAY_FOREACH_B_BASE(nodes, node, CCNode*, ix) {
totalHeight += node->getScaledContentSize().height;
if (ix) {
totalHeight += m_gap;
}
}
float pos;
switch (m_alignment) {
default:
case Alignment::Center: pos = -totalHeight / 2; break;
case Alignment::Begin: pos = -totalHeight; break;
case Alignment::End: pos = 0.f; break;
}
CCARRAY_FOREACH_B_TYPE(nodes, node, CCNode) {
auto sw = node->getScaledContentSize().height;
node->setPositionY(pos + sw / 2);
if (m_alignHorizontally) {
node->setPositionX(m_alignHorizontally.value());
}
pos += sw + m_gap;
}
}
ColumnLayout* ColumnLayout::create(
float gap,
std::optional<float> alignHorizontally
) {
auto ret = new ColumnLayout;
ret->m_gap = gap;
ret->m_alignHorizontally = alignHorizontally;
return ret;
}
ColumnLayout* ColumnLayout::setAlignment(Alignment align) {
m_alignment = align;
return this;
}
ColumnLayout* ColumnLayout::setGap(float gap) {
m_gap = gap;
return this;
}
ColumnLayout* ColumnLayout::setAlignHorizontally(std::optional<float> align) {
m_alignHorizontally = align;
return this;
}
void GridLayout::apply(CCArray* nodes, CCSize const& availableSize) {
// todo
}
GridLayout* GridLayout::create(
std::optional<size_t> rowSize,
GridAlignment alignment,
GridDirection direction
) {
auto ret = new GridLayout;
ret->m_rowSize = rowSize;
ret->m_alignment = alignment;
ret->m_direction = direction;
return ret;
}
GridLayout* GridLayout::setDirection(GridDirection direction) {
m_direction = direction;
return this;
}
GridLayout* GridLayout::setAlignment(GridAlignment alignment) {
m_alignment = alignment;
return this;
}
GridLayout* GridLayout::setRowSize(std::optional<size_t> rowSize) {
m_rowSize = rowSize;
return this;
}

View file

@ -18,6 +18,8 @@ private:
FieldContainer* m_fieldContainer;
Ref<cocos2d::CCObject> m_userObject;
std::string m_id = "";
std::unique_ptr<Layout> m_layout = nullptr;
PositionHint m_positionHint = PositionHint::Default;
friend class ProxyCCNode;
friend class cocos2d::CCNode;
@ -102,4 +104,37 @@ CCNode* CCNode::getChildByIDRecursive(std::string const& id) {
return nullptr;
}
void CCNode::setLayout(Layout* layout, bool apply) {
GeodeNodeMetadata::set(this)->m_layout.reset(layout);
if (apply) {
this->updateLayout();
}
}
Layout* CCNode::getLayout() {
return GeodeNodeMetadata::set(this)->m_layout.get();
}
void CCNode::updateLayout() {
if (auto layout = GeodeNodeMetadata::set(this)->m_layout.get()) {
// nodes with absolute position should never be rearranged
auto filtered = CCArray::createWithCapacity(m_pChildren->capacity());
CCARRAY_FOREACH_B_TYPE(m_pChildren, child, CCNode) {
if (child->getPositionHint() != PositionHint::Absolute) {
filtered->addObject(child);
}
}
layout->apply(filtered, m_obContentSize);
filtered->release();
}
}
void CCNode::setPositionHint(PositionHint hint) {
GeodeNodeMetadata::set(this)->m_positionHint = hint;
}
PositionHint CCNode::getPositionHint() {
return GeodeNodeMetadata::set(this)->m_positionHint;
}
#pragma warning(pop)

View file

@ -1,4 +1,4 @@
#include <Geode/Geode.hpp>
#include <Geode/Bindings.hpp>
#include <Geode/utils/WackyGeodeMacros.hpp>
#include <Geode/ui/BasedButtonSprite.hpp>
#include <Geode/ui/Notification.hpp>
@ -139,77 +139,12 @@ class $modify(CustomMenuLayer, MenuLayer) {
bool init() {
if (!MenuLayer::init())
return false;
Loader::get()->updateResourcePaths();
auto setIDSafe = +[](CCNode* node, int index, const char* id) {
if (auto child = getChild(node, index)) {
child->setID(id);
}
};
// set IDs to everything
this->setID("main-menu-layer");
setIDSafe(this, 0, "main-menu-bg");
getChildOfType<CCSprite>(this, 0)->setID("main-title");
if (PlatformToolbox::isControllerConnected()) {
getChildOfType<CCSprite>(this, 1)->setID("play-gamepad-icon");
getChildOfType<CCSprite>(this, 2)->setID("editor-gamepad-icon");
getChildOfType<CCSprite>(this, 3)->setID("icon-kit-gamepad-icon");
getChildOfType<CCSprite>(this, 4)->setID("settings-gamepad-icon");
getChildOfType<CCSprite>(this, 5)->setID("mouse-gamepad-icon");
getChildOfType<CCSprite>(this, 6)->setID("click-gamepad-icon");
getChildOfType<CCLabelBMFont>(this, 0)->setID("mouse-gamepad-label");
getChildOfType<CCLabelBMFont>(this, 1)->setID("click-gamepad-label");
getChildOfType<CCLabelBMFont>(this, 2)->setID("player-username");
} else {
getChildOfType<CCLabelBMFont>(this, 0)->setID("player-username");
}
if (auto menu = getChildOfType<CCMenu>(this, 0)) {
menu->setID("main-menu");
setIDSafe(menu, 0, "play-button");
setIDSafe(menu, 1, "icon-kit-button");
setIDSafe(menu, 2, "editor-button");
setIDSafe(menu, 3, "profile-button");
}
if (auto menu = getChildOfType<CCMenu>(this, 1)) {
menu->setID("bottom-menu");
setIDSafe(menu, 0, "achievements-button");
setIDSafe(menu, 1, "settings-button");
setIDSafe(menu, 2, "stats-button");
setIDSafe(menu, 3, "newgrounds-button");
setIDSafe(menu, -1,"daily-chest-button");
}
if (auto menu = getChildOfType<CCMenu>(this, 2)) {
menu->setID("social-media-menu");
setIDSafe(menu, 0, "robtop-logo-button");
setIDSafe(menu, 1, "facebook-button");
setIDSafe(menu, 2, "twitter-button");
setIDSafe(menu, 3, "youtube-button");
}
if (auto menu = getChildOfType<CCMenu>(this, 3)) {
menu->setID("more-games-menu");
setIDSafe(menu, 0, "more-games-button");
setIDSafe(menu, 1, "close-button");
}
auto winSize = CCDirector::sharedDirector()->getWinSize();
// add geode button
auto bottomMenu = static_cast<CCMenu*>(this->getChildByID("bottom-menu"));
// keep chest in the same position
auto chest = bottomMenu->getChildByID("daily-chest-button");
if (chest) {
chest->retain();
chest->removeFromParent();
}
auto y = getChild(bottomMenu, 0)->getPositionY();
g_geodeButton = SafeCreate<CCSprite>()
.with(CircleButtonSprite::createWithSpriteFrameName(
@ -221,21 +156,16 @@ class $modify(CustomMenuLayer, MenuLayer) {
.orMake<ButtonSprite>("!!");
addUpdateIcon();
auto bottomMenu = static_cast<CCMenu*>(this->getChildByID("bottom-menu"));
auto btn = CCMenuItemSpriteExtra::create(
g_geodeButton.data(), this, menu_selector(CustomMenuLayer::onGeode)
);
btn->setID("geode-button");
bottomMenu->addChild(btn);
bottomMenu->alignItemsHorizontallyWithPadding(3.f);
CCARRAY_FOREACH_B_TYPE(bottomMenu->getChildren(), node, CCNode) {
node->setPositionY(y);
}
if (chest) {
bottomMenu->addChild(chest);
chest->release();
}
bottomMenu->updateLayout();
if (auto node = this->getChildByID("settings-gamepad-icon")) {
node->setPositionX(bottomMenu->getChildByID(
@ -286,7 +216,7 @@ class $modify(CustomMenuLayer, MenuLayer) {
Index::get()->updateIndex(updateIndexProgress);
}
return true;
}

View file

@ -0,0 +1,81 @@
#include <Geode/Modify.hpp>
#include <Geode/Bindings.hpp>
#include <Geode/utils/cocos.hpp>
#include <Geode/ui/EnterLayerEvent.hpp>
USE_GEODE_NAMESPACE();
class $modify(MenuLayer) {
bool init() {
auto setIDSafe = +[](CCNode* node, int index, const char* id) -> CCNode* {
if (auto child = getChild(node, index)) {
child->setID(id);
return child;
}
return nullptr;
};
// set IDs to everything
this->setID("main-menu-layer");
setIDSafe(this, 0, "main-menu-bg");
getChildOfType<CCSprite>(this, 0)->setID("main-title");
if (PlatformToolbox::isControllerConnected()) {
getChildOfType<CCSprite>(this, 1)->setID("play-gamepad-icon");
getChildOfType<CCSprite>(this, 2)->setID("editor-gamepad-icon");
getChildOfType<CCSprite>(this, 3)->setID("icon-kit-gamepad-icon");
getChildOfType<CCSprite>(this, 4)->setID("settings-gamepad-icon");
getChildOfType<CCSprite>(this, 5)->setID("mouse-gamepad-icon");
getChildOfType<CCSprite>(this, 6)->setID("click-gamepad-icon");
getChildOfType<CCLabelBMFont>(this, 0)->setID("mouse-gamepad-label");
getChildOfType<CCLabelBMFont>(this, 1)->setID("click-gamepad-label");
getChildOfType<CCLabelBMFont>(this, 2)->setID("player-username");
} else {
getChildOfType<CCLabelBMFont>(this, 0)->setID("player-username");
}
if (auto menu = getChildOfType<CCMenu>(this, 0)) {
menu->setID("main-menu");
setIDSafe(menu, 0, "play-button");
auto iconKit = setIDSafe(menu, 1, "icon-kit-button");
setIDSafe(menu, 2, "editor-button");
if (auto pfp = setIDSafe(menu, 3, "profile-button")) {
pfp->setPositionHint(PositionHint::Absolute);
}
menu->setLayout(RowLayout::create(5.f, 0.f));
}
if (auto menu = getChildOfType<CCMenu>(this, 1)) {
menu->setID("bottom-menu");
auto ach = setIDSafe(menu, 0, "achievements-button");
setIDSafe(menu, 1, "settings-button");
setIDSafe(menu, 2, "stats-button");
setIDSafe(menu, 3, "newgrounds-button");
if (auto dailyChest = setIDSafe(menu, -1, "daily-chest-button")) {
dailyChest->setPositionHint(PositionHint::Absolute);
}
menu->setLayout(RowLayout::create(5.f, ach->getPositionY()));
}
if (auto menu = getChildOfType<CCMenu>(this, 2)) {
menu->setID("social-media-menu");
setIDSafe(menu, 0, "robtop-logo-button");
setIDSafe(menu, 1, "facebook-button");
setIDSafe(menu, 2, "twitter-button");
setIDSafe(menu, 3, "youtube-button");
}
if (auto menu = getChildOfType<CCMenu>(this, 3)) {
menu->setID("more-games-menu");
setIDSafe(menu, 0, "more-games-button");
setIDSafe(menu, 1, "close-button");
}
EnterLayerEvent("MenuLayer", this).post();
return true;
}
};