2021-09-07 17:36:50 +03:00
|
|
|
package rui
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// DefaultAnimation - default animation of StackLayout push
|
|
|
|
DefaultAnimation = 0
|
|
|
|
// StartToEndAnimation - start to end animation of StackLayout push
|
|
|
|
StartToEndAnimation = 1
|
|
|
|
// EndToStartAnimation - end to start animation of StackLayout push
|
|
|
|
EndToStartAnimation = 2
|
|
|
|
// TopDownAnimation - top down animation of StackLayout push
|
|
|
|
TopDownAnimation = 3
|
|
|
|
// BottomUpAnimation - bottom up animation of StackLayout push
|
|
|
|
BottomUpAnimation = 4
|
|
|
|
)
|
|
|
|
|
|
|
|
// StackLayout - list-container of View
|
|
|
|
type StackLayout interface {
|
|
|
|
ViewsContainer
|
|
|
|
Peek() View
|
|
|
|
MoveToFront(view View) bool
|
|
|
|
MoveToFrontByID(viewID string) bool
|
|
|
|
Push(view View, animation int, onPushFinished func())
|
|
|
|
Pop(animation int, onPopFinished func(View)) bool
|
|
|
|
}
|
|
|
|
|
|
|
|
type stackLayoutData struct {
|
|
|
|
viewsContainerData
|
2021-11-20 14:22:00 +03:00
|
|
|
peek int
|
2021-09-07 17:36:50 +03:00
|
|
|
pushView, popView View
|
|
|
|
animationType int
|
|
|
|
onPushFinished func()
|
|
|
|
onPopFinished func(View)
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewStackLayout create new StackLayout object and return it
|
|
|
|
func NewStackLayout(session Session, params Params) StackLayout {
|
|
|
|
view := new(stackLayoutData)
|
2022-09-01 11:04:50 +03:00
|
|
|
view.init(session)
|
2021-09-07 17:36:50 +03:00
|
|
|
setInitParams(view, params)
|
|
|
|
return view
|
|
|
|
}
|
|
|
|
|
|
|
|
func newStackLayout(session Session) View {
|
|
|
|
return NewStackLayout(session, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Init initialize fields of ViewsContainer by default values
|
2022-09-01 11:04:50 +03:00
|
|
|
func (layout *stackLayoutData) init(session Session) {
|
|
|
|
layout.viewsContainerData.init(session)
|
2021-09-07 17:36:50 +03:00
|
|
|
layout.tag = "StackLayout"
|
|
|
|
layout.systemClass = "ruiStackLayout"
|
2021-10-04 17:58:17 +03:00
|
|
|
layout.properties[TransitionEndEvent] = []func(View, string){layout.pushFinished, layout.popFinished}
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
|
2022-05-22 12:54:02 +03:00
|
|
|
func (layout *stackLayoutData) String() string {
|
|
|
|
return getViewString(layout)
|
|
|
|
}
|
|
|
|
|
2021-10-04 17:58:17 +03:00
|
|
|
func (layout *stackLayoutData) pushFinished(view View, tag string) {
|
|
|
|
if tag == "ruiPush" {
|
2021-09-07 17:36:50 +03:00
|
|
|
if layout.pushView != nil {
|
|
|
|
layout.pushView = nil
|
|
|
|
count := len(layout.views)
|
|
|
|
if count > 0 {
|
2021-11-20 14:22:00 +03:00
|
|
|
layout.peek = count - 1
|
2021-09-07 17:36:50 +03:00
|
|
|
} else {
|
|
|
|
layout.peek = 0
|
|
|
|
}
|
|
|
|
updateInnerHTML(layout.htmlID(), layout.session)
|
2021-11-20 14:22:00 +03:00
|
|
|
layout.propertyChangedEvent(Current)
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
2021-10-04 17:58:17 +03:00
|
|
|
|
2021-09-07 17:36:50 +03:00
|
|
|
if layout.onPushFinished != nil {
|
|
|
|
onPushFinished := layout.onPushFinished
|
|
|
|
layout.onPushFinished = nil
|
|
|
|
onPushFinished()
|
|
|
|
}
|
2021-10-04 17:58:17 +03:00
|
|
|
}
|
|
|
|
}
|
2021-09-07 17:36:50 +03:00
|
|
|
|
2021-10-04 17:58:17 +03:00
|
|
|
func (layout *stackLayoutData) popFinished(view View, tag string) {
|
|
|
|
if tag == "ruiPop" {
|
2021-09-07 17:36:50 +03:00
|
|
|
popView := layout.popView
|
|
|
|
layout.popView = nil
|
|
|
|
updateInnerHTML(layout.htmlID(), layout.session)
|
|
|
|
if layout.onPopFinished != nil {
|
|
|
|
onPopFinished := layout.onPopFinished
|
|
|
|
layout.onPopFinished = nil
|
|
|
|
onPopFinished(popView)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-26 18:36:00 +03:00
|
|
|
func (layout *stackLayoutData) Set(tag string, value any) bool {
|
2021-11-20 11:15:28 +03:00
|
|
|
return layout.set(strings.ToLower(tag), value)
|
|
|
|
}
|
|
|
|
|
2022-07-26 18:36:00 +03:00
|
|
|
func (layout *stackLayoutData) set(tag string, value any) bool {
|
2021-11-20 14:22:00 +03:00
|
|
|
if value == nil {
|
|
|
|
layout.remove(tag)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
switch tag {
|
|
|
|
case TransitionEndEvent:
|
2022-07-27 20:31:57 +03:00
|
|
|
listeners, ok := valueToEventListeners[View, string](value)
|
|
|
|
if ok && listeners != nil {
|
2021-10-04 17:58:17 +03:00
|
|
|
listeners = append(listeners, layout.pushFinished)
|
|
|
|
listeners = append(listeners, layout.popFinished)
|
|
|
|
layout.properties[TransitionEndEvent] = listeners
|
2021-11-20 11:15:28 +03:00
|
|
|
layout.propertyChangedEvent(TransitionEndEvent)
|
2021-10-04 17:58:17 +03:00
|
|
|
}
|
|
|
|
return ok
|
2021-11-20 14:22:00 +03:00
|
|
|
|
|
|
|
case Current:
|
|
|
|
setCurrent := func(index int) {
|
|
|
|
if index != layout.peek {
|
|
|
|
if layout.peek < len(layout.views) {
|
2022-10-30 17:22:33 +03:00
|
|
|
layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(layout.peek), "visibility", "hidden")
|
2021-11-20 14:22:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
layout.peek = index
|
2022-10-30 17:22:33 +03:00
|
|
|
layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(index), "visibility", "visible")
|
2021-11-20 14:22:00 +03:00
|
|
|
layout.propertyChangedEvent(Current)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
switch value := value.(type) {
|
|
|
|
case string:
|
|
|
|
text, ok := layout.session.resolveConstants(value)
|
|
|
|
if !ok {
|
|
|
|
invalidPropertyValue(tag, value)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
n, err := strconv.Atoi(strings.Trim(text, " \t"))
|
|
|
|
if err != nil {
|
|
|
|
invalidPropertyValue(tag, value)
|
|
|
|
ErrorLog(err.Error())
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
setCurrent(n)
|
|
|
|
|
|
|
|
default:
|
|
|
|
n, ok := isInt(value)
|
|
|
|
if !ok {
|
|
|
|
notCompatibleType(tag, value)
|
|
|
|
return false
|
|
|
|
} else if n < 0 || n >= len(layout.views) {
|
|
|
|
ErrorLogF(`The view index "%d" of "%s" property is out of range`, n, tag)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
setCurrent(n)
|
|
|
|
}
|
|
|
|
return true
|
2021-10-04 17:58:17 +03:00
|
|
|
}
|
2021-11-20 11:15:28 +03:00
|
|
|
return layout.viewsContainerData.set(tag, value)
|
2021-10-04 17:58:17 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (layout *stackLayoutData) Remove(tag string) {
|
2021-11-20 11:15:28 +03:00
|
|
|
layout.remove(strings.ToLower(tag))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (layout *stackLayoutData) remove(tag string) {
|
2021-11-20 14:22:00 +03:00
|
|
|
switch tag {
|
|
|
|
case TransitionEndEvent:
|
2021-10-04 17:58:17 +03:00
|
|
|
layout.properties[TransitionEndEvent] = []func(View, string){layout.pushFinished, layout.popFinished}
|
2021-11-20 11:15:28 +03:00
|
|
|
layout.propertyChangedEvent(TransitionEndEvent)
|
2021-11-20 14:22:00 +03:00
|
|
|
|
|
|
|
case Current:
|
|
|
|
layout.set(Current, 0)
|
|
|
|
|
|
|
|
default:
|
2021-11-20 11:15:28 +03:00
|
|
|
layout.viewsContainerData.remove(tag)
|
2021-10-04 17:58:17 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-26 18:36:00 +03:00
|
|
|
func (layout *stackLayoutData) Get(tag string) any {
|
2021-11-20 14:22:00 +03:00
|
|
|
return layout.get(strings.ToLower(tag))
|
|
|
|
}
|
|
|
|
|
2022-07-26 18:36:00 +03:00
|
|
|
func (layout *stackLayoutData) get(tag string) any {
|
2021-11-20 14:22:00 +03:00
|
|
|
if tag == Current {
|
|
|
|
return layout.peek
|
|
|
|
}
|
|
|
|
return layout.viewsContainerData.get(tag)
|
|
|
|
}
|
|
|
|
|
2021-09-07 17:36:50 +03:00
|
|
|
func (layout *stackLayoutData) Peek() View {
|
|
|
|
if int(layout.peek) < len(layout.views) {
|
|
|
|
return layout.views[layout.peek]
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (layout *stackLayoutData) MoveToFront(view View) bool {
|
|
|
|
peek := int(layout.peek)
|
|
|
|
htmlID := view.htmlID()
|
|
|
|
for i, view2 := range layout.views {
|
|
|
|
if view2.htmlID() == htmlID {
|
|
|
|
if i != peek {
|
|
|
|
if peek < len(layout.views) {
|
2022-10-30 17:22:33 +03:00
|
|
|
layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(peek), "visibility", "hidden")
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
|
2021-11-20 14:22:00 +03:00
|
|
|
layout.peek = i
|
2022-10-30 17:22:33 +03:00
|
|
|
layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(i), "visibility", "visible")
|
2021-11-20 14:22:00 +03:00
|
|
|
layout.propertyChangedEvent(Current)
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ErrorLog(`MoveToFront() fail. Subview not found."`)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (layout *stackLayoutData) MoveToFrontByID(viewID string) bool {
|
|
|
|
peek := int(layout.peek)
|
|
|
|
for i, view := range layout.views {
|
|
|
|
if view.ID() == viewID {
|
|
|
|
if i != peek {
|
|
|
|
if peek < len(layout.views) {
|
2022-10-30 17:22:33 +03:00
|
|
|
layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(peek), "visibility", "hidden")
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
|
2021-11-20 14:22:00 +03:00
|
|
|
layout.peek = i
|
2022-10-30 17:22:33 +03:00
|
|
|
layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(i), "visibility", "visible")
|
2021-11-20 14:22:00 +03:00
|
|
|
layout.propertyChangedEvent(Current)
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ErrorLogF(`MoveToFront("%s") fail. Subview with "%s" not found."`, viewID, viewID)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (layout *stackLayoutData) Append(view View) {
|
|
|
|
if view != nil {
|
2021-11-20 14:22:00 +03:00
|
|
|
layout.peek = len(layout.views)
|
2021-09-07 17:36:50 +03:00
|
|
|
layout.viewsContainerData.Append(view)
|
2021-11-20 14:22:00 +03:00
|
|
|
layout.propertyChangedEvent(Current)
|
2021-09-07 17:36:50 +03:00
|
|
|
} else {
|
|
|
|
ErrorLog("StackLayout.Append(nil, ....) is forbidden")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-20 14:22:00 +03:00
|
|
|
func (layout *stackLayoutData) Insert(view View, index int) {
|
2021-09-07 17:36:50 +03:00
|
|
|
if view != nil {
|
2021-11-20 14:22:00 +03:00
|
|
|
count := len(layout.views)
|
2021-09-07 17:36:50 +03:00
|
|
|
if index < count {
|
2021-11-20 14:22:00 +03:00
|
|
|
layout.peek = int(index)
|
2021-09-07 17:36:50 +03:00
|
|
|
} else {
|
|
|
|
layout.peek = count
|
|
|
|
}
|
|
|
|
layout.viewsContainerData.Insert(view, index)
|
2021-11-20 14:22:00 +03:00
|
|
|
layout.propertyChangedEvent(Current)
|
2021-09-07 17:36:50 +03:00
|
|
|
} else {
|
|
|
|
ErrorLog("StackLayout.Insert(nil, ....) is forbidden")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-20 14:22:00 +03:00
|
|
|
func (layout *stackLayoutData) RemoveView(index int) View {
|
|
|
|
if index < 0 || index >= len(layout.views) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-09-07 17:36:50 +03:00
|
|
|
if layout.peek > 0 {
|
|
|
|
layout.peek--
|
|
|
|
}
|
2021-11-20 14:22:00 +03:00
|
|
|
defer layout.propertyChangedEvent(Current)
|
2021-09-07 17:36:50 +03:00
|
|
|
return layout.viewsContainerData.RemoveView(index)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (layout *stackLayoutData) Push(view View, animation int, onPushFinished func()) {
|
|
|
|
if view == nil {
|
|
|
|
ErrorLog("StackLayout.Push(nil, ....) is forbidden")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
layout.pushView = view
|
|
|
|
layout.animationType = animation
|
2021-10-04 17:58:17 +03:00
|
|
|
//layout.animation["ruiPush"] = Animation{FinishListener: layout}
|
2021-09-07 17:36:50 +03:00
|
|
|
layout.onPushFinished = onPushFinished
|
|
|
|
|
|
|
|
htmlID := layout.htmlID()
|
|
|
|
session := layout.Session()
|
|
|
|
|
|
|
|
buffer := allocStringBuilder()
|
|
|
|
defer freeStringBuilder(buffer)
|
|
|
|
|
|
|
|
buffer.WriteString(`<div id="`)
|
|
|
|
buffer.WriteString(htmlID)
|
2022-10-29 20:48:03 +03:00
|
|
|
buffer.WriteString(`push" class="ruiStackPageLayout" ontransitionend="stackTransitionEndEvent('`)
|
2021-09-07 17:36:50 +03:00
|
|
|
buffer.WriteString(htmlID)
|
2022-10-29 20:48:03 +03:00
|
|
|
buffer.WriteString(`', 'ruiPush', event)" style="`)
|
2021-09-07 17:36:50 +03:00
|
|
|
|
|
|
|
switch layout.animationType {
|
|
|
|
case StartToEndAnimation:
|
|
|
|
buffer.WriteString(fmt.Sprintf("transform: translate(-%gpx, 0px); transition: transform ", layout.frame.Width))
|
|
|
|
|
|
|
|
case TopDownAnimation:
|
|
|
|
buffer.WriteString(fmt.Sprintf("transform: translate(0px, -%gpx); transition: transform ", layout.frame.Height))
|
|
|
|
|
|
|
|
case BottomUpAnimation:
|
|
|
|
buffer.WriteString(fmt.Sprintf("transform: translate(0px, %gpx); transition: transform ", layout.frame.Height))
|
|
|
|
|
|
|
|
default:
|
|
|
|
buffer.WriteString(fmt.Sprintf("transform: translate(%gpx, 0px); transition: transform ", layout.frame.Width))
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer.WriteString(`1s ease;">`)
|
|
|
|
|
|
|
|
viewHTML(layout.pushView, buffer)
|
|
|
|
buffer.WriteString(`</div>`)
|
|
|
|
|
2022-10-30 17:22:33 +03:00
|
|
|
session.appendToInnerHTML(htmlID, buffer.String())
|
|
|
|
layout.session.updateCSSProperty(htmlID+"push", "transform", "translate(0px, 0px)")
|
2021-09-07 17:36:50 +03:00
|
|
|
|
|
|
|
layout.views = append(layout.views, view)
|
|
|
|
view.setParentID(htmlID)
|
2021-11-20 11:15:28 +03:00
|
|
|
layout.propertyChangedEvent(Content)
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (layout *stackLayoutData) Pop(animation int, onPopFinished func(View)) bool {
|
2021-11-20 14:22:00 +03:00
|
|
|
count := len(layout.views)
|
2021-09-07 17:36:50 +03:00
|
|
|
if count == 0 || layout.peek >= count {
|
|
|
|
ErrorLog("StackLayout is empty")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
layout.popView = layout.views[layout.peek]
|
|
|
|
layout.RemoveView(layout.peek)
|
|
|
|
|
|
|
|
layout.animationType = animation
|
2021-10-04 17:58:17 +03:00
|
|
|
//layout.animation["ruiPop"] = Animation{FinishListener: layout}
|
2021-09-07 17:36:50 +03:00
|
|
|
layout.onPopFinished = onPopFinished
|
|
|
|
|
|
|
|
htmlID := layout.htmlID()
|
|
|
|
session := layout.Session()
|
|
|
|
|
|
|
|
buffer := allocStringBuilder()
|
|
|
|
defer freeStringBuilder(buffer)
|
|
|
|
|
|
|
|
buffer.WriteString(`<div id="`)
|
|
|
|
buffer.WriteString(htmlID)
|
2022-10-29 20:48:03 +03:00
|
|
|
buffer.WriteString(`pop" class="ruiStackPageLayout" ontransitionend="stackTransitionEndEvent('`)
|
2021-09-07 17:36:50 +03:00
|
|
|
buffer.WriteString(htmlID)
|
2022-10-29 20:48:03 +03:00
|
|
|
buffer.WriteString(`', 'ruiPop', event)" ontransitioncancel="stackTransitionEndEvent('`)
|
2022-08-07 18:59:56 +03:00
|
|
|
buffer.WriteString(htmlID)
|
2022-10-29 20:48:03 +03:00
|
|
|
buffer.WriteString(`', 'ruiPop', event)" style="transition: transform 1s ease;">`)
|
2021-09-07 17:36:50 +03:00
|
|
|
viewHTML(layout.popView, buffer)
|
|
|
|
buffer.WriteString(`</div>`)
|
|
|
|
|
2022-10-30 17:22:33 +03:00
|
|
|
session.appendToInnerHTML(htmlID, buffer.String())
|
2021-09-07 17:36:50 +03:00
|
|
|
|
|
|
|
var value string
|
|
|
|
switch layout.animationType {
|
|
|
|
case TopDownAnimation:
|
|
|
|
value = fmt.Sprintf("translate(0px, -%gpx)", layout.frame.Height)
|
|
|
|
|
|
|
|
case BottomUpAnimation:
|
|
|
|
value = fmt.Sprintf("translate(0px, %gpx)", layout.frame.Height)
|
|
|
|
|
|
|
|
case StartToEndAnimation:
|
|
|
|
value = fmt.Sprintf("translate(-%gpx, 0px)", layout.frame.Width)
|
|
|
|
|
|
|
|
default:
|
|
|
|
value = fmt.Sprintf("translate(%gpx, 0px)", layout.frame.Width)
|
|
|
|
}
|
|
|
|
|
2022-10-30 17:22:33 +03:00
|
|
|
layout.session.updateCSSProperty(htmlID+"pop", "transform", value)
|
2021-09-07 17:36:50 +03:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (layout *stackLayoutData) htmlSubviews(self View, buffer *strings.Builder) {
|
|
|
|
count := len(layout.views)
|
|
|
|
if count > 0 {
|
|
|
|
htmlID := layout.htmlID()
|
|
|
|
peek := int(layout.peek)
|
|
|
|
if peek >= count {
|
|
|
|
peek = count - 1
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, view := range layout.views {
|
|
|
|
buffer.WriteString(`<div id="`)
|
|
|
|
buffer.WriteString(htmlID)
|
|
|
|
buffer.WriteString(`page`)
|
|
|
|
buffer.WriteString(strconv.Itoa(i))
|
|
|
|
buffer.WriteString(`" class="ruiStackPageLayout"`)
|
|
|
|
if i != peek {
|
|
|
|
buffer.WriteString(` style="visibility: hidden;"`)
|
|
|
|
}
|
|
|
|
buffer.WriteString(`>`)
|
|
|
|
viewHTML(view, buffer)
|
|
|
|
buffer.WriteString(`</div>`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|