mirror of https://github.com/anoshenko/rui.git
1041 lines
29 KiB
Go
1041 lines
29 KiB
Go
package rui
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Frame - the location and size of a rectangle area
|
|
type Frame struct {
|
|
// Left - the left border
|
|
Left float64
|
|
// Top - the top border
|
|
Top float64
|
|
// Width - the width of a rectangle area
|
|
Width float64
|
|
// Height - the height of a rectangle area
|
|
Height float64
|
|
}
|
|
|
|
// Right returns the right border
|
|
func (frame Frame) Right() float64 {
|
|
return frame.Left + frame.Width
|
|
}
|
|
|
|
// Bottom returns the bottom border
|
|
func (frame Frame) Bottom() float64 {
|
|
return frame.Top + frame.Height
|
|
}
|
|
|
|
// View represents a base view interface
|
|
type View interface {
|
|
ViewStyle
|
|
fmt.Stringer
|
|
|
|
// Session returns the current Session interface
|
|
Session() Session
|
|
|
|
// Parent returns the parent view
|
|
Parent() View
|
|
|
|
// Tag returns the tag of View interface
|
|
Tag() string
|
|
|
|
// ID returns the id of the view
|
|
ID() string
|
|
|
|
// Focusable returns true if the view receives the focus
|
|
Focusable() bool
|
|
|
|
// Frame returns the location and size of the view in pixels
|
|
Frame() Frame
|
|
|
|
// Scroll returns the location size of the scrollable view in pixels
|
|
Scroll() Frame
|
|
|
|
// SetParams sets properties with name "tag" of the "rootView" subview. Result:
|
|
// - true - all properties were set successful,
|
|
// - false - error (incompatible type or invalid format of a string value, see AppLog).
|
|
SetParams(params Params) bool
|
|
|
|
// SetAnimated sets the value (second argument) of the property with name defined by the first argument.
|
|
// Return "true" if the value has been set, in the opposite case "false" are returned and
|
|
// a description of the error is written to the log
|
|
SetAnimated(tag PropertyName, value any, animation AnimationProperty) bool
|
|
|
|
// SetChangeListener set the function to track the change of the View property
|
|
SetChangeListener(tag PropertyName, listener func(View, PropertyName))
|
|
|
|
// HasFocus returns 'true' if the view has focus
|
|
HasFocus() bool
|
|
|
|
init(session Session)
|
|
handleCommand(self View, command PropertyName, data DataObject) bool
|
|
htmlClass(disabled bool) string
|
|
htmlTag() string
|
|
closeHTMLTag() bool
|
|
htmlID() string
|
|
parentHTMLID() string
|
|
setParentID(parentID string)
|
|
htmlSubviews(self View, buffer *strings.Builder)
|
|
htmlProperties(self View, buffer *strings.Builder)
|
|
cssStyle(self View, builder cssBuilder)
|
|
addToCSSStyle(addCSS map[string]string)
|
|
exscludeTags() []PropertyName
|
|
htmlDisabledProperty() bool
|
|
|
|
onResize(self View, x, y, width, height float64)
|
|
onItemResize(self View, index string, x, y, width, height float64)
|
|
setNoResizeEvent()
|
|
isNoResizeEvent() bool
|
|
setScroll(x, y, width, height float64)
|
|
}
|
|
|
|
// viewData - base implementation of View interface
|
|
type viewData struct {
|
|
viewStyle
|
|
session Session
|
|
tag string
|
|
viewID string
|
|
_htmlID string
|
|
parentID string
|
|
systemClass string
|
|
changeListener map[PropertyName]func(View, PropertyName)
|
|
singleTransition map[PropertyName]AnimationProperty
|
|
addCSS map[string]string
|
|
frame Frame
|
|
scroll Frame
|
|
noResizeEvent bool
|
|
created bool
|
|
hasFocus bool
|
|
hasHtmlDisabled bool
|
|
get func(tag PropertyName) any
|
|
set func(tag PropertyName, value any) []PropertyName
|
|
remove func(tag PropertyName) []PropertyName
|
|
changed func(tag PropertyName)
|
|
}
|
|
|
|
func newView(session Session) View {
|
|
return new(viewData)
|
|
}
|
|
|
|
// NewView create new View object and return it
|
|
func NewView(session Session, params Params) View {
|
|
view := new(viewData)
|
|
view.init(session)
|
|
setInitParams(view, params)
|
|
return view
|
|
}
|
|
|
|
func setInitParams(view View, params Params) {
|
|
if params != nil {
|
|
session := view.Session()
|
|
if !session.ignoreViewUpdates() {
|
|
session.setIgnoreViewUpdates(true)
|
|
defer session.setIgnoreViewUpdates(false)
|
|
}
|
|
for _, tag := range params.AllTags() {
|
|
if value, ok := params[tag]; ok {
|
|
view.Set(tag, value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (view *viewData) init(session Session) {
|
|
view.viewStyle.init()
|
|
view.get = view.getFunc
|
|
view.set = view.setFunc
|
|
view.remove = view.removeFunc
|
|
view.normalize = normalizeViewTag
|
|
view.changed = view.propertyChanged
|
|
view.tag = "View"
|
|
view.session = session
|
|
view.changeListener = map[PropertyName]func(View, PropertyName){}
|
|
view.addCSS = map[string]string{}
|
|
//view.animation = map[string]AnimationEndListener{}
|
|
view.singleTransition = map[PropertyName]AnimationProperty{}
|
|
view.noResizeEvent = false
|
|
view.created = false
|
|
view.hasHtmlDisabled = false
|
|
}
|
|
|
|
func (view *viewData) Session() Session {
|
|
return view.session
|
|
}
|
|
|
|
func (view *viewData) Parent() View {
|
|
return view.session.viewByHTMLID(view.parentID)
|
|
}
|
|
|
|
func (view *viewData) parentHTMLID() string {
|
|
return view.parentID
|
|
}
|
|
|
|
func (view *viewData) setParentID(parentID string) {
|
|
view.parentID = parentID
|
|
}
|
|
|
|
func (view *viewData) Tag() string {
|
|
return view.tag
|
|
}
|
|
|
|
func (view *viewData) ID() string {
|
|
return view.viewID
|
|
}
|
|
|
|
func (view *viewData) ViewByID(id string) View {
|
|
if id == view.ID() {
|
|
if v := view.session.viewByHTMLID(view.htmlID()); v != nil {
|
|
return v
|
|
}
|
|
return view
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (view *viewData) Focusable() bool {
|
|
if focus, ok := boolProperty(view, Focusable, view.session); ok {
|
|
return focus
|
|
}
|
|
|
|
if style, ok := stringProperty(view, Style, view.session); ok {
|
|
if style, ok := view.session.resolveConstants(style); ok {
|
|
if value := view.session.styleProperty(style, Focusable); ok {
|
|
if focus, ok := valueToBool(value, view.Session()); ok {
|
|
return focus
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (view *viewData) Remove(tag PropertyName) {
|
|
changedTags := view.removeFunc(view.normalize(tag))
|
|
|
|
if view.created && len(changedTags) > 0 {
|
|
for _, tag := range changedTags {
|
|
view.changed(tag)
|
|
}
|
|
|
|
for _, tag := range changedTags {
|
|
if listener, ok := view.changeListener[tag]; ok {
|
|
listener(view, tag)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (view *viewData) Get(tag PropertyName) any {
|
|
switch tag {
|
|
case ID:
|
|
return view.ID()
|
|
}
|
|
return view.get(view.normalize(tag))
|
|
|
|
}
|
|
|
|
func (view *viewData) Set(tag PropertyName, value any) bool {
|
|
if value == nil {
|
|
view.Remove(tag)
|
|
return true
|
|
}
|
|
|
|
tag = view.normalize(tag)
|
|
changedTags := view.set(tag, value)
|
|
|
|
if view.created && len(changedTags) > 0 {
|
|
for _, tag := range changedTags {
|
|
view.changed(tag)
|
|
}
|
|
|
|
for _, tag := range changedTags {
|
|
if listener, ok := view.changeListener[tag]; ok {
|
|
listener(view, tag)
|
|
}
|
|
}
|
|
}
|
|
|
|
return changedTags != nil
|
|
}
|
|
|
|
func normalizeViewTag(tag PropertyName) PropertyName {
|
|
tag = normalizeViewStyleTag(tag)
|
|
switch tag {
|
|
case "tab-index":
|
|
return TabIndex
|
|
}
|
|
return tag
|
|
}
|
|
|
|
func (view *viewData) getFunc(tag PropertyName) any {
|
|
if tag == ID {
|
|
if id := view.ID(); id != "" {
|
|
return id
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
return viewStyleGet(view, tag)
|
|
}
|
|
|
|
func (view *viewData) removeFunc(tag PropertyName) []PropertyName {
|
|
var changedTags []PropertyName = nil
|
|
|
|
switch tag {
|
|
case ID:
|
|
if view.viewID != "" {
|
|
view.viewID = ""
|
|
changedTags = []PropertyName{ID}
|
|
} else {
|
|
changedTags = []PropertyName{}
|
|
}
|
|
|
|
case Animation:
|
|
if val := view.getRaw(Animation); val != nil {
|
|
if animations, ok := val.([]AnimationProperty); ok {
|
|
for _, animation := range animations {
|
|
animation.unused(view.session)
|
|
}
|
|
}
|
|
|
|
view.setRaw(Animation, nil)
|
|
changedTags = []PropertyName{Animation}
|
|
}
|
|
|
|
default:
|
|
changedTags = viewStyleRemove(view, tag)
|
|
}
|
|
|
|
return changedTags
|
|
}
|
|
|
|
func (view *viewData) setFunc(tag PropertyName, value any) []PropertyName {
|
|
|
|
switch tag {
|
|
|
|
case ID:
|
|
if text, ok := value.(string); ok {
|
|
view.viewID = text
|
|
view.setRaw(ID, text)
|
|
return []PropertyName{ID}
|
|
}
|
|
notCompatibleType(ID, value)
|
|
return nil
|
|
|
|
case Animation:
|
|
oldAnimations := []AnimationProperty{}
|
|
if val := view.getRaw(Animation); val != nil {
|
|
if animation, ok := val.([]AnimationProperty); ok {
|
|
oldAnimations = animation
|
|
}
|
|
}
|
|
|
|
if !setAnimationProperty(view, tag, value) {
|
|
return nil
|
|
}
|
|
|
|
for _, animation := range oldAnimations {
|
|
animation.unused(view.session)
|
|
}
|
|
return []PropertyName{Animation}
|
|
|
|
case TabIndex, "tab-index":
|
|
return setIntProperty(view, TabIndex, value)
|
|
|
|
case UserData:
|
|
view.setRaw(tag, value)
|
|
return []PropertyName{UserData}
|
|
|
|
case Style, StyleDisabled:
|
|
if text, ok := value.(string); ok {
|
|
view.setRaw(tag, text)
|
|
return []PropertyName{tag}
|
|
}
|
|
notCompatibleType(ID, value)
|
|
return nil
|
|
|
|
case FocusEvent, LostFocusEvent:
|
|
return setNoArgEventListener[View](view, tag, value)
|
|
|
|
case KeyDownEvent, KeyUpEvent:
|
|
return setOneArgEventListener[View, KeyEvent](view, tag, value)
|
|
|
|
case ClickEvent, DoubleClickEvent, MouseDown, MouseUp, MouseMove, MouseOut, MouseOver, ContextMenuEvent:
|
|
return setOneArgEventListener[View, MouseEvent](view, tag, value)
|
|
|
|
case PointerDown, PointerUp, PointerMove, PointerOut, PointerOver, PointerCancel:
|
|
return setOneArgEventListener[View, PointerEvent](view, tag, value)
|
|
|
|
case TouchStart, TouchEnd, TouchMove, TouchCancel:
|
|
return setOneArgEventListener[View, TouchEvent](view, tag, value)
|
|
|
|
case TransitionRunEvent, TransitionStartEvent, TransitionEndEvent, TransitionCancelEvent:
|
|
result := setOneArgEventListener[View, PropertyName](view, tag, value)
|
|
if result == nil {
|
|
result = setOneArgEventListener[View, string](view, tag, value)
|
|
if result != nil {
|
|
if listeners, ok := view.getRaw(tag).([]func(View, string)); ok {
|
|
newListeners := make([]func(View, PropertyName), len(listeners))
|
|
for i, listener := range listeners {
|
|
newListeners[i] = func(view View, name PropertyName) {
|
|
listener(view, string(name))
|
|
}
|
|
}
|
|
view.setRaw(tag, newListeners)
|
|
return result
|
|
}
|
|
view.setRaw(tag, nil)
|
|
return nil
|
|
}
|
|
}
|
|
return result
|
|
|
|
case AnimationStartEvent, AnimationEndEvent, AnimationIterationEvent, AnimationCancelEvent:
|
|
return setOneArgEventListener[View, string](view, tag, value)
|
|
|
|
case ResizeEvent, ScrollEvent:
|
|
return setOneArgEventListener[View, Frame](view, tag, value)
|
|
}
|
|
|
|
return viewStyleSet(view, tag, value)
|
|
}
|
|
|
|
func (view *viewData) SetParams(params Params) bool {
|
|
if params == nil {
|
|
errorLog("Argument of function SetParams is nil")
|
|
return false
|
|
}
|
|
|
|
session := view.Session()
|
|
session.startUpdateScript(view.htmlID())
|
|
result := true
|
|
for _, tag := range params.AllTags() {
|
|
if value, ok := params[tag]; ok {
|
|
result = view.Set(tag, value) && result
|
|
}
|
|
}
|
|
session.finishUpdateScript(view.htmlID())
|
|
return result
|
|
}
|
|
|
|
func (view *viewData) propertyChanged(tag PropertyName) {
|
|
|
|
htmlID := view.htmlID()
|
|
session := view.Session()
|
|
|
|
switch tag {
|
|
case TabIndex:
|
|
if value, ok := intProperty(view, TabIndex, view.Session(), 0); ok {
|
|
session.updateProperty(view.htmlID(), "tabindex", strconv.Itoa(value))
|
|
} else if view.Focusable() {
|
|
session.updateProperty(view.htmlID(), "tabindex", "0")
|
|
} else {
|
|
session.updateProperty(view.htmlID(), "tabindex", "-1")
|
|
}
|
|
|
|
case Style, StyleDisabled:
|
|
session.updateProperty(view.htmlID(), "class", view.htmlClass(IsDisabled(view)))
|
|
|
|
case Disabled:
|
|
tabIndex := GetTabIndex(view, htmlID)
|
|
enabledClass := view.htmlClass(false)
|
|
disabledClass := view.htmlClass(true)
|
|
session.startUpdateScript(htmlID)
|
|
if IsDisabled(view) {
|
|
session.updateProperty(htmlID, "data-disabled", "1")
|
|
if view.htmlDisabledProperty() {
|
|
session.updateProperty(htmlID, "disabled", true)
|
|
}
|
|
if tabIndex >= 0 {
|
|
session.updateProperty(htmlID, "tabindex", -1)
|
|
}
|
|
if enabledClass != disabledClass {
|
|
session.updateProperty(htmlID, "class", disabledClass)
|
|
}
|
|
} else {
|
|
session.updateProperty(htmlID, "data-disabled", "0")
|
|
if view.htmlDisabledProperty() {
|
|
session.removeProperty(htmlID, "disabled")
|
|
}
|
|
if tabIndex >= 0 {
|
|
session.updateProperty(htmlID, "tabindex", tabIndex)
|
|
}
|
|
if enabledClass != disabledClass {
|
|
session.updateProperty(htmlID, "class", enabledClass)
|
|
}
|
|
}
|
|
session.finishUpdateScript(htmlID)
|
|
updateInnerHTML(htmlID, session)
|
|
|
|
case Visibility:
|
|
switch GetVisibility(view) {
|
|
case Invisible:
|
|
session.updateCSSProperty(htmlID, string(Visibility), "hidden")
|
|
session.updateCSSProperty(htmlID, "display", "")
|
|
session.callFunc("hideTooltip")
|
|
|
|
case Gone:
|
|
session.updateCSSProperty(htmlID, string(Visibility), "hidden")
|
|
session.updateCSSProperty(htmlID, "display", "none")
|
|
session.callFunc("hideTooltip")
|
|
|
|
default:
|
|
session.updateCSSProperty(htmlID, string(Visibility), "visible")
|
|
session.updateCSSProperty(htmlID, "display", "")
|
|
}
|
|
|
|
case Background:
|
|
session.updateCSSProperty(htmlID, string(Background), backgroundCSS(view, session))
|
|
|
|
case Mask:
|
|
session.updateCSSProperty(htmlID, "mask", maskCSS(view, session))
|
|
|
|
case Border, BorderLeft, BorderRight, BorderTop, BorderBottom:
|
|
cssWidth := ""
|
|
cssColor := ""
|
|
cssStyle := "none"
|
|
|
|
if border := getBorderProperty(view, Border); border != nil {
|
|
cssWidth = border.cssWidthValue(session)
|
|
cssColor = border.cssColorValue(session)
|
|
cssStyle = border.cssStyleValue(session)
|
|
}
|
|
|
|
session.updateCSSProperty(htmlID, string(BorderWidth), cssWidth)
|
|
session.updateCSSProperty(htmlID, string(BorderColor), cssColor)
|
|
session.updateCSSProperty(htmlID, string(BorderStyle), cssStyle)
|
|
|
|
case BorderStyle, BorderLeftStyle, BorderRightStyle, BorderTopStyle, BorderBottomStyle:
|
|
if border := getBorderProperty(view, Border); border != nil {
|
|
session.updateCSSProperty(htmlID, string(BorderStyle), border.cssStyleValue(session))
|
|
}
|
|
|
|
case BorderColor, BorderLeftColor, BorderRightColor, BorderTopColor, BorderBottomColor:
|
|
if border := getBorderProperty(view, Border); border != nil {
|
|
session.updateCSSProperty(htmlID, string(BorderColor), border.cssColorValue(session))
|
|
}
|
|
|
|
case BorderWidth, BorderLeftWidth, BorderRightWidth, BorderTopWidth, BorderBottomWidth:
|
|
if border := getBorderProperty(view, Border); border != nil {
|
|
session.updateCSSProperty(htmlID, string(BorderWidth), border.cssWidthValue(session))
|
|
}
|
|
|
|
case Outline, OutlineColor, OutlineStyle, OutlineWidth:
|
|
session.updateCSSProperty(htmlID, string(Outline), GetOutline(view).cssString(session))
|
|
|
|
case Shadow:
|
|
session.updateCSSProperty(htmlID, "box-shadow", shadowCSS(view, Shadow, session))
|
|
|
|
case TextShadow:
|
|
session.updateCSSProperty(htmlID, "text-shadow", shadowCSS(view, TextShadow, session))
|
|
|
|
case Radius, RadiusX, RadiusY, RadiusTopLeft, RadiusTopLeftX, RadiusTopLeftY,
|
|
RadiusTopRight, RadiusTopRightX, RadiusTopRightY,
|
|
RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY,
|
|
RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY:
|
|
radius := GetRadius(view)
|
|
session.updateCSSProperty(htmlID, "border-radius", radius.cssString(session))
|
|
|
|
case Margin, MarginTop, MarginRight, MarginBottom, MarginLeft,
|
|
"top-margin", "right-margin", "bottom-margin", "left-margin":
|
|
margin := GetMargin(view)
|
|
session.updateCSSProperty(htmlID, string(Margin), margin.cssString(session))
|
|
|
|
case Padding, PaddingTop, PaddingRight, PaddingBottom, PaddingLeft,
|
|
"top-padding", "right-padding", "bottom-padding", "left-padding":
|
|
padding := GetPadding(view)
|
|
session.updateCSSProperty(htmlID, string(Padding), padding.cssString(session))
|
|
|
|
case AvoidBreak:
|
|
if avoid, ok := boolProperty(view, AvoidBreak, session); ok {
|
|
if avoid {
|
|
session.updateCSSProperty(htmlID, "break-inside", "avoid")
|
|
} else {
|
|
session.updateCSSProperty(htmlID, "break-inside", "auto")
|
|
}
|
|
}
|
|
|
|
case Clip:
|
|
if clip := getClipShapeProperty(view, Clip, session); clip != nil && clip.valid(session) {
|
|
session.updateCSSProperty(htmlID, `clip-path`, clip.cssStyle(session))
|
|
} else {
|
|
session.updateCSSProperty(htmlID, `clip-path`, "none")
|
|
}
|
|
|
|
case ShapeOutside:
|
|
if clip := getClipShapeProperty(view, ShapeOutside, session); clip != nil && clip.valid(session) {
|
|
session.updateCSSProperty(htmlID, string(ShapeOutside), clip.cssStyle(session))
|
|
} else {
|
|
session.updateCSSProperty(htmlID, string(ShapeOutside), "none")
|
|
}
|
|
|
|
case Filter:
|
|
text := ""
|
|
if value := view.getRaw(Filter); value != nil {
|
|
if filter, ok := value.(FilterProperty); ok {
|
|
text = filter.cssStyle(session)
|
|
}
|
|
}
|
|
session.updateCSSProperty(htmlID, string(Filter), text)
|
|
|
|
case BackdropFilter:
|
|
text := ""
|
|
if value := view.getRaw(BackdropFilter); value != nil {
|
|
if filter, ok := value.(FilterProperty); ok {
|
|
text = filter.cssStyle(session)
|
|
}
|
|
}
|
|
if session.startUpdateScript(htmlID) {
|
|
defer session.finishUpdateScript(htmlID)
|
|
}
|
|
session.updateCSSProperty(htmlID, "-webkit-backdrop-filter", text)
|
|
session.updateCSSProperty(htmlID, string(BackdropFilter), text)
|
|
|
|
case FontName:
|
|
if font, ok := stringProperty(view, FontName, session); ok {
|
|
session.updateCSSProperty(htmlID, "font-family", font)
|
|
} else {
|
|
session.updateCSSProperty(htmlID, "font-family", "")
|
|
}
|
|
|
|
case Italic:
|
|
if state, ok := boolProperty(view, tag, session); ok {
|
|
if state {
|
|
session.updateCSSProperty(htmlID, "font-style", "italic")
|
|
} else {
|
|
session.updateCSSProperty(htmlID, "font-style", "normal")
|
|
}
|
|
} else {
|
|
session.updateCSSProperty(htmlID, "font-style", "")
|
|
}
|
|
|
|
case SmallCaps:
|
|
if state, ok := boolProperty(view, tag, session); ok {
|
|
if state {
|
|
session.updateCSSProperty(htmlID, "font-variant", "small-caps")
|
|
} else {
|
|
session.updateCSSProperty(htmlID, "font-variant", "normal")
|
|
}
|
|
} else {
|
|
session.updateCSSProperty(htmlID, "font-variant", "")
|
|
}
|
|
|
|
case Strikethrough, Overline, Underline:
|
|
session.updateCSSProperty(htmlID, "text-decoration", textDecorationCSS(view, session))
|
|
/*
|
|
for _, tag2 := range []PropertyName{TextLineColor, TextLineStyle, TextLineThickness} {
|
|
view.propertyChanged(tag2)
|
|
}
|
|
*/
|
|
|
|
case Transition:
|
|
session.updateCSSProperty(htmlID, "transition", transitionCSS(view, session))
|
|
|
|
case Animation:
|
|
session.updateCSSProperty(htmlID, "animation", animationCSS(view, session))
|
|
|
|
case AnimationPaused:
|
|
paused, ok := boolProperty(view, AnimationPaused, session)
|
|
if !ok {
|
|
session.updateCSSProperty(htmlID, `animation-play-state`, ``)
|
|
} else if paused {
|
|
session.updateCSSProperty(htmlID, `animation-play-state`, `paused`)
|
|
} else {
|
|
session.updateCSSProperty(htmlID, `animation-play-state`, `running`)
|
|
}
|
|
|
|
case ZIndex, Order, TabSize:
|
|
if i, ok := intProperty(view, tag, session, 0); ok {
|
|
session.updateCSSProperty(htmlID, string(tag), strconv.Itoa(i))
|
|
} else {
|
|
session.updateCSSProperty(htmlID, string(tag), "")
|
|
}
|
|
|
|
case Row, Column:
|
|
if parentID := view.parentHTMLID(); parentID != "" {
|
|
updateInnerHTML(parentID, session)
|
|
}
|
|
|
|
case UserSelect:
|
|
if session.startUpdateScript(htmlID) {
|
|
defer session.finishUpdateScript(htmlID)
|
|
}
|
|
if userSelect, ok := boolProperty(view, UserSelect, session); ok {
|
|
if userSelect {
|
|
session.updateCSSProperty(htmlID, "-webkit-user-select", "auto")
|
|
session.updateCSSProperty(htmlID, "user-select", "auto")
|
|
} else {
|
|
session.updateCSSProperty(htmlID, "-webkit-user-select", "none")
|
|
session.updateCSSProperty(htmlID, "user-select", "none")
|
|
}
|
|
} else {
|
|
session.updateCSSProperty(htmlID, "-webkit-user-select", "")
|
|
session.updateCSSProperty(htmlID, "user-select", "")
|
|
}
|
|
|
|
case ColumnSpanAll:
|
|
if spanAll, ok := boolProperty(view, ColumnSpanAll, session); ok && spanAll {
|
|
session.updateCSSProperty(htmlID, `column-span`, `all`)
|
|
} else {
|
|
session.updateCSSProperty(htmlID, `column-span`, `none`)
|
|
}
|
|
|
|
case Tooltip:
|
|
if tooltip := GetTooltip(view); tooltip == "" {
|
|
session.removeProperty(htmlID, "data-tooltip")
|
|
} else {
|
|
session.updateProperty(htmlID, "data-tooltip", tooltip)
|
|
session.updateProperty(htmlID, "onmouseenter", "mouseEnterEvent(this, event)")
|
|
session.updateProperty(htmlID, "onmouseleave", "mouseLeaveEvent(this, event)")
|
|
}
|
|
|
|
case PerspectiveOriginX, PerspectiveOriginY:
|
|
x, y := GetPerspectiveOrigin(view)
|
|
session.updateCSSProperty(htmlID, "perspective-origin", transformOriginCSS(x, y, AutoSize(), view.Session()))
|
|
|
|
case BackfaceVisible:
|
|
if GetBackfaceVisible(view) {
|
|
session.updateCSSProperty(htmlID, string(BackfaceVisible), "visible")
|
|
} else {
|
|
session.updateCSSProperty(htmlID, string(BackfaceVisible), "hidden")
|
|
}
|
|
|
|
case TransformOriginX, TransformOriginY, TransformOriginZ:
|
|
x, y, z := getTransformOrigin(view, session)
|
|
session.updateCSSProperty(htmlID, "transform-origin", transformOriginCSS(x, y, z, view.Session()))
|
|
|
|
case Transform:
|
|
css := ""
|
|
if transform := getTransformProperty(view, Transform); transform != nil {
|
|
css = transform.transformCSS(session)
|
|
}
|
|
session.updateCSSProperty(htmlID, "transform", css)
|
|
|
|
case Perspective, SkewX, SkewY, TranslateX, TranslateY, TranslateZ,
|
|
ScaleX, ScaleY, ScaleZ, Rotate, RotateX, RotateY, RotateZ:
|
|
// do nothing
|
|
|
|
case FocusEvent, LostFocusEvent, ResizeEvent, ScrollEvent, KeyDownEvent, KeyUpEvent,
|
|
ClickEvent, DoubleClickEvent, MouseDown, MouseUp, MouseMove, MouseOut, MouseOver, ContextMenuEvent,
|
|
PointerDown, PointerUp, PointerMove, PointerOut, PointerOver, PointerCancel,
|
|
TouchStart, TouchEnd, TouchMove, TouchCancel,
|
|
TransitionRunEvent, TransitionStartEvent, TransitionEndEvent, TransitionCancelEvent,
|
|
AnimationStartEvent, AnimationEndEvent, AnimationIterationEvent, AnimationCancelEvent:
|
|
|
|
updateEventListenerHtml(view, tag)
|
|
|
|
case DataList:
|
|
updateInnerHTML(view.htmlID(), view.Session())
|
|
|
|
default:
|
|
if cssTag, ok := sizeProperties[tag]; ok {
|
|
if size, ok := sizeProperty(view, tag, session); ok {
|
|
session.updateCSSProperty(htmlID, cssTag, size.cssString("", session))
|
|
} else {
|
|
session.updateCSSProperty(htmlID, cssTag, "")
|
|
}
|
|
return
|
|
}
|
|
|
|
colorTags := map[PropertyName]string{
|
|
BackgroundColor: string(BackgroundColor),
|
|
TextColor: "color",
|
|
TextLineColor: "text-decoration-color",
|
|
CaretColor: string(CaretColor),
|
|
AccentColor: string(AccentColor),
|
|
}
|
|
if cssTag, ok := colorTags[tag]; ok {
|
|
if color, ok := colorProperty(view, tag, session); ok {
|
|
session.updateCSSProperty(htmlID, cssTag, color.cssString())
|
|
} else {
|
|
session.updateCSSProperty(htmlID, cssTag, "")
|
|
}
|
|
return
|
|
}
|
|
|
|
if valuesData, ok := enumProperties[tag]; ok && valuesData.cssTag != "" {
|
|
if n, ok := enumProperty(view, tag, session, 0); ok {
|
|
session.updateCSSProperty(htmlID, valuesData.cssTag, valuesData.cssValues[n])
|
|
} else {
|
|
session.updateCSSProperty(htmlID, valuesData.cssTag, "")
|
|
}
|
|
return
|
|
}
|
|
|
|
if f, ok := floatTextProperty(view, Opacity, session, 0); ok {
|
|
session.updateCSSProperty(htmlID, string(Opacity), f)
|
|
} else {
|
|
session.updateCSSProperty(htmlID, string(Opacity), "")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (view *viewData) htmlTag() string {
|
|
if semantics := GetSemantics(view); semantics > DefaultSemantics {
|
|
values := enumProperties[Semantics].cssValues
|
|
if semantics < len(values) {
|
|
return values[semantics]
|
|
}
|
|
}
|
|
return "div"
|
|
}
|
|
|
|
func (view *viewData) closeHTMLTag() bool {
|
|
return true
|
|
}
|
|
|
|
func (view *viewData) htmlID() string {
|
|
if view._htmlID == "" {
|
|
view._htmlID = view.session.nextViewID()
|
|
}
|
|
return view._htmlID
|
|
}
|
|
|
|
func (view *viewData) htmlSubviews(self View, buffer *strings.Builder) {
|
|
}
|
|
|
|
func (view *viewData) addToCSSStyle(addCSS map[string]string) {
|
|
view.addCSS = addCSS
|
|
}
|
|
|
|
func (view *viewData) cssStyle(self View, builder cssBuilder) {
|
|
view.viewStyle.cssViewStyle(builder, view.session)
|
|
/*
|
|
switch GetVisibility(view) {
|
|
case Invisible:
|
|
builder.add(`visibility`, `hidden`)
|
|
|
|
case Gone:
|
|
builder.add(`display`, `none`)
|
|
}
|
|
*/
|
|
if view.addCSS != nil {
|
|
for tag, value := range view.addCSS {
|
|
builder.add(tag, value)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (view *viewData) htmlDisabledProperty() bool {
|
|
return view.hasHtmlDisabled
|
|
}
|
|
|
|
func (view *viewData) htmlProperties(self View, buffer *strings.Builder) {
|
|
view.created = true
|
|
|
|
if IsDisabled(self) {
|
|
buffer.WriteString(` data-disabled="1"`)
|
|
if view.hasHtmlDisabled {
|
|
buffer.WriteString(` disabled`)
|
|
}
|
|
} else {
|
|
buffer.WriteString(` data-disabled="0"`)
|
|
}
|
|
|
|
if view.frame.Left != 0 || view.frame.Top != 0 || view.frame.Width != 0 || view.frame.Height != 0 {
|
|
buffer.WriteString(fmt.Sprintf(` data-left="%g" data-top="%g" data-width="%g" data-height="%g"`,
|
|
view.frame.Left, view.frame.Top, view.frame.Width, view.frame.Height))
|
|
}
|
|
}
|
|
|
|
func viewHTML(view View, buffer *strings.Builder, htmlTag string) {
|
|
if htmlTag == "" {
|
|
htmlTag = view.htmlTag()
|
|
}
|
|
//viewHTMLTag := view.htmlTag()
|
|
buffer.WriteRune('<')
|
|
buffer.WriteString(htmlTag)
|
|
buffer.WriteString(` id="`)
|
|
buffer.WriteString(view.htmlID())
|
|
buffer.WriteRune('"')
|
|
|
|
disabled := IsDisabled(view)
|
|
|
|
if cls := view.htmlClass(disabled); cls != "" {
|
|
buffer.WriteString(` class="`)
|
|
buffer.WriteString(cls)
|
|
buffer.WriteRune('"')
|
|
}
|
|
|
|
cssBuilder := viewCSSBuilder{buffer: allocStringBuilder()}
|
|
view.cssStyle(view, &cssBuilder)
|
|
|
|
if style := cssBuilder.finish(); style != "" {
|
|
buffer.WriteString(` style="`)
|
|
buffer.WriteString(style)
|
|
buffer.WriteRune('"')
|
|
}
|
|
|
|
buffer.WriteRune(' ')
|
|
view.htmlProperties(view, buffer)
|
|
|
|
if view.isNoResizeEvent() {
|
|
buffer.WriteString(` data-noresize="1" `)
|
|
} else {
|
|
buffer.WriteRune(' ')
|
|
}
|
|
|
|
if !disabled {
|
|
if tabIndex := GetTabIndex(view); tabIndex >= 0 {
|
|
buffer.WriteString(`tabindex="`)
|
|
buffer.WriteString(strconv.Itoa(tabIndex))
|
|
buffer.WriteString(`" `)
|
|
}
|
|
}
|
|
|
|
if tooltip := GetTooltip(view); tooltip != "" {
|
|
buffer.WriteString(`data-tooltip=" `)
|
|
buffer.WriteString(tooltip)
|
|
buffer.WriteString(`" onmouseenter="mouseEnterEvent(this, event)" onmouseleave="mouseLeaveEvent(this, event)" `)
|
|
}
|
|
|
|
buffer.WriteString(`onscroll="scrollEvent(this, event)" `)
|
|
|
|
focusEventsHtml(view, buffer)
|
|
keyEventsHtml(view, buffer)
|
|
|
|
viewEventsHtml[MouseEvent](view, []PropertyName{ClickEvent, DoubleClickEvent, MouseDown, MouseUp, MouseMove, MouseOut, MouseOver, ContextMenuEvent}, buffer)
|
|
//mouseEventsHtml(view, buffer, hasTooltip)
|
|
|
|
viewEventsHtml[PointerEvent](view, []PropertyName{PointerDown, PointerUp, PointerMove, PointerOut, PointerOver, PointerCancel}, buffer)
|
|
//pointerEventsHtml(view, buffer)
|
|
|
|
viewEventsHtml[TouchEvent](view, []PropertyName{TouchStart, TouchEnd, TouchMove, TouchCancel}, buffer)
|
|
//touchEventsHtml(view, buffer)
|
|
|
|
viewEventsHtml[string](view, []PropertyName{TransitionRunEvent, TransitionStartEvent, TransitionEndEvent, TransitionCancelEvent,
|
|
AnimationStartEvent, AnimationEndEvent, AnimationIterationEvent, AnimationCancelEvent}, buffer)
|
|
//transitionEventsHtml(view, buffer)
|
|
//animationEventsHtml(view, buffer)
|
|
|
|
buffer.WriteRune('>')
|
|
view.htmlSubviews(view, buffer)
|
|
if view.closeHTMLTag() {
|
|
buffer.WriteString(`</`)
|
|
buffer.WriteString(htmlTag)
|
|
buffer.WriteRune('>')
|
|
}
|
|
}
|
|
|
|
func (view *viewData) htmlClass(disabled bool) string {
|
|
cls := "ruiView"
|
|
disabledStyle := false
|
|
if disabled {
|
|
if value, ok := stringProperty(view, StyleDisabled, view.Session()); ok && value != "" {
|
|
cls += " " + value
|
|
disabledStyle = true
|
|
}
|
|
}
|
|
if !disabledStyle {
|
|
if value, ok := stringProperty(view, Style, view.Session()); ok {
|
|
cls += " " + value
|
|
}
|
|
}
|
|
|
|
if view.systemClass != "" {
|
|
cls = view.systemClass + " " + cls
|
|
}
|
|
|
|
return cls
|
|
}
|
|
|
|
func (view *viewData) handleCommand(self View, command PropertyName, data DataObject) bool {
|
|
switch command {
|
|
|
|
case KeyDownEvent, KeyUpEvent:
|
|
if !IsDisabled(self) {
|
|
handleKeyEvents(self, command, data)
|
|
}
|
|
|
|
case ClickEvent, DoubleClickEvent, MouseDown, MouseUp, MouseMove, MouseOut, MouseOver, ContextMenuEvent:
|
|
handleMouseEvents(self, command, data)
|
|
|
|
case PointerDown, PointerUp, PointerMove, PointerOut, PointerOver, PointerCancel:
|
|
handlePointerEvents(self, command, data)
|
|
|
|
case TouchStart, TouchEnd, TouchMove, TouchCancel:
|
|
handleTouchEvents(self, command, data)
|
|
|
|
case FocusEvent:
|
|
view.hasFocus = true
|
|
for _, listener := range getNoArgEventListeners[View](view, nil, command) {
|
|
listener(self)
|
|
}
|
|
|
|
case LostFocusEvent:
|
|
view.hasFocus = false
|
|
for _, listener := range getNoArgEventListeners[View](view, nil, command) {
|
|
listener(self)
|
|
}
|
|
|
|
case TransitionRunEvent, TransitionStartEvent, TransitionEndEvent, TransitionCancelEvent:
|
|
view.handleTransitionEvents(command, data)
|
|
|
|
case AnimationStartEvent, AnimationEndEvent, AnimationIterationEvent, AnimationCancelEvent:
|
|
view.handleAnimationEvents(command, data)
|
|
|
|
case "scroll":
|
|
view.onScroll(view, dataFloatProperty(data, "x"), dataFloatProperty(data, "y"), dataFloatProperty(data, "width"), dataFloatProperty(data, "height"))
|
|
|
|
case "widthChanged":
|
|
if value, ok := data.PropertyValue("width"); ok {
|
|
if width, ok := StringToSizeUnit(value); ok {
|
|
self.setRaw(Width, width)
|
|
}
|
|
}
|
|
|
|
case "heightChanged":
|
|
if value, ok := data.PropertyValue("height"); ok {
|
|
if height, ok := StringToSizeUnit(value); ok {
|
|
self.setRaw(Height, height)
|
|
}
|
|
}
|
|
|
|
/*
|
|
case "resize":
|
|
floatProperty := func(tag string) float64 {
|
|
if value, ok := data.PropertyValue(tag); ok {
|
|
if result, err := strconv.ParseFloat(value, 64); err == nil {
|
|
return result
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
self.onResize(self, floatProperty("x"), floatProperty("y"), floatProperty("width"), floatProperty("height"))
|
|
return true
|
|
*/
|
|
default:
|
|
return false
|
|
}
|
|
return true
|
|
|
|
}
|
|
|
|
func (view *viewData) SetChangeListener(tag PropertyName, listener func(View, PropertyName)) {
|
|
if listener == nil {
|
|
delete(view.changeListener, tag)
|
|
} else {
|
|
view.changeListener[tag] = listener
|
|
}
|
|
}
|
|
|
|
func (view *viewData) HasFocus() bool {
|
|
return view.hasFocus
|
|
}
|
|
|
|
func (view *viewData) String() string {
|
|
buffer := allocStringBuilder()
|
|
defer freeStringBuilder(buffer)
|
|
writeViewStyle(view.tag, view, buffer, "", nil)
|
|
return buffer.String()
|
|
}
|
|
|
|
func (view *viewData) exscludeTags() []PropertyName {
|
|
return nil
|
|
}
|