mirror of https://github.com/anoshenko/rui.git
Added Popup show/hide animation
This commit is contained in:
parent
5cc4475370
commit
bc6e0c4db9
|
@ -79,7 +79,7 @@ ul:focus {
|
|||
}
|
||||
|
||||
.ruiPopupLayer {
|
||||
background-color: rgba(128,128,128,0.1);
|
||||
/*background-color: rgba(128,128,128,0.1);*/
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
|
|
8
color.go
8
color.go
|
@ -11,6 +11,14 @@ import (
|
|||
// Color - represent color in argb format
|
||||
type Color uint32
|
||||
|
||||
// ARGB creates Color using alpha, red, green and blue components
|
||||
func ARGB[T int | uint | int8 | uint8](alpha, red, green, blue T) Color {
|
||||
return ((Color(alpha) & 0xFF) << 24) +
|
||||
((Color(red) & 0xFF) << 16) +
|
||||
((Color(green) & 0xFF) << 8) +
|
||||
(Color(blue) & 0xFF)
|
||||
}
|
||||
|
||||
// ARGB - return alpha, red, green and blue components of the color
|
||||
func (color Color) ARGB() (uint8, uint8, uint8, uint8) {
|
||||
return uint8(color >> 24),
|
||||
|
|
286
popup.go
286
popup.go
|
@ -1,6 +1,7 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -143,6 +144,48 @@ const (
|
|||
// See `SizeUnit` description for more details.
|
||||
ArrowWidth PropertyName = "arrow-width"
|
||||
|
||||
// ShowTransform is the constant for "show-transform" property tag.
|
||||
//
|
||||
// Used by `Popup`.
|
||||
// Specify start translation, scale and rotation over x, y and z axes as well as a distortion
|
||||
// for an animated Popup showing/hidding.
|
||||
//
|
||||
// Supported types: `TransformProperty`, `string`.
|
||||
//
|
||||
// See `TransformProperty` description for more details.
|
||||
//
|
||||
// Conversion rules:
|
||||
// `TransformProperty` - stored as is, no conversion performed.
|
||||
// `string` - string representation of `Transform` interface. Example: "_{translate-x = 10px, scale-y = 1.1}".
|
||||
ShowTransform = "show-transform"
|
||||
|
||||
// ShowDuration is the constant for "show-duration" property tag.
|
||||
//
|
||||
// Used by `Popup`.
|
||||
// Sets the length of time in seconds that a Popup show/hide animation takes to complete.
|
||||
//
|
||||
// Supported types: `float`, `int`, `string`.
|
||||
//
|
||||
// Internal type is `float`, other types converted to it during assignment.
|
||||
ShowDuration = "show-duration"
|
||||
|
||||
// ShowTiming is the constant for "show-timing" property tag.
|
||||
//
|
||||
// Used by `StackLayout`.
|
||||
// Set how a Popup show/hide 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.
|
||||
ShowTiming = "show-timing"
|
||||
|
||||
ShowOpacity = "show-opacity"
|
||||
|
||||
// ArrowOffset is the constant for "arrow-offset" property tag.
|
||||
//
|
||||
// Used by `Popup`.
|
||||
|
@ -219,14 +262,21 @@ type Popup interface {
|
|||
html(buffer *strings.Builder)
|
||||
viewByHTMLID(id string) View
|
||||
keyEvent(event KeyEvent) bool
|
||||
showAnimation()
|
||||
dissmissAnimation(listener func(PropertyName)) bool
|
||||
}
|
||||
|
||||
type popupData struct {
|
||||
layerView View
|
||||
view View
|
||||
layerView GridLayout
|
||||
popupView GridLayout
|
||||
contentView View
|
||||
buttons []PopupButton
|
||||
cancelable bool
|
||||
dismissListener []func(Popup)
|
||||
showTransform TransformProperty
|
||||
showOpacity float64
|
||||
showDuration float64
|
||||
showTiming string
|
||||
}
|
||||
|
||||
type popupManager struct {
|
||||
|
@ -397,39 +447,15 @@ func (arrow *popupArrow) createView(popupView View) View {
|
|||
return NewGridLayout(session, params)
|
||||
}
|
||||
|
||||
func (popup *popupData) init(view View, popupParams Params) {
|
||||
popup.view = view
|
||||
popup.cancelable = false
|
||||
session := view.Session()
|
||||
func (popup *popupData) layerCellWidth(arrowLocation int, popupParams Params, session Session) []SizeUnit {
|
||||
|
||||
columnCount := 3
|
||||
rowCount := 3
|
||||
popupRow := 1
|
||||
popupColumn := 1
|
||||
arrow := popupArrow{
|
||||
row: 1,
|
||||
column: 1,
|
||||
align: CenterAlign,
|
||||
}
|
||||
|
||||
switch arrow.location, _ = enumProperty(popupParams, Arrow, session, NoneArrow); arrow.location {
|
||||
case TopArrow:
|
||||
rowCount = 4
|
||||
popupRow = 2
|
||||
|
||||
case BottomArrow:
|
||||
rowCount = 4
|
||||
arrow.row = 2
|
||||
|
||||
case LeftArrow:
|
||||
var columnCount int
|
||||
switch arrowLocation {
|
||||
case LeftArrow, RightArrow:
|
||||
columnCount = 4
|
||||
popupColumn = 2
|
||||
|
||||
case RightArrow:
|
||||
columnCount = 4
|
||||
arrow.column = 2
|
||||
|
||||
default:
|
||||
columnCount = 3
|
||||
}
|
||||
|
||||
cellWidth := make([]SizeUnit, columnCount)
|
||||
|
@ -444,6 +470,19 @@ func (popup *popupData) init(view View, popupParams Params) {
|
|||
cellWidth[0] = Fr(1)
|
||||
cellWidth[columnCount-1] = Fr(1)
|
||||
}
|
||||
return cellWidth
|
||||
}
|
||||
|
||||
func (popup *popupData) layerCellHeight(arrowLocation int, popupParams Params, session Session) []SizeUnit {
|
||||
|
||||
var rowCount int
|
||||
switch arrowLocation {
|
||||
case TopArrow, BottomArrow:
|
||||
rowCount = 4
|
||||
|
||||
default:
|
||||
rowCount = 3
|
||||
}
|
||||
|
||||
cellHeight := make([]SizeUnit, rowCount)
|
||||
switch vAlign, _ := enumProperty(popupParams, VerticalAlign, session, CenterAlign); vAlign {
|
||||
|
@ -458,16 +497,47 @@ func (popup *popupData) init(view View, popupParams Params) {
|
|||
cellHeight[rowCount-1] = Fr(1)
|
||||
}
|
||||
|
||||
return cellHeight
|
||||
}
|
||||
|
||||
func (popup *popupData) init(view View, popupParams Params) {
|
||||
popup.contentView = view
|
||||
popup.cancelable = false
|
||||
session := view.Session()
|
||||
|
||||
popupRow := 1
|
||||
popupColumn := 1
|
||||
arrow := popupArrow{
|
||||
row: 1,
|
||||
column: 1,
|
||||
align: CenterAlign,
|
||||
}
|
||||
|
||||
switch arrow.location, _ = enumProperty(popupParams, Arrow, session, NoneArrow); arrow.location {
|
||||
case TopArrow:
|
||||
popupRow = 2
|
||||
|
||||
case BottomArrow:
|
||||
arrow.row = 2
|
||||
|
||||
case LeftArrow:
|
||||
popupColumn = 2
|
||||
|
||||
case RightArrow:
|
||||
arrow.column = 2
|
||||
}
|
||||
|
||||
layerParams := Params{
|
||||
Style: "ruiPopupLayer",
|
||||
MaxWidth: Percent(100),
|
||||
MaxHeight: Percent(100),
|
||||
CellWidth: cellWidth,
|
||||
CellHeight: cellHeight,
|
||||
CellWidth: popup.layerCellWidth(arrow.location, popupParams, session),
|
||||
CellHeight: popup.layerCellHeight(arrow.location, popupParams, session),
|
||||
}
|
||||
|
||||
params := Params{
|
||||
Style: "ruiPopup",
|
||||
ID: "ruiPopup",
|
||||
Row: popupRow,
|
||||
Column: popupColumn,
|
||||
MaxWidth: Percent(100),
|
||||
|
@ -483,10 +553,14 @@ func (popup *popupData) init(view View, popupParams Params) {
|
|||
}
|
||||
|
||||
var closeButton View = nil
|
||||
outsideClose := false
|
||||
buttons := []PopupButton{}
|
||||
titleStyle := "ruiPopupTitle"
|
||||
var title View = nil
|
||||
outsideClose := false
|
||||
popup.buttons = []PopupButton{}
|
||||
titleStyle := "ruiPopupTitle"
|
||||
|
||||
popup.showOpacity = 1.0
|
||||
popup.showDuration = 1.0
|
||||
popup.showTiming = "easy"
|
||||
|
||||
for tag, value := range popupParams {
|
||||
if value != nil {
|
||||
|
@ -532,10 +606,10 @@ func (popup *popupData) init(view View, popupParams Params) {
|
|||
case Buttons:
|
||||
switch value := value.(type) {
|
||||
case PopupButton:
|
||||
buttons = []PopupButton{value}
|
||||
popup.buttons = []PopupButton{value}
|
||||
|
||||
case []PopupButton:
|
||||
buttons = value
|
||||
popup.buttons = value
|
||||
}
|
||||
|
||||
case Title:
|
||||
|
@ -587,13 +661,36 @@ func (popup *popupData) init(view View, popupParams Params) {
|
|||
case ArrowOffset:
|
||||
arrow.off, _ = sizeProperty(popupParams, ArrowOffset, session)
|
||||
|
||||
case ShowOpacity:
|
||||
if opacity, _ := floatProperty(popupParams, ShowOpacity, session, 1); opacity >= 0 && opacity < 1 {
|
||||
popup.showOpacity = opacity
|
||||
}
|
||||
|
||||
case ShowTransform:
|
||||
if transform := valueToTransformProperty(value); transform != nil && !transform.empty() {
|
||||
popup.showTransform = transform
|
||||
}
|
||||
|
||||
case ShowDuration:
|
||||
if duration, _ := floatProperty(popupParams, ShowDuration, session, 1); duration > 0 {
|
||||
popup.showDuration = duration
|
||||
}
|
||||
|
||||
case ShowTiming:
|
||||
if text, ok := value.(string); ok {
|
||||
text, _ = session.resolveConstants(text)
|
||||
if isTimingFunctionValid(text) {
|
||||
popup.showTiming = text
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
params[tag] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
popupView := NewGridLayout(view.Session(), params)
|
||||
popup.popupView = NewGridLayout(view.Session(), params)
|
||||
|
||||
var popupCellHeight []SizeUnit
|
||||
viewRow := 0
|
||||
|
@ -605,7 +702,7 @@ func (popup *popupData) init(view View, popupParams Params) {
|
|||
if closeButton != nil {
|
||||
titleContent = append(titleContent, closeButton)
|
||||
}
|
||||
popupView.Append(NewGridLayout(session, Params{
|
||||
popup.popupView.Append(NewGridLayout(session, Params{
|
||||
Row: 0,
|
||||
Style: titleStyle,
|
||||
CellWidth: []any{Fr(1), AutoSize()},
|
||||
|
@ -621,10 +718,9 @@ func (popup *popupData) init(view View, popupParams Params) {
|
|||
}
|
||||
|
||||
view.Set(Row, viewRow)
|
||||
popupView.Append(view)
|
||||
popup.popupView.Append(view)
|
||||
|
||||
popup.buttons = buttons
|
||||
if buttonCount := len(buttons); buttonCount > 0 {
|
||||
if buttonCount := len(popup.buttons); buttonCount > 0 {
|
||||
buttonsAlign, _ := enumProperty(params, ButtonsAlign, session, RightAlign)
|
||||
popupCellHeight = append(popupCellHeight, AutoSize())
|
||||
gap, _ := sizeConstant(session, "ruiPopupButtonGap")
|
||||
|
@ -641,7 +737,7 @@ func (popup *popupData) init(view View, popupParams Params) {
|
|||
buttonsPanel.Set(Margin, gap)
|
||||
}
|
||||
|
||||
for i, button := range buttons {
|
||||
for i, button := range popup.buttons {
|
||||
title := button.Title
|
||||
if title == "" && button.Type == CancelButton {
|
||||
title = "Cancel"
|
||||
|
@ -668,33 +764,82 @@ func (popup *popupData) init(view View, popupParams Params) {
|
|||
buttonsPanel.Append(buttonView)
|
||||
}
|
||||
|
||||
popupView.Append(NewGridLayout(session, Params{
|
||||
popup.popupView.Append(NewGridLayout(session, Params{
|
||||
Row: viewRow + 1,
|
||||
CellHorizontalAlign: buttonsAlign,
|
||||
Content: buttonsPanel,
|
||||
}))
|
||||
}
|
||||
|
||||
popupView.Set(CellHeight, popupCellHeight)
|
||||
popup.popupView.Set(CellHeight, popupCellHeight)
|
||||
|
||||
if arrow.location != NoneArrow {
|
||||
layerParams[Content] = []View{popupView, arrow.createView(popupView)}
|
||||
layerParams[Content] = []View{popup.popupView, arrow.createView(popup.popupView)}
|
||||
} else {
|
||||
layerParams[Content] = []View{popupView}
|
||||
layerParams[Content] = []View{popup.popupView}
|
||||
}
|
||||
|
||||
popup.layerView = NewGridLayout(session, layerParams)
|
||||
|
||||
if popup.showOpacity != 1 || popup.showTransform != nil {
|
||||
animation := NewAnimation(Params{
|
||||
Duration: popup.showDuration,
|
||||
TimingFunction: popup.showTiming,
|
||||
})
|
||||
if popup.showOpacity != 1 {
|
||||
popup.popupView.Set(Opacity, popup.showOpacity)
|
||||
popup.popupView.SetTransition(Opacity, animation)
|
||||
}
|
||||
if popup.showTransform != nil {
|
||||
popup.popupView.Set(Transform, popup.showTransform)
|
||||
popup.popupView.SetTransition(Transform, animation)
|
||||
}
|
||||
} else {
|
||||
session.updateCSSProperty("ruiPopupLayer", "transition", "")
|
||||
}
|
||||
|
||||
if outsideClose {
|
||||
popup.layerView.Set(ClickEvent, popup.cancel)
|
||||
}
|
||||
}
|
||||
|
||||
func (popup popupData) View() View {
|
||||
return popup.view
|
||||
func (popup *popupData) showAnimation() {
|
||||
if popup.showOpacity != 1 || popup.showTransform != nil {
|
||||
htmlID := popup.popupView.htmlID()
|
||||
session := popup.Session()
|
||||
if popup.showOpacity != 1 {
|
||||
session.updateCSSProperty(htmlID, string(Opacity), "1")
|
||||
}
|
||||
if popup.showTransform != nil {
|
||||
session.updateCSSProperty(htmlID, string(Transform), "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (popup *popupData) dissmissAnimation(listener func(PropertyName)) bool {
|
||||
if popup.showOpacity != 1 || popup.showTransform != nil {
|
||||
session := popup.Session()
|
||||
popup.popupView.Set(TransitionEndEvent, listener)
|
||||
popup.popupView.Set(TransitionCancelEvent, listener)
|
||||
|
||||
htmlID := popup.popupView.htmlID()
|
||||
if popup.showOpacity != 1 {
|
||||
session.updateCSSProperty(htmlID, string(Opacity), fmt.Sprintf("%.2f", popup.showOpacity))
|
||||
}
|
||||
if popup.showTransform != nil {
|
||||
session.updateCSSProperty(htmlID, string(Transform), popup.showTransform.transformCSS(session))
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (popup *popupData) View() View {
|
||||
return popup.contentView
|
||||
}
|
||||
|
||||
func (popup *popupData) Session() Session {
|
||||
return popup.view.Session()
|
||||
return popup.contentView.Session()
|
||||
}
|
||||
|
||||
func (popup *popupData) cancel() {
|
||||
|
@ -709,9 +854,6 @@ func (popup *popupData) cancel() {
|
|||
|
||||
func (popup *popupData) Dismiss() {
|
||||
popup.Session().popupManager().dismissPopup(popup)
|
||||
for _, listener := range popup.dismissListener {
|
||||
listener(popup)
|
||||
}
|
||||
}
|
||||
|
||||
func (popup *popupData) Show() {
|
||||
|
@ -719,7 +861,6 @@ func (popup *popupData) Show() {
|
|||
}
|
||||
|
||||
func (popup *popupData) html(buffer *strings.Builder) {
|
||||
|
||||
viewHTML(popup.layerView, buffer, "")
|
||||
}
|
||||
|
||||
|
@ -728,6 +869,8 @@ func (popup *popupData) viewByHTMLID(id string) View {
|
|||
}
|
||||
|
||||
func (popup *popupData) onDismiss() {
|
||||
popup.Session().callFunc("removeView", popup.layerView.htmlID())
|
||||
|
||||
for _, listener := range popup.dismissListener {
|
||||
listener(popup)
|
||||
}
|
||||
|
@ -808,6 +951,7 @@ func (manager *popupManager) showPopup(popup Popup) {
|
|||
session.updateCSSProperty("ruiTooltipLayer", "opacity", "0")
|
||||
session.updateCSSProperty("ruiPopupLayer", "visibility", "visible")
|
||||
session.updateCSSProperty("ruiRoot", "pointer-events", "none")
|
||||
popup.showAnimation()
|
||||
}
|
||||
|
||||
func (manager *popupManager) dismissPopup(popup Popup) {
|
||||
|
@ -821,31 +965,37 @@ func (manager *popupManager) dismissPopup(popup Popup) {
|
|||
return
|
||||
}
|
||||
|
||||
index := -1
|
||||
for n, p := range manager.popups {
|
||||
if p == popup {
|
||||
index = n
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if index < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
session := popup.Session()
|
||||
if manager.popups[count-1] == popup {
|
||||
listener := func(PropertyName) {
|
||||
if index == count-1 {
|
||||
if count == 1 {
|
||||
manager.popups = []Popup{}
|
||||
session.updateCSSProperty("ruiRoot", "pointer-events", "auto")
|
||||
session.updateCSSProperty("ruiPopupLayer", "visibility", "hidden")
|
||||
session.updateInnerHTML("ruiPopupLayer", "")
|
||||
} else {
|
||||
manager.popups = manager.popups[:count-1]
|
||||
manager.updatePopupLayerInnerHTML(session)
|
||||
}
|
||||
popup.onDismiss()
|
||||
return
|
||||
}
|
||||
|
||||
for n, p := range manager.popups {
|
||||
if p == popup {
|
||||
if n == 0 {
|
||||
} else if index == 0 {
|
||||
manager.popups = manager.popups[1:]
|
||||
} else {
|
||||
manager.popups = append(manager.popups[:n], manager.popups[n+1:]...)
|
||||
manager.popups = append(manager.popups[:index], manager.popups[index+1:]...)
|
||||
}
|
||||
manager.updatePopupLayerInnerHTML(session)
|
||||
popup.onDismiss()
|
||||
return
|
||||
}
|
||||
|
||||
if !popup.dissmissAnimation(listener) {
|
||||
listener("")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -534,6 +534,10 @@ func (layout *stackLayoutData) moveToFrontByIndex(index int, onShow []func(View)
|
|||
layout.views = append(append(layout.views[:index], layout.views[index+1:]...), view)
|
||||
}
|
||||
|
||||
if !layout.created {
|
||||
return
|
||||
}
|
||||
|
||||
session := layout.Session()
|
||||
pageID := view.htmlID() + "page"
|
||||
peekPageID := peekID + "page"
|
||||
|
@ -649,6 +653,7 @@ func (layout *stackLayoutData) Append(view View) {
|
|||
layout.views = append(layout.views, view)
|
||||
}
|
||||
|
||||
if layout.created {
|
||||
buffer := allocStringBuilder()
|
||||
defer freeStringBuilder(buffer)
|
||||
|
||||
|
@ -667,6 +672,7 @@ func (layout *stackLayoutData) Append(view View) {
|
|||
if listener, ok := layout.changeListener[Content]; ok {
|
||||
listener(layout, Content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert inserts a view to the "index" position in the list of a view children
|
||||
|
|
|
@ -712,7 +712,7 @@ func (style *viewStyle) writeViewTransformCSS(builder cssBuilder, session Sessio
|
|||
}
|
||||
|
||||
if transform := getTransformProperty(style, Transform); transform != nil {
|
||||
builder.add(`TransformProperty`, transform.transformCSS(session))
|
||||
builder.add(`transform`, transform.transformCSS(session))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -75,6 +75,7 @@ func (container *viewsContainerData) Append(view View) {
|
|||
container.views = append(container.views, view)
|
||||
}
|
||||
|
||||
if container.created {
|
||||
buffer := allocStringBuilder()
|
||||
defer freeStringBuilder(buffer)
|
||||
|
||||
|
@ -85,6 +86,7 @@ func (container *viewsContainerData) Append(view View) {
|
|||
listener(container, Content)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert inserts a view to the "index" position in the list of a view children
|
||||
|
@ -102,11 +104,14 @@ func (container *viewsContainerData) Insert(view View, index int) {
|
|||
} else {
|
||||
container.views = append([]View{view}, container.views...)
|
||||
}
|
||||
|
||||
if container.created {
|
||||
updateInnerHTML(htmlID, container.Session())
|
||||
if listener, ok := container.changeListener[Content]; ok {
|
||||
listener(container, Content)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes view from list and return it
|
||||
|
@ -132,10 +137,12 @@ func (container *viewsContainerData) RemoveView(index int) View {
|
|||
|
||||
view.setParentID("")
|
||||
|
||||
if container.created {
|
||||
container.Session().callFunc("removeView", view.htmlID())
|
||||
if listener, ok := container.changeListener[Content]; ok {
|
||||
listener(container, Content)
|
||||
}
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue