mirror of https://github.com/anoshenko/rui.git
Improved StackLayout push/pop animation
This commit is contained in:
parent
31c07ced98
commit
ed639c94c6
|
@ -1,9 +1,13 @@
|
||||||
# v0.18.0
|
# v0.18.0
|
||||||
|
|
||||||
* Property name type changed to PropertyName.
|
* Property name type changed from string to PropertyName.
|
||||||
* Transform interface renamed to TransformProperty. NewTransform function renamed to NewTransformProperty. TransformTag constant renamed to Transform.
|
* Transform interface renamed to TransformProperty. NewTransform function renamed to NewTransformProperty. TransformTag constant renamed to Transform.
|
||||||
* OriginX, OriginY, and OriginZ properties renamed to TransformOriginX, TransformOriginY, and TransformOriginZ
|
* OriginX, OriginY, and OriginZ properties renamed to TransformOriginX, TransformOriginY, and TransformOriginZ
|
||||||
* GetOrigin function renamed to GetTransformOrigin
|
* GetOrigin function renamed to GetTransformOrigin.
|
||||||
|
* Changed Push and Pop method of StackLayout interface.
|
||||||
|
* Removed DefaultAnimation, StartToEndAnimation, EndToStartAnimation, TopDownAnimation, and BottomUpAnimation constants.
|
||||||
|
* Added "push-transform", "push-duration", "push-timing", and "move-to-front-animation" properties.
|
||||||
|
* Added GetPushDuration, GetPushTiming, and IsMoveToFrontAnimation functions.
|
||||||
* Added LineJoin type. Type of constants MiterJoin, RoundJoin, and BevelJoin changed to LineJoin. Type of Canvas.SetLineJoin function argument changed to LineJoin.
|
* Added LineJoin type. Type of constants MiterJoin, RoundJoin, and BevelJoin changed to LineJoin. Type of Canvas.SetLineJoin function argument changed to LineJoin.
|
||||||
* Added LineCap type. Type of constants ButtCap, RoundCap, and SquareCap changed to LineCap. Type of Canvas.SetLineCap function argument changed to LineCap.
|
* Added LineCap type. Type of constants ButtCap, RoundCap, and SquareCap changed to LineCap. Type of Canvas.SetLineCap function argument changed to LineCap.
|
||||||
|
|
||||||
|
|
|
@ -556,7 +556,7 @@ func (animation *animationData) animationCSS(session Session) string {
|
||||||
buffer.WriteString(" 1s ")
|
buffer.WriteString(" 1s ")
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.WriteString(animation.timingFunctionCSS(session))
|
buffer.WriteString(timingFunctionCSS(animation, TimingFunction, session))
|
||||||
|
|
||||||
if delay, ok := floatProperty(animation, Delay, session, 0); ok && delay > 0 {
|
if delay, ok := floatProperty(animation, Delay, session, 0); ok && delay > 0 {
|
||||||
buffer.WriteString(fmt.Sprintf(" %gs", delay))
|
buffer.WriteString(fmt.Sprintf(" %gs", delay))
|
||||||
|
@ -594,7 +594,7 @@ func (animation *animationData) transitionCSS(buffer *strings.Builder, session S
|
||||||
buffer.WriteString(" 1s ")
|
buffer.WriteString(" 1s ")
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.WriteString(animation.timingFunctionCSS(session))
|
buffer.WriteString(timingFunctionCSS(animation, TimingFunction, session))
|
||||||
|
|
||||||
if delay, ok := floatProperty(animation, Delay, session, 0); ok && delay > 0 {
|
if delay, ok := floatProperty(animation, Delay, session, 0); ok && delay > 0 {
|
||||||
buffer.WriteString(fmt.Sprintf(" %gs", delay))
|
buffer.WriteString(fmt.Sprintf(" %gs", delay))
|
||||||
|
@ -643,8 +643,8 @@ func (animation *animationData) writeTransitionString(tag PropertyName, buffer *
|
||||||
buffer.WriteString(" }")
|
buffer.WriteString(" }")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (animation *animationData) timingFunctionCSS(session Session) string {
|
func timingFunctionCSS(properties Properties, tag PropertyName, session Session) string {
|
||||||
if timingFunction, ok := stringProperty(animation, TimingFunction, session); ok {
|
if timingFunction, ok := stringProperty(properties, tag, session); ok {
|
||||||
if timingFunction, ok = session.resolveConstants(timingFunction); ok && isTimingFunctionValid(timingFunction) {
|
if timingFunction, ok = session.resolveConstants(timingFunction); ok && isTimingFunctionValid(timingFunction) {
|
||||||
return timingFunction
|
return timingFunction
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ var boolProperties = []PropertyName{
|
||||||
Repeating,
|
Repeating,
|
||||||
UserSelect,
|
UserSelect,
|
||||||
ColumnSpanAll,
|
ColumnSpanAll,
|
||||||
|
MoveToFrontAnimation,
|
||||||
}
|
}
|
||||||
|
|
||||||
var intProperties = []PropertyName{
|
var intProperties = []PropertyName{
|
||||||
|
@ -88,6 +89,7 @@ var floatProperties = map[PropertyName]struct{ min, max float64 }{
|
||||||
ProgressBarValue: {min: 0, max: math.MaxFloat64},
|
ProgressBarValue: {min: 0, max: math.MaxFloat64},
|
||||||
VideoWidth: {min: 0, max: 10000},
|
VideoWidth: {min: 0, max: 10000},
|
||||||
VideoHeight: {min: 0, max: 10000},
|
VideoHeight: {min: 0, max: 10000},
|
||||||
|
PushDuration: {min: 0, max: math.MaxFloat64},
|
||||||
}
|
}
|
||||||
|
|
||||||
var sizeProperties = map[PropertyName]string{
|
var sizeProperties = map[PropertyName]string{
|
||||||
|
|
660
stackLayout.go
660
stackLayout.go
|
@ -2,22 +2,62 @@ package rui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Constants which represent [StackLayout] animation type during pushing or popping views
|
// Constants which represent [StackLayout] animation type during pushing or popping views
|
||||||
const (
|
const (
|
||||||
// DefaultAnimation - default animation of StackLayout push
|
// PushTransform is the constant for "push-transform" property tag.
|
||||||
DefaultAnimation = 0
|
//
|
||||||
// StartToEndAnimation - start to end animation of StackLayout push
|
// Used by `StackLayout`.
|
||||||
StartToEndAnimation = 1
|
// Specify start translation, scale and rotation over x, y and z axes as well as a distortion
|
||||||
// EndToStartAnimation - end to start animation of StackLayout push
|
// for an animated pushing of a child view.
|
||||||
EndToStartAnimation = 2
|
//
|
||||||
// TopDownAnimation - top down animation of StackLayout push
|
// Supported types: `TransformProperty`, `string`.
|
||||||
TopDownAnimation = 3
|
//
|
||||||
// BottomUpAnimation - bottom up animation of StackLayout push
|
// See `TransformProperty` description for more details.
|
||||||
BottomUpAnimation = 4
|
//
|
||||||
|
// Conversion rules:
|
||||||
|
// `TransformProperty` - stored as is, no conversion performed.
|
||||||
|
// `string` - string representation of `Transform` interface. Example: "_{translate-x = 10px, scale-y = 1.1}".
|
||||||
|
PushTransform = "push-transform"
|
||||||
|
|
||||||
|
// PushDuration is the constant for "push-duration" property tag.
|
||||||
|
//
|
||||||
|
// Used by `StackLayout`.
|
||||||
|
// Sets the length of time in seconds that an push/pop animation takes to complete.
|
||||||
|
//
|
||||||
|
// Supported types: `float`, `int`, `string`.
|
||||||
|
//
|
||||||
|
// Internal type is `float`, other types converted to it during assignment.
|
||||||
|
PushDuration = "push-duration"
|
||||||
|
|
||||||
|
// PushTiming is the constant for "push-timing" property tag.
|
||||||
|
//
|
||||||
|
// Used by `StackLayout`.
|
||||||
|
// Set how an push/pop animation progresses through the duration of each cycle.
|
||||||
|
//
|
||||||
|
// Supported types: `string`.
|
||||||
|
//
|
||||||
|
// Values:
|
||||||
|
// "ease"(`EaseTiming`) - Speed increases towards the middle and slows down at the end.
|
||||||
|
// "ease-in"(`EaseInTiming`) - Speed is slow at first, but increases in the end.
|
||||||
|
// "ease-out"(`EaseOutTiming`) - Speed is fast at first, but decreases in the end.
|
||||||
|
// "ease-in-out"(`EaseInOutTiming`) - Speed is slow at first, but quickly increases and at the end it decreases again.
|
||||||
|
// "linear"(`LinearTiming`) - Constant speed.
|
||||||
|
PushTiming = "push-timing"
|
||||||
|
|
||||||
|
// MoveToFrontAnimation is the constant for "move-to-front-animation" property tag.
|
||||||
|
//
|
||||||
|
// Used by `StackLayout`.
|
||||||
|
// Specifies whether animation is used when calling the MoveToFront/MoveToFrontByID method of StackLayout interface.
|
||||||
|
//
|
||||||
|
// Supported types: `bool`, `int`, `string`.
|
||||||
|
//
|
||||||
|
// Values:
|
||||||
|
// `true` or `1` or "true", "yes", "on", "1" - animation is used (default value).
|
||||||
|
// `false` or `0` or "false", "no", "off", "0" - animation is not used.
|
||||||
|
MoveToFrontAnimation = "move-to-front-animation"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StackLayout represents a StackLayout view
|
// StackLayout represents a StackLayout view
|
||||||
|
@ -45,21 +85,28 @@ type StackLayout interface {
|
||||||
// * TopDownAnimation (3) - Top-down animation;
|
// * TopDownAnimation (3) - Top-down animation;
|
||||||
// * BottomUpAnimation (4) - Bottom up animation.
|
// * BottomUpAnimation (4) - Bottom up animation.
|
||||||
// The third argument `onPushFinished` is the function to be called when the animation ends. It may be nil.
|
// The third argument `onPushFinished` is the function to be called when the animation ends. It may be nil.
|
||||||
Push(view View, animation int, onPushFinished func())
|
Push(view View, onPushFinished func())
|
||||||
|
|
||||||
// Pop removes the current View from the container using animation.
|
// 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 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.
|
// The function will return false if the StackLayout is empty and true if the current item has been removed.
|
||||||
Pop(animation int, onPopFinished func(View)) bool
|
Pop(onPopFinished func(View)) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type pushFinished struct {
|
||||||
|
peekID string
|
||||||
|
listener func()
|
||||||
|
}
|
||||||
|
|
||||||
|
type popFinished struct {
|
||||||
|
view View
|
||||||
|
listener func(View)
|
||||||
}
|
}
|
||||||
|
|
||||||
type stackLayoutData struct {
|
type stackLayoutData struct {
|
||||||
viewsContainerData
|
viewsContainerData
|
||||||
peek, prevPeek int
|
onPushFinished map[string]pushFinished
|
||||||
pushView, popView View
|
onPopFinished map[string]popFinished
|
||||||
animationType int
|
|
||||||
onPushFinished func()
|
|
||||||
onPopFinished func(View)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStackLayout create new StackLayout object and return it
|
// NewStackLayout create new StackLayout object and return it
|
||||||
|
@ -80,44 +127,88 @@ func (layout *stackLayoutData) init(session Session) {
|
||||||
layout.viewsContainerData.init(session)
|
layout.viewsContainerData.init(session)
|
||||||
layout.tag = "StackLayout"
|
layout.tag = "StackLayout"
|
||||||
layout.systemClass = "ruiStackLayout"
|
layout.systemClass = "ruiStackLayout"
|
||||||
layout.properties[TransitionEndEvent] = []func(View, PropertyName){layout.pushFinished, layout.popFinished}
|
layout.onPushFinished = map[string]pushFinished{}
|
||||||
layout.get = layout.getFunc
|
layout.onPopFinished = map[string]popFinished{}
|
||||||
layout.set = layout.setFunc
|
layout.set = layout.setFunc
|
||||||
layout.remove = layout.removeFunc
|
layout.remove = layout.removeFunc
|
||||||
layout.changed = layout.propertyChanged
|
|
||||||
}
|
|
||||||
|
|
||||||
func (layout *stackLayoutData) pushFinished(view View, tag PropertyName) {
|
layout.setRaw(TransitionEndEvent, []func(View, PropertyName){layout.transitionFinished})
|
||||||
if tag == "ruiPush" {
|
if session.TextDirection() == RightToLeftDirection {
|
||||||
if layout.pushView != nil {
|
layout.setRaw(PushTransform, NewTransformProperty(Params{TranslateX: Percent(-100)}))
|
||||||
layout.pushView = nil
|
|
||||||
count := len(layout.views)
|
|
||||||
if count > 0 {
|
|
||||||
layout.peek = count - 1
|
|
||||||
} else {
|
} else {
|
||||||
layout.peek = 0
|
layout.setRaw(PushTransform, NewTransformProperty(Params{TranslateX: Percent(100)}))
|
||||||
}
|
|
||||||
updateInnerHTML(layout.htmlID(), layout.session)
|
|
||||||
layout.currentChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
if layout.onPushFinished != nil {
|
|
||||||
onPushFinished := layout.onPushFinished
|
|
||||||
layout.onPushFinished = nil
|
|
||||||
onPushFinished()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (layout *stackLayoutData) popFinished(view View, tag PropertyName) {
|
func (layout *stackLayoutData) transitionFinished(view View, tag PropertyName) {
|
||||||
if tag == "ruiPop" {
|
if tags := strings.Split(string(tag), "-"); len(tags) >= 2 {
|
||||||
popView := layout.popView
|
session := layout.Session()
|
||||||
layout.popView = nil
|
viewID := tags[1]
|
||||||
updateInnerHTML(layout.htmlID(), layout.session)
|
|
||||||
if layout.onPopFinished != nil {
|
switch tags[0] {
|
||||||
onPopFinished := layout.onPopFinished
|
case "push":
|
||||||
layout.onPopFinished = nil
|
if finished, ok := layout.onPushFinished[viewID]; ok {
|
||||||
onPopFinished(popView)
|
if finished.peekID != "" {
|
||||||
|
pageID := finished.peekID + "page"
|
||||||
|
session.startUpdateScript(pageID)
|
||||||
|
session.updateCSSProperty(pageID, "visibility", "hidden")
|
||||||
|
session.updateCSSProperty(pageID, "transition", "")
|
||||||
|
session.updateCSSProperty(pageID, "transform", "")
|
||||||
|
session.removeProperty(pageID, "ontransitionend")
|
||||||
|
session.removeProperty(pageID, "ontransitioncancel")
|
||||||
|
session.finishUpdateScript(pageID)
|
||||||
|
}
|
||||||
|
|
||||||
|
pageID := viewID + "page"
|
||||||
|
session.startUpdateScript(pageID)
|
||||||
|
session.updateCSSProperty(pageID, "z-index", "auto")
|
||||||
|
session.updateCSSProperty(pageID, "transition", "")
|
||||||
|
session.removeProperty(pageID, "ontransitionend")
|
||||||
|
session.removeProperty(pageID, "ontransitioncancel")
|
||||||
|
session.finishUpdateScript(pageID)
|
||||||
|
|
||||||
|
if finished.listener != nil {
|
||||||
|
finished.listener()
|
||||||
|
}
|
||||||
|
delete(layout.onPushFinished, viewID)
|
||||||
|
layout.contentChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
case "pop":
|
||||||
|
if finished, ok := layout.onPopFinished[viewID]; ok {
|
||||||
|
session.updateCSSProperty(viewID+"page", "display", "none")
|
||||||
|
if finished.listener != nil {
|
||||||
|
finished.listener(finished.view)
|
||||||
|
}
|
||||||
|
delete(layout.onPopFinished, viewID)
|
||||||
|
|
||||||
|
if count := len(layout.views); count > 0 {
|
||||||
|
peekID := layout.views[count-1].htmlID() + "page"
|
||||||
|
session.startUpdateScript(peekID)
|
||||||
|
session.removeProperty(peekID, "ontransitionend")
|
||||||
|
session.removeProperty(peekID, "ontransitioncancel")
|
||||||
|
session.finishUpdateScript(peekID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "move":
|
||||||
|
if count := len(layout.views); count > 1 {
|
||||||
|
pageID := layout.views[count-2].htmlID() + "page"
|
||||||
|
session.startUpdateScript(pageID)
|
||||||
|
session.updateCSSProperty(pageID, "visibility", "hidden")
|
||||||
|
session.updateCSSProperty(pageID, "transition", "")
|
||||||
|
session.updateCSSProperty(pageID, "transform", "")
|
||||||
|
session.finishUpdateScript(pageID)
|
||||||
|
}
|
||||||
|
|
||||||
|
pageID := viewID + "page"
|
||||||
|
session.startUpdateScript(pageID)
|
||||||
|
session.updateCSSProperty(pageID, "z-index", "auto")
|
||||||
|
session.updateCSSProperty(pageID, "transition", "")
|
||||||
|
session.removeProperty(pageID, "ontransitionend")
|
||||||
|
session.removeProperty(pageID, "ontransitioncancel")
|
||||||
|
session.finishUpdateScript(pageID)
|
||||||
|
layout.contentChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,199 +216,225 @@ func (layout *stackLayoutData) popFinished(view View, tag PropertyName) {
|
||||||
func (layout *stackLayoutData) setFunc(tag PropertyName, value any) []PropertyName {
|
func (layout *stackLayoutData) setFunc(tag PropertyName, value any) []PropertyName {
|
||||||
switch tag {
|
switch tag {
|
||||||
case TransitionEndEvent:
|
case TransitionEndEvent:
|
||||||
|
// TODO
|
||||||
listeners, ok := valueToOneArgEventListeners[View, PropertyName](value)
|
listeners, ok := valueToOneArgEventListeners[View, PropertyName](value)
|
||||||
if ok && listeners != nil {
|
if ok && listeners != nil {
|
||||||
listeners = append(listeners, layout.pushFinished)
|
listeners = append(listeners, layout.transitionFinished)
|
||||||
listeners = append(listeners, layout.popFinished)
|
|
||||||
layout.setRaw(TransitionEndEvent, listeners)
|
layout.setRaw(TransitionEndEvent, listeners)
|
||||||
return []PropertyName{tag}
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case Current:
|
case PushTiming:
|
||||||
newCurrent := 0
|
if text, ok := value.(string); ok {
|
||||||
switch value := value.(type) {
|
layout.setRaw(tag, text)
|
||||||
case string:
|
|
||||||
text, ok := layout.session.resolveConstants(value)
|
|
||||||
if !ok {
|
|
||||||
invalidPropertyValue(tag, value)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
n, err := strconv.Atoi(strings.Trim(text, " \t"))
|
|
||||||
if err != nil {
|
|
||||||
invalidPropertyValue(tag, value)
|
|
||||||
ErrorLog(err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
newCurrent = n
|
|
||||||
|
|
||||||
default:
|
|
||||||
n, ok := isInt(value)
|
|
||||||
if !ok {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return nil
|
|
||||||
} else if n < 0 || n >= len(layout.views) {
|
|
||||||
ErrorLogF(`The view index "%d" of "%s" property is out of range`, n, tag)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
newCurrent = n
|
|
||||||
}
|
|
||||||
|
|
||||||
layout.prevPeek = layout.peek
|
|
||||||
if newCurrent == layout.peek {
|
|
||||||
return []PropertyName{}
|
|
||||||
}
|
|
||||||
|
|
||||||
layout.peek = newCurrent
|
|
||||||
return []PropertyName{tag}
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return layout.viewsContainerData.setFunc(tag, value)
|
return layout.viewsContainerData.setFunc(tag, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (layout *stackLayoutData) propertyChanged(tag PropertyName) {
|
|
||||||
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")
|
|
||||||
layout.prevPeek = layout.peek
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
layout.viewsContainerData.propertyChanged(tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (layout *stackLayoutData) removeFunc(tag PropertyName) []PropertyName {
|
func (layout *stackLayoutData) removeFunc(tag PropertyName) []PropertyName {
|
||||||
switch tag {
|
switch tag {
|
||||||
case TransitionEndEvent:
|
case TransitionEndEvent:
|
||||||
layout.setRaw(TransitionEndEvent, []func(View, PropertyName){layout.pushFinished, layout.popFinished})
|
layout.setRaw(TransitionEndEvent, []func(View, PropertyName){layout.transitionFinished})
|
||||||
return []PropertyName{tag}
|
|
||||||
|
|
||||||
case Current:
|
|
||||||
layout.setRaw(Current, 0)
|
|
||||||
return []PropertyName{tag}
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
return layout.viewsContainerData.removeFunc(tag)
|
return layout.viewsContainerData.removeFunc(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (layout *stackLayoutData) getFunc(tag PropertyName) any {
|
|
||||||
if tag == Current {
|
|
||||||
return layout.peek
|
|
||||||
}
|
|
||||||
return layout.viewsContainerData.getFunc(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (layout *stackLayoutData) Peek() View {
|
func (layout *stackLayoutData) Peek() View {
|
||||||
if int(layout.peek) < len(layout.views) {
|
if count := len(layout.views); count > 0 {
|
||||||
return layout.views[layout.peek]
|
return layout.views[count-1]
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (layout *stackLayoutData) MoveToFront(view View) bool {
|
func (layout *stackLayoutData) MoveToFront(view View) bool {
|
||||||
peek := int(layout.peek)
|
if view == nil {
|
||||||
htmlID := view.htmlID()
|
ErrorLog(`MoveToFront(nil) forbidden`)
|
||||||
for i, view2 := range layout.views {
|
|
||||||
if view2.htmlID() == htmlID {
|
|
||||||
if i != peek {
|
|
||||||
if peek < len(layout.views) {
|
|
||||||
layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(peek), "visibility", "hidden")
|
|
||||||
}
|
|
||||||
|
|
||||||
layout.peek = i
|
|
||||||
layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(i), "visibility", "visible")
|
|
||||||
layout.currentChanged()
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorLog(`MoveToFront() fail. Subview not found."`)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (layout *stackLayoutData) currentChanged() {
|
htmlID := view.htmlID()
|
||||||
if listener, ok := layout.changeListener[Current]; ok {
|
switch count := len(layout.views); count {
|
||||||
listener(layout, Current)
|
case 0:
|
||||||
|
// do nothing
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
if layout.views[0].htmlID() == htmlID {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
for i, view := range layout.views {
|
||||||
|
if view.htmlID() == htmlID {
|
||||||
|
layout.moveToFrontByIndex(i)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorLog(`MoveToFront() fail. Subview not found.`)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (layout *stackLayoutData) MoveToFrontByID(viewID string) bool {
|
func (layout *stackLayoutData) MoveToFrontByID(viewID string) bool {
|
||||||
peek := int(layout.peek)
|
switch count := len(layout.views); count {
|
||||||
for i, view := range layout.views {
|
case 0:
|
||||||
if view.ID() == viewID {
|
// do nothing
|
||||||
if i != peek {
|
|
||||||
if peek < len(layout.views) {
|
case 1:
|
||||||
layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(peek), "visibility", "hidden")
|
if layout.views[0].ID() == viewID {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
layout.peek = i
|
default:
|
||||||
layout.Session().updateCSSProperty(layout.htmlID()+"page"+strconv.Itoa(i), "visibility", "visible")
|
for i, view := range layout.views {
|
||||||
layout.currentChanged()
|
if view.ID() == viewID {
|
||||||
}
|
layout.moveToFrontByIndex(i)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ErrorLogF(`MoveToFront("%s") fail. Subview with "%s" not found."`, viewID, viewID)
|
ErrorLogF(`MoveToFront("%s") fail. Subview with "%s" not found.`, viewID, viewID)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (layout *stackLayoutData) Append(view View) {
|
func (layout *stackLayoutData) moveToFrontByIndex(index int) {
|
||||||
if view != nil {
|
|
||||||
layout.peek = len(layout.views)
|
|
||||||
layout.viewsContainerData.Append(view)
|
|
||||||
layout.currentChanged()
|
|
||||||
} else {
|
|
||||||
ErrorLog("StackLayout.Append(nil, ....) is forbidden")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (layout *stackLayoutData) Insert(view View, index int) {
|
|
||||||
if view != nil {
|
|
||||||
count := len(layout.views)
|
count := len(layout.views)
|
||||||
if index < count {
|
if index == count-1 {
|
||||||
layout.peek = int(index)
|
return
|
||||||
} else {
|
|
||||||
layout.peek = count
|
|
||||||
}
|
|
||||||
layout.viewsContainerData.Insert(view, index)
|
|
||||||
layout.currentChanged()
|
|
||||||
} else {
|
|
||||||
ErrorLog("StackLayout.Insert(nil, ....) is forbidden")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (layout *stackLayoutData) RemoveView(index int) View {
|
view := layout.views[index]
|
||||||
if index < 0 || index >= len(layout.views) {
|
peekID := layout.views[count-1].htmlID()
|
||||||
return nil
|
if index == 0 {
|
||||||
|
layout.views = append(layout.views[1:], view)
|
||||||
|
} else {
|
||||||
|
layout.views = append(append(layout.views[:index], layout.views[index+1:]...), view)
|
||||||
}
|
}
|
||||||
|
|
||||||
if layout.peek > 0 {
|
session := layout.Session()
|
||||||
layout.peek--
|
pageID := view.htmlID() + "page"
|
||||||
|
peekPageID := peekID + "page"
|
||||||
|
|
||||||
|
animated := IsMoveToFrontAnimation(layout)
|
||||||
|
|
||||||
|
var transform TransformProperty = nil
|
||||||
|
if animated {
|
||||||
|
transform = GetPushTransform(layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
if transform == nil {
|
||||||
|
session.updateCSSProperty(peekPageID, "visibility", "hidden")
|
||||||
|
session.updateCSSProperty(pageID, "visibility", "visible")
|
||||||
|
layout.contentChanged()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
|
buffer.WriteString(`stackTransitionEndEvent('`)
|
||||||
|
buffer.WriteString(layout.htmlID())
|
||||||
|
buffer.WriteString(`', 'move-`)
|
||||||
|
buffer.WriteString(view.htmlID())
|
||||||
|
buffer.WriteString(`', event)`)
|
||||||
|
|
||||||
|
listener := buffer.String()
|
||||||
|
|
||||||
|
transformCSS := transformMirror(transform, session).transformCSS(session)
|
||||||
|
transitionCSS := layout.pushTransitionCSS()
|
||||||
|
|
||||||
|
session.updateCSSProperty(peekPageID, "transition", transitionCSS)
|
||||||
|
|
||||||
|
session.startUpdateScript(pageID)
|
||||||
|
session.updateProperty(pageID, "ontransitionend", listener)
|
||||||
|
session.updateProperty(pageID, "ontransitioncancel", listener)
|
||||||
|
session.updateCSSProperty(pageID, "transform", transformCSS)
|
||||||
|
session.updateCSSProperty(pageID, "z-index", "100")
|
||||||
|
session.updateCSSProperty(pageID, "visibility", "visible")
|
||||||
|
session.finishUpdateScript(pageID)
|
||||||
|
|
||||||
|
session.updateCSSProperty(pageID, "transition", transitionCSS)
|
||||||
|
session.updateCSSProperty(pageID, "transform", "")
|
||||||
|
|
||||||
|
session.updateCSSProperty(peekPageID, "transform", transformCSS)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (layout *stackLayoutData) contentChanged() {
|
||||||
|
if listener, ok := layout.changeListener[Content]; ok {
|
||||||
|
listener(layout, Content)
|
||||||
}
|
}
|
||||||
defer layout.currentChanged()
|
|
||||||
return layout.viewsContainerData.RemoveView(index)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (layout *stackLayoutData) RemovePeek() View {
|
func (layout *stackLayoutData) RemovePeek() View {
|
||||||
return layout.RemoveView(len(layout.views) - 1)
|
return layout.RemoveView(len(layout.views) - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (layout *stackLayoutData) Push(view View, animation int, onPushFinished func()) {
|
func (layout *stackLayoutData) pushTransitionCSS() string {
|
||||||
|
return fmt.Sprintf("transform %.2fs %s", GetPushDuration(layout), GetPushTiming(layout))
|
||||||
|
}
|
||||||
|
|
||||||
|
func transformMirror(transform TransformProperty, session Session) TransformProperty {
|
||||||
|
result := NewTransformProperty(nil)
|
||||||
|
|
||||||
|
for _, tag := range []PropertyName{Perspective, RotateX, RotateY, RotateZ, ScaleX, ScaleY, ScaleZ, TranslateZ} {
|
||||||
|
if value := transform.getRaw(tag); value != nil {
|
||||||
|
result.Set(tag, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tag := range []PropertyName{Rotate, SkewX, SkewY} {
|
||||||
|
if angle, ok := angleProperty(transform, tag, session); ok {
|
||||||
|
angle.Value = -angle.Value
|
||||||
|
result.Set(tag, angle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tag := range []PropertyName{TranslateX, TranslateY} {
|
||||||
|
if size, ok := sizeProperty(transform, tag, session); ok {
|
||||||
|
size.Value = -size.Value
|
||||||
|
result.Set(tag, size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (layout *stackLayoutData) Push(view View, onPushFinished func()) {
|
||||||
if view == nil {
|
if view == nil {
|
||||||
ErrorLog("StackLayout.Push(nil, ....) is forbidden")
|
ErrorLog("StackLayout.Push(nil, ....) is forbidden")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
layout.pushView = view
|
transform := GetPushTransform(layout)
|
||||||
layout.animationType = animation
|
if transform == nil {
|
||||||
//layout.animation["ruiPush"] = Animation{FinishListener: layout}
|
layout.Append(view)
|
||||||
layout.onPushFinished = onPushFinished
|
if onPushFinished != nil {
|
||||||
|
onPushFinished()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
prevPeek := ""
|
||||||
|
finished := pushFinished{
|
||||||
|
listener: onPushFinished,
|
||||||
|
}
|
||||||
|
if count := len(layout.views); count > 0 {
|
||||||
|
finished.peekID = layout.views[count-1].htmlID()
|
||||||
|
prevPeek = finished.peekID + "page"
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlID := view.htmlID()
|
||||||
|
layout.onPushFinished[htmlID] = finished
|
||||||
|
|
||||||
|
view.setParentID(layout.htmlID())
|
||||||
|
layout.views = append(layout.views, view)
|
||||||
|
|
||||||
htmlID := layout.htmlID()
|
|
||||||
session := layout.Session()
|
session := layout.Session()
|
||||||
|
|
||||||
buffer := allocStringBuilder()
|
buffer := allocStringBuilder()
|
||||||
|
@ -325,105 +442,110 @@ func (layout *stackLayoutData) Push(view View, animation int, onPushFinished fun
|
||||||
|
|
||||||
buffer.WriteString(`<div id="`)
|
buffer.WriteString(`<div id="`)
|
||||||
buffer.WriteString(htmlID)
|
buffer.WriteString(htmlID)
|
||||||
buffer.WriteString(`push" class="ruiStackPageLayout" ontransitionend="stackTransitionEndEvent('`)
|
buffer.WriteString(`page" class="ruiStackPageLayout" ontransitionend="stackTransitionEndEvent('`)
|
||||||
|
buffer.WriteString(layout.htmlID())
|
||||||
|
buffer.WriteString(`', 'push-`)
|
||||||
buffer.WriteString(htmlID)
|
buffer.WriteString(htmlID)
|
||||||
buffer.WriteString(`', 'ruiPush', event)" style="`)
|
buffer.WriteString(`', event)" style="z-index: 100; transform: `)
|
||||||
|
buffer.WriteString(transform.transformCSS(layout.session))
|
||||||
|
buffer.WriteRune(';')
|
||||||
|
|
||||||
switch layout.animationType {
|
transitionCSS := layout.pushTransitionCSS()
|
||||||
case StartToEndAnimation:
|
buffer.WriteString(" transition: ")
|
||||||
buffer.WriteString(fmt.Sprintf("transform: translate(-%gpx, 0px); transition: transform ", layout.frame.Width))
|
buffer.WriteString(transitionCSS)
|
||||||
|
buffer.WriteString(`;">`)
|
||||||
|
|
||||||
case TopDownAnimation:
|
viewHTML(view, buffer, "")
|
||||||
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>`)
|
buffer.WriteString(`</div>`)
|
||||||
|
|
||||||
session.appendToInnerHTML(htmlID, buffer.String())
|
session.appendToInnerHTML(layout.htmlID(), buffer.String())
|
||||||
layout.session.updateCSSProperty(htmlID+"push", "transform", "translate(0px, 0px)")
|
|
||||||
|
|
||||||
layout.views = append(layout.views, view)
|
if prevPeek != "" {
|
||||||
view.setParentID(htmlID)
|
mirror := transformMirror(transform, session)
|
||||||
|
layout.session.updateCSSProperty(prevPeek, "transition", transitionCSS)
|
||||||
if listener, ok := layout.changeListener[Content]; ok {
|
layout.session.updateCSSProperty(prevPeek, "transform", mirror.transformCSS(session))
|
||||||
listener(layout, Content)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (layout *stackLayoutData) Pop(animation int, onPopFinished func(View)) bool {
|
layout.session.updateCSSProperty(htmlID+"page", "transform", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (layout *stackLayoutData) Pop(onPopFinished func(View)) bool {
|
||||||
count := len(layout.views)
|
count := len(layout.views)
|
||||||
if count == 0 || layout.peek >= count {
|
if count == 0 {
|
||||||
ErrorLog("StackLayout is empty")
|
ErrorLog("StackLayout is empty")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
layout.popView = layout.views[layout.peek]
|
transform := GetPushTransform(layout)
|
||||||
layout.RemoveView(layout.peek)
|
if transform == nil {
|
||||||
|
if view := layout.RemovePeek(); view != nil {
|
||||||
|
if onPopFinished != nil {
|
||||||
|
onPopFinished(view)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
layout.animationType = animation
|
peek := count - 1
|
||||||
//layout.animation["ruiPop"] = Animation{FinishListener: layout}
|
view := layout.views[peek]
|
||||||
layout.onPopFinished = onPopFinished
|
view.setParentID("")
|
||||||
|
|
||||||
htmlID := layout.htmlID()
|
layout.views = layout.views[:peek]
|
||||||
|
layout.contentChanged()
|
||||||
|
|
||||||
|
layout.onPopFinished[view.htmlID()] = popFinished{
|
||||||
|
view: view,
|
||||||
|
listener: onPopFinished,
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlID := view.htmlID()
|
||||||
session := layout.Session()
|
session := layout.Session()
|
||||||
|
|
||||||
buffer := allocStringBuilder()
|
buffer := allocStringBuilder()
|
||||||
defer freeStringBuilder(buffer)
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
buffer.WriteString(`<div id="`)
|
buffer.WriteString(`stackTransitionEndEvent('`)
|
||||||
|
buffer.WriteString(layout.htmlID())
|
||||||
|
buffer.WriteString(`', 'pop-`)
|
||||||
buffer.WriteString(htmlID)
|
buffer.WriteString(htmlID)
|
||||||
buffer.WriteString(`pop" class="ruiStackPageLayout" ontransitionend="stackTransitionEndEvent('`)
|
buffer.WriteString(`', event)`)
|
||||||
buffer.WriteString(htmlID)
|
|
||||||
buffer.WriteString(`', 'ruiPop', event)" ontransitioncancel="stackTransitionEndEvent('`)
|
|
||||||
buffer.WriteString(htmlID)
|
|
||||||
buffer.WriteString(`', 'ruiPop', event)" style="transition: transform 1s ease;">`)
|
|
||||||
viewHTML(layout.popView, buffer, "")
|
|
||||||
buffer.WriteString(`</div>`)
|
|
||||||
|
|
||||||
session.appendToInnerHTML(htmlID, buffer.String())
|
listener := buffer.String()
|
||||||
|
pageID := htmlID + "page"
|
||||||
|
|
||||||
var value string
|
transitionCSS := layout.pushTransitionCSS()
|
||||||
switch layout.animationType {
|
|
||||||
case TopDownAnimation:
|
|
||||||
value = fmt.Sprintf("translate(0px, -%gpx)", layout.frame.Height)
|
|
||||||
|
|
||||||
case BottomUpAnimation:
|
session.startUpdateScript(pageID)
|
||||||
value = fmt.Sprintf("translate(0px, %gpx)", layout.frame.Height)
|
session.updateProperty(pageID, "ontransitionend", listener)
|
||||||
|
session.updateProperty(pageID, "ontransitioncancel", listener)
|
||||||
|
session.updateCSSProperty(pageID, "z-index", "100")
|
||||||
|
session.updateCSSProperty(pageID, "transition", transitionCSS)
|
||||||
|
session.finishUpdateScript(pageID)
|
||||||
|
|
||||||
case StartToEndAnimation:
|
peek--
|
||||||
value = fmt.Sprintf("translate(-%gpx, 0px)", layout.frame.Width)
|
if peek >= 0 {
|
||||||
|
peekID := layout.views[peek].htmlID() + "page"
|
||||||
default:
|
session.updateCSSProperty(peekID, "transition", "")
|
||||||
value = fmt.Sprintf("translate(%gpx, 0px)", layout.frame.Width)
|
session.startUpdateScript(peekID)
|
||||||
|
session.updateCSSProperty(peekID, "transform", transformMirror(transform, session).transformCSS(session))
|
||||||
|
session.updateCSSProperty(peekID, "visibility", "visible")
|
||||||
|
session.finishUpdateScript(peekID)
|
||||||
|
session.updateCSSProperty(peekID, "transition", transitionCSS)
|
||||||
|
session.updateCSSProperty(peekID, "transform", "")
|
||||||
}
|
}
|
||||||
|
session.updateCSSProperty(pageID, "transform", transform.transformCSS(session))
|
||||||
|
|
||||||
layout.session.updateCSSProperty(htmlID+"pop", "transform", value)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (layout *stackLayoutData) htmlSubviews(self View, buffer *strings.Builder) {
|
func (layout *stackLayoutData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
count := len(layout.views)
|
if count := len(layout.views); count > 0 {
|
||||||
if count > 0 {
|
peek := count - 1
|
||||||
htmlID := layout.htmlID()
|
|
||||||
peek := int(layout.peek)
|
|
||||||
if peek >= count {
|
|
||||||
peek = count - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, view := range layout.views {
|
for i, view := range layout.views {
|
||||||
buffer.WriteString(`<div id="`)
|
buffer.WriteString(`<div id="`)
|
||||||
buffer.WriteString(htmlID)
|
buffer.WriteString(view.htmlID())
|
||||||
buffer.WriteString(`page`)
|
buffer.WriteString(`page`)
|
||||||
buffer.WriteString(strconv.Itoa(i))
|
|
||||||
buffer.WriteString(`" class="ruiStackPageLayout"`)
|
buffer.WriteString(`" class="ruiStackPageLayout"`)
|
||||||
if i != peek {
|
if i != peek {
|
||||||
buffer.WriteString(` style="visibility: hidden;"`)
|
buffer.WriteString(` style="visibility: hidden;"`)
|
||||||
|
@ -434,3 +556,49 @@ func (layout *stackLayoutData) htmlSubviews(self View, buffer *strings.Builder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsMoveToFrontAnimation returns "true" if an animation is used when calling the MoveToFront/MoveToFrontByID method of StackLayout interface.
|
||||||
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
|
func IsMoveToFrontAnimation(view View, subviewID ...string) bool {
|
||||||
|
if len(subviewID) > 0 && subviewID[0] != "" {
|
||||||
|
view = ViewByID(view, subviewID[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if view != nil {
|
||||||
|
if value, ok := boolProperty(view, MoveToFrontAnimation, view.Session()); ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
if value := valueFromStyle(view, MoveToFrontAnimation); value != nil {
|
||||||
|
if b, ok := valueToBool(value, view.Session()); ok {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPushDuration returns the length of time in seconds that an push/pop StackLayout animation takes to complete.
|
||||||
|
// If the second argument (subviewID) is not specified or it is "" then a width of the first argument (view) is returned
|
||||||
|
func GetPushDuration(view View, subviewID ...string) float64 {
|
||||||
|
return floatStyledProperty(view, subviewID, PushDuration, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPushTiming returns the function which sets how an push/pop animation progresses.
|
||||||
|
// If the second argument (subviewID) is not specified or it is "" then a width of the first argument (view) is returned
|
||||||
|
func GetPushTiming(view View, subviewID ...string) string {
|
||||||
|
result := stringStyledProperty(view, subviewID, PushTiming, false)
|
||||||
|
if isTimingFunctionValid(result) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return "easy"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransform returns the start transform (translation, scale and rotation over x, y and z axes as well as a distortion)
|
||||||
|
// for an animated pushing of a child view.
|
||||||
|
// The default value is nil (no transform).
|
||||||
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
|
func GetPushTransform(view View, subviewID ...string) TransformProperty {
|
||||||
|
return transformStyledProperty(view, subviewID, PushTransform)
|
||||||
|
}
|
||||||
|
|
51
transform.go
51
transform.go
|
@ -11,14 +11,14 @@ const (
|
||||||
// Transform is the constant for "transform" property tag.
|
// Transform is the constant for "transform" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by `View`.
|
||||||
// Specify translation, scale and rotation over x, y and z axes as well as a distorsion of a view along x and y axes.
|
// Specify translation, scale and rotation over x, y and z axes as well as a distortion of a view along x and y axes.
|
||||||
//
|
//
|
||||||
// Supported types: `TransformProperty`, `string`.
|
// Supported types: `TransformProperty`, `string`.
|
||||||
//
|
//
|
||||||
// See `Transform` description for more details.
|
// See `TransformProperty` description for more details.
|
||||||
//
|
//
|
||||||
// Conversion rules:
|
// Conversion rules:
|
||||||
// `Transform` - stored as is, no conversion performed.
|
// `TransformProperty` - stored as is, no conversion performed.
|
||||||
// `string` - string representation of `Transform` interface. Example: "_{translate-x = 10px, scale-y = 1.1}".
|
// `string` - string representation of `Transform` interface. Example: "_{translate-x = 10px, scale-y = 1.1}".
|
||||||
Transform PropertyName = "transform"
|
Transform PropertyName = "transform"
|
||||||
|
|
||||||
|
@ -466,9 +466,9 @@ func transformSet(properties Properties, tag PropertyName, value any) []Property
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setTransformProperty(properties Properties, value any) bool {
|
func valueToTransformProperty(value any) TransformProperty {
|
||||||
|
|
||||||
setObject := func(obj DataObject) bool {
|
parseObject := func(obj DataObject) TransformProperty {
|
||||||
transform := NewTransformProperty(nil)
|
transform := NewTransformProperty(nil)
|
||||||
ok := true
|
ok := true
|
||||||
for i := 0; i < obj.PropertyCount(); i++ {
|
for i := 0; i < obj.PropertyCount(); i++ {
|
||||||
|
@ -482,41 +482,44 @@ func setTransformProperty(properties Properties, value any) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok && transform.empty() {
|
if !ok && transform.empty() {
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
return transform
|
||||||
properties.setRaw(Transform, transform)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case TransformProperty:
|
case TransformProperty:
|
||||||
properties.setRaw(Transform, value)
|
return value
|
||||||
return true
|
|
||||||
|
|
||||||
case DataObject:
|
case DataObject:
|
||||||
return setObject(value)
|
return parseObject(value)
|
||||||
|
|
||||||
case DataNode:
|
case DataNode:
|
||||||
if obj := value.Object(); obj != nil {
|
if obj := value.Object(); obj != nil {
|
||||||
return setObject(obj)
|
return parseObject(obj)
|
||||||
}
|
}
|
||||||
notCompatibleType(Transform, value)
|
|
||||||
return false
|
|
||||||
|
|
||||||
case string:
|
case string:
|
||||||
if obj := ParseDataText(value); obj != nil {
|
if obj := ParseDataText(value); obj != nil {
|
||||||
return setObject(obj)
|
return parseObject(obj)
|
||||||
}
|
}
|
||||||
notCompatibleType(Transform, value)
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTransformProperty(properties Properties, tag PropertyName, value any) bool {
|
||||||
|
if transform := valueToTransformProperty(value); transform != nil {
|
||||||
|
properties.setRaw(tag, transform)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
notCompatibleType(tag, value)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
func getTransformProperty(properties Properties, tag PropertyName) TransformProperty {
|
||||||
}
|
if val := properties.getRaw(tag); val != nil {
|
||||||
|
|
||||||
func getTransformProperty(properties Properties) TransformProperty {
|
|
||||||
if val := properties.getRaw(Transform); val != nil {
|
|
||||||
if transform, ok := val.(TransformProperty); ok {
|
if transform, ok := val.(TransformProperty); ok {
|
||||||
return transform
|
return transform
|
||||||
}
|
}
|
||||||
|
@ -528,7 +531,7 @@ func setTransformPropertyElement(properties Properties, tag PropertyName, value
|
||||||
switch tag {
|
switch tag {
|
||||||
case Perspective, RotateX, RotateY, RotateZ, Rotate, SkewX, SkewY, ScaleX, ScaleY, ScaleZ, TranslateX, TranslateY, TranslateZ:
|
case Perspective, RotateX, RotateY, RotateZ, Rotate, SkewX, SkewY, ScaleX, ScaleY, ScaleZ, TranslateX, TranslateY, TranslateZ:
|
||||||
var result []PropertyName = nil
|
var result []PropertyName = nil
|
||||||
if transform := getTransformProperty(properties); transform != nil {
|
if transform := getTransformProperty(properties, Transform); transform != nil {
|
||||||
if result = transformSet(transform, tag, value); result != nil {
|
if result = transformSet(transform, tag, value); result != nil {
|
||||||
result = append(result, Transform)
|
result = append(result, Transform)
|
||||||
}
|
}
|
||||||
|
@ -694,7 +697,7 @@ func (style *viewStyle) writeViewTransformCSS(builder cssBuilder, session Sessio
|
||||||
builder.add(`transform-origin`, css)
|
builder.add(`transform-origin`, css)
|
||||||
}
|
}
|
||||||
|
|
||||||
if transform := getTransformProperty(style); transform != nil {
|
if transform := getTransformProperty(style, Transform); transform != nil {
|
||||||
builder.add(`transform`, transform.transformCSS(session))
|
builder.add(`transform`, transform.transformCSS(session))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
view.go
2
view.go
|
@ -708,7 +708,7 @@ func (view *viewData) propertyChanged(tag PropertyName) {
|
||||||
case Transform, Perspective, SkewX, SkewY, TranslateX, TranslateY, TranslateZ,
|
case Transform, Perspective, SkewX, SkewY, TranslateX, TranslateY, TranslateZ,
|
||||||
ScaleX, ScaleY, ScaleZ, Rotate, RotateX, RotateY, RotateZ:
|
ScaleX, ScaleY, ScaleZ, Rotate, RotateX, RotateY, RotateZ:
|
||||||
css := ""
|
css := ""
|
||||||
if transform := getTransformProperty(view); transform != nil {
|
if transform := getTransformProperty(view, Transform); transform != nil {
|
||||||
css = transform.transformCSS(session)
|
css = transform.transformCSS(session)
|
||||||
}
|
}
|
||||||
session.updateCSSProperty(htmlID, "transform", css)
|
session.updateCSSProperty(htmlID, "transform", css)
|
||||||
|
|
|
@ -562,7 +562,7 @@ func viewStyleGet(style Properties, tag PropertyName) any {
|
||||||
|
|
||||||
case RotateX, RotateY, RotateZ, Rotate, SkewX, SkewY, ScaleX, ScaleY, ScaleZ,
|
case RotateX, RotateY, RotateZ, Rotate, SkewX, SkewY, ScaleX, ScaleY, ScaleZ,
|
||||||
TranslateX, TranslateY, TranslateZ:
|
TranslateX, TranslateY, TranslateZ:
|
||||||
if transform := getTransformProperty(style); transform != nil {
|
if transform := getTransformProperty(style, Transform); transform != nil {
|
||||||
return transform.Get(tag)
|
return transform.Get(tag)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -345,8 +345,8 @@ func viewStyleSet(style Properties, tag PropertyName, value any) []PropertyName
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case Transform:
|
case Transform, PushTransform:
|
||||||
if setTransformProperty(style, value) {
|
if setTransformProperty(style, tag, value) {
|
||||||
return []PropertyName{Transform}
|
return []PropertyName{Transform}
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
|
|
23
viewUtils.go
23
viewUtils.go
|
@ -567,6 +567,13 @@ func GetColumn(view View, subviewID ...string) Range {
|
||||||
return Range{}
|
return Range{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTransform returns a view transform: translation, scale and rotation over x, y and z axes as well as a distortion of a view along x and y axes.
|
||||||
|
// The default value is nil (no transform)
|
||||||
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
|
func GetTransform(view View, subviewID ...string) TransformProperty {
|
||||||
|
return transformStyledProperty(view, subviewID, Transform)
|
||||||
|
}
|
||||||
|
|
||||||
// GetPerspective returns a distance between the z = 0 plane and the user in order to give a 3D-positioned
|
// GetPerspective returns a distance between the z = 0 plane and the user in order to give a 3D-positioned
|
||||||
// element some perspective. Each 3D element with z > 0 becomes larger; each 3D-element with z < 0 becomes smaller.
|
// element some perspective. Each 3D element with z > 0 becomes larger; each 3D-element with z < 0 becomes smaller.
|
||||||
// The default value is 0 (no 3D effects).
|
// The default value is 0 (no 3D effects).
|
||||||
|
@ -860,6 +867,22 @@ func colorStyledProperty(view View, subviewID []string, tag PropertyName, inheri
|
||||||
return Color(0)
|
return Color(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func transformStyledProperty(view View, subviewID []string, tag PropertyName) TransformProperty {
|
||||||
|
if len(subviewID) > 0 && subviewID[0] != "" {
|
||||||
|
view = ViewByID(view, subviewID[0])
|
||||||
|
}
|
||||||
|
if view != nil {
|
||||||
|
if transform := getTransformProperty(view, tag); transform != nil {
|
||||||
|
return transform
|
||||||
|
}
|
||||||
|
|
||||||
|
if value := valueFromStyle(view, tag); value != nil {
|
||||||
|
return valueToTransformProperty(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// FocusView sets focus on the specified subview, if it can be focused.
|
// FocusView sets focus on the specified subview, if it can be focused.
|
||||||
// The focused View is the View which will receive keyboard events by default.
|
// The focused View is the View which will receive keyboard events by default.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then focus is set on the first argument (view)
|
// If the second argument (subviewID) is not specified or it is "" then focus is set on the first argument (view)
|
||||||
|
|
Loading…
Reference in New Issue