rui_orig/gridLayout.go

567 lines
16 KiB
Go
Raw Normal View History

2021-09-07 17:36:50 +03:00
package rui
import (
"fmt"
"strings"
)
// Constants related to [GridLayout] specific properties and events
const (
// CellVerticalAlign is the constant for "cell-vertical-align" property tag.
//
// Used by `GridLayout`, `SvgImageView`.
//
// Usage in `GridLayout`:
// Sets the default vertical alignment of `GridLayout` children within the cell they are occupying.
//
// Supported types: `int`, `string`.
//
// Values:
// `0`(`TopAlign`) or "top" - Top alignment.
// `1`(`BottomAlign`) or "bottom" - Bottom alignment.
// `2`(`CenterAlign`) or "center" - Center alignment.
// `3`(`StretchAlign`) or "stretch" - Full height stretch.
//
// Usage in `SvgImageView`:
// Same as "vertical-align".
2024-11-13 12:56:39 +03:00
CellVerticalAlign PropertyName = "cell-vertical-align"
// CellHorizontalAlign is the constant for "cell-horizontal-align" property tag.
//
// Used by `GridLayout`, `SvgImageView`.
//
// Usage in `GridLayout`:
// Sets the default horizontal alignment of `GridLayout` children within the occupied cell.
//
// Supported types: `int`, `string`.
//
// Values:
// `0`(`LeftAlign`) or "left" - Left alignment.
// `1`(`RightAlign`) or "right" - Right alignment.
// `2`(`CenterAlign`) or "center" - Center alignment.
// `3`(`StretchAlign`) or "stretch" - Full width stretch.
//
// Usage in `SvgImageView`:
// Same as "horizontal-align".
2024-11-13 12:56:39 +03:00
CellHorizontalAlign PropertyName = "cell-horizontal-align"
// CellVerticalSelfAlign is the constant for "cell-vertical-self-align" property tag.
//
// Used by `GridLayout`.
2024-11-13 12:56:39 +03:00
// Sets the vertical alignment of `GridLayout` children within the cell they are occupying. The property is set for the
// child view of `GridLayout`.
//
// Supported types: `int`, `string`.
//
// Values:
// `0`(`TopAlign`) or "top" - Top alignment.
// `1`(`BottomAlign`) or "bottom" - Bottom alignment.
// `2`(`CenterAlign`) or "center" - Center alignment.
// `3`(`StretchAlign`) or "stretch" - Full height stretch.
2024-11-13 12:56:39 +03:00
CellVerticalSelfAlign PropertyName = "cell-vertical-self-align"
// CellHorizontalSelfAlign is the constant for "cell-horizontal-self-align" property tag.
//
// Used by `GridLayout`.
2024-11-13 12:56:39 +03:00
// Sets the horizontal alignment of `GridLayout` children within the occupied cell. The property is set for the child view
// of `GridLayout`.
//
// Supported types: `int`, `string`.
//
// Values:
// `0`(`LeftAlign`) or "left" - Left alignment.
// `1`(`RightAlign`) or "right" - Right alignment.
// `2`(`CenterAlign`) or "center" - Center alignment.
// `3`(`StretchAlign`) or "stretch" - Full width stretch.
2024-11-13 12:56:39 +03:00
CellHorizontalSelfAlign PropertyName = "cell-horizontal-self-align"
)
// GridAdapter is an interface to define [GridLayout] content. [GridLayout] will query interface functions to populate
// its content
2024-06-06 16:20:15 +03:00
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 the [GridAdapter] interface
2024-06-06 16:20:15 +03:00
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 the [GridAdapter] interface
2024-06-06 16:20:15 +03:00
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 represents a GridLayout view
2021-09-07 17:36:50 +03:00
type GridLayout interface {
ViewsContainer
2024-06-06 16:20:15 +03:00
// UpdateContent updates child Views if the "content" property value is set to GridAdapter,
// otherwise does nothing
UpdateGridContent()
2021-09-07 17:36:50 +03:00
}
type gridLayoutData struct {
viewsContainerData
2024-06-06 16:20:15 +03:00
adapter GridAdapter
2021-09-07 17:36:50 +03:00
}
// NewGridLayout create new GridLayout object and return it
func NewGridLayout(session Session, params Params) GridLayout {
view := new(gridLayoutData)
view.init(session)
2021-09-07 17:36:50 +03:00
setInitParams(view, params)
return view
}
func newGridLayout(session Session) View {
2024-11-13 12:56:39 +03:00
//return NewGridLayout(session, nil)
return new(gridLayoutData)
2021-09-07 17:36:50 +03:00
}
// Init initialize fields of GridLayout by default values
func (gridLayout *gridLayoutData) init(session Session) {
gridLayout.viewsContainerData.init(session)
2021-09-07 17:36:50 +03:00
gridLayout.tag = "GridLayout"
gridLayout.systemClass = "ruiGridLayout"
2024-06-06 16:20:15 +03:00
gridLayout.adapter = nil
2024-11-13 12:56:39 +03:00
gridLayout.normalize = normalizeGridLayoutTag
gridLayout.getFunc = gridLayout.get
gridLayout.set = gridLayout.setFunc
gridLayout.remove = gridLayout.removeFunc
2021-09-07 17:36:50 +03:00
2022-05-22 12:54:02 +03:00
}
2024-11-13 12:56:39 +03:00
func setGridCellSize(properties Properties, tag PropertyName, value any) []PropertyName {
2021-09-07 17:36:50 +03:00
setValues := func(values []string) bool {
count := len(values)
if count > 1 {
2022-07-26 18:36:00 +03:00
sizes := make([]any, count)
2021-09-07 17:36:50 +03:00
for i, val := range values {
val = strings.Trim(val, " \t\n\r")
if isConstantName(val) {
sizes[i] = val
2022-09-06 09:57:33 +03:00
} else if fn := parseSizeFunc(val); fn != nil {
sizes[i] = SizeUnit{Type: SizeFunction, Function: fn}
2022-05-01 13:27:04 +03:00
} else if size, err := stringToSizeUnit(val); err == nil {
2021-09-07 17:36:50 +03:00
sizes[i] = size
} else {
invalidPropertyValue(tag, value)
return false
}
}
2024-11-13 12:56:39 +03:00
properties.setRaw(tag, sizes)
2021-09-07 17:36:50 +03:00
} else if isConstantName(values[0]) {
2024-11-13 12:56:39 +03:00
properties.setRaw(tag, values[0])
2022-05-01 13:27:04 +03:00
} else if size, err := stringToSizeUnit(values[0]); err == nil {
2024-11-13 12:56:39 +03:00
properties.setRaw(tag, size)
2021-09-07 17:36:50 +03:00
} else {
invalidPropertyValue(tag, value)
return false
}
return true
}
switch tag {
case CellWidth, CellHeight:
switch value := value.(type) {
case SizeUnit, []SizeUnit:
2024-11-13 12:56:39 +03:00
properties.setRaw(tag, value)
2021-09-07 17:36:50 +03:00
case string:
if !setValues(strings.Split(value, ",")) {
2024-11-13 12:56:39 +03:00
return nil
2021-09-07 17:36:50 +03:00
}
case []string:
if !setValues(value) {
2024-11-13 12:56:39 +03:00
return nil
2021-09-07 17:36:50 +03:00
}
case []DataValue:
count := len(value)
if count == 0 {
invalidPropertyValue(tag, value)
2024-11-13 12:56:39 +03:00
return nil
2021-09-07 17:36:50 +03:00
}
values := make([]string, count)
for i, val := range value {
if val.IsObject() {
invalidPropertyValue(tag, value)
2024-11-13 12:56:39 +03:00
return nil
2021-09-07 17:36:50 +03:00
}
values[i] = val.Value()
}
if !setValues(values) {
2024-11-13 12:56:39 +03:00
return nil
2021-09-07 17:36:50 +03:00
}
2022-07-26 18:36:00 +03:00
case []any:
2021-09-07 17:36:50 +03:00
count := len(value)
if count == 0 {
invalidPropertyValue(tag, value)
2024-11-13 12:56:39 +03:00
return nil
2021-09-07 17:36:50 +03:00
}
2022-07-26 18:36:00 +03:00
sizes := make([]any, count)
2021-09-07 17:36:50 +03:00
for i, val := range value {
switch val := val.(type) {
case SizeUnit:
sizes[i] = val
case string:
if isConstantName(val) {
sizes[i] = val
2022-05-01 13:27:04 +03:00
} else if size, err := stringToSizeUnit(val); err == nil {
2021-09-07 17:36:50 +03:00
sizes[i] = size
} else {
invalidPropertyValue(tag, value)
2024-11-13 12:56:39 +03:00
return nil
2021-09-07 17:36:50 +03:00
}
default:
invalidPropertyValue(tag, value)
2024-11-13 12:56:39 +03:00
return nil
2021-09-07 17:36:50 +03:00
}
}
2024-11-13 12:56:39 +03:00
properties.setRaw(tag, sizes)
2021-09-07 17:36:50 +03:00
default:
notCompatibleType(tag, value)
2024-11-13 12:56:39 +03:00
return nil
2021-09-07 17:36:50 +03:00
}
2024-11-13 12:56:39 +03:00
return []PropertyName{tag}
2021-09-07 17:36:50 +03:00
}
2024-11-13 12:56:39 +03:00
return nil
2021-09-07 17:36:50 +03:00
}
2024-11-13 12:56:39 +03:00
func gridCellSizesCSS(properties Properties, tag PropertyName, session Session) string {
switch cellSize := gridCellSizes(properties, tag, session); len(cellSize) {
2021-09-07 17:36:50 +03:00
case 0:
case 1:
if cellSize[0].Type != Auto {
return `repeat(auto-fill, ` + cellSize[0].cssString(`auto`, session) + `)`
2021-09-07 17:36:50 +03:00
}
default:
allAuto := true
allEqual := true
for i, size := range cellSize {
if size.Type != Auto {
allAuto = false
}
if i > 0 && !size.Equal(cellSize[0]) {
allEqual = false
}
}
if !allAuto {
if allEqual {
return fmt.Sprintf(`repeat(%d, %s)`, len(cellSize), cellSize[0].cssString(`auto`, session))
2021-09-07 17:36:50 +03:00
}
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
for _, size := range cellSize {
buffer.WriteRune(' ')
buffer.WriteString(size.cssString(`auto`, session))
2021-09-07 17:36:50 +03:00
}
return buffer.String()
}
}
return ""
}
2024-11-13 12:56:39 +03:00
func normalizeGridLayoutTag(tag PropertyName) PropertyName {
tag = defaultNormalize(tag)
2021-09-07 17:36:50 +03:00
switch tag {
case VerticalAlign:
return CellVerticalAlign
case HorizontalAlign:
return CellHorizontalAlign
case "row-gap":
return GridRowGap
case ColumnGap:
return GridColumnGap
}
return tag
}
2024-11-13 12:56:39 +03:00
func (gridLayout *gridLayoutData) get(self View, tag PropertyName) any {
switch tag {
case Gap:
rowGap := GetGridRowGap(gridLayout)
columnGap := GetGridColumnGap(gridLayout)
2021-09-07 17:36:50 +03:00
if rowGap.Equal(columnGap) {
return rowGap
}
return AutoSize()
2024-11-13 12:56:39 +03:00
case Content:
if gridLayout.adapter != nil {
return gridLayout.adapter
}
}
2021-09-07 17:36:50 +03:00
2024-11-13 12:56:39 +03:00
return gridLayout.viewsContainerData.get(gridLayout, tag)
2021-09-07 17:36:50 +03:00
}
2024-11-13 12:56:39 +03:00
func (gridLayout *gridLayoutData) removeFunc(self View, tag PropertyName) []PropertyName {
2024-06-06 16:20:15 +03:00
switch tag {
case Gap:
2024-11-13 12:56:39 +03:00
result := []PropertyName{}
for _, tag := range []PropertyName{GridRowGap, GridColumnGap} {
if gridLayout.getRaw(tag) != nil {
gridLayout.setRaw(tag, nil)
result = append(result, tag)
}
}
return result
2024-06-06 16:20:15 +03:00
case Content:
2024-11-13 12:56:39 +03:00
if len(gridLayout.views) > 0 || gridLayout.adapter != nil {
gridLayout.views = []View{}
gridLayout.adapter = nil
return []PropertyName{Content}
}
2024-11-13 12:56:39 +03:00
return []PropertyName{}
2021-09-07 17:36:50 +03:00
}
2024-11-13 12:56:39 +03:00
return gridLayout.viewsContainerData.removeFunc(gridLayout, tag)
2021-09-07 17:36:50 +03:00
}
2024-11-13 12:56:39 +03:00
func (gridLayout *gridLayoutData) setFunc(self View, tag PropertyName, value any) []PropertyName {
2024-06-06 16:20:15 +03:00
switch tag {
case Gap:
2024-11-13 12:56:39 +03:00
result := gridLayout.setFunc(gridLayout, GridRowGap, value)
if result != nil {
if gap := gridLayout.getRaw(GridRowGap); gap != nil {
gridLayout.setRaw(GridColumnGap, gap)
result = append(result, GridColumnGap)
}
}
return result
2024-06-06 16:20:15 +03:00
case Content:
if adapter, ok := value.(GridAdapter); ok {
gridLayout.adapter = adapter
2024-11-13 12:56:39 +03:00
gridLayout.createGridContent()
} else if gridLayout.setContent(value) {
gridLayout.adapter = nil
} else {
return nil
2024-06-06 16:20:15 +03:00
}
2024-11-13 12:56:39 +03:00
return []PropertyName{Content}
2021-09-07 17:36:50 +03:00
}
2024-11-13 12:56:39 +03:00
return gridLayout.viewsContainerData.setFunc(gridLayout, tag, value)
}
func gridLayoutPropertyChanged(view View, tag PropertyName) {
switch tag {
case CellWidth:
view.Session().updateCSSProperty(view.htmlID(), `grid-template-columns`,
gridCellSizesCSS(view, CellWidth, view.Session()))
2021-09-07 17:36:50 +03:00
2024-11-13 12:56:39 +03:00
case CellHeight:
view.Session().updateCSSProperty(view.htmlID(), `grid-template-rows`,
gridCellSizesCSS(view, CellHeight, view.Session()))
2021-09-07 17:36:50 +03:00
2024-11-13 12:56:39 +03:00
default:
viewsContainerPropertyChanged(view, tag)
2021-09-07 17:36:50 +03:00
}
}
2024-11-13 12:56:39 +03:00
func (gridLayout *gridLayoutData) createGridContent() bool {
if gridLayout.adapter == nil {
return false
}
2024-06-06 16:20:15 +03:00
2024-11-13 12:56:39 +03:00
adapter := gridLayout.adapter
gridLayout.views = []View{}
2024-06-06 16:20:15 +03:00
2024-11-13 12:56:39 +03:00
session := gridLayout.session
htmlID := gridLayout.htmlID()
isDisabled := IsDisabled(gridLayout)
2024-06-06 16:20:15 +03:00
2024-11-13 12:56:39 +03:00
var columnSpan GridCellColumnSpanAdapter = nil
if span, ok := adapter.(GridCellColumnSpanAdapter); ok {
columnSpan = span
}
2024-06-06 16:20:15 +03:00
2024-11-13 12:56:39 +03:00
var rowSpan GridCellRowSpanAdapter = nil
if span, ok := adapter.(GridCellRowSpanAdapter); ok {
rowSpan = span
}
2024-06-06 16:20:15 +03:00
2024-11-13 12:56:39 +03:00
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)
2024-06-06 16:20:15 +03:00
2024-11-13 12:56:39 +03:00
columnCount := 1
if columnSpan != nil {
columnCount = columnSpan.GridCellColumnSpan(row, column)
}
2024-06-06 16:20:15 +03:00
2024-11-13 12:56:39 +03:00
if columnCount > 1 {
view.Set(Column, Range{First: column, Last: column + columnCount - 1})
} else {
view.Set(Column, column)
}
2024-06-06 16:20:15 +03:00
2024-11-13 12:56:39 +03:00
rowCount := 1
if rowSpan != nil {
rowCount = rowSpan.GridCellRowSpan(row, column)
}
2024-06-06 16:20:15 +03:00
2024-11-13 12:56:39 +03:00
if rowCount > 1 {
view.Set(Row, Range{First: row, Last: row + rowCount - 1})
} else {
view.Set(Row, row)
}
2024-06-06 16:20:15 +03:00
2024-11-13 12:56:39 +03:00
if isDisabled {
view.Set(Disabled, true)
2024-06-06 16:20:15 +03:00
}
2024-11-13 12:56:39 +03:00
gridLayout.views = append(gridLayout.views, view)
2024-06-06 16:20:15 +03:00
}
}
2024-11-13 12:56:39 +03:00
}
2024-06-06 16:20:15 +03:00
2024-11-13 12:56:39 +03:00
return true
}
func (gridLayout *gridLayoutData) UpdateGridContent() {
if gridLayout.createGridContent() {
2024-06-06 16:20:15 +03:00
if gridLayout.created {
2024-11-13 12:56:39 +03:00
updateInnerHTML(gridLayout.htmlID(), gridLayout.session)
2024-06-06 16:20:15 +03:00
}
2024-11-13 12:56:39 +03:00
if listener, ok := gridLayout.changeListener[Content]; ok {
listener(gridLayout, Content)
}
2024-06-06 16:20:15 +03:00
}
}
2024-11-13 12:56:39 +03:00
func gridCellSizes(properties Properties, tag PropertyName, session Session) []SizeUnit {
2021-09-07 17:36:50 +03:00
if value := properties.Get(tag); value != nil {
switch value := value.(type) {
case []SizeUnit:
return value
case SizeUnit:
return []SizeUnit{value}
2022-07-26 18:36:00 +03:00
case []any:
2021-09-07 17:36:50 +03:00
result := make([]SizeUnit, len(value))
for i, val := range value {
result[i] = AutoSize()
switch val := val.(type) {
case SizeUnit:
result[i] = val
case string:
if text, ok := session.resolveConstants(val); ok {
2022-05-01 13:27:04 +03:00
result[i], _ = stringToSizeUnit(text)
2021-09-07 17:36:50 +03:00
}
}
}
return result
case string:
if text, ok := session.resolveConstants(value); ok {
values := strings.Split(text, ",")
result := make([]SizeUnit, len(values))
for i, val := range values {
2022-05-01 13:27:04 +03:00
result[i], _ = stringToSizeUnit(val)
2021-09-07 17:36:50 +03:00
}
return result
}
}
}
return []SizeUnit{}
}
2021-11-20 16:53:45 +03:00
/*
2021-09-07 17:36:50 +03:00
func (gridLayout *gridLayoutData) cssStyle(self View, builder cssBuilder) {
gridLayout.viewsContainerData.cssStyle(self, builder)
}
2021-11-20 16:53:45 +03:00
*/
2021-09-07 17:36:50 +03:00
// GetCellVerticalAlign returns the vertical align of a GridLayout cell content: TopAlign (0), BottomAlign (1), CenterAlign (2), StretchAlign (3)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetCellVerticalAlign(view View, subviewID ...string) int {
2022-07-28 12:41:50 +03:00
return enumStyledProperty(view, subviewID, CellVerticalAlign, StretchAlign, false)
2021-09-07 17:36:50 +03:00
}
// GetCellHorizontalAlign returns the vertical align of a GridLayout cell content: LeftAlign (0), RightAlign (1), CenterAlign (2), StretchAlign (3)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetCellHorizontalAlign(view View, subviewID ...string) int {
2022-07-28 12:41:50 +03:00
return enumStyledProperty(view, subviewID, CellHorizontalAlign, StretchAlign, false)
2021-09-07 17:36:50 +03:00
}
// GetGridAutoFlow returns the value of the "grid-auto-flow" property
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetGridAutoFlow(view View, subviewID ...string) int {
2022-07-28 12:41:50 +03:00
return enumStyledProperty(view, subviewID, GridAutoFlow, 0, false)
}
2021-09-07 17:36:50 +03:00
// GetCellWidth returns the width of a GridLayout cell. If the result is an empty array, then the width is not set.
// If the result is a single value array, then the width of all cell is equal.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetCellWidth(view View, subviewID ...string) []SizeUnit {
if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0])
2021-09-07 17:36:50 +03:00
}
if view != nil {
return gridCellSizes(view, CellWidth, view.Session())
}
return []SizeUnit{}
}
// GetCellHeight returns the height of a GridLayout cell. If the result is an empty array, then the height is not set.
// If the result is a single value array, then the height of all cell is equal.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetCellHeight(view View, subviewID ...string) []SizeUnit {
if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0])
2021-09-07 17:36:50 +03:00
}
if view != nil {
return gridCellSizes(view, CellHeight, view.Session())
}
return []SizeUnit{}
}
// GetGridRowGap returns the gap between GridLayout rows.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetGridRowGap(view View, subviewID ...string) SizeUnit {
2022-07-28 12:53:50 +03:00
return sizeStyledProperty(view, subviewID, GridRowGap, false)
2021-09-07 17:36:50 +03:00
}
// GetGridColumnGap returns the gap between GridLayout columns.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetGridColumnGap(view View, subviewID ...string) SizeUnit {
2022-07-28 12:53:50 +03:00
return sizeStyledProperty(view, subviewID, GridColumnGap, false)
2021-09-07 17:36:50 +03:00
}