Merge submodule contents for specter/master

This commit is contained in:
Luke Street 2021-04-06 13:10:24 -04:00
commit 7469f6aed7
69 changed files with 13226 additions and 0 deletions

8
.gitmodules vendored
View File

@ -58,3 +58,11 @@
path = hecl/extern/libjpeg-turbo
url = ../libjpeg-turbo.git
branch = thp
[submodule "freetype2"]
path = specter/freetype2
url = ../freetype2
branch = master
[submodule "zeus"]
path = specter/zeus
url = ../zeus.git
branch = master

1
specter/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
docs

101
specter/CMakeLists.txt Normal file
View File

@ -0,0 +1,101 @@
add_subdirectory(freetype2)
if (NOT MSVC)
target_compile_options(freetype PRIVATE -Wno-implicit-fallthrough)
endif()
add_subdirectory(zeus)
set(ZEUS_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/zeus/include)
add_subdirectory(resources/fonts)
add_library(specter
lib/Button.cpp
lib/Control.cpp
lib/FileBrowser.cpp
lib/FontCache.cpp
lib/Icon.cpp
lib/Menu.cpp
lib/MessageWindow.cpp
lib/ModalWindow.cpp
lib/MultiLineTextView.cpp
lib/PathButtons.cpp
lib/RootView.cpp
lib/ScrollView.cpp
lib/Space.cpp
lib/SplitView.cpp
lib/Table.cpp
lib/TextField.cpp
lib/TextView.cpp
lib/Toolbar.cpp
lib/Tooltip.cpp
lib/View.cpp
lib/ViewResources.cpp
include/specter/Button.hpp
include/specter/Control.hpp
include/specter/FileBrowser.hpp
include/specter/FontCache.hpp
include/specter/IMenuNode.hpp
include/specter/IViewManager.hpp
include/specter/Icon.hpp
include/specter/Menu.hpp
include/specter/MessageWindow.hpp
include/specter/ModalWindow.hpp
include/specter/MultiLineTextView.hpp
include/specter/Node.hpp
include/specter/NodeSocket.hpp
include/specter/NumericField.hpp
include/specter/Outliner.hpp
include/specter/Panel.hpp
include/specter/PathButtons.hpp
include/specter/RootView.hpp
include/specter/ScrollView.hpp
include/specter/Space.hpp
include/specter/SplitView.hpp
include/specter/Table.hpp
include/specter/TextField.hpp
include/specter/TextView.hpp
include/specter/Toolbar.hpp
include/specter/Tooltip.hpp
include/specter/View.hpp
include/specter/ViewResources.hpp
include/specter/genie.hpp
include/specter/specter.hpp
)
if (MSVC)
target_compile_options(specter PRIVATE
# Enforce various standards compliant behavior.
$<$<COMPILE_LANGUAGE:CXX>:/permissive->
# Enable standard volatile semantics.
$<$<COMPILE_LANGUAGE:CXX>:/volatile:iso>
# Reports the proper value for the __cplusplus preprocessor macro.
$<$<COMPILE_LANGUAGE:CXX>:/Zc:__cplusplus>
# Use latest C++ standard.
$<$<COMPILE_LANGUAGE:CXX>:/std:c++latest>
)
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
# Flags for MSVC (not clang-cl)
target_compile_options(specter PRIVATE
# Allow constexpr variables to have explicit external linkage.
$<$<COMPILE_LANGUAGE:CXX>:/Zc:externConstexpr>
# Assume that new throws exceptions, allowing better code generation.
$<$<COMPILE_LANGUAGE:CXX>:/Zc:throwingNew>
)
endif()
endif()
target_link_libraries(specter PUBLIC
freetype
hecl-full
specter-fonts
zeus
UrdeLocales
)
target_include_directories(specter PUBLIC include freetype2/include)
target_atdna(specter atdna_FontCache.cpp include/specter/FontCache.hpp)

22
specter/LICENSE Normal file
View File

@ -0,0 +1,22 @@
The MIT License
Copyright (c) 2015-2016 Specter Contributors
Original Authors: Jack Andersen and Phillip "Antidote" Stephens
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

2410
specter/doxygen.cfg Normal file

File diff suppressed because it is too large Load Diff

1
specter/freetype2 Submodule

@ -0,0 +1 @@
Subproject commit 6f049e5c977aaae9d206c33cd26fd847eca34ea4

View File

@ -0,0 +1,93 @@
#pragma once
#include <memory>
#include <string>
#include "specter/Control.hpp"
#include "specter/View.hpp"
#include <boo/IWindow.hpp>
#include <zeus/CColor.hpp>
namespace specter {
class IconView;
class TextView;
struct Icon;
class Button : public Control {
struct ButtonTarget;
struct MenuTarget;
public:
enum class Style {
Block,
Text,
};
private:
Style m_style;
IButtonBinding::MenuStyle m_menuStyle = IButtonBinding::MenuStyle::None;
zeus::CColor m_textColor;
zeus::CColor m_bgColor;
std::string m_textStr;
std::unique_ptr<TextView> m_text;
std::unique_ptr<IconView> m_icon;
SolidShaderVert m_verts[40];
VertexBufferBindingSolid m_vertsBinding;
void _loadVerts() { m_vertsBinding.load<decltype(m_verts)>(m_verts); }
RectangleConstraint m_constraint;
int m_nomWidth;
int m_nomHeight;
int m_textWidth;
int m_textIconWidth;
ViewChild<std::unique_ptr<ButtonTarget>> m_buttonTarget;
ViewChild<std::unique_ptr<MenuTarget>> m_menuTarget;
ViewChild<std::unique_ptr<View>> m_modalMenu;
public:
class Resources {
friend class Button;
friend class ViewResources;
void init(boo::IGraphicsDataFactory::Context& ctx, const IThemeData& theme);
void destroy() {}
};
Button(ViewResources& res, View& parentView, IButtonBinding* controlBinding, std::string_view text,
Icon* icon = nullptr, Style style = Style::Block, const zeus::CColor& bgColor = zeus::skWhite,
RectangleConstraint constraint = RectangleConstraint());
Button(ViewResources& res, View& parentView, IButtonBinding* controlBinding, std::string_view text,
const zeus::CColor& textColor, Icon* icon = nullptr, Style style = Style::Block,
const zeus::CColor& bgColor = zeus::skWhite, RectangleConstraint constraint = RectangleConstraint());
~Button() override;
void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseMove(const boo::SWindowCoord&) override;
void mouseLeave(const boo::SWindowCoord&) override;
void think() override;
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
void setText(std::string_view text, const zeus::CColor& textColor);
void setText(std::string_view text);
void setIcon(Icon* icon = nullptr);
std::string_view getText() const { return m_textStr; }
void colorGlyphs(const zeus::CColor& newColor);
int nominalWidth() const override { return m_nomWidth; }
int nominalHeight() const override { return m_nomHeight; }
void closeMenu(const boo::SWindowCoord& coord);
ViewChild<std::unique_ptr<View>>& getMenu() { return m_modalMenu; }
void setMultiplyColor(const zeus::CColor& color) override;
};
} // namespace specter

View File

@ -0,0 +1,120 @@
#pragma once
#include <cfloat>
#include <climits>
#include <memory>
#include <mutex>
#include <string>
#include <utility>
#include "specter/View.hpp"
#include <boo/IWindow.hpp>
namespace hecl {
class CVar;
}
namespace specter {
class Control;
class Button;
enum class ControlType { Button, Float, Int, String, CVar };
struct IControlBinding {
virtual ControlType type() const = 0;
virtual std::string_view name(const Control* control) const = 0;
virtual std::string_view help(const Control* control) const { return {}; }
};
struct IButtonBinding : IControlBinding {
ControlType type() const override { return ControlType::Button; }
static IButtonBinding* castTo(IControlBinding* bind) {
return bind->type() == ControlType::Button ? static_cast<IButtonBinding*>(bind) : nullptr;
}
/** Pressed/Released while Hovering action,
* cancellable by holding the button and releasing outside */
virtual void activated(const Button* button, const boo::SWindowCoord& coord) {}
/** Pass-through down action */
virtual void down(const Button* button, const boo::SWindowCoord& coord) {}
/** Pass-through up action */
virtual void up(const Button* button, const boo::SWindowCoord& coord) {}
/** Optional style of menu to bind to button */
enum class MenuStyle {
None, /**< No menu; normal button */
Primary, /**< Menu button replaces normal button */
Auxiliary /**< Menu button placed alongside normal button */
};
/** Informs button which MenuStyle to present to user */
virtual MenuStyle menuStyle(const specter::Button* button) const { return MenuStyle::None; }
/** Called when user requests menu, Button assumes modal ownership */
virtual std::unique_ptr<View> buildMenu(const specter::Button* button) { return nullptr; }
};
struct IFloatBinding : IControlBinding {
ControlType type() const override { return ControlType::Float; }
static IFloatBinding* castTo(IControlBinding* bind) {
return bind->type() == ControlType::Float ? static_cast<IFloatBinding*>(bind) : nullptr;
}
virtual float getDefault(const Control* control) const { return 0.0; }
virtual std::pair<float, float> getBounds(const Control* control) const { return std::make_pair(FLT_MIN, FLT_MAX); }
virtual void changed(const Control* control, float val) = 0;
};
struct IIntBinding : IControlBinding {
ControlType type() const override { return ControlType::Int; }
static IIntBinding* castTo(IControlBinding* bind) {
return bind->type() == ControlType::Int ? static_cast<IIntBinding*>(bind) : nullptr;
}
virtual int getDefault(const Control* control) const { return 0; }
virtual std::pair<int, int> getBounds(const Control* control) const { return std::make_pair(INT_MIN, INT_MAX); }
virtual void changed(const Control* control, int val) = 0;
};
struct IStringBinding : IControlBinding {
ControlType type() const override { return ControlType::String; }
static IStringBinding* castTo(IControlBinding* bind) {
return bind->type() == ControlType::String ? static_cast<IStringBinding*>(bind) : nullptr;
}
virtual std::string getDefault(const Control* control) const { return ""; }
virtual void changed(const Control* control, std::string_view val) = 0;
};
struct CVarControlBinding : IControlBinding {
hecl::CVar* m_cvar;
CVarControlBinding(hecl::CVar* cvar) : m_cvar(cvar) {}
ControlType type() const override { return ControlType::CVar; }
static CVarControlBinding* castTo(IControlBinding* bind) {
return bind->type() == ControlType::CVar ? static_cast<CVarControlBinding*>(bind) : nullptr;
}
std::string_view name(const Control* control) const override;
std::string_view help(const Control* control) const override;
};
class Control : public View {
protected:
IControlBinding* m_controlBinding = nullptr;
public:
Control(ViewResources& res, View& parentView, IControlBinding* controlBinding);
};
class ITextInputView : public Control, public boo::ITextInputCallback {
protected:
static std::recursive_mutex m_textInputLk;
ITextInputView(ViewResources& res, View& parentView, IControlBinding* controlBinding)
: Control(res, parentView, controlBinding) {}
public:
virtual void clipboardCopy() {}
virtual void clipboardCut() {}
virtual void clipboardPaste() {}
};
} // namespace specter

View File

@ -0,0 +1,261 @@
#pragma once
#include <cstddef>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include "specter/ModalWindow.hpp"
#include "specter/PathButtons.hpp"
#include "specter/Table.hpp"
#include "specter/View.hpp"
#include "specter/ViewResources.hpp"
#include <hecl/SystemChar.hpp>
namespace hecl {
class DirectoryEnumerator;
}
namespace specter {
class MessageWindow;
class TextField;
struct IViewManager;
class FileBrowser : public ModalWindow, public IPathButtonsBinding {
public:
enum class Type { SaveFile, SaveDirectory, OpenFile, OpenDirectory, NewHECLProject, OpenHECLProject };
private:
Type m_type;
hecl::SystemString m_path;
std::vector<hecl::SystemString> m_comps;
bool m_showingHidden = false;
class LeftSide : public View {
friend class FileBrowser;
FileBrowser& m_fb;
LeftSide(FileBrowser& fb, ViewResources& res) : View(res, fb), m_fb(fb) {}
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
} m_left;
class RightSide : public View {
friend class FileBrowser;
FileBrowser& m_fb;
RightSide(FileBrowser& fb, ViewResources& res) : View(res, fb), m_fb(fb) {}
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
} m_right;
ViewChild<std::unique_ptr<SplitView>> m_split;
void okActivated(bool viaButton);
struct OKButton : IButtonBinding {
FileBrowser& m_fb;
ViewChild<std::unique_ptr<Button>> m_button;
std::string m_text;
OKButton(FileBrowser& fb, ViewResources& res, std::string_view text);
std::string_view name(const Control* control) const override { return m_text; }
void activated(const Button* button, const boo::SWindowCoord&) override { m_fb.okActivated(true); }
} m_ok;
void cancelActivated();
struct CancelButton : IButtonBinding {
FileBrowser& m_fb;
ViewChild<std::unique_ptr<Button>> m_button;
std::string m_text;
CancelButton(FileBrowser& fb, ViewResources& res, std::string_view text);
std::string_view name(const Control* control) const override { return m_text; }
void activated(const Button* button, const boo::SWindowCoord&) override { m_fb.cancelActivated(); }
} m_cancel;
void pathButtonActivated(size_t idx) override;
ViewChild<std::unique_ptr<PathButtons>> m_pathButtons;
ViewChild<std::unique_ptr<TextField>> m_fileField;
struct FileFieldBind : IStringBinding {
FileBrowser& m_browser;
std::string m_name;
FileFieldBind(FileBrowser& browser, const IViewManager& vm);
std::string_view name(const Control* control) const override { return m_name; }
void changed(const Control* control, std::string_view val) override {}
} m_fileFieldBind;
std::unique_ptr<MessageWindow> m_confirmWindow;
struct FileListingDataBind : ITableDataBinding, ITableStateBinding {
FileBrowser& m_fb;
struct Entry {
hecl::SystemString m_path;
std::string m_name;
std::string m_type;
std::string m_size;
};
std::vector<Entry> m_entries;
std::string m_nameCol;
std::string m_typeCol;
std::string m_sizeCol;
std::string m_dirStr;
std::string m_projStr;
std::string m_fileStr;
size_t columnCount() const override { return 3; }
size_t rowCount() const override { return m_entries.size(); }
std::string_view header(size_t cIdx) const override {
switch (cIdx) {
case 0:
return m_nameCol;
case 1:
return m_typeCol;
case 2:
return m_sizeCol;
default:
break;
}
return {};
}
std::string_view cell(size_t cIdx, size_t rIdx) const override {
switch (cIdx) {
case 0:
return m_entries.at(rIdx).m_name;
case 1:
return m_entries.at(rIdx).m_type;
case 2:
return m_entries.at(rIdx).m_size;
default:
break;
}
return {};
}
float m_columnSplits[3] = {0.0f, 0.7f, 0.9f};
bool columnSplitResizeAllowed() const override { return true; }
float getColumnSplit(size_t cIdx) const override { return m_columnSplits[cIdx]; }
void setColumnSplit(size_t cIdx, float split) override { m_columnSplits[cIdx] = split; }
void updateListing(const hecl::DirectoryEnumerator& dEnum);
bool m_sizeSort = false;
SortDirection m_sortDir = SortDirection::Ascending;
bool m_needsUpdate = false;
SortDirection getSort(size_t& cIdx) const override {
cIdx = m_sizeSort ? 2 : 0;
return m_sortDir;
}
void setSort(size_t cIdx, SortDirection dir) override {
if (cIdx == 1)
return;
m_sizeSort = cIdx == 2;
m_sortDir = dir;
m_needsUpdate = true;
}
void setSelectedRow(size_t rIdx) override;
void rowActivated(size_t rIdx) override { m_fb.okActivated(false); }
FileListingDataBind(FileBrowser& fb, const IViewManager& vm);
} m_fileListingBind;
ViewChild<std::unique_ptr<Table>> m_fileListing;
struct BookmarkDataBind : ITableDataBinding, ITableStateBinding {
FileBrowser& m_fb;
BookmarkDataBind(FileBrowser& fb) : m_fb(fb) {}
struct Entry {
hecl::SystemString m_path;
std::string m_name;
Entry(std::pair<hecl::SystemString, std::string>&& path)
: m_path(std::move(path.first)), m_name(std::move(path.second)) {}
Entry(hecl::SystemStringView path) : m_path(path) {
hecl::SystemUTF8Conv utf8(path);
if (utf8.str().size() == 1 && utf8.str()[0] == '/') {
m_name = "/";
return;
}
size_t lastSlash = utf8.str().rfind('/');
if (lastSlash != std::string::npos)
m_name.assign(utf8.str().cbegin() + lastSlash + 1, utf8.str().cend());
else
m_name = utf8.str();
}
};
std::vector<Entry> m_entries;
size_t columnCount() const override { return 1; }
size_t rowCount() const override { return m_entries.size(); }
std::string_view cell(size_t, size_t rIdx) const override { return m_entries.at(rIdx).m_name; }
void setSelectedRow(size_t rIdx) override {
if (rIdx != SIZE_MAX)
m_fb.navigateToPath(m_entries.at(rIdx).m_path);
}
void rowActivated(size_t rIdx) override { m_fb.okActivated(true); }
};
BookmarkDataBind m_systemBookmarkBind;
std::unique_ptr<TextView> m_systemBookmarksLabel;
ViewChild<std::unique_ptr<Table>> m_systemBookmarks;
BookmarkDataBind m_projectBookmarkBind;
std::unique_ptr<TextView> m_projectBookmarksLabel;
ViewChild<std::unique_ptr<Table>> m_projectBookmarks;
BookmarkDataBind m_recentBookmarkBind;
std::unique_ptr<TextView> m_recentBookmarksLabel;
ViewChild<std::unique_ptr<Table>> m_recentBookmarks;
std::function<void(bool, hecl::SystemStringView)> m_returnFunc;
public:
FileBrowser(ViewResources& res, View& parentView, std::string_view title, Type type,
std::function<void(bool, hecl::SystemStringView)> returnFunc);
FileBrowser(ViewResources& res, View& parentView, std::string_view title, Type type,
hecl::SystemStringView initialPath, std::function<void(bool, hecl::SystemStringView)> returnFunc);
~FileBrowser() override;
static std::vector<hecl::SystemString> PathComponents(hecl::SystemStringView path);
static void SyncBookmarkSelections(Table& table, BookmarkDataBind& binding, const hecl::SystemString& sel);
void navigateToPath(hecl::SystemStringView path);
bool showingHidden() const { return m_showingHidden; }
void setShowingHidden(bool showingHidden) {
m_showingHidden = showingHidden;
navigateToPath(m_path);
}
void updateContentOpacity(float opacity) override;
void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseMove(const boo::SWindowCoord&) override;
void mouseLeave(const boo::SWindowCoord&) override;
void scroll(const boo::SWindowCoord&, const boo::SScrollDelta&) override;
void touchDown(const boo::STouchCoord&, uintptr_t) override;
void touchUp(const boo::STouchCoord&, uintptr_t) override;
void touchMove(const boo::STouchCoord&, uintptr_t) override;
void charKeyDown(unsigned long, boo::EModifierKey, bool) override;
void specialKeyDown(boo::ESpecialKey, boo::EModifierKey, bool) override;
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) override;
void think() override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
};
} // namespace specter

View File

@ -0,0 +1,212 @@
#pragma once
#include <ft2build.h>
#include FT_FREETYPE_H
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <athena/FileReader.hpp>
#include <athena/FileWriter.hpp>
#include <athena/DNA.hpp>
#include <boo/BooObject.hpp>
#include <boo/graphicsdev/IGraphicsDataFactory.hpp>
#include <hecl/SystemChar.hpp>
namespace hecl::Runtime {
class FileStoreManager;
}
namespace specter {
class FontTag {
friend class FontCache;
uint64_t m_hash = 0;
FontTag(std::string_view name, bool subpixel, float points, unsigned dpi);
public:
FontTag() = default;
operator bool() const { return m_hash != 0; }
uint64_t hash() const { return m_hash; }
bool operator==(const FontTag& other) const { return m_hash == other.m_hash; }
};
} // namespace specter
namespace std {
template <>
struct hash<specter::FontTag> {
size_t operator()(const specter::FontTag& handle) const noexcept { return size_t(handle.hash()); }
};
} // namespace std
namespace specter {
class FreeTypeGZipMemFace {
FT_Library m_lib;
FT_StreamRec m_comp = {};
FT_StreamRec m_decomp = {};
FT_Face m_face = nullptr;
public:
FreeTypeGZipMemFace(FT_Library lib, const uint8_t* data, size_t sz);
FreeTypeGZipMemFace(const FreeTypeGZipMemFace& other) = delete;
FreeTypeGZipMemFace& operator=(const FreeTypeGZipMemFace& other) = delete;
~FreeTypeGZipMemFace() { close(); }
void open();
void close();
operator FT_Face() {
open();
return m_face;
}
};
using FCharFilter = std::pair<std::string_view, bool (*)(uint32_t)>;
class FontAtlas {
FT_Face m_face;
std::vector<uint8_t> m_texmap;
boo::ObjToken<boo::ITextureSA> m_tex;
uint32_t m_dpi;
FT_Fixed m_ftXscale;
FT_UShort m_ftXPpem;
FT_Pos m_lineHeight;
unsigned m_finalHeight;
unsigned m_fullTexmapLayers;
bool m_subpixel;
bool m_ready = false;
public:
struct Glyph {
atUint32 m_unicodePoint;
atUint32 m_glyphIdx;
atUint32 m_layerIdx;
float m_layerFloat;
float m_uv[4];
atInt32 m_leftPadding;
atInt32 m_advance;
atInt32 m_width;
atInt32 m_height;
atInt32 m_verticalOffset;
};
private:
std::vector<Glyph> m_glyphs;
std::unordered_map<atUint16, std::vector<std::pair<atUint16, atInt16>>> m_kernAdjs;
struct TT_KernHead : athena::io::DNA<athena::Endian::Big> {
AT_DECL_DNA
Value<atUint32> length;
Value<atUint16> coverage;
};
struct TT_KernSubHead : athena::io::DNA<athena::Endian::Big> {
AT_DECL_DNA
Value<atUint16> nPairs;
Value<atUint16> searchRange;
Value<atUint16> entrySelector;
Value<atUint16> rangeShift;
};
struct TT_KernPair : athena::io::DNA<athena::Endian::Big> {
AT_DECL_DNA
Value<atUint16> left;
Value<atUint16> right;
Value<atInt16> value;
};
void buildKernTable(FT_Face face);
std::unordered_map<atUint32, size_t> m_glyphLookup;
public:
FontAtlas(FT_Face face, uint32_t dpi, bool subpixel, FCharFilter& filter, athena::io::FileWriter& writer);
FontAtlas(FT_Face face, uint32_t dpi, bool subpixel, FCharFilter& filter, athena::io::FileReader& reader);
FontAtlas(const FontAtlas& other) = delete;
FontAtlas& operator=(const FontAtlas& other) = delete;
uint32_t dpi() const { return m_dpi; }
FT_Fixed FT_Xscale() const { return m_ftXscale; }
FT_UShort FT_XPPem() const { return m_ftXPpem; }
FT_Pos FT_LineHeight() const { return m_lineHeight; }
bool isReady() const { return m_ready; }
boo::ObjToken<boo::ITextureSA> texture(boo::IGraphicsDataFactory* gf) const;
bool subpixel() const { return m_subpixel; }
const Glyph* lookupGlyph(atUint32 charcode) const {
auto search = m_glyphLookup.find(charcode);
if (search == m_glyphLookup.end())
return nullptr;
return &m_glyphs[search->second];
}
atInt16 lookupKern(atUint32 leftIdx, atUint32 rightIdx) const {
auto pairSearch = m_kernAdjs.find(leftIdx);
if (pairSearch == m_kernAdjs.cend())
return 0;
for (const std::pair<atUint16, atInt16>& p : pairSearch->second)
if (p.first == rightIdx)
return p.second;
return 0;
}
};
extern const FCharFilter AllCharFilter;
extern const FCharFilter LatinCharFilter;
extern const FCharFilter LatinAndJapaneseCharFilter;
class FontCache {
const hecl::Runtime::FileStoreManager& m_fileMgr;
hecl::SystemString m_cacheRoot;
struct Library {
FT_Library m_lib = nullptr;
Library();
~Library();
operator FT_Library() { return m_lib; }
} m_fontLib;
FreeTypeGZipMemFace m_regFace;
FreeTypeGZipMemFace m_monoFace;
FreeTypeGZipMemFace m_curvesFace;
std::unordered_map<FontTag, std::unique_ptr<FontAtlas>> m_cachedAtlases;
public:
FontCache(const hecl::Runtime::FileStoreManager& fileMgr);
~FontCache();
FontCache(const FontCache& other) = delete;
FontCache& operator=(const FontCache& other) = delete;
FontTag prepCustomFont(std::string_view name, FT_Face face, FCharFilter filter = AllCharFilter, bool subpixel = false,
float points = 10.0, uint32_t dpi = 72);
FontTag prepMainFont(FCharFilter filter = AllCharFilter, bool subpixel = false, float points = 10.0,
uint32_t dpi = 72) {
return prepCustomFont("droidsans-permissive", m_regFace, filter, subpixel, points, dpi);
}
FontTag prepMonoFont(FCharFilter filter = AllCharFilter, bool subpixel = false, float points = 10.0,
uint32_t dpi = 72) {
return prepCustomFont("bmonofont", m_monoFace, filter, subpixel, points, dpi);
}
FontTag prepCurvesFont(FCharFilter filter = AllCharFilter, bool subpixel = false, float points = 10.0,
uint32_t dpi = 72) {
return prepCustomFont("spectercurves", m_curvesFace, filter, subpixel, points, dpi);
}
void closeBuiltinFonts() {
m_regFace.close();
m_monoFace.close();
m_curvesFace.close();
}
const FontAtlas& lookupAtlas(FontTag tag) const;
void destroyAtlases() { m_cachedAtlases.clear(); }
};
} // namespace specter

View File

@ -0,0 +1,22 @@
#pragma once
#include <cstddef>
#include <string>
namespace boo {
struct ITexture;
struct SWindowCoord;
} // namespace boo
namespace specter {
struct IMenuNode {
virtual ~IMenuNode() = default;
virtual boo::ITexture* icon() const { return nullptr; }
virtual const std::string* text() const { return nullptr; }
virtual size_t subNodeCount() const { return 0; }
virtual IMenuNode* subNode(size_t idx) { return nullptr; }
virtual void activated(const boo::SWindowCoord& coord) {}
};
} // namespace specter

View File

@ -0,0 +1,36 @@
#pragma once
#include <utility>
#include <vector>
#include "specter/SplitView.hpp"
#include <locale.hpp>
#include <hecl/SystemChar.hpp>
namespace boo {
struct SWindowCoord;
}
namespace specter {
struct ISpaceController;
struct IViewManager {
public:
virtual locale::ELocale getTranslatorLocale() const { return locale::ELocale::en_US; }
template <typename Key, typename... Args>
constexpr auto translate(Args&&... args) const {
return locale::Translate<Key>(getTranslatorLocale(), std::forward<Args>(args)...);
}
virtual void deferSpaceSplit(ISpaceController* split, SplitView::Axis axis, int thisSlot,
const boo::SWindowCoord& coord) {}
virtual const std::vector<hecl::SystemString>* recentProjects() const { return nullptr; }
virtual void pushRecentProject(hecl::SystemStringView path) {}
virtual const std::vector<hecl::SystemString>* recentFiles() const { return nullptr; }
virtual void pushRecentFile(hecl::SystemStringView path) {}
};
} // namespace specter

View File

@ -0,0 +1,78 @@
#pragma once
#include <array>
#include <cstddef>
#include "specter/View.hpp"
#include <boo/BooObject.hpp>
#include <boo/IWindow.hpp>
#include <boo/graphicsdev/IGraphicsDataFactory.hpp>
#include <zeus/CVector2f.hpp>
namespace specter {
class ViewResources;
struct Icon {
boo::ObjToken<boo::ITexture> m_tex;
std::array<zeus::CVector2f, 4> m_uvCoords;
Icon() = default;
Icon(boo::ObjToken<boo::ITexture> tex, const std::array<float, 4>& rect) : m_tex(std::move(tex)) {
m_uvCoords[0][0] = rect[0];
m_uvCoords[0][1] = -rect[3];
m_uvCoords[1][0] = rect[0];
m_uvCoords[1][1] = -rect[1];
m_uvCoords[2][0] = rect[2];
m_uvCoords[2][1] = -rect[3];
m_uvCoords[3][0] = rect[2];
m_uvCoords[3][1] = -rect[1];
}
};
template <size_t COLS, size_t ROWS>
class IconAtlas {
boo::ObjToken<boo::ITextureS> m_tex;
std::array<std::array<Icon, ROWS>, COLS> m_icons;
Icon MakeIcon(float x, float y) const {
const std::array<float, 4> rect{
x / float(COLS),
y / float(ROWS),
x / float(COLS) + 1.f / float(COLS),
y / float(ROWS) + 1.f / float(ROWS),
};
return Icon(m_tex.get(), rect);
}
public:
IconAtlas() = default;
operator bool() const { return m_tex.operator bool(); }
void initializeAtlas(boo::ObjToken<boo::ITextureS> tex) {
m_tex = std::move(tex);
for (size_t c = 0; c < COLS; ++c)
for (size_t r = 0; r < ROWS; ++r)
m_icons[c][r] = MakeIcon(c, r);
}
void destroyAtlas() {
for (size_t c = 0; c < COLS; ++c)
for (size_t r = 0; r < ROWS; ++r)
m_icons[c][r].m_tex.reset();
m_tex.reset();
}
Icon& getIcon(size_t c, size_t r) { return m_icons[c][r]; }
};
class IconView : public View {
VertexBufferBindingTex m_vertexBinding;
public:
IconView(ViewResources& res, View& parentView, Icon& icon);
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
};
} // namespace specter

View File

@ -0,0 +1,96 @@
#pragma once
#include <cstddef>
#include <memory>
#include <string_view>
#include <vector>
#include "specter/View.hpp"
#include <boo/IWindow.hpp>
namespace specter {
class ScrollView;
class TextView;
struct IMenuNode;
class Menu : public View {
IMenuNode* m_rootNode;
IMenuNode* m_thisNode;
std::unique_ptr<Menu> m_subMenu;
std::unique_ptr<TextView> m_headText;
int m_cWidth, m_cHeight, m_cTop;
SolidShaderVert m_verts[8];
VertexBufferBindingSolid m_vertsBinding;
void setVerts(int width, int height, float pf);
struct ContentView : View {
Menu& m_menu;
ContentView(ViewResources& res, Menu& menu);
boo::SWindowRect m_scissorRect;
SolidShaderVert m_hlVerts[4];
VertexBufferBindingSolid m_hlVertsBinding;
size_t m_highlightedItem = SIZE_MAX;
void setHighlightedItem(size_t idx);
void unsetHighlightedItem(size_t idx) {
if (m_highlightedItem == idx)
setHighlightedItem(-1);
}
void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseMove(const boo::SWindowCoord&) override;
void mouseLeave(const boo::SWindowCoord&) override;
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub, const boo::SWindowRect& scissor) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
int nominalWidth() const override { return m_menu.m_cWidth; }
int nominalHeight() const override { return m_menu.m_cHeight; }
};
std::unique_ptr<ContentView> m_content;
ViewChild<std::unique_ptr<ScrollView>> m_scroll;
struct ItemView : View {
Menu& m_menu;
std::unique_ptr<TextView> m_textView;
size_t m_idx;
IMenuNode* m_node;
ItemView(ViewResources& res, Menu& menu, std::string_view text, size_t idx, IMenuNode* node);
void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseEnter(const boo::SWindowCoord&) override;
void mouseLeave(const boo::SWindowCoord&) override;
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
};
std::vector<ViewChild<std::unique_ptr<ItemView>>> m_items;
IMenuNode* m_deferredActivation = nullptr;
Menu(ViewResources& res, View& parentView, IMenuNode* rootNode, IMenuNode* thisNode);
public:
Menu(ViewResources& res, View& parentView, IMenuNode* rootNode);
~Menu() override;
void reset(IMenuNode* rootNode);
void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseMove(const boo::SWindowCoord&) override;
void mouseLeave(const boo::SWindowCoord&) override;
void scroll(const boo::SWindowCoord&, const boo::SScrollDelta&) override;
void think() override;
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
};
} // namespace specter

View File

@ -0,0 +1,58 @@
#pragma once
#include <functional>
#include <memory>
#include <string>
#include "specter/Button.hpp"
#include "specter/ModalWindow.hpp"
namespace specter {
class MultiLineTextView;
class MessageWindow : public ModalWindow {
public:
enum class Type { InfoOk, ErrorOk, ConfirmOkCancel };
private:
Type m_type;
std::function<void(bool ok)> m_func;
std::unique_ptr<MultiLineTextView> m_text;
struct OKBinding : IButtonBinding {
MessageWindow& m_mw;
std::string m_name;
OKBinding(MessageWindow& mw, std::string_view name) : m_mw(mw), m_name(name) {}
std::string_view name(const Control* control) const override { return m_name; }
void activated(const Button* button, const boo::SWindowCoord& coord) override { m_mw.m_func(true); }
} m_okBind;
ViewChild<std::unique_ptr<Button>> m_ok;
struct CancelBinding : IButtonBinding {
MessageWindow& m_mw;
std::string m_name;
CancelBinding(MessageWindow& mw, std::string_view name) : m_mw(mw), m_name(name) {}
std::string_view name(const Control* control) const override { return m_name; }
void activated(const Button* button, const boo::SWindowCoord& coord) override { m_mw.m_func(false); }
} m_cancelBind;
ViewChild<std::unique_ptr<Button>> m_cancel;
public:
MessageWindow(ViewResources& res, View& parentView, Type type, std::string_view message,
std::function<void(bool ok)> func);
~MessageWindow() override;
void updateContentOpacity(float opacity) override;
void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseMove(const boo::SWindowCoord&) override;
void mouseEnter(const boo::SWindowCoord&) override;
void mouseLeave(const boo::SWindowCoord&) override;
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
};
} // namespace specter

View File

@ -0,0 +1,78 @@
#pragma once
#include <memory>
#include "specter/View.hpp"
#include <boo/IWindow.hpp>
#include <hecl/UniformBufferPool.hpp>
#include <zeus/CColor.hpp>
namespace specter {
class TextView;
class ViewResources;
class ModalWindow : public View {
public:
enum class Phase { BuildIn, ResWait, Showing, BuildOut, Done };
private:
int m_frame = 0;
int m_contentStartFrame = 0;
float m_lineTime = 0.0;
Phase m_phase = Phase::BuildIn;
int m_width = 0;
int m_height = 0;
RectangleConstraint m_constraint;
zeus::CColor m_windowBg;
zeus::CColor m_windowBgClear;
zeus::CColor m_line1;
zeus::CColor m_line2;
zeus::CColor m_line2Clear;
ViewBlock m_viewBlock;
hecl::UniformBufferPool<ViewBlock>::Token m_viewBlockBuf;
union {
struct {
SolidShaderVert lineVerts[22];
SolidShaderVert fillVerts[16];
} m_verts;
SolidShaderVert _m_verts[38];
};
void setLineVerts(int width, int height, float pf, float t);
void setLineVertsOut(int width, int height, float pf, float t);
void setLineColors(float t);
void setLineColorsOut(float t);
void setFillVerts(int width, int height, float pf);
void setFillColors(float t);
VertexBufferBindingSolid m_vertsBinding;
void _loadVerts() { m_vertsBinding.load<decltype(_m_verts)>(_m_verts); }
std::unique_ptr<TextView> m_cornersOutline[4];
std::unique_ptr<TextView> m_cornersFilled[4];
protected:
virtual void updateContentOpacity(float opacity) {}
RectangleConstraint& constraint() { return m_constraint; }
public:
ModalWindow(ViewResources& res, View& parentView, const RectangleConstraint& constraint, const zeus::CColor& bgColor);
ModalWindow(ViewResources& res, View& parentView, const RectangleConstraint& constraint);
~ModalWindow() override;
void think() override;
bool skipBuildInAnimation();
void close(bool skipAnimation = false);
bool closed() const { return m_phase >= Phase::BuildOut; }
ModalWindow::Phase phase() const { return m_phase; }
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
};
} // namespace specter

View File

@ -0,0 +1,55 @@
#pragma once
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
#include "specter/FontCache.hpp"
#include "specter/TextView.hpp"
#include "specter/View.hpp"
#include <boo/IWindow.hpp>
#include <zeus/CColor.hpp>
namespace specter {
class ViewResources;
class MultiLineTextView : public View {
ViewResources& m_viewSystem;
std::vector<std::unique_ptr<TextView>> m_lines;
const FontAtlas& m_fontAtlas;
TextView::Alignment m_align;
size_t m_lineCapacity;
float m_lineHeight;
int m_width;
std::string LineWrap(std::string_view str, int wrap);
std::wstring LineWrap(std::wstring_view str, int wrap);
public:
MultiLineTextView(ViewResources& res, View& parentView, const FontAtlas& font,
TextView::Alignment align = TextView::Alignment::Left, size_t lineCapacity = 256,
float lineHeight = 1.0);
MultiLineTextView(ViewResources& res, View& parentView, FontTag font,
TextView::Alignment align = TextView::Alignment::Left, size_t lineCapacity = 256,
float lineHeight = 1.0);
void typesetGlyphs(std::string_view str, const zeus::CColor& defaultColor = zeus::skWhite, unsigned wrap = 0);
void typesetGlyphs(std::wstring_view str, const zeus::CColor& defaultColor = zeus::skWhite,
unsigned wrap = 0);
void colorGlyphs(const zeus::CColor& newColor);
void setMultiplyColor(const zeus::CColor& color) override {
for (std::unique_ptr<TextView>& l : m_lines)
l->setMultiplyColor(color);
}
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
int nominalWidth() const override { return m_width; }
int nominalHeight() const override { return (int(m_lineHeight * m_fontAtlas.FT_LineHeight()) >> 6) * m_lines.size(); }
};
} // namespace specter

View File

@ -0,0 +1 @@
#pragma once

View File

@ -0,0 +1 @@
#pragma once

View File

@ -0,0 +1,59 @@
#pragma once
#include <memory>
#include <string>
#include "specter/View.hpp"
namespace boo {
struct IGraphicsBufferD;
struct IGraphicsDataFactory;
struct IShaderDataBinding;
} // namespace boo
namespace specter {
class TextView;
class ViewResources;
class NumericField : public View {
std::string m_textStr;
std::unique_ptr<TextView> m_text;
SolidShaderVert m_verts[28];
ViewBlock m_bBlock;
boo::IGraphicsBufferD* m_bBlockBuf;
boo::IGraphicsBufferD* m_bVertsBuf;
boo::IShaderDataBinding* m_bShaderBinding;
int m_nomWidth, m_nomHeight;
bool m_pressed = false;
bool m_hovered = false;
void setInactive();
void setHover();
void setPressed();
void setDisabled();
public:
class Resources {
friend class Button;
friend class ViewResources;
void init(boo::IGraphicsDataFactory* factory, const IThemeData& theme);
};
NumericField(ViewResources& res, View& parentView, std::string_view text);
void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseEnter(const boo::SWindowCoord&) override;
void mouseLeave(const boo::SWindowCoord&) override;
void resized(const boo::SWindowRect& rootView, const boo::SWindowRect& sub) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
void setText(std::string_view text);
int nominalWidth() const override { return m_nomWidth; }
int nominalHeight() const override { return m_nomHeight; }
};
} // namespace specter

View File

@ -0,0 +1,43 @@
#pragma once
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
#include "specter/View.hpp"
namespace specter {
class Outliner {
class Node : public View {
struct INodeController {
virtual boo::ITexture* icon() const { return nullptr; }
virtual const std::string* text() const { return nullptr; }
virtual size_t subNodeCount() const { return 0; }
virtual INodeController* subNode(size_t idx) { return nullptr; }
virtual void activated(const boo::SWindowCoord& coord) {}
};
std::string m_description;
std::vector<std::unique_ptr<Node>> m_children;
bool m_collapsible;
bool m_collapsed;
public:
class Resources {
friend class ViewResources;
void init(boo::IGraphicsDataFactory* factory, const IThemeData& theme);
};
Node(ViewResources& res, View& parentView, std::string_view description);
void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseEnter(const boo::SWindowCoord&) override;
void mouseLeave(const boo::SWindowCoord&) override;
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
void think() override;
};
};
} // namespace specter

View File

@ -0,0 +1 @@
#pragma once

View File

@ -0,0 +1,40 @@
#pragma once
#include <cstddef>
#include <memory>
#include <vector>
#include "specter/ScrollView.hpp"
#include <hecl/SystemChar.hpp>
namespace specter {
class ViewResources;
struct IPathButtonsBinding {
virtual void pathButtonActivated(size_t idx) = 0;
};
class PathButtons : public ScrollView {
struct ContentView;
struct PathButton;
friend struct PathButton;
ViewChild<std::unique_ptr<ContentView>> m_contentView;
int m_pathButtonPending = -1;
IPathButtonsBinding& m_binding;
bool m_fillContainer;
std::vector<PathButton> m_pathButtons;
public:
PathButtons(ViewResources& res, View& parentView, IPathButtonsBinding& binding, bool fillContainer = false);
~PathButtons() override;
void setButtons(const std::vector<hecl::SystemString>& comps);
void setMultiplyColor(const zeus::CColor& color) override;
/* Fill all available space in container when requested */
void containerResized(const boo::SWindowRect& root, const boo::SWindowRect& sub) override;
};
} // namespace specter

View File

@ -0,0 +1,233 @@
#pragma once
#include <cstddef>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "specter/IMenuNode.hpp"
#include "specter/SplitView.hpp"
#include "specter/View.hpp"
#include <boo/BooObject.hpp>
#include <boo/DeferredWindowEvents.hpp>
#include <boo/IWindow.hpp>
#include <boo/graphicsdev/IGraphicsDataFactory.hpp>
#include <hecl/UniformBufferPool.hpp>
namespace specter {
class Button;
class ITextInputView;
class Tooltip;
class ViewResources;
struct IViewManager;
class RootView : public View {
boo::IWindow* m_window = nullptr;
boo::ObjToken<boo::ITextureR> m_renderTex;
boo::SWindowRect m_rootRect = {};
bool m_resizeRTDirty = false;
bool m_destroyed = false;
IViewManager& m_viewMan;
ViewResources* m_viewRes;
ITextInputView* m_activeTextView = nullptr;
View* m_activeDragView = nullptr;
Button* m_activeMenuButton = nullptr;
ViewChild<std::unique_ptr<View>> m_rightClickMenu;
boo::SWindowRect m_rightClickMenuRootAndLoc;
SplitView* m_hoverSplitDragView = nullptr;
bool m_activeSplitDragView = false;
SplitView* recursiveTestSplitHover(SplitView* sv, const boo::SWindowCoord& coord) const;
boo::DeferredWindowEvents<RootView> m_events;
struct SplitMenuSystem : IMenuNode {
RootView& m_rv;
std::string m_text;
SplitView* m_splitView = nullptr;
enum class Phase {
Inactive,
InteractiveSplit,
InteractiveJoin,
} m_phase = Phase::Inactive;
int m_interactiveSlot = 0;
float m_interactiveSplit = 0.5;
bool m_interactiveDown = false;
VertexBufferBindingSolid m_vertsBinding;
ViewBlock m_viewBlock;
hecl::UniformBufferPool<ViewBlock>::Token m_viewVertBlockBuf;
SolidShaderVert m_verts[32];
void setArrowVerts(const boo::SWindowRect& rect, SplitView::ArrowDir dir);
void setLineVerts(const boo::SWindowRect& rect, float split, SplitView::Axis axis);
void mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mods);
void mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mods);
void mouseMove(const boo::SWindowCoord& coord);
void mouseLeave(const boo::SWindowCoord& coord);
void resized();
void draw(boo::IGraphicsCommandQueue* gfxQ);
SplitMenuSystem(RootView& rv, boo::IGraphicsDataFactory::Context& ctx);
const std::string* text() const override { return &m_text; }
size_t subNodeCount() const override { return 2; }
IMenuNode* subNode(size_t idx) override {
if (idx)
return &m_joinActionNode;
else
return &m_splitActionNode;
}
boo::SWindowCoord m_deferredCoord;
bool m_deferredSplit = false;
bool m_deferredJoin = false;
struct SplitActionNode : IMenuNode {
SplitMenuSystem& m_smn;
std::string m_text;
SplitActionNode(SplitMenuSystem& smn);
const std::string* text() const override { return &m_text; }
void activated(const boo::SWindowCoord& coord) override {
m_smn.m_deferredSplit = true;
m_smn.m_deferredCoord = coord;
}
} m_splitActionNode;
struct JoinActionNode : IMenuNode {
SplitMenuSystem& m_smn;
std::string m_text;
JoinActionNode(SplitMenuSystem& smn);
const std::string* text() const override { return &m_text; }
void activated(const boo::SWindowCoord& coord) override {
m_smn.m_deferredJoin = true;
m_smn.m_deferredCoord = coord;
}
} m_joinActionNode;
};
std::optional<SplitMenuSystem> m_splitMenuSystem;
public:
RootView(IViewManager& viewMan, ViewResources& res, boo::IWindow* window);
~RootView() override;
void destroyed();
bool isDestroyed() const { return m_destroyed; }
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Woverloaded-virtual"
void resized(const boo::SWindowRect& rect, bool) { resized(rect, rect); }
#pragma GCC diagnostic pop
void resized(const boo::SWindowRect& rootView, const boo::SWindowRect& sub) override;
void mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mods) override;
void mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mods) override;
void mouseMove(const boo::SWindowCoord& coord) override;
void mouseEnter(const boo::SWindowCoord& coord) override;
void mouseLeave(const boo::SWindowCoord& coord) override;
void scroll(const boo::SWindowCoord& coord, const boo::SScrollDelta& scroll) override;
void touchDown(const boo::STouchCoord& coord, uintptr_t tid) override;
void touchUp(const boo::STouchCoord& coord, uintptr_t tid) override;
void touchMove(const boo::STouchCoord& coord, uintptr_t tid) override;
void charKeyDown(unsigned long charCode, boo::EModifierKey mods, bool isRepeat) override;
void charKeyUp(unsigned long charCode, boo::EModifierKey mods) override;
void specialKeyDown(boo::ESpecialKey key, boo::EModifierKey mods, bool isRepeat) override;
void specialKeyUp(boo::ESpecialKey key, boo::EModifierKey mods) override;
void modKeyDown(boo::EModifierKey mod, bool isRepeat) override;
void modKeyUp(boo::EModifierKey mod) override;
boo::ITextInputCallback* getTextInputCallback();
void internalThink();
void dispatchEvents() { m_events.dispatchEvents(); }
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
const boo::SWindowRect& rootRect() const { return m_rootRect; }
boo::IWindow* window() const { return m_window; }
IViewManager& viewManager() const { return m_viewMan; }
ViewResources& viewRes() const { return *m_viewRes; }
const IThemeData& themeData() const;
const boo::ObjToken<boo::ITextureR>& renderTex() const { return m_renderTex; }
std::vector<View*>& accessContentViews() { return m_views; }
void adoptRightClickMenu(std::unique_ptr<View>&& menu, const boo::SWindowCoord& coord) {
m_rightClickMenu.m_view = std::move(menu);
m_rightClickMenuRootAndLoc = subRect();
m_rightClickMenuRootAndLoc.location[0] = coord.pixel[0];
m_rightClickMenuRootAndLoc.location[1] = coord.pixel[1];
updateSize();
}
View* getRightClickMenu() { return m_rightClickMenu.m_view.get(); }
void setActiveTextView(ITextInputView* textView);
void setActiveDragView(View* dragView) { m_activeDragView = dragView; }
void unsetActiveDragView(View* dragView) {
if (dragView == m_activeDragView)
m_activeDragView = nullptr;
}
void setActiveMenuButton(Button* button) { m_activeMenuButton = button; }
void unsetActiveMenuButton(Button* button) {
if (button == m_activeMenuButton)
m_activeMenuButton = nullptr;
}
void startSplitDrag(SplitView* sv, const boo::SWindowCoord& coord) {
m_hoverSplitDragView = sv;
m_activeSplitDragView = true;
sv->startDragSplit(coord);
}
bool m_hSplitHover = false;
void setHorizontalSplitHover(bool hover) {
m_hSplitHover = hover;
_updateCursor();
}
bool m_vSplitHover = false;
void setVerticalSplitHover(bool hover) {
m_vSplitHover = hover;
_updateCursor();
}
bool m_textFieldHover = false;
void setTextFieldHover(bool hover) {
m_textFieldHover = hover;
_updateCursor();
}
bool m_spaceCornerHover = false;
void setSpaceCornerHover(bool hover) {
m_spaceCornerHover = hover;
_updateCursor();
}
void resetTooltip(ViewResources& res);
void displayTooltip(std::string_view name, std::string_view help);
void beginInteractiveJoin(SplitView* sv, const boo::SWindowCoord& coord) {
m_splitMenuSystem->m_phase = SplitMenuSystem::Phase::InteractiveJoin;
m_splitMenuSystem->m_splitView = sv;
m_splitMenuSystem->mouseMove(coord);
}
private:
void _updateCursor() {
if (m_spaceCornerHover)
m_window->setCursor(boo::EMouseCursor::Crosshairs);
else if (m_vSplitHover)
m_window->setCursor(boo::EMouseCursor::HorizontalArrow);
else if (m_hSplitHover)
m_window->setCursor(boo::EMouseCursor::VerticalArrow);
else if (m_textFieldHover)
m_window->setCursor(boo::EMouseCursor::IBeam);
else
m_window->setCursor(boo::EMouseCursor::Pointer);
}
std::vector<View*> m_views;
std::unique_ptr<Tooltip> m_tooltip;
};
} // namespace specter

View File

@ -0,0 +1,77 @@
#pragma once
#include <cstddef>
#include <memory>
#include <string>
#include "specter/Button.hpp"
#include "specter/View.hpp"
namespace specter {
class Button;
class Control;
class ViewResources;
struct IViewManager;
class ScrollView : public View {
public:
enum class Style { Plain, ThinIndicator, SideButtons };
private:
Style m_style;
ScissorViewChild<View*> m_contentView;
int m_scroll[2] = {};
int m_targetScroll[2] = {};
size_t m_consecutiveIdx = 0;
double m_consecutiveScroll[16][2] = {};
bool m_drawInd = false;
bool m_drawSideButtons = false;
SolidShaderVert m_verts[4];
VertexBufferBindingSolid m_vertsBinding;
enum class SideButtonState { None, ScrollLeft, ScrollRight } m_sideButtonState = SideButtonState::None;
struct SideButtonBinding : IButtonBinding {
ScrollView& m_sv;
std::string m_leftName, m_rightName;
SideButtonBinding(ScrollView& sv, IViewManager& vm);
std::string_view name(const Control* control) const override;
void down(const Button* button, const boo::SWindowCoord& coord) override;
void up(const Button* button, const boo::SWindowCoord& coord) override;
} m_sideButtonBind;
ViewChild<std::unique_ptr<Button>> m_sideButtons[2];
bool _scroll(const boo::SScrollDelta& scroll);
int scrollAreaWidth() const;
public:
ScrollView(ViewResources& res, View& parentView, Style style);
void setContentView(View* v) {
m_contentView.m_view = v;
updateSize();
}
void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseMove(const boo::SWindowCoord&) override;
void mouseEnter(const boo::SWindowCoord&) override;
void mouseLeave(const boo::SWindowCoord&) override;
void scroll(const boo::SWindowCoord& coord, const boo::SScrollDelta& scroll) override;
int getScrollX() const { return m_scroll[0]; }
int getScrollY() const { return m_scroll[1]; }
int nominalWidth() const override { return subRect().size[0]; }
int nominalHeight() const override { return subRect().size[1]; }
void setMultiplyColor(const zeus::CColor& color) override;
void think() override;
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
};
} // namespace specter

View File

@ -0,0 +1,69 @@
#pragma once
#include <memory>
#include "specter/SplitView.hpp"
#include "specter/Toolbar.hpp"
#include "specter/View.hpp"
namespace specter {
class ViewResources;
struct ISplitSpaceController;
struct ISpaceController {
virtual bool spaceSplitAllowed() const { return false; }
virtual ISplitSpaceController* spaceSplit(SplitView::Axis axis, int thisSlot) { return nullptr; }
};
struct ISplitSpaceController {
virtual SplitView* splitView() = 0;
virtual void updateSplit(float split) = 0;
virtual void joinViews(SplitView* thisSplit, int thisSlot, SplitView* otherSplit, int otherSlot) = 0;
};
class Space : public View {
struct CornerView;
friend class RootView;
friend struct CornerView;
ISpaceController& m_controller;
Toolbar::Position m_tbPos;
ViewChild<std::unique_ptr<Toolbar>> m_toolbar;
ViewChild<View*> m_contentView;
bool m_cornerDrag = false;
int m_cornerDragPoint[2];
ViewChild<std::unique_ptr<CornerView>> m_cornerView;
public:
Space(ViewResources& res, View& parentView, ISpaceController& controller, Toolbar::Position toolbarPos,
unsigned tbUnits);
~Space() override;
View* setContentView(View* view);
Toolbar* toolbar() { return m_toolbar.m_view.get(); }
void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseMove(const boo::SWindowCoord&) override;
void mouseEnter(const boo::SWindowCoord&) override;
void mouseLeave(const boo::SWindowCoord&) override;
void resized(const boo::SWindowRect& rootView, const boo::SWindowRect& sub) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
SplitView* findSplitViewOnSide(SplitView::Axis axis, int side);
void setMultiplyColor(const zeus::CColor& color) override {
View::setMultiplyColor(color);
if (m_contentView.m_view)
m_contentView.m_view->setMultiplyColor(color);
if (m_toolbar.m_view)
m_toolbar.m_view->setMultiplyColor(color);
}
bool isSpace() const override { return true; }
};
inline Space* View::castToSpace() { return isSpace() ? static_cast<Space*>(this) : nullptr; }
} // namespace specter

View File

@ -0,0 +1,108 @@
#pragma once
#include "specter/View.hpp"
#include <boo/BooObject.hpp>
#include <hecl/UniformBufferPool.hpp>
namespace specter {
struct ISplitSpaceController;
class SplitView : public View {
friend class RootView;
friend class Space;
public:
class Resources {
friend class SplitView;
friend class ViewResources;
boo::ObjToken<boo::ITextureS> m_shadingTex;
void init(boo::IGraphicsDataFactory::Context& ctx, const IThemeData& theme);
void destroy() { m_shadingTex.reset(); }
};
enum class ArrowDir { Up, Down, Left, Right };
enum class Axis { Horizontal, Vertical };
private:
ISplitSpaceController* m_controller;
Axis m_axis;
float m_slide = 0.5;
void _setSplit(float slide);
bool m_dragging = false;
ViewChild<View*> m_views[2];
ViewBlock m_splitBlock;
hecl::UniformBufferPool<ViewBlock>::Token m_splitBlockBuf;
TexShaderVert m_splitVerts[4];
int m_clearanceA, m_clearanceB;
void setHorizontalVerts(int width) {
m_splitVerts[0].m_pos.assign(0, 2, 0);
m_splitVerts[0].m_uv.assign(0, 0);
m_splitVerts[1].m_pos.assign(0, -1, 0);
m_splitVerts[1].m_uv.assign(1, 0);
m_splitVerts[2].m_pos.assign(width, 2, 0);
m_splitVerts[2].m_uv.assign(0, 0);
m_splitVerts[3].m_pos.assign(width, -1, 0);
m_splitVerts[3].m_uv.assign(1, 0);
}
void setVerticalVerts(int height) {
m_splitVerts[0].m_pos.assign(-1, height, 0);
m_splitVerts[0].m_uv.assign(0, 0);
m_splitVerts[1].m_pos.assign(-1, 0, 0);
m_splitVerts[1].m_uv.assign(0, 0);
m_splitVerts[2].m_pos.assign(2, height, 0);
m_splitVerts[2].m_uv.assign(1, 0);
m_splitVerts[3].m_pos.assign(2, 0, 0);
m_splitVerts[3].m_uv.assign(1, 0);
}
VertexBufferBindingTex m_splitVertsBinding;
public:
SplitView(ViewResources& res, View& parentView, ISplitSpaceController* controller, Axis axis, float split,
int clearanceA = -1, int clearanceB = -1);
View* setContentView(int slot, View* view);
void setSplit(float split);
void setAxis(Axis axis);
Axis axis() const { return m_axis; }
float split() const { return m_slide; }
bool testSplitHover(const boo::SWindowCoord& coord);
bool testJoinArrowHover(const boo::SWindowCoord& coord, int& origSlotOut, SplitView*& splitOut, int& slotOut,
boo::SWindowRect& rectOut, ArrowDir& dirOut, int forceSlot = -1);
void getJoinArrowHover(int slot, boo::SWindowRect& rectOut, ArrowDir& dirOut);
bool testSplitLineHover(const boo::SWindowCoord& coord, int& slotOut, boo::SWindowRect& rectOut, float& splitOut,
Axis& axisOut);
void getSplitLineHover(int slot, boo::SWindowRect& rectOut, Axis& axisOut);
void startDragSplit(const boo::SWindowCoord& coord);
void endDragSplit();
void moveDragSplit(const boo::SWindowCoord& coord);
void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseMove(const boo::SWindowCoord&) override;
void mouseEnter(const boo::SWindowCoord&) override;
void mouseLeave(const boo::SWindowCoord&) override;
void resized(const boo::SWindowRect& rootView, const boo::SWindowRect& sub) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
void setMultiplyColor(const zeus::CColor& color) override {
View::setMultiplyColor(color);
m_splitBlock.m_color = color;
m_splitBlockBuf.access().finalAssign(m_splitBlock);
if (m_views[0].m_view)
m_views[0].m_view->setMultiplyColor(color);
if (m_views[1].m_view)
m_views[1].m_view->setMultiplyColor(color);
}
bool isSplitView() const override { return true; }
};
inline SplitView* View::castToSplitView() { return isSplitView() ? static_cast<SplitView*>(this) : nullptr; }
} // namespace specter

View File

@ -0,0 +1,119 @@
#pragma once
#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include "specter/View.hpp"
namespace specter {
#define SPECTER_TABLE_MAX_ROWS 128ul
class ScrollView;
enum class SortDirection { None, Ascending, Descending };
struct ITableDataBinding {
virtual size_t columnCount() const = 0;
virtual size_t rowCount() const = 0;
virtual std::string_view header(size_t cIdx) const { return {}; }
virtual std::string_view cell(size_t cIdx, size_t rIdx) const { return {}; }
};
struct ITableStateBinding {
virtual float getColumnSplit(size_t cIdx) const { return -1.0; }
virtual bool columnSplitResizeAllowed() const { return false; }
virtual void setColumnSplit(size_t cIdx, float split) {}
virtual SortDirection getSort(size_t& cIdx) const {
cIdx = 0;
return SortDirection::None;
}
virtual void setSort(size_t cIdx, SortDirection dir) {}
virtual void setSelectedRow(size_t rIdx) {}
virtual void rowActivated(size_t rIdx) {}
};
class Table : public View {
struct CellView;
ITableDataBinding* m_data;
ITableStateBinding* m_state;
size_t m_maxColumns;
size_t m_rows = 0;
size_t m_columns = 0;
size_t m_selectedRow = SIZE_MAX;
size_t m_deferredActivation = SIZE_MAX;
size_t m_clickFrames = 15;
std::vector<ViewChild<std::unique_ptr<CellView>>> m_headerViews;
using ColumnPool = std::array<std::array<ViewChild<std::unique_ptr<CellView>>, SPECTER_TABLE_MAX_ROWS>, 2>;
std::vector<ColumnPool> m_cellPools;
size_t m_ensuredRows = 0;
std::vector<ColumnPool>& ensureCellPools(size_t rows, size_t cols, ViewResources& res);
size_t m_activePool = SIZE_MAX;
bool m_header = false;
std::vector<boo::SWindowRect> m_hCellRects;
size_t m_hDraggingIdx = 0;
std::unique_ptr<SolidShaderVert[]> m_hVerts;
VertexBufferBindingSolid m_vertsBinding;
void _setHeaderVerts(const boo::SWindowRect& rect);
std::vector<boo::SWindowRect> getCellRects(const boo::SWindowRect& tableRect) const;
ViewChild<std::unique_ptr<ScrollView>> m_scroll;
struct RowsView : public View {
Table& m_t;
std::unique_ptr<SolidShaderVert[]> m_verts;
VertexBufferBindingSolid m_vertsBinding;
size_t m_visibleStart = 0;
size_t m_visibleRows = 0;
boo::SWindowRect m_scissorRect;
void _setRowVerts(const boo::SWindowRect& rowsRect, const boo::SWindowRect& scissor);
RowsView(Table& t, ViewResources& res);
int nominalHeight() const override;
int nominalWidth() const override;
void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseMove(const boo::SWindowCoord&) override;
void mouseLeave(const boo::SWindowCoord&) override;
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub, const boo::SWindowRect& scissor) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
} m_rowsView;
bool m_headerNeedsUpdate = false;
bool m_inSelectRow = false;
void _updateData();
public:
Table(ViewResources& res, View& parentView, ITableDataBinding* data, ITableStateBinding* state = nullptr,
size_t maxColumns = 8);
~Table() override;
void cycleSortColumn(size_t c);
void selectRow(size_t r);
void setMultiplyColor(const zeus::CColor& color) override;
void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseMove(const boo::SWindowCoord&) override;
void mouseEnter(const boo::SWindowCoord&) override;
void mouseLeave(const boo::SWindowCoord&) override;
void scroll(const boo::SWindowCoord&, const boo::SScrollDelta&) override;
void think() override;
void updateData();
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
};
} // namespace specter

View File

@ -0,0 +1,127 @@
#pragma once
#include <cstddef>
#include <memory>
#include <string>
#include <utility>
#include "specter/Control.hpp"
#include <boo/IWindow.hpp>
namespace specter {
class TextView;
class ViewResources;
class TextField : public ITextInputView {
bool m_hasTextSet = false;
bool m_hasMarkSet = false;
std::string m_textStr;
std::string m_deferredTextStr;
std::string m_deferredMarkStr;
std::unique_ptr<TextView> m_text;
std::unique_ptr<TextView> m_errText;
SolidShaderVert m_verts[41];
VertexBufferBindingSolid m_vertsBinding;
int m_nomWidth = 0;
int m_nomHeight = 0;
bool m_hasSelectionClear = false;
bool m_hasSelectionSet = false;
bool m_hasCursorSet = false;
size_t m_selectionStart = 0;
size_t m_deferredSelectionStart = 0;
size_t m_selectionCount = 0;
size_t m_deferredSelectionCount = 0;
size_t m_markReplStart = 0;
size_t m_deferredMarkReplStart = 0;
size_t m_markReplCount = 0;
size_t m_deferredMarkReplCount = 0;
size_t m_markSelStart = 0;
size_t m_deferredMarkSelStart = 0;
size_t m_markSelCount = 0;
size_t m_deferredMarkSelCount = 0;
size_t m_cursorPos = 0;
size_t m_deferredCursorPos = SIZE_MAX;
size_t m_cursorFrames = 0;
size_t m_clickFrames = 15;
size_t m_clickFrames2 = 15;
size_t m_errorFrames = 360;
size_t m_dragStart = 0;
size_t m_dragging = 0;
bool m_active = false;
bool m_error = false;
enum class BGState { Inactive, Hover, Disabled } m_bgState = BGState::Inactive;
void setInactive();
void setHover();
void setDisabled();
void refreshBg();
public:
TextField(ViewResources& res, View& parentView, IStringBinding* strBind);
~TextField() override;
std::string_view getText() const { return m_textStr; }
void setText(std::string_view str);
void clipboardCopy() override;
void clipboardCut() override;
void clipboardPaste() override;
void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseMove(const boo::SWindowCoord&) override;
void mouseEnter(const boo::SWindowCoord&) override;
void mouseLeave(const boo::SWindowCoord&) override;
void specialKeyDown(boo::ESpecialKey, boo::EModifierKey, bool) override;
bool hasMarkedText() const override;
std::pair<int, int> markedRange() const override;
std::pair<int, int> selectedRange() const override;
void setMarkedText(std::string_view str, const std::pair<int, int>& selectedRange,
const std::pair<int, int>& replacementRange) override;
void unmarkText() override;
std::string substringForRange(const std::pair<int, int>& range, std::pair<int, int>& actualRange) const override;
void insertText(std::string_view str, const std::pair<int, int>& range) override;
int characterIndexAtPoint(const boo::SWindowCoord& point) const override;
boo::SWindowRect rectForCharacterRange(const std::pair<int, int>& range,
std::pair<int, int>& actualRange) const override;
void think() override;
void resized(const boo::SWindowRect& rootView, const boo::SWindowRect& sub) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
int nominalWidth() const override { return m_nomWidth; }
int nominalHeight() const override { return m_nomHeight; }
void setActive(bool active) override;
void setCursorPos(size_t pos);
void setErrorState(std::string_view message);
void clearErrorState();
void setSelectionRange(size_t start, size_t count);
void clearSelectionRange();
void setMultiplyColor(const zeus::CColor& color) override;
private:
void _setCursorPos();
void _reallySetCursorPos(size_t pos);
void _setSelectionRange();
void _reallySetSelectionRange(size_t start, size_t len);
void _reallySetMarkRange(size_t start, size_t len);
void _clearSelectionRange();
void _setText();
void _setMarkedText();
};
} // namespace specter

View File

@ -0,0 +1,116 @@
#pragma once
#include "View.hpp"
#include "boo/graphicsdev/IGraphicsDataFactory.hpp"
#include "FontCache.hpp"
namespace specter {
class ViewResources;
class TextView : public View {
public:
enum class Alignment { Left, Center, Right };
struct RenderGlyph {
zeus::CVector3f m_pos[4];
zeus::CMatrix4f m_mv;
zeus::CVector3f m_uv[4];
zeus::CColor m_color;
// char _dummy[48];
RenderGlyph& operator=(const RenderGlyph& other) {
m_pos[0] = other.m_pos[0];
m_pos[1] = other.m_pos[1];
m_pos[2] = other.m_pos[2];
m_pos[3] = other.m_pos[3];
m_mv = other.m_mv;
m_uv[0] = other.m_uv[0];
m_uv[1] = other.m_uv[1];
m_uv[2] = other.m_uv[2];
m_uv[3] = other.m_uv[3];
m_color = other.m_color;
return *this;
}
RenderGlyph(int& adv, const FontAtlas::Glyph& glyph, const zeus::CColor& defaultColor);
};
struct RenderGlyphInfo {
uint32_t m_char;
std::pair<int, int> m_dims;
int m_adv;
bool m_space = false;
RenderGlyphInfo(uint32_t ch, int width, int height, int adv)
: m_char(ch), m_dims(width, height), m_adv(adv), m_space(iswspace(ch) != 0) {}
};
private:
size_t m_capacity;
size_t m_curSize = 0;
hecl::VertexBufferPool<RenderGlyph>::Token m_glyphBuf;
boo::ObjToken<boo::IShaderDataBinding> m_shaderBinding;
const FontAtlas& m_fontAtlas;
Alignment m_align;
int m_width = 0;
friend class MultiLineTextView;
static int DoKern(FT_Pos val, const FontAtlas& atlas);
void _commitResources(size_t capacity);
public:
class Resources {
friend class ViewResources;
friend class TextView;
friend class MultiLineTextView;
hecl::VertexBufferPool<RenderGlyph> m_glyphPool;
void updateBuffers() { m_glyphPool.updateBuffers(); }
FontCache* m_fcache = nullptr;
boo::ObjToken<boo::IShaderPipeline> m_regular;
boo::ObjToken<boo::IShaderPipeline> m_subpixel;
void init(boo::IGraphicsDataFactory::Context& ctx, FontCache* fcache);
void destroy() {
m_glyphPool.doDestroy();
m_regular.reset();
m_subpixel.reset();
}
};
TextView(ViewResources& res, View& parentView, const FontAtlas& font, Alignment align = Alignment::Left,
size_t capacity = 256);
TextView(ViewResources& res, View& parentView, FontTag font, Alignment align = Alignment::Left,
size_t capacity = 256);
std::vector<RenderGlyph>& accessGlyphs() { return m_glyphs; }
const std::vector<RenderGlyph>& accessGlyphs() const { return m_glyphs; }
void typesetGlyphs(std::string_view str, const zeus::CColor& defaultColor = zeus::skWhite);
void typesetGlyphs(std::wstring_view str, const zeus::CColor& defaultColor = zeus::skWhite);
void invalidateGlyphs();
void colorGlyphs(const zeus::CColor& newColor);
void colorGlyphsTypeOn(const zeus::CColor& newColor, float startInterval = 0.2, float fadeTime = 0.5);
void think() override;
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
int nominalWidth() const override { return m_width; }
int nominalHeight() const override { return m_fontAtlas.FT_LineHeight() >> 6; }
std::pair<int, int> queryGlyphDimensions(size_t pos) const;
size_t reverseSelectGlyph(int x) const;
int queryReverseAdvance(size_t idx) const;
std::pair<size_t, size_t> queryWholeWordRange(size_t idx) const;
private:
std::vector<RenderGlyph> m_glyphs;
std::vector<RenderGlyphInfo> m_glyphInfo;
};
} // namespace specter

View File

@ -0,0 +1,77 @@
#pragma once
#include <vector>
#include "specter/View.hpp"
#include <boo/BooObject.hpp>
#include <boo/IWindow.hpp>
#include <boo/graphicsdev/IGraphicsDataFactory.hpp>
#include <hecl/UniformBufferPool.hpp>
namespace boo {
struct IGraphicsCommandQueue;
}
namespace specter {
#define SPECTER_TOOLBAR_GAUGE 28
class Toolbar : public View {
public:
class Resources {
friend class ViewResources;
friend class Toolbar;
boo::ObjToken<boo::ITextureS> m_shadingTex;
void init(boo::IGraphicsDataFactory::Context& ctx, const IThemeData& theme);
void destroy() { m_shadingTex.reset(); }
};
enum class Position { None, Bottom, Top };
private:
unsigned m_units;
std::vector<std::vector<ViewChild<View*>>> m_children;
ViewBlock m_tbBlock;
hecl::UniformBufferPool<ViewBlock>::Token m_tbBlockBuf;
TexShaderVert m_tbVerts[10];
int m_nomGauge = 25;
int m_padding = 10;
void setHorizontalVerts(int width);
void setVerticalVerts(int height);
VertexBufferBindingTex m_vertsBinding;
public:
Toolbar(ViewResources& res, View& parentView, Position toolbarPos, unsigned units);
~Toolbar() override;
void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseMove(const boo::SWindowCoord&) override;
void mouseEnter(const boo::SWindowCoord&) override;
void mouseLeave(const boo::SWindowCoord& coord) override;
void resized(const boo::SWindowRect& rootView, const boo::SWindowRect& sub) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
int nominalHeight() const override { return m_nomGauge; }
void clear() {
for (std::vector<ViewChild<View*>>& u : m_children)
u.clear();
}
void push_back(View* v, unsigned unit);
void setMultiplyColor(const zeus::CColor& color) override {
View::setMultiplyColor(color);
for (std::vector<ViewChild<View*>>& u : m_children)
for (ViewChild<View*>& c : u)
if (c.m_view)
c.m_view->setMultiplyColor(color);
}
};
} // namespace specter

View File

@ -0,0 +1,44 @@
#pragma once
#include <memory>
#include <string>
#include "specter/View.hpp"
#include <hecl/UniformBufferPool.hpp>
namespace specter {
class MultiLineTextView;
class TextView;
class Tooltip : public View {
ViewBlock m_ttBlock;
hecl::UniformBufferPool<ViewBlock>::Token m_ttBlockBuf;
SolidShaderVert m_ttVerts[16];
int m_nomWidth = 25;
int m_nomHeight = 25;
void setVerts(int width, int height, float pf);
VertexBufferBindingSolid m_vertsBinding;
std::string m_titleStr;
std::unique_ptr<TextView> m_title;
std::string m_messageStr;
std::unique_ptr<MultiLineTextView> m_message;
std::unique_ptr<TextView> m_cornersOutline[4];
std::unique_ptr<TextView> m_cornersFilled[4];
public:
Tooltip(ViewResources& res, View& parentView, std::string_view title, std::string_view message);
~Tooltip() override;
void resized(const boo::SWindowRect& rootView, const boo::SWindowRect& sub) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
int nominalWidth() const override { return m_nomWidth; }
int nominalHeight() const override { return m_nomHeight; }
};
} // namespace specter

View File

@ -0,0 +1,354 @@
#pragma once
#include <algorithm>
#include <iterator>
#include <optional>
#include <type_traits>
#include <utility>
#include <boo/BooObject.hpp>
#include <boo/IWindow.hpp>
#include <boo/graphicsdev/IGraphicsDataFactory.hpp>
#include <hecl/UniformBufferPool.hpp>
#include <hecl/VertexBufferPool.hpp>
#include <zeus/CColor.hpp>
#include <zeus/CMatrix4f.hpp>
#include <zeus/CTransform.hpp>
#include <zeus/CVector3f.hpp>
namespace boo {
struct IGraphicsCommandQueue;
}
namespace specter {
class IThemeData;
class RootView;
class Space;
class SplitView;
class ViewResources;
extern zeus::CMatrix4f g_PlatformMatrix;
class RectangleConstraint {
public:
enum class Test { Fixed, Minimum, Maximum };
private:
int m_x, m_y;
Test m_xtest, m_ytest;
public:
RectangleConstraint(int x = -1, int y = -1, Test xtest = Test::Fixed, Test ytest = Test::Fixed)
: m_x(x), m_y(y), m_xtest(xtest), m_ytest(ytest) {}
std::pair<int, int> solve(int x, int y) const {
std::pair<int, int> ret;
if (m_x < 0)
ret.first = x;
else {
switch (m_xtest) {
case Test::Fixed:
ret.first = m_x;
break;
case Test::Minimum:
ret.first = std::max(m_x, x);
break;
case Test::Maximum:
ret.first = std::min(m_x, x);
break;
}
}
if (m_y < 0)
ret.second = y;
else {
switch (m_ytest) {
case Test::Fixed:
ret.second = m_y;
break;
case Test::Minimum:
ret.second = std::max(m_y, y);
break;
case Test::Maximum:
ret.second = std::min(m_y, y);
break;
}
}
return ret;
}
};
class View {
public:
struct SolidShaderVert {
zeus::CVector3f m_pos;
zeus::CColor m_color = zeus::skClear;
};
struct TexShaderVert {
zeus::CVector3f m_pos;
zeus::CVector2f m_uv;
};
struct ViewBlock {
zeus::CMatrix4f m_mv;
zeus::CColor m_color = zeus::skWhite;
void setViewRect(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
m_mv[0][0] = 2.0f / root.size[0];
m_mv[1][1] = 2.0f / root.size[1];
m_mv[3][0] = sub.location[0] * m_mv[0][0] - 1.0f;
m_mv[3][1] = sub.location[1] * m_mv[1][1] - 1.0f;
}
void finalAssign(const ViewBlock& other) {
m_mv = g_PlatformMatrix * other.m_mv;
m_color = other.m_color;
}
};
template <typename VertStruct>
struct VertexBufferBinding {
typename hecl::VertexBufferPool<VertStruct>::Token m_vertsBuf;
boo::ObjToken<boo::IShaderDataBinding> m_shaderBinding;
void load(const VertStruct* data, size_t count) {
if (m_vertsBuf) {
VertStruct* const out = m_vertsBuf.access();
std::copy(data, data + count, out);
}
}
template <typename ContiguousContainer>
void load(const ContiguousContainer& container) {
// All contiguous containers (even those that aren't containers like C arrays) are usable
// with std::begin(). Because of that, we can use it to deduce the contained type.
static_assert(std::is_same_v<std::remove_reference_t<decltype(*std::begin(std::declval<ContiguousContainer&>()))>,
VertStruct>,
"Supplied container doesn't contain same type of vertex struct");
load(std::data(container), std::size(container));
}
operator const boo::ObjToken<boo::IShaderDataBinding>&() { return m_shaderBinding; }
};
struct VertexBufferBindingSolid : VertexBufferBinding<SolidShaderVert> {
void init(boo::IGraphicsDataFactory::Context& ctx, ViewResources& res, size_t count,
const hecl::UniformBufferPool<ViewBlock>::Token& viewBlockBuf);
};
struct VertexBufferBindingTex : VertexBufferBinding<TexShaderVert> {
void init(boo::IGraphicsDataFactory::Context& ctx, ViewResources& res, size_t count,
const hecl::UniformBufferPool<ViewBlock>::Token& viewBlockBuf,
const boo::ObjToken<boo::ITexture>& texture);
};
private:
RootView& m_rootView;
View& m_parentView;
boo::SWindowRect m_subRect;
VertexBufferBindingSolid m_bgVertsBinding;
SolidShaderVert m_bgRect[4];
friend class RootView;
View(ViewResources& res);
protected:
ViewBlock m_viewVertBlock;
hecl::UniformBufferPool<ViewBlock>::Token m_viewVertBlockBuf;
public:
struct Resources {
hecl::UniformBufferPool<ViewBlock> m_bufPool;
hecl::VertexBufferPool<SolidShaderVert> m_solidPool;
hecl::VertexBufferPool<TexShaderVert> m_texPool;
void updateBuffers() {
m_bufPool.updateBuffers();
m_solidPool.updateBuffers();
m_texPool.updateBuffers();
}
boo::ObjToken<boo::IShaderPipeline> m_solidShader;
boo::ObjToken<boo::IShaderPipeline> m_texShader;
void init(boo::IGraphicsDataFactory::Context& ctx, const IThemeData& theme);
void destroy() {
m_bufPool.doDestroy();
m_solidPool.doDestroy();
m_texPool.doDestroy();
m_solidShader.reset();
m_texShader.reset();
}
};
protected:
View(ViewResources& res, View& parentView);
void buildResources(boo::IGraphicsDataFactory::Context& ctx, ViewResources& res);
void commitResources(ViewResources& res, const boo::FactoryCommitFunc& commitFunc);
public:
virtual ~View() {}
View() = delete;
View(const View& other) = delete;
View& operator=(const View& other) = delete;
View& parentView() { return m_parentView; }
RootView& rootView() { return m_rootView; }
const RootView& rootView() const { return m_rootView; }
const boo::SWindowRect& subRect() const { return m_subRect; }
int width() const { return m_subRect.size[0]; }
int height() const { return m_subRect.size[1]; }
void updateSize();
void setBackground(const zeus::CColor& color) {
for (int i = 0; i < 4; ++i)
m_bgRect[i].m_color = color;
m_bgVertsBinding.load<decltype(m_bgRect)>(m_bgRect);
}
virtual void setMultiplyColor(const zeus::CColor& color) {
m_viewVertBlock.m_color = color;
if (m_viewVertBlockBuf)
m_viewVertBlockBuf.access().finalAssign(m_viewVertBlock);
}
virtual int nominalWidth() const { return 0; }
virtual int nominalHeight() const { return 0; }
virtual void setActive(bool) {}
virtual void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) {}
virtual void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) {}
virtual void mouseMove(const boo::SWindowCoord&) {}
virtual void mouseEnter(const boo::SWindowCoord&) {}
virtual void mouseLeave(const boo::SWindowCoord&) {}
virtual void scroll(const boo::SWindowCoord&, const boo::SScrollDelta&) {}
virtual void touchDown(const boo::STouchCoord&, uintptr_t) {}
virtual void touchUp(const boo::STouchCoord&, uintptr_t) {}
virtual void touchMove(const boo::STouchCoord&, uintptr_t) {}
virtual void charKeyDown(unsigned long, boo::EModifierKey, bool) {}
virtual void charKeyUp(unsigned long, boo::EModifierKey) {}
virtual void specialKeyDown(boo::ESpecialKey, boo::EModifierKey, bool) {}
virtual void specialKeyUp(boo::ESpecialKey, boo::EModifierKey) {}
virtual void modKeyDown(boo::EModifierKey, bool) {}
virtual void modKeyUp(boo::EModifierKey) {}
virtual void containerResized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {}
virtual void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub);
virtual void resized(const ViewBlock& vb, const boo::SWindowRect& sub);
virtual void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub, const boo::SWindowRect& scissor) {
resized(root, sub);
}
virtual void think() {}
virtual void draw(boo::IGraphicsCommandQueue* gfxQ);
virtual bool isSpace() const { return false; }
virtual bool isSplitView() const { return false; }
Space* castToSpace();
SplitView* castToSplitView();
};
template <class ViewPtrType>
struct ViewChild {
ViewPtrType m_view = ViewPtrType();
bool m_mouseIn = false;
int m_mouseDown = 0;
bool mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
if (!m_view)
return false;
if (m_view->subRect().coordInRect(coord)) {
if ((m_mouseDown & 1 << int(button)) == 0) {
m_view->mouseDown(coord, button, mod);
m_mouseDown |= 1 << int(button);
}
return true;
}
return false;
}
void mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
if (!m_view)
return;
if ((m_mouseDown & 1 << int(button)) != 0) {
m_view->mouseUp(coord, button, mod);
m_mouseDown &= ~(1 << int(button));
}
}
void mouseMove(const boo::SWindowCoord& coord) {
if (!m_view)
return;
if (m_view->subRect().coordInRect(coord)) {
if (!m_mouseIn) {
m_view->mouseEnter(coord);
m_mouseIn = true;
}
m_view->mouseMove(coord);
} else {
if (m_mouseIn) {
m_view->mouseLeave(coord);
m_mouseIn = false;
}
}
}
void mouseEnter(const boo::SWindowCoord& coord) {
if (!m_view)
return;
}
void mouseLeave(const boo::SWindowCoord& coord) {
if (!m_view)
return;
if (m_mouseIn) {
m_view->mouseLeave(coord);
m_mouseIn = false;
}
}
void scroll(const boo::SWindowCoord& coord, const boo::SScrollDelta& scroll) {
if (!m_view)
return;
if (m_mouseIn)
m_view->scroll(coord, scroll);
}
};
template <class ViewPtrType>
struct ScissorViewChild : ViewChild<ViewPtrType> {
using base = ViewChild<ViewPtrType>;
boo::SWindowRect m_scissorRect;
bool mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
if (!base::m_view)
return false;
if (base::m_view->subRect().coordInRect(coord) && m_scissorRect.coordInRect(coord)) {
if ((base::m_mouseDown & 1 << int(button)) == 0) {
base::m_view->mouseDown(coord, button, mod);
base::m_mouseDown |= 1 << int(button);
}
return true;
}
return false;
}
void mouseMove(const boo::SWindowCoord& coord) {
if (!base::m_view)
return;
if (base::m_view->subRect().coordInRect(coord) && m_scissorRect.coordInRect(coord)) {
if (!base::m_mouseIn) {
base::m_view->mouseEnter(coord);
base::m_mouseIn = true;
}
base::m_view->mouseMove(coord);
} else {
if (base::m_mouseIn) {
base::m_view->mouseLeave(coord);
base::m_mouseIn = false;
}
}
}
};
} // namespace specter

View File

@ -0,0 +1,211 @@
#pragma once
#include <atomic>
#include <thread>
#include "specter/Button.hpp"
#include "specter/SplitView.hpp"
#include "specter/TextView.hpp"
#include "specter/Toolbar.hpp"
#include <zeus/CColor.hpp>
namespace specter {
class IThemeData {
public:
virtual const zeus::CColor& uiText() const = 0;
virtual const zeus::CColor& uiAltText() const = 0;
virtual const zeus::CColor& fieldText() const = 0;
virtual const zeus::CColor& fieldMarkedText() const = 0;
virtual const zeus::CColor& selectedFieldText() const = 0;
virtual const zeus::CColor& viewportBackground() const = 0;
virtual const zeus::CColor& toolbarBackground() const = 0;
virtual const zeus::CColor& tooltipBackground() const = 0;
virtual const zeus::CColor& spaceBackground() const = 0;
virtual const zeus::CColor& splashBackground() const = 0;
virtual const zeus::CColor& splashErrorBackground() const = 0;
virtual const zeus::CColor& splash1() const = 0;
virtual const zeus::CColor& splash2() const = 0;
virtual const zeus::CColor& button1Inactive() const = 0;
virtual const zeus::CColor& button2Inactive() const = 0;
virtual const zeus::CColor& button1Hover() const = 0;
virtual const zeus::CColor& button2Hover() const = 0;
virtual const zeus::CColor& button1Press() const = 0;
virtual const zeus::CColor& button2Press() const = 0;
virtual const zeus::CColor& button1Disabled() const = 0;
virtual const zeus::CColor& button2Disabled() const = 0;
virtual const zeus::CColor& textfield1Inactive() const = 0;
virtual const zeus::CColor& textfield2Inactive() const = 0;
virtual const zeus::CColor& textfield1Hover() const = 0;
virtual const zeus::CColor& textfield2Hover() const = 0;
virtual const zeus::CColor& textfield1Disabled() const = 0;
virtual const zeus::CColor& textfield2Disabled() const = 0;
virtual const zeus::CColor& textfieldSelection() const = 0;
virtual const zeus::CColor& textfieldMarkSelection() const = 0;
virtual const zeus::CColor& tableCellBg1() const = 0;
virtual const zeus::CColor& tableCellBg2() const = 0;
virtual const zeus::CColor& tableCellBgSelected() const = 0;
virtual const zeus::CColor& scrollIndicator() const = 0;
virtual const zeus::CColor& spaceTriangleShading1() const = 0;
virtual const zeus::CColor& spaceTriangleShading2() const = 0;
};
class DefaultThemeData : public IThemeData {
zeus::CColor m_uiText = zeus::skWhite;
zeus::CColor m_uiAltText = zeus::skGrey;
zeus::CColor m_fieldText = zeus::skBlack;
zeus::CColor m_fieldMarkedText = {0.25, 0.25, 0.25, 1.0};
zeus::CColor m_selectedFieldText = zeus::skWhite;
zeus::CColor m_vpBg = {0.2, 0.2, 0.2, 1.0};
zeus::CColor m_tbBg = {0.2, 0.2, 0.2, 0.9};
zeus::CColor m_tooltipBg = {0.1, 0.1, 0.1, 0.85};
zeus::CColor m_spaceBg = {0.075, 0.075, 0.075, 0.85};
zeus::CColor m_splashBg = {0.075, 0.075, 0.075, 0.85};
zeus::CColor m_splashErrorBg = {0.1, 0.01, 0.01, 0.85};
zeus::CColor m_splash1 = {1.0, 1.0, 1.0, 1.0};
zeus::CColor m_splash2 = {0.3, 0.3, 0.3, 1.0};
zeus::CColor m_button1Inactive = {0.2823, 0.2823, 0.2823, 1.0};
zeus::CColor m_button2Inactive = {0.1725, 0.1725, 0.1725, 1.0};
zeus::CColor m_button1Hover = {0.3523, 0.3523, 0.3523, 1.0};
zeus::CColor m_button2Hover = {0.2425, 0.2425, 0.2425, 1.0};
zeus::CColor m_button1Press = {0.1725, 0.1725, 0.1725, 1.0};
zeus::CColor m_button2Press = {0.2823, 0.2823, 0.2823, 1.0};
zeus::CColor m_button1Disabled = {0.2823, 0.2823, 0.2823, 0.5};
zeus::CColor m_button2Disabled = {0.1725, 0.1725, 0.1725, 0.5};
zeus::CColor m_textfield2Inactive = {0.7823, 0.7823, 0.7823, 1.0};
zeus::CColor m_textfield1Inactive = {0.5725, 0.5725, 0.5725, 1.0};
zeus::CColor m_textfield2Hover = {0.8523, 0.8523, 0.8523, 1.0};
zeus::CColor m_textfield1Hover = {0.6425, 0.6425, 0.6425, 1.0};
zeus::CColor m_textfield2Disabled = {0.7823, 0.7823, 0.7823, 0.5};
zeus::CColor m_textfield1Disabled = {0.5725, 0.5725, 0.5725, 0.5};
zeus::CColor m_textfieldSelection = {0.2725, 0.2725, 0.2725, 1.0};
zeus::CColor m_textfieldMarkSelection = {1.0, 1.0, 0.2725, 1.0};
zeus::CColor m_tableCellBg1 = {0.1725, 0.1725, 0.1725, 0.75};
zeus::CColor m_tableCellBg2 = {0.2425, 0.2425, 0.2425, 0.75};
zeus::CColor m_tableCellBgSelected = {0.6425, 0.6425, 0.6425, 1.0};
zeus::CColor m_scrollIndicator = {0.2823, 0.2823, 0.2823, 1.0};
zeus::CColor m_spaceTriangleShading1 = {0.6425, 0.6425, 0.6425, 1.0};
zeus::CColor m_spaceTriangleShading2 = {0.5725, 0.5725, 0.5725, 1.0};
public:
const zeus::CColor& uiText() const override { return m_uiText; }
const zeus::CColor& uiAltText() const override { return m_uiAltText; }
const zeus::CColor& fieldText() const override { return m_fieldText; }
const zeus::CColor& fieldMarkedText() const override { return m_fieldMarkedText; }
const zeus::CColor& selectedFieldText() const override { return m_selectedFieldText; }
const zeus::CColor& viewportBackground() const override { return m_vpBg; }
const zeus::CColor& toolbarBackground() const override { return m_tbBg; }
const zeus::CColor& tooltipBackground() const override { return m_tooltipBg; }
const zeus::CColor& spaceBackground() const override { return m_spaceBg; }
const zeus::CColor& splashBackground() const override { return m_splashBg; }
const zeus::CColor& splashErrorBackground() const override { return m_splashErrorBg; }
const zeus::CColor& splash1() const override { return m_splash1; }
const zeus::CColor& splash2() const override { return m_splash2; }
const zeus::CColor& button1Inactive() const override { return m_button1Inactive; }
const zeus::CColor& button2Inactive() const override { return m_button2Inactive; }
const zeus::CColor& button1Hover() const override { return m_button1Hover; }
const zeus::CColor& button2Hover() const override { return m_button2Hover; }
const zeus::CColor& button1Press() const override { return m_button1Press; }
const zeus::CColor& button2Press() const override { return m_button2Press; }
const zeus::CColor& button1Disabled() const override { return m_button1Disabled; }
const zeus::CColor& button2Disabled() const override { return m_button2Disabled; }
const zeus::CColor& textfield1Inactive() const override { return m_textfield1Inactive; }
const zeus::CColor& textfield2Inactive() const override { return m_textfield2Inactive; }
const zeus::CColor& textfield1Hover() const override { return m_textfield1Hover; }
const zeus::CColor& textfield2Hover() const override { return m_textfield2Hover; }
const zeus::CColor& textfield1Disabled() const override { return m_textfield1Disabled; }
const zeus::CColor& textfield2Disabled() const override { return m_textfield2Disabled; }
const zeus::CColor& textfieldSelection() const override { return m_textfieldSelection; }
const zeus::CColor& textfieldMarkSelection() const override { return m_textfieldMarkSelection; }
const zeus::CColor& tableCellBg1() const override { return m_tableCellBg1; }
const zeus::CColor& tableCellBg2() const override { return m_tableCellBg2; }
const zeus::CColor& tableCellBgSelected() const override { return m_tableCellBgSelected; }
const zeus::CColor& scrollIndicator() const override { return m_scrollIndicator; }
const zeus::CColor& spaceTriangleShading1() const override { return m_spaceTriangleShading1; }
const zeus::CColor& spaceTriangleShading2() const override { return m_spaceTriangleShading2; }
};
class ViewResources {
void init(boo::IGraphicsDataFactory::Context& factory, const IThemeData& theme, FontCache* fcache) {
m_viewRes.init(factory, theme);
m_textRes.init(factory, fcache);
m_splitRes.init(factory, theme);
m_toolbarRes.init(factory, theme);
m_buttonRes.init(factory, theme);
}
public:
boo::IGraphicsDataFactory* m_factory;
FontCache* m_fcache = nullptr;
View::Resources m_viewRes;
TextView::Resources m_textRes;
SplitView::Resources m_splitRes;
Toolbar::Resources m_toolbarRes;
Button::Resources m_buttonRes;
specter::FontTag m_mainFont;
specter::FontTag m_monoFont10;
specter::FontTag m_monoFont18;
specter::FontTag m_heading14;
specter::FontTag m_heading18;
specter::FontTag m_titleFont;
specter::FontTag m_curveFont;
std::thread m_fcacheThread;
std::atomic_bool m_fcacheInterrupt = {false};
std::atomic_bool m_fcacheReady = {false};
ViewResources() = default;
ViewResources(const ViewResources& other) = delete;
ViewResources& operator=(const ViewResources& other) = delete;
void updateBuffers() {
m_viewRes.updateBuffers();
m_textRes.updateBuffers();
}
~ViewResources() {
if (m_fcacheThread.joinable()) {
m_fcacheInterrupt.store(true);
m_fcacheThread.join();
}
}
void init(boo::IGraphicsDataFactory* factory, FontCache* fcache, const IThemeData* theme, float pixelFactor);
void destroyResData();
void prepFontCacheSync();
void prepFontCacheAsync(boo::IWindow* window);
bool fontCacheReady() const { return m_fcacheReady.load(); }
void resetPixelFactor(float pixelFactor);
void resetTheme(const IThemeData* theme);
float m_pixelFactor = 0;
float pixelFactor() const { return m_pixelFactor; }
const IThemeData* m_theme;
const IThemeData& themeData() const { return *m_theme; }
};
} // namespace specter

View File

@ -0,0 +1,9 @@
#pragma once
#if __specter__
#define SPECTER_PROPERTY(n, d) [[using specter: name(n), description(d)]]
#define SPECTER_ENUM(n, d, et) [[using specter: name(n), description(d), enum_type(et)]]
#else
#define SPECTER_PROPERTY(n, d)
#define SPECTER_ENUM(n, d, et)
#endif

View File

@ -0,0 +1,18 @@
#pragma once
#include "View.hpp"
#include "RootView.hpp"
#include "TextView.hpp"
#include "Space.hpp"
#include "Table.hpp"
#include "Outliner.hpp"
#include "Panel.hpp"
#include "Control.hpp"
#include "Button.hpp"
#include "TextField.hpp"
#include "NumericField.hpp"
#include "Menu.hpp"
#include "Node.hpp"
#include "NodeSocket.hpp"
#include "FontCache.hpp"
#include "ViewResources.hpp"

607
specter/lib/Button.cpp Normal file
View File

@ -0,0 +1,607 @@
#include "specter/Button.hpp"
#include "specter/Icon.hpp"
#include "specter/RootView.hpp"
#include "specter/TextView.hpp"
#include "specter/ViewResources.hpp"
#include <boo/graphicsdev/IGraphicsCommandQueue.hpp>
#include <logvisor/logvisor.hpp>
namespace specter {
static logvisor::Module Log("specter::Button");
struct Button::ButtonTarget : View {
Button& m_button;
bool m_pressed = false;
bool m_hovered = false;
void setInactive();
void setHover();
void setPressed();
void setDisabled();
void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseEnter(const boo::SWindowCoord&) override;
void mouseLeave(const boo::SWindowCoord&) override;
ButtonTarget(ViewResources& res, Button& button) : View(res, button), m_button(button) {}
};
struct Button::MenuTarget : View {
Button& m_button;
bool m_pressed = false;
bool m_hovered = false;
void setInactive();
void setHover();
void setPressed();
void setDisabled();
void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseEnter(const boo::SWindowCoord&) override;
void mouseLeave(const boo::SWindowCoord&) override;
MenuTarget(ViewResources& res, Button& button) : View(res, button), m_button(button) {}
};
void Button::Resources::init(boo::IGraphicsDataFactory::Context& ctx, const IThemeData& theme) {}
Button::Button(ViewResources& res, View& parentView, IButtonBinding* controlBinding, std::string_view text, Icon* icon,
Style style, const zeus::CColor& bgColor, RectangleConstraint constraint)
: Button(res, parentView, controlBinding, text, res.themeData().uiText(), icon, style, bgColor, constraint) {}
Button::Button(ViewResources& res, View& parentView, IButtonBinding* controlBinding, std::string_view text,
const zeus::CColor& textColor, Icon* icon, Style style, const zeus::CColor& bgColor,
RectangleConstraint constraint)
: Control(res, parentView, controlBinding)
, m_style(style)
, m_textColor(textColor)
, m_bgColor(bgColor)
, m_textStr(text)
, m_constraint(constraint) {
commitResources(res, [&](boo::IGraphicsDataFactory::Context& ctx) -> bool {
buildResources(ctx, res);
m_vertsBinding.init(ctx, res, 40, m_viewVertBlockBuf);
return true;
});
m_buttonTarget.m_view = std::make_unique<ButtonTarget>(res, *this);
m_menuTarget.m_view = std::make_unique<MenuTarget>(res, *this);
if (style == Style::Block) {
zeus::CColor c1 = res.themeData().button1Inactive() * bgColor;
zeus::CColor c2 = res.themeData().button2Inactive() * bgColor;
m_verts[0].m_color = c1;
m_verts[1].m_color = c2;
m_verts[2].m_color = c1;
m_verts[3].m_color = c2;
m_verts[4].m_color = c2;
for (int i = 5; i < 28; ++i)
m_verts[i].m_color = c2;
m_verts[31].m_color = c1;
m_verts[32].m_color = c2;
m_verts[33].m_color = c1;
m_verts[34].m_color = c2;
for (int i = 35; i < 39; ++i)
m_verts[i].m_color = c2;
} else {
for (int i = 0; i < 4; ++i)
m_verts[i].m_color = zeus::skClear;
for (int i = 31; i < 35; ++i)
m_verts[i].m_color = zeus::skClear;
}
for (int i = 28; i < 31; ++i)
m_verts[i].m_color = m_textColor;
_loadVerts();
if (controlBinding != nullptr) {
m_menuStyle = controlBinding->menuStyle(this);
}
if (icon != nullptr) {
m_icon = std::make_unique<IconView>(res, *this, *icon);
}
m_text = std::make_unique<TextView>(res, *this, res.m_mainFont, TextView::Alignment::Center);
setText(m_textStr);
}
Button::~Button() { closeMenu({}); }
void Button::setText(std::string_view text) { setText(text, m_textColor); }
void Button::setText(std::string_view text, const zeus::CColor& textColor) {
m_textStr = text;
m_textColor = textColor;
m_text->typesetGlyphs(text, textColor);
float pf = rootView().viewRes().pixelFactor();
int width, height;
if (m_style == Style::Block) {
m_textWidth = m_text->nominalWidth();
int nomWidth = m_textWidth + 12 * pf;
if (m_icon)
nomWidth += 18 * pf;
std::pair<int, int> constraint = m_constraint.solve(nomWidth, 20 * pf);
width = constraint.first;
height = constraint.second;
m_verts[0].m_pos.assign(1, height + 1, 0);
m_verts[1].m_pos.assign(1, 1, 0);
m_verts[2].m_pos.assign(width + 1, height + 1, 0);
m_verts[3].m_pos.assign(width + 1, 1, 0);
m_verts[4].m_pos.assign(width + 1, 1, 0);
m_textIconWidth = width;
if (m_menuStyle == IButtonBinding::MenuStyle::Primary)
width += 12 * pf;
else if (m_menuStyle == IButtonBinding::MenuStyle::Auxiliary)
width += 16 * pf;
m_verts[5].m_pos.assign(1, height + 1, 0);
m_verts[6].m_pos.assign(1, height + 1, 0);
m_verts[7].m_pos.assign(0, height + 1, 0);
m_verts[8].m_pos.assign(1, 1, 0);
m_verts[9].m_pos.assign(0, 1, 0);
m_verts[10].m_pos.assign(0, 1, 0);
m_verts[11].m_pos.assign(width + 2, height + 1, 0);
m_verts[12].m_pos.assign(width + 2, height + 1, 0);
m_verts[13].m_pos.assign(width + 1, height + 1, 0);
m_verts[14].m_pos.assign(width + 2, 1, 0);
m_verts[15].m_pos.assign(width + 1, 1, 0);
m_verts[16].m_pos.assign(width + 1, 1, 0);
m_verts[17].m_pos.assign(1, height + 2, 0);
m_verts[18].m_pos.assign(1, height + 2, 0);
m_verts[19].m_pos.assign(1, height + 1, 0);
m_verts[20].m_pos.assign(width + 1, height + 2, 0);
m_verts[21].m_pos.assign(width + 1, height + 1, 0);
m_verts[22].m_pos.assign(width + 1, height + 1, 0);
m_verts[23].m_pos.assign(1, 1, 0);
m_verts[24].m_pos.assign(1, 1, 0);
m_verts[25].m_pos.assign(1, 0, 0);
m_verts[26].m_pos.assign(width + 1, 1, 0);
m_verts[27].m_pos.assign(width + 1, 0, 0);
int arrowX = m_textIconWidth + 5 * pf;
int arrowY = 7 * pf;
int menuBgX = m_textIconWidth;
if (m_menuStyle == IButtonBinding::MenuStyle::Primary) {
menuBgX = 0;
arrowX -= 5 * pf;
}
m_verts[28].m_pos.assign(arrowX + 4 * pf, arrowY + 1 * pf, 0);
m_verts[29].m_pos.assign(arrowX, arrowY + 5 * pf, 0);
m_verts[30].m_pos.assign(arrowX + 8 * pf, arrowY + 5 * pf, 0);
m_verts[31].m_pos.assign(menuBgX + 1, height + 1, 0);
m_verts[32].m_pos.assign(menuBgX + 1, 1, 0);
m_verts[33].m_pos.assign(width + 1, height + 1, 0);
m_verts[34].m_pos.assign(width + 1, 1, 0);
m_verts[35].m_pos.assign(m_textIconWidth, height + 1, 0);
m_verts[36].m_pos.assign(m_textIconWidth, 1, 0);
m_verts[37].m_pos.assign(m_textIconWidth + 1, height + 1, 0);
m_verts[38].m_pos.assign(m_textIconWidth + 1, 1, 0);
_loadVerts();
} else {
width = m_text->nominalWidth();
height = 10 * pf;
m_verts[0].m_pos.assign(1 * pf, -1 * pf, 0);
m_verts[1].m_pos.assign(1 * pf, -2 * pf, 0);
m_verts[2].m_pos.assign(width, -1 * pf, 0);
m_verts[3].m_pos.assign(width, -2 * pf, 0);
int arrowX = width + 5 * pf;
m_verts[28].m_pos.assign(arrowX + 4 * pf, 1 * pf, 0);
m_verts[29].m_pos.assign(arrowX, 5 * pf, 0);
m_verts[30].m_pos.assign(arrowX + 8 * pf, 5 * pf, 0);
m_textWidth = width;
m_textIconWidth = width;
int arrowLineWidth = 7 * pf;
if (m_menuStyle != IButtonBinding::MenuStyle::None) {
width += 13 * pf;
if (m_menuStyle == IButtonBinding::MenuStyle::Primary) {
arrowLineWidth = width;
arrowX = 1 * pf;
}
}
m_verts[31].m_pos.assign(arrowX, -1 * pf, 0);
m_verts[32].m_pos.assign(arrowX, -2 * pf, 0);
m_verts[33].m_pos.assign(arrowX + arrowLineWidth, -1 * pf, 0);
m_verts[34].m_pos.assign(arrowX + arrowLineWidth, -2 * pf, 0);
_loadVerts();
}
m_nomWidth = width;
m_nomHeight = height;
}
void Button::setIcon(Icon* icon) {
if (icon != nullptr) {
m_icon = std::make_unique<IconView>(rootView().viewRes(), *this, *icon);
} else {
m_icon.reset();
}
setText(m_textStr);
updateSize();
}
void Button::colorGlyphs(const zeus::CColor& newColor) {
m_textColor = newColor;
m_text->colorGlyphs(newColor);
for (int i = 28; i < 31; ++i)
m_verts[i].m_color = newColor;
_loadVerts();
}
void Button::ButtonTarget::setInactive() {
if (m_button.m_style == Style::Block) {
zeus::CColor c1 = rootView().themeData().button1Inactive() * m_button.m_bgColor;
zeus::CColor c2 = rootView().themeData().button2Inactive() * m_button.m_bgColor;
m_button.m_verts[0].m_color = c1;
m_button.m_verts[1].m_color = c2;
m_button.m_verts[2].m_color = c1;
m_button.m_verts[3].m_color = c2;
m_button.m_verts[4].m_color = c2;
m_button._loadVerts();
} else {
for (int i = 0; i < 4; ++i)
m_button.m_verts[i].m_color = zeus::skClear;
m_button._loadVerts();
m_button.m_text->colorGlyphs(m_button.m_textColor);
}
}
void Button::MenuTarget::setInactive() {
if (m_button.m_style == Style::Block) {
zeus::CColor c1 = rootView().themeData().button1Inactive() * m_button.m_bgColor;
zeus::CColor c2 = rootView().themeData().button2Inactive() * m_button.m_bgColor;
m_button.m_verts[31].m_color = c1;
m_button.m_verts[32].m_color = c2;
m_button.m_verts[33].m_color = c1;
m_button.m_verts[34].m_color = c2;
m_button._loadVerts();
} else {
for (int i = 28; i < 31; ++i)
m_button.m_verts[i].m_color = m_button.m_textColor;
for (int i = 31; i < 35; ++i)
m_button.m_verts[i].m_color = zeus::skClear;
m_button._loadVerts();
}
}
void Button::ButtonTarget::setHover() {
if (m_button.m_style == Style::Block) {
zeus::CColor c1 = rootView().themeData().button1Hover() * m_button.m_bgColor;
zeus::CColor c2 = rootView().themeData().button2Hover() * m_button.m_bgColor;
m_button.m_verts[0].m_color = c1;
m_button.m_verts[1].m_color = c2;
m_button.m_verts[2].m_color = c1;
m_button.m_verts[3].m_color = c2;
m_button.m_verts[4].m_color = c2;
m_button._loadVerts();
} else {
for (int i = 0; i < 4; ++i)
m_button.m_verts[i].m_color = m_button.m_textColor;
m_button._loadVerts();
m_button.m_text->colorGlyphs(m_button.m_textColor);
}
}
void Button::MenuTarget::setHover() {
if (m_button.m_style == Style::Block) {
zeus::CColor c1 = rootView().themeData().button1Hover() * m_button.m_bgColor;
zeus::CColor c2 = rootView().themeData().button2Hover() * m_button.m_bgColor;
m_button.m_verts[31].m_color = c1;
m_button.m_verts[32].m_color = c2;
m_button.m_verts[33].m_color = c1;
m_button.m_verts[34].m_color = c2;
m_button._loadVerts();
} else {
for (int i = 28; i < 31; ++i)
m_button.m_verts[i].m_color = m_button.m_textColor;
for (int i = 31; i < 35; ++i)
m_button.m_verts[i].m_color = m_button.m_textColor;
m_button._loadVerts();
}
}
void Button::ButtonTarget::setPressed() {
if (m_button.m_style == Style::Block) {
zeus::CColor c1 = rootView().themeData().button1Press() * m_button.m_bgColor;
zeus::CColor c2 = rootView().themeData().button2Press() * m_button.m_bgColor;
m_button.m_verts[0].m_color = c1;
m_button.m_verts[1].m_color = c2;
m_button.m_verts[2].m_color = c1;
m_button.m_verts[3].m_color = c2;
m_button.m_verts[4].m_color = c2;
m_button._loadVerts();
} else {
for (int i = 0; i < 4; ++i)
m_button.m_verts[i].m_color = m_button.m_textColor;
m_button._loadVerts();
m_button.m_text->colorGlyphs(m_button.m_textColor);
}
}
void Button::MenuTarget::setPressed() {
if (m_button.m_style == Style::Block) {
zeus::CColor c1 = rootView().themeData().button1Press() * m_button.m_bgColor;
zeus::CColor c2 = rootView().themeData().button2Press() * m_button.m_bgColor;
m_button.m_verts[31].m_color = c1;
m_button.m_verts[32].m_color = c2;
m_button.m_verts[33].m_color = c1;
m_button.m_verts[34].m_color = c2;
m_button._loadVerts();
} else {
for (int i = 28; i < 31; ++i)
m_button.m_verts[i].m_color = m_button.m_textColor;
for (int i = 31; i < 35; ++i)
m_button.m_verts[i].m_color = m_button.m_textColor;
m_button._loadVerts();
}
}
void Button::ButtonTarget::setDisabled() {
if (m_button.m_style == Style::Block) {
zeus::CColor c1 = rootView().themeData().button1Disabled() * m_button.m_bgColor;
zeus::CColor c2 = rootView().themeData().button2Disabled() * m_button.m_bgColor;
m_button.m_verts[0].m_color = c1;
m_button.m_verts[1].m_color = c2;
m_button.m_verts[2].m_color = c1;
m_button.m_verts[3].m_color = c2;
m_button.m_verts[4].m_color = c2;
m_button._loadVerts();
} else {
for (int i = 0; i < 4; ++i)
m_button.m_verts[i].m_color = zeus::skClear;
m_button._loadVerts();
zeus::CColor dimText = m_button.m_textColor;
dimText[3] *= 0.5;
m_button.m_text->colorGlyphs(dimText);
}
}
void Button::MenuTarget::setDisabled() {
if (m_button.m_style == Style::Block) {
zeus::CColor c1 = rootView().themeData().button1Disabled() * m_button.m_bgColor;
zeus::CColor c2 = rootView().themeData().button2Disabled() * m_button.m_bgColor;
m_button.m_verts[31].m_color = c1;
m_button.m_verts[32].m_color = c2;
m_button.m_verts[33].m_color = c1;
m_button.m_verts[34].m_color = c2;
m_button._loadVerts();
} else {
zeus::CColor dimText = m_button.m_textColor;
dimText[3] *= 0.5;
for (int i = 28; i < 31; ++i)
m_button.m_verts[i].m_color = dimText;
for (int i = 31; i < 35; ++i)
m_button.m_verts[i].m_color = zeus::skClear;
m_button._loadVerts();
}
}
void Button::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
if (m_menuStyle != IButtonBinding::MenuStyle::Primary)
m_buttonTarget.mouseDown(coord, button, mod);
m_menuTarget.mouseDown(coord, button, mod);
}
void Button::ButtonTarget::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
m_pressed = true;
setPressed();
if (m_button.m_controlBinding)
static_cast<IButtonBinding&>(*m_button.m_controlBinding).down(&m_button, coord);
}
void Button::MenuTarget::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
m_pressed = true;
setPressed();
if (m_hovered) {
Log.report(logvisor::Info, FMT_STRING("button menu '{}' activated"), m_button.m_textStr);
if (m_button.m_controlBinding) {
m_button.m_modalMenu.m_view = static_cast<IButtonBinding&>(*m_button.m_controlBinding).buildMenu(&m_button);
rootView().setActiveMenuButton(&m_button);
m_button.updateSize();
}
}
}
void Button::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
if (m_menuStyle != IButtonBinding::MenuStyle::Primary)
m_buttonTarget.mouseUp(coord, button, mod);
m_menuTarget.mouseUp(coord, button, mod);
}
void Button::ButtonTarget::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
if (m_pressed) {
if (m_button.m_controlBinding)
static_cast<IButtonBinding&>(*m_button.m_controlBinding).up(&m_button, coord);
if (m_hovered) {
Log.report(logvisor::Info, FMT_STRING("button '{}' activated"), m_button.m_textStr);
if (m_button.m_controlBinding)
static_cast<IButtonBinding&>(*m_button.m_controlBinding).activated(&m_button, coord);
}
m_pressed = false;
}
if (m_hovered)
setHover();
else
setInactive();
}
void Button::MenuTarget::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
m_pressed = false;
if (m_hovered)
setHover();
else
setInactive();
}
void Button::mouseMove(const boo::SWindowCoord& coord) {
if (m_menuStyle != IButtonBinding::MenuStyle::Primary)
m_buttonTarget.mouseMove(coord);
m_menuTarget.mouseMove(coord);
}
void Button::ButtonTarget::mouseEnter(const boo::SWindowCoord& coord) {
m_hovered = true;
if (m_pressed)
setPressed();
else
setHover();
}
void Button::MenuTarget::mouseEnter(const boo::SWindowCoord& coord) {
m_hovered = true;
if (m_pressed)
setPressed();
else
setHover();
}
void Button::mouseLeave(const boo::SWindowCoord& coord) {
if (m_menuStyle != IButtonBinding::MenuStyle::Primary)
m_buttonTarget.mouseLeave(coord);
m_menuTarget.mouseLeave(coord);
}
void Button::ButtonTarget::mouseLeave(const boo::SWindowCoord& coord) {
m_hovered = false;
setInactive();
}
void Button::MenuTarget::mouseLeave(const boo::SWindowCoord& coord) {
m_hovered = false;
setInactive();
}
void Button::closeMenu(const boo::SWindowCoord& coord) {
rootView().unsetActiveMenuButton(this);
m_modalMenu.m_view.reset();
m_menuTarget.mouseMove(coord);
}
void Button::setMultiplyColor(const zeus::CColor& color) {
View::setMultiplyColor(color);
m_viewVertBlock.m_color = color;
if (m_viewVertBlockBuf)
m_viewVertBlockBuf.access().finalAssign(m_viewVertBlock);
m_text->setMultiplyColor(color);
if (m_icon)
m_icon->setMultiplyColor(color);
}
void Button::think() {
if (m_modalMenu.m_view)
m_modalMenu.m_view->think();
}
void Button::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
View::resized(root, sub);
boo::SWindowRect textRect = sub;
float pf = rootView().viewRes().pixelFactor();
if (m_icon) {
textRect.location[0] += 18 * pf;
boo::SWindowRect iconRect = sub;
iconRect.size[0] = 16 * pf;
iconRect.size[1] = 16 * pf;
iconRect.location[1] += 2 * pf;
if (m_style == Style::Block) {
iconRect.location[0] += 5 * pf;
iconRect.location[1] += pf;
}
m_icon->resized(root, iconRect);
}
if (m_style == Style::Block) {
textRect.location[0] += 6 * pf;
textRect.location[1] += 7 * pf;
}
textRect.location[0] += m_textWidth / 2;
textRect.size[0] = m_textWidth;
textRect.size[1] = m_nomHeight;
m_text->resized(root, textRect);
if (m_style == Style::Block) {
if (m_menuStyle == IButtonBinding::MenuStyle::None) {
m_buttonTarget.m_view->resized(root, sub);
} else if (m_menuStyle == IButtonBinding::MenuStyle::Primary) {
m_menuTarget.m_view->resized(root, sub);
} else {
boo::SWindowRect targetRect = sub;
targetRect.size[0] = m_textIconWidth;
m_buttonTarget.m_view->resized(root, targetRect);
targetRect.location[0] += targetRect.size[0];
targetRect.size[0] = 16 * pf;
m_menuTarget.m_view->resized(root, targetRect);
}
} else {
if (m_menuStyle == IButtonBinding::MenuStyle::Primary) {
boo::SWindowRect targetRect = sub;
targetRect.size[0] = m_nomWidth;
targetRect.size[1] = m_nomHeight;
m_menuTarget.m_view->resized(root, targetRect);
} else {
boo::SWindowRect targetRect = sub;
targetRect.size[0] = m_textIconWidth + 3 * pf;
targetRect.size[1] = m_nomHeight;
m_buttonTarget.m_view->resized(root, targetRect);
targetRect.location[0] += targetRect.size[0];
targetRect.size[0] = 15 * pf;
m_menuTarget.m_view->resized(root, targetRect);
}
}
if (m_modalMenu.m_view) {
boo::SWindowRect menuRect = sub;
if (m_style == Style::Text)
menuRect.location[1] -= 6 * pf;
m_modalMenu.m_view->resized(root, menuRect);
}
}
void Button::draw(boo::IGraphicsCommandQueue* gfxQ) {
View::draw(gfxQ);
gfxQ->setShaderDataBinding(m_vertsBinding);
if (m_style == Style::Block) {
gfxQ->draw(0, 28);
if (m_menuStyle != IButtonBinding::MenuStyle::None) {
gfxQ->draw(31, 4);
gfxQ->draw(28, 3);
if (m_menuStyle == IButtonBinding::MenuStyle::Auxiliary)
gfxQ->draw(35, 4);
}
} else {
gfxQ->draw(0, 4);
if (m_menuStyle != IButtonBinding::MenuStyle::None) {
gfxQ->draw(28, 3);
gfxQ->draw(31, 4);
}
}
if (m_icon)
m_icon->draw(gfxQ);
if (m_textStr.size())
m_text->draw(gfxQ);
if (m_modalMenu.m_view)
m_modalMenu.m_view->draw(gfxQ);
}
} // namespace specter

16
specter/lib/Control.cpp Normal file
View File

@ -0,0 +1,16 @@
#include "specter/Control.hpp"
#include <hecl/CVar.hpp>
namespace specter {
std::string_view CVarControlBinding::name([[maybe_unused]] const Control* control) const { return m_cvar->name(); }
std::string_view CVarControlBinding::help([[maybe_unused]] const Control* control) const { return m_cvar->rawHelp(); }
Control::Control(ViewResources& res, View& parentView, IControlBinding* controlBinding)
: View(res, parentView), m_controlBinding(controlBinding) {}
std::recursive_mutex ITextInputView::m_textInputLk;
} // namespace specter

599
specter/lib/FileBrowser.cpp Normal file
View File

@ -0,0 +1,599 @@
#include "specter/FileBrowser.hpp"
#include "specter/Button.hpp"
#include "specter/IViewManager.hpp"
#include "specter/MessageWindow.hpp"
#include "specter/RootView.hpp"
#include "specter/TextField.hpp"
#include <hecl/hecl.hpp>
namespace specter {
#define BROWSER_MARGIN 8
#define BROWSER_MIN_WIDTH 600
#define BROWSER_MIN_HEIGHT 300
std::vector<hecl::SystemString> FileBrowser::PathComponents(hecl::SystemStringView path) {
std::vector<hecl::SystemString> ret;
hecl::SystemString sPath(path);
hecl::SanitizePath(sPath);
if (sPath.empty())
return ret;
auto it = sPath.cbegin();
if (*it == _SYS_STR('/')) {
ret.push_back(_SYS_STR("/"));
++it;
}
hecl::SystemString comp;
for (; it != sPath.cend(); ++it) {
if (*it == _SYS_STR('/')) {
if (comp.empty())
continue;
ret.push_back(std::move(comp));
comp.clear();
continue;
}
comp += *it;
}
if (comp.size())
ret.push_back(std::move(comp));
return ret;
}
FileBrowser::FileBrowser(ViewResources& res, View& parentView, std::string_view title, Type type,
std::function<void(bool, hecl::SystemStringView)> returnFunc)
: FileBrowser(res, parentView, title, type, hecl::GetcwdStr(), std::move(returnFunc)) {}
FileBrowser::FileBrowser(ViewResources& res, View& parentView, std::string_view title, Type type,
hecl::SystemStringView initialPath,
std::function<void(bool, hecl::SystemStringView)> returnFunc)
: ModalWindow(res, parentView,
RectangleConstraint(BROWSER_MIN_WIDTH * res.pixelFactor(), BROWSER_MIN_HEIGHT * res.pixelFactor(),
RectangleConstraint::Test::Minimum, RectangleConstraint::Test::Minimum))
, m_type(type)
, m_left(*this, res)
, m_right(*this, res)
, m_ok(*this, res, title)
, m_cancel(*this, res, rootView().viewManager().translate<locale::cancel>())
, m_fileFieldBind(*this, rootView().viewManager())
, m_fileListingBind(*this, rootView().viewManager())
, m_systemBookmarkBind(*this)
, m_projectBookmarkBind(*this)
, m_recentBookmarkBind(*this)
, m_returnFunc(std::move(returnFunc)) {
setBackground({0, 0, 0, 0.5});
IViewManager& vm = rootView().viewManager();
m_pathButtons.m_view = std::make_unique<PathButtons>(res, *this, *this);
m_fileField.m_view = std::make_unique<TextField>(res, *this, &m_fileFieldBind);
m_fileListing.m_view = std::make_unique<Table>(res, *this, &m_fileListingBind, &m_fileListingBind, 3);
m_systemBookmarks.m_view = std::make_unique<Table>(res, *this, &m_systemBookmarkBind, &m_systemBookmarkBind, 1);
m_systemBookmarksLabel = std::make_unique<TextView>(res, *this, res.m_mainFont);
m_systemBookmarksLabel->typesetGlyphs(vm.translate<locale::system_locations>(), res.themeData().uiText());
m_projectBookmarks.m_view = std::make_unique<Table>(res, *this, &m_projectBookmarkBind, &m_projectBookmarkBind, 1);
m_projectBookmarksLabel = std::make_unique<TextView>(res, *this, res.m_mainFont);
m_projectBookmarksLabel->typesetGlyphs(vm.translate<locale::recent_projects>(), res.themeData().uiText());
m_recentBookmarks.m_view = std::make_unique<Table>(res, *this, &m_recentBookmarkBind, &m_recentBookmarkBind, 1);
m_recentBookmarksLabel = std::make_unique<TextView>(res, *this, res.m_mainFont);
m_recentBookmarksLabel->typesetGlyphs(vm.translate<locale::recent_files>(), res.themeData().uiText());
/* Populate system bookmarks */
std::vector<std::pair<hecl::SystemString, std::string>> systemLocs = hecl::GetSystemLocations();
for (auto& loc : systemLocs)
m_systemBookmarkBind.m_entries.emplace_back(std::move(loc));
m_systemBookmarks.m_view->updateData();
const std::vector<hecl::SystemString>* recentProjects = vm.recentProjects();
for (auto& proj : *recentProjects)
m_projectBookmarkBind.m_entries.emplace_back(proj);
m_projectBookmarks.m_view->updateData();
const std::vector<hecl::SystemString>* recentFiles = vm.recentFiles();
for (auto& file : *recentFiles)
m_recentBookmarkBind.m_entries.emplace_back(file);
m_recentBookmarks.m_view->updateData();
navigateToPath(initialPath);
m_split.m_view = std::make_unique<SplitView>(res, *this, nullptr, SplitView::Axis::Vertical, 0.2,
200 * res.pixelFactor(), 400 * res.pixelFactor());
m_split.m_view->setContentView(0, &m_left);
m_split.m_view->setContentView(1, &m_right);
updateContentOpacity(0.0);
}
FileBrowser::~FileBrowser() = default;
void FileBrowser::SyncBookmarkSelections(Table& table, BookmarkDataBind& binding, const hecl::SystemString& sel) {
size_t idx = 0;
for (const BookmarkDataBind::Entry& e : binding.m_entries) {
if (e.m_path == sel) {
table.selectRow(idx);
break;
}
++idx;
}
if (idx == binding.m_entries.size())
table.selectRow(-1);
}
void FileBrowser::navigateToPath(hecl::SystemStringView path) {
hecl::Sstat theStat;
if (hecl::Stat(path.data(), &theStat))
return;
m_path = hecl::SystemString(path);
m_comps = PathComponents(m_path);
if (S_ISREG(theStat.st_mode)) {
hecl::SystemUTF8Conv utf8(m_comps.back());
m_fileField.m_view->setText(utf8.str());
m_fileField.m_view->clearErrorState();
m_comps.pop_back();
}
hecl::SystemString dir;
bool needSlash = false;
for (const hecl::SystemString& d : m_comps) {
if (needSlash)
dir += _SYS_STR('/');
if (d != _SYS_STR("/"))
needSlash = true;
dir += d;
}
SyncBookmarkSelections(*m_systemBookmarks.m_view, m_systemBookmarkBind, dir);
SyncBookmarkSelections(*m_projectBookmarks.m_view, m_projectBookmarkBind, dir);
SyncBookmarkSelections(*m_recentBookmarks.m_view, m_recentBookmarkBind, dir);
hecl::DirectoryEnumerator dEnum(dir, hecl::DirectoryEnumerator::Mode::DirsThenFilesSorted,
m_fileListingBind.m_sizeSort,
m_fileListingBind.m_sortDir == SortDirection::Descending, !m_showingHidden);
m_fileListingBind.updateListing(dEnum);
m_fileListing.m_view->selectRow(-1);
m_fileListing.m_view->updateData();
m_pathButtons.m_view->setButtons(m_comps);
updateSize();
}
void FileBrowser::updateContentOpacity(float opacity) {
zeus::CColor color = zeus::CColor::lerp({1, 1, 1, 0}, {1, 1, 1, 1}, opacity);
m_split.m_view->setMultiplyColor(color);
m_pathButtons.m_view->setMultiplyColor(color);
m_fileField.m_view->setMultiplyColor(color);
m_fileListing.m_view->setMultiplyColor(color);
m_ok.m_button.m_view->setMultiplyColor(color);
m_cancel.m_button.m_view->setMultiplyColor(color);
m_systemBookmarks.m_view->setMultiplyColor(color);
m_systemBookmarksLabel->setMultiplyColor(color);
m_projectBookmarks.m_view->setMultiplyColor(color);
m_projectBookmarksLabel->setMultiplyColor(color);
m_recentBookmarks.m_view->setMultiplyColor(color);
m_recentBookmarksLabel->setMultiplyColor(color);
}
void FileBrowser::okActivated(bool viaButton) {
IViewManager& vm = rootView().viewManager();
hecl::SystemString path;
bool needSlash = false;
for (const hecl::SystemString& d : m_comps) {
if (needSlash)
path += _SYS_STR('/');
if (d != _SYS_STR("/"))
needSlash = true;
path += d;
}
hecl::Sstat theStat;
if (hecl::Stat(path.c_str(), &theStat) || !S_ISDIR(theStat.st_mode)) {
hecl::SystemUTF8Conv utf8(path);
m_fileField.m_view->setErrorState(vm.translate<locale::no_access_as_dir>(utf8));
return;
}
path += _SYS_STR('/');
path += hecl::SystemStringConv(m_fileField.m_view->getText()).sys_str();
int err = hecl::Stat(path.c_str(), &theStat);
if (m_type == Type::SaveFile) {
if (m_fileField.m_view->getText().empty()) {
m_fileField.m_view->setErrorState(vm.translate<locale::file_field_empty>());
return;
}
if (!err && !S_ISDIR(theStat.st_mode)) {
m_confirmWindow = std::make_unique<MessageWindow>(
rootView().viewRes(), *this, MessageWindow::Type::ConfirmOkCancel,
vm.translate<locale::overwrite_confirm>(hecl::SystemUTF8Conv(path)), [&, path](bool ok) {
if (ok) {
m_returnFunc(true, path);
m_confirmWindow->close();
close();
} else
m_confirmWindow->close();
});
updateSize();
return;
}
if (!err && S_ISDIR(theStat.st_mode)) {
navigateToPath(path);
return;
}
m_returnFunc(true, path);
close();
return;
} else if (m_type == Type::SaveDirectory || m_type == Type::NewHECLProject) {
if (m_fileField.m_view->getText().empty()) {
m_fileField.m_view->setErrorState(vm.translate<locale::directory_field_empty>());
return;
}
if (!err && !S_ISDIR(theStat.st_mode)) {
m_fileField.m_view->setErrorState(vm.translate<locale::no_overwrite_file>());
return;
}
if (!err && S_ISDIR(theStat.st_mode)) {
if (m_type == Type::NewHECLProject) {
hecl::ProjectRootPath projRoot = hecl::SearchForProject(path);
if (projRoot) {
m_fileField.m_view->setErrorState(vm.translate<locale::no_overwrite_project>());
return;
}
}
navigateToPath(path);
return;
}
m_returnFunc(true, path);
close();
return;
}
if (m_type == Type::OpenFile) {
if (!err && S_ISDIR(theStat.st_mode)) {
navigateToPath(path);
return;
} else if (err || !S_ISREG(theStat.st_mode)) {
hecl::SystemUTF8Conv utf8(path);
m_fileField.m_view->setErrorState(vm.translate<locale::no_access_as_file>(utf8));
return;
}
m_returnFunc(true, path);
close();
return;
} else if (m_type == Type::OpenDirectory || m_type == Type::OpenHECLProject) {
if (!viaButton && !err && S_ISDIR(theStat.st_mode)) {
if (m_type == Type::OpenHECLProject) {
hecl::ProjectRootPath projRoot = hecl::SearchForProject(path);
if (projRoot) {
m_returnFunc(true, projRoot.getAbsolutePath());
close();
return;
}
}
navigateToPath(path);
return;
}
if (err || !S_ISDIR(theStat.st_mode)) {
hecl::SystemUTF8Conv utf8(path);
m_fileField.m_view->setErrorState(vm.translate<locale::no_access_as_dir>(utf8));
return;
}
m_returnFunc(true, path);
close();
return;
}
}
void FileBrowser::cancelActivated() {
hecl::SystemString path;
bool needSlash = false;
for (const hecl::SystemString& d : m_comps) {
if (needSlash)
path += _SYS_STR('/');
if (d != _SYS_STR("/"))
needSlash = true;
path += d;
}
path += _SYS_STR('/');
path += hecl::SystemStringConv(m_fileField.m_view->getText()).sys_str();
m_returnFunc(false, path);
close();
}
FileBrowser::FileFieldBind::FileFieldBind(FileBrowser& browser, const IViewManager& vm)
: m_browser(browser), m_name(vm.translate<locale::file_name>()) {}
void FileBrowser::pathButtonActivated(size_t idx) {
if (idx >= m_comps.size())
return;
hecl::SystemString dir;
bool needSlash = false;
size_t i = 0;
for (const hecl::SystemString& d : m_comps) {
if (needSlash)
dir += _SYS_STR('/');
if (d != _SYS_STR("/"))
needSlash = true;
dir += d;
if (++i > idx)
break;
}
navigateToPath(dir);
}
void FileBrowser::FileListingDataBind::updateListing(const hecl::DirectoryEnumerator& dEnum) {
m_entries.clear();
m_entries.reserve(dEnum.size());
for (const hecl::DirectoryEnumerator::Entry& d : dEnum) {
Entry& ent = m_entries.emplace_back();
ent.m_path = d.m_path;
hecl::SystemUTF8Conv nameUtf8(d.m_name);
ent.m_name = nameUtf8.str();
if (d.m_isDir) {
if (hecl::SearchForProject(d.m_path))
ent.m_type = m_projStr;
else
ent.m_type = m_dirStr;
} else {
ent.m_type = m_fileStr;
ent.m_size = hecl::HumanizeNumber(d.m_fileSz, 7, nullptr, int(hecl::HNScale::AutoScale),
hecl::HNFlags::B | hecl::HNFlags::Decimal);
}
}
m_needsUpdate = false;
}
void FileBrowser::FileListingDataBind::setSelectedRow(size_t rIdx) {
if (rIdx != SIZE_MAX) {
m_fb.m_fileField.m_view->setText(m_entries.at(rIdx).m_name);
} else {
m_fb.m_fileField.m_view->setText("");
}
m_fb.m_fileField.m_view->clearErrorState();
}
FileBrowser::FileListingDataBind::FileListingDataBind(FileBrowser& fb, const IViewManager& vm) : m_fb(fb) {
m_nameCol = vm.translate<locale::name>();
m_typeCol = vm.translate<locale::type>();
m_sizeCol = vm.translate<locale::size>();
m_dirStr = vm.translate<locale::directory>();
m_projStr = vm.translate<locale::hecl_project>();
m_fileStr = vm.translate<locale::file>();
}
void FileBrowser::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
if (skipBuildInAnimation() || closed())
return;
if (m_confirmWindow)
m_confirmWindow->mouseDown(coord, button, mod);
m_split.mouseDown(coord, button, mod);
m_pathButtons.mouseDown(coord, button, mod);
m_fileField.mouseDown(coord, button, mod);
m_fileListing.mouseDown(coord, button, mod);
m_systemBookmarks.m_view->mouseDown(coord, button, mod);
m_projectBookmarks.m_view->mouseDown(coord, button, mod);
m_recentBookmarks.m_view->mouseDown(coord, button, mod);
m_ok.m_button.mouseDown(coord, button, mod);
m_cancel.m_button.mouseDown(coord, button, mod);
}
void FileBrowser::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
if (closed())
return;
m_split.mouseUp(coord, button, mod);
m_pathButtons.mouseUp(coord, button, mod);
m_fileField.mouseUp(coord, button, mod);
m_fileListing.mouseUp(coord, button, mod);
m_systemBookmarks.m_view->mouseUp(coord, button, mod);
m_projectBookmarks.m_view->mouseUp(coord, button, mod);
m_recentBookmarks.m_view->mouseUp(coord, button, mod);
m_ok.m_button.mouseUp(coord, button, mod);
m_cancel.m_button.mouseUp(coord, button, mod);
if (m_confirmWindow)
m_confirmWindow->mouseUp(coord, button, mod);
}
void FileBrowser::mouseMove(const boo::SWindowCoord& coord) {
if (closed())
return;
m_split.mouseMove(coord);
m_pathButtons.mouseMove(coord);
m_fileField.mouseMove(coord);
m_fileListing.mouseMove(coord);
m_systemBookmarks.m_view->mouseMove(coord);
m_projectBookmarks.m_view->mouseMove(coord);
m_recentBookmarks.m_view->mouseMove(coord);
m_ok.m_button.mouseMove(coord);
m_cancel.m_button.mouseMove(coord);
if (m_confirmWindow)
m_confirmWindow->mouseMove(coord);
}
void FileBrowser::mouseLeave(const boo::SWindowCoord& coord) {
if (closed())
return;
m_split.mouseLeave(coord);
m_pathButtons.mouseLeave(coord);
m_fileField.mouseLeave(coord);
m_fileListing.mouseLeave(coord);
m_ok.m_button.mouseLeave(coord);
m_cancel.m_button.mouseLeave(coord);
if (m_confirmWindow)
m_confirmWindow->mouseLeave(coord);
}
void FileBrowser::scroll(const boo::SWindowCoord& coord, const boo::SScrollDelta& scroll) {
m_pathButtons.scroll(coord, scroll);
m_fileListing.scroll(coord, scroll);
}
void FileBrowser::touchDown(const boo::STouchCoord& coord, uintptr_t tid) {}
void FileBrowser::touchUp(const boo::STouchCoord& coord, uintptr_t tid) {}
void FileBrowser::touchMove(const boo::STouchCoord& coord, uintptr_t tid) {}
void FileBrowser::charKeyDown(unsigned long charcode, boo::EModifierKey mod, bool isRepeat) {
if (skipBuildInAnimation() || closed())
return;
if (True(mod & boo::EModifierKey::CtrlCommand) && !isRepeat) {
if (charcode == 'h' || charcode == 'H')
setShowingHidden(!showingHidden());
else if (charcode == 'r' || charcode == 'R')
navigateToPath(m_path);
}
}
void FileBrowser::specialKeyDown(boo::ESpecialKey key, boo::EModifierKey mod, bool isRepeat) {
if (skipBuildInAnimation() || closed())
return;
if (key == boo::ESpecialKey::Enter && !isRepeat)
okActivated(true);
}
void FileBrowser::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
ModalWindow::resized(root, root);
float pf = rootView().viewRes().pixelFactor();
boo::SWindowRect centerRect = subRect();
centerRect.location[0] = root.size[0] / 2 - (centerRect.size[0] / 2.0) + 2 * pf;
centerRect.location[1] = root.size[1] / 2 - (centerRect.size[1] / 2.0) + 2 * pf;
centerRect.size[0] -= 4 * pf;
centerRect.size[1] -= 4 * pf;
if (m_split.m_view)
m_split.m_view->resized(root, centerRect);
if (m_confirmWindow)
m_confirmWindow->resized(root, sub);
}
void FileBrowser::LeftSide::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
float pf = rootView().viewRes().pixelFactor();
int div = (sub.size[1] - BROWSER_MARGIN * pf) / 3;
boo::SWindowRect bookmarkRect = sub;
bookmarkRect.size[0] -= BROWSER_MARGIN * 2 * pf;
bookmarkRect.size[1] = div;
bookmarkRect.location[0] += BROWSER_MARGIN * pf;
bookmarkRect.location[1] = sub.location[1] + BROWSER_MARGIN * pf + div * 2;
boo::SWindowRect labelRect = bookmarkRect;
labelRect.size[1] = 20;
labelRect.location[1] += div - 16 * pf;
m_fb.m_systemBookmarks.m_view->resized(root, bookmarkRect);
m_fb.m_systemBookmarksLabel->resized(root, labelRect);
bookmarkRect.location[1] -= div;
labelRect.location[1] -= div;
m_fb.m_projectBookmarks.m_view->resized(root, bookmarkRect);
m_fb.m_projectBookmarksLabel->resized(root, labelRect);
bookmarkRect.location[1] -= div;
labelRect.location[1] -= div;
m_fb.m_recentBookmarks.m_view->resized(root, bookmarkRect);
m_fb.m_recentBookmarksLabel->resized(root, labelRect);
}
void FileBrowser::RightSide::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
float pf = rootView().viewRes().pixelFactor();
boo::SWindowRect pathRect = sub;
pathRect.location[0] += BROWSER_MARGIN * pf;
pathRect.location[1] += pathRect.size[1] - (BROWSER_MARGIN + 20) * pf;
pathRect.size[0] = sub.size[0] - m_fb.m_ok.m_button.m_view->nominalWidth() - 20 * pf;
pathRect.size[1] = m_fb.m_fileField.m_view->nominalHeight();
m_fb.m_pathButtons.m_view->resized(root, pathRect);
pathRect.location[0] = sub.location[0] + BROWSER_MARGIN * pf;
pathRect.location[1] -= 25 * pf;
m_fb.m_fileField.m_view->resized(root, pathRect);
pathRect.location[1] = sub.location[1] + BROWSER_MARGIN * pf;
pathRect.size[0] = sub.size[0] - BROWSER_MARGIN * 2 * pf;
pathRect.size[1] = sub.size[1] - (BROWSER_MARGIN + 56) * pf;
m_fb.m_fileListing.m_view->resized(root, pathRect);
boo::SWindowRect buttonRect = sub;
buttonRect.size[0] = m_fb.m_ok.m_button.m_view->nominalWidth();
buttonRect.size[1] = m_fb.m_ok.m_button.m_view->nominalHeight();
buttonRect.location[0] += sub.size[0] - BROWSER_MARGIN * pf - buttonRect.size[0];
buttonRect.location[1] += sub.size[1] - (BROWSER_MARGIN + 20) * pf;
m_fb.m_ok.m_button.m_view->resized(root, buttonRect);
buttonRect.location[1] -= 25 * pf;
m_fb.m_cancel.m_button.m_view->resized(root, buttonRect);
}
void FileBrowser::think() {
ModalWindow::think();
if (m_fileListingBind.m_needsUpdate)
navigateToPath(m_path);
m_pathButtons.m_view->think();
m_fileField.m_view->think();
m_fileListing.m_view->think();
m_systemBookmarks.m_view->think();
m_projectBookmarks.m_view->think();
m_recentBookmarks.m_view->think();
if (m_confirmWindow)
m_confirmWindow->think();
}
void FileBrowser::draw(boo::IGraphicsCommandQueue* gfxQ) {
ModalWindow::draw(gfxQ);
m_split.m_view->draw(gfxQ);
if (m_confirmWindow)
m_confirmWindow->draw(gfxQ);
}
void FileBrowser::LeftSide::draw(boo::IGraphicsCommandQueue* gfxQ) {
m_fb.m_systemBookmarks.m_view->draw(gfxQ);
m_fb.m_systemBookmarksLabel->draw(gfxQ);
m_fb.m_projectBookmarks.m_view->draw(gfxQ);
m_fb.m_projectBookmarksLabel->draw(gfxQ);
m_fb.m_recentBookmarks.m_view->draw(gfxQ);
m_fb.m_recentBookmarksLabel->draw(gfxQ);
}
void FileBrowser::RightSide::draw(boo::IGraphicsCommandQueue* gfxQ) {
m_fb.m_pathButtons.m_view->draw(gfxQ);
m_fb.m_fileListing.m_view->draw(gfxQ);
m_fb.m_ok.m_button.m_view->draw(gfxQ);
m_fb.m_cancel.m_button.m_view->draw(gfxQ);
m_fb.m_fileField.m_view->draw(gfxQ);
}
FileBrowser::OKButton::OKButton(FileBrowser& fb, ViewResources& res, std::string_view text) : m_fb(fb), m_text(text) {
m_button.m_view =
std::make_unique<Button>(res, fb, this, text, nullptr, Button::Style::Block, zeus::skWhite,
RectangleConstraint(100 * res.pixelFactor(), -1, RectangleConstraint::Test::Minimum));
}
FileBrowser::CancelButton::CancelButton(FileBrowser& fb, ViewResources& res, std::string_view text)
: m_fb(fb), m_text(text) {
m_button.m_view = std::make_unique<Button>(
res, fb, this, text, nullptr, Button::Style::Block, zeus::skWhite,
RectangleConstraint(m_fb.m_ok.m_button.m_view->nominalWidth(), -1, RectangleConstraint::Test::Minimum));
}
} // namespace specter

667
specter/lib/FontCache.cpp Normal file
View File

@ -0,0 +1,667 @@
#ifndef NOMINMAX
#define NOMINMAX 1
#endif
#include "specter/FontCache.hpp"
#include <cstddef>
#include <cstdint>
#include <memory>
#include <vector>
#include <athena/FileReader.hpp>
#include <athena/FileWriter.hpp>
#include <athena/MemoryReader.hpp>
#include <boo/System.hpp>
#include FT_GZIP_H
#include FT_SYSTEM_H
#include FT_OUTLINE_H
#include <freetype/internal/internal.h>
#include <freetype/internal/ftstream.h>
#include <freetype/internal/tttypes.h>
#include <hecl/hecl.hpp>
#include <hecl/Runtime.hpp>
#include <logvisor/logvisor.hpp>
#include <xxhash/xxhash.h>
#include <zlib.h>
extern "C" const uint8_t DROIDSANS_PERMISSIVE[];
extern "C" size_t DROIDSANS_PERMISSIVE_SZ;
extern "C" const uint8_t BMONOFONT[];
extern "C" size_t BMONOFONT_SZ;
extern "C" const uint8_t SPECTERCURVES[];
extern "C" size_t SPECTERCURVES_SZ;
extern "C" const FT_Driver_ClassRec tt_driver_class;
namespace specter {
static logvisor::Module Log("specter::FontCache");
const FCharFilter AllCharFilter{"all-glyphs", [](uint32_t) { return true; }};
const FCharFilter LatinCharFilter{"latin-glyphs",
[](uint32_t c) { return c <= 0xff || (c - 0x2200) <= (0x23FF - 0x2200); }};
const FCharFilter LatinAndJapaneseCharFilter{"latin-and-jp-glyphs", [](uint32_t c) {
return LatinCharFilter.second(c) || (c - 0x2E00) <= (0x30FF - 0x2E00) ||
(c - 0x4E00) <= (0x9FFF - 0x4E00) ||
(c - 0xFF00) <= (0xFFEF - 0xFF00);
}};
FontTag::FontTag(std::string_view name, bool subpixel, float points, uint32_t dpi) {
XXH64_state_t st;
XXH64_reset(&st, 0);
XXH64_update(&st, name.data(), name.size());
XXH64_update(&st, &subpixel, 1);
XXH64_update(&st, &points, 4);
XXH64_update(&st, &dpi, 4);
m_hash = XXH64_digest(&st);
}
FreeTypeGZipMemFace::FreeTypeGZipMemFace(FT_Library lib, const uint8_t* data, size_t sz) : m_lib(lib) {
m_comp.base = (unsigned char*)data;
m_comp.size = sz;
m_comp.memory = lib->memory;
}
void FreeTypeGZipMemFace::open() {
if (m_face)
return;
if (FT_Stream_OpenGzip(&m_decomp, &m_comp))
Log.report(logvisor::Fatal, FMT_STRING("unable to open FreeType gzip stream"));
FT_Open_Args args = {FT_OPEN_STREAM, nullptr, 0, nullptr, &m_decomp};
if (FT_Open_Face(m_lib, &args, 0, &m_face))
Log.report(logvisor::Fatal, FMT_STRING("unable to open FreeType gzip face"));
}
void FreeTypeGZipMemFace::close() {
if (!m_face)
return;
FT_Done_Face(m_face);
m_face = nullptr;
}
#define TEXMAP_DIM 1024
static unsigned RoundUpPow2(unsigned v) {
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
using GreyPixel = uint8_t;
static void MemcpyRect(GreyPixel* img, const FT_Bitmap* bmp, unsigned slice, unsigned x, unsigned y) {
unsigned sy = TEXMAP_DIM * slice + y;
for (unsigned i = 0; i < bmp->rows; ++i) {
const unsigned char* s = &bmp->buffer[bmp->pitch * i];
GreyPixel* t = &img[TEXMAP_DIM * (sy + i) + x];
memcpy(t, s, bmp->width);
}
}
union RgbaPixel {
uint8_t rgba[4];
uint32_t pixel;
};
static void MemcpyRect(RgbaPixel* img, const FT_Bitmap* bmp, unsigned slice, unsigned x, unsigned y) {
unsigned sy = TEXMAP_DIM * slice + y;
for (unsigned i = 0; i < bmp->rows; ++i) {
const unsigned char* s = &bmp->buffer[bmp->pitch * i];
RgbaPixel* t = &img[TEXMAP_DIM * (sy + i) + x];
for (unsigned j = 0; j < bmp->width / 3; ++j) {
t[j].rgba[0] = s[j * 3];
t[j].rgba[1] = s[j * 3 + 1];
t[j].rgba[2] = s[j * 3 + 2];
t[j].rgba[3] = 0xff;
}
}
}
static void GridFitGlyph(FT_GlyphSlot slot, FT_UInt& width, FT_UInt& height) {
width = slot->metrics.width >> 6;
height = slot->metrics.height >> 6;
}
void FontAtlas::buildKernTable(FT_Face face) {
if (face->driver->clazz == &tt_driver_class) {
TT_Face ttface = reinterpret_cast<TT_Face>(face);
if (!ttface->kern_table)
return;
athena::io::MemoryReader r(ttface->kern_table, ttface->kern_table_size);
auto it = m_kernAdjs.end();
atUint32 nSubs = r.readUint32Big();
for (atUint32 i = 0; i < nSubs; ++i) {
TT_KernHead kernHead;
kernHead.read(r);
if (kernHead.coverage >> 8 != 0) {
r.seek(kernHead.length - 6, athena::SeekOrigin::Current);
continue;
}
TT_KernSubHead subHead;
subHead.read(r);
for (atUint16 p = 0; p < subHead.nPairs; ++p) {
TT_KernPair pair;
pair.read(r);
if (it == m_kernAdjs.end() || it->first != pair.left)
if ((it = m_kernAdjs.find(pair.left)) == m_kernAdjs.end())
it = m_kernAdjs.insert(std::make_pair(pair.left, std::vector<std::pair<atUint16, atInt16>>())).first;
it->second.emplace_back(pair.right, pair.value);
}
}
}
}
#define NO_ZLIB 0
#define ZLIB_BUF_SZ 32768
static void WriteCompressed(athena::io::FileWriter& writer, const atUint8* data, size_t sz) {
#if NO_ZLIB
writer.writeUBytes(data, sz);
return;
#endif
atUint8 compBuf[ZLIB_BUF_SZ];
z_stream z = {};
deflateInit(&z, Z_DEFAULT_COMPRESSION);
z.next_in = (Bytef*)data;
z.avail_in = sz;
writer.writeUint32Big(sz);
atUint64 adlerPos = writer.position();
writer.writeUint32Big(0); /* Space for adler32 */
while (z.avail_in) {
z.next_out = compBuf;
z.avail_out = ZLIB_BUF_SZ;
deflate(&z, Z_NO_FLUSH);
writer.writeUBytes(compBuf, ZLIB_BUF_SZ - z.avail_out);
}
int finishCycle = Z_OK;
while (finishCycle != Z_STREAM_END) {
z.next_out = compBuf;
z.avail_out = ZLIB_BUF_SZ;
finishCycle = deflate(&z, Z_FINISH);
writer.writeUBytes(compBuf, ZLIB_BUF_SZ - z.avail_out);
}
writer.seek(adlerPos, athena::SeekOrigin::Begin);
writer.writeUint32Big(z.adler);
deflateEnd(&z);
}
static bool ReadDecompressed(athena::io::FileReader& reader, atUint8* data, size_t sz) {
#if NO_ZLIB
reader.readUBytesToBuf(data, sz);
return true;
#endif
atUint8 compBuf[ZLIB_BUF_SZ];
z_stream z = {};
inflateInit(&z);
z.next_out = data;
atUint32 targetSz = reader.readUint32Big();
atUint32 adler32 = reader.readUint32Big();
z.avail_out = std::min(sz, size_t(targetSz));
size_t readSz;
while ((readSz = reader.readUBytesToBuf(compBuf, ZLIB_BUF_SZ))) {
z.next_in = compBuf;
z.avail_in = readSz;
if (inflate(&z, Z_NO_FLUSH) == Z_STREAM_END)
break;
}
inflateEnd(&z);
return adler32 == z.adler;
}
FontAtlas::FontAtlas(FT_Face face, uint32_t dpi, bool subpixel, FCharFilter& filter, athena::io::FileWriter& writer)
: m_dpi(dpi)
, m_ftXscale(face->size->metrics.x_scale)
, m_ftXPpem(face->size->metrics.x_ppem)
, m_lineHeight(face->size->metrics.height)
, m_subpixel(subpixel) {
FT_Int32 baseFlags = FT_LOAD_NO_BITMAP;
if (subpixel)
baseFlags |= FT_LOAD_TARGET_LCD;
else
baseFlags |= FT_LOAD_TARGET_NORMAL;
/* First count glyphs exposed by unicode charmap and tally required area */
size_t glyphCount = 0;
FT_UInt gindex;
FT_ULong charcode = FT_Get_First_Char(face, &gindex);
unsigned curLineWidth = 1;
unsigned curLineHeight = 0;
unsigned totalHeight = 1;
m_fullTexmapLayers = 0;
while (gindex != 0) {
if (!filter.second(charcode)) {
charcode = FT_Get_Next_Char(face, charcode, &gindex);
continue;
}
++glyphCount;
FT_Load_Glyph(face, gindex, baseFlags);
FT_UInt width, height;
GridFitGlyph(face->glyph, width, height);
if (curLineWidth + width + 1 > TEXMAP_DIM) {
totalHeight += curLineHeight + 1;
curLineHeight = 0;
curLineWidth = 1;
}
curLineHeight = std::max(curLineHeight, height);
if (totalHeight + curLineHeight + 1 > TEXMAP_DIM) {
totalHeight = 1;
++m_fullTexmapLayers;
// printf("StagedB: %u\n", gindex);
curLineHeight = 0;
curLineWidth = 1;
}
curLineWidth += width + 1;
charcode = FT_Get_Next_Char(face, charcode, &gindex);
}
if (curLineHeight)
totalHeight += curLineHeight + 1;
m_glyphs.reserve(glyphCount);
m_glyphLookup.reserve(glyphCount);
totalHeight = RoundUpPow2(totalHeight);
m_finalHeight = m_fullTexmapLayers ? TEXMAP_DIM : totalHeight;
writer.writeUint32Big(m_fullTexmapLayers + 1);
writer.writeUint32Big(TEXMAP_DIM);
writer.writeUint32Big(m_finalHeight);
if (subpixel) {
/* Allocate texmap */
if (m_fullTexmapLayers) {
// printf("ALLOC: %u\n", fullTexmapLayers + 1);
size_t count = TEXMAP_DIM * TEXMAP_DIM * (m_fullTexmapLayers + 1);
m_texmap.resize(count * sizeof(RgbaPixel));
} else {
size_t count = TEXMAP_DIM * totalHeight;
m_texmap.resize(count * sizeof(RgbaPixel));
}
/* Assemble glyph texmaps and internal data structures */
charcode = FT_Get_First_Char(face, &gindex);
curLineWidth = 1;
curLineHeight = 0;
totalHeight = 1;
m_fullTexmapLayers = 0;
while (gindex != 0) {
if (!filter.second(charcode)) {
charcode = FT_Get_Next_Char(face, charcode, &gindex);
continue;
}
FT_Load_Glyph(face, gindex, FT_LOAD_RENDER | baseFlags);
FT_UInt width, height;
GridFitGlyph(face->glyph, width, height);
if (curLineWidth + width + 1 > TEXMAP_DIM) {
totalHeight += curLineHeight + 1;
curLineHeight = 0;
curLineWidth = 1;
}
curLineHeight = std::max(curLineHeight, height);
if (totalHeight + curLineHeight + 1 > TEXMAP_DIM) {
totalHeight = 1;
++m_fullTexmapLayers;
// printf("RealB: %u\n", gindex);
curLineHeight = 0;
curLineWidth = 1;
}
m_glyphLookup.insert_or_assign(charcode, m_glyphs.size());
Glyph& g = m_glyphs.emplace_back();
g.m_unicodePoint = charcode;
g.m_glyphIdx = gindex;
g.m_layerIdx = m_fullTexmapLayers;
g.m_layerFloat = float(g.m_layerIdx);
g.m_width = face->glyph->bitmap.width / 3;
g.m_height = face->glyph->bitmap.rows;
g.m_uv[0] = curLineWidth / float(TEXMAP_DIM);
g.m_uv[1] = totalHeight / float(m_finalHeight);
g.m_uv[2] = g.m_uv[0] + g.m_width / float(TEXMAP_DIM);
g.m_uv[3] = g.m_uv[1] + g.m_height / float(m_finalHeight);
g.m_leftPadding = face->glyph->metrics.horiBearingX >> 6;
g.m_advance = face->glyph->advance.x >> 6;
g.m_verticalOffset = (face->glyph->metrics.horiBearingY - face->glyph->metrics.height) >> 6;
MemcpyRect((RgbaPixel*)m_texmap.data(), &face->glyph->bitmap, m_fullTexmapLayers, curLineWidth, totalHeight);
curLineWidth += width + 1;
charcode = FT_Get_Next_Char(face, charcode, &gindex);
}
WriteCompressed(writer, m_texmap.data(), m_texmap.size());
} else {
/* Allocate texmap */
if (m_fullTexmapLayers) {
// printf("ALLOC: %u\n", fullTexmapLayers + 1);
size_t count = TEXMAP_DIM * TEXMAP_DIM * (m_fullTexmapLayers + 1);
m_texmap.resize(count * sizeof(GreyPixel));
} else {
size_t count = TEXMAP_DIM * totalHeight;
m_texmap.resize(count * sizeof(GreyPixel));
}
/* Assemble glyph texmaps and internal data structures */
charcode = FT_Get_First_Char(face, &gindex);
curLineWidth = 1;
curLineHeight = 0;
totalHeight = 1;
m_fullTexmapLayers = 0;
while (gindex != 0) {
if (!filter.second(charcode)) {
charcode = FT_Get_Next_Char(face, charcode, &gindex);
continue;
}
FT_Load_Glyph(face, gindex, FT_LOAD_RENDER | baseFlags);
FT_UInt width, height;
GridFitGlyph(face->glyph, width, height);
if (curLineWidth + width + 1 > TEXMAP_DIM) {
totalHeight += curLineHeight + 1;
curLineHeight = 0;
curLineWidth = 1;
}
curLineHeight = std::max(curLineHeight, height);
if (totalHeight + curLineHeight + 1 > TEXMAP_DIM) {
totalHeight = 1;
++m_fullTexmapLayers;
// printf("RealB: %u\n", gindex);
curLineHeight = 0;
curLineWidth = 1;
}
m_glyphLookup.insert_or_assign(charcode, m_glyphs.size());
Glyph& g = m_glyphs.emplace_back();
g.m_unicodePoint = charcode;
g.m_glyphIdx = gindex;
g.m_layerIdx = m_fullTexmapLayers;
g.m_layerFloat = float(g.m_layerIdx);
g.m_width = face->glyph->bitmap.width;
g.m_height = face->glyph->bitmap.rows;
g.m_uv[0] = curLineWidth / float(TEXMAP_DIM);
g.m_uv[1] = totalHeight / float(m_finalHeight);
g.m_uv[2] = g.m_uv[0] + g.m_width / float(TEXMAP_DIM);
g.m_uv[3] = g.m_uv[1] + g.m_height / float(m_finalHeight);
g.m_leftPadding = face->glyph->metrics.horiBearingX >> 6;
g.m_advance = face->glyph->advance.x >> 6;
g.m_verticalOffset = (face->glyph->metrics.horiBearingY - face->glyph->metrics.height) >> 6;
MemcpyRect(m_texmap.data(), &face->glyph->bitmap, m_fullTexmapLayers, curLineWidth, totalHeight);
curLineWidth += width + 1;
charcode = FT_Get_Next_Char(face, charcode, &gindex);
}
WriteCompressed(writer, m_texmap.data(), m_texmap.size());
}
buildKernTable(face);
m_ready = true;
}
FontAtlas::FontAtlas(FT_Face face, uint32_t dpi, bool subpixel, FCharFilter& filter, athena::io::FileReader& reader)
: m_dpi(dpi)
, m_ftXscale(face->size->metrics.x_scale)
, m_ftXPpem(face->size->metrics.x_ppem)
, m_lineHeight(face->size->metrics.height)
, m_subpixel(subpixel) {
FT_Int32 baseFlags = FT_LOAD_NO_BITMAP;
if (subpixel)
baseFlags |= FT_LOAD_TARGET_LCD;
else
baseFlags |= FT_LOAD_TARGET_NORMAL;
/* First count glyphs exposed by unicode charmap */
size_t glyphCount = 0;
FT_UInt gindex;
FT_ULong charcode = FT_Get_First_Char(face, &gindex);
while (gindex != 0) {
if (!filter.second(charcode)) {
charcode = FT_Get_Next_Char(face, charcode, &gindex);
continue;
}
++glyphCount;
charcode = FT_Get_Next_Char(face, charcode, &gindex);
}
m_glyphs.reserve(glyphCount);
m_glyphLookup.reserve(glyphCount);
m_fullTexmapLayers = reader.readUint32Big() - 1;
reader.readUint32Big();
m_finalHeight = reader.readUint32Big();
if (subpixel) {
/* Allocate texmap */
if (m_fullTexmapLayers) {
// printf("ALLOC: %u\n", fullTexmapLayers + 1);
size_t count = TEXMAP_DIM * TEXMAP_DIM * (m_fullTexmapLayers + 1);
m_texmap.resize(count * sizeof(RgbaPixel));
} else {
size_t count = TEXMAP_DIM * m_finalHeight;
m_texmap.resize(count * sizeof(RgbaPixel));
}
/* Assemble glyph texmaps and internal data structures */
charcode = FT_Get_First_Char(face, &gindex);
unsigned curLineWidth = 1;
unsigned curLineHeight = 0;
unsigned totalHeight = 1;
m_fullTexmapLayers = 0;
while (gindex != 0) {
if (!filter.second(charcode)) {
charcode = FT_Get_Next_Char(face, charcode, &gindex);
continue;
}
FT_Load_Glyph(face, gindex, baseFlags);
FT_UInt width, height;
GridFitGlyph(face->glyph, width, height);
if (curLineWidth + width + 1 > TEXMAP_DIM) {
totalHeight += curLineHeight + 1;
curLineHeight = 0;
curLineWidth = 1;
}
curLineHeight = std::max(curLineHeight, height);
if (totalHeight + curLineHeight + 1 > TEXMAP_DIM) {
totalHeight = 1;
++m_fullTexmapLayers;
// printf("RealB: %u\n", gindex);
curLineHeight = 0;
curLineWidth = 1;
}
m_glyphLookup.insert_or_assign(charcode, m_glyphs.size());
Glyph& g = m_glyphs.emplace_back();
g.m_unicodePoint = charcode;
g.m_glyphIdx = gindex;
g.m_layerIdx = m_fullTexmapLayers;
g.m_layerFloat = float(g.m_layerIdx);
g.m_width = width;
g.m_height = height;
g.m_uv[0] = curLineWidth / float(TEXMAP_DIM);
g.m_uv[1] = totalHeight / float(m_finalHeight);
g.m_uv[2] = g.m_uv[0] + g.m_width / float(TEXMAP_DIM);
g.m_uv[3] = g.m_uv[1] + g.m_height / float(m_finalHeight);
g.m_leftPadding = face->glyph->metrics.horiBearingX >> 6;
g.m_advance = face->glyph->advance.x >> 6;
g.m_verticalOffset = (face->glyph->metrics.horiBearingY - face->glyph->metrics.height) >> 6;
curLineWidth += width + 1;
charcode = FT_Get_Next_Char(face, charcode, &gindex);
}
if (!ReadDecompressed(reader, m_texmap.data(), m_texmap.size()))
return;
} else {
/* Allocate texmap */
if (m_fullTexmapLayers) {
// printf("ALLOC: %u\n", fullTexmapLayers + 1);
size_t count = TEXMAP_DIM * TEXMAP_DIM * (m_fullTexmapLayers + 1);
m_texmap.resize(count * sizeof(GreyPixel));
} else {
size_t count = TEXMAP_DIM * m_finalHeight;
m_texmap.resize(count * sizeof(GreyPixel));
}
/* Assemble glyph texmaps and internal data structures */
charcode = FT_Get_First_Char(face, &gindex);
unsigned curLineWidth = 1;
unsigned curLineHeight = 0;
unsigned totalHeight = 1;
m_fullTexmapLayers = 0;
while (gindex != 0) {
if (!filter.second(charcode)) {
charcode = FT_Get_Next_Char(face, charcode, &gindex);
continue;
}
FT_Load_Glyph(face, gindex, baseFlags);
FT_UInt width, height;
GridFitGlyph(face->glyph, width, height);
if (curLineWidth + width + 1 > TEXMAP_DIM) {
totalHeight += curLineHeight + 1;
curLineHeight = 0;
curLineWidth = 1;
}
curLineHeight = std::max(curLineHeight, height);
if (totalHeight + curLineHeight + 1 > TEXMAP_DIM) {
totalHeight = 1;
++m_fullTexmapLayers;
// printf("RealB: %u\n", gindex);
curLineHeight = 0;
curLineWidth = 1;
}
m_glyphLookup.insert_or_assign(charcode, m_glyphs.size());
Glyph& g = m_glyphs.emplace_back();
g.m_unicodePoint = charcode;
g.m_glyphIdx = gindex;
g.m_layerIdx = m_fullTexmapLayers;
g.m_layerFloat = float(g.m_layerIdx);
g.m_width = width;
g.m_height = height;
g.m_uv[0] = curLineWidth / float(TEXMAP_DIM);
g.m_uv[1] = totalHeight / float(m_finalHeight);
g.m_uv[2] = g.m_uv[0] + g.m_width / float(TEXMAP_DIM);
g.m_uv[3] = g.m_uv[1] + g.m_height / float(m_finalHeight);
g.m_leftPadding = face->glyph->metrics.horiBearingX >> 6;
g.m_advance = face->glyph->advance.x >> 6;
g.m_verticalOffset = (face->glyph->metrics.horiBearingY - face->glyph->metrics.height) >> 6;
curLineWidth += width + 1;
charcode = FT_Get_Next_Char(face, charcode, &gindex);
}
if (!ReadDecompressed(reader, m_texmap.data(), m_texmap.size()))
return;
}
buildKernTable(face);
m_ready = true;
}
boo::ObjToken<boo::ITextureSA> FontAtlas::texture(boo::IGraphicsDataFactory* gf) const {
if (!m_ready)
return {};
if (m_tex)
return m_tex;
gf->commitTransaction([&](boo::IGraphicsDataFactory::Context& ctx) {
if (m_subpixel)
const_cast<boo::ObjToken<boo::ITextureSA>&>(m_tex) =
ctx.newStaticArrayTexture(TEXMAP_DIM, m_finalHeight, m_fullTexmapLayers + 1, 1, boo::TextureFormat::RGBA8,
boo::TextureClampMode::Repeat, m_texmap.data(), m_texmap.size());
else
const_cast<boo::ObjToken<boo::ITextureSA>&>(m_tex) =
ctx.newStaticArrayTexture(TEXMAP_DIM, m_finalHeight, m_fullTexmapLayers + 1, 1, boo::TextureFormat::I8,
boo::TextureClampMode::Repeat, m_texmap.data(), m_texmap.size());
const_cast<std::vector<uint8_t>&>(m_texmap) = std::vector<uint8_t>();
return true;
} BooTrace);
return m_tex;
}
FontCache::Library::Library() {
FT_Error err = FT_Init_FreeType(&m_lib);
if (err)
Log.report(logvisor::Fatal, FMT_STRING("unable to FT_Init_FreeType"));
}
FontCache::Library::~Library() { FT_Done_FreeType(m_lib); }
FontCache::FontCache(const hecl::Runtime::FileStoreManager& fileMgr)
: m_fileMgr(fileMgr)
, m_cacheRoot(hecl::SystemString(m_fileMgr.getStoreRoot()) + _SYS_STR("/fontcache"))
, m_regFace(m_fontLib, DROIDSANS_PERMISSIVE, DROIDSANS_PERMISSIVE_SZ)
, m_monoFace(m_fontLib, BMONOFONT, BMONOFONT_SZ)
, m_curvesFace(m_fontLib, SPECTERCURVES, SPECTERCURVES_SZ) {
hecl::MakeDir(m_cacheRoot.c_str());
}
FontCache::~FontCache() = default;
FontTag FontCache::prepCustomFont(std::string_view name, FT_Face face, FCharFilter filter, bool subpixel, float points,
uint32_t dpi) {
/* Quick validation */
if (!face)
Log.report(logvisor::Fatal, FMT_STRING("invalid freetype face"));
if (!face->charmap || face->charmap->encoding != FT_ENCODING_UNICODE)
Log.report(logvisor::Fatal, FMT_STRING("font does not contain a unicode char map"));
/* Set size with FreeType */
FT_Set_Char_Size(face, 0, points * 64.0, 0, dpi);
/* Make tag and search for cached version */
const FontTag tag(std::string(name).append(1, '_').append(filter.first), subpixel, points, dpi);
const auto search = m_cachedAtlases.find(tag);
if (search != m_cachedAtlases.end()) {
return tag;
}
/* Now check filesystem cache */
hecl::SystemString cachePath = m_cacheRoot + _SYS_STR('/') + fmt::format(FMT_STRING(_SYS_STR("{:x}")), tag.hash());
hecl::Sstat st;
if (!hecl::Stat(cachePath.c_str(), &st) && S_ISREG(st.st_mode)) {
athena::io::FileReader r(cachePath);
if (!r.hasError()) {
atUint32 magic = r.readUint32Big();
if (r.position() == 4 && magic == 'FONT') {
std::unique_ptr<FontAtlas> fa = std::make_unique<FontAtlas>(face, dpi, subpixel, filter, r);
if (fa->isReady()) {
m_cachedAtlases.emplace(tag, std::move(fa));
return tag;
}
}
}
}
/* Nada, build and cache now */
athena::io::FileWriter w(cachePath);
if (w.hasError())
Log.report(logvisor::Fatal, FMT_STRING(_SYS_STR("unable to open '{}' for writing")), cachePath);
w.writeUint32Big('FONT');
m_cachedAtlases.emplace(tag, std::make_unique<FontAtlas>(face, dpi, subpixel, filter, w));
return tag;
}
const FontAtlas& FontCache::lookupAtlas(FontTag tag) const {
auto search = m_cachedAtlases.find(tag);
if (search == m_cachedAtlases.cend())
Log.report(logvisor::Fatal, FMT_STRING("invalid font"));
return *search->second.get();
}
} // namespace specter

36
specter/lib/Icon.cpp Normal file
View File

@ -0,0 +1,36 @@
#include "specter/Icon.hpp"
#include "specter/RootView.hpp"
#include <boo/graphicsdev/IGraphicsCommandQueue.hpp>
namespace specter {
IconView::IconView(ViewResources& res, View& parentView, Icon& icon) : View(res, parentView) {
commitResources(res, [&](boo::IGraphicsDataFactory::Context& ctx) -> bool {
buildResources(ctx, res);
m_vertexBinding.init(ctx, res, 4, m_viewVertBlockBuf, icon.m_tex);
return true;
});
TexShaderVert verts[] = {
{{0, 1, 0}, icon.m_uvCoords[0]},
{{0, 0, 0}, icon.m_uvCoords[1]},
{{1, 1, 0}, icon.m_uvCoords[2]},
{{1, 0, 0}, icon.m_uvCoords[3]},
};
m_vertexBinding.load<decltype(verts)>(verts);
setBackground(zeus::skBlue);
}
void IconView::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
m_viewVertBlock.setViewRect(root, sub);
m_viewVertBlock.m_mv[0][0] *= sub.size[0];
m_viewVertBlock.m_mv[1][1] *= sub.size[1];
View::resized(m_viewVertBlock, sub);
}
void IconView::draw(boo::IGraphicsCommandQueue* gfxQ) {
gfxQ->setShaderDataBinding(m_vertexBinding);
gfxQ->draw(0, 4);
}
} // namespace specter

274
specter/lib/Menu.cpp Normal file
View File

@ -0,0 +1,274 @@
#include "specter/Menu.hpp"
#include "specter/IMenuNode.hpp"
#include "specter/RootView.hpp"
#include "specter/ScrollView.hpp"
#include "specter/TextView.hpp"
#include "specter/ViewResources.hpp"
#include <boo/graphicsdev/IGraphicsCommandQueue.hpp>
#define ROW_HEIGHT 18
#define ITEM_MARGIN 1
namespace specter {
Menu::Menu(ViewResources& res, View& parentView, IMenuNode* rootNode) : View(res, parentView) {
commitResources(res, [&](boo::IGraphicsDataFactory::Context& ctx) -> bool {
buildResources(ctx, res);
m_vertsBinding.init(ctx, res, 8, m_viewVertBlockBuf);
return true;
});
m_headText = std::make_unique<TextView>(res, *this, res.m_mainFont);
m_scroll.m_view = std::make_unique<ScrollView>(res, *this, ScrollView::Style::ThinIndicator);
m_content = std::make_unique<ContentView>(res, *this);
m_scroll.m_view->setContentView(m_content.get());
reset(rootNode);
}
Menu::~Menu() = default;
void Menu::reset(IMenuNode* rootNode) {
m_rootNode = rootNode;
m_thisNode = rootNode;
ViewResources& res = rootView().viewRes();
for (int i = 0; i < 8; ++i)
m_verts[i].m_color = res.themeData().tooltipBackground();
m_vertsBinding.load<decltype(m_verts)>(m_verts);
m_subMenu.reset();
const std::string* headText = rootNode->text();
m_headText->typesetGlyphs(headText ? *headText : "", rootView().themeData().uiAltText());
float pf = rootView().viewRes().pixelFactor();
int itemAdv = (ROW_HEIGHT + ITEM_MARGIN * 2) * pf;
m_cWidth = m_headText->nominalWidth() + 10 * pf;
m_cHeight = headText ? itemAdv : 0;
m_cTop = m_cHeight;
size_t subCount = rootNode->subNodeCount();
m_items.clear();
if (subCount) {
m_items.reserve(subCount);
for (size_t i = 0; i < subCount; ++i) {
IMenuNode* node = rootNode->subNode(i);
const std::string* nodeText = node->text();
ViewChild<std::unique_ptr<ItemView>>& item = m_items.emplace_back();
if (nodeText != nullptr) {
item.m_view = std::make_unique<ItemView>(res, *this, *nodeText, i, node);
m_cWidth = std::max(m_cWidth, int(item.m_view->m_textView->nominalWidth() + 10 * pf));
}
m_cHeight += itemAdv;
}
}
}
Menu::Menu(ViewResources& res, View& parentView, IMenuNode* rootNode, IMenuNode* thisNode)
: View(res, parentView), m_rootNode(rootNode), m_thisNode(thisNode) {
commitResources(res, [&](boo::IGraphicsDataFactory::Context& ctx) -> bool {
buildResources(ctx, res);
m_vertsBinding.init(ctx, res, 8, m_viewVertBlockBuf);
return true;
});
m_headText = std::make_unique<TextView>(res, *this, res.m_mainFont);
m_scroll.m_view = std::make_unique<ScrollView>(res, *this, ScrollView::Style::ThinIndicator);
m_content = std::make_unique<ContentView>(res, *this);
m_scroll.m_view->setContentView(m_content.get());
}
Menu::ContentView::ContentView(ViewResources& res, Menu& menu) : View(res, menu), m_menu(menu) {
commitResources(res, [&](boo::IGraphicsDataFactory::Context& ctx) -> bool {
buildResources(ctx, res);
m_hlVertsBinding.init(ctx, res, 4, m_viewVertBlockBuf);
return true;
});
m_hlVerts[0].m_color = res.themeData().button1Hover();
m_hlVerts[1].m_color = res.themeData().button2Hover();
m_hlVerts[2].m_color = res.themeData().button1Hover();
m_hlVerts[3].m_color = res.themeData().button2Hover();
}
Menu::ItemView::ItemView(ViewResources& res, Menu& menu, std::string_view text, size_t idx, IMenuNode* node)
: View(res, menu), m_menu(menu), m_idx(idx), m_node(node) {
commitResources(res, [&](boo::IGraphicsDataFactory::Context& ctx) -> bool {
buildResources(ctx, res);
return true;
});
m_textView = std::make_unique<TextView>(res, *this, res.m_mainFont);
m_textView->typesetGlyphs(text, res.themeData().uiText());
}
void Menu::setVerts(int width, int height, float pf) {
m_verts[0].m_pos.assign(0, height - m_cTop - pf, 0);
m_verts[1].m_pos.assign(0, 0, 0);
m_verts[2].m_pos.assign(width, height - m_cTop - pf, 0);
m_verts[3].m_pos.assign(width, 0, 0);
m_verts[4].m_pos.assign(0, height, 0);
m_verts[5].m_pos.assign(0, height - m_cTop + pf, 0);
m_verts[6].m_pos.assign(width, height, 0);
m_verts[7].m_pos.assign(width, height - m_cTop + pf, 0);
m_vertsBinding.load<decltype(m_verts)>(m_verts);
}
void Menu::ContentView::setHighlightedItem(size_t idx) {
if (idx == SIZE_MAX) {
m_highlightedItem = SIZE_MAX;
return;
}
ViewChild<std::unique_ptr<ItemView>>& vc = m_menu.m_items[idx];
if (!vc.m_view) {
m_highlightedItem = SIZE_MAX;
return;
}
m_highlightedItem = idx;
const boo::SWindowRect& bgRect = subRect();
const boo::SWindowRect& itemRect = vc.m_view->subRect();
int y = itemRect.location[1] - bgRect.location[1];
m_hlVerts[0].m_pos.assign(0, y + itemRect.size[1], 0);
m_hlVerts[1].m_pos.assign(0, y, 0);
m_hlVerts[2].m_pos.assign(itemRect.size[0], y + itemRect.size[1], 0);
m_hlVerts[3].m_pos.assign(itemRect.size[0], y, 0);
m_hlVertsBinding.load<decltype(m_hlVerts)>(m_hlVerts);
}
void Menu::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
m_scroll.mouseDown(coord, button, mod);
}
void Menu::ContentView::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
for (ViewChild<std::unique_ptr<ItemView>>& v : m_menu.m_items)
v.mouseDown(coord, button, mod);
}
void Menu::ItemView::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {}
void Menu::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
m_scroll.mouseUp(coord, button, mod);
if (m_deferredActivation) {
IMenuNode* node = m_deferredActivation;
m_deferredActivation = nullptr;
node->activated(coord);
}
}
void Menu::ContentView::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
for (ViewChild<std::unique_ptr<ItemView>>& v : m_menu.m_items)
v.mouseUp(coord, button, mod);
}
void Menu::ItemView::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
if (m_menu.m_content->m_highlightedItem == m_idx)
m_menu.m_deferredActivation = m_node;
}
void Menu::mouseMove(const boo::SWindowCoord& coord) { m_scroll.mouseMove(coord); }
void Menu::ContentView::mouseMove(const boo::SWindowCoord& coord) {
for (ViewChild<std::unique_ptr<ItemView>>& v : m_menu.m_items)
v.mouseMove(coord);
}
void Menu::ItemView::mouseEnter(const boo::SWindowCoord& coord) { m_menu.m_content->setHighlightedItem(m_idx); }
void Menu::mouseLeave(const boo::SWindowCoord& coord) { m_scroll.mouseLeave(coord); }
void Menu::ContentView::mouseLeave(const boo::SWindowCoord& coord) {
for (ViewChild<std::unique_ptr<ItemView>>& v : m_menu.m_items)
v.mouseLeave(coord);
}
void Menu::ItemView::mouseLeave(const boo::SWindowCoord& coord) { m_menu.m_content->unsetHighlightedItem(m_idx); }
void Menu::scroll(const boo::SWindowCoord& coord, const boo::SScrollDelta& scroll) { m_scroll.scroll(coord, scroll); }
void Menu::think() { m_scroll.m_view->think(); }
void Menu::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
float pf = rootView().viewRes().pixelFactor();
boo::SWindowRect rect = sub;
rect.size[0] = m_cWidth;
if (rect.location[1] - m_cHeight < 0) {
rect.location[1] += ROW_HEIGHT * pf;
rect.size[1] = std::min(root.size[1] - rect.location[1], m_cHeight);
} else {
rect.location[1] -= m_cHeight;
rect.size[1] = m_cHeight;
}
View::resized(root, rect);
m_scroll.m_view->resized(root, rect);
setVerts(rect.size[0], rect.size[1], pf);
rect.location[0] += 5 * pf;
rect.location[1] += rect.size[1] - (ROW_HEIGHT + ITEM_MARGIN - 5) * pf;
rect.size[0] = m_headText->nominalWidth();
rect.size[1] = m_headText->nominalHeight();
m_headText->resized(root, rect);
}
void Menu::ContentView::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub,
const boo::SWindowRect& scissor) {
View::resized(root, sub);
float pf = rootView().viewRes().pixelFactor();
m_scissorRect = scissor;
boo::SWindowRect itemRect = sub;
itemRect.size[0] = m_menu.m_cWidth;
itemRect.size[1] = ROW_HEIGHT * pf;
itemRect.location[1] += sub.size[1] - m_menu.m_cTop + ITEM_MARGIN * pf;
int itemAdv = (ROW_HEIGHT + ITEM_MARGIN * 2) * pf;
for (ViewChild<std::unique_ptr<ItemView>>& c : m_menu.m_items) {
itemRect.location[1] -= itemAdv;
if (c.m_view)
c.m_view->resized(root, itemRect);
}
}
void Menu::ItemView::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
View::resized(root, sub);
float pf = rootView().viewRes().pixelFactor();
boo::SWindowRect textRect = sub;
textRect.location[0] += 5 * pf;
textRect.location[1] += 5 * pf;
m_textView->resized(root, textRect);
}
void Menu::draw(boo::IGraphicsCommandQueue* gfxQ) {
View::draw(gfxQ);
gfxQ->setShaderDataBinding(m_vertsBinding);
gfxQ->draw(0, 4);
m_scroll.m_view->draw(gfxQ);
gfxQ->setShaderDataBinding(m_vertsBinding);
gfxQ->draw(4, 4);
m_headText->draw(gfxQ);
}
void Menu::ContentView::draw(boo::IGraphicsCommandQueue* gfxQ) {
View::draw(gfxQ);
gfxQ->setScissor(m_scissorRect);
if (m_highlightedItem != SIZE_MAX) {
gfxQ->setShaderDataBinding(m_hlVertsBinding);
gfxQ->draw(0, 4);
}
for (ViewChild<std::unique_ptr<ItemView>>& c : m_menu.m_items)
if (c.m_view)
c.m_view->draw(gfxQ);
gfxQ->setScissor(rootView().subRect());
}
void Menu::ItemView::draw(boo::IGraphicsCommandQueue* gfxQ) {
View::draw(gfxQ);
m_textView->draw(gfxQ);
}
} // namespace specter

View File

@ -0,0 +1,113 @@
#include "specter/MessageWindow.hpp"
#include "specter/IViewManager.hpp"
#include "specter/Menu.hpp"
#include "specter/MultiLineTextView.hpp"
#include "specter/RootView.hpp"
#include "specter/ViewResources.hpp"
#include <locale.hpp>
#include <zeus/CColor.hpp>
namespace specter {
MessageWindow::MessageWindow(ViewResources& res, View& parentView, Type type, std::string_view message,
std::function<void(bool)> func)
: ModalWindow(res, parentView, RectangleConstraint(),
type == Type::ErrorOk ? res.themeData().splashErrorBackground() : res.themeData().splashBackground())
, m_type(type)
, m_func(std::move(func))
, m_okBind(*this, rootView().viewManager().translate<locale::ok>())
, m_cancelBind(*this, rootView().viewManager().translate<locale::cancel>()) {
m_text = std::make_unique<MultiLineTextView>(res, *this, res.m_mainFont, TextView::Alignment::Center);
m_text->typesetGlyphs(message, res.themeData().uiText(), 380 * res.pixelFactor());
constraint() = RectangleConstraint(400 * res.pixelFactor(), 80 * res.pixelFactor() + m_text->nominalHeight());
m_ok.m_view = std::make_unique<Button>(res, *this, &m_okBind, m_okBind.m_name, nullptr, Button::Style::Block,
zeus::skWhite, RectangleConstraint(150 * res.pixelFactor()));
if (type == Type::ConfirmOkCancel) {
m_cancel.m_view =
std::make_unique<Button>(res, *this, &m_cancelBind, m_cancelBind.m_name, nullptr, Button::Style::Block,
zeus::skWhite, RectangleConstraint(150 * res.pixelFactor()));
}
updateContentOpacity(0.0);
}
MessageWindow::~MessageWindow() = default;
void MessageWindow::updateContentOpacity(float opacity) {
zeus::CColor color = zeus::CColor::lerp({1, 1, 1, 0}, {1, 1, 1, 1}, opacity);
ModalWindow::setMultiplyColor(color);
m_text->setMultiplyColor(color);
m_ok.m_view->setMultiplyColor(color);
m_cancel.m_view->setMultiplyColor(color);
}
void MessageWindow::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mods) {
if (closed() || skipBuildInAnimation())
return;
m_ok.mouseDown(coord, button, mods);
m_cancel.mouseDown(coord, button, mods);
}
void MessageWindow::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mods) {
if (closed())
return;
m_ok.mouseUp(coord, button, mods);
m_cancel.mouseUp(coord, button, mods);
}
void MessageWindow::mouseMove(const boo::SWindowCoord& coord) {
if (closed())
return;
m_ok.mouseMove(coord);
m_cancel.mouseMove(coord);
}
void MessageWindow::mouseEnter(const boo::SWindowCoord& coord) {
if (closed())
return;
m_ok.mouseEnter(coord);
m_cancel.mouseEnter(coord);
}
void MessageWindow::mouseLeave(const boo::SWindowCoord& coord) {
if (closed())
return;
m_ok.mouseLeave(coord);
m_cancel.mouseLeave(coord);
}
void MessageWindow::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
ModalWindow::resized(root, sub);
boo::SWindowRect buttonRect = subRect();
float pf = rootView().viewRes().pixelFactor();
buttonRect.location[1] += 20 * pf;
buttonRect.size[0] = m_ok.m_view->nominalWidth();
buttonRect.size[1] = m_ok.m_view->nominalHeight();
if (m_type == Type::ConfirmOkCancel) {
buttonRect.location[0] += 45 * pf;
m_ok.m_view->resized(root, buttonRect);
buttonRect.location[0] += 160 * pf;
m_cancel.m_view->resized(root, buttonRect);
} else {
buttonRect.location[0] += 125 * pf;
m_ok.m_view->resized(root, buttonRect);
}
boo::SWindowRect textRect = subRect();
textRect.location[0] += 200 * pf;
textRect.location[1] += 65 * pf;
m_text->resized(root, textRect);
}
void MessageWindow::draw(boo::IGraphicsCommandQueue* gfxQ) {
ModalWindow::draw(gfxQ);
m_text->draw(gfxQ);
m_ok.m_view->draw(gfxQ);
if (m_type == Type::ConfirmOkCancel)
m_cancel.m_view->draw(gfxQ);
}
} // namespace specter

474
specter/lib/ModalWindow.cpp Normal file
View File

@ -0,0 +1,474 @@
#include "specter/ModalWindow.hpp"
#include <algorithm>
#include "specter/MultiLineTextView.hpp"
#include "specter/RootView.hpp"
#include "specter/ViewResources.hpp"
#include <boo/System.hpp>
#include <boo/graphicsdev/IGraphicsCommandQueue.hpp>
namespace specter {
#define WIRE_START 0
#define WIRE_FRAMES 40
#define SOLID_START 30
#define SOLID_FRAMES 20
#define CONTENT_START 40
#define CONTENT_FRAMES 10
#define LINE_WIDTH 2
#define CONTENT_MARGIN 10
#define WINDOW_MIN_DIM 16
void ModalWindow::setLineVerts(int width, int height, float pf, float t) {
const std::pair<int, int> margin = m_cornersOutline[0]->queryGlyphDimensions(0);
const float t1 = std::clamp(t * 2.f, 0.f, 1.f);
const float t2 = std::clamp(t * 2.f - 1.f, 0.f, 1.f);
float lineLeft = 0;
float lineRight = pf * LINE_WIDTH;
float lineTop = height - margin.second;
float lineBottom = margin.second;
m_verts.lineVerts[0].m_pos.assign(lineLeft, lineTop, 0);
m_verts.lineVerts[1].m_pos = zeus::CVector3f::lerp({lineLeft, lineTop, 0}, {lineLeft, lineBottom, 0}, t1);
m_verts.lineVerts[2].m_pos.assign(lineRight, lineTop, 0);
m_verts.lineVerts[3].m_pos = zeus::CVector3f::lerp({lineRight, lineTop, 0}, {lineRight, lineBottom, 0}, t1);
m_verts.lineVerts[4].m_pos = m_verts.lineVerts[3].m_pos;
lineLeft = margin.first;
lineRight = width - margin.first;
lineTop = height;
lineBottom = height - pf * LINE_WIDTH;
m_verts.lineVerts[5].m_pos.assign(lineLeft, lineTop, 0);
m_verts.lineVerts[6].m_pos = m_verts.lineVerts[5].m_pos;
m_verts.lineVerts[7].m_pos.assign(lineLeft, lineBottom, 0);
m_verts.lineVerts[8].m_pos = zeus::CVector3f::lerp({lineLeft, lineTop, 0}, {lineRight, lineTop, 0}, t1);
m_verts.lineVerts[9].m_pos = zeus::CVector3f::lerp({lineLeft, lineBottom, 0}, {lineRight, lineBottom, 0}, t1);
m_verts.lineVerts[10].m_pos = m_verts.lineVerts[9].m_pos;
lineLeft = width - pf * LINE_WIDTH;
lineRight = width;
lineTop = height - margin.second;
lineBottom = margin.second;
m_verts.lineVerts[11].m_pos.assign(lineLeft, lineTop, 0);
m_verts.lineVerts[12].m_pos = m_verts.lineVerts[11].m_pos;
m_verts.lineVerts[13].m_pos = zeus::CVector3f::lerp({lineLeft, lineTop, 0}, {lineLeft, lineBottom, 0}, t2);
m_verts.lineVerts[14].m_pos.assign(lineRight, lineTop, 0);
m_verts.lineVerts[15].m_pos = zeus::CVector3f::lerp({lineRight, lineTop, 0}, {lineRight, lineBottom, 0}, t2);
m_verts.lineVerts[16].m_pos = m_verts.lineVerts[15].m_pos;
lineLeft = margin.first;
lineRight = width - margin.first;
lineTop = pf * LINE_WIDTH;
lineBottom = 0;
m_verts.lineVerts[17].m_pos.assign(lineLeft, lineTop, 0);
m_verts.lineVerts[18].m_pos = m_verts.lineVerts[17].m_pos;
m_verts.lineVerts[19].m_pos.assign(lineLeft, lineBottom, 0);
m_verts.lineVerts[20].m_pos = zeus::CVector3f::lerp({lineLeft, lineTop, 0}, {lineRight, lineTop, 0}, t2);
m_verts.lineVerts[21].m_pos = zeus::CVector3f::lerp({lineLeft, lineBottom, 0}, {lineRight, lineBottom, 0}, t2);
}
void ModalWindow::setLineVertsOut(int width, int height, float pf, float t) {
const std::pair<int, int> margin = m_cornersOutline[0]->queryGlyphDimensions(0);
const float t1 = std::clamp(t * 2.f - 1.f, 0.f, 1.f);
const float t2 = std::clamp(t * 2.f, 0.f, 1.f);
float lineLeft = 0;
float lineRight = pf * LINE_WIDTH;
float lineTop = height - margin.second;
float lineBottom = margin.second;
m_verts.lineVerts[0].m_pos = zeus::CVector3f::lerp({lineLeft, lineBottom, 0}, {lineLeft, lineTop, 0}, t1);
m_verts.lineVerts[1].m_pos.assign(lineLeft, lineBottom, 0);
m_verts.lineVerts[2].m_pos = zeus::CVector3f::lerp({lineRight, lineBottom, 0}, {lineRight, lineTop, 0}, t1);
m_verts.lineVerts[3].m_pos.assign(lineRight, lineBottom, 0);
m_verts.lineVerts[4].m_pos = m_verts.lineVerts[3].m_pos;
lineLeft = margin.first;
lineRight = width - margin.first;
lineTop = height;
lineBottom = height - pf * LINE_WIDTH;
m_verts.lineVerts[5].m_pos = zeus::CVector3f::lerp({lineRight, lineTop, 0}, {lineLeft, lineTop, 0}, t1);
m_verts.lineVerts[6].m_pos = m_verts.lineVerts[5].m_pos;
m_verts.lineVerts[7].m_pos = zeus::CVector3f::lerp({lineRight, lineBottom, 0}, {lineLeft, lineBottom, 0}, t1);
m_verts.lineVerts[8].m_pos.assign(lineRight, lineTop, 0);
m_verts.lineVerts[9].m_pos.assign(lineRight, lineBottom, 0);
m_verts.lineVerts[10].m_pos = m_verts.lineVerts[9].m_pos;
lineLeft = width - pf * LINE_WIDTH;
lineRight = width;
lineTop = height - margin.second;
lineBottom = margin.second;
m_verts.lineVerts[11].m_pos = zeus::CVector3f::lerp({lineLeft, lineBottom, 0}, {lineLeft, lineTop, 0}, t2);
m_verts.lineVerts[12].m_pos = m_verts.lineVerts[11].m_pos;
m_verts.lineVerts[13].m_pos.assign(lineLeft, lineBottom, 0);
m_verts.lineVerts[14].m_pos = zeus::CVector3f::lerp({lineRight, lineBottom, 0}, {lineRight, lineTop, 0}, t2);
m_verts.lineVerts[15].m_pos.assign(lineRight, lineBottom, 0);
m_verts.lineVerts[16].m_pos = m_verts.lineVerts[15].m_pos;
lineLeft = margin.first;
lineRight = width - margin.first;
lineTop = pf * LINE_WIDTH;
lineBottom = 0;
m_verts.lineVerts[17].m_pos = zeus::CVector3f::lerp({lineRight, lineTop, 0}, {lineLeft, lineTop, 0}, t2);
m_verts.lineVerts[18].m_pos = m_verts.lineVerts[17].m_pos;
m_verts.lineVerts[19].m_pos = zeus::CVector3f::lerp({lineRight, lineBottom, 0}, {lineLeft, lineBottom, 0}, t2);
m_verts.lineVerts[20].m_pos.assign(lineRight, lineTop, 0);
m_verts.lineVerts[21].m_pos.assign(lineRight, lineBottom, 0);
}
void ModalWindow::setLineColors(float t) {
const float t1 = std::clamp(t * 2.f, 0.f, 1.f);
const float t2 = std::clamp(t * 2.f - 1.f, 0.f, 1.f);
const float t3 = std::clamp(t * 2.f - 2.f, 0.f, 1.f);
const zeus::CColor c1 = zeus::CColor::lerp(m_line1, m_line2, t1);
const zeus::CColor c2 = zeus::CColor::lerp(m_line1, m_line2, t2);
const zeus::CColor c3 = zeus::CColor::lerp(m_line1, m_line2, t3);
m_cornersOutline[0]->colorGlyphs(c1);
if (t < 0.5) {
m_cornersOutline[1]->colorGlyphs(zeus::skClear);
m_cornersOutline[2]->colorGlyphs(zeus::skClear);
m_cornersOutline[3]->colorGlyphs(zeus::skClear);
} else if (t < 1.0) {
m_cornersOutline[1]->colorGlyphs(c2);
m_cornersOutline[2]->colorGlyphs(zeus::skClear);
m_cornersOutline[3]->colorGlyphs(c2);
} else {
m_cornersOutline[1]->colorGlyphs(c2);
m_cornersOutline[2]->colorGlyphs(c3);
m_cornersOutline[3]->colorGlyphs(c2);
}
m_verts.lineVerts[0].m_color = c1;
m_verts.lineVerts[1].m_color = c2;
m_verts.lineVerts[2].m_color = m_verts.lineVerts[0].m_color;
m_verts.lineVerts[3].m_color = m_verts.lineVerts[1].m_color;
m_verts.lineVerts[4].m_color = m_verts.lineVerts[3].m_color;
m_verts.lineVerts[5].m_color = c1;
m_verts.lineVerts[6].m_color = m_verts.lineVerts[5].m_color;
m_verts.lineVerts[7].m_color = m_verts.lineVerts[6].m_color;
m_verts.lineVerts[8].m_color = c2;
m_verts.lineVerts[9].m_color = m_verts.lineVerts[8].m_color;
m_verts.lineVerts[10].m_color = m_verts.lineVerts[9].m_color;
m_verts.lineVerts[11].m_color = c2;
m_verts.lineVerts[12].m_color = m_verts.lineVerts[11].m_color;
m_verts.lineVerts[13].m_color = c3;
m_verts.lineVerts[14].m_color = m_verts.lineVerts[12].m_color;
m_verts.lineVerts[15].m_color = m_verts.lineVerts[13].m_color;
m_verts.lineVerts[16].m_color = m_verts.lineVerts[15].m_color;
m_verts.lineVerts[17].m_color = c2;
m_verts.lineVerts[18].m_color = m_verts.lineVerts[17].m_color;
m_verts.lineVerts[19].m_color = m_verts.lineVerts[18].m_color;
m_verts.lineVerts[20].m_color = c3;
m_verts.lineVerts[21].m_color = m_verts.lineVerts[20].m_color;
}
void ModalWindow::setLineColorsOut(float t) {
const float t1 = std::clamp(t * 2.f, 0.f, 1.f);
const float t2 = std::clamp(t * 2.f - 1.f, 0.f, 1.f);
const float t3 = std::clamp(t * 2.f - 2.f, 0.f, 1.f);
const zeus::CColor c1 = zeus::CColor::lerp(m_line2Clear, m_line2, t1);
const zeus::CColor c2 = zeus::CColor::lerp(m_line2Clear, m_line2, t2);
const zeus::CColor c3 = zeus::CColor::lerp(m_line2Clear, m_line2, t3);
m_cornersOutline[2]->colorGlyphs(c1);
if (t < 0.5) {
m_cornersOutline[1]->colorGlyphs(zeus::skClear);
m_cornersOutline[0]->colorGlyphs(zeus::skClear);
m_cornersOutline[3]->colorGlyphs(zeus::skClear);
} else if (t < 1.0) {
m_cornersOutline[1]->colorGlyphs(c2);
m_cornersOutline[0]->colorGlyphs(zeus::skClear);
m_cornersOutline[3]->colorGlyphs(c2);
} else {
m_cornersOutline[1]->colorGlyphs(c2);
m_cornersOutline[0]->colorGlyphs(c3);
m_cornersOutline[3]->colorGlyphs(c2);
}
m_verts.lineVerts[0].m_color = c3;
m_verts.lineVerts[1].m_color = c2;
m_verts.lineVerts[2].m_color = m_verts.lineVerts[0].m_color;
m_verts.lineVerts[3].m_color = m_verts.lineVerts[1].m_color;
m_verts.lineVerts[4].m_color = m_verts.lineVerts[3].m_color;
m_verts.lineVerts[5].m_color = c3;
m_verts.lineVerts[6].m_color = m_verts.lineVerts[5].m_color;
m_verts.lineVerts[7].m_color = m_verts.lineVerts[6].m_color;
m_verts.lineVerts[8].m_color = c2;
m_verts.lineVerts[9].m_color = m_verts.lineVerts[8].m_color;
m_verts.lineVerts[10].m_color = m_verts.lineVerts[9].m_color;
m_verts.lineVerts[11].m_color = c2;
m_verts.lineVerts[12].m_color = m_verts.lineVerts[11].m_color;
m_verts.lineVerts[13].m_color = c1;
m_verts.lineVerts[14].m_color = m_verts.lineVerts[12].m_color;
m_verts.lineVerts[15].m_color = m_verts.lineVerts[13].m_color;
m_verts.lineVerts[16].m_color = m_verts.lineVerts[15].m_color;
m_verts.lineVerts[17].m_color = c2;
m_verts.lineVerts[18].m_color = m_verts.lineVerts[17].m_color;
m_verts.lineVerts[19].m_color = m_verts.lineVerts[18].m_color;
m_verts.lineVerts[20].m_color = c1;
m_verts.lineVerts[21].m_color = m_verts.lineVerts[20].m_color;
}
void ModalWindow::setFillVerts(int width, int height, float pf) {
std::pair<int, int> margin = m_cornersFilled[0]->queryGlyphDimensions(0);
float fillLeft = pf * LINE_WIDTH;
float fillRight = width - pf * LINE_WIDTH;
float fillTop = height - margin.second;
float fillBottom = margin.second;
m_verts.fillVerts[0].m_pos.assign(fillLeft, fillTop, 0);
m_verts.fillVerts[1].m_pos.assign(fillLeft, fillBottom, 0);
m_verts.fillVerts[2].m_pos.assign(fillRight, fillTop, 0);
m_verts.fillVerts[3].m_pos.assign(fillRight, fillBottom, 0);
m_verts.fillVerts[4].m_pos = m_verts.fillVerts[3].m_pos;
fillLeft = margin.first;
fillRight = width - margin.first;
fillTop = height - pf * LINE_WIDTH;
fillBottom = height - margin.second;
m_verts.fillVerts[5].m_pos.assign(fillLeft, fillTop, 0);
m_verts.fillVerts[6].m_pos = m_verts.fillVerts[5].m_pos;
m_verts.fillVerts[7].m_pos.assign(fillLeft, fillBottom, 0);
m_verts.fillVerts[8].m_pos.assign(fillRight, fillTop, 0);
m_verts.fillVerts[9].m_pos.assign(fillRight, fillBottom, 0);
m_verts.fillVerts[10].m_pos = m_verts.fillVerts[9].m_pos;
fillLeft = margin.first;
fillRight = width - margin.first;
fillTop = margin.second;
fillBottom = pf * LINE_WIDTH;
m_verts.fillVerts[11].m_pos.assign(fillLeft, fillTop, 0);
m_verts.fillVerts[12].m_pos = m_verts.fillVerts[11].m_pos;
m_verts.fillVerts[13].m_pos.assign(fillLeft, fillBottom, 0);
m_verts.fillVerts[14].m_pos.assign(fillRight, fillTop, 0);
m_verts.fillVerts[15].m_pos.assign(fillRight, fillBottom, 0);
}
void ModalWindow::setFillColors(float t) {
t = std::clamp(t, 0.f, 1.f);
zeus::CColor color = zeus::CColor::lerp(m_windowBgClear, m_windowBg, t);
for (int i = 0; i < 16; ++i)
m_verts.fillVerts[i].m_color = color;
for (int i = 0; i < 4; ++i)
m_cornersFilled[i]->colorGlyphs(color);
}
ModalWindow::ModalWindow(ViewResources& res, View& parentView, const RectangleConstraint& constraint)
: ModalWindow(res, parentView, constraint, res.themeData().splashBackground()) {}
ModalWindow::ModalWindow(ViewResources& res, View& parentView, const RectangleConstraint& constraint,
const zeus::CColor& bgColor)
: View(res, parentView)
, m_constraint(constraint)
, m_windowBg(bgColor)
, m_windowBgClear(m_windowBg)
, m_line1(res.themeData().splash1())
, m_line2(res.themeData().splash2())
, m_line2Clear(m_line2) {
m_windowBgClear[3] = 0.0;
m_line2Clear[3] = 0.0;
res.m_factory->commitTransaction([&](boo::IGraphicsDataFactory::Context& ctx) {
buildResources(ctx, res);
m_viewBlockBuf = res.m_viewRes.m_bufPool.allocateBlock(res.m_factory);
m_vertsBinding.init(ctx, res, 38, m_viewBlockBuf);
return true;
} BooTrace);
for (int i = 0; i < 4; ++i) {
m_cornersOutline[i] =
std::make_unique<TextView>(res, *this, res.m_curveFont, specter::TextView::Alignment::Left, 1);
m_cornersFilled[i] = std::make_unique<TextView>(res, *this, res.m_curveFont, specter::TextView::Alignment::Left, 1);
}
m_cornersOutline[0]->typesetGlyphs(L"\xF4F0");
m_cornersFilled[0]->typesetGlyphs(L"\xF4F1", res.themeData().splashBackground());
m_cornersOutline[1]->typesetGlyphs(L"\xF4F2");
m_cornersFilled[1]->typesetGlyphs(L"\xF4F3", res.themeData().splashBackground());
m_cornersOutline[2]->typesetGlyphs(L"\xF4F4");
m_cornersFilled[2]->typesetGlyphs(L"\xF4F5", res.themeData().splashBackground());
m_cornersOutline[3]->typesetGlyphs(L"\xF4F6");
m_cornersFilled[3]->typesetGlyphs(L"\xF4F7", res.themeData().splashBackground());
setLineColors(0.0);
setFillColors(0.0);
_loadVerts();
}
ModalWindow::~ModalWindow() = default;
static float CubicEase(float t) {
t *= 2.f;
if (t < 1)
return 1.f / 2.f * t * t * t;
t -= 2.f;
return 1.f / 2.f * (t * t * t + 2.f);
}
void ModalWindow::think() {
specter::ViewResources& res = rootView().viewRes();
float pf = res.pixelFactor();
switch (m_phase) {
case Phase::BuildIn: {
bool loadVerts = false;
int doneCount = 0;
if (m_frame > WIRE_START) {
float wt = (m_frame - WIRE_START) / float(WIRE_FRAMES);
wt = std::clamp(wt, 0.f, 2.f);
m_lineTime = CubicEase(wt);
setLineVerts(m_width, m_height, pf, m_lineTime);
setLineColors(wt);
if (wt == 2.f)
++doneCount;
loadVerts = true;
}
if (m_frame > SOLID_START) {
float ft = (m_frame - SOLID_START) / float(SOLID_FRAMES);
ft = std::clamp(ft, 0.f, 2.f);
setFillColors(ft);
if (ft == 2.f)
++doneCount;
loadVerts = true;
}
if (res.fontCacheReady() && m_frame > CONTENT_START) {
if (!m_contentStartFrame)
m_contentStartFrame = m_frame;
float tt = (m_frame - m_contentStartFrame) / float(CONTENT_FRAMES);
tt = std::clamp(tt, 0.f, 1.f);
updateContentOpacity(tt);
if (tt == 1.f)
++doneCount;
}
if (doneCount == 3)
m_phase = Phase::Showing;
if (loadVerts)
_loadVerts();
++m_frame;
break;
}
case Phase::ResWait: {
if (res.fontCacheReady()) {
updateContentOpacity(1.0);
m_phase = Phase::Showing;
}
break;
}
case Phase::BuildOut: {
{
float wt = (WIRE_FRAMES - m_frame) / float(WIRE_FRAMES);
wt = std::clamp(wt, 0.f, 1.f);
m_lineTime = CubicEase(wt);
setLineVertsOut(m_width, m_height, pf, m_lineTime);
setLineColorsOut(wt);
if (wt == 0.f)
m_phase = Phase::Done;
}
{
float ft = (SOLID_FRAMES - m_frame) / float(SOLID_FRAMES);
ft = std::clamp(ft, 0.f, 1.f);
setFillColors(ft);
}
if (res.fontCacheReady()) {
float tt = (CONTENT_FRAMES - m_frame) / float(CONTENT_FRAMES);
tt = std::clamp(tt, 0.f, 1.f);
updateContentOpacity(tt);
}
_loadVerts();
++m_frame;
break;
}
default:
break;
}
}
bool ModalWindow::skipBuildInAnimation() {
if (m_phase != Phase::BuildIn)
return false;
specter::ViewResources& res = rootView().viewRes();
float pf = res.pixelFactor();
m_lineTime = 1.0;
setLineVerts(m_width, m_height, pf, 1.0);
setLineColors(2.0);
setFillColors(2.0);
_loadVerts();
m_phase = Phase::ResWait;
return true;
}
void ModalWindow::close(bool skipAnimation) {
m_phase = skipAnimation ? Phase::Done : Phase::BuildOut;
m_frame = 0;
}
void ModalWindow::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
float pf = rootView().viewRes().pixelFactor();
boo::SWindowRect centerRect = sub;
std::pair<int, int> constrained =
m_constraint.solve(root.size[0] - CONTENT_MARGIN * pf * 2, root.size[1] - CONTENT_MARGIN * pf * 2);
m_width = std::max(constrained.first, int(WINDOW_MIN_DIM * pf));
m_height = std::max(constrained.second, int(WINDOW_MIN_DIM * pf));
centerRect.size[0] = m_width;
centerRect.size[1] = m_height;
centerRect.location[0] = root.size[0] / 2 - m_width / 2.0;
centerRect.location[1] = root.size[1] / 2 - m_height / 2.0;
View::resized(root, centerRect);
m_viewBlock.setViewRect(root, centerRect);
m_viewBlockBuf.access().finalAssign(m_viewBlock);
setLineVerts(m_width, m_height, pf, m_lineTime);
setFillVerts(m_width, m_height, pf);
_loadVerts();
boo::SWindowRect cornerRect = centerRect;
cornerRect.size[0] = cornerRect.size[1] = 8 * pf;
cornerRect.location[1] = centerRect.location[1] + m_height - 8 * pf;
m_cornersOutline[0]->resized(root, cornerRect);
m_cornersFilled[0]->resized(root, cornerRect);
cornerRect.location[0] = centerRect.location[0] + m_width - 8 * pf;
m_cornersOutline[1]->resized(root, cornerRect);
m_cornersFilled[1]->resized(root, cornerRect);
cornerRect.location[1] = centerRect.location[1];
m_cornersOutline[2]->resized(root, cornerRect);
m_cornersFilled[2]->resized(root, cornerRect);
cornerRect.location[0] = centerRect.location[0];
m_cornersOutline[3]->resized(root, cornerRect);
m_cornersFilled[3]->resized(root, cornerRect);
}
void ModalWindow::draw(boo::IGraphicsCommandQueue* gfxQ) {
if (m_phase == Phase::Done)
return;
gfxQ->setShaderDataBinding(m_vertsBinding);
gfxQ->draw(0, 22);
gfxQ->draw(22, 16);
m_cornersFilled[0]->draw(gfxQ);
m_cornersFilled[1]->draw(gfxQ);
m_cornersFilled[2]->draw(gfxQ);
m_cornersFilled[3]->draw(gfxQ);
m_cornersOutline[0]->draw(gfxQ);
m_cornersOutline[1]->draw(gfxQ);
m_cornersOutline[2]->draw(gfxQ);
m_cornersOutline[3]->draw(gfxQ);
}
} // namespace specter

View File

@ -0,0 +1,247 @@
#include "specter/MultiLineTextView.hpp"
#include "specter/ViewResources.hpp"
namespace specter {
static logvisor::Module Log("specter::MultiLineTextView");
std::string MultiLineTextView::LineWrap(std::string_view str, int wrap) {
size_t rem = str.size();
const utf8proc_uint8_t* it = reinterpret_cast<const utf8proc_uint8_t*>(str.data());
uint32_t lCh = UINT32_MAX;
int adv = 0;
std::string ret;
ret.reserve(str.size());
size_t lastSpaceRem;
const utf8proc_uint8_t* lastSpaceIt = nullptr;
size_t rollbackPos;
while (rem) {
utf8proc_int32_t ch;
utf8proc_ssize_t sz = utf8proc_iterate(it, -1, &ch);
if (sz < 0)
Log.report(logvisor::Fatal, FMT_STRING("invalid UTF-8 char"));
if (ch == '\n') {
ret += '\n';
lCh = -1;
rem -= sz;
it += sz;
lastSpaceIt = nullptr;
adv = 0;
continue;
}
const FontAtlas::Glyph* glyph = m_fontAtlas.lookupGlyph(ch);
if (!glyph) {
rem -= sz;
it += sz;
continue;
}
if (lCh != UINT32_MAX)
adv += TextView::DoKern(m_fontAtlas.lookupKern(lCh, glyph->m_glyphIdx), m_fontAtlas);
adv += glyph->m_advance;
if (adv > wrap && lastSpaceIt) {
ret.assign(ret.cbegin(), ret.cbegin() + rollbackPos);
ret += '\n';
lCh = UINT32_MAX;
rem = lastSpaceRem;
it = lastSpaceIt;
lastSpaceIt = nullptr;
adv = 0;
continue;
}
if (sz == 1 && (it[0] == ' ' || it[0] == '-' || it[0] == '/' || it[0] == '\\')) {
lastSpaceIt = it + 1;
lastSpaceRem = rem - 1;
rollbackPos = ret.size() + 1;
}
for (utf8proc_ssize_t i = 0; i < sz; ++i)
ret += it[i];
lCh = glyph->m_glyphIdx;
rem -= sz;
it += sz;
}
return ret;
}
std::wstring MultiLineTextView::LineWrap(std::wstring_view str, int wrap) {
uint32_t lCh = UINT32_MAX;
int adv = 0;
std::wstring ret;
ret.reserve(str.size());
std::wstring_view::const_iterator lastSpaceIt = str.cend();
size_t rollbackPos;
for (std::wstring_view::const_iterator it = str.cbegin(); it != str.cend(); ++it) {
wchar_t ch = *it;
if (ch == L'\n') {
ret += L'\n';
lCh = -1;
lastSpaceIt = str.cend();
adv = 0;
continue;
}
const FontAtlas::Glyph* glyph = m_fontAtlas.lookupGlyph(ch);
if (!glyph)
continue;
if (lCh != UINT32_MAX)
adv += TextView::DoKern(m_fontAtlas.lookupKern(lCh, glyph->m_glyphIdx), m_fontAtlas);
adv += glyph->m_advance;
if (adv > wrap && lastSpaceIt != str.cend()) {
ret.assign(ret.cbegin(), ret.cbegin() + rollbackPos);
ret += L'\n';
lCh = UINT32_MAX;
it = lastSpaceIt;
lastSpaceIt = str.cend();
adv = 0;
continue;
}
if (ch == L' ' || ch == L'-') {
lastSpaceIt = it + 1;
rollbackPos = ret.size() + 1;
}
ret += ch;
lCh = glyph->m_glyphIdx;
}
return ret;
}
MultiLineTextView::MultiLineTextView(ViewResources& res, View& parentView, const FontAtlas& font,
TextView::Alignment align, size_t lineCapacity, float lineHeight)
: View(res, parentView)
, m_viewSystem(res)
, m_fontAtlas(font)
, m_align(align)
, m_lineCapacity(lineCapacity)
, m_lineHeight(lineHeight) {
commitResources(res, [&](boo::IGraphicsDataFactory::Context& ctx) -> bool {
buildResources(ctx, res);
return true;
});
}
MultiLineTextView::MultiLineTextView(ViewResources& res, View& parentView, FontTag font, TextView::Alignment align,
size_t lineCapacity, float lineHeight)
: MultiLineTextView(res, parentView, res.m_textRes.m_fcache->lookupAtlas(font), align, lineCapacity, lineHeight) {}
void MultiLineTextView::typesetGlyphs(std::string_view str, const zeus::CColor& defaultColor, unsigned wrap) {
if (wrap) {
typesetGlyphs(LineWrap(str, wrap), defaultColor);
return;
}
m_width = 0;
size_t rem = str.size() + 1;
const utf8proc_uint8_t* it = reinterpret_cast<const utf8proc_uint8_t*>(str.data());
size_t lineCount = 0;
while (rem) {
utf8proc_int32_t ch;
utf8proc_ssize_t sz = utf8proc_iterate(it, -1, &ch);
if (sz < 0)
Log.report(logvisor::Fatal, FMT_STRING("invalid UTF-8 char"));
if (ch == '\n' || ch == '\0')
++lineCount;
rem -= sz;
it += sz;
}
m_lines.reserve(lineCount);
rem = str.size() + 1;
it = reinterpret_cast<const utf8proc_uint8_t*>(str.data());
const utf8proc_uint8_t* beginIt = it;
size_t lineIt = 0;
while (rem) {
utf8proc_int32_t ch;
utf8proc_ssize_t sz = utf8proc_iterate(it, -1, &ch);
if (ch == '\n' || ch == '\0') {
TextView& tv = (lineIt < m_lines.size()) ? *m_lines[lineIt]
: *m_lines.emplace_back(std::make_unique<TextView>(
m_viewSystem, *this, m_fontAtlas, m_align, m_lineCapacity));
tv.typesetGlyphs(std::string((char*)beginIt, it - beginIt), defaultColor);
m_width = std::max(m_width, tv.nominalWidth());
beginIt = it + 1;
++lineIt;
}
rem -= sz;
it += sz;
}
updateSize();
}
void MultiLineTextView::typesetGlyphs(std::wstring_view str, const zeus::CColor& defaultColor, unsigned wrap) {
if (wrap) {
typesetGlyphs(LineWrap(str, wrap), defaultColor);
return;
}
m_width = 0;
size_t rem = str.size() + 1;
auto it = str.cbegin();
size_t lineCount = 0;
while (rem) {
if (*it == L'\n' || *it == L'\0')
++lineCount;
--rem;
++it;
}
m_lines.reserve(lineCount);
rem = str.size() + 1;
it = str.cbegin();
auto beginIt = it;
size_t lineIt = 0;
while (rem) {
if (*it == L'\n' || *it == L'\0') {
TextView& tv = (lineIt < m_lines.size()) ? *m_lines[lineIt]
: *m_lines.emplace_back(std::make_unique<TextView>(
m_viewSystem, *this, m_fontAtlas, m_align, m_lineCapacity));
tv.typesetGlyphs(std::wstring(beginIt, it), defaultColor);
m_width = std::max(m_width, tv.nominalWidth());
beginIt = it + 1;
++lineIt;
}
--rem;
++it;
}
updateSize();
}
void MultiLineTextView::colorGlyphs(const zeus::CColor& newColor) {
for (std::unique_ptr<TextView>& tv : m_lines)
tv->colorGlyphs(newColor);
}
void MultiLineTextView::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
View::resized(root, sub);
unsigned lHeight = unsigned(m_lineHeight * m_fontAtlas.FT_LineHeight()) >> 6;
unsigned decumHeight = lHeight * m_lines.size();
boo::SWindowRect tsub = sub;
tsub.location[1] += decumHeight;
tsub.size[1] = 10;
for (std::unique_ptr<TextView>& tv : m_lines) {
tsub.location[1] -= lHeight;
tv->resized(root, tsub);
}
}
void MultiLineTextView::draw(boo::IGraphicsCommandQueue* gfxQ) {
View::draw(gfxQ);
for (std::unique_ptr<TextView>& tv : m_lines)
tv->draw(gfxQ);
}
} // namespace specter

127
specter/lib/PathButtons.cpp Normal file
View File

@ -0,0 +1,127 @@
#include "specter/PathButtons.hpp"
#include "specter/Button.hpp"
#include "specter/RootView.hpp"
#include "specter/ViewResources.hpp"
#include <boo/graphicsdev/IGraphicsCommandQueue.hpp>
namespace specter {
struct PathButtons::PathButton final : IButtonBinding {
PathButtons& m_pb;
size_t m_idx;
ViewChild<std::unique_ptr<Button>> m_button;
PathButton(PathButtons& pb, ViewResources& res, size_t idx, const hecl::SystemString& str) : m_pb(pb), m_idx(idx) {
m_button.m_view = std::make_unique<Button>(res, pb, this, hecl::SystemUTF8Conv(str).str());
}
std::string_view name(const Control* control) const override { return m_button.m_view->getText(); }
void activated(const Button* button, const boo::SWindowCoord&) override { m_pb.m_pathButtonPending = m_idx; }
};
struct PathButtons::ContentView : public View {
PathButtons& m_pb;
boo::SWindowRect m_scissorRect;
ContentView(ViewResources& res, PathButtons& pb) : View(res, pb), m_pb(pb) {}
void mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) override {
for (PathButton& b : m_pb.m_pathButtons) {
b.m_button.mouseDown(coord, button, mod);
}
}
void mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) override {
for (PathButton& b : m_pb.m_pathButtons) {
b.m_button.mouseUp(coord, button, mod);
}
if (m_pb.m_pathButtonPending >= 0) {
m_pb.m_binding.pathButtonActivated(m_pb.m_pathButtonPending);
m_pb.m_pathButtonPending = -1;
}
}
void mouseMove(const boo::SWindowCoord& coord) override {
for (PathButton& b : m_pb.m_pathButtons) {
b.m_button.mouseMove(coord);
}
}
void mouseLeave(const boo::SWindowCoord& coord) override {
for (PathButton& b : m_pb.m_pathButtons) {
b.m_button.mouseLeave(coord);
}
}
int nominalWidth() const override {
int ret = 0;
for (const PathButton& b : m_pb.m_pathButtons) {
ret += b.m_button.m_view->nominalWidth() + 2;
}
return ret;
}
int nominalHeight() const override {
return m_pb.m_pathButtons.size() ? m_pb.m_pathButtons[0].m_button.m_view->nominalHeight() : 0;
}
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub, const boo::SWindowRect& scissor) override {
View::resized(root, sub);
m_scissorRect = scissor;
m_scissorRect.size[1] += 2;
boo::SWindowRect pathRect = sub;
for (PathButton& b : m_pb.m_pathButtons) {
pathRect.size[0] = b.m_button.m_view->nominalWidth();
pathRect.size[1] = b.m_button.m_view->nominalHeight();
b.m_button.m_view->resized(root, pathRect);
pathRect.location[0] += pathRect.size[0] + 2;
}
}
void draw(boo::IGraphicsCommandQueue* gfxQ) override {
gfxQ->setScissor(m_scissorRect);
for (PathButton& b : m_pb.m_pathButtons) {
b.m_button.m_view->draw(gfxQ);
}
gfxQ->setScissor(rootView().subRect());
}
};
PathButtons::PathButtons(ViewResources& res, View& parentView, IPathButtonsBinding& binding, bool fillContainer)
: ScrollView(res, parentView, ScrollView::Style::SideButtons), m_binding(binding), m_fillContainer(fillContainer) {
m_contentView.m_view = std::make_unique<ContentView>(res, *this);
setContentView(m_contentView.m_view.get());
}
PathButtons::~PathButtons() = default;
void PathButtons::setButtons(const std::vector<hecl::SystemString>& comps) {
m_pathButtons.clear();
m_pathButtons.reserve(comps.size());
size_t idx = 0;
ViewResources& res = rootView().viewRes();
for (const hecl::SystemString& c : comps)
m_pathButtons.emplace_back(*this, res, idx++, c);
}
void PathButtons::setMultiplyColor(const zeus::CColor& color) {
ScrollView::setMultiplyColor(color);
for (PathButton& b : m_pathButtons)
b.m_button.m_view->setMultiplyColor(color);
}
void PathButtons::containerResized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
if (m_fillContainer) {
boo::SWindowRect fillRect = sub;
fillRect.size[1] = 20 * rootView().viewRes().pixelFactor();
View::resized(root, fillRect);
}
}
} // namespace specter

584
specter/lib/RootView.cpp Normal file
View File

@ -0,0 +1,584 @@
#include "specter/RootView.hpp"
#include "specter/Button.hpp"
#include "specter/Control.hpp"
#include "specter/IViewManager.hpp"
#include "specter/Menu.hpp"
#include "specter/Space.hpp"
#include "specter/Tooltip.hpp"
#include "specter/ViewResources.hpp"
#include <boo/graphicsdev/IGraphicsCommandQueue.hpp>
namespace specter {
RootView::RootView(IViewManager& viewMan, ViewResources& res, boo::IWindow* window)
: View(res), m_window(window), m_viewMan(viewMan), m_viewRes(&res), m_events(*this) {
window->setCallback(&m_events);
boo::SWindowRect rect = window->getWindowFrame();
commitResources(res, [&](boo::IGraphicsDataFactory::Context& ctx) {
buildResources(ctx, res);
m_splitMenuSystem.emplace(*this, ctx);
m_renderTex = ctx.newRenderTexture(rect.size[0], rect.size[1], boo::TextureClampMode::ClampToEdge, 3, 3);
return true;
});
resized(rect, rect);
}
RootView::~RootView() { m_window->setCallback(nullptr); }
RootView::SplitMenuSystem::SplitMenuSystem(RootView& rv, boo::IGraphicsDataFactory::Context& ctx)
: m_rv(rv)
, m_text(rv.m_viewMan.translate<locale::boundary_action>())
, m_splitActionNode(*this)
, m_joinActionNode(*this) {
ViewResources& res = *rv.m_viewRes;
m_viewVertBlockBuf = res.m_viewRes.m_bufPool.allocateBlock(res.m_factory);
m_vertsBinding.init(ctx, res, 32, m_viewVertBlockBuf);
zeus::CColor col = {0.0, 0.0, 0.0, 0.5};
for (int i = 0; i < 32; ++i)
m_verts[i].m_color = col;
m_verts[0].m_pos.assign(0.0, 0.25, 0);
m_verts[1].m_pos.assign(0.0, 0.0, 0);
m_verts[2].m_pos.assign(0.375, 0.25, 0);
m_verts[3].m_pos.assign(0.375, 0.0, 0);
m_verts[4].m_pos = m_verts[3].m_pos;
m_verts[5].m_pos.assign(0.0, 1.0, 0);
m_verts[6].m_pos = m_verts[5].m_pos;
m_verts[7].m_pos = m_verts[0].m_pos;
m_verts[8].m_pos.assign(0.5, 1.0, 0);
m_verts[9].m_pos.assign(0.25, 0.25, 0);
m_verts[10].m_pos.assign(0.5, 0.5, 0);
m_verts[11].m_pos = m_verts[10].m_pos;
m_verts[12].m_pos = m_verts[8].m_pos;
m_verts[13].m_pos = m_verts[12].m_pos;
m_verts[14].m_pos = m_verts[11].m_pos;
m_verts[15].m_pos.assign(1.0, 1.0, 0);
m_verts[16].m_pos.assign(1.0, 0.25, 0);
m_verts[17].m_pos = m_verts[16].m_pos;
m_verts[18].m_pos = m_verts[14].m_pos;
m_verts[19].m_pos = m_verts[18].m_pos;
m_verts[20].m_pos.assign(0.75, 0.25, 0);
m_verts[21].m_pos = m_verts[17].m_pos;
m_verts[22].m_pos = m_verts[21].m_pos;
m_verts[23].m_pos.assign(0.625, 0.25, 0);
m_verts[24].m_pos = m_verts[23].m_pos;
m_verts[25].m_pos.assign(0.625, 0.0, 0);
m_verts[26].m_pos = m_verts[22].m_pos;
m_verts[27].m_pos.assign(1.0, 0.0, 0);
m_verts[28].m_pos.assign(-1.0, 1.0, 0);
m_verts[29].m_pos.assign(-1.0, -1.0, 0);
m_verts[30].m_pos.assign(1.0, 1.0, 0);
m_verts[31].m_pos.assign(1.0, -1.0, 0);
m_vertsBinding.load<decltype(m_verts)>(m_verts);
}
RootView::SplitMenuSystem::SplitActionNode::SplitActionNode(SplitMenuSystem& smn)
: m_smn(smn), m_text(smn.m_rv.m_viewMan.translate<locale::split>()) {}
RootView::SplitMenuSystem::JoinActionNode::JoinActionNode(SplitMenuSystem& smn)
: m_smn(smn), m_text(smn.m_rv.m_viewMan.translate<locale::join>()) {}
void RootView::SplitMenuSystem::setArrowVerts(const boo::SWindowRect& rect, SplitView::ArrowDir dir) {
const boo::SWindowRect& root = m_rv.subRect();
if (dir == SplitView::ArrowDir::Left || dir == SplitView::ArrowDir::Right) {
m_viewBlock.m_mv[0][1] = 2.0f * rect.size[1] / float(root.size[1]);
m_viewBlock.m_mv[0][0] = 0.0f;
m_viewBlock.m_mv[1][0] =
2.0f * (dir == SplitView::ArrowDir::Left ? -rect.size[0] : rect.size[0]) / float(root.size[0]);
m_viewBlock.m_mv[1][1] = 0.0f;
m_viewBlock.m_mv[3][0] =
2.0f * (rect.location[0] + (dir == SplitView::ArrowDir::Left ? rect.size[0] : 0)) / float(root.size[0]) - 1.0f;
m_viewBlock.m_mv[3][1] = 2.0f * rect.location[1] / float(root.size[1]) - 1.0f;
} else {
m_viewBlock.m_mv[0][0] = 2.0f * rect.size[0] / float(root.size[0]);
m_viewBlock.m_mv[0][1] = 0.0f;
m_viewBlock.m_mv[1][1] =
2.0f * (dir == SplitView::ArrowDir::Down ? -rect.size[1] : rect.size[1]) / float(root.size[1]);
m_viewBlock.m_mv[1][0] = 0.0f;
m_viewBlock.m_mv[3][0] = 2.0f * rect.location[0] / float(root.size[0]) - 1.0f;
m_viewBlock.m_mv[3][1] =
2.0f * (rect.location[1] + (dir == SplitView::ArrowDir::Down ? rect.size[1] : 0)) / float(root.size[1]) - 1.0f;
}
m_viewVertBlockBuf.access().finalAssign(m_viewBlock);
}
void RootView::SplitMenuSystem::setLineVerts(const boo::SWindowRect& rect, float split, SplitView::Axis axis) {
const boo::SWindowRect& root = m_rv.subRect();
if (axis == SplitView::Axis::Horizontal) {
m_viewBlock.m_mv[0][0] = rect.size[0] / float(root.size[0]);
m_viewBlock.m_mv[0][1] = 0.0f;
m_viewBlock.m_mv[1][1] = 2.0f / float(root.size[1]);
m_viewBlock.m_mv[1][0] = 0.0f;
m_viewBlock.m_mv[3][0] = 2.0f * (rect.location[0] + rect.size[0] / 2.0f) / float(root.size[0]) - 1.0f;
m_viewBlock.m_mv[3][1] = (rect.location[1] + split * rect.size[1]) * m_viewBlock.m_mv[1][1] - 1.0f;
} else {
m_viewBlock.m_mv[0][0] = 2.0f / float(root.size[0]);
m_viewBlock.m_mv[0][1] = 0.0f;
m_viewBlock.m_mv[1][1] = rect.size[1] / float(root.size[1]);
m_viewBlock.m_mv[1][0] = 0.0f;
m_viewBlock.m_mv[3][0] = (rect.location[0] + split * rect.size[0]) * m_viewBlock.m_mv[0][0] - 1.0f;
m_viewBlock.m_mv[3][1] = 2.0f * (rect.location[1] + rect.size[1] / 2.0f) / float(root.size[1]) - 1.0f;
}
m_viewVertBlockBuf.access().finalAssign(m_viewBlock);
}
void RootView::destroyed() { m_destroyed = true; }
void RootView::resized(const boo::SWindowRect& root, const boo::SWindowRect&) {
m_rootRect = root;
m_rootRect.location[0] = 0;
m_rootRect.location[1] = 0;
View::resized(m_rootRect, m_rootRect);
for (View* v : m_views)
v->resized(m_rootRect, m_rootRect);
if (m_tooltip)
m_tooltip->resized(m_rootRect, m_rootRect);
if (m_rightClickMenu.m_view) {
float wr = root.size[0] / float(m_rightClickMenuRootAndLoc.size[0]);
float hr = root.size[1] / float(m_rightClickMenuRootAndLoc.size[1]);
m_rightClickMenuRootAndLoc.size[0] = root.size[0];
m_rightClickMenuRootAndLoc.size[1] = root.size[1];
m_rightClickMenuRootAndLoc.location[0] *= wr;
m_rightClickMenuRootAndLoc.location[1] *= hr;
m_rightClickMenu.m_view->resized(root, m_rightClickMenuRootAndLoc);
}
m_splitMenuSystem->resized();
m_resizeRTDirty = true;
}
void RootView::SplitMenuSystem::resized() {
if (m_phase == Phase::InteractiveJoin) {
boo::SWindowRect rect;
SplitView::ArrowDir arrow;
m_splitView->getJoinArrowHover(m_interactiveSlot, rect, arrow);
setArrowVerts(rect, arrow);
} else if (m_phase == Phase::InteractiveSplit) {
boo::SWindowRect rect;
SplitView::Axis axis;
m_splitView->getSplitLineHover(m_interactiveSlot, rect, axis);
setLineVerts(rect, m_interactiveSplit, axis);
}
}
void RootView::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mods) {
if (m_splitMenuSystem->m_phase != SplitMenuSystem::Phase::Inactive) {
m_splitMenuSystem->mouseDown(coord, button, mods);
return;
}
if (m_rightClickMenu.m_view) {
if (!m_rightClickMenu.mouseDown(coord, button, mods))
m_rightClickMenu.m_view.reset();
return;
}
if (m_activeMenuButton) {
ViewChild<std::unique_ptr<View>>& mv = m_activeMenuButton->getMenu();
if (!mv.mouseDown(coord, button, mods))
m_activeMenuButton->closeMenu(coord);
return;
}
if (m_hoverSplitDragView) {
if (button == boo::EMouseButton::Primary) {
m_activeSplitDragView = true;
m_hoverSplitDragView->startDragSplit(coord);
} else if (button == boo::EMouseButton::Secondary) {
m_splitMenuSystem->m_splitView = m_hoverSplitDragView;
adoptRightClickMenu(std::make_unique<specter::Menu>(*m_viewRes, *this, &*m_splitMenuSystem), coord);
}
return;
}
if (m_activeTextView && !m_activeTextView->subRect().coordInRect(coord))
setActiveTextView(nullptr);
for (View* v : m_views)
v->mouseDown(coord, button, mods);
}
void RootView::SplitMenuSystem::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button,
boo::EModifierKey mods) {
if (m_phase == Phase::InteractiveJoin) {
int origDummy;
SplitView* selSplit;
boo::SWindowRect rect;
SplitView::ArrowDir arrow;
if (m_splitView->testJoinArrowHover(coord, origDummy, selSplit, m_interactiveSlot, rect, arrow)) {
setArrowVerts(rect, arrow);
m_interactiveDown = true;
}
} else if (m_phase == Phase::InteractiveSplit) {
boo::SWindowRect rect;
SplitView::Axis axis;
if (m_splitView->testSplitLineHover(coord, m_interactiveSlot, rect, m_interactiveSplit, axis)) {
setLineVerts(rect, m_interactiveSplit, axis);
m_interactiveDown = true;
}
}
}
void RootView::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mods) {
if (m_splitMenuSystem->m_phase != SplitMenuSystem::Phase::Inactive) {
m_splitMenuSystem->mouseUp(coord, button, mods);
return;
}
if (m_rightClickMenu.m_view) {
m_rightClickMenu.mouseUp(coord, button, mods);
return;
}
if (m_activeMenuButton) {
ViewChild<std::unique_ptr<View>>& mv = m_activeMenuButton->getMenu();
mv.mouseUp(coord, button, mods);
return;
}
if (m_activeSplitDragView && button == boo::EMouseButton::Primary) {
m_activeSplitDragView = false;
m_hoverSplitDragView->endDragSplit();
m_spaceCornerHover = false;
m_hSplitHover = false;
m_vSplitHover = false;
_updateCursor();
}
for (View* v : m_views)
v->mouseUp(coord, button, mods);
}
void RootView::SplitMenuSystem::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button,
boo::EModifierKey mods) {
if (m_phase == Phase::InteractiveJoin) {
int origSlot;
SplitView* selSplit;
boo::SWindowRect rect;
SplitView::ArrowDir arrow;
if (m_splitView->testJoinArrowHover(coord, origSlot, selSplit, m_interactiveSlot, rect, arrow)) {
setArrowVerts(rect, arrow);
if (m_interactiveDown) {
m_interactiveDown = false;
m_phase = Phase::Inactive;
m_splitView->m_controller->joinViews(m_splitView, origSlot, selSplit, m_interactiveSlot);
}
}
} else if (m_phase == Phase::InteractiveSplit) {
boo::SWindowRect rect;
SplitView::Axis axis;
if (m_splitView->testSplitLineHover(coord, m_interactiveSlot, rect, m_interactiveSplit, axis)) {
setLineVerts(rect, m_interactiveSplit, axis);
if (m_interactiveDown) {
m_interactiveDown = false;
m_phase = Phase::Inactive;
Space* space = m_splitView->m_views[m_interactiveSlot].m_view->castToSpace();
if (space && space->m_controller.spaceSplitAllowed()) {
ISplitSpaceController* ss = space->m_controller.spaceSplit(axis, 0);
ss->splitView()->setSplit(m_interactiveSplit);
}
}
}
}
}
SplitView* RootView::recursiveTestSplitHover(SplitView* sv, const boo::SWindowCoord& coord) const {
if (sv->testSplitHover(coord))
return sv;
for (int i = 0; i < 2; ++i) {
SplitView* child = sv->m_views[i].m_view->castToSplitView();
if (child) {
SplitView* res = recursiveTestSplitHover(child, coord);
if (res)
return res;
}
}
return nullptr;
}
void RootView::mouseMove(const boo::SWindowCoord& coord) {
if (m_splitMenuSystem->m_phase != SplitMenuSystem::Phase::Inactive) {
m_splitMenuSystem->mouseMove(coord);
return;
}
if (m_rightClickMenu.m_view) {
m_hSplitHover = false;
m_vSplitHover = false;
_updateCursor();
m_rightClickMenu.mouseMove(coord);
return;
}
if (m_activeMenuButton) {
m_hSplitHover = false;
m_vSplitHover = false;
_updateCursor();
ViewChild<std::unique_ptr<View>>& mv = m_activeMenuButton->getMenu();
mv.mouseMove(coord);
return;
}
if (m_activeSplitDragView) {
m_hoverSplitDragView->moveDragSplit(coord);
m_spaceCornerHover = false;
if (m_hoverSplitDragView->axis() == SplitView::Axis::Horizontal)
setHorizontalSplitHover(true);
else
setVerticalSplitHover(true);
return;
}
m_hoverSplitDragView = nullptr;
if (!m_spaceCornerHover) {
for (View* v : m_views) {
SplitView* sv = v->castToSplitView();
if (sv)
sv = recursiveTestSplitHover(sv, coord);
if (sv) {
m_hoverSplitDragView = sv;
break;
} else {
m_hSplitHover = false;
m_vSplitHover = false;
_updateCursor();
}
}
}
if (m_activeDragView)
m_activeDragView->mouseMove(coord);
else {
for (View* v : m_views)
v->mouseMove(coord);
}
if (m_hoverSplitDragView) {
if (m_hoverSplitDragView->axis() == SplitView::Axis::Horizontal)
setHorizontalSplitHover(true);
else
setVerticalSplitHover(true);
}
boo::SWindowRect ttrect = m_rootRect;
ttrect.location[0] = coord.pixel[0];
ttrect.location[1] = coord.pixel[1];
if (m_tooltip) {
if (coord.pixel[0] + m_tooltip->nominalWidth() > m_rootRect.size[0])
ttrect.location[0] -= m_tooltip->nominalWidth();
if (coord.pixel[1] + m_tooltip->nominalHeight() > m_rootRect.size[1])
ttrect.location[1] -= m_tooltip->nominalHeight();
m_tooltip->resized(m_rootRect, ttrect);
}
}
void RootView::SplitMenuSystem::mouseMove(const boo::SWindowCoord& coord) {
if (m_phase == Phase::InteractiveJoin) {
int origDummy;
SplitView* selSplit;
boo::SWindowRect rect;
SplitView::ArrowDir arrow;
if (m_splitView->testJoinArrowHover(coord, origDummy, selSplit, m_interactiveSlot, rect, arrow))
setArrowVerts(rect, arrow);
} else if (m_phase == Phase::InteractiveSplit) {
boo::SWindowRect rect;
SplitView::Axis axis;
if (m_splitView->testSplitLineHover(coord, m_interactiveSlot, rect, m_interactiveSplit, axis))
setLineVerts(rect, m_interactiveSplit, axis);
}
}
void RootView::mouseEnter(const boo::SWindowCoord& coord) {
for (View* v : m_views)
v->mouseEnter(coord);
}
void RootView::mouseLeave(const boo::SWindowCoord& coord) {
if (m_splitMenuSystem->m_phase != SplitMenuSystem::Phase::Inactive) {
m_splitMenuSystem->mouseLeave(coord);
return;
}
if (m_rightClickMenu.m_view) {
m_rightClickMenu.mouseLeave(coord);
return;
}
if (m_activeMenuButton) {
ViewChild<std::unique_ptr<View>>& mv = m_activeMenuButton->getMenu();
mv.mouseLeave(coord);
return;
}
for (View* v : m_views)
v->mouseLeave(coord);
}
void RootView::SplitMenuSystem::mouseLeave(const boo::SWindowCoord& coord) {}
void RootView::scroll(const boo::SWindowCoord& coord, const boo::SScrollDelta& scroll) {
if (m_activeMenuButton) {
ViewChild<std::unique_ptr<View>>& mv = m_activeMenuButton->getMenu();
mv.scroll(coord, scroll);
return;
}
for (View* v : m_views)
v->scroll(coord, scroll);
}
void RootView::touchDown(const boo::STouchCoord& coord, uintptr_t tid) {
for (View* v : m_views)
v->touchDown(coord, tid);
}
void RootView::touchUp(const boo::STouchCoord& coord, uintptr_t tid) {
for (View* v : m_views)
v->touchUp(coord, tid);
}
void RootView::touchMove(const boo::STouchCoord& coord, uintptr_t tid) {
for (View* v : m_views)
v->touchMove(coord, tid);
}
void RootView::charKeyDown(unsigned long charCode, boo::EModifierKey mods, bool isRepeat) {
for (View* v : m_views)
v->charKeyDown(charCode, mods, isRepeat);
if (m_activeTextView && True(mods & (boo::EModifierKey::Ctrl | boo::EModifierKey::Command))) {
if (charCode == 'c' || charCode == 'C')
m_activeTextView->clipboardCopy();
else if (charCode == 'x' || charCode == 'X')
m_activeTextView->clipboardCut();
else if (charCode == 'v' || charCode == 'V')
m_activeTextView->clipboardPaste();
}
}
void RootView::charKeyUp(unsigned long charCode, boo::EModifierKey mods) {
for (View* v : m_views)
v->charKeyUp(charCode, mods);
}
void RootView::specialKeyDown(boo::ESpecialKey key, boo::EModifierKey mods, bool isRepeat) {
if (key == boo::ESpecialKey::Enter && True(mods & boo::EModifierKey::Alt)) {
m_window->setFullscreen(!m_window->isFullscreen());
return;
}
if (key == boo::ESpecialKey::Esc && m_splitMenuSystem->m_phase != SplitMenuSystem::Phase::Inactive) {
m_splitMenuSystem->m_phase = SplitMenuSystem::Phase::Inactive;
return;
}
for (View* v : m_views)
v->specialKeyDown(key, mods, isRepeat);
if (m_activeTextView)
m_activeTextView->specialKeyDown(key, mods, isRepeat);
}
void RootView::specialKeyUp(boo::ESpecialKey key, boo::EModifierKey mods) {
for (View* v : m_views)
v->specialKeyUp(key, mods);
if (m_activeTextView)
m_activeTextView->specialKeyUp(key, mods);
}
void RootView::modKeyDown(boo::EModifierKey mod, bool isRepeat) {
for (View* v : m_views)
v->modKeyDown(mod, isRepeat);
if (m_activeTextView)
m_activeTextView->modKeyDown(mod, isRepeat);
}
void RootView::modKeyUp(boo::EModifierKey mod) {
for (View* v : m_views)
v->modKeyUp(mod);
if (m_activeTextView)
m_activeTextView->modKeyUp(mod);
}
boo::ITextInputCallback* RootView::getTextInputCallback() { return m_activeTextView; }
void RootView::resetTooltip(ViewResources& res) {
m_tooltip = std::make_unique<Tooltip>(
res, *this, "Test",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi hendrerit nisl quis lobortis mattis. "
"Mauris efficitur, est a vestibulum iaculis, leo orci pellentesque nunc, non rutrum ipsum lectus "
"eget nisl. Aliquam accumsan vestibulum turpis. Duis id lacus ac lectus sollicitudin posuere vel sit "
"amet metus. Aenean nec tortor id enim efficitur accumsan vitae eu ante. Lorem ipsum dolor sit amet, "
"consectetur adipiscing elit. Fusce magna eros, lacinia a leo eget, volutpat rhoncus urna.");
}
void RootView::displayTooltip(std::string_view name, std::string_view help) {}
void RootView::internalThink() {
if (m_splitMenuSystem->m_deferredSplit) {
m_splitMenuSystem->m_deferredSplit = false;
m_rightClickMenu.m_view.reset();
m_splitMenuSystem->m_phase = SplitMenuSystem::Phase::InteractiveSplit;
m_splitMenuSystem->mouseMove(m_splitMenuSystem->m_deferredCoord);
}
if (m_splitMenuSystem->m_deferredJoin) {
m_splitMenuSystem->m_deferredJoin = false;
m_rightClickMenu.m_view.reset();
m_splitMenuSystem->m_phase = SplitMenuSystem::Phase::InteractiveJoin;
m_splitMenuSystem->mouseMove(m_splitMenuSystem->m_deferredCoord);
}
if (m_rightClickMenu.m_view)
m_rightClickMenu.m_view->think();
}
void RootView::setActiveTextView(ITextInputView* textView) {
if (m_activeTextView) {
m_activeTextView->setActive(false);
}
m_activeTextView = textView;
if (textView) {
textView->setActive(true);
}
}
void RootView::draw(boo::IGraphicsCommandQueue* gfxQ) {
if (m_resizeRTDirty) {
gfxQ->resizeRenderTexture(m_renderTex, m_rootRect.size[0], m_rootRect.size[1]);
m_resizeRTDirty = false;
gfxQ->schedulePostFrameHandler([&]() { m_events.m_resizeCv.notify_one(); });
}
m_viewRes->updateBuffers();
gfxQ->setRenderTarget(m_renderTex);
gfxQ->setViewport(m_rootRect);
gfxQ->setScissor(m_rootRect);
View::draw(gfxQ);
for (View* v : m_views)
v->draw(gfxQ);
if (m_tooltip)
m_tooltip->draw(gfxQ);
m_splitMenuSystem->draw(gfxQ);
if (m_rightClickMenu.m_view)
m_rightClickMenu.m_view->draw(gfxQ);
gfxQ->resolveDisplay(m_renderTex);
}
const IThemeData& RootView::themeData() const { return *m_viewRes->m_theme; }
void RootView::SplitMenuSystem::draw(boo::IGraphicsCommandQueue* gfxQ) {
if (m_phase == Phase::Inactive)
return;
gfxQ->setShaderDataBinding(m_vertsBinding);
if (m_phase == Phase::InteractiveJoin)
gfxQ->draw(0, 28);
else
gfxQ->draw(28, 4);
}
} // namespace specter

290
specter/lib/ScrollView.cpp Normal file
View File

@ -0,0 +1,290 @@
#include "specter/ScrollView.hpp"
#include <algorithm>
#include "specter/Button.hpp"
#include "specter/IViewManager.hpp"
#include "specter/RootView.hpp"
#include "specter/ViewResources.hpp"
#include <boo/graphicsdev/IGraphicsCommandQueue.hpp>
namespace specter {
#define MAX_SCROLL_SPEED 100
ScrollView::ScrollView(ViewResources& res, View& parentView, Style style)
: View(res, parentView), m_style(style), m_sideButtonBind(*this, rootView().viewManager()) {
commitResources(res, [&](boo::IGraphicsDataFactory::Context& ctx) -> bool {
buildResources(ctx, res);
m_vertsBinding.init(ctx, res, 4, m_viewVertBlockBuf);
return true;
});
if (style == Style::SideButtons) {
m_sideButtons[0].m_view = std::make_unique<Button>(res, *this, &m_sideButtonBind, "<");
m_sideButtons[1].m_view = std::make_unique<Button>(res, *this, &m_sideButtonBind, ">");
}
}
ScrollView::SideButtonBinding::SideButtonBinding(ScrollView& sv, IViewManager& vm)
: m_sv(sv), m_leftName(vm.translate<locale::scroll_left>()), m_rightName(vm.translate<locale::scroll_right>()) {}
std::string_view ScrollView::SideButtonBinding::name(const Control* control) const {
return (control == reinterpret_cast<Control*>(m_sv.m_sideButtons[0].m_view.get())) ? m_leftName.c_str()
: m_rightName.c_str();
}
void ScrollView::SideButtonBinding::down(const Button* button, [[maybe_unused]] const boo::SWindowCoord& coord) {
if (button == m_sv.m_sideButtons[0].m_view.get()) {
m_sv.m_sideButtonState = SideButtonState::ScrollRight;
} else {
m_sv.m_sideButtonState = SideButtonState::ScrollLeft;
}
}
void ScrollView::SideButtonBinding::up([[maybe_unused]] const Button* button,
[[maybe_unused]] const boo::SWindowCoord& coord) {
m_sv.m_sideButtonState = SideButtonState::None;
}
bool ScrollView::_scroll(const boo::SScrollDelta& scroll) {
if (m_contentView.m_view) {
float ratioX = subRect().size[0] / float(m_contentView.m_view->nominalWidth());
float ratioY = subRect().size[1] / float(m_contentView.m_view->nominalHeight());
float pf = rootView().viewRes().pixelFactor();
double mult = 20.0 * pf;
if (scroll.isFine)
mult = 1.0 * pf;
bool ret = false;
if (ratioX >= 1.f) {
m_scroll[0] = 0;
m_targetScroll[0] = 0;
m_drawSideButtons = false;
ret = true;
} else {
m_drawSideButtons = true;
m_targetScroll[0] += scroll.delta[0] * mult;
m_targetScroll[0] = std::min(m_targetScroll[0], 0);
int scrollWidth = m_contentView.m_view->nominalWidth() - scrollAreaWidth();
m_targetScroll[0] = std::max(m_targetScroll[0], -scrollWidth);
}
if (ratioY >= 1.f) {
m_scroll[1] = 0;
m_targetScroll[1] = 0;
ret = true;
} else {
m_targetScroll[1] -= scroll.delta[1] * mult;
m_targetScroll[1] = std::max(m_targetScroll[1], 0);
int scrollHeight = m_contentView.m_view->nominalHeight() - subRect().size[1];
m_targetScroll[1] = std::min(m_targetScroll[1], scrollHeight);
}
if (scroll.isFine) {
m_scroll[0] = m_targetScroll[0];
m_scroll[1] = m_targetScroll[1];
ret = true;
}
return ret;
} else {
m_scroll[0] = 0;
m_scroll[1] = 0;
m_targetScroll[0] = 0;
m_targetScroll[1] = 0;
m_drawSideButtons = false;
return true;
}
return false;
}
int ScrollView::scrollAreaWidth() const {
int ret = subRect().size[0];
if (m_style == Style::SideButtons && m_drawSideButtons) {
ret -= m_sideButtons[0].m_view->nominalWidth();
ret -= m_sideButtons[1].m_view->nominalWidth();
}
return std::max(0, ret);
}
void ScrollView::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
if (m_style == Style::SideButtons && m_drawSideButtons) {
if (m_sideButtons[0].mouseDown(coord, button, mod) || m_sideButtons[1].mouseDown(coord, button, mod))
return;
}
m_contentView.mouseDown(coord, button, mod);
}
void ScrollView::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
if (m_style == Style::SideButtons) {
m_sideButtons[0].mouseUp(coord, button, mod);
m_sideButtons[1].mouseUp(coord, button, mod);
}
m_contentView.mouseUp(coord, button, mod);
}
void ScrollView::mouseMove(const boo::SWindowCoord& coord) {
if (m_style == Style::SideButtons && m_drawSideButtons) {
m_sideButtons[0].mouseMove(coord);
m_sideButtons[1].mouseMove(coord);
}
m_contentView.mouseMove(coord);
}
void ScrollView::mouseEnter(const boo::SWindowCoord& coord) {
if (m_style == Style::SideButtons && m_drawSideButtons) {
m_sideButtons[0].mouseEnter(coord);
m_sideButtons[1].mouseEnter(coord);
}
m_contentView.mouseEnter(coord);
}
void ScrollView::mouseLeave(const boo::SWindowCoord& coord) {
if (m_style == Style::SideButtons) {
m_sideButtons[0].mouseLeave(coord);
m_sideButtons[1].mouseLeave(coord);
}
m_contentView.mouseLeave(coord);
}
void ScrollView::scroll(const boo::SWindowCoord& coord, const boo::SScrollDelta& scroll) {
if (!scroll.isAccelerated) {
boo::SScrollDelta newScroll = scroll;
m_consecutiveScroll[m_consecutiveIdx][0] += scroll.delta[0];
m_consecutiveScroll[m_consecutiveIdx][1] += scroll.delta[1];
newScroll.delta[0] = 0;
newScroll.delta[1] = 0;
for (size_t i = 0; i < 16; ++i) {
newScroll.delta[0] += m_consecutiveScroll[i][0];
newScroll.delta[1] += m_consecutiveScroll[i][1];
}
if (_scroll(newScroll))
updateSize();
return;
}
if (_scroll(scroll))
updateSize();
}
void ScrollView::setMultiplyColor(const zeus::CColor& color) {
View::setMultiplyColor(color);
if (m_style == Style::SideButtons) {
m_sideButtons[0].m_view->setMultiplyColor(color);
m_sideButtons[1].m_view->setMultiplyColor(color);
}
if (m_contentView.m_view)
m_contentView.m_view->setMultiplyColor(color);
}
void ScrollView::think() {
m_consecutiveIdx = (m_consecutiveIdx + 1) % 16;
m_consecutiveScroll[m_consecutiveIdx][0] = 0.0;
m_consecutiveScroll[m_consecutiveIdx][1] = 0.0;
if (m_sideButtonState != SideButtonState::None) {
if (m_sideButtonState == SideButtonState::ScrollLeft)
m_targetScroll[0] -= 3;
else if (m_sideButtonState == SideButtonState::ScrollRight)
m_targetScroll[0] += 3;
m_targetScroll[0] = std::min(m_targetScroll[0], 0);
int scrollWidth = m_contentView.m_view->nominalWidth() - scrollAreaWidth();
m_targetScroll[0] = std::max(m_targetScroll[0], -scrollWidth);
}
bool update = false;
float pf = rootView().viewRes().pixelFactor();
int xSpeed = std::max(1, std::min(abs(m_targetScroll[0] - m_scroll[0]) / int(5 * pf), int(pf * MAX_SCROLL_SPEED)));
if (m_scroll[0] < m_targetScroll[0]) {
m_scroll[0] += xSpeed;
update = true;
} else if (m_scroll[0] > m_targetScroll[0]) {
m_scroll[0] -= xSpeed;
update = true;
}
int ySpeed = std::max(1, std::min(abs(m_targetScroll[1] - m_scroll[1]) / int(5 * pf), int(pf * MAX_SCROLL_SPEED)));
if (m_scroll[1] < m_targetScroll[1]) {
m_scroll[1] += ySpeed;
update = true;
} else if (m_scroll[1] > m_targetScroll[1]) {
m_scroll[1] -= ySpeed;
update = true;
}
if (update)
updateSize();
}
void ScrollView::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
View::resized(root, sub);
_scroll({});
if (m_contentView.m_view) {
boo::SWindowRect cRect = sub;
cRect.location[0] += m_scroll[0];
cRect.location[1] += sub.size[1] - m_contentView.m_view->nominalHeight() + m_scroll[1];
cRect.size[0] = m_contentView.m_view->nominalWidth();
cRect.size[1] = m_contentView.m_view->nominalHeight();
m_contentView.m_scissorRect = sub;
if (m_style == Style::SideButtons && m_drawSideButtons) {
int width0 = m_sideButtons[0].m_view->nominalWidth() + 2;
int width1 = m_sideButtons[1].m_view->nominalWidth();
cRect.location[0] += width0;
cRect.size[0] -= (width0 + width1);
m_contentView.m_scissorRect.location[0] += width0;
m_contentView.m_scissorRect.size[0] -= (width0 + width1);
}
m_contentView.m_view->resized(root, cRect, m_contentView.m_scissorRect);
if (m_style == Style::ThinIndicator) {
float ratio = sub.size[1] / float(cRect.size[1]);
m_drawInd = ratio < 1.f;
if (m_drawInd) {
float pf = rootView().viewRes().pixelFactor();
int barHeight = sub.size[1] * ratio;
int scrollHeight = sub.size[1] - barHeight;
float prog = m_scroll[1] / float(cRect.size[1] - sub.size[1]);
int x = sub.size[0];
int y = sub.size[1] - scrollHeight * prog;
m_verts[0].m_pos.assign(x, y, 0);
m_verts[1].m_pos.assign(x, y - barHeight, 0);
m_verts[2].m_pos.assign(x + 2 * pf, y, 0);
m_verts[3].m_pos.assign(x + 2 * pf, y - barHeight, 0);
const zeus::CColor& color = rootView().themeData().scrollIndicator();
for (int i = 0; i < 4; ++i)
m_verts[i].m_color = color;
m_vertsBinding.load<decltype(m_verts)>(m_verts);
}
} else if (m_style == Style::SideButtons && m_drawSideButtons) {
boo::SWindowRect bRect = sub;
bRect.size[0] = m_sideButtons[0].m_view->nominalWidth();
bRect.size[1] = m_sideButtons[0].m_view->nominalHeight();
m_sideButtons[0].m_view->resized(root, bRect);
bRect.size[0] = m_sideButtons[1].m_view->nominalWidth();
bRect.size[1] = m_sideButtons[1].m_view->nominalHeight();
bRect.location[0] += sub.size[0] - bRect.size[0];
m_sideButtons[1].m_view->resized(root, bRect);
}
}
}
void ScrollView::draw(boo::IGraphicsCommandQueue* gfxQ) {
if (m_contentView.m_view) {
m_contentView.m_view->draw(gfxQ);
if (m_style == Style::ThinIndicator && m_drawInd) {
gfxQ->setShaderDataBinding(m_vertsBinding);
gfxQ->draw(0, 4);
} else if (m_style == Style::SideButtons && m_drawSideButtons) {
m_sideButtons[0].m_view->draw(gfxQ);
m_sideButtons[1].m_view->draw(gfxQ);
}
}
}
} // namespace specter

318
specter/lib/Space.cpp Normal file
View File

@ -0,0 +1,318 @@
#include "specter/Space.hpp"
#include "specter/IViewManager.hpp"
#include "specter/RootView.hpp"
#include "specter/ViewResources.hpp"
#include <boo/graphicsdev/IGraphicsCommandQueue.hpp>
namespace specter {
#define TRIANGLE_DIM 12
#define TRIANGLE_DIM1 10
#define TRIANGLE_DIM2 8
#define TRIANGLE_DIM3 6
#define TRIANGLE_DIM4 4
#define TRIANGLE_DIM5 2
#define CORNER_DRAG_THRESHOLD 20
static const zeus::CColor TriColor = {0.75, 0.75, 0.75, 1.0};
struct Space::CornerView : View {
Space& m_space;
VertexBufferBindingSolid m_vertexBinding;
bool m_flip;
CornerView(ViewResources& res, Space& space, const zeus::CColor& triColor);
void mouseEnter(const boo::SWindowCoord&) override;
void mouseLeave(const boo::SWindowCoord&) override;
void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
using View::resized;
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub, bool flip);
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
};
Space::Space(ViewResources& res, View& parentView, ISpaceController& controller, Toolbar::Position tbPos,
unsigned tbUnits)
: View(res, parentView), m_controller(controller), m_tbPos(tbPos) {
commitResources(res, [&](boo::IGraphicsDataFactory::Context& ctx) -> bool {
buildResources(ctx, res);
return true;
});
setBackground(res.themeData().spaceBackground());
if (controller.spaceSplitAllowed()) {
m_cornerView.m_view = std::make_unique<CornerView>(res, *this, TriColor);
}
if (tbPos != Toolbar::Position::None) {
m_toolbar.m_view = std::make_unique<Toolbar>(res, *this, tbPos, tbUnits);
}
}
Space::~Space() = default;
Space::CornerView::CornerView(ViewResources& res, Space& space, const zeus::CColor& triColor)
: View(res, space), m_space(space) {
commitResources(res, [&](boo::IGraphicsDataFactory::Context& ctx) -> bool {
buildResources(ctx, res);
m_vertexBinding.init(ctx, res, 34, m_viewVertBlockBuf);
return true;
});
float pf = res.pixelFactor();
zeus::CColor edgeColor1 = triColor * res.themeData().spaceTriangleShading1();
zeus::CColor edgeColor2 = triColor * res.themeData().spaceTriangleShading2();
View::SolidShaderVert verts[34];
verts[0].m_pos.assign(0, TRIANGLE_DIM * pf, 0);
verts[0].m_color = edgeColor1;
verts[1].m_pos.assign(TRIANGLE_DIM * pf, 0, 0);
verts[1].m_color = edgeColor1;
verts[2].m_pos.assign(0, (TRIANGLE_DIM + 1) * pf, 0);
verts[2].m_color = edgeColor1;
verts[3].m_pos.assign((TRIANGLE_DIM + 1) * pf, 0, 0);
verts[3].m_color = edgeColor1;
verts[4] = verts[3];
verts[5].m_pos.assign(0, TRIANGLE_DIM1 * pf, 0);
verts[5].m_color = edgeColor2;
verts[6] = verts[5];
verts[7].m_pos.assign(TRIANGLE_DIM1 * pf, 0, 0);
verts[7].m_color = edgeColor2;
verts[8].m_pos.assign(0, (TRIANGLE_DIM1 + 1) * pf, 0);
verts[8].m_color = edgeColor2;
verts[9].m_pos.assign((TRIANGLE_DIM1 + 1) * pf, 0, 0);
verts[9].m_color = edgeColor2;
verts[10] = verts[9];
verts[11].m_pos.assign(0, TRIANGLE_DIM2 * pf, 0);
verts[11].m_color = edgeColor2;
verts[12] = verts[11];
verts[13].m_pos.assign(TRIANGLE_DIM2 * pf, 0, 0);
verts[13].m_color = edgeColor2;
verts[14].m_pos.assign(0, (TRIANGLE_DIM2 + 1) * pf, 0);
verts[14].m_color = edgeColor2;
verts[15].m_pos.assign((TRIANGLE_DIM2 + 1) * pf, 0, 0);
verts[15].m_color = edgeColor2;
verts[16] = verts[15];
verts[17].m_pos.assign(0, TRIANGLE_DIM3 * pf, 0);
verts[17].m_color = edgeColor2;
verts[18] = verts[17];
verts[19].m_pos.assign(TRIANGLE_DIM3 * pf, 0, 0);
verts[19].m_color = edgeColor2;
verts[20].m_pos.assign(0, (TRIANGLE_DIM3 + 1) * pf, 0);
verts[20].m_color = edgeColor2;
verts[21].m_pos.assign((TRIANGLE_DIM3 + 1) * pf, 0, 0);
verts[21].m_color = edgeColor2;
verts[22] = verts[21];
verts[23].m_pos.assign(0, TRIANGLE_DIM4 * pf, 0);
verts[23].m_color = edgeColor2;
verts[24] = verts[23];
verts[25].m_pos.assign(TRIANGLE_DIM4 * pf, 0, 0);
verts[25].m_color = edgeColor2;
verts[26].m_pos.assign(0, (TRIANGLE_DIM4 + 1) * pf, 0);
verts[26].m_color = edgeColor2;
verts[27].m_pos.assign((TRIANGLE_DIM4 + 1) * pf, 0, 0);
verts[27].m_color = edgeColor2;
verts[28] = verts[27];
verts[29].m_pos.assign(0, TRIANGLE_DIM5 * pf, 0);
verts[29].m_color = edgeColor2;
verts[30] = verts[29];
verts[31].m_pos.assign(TRIANGLE_DIM5 * pf, 0, 0);
verts[31].m_color = edgeColor2;
verts[32].m_pos.assign(0, (TRIANGLE_DIM5 + 1) * pf, 0);
verts[32].m_color = edgeColor2;
verts[33].m_pos.assign((TRIANGLE_DIM5 + 1) * pf, 0, 0);
verts[33].m_color = edgeColor2;
m_vertexBinding.load<decltype(verts)>(verts);
}
View* Space::setContentView(View* view) {
View* ret = m_contentView.m_view;
m_contentView.m_view = view;
updateSize();
return ret;
}
void Space::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
if (m_cornerView.mouseDown(coord, button, mod))
return;
m_contentView.mouseDown(coord, button, mod);
m_toolbar.mouseDown(coord, button, mod);
}
void Space::CornerView::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
if (button == boo::EMouseButton::Primary) {
m_space.m_cornerDrag = true;
m_space.m_cornerDragPoint[0] = coord.pixel[0];
m_space.m_cornerDragPoint[1] = coord.pixel[1];
rootView().setActiveDragView(&m_space);
}
}
void Space::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
m_cornerView.mouseUp(coord, button, mod);
m_contentView.mouseUp(coord, button, mod);
m_toolbar.mouseUp(coord, button, mod);
}
void Space::CornerView::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
if (button == boo::EMouseButton::Primary) {
m_space.m_cornerDrag = false;
rootView().unsetActiveDragView(&m_space);
}
}
void Space::mouseMove(const boo::SWindowCoord& coord) {
if (m_cornerDrag) {
float pf = rootView().viewRes().pixelFactor();
if (coord.pixel[0] < m_cornerDragPoint[0] - CORNER_DRAG_THRESHOLD * pf) {
if (m_cornerView.m_view->m_flip) {
rootView().mouseUp(coord, boo::EMouseButton::Primary, boo::EModifierKey::None);
rootView().viewManager().deferSpaceSplit(&m_controller, SplitView::Axis::Vertical, 1, coord);
} else {
SplitView* sv = findSplitViewOnSide(SplitView::Axis::Vertical, 0);
if (sv) {
rootView().mouseUp(coord, boo::EMouseButton::Primary, boo::EModifierKey::None);
rootView().beginInteractiveJoin(sv, coord);
}
}
} else if (coord.pixel[1] < m_cornerDragPoint[1] - CORNER_DRAG_THRESHOLD * pf) {
if (m_cornerView.m_view->m_flip) {
rootView().mouseUp(coord, boo::EMouseButton::Primary, boo::EModifierKey::None);
rootView().viewManager().deferSpaceSplit(&m_controller, SplitView::Axis::Horizontal, 1, coord);
} else {
SplitView* sv = findSplitViewOnSide(SplitView::Axis::Horizontal, 0);
if (sv) {
rootView().mouseUp(coord, boo::EMouseButton::Primary, boo::EModifierKey::None);
rootView().beginInteractiveJoin(sv, coord);
}
}
} else if (coord.pixel[0] > m_cornerDragPoint[0] + CORNER_DRAG_THRESHOLD * pf) {
if (!m_cornerView.m_view->m_flip) {
rootView().mouseUp(coord, boo::EMouseButton::Primary, boo::EModifierKey::None);
rootView().viewManager().deferSpaceSplit(&m_controller, SplitView::Axis::Vertical, 0, coord);
} else {
SplitView* sv = findSplitViewOnSide(SplitView::Axis::Vertical, 1);
if (sv) {
rootView().mouseUp(coord, boo::EMouseButton::Primary, boo::EModifierKey::None);
rootView().beginInteractiveJoin(sv, coord);
}
}
} else if (coord.pixel[1] > m_cornerDragPoint[1] + CORNER_DRAG_THRESHOLD * pf) {
if (!m_cornerView.m_view->m_flip) {
rootView().mouseUp(coord, boo::EMouseButton::Primary, boo::EModifierKey::None);
rootView().viewManager().deferSpaceSplit(&m_controller, SplitView::Axis::Horizontal, 0, coord);
} else {
SplitView* sv = findSplitViewOnSide(SplitView::Axis::Horizontal, 1);
if (sv) {
rootView().mouseUp(coord, boo::EMouseButton::Primary, boo::EModifierKey::None);
rootView().beginInteractiveJoin(sv, coord);
}
}
}
} else {
m_cornerView.mouseMove(coord);
m_contentView.mouseMove(coord);
m_toolbar.mouseMove(coord);
}
}
void Space::mouseEnter(const boo::SWindowCoord& coord) {
m_cornerView.mouseEnter(coord);
m_contentView.mouseEnter(coord);
m_toolbar.mouseEnter(coord);
}
void Space::CornerView::mouseEnter(const boo::SWindowCoord& coord) { rootView().setSpaceCornerHover(true); }
void Space::mouseLeave(const boo::SWindowCoord& coord) {
m_cornerView.mouseLeave(coord);
m_contentView.mouseLeave(coord);
m_toolbar.mouseLeave(coord);
}
void Space::CornerView::mouseLeave(const boo::SWindowCoord& coord) { rootView().setSpaceCornerHover(false); }
SplitView* Space::findSplitViewOnSide(SplitView::Axis axis, int side) {
SplitView* ret = parentView().castToSplitView();
View* test = this;
while (ret) {
if (ret->axis() != axis)
return nullptr;
if (ret->m_views[side ^ 1].m_view == test)
return ret;
else if (ret->m_views[side].m_view == test)
test = ret;
ret = ret->parentView().castToSplitView();
}
return nullptr;
}
void Space::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
View::resized(root, sub);
float pf = rootView().viewRes().pixelFactor();
if (m_cornerView.m_view) {
boo::SWindowRect cornerRect = sub;
int triDim = TRIANGLE_DIM * pf;
cornerRect.size[0] = cornerRect.size[1] = triDim;
if (cornerRect.location[0] < triDim && cornerRect.location[1] < triDim) {
cornerRect.location[0] += sub.size[0] - triDim;
cornerRect.location[1] += sub.size[1] - triDim;
m_cornerView.m_view->resized(root, cornerRect, true);
} else
m_cornerView.m_view->resized(root, cornerRect, false);
}
boo::SWindowRect tbRect = sub;
if (m_toolbar.m_view) {
tbRect.size[1] = m_toolbar.m_view->nominalHeight();
if (m_tbPos == Toolbar::Position::Top)
tbRect.location[1] += sub.size[1] - tbRect.size[1];
m_toolbar.m_view->resized(root, tbRect);
} else
tbRect.size[1] = 0;
if (m_contentView.m_view) {
boo::SWindowRect contentRect = sub;
if (m_tbPos == Toolbar::Position::Bottom)
contentRect.location[1] += tbRect.size[1];
contentRect.size[1] = sub.size[1] - tbRect.size[1];
contentRect.size[1] = std::max(contentRect.size[1], 0);
m_contentView.m_view->resized(root, contentRect);
}
}
void Space::CornerView::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub, bool flip) {
m_flip = flip;
if (flip) {
m_viewVertBlock.m_mv[0][0] = -2.0f / root.size[0];
m_viewVertBlock.m_mv[1][1] = -2.0f / root.size[1];
m_viewVertBlock.m_mv[3][0] = (sub.location[0] + sub.size[0]) * -m_viewVertBlock.m_mv[0][0] - 1.0f;
m_viewVertBlock.m_mv[3][1] = (sub.location[1] + sub.size[1]) * -m_viewVertBlock.m_mv[1][1] - 1.0f;
View::resized(m_viewVertBlock, sub);
} else
View::resized(root, sub);
}
void Space::draw(boo::IGraphicsCommandQueue* gfxQ) {
View::draw(gfxQ);
if (m_contentView.m_view)
m_contentView.m_view->draw(gfxQ);
if (m_toolbar.m_view)
m_toolbar.m_view->draw(gfxQ);
if (m_cornerView.m_view)
m_cornerView.m_view->draw(gfxQ);
}
void Space::CornerView::draw(boo::IGraphicsCommandQueue* gfxQ) {
gfxQ->setShaderDataBinding(m_vertexBinding);
gfxQ->draw(0, 34);
}
} // namespace specter

407
specter/lib/SplitView.cpp Normal file
View File

@ -0,0 +1,407 @@
#include "specter/SplitView.hpp"
#include "specter/Space.hpp"
#include "specter/RootView.hpp"
#include "specter/ViewResources.hpp"
#include <boo/graphicsdev/IGraphicsCommandQueue.hpp>
#include <logvisor/logvisor.hpp>
namespace specter {
static logvisor::Module Log("specter::SplitView");
static const zeus::RGBA32 Tex[3] = {{{0, 0, 0, 64}}, {{0, 0, 0, 255}}, {{255, 255, 255, 64}}};
void SplitView::Resources::init(boo::IGraphicsDataFactory::Context& ctx, const IThemeData& theme) {
m_shadingTex = ctx.newStaticTexture(3, 1, 1, boo::TextureFormat::RGBA8, boo::TextureClampMode::Repeat, Tex, 12);
}
SplitView::SplitView(ViewResources& res, View& parentView, ISplitSpaceController* controller, Axis axis, float split,
int clearanceA, int clearanceB)
: View(res, parentView)
, m_controller(controller)
, m_axis(axis)
, m_slide(split)
, m_clearanceA(clearanceA)
, m_clearanceB(clearanceB) {
commitResources(res, [&](boo::IGraphicsDataFactory::Context& ctx) -> bool {
buildResources(ctx, res);
m_splitBlockBuf = res.m_viewRes.m_bufPool.allocateBlock(res.m_factory);
m_splitVertsBinding.init(ctx, res, 4, m_splitBlockBuf, res.m_splitRes.m_shadingTex.get());
return true;
});
}
View* SplitView::setContentView(int slot, View* view) {
if (slot < 0 || slot > 1)
Log.report(logvisor::Fatal, FMT_STRING("out-of-range slot to RootView::SplitView::setContentView"));
View* ret = m_views[slot].m_view;
m_views[slot].m_view = view;
m_views[slot].m_mouseDown = 0;
m_views[slot].m_mouseIn = false;
updateSize();
return ret;
}
void SplitView::_setSplit(float slide) {
m_slide = std::min(std::max(slide, 0.0f), 1.0f);
const boo::SWindowRect& rect = subRect();
if (rect.size[0] && rect.size[1] && (m_clearanceA >= 0 || m_clearanceB >= 0)) {
if (m_axis == Axis::Horizontal) {
int slidePx = rect.size[1] * m_slide;
if (m_clearanceA >= 0 && slidePx < m_clearanceA)
m_slide = m_clearanceA / float(rect.size[1]);
if (m_clearanceB >= 0 && (rect.size[1] - slidePx) < m_clearanceB)
m_slide = 1.0 - m_clearanceB / float(rect.size[1]);
} else if (m_axis == Axis::Vertical) {
int slidePx = rect.size[0] * m_slide;
if (m_clearanceA >= 0 && slidePx < m_clearanceA)
m_slide = m_clearanceA / float(rect.size[0]);
if (m_clearanceB >= 0 && (rect.size[0] - slidePx) < m_clearanceB)
m_slide = 1.0 - m_clearanceB / float(rect.size[0]);
}
m_slide = std::min(std::max(m_slide, 0.0f), 1.0f);
}
if (m_controller)
m_controller->updateSplit(m_slide);
}
void SplitView::setSplit(float slide) {
_setSplit(slide);
updateSize();
}
void SplitView::setAxis(Axis axis) {
m_axis = axis;
setSplit(m_slide);
}
bool SplitView::testSplitHover(const boo::SWindowCoord& coord) {
if (m_axis == Axis::Horizontal) {
int slidePx = subRect().size[1] * m_slide;
if (abs(int(coord.pixel[1] - subRect().location[1]) - slidePx) < 4)
return true;
} else if (m_axis == Axis::Vertical) {
int slidePx = subRect().size[0] * m_slide;
if (abs(int(coord.pixel[0] - subRect().location[0]) - slidePx) < 4)
return true;
}
return false;
}
bool SplitView::testJoinArrowHover(const boo::SWindowCoord& coord, int& origSlotOut, SplitView*& splitOut, int& slotOut,
boo::SWindowRect& rectOut, ArrowDir& dirOut, int forceSlot) {
if (!subRect().coordInRect(coord))
return false;
int origDummy;
ArrowDir dirDummy;
if (m_axis == Axis::Horizontal) {
int slidePx = subRect().size[1] * m_slide;
if ((forceSlot == -1 && coord.pixel[1] - subRect().location[1] - slidePx >= 0) || forceSlot == 1) {
origSlotOut = 0;
dirOut = ArrowDir::Up;
if (m_views[1].m_view) {
SplitView* chSplit = m_views[1].m_view->castToSplitView();
if (chSplit && chSplit->m_axis == Axis::Horizontal)
return chSplit->testJoinArrowHover(coord, origDummy, splitOut, slotOut, rectOut, dirDummy, 0);
}
splitOut = this;
slotOut = 1;
rectOut = subRect();
rectOut.location[1] += slidePx;
rectOut.size[1] -= slidePx;
return true;
} else {
origSlotOut = 1;
dirOut = ArrowDir::Down;
if (m_views[0].m_view) {
SplitView* chSplit = m_views[0].m_view->castToSplitView();
if (chSplit && chSplit->m_axis == Axis::Horizontal)
return chSplit->testJoinArrowHover(coord, origDummy, splitOut, slotOut, rectOut, dirDummy, 1);
}
splitOut = this;
slotOut = 0;
rectOut = subRect();
rectOut.size[1] = slidePx;
return true;
}
} else {
int slidePx = subRect().size[0] * m_slide;
if ((forceSlot == -1 && coord.pixel[0] - subRect().location[0] - slidePx >= 0) || forceSlot == 1) {
origSlotOut = 0;
dirOut = ArrowDir::Right;
if (m_views[1].m_view) {
SplitView* chSplit = m_views[1].m_view->castToSplitView();
if (chSplit && chSplit->m_axis == Axis::Vertical)
return chSplit->testJoinArrowHover(coord, origDummy, splitOut, slotOut, rectOut, dirDummy, 0);
}
splitOut = this;
slotOut = 1;
rectOut = subRect();
rectOut.location[0] += slidePx;
rectOut.size[0] -= slidePx;
return true;
} else {
origSlotOut = 1;
dirOut = ArrowDir::Left;
if (m_views[0].m_view) {
SplitView* chSplit = m_views[0].m_view->castToSplitView();
if (chSplit && chSplit->m_axis == Axis::Vertical)
return chSplit->testJoinArrowHover(coord, origDummy, splitOut, slotOut, rectOut, dirDummy, 1);
}
splitOut = this;
slotOut = 0;
rectOut = subRect();
rectOut.size[0] = slidePx;
return true;
}
}
}
void SplitView::getJoinArrowHover(int slot, boo::SWindowRect& rectOut, ArrowDir& dirOut) {
if (m_axis == Axis::Horizontal) {
int slidePx = subRect().size[1] * m_slide;
if (slot == 1) {
if (m_views[1].m_view) {
SplitView* chSplit = m_views[1].m_view->castToSplitView();
if (chSplit && chSplit->m_axis == Axis::Horizontal)
return chSplit->getJoinArrowHover(0, rectOut, dirOut);
}
rectOut = subRect();
rectOut.location[1] += slidePx;
rectOut.size[1] -= slidePx;
dirOut = ArrowDir::Up;
} else {
if (m_views[0].m_view) {
SplitView* chSplit = m_views[0].m_view->castToSplitView();
if (chSplit && chSplit->m_axis == Axis::Horizontal)
return chSplit->getJoinArrowHover(1, rectOut, dirOut);
}
rectOut = subRect();
rectOut.size[1] = slidePx;
dirOut = ArrowDir::Down;
}
} else {
int slidePx = subRect().size[0] * m_slide;
if (slot == 1) {
if (m_views[1].m_view) {
SplitView* chSplit = m_views[1].m_view->castToSplitView();
if (chSplit && chSplit->m_axis == Axis::Vertical)
return chSplit->getJoinArrowHover(0, rectOut, dirOut);
}
rectOut = subRect();
rectOut.location[0] += slidePx;
rectOut.size[0] -= slidePx;
dirOut = ArrowDir::Right;
} else {
if (m_views[0].m_view) {
SplitView* chSplit = m_views[0].m_view->castToSplitView();
if (chSplit && chSplit->m_axis == Axis::Vertical)
return chSplit->getJoinArrowHover(1, rectOut, dirOut);
}
rectOut = subRect();
rectOut.size[0] = slidePx;
dirOut = ArrowDir::Left;
}
}
}
bool SplitView::testSplitLineHover(const boo::SWindowCoord& coord, int& slotOut, boo::SWindowRect& rectOut,
float& splitOut, Axis& axisOut) {
if (!subRect().coordInRect(coord))
return false;
if (m_axis == Axis::Horizontal) {
int slidePx = subRect().size[1] * m_slide;
if (coord.pixel[1] - subRect().location[1] - slidePx >= 0) {
if (m_views[1].m_view) {
SplitView* chSplit = m_views[1].m_view->castToSplitView();
if (chSplit && chSplit->m_axis == Axis::Horizontal)
return chSplit->testSplitLineHover(coord, slotOut, rectOut, splitOut, axisOut);
}
slotOut = 1;
rectOut = subRect();
rectOut.location[1] += slidePx;
rectOut.size[1] -= slidePx;
splitOut = (coord.pixel[0] - rectOut.location[0]) / float(rectOut.size[0]);
axisOut = Axis::Vertical;
return true;
} else {
if (m_views[0].m_view) {
SplitView* chSplit = m_views[0].m_view->castToSplitView();
if (chSplit && chSplit->m_axis == Axis::Horizontal)
return chSplit->testSplitLineHover(coord, slotOut, rectOut, splitOut, axisOut);
}
slotOut = 0;
rectOut = subRect();
rectOut.size[1] = slidePx;
splitOut = (coord.pixel[0] - rectOut.location[0]) / float(rectOut.size[0]);
axisOut = Axis::Vertical;
return true;
}
} else {
int slidePx = subRect().size[0] * m_slide;
if (coord.pixel[0] - subRect().location[0] - slidePx >= 0) {
if (m_views[1].m_view) {
SplitView* chSplit = m_views[1].m_view->castToSplitView();
if (chSplit && chSplit->m_axis == Axis::Vertical)
return chSplit->testSplitLineHover(coord, slotOut, rectOut, splitOut, axisOut);
}
slotOut = 1;
rectOut = subRect();
rectOut.location[0] += slidePx;
rectOut.size[0] -= slidePx;
splitOut = (coord.pixel[1] - rectOut.location[1]) / float(rectOut.size[1]);
axisOut = Axis::Horizontal;
return true;
} else {
if (m_views[0].m_view) {
SplitView* chSplit = m_views[0].m_view->castToSplitView();
if (chSplit && chSplit->m_axis == Axis::Vertical)
return chSplit->testSplitLineHover(coord, slotOut, rectOut, splitOut, axisOut);
}
slotOut = 0;
rectOut = subRect();
rectOut.size[0] = slidePx;
splitOut = (coord.pixel[1] - rectOut.location[1]) / float(rectOut.size[1]);
axisOut = Axis::Horizontal;
return true;
}
}
}
void SplitView::getSplitLineHover(int slot, boo::SWindowRect& rectOut, Axis& axisOut) {
if (m_axis == Axis::Horizontal) {
int slidePx = subRect().size[1] * m_slide;
if (slot == 1) {
if (m_views[1].m_view) {
SplitView* chSplit = m_views[1].m_view->castToSplitView();
if (chSplit && chSplit->m_axis == Axis::Horizontal)
return chSplit->getSplitLineHover(0, rectOut, axisOut);
}
rectOut = subRect();
rectOut.location[1] += slidePx;
rectOut.size[1] -= slidePx;
axisOut = Axis::Vertical;
} else {
if (m_views[0].m_view) {
SplitView* chSplit = m_views[0].m_view->castToSplitView();
if (chSplit && chSplit->m_axis == Axis::Horizontal)
return chSplit->getSplitLineHover(1, rectOut, axisOut);
}
rectOut = subRect();
rectOut.size[1] = slidePx;
axisOut = Axis::Vertical;
}
} else {
int slidePx = subRect().size[0] * m_slide;
if (slot == 1) {
if (m_views[1].m_view) {
SplitView* chSplit = m_views[1].m_view->castToSplitView();
if (chSplit && chSplit->m_axis == Axis::Vertical)
return chSplit->getSplitLineHover(0, rectOut, axisOut);
}
rectOut = subRect();
rectOut.location[0] += slidePx;
rectOut.size[0] -= slidePx;
axisOut = Axis::Horizontal;
} else {
if (m_views[0].m_view) {
SplitView* chSplit = m_views[0].m_view->castToSplitView();
if (chSplit && chSplit->m_axis == Axis::Vertical)
return chSplit->getSplitLineHover(1, rectOut, axisOut);
}
rectOut = subRect();
rectOut.size[0] = slidePx;
axisOut = Axis::Horizontal;
}
}
}
void SplitView::startDragSplit(const boo::SWindowCoord& coord) {
m_dragging = true;
if (m_axis == Axis::Horizontal)
setSplit((coord.pixel[1] - subRect().location[1]) / float(subRect().size[1]));
else if (m_axis == Axis::Vertical)
setSplit((coord.pixel[0] - subRect().location[0]) / float(subRect().size[0]));
}
void SplitView::endDragSplit() { m_dragging = false; }
void SplitView::moveDragSplit(const boo::SWindowCoord& coord) {
if (m_axis == Axis::Horizontal)
setSplit((coord.pixel[1] - subRect().location[1]) / float(subRect().size[1]));
else if (m_axis == Axis::Vertical)
setSplit((coord.pixel[0] - subRect().location[0]) / float(subRect().size[0]));
}
void SplitView::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
m_views[0].mouseDown(coord, button, mod);
m_views[1].mouseDown(coord, button, mod);
}
void SplitView::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
m_views[0].mouseUp(coord, button, mod);
m_views[1].mouseUp(coord, button, mod);
}
void SplitView::mouseMove(const boo::SWindowCoord& coord) {
m_views[0].mouseMove(coord);
m_views[1].mouseMove(coord);
}
void SplitView::mouseEnter(const boo::SWindowCoord& coord) {
m_views[0].mouseEnter(coord);
m_views[1].mouseEnter(coord);
}
void SplitView::mouseLeave(const boo::SWindowCoord& coord) {
m_views[0].mouseLeave(coord);
m_views[1].mouseLeave(coord);
}
void SplitView::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
View::resized(root, sub);
_setSplit(m_slide);
if (m_axis == Axis::Horizontal) {
boo::SWindowRect ssub = sub;
ssub.size[1] *= m_slide;
if (m_views[0].m_view)
m_views[0].m_view->resized(root, ssub);
ssub.location[1] += ssub.size[1];
ssub.size[1] = sub.size[1] - ssub.size[1];
if (m_views[1].m_view)
m_views[1].m_view->resized(root, ssub);
ssub.location[1] -= 1;
m_splitBlock.setViewRect(root, ssub);
setHorizontalVerts(ssub.size[0]);
} else if (m_axis == Axis::Vertical) {
boo::SWindowRect ssub = sub;
ssub.size[0] *= m_slide;
if (m_views[0].m_view)
m_views[0].m_view->resized(root, ssub);
ssub.location[0] += ssub.size[0];
ssub.size[0] = sub.size[0] - ssub.size[0];
if (m_views[1].m_view)
m_views[1].m_view->resized(root, ssub);
ssub.location[0] -= 1;
m_splitBlock.setViewRect(root, ssub);
setVerticalVerts(ssub.size[1]);
}
m_splitBlockBuf.access().finalAssign(m_splitBlock);
m_splitVertsBinding.load<decltype(m_splitVerts)>(m_splitVerts);
}
void SplitView::draw(boo::IGraphicsCommandQueue* gfxQ) {
View::draw(gfxQ);
if (m_views[0].m_view)
m_views[0].m_view->draw(gfxQ);
if (m_views[1].m_view)
m_views[1].m_view->draw(gfxQ);
gfxQ->setShaderDataBinding(m_splitVertsBinding);
gfxQ->draw(0, 4);
}
} // namespace specter

797
specter/lib/Table.cpp Normal file
View File

@ -0,0 +1,797 @@
#include "specter/Table.hpp"
#include "specter/RootView.hpp"
#include "specter/ScrollView.hpp"
#include "specter/TextView.hpp"
#include "specter/ViewResources.hpp"
#include <boo/graphicsdev/IGraphicsCommandQueue.hpp>
namespace specter {
static logvisor::Module Log("specter::Table");
#define ROW_HEIGHT 18
#define CELL_MARGIN 1
struct Table::CellView : public View {
Table& m_t;
std::unique_ptr<TextView> m_text;
size_t m_c = SIZE_MAX, m_r = SIZE_MAX;
boo::SWindowRect m_scissorRect;
uint64_t m_textHash = 0;
bool m_selected = false;
CellView(Table& t, ViewResources& res);
void select();
void deselect();
void reset();
bool reset(size_t c);
bool reset(size_t c, size_t r);
void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) override;
void mouseEnter(const boo::SWindowCoord&) override;
void mouseLeave(const boo::SWindowCoord&) override;
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub, const boo::SWindowRect& scissor) override;
void draw(boo::IGraphicsCommandQueue* gfxQ) override;
};
Table::Table(ViewResources& res, View& parentView, ITableDataBinding* data, ITableStateBinding* state,
size_t maxColumns)
: View(res, parentView)
, m_data(data)
, m_state(state)
, m_maxColumns(maxColumns)
, m_hVerts(std::make_unique<SolidShaderVert[]>(maxColumns * 6))
, m_rowsView(*this, res) {
if (maxColumns == 0) {
Log.report(logvisor::Fatal, FMT_STRING("0-column tables not supported"));
}
m_scroll.m_view = std::make_unique<ScrollView>(res, *this, ScrollView::Style::ThinIndicator);
commitResources(res, [&](boo::IGraphicsDataFactory::Context& ctx) -> bool {
buildResources(ctx, res);
m_vertsBinding.init(ctx, res, maxColumns * 6, m_viewVertBlockBuf);
return true;
});
m_scroll.m_view->setContentView(&m_rowsView);
updateData();
}
Table::~Table() = default;
Table::RowsView::RowsView(Table& t, ViewResources& res)
: View(res, t), m_t(t), m_verts(std::make_unique<SolidShaderVert[]>(SPECTER_TABLE_MAX_ROWS * t.m_maxColumns * 6)) {
commitResources(res, [&](boo::IGraphicsDataFactory::Context& ctx) -> bool {
buildResources(ctx, res);
m_vertsBinding.init(ctx, res, SPECTER_TABLE_MAX_ROWS * t.m_maxColumns * 6, m_viewVertBlockBuf);
return true;
});
}
Table::CellView::CellView(Table& t, ViewResources& res)
: View(res, t), m_t(t), m_text(std::make_unique<TextView>(res, *this, res.m_mainFont)) {}
void Table::_setHeaderVerts(const boo::SWindowRect& sub) {
;
if (m_headerViews.empty())
return;
SolidShaderVert* v = m_hVerts.get();
const IThemeData& theme = rootView().themeData();
float pf = rootView().viewRes().pixelFactor();
int margin = CELL_MARGIN * pf;
int rowHeight = ROW_HEIGHT * pf;
int xOff = 0;
int yOff = sub.size[1];
size_t c;
auto it = m_headerViews.cbegin();
size_t sCol = -1;
SortDirection sDir = SortDirection::None;
if (m_state)
sDir = m_state->getSort(sCol);
boo::SWindowRect headRowRect = sub;
headRowRect.size[1] = rowHeight;
headRowRect.location[1] = sub.size[1] - rowHeight;
m_hCellRects = getCellRects(headRowRect);
auto cellRectsIt = m_hCellRects.begin();
for (c = 0; c < std::min(m_maxColumns, m_columns); ++c) {
const ViewChild<std::unique_ptr<CellView>>& hv = *it;
const zeus::CColor* c1 = &theme.button1Inactive();
const zeus::CColor* c2 = &theme.button2Inactive();
if (hv.m_mouseDown && hv.m_mouseIn) {
c1 = &theme.button1Press();
c2 = &theme.button2Press();
} else if (hv.m_mouseIn) {
c1 = &theme.button1Hover();
c2 = &theme.button2Hover();
}
zeus::CColor cm1 = *c1;
zeus::CColor cm2 = *c2;
if (sCol == c) {
if (sDir == SortDirection::Ascending) {
cm1 *= zeus::skGreen;
cm2 *= zeus::skGreen;
} else if (sDir == SortDirection::Descending) {
cm1 *= zeus::skRed;
cm2 *= zeus::skRed;
}
}
int div = cellRectsIt->size[0];
v[0].m_pos.assign(xOff + margin, yOff - margin, 0);
v[0].m_color = cm1;
v[1] = v[0];
v[2].m_pos.assign(xOff + margin, yOff - margin - rowHeight, 0);
v[2].m_color = cm2;
v[3].m_pos.assign(xOff + div - margin, yOff - margin, 0);
v[3].m_color = cm1;
v[4].m_pos.assign(xOff + div - margin, yOff - margin - rowHeight, 0);
v[4].m_color = cm2;
v[5] = v[4];
v += 6;
xOff += div;
++it;
++cellRectsIt;
}
if (c)
m_vertsBinding.load(m_hVerts.get(), 6 * c);
m_headerNeedsUpdate = false;
}
void Table::RowsView::_setRowVerts(const boo::SWindowRect& sub, const boo::SWindowRect& scissor) {
SolidShaderVert* v = m_verts.get();
const IThemeData& theme = rootView().themeData();
if (m_t.m_cellPools.empty())
return;
float pf = rootView().viewRes().pixelFactor();
int spacing = (ROW_HEIGHT + CELL_MARGIN * 2) * pf;
int margin = CELL_MARGIN * pf;
int rowHeight = ROW_HEIGHT * pf;
int yOff = 0;
int idx = 0;
while (sub.location[1] + yOff < scissor.location[1] + scissor.size[1] || (idx & 1) != 0) {
yOff += spacing;
++idx;
}
int startIdx = int(m_t.m_rows) - idx;
std::vector<boo::SWindowRect> cellRects = m_t.getCellRects(sub);
size_t r, c;
for (r = 0, c = 0; r < SPECTER_TABLE_MAX_ROWS && (sub.location[1] + yOff + spacing) >= scissor.location[1]; ++r) {
const zeus::CColor& color = (startIdx + r == m_t.m_selectedRow)
? theme.tableCellBgSelected()
: ((r & 1) ? theme.tableCellBg1() : theme.tableCellBg2());
int xOff = 0;
auto cellRectsIt = cellRects.begin();
for (c = 0; c < std::min(m_t.m_maxColumns, m_t.m_columns); ++c) {
int div = cellRectsIt->size[0];
v[0].m_pos.assign(xOff + margin, yOff - margin, 0);
v[0].m_color = color;
v[1] = v[0];
v[2].m_pos.assign(xOff + margin, yOff - margin - rowHeight, 0);
v[2].m_color = color;
v[3].m_pos.assign(xOff + div - margin, yOff - margin, 0);
v[3].m_color = color;
v[4].m_pos.assign(xOff + div - margin, yOff - margin - rowHeight, 0);
v[4].m_color = color;
v[5] = v[4];
v += 6;
xOff += div;
++cellRectsIt;
}
yOff -= spacing;
}
m_visibleStart = std::max(0, startIdx);
m_visibleRows = r;
if (r && c)
m_vertsBinding.load(m_verts.get(), 6 * r * c);
}
void Table::cycleSortColumn(size_t c) {
if (c >= m_columns)
Log.report(logvisor::Fatal, FMT_STRING("cycleSortColumn out of bounds ({}, {})"), c, m_columns);
if (m_state) {
size_t cIdx;
SortDirection dir = m_state->getSort(cIdx);
if (dir == SortDirection::None || cIdx != c)
m_state->setSort(c, SortDirection::Ascending);
else if (dir == SortDirection::Ascending)
m_state->setSort(c, SortDirection::Descending);
else if (dir == SortDirection::Descending)
m_state->setSort(c, SortDirection::Ascending);
}
}
void Table::selectRow(size_t r) {
if (m_inSelectRow)
return;
if (r >= m_rows && r != SIZE_MAX)
Log.report(logvisor::Fatal, FMT_STRING("selectRow out of bounds ({}, {})"), r, m_rows);
if (r == m_selectedRow) {
if (m_state) {
m_inSelectRow = true;
m_state->setSelectedRow(r);
m_inSelectRow = false;
}
return;
}
if (m_selectedRow != SIZE_MAX && m_activePool != SIZE_MAX) {
size_t poolIdx = m_selectedRow / SPECTER_TABLE_MAX_ROWS;
int pool0 = (poolIdx & 1) != 0;
int pool1 = (poolIdx & 1) == 0;
if (m_activePool == poolIdx) {
for (auto& col : m_cellPools) {
ViewChild<std::unique_ptr<CellView>>& cv = col[pool0].at(m_selectedRow % SPECTER_TABLE_MAX_ROWS);
if (cv.m_view)
cv.m_view->deselect();
}
} else if (m_activePool + 1 == poolIdx) {
for (auto& col : m_cellPools) {
ViewChild<std::unique_ptr<CellView>>& cv = col[pool1].at(m_selectedRow % SPECTER_TABLE_MAX_ROWS);
if (cv.m_view)
cv.m_view->deselect();
}
}
}
m_selectedRow = r;
if (m_selectedRow != SIZE_MAX && m_activePool != SIZE_MAX) {
size_t poolIdx = m_selectedRow / SPECTER_TABLE_MAX_ROWS;
int pool0 = (poolIdx & 1) != 0;
int pool1 = (poolIdx & 1) == 0;
if (m_activePool == poolIdx) {
for (auto& col : m_cellPools) {
ViewChild<std::unique_ptr<CellView>>& cv = col[pool0].at(m_selectedRow % SPECTER_TABLE_MAX_ROWS);
if (cv.m_view)
cv.m_view->select();
}
} else if (m_activePool + 1 == poolIdx) {
for (auto& col : m_cellPools) {
ViewChild<std::unique_ptr<CellView>>& cv = col[pool1].at(m_selectedRow % SPECTER_TABLE_MAX_ROWS);
if (cv.m_view)
cv.m_view->select();
}
}
}
updateSize();
if (m_state) {
m_inSelectRow = true;
m_state->setSelectedRow(r);
m_inSelectRow = false;
}
}
void Table::setMultiplyColor(const zeus::CColor& color) {
View::setMultiplyColor(color);
if (m_scroll.m_view)
m_scroll.m_view->setMultiplyColor(color);
for (ViewChild<std::unique_ptr<CellView>>& hv : m_headerViews)
if (hv.m_view)
hv.m_view->m_text->setMultiplyColor(color);
for (auto& col : m_cellPools) {
for (ViewChild<std::unique_ptr<CellView>>& cv : col[0])
if (cv.m_view)
cv.m_view->m_text->setMultiplyColor(color);
for (ViewChild<std::unique_ptr<CellView>>& cv : col[1])
if (cv.m_view)
cv.m_view->m_text->setMultiplyColor(color);
}
}
void Table::CellView::select() {
m_selected = true;
m_text->colorGlyphs(rootView().themeData().fieldText());
}
void Table::CellView::deselect() {
m_selected = false;
m_text->colorGlyphs(rootView().themeData().uiText());
}
void Table::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
if (m_state && m_state->columnSplitResizeAllowed()) {
size_t cIdx = 0;
for (const boo::SWindowRect& rect : m_hCellRects) {
if (cIdx == 0) {
++cIdx;
continue;
}
if (abs(coord.pixel[0] - rect.location[0]) < 4 &&
unsigned(coord.pixel[1] - subRect().location[1] - subRect().size[1] + rect.size[1]) <
unsigned(rect.size[1])) {
m_hDraggingIdx = cIdx;
rootView().setActiveDragView(this);
return;
}
++cIdx;
}
}
m_scroll.mouseDown(coord, button, mod);
if (m_headerNeedsUpdate)
_setHeaderVerts(subRect());
if (m_deferredActivation != SIZE_MAX && m_state) {
m_state->rowActivated(m_deferredActivation);
m_deferredActivation = SIZE_MAX;
}
}
void Table::RowsView::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
for (ViewChild<std::unique_ptr<CellView>>& hv : m_t.m_headerViews)
if (hv.mouseDown(coord, button, mod))
return; /* Trap header event */
for (auto& col : m_t.m_cellPools) {
for (ViewChild<std::unique_ptr<CellView>>& cv : col[0])
cv.mouseDown(coord, button, mod);
for (ViewChild<std::unique_ptr<CellView>>& cv : col[1])
cv.mouseDown(coord, button, mod);
}
}
void Table::CellView::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
if (m_r != SIZE_MAX) {
m_t.selectRow(m_r);
if (m_t.m_clickFrames < 15)
m_t.m_deferredActivation = m_r;
else
m_t.m_clickFrames = 0;
} else
m_t.m_headerNeedsUpdate = true;
}
void Table::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
m_scroll.mouseUp(coord, button, mod);
if (m_headerNeedsUpdate)
_setHeaderVerts(subRect());
if (m_hDraggingIdx) {
rootView().setActiveDragView(nullptr);
m_hDraggingIdx = 0;
}
}
void Table::RowsView::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
size_t idx = 0;
for (ViewChild<std::unique_ptr<CellView>>& hv : m_t.m_headerViews) {
if (hv.m_mouseDown && hv.m_mouseIn)
m_t.cycleSortColumn(idx);
hv.mouseUp(coord, button, mod);
++idx;
}
for (auto& col : m_t.m_cellPools) {
for (ViewChild<std::unique_ptr<CellView>>& cv : col[0])
cv.mouseUp(coord, button, mod);
for (ViewChild<std::unique_ptr<CellView>>& cv : col[1])
cv.mouseUp(coord, button, mod);
}
}
void Table::CellView::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
if (m_r == SIZE_MAX)
m_t.m_headerNeedsUpdate = true;
}
void Table::mouseMove(const boo::SWindowCoord& coord) {
if (m_state && m_state->columnSplitResizeAllowed()) {
if (m_hDraggingIdx) {
float split = (coord.pixel[0] - subRect().location[0]) / float(subRect().size[0]);
if (m_hDraggingIdx <= 1)
split = std::max(0.03f, split);
else
split = std::max(m_state->getColumnSplit(m_hDraggingIdx - 1) + 0.03f, split);
if (m_hDraggingIdx >= m_columns - 1)
split = std::min(0.97f, split);
else
split = std::min(m_state->getColumnSplit(m_hDraggingIdx + 1) - 0.03f, split);
m_state->setColumnSplit(m_hDraggingIdx, split);
updateSize();
return;
}
size_t cIdx = 0;
bool hovering = false;
for (const boo::SWindowRect& rect : m_hCellRects) {
if (cIdx++ == 0)
continue;
if (abs(coord.pixel[0] - rect.location[0]) < 4 &&
unsigned(coord.pixel[1] - subRect().location[1] - subRect().size[1] + rect.size[1]) <
unsigned(rect.size[1])) {
hovering = true;
break;
}
}
rootView().setVerticalSplitHover(hovering);
}
m_scroll.mouseMove(coord);
if (m_headerNeedsUpdate)
_setHeaderVerts(subRect());
}
void Table::RowsView::mouseMove(const boo::SWindowCoord& coord) {
for (ViewChild<std::unique_ptr<CellView>>& hv : m_t.m_headerViews)
hv.mouseMove(coord);
for (auto& col : m_t.m_cellPools) {
for (ViewChild<std::unique_ptr<CellView>>& cv : col[0])
cv.mouseMove(coord);
for (ViewChild<std::unique_ptr<CellView>>& cv : col[1])
cv.mouseMove(coord);
}
}
void Table::mouseEnter(const boo::SWindowCoord& coord) {
m_scroll.mouseEnter(coord);
if (m_headerNeedsUpdate)
_setHeaderVerts(subRect());
}
void Table::CellView::mouseEnter(const boo::SWindowCoord& coord) {
if (m_r == SIZE_MAX)
m_t.m_headerNeedsUpdate = true;
}
void Table::mouseLeave(const boo::SWindowCoord& coord) {
m_scroll.mouseLeave(coord);
if (m_headerNeedsUpdate)
_setHeaderVerts(subRect());
}
void Table::RowsView::mouseLeave(const boo::SWindowCoord& coord) {
for (ViewChild<std::unique_ptr<CellView>>& hv : m_t.m_headerViews)
hv.mouseLeave(coord);
for (auto& col : m_t.m_cellPools) {
for (ViewChild<std::unique_ptr<CellView>>& cv : col[0])
cv.mouseLeave(coord);
for (ViewChild<std::unique_ptr<CellView>>& cv : col[1])
cv.mouseLeave(coord);
}
}
void Table::CellView::mouseLeave(const boo::SWindowCoord& coord) {
if (m_r == SIZE_MAX)
m_t.m_headerNeedsUpdate = true;
}
void Table::scroll(const boo::SWindowCoord& coord, const boo::SScrollDelta& scroll) { m_scroll.scroll(coord, scroll); }
void Table::think() {
if (m_scroll.m_view)
m_scroll.m_view->think();
++m_clickFrames;
}
void Table::CellView::reset() {
m_c = -1;
m_r = -1;
if (m_textHash) {
m_text->typesetGlyphs("");
m_textHash = 0;
}
}
bool Table::CellView::reset(size_t c) {
m_c = c;
m_r = -1;
auto headerText = m_t.m_data->header(c);
if (!headerText.empty()) {
uint64_t hash = XXH64(headerText.data(), headerText.size(), 0);
if (hash != m_textHash) {
m_text->typesetGlyphs(headerText, rootView().themeData().uiText());
m_textHash = hash;
}
return true;
} else if (m_textHash) {
m_text->typesetGlyphs("");
m_textHash = 0;
}
return false;
}
bool Table::CellView::reset(size_t c, size_t r) {
m_c = c;
m_r = r;
auto cellText = m_t.m_data->cell(c, r);
if (!cellText.empty()) {
uint64_t hash = XXH64(cellText.data(), cellText.size(), 0);
if (hash != m_textHash) {
m_text->typesetGlyphs(cellText, rootView().themeData().uiText());
m_textHash = hash;
}
return true;
} else if (m_textHash) {
m_text->typesetGlyphs("");
m_textHash = 0;
}
return false;
}
std::vector<Table::ColumnPool>& Table::ensureCellPools(size_t rows, size_t cols, ViewResources& res) {
if (m_cellPools.size() < cols) {
m_cellPools.resize(cols);
m_ensuredRows = 0;
}
if (m_ensuredRows < rows) {
for (size_t i = 0; i < cols; ++i) {
ColumnPool& cp = m_cellPools[i];
if (rows > SPECTER_TABLE_MAX_ROWS) {
for (int p = 0; p < 2; ++p) {
for (ViewChild<std::unique_ptr<CellView>>& cv : cp[p]) {
if (cv.m_view == nullptr) {
cv.m_view = std::make_unique<CellView>(*this, res);
}
}
}
} else {
size_t r = 0;
for (ViewChild<std::unique_ptr<CellView>>& cv : cp[0]) {
if (cv.m_view == nullptr) {
cv.m_view = std::make_unique<CellView>(*this, res);
}
++r;
if (r >= rows) {
break;
}
}
}
}
m_ensuredRows = rows;
}
return m_cellPools;
}
void Table::_updateData() {
m_header = false;
bool newViewChildren = false;
if (m_columns != m_data->columnCount() || m_rows > m_data->rowCount() + SPECTER_TABLE_MAX_ROWS)
newViewChildren = true;
m_rows = m_data->rowCount();
m_columns = m_data->columnCount();
if (!m_columns)
return;
ViewResources& res = rootView().viewRes();
if (newViewChildren) {
m_headerViews.clear();
m_cellPools.clear();
m_headerViews.resize(m_columns);
m_ensuredRows = 0;
}
ensureCellPools(m_rows, m_columns, res);
size_t poolIdx = m_rowsView.m_visibleStart / SPECTER_TABLE_MAX_ROWS;
size_t startRow = poolIdx * SPECTER_TABLE_MAX_ROWS;
int pool0 = (poolIdx & 1) != 0;
int pool1 = (poolIdx & 1) == 0;
for (size_t c = 0; c < m_columns; ++c) {
std::unique_ptr<CellView>& cv = m_headerViews[c].m_view;
if (cv == nullptr) {
cv = std::make_unique<CellView>(*this, res);
}
if (cv->reset(c)) {
m_header = true;
}
ColumnPool& col = m_cellPools[c];
if (poolIdx != m_activePool) {
for (size_t r = startRow, i = 0; i < SPECTER_TABLE_MAX_ROWS; ++r, ++i) {
ViewChild<std::unique_ptr<CellView>>& cv = col[pool0][i];
if (cv.m_view) {
if (r < m_rows)
cv.m_view->reset(c, r);
else
cv.m_view->reset();
}
}
for (size_t r = startRow + SPECTER_TABLE_MAX_ROWS, i = 0; i < SPECTER_TABLE_MAX_ROWS; ++r, ++i) {
ViewChild<std::unique_ptr<CellView>>& cv = col[pool1][i];
if (cv.m_view) {
if (r < m_rows)
cv.m_view->reset(c, r);
else
cv.m_view->reset();
}
}
}
}
m_activePool = poolIdx;
}
void Table::updateData() {
m_activePool = -1;
updateSize();
}
std::vector<boo::SWindowRect> Table::getCellRects(const boo::SWindowRect& sub) const {
if (!m_columns)
return {};
float pf = rootView().viewRes().pixelFactor();
/* Validate column split values */
bool valid = false;
std::vector<float> splits;
splits.reserve(m_columns);
if (m_state) {
float lastSplit = 0.0;
size_t i;
for (i = 0; i < m_columns; ++i) {
float split = m_state->getColumnSplit(i);
if (split < lastSplit || split < 0.0 || split > 1.0)
break;
splits.push_back(split);
lastSplit = split;
}
if (i == m_columns)
valid = true;
}
/* Uniform split otherwise */
if (!valid) {
float split = 0.0;
for (size_t i = 0; i < m_columns; ++i) {
splits.push_back(split);
split += 1.0 / float(m_columns);
}
}
std::vector<boo::SWindowRect> ret;
ret.reserve(m_columns);
int lastX = 0;
for (size_t i = 0; i < m_columns; ++i) {
float nextSplit = (i == m_columns - 1) ? 1.0 : splits[i + 1];
int x = nextSplit * sub.size[0];
ret.push_back({sub.location[0] + lastX, sub.location[1], x - lastX, int(ROW_HEIGHT * pf)});
lastX = x;
}
return ret;
}
void Table::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
View::resized(root, sub);
if (m_scroll.m_view)
m_scroll.m_view->resized(root, sub);
boo::SWindowRect headRow = sub;
float pf = rootView().viewRes().pixelFactor();
headRow.location[1] += sub.size[1] - ROW_HEIGHT * pf;
std::vector<boo::SWindowRect> cellRects = getCellRects(headRow);
_setHeaderVerts(sub);
size_t cIdx = 0;
for (ViewChild<std::unique_ptr<CellView>>& hv : m_headerViews) {
if (hv.m_view)
hv.m_view->resized(root, cellRects[cIdx], sub);
++cIdx;
}
}
int Table::RowsView::nominalHeight() const {
float pf = rootView().viewRes().pixelFactor();
int rows = m_t.m_rows + 1;
return rows * (ROW_HEIGHT + CELL_MARGIN * 2) * pf;
}
int Table::RowsView::nominalWidth() const { return m_t.m_scroll.m_view->nominalWidth(); }
void Table::RowsView::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub,
const boo::SWindowRect& scissor) {
View::resized(root, sub);
_setRowVerts(sub, scissor);
m_t._updateData();
if (!m_t.m_columns)
return;
float pf = rootView().viewRes().pixelFactor();
boo::SWindowRect cellScissor = scissor;
cellScissor.size[1] -= 2 * pf;
boo::SWindowRect rowRect = sub;
rowRect.location[1] += sub.size[1] - ROW_HEIGHT * pf;
int spacing = (ROW_HEIGHT + CELL_MARGIN * 2) * pf;
std::vector<boo::SWindowRect> cellRects = m_t.getCellRects(rowRect);
auto cellRectIt = cellRects.begin();
int poolIdx = m_visibleStart / SPECTER_TABLE_MAX_ROWS;
int pool0 = (poolIdx & 1) != 0;
int pool1 = (poolIdx & 1) == 0;
int locationStart = spacing * poolIdx * SPECTER_TABLE_MAX_ROWS;
for (auto& col : m_t.m_cellPools) {
cellRectIt->location[1] -= locationStart;
for (ViewChild<std::unique_ptr<CellView>>& cv : col[pool0]) {
cellRectIt->location[1] -= spacing;
if (cv.m_view)
cv.m_view->resized(root, *cellRectIt, cellScissor);
}
for (ViewChild<std::unique_ptr<CellView>>& cv : col[pool1]) {
cellRectIt->location[1] -= spacing;
if (cv.m_view)
cv.m_view->resized(root, *cellRectIt, cellScissor);
}
++cellRectIt;
}
m_scissorRect = scissor;
m_scissorRect.size[1] -= spacing;
}
void Table::CellView::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub,
const boo::SWindowRect& scissor) {
View::resized(root, sub);
boo::SWindowRect textRect = sub;
float pf = rootView().viewRes().pixelFactor();
textRect.location[0] += 5 * pf;
textRect.location[1] += 5 * pf;
m_text->resized(root, textRect);
m_scissorRect = sub.intersect(scissor);
}
void Table::draw(boo::IGraphicsCommandQueue* gfxQ) {
if (m_scroll.m_view)
m_scroll.m_view->draw(gfxQ);
}
void Table::RowsView::draw(boo::IGraphicsCommandQueue* gfxQ) {
gfxQ->setShaderDataBinding(m_vertsBinding);
gfxQ->setScissor(m_scissorRect);
gfxQ->draw(1, m_visibleRows * m_t.m_columns * 6 - 2);
int poolIdx = m_t.m_activePool;
int pool0 = (poolIdx & 1) != 0;
int pool1 = (poolIdx & 1) == 0;
for (auto& col : m_t.m_cellPools) {
size_t idx = poolIdx * SPECTER_TABLE_MAX_ROWS;
for (ViewChild<std::unique_ptr<CellView>>& cv : col[pool0]) {
if (cv.m_view && idx >= m_visibleStart && idx < m_visibleStart + m_visibleRows)
cv.m_view->draw(gfxQ);
++idx;
}
for (ViewChild<std::unique_ptr<CellView>>& cv : col[pool1]) {
if (cv.m_view && idx >= m_visibleStart && idx < m_visibleStart + m_visibleRows)
cv.m_view->draw(gfxQ);
++idx;
}
}
if (m_t.m_header) {
gfxQ->setShaderDataBinding(m_t.m_vertsBinding);
gfxQ->setScissor(rootView().subRect());
gfxQ->draw(1, m_t.m_columns * 6 - 2);
for (ViewChild<std::unique_ptr<CellView>>& hv : m_t.m_headerViews)
if (hv.m_view)
hv.m_view->draw(gfxQ);
}
gfxQ->setScissor(rootView().subRect());
}
void Table::CellView::draw(boo::IGraphicsCommandQueue* gfxQ) {
if (m_scissorRect.size[0] && m_scissorRect.size[1]) {
gfxQ->setScissor(m_scissorRect);
m_text->draw(gfxQ);
}
}
} // namespace specter

841
specter/lib/TextField.cpp Normal file
View File

@ -0,0 +1,841 @@
#include "specter/TextField.hpp"
#include "specter/RootView.hpp"
#include "specter/TextView.hpp"
#include "specter/ViewResources.hpp"
#include <boo/graphicsdev/IGraphicsCommandQueue.hpp>
namespace specter {
TextField::TextField(ViewResources& res, View& parentView, IStringBinding* strBind)
: ITextInputView(res, parentView, strBind) {
commitResources(res, [&](boo::IGraphicsDataFactory::Context& ctx) -> bool {
buildResources(ctx, res);
m_vertsBinding.init(ctx, res, 41, m_viewVertBlockBuf);
return true;
});
for (int i = 28; i < 32; ++i)
m_verts[i].m_color = res.themeData().textfieldSelection();
setInactive();
m_vertsBinding.load<decltype(m_verts)>(m_verts);
m_text = std::make_unique<TextView>(res, *this, res.m_mainFont, TextView::Alignment::Left, 1024);
if (strBind != nullptr) {
setText(strBind->getDefault(this));
}
}
TextField::~TextField() = default;
void TextField::_setText() {
if (m_hasTextSet) {
_clearSelectionRange();
m_textStr = m_deferredTextStr;
m_text->typesetGlyphs(m_textStr, m_error ? rootView().themeData().uiText() : rootView().themeData().fieldText());
if (m_controlBinding)
if (IStringBinding* strBind = IStringBinding::castTo(m_controlBinding))
strBind->changed(this, m_textStr);
m_hasTextSet = false;
if (m_deferredMarkStr.size())
m_hasMarkSet = true;
}
}
void TextField::_setMarkedText() {
if (m_hasMarkSet) {
m_markReplStart = m_deferredMarkReplStart;
m_markReplCount = m_deferredMarkReplCount;
m_markSelStart = m_deferredMarkSelStart;
m_markSelCount = m_deferredMarkSelCount;
size_t repPoint;
size_t repEnd;
if (m_selectionCount) {
repPoint = m_selectionStart;
repEnd = m_selectionStart;
} else {
repPoint = m_cursorPos;
repEnd = m_cursorPos;
}
if (m_markReplStart != SIZE_MAX) {
repPoint += m_markReplStart;
repEnd += m_markReplStart + m_markReplCount;
}
std::string_view textStr(m_textStr);
size_t len = UTF8Iterator(textStr.cbegin()).countTo(textStr.cend());
repPoint = std::min(repPoint, len);
repEnd = std::min(repEnd, len);
std::string compStr(textStr.cbegin(), (UTF8Iterator(textStr.cbegin()) + repPoint).iter());
compStr += m_deferredMarkStr;
compStr += std::string((UTF8Iterator(textStr.cbegin()) + repEnd).iter(), textStr.cend());
m_text->typesetGlyphs(compStr, m_error ? rootView().themeData().uiText() : rootView().themeData().fieldText());
size_t pos = m_cursorPos;
if (m_deferredMarkStr.size())
pos += m_markSelStart;
if (m_markSelCount)
_reallySetMarkRange(pos, m_markSelCount);
else
_reallySetCursorPos(pos);
std::string_view deferredMarkStr(m_deferredMarkStr);
std::vector<TextView::RenderGlyph>& glyphs = m_text->accessGlyphs();
size_t defLen = UTF8Iterator(deferredMarkStr.cbegin()).countTo(deferredMarkStr.cend());
for (auto it = glyphs.begin() + repPoint; it < glyphs.begin() + repPoint + defLen; ++it)
it->m_color = rootView().themeData().fieldMarkedText();
m_text->invalidateGlyphs();
m_hasMarkSet = false;
}
}
void TextField::setText(std::string_view str) {
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
UTF8Iterator it(str.cbegin());
for (; it.iter() != str.cend(); ++it)
if (*it.iter() == '\n')
break;
m_deferredTextStr.assign(str.cbegin(), it.iter());
m_hasTextSet = true;
}
void TextField::setInactive() {
const IThemeData& theme = rootView().themeData();
if (m_error) {
m_verts[0].m_color = theme.textfield1Inactive() * zeus::skRed;
m_verts[1].m_color = theme.textfield2Inactive() * zeus::skRed;
m_verts[2].m_color = theme.textfield1Inactive() * zeus::skRed;
m_verts[3].m_color = theme.textfield2Inactive() * zeus::skRed;
m_verts[4].m_color = theme.textfield2Inactive() * zeus::skRed;
for (int i = 5; i < 28; ++i)
m_verts[i].m_color = theme.textfield2Inactive() * zeus::skRed;
} else {
m_verts[0].m_color = theme.textfield1Inactive();
m_verts[1].m_color = theme.textfield2Inactive();
m_verts[2].m_color = theme.textfield1Inactive();
m_verts[3].m_color = theme.textfield2Inactive();
m_verts[4].m_color = theme.textfield2Inactive();
for (int i = 5; i < 28; ++i)
m_verts[i].m_color = theme.textfield2Inactive();
}
m_vertsBinding.load<decltype(m_verts)>(m_verts);
m_bgState = BGState::Inactive;
}
void TextField::setHover() {
const IThemeData& theme = rootView().themeData();
if (m_error) {
m_verts[0].m_color = theme.textfield1Hover() * zeus::skRed;
m_verts[1].m_color = theme.textfield2Hover() * zeus::skRed;
m_verts[2].m_color = theme.textfield1Hover() * zeus::skRed;
m_verts[3].m_color = theme.textfield2Hover() * zeus::skRed;
m_verts[4].m_color = theme.textfield2Hover() * zeus::skRed;
for (int i = 5; i < 28; ++i)
m_verts[i].m_color = theme.textfield2Inactive() * zeus::skRed;
} else {
m_verts[0].m_color = theme.textfield1Hover();
m_verts[1].m_color = theme.textfield2Hover();
m_verts[2].m_color = theme.textfield1Hover();
m_verts[3].m_color = theme.textfield2Hover();
m_verts[4].m_color = theme.textfield2Hover();
for (int i = 5; i < 28; ++i)
m_verts[i].m_color = theme.textfield2Inactive();
}
m_vertsBinding.load<decltype(m_verts)>(m_verts);
m_bgState = BGState::Hover;
}
void TextField::setDisabled() {
const IThemeData& theme = rootView().themeData();
if (m_error) {
m_verts[0].m_color = theme.textfield1Disabled() * zeus::skRed;
m_verts[1].m_color = theme.textfield2Disabled() * zeus::skRed;
m_verts[2].m_color = theme.textfield1Disabled() * zeus::skRed;
m_verts[3].m_color = theme.textfield2Disabled() * zeus::skRed;
m_verts[4].m_color = theme.textfield2Disabled() * zeus::skRed;
for (int i = 5; i < 28; ++i)
m_verts[i].m_color = theme.textfield2Disabled() * zeus::skRed;
} else {
m_verts[0].m_color = theme.textfield1Disabled();
m_verts[1].m_color = theme.textfield2Disabled();
m_verts[2].m_color = theme.textfield1Disabled();
m_verts[3].m_color = theme.textfield2Disabled();
m_verts[4].m_color = theme.textfield2Disabled();
for (int i = 5; i < 28; ++i)
m_verts[i].m_color = theme.textfield2Disabled();
}
m_vertsBinding.load<decltype(m_verts)>(m_verts);
m_bgState = BGState::Disabled;
}
void TextField::refreshBg() {
switch (m_bgState) {
case BGState::Inactive:
setInactive();
break;
case BGState::Hover:
setHover();
break;
case BGState::Disabled:
setDisabled();
break;
}
}
void TextField::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
if (!m_active) {
rootView().setActiveTextView(this);
} else if (m_clickFrames2 < 15) {
std::string_view textStr(m_textStr);
size_t len = UTF8Iterator(textStr.cbegin()).countTo(textStr.cend());
setSelectionRange(0, len);
} else if (m_clickFrames < 15) {
size_t startPos = m_text->reverseSelectGlyph(coord.pixel[0] - m_text->subRect().location[0]);
std::pair<size_t, size_t> range = m_text->queryWholeWordRange(startPos);
setSelectionRange(range.first, range.second);
m_clickFrames2 = 0;
} else {
size_t startPos = m_text->reverseSelectGlyph(coord.pixel[0] - m_text->subRect().location[0]);
setCursorPos(startPos);
m_dragging |= size_t(1) << size_t(button);
m_dragStart = startPos;
rootView().setActiveDragView(this);
}
m_clickFrames = 0;
}
void TextField::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
m_dragging &= ~(size_t(1) << size_t(button));
if (m_dragging == 0)
rootView().setActiveDragView(nullptr);
}
void TextField::mouseMove(const boo::SWindowCoord& coord) {
if (m_dragging != 0) {
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
size_t thisPos = m_text->reverseSelectGlyph(coord.pixel[0] - m_text->subRect().location[0]);
size_t minPos = std::min(m_dragStart, thisPos);
size_t maxPos = std::max(m_dragStart, thisPos);
if (minPos != maxPos)
setSelectionRange(minPos, maxPos - minPos);
else
setCursorPos(minPos);
}
}
void TextField::mouseEnter(const boo::SWindowCoord& coord) {
setHover();
rootView().setTextFieldHover(true);
}
void TextField::mouseLeave(const boo::SWindowCoord& coord) {
setInactive();
rootView().setTextFieldHover(false);
}
void TextField::clipboardCopy() {
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
if (!m_selectionCount)
return;
std::string_view textStr(m_textStr);
UTF8Iterator begin(textStr.cbegin());
begin += m_selectionStart;
UTF8Iterator end(begin.iter());
end += m_selectionCount;
rootView().window()->clipboardCopy(boo::EClipboardType::UTF8String, (uint8_t*)&*begin.iter(),
end.iter() - begin.iter());
}
void TextField::clipboardCut() {
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
if (!m_selectionCount)
return;
std::string_view textStr(m_textStr);
UTF8Iterator begin(textStr.cbegin());
begin += m_selectionStart;
UTF8Iterator end(begin.iter());
end += m_selectionCount;
rootView().window()->clipboardCopy(boo::EClipboardType::UTF8String, (uint8_t*)&*begin.iter(),
end.iter() - begin.iter());
std::string newStr(textStr.cbegin(), (UTF8Iterator(textStr.cbegin()) + m_selectionStart).iter());
newStr.append((UTF8Iterator(textStr.cbegin()) + m_selectionStart + m_selectionCount).iter(), textStr.cend());
size_t selStart = m_selectionStart;
setText(newStr);
setCursorPos(selStart);
}
static std::string SanitizeUTF8TextLine(const char* string, size_t len) {
const char* it = string;
utf8proc_int32_t ch;
utf8proc_ssize_t sz;
std::string ret;
ret.reserve(len);
for (sz = utf8proc_iterate((utf8proc_uint8_t*)it, -1, &ch); it < string + len;
it += sz, sz = utf8proc_iterate((utf8proc_uint8_t*)it, -1, &ch)) {
if (sz <= 0)
break;
if (ch >= 0x20)
ret.append(it, sz);
}
return ret;
}
void TextField::clipboardPaste() {
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
size_t retSz;
std::unique_ptr<uint8_t[]> retData = rootView().window()->clipboardPaste(boo::EClipboardType::UTF8String, retSz);
std::string saniData = SanitizeUTF8TextLine((char*)retData.get(), retSz);
if (retData && saniData.size()) {
std::string_view textStr(m_textStr);
if (m_selectionCount) {
std::string newStr(textStr.cbegin(), (UTF8Iterator(textStr.cbegin()) + m_selectionStart).iter());
newStr.append(saniData);
std::string_view newStrView(newStr);
size_t newSel = UTF8Iterator(newStrView.cbegin()).countTo(newStrView.cend());
newStr.append((UTF8Iterator(textStr.cbegin()) + m_selectionStart + m_selectionCount).iter(), textStr.cend());
setText(newStr);
setCursorPos(newSel);
} else {
std::string newStr(textStr.cbegin(), (UTF8Iterator(textStr.cbegin()) + m_cursorPos).iter());
newStr.append(saniData);
std::string_view newStrView(newStr);
size_t newSel = UTF8Iterator(newStrView.cbegin()).countTo(newStrView.cend());
newStr.append((UTF8Iterator(textStr.cbegin()) + m_cursorPos).iter(), textStr.cend());
setText(newStr);
setCursorPos(newSel);
}
}
}
void TextField::specialKeyDown(boo::ESpecialKey key, boo::EModifierKey mods, bool isRepeat) {
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
if (m_deferredMarkStr.size())
return;
if (key == boo::ESpecialKey::Left) {
if (True(mods & boo::EModifierKey::Shift)) {
if (m_cursorPos) {
size_t origPos = m_cursorPos;
if (m_selectionCount) {
if (m_cursorPos == m_selectionStart)
setSelectionRange(m_cursorPos - 1, m_selectionCount + 1);
else
setSelectionRange(m_selectionStart, m_selectionCount - 1);
} else
setSelectionRange(m_cursorPos - 1, 1);
m_cursorPos = origPos - 1;
}
} else {
if (m_selectionCount)
m_cursorPos = m_selectionStart;
setCursorPos(m_cursorPos == 0 ? 0 : (m_cursorPos - 1));
}
} else if (key == boo::ESpecialKey::Right) {
if (True(mods & boo::EModifierKey::Shift)) {
std::string_view textStr(m_textStr);
size_t len = UTF8Iterator(textStr.cbegin()).countTo(textStr.cend());
if (m_cursorPos < len) {
size_t origPos = m_cursorPos;
if (m_selectionCount) {
if (m_cursorPos == m_selectionStart)
setSelectionRange(m_cursorPos + 1, m_selectionCount - 1);
else
setSelectionRange(m_selectionStart, m_selectionCount + 1);
} else
setSelectionRange(m_cursorPos, 1);
m_cursorPos = origPos + 1;
}
} else {
if (m_selectionCount)
m_cursorPos = m_selectionStart + m_selectionCount - 1;
setCursorPos(m_cursorPos + 1);
}
} else if (key == boo::ESpecialKey::Backspace) {
std::string_view textStr(m_textStr);
if (m_selectionCount) {
std::string newStr(textStr.cbegin(), (UTF8Iterator(textStr.cbegin()) + m_selectionStart).iter());
newStr.append((UTF8Iterator(textStr.cbegin()) + m_selectionStart + m_selectionCount).iter(), textStr.cend());
size_t selStart = m_selectionStart;
setText(newStr);
setCursorPos(selStart);
} else if (m_cursorPos > 0) {
std::string newStr(textStr.cbegin(), (UTF8Iterator(textStr.cbegin()) + (m_cursorPos - 1)).iter());
newStr.append((UTF8Iterator(textStr.cbegin()) + m_cursorPos).iter(), textStr.cend());
setText(newStr);
setCursorPos(m_cursorPos - 1);
}
} else if (key == boo::ESpecialKey::Delete) {
std::string_view textStr(m_textStr);
size_t len = UTF8Iterator(textStr.cbegin()).countTo(textStr.cend());
if (m_selectionCount) {
std::string newStr(textStr.cbegin(), (UTF8Iterator(textStr.cbegin()) + m_selectionStart).iter());
newStr.append((UTF8Iterator(textStr.cbegin()) + m_selectionStart + m_selectionCount).iter(), textStr.cend());
size_t selStart = m_selectionStart;
setText(newStr);
setCursorPos(selStart);
} else if (m_cursorPos < len) {
std::string newStr(textStr.cbegin(), (UTF8Iterator(textStr.cbegin()) + m_cursorPos).iter());
newStr.append((UTF8Iterator(textStr.cbegin()) + (m_cursorPos + 1)).iter(), textStr.cend());
setText(newStr);
setCursorPos(m_cursorPos);
}
}
}
bool TextField::hasMarkedText() const {
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
return m_deferredMarkStr.size() != 0;
}
std::pair<int, int> TextField::markedRange() const {
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
if (m_deferredMarkStr.empty())
return {-1, 0};
std::string_view deferredMarkStr(m_deferredMarkStr);
return {m_cursorPos, UTF8Iterator(deferredMarkStr.cbegin()).countTo(deferredMarkStr.cend())};
}
std::pair<int, int> TextField::selectedRange() const {
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
if (!m_deferredSelectionCount)
return {-1, 0};
return {m_deferredSelectionStart, m_deferredSelectionCount};
}
void TextField::setMarkedText(std::string_view str, const std::pair<int, int>& selectedRange,
const std::pair<int, int>& replacementRange) {
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
m_deferredMarkStr = SanitizeUTF8TextLine(str.data(), str.size());
m_deferredMarkSelStart = selectedRange.first;
m_deferredMarkSelCount = selectedRange.second;
m_deferredMarkReplStart = replacementRange.first;
m_deferredMarkReplCount = replacementRange.second;
m_hasMarkSet = true;
}
void TextField::unmarkText() {
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
m_deferredMarkStr.clear();
m_deferredMarkReplStart = 0;
m_deferredMarkReplCount = 0;
m_deferredMarkSelStart = 0;
m_deferredMarkSelCount = 0;
m_hasMarkSet = true;
}
std::string TextField::substringForRange(const std::pair<int, int>& range, std::pair<int, int>& actualRange) const {
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
std::string_view deferredTextStr(m_deferredTextStr);
UTF8Iterator begin(deferredTextStr.cbegin());
size_t curLen = UTF8Iterator(deferredTextStr.cbegin()).countTo(deferredTextStr.cend());
if (range.first >= curLen)
return std::string();
begin += range.first;
size_t endIdx = std::min(size_t(range.first + range.second), curLen);
UTF8Iterator end(deferredTextStr.cbegin());
end += endIdx;
actualRange.first = range.first;
actualRange.second = endIdx;
return std::string(begin.iter(), end.iter());
}
void TextField::insertText(std::string_view str, const std::pair<int, int>& range) {
std::string saniStr = SanitizeUTF8TextLine(str.data(), str.size());
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
std::string_view deferredTextStr(m_deferredTextStr);
size_t curLen = UTF8Iterator(deferredTextStr.cbegin()).countTo(deferredTextStr.cend());
if (range.first < 0 || range.first >= curLen) {
size_t beginPos = m_deferredCursorPos;
if (m_selectionCount)
beginPos = m_selectionCount;
beginPos = std::min(beginPos, curLen);
std::string newStr(deferredTextStr.cbegin(), (UTF8Iterator(deferredTextStr.cbegin()) + beginPos).iter());
newStr += saniStr;
std::string_view newStrView(newStr);
size_t newPos = UTF8Iterator(newStrView.cbegin()).countTo(newStrView.cend());
newStr += std::string((UTF8Iterator(deferredTextStr.cbegin()) + beginPos).iter(), deferredTextStr.cend());
setText(newStr);
setCursorPos(newPos);
unmarkText();
return;
}
std::string newStr(deferredTextStr.cbegin(), (UTF8Iterator(deferredTextStr.cbegin()) + range.first).iter());
newStr += saniStr;
std::string_view newStrView(newStr);
size_t newSel = UTF8Iterator(newStrView.cbegin()).countTo(newStrView.cend());
size_t endIdx = range.first + range.second;
if (endIdx >= newSel)
endIdx = newSel - 1;
newStr.append((UTF8Iterator(deferredTextStr.cbegin()) + endIdx).iter(), deferredTextStr.cend());
setText(newStr);
setCursorPos(newSel);
unmarkText();
}
int TextField::characterIndexAtPoint(const boo::SWindowCoord& point) const {
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
return m_text->reverseSelectGlyph(point.pixel[0]);
}
boo::SWindowRect TextField::rectForCharacterRange(const std::pair<int, int>& range,
std::pair<int, int>& actualRange) const {
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
std::string_view textStr(m_textStr);
UTF8Iterator begin(textStr.cbegin());
size_t curLen = UTF8Iterator(textStr.cbegin()).countTo(textStr.cend());
if (range.first >= curLen) {
const std::vector<TextView::RenderGlyph>& glyphs = m_text->accessGlyphs();
const TextView::RenderGlyph& g = glyphs.back();
return {subRect().location[0] + int(g.m_pos[3][0]), subRect().location[1] + int(g.m_pos[3][1]), 0, 0};
}
begin += range.first;
size_t endIdx = std::min(size_t(range.first + range.second), curLen);
UTF8Iterator end(textStr.cbegin());
end += endIdx;
actualRange.first = range.first;
actualRange.second = endIdx;
const std::vector<TextView::RenderGlyph>& glyphs = m_text->accessGlyphs();
const TextView::RenderGlyph& g1 = glyphs[range.first];
const TextView::RenderGlyph& g2 = glyphs[endIdx];
return {subRect().location[0] + int(g1.m_pos[1][0]), subRect().location[1] + int(g1.m_pos[1][1]),
int(g2.m_pos[3][0] - g1.m_pos[1][0]), int(g2.m_pos[0][1] - g1.m_pos[1][1])};
}
void TextField::think() {
++m_cursorFrames;
++m_clickFrames;
++m_clickFrames2;
++m_errorFrames;
if (m_error && m_errorFrames <= 360) {
zeus::CColor errMult;
zeus::CColor errBg;
if (m_errorFrames < 300) {
errMult = m_viewVertBlock.m_color;
errBg = rootView().themeData().tooltipBackground() * m_viewVertBlock.m_color;
} else if (m_errorFrames >= 360) {
errMult = zeus::skClear;
errBg = rootView().themeData().tooltipBackground();
errBg[3] = 0.0;
} else {
float t = (m_errorFrames - 300) / 60.0;
errMult = zeus::CColor::lerp(m_viewVertBlock.m_color, zeus::skClear, t);
errBg = zeus::CColor::lerp(rootView().themeData().tooltipBackground() * m_viewVertBlock.m_color,
zeus::skClear, t);
}
for (size_t i = 32; i < 41; ++i)
m_verts[i].m_color = errBg;
m_vertsBinding.load<decltype(m_verts)>(m_verts);
m_errText->setMultiplyColor(errMult);
}
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
_setText();
_setSelectionRange();
_clearSelectionRange();
_setCursorPos();
_setMarkedText();
}
void TextField::setActive(bool active) {
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
m_active = active;
if (!active) {
clearSelectionRange();
rootView().window()->claimKeyboardFocus(nullptr);
} else if (!m_selectionCount) {
std::string_view textStr(m_textStr);
size_t len = UTF8Iterator(textStr.cbegin()).countTo(textStr.cend());
setSelectionRange(0, len);
}
}
void TextField::_reallySetCursorPos(size_t pos) {
float pf = rootView().viewRes().pixelFactor();
int offset1 = 4 * pf + m_text->queryReverseAdvance(pos);
int offset2 = offset1 + 2 * pf;
const zeus::CColor& selColor = rootView().viewRes().themeData().textfieldSelection();
m_verts[28].m_pos.assign(offset1, 18 * pf, 0);
m_verts[28].m_color = selColor;
m_verts[29].m_pos.assign(offset1, 4 * pf, 0);
m_verts[29].m_color = selColor;
m_verts[30].m_pos.assign(offset2, 18 * pf, 0);
m_verts[30].m_color = selColor;
m_verts[31].m_pos.assign(offset2, 4 * pf, 0);
m_verts[31].m_color = selColor;
m_vertsBinding.load<decltype(m_verts)>(m_verts);
int focusRect[2] = {subRect().location[0] + offset1, subRect().location[1]};
rootView().window()->claimKeyboardFocus(focusRect);
}
void TextField::_setCursorPos() {
if (m_hasCursorSet) {
m_hasSelectionClear = true;
_clearSelectionRange();
std::string_view textStr(m_textStr);
m_cursorPos = std::min(m_deferredCursorPos, UTF8Iterator(textStr.cbegin()).countTo(textStr.cend()));
m_deferredCursorPos = m_cursorPos;
m_cursorFrames = 0;
_reallySetCursorPos(m_cursorPos);
m_hasCursorSet = false;
}
}
void TextField::setCursorPos(size_t pos) {
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
m_deferredCursorPos = pos;
m_hasCursorSet = true;
}
void TextField::setErrorState(std::string_view message) {
m_error = true;
if (m_selectionCount)
_reallySetSelectionRange(m_selectionStart, m_selectionCount);
else
clearSelectionRange();
refreshBg();
m_errText = std::make_unique<TextView>(rootView().viewRes(), *this, rootView().viewRes().m_mainFont);
m_errText->typesetGlyphs(message, rootView().themeData().uiText());
updateSize();
m_errorFrames = 0;
}
void TextField::clearErrorState() {
m_error = false;
if (m_selectionCount)
_reallySetSelectionRange(m_selectionStart, m_selectionCount);
else
clearSelectionRange();
refreshBg();
m_errText.reset();
m_errorFrames = 360;
}
void TextField::_reallySetSelectionRange(size_t start, size_t len) {
ViewResources& res = rootView().viewRes();
float pf = res.pixelFactor();
int offset1 = 5 * pf;
int offset2 = offset1;
std::vector<TextView::RenderGlyph>& glyphs = m_text->accessGlyphs();
offset1 += glyphs[start].m_pos[0][0];
offset2 += glyphs[start + len - 1].m_pos[2][0];
const zeus::CColor& selColor = rootView().themeData().selectedFieldText();
const zeus::CColor& deselColor = m_error ? rootView().themeData().uiText() : rootView().themeData().fieldText();
for (size_t i = 0; i < glyphs.size(); ++i) {
if (i >= start && i < start + len)
glyphs[i].m_color = selColor;
else
glyphs[i].m_color = deselColor;
}
m_text->invalidateGlyphs();
m_verts[28].m_pos.assign(offset1, 18 * pf, 0);
m_verts[29].m_pos.assign(offset1, 4 * pf, 0);
m_verts[30].m_pos.assign(offset2, 18 * pf, 0);
m_verts[31].m_pos.assign(offset2, 4 * pf, 0);
m_vertsBinding.load<decltype(m_verts)>(m_verts);
int focusRect[2] = {subRect().location[0] + offset1, subRect().location[1]};
rootView().window()->claimKeyboardFocus(focusRect);
}
void TextField::_reallySetMarkRange(size_t start, size_t len) {
ViewResources& res = rootView().viewRes();
float pf = res.pixelFactor();
int offset1 = 5 * pf;
int offset2 = offset1;
std::vector<TextView::RenderGlyph>& glyphs = m_text->accessGlyphs();
offset1 += glyphs[start].m_pos[0][0];
offset2 += glyphs[start + len - 1].m_pos[2][0];
const zeus::CColor& selColor = rootView().themeData().textfieldMarkSelection();
m_verts[28].m_pos.assign(offset1, 18 * pf, 0);
m_verts[28].m_color = selColor;
m_verts[29].m_pos.assign(offset1, 4 * pf, 0);
m_verts[29].m_color = selColor;
m_verts[30].m_pos.assign(offset2, 18 * pf, 0);
m_verts[30].m_color = selColor;
m_verts[31].m_pos.assign(offset2, 4 * pf, 0);
m_verts[31].m_color = selColor;
m_vertsBinding.load<decltype(m_verts)>(m_verts);
int focusRect[2] = {subRect().location[0] + offset1, subRect().location[1]};
rootView().window()->claimKeyboardFocus(focusRect);
}
void TextField::_setSelectionRange() {
if (m_hasSelectionSet) {
std::string_view textStr(m_textStr);
size_t len = UTF8Iterator(textStr.cbegin()).countTo(textStr.cend());
m_selectionStart = std::min(m_deferredSelectionStart, len - 1);
m_deferredSelectionStart = m_selectionStart;
m_selectionCount = std::min(m_deferredSelectionCount, len - m_selectionStart);
m_deferredSelectionCount = m_selectionCount;
_reallySetSelectionRange(m_selectionStart, m_selectionCount);
m_hasSelectionSet = false;
}
}
void TextField::setSelectionRange(size_t start, size_t count) {
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
if (!count) {
setCursorPos(start);
return;
}
m_deferredSelectionStart = start;
m_deferredSelectionCount = count;
m_hasSelectionSet = true;
m_hasSelectionClear = false;
}
void TextField::_clearSelectionRange() {
if (m_hasSelectionClear) {
m_selectionStart = 0;
m_selectionCount = 0;
const zeus::CColor& deselColor = m_error ? rootView().themeData().uiText() : rootView().themeData().fieldText();
std::vector<TextView::RenderGlyph>& glyphs = m_text->accessGlyphs();
for (size_t i = 0; i < glyphs.size(); ++i)
glyphs[i].m_color = deselColor;
m_text->invalidateGlyphs();
m_hasSelectionClear = false;
}
}
void TextField::clearSelectionRange() {
std::unique_lock<std::recursive_mutex> lk(m_textInputLk);
m_deferredSelectionStart = 0;
m_deferredSelectionCount = 0;
m_hasSelectionClear = true;
m_hasSelectionSet = false;
}
void TextField::setMultiplyColor(const zeus::CColor& color) {
View::setMultiplyColor(color);
m_viewVertBlock.m_color = color;
if (m_viewVertBlockBuf) {
m_viewVertBlockBuf.access().finalAssign(m_viewVertBlock);
}
m_text->setMultiplyColor(color);
if (m_errText) {
m_errText->setMultiplyColor(color);
}
}
void TextField::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
float pf = rootView().viewRes().pixelFactor();
int width = sub.size[0];
int height = 20 * pf;
boo::SWindowRect newRect = sub;
newRect.size[1] = height;
View::resized(root, newRect);
m_verts[0].m_pos.assign(1, height + 1, 0);
m_verts[1].m_pos.assign(1, 1, 0);
m_verts[2].m_pos.assign(width + 1, height + 1, 0);
m_verts[3].m_pos.assign(width + 1, 1, 0);
m_verts[4].m_pos.assign(width + 1, 1, 0);
m_verts[5].m_pos.assign(1, height + 1, 0);
m_verts[6].m_pos.assign(1, height + 1, 0);
m_verts[7].m_pos.assign(0, height + 1, 0);
m_verts[8].m_pos.assign(1, 1, 0);
m_verts[9].m_pos.assign(0, 1, 0);
m_verts[10].m_pos.assign(0, 1, 0);
m_verts[11].m_pos.assign(width + 2, height + 1, 0);
m_verts[12].m_pos.assign(width + 2, height + 1, 0);
m_verts[13].m_pos.assign(width + 1, height + 1, 0);
m_verts[14].m_pos.assign(width + 2, 1, 0);
m_verts[15].m_pos.assign(width + 1, 1, 0);
m_verts[16].m_pos.assign(width + 1, 1, 0);
m_verts[17].m_pos.assign(1, height + 2, 0);
m_verts[18].m_pos.assign(1, height + 2, 0);
m_verts[19].m_pos.assign(1, height + 1, 0);
m_verts[20].m_pos.assign(width + 1, height + 2, 0);
m_verts[21].m_pos.assign(width + 1, height + 1, 0);
m_verts[22].m_pos.assign(width + 1, height + 1, 0);
m_verts[23].m_pos.assign(1, 1, 0);
m_verts[24].m_pos.assign(1, 1, 0);
m_verts[25].m_pos.assign(1, 0, 0);
m_verts[26].m_pos.assign(width + 1, 1, 0);
m_verts[27].m_pos.assign(width + 1, 0, 0);
if (m_error) {
boo::SWindowRect errRect = sub;
errRect.location[1] -= 16 * pf;
errRect.location[0] += 5 * pf;
m_errText->resized(root, errRect);
int eX = 0;
int eY = -22 * pf;
int eWidth = m_errText->nominalWidth() + 10 * pf;
int eHeight = 20 * pf;
m_verts[32].m_pos.assign(eX, eY + eHeight, 0);
m_verts[33].m_pos.assign(eX, eY, 0);
m_verts[34].m_pos.assign(eX + eWidth, eY + eHeight, 0);
m_verts[35].m_pos.assign(eX + eWidth, eY, 0);
m_verts[36] = m_verts[35];
m_verts[37].m_pos.assign(eX + 7 * pf, eY + eHeight + 7 * pf, 0);
m_verts[38] = m_verts[37];
m_verts[39].m_pos.assign(eX, eY + eHeight, 0);
m_verts[40].m_pos.assign(eX + 14 * pf, eY + eHeight, 0);
for (size_t i = 32; i < 41; ++i)
m_verts[i].m_color = zeus::skClear;
}
m_vertsBinding.load<decltype(m_verts)>(m_verts);
m_nomWidth = width;
m_nomHeight = height;
boo::SWindowRect textRect = sub;
textRect.location[0] += 5 * pf;
textRect.location[1] += 7 * pf;
m_text->resized(root, textRect);
}
void TextField::draw(boo::IGraphicsCommandQueue* gfxQ) {
View::draw(gfxQ);
gfxQ->setShaderDataBinding(m_vertsBinding);
gfxQ->draw(0, 28);
if (m_active) {
if (!m_selectionCount && !m_markSelCount) {
if (m_cursorFrames % 60 < 30)
gfxQ->draw(28, 4);
} else
gfxQ->draw(28, 4);
}
if (m_error) {
gfxQ->draw(32, 9);
m_errText->draw(gfxQ);
}
m_text->draw(gfxQ);
}
} // namespace specter

292
specter/lib/TextView.cpp Normal file
View File

@ -0,0 +1,292 @@
#include "specter/RootView.hpp"
#include "specter/TextView.hpp"
#include "specter/ViewResources.hpp"
#include "utf8proc.h"
#include "hecl/Pipeline.hpp"
#include <boo/graphicsdev/IGraphicsCommandQueue.hpp>
#include <freetype/internal/internal.h>
#include <freetype/internal/ftobjs.h>
namespace specter {
static logvisor::Module Log("specter::TextView");
void TextView::Resources::init(boo::IGraphicsDataFactory::Context& ctx, FontCache* fcache) {
m_fcache = fcache;
m_regular = hecl::conv->convert(ctx, Shader_SpecterTextViewShader{});
m_subpixel = hecl::conv->convert(ctx, Shader_SpecterTextViewShaderSubpixel{});
}
void TextView::_commitResources(size_t capacity) {
auto& res = rootView().viewRes();
auto fontTex = m_fontAtlas.texture(res.m_factory);
View::commitResources(res, [&](boo::IGraphicsDataFactory::Context& ctx) {
buildResources(ctx, res);
if (capacity) {
m_glyphBuf = res.m_textRes.m_glyphPool.allocateBlock(res.m_factory, capacity);
boo::ObjToken<boo::IShaderPipeline> shader;
if (m_fontAtlas.subpixel())
shader = res.m_textRes.m_subpixel;
else
shader = res.m_textRes.m_regular;
auto vBufInfo = m_glyphBuf.getBufferInfo();
auto uBufInfo = m_viewVertBlockBuf.getBufferInfo();
boo::ObjToken<boo::IGraphicsBuffer> uBufs[] = {uBufInfo.first.get()};
size_t uBufOffs[] = {size_t(uBufInfo.second)};
size_t uBufSizes[] = {sizeof(ViewBlock)};
boo::ObjToken<boo::ITexture> texs[] = {fontTex.get()};
m_shaderBinding = ctx.newShaderDataBinding(shader, {}, vBufInfo.first.get(), nullptr, 1, uBufs, nullptr, uBufOffs,
uBufSizes, 1, texs, nullptr, nullptr, 0, vBufInfo.second);
}
return true;
});
}
TextView::TextView(ViewResources& res, View& parentView, const FontAtlas& font, Alignment align, size_t capacity)
: View(res, parentView), m_capacity(capacity), m_fontAtlas(font), m_align(align) {
if (size_t(hecl::VertexBufferPool<RenderGlyph>::bucketCapacity()) < capacity)
Log.report(logvisor::Fatal, FMT_STRING("bucket overflow [{}/{}]"), capacity,
hecl::VertexBufferPool<RenderGlyph>::bucketCapacity());
_commitResources(0);
}
TextView::TextView(ViewResources& res, View& parentView, FontTag font, Alignment align, size_t capacity)
: TextView(res, parentView, res.m_textRes.m_fcache->lookupAtlas(font), align, capacity) {}
TextView::RenderGlyph::RenderGlyph(int& adv, const FontAtlas::Glyph& glyph, const zeus::CColor& defaultColor) {
m_pos[0].assign(adv + glyph.m_leftPadding, glyph.m_verticalOffset + glyph.m_height, 0.f);
m_pos[1].assign(adv + glyph.m_leftPadding, glyph.m_verticalOffset, 0.f);
m_pos[2].assign(adv + glyph.m_leftPadding + glyph.m_width, glyph.m_verticalOffset + glyph.m_height, 0.f);
m_pos[3].assign(adv + glyph.m_leftPadding + glyph.m_width, glyph.m_verticalOffset, 0.f);
m_uv[0].assign(glyph.m_uv[0], glyph.m_uv[1], glyph.m_layerFloat);
m_uv[1].assign(glyph.m_uv[0], glyph.m_uv[3], glyph.m_layerFloat);
m_uv[2].assign(glyph.m_uv[2], glyph.m_uv[1], glyph.m_layerFloat);
m_uv[3].assign(glyph.m_uv[2], glyph.m_uv[3], glyph.m_layerFloat);
m_color = defaultColor;
adv += glyph.m_advance;
}
int TextView::DoKern(FT_Pos val, const FontAtlas& atlas) {
if (!val)
return 0;
val = FT_MulFix(val, atlas.FT_Xscale());
FT_Pos orig_x = val;
/* we scale down kerning values for small ppem values */
/* to avoid that rounding makes them too big. */
/* `25' has been determined heuristically. */
if (atlas.FT_XPPem() < 25)
val = FT_MulDiv(orig_x, atlas.FT_XPPem(), 25);
return FT_PIX_ROUND(val) >> 6;
}
void TextView::typesetGlyphs(std::string_view str, const zeus::CColor& defaultColor) {
UTF8Iterator it(str.begin());
size_t charLen = str.size() ? std::min(it.countTo(str.end()), m_capacity) : 0;
if (charLen > m_curSize) {
m_curSize = charLen;
_commitResources(charLen);
}
uint32_t lCh = UINT32_MAX;
m_glyphs.clear();
m_glyphs.reserve(charLen);
m_glyphInfo.clear();
m_glyphInfo.reserve(charLen);
int adv = 0;
if (charLen) {
for (; it.iter() < str.end(); ++it) {
utf8proc_int32_t ch = *it;
if (ch == -1) {
Log.report(logvisor::Warning, FMT_STRING("invalid UTF-8 char"));
break;
}
if (ch == '\n' || ch == '\0')
break;
const FontAtlas::Glyph* glyph = m_fontAtlas.lookupGlyph(ch);
if (!glyph)
continue;
if (lCh != UINT32_MAX)
adv += DoKern(m_fontAtlas.lookupKern(lCh, glyph->m_glyphIdx), m_fontAtlas);
m_glyphs.emplace_back(adv, *glyph, defaultColor);
m_glyphInfo.emplace_back(ch, glyph->m_width, glyph->m_height, adv);
lCh = glyph->m_glyphIdx;
if (m_glyphs.size() == m_capacity)
break;
}
}
if (m_align == Alignment::Right) {
int adj = -adv;
for (RenderGlyph& g : m_glyphs) {
g.m_pos[0][0] += adj;
g.m_pos[1][0] += adj;
g.m_pos[2][0] += adj;
g.m_pos[3][0] += adj;
}
} else if (m_align == Alignment::Center) {
int adj = -adv / 2;
for (RenderGlyph& g : m_glyphs) {
g.m_pos[0][0] += adj;
g.m_pos[1][0] += adj;
g.m_pos[2][0] += adj;
g.m_pos[3][0] += adj;
}
}
m_width = adv;
invalidateGlyphs();
updateSize();
}
void TextView::typesetGlyphs(std::wstring_view str, const zeus::CColor& defaultColor) {
size_t charLen = std::min(str.size(), m_capacity);
if (charLen > m_curSize) {
m_curSize = charLen;
_commitResources(charLen);
}
uint32_t lCh = UINT32_MAX;
m_glyphs.clear();
m_glyphs.reserve(charLen);
m_glyphInfo.clear();
m_glyphInfo.reserve(charLen);
int adv = 0;
for (wchar_t ch : str) {
if (ch == L'\n')
break;
const FontAtlas::Glyph* glyph = m_fontAtlas.lookupGlyph(ch);
if (!glyph)
continue;
if (lCh != UINT32_MAX)
adv += DoKern(m_fontAtlas.lookupKern(lCh, glyph->m_glyphIdx), m_fontAtlas);
m_glyphs.emplace_back(adv, *glyph, defaultColor);
m_glyphInfo.emplace_back(ch, glyph->m_width, glyph->m_height, adv);
lCh = glyph->m_glyphIdx;
if (m_glyphs.size() == m_capacity)
break;
}
if (m_align == Alignment::Right) {
int adj = -adv;
for (RenderGlyph& g : m_glyphs) {
g.m_pos[0][0] += adj;
g.m_pos[1][0] += adj;
g.m_pos[2][0] += adj;
g.m_pos[3][0] += adj;
}
} else if (m_align == Alignment::Center) {
int adj = -adv / 2;
for (RenderGlyph& g : m_glyphs) {
g.m_pos[0][0] += adj;
g.m_pos[1][0] += adj;
g.m_pos[2][0] += adj;
g.m_pos[3][0] += adj;
}
}
m_width = adv;
invalidateGlyphs();
updateSize();
}
void TextView::colorGlyphs(const zeus::CColor& newColor) {
for (RenderGlyph& glyph : m_glyphs)
glyph.m_color = newColor;
invalidateGlyphs();
}
void TextView::colorGlyphsTypeOn(const zeus::CColor& newColor, float startInterval, float fadeTime) {}
void TextView::invalidateGlyphs() {
if (m_glyphBuf) {
RenderGlyph* out = m_glyphBuf.access();
size_t i = 0;
for (RenderGlyph& glyph : m_glyphs)
out[i++] = glyph;
}
}
void TextView::think() {}
void TextView::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) { View::resized(root, sub); }
void TextView::draw(boo::IGraphicsCommandQueue* gfxQ) {
View::draw(gfxQ);
if (m_glyphs.size()) {
gfxQ->setShaderDataBinding(m_shaderBinding);
gfxQ->drawInstances(0, 4, m_glyphs.size());
}
}
std::pair<int, int> TextView::queryGlyphDimensions(size_t pos) const {
if (pos >= m_glyphInfo.size())
Log.report(logvisor::Fatal, FMT_STRING("TextView::queryGlyphWidth({}) out of bounds: {}"), pos,
m_glyphInfo.size());
return m_glyphInfo[pos].m_dims;
}
size_t TextView::reverseSelectGlyph(int x) const {
size_t ret = 0;
size_t idx = 1;
int minDelta = abs(x);
for (const RenderGlyphInfo& info : m_glyphInfo) {
int thisDelta = abs(info.m_adv - x);
if (thisDelta < minDelta) {
minDelta = thisDelta;
ret = idx;
}
++idx;
}
return ret;
}
int TextView::queryReverseAdvance(size_t idx) const {
if (idx > m_glyphInfo.size())
Log.report(logvisor::Fatal, FMT_STRING("TextView::queryReverseGlyph({}) out of inclusive bounds: {}"), idx,
m_glyphInfo.size());
if (!idx)
return 0;
return m_glyphInfo[idx - 1].m_adv;
}
std::pair<size_t, size_t> TextView::queryWholeWordRange(size_t idx) const {
if (idx > m_glyphInfo.size())
Log.report(logvisor::Fatal, FMT_STRING("TextView::queryWholeWordRange({}) out of inclusive bounds: {}"), idx,
m_glyphInfo.size());
if (m_glyphInfo.empty())
return {0, 0};
if (idx == m_glyphInfo.size())
--idx;
size_t begin = idx;
while (begin > 0 && !m_glyphInfo[begin - 1].m_space)
--begin;
size_t end = idx;
while (end < m_glyphInfo.size() && !m_glyphInfo[end].m_space)
++end;
return {begin, end - begin};
}
} // namespace specter

167
specter/lib/Toolbar.cpp Normal file
View File

@ -0,0 +1,167 @@
#include "specter/Toolbar.hpp"
#include "specter/RootView.hpp"
#include "specter/ViewResources.hpp"
#include <boo/graphicsdev/IGraphicsCommandQueue.hpp>
#include <logvisor/logvisor.hpp>
#define TOOLBAR_PADDING 10
namespace specter {
static logvisor::Module Log("specter::Toolbar");
static const zeus::RGBA32 Tex[] = {{{255, 255, 255, 64}}, {{255, 255, 255, 64}}, {{0, 0, 0, 64}}, {{0, 0, 0, 64}}};
void Toolbar::Resources::init(boo::IGraphicsDataFactory::Context& ctx, const IThemeData& theme) {
m_shadingTex = ctx.newStaticTexture(4, 1, 1, boo::TextureFormat::RGBA8, boo::TextureClampMode::Repeat, Tex, 16);
}
Toolbar::Toolbar(ViewResources& res, View& parentView, Position tbPos, unsigned units)
: View(res, parentView)
, m_units(units)
, m_children(units)
, m_nomGauge(res.pixelFactor() * SPECTER_TOOLBAR_GAUGE * units)
, m_padding(res.pixelFactor() * TOOLBAR_PADDING) {
commitResources(res, [&](boo::IGraphicsDataFactory::Context& ctx) -> bool {
buildResources(ctx, res);
m_tbBlockBuf = res.m_viewRes.m_bufPool.allocateBlock(res.m_factory);
m_vertsBinding.init(ctx, res, 10, m_tbBlockBuf, res.m_toolbarRes.m_shadingTex.get());
return true;
});
setBackground(res.themeData().toolbarBackground());
}
Toolbar::~Toolbar() = default;
void Toolbar::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
for (std::vector<ViewChild<View*>>& u : m_children)
for (ViewChild<View*>& c : u)
c.mouseDown(coord, button, mod);
}
void Toolbar::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) {
for (std::vector<ViewChild<View*>>& u : m_children)
for (ViewChild<View*>& c : u)
c.mouseUp(coord, button, mod);
}
void Toolbar::mouseMove(const boo::SWindowCoord& coord) {
for (std::vector<ViewChild<View*>>& u : m_children)
for (ViewChild<View*>& c : u)
c.mouseMove(coord);
}
void Toolbar::mouseEnter(const boo::SWindowCoord& coord) {
for (std::vector<ViewChild<View*>>& u : m_children)
for (ViewChild<View*>& c : u)
c.mouseEnter(coord);
}
void Toolbar::mouseLeave(const boo::SWindowCoord& coord) {
for (std::vector<ViewChild<View*>>& u : m_children)
for (ViewChild<View*>& c : u)
c.mouseLeave(coord);
}
void Toolbar::setHorizontalVerts(int width) {
m_tbVerts[0].m_pos.assign(0, 1 + m_nomGauge, 0);
m_tbVerts[0].m_uv.assign(0, 0);
m_tbVerts[1].m_pos.assign(0, -1 + m_nomGauge, 0);
m_tbVerts[1].m_uv.assign(0.5, 0);
m_tbVerts[2].m_pos.assign(width, 1 + m_nomGauge, 0);
m_tbVerts[2].m_uv.assign(0, 0);
m_tbVerts[3].m_pos.assign(width, -1 + m_nomGauge, 0);
m_tbVerts[3].m_uv.assign(0.5, 0);
m_tbVerts[4].m_pos.assign(width, -1 + m_nomGauge, 0);
m_tbVerts[4].m_uv.assign(0.5, 0);
m_tbVerts[5].m_pos.assign(0, 1, 0);
m_tbVerts[5].m_uv.assign(0.5, 0);
m_tbVerts[6].m_pos.assign(0, 1, 0);
m_tbVerts[6].m_uv.assign(0.5, 0);
m_tbVerts[7].m_pos.assign(0, -1, 0);
m_tbVerts[7].m_uv.assign(1, 0);
m_tbVerts[8].m_pos.assign(width, 1, 0);
m_tbVerts[8].m_uv.assign(0.5, 0);
m_tbVerts[9].m_pos.assign(width, -1, 0);
m_tbVerts[9].m_uv.assign(1, 0);
}
void Toolbar::setVerticalVerts(int height) {
m_tbVerts[0].m_pos.assign(-1, height, 0);
m_tbVerts[0].m_uv.assign(0, 0);
m_tbVerts[1].m_pos.assign(-1, 0, 0);
m_tbVerts[1].m_uv.assign(0, 0);
m_tbVerts[2].m_pos.assign(1, height, 0);
m_tbVerts[2].m_uv.assign(0.5, 0);
m_tbVerts[3].m_pos.assign(1, 0, 0);
m_tbVerts[3].m_uv.assign(0.5, 0);
m_tbVerts[4].m_pos.assign(1, 0, 0);
m_tbVerts[4].m_uv.assign(0.5, 0);
m_tbVerts[5].m_pos.assign(-1 + m_nomGauge, height, 0);
m_tbVerts[5].m_uv.assign(0.5, 0);
m_tbVerts[6].m_pos.assign(-1 + m_nomGauge, height, 0);
m_tbVerts[6].m_uv.assign(0.5, 0);
m_tbVerts[7].m_pos.assign(-1 + m_nomGauge, 0, 0);
m_tbVerts[7].m_uv.assign(0.5, 0);
m_tbVerts[8].m_pos.assign(1 + m_nomGauge, height, 0);
m_tbVerts[8].m_uv.assign(1, 0);
m_tbVerts[9].m_pos.assign(1 + m_nomGauge, 0, 0);
m_tbVerts[9].m_uv.assign(1, 0);
}
void Toolbar::push_back(View* v, unsigned unit) {
if (unit >= m_units) {
Log.report(logvisor::Fatal, FMT_STRING("unit {} out of range {}"), unit, m_units);
}
std::vector<ViewChild<View*>>& children = m_children[unit];
auto& child = children.emplace_back();
child.m_view = v;
}
void Toolbar::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
View::resized(root, sub);
setHorizontalVerts(sub.size[0]);
m_vertsBinding.load<decltype(m_tbVerts)>(m_tbVerts);
m_tbBlock.setViewRect(root, sub);
m_tbBlockBuf.access().finalAssign(m_tbBlock);
float gaugeUnit = rootView().viewRes().pixelFactor() * SPECTER_TOOLBAR_GAUGE;
float yOff = 0.0;
for (std::vector<ViewChild<View*>>& u : m_children) {
boo::SWindowRect childRect = sub;
boo::SWindowRect containRect = sub;
containRect.location[0] += m_padding;
containRect.size[0] -= m_padding * 2;
containRect.size[1] = gaugeUnit;
for (ViewChild<View*>& c : u) {
c.m_view->containerResized(root, containRect);
childRect.size[0] = c.m_view->nominalWidth();
childRect.size[1] = c.m_view->nominalHeight();
childRect.location[0] += m_padding;
childRect.location[1] = sub.location[1] + (gaugeUnit - childRect.size[1]) / 2 - 1 + yOff;
c.m_view->resized(root, childRect);
childRect.location[0] += childRect.size[0];
containRect.location[0] += m_padding + childRect.size[0];
containRect.size[0] -= m_padding + childRect.size[0];
containRect.size[1] = gaugeUnit;
}
yOff += gaugeUnit;
}
}
void Toolbar::draw(boo::IGraphicsCommandQueue* gfxQ) {
View::draw(gfxQ);
gfxQ->setShaderDataBinding(m_vertsBinding);
gfxQ->draw(0, 10);
for (std::vector<ViewChild<View*>>& u : m_children)
for (ViewChild<View*>& c : u)
c.m_view->draw(gfxQ);
}
} // namespace specter

123
specter/lib/Tooltip.cpp Normal file
View File

@ -0,0 +1,123 @@
#include "specter/Tooltip.hpp"
#include "specter/MultiLineTextView.hpp"
#include "specter/RootView.hpp"
#include "specter/ViewResources.hpp"
#include <boo/graphicsdev/IGraphicsCommandQueue.hpp>
namespace specter {
#define TOOLTIP_MAX_WIDTH 316
#define TOOLTIP_MAX_TEXT_WIDTH 300
Tooltip::Tooltip(ViewResources& res, View& parentView, std::string_view title, std::string_view message)
: View(res, parentView), m_titleStr(title), m_messageStr(message) {
for (int i = 0; i < 16; ++i)
m_ttVerts[i].m_color = res.themeData().tooltipBackground();
commitResources(res, [&](boo::IGraphicsDataFactory::Context& ctx) -> bool {
buildResources(ctx, res);
m_ttBlockBuf = res.m_viewRes.m_bufPool.allocateBlock(res.m_factory);
m_vertsBinding.init(ctx, res, 16, m_ttBlockBuf);
return true;
});
for (int i = 0; i < 4; ++i) {
m_cornersOutline[i] = std::make_unique<TextView>(res, *this, res.m_curveFont, TextView::Alignment::Left, 1);
m_cornersFilled[i] = std::make_unique<TextView>(res, *this, res.m_curveFont, TextView::Alignment::Left, 1);
}
m_cornersOutline[0]->typesetGlyphs(L"\xF4F0");
m_cornersFilled[0]->typesetGlyphs(L"\xF4F1", res.themeData().tooltipBackground());
m_cornersOutline[1]->typesetGlyphs(L"\xF4F2");
m_cornersFilled[1]->typesetGlyphs(L"\xF4F3", res.themeData().tooltipBackground());
m_cornersOutline[2]->typesetGlyphs(L"\xF4F4");
m_cornersFilled[2]->typesetGlyphs(L"\xF4F5", res.themeData().tooltipBackground());
m_cornersOutline[3]->typesetGlyphs(L"\xF4F6");
m_cornersFilled[3]->typesetGlyphs(L"\xF4F7", res.themeData().tooltipBackground());
m_title = std::make_unique<TextView>(res, *this, res.m_heading14);
m_title->typesetGlyphs(m_titleStr);
m_message = std::make_unique<MultiLineTextView>(res, *this, res.m_mainFont);
m_message->typesetGlyphs(m_messageStr, zeus::skWhite, int(TOOLTIP_MAX_TEXT_WIDTH * res.pixelFactor()));
float pf = res.pixelFactor();
std::pair<int, int> margin = m_cornersOutline[0]->queryGlyphDimensions(0);
m_nomWidth = std::min(int(TOOLTIP_MAX_WIDTH * pf),
int(std::max(m_title->nominalWidth(), m_message->nominalWidth()) + margin.first * 2));
m_nomHeight = m_title->nominalHeight() + m_message->nominalHeight() + margin.second * 3;
}
Tooltip::~Tooltip() = default;
void Tooltip::setVerts(int width, int height, float pf) {
std::pair<int, int> margin = m_cornersFilled[0]->queryGlyphDimensions(0);
width = std::max(width, margin.first * 2);
height = std::max(height, margin.second * 2);
m_ttVerts[0].m_pos.assign(0, height - margin.second, 0);
m_ttVerts[1].m_pos.assign(0, margin.second, 0);
m_ttVerts[2].m_pos.assign(width, height - margin.second, 0);
m_ttVerts[3].m_pos.assign(width, margin.second, 0);
m_ttVerts[4].m_pos.assign(width, margin.second, 0);
m_ttVerts[5].m_pos.assign(margin.first, height, 0);
m_ttVerts[6].m_pos.assign(margin.first, height, 0);
m_ttVerts[7].m_pos.assign(margin.first, height - margin.second, 0);
m_ttVerts[8].m_pos.assign(width - margin.first, height, 0);
m_ttVerts[9].m_pos.assign(width - margin.first, height - margin.second, 0);
m_ttVerts[10].m_pos.assign(width - margin.first, height - margin.second, 0);
m_ttVerts[11].m_pos.assign(margin.first, margin.second, 0);
m_ttVerts[12].m_pos.assign(margin.first, margin.second, 0);
m_ttVerts[13].m_pos.assign(margin.first, 0, 0);
m_ttVerts[14].m_pos.assign(width - margin.first, margin.second, 0);
m_ttVerts[15].m_pos.assign(width - margin.first, 0, 0);
m_vertsBinding.load<decltype(m_ttVerts)>(m_ttVerts);
}
void Tooltip::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
View::resized(root, sub);
float pf = rootView().viewRes().pixelFactor();
setVerts(m_nomWidth, m_nomHeight, pf);
m_ttBlock.setViewRect(root, sub);
m_ttBlockBuf.access().finalAssign(m_ttBlock);
std::pair<int, int> margin = m_cornersFilled[0]->queryGlyphDimensions(0);
boo::SWindowRect textRect = sub;
textRect.location[0] += margin.first;
textRect.location[1] += margin.second * 1.5;
m_message->resized(root, textRect);
textRect.location[1] += m_message->nominalHeight() + margin.second;
m_title->resized(root, textRect);
boo::SWindowRect cornerRect = sub;
cornerRect.location[1] += m_nomHeight - margin.second; // Upper left
m_cornersOutline[0]->resized(root, cornerRect);
m_cornersFilled[0]->resized(root, cornerRect);
cornerRect.location[0] += m_nomWidth - margin.first; // Upper right
m_cornersOutline[1]->resized(root, cornerRect);
m_cornersFilled[1]->resized(root, cornerRect);
cornerRect.location[1] = sub.location[1]; // Lower right
m_cornersOutline[2]->resized(root, cornerRect);
m_cornersFilled[2]->resized(root, cornerRect);
cornerRect.location[0] = sub.location[0]; // Lower left
m_cornersOutline[3]->resized(root, cornerRect);
m_cornersFilled[3]->resized(root, cornerRect);
}
void Tooltip::draw(boo::IGraphicsCommandQueue* gfxQ) {
gfxQ->setShaderDataBinding(m_vertsBinding);
gfxQ->draw(0, 16);
for (int i = 0; i < 4; ++i)
m_cornersFilled[i]->draw(gfxQ);
m_title->draw(gfxQ);
m_message->draw(gfxQ);
}
} // namespace specter

102
specter/lib/View.cpp Normal file
View File

@ -0,0 +1,102 @@
#include "specter/View.hpp"
#include "specter/RootView.hpp"
#include "specter/ViewResources.hpp"
#include <boo/System.hpp>
#include <boo/graphicsdev/IGraphicsCommandQueue.hpp>
#include <hecl/Pipeline.hpp>
namespace specter {
zeus::CMatrix4f g_PlatformMatrix;
void View::Resources::init(boo::IGraphicsDataFactory::Context& ctx, const IThemeData& theme) {
switch (ctx.platform()) {
case boo::IGraphicsDataFactory::Platform::Vulkan:
g_PlatformMatrix.m[1][1] = -1.f;
break;
default:
break;
}
m_solidShader = hecl::conv->convert(ctx, Shader_SpecterViewShaderSolid{});
m_texShader = hecl::conv->convert(ctx, Shader_SpecterViewShaderTex{});
}
void View::buildResources(boo::IGraphicsDataFactory::Context& ctx, ViewResources& res) {
m_viewVertBlockBuf = res.m_viewRes.m_bufPool.allocateBlock(res.m_factory);
m_bgVertsBinding.init(ctx, res, 4, m_viewVertBlockBuf);
}
View::View(ViewResources& res)
: m_rootView(*static_cast<RootView*>(this)), m_parentView(*static_cast<RootView*>(this)) {}
View::View(ViewResources& res, View& parentView) : m_rootView(parentView.rootView()), m_parentView(parentView) {}
void View::updateSize() { resized(m_rootView.rootRect(), m_subRect); }
void View::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) {
m_subRect = sub;
m_viewVertBlock.setViewRect(root, sub);
m_bgRect[0].m_pos.assign(0.f, sub.size[1], 0.f);
m_bgRect[1].m_pos.assign(0.f, 0.f, 0.f);
m_bgRect[2].m_pos.assign(sub.size[0], sub.size[1], 0.f);
m_bgRect[3].m_pos.assign(sub.size[0], 0.f, 0.f);
if (m_viewVertBlockBuf)
m_viewVertBlockBuf.access().finalAssign(m_viewVertBlock);
m_bgVertsBinding.load<decltype(m_bgRect)>(m_bgRect);
}
void View::resized(const ViewBlock& vb, const boo::SWindowRect& sub) {
m_subRect = sub;
m_bgRect[0].m_pos.assign(0.f, sub.size[1], 0.f);
m_bgRect[1].m_pos.assign(0.f, 0.f, 0.f);
m_bgRect[2].m_pos.assign(sub.size[0], sub.size[1], 0.f);
m_bgRect[3].m_pos.assign(sub.size[0], 0.f, 0.f);
if (m_viewVertBlockBuf)
m_viewVertBlockBuf.access().finalAssign(vb);
m_bgVertsBinding.load<decltype(m_bgRect)>(m_bgRect);
}
void View::draw(boo::IGraphicsCommandQueue* gfxQ) {
if (m_bgVertsBinding.m_shaderBinding) {
gfxQ->setShaderDataBinding(m_bgVertsBinding);
gfxQ->draw(0, 4);
}
}
void View::commitResources(ViewResources& res, const boo::FactoryCommitFunc& commitFunc) {
res.m_factory->commitTransaction(commitFunc BooTrace);
}
void View::VertexBufferBindingSolid::init(boo::IGraphicsDataFactory::Context& ctx, ViewResources& res, size_t count,
const hecl::UniformBufferPool<ViewBlock>::Token& viewBlockBuf) {
m_vertsBuf = res.m_viewRes.m_solidPool.allocateBlock(res.m_factory, count);
auto vBufInfo = m_vertsBuf.getBufferInfo();
auto uBufInfo = viewBlockBuf.getBufferInfo();
boo::ObjToken<boo::IGraphicsBuffer> bufs[] = {uBufInfo.first.get()};
size_t bufOffs[] = {size_t(uBufInfo.second)};
size_t bufSizes[] = {sizeof(ViewBlock)};
m_shaderBinding =
ctx.newShaderDataBinding(res.m_viewRes.m_solidShader, vBufInfo.first.get(), nullptr, nullptr, 1, bufs, nullptr,
bufOffs, bufSizes, 0, nullptr, nullptr, nullptr, vBufInfo.second);
}
void View::VertexBufferBindingTex::init(boo::IGraphicsDataFactory::Context& ctx, ViewResources& res, size_t count,
const hecl::UniformBufferPool<ViewBlock>::Token& viewBlockBuf,
const boo::ObjToken<boo::ITexture>& texture) {
m_vertsBuf = res.m_viewRes.m_texPool.allocateBlock(res.m_factory, count);
auto vBufInfo = m_vertsBuf.getBufferInfo();
auto uBufInfo = viewBlockBuf.getBufferInfo();
boo::ObjToken<boo::IGraphicsBuffer> bufs[] = {uBufInfo.first.get()};
size_t bufOffs[] = {size_t(uBufInfo.second)};
size_t bufSizes[] = {sizeof(ViewBlock)};
boo::ObjToken<boo::ITexture> tex[] = {texture};
m_shaderBinding = ctx.newShaderDataBinding(res.m_viewRes.m_texShader, vBufInfo.first.get(), nullptr, nullptr, 1, bufs,
nullptr, bufOffs, bufSizes, 1, tex, nullptr, nullptr, vBufInfo.second);
}
} // namespace specter

View File

@ -0,0 +1,75 @@
#include "specter/ViewResources.hpp"
#include <boo/System.hpp>
#include <logvisor/logvisor.hpp>
namespace specter {
static logvisor::Module Log("specter::ViewResources");
void ViewResources::init(boo::IGraphicsDataFactory* factory, FontCache* fcache, const IThemeData* theme, float pf) {
if (!factory || !fcache || !theme)
Log.report(logvisor::Fatal, FMT_STRING("all arguments of ViewResources::init() must be non-null"));
m_pixelFactor = pf;
m_factory = factory;
m_theme = theme;
m_fcache = fcache;
unsigned dpi = 72.f * m_pixelFactor;
m_curveFont = fcache->prepCurvesFont(AllCharFilter, false, 8.f, dpi);
factory->commitTransaction([&](boo::IGraphicsDataFactory::Context& ctx) {
init(ctx, *theme, fcache);
return true;
} BooTrace);
factory->waitUntilShadersReady();
}
void ViewResources::destroyResData() {
m_viewRes.destroy();
m_textRes.destroy();
m_splitRes.destroy();
m_toolbarRes.destroy();
m_buttonRes.destroy();
}
void ViewResources::prepFontCacheSync() {
unsigned dpi = 72.f * m_pixelFactor;
if (m_fcacheInterrupt.load())
return;
m_mainFont = m_fcache->prepMainFont(AllCharFilter, false, 10.f, dpi);
if (m_fcacheInterrupt.load())
return;
m_monoFont10 = m_fcache->prepMonoFont(AllCharFilter, false, 10.f, dpi);
if (m_fcacheInterrupt.load())
return;
m_monoFont18 = m_fcache->prepMonoFont(AllCharFilter, false, 18.f, dpi);
if (m_fcacheInterrupt.load())
return;
m_heading14 = m_fcache->prepMainFont(LatinAndJapaneseCharFilter, false, 14.f, dpi);
if (m_fcacheInterrupt.load())
return;
m_heading18 = m_fcache->prepMainFont(LatinAndJapaneseCharFilter, false, 18.f, dpi);
if (m_fcacheInterrupt.load())
return;
m_titleFont = m_fcache->prepMainFont(LatinCharFilter, false, 36.f, dpi);
if (m_fcacheInterrupt.load())
return;
m_fcache->closeBuiltinFonts();
m_fcacheReady.store(true);
}
void ViewResources::prepFontCacheAsync(boo::IWindow* window) {
m_fcacheReady.store(false);
m_fcacheThread = std::thread([this]() { prepFontCacheSync(); });
}
void ViewResources::resetPixelFactor(float pf) {
m_pixelFactor = pf;
unsigned dpi = 72.f * m_pixelFactor;
m_curveFont = m_fcache->prepCurvesFont(AllCharFilter, false, 8.f, dpi);
prepFontCacheSync();
}
void ViewResources::resetTheme(const IThemeData* theme) { m_theme = theme; }
} // namespace specter

View File

@ -0,0 +1,4 @@
bintoc(droidsans-permissive.cpp droidsans-permissive.ttf.gz DROIDSANS_PERMISSIVE)
bintoc(bmonofont-i18n.cpp bmonofont-i18n.ttf.gz BMONOFONT)
bintoc(SpecterCurveGlyphs.cpp SpecterCurveGlyphs.ttf.gz SPECTERCURVES)
add_library(specter-fonts droidsans-permissive.cpp bmonofont-i18n.cpp SpecterCurveGlyphs.cpp)

View File

@ -0,0 +1,100 @@
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. Glyphs imported from Arev fonts are (c) Tavmjung Bah (see below)
'DeJaVu-Lite' changes (removing characters for lighter file size) are in public domain. Source file is accompanied in this directory, BZip2 compressed, DeJaVuSans-Lite.sfd.bz2 .
Bitstream Vera Fonts Copyright
------------------------------
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of the fonts accompanying this license ("Fonts") and associated
documentation files (the "Font Software"), to reproduce and distribute the
Font Software, including without limitation the rights to use, copy, merge,
publish, distribute, and/or sell copies of the Font Software, and to permit
persons to whom the Font Software is furnished to do so, subject to the
following conditions:
The above copyright and trademark notices and this permission notice shall
be included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular
the designs of glyphs or characters in the Fonts may be modified and
additional glyphs or characters may be added to the Fonts, only if the fonts
are renamed to names not containing either the words "Bitstream" or the word
"Vera".
This License becomes null and void to the extent applicable to Fonts or Font
Software that has been modified and is distributed under the "Bitstream
Vera" names.
The Font Software may be sold as part of a larger software package but no
copy of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
FONT SOFTWARE.
Except as contained in this notice, the names of Gnome, the Gnome
Foundation, and Bitstream Inc., shall not be used in advertising or
otherwise to promote the sale, use or other dealings in this Font Software
without prior written authorization from the Gnome Foundation or Bitstream
Inc., respectively. For further information, contact: fonts at gnome dot
org.
Arev Fonts Copyright
------------------------------
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining
a copy of the fonts accompanying this license ("Fonts") and
associated documentation files (the "Font Software"), to reproduce
and distribute the modifications to the Bitstream Vera Font Software,
including without limitation the rights to use, copy, merge, publish,
distribute, and/or sell copies of the Font Software, and to permit
persons to whom the Font Software is furnished to do so, subject to
the following conditions:
The above copyright and trademark notices and this permission notice
shall be included in all copies of one or more of the Font Software
typefaces.
The Font Software may be modified, altered, or added to, and in
particular the designs of glyphs or characters in the Fonts may be
modified and additional glyphs or characters may be added to the
Fonts, only if the fonts are renamed to names not containing either
the words "Tavmjong Bah" or the word "Arev".
This License becomes null and void to the extent applicable to Fonts
or Font Software that has been modified and is distributed under the
"Tavmjong Bah Arev" names.
The Font Software may be sold as part of a larger software package but
no copy of one or more of the Font Software typefaces may be sold by
itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the name of Tavmjong Bah shall not
be used in advertising or otherwise to promote the sale, use or other
dealings in this Font Software without prior written authorization
from Tavmjong Bah. For further information, contact: tavmjong @ free
. fr.

View File

@ -0,0 +1,92 @@
Specter Mono font based on Blender Mono font.
Specter Mono I18n font includes glyphs imported from the following fonts:
1. DejaVu Sans Mono
2. M+ 1M Regular
3. Wen Quan Yi Micro Hei Mono
4. Droid Sans Hebrew Regular (with some edits)
These were merged using FontForge in the above order. For each glyph,
a license of the font from which it was imported is applied.
----------------------------------------------------------------------
Summary of Copyrights and Licenses
(1) DejaVu Sans Mono
Copyright: 2003 Bitstream, Inc. (Bitstream font glyphs)
2006 Tavmjong Bah (Arev font glyphs)
DejaVu changes are in public domain
License:
DejaVu font glyphs are same as bmonofont.ttf. See LICENSE-bfont.ttf.txt.
(2) M+ 1M Regular
Copyright: 2002-2012 M+ FONTS PROJECT
License:
These fonts are free software.
Unlimited permission is granted to use, copy, and distribute it, with or
without modification, either commercially and noncommercially.
THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY.
(3) Wen Quan Yi Micro Hei Mono
Copyright: 2007 Google Corporation (Digitized data)
2008-2009 WenQuanYi Project Board of Trustees
2008-2009 mozbug and Qianqian Fang (Droid Sans Fallback extension interface)
License: Apache-2.0 or GPL-3 with font embedding exception
See Appendices A and B.
(4) Droid Sans Hebrew Regular
Copyright: 2011 Google Corporation
License: Apache-2.0
See Appendix A.
----------------------------------------------------------------------
Appendix A. Apache License Version 2.0
Licensed under the Apache License, Version 2.0 (the "License"); you
may not use this file except in compliance with the License. You may
obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied. See the License for the specific language governing
permissions and limitations under the License.
----------------------------------------------------------------------
Appendix B. GNU GPL Version 3 with font embedding exception
GPL-3:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Font embedding exception:
As a special exception, if you create a document which uses this
font, and embed this font or unaltered portions of this font into
the document, this font does not by itself cause the resulting
document to be covered by the GNU General Public License. This
exception does not however invalidate any other reasons why the
document might be covered by the GNU General Public License. If you
modify this font, you may extend this exception to your version of
the font, but you are not obligated to do so. If you do not wish to
do so, delete this exception statement from your version.

View File

@ -0,0 +1,72 @@
Specter Main font based on Blender main font with GPLv3 licensed glyphs removed.
Specter Main I18n font ("droidsans-permissive.ttf") includes glyphs imported from the following fonts:
1. DejaVu Sans
2. Droid Sans Regular
4. Droid Sans Hebrew Regular
5. Droid Sans Ethiopic Regular
6. KhmerOSsys
These were merged using FontForge in (approximately) the above order. For each glyph,
a license of the font from which it was imported is applied.
----------------------------------------------------------------------
Summary of Copyrights and Licenses
(1) DejaVu Sans
Copyright: 2003 Bitstream, Inc. (Bitstream font glyphs)
2006 Tavmjong Bah (Arev font glyphs)
DejaVu changes are in public domain
License:
DejaVu font glyphs are same as bfont.ttf. See LICENSE-bfont.ttf.txt.
(2), (4), (5) Droid Sans Fonts family
Copyright:
Copyright © 2006, 2007, 2008, 2009, 2010 Google Corp.
Droid is a trademark of Google Corp.
License: Apache-2.0
See Appendix A.
(6) KhmerOSsys
Copyright: 2005, 2006 Danh Hong
2005, 2006 Open Forum of Cambodia
License: GPL-2.1+
See Appendices B.
----------------------------------------------------------------------
Appendix A. Apache License Version 2.0
Licensed under the Apache License, Version 2.0 (the "License"); you
may not use this file except in compliance with the License. You may
obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied. See the License for the specific language governing
permissions and limitations under the License.
----------------------------------------------------------------------
Appendix B. GNU GPL Version 2.1
This font is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
On Debian systems, the complete text of the GNU Lesser General Public
License can be found in the file /usr/share/common-licenses/LGPL-2.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,6 @@
include_directories(../../hecl/include
../../hecl/extern/boo/include
../../hecl/extern/boo/logvisor/include
../../nod/logvisor/fmt/include)
add_shader(SpecterViewShaders)
add_shader(SpecterTextViewShaders)

View File

@ -0,0 +1,18 @@
#define SPECTER_GLSL_VIEW_VERT_BLOCK\
UBINDING0 uniform SpecterViewBlock\
{\
mat4 mv;\
vec4 mulColor;\
};
#define SPECTER_HLSL_VIEW_VERT_BLOCK\
cbuffer SpecterViewBlock : register(b0)\
{\
float4x4 mv;\
float4 mulColor;\
};
#define SPECTER_METAL_VIEW_VERT_BLOCK\
struct SpecterViewBlock\
{\
float4x4 mv;\
float4 mulColor;\
};

View File

@ -0,0 +1,205 @@
#include "SpecterCommon.shader"
#shader SpecterTextViewShader
#instattribute position4 0
#instattribute position4 1
#instattribute position4 2
#instattribute position4 3
#instattribute modelview 0
#instattribute modelview 1
#instattribute modelview 2
#instattribute modelview 3
#instattribute uv4 0
#instattribute uv4 1
#instattribute uv4 2
#instattribute uv4 3
#instattribute color
#srcfac srcalpha
#dstfac invsrcalpha
#primitive tristrips
#depthtest none
#depthwrite false
#culling none
#vertex glsl
layout(location=0) in vec3 posIn[4];
layout(location=4) in mat4 mvMtx;
layout(location=8) in vec3 uvIn[4];
layout(location=12) in vec4 colorIn;
SPECTER_GLSL_VIEW_VERT_BLOCK
struct VertToFrag
{
vec3 uv;
vec4 color;
};
SBINDING(0) out VertToFrag vtf;
void main()
{
vec3 pos = posIn[gl_VertexID];
vtf.uv = uvIn[gl_VertexID];
vtf.color = colorIn * mulColor;
gl_Position = mv * mvMtx * vec4(pos, 1.0);
}
#fragment glsl
TBINDING0 uniform sampler2DArray fontTex;
struct VertToFrag
{
vec3 uv;
vec4 color;
};
SBINDING(0) in VertToFrag vtf;
layout(location=0) out vec4 colorOut;
void main()
{
colorOut = vtf.color;
colorOut.a *= texture(fontTex, vtf.uv).r;
}
#vertex hlsl
struct VertData
{
float3 posIn[4] : POSITION;
float4x4 mvMtx : MODELVIEW;
float3 uvIn[4] : UV;
float4 colorIn : COLOR;
};
SPECTER_HLSL_VIEW_VERT_BLOCK
struct VertToFrag
{
float4 position : SV_Position;
float3 uv : UV;
float4 color : COLOR;
};
VertToFrag main(in VertData v, in uint vertId : SV_VertexID)
{
VertToFrag vtf;
vtf.uv = v.uvIn[vertId];
vtf.color = v.colorIn * mulColor;
vtf.position = mul(mv, mul(v.mvMtx, float4(v.posIn[vertId], 1.0)));
return vtf;
}
#fragment hlsl
Texture2DArray fontTex : register(t0);
SamplerState samp : register(s0);
struct VertToFrag
{
float4 position : SV_Position;
float3 uv : UV;
float4 color : COLOR;
};
float4 main(in VertToFrag vtf) : SV_Target0
{
float4 colorOut = vtf.color;
colorOut.a *= fontTex.Sample(samp, vtf.uv).r;
return colorOut;
}
#vertex metal
struct VertData
{
float3 posIn[4];
float4x4 mvMtx;
float3 uvIn[4];
float4 colorIn;
};
SPECTER_METAL_VIEW_VERT_BLOCK
struct VertToFrag
{
float4 position [[ position ]];
float3 uv;
float4 color;
};
vertex VertToFrag vmain(constant VertData* va [[ buffer(1) ]],
uint vertId [[ vertex_id ]], uint instId [[ instance_id ]],
constant SpecterViewBlock& view [[ buffer(2) ]])
{
VertToFrag vtf;
constant VertData& v = va[instId];
vtf.uv = v.uvIn[vertId];
vtf.color = v.colorIn * view.mulColor;
vtf.position = view.mv * v.mvMtx * float4(v.posIn[vertId], 1.0);
return vtf;
}
#fragment metal
struct VertToFrag
{
float4 position [[ position ]];
float3 uv;
float4 color;
};
fragment float4 fmain(VertToFrag vtf [[ stage_in ]],
sampler samp [[ sampler(0) ]],
texture2d_array<float> fontTex [[ texture(0) ]])
{
float4 colorOut = vtf.color;
colorOut.a *= fontTex.sample(samp, vtf.uv.xy, vtf.uv.z).r;
return colorOut;
}
#shader SpecterTextViewShaderSubpixel : SpecterTextViewShader
#srcfac srccolor1
#dstfac invsrccolor1
#overwritealpha true
#fragment glsl
TBINDING0 uniform sampler2DArray fontTex;
struct VertToFrag
{
vec3 uv;
vec4 color;
};
SBINDING(0) in VertToFrag vtf;
layout(location=0, index=0) out vec4 colorOut;
layout(location=0, index=1) out vec4 blendOut;
void main()
{
colorOut = vtf.color;
blendOut = colorOut.a * texture(fontTex, vtf.uv);
}
#fragment hlsl
Texture2DArray fontTex : register(t0);
SamplerState samp : register(s0);
struct VertToFrag
{
float4 position : SV_Position;
float3 uv : UV;
float4 color : COLOR;
};
struct BlendOut
{
float4 colorOut : SV_Target0;
float4 blendOut : SV_Target1;
};
BlendOut main(in VertToFrag vtf)
{
BlendOut ret;
ret.colorOut = vtf.color;
ret.blendOut = ret.colorOut.a * fontTex.Sample(samp, vtf.uv);
return ret;
}
#fragment metal
struct VertToFrag
{
float4 position [[ position ]];
float3 uv;
float4 color;
};
struct BlendOut
{
float4 colorOut [[ color(0), index(0) ]];
float4 blendOut [[ color(0), index(1) ]];
};
fragment BlendOut fmain(VertToFrag vtf [[ stage_in ]],
sampler samp [[ sampler(0) ]],
texture2d_array<float> fontTex [[ texture(0) ]])
{
BlendOut ret;
ret.colorOut = vtf.color;
ret.blendOut = ret.colorOut.a * fontTex.sample(samp, vtf.uv.xy, vtf.uv.z);
return ret;
}

View File

@ -0,0 +1,213 @@
#include "SpecterCommon.shader"
#shader SpecterViewShaderSolid
#attribute position4
#attribute color
#srcfac srcalpha
#dstfac invsrcalpha
#primitive tristrips
#depthtest none
#depthwrite false
#culling none
#vertex glsl
layout(location=0) in vec3 posIn;
layout(location=1) in vec4 colorIn;
SPECTER_GLSL_VIEW_VERT_BLOCK
struct VertToFrag
{
vec4 color;
};
SBINDING(0) out VertToFrag vtf;
void main()
{
vtf.color = colorIn * mulColor;
gl_Position = mv * vec4(posIn, 1.0);
}
#fragment glsl
struct VertToFrag
{
vec4 color;
};
SBINDING(0) in VertToFrag vtf;
layout(location=0) out vec4 colorOut;
void main()
{
colorOut = vtf.color;
}
#vertex hlsl
struct VertData
{
float3 posIn : POSITION;
float4 colorIn : COLOR;
};
SPECTER_HLSL_VIEW_VERT_BLOCK
struct VertToFrag
{
float4 position : SV_Position;
float4 color : COLOR;
};
VertToFrag main(in VertData v)
{
VertToFrag vtf;
vtf.color = v.colorIn * mulColor;
vtf.position = mul(mv, float4(v.posIn, 1.0));
return vtf;
}
#fragment hlsl
struct VertToFrag
{
float4 position : SV_Position;
float4 color : COLOR;
};
float4 main(in VertToFrag vtf) : SV_Target0
{
return vtf.color;
}
#vertex metal
struct VertData
{
float3 posIn [[ attribute(0) ]];
float4 colorIn [[ attribute(1) ]];
};
SPECTER_METAL_VIEW_VERT_BLOCK
struct VertToFrag
{
float4 position [[ position ]];
float4 color;
};
vertex VertToFrag vmain(VertData v [[ stage_in ]], constant SpecterViewBlock& view [[ buffer(2) ]])
{
VertToFrag vtf;
vtf.color = v.colorIn * view.mulColor;
vtf.position = view.mv * float4(v.posIn, 1.0);
return vtf;
}
#fragment metal
struct VertToFrag
{
float4 position [[ position ]];
float4 color;
};
fragment float4 fmain(VertToFrag vtf [[ stage_in ]])
{
return vtf.color;
}
#shader SpecterViewShaderTex
#attribute position4
#attribute uv4
#srcfac srcalpha
#dstfac invsrcalpha
#primitive tristrips
#depthtest none
#depthwrite false
#culling none
#vertex glsl
layout(location=0) in vec3 posIn;
layout(location=1) in vec2 uvIn;
SPECTER_GLSL_VIEW_VERT_BLOCK
struct VertToFrag
{
vec4 color;
vec4 uv;
};
SBINDING(0) out VertToFrag vtf;
void main()
{
vtf.uv.xy = uvIn;
vtf.color = mulColor;
gl_Position = mv * vec4(posIn, 1.0);
}
#fragment glsl
struct VertToFrag
{
vec4 color;
vec4 uv;
};
SBINDING(0) in VertToFrag vtf;
TBINDING0 uniform sampler2D tex;
layout(location=0) out vec4 colorOut;
void main()
{
colorOut = texture(tex, vtf.uv.xy) * vtf.color;
}
#vertex hlsl
struct VertData
{
float3 posIn : POSITION;
float2 uvIn : UV;
};
SPECTER_HLSL_VIEW_VERT_BLOCK
struct VertToFrag
{
float4 position : SV_Position;
float4 color : COLOR;
float2 uv : UV;
};
VertToFrag main(in VertData v)
{
VertToFrag vtf;
vtf.uv = v.uvIn;
vtf.color = mulColor;
vtf.position = mul(mv, float4(v.posIn, 1.0));
return vtf;
}
#fragment hlsl
struct VertToFrag
{
float4 position : SV_Position;
float4 color : COLOR;
float2 uv : UV;
};
Texture2D tex : register(t0);
SamplerState samp : register(s0);
float4 main(in VertToFrag vtf) : SV_Target0
{
return tex.Sample(samp, vtf.uv) * vtf.color;
}
#vertex metal
struct VertData
{
float3 posIn [[ attribute(0) ]];
float2 uvIn [[ attribute(1) ]];
};
SPECTER_METAL_VIEW_VERT_BLOCK
struct VertToFrag
{
float4 position [[ position ]];
float4 color;
float2 uv;
};
vertex VertToFrag vmain(VertData v [[ stage_in ]], constant SpecterViewBlock& view [[ buffer(2) ]])
{
VertToFrag vtf;
vtf.uv = v.uvIn;
vtf.color = view.mulColor;
vtf.position = view.mv * float4(v.posIn, 1.0);
return vtf;
}
#fragment metal
struct VertToFrag
{
float4 position [[ position ]];
float4 color;
float2 uv;
};
fragment float4 fmain(VertToFrag vtf [[ stage_in ]],
sampler samp [[ sampler(0) ]],
texture2d<float> tex [[ texture(0) ]])
{
return tex.sample(samp, vtf.uv) * vtf.color;
}

1
specter/zeus Submodule

@ -0,0 +1 @@
Subproject commit b3806c03a5e7d5cde5d10f04b18e8fb25a751b59