mirror of https://github.com/AxioDL/metaforce.git
Merge submodule contents for specter/master
This commit is contained in:
commit
7469f6aed7
|
@ -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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
docs
|
|
@ -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)
|
|
@ -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.
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1 @@
|
|||
Subproject commit 6f049e5c977aaae9d206c33cd26fd847eca34ea4
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
#pragma once
|
|
@ -0,0 +1 @@
|
|||
#pragma once
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
#pragma once
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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"
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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.
|
||||
|
|
@ -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.
|
|
@ -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.
|
@ -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)
|
|
@ -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;\
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
Subproject commit b3806c03a5e7d5cde5d10f04b18e8fb25a751b59
|
Loading…
Reference in New Issue