rui_orig/stackLayout.go

437 lines
12 KiB
Go
Raw Permalink Normal View History

2021-09-07 17:36:50 +03:00
package rui
import (
"fmt"
"strconv"
"strings"
)
// Constants which represent [StackLayout] animation type during pushing or popping views
2021-09-07 17:36:50 +03:00
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 represents a StackLayout view
2021-09-07 17:36:50 +03:00
type StackLayout interface {
ViewsContainer
2024-04-23 19:34:36 +03:00
// Peek returns the current (visible) View. If StackLayout is empty then it returns nil.
2021-09-07 17:36:50 +03:00
Peek() View
2024-04-23 19:34:36 +03:00
// RemovePeek removes the current View and returns it. If StackLayout is empty then it doesn't do anything and returns nil.
RemovePeek() View
// MoveToFront makes the given View current. Returns true if successful, false otherwise.
2021-09-07 17:36:50 +03:00
MoveToFront(view View) bool
2024-04-23 19:34:36 +03:00
// MoveToFrontByID makes the View current by viewID. Returns true if successful, false otherwise.
2021-09-07 17:36:50 +03:00
MoveToFrontByID(viewID string) bool
2024-04-23 19:34:36 +03:00
// Push adds a new View to the container and makes it current.
// It is similar to Append, but the addition is done using an animation effect.
// The animation type is specified by the second argument and can take the following values:
// * DefaultAnimation (0) - Default animation. For the Push function it is EndToStartAnimation, for Pop - StartToEndAnimation;
// * StartToEndAnimation (1) - Animation from beginning to end. The beginning and the end are determined by the direction of the text output;
// * EndToStartAnimation (2) - End-to-Beginning animation;
// * TopDownAnimation (3) - Top-down animation;
// * BottomUpAnimation (4) - Bottom up animation.
// The third argument `onPushFinished` is the function to be called when the animation ends. It may be nil.
2021-09-07 17:36:50 +03:00
Push(view View, animation int, onPushFinished func())
2024-04-23 19:34:36 +03:00
// Pop removes the current View from the container using animation.
// The second argument `onPopFinished`` is the function to be called when the animation ends. It may be nil.
// The function will return false if the StackLayout is empty and true if the current item has been removed.
2021-09-07 17:36:50 +03:00
Pop(animation int, onPopFinished func(View)) bool
}
type stackLayoutData struct {
viewsContainerData
2024-11-13 12:56:39 +03:00
peek, prevPeek 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)
view.init(session)
2021-09-07 17:36:50 +03:00
setInitParams(view, params)
return view
}
func newStackLayout(session Session) View {
2024-11-13 12:56:39 +03:00
//return NewStackLayout(session, nil)
return new(stackLayoutData)
2021-09-07 17:36:50 +03:00
}
// Init initialize fields of ViewsContainer by default values
func (layout *stackLayoutData) init(session Session) {
layout.viewsContainerData.init(session)
2021-09-07 17:36:50 +03:00
layout.tag = "StackLayout"
layout.systemClass = "ruiStackLayout"
2024-11-22 16:36:08 +03:00
layout.properties[TransitionEndEvent] = []func(View, PropertyName){layout.pushFinished, layout.popFinished}
layout.get = layout.getFunc
2024-11-13 12:56:39 +03:00
layout.set = layout.setFunc
layout.remove = layout.removeFunc
layout.changed = layout.propertyChanged
2022-05-22 12:54:02 +03:00
}
2024-11-22 16:36:08 +03:00
func (layout *stackLayoutData) pushFinished(view View, tag PropertyName) {
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 {
layout.peek = count - 1
2021-09-07 17:36:50 +03:00
} else {
layout.peek = 0
}
updateInnerHTML(layout.htmlID(), layout.session)
2024-11-13 12:56:39 +03:00
layout.currentChanged()
2021-09-07 17:36:50 +03:00
}
2021-09-07 17:36:50 +03:00
if layout.onPushFinished != nil {
onPushFinished := layout.onPushFinished
layout.onPushFinished = nil
onPushFinished()
}
}
}
2021-09-07 17:36:50 +03:00
2024-11-22 16:36:08 +03:00
func (layout *stackLayoutData) popFinished(view View, tag PropertyName) {
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)
}
}
}
func (layout *stackLayoutData) setFunc(tag PropertyName, value any) []PropertyName {
switch tag {
case TransitionEndEvent:
2024-11-22 16:36:08 +03:00
listeners, ok := valueToOneArgEventListeners[View, PropertyName](value)
2022-07-27 20:31:57 +03:00
if ok && listeners != nil {
listeners = append(listeners, layout.pushFinished)
listeners = append(listeners, layout.popFinished)
layout.setRaw(TransitionEndEvent, listeners)
2024-11-13 12:56:39 +03:00
return []PropertyName{tag}
}
2024-11-13 12:56:39 +03:00
return nil
case Current:
2024-11-13 12:56:39 +03:00
newCurrent := 0
switch value := value.(type) {
case string:
text, ok := layout.session.resolveConstants(value)
if !ok {
invalidPropertyValue(tag, value)
2024-11-13 12:56:39 +03:00
return nil
}
n, err := strconv.Atoi(strings.Trim(text, " \t"))
if err != nil {
invalidPropertyValue(tag, value)
ErrorLog(err.Error())
2024-11-13 12:56:39 +03:00
return nil
}
2024-11-13 12:56:39 +03:00
newCurrent = n
default:
n, ok := isInt(value)
if !ok {
notCompatibleType(tag, value)
2024-11-13 12:56:39 +03:00
return nil
} else if n < 0 || n >= len(layout.views) {
ErrorLogF(`The view index "%d" of "%s" property is out of range`, n, tag)
2024-11-13 12:56:39 +03:00
return nil
}
2024-11-13 12:56:39 +03:00
newCurrent = n
}
2024-11-13 12:56:39 +03:00
layout.prevPeek = layout.peek
if newCurrent == layout.peek {
return []PropertyName{}
}
layout.peek = newCurrent
return []PropertyName{tag}
}
return layout.viewsContainerData.setFunc(tag, value)
}
func (layout *stackLayoutData) propertyChanged(tag PropertyName) {
2024-11-13 12:56:39 +03:00
switch tag {
case Current:
if layout.prevPeek != layout.peek {
if layout.prevPeek < len(layout.views) {
layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(layout.prevPeek), "visibility", "hidden")
}
layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(layout.peek), "visibility", "visible")
2024-11-13 12:56:39 +03:00
layout.prevPeek = layout.peek
}
default:
layout.viewsContainerData.propertyChanged(tag)
2024-11-13 12:56:39 +03:00
}
}
func (layout *stackLayoutData) removeFunc(tag PropertyName) []PropertyName {
switch tag {
case TransitionEndEvent:
2024-11-22 16:36:08 +03:00
layout.setRaw(TransitionEndEvent, []func(View, PropertyName){layout.pushFinished, layout.popFinished})
2024-11-13 12:56:39 +03:00
return []PropertyName{tag}
case Current:
layout.setRaw(Current, 0)
2024-11-13 12:56:39 +03:00
return []PropertyName{tag}
}
return layout.viewsContainerData.removeFunc(tag)
}
func (layout *stackLayoutData) getFunc(tag PropertyName) any {
if tag == Current {
return layout.peek
}
return layout.viewsContainerData.getFunc(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
}
layout.peek = i
2022-10-30 17:22:33 +03:00
layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(i), "visibility", "visible")
2024-11-13 12:56:39 +03:00
layout.currentChanged()
2021-09-07 17:36:50 +03:00
}
return true
}
}
ErrorLog(`MoveToFront() fail. Subview not found."`)
return false
}
2024-11-13 12:56:39 +03:00
func (layout *stackLayoutData) currentChanged() {
if listener, ok := layout.changeListener[Current]; ok {
listener(layout, Current)
}
}
2021-09-07 17:36:50 +03:00
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
}
layout.peek = i
2022-10-30 17:22:33 +03:00
layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(i), "visibility", "visible")
2024-11-13 12:56:39 +03:00
layout.currentChanged()
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 {
layout.peek = len(layout.views)
2021-09-07 17:36:50 +03:00
layout.viewsContainerData.Append(view)
2024-11-13 12:56:39 +03:00
layout.currentChanged()
2021-09-07 17:36:50 +03:00
} else {
ErrorLog("StackLayout.Append(nil, ....) is forbidden")
}
}
func (layout *stackLayoutData) Insert(view View, index int) {
2021-09-07 17:36:50 +03:00
if view != nil {
count := len(layout.views)
2021-09-07 17:36:50 +03:00
if index < count {
layout.peek = int(index)
2021-09-07 17:36:50 +03:00
} else {
layout.peek = count
}
layout.viewsContainerData.Insert(view, index)
2024-11-13 12:56:39 +03:00
layout.currentChanged()
2021-09-07 17:36:50 +03:00
} else {
ErrorLog("StackLayout.Insert(nil, ....) is forbidden")
}
}
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--
}
2024-11-13 12:56:39 +03:00
defer layout.currentChanged()
2021-09-07 17:36:50 +03:00
return layout.viewsContainerData.RemoveView(index)
}
2024-04-23 19:34:36 +03:00
func (layout *stackLayoutData) RemovePeek() View {
return layout.RemoveView(len(layout.views) - 1)
}
2021-09-07 17:36:50 +03:00
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
//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;">`)
2024-11-21 09:25:46 +03:00
viewHTML(layout.pushView, buffer, "")
2021-09-07 17:36:50 +03:00
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)
2024-11-13 12:56:39 +03:00
if listener, ok := layout.changeListener[Content]; ok {
listener(layout, Content)
}
2021-09-07 17:36:50 +03:00
}
func (layout *stackLayoutData) Pop(animation int, onPopFinished func(View)) bool {
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
//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;">`)
2024-11-21 09:25:46 +03:00
viewHTML(layout.popView, buffer, "")
2021-09-07 17:36:50 +03:00
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(`>`)
2024-11-21 09:25:46 +03:00
viewHTML(view, buffer, "")
2021-09-07 17:36:50 +03:00
buffer.WriteString(`</div>`)
}
}
}