rui_orig/listView.go

1334 lines
38 KiB
Go
Raw Normal View History

2021-09-07 17:36:50 +03:00
package rui
import (
"fmt"
"strconv"
"strings"
)
const (
// ListItemClickedEvent is the constant for "list-item-clicked" property tag.
// The "list-item-clicked" event occurs when the user clicks on an item in the list.
// The main listener format: func(ListView, int), where the second argument is the item index.
ListItemClickedEvent = "list-item-clicked"
2024-04-23 19:34:36 +03:00
2021-09-07 17:36:50 +03:00
// ListItemSelectedEvent is the constant for "list-item-selected" property tag.
// The "list-item-selected" event occurs when a list item becomes selected.
// The main listener format: func(ListView, int), where the second argument is the item index.
ListItemSelectedEvent = "list-item-selected"
2024-04-23 19:34:36 +03:00
2021-09-07 17:36:50 +03:00
// ListItemCheckedEvent is the constant for "list-item-checked" property tag.
// The "list-item-checked" event occurs when a list item checkbox becomes checked/unchecked.
// The main listener format: func(ListView, []int), where the second argument is the array of checked item indexes.
ListItemCheckedEvent = "list-item-checked"
2024-04-23 19:34:36 +03:00
2021-11-04 21:13:34 +03:00
// ListItemStyle is the constant for "list-item-style" property tag.
2021-09-07 17:36:50 +03:00
// The "list-item-style" string property defines the style of an unselected item
ListItemStyle = "list-item-style"
2024-04-23 19:34:36 +03:00
2021-11-04 21:13:34 +03:00
// CurrentStyle is the constant for "current-style" property tag.
2021-09-07 17:36:50 +03:00
// The "current-style" string property defines the style of the selected item when the ListView is focused.
CurrentStyle = "current-style"
2024-04-23 19:34:36 +03:00
2021-11-04 21:13:34 +03:00
// CurrentInactiveStyle is the constant for "current-inactive-style" property tag.
2021-09-07 17:36:50 +03:00
// The "current-inactive-style" string property defines the style of the selected item when the ListView is unfocused.
CurrentInactiveStyle = "current-inactive-style"
)
const (
// VerticalOrientation is the vertical ListView orientation
VerticalOrientation = 0
// HorizontalOrientation is the horizontal ListView orientation
HorizontalOrientation = 1
// NoneCheckbox is value of "checkbox" property: no checkbox
NoneCheckbox = 0
// SingleCheckbox is value of "checkbox" property: only one item can be checked
SingleCheckbox = 1
// MultipleCheckbox is value of "checkbox" property: several items can be checked
MultipleCheckbox = 2
)
// ListView - the list view interface
type ListView interface {
View
2022-11-23 15:10:29 +03:00
ParentView
2021-09-07 17:36:50 +03:00
// ReloadListViewData updates ListView content
ReloadListViewData()
getCheckedItems() []int
getItemFrames() []Frame
}
type listViewData struct {
viewData
adapter ListAdapter
clickedListeners []func(ListView, int)
selectedListeners []func(ListView, int)
checkedListeners []func(ListView, []int)
items []View
itemFrame []Frame
checkedItem []int
}
// NewListView creates the new list view
func NewListView(session Session, params Params) ListView {
view := new(listViewData)
view.init(session)
2021-09-07 17:36:50 +03:00
setInitParams(view, params)
return view
}
func newListView(session Session) View {
return NewListView(session, nil)
}
// Init initialize fields of ViewsContainer by default values
func (listView *listViewData) init(session Session) {
listView.viewData.init(session)
2021-09-07 17:36:50 +03:00
listView.tag = "ListView"
listView.systemClass = "ruiListView"
listView.items = []View{}
listView.itemFrame = []Frame{}
listView.checkedItem = []int{}
listView.clickedListeners = []func(ListView, int){}
listView.selectedListeners = []func(ListView, int){}
listView.checkedListeners = []func(ListView, []int){}
}
2022-05-22 12:54:02 +03:00
func (listView *listViewData) String() string {
return getViewString(listView, nil)
2022-05-22 12:54:02 +03:00
}
2021-09-07 17:36:50 +03:00
func (listView *listViewData) Views() []View {
return listView.items
}
func (listView *listViewData) normalizeTag(tag string) string {
tag = strings.ToLower(tag)
switch tag {
case HorizontalAlign:
tag = ItemHorizontalAlign
case VerticalAlign:
tag = ItemVerticalAlign
case "wrap":
tag = ListWrap
case "row-gap":
return ListRowGap
case ColumnGap:
return ListColumnGap
2021-09-07 17:36:50 +03:00
}
return tag
}
func (listView *listViewData) Remove(tag string) {
listView.remove(listView.normalizeTag(tag))
}
func (listView *listViewData) remove(tag string) {
switch tag {
case Gap:
listView.remove(ListRowGap)
listView.remove(ListColumnGap)
2023-05-18 12:26:54 +03:00
return
2021-09-07 17:36:50 +03:00
case Checked:
2023-05-18 12:26:54 +03:00
if len(listView.checkedItem) == 0 {
return
}
listView.checkedItem = []int{}
if listView.created {
updateInnerHTML(listView.htmlID(), listView.session)
}
2021-09-07 17:36:50 +03:00
case Items:
2023-05-18 12:26:54 +03:00
if listView.adapter == nil {
return
}
listView.adapter = nil
if listView.created {
updateInnerHTML(listView.htmlID(), listView.session)
}
2021-09-07 17:36:50 +03:00
case Orientation, ListWrap:
2023-05-18 12:26:54 +03:00
if _, ok := listView.properties[tag]; !ok {
return
}
delete(listView.properties, tag)
if listView.created {
updateCSSStyle(listView.htmlID(), listView.session)
}
2021-09-07 17:36:50 +03:00
case Current:
current := GetCurrent(listView)
2023-05-18 12:26:54 +03:00
if current == -1 {
return
}
2021-09-07 17:36:50 +03:00
delete(listView.properties, tag)
if listView.created {
2023-05-18 12:26:54 +03:00
htmlID := listView.htmlID()
session := listView.session
session.removeProperty(htmlID, "data-current")
updateInnerHTML(htmlID, session)
}
2021-09-07 17:36:50 +03:00
if current != -1 {
for _, listener := range listView.selectedListeners {
listener(listView, -1)
}
}
case ItemWidth, ItemHeight, ItemHorizontalAlign, ItemVerticalAlign, ItemCheckbox,
2023-05-18 12:26:54 +03:00
CheckboxHorizontalAlign, CheckboxVerticalAlign:
if _, ok := listView.properties[tag]; !ok {
return
}
delete(listView.properties, tag)
if listView.created {
updateInnerHTML(listView.htmlID(), listView.session)
}
case ListItemStyle, CurrentStyle, CurrentInactiveStyle:
if !listView.setItemStyle(tag, "") {
return
}
2021-09-07 17:36:50 +03:00
case ListItemClickedEvent:
2023-05-18 12:26:54 +03:00
if len(listView.clickedListeners) == 0 {
return
2021-09-07 17:36:50 +03:00
}
2023-05-18 12:26:54 +03:00
listView.clickedListeners = []func(ListView, int){}
2021-09-07 17:36:50 +03:00
case ListItemSelectedEvent:
2023-05-18 12:26:54 +03:00
if len(listView.selectedListeners) == 0 {
return
2021-09-07 17:36:50 +03:00
}
2023-05-18 12:26:54 +03:00
listView.selectedListeners = []func(ListView, int){}
2021-09-07 17:36:50 +03:00
case ListItemCheckedEvent:
2023-05-18 12:26:54 +03:00
if len(listView.checkedListeners) == 0 {
return
2021-09-07 17:36:50 +03:00
}
2023-05-18 12:26:54 +03:00
listView.checkedListeners = []func(ListView, []int){}
2021-09-07 17:36:50 +03:00
default:
listView.viewData.remove(tag)
2023-05-18 12:26:54 +03:00
return
2021-09-07 17:36:50 +03:00
}
2023-05-18 12:26:54 +03:00
listView.propertyChangedEvent(tag)
2021-09-07 17:36:50 +03:00
}
2022-07-26 18:36:00 +03:00
func (listView *listViewData) Set(tag string, value any) bool {
2021-09-07 17:36:50 +03:00
return listView.set(listView.normalizeTag(tag), value)
}
2022-07-26 18:36:00 +03:00
func (listView *listViewData) set(tag string, value any) bool {
2021-09-07 17:36:50 +03:00
if value == nil {
listView.remove(tag)
return true
}
switch tag {
case Gap:
return listView.set(ListRowGap, value) && listView.set(ListColumnGap, value)
2021-09-07 17:36:50 +03:00
case ListItemClickedEvent:
2022-07-27 20:31:57 +03:00
listeners, ok := valueToEventListeners[ListView, int](value)
if !ok {
2021-09-07 17:36:50 +03:00
notCompatibleType(tag, value)
return false
2022-07-27 20:31:57 +03:00
} else if listeners == nil {
listeners = []func(ListView, int){}
2021-09-07 17:36:50 +03:00
}
listView.clickedListeners = listeners
listView.propertyChangedEvent(tag)
2021-09-07 17:36:50 +03:00
return true
case ListItemSelectedEvent:
2022-07-27 20:31:57 +03:00
listeners, ok := valueToEventListeners[ListView, int](value)
if !ok {
2021-09-07 17:36:50 +03:00
notCompatibleType(tag, value)
return false
2022-07-27 20:31:57 +03:00
} else if listeners == nil {
listeners = []func(ListView, int){}
2021-09-07 17:36:50 +03:00
}
listView.selectedListeners = listeners
listView.propertyChangedEvent(tag)
2021-09-07 17:36:50 +03:00
return true
case ListItemCheckedEvent:
2022-07-27 20:31:57 +03:00
listeners, ok := valueToEventListeners[ListView, []int](value)
if !ok {
notCompatibleType(tag, value)
return false
2022-07-27 20:31:57 +03:00
} else if listeners == nil {
listeners = []func(ListView, []int){}
}
2022-07-27 20:31:57 +03:00
listView.checkedListeners = listeners
listView.propertyChangedEvent(tag)
return true
2021-09-07 17:36:50 +03:00
case Checked:
if !listView.setChecked(value) {
return false
}
2021-09-07 17:36:50 +03:00
case Items:
if !listView.setItems(value) {
return false
}
2021-09-07 17:36:50 +03:00
case Current:
oldCurrent := GetCurrent(listView)
if !listView.setIntProperty(Current, value) {
return false
}
2023-05-18 12:26:54 +03:00
current := GetCurrent(listView)
if oldCurrent == current {
2021-09-07 17:36:50 +03:00
return true
}
2023-05-18 12:26:54 +03:00
if listView.created {
htmlID := listView.htmlID()
if current >= 0 {
listView.session.updateProperty(htmlID, "data-current", fmt.Sprintf("%s-%d", htmlID, current))
} else {
listView.session.removeProperty(htmlID, "data-current")
}
}
for _, listener := range listView.selectedListeners {
listener(listView, current)
}
2021-09-07 17:36:50 +03:00
case Orientation, ListWrap, ListRowGap, ListColumnGap, VerticalAlign, HorizontalAlign, Style, StyleDisabled, ItemWidth, ItemHeight:
result := listView.viewData.set(tag, value)
if result && listView.created {
updateInnerHTML(listView.htmlID(), listView.session)
}
return result
2021-09-07 17:36:50 +03:00
case ItemHorizontalAlign, ItemVerticalAlign, ItemCheckbox, CheckboxHorizontalAlign, CheckboxVerticalAlign:
if !listView.setEnumProperty(tag, value, enumProperties[tag].values) {
return false
}
2021-09-07 17:36:50 +03:00
case ListItemStyle, CurrentStyle, CurrentInactiveStyle:
2023-05-18 12:26:54 +03:00
if !listView.setItemStyle(tag, value) {
2021-09-07 17:36:50 +03:00
return false
}
default:
return listView.viewData.set(tag, value)
}
if listView.created {
2021-09-07 17:36:50 +03:00
updateInnerHTML(listView.htmlID(), listView.session)
}
listView.propertyChangedEvent(tag)
return true
2021-09-07 17:36:50 +03:00
}
2023-05-18 12:26:54 +03:00
func (listView *listViewData) setItemStyle(tag string, value any) bool {
switch value := value.(type) {
case string:
if value == "" {
delete(listView.properties, tag)
} else {
listView.properties[tag] = value
}
default:
notCompatibleType(tag, value)
return false
}
if listView.created {
switch tag {
case CurrentStyle:
listView.session.updateProperty(listView.htmlID(), "data-focusitemstyle", listView.currentStyle())
case CurrentInactiveStyle:
listView.session.updateProperty(listView.htmlID(), "data-bluritemstyle", listView.currentInactiveStyle())
}
}
return true
}
2022-07-26 18:36:00 +03:00
func (listView *listViewData) Get(tag string) any {
2021-09-07 17:36:50 +03:00
return listView.get(listView.normalizeTag(tag))
}
2022-07-26 18:36:00 +03:00
func (listView *listViewData) get(tag string) any {
2021-09-07 17:36:50 +03:00
switch tag {
case Gap:
if rowGap := GetListRowGap(listView); rowGap.Equal(GetListColumnGap(listView)) {
return rowGap
}
return AutoSize()
2021-09-07 17:36:50 +03:00
case ListItemClickedEvent:
return listView.clickedListeners
case ListItemSelectedEvent:
return listView.selectedListeners
case ListItemCheckedEvent:
return listView.checkedListeners
case Checked:
return listView.checkedItem
case Items:
return listView.adapter
case ListItemStyle:
return listView.listItemStyle()
case CurrentStyle:
return listView.currentStyle()
case CurrentInactiveStyle:
return listView.currentInactiveStyle()
}
return listView.viewData.get(tag)
}
2022-07-26 18:36:00 +03:00
func (listView *listViewData) setItems(value any) bool {
2021-09-07 17:36:50 +03:00
switch value := value.(type) {
case []string:
listView.adapter = NewTextListAdapter(value, nil)
case []DataValue:
hasObject := false
for _, val := range value {
if val.IsObject() {
hasObject = true
break
}
}
if hasObject {
items := make([]View, len(value))
for i, val := range value {
if val.IsObject() {
if view := CreateViewFromObject(listView.session, val.Object()); view != nil {
items[i] = view
} else {
return false
}
} else {
items[i] = NewTextView(listView.session, Params{Text: val.Value()})
}
}
listView.adapter = NewViewListAdapter(items)
} else {
items := make([]string, len(value))
for i, val := range value {
items[i] = val.Value()
}
listView.adapter = NewTextListAdapter(items, nil)
}
2022-07-26 18:36:00 +03:00
case []any:
2021-09-07 17:36:50 +03:00
items := make([]View, len(value))
for i, val := range value {
switch value := val.(type) {
case View:
items[i] = value
case string:
items[i] = NewTextView(listView.session, Params{Text: value})
case fmt.Stringer:
items[i] = NewTextView(listView.session, Params{Text: value.String()})
case float32:
items[i] = NewTextView(listView.session, Params{Text: fmt.Sprintf("%g", float64(value))})
case float64:
items[i] = NewTextView(listView.session, Params{Text: fmt.Sprintf("%g", value)})
default:
if n, ok := isInt(val); ok {
items[i] = NewTextView(listView.session, Params{Text: strconv.Itoa(n)})
} else {
notCompatibleType(Items, value)
return false
}
}
}
listView.adapter = NewViewListAdapter(items)
case []View:
listView.adapter = NewViewListAdapter(value)
case ListAdapter:
listView.adapter = value
default:
notCompatibleType(Items, value)
return false
}
size := listView.adapter.ListSize()
listView.items = make([]View, size)
listView.itemFrame = make([]Frame, size)
return true
}
2022-07-26 18:36:00 +03:00
func (listView *listViewData) setChecked(value any) bool {
2021-09-07 17:36:50 +03:00
var checked []int
if value == nil {
checked = []int{}
} else {
switch value := value.(type) {
2022-04-05 18:50:48 +03:00
case string:
checked = []int{}
for _, val := range strings.Split(value, ",") {
n, err := strconv.Atoi(strings.Trim(val, " \t"))
if err != nil {
invalidPropertyValue(Checked, value)
ErrorLog(err.Error())
return false
}
checked = append(checked, n)
}
2021-09-07 17:36:50 +03:00
case int:
checked = []int{value}
case []int:
checked = value
default:
return false
}
}
switch GetListViewCheckbox(listView) {
2021-09-07 17:36:50 +03:00
case SingleCheckbox:
count := len(checked)
if count > 1 {
return false
}
if len(listView.checkedItem) > 0 &&
(count == 0 || listView.checkedItem[0] != checked[0]) {
listView.updateCheckboxItem(listView.checkedItem[0], false)
}
if count == 1 {
listView.updateCheckboxItem(checked[0], true)
}
case MultipleCheckbox:
inSlice := func(n int, slice []int) bool {
for _, n2 := range slice {
if n2 == n {
return true
}
}
return false
}
for _, n := range listView.checkedItem {
if !inSlice(n, checked) {
listView.updateCheckboxItem(n, false)
}
}
for _, n := range checked {
if !inSlice(n, listView.checkedItem) {
listView.updateCheckboxItem(n, true)
}
}
default:
return false
}
listView.checkedItem = checked
for _, listener := range listView.checkedListeners {
listener(listView, listView.checkedItem)
}
return true
}
func (listView *listViewData) Focusable() bool {
return true
}
func (listView *listViewData) ReloadListViewData() {
itemCount := 0
if listView.adapter != nil {
itemCount = listView.adapter.ListSize()
if itemCount != len(listView.items) {
listView.items = make([]View, itemCount)
listView.itemFrame = make([]Frame, itemCount)
}
for i := 0; i < itemCount; i++ {
listView.items[i] = listView.adapter.ListItem(i, listView.Session())
}
} else if len(listView.items) > 0 {
listView.items = []View{}
listView.itemFrame = []Frame{}
}
updateInnerHTML(listView.htmlID(), listView.session)
}
func (listView *listViewData) getCheckedItems() []int {
return listView.checkedItem
}
func (listView *listViewData) getItemFrames() []Frame {
return listView.itemFrame
}
2024-02-27 17:08:05 +03:00
func (listView *listViewData) itemAlign(buffer *strings.Builder) {
2021-09-07 17:36:50 +03:00
values := enumProperties[ItemHorizontalAlign].cssValues
if hAlign := GetListItemHorizontalAlign(listView); hAlign >= 0 && hAlign < len(values) {
2021-09-07 17:36:50 +03:00
buffer.WriteString(" justify-items: ")
buffer.WriteString(values[hAlign])
buffer.WriteRune(';')
}
values = enumProperties[ItemVerticalAlign].cssValues
if vAlign := GetListItemVerticalAlign(listView); vAlign >= 0 && vAlign < len(values) {
2021-09-07 17:36:50 +03:00
buffer.WriteString(" align-items: ")
buffer.WriteString(values[vAlign])
buffer.WriteRune(';')
}
}
2024-02-27 17:08:05 +03:00
func (listView *listViewData) itemSize(buffer *strings.Builder) {
if itemWidth := GetListItemWidth(listView); itemWidth.Type != Auto {
2021-09-07 17:36:50 +03:00
buffer.WriteString(` min-width: `)
buffer.WriteString(itemWidth.cssString("", listView.Session()))
2021-09-07 17:36:50 +03:00
buffer.WriteRune(';')
}
if itemHeight := GetListItemHeight(listView); itemHeight.Type != Auto {
2021-09-07 17:36:50 +03:00
buffer.WriteString(` min-height: `)
buffer.WriteString(itemHeight.cssString("", listView.Session()))
2021-09-07 17:36:50 +03:00
buffer.WriteRune(';')
}
}
2024-02-27 17:08:05 +03:00
func (listView *listViewData) getDivs(checkbox, hCheckboxAlign, vCheckboxAlign int) (string, string, string) {
2021-09-07 17:36:50 +03:00
session := listView.Session()
contentBuilder := allocStringBuilder()
defer freeStringBuilder(contentBuilder)
contentBuilder.WriteString(`<div style="display: grid;`)
2024-02-27 17:08:05 +03:00
listView.itemAlign(contentBuilder)
2021-09-07 17:36:50 +03:00
onDivBuilder := allocStringBuilder()
defer freeStringBuilder(onDivBuilder)
if hCheckboxAlign == CenterAlign {
if vCheckboxAlign == BottomAlign {
onDivBuilder.WriteString(`<div style="grid-row: 2 / 3; grid-column: 1 / 2; display: grid; justify-items: center;`)
contentBuilder.WriteString(` grid-row: 1 / 2; grid-column: 1 / 2;">`)
} else {
vCheckboxAlign = TopAlign
onDivBuilder.WriteString(`<div style="grid-row: 1 / 2; grid-column: 1 / 2; display: grid; justify-items: center;`)
contentBuilder.WriteString(` grid-row: 2 / 3; grid-column: 1 / 2;">`)
}
} else {
if hCheckboxAlign == RightAlign {
onDivBuilder.WriteString(`<div style="grid-row: 1 / 2; grid-column: 2 / 3; display: grid;`)
contentBuilder.WriteString(` grid-row: 1 / 2; grid-column: 1 / 2;">`)
} else {
onDivBuilder.WriteString(`<div style="grid-row: 1 / 2; grid-column: 1 / 2; display: grid;`)
contentBuilder.WriteString(` grid-row: 1 / 2; grid-column: 2 / 3;">`)
}
switch vCheckboxAlign {
case BottomAlign:
onDivBuilder.WriteString(` align-items: end;`)
case CenterAlign:
onDivBuilder.WriteString(` align-items: center;`)
default:
onDivBuilder.WriteString(` align-items: start;`)
}
}
onDivBuilder.WriteString(`">`)
offDivBuilder := allocStringBuilder()
defer freeStringBuilder(offDivBuilder)
offDivBuilder.WriteString(onDivBuilder.String())
if checkbox == SingleCheckbox {
offDivBuilder.WriteString(session.radiobuttonOffImage())
onDivBuilder.WriteString(session.radiobuttonOnImage())
} else {
offDivBuilder.WriteString(session.checkboxOffImage())
onDivBuilder.WriteString(session.checkboxOnImage())
}
onDivBuilder.WriteString("</div>")
offDivBuilder.WriteString("</div>")
return onDivBuilder.String(), offDivBuilder.String(), contentBuilder.String()
}
2024-02-27 17:08:05 +03:00
func (listView *listViewData) checkboxItemDiv(checkbox, hCheckboxAlign, vCheckboxAlign int) string {
2021-09-07 17:36:50 +03:00
itemStyleBuilder := allocStringBuilder()
defer freeStringBuilder(itemStyleBuilder)
itemStyleBuilder.WriteString(`<div style="display: grid; justify-items: stretch; align-items: stretch;`)
if hCheckboxAlign == CenterAlign {
if vCheckboxAlign == BottomAlign {
itemStyleBuilder.WriteString(` grid-template-columns: 1fr; grid-template-rows: 1fr auto;`)
} else {
vCheckboxAlign = TopAlign
itemStyleBuilder.WriteString(` grid-template-columns: 1fr; grid-template-rows: auto 1fr;`)
}
} else {
if hCheckboxAlign == RightAlign {
itemStyleBuilder.WriteString(` grid-template-columns: 1fr auto; grid-template-rows: 1fr;`)
} else {
itemStyleBuilder.WriteString(` grid-template-columns: auto 1fr; grid-template-rows: 1fr;`)
}
}
if gap, ok := sizeConstant(listView.session, "ruiCheckboxGap"); ok && gap.Type != Auto {
itemStyleBuilder.WriteString(` grid-gap: `)
itemStyleBuilder.WriteString(gap.cssString("auto", listView.Session()))
2021-09-07 17:36:50 +03:00
itemStyleBuilder.WriteRune(';')
}
itemStyleBuilder.WriteString(`">`)
return itemStyleBuilder.String()
}
func (listView *listViewData) getItemView(index int) View {
if listView.adapter == nil || index < 0 || index >= listView.adapter.ListSize() {
return nil
}
size := listView.adapter.ListSize()
if size != len(listView.items) {
listView.items = make([]View, size)
}
if listView.items[index] == nil {
listView.items[index] = listView.adapter.ListItem(index, listView.Session())
}
return listView.items[index]
}
func (listView *listViewData) itemStyle(tag, defaultStyle string) string {
if value := listView.getRaw(tag); value != nil {
2021-09-07 17:36:50 +03:00
if style, ok := value.(string); ok {
if style, ok = listView.session.resolveConstants(style); ok {
return style
}
}
}
if value := valueFromStyle(listView, tag); value != nil {
2021-09-07 17:36:50 +03:00
if style, ok := value.(string); ok {
if style, ok = listView.session.resolveConstants(style); ok {
return style
}
}
}
return defaultStyle
}
func (listView *listViewData) listItemStyle() string {
return listView.itemStyle(ListItemStyle, "ruiListItem")
}
func (listView *listViewData) currentStyle() string {
return listView.itemStyle(CurrentStyle, "ruiListItemFocused")
2021-09-07 17:36:50 +03:00
}
func (listView *listViewData) currentInactiveStyle() string {
return listView.itemStyle(CurrentInactiveStyle, "ruiListItemSelected")
2021-09-07 17:36:50 +03:00
}
2024-02-27 17:08:05 +03:00
func (listView *listViewData) checkboxSubviews(buffer *strings.Builder, checkbox int) {
2021-09-07 17:36:50 +03:00
count := listView.adapter.ListSize()
listViewID := listView.htmlID()
hCheckboxAlign := GetListViewCheckboxHorizontalAlign(listView)
vCheckboxAlign := GetListViewCheckboxVerticalAlign(listView)
2021-09-07 17:36:50 +03:00
2024-02-27 17:08:05 +03:00
itemDiv := listView.checkboxItemDiv(checkbox, hCheckboxAlign, vCheckboxAlign)
onDiv, offDiv, contentDiv := listView.getDivs(checkbox, hCheckboxAlign, vCheckboxAlign)
2021-09-07 17:36:50 +03:00
current := GetCurrent(listView)
checkedItems := GetListViewCheckedItems(listView)
2021-09-07 17:36:50 +03:00
for i := 0; i < count; i++ {
buffer.WriteString(`<div id="`)
buffer.WriteString(listViewID)
buffer.WriteRune('-')
buffer.WriteString(strconv.Itoa(i))
buffer.WriteString(`" class="ruiView `)
buffer.WriteString(listView.listItemStyle())
if i == current {
buffer.WriteRune(' ')
buffer.WriteString(listView.currentInactiveStyle())
}
buffer.WriteString(`" onclick="listItemClickEvent(this, event)" data-left="0" data-top="0" data-width="0" data-height="0" style="display: grid; justify-items: stretch; align-items: stretch;`)
2024-02-27 17:08:05 +03:00
listView.itemSize(buffer)
if ext, ok := listView.adapter.(ListItemEnabled); ok {
if !ext.IsListItemEnabled(i) {
buffer.WriteString(`" data-disabled="1`)
}
2023-05-03 15:16:03 +03:00
}
2021-09-07 17:36:50 +03:00
buffer.WriteString(`">`)
buffer.WriteString(itemDiv)
checked := false
for _, index := range checkedItems {
if index == i {
buffer.WriteString(onDiv)
checked = true
break
}
}
if !checked {
buffer.WriteString(offDiv)
}
buffer.WriteString(contentDiv)
if view := listView.getItemView(i); view != nil {
//view.setNoResizeEvent()
viewHTML(view, buffer)
} else {
buffer.WriteString("ERROR: invalid item view")
}
buffer.WriteString(`</div></div></div>`)
}
}
2024-02-27 17:08:05 +03:00
func (listView *listViewData) noneCheckboxSubviews(buffer *strings.Builder) {
2021-09-07 17:36:50 +03:00
count := listView.adapter.ListSize()
listViewID := listView.htmlID()
itemStyleBuilder := allocStringBuilder()
defer freeStringBuilder(itemStyleBuilder)
itemStyleBuilder.WriteString(`data-left="0" data-top="0" data-width="0" data-height="0" style="max-width: 100%; max-height: 100%; display: grid;`)
2024-02-27 17:08:05 +03:00
listView.itemAlign(itemStyleBuilder)
listView.itemSize(itemStyleBuilder)
2021-09-07 17:36:50 +03:00
itemStyleBuilder.WriteString(`" onclick="listItemClickEvent(this, event)"`)
itemStyle := itemStyleBuilder.String()
current := GetCurrent(listView)
2021-09-07 17:36:50 +03:00
for i := 0; i < count; i++ {
buffer.WriteString(`<div id="`)
buffer.WriteString(listViewID)
buffer.WriteRune('-')
buffer.WriteString(strconv.Itoa(i))
buffer.WriteString(`" class="ruiView `)
buffer.WriteString(listView.listItemStyle())
if i == current {
buffer.WriteRune(' ')
buffer.WriteString(listView.currentInactiveStyle())
}
buffer.WriteString(`" `)
buffer.WriteString(itemStyle)
if ext, ok := listView.adapter.(ListItemEnabled); ok {
if !ext.IsListItemEnabled(i) {
buffer.WriteString(` data-disabled="1"`)
}
2023-05-03 15:16:03 +03:00
}
2021-09-07 17:36:50 +03:00
buffer.WriteString(`>`)
if view := listView.getItemView(i); view != nil {
//view.setNoResizeEvent()
viewHTML(view, buffer)
} else {
buffer.WriteString("ERROR: invalid item view")
}
buffer.WriteString(`</div>`)
}
}
func (listView *listViewData) updateCheckboxItem(index int, checked bool) {
checkbox := GetListViewCheckbox(listView)
hCheckboxAlign := GetListViewCheckboxHorizontalAlign(listView)
vCheckboxAlign := GetListViewCheckboxVerticalAlign(listView)
2024-02-27 17:08:05 +03:00
onDiv, offDiv, contentDiv := listView.getDivs(checkbox, hCheckboxAlign, vCheckboxAlign)
2021-09-07 17:36:50 +03:00
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
2024-02-27 17:08:05 +03:00
buffer.WriteString(listView.checkboxItemDiv(checkbox, hCheckboxAlign, vCheckboxAlign))
2021-09-07 17:36:50 +03:00
if checked {
buffer.WriteString(onDiv)
} else {
buffer.WriteString(offDiv)
}
buffer.WriteString(contentDiv)
session := listView.Session()
if listView.adapter != nil {
if view := listView.getItemView(index); view != nil {
view.setNoResizeEvent()
viewHTML(view, buffer)
} else {
buffer.WriteString("ERROR: invalid item view")
}
}
2022-10-29 20:16:40 +03:00
buffer.WriteString(`</div></div>`)
2022-10-30 17:22:33 +03:00
session.updateInnerHTML(listView.htmlID()+"-"+strconv.Itoa(index), buffer.String())
2021-09-07 17:36:50 +03:00
}
2021-11-11 13:23:41 +03:00
func (listView *listViewData) htmlProperties(self View, buffer *strings.Builder) {
2021-11-22 15:20:53 +03:00
listView.viewData.htmlProperties(self, buffer)
2021-11-11 13:23:41 +03:00
buffer.WriteString(`onfocus="listViewFocusEvent(this, event)" onblur="listViewBlurEvent(this, event)"`)
buffer.WriteString(` onkeydown="listViewKeyDownEvent(this, event)" data-focusitemstyle="`)
buffer.WriteString(listView.currentStyle())
buffer.WriteString(`" data-bluritemstyle="`)
buffer.WriteString(listView.currentInactiveStyle())
buffer.WriteString(`"`)
current := GetCurrent(listView)
2021-11-11 13:23:41 +03:00
if listView.adapter != nil && current >= 0 && current < listView.adapter.ListSize() {
buffer.WriteString(` data-current="`)
buffer.WriteString(listView.htmlID())
buffer.WriteRune('-')
buffer.WriteString(strconv.Itoa(current))
buffer.WriteRune('"')
}
listView.viewData.htmlProperties(self, buffer)
2021-11-11 13:23:41 +03:00
}
/*
func (listView *listViewData) cssStyle(self View, builder cssBuilder) {
listView.viewData.cssStyle(self, builder)
if GetListWrap(listView) != WrapOff {
switch GetListOrientation(listView) {
2021-11-11 13:23:41 +03:00
case TopDownOrientation, BottomUpOrientation:
builder.add(`max-height`, `100%`)
default:
builder.add(`max-width`, `100%`)
}
2021-09-07 17:36:50 +03:00
}
2021-11-11 13:23:41 +03:00
}
*/
func (listView *listViewData) htmlSubviews(self View, buffer *strings.Builder) {
if listView.adapter == nil || listView.adapter.ListSize() == 0 {
2021-09-07 17:36:50 +03:00
return
}
if !listView.session.ignoreViewUpdates() {
listView.session.setIgnoreViewUpdates(true)
defer listView.session.setIgnoreViewUpdates(false)
}
2021-11-11 13:23:41 +03:00
buffer.WriteString(`<div style="display: flex; align-content: stretch;`)
if gap := GetListRowGap(listView); gap.Type != Auto {
buffer.WriteString(` row-gap: `)
buffer.WriteString(gap.cssString("0", listView.Session()))
buffer.WriteRune(';')
}
if gap := GetListColumnGap(listView); gap.Type != Auto {
buffer.WriteString(` column-gap: `)
buffer.WriteString(gap.cssString("0", listView.Session()))
buffer.WriteRune(';')
}
wrap := GetListWrap(listView)
orientation := GetListOrientation(listView)
2021-11-11 13:23:41 +03:00
rows := (orientation == StartToEndOrientation || orientation == EndToStartOrientation)
if rows {
if wrap == ListWrapOff {
2021-11-11 13:23:41 +03:00
buffer.WriteString(` min-width: 100%; height: 100%;`)
} else {
buffer.WriteString(` width: 100%; min-height: 100%;`)
}
} else {
if wrap == ListWrapOff {
2021-11-11 13:23:41 +03:00
buffer.WriteString(` width: 100%; min-height: 100%;`)
} else {
buffer.WriteString(` min-width: 100%; height: 100%;`)
}
}
buffer.WriteString(` flex-flow: `)
buffer.WriteString(enumProperties[Orientation].cssValues[orientation])
switch wrap {
case ListWrapOn:
2021-11-11 13:23:41 +03:00
buffer.WriteString(` wrap;`)
case ListWrapReverse:
2021-11-11 13:23:41 +03:00
buffer.WriteString(` wrap-reverse;`)
default:
buffer.WriteString(`;`)
}
var hAlignTag, vAlignTag string
if rows {
hAlignTag = `justify-content`
vAlignTag = `align-items`
} else {
hAlignTag = `align-items`
vAlignTag = `justify-content`
}
value := ""
switch GetListHorizontalAlign(listView) {
2022-07-28 12:41:50 +03:00
case LeftAlign:
if (!rows && wrap == ListWrapReverse) || orientation == EndToStartOrientation {
value = `flex-end`
} else {
value = `flex-start`
}
case RightAlign:
if (!rows && wrap == ListWrapReverse) || orientation == EndToStartOrientation {
value = `flex-start`
} else {
value = `flex-end`
}
case CenterAlign:
value = `center`
2021-11-11 13:23:41 +03:00
2022-07-28 12:41:50 +03:00
case StretchAlign:
if rows {
value = `space-between`
} else {
value = `stretch`
2021-11-11 13:23:41 +03:00
}
}
if value != "" {
buffer.WriteRune(' ')
buffer.WriteString(hAlignTag)
buffer.WriteString(`: `)
buffer.WriteString(value)
buffer.WriteRune(';')
}
value = ""
switch GetListVerticalAlign(listView) {
2022-07-28 12:41:50 +03:00
case TopAlign:
if (rows && wrap == ListWrapReverse) || orientation == BottomUpOrientation {
value = `flex-end`
} else {
value = `flex-start`
}
case BottomAlign:
if (rows && wrap == ListWrapReverse) || orientation == BottomUpOrientation {
value = `flex-start`
} else {
value = `flex-end`
}
case CenterAlign:
value = `center`
2021-11-11 13:23:41 +03:00
2022-07-28 12:41:50 +03:00
case StretchAlign:
if rows {
value = `stretch`
} else {
value = `space-between`
2021-11-11 13:23:41 +03:00
}
}
if value != "" {
buffer.WriteRune(' ')
buffer.WriteString(vAlignTag)
buffer.WriteString(`: `)
buffer.WriteString(value)
buffer.WriteRune(';')
}
buffer.WriteString(`">`)
checkbox := GetListViewCheckbox(listView)
2021-09-07 17:36:50 +03:00
if checkbox == NoneCheckbox {
2024-02-27 17:08:05 +03:00
listView.noneCheckboxSubviews(buffer)
2021-09-07 17:36:50 +03:00
} else {
2024-02-27 17:08:05 +03:00
listView.checkboxSubviews(buffer, checkbox)
2021-09-07 17:36:50 +03:00
}
2021-11-11 13:23:41 +03:00
buffer.WriteString(`</div>`)
2021-09-07 17:36:50 +03:00
}
func (listView *listViewData) handleCommand(self View, command string, data DataObject) bool {
switch command {
case "itemSelected":
if number, ok := dataIntProperty(data, `number`); ok {
listView.properties[Current] = number
for _, listener := range listView.selectedListeners {
listener(listView, number)
2021-09-07 17:36:50 +03:00
}
listView.propertyChangedEvent(Current)
2021-09-07 17:36:50 +03:00
}
case "itemUnselected":
if _, ok := listView.properties[Current]; ok {
delete(listView.properties, Current)
for _, listener := range listView.selectedListeners {
listener(listView, -1)
}
listView.propertyChangedEvent(Current)
2021-09-07 17:36:50 +03:00
}
case "itemClick":
listView.onItemClick()
default:
return listView.viewData.handleCommand(self, command, data)
}
return true
}
func (listView *listViewData) onItemClick() {
current := GetCurrent(listView)
if current >= 0 && !IsDisabled(listView) {
checkbox := GetListViewCheckbox(listView)
2021-09-07 17:36:50 +03:00
m:
switch checkbox {
case SingleCheckbox:
if len(listView.checkedItem) == 0 {
listView.checkedItem = []int{current}
listView.updateCheckboxItem(current, true)
} else if listView.checkedItem[0] != current {
listView.updateCheckboxItem(listView.checkedItem[0], false)
listView.checkedItem[0] = current
listView.updateCheckboxItem(current, true)
}
case MultipleCheckbox:
for i, index := range listView.checkedItem {
if index == current {
listView.updateCheckboxItem(index, false)
count := len(listView.checkedItem)
if count == 1 {
listView.checkedItem = []int{}
} else if i == 0 {
listView.checkedItem = listView.checkedItem[1:]
} else if i == count-1 {
listView.checkedItem = listView.checkedItem[:i]
} else {
listView.checkedItem = append(listView.checkedItem[:i], listView.checkedItem[i+1:]...)
}
break m
}
}
listView.updateCheckboxItem(current, true)
listView.checkedItem = append(listView.checkedItem, current)
}
if checkbox != NoneCheckbox {
for _, listener := range listView.checkedListeners {
listener(listView, listView.checkedItem)
}
listView.propertyChangedEvent(Checked)
2021-09-07 17:36:50 +03:00
}
for _, listener := range listView.clickedListeners {
listener(listView, current)
}
}
}
func (listView *listViewData) onItemResize(self View, index string, x, y, width, height float64) {
n, err := strconv.Atoi(index)
if err != nil {
ErrorLog(err.Error())
} else if n >= 0 && n < len(listView.itemFrame) {
listView.itemFrame[n] = Frame{Left: x, Top: y, Width: width, Height: height}
} else {
ErrorLogF(`Invalid ListView item index: %d`, n)
2021-09-07 17:36:50 +03:00
}
}
// GetVerticalAlign return the vertical align of a list: 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 GetVerticalAlign(view View, subviewID ...string) int {
2022-08-24 15:00:25 +03:00
return enumStyledProperty(view, subviewID, VerticalAlign, TopAlign, false)
2021-09-07 17:36:50 +03:00
}
2022-08-24 15:00:25 +03:00
// GetHorizontalAlign return the vertical align of a list/checkbox: 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 GetHorizontalAlign(view View, subviewID ...string) int {
2022-08-24 15:00:25 +03:00
return enumStyledProperty(view, subviewID, HorizontalAlign, LeftAlign, false)
2021-09-07 17:36:50 +03:00
}
// GetListItemClickedListeners returns a ListItemClickedListener of the ListView.
// If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetListItemClickedListeners(view View, subviewID ...string) []func(ListView, int) {
return getEventListeners[ListView, int](view, subviewID, ListItemClickedEvent)
2021-09-07 17:36:50 +03:00
}
// GetListItemSelectedListeners returns a ListItemSelectedListener of the ListView.
// If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetListItemSelectedListeners(view View, subviewID ...string) []func(ListView, int) {
return getEventListeners[ListView, int](view, subviewID, ListItemSelectedEvent)
2021-09-07 17:36:50 +03:00
}
// GetListItemCheckedListeners returns a ListItemCheckedListener of the ListView.
// If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetListItemCheckedListeners(view View, subviewID ...string) []func(ListView, []int) {
return getEventListeners[ListView, []int](view, subviewID, ListItemCheckedEvent)
2021-09-07 17:36:50 +03:00
}
// GetListItemWidth returns the width of a ListView item.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetListItemWidth(view View, subviewID ...string) SizeUnit {
return sizeStyledProperty(view, subviewID, ItemWidth, false)
2021-09-07 17:36:50 +03:00
}
// GetListItemHeight returns the height of a ListView item.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetListItemHeight(view View, subviewID ...string) SizeUnit {
return sizeStyledProperty(view, subviewID, ItemHeight, false)
2021-09-07 17:36:50 +03:00
}
// GetListViewCheckbox returns the ListView checkbox type: NoneCheckbox (0), SingleCheckbox (1), or MultipleCheckbox (2).
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetListViewCheckbox(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, ItemCheckbox, 0, false)
2021-09-07 17:36:50 +03:00
}
// GetListViewCheckedItems returns the array of ListView checked items.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetListViewCheckedItems(view View, subviewID ...string) []int {
if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0])
2021-09-07 17:36:50 +03:00
}
2021-09-07 17:36:50 +03:00
if view != nil {
if listView, ok := view.(ListView); ok {
checkedItems := listView.getCheckedItems()
switch GetListViewCheckbox(view) {
2021-09-07 17:36:50 +03:00
case NoneCheckbox:
return []int{}
case SingleCheckbox:
if len(checkedItems) > 1 {
return []int{checkedItems[0]}
}
}
return checkedItems
}
}
return []int{}
}
// IsListViewCheckedItem returns true if the ListView item with index is checked, false otherwise.
// If the second argument (subviewID) is "" then a value from the first argument (view) is returned.
func IsListViewCheckedItem(view View, subviewID string, index int) bool {
for _, n := range GetListViewCheckedItems(view, subviewID) {
if n == index {
return true
}
}
return false
}
// GetListViewCheckboxVerticalAlign returns the vertical align of the ListView checkbox:
// TopAlign (0), BottomAlign (1), CenterAlign (2)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetListViewCheckboxVerticalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, CheckboxVerticalAlign, TopAlign, false)
2021-09-07 17:36:50 +03:00
}
// GetListViewCheckboxHorizontalAlign returns the horizontal align of the ListView checkbox:
// LeftAlign (0), RightAlign (1), CenterAlign (2)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetListViewCheckboxHorizontalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, CheckboxHorizontalAlign, LeftAlign, false)
2021-09-07 17:36:50 +03:00
}
// GetListItemVerticalAlign returns the vertical align of the ListView item content:
// TopAlign (0), BottomAlign (1), CenterAlign (2)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetListItemVerticalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, ItemVerticalAlign, TopAlign, false)
2021-09-07 17:36:50 +03:00
}
// ItemHorizontalAlign returns the horizontal align of the ListView item 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 GetListItemHorizontalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, ItemHorizontalAlign, LeftAlign, false)
2021-09-07 17:36:50 +03:00
}
// GetListItemFrame - returns the location and size of the ListView item in pixels.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
2021-09-07 17:36:50 +03:00
func GetListItemFrame(view View, subviewID string, index int) Frame {
if subviewID != "" {
view = ViewByID(view, subviewID)
}
if view != nil {
if listView, ok := view.(ListView); ok {
itemFrames := listView.getItemFrames()
if index >= 0 && index < len(itemFrames) {
return itemFrames[index]
}
}
}
return Frame{Left: 0, Top: 0, Width: 0, Height: 0}
}
// GetListViewAdapter - returns the ListView adapter.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetListViewAdapter(view View, subviewID ...string) ListAdapter {
if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0])
2021-09-07 17:36:50 +03:00
}
if view != nil {
if value := view.Get(Items); value != nil {
if adapter, ok := value.(ListAdapter); ok {
return adapter
}
}
}
return nil
}
// ReloadListViewData updates ListView content
// If the second argument (subviewID) is not specified or it is "" then content the first argument (view) is updated.
func ReloadListViewData(view View, subviewID ...string) {
if len(subviewID) > 0 && subviewID[0] != "" {
view = ViewByID(view, subviewID[0])
2021-09-07 17:36:50 +03:00
}
2021-09-07 17:36:50 +03:00
if view != nil {
if listView, ok := view.(ListView); ok {
listView.ReloadListViewData()
}
}
}