diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c59277df1..722cf8d000 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -342,6 +342,7 @@ add_library(nanogui-obj OBJECT include/nanogui/tabheader.h src/tabheader.cpp include/nanogui/tabwidget.h src/tabwidget.cpp include/nanogui/glcanvas.h src/glcanvas.cpp + include/nanogui/table.h src/table.cpp include/nanogui/formhelper.h include/nanogui/toolbutton.h include/nanogui/opengl.h diff --git a/include/nanogui/common.h b/include/nanogui/common.h index e87fd1188e..96e485df0d 100644 --- a/include/nanogui/common.h +++ b/include/nanogui/common.h @@ -475,6 +475,7 @@ extern NANOGUI_EXPORT std::string file_dialog(const std::vector> &filetypes, bool save); +bool isPointInsideRect(const Vector2i& p, const Vector4i& r); /** * \brief Open a native file open dialog, which allows multiple selection. * diff --git a/include/nanogui/label.h b/include/nanogui/label.h index 658ea6b477..bbb29ee9d6 100644 --- a/include/nanogui/label.h +++ b/include/nanogui/label.h @@ -26,6 +26,7 @@ NAMESPACE_BEGIN(nanogui) */ class NANOGUI_EXPORT Label : public Widget { public: + enum TextAlign { alLeft=0, alCenter, alRight }; Label(Widget *parent, const std::string &caption, const std::string &font = "sans", int fontSize = -1); @@ -43,6 +44,7 @@ class NANOGUI_EXPORT Label : public Widget { Color color() const { return mColor; } /// Set the label color void setColor(const Color& color) { mColor = color; } + void setTextAlign(TextAlign align) { mTextAlign = align; } /// Set the \ref Theme used to draw this widget virtual void setTheme(Theme *theme) override; @@ -59,6 +61,7 @@ class NANOGUI_EXPORT Label : public Widget { std::string mCaption; std::string mFont; Color mColor; + TextAlign mTextAlign = alLeft; public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW }; diff --git a/include/nanogui/table.h b/include/nanogui/table.h new file mode 100644 index 0000000000..6d9f13f4ee --- /dev/null +++ b/include/nanogui/table.h @@ -0,0 +1,283 @@ +#pragma once + +#include +#include +#include + +NAMESPACE_BEGIN(nanogui) + +class ScrollBar; +class TextBox; + +class NANOGUI_EXPORT Table : public Widget +{ +public: + //! modes for ordering used when a column header is clicked + enum ColumnOrder + { + //! Do not use ordering + coNone, + + //! Send a EGET_TABLE_HEADER_CHANGED message when a column header is clicked. + coCustom, + + //! Sort it ascending by it's ascii value like: a,b,c,... + coAscending, + + //! Sort it descending by it's ascii value like: z,x,y,... + coDescending, + + //! Sort it ascending on first click, descending on next, etc + coAscendingDescending, + + //! Not used as mode, only to get maximum value for this enum + coCount + }; + + enum RowOrder + { + //! No element ordering + roNone, + + //! Elements are ordered from the smallest to the largest. + roAscending, + + //! Elements are ordered from the largest to the smallest. + roDescending, + + //! this value is not used, it only specifies the amount of default ordering types + //! available. + roCount + }; + + enum DrawFlag + { + drawRows = 0, + drawColumns = 1, + drawActiveRow = 2, + drawRowBackground = 3, + drawBorder = 4, + drawCount + } ; + //! constructor + Table( Widget* parent, + const std::string& id, const Vector4i& rectangle, bool clip=true, + bool drawBack=false, bool moveOverSelect=true); + + //! destructor + virtual ~Table(); + + //! Adds a column + //! If columnIndex is outside the current range, do push new colum at the end + virtual void addColumn(const std::string& caption, uint32_t columnIndex=0xffffffff); + + //! remove a column from the table + virtual void removeColumn(uint32_t columnIndex); + + //! Returns the number of columns in the table control + virtual int getColumnCount() const; + + //! Makes a column active. This will trigger an ordering process. + /** \param idx: The id of the column to make active. + \return True if successful. */ + virtual bool setActiveColumn(int columnIndex, bool doOrder=false); + + //! Returns which header is currently active + virtual int getActiveColumn() const; + + bool scrollEvent(const Vector2i &p, const Vector2f &rel) override; + + //! Returns the ordering used by the currently active column + virtual RowOrder getActiveColumnOrdering() const; + + //! set a column width + virtual void setColumnWidth(uint32_t columnIndex, uint32_t width); + + //! returns column width + virtual uint32_t getColumnWidth(uint32_t columnIndex) const; + + //! columns can be resized by drag 'n drop + virtual void setResizableColumns(bool resizable); + + //! can columns be resized by dran 'n drop? + virtual bool hasResizableColumns() const; + + //! This tells the table control which ordering mode should be used when + //! a column header is clicked. + /** \param columnIndex: The index of the column header. + \param state: If true, a message will be sent and you can order the table data as you whish.*/ + //! \param mode: One of the modes defined in EGUI_COLUMN_ORDERING + virtual void setColumnOrdering(uint32_t columnIndex, ColumnOrder mode); + + //! Returns which row is currently selected + virtual int getSelected() const; + + //! set wich row is currently selected + virtual void setSelected( int index ); + + virtual ScrollBar* getVerticalScrolBar(); + + virtual int getSelectedColumn() const; + + //! Returns amount of rows in the tabcontrol + virtual int getRowCount() const; + + //! adds a row to the table + /** \param rowIndex: zero based index of rows. The row will be + inserted at this position. If a row already exists + there, it will be placed after it. If the row is larger + than the actual number of rows by more than one, it + won't be created. Note that if you create a row that is + not at the end, there might be performance issues*/ + virtual uint32_t addRow(uint32_t rowIndex); + + //! Remove a row from the table + virtual void removeRow(uint32_t rowIndex); + + //! clear the table rows, but keep the columns intact + virtual void clearRows(); + + //! Swap two row positions. This is useful for a custom ordering algo. + virtual void swapRows(uint32_t rowIndexA, uint32_t rowIndexB); + + //! This tells the table to start ordering all the rows. You + //! need to explicitly tell the table to reorder the rows when + //! a new row is added or the cells data is changed. This makes + //! the system more flexible and doesn't make you pay the cost + //! of ordering when adding a lot of rows. + //! \param columnIndex: When set to -1 the active column is used. + virtual void orderRows(int columnIndex=-1, RowOrder mode=roNone); + + + //! Set the text of a cell + virtual void setCellText(uint32_t rowIndex, uint32_t columnIndex, const std::string& text); + + //! Set element of a cell + virtual void setCellElement(uint32_t rowIndex, uint32_t columnIndex, Widget* elm ); + + //! Remove element from cell + virtual void removeCellElement(uint32_t rowIndex, uint32_t columnIndex); + + //! Get element from cell + virtual Widget* getCellElement(uint32_t rowIndex, uint32_t columnIndex) const; + + //! Set the text of a cell, and set a color of this cell. + virtual void setCellText(uint32_t rowIndex, uint32_t columnIndex, const std::string& text, const Color& color); + + //! Set the data of a cell + //! data will not be serialized. + virtual void setCellDataptr(uint32_t rowIndex, uint32_t columnIndex, uintptr_t data); + + //! Set the color of a cell text + virtual void setCellTextColor(uint32_t rowIndex, uint32_t columnIndex, const Color& color); + + //! Get the text of a cell + virtual std::string getCellText(uint32_t rowIndex, uint32_t columnIndex ) const; + + //! Get the data of a cell + virtual uintptr_t getCellDataptr(uint32_t rowIndex, uint32_t columnIndex ) const; + + //! clears the table, deletes all items in the table + virtual void clear(); + + //! clears the contents int table, deletes all cell elements in the table + virtual void clearContent(); + + //! called if an event happened. + //virtual bool onEvent(const NEvent &event); + + //! draws the element and its children + virtual void draw(NVGcontext* ctx); + + //! Set flags, as defined in EGUI_TABLE_DRAW_FLAGS, which influence the layout + virtual void setDrawFlag( DrawFlag flag, bool enabled=true ); + + //! Get the flags, as defined in EGUI_TABLE_DRAW_FLAGS, which influence the layout + virtual bool isFlag( DrawFlag flag ) const; + + //! + virtual void setItemHeight(int height); + + //! + virtual void beforePaint(NVGcontext* ctx); + + bool mouseButtonEvent(const Vector2i &pp, int button, bool down, int modifiers) override; + bool mouseMotionEvent(const Vector2i &p, const Vector2i &rel, int button, int modifiers) override; + + //! Writes attributes of the object. + //! Implement this to expose the attributes of your scene node animator for + //! scripting languages, editors, debuggers or xml serialization purposes. + //virtual void save( core::VariantArray* out ) const; + + //! Reads attributes of the object. + //! Implement this to set the attributes of your scene node animator for + //! scripting languages, editors, debuggers or xml deserialization purposes. + //virtual void load( core::VariantArray* in ); + + virtual void removeChild(Widget* child); + +protected: + virtual void _refreshControls(); + virtual void _recalculateScrollBars(); + void _startEditCell(int col, int row); + void _finishEditCell(); + +private: + void _selectNew(int xpos, int ypos, bool lmb, bool onlyHover); + bool _selectColumnHeader(int xpos, int ypos); + bool _dragColumnStart(int xpos, int ypos); + bool _dragColumnUpdate(int xpos); + void _recalculateHeights(); + void _recalculateColumnsWidth(); + + int _getCurrentColumn(int xpos, int ypos ); + void _recalculateCells(); + + bool _clip; + bool _moveOverSelect; + bool _selecting; + int _currentResizedColumn; + int _resizeStart; + bool _resizableColumns; + + int _itemHeight; + int _overItemHeight; + int _totalItemHeight; + int _totalItemWidth; + int _selectedRow, _selectedColumn; + int _editedRow, _editedColumn; + int _cellHeightPadding; + int _cellWidthPadding; + int _activeTab; + RowOrder _currentOrdering; + + class Cell; + class Column; + class HidingElement; + struct Row { std::vector items; }; + + typedef std::vector Columns; + typedef std::vector Rows; + typedef std::bitset<16> DrawFlags; + + DrawFlags _drawflags; + Columns _columns; + Rows _rows; + std::string _font = "sans"; + + Widget* _header; + TextBox* _edit = nullptr; + Widget* _itemsArea; + ScrollBar* _verticalScrollBar; + ScrollBar* _horizontalScrollBar; + bool _needRefreshCellsGeometry; + uint32_t _cellLastTimeClick; + + int _vscrollsize = 0; + int _hscrollsize = 0; + + Cell* _getCell(int row, int col); +}; + +NAMESPACE_END(nanogui) + diff --git a/include/nanogui/textbox.h b/include/nanogui/textbox.h index 23b12c05a2..604dbe7150 100644 --- a/include/nanogui/textbox.h +++ b/include/nanogui/textbox.h @@ -81,6 +81,7 @@ class NANOGUI_EXPORT TextBox : public Widget { /// Sets the callback to execute when the value of this TextBox has changed. void setCallback(const std::function &callback) { mCallback = callback; } + void setComitCallback(const std::function &callback) { mComitCallback = callback; } virtual bool mouseButtonEvent(const Vector2i &p, int button, bool down, int modifiers) override; virtual bool mouseMotionEvent(const Vector2i &p, const Vector2i &rel, int button, int modifiers) override; @@ -121,6 +122,7 @@ class NANOGUI_EXPORT TextBox : public Widget { std::string mFormat; int mUnitsImage; std::function mCallback; + std::function mComitCallback; bool mValidFormat; std::string mValueTemp; std::string mPlaceholder; diff --git a/include/nanogui/widget.h b/include/nanogui/widget.h index d8ea3034e3..1cb7475147 100644 --- a/include/nanogui/widget.h +++ b/include/nanogui/widget.h @@ -57,8 +57,19 @@ class NANOGUI_EXPORT Widget : public Object { /// Return the position relative to the parent widget const Vector2i &position() const { return mPos; } + const int right() const { return mPos.x() + mSize.x(); } + const int left() const { return mPos.x(); } /// Set the position relative to the parent widget void setPosition(const Vector2i &pos) { mPos = pos; } + void setPosition(int x, int y) { mPos = Vector2i(x, y); } + + void setGeometry(const Vector4i &vec) { + setPosition(vec.x(), vec.y()); + setSize(vec.z() - vec.x(), vec.w() - vec.y()); + } + + bool sendChildToBack(Widget* child); + bool sendToBack(); /// Return the absolute position on screen Vector2i absolutePosition() const { @@ -66,6 +77,26 @@ class NANOGUI_EXPORT Widget : public Object { (parent()->absolutePosition() + mPos) : mPos; } + Vector4i absoluteRect() const { + Vector2i ap = absolutePosition(); + return Vector4i(ap.x(), ap.y(), ap.x() + width(), ap.y() + height()); + } + + Widget *findWidget(const std::string& id, bool inchildren = true); + Widget *findWidget(std::function cond, bool inchildren = true); + + Widget *findWidgetGlobal(const std::string& id); + Widget *findWidgetGlobal(std::function cond); + + virtual std::string wtypename() const; + + template + RetClass *findWidgetGlobal(const std::string& id) + { + Widget* f = findWidgetGlobal(id); + return f ? f->cast() : nullptr; + } + /// Return the size of the widget const Vector2i &size() const { return mSize; } /// set the size of the widget diff --git a/src/common.cpp b/src/common.cpp index 53bc946c19..11a2061cda 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -29,6 +29,11 @@ NAMESPACE_BEGIN(nanogui) +bool isPointInsideRect(const Vector2i& p, const Vector4i& r) +{ + return (p.x() >= r.x() && p.y() >= r.y() && p.x() <= r.z() && p.y() <= r.w()); +} + extern std::map __nanogui_screens; #if defined(__APPLE__) diff --git a/src/label.cpp b/src/label.cpp index 98158dfa51..5d6fd39550 100644 --- a/src/label.cpp +++ b/src/label.cpp @@ -34,22 +34,27 @@ void Label::setTheme(Theme *theme) { } Vector2i Label::preferredSize(NVGcontext *ctx) const { - if (mCaption == "") - return Vector2i::Zero(); - nvgFontFace(ctx, mFont.c_str()); - nvgFontSize(ctx, fontSize()); - if (mFixedSize.x() > 0) { - float bounds[4]; - nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); - nvgTextBoxBounds(ctx, mPos.x(), mPos.y(), mFixedSize.x(), mCaption.c_str(), nullptr, bounds); - return Vector2i(mFixedSize.x(), bounds[3] - bounds[1]); - } else { - nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); - return Vector2i( - nvgTextBounds(ctx, 0, 0, mCaption.c_str(), nullptr, nullptr) + 2, - fontSize() - ); - } + if (mCaption == "") + { + if (mFixedSize.x() > 0 || mFixedSize.y() > 0) + return mFixedSize; + + return Vector2i::Zero(); + } + nvgFontFace(ctx, mFont.c_str()); + nvgFontSize(ctx, fontSize()); + if (mFixedSize.x() > 0) { + float bounds[4]; + nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); + nvgTextBoxBounds(ctx, mPos.x(), mPos.y(), mFixedSize.x(), mCaption.c_str(), nullptr, bounds); + return Vector2i(mFixedSize.x(), bounds[3] - bounds[1]); + } else { + nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); + return Vector2i( + nvgTextBounds(ctx, 0, 0, mCaption.c_str(), nullptr, nullptr) + 2, + fontSize() + ); + } } void Label::draw(NVGcontext *ctx) { @@ -58,10 +63,10 @@ void Label::draw(NVGcontext *ctx) { nvgFontSize(ctx, fontSize()); nvgFillColor(ctx, mColor); if (mFixedSize.x() > 0) { - nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); + nvgTextAlign(ctx, (1<. +The widget drawing code is based on the NanoVG demo application +by Mikko Mononen. +Table Widget was developed by dalerank + +All rights reserved. Use of this source code is governed by a +BSD-style license that can be found in the LICENSE.txt file. +*/ + + +#include +#include +#include +#include +#include +#include +#include + +#define ARROW_PAD 15 +#define DEFAULT_SCROLLBAR_SIZE 16 + +NAMESPACE_BEGIN(nanogui) + +const std::string TableOrderingModeNames[] = +{ + "none", + "ascending", + "descending", + "" +}; + +const std::string TableColumnOrderingNames[] = +{ + "none", + "custom", + "ascend", + "descend", + "ascend_descend", + "" +}; + +class Table::Cell : public Label +{ +public: + Cell(Widget* parent, const Vector4i& r) : Label(parent,"") + { + setPosition(r.x(), r.y()); + setSize(r.z() - r.x(), r.w() - r.y()); + + element = nullptr; + data = 0; + } + + void draw(NVGcontext* ctx) override + { + if (inEditMode) + Widget::draw(ctx); + else + Label::draw(ctx); + } + + Widget* element; + bool inEditMode = false; + uintptr_t data; +}; + +class Table::HidingElement : public Widget +{ +public: + HidingElement( Widget* parent, const Vector4i& r ) + : Widget(parent) + { + setPosition(r.x(), r.y()); + setSize(r.z() - r.x(), r.w() - r.y()); + } + + bool isPointInside(const Vector2i& point) const + { + return false; + } +}; + +class Table::Column : public Label +{ +public: + Column( Widget* parent, const Vector4i& r ) + : Label( parent, "" ), orderingMode(Table::ColumnOrder::coNone) + { + setPosition(r.x(), r.y()); + setSize(r.z() - r.x(), r.w() - r.y()); + setTextAlign(TextAlign::alCenter); + } + + Table::ColumnOrder orderingMode; + + bool isPointInside(const Vector2i& point) const + { + return false; + } +}; + +Table::Table( Widget* parent, const std::string& id, + const Vector4i& r, bool clip, + bool drawBack, bool moveOverSelect) +: Widget(parent), + _clip(clip), _moveOverSelect(moveOverSelect), + _selecting(false), _currentResizedColumn(-1), _resizeStart(0), _resizableColumns(true), + _itemHeight(0), _overItemHeight(0), _totalItemHeight(0), _totalItemWidth(0), + _selectedRow(-1), _selectedColumn(-1), + _editedRow(-1), _editedColumn(-1), + _cellHeightPadding(2), _cellWidthPadding(5), _activeTab(-1), + _currentOrdering( RowOrder::roNone ) +{ + setPosition(r.x(), r.y()); + setSize(r.z() - r.x(), r.w() - r.y()); + + setDrawFlag( drawBorder ); + setDrawFlag( drawRows ); + setDrawFlag( drawColumns ); + setDrawFlag( drawActiveRow ); + + _cellLastTimeClick = 0; + _header = new HidingElement( this, Vector4i( 0, 0, width(), DEFAULT_SCROLLBAR_SIZE ) ); + //_header->setAlignment( alignUpperLeft, alignLowerRight, alignUpperLeft, alignUpperLeft ); + _header->setSubElement( true ); + + Widget* iaparent = new HidingElement(this, Vector4i(0, DEFAULT_SCROLLBAR_SIZE, width(), height())); + _itemsArea = new HidingElement(iaparent, Vector4i( 0, 0, 1, 1 ) ); + //_itemsArea->setAlignment( alignUpperLeft, alignLowerRight, alignUpperLeft, alignLowerRight ); + _itemsArea->setSubElement( true ); + + _verticalScrollBar = new ScrollBar(this, ScrollBar::VerticalRight); + //_verticalScrollBar->grab(); + //_verticalScrollBar->setNotClipped(false); + _verticalScrollBar->setSubElement(true); + //_verticalScrollBar->setVisibleFilledArea( false ); + //_verticalScrollBar->setAlignment( alignLowerRight, alignLowerRight, alignUpperLeft, alignLowerRight ); + + _horizontalScrollBar = new ScrollBar( this, ScrollBar::HorizontalBottom ); + //_horizontalScrollBar->grab(); + //_horizontalScrollBar->setNotClipped(false); + _horizontalScrollBar->setSubElement(true); + //_horizontalScrollBar->setVisibleFilledArea( false ); + //_horizontalScrollBar->setAlignment( alignUpperLeft, alignLowerRight, alignLowerRight, alignLowerRight ); + + _recalculateHeights(); + _refreshControls(); +} + +Table::~Table() {} + +void Table::addColumn(const std::string& caption, uint32_t columnIndex) +{ + Column* columnHeader = new Column( _header, Vector4i( 0, 0, 1, 1 ) ); + columnHeader->setSubElement( true ); + columnHeader->setCaption( caption ); + columnHeader->setPosition(0, 0); + float tw = nvgTextBounds(screen()->nvgContext(), 0, 0, caption.c_str(), nullptr, nullptr); + columnHeader->setSize({ tw + (_cellWidthPadding * 2) + ARROW_PAD, _header->height() }); + columnHeader->setFixedSize({ tw + (_cellWidthPadding * 2) + ARROW_PAD, _header->height() }); + + columnHeader->orderingMode = ColumnOrder::coNone; + + if ( columnIndex >= _columns.size() ) + { + _columns.push_back( columnHeader ); + for (Row& row: _rows) + row.items.push_back( new Cell( _itemsArea, Vector4i( 0, 0, 1, 1 ) ) ); + } + else + { + _columns.insert(_columns.begin() + columnIndex, columnHeader); + for (Row& row: _rows) + row.items.insert(row.items.begin() + columnIndex, new Cell( _itemsArea, Vector4i(0, 0, 1, 1) )); + } + + if (_activeTab == -1) + _activeTab = 0; + + _recalculateColumnsWidth(); + _recalculateCells(); + _recalculateScrollBars(); +} + +//! remove a column from the table +void Table::removeColumn(uint32_t columnIndex) +{ + if ( columnIndex < _columns.size() ) + { + _columns.erase( _columns.begin() + columnIndex ); + for (Row& row: _rows) + { + row.items[ columnIndex ]->remove(); + row.items.erase(row.items.begin() + columnIndex ); + } + } + + if ( (int)columnIndex <= _activeTab ) + _activeTab = _columns.size() ? 0 : -1; + + _recalculateColumnsWidth(); +} + + +int Table::getColumnCount() const { return _columns.size(); } +int Table::getRowCount() const { return _rows.size(); } + +bool Table::setActiveColumn(int idx, bool doOrder ) +{ + if (idx < 0 || idx >= (int)_columns.size()) + return false; + + bool changed = (_activeTab != idx); + + _activeTab = idx; + if ( _activeTab < 0 ) + return false; + + if ( doOrder ) + { + switch ( _columns[idx]->orderingMode ) + { + case ColumnOrder::coNone: + _currentOrdering = RowOrder::roNone; + break; + + case ColumnOrder::coCustom: + _currentOrdering = RowOrder::roNone; + break; + + case ColumnOrder::coAscending: + _currentOrdering = RowOrder::roAscending; + break; + + case ColumnOrder::coDescending: + _currentOrdering = RowOrder::roDescending; + break; + + case ColumnOrder::coAscendingDescending: + _currentOrdering = (ColumnOrder::coAscending == _currentOrdering ? RowOrder::roDescending : RowOrder::roAscending); + break; + default: + _currentOrdering = RowOrder::roNone; + } + + orderRows( getActiveColumn(), _currentOrdering ); + } + + return true; +} + +int Table::getActiveColumn() const { return _activeTab; } +Table::RowOrder Table::getActiveColumnOrdering() const {return _currentOrdering;} + +void Table::setColumnWidth(uint32_t columnIndex, uint32_t width) +{ + if ( columnIndex < _columns.size() ) + { + int tw = nvgTextBounds(screen()->nvgContext(), 0, 0, _columns[columnIndex]->caption().c_str(), nullptr, nullptr ); + const uint32_t MIN_WIDTH = tw + (_cellWidthPadding * 2); + if ( width < MIN_WIDTH ) + width = MIN_WIDTH; + + _columns[columnIndex]->setWidth(width); + _columns[columnIndex]->setFixedWidth(width); + } + + _recalculateColumnsWidth(); + _recalculateCells(); + _recalculateScrollBars(); +} + +//! Get the width of a column +uint32_t Table::getColumnWidth(uint32_t columnIndex) const +{ + if ( columnIndex >= _columns.size() ) + return 0; + + return _columns[columnIndex]->width(); +} + +void Table::setResizableColumns(bool resizable) { _resizableColumns = resizable; } +bool Table::hasResizableColumns() const { return _resizableColumns; } + +uint32_t Table::addRow(uint32_t rowIndex) +{ + if ( rowIndex > _rows.size() ) + rowIndex = _rows.size(); + + Row row; + + if ( rowIndex == _rows.size() ) + _rows.push_back( row ); + else + _rows.insert(_rows.begin() + rowIndex, row); + + _rows[rowIndex].items.resize( _columns.size() ); + + for ( uint32_t i = 0 ; i < _columns.size() ; ++i ) + _rows[rowIndex].items[ i ] = nullptr; + + for ( uint32_t i = 0 ; i < _columns.size() ; ++i ) + _rows[rowIndex].items[ i ] = new Cell( _itemsArea, Vector4i( 0, 0, 1, 1 ) ); + + _recalculateHeights(); + _recalculateCells(); + _recalculateScrollBars(); + return rowIndex; +} + + +void Table::removeRow(uint32_t rowIndex) +{ + if ( rowIndex > _rows.size() ) + return; + + for( uint32_t colNum=0; colNum < _columns.size(); colNum++ ) + removeCellElement( rowIndex, colNum ); + + _rows.erase(_rows.begin() + rowIndex ); + + if ( !(_selectedRow < int(_rows.size())) ) + _selectedRow = _rows.size() - 1; + + _recalculateHeights(); + _recalculateScrollBars(); +} + +//! adds an list item, returns id of item +void Table::setCellText(uint32_t rowIndex, uint32_t columnIndex, const std::string& text) +{ + if ( rowIndex < _rows.size() && columnIndex < _columns.size() ) + { + _rows[rowIndex].items[columnIndex]->setCaption( text ); + } +} + +void Table::setCellText(uint32_t rowIndex, uint32_t columnIndex, const std::string& text, const Color& color) +{ + if ( rowIndex < _rows.size() && columnIndex < _columns.size() ) + { + _rows[rowIndex].items[columnIndex]->setCaption( text ); + _rows[rowIndex].items[columnIndex]->setColor( color ); + } +} + +void Table::setCellTextColor(uint32_t rowIndex, uint32_t columnIndex, const Color& color) +{ + if ( rowIndex < _rows.size() && columnIndex < _columns.size() ) + _rows[rowIndex].items[columnIndex]->setColor( color ); +} + +void Table::setCellDataptr(uint32_t rowIndex, uint32_t columnIndex, uintptr_t data) +{ + if ( rowIndex < _rows.size() && columnIndex < _columns.size() ) + _rows[rowIndex].items[columnIndex]->data = data; +} + +std::string Table::getCellText(uint32_t rowIndex, uint32_t columnIndex ) const +{ + if ( rowIndex < _rows.size() && columnIndex < _columns.size() ) + return _rows[rowIndex].items[columnIndex]->caption(); + + return ""; +} + +uintptr_t Table::getCellDataptr(uint32_t rowIndex, uint32_t columnIndex ) const +{ + if ( rowIndex < _rows.size() && columnIndex < _columns.size() ) + { + return _rows[rowIndex].items[columnIndex]->data; + } + + return 0; +} + +void Table::clear() +{ + _selectedRow = -1; + clearRows(); + + for(auto& col: _columns) + col->remove(); + + _rows.clear(); + _columns.clear(); + + if (_verticalScrollBar) _verticalScrollBar->setScroll(0); + if ( _horizontalScrollBar ) _horizontalScrollBar->setScroll(0); + + _recalculateHeights(); + _recalculateColumnsWidth(); +} + +void Table::clearContent() +{ + for( uint32_t rowNum=0; rowNum < _rows.size(); rowNum++ ) + for( uint32_t colNum=0; colNum < _columns.size(); colNum++ ) + removeCellElement( rowNum, colNum ); + + _recalculateCells(); +} + +void Table::clearRows() +{ + _selectedRow = -1; + + const auto& tableAreaChilds = _itemsArea->children(); + for(auto& w: tableAreaChilds ) + w->remove(); + + _rows.clear(); + + if (_verticalScrollBar) _verticalScrollBar->setScroll(0); + + _recalculateHeights(); +} + +int Table::getSelected() const { return _selectedRow; } + +//! set wich row is currently selected +void Table::setSelected( int index ) +{ + _selectedRow = -1; + if ( index >= 0 && index < (int) _rows.size() ) + _selectedRow = index; +} + +void Table::_recalculateColumnsWidth() +{ + _totalItemWidth=0; + for (auto& col: _columns) + { + col->setPosition(_totalItemWidth, col->position().y()); + _totalItemWidth += col->width(); + } + + _header->setWidth(_totalItemWidth); + _header->setFixedWidth(_totalItemWidth); +} + +void Table::_recalculateHeights() +{ + _totalItemHeight = 0; + float bounds[4] = { 0 }; + nvgTextBounds(screen()->nvgContext(), 0, 0, "A", nullptr, bounds); + int fontH = bounds[3] - bounds[1] + (_cellHeightPadding * 2); + _itemHeight = _overItemHeight == 0 ? fontH : _overItemHeight; + + _totalItemHeight = _itemHeight * _rows.size(); // header is not counted, because we only want items +} + + +// automatic enabled/disabling and resizing of scrollbars +void Table::_recalculateScrollBars() +{ + if ( !_horizontalScrollBar || !_verticalScrollBar ) + return; + + bool wasHorizontalScrollBarVisible = _horizontalScrollBar->visible(); + bool wasVerticalScrollBarVisible = _verticalScrollBar->visible(); + _horizontalScrollBar->setVisible(false); + _verticalScrollBar->setVisible(false); + + // CAREFUL: near identical calculations for tableRect and clientClip are also done in draw + // area of table used for drawing without scrollbars + Vector2i taPos = _itemsArea->position(); + Vector4i tableRect(taPos.x(), taPos.y(), _itemsArea->width(), _itemsArea->height()); + + // needs horizontal scroll be visible? + if( _totalItemWidth > _itemsArea->width() ) + { + tableRect.w() -= _horizontalScrollBar->height(); + _horizontalScrollBar->setVisible( true ); + _hscrollsize = std::max(0, _totalItemWidth - tableRect.z()); + } + + // needs vertical scroll be visible? + if( _totalItemHeight > tableRect.w() ) + { + tableRect.z() -= _verticalScrollBar->width(); + _verticalScrollBar->setVisible( true ); + _vscrollsize = std::max(0, _totalItemHeight - tableRect.w() + 2 * _verticalScrollBar->width()); + + // check horizontal again because we have now smaller clientClip + if ( !_horizontalScrollBar->visible() ) + { + if( _totalItemWidth > tableRect.z() ) + { + tableRect.w() -= _horizontalScrollBar->height(); + _horizontalScrollBar->setVisible(true); + _hscrollsize = std::max(0, _totalItemWidth - tableRect.z()); + } + } + } + + // find the correct size for the vertical scrollbar + if ( _verticalScrollBar->visible() ) + { + if (!wasVerticalScrollBarVisible ) + _verticalScrollBar->setScroll(0); + + int offset = _horizontalScrollBar->visible() + ? _horizontalScrollBar->height() + : 0; + + _verticalScrollBar->setHeight( height()-(1 + offset) ); + _verticalScrollBar->setPosition( width() - _verticalScrollBar->width(), _verticalScrollBar->position().y() ); + } + + // find the correct size for the horizontal scrollbar + if ( _horizontalScrollBar->visible() ) + { + if ( !wasHorizontalScrollBarVisible ) + _horizontalScrollBar->setScroll(0); + + int offset = _verticalScrollBar->visible() + ? _verticalScrollBar->width() + : 0; + _horizontalScrollBar->setWidth( width()-(1+offset) ); + _horizontalScrollBar->setPosition(_horizontalScrollBar->position().x(), height() - _horizontalScrollBar->height() ); + } + + _itemsArea->setWidth( width() - ( _verticalScrollBar->visible() ? 1 : 0 ) * _verticalScrollBar->width() ); + _itemsArea->setHeight( height() - _header->height() + - ( _horizontalScrollBar->visible() ? 1 : 0 ) * _horizontalScrollBar->height() ); + + _verticalScrollBar->bringToFront(); + _horizontalScrollBar->bringToFront(); +} + +void Table::_refreshControls() +{ + //updateAbsolutePosition(); + + _recalculateColumnsWidth(); + _recalculateHeights(); + _recalculateScrollBars(); + _recalculateCells(); +} + +void Table::_recalculateCells() +{ + int yPos = 0; + int xPos = 0; + for (auto& row: _rows) + { + auto cit = _columns.begin(); + xPos = 0; + for (int index=0; cit != _columns.end(); cit++, index++ ) + { + row.items[index]->setPosition((*cit)->position().x(), yPos); + row.items[index]->setSize({ (*cit)->width(), _itemHeight }); + row.items[index]->setFixedSize({ (*cit)->width(), _itemHeight }); + row.items[index]->sendToBack(); + xPos += (*cit)->width(); + } + + yPos += _itemHeight; + } + + _itemsArea->setFixedSize({ xPos, yPos }); +} + +bool Table::scrollEvent(const Vector2i &p, const Vector2f &rel) +{ + int current = _totalItemHeight - _itemsArea->height(); + //float d = (rel.y() < 0 ? -1 : 1) * _itemHeight; + _verticalScrollBar->setScroll(_verticalScrollBar->scroll() + (rel.y() < 0 ? -1 : 1)*0.05); + return true; +} + +bool Table::mouseButtonEvent(const Vector2i &pp, int button, bool down, int modifiers) +{ + if (enabled()) + { + if (button == GLFW_MOUSE_BUTTON_LEFT && down) + { + if (_dragColumnStart(pp.x(), pp.y())) + { + requestFocus(); + return true; + } + + if (_selectColumnHeader(pp.x(), pp.y())) + return true; + + _selecting = true; + requestFocus(); + return true; + } + else if (button == GLFW_MOUSE_BUTTON_LEFT && !down) + { + _currentResizedColumn = -1; + _selecting = false; + _selectNew(pp.x(), pp.y(), true, false); + } + } + + return Widget::mouseButtonEvent(pp, button, down, modifiers); +} + +bool Table::mouseMotionEvent(const Vector2i &p, const Vector2i &rel, int button, int modifiers) +{ + if (_currentResizedColumn >= 0) + { + if (_dragColumnUpdate(p.x())) + { + return true; + } + } + if (_selecting || _moveOverSelect) + { + if (isPointInsideRect(p, absoluteRect())) + { + _selectNew(p.x(), p.y(), false, true); + return true; + } + } + + return Widget::mouseMotionEvent(p, rel, button, modifiers); +} + +void Table::setColumnOrdering(uint32_t columnIndex, ColumnOrder mode) +{ + if ( columnIndex < _columns.size() ) + _columns[columnIndex]->orderingMode = mode; +} + +void Table::swapRows(uint32_t rowIndexA, uint32_t rowIndexB) +{ + if ( rowIndexA >= _rows.size() ) + return; + + if ( rowIndexB >= _rows.size() ) + return; + + Row swap = _rows[rowIndexA]; + _rows[rowIndexA] = _rows[rowIndexB]; + _rows[rowIndexB] = swap; + + if ( _selectedRow == int(rowIndexA) ) + _selectedRow = rowIndexB; + else if( _selectedRow == int(rowIndexB) ) + _selectedRow = rowIndexA; +} + +bool Table::_dragColumnStart(int xpos, int ypos) +{ + if ( !_resizableColumns ) + return false; + + if ( ypos > ( absolutePosition().y() + _itemHeight ) ) + return false; + + const int CLICK_AREA = 12; // to left and right of line which can be dragged + int pos = absolutePosition().x() + 1; + + if ( _horizontalScrollBar && _horizontalScrollBar->visible() ) + pos -= (_horizontalScrollBar->scroll() * _hscrollsize); + + pos += _totalItemWidth; + + // have to search from the right as otherwise lines could no longer be resized when a column width is 0 + for ( int i = (int)_columns.size()-1; i >= 0 ; --i ) + { + uint32_t colWidth = _columns[i]->width(); + + if ( xpos >= (pos - CLICK_AREA) && xpos < ( pos + CLICK_AREA ) ) + { + _currentResizedColumn = i; + _resizeStart = xpos; + return true; + } + + pos -= colWidth; + } + + return false; +} + +bool Table::_dragColumnUpdate(int xpos) +{ + if ( !_resizableColumns || _currentResizedColumn < 0 || _currentResizedColumn >= int(_columns.size()) ) + { + _currentResizedColumn = -1; + return false; + } + + int ww = int(_columns[_currentResizedColumn]->width()) + (xpos-_resizeStart); + if ( ww < 0 ) + ww = 0; + + setColumnWidth(_currentResizedColumn, uint32_t(ww)); + _resizeStart = xpos; + + _recalculateCells(); + return false; +} + +int Table::_getCurrentColumn( int xpos, int ypos ) +{ + int pos = absolutePosition().x() + 1; + + if ( _horizontalScrollBar && _horizontalScrollBar->visible() ) + pos -= (_horizontalScrollBar->scroll() * _hscrollsize); + + for ( uint32_t i = 0 ; i < _columns.size() ; ++i ) + { + uint32_t colWidth = _columns[i]->width(); + + if ( xpos >= pos && xpos < ( pos + int(colWidth) ) ) + return i; + + pos += colWidth; + } + + return -1; +} + +bool Table::_selectColumnHeader(int xpos, int ypos) +{ + if ( ypos > ( absolutePosition().y() + _itemHeight ) ) + return false; + + _selectedColumn = _getCurrentColumn( xpos, ypos ); + if( _selectedColumn >= 0 ) + { + setActiveColumn( _selectedColumn, true ); + return true; + } + + return false; +} + +bool _nstrCmp(const std::string& s1, const std::string& s2) +{ + return strcmp(s1.c_str(), s2.c_str()); +} + +void Table::orderRows(int columnIndex, RowOrder mode) +{ + Row swap; + + if ( columnIndex == -1 ) + columnIndex = getActiveColumn(); + if ( columnIndex < 0 ) + return; + + if ( mode == RowOrder::roAscending ) + { + for ( int i = 0 ; i < int(_rows.size()) - 1 ; ++i ) + { + for ( int j = 0 ; j < int(_rows.size()) - i - 1 ; ++j ) + { + if (_nstrCmp(_rows[j+1].items[columnIndex]->caption(), _rows[j].items[columnIndex]->caption()) ) + { + swap = _rows[j]; + _rows[j] = _rows[j+1]; + _rows[j+1] = swap; + + if ( _selectedRow == j ) + _selectedRow = j+1; + else if( _selectedRow == j+1 ) + _selectedRow = j; + } + } + } + } + else if ( mode == RowOrder::roDescending ) + { + for ( int i = 0 ; i < int(_rows.size()) - 1 ; ++i ) + { + for ( int j = 0 ; j < int(_rows.size()) - i - 1 ; ++j ) + { + if ( _nstrCmp(_rows[j].items[columnIndex]->caption(), _rows[j+1].items[columnIndex]->caption()) ) + { + swap = _rows[j]; + _rows[j] = _rows[j+1]; + _rows[j+1] = swap; + + if ( _selectedRow == j ) + _selectedRow = j+1; + else if( _selectedRow == j+1 ) + _selectedRow = j; + } + } + } + } +} + +void Table::_selectNew( int xpos, int ypos, bool lmb, bool onlyHover) +{ + int oldSelectedRow = _selectedRow; + int oldSelectedColumn = _selectedColumn; + + if ( ypos < ( absolutePosition().y() + _itemHeight ) ) + return; + + // find new selected item. + if (_itemHeight!=0) + _selectedRow = ((ypos - absolutePosition().y() - _itemHeight - 1) + (_verticalScrollBar->scroll() * _vscrollsize)) / _itemHeight; + + _selectedColumn = _getCurrentColumn( xpos, ypos ); + + if (_selectedRow >= (int)_rows.size()) + _selectedRow = _rows.size() - 1; + else if (_selectedRow<0) + _selectedRow = 0; + + // post the news + if( !onlyHover ) + { + bool selectedAgain = (_selectedRow != oldSelectedRow || _selectedColumn != oldSelectedColumn ) + ? false + : true; + + if( lmb && selectedAgain && ( glfwGetTime() - _cellLastTimeClick ) < 200 ) + { + _finishEditCell(); + _startEditCell(_selectedColumn, _selectedRow); + } + + _cellLastTimeClick = glfwGetTime(); + } +} + +Table::Cell* Table::_getCell(int row, int col) +{ + if (row < _rows.size() && col < _columns.size()) + return _rows[row].items[col]; + + return nullptr; +} + +void Table::_startEditCell(int col, int row) +{ + if (_edit) + return; + + Cell* cell = _getCell(row, col); + if (cell) + { + cell->inEditMode = true; + _edit = new TextBox(cell, getCellText(row, col)); + _edit->setSize(cell->size()); + _edit->setFixedSize(cell->size()); + _edit->setEditable(true); + _edit->requestFocus(); + _edit->setComitCallback([cell, this](Widget* w) { + if (TextBox* ed = dynamic_cast(w)) + { + cell->setCaption(ed->value()); + cell->requestFocus(); + cell->inEditMode = false; + } + _finishEditCell(); + }); + } +} + +void Table::_finishEditCell() +{ + if (!_edit) + return; + _edit->remove(); + _edit = nullptr; +} + +void Table::beforePaint(NVGcontext* ctx) +{ + if( _needRefreshCellsGeometry ) + { + _recalculateCells(); + _needRefreshCellsGeometry = false; + } +} + +//! draws the element and its children +void Table::draw(NVGcontext* ctx) +{ + if ( !visible() ) + return; + + if ( _font.empty() ) + return; + + int yOffset = _verticalScrollBar->scroll() * _vscrollsize; + int xOffset = _horizontalScrollBar->scroll() * _hscrollsize; + + _header->setPosition(-xOffset, _header->position().y()); + _itemsArea->setPosition(-xOffset, 0); + + if (_drawflags.test(drawRows)) + { + nvgBeginPath(ctx); + nvgStrokeColor(ctx, Color(0xc0, 0x80)); + + for (uint32_t i = 0; i < _rows.size(); ++i) + { + Vector4i r = _rows[i].items.front()->absoluteRect(); + //r.y() = r.w() - 1; + r.z() = position().x() + width(); + nvgRect(ctx, r.x(), r.y(), r.z() - r.x(), r.w() - r.y()); + } + nvgStroke(ctx); + } + + // draw selected row background highlighted + if (_drawflags.test(drawRows)) + { + nvgBeginPath(ctx); + nvgStrokeColor(ctx, Color(0xff, 0x0, 0x0, 0x80)); + if (_selectedRow >= 0 && _selectedRow < _rows.size() + && _selectedColumn >= 0 && _selectedColumn < _columns.size() + && _drawflags.test(drawActiveRow)) + { + Vector4i r(_rows[_selectedRow].items[_selectedColumn]->absoluteRect()); + nvgRect(ctx, r.x(), r.y(), r.z() - r.x(), r.w() - r.y()); + } + nvgStroke(ctx); + } + + if ( _drawflags.test( drawColumns ) ) + { + nvgBeginPath(ctx); + nvgStrokeColor(ctx, Color(0xc0, 0x80)); + + for (auto& c: _columns) + { + Vector4i ar = c->absoluteRect(); + Vector4i r( ar.x(), ar.y(), ar.z(), ar.y() + height()); + nvgRect(ctx, r.x(), r.y(), r.z() - r.x(), r.w() - r.y()); + } + + nvgStroke(ctx); + } + + Widget::draw(ctx); + + // draw background for whole element + if (_drawflags.test(drawBorder)) + { + nvgBeginPath(ctx); + nvgRect(ctx, mPos.x(), mPos.y(), width(), height()); + nvgStrokeColor(ctx, nvgRGBA(0, 0, 0, 255)); + nvgStroke(ctx); + } +} + +void Table::setDrawFlag( DrawFlag flag, bool enabled ) { _drawflags.set( flag, enabled ); } +bool Table::isFlag( DrawFlag flag ) const { return _drawflags.test( (int)flag ); } + +//! Writes attributes of the element. +/*void Table::save( core::VariantArray* out ) const +{ +}*/ + +//! Reads attributes of the element +/*void Table::load( core::VariantArray* in ) +{ +}*/ + +void Table::setItemHeight( int height ) +{ + _overItemHeight = height; + float bounds[4] = { 0 }; + nvgTextBounds(screen()->nvgContext(), 0, 0, "A", nullptr, bounds); + int hh = bounds[3] - bounds[1]; + _itemHeight = _overItemHeight == 0 ? hh + (_cellHeightPadding * 2) : _overItemHeight; +} + +int Table::getSelectedColumn() const { return _selectedColumn; } + +void Table::removeChild(Widget* child) +{ + for ( uint32_t rowIndex = 0 ; rowIndex < _rows.size() ; ++rowIndex ) + for ( uint32_t columnIndex = 0 ; columnIndex < _columns.size() ; ++columnIndex ) + { + Cell* cell = _rows[rowIndex].items[columnIndex]; + if( cell && cell->element == child ) + { + cell->element = nullptr; + break; + } + } + + Widget::removeChild( child ); +} + +void Table::setCellElement( uint32_t rowIndex, uint32_t columnIndex, Widget* elm ) +{ + if ( rowIndex < _rows.size() && columnIndex < _columns.size() ) + { + Cell* cell = _rows[rowIndex].items[columnIndex]; + + if( elm != getCellElement( rowIndex, columnIndex ) ) + { + removeCellElement( rowIndex, columnIndex ); + + if( elm ) + { + cell->addChild( elm ); + cell->element = elm; + } + } + } + + _verticalScrollBar->bringToFront(); + _horizontalScrollBar->bringToFront(); +} + +void Table::removeCellElement( uint32_t rowIndex, uint32_t columnIndex ) +{ + if ( rowIndex < _rows.size() && columnIndex < _columns.size() ) + { + Cell* cell = _rows[rowIndex].items[columnIndex]; + + if( cell->element ) + cell->element->remove(); + + cell->element = nullptr; + } +} + +Widget* Table::getCellElement( uint32_t rowIndex, uint32_t columnIndex ) const +{ + if ( rowIndex < _rows.size() && columnIndex < _columns.size() ) + { + return _rows[rowIndex].items[columnIndex]->element; + } + + return NULL; +} + +ScrollBar* Table::getVerticalScrolBar() { return _verticalScrollBar; } + +NAMESPACE_END(nanogui) diff --git a/src/textbox.cpp b/src/textbox.cpp index c3a431889c..bd2593610b 100644 --- a/src/textbox.cpp +++ b/src/textbox.cpp @@ -456,6 +456,11 @@ bool TextBox::keyboardEvent(int key, int /* scancode */, int action, int modifie } else if (key == GLFW_KEY_ENTER) { if (!mCommitted) focusEvent(false); + if (mComitCallback) + { + mComitCallback(this); + return true; + } } else if (key == GLFW_KEY_A && modifiers == SYSTEM_COMMAND_MOD) { mCursorPos = (int) mValueTemp.length(); mSelectionPos = 0; diff --git a/src/widget.cpp b/src/widget.cpp index 03af6b17ed..53248bbf33 100644 --- a/src/widget.cpp +++ b/src/widget.cpp @@ -139,14 +139,105 @@ bool Widget::keyboardCharacterEvent(unsigned int) { return false; } +Widget* Widget::findWidget(std::function cond, bool inchildren) +{ + if (cond(this)) + return this; + + if (inchildren) + { + for (auto* child : mChildren) + { + Widget* w = child->findWidget(cond, inchildren); + if (w) + return w; + } + } + + return nullptr; +} + +Widget* Widget::findWidget(const std::string& id, bool inchildren) +{ + return findWidget([id](Widget* w) -> bool { return w->id() == id; }, inchildren); +} + +Widget *Widget::findWidgetGlobal(const std::string& id) +{ + return screen()->findWidget(id, true); +} + +Widget *Widget::findWidgetGlobal(std::function cond) +{ + return screen()->findWidget(cond); +} + void Widget::addChild(int index, Widget * widget) { assert(index <= childCount()); + Widget* prevparent = widget->parent(); + mChildren.insert(mChildren.begin() + index, widget); widget->incRef(); widget->setParent(this); widget->setTheme(mTheme); + + if (prevparent && prevparent != this) + prevparent->removeChild(widget); +} + +std::string Widget::wtypename() const { return "widget"; } + +bool Widget::bringToFront() +{ + if (parent()) + return parent()->bringChildToFront(this); + + return false; } +bool Widget::sendChildToBack(Widget* child) +{ + auto it = mChildren.begin(); + if (child == (*it)) // already there + return true; + for (; it != mChildren.end(); ++it) + { + if (child == (*it)) + { + mChildren.erase(it); + mChildren.insert(mChildren.begin(), child); + return true; + } + } + + return false; +} + +bool Widget::sendToBack() +{ + if (parent()) + return parent()->sendChildToBack(this); + + return false; +} + +bool Widget::bringChildToFront(Widget* element) +{ + auto it = mChildren.begin(); + for (; it != mChildren.end(); ++it) + { + if (element == (*it)) + { + mChildren.erase(it); + mChildren.push_back(element); + return true; + } + } + + return false; +} + + void Widget::addChild(Widget * widget) { addChild(childCount(), widget); } @@ -156,6 +247,12 @@ void Widget::removeChild(const Widget *widget) { widget->decRef(); } +void Widget::remove() +{ + if (parent()) + parent()->removeChild(this); +} + void Widget::removeChild(int index) { Widget *widget = mChildren[index]; mChildren.erase(mChildren.begin() + index);