Merge pull request from geode-sdk/better-layouts

Better layouts
This commit is contained in:
HJfod 2023-02-23 22:34:14 +02:00 committed by GitHub
commit 75be0b6502
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 2546 additions and 577 deletions

View file

@ -15,14 +15,32 @@ string(STRIP "${GEODE_VERSION}" GEODE_VERSION)
# Check if version has a tag like v1.0.0-alpha
string(FIND ${GEODE_VERSION} "-" GEODE_VERSION_HAS_TAG)
if (GEODE_VERSION_HAS_TAG)
string(REGEX MATCH "[a-z]+[0-9]?$" GEODE_VERSION_TAG ${GEODE_VERSION})
if (NOT ${GEODE_VERSION_HAS_TAG} EQUAL "-1")
string(REGEX MATCH "[a-z]+(\.[0-9]+)?$" GEODE_VERSION_TAG ${GEODE_VERSION})
string(SUBSTRING "${GEODE_VERSION}" 0 ${GEODE_VERSION_HAS_TAG} GEODE_VERSION)
string(FIND ${GEODE_VERSION_TAG} "." GEODE_VERSION_TAG_HAS_NUMBER)
# Extract tag type and number from tag
if (NOT ${GEODE_VERSION_TAG_HAS_NUMBER} EQUAL "-1")
string(SUBSTRING "${GEODE_VERSION_TAG}" 0 ${GEODE_VERSION_TAG_HAS_NUMBER} GEODE_VERSION_TAG_TYPE)
math(EXPR GEODE_VERSION_TAG_HAS_NUMBER "${GEODE_VERSION_TAG_HAS_NUMBER} + 1")
string(SUBSTRING "${GEODE_VERSION_TAG}" ${GEODE_VERSION_TAG_HAS_NUMBER} -1 GEODE_VERSION_TAG_NUMBER)
else()
set(GEODE_VERSION_TAG_TYPE "${GEODE_VERSION_TAG}")
set(GEODE_VERSION_TAG_NUMBER "")
endif()
# Capitalize first letter of tag type
string(SUBSTRING ${GEODE_VERSION_TAG_TYPE} 0 1 FIRST_LETTER)
string(TOUPPER ${FIRST_LETTER} FIRST_LETTER)
string(REGEX REPLACE "^.(.*)" "${FIRST_LETTER}\\1" GEODE_VERSION_TAG_TYPE "${GEODE_VERSION_TAG_TYPE}")
else()
set(GEODE_VERSION_TAG "")
set(GEODE_VERSION_TAG_TYPE "")
set(GEODE_VERSION_TAG_NUMBER "")
endif()
message(STATUS "Version: ${GEODE_VERSION}, tag: ${GEODE_VERSION_TAG}")
message(STATUS "Version: ${GEODE_VERSION}, tag: ${GEODE_VERSION_TAG} (type: ${GEODE_VERSION_TAG_TYPE}, number: ${GEODE_VERSION_TAG_NUMBER})")
project(geode-sdk VERSION ${GEODE_VERSION} LANGUAGES CXX C)
@ -60,7 +78,7 @@ if (GEODE_DISABLE_FMT_CONSTEVAL)
target_compile_definitions(${PROJECT_NAME} INTERFACE -DFMT_CONSTEVAL=)
endif()
CPMAddPackage("gh:geode-sdk/json#2b76460")
CPMAddPackage("gh:geode-sdk/json#cef9c64")
CPMAddPackage("gh:fmtlib/fmt#9.1.0")
CPMAddPackage("gh:gulrak/filesystem#3e5b930")

View file

@ -1 +1 @@
1.0.0-beta
1.0.0-beta.6

View file

@ -296,6 +296,7 @@ class CCCircleWave : cocos2d::CCNode {
PAD = win 0x4;
float m_currentRadius;
float m_currentOpacity;
cocos2d::ccColor3B m_color;
cocos2d::CCPoint m_circleCenter;
int m_filled;
int m_lineWidth;
@ -1120,7 +1121,7 @@ class EditorPauseLayer : CCBlockLayer, FLAlertLayerProtocol {
if (!EditorUI::get()) return nullptr;
auto editor = LevelEditorLayer::get();
for (auto i = 0; i < editor->getChildrenCount(); ++i) {
for (auto i = 0u; i < editor->getChildrenCount(); ++i) {
if (auto layer = cast::safe_cast<EditorPauseLayer*>(editor->getChildren()->objectAtIndex(i))) {
return layer;
}
@ -1154,11 +1155,13 @@ class EditorPauseLayer : CCBlockLayer, FLAlertLayerProtocol {
void uncheckAllPortals(cocos2d::CCObject* sender) = win 0x74760;
void onResetUnusedColors(cocos2d::CCObject* sender) = win 0x74810;
void doResetUnused() = win 0x165070;
void updateSongButton() = win 0x74f10, mac 0x13e530;
void onSong(cocos2d::CCObject*) = win 0x74e70, mac 0x13e470;
bool m_saved;
PAD = mac 0x8, win 0x4;
CCMenuItemSpriteExtra* m_button0;
CCMenuItemSpriteExtra* m_button1;
CCMenuItemSpriteExtra* m_guidelinesOffButton;
CCMenuItemSpriteExtra* m_guidelinesOnButton;
LevelEditorLayer* m_editorLayer;
}
@ -2615,6 +2618,9 @@ class GameLevelManager : cocos2d::CCNode {
void storeUserNames(gd::string) = win 0xa1840;
gd::string userNameForUserID(int id) = win 0xa1c20;
void updateUserScore() = win 0xada60;
void downloadLevel(int id, bool downloadData) = win 0xaa730;
bool hasDownloadedLevel(int id) = win 0xab830;
GJGameLevel* getSavedLevel(int id) = win 0xa2ee0;
inline static GameLevelManager* get() {
return GameLevelManager::sharedState();
@ -2820,7 +2826,7 @@ class GameManager : GManager {
void getGTexture(int) = mac 0x1cca40, win 0xc9a50;
virtual bool init() = mac 0x1c2ec0, win 0xc4ad0;
void reportAchievementWithID(char const*, int, bool) = mac 0x1c6460, win 0xc64c0;
cocos2d::CCSize* resolutionForKey(cocos2d::CCSize*, int) = mac 0x1d0b40, win 0xceca0;
cocos2d::CCSize resolutionForKey(int) = mac 0x1d0b40, win 0xceca0;
virtual void update(float) = mac 0x1d0270, win 0xce440;
bool isColorUnlocked(int _id, bool _type) = mac 0x1c3b90, win 0xc53f0;
bool isIconUnlocked(int _id, IconType _type) = mac 0x1c35b0, win 0xc4fc0;
@ -3271,6 +3277,9 @@ class GameSoundManager : cocos2d::CCNode {
void asynchronousSetup() = win 0x25520;
~GameSoundManager() = mac 0x362c00, win 0x25640;
static GameSoundManager* sharedManager() = mac 0x3610f0, win 0x24800;
inline static GameSoundManager* get() {
return GameSoundManager::sharedManager();
}
cocos2d::CCDictionary* m_dictionary1;
cocos2d::CCDictionary* m_dictionary2;
@ -3962,6 +3971,9 @@ class MenuLayer : cocos2d::CCLayer, FLAlertLayerProtocol, GooglePlayDelegate {
void onYouTube(cocos2d::CCObject*) = win 0x1919A0;
static cocos2d::CCScene* scene(bool) = mac 0x1d12d0, win 0x190720, ios 0x19e57c;
static MenuLayer* node() = win 0x190550;
inline static MenuLayer* create() {
return MenuLayer::node();
}
cocos2d::CCSprite* m_googlePlaySprite;
cocos2d::CCSprite* m_viewProfileInfoText;
@ -4562,7 +4574,7 @@ class PlayerObject : GameObject, AnimatedSpriteDelegate {
void modeDidChange() = mac 0x22bfd0;
void placeStreakPoint() = mac 0x21af90, win 0x1f95e0;
void playBurstEffect() = mac 0x21c780, win 0x1f6790;
void playDeathEffect() = mac 0x225930, win 0x2efbe0;
void playDeathEffect() = mac 0x225930, win 0x1efbe0;
void playDynamicSpiderRun() = mac 0x222ec0, win 0x1f9d80;
void playerDestroyed(bool) = mac 0x2256d0, win 0x1efaa0;
bool playerIsFalling() = mac 0x21c730, win 0x1f5d60;

View file

@ -1,8 +1,21 @@
cmake_minimum_required(VERSION 3.21 FATAL_ERROR)
project(geode-loader VERSION ${GEODE_VERSION} LANGUAGES C CXX)
set(PROJECT_VERSION_TYPE geode::VersionTag::Beta)
set(PROJECT_VERSION_SUFFIX -beta)
if (GEODE_VERSION_TAG_TYPE)
if (GEODE_VERSION_TAG_NUMBER)
set(PROJECT_VERSION_TAG_CONSTR "geode::VersionTag(geode::VersionTag::${GEODE_VERSION_TAG_TYPE}, ${GEODE_VERSION_TAG_NUMBER})")
else()
set(PROJECT_VERSION_TAG_CONSTR "geode::VersionTag::${GEODE_VERSION_TAG_TYPE}")
endif()
else()
set(PROJECT_VERSION_TAG_CONSTR "std::nullopt")
endif()
if (GEODE_VERSION_TAG)
set(PROJECT_VERSION_SUFFIX "-${GEODE_VERSION_TAG}")
else()
set(PROJECT_VERSION_SUFFIX "")
endif()
# Package info file for internal representation
configure_file(resources/mod.json.in ${CMAKE_CURRENT_SOURCE_DIR}/resources/mod.json)

View file

@ -616,17 +616,15 @@ public:
* Return an array of children
*
* Composing a "tree" structure is a very important feature of CCNode
* Here's a sample code of traversing children array:
* @code
* @example
* // Here's a sample code of traversing children array:
* CCNode* node = NULL;
* CCARRAY_FOREACH(parent->getChildren(), node)
* {
* node->setPosition(0,0);
* }
* @endcode
* This sample code traverses all children nodes, and set theie position to (0,0)
*
* @return An array of children
* // This sample code traverses all children nodes, and set theie position to (0,0)
* @returns An array of children
*/
virtual CCArray* getChildren();
@ -761,7 +759,7 @@ public:
* Returns a tag that is used to identify the node easily.
*
* You can set tags to node then identify them easily.
* @code
* @example
* #define TAG_PLAYER 1
* #define TAG_MONSTER 2
* #define TAG_BOSS 3
@ -786,9 +784,7 @@ public:
* break;
* }
* }
* @endcode
*
* @return A interger that identifies the node.
* @returns A interger that identifies the node.
*/
RT_REMOVE( virtual int getTag() const; )
/**
@ -883,6 +879,26 @@ public:
*/
GEODE_DLL CCNode* getChildByIDRecursive(std::string const& id);
/**
* Add a child before a specified existing child
* @param child The node to add. The node may not be a child of another
* node already
* @param before The child the node is added before of. If this is null or
* not a child of this node, the new child will be placed at the start of the
* child list
*/
GEODE_DLL void insertBefore(CCNode* child, CCNode* before);
/**
* Add a child after an specified existing child
* @param child The node to add. The node may not be a child of another
* node already
* @param after The child the node is added after of. If this is null or
* not a child of this node, the new child will be placed at the end of the
* child list
*/
GEODE_DLL void insertAfter(CCNode* child, CCNode* after);
/**
* Set an attribute on a node. Attributes are a system added by Geode,
* where a node may have any sort of extra data associated with it. Used
@ -921,9 +937,14 @@ public:
* has been added, call updateLayout
* @param layout Layout to set to this node
* @param apply Whether to call updateLayout now or not
* @param respectAnchor If true, if the target node is
* isIgnoreAnchorPointForPosition, then it is set to false and the children
* are automatically moved to match where they should be positioned.
* Visually, this should result in no difference; however, when dealing with
* CCLayers / CCMenus, this will change where the children are located
* @note Geode addition
*/
GEODE_DLL void setLayout(Layout* layout, bool apply = true);
GEODE_DLL void setLayout(Layout* layout, bool apply = true, bool respectAnchor = true);
/**
* Get the Layout for this node
* @returns The current layout, or nullptr if no layout is set
@ -935,23 +956,22 @@ public:
* set, nothing happens
* @note Geode addition
*/
GEODE_DLL void updateLayout();
GEODE_DLL void updateLayout(bool updateChildOrder = true);
/**
* Give a hint to the current Layout about where this node should be
* positioned in it. Allows detaching the node from the current
* layout by setting position to absolute
* @param hint The hint to set
* @note The layout definitely should, but might not respect the hint
* given
* Set the layout options for this node. Layout options can be used to
* control how this node is positioned in its parent's Layout, for example
* setting the grow size for a flex layout
* @param options The layout options
* @param apply Whether to update the layout of the parent node
* @note Geode addition
*/
GEODE_DLL void setPositionHint(PositionHint hint);
GEODE_DLL void setLayoutOptions(LayoutOptions* options, bool apply = true);
/**
* Get the current position hint for this node
* Get the layout options for this node
* @returns The current layout options, or nullptr if no options are set
* @note Geode addition
*/
GEODE_DLL PositionHint getPositionHint();
GEODE_DLL LayoutOptions* getLayoutOptions();
/**
* Swap two children

View file

@ -18,136 +18,334 @@ class CCNode;
* RowLayout, ColumnLayout, and GridLayout, but if you need a different kind
* of layout you can inherit from the Layout class.
*/
class Layout {
class GEODE_DLL Layout {
protected:
static CCArray* getNodesToPosition(CCNode* forNode);
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
* @param on Node to apply the layout on. Position's the node's children
* according to the layout. The content size of the node should be
* respected as a boundary the layout shouldn't overflow. The node may be
* rescaled to better fit its contents
*/
virtual void apply(CCArray* nodes, CCSize const& availableSize) = 0;
virtual void apply(CCNode* on) = 0;
virtual ~Layout() = default;
};
class GEODE_DLL LayoutOptions {
public:
virtual ~LayoutOptions() = default;
};
/**
* Determines how a node should be positioned within its parent, if that
* parent has an automatically positioning layout
* The direction of an AxisLayout
*/
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,
enum class Axis {
Row,
Column,
};
/**
* Specifies the alignment of something
* Specifies the alignment of something in an AxisLayout
*/
enum class Alignment {
Begin,
enum class AxisAlignment {
// Align items to the start
// |ooo......|
Start,
// All items are centered
// |...ooo...|
Center,
// Align items to the end
// |......ooo|
End,
// Each item gets the same portion from the layout (disregards gap)
// |.o..o..o.|
Even,
};
constexpr float AXISLAYOUT_DEFAULT_MIN_SCALE = 0.65f;
constexpr int AXISLAYOUT_DEFAULT_PRIORITY = 0;
/**
* Options for controlling the behaviour of individual nodes in an AxisLayout
* @example
* auto node = CCNode::create();
* // this node will have 10 units of spacing between it and the next one
* node->setLayoutOptions(
* AxisLayoutOptions::create()
* ->setNextGap(10.f)
* );
* someNodeWithALayout->addChild(node);
*/
class GEODE_DLL AxisLayoutOptions : public LayoutOptions {
protected:
std::optional<bool> m_autoScale = std::nullopt;
float m_maxScale = 1.f;
float m_minScale = AXISLAYOUT_DEFAULT_MIN_SCALE;
float m_relativeScale = 1.f;
std::optional<float> m_length = std::nullopt;
std::optional<float> m_nextGap = std::nullopt;
std::optional<float> m_prevGap = std::nullopt;
bool m_breakLine = false;
bool m_sameLine = false;
int m_scalePriority = AXISLAYOUT_DEFAULT_PRIORITY;
public:
static AxisLayoutOptions* create();
std::optional<bool> getAutoScale() const;
float getMaxScale() const;
float getMinScale() const;
float getRelativeScale() const;
std::optional<float> getLength() const;
std::optional<float> getPrevGap() const;
std::optional<float> getNextGap() const;
bool getBreakLine() const;
bool getSameLine() const;
int getScalePriority() const;
/**
* Set the maximum scale this node can be if it's contained in an
* auto-scaled layout. Default is 1
*/
AxisLayoutOptions* setMaxScale(float scale);
/**
* Set the minimum scale this node can be if it's contained in an
* auto-scaled layout. Default is AXISLAYOUT_DEFAULT_MIN_SCALE
*/
AxisLayoutOptions* setMinScale(float scale);
/**
* Set the relative scale of this node compared to other nodes if it's
* contained in an auto-scaled layout. Default is 1
*/
AxisLayoutOptions* setRelativeScale(float scale);
/**
* Set auto-scaling for this node, overriding the layout's auto-scale
* setting. If nullopt, the layout's auto-scale options will be used
*/
AxisLayoutOptions* setAutoScale(std::optional<bool> enabled);
/**
* Set an absolute length for this node. If nullopt, the length will be
* dynamically calculated based on content size
*/
AxisLayoutOptions* setLength(std::optional<float> length);
/**
* Override the default gap in the layout between this node and the
* previous one. If nullopt, the default gap of the layout will be used
*/
AxisLayoutOptions* setPrevGap(std::optional<float> gap);
/**
* Override the default gap in the layout between this node and the next
* one. If nullopt, the default gap of the layout will be used
*/
AxisLayoutOptions* setNextGap(std::optional<float> gap);
/**
* If enabled, the node will always cause a growable axis layout to break
* into a new line even if the current line could've fit the next node
*/
AxisLayoutOptions* setBreakLine(bool enable);
/**
* If enabled, the node will be forced to be on the same line as the
* previous node even if doing this would overflow
*/
AxisLayoutOptions* setSameLine(bool enable);
/**
* Set the scale priority of this node. Nodes with higher priority will be
* scaled down first before nodes with lower priority when an auto-scaled
* layout attempts to fit its contents. Default is
* AXISLAYOUT_DEFAULT_PRIORITY
* @note For optimal performance, the priorities should all be close to
* each other with no gaps
*/
AxisLayoutOptions* setScalePriority(int priority);
};
/**
* A multi-purpose dynamic layout for arranging nodes along an axis. Can be
* used to arrange nodes in a single line, a grid, or a flex layout. The
* RowLayout and ColumnLayout classes function as simple thin wrappers over
* AxisLayout. The positioning of individual nodes in the layout can be
* further controlled using AxisLayoutOptions
* @warning Calculating layouts can get increasingly expensive for large
* amounts of child nodes being fit into a small space - while this should
* never prove a real performance concern as most layouts only have a few
* hundred children at the very most, be aware that you probably shouldn't
* call CCNode::updateLayout every frame for a menu with thousands of children
* @example
* auto menu = CCMenu::create();
* // The menu's children will be arranged horizontally, unless they overflow
* // the content size width in which case a new line will be inserted and
* // aligned to the left. The menu automatically will automatically grow in
* // height to fit all the rows
* menu->setLayout(
* RowLayout::create()
* ->setGap(10.f)
* ->setGrowCrossAxis(true)
* ->setAxisAlignment(AxisAlignment::Start)
* );
* menu->setContentSize({ 200.f, 0.f });
* menu->addChild(...);
* menu->updateLayout();
*/
class GEODE_DLL AxisLayout : public Layout {
protected:
Axis m_axis;
AxisAlignment m_axisAlignment = AxisAlignment::Center;
AxisAlignment m_crossAlignment = AxisAlignment::Center;
AxisAlignment m_crossLineAlignment = AxisAlignment::Center;
float m_gap = 5.f;
bool m_autoScale = true;
bool m_axisReverse = false;
bool m_crossReverse = false;
bool m_allowCrossAxisOverflow = true;
bool m_growCrossAxis = false;
struct Row;
float minScaleForPrio(CCArray* nodes, int prio) const;
float maxScaleForPrio(CCArray* nodes, int prio) const;
bool shouldAutoScale(AxisLayoutOptions const* opts) const;
bool canTryScalingDown(
CCArray* nodes,
int& prio, float& scale,
float crossScaleDownFactor,
std::pair<int, int> const& minMaxPrios
) const;
float nextGap(AxisLayoutOptions const* now, AxisLayoutOptions const* next) const;
Row* fitInRow(
CCNode* on, CCArray* nodes,
std::pair<int, int> const& minMaxPrios,
bool doAutoScale,
float scale, float squish, int prio
) const;
void tryFitLayout(
CCNode* on, CCArray* nodes,
std::pair<int, int> const& minMaxPrios,
bool doAutoScale,
float scale, float squish, int prio
) const;
AxisLayout(Axis);
public:
/**
* Create a new AxisLayout. Note that this class is not automatically
* managed by default, so you must assign it to a CCNode or manually
* manage the memory yourself. See the chainable setters on AxisLayout for
* what options you can customize for the layout
* @param axis The direction of the layout
* @note For convenience, you can use the RowLayout and ColumnLayout
* classes, which are just thin wrappers over AxisLayout
* @returns Created AxisLayout
*/
static AxisLayout* create(Axis axis = Axis::Row);
void apply(CCNode* on) override;
Axis getAxis() const;
AxisAlignment getAxisAlignment() const;
AxisAlignment getCrossAxisAlignment() const;
AxisAlignment getCrossAxisLineAlignment() const;
float getGap() const;
bool getAxisReverse() const;
bool getCrossAxisReverse() const;
bool getAutoScale() const;
bool getGrowCrossAxis() const;
bool getCrossAxisOverflow() const;
AxisLayout* setAxis(Axis axis);
/**
* Sets where to align the target node's children on the main axis (X-axis
* for Row, Y-axis for Column)
*/
AxisLayout* setAxisAlignment(AxisAlignment align);
/**
* Sets where to align the target node's children on the cross-axis (Y-axis
* for Row, X-axis for Column)
*/
AxisLayout* setCrossAxisAlignment(AxisAlignment align);
/**
* Sets where to align the target node's children on the cross-axis for
* each row (Y-axis for Row, X-axis for Column)
*/
AxisLayout* setCrossAxisLineAlignment(AxisAlignment align);
/**
* The spacing between the children of the node this layout applies to.
* Measured as the space between their edges, not centres. Does not apply
* on the main / cross axis if their alignment is AxisAlignment::Even
*/
AxisLayout* setGap(float gap);
/**
* Whether to reverse the direction of the children in this layout or not
*/
AxisLayout* setAxisReverse(bool reverse);
/**
* Whether to reverse the direction of the rows on the cross-axis or not
*/
AxisLayout* setCrossAxisReverse(bool reverse);
/**
* If enabled, then the layout may scale the target's children if they are
* about to overflow. Assumes that all the childrens' intended scale is 1
*/
AxisLayout* setAutoScale(bool enable);
/**
* If true, if the main axis overflows extra nodes will be placed on new
* rows/columns on the cross-axis
*/
AxisLayout* setGrowCrossAxis(bool expand);
/**
* If true, the cross-axis content size of the target node will be
* automatically adjusted to fit the children
*/
AxisLayout* setCrossAxisOverflow(bool allow);
};
/**
* Simple layout for arranging nodes in a row (horizontal line)
*/
class GEODE_DLL RowLayout : public Layout {
class GEODE_DLL RowLayout : public AxisLayout {
protected:
Alignment m_alignment = Alignment::Center;
std::optional<float> m_alignVertically;
float m_gap;
RowLayout();
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
* manage the memory yourself. See the chainable setters on RowLayout for
* what options you can customize for the layout
* @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);
static RowLayout* create();
};
/**
* Simple layout for arranging nodes in a column (vertical line)
*/
class GEODE_DLL ColumnLayout : public Layout {
class GEODE_DLL ColumnLayout : public AxisLayout {
protected:
Alignment m_alignment = Alignment::Center;
std::optional<float> m_alignHorizontally;
float m_gap;
ColumnLayout();
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);
/**
* Create a new ColumnLayout. Note that this class is not automatically
* managed by default, so you must assign it to a CCNode or manually
* manage the memory yourself. See the chainable setters on RowLayout for
* what options you can customize for the layout
* @returns Created ColumnLayout
*/
static ColumnLayout* create();
};
NS_CC_END

View file

@ -234,13 +234,11 @@ public:
void fastRemoveObject(CCObject* object);
/** Fast way to remove an element with a certain index */
void fastRemoveObjectAtIndex(unsigned int index);
RT_ADD(
/**
* Rob modification
* Fast way to remove an element with a certain index */
void fastRemoveObjectAtIndexNew(unsigned int index);
);
/**
* Fast way to remove an element with a certain index
* @note RobTop addition
*/
void fastRemoveObjectAtIndexNew(unsigned int index);
// Rearranging Content
@ -263,6 +261,13 @@ public:
*/
virtual CCObject* copyWithZone(CCZone* pZone);
/**
* Creates a shallow copy of this array, aka only clones the pointers to
* the array members and not the members themselves
* @returns New array with same members
*/
GEODE_DLL CCArray* shallowCopy();
/* override functions */
virtual void acceptVisitor(CCDataVisitor &visitor);

View file

@ -360,6 +360,13 @@ public:
inline bool equals(const CCSize& target) const {
return (fabs(this->width - target.width) < FLT_EPSILON) && (fabs(this->height - target.height) < FLT_EPSILON);
}
/**
* Get the aspect ratio of this CCSize
* @note Geode addition
*/
inline float aspect() const {
return this->width / this->height;
}
};
// alk cont

View file

@ -96,18 +96,35 @@ public:
*/
static CCEGLView* sharedOpenGLView();
/**
* @note Geode addition
*/
static GEODE_DLL CCEGLView* get();
RT_ADD( static CCEGLView* create(const gd::string&); )
/**
* @note RobTop addition
*/
static CCEGLView* create(const gd::string&);
RT_ADD(
//actually this is my function but i dont wanna make a new macro for it
inline CCPoint getMousePosition() { return { m_fMouseX, m_fMouseY }; }
/**
* @note Geode addition
*/
inline CCPoint getMousePosition() { return { m_fMouseX, m_fMouseY }; }
void toggleFullScreen(bool fullscreen);
/**
* @note RobTop addition
*/
void toggleFullScreen(bool fullscreen);
GLFWwindow* getWindow(void) const;
)
/**
* @note RobTop addition
*/
GLFWwindow* getWindow(void) const;
/**
* @note RobTop addition
*/
CCSize getDisplaySize();
protected:
static CCEGLView* s_pEglView;

View file

@ -58,6 +58,44 @@ namespace geode {
return buf.str();
}
// todo: maybe add a debugParse function for these?
template <class T>
requires requires(T t) {
parse(t);
}
std::string parse(std::optional<T> const& thing) {
if (thing.has_value()) {
return "opt(" + parse(thing.value()) + ")";
}
return "nullopt";
}
template <class A, class B>
requires requires(A a, B b) {
parse(a);
parse(b);
}
std::string parse(std::pair<A, B> const& thing) {
return "(" + parse(thing.first) + ", " + parse(thing.second) + ")";
}
template <class... T, std::size_t... Is>
std::string parseTupleImpl(std::tuple<T...> const& tuple, std::index_sequence<Is...>) {
std::string ret = "(";
((ret += (Is == 0 ? "" : ", ") + parse(std::get<Is>(tuple))), ...);
ret += ")";
return ret;
}
template <class... T>
requires requires(T... t) {
(parse(t), ...);
}
std::string parse(std::tuple<T...> const& tuple) {
return parseTupleImpl(tuple, std::index_sequence_for<T...> {});
}
// Log component system
struct GEODE_DLL ComponentTrait {

View file

@ -75,6 +75,10 @@ namespace geode::cast {
std::is_polymorphic_v<std::remove_pointer_t<Before>>, "Input is not a polymorphic type"
);
if (!ptr) {
return After();
}
auto basePtr = dynamic_cast<void*>(ptr);
auto vftable = *reinterpret_cast<VftableType**>(basePtr);

View file

@ -2,6 +2,8 @@
#include <cocos2d.h>
#pragma warning(disable: 4275)
namespace geode {
enum class CircleBaseSize {
Tiny = 0, // Equivalent to the tiny delete button

View file

@ -77,7 +77,7 @@ namespace geode {
struct JsonMaybeObject;
struct JsonMaybeValue;
struct JsonMaybeSomething {
struct GEODE_DLL JsonMaybeSomething {
protected:
JsonChecker& m_checker;
json::Value& m_json;
@ -87,29 +87,29 @@ namespace geode {
friend struct JsonMaybeObject;
friend struct JsonMaybeValue;
GEODE_DLL void setError(std::string const& error);
void setError(std::string const& error);
public:
GEODE_DLL json::Value& json();
json::Value& json();
GEODE_DLL JsonMaybeSomething(
JsonMaybeSomething(
JsonChecker& checker, json::Value& json, std::string const& hierarchy, bool hasValue
);
GEODE_DLL bool isError() const;
GEODE_DLL std::string getError() const;
bool isError() const;
std::string getError() const;
GEODE_DLL operator bool() const;
operator bool() const;
};
struct JsonMaybeValue : public JsonMaybeSomething {
struct GEODE_DLL JsonMaybeValue : public JsonMaybeSomething {
bool m_inferType = true;
GEODE_DLL JsonMaybeValue(
JsonMaybeValue(
JsonChecker& checker, json::Value& json, std::string const& hierarchy, bool hasValue
);
GEODE_DLL JsonMaybeSomething& self();
JsonMaybeSomething& self();
template <json::Type T>
JsonMaybeValue& as() {
@ -124,7 +124,7 @@ namespace geode {
return *this;
}
GEODE_DLL JsonMaybeValue& array();
JsonMaybeValue& array();
template <json::Type... T>
JsonMaybeValue& asOneOf() {
@ -231,7 +231,7 @@ namespace geode {
return T();
}
GEODE_DLL JsonMaybeObject obj();
JsonMaybeObject obj();
template <class T>
struct Iterator {
@ -257,46 +257,46 @@ namespace geode {
}
};
GEODE_DLL JsonMaybeValue at(size_t i);
JsonMaybeValue at(size_t i);
GEODE_DLL Iterator<JsonMaybeValue> iterate();
Iterator<JsonMaybeValue> iterate();
GEODE_DLL Iterator<std::pair<std::string, JsonMaybeValue>> items();
Iterator<std::pair<std::string, JsonMaybeValue>> items();
};
struct JsonMaybeObject : JsonMaybeSomething {
struct GEODE_DLL JsonMaybeObject : JsonMaybeSomething {
std::set<std::string> m_knownKeys;
GEODE_DLL JsonMaybeObject(
JsonMaybeObject(
JsonChecker& checker, json::Value& json, std::string const& hierarchy, bool hasValue
);
GEODE_DLL JsonMaybeSomething& self();
JsonMaybeSomething& self();
GEODE_DLL void addKnownKey(std::string const& key);
void addKnownKey(std::string const& key);
GEODE_DLL json::Value& json();
json::Value& json();
GEODE_DLL JsonMaybeValue emptyValue();
JsonMaybeValue emptyValue();
GEODE_DLL JsonMaybeValue has(std::string const& key);
JsonMaybeValue has(std::string const& key);
GEODE_DLL JsonMaybeValue needs(std::string const& key);
JsonMaybeValue needs(std::string const& key);
GEODE_DLL void checkUnknownKeys();
void checkUnknownKeys();
};
struct JsonChecker {
struct GEODE_DLL JsonChecker {
std::variant<std::monostate, std::string> m_result;
json::Value& m_json;
GEODE_DLL JsonChecker(json::Value& json);
JsonChecker(json::Value& json);
GEODE_DLL bool isError() const;
bool isError() const;
GEODE_DLL std::string getError() const;
std::string getError() const;
GEODE_DLL JsonMaybeValue root(std::string const& hierarchy);
JsonMaybeValue root(std::string const& hierarchy);
};
}

View file

@ -14,24 +14,73 @@ namespace geode {
};
/**
* A version label, like v1.0.0-alpha or v2.3.4-prerelease. Purely semantic,
* and not used in comparisons; so for example v1.0.0-alpha == v1.0.0.
* A version label, like v1.0.0-alpha or v2.3.4-prerelease. Limited to these
* options; arbitary identifiers are not supported. Additional numbering
* may be added after the identifier, such as v1.0.0-beta.1
*/
enum class VersionTag {
Alpha,
Beta,
Prerelease,
struct VersionTag {
enum {
Alpha,
Beta,
Prerelease,
} value;
std::optional<size_t> number;
using Type = decltype(value);
constexpr VersionTag(Type const& value) : value(value) {}
constexpr VersionTag(Type const& value, std::optional<size_t> number)
: value(value), number(number) {}
constexpr bool operator==(VersionTag const& other) const {
return value == other.value && number == other.number;
}
constexpr bool operator<(VersionTag const& other) const {
if (value == other.value) {
if (number && other.number) return number < other.number;
if (number) return true;
if (other.number) return false;
return false;
}
return value < other.value;
}
constexpr bool operator<=(VersionTag const& other) const {
if (value == other.value) {
if (number && other.number) return number <= other.number;
if (number) return true;
if (other.number) return false;
return true;
}
return value <= other.value;
}
constexpr bool operator>(VersionTag const& other) const {
if (value == other.value) {
if (number && other.number) return number > other.number;
if (number) return true;
if (other.number) return false;
return false;
}
return value > other.value;
}
constexpr bool operator>=(VersionTag const& other) const {
if (value == other.value) {
if (number && other.number) return number >= other.number;
if (number) return true;
if (other.number) return false;
return true;
}
return value >= other.value;
}
static Result<VersionTag> parse(std::stringstream& str);
std::string toSuffixString() const;
std::string toString() const;
};
GEODE_DLL std::optional<VersionTag> versionTagFromString(std::string const& str);
GEODE_DLL std::string versionTagToSuffixString(VersionTag tag);
GEODE_DLL std::string versionTagToString(VersionTag tag);
/**
* Class representing version information. Not strictly semver, notably in
* regard to identifiers; identifiers are restricted to a few common ones,
* and are purely semantic, i.e. not used in comparisons. See VersionTag
* for details
* @class VersionInfo
* Class representing version information. Uses a limited subset of SemVer;
* identifiers are restricted to a few predefined ones, and only one
* identifier is allowed. See VersionTag for details
*/
class GEODE_DLL VersionInfo final {
protected:
@ -78,24 +127,24 @@ namespace geode {
// Apple clang does not support operator<=>! Yippee!
constexpr bool operator==(VersionInfo const& other) const {
return std::tie(m_major, m_minor, m_patch) ==
std::tie(other.m_major, other.m_minor, other.m_patch);
return std::tie(m_major, m_minor, m_patch, m_tag) ==
std::tie(other.m_major, other.m_minor, other.m_patch, other.m_tag);
}
constexpr bool operator<(VersionInfo const& other) const {
return std::tie(m_major, m_minor, m_patch) <
std::tie(other.m_major, other.m_minor, other.m_patch);
return std::tie(m_major, m_minor, m_patch, m_tag) <
std::tie(other.m_major, other.m_minor, other.m_patch, other.m_tag);
}
constexpr bool operator<=(VersionInfo const& other) const {
return std::tie(m_major, m_minor, m_patch) <=
std::tie(other.m_major, other.m_minor, other.m_patch);
return std::tie(m_major, m_minor, m_patch, m_tag) <=
std::tie(other.m_major, other.m_minor, other.m_patch, other.m_tag);
}
constexpr bool operator>(VersionInfo const& other) const {
return std::tie(m_major, m_minor, m_patch) >
std::tie(other.m_major, other.m_minor, other.m_patch);
return std::tie(m_major, m_minor, m_patch, m_tag) >
std::tie(other.m_major, other.m_minor, other.m_patch, other.m_tag);
}
constexpr bool operator>=(VersionInfo const& other) const {
return std::tie(m_major, m_minor, m_patch) >=
std::tie(other.m_major, other.m_minor, other.m_patch);
return std::tie(m_major, m_minor, m_patch, m_tag) >=
std::tie(other.m_major, other.m_minor, other.m_patch, other.m_tag);
}
std::string toString(bool includeTag = true) const;

View file

@ -410,16 +410,31 @@ namespace geode::cocos {
* or nullptr if index exceeds bounds
*/
template <class Type = cocos2d::CCNode>
static Type* getChildOfType(cocos2d::CCNode* node, size_t index) {
static Type* getChildOfType(cocos2d::CCNode* node, int index) {
size_t indexCounter = 0;
for (size_t i = 0; i < node->getChildrenCount(); ++i) {
auto obj = cast::typeinfo_cast<Type*>(node->getChildren()->objectAtIndex(i));
if (obj != nullptr) {
if (indexCounter == index) {
return obj;
// start from end for negative index
if (index < 0) {
index = -index - 1;
for (size_t i = node->getChildrenCount() - 1; i >= 0; i--) {
auto obj = cast::typeinfo_cast<Type*>(node->getChildren()->objectAtIndex(i));
if (obj != nullptr) {
if (indexCounter == index) {
return obj;
}
++indexCounter;
}
}
}
else {
for (size_t i = 0; i < node->getChildrenCount(); i++) {
auto obj = cast::typeinfo_cast<Type*>(node->getChildren()->objectAtIndex(i));
if (obj != nullptr) {
if (indexCounter == index) {
return obj;
}
++indexCounter;
}
++indexCounter;
}
}
@ -548,86 +563,6 @@ namespace geode::cocos {
*/
GEODE_DLL bool fileExistsInSearchPaths(char const* filename);
template <typename T>
struct CCArrayIterator {
public:
CCArrayIterator(T* p) : m_ptr(p) {}
T* m_ptr;
auto& operator*() {
return *m_ptr;
}
auto& operator*() const {
return *m_ptr;
}
auto operator->() {
return m_ptr;
}
auto operator->() const {
return m_ptr;
}
auto& operator++() {
++m_ptr;
return *this;
}
auto& operator--() {
--m_ptr;
return *this;
}
auto& operator+=(size_t val) {
m_ptr += val;
return *this;
}
auto& operator-=(size_t val) {
m_ptr -= val;
return *this;
}
auto operator+(size_t val) const {
return CCArrayIterator<T>(m_ptr + val);
}
auto operator-(size_t val) const {
return CCArrayIterator<T>(m_ptr - val);
}
auto operator-(CCArrayIterator<T> const& other) const {
return m_ptr - other.m_ptr;
}
bool operator<(CCArrayIterator<T> const& other) const {
return m_ptr < other.m_ptr;
}
bool operator>(CCArrayIterator<T> const& other) const {
return m_ptr > other.m_ptr;
}
bool operator<=(CCArrayIterator<T> const& other) const {
return m_ptr <= other.m_ptr;
}
bool operator>=(CCArrayIterator<T> const& other) const {
return m_ptr >= other.m_ptr;
}
bool operator==(CCArrayIterator<T> const& other) const {
return m_ptr == other.m_ptr;
}
bool operator!=(CCArrayIterator<T> const& other) const {
return m_ptr != other.m_ptr;
}
};
inline void ccDrawColor4B(cocos2d::ccColor4B const& color) {
cocos2d::ccDrawColor4B(color.r, color.g, color.b, color.a);
}
@ -766,16 +701,6 @@ namespace std {
return std::hash<T*>()(ref.data());
}
};
template <typename T>
struct iterator_traits<geode::cocos::CCArrayIterator<T>> {
using difference_type = ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
using iterator_category =
std::random_access_iterator_tag; // its random access but im too lazy to implement it
};
}
// more utils
@ -807,9 +732,14 @@ namespace geode::cocos {
using T = std::remove_pointer_t<_Type>;
public:
using value_type = T;
using iterator = T**;
using const_iterator = const T**;
CCArrayExt() : m_arr(cocos2d::CCArray::create()) {}
CCArrayExt(cocos2d::CCArray* arr) : m_arr(arr) {}
CCArrayExt(cocos2d::CCArray* arr)
: m_arr(arr) {}
CCArrayExt(CCArrayExt const& a) : m_arr(a.m_arr) {}
@ -819,18 +749,26 @@ namespace geode::cocos {
~CCArrayExt() {}
auto begin() {
T** begin() const {
if (!m_arr) {
return CCArrayIterator<T*>(nullptr);
return nullptr;
}
return CCArrayIterator<T*>(reinterpret_cast<T**>(m_arr->data->arr));
return reinterpret_cast<T**>(m_arr->data->arr);
}
auto end() {
T** end() const {
if (!m_arr) {
return CCArrayIterator<T*>(nullptr);
return nullptr;
}
return CCArrayIterator<T*>(reinterpret_cast<T**>(m_arr->data->arr) + m_arr->count());
return reinterpret_cast<T**>(m_arr->data->arr) + m_arr->count();
}
auto rbegin() const {
return std::reverse_iterator(this->end());
}
auto rend() const {
return std::reverse_iterator(this->begin());
}
size_t size() const {

View file

@ -289,23 +289,21 @@ namespace geode::utils::ranges {
return member(*it);
}
template <ValidConstContainer C>
struct ConstReverseWrapper {
C const& iter;
template <class C>
struct ReverseWrapper {
C iter;
decltype(auto) begin() {
return std::rbegin(iter);
}
decltype(auto) end() {
return std::rend(iter);
}
};
template <ValidConstContainer C>
auto begin(ConstReverseWrapper<C> const& c) {
return std::rbegin(c.iter);
}
template <ValidConstContainer C>
auto end(ConstReverseWrapper<C> const& c) {
return std::rend(c.iter);
}
template <ValidConstContainer C>
ConstReverseWrapper<C> reverse(C const& iter) {
return { iter };
template <class C>
auto reverse(C&& iter) {
return ReverseWrapper<C>{std::forward<C>(iter)};
}
}

View file

@ -1,5 +1,5 @@
{
"geode": "@PROJECT_VERSION@",
"geode": "@PROJECT_VERSION@@PROJECT_VERSION_SUFFIX@",
"id": "geode.loader",
"version": "@PROJECT_VERSION@@PROJECT_VERSION_SUFFIX@",
"name": "Geode",

View file

@ -16,4 +16,10 @@ void CCArray::removeFirstObject(bool bReleaseObj) {
this->removeObjectAtIndex(0, bReleaseObj);
}
CCArray* CCArray::shallowCopy() {
auto r = CCArray::createWithCapacity(this->capacity());
r->addObjectsFromArray(this);
return r;
}
#pragma warning(pop)

File diff suppressed because it is too large Load diff

View file

@ -20,7 +20,7 @@ private:
Ref<cocos2d::CCObject> m_userObject;
std::string m_id = "";
std::unique_ptr<Layout> m_layout = nullptr;
PositionHint m_positionHint = PositionHint::Default;
std::unique_ptr<LayoutOptions> m_layoutOptions = nullptr;
std::unordered_map<std::string, std::any> m_attributes;
friend class ProxyCCNode;
@ -118,7 +118,13 @@ CCNode* CCNode::getChildByIDRecursive(std::string const& id) {
return nullptr;
}
void CCNode::setLayout(Layout* layout, bool apply) {
void CCNode::setLayout(Layout* layout, bool apply, bool respectAnchor) {
if (respectAnchor && this->isIgnoreAnchorPointForPosition()) {
for (auto child : CCArrayExt<CCNode>(m_pChildren)) {
child->setPosition(child->getPosition() + this->getScaledContentSize());
}
this->ignoreAnchorPointForPosition(false);
}
GeodeNodeMetadata::set(this)->m_layout.reset(layout);
if (apply) {
this->updateLayout();
@ -129,26 +135,24 @@ 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::create();
for (auto& child : CCArrayExt<CCNode>(m_pChildren)) {
if (child->getPositionHint() != PositionHint::Absolute) {
filtered->addObject(child);
}
}
layout->apply(filtered, m_obContentSize);
filtered->release();
void CCNode::setLayoutOptions(LayoutOptions* options, bool apply) {
GeodeNodeMetadata::set(this)->m_layoutOptions.reset(options);
if (apply && m_pParent) {
m_pParent->updateLayout();
}
}
void CCNode::setPositionHint(PositionHint hint) {
GeodeNodeMetadata::set(this)->m_positionHint = hint;
LayoutOptions* CCNode::getLayoutOptions() {
return GeodeNodeMetadata::set(this)->m_layoutOptions.get();
}
PositionHint CCNode::getPositionHint() {
return GeodeNodeMetadata::set(this)->m_positionHint;
void CCNode::updateLayout(bool updateChildOrder) {
if (updateChildOrder) {
this->sortAllChildren();
}
if (auto layout = GeodeNodeMetadata::set(this)->m_layout.get()) {
layout->apply(this);
}
}
void CCNode::setAttribute(std::string const& attr, std::any value) {

View file

@ -33,10 +33,13 @@ void setIDs(CCNode* node, int startIndex, Args... args) {
}
static void switchToMenu(CCNode* node, CCMenu* menu) {
if (!node || !menu) return;
auto worldPos = node->getParent()->convertToWorldSpace(node->getPosition());
node->retain();
node->removeFromParent();
node->setZOrder(0);
menu->addChild(node);
node->setPosition(menu->convertToNodeSpace(worldPos));
@ -55,6 +58,14 @@ static void switchChildrenToMenu(CCNode* parent, CCMenu* menu, Args... args) {
template <typename T, typename ...Args>
static CCMenu* detachAndCreateMenu(CCNode* parent, const char* menuID, Layout* layout, T first, Args... args) {
if (!first) {
auto menu = CCMenu::create();
menu->setID(menuID);
menu->setLayout(layout);
parent->addChild(menu);
return menu;
}
auto oldMenu = first->getParent();
first->retain();
@ -64,14 +75,25 @@ static CCMenu* detachAndCreateMenu(CCNode* parent, const char* menuID, Layout* l
newMenu->setPosition(parent->convertToNodeSpace(oldMenu->convertToWorldSpace(first->getPosition())));
newMenu->setID(menuID);
newMenu->setZOrder(oldMenu->getZOrder());
newMenu->setLayout(layout);
parent->addChild(newMenu);
first->setPosition(0, 0);
first->setZOrder(0);
newMenu->addChild(first);
first->release();
(switchToMenu(args, newMenu), ...);
newMenu->setLayout(layout);
return newMenu;
}
static CCSize getSizeSafe(CCNode* node) {
if (node) {
return node->getScaledContentSize();
}
else {
return CCSizeZero;
}
}

View file

@ -6,49 +6,118 @@
USE_GEODE_NAMESPACE();
template<class... Args>
static void reorderButtons(Args... args) {
int ooa = 0;
for (auto& arg : { args... }) {
if (arg) {
arg->setOrderOfArrival(ooa);
ooa += 1;
}
}
}
$register_ids(CreatorLayer) {
setIDSafe<CCSprite>(this, 0, "background");
auto winSize = CCDirector::get()->getWinSize();
if (auto menu = getChildOfType<CCMenu>(this, 0)) {
menu->setID("creator-buttons-menu");
setIDSafe(menu, 0, "create-button");
setIDSafe(menu, 1, "saved-button");
setIDSafe(menu, 2, "scores-button");
setIDSafe(menu, 3, "quests-button");
setIDSafe(menu, 4, "daily-button");
setIDSafe(menu, 5, "weekly-button");
setIDSafe(menu, 6, "featured-button");
setIDSafe(menu, 7, "hall-of-fame-button");
setIDSafe(menu, 8, "map-packs-button");
setIDSafe(menu, 9, "search-button");
setIDSafe(menu, 10, "gauntlets-button");
// move vault button to its own menu
if (auto lockBtn = setIDSafe(menu, -2, "vault-button")) {
detachAndCreateMenu(
auto menu = detachAndCreateMenu(
this,
"top-right-menu",
ColumnLayout::create(5.f, 0.f)->setAlignment(Alignment::Begin),
ColumnLayout::create()
->setAxisReverse(true)
->setAxisAlignment(AxisAlignment::End),
lockBtn
);
menu->setPositionY(
menu->getPositionY() - 150.f / 2 +
lockBtn->getScaledContentSize().height / 2
);
menu->setContentSize({ 60.f, 150.f });
menu->updateLayout();
}
// move treasure room button to its own menu
if (auto roomBtn = setIDSafe(menu, -1, "treasure-room-button")) {
detachAndCreateMenu(
auto menu = detachAndCreateMenu(
this,
"bottom-right-menu",
ColumnLayout::create(5.f, 0.f)->setAlignment(Alignment::End),
ColumnLayout::create()
->setAxisAlignment(AxisAlignment::Start),
roomBtn
);
menu->setPositionY(
menu->getPositionY() + 125.f / 2 -
roomBtn->getScaledContentSize().height / 2
);
menu->setContentSize({ 60.f, 125.f });
menu->updateLayout();
}
// row order is inverted because of layout
reorderButtons(
setIDSafe(menu, 6, "featured-button"),
setIDSafe(menu, 7, "hall-of-fame-button"),
setIDSafe(menu, 8, "map-packs-button"),
setIDSafe(menu, 9, "search-button"),
setIDSafe(menu, 3, "quests-button"),
setIDSafe(menu, 4, "daily-button"),
setIDSafe(menu, 5, "weekly-button"),
setIDSafe(menu, 10, "gauntlets-button"),
setIDSafe(menu, 0, "create-button"),
setIDSafe(menu, 1, "saved-button"),
setIDSafe(menu, 2, "scores-button")
);
if (winSize.width / winSize.height <= 5.1f / 3.f) {
menu->setContentSize({ winSize.width - 80.f, 310.f });
}
else {
menu->setContentSize({ winSize.width - 120.f, 310.f });
}
menu->setLayout(
RowLayout::create()
->setGap(12.f)
->setCrossAxisReverse(true)
->setGrowCrossAxis(true)
->setCrossAxisOverflow(false)
);
}
if (auto menu = getChildOfType<CCMenu>(this, 1)) {
menu->setID("exit-menu");
setIDSafe(menu, 0, "exit-button");
auto exitBtn = setIDSafe(menu, 0, "exit-button");
menu->setPositionY(
menu->getPositionY() - 125.f / 2 +
getSizeSafe(exitBtn).height / 2
);
menu->setContentSize({ 60.f, 125.f });
menu->setLayout(
ColumnLayout::create()
->setAxisAlignment(AxisAlignment::End)
);
}
// add a menu to the bottom left corner that is empty but prolly a place mods
// want to add stuff to
auto menu = CCMenu::create();
menu->setPosition(24.f, 0.f + 125.f / 2);
menu->setID("bottom-left-menu");
menu->setContentSize({ 60.f, 125.f });
menu->setLayout(
ColumnLayout::create()
->setAxisAlignment(AxisAlignment::Start)
);
this->addChild(menu);
}
struct CreatorLayerIDs : Modify<CreatorLayerIDs, CreatorLayer> {

View file

@ -3,6 +3,7 @@
#include <Geode/Bindings.hpp>
#include <Geode/modify/EditLevelLayer.hpp>
#include <Geode/utils/cocos.hpp>
#include <Geode/ui/BasedButtonSprite.hpp>
USE_GEODE_NAMESPACE();
@ -18,22 +19,26 @@ $register_ids(EditLevelLayer) {
"description-background",
"description-input",
"description-text-area",
"level-action-menu",
"level-edit-menu",
"level-length",
"level-song",
"level-verified",
"version-label",
"level-id-label",
"right-side-menu",
"back-button-menu",
"level-actions-menu",
"back-menu",
"info-button-menu"
);
if (auto menu = this->getChildByID("level-action-menu")) {
auto winSize = CCDirector::get()->getWinSize();
if (auto menu = this->getChildByID("level-edit-menu")) {
setIDs(menu, 0, "edit-button", "play-button", "share-button");
menu->setContentSize({ winSize.width - 160.f, 100.f });
menu->setLayout(RowLayout::create()->setGap(25.f));
}
if (auto menu = this->getChildByID("right-side-menu")) {
if (auto menu = this->getChildByID("level-actions-menu")) {
setIDs(
menu,
0,
@ -44,12 +49,42 @@ $register_ids(EditLevelLayer) {
"folder-button"
);
detachAndCreateMenu(
menu, "folder-menu", ColumnLayout::create(), menu->getChildByID("folder-button")
auto folderMenu = detachAndCreateMenu(
this, "folder-menu",
ColumnLayout::create(),
menu->getChildByID("folder-button")
);
folderMenu->setContentSize({ 50.f, 215.f });
folderMenu->updateLayout();
menu->setPosition(
menu->getPositionX() + static_cast<CCNode*>(
menu->getChildren()->firstObject()
)->getPositionX(),
winSize.height / 2
);
menu->setContentSize({ 60.f, winSize.height - 15.f });
menu->setLayout(
ColumnLayout::create()
->setGap(7.f)
->setAxisAlignment(AxisAlignment::End)
->setAxisReverse(true)
);
menu->setZOrder(1);
}
if (auto menu = this->getChildByID("back-button-menu")) setIDSafe(menu, 0, "back-button");
if (auto menu = this->getChildByID("back-menu")) {
auto backBtn = setIDSafe(menu, 0, "back-button");
menu->setPositionX(
menu->getPositionX() + 100.f / 2 -
getSizeSafe(backBtn).width / 2
);
menu->setContentSize({ 100.f, 50.f });
menu->setLayout(
RowLayout::create()
->setAxisAlignment(AxisAlignment::Start)
);
}
if (auto menu = this->getChildByID("info-button-menu")) setIDSafe(menu, 0, "info-button");
}

View file

@ -0,0 +1,273 @@
#include "AddIDs.hpp"
#include <Geode/modify/EditorPauseLayer.hpp>
USE_GEODE_NAMESPACE();
// special class for this because making it a CCMenuItemToggler would be very UB
// (not gonna reinterpret_cast that into the members)
class GuidelinesButton : public CCMenuItemSpriteExtra {
protected:
bool init() {
if (!CCMenuItemSpriteExtra::init(
CCSprite::createWithSpriteFrameName("GJ_audioOffBtn_001.png"),
nullptr,
this, nullptr
)) return false;
this->updateSprite();
return true;
}
void updateSprite() {
this->setNormalImage(CCSprite::createWithSpriteFrameName(
GameManager::get()->m_showSongMarkers ?
"GJ_audioOnBtn_001.png" :
"GJ_audioOffBtn_001.png"
));
}
void activate() override {
CCMenuItemSpriteExtra::activate();
GameManager::get()->m_showSongMarkers ^= 1;
this->updateSprite();
}
public:
static GuidelinesButton* create() {
auto ret = new GuidelinesButton();
if (ret && ret->init()) {
ret->autorelease();
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}
};
$register_ids(EditorPauseLayer) {
auto winSize = CCDirector::get()->getWinSize();
if (auto menu = getChildOfType<CCMenu>(this, 0)) {
menu->setID("resume-menu");
setIDs(
menu, 0,
"resume-button",
"save-and-play-button",
"save-and-exit-button",
"save-button",
"exit-button"
);
menu->setContentSize({ 100.f, 220.f });
menu->setLayout(
ColumnLayout::create()
->setGap(12.5f)
->setAxisReverse(true)
);
}
setIDs(
this, 2,
"ignore-damage-label",
"follow-player-label",
"select-filter-label",
"show-grid-label",
"show-object-info-label",
"show-ground-label",
"preview-mode-label",
"object-count-label",
"length-label",
"length-name-label"
);
if (auto menu = getChildOfType<CCMenu>(this, 1)) {
menu->setID("bottom-menu");
setIDs(
menu, 0,
"guidelines-enable-button",
"help-button",
"guidelines-disable-button",
"uncheck-portals-button",
"reset-unused-button",
"create-edges-button",
"create-outlines-button",
"create-base-button",
"build-helper-button",
"align-x-button",
"align-y-button",
"select-all-button",
"select-all-left-button",
"select-all-right-button",
"ignore-damage-toggle",
"follow-player-toggle",
"select-filter-toggle",
"show-grid-toggle",
"show-object-info-toggle",
"show-ground-toggle",
"preview-mode-toggle",
"keys-button",
"settings-button"
);
auto smallActionsMenu = detachAndCreateMenu(
this,
"small-actions-menu",
ColumnLayout::create()
->setAxisAlignment(AxisAlignment::Start)
->setAxisReverse(true),
menu->getChildByID("align-x-button"),
menu->getChildByID("align-y-button"),
menu->getChildByID("select-all-button"),
menu->getChildByID("select-all-left-button"),
menu->getChildByID("select-all-right-button")
);
smallActionsMenu->setContentSize({ 100.f, 240.f });
smallActionsMenu->setPositionY(130.f);
smallActionsMenu->updateLayout();
auto actionsMenu = detachAndCreateMenu(
this,
"actions-menu",
ColumnLayout::create()
->setAxisAlignment(AxisAlignment::Start)
->setAxisReverse(true),
menu->getChildByID("keys-button"),
menu->getChildByID("build-helper-button"),
menu->getChildByID("create-base-button"),
menu->getChildByID("create-outlines-button"),
menu->getChildByID("create-edges-button"),
menu->getChildByID("reset-unused-button"),
menu->getChildByID("uncheck-portals-button")
);
if (auto keysBtn = actionsMenu->getChildByID("keys-button")) {
keysBtn->setLayoutOptions(AxisLayoutOptions::create()->setPrevGap(10.f));
}
actionsMenu->setContentSize({ 100.f, 240.f });
actionsMenu->setPositionY(130.f);
actionsMenu->updateLayout();
auto optionsMenu = detachAndCreateMenu(
this,
"options-menu",
RowLayout::create()
->setGap(0.f)
->setAxisAlignment(AxisAlignment::Start)
->setGrowCrossAxis(true)
->setCrossAxisAlignment(AxisAlignment::Start)
->setCrossAxisOverflow(false),
menu->getChildByID("preview-mode-toggle"),
this->getChildByID("preview-mode-label"),
menu->getChildByID("show-ground-toggle"),
this->getChildByID("show-ground-label"),
menu->getChildByID("show-object-info-toggle"),
this->getChildByID("show-object-info-label"),
menu->getChildByID("show-grid-toggle"),
this->getChildByID("show-grid-label"),
menu->getChildByID("select-filter-toggle"),
this->getChildByID("select-filter-label"),
menu->getChildByID("follow-player-toggle"),
this->getChildByID("follow-player-label"),
menu->getChildByID("ignore-damage-toggle"),
this->getChildByID("ignore-damage-label")
);
for (auto node : CCArrayExt<CCNode>(optionsMenu->getChildren())) {
if (auto label = typeinfo_cast<CCLabelBMFont*>(node)) {
label->setLayoutOptions(
AxisLayoutOptions::create()
->setSameLine(true)
->setBreakLine(true)
->setPrevGap(5.f)
->setMinScale(.1f)
->setMaxScale(.5f)
->setScalePriority(1)
);
}
}
optionsMenu->setContentSize({ 120.f, winSize.height - 100.f });
optionsMenu->setPosition(70.f, winSize.height / 2 - 50.f + 10.f);
optionsMenu->updateLayout();
auto settingsMenu = detachAndCreateMenu(
this,
"settings-menu",
RowLayout::create()
->setAxisReverse(true),
menu->getChildByID("settings-button")
);
settingsMenu->setContentSize({ 95.f, 50.f });
settingsMenu->updateLayout();
auto guidelinesMenu = menu;
// replace the two guidelines buttons with a single toggle
guidelinesMenu->getChildByID("guidelines-enable-button")->removeFromParent();
guidelinesMenu->getChildByID("guidelines-disable-button")->removeFromParent();
auto glToggle = GuidelinesButton::create();
glToggle->setID("guidelines-enable-toggle");
guidelinesMenu->insertBefore(glToggle, nullptr);
m_guidelinesOffButton = m_guidelinesOnButton = glToggle;
this->updateSongButton();
guidelinesMenu->setID("guidelines-menu");
guidelinesMenu->setContentSize({ winSize.width / 2, 50.f });
guidelinesMenu->setLayout(RowLayout::create());
auto topMenu = CCMenu::create();
topMenu->setContentSize({ winSize.width / 2, 50.f });
topMenu->setPosition(winSize.width / 2, winSize.height - 30.f);
topMenu->setID("top-menu");
topMenu->setLayout(RowLayout::create());
this->addChild(topMenu);
}
if (auto menu = detachAndCreateMenu(
this, "info-menu",
ColumnLayout::create()
->setGap(10.f)
->setAxisAlignment(AxisAlignment::End)
->setAxisReverse(true)
->setCrossAxisOverflow(false)
->setCrossAxisLineAlignment(AxisAlignment::Start),
this->getChildByID("object-count-label"),
this->getChildByID("length-label"),
this->getChildByID("length-name-label")
)) {
for (auto child : CCArrayExt<CCNode>(menu->getChildren())) {
child->setLayoutOptions(
AxisLayoutOptions::create()
->setMinScale(.1f)
->setMaxScale(.6f)
->setBreakLine(true)
);
}
menu->setContentSize({ 165.f, 100.f });
menu->setPosition(85.f, winSize.height - 55.f);
menu->updateLayout();
}
}
struct EditorPauseLayerIDs : Modify<EditorPauseLayerIDs, EditorPauseLayer> {
static void onModify(auto& self) {
if (!self.setHookPriority("EditorPauseLayer::init", GEODE_ID_PRIORITY)) {
log::warn("Failed to set EditorPauseLayer::init hook priority, node IDs may not work properly");
}
}
bool init(LevelEditorLayer* lel) {
if (!EditorPauseLayer::init(lel)) return false;
NodeIDs::get()->provide(this);
return true;
}
};

View file

@ -11,6 +11,8 @@ $register_ids(EditorUI) {
setIDSafe(this, this->getChildrenCount() - 2, "layer-index-label");
setIDSafe(this, this->getChildrenCount() - 1, "object-info-label");
auto winSize = CCDirector::get()->getWinSize();
if (auto menu = getChildOfType<CCMenu>(this, 0)) {
menu->setID("toolbar-categories-menu");
@ -28,7 +30,7 @@ $register_ids(EditorUI) {
"undo-button",
"redo-button",
"delete-button",
"delete-trash-button",
"music-playback-button",
@ -42,51 +44,105 @@ $register_ids(EditorUI) {
"unlink-button"
);
detachAndCreateMenu(
auto toolbarTogglesMenu = detachAndCreateMenu(
this,
"toolbar-toggles-menu",
GridLayout::create(2, GridAlignment::Begin, GridDirection::Column),
RowLayout::create()
->setCrossAxisOverflow(false)
->setGrowCrossAxis(true)
->setAxisAlignment(AxisAlignment::Center)
->setCrossAxisAlignment(AxisAlignment::Center),
menu->getChildByID("swipe-button"),
menu->getChildByID("rotate-button"),
menu->getChildByID("free-move-button"),
menu->getChildByID("snap-button"),
menu->getChildByID("rotate-button")
menu->getChildByID("snap-button")
);
toolbarTogglesMenu->setPosition(
winSize.width - 47.f,
45.f
);
toolbarTogglesMenu->setContentSize({ 90.f, 90.f });
toolbarTogglesMenu->updateLayout();
detachAndCreateMenu(
auto undoMenuWidth = winSize.width / 2 - 90.f;
auto undoMenu = detachAndCreateMenu(
this,
"top-left-menu",
RowLayout::create(),
"undo-menu",
RowLayout::create()
->setAxisAlignment(AxisAlignment::Start)
->setGap(10.f),
menu->getChildByID("undo-button"),
menu->getChildByID("redo-button"),
menu->getChildByID("delete-button")
menu->getChildByID("delete-trash-button")
);
detachAndCreateMenu(
this, "playback-menu", RowLayout::create(), menu->getChildByID("music-playback-button")
undoMenu->setContentSize({ undoMenuWidth, 50.f });
undoMenu->setPositionX(
undoMenu->getPositionX() + undoMenuWidth / 2 -
getSizeSafe(undoMenu->getChildByID("undo-button")).width / 2
);
undoMenu->updateLayout();
detachAndCreateMenu(
auto playBackMenu = detachAndCreateMenu(
this,
"playback-menu",
RowLayout::create()
->setAxisAlignment(AxisAlignment::Start),
menu->getChildByID("music-playback-button")
);
playBackMenu->setContentSize({ 100.f, 50.f });
playBackMenu->setPositionX(
playBackMenu->getPositionX() + 100.f / 2 -
getSizeSafe(playBackMenu->getChildByID("music-playback-button")).width / 2
);
playBackMenu->updateLayout();
auto playTestMenu = detachAndCreateMenu(
this,
"playtest-menu",
RowLayout::create(),
RowLayout::create()
->setAxisAlignment(AxisAlignment::Start),
menu->getChildByID("playtest-button"),
menu->getChildByID("stop-playtest-button")
);
playTestMenu->setContentSize({ 100.f, 50.f });
playTestMenu->setPositionX(
playTestMenu->getPositionX() + 100.f / 2 -
getSizeSafe(playTestMenu->getChildByID("playtest-button")).width / 2
);
playTestMenu->updateLayout();
detachAndCreateMenu(
auto zoomMenuHeight = winSize.height - 245.f;
auto zoomMenu = detachAndCreateMenu(
this,
"zoom-menu",
ColumnLayout::create(),
menu->getChildByID("zoom-in-button"),
menu->getChildByID("zoom-out-button")
ColumnLayout::create()
->setAxisAlignment(AxisAlignment::Start),
menu->getChildByID("zoom-out-button"),
menu->getChildByID("zoom-in-button")
);
zoomMenu->setPositionY(150.f * winSize.height / 320);
zoomMenu->setContentSize({ 50.f, zoomMenuHeight });
zoomMenu->updateLayout();
detachAndCreateMenu(
auto linkMenu = detachAndCreateMenu(
this,
"link-menu",
ColumnLayout::create(),
menu->getChildByID("link-button"),
menu->getChildByID("unlink-button")
ColumnLayout::create()
->setAxisAlignment(AxisAlignment::Start)
->setGrowCrossAxis(true),
menu->getChildByID("unlink-button"),
menu->getChildByID("link-button")
);
linkMenu->setPositionY(150.f * winSize.height / 320);
linkMenu->setContentSize({ 125.f, zoomMenuHeight });
linkMenu->updateLayout();
menu->setPosition(42.f, 45.f);
menu->setContentSize({ 100.f, 90.f });
menu->setLayout(
ColumnLayout::create()
->setGap(4.f)
->setAxisReverse(true)
);
}
@ -108,24 +164,43 @@ $register_ids(EditorUI) {
"delete-help-icon"
);
detachAndCreateMenu(
auto deleteButtonMenu = detachAndCreateMenu(
menu,
"delete-button-menu",
GridLayout::create(2, GridAlignment::Begin, GridDirection::Column),
ColumnLayout::create()
->setCrossAxisOverflow(false)
->setGrowCrossAxis(true)
->setAxisReverse(true)
->setCrossAxisReverse(true)
->setAxisAlignment(AxisAlignment::End)
->setCrossAxisAlignment(AxisAlignment::Center),
menu->getChildByID("delete-button"),
menu->getChildByID("delete-all-of-button"),
menu->getChildByID("delete-startpos-button")
);
detachAndCreateMenu(
deleteButtonMenu->setPosition(-88.5f, 0.f);
deleteButtonMenu->setContentSize({ winSize.width / 2 - 120.f, 80.f });
deleteButtonMenu->updateLayout();
auto filterMenuWidth = winSize.width / 2 - 150.f;
auto deleteFilterMenu = detachAndCreateMenu(
menu,
"delete-filter-menu",
GridLayout::create(2, GridAlignment::Begin, GridDirection::Column),
ColumnLayout::create()
->setCrossAxisOverflow(false)
->setGrowCrossAxis(true)
->setAxisReverse(true)
->setCrossAxisReverse(true)
->setAxisAlignment(AxisAlignment::End)
->setCrossAxisAlignment(AxisAlignment::Start),
menu->getChildByID("delete-filter-none"),
menu->getChildByID("delete-filter-static"),
menu->getChildByID("delete-filter-detail"),
menu->getChildByID("delete-filter-custom")
);
deleteFilterMenu->setPosition(48.5f + filterMenuWidth / 2, 0.f);
deleteFilterMenu->setContentSize({ filterMenuWidth, 80.f });
deleteFilterMenu->updateLayout();
}
if (auto menu = getChildOfType<CCMenu>(this, 2)) {
@ -134,20 +209,27 @@ $register_ids(EditorUI) {
setIDs(
menu,
0,
"static-tab-1",
"static-tab-2",
"static-tab-3",
"block-tab",
"half-block-tab",
"outline-tab",
"slope-tab",
"hazard-tab",
"3d-tab",
"portal-tab",
"deco-tab-1",
"deco-tab-2",
"ground-deco-tab",
"air-deco-tab",
"pulse-deco-tab",
"sawblade-tab",
"trigger-tab",
"custom-tab"
);
menu->setPosition(winSize.width / 2, 100.f);
menu->setContentSize({ winSize.width, 50.f });
menu->setLayout(
RowLayout::create()
->setGap(0.f)
);
}
if (auto menu = getChildOfType<CCMenu>(this, 3)) {
@ -174,18 +256,33 @@ $register_ids(EditorUI) {
"all-layers-button"
);
detachAndCreateMenu(
auto topRightMenuWidth = winSize.width / 2 - 140.f;
auto topRightMenu = detachAndCreateMenu(
this,
"top-right-menu",
RowLayout::create()->setAlignment(Alignment::End),
"settings-menu",
RowLayout::create()
->setAxisReverse(true)
->setAxisAlignment(AxisAlignment::End),
menu->getChildByID("pause-button"),
menu->getChildByID("settings-button")
);
topRightMenu->setContentSize({ topRightMenuWidth, 60.f });
topRightMenu->setPositionX(
topRightMenu->getPositionX() - topRightMenuWidth / 2 +
getSizeSafe(topRightMenu->getChildByID("pause-button")).width / 2
);
topRightMenu->updateLayout();
detachAndCreateMenu(
auto rightMenu = detachAndCreateMenu(
this,
"editor-buttons-menu",
GridLayout::create(4, GridAlignment::End, GridDirection::Column),
ColumnLayout::create()
->setAxisAlignment(AxisAlignment::End)
->setCrossAxisAlignment(AxisAlignment::End)
->setGap(-3.5f)
->setGrowCrossAxis(true)
->setCrossAxisOverflow(false)
->setAxisReverse(true),
menu->getChildByID("copy-paste-button"),
menu->getChildByID("edit-object-button"),
menu->getChildByID("paste-color-button"),
@ -199,16 +296,35 @@ $register_ids(EditorUI) {
menu->getChildByID("copy-values-button"),
menu->getChildByID("hsv-button")
);
for (auto btn : CCArrayExt<CCNode>(rightMenu->getChildren())) {
btn->setContentSize({ 40.f, 40.f });
}
rightMenu->setContentSize({ 210.f, 160.f });
rightMenu->setPosition(
winSize.width - 210.f / 2 - 5.f,
winSize.height / 2 + 42.5f
);
rightMenu->updateLayout();
detachAndCreateMenu(
this->getChildByID("layer-index-label")->setLayoutOptions(
AxisLayoutOptions::create()
->setAutoScale(false)
->setLength(25.f)
);
auto layerMenu = detachAndCreateMenu(
this,
"layer-menu",
RowLayout::create(),
RowLayout::create()
->setAxisAlignment(AxisAlignment::Start),
menu->getChildByID("all-layers-button"),
menu->getChildByID("prev-layer-button"),
this->getChildByID("layer-index-label"),
menu->getChildByID("next-layer-button")
);
layerMenu->setPositionX(winSize.width - 110.f / 2);
layerMenu->setContentSize({ 110.f, 30.f });
layerMenu->updateLayout();
}
}

View file

@ -10,8 +10,10 @@ $register_ids(GJGarageLayer) {
setIDSafe(this, 2, "username-label");
setIDSafe(this, 6, "player-icon");
auto winSize = CCDirector::get()->getWinSize();
if (auto menu = getChildOfType<CCMenu>(this, 0)) {
menu->setID("icon-select-menu");
menu->setID("category-menu");
setIDs(
menu,
@ -26,6 +28,13 @@ $register_ids(GJGarageLayer) {
"trail-button",
"death-effect-button"
);
menu->setContentSize({ 320.f, 50.f });
menu->setLayout(
RowLayout::create()
->setAxisAlignment(AxisAlignment::Start)
->setGap(-4.f)
);
}
setIDs(
@ -47,13 +56,99 @@ $register_ids(GJGarageLayer) {
"color-selection-menu"
);
if (auto menu = getChildOfType<CCMenu>(this, 11)) {
if (auto menu = getChildOfType<CCMenu>(this, 1)) {
menu->setID("top-left-menu");
setIDs(menu, 0, "back-button", "shop-button", "shards-button");
detachAndCreateMenu(
menu, "shards-button-menu", ColumnLayout::create(), menu->getChildByID("shards-button")
auto backBtn = menu->getChildByID("back-button");
auto backMenu = detachAndCreateMenu(
this,
"back-menu",
RowLayout::create()
->setAxisAlignment(AxisAlignment::Start),
backBtn
);
backMenu->setContentSize({ 100.f, 50.f });
backMenu->setPositionX(
backMenu->getPositionX() + 100.f / 2 -
getSizeSafe(backBtn).width / 2
);
backMenu->updateLayout();
auto shardsBtn = menu->getChildByID("shards-button");
auto shardsMenu = detachAndCreateMenu(
this,
"shards-menu",
ColumnLayout::create()
->setAxisReverse(true)
->setAxisAlignment(AxisAlignment::End),
shardsBtn
);
shardsMenu->setContentSize({ 50.f, 100.f });
shardsMenu->setPositionY(
shardsMenu->getPositionY() - 100.f / 2 +
getSizeSafe(shardsBtn).height / 2
);
shardsMenu->updateLayout();
}
auto bottomLeftMenu = CCMenu::create();
bottomLeftMenu->setID("bottom-left-menu");
bottomLeftMenu->setContentSize({ 50.f, 70.f });
bottomLeftMenu->setPosition(30.f, 115.f);
bottomLeftMenu->setLayout(
ColumnLayout::create()
->setAxisAlignment(AxisAlignment::Start)
);
this->addChild(bottomLeftMenu);
auto bottomRightMenu = CCMenu::create();
bottomRightMenu->setID("bottom-right-menu");
bottomRightMenu->setContentSize({ 50.f, 110.f });
bottomRightMenu->setPosition(winSize.width - 30.f, 135.f);
bottomRightMenu->setLayout(
ColumnLayout::create()
->setAxisAlignment(AxisAlignment::Start)
);
this->addChild(bottomRightMenu);
// aspect ratio responsiveness
if (winSize.width / winSize.height <= 5.1f / 3.f) {
bottomLeftMenu->setPosition(15.f, 115.f);
bottomRightMenu->setPosition(winSize.width - 15.f, 135.f);
if (auto shardsMenu = this->getChildByID("shards-menu")) {
shardsMenu->setContentSize({ 110.f, 50.f });
shardsMenu->setPosition(
shardsMenu->getPosition() + ccp(50.f, 30.f)
);
shardsMenu->setLayout(
RowLayout::create()
->setAxisAlignment(AxisAlignment::Start)
);
}
}
if (winSize.width / winSize.height <= 4.1f / 3.f) {
bottomLeftMenu->setContentSize({ 90.f, 50.f });
bottomLeftMenu->setPosition(
15.f + 110.f / 2,
85.f
);
bottomLeftMenu->setLayout(
RowLayout::create()
->setAxisAlignment(AxisAlignment::Start)
);
bottomRightMenu->setContentSize({ 90.f, 50.f });
bottomRightMenu->setPosition(
winSize.width - 15.f - 110.f / 2,
85.f
);
bottomRightMenu->setLayout(
RowLayout::create()
->setAxisReverse(true)
->setAxisAlignment(AxisAlignment::End)
);
}
}

View file

@ -7,28 +7,153 @@
USE_GEODE_NAMESPACE();
$register_ids(LevelBrowserLayer) {
auto winSize = CCDirector::get()->getWinSize();
if (auto menu = getChildOfType<CCMenu>(this, 0)) {
menu->setID("go-back-menu");
setIDSafe(menu, 0, "back-button");
menu->setID("back-menu");
auto btn = setIDSafe(menu, 0, "back-button");
menu->setContentSize({ 100.f, 50.f });
menu->setPositionX(
menu->getPositionX() + 100.f / 2 -
getSizeSafe(btn).width / 2
);
menu->setLayout(
RowLayout::create()
->setAxisAlignment(AxisAlignment::Start)
);
}
if (m_searchObject->m_searchType == SearchType::MyLevels) {
if (auto menu = getChildOfType<CCMenu>(this, 2)) {
menu->setID("new-level-menu");
setIDSafe(menu, 0, "new-level-button");
auto newLvlBtn = setIDSafe(menu, 0, "new-level-button");
if (auto myLevelsBtn = setIDSafe(menu, 1, "my-levels-button")) {
detachAndCreateMenu(
auto menu = detachAndCreateMenu(
this,
"my-levels-menu",
ColumnLayout::create(5.f, 0.f)->setAlignment(Alignment::End),
ColumnLayout::create()
->setAxisAlignment(AxisAlignment::Start),
myLevelsBtn
);
menu->setPositionY(
menu->getPositionY() + 125.f / 2 -
myLevelsBtn->getScaledContentSize().height / 2
);
menu->setContentSize({ 50.f, 125.f });
menu->updateLayout();
}
menu->setLayout(ColumnLayout::create(5.f, 0.f)->setAlignment(Alignment::End));
menu->setLayout(
ColumnLayout::create()
->setAxisAlignment(AxisAlignment::Start)
);
menu->setPositionY(
menu->getPositionY() + 130.f / 2 -
getSizeSafe(newLvlBtn).height / 2
);
menu->setContentSize({ 50.f, 130.f });
menu->updateLayout();
}
if (auto menu = getChildOfType<CCMenu>(this, 1)) {
if (auto searchBtn = setIDSafe(menu, 5, "search-button")) {
auto clearBtn = setIDSafe(menu, 6, "clear-search-button");
// this is a hacky fix because for some reason adding children
// before the clear button is made visible is inconsistent
if (clearBtn) {
searchBtn->setZOrder(-1);
clearBtn->setZOrder(-1);
}
auto searchMenu = detachAndCreateMenu(
this,
"search-menu",
ColumnLayout::create()
->setAxisReverse(true)
->setCrossAxisReverse(true)
->setGrowCrossAxis(true)
->setCrossAxisOverflow(false)
->setCrossAxisAlignment(AxisAlignment::Start)
->setAxisAlignment(AxisAlignment::End),
searchBtn,
clearBtn
);
auto width = 45.f * winSize.aspect();
searchMenu->setPosition(
searchMenu->getPositionX() + width / 2 -
searchBtn->getScaledContentSize().width / 2,
searchMenu->getPositionY() - 80.f / 2 +
searchBtn->getScaledContentSize().height / 2
);
searchMenu->setContentSize({ width, 80.f });
searchMenu->updateLayout();
}
if (auto pageBtn = setIDSafe(menu, 2, "page-button")) {
auto folderBtn = setIDSafe(menu, 3, "folder-button");
auto lastPageBtn = setIDSafe(menu, 4, "last-page-button");
auto pageMenu = detachAndCreateMenu(
this,
"page-menu",
ColumnLayout::create()
->setAxisReverse(true)
->setGrowCrossAxis(true)
->setAxisAlignment(AxisAlignment::End),
pageBtn,
folderBtn,
lastPageBtn
);
pageMenu->setContentSize({ 40.f, 110.f });
pageMenu->setAnchorPoint({ 1.f, .5f });
pageMenu->setPosition(
pageMenu->getPositionX() + 20.f,
pageMenu->getPositionY() - 110.f / 2 + 12.5f
);
pageMenu->updateLayout();
}
auto navMenuWidth = 50.f * winSize.aspect();
if (auto prevPageBtn = setIDSafe(menu, 0, "prev-page-button")) {
auto navMenu = detachAndCreateMenu(
this,
"prev-page-menu",
RowLayout::create()
->setAxisAlignment(AxisAlignment::Start),
prevPageBtn
);
prevPageBtn->setZOrder(-1);
navMenu->setContentSize({ navMenuWidth, 40.f });
navMenu->setPositionX(
navMenu->getPositionX() + navMenuWidth / 2 -
prevPageBtn->getScaledContentSize().width / 2
);
navMenu->updateLayout();
}
auto nextPageBtn = setIDSafe(menu, 0, "next-page-button");
menu->setID("next-page-menu");
menu->setLayout(
RowLayout::create()
->setAxisReverse(true)
->setAxisAlignment(AxisAlignment::End)
);
menu->setContentSize({ navMenuWidth, 40.f });
menu->setPositionX(
winSize.width - navMenuWidth / 2 - 5.f
);
menu->updateLayout();
}
}
auto bottomMenu = CCMenu::create();
bottomMenu->setID("bottom-menu");
bottomMenu->setContentSize({ 325.f + 20.f * winSize.aspect(), 50.f });
bottomMenu->setPosition(winSize.width / 2, 28.f);
bottomMenu->setZOrder(15);
bottomMenu->setLayout(RowLayout::create());
this->addChild(bottomMenu);
}
struct LevelBrowserLayerIDs : Modify<LevelBrowserLayerIDs, LevelBrowserLayer> {

View file

@ -35,26 +35,52 @@ $register_ids(LevelInfoLayer) {
setIDSafe<CustomSongWidget>(this, 0, "custom-songs-widget");
if (auto menu = getChildOfType<CCMenu>(this, 0)) {
menu->setID("exit-menu");
setIDSafe(menu, 0, "exit-button");
menu->setID("play-menu");
setIDSafe(menu, 0, "play-button");
}
if (auto menu = getChildOfType<CCMenu>(this, 2)) {
menu->setID("back-menu");
auto backBtn = setIDSafe(menu, 0, "back-button");
menu->setPositionX(
menu->getPositionX() + 100.f / 2 -
getSizeSafe(backBtn).width / 2
);
menu->setContentSize({ 100.f, 50.f });
menu->setLayout(
RowLayout::create()
->setAxisAlignment(AxisAlignment::Start)
);
}
if (auto menu = getChildOfType<CCMenu>(this, 1)) {
menu->setID("right-side-menu");
if (auto name = setIDSafe(menu, 0, "creator-name")) {
detachAndCreateMenu(
this, "creator-info-menu", ColumnLayout::create()->setAlignment(Alignment::Begin), name
auto menu = detachAndCreateMenu(
this,
"creator-info-menu",
ColumnLayout::create()
->setAxisReverse(true)
->setAxisAlignment(AxisAlignment::End),
name
);
menu->setPositionY(
menu->getPositionY() - 40.f / 2 +
name->getScaledContentSize().height / 2
);
menu->setContentSize({ 60.f, 40.f });
menu->updateLayout();
}
auto leftSideMenu = CCMenu::create();
leftSideMenu->setPosition(winSize / 2 + ccp(-254.f, 30.f));
leftSideMenu->setPosition(30.f, winSize.height / 2);
leftSideMenu->setLayout(ColumnLayout::create());
leftSideMenu->setID("left-side-menu");
leftSideMenu->setContentSize({ 50.f, 225.f });
this->addChild(leftSideMenu);
menu->setPosition(winSize / 2 + ccp(254.f, 0.f));
menu->setPosition(winSize.width - 30.f, winSize.height / 2);
for (auto child : CCArrayExt<CCNode>(menu->getChildren())) {
if (child->getPositionX() < 0.f) {
@ -73,6 +99,20 @@ $register_ids(LevelInfoLayer) {
setIDSafe(menu, 4, "like-button");
setIDSafe(menu, 5, "rate-button");
menu->setPosition(
menu->getPositionX() + static_cast<CCNode*>(
menu->getChildren()->firstObject()
)->getPositionX(),
winSize.height / 2
);
menu->setContentSize({ 60.f, winSize.height - 15.f });
menu->setLayout(
ColumnLayout::create()
->setGap(3.f)
->setAxisAlignment(AxisAlignment::End)
->setAxisReverse(true)
);
setIDSafe(leftSideMenu, 0, "copy-button");
menu->updateLayout();

View file

@ -162,12 +162,15 @@ $register_ids(LevelSettingsLayer) {
menu->getChildByID("2-player-toggle")
);
detachAndCreateMenu(
auto fontButtonMenu = detachAndCreateMenu(
this,
"font-button-menu",
RowLayout::create()->setAlignment(Alignment::End),
RowLayout::create()
->setAxisAlignment(AxisAlignment::End),
menu->getChildByID("font-button")
);
fontButtonMenu->setPositionY(fontButtonMenu->getPositionY() - 100.f / 2);
fontButtonMenu->setContentSize({ 50.f, 100.f });
}
}

View file

@ -2,6 +2,7 @@
#include <Geode/modify/MenuLayer.hpp>
#include <Geode/utils/cocos.hpp>
#include <Geode/ui/BasedButtonSprite.hpp>
USE_GEODE_NAMESPACE();
@ -10,6 +11,8 @@ $register_ids(MenuLayer) {
setIDSafe(this, 0, "main-menu-bg");
setIDSafe<CCSprite>(this, 0, "main-title");
auto winSize = CCDirector::get()->getWinSize();
// controller
if (PlatformToolbox::isControllerConnected()) {
setIDSafe<CCSprite>(this, 1, "play-gamepad-icon");
@ -28,24 +31,42 @@ $register_ids(MenuLayer) {
else {
setIDSafe<CCLabelBMFont>(this, 0, "player-username");
}
// main menu
if (auto menu = getChildOfType<CCMenu>(this, 0)) {
menu->setID("main-menu");
auto playBtn = setIDSafe(menu, 0, "play-button");
auto iconBtn = setIDSafe(menu, 1, "icon-kit-button");
setIDSafe(menu, 2, "editor-button");
if (auto pfp = setIDSafe(menu, 3, "profile-button")) {
auto profileMenu = detachAndCreateMenu(
this, "profile-menu",
RowLayout::create()
->setAxisAlignment(AxisAlignment::Start),
pfp
);
profileMenu->setContentSize({ 150.f, 50.f });
profileMenu->setPositionX(
profileMenu->getPositionX() + 150.f / 2 -
pfp->getScaledContentSize().height / 2
);
profileMenu->updateLayout();
}
// the buttons are added in order play, icon, editor which doesn't work
// well with setLayout that deals with children in order
menu->swapChildIndices(playBtn, iconBtn);
setIDSafe(menu, 2, "editor-button");
if (auto pfp = setIDSafe(menu, 3, "profile-button")) {
pfp->setPositionHint(PositionHint::Absolute);
}
menu->setLayout(RowLayout::create(18.f, 0.f));
menu->setContentSize({ winSize.width - 140.f, 65.f });
menu->setLayout(
RowLayout::create()
->setGap(18.f)
->setCrossAxisOverflow(true)
);
}
// bottom menu
if (auto menu = getChildOfType<CCMenu>(this, 1)) {
menu->setID("bottom-menu");
@ -57,11 +78,22 @@ $register_ids(MenuLayer) {
// move daily chest to its own menu
if (auto dailyChest = setIDSafe(menu, -1, "daily-chest-button")) {
detachAndCreateMenu(this, "right-side-menu", ColumnLayout::create(0.f, 0.f), dailyChest);
auto menu = detachAndCreateMenu(
this,
"right-side-menu",
ColumnLayout::create(),
dailyChest
);
menu->setContentSize({ 65.f, 180.f });
menu->updateLayout();
}
menu->setLayout(RowLayout::create(5.f, ach->getPositionY()));
menu->setContentSize({ winSize.width - 220.f, 65.f });
menu->setLayout(
RowLayout::create()
);
}
// social media menu
if (auto menu = getChildOfType<CCMenu>(this, 2)) {
menu->setID("social-media-menu");
@ -70,19 +102,62 @@ $register_ids(MenuLayer) {
setIDSafe(menu, 2, "twitter-button");
setIDSafe(menu, 3, "youtube-button");
}
// more games menu
if (auto menu = getChildOfType<CCMenu>(this, 3)) {
menu->setID("more-games-menu");
setIDSafe(menu, 0, "more-games-button");
auto moreGamesBtn = setIDSafe(menu, 0, "more-games-button");
// move close button to its own menu
if (auto closeBtn = setIDSafe(menu, 1, "close-button")) {
detachAndCreateMenu(
this, "close-menu", RowLayout::create(5.f, 0.f)->setAlignment(Alignment::Begin), closeBtn
auto closeMenu = detachAndCreateMenu(
this,
"close-menu",
RowLayout::create()
->setAxisAlignment(AxisAlignment::Start),
closeBtn
);
closeMenu->setContentSize({ 200.f, 50.f });
closeMenu->setPositionX(
closeMenu->getPositionX() + 200.f / 2 -
closeBtn->getScaledContentSize().width / 2
);
closeMenu->updateLayout();
}
menu->setContentSize({ 100.f, 50.f });
menu->setPositionX(
menu->getPositionX() - 100.f / 2 +
getSizeSafe(moreGamesBtn).width / 2
);
menu->setLayout(
RowLayout::create()
->setAxisAlignment(AxisAlignment::End)
->setAxisReverse(true)
);
}
// add a menu to the top right corner and middle left that are empty
// but prolly a place mods want to add stuff
auto topRightMenu = CCMenu::create();
topRightMenu->setPosition(winSize.width - 200.f / 2, winSize.height - 50.f / 2);
topRightMenu->setID("top-right-menu");
topRightMenu->setContentSize({ 200.f, 50.f });
topRightMenu->setLayout(
RowLayout::create()
->setAxisReverse(true)
->setAxisAlignment(AxisAlignment::End)
);
this->addChild(topRightMenu);
auto middleLeftMenu = CCMenu::create();
middleLeftMenu->setPosition(25.f, 215.f);
middleLeftMenu->setID("side-menu");
middleLeftMenu->setContentSize({ 50.f, 120.f });
middleLeftMenu->setLayout(ColumnLayout::create());
this->addChild(middleLeftMenu);
}
// MenuLayer::init is hooked in ../hooks/MenuLayer.cpp

View file

@ -11,6 +11,6 @@ static constexpr geode::VersionInfo LOADER_VERSION = {
@PROJECT_VERSION_MAJOR@,
@PROJECT_VERSION_MINOR@,
@PROJECT_VERSION_PATCH@,
@PROJECT_VERSION_TYPE@,
@PROJECT_VERSION_TAG_CONSTR@,
};
static constexpr const char* LOADER_MOD_JSON = R"JSON_SEPARATOR(@LOADER_MOD_JSON@)JSON_SEPARATOR";

View file

@ -97,8 +97,7 @@ Result<ModInfo> ModInfo::Impl::createFromSchemaV010(ModJson const& rawJson) {
root.has("unloadable").into(impl->m_supportsUnloading);
root.has("early-load").into(impl->m_needsEarlyLoad);
if (root.has("api")) {
// TODO: figure out what got wiped with merge
// impl->isAPI = true;
impl->m_isAPI = true;
}
for (auto& dep : root.has("dependencies").iterate()) {
@ -194,11 +193,9 @@ Result<ModInfo> ModInfo::Impl::create(ModJson const& json) {
return Err(
"[mod.json] targets a version (" + schema.toString() +
") that isn't "
"supported by this version (v" +
") that isn't supported by this version (v" +
LOADER_VERSION_STR +
") of geode. "
"This is probably a bug; report it to "
") of geode. This is probably a bug; report it to "
"the Geode Development Team."
);
}

View file

@ -79,9 +79,7 @@ void ModListCell::setupInfo(
this->addChild(versionLabel);
if (auto tag = info.version().getTag()) {
auto tagLabel = TagNode::create(
versionTagToString(tag.value()).c_str()
);
auto tagLabel = TagNode::create(tag.value().toString().c_str());
tagLabel->setAnchorPoint({ .0f, .5f });
tagLabel->setScale(.3f);
tagLabel->setPosition(

View file

@ -10,31 +10,57 @@ USE_GEODE_NAMESPACE();
// VersionTag
std::optional<VersionTag> geode::versionTagFromString(std::string const& str) {
switch (hash(str.c_str())) {
case hash("alpha"): return VersionTag::Alpha;
case hash("beta"): return VersionTag::Beta;
case hash("prerelease"): return VersionTag::Prerelease;
default: return std::nullopt;
Result<VersionTag> VersionTag::parse(std::stringstream& str) {
std::string iden;
while ('a' <= str.peek() && str.peek() <= 'z') {
iden += str.get();
}
if (str.fail()) {
return Err("Unable to parse tag");
}
VersionTag tag = VersionTag::Alpha;
switch (hash(iden.c_str())) {
case hash("alpha"): tag = VersionTag::Alpha; break;
case hash("beta"): tag = VersionTag::Beta; break;
case hash("prerelease"): case hash("pr"): tag = VersionTag::Prerelease; break;
default: return Err("Invalid tag \"" + iden + "\"");
}
if (str.peek() == '.') {
str.get();
size_t num;
str >> num;
if (str.fail()) {
return Err("Unable to parse tag number");
}
tag.number = num;
}
return Ok(tag);
}
std::string geode::versionTagToSuffixString(VersionTag tag) {
switch (tag) {
case VersionTag::Alpha: return "-alpha";
case VersionTag::Beta: return "-beta";
case VersionTag::Prerelease: return "-prerelease";
std::string VersionTag::toSuffixString() const {
std::string res = "";
switch (value) {
case Alpha: res += "-alpha"; break;
case Beta: res += "-beta"; break;
case Prerelease: res += "-prerelease"; break;
}
return "";
if (number) {
res += "." + std::to_string(number.value());
}
return res;
}
std::string geode::versionTagToString(VersionTag tag) {
switch (tag) {
case VersionTag::Alpha: return "Alpha";
case VersionTag::Beta: return "Beta";
case VersionTag::Prerelease: return "Prerelease";
std::string VersionTag::toString() const {
std::string res = "";
switch (value) {
case Alpha: res += "Alpha"; break;
case Beta: res += "Beta"; break;
case Prerelease: res += "Prerelease"; break;
}
return "";
if (number) {
res += " " + std::to_string(number.value());
}
return res;
}
// VersionInfo
@ -77,18 +103,13 @@ Result<VersionInfo> VersionInfo::parse(std::string const& string) {
std::optional<VersionTag> tag;
if (str.peek() == '-') {
str.get();
std::string iden;
str >> iden;
if (str.fail()) {
return Err("Unable to parse tag");
}
if (auto t = versionTagFromString(iden)) {
tag = t;
}
else {
return Err("Invalid tag \"" + iden + "\"");
}
GEODE_UNWRAP_INTO(tag, VersionTag::parse(str));
}
if (!str.eof()) {
return Err("Expected end of version, found '" + std::string(1, str.get()) + "'");
}
return Ok(VersionInfo(major, minor, patch, tag));
}
@ -97,7 +118,7 @@ std::string VersionInfo::toString(bool includeTag) const {
return fmt::format(
"v{}.{}.{}{}",
m_major, m_minor, m_patch,
versionTagToSuffixString(m_tag.value())
m_tag.value().toSuffixString()
);
}
return fmt::format("v{}.{}.{}", m_major, m_minor, m_patch);