diff --git a/specter/CMakeLists.txt b/specter/CMakeLists.txt index cf2b564d0..9604dd28a 100644 --- a/specter/CMakeLists.txt +++ b/specter/CMakeLists.txt @@ -34,7 +34,9 @@ list(APPEND SPECTER_HEADERS include/Specter/ViewSystem.hpp include/Specter/View.hpp include/Specter/RootView.hpp + include/Specter/ScrollView.hpp include/Specter/TextView.hpp + include/Specter/MultiLineTextView.hpp include/Specter/Space.hpp include/Specter/Table.hpp include/Specter/Outliner.hpp @@ -54,7 +56,9 @@ list(APPEND SPECTER_SOURCES lib/Specter.cpp lib/View.cpp lib/RootView.cpp + lib/ScrollView.cpp lib/TextView.cpp + lib/MultiLineTextView.cpp lib/Space.cpp lib/Table.cpp lib/Outliner.cpp diff --git a/specter/MathLib b/specter/MathLib index f075c38a4..9885d3442 160000 --- a/specter/MathLib +++ b/specter/MathLib @@ -1 +1 @@ -Subproject commit f075c38a4cb65ce4d1e812a308de45b41cb88eb8 +Subproject commit 9885d34420ec70ba52d8270b4e0a9ddc22663dea diff --git a/specter/include/Specter/FontCache.hpp b/specter/include/Specter/FontCache.hpp index 4ba78b5d3..797ac9698 100644 --- a/specter/include/Specter/FontCache.hpp +++ b/specter/include/Specter/FontCache.hpp @@ -62,6 +62,8 @@ class FontAtlas uint32_t m_dpi; FT_Fixed m_ftXscale; FT_UShort m_ftXPpem; + FT_Pos m_lineHeight; + bool m_subpixel; public: struct Glyph @@ -120,7 +122,9 @@ public: 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;} boo::ITexture* texture() const {return m_tex;} + bool subpixel() const {return m_subpixel;} const Glyph* lookupGlyph(atUint32 charcode) const { diff --git a/specter/include/Specter/MultiLineTextView.hpp b/specter/include/Specter/MultiLineTextView.hpp new file mode 100644 index 000000000..7cb886a56 --- /dev/null +++ b/specter/include/Specter/MultiLineTextView.hpp @@ -0,0 +1,33 @@ +#ifndef SPECTER_MULTILINETEXTVIEW_HPP +#define SPECTER_MULTILINETEXTVIEW_HPP + +#include "View.hpp" +#include "TextView.hpp" +#include "FontCache.hpp" + +namespace Specter +{ + +class MultiLineTextView : public View +{ + ViewSystem& m_viewSystem; + std::vector m_lines; + const FontAtlas& m_fontAtlas; + size_t m_lineCapacity; + float m_lineHeight; +public: + MultiLineTextView(ViewSystem& system, View& parentView, const FontAtlas& font, size_t lineCapacity=256, float lineHeight=1.0); + MultiLineTextView(ViewSystem& system, View& parentView, FontTag font, size_t lineCapacity=256, float lineHeight=1.0); + + void typesetGlyphs(const std::string& str, + const Zeus::CColor& defaultColor=Zeus::CColor::skWhite); + void typesetGlyphs(const std::wstring& str, + const Zeus::CColor& defaultColor=Zeus::CColor::skWhite); + + void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub); + void draw(boo::IGraphicsCommandQueue* gfxQ); +}; + +} + +#endif // SPECTER_MULTILINETEXTVIEW_HPP diff --git a/specter/include/Specter/RootView.hpp b/specter/include/Specter/RootView.hpp index fd185a079..0c300dfc6 100644 --- a/specter/include/Specter/RootView.hpp +++ b/specter/include/Specter/RootView.hpp @@ -2,7 +2,7 @@ #define SPECTER_ROOTVIEW_HPP #include "View.hpp" -#include "TextView.hpp" +#include "MultiLineTextView.hpp" #include "FontCache.hpp" #include @@ -14,7 +14,7 @@ class RootView : public View, public boo::IWindowCallback { boo::IWindow* m_window = nullptr; boo::ITextureR* m_renderTex = nullptr; - TextView m_textView; + MultiLineTextView m_textView; boo::SWindowRect m_rootRect; bool m_resizeRTDirty = false; bool m_destroyed = false; @@ -46,6 +46,8 @@ public: void draw(boo::IGraphicsCommandQueue* gfxQ); RootView(ViewSystem& system, boo::IWindow* window); + + const boo::SWindowRect& rootRect() const {return m_rootRect;} }; } diff --git a/specter/include/Specter/ScrollView.hpp b/specter/include/Specter/ScrollView.hpp new file mode 100644 index 000000000..92876a00f --- /dev/null +++ b/specter/include/Specter/ScrollView.hpp @@ -0,0 +1,19 @@ +#ifndef SPECTER_SCROLLVIEW_HPP +#define SPECTER_SCROLLVIEW_HPP + +#include "View.hpp" + +namespace Specter +{ +class ViewSystem; + +class ScrollView : public View +{ + View& m_contentView; +public: + ScrollView(ViewSystem& system, View& parentView, View& contentView); +}; + +} + +#endif // SPECTER_SCROLLVIEW_HPP diff --git a/specter/include/Specter/TextView.hpp b/specter/include/Specter/TextView.hpp index b58a7ce8e..1fe27f9ba 100644 --- a/specter/include/Specter/TextView.hpp +++ b/specter/include/Specter/TextView.hpp @@ -26,6 +26,7 @@ public: { friend class ViewSystem; friend class TextView; + friend class MultiLineTextView; FontCache* m_fcache = nullptr; boo::IShaderPipeline* m_regular = nullptr; boo::IShaderPipeline* m_subpixel = nullptr; @@ -39,7 +40,8 @@ public: #endif }; - TextView(ViewSystem& system, FontTag font, size_t capacity=256); + TextView(ViewSystem& system, View& parentView, const FontAtlas& font, size_t capacity=256); + TextView(ViewSystem& system, View& parentView, FontTag font, size_t capacity=256); struct RenderGlyph { diff --git a/specter/include/Specter/View.hpp b/specter/include/Specter/View.hpp index d0bede7b7..7162b4a36 100644 --- a/specter/include/Specter/View.hpp +++ b/specter/include/Specter/View.hpp @@ -14,9 +14,13 @@ namespace Specter { class ViewSystem; +class RootView; class View { + RootView& m_rootView; + View& m_parentView; + boo::SWindowRect m_subRect; boo::IGraphicsBufferD* m_bgVertBuf; boo::IGraphicsBufferD* m_bgInstBuf; boo::IVertexFormat* m_bgVtxFmt = nullptr; /* OpenGL only */ @@ -25,6 +29,10 @@ class View Zeus::CColor m_bgColor; int m_bgValidSlots = 0; + friend class RootView; + void buildResources(ViewSystem& system); + View(ViewSystem& system, RootView& parentView); + protected: struct VertexBlock { @@ -71,10 +79,15 @@ public: }; protected: - View(ViewSystem& system); + View(ViewSystem& system, View& parentView); public: View() = delete; + + View& parent() {return m_parentView;} + RootView& root() {return m_rootView;} + void updateSize(); + void setBackground(Zeus::CColor color) {m_bgColor = color; m_bgValidSlots = 0;} virtual void resized(const boo::SWindowRect &root, const boo::SWindowRect& sub); virtual void draw(boo::IGraphicsCommandQueue* gfxQ); diff --git a/specter/lib/FontCache.cpp b/specter/lib/FontCache.cpp index fcf8c39ef..f1cf52ae2 100644 --- a/specter/lib/FontCache.cpp +++ b/specter/lib/FontCache.cpp @@ -221,7 +221,9 @@ FontAtlas::FontAtlas(boo::IGraphicsDataFactory* gf, 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_ftXPpem(face->size->metrics.x_ppem), + m_lineHeight(face->size->metrics.height), + m_subpixel(subpixel) { FT_Int32 baseFlags = FT_LOAD_NO_BITMAP; if (subpixel) @@ -433,7 +435,9 @@ FontAtlas::FontAtlas(boo::IGraphicsDataFactory* gf, 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_ftXPpem(face->size->metrics.x_ppem), + m_lineHeight(face->size->metrics.height), + m_subpixel(subpixel) { FT_Int32 baseFlags = FT_LOAD_NO_BITMAP; if (subpixel) diff --git a/specter/lib/MultiLineTextView.cpp b/specter/lib/MultiLineTextView.cpp new file mode 100644 index 000000000..3601de7b1 --- /dev/null +++ b/specter/lib/MultiLineTextView.cpp @@ -0,0 +1,130 @@ +#include "Specter/MultiLineTextView.hpp" +#include "Specter/ViewSystem.hpp" + +namespace Specter +{ +static LogVisor::LogModule Log("Specter::MultiLineTextView"); + +MultiLineTextView::MultiLineTextView(ViewSystem& system, + View& parentView, + const FontAtlas& font, + size_t lineCapacity, + float lineHeight) +: View(system, parentView), + m_viewSystem(system), + m_fontAtlas(font), + m_lineCapacity(lineCapacity), + m_lineHeight(lineHeight) {} + +MultiLineTextView::MultiLineTextView(ViewSystem& system, + View& parentView, + FontTag font, + size_t lineCapacity, + float lineHeight) +: MultiLineTextView(system, + parentView, + system.m_textSystem.m_fcache->lookupAtlas(font), + lineCapacity, + lineHeight) {} + +void MultiLineTextView::typesetGlyphs(const std::string& str, + const Zeus::CColor& defaultColor) +{ + m_lines.clear(); + size_t rem = str.size() + 1; + const utf8proc_uint8_t* it = reinterpret_cast(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::FatalError, "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(str.data()); + const utf8proc_uint8_t* beginIt = it; + + while (rem) + { + utf8proc_int32_t ch; + utf8proc_ssize_t sz = utf8proc_iterate(it, -1, &ch); + if (ch == '\n' || ch == '\0') + { + m_lines.emplace_back(m_viewSystem, *this, m_fontAtlas, m_lineCapacity); + m_lines.back().typesetGlyphs(std::string((char*)beginIt, it - beginIt), defaultColor); + beginIt = it + 1; + } + rem -= sz; + it += sz; + } + + updateSize(); +} + +void MultiLineTextView::typesetGlyphs(const std::wstring& str, + const Zeus::CColor& defaultColor) +{ + m_lines.clear(); + 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; + + while (rem) + { + if (*it == L'\n' || *it == L'\0') + { + m_lines.emplace_back(m_viewSystem, *this, m_fontAtlas, m_lineCapacity); + m_lines.back().typesetGlyphs(std::wstring(beginIt, it), defaultColor); + beginIt = it + 1; + } + --rem; + ++it; + } + + updateSize(); +} + +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 (TextView& tv : m_lines) + { + tsub.location[1] -= lHeight; + tv.resized(root, tsub); + } +} + +void MultiLineTextView::draw(boo::IGraphicsCommandQueue* gfxQ) +{ + View::draw(gfxQ); + for (TextView& tv : m_lines) + tv.draw(gfxQ); +} + +} diff --git a/specter/lib/RootView.cpp b/specter/lib/RootView.cpp index c9809bdfc..14b9cfeae 100644 --- a/specter/lib/RootView.cpp +++ b/specter/lib/RootView.cpp @@ -5,17 +5,17 @@ namespace Specter { RootView::RootView(ViewSystem& system, boo::IWindow* window) -: View(system), m_window(window), m_textView(system, system.m_mainFont) +: View(system, *this), m_window(window), m_textView(system, *this, system.m_mainFont) { window->setCallback(this); boo::SWindowRect rect = window->getWindowFrame(); m_renderTex = system.m_factory->newRenderTexture(rect.size[0], rect.size[1], 1); system.m_factory->commit(); resized(rect); - m_textView.typesetGlyphs("Hello, World! — こんにちは世界!", Zeus::CColor::skGreen); + m_textView.typesetGlyphs("Hello, World!\nこんにちは世界!\n\n", Zeus::CColor::skWhite); Zeus::CColor transBlack(0.f, 0.f, 0.f, 0.5f); m_textView.setBackground(transBlack); - setBackground(Zeus::CColor::skBlue); + setBackground(Zeus::CColor::skGrey); } void RootView::destroyed() @@ -34,14 +34,16 @@ void RootView::resized(const boo::SWindowRect& root, const boo::SWindowRect&) m_rootRect.location[0] = 0; m_rootRect.location[1] = 0; View::resized(m_rootRect, m_rootRect); + boo::SWindowRect textRect = m_rootRect; textRect.location[0] = 10; textRect.location[1] = 10; textRect.size[0] -= 20; if (textRect.size[0] < 0) textRect.size[0] = 0; - textRect.size[1] = 10; + textRect.size[1] = 100; m_textView.resized(m_rootRect, textRect); + m_resizeRTDirty = true; } diff --git a/specter/lib/ScrollView.cpp b/specter/lib/ScrollView.cpp new file mode 100644 index 000000000..68a162344 --- /dev/null +++ b/specter/lib/ScrollView.cpp @@ -0,0 +1,11 @@ +#include "Specter/ScrollView.hpp" + +namespace Specter +{ + +ScrollView::ScrollView(ViewSystem& system, View& parentView, View& contentView) +: View(system, parentView), m_contentView(contentView) +{ +} + +} diff --git a/specter/lib/TextView.cpp b/specter/lib/TextView.cpp index 292c3c3dc..5d59a3e54 100644 --- a/specter/lib/TextView.cpp +++ b/specter/lib/TextView.cpp @@ -259,15 +259,21 @@ void TextView::System::init(boo::MetalDataFactory* factory, FontCache* fcache) #endif -TextView::TextView(ViewSystem& system, FontTag font, size_t capacity) -: View(system), +TextView::TextView(ViewSystem& system, View& parentView, const FontAtlas& font, size_t capacity) +: View(system, parentView), m_capacity(capacity), - m_fontAtlas(system.m_textSystem.m_fcache->lookupAtlas(font)) + m_fontAtlas(font) { m_glyphBuf = system.m_factory->newDynamicBuffer(boo::BufferUse::Vertex, sizeof(RenderGlyph), capacity); + boo::IShaderPipeline* shader; + if (font.subpixel()) + shader = system.m_textSystem.m_subpixel; + else + shader = system.m_textSystem.m_regular; + if (!system.m_textSystem.m_vtxFmt) { boo::VertexElementDescriptor vdescs[] = @@ -288,7 +294,7 @@ TextView::TextView(ViewSystem& system, FontTag font, size_t capacity) }; m_vtxFmt = system.m_factory->newVertexFormat(13, vdescs); boo::ITexture* texs[] = {m_fontAtlas.texture()}; - m_shaderBinding = system.m_factory->newShaderDataBinding(system.m_textSystem.m_regular, m_vtxFmt, + m_shaderBinding = system.m_factory->newShaderDataBinding(shader, m_vtxFmt, nullptr, m_glyphBuf, nullptr, 1, (boo::IGraphicsBuffer**)&m_viewVertBlockBuf, 1, texs); @@ -296,7 +302,7 @@ TextView::TextView(ViewSystem& system, FontTag font, size_t capacity) else { boo::ITexture* texs[] = {m_fontAtlas.texture()}; - m_shaderBinding = system.m_factory->newShaderDataBinding(system.m_textSystem.m_regular, system.m_textSystem.m_vtxFmt, + m_shaderBinding = system.m_factory->newShaderDataBinding(shader, system.m_textSystem.m_vtxFmt, nullptr, m_glyphBuf, nullptr, 1, (boo::IGraphicsBuffer**)&m_viewVertBlockBuf, 1, texs); @@ -305,6 +311,9 @@ TextView::TextView(ViewSystem& system, FontTag font, size_t capacity) m_glyphs.reserve(capacity); } +TextView::TextView(ViewSystem& system, View& parentView, FontTag font, size_t capacity) +: TextView(system, parentView, system.m_textSystem.m_fcache->lookupAtlas(font), 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); diff --git a/specter/lib/View.cpp b/specter/lib/View.cpp index 5dcdd4de7..e428441c6 100644 --- a/specter/lib/View.cpp +++ b/specter/lib/View.cpp @@ -1,5 +1,6 @@ #include "Specter/View.hpp" #include "Specter/ViewSystem.hpp" +#include "Specter/RootView.hpp" namespace Specter { @@ -145,8 +146,10 @@ void View::System::init(boo::MetalDataFactory* factory) #endif -View::View(ViewSystem& system) +void View::buildResources(ViewSystem& system) { + m_bgColor = Zeus::CColor::skClear; + m_bgVertBuf = system.m_factory->newDynamicBuffer(boo::BufferUse::Vertex, sizeof(Zeus::CVector3f), 4); @@ -181,11 +184,30 @@ View::View(ViewSystem& system) (boo::IGraphicsBuffer**)&m_viewVertBlockBuf, 0, nullptr); } +} +View::View(ViewSystem& system, RootView& rootView) +: m_rootView(rootView), + m_parentView(rootView) +{ + buildResources(system); +} + +View::View(ViewSystem& system, View& parentView) +: m_rootView(parentView.root()), + m_parentView(parentView) +{ + buildResources(system); +} + +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].assign(0.f, sub.size[1], 0.f); m_bgRect[1].assign(0.f, 0.f, 0.f);