Stand/Stand/CommandList.hpp
2024-10-16 11:20:42 +08:00

287 lines
11 KiB
C++

#pragma once
#include "CommandPhysical.hpp"
#include <memory>
#include <functional>
#include <soup/WeakRef.hpp>
#include "CommandBuilder.hpp"
namespace Stand
{
enum ListIndicatorType : uint8_t
{
LISTINDICATOR_ARROW = 0,
LISTINDICATOR_ARROW_IF_CHILDREN,
LISTINDICATOR_OFF,
LISTINDICATOR_ON,
_LISTINDICATOR_SIZE
};
static_assert(LISTINDICATOR_ON == 0b11); // indicator_type uses 2 bits
#pragma pack(push, 1)
class CommandList : public CommandPhysical
{
public:
static constexpr cursor_t max_web_commands = 360;
std::vector<std::unique_ptr<Command>> children{};
cursor_t m_cursor = 0;
cursor_t m_offset = 0;
soup::WeakRef<Command> parent_for_back{};
size_t dividers = 0;
bool web_state_update_queued : 1 = false;
ListIndicatorType indicator_type : 2 = LISTINDICATOR_ARROW;
explicit CommandList(CommandList* const parent, Label&& menu_name, std::vector<CommandName>&& command_names = {}, Label&& help_text = NOLABEL, const commandflags_t flags = CMDFLAGS_LIST, const CommandType type = COMMAND_LIST);
template <class T, typename... Args>
[[nodiscard]] std::unique_ptr<T> makeChild(Args&&...args)
{
return std::make_unique<T>(this, std::forward<Args>(args)...);
}
template <class T, typename... Args>
[[nodiscard]] std::unique_ptr<T> makeChild(Label&& menu_name, std::vector<CommandName>&& command_names, Args&&...args)
{
return std::make_unique<T>(this, std::move(menu_name), std::move(command_names), std::forward<Args>(args)...);
}
template <class T, typename Arg1, typename... Args>
[[nodiscard]] std::unique_ptr<T> makeChild(Arg1&& arg1, Label&& menu_name, std::vector<CommandName>&& command_names, Args&&...args)
{
return std::make_unique<T>(this, std::move(arg1), std::move(menu_name), std::move(command_names), std::forward<Args>(args)...);
}
template <class T, typename... Args>
T* createChild(Args&&...args)
{
return (T*)this->children.emplace_back(std::make_unique<T>(this, std::forward<Args>(args)...)).get();
}
template <class T, typename... Args>
T* createChild(std::vector<CommandName>&& command_names, Args&&...args)
{
return (T*)this->children.emplace_back(std::make_unique<T>(this, std::move(command_names), std::forward<Args>(args)...)).get();
}
template <class T, typename... Args>
T* createChild(Label&& menu_name, std::vector<CommandName>&& command_names, Args&&...args)
{
return (T*)this->children.emplace_back(std::make_unique<T>(this, std::move(menu_name), std::move(command_names), std::forward<Args>(args)...)).get();
}
template <class T, typename Arg1, typename... Args>
T* createChild(Arg1&& arg1, Label&& menu_name, std::vector<CommandName>&& command_names, Args&&...args)
{
return (T*)this->children.emplace_back(std::make_unique<T>(this, std::move(arg1), std::move(menu_name), std::move(command_names), std::forward<Args>(args)...)).get();
}
template <class T, typename... Args>
CommandBuilder<T> createChildWithBuilder(Args&&...args)
{
return this->createChild<T>(std::forward<Args>(args)...);
}
[[nodiscard]] bool isRoot() const noexcept;
void preDelete() override;
void preDetach() override;
void dispatchPreDeleteForAllChildren() const;
void resetChildrenWithPreDelete();
void resetChildren() noexcept;
void processChildrenUpdate();
void processChildrenVisualUpdate();
void processChildrenUpdateWithPossibleCursorDisplacement(Command* prev_focus, ThreadContext tc);
void emplaceVisible(std::unique_ptr<Command>&& cmd);
void removeChild(std::vector<std::unique_ptr<Command>>::iterator it);
[[nodiscard]] bool canDispatchOnTickInViewportForChildren() const noexcept;
[[nodiscard]] bool canDispatchOnTickInWebViewportForChildren() const noexcept;
void onClick(Click& click) override;
protected:
static void onClickImplUnavailable(Click& click);
public:
void open(ThreadContext thread_context, bool unintrusive = false, bool without_grid_update = false, bool can_update_root_cursor = true);
void redirectOpen(Click& click, const CommandPhysical* cmd);
void redirectOpen(ThreadContext tc, const CommandPhysical* cmd);
void redirectOpen(const CommandPhysical* cmd);
[[nodiscard]] CommandList* getParentForBack(CommandList* tab);
private:
[[nodiscard]] CommandList* getParentForBackIfApplicable(const CommandList* const tab) const;
public:
[[nodiscard]] CommandList* getParentForTab(const CommandList* const tab) const;
void setIndicatorType(ListIndicatorType t);
void fixCursorAndOffset(bool no_padding = false);
bool fixCursor();
void fixOffset(bool no_padding = false);
void processFocusUpdate(ThreadContext thread_context, Direction momentum, bool user_action = false);
virtual void onActiveListUpdate();
void resetCursor() noexcept;
bool resetCursorIfApplicable() noexcept;
virtual void onBack(ThreadContext thread_context);
[[nodiscard]] bool isCurrentUiList() const;
[[nodiscard]] bool isCurrentMenuList() const;
[[nodiscard]] bool isCurrentWebList() const;
[[nodiscard]] bool isCurrentUiOrWebList() const;
[[nodiscard]] bool isCurrentMenuOrWebList() const;
[[nodiscard]] bool isCurrentMenuListInTab() const;
[[nodiscard]] bool isThisOrSublist(const CommandList* list) const;
[[nodiscard]] bool isThisOrSublistInTab(const CommandList* list) const;
[[nodiscard]] bool isThisOrSublistCurrentUiList() const;
[[nodiscard]] bool isThisOrSublistCurrentMenuList() const;
[[nodiscard]] bool isThisOrSublistCurrentWebList() const;
[[nodiscard]] bool isThisOrSublistCurrentUiOrWebList() const;
[[nodiscard]] bool isThisOrSublistCurrentMenuOrWebList() const;
[[nodiscard]] bool isThisOrSublistCurrentUiListInTab() const;
[[nodiscard]] bool isThisOrSublistActiveInMyTabMenu() const;
[[nodiscard]] bool isThisOrSublistCurrentMenuListInTab() const;
void goBackIfActive(ThreadContext thread_context);
using CommandPhysical::isInViewport;
[[nodiscard]] bool isInViewport(const CommandPhysical* const physical) const;
[[nodiscard]] bool isInViewportRootReadlocked(const CommandPhysical* const physical) const;
[[nodiscard]] bool canUpdateCursor() const;
void approachCursor(cursor_t desired);
void evacuateCursor();
void evacuateCursorTab(uint8_t tab_idx);
void evacuateCursorWeb();
void evacuateCursorStandalone();
void evacuateCursorStandaloneNocheck();
void updateCursorGoingBack(CommandList* prev_active_list);
void updateWebState();
private:
void updateWebStateNoCheck();
public:
void updateWebStateImpl() const;
[[nodiscard]] Command* getFocus() const;
[[nodiscard]] CommandPhysical* getFocusPhysical() const;
[[nodiscard]] Command* getChild(CommandType type) const;
[[nodiscard]] CommandPhysical* resolveChildByMenuName(const Label& target) const;
[[nodiscard]] CommandPhysical* resolveChildByMenuNameHash(hash_t target) const;
[[nodiscard]] CommandPhysical* resolveChildByMenuNameLiteral(const std::string& target) const;
[[nodiscard]] CommandPhysical* resolveChildByMenuNameWebString(const std::string& target) const;
[[nodiscard]] CommandPhysical* recursivelyResolveChildByMenuName(const Label& target) const;
[[nodiscard]] CommandPhysical* recursivelyResolveChildByMenuNameHash(hash_t target) const;
[[nodiscard]] CommandPhysical* resolveCommandConfig(const std::string& target, const std::string& prefix = {}) const;
[[nodiscard]] CommandPhysical* resolveCommandEnglish(const std::string& target, const std::string& prefix = {}) const;
[[nodiscard]] CommandPhysical* resolveCommandWeb(const std::string& target, const std::string& prefix = {}) const;
enum CommandNameMatchType : int8_t
{
NMT_OVERSHOT = -1,
NMT_MISSED = 0,
NMT_GRAZED,
NMT_HIT,
};
[[nodiscard]] static int8_t checkCommandNameMatch(const CommandName& command_name_prefix, const CommandName& command_name);
template <class T = CommandIssuable>
bool findCommandsWhereCommandNameStartsWith(std::vector<T*>& results, const CommandName& command_name_prefix, const CommandPerm perm = COMMANDPERM_ALL, const size_t results_limit = 2000) const
{
for (auto& _command : this->children)
{
if (!_command->isT<T>())
{
continue;
}
auto* command = reinterpret_cast<T*>(_command.get());
bool recurse_into = !command->isListAction();
bool grazed = false;
for (const auto& command_name : command->command_names)
{
auto nmt = checkCommandNameMatch(command_name_prefix, command_name);
if (nmt != NMT_MISSED)
{
if (nmt == NMT_HIT)
{
results = { command };
return true;
}
else if (nmt == NMT_GRAZED)
{
if (!grazed) // Don't want the same command multiple times in results (for command box completions, e.g. when entering "quit")
{
grazed = true;
if (results.size() != results_limit) // If we hit results limit, keep searching for possible hit, but don't record any more partials. (for `menu.focus(menu.ref_by_command_name("time"))`)
{
results.emplace_back(command);
}
}
}
else if (nmt == NMT_OVERSHOT)
{
recurse_into = true;
}
}
}
if (command->isList()
&& recurse_into
&& command->as<CommandList>()->hasCommandsWhereCommandNameStartsWith(command_name_prefix)
&& command->as<CommandList>()->findCommandsWhereCommandNameStartsWith<T>(results, command_name_prefix, perm, results_limit)
)
{
return true;
}
}
return false;
}
[[nodiscard]] virtual bool hasCommandsWhereCommandNameStartsWith(const CommandName& command_name_prefix) const noexcept;
void recursivelyApplyDefaultState() const;
void recursivelySaveStateInMemory(std::unordered_map<std::string, std::string>& state, const std::string& prefix = {}) const;
[[nodiscard]] size_t countChildren(bool include_invisible, bool include_non_resolvable) const noexcept;
[[nodiscard]] size_t countVisibleChildren() const noexcept;
[[nodiscard]] std::wstring getCursorText() const;
[[nodiscard]] cursor_t getCursorIgnoreUnresolvable() const;
[[nodiscard]] size_t countResolvableChildren() const noexcept;
void forEachChild(std::function<void(Command*)>&& func) const;
void forEachPhysicalChild(const std::function<void(CommandPhysical*)>& func) const;
void forEachChildAsPhysical(std::function<bool(CommandPhysical*)>&& consumer) const;
void recursiveForEachNonListChild(const std::function<void(Command*)>& func) const;
void recursiveForEachNonListChildRootReadLocked(const std::function<void(Command*)>& func) const;
void recursiveForEachChild(const std::function<bool(const std::unique_ptr<Command>&)>& consumer) const;
private:
void recursiveForEachChildImpl(const std::function<bool(const std::unique_ptr<Command>&)>& consumer) const;
public:
void setCursorPos(const Command* child) const;
using sort_algo_t = bool(*)(const std::unique_ptr<Command>&, const std::unique_ptr<Command>&);
static bool sortAlgoName(const std::unique_ptr<Command>& a, const std::unique_ptr<Command>& b);
void sortChildren(sort_algo_t sort_algo = &sortAlgoName);
};
#pragma pack(pop)
}