diff --git a/specter/include/Specter/FileBrowser.hpp b/specter/include/Specter/FileBrowser.hpp index 0eae3fde6..8f929e352 100644 --- a/specter/include/Specter/FileBrowser.hpp +++ b/specter/include/Specter/FileBrowser.hpp @@ -60,7 +60,13 @@ public: void mouseMove(const boo::SWindowCoord&); void mouseEnter(const boo::SWindowCoord&); void mouseLeave(const boo::SWindowCoord&); + void scroll(const boo::SWindowCoord&, const boo::SScrollDelta&); + void touchDown(const boo::STouchCoord&, uintptr_t); + void touchUp(const boo::STouchCoord&, uintptr_t); + void touchMove(const boo::STouchCoord&, uintptr_t); + void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub); + void think(); void draw(boo::IGraphicsCommandQueue* gfxQ); }; diff --git a/specter/include/Specter/ModalWindow.hpp b/specter/include/Specter/ModalWindow.hpp index 42c0851c6..cddb582f0 100644 --- a/specter/include/Specter/ModalWindow.hpp +++ b/specter/include/Specter/ModalWindow.hpp @@ -23,8 +23,7 @@ class ModalWindow : public View int m_width = 0; int m_height = 0; - int m_widthConstrain; - int m_heightConstrain; + RectangleConstraint m_constraint; Zeus::CColor m_windowBg; Zeus::CColor m_windowBgClear; @@ -57,7 +56,7 @@ protected: virtual void updateContentOpacity(float opacity) {} public: - ModalWindow(ViewResources& res, View& parentView, int widthConstrain=-1, int heightConstrain=-1); + ModalWindow(ViewResources& res, View& parentView, const RectangleConstraint& constraint); void think(); bool skipBuildInAnimation(); diff --git a/specter/include/Specter/RootView.hpp b/specter/include/Specter/RootView.hpp index 4a963150b..de3f53f98 100644 --- a/specter/include/Specter/RootView.hpp +++ b/specter/include/Specter/RootView.hpp @@ -4,6 +4,7 @@ #include "View.hpp" #include "ViewResources.hpp" #include "MultiLineTextView.hpp" +#include "TextField.hpp" #include "SplitView.hpp" #include "Tooltip.hpp" #include "FontCache.hpp" @@ -26,6 +27,7 @@ class RootView : public View bool m_destroyed = false; IViewManager& m_viewMan; ViewResources* m_viewRes; + View* m_activeTextView = nullptr; DeferredWindowEvents m_events; @@ -64,6 +66,13 @@ public: const ThemeData& themeData() const {return m_viewRes->m_theme;} View* setContentView(View* view); + void setActiveTextView(View* textView) + { + if (m_activeTextView) + m_activeTextView->setActive(false); + m_activeTextView = textView; + textView->setActive(true); + } void resetTooltip(ViewResources& res); void displayTooltip(const std::string& name, const std::string& help); diff --git a/specter/include/Specter/TextField.hpp b/specter/include/Specter/TextField.hpp index f7cb7c194..a4745ed6a 100644 --- a/specter/include/Specter/TextField.hpp +++ b/specter/include/Specter/TextField.hpp @@ -12,7 +12,7 @@ class TextField : public Control std::string m_textStr; std::unique_ptr m_text; - SolidShaderVert m_verts[28]; + SolidShaderVert m_verts[32]; boo::IGraphicsBufferD* m_bVertsBuf = nullptr; boo::IVertexFormat* m_bVtxFmt = nullptr; /* OpenGL only */ boo::IShaderDataBinding* m_bShaderBinding = nullptr; @@ -20,6 +20,13 @@ class TextField : public Control int m_nomWidth = 0; int m_nomHeight = 0; + size_t m_selectionStart = 0; + size_t m_selectionCount = 0; + size_t m_cursorPos = 0; + size_t m_cursorFrames = 0; + + bool m_active = false; + void setInactive(); void setHover(); void setDisabled(); @@ -27,19 +34,29 @@ class TextField : public Control public: TextField(ViewResources& res, View& parentView, IStringBinding* strBind); + const std::string& getText() const {return m_textStr;} void setText(const std::string& str); void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey); void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey); void mouseMove(const boo::SWindowCoord&); void mouseEnter(const boo::SWindowCoord&); - void mouseLeave(const boo::SWindowCoord&coord); + void mouseLeave(const boo::SWindowCoord&); + void charKeyDown(unsigned long, boo::EModifierKey, bool); + void specialKeyDown(boo::ESpecialKey, boo::EModifierKey, bool); + void think(); void resized(const boo::SWindowRect& rootView, const boo::SWindowRect& sub); void draw(boo::IGraphicsCommandQueue* gfxQ); int nominalWidth() const {return m_nomWidth;} int nominalHeight() const {return m_nomHeight;} + void setActive(bool active); + void setCursorPos(size_t pos); + + void setSelectionRange(size_t start, size_t count); + void clearSelectionRange(); + void setMultiplyColor(const Zeus::CColor& color) { View::setMultiplyColor(color); diff --git a/specter/include/Specter/TextView.hpp b/specter/include/Specter/TextView.hpp index dd9eee799..4464fd69c 100644 --- a/specter/include/Specter/TextView.hpp +++ b/specter/include/Specter/TextView.hpp @@ -67,6 +67,7 @@ public: RenderGlyph(int& adv, const FontAtlas::Glyph& glyph, const Zeus::CColor& defaultColor); }; std::vector& accessGlyphs() {return m_glyphs;} + const std::vector& accessGlyphs() const {return m_glyphs;} void updateGlyphs() {m_valid = false;} void typesetGlyphs(const std::string& str, @@ -85,10 +86,13 @@ public: int nominalHeight() const {return m_fontAtlas.FT_LineHeight() >> 6;} std::pair queryGlyphDimensions(size_t pos) const; + size_t reverseSelectGlyph(int x) const; + int queryReverseAdvance(size_t idx) const; private: std::vector m_glyphs; std::vector> m_glyphDims; + std::vector m_glyphAdvs; }; } diff --git a/specter/include/Specter/View.hpp b/specter/include/Specter/View.hpp index f8b90ef63..affbedd50 100644 --- a/specter/include/Specter/View.hpp +++ b/specter/include/Specter/View.hpp @@ -18,6 +18,65 @@ class ThemeData; class ViewResources; class RootView; +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 solve(int x, int y) const + { + std::pair 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: @@ -127,13 +186,26 @@ public: virtual int nominalWidth() const {return 0;} virtual int nominalHeight() const {return 0;} - virtual void updateCVar(HECL::CVar* cvar) {} + 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 resized(const boo::SWindowRect& root, const boo::SWindowRect& sub); + virtual void think() {} virtual void draw(boo::IGraphicsCommandQueue* gfxQ); }; diff --git a/specter/include/Specter/ViewResources.hpp b/specter/include/Specter/ViewResources.hpp index ce85eefd7..7edfc301f 100644 --- a/specter/include/Specter/ViewResources.hpp +++ b/specter/include/Specter/ViewResources.hpp @@ -14,6 +14,7 @@ class ThemeData { Zeus::CColor m_uiText = Zeus::CColor::skWhite; Zeus::CColor m_fieldText = Zeus::CColor::skBlack; + Zeus::CColor m_selectedFieldText = Zeus::CColor::skWhite; Zeus::CColor m_vpBg = {0.2, 0.2, 0.2, 1.0}; Zeus::CColor m_tbBg = {0.4, 0.4, 0.4, 1.0}; @@ -38,10 +39,12 @@ class ThemeData 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}; public: virtual const Zeus::CColor& uiText() const {return m_uiText;} virtual const Zeus::CColor& fieldText() const {return m_fieldText;} + virtual const Zeus::CColor& selectedFieldText() const {return m_selectedFieldText;} virtual const Zeus::CColor& viewportBackground() const {return m_vpBg;} virtual const Zeus::CColor& toolbarBackground() const {return m_tbBg;} @@ -66,6 +69,7 @@ public: virtual const Zeus::CColor& textfield2Hover() const {return m_textfield2Hover;} virtual const Zeus::CColor& textfield1Disabled() const {return m_textfield1Disabled;} virtual const Zeus::CColor& textfield2Disabled() const {return m_textfield2Disabled;} + virtual const Zeus::CColor& textfieldSelection() const {return m_textfieldSelection;} }; class ViewResources diff --git a/specter/lib/FileBrowser.cpp b/specter/lib/FileBrowser.cpp index 1e777201e..87e343b64 100644 --- a/specter/lib/FileBrowser.cpp +++ b/specter/lib/FileBrowser.cpp @@ -5,8 +5,8 @@ namespace Specter { #define BROWSER_MARGIN 30 -#define BROWSER_MIN_WIDTH 100 -#define BROWSER_MIN_HEIGHT 100 +#define BROWSER_MIN_WIDTH 600 +#define BROWSER_MIN_HEIGHT 300 static std::vector PathComponents(const HECL::SystemString& path) { @@ -40,7 +40,10 @@ static std::vector PathComponents(const HECL::SystemString& } FileBrowser::FileBrowser(ViewResources& res, View& parentView, const HECL::SystemString& initialPath) -: ModalWindow(res, parentView), +: ModalWindow(res, parentView, RectangleConstraint(BROWSER_MIN_WIDTH * res.pixelFactor(), + BROWSER_MIN_HEIGHT * res.pixelFactor(), + RectangleConstraint::Test::Minimum, + RectangleConstraint::Test::Minimum)), m_comps(PathComponents(initialPath)), m_fileFieldBind(*this) { @@ -102,20 +105,34 @@ void FileBrowser::mouseLeave(const boo::SWindowCoord& coord) m_fileField.mouseLeave(coord); } +void FileBrowser::scroll(const boo::SWindowCoord& coord, const boo::SScrollDelta& 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::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) { ModalWindow::resized(root, root); float pf = rootView().viewRes().pixelFactor(); - boo::SWindowRect centerRect = sub; - centerRect.size[0] = std::max(root.size[0] - BROWSER_MARGIN * 2 * pf, BROWSER_MIN_WIDTH * pf); - centerRect.size[1] = std::max(root.size[1] - BROWSER_MARGIN * 2 * pf, BROWSER_MIN_HEIGHT * pf); + boo::SWindowRect centerRect = subRect(); centerRect.location[0] = root.size[0] / 2 - (centerRect.size[0] / 2.0); centerRect.location[1] = root.size[1] / 2 - (centerRect.size[1] / 2.0); boo::SWindowRect pathRect = centerRect; - pathRect.location[0] += 10 * pf; - pathRect.location[1] += pathRect.size[1] - 20 * pf; + pathRect.location[0] += 25 * pf; + pathRect.location[1] += pathRect.size[1] - 50 * pf; for (PathButton& b : m_pathButtons) { pathRect.size[0] = b.m_button.m_view->nominalWidth(); @@ -124,13 +141,19 @@ void FileBrowser::resized(const boo::SWindowRect& root, const boo::SWindowRect& pathRect.location[0] += pathRect.size[0] + 2; } - pathRect.location[0] = centerRect.location[0] + 10 * pf; + pathRect.location[0] = centerRect.location[0] + 25 * pf; pathRect.location[1] -= 25 * pf; - pathRect.size[0] = centerRect.size[0] - 20 * pf; + pathRect.size[0] = centerRect.size[0] - 50 * pf; pathRect.size[1] = m_fileField.m_view->nominalHeight(); m_fileField.m_view->resized(root, pathRect); } +void FileBrowser::think() +{ + ModalWindow::think(); + m_fileField.m_view->think(); +} + void FileBrowser::draw(boo::IGraphicsCommandQueue* gfxQ) { ModalWindow::draw(gfxQ); diff --git a/specter/lib/ModalWindow.cpp b/specter/lib/ModalWindow.cpp index c7a67c98e..f516d6cec 100644 --- a/specter/lib/ModalWindow.cpp +++ b/specter/lib/ModalWindow.cpp @@ -168,10 +168,9 @@ void ModalWindow::setFillColors(float t) m_cornersFilled[i]->colorGlyphs(color); } -ModalWindow::ModalWindow(ViewResources& res, View& parentView, int widthConstrain, int heightConstrain) +ModalWindow::ModalWindow(ViewResources& res, View& parentView, const RectangleConstraint& constraint) : View(res, parentView), - m_widthConstrain(widthConstrain), - m_heightConstrain(heightConstrain), + m_constraint(constraint), m_windowBg(res.themeData().splashBackground()), m_windowBgClear(m_windowBg), m_line1(res.themeData().splash1()), @@ -302,10 +301,10 @@ void ModalWindow::resized(const boo::SWindowRect& root, const boo::SWindowRect& float pf = rootView().viewRes().pixelFactor(); boo::SWindowRect centerRect = sub; - m_width = m_widthConstrain < 0 ? root.size[0] - CONTENT_MARGIN * pf * 2 : m_widthConstrain; - m_height = m_heightConstrain < 0 ? root.size[1] - CONTENT_MARGIN * pf * 2 : m_heightConstrain; - m_width = std::max(m_width, int(WINDOW_MIN_DIM * pf)); - m_height = std::max(m_height, int(WINDOW_MIN_DIM * pf)); + std::pair 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; diff --git a/specter/lib/RootView.cpp b/specter/lib/RootView.cpp index 53fe3fb6e..c0bbefeb0 100644 --- a/specter/lib/RootView.cpp +++ b/specter/lib/RootView.cpp @@ -80,44 +80,79 @@ void RootView::mouseLeave(const boo::SWindowCoord& coord) void RootView::scroll(const boo::SWindowCoord& coord, const boo::SScrollDelta& scroll) { + if (m_view) + m_view->scroll(coord, scroll); } void RootView::touchDown(const boo::STouchCoord& coord, uintptr_t tid) { + if (m_view) + m_view->touchDown(coord, tid); } void RootView::touchUp(const boo::STouchCoord& coord, uintptr_t tid) { + if (m_view) + m_view->touchUp(coord, tid); } void RootView::touchMove(const boo::STouchCoord& coord, uintptr_t tid) { + if (m_view) + m_view->touchMove(coord, tid); } void RootView::charKeyDown(unsigned long charCode, boo::EModifierKey mods, bool isRepeat) { + if (m_view) + m_view->charKeyDown(charCode, mods, isRepeat); + if (m_activeTextView) + m_activeTextView->charKeyDown(charCode, mods, isRepeat); } void RootView::charKeyUp(unsigned long charCode, boo::EModifierKey mods) { + if (m_view) + m_view->charKeyUp(charCode, mods); + if (m_activeTextView) + m_activeTextView->charKeyUp(charCode, mods); } void RootView::specialKeyDown(boo::ESpecialKey key, boo::EModifierKey mods, bool isRepeat) { if (key == boo::ESpecialKey::Enter && (mods & boo::EModifierKey::Alt) != boo::EModifierKey::None) + { m_window->setFullscreen(!m_window->isFullscreen()); + return; + } + if (m_view) + m_view->specialKeyDown(key, mods, isRepeat); + if (m_activeTextView) + m_activeTextView->specialKeyDown(key, mods, isRepeat); } void RootView::specialKeyUp(boo::ESpecialKey key, boo::EModifierKey mods) { + if (m_view) + m_view->specialKeyUp(key, mods); + if (m_activeTextView) + m_activeTextView->specialKeyUp(key, mods); } void RootView::modKeyDown(boo::EModifierKey mod, bool isRepeat) { + if (m_view) + m_view->modKeyDown(mod, isRepeat); + if (m_activeTextView) + m_activeTextView->modKeyDown(mod, isRepeat); } void RootView::modKeyUp(boo::EModifierKey mod) { + if (m_view) + m_view->modKeyUp(mod); + if (m_activeTextView) + m_activeTextView->modKeyUp(mod); } View* RootView::setContentView(View* view) diff --git a/specter/lib/TextField.cpp b/specter/lib/TextField.cpp index 7bb8ae1a5..8a97cdac3 100644 --- a/specter/lib/TextField.cpp +++ b/specter/lib/TextField.cpp @@ -8,7 +8,7 @@ namespace Specter TextField::TextField(ViewResources& res, View& parentView, IStringBinding* strBind) : Control(res, parentView, strBind) { - m_bVertsBuf = res.m_factory->newDynamicBuffer(boo::BufferUse::Vertex, sizeof(SolidShaderVert), 28); + m_bVertsBuf = res.m_factory->newDynamicBuffer(boo::BufferUse::Vertex, sizeof(SolidShaderVert), 32); if (!res.m_viewRes.m_texVtxFmt) { @@ -40,7 +40,9 @@ TextField::TextField(ViewResources& res, View& parentView, IStringBinding* strBi m_verts[4].m_color = rootView().themeData().textfield2Inactive(); for (int i=5 ; i<28 ; ++i) m_verts[i].m_color = res.themeData().textfield2Inactive(); - m_bVertsBuf->load(m_verts, sizeof(SolidShaderVert) * 28); + for (int i=28 ; i<32 ; ++i) + m_verts[i].m_color = res.themeData().textfieldSelection(); + m_bVertsBuf->load(m_verts, sizeof(m_verts)); m_text.reset(new TextView(res, *this, res.m_mainFont, TextView::Alignment::Left, 1024)); setText("Test"); @@ -48,8 +50,13 @@ TextField::TextField(ViewResources& res, View& parentView, IStringBinding* strBi void TextField::setText(const std::string& str) { - m_textStr = str; - m_text->typesetGlyphs(str, rootView().themeData().fieldText()); + clearSelectionRange(); + auto it = str.cbegin(); + for (; it != str.cend() ; ++it) + if (*it == '\n') + break; + m_textStr.assign(str.cbegin(), it); + m_text->typesetGlyphs(m_textStr, rootView().themeData().fieldText()); } void TextField::setInactive() @@ -59,7 +66,7 @@ void TextField::setInactive() m_verts[2].m_color = rootView().themeData().textfield1Inactive(); m_verts[3].m_color = rootView().themeData().textfield2Inactive(); m_verts[4].m_color = rootView().themeData().textfield2Inactive(); - m_bVertsBuf->load(m_verts, sizeof(SolidShaderVert) * 28); + m_bVertsBuf->load(m_verts, sizeof(m_verts)); } void TextField::setHover() @@ -69,7 +76,7 @@ void TextField::setHover() m_verts[2].m_color = rootView().themeData().textfield1Hover(); m_verts[3].m_color = rootView().themeData().textfield2Hover(); m_verts[4].m_color = rootView().themeData().textfield2Hover(); - m_bVertsBuf->load(m_verts, sizeof(SolidShaderVert) * 28); + m_bVertsBuf->load(m_verts, sizeof(m_verts)); } void TextField::setDisabled() @@ -79,11 +86,19 @@ void TextField::setDisabled() m_verts[2].m_color = rootView().themeData().textfield1Disabled(); m_verts[3].m_color = rootView().themeData().textfield2Disabled(); m_verts[4].m_color = rootView().themeData().textfield2Disabled(); - m_bVertsBuf->load(m_verts, sizeof(SolidShaderVert) * 28); + m_bVertsBuf->load(m_verts, sizeof(m_verts)); } void TextField::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) { + if (!m_active) + { + rootView().setActiveTextView(this); + if (!m_selectionCount) + setSelectionRange(0, m_textStr.size()); + } + else + setCursorPos(m_text->reverseSelectGlyph(coord.pixel[0] - m_text->subRect().location[0])); } void TextField::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) @@ -97,11 +112,120 @@ void TextField::mouseMove(const boo::SWindowCoord& coord) void TextField::mouseEnter(const boo::SWindowCoord& coord) { setHover(); + rootView().window()->setCursor(boo::EMouseCursor::IBeam); } void TextField::mouseLeave(const boo::SWindowCoord& coord) { setInactive(); + rootView().window()->setCursor(boo::EMouseCursor::Pointer); +} + +void TextField::charKeyDown(unsigned long charCode, boo::EModifierKey mods, bool isRepeat) +{ + if (m_selectionCount) + { + std::string newStr(m_textStr.cbegin(), m_textStr.cbegin() + m_selectionStart); + utf8proc_uint8_t theChar[5] = {}; + utf8proc_ssize_t sz = utf8proc_encode_char(charCode, theChar); + if (sz > 0) + newStr += (char*)theChar; + newStr.append(m_textStr.cbegin() + m_selectionStart + m_selectionCount, m_textStr.cend()); + setText(newStr); + } + else + { + std::string newStr(m_textStr.cbegin(), m_textStr.cbegin() + m_cursorPos); + utf8proc_uint8_t theChar[5] = {}; + utf8proc_ssize_t sz = utf8proc_encode_char(charCode, theChar); + if (sz > 0) + newStr += (char*)theChar; + newStr.append(m_textStr.cbegin() + m_cursorPos, m_textStr.cend()); + setText(newStr); + } +} + +void TextField::specialKeyDown(boo::ESpecialKey key, boo::EModifierKey mods, bool isRepeat) +{ + if (key == boo::ESpecialKey::Left) + { + if (m_selectionCount) + m_cursorPos = m_selectionStart; + setCursorPos(m_cursorPos==0 ? 0 : (m_cursorPos-1)); + } + else if (key == boo::ESpecialKey::Right) + { + if (m_selectionCount) + m_cursorPos = m_selectionStart + m_selectionCount - 1; + setCursorPos(m_cursorPos+1); + } +} + +void TextField::think() +{ + ++m_cursorFrames; +} + +void TextField::setActive(bool active) +{ + m_active = active; + if (!active) + clearSelectionRange(); +} + +void TextField::setCursorPos(size_t pos) +{ + clearSelectionRange(); + m_cursorPos = std::min(pos, m_textStr.size()); + m_cursorFrames = 0; + + float pf = rootView().viewRes().pixelFactor(); + int offset1 = 4 * pf + m_text->queryReverseAdvance(m_cursorPos); + int offset2 = offset1 + 2 * pf; + 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_bVertsBuf->load(m_verts, sizeof(m_verts)); +} + +void TextField::setSelectionRange(size_t start, size_t count) +{ + m_selectionStart = std::min(start, m_textStr.size()-1); + m_selectionCount = std::min(count, m_textStr.size()-m_selectionStart); + + ViewResources& res = rootView().viewRes(); + float pf = res.pixelFactor(); + int offset1 = 5 * pf; + int offset2 = offset1; + std::vector& glyphs = m_text->accessGlyphs(); + offset1 += glyphs[m_selectionStart].m_pos[0][0]; + offset2 += glyphs[m_selectionStart+m_selectionCount-1].m_pos[2][0]; + for (size_t i=0 ; i= m_selectionStart && i< m_selectionStart + m_selectionCount) + glyphs[i].m_color = rootView().themeData().selectedFieldText(); + else + glyphs[i].m_color = rootView().themeData().fieldText(); + } + m_text->updateGlyphs(); + + 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_bVertsBuf->load(m_verts, sizeof(m_verts)); +} + +void TextField::clearSelectionRange() +{ + m_selectionStart = 0; + m_selectionCount = 0; + + std::vector& glyphs = m_text->accessGlyphs(); + for (size_t i=0 ; iupdateGlyphs(); } void TextField::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) @@ -146,7 +270,7 @@ void TextField::resized(const boo::SWindowRect& root, const boo::SWindowRect& su m_verts[26].m_pos.assign(width+1, 1, 0); m_verts[27].m_pos.assign(width+1, 0, 0); - m_bVertsBuf->load(m_verts, sizeof(SolidShaderVert) * 28); + m_bVertsBuf->load(m_verts, sizeof(m_verts)); m_nomWidth = width; m_nomHeight = height; @@ -163,6 +287,16 @@ void TextField::draw(boo::IGraphicsCommandQueue* gfxQ) gfxQ->setShaderDataBinding(m_bShaderBinding); gfxQ->setDrawPrimitive(boo::Primitive::TriStrips); gfxQ->draw(0, 28); + if (m_active) + { + if (!m_selectionCount) + { + if (m_cursorFrames % 60 < 30) + gfxQ->draw(28, 4); + } + else + gfxQ->draw(28, 4); + } m_text->draw(gfxQ); } diff --git a/specter/lib/TextView.cpp b/specter/lib/TextView.cpp index a655b319b..6207d9fb7 100644 --- a/specter/lib/TextView.cpp +++ b/specter/lib/TextView.cpp @@ -355,6 +355,8 @@ void TextView::typesetGlyphs(const std::string& str, const Zeus::CColor& default m_glyphs.reserve(str.size()); m_glyphDims.clear(); m_glyphDims.reserve(str.size()); + m_glyphAdvs.clear(); + m_glyphAdvs.reserve(str.size()); int adv = 0; while (rem) @@ -378,6 +380,7 @@ void TextView::typesetGlyphs(const std::string& str, const Zeus::CColor& default adv += DoKern(m_fontAtlas.lookupKern(lCh, glyph->m_glyphIdx), m_fontAtlas); m_glyphs.emplace_back(adv, *glyph, defaultColor); m_glyphDims.emplace_back(glyph->m_width, glyph->m_height); + m_glyphAdvs.push_back(adv); lCh = glyph->m_glyphIdx; rem -= sz; @@ -422,6 +425,8 @@ void TextView::typesetGlyphs(const std::wstring& str, const Zeus::CColor& defaul m_glyphs.reserve(str.size()); m_glyphDims.clear(); m_glyphDims.reserve(str.size()); + m_glyphAdvs.clear(); + m_glyphAdvs.reserve(str.size()); int adv = 0; for (wchar_t ch : str) @@ -437,6 +442,7 @@ void TextView::typesetGlyphs(const std::wstring& str, const Zeus::CColor& defaul adv += DoKern(m_fontAtlas.lookupKern(lCh, glyph->m_glyphIdx), m_fontAtlas); m_glyphs.emplace_back(adv, *glyph, defaultColor); m_glyphDims.emplace_back(glyph->m_width, glyph->m_height); + m_glyphAdvs.push_back(adv); lCh = glyph->m_glyphIdx; @@ -516,5 +522,32 @@ std::pair TextView::queryGlyphDimensions(size_t pos) const return m_glyphDims[pos]; } +size_t TextView::reverseSelectGlyph(int x) const +{ + size_t ret = 0; + size_t idx = 1; + int minDelta = abs(x); + for (int adv : m_glyphAdvs) + { + int thisDelta = abs(adv-x); + if (thisDelta < minDelta) + { + minDelta = thisDelta; + ret = idx; + } + ++idx; + } + return ret; +} + +int TextView::queryReverseAdvance(size_t idx) const +{ + if (idx > m_glyphAdvs.size()) + Log.report(LogVisor::FatalError, + "TextView::queryReverseGlyph(%" PRISize ") out of inclusive bounds: %" PRISize, + idx, m_glyphAdvs.size()); + if (!idx) return 0; + return m_glyphAdvs[idx-1]; +} }