diff --git a/specter/include/Specter/FileBrowser.hpp b/specter/include/Specter/FileBrowser.hpp index 5fc9b7073..8397bc485 100644 --- a/specter/include/Specter/FileBrowser.hpp +++ b/specter/include/Specter/FileBrowser.hpp @@ -28,6 +28,7 @@ private: Type m_type; HECL::SystemString m_path; std::vector m_comps; + bool m_showingHidden = false; class LeftSide : public View { @@ -49,7 +50,7 @@ private: ViewChild> m_split; - void okActivated(); + void okActivated(bool viaButton); struct OKButton : IButtonBinding { FileBrowser& m_fb; @@ -62,7 +63,7 @@ private: RectangleConstraint(100 * res.pixelFactor(), -1, RectangleConstraint::Test::Minimum))); } const char* name() const {return m_text.c_str();} - void activated(const boo::SWindowCoord&) {m_fb.okActivated();} + void activated(const boo::SWindowCoord&) {m_fb.okActivated(true);} } m_ok; void cancelActivated(); @@ -115,6 +116,8 @@ private: struct FileListingDataBind : ITableDataBinding, ITableStateBinding { + FileBrowser& m_fb; + struct Entry { HECL::SystemString m_path; @@ -164,6 +167,20 @@ private: return nullptr; } + float m_columnSplits[3] = {0.0, 0.7, 0.9}; + + bool columnSplitResizeAllowed() const {return true;} + + float getColumnSplit(size_t cIdx) const + { + return m_columnSplits[cIdx]; + } + + void setColumnSplit(size_t cIdx, float split) + { + m_columnSplits[cIdx] = split; + } + void updateListing(const HECL::DirectoryEnumerator& dEnum) { m_entries.clear(); @@ -210,10 +227,20 @@ private: void setSelectedRow(size_t rIdx) { - + if (rIdx != -1) + 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(); } - FileListingDataBind(const IViewManager& vm) + void rowActivated(size_t rIdx) + { + m_fb.okActivated(false); + } + + FileListingDataBind(FileBrowser& fb, const IViewManager& vm) + : m_fb(fb) { m_nameCol = vm.translateOr("name", "Name"); m_typeCol = vm.translateOr("type", "Type"); @@ -225,12 +252,30 @@ private: } m_fileListingBind; ViewChild> m_fileListing; - struct BookmarkDataBind : ITableDataBinding + struct BookmarkDataBind : ITableDataBinding, ITableStateBinding { + FileBrowser& m_fb; + BookmarkDataBind(FileBrowser& fb) : m_fb(fb) {} + struct Entry { HECL::SystemString m_path; std::string m_name; + Entry(const HECL::SystemString& path) + : m_path(path) + { + HECL::SystemUTF8View 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 m_entries; @@ -241,6 +286,12 @@ private: { return &m_entries.at(rIdx).m_name; } + + void setSelectedRow(size_t rIdx) + { + if (rIdx != -1) + m_fb.navigateToPath(m_entries.at(rIdx).m_path); + } }; BookmarkDataBind m_systemBookmarkBind; @@ -255,12 +306,25 @@ private: std::unique_ptr m_recentBookmarksLabel; ViewChild> m_recentBookmarks; -public: - FileBrowser(ViewResources& res, View& parentView, const std::string& title, Type type) - : FileBrowser(res, parentView, title, type, HECL::GetcwdStr()) {} - FileBrowser(ViewResources& res, View& parentView, const std::string& title, Type type, const HECL::SystemString& initialPath); + std::function m_returnFunc; +public: + FileBrowser(ViewResources& res, View& parentView, const std::string& title, Type type, + std::function returnFunc) + : FileBrowser(res, parentView, title, type, HECL::GetcwdStr(), returnFunc) {} + FileBrowser(ViewResources& res, View& parentView, const std::string& title, Type type, + const HECL::SystemString& initialPath, + std::function returnFunc); + + static void SyncBookmarkSelections(Table& table, BookmarkDataBind& binding, + const HECL::SystemString& sel); void navigateToPath(const HECL::SystemString& path); + bool showingHidden() const {return m_showingHidden;} + void setShowingHidden(bool showingHidden) + { + m_showingHidden = showingHidden; + navigateToPath(m_path); + } void updateContentOpacity(float opacity); void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey); @@ -272,6 +336,8 @@ public: void touchDown(const boo::STouchCoord&, uintptr_t); void touchUp(const boo::STouchCoord&, uintptr_t); void touchMove(const boo::STouchCoord&, uintptr_t); + void charKeyDown(unsigned long, boo::EModifierKey, bool); + void specialKeyDown(boo::ESpecialKey, boo::EModifierKey, bool); void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub); void think(); diff --git a/specter/include/Specter/IViewManager.hpp b/specter/include/Specter/IViewManager.hpp index 13b3d203c..e9b0fbe86 100644 --- a/specter/include/Specter/IViewManager.hpp +++ b/specter/include/Specter/IViewManager.hpp @@ -2,6 +2,7 @@ #define SPECTER_IVIEWMANAGER_HPP #include "Translator.hpp" +#include namespace Specter { @@ -17,6 +18,12 @@ public: return trans->translateOr(key, vor); return vor; } + + virtual const std::vector* recentProjects() const {return nullptr;} + virtual void pushRecentProject(const HECL::SystemString& path) {} + + virtual const std::vector* recentFiles() const {return nullptr;} + virtual void pushRecentFile(const HECL::SystemString& path) {} }; } diff --git a/specter/include/Specter/Table.hpp b/specter/include/Specter/Table.hpp index 1ea923316..00531f0de 100644 --- a/specter/include/Specter/Table.hpp +++ b/specter/include/Specter/Table.hpp @@ -27,6 +27,7 @@ struct ITableDataBinding 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) {} @@ -50,6 +51,7 @@ class Table : public View Table& m_t; std::unique_ptr m_text; size_t m_c, m_r; + boo::SWindowRect m_scissorRect; CellView(Table& t, ViewResources& res, size_t c, size_t r); bool m_selected = false; @@ -60,19 +62,25 @@ class Table : public View void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey); void mouseEnter(const boo::SWindowCoord&); void mouseLeave(const boo::SWindowCoord&); - void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub); + void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub, + const boo::SWindowRect& scissor); void draw(boo::IGraphicsCommandQueue* gfxQ); }; std::vector>> m_headerViews; std::vector>>> m_cellViews; bool m_header = false; + std::vector m_hCellRects; + size_t m_hDraggingIdx = 0; + std::unique_ptr m_hVerts; boo::IGraphicsBufferD* m_hVertsBuf = nullptr; boo::IVertexFormat* m_hVtxFmt = nullptr; /* OpenGL only */ boo::IShaderDataBinding* m_hShaderBinding = nullptr; void _setHeaderVerts(const boo::SWindowRect& rect); + std::vector getCellRects(const boo::SWindowRect& tableRect) const; + ViewChild> m_scroll; struct RowsView : public View diff --git a/specter/include/Specter/TextField.hpp b/specter/include/Specter/TextField.hpp index 6264900fa..b1bb04c0f 100644 --- a/specter/include/Specter/TextField.hpp +++ b/specter/include/Specter/TextField.hpp @@ -16,8 +16,9 @@ class TextField : public ITextInputView std::string m_deferredTextStr; std::string m_deferredMarkStr; std::unique_ptr m_text; + std::unique_ptr m_errText; - SolidShaderVert m_verts[32]; + SolidShaderVert m_verts[41]; boo::IGraphicsBufferD* m_bVertsBuf = nullptr; boo::IVertexFormat* m_bVtxFmt = nullptr; /* OpenGL only */ boo::IShaderDataBinding* m_bShaderBinding = nullptr; @@ -48,15 +49,24 @@ class TextField : public ITextInputView 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); @@ -99,6 +109,8 @@ public: void setActive(bool active); void setCursorPos(size_t pos); + void setErrorState(const std::string& message); + void clearErrorState(); void setSelectionRange(size_t start, size_t count); void clearSelectionRange(); @@ -109,8 +121,10 @@ public: m_viewVertBlock.m_color = color; m_viewVertBlockBuf->load(&m_viewVertBlock, sizeof(ViewBlock)); m_text->setMultiplyColor(color); + if (m_errText) + m_errText->setMultiplyColor(color); } - + private: void _setCursorPos(); void _reallySetCursorPos(size_t pos); diff --git a/specter/include/Specter/TextView.hpp b/specter/include/Specter/TextView.hpp index 60c376e44..23d2c4afb 100644 --- a/specter/include/Specter/TextView.hpp +++ b/specter/include/Specter/TextView.hpp @@ -89,7 +89,7 @@ public: void colorGlyphsTypeOn(const Zeus::CColor& newColor, float startInterval=0.2, float fadeTime=0.5); void think(); - void resized(const boo::SWindowRect &rootView, const boo::SWindowRect& sub); + void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub); void draw(boo::IGraphicsCommandQueue* gfxQ); int nominalWidth() const {return m_width;} diff --git a/specter/include/Specter/ViewResources.hpp b/specter/include/Specter/ViewResources.hpp index 510973e46..6f9155527 100644 --- a/specter/include/Specter/ViewResources.hpp +++ b/specter/include/Specter/ViewResources.hpp @@ -19,7 +19,7 @@ class ThemeData Zeus::CColor m_vpBg = {0.2, 0.2, 0.2, 1.0}; Zeus::CColor m_tbBg = {0.4, 0.4, 0.4, 1.0}; - Zeus::CColor m_tooltipBg = {0.0, 0.0, 0.0, 0.65}; + Zeus::CColor m_tooltipBg = {0.1, 0.1, 0.1, 0.85}; Zeus::CColor m_splashBg = {0.075, 0.075, 0.075, 0.85}; Zeus::CColor m_splash1 = {1.0, 1.0, 1.0, 1.0}; diff --git a/specter/lib/Button.cpp b/specter/lib/Button.cpp index 95b470b7b..cc0992ec1 100644 --- a/specter/lib/Button.cpp +++ b/specter/lib/Button.cpp @@ -276,6 +276,8 @@ void Button::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) if (m_style == Style::Block) textRect.location[1] += 7 * pf; textRect.location[0] += sub.size[0] / 2; + textRect.size[0] = m_nomWidth; + textRect.size[1] = m_nomHeight; m_text->resized(root, textRect); } diff --git a/specter/lib/FileBrowser.cpp b/specter/lib/FileBrowser.cpp index 1f3a40b9e..6d1b49316 100644 --- a/specter/lib/FileBrowser.cpp +++ b/specter/lib/FileBrowser.cpp @@ -3,6 +3,7 @@ namespace Specter { +static LogVisor::LogModule Log("Specter::FileBrowser"); #define BROWSER_MARGIN 8 #define BROWSER_MIN_WIDTH 600 @@ -40,7 +41,8 @@ static std::vector PathComponents(const HECL::SystemString& } FileBrowser::FileBrowser(ViewResources& res, View& parentView, const std::string& title, - Type type, const HECL::SystemString& initialPath) + Type type, const HECL::SystemString& initialPath, + std::function returnFunc) : ModalWindow(res, parentView, RectangleConstraint(BROWSER_MIN_WIDTH * res.pixelFactor(), BROWSER_MIN_HEIGHT * res.pixelFactor(), RectangleConstraint::Test::Minimum, @@ -51,7 +53,11 @@ FileBrowser::FileBrowser(ViewResources& res, View& parentView, const std::string m_ok(*this, res, title), m_cancel(*this, res, rootView().viewManager().translateOr("cancel", "Cancel")), m_fileFieldBind(*this, rootView().viewManager()), - m_fileListingBind(rootView().viewManager()) + m_fileListingBind(*this, rootView().viewManager()), + m_systemBookmarkBind(*this), + m_projectBookmarkBind(*this), + m_recentBookmarkBind(*this), + m_returnFunc(returnFunc) { commitResources(res); setBackground({0,0,0,0.5}); @@ -59,16 +65,22 @@ FileBrowser::FileBrowser(ViewResources& res, View& parentView, const std::string IViewManager& vm = rootView().viewManager(); m_fileField.m_view.reset(new TextField(res, *this, &m_fileFieldBind)); m_fileListing.m_view.reset(new Table(res, *this, &m_fileListingBind, &m_fileListingBind, 3)); - m_systemBookmarks.m_view.reset(new Table(res, *this, &m_systemBookmarkBind, nullptr, 1)); + m_systemBookmarks.m_view.reset(new Table(res, *this, &m_systemBookmarkBind, &m_systemBookmarkBind, 1)); m_systemBookmarksLabel.reset(new TextView(res, *this, res.m_mainFont)); m_systemBookmarksLabel->typesetGlyphs(vm.translateOr("system_locations", "System Locations"), res.themeData().uiText()); - m_projectBookmarks.m_view.reset(new Table(res, *this, &m_projectBookmarkBind, nullptr, 1)); + m_projectBookmarks.m_view.reset(new Table(res, *this, &m_projectBookmarkBind, &m_projectBookmarkBind, 1)); m_projectBookmarksLabel.reset(new TextView(res, *this, res.m_mainFont)); m_projectBookmarksLabel->typesetGlyphs(vm.translateOr("recent_projects", "Recent Projects"), res.themeData().uiText()); - m_recentBookmarks.m_view.reset(new Table(res, *this, &m_recentBookmarkBind, nullptr, 1)); + m_recentBookmarks.m_view.reset(new Table(res, *this, &m_recentBookmarkBind, &m_recentBookmarkBind, 1)); m_recentBookmarksLabel.reset(new TextView(res, *this, res.m_mainFont)); m_recentBookmarksLabel->typesetGlyphs(vm.translateOr("recent_files", "Recent Files"), res.themeData().uiText()); + /* Populate system bookmarks */ + std::vector systemLocs = HECL::GetSystemLocations(); + for (const HECL::SystemString& loc : systemLocs) + m_systemBookmarkBind.m_entries.emplace_back(loc); + m_systemBookmarks.m_view->updateData(); + navigateToPath(initialPath); m_split.m_view.reset(new SplitView(res, *this, SplitView::Axis::Vertical, @@ -80,6 +92,23 @@ FileBrowser::FileBrowser(ViewResources& res, View& parentView, const std::string updateContentOpacity(0.0); } +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(const HECL::SystemString& path) { HECL::Sstat theStat; @@ -92,6 +121,7 @@ void FileBrowser::navigateToPath(const HECL::SystemString& path) { HECL::SystemUTF8View utf8(m_comps.back()); m_fileField.m_view->setText(utf8); + m_fileField.m_view->clearErrorState(); m_comps.pop_back(); } @@ -100,12 +130,20 @@ void FileBrowser::navigateToPath(const HECL::SystemString& path) for (const HECL::SystemString& d : m_comps) { if (needSlash) - dir += '/'; - needSlash = true; + dir += _S('/'); + if (d.compare(_S("/"))) + 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_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(); @@ -138,12 +176,104 @@ void FileBrowser::updateContentOpacity(float opacity) m_recentBookmarksLabel->setMultiplyColor(color); } -void FileBrowser::okActivated() +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 += _S('/'); + if (d.compare(_S("/"))) + needSlash = true; + path += d; + } + + HECL::Sstat theStat; + if (HECL::Stat(path.c_str(), &theStat) || !S_ISDIR(theStat.st_mode)) + { + HECL::SystemUTF8View utf8(path); + m_fileField.m_view->setErrorState( + HECL::Format(vm.translateOr("no_access_as_dir", "Unable to access '%s' as directory").c_str(), + utf8.c_str())); + return; + } + + path += _S('/'); + path += m_fileField.m_view->getText(); + + int err = HECL::Stat(path.c_str(), &theStat); + if (m_type == Type::SaveFile) + { + if (!err && S_ISDIR(theStat.st_mode)) + { + 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::SystemUTF8View utf8(path); + m_fileField.m_view->setErrorState( + HECL::Format(vm.translateOr("no_access_as_file", "Unable to access '%s' as file").c_str(), + utf8.c_str())); + 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)) + { + navigateToPath(path); + return; + } + if (err || !S_ISDIR(theStat.st_mode)) + { + HECL::SystemUTF8View utf8(path); + m_fileField.m_view->setErrorState( + HECL::Format(vm.translateOr("no_access_as_dir", "Unable to access '%s' as directory").c_str(), + utf8.c_str())); + 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 += _S('/'); + if (d.compare(_S("/"))) + needSlash = true; + path += d; + } + + path += _S('/'); + path += m_fileField.m_view->getText(); + + m_returnFunc(false, path); close(); } @@ -158,8 +288,9 @@ void FileBrowser::pathButtonActivated(size_t idx) for (const HECL::SystemString& d : m_comps) { if (needSlash) - dir += '/'; - needSlash = true; + dir += _S('/'); + if (d.compare(_S("/"))) + needSlash = true; dir += d; if (++i > idx) break; @@ -176,6 +307,9 @@ void FileBrowser::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton bu b.m_button.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); } @@ -197,6 +331,9 @@ void FileBrowser::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton butt 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); } @@ -210,6 +347,9 @@ void FileBrowser::mouseMove(const boo::SWindowCoord& coord) b.m_button.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); } @@ -248,6 +388,27 @@ 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 ((mod & boo::EModifierKey::CtrlCommand) != boo::EModifierKey::None && !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); @@ -361,10 +522,10 @@ void FileBrowser::RightSide::draw(boo::IGraphicsCommandQueue* gfxQ) { for (PathButton& b : m_fb.m_pathButtons) b.m_button.m_view->draw(gfxQ); - m_fb.m_fileField.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); } } diff --git a/specter/lib/RootView.cpp b/specter/lib/RootView.cpp index 174e09d6d..742fc925a 100644 --- a/specter/lib/RootView.cpp +++ b/specter/lib/RootView.cpp @@ -50,10 +50,10 @@ void RootView::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, void RootView::mouseMove(const boo::SWindowCoord& coord) { - if (m_view) - m_view->mouseMove(coord); if (m_activeDragView) m_activeDragView->mouseMove(coord); + else if (m_view) + m_view->mouseMove(coord); boo::SWindowRect ttrect = m_rootRect; ttrect.location[0] = coord.pixel[0]; diff --git a/specter/lib/Table.cpp b/specter/lib/Table.cpp index ed9bd5949..35a70466b 100644 --- a/specter/lib/Table.cpp +++ b/specter/lib/Table.cpp @@ -8,14 +8,17 @@ static LogVisor::LogModule Log("Specter::Table"); #define ROW_HEIGHT 18 #define CELL_MARGIN 1 -Table::Table(ViewResources& res, View& parentView, ITableDataBinding* data, ITableStateBinding* state, size_t maxColumns) +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(new SolidShaderVert[maxColumns * 6]), m_rowsView(*this, res) { if (!maxColumns) Log.report(LogVisor::FatalError, "0-column tables not supported"); - m_hVertsBuf = res.m_factory->newDynamicBuffer(boo::BufferUse::Vertex, sizeof(SolidShaderVert), maxColumns * 6); + m_hVertsBuf = res.m_factory->newDynamicBuffer(boo::BufferUse::Vertex, + sizeof(SolidShaderVert), + maxColumns * 6); if (!res.m_viewRes.m_texVtxFmt) { @@ -86,7 +89,6 @@ void Table::_setHeaderVerts(const boo::SWindowRect& sub) const ThemeData& theme = rootView().themeData(); float pf = rootView().viewRes().pixelFactor(); - int div = sub.size[0] / m_headerViews.size(); int margin = CELL_MARGIN * pf; int rowHeight = ROW_HEIGHT * pf; int xOff = 0; @@ -99,6 +101,12 @@ void Table::_setHeaderVerts(const boo::SWindowRect& sub) 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>& hv = *it; @@ -131,6 +139,7 @@ void Table::_setHeaderVerts(const boo::SWindowRect& sub) } } + int div = cellRectsIt->size[0]; v[0].m_pos.assign(xOff + margin, yOff - margin, 0); v[0].m_color = cm1; v[1] = v[0]; @@ -144,6 +153,7 @@ void Table::_setHeaderVerts(const boo::SWindowRect& sub) v += 6; xOff += div; ++it; + ++cellRectsIt; } if (c) @@ -161,7 +171,6 @@ void Table::RowsView::_setRowVerts(const boo::SWindowRect& sub, const boo::SWind return; float pf = rootView().viewRes().pixelFactor(); - int div = sub.size[0] / m_t.m_cellViews.size(); int spacing = (ROW_HEIGHT + CELL_MARGIN * 2) * pf; int margin = CELL_MARGIN * pf; int rowHeight = ROW_HEIGHT * pf; @@ -173,15 +182,22 @@ void Table::RowsView::_setRowVerts(const boo::SWindowRect& sub, const boo::SWind ++idx; } int startIdx = int(m_t.m_rows) - idx; + if (!m_t.m_header) + startIdx -= 1; + + std::vector cellRects = m_t.getCellRects(sub); size_t r, c; - for (r=0, c=0 ; r= scissor.location[1] ; ++r) + for (r=0, c=0 ; r= 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 ; csize[0]; v[0].m_pos.assign(xOff + margin, yOff - margin, 0); v[0].m_color = color; v[1] = v[0]; @@ -194,6 +210,7 @@ void Table::RowsView::_setRowVerts(const boo::SWindowRect& sub, const boo::SWind v[5] = v[4]; v += 6; xOff += div; + ++cellRectsIt; } yOff -= spacing; } @@ -276,15 +293,38 @@ void Table::CellView::deselect() m_text->colorGlyphs(rootView().themeData().uiText()); } -void Table::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) +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]) < rect.size[1]) + { + m_hDraggingIdx = cIdx; + rootView().setActiveDragView(this); + return; + } + ++cIdx; + } + } + m_scroll.mouseDown(coord, button, mod); if (m_headerNeedsUpdate) _setHeaderVerts(subRect()); } - -void Table::RowsView::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) +void Table::RowsView::mouseDown(const boo::SWindowCoord& coord, + boo::EMouseButton button, boo::EModifierKey mod) { for (ViewChild>& hv : m_t.m_headerViews) if (hv.mouseDown(coord, button, mod)) @@ -294,7 +334,8 @@ void Table::RowsView::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButto cv.mouseDown(coord, button, mod); } -void Table::CellView::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) +void Table::CellView::mouseDown(const boo::SWindowCoord& coord, + boo::EMouseButton button, boo::EModifierKey mod) { if (m_r != -1) { @@ -308,14 +349,21 @@ void Table::CellView::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButto m_t.m_headerNeedsUpdate = true; } -void Table::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) +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) +void Table::RowsView::mouseUp(const boo::SWindowCoord& coord, + boo::EMouseButton button, boo::EModifierKey mod) { size_t idx = 0; for (ViewChild>& hv : m_t.m_headerViews) @@ -330,7 +378,8 @@ void Table::RowsView::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton cv.mouseUp(coord, button, mod); } -void Table::CellView::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mod) +void Table::CellView::mouseUp(const boo::SWindowCoord& coord, + boo::EMouseButton button, boo::EModifierKey mod) { if (m_r == -1) m_t.m_headerNeedsUpdate = true; @@ -338,6 +387,43 @@ void Table::CellView::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton 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]) < rect.size[1]) + { + hovering = true; + break; + } + } + rootView().setVerticalSplitHover(hovering); + } + m_scroll.mouseMove(coord); if (m_headerNeedsUpdate) _setHeaderVerts(subRect()); @@ -460,25 +546,77 @@ void Table::updateData() updateSize(); } +std::vector 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 splits; + splits.reserve(m_columns); + if (m_state) + { + float lastSplit = 0.0; + size_t i; + for (i=0 ; igetColumnSplit(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 ret; + ret.reserve(m_columns); + + int lastX = 0; + for (size_t i=0 ; iresized(root, sub); + boo::SWindowRect headRow = sub; float pf = rootView().viewRes().pixelFactor(); - boo::SWindowRect cell = sub; - cell.size[1] = ROW_HEIGHT * pf; - cell.location[1] += sub.size[1] - cell.size[1]; - int div = sub.size[0] / m_cellViews.size(); - cell.size[0] = div; - + headRow.location[1] += sub.size[1] - ROW_HEIGHT * pf; + std::vector cellRects = getCellRects(headRow); _setHeaderVerts(sub); + size_t cIdx = 0; for (ViewChild>& hv : m_headerViews) { if (hv.m_view) - hv.m_view->resized(root, cell); - cell.location[0] += div; + hv.m_view->resized(root, cellRects[cIdx], sub); + ++cIdx; } } @@ -497,34 +635,32 @@ void Table::RowsView::resized(const boo::SWindowRect& root, const boo::SWindowRe View::resized(root, sub); _setRowVerts(sub, scissor); - if (m_t.m_cellViews.empty()) + if (!m_t.m_columns) return; float pf = rootView().viewRes().pixelFactor(); - int div = sub.size[0] / m_t.m_cellViews.size(); - boo::SWindowRect cell = sub; - cell.size[0] = div; - cell.size[1] = ROW_HEIGHT * pf; - cell.location[1] += sub.size[1] - cell.size[1]; + boo::SWindowRect rowRect = sub; + rowRect.location[1] += sub.size[1] - ROW_HEIGHT * pf; int spacing = (ROW_HEIGHT + CELL_MARGIN * 2) * pf; - int hStart = cell.location[1]; + std::vector cellRects = m_t.getCellRects(rowRect); + auto cellRectIt = cellRects.begin(); for (auto& col : m_t.m_cellViews) { - cell.location[1] = hStart; for (ViewChild>& cv : col) { - cell.location[1] -= spacing; + cellRectIt->location[1] -= spacing; if (cv.m_view) - cv.m_view->resized(root, cell); + cv.m_view->resized(root, *cellRectIt, scissor); } - cell.location[0] += div; + ++cellRectIt; } m_scissorRect = scissor; m_scissorRect.size[1] -= spacing; } -void Table::CellView::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub) +void Table::CellView::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub, + const boo::SWindowRect& scissor) { View::resized(root, sub); boo::SWindowRect textRect = sub; @@ -532,6 +668,7 @@ void Table::CellView::resized(const boo::SWindowRect& root, const boo::SWindowRe 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) @@ -557,22 +694,28 @@ void Table::RowsView::draw(boo::IGraphicsCommandQueue* gfxQ) ++idx; } } - gfxQ->setScissor(rootView().subRect()); if (m_t.m_header) { gfxQ->setShaderDataBinding(m_t.m_hShaderBinding); gfxQ->setDrawPrimitive(boo::Primitive::TriStrips); + gfxQ->setScissor(rootView().subRect()); gfxQ->draw(1, m_t.m_columns * 6 - 2); for (ViewChild>& 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) { - m_text->draw(gfxQ); + if (m_scissorRect.size[0] && m_scissorRect.size[1]) + { + gfxQ->setScissor(m_scissorRect); + m_text->draw(gfxQ); + } } } diff --git a/specter/lib/TextField.cpp b/specter/lib/TextField.cpp index d79211173..56d047b5b 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) : ITextInputView(res, parentView, strBind) { - m_bVertsBuf = res.m_factory->newDynamicBuffer(boo::BufferUse::Vertex, sizeof(SolidShaderVert), 32); + m_bVertsBuf = res.m_factory->newDynamicBuffer(boo::BufferUse::Vertex, sizeof(SolidShaderVert), 41); if (!res.m_viewRes.m_texVtxFmt) { @@ -33,18 +33,14 @@ TextField::TextField(ViewResources& res, View& parentView, IStringBinding* strBi } commitResources(res); - m_verts[0].m_color = rootView().themeData().textfield1Inactive(); - m_verts[1].m_color = rootView().themeData().textfield2Inactive(); - m_verts[2].m_color = rootView().themeData().textfield1Inactive(); - m_verts[3].m_color = rootView().themeData().textfield2Inactive(); - m_verts[4].m_color = rootView().themeData().textfield2Inactive(); - for (int i=5 ; i<28 ; ++i) - m_verts[i].m_color = res.themeData().textfield2Inactive(); for (int i=28 ; i<32 ; ++i) m_verts[i].m_color = res.themeData().textfieldSelection(); + setInactive(); m_bVertsBuf->load(m_verts, sizeof(m_verts)); m_text.reset(new TextView(res, *this, res.m_mainFont, TextView::Alignment::Left, 1024)); + if (strBind) + setText(strBind->getDefault()); } void TextField::_setText() @@ -53,7 +49,10 @@ void TextField::_setText() { _clearSelectionRange(); m_textStr = m_deferredTextStr; - m_text->typesetGlyphs(m_textStr, rootView().themeData().fieldText()); + m_text->typesetGlyphs(m_textStr, m_error ? rootView().themeData().uiText() : + rootView().themeData().fieldText()); + if (m_controlBinding && dynamic_cast(m_controlBinding)) + static_cast(*m_controlBinding).changed(m_textStr); m_hasTextSet = false; if (m_deferredMarkStr.size()) m_hasMarkSet = true; @@ -94,7 +93,8 @@ void TextField::_setMarkedText() std::string compStr(m_textStr.cbegin(), (UTF8Iterator(m_textStr.cbegin()) + repPoint).iter()); compStr += m_deferredMarkStr; compStr += std::string((UTF8Iterator(m_textStr.cbegin()) + repEnd).iter(), m_textStr.cend()); - m_text->typesetGlyphs(compStr, rootView().themeData().fieldText()); + m_text->typesetGlyphs(compStr, m_error ? rootView().themeData().uiText() : + rootView().themeData().fieldText()); size_t pos = m_cursorPos; if (m_deferredMarkStr.size()) @@ -128,32 +128,96 @@ void TextField::setText(const std::string& str) void TextField::setInactive() { - m_verts[0].m_color = rootView().themeData().textfield1Inactive(); - m_verts[1].m_color = rootView().themeData().textfield2Inactive(); - m_verts[2].m_color = rootView().themeData().textfield1Inactive(); - m_verts[3].m_color = rootView().themeData().textfield2Inactive(); - m_verts[4].m_color = rootView().themeData().textfield2Inactive(); + const ThemeData& theme = rootView().themeData(); + if (m_error) + { + m_verts[0].m_color = theme.textfield1Inactive() * Zeus::CColor::skRed; + m_verts[1].m_color = theme.textfield2Inactive() * Zeus::CColor::skRed; + m_verts[2].m_color = theme.textfield1Inactive() * Zeus::CColor::skRed; + m_verts[3].m_color = theme.textfield2Inactive() * Zeus::CColor::skRed; + m_verts[4].m_color = theme.textfield2Inactive() * Zeus::CColor::skRed; + for (int i=5 ; i<28 ; ++i) + m_verts[i].m_color = theme.textfield2Inactive() * Zeus::CColor::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_bVertsBuf->load(m_verts, sizeof(m_verts)); + m_bgState = BGState::Inactive; } void TextField::setHover() { - m_verts[0].m_color = rootView().themeData().textfield1Hover(); - m_verts[1].m_color = rootView().themeData().textfield2Hover(); - m_verts[2].m_color = rootView().themeData().textfield1Hover(); - m_verts[3].m_color = rootView().themeData().textfield2Hover(); - m_verts[4].m_color = rootView().themeData().textfield2Hover(); + const ThemeData& theme = rootView().themeData(); + if (m_error) + { + m_verts[0].m_color = theme.textfield1Hover() * Zeus::CColor::skRed; + m_verts[1].m_color = theme.textfield2Hover() * Zeus::CColor::skRed; + m_verts[2].m_color = theme.textfield1Hover() * Zeus::CColor::skRed; + m_verts[3].m_color = theme.textfield2Hover() * Zeus::CColor::skRed; + m_verts[4].m_color = theme.textfield2Hover() * Zeus::CColor::skRed; + for (int i=5 ; i<28 ; ++i) + m_verts[i].m_color = theme.textfield2Inactive() * Zeus::CColor::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_bVertsBuf->load(m_verts, sizeof(m_verts)); + m_bgState = BGState::Hover; } void TextField::setDisabled() { - m_verts[0].m_color = rootView().themeData().textfield1Disabled(); - m_verts[1].m_color = rootView().themeData().textfield2Disabled(); - m_verts[2].m_color = rootView().themeData().textfield1Disabled(); - m_verts[3].m_color = rootView().themeData().textfield2Disabled(); - m_verts[4].m_color = rootView().themeData().textfield2Disabled(); + const ThemeData& theme = rootView().themeData(); + if (m_error) + { + m_verts[0].m_color = theme.textfield1Disabled() * Zeus::CColor::skRed; + m_verts[1].m_color = theme.textfield2Disabled() * Zeus::CColor::skRed; + m_verts[2].m_color = theme.textfield1Disabled() * Zeus::CColor::skRed; + m_verts[3].m_color = theme.textfield2Disabled() * Zeus::CColor::skRed; + m_verts[4].m_color = theme.textfield2Disabled() * Zeus::CColor::skRed; + for (int i=5 ; i<28 ; ++i) + m_verts[i].m_color = theme.textfield2Disabled() * Zeus::CColor::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_bVertsBuf->load(m_verts, sizeof(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) @@ -524,7 +588,6 @@ boo::SWindowRect TextField::rectForCharacterRange(const std::pair& rang const std::vector& glyphs = m_text->accessGlyphs(); const TextView::RenderGlyph& g1 = glyphs[range.first]; const TextView::RenderGlyph& g2 = glyphs[endIdx]; - fprintf(stderr, "Ret %d %d\n", subRect().location[0] + int(g1.m_pos[1][0]), subRect().location[1] + int(g1.m_pos[1][1])); 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])}}; } @@ -534,6 +597,36 @@ 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::CColor::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::CColor::skClear, t); + errBg = Zeus::CColor::lerp(rootView().themeData().tooltipBackground() * m_viewVertBlock.m_color, + Zeus::CColor::skClear, t); + } + for (size_t i=32 ; i<41 ; ++i) + m_verts[i].m_color = errBg; + m_bVertsBuf->load(m_verts, sizeof(m_verts)); + + m_errText->setMultiplyColor(errMult); + } std::unique_lock lk(m_textInputLk); _setText(); @@ -599,6 +692,34 @@ void TextField::setCursorPos(size_t pos) m_deferredCursorPos = pos; m_hasCursorSet = true; } + +void TextField::setErrorState(const std::string& message) +{ + m_error = true; + if (m_selectionCount) + _reallySetSelectionRange(m_selectionStart, m_selectionCount); + else + clearSelectionRange(); + refreshBg(); + + m_errText.reset(new 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) { @@ -609,12 +730,16 @@ void TextField::_reallySetSelectionRange(size_t start, size_t len) std::vector& 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= start && i < start + len) - glyphs[i].m_color = rootView().themeData().selectedFieldText(); + glyphs[i].m_color = selColor; else - glyphs[i].m_color = rootView().themeData().fieldText(); + glyphs[i].m_color = deselColor; } m_text->updateGlyphs(); @@ -659,7 +784,7 @@ void TextField::_setSelectionRange() { size_t len = UTF8Iterator(m_textStr.cbegin()).countTo(m_textStr.cend()); m_selectionStart = std::min(m_deferredSelectionStart, len-1); - m_deferredSelectionStart = m_selectionStart; + m_deferredSelectionStart = m_selectionStart; m_selectionCount = std::min(m_deferredSelectionCount, len-m_selectionStart); m_deferredSelectionCount = m_selectionCount; _reallySetSelectionRange(m_selectionStart, m_selectionCount); @@ -690,9 +815,12 @@ void TextField::_clearSelectionRange() m_selectionStart = 0; m_selectionCount = 0; + const Zeus::CColor& deselColor = m_error ? rootView().themeData().uiText() : + rootView().themeData().fieldText(); + std::vector& glyphs = m_text->accessGlyphs(); for (size_t i=0 ; iupdateGlyphs(); m_hasSelectionClear = false; @@ -750,6 +878,30 @@ 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); + 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::CColor::skClear; + } + m_bVertsBuf->load(m_verts, sizeof(m_verts)); m_nomWidth = width; @@ -777,6 +929,13 @@ void TextField::draw(boo::IGraphicsCommandQueue* gfxQ) else gfxQ->draw(28, 4); } + + if (m_error) + { + gfxQ->draw(32, 9); + m_errText->draw(gfxQ); + } + m_text->draw(gfxQ); }