diff --git a/CHANGELOG.md b/CHANGELOG.md index 975e61c..c77dd55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # v0.16.0 * Can use ListAdapter as "content" property value of ListLayout * The IsListItemEnabled method of the ListAdapter interface has been made optional +* Can use GridAdapter as "content" property value of GridLayout * Bug fixing # v0.15.0 diff --git a/README-ru.md b/README-ru.md index a4635a1..6f922f5 100644 --- a/README-ru.md +++ b/README-ru.md @@ -2344,13 +2344,13 @@ ListAdapter используется для создания дочерних Vi Соответственно функции этого интерфейса должны возвращать количество элементов и View i-го элемента. ListAdapter создает дочерние View в момент установки свойства "content". -Для пересоздания дочерних элементов ListLayout имеет свойство UpdateContent(). +Для пересоздания дочерних элементов ListLayout имеет метод UpdateContent(). Данный метод удаляет все дочерние View и создает их заново используя ListAdapter. Внимание! При вызове метода UpdateContent() данные из старых View не переносятся в заново создаваемые. Вы должны сделать это в ручную. -Если свойство "content" присвоен не ListAdapter, то метод UpdateContent() ничего не делает. +Если свойству "content" присвоен не ListAdapter, то метод UpdateContent() ничего не делает. Вызвать метод UpdateContent можно также с помощью глобальной функции @@ -2435,6 +2435,50 @@ GridLayout является контейнером, реализующим ин Все дочерние элементы располагаются в ячейках таблицы. Ячейка адресуется по номеру строки и столбца. Номера строк и столбцов начинаются с 0. +### "content" + +Свойство "content" (константа Content) определяет массив дочерних View. +Данное свойство унаследовано от ViewsContainer. +Также как и для ViewsContainer данному свойству можно присваивать следующие типы данных: + +* View (преобразуется во []View содержащий один View); +* []View; +* string (преобразуется во []View содержащий один TextView); +* []string (преобразуется во []View содержащий TextView); +* []any - данный массив должен содержать только View и string (преобразуется в TextView). + +Однако кроме этих типов данных свойству "content" GridLayout может быть назначена реализация интерфейса GridAdapter. + +GridAdapter используется для создания дочерних View и объявлен как + + type GridAdapter interface { + GridColumnCount() int + GridRowCount() int + GridCellContent(row, column int, session Session) View + } + +Соответственно функции этого интерфейса должны возвращать количество столбцов и строк и View элемента в позиции (row, column). + +Кроме этих трех обязательных методов, при реализации GridAdapter, могут быть заданы еще два опциональных: + + GridCellColumnSpan(row, column int) int + GridCellRowSpan(row, column int) int + +Первый метод задает сколько столбцов занимает View в позиции (row, column) а вторая - сколько строк. + +GridAdapter создает дочерние View в момент установки свойства "content". +Для пересоздания дочерних элементов GridLayout имеет метод UpdateGridContent(). +Данный метод удаляет все дочерние View и создает их заново используя GridAdapter. + +Внимание! При вызове метода UpdateGridContent() данные из старых View не переносятся в заново создаваемые. +Вы должны сделать это в ручную. + +Если свойству "content" присвоен не GridAdapter, то метод UpdateGridContent() ничего не делает. + +Вызвать метод UpdateGridContent можно также с помощью глобальной функции + + func UpdateContent(view View, subviewID ...string) + ### "column" и "row" Расположение View внутри GridLayout определяется с помощью свойств "column" и "row". diff --git a/README.md b/README.md index b34bf37..4c46dae 100644 --- a/README.md +++ b/README.md @@ -2410,6 +2410,51 @@ The container space of this container is split into cells in the form of a table All children are located in the cells of the table. A cell is addressed by row and column number. Row and column numbers start at 0. +### "content" + +The "content" property (Content constant) defines an array of child Views. +This property is inherited from ViewsContainer. +Just like for ViewsContainer, this property can be assigned the following data types: + +* View (converts to []View containing one View); +* []View; +* string (converts to []View containing one TextView); +* []string (converts to []View containing TextView); +* []any - this array must contain only View and string. + +However, in addition to these data types, the "content" property of a GridLayout +can be assigned an implementation of the GridAdapter interface. + +GridAdapter is used to create child Views and is declared as + + type GridAdapter interface { + GridColumnCount() int + GridRowCount() int + GridCellContent(row, column int, session Session) View + } + +Accordingly, the functions of this interface must return the number of columns and rows and the View of the element at position (row, column). + +In addition to these three required methods, when implementing a GridAdapter, two more optional ones can be specified: + + GridCellColumnSpan(row, column int) int + GridCellRowSpan(row, column int) int + +The first method sets how many columns the View occupies in (row, column) position and the second sets how many rows. + +GridAdapter creates child Views when the "content" property is set. +To recreate child elements, GridLayout has the UpdateGridContent() method. +This method deletes all child Views and creates them again using the GridAdapter. + +Attention! When calling the UpdateGridContent() method, data from old Views is not transferred to newly created ones. +You must do this manually. + +If the "content" property is not set to a GridAdapter then the UpdateGridContent() method does nothing. + +You can also call the UpdateGridContent method using the global function + + func UpdateContent(view View, subviewID ...string) + ### "column" and "row" properties The location of the View inside the GridLayout is determined using the "column" and "row" properties. diff --git a/gridLayout.go b/gridLayout.go index e0e83e4..ae22893 100644 --- a/gridLayout.go +++ b/gridLayout.go @@ -43,13 +43,43 @@ const ( CellHorizontalSelfAlign = "cell-horizontal-self-align" ) +type GridAdapter interface { + // GridColumnCount returns the number of columns in the grid + GridColumnCount() int + + // GridRowCount returns the number of rows in the grid + GridRowCount() int + + // GridCellContent creates a View at the given cell + GridCellContent(row, column int, session Session) View +} + +// GridCellColumnSpanAdapter implements the optional method of GridAdapter interface +type GridCellColumnSpanAdapter interface { + // GridCellColumnSpan returns the number of columns that a cell spans. + // Values ​​less than 1 are ignored. + GridCellColumnSpan(row, column int) int +} + +// GridCellColumnSpanAdapter implements the optional method of GridAdapter interface +type GridCellRowSpanAdapter interface { + // GridCellRowSpan returns the number of rows that a cell spans + // Values ​​less than 1 are ignored. + GridCellRowSpan(row, column int) int +} + // GridLayout - grid-container of View type GridLayout interface { ViewsContainer + + // UpdateContent updates child Views if the "content" property value is set to GridAdapter, + // otherwise does nothing + UpdateGridContent() } type gridLayoutData struct { viewsContainerData + adapter GridAdapter } // NewGridLayout create new GridLayout object and return it @@ -69,6 +99,7 @@ func (gridLayout *gridLayoutData) init(session Session) { gridLayout.viewsContainerData.init(session) gridLayout.tag = "GridLayout" gridLayout.systemClass = "ruiGridLayout" + gridLayout.adapter = nil } func (gridLayout *gridLayoutData) String() string { @@ -257,14 +288,19 @@ func (gridLayout *gridLayoutData) Remove(tag string) { } func (gridLayout *gridLayoutData) remove(tag string) { - if tag == Gap { + switch tag { + case Gap: gridLayout.remove(GridRowGap) gridLayout.remove(GridColumnGap) gridLayout.propertyChangedEvent(Gap) return + + case Content: + gridLayout.adapter = nil } gridLayout.viewsContainerData.remove(tag) + if gridLayout.created { switch tag { case CellWidth: @@ -289,8 +325,17 @@ func (gridLayout *gridLayoutData) set(tag string, value any) bool { return true } - if tag == Gap { + switch tag { + case Gap: return gridLayout.set(GridRowGap, value) && gridLayout.set(GridColumnGap, value) + + case Content: + if adapter, ok := value.(GridAdapter); ok { + gridLayout.adapter = adapter + gridLayout.UpdateGridContent() + return true + } + gridLayout.adapter = nil } if gridLayout.viewsContainerData.set(tag, value) { @@ -312,6 +357,70 @@ func (gridLayout *gridLayoutData) set(tag string, value any) bool { return false } +func (gridLayout *gridLayoutData) UpdateGridContent() { + if adapter := gridLayout.adapter; adapter != nil { + gridLayout.views = []View{} + + session := gridLayout.session + htmlID := gridLayout.htmlID() + isDisabled := IsDisabled(gridLayout) + + var columnSpan GridCellColumnSpanAdapter = nil + if span, ok := adapter.(GridCellColumnSpanAdapter); ok { + columnSpan = span + } + + var rowSpan GridCellRowSpanAdapter = nil + if span, ok := adapter.(GridCellRowSpanAdapter); ok { + rowSpan = span + } + + width := adapter.GridColumnCount() + height := adapter.GridRowCount() + for column := 0; column < width; column++ { + for row := 0; row < height; row++ { + if view := adapter.GridCellContent(row, column, session); view != nil { + view.setParentID(htmlID) + + columnCount := 1 + if columnSpan != nil { + columnCount = columnSpan.GridCellColumnSpan(row, column) + } + + if columnCount > 1 { + view.Set(Column, Range{First: column, Last: column + columnCount - 1}) + } else { + view.Set(Column, column) + } + + rowCount := 1 + if rowSpan != nil { + rowCount = rowSpan.GridCellRowSpan(row, column) + } + + if rowCount > 1 { + view.Set(Row, Range{First: row, Last: row + rowCount - 1}) + } else { + view.Set(Row, row) + } + + if isDisabled { + view.Set(Disabled, true) + } + + gridLayout.views = append(gridLayout.views, view) + } + } + } + + if gridLayout.created { + updateInnerHTML(htmlID, session) + } + + gridLayout.propertyChangedEvent(Content) + } +} + func gridCellSizes(properties Properties, tag string, session Session) []SizeUnit { if value := properties.Get(tag); value != nil { switch value := value.(type) { diff --git a/listLayout.go b/listLayout.go index 294df07..21a7c3a 100644 --- a/listLayout.go +++ b/listLayout.go @@ -245,7 +245,7 @@ func GetListColumnGap(view View, subviewID ...string) SizeUnit { return sizeStyledProperty(view, subviewID, ListColumnGap, false) } -// UpdateContent updates child Views of ListLayout subview if the "content" property value is set to ListAdapter, +// UpdateContent updates child Views of ListLayout/GridLayout subview if the "content" property value is set to ListAdapter/GridAdapter, // otherwise does nothing. // If the second argument (subviewID) is not specified or it is "" then the first argument (view) updates. func UpdateContent(view View, subviewID ...string) { @@ -255,6 +255,9 @@ func UpdateContent(view View, subviewID ...string) { if view != nil { switch view := view.(type) { + case GridLayout: + view.UpdateGridContent() + case ListLayout: view.UpdateContent()