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 Animation) 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]Animation addCSS map[string]string frame Frame scroll Frame noResizeEvent bool created bool hasFocus bool hasHtmlDisabled bool getFunc func(view View, tag PropertyName) any set func(view View, tag PropertyName, value any) []PropertyName remove func(view View, tag PropertyName) []PropertyName changed func(view View, 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.getFunc = viewGet view.set = viewSet view.normalize = normalizeViewTag view.changed = viewPropertyChanged 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]Animation{} 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) { tag = view.normalize(tag) var changedTags []PropertyName = nil switch tag { case ID: if view.viewID != "" { view.viewID = "" changedTags = []PropertyName{ID} } case AnimationTag: if val := view.getRaw(AnimationTag); val != nil { if animations, ok := val.([]Animation); ok { for _, animation := range animations { animation.unused(view.session) } } view.setRaw(AnimationTag, nil) changedTags = []PropertyName{AnimationTag} } default: changedTags = view.remove(view, tag) } if view.created && len(changedTags) > 0 { for _, tag := range changedTags { view.changed(view, tag) } for _, tag := range changedTags { if listener, ok := view.changeListener[tag]; ok { listener(view, tag) } } } } func (view *viewData) Set(tag PropertyName, value any) bool { if value == nil { view.Remove(tag) return true } tag = view.normalize(tag) var changedTags []PropertyName = nil switch tag { case ID: text, ok := value.(string) if !ok { notCompatibleType(ID, value) return false } view.viewID = text changedTags = []PropertyName{ID} case AnimationTag: oldAnimations := []Animation{} if val := view.getRaw(AnimationTag); val != nil { if animation, ok := val.([]Animation); ok { oldAnimations = animation } } if !setAnimationProperty(view, tag, value) { return false } for _, animation := range oldAnimations { animation.unused(view.session) } changedTags = []PropertyName{AnimationTag} default: changedTags = viewSet(view, tag, value) } if view.created && len(changedTags) > 0 { for _, tag := range changedTags { view.changed(view, 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) propertyChangedEvent(tag PropertyName) { if listener, ok := view.changeListener[tag]; ok { listener(view, tag) } switch tag { case BorderLeft, BorderRight, BorderTop, BorderBottom, BorderStyle, BorderLeftStyle, BorderRightStyle, BorderTopStyle, BorderBottomStyle, BorderColor, BorderLeftColor, BorderRightColor, BorderTopColor, BorderBottomColor, BorderWidth, BorderLeftWidth, BorderRightWidth, BorderTopWidth, BorderBottomWidth: tag = Border case CellBorderStyle, CellBorderColor, CellBorderWidth, CellBorderLeft, CellBorderLeftStyle, CellBorderLeftColor, CellBorderLeftWidth, CellBorderRight, CellBorderRightStyle, CellBorderRightColor, CellBorderRightWidth, CellBorderTop, CellBorderTopStyle, CellBorderTopColor, CellBorderTopWidth, CellBorderBottom, CellBorderBottomStyle, CellBorderBottomColor, CellBorderBottomWidth: tag = CellBorder case OutlineColor, OutlineStyle, OutlineWidth: tag = Outline case RadiusX, RadiusY, RadiusTopLeft, RadiusTopLeftX, RadiusTopLeftY, RadiusTopRight, RadiusTopRightX, RadiusTopRightY, RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY, RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY: tag = Radius case MarginTop, MarginRight, MarginBottom, MarginLeft, "top-margin", "right-margin", "bottom-margin", "left-margin": tag = Margin case PaddingTop, PaddingRight, PaddingBottom, PaddingLeft, "top-padding", "right-padding", "bottom-padding", "left-padding": tag = Padding case CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft: tag = CellPadding case ColumnSeparatorStyle, ColumnSeparatorWidth, ColumnSeparatorColor: tag = ColumnSeparator default: return } if listener, ok := view.changeListener[tag]; ok { listener(view, tag) } } */ func viewRemove(properties Properties, tag PropertyName) []PropertyName { return viewStyleRemove(properties, tag) } func viewSet(view View, tag PropertyName, value any) []PropertyName { switch tag { 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 setNoParamEventListener[View](view, tag, value) case KeyDownEvent, KeyUpEvent: return setViewEventListener[View, KeyEvent](view, tag, value) case ClickEvent, DoubleClickEvent, MouseDown, MouseUp, MouseMove, MouseOut, MouseOver, ContextMenuEvent: return setViewEventListener[View, MouseEvent](view, tag, value) case PointerDown, PointerUp, PointerMove, PointerOut, PointerOver, PointerCancel: return setViewEventListener[View, PointerEvent](view, tag, value) case TouchStart, TouchEnd, TouchMove, TouchCancel: return setViewEventListener[View, TouchEvent](view, tag, value) case TransitionRunEvent, TransitionStartEvent, TransitionEndEvent, TransitionCancelEvent, AnimationStartEvent, AnimationEndEvent, AnimationIterationEvent, AnimationCancelEvent: return setViewEventListener[View, string](view, tag, value) //return setTransitionListener(view, tag, value), tag //return setAnimationListener(view, tag, value), tag case ResizeEvent, ScrollEvent: return setViewEventListener[View, Frame](view, tag, value) //return setFrameListener(view, tag, value), tag } 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 viewPropertyChanged(view View, tag PropertyName) { /* if view.updateTransformProperty(tag) { return } */ 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 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 := getClipShape(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 := getClipShape(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.(ViewFilter); 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.(ViewFilter); 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} { viewPropertyChanged(view, tag2) } case Transition: session.updateCSSProperty(htmlID, "transition", transitionCSS(view, session)) case AnimationTag: 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: if getTransform3D(view, session) { x, y := GetPerspectiveOrigin(view) value := "" if x.Type != Auto || y.Type != Auto { value = x.cssString("50%", session) + " " + y.cssString("50%", session) } session.updateCSSProperty(htmlID, "perspective-origin", value) } case BackfaceVisible: if getTransform3D(view, session) { if GetBackfaceVisible(view) { session.updateCSSProperty(htmlID, string(BackfaceVisible), "visible") } else { session.updateCSSProperty(htmlID, string(BackfaceVisible), "hidden") } } case OriginX, OriginY, OriginZ: x, y, z := getOrigin(view, session) value := "" if z.Type != Auto { value = x.cssString("50%", session) + " " + y.cssString("50%", session) + " " + z.cssString("50%", session) } else if x.Type != Auto || y.Type != Auto { value = x.cssString("50%", session) + " " + y.cssString("50%", session) } session.updateCSSProperty(htmlID, "transform-origin", value) case TransformTag, Perspective, SkewX, SkewY, TranslateX, TranslateY, TranslateZ, ScaleX, ScaleY, ScaleZ, Rotate, RotateX, RotateY, RotateZ: css := "" if transform := getTransformProperty(view); transform != nil { css = transform.transformCSS(session) } session.updateCSSProperty(htmlID, "transform", css) 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 viewGet(view View, tag PropertyName) any { if tag == ID { if id := view.ID(); id != "" { return id } else { return nil } } return viewStyleGet(view, tag) } 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) { viewHTMLTag := view.htmlTag() buffer.WriteRune('<') buffer.WriteString(viewHTMLTag) 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(`') } } 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 getNoParamEventListeners[View](view, nil, command) { listener(self) } case LostFocusEvent: view.hasFocus = false for _, listener := range getNoParamEventListeners[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 }